Changeset 13934
- Timestamp:
- Feb 2, 2013, 4:22:21 PM (8 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/libtransmission/rpc-server.c
r13868 r13934 60 60 bool isWhitelistEnabled; 61 61 tr_port port; 62 char *url;62 char * url; 63 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;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 72 time_t sessionIdExpiresAt; 73 73 … … 79 79 80 80 #define dbgmsg(...) \ 81 82 83 84 81 do { \ 82 if (tr_logGetDeepEnabled ()) \ 83 tr_logAddDeep (__FILE__, __LINE__, MY_NAME, __VA_ARGS__); \ 84 } while (0) 85 85 86 86 … … 92 92 get_current_session_id (struct tr_rpc_server * server) 93 93 { 94 95 96 97 { 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 } 113 114 94 const time_t now = tr_time (); 95 96 if (!server->sessionId || (now >= server->sessionIdExpiresAt)) 97 { 98 int i; 99 const int n = 48; 100 const char * pool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 101 const size_t pool_size = strlen (pool); 102 unsigned char * buf = tr_new (unsigned char, n+1); 103 104 tr_cryptoRandBuf (buf, n); 105 for (i=0; i<n; ++i) 106 buf[i] = pool[ buf[i] % pool_size ]; 107 buf[n] = '\0'; 108 109 tr_free (server->sessionId); 110 server->sessionId = (char*) buf; 111 server->sessionIdExpiresAt = now + (60*60); /* expire in an hour */ 112 } 113 114 return server->sessionId; 115 115 } 116 116 … … 123 123 send_simple_response (struct evhttp_request * req, 124 124 int code, 125 const char *text)126 { 127 const char *code_text = tr_webGetResponseStr (code);128 129 130 131 132 133 134 135 125 const char * text) 126 { 127 const char * code_text = tr_webGetResponseStr (code); 128 struct evbuffer * body = evbuffer_new (); 129 130 evbuffer_add_printf (body, "<h1>%d: %s</h1>", code, code_text); 131 if (text) 132 evbuffer_add_printf (body, "%s", text); 133 evhttp_send_reply (req, code, code_text, body); 134 135 evbuffer_free (body); 136 136 } 137 137 138 138 struct tr_mimepart 139 139 { 140 141 142 143 140 char * headers; 141 int headers_len; 142 char * body; 143 int body_len; 144 144 }; 145 145 … … 147 147 tr_mimepart_free (struct tr_mimepart * p) 148 148 { 149 150 151 152 } 153 154 static void 155 extract_parts_from_multipart (const struct evkeyvalq * headers,156 struct evbuffer * body,157 tr_ptrArray * setme_parts)158 { 159 160 161 162 163 164 165 166 167 168 169 170 171 { 172 173 174 175 176 177 178 179 180 181 182 { 183 184 149 tr_free (p->body); 150 tr_free (p->headers); 151 tr_free (p); 152 } 153 154 static void 155 extract_parts_from_multipart (const struct evkeyvalq * headers, 156 struct evbuffer * body, 157 tr_ptrArray * setme_parts) 158 { 159 const char * content_type = evhttp_find_header (headers, "Content-Type"); 160 const char * in = (const char*) evbuffer_pullup (body, -1); 161 size_t inlen = evbuffer_get_length (body); 162 163 const char * boundary_key = "boundary="; 164 const char * boundary_key_begin = content_type ? strstr (content_type, boundary_key) : NULL; 165 const char * boundary_val = boundary_key_begin ? boundary_key_begin + strlen (boundary_key) : "arglebargle"; 166 char * boundary = tr_strdup_printf ("--%s", boundary_val); 167 const size_t boundary_len = strlen (boundary); 168 169 const char * delim = tr_memmem (in, inlen, boundary, boundary_len); 170 while (delim) 171 { 172 size_t part_len; 173 const char * part = delim + boundary_len; 174 175 inlen -= (part - in); 176 in = part; 177 178 delim = tr_memmem (in, inlen, boundary, boundary_len); 179 part_len = delim ? (size_t)(delim - part) : inlen; 180 181 if (part_len) 182 { 183 const char * rnrn = tr_memmem (part, part_len, "\r\n\r\n", 4); 184 if (rnrn) 185 185 { 186 187 188 189 190 191 186 struct tr_mimepart * p = tr_new (struct tr_mimepart, 1); 187 p->headers_len = rnrn - part; 188 p->headers = tr_strndup (part, p->headers_len); 189 p->body_len = (part+part_len) - (rnrn + 4); 190 p->body = tr_strndup (rnrn+4, p->body_len); 191 tr_ptrArrayAppend (setme_parts, p); 192 192 } 193 193 } 194 194 } 195 195 196 196 tr_free (boundary); 197 197 } 198 198 199 199 static void 200 200 handle_upload (struct evhttp_request * req, 201 struct tr_rpc_server * server) 202 { 203 if (req->type != EVHTTP_REQ_POST) 204 { 205 send_simple_response (req, 405, NULL); 206 } 207 else 208 { 209 int i; 210 int n; 211 bool hasSessionId = false; 212 tr_ptrArray parts = TR_PTR_ARRAY_INIT; 213 214 const char * query = strchr (req->uri, '?'); 215 const bool paused = query && strstr (query + 1, "paused=true"); 216 217 extract_parts_from_multipart (req->input_headers, req->input_buffer, &parts); 218 n = tr_ptrArraySize (&parts); 219 220 /* first look for the session id */ 221 for (i=0; i<n; ++i) { 222 struct tr_mimepart * p = tr_ptrArrayNth (&parts, i); 223 if (tr_memmem (p->headers, p->headers_len, TR_RPC_SESSION_ID_HEADER, strlen (TR_RPC_SESSION_ID_HEADER))) 224 break; 225 } 226 if (i<n) { 227 const struct tr_mimepart * p = tr_ptrArrayNth (&parts, i); 228 const char * ours = get_current_session_id (server); 229 const int ourlen = strlen (ours); 230 hasSessionId = ourlen<=p->body_len && !memcmp (p->body, ours, ourlen); 231 } 232 233 if (!hasSessionId) 234 { 235 int code = 409; 236 const char * codetext = tr_webGetResponseStr (code); 237 struct evbuffer * body = evbuffer_new (); 238 evbuffer_add_printf (body, "%s", "{ \"success\": false, \"msg\": \"Bad Session-Id\" }");; 239 evhttp_send_reply (req, code, codetext, body); 240 evbuffer_free (body); 241 } 242 else for (i=0; i<n; ++i) 243 { 244 struct tr_mimepart * p = tr_ptrArrayNth (&parts, i); 245 int body_len = p->body_len; 246 tr_variant top, *args; 247 tr_variant test; 248 bool have_source = false; 249 char * body = p->body; 250 251 if (body_len >= 2 && !memcmp (&body[body_len - 2], "\r\n", 2)) 252 body_len -= 2; 253 254 tr_variantInitDict (&top, 2); 255 tr_variantDictAddStr (&top, TR_KEY_method, "torrent-add"); 256 args = tr_variantDictAddDict (&top, TR_KEY_arguments, 2); 257 tr_variantDictAddBool (args, TR_KEY_paused, paused); 258 259 if (tr_urlIsValid (body, body_len)) 201 struct tr_rpc_server * server) 202 { 203 if (req->type != EVHTTP_REQ_POST) 204 { 205 send_simple_response (req, 405, NULL); 206 } 207 else 208 { 209 int i; 210 int n; 211 bool hasSessionId = false; 212 tr_ptrArray parts = TR_PTR_ARRAY_INIT; 213 214 const char * query = strchr (req->uri, '?'); 215 const bool paused = query && strstr (query + 1, "paused=true"); 216 217 extract_parts_from_multipart (req->input_headers, req->input_buffer, &parts); 218 n = tr_ptrArraySize (&parts); 219 220 /* first look for the session id */ 221 for (i=0; i<n; ++i) 222 { 223 struct tr_mimepart * p = tr_ptrArrayNth (&parts, i); 224 if (tr_memmem (p->headers, p->headers_len, TR_RPC_SESSION_ID_HEADER, strlen (TR_RPC_SESSION_ID_HEADER))) 225 break; 226 } 227 228 if (i<n) 229 { 230 const struct tr_mimepart * p = tr_ptrArrayNth (&parts, i); 231 const char * ours = get_current_session_id (server); 232 const int ourlen = strlen (ours); 233 hasSessionId = ourlen<=p->body_len && !memcmp (p->body, ours, ourlen); 234 } 235 236 if (!hasSessionId) 237 { 238 int code = 409; 239 const char * codetext = tr_webGetResponseStr (code); 240 struct evbuffer * body = evbuffer_new (); 241 evbuffer_add_printf (body, "%s", "{ \"success\": false, \"msg\": \"Bad Session-Id\" }");; 242 evhttp_send_reply (req, code, codetext, body); 243 evbuffer_free (body); 244 } 245 else for (i=0; i<n; ++i) 246 { 247 struct tr_mimepart * p = tr_ptrArrayNth (&parts, i); 248 int body_len = p->body_len; 249 tr_variant top, *args; 250 tr_variant test; 251 bool have_source = false; 252 char * body = p->body; 253 254 if (body_len >= 2 && !memcmp (&body[body_len - 2], "\r\n", 2)) 255 body_len -= 2; 256 257 tr_variantInitDict (&top, 2); 258 tr_variantDictAddStr (&top, TR_KEY_method, "torrent-add"); 259 args = tr_variantDictAddDict (&top, TR_KEY_arguments, 2); 260 tr_variantDictAddBool (args, TR_KEY_paused, paused); 261 262 if (tr_urlIsValid (body, body_len)) 260 263 { 261 262 264 tr_variantDictAddRaw (args, TR_KEY_filename, body, body_len); 265 have_source = true; 263 266 } 264 267 else if (!tr_variantFromBenc (&test, body, body_len)) 265 268 { 266 267 268 269 269 char * b64 = tr_base64_encode (body, body_len, NULL); 270 tr_variantDictAddStr (args, TR_KEY_metainfo, b64); 271 tr_free (b64); 272 have_source = true; 270 273 } 271 274 272 275 if (have_source) 273 276 { 274 275 276 277 278 279 277 struct evbuffer * json = tr_variantToBuf (&top, TR_VARIANT_FMT_JSON); 278 tr_rpc_request_exec_json (server->session, 279 evbuffer_pullup (json, -1), 280 evbuffer_get_length (json), 281 NULL, NULL); 282 evbuffer_free (json); 280 283 } 281 284 282 283 } 284 285 286 287 288 289 290 291 292 293 294 295 285 tr_variantFree (&top); 286 } 287 288 tr_ptrArrayDestruct (&parts, (PtrArrayForeachFunc)tr_mimepart_free); 289 290 /* send "success" response */ 291 { 292 int code = HTTP_OK; 293 const char * codetext = tr_webGetResponseStr (code); 294 struct evbuffer * body = evbuffer_new (); 295 evbuffer_add_printf (body, "%s", "{ \"success\": true, \"msg\": \"Torrent Added\" }");; 296 evhttp_send_reply (req, code, codetext, body); 297 evbuffer_free (body); 298 } 296 299 } 297 300 } … … 300 303 mimetype_guess (const char * path) 301 304 { 302 unsigned int i; 303 304 const struct 305 { 306 const char * suffix; 307 const char * mime_type; 308 } types[] = { 309 /* these are the ones we need for serving the web client's files... */ 310 { "css", "text/css" }, 311 { "gif", "image/gif" }, 312 { "html", "text/html" }, 313 { "ico", "image/vnd.microsoft.icon" }, 314 { "js", "application/javascript" }, 315 { "png", "image/png" } 316 }; 317 const char * dot = strrchr (path, '.'); 318 319 for (i = 0; dot && i < TR_N_ELEMENTS (types); ++i) 320 if (!strcmp (dot + 1, types[i].suffix)) 321 return types[i].mime_type; 322 323 return "application/octet-stream"; 324 } 325 326 static void 327 add_response (struct evhttp_request * req, struct tr_rpc_server * server, 328 struct evbuffer * out, struct evbuffer * content) 305 unsigned int i; 306 307 const struct { 308 const char * suffix; 309 const char * mime_type; 310 } types[] = { 311 /* these are the ones we need for serving the web client's files... */ 312 { "css", "text/css" }, 313 { "gif", "image/gif" }, 314 { "html", "text/html" }, 315 { "ico", "image/vnd.microsoft.icon" }, 316 { "js", "application/javascript" }, 317 { "png", "image/png" } 318 }; 319 const char * dot = strrchr (path, '.'); 320 321 for (i = 0; dot && i < TR_N_ELEMENTS (types); ++i) 322 if (!strcmp (dot + 1, types[i].suffix)) 323 return types[i].mime_type; 324 325 return "application/octet-stream"; 326 } 327 328 static void 329 add_response (struct evhttp_request * req, 330 struct tr_rpc_server * server, 331 struct evbuffer * out, 332 struct evbuffer * content) 329 333 { 330 334 #ifndef HAVE_ZLIB 331 335 evbuffer_add_buffer (out, content); 332 336 #else 333 334 335 336 337 338 { 339 340 } 341 342 { 343 344 345 346 347 348 349 { 350 351 352 353 354 355 356 357 358 337 const char * key = "Accept-Encoding"; 338 const char * encoding = evhttp_find_header (req->input_headers, key); 339 const int do_compress = encoding && strstr (encoding, "gzip"); 340 341 if (!do_compress) 342 { 343 evbuffer_add_buffer (out, content); 344 } 345 else 346 { 347 int state; 348 struct evbuffer_iovec iovec[1]; 349 void * content_ptr = evbuffer_pullup (content, -1); 350 const size_t content_len = evbuffer_get_length (content); 351 352 if (!server->isStreamInitialized) 353 { 354 int compressionLevel; 355 356 server->isStreamInitialized = true; 357 server->stream.zalloc = (alloc_func) Z_NULL; 358 server->stream.zfree = (free_func) Z_NULL; 359 server->stream.opaque = (voidpf) Z_NULL; 360 361 /* zlib's manual says: "Add 16 to windowBits to write a simple gzip header 362 * and trailer around the compressed data instead of a zlib wrapper." */ 359 363 #ifdef TR_LIGHTWEIGHT 360 364 compressionLevel = Z_DEFAULT_COMPRESSION; 361 365 #else 362 366 compressionLevel = Z_BEST_COMPRESSION; 363 367 #endif 364 365 } 366 367 368 369 370 371 372 373 374 375 376 377 378 379 { 380 368 deflateInit2 (&server->stream, compressionLevel, Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY); 369 } 370 371 server->stream.next_in = content_ptr; 372 server->stream.avail_in = content_len; 373 374 /* allocate space for the raw data and call deflate () just once -- 375 * we won't use the deflated data if it's longer than the raw data, 376 * so it's okay to let deflate () run out of output buffer space */ 377 evbuffer_reserve_space (out, content_len, iovec, 1); 378 server->stream.next_out = iovec[0].iov_base; 379 server->stream.avail_out = iovec[0].iov_len; 380 state = deflate (&server->stream, Z_FINISH); 381 382 if (state == Z_STREAM_END) 383 { 384 iovec[0].iov_len -= server->stream.avail_out; 381 385 382 386 #if 0 383 384 385 387 fprintf (stderr, "compressed response is %.2f of original (raw==%zu bytes; compressed==%zu)\n", 388 (double)evbuffer_get_length (out)/content_len, 389 content_len, evbuffer_get_length (out)); 386 390 #endif 387 388 389 } 390 391 { 392 393 394 } 395 396 397 391 evhttp_add_header (req->output_headers, 392 "Content-Encoding", "gzip"); 393 } 394 else 395 { 396 memcpy (iovec[0].iov_base, content_ptr, content_len); 397 iovec[0].iov_len = content_len; 398 } 399 400 evbuffer_commit_space (out, iovec, 1); 401 deflateReset (&server->stream); 398 402 } 399 403 #endif … … 401 405 402 406 static void 403 add_time_header (struct evkeyvalq * headers, const char * key, time_t value) 404 { 405 /* According to RFC 2616 this must follow RFC 1123's date format, 406 so use gmtime instead of localtime... */ 407 char buf[128]; 408 struct tm tm = *gmtime (&value); 409 strftime (buf, sizeof (buf), "%a, %d %b %Y %H:%M:%S GMT", &tm); 410 evhttp_add_header (headers, key, buf); 411 } 412 413 static void 414 evbuffer_ref_cleanup_tr_free (const void * data UNUSED, size_t datalen UNUSED, void * extra) 415 { 416 tr_free (extra); 417 } 418 419 static void 420 serve_file (struct evhttp_request * req, 421 struct tr_rpc_server * server, 422 const char * filename) 423 { 424 if (req->type != EVHTTP_REQ_GET) 425 { 426 evhttp_add_header (req->output_headers, "Allow", "GET"); 427 send_simple_response (req, 405, NULL); 428 } 429 else 430 { 431 void * file; 432 size_t file_len; 433 struct evbuffer * content; 434 const int error = errno; 435 436 errno = 0; 437 file_len = 0; 438 file = tr_loadFile (filename, &file_len); 439 content = evbuffer_new (); 440 evbuffer_add_reference (content, file, file_len, evbuffer_ref_cleanup_tr_free, file); 441 442 if (errno) 443 { 444 char * tmp = tr_strdup_printf ("%s (%s)", filename, tr_strerror (errno)); 445 send_simple_response (req, HTTP_NOTFOUND, tmp); 446 tr_free (tmp); 447 } 448 else 449 { 450 struct evbuffer * out; 451 const time_t now = tr_time (); 452 453 errno = error; 454 out = evbuffer_new (); 455 evhttp_add_header (req->output_headers, "Content-Type", mimetype_guess (filename)); 456 add_time_header (req->output_headers, "Date", now); 457 add_time_header (req->output_headers, "Expires", now+ (24*60*60)); 458 add_response (req, server, out, content); 459 evhttp_send_reply (req, HTTP_OK, "OK", out); 460 461 evbuffer_free (out); 462 } 463 464 evbuffer_free (content); 407 add_time_header (struct evkeyvalq * headers, 408 const char * key, 409 time_t value) 410 { 411 /* According to RFC 2616 this must follow RFC 1123's date format, 412 so use gmtime instead of localtime... */ 413 char buf[128]; 414 struct tm tm = *gmtime (&value); 415 strftime (buf, sizeof (buf), "%a, %d %b %Y %H:%M:%S GMT", &tm); 416 evhttp_add_header (headers, key, buf); 417 } 418 419 static void 420 evbuffer_ref_cleanup_tr_free (const void * data UNUSED, 421 size_t datalen UNUSED, 422 void * extra) 423 { 424 tr_free (extra); 425 } 426 427 static void 428 serve_file (struct evhttp_request * req, 429 struct tr_rpc_server * server, 430 const char * filename) 431 { 432 if (req->type != EVHTTP_REQ_GET) 433 { 434 evhttp_add_header (req->output_headers, "Allow", "GET"); 435 send_simple_response (req, 405, NULL); 436 } 437 else 438 { 439 void * file; 440 size_t file_len; 441 struct evbuffer * content; 442 const int error = errno; 443 444 errno = 0; 445 file_len = 0; 446 file = tr_loadFile (filename, &file_len); 447 content = evbuffer_new (); 448 evbuffer_add_reference (content, file, file_len, evbuffer_ref_cleanup_tr_free, file); 449 450 if (errno) 451 { 452 char * tmp = tr_strdup_printf ("%s (%s)", filename, tr_strerror (errno)); 453 send_simple_response (req, HTTP_NOTFOUND, tmp); 454 tr_free (tmp); 455 } 456 else 457 { 458 struct evbuffer * out; 459 const time_t now = tr_time (); 460 461 errno = error; 462 out = evbuffer_new (); 463 evhttp_add_header (req->output_headers, "Content-Type", mimetype_guess (filename)); 464 add_time_header (req->output_headers, "Date", now); 465 add_time_header (req->output_headers, "Expires", now+ (24*60*60)); 466 add_response (req, server, out, content); 467 evhttp_send_reply (req, HTTP_OK, "OK", out); 468 469 evbuffer_free (out); 470 } 471 472 evbuffer_free (content); 465 473 } 466 474 } … … 470 478 struct tr_rpc_server * server) 471 479 { 472 473 474 480 const char * webClientDir = tr_getWebClientDir (server->session); 481 482 if (!webClientDir || !*webClientDir) 475 483 { 476 484 send_simple_response (req, HTTP_NOTFOUND, 477 478 479 480 481 482 483 484 485 } 486 487 { 488 489 490 491 492 493 494 495 496 { 497 498 } 499 500 { 501 502 webClientDir,503 TR_PATH_DELIMITER_STR,504 subpath && *subpath ? subpath : "index.html");505 506 507 } 508 509 485 "<p>Couldn't find Transmission's web interface files!</p>" 486 "<p>Users: to tell Transmission where to look, " 487 "set the TRANSMISSION_WEB_HOME environment " 488 "variable to the folder where the web interface's " 489 "index.html is located.</p>" 490 "<p>Package Builders: to set a custom default at compile time, " 491 "#define PACKAGE_DATA_DIR in libtransmission/platform.c " 492 "or tweak tr_getClutchDir () by hand.</p>"); 493 } 494 else 495 { 496 char * pch; 497 char * subpath; 498 499 subpath = tr_strdup (req->uri + strlen (server->url) + 4); 500 if ((pch = strchr (subpath, '?'))) 501 *pch = '\0'; 502 503 if (strstr (subpath, "..")) 504 { 505 send_simple_response (req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>"); 506 } 507 else 508 { 509 char * filename = tr_strdup_printf ("%s%s%s", 510 webClientDir, 511 TR_PATH_DELIMITER_STR, 512 subpath && *subpath ? subpath : "index.html"); 513 serve_file (req, server, filename); 514 tr_free (filename); 515 } 516 517 tr_free (subpath); 510 518 } 511 519 } … … 513 521 struct rpc_response_data 514 522 { 515 516 523 struct evhttp_request * req; 524 struct tr_rpc_server * server; 517 525 }; 518 526 … … 522 530 void * user_data) 523 531 { 524 struct rpc_response_data * data = user_data; 525 struct evbuffer * buf = evbuffer_new (); 526 527 add_response (data->req, data->server, buf, response); 528 evhttp_add_header (data->req->output_headers, 529 "Content-Type", "application/json; charset=UTF-8"); 530 evhttp_send_reply (data->req, HTTP_OK, "OK", buf); 531 532 evbuffer_free (buf); 533 tr_free (data); 534 } 535 536 537 static void 538 handle_rpc (struct evhttp_request * req, 539 struct tr_rpc_server * server) 540 { 541 struct rpc_response_data * data = tr_new0 (struct rpc_response_data, 1); 542 543 data->req = req; 544 data->server = server; 545 546 if (req->type == EVHTTP_REQ_GET) 547 { 548 const char * q; 549 if ((q = strchr (req->uri, '?'))) 550 tr_rpc_request_exec_uri (server->session, q+1, -1, rpc_response_func, data); 551 } 552 else if (req->type == EVHTTP_REQ_POST) 553 { 554 tr_rpc_request_exec_json (server->session, 555 evbuffer_pullup (req->input_buffer, -1), 556 evbuffer_get_length (req->input_buffer), 557 rpc_response_func, data); 532 struct rpc_response_data * data = user_data; 533 struct evbuffer * buf = evbuffer_new (); 534 535 add_response (data->req, data->server, buf, response); 536 evhttp_add_header (data->req->output_headers, 537 "Content-Type", "application/json; charset=UTF-8"); 538 evhttp_send_reply (data->req, HTTP_OK, "OK", buf); 539 540 evbuffer_free (buf); 541 tr_free (data); 542 } 543 544 545 static void 546 handle_rpc (struct evhttp_request * req, struct tr_rpc_server * server) 547 { 548 struct rpc_response_data * data = tr_new0 (struct rpc_response_data, 1); 549 550 data->req = req; 551 data->server = server; 552 553 if (req->type == EVHTTP_REQ_GET) 554 { 555 const char * q; 556 if ((q = strchr (req->uri, '?'))) 557 tr_rpc_request_exec_uri (server->session, q+1, -1, rpc_response_func, data); 558 } 559 else if (req->type == EVHTTP_REQ_POST) 560 { 561 tr_rpc_request_exec_json (server->session, 562 evbuffer_pullup (req->input_buffer, -1), 563 evbuffer_get_length (req->input_buffer), 564 rpc_response_func, data); 558 565 } 559 566 … … 561 568 562 569 static bool 563 isAddressAllowed (const tr_rpc_server * server, 564 const char * address) 565 { 566 tr_list * l; 567 568 if (!server->isWhitelistEnabled) 569 return true; 570 571 for (l=server->whitelist; l!=NULL; l=l->next) 572 if (tr_wildmat (address, l->data)) 573 return true; 574 575 return false; 570 isAddressAllowed (const tr_rpc_server * server, const char * address) 571 { 572 tr_list * l; 573 574 if (!server->isWhitelistEnabled) 575 return true; 576 577 for (l=server->whitelist; l!=NULL; l=l->next) 578 if (tr_wildmat (address, l->data)) 579 return true; 580 581 return false; 576 582 } 577 583 … … 579 585 test_session_id (struct tr_rpc_server * server, struct evhttp_request * req) 580 586 { 581 582 583 584 587 const char * ours = get_current_session_id (server); 588 const char * theirs = evhttp_find_header (req->input_headers, TR_RPC_SESSION_ID_HEADER); 589 const bool success = theirs && !strcmp (theirs, ours); 590 return success; 585 591 } 586 592 … … 588 594 handle_request (struct evhttp_request * req, void * arg) 589 595 { 590 591 592 593 { 594 595 596 597 598 599 600 601 602 { 603 intplen;604 605 596 struct tr_rpc_server * server = arg; 597 598 if (req && req->evcon) 599 { 600 const char * auth; 601 char * user = NULL; 602 char * pass = NULL; 603 604 evhttp_add_header (req->output_headers, "Server", MY_REALM); 605 606 auth = evhttp_find_header (req->input_headers, "Authorization"); 607 if (auth && !evutil_ascii_strncasecmp (auth, "basic ", 6)) 608 { 609 int plen; 610 char * p = tr_base64_decode (auth + 6, 0, &plen); 611 if (p && plen && ((pass = strchr (p, ':')))) 606 612 { 607 608 613 user = p; 614 *pass++ = '\0'; 609 615 } 610 616 } 611 617 612 613 { 614 615 616 617 618 619 } 620 618 if (!isAddressAllowed (server, req->remote_host)) 619 { 620 send_simple_response (req, 403, 621 "<p>Unauthorized IP Address.</p>" 622 "<p>Either disable the IP address whitelist or add your address to it.</p>" 623 "<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>" 624 "<p>If you're still using ACLs, use a whitelist instead. See the transmission-daemon manpage for details.</p>"); 625 } 626 else if (server->isPasswordEnabled 621 627 && (!pass || !user || strcmp (server->username, user) 622 628 || !tr_ssha1_matches (server->password, 623 629 pass))) 624 630 { 625 626 627 628 629 } 630 631 { 632 633 634 635 636 } 637 638 { 639 640 } 641 642 { 643 631 evhttp_add_header (req->output_headers, 632 "WWW-Authenticate", 633 "Basic realm=\"" MY_REALM "\""); 634 send_simple_response (req, 401, "Unauthorized User"); 635 } 636 else if (strncmp (req->uri, server->url, strlen (server->url))) 637 { 638 char * location = tr_strdup_printf ("%sweb/", server->url); 639 evhttp_add_header (req->output_headers, "Location", location); 640 send_simple_response (req, HTTP_MOVEPERM, NULL); 641 tr_free (location); 642 } 643 else if (!strncmp (req->uri + strlen (server->url), "web/", 4)) 644 { 645 handle_web_client (req, server); 646 } 647 else if (!strncmp (req->uri + strlen (server->url), "upload", 6)) 648 { 649 handle_upload (req, server); 644 650 } 645 651 #ifdef REQUIRE_SESSION_ID 646 647 { 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 652 else if (!test_session_id (server, req)) 653 { 654 const char * sessionId = get_current_session_id (server); 655 char * tmp = tr_strdup_printf ( 656 "<p>Your request had an invalid session-id header.</p>" 657 "<p>To fix this, follow these steps:" 658 "<ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it" 659 "<li> Add the updated header to your outgoing requests" 660 "<li> When you get this 409 error message, resend your request with the updated header" 661 "</ol></p>" 662 "<p>This requirement has been added to help prevent " 663 "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> " 664 "attacks.</p>" 665 "<p><code>%s: %s</code></p>", 666 TR_RPC_SESSION_ID_HEADER, sessionId); 667 evhttp_add_header (req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId); 668 send_simple_response (req, 409, tmp); 669 tr_free (tmp); 664 670 } 665 671 #endif 666 667 { 668 669 } 670 671 { 672 673 } 674 675 672 else if (!strncmp (req->uri + strlen (server->url), "rpc", 3)) 673 { 674 handle_rpc (req, server); 675 } 676 else 677 { 678 send_simple_response (req, HTTP_NOTFOUND, req->uri); 679 } 680 681 tr_free (user); 676 682 } 677 683 } … … 680 686 startServer (void * vserver) 681 687 { 682 tr_rpc_server * server = vserver; 683 tr_address addr; 684 685 if (!server->httpd) 686 { 687 addr.type = TR_AF_INET; 688 addr.addr.addr4 = server->bindAddress; 689 server->httpd = evhttp_new (server->session->event_base); 690 evhttp_bind_socket (server->httpd, tr_address_to_string (&addr), server->port); 691 evhttp_set_gencb (server->httpd, handle_request, server); 692 688 tr_rpc_server * server = vserver; 689 tr_address addr; 690 691 if (!server->httpd) 692 { 693 addr.type = TR_AF_INET; 694 addr.addr.addr4 = server->bindAddress; 695 server->httpd = evhttp_new (server->session->event_base); 696 evhttp_bind_socket (server->httpd, tr_address_to_string (&addr), server->port); 697 evhttp_set_gencb (server->httpd, handle_request, server); 693 698 } 694 699 } … … 697 702 stopServer (tr_rpc_server * server) 698 703 { 699 700 { 701 702 704 if (server->httpd) 705 { 706 evhttp_free (server->httpd); 707 server->httpd = NULL; 703 708 } 704 709 } … … 707 712 onEnabledChanged (void * vserver) 708 713 { 709 710 711 712 713 714 714 tr_rpc_server * server = vserver; 715 716 if (!server->isEnabled) 717 stopServer (server); 718 else 719 startServer (server); 715 720 } 716 721 … … 719 724 bool isEnabled) 720 725 { 721 722 723 726 server->isEnabled = isEnabled; 727 728 tr_runInEventThread (server->session, onEnabledChanged, server); 724 729 } 725 730 … … 727 732 tr_rpcIsEnabled (const tr_rpc_server * server) 728 733 { 729 734 return server->isEnabled; 730 735 } 731 736 … … 733 738 restartServer (void * vserver) 734 739 { 735 736 737 738 { 739 740 740 tr_rpc_server * server = vserver; 741 742 if (server->isEnabled) 743 { 744 stopServer (server); 745 startServer (server); 741 746 } 742 747 } … … 746 751 tr_port port) 747 752 { 748 749 750 751 { 752 753 754 755 753 assert (server != NULL); 754 755 if (server->port != port) 756 { 757 server->port = port; 758 759 if (server->isEnabled) 760 tr_runInEventThread (server->session, restartServer, server); 756 761 } 757 762 } … … 760 765 tr_rpcGetPort (const tr_rpc_server * server) 761 766 { 762 767 return server->port; 763 768 } 764 769 … … 766 771 tr_rpcSetUrl (tr_rpc_server * server, const char * url) 767 772 { 768 769 770 771 773 char * tmp = server->url; 774 server->url = tr_strdup (url); 775 dbgmsg ("setting our URL to [%s]", server->url); 776 tr_free (tmp); 772 777 } 773 778 … … 775 780 tr_rpcGetUrl (const tr_rpc_server * server) 776 781 { 777 782 return server->url ? server->url : ""; 778 783 } 779 784 … … 781 786 tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr) 782 787 { 783 void * tmp; 784 const char * walk; 785 786 /* keep the string */ 787 tmp = server->whitelistStr; 788 server->whitelistStr = tr_strdup (whitelistStr); 788 void * tmp; 789 const char * walk; 790 791 /* keep the string */ 792 tmp = server->whitelistStr; 793 server->whitelistStr = tr_strdup (whitelistStr); 794 tr_free (tmp); 795 796 /* clear out the old whitelist entries */ 797 while ((tmp = tr_list_pop_front (&server->whitelist))) 789 798 tr_free (tmp); 790 799 791 /* clear out the old whitelist entries */ 792 while ((tmp = tr_list_pop_front (&server->whitelist))) 793 tr_free (tmp); 794 795 /* build the new whitelist entries */ 796 for (walk=whitelistStr; walk && *walk;) { 797 const char * delimiters = " ,;"; 798 const size_t len = strcspn (walk, delimiters); 799 char * token = tr_strndup (walk, len); 800 tr_list_append (&server->whitelist, token); 801 if (strcspn (token, "+-") < len) 802 tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'! Are you using an old ACL by mistake?)", token); 803 else 804 tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s", token); 805 806 if (walk[len]=='\0') 807 break; 808 walk += len + 1; 800 /* build the new whitelist entries */ 801 for (walk=whitelistStr; walk && *walk;) 802 { 803 const char * delimiters = " ,;"; 804 const size_t len = strcspn (walk, delimiters); 805 char * token = tr_strndup (walk, len); 806 tr_list_append (&server->whitelist, token); 807 if (strcspn (token, "+-") < len) 808 tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'! Are you using an old ACL by mistake?)", token); 809 else 810 tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s", token); 811 812 if (walk[len]=='\0') 813 break; 814 815 walk += len + 1; 809 816 } 810 817 } … … 813 820 tr_rpcGetWhitelist (const tr_rpc_server * server) 814 821 { 815 822 return server->whitelistStr ? server->whitelistStr : ""; 816 823 } 817 824 … … 820 827 bool isEnabled) 821 828 { 822 829 server->isWhitelistEnabled = isEnabled != 0; 823 830 } 824 831 … … 826 833 tr_rpcGetWhitelistEnabled (const tr_rpc_server * server) 827 834 { 828 835 return server->isWhitelistEnabled; 829 836 } 830 837 … … 836 843 tr_rpcSetUsername (tr_rpc_server * server, const char * username) 837 844 { 838 839 840 841 845 char * tmp = server->username; 846 server->username = tr_strdup (username); 847 dbgmsg ("setting our Username to [%s]", server->username); 848 tr_free (tmp); 842 849 } 843 850 … … 845 852 tr_rpcGetUsername (const tr_rpc_server * server) 846 853 { 847 854 return server->username ? server->username : ""; 848 855 } 849 856 … … 852 859 const char * password) 853 860 { 854 855 856 857 858 859 861 tr_free (server->password); 862 if (*password != '{') 863 server->password = tr_ssha1 (password); 864 else 865 server->password = strdup (password); 866 dbgmsg ("setting our Password to [%s]", server->password); 860 867 } 861 868 … … 863 870 tr_rpcGetPassword (const tr_rpc_server * server) 864 871 { 865 872 return server->password ? server->password : "" ; 866 873 } 867 874 … … 869 876 tr_rpcSetPasswordEnabled (tr_rpc_server * server, bool isEnabled) 870 877 { 871 872 878 server->isPasswordEnabled = isEnabled; 879 dbgmsg ("setting 'password enabled' to %d", (int)isEnabled); 873 880 } 874 881 … … 876 883 tr_rpcIsPasswordEnabled (const tr_rpc_server * server) 877 884 { 878 885 return server->isPasswordEnabled; 879 886 } 880 887 … … 882 889 tr_rpcGetBindAddress (const tr_rpc_server * server) 883 890 { 884 885 886 887 891 tr_address addr; 892 addr.type = TR_AF_INET; 893 addr.addr.addr4 = server->bindAddress; 894 return tr_address_to_string (&addr); 888 895 } 889 896 … … 895 902 closeServer (void * vserver) 896 903 { 897 898 899 900 901 902 904 void * tmp; 905 tr_rpc_server * s = vserver; 906 907 stopServer (s); 908 while ((tmp = tr_list_pop_front (&s->whitelist))) 909 tr_free (tmp); 903 910 #ifdef HAVE_ZLIB 904 905 911 if (s->isStreamInitialized) 912 deflateEnd (&s->stream); 906 913 #endif 907 908 909 910 911 912 914 tr_free (s->url); 915 tr_free (s->sessionId); 916 tr_free (s->whitelistStr); 917 tr_free (s->username); 918 tr_free (s->password); 919 tr_free (s); 913 920 } 914 921 … … 916 923 tr_rpcClose (tr_rpc_server ** ps) 917 924 { 918 919 925 tr_runInEventThread ((*ps)->session, closeServer, *ps); 926 *ps = NULL; 920 927 } 921 928 … … 930 937 tr_rpcInit (tr_session * session, tr_variant * settings) 931 938 { 932 tr_rpc_server * s; 933 bool boolVal; 934 int64_t i; 935 const char * str; 936 tr_quark key; 937 tr_address address; 938 939 s = tr_new0 (tr_rpc_server, 1); 940 s->session = session; 941 942 key = TR_KEY_rpc_enabled; 943 if (!tr_variantDictFindBool (settings, key, &boolVal)) 944 missing_settings_key (key); 945 else 946 s->isEnabled = boolVal; 947 948 key = TR_KEY_rpc_port; 949 if (!tr_variantDictFindInt (settings, key, &i)) 950 missing_settings_key (key); 951 else 952 s->port = i; 953 954 key = TR_KEY_rpc_url; 955 if (!tr_variantDictFindStr (settings, key, &str, NULL)) 956 missing_settings_key (key); 957 else 958 s->url = tr_strdup (str); 959 960 key = TR_KEY_rpc_whitelist_enabled; 961 if (!tr_variantDictFindBool (settings, key, &boolVal)) 962 missing_settings_key (key); 963 else 964 tr_rpcSetWhitelistEnabled (s, boolVal); 965 966 key = TR_KEY_rpc_authentication_required; 967 if (!tr_variantDictFindBool (settings, key, &boolVal)) 968 missing_settings_key (key); 969 else 970 tr_rpcSetPasswordEnabled (s, boolVal); 971 972 key = TR_KEY_rpc_whitelist; 973 if (!tr_variantDictFindStr (settings, key, &str, NULL) && str) 974 missing_settings_key (key); 975 else 976 tr_rpcSetWhitelist (s, str); 977 978 key = TR_KEY_rpc_username; 979 if (!tr_variantDictFindStr (settings, key, &str, NULL)) 980 missing_settings_key (key); 981 else 982 tr_rpcSetUsername (s, str); 983 984 key = TR_KEY_rpc_password; 985 if (!tr_variantDictFindStr (settings, key, &str, NULL)) 986 missing_settings_key (key); 987 else 988 tr_rpcSetPassword (s, str); 989 990 key = TR_KEY_rpc_bind_address; 991 if (!tr_variantDictFindStr (settings, key, &str, NULL)) { 992 missing_settings_key (key); 993 address = tr_inaddr_any; 994 } else if (!tr_address_from_string (&address, str)) { 995 tr_logAddNamedError (MY_NAME, _("%s is not a valid address"), str); 996 address = tr_inaddr_any; 997 } else if (address.type != TR_AF_INET) { 998 tr_logAddNamedError (MY_NAME, _("%s is not an IPv4 address. RPC listeners must be IPv4"), str); 999 address = tr_inaddr_any; 1000 } 1001 s->bindAddress = address.addr.addr4; 1002 1003 if (s->isEnabled) 1004 { 1005 tr_logAddNamedInfo (MY_NAME, _("Serving RPC and Web requests on port 127.0.0.1:%d%s"), (int) s->port, s->url); 1006 tr_runInEventThread (session, startServer, s); 1007 1008 if (s->isWhitelistEnabled) 1009 tr_logAddNamedInfo (MY_NAME, "%s", _("Whitelist enabled")); 1010 1011 if (s->isPasswordEnabled) 1012 tr_logAddNamedInfo (MY_NAME, "%s", _("Password required")); 1013 } 1014 1015 return s; 1016 } 939 tr_rpc_server * s; 940 bool boolVal; 941 int64_t i; 942 const char * str; 943 tr_quark key; 944 tr_address address; 945 946 s = tr_new0 (tr_rpc_server, 1); 947 s->session = session; 948 949 key = TR_KEY_rpc_enabled; 950 if (!tr_variantDictFindBool (settings, key, &boolVal)) 951 missing_settings_key (key); 952 else 953 s->isEnabled = boolVal; 954 955 key = TR_KEY_rpc_port; 956 if (!tr_variantDictFindInt (settings, key, &i)) 957 missing_settings_key (key); 958 else 959 s->port = i; 960 961 key = TR_KEY_rpc_url; 962 if (!tr_variantDictFindStr (settings, key, &str, NULL)) 963 missing_settings_key (key); 964 else 965 s->url = tr_strdup (str); 966 967 key = TR_KEY_rpc_whitelist_enabled; 968 if (!tr_variantDictFindBool (settings, key, &boolVal)) 969 missing_settings_key (key); 970 else 971 tr_rpcSetWhitelistEnabled (s, boolVal); 972 973 key = TR_KEY_rpc_authentication_required; 974 if (!tr_variantDictFindBool (settings, key, &boolVal)) 975 missing_settings_key (key); 976 else 977 tr_rpcSetPasswordEnabled (s, boolVal); 978 979 key = TR_KEY_rpc_whitelist; 980 if (!tr_variantDictFindStr (settings, key, &str, NULL) && str) 981 missing_settings_key (key); 982 else 983 tr_rpcSetWhitelist (s, str); 984 985 key = TR_KEY_rpc_username; 986 if (!tr_variantDictFindStr (settings, key, &str, NULL)) 987 missing_settings_key (key); 988 else 989 tr_rpcSetUsername (s, str); 990 991 key = TR_KEY_rpc_password; 992 if (!tr_variantDictFindStr (settings, key, &str, NULL)) 993 missing_settings_key (key); 994 else 995 tr_rpcSetPassword (s, str); 996 997 key = TR_KEY_rpc_bind_address; 998 if (!tr_variantDictFindStr (settings, key, &str, NULL)) 999 { 1000 missing_settings_key (key); 1001 address = tr_inaddr_any; 1002 } 1003 else if (!tr_address_from_string (&address, str)) 1004 { 1005 tr_logAddNamedError (MY_NAME, _("%s is not a valid address"), str); 1006 address = tr_inaddr_any; 1007 } 1008 else if (address.type != TR_AF_INET) 1009 { 1010 tr_logAddNamedError (MY_NAME, _("%s is not an IPv4 address. RPC listeners must be IPv4"), str); 1011 address = tr_inaddr_any; 1012 } 1013 s->bindAddress = address.addr.addr4; 1014 1015 if (s->isEnabled) 1016 { 1017 tr_logAddNamedInfo (MY_NAME, _("Serving RPC and Web requests on port 127.0.0.1:%d%s"), (int) s->port, s->url); 1018 tr_runInEventThread (session, startServer, s); 1019 1020 if (s->isWhitelistEnabled) 1021 tr_logAddNamedInfo (MY_NAME, "%s", _("Whitelist enabled")); 1022 1023 if (s->isPasswordEnabled) 1024 tr_logAddNamedInfo (MY_NAME, "%s", _("Password required")); 1025 } 1026 1027 return s; 1028 }
Note: See TracChangeset
for help on using the changeset viewer.