source: branches/2.0x/libtransmission/rpc-server.c @ 10985

Last change on this file since 10985 was 10985, checked in by charles, 12 years ago

(2.0x libT) #3411 "Make HTTPS behave nicer when using stunnel" -- fixed by backporting r10984

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