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

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

(trunk, libT) #5165: fix r13625 oops

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