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

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

(rpc) fix possible password authentication error reported by kman

  • Property svn:keywords set to Date Rev Author Id
File size: 17.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 6337 2008-07-15 20:39:50Z 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 "bencode.h"
27#include "platform.h"
28#include "rpc.h"
29#include "rpc-server.h"
30#include "utils.h"
31
32#define MY_NAME "RPC Server"
33#define MY_REALM "Transmission RPC Server"
34
35#define BUSY_INTERVAL_MSEC 30
36#define IDLE_INTERVAL_MSEC 66
37#define UNUSED_INTERVAL_MSEC 100
38
39struct tr_rpc_server
40{
41    int port;
42    time_t lastRequestTime;
43    struct shttpd_ctx * ctx;
44    tr_handle * session;
45    struct evbuffer * in;
46    struct evbuffer * out;
47    struct event timer;
48    int isPasswordEnabled;
49    char * username;
50    char * password;
51    char * acl;
52};
53
54#define dbgmsg(fmt...) tr_deepLog(__FILE__, __LINE__, MY_NAME, ##fmt )
55
56static const char*
57tr_memmem( const char * s1, size_t l1,
58           const char * s2, size_t l2 )
59{
60        if (!l2) return s1;
61        while (l1 >= l2) {
62                l1--;
63                if (!memcmp(s1,s2,l2))
64                        return s1;
65                s1++;
66        }
67        return NULL;
68}
69
70static void
71handle_upload( struct shttpd_arg * arg )
72{
73    struct tr_rpc_server * s = arg->user_data;
74    s->lastRequestTime = time( NULL );
75
76    /* if we haven't parsed the POST, do that now */
77    if( !EVBUFFER_LENGTH( s->out ) )
78    {
79        /* if we haven't finished reading the POST, read more now */
80        evbuffer_add( s->in, arg->in.buf, arg->in.len );
81        arg->in.num_bytes = arg->in.len;
82        if( arg->flags & SHTTPD_MORE_POST_DATA )
83            return;
84
85        const char * query_string = shttpd_get_env( arg, "QUERY_STRING" );
86        const char * content_type = shttpd_get_header( arg, "Content-Type" );
87        const char * delim;
88        const char * in = (const char *) EVBUFFER_DATA( s->in );
89        size_t inlen = EVBUFFER_LENGTH( s->in );
90        char * boundary = tr_strdup_printf( "--%s", strstr( content_type, "boundary=" ) + strlen( "boundary=" ) );
91        const size_t boundary_len = strlen( boundary );
92        char buf[64];
93        int paused = ( query_string != NULL )
94                  && ( shttpd_get_var( "paused", query_string, strlen( query_string ), buf, sizeof( buf ) ) == 4 )
95                  && ( !strcmp( buf, "true" ) );
96
97        delim = tr_memmem( in, inlen, boundary, boundary_len );
98        if( delim ) do
99        {
100            size_t part_len;
101            const char * part = delim + boundary_len;
102            inlen -= ( part - in );
103            in = part;
104            delim = tr_memmem( in, inlen, boundary, boundary_len );
105            part_len = delim ? (size_t)(delim-part) : inlen;
106
107            if( part_len )
108            {
109                char * text = tr_strndup( part, part_len );
110                if( strstr( text, "filename=\"" ) )
111                {
112                    const char * body = strstr( text, "\r\n\r\n" );
113                    if( body )
114                    {
115                        char * b64, *json, *freeme;
116                        int json_len;
117                        size_t body_len;
118                        tr_benc top, *args;
119
120                        body += 4;
121                        body_len = part_len - ( body - text );
122                        if( body_len >= 2 && !memcmp(&body[body_len-2],"\r\n",2) )
123                            body_len -= 2;
124
125                        tr_bencInitDict( &top, 2 );
126                        args = tr_bencDictAddDict( &top, "arguments", 2 );
127                        tr_bencDictAddStr( &top, "method", "torrent-add" );
128                        b64 = tr_base64_encode( body, body_len, NULL );
129                        tr_bencDictAddStr( args, "metainfo", b64 );
130                        tr_bencDictAddInt( args, "paused", paused );
131                        json = tr_bencSaveAsJSON( &top, &json_len );
132                        freeme = tr_rpc_request_exec_json( s->session, json, json_len, NULL );
133
134                        tr_free( freeme );
135                        tr_free( json );
136                        tr_free( b64 );
137                        tr_bencFree( &top );
138                    }
139                }
140                tr_free( text );
141            }
142        }
143        while( delim );
144
145        evbuffer_drain( s->in, EVBUFFER_LENGTH( s->in ) );
146        tr_free( boundary );
147
148        {
149            /* use xml here because json responses to file uploads is trouble.
150             * see http://www.malsup.com/jquery/form/#sample7 for details */
151            const char * response = "<result>success</result>";
152            const int len = strlen( response );
153            evbuffer_add_printf( s->out, "HTTP/1.1 200 OK\r\n"
154                                         "Content-Type: text/xml\r\n"
155                                         "Content-Length: %d\r\n"
156                                         "\r\n"
157                                         "%s\r\n", len, response );
158        }
159    }
160
161    if( EVBUFFER_LENGTH( s->out ) )
162    {
163        const int n = MIN( ( int )EVBUFFER_LENGTH( s->out ), arg->out.len );
164        memcpy( arg->out.buf, EVBUFFER_DATA( s->out ), n );
165        evbuffer_drain( s->out, n );
166        arg->out.num_bytes = n;
167    }
168
169    if( !EVBUFFER_LENGTH( s->out ) )
170        arg->flags |= SHTTPD_END_OF_OUTPUT;
171}
172
173static void
174handle_rpc( struct shttpd_arg * arg )
175{
176    struct tr_rpc_server * s = arg->user_data;
177    s->lastRequestTime = time( NULL );
178
179    if( !EVBUFFER_LENGTH( s->out ) )
180    {
181        int len = 0;
182        char * response = NULL;
183        const char * request_method = shttpd_get_env( arg, "REQUEST_METHOD" );
184        const char * query_string = shttpd_get_env( arg, "QUERY_STRING" );
185
186        if( query_string && *query_string )
187            response = tr_rpc_request_exec_uri( s->session,
188                                                query_string,
189                                                strlen( query_string ),
190                                                &len );
191        else if( !strcmp( request_method, "POST" ) )
192        {
193            evbuffer_add( s->in, arg->in.buf, arg->in.len );
194            arg->in.num_bytes = arg->in.len;
195            if( arg->flags & SHTTPD_MORE_POST_DATA )
196                return;
197            response = tr_rpc_request_exec_json( s->session,
198                                                 EVBUFFER_DATA( s->in ),
199                                                 EVBUFFER_LENGTH( s->in ),
200                                                 &len );
201            evbuffer_drain( s->in, EVBUFFER_LENGTH( s->in ) );
202        }
203
204        evbuffer_add_printf( s->out, "HTTP/1.1 200 OK\r\n"
205                                     "Content-Type: application/json\r\n"
206                                     "Content-Length: %d\r\n"
207                                     "\r\n"
208                                     "%*.*s", len, len, len, response );
209        tr_free( response );
210    }
211
212    if( EVBUFFER_LENGTH( s->out ) )
213    {
214        const int n = MIN( ( int )EVBUFFER_LENGTH( s->out ), arg->out.len );
215        memcpy( arg->out.buf, EVBUFFER_DATA( s->out ), n );
216        evbuffer_drain( s->out, n );
217        arg->out.num_bytes = n;
218    }
219
220    if( !EVBUFFER_LENGTH( s->out ) )
221        arg->flags |= SHTTPD_END_OF_OUTPUT;
222}
223
224static void
225rpcPulse( int socket UNUSED, short action UNUSED, void * vserver )
226{
227    int interval;
228    struct timeval tv;
229    tr_rpc_server * server = vserver;
230    const time_t now = time( NULL );
231
232    assert( server );
233
234    if( server->ctx )
235        shttpd_poll( server->ctx, 1 );
236
237    /* set a timer for the next pulse */
238    if( EVBUFFER_LENGTH( server->in ) || EVBUFFER_LENGTH( server->out ) )
239        interval = BUSY_INTERVAL_MSEC;
240    else if( now - server->lastRequestTime < 300 )
241        interval = IDLE_INTERVAL_MSEC;
242    else
243        interval = UNUSED_INTERVAL_MSEC;
244    tv = tr_timevalMsec( interval );
245    evtimer_add( &server->timer, &tv );
246}
247
248static void
249getPasswordFile( tr_rpc_server * server, char * buf, int buflen )
250{
251    tr_buildPath( buf, buflen, tr_sessionGetConfigDir( server->session ),
252                               "htpasswd",
253                               NULL );
254}
255
256static void
257startServer( tr_rpc_server * server )
258{
259    dbgmsg( "in startServer; current context is %p", server->ctx );
260
261    if( !server->ctx )
262    {
263        char ports[128];
264        char passwd[MAX_PATH_LENGTH];
265        const char * clutchDir = tr_getClutchDir( server->session );
266        struct timeval tv = tr_timevalMsec( UNUSED_INTERVAL_MSEC );
267
268        getPasswordFile( server, passwd, sizeof( passwd ) );
269        if( !server->isPasswordEnabled )
270            unlink( passwd );
271        else
272            edit_passwords( passwd, MY_REALM, server->username, server->password );
273
274        server->ctx = shttpd_init( );
275        tr_snprintf( ports, sizeof( ports ), "%d", server->port );
276        shttpd_register_uri( server->ctx, "/transmission/rpc", handle_rpc, server );
277        shttpd_register_uri( server->ctx, "/transmission/upload", handle_upload, server );
278
279        if( clutchDir && *clutchDir ) {
280            char * clutchAlias = tr_strdup_printf( "%s=%s,%s=%s",
281                "/transmission/clutch", clutchDir,
282                "/transmission/web", clutchDir );
283            tr_inf( _( "Serving the web interface files from \"%s\"" ), clutchDir );
284            shttpd_set_option( server->ctx, "aliases", clutchAlias );
285            tr_free( clutchAlias );
286        }
287
288        shttpd_set_option( server->ctx, "ports", ports );
289        shttpd_set_option( server->ctx, "dir_list", "0" );
290        //shttpd_set_option( server->ctx, "root", "/dev/null" );
291        shttpd_set_option( server->ctx, "auth_realm", MY_REALM );
292        if( server->acl ) {
293            dbgmsg( "setting acl [%s]", server->acl );
294            shttpd_set_option( server->ctx, "acl", server->acl );
295        }
296        if( server->isPasswordEnabled ) {
297            char * buf = tr_strdup_printf( "/transmission=%s", passwd );
298            shttpd_set_option( server->ctx, "protect", buf );
299            tr_free( buf );
300        }
301
302        evtimer_set( &server->timer, rpcPulse, server );
303        evtimer_add( &server->timer, &tv );
304    }
305}
306
307static void
308stopServer( tr_rpc_server * server )
309{
310    if( server->ctx )
311    {
312        char passwd[MAX_PATH_LENGTH];
313        getPasswordFile( server, passwd, sizeof( passwd ) );
314        unlink( passwd );
315
316        evtimer_del( &server->timer );
317        shttpd_fini( server->ctx );
318        server->ctx = NULL;
319    }
320}
321
322void
323tr_rpcSetEnabled( tr_rpc_server * server, int isEnabled )
324{
325    if( !isEnabled && server->ctx )
326        stopServer( server );
327
328    if( isEnabled && !server->ctx )
329        startServer( server );
330}
331
332int
333tr_rpcIsEnabled( const tr_rpc_server * server )
334{
335    return server->ctx != NULL;
336}
337
338void
339tr_rpcSetPort( tr_rpc_server * server, int port )
340{
341    if( server->port != port )
342    {
343        server->port = port;
344
345        if( server->ctx )
346        {
347            stopServer( server );
348            startServer( server );
349        }
350    }
351}
352
353int
354tr_rpcGetPort( const tr_rpc_server * server )
355{
356    return server->port;
357}
358
359/****
360*****  ACL
361****/
362
363/*
364 * DELIM_CHARS, FOR_EACH_WORD_IN_LIST, isbyte, and testACL are from, or modified from,
365 * shttpd, written by Sergey Lyubka under this license:
366 * "THE BEER-WARE LICENSE" (Revision 42):
367 * Sergey Lyubka wrote this file.  As long as you retain this notice you
368 * can do whatever you want with this stuff. If we meet some day, and you think
369 * this stuff is worth it, you can buy me a beer in return.
370 */
371
372#define  DELIM_CHARS "," /* Separators for lists */
373
374#define FOR_EACH_WORD_IN_LIST(s,len)                                    \
375        for (; s != NULL && (len = strcspn(s, DELIM_CHARS)) != 0;       \
376                        s += len, s+= strspn(s, DELIM_CHARS))
377
378static int isbyte(int n) { return (n >= 0 && n <= 255); }
379
380static char*
381testACL( const char * s )
382{
383    int len;
384
385    FOR_EACH_WORD_IN_LIST(s, len)
386    {
387
388        char flag;
389        int  a, b, c, d, n, mask;
390
391        if( sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5 )
392            return tr_strdup_printf( _( "[%s]: subnet must be [+|-]x.x.x.x[/x]" ), s );
393        if( flag != '+' && flag != '-')
394            return tr_strdup_printf( _( "[%s]: flag must be + or -" ), s );
395        if( !isbyte(a) || !isbyte(b) || !isbyte(c) || !isbyte(d) )
396            return tr_strdup_printf( _( "[%s]: bad ip address" ), s );
397        if( sscanf(s + n, "/%d", &mask) == 1 && ( mask<0 || mask>32 ) )
398            return tr_strdup_printf( _( "[%s]: bad subnet mask %d" ), s, n );
399    }
400
401    return NULL;
402}
403
404/* 192.*.*.* --> 192.0.0.0/8
405   192.64.*.* --> 192.64.0.0/16
406   192.64.1.* --> 192.64.1.0/24
407   192.64.1.2 --> 192.64.1.2/32 */
408static void
409cidrizeOne( const char * in, int len, struct evbuffer * out )
410{
411    int stars = 0;
412    const char * pch;
413    const char * end;
414    char zero = '0';
415    char huh = '?';
416
417    for( pch=in, end=pch+len; pch!=end; ++pch ) {
418        if( stars && isdigit(*pch) )
419            evbuffer_add( out, &huh, 1 ); 
420        else if( *pch!='*' )
421            evbuffer_add( out, pch, 1 );
422        else {
423            evbuffer_add( out, &zero, 1 );
424            ++stars;
425        }
426    }
427
428    evbuffer_add_printf( out, "/%d", (32-(stars*8)));
429}
430
431char*
432cidrize( const char * acl )
433{
434    int len;
435    const char * walk = acl;
436    char * ret;
437    struct evbuffer * out = evbuffer_new( );
438
439    FOR_EACH_WORD_IN_LIST( walk, len )
440    {
441        cidrizeOne( walk, len, out );
442        evbuffer_add_printf( out, "," );
443    }
444
445    /* the -1 is to eat the final ", " */
446    ret = tr_strndup( (char*) EVBUFFER_DATA(out), EVBUFFER_LENGTH(out)-1 );
447    evbuffer_free( out );
448    return ret;
449}
450
451int
452tr_rpcTestACL( const tr_rpc_server  * server UNUSED,
453               const char           * acl,
454               char                ** setme_errmsg )
455{
456    int err = 0;
457    char * cidr = cidrize( acl );
458    char * errmsg = testACL( cidr );
459    if( errmsg )
460    {
461        if( setme_errmsg )
462            *setme_errmsg = errmsg;
463        else
464            tr_free( errmsg );
465        err = -1;
466    }
467    tr_free( cidr );
468    return err;
469}
470
471int
472tr_rpcSetACL( tr_rpc_server   * server,
473              const char      * acl,
474              char           ** setme_errmsg )
475{
476    char * cidr = cidrize( acl );
477    const int err = tr_rpcTestACL( server, cidr, setme_errmsg );
478
479    if( !err )
480    {
481        const int isRunning = server->ctx != NULL;
482
483        if( isRunning )
484            stopServer( server );
485
486        tr_free( server->acl );
487        server->acl = tr_strdup( cidr );
488        dbgmsg( "setting our ACL to [%s]", server->acl );
489
490        if( isRunning )
491            startServer( server );
492    }
493    tr_free( cidr );
494
495    return err;
496}
497
498char*
499tr_rpcGetACL( const tr_rpc_server * server )
500{
501    return tr_strdup( server->acl ? server->acl : "" );
502}
503
504/****
505*****  PASSWORD
506****/
507
508void
509tr_rpcSetUsername( tr_rpc_server        * server,
510                   const char           * username )
511{
512    const int isRunning = server->ctx != NULL;
513
514    if( isRunning )
515        stopServer( server );
516
517    tr_free( server->username );
518    server->username = tr_strdup( username );
519    dbgmsg( "setting our Username to [%s]", server->username );
520
521    if( isRunning )
522        startServer( server );
523}
524
525char*
526tr_rpcGetUsername( const tr_rpc_server  * server )
527{
528    return tr_strdup( server->username ? server->username : "" );
529}
530
531void
532tr_rpcSetPassword( tr_rpc_server        * server,
533                   const char           * password )
534{
535    const int isRunning = server->ctx != NULL;
536
537    if( isRunning )
538        stopServer( server );
539
540    tr_free( server->password );
541    server->password = tr_strdup( password );
542    dbgmsg( "setting our Password to [%s]", server->password );
543
544    if( isRunning )
545        startServer( server );
546}
547
548char*
549tr_rpcGetPassword( const tr_rpc_server  * server )
550{
551    return tr_strdup( server->password ? server->password : "" );
552}
553
554void
555tr_rpcSetPasswordEnabled( tr_rpc_server  * server,
556                          int              isEnabled )
557{
558    const int isRunning = server->ctx != NULL;
559
560    if( isRunning )
561        stopServer( server );
562
563    server->isPasswordEnabled = isEnabled;
564    dbgmsg( "setting 'password enabled' to %d", isEnabled );
565
566    if( isRunning )
567        startServer( server );
568}
569
570int
571tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
572{
573    return server->isPasswordEnabled;
574}
575
576/****
577*****  LIFE CYCLE
578****/
579
580void
581tr_rpcClose( tr_rpc_server ** ps )
582{
583    tr_rpc_server * s = *ps;
584    *ps = NULL;
585
586    stopServer( s );
587    evbuffer_free( s->in );
588    evbuffer_free( s->out );
589    tr_free( s->acl );
590    tr_free( s );
591}
592
593tr_rpc_server *
594tr_rpcInit( tr_handle   * session,
595            int           isEnabled,
596            int           port,
597            const char  * acl,
598            int           isPasswordEnabled,
599            const char  * username,
600            const char  * password )
601{
602    char * errmsg;
603    tr_rpc_server * s;
604
605    if(( errmsg = testACL ( acl )))
606    {
607        tr_nerr( MY_NAME, errmsg );
608        tr_free( errmsg );
609        acl = TR_DEFAULT_RPC_ACL;
610        tr_nerr( MY_NAME, "using fallback ACL \"%s\"", acl );
611    }
612
613    s = tr_new0( tr_rpc_server, 1 );
614    s->session = session;
615    s->port = port;
616    s->in = evbuffer_new( );
617    s->out = evbuffer_new( );
618    s->acl = tr_strdup( acl );
619    s->username = tr_strdup( username );
620    s->password = tr_strdup( password );
621    s->isPasswordEnabled = isPasswordEnabled;
622   
623    if( isEnabled )
624        startServer( s );
625    return s;   
626}
Note: See TracBrowser for help on using the repository browser.