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

Last change on this file since 10984 was 10984, checked in by charles, 11 years ago

(trunk libT) #3411 "make https behave nicer when using stunnel" -- experimental fix

  • 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 10984 2010-07-09 14:11:58Z 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            evhttp_add_header( req->output_headers, "Location", "/transmission/web/" );
612            send_simple_response( req, HTTP_MOVEPERM, NULL );
613        }
614        else if( !strncmp( req->uri, "/transmission/web/", 18 ) )
615        {
616            handle_web_client( req, server );
617        }
618        else if( !strncmp( req->uri, "/transmission/upload", 20 ) )
619        {
620            handle_upload( req, server );
621        }
622#ifdef REQUIRE_SESSION_ID
623        else if( !test_session_id( server, req ) )
624        {
625            const char * sessionId = get_current_session_id( server );
626            char * tmp = tr_strdup_printf(
627                "<p>Your request had an invalid session-id header.</p>"
628                "<p>To fix this, follow these steps:"
629                "<ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it"
630                "<li> Add the updated header to your outgoing requests"
631                "<li> When you get this 409 error message, resend your request with the updated header"
632                "</ol></p>"
633                "<p>This requirement has been added to help prevent "
634                "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
635                "attacks.</p>"
636                "<p><code>%s: %s</code></p>",
637                TR_RPC_SESSION_ID_HEADER, sessionId );
638            evhttp_add_header( req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId );
639            send_simple_response( req, 409, tmp );
640            tr_free( tmp );
641        }
642#endif
643        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
644        {
645            handle_rpc( req, server );
646        }
647        else
648        {
649            send_simple_response( req, HTTP_NOTFOUND, req->uri );
650        }
651
652        tr_free( user );
653    }
654}
655
656static void
657startServer( void * vserver )
658{
659    tr_rpc_server * server  = vserver;
660    tr_address addr;
661
662    if( !server->httpd )
663    {
664        addr.type = TR_AF_INET;
665        addr.addr.addr4 = server->bindAddress;
666        server->httpd = evhttp_new( tr_eventGetBase( server->session ) );
667        evhttp_bind_socket( server->httpd, tr_ntop_non_ts( &addr ),
668                            server->port );
669        evhttp_set_gencb( server->httpd, handle_request, server );
670
671    }
672}
673
674static void
675stopServer( tr_rpc_server * server )
676{
677    if( server->httpd )
678    {
679        evhttp_free( server->httpd );
680        server->httpd = NULL;
681    }
682}
683
684static void
685onEnabledChanged( void * vserver )
686{
687    tr_rpc_server * server = vserver;
688
689    if( !server->isEnabled )
690        stopServer( server );
691    else
692        startServer( server );
693}
694
695void
696tr_rpcSetEnabled( tr_rpc_server * server,
697                  tr_bool         isEnabled )
698{
699    server->isEnabled = isEnabled;
700
701    tr_runInEventThread( server->session, onEnabledChanged, server );
702}
703
704tr_bool
705tr_rpcIsEnabled( const tr_rpc_server * server )
706{
707    return server->isEnabled;
708}
709
710static void
711restartServer( void * vserver )
712{
713    tr_rpc_server * server = vserver;
714
715    if( server->isEnabled )
716    {
717        stopServer( server );
718        startServer( server );
719    }
720}
721
722void
723tr_rpcSetPort( tr_rpc_server * server,
724               tr_port         port )
725{
726    assert( server != NULL );
727
728    if( server->port != port )
729    {
730        server->port = port;
731
732        if( server->isEnabled )
733            tr_runInEventThread( server->session, restartServer, server );
734    }
735}
736
737tr_port
738tr_rpcGetPort( const tr_rpc_server * server )
739{
740    return server->port;
741}
742
743void
744tr_rpcSetWhitelist( tr_rpc_server * server,
745                    const char    * whitelistStr )
746{
747    void * tmp;
748    const char * walk;
749
750    /* keep the string */
751    tr_free( server->whitelistStr );
752    server->whitelistStr = tr_strdup( whitelistStr );
753
754    /* clear out the old whitelist entries */
755    while(( tmp = tr_list_pop_front( &server->whitelist )))
756        tr_free( tmp );
757
758    /* build the new whitelist entries */
759    for( walk=whitelistStr; walk && *walk; ) {
760        const char * delimiters = " ,;";
761        const size_t len = strcspn( walk, delimiters );
762        char * token = tr_strndup( walk, len );
763        tr_list_append( &server->whitelist, token );
764        if( strcspn( token, "+-" ) < len )
765            tr_ninf( MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token );
766        else
767            tr_ninf( MY_NAME, "Adding address to whitelist: %s", token );
768
769        if( walk[len]=='\0' )
770            break;
771        walk += len + 1;
772    }
773}
774
775const char*
776tr_rpcGetWhitelist( const tr_rpc_server * server )
777{
778    return server->whitelistStr ? server->whitelistStr : "";
779}
780
781void
782tr_rpcSetWhitelistEnabled( tr_rpc_server  * server,
783                           tr_bool          isEnabled )
784{
785    server->isWhitelistEnabled = isEnabled != 0;
786}
787
788tr_bool
789tr_rpcGetWhitelistEnabled( const tr_rpc_server * server )
790{
791    return server->isWhitelistEnabled;
792}
793
794/****
795*****  PASSWORD
796****/
797
798void
799tr_rpcSetUsername( tr_rpc_server * server,
800                   const char *    username )
801{
802    tr_free( server->username );
803    server->username = tr_strdup( username );
804    dbgmsg( "setting our Username to [%s]", server->username );
805}
806
807const char*
808tr_rpcGetUsername( const tr_rpc_server * server )
809{
810    return server->username ? server->username : "";
811}
812
813void
814tr_rpcSetPassword( tr_rpc_server * server,
815                   const char *    password )
816{
817    tr_free( server->password );
818    if( *password != '{' )
819        server->password = tr_ssha1( password );
820    else
821        server->password = strdup( password );
822    dbgmsg( "setting our Password to [%s]", server->password );
823}
824
825const char*
826tr_rpcGetPassword( const tr_rpc_server * server )
827{
828    return server->password ? server->password : "" ;
829}
830
831void
832tr_rpcSetPasswordEnabled( tr_rpc_server * server,
833                          tr_bool          isEnabled )
834{
835    server->isPasswordEnabled = isEnabled;
836    dbgmsg( "setting 'password enabled' to %d", (int)isEnabled );
837}
838
839tr_bool
840tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
841{
842    return server->isPasswordEnabled;
843}
844
845const char *
846tr_rpcGetBindAddress( const tr_rpc_server * server )
847{
848    tr_address addr;
849    addr.type = TR_AF_INET;
850    addr.addr.addr4 = server->bindAddress;
851    return tr_ntop_non_ts( &addr );
852}
853
854/****
855*****  LIFE CYCLE
856****/
857
858static void
859closeServer( void * vserver )
860{
861    void * tmp;
862    tr_rpc_server * s = vserver;
863
864    stopServer( s );
865    while(( tmp = tr_list_pop_front( &s->whitelist )))
866        tr_free( tmp );
867#ifdef HAVE_ZLIB
868    if( s->isStreamInitialized )
869        deflateEnd( &s->stream );
870#endif
871    tr_free( s->sessionId );
872    tr_free( s->whitelistStr );
873    tr_free( s->username );
874    tr_free( s->password );
875    tr_free( s );
876}
877
878void
879tr_rpcClose( tr_rpc_server ** ps )
880{
881    tr_runInEventThread( ( *ps )->session, closeServer, *ps );
882    *ps = NULL;
883}
884
885tr_rpc_server *
886tr_rpcInit( tr_session  * session, tr_benc * settings )
887{
888    tr_rpc_server * s;
889    tr_bool found;
890    tr_bool boolVal;
891    int64_t i;
892    const char *str;
893    tr_address address;
894
895    s = tr_new0( tr_rpc_server, 1 );
896    s->session = session;
897
898    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_ENABLED, &boolVal );
899    assert( found );
900    s->isEnabled = boolVal;
901
902    found = tr_bencDictFindInt( settings, TR_PREFS_KEY_RPC_PORT, &i );
903    assert( found );
904    s->port = i;
905
906    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, &boolVal );
907    assert( found );
908    tr_rpcSetWhitelistEnabled( s, boolVal );
909
910    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, &boolVal );
911    assert( found );
912    tr_rpcSetPasswordEnabled( s, boolVal );
913
914    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_WHITELIST, &str );
915    assert( found );
916    tr_rpcSetWhitelist( s, str ? str : "127.0.0.1" );
917
918    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_USERNAME, &str );
919    assert( found );
920    tr_rpcSetUsername( s, str );
921
922    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_PASSWORD, &str );
923    assert( found );
924    tr_rpcSetPassword( s, str );
925
926    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_BIND_ADDRESS, &str );
927    assert( found );
928    if( tr_pton( str, &address ) == NULL ) {
929        tr_err( _( "%s is not a valid address" ), str );
930        address = tr_inaddr_any;
931    } else if( address.type != TR_AF_INET ) {
932        tr_err( _( "%s is not an IPv4 address. RPC listeners must be IPv4" ),
933                   str );
934        address = tr_inaddr_any;
935    }
936    s->bindAddress = address.addr.addr4;
937
938    if( s->isEnabled )
939    {
940        tr_ninf( MY_NAME, _( "Serving RPC and Web requests on port %d" ), (int) s->port );
941        tr_runInEventThread( session, startServer, s );
942
943        if( s->isWhitelistEnabled )
944            tr_ninf( MY_NAME, "%s", _( "Whitelist enabled" ) );
945
946        if( s->isPasswordEnabled )
947            tr_ninf( MY_NAME, "%s", _( "Password required" ) );
948    }
949
950    return s;
951}
Note: See TracBrowser for help on using the repository browser.