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

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

(trunk) support an X-Transmission-Session-Id header in the RPC server. Yesterday's approach of including the session_id in posted forms -- which is a typical approach -- isn't sufficient for Transmission, since it also allows remote access via JSON/RPC. (part 1 of 2. part 2 is kjg's web ui patch)

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