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

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

(trunk libT) if we compile with TR_EMBEDDED, use Z_DEFAULT_COMPRESSION. otherwise, use Z_BEST_COMPRESSION

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