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 $
15 #include <string.h> /* memcpy */
16 #include <limits.h> /* INT_MAX */
18 #include <unistd.h> /* close */
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"
31 #include "crypto.h" /* tr_cryptoRandBuf(), tr_ssha1_matches() */
35 #include "platform.h" /* tr_getWebClientDir() */
38 #include "rpc-server.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 ) )
58 bool isPasswordEnabled
;
59 bool isWhitelistEnabled
;
62 struct in_addr bindAddress
;
63 struct evhttp
* httpd
;
71 time_t sessionIdExpiresAt
;
74 bool isStreamInitialized
;
79 #define dbgmsg( ... ) \
81 if( tr_deepLoggingIsActive( ) ) \
82 tr_deepLog( __FILE__, __LINE__, MY_NAME, __VA_ARGS__ ); \
91 get_current_session_id( struct tr_rpc_server
* server
)
93 const time_t now
= tr_time( );
95 if( !server
->sessionId
|| ( now
>= server
->sessionIdExpiresAt
) )
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
);
105 buf
[i
] = pool
[ buf
[i
] % pool_size
];
108 tr_free( server
->sessionId
);
109 server
->sessionId
= (char*) buf
;
110 server
->sessionIdExpiresAt
= now
+ (60*60); /* expire in an hour */
113 return server
->sessionId
;
122 send_simple_response( struct evhttp_request
* req
,
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
);
131 evbuffer_add_printf( body
, "%s", text
);
132 evhttp_send_reply( req
, code
, code_text
, body
);
134 evbuffer_free( body
);
146 tr_mimepart_free( struct tr_mimepart
* p
)
149 tr_free( p
->headers
);
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
);
172 const char * part
= delim
+ boundary_len
;
174 inlen
-= ( part
- in
);
177 delim
= tr_memmem( in
, inlen
, boundary
, boundary_len
);
178 part_len
= delim
? (size_t)( delim
- part
) : inlen
;
182 const char * rnrn
= tr_memmem( part
, part_len
, "\r\n\r\n", 4 );
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
);
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
);
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
) ) )
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
);
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
;
247 bool have_source
= false;
248 char * body
= p
->body
;
250 if( body_len
>= 2 && !memcmp( &body
[body_len
- 2], "\r\n", 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
);
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
);
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
),
278 evbuffer_free( json
);
284 tr_ptrArrayDestruct( &parts
, (PtrArrayForeachFunc
)tr_mimepart_free
);
286 /* send "success" response */
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
);
299 mimetype_guess( const char * path
)
306 const char * mime_type
;
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";
326 add_response( struct evhttp_request
* req
, struct tr_rpc_server
* server
,
327 struct evbuffer
* out
, struct evbuffer
* content
)
330 evbuffer_add_buffer( out
, content
);
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" );
338 evbuffer_add_buffer( out
, content
);
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
;
361 compressionLevel
= Z_BEST_COMPRESSION
;
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
;
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
) );
386 evhttp_add_header( req
->output_headers
,
387 "Content-Encoding", "gzip" );
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
);
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... */
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
);
413 evbuffer_ref_cleanup_tr_free( const void * data UNUSED
, size_t datalen UNUSED
, void * extra
)
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
);
432 struct evbuffer
* content
;
433 const int error
= errno
;
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
);
443 char * tmp
= tr_strdup_printf( "%s (%s)", filename
, tr_strerror( errno
) );
444 send_simple_response( req
, HTTP_NOTFOUND
, tmp
);
449 struct evbuffer
* out
;
450 const time_t now
= tr_time( );
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
);
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>" );
490 subpath
= tr_strdup( req
->uri
+ strlen( server
->url
) + 4 );
491 if(( pch
= strchr( subpath
, '?' )))
494 if( strstr( subpath
, ".." ) )
496 send_simple_response( req
, HTTP_NOTFOUND
, "<p>Tsk, tsk.</p>" );
500 char * filename
= tr_strdup_printf( "%s%s%s",
502 TR_PATH_DELIMITER_STR
,
503 subpath
&& *subpath
? subpath
: "index.html" );
504 serve_file( req
, server
, filename
);
512 struct rpc_response_data
514 struct evhttp_request
* req
;
515 struct tr_rpc_server
* server
;
519 rpc_response_func( tr_session
* session UNUSED
,
520 struct evbuffer
* response
,
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
);
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 );
543 data
->server
= server
;
545 if( req
->type
== EVHTTP_REQ_GET
)
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
);
562 isAddressAllowed( const tr_rpc_server
* server
,
563 const char * address
)
567 if( !server
->isWhitelistEnabled
)
570 for( l
=server
->whitelist
; l
!=NULL
; l
=l
->next
)
571 if( tr_wildmat( address
, l
->data
) )
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
);
587 handle_request( struct evhttp_request
* req
, void * arg
)
589 struct tr_rpc_server
* server
= arg
;
591 if( req
&& req
->evcon
)
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 ) )
603 char * p
= tr_base64_decode( auth
+ 6, 0, &plen
);
604 if( p
&& plen
&& ( ( pass
= strchr( p
, ':' ) ) ) )
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
,
624 evhttp_add_header( req
->output_headers
,
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
);
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"
655 "<p>This requirement has been added to help prevent "
656 "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
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
);
665 else if( !strncmp( req
->uri
+ strlen( server
->url
), "rpc", 3 ) )
667 handle_rpc( req
, server
);
671 send_simple_response( req
, HTTP_NOTFOUND
, req
->uri
);
679 startServer( void * vserver
)
681 tr_rpc_server
* server
= vserver
;
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
);
696 stopServer( tr_rpc_server
* server
)
700 evhttp_free( server
->httpd
);
701 server
->httpd
= NULL
;
706 onEnabledChanged( void * vserver
)
708 tr_rpc_server
* server
= vserver
;
710 if( !server
->isEnabled
)
711 stopServer( server
);
713 startServer( server
);
717 tr_rpcSetEnabled( tr_rpc_server
* server
,
720 server
->isEnabled
= isEnabled
;
722 tr_runInEventThread( server
->session
, onEnabledChanged
, server
);
726 tr_rpcIsEnabled( const tr_rpc_server
* server
)
728 return server
->isEnabled
;
732 restartServer( void * vserver
)
734 tr_rpc_server
* server
= vserver
;
736 if( server
->isEnabled
)
738 stopServer( server
);
739 startServer( server
);
744 tr_rpcSetPort( tr_rpc_server
* server
,
747 assert( server
!= NULL
);
749 if( server
->port
!= port
)
753 if( server
->isEnabled
)
754 tr_runInEventThread( server
->session
, restartServer
, server
);
759 tr_rpcGetPort( const tr_rpc_server
* server
)
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
);
774 tr_rpcGetUrl( const tr_rpc_server
* server
)
776 return server
->url
? server
->url
: "";
780 tr_rpcSetWhitelist( tr_rpc_server
* server
, const char * whitelistStr
)
785 /* keep the string */
786 tmp
= server
->whitelistStr
;
787 server
->whitelistStr
= tr_strdup( whitelistStr
);
790 /* clear out the old whitelist entries */
791 while(( tmp
= tr_list_pop_front( &server
->whitelist
)))
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
);
803 tr_ninf( MY_NAME
, "Adding address to whitelist: %s", token
);
805 if( walk
[len
]=='\0' )
812 tr_rpcGetWhitelist( const tr_rpc_server
* server
)
814 return server
->whitelistStr
? server
->whitelistStr
: "";
818 tr_rpcSetWhitelistEnabled( tr_rpc_server
* server
,
821 server
->isWhitelistEnabled
= isEnabled
!= 0;
825 tr_rpcGetWhitelistEnabled( const tr_rpc_server
* server
)
827 return server
->isWhitelistEnabled
;
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
);
844 tr_rpcGetUsername( const tr_rpc_server
* server
)
846 return server
->username
? server
->username
: "";
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
);
857 server
->password
= strdup( password
);
858 dbgmsg( "setting our Password to [%s]", server
->password
);
862 tr_rpcGetPassword( const tr_rpc_server
* server
)
864 return server
->password
? server
->password
: "" ;
868 tr_rpcSetPasswordEnabled( tr_rpc_server
* server
, bool isEnabled
)
870 server
->isPasswordEnabled
= isEnabled
;
871 dbgmsg( "setting 'password enabled' to %d", (int)isEnabled
);
875 tr_rpcIsPasswordEnabled( const tr_rpc_server
* server
)
877 return server
->isPasswordEnabled
;
881 tr_rpcGetBindAddress( const tr_rpc_server
* server
)
884 addr
.type
= TR_AF_INET
;
885 addr
.addr
.addr4
= server
->bindAddress
;
886 return tr_address_to_string( &addr
);
894 closeServer( void * vserver
)
897 tr_rpc_server
* s
= vserver
;
900 while(( tmp
= tr_list_pop_front( &s
->whitelist
)))
903 if( s
->isStreamInitialized
)
904 deflateEnd( &s
->stream
);
907 tr_free( s
->sessionId
);
908 tr_free( s
->whitelistStr
);
909 tr_free( s
->username
);
910 tr_free( s
->password
);
915 tr_rpcClose( tr_rpc_server
** ps
)
917 tr_runInEventThread( ( *ps
)->session
, closeServer
, *ps
);
922 tr_rpcInit( tr_session
* session
, tr_benc
* settings
)
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
;
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" ) );