2008-05-18 16:44:30 +00:00
|
|
|
/*
|
2014-01-19 01:09:44 +00:00
|
|
|
* This file Copyright (C) 2008-2014 Mnemosyne LLC
|
2008-05-18 16:44:30 +00:00
|
|
|
*
|
2014-01-21 03:10:30 +00:00
|
|
|
* It may be used under the GNU GPL versions 2 or 3
|
2014-01-19 01:09:44 +00:00
|
|
|
* or any future license endorsed by Mnemosyne LLC.
|
2008-05-18 16:44:30 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2021-09-19 20:41:35 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <cerrno>
|
|
|
|
#include <cstring> /* memcpy */
|
2021-09-27 15:03:54 +00:00
|
|
|
#include <list>
|
|
|
|
#include <string>
|
2021-11-09 03:30:03 +00:00
|
|
|
#include <vector>
|
2008-09-25 05:03:39 +00:00
|
|
|
|
2014-07-13 21:50:56 +00:00
|
|
|
#include <zlib.h>
|
2008-10-01 15:53:56 +00:00
|
|
|
|
2010-12-24 08:58:41 +00:00
|
|
|
#include <event2/buffer.h>
|
|
|
|
#include <event2/event.h>
|
|
|
|
#include <event2/http.h>
|
|
|
|
#include <event2/http_struct.h> /* TODO: eventually remove this */
|
2008-05-18 16:44:30 +00:00
|
|
|
|
|
|
|
#include "transmission.h"
|
2021-11-09 03:30:03 +00:00
|
|
|
|
2017-04-21 07:40:57 +00:00
|
|
|
#include "crypto-utils.h" /* tr_rand_buffer() */
|
2021-11-09 03:30:03 +00:00
|
|
|
#include "crypto.h" /* tr_ssha1_matches() */
|
2015-04-11 14:54:01 +00:00
|
|
|
#include "error.h"
|
2010-12-20 02:07:51 +00:00
|
|
|
#include "fdlimit.h"
|
2013-01-25 23:34:20 +00:00
|
|
|
#include "log.h"
|
2009-07-01 14:58:57 +00:00
|
|
|
#include "net.h"
|
2017-04-21 07:40:57 +00:00
|
|
|
#include "platform.h" /* tr_getWebClientDir() */
|
2008-05-18 16:44:30 +00:00
|
|
|
#include "rpc-server.h"
|
2021-11-09 03:30:03 +00:00
|
|
|
#include "rpcimpl.h"
|
2016-09-05 19:06:26 +00:00
|
|
|
#include "session-id.h"
|
2021-11-09 03:30:03 +00:00
|
|
|
#include "session.h"
|
2017-06-08 07:24:12 +00:00
|
|
|
#include "tr-assert.h"
|
2008-09-29 04:26:52 +00:00
|
|
|
#include "trevent.h"
|
2008-05-18 16:44:30 +00:00
|
|
|
#include "utils.h"
|
2012-12-14 04:34:42 +00:00
|
|
|
#include "variant.h"
|
2021-11-09 03:30:03 +00:00
|
|
|
#include "web-utils.h"
|
2008-09-25 18:48:09 +00:00
|
|
|
#include "web.h"
|
2008-05-18 16:44:30 +00:00
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
using namespace std::literals;
|
|
|
|
|
2009-05-08 14:56:11 +00:00
|
|
|
/* session-id is used to make cross-site request forgery attacks difficult.
|
|
|
|
* Don't disable this feature unless you really know what you're doing!
|
|
|
|
* http://en.wikipedia.org/wiki/Cross-site_request_forgery
|
|
|
|
* http://shiflett.org/articles/cross-site-request-forgeries
|
|
|
|
* http://www.webappsec.org/lists/websecurity/archive/2008-04/msg00037.html */
|
|
|
|
#define REQUIRE_SESSION_ID
|
|
|
|
|
2008-06-02 19:57:16 +00:00
|
|
|
#define MY_NAME "RPC Server"
|
2008-08-22 23:04:16 +00:00
|
|
|
#define MY_REALM "Transmission"
|
2008-09-25 05:03:39 +00:00
|
|
|
|
2017-05-22 20:12:57 +00:00
|
|
|
#define dbgmsg(...) tr_logAddDeepNamed(MY_NAME, __VA_ARGS__)
|
2008-06-05 04:02:46 +00:00
|
|
|
|
2009-05-14 17:21:07 +00:00
|
|
|
/***
|
|
|
|
****
|
|
|
|
***/
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
static char const* get_current_session_id(tr_rpc_server* server)
|
2009-05-14 17:21:07 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
return tr_session_id_get_current(server->session->session_id);
|
2009-05-14 17:21:07 +00:00
|
|
|
}
|
|
|
|
|
2008-09-26 15:40:24 +00:00
|
|
|
/**
|
|
|
|
***
|
|
|
|
**/
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
static void send_simple_response(struct evhttp_request* req, int code, char const* text)
|
2008-09-26 15:40:24 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* code_text = tr_webGetResponseStr(code);
|
2017-04-19 12:04:45 +00:00
|
|
|
struct evbuffer* body = evbuffer_new();
|
|
|
|
|
|
|
|
evbuffer_add_printf(body, "<h1>%d: %s</h1>", code, code_text);
|
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (text != nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
evbuffer_add_printf(body, "%s", text);
|
|
|
|
}
|
2008-09-26 15:40:24 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_send_reply(req, code, code_text, body);
|
2008-12-30 20:32:00 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
evbuffer_free(body);
|
2008-09-26 15:40:24 +00:00
|
|
|
}
|
|
|
|
|
2009-05-14 17:21:07 +00:00
|
|
|
struct tr_mimepart
|
|
|
|
{
|
2021-09-30 02:14:31 +00:00
|
|
|
std::string headers;
|
|
|
|
std::string body;
|
2009-05-14 17:21:07 +00:00
|
|
|
};
|
|
|
|
|
2021-09-30 02:14:31 +00:00
|
|
|
static auto extract_parts_from_multipart(struct evkeyvalq const* headers, struct evbuffer* body)
|
2009-05-14 17:21:07 +00:00
|
|
|
{
|
2021-09-30 02:14:31 +00:00
|
|
|
auto ret = std::vector<tr_mimepart>{};
|
2009-05-14 17:21:07 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* content_type = evhttp_find_header(headers, "Content-Type");
|
|
|
|
char const* in = (char const*)evbuffer_pullup(body, -1);
|
2017-04-19 12:04:45 +00:00
|
|
|
size_t inlen = evbuffer_get_length(body);
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* boundary_key = "boundary=";
|
2021-09-15 00:18:09 +00:00
|
|
|
char const* boundary_key_begin = content_type != nullptr ? strstr(content_type, boundary_key) : nullptr;
|
|
|
|
char const* boundary_val = boundary_key_begin != nullptr ? boundary_key_begin + strlen(boundary_key) : "arglebargle";
|
2017-04-19 12:04:45 +00:00
|
|
|
char* boundary = tr_strdup_printf("--%s", boundary_val);
|
2017-04-20 16:02:19 +00:00
|
|
|
size_t const boundary_len = strlen(boundary);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* delim = tr_memmem(in, inlen, boundary, boundary_len);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
while (delim != nullptr)
|
2009-05-14 17:21:07 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* part = delim + boundary_len;
|
2009-05-14 17:21:07 +00:00
|
|
|
|
2017-04-30 16:30:03 +00:00
|
|
|
inlen -= part - in;
|
2017-04-19 12:04:45 +00:00
|
|
|
in = part;
|
2009-05-14 17:21:07 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
delim = tr_memmem(in, inlen, boundary, boundary_len);
|
2021-10-22 13:51:36 +00:00
|
|
|
size_t part_len = delim != nullptr ? (size_t)(delim - part) : inlen;
|
2009-05-14 17:21:07 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if (part_len != 0)
|
2009-05-14 17:21:07 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* rnrn = tr_memmem(part, part_len, "\r\n\r\n", 4);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (rnrn != nullptr)
|
2009-05-14 17:21:07 +00:00
|
|
|
{
|
2021-09-30 02:14:31 +00:00
|
|
|
auto tmp = tr_mimepart{};
|
|
|
|
tmp.headers.assign(part, rnrn - part);
|
|
|
|
tmp.body.assign(rnrn + 4, (part + part_len) - (rnrn + 4));
|
|
|
|
ret.push_back(tmp);
|
2009-05-14 17:21:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_free(boundary);
|
2021-09-30 02:14:31 +00:00
|
|
|
|
|
|
|
return ret;
|
2009-05-14 17:21:07 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
static void handle_upload(struct evhttp_request* req, tr_rpc_server* server)
|
2008-06-30 21:11:53 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (req->type != EVHTTP_REQ_POST)
|
2008-09-25 05:03:39 +00:00
|
|
|
{
|
2021-09-15 00:18:09 +00:00
|
|
|
send_simple_response(req, 405, nullptr);
|
2008-09-25 05:03:39 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2008-09-25 05:03:39 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
bool hasSessionId = false;
|
2013-02-02 16:22:21 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* query = strchr(req->uri, '?');
|
2021-09-15 00:18:09 +00:00
|
|
|
bool const paused = query != nullptr && strstr(query + 1, "paused=true") != nullptr;
|
2013-02-02 16:22:21 +00:00
|
|
|
|
2021-09-30 02:14:31 +00:00
|
|
|
auto const parts = extract_parts_from_multipart(req->input_headers, req->input_buffer);
|
2013-02-02 16:22:21 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* first look for the session id */
|
2021-09-30 02:14:31 +00:00
|
|
|
for (auto const& p : parts)
|
2013-02-02 16:22:21 +00:00
|
|
|
{
|
2021-09-30 02:14:31 +00:00
|
|
|
if (tr_strcasestr(p.headers.c_str(), TR_RPC_SESSION_ID_HEADER) != nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2017-05-13 22:38:31 +00:00
|
|
|
char const* ours = get_current_session_id(server);
|
|
|
|
size_t const ourlen = strlen(ours);
|
2021-09-30 02:14:31 +00:00
|
|
|
hasSessionId = ourlen <= std::size(p.body) && memcmp(p.body.c_str(), ours, ourlen) == 0;
|
2017-04-19 12:04:45 +00:00
|
|
|
break;
|
|
|
|
}
|
2009-05-14 17:21:07 +00:00
|
|
|
}
|
2013-02-02 16:22:21 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!hasSessionId)
|
2008-09-26 04:41:13 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
int code = 409;
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* codetext = tr_webGetResponseStr(code);
|
2017-04-19 12:04:45 +00:00
|
|
|
struct evbuffer* body = evbuffer_new();
|
|
|
|
evbuffer_add_printf(body, "%s", "{ \"success\": false, \"msg\": \"Bad Session-Id\" }");
|
|
|
|
evhttp_send_reply(req, code, codetext, body);
|
|
|
|
evbuffer_free(body);
|
2009-05-14 17:21:07 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2009-05-14 17:21:07 +00:00
|
|
|
{
|
2021-09-30 02:14:31 +00:00
|
|
|
for (auto const& p : parts)
|
2010-08-21 12:50:13 +00:00
|
|
|
{
|
2021-09-30 02:14:31 +00:00
|
|
|
auto const& body = p.body;
|
2021-10-22 13:51:36 +00:00
|
|
|
auto body_len = std::size(body);
|
2017-04-19 12:04:45 +00:00
|
|
|
if (body_len >= 2 && memcmp(&body[body_len - 2], "\r\n", 2) == 0)
|
|
|
|
{
|
|
|
|
body_len -= 2;
|
|
|
|
}
|
|
|
|
|
2021-10-22 13:51:36 +00:00
|
|
|
auto top = tr_variant{};
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_variantInitDict(&top, 2);
|
|
|
|
tr_variantDictAddStr(&top, TR_KEY_method, "torrent-add");
|
2021-10-22 13:51:36 +00:00
|
|
|
auto* const args = tr_variantDictAddDict(&top, TR_KEY_arguments, 2);
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_variantDictAddBool(args, TR_KEY_paused, paused);
|
|
|
|
|
2021-10-22 13:51:36 +00:00
|
|
|
auto test = tr_variant{};
|
|
|
|
auto have_source = bool{ false };
|
2021-11-15 07:21:57 +00:00
|
|
|
if (tr_urlIsValid(body))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2021-11-15 07:21:57 +00:00
|
|
|
tr_variantDictAddStrView(args, TR_KEY_filename, body);
|
2017-04-19 12:04:45 +00:00
|
|
|
have_source = true;
|
|
|
|
}
|
2021-11-15 07:21:57 +00:00
|
|
|
else if (tr_variantFromBenc(&test, body) == 0)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2021-09-30 02:14:31 +00:00
|
|
|
auto* b64 = static_cast<char*>(tr_base64_encode(body.c_str(), body_len, nullptr));
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_variantDictAddStr(args, TR_KEY_metainfo, b64);
|
|
|
|
tr_free(b64);
|
|
|
|
have_source = true;
|
|
|
|
}
|
2010-08-21 12:50:13 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (have_source)
|
|
|
|
{
|
2021-09-15 00:18:09 +00:00
|
|
|
tr_rpc_request_exec_json(server->session, &top, nullptr, nullptr);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2010-08-21 12:50:13 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_variantFree(&top);
|
|
|
|
}
|
2008-09-26 04:41:13 +00:00
|
|
|
}
|
2008-06-30 21:11:53 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* send "success" response */
|
|
|
|
{
|
|
|
|
int code = HTTP_OK;
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* codetext = tr_webGetResponseStr(code);
|
2017-04-19 12:04:45 +00:00
|
|
|
struct evbuffer* body = evbuffer_new();
|
|
|
|
evbuffer_add_printf(body, "%s", "{ \"success\": true, \"msg\": \"Torrent Added\" }");
|
|
|
|
evhttp_send_reply(req, code, codetext, body);
|
|
|
|
evbuffer_free(body);
|
|
|
|
}
|
2008-07-16 17:47:20 +00:00
|
|
|
}
|
2008-06-30 21:11:53 +00:00
|
|
|
}
|
|
|
|
|
2013-02-10 18:33:04 +00:00
|
|
|
/***
|
|
|
|
****
|
|
|
|
***/
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
static char const* mimetype_guess(char const* path)
|
2008-08-06 00:24:05 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
struct
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* suffix;
|
|
|
|
char const* mime_type;
|
2021-08-15 09:41:48 +00:00
|
|
|
} const types[] = {
|
2017-04-19 12:04:45 +00:00
|
|
|
/* these are the ones we need for serving the web client's files... */
|
|
|
|
{ "css", "text/css" },
|
|
|
|
{ "gif", "image/gif" },
|
|
|
|
{ "html", "text/html" },
|
|
|
|
{ "ico", "image/vnd.microsoft.icon" },
|
|
|
|
{ "js", "application/javascript" },
|
2020-10-24 01:04:25 +00:00
|
|
|
{ "png", "image/png" },
|
2021-08-15 09:41:48 +00:00
|
|
|
{ "svg", "image/svg+xml" },
|
2017-04-19 12:04:45 +00:00
|
|
|
};
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* dot = strrchr(path, '.');
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
for (unsigned int i = 0; dot != nullptr && i < TR_N_ELEMENTS(types); ++i)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
if (strcmp(dot + 1, types[i].suffix) == 0)
|
|
|
|
{
|
|
|
|
return types[i].mime_type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "application/octet-stream";
|
2008-08-06 00:24:05 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
static void add_response(struct evhttp_request* req, tr_rpc_server* server, struct evbuffer* out, struct evbuffer* content)
|
2008-10-01 15:53:56 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* key = "Accept-Encoding";
|
|
|
|
char const* encoding = evhttp_find_header(req->input_headers, key);
|
2021-09-15 00:18:09 +00:00
|
|
|
bool const do_compress = encoding != nullptr && strstr(encoding, "gzip") != nullptr;
|
2008-10-03 17:38:14 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!do_compress)
|
2008-10-08 13:33:19 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
evbuffer_add_buffer(out, content);
|
2008-10-01 20:23:57 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2008-10-08 13:33:19 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct evbuffer_iovec iovec[1];
|
|
|
|
void* content_ptr = evbuffer_pullup(content, -1);
|
2017-04-20 16:02:19 +00:00
|
|
|
size_t const content_len = evbuffer_get_length(content);
|
2009-06-02 18:21:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!server->isStreamInitialized)
|
2009-07-02 20:20:00 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
server->isStreamInitialized = true;
|
|
|
|
server->stream.zalloc = (alloc_func)Z_NULL;
|
|
|
|
server->stream.zfree = (free_func)Z_NULL;
|
|
|
|
server->stream.opaque = (voidpf)Z_NULL;
|
2010-08-04 20:57:08 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
/* zlib's manual says: "Add 16 to windowBits to write a simple gzip header
|
|
|
|
* and trailer around the compressed data instead of a zlib wrapper." */
|
2011-01-27 03:53:02 +00:00
|
|
|
#ifdef TR_LIGHTWEIGHT
|
2021-10-22 13:51:36 +00:00
|
|
|
int const compressionLevel = Z_DEFAULT_COMPRESSION;
|
2010-08-15 23:45:58 +00:00
|
|
|
#else
|
2021-10-22 13:51:36 +00:00
|
|
|
int const compressionLevel = Z_BEST_COMPRESSION;
|
2010-08-15 23:45:58 +00:00
|
|
|
#endif
|
2021-11-17 21:56:50 +00:00
|
|
|
// "windowBits can also be greater than 15 for optional gzip encoding.
|
|
|
|
// Add 16 to windowBits to write a simple gzip header and trailer
|
|
|
|
// around the compressed data instead of a zlib wrapper."
|
|
|
|
if (Z_OK != deflateInit2(&server->stream, compressionLevel, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY))
|
|
|
|
{
|
|
|
|
tr_logAddNamedDbg(MY_NAME, "deflateInit2 failed: %s", server->stream.msg);
|
|
|
|
}
|
2009-07-02 20:20:00 +00:00
|
|
|
}
|
|
|
|
|
2021-09-12 17:41:49 +00:00
|
|
|
server->stream.next_in = static_cast<Bytef*>(content_ptr);
|
2017-04-19 12:04:45 +00:00
|
|
|
server->stream.avail_in = content_len;
|
2008-10-01 20:23:57 +00:00
|
|
|
|
2017-04-21 07:40:57 +00:00
|
|
|
/* allocate space for the raw data and call deflate() just once --
|
2017-04-19 12:04:45 +00:00
|
|
|
* we won't use the deflated data if it's longer than the raw data,
|
2017-04-21 07:40:57 +00:00
|
|
|
* so it's okay to let deflate() run out of output buffer space */
|
2017-04-19 12:04:45 +00:00
|
|
|
evbuffer_reserve_space(out, content_len, iovec, 1);
|
2021-09-12 17:41:49 +00:00
|
|
|
server->stream.next_out = static_cast<Bytef*>(iovec[0].iov_base);
|
2017-04-19 12:04:45 +00:00
|
|
|
server->stream.avail_out = iovec[0].iov_len;
|
2021-10-22 13:51:36 +00:00
|
|
|
auto const state = deflate(&server->stream, Z_FINISH);
|
2008-10-08 13:39:44 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (state == Z_STREAM_END)
|
2008-10-08 13:39:44 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
iovec[0].iov_len -= server->stream.avail_out;
|
2008-11-01 22:09:16 +00:00
|
|
|
|
2008-11-19 16:12:00 +00:00
|
|
|
#if 0
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
fprintf(stderr, "compressed response is %.2f of original (raw==%zu bytes; compressed==%zu)\n",
|
|
|
|
(double)evbuffer_get_length(out) / content_len, content_len, evbuffer_get_length(out));
|
|
|
|
|
2008-11-19 16:12:00 +00:00
|
|
|
#endif
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-30 16:30:03 +00:00
|
|
|
evhttp_add_header(req->output_headers, "Content-Encoding", "gzip");
|
2008-10-08 13:33:19 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2008-10-08 13:39:44 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
memcpy(iovec[0].iov_base, content_ptr, content_len);
|
|
|
|
iovec[0].iov_len = content_len;
|
2008-10-08 13:39:44 +00:00
|
|
|
}
|
2008-10-08 13:33:19 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
evbuffer_commit_space(out, iovec, 1);
|
|
|
|
deflateReset(&server->stream);
|
2008-10-08 13:33:19 +00:00
|
|
|
}
|
2008-10-01 15:53:56 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
static void add_time_header(struct evkeyvalq* headers, char const* key, time_t value)
|
2009-05-08 16:41:32 +00:00
|
|
|
{
|
2020-09-11 21:07:45 +00:00
|
|
|
char buf[128];
|
|
|
|
struct tm tm;
|
2017-04-19 12:04:45 +00:00
|
|
|
/* According to RFC 2616 this must follow RFC 1123's date format,
|
|
|
|
so use gmtime instead of localtime... */
|
2020-09-11 21:07:45 +00:00
|
|
|
tr_gmtime_r(&value, &tm);
|
2017-04-19 12:04:45 +00:00
|
|
|
strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm);
|
|
|
|
evhttp_add_header(headers, key, buf);
|
2009-05-08 16:41:32 +00:00
|
|
|
}
|
|
|
|
|
2021-10-24 16:41:54 +00:00
|
|
|
static void evbuffer_ref_cleanup_tr_free(void const* /*data*/, size_t /*datalen*/, void* extra)
|
2011-03-16 17:42:32 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_free(extra);
|
2011-03-16 17:42:32 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
static void serve_file(struct evhttp_request* req, tr_rpc_server* server, char const* filename)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (req->type != EVHTTP_REQ_GET)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_add_header(req->output_headers, "Allow", "GET");
|
2021-09-15 00:18:09 +00:00
|
|
|
send_simple_response(req, 405, nullptr);
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2021-10-22 13:51:36 +00:00
|
|
|
auto file_len = size_t{};
|
2021-09-15 00:18:09 +00:00
|
|
|
tr_error* error = nullptr;
|
2021-10-22 13:51:36 +00:00
|
|
|
void* const file = tr_loadFile(filename, &file_len, &error);
|
2013-02-02 16:22:21 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (file == nullptr)
|
2009-05-08 20:52:12 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
char* tmp = tr_strdup_printf("%s (%s)", filename, error->message);
|
|
|
|
send_simple_response(req, HTTP_NOTFOUND, tmp);
|
|
|
|
tr_free(tmp);
|
|
|
|
tr_error_free(error);
|
2009-05-08 20:52:12 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2008-09-25 05:03:39 +00:00
|
|
|
{
|
2021-10-22 13:51:36 +00:00
|
|
|
auto const now = tr_time();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-10-22 13:51:36 +00:00
|
|
|
auto* const content = evbuffer_new();
|
2017-04-19 12:04:45 +00:00
|
|
|
evbuffer_add_reference(content, file, file_len, evbuffer_ref_cleanup_tr_free, file);
|
|
|
|
|
2021-10-22 13:51:36 +00:00
|
|
|
auto* const out = evbuffer_new();
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_add_header(req->output_headers, "Content-Type", mimetype_guess(filename));
|
|
|
|
add_time_header(req->output_headers, "Date", now);
|
|
|
|
add_time_header(req->output_headers, "Expires", now + (24 * 60 * 60));
|
|
|
|
add_response(req, server, out, content);
|
|
|
|
evhttp_send_reply(req, HTTP_OK, "OK", out);
|
|
|
|
|
|
|
|
evbuffer_free(out);
|
|
|
|
evbuffer_free(content);
|
2008-09-25 05:03:39 +00:00
|
|
|
}
|
2008-07-16 17:47:20 +00:00
|
|
|
}
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
static void handle_web_client(struct evhttp_request* req, tr_rpc_server* server)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* webClientDir = tr_getWebClientDir(server->session);
|
2008-09-25 05:03:39 +00:00
|
|
|
|
2019-07-13 08:52:44 +00:00
|
|
|
if (tr_str_is_empty(webClientDir))
|
2008-10-07 01:25:29 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
send_simple_response(
|
|
|
|
req,
|
|
|
|
HTTP_NOTFOUND,
|
2017-04-19 12:04:45 +00:00
|
|
|
"<p>Couldn't find Transmission's web interface files!</p>"
|
|
|
|
"<p>Users: to tell Transmission where to look, "
|
|
|
|
"set the TRANSMISSION_WEB_HOME environment "
|
|
|
|
"variable to the folder where the web interface's "
|
|
|
|
"index.html is located.</p>"
|
|
|
|
"<p>Package Builders: to set a custom default at compile time, "
|
|
|
|
"#define PACKAGE_DATA_DIR in libtransmission/platform.c "
|
2017-04-21 07:40:57 +00:00
|
|
|
"or tweak tr_getClutchDir() by hand.</p>");
|
2008-10-07 01:25:29 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2008-10-07 01:25:29 +00:00
|
|
|
{
|
2021-10-22 13:51:36 +00:00
|
|
|
// TODO: string_view
|
2021-11-14 21:14:37 +00:00
|
|
|
char* const subpath = tr_strdup(req->uri + std::size(server->url) + 4);
|
2021-10-22 13:51:36 +00:00
|
|
|
char* pch = strchr(subpath, '?');
|
|
|
|
if (pch != nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
*pch = '\0';
|
|
|
|
}
|
2008-09-26 15:40:24 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (strstr(subpath, "..") != nullptr)
|
2009-04-10 15:09:31 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
send_simple_response(req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>");
|
2009-04-10 15:09:31 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2009-04-10 15:09:31 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
char* filename = tr_strdup_printf(
|
|
|
|
"%s%s%s",
|
|
|
|
webClientDir,
|
|
|
|
TR_PATH_DELIMITER_STR,
|
2019-07-13 08:52:44 +00:00
|
|
|
tr_str_is_empty(subpath) ? "index.html" : subpath);
|
2017-04-19 12:04:45 +00:00
|
|
|
serve_file(req, server, filename);
|
|
|
|
tr_free(filename);
|
2009-04-10 15:09:31 +00:00
|
|
|
}
|
2008-05-18 16:44:30 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_free(subpath);
|
2008-10-07 01:25:29 +00:00
|
|
|
}
|
2008-06-05 16:23:03 +00:00
|
|
|
}
|
|
|
|
|
2009-01-18 15:24:26 +00:00
|
|
|
struct rpc_response_data
|
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
struct evhttp_request* req;
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpc_server* server;
|
2009-01-18 15:24:26 +00:00
|
|
|
};
|
|
|
|
|
2021-10-24 16:41:54 +00:00
|
|
|
static void rpc_response_func(tr_session* /*session*/, tr_variant* response, void* user_data)
|
2009-01-18 15:24:26 +00:00
|
|
|
{
|
2021-09-12 17:41:49 +00:00
|
|
|
auto* data = static_cast<struct rpc_response_data*>(user_data);
|
2017-04-19 12:04:45 +00:00
|
|
|
struct evbuffer* response_buf = tr_variantToBuf(response, TR_VARIANT_FMT_JSON_LEAN);
|
|
|
|
struct evbuffer* buf = evbuffer_new();
|
|
|
|
|
|
|
|
add_response(data->req, data->server, buf, response_buf);
|
|
|
|
evhttp_add_header(data->req->output_headers, "Content-Type", "application/json; charset=UTF-8");
|
|
|
|
evhttp_send_reply(data->req, HTTP_OK, "OK", buf);
|
|
|
|
|
|
|
|
evbuffer_free(buf);
|
|
|
|
evbuffer_free(response_buf);
|
|
|
|
tr_free(data);
|
2009-01-18 15:24:26 +00:00
|
|
|
}
|
|
|
|
|
2021-11-15 07:21:57 +00:00
|
|
|
static void handle_rpc_from_json(struct evhttp_request* req, tr_rpc_server* server, std::string_view json)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2021-10-22 13:51:36 +00:00
|
|
|
auto top = tr_variant{};
|
2021-11-15 07:21:57 +00:00
|
|
|
auto const have_content = tr_variantFromJson(&top, json) == 0;
|
2008-06-05 04:02:46 +00:00
|
|
|
|
2021-10-22 13:51:36 +00:00
|
|
|
auto* const data = tr_new0(struct rpc_response_data, 1);
|
2017-04-19 12:04:45 +00:00
|
|
|
data->req = req;
|
|
|
|
data->server = server;
|
2009-08-12 14:40:32 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
tr_rpc_request_exec_json(server->session, have_content ? &top : nullptr, rpc_response_func, data);
|
2015-07-13 00:32:48 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (have_content)
|
|
|
|
{
|
|
|
|
tr_variantFree(&top);
|
|
|
|
}
|
2013-02-10 18:33:04 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
static void handle_rpc(struct evhttp_request* req, tr_rpc_server* server)
|
2013-02-10 18:33:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (req->type == EVHTTP_REQ_POST)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2021-11-15 07:21:57 +00:00
|
|
|
auto json = std::string_view{ reinterpret_cast<char const*>(evbuffer_pullup(req->input_buffer, -1)),
|
|
|
|
evbuffer_get_length(req->input_buffer) };
|
|
|
|
handle_rpc_from_json(req, server, json);
|
2019-03-17 06:09:08 +00:00
|
|
|
return;
|
2008-09-25 05:03:39 +00:00
|
|
|
}
|
2019-03-17 06:09:08 +00:00
|
|
|
|
|
|
|
if (req->type == EVHTTP_REQ_GET)
|
2013-02-10 18:33:04 +00:00
|
|
|
{
|
2019-03-17 06:09:08 +00:00
|
|
|
char const* q = strchr(req->uri, '?');
|
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (q != nullptr)
|
2019-03-17 06:09:08 +00:00
|
|
|
{
|
|
|
|
struct rpc_response_data* data = tr_new0(struct rpc_response_data, 1);
|
|
|
|
data->req = req;
|
|
|
|
data->server = server;
|
|
|
|
tr_rpc_request_exec_uri(server->session, q + 1, TR_BAD_SIZE, rpc_response_func, data);
|
|
|
|
return;
|
|
|
|
}
|
2008-12-30 22:07:39 +00:00
|
|
|
}
|
2019-03-17 06:09:08 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
send_simple_response(req, 405, nullptr);
|
2008-09-25 05:03:39 +00:00
|
|
|
}
|
2008-07-17 14:45:31 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
static bool isAddressAllowed(tr_rpc_server const* server, char const* address)
|
2008-09-26 15:40:24 +00:00
|
|
|
{
|
2021-09-27 15:03:54 +00:00
|
|
|
auto const& src = server->whitelist;
|
2008-09-26 15:40:24 +00:00
|
|
|
|
2021-09-27 15:03:54 +00:00
|
|
|
return !server->isWhitelistEnabled ||
|
|
|
|
std::any_of(std::begin(src), std::end(src), [&address](auto const& s) { return tr_wildmat(address, s.c_str()); });
|
2008-09-26 15:40:24 +00:00
|
|
|
}
|
|
|
|
|
2018-01-25 00:07:52 +00:00
|
|
|
static bool isIPAddressWithOptionalPort(char const* host)
|
2018-01-17 20:32:14 +00:00
|
|
|
{
|
2018-01-25 00:07:52 +00:00
|
|
|
struct sockaddr_storage address;
|
|
|
|
int address_len = sizeof(address);
|
2018-01-17 20:32:14 +00:00
|
|
|
|
2018-01-25 00:07:52 +00:00
|
|
|
/* TODO: move to net.{c,h} */
|
2019-02-10 11:05:16 +00:00
|
|
|
return evutil_parse_sockaddr_port(host, (struct sockaddr*)&address, &address_len) != -1;
|
2018-01-17 20:32:14 +00:00
|
|
|
}
|
|
|
|
|
2018-01-11 18:00:41 +00:00
|
|
|
static bool isHostnameAllowed(tr_rpc_server const* server, struct evhttp_request* req)
|
|
|
|
{
|
|
|
|
/* If password auth is enabled, any hostname is permitted. */
|
|
|
|
if (server->isPasswordEnabled)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If whitelist is disabled, no restrictions. */
|
|
|
|
if (!server->isHostWhitelistEnabled)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
char const* const host = evhttp_find_header(req->input_headers, "Host");
|
|
|
|
|
|
|
|
/* No host header, invalid request. */
|
2021-09-15 00:18:09 +00:00
|
|
|
if (host == nullptr)
|
2018-01-11 18:00:41 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-25 00:07:52 +00:00
|
|
|
/* IP address is always acceptable. */
|
|
|
|
if (isIPAddressWithOptionalPort(host))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2018-01-17 20:32:14 +00:00
|
|
|
|
2018-01-11 18:00:41 +00:00
|
|
|
/* Host header might include the port. */
|
2021-09-27 15:03:54 +00:00
|
|
|
auto const hostname = std::string(host, strcspn(host, ":"));
|
2018-01-11 18:00:41 +00:00
|
|
|
|
2018-01-25 00:07:52 +00:00
|
|
|
/* localhost is always acceptable. */
|
2021-09-27 15:03:54 +00:00
|
|
|
if (hostname == "localhost" || hostname == "localhost.")
|
2018-01-11 18:00:41 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-09-27 15:03:54 +00:00
|
|
|
auto const& src = server->hostWhitelist;
|
|
|
|
return std::any_of(
|
|
|
|
std::begin(src),
|
|
|
|
std::end(src),
|
|
|
|
[&hostname](auto const& str) { return tr_wildmat(hostname.c_str(), str.c_str()); });
|
2018-01-11 18:00:41 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
static bool test_session_id(tr_rpc_server* server, struct evhttp_request* req)
|
2009-05-08 14:56:11 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* ours = get_current_session_id(server);
|
|
|
|
char const* theirs = evhttp_find_header(req->input_headers, TR_RPC_SESSION_ID_HEADER);
|
2021-09-15 00:18:09 +00:00
|
|
|
bool const success = theirs != nullptr && strcmp(theirs, ours) == 0;
|
2017-04-19 12:04:45 +00:00
|
|
|
return success;
|
2009-05-08 14:56:11 +00:00
|
|
|
}
|
|
|
|
|
2021-11-15 03:54:48 +00:00
|
|
|
static bool isAuthorized(tr_rpc_server const* server, char const* auth_header)
|
|
|
|
{
|
|
|
|
if (!server->isPasswordEnabled)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://datatracker.ietf.org/doc/html/rfc7617
|
|
|
|
// `Basic ${base64(username)}:${base64(password)}`
|
|
|
|
|
|
|
|
auto constexpr Prefix = "Basic "sv;
|
|
|
|
auto auth = std::string_view{ auth_header != nullptr ? auth_header : "" };
|
|
|
|
if (!tr_strvStartsWith(auth, Prefix))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auth.remove_prefix(std::size(Prefix));
|
|
|
|
auto const decoded_str = tr_base64_decode_str(auth);
|
|
|
|
auto decoded = std::string_view{ decoded_str };
|
|
|
|
auto const username = tr_strvSep(&decoded, ':');
|
|
|
|
auto const password = decoded;
|
|
|
|
return server->username == username && tr_ssha1_matches(server->salted_password, password);
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void handle_request(struct evhttp_request* req, void* arg)
|
2008-09-25 05:03:39 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
auto* server = static_cast<tr_rpc_server*>(arg);
|
2008-07-17 14:45:31 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (req != nullptr && req->evcon != nullptr)
|
2008-09-25 05:03:39 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_add_header(req->output_headers, "Server", MY_REALM);
|
2008-09-26 15:40:24 +00:00
|
|
|
|
2020-11-14 18:43:42 +00:00
|
|
|
if (server->isAntiBruteForceEnabled && server->loginattempts >= server->antiBruteForceThreshold)
|
2017-08-12 14:36:43 +00:00
|
|
|
{
|
2017-08-13 11:53:41 +00:00
|
|
|
send_simple_response(req, 403, "<p>Too many unsuccessful login attempts. Please restart transmission-daemon.</p>");
|
2017-08-12 14:36:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isAddressAllowed(server, req->remote_host))
|
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
send_simple_response(
|
|
|
|
req,
|
|
|
|
403,
|
2017-08-12 14:36:43 +00:00
|
|
|
"<p>Unauthorized IP Address.</p>"
|
|
|
|
"<p>Either disable the IP address whitelist or add your address to it.</p>"
|
|
|
|
"<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>"
|
|
|
|
"<p>If you're still using ACLs, use a whitelist instead. See the transmission-daemon manpage for details.</p>");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-15 20:10:09 +00:00
|
|
|
evhttp_add_header(req->output_headers, "Access-Control-Allow-Origin", "*");
|
|
|
|
|
|
|
|
if (req->type == EVHTTP_REQ_OPTIONS)
|
|
|
|
{
|
|
|
|
char const* headers = evhttp_find_header(req->input_headers, "Access-Control-Request-Headers");
|
|
|
|
if (headers != nullptr)
|
|
|
|
{
|
|
|
|
evhttp_add_header(req->output_headers, "Access-Control-Allow-Headers", headers);
|
|
|
|
}
|
|
|
|
|
|
|
|
evhttp_add_header(req->output_headers, "Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
|
|
send_simple_response(req, 200, "");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-15 03:54:48 +00:00
|
|
|
if (!isAuthorized(server, evhttp_find_header(req->input_headers, "Authorization")))
|
2008-07-16 23:55:49 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_add_header(req->output_headers, "WWW-Authenticate", "Basic realm=\"" MY_REALM "\"");
|
2020-11-14 18:43:42 +00:00
|
|
|
if (server->isAntiBruteForceEnabled)
|
|
|
|
{
|
2021-11-15 03:54:48 +00:00
|
|
|
++server->loginattempts;
|
2020-11-14 18:43:42 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
char* unauthuser = tr_strdup_printf(
|
|
|
|
"<p>Unauthorized User. %d unsuccessful login attempts.</p>",
|
2019-02-10 11:05:16 +00:00
|
|
|
server->loginattempts);
|
2017-08-12 14:36:43 +00:00
|
|
|
send_simple_response(req, 401, unauthuser);
|
2017-08-13 11:53:41 +00:00
|
|
|
tr_free(unauthuser);
|
2017-08-12 14:36:43 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-08-13 11:53:41 +00:00
|
|
|
|
|
|
|
server->loginattempts = 0;
|
2017-08-12 14:36:43 +00:00
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
auto uri = std::string_view{ req->uri };
|
|
|
|
auto const location = tr_strvStartsWith(uri, server->url) ? uri.substr(std::size(server->url)) : ""sv;
|
2021-10-12 14:28:46 +00:00
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
if (std::empty(location) || location == "web"sv)
|
2008-07-16 23:55:49 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
auto const new_location = tr_strvJoin(server->url, "web/");
|
|
|
|
evhttp_add_header(req->output_headers, "Location", new_location.c_str());
|
2021-10-12 19:45:18 +00:00
|
|
|
send_simple_response(req, HTTP_MOVEPERM, nullptr);
|
2008-06-05 16:23:03 +00:00
|
|
|
}
|
2021-11-14 21:14:37 +00:00
|
|
|
else if (tr_strvStartsWith(location, "web/"sv))
|
2008-09-25 05:03:39 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
handle_web_client(req, server);
|
2008-09-25 05:03:39 +00:00
|
|
|
}
|
2021-11-14 21:14:37 +00:00
|
|
|
else if (tr_strvStartsWith(location, "upload"sv))
|
2010-12-31 14:07:27 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
handle_upload(req, server);
|
2010-12-31 14:07:27 +00:00
|
|
|
}
|
2018-01-11 18:00:41 +00:00
|
|
|
else if (!isHostnameAllowed(server, req))
|
|
|
|
{
|
|
|
|
char* const tmp = tr_strdup_printf(
|
|
|
|
"<p>Transmission received your request, but the hostname was unrecognized.</p>"
|
|
|
|
"<p>To fix this, choose one of the following options:"
|
|
|
|
"<ul>"
|
|
|
|
"<li>Enable password authentication, then any hostname is allowed.</li>"
|
|
|
|
"<li>Add the hostname you want to use to the whitelist in settings.</li>"
|
|
|
|
"</ul></p>"
|
|
|
|
"<p>If you're editing settings.json, see the 'rpc-host-whitelist' and 'rpc-host-whitelist-enabled' entries.</p>"
|
|
|
|
"<p>This requirement has been added to help prevent "
|
|
|
|
"<a href=\"https://en.wikipedia.org/wiki/DNS_rebinding\">DNS Rebinding</a> "
|
|
|
|
"attacks.</p>");
|
|
|
|
send_simple_response(req, 421, tmp);
|
|
|
|
tr_free(tmp);
|
|
|
|
}
|
2018-01-17 20:34:40 +00:00
|
|
|
#ifdef REQUIRE_SESSION_ID
|
2017-04-19 12:04:45 +00:00
|
|
|
else if (!test_session_id(server, req))
|
2009-05-08 14:56:11 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* sessionId = get_current_session_id(server);
|
2017-04-19 12:04:45 +00:00
|
|
|
char* tmp = tr_strdup_printf(
|
|
|
|
"<p>Your request had an invalid session-id header.</p>"
|
|
|
|
"<p>To fix this, follow these steps:"
|
|
|
|
"<ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it"
|
|
|
|
"<li> Add the updated header to your outgoing requests"
|
|
|
|
"<li> When you get this 409 error message, resend your request with the updated header"
|
|
|
|
"</ol></p>"
|
|
|
|
"<p>This requirement has been added to help prevent "
|
2018-01-11 18:00:41 +00:00
|
|
|
"<a href=\"https://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
|
2017-04-19 12:04:45 +00:00
|
|
|
"attacks.</p>"
|
|
|
|
"<p><code>%s: %s</code></p>",
|
2021-08-15 09:41:48 +00:00
|
|
|
TR_RPC_SESSION_ID_HEADER,
|
|
|
|
sessionId);
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_add_header(req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId);
|
2021-10-15 20:10:09 +00:00
|
|
|
evhttp_add_header(req->output_headers, "Access-Control-Expose-Headers", TR_RPC_SESSION_ID_HEADER);
|
2017-04-19 12:04:45 +00:00
|
|
|
send_simple_response(req, 409, tmp);
|
|
|
|
tr_free(tmp);
|
2009-05-08 14:56:11 +00:00
|
|
|
}
|
|
|
|
#endif
|
2021-11-15 03:54:48 +00:00
|
|
|
else if (tr_strvStartsWith(location, "rpc"sv))
|
2008-09-25 05:03:39 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
handle_rpc(req, server);
|
2008-09-25 05:03:39 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2008-09-03 19:59:09 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
send_simple_response(req, HTTP_NOTFOUND, req->uri);
|
2008-09-03 19:59:09 +00:00
|
|
|
}
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-21 00:39:05 +00:00
|
|
|
static auto constexpr ServerStartRetryCount = int{ 10 };
|
|
|
|
static auto constexpr ServerStartRetryDelayIncrement = int{ 5 };
|
|
|
|
static auto constexpr ServerStartRetryDelayStep = int{ 3 };
|
|
|
|
static auto constexpr ServerStartRetryMaxDelay = int{ 60 };
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void startServer(void* vserver);
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2021-10-24 16:41:54 +00:00
|
|
|
static void rpc_server_on_start_retry(evutil_socket_t /*fd*/, short /*type*/, void* context)
|
2008-09-25 05:03:39 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
startServer(context);
|
2016-01-07 15:28:58 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static int rpc_server_start_retry(tr_rpc_server* server)
|
2016-01-07 15:28:58 +00:00
|
|
|
{
|
2021-10-21 00:39:05 +00:00
|
|
|
int retry_delay = (server->start_retry_counter / ServerStartRetryDelayStep + 1) * ServerStartRetryDelayIncrement;
|
|
|
|
retry_delay = std::min(retry_delay, int{ ServerStartRetryMaxDelay });
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (server->start_retry_timer == nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
server->start_retry_timer = evtimer_new(server->session->event_base, rpc_server_on_start_retry, server);
|
|
|
|
}
|
2008-10-01 04:26:38 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_timerAdd(server->start_retry_timer, retry_delay, 0);
|
|
|
|
++server->start_retry_counter;
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
return retry_delay;
|
2016-01-07 15:28:58 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void rpc_server_start_retry_cancel(tr_rpc_server* server)
|
2016-01-07 15:28:58 +00:00
|
|
|
{
|
2021-09-15 00:18:09 +00:00
|
|
|
if (server->start_retry_timer != nullptr)
|
2008-09-29 03:02:27 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
event_free(server->start_retry_timer);
|
2021-09-15 00:18:09 +00:00
|
|
|
server->start_retry_timer = nullptr;
|
2008-09-29 03:02:27 +00:00
|
|
|
}
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
server->start_retry_counter = 0;
|
2008-09-25 05:03:39 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void startServer(void* vserver)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2021-09-12 17:41:49 +00:00
|
|
|
auto* server = static_cast<tr_rpc_server*>(vserver);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (server->httpd != nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct evhttp* httpd = evhttp_new(server->session->event_base);
|
2021-10-15 20:10:09 +00:00
|
|
|
evhttp_set_allowed_methods(httpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_OPTIONS);
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* address = tr_rpcGetBindAddress(server);
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2020-11-05 22:46:21 +00:00
|
|
|
tr_port const port = server->port;
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (evhttp_bind_socket(httpd, address, port) == -1)
|
2016-01-07 15:28:58 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_free(httpd);
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2021-10-21 00:39:05 +00:00
|
|
|
if (server->start_retry_counter < ServerStartRetryCount)
|
2016-01-07 15:28:58 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
int const retry_delay = rpc_server_start_retry(server);
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddNamedDbg(MY_NAME, "Unable to bind to %s:%d, retrying in %d seconds", address, port, retry_delay);
|
|
|
|
return;
|
2016-01-07 15:28:58 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
tr_logAddNamedError(
|
|
|
|
MY_NAME,
|
|
|
|
"Unable to bind to %s:%d after %d attempts, giving up",
|
|
|
|
address,
|
|
|
|
port,
|
2021-10-21 00:39:05 +00:00
|
|
|
ServerStartRetryCount);
|
2016-01-07 15:28:58 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_set_gencb(httpd, handle_request, server);
|
|
|
|
server->httpd = httpd;
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddNamedDbg(MY_NAME, "Started listening on %s:%d", address, port);
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
rpc_server_start_retry_cancel(server);
|
2016-01-07 15:28:58 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void stopServer(tr_rpc_server* server)
|
2016-01-07 15:28:58 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
TR_ASSERT(tr_amInEventThread(server->session));
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
rpc_server_start_retry_cancel(server);
|
|
|
|
|
|
|
|
struct evhttp* httpd = server->httpd;
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
if (httpd == nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* address = tr_rpcGetBindAddress(server);
|
|
|
|
int const port = server->port;
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2021-09-15 00:18:09 +00:00
|
|
|
server->httpd = nullptr;
|
2017-04-19 12:04:45 +00:00
|
|
|
evhttp_free(httpd);
|
2016-01-07 15:28:58 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddNamedDbg(MY_NAME, "Stopped listening on %s:%d", address, port);
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void onEnabledChanged(void* vserver)
|
2008-10-01 04:26:38 +00:00
|
|
|
{
|
2021-09-12 17:41:49 +00:00
|
|
|
auto* server = static_cast<tr_rpc_server*>(vserver);
|
2008-10-01 04:26:38 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!server->isEnabled)
|
|
|
|
{
|
|
|
|
stopServer(server);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
startServer(server);
|
|
|
|
}
|
2008-10-01 04:26:38 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void tr_rpcSetEnabled(tr_rpc_server* server, bool isEnabled)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
server->isEnabled = isEnabled;
|
2008-05-18 16:44:30 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_runInEventThread(server->session, onEnabledChanged, server);
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
bool tr_rpcIsEnabled(tr_rpc_server const* server)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
return server->isEnabled;
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
static void restartServer(void* vserver)
|
2008-10-01 04:26:38 +00:00
|
|
|
{
|
2021-09-12 17:41:49 +00:00
|
|
|
auto* server = static_cast<tr_rpc_server*>(vserver);
|
2008-10-01 04:26:38 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (server->isEnabled)
|
2008-10-01 04:26:38 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
stopServer(server);
|
|
|
|
startServer(server);
|
2008-10-01 04:26:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void tr_rpcSetPort(tr_rpc_server* server, tr_port port)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2021-09-15 00:18:09 +00:00
|
|
|
TR_ASSERT(server != nullptr);
|
2010-01-16 22:46:38 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (server->port != port)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
server->port = port;
|
2008-05-18 16:44:30 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (server->isEnabled)
|
|
|
|
{
|
|
|
|
tr_runInEventThread(server->session, restartServer, server);
|
|
|
|
}
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
tr_port tr_rpcGetPort(tr_rpc_server const* server)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
return server->port;
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
void tr_rpcSetUrl(tr_rpc_server* server, std::string_view url)
|
2010-12-12 18:22:11 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
server->url = url;
|
|
|
|
dbgmsg("setting our URL to [%s]", server->url.c_str());
|
2010-12-12 18:22:11 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
std::string const& tr_rpcGetUrl(tr_rpc_server const* server)
|
2010-12-12 18:22:11 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
return server->url;
|
2010-12-12 18:22:11 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
static auto parseWhitelist(std::string_view whitelist)
|
2008-12-21 19:23:41 +00:00
|
|
|
{
|
2021-09-27 15:03:54 +00:00
|
|
|
auto list = std::list<std::string>{};
|
2008-12-21 19:23:41 +00:00
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
while (!std::empty(whitelist))
|
2013-02-02 16:22:21 +00:00
|
|
|
{
|
2021-11-14 06:02:45 +00:00
|
|
|
auto const pos = whitelist.find_first_of(" ,;"sv);
|
|
|
|
auto const token = tr_strvStrip(whitelist.substr(0, pos));
|
|
|
|
list.emplace_back(token);
|
|
|
|
whitelist = pos == whitelist.npos ? ""sv : whitelist.substr(pos + 1);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
if (token.find_first_of("+-"sv) != token.npos)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
tr_logAddNamedInfo(
|
|
|
|
MY_NAME,
|
2021-11-14 06:02:45 +00:00
|
|
|
"Adding address to whitelist: %" TR_PRIsv " (And it has a '+' or '-'! Are you using an old ACL by mistake?)",
|
|
|
|
TR_PRIsv_ARG(token));
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 06:02:45 +00:00
|
|
|
tr_logAddNamedInfo(MY_NAME, "Adding address to whitelist: %" TR_PRIsv, TR_PRIsv_ARG(token));
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2008-12-21 19:23:41 +00:00
|
|
|
}
|
2021-09-27 15:03:54 +00:00
|
|
|
|
|
|
|
return list;
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
static void tr_rpcSetHostWhitelist(tr_rpc_server* server, std::string_view whitelist)
|
2018-01-11 18:00:41 +00:00
|
|
|
{
|
2021-11-14 06:02:45 +00:00
|
|
|
server->hostWhitelist = parseWhitelist(whitelist);
|
2018-01-11 18:00:41 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
void tr_rpcSetWhitelist(tr_rpc_server* server, std::string_view whitelist)
|
2018-01-11 18:00:41 +00:00
|
|
|
{
|
2021-11-14 06:02:45 +00:00
|
|
|
server->whitelistStr = whitelist;
|
|
|
|
server->whitelist = parseWhitelist(whitelist);
|
2018-01-11 18:00:41 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
std::string const& tr_rpcGetWhitelist(tr_rpc_server const* server)
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2021-11-14 06:02:45 +00:00
|
|
|
return server->whitelistStr;
|
2008-06-05 16:23:03 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void tr_rpcSetWhitelistEnabled(tr_rpc_server* server, bool isEnabled)
|
2008-10-01 22:59:29 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
server->isWhitelistEnabled = isEnabled;
|
2008-10-01 22:59:29 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
bool tr_rpcGetWhitelistEnabled(tr_rpc_server const* server)
|
2008-10-01 22:59:29 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
return server->isWhitelistEnabled;
|
2008-10-01 22:59:29 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
static void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled)
|
2018-01-11 18:00:41 +00:00
|
|
|
{
|
|
|
|
server->isHostWhitelistEnabled = isEnabled;
|
|
|
|
}
|
|
|
|
|
2008-06-05 16:23:03 +00:00
|
|
|
/****
|
|
|
|
***** PASSWORD
|
|
|
|
****/
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
void tr_rpcSetUsername(tr_rpc_server* server, std::string_view username)
|
2008-06-05 16:23:03 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
server->username = username;
|
|
|
|
dbgmsg("setting our Username to [%s]", server->username.c_str());
|
2008-06-05 16:23:03 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
std::string const& tr_rpcGetUsername(tr_rpc_server const* server)
|
2008-06-05 16:23:03 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
return server->username;
|
2008-06-05 16:23:03 +00:00
|
|
|
}
|
|
|
|
|
2021-11-15 03:54:48 +00:00
|
|
|
static constexpr bool isSalted(std::string_view password)
|
|
|
|
{
|
|
|
|
return !std::empty(password) && password.front() == '{';
|
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
void tr_rpcSetPassword(tr_rpc_server* server, std::string_view password)
|
2008-06-05 16:23:03 +00:00
|
|
|
{
|
2021-11-15 03:54:48 +00:00
|
|
|
server->salted_password = isSalted(password) ? password : tr_ssha1(password);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-11-15 03:54:48 +00:00
|
|
|
dbgmsg("setting our salted password to [%s]", server->salted_password.c_str());
|
2008-06-05 16:23:03 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
std::string const& tr_rpcGetPassword(tr_rpc_server const* server)
|
2008-06-05 16:23:03 +00:00
|
|
|
{
|
2021-11-15 03:54:48 +00:00
|
|
|
return server->salted_password;
|
2008-06-05 16:23:03 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void tr_rpcSetPasswordEnabled(tr_rpc_server* server, bool isEnabled)
|
2008-06-05 16:23:03 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
server->isPasswordEnabled = isEnabled;
|
|
|
|
dbgmsg("setting 'password enabled' to %d", (int)isEnabled);
|
2008-06-05 16:23:03 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
bool tr_rpcIsPasswordEnabled(tr_rpc_server const* server)
|
2008-06-05 16:23:03 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
return server->isPasswordEnabled;
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
char const* tr_rpcGetBindAddress(tr_rpc_server const* server)
|
2009-04-15 21:05:58 +00:00
|
|
|
{
|
2017-01-28 20:18:38 +00:00
|
|
|
return tr_address_to_string(&server->bindAddress);
|
2009-04-15 21:05:58 +00:00
|
|
|
}
|
|
|
|
|
2020-11-14 18:43:42 +00:00
|
|
|
bool tr_rpcGetAntiBruteForceEnabled(tr_rpc_server const* server)
|
|
|
|
{
|
|
|
|
return server->isAntiBruteForceEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void tr_rpcSetAntiBruteForceEnabled(tr_rpc_server* server, bool isEnabled)
|
|
|
|
{
|
|
|
|
server->isAntiBruteForceEnabled = isEnabled;
|
|
|
|
if (!isEnabled)
|
|
|
|
{
|
|
|
|
server->loginattempts = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int tr_rpcGetAntiBruteForceThreshold(tr_rpc_server const* server)
|
|
|
|
{
|
|
|
|
return server->antiBruteForceThreshold;
|
|
|
|
}
|
|
|
|
|
|
|
|
void tr_rpcSetAntiBruteForceThreshold(tr_rpc_server* server, int badRequests)
|
|
|
|
{
|
|
|
|
server->antiBruteForceThreshold = badRequests;
|
|
|
|
}
|
|
|
|
|
2008-06-05 16:23:03 +00:00
|
|
|
/****
|
|
|
|
***** LIFE CYCLE
|
|
|
|
****/
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
static void missing_settings_key(tr_quark const q)
|
2012-12-22 20:35:19 +00:00
|
|
|
{
|
2021-11-02 01:01:27 +00:00
|
|
|
char const* str = tr_quark_get_string(q);
|
2017-04-19 12:04:45 +00:00
|
|
|
tr_logAddNamedError(MY_NAME, _("Couldn't find settings key \"%s\""), str);
|
2015-01-02 11:15:31 +00:00
|
|
|
}
|
2012-12-22 20:35:19 +00:00
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpc_server::tr_rpc_server(tr_session* session_in, tr_variant* settings)
|
|
|
|
: session{ session_in }
|
2008-05-18 16:44:30 +00:00
|
|
|
{
|
2021-10-22 13:51:36 +00:00
|
|
|
auto address = tr_address{};
|
|
|
|
auto boolVal = bool{};
|
|
|
|
auto i = int64_t{};
|
2021-11-14 22:06:32 +00:00
|
|
|
auto sv = std::string_view{};
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
auto key = TR_KEY_rpc_enabled;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
if (!tr_variantDictFindBool(settings, key, &boolVal))
|
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
this->isEnabled = boolVal;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key = TR_KEY_rpc_port;
|
|
|
|
|
|
|
|
if (!tr_variantDictFindInt(settings, key, &i))
|
2013-02-02 16:22:21 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
missing_settings_key(key);
|
2013-02-02 16:22:21 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
this->port = (tr_port)i;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key = TR_KEY_rpc_url;
|
2021-11-14 21:14:37 +00:00
|
|
|
|
|
|
|
if (!tr_variantDictFindStrView(settings, key, &sv))
|
2013-02-02 16:22:21 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
missing_settings_key(key);
|
2013-02-02 16:22:21 +00:00
|
|
|
}
|
2021-11-14 21:14:37 +00:00
|
|
|
else if (std::empty(sv) || sv.back() != '/')
|
2021-11-05 06:29:19 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
this->url = tr_strvJoin(sv, "/"sv);
|
2021-11-05 06:29:19 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-02 16:22:21 +00:00
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
this->url = sv;
|
2009-04-15 21:05:58 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
key = TR_KEY_rpc_whitelist_enabled;
|
|
|
|
|
|
|
|
if (!tr_variantDictFindBool(settings, key, &boolVal))
|
2008-12-16 16:13:21 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcSetWhitelistEnabled(this, boolVal);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2008-12-16 16:13:21 +00:00
|
|
|
|
2018-01-11 18:00:41 +00:00
|
|
|
key = TR_KEY_rpc_host_whitelist_enabled;
|
|
|
|
|
|
|
|
if (!tr_variantDictFindBool(settings, key, &boolVal))
|
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcSetHostWhitelistEnabled(this, boolVal);
|
2018-01-11 18:00:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key = TR_KEY_rpc_host_whitelist;
|
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
if (!tr_variantDictFindStrView(settings, key, &sv) && !std::empty(sv))
|
2018-01-11 18:00:41 +00:00
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcSetHostWhitelist(this, sv);
|
2018-01-11 18:00:41 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
key = TR_KEY_rpc_authentication_required;
|
2008-12-16 16:13:21 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!tr_variantDictFindBool(settings, key, &boolVal))
|
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcSetPasswordEnabled(this, boolVal);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key = TR_KEY_rpc_whitelist;
|
|
|
|
|
2021-11-14 06:02:45 +00:00
|
|
|
if (!tr_variantDictFindStrView(settings, key, &sv) && !std::empty(sv))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcSetWhitelist(this, sv);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key = TR_KEY_rpc_username;
|
|
|
|
|
2021-11-14 22:06:32 +00:00
|
|
|
if (!tr_variantDictFindStrView(settings, key, &sv))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 22:06:32 +00:00
|
|
|
tr_rpcSetUsername(this, sv);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key = TR_KEY_rpc_password;
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
if (!tr_variantDictFindStrView(settings, key, &sv))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcSetPassword(this, sv);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-14 18:43:42 +00:00
|
|
|
key = TR_KEY_anti_brute_force_enabled;
|
|
|
|
|
|
|
|
if (!tr_variantDictFindBool(settings, key, &boolVal))
|
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcSetAntiBruteForceEnabled(this, boolVal);
|
2020-11-14 18:43:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
key = TR_KEY_anti_brute_force_threshold;
|
|
|
|
|
|
|
|
if (!tr_variantDictFindInt(settings, key, &i))
|
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcSetAntiBruteForceThreshold(this, i);
|
2020-11-14 18:43:42 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
key = TR_KEY_rpc_bind_address;
|
|
|
|
|
2021-11-14 22:06:32 +00:00
|
|
|
if (!tr_variantDictFindStrView(settings, key, &sv))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
missing_settings_key(key);
|
|
|
|
address = tr_inaddr_any;
|
|
|
|
}
|
2021-11-14 22:06:32 +00:00
|
|
|
else
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2021-11-14 22:06:32 +00:00
|
|
|
if (!tr_address_from_string(&address, std::string{ sv }.c_str()))
|
|
|
|
{
|
|
|
|
tr_logAddNamedError(MY_NAME, _("%" TR_PRIsv " is not a valid address"), TR_PRIsv_ARG(sv));
|
|
|
|
address = tr_inaddr_any;
|
|
|
|
}
|
|
|
|
else if (address.type != TR_AF_INET && address.type != TR_AF_INET6)
|
|
|
|
{
|
|
|
|
tr_logAddNamedError(
|
|
|
|
MY_NAME,
|
|
|
|
_("%" TR_PRIsv " is not an IPv4 or IPv6 address. RPC listeners must be IPv4 or IPv6"),
|
|
|
|
TR_PRIsv_ARG(sv));
|
|
|
|
address = tr_inaddr_any;
|
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
this->bindAddress = address;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
if (this->isEnabled)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
tr_logAddNamedInfo(
|
|
|
|
MY_NAME,
|
|
|
|
_("Serving RPC and Web requests on %s:%d%s"),
|
2021-11-14 21:14:37 +00:00
|
|
|
tr_rpcGetBindAddress(this),
|
|
|
|
(int)this->port,
|
|
|
|
this->url.c_str());
|
|
|
|
tr_runInEventThread(session, startServer, this);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
if (this->isWhitelistEnabled)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
tr_logAddNamedInfo(MY_NAME, "%s", _("Whitelist enabled"));
|
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
if (this->isPasswordEnabled)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
tr_logAddNamedInfo(MY_NAME, "%s", _("Password required"));
|
|
|
|
}
|
2008-12-16 16:13:21 +00:00
|
|
|
}
|
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
char const* webClientDir = tr_getWebClientDir(this->session);
|
2020-10-24 01:04:25 +00:00
|
|
|
if (!tr_str_is_empty(webClientDir))
|
|
|
|
{
|
|
|
|
tr_logAddNamedInfo(MY_NAME, _("Serving RPC and Web requests from directory '%s'"), webClientDir);
|
|
|
|
}
|
2021-11-14 21:14:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tr_rpc_server::~tr_rpc_server()
|
|
|
|
{
|
|
|
|
TR_ASSERT(tr_amInEventThread(this->session));
|
|
|
|
|
|
|
|
stopServer(this);
|
2020-10-24 01:04:25 +00:00
|
|
|
|
2021-11-14 21:14:37 +00:00
|
|
|
if (this->isStreamInitialized)
|
|
|
|
{
|
|
|
|
deflateEnd(&this->stream);
|
|
|
|
}
|
2008-05-18 16:44:30 +00:00
|
|
|
}
|