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

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

when getting 5xx errors from a tracker, keep increasing the intervals between retries, instead of hammering the tracker at constant intervals. thanks to mape for this suggestion.

  • Property svn:keywords set to Date Rev Author Id
File size: 13.0 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 6251 2008-06-24 21:39:07Z 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 66
35#define UNUSED_INTERVAL_MSEC 100
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    tv = tr_timevalMsec( interval );
125    evtimer_add( &server->timer, &tv );
126}
127
128static void
129getPasswordFile( tr_rpc_server * server, char * buf, int buflen )
130{
131    tr_buildPath( buf, buflen, tr_sessionGetConfigDir( server->session ),
132                               "htpasswd",
133                               NULL );
134}
135
136static void
137startServer( tr_rpc_server * server )
138{
139    dbgmsg( "in startServer; current context is %p", server->ctx );
140
141    if( !server->ctx )
142    {
143        char ports[128];
144        char passwd[MAX_PATH_LENGTH];
145        char clutchDir[MAX_PATH_LENGTH];
146        char * clutchAlias;
147        struct timeval tv = tr_timevalMsec( UNUSED_INTERVAL_MSEC );
148
149        tr_buildPath( clutchDir, sizeof( clutchDir ), tr_sessionGetConfigDir( server->session ), "clutch", NULL );
150        clutchAlias = tr_strdup_printf( "%s=%s", "/transmission/clutch", clutchDir );
151
152        getPasswordFile( server, passwd, sizeof( passwd ) );
153        if( !server->isPasswordEnabled )
154            unlink( passwd );
155        else
156            edit_passwords( passwd, MY_REALM, server->username, server->password );
157
158        server->ctx = shttpd_init( );
159        snprintf( ports, sizeof( ports ), "%d", server->port );
160        shttpd_register_uri( server->ctx, "/transmission/rpc", handle_rpc, server );
161        shttpd_set_option(server->ctx, "aliases", clutchAlias );
162        shttpd_set_option( server->ctx, "ports", ports );
163        shttpd_set_option( server->ctx, "dir_list", "0" );
164        //shttpd_set_option( server->ctx, "root", "/dev/null" );
165        shttpd_set_option( server->ctx, "auth_realm", MY_REALM );
166        if( server->acl ) {
167            dbgmsg( "setting acl [%s]", server->acl );
168            shttpd_set_option( server->ctx, "acl", server->acl );
169        }
170        if( server->isPasswordEnabled ) {
171            char * buf = tr_strdup_printf( "/transmission/rpc=%s", passwd );
172            shttpd_set_option( server->ctx, "protect", buf );
173            tr_free( buf );
174        }
175
176        evtimer_set( &server->timer, rpcPulse, server );
177        evtimer_add( &server->timer, &tv );
178
179        tr_free( clutchAlias );
180    }
181}
182
183static void
184stopServer( tr_rpc_server * server )
185{
186    if( server->ctx )
187    {
188        char passwd[MAX_PATH_LENGTH];
189        getPasswordFile( server, passwd, sizeof( passwd ) );
190        unlink( passwd );
191
192        evtimer_del( &server->timer );
193        shttpd_fini( server->ctx );
194        server->ctx = NULL;
195    }
196}
197
198void
199tr_rpcSetEnabled( tr_rpc_server * server, int isEnabled )
200{
201    if( !isEnabled && server->ctx )
202        stopServer( server );
203
204    if( isEnabled && !server->ctx )
205        startServer( server );
206}
207
208int
209tr_rpcIsEnabled( const tr_rpc_server * server )
210{
211    return server->ctx != NULL;
212}
213
214void
215tr_rpcSetPort( tr_rpc_server * server, int port )
216{
217    if( server->port != port )
218    {
219        server->port = port;
220
221        if( server->ctx )
222        {
223            stopServer( server );
224            startServer( server );
225        }
226    }
227}
228
229int
230tr_rpcGetPort( const tr_rpc_server * server )
231{
232    return server->port;
233}
234
235/****
236*****  ACL
237****/
238
239/*
240 * DELIM_CHARS, FOR_EACH_WORD_IN_LIST, isbyte, and testACL are from, or modified from,
241 * shttpd, written by Sergey Lyubka under this license:
242 * "THE BEER-WARE LICENSE" (Revision 42):
243 * Sergey Lyubka wrote this file.  As long as you retain this notice you
244 * can do whatever you want with this stuff. If we meet some day, and you think
245 * this stuff is worth it, you can buy me a beer in return.
246 */
247
248#define  DELIM_CHARS "," /* Separators for lists */
249
250#define FOR_EACH_WORD_IN_LIST(s,len)                                    \
251        for (; s != NULL && (len = strcspn(s, DELIM_CHARS)) != 0;       \
252                        s += len, s+= strspn(s, DELIM_CHARS))
253
254static int isbyte(int n) { return (n >= 0 && n <= 255); }
255
256static char*
257testACL( const char * s )
258{
259    int len;
260
261    FOR_EACH_WORD_IN_LIST(s, len)
262    {
263
264        char flag;
265        int  a, b, c, d, n, mask;
266
267        if( sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5 )
268            return tr_strdup_printf( _( "[%s]: subnet must be [+|-]x.x.x.x[/x]" ), s );
269        if( flag != '+' && flag != '-')
270            return tr_strdup_printf( _( "[%s]: flag must be + or -" ), s );
271        if( !isbyte(a) || !isbyte(b) || !isbyte(c) || !isbyte(d) )
272            return tr_strdup_printf( _( "[%s]: bad ip address" ), s );
273        if( sscanf(s + n, "/%d", &mask) == 1 && ( mask<0 || mask>32 ) )
274            return tr_strdup_printf( _( "[%s]: bad subnet mask %d" ), s, n );
275    }
276
277    return NULL;
278}
279
280/* 192.*.*.* --> 192.0.0.0/8
281   192.64.*.* --> 192.64.0.0/16
282   192.64.1.* --> 192.64.1.0/24
283   192.64.1.2 --> 192.64.1.2/32 */
284static void
285cidrizeOne( const char * in, int len, struct evbuffer * out )
286{
287    int stars = 0;
288    const char * pch;
289    const char * end;
290    char zero = '0';
291    char huh = '?';
292
293    for( pch=in, end=pch+len; pch!=end; ++pch ) {
294        if( stars && isdigit(*pch) )
295            evbuffer_add( out, &huh, 1 ); 
296        else if( *pch!='*' )
297            evbuffer_add( out, pch, 1 );
298        else {
299            evbuffer_add( out, &zero, 1 );
300            ++stars;
301        }
302    }
303
304    evbuffer_add_printf( out, "/%d", (32-(stars*8)));
305}
306
307char*
308cidrize( const char * acl )
309{
310    int len;
311    const char * walk = acl;
312    char * ret;
313    struct evbuffer * out = evbuffer_new( );
314
315    FOR_EACH_WORD_IN_LIST( walk, len )
316    {
317        cidrizeOne( walk, len, out );
318        evbuffer_add_printf( out, "," );
319    }
320
321    /* the -1 is to eat the final ", " */
322    ret = tr_strndup( (char*) EVBUFFER_DATA(out), EVBUFFER_LENGTH(out)-1 );
323    evbuffer_free( out );
324    return ret;
325}
326
327int
328tr_rpcTestACL( const tr_rpc_server  * server UNUSED,
329               const char           * acl,
330               char                ** setme_errmsg )
331{
332    int err = 0;
333    char * cidr = cidrize( acl );
334    char * errmsg = testACL( cidr );
335    if( errmsg )
336    {
337        if( setme_errmsg )
338            *setme_errmsg = errmsg;
339        else
340            tr_free( errmsg );
341        err = -1;
342    }
343    tr_free( cidr );
344    return err;
345}
346
347int
348tr_rpcSetACL( tr_rpc_server   * server,
349              const char      * acl,
350              char           ** setme_errmsg )
351{
352    char * cidr = cidrize( acl );
353    const int err = tr_rpcTestACL( server, cidr, setme_errmsg );
354
355    if( !err )
356    {
357        const int isRunning = server->ctx != NULL;
358
359        if( isRunning )
360            stopServer( server );
361
362        tr_free( server->acl );
363        server->acl = tr_strdup( cidr );
364        dbgmsg( "setting our ACL to [%s]", server->acl );
365
366        if( isRunning )
367            startServer( server );
368    }
369    tr_free( cidr );
370
371    return err;
372}
373
374char*
375tr_rpcGetACL( const tr_rpc_server * server )
376{
377    return tr_strdup( server->acl ? server->acl : "" );
378}
379
380/****
381*****  PASSWORD
382****/
383
384void
385tr_rpcSetUsername( tr_rpc_server        * server,
386                   const char           * username )
387{
388    const int isRunning = server->ctx != NULL;
389
390    if( isRunning )
391        stopServer( server );
392
393    tr_free( server->username );
394    server->username = tr_strdup( username );
395    dbgmsg( "setting our Username to [%s]", server->username );
396
397    if( isRunning )
398        startServer( server );
399}
400
401char*
402tr_rpcGetUsername( const tr_rpc_server  * server )
403{
404    return tr_strdup( server->username ? server->username : "" );
405}
406
407void
408tr_rpcSetPassword( tr_rpc_server        * server,
409                   const char           * password )
410{
411    const int isRunning = server->ctx != NULL;
412
413    if( isRunning )
414        stopServer( server );
415
416    tr_free( server->password );
417    server->password = tr_strdup( password );
418    dbgmsg( "setting our Password to [%s]", server->password );
419
420    if( isRunning )
421        startServer( server );
422}
423
424char*
425tr_rpcGetPassword( const tr_rpc_server  * server )
426{
427    return tr_strdup( server->password ? server->password : "" );
428}
429
430void
431tr_rpcSetPasswordEnabled( tr_rpc_server  * server,
432                          int              isEnabled )
433{
434    const int isRunning = server->ctx != NULL;
435
436    if( isRunning )
437        stopServer( server );
438
439    server->isPasswordEnabled = isEnabled;
440    dbgmsg( "setting 'password enabled' to %d", isEnabled );
441
442    if( isRunning )
443        startServer( server );
444}
445
446int
447tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
448{
449    return server->isPasswordEnabled;
450}
451
452/****
453*****  LIFE CYCLE
454****/
455
456void
457tr_rpcClose( tr_rpc_server ** ps )
458{
459    tr_rpc_server * s = *ps;
460    *ps = NULL;
461
462    stopServer( s );
463    evbuffer_free( s->in );
464    evbuffer_free( s->out );
465    tr_free( s->acl );
466    tr_free( s );
467}
468
469tr_rpc_server *
470tr_rpcInit( tr_handle   * session,
471            int           isEnabled,
472            int           port,
473            const char  * acl,
474            int           isPasswordEnabled,
475            const char  * username,
476            const char  * password )
477{
478    char * errmsg;
479    tr_rpc_server * s;
480
481    if(( errmsg = testACL ( acl )))
482    {
483        tr_nerr( MY_NAME, errmsg );
484        tr_free( errmsg );
485        acl = TR_DEFAULT_RPC_ACL;
486        tr_nerr( MY_NAME, "using fallback ACL \"%s\"", acl );
487    }
488
489    s = tr_new0( tr_rpc_server, 1 );
490    s->session = session;
491    s->port = port;
492    s->in = evbuffer_new( );
493    s->out = evbuffer_new( );
494    s->acl = tr_strdup( acl );
495    s->username = tr_strdup( username );
496    s->password = tr_strdup( password );
497    s->isPasswordEnabled = isPasswordEnabled;
498   
499    if( isEnabled )
500        startServer( s );
501    return s;   
502}
Note: See TracBrowser for help on using the repository browser.