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

Last change on this file since 8360 was 8360, checked in by charles, 13 years ago

(trunk libT) #2043: allow static files served by rpc-server to be cached

  • Property svn:keywords set to Date Rev Author Id
File size: 26.2 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 Charles Kerr <charles@transmissionbt.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 8360 2009-05-08 16:41:32Z charles $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <string.h> /* memcpy */
16#include <limits.h> /* INT_MAX */
17
18#include <sys/types.h> /* open */
19#include <sys/stat.h>  /* open */
20#include <fcntl.h>     /* open */
21#include <unistd.h>    /* close */
22
23#ifdef HAVE_ZLIB
24 #include <zlib.h>
25#endif
26
27#include <libevent/event.h>
28#include <libevent/evhttp.h>
29
30#include "transmission.h"
31#include "bencode.h"
32#include "crypto.h"
33#include "list.h"
34#include "platform.h"
35#include "rpcimpl.h"
36#include "rpc-server.h"
37#include "trevent.h"
38#include "utils.h"
39#include "web.h"
40#include "net.h"
41
42/* session-id is used to make cross-site request forgery attacks difficult.
43 * Don't disable this feature unless you really know what you're doing!
44 * http://en.wikipedia.org/wiki/Cross-site_request_forgery
45 * http://shiflett.org/articles/cross-site-request-forgeries
46 * http://www.webappsec.org/lists/websecurity/archive/2008-04/msg00037.html */
47#define REQUIRE_SESSION_ID
48
49#define MY_NAME "RPC Server"
50#define MY_REALM "Transmission"
51#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
52
53#ifdef WIN32
54#define strncasecmp _strnicmp
55#endif
56
57struct tr_rpc_server
58{
59    tr_bool            isEnabled;
60    tr_bool            isPasswordEnabled;
61    tr_bool            isWhitelistEnabled;
62    tr_port            port;
63    struct in_addr     bindAddress;
64    struct evhttp *    httpd;
65    tr_session *       session;
66    char *             username;
67    char *             password;
68    char *             whitelistStr;
69    tr_list *          whitelist;
70
71    char *             sessionId;
72    time_t             sessionIdExpiresAt;
73
74#ifdef HAVE_ZLIB
75    z_stream           stream;
76#endif
77};
78
79#define dbgmsg( ... ) \
80    do { \
81        if( tr_deepLoggingIsActive( ) ) \
82            tr_deepLog( __FILE__, __LINE__, MY_NAME, __VA_ARGS__ ); \
83    } while( 0 )
84
85
86/**
87***
88**/
89
90static void
91send_simple_response( struct evhttp_request * req,
92                      int                     code,
93                      const char *            text )
94{
95    const char *      code_text = tr_webGetResponseStr( code );
96    struct evbuffer * body = tr_getBuffer( );
97
98    evbuffer_add_printf( body, "<h1>%d: %s</h1>", code, code_text );
99    if( text )
100        evbuffer_add_printf( body, "%s", text );
101    evhttp_send_reply( req, code, code_text, body );
102
103    tr_releaseBuffer( body );
104}
105
106static const char*
107tr_memmem( const char * s1,
108           size_t       l1,
109           const char * s2,
110           size_t       l2 )
111{
112    if( !l2 ) return s1;
113    while( l1 >= l2 )
114    {
115        l1--;
116        if( !memcmp( s1, s2, l2 ) )
117            return s1;
118        s1++;
119    }
120
121    return NULL;
122}
123
124static void
125handle_upload( struct evhttp_request * req,
126               struct tr_rpc_server *  server )
127{
128    if( req->type != EVHTTP_REQ_POST )
129    {
130        send_simple_response( req, 405, NULL );
131    }
132    else
133    {
134        const char * content_type = evhttp_find_header( req->input_headers,
135                                                        "Content-Type" );
136
137        const char * query = strchr( req->uri, '?' );
138        const int    paused = query && strstr( query + 1, "paused=true" );
139
140        const char * in = (const char *) EVBUFFER_DATA( req->input_buffer );
141        size_t       inlen = EVBUFFER_LENGTH( req->input_buffer );
142
143        const char * boundary_key = "boundary=";
144        const char * boundary_key_begin = strstr( content_type,
145                                                  boundary_key );
146        const char * boundary_val =
147            boundary_key_begin ? boundary_key_begin +
148            strlen( boundary_key ) : "arglebargle";
149
150        char *       boundary = tr_strdup_printf( "--%s", boundary_val );
151        const size_t boundary_len = strlen( boundary );
152
153        const char * delim = tr_memmem( in, inlen, boundary, boundary_len );
154        while( delim )
155        {
156            size_t       part_len;
157            const char * part = delim + boundary_len;
158            inlen -= ( part - in );
159            in = part;
160            delim = tr_memmem( in, inlen, boundary, boundary_len );
161            part_len = delim ? (size_t)( delim - part ) : inlen;
162
163            if( part_len )
164            {
165                char * text = tr_strndup( part, part_len );
166                if( strstr( text, "filename=\"" ) )
167                {
168                    const char * body = strstr( text, "\r\n\r\n" );
169                    if( body )
170                    {
171                        char * b64;
172                        size_t  body_len;
173                        tr_benc top, *args;
174                        struct evbuffer * json = tr_getBuffer( );
175
176                        body += 4; /* walk past the \r\n\r\n */
177                        body_len = part_len - ( body - text );
178                        if( body_len >= 2
179                          && !memcmp( &body[body_len - 2], "\r\n", 2 ) )
180                            body_len -= 2;
181
182                        tr_bencInitDict( &top, 2 );
183                        args = tr_bencDictAddDict( &top, "arguments", 2 );
184                        tr_bencDictAddStr( &top, "method", "torrent-add" );
185                        b64 = tr_base64_encode( body, body_len, NULL );
186                        tr_bencDictAddStr( args, "metainfo", b64 );
187                        tr_bencDictAddBool( args, "paused", paused );
188                        tr_bencSaveAsJSON( &top, json );
189                        tr_rpc_request_exec_json( server->session,
190                                                  EVBUFFER_DATA( json ),
191                                                  EVBUFFER_LENGTH( json ),
192                                                  NULL, NULL );
193
194                        tr_releaseBuffer( json );
195                        tr_free( b64 );
196                        tr_bencFree( &top );
197                    }
198                }
199                tr_free( text );
200            }
201        }
202
203        tr_free( boundary );
204
205        /* use xml here because json responses to file uploads is trouble.
206         * see http://www.malsup.com/jquery/form/#sample7 for details */
207        evhttp_add_header( req->output_headers, "Content-Type",
208                           "text/xml; charset=UTF-8" );
209        send_simple_response( req, HTTP_OK, NULL );
210    }
211}
212
213static const char*
214mimetype_guess( const char * path )
215{
216    unsigned int i;
217
218    const struct
219    {
220        const char *    suffix;
221        const char *    mime_type;
222    } types[] = {
223        /* these are just the ones we need for serving clutch... */
224        { "css",  "text/css"                  },
225        { "gif",  "image/gif"                 },
226        { "html", "text/html"                 },
227        { "ico",  "image/vnd.microsoft.icon"  },
228        { "js",   "application/javascript"    },
229        { "png",  "image/png"                 }
230    };
231    const char * dot = strrchr( path, '.' );
232
233    for( i = 0; dot && i < TR_N_ELEMENTS( types ); ++i )
234        if( !strcmp( dot + 1, types[i].suffix ) )
235            return types[i].mime_type;
236
237    return "application/octet-stream";
238}
239
240static void
241add_response( struct evhttp_request * req,
242              struct tr_rpc_server *  server,
243              struct evbuffer *       out,
244              const void *            content,
245              size_t                  content_len )
246{
247#ifndef HAVE_ZLIB
248    evbuffer_add( out, content, content_len );
249#else
250    const char * key = "Accept-Encoding";
251    const char * encoding = evhttp_find_header( req->input_headers, key );
252    const int do_deflate = encoding && strstr( encoding, "deflate" );
253
254    if( !do_deflate )
255    {
256        evbuffer_add( out, content, content_len );
257    }
258    else
259    {
260        int state;
261
262        server->stream.next_in = (Bytef*) content;
263        server->stream.avail_in = content_len;
264
265        /* allocate space for the raw data and call deflate() just once --
266         * we won't use the deflated data if it's longer than the raw data,
267         * so it's okay to let deflate() run out of output buffer space */
268        evbuffer_expand( out, content_len );
269        server->stream.next_out = EVBUFFER_DATA( out );
270        server->stream.avail_out = content_len;
271
272        state = deflate( &server->stream, Z_FINISH );
273
274        if( state == Z_STREAM_END )
275        {
276            EVBUFFER_LENGTH( out ) = content_len - server->stream.avail_out;
277
278            /* http://carsten.codimi.de/gzip.yaws/
279               It turns out that some browsers expect deflated data without
280               the first two bytes (a kind of header) and and the last four
281               bytes (an ADLER32 checksum). This format can of course
282               be produced by simply stripping these off. */
283            if( EVBUFFER_LENGTH( out ) >= 6 ) {
284                EVBUFFER_LENGTH( out ) -= 4;
285                evbuffer_drain( out, 2 );
286            }
287
288#if 0
289            tr_ninf( MY_NAME, _( "Deflated response from %zu bytes to %zu" ),
290                              content_len,
291                              EVBUFFER_LENGTH( out ) );
292#endif
293            evhttp_add_header( req->output_headers,
294                               "Content-Encoding", "deflate" );
295        }
296        else
297        {
298            evbuffer_drain( out, EVBUFFER_LENGTH( out ) );
299            evbuffer_add( out, content, content_len );
300        }
301
302        deflateReset( &server->stream );
303    }
304#endif
305}
306
307static void
308add_time_header( struct evkeyvalq * headers, const char * key, time_t value )
309{
310    /* According to RFC 2616 this must follow RFC 1123's date format,
311       so use gmtime instead of localtime... */
312    char buf[1024];
313    struct tm tm = *gmtime( &value );
314    strftime( buf, sizeof( buf ), "%a, %d %b %Y %H:%M:%S GMT", &tm );
315    evhttp_add_header( headers, key, buf );
316}
317
318static void
319serve_file( struct evhttp_request * req,
320            struct tr_rpc_server *  server,
321            const char *            filename )
322{
323    if( req->type != EVHTTP_REQ_GET )
324    {
325        evhttp_add_header( req->output_headers, "Allow", "GET" );
326        send_simple_response( req, 405, NULL );
327    }
328    else
329    {
330        size_t content_len;
331        uint8_t * content;
332        const int error = errno;
333
334        errno = 0;
335        content_len = 0;
336        content = tr_loadFile( filename, &content_len );
337
338        if( errno )
339        {
340            char * tmp = tr_strdup_printf( "%s (%s)", filename, tr_strerror( errno ) );
341            send_simple_response( req, HTTP_NOTFOUND, tmp );
342            tr_free( tmp );
343        }
344        else
345        {
346            struct evbuffer * out;
347            const time_t now = time( NULL );
348
349            errno = error;
350            out = tr_getBuffer( );
351            evhttp_add_header( req->output_headers, "Content-Type", mimetype_guess( filename ) );
352            add_time_header( req->output_headers, "Date", now );
353            add_time_header( req->output_headers, "Expires", now+(24*60*60) );
354            add_response( req, server, out, content, content_len );
355            evhttp_send_reply( req, HTTP_OK, "OK", out );
356
357            tr_releaseBuffer( out );
358            tr_free( content );
359        }
360    }
361}
362
363static void
364handle_clutch( struct evhttp_request * req,
365               struct tr_rpc_server *  server )
366{
367    const char * clutchDir = tr_getClutchDir( server->session );
368
369    assert( !strncmp( req->uri, "/transmission/web/", 18 ) );
370
371    if( !clutchDir || !*clutchDir )
372    {
373        send_simple_response( req, HTTP_NOTFOUND,
374            "<p>Couldn't find Transmission's web interface files!</p>"
375            "<p>Users: to tell Transmission where to look, "
376            "set the TRANSMISSION_WEB_HOME environmental "
377            "variable to the folder where the web interface's "
378            "index.html is located.</p>"
379            "<p>Package Builders: to set a custom default at compile time, "
380            "#define PACKAGE_DATA_DIR in libtransmission/platform.c "
381            "or tweak tr_getClutchDir() by hand.</p>" );
382    }
383    else
384    {
385        char * pch;
386        char * subpath;
387
388        subpath = tr_strdup( req->uri + 18 );
389        if(( pch = strchr( subpath, '?' )))
390            *pch = '\0';
391
392        if( strstr( subpath, ".." ) )
393        {
394            send_simple_response( req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>" );
395        }
396        else
397        {
398            char * filename = tr_strdup_printf( "%s%s%s",
399                                 clutchDir,
400                                 TR_PATH_DELIMITER_STR,
401                                 subpath && *subpath ? subpath : "index.html" );
402            serve_file( req, server, filename );
403            tr_free( filename );
404        }
405
406        tr_free( subpath );
407    }
408}
409
410struct rpc_response_data
411{
412    struct evhttp_request * req;
413    struct tr_rpc_server  * server;
414};
415
416static void
417rpc_response_func( tr_session      * session UNUSED,
418                   const char      * response,
419                   size_t            response_len,
420                   void            * user_data )
421{
422    struct rpc_response_data * data = user_data;
423    struct evbuffer * buf = tr_getBuffer( );
424
425    add_response( data->req, data->server, buf, response, response_len );
426    evhttp_add_header( data->req->output_headers,
427                           "Content-Type", "application/json; charset=UTF-8" );
428    evhttp_send_reply( data->req, HTTP_OK, "OK", buf );
429
430    tr_releaseBuffer( buf );
431    tr_free( data );
432}
433
434
435static void
436handle_rpc( struct evhttp_request * req,
437            struct tr_rpc_server  * server )
438{
439    struct rpc_response_data * data = tr_new0( struct rpc_response_data, 1 );
440
441    data->req = req;
442    data->server = server;
443   
444    if( req->type == EVHTTP_REQ_GET )
445    {
446        const char * q;
447        if( ( q = strchr( req->uri, '?' ) ) )
448            tr_rpc_request_exec_uri( server->session, q+1, -1, rpc_response_func, data );
449    }
450    else if( req->type == EVHTTP_REQ_POST )
451    {
452        tr_rpc_request_exec_json( server->session,
453                                  EVBUFFER_DATA( req->input_buffer ),
454                                  EVBUFFER_LENGTH( req->input_buffer ),
455                                  rpc_response_func, data );
456    }
457
458}
459
460static tr_bool
461isAddressAllowed( const tr_rpc_server * server,
462                  const char *          address )
463{
464    tr_list * l;
465
466    if( !server->isWhitelistEnabled )
467        return TRUE;
468
469    for( l=server->whitelist; l!=NULL; l=l->next )
470        if( tr_wildmat( address, l->data ) )
471            return TRUE;
472
473    return FALSE;
474}
475
476static char*
477get_current_session_id( struct tr_rpc_server * server )
478{
479    const time_t now = time( NULL );
480
481    if( !server->sessionId || ( now >= server->sessionIdExpiresAt ) )
482    {
483        int i;
484        const int n = 48;
485        const char * pool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
486        const size_t pool_size = strlen( pool );
487        char * buf = tr_new( char, n+1 );
488
489        for( i=0; i<n; ++i )
490            buf[i] = pool[ tr_cryptoRandInt( pool_size ) ];
491        buf[n] = '\0';
492
493        tr_free( server->sessionId );
494        server->sessionId = buf;
495        server->sessionIdExpiresAt = now + (60*60); /* expire in an hour */
496    }
497
498    return server->sessionId;
499}
500
501
502static tr_bool
503test_session_id( struct tr_rpc_server * server, struct evhttp_request * req )
504{
505    const char * ours = get_current_session_id( server );
506    const char * theirs = evhttp_find_header( req->input_headers, TR_RPC_SESSION_ID_HEADER );
507    const tr_bool success =  theirs && !strcmp( theirs, ours );
508    return success;
509}
510
511static void
512handle_request( struct evhttp_request * req, void * arg )
513{
514    struct tr_rpc_server * server = arg;
515
516    if( req && req->evcon )
517    {
518        const char * auth;
519        char * user = NULL;
520        char * pass = NULL;
521
522        evhttp_add_header( req->output_headers, "Server", MY_REALM );
523
524        auth = evhttp_find_header( req->input_headers, "Authorization" );
525        if( auth && !strncasecmp( auth, "basic ", 6 ) )
526        {
527            int    plen;
528            char * p = tr_base64_decode( auth + 6, 0, &plen );
529            if( p && plen && ( ( pass = strchr( p, ':' ) ) ) )
530            {
531                user = p;
532                *pass++ = '\0';
533            }
534        }
535
536        if( !isAddressAllowed( server, req->remote_host ) )
537        {
538            send_simple_response( req, 403,
539                "<p>Unauthorized IP Address.</p>"
540                "<p>Either disable the IP address whitelist or add your address to it.</p>"
541                "<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>"
542                "<p>If you're still using ACLs, use a whitelist instead.  See the transmission-daemon manpage for details.</p>" );
543        }
544        else if( server->isPasswordEnabled
545                 && ( !pass || !user || strcmp( server->username, user )
546                                     || !tr_ssha1_matches( server->password,
547                                                           pass ) ) )
548        {
549            evhttp_add_header( req->output_headers,
550                               "WWW-Authenticate",
551                               "Basic realm=\"" MY_REALM "\"" );
552            send_simple_response( req, 401, "Unauthorized User" );
553        }
554        else if( !strcmp( req->uri, "/transmission/web" )
555               || !strcmp( req->uri, "/transmission/clutch" )
556               || !strcmp( req->uri, "/" ) )
557        {
558            const char * protocol = "http";
559            const char * host = evhttp_find_header( req->input_headers, "Host" );
560            const char * uri = "transmission/web/";
561            char * location = tr_strdup_printf( "%s://%s/%s", protocol, host, uri );
562            evhttp_add_header( req->output_headers, "Location", location );
563            send_simple_response( req, HTTP_MOVEPERM, NULL );
564            tr_free( location );
565        }
566        else if( !strncmp( req->uri, "/transmission/web/", 18 ) )
567        {
568            handle_clutch( req, server );
569        }
570#ifdef REQUIRE_SESSION_ID
571        else if( !test_session_id( server, req ) )
572        {
573            const char * sessionId = get_current_session_id( server );
574            char * tmp = tr_strdup_printf(
575                "<p>Please add this header to your requests:</p>"
576                "<p><code>%s: %s</code></p>"
577                "<p>This requirement is to make "
578                "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a>"
579                " attacks more difficult.</p>",
580                TR_RPC_SESSION_ID_HEADER, sessionId );
581            evhttp_add_header( req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId );
582            send_simple_response( req, 409, tmp );
583            tr_free( tmp );
584        }
585#endif
586        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
587        {
588            handle_rpc( req, server );
589        }
590        else if( !strncmp( req->uri, "/transmission/upload", 20 ) )
591        {
592            handle_upload( req, server );
593        }
594        else
595        {
596            send_simple_response( req, HTTP_NOTFOUND, req->uri );
597        }
598
599        tr_free( user );
600    }
601}
602
603static void
604startServer( void * vserver )
605{
606    tr_rpc_server * server  = vserver;
607    tr_address addr;
608
609    if( !server->httpd )
610    {
611        addr.type = TR_AF_INET;
612        addr.addr.addr4 = server->bindAddress;
613        server->httpd = evhttp_new( tr_eventGetBase( server->session ) );
614        evhttp_bind_socket( server->httpd, tr_ntop_non_ts( &addr ),
615                            server->port );
616        evhttp_set_gencb( server->httpd, handle_request, server );
617
618    }
619}
620
621static void
622stopServer( tr_rpc_server * server )
623{
624    if( server->httpd )
625    {
626        evhttp_free( server->httpd );
627        server->httpd = NULL;
628    }
629}
630
631static void
632onEnabledChanged( void * vserver )
633{
634    tr_rpc_server * server = vserver;
635
636    if( !server->isEnabled )
637        stopServer( server );
638    else
639        startServer( server );
640}
641
642void
643tr_rpcSetEnabled( tr_rpc_server * server,
644                  tr_bool         isEnabled )
645{
646    server->isEnabled = isEnabled;
647
648    tr_runInEventThread( server->session, onEnabledChanged, server );
649}
650
651tr_bool
652tr_rpcIsEnabled( const tr_rpc_server * server )
653{
654    return server->isEnabled;
655}
656
657static void
658restartServer( void * vserver )
659{
660    tr_rpc_server * server = vserver;
661
662    if( server->isEnabled )
663    {
664        stopServer( server );
665        startServer( server );
666    }
667}
668
669void
670tr_rpcSetPort( tr_rpc_server * server,
671               tr_port         port )
672{
673    if( server->port != port )
674    {
675        server->port = port;
676
677        if( server->isEnabled )
678            tr_runInEventThread( server->session, restartServer, server );
679    }
680}
681
682tr_port
683tr_rpcGetPort( const tr_rpc_server * server )
684{
685    return server->port;
686}
687
688void
689tr_rpcSetWhitelist( tr_rpc_server * server,
690                    const char    * whitelistStr )
691{
692    void * tmp;
693    const char * walk;
694
695    /* keep the string */
696    tr_free( server->whitelistStr );
697    server->whitelistStr = tr_strdup( whitelistStr );
698
699    /* clear out the old whitelist entries */
700    while(( tmp = tr_list_pop_front( &server->whitelist )))
701        tr_free( tmp );
702
703    /* build the new whitelist entries */
704    for( walk=whitelistStr; walk && *walk; ) {
705        const char * delimiters = " ,;";
706        const size_t len = strcspn( walk, delimiters );
707        char * token = tr_strndup( walk, len );
708        tr_list_append( &server->whitelist, token );
709        if( strcspn( token, "+-" ) < len )
710            tr_ninf( MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token );
711        else
712            tr_ninf( MY_NAME, "Adding address to whitelist: %s", token );
713       
714        if( walk[len]=='\0' )
715            break;
716        walk += len + 1;
717    }
718}
719
720const char*
721tr_rpcGetWhitelist( const tr_rpc_server * server )
722{
723    return server->whitelistStr ? server->whitelistStr : "";
724}
725
726void
727tr_rpcSetWhitelistEnabled( tr_rpc_server  * server,
728                           tr_bool          isEnabled )
729{
730    server->isWhitelistEnabled = isEnabled != 0;
731}
732
733tr_bool
734tr_rpcGetWhitelistEnabled( const tr_rpc_server * server )
735{
736    return server->isWhitelistEnabled;
737}
738
739/****
740*****  PASSWORD
741****/
742
743void
744tr_rpcSetUsername( tr_rpc_server * server,
745                   const char *    username )
746{
747    tr_free( server->username );
748    server->username = tr_strdup( username );
749    dbgmsg( "setting our Username to [%s]", server->username );
750}
751
752const char*
753tr_rpcGetUsername( const tr_rpc_server * server )
754{
755    return server->username ? server->username : "";
756}
757
758void
759tr_rpcSetPassword( tr_rpc_server * server,
760                   const char *    password )
761{
762    tr_free( server->password );
763    if( *password != '{' )
764        server->password = tr_ssha1( password );
765    else
766        server->password = strdup( password );
767    dbgmsg( "setting our Password to [%s]", server->password );
768}
769
770const char*
771tr_rpcGetPassword( const tr_rpc_server * server )
772{
773    return server->password ? server->password : "" ;
774}
775
776void
777tr_rpcSetPasswordEnabled( tr_rpc_server * server,
778                          tr_bool          isEnabled )
779{
780    server->isPasswordEnabled = isEnabled;
781    dbgmsg( "setting 'password enabled' to %d", (int)isEnabled );
782}
783
784tr_bool
785tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
786{
787    return server->isPasswordEnabled;
788}
789
790const char *
791tr_rpcGetBindAddress( const tr_rpc_server * server )
792{
793    tr_address addr;
794    addr.type = TR_AF_INET;
795    addr.addr.addr4 = server->bindAddress;
796    return tr_ntop_non_ts( &addr );
797}
798
799/****
800*****  LIFE CYCLE
801****/
802
803static void
804closeServer( void * vserver )
805{
806    void * tmp;
807    tr_rpc_server * s = vserver;
808
809    stopServer( s );
810    while(( tmp = tr_list_pop_front( &s->whitelist )))
811        tr_free( tmp );
812#ifdef HAVE_ZLIB
813    deflateEnd( &s->stream );
814#endif
815    tr_free( s->whitelistStr );
816    tr_free( s->username );
817    tr_free( s->password );
818    tr_free( s );
819}
820
821void
822tr_rpcClose( tr_rpc_server ** ps )
823{
824    tr_runInEventThread( ( *ps )->session, closeServer, *ps );
825    *ps = NULL;
826}
827
828tr_rpc_server *
829tr_rpcInit( tr_session  * session,
830            tr_benc * settings )
831{
832    tr_rpc_server * s;
833    tr_bool found;
834    tr_bool boolVal;
835    int64_t i;
836    const char *str;
837    tr_address address;
838
839    s = tr_new0( tr_rpc_server, 1 );
840    s->session = session;
841
842    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_ENABLED, &boolVal );
843    assert( found );
844    s->isEnabled = boolVal;
845
846    found = tr_bencDictFindInt( settings, TR_PREFS_KEY_RPC_PORT, &i );
847    assert( found );
848    s->port = i;
849
850    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, &boolVal );
851    assert( found );
852    s->isWhitelistEnabled = boolVal;
853
854    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, &boolVal );
855    assert( found );
856    s->isPasswordEnabled = boolVal;
857
858    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_WHITELIST, &str );
859    assert( found );
860    tr_rpcSetWhitelist( s, str ? str : "127.0.0.1" );
861
862    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_USERNAME, &str );
863    assert( found );
864    s->username = tr_strdup( str );
865
866    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_PASSWORD, &str );
867    assert( found );
868    if( *str != '{' )
869        s->password = tr_ssha1( str );
870    else
871        s->password = strdup( str );
872
873    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_BIND_ADDRESS, &str );
874    assert( found );
875    if( tr_pton( str, &address ) == NULL ) {
876        tr_err( _( "%s is not a valid address" ), str );
877        address = tr_inaddr_any;
878    } else if( address.type != TR_AF_INET ) {
879        tr_err( _( "%s is not an IPv4 address. RPC listeners must be IPv4" ),
880                   str );
881        address = tr_inaddr_any;
882    }
883    s->bindAddress = address.addr.addr4;
884
885#ifdef HAVE_ZLIB
886    s->stream.zalloc = (alloc_func) Z_NULL;
887    s->stream.zfree = (free_func) Z_NULL;
888    s->stream.opaque = (voidpf) Z_NULL;
889    deflateInit( &s->stream, Z_BEST_COMPRESSION );
890#endif
891
892    if( s->isEnabled )
893    {
894        tr_ninf( MY_NAME, _( "Serving RPC and Web requests on port %d" ), (int) s->port );
895        tr_runInEventThread( session, startServer, s );
896
897        if( s->isWhitelistEnabled )
898            tr_ninf( MY_NAME, _( "Whitelist enabled" ) );
899
900        if( s->isPasswordEnabled )
901            tr_ninf( MY_NAME, _( "Password required" ) );
902    }
903
904    return s;
905}
Note: See TracBrowser for help on using the repository browser.