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

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

(trunk libT) #2030: revert false alarm r8361 as per this suggestion http://trac.transmissionbt.com/ticket/2030#comment:45

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