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

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

(trunk libT) copyediting: '#include "crypto.h"' cleanup

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