source: trunk/libtransmission/rpc-server.c

Last change on this file was 14718, checked in by mikedld, 5 years ago

Explicitly compare result of str(n)cmp/memcmp to signify that it's not boolean

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