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

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

(trunk) #4138 "use stdbool.h instead of tr_bool" -- done.

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