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

Last change on this file since 12142 was 12142, checked in by jordan, 11 years ago

(trunk libT) fix minor memory leak reported by rpc-server.c

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