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

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

Forward-declare tr_error structure in headers

  • Property svn:keywords set to Date Rev Author Id
File size: 28.6 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 14493 2015-04-11 14:54: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    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 && *subpath ? subpath : "index.html");
504          serve_file (req, server, filename);
505          tr_free (filename);
506        }
507
508      tr_free (subpath);
509    }
510}
511
512struct rpc_response_data
513{
514  struct evhttp_request * req;
515  struct tr_rpc_server  * server;
516};
517
518static void
519rpc_response_func (tr_session      * session UNUSED,
520                   struct evbuffer * response,
521                   void            * user_data)
522{
523  struct rpc_response_data * data = user_data;
524  struct evbuffer * buf = evbuffer_new ();
525
526  add_response (data->req, data->server, buf, response);
527  evhttp_add_header (data->req->output_headers,
528                     "Content-Type", "application/json; charset=UTF-8");
529  evhttp_send_reply (data->req, HTTP_OK, "OK", buf);
530
531  evbuffer_free (buf);
532  tr_free (data);
533}
534
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 && plen && ((pass = strchr (p, ':'))))
617            {
618              user = p;
619              *pass++ = '\0';
620            }
621        }
622
623      if (!isAddressAllowed (server, req->remote_host))
624        {
625          send_simple_response (req, 403,
626            "<p>Unauthorized IP Address.</p>"
627            "<p>Either disable the IP address whitelist or add your address to it.</p>"
628            "<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>"
629            "<p>If you're still using ACLs, use a whitelist instead. See the transmission-daemon manpage for details.</p>");
630        }
631      else if (server->isPasswordEnabled
632                 && (!pass || !user || strcmp (server->username, user)
633                                     || !tr_ssha1_matches (server->password,
634                                                           pass)))
635        {
636          evhttp_add_header (req->output_headers,
637                             "WWW-Authenticate",
638                             "Basic realm=\"" MY_REALM "\"");
639          send_simple_response (req, 401, "Unauthorized User");
640        }
641      else if (strncmp (req->uri, server->url, strlen (server->url)))
642        {
643          char * location = tr_strdup_printf ("%sweb/", server->url);
644          evhttp_add_header (req->output_headers, "Location", location);
645          send_simple_response (req, HTTP_MOVEPERM, NULL);
646          tr_free (location);
647        }
648      else if (!strncmp (req->uri + strlen (server->url), "web/", 4))
649        {
650          handle_web_client (req, server);
651        }
652      else if (!strcmp (req->uri + strlen (server->url), "upload"))
653        {
654          handle_upload (req, server);
655        }
656#ifdef REQUIRE_SESSION_ID
657      else if (!test_session_id (server, req))
658        {
659          const char * sessionId = get_current_session_id (server);
660          char * tmp = tr_strdup_printf (
661            "<p>Your request had an invalid session-id header.</p>"
662            "<p>To fix this, follow these steps:"
663            "<ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it"
664            "<li> Add the updated header to your outgoing requests"
665            "<li> When you get this 409 error message, resend your request with the updated header"
666            "</ol></p>"
667            "<p>This requirement has been added to help prevent "
668            "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
669            "attacks.</p>"
670            "<p><code>%s: %s</code></p>",
671            TR_RPC_SESSION_ID_HEADER, sessionId);
672          evhttp_add_header (req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId);
673          send_simple_response (req, 409, tmp);
674          tr_free (tmp);
675        }
676#endif
677      else if (!strncmp (req->uri + strlen (server->url), "rpc", 3))
678        {
679          handle_rpc (req, server);
680        }
681      else
682        {
683          send_simple_response (req, HTTP_NOTFOUND, req->uri);
684        }
685
686      tr_free (user);
687    }
688}
689
690static void
691startServer (void * vserver)
692{
693  tr_rpc_server * server  = vserver;
694  tr_address addr;
695
696  if (!server->httpd)
697    {
698      addr.type = TR_AF_INET;
699      addr.addr.addr4 = server->bindAddress;
700      server->httpd = evhttp_new (server->session->event_base);
701      evhttp_bind_socket (server->httpd, tr_address_to_string (&addr), server->port);
702      evhttp_set_gencb (server->httpd, handle_request, server);
703    }
704}
705
706static void
707stopServer (tr_rpc_server * server)
708{
709  if (server->httpd)
710    {
711      evhttp_free (server->httpd);
712      server->httpd = NULL;
713    }
714}
715
716static void
717onEnabledChanged (void * vserver)
718{
719  tr_rpc_server * server = vserver;
720
721  if (!server->isEnabled)
722    stopServer (server);
723  else
724    startServer (server);
725}
726
727void
728tr_rpcSetEnabled (tr_rpc_server * server,
729                  bool            isEnabled)
730{
731  server->isEnabled = isEnabled;
732
733  tr_runInEventThread (server->session, onEnabledChanged, server);
734}
735
736bool
737tr_rpcIsEnabled (const tr_rpc_server * server)
738{
739  return server->isEnabled;
740}
741
742static void
743restartServer (void * vserver)
744{
745  tr_rpc_server * server = vserver;
746
747  if (server->isEnabled)
748    {
749      stopServer (server);
750      startServer (server);
751    }
752}
753
754void
755tr_rpcSetPort (tr_rpc_server * server,
756               tr_port         port)
757{
758  assert (server != NULL);
759
760  if (server->port != port)
761    {
762      server->port = port;
763
764      if (server->isEnabled)
765        tr_runInEventThread (server->session, restartServer, server);
766    }
767}
768
769tr_port
770tr_rpcGetPort (const tr_rpc_server * server)
771{
772  return server->port;
773}
774
775void
776tr_rpcSetUrl (tr_rpc_server * server, const char * url)
777{
778  char * tmp = server->url;
779  server->url = tr_strdup (url);
780  dbgmsg ("setting our URL to [%s]", server->url);
781  tr_free (tmp);
782}
783
784const char*
785tr_rpcGetUrl (const tr_rpc_server * server)
786{
787  return server->url ? server->url : "";
788}
789
790void
791tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
792{
793  void * tmp;
794  const char * walk;
795
796  /* keep the string */
797  tmp = server->whitelistStr;
798  server->whitelistStr = tr_strdup (whitelistStr);
799  tr_free (tmp);
800
801  /* clear out the old whitelist entries */
802  while ((tmp = tr_list_pop_front (&server->whitelist)))
803    tr_free (tmp);
804
805  /* build the new whitelist entries */
806  for (walk=whitelistStr; walk && *walk;)
807    {
808      const char * delimiters = " ,;";
809      const size_t len = strcspn (walk, delimiters);
810      char * token = tr_strndup (walk, len);
811      tr_list_append (&server->whitelist, token);
812      if (strcspn (token, "+-") < len)
813        tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token);
814      else
815        tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s", token);
816
817      if (walk[len]=='\0')
818        break;
819
820      walk += len + 1;
821    }
822}
823
824const char*
825tr_rpcGetWhitelist (const tr_rpc_server * server)
826{
827  return server->whitelistStr ? server->whitelistStr : "";
828}
829
830void
831tr_rpcSetWhitelistEnabled (tr_rpc_server  * server,
832                           bool             isEnabled)
833{
834  assert (tr_isBool (isEnabled));
835
836  server->isWhitelistEnabled = isEnabled;
837}
838
839bool
840tr_rpcGetWhitelistEnabled (const tr_rpc_server * server)
841{
842  return server->isWhitelistEnabled;
843}
844
845/****
846*****  PASSWORD
847****/
848
849void
850tr_rpcSetUsername (tr_rpc_server * server, const char * username)
851{
852  char * tmp = server->username;
853  server->username = tr_strdup (username);
854  dbgmsg ("setting our Username to [%s]", server->username);
855  tr_free (tmp);
856}
857
858const char*
859tr_rpcGetUsername (const tr_rpc_server * server)
860{
861  return server->username ? server->username : "";
862}
863
864void
865tr_rpcSetPassword (tr_rpc_server * server,
866                   const char *    password)
867{
868  tr_free (server->password);
869  if (*password != '{')
870    server->password = tr_ssha1 (password);
871  else
872    server->password = strdup (password);
873  dbgmsg ("setting our Password to [%s]", server->password);
874}
875
876const char*
877tr_rpcGetPassword (const tr_rpc_server * server)
878{
879  return server->password ? server->password : "" ;
880}
881
882void
883tr_rpcSetPasswordEnabled (tr_rpc_server * server, bool isEnabled)
884{
885  server->isPasswordEnabled = isEnabled;
886  dbgmsg ("setting 'password enabled' to %d", (int)isEnabled);
887}
888
889bool
890tr_rpcIsPasswordEnabled (const tr_rpc_server * server)
891{
892  return server->isPasswordEnabled;
893}
894
895const char *
896tr_rpcGetBindAddress (const tr_rpc_server * server)
897{
898  tr_address addr;
899  addr.type = TR_AF_INET;
900  addr.addr.addr4 = server->bindAddress;
901  return tr_address_to_string (&addr);
902}
903
904/****
905*****  LIFE CYCLE
906****/
907
908static void
909closeServer (void * vserver)
910{
911  void * tmp;
912  tr_rpc_server * s = vserver;
913
914  stopServer (s);
915  while ((tmp = tr_list_pop_front (&s->whitelist)))
916    tr_free (tmp);
917  if (s->isStreamInitialized)
918    deflateEnd (&s->stream);
919  tr_free (s->url);
920  tr_free (s->sessionId);
921  tr_free (s->whitelistStr);
922  tr_free (s->username);
923  tr_free (s->password);
924  tr_free (s);
925}
926
927void
928tr_rpcClose (tr_rpc_server ** ps)
929{
930  tr_runInEventThread ((*ps)->session, closeServer, *ps);
931  *ps = NULL;
932}
933
934static void
935missing_settings_key (const tr_quark q)
936{
937  const char * str = tr_quark_get_string (q, NULL);
938  tr_logAddNamedError (MY_NAME, _("Couldn't find settings key \"%s\""), str);
939}
940
941tr_rpc_server *
942tr_rpcInit (tr_session  * session, tr_variant * settings)
943{
944  tr_rpc_server * s;
945  bool boolVal;
946  int64_t i;
947  const char * str;
948  tr_quark key;
949  tr_address address;
950
951  s = tr_new0 (tr_rpc_server, 1);
952  s->session = session;
953
954  key = TR_KEY_rpc_enabled;
955  if (!tr_variantDictFindBool (settings, key, &boolVal))
956    missing_settings_key (key);
957  else
958    s->isEnabled = boolVal;
959
960  key = TR_KEY_rpc_port;
961  if (!tr_variantDictFindInt (settings, key, &i))
962    missing_settings_key (key);
963  else
964    s->port = i;
965
966  key = TR_KEY_rpc_url;
967  if (!tr_variantDictFindStr (settings, key, &str, NULL))
968    missing_settings_key (key);
969  else
970    s->url = tr_strdup (str);
971
972  key = TR_KEY_rpc_whitelist_enabled;
973  if (!tr_variantDictFindBool (settings, key, &boolVal))
974    missing_settings_key (key);
975  else
976    tr_rpcSetWhitelistEnabled (s, boolVal);
977
978  key = TR_KEY_rpc_authentication_required;
979  if (!tr_variantDictFindBool (settings, key, &boolVal))
980    missing_settings_key (key);
981  else
982    tr_rpcSetPasswordEnabled (s, boolVal);
983
984  key = TR_KEY_rpc_whitelist;
985  if (!tr_variantDictFindStr (settings, key, &str, NULL) && str)
986    missing_settings_key (key);
987  else
988    tr_rpcSetWhitelist (s, str);
989
990  key = TR_KEY_rpc_username;
991  if (!tr_variantDictFindStr (settings, key, &str, NULL))
992    missing_settings_key (key);
993  else
994    tr_rpcSetUsername (s, str);
995
996  key = TR_KEY_rpc_password;
997  if (!tr_variantDictFindStr (settings, key, &str, NULL))
998    missing_settings_key (key);
999  else
1000    tr_rpcSetPassword (s, str);
1001
1002  key = TR_KEY_rpc_bind_address;
1003  if (!tr_variantDictFindStr (settings, key, &str, NULL))
1004    {
1005      missing_settings_key (key);
1006      address = tr_inaddr_any;
1007    }
1008  else if (!tr_address_from_string (&address, str))
1009    {
1010      tr_logAddNamedError (MY_NAME, _("%s is not a valid address"), str);
1011      address = tr_inaddr_any;
1012    }
1013  else if (address.type != TR_AF_INET)
1014    {
1015      tr_logAddNamedError (MY_NAME, _("%s is not an IPv4 address. RPC listeners must be IPv4"), str);
1016      address = tr_inaddr_any;
1017    }
1018  s->bindAddress = address.addr.addr4;
1019
1020  if (s->isEnabled)
1021    {
1022      tr_logAddNamedInfo (MY_NAME, _("Serving RPC and Web requests on port 127.0.0.1:%d%s"), (int) s->port, s->url);
1023      tr_runInEventThread (session, startServer, s);
1024
1025      if (s->isWhitelistEnabled)
1026        tr_logAddNamedInfo (MY_NAME, "%s", _("Whitelist enabled"));
1027
1028      if (s->isPasswordEnabled)
1029        tr_logAddNamedInfo (MY_NAME, "%s", _("Password required"));
1030    }
1031
1032  return s;
1033}
Note: See TracBrowser for help on using the repository browser.