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

Last change on this file since 8424 was 8424, checked in by charles, 13 years ago

(trunk libT) expose tr_memmem() so it can be used by client code. use the OS' implementation if that's available.

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