Transmission: update from 2.42 to 2.50
[tomato.git] / release / src / router / transmission / libtransmission / rpc-server.c
blobbc47bf463768f1483b3079e20f254a9f400c26b3
1 /*
2 * This file Copyright (C) Mnemosyne LLC
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.
10 * $Id: rpc-server.c 13226 2012-02-15 01:44:21Z jordan $
13 #include <assert.h>
14 #include <errno.h>
15 #include <string.h> /* memcpy */
16 #include <limits.h> /* INT_MAX */
18 #include <unistd.h> /* close */
20 #ifdef HAVE_ZLIB
21 #include <zlib.h>
22 #endif
24 #include <event2/buffer.h>
25 #include <event2/event.h>
26 #include <event2/http.h>
27 #include <event2/http_struct.h> /* TODO: eventually remove this */
29 #include "transmission.h"
30 #include "bencode.h"
31 #include "crypto.h" /* tr_cryptoRandBuf(), tr_ssha1_matches() */
32 #include "fdlimit.h"
33 #include "list.h"
34 #include "net.h"
35 #include "platform.h" /* tr_getWebClientDir() */
36 #include "ptrarray.h"
37 #include "rpcimpl.h"
38 #include "rpc-server.h"
39 #include "session.h"
40 #include "trevent.h"
41 #include "utils.h"
42 #include "web.h"
44 /* session-id is used to make cross-site request forgery attacks difficult.
45 * Don't disable this feature unless you really know what you're doing!
46 * http://en.wikipedia.org/wiki/Cross-site_request_forgery
47 * http://shiflett.org/articles/cross-site-request-forgeries
48 * http://www.webappsec.org/lists/websecurity/archive/2008-04/msg00037.html */
49 #define REQUIRE_SESSION_ID
51 #define MY_NAME "RPC Server"
52 #define MY_REALM "Transmission"
53 #define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
55 struct tr_rpc_server
57 bool isEnabled;
58 bool isPasswordEnabled;
59 bool isWhitelistEnabled;
60 tr_port port;
61 char * url;
62 struct in_addr bindAddress;
63 struct evhttp * httpd;
64 tr_session * session;
65 char * username;
66 char * password;
67 char * whitelistStr;
68 tr_list * whitelist;
70 char * sessionId;
71 time_t sessionIdExpiresAt;
73 #ifdef HAVE_ZLIB
74 bool isStreamInitialized;
75 z_stream stream;
76 #endif
79 #define dbgmsg( ... ) \
80 do { \
81 if( tr_deepLoggingIsActive( ) ) \
82 tr_deepLog( __FILE__, __LINE__, MY_NAME, __VA_ARGS__ ); \
83 } while( 0 )
86 /***
87 ****
88 ***/
90 static char*
91 get_current_session_id( struct tr_rpc_server * server )
93 const time_t now = tr_time( );
95 if( !server->sessionId || ( now >= server->sessionIdExpiresAt ) )
97 int i;
98 const int n = 48;
99 const char * pool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
100 const size_t pool_size = strlen( pool );
101 unsigned char * buf = tr_new( unsigned char, n+1 );
103 tr_cryptoRandBuf( buf, n );
104 for( i=0; i<n; ++i )
105 buf[i] = pool[ buf[i] % pool_size ];
106 buf[n] = '\0';
108 tr_free( server->sessionId );
109 server->sessionId = (char*) buf;
110 server->sessionIdExpiresAt = now + (60*60); /* expire in an hour */
113 return server->sessionId;
121 static void
122 send_simple_response( struct evhttp_request * req,
123 int code,
124 const char * text )
126 const char * code_text = tr_webGetResponseStr( code );
127 struct evbuffer * body = evbuffer_new( );
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 );
134 evbuffer_free( body );
137 struct tr_mimepart
139 char * headers;
140 int headers_len;
141 char * body;
142 int body_len;
145 static void
146 tr_mimepart_free( struct tr_mimepart * p )
148 tr_free( p->body );
149 tr_free( p->headers );
150 tr_free( p );
153 static void
154 extract_parts_from_multipart( const struct evkeyvalq * headers,
155 struct evbuffer * body,
156 tr_ptrArray * setme_parts )
158 const char * content_type = evhttp_find_header( headers, "Content-Type" );
159 const char * in = (const char*) evbuffer_pullup( body, -1 );
160 size_t inlen = evbuffer_get_length( body );
162 const char * boundary_key = "boundary=";
163 const char * boundary_key_begin = content_type ? strstr( content_type, boundary_key ) : NULL;
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 );
168 const char * delim = tr_memmem( in, inlen, boundary, boundary_len );
169 while( delim )
171 size_t part_len;
172 const char * part = delim + boundary_len;
174 inlen -= ( part - in );
175 in = part;
177 delim = tr_memmem( in, inlen, boundary, boundary_len );
178 part_len = delim ? (size_t)( delim - part ) : inlen;
180 if( part_len )
182 const char * rnrn = tr_memmem( part, part_len, "\r\n\r\n", 4 );
183 if( rnrn )
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 );
195 tr_free( boundary );
198 static void
199 handle_upload( struct evhttp_request * req,
200 struct tr_rpc_server * server )
202 if( req->type != EVHTTP_REQ_POST )
204 send_simple_response( req, 405, NULL );
206 else
208 int i;
209 int n;
210 bool hasSessionId = false;
211 tr_ptrArray parts = TR_PTR_ARRAY_INIT;
213 const char * query = strchr( req->uri, '?' );
214 const bool paused = query && strstr( query + 1, "paused=true" );
216 extract_parts_from_multipart( req->input_headers, req->input_buffer, &parts );
217 n = tr_ptrArraySize( &parts );
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;
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 );
232 if( !hasSessionId )
234 int code = 409;
235 const char * codetext = tr_webGetResponseStr( code );
236 struct evbuffer * body = evbuffer_new( );
237 evbuffer_add_printf( body, "%s", "{ \"success\": false, \"msg\": \"Bad Session-Id\" }" );;
238 evhttp_send_reply( req, code, codetext, body );
239 evbuffer_free( body );
241 else for( i=0; i<n; ++i )
243 struct tr_mimepart * p = tr_ptrArrayNth( &parts, i );
244 int body_len = p->body_len;
245 tr_benc top, *args;
246 tr_benc test;
247 bool have_source = false;
248 char * body = p->body;
250 if( body_len >= 2 && !memcmp( &body[body_len - 2], "\r\n", 2 ) )
251 body_len -= 2;
253 tr_bencInitDict( &top, 2 );
254 tr_bencDictAddStr( &top, "method", "torrent-add" );
255 args = tr_bencDictAddDict( &top, "arguments", 2 );
256 tr_bencDictAddBool( args, "paused", paused );
258 if( tr_urlIsValid( body, body_len ) )
260 tr_bencDictAddRaw( args, "filename", body, body_len );
261 have_source = true;
263 else if( !tr_bencLoad( body, body_len, &test, NULL ) )
265 char * b64 = tr_base64_encode( body, body_len, NULL );
266 tr_bencDictAddStr( args, "metainfo", b64 );
267 tr_free( b64 );
268 have_source = true;
271 if( have_source )
273 struct evbuffer * json = tr_bencToBuf( &top, TR_FMT_JSON );
274 tr_rpc_request_exec_json( server->session,
275 evbuffer_pullup( json, -1 ),
276 evbuffer_get_length( json ),
277 NULL, NULL );
278 evbuffer_free( json );
281 tr_bencFree( &top );
284 tr_ptrArrayDestruct( &parts, (PtrArrayForeachFunc)tr_mimepart_free );
286 /* send "success" response */
288 int code = HTTP_OK;
289 const char * codetext = tr_webGetResponseStr( code );
290 struct evbuffer * body = evbuffer_new( );
291 evbuffer_add_printf( body, "%s", "{ \"success\": true, \"msg\": \"Torrent Added\" }" );;
292 evhttp_send_reply( req, code, codetext, body );
293 evbuffer_free( body );
298 static const char*
299 mimetype_guess( const char * path )
301 unsigned int i;
303 const struct
305 const char * suffix;
306 const char * mime_type;
307 } types[] = {
308 /* these are the ones we need for serving the web client's files... */
309 { "css", "text/css" },
310 { "gif", "image/gif" },
311 { "html", "text/html" },
312 { "ico", "image/vnd.microsoft.icon" },
313 { "js", "application/javascript" },
314 { "png", "image/png" }
316 const char * dot = strrchr( path, '.' );
318 for( i = 0; dot && i < TR_N_ELEMENTS( types ); ++i )
319 if( !strcmp( dot + 1, types[i].suffix ) )
320 return types[i].mime_type;
322 return "application/octet-stream";
325 static void
326 add_response( struct evhttp_request * req, struct tr_rpc_server * server,
327 struct evbuffer * out, struct evbuffer * content )
329 #ifndef HAVE_ZLIB
330 evbuffer_add_buffer( out, content );
331 #else
332 const char * key = "Accept-Encoding";
333 const char * encoding = evhttp_find_header( req->input_headers, key );
334 const int do_compress = encoding && strstr( encoding, "gzip" );
336 if( !do_compress )
338 evbuffer_add_buffer( out, content );
340 else
342 int state;
343 struct evbuffer_iovec iovec[1];
344 void * content_ptr = evbuffer_pullup( content, -1 );
345 const size_t content_len = evbuffer_get_length( content );
347 if( !server->isStreamInitialized )
349 int compressionLevel;
351 server->isStreamInitialized = true;
352 server->stream.zalloc = (alloc_func) Z_NULL;
353 server->stream.zfree = (free_func) Z_NULL;
354 server->stream.opaque = (voidpf) Z_NULL;
356 /* zlib's manual says: "Add 16 to windowBits to write a simple gzip header
357 * and trailer around the compressed data instead of a zlib wrapper." */
358 #ifdef TR_LIGHTWEIGHT
359 compressionLevel = Z_DEFAULT_COMPRESSION;
360 #else
361 compressionLevel = Z_BEST_COMPRESSION;
362 #endif
363 deflateInit2( &server->stream, compressionLevel, Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY );
366 server->stream.next_in = content_ptr;
367 server->stream.avail_in = content_len;
369 /* allocate space for the raw data and call deflate() just once --
370 * we won't use the deflated data if it's longer than the raw data,
371 * so it's okay to let deflate() run out of output buffer space */
372 evbuffer_reserve_space( out, content_len, iovec, 1 );
373 server->stream.next_out = iovec[0].iov_base;
374 server->stream.avail_out = iovec[0].iov_len;
375 state = deflate( &server->stream, Z_FINISH );
377 if( state == Z_STREAM_END )
379 iovec[0].iov_len -= server->stream.avail_out;
381 #if 0
382 fprintf( stderr, "compressed response is %.2f of original (raw==%zu bytes; compressed==%zu)\n",
383 (double)evbuffer_get_length(out)/content_len,
384 content_len, evbuffer_get_length(out) );
385 #endif
386 evhttp_add_header( req->output_headers,
387 "Content-Encoding", "gzip" );
389 else
391 memcpy( iovec[0].iov_base, content_ptr, content_len );
392 iovec[0].iov_len = content_len;
395 evbuffer_commit_space( out, iovec, 1 );
396 deflateReset( &server->stream );
398 #endif
401 static void
402 add_time_header( struct evkeyvalq * headers, const char * key, time_t value )
404 /* According to RFC 2616 this must follow RFC 1123's date format,
405 so use gmtime instead of localtime... */
406 char buf[128];
407 struct tm tm = *gmtime( &value );
408 strftime( buf, sizeof( buf ), "%a, %d %b %Y %H:%M:%S GMT", &tm );
409 evhttp_add_header( headers, key, buf );
412 static void
413 evbuffer_ref_cleanup_tr_free( const void * data UNUSED, size_t datalen UNUSED, void * extra )
415 tr_free( extra );
418 static void
419 serve_file( struct evhttp_request * req,
420 struct tr_rpc_server * server,
421 const char * filename )
423 if( req->type != EVHTTP_REQ_GET )
425 evhttp_add_header( req->output_headers, "Allow", "GET" );
426 send_simple_response( req, 405, NULL );
428 else
430 void * file;
431 size_t file_len;
432 struct evbuffer * content;
433 const int error = errno;
435 errno = 0;
436 file_len = 0;
437 file = tr_loadFile( filename, &file_len );
438 content = evbuffer_new( );
439 evbuffer_add_reference( content, file, file_len, evbuffer_ref_cleanup_tr_free, file );
441 if( errno )
443 char * tmp = tr_strdup_printf( "%s (%s)", filename, tr_strerror( errno ) );
444 send_simple_response( req, HTTP_NOTFOUND, tmp );
445 tr_free( tmp );
447 else
449 struct evbuffer * out;
450 const time_t now = tr_time( );
452 errno = error;
453 out = evbuffer_new( );
454 evhttp_add_header( req->output_headers, "Content-Type", mimetype_guess( filename ) );
455 add_time_header( req->output_headers, "Date", now );
456 add_time_header( req->output_headers, "Expires", now+(24*60*60) );
457 add_response( req, server, out, content );
458 evhttp_send_reply( req, HTTP_OK, "OK", out );
460 evbuffer_free( out );
463 evbuffer_free( content );
467 static void
468 handle_web_client( struct evhttp_request * req,
469 struct tr_rpc_server * server )
471 const char * webClientDir = tr_getWebClientDir( server->session );
473 if( !webClientDir || !*webClientDir )
475 send_simple_response( req, HTTP_NOTFOUND,
476 "<p>Couldn't find Transmission's web interface files!</p>"
477 "<p>Users: to tell Transmission where to look, "
478 "set the TRANSMISSION_WEB_HOME environment "
479 "variable to the folder where the web interface's "
480 "index.html is located.</p>"
481 "<p>Package Builders: to set a custom default at compile time, "
482 "#define PACKAGE_DATA_DIR in libtransmission/platform.c "
483 "or tweak tr_getClutchDir() by hand.</p>" );
485 else
487 char * pch;
488 char * subpath;
490 subpath = tr_strdup( req->uri + strlen( server->url ) + 4 );
491 if(( pch = strchr( subpath, '?' )))
492 *pch = '\0';
494 if( strstr( subpath, ".." ) )
496 send_simple_response( req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>" );
498 else
500 char * filename = tr_strdup_printf( "%s%s%s",
501 webClientDir,
502 TR_PATH_DELIMITER_STR,
503 subpath && *subpath ? subpath : "index.html" );
504 serve_file( req, server, filename );
505 tr_free( filename );
508 tr_free( subpath );
512 struct rpc_response_data
514 struct evhttp_request * req;
515 struct tr_rpc_server * server;
518 static void
519 rpc_response_func( tr_session * session UNUSED,
520 struct evbuffer * response,
521 void * user_data )
523 struct rpc_response_data * data = user_data;
524 struct evbuffer * buf = evbuffer_new( );
526 add_response( data->req, data->server, buf, response );
527 evhttp_add_header( data->req->output_headers,
528 "Content-Type", "application/json; charset=UTF-8" );
529 evhttp_send_reply( data->req, HTTP_OK, "OK", buf );
531 evbuffer_free( buf );
532 tr_free( data );
536 static void
537 handle_rpc( struct evhttp_request * req,
538 struct tr_rpc_server * server )
540 struct rpc_response_data * data = tr_new0( struct rpc_response_data, 1 );
542 data->req = req;
543 data->server = server;
545 if( req->type == EVHTTP_REQ_GET )
547 const char * q;
548 if( ( q = strchr( req->uri, '?' ) ) )
549 tr_rpc_request_exec_uri( server->session, q+1, -1, rpc_response_func, data );
551 else if( req->type == EVHTTP_REQ_POST )
553 tr_rpc_request_exec_json( server->session,
554 evbuffer_pullup( req->input_buffer, -1 ),
555 evbuffer_get_length( req->input_buffer ),
556 rpc_response_func, data );
561 static bool
562 isAddressAllowed( const tr_rpc_server * server,
563 const char * address )
565 tr_list * l;
567 if( !server->isWhitelistEnabled )
568 return true;
570 for( l=server->whitelist; l!=NULL; l=l->next )
571 if( tr_wildmat( address, l->data ) )
572 return true;
574 return false;
577 static bool
578 test_session_id( struct tr_rpc_server * server, struct evhttp_request * req )
580 const char * ours = get_current_session_id( server );
581 const char * theirs = evhttp_find_header( req->input_headers, TR_RPC_SESSION_ID_HEADER );
582 const bool success = theirs && !strcmp( theirs, ours );
583 return success;
586 static void
587 handle_request( struct evhttp_request * req, void * arg )
589 struct tr_rpc_server * server = arg;
591 if( req && req->evcon )
593 const char * auth;
594 char * user = NULL;
595 char * pass = NULL;
597 evhttp_add_header( req->output_headers, "Server", MY_REALM );
599 auth = evhttp_find_header( req->input_headers, "Authorization" );
600 if( auth && !evutil_ascii_strncasecmp( auth, "basic ", 6 ) )
602 int plen;
603 char * p = tr_base64_decode( auth + 6, 0, &plen );
604 if( p && plen && ( ( pass = strchr( p, ':' ) ) ) )
606 user = p;
607 *pass++ = '\0';
611 if( !isAddressAllowed( server, req->remote_host ) )
613 send_simple_response( req, 403,
614 "<p>Unauthorized IP Address.</p>"
615 "<p>Either disable the IP address whitelist or add your address to it.</p>"
616 "<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>"
617 "<p>If you're still using ACLs, use a whitelist instead. See the transmission-daemon manpage for details.</p>" );
619 else if( server->isPasswordEnabled
620 && ( !pass || !user || strcmp( server->username, user )
621 || !tr_ssha1_matches( server->password,
622 pass ) ) )
624 evhttp_add_header( req->output_headers,
625 "WWW-Authenticate",
626 "Basic realm=\"" MY_REALM "\"" );
627 send_simple_response( req, 401, "Unauthorized User" );
629 else if( strncmp( req->uri, server->url, strlen( server->url ) ) )
631 char * location = tr_strdup_printf( "%sweb/", server->url );
632 evhttp_add_header( req->output_headers, "Location", location );
633 send_simple_response( req, HTTP_MOVEPERM, NULL );
634 tr_free( location );
636 else if( !strncmp( req->uri + strlen( server->url ), "web/", 4 ) )
638 handle_web_client( req, server );
640 else if( !strncmp( req->uri + strlen( server->url ), "upload", 6 ) )
642 handle_upload( req, server );
644 #ifdef REQUIRE_SESSION_ID
645 else if( !test_session_id( server, req ) )
647 const char * sessionId = get_current_session_id( server );
648 char * tmp = tr_strdup_printf(
649 "<p>Your request had an invalid session-id header.</p>"
650 "<p>To fix this, follow these steps:"
651 "<ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it"
652 "<li> Add the updated header to your outgoing requests"
653 "<li> When you get this 409 error message, resend your request with the updated header"
654 "</ol></p>"
655 "<p>This requirement has been added to help prevent "
656 "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
657 "attacks.</p>"
658 "<p><code>%s: %s</code></p>",
659 TR_RPC_SESSION_ID_HEADER, sessionId );
660 evhttp_add_header( req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId );
661 send_simple_response( req, 409, tmp );
662 tr_free( tmp );
664 #endif
665 else if( !strncmp( req->uri + strlen( server->url ), "rpc", 3 ) )
667 handle_rpc( req, server );
669 else
671 send_simple_response( req, HTTP_NOTFOUND, req->uri );
674 tr_free( user );
678 static void
679 startServer( void * vserver )
681 tr_rpc_server * server = vserver;
682 tr_address addr;
684 if( !server->httpd )
686 addr.type = TR_AF_INET;
687 addr.addr.addr4 = server->bindAddress;
688 server->httpd = evhttp_new( server->session->event_base );
689 evhttp_bind_socket( server->httpd, tr_address_to_string( &addr ), server->port );
690 evhttp_set_gencb( server->httpd, handle_request, server );
695 static void
696 stopServer( tr_rpc_server * server )
698 if( server->httpd )
700 evhttp_free( server->httpd );
701 server->httpd = NULL;
705 static void
706 onEnabledChanged( void * vserver )
708 tr_rpc_server * server = vserver;
710 if( !server->isEnabled )
711 stopServer( server );
712 else
713 startServer( server );
716 void
717 tr_rpcSetEnabled( tr_rpc_server * server,
718 bool isEnabled )
720 server->isEnabled = isEnabled;
722 tr_runInEventThread( server->session, onEnabledChanged, server );
725 bool
726 tr_rpcIsEnabled( const tr_rpc_server * server )
728 return server->isEnabled;
731 static void
732 restartServer( void * vserver )
734 tr_rpc_server * server = vserver;
736 if( server->isEnabled )
738 stopServer( server );
739 startServer( server );
743 void
744 tr_rpcSetPort( tr_rpc_server * server,
745 tr_port port )
747 assert( server != NULL );
749 if( server->port != port )
751 server->port = port;
753 if( server->isEnabled )
754 tr_runInEventThread( server->session, restartServer, server );
758 tr_port
759 tr_rpcGetPort( const tr_rpc_server * server )
761 return server->port;
764 void
765 tr_rpcSetUrl( tr_rpc_server * server, const char * url )
767 char * tmp = server->url;
768 server->url = tr_strdup( url );
769 dbgmsg( "setting our URL to [%s]", server->url );
770 tr_free( tmp );
773 const char*
774 tr_rpcGetUrl( const tr_rpc_server * server )
776 return server->url ? server->url : "";
779 void
780 tr_rpcSetWhitelist( tr_rpc_server * server, const char * whitelistStr )
782 void * tmp;
783 const char * walk;
785 /* keep the string */
786 tmp = server->whitelistStr;
787 server->whitelistStr = tr_strdup( whitelistStr );
788 tr_free( tmp );
790 /* clear out the old whitelist entries */
791 while(( tmp = tr_list_pop_front( &server->whitelist )))
792 tr_free( tmp );
794 /* build the new whitelist entries */
795 for( walk=whitelistStr; walk && *walk; ) {
796 const char * delimiters = " ,;";
797 const size_t len = strcspn( walk, delimiters );
798 char * token = tr_strndup( walk, len );
799 tr_list_append( &server->whitelist, token );
800 if( strcspn( token, "+-" ) < len )
801 tr_ninf( MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'! Are you using an old ACL by mistake?)", token );
802 else
803 tr_ninf( MY_NAME, "Adding address to whitelist: %s", token );
805 if( walk[len]=='\0' )
806 break;
807 walk += len + 1;
811 const char*
812 tr_rpcGetWhitelist( const tr_rpc_server * server )
814 return server->whitelistStr ? server->whitelistStr : "";
817 void
818 tr_rpcSetWhitelistEnabled( tr_rpc_server * server,
819 bool isEnabled )
821 server->isWhitelistEnabled = isEnabled != 0;
824 bool
825 tr_rpcGetWhitelistEnabled( const tr_rpc_server * server )
827 return server->isWhitelistEnabled;
830 /****
831 ***** PASSWORD
832 ****/
834 void
835 tr_rpcSetUsername( tr_rpc_server * server, const char * username )
837 char * tmp = server->username;
838 server->username = tr_strdup( username );
839 dbgmsg( "setting our Username to [%s]", server->username );
840 tr_free( tmp );
843 const char*
844 tr_rpcGetUsername( const tr_rpc_server * server )
846 return server->username ? server->username : "";
849 void
850 tr_rpcSetPassword( tr_rpc_server * server,
851 const char * password )
853 tr_free( server->password );
854 if( *password != '{' )
855 server->password = tr_ssha1( password );
856 else
857 server->password = strdup( password );
858 dbgmsg( "setting our Password to [%s]", server->password );
861 const char*
862 tr_rpcGetPassword( const tr_rpc_server * server )
864 return server->password ? server->password : "" ;
867 void
868 tr_rpcSetPasswordEnabled( tr_rpc_server * server, bool isEnabled )
870 server->isPasswordEnabled = isEnabled;
871 dbgmsg( "setting 'password enabled' to %d", (int)isEnabled );
874 bool
875 tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
877 return server->isPasswordEnabled;
880 const char *
881 tr_rpcGetBindAddress( const tr_rpc_server * server )
883 tr_address addr;
884 addr.type = TR_AF_INET;
885 addr.addr.addr4 = server->bindAddress;
886 return tr_address_to_string( &addr );
889 /****
890 ***** LIFE CYCLE
891 ****/
893 static void
894 closeServer( void * vserver )
896 void * tmp;
897 tr_rpc_server * s = vserver;
899 stopServer( s );
900 while(( tmp = tr_list_pop_front( &s->whitelist )))
901 tr_free( tmp );
902 #ifdef HAVE_ZLIB
903 if( s->isStreamInitialized )
904 deflateEnd( &s->stream );
905 #endif
906 tr_free( s->url );
907 tr_free( s->sessionId );
908 tr_free( s->whitelistStr );
909 tr_free( s->username );
910 tr_free( s->password );
911 tr_free( s );
914 void
915 tr_rpcClose( tr_rpc_server ** ps )
917 tr_runInEventThread( ( *ps )->session, closeServer, *ps );
918 *ps = NULL;
921 tr_rpc_server *
922 tr_rpcInit( tr_session * session, tr_benc * settings )
924 tr_rpc_server * s;
925 bool boolVal;
926 int64_t i;
927 const char * str;
928 const char * key;
929 tr_address address;
931 s = tr_new0( tr_rpc_server, 1 );
932 s->session = session;
934 key = TR_PREFS_KEY_RPC_ENABLED;
935 if( !tr_bencDictFindBool( settings, key, &boolVal ) )
936 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
937 else
938 s->isEnabled = boolVal;
940 key = TR_PREFS_KEY_RPC_PORT;
941 if( !tr_bencDictFindInt( settings, key, &i ) )
942 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
943 else
944 s->port = i;
946 key = TR_PREFS_KEY_RPC_URL;
947 if( !tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_URL, &str ) )
948 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
949 else
950 s->url = tr_strdup( str );
952 key = TR_PREFS_KEY_RPC_WHITELIST_ENABLED;
953 if( !tr_bencDictFindBool( settings, key, &boolVal ) )
954 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
955 else
956 tr_rpcSetWhitelistEnabled( s, boolVal );
958 key = TR_PREFS_KEY_RPC_AUTH_REQUIRED;
959 if( !tr_bencDictFindBool( settings, key, &boolVal ) )
960 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
961 else
962 tr_rpcSetPasswordEnabled( s, boolVal );
964 key = TR_PREFS_KEY_RPC_WHITELIST;
965 if( !tr_bencDictFindStr( settings, key, &str ) && str )
966 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
967 else
968 tr_rpcSetWhitelist( s, str );
970 key = TR_PREFS_KEY_RPC_USERNAME;
971 if( !tr_bencDictFindStr( settings, key, &str ) )
972 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
973 else
974 tr_rpcSetUsername( s, str );
976 key = TR_PREFS_KEY_RPC_PASSWORD;
977 if( !tr_bencDictFindStr( settings, key, &str ) )
978 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
979 else
980 tr_rpcSetPassword( s, str );
982 key = TR_PREFS_KEY_RPC_BIND_ADDRESS;
983 if( !tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_BIND_ADDRESS, &str ) ) {
984 tr_nerr( MY_NAME, _( "Couldn't find settings key \"%s\"" ), key );
985 address = tr_inaddr_any;
986 } else if( !tr_address_from_string( &address, str ) ) {
987 tr_nerr( MY_NAME, _( "%s is not a valid address" ), str );
988 address = tr_inaddr_any;
989 } else if( address.type != TR_AF_INET ) {
990 tr_nerr( MY_NAME, _( "%s is not an IPv4 address. RPC listeners must be IPv4" ), str );
991 address = tr_inaddr_any;
993 s->bindAddress = address.addr.addr4;
995 if( s->isEnabled )
997 tr_ninf( MY_NAME, _( "Serving RPC and Web requests on port 127.0.0.1:%d%s" ), (int) s->port, s->url );
998 tr_runInEventThread( session, startServer, s );
1000 if( s->isWhitelistEnabled )
1001 tr_ninf( MY_NAME, "%s", _( "Whitelist enabled" ) );
1003 if( s->isPasswordEnabled )
1004 tr_ninf( MY_NAME, "%s", _( "Password required" ) );
1007 return s;