1 /*
2 * This file Copyright (C) 2008-2014 Mnemosyne LLC
3 *
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 */
8
9 #include <errno.h>
10 #include <string.h> /* memcpy */
11
12 #include <zlib.h>
13
14 #include <event2/buffer.h>
15 #include <event2/event.h>
16 #include <event2/http.h>
17 #include <event2/http_struct.h> /* TODO: eventually remove this */
18
19 #include "transmission.h"
20 #include "crypto.h" /* tr_ssha1_matches() */
21 #include "crypto-utils.h" /* tr_rand_buffer() */
22 #include "error.h"
23 #include "fdlimit.h"
24 #include "list.h"
25 #include "log.h"
26 #include "net.h"
27 #include "platform.h" /* tr_getWebClientDir() */
28 #include "ptrarray.h"
29 #include "rpcimpl.h"
30 #include "rpc-server.h"
31 #include "session.h"
32 #include "session-id.h"
33 #include "tr-assert.h"
34 #include "trevent.h"
35 #include "utils.h"
36 #include "variant.h"
37 #include "web.h"
38
39 /* session-id is used to make cross-site request forgery attacks difficult.
40 * Don't disable this feature unless you really know what you're doing!
41 * http://en.wikipedia.org/wiki/Cross-site_request_forgery
42 * http://shiflett.org/articles/cross-site-request-forgeries
43 * http://www.webappsec.org/lists/websecurity/archive/2008-04/msg00037.html */
44 #define REQUIRE_SESSION_ID
45
46 #define MY_NAME "RPC Server"
47 #define MY_REALM "Transmission"
48
49 struct tr_rpc_server
50 {
51 bool isEnabled;
52 bool isPasswordEnabled;
53 bool isWhitelistEnabled;
54 bool isHostWhitelistEnabled;
55 tr_port port;
56 char* url;
57 struct tr_address bindAddress;
58 struct evhttp* httpd;
59 struct event* start_retry_timer;
60 int start_retry_counter;
61 tr_session* session;
62 char* username;
63 char* password;
64 char* whitelistStr;
65 tr_list* whitelist;
66 tr_list* hostWhitelist;
67 int loginattempts;
68
69 bool isStreamInitialized;
70 z_stream stream;
71 };
72
73 #define dbgmsg(...) tr_logAddDeepNamed(MY_NAME, __VA_ARGS__)
74
75 /***
76 ****
77 ***/
78
get_current_session_id(struct tr_rpc_server * server)79 static char const* get_current_session_id(struct tr_rpc_server* server)
80 {
81 return tr_session_id_get_current(server->session->session_id);
82 }
83
84 /**
85 ***
86 **/
87
send_simple_response(struct evhttp_request * req,int code,char const * text)88 static void send_simple_response(struct evhttp_request* req, int code, char const* text)
89 {
90 char const* code_text = tr_webGetResponseStr(code);
91 struct evbuffer* body = evbuffer_new();
92
93 evbuffer_add_printf(body, "<h1>%d: %s</h1>", code, code_text);
94
95 if (text != NULL)
96 {
97 evbuffer_add_printf(body, "%s", text);
98 }
99
100 evhttp_send_reply(req, code, code_text, body);
101
102 evbuffer_free(body);
103 }
104
105 struct tr_mimepart
106 {
107 char* headers;
108 size_t headers_len;
109 char* body;
110 size_t body_len;
111 };
112
tr_mimepart_free(struct tr_mimepart * p)113 static void tr_mimepart_free(struct tr_mimepart* p)
114 {
115 tr_free(p->body);
116 tr_free(p->headers);
117 tr_free(p);
118 }
119
extract_parts_from_multipart(struct evkeyvalq const * headers,struct evbuffer * body,tr_ptrArray * setme_parts)120 static void extract_parts_from_multipart(struct evkeyvalq const* headers, struct evbuffer* body, tr_ptrArray* setme_parts)
121 {
122 char const* content_type = evhttp_find_header(headers, "Content-Type");
123 char const* in = (char const*)evbuffer_pullup(body, -1);
124 size_t inlen = evbuffer_get_length(body);
125
126 char const* boundary_key = "boundary=";
127 char const* boundary_key_begin = content_type != NULL ? strstr(content_type, boundary_key) : NULL;
128 char const* boundary_val = boundary_key_begin != NULL ? boundary_key_begin + strlen(boundary_key) : "arglebargle";
129 char* boundary = tr_strdup_printf("--%s", boundary_val);
130 size_t const boundary_len = strlen(boundary);
131
132 char const* delim = tr_memmem(in, inlen, boundary, boundary_len);
133
134 while (delim != NULL)
135 {
136 size_t part_len;
137 char const* part = delim + boundary_len;
138
139 inlen -= part - in;
140 in = part;
141
142 delim = tr_memmem(in, inlen, boundary, boundary_len);
143 part_len = delim != NULL ? (size_t)(delim - part) : inlen;
144
145 if (part_len != 0)
146 {
147 char const* rnrn = tr_memmem(part, part_len, "\r\n\r\n", 4);
148
149 if (rnrn != NULL)
150 {
151 struct tr_mimepart* p = tr_new(struct tr_mimepart, 1);
152 p->headers_len = (size_t)(rnrn - part);
153 p->headers = tr_strndup(part, p->headers_len);
154 p->body_len = (size_t)((part + part_len) - (rnrn + 4));
155 p->body = tr_strndup(rnrn + 4, p->body_len);
156 tr_ptrArrayAppend(setme_parts, p);
157 }
158 }
159 }
160
161 tr_free(boundary);
162 }
163
handle_upload(struct evhttp_request * req,struct tr_rpc_server * server)164 static void handle_upload(struct evhttp_request* req, struct tr_rpc_server* server)
165 {
166 if (req->type != EVHTTP_REQ_POST)
167 {
168 send_simple_response(req, 405, NULL);
169 }
170 else
171 {
172 int n;
173 bool hasSessionId = false;
174 tr_ptrArray parts = TR_PTR_ARRAY_INIT;
175
176 char const* query = strchr(req->uri, '?');
177 bool const paused = query != NULL && strstr(query + 1, "paused=true") != NULL;
178
179 extract_parts_from_multipart(req->input_headers, req->input_buffer, &parts);
180 n = tr_ptrArraySize(&parts);
181
182 /* first look for the session id */
183 for (int i = 0; i < n; ++i)
184 {
185 struct tr_mimepart* p = tr_ptrArrayNth(&parts, i);
186
187 if (tr_strcasestr(p->headers, TR_RPC_SESSION_ID_HEADER) != NULL)
188 {
189 char const* ours = get_current_session_id(server);
190 size_t const ourlen = strlen(ours);
191 hasSessionId = ourlen <= p->body_len && memcmp(p->body, ours, ourlen) == 0;
192 break;
193 }
194 }
195
196 if (!hasSessionId)
197 {
198 int code = 409;
199 char const* codetext = tr_webGetResponseStr(code);
200 struct evbuffer* body = evbuffer_new();
201 evbuffer_add_printf(body, "%s", "{ \"success\": false, \"msg\": \"Bad Session-Id\" }");
202 evhttp_send_reply(req, code, codetext, body);
203 evbuffer_free(body);
204 }
205 else
206 {
207 for (int i = 0; i < n; ++i)
208 {
209 struct tr_mimepart* p = tr_ptrArrayNth(&parts, i);
210 size_t body_len = p->body_len;
211 tr_variant top;
212 tr_variant* args;
213 tr_variant test;
214 bool have_source = false;
215 char* body = p->body;
216
217 if (body_len >= 2 && memcmp(&body[body_len - 2], "\r\n", 2) == 0)
218 {
219 body_len -= 2;
220 }
221
222 tr_variantInitDict(&top, 2);
223 tr_variantDictAddStr(&top, TR_KEY_method, "torrent-add");
224 args = tr_variantDictAddDict(&top, TR_KEY_arguments, 2);
225 tr_variantDictAddBool(args, TR_KEY_paused, paused);
226
227 if (tr_urlIsValid(body, body_len))
228 {
229 tr_variantDictAddRaw(args, TR_KEY_filename, body, body_len);
230 have_source = true;
231 }
232 else if (tr_variantFromBenc(&test, body, body_len) == 0)
233 {
234 char* b64 = tr_base64_encode(body, body_len, NULL);
235 tr_variantDictAddStr(args, TR_KEY_metainfo, b64);
236 tr_free(b64);
237 have_source = true;
238 }
239
240 if (have_source)
241 {
242 tr_rpc_request_exec_json(server->session, &top, NULL, NULL);
243 }
244
245 tr_variantFree(&top);
246 }
247 }
248
249 tr_ptrArrayDestruct(&parts, (PtrArrayForeachFunc)tr_mimepart_free);
250
251 /* send "success" response */
252 {
253 int code = HTTP_OK;
254 char const* codetext = tr_webGetResponseStr(code);
255 struct evbuffer* body = evbuffer_new();
256 evbuffer_add_printf(body, "%s", "{ \"success\": true, \"msg\": \"Torrent Added\" }");
257 evhttp_send_reply(req, code, codetext, body);
258 evbuffer_free(body);
259 }
260 }
261 }
262
263 /***
264 ****
265 ***/
266
mimetype_guess(char const * path)267 static char const* mimetype_guess(char const* path)
268 {
269 struct
270 {
271 char const* suffix;
272 char const* mime_type;
273 }
274 const types[] =
275 {
276 /* these are the ones we need for serving the web client's files... */
277 { "css", "text/css" },
278 { "gif", "image/gif" },
279 { "html", "text/html" },
280 { "ico", "image/vnd.microsoft.icon" },
281 { "js", "application/javascript" },
282 { "png", "image/png" }
283 };
284 char const* dot = strrchr(path, '.');
285
286 for (unsigned int i = 0; dot != NULL && i < TR_N_ELEMENTS(types); ++i)
287 {
288 if (strcmp(dot + 1, types[i].suffix) == 0)
289 {
290 return types[i].mime_type;
291 }
292 }
293
294 return "application/octet-stream";
295 }
296
add_response(struct evhttp_request * req,struct tr_rpc_server * server,struct evbuffer * out,struct evbuffer * content)297 static void add_response(struct evhttp_request* req, struct tr_rpc_server* server, struct evbuffer* out,
298 struct evbuffer* content)
299 {
300 char const* key = "Accept-Encoding";
301 char const* encoding = evhttp_find_header(req->input_headers, key);
302 bool const do_compress = encoding != NULL && strstr(encoding, "gzip") != NULL;
303
304 if (!do_compress)
305 {
306 evbuffer_add_buffer(out, content);
307 }
308 else
309 {
310 int state;
311 struct evbuffer_iovec iovec[1];
312 void* content_ptr = evbuffer_pullup(content, -1);
313 size_t const content_len = evbuffer_get_length(content);
314
315 if (!server->isStreamInitialized)
316 {
317 int compressionLevel;
318
319 server->isStreamInitialized = true;
320 server->stream.zalloc = (alloc_func)Z_NULL;
321 server->stream.zfree = (free_func)Z_NULL;
322 server->stream.opaque = (voidpf)Z_NULL;
323
324 /* zlib's manual says: "Add 16 to windowBits to write a simple gzip header
325 * and trailer around the compressed data instead of a zlib wrapper." */
326 #ifdef TR_LIGHTWEIGHT
327 compressionLevel = Z_DEFAULT_COMPRESSION;
328 #else
329 compressionLevel = Z_BEST_COMPRESSION;
330 #endif
331 deflateInit2(&server->stream, compressionLevel, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
332 }
333
334 server->stream.next_in = content_ptr;
335 server->stream.avail_in = content_len;
336
337 /* allocate space for the raw data and call deflate() just once --
338 * we won't use the deflated data if it's longer than the raw data,
339 * so it's okay to let deflate() run out of output buffer space */
340 evbuffer_reserve_space(out, content_len, iovec, 1);
341 server->stream.next_out = iovec[0].iov_base;
342 server->stream.avail_out = iovec[0].iov_len;
343 state = deflate(&server->stream, Z_FINISH);
344
345 if (state == Z_STREAM_END)
346 {
347 iovec[0].iov_len -= server->stream.avail_out;
348
349 #if 0
350
351 fprintf(stderr, "compressed response is %.2f of original (raw==%zu bytes; compressed==%zu)\n",
352 (double)evbuffer_get_length(out) / content_len, content_len, evbuffer_get_length(out));
353
354 #endif
355
356 evhttp_add_header(req->output_headers, "Content-Encoding", "gzip");
357 }
358 else
359 {
360 memcpy(iovec[0].iov_base, content_ptr, content_len);
361 iovec[0].iov_len = content_len;
362 }
363
364 evbuffer_commit_space(out, iovec, 1);
365 deflateReset(&server->stream);
366 }
367 }
368
add_time_header(struct evkeyvalq * headers,char const * key,time_t value)369 static void add_time_header(struct evkeyvalq* headers, char const* key, time_t value)
370 {
371 /* According to RFC 2616 this must follow RFC 1123's date format,
372 so use gmtime instead of localtime... */
373 char buf[128];
374 struct tm tm = *gmtime(&value);
375 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm);
376 evhttp_add_header(headers, key, buf);
377 }
378
evbuffer_ref_cleanup_tr_free(void const * data UNUSED,size_t datalen UNUSED,void * extra)379 static void evbuffer_ref_cleanup_tr_free(void const* data UNUSED, size_t datalen UNUSED, void* extra)
380 {
381 tr_free(extra);
382 }
383
serve_file(struct evhttp_request * req,struct tr_rpc_server * server,char const * filename)384 static void serve_file(struct evhttp_request* req, struct tr_rpc_server* server, char const* filename)
385 {
386 if (req->type != EVHTTP_REQ_GET)
387 {
388 evhttp_add_header(req->output_headers, "Allow", "GET");
389 send_simple_response(req, 405, NULL);
390 }
391 else
392 {
393 void* file;
394 size_t file_len;
395 tr_error* error = NULL;
396
397 file_len = 0;
398 file = tr_loadFile(filename, &file_len, &error);
399
400 if (file == NULL)
401 {
402 char* tmp = tr_strdup_printf("%s (%s)", filename, error->message);
403 send_simple_response(req, HTTP_NOTFOUND, tmp);
404 tr_free(tmp);
405 tr_error_free(error);
406 }
407 else
408 {
409 struct evbuffer* content;
410 struct evbuffer* out;
411 time_t const now = tr_time();
412
413 content = evbuffer_new();
414 evbuffer_add_reference(content, file, file_len, evbuffer_ref_cleanup_tr_free, file);
415
416 out = evbuffer_new();
417 evhttp_add_header(req->output_headers, "Content-Type", mimetype_guess(filename));
418 add_time_header(req->output_headers, "Date", now);
419 add_time_header(req->output_headers, "Expires", now + (24 * 60 * 60));
420 add_response(req, server, out, content);
421 evhttp_send_reply(req, HTTP_OK, "OK", out);
422
423 evbuffer_free(out);
424 evbuffer_free(content);
425 }
426 }
427 }
428
handle_web_client(struct evhttp_request * req,struct tr_rpc_server * server)429 static void handle_web_client(struct evhttp_request* req, struct tr_rpc_server* server)
430 {
431 char const* webClientDir = tr_getWebClientDir(server->session);
432
433 if (tr_str_is_empty(webClientDir))
434 {
435 send_simple_response(req, HTTP_NOTFOUND,
436 "<p>Couldn't find Transmission's web interface files!</p>"
437 "<p>Users: to tell Transmission where to look, "
438 "set the TRANSMISSION_WEB_HOME environment "
439 "variable to the folder where the web interface's "
440 "index.html is located.</p>"
441 "<p>Package Builders: to set a custom default at compile time, "
442 "#define PACKAGE_DATA_DIR in libtransmission/platform.c "
443 "or tweak tr_getClutchDir() by hand.</p>");
444 }
445 else
446 {
447 char* pch;
448 char* subpath;
449
450 subpath = tr_strdup(req->uri + strlen(server->url) + 4);
451
452 if ((pch = strchr(subpath, '?')) != NULL)
453 {
454 *pch = '\0';
455 }
456
457 if (strstr(subpath, "..") != NULL)
458 {
459 send_simple_response(req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>");
460 }
461 else
462 {
463 char* filename = tr_strdup_printf("%s%s%s", webClientDir, TR_PATH_DELIMITER_STR,
464 tr_str_is_empty(subpath) ? "index.html" : subpath);
465 serve_file(req, server, filename);
466 tr_free(filename);
467 }
468
469 tr_free(subpath);
470 }
471 }
472
473 struct rpc_response_data
474 {
475 struct evhttp_request* req;
476 struct tr_rpc_server* server;
477 };
478
rpc_response_func(tr_session * session UNUSED,tr_variant * response,void * user_data)479 static void rpc_response_func(tr_session* session UNUSED, tr_variant* response, void* user_data)
480 {
481 struct rpc_response_data* data = user_data;
482 struct evbuffer* response_buf = tr_variantToBuf(response, TR_VARIANT_FMT_JSON_LEAN);
483 struct evbuffer* buf = evbuffer_new();
484
485 add_response(data->req, data->server, buf, response_buf);
486 evhttp_add_header(data->req->output_headers, "Content-Type", "application/json; charset=UTF-8");
487 evhttp_send_reply(data->req, HTTP_OK, "OK", buf);
488
489 evbuffer_free(buf);
490 evbuffer_free(response_buf);
491 tr_free(data);
492 }
493
handle_rpc_from_json(struct evhttp_request * req,struct tr_rpc_server * server,char const * json,size_t json_len)494 static void handle_rpc_from_json(struct evhttp_request* req, struct tr_rpc_server* server, char const* json, size_t json_len)
495 {
496 tr_variant top;
497 bool have_content = tr_variantFromJson(&top, json, json_len) == 0;
498 struct rpc_response_data* data;
499
500 data = tr_new0(struct rpc_response_data, 1);
501 data->req = req;
502 data->server = server;
503
504 tr_rpc_request_exec_json(server->session, have_content ? &top : NULL, rpc_response_func, data);
505
506 if (have_content)
507 {
508 tr_variantFree(&top);
509 }
510 }
511
handle_rpc(struct evhttp_request * req,struct tr_rpc_server * server)512 static void handle_rpc(struct evhttp_request* req, struct tr_rpc_server* server)
513 {
514 if (req->type == EVHTTP_REQ_POST)
515 {
516 handle_rpc_from_json(req, server, (char const*)evbuffer_pullup(req->input_buffer, -1),
517 evbuffer_get_length(req->input_buffer));
518 return;
519 }
520
521 if (req->type == EVHTTP_REQ_GET)
522 {
523 char const* q = strchr(req->uri, '?');
524
525 if (q != NULL)
526 {
527 struct rpc_response_data* data = tr_new0(struct rpc_response_data, 1);
528 data->req = req;
529 data->server = server;
530 tr_rpc_request_exec_uri(server->session, q + 1, TR_BAD_SIZE, rpc_response_func, data);
531 return;
532 }
533 }
534
535 send_simple_response(req, 405, NULL);
536 }
537
isAddressAllowed(tr_rpc_server const * server,char const * address)538 static bool isAddressAllowed(tr_rpc_server const* server, char const* address)
539 {
540 if (!server->isWhitelistEnabled)
541 {
542 return true;
543 }
544
545 for (tr_list* l = server->whitelist; l != NULL; l = l->next)
546 {
547 if (tr_wildmat(address, l->data))
548 {
549 return true;
550 }
551 }
552
553 return false;
554 }
555
isIPAddressWithOptionalPort(char const * host)556 static bool isIPAddressWithOptionalPort(char const* host)
557 {
558 struct sockaddr_storage address;
559 int address_len = sizeof(address);
560
561 /* TODO: move to net.{c,h} */
562 return evutil_parse_sockaddr_port(host, (struct sockaddr*)&address, &address_len) != -1;
563 }
564
isHostnameAllowed(tr_rpc_server const * server,struct evhttp_request * req)565 static bool isHostnameAllowed(tr_rpc_server const* server, struct evhttp_request* req)
566 {
567 /* If password auth is enabled, any hostname is permitted. */
568 if (server->isPasswordEnabled)
569 {
570 return true;
571 }
572
573 /* If whitelist is disabled, no restrictions. */
574 if (!server->isHostWhitelistEnabled)
575 {
576 return true;
577 }
578
579 char const* const host = evhttp_find_header(req->input_headers, "Host");
580
581 /* No host header, invalid request. */
582 if (host == NULL)
583 {
584 return false;
585 }
586
587 /* IP address is always acceptable. */
588 if (isIPAddressWithOptionalPort(host))
589 {
590 return true;
591 }
592
593 /* Host header might include the port. */
594 char* const hostname = tr_strndup(host, strcspn(host, ":"));
595
596 /* localhost is always acceptable. */
597 if (strcmp(hostname, "localhost") == 0 || strcmp(hostname, "localhost.") == 0)
598 {
599 tr_free(hostname);
600 return true;
601 }
602
603 /* Otherwise, hostname must be whitelisted. */
604 for (tr_list* l = server->hostWhitelist; l != NULL; l = l->next)
605 {
606 if (tr_wildmat(hostname, l->data))
607 {
608 tr_free(hostname);
609 return true;
610 }
611 }
612
613 tr_free(hostname);
614 return false;
615 }
616
test_session_id(struct tr_rpc_server * server,struct evhttp_request * req)617 static bool test_session_id(struct tr_rpc_server* server, struct evhttp_request* req)
618 {
619 char const* ours = get_current_session_id(server);
620 char const* theirs = evhttp_find_header(req->input_headers, TR_RPC_SESSION_ID_HEADER);
621 bool const success = theirs != NULL && strcmp(theirs, ours) == 0;
622 return success;
623 }
624
handle_request(struct evhttp_request * req,void * arg)625 static void handle_request(struct evhttp_request* req, void* arg)
626 {
627 struct tr_rpc_server* server = arg;
628
629 if (req != NULL && req->evcon != NULL)
630 {
631 char const* auth;
632 char* user = NULL;
633 char* pass = NULL;
634
635 evhttp_add_header(req->output_headers, "Server", MY_REALM);
636
637 if (server->loginattempts == 100)
638 {
639 send_simple_response(req, 403, "<p>Too many unsuccessful login attempts. Please restart transmission-daemon.</p>");
640 return;
641 }
642
643 if (!isAddressAllowed(server, req->remote_host))
644 {
645 send_simple_response(req, 403,
646 "<p>Unauthorized IP Address.</p>"
647 "<p>Either disable the IP address whitelist or add your address to it.</p>"
648 "<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>"
649 "<p>If you're still using ACLs, use a whitelist instead. See the transmission-daemon manpage for details.</p>");
650 return;
651 }
652
653 auth = evhttp_find_header(req->input_headers, "Authorization");
654
655 if (auth != NULL && evutil_ascii_strncasecmp(auth, "basic ", 6) == 0)
656 {
657 char* p = tr_base64_decode_str(auth + 6, NULL);
658
659 if (p != NULL)
660 {
661 if ((pass = strchr(p, ':')) != NULL)
662 {
663 user = p;
664 *pass++ = '\0';
665 }
666 else
667 {
668 tr_free(p);
669 }
670 }
671 }
672
673 if (server->isPasswordEnabled && (pass == NULL || user == NULL || strcmp(server->username, user) != 0 ||
674 !tr_ssha1_matches(server->password, pass)))
675 {
676 evhttp_add_header(req->output_headers, "WWW-Authenticate", "Basic realm=\"" MY_REALM "\"");
677 server->loginattempts++;
678 char* unauthuser = tr_strdup_printf("<p>Unauthorized User. %d unsuccessful login attempts.</p>",
679 server->loginattempts);
680 send_simple_response(req, 401, unauthuser);
681 tr_free(unauthuser);
682 tr_free(user);
683 return;
684 }
685
686 server->loginattempts = 0;
687
688 if (strncmp(req->uri, server->url, strlen(server->url)) != 0)
689 {
690 char* location = tr_strdup_printf("%sweb/", server->url);
691 evhttp_add_header(req->output_headers, "Location", location);
692 send_simple_response(req, HTTP_MOVEPERM, NULL);
693 tr_free(location);
694 }
695 else if (strncmp(req->uri + strlen(server->url), "web/", 4) == 0)
696 {
697 handle_web_client(req, server);
698 }
699 else if (strcmp(req->uri + strlen(server->url), "upload") == 0)
700 {
701 handle_upload(req, server);
702 }
703 else if (!isHostnameAllowed(server, req))
704 {
705 char* const tmp = tr_strdup_printf(
706 "<p>Transmission received your request, but the hostname was unrecognized.</p>"
707 "<p>To fix this, choose one of the following options:"
708 "<ul>"
709 "<li>Enable password authentication, then any hostname is allowed.</li>"
710 "<li>Add the hostname you want to use to the whitelist in settings.</li>"
711 "</ul></p>"
712 "<p>If you're editing settings.json, see the 'rpc-host-whitelist' and 'rpc-host-whitelist-enabled' entries.</p>"
713 "<p>This requirement has been added to help prevent "
714 "<a href=\"https://en.wikipedia.org/wiki/DNS_rebinding\">DNS Rebinding</a> "
715 "attacks.</p>");
716 send_simple_response(req, 421, tmp);
717 tr_free(tmp);
718 }
719
720 #ifdef REQUIRE_SESSION_ID
721
722 else if (!test_session_id(server, req))
723 {
724 char const* sessionId = get_current_session_id(server);
725 char* tmp = tr_strdup_printf(
726 "<p>Your request had an invalid session-id header.</p>"
727 "<p>To fix this, follow these steps:"
728 "<ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it"
729 "<li> Add the updated header to your outgoing requests"
730 "<li> When you get this 409 error message, resend your request with the updated header"
731 "</ol></p>"
732 "<p>This requirement has been added to help prevent "
733 "<a href=\"https://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
734 "attacks.</p>"
735 "<p><code>%s: %s</code></p>",
736 TR_RPC_SESSION_ID_HEADER, sessionId);
737 evhttp_add_header(req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId);
738 send_simple_response(req, 409, tmp);
739 tr_free(tmp);
740 }
741
742 #endif
743
744 else if (strncmp(req->uri + strlen(server->url), "rpc", 3) == 0)
745 {
746 handle_rpc(req, server);
747 }
748 else
749 {
750 send_simple_response(req, HTTP_NOTFOUND, req->uri);
751 }
752
753 tr_free(user);
754 }
755 }
756
757 enum
758 {
759 SERVER_START_RETRY_COUNT = 10,
760 SERVER_START_RETRY_DELAY_STEP = 3,
761 SERVER_START_RETRY_DELAY_INCREMENT = 5,
762 SERVER_START_RETRY_MAX_DELAY = 60
763 };
764
765 static void startServer(void* vserver);
766
rpc_server_on_start_retry(evutil_socket_t fd UNUSED,short type UNUSED,void * context)767 static void rpc_server_on_start_retry(evutil_socket_t fd UNUSED, short type UNUSED, void* context)
768 {
769 startServer(context);
770 }
771
rpc_server_start_retry(tr_rpc_server * server)772 static int rpc_server_start_retry(tr_rpc_server* server)
773 {
774 int retry_delay = (server->start_retry_counter / SERVER_START_RETRY_DELAY_STEP + 1) * SERVER_START_RETRY_DELAY_INCREMENT;
775 retry_delay = MIN(retry_delay, SERVER_START_RETRY_MAX_DELAY);
776
777 if (server->start_retry_timer == NULL)
778 {
779 server->start_retry_timer = evtimer_new(server->session->event_base, rpc_server_on_start_retry, server);
780 }
781
782 tr_timerAdd(server->start_retry_timer, retry_delay, 0);
783 ++server->start_retry_counter;
784
785 return retry_delay;
786 }
787
rpc_server_start_retry_cancel(tr_rpc_server * server)788 static void rpc_server_start_retry_cancel(tr_rpc_server* server)
789 {
790 if (server->start_retry_timer != NULL)
791 {
792 event_free(server->start_retry_timer);
793 server->start_retry_timer = NULL;
794 }
795
796 server->start_retry_counter = 0;
797 }
798
startServer(void * vserver)799 static void startServer(void* vserver)
800 {
801 tr_rpc_server* server = vserver;
802
803 if (server->httpd != NULL)
804 {
805 return;
806 }
807
808 struct evhttp* httpd = evhttp_new(server->session->event_base);
809
810 char const* address = tr_rpcGetBindAddress(server);
811
812 int const port = server->port;
813
814 if (evhttp_bind_socket(httpd, address, port) == -1)
815 {
816 evhttp_free(httpd);
817
818 if (server->start_retry_counter < SERVER_START_RETRY_COUNT)
819 {
820 int const retry_delay = rpc_server_start_retry(server);
821
822 tr_logAddNamedDbg(MY_NAME, "Unable to bind to %s:%d, retrying in %d seconds", address, port, retry_delay);
823 return;
824 }
825
826 tr_logAddNamedError(MY_NAME, "Unable to bind to %s:%d after %d attempts, giving up", address, port,
827 SERVER_START_RETRY_COUNT);
828 }
829 else
830 {
831 evhttp_set_gencb(httpd, handle_request, server);
832 server->httpd = httpd;
833
834 tr_logAddNamedDbg(MY_NAME, "Started listening on %s:%d", address, port);
835 }
836
837 rpc_server_start_retry_cancel(server);
838 }
839
stopServer(tr_rpc_server * server)840 static void stopServer(tr_rpc_server* server)
841 {
842 rpc_server_start_retry_cancel(server);
843
844 struct evhttp* httpd = server->httpd;
845
846 if (httpd == NULL)
847 {
848 return;
849 }
850
851 char const* address = tr_rpcGetBindAddress(server);
852 int const port = server->port;
853
854 server->httpd = NULL;
855 evhttp_free(httpd);
856
857 tr_logAddNamedDbg(MY_NAME, "Stopped listening on %s:%d", address, port);
858 }
859
onEnabledChanged(void * vserver)860 static void onEnabledChanged(void* vserver)
861 {
862 tr_rpc_server* server = vserver;
863
864 if (!server->isEnabled)
865 {
866 stopServer(server);
867 }
868 else
869 {
870 startServer(server);
871 }
872 }
873
tr_rpcSetEnabled(tr_rpc_server * server,bool isEnabled)874 void tr_rpcSetEnabled(tr_rpc_server* server, bool isEnabled)
875 {
876 server->isEnabled = isEnabled;
877
878 tr_runInEventThread(server->session, onEnabledChanged, server);
879 }
880
tr_rpcIsEnabled(tr_rpc_server const * server)881 bool tr_rpcIsEnabled(tr_rpc_server const* server)
882 {
883 return server->isEnabled;
884 }
885
restartServer(void * vserver)886 static void restartServer(void* vserver)
887 {
888 tr_rpc_server* server = vserver;
889
890 if (server->isEnabled)
891 {
892 stopServer(server);
893 startServer(server);
894 }
895 }
896
tr_rpcSetPort(tr_rpc_server * server,tr_port port)897 void tr_rpcSetPort(tr_rpc_server* server, tr_port port)
898 {
899 TR_ASSERT(server != NULL);
900
901 if (server->port != port)
902 {
903 server->port = port;
904
905 if (server->isEnabled)
906 {
907 tr_runInEventThread(server->session, restartServer, server);
908 }
909 }
910 }
911
tr_rpcGetPort(tr_rpc_server const * server)912 tr_port tr_rpcGetPort(tr_rpc_server const* server)
913 {
914 return server->port;
915 }
916
tr_rpcSetUrl(tr_rpc_server * server,char const * url)917 void tr_rpcSetUrl(tr_rpc_server* server, char const* url)
918 {
919 char* tmp = server->url;
920 server->url = tr_strdup(url);
921 dbgmsg("setting our URL to [%s]", server->url);
922 tr_free(tmp);
923 }
924
tr_rpcGetUrl(tr_rpc_server const * server)925 char const* tr_rpcGetUrl(tr_rpc_server const* server)
926 {
927 return server->url != NULL ? server->url : "";
928 }
929
tr_rpcSetList(char const * whitelistStr,tr_list ** list)930 static void tr_rpcSetList(char const* whitelistStr, tr_list** list)
931 {
932 void* tmp;
933
934 /* clear out the old whitelist entries */
935 while ((tmp = tr_list_pop_front(list)) != NULL)
936 {
937 tr_free(tmp);
938 }
939
940 /* build the new whitelist entries */
941 for (char const* walk = whitelistStr; !tr_str_is_empty(walk);)
942 {
943 char const* delimiters = " ,;";
944 size_t const len = strcspn(walk, delimiters);
945 char* token = tr_strndup(walk, len);
946
947 tr_list_append(list, token);
948
949 if (strcspn(token, "+-") < len)
950 {
951 tr_logAddNamedInfo(MY_NAME,
952 "Adding address to whitelist: %s (And it has a '+' or '-'! Are you using an old ACL by mistake?)", token);
953 }
954 else
955 {
956 tr_logAddNamedInfo(MY_NAME, "Adding address to whitelist: %s", token);
957 }
958
959 walk += len;
960
961 if (*walk == '\0')
962 {
963 break;
964 }
965
966 ++walk;
967 }
968 }
969
tr_rpcSetHostWhitelist(tr_rpc_server * server,char const * whitelistStr)970 void tr_rpcSetHostWhitelist(tr_rpc_server* server, char const* whitelistStr)
971 {
972 tr_rpcSetList(whitelistStr, &server->hostWhitelist);
973 }
974
tr_rpcSetWhitelist(tr_rpc_server * server,char const * whitelistStr)975 void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr)
976 {
977 /* keep the string */
978 char* const tmp = server->whitelistStr;
979 server->whitelistStr = tr_strdup(whitelistStr);
980 tr_free(tmp);
981
982 tr_rpcSetList(whitelistStr, &server->whitelist);
983 }
984
tr_rpcGetWhitelist(tr_rpc_server const * server)985 char const* tr_rpcGetWhitelist(tr_rpc_server const* server)
986 {
987 return server->whitelistStr != NULL ? server->whitelistStr : "";
988 }
989
tr_rpcSetWhitelistEnabled(tr_rpc_server * server,bool isEnabled)990 void tr_rpcSetWhitelistEnabled(tr_rpc_server* server, bool isEnabled)
991 {
992 server->isWhitelistEnabled = isEnabled;
993 }
994
tr_rpcGetWhitelistEnabled(tr_rpc_server const * server)995 bool tr_rpcGetWhitelistEnabled(tr_rpc_server const* server)
996 {
997 return server->isWhitelistEnabled;
998 }
999
tr_rpcSetHostWhitelistEnabled(tr_rpc_server * server,bool isEnabled)1000 void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled)
1001 {
1002 server->isHostWhitelistEnabled = isEnabled;
1003 }
1004
1005 /****
1006 ***** PASSWORD
1007 ****/
1008
tr_rpcSetUsername(tr_rpc_server * server,char const * username)1009 void tr_rpcSetUsername(tr_rpc_server* server, char const* username)
1010 {
1011 char* tmp = server->username;
1012 server->username = tr_strdup(username);
1013 dbgmsg("setting our Username to [%s]", server->username);
1014 tr_free(tmp);
1015 }
1016
tr_rpcGetUsername(tr_rpc_server const * server)1017 char const* tr_rpcGetUsername(tr_rpc_server const* server)
1018 {
1019 return server->username != NULL ? server->username : "";
1020 }
1021
tr_rpcSetPassword(tr_rpc_server * server,char const * password)1022 void tr_rpcSetPassword(tr_rpc_server* server, char const* password)
1023 {
1024 tr_free(server->password);
1025
1026 if (*password != '{')
1027 {
1028 server->password = tr_ssha1(password);
1029 }
1030 else
1031 {
1032 server->password = strdup(password);
1033 }
1034
1035 dbgmsg("setting our Password to [%s]", server->password);
1036 }
1037
tr_rpcGetPassword(tr_rpc_server const * server)1038 char const* tr_rpcGetPassword(tr_rpc_server const* server)
1039 {
1040 return server->password != NULL ? server->password : "";
1041 }
1042
tr_rpcSetPasswordEnabled(tr_rpc_server * server,bool isEnabled)1043 void tr_rpcSetPasswordEnabled(tr_rpc_server* server, bool isEnabled)
1044 {
1045 server->isPasswordEnabled = isEnabled;
1046 dbgmsg("setting 'password enabled' to %d", (int)isEnabled);
1047 }
1048
tr_rpcIsPasswordEnabled(tr_rpc_server const * server)1049 bool tr_rpcIsPasswordEnabled(tr_rpc_server const* server)
1050 {
1051 return server->isPasswordEnabled;
1052 }
1053
tr_rpcGetBindAddress(tr_rpc_server const * server)1054 char const* tr_rpcGetBindAddress(tr_rpc_server const* server)
1055 {
1056 return tr_address_to_string(&server->bindAddress);
1057 }
1058
1059 /****
1060 ***** LIFE CYCLE
1061 ****/
1062
closeServer(void * vserver)1063 static void closeServer(void* vserver)
1064 {
1065 void* tmp;
1066 tr_rpc_server* s = vserver;
1067
1068 stopServer(s);
1069
1070 while ((tmp = tr_list_pop_front(&s->whitelist)) != NULL)
1071 {
1072 tr_free(tmp);
1073 }
1074
1075 if (s->isStreamInitialized)
1076 {
1077 deflateEnd(&s->stream);
1078 }
1079
1080 tr_free(s->url);
1081 tr_free(s->whitelistStr);
1082 tr_free(s->username);
1083 tr_free(s->password);
1084 tr_free(s);
1085 }
1086
tr_rpcClose(tr_rpc_server ** ps)1087 void tr_rpcClose(tr_rpc_server** ps)
1088 {
1089 tr_runInEventThread((*ps)->session, closeServer, *ps);
1090 *ps = NULL;
1091 }
1092
missing_settings_key(tr_quark const q)1093 static void missing_settings_key(tr_quark const q)
1094 {
1095 char const* str = tr_quark_get_string(q, NULL);
1096 tr_logAddNamedError(MY_NAME, _("Couldn't find settings key \"%s\""), str);
1097 }
1098
tr_rpcInit(tr_session * session,tr_variant * settings)1099 tr_rpc_server* tr_rpcInit(tr_session* session, tr_variant* settings)
1100 {
1101 tr_rpc_server* s;
1102 bool boolVal;
1103 int64_t i;
1104 char const* str;
1105 tr_quark key;
1106 tr_address address;
1107
1108 s = tr_new0(tr_rpc_server, 1);
1109 s->session = session;
1110
1111 key = TR_KEY_rpc_enabled;
1112
1113 if (!tr_variantDictFindBool(settings, key, &boolVal))
1114 {
1115 missing_settings_key(key);
1116 }
1117 else
1118 {
1119 s->isEnabled = boolVal;
1120 }
1121
1122 key = TR_KEY_rpc_port;
1123
1124 if (!tr_variantDictFindInt(settings, key, &i))
1125 {
1126 missing_settings_key(key);
1127 }
1128 else
1129 {
1130 s->port = i;
1131 }
1132
1133 key = TR_KEY_rpc_url;
1134
1135 if (!tr_variantDictFindStr(settings, key, &str, NULL))
1136 {
1137 missing_settings_key(key);
1138 }
1139 else
1140 {
1141 s->url = tr_strdup(str);
1142 }
1143
1144 key = TR_KEY_rpc_whitelist_enabled;
1145
1146 if (!tr_variantDictFindBool(settings, key, &boolVal))
1147 {
1148 missing_settings_key(key);
1149 }
1150 else
1151 {
1152 tr_rpcSetWhitelistEnabled(s, boolVal);
1153 }
1154
1155 key = TR_KEY_rpc_host_whitelist_enabled;
1156
1157 if (!tr_variantDictFindBool(settings, key, &boolVal))
1158 {
1159 missing_settings_key(key);
1160 }
1161 else
1162 {
1163 tr_rpcSetHostWhitelistEnabled(s, boolVal);
1164 }
1165
1166 key = TR_KEY_rpc_host_whitelist;
1167
1168 if (!tr_variantDictFindStr(settings, key, &str, NULL) && str != NULL)
1169 {
1170 missing_settings_key(key);
1171 }
1172 else
1173 {
1174 tr_rpcSetHostWhitelist(s, str);
1175 }
1176
1177 key = TR_KEY_rpc_authentication_required;
1178
1179 if (!tr_variantDictFindBool(settings, key, &boolVal))
1180 {
1181 missing_settings_key(key);
1182 }
1183 else
1184 {
1185 tr_rpcSetPasswordEnabled(s, boolVal);
1186 }
1187
1188 key = TR_KEY_rpc_whitelist;
1189
1190 if (!tr_variantDictFindStr(settings, key, &str, NULL) && str != NULL)
1191 {
1192 missing_settings_key(key);
1193 }
1194 else
1195 {
1196 tr_rpcSetWhitelist(s, str);
1197 }
1198
1199 key = TR_KEY_rpc_username;
1200
1201 if (!tr_variantDictFindStr(settings, key, &str, NULL))
1202 {
1203 missing_settings_key(key);
1204 }
1205 else
1206 {
1207 tr_rpcSetUsername(s, str);
1208 }
1209
1210 key = TR_KEY_rpc_password;
1211
1212 if (!tr_variantDictFindStr(settings, key, &str, NULL))
1213 {
1214 missing_settings_key(key);
1215 }
1216 else
1217 {
1218 tr_rpcSetPassword(s, str);
1219 }
1220
1221 key = TR_KEY_rpc_bind_address;
1222
1223 if (!tr_variantDictFindStr(settings, key, &str, NULL))
1224 {
1225 missing_settings_key(key);
1226 address = tr_inaddr_any;
1227 }
1228 else if (!tr_address_from_string(&address, str))
1229 {
1230 tr_logAddNamedError(MY_NAME, _("%s is not a valid address"), str);
1231 address = tr_inaddr_any;
1232 }
1233 else if (address.type != TR_AF_INET && address.type != TR_AF_INET6)
1234 {
1235 tr_logAddNamedError(MY_NAME, _("%s is not an IPv4 or IPv6 address. RPC listeners must be IPv4 or IPv6"), str);
1236 address = tr_inaddr_any;
1237 }
1238
1239 s->bindAddress = address;
1240
1241 if (s->isEnabled)
1242 {
1243 tr_logAddNamedInfo(MY_NAME, _("Serving RPC and Web requests on %s:%d%s"), tr_rpcGetBindAddress(s), (int)s->port,
1244 s->url);
1245 tr_runInEventThread(session, startServer, s);
1246
1247 if (s->isWhitelistEnabled)
1248 {
1249 tr_logAddNamedInfo(MY_NAME, "%s", _("Whitelist enabled"));
1250 }
1251
1252 if (s->isPasswordEnabled)
1253 {
1254 tr_logAddNamedInfo(MY_NAME, "%s", _("Password required"));
1255 }
1256 }
1257
1258 return s;
1259 }
1260