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

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

(trunk) silence some minor gcc warnings

  • Property svn:keywords set to Date Rev Author Id
File size: 28.0 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 8736 2009-06-23 14:15:23Z 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 "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 = evbuffer_new( );
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    evbuffer_free( 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_bencToBuf( &top, TR_FMT_JSON_LEAN, json );
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        /* FIXME(libevent2): this won't compile under libevent2.
328           but we can use evbuffer_reserve_space() + evbuffer_commit_space() instead */
329
330        server->stream.next_in = (Bytef*) content;
331        server->stream.avail_in = content_len;
332
333        /* allocate space for the raw data and call deflate() just once --
334         * we won't use the deflated data if it's longer than the raw data,
335         * so it's okay to let deflate() run out of output buffer space */
336        evbuffer_expand( out, content_len );
337        server->stream.next_out = EVBUFFER_DATA( out );
338        server->stream.avail_out = content_len;
339
340        state = deflate( &server->stream, Z_FINISH );
341
342        if( state == Z_STREAM_END )
343        {
344            EVBUFFER_LENGTH( out ) = content_len - server->stream.avail_out;
345
346            /* http://carsten.codimi.de/gzip.yaws/
347               It turns out that some browsers expect deflated data without
348               the first two bytes (a kind of header) and and the last four
349               bytes (an ADLER32 checksum). This format can of course
350               be produced by simply stripping these off. */
351            if( EVBUFFER_LENGTH( out ) >= 6 ) {
352                EVBUFFER_LENGTH( out ) -= 4;
353                evbuffer_drain( out, 2 );
354            }
355
356#if 0
357            tr_ninf( MY_NAME, _( "Deflated response from %zu bytes to %zu" ),
358                              content_len,
359                              EVBUFFER_LENGTH( out ) );
360#endif
361            evhttp_add_header( req->output_headers,
362                               "Content-Encoding", "deflate" );
363        }
364        else
365        {
366            evbuffer_drain( out, EVBUFFER_LENGTH( out ) );
367            evbuffer_add( out, content, content_len );
368        }
369
370        deflateReset( &server->stream );
371    }
372#endif
373}
374
375static void
376add_time_header( struct evkeyvalq * headers, const char * key, time_t value )
377{
378    /* According to RFC 2616 this must follow RFC 1123's date format,
379       so use gmtime instead of localtime... */
380    char buf[128];
381    struct tm tm = *gmtime( &value );
382    strftime( buf, sizeof( buf ), "%a, %d %b %Y %H:%M:%S GMT", &tm );
383    evhttp_add_header( headers, key, buf );
384}
385
386static void
387serve_file( struct evhttp_request * req,
388            struct tr_rpc_server *  server,
389            const char *            filename )
390{
391    if( req->type != EVHTTP_REQ_GET )
392    {
393        evhttp_add_header( req->output_headers, "Allow", "GET" );
394        send_simple_response( req, 405, NULL );
395    }
396    else
397    {
398        size_t content_len;
399        uint8_t * content;
400        const int error = errno;
401
402        errno = 0;
403        content_len = 0;
404        content = tr_loadFile( filename, &content_len );
405
406        if( errno )
407        {
408            char * tmp = tr_strdup_printf( "%s (%s)", filename, tr_strerror( errno ) );
409            send_simple_response( req, HTTP_NOTFOUND, tmp );
410            tr_free( tmp );
411        }
412        else
413        {
414            struct evbuffer * out;
415            const time_t now = time( NULL );
416
417            errno = error;
418            out = evbuffer_new( );
419            evhttp_add_header( req->output_headers, "Content-Type", mimetype_guess( filename ) );
420            add_time_header( req->output_headers, "Date", now );
421            add_time_header( req->output_headers, "Expires", now+(24*60*60) );
422            add_response( req, server, out, content, content_len );
423            evhttp_send_reply( req, HTTP_OK, "OK", out );
424
425            evbuffer_free( out );
426            tr_free( content );
427        }
428    }
429}
430
431static void
432handle_clutch( struct evhttp_request * req,
433               struct tr_rpc_server *  server )
434{
435    const char * clutchDir = tr_getClutchDir( server->session );
436
437    assert( !strncmp( req->uri, "/transmission/web/", 18 ) );
438
439    if( !clutchDir || !*clutchDir )
440    {
441        send_simple_response( req, HTTP_NOTFOUND,
442            "<p>Couldn't find Transmission's web interface files!</p>"
443            "<p>Users: to tell Transmission where to look, "
444            "set the TRANSMISSION_WEB_HOME environment "
445            "variable to the folder where the web interface's "
446            "index.html is located.</p>"
447            "<p>Package Builders: to set a custom default at compile time, "
448            "#define PACKAGE_DATA_DIR in libtransmission/platform.c "
449            "or tweak tr_getClutchDir() by hand.</p>" );
450    }
451    else
452    {
453        char * pch;
454        char * subpath;
455
456        subpath = tr_strdup( req->uri + 18 );
457        if(( pch = strchr( subpath, '?' )))
458            *pch = '\0';
459
460        if( strstr( subpath, ".." ) )
461        {
462            send_simple_response( req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>" );
463        }
464        else
465        {
466            char * filename = tr_strdup_printf( "%s%s%s",
467                                 clutchDir,
468                                 TR_PATH_DELIMITER_STR,
469                                 subpath && *subpath ? subpath : "index.html" );
470            serve_file( req, server, filename );
471            tr_free( filename );
472        }
473
474        tr_free( subpath );
475    }
476}
477
478struct rpc_response_data
479{
480    struct evhttp_request * req;
481    struct tr_rpc_server  * server;
482};
483
484/* FIXME(libevent2): make "response" an evbuffer and remove response_len */
485static void
486rpc_response_func( tr_session      * session UNUSED,
487                   const char      * response,
488                   size_t            response_len,
489                   void            * user_data )
490{
491    struct rpc_response_data * data = user_data;
492    struct evbuffer * buf = evbuffer_new( );
493
494    add_response( data->req, data->server, buf, response, response_len );
495    evhttp_add_header( data->req->output_headers,
496                           "Content-Type", "application/json; charset=UTF-8" );
497    evhttp_send_reply( data->req, HTTP_OK, "OK", buf );
498
499    evbuffer_free( buf );
500    tr_free( data );
501}
502
503
504static void
505handle_rpc( struct evhttp_request * req,
506            struct tr_rpc_server  * server )
507{
508    struct rpc_response_data * data = tr_new0( struct rpc_response_data, 1 );
509
510    data->req = req;
511    data->server = server;
512   
513    if( req->type == EVHTTP_REQ_GET )
514    {
515        const char * q;
516        if( ( q = strchr( req->uri, '?' ) ) )
517            tr_rpc_request_exec_uri( server->session, q+1, -1, rpc_response_func, data );
518    }
519    else if( req->type == EVHTTP_REQ_POST )
520    {
521        tr_rpc_request_exec_json( server->session,
522                                  EVBUFFER_DATA( req->input_buffer ),
523                                  EVBUFFER_LENGTH( req->input_buffer ),
524                                  rpc_response_func, data );
525    }
526
527}
528
529static tr_bool
530isAddressAllowed( const tr_rpc_server * server,
531                  const char *          address )
532{
533    tr_list * l;
534
535    if( !server->isWhitelistEnabled )
536        return TRUE;
537
538    for( l=server->whitelist; l!=NULL; l=l->next )
539        if( tr_wildmat( address, l->data ) )
540            return TRUE;
541
542    return FALSE;
543}
544
545static tr_bool
546test_session_id( struct tr_rpc_server * server, struct evhttp_request * req )
547{
548    const char * ours = get_current_session_id( server );
549    const char * theirs = evhttp_find_header( req->input_headers, TR_RPC_SESSION_ID_HEADER );
550    const tr_bool success =  theirs && !strcmp( theirs, ours );
551    return success;
552}
553
554static void
555handle_request( struct evhttp_request * req, void * arg )
556{
557    struct tr_rpc_server * server = arg;
558
559    if( req && req->evcon )
560    {
561        const char * auth;
562        char * user = NULL;
563        char * pass = NULL;
564
565        evhttp_add_header( req->output_headers, "Server", MY_REALM );
566
567        auth = evhttp_find_header( req->input_headers, "Authorization" );
568        if( auth && !strncasecmp( auth, "basic ", 6 ) )
569        {
570            int    plen;
571            char * p = tr_base64_decode( auth + 6, 0, &plen );
572            if( p && plen && ( ( pass = strchr( p, ':' ) ) ) )
573            {
574                user = p;
575                *pass++ = '\0';
576            }
577        }
578
579        if( !isAddressAllowed( server, req->remote_host ) )
580        {
581            send_simple_response( req, 403,
582                "<p>Unauthorized IP Address.</p>"
583                "<p>Either disable the IP address whitelist or add your address to it.</p>"
584                "<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>"
585                "<p>If you're still using ACLs, use a whitelist instead.  See the transmission-daemon manpage for details.</p>" );
586        }
587        else if( server->isPasswordEnabled
588                 && ( !pass || !user || strcmp( server->username, user )
589                                     || !tr_ssha1_matches( server->password,
590                                                           pass ) ) )
591        {
592            evhttp_add_header( req->output_headers,
593                               "WWW-Authenticate",
594                               "Basic realm=\"" MY_REALM "\"" );
595            send_simple_response( req, 401, "Unauthorized User" );
596        }
597        else if( !strcmp( req->uri, "/transmission/web" )
598               || !strcmp( req->uri, "/transmission/clutch" )
599               || !strcmp( req->uri, "/" ) )
600        {
601            const char * protocol = "http";
602            const char * host = evhttp_find_header( req->input_headers, "Host" );
603            const char * uri = "transmission/web/";
604            char * location = tr_strdup_printf( "%s://%s/%s", protocol, host, uri );
605            evhttp_add_header( req->output_headers, "Location", location );
606            send_simple_response( req, HTTP_MOVEPERM, NULL );
607            tr_free( location );
608        }
609        else if( !strncmp( req->uri, "/transmission/web/", 18 ) )
610        {
611            handle_clutch( req, server );
612        }
613        else if( !strncmp( req->uri, "/transmission/upload", 20 ) )
614        {
615            handle_upload( req, server );
616        }
617#ifdef REQUIRE_SESSION_ID
618        else if( !test_session_id( server, req ) )
619        {
620            const char * sessionId = get_current_session_id( server );
621            char * tmp = tr_strdup_printf(
622                "<p>Please add this header to your HTTP requests:</p>"
623                "<p style=\"padding-left: 20pt;\"><code>%s: %s</code></p>"
624                "<p><b>RPC Application Developers:</b></p>"
625                "<p style=\"padding-left: 20pt;\">As of Transmission 1.53 and 1.61, RPC clients "
626                "need to look for this 409 response containing the phrase \"invalid session-id\".  "
627                "It occurs when the request's "TR_RPC_SESSION_ID_HEADER" header was missing "
628                "(such as during bootstrapping) or expired. "
629                "Either way, you can parse this response's headers for the new session-id.</p>"
630                "<p style=\"padding-left: 20pt;\">This requirement has been added to make "
631                "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a>"
632                " attacks more difficult.</p>",
633                TR_RPC_SESSION_ID_HEADER, sessionId );
634            evhttp_add_header( req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId );
635            send_simple_response( req, 409, tmp );
636            tr_free( tmp );
637        }
638#endif
639        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
640        {
641            handle_rpc( req, server );
642        }
643        else
644        {
645            send_simple_response( req, HTTP_NOTFOUND, req->uri );
646        }
647
648        tr_free( user );
649    }
650}
651
652static void
653startServer( void * vserver )
654{
655    tr_rpc_server * server  = vserver;
656    tr_address addr;
657
658    if( !server->httpd )
659    {
660        addr.type = TR_AF_INET;
661        addr.addr.addr4 = server->bindAddress;
662        server->httpd = evhttp_new( tr_eventGetBase( server->session ) );
663        evhttp_bind_socket( server->httpd, tr_ntop_non_ts( &addr ),
664                            server->port );
665        evhttp_set_gencb( server->httpd, handle_request, server );
666
667    }
668}
669
670static void
671stopServer( tr_rpc_server * server )
672{
673    if( server->httpd )
674    {
675        evhttp_free( server->httpd );
676        server->httpd = NULL;
677    }
678}
679
680static void
681onEnabledChanged( void * vserver )
682{
683    tr_rpc_server * server = vserver;
684
685    if( !server->isEnabled )
686        stopServer( server );
687    else
688        startServer( server );
689}
690
691void
692tr_rpcSetEnabled( tr_rpc_server * server,
693                  tr_bool         isEnabled )
694{
695    server->isEnabled = isEnabled;
696
697    tr_runInEventThread( server->session, onEnabledChanged, server );
698}
699
700tr_bool
701tr_rpcIsEnabled( const tr_rpc_server * server )
702{
703    return server->isEnabled;
704}
705
706static void
707restartServer( void * vserver )
708{
709    tr_rpc_server * server = vserver;
710
711    if( server->isEnabled )
712    {
713        stopServer( server );
714        startServer( server );
715    }
716}
717
718void
719tr_rpcSetPort( tr_rpc_server * server,
720               tr_port         port )
721{
722    if( server->port != port )
723    {
724        server->port = port;
725
726        if( server->isEnabled )
727            tr_runInEventThread( server->session, restartServer, server );
728    }
729}
730
731tr_port
732tr_rpcGetPort( const tr_rpc_server * server )
733{
734    return server->port;
735}
736
737void
738tr_rpcSetWhitelist( tr_rpc_server * server,
739                    const char    * whitelistStr )
740{
741    void * tmp;
742    const char * walk;
743
744    /* keep the string */
745    tr_free( server->whitelistStr );
746    server->whitelistStr = tr_strdup( whitelistStr );
747
748    /* clear out the old whitelist entries */
749    while(( tmp = tr_list_pop_front( &server->whitelist )))
750        tr_free( tmp );
751
752    /* build the new whitelist entries */
753    for( walk=whitelistStr; walk && *walk; ) {
754        const char * delimiters = " ,;";
755        const size_t len = strcspn( walk, delimiters );
756        char * token = tr_strndup( walk, len );
757        tr_list_append( &server->whitelist, token );
758        if( strcspn( token, "+-" ) < len )
759            tr_ninf( MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token );
760        else
761            tr_ninf( MY_NAME, "Adding address to whitelist: %s", token );
762       
763        if( walk[len]=='\0' )
764            break;
765        walk += len + 1;
766    }
767}
768
769const char*
770tr_rpcGetWhitelist( const tr_rpc_server * server )
771{
772    return server->whitelistStr ? server->whitelistStr : "";
773}
774
775void
776tr_rpcSetWhitelistEnabled( tr_rpc_server  * server,
777                           tr_bool          isEnabled )
778{
779    server->isWhitelistEnabled = isEnabled != 0;
780}
781
782tr_bool
783tr_rpcGetWhitelistEnabled( const tr_rpc_server * server )
784{
785    return server->isWhitelistEnabled;
786}
787
788/****
789*****  PASSWORD
790****/
791
792void
793tr_rpcSetUsername( tr_rpc_server * server,
794                   const char *    username )
795{
796    tr_free( server->username );
797    server->username = tr_strdup( username );
798    dbgmsg( "setting our Username to [%s]", server->username );
799}
800
801const char*
802tr_rpcGetUsername( const tr_rpc_server * server )
803{
804    return server->username ? server->username : "";
805}
806
807void
808tr_rpcSetPassword( tr_rpc_server * server,
809                   const char *    password )
810{
811    tr_free( server->password );
812    if( *password != '{' )
813        server->password = tr_ssha1( password );
814    else
815        server->password = strdup( password );
816    dbgmsg( "setting our Password to [%s]", server->password );
817}
818
819const char*
820tr_rpcGetPassword( const tr_rpc_server * server )
821{
822    return server->password ? server->password : "" ;
823}
824
825void
826tr_rpcSetPasswordEnabled( tr_rpc_server * server,
827                          tr_bool          isEnabled )
828{
829    server->isPasswordEnabled = isEnabled;
830    dbgmsg( "setting 'password enabled' to %d", (int)isEnabled );
831}
832
833tr_bool
834tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
835{
836    return server->isPasswordEnabled;
837}
838
839const char *
840tr_rpcGetBindAddress( const tr_rpc_server * server )
841{
842    tr_address addr;
843    addr.type = TR_AF_INET;
844    addr.addr.addr4 = server->bindAddress;
845    return tr_ntop_non_ts( &addr );
846}
847
848/****
849*****  LIFE CYCLE
850****/
851
852static void
853closeServer( void * vserver )
854{
855    void * tmp;
856    tr_rpc_server * s = vserver;
857
858    stopServer( s );
859    while(( tmp = tr_list_pop_front( &s->whitelist )))
860        tr_free( tmp );
861#ifdef HAVE_ZLIB
862    deflateEnd( &s->stream );
863#endif
864    tr_free( s->sessionId );
865    tr_free( s->whitelistStr );
866    tr_free( s->username );
867    tr_free( s->password );
868    tr_free( s );
869}
870
871void
872tr_rpcClose( tr_rpc_server ** ps )
873{
874    tr_runInEventThread( ( *ps )->session, closeServer, *ps );
875    *ps = NULL;
876}
877
878tr_rpc_server *
879tr_rpcInit( tr_session  * session,
880            tr_benc * settings )
881{
882    tr_rpc_server * s;
883    tr_bool found;
884    tr_bool boolVal;
885    int64_t i;
886    const char *str;
887    tr_address address;
888
889    s = tr_new0( tr_rpc_server, 1 );
890    s->session = session;
891
892    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_ENABLED, &boolVal );
893    assert( found );
894    s->isEnabled = boolVal;
895
896    found = tr_bencDictFindInt( settings, TR_PREFS_KEY_RPC_PORT, &i );
897    assert( found );
898    s->port = i;
899
900    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, &boolVal );
901    assert( found );
902    s->isWhitelistEnabled = boolVal;
903
904    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, &boolVal );
905    assert( found );
906    s->isPasswordEnabled = boolVal;
907
908    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_WHITELIST, &str );
909    assert( found );
910    tr_rpcSetWhitelist( s, str ? str : "127.0.0.1" );
911
912    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_USERNAME, &str );
913    assert( found );
914    s->username = tr_strdup( str );
915
916    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_PASSWORD, &str );
917    assert( found );
918    if( *str != '{' )
919        s->password = tr_ssha1( str );
920    else
921        s->password = strdup( str );
922
923    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_BIND_ADDRESS, &str );
924    assert( found );
925    if( tr_pton( str, &address ) == NULL ) {
926        tr_err( _( "%s is not a valid address" ), str );
927        address = tr_inaddr_any;
928    } else if( address.type != TR_AF_INET ) {
929        tr_err( _( "%s is not an IPv4 address. RPC listeners must be IPv4" ),
930                   str );
931        address = tr_inaddr_any;
932    }
933    s->bindAddress = address.addr.addr4;
934
935#ifdef HAVE_ZLIB
936    s->stream.zalloc = (alloc_func) Z_NULL;
937    s->stream.zfree = (free_func) Z_NULL;
938    s->stream.opaque = (voidpf) Z_NULL;
939    deflateInit( &s->stream, Z_BEST_COMPRESSION );
940#endif
941
942    if( s->isEnabled )
943    {
944        tr_ninf( MY_NAME, _( "Serving RPC and Web requests on port %d" ), (int) s->port );
945        tr_runInEventThread( session, startServer, s );
946
947        if( s->isWhitelistEnabled )
948            tr_ninf( MY_NAME, "%s", _( "Whitelist enabled" ) );
949
950        if( s->isPasswordEnabled )
951            tr_ninf( MY_NAME, "%s", _( "Password required" ) );
952    }
953
954    return s;
955}
Note: See TracBrowser for help on using the repository browser.