1/* 2 * This file Copyright (C) Mnemosyne LLC 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 13226 2012-02-15 01:44:21Z jordan $ 11 */ 12 13#include <assert.h> 14#include <errno.h> 15#include <string.h> /* memcpy */ 16#include <limits.h> /* INT_MAX */ 17 18#include <unistd.h> /* close */ 19 20#ifdef HAVE_ZLIB 21 #include <zlib.h> 22#endif 23 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 */ 28 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" 43 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 50 51#define MY_NAME "RPC Server" 52#define MY_REALM "Transmission" 53#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) ) 54 55struct tr_rpc_server 56{ 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; 69 70 char * sessionId; 71 time_t sessionIdExpiresAt; 72 73#ifdef HAVE_ZLIB 74 bool isStreamInitialized; 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 char* 91get_current_session_id( struct tr_rpc_server * server ) 92{ 93 const time_t now = tr_time( ); 94 95 if( !server->sessionId || ( now >= server->sessionIdExpiresAt ) ) 96 { 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 ); 102 103 tr_cryptoRandBuf( buf, n ); 104 for( i=0; i<n; ++i ) 105 buf[i] = pool[ buf[i] % pool_size ]; 106 buf[n] = '\0'; 107 108 tr_free( server->sessionId ); 109 server->sessionId = (char*) buf; 110 server->sessionIdExpiresAt = now + (60*60); /* expire in an hour */ 111 } 112 113 return server->sessionId; 114} 115 116 117/** 118*** 119**/ 120 121static void 122send_simple_response( struct evhttp_request * req, 123 int code, 124 const char * text ) 125{ 126 const char * code_text = tr_webGetResponseStr( code ); 127 struct evbuffer * body = evbuffer_new( ); 128 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 ); 133 134 evbuffer_free( body ); 135} 136 137struct tr_mimepart 138{ 139 char * headers; 140 int headers_len; 141 char * body; 142 int body_len; 143}; 144 145static void 146tr_mimepart_free( struct tr_mimepart * p ) 147{ 148 tr_free( p->body ); 149 tr_free( p->headers ); 150 tr_free( p ); 151} 152 153static void 154extract_parts_from_multipart( const struct evkeyvalq * headers, 155 struct evbuffer * body, 156 tr_ptrArray * setme_parts ) 157{ 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 ); 161 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 ); 167 168 const char * delim = tr_memmem( in, inlen, boundary, boundary_len ); 169 while( delim ) 170 { 171 size_t part_len; 172 const char * part = delim + boundary_len; 173 174 inlen -= ( part - in ); 175 in = part; 176 177 delim = tr_memmem( in, inlen, boundary, boundary_len ); 178 part_len = delim ? (size_t)( delim - part ) : inlen; 179 180 if( part_len ) 181 { 182 const char * rnrn = tr_memmem( part, part_len, "\r\n\r\n", 4 ); 183 if( rnrn ) 184 { 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 ); 191 } 192 } 193 } 194 195 tr_free( boundary ); 196} 197 198static void 199handle_upload( struct evhttp_request * req, 200 struct tr_rpc_server * server ) 201{ 202 if( req->type != EVHTTP_REQ_POST ) 203 { 204 send_simple_response( req, 405, NULL ); 205 } 206 else 207 { 208 int i; 209 int n; 210 bool hasSessionId = false; 211 tr_ptrArray parts = TR_PTR_ARRAY_INIT; 212 213 const char * query = strchr( req->uri, '?' ); 214 const bool paused = query && strstr( query + 1, "paused=true" ); 215 216 extract_parts_from_multipart( req->input_headers, req->input_buffer, &parts ); 217 n = tr_ptrArraySize( &parts ); 218 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; 224 } 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 ); 230 } 231 232 if( !hasSessionId ) 233 { 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 ); 240 } 241 else for( i=0; i<n; ++i ) 242 { 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; 249 250 if( body_len >= 2 && !memcmp( &body[body_len - 2], "\r\n", 2 ) ) 251 body_len -= 2; 252 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 ); 257 258 if( tr_urlIsValid( body, body_len ) ) 259 { 260 tr_bencDictAddRaw( args, "filename", body, body_len ); 261 have_source = true; 262 } 263 else if( !tr_bencLoad( body, body_len, &test, NULL ) ) 264 { 265 char * b64 = tr_base64_encode( body, body_len, NULL ); 266 tr_bencDictAddStr( args, "metainfo", b64 ); 267 tr_free( b64 ); 268 have_source = true; 269 } 270 271 if( have_source ) 272 { 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 ); 279 } 280 281 tr_bencFree( &top ); 282 } 283 284 tr_ptrArrayDestruct( &parts, (PtrArrayForeachFunc)tr_mimepart_free ); 285 286 /* send "success" response */ 287 { 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 ); 294 } 295 } 296} 297 298static const char* 299mimetype_guess( const char * path ) 300{ 301 unsigned int i; 302 303 const struct 304 { 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" } 315 }; 316 const char * dot = strrchr( path, '.' ); 317 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; 321 322 return "application/octet-stream"; 323} 324 325static void 326add_response( struct evhttp_request * req, struct tr_rpc_server * server, 327 struct evbuffer * out, struct evbuffer * content ) 328{ 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" ); 335 336 if( !do_compress ) 337 { 338 evbuffer_add_buffer( out, content ); 339 } 340 else 341 { 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 ); 346 347 if( !server->isStreamInitialized ) 348 { 349 int compressionLevel; 350 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; 355 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 ); 364 } 365 366 server->stream.next_in = content_ptr; 367 server->stream.avail_in = content_len; 368 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 ); 376 377 if( state == Z_STREAM_END ) 378 { 379 iovec[0].iov_len -= server->stream.avail_out; 380 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" ); 388 } 389 else 390 { 391 memcpy( iovec[0].iov_base, content_ptr, content_len ); 392 iovec[0].iov_len = content_len; 393 } 394 395 evbuffer_commit_space( out, iovec, 1 ); 396 deflateReset( &server->stream ); 397 } 398#endif 399} 400 401static void 402add_time_header( struct evkeyvalq * headers, const char * key, time_t value ) 403{ 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 ); 410} 411 412static void 413evbuffer_ref_cleanup_tr_free( const void * data UNUSED, size_t datalen UNUSED, void * extra ) 414{ 415 tr_free( extra ); 416} 417 418static void 419serve_file( struct evhttp_request * req, 420 struct tr_rpc_server * server, 421 const char * filename ) 422{ 423 if( req->type != EVHTTP_REQ_GET ) 424 { 425 evhttp_add_header( req->output_headers, "Allow", "GET" ); 426 send_simple_response( req, 405, NULL ); 427 } 428 else 429 { 430 void * file; 431 size_t file_len; 432 struct evbuffer * content; 433 const int error = errno; 434 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 ); 440 441 if( errno ) 442 { 443 char * tmp = tr_strdup_printf( "%s (%s)", filename, tr_strerror( errno ) ); 444 send_simple_response( req, HTTP_NOTFOUND, tmp ); 445 tr_free( tmp ); 446 } 447 else 448 { 449 struct evbuffer * out; 450 const time_t now = tr_time( ); 451 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 ); 459 460 evbuffer_free( out ); 461 } 462 463 evbuffer_free( content ); 464 } 465} 466 467static void 468handle_web_client( struct evhttp_request * req, 469 struct tr_rpc_server * server ) 470{ 471 const char * webClientDir = tr_getWebClientDir( server->session ); 472 473 if( !webClientDir || !*webClientDir ) 474 { 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>" ); 484 } 485 else 486 { 487 char * pch; 488 char * subpath; 489 490 subpath = tr_strdup( req->uri + strlen( server->url ) + 4 ); 491 if(( pch = strchr( subpath, '?' ))) 492 *pch = '\0'; 493 494 if( strstr( subpath, ".." ) ) 495 { 496 send_simple_response( req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>" ); 497 } 498 else 499 { 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 ); 506 } 507 508 tr_free( subpath ); 509 } 510} 511 512struct rpc_response_data 513{ 514 struct evhttp_request * req; 515 struct tr_rpc_server * server; 516}; 517 518static void 519rpc_response_func( tr_session * session UNUSED, 520 struct evbuffer * response, 521 void * user_data ) 522{ 523 struct rpc_response_data * data = user_data; 524 struct evbuffer * buf = evbuffer_new( ); 525 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 ); 530 531 evbuffer_free( buf ); 532 tr_free( data ); 533} 534 535 536static void 537handle_rpc( struct evhttp_request * req, 538 struct tr_rpc_server * server ) 539{ 540 struct rpc_response_data * data = tr_new0( struct rpc_response_data, 1 ); 541 542 data->req = req; 543 data->server = server; 544 545 if( req->type == EVHTTP_REQ_GET ) 546 { 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 ); 550 } 551 else if( req->type == EVHTTP_REQ_POST ) 552 { 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 ); 557 } 558 559} 560 561static bool 562isAddressAllowed( const tr_rpc_server * server, 563 const char * address ) 564{ 565 tr_list * l; 566 567 if( !server->isWhitelistEnabled ) 568 return true; 569 570 for( l=server->whitelist; l!=NULL; l=l->next ) 571 if( tr_wildmat( address, l->data ) ) 572 return true; 573 574 return false; 575} 576 577static bool 578test_session_id( struct tr_rpc_server * server, struct evhttp_request * req ) 579{ 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; 584} 585 586static void 587handle_request( struct evhttp_request * req, void * arg ) 588{ 589 struct tr_rpc_server * server = arg; 590 591 if( req && req->evcon ) 592 { 593 const char * auth; 594 char * user = NULL; 595 char * pass = NULL; 596 597 evhttp_add_header( req->output_headers, "Server", MY_REALM ); 598 599 auth = evhttp_find_header( req->input_headers, "Authorization" ); 600 if( auth && !evutil_ascii_strncasecmp( auth, "basic ", 6 ) ) 601 { 602 int plen; 603 char * p = tr_base64_decode( auth + 6, 0, &plen ); 604 if( p && plen && ( ( pass = strchr( p, ':' ) ) ) ) 605 { 606 user = p; 607 *pass++ = '\0'; 608 } 609 } 610 611 if( !isAddressAllowed( server, req->remote_host ) ) 612 { 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>" ); 618 } 619 else if( server->isPasswordEnabled 620 && ( !pass || !user || strcmp( server->username, user ) 621 || !tr_ssha1_matches( server->password, 622 pass ) ) ) 623 { 624 evhttp_add_header( req->output_headers, 625 "WWW-Authenticate", 626 "Basic realm=\"" MY_REALM "\"" ); 627 send_simple_response( req, 401, "Unauthorized User" ); 628 } 629 else if( strncmp( req->uri, server->url, strlen( server->url ) ) ) 630 { 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 ); 635 } 636 else if( !strncmp( req->uri + strlen( server->url ), "web/", 4 ) ) 637 { 638 handle_web_client( req, server ); 639 } 640 else if( !strncmp( req->uri + strlen( server->url ), "upload", 6 ) ) 641 { 642 handle_upload( req, server ); 643 } 644#ifdef REQUIRE_SESSION_ID 645 else if( !test_session_id( server, req ) ) 646 { 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 ); 663 } 664#endif 665 else if( !strncmp( req->uri + strlen( server->url ), "rpc", 3 ) ) 666 { 667 handle_rpc( req, server ); 668 } 669 else 670 { 671 send_simple_response( req, HTTP_NOTFOUND, req->uri ); 672 } 673 674 tr_free( user ); 675 } 676} 677 678static void 679startServer( void * vserver ) 680{ 681 tr_rpc_server * server = vserver; 682 tr_address addr; 683 684 if( !server->httpd ) 685 { 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 ); 691 692 } 693} 694 695static void 696stopServer( tr_rpc_server * server ) 697{ 698 if( server->httpd ) 699 { 700 evhttp_free( server->httpd ); 701 server->httpd = NULL; 702 } 703} 704 705static void 706onEnabledChanged( void * vserver ) 707{ 708 tr_rpc_server * server = vserver; 709 710 if( !server->isEnabled ) 711 stopServer( server ); 712 else 713 startServer( server ); 714} 715 716void 717tr_rpcSetEnabled( tr_rpc_server * server, 718 bool isEnabled ) 719{ 720 server->isEnabled = isEnabled; 721 722 tr_runInEventThread( server->session, onEnabledChanged, server ); 723} 724 725bool 726tr_rpcIsEnabled( const tr_rpc_server * server ) 727{ 728 return server->isEnabled; 729} 730 731static void 732restartServer( void * vserver ) 733{ 734 tr_rpc_server * server = vserver; 735 736 if( server->isEnabled ) 737 { 738 stopServer( server ); 739 startServer( server ); 740 } 741} 742 743void 744tr_rpcSetPort( tr_rpc_server * server, 745 tr_port port ) 746{ 747 assert( server != NULL ); 748 749 if( server->port != port ) 750 { 751 server->port = port; 752 753 if( server->isEnabled ) 754 tr_runInEventThread( server->session, restartServer, server ); 755 } 756} 757 758tr_port 759tr_rpcGetPort( const tr_rpc_server * server ) 760{ 761 return server->port; 762} 763 764void 765tr_rpcSetUrl( tr_rpc_server * server, const char * url ) 766{ 767 char * tmp = server->url; 768 server->url = tr_strdup( url ); 769 dbgmsg( "setting our URL to [%s]", server->url ); 770 tr_free( tmp ); 771} 772 773const char* 774tr_rpcGetUrl( const tr_rpc_server * server ) 775{ 776 return server->url ? server->url : ""; 777} 778 779void 780tr_rpcSetWhitelist( tr_rpc_server * server, const char * whitelistStr ) 781{ 782 void * tmp; 783 const char * walk; 784 785 /* keep the string */ 786 tmp = server->whitelistStr; 787 server->whitelistStr = tr_strdup( whitelistStr ); 788 tr_free( tmp ); 789 790 /* clear out the old whitelist entries */ 791 while(( tmp = tr_list_pop_front( &server->whitelist ))) 792 tr_free( tmp ); 793 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 ); 804 805 if( walk[len]=='\0' ) 806 break; 807 walk += len + 1; 808 } 809} 810 811const char* 812tr_rpcGetWhitelist( const tr_rpc_server * server ) 813{ 814 return server->whitelistStr ? server->whitelistStr : ""; 815} 816 817void 818tr_rpcSetWhitelistEnabled( tr_rpc_server * server, 819 bool isEnabled ) 820{ 821 server->isWhitelistEnabled = isEnabled != 0; 822} 823 824bool 825tr_rpcGetWhitelistEnabled( const tr_rpc_server * server ) 826{ 827 return server->isWhitelistEnabled; 828} 829 830/**** 831***** PASSWORD 832****/ 833 834void 835tr_rpcSetUsername( tr_rpc_server * server, const char * username ) 836{ 837 char * tmp = server->username; 838 server->username = tr_strdup( username ); 839 dbgmsg( "setting our Username to [%s]", server->username ); 840 tr_free( tmp ); 841} 842 843const char* 844tr_rpcGetUsername( const tr_rpc_server * server ) 845{ 846 return server->username ? server->username : ""; 847} 848 849void 850tr_rpcSetPassword( tr_rpc_server * server, 851 const char * password ) 852{ 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 ); 859} 860 861const char* 862tr_rpcGetPassword( const tr_rpc_server * server ) 863{ 864 return server->password ? server->password : "" ; 865} 866 867void 868tr_rpcSetPasswordEnabled( tr_rpc_server * server, bool isEnabled ) 869{ 870 server->isPasswordEnabled = isEnabled; 871 dbgmsg( "setting 'password enabled' to %d", (int)isEnabled ); 872} 873 874bool 875tr_rpcIsPasswordEnabled( const tr_rpc_server * server ) 876{ 877 return server->isPasswordEnabled; 878} 879 880const char * 881tr_rpcGetBindAddress( const tr_rpc_server * server ) 882{ 883 tr_address addr; 884 addr.type = TR_AF_INET; 885 addr.addr.addr4 = server->bindAddress; 886 return tr_address_to_string( &addr ); 887} 888 889/**** 890***** LIFE CYCLE 891****/ 892 893static void 894closeServer( void * vserver ) 895{ 896 void * tmp; 897 tr_rpc_server * s = vserver; 898 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 ); 912} 913 914void 915tr_rpcClose( tr_rpc_server ** ps ) 916{ 917 tr_runInEventThread( ( *ps )->session, closeServer, *ps ); 918 *ps = NULL; 919} 920 921tr_rpc_server * 922tr_rpcInit( tr_session * session, tr_benc * settings ) 923{ 924 tr_rpc_server * s; 925 bool boolVal; 926 int64_t i; 927 const char * str; 928 const char * key; 929 tr_address address; 930 931 s = tr_new0( tr_rpc_server, 1 ); 932 s->session = session; 933 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; 939 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; 945 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 ); 951 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 ); 957 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 ); 963 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 ); 969 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 ); 975 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 ); 981 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; 992 } 993 s->bindAddress = address.addr.addr4; 994 995 if( s->isEnabled ) 996 { 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 ); 999 1000 if( s->isWhitelistEnabled ) 1001 tr_ninf( MY_NAME, "%s", _( "Whitelist enabled" ) ); 1002 1003 if( s->isPasswordEnabled ) 1004 tr_ninf( MY_NAME, "%s", _( "Password required" ) ); 1005 } 1006 1007 return s; 1008} 1009