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

Last change on this file since 8773 was 8773, checked in by charles, 12 years ago

(trunk libT) #2261: Save memory by not initializing the RPC server's zlib stream until it's needed

  • Property svn:keywords set to Date Rev Author Id
File size: 28.2 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 Charles Kerr <charles@transmissionbt.com>
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 8773 2009-07-02 20:20:00Z 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 = time( NULL );
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 just the ones we need for serving clutch... */
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_deflate = encoding && strstr( encoding, "deflate" );
319
320    if( !do_deflate )
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            server->isStreamInitialized = TRUE;
334            server->stream.zalloc = (alloc_func) Z_NULL;
335            server->stream.zfree = (free_func) Z_NULL;
336            server->stream.opaque = (voidpf) Z_NULL;
337            deflateInit( &server->stream, Z_BEST_COMPRESSION );
338        }
339
340        server->stream.next_in = (Bytef*) content;
341        server->stream.avail_in = content_len;
342
343        /* allocate space for the raw data and call deflate() just once --
344         * we won't use the deflated data if it's longer than the raw data,
345         * so it's okay to let deflate() run out of output buffer space */
346        evbuffer_expand( out, content_len );
347        server->stream.next_out = EVBUFFER_DATA( out );
348        server->stream.avail_out = content_len;
349
350        state = deflate( &server->stream, Z_FINISH );
351
352        if( state == Z_STREAM_END )
353        {
354            EVBUFFER_LENGTH( out ) = content_len - server->stream.avail_out;
355
356            /* http://carsten.codimi.de/gzip.yaws/
357               It turns out that some browsers expect deflated data without
358               the first two bytes (a kind of header) and and the last four
359               bytes (an ADLER32 checksum). This format can of course
360               be produced by simply stripping these off. */
361            if( EVBUFFER_LENGTH( out ) >= 6 ) {
362                EVBUFFER_LENGTH( out ) -= 4;
363                evbuffer_drain( out, 2 );
364            }
365
366#if 0
367            tr_ninf( MY_NAME, _( "Deflated response from %zu bytes to %zu" ),
368                              content_len,
369                              EVBUFFER_LENGTH( out ) );
370#endif
371            evhttp_add_header( req->output_headers,
372                               "Content-Encoding", "deflate" );
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 = time( NULL );
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_clutch( struct evhttp_request * req,
443               struct tr_rpc_server *  server )
444{
445    const char * clutchDir = tr_getClutchDir( server->session );
446
447    assert( !strncmp( req->uri, "/transmission/web/", 18 ) );
448
449    if( !clutchDir || !*clutchDir )
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                                 clutchDir,
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            const char * protocol = "http";
612            const char * host = evhttp_find_header( req->input_headers, "Host" );
613            const char * uri = "transmission/web/";
614            char * location = tr_strdup_printf( "%s://%s/%s", protocol, host, uri );
615            evhttp_add_header( req->output_headers, "Location", location );
616            send_simple_response( req, HTTP_MOVEPERM, NULL );
617            tr_free( location );
618        }
619        else if( !strncmp( req->uri, "/transmission/web/", 18 ) )
620        {
621            handle_clutch( req, server );
622        }
623        else if( !strncmp( req->uri, "/transmission/upload", 20 ) )
624        {
625            handle_upload( req, server );
626        }
627#ifdef REQUIRE_SESSION_ID
628        else if( !test_session_id( server, req ) )
629        {
630            const char * sessionId = get_current_session_id( server );
631            char * tmp = tr_strdup_printf(
632                "<p>Please add this header to your HTTP requests:</p>"
633                "<p style=\"padding-left: 20pt;\"><code>%s: %s</code></p>"
634                "<p><b>RPC Application Developers:</b></p>"
635                "<p style=\"padding-left: 20pt;\">As of Transmission 1.53 and 1.61, RPC clients "
636                "need to look for this 409 response containing the phrase \"invalid session-id\".  "
637                "It occurs when the request's "TR_RPC_SESSION_ID_HEADER" header was missing "
638                "(such as during bootstrapping) or expired. "
639                "Either way, you can parse this response's headers for the new session-id.</p>"
640                "<p style=\"padding-left: 20pt;\">This requirement has been added to make "
641                "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a>"
642                " attacks more difficult.</p>",
643                TR_RPC_SESSION_ID_HEADER, sessionId );
644            evhttp_add_header( req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId );
645            send_simple_response( req, 409, tmp );
646            tr_free( tmp );
647        }
648#endif
649        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
650        {
651            handle_rpc( req, server );
652        }
653        else
654        {
655            send_simple_response( req, HTTP_NOTFOUND, req->uri );
656        }
657
658        tr_free( user );
659    }
660}
661
662static void
663startServer( void * vserver )
664{
665    tr_rpc_server * server  = vserver;
666    tr_address addr;
667
668    if( !server->httpd )
669    {
670        addr.type = TR_AF_INET;
671        addr.addr.addr4 = server->bindAddress;
672        server->httpd = evhttp_new( tr_eventGetBase( server->session ) );
673        evhttp_bind_socket( server->httpd, tr_ntop_non_ts( &addr ),
674                            server->port );
675        evhttp_set_gencb( server->httpd, handle_request, server );
676
677    }
678}
679
680static void
681stopServer( tr_rpc_server * server )
682{
683    if( server->httpd )
684    {
685        evhttp_free( server->httpd );
686        server->httpd = NULL;
687    }
688}
689
690static void
691onEnabledChanged( void * vserver )
692{
693    tr_rpc_server * server = vserver;
694
695    if( !server->isEnabled )
696        stopServer( server );
697    else
698        startServer( server );
699}
700
701void
702tr_rpcSetEnabled( tr_rpc_server * server,
703                  tr_bool         isEnabled )
704{
705    server->isEnabled = isEnabled;
706
707    tr_runInEventThread( server->session, onEnabledChanged, server );
708}
709
710tr_bool
711tr_rpcIsEnabled( const tr_rpc_server * server )
712{
713    return server->isEnabled;
714}
715
716static void
717restartServer( void * vserver )
718{
719    tr_rpc_server * server = vserver;
720
721    if( server->isEnabled )
722    {
723        stopServer( server );
724        startServer( server );
725    }
726}
727
728void
729tr_rpcSetPort( tr_rpc_server * server,
730               tr_port         port )
731{
732    if( server->port != port )
733    {
734        server->port = port;
735
736        if( server->isEnabled )
737            tr_runInEventThread( server->session, restartServer, server );
738    }
739}
740
741tr_port
742tr_rpcGetPort( const tr_rpc_server * server )
743{
744    return server->port;
745}
746
747void
748tr_rpcSetWhitelist( tr_rpc_server * server,
749                    const char    * whitelistStr )
750{
751    void * tmp;
752    const char * walk;
753
754    /* keep the string */
755    tr_free( server->whitelistStr );
756    server->whitelistStr = tr_strdup( whitelistStr );
757
758    /* clear out the old whitelist entries */
759    while(( tmp = tr_list_pop_front( &server->whitelist )))
760        tr_free( tmp );
761
762    /* build the new whitelist entries */
763    for( walk=whitelistStr; walk && *walk; ) {
764        const char * delimiters = " ,;";
765        const size_t len = strcspn( walk, delimiters );
766        char * token = tr_strndup( walk, len );
767        tr_list_append( &server->whitelist, token );
768        if( strcspn( token, "+-" ) < len )
769            tr_ninf( MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token );
770        else
771            tr_ninf( MY_NAME, "Adding address to whitelist: %s", token );
772       
773        if( walk[len]=='\0' )
774            break;
775        walk += len + 1;
776    }
777}
778
779const char*
780tr_rpcGetWhitelist( const tr_rpc_server * server )
781{
782    return server->whitelistStr ? server->whitelistStr : "";
783}
784
785void
786tr_rpcSetWhitelistEnabled( tr_rpc_server  * server,
787                           tr_bool          isEnabled )
788{
789    server->isWhitelistEnabled = isEnabled != 0;
790}
791
792tr_bool
793tr_rpcGetWhitelistEnabled( const tr_rpc_server * server )
794{
795    return server->isWhitelistEnabled;
796}
797
798/****
799*****  PASSWORD
800****/
801
802void
803tr_rpcSetUsername( tr_rpc_server * server,
804                   const char *    username )
805{
806    tr_free( server->username );
807    server->username = tr_strdup( username );
808    dbgmsg( "setting our Username to [%s]", server->username );
809}
810
811const char*
812tr_rpcGetUsername( const tr_rpc_server * server )
813{
814    return server->username ? server->username : "";
815}
816
817void
818tr_rpcSetPassword( tr_rpc_server * server,
819                   const char *    password )
820{
821    tr_free( server->password );
822    if( *password != '{' )
823        server->password = tr_ssha1( password );
824    else
825        server->password = strdup( password );
826    dbgmsg( "setting our Password to [%s]", server->password );
827}
828
829const char*
830tr_rpcGetPassword( const tr_rpc_server * server )
831{
832    return server->password ? server->password : "" ;
833}
834
835void
836tr_rpcSetPasswordEnabled( tr_rpc_server * server,
837                          tr_bool          isEnabled )
838{
839    server->isPasswordEnabled = isEnabled;
840    dbgmsg( "setting 'password enabled' to %d", (int)isEnabled );
841}
842
843tr_bool
844tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
845{
846    return server->isPasswordEnabled;
847}
848
849const char *
850tr_rpcGetBindAddress( const tr_rpc_server * server )
851{
852    tr_address addr;
853    addr.type = TR_AF_INET;
854    addr.addr.addr4 = server->bindAddress;
855    return tr_ntop_non_ts( &addr );
856}
857
858/****
859*****  LIFE CYCLE
860****/
861
862static void
863closeServer( void * vserver )
864{
865    void * tmp;
866    tr_rpc_server * s = vserver;
867
868    stopServer( s );
869    while(( tmp = tr_list_pop_front( &s->whitelist )))
870        tr_free( tmp );
871#ifdef HAVE_ZLIB
872    if( s->isStreamInitialized )
873        deflateEnd( &s->stream );
874#endif
875    tr_free( s->sessionId );
876    tr_free( s->whitelistStr );
877    tr_free( s->username );
878    tr_free( s->password );
879    tr_free( s );
880}
881
882void
883tr_rpcClose( tr_rpc_server ** ps )
884{
885    tr_runInEventThread( ( *ps )->session, closeServer, *ps );
886    *ps = NULL;
887}
888
889tr_rpc_server *
890tr_rpcInit( tr_session  * session,
891            tr_benc * settings )
892{
893    tr_rpc_server * s;
894    tr_bool found;
895    tr_bool boolVal;
896    int64_t i;
897    const char *str;
898    tr_address address;
899
900    s = tr_new0( tr_rpc_server, 1 );
901    s->session = session;
902
903    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_ENABLED, &boolVal );
904    assert( found );
905    s->isEnabled = boolVal;
906
907    found = tr_bencDictFindInt( settings, TR_PREFS_KEY_RPC_PORT, &i );
908    assert( found );
909    s->port = i;
910
911    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, &boolVal );
912    assert( found );
913    s->isWhitelistEnabled = boolVal;
914
915    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, &boolVal );
916    assert( found );
917    s->isPasswordEnabled = boolVal;
918
919    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_WHITELIST, &str );
920    assert( found );
921    tr_rpcSetWhitelist( s, str ? str : "127.0.0.1" );
922
923    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_USERNAME, &str );
924    assert( found );
925    s->username = tr_strdup( str );
926
927    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_PASSWORD, &str );
928    assert( found );
929    if( *str != '{' )
930        s->password = tr_ssha1( str );
931    else
932        s->password = strdup( str );
933
934    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_BIND_ADDRESS, &str );
935    assert( found );
936    if( tr_pton( str, &address ) == NULL ) {
937        tr_err( _( "%s is not a valid address" ), str );
938        address = tr_inaddr_any;
939    } else if( address.type != TR_AF_INET ) {
940        tr_err( _( "%s is not an IPv4 address. RPC listeners must be IPv4" ),
941                   str );
942        address = tr_inaddr_any;
943    }
944    s->bindAddress = address.addr.addr4;
945
946    if( s->isEnabled )
947    {
948        tr_ninf( MY_NAME, _( "Serving RPC and Web requests on port %d" ), (int) s->port );
949        tr_runInEventThread( session, startServer, s );
950
951        if( s->isWhitelistEnabled )
952            tr_ninf( MY_NAME, "%s", _( "Whitelist enabled" ) );
953
954        if( s->isPasswordEnabled )
955            tr_ninf( MY_NAME, "%s", _( "Password required" ) );
956    }
957
958    return s;
959}
Note: See TracBrowser for help on using the repository browser.