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

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

(trunk) trunk's just been too stable lately. #2119: reload settings.json on SIGHUP

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