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

Last change on this file since 13934 was 13934, checked in by jordan, 9 years ago

(libT) copyediting: indentation/whitespace in rpc-server.c

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