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

Last change on this file since 6121 was 6121, checked in by charles, 14 years ago

(libT) poll the RPC request queue more frequently if there have been recent requests.

  • 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 6121 2008-06-10 17:13:56Z charles $
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    s->lastRequestTime = time( NULL );
59
60    if( !EVBUFFER_LENGTH( s->out ) )
61    {
62        int len = 0;
63        char * response = NULL;
64        const char * request_method = shttpd_get_env( arg, "REQUEST_METHOD" );
65        const char * query_string = shttpd_get_env( arg, "QUERY_STRING" );
66
67        if( query_string && *query_string )
68            response = tr_rpc_request_exec_uri( s->session,
69                                                query_string,
70                                                strlen( query_string ),
71                                                &len );
72        else if( !strcmp( request_method, "POST" ) )
73        {
74            evbuffer_add( s->in, arg->in.buf, arg->in.len );
75            arg->in.num_bytes = arg->in.len;
76            if( arg->flags & SHTTPD_MORE_POST_DATA )
77                return;
78            response = tr_rpc_request_exec_json( s->session,
79                                                 EVBUFFER_DATA( s->in ),
80                                                 EVBUFFER_LENGTH( s->in ),
81                                                 &len );
82            evbuffer_drain( s->in, EVBUFFER_LENGTH( s->in ) );
83        }
84
85        evbuffer_add_printf( s->out, "HTTP/1.1 200 OK\r\n"
86                                     "Content-Type: application/json\r\n"
87                                     "Content-Length: %d\r\n"
88                                     "\r\n"
89                                     "%*.*s\r\n", len, len, len, response );
90        tr_free( response );
91    }
92
93    if( EVBUFFER_LENGTH( s->out ) )
94    {
95        const int n = MIN( ( int )EVBUFFER_LENGTH( s->out ), arg->out.len );
96        memcpy( arg->out.buf, EVBUFFER_DATA( s->out ), n );
97        evbuffer_drain( s->out, n );
98        arg->out.num_bytes = n;
99    }
100
101    if( !EVBUFFER_LENGTH( s->out ) )
102        arg->flags |= SHTTPD_END_OF_OUTPUT;
103}
104
105static void
106rpcPulse( int socket UNUSED, short action UNUSED, void * vserver )
107{
108    int interval;
109    struct timeval tv;
110    tr_rpc_server * server = vserver;
111    const time_t now = time( NULL );
112
113    assert( server );
114
115    shttpd_poll( server->ctx, 1 );
116
117    /* set a timer for the next pulse */
118    if( EVBUFFER_LENGTH( server->in ) || EVBUFFER_LENGTH( server->out ) ) {
119        interval = BUSY_INTERVAL_MSEC;
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.