source: trunk/libtransmission/rpc-server.c @ 6083

Last change on this file since 6083 was 6083, checked in by livings124, 14 years ago

fix minor memory leak when setting the rpc acl

  • Property svn:keywords set to Date Rev Author Id
File size: 12.6 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: rpc-server.c 6083 2008-06-08 04:23:56Z livings124 $
11 */
12
13#include <assert.h>
14#include <ctype.h> /* isdigit */
15#include <errno.h>
16#include <stdlib.h> /* strtol */
17#include <string.h>
18
19#include <unistd.h> /* unlink */
20
21#include <libevent/event.h>
22#include <shttpd/defs.h> /* edit_passwords */
23#include <shttpd/shttpd.h>
24
25#include "transmission.h"
26#include "rpc.h"
27#include "rpc-server.h"
28#include "utils.h"
29
30#define MY_NAME "RPC Server"
31#define MY_REALM "Transmission RPC Server"
32
33#define BUSY_INTERVAL_MSEC 30
34#define IDLE_INTERVAL_MSEC 100
35#define UNUSED_INTERVAL_MSEC 1000
36
37struct tr_rpc_server
38{
39    int port;
40    time_t lastRequestTime;
41    struct shttpd_ctx * ctx;
42    tr_handle * session;
43    struct evbuffer * in;
44    struct evbuffer * out;
45    struct event timer;
46    int isPasswordEnabled;
47    char * username;
48    char * password;
49    char * acl;
50};
51
52#define dbgmsg(fmt...) tr_deepLog(__FILE__, __LINE__, MY_NAME, ##fmt )
53
54static void
55handle_rpc( struct shttpd_arg * arg )
56{
57    struct tr_rpc_server * s = arg->user_data;
58
59    if( !EVBUFFER_LENGTH( s->out ) )
60    {
61        int len = 0;
62        char * response = NULL;
63        const char * request_method = shttpd_get_env( arg, "REQUEST_METHOD" );
64        const char * query_string = shttpd_get_env( arg, "QUERY_STRING" );
65
66        if( query_string && *query_string )
67            response = tr_rpc_request_exec_uri( s->session,
68                                                query_string,
69                                                strlen( query_string ),
70                                                &len );
71        else if( !strcmp( request_method, "POST" ) )
72        {
73            evbuffer_add( s->in, arg->in.buf, arg->in.len );
74            arg->in.num_bytes = arg->in.len;
75            if( arg->flags & SHTTPD_MORE_POST_DATA )
76                return;
77            response = tr_rpc_request_exec_json( s->session,
78                                                 EVBUFFER_DATA( s->in ),
79                                                 EVBUFFER_LENGTH( s->in ),
80                                                 &len );
81            evbuffer_drain( s->in, EVBUFFER_LENGTH( s->in ) );
82        }
83
84        evbuffer_add_printf( s->out, "HTTP/1.1 200 OK\r\n"
85                                     "Content-Type: application/json\r\n"
86                                     "Content-Length: %d\r\n"
87                                     "\r\n"
88                                     "%*.*s\r\n", len, len, len, response );
89        tr_free( response );
90    }
91
92    if( EVBUFFER_LENGTH( s->out ) )
93    {
94        const int n = MIN( ( int )EVBUFFER_LENGTH( s->out ), arg->out.len );
95        memcpy( arg->out.buf, EVBUFFER_DATA( s->out ), n );
96        evbuffer_drain( s->out, n );
97        arg->out.num_bytes = n;
98    }
99
100    if( !EVBUFFER_LENGTH( s->out ) )
101        arg->flags |= SHTTPD_END_OF_OUTPUT;
102}
103
104static void
105rpcPulse( int socket UNUSED, short action UNUSED, void * vserver )
106{
107    int interval;
108    struct timeval tv;
109    tr_rpc_server * server = vserver;
110    const time_t now = time( NULL );
111
112    assert( server );
113
114    shttpd_poll( server->ctx, 1 );
115
116    /* set a timer for the next pulse */
117    if( EVBUFFER_LENGTH( server->in ) || EVBUFFER_LENGTH( server->out ) ) {
118        interval = BUSY_INTERVAL_MSEC;
119        server->lastRequestTime = now;
120    } else if( now - server->lastRequestTime < 300 ) {
121        interval = IDLE_INTERVAL_MSEC;
122    } else {
123        interval = UNUSED_INTERVAL_MSEC;
124    }
125    tv = tr_timevalMsec( interval );
126    evtimer_add( &server->timer, &tv );
127}
128
129static void
130getPasswordFile( tr_rpc_server * server, char * buf, int buflen )
131{
132    tr_buildPath( buf, buflen, tr_sessionGetConfigDir( server->session ),
133                               "htpasswd",
134                               NULL );
135}
136
137static void
138startServer( tr_rpc_server * server )
139{
140    dbgmsg( "in startServer; current context is %p", server->ctx );
141
142    if( !server->ctx )
143    {
144        char ports[128];
145        char passwd[MAX_PATH_LENGTH];
146        struct timeval tv = tr_timevalMsec( UNUSED_INTERVAL_MSEC );
147
148        getPasswordFile( server, passwd, sizeof( passwd ) );
149        if( !server->isPasswordEnabled )
150            unlink( passwd );
151        else
152            edit_passwords( passwd, MY_REALM, server->username, server->password );
153
154        server->ctx = shttpd_init( );
155        snprintf( ports, sizeof( ports ), "%d", server->port );
156        shttpd_register_uri( server->ctx, "/transmission", handle_rpc, server );
157        shttpd_set_option( server->ctx, "ports", ports );
158        shttpd_set_option( server->ctx, "dir_list", "0" );
159        shttpd_set_option( server->ctx, "root", "/dev/null" );
160        shttpd_set_option( server->ctx, "auth_realm", MY_REALM );
161        if( server->acl ) {
162            dbgmsg( "setting acl [%s]", server->acl );
163            shttpd_set_option( server->ctx, "acl", server->acl );
164        }
165        if( server->isPasswordEnabled ) {
166            char * buf = tr_strdup_printf( "/transmission=%s", passwd );
167            shttpd_set_option( server->ctx, "protect", buf );
168            tr_free( buf );
169        }
170
171        evtimer_set( &server->timer, rpcPulse, server );
172        evtimer_add( &server->timer, &tv );
173    }
174}
175
176static void
177stopServer( tr_rpc_server * server )
178{
179    if( server->ctx )
180    {
181        char passwd[MAX_PATH_LENGTH];
182        getPasswordFile( server, passwd, sizeof( passwd ) );
183        unlink( passwd );
184
185        evtimer_del( &server->timer );
186        shttpd_fini( server->ctx );
187        server->ctx = NULL;
188    }
189}
190
191void
192tr_rpcSetEnabled( tr_rpc_server * server, int isEnabled )
193{
194    if( !isEnabled && server->ctx )
195        stopServer( server );
196
197    if( isEnabled && !server->ctx )
198        startServer( server );
199}
200
201int
202tr_rpcIsEnabled( const tr_rpc_server * server )
203{
204    return server->ctx != NULL;
205}
206
207void
208tr_rpcSetPort( tr_rpc_server * server, int port )
209{
210    if( server->port != port )
211    {
212        server->port = port;
213
214        if( server->ctx )
215        {
216            stopServer( server );
217            startServer( server );
218        }
219    }
220}
221
222int
223tr_rpcGetPort( const tr_rpc_server * server )
224{
225    return server->port;
226}
227
228/****
229*****  ACL
230****/
231
232/*
233 * DELIM_CHARS, FOR_EACH_WORD_IN_LIST, isbyte, and testACL are from, or modified from,
234 * shttpd, written by Sergey Lyubka under this license:
235 * "THE BEER-WARE LICENSE" (Revision 42):
236 * Sergey Lyubka wrote this file.  As long as you retain this notice you
237 * can do whatever you want with this stuff. If we meet some day, and you think
238 * this stuff is worth it, you can buy me a beer in return.
239 */
240
241#define  DELIM_CHARS " ," /* Separators for lists */
242
243#define FOR_EACH_WORD_IN_LIST(s,len)                                    \
244        for (; s != NULL && (len = strcspn(s, DELIM_CHARS)) != 0;       \
245                        s += len, s+= strspn(s, DELIM_CHARS))
246
247static int isbyte(int n) { return (n >= 0 && n <= 255); }
248
249static char*
250testACL( const char * s )
251{
252    int len;
253
254    FOR_EACH_WORD_IN_LIST(s, len)
255    {
256
257        char flag;
258        int  a, b, c, d, n, mask;
259
260        if( sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5 )
261            return tr_strdup_printf( _( "[%s]: subnet must be [+|-]x.x.x.x[/x]" ), s );
262        if( flag != '+' && flag != '-')
263            return tr_strdup_printf( _( "[%s]: flag must be + or -" ), s );
264        if( !isbyte(a) || !isbyte(b) || !isbyte(c) || !isbyte(d) )
265            return tr_strdup_printf( _( "[%s]: bad ip address" ), s );
266        if( sscanf(s + n, "/%d", &mask) == 1 && ( mask<0 || mask>32 ) )
267            return tr_strdup_printf( _( "[%s]: bad subnet mask %d" ), s, n );
268    }
269
270    return NULL;
271}
272
273/* 192.*.*.* --> 192.0.0.0/8
274   192.64.*.* --> 192.64.0.0/16
275   192.64.1.* --> 192.64.1.0/24
276   192.64.1.2 --> 192.64.1.2/32 */
277static void
278cidrizeOne( const char * in, int len, struct evbuffer * out )
279{
280    int stars = 0;
281    const char * pch;
282    const char * end;
283    char zero = '0';
284    char huh = '?';
285
286    for( pch=in, end=pch+len; pch!=end; ++pch ) {
287        if( stars && isdigit(*pch) )
288            evbuffer_add( out, &huh, 1 ); 
289        else if( *pch!='*' )
290            evbuffer_add( out, pch, 1 );
291        else {
292            evbuffer_add( out, &zero, 1 );
293            ++stars;
294        }
295    }
296
297    evbuffer_add_printf( out, "/%d", (32-(stars*8)));
298}
299
300char*
301cidrize( const char * acl )
302{
303    int len;
304    const char * walk = acl;
305    char * ret;
306    struct evbuffer * out = evbuffer_new( );
307
308    FOR_EACH_WORD_IN_LIST( walk, len )
309    {
310        cidrizeOne( walk, len, out );
311        evbuffer_add_printf( out, ", " );
312    }
313
314    /* the -2 is to eat the final ", " */
315    ret = tr_strndup( (char*) EVBUFFER_DATA(out), EVBUFFER_LENGTH(out)-2 );
316    evbuffer_free( out );
317    return ret;
318}
319
320int
321tr_rpcTestACL( const tr_rpc_server  * server UNUSED,
322               const char           * acl,
323               char                ** setme_errmsg )
324{
325    int err = 0;
326    char * cidr = cidrize( acl );
327    char * errmsg = testACL( cidr );
328    if( errmsg )
329    {
330        if( setme_errmsg )
331            *setme_errmsg = errmsg;
332        else
333            tr_free( errmsg );
334        err = -1;
335    }
336    tr_free( cidr );
337    return err;
338}
339
340int
341tr_rpcSetACL( tr_rpc_server   * server,
342              const char      * acl,
343              char           ** setme_errmsg )
344{
345    char * cidr = cidrize( acl );
346    const int err = tr_rpcTestACL( server, cidr, setme_errmsg );
347
348    if( !err )
349    {
350        const int isRunning = server->ctx != NULL;
351
352        if( isRunning )
353            stopServer( server );
354
355        tr_free( server->acl );
356        server->acl = tr_strdup( cidr );
357        dbgmsg( "setting our ACL to [%s]", server->acl );
358
359        if( isRunning )
360            startServer( server );
361    }
362    tr_free( cidr );
363
364    return err;
365}
366
367char*
368tr_rpcGetACL( const tr_rpc_server * server )
369{
370    return tr_strdup( server->acl ? server->acl : "" );
371}
372
373/****
374*****  PASSWORD
375****/
376
377void
378tr_rpcSetUsername( tr_rpc_server        * server,
379                   const char           * username )
380{
381    const int isRunning = server->ctx != NULL;
382
383    if( isRunning )
384        stopServer( server );
385
386    tr_free( server->username );
387    server->username = tr_strdup( username );
388    dbgmsg( "setting our Username to [%s]", server->username );
389
390    if( isRunning )
391        startServer( server );
392}
393
394char*
395tr_rpcGetUsername( const tr_rpc_server  * server )
396{
397    return tr_strdup( server->username ? server->username : "" );
398}
399
400void
401tr_rpcSetPassword( tr_rpc_server        * server,
402                   const char           * password )
403{
404    const int isRunning = server->ctx != NULL;
405
406    if( isRunning )
407        stopServer( server );
408
409    tr_free( server->password );
410    server->password = tr_strdup( password );
411    dbgmsg( "setting our Password to [%s]", server->password );
412
413    if( isRunning )
414        startServer( server );
415}
416
417char*
418tr_rpcGetPassword( const tr_rpc_server  * server )
419{
420    return tr_strdup( server->password ? server->password : "" );
421}
422
423void
424tr_rpcSetPasswordEnabled( tr_rpc_server  * server,
425                          int              isEnabled )
426{
427    const int isRunning = server->ctx != NULL;
428
429    if( isRunning )
430        stopServer( server );
431
432    server->isPasswordEnabled = isEnabled;
433    dbgmsg( "setting 'password enabled' to %d", isEnabled );
434
435    if( isRunning )
436        startServer( server );
437}
438
439int
440tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
441{
442    return server->isPasswordEnabled;
443}
444
445/****
446*****  LIFE CYCLE
447****/
448
449void
450tr_rpcClose( tr_rpc_server ** ps )
451{
452    tr_rpc_server * s = *ps;
453    *ps = NULL;
454
455    stopServer( s );
456    evbuffer_free( s->in );
457    evbuffer_free( s->out );
458    tr_free( s->acl );
459    tr_free( s );
460}
461
462tr_rpc_server *
463tr_rpcInit( tr_handle   * session,
464            int           isEnabled,
465            int           port,
466            const char  * acl,
467            int           isPasswordEnabled,
468            const char  * username,
469            const char  * password )
470{
471    char * errmsg;
472    tr_rpc_server * s;
473
474    if(( errmsg = testACL ( acl )))
475    {
476        tr_nerr( MY_NAME, errmsg );
477        tr_free( errmsg );
478        acl = TR_DEFAULT_RPC_ACL;
479        tr_nerr( MY_NAME, "using fallback ACL \"%s\"", acl );
480    }
481
482    s = tr_new0( tr_rpc_server, 1 );
483    s->session = session;
484    s->port = port;
485    s->in = evbuffer_new( );
486    s->out = evbuffer_new( );
487    s->acl = tr_strdup( acl );
488    s->username = tr_strdup( username );
489    s->password = tr_strdup( password );
490    s->isPasswordEnabled = isPasswordEnabled;
491   
492    if( isEnabled )
493        startServer( s );
494    return s;   
495}
Note: See TracBrowser for help on using the repository browser.