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

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

Update the copyright year in the source code comments.

The Berne Convention says that the copyright year is moot, so instead of adding another year to each file as in previous years, I've removed the year altogether from the source code comments in libtransmission, gtk, qt, utils, daemon, and cli.

Juliusz's copyright notice in tr-dht and Johannes' copyright notice in tr-lpd have been left alone; it didn't seem appropriate to modify them.

  • 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 11709 2011-01-19 13:48:47Z 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_EMBEDDED
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 = evbuffer_new( );
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.