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

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

(trunk libT) give a better explanation of what third-party apps need to do

  • 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 8364 2009-05-08 18:07:30Z 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( content )
339        {
340            struct evbuffer * out;
341            const time_t now = time( NULL );
342
343            errno = error;
344            out = tr_getBuffer( );
345            evhttp_add_header( req->output_headers, "Content-Type", mimetype_guess( filename ) );
346            add_time_header( req->output_headers, "Date", now );
347            add_time_header( req->output_headers, "Expires", now+(24*60*60) );
348            add_response( req, server, out, content, content_len );
349            evhttp_send_reply( req, HTTP_OK, "OK", out );
350
351            tr_releaseBuffer( out );
352            tr_free( content );
353        }
354        else
355        {
356            char * tmp = tr_strdup_printf( "%s (%s)", filename, tr_strerror( errno ) );
357            send_simple_response( req, HTTP_NOTFOUND, tmp );
358            tr_free( tmp );
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                TR_RPC_SESSION_ID_HEADER );
588            evhttp_add_header( req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId );
589            send_simple_response( req, 409, tmp );
590            tr_free( tmp );
591        }
592#endif
593        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
594        {
595            handle_rpc( req, server );
596        }
597        else if( !strncmp( req->uri, "/transmission/upload", 20 ) )
598        {
599            handle_upload( req, server );
600        }
601        else
602        {
603            send_simple_response( req, HTTP_NOTFOUND, req->uri );
604        }
605
606        tr_free( user );
607    }
608}
609
610static void
611startServer( void * vserver )
612{
613    tr_rpc_server * server  = vserver;
614    tr_address addr;
615
616    if( !server->httpd )
617    {
618        addr.type = TR_AF_INET;
619        addr.addr.addr4 = server->bindAddress;
620        server->httpd = evhttp_new( tr_eventGetBase( server->session ) );
621        evhttp_bind_socket( server->httpd, tr_ntop_non_ts( &addr ),
622                            server->port );
623        evhttp_set_gencb( server->httpd, handle_request, server );
624
625    }
626}
627
628static void
629stopServer( tr_rpc_server * server )
630{
631    if( server->httpd )
632    {
633        evhttp_free( server->httpd );
634        server->httpd = NULL;
635    }
636}
637
638static void
639onEnabledChanged( void * vserver )
640{
641    tr_rpc_server * server = vserver;
642
643    if( !server->isEnabled )
644        stopServer( server );
645    else
646        startServer( server );
647}
648
649void
650tr_rpcSetEnabled( tr_rpc_server * server,
651                  tr_bool         isEnabled )
652{
653    server->isEnabled = isEnabled;
654
655    tr_runInEventThread( server->session, onEnabledChanged, server );
656}
657
658tr_bool
659tr_rpcIsEnabled( const tr_rpc_server * server )
660{
661    return server->isEnabled;
662}
663
664static void
665restartServer( void * vserver )
666{
667    tr_rpc_server * server = vserver;
668
669    if( server->isEnabled )
670    {
671        stopServer( server );
672        startServer( server );
673    }
674}
675
676void
677tr_rpcSetPort( tr_rpc_server * server,
678               tr_port         port )
679{
680    if( server->port != port )
681    {
682        server->port = port;
683
684        if( server->isEnabled )
685            tr_runInEventThread( server->session, restartServer, server );
686    }
687}
688
689tr_port
690tr_rpcGetPort( const tr_rpc_server * server )
691{
692    return server->port;
693}
694
695void
696tr_rpcSetWhitelist( tr_rpc_server * server,
697                    const char    * whitelistStr )
698{
699    void * tmp;
700    const char * walk;
701
702    /* keep the string */
703    tr_free( server->whitelistStr );
704    server->whitelistStr = tr_strdup( whitelistStr );
705
706    /* clear out the old whitelist entries */
707    while(( tmp = tr_list_pop_front( &server->whitelist )))
708        tr_free( tmp );
709
710    /* build the new whitelist entries */
711    for( walk=whitelistStr; walk && *walk; ) {
712        const char * delimiters = " ,;";
713        const size_t len = strcspn( walk, delimiters );
714        char * token = tr_strndup( walk, len );
715        tr_list_append( &server->whitelist, token );
716        if( strcspn( token, "+-" ) < len )
717            tr_ninf( MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token );
718        else
719            tr_ninf( MY_NAME, "Adding address to whitelist: %s", token );
720       
721        if( walk[len]=='\0' )
722            break;
723        walk += len + 1;
724    }
725}
726
727const char*
728tr_rpcGetWhitelist( const tr_rpc_server * server )
729{
730    return server->whitelistStr ? server->whitelistStr : "";
731}
732
733void
734tr_rpcSetWhitelistEnabled( tr_rpc_server  * server,
735                           tr_bool          isEnabled )
736{
737    server->isWhitelistEnabled = isEnabled != 0;
738}
739
740tr_bool
741tr_rpcGetWhitelistEnabled( const tr_rpc_server * server )
742{
743    return server->isWhitelistEnabled;
744}
745
746/****
747*****  PASSWORD
748****/
749
750void
751tr_rpcSetUsername( tr_rpc_server * server,
752                   const char *    username )
753{
754    tr_free( server->username );
755    server->username = tr_strdup( username );
756    dbgmsg( "setting our Username to [%s]", server->username );
757}
758
759const char*
760tr_rpcGetUsername( const tr_rpc_server * server )
761{
762    return server->username ? server->username : "";
763}
764
765void
766tr_rpcSetPassword( tr_rpc_server * server,
767                   const char *    password )
768{
769    tr_free( server->password );
770    if( *password != '{' )
771        server->password = tr_ssha1( password );
772    else
773        server->password = strdup( password );
774    dbgmsg( "setting our Password to [%s]", server->password );
775}
776
777const char*
778tr_rpcGetPassword( const tr_rpc_server * server )
779{
780    return server->password ? server->password : "" ;
781}
782
783void
784tr_rpcSetPasswordEnabled( tr_rpc_server * server,
785                          tr_bool          isEnabled )
786{
787    server->isPasswordEnabled = isEnabled;
788    dbgmsg( "setting 'password enabled' to %d", (int)isEnabled );
789}
790
791tr_bool
792tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
793{
794    return server->isPasswordEnabled;
795}
796
797const char *
798tr_rpcGetBindAddress( const tr_rpc_server * server )
799{
800    tr_address addr;
801    addr.type = TR_AF_INET;
802    addr.addr.addr4 = server->bindAddress;
803    return tr_ntop_non_ts( &addr );
804}
805
806/****
807*****  LIFE CYCLE
808****/
809
810static void
811closeServer( void * vserver )
812{
813    void * tmp;
814    tr_rpc_server * s = vserver;
815
816    stopServer( s );
817    while(( tmp = tr_list_pop_front( &s->whitelist )))
818        tr_free( tmp );
819#ifdef HAVE_ZLIB
820    deflateEnd( &s->stream );
821#endif
822    tr_free( s->whitelistStr );
823    tr_free( s->username );
824    tr_free( s->password );
825    tr_free( s );
826}
827
828void
829tr_rpcClose( tr_rpc_server ** ps )
830{
831    tr_runInEventThread( ( *ps )->session, closeServer, *ps );
832    *ps = NULL;
833}
834
835tr_rpc_server *
836tr_rpcInit( tr_session  * session,
837            tr_benc * settings )
838{
839    tr_rpc_server * s;
840    tr_bool found;
841    tr_bool boolVal;
842    int64_t i;
843    const char *str;
844    tr_address address;
845
846    s = tr_new0( tr_rpc_server, 1 );
847    s->session = session;
848
849    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_ENABLED, &boolVal );
850    assert( found );
851    s->isEnabled = boolVal;
852
853    found = tr_bencDictFindInt( settings, TR_PREFS_KEY_RPC_PORT, &i );
854    assert( found );
855    s->port = i;
856
857    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, &boolVal );
858    assert( found );
859    s->isWhitelistEnabled = boolVal;
860
861    found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, &boolVal );
862    assert( found );
863    s->isPasswordEnabled = boolVal;
864
865    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_WHITELIST, &str );
866    assert( found );
867    tr_rpcSetWhitelist( s, str ? str : "127.0.0.1" );
868
869    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_USERNAME, &str );
870    assert( found );
871    s->username = tr_strdup( str );
872
873    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_PASSWORD, &str );
874    assert( found );
875    if( *str != '{' )
876        s->password = tr_ssha1( str );
877    else
878        s->password = strdup( str );
879
880    found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_BIND_ADDRESS, &str );
881    assert( found );
882    if( tr_pton( str, &address ) == NULL ) {
883        tr_err( _( "%s is not a valid address" ), str );
884        address = tr_inaddr_any;
885    } else if( address.type != TR_AF_INET ) {
886        tr_err( _( "%s is not an IPv4 address. RPC listeners must be IPv4" ),
887                   str );
888        address = tr_inaddr_any;
889    }
890    s->bindAddress = address.addr.addr4;
891
892#ifdef HAVE_ZLIB
893    s->stream.zalloc = (alloc_func) Z_NULL;
894    s->stream.zfree = (free_func) Z_NULL;
895    s->stream.opaque = (voidpf) Z_NULL;
896    deflateInit( &s->stream, Z_BEST_COMPRESSION );
897#endif
898
899    if( s->isEnabled )
900    {
901        tr_ninf( MY_NAME, _( "Serving RPC and Web requests on port %d" ), (int) s->port );
902        tr_runInEventThread( session, startServer, s );
903
904        if( s->isWhitelistEnabled )
905            tr_ninf( MY_NAME, _( "Whitelist enabled" ) );
906
907        if( s->isPasswordEnabled )
908            tr_ninf( MY_NAME, _( "Password required" ) );
909    }
910
911    return s;
912}
Note: See TracBrowser for help on using the repository browser.