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

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

(trunk) #3836 "libevent2 support" -- bump libevent2 requirement to 2.0.10. This will break the Mac build for a minute intil BMW applies his Mac patch

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