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

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

(rpc) get torrent uploads partially working.

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