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

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

In Web Client, use jQuery.ajax() to upload files

If we use FormData? and jQuery.ajax() calls to upload a torrent,
we can stop bundling the jquery.form.js module. In addition, this
simplifies passing arguments in the headers s.t. rpc-server.c doesn't
have to look for the CSRF token as one of the multiparts.

This changes the upload POST behavior, so give it a new name (upload2).
The old function (upload) will be deprecated but kept until 2.90 so
that third-party web clients using the old POST semantics will have
time to update.

Bug #5290 <https://trac.transmissionbt.com/ticket/5290>

  • Property svn:keywords set to Date Rev Author Id
File size: 31.2 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 14005 2013-02-10 18:33:04Z 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
302/***
303****
304***/
305
306static void
307handle_rpc_from_json (struct evhttp_request * req,
308                      struct tr_rpc_server  * server,
309                      const char            * json,
310                      size_t                  json_len);
311
312static void
313handle_upload2 (struct evhttp_request * req,
314                struct tr_rpc_server  * server)
315{
316  if (req->type != EVHTTP_REQ_POST)
317    {
318      send_simple_response (req, 405, NULL);
319    }
320  else
321    {
322      const char * val;
323      tr_variant top;
324      tr_variant * args;
325      char * json;
326      int json_len;
327
328      tr_variantInitDict (&top, 2);
329      tr_variantDictAddStr (&top, TR_KEY_method, "torrent-add");
330      args = tr_variantDictAddDict (&top, TR_KEY_arguments, 3);
331
332      if ((val = evhttp_find_header (req->input_headers, "X-Transmission-Add-Paused")))
333        tr_variantDictAddBool (args, TR_KEY_paused, !tr_strcmp0(val,"true"));
334
335      if ((val = evhttp_find_header (req->input_headers, "X-Transmission-Add-Download-Dir")) && *val)
336        tr_variantDictAddStr (args, TR_KEY_download_dir, val);
337
338      if ((val = evhttp_find_header (req->input_headers, "X-Transmission-Add-URL")) && *val)
339        {
340          tr_variantDictAddStr (args, TR_KEY_filename, val);
341        }
342      else
343        {
344          int i;
345          int n;
346          bool have_source = false;
347          tr_ptrArray parts = TR_PTR_ARRAY_INIT;
348
349          extract_parts_from_multipart (req->input_headers, req->input_buffer, &parts);
350          n = tr_ptrArraySize (&parts);
351          for (i=0; !have_source && i<n; ++i)
352            {
353              tr_variant test;
354              const struct tr_mimepart * p = tr_ptrArrayNth (&parts, i);
355              if (!tr_variantFromBenc (&test, p->body, p->body_len))
356                {
357                  char * b64 = tr_base64_encode (p->body, p->body_len, NULL);
358                  tr_variantDictAddStr (args, TR_KEY_metainfo, b64);
359                  have_source = true;
360                  tr_free (b64);
361                  tr_variantFree (&test);
362                }
363            }
364          tr_ptrArrayDestruct (&parts, (PtrArrayForeachFunc)tr_mimepart_free);
365        }
366
367      json = tr_variantToStr (&top, TR_VARIANT_FMT_JSON, &json_len);
368      handle_rpc_from_json (req, server, json, json_len);
369      tr_free (json);
370      tr_variantFree (&top);
371    }
372}
373
374static const char*
375mimetype_guess (const char * path)
376{
377  unsigned int i;
378
379  const struct {
380    const char * suffix;
381    const char * mime_type;
382  } types[] = {
383    /* these are the ones we need for serving the web client's files... */
384    { "css",  "text/css"                  },
385    { "gif",  "image/gif"                 },
386    { "html", "text/html"                 },
387    { "ico",  "image/vnd.microsoft.icon"  },
388    { "js",   "application/javascript"    },
389    { "png",  "image/png"                 }
390  };
391  const char * dot = strrchr (path, '.');
392
393  for (i = 0; dot && i < TR_N_ELEMENTS (types); ++i)
394    if (!strcmp (dot + 1, types[i].suffix))
395      return types[i].mime_type;
396
397  return "application/octet-stream";
398}
399
400static void
401add_response (struct evhttp_request * req,
402              struct tr_rpc_server  * server,
403              struct evbuffer       * out,
404              struct evbuffer       * content)
405{
406#ifndef HAVE_ZLIB
407  evbuffer_add_buffer (out, content);
408#else
409  const char * key = "Accept-Encoding";
410  const char * encoding = evhttp_find_header (req->input_headers, key);
411  const int do_compress = encoding && strstr (encoding, "gzip");
412
413  if (!do_compress)
414    {
415      evbuffer_add_buffer (out, content);
416    }
417  else
418    {
419      int state;
420      struct evbuffer_iovec iovec[1];
421      void * content_ptr = evbuffer_pullup (content, -1);
422      const size_t content_len = evbuffer_get_length (content);
423
424      if (!server->isStreamInitialized)
425        {
426          int compressionLevel;
427
428          server->isStreamInitialized = true;
429          server->stream.zalloc = (alloc_func) Z_NULL;
430          server->stream.zfree = (free_func) Z_NULL;
431          server->stream.opaque = (voidpf) Z_NULL;
432
433          /* zlib's manual says: "Add 16 to windowBits to write a simple gzip header
434           * and trailer around the compressed data instead of a zlib wrapper." */
435#ifdef TR_LIGHTWEIGHT
436          compressionLevel = Z_DEFAULT_COMPRESSION;
437#else
438          compressionLevel = Z_BEST_COMPRESSION;
439#endif
440          deflateInit2 (&server->stream, compressionLevel, Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY);
441        }
442
443      server->stream.next_in = content_ptr;
444      server->stream.avail_in = content_len;
445
446      /* allocate space for the raw data and call deflate () just once --
447       * we won't use the deflated data if it's longer than the raw data,
448       * so it's okay to let deflate () run out of output buffer space */
449      evbuffer_reserve_space (out, content_len, iovec, 1);
450      server->stream.next_out = iovec[0].iov_base;
451      server->stream.avail_out = iovec[0].iov_len;
452      state = deflate (&server->stream, Z_FINISH);
453
454      if (state == Z_STREAM_END)
455        {
456          iovec[0].iov_len -= server->stream.avail_out;
457
458#if 0
459          fprintf (stderr, "compressed response is %.2f of original (raw==%zu bytes; compressed==%zu)\n",
460                   (double)evbuffer_get_length (out)/content_len,
461                   content_len, evbuffer_get_length (out));
462#endif
463          evhttp_add_header (req->output_headers,
464                             "Content-Encoding", "gzip");
465        }
466      else
467        {
468          memcpy (iovec[0].iov_base, content_ptr, content_len);
469          iovec[0].iov_len = content_len;
470        }
471
472      evbuffer_commit_space (out, iovec, 1);
473      deflateReset (&server->stream);
474    }
475#endif
476}
477
478static void
479add_time_header (struct evkeyvalq  * headers,
480                 const char        * key,
481                 time_t              value)
482{
483  /* According to RFC 2616 this must follow RFC 1123's date format,
484     so use gmtime instead of localtime... */
485  char buf[128];
486  struct tm tm = *gmtime (&value);
487  strftime (buf, sizeof (buf), "%a, %d %b %Y %H:%M:%S GMT", &tm);
488  evhttp_add_header (headers, key, buf);
489}
490
491static void
492evbuffer_ref_cleanup_tr_free (const void  * data UNUSED,
493                              size_t        datalen UNUSED,
494                              void        * extra)
495{
496  tr_free (extra);
497}
498
499static void
500serve_file (struct evhttp_request  * req,
501            struct tr_rpc_server   * server,
502            const char             * filename)
503{
504  if (req->type != EVHTTP_REQ_GET)
505    {
506      evhttp_add_header (req->output_headers, "Allow", "GET");
507      send_simple_response (req, 405, NULL);
508    }
509  else
510    {
511      void * file;
512      size_t file_len;
513      struct evbuffer * content;
514      const int error = errno;
515
516      errno = 0;
517      file_len = 0;
518      file = tr_loadFile (filename, &file_len);
519      content = evbuffer_new ();
520      evbuffer_add_reference (content, file, file_len, evbuffer_ref_cleanup_tr_free, file);
521
522      if (errno)
523        {
524          char * tmp = tr_strdup_printf ("%s (%s)", filename, tr_strerror (errno));
525          send_simple_response (req, HTTP_NOTFOUND, tmp);
526          tr_free (tmp);
527        }
528      else
529        {
530          struct evbuffer * out;
531          const time_t now = tr_time ();
532
533          errno = error;
534          out = evbuffer_new ();
535          evhttp_add_header (req->output_headers, "Content-Type", mimetype_guess (filename));
536          add_time_header (req->output_headers, "Date", now);
537          add_time_header (req->output_headers, "Expires", now+ (24*60*60));
538          add_response (req, server, out, content);
539          evhttp_send_reply (req, HTTP_OK, "OK", out);
540
541          evbuffer_free (out);
542        }
543
544      evbuffer_free (content);
545    }
546}
547
548static void
549handle_web_client (struct evhttp_request * req,
550                   struct tr_rpc_server *  server)
551{
552  const char * webClientDir = tr_getWebClientDir (server->session);
553
554  if (!webClientDir || !*webClientDir)
555    {
556        send_simple_response (req, HTTP_NOTFOUND,
557          "<p>Couldn't find Transmission's web interface files!</p>"
558          "<p>Users: to tell Transmission where to look, "
559          "set the TRANSMISSION_WEB_HOME environment "
560          "variable to the folder where the web interface's "
561          "index.html is located.</p>"
562          "<p>Package Builders: to set a custom default at compile time, "
563          "#define PACKAGE_DATA_DIR in libtransmission/platform.c "
564          "or tweak tr_getClutchDir () by hand.</p>");
565    }
566  else
567    {
568      char * pch;
569      char * subpath;
570
571      subpath = tr_strdup (req->uri + strlen (server->url) + 4);
572      if ((pch = strchr (subpath, '?')))
573        *pch = '\0';
574
575      if (strstr (subpath, ".."))
576        {
577          send_simple_response (req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>");
578        }
579      else
580        {
581          char * filename = tr_strdup_printf ("%s%s%s",
582                                              webClientDir,
583                                              TR_PATH_DELIMITER_STR,
584                                              subpath && *subpath ? subpath : "index.html");
585          serve_file (req, server, filename);
586          tr_free (filename);
587        }
588
589      tr_free (subpath);
590    }
591}
592
593struct rpc_response_data
594{
595  struct evhttp_request * req;
596  struct tr_rpc_server  * server;
597};
598
599static void
600rpc_response_func (tr_session      * session UNUSED,
601                   struct evbuffer * response,
602                   void            * user_data)
603{
604  struct rpc_response_data * data = user_data;
605  struct evbuffer * buf = evbuffer_new ();
606
607  add_response (data->req, data->server, buf, response);
608  evhttp_add_header (data->req->output_headers,
609                     "Content-Type", "application/json; charset=UTF-8");
610  evhttp_send_reply (data->req, HTTP_OK, "OK", buf);
611
612  evbuffer_free (buf);
613  tr_free (data);
614}
615
616static void
617handle_rpc_from_json (struct evhttp_request * req,
618                      struct tr_rpc_server  * server,
619                      const char            * json,
620                      size_t                  json_len)
621{
622  struct rpc_response_data * data;
623
624  data = tr_new0 (struct rpc_response_data, 1);
625  data->req = req;
626  data->server = server;
627
628  tr_rpc_request_exec_json (server->session, json, json_len, rpc_response_func, data);
629}
630
631static void
632handle_rpc (struct evhttp_request * req, struct tr_rpc_server  * server)
633{
634  const char * q;
635
636  if (req->type == EVHTTP_REQ_POST)
637    {
638      handle_rpc_from_json (req, server,
639                            (const char *) evbuffer_pullup (req->input_buffer, -1),
640                            evbuffer_get_length (req->input_buffer));
641    }
642  else if ((req->type == EVHTTP_REQ_GET) && ((q = strchr (req->uri, '?'))))
643    {
644      struct rpc_response_data * data = tr_new0 (struct rpc_response_data, 1);
645      data->req = req;
646      data->server = server;
647      tr_rpc_request_exec_uri (server->session, q+1, -1, rpc_response_func, data);
648    }
649  else
650    {
651      send_simple_response (req, 405, NULL);
652    }
653}
654
655static bool
656isAddressAllowed (const tr_rpc_server * server, const char * address)
657{
658  tr_list * l;
659
660  if (!server->isWhitelistEnabled)
661    return true;
662
663  for (l=server->whitelist; l!=NULL; l=l->next)
664    if (tr_wildmat (address, l->data))
665      return true;
666
667  return false;
668}
669
670static bool
671test_session_id (struct tr_rpc_server * server, struct evhttp_request * req)
672{
673  const char * ours = get_current_session_id (server);
674  const char * theirs = evhttp_find_header (req->input_headers, TR_RPC_SESSION_ID_HEADER);
675  const bool success =  theirs && !strcmp (theirs, ours);
676  return success;
677}
678
679static void
680handle_request (struct evhttp_request * req, void * arg)
681{
682  struct tr_rpc_server * server = arg;
683
684  if (req && req->evcon)
685    {
686      const char * auth;
687      char * user = NULL;
688      char * pass = NULL;
689
690      evhttp_add_header (req->output_headers, "Server", MY_REALM);
691
692      auth = evhttp_find_header (req->input_headers, "Authorization");
693      if (auth && !evutil_ascii_strncasecmp (auth, "basic ", 6))
694        {
695          int plen;
696          char * p = tr_base64_decode (auth + 6, 0, &plen);
697          if (p && plen && ((pass = strchr (p, ':'))))
698            {
699              user = p;
700              *pass++ = '\0';
701            }
702        }
703
704      if (!isAddressAllowed (server, req->remote_host))
705        {
706          send_simple_response (req, 403,
707            "<p>Unauthorized IP Address.</p>"
708            "<p>Either disable the IP address whitelist or add your address to it.</p>"
709            "<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>"
710            "<p>If you're still using ACLs, use a whitelist instead. See the transmission-daemon manpage for details.</p>");
711        }
712      else if (server->isPasswordEnabled
713                 && (!pass || !user || strcmp (server->username, user)
714                                     || !tr_ssha1_matches (server->password,
715                                                           pass)))
716        {
717          evhttp_add_header (req->output_headers,
718                             "WWW-Authenticate",
719                             "Basic realm=\"" MY_REALM "\"");
720          send_simple_response (req, 401, "Unauthorized User");
721        }
722      else if (strncmp (req->uri, server->url, strlen (server->url)))
723        {
724          char * location = tr_strdup_printf ("%sweb/", server->url);
725          evhttp_add_header (req->output_headers, "Location", location);
726          send_simple_response (req, HTTP_MOVEPERM, NULL);
727          tr_free (location);
728        }
729      else if (!strncmp (req->uri + strlen (server->url), "web/", 4))
730        {
731          handle_web_client (req, server);
732        }
733      else if (!strcmp (req->uri + strlen (server->url), "upload"))
734        {
735          handle_upload (req, server);
736        }
737#ifdef REQUIRE_SESSION_ID
738      else if (!test_session_id (server, req))
739        {
740          const char * sessionId = get_current_session_id (server);
741          char * tmp = tr_strdup_printf (
742            "<p>Your request had an invalid session-id header.</p>"
743            "<p>To fix this, follow these steps:"
744            "<ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it"
745            "<li> Add the updated header to your outgoing requests"
746            "<li> When you get this 409 error message, resend your request with the updated header"
747            "</ol></p>"
748            "<p>This requirement has been added to help prevent "
749            "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
750            "attacks.</p>"
751            "<p><code>%s: %s</code></p>",
752            TR_RPC_SESSION_ID_HEADER, sessionId);
753          evhttp_add_header (req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId);
754          send_simple_response (req, 409, tmp);
755          tr_free (tmp);
756        }
757#endif
758      else if (!strcmp (req->uri + strlen (server->url), "upload2"))
759        {
760          handle_upload2 (req, server);
761        }
762      else if (!strncmp (req->uri + strlen (server->url), "rpc", 3))
763        {
764          handle_rpc (req, server);
765        }
766      else
767        {
768          send_simple_response (req, HTTP_NOTFOUND, req->uri);
769        }
770
771      tr_free (user);
772    }
773}
774
775static void
776startServer (void * vserver)
777{
778  tr_rpc_server * server  = vserver;
779  tr_address addr;
780
781  if (!server->httpd)
782    {
783      addr.type = TR_AF_INET;
784      addr.addr.addr4 = server->bindAddress;
785      server->httpd = evhttp_new (server->session->event_base);
786      evhttp_bind_socket (server->httpd, tr_address_to_string (&addr), server->port);
787      evhttp_set_gencb (server->httpd, handle_request, server);
788    }
789}
790
791static void
792stopServer (tr_rpc_server * server)
793{
794  if (server->httpd)
795    {
796      evhttp_free (server->httpd);
797      server->httpd = NULL;
798    }
799}
800
801static void
802onEnabledChanged (void * vserver)
803{
804  tr_rpc_server * server = vserver;
805
806  if (!server->isEnabled)
807    stopServer (server);
808  else
809    startServer (server);
810}
811
812void
813tr_rpcSetEnabled (tr_rpc_server * server,
814                  bool            isEnabled)
815{
816  server->isEnabled = isEnabled;
817
818  tr_runInEventThread (server->session, onEnabledChanged, server);
819}
820
821bool
822tr_rpcIsEnabled (const tr_rpc_server * server)
823{
824  return server->isEnabled;
825}
826
827static void
828restartServer (void * vserver)
829{
830  tr_rpc_server * server = vserver;
831
832  if (server->isEnabled)
833    {
834      stopServer (server);
835      startServer (server);
836    }
837}
838
839void
840tr_rpcSetPort (tr_rpc_server * server,
841               tr_port         port)
842{
843  assert (server != NULL);
844
845  if (server->port != port)
846    {
847      server->port = port;
848
849      if (server->isEnabled)
850        tr_runInEventThread (server->session, restartServer, server);
851    }
852}
853
854tr_port
855tr_rpcGetPort (const tr_rpc_server * server)
856{
857  return server->port;
858}
859
860void
861tr_rpcSetUrl (tr_rpc_server * server, const char * url)
862{
863  char * tmp = server->url;
864  server->url = tr_strdup (url);
865  dbgmsg ("setting our URL to [%s]", server->url);
866  tr_free (tmp);
867}
868
869const char*
870tr_rpcGetUrl (const tr_rpc_server * server)
871{
872  return server->url ? server->url : "";
873}
874
875void
876tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
877{
878  void * tmp;
879  const char * walk;
880
881  /* keep the string */
882  tmp = server->whitelistStr;
883  server->whitelistStr = tr_strdup (whitelistStr);
884  tr_free (tmp);
885
886  /* clear out the old whitelist entries */
887  while ((tmp = tr_list_pop_front (&server->whitelist)))
888    tr_free (tmp);
889
890  /* build the new whitelist entries */
891  for (walk=whitelistStr; walk && *walk;)
892    {
893      const char * delimiters = " ,;";
894      const size_t len = strcspn (walk, delimiters);
895      char * token = tr_strndup (walk, len);
896      tr_list_append (&server->whitelist, token);
897      if (strcspn (token, "+-") < len)
898        tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token);
899      else
900        tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s", token);
901
902      if (walk[len]=='\0')
903        break;
904
905      walk += len + 1;
906    }
907}
908
909const char*
910tr_rpcGetWhitelist (const tr_rpc_server * server)
911{
912  return server->whitelistStr ? server->whitelistStr : "";
913}
914
915void
916tr_rpcSetWhitelistEnabled (tr_rpc_server  * server,
917                           bool             isEnabled)
918{
919  server->isWhitelistEnabled = isEnabled != 0;
920}
921
922bool
923tr_rpcGetWhitelistEnabled (const tr_rpc_server * server)
924{
925  return server->isWhitelistEnabled;
926}
927
928/****
929*****  PASSWORD
930****/
931
932void
933tr_rpcSetUsername (tr_rpc_server * server, const char * username)
934{
935  char * tmp = server->username;
936  server->username = tr_strdup (username);
937  dbgmsg ("setting our Username to [%s]", server->username);
938  tr_free (tmp);
939}
940
941const char*
942tr_rpcGetUsername (const tr_rpc_server * server)
943{
944  return server->username ? server->username : "";
945}
946
947void
948tr_rpcSetPassword (tr_rpc_server * server,
949                   const char *    password)
950{
951  tr_free (server->password);
952  if (*password != '{')
953    server->password = tr_ssha1 (password);
954  else
955    server->password = strdup (password);
956  dbgmsg ("setting our Password to [%s]", server->password);
957}
958
959const char*
960tr_rpcGetPassword (const tr_rpc_server * server)
961{
962  return server->password ? server->password : "" ;
963}
964
965void
966tr_rpcSetPasswordEnabled (tr_rpc_server * server, bool isEnabled)
967{
968  server->isPasswordEnabled = isEnabled;
969  dbgmsg ("setting 'password enabled' to %d", (int)isEnabled);
970}
971
972bool
973tr_rpcIsPasswordEnabled (const tr_rpc_server * server)
974{
975  return server->isPasswordEnabled;
976}
977
978const char *
979tr_rpcGetBindAddress (const tr_rpc_server * server)
980{
981  tr_address addr;
982  addr.type = TR_AF_INET;
983  addr.addr.addr4 = server->bindAddress;
984  return tr_address_to_string (&addr);
985}
986
987/****
988*****  LIFE CYCLE
989****/
990
991static void
992closeServer (void * vserver)
993{
994  void * tmp;
995  tr_rpc_server * s = vserver;
996
997  stopServer (s);
998  while ((tmp = tr_list_pop_front (&s->whitelist)))
999    tr_free (tmp);
1000#ifdef HAVE_ZLIB
1001  if (s->isStreamInitialized)
1002    deflateEnd (&s->stream);
1003#endif
1004  tr_free (s->url);
1005  tr_free (s->sessionId);
1006  tr_free (s->whitelistStr);
1007  tr_free (s->username);
1008  tr_free (s->password);
1009  tr_free (s);
1010}
1011
1012void
1013tr_rpcClose (tr_rpc_server ** ps)
1014{
1015  tr_runInEventThread ((*ps)->session, closeServer, *ps);
1016  *ps = NULL;
1017}
1018
1019static void
1020missing_settings_key (const tr_quark q)
1021{
1022  const char * str = tr_quark_get_string (q, NULL);
1023  tr_logAddNamedError (MY_NAME, _("Couldn't find settings key \"%s\""), str);
1024} 
1025
1026tr_rpc_server *
1027tr_rpcInit (tr_session  * session, tr_variant * settings)
1028{
1029  tr_rpc_server * s;
1030  bool boolVal;
1031  int64_t i;
1032  const char * str;
1033  tr_quark key;
1034  tr_address address;
1035
1036  s = tr_new0 (tr_rpc_server, 1);
1037  s->session = session;
1038
1039  key = TR_KEY_rpc_enabled;
1040  if (!tr_variantDictFindBool (settings, key, &boolVal))
1041    missing_settings_key (key);
1042  else
1043    s->isEnabled = boolVal;
1044
1045  key = TR_KEY_rpc_port;
1046  if (!tr_variantDictFindInt (settings, key, &i))
1047    missing_settings_key (key);
1048  else
1049    s->port = i;
1050
1051  key = TR_KEY_rpc_url;
1052  if (!tr_variantDictFindStr (settings, key, &str, NULL))
1053    missing_settings_key (key);
1054  else
1055    s->url = tr_strdup (str);
1056
1057  key = TR_KEY_rpc_whitelist_enabled;
1058  if (!tr_variantDictFindBool (settings, key, &boolVal))
1059    missing_settings_key (key);
1060  else
1061    tr_rpcSetWhitelistEnabled (s, boolVal);
1062
1063  key = TR_KEY_rpc_authentication_required;
1064  if (!tr_variantDictFindBool (settings, key, &boolVal))
1065    missing_settings_key (key);
1066  else
1067    tr_rpcSetPasswordEnabled (s, boolVal);
1068
1069  key = TR_KEY_rpc_whitelist;
1070  if (!tr_variantDictFindStr (settings, key, &str, NULL) && str)
1071    missing_settings_key (key);
1072  else
1073    tr_rpcSetWhitelist (s, str);
1074
1075  key = TR_KEY_rpc_username;
1076  if (!tr_variantDictFindStr (settings, key, &str, NULL))
1077    missing_settings_key (key);
1078  else
1079    tr_rpcSetUsername (s, str);
1080
1081  key = TR_KEY_rpc_password;
1082  if (!tr_variantDictFindStr (settings, key, &str, NULL))
1083    missing_settings_key (key);
1084  else
1085    tr_rpcSetPassword (s, str);
1086
1087  key = TR_KEY_rpc_bind_address;
1088  if (!tr_variantDictFindStr (settings, key, &str, NULL))
1089    {
1090      missing_settings_key (key);
1091      address = tr_inaddr_any;
1092    }
1093  else if (!tr_address_from_string (&address, str))
1094    {
1095      tr_logAddNamedError (MY_NAME, _("%s is not a valid address"), str);
1096      address = tr_inaddr_any;
1097    }
1098  else if (address.type != TR_AF_INET)
1099    {
1100      tr_logAddNamedError (MY_NAME, _("%s is not an IPv4 address. RPC listeners must be IPv4"), str);
1101      address = tr_inaddr_any;
1102    }
1103  s->bindAddress = address.addr.addr4;
1104
1105  if (s->isEnabled)
1106    {
1107      tr_logAddNamedInfo (MY_NAME, _("Serving RPC and Web requests on port 127.0.0.1:%d%s"), (int) s->port, s->url);
1108      tr_runInEventThread (session, startServer, s);
1109
1110      if (s->isWhitelistEnabled)
1111        tr_logAddNamedInfo (MY_NAME, "%s", _("Whitelist enabled"));
1112
1113      if (s->isPasswordEnabled)
1114        tr_logAddNamedInfo (MY_NAME, "%s", _("Password required"));
1115    }
1116
1117  return s;
1118}
Note: See TracBrowser for help on using the repository browser.