source: trunk/libtransmission/rpc-server.c @ 14525

Last change on this file since 14525 was 14525, checked in by mikedld, 6 years ago

Fix some issues revealed by coverity

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