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

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

(trunk libT) add some new bugs to the code so that it will crash when vraa tries to use it

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