axyd

rpc-server.cc

Oct 4th, 2022
226
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 33.26 KB | None | 0 0
  1. // This file Copyright © 2008-2022 Mnemosyne LLC.
  2. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
  3. // or any future license endorsed by Mnemosyne LLC.
  4. // License text can be found in the licenses/ folder.
  5.  
  6. #include <algorithm>
  7. #include <array>
  8. #include <chrono>
  9. #include <cstring> /* for strcspn() */
  10. #include <ctime>
  11. #include <memory>
  12. #include <string>
  13. #include <string_view>
  14. #include <utility>
  15. #include <vector>
  16.  
  17. #ifndef _WIN32
  18. #include <sys/un.h>
  19. #include <sys/stat.h>
  20. #include <unistd.h>
  21. #endif
  22.  
  23. #include <event2/buffer.h>
  24. #include <event2/http.h>
  25. #include <event2/http_struct.h> /* TODO: eventually remove this */
  26. #include <event2/listener.h>
  27.  
  28. #include <fmt/core.h>
  29. #include <fmt/chrono.h>
  30.  
  31. #include <libdeflate.h>
  32.  
  33. #include "transmission.h"
  34.  
  35. #include "crypto-utils.h" /* tr_rand_buffer(), tr_ssha1_matches() */
  36. #include "error.h"
  37. #include "log.h"
  38. #include "net.h"
  39. #include "platform.h" /* tr_getWebClientDir() */
  40. #include "quark.h"
  41. #include "rpc-server.h"
  42. #include "rpcimpl.h"
  43. #include "session-id.h"
  44. #include "session.h"
  45. #include "timer.h"
  46. #include "tr-assert.h"
  47. #include "tr-strbuf.h"
  48. #include "trevent.h"
  49. #include "utils.h"
  50. #include "variant.h"
  51. #include "web-utils.h"
  52. #include "web.h"
  53.  
  54. using namespace std::literals;
  55.  
  56. /* session-id is used to make cross-site request forgery attacks difficult.
  57.  * Don't disable this feature unless you really know what you're doing!
  58.  * https://en.wikipedia.org/wiki/Cross-site_request_forgery
  59.  * https://shiflett.org/articles/cross-site-request-forgeries
  60.  * http://www.webappsec.org/lists/websecurity/archive/2008-04/msg00037.html */
  61. //#define REQUIRE_SESSION_ID
  62.  
  63. static auto constexpr TrUnixSocketPrefix = "unix:"sv;
  64.  
  65. /* The maximum size of a unix socket path is defined per-platform based on sockaddr_un.sun_path.
  66.  * On Windows the fallback is the length of an ipv6 address. Subtracting one at the end is for
  67.  * double counting null terminators from sun_path and TrUnixSocketPrefix. */
  68. #ifdef _WIN32
  69. auto inline constexpr TrUnixAddrStrLen = size_t{ INET6_ADDRSTRLEN };
  70. #else
  71. auto inline constexpr TrUnixAddrStrLen = size_t{ sizeof(((struct sockaddr_un*)nullptr)->sun_path) +
  72.                                                  std::size(TrUnixSocketPrefix) };
  73. #endif
  74.  
  75. enum tr_rpc_address_type
  76. {
  77.     TR_RPC_AF_INET,
  78.     TR_RPC_AF_INET6,
  79.     TR_RPC_AF_UNIX
  80. };
  81.  
  82. struct tr_rpc_address
  83. {
  84.     tr_rpc_address_type type;
  85.     union
  86.     {
  87.         struct in_addr addr4;
  88.         struct in6_addr addr6;
  89.         std::array<char, TrUnixAddrStrLen> unixSocketPath;
  90.     } addr;
  91.  
  92.     void set_inaddr_any()
  93.     {
  94.         type = TR_RPC_AF_INET;
  95.         addr.addr4 = { INADDR_ANY };
  96.     }
  97. };
  98.  
  99. #define MY_REALM "Transmission"
  100.  
  101. static int constexpr DeflateLevel = 6; // medium / default
  102.  
  103. #ifdef TR_ENABLE_ASSERTS
  104. static bool constexpr tr_rpc_address_is_valid(tr_rpc_address const& a)
  105. {
  106.     return a.type == TR_RPC_AF_INET || a.type == TR_RPC_AF_INET6 || a.type == TR_RPC_AF_UNIX;
  107. }
  108. #endif
  109.  
  110. /**
  111. ***
  112. **/
  113.  
  114. static void send_simple_response(struct evhttp_request* req, int code, char const* text)
  115. {
  116.     char const* code_text = tr_webGetResponseStr(code);
  117.     struct evbuffer* body = evbuffer_new();
  118.  
  119.     evbuffer_add_printf(body, "<h1>%d: %s</h1>", code, code_text);
  120.  
  121.     if (text != nullptr)
  122.     {
  123.         evbuffer_add_printf(body, "%s", text);
  124.     }
  125.  
  126.     evhttp_send_reply(req, code, code_text, body);
  127.  
  128.     evbuffer_free(body);
  129. }
  130.  
  131. /***
  132. ****
  133. ***/
  134.  
  135. static char const* mimetype_guess(std::string_view path)
  136. {
  137.     // these are the ones we need for serving the web client's files...
  138.     static auto constexpr Types = std::array<std::pair<std::string_view, char const*>, 7>{ {
  139.         { ".css"sv, "text/css" },
  140.         { ".gif"sv, "image/gif" },
  141.         { ".html"sv, "text/html" },
  142.         { ".ico"sv, "image/vnd.microsoft.icon" },
  143.         { ".js"sv, "application/javascript" },
  144.         { ".png"sv, "image/png" },
  145.         { ".svg"sv, "image/svg+xml" },
  146.     } };
  147.  
  148.     for (auto const& [suffix, mime_type] : Types)
  149.     {
  150.         if (tr_strvEndsWith(path, suffix))
  151.         {
  152.             return mime_type;
  153.         }
  154.     }
  155.  
  156.     return "application/octet-stream";
  157. }
  158.  
  159. static evbuffer* make_response(struct evhttp_request* req, tr_rpc_server const* server, std::string_view content)
  160. {
  161.     auto* const out = evbuffer_new();
  162.  
  163.     char const* key = "Accept-Encoding";
  164.     char const* encoding = evhttp_find_header(req->input_headers, key);
  165.  
  166.     if (bool const do_compress = encoding != nullptr && tr_strvContains(encoding, "gzip"sv); !do_compress)
  167.     {
  168.         evbuffer_add(out, std::data(content), std::size(content));
  169.     }
  170.     else
  171.     {
  172.         auto const max_compressed_len = libdeflate_deflate_compress_bound(server->compressor.get(), std::size(content));
  173.  
  174.         auto iov = evbuffer_iovec{};
  175.         evbuffer_reserve_space(out, std::max(std::size(content), max_compressed_len), &iov, 1);
  176.  
  177.         auto const compressed_len = libdeflate_gzip_compress(
  178.             server->compressor.get(),
  179.             std::data(content),
  180.             std::size(content),
  181.             iov.iov_base,
  182.             iov.iov_len);
  183.         if (0 < compressed_len && compressed_len < std::size(content))
  184.         {
  185.             iov.iov_len = compressed_len;
  186.             evhttp_add_header(req->output_headers, "Content-Encoding", "gzip");
  187.         }
  188.         else
  189.         {
  190.             std::copy(std::begin(content), std::end(content), static_cast<char*>(iov.iov_base));
  191.             iov.iov_len = std::size(content);
  192.         }
  193.  
  194.         evbuffer_commit_space(out, &iov, 1);
  195.     }
  196.  
  197.     return out;
  198. }
  199.  
  200. static void add_time_header(struct evkeyvalq* headers, char const* key, time_t now)
  201. {
  202.     // RFC 2616 says this must follow RFC 1123's date format, so use gmtime instead of localtime
  203.     evhttp_add_header(headers, key, fmt::format("{:%a %b %d %T %Y%n}", fmt::gmtime(now)).c_str());
  204. }
  205.  
  206. static void serve_file(struct evhttp_request* req, tr_rpc_server const* server, std::string_view filename)
  207. {
  208.     if (req->type != EVHTTP_REQ_GET)
  209.     {
  210.         evhttp_add_header(req->output_headers, "Allow", "GET");
  211.         send_simple_response(req, 405, nullptr);
  212.         return;
  213.     }
  214.  
  215.     auto content = std::vector<char>{};
  216.  
  217.     if (tr_error* error = nullptr; !tr_loadFile(filename, content, &error))
  218.     {
  219.         send_simple_response(req, HTTP_NOTFOUND, fmt::format("{} ({})", filename, error->message).c_str());
  220.         tr_error_free(error);
  221.         return;
  222.     }
  223.  
  224.     auto const now = tr_time();
  225.     add_time_header(req->output_headers, "Date", now);
  226.     add_time_header(req->output_headers, "Expires", now + (24 * 60 * 60));
  227.     evhttp_add_header(req->output_headers, "Content-Type", mimetype_guess(filename));
  228.  
  229.     auto* const response = make_response(req, server, std::string_view{ std::data(content), std::size(content) });
  230.     evhttp_send_reply(req, HTTP_OK, "OK", response);
  231.     evbuffer_free(response);
  232. }
  233.  
  234. static void handle_web_client(struct evhttp_request* req, tr_rpc_server* server)
  235. {
  236.     if (std::empty(server->web_client_dir_))
  237.     {
  238.         send_simple_response(
  239.             req,
  240.             HTTP_NOTFOUND,
  241.             "<p>Couldn't find Transmission's web interface files!</p>"
  242.             "<p>Users: to tell Transmission where to look, "
  243.             "set the TRANSMISSION_WEB_HOME environment "
  244.             "variable to the folder where the web interface's "
  245.             "index.html is located.</p>"
  246.             "<p>Package Builders: to set a custom default at compile time, "
  247.             "#define PACKAGE_DATA_DIR in libtransmission/platform.c "
  248.             "or tweak tr_getClutchDir() by hand.</p>");
  249.     }
  250.     else
  251.     {
  252.         // convert `req->uri` (ex: "/transmission/web/images/favicon.png")
  253.         // into a filesystem path (ex: "/usr/share/transmission/web/images/favicon.png")
  254.  
  255.         // remove the "/transmission/web/" prefix
  256.         static auto constexpr Web = "web/"sv;
  257.         auto subpath = std::string_view{ req->uri }.substr(std::size(server->url()) + std::size(Web));
  258.  
  259.         // remove any trailing query / fragment
  260.         subpath = subpath.substr(0, subpath.find_first_of("?#"sv));
  261.  
  262.         // if the query is empty, use the default
  263.         static auto constexpr DefaultPage = "index.html"sv;
  264.         if (std::empty(subpath))
  265.         {
  266.             subpath = DefaultPage;
  267.         }
  268.  
  269.         if (tr_strvContains(subpath, ".."sv))
  270.         {
  271.             send_simple_response(req, HTTP_NOTFOUND, "<p>Tsk, tsk.</p>");
  272.         }
  273.         else
  274.         {
  275.             serve_file(req, server, tr_pathbuf{ server->web_client_dir_, '/', subpath });
  276.         }
  277.     }
  278. }
  279.  
  280. struct rpc_response_data
  281. {
  282.     struct evhttp_request* req;
  283.     tr_rpc_server* server;
  284. };
  285.  
  286. static void rpc_response_func(tr_session* /*session*/, tr_variant* content, void* user_data)
  287. {
  288.     auto* data = static_cast<struct rpc_response_data*>(user_data);
  289.  
  290.     auto* const response = make_response(data->req, data->server, tr_variantToStr(content, TR_VARIANT_FMT_JSON_LEAN));
  291.     evhttp_add_header(data->req->output_headers, "Content-Type", "application/json; charset=UTF-8");
  292.     evhttp_send_reply(data->req, HTTP_OK, "OK", response);
  293.     evbuffer_free(response);
  294.  
  295.     delete data;
  296. }
  297.  
  298. static void handle_rpc_from_json(struct evhttp_request* req, tr_rpc_server* server, std::string_view json)
  299. {
  300.     auto top = tr_variant{};
  301.     auto const have_content = tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, json);
  302.  
  303.     tr_rpc_request_exec_json(
  304.         server->session,
  305.         have_content ? &top : nullptr,
  306.         rpc_response_func,
  307.         new rpc_response_data{ req, server });
  308.  
  309.     if (have_content)
  310.     {
  311.         tr_variantClear(&top);
  312.     }
  313. }
  314.  
  315. static void handle_rpc(struct evhttp_request* req, tr_rpc_server* server)
  316. {
  317.     if (req->type == EVHTTP_REQ_POST)
  318.     {
  319.         auto json = std::string_view{ reinterpret_cast<char const*>(evbuffer_pullup(req->input_buffer, -1)),
  320.                                       evbuffer_get_length(req->input_buffer) };
  321.         handle_rpc_from_json(req, server, json);
  322.         return;
  323.     }
  324.  
  325.     send_simple_response(req, 405, nullptr);
  326. }
  327.  
  328. static bool isAddressAllowed(tr_rpc_server const* server, char const* address)
  329. {
  330.     if (!server->isWhitelistEnabled())
  331.     {
  332.         return true;
  333.     }
  334.  
  335.     auto const& src = server->whitelist_;
  336.     return std::any_of(std::begin(src), std::end(src), [&address](auto const& s) { return tr_wildmat(address, s); });
  337. }
  338.  
  339. static bool isIPAddressWithOptionalPort(char const* host)
  340. {
  341.     auto address = sockaddr_storage{};
  342.     int address_len = sizeof(address);
  343.  
  344.     /* TODO: move to net.{c,h} */
  345.     return evutil_parse_sockaddr_port(host, (struct sockaddr*)&address, &address_len) != -1;
  346. }
  347.  
  348. static bool isHostnameAllowed(tr_rpc_server const* server, evhttp_request const* req)
  349. {
  350.     /* If password auth is enabled, any hostname is permitted. */
  351.     if (server->isPasswordEnabled())
  352.     {
  353.         return true;
  354.     }
  355.  
  356.     /* If whitelist is disabled, no restrictions. */
  357.     if (!server->isHostWhitelistEnabled)
  358.     {
  359.         return true;
  360.     }
  361.  
  362.     char const* const host = evhttp_find_header(req->input_headers, "Host");
  363.  
  364.     /* No host header, invalid request. */
  365.     if (host == nullptr)
  366.     {
  367.         return false;
  368.     }
  369.  
  370.     /* IP address is always acceptable. */
  371.     if (isIPAddressWithOptionalPort(host))
  372.     {
  373.         return true;
  374.     }
  375.  
  376.     /* Host header might include the port. */
  377.     auto const hostname = std::string(host, strcspn(host, ":"));
  378.  
  379.     /* localhost is always acceptable. */
  380.     if (hostname == "localhost" || hostname == "localhost.")
  381.     {
  382.         return true;
  383.     }
  384.  
  385.     auto const& src = server->hostWhitelist;
  386.     return std::any_of(
  387.         std::begin(src),
  388.         std::end(src),
  389.         [&hostname](auto const& str) { return tr_wildmat(hostname.c_str(), str.c_str()); });
  390. }
  391.  
  392. static bool test_session_id(tr_rpc_server const* server, evhttp_request const* req)
  393. {
  394.     char const* const session_id = evhttp_find_header(req->input_headers, TR_RPC_SESSION_ID_HEADER);
  395.     return session_id != nullptr && server->session->sessionId() == session_id;
  396. }
  397.  
  398. static bool isAuthorized(tr_rpc_server const* server, char const* auth_header)
  399. {
  400.     if (!server->isPasswordEnabled())
  401.     {
  402.         return true;
  403.     }
  404.  
  405.     // https://datatracker.ietf.org/doc/html/rfc7617
  406.     // `Basic ${base64(username)}:${base64(password)}`
  407.  
  408.     auto constexpr Prefix = "Basic "sv;
  409.     auto auth = std::string_view{ auth_header != nullptr ? auth_header : "" };
  410.     if (!tr_strvStartsWith(auth, Prefix))
  411.     {
  412.         return false;
  413.     }
  414.  
  415.     auth.remove_prefix(std::size(Prefix));
  416.     auto const decoded_str = tr_base64_decode(auth);
  417.     auto decoded = std::string_view{ decoded_str };
  418.     auto const username = tr_strvSep(&decoded, ':');
  419.     auto const password = decoded;
  420.     return server->username() == username && tr_ssha1_matches(server->salted_password_, password);
  421. }
  422.  
  423. static void handle_request(struct evhttp_request* req, void* arg)
  424. {
  425.     auto* server = static_cast<tr_rpc_server*>(arg);
  426.  
  427.     if (req != nullptr && req->evcon != nullptr)
  428.     {
  429.         evhttp_add_header(req->output_headers, "Server", MY_REALM);
  430.  
  431.         if (server->isAntiBruteForceEnabled() && server->login_attempts_ >= server->anti_brute_force_limit_)
  432.         {
  433.             send_simple_response(req, 403, "<p>Too many unsuccessful login attempts. Please restart transmission-daemon.</p>");
  434.             return;
  435.         }
  436.  
  437.         if (!isAddressAllowed(server, req->remote_host))
  438.         {
  439.             send_simple_response(
  440.                 req,
  441.                 403,
  442.                 "<p>Unauthorized IP Address.</p>"
  443.                 "<p>Either disable the IP address whitelist or add your address to it.</p>"
  444.                 "<p>If you're editing settings.json, see the 'rpc-whitelist' and 'rpc-whitelist-enabled' entries.</p>"
  445.                 "<p>If you're still using ACLs, use a whitelist instead. See the transmission-daemon manpage for details.</p>");
  446.             return;
  447.         }
  448.  
  449.         evhttp_add_header(req->output_headers, "Access-Control-Allow-Origin", "*");
  450.  
  451.         if (req->type == EVHTTP_REQ_OPTIONS)
  452.         {
  453.             char const* headers = evhttp_find_header(req->input_headers, "Access-Control-Request-Headers");
  454.             if (headers != nullptr)
  455.             {
  456.                 evhttp_add_header(req->output_headers, "Access-Control-Allow-Headers", headers);
  457.             }
  458.  
  459.             evhttp_add_header(req->output_headers, "Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  460.             send_simple_response(req, 200, "");
  461.             return;
  462.         }
  463.  
  464.         if (!isAuthorized(server, evhttp_find_header(req->input_headers, "Authorization")))
  465.         {
  466.             evhttp_add_header(req->output_headers, "WWW-Authenticate", "Basic realm=\"" MY_REALM "\"");
  467.             if (server->isAntiBruteForceEnabled())
  468.             {
  469.                 ++server->login_attempts_;
  470.             }
  471.  
  472.             auto const unauthuser = fmt::format(
  473.                 FMT_STRING("<p>Unauthorized User. {:d} unsuccessful login attempts.</p>"),
  474.                 server->login_attempts_);
  475.             send_simple_response(req, 401, unauthuser.c_str());
  476.             return;
  477.         }
  478.  
  479.         server->login_attempts_ = 0;
  480.  
  481.         auto uri = std::string_view{ req->uri };
  482.         auto const location = tr_strvStartsWith(uri, server->url()) ? uri.substr(std::size(server->url())) : ""sv;
  483.  
  484.         if (std::empty(location) || location == "web"sv)
  485.         {
  486.             auto const new_location = fmt::format(FMT_STRING("{:s}web/"), server->url());
  487.             evhttp_add_header(req->output_headers, "Location", new_location.c_str());
  488.             send_simple_response(req, HTTP_MOVEPERM, nullptr);
  489.         }
  490.         else if (tr_strvStartsWith(location, "web/"sv))
  491.         {
  492.             handle_web_client(req, server);
  493.         }
  494.         else if (!isHostnameAllowed(server, req))
  495.         {
  496.             char const* const tmp =
  497.                 "<p>Transmission received your request, but the hostname was unrecognized.</p>"
  498.                 "<p>To fix this, choose one of the following options:"
  499.                 "<ul>"
  500.                 "<li>Enable password authentication, then any hostname is allowed.</li>"
  501.                 "<li>Add the hostname you want to use to the whitelist in settings.</li>"
  502.                 "</ul></p>"
  503.                 "<p>If you're editing settings.json, see the 'rpc-host-whitelist' and 'rpc-host-whitelist-enabled' entries.</p>"
  504.                 "<p>This requirement has been added to help prevent "
  505.                 "<a href=\"https://en.wikipedia.org/wiki/DNS_rebinding\">DNS Rebinding</a> "
  506.                 "attacks.</p>";
  507.             send_simple_response(req, 421, tmp);
  508.         }
  509. /*
  510. #ifdef REQUIRE_SESSION_ID
  511.         else if (!test_session_id(server, req))
  512.         {
  513.             auto const session_id = std::string{ server->session->sessionId() };
  514.             auto const tmp = fmt::format(
  515.                 FMT_STRING("<p>Your request had an invalid session-id header.</p>"
  516.                            "<p>To fix this, follow these steps:"
  517.                            "<ol><li> When reading a response, get its X-Transmission-Session-Id header and remember it"
  518.                            "<li> Add the updated header to your outgoing requests"
  519.                            "<li> When you get this 409 error message, resend your request with the updated header"
  520.                            "</ol></p>"
  521.                            "<p>This requirement has been added to help prevent "
  522.                            "<a href=\"https://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
  523.                            "attacks.</p>"
  524.                            "<p><code>{:s}: {:s}</code></p>"),
  525.                 TR_RPC_SESSION_ID_HEADER,
  526.                 session_id);
  527.             evhttp_add_header(req->output_headers, TR_RPC_SESSION_ID_HEADER, session_id.c_str());
  528.             evhttp_add_header(req->output_headers, "Access-Control-Expose-Headers", TR_RPC_SESSION_ID_HEADER);
  529.             send_simple_response(req, 409, tmp.c_str());
  530.         }
  531. #endif
  532.         else if (tr_strvStartsWith(location, "rpc"sv))
  533.         {
  534.             handle_rpc(req, server);
  535.         }
  536.         else
  537.         {
  538.             send_simple_response(req, HTTP_NOTFOUND, req->uri);
  539.         }
  540.     }
  541.     */
  542. }
  543.  
  544. static auto constexpr ServerStartRetryCount = int{ 10 };
  545. static auto constexpr ServerStartRetryDelayIncrement = 5s;
  546. static auto constexpr ServerStartRetryMaxDelay = 60s;
  547.  
  548. static char const* tr_rpc_address_to_string(tr_rpc_address const& addr, char* buf, size_t buflen)
  549. {
  550.     TR_ASSERT(tr_rpc_address_is_valid(addr));
  551.  
  552.     switch (addr.type)
  553.     {
  554.     case TR_RPC_AF_INET:
  555.         return evutil_inet_ntop(AF_INET, &addr.addr, buf, buflen);
  556.  
  557.     case TR_RPC_AF_INET6:
  558.         return evutil_inet_ntop(AF_INET6, &addr.addr, buf, buflen);
  559.  
  560.     case TR_RPC_AF_UNIX:
  561.         tr_strlcpy(buf, std::data(addr.addr.unixSocketPath), buflen);
  562.         return buf;
  563.  
  564.     default:
  565.         return nullptr;
  566.     }
  567. }
  568.  
  569. static std::string tr_rpc_address_with_port(tr_rpc_server const* server)
  570. {
  571.     auto addr_buf = std::array<char, TrUnixAddrStrLen>{};
  572.     tr_rpc_address_to_string(*server->bindAddress, std::data(addr_buf), std::size(addr_buf));
  573.  
  574.     std::string addr_port_str = std::data(addr_buf);
  575.     if (server->bindAddress->type != TR_RPC_AF_UNIX)
  576.     {
  577.         addr_port_str.append(":" + std::to_string(server->port().host()));
  578.     }
  579.     return addr_port_str;
  580. }
  581.  
  582. static bool tr_rpc_address_from_string(tr_rpc_address& dst, std::string_view src)
  583. {
  584.     if (tr_strvStartsWith(src, TrUnixSocketPrefix))
  585.     {
  586.         if (std::size(src) >= TrUnixAddrStrLen)
  587.         {
  588.             tr_logAddError(fmt::format(
  589.                 _("Unix socket path must be fewer than {count} characters (including '{prefix}' prefix)"),
  590.                 fmt::arg("count", TrUnixAddrStrLen - 1),
  591.                 fmt::arg("prefix", TrUnixSocketPrefix)));
  592.             return false;
  593.         }
  594.  
  595.         dst.type = TR_RPC_AF_UNIX;
  596.         tr_strlcpy(std::data(dst.addr.unixSocketPath), std::string{ src }.c_str(), std::size(dst.addr.unixSocketPath));
  597.         return true;
  598.     }
  599.  
  600.     if (evutil_inet_pton(AF_INET, std::string{ src }.c_str(), &dst.addr) == 1)
  601.     {
  602.         dst.type = TR_RPC_AF_INET;
  603.         return true;
  604.     }
  605.  
  606.     if (evutil_inet_pton(AF_INET6, std::string{ src }.c_str(), &dst.addr) == 1)
  607.     {
  608.         dst.type = TR_RPC_AF_INET6;
  609.         return true;
  610.     }
  611.  
  612.     return false;
  613. }
  614.  
  615. static bool bindUnixSocket(
  616.     [[maybe_unused]] struct event_base* base,
  617.     [[maybe_unused]] struct evhttp* httpd,
  618.     [[maybe_unused]] char const* path,
  619.     [[maybe_unused]] int socket_mode)
  620. {
  621. #ifdef _WIN32
  622.     tr_logAddError(fmt::format(
  623.         _("Unix sockets are unsupported on Windows. Please change '{key}' in your settings."),
  624.         fmt::arg("key", tr_quark_get_string_view(TR_KEY_rpc_bind_address))));
  625.     return false;
  626. #else
  627.     auto addr = sockaddr_un{};
  628.     addr.sun_family = AF_UNIX;
  629.     tr_strlcpy(addr.sun_path, path + std::size(TrUnixSocketPrefix), sizeof(addr.sun_path));
  630.  
  631.     unlink(addr.sun_path);
  632.  
  633.     struct evconnlistener* lev = evconnlistener_new_bind(
  634.         base,
  635.         nullptr,
  636.         nullptr,
  637.         LEV_OPT_CLOSE_ON_FREE,
  638.         -1,
  639.         reinterpret_cast<sockaddr const*>(&addr),
  640.         sizeof(addr));
  641.  
  642.     if (lev == nullptr)
  643.     {
  644.         return false;
  645.     }
  646.  
  647.     if (chmod(addr.sun_path, (mode_t)socket_mode) != 0)
  648.     {
  649.         tr_logAddWarn(
  650.             fmt::format(_("Couldn't set RPC socket mode to {mode:#o}, defaulting to 0755"), fmt::arg("mode", socket_mode)));
  651.     }
  652.  
  653.     return evhttp_bind_listener(httpd, lev) != nullptr;
  654. #endif
  655. }
  656.  
  657. static void startServer(tr_rpc_server* server);
  658.  
  659. static auto rpc_server_start_retry(tr_rpc_server* server)
  660. {
  661.     if (!server->start_retry_timer)
  662.     {
  663.         server->start_retry_timer = server->session->timerMaker().create([server]() { startServer(server); });
  664.     }
  665.  
  666.     ++server->start_retry_counter;
  667.     auto const interval = std::min(ServerStartRetryDelayIncrement * server->start_retry_counter, ServerStartRetryMaxDelay);
  668.     server->start_retry_timer->startSingleShot(std::chrono::duration_cast<std::chrono::milliseconds>(interval));
  669.     return interval;
  670. }
  671.  
  672. static void rpc_server_start_retry_cancel(tr_rpc_server* server)
  673. {
  674.     server->start_retry_timer.reset();
  675.     server->start_retry_counter = 0;
  676. }
  677.  
  678. static void startServer(tr_rpc_server* server)
  679. {
  680.     if (server->httpd != nullptr)
  681.     {
  682.         return;
  683.     }
  684.  
  685.     struct event_base* base = server->session->eventBase();
  686.     struct evhttp* httpd = evhttp_new(base);
  687.  
  688.     evhttp_set_allowed_methods(httpd, EVHTTP_REQ_GET | EVHTTP_REQ_POST | EVHTTP_REQ_OPTIONS);
  689.  
  690.     auto const address = server->getBindAddress();
  691.     auto const port = server->port();
  692.  
  693.     bool const success = server->bindAddress->type == TR_RPC_AF_UNIX ?
  694.         bindUnixSocket(base, httpd, address.c_str(), server->socket_mode_) :
  695.         (evhttp_bind_socket(httpd, address.c_str(), port.host()) != -1);
  696.  
  697.     auto const addr_port_str = tr_rpc_address_with_port(server);
  698.  
  699.     if (!success)
  700.     {
  701.         evhttp_free(httpd);
  702.  
  703.         if (server->start_retry_counter < ServerStartRetryCount)
  704.         {
  705.             auto const retry_delay = rpc_server_start_retry(server);
  706.             auto const seconds = std::chrono::duration_cast<std::chrono::seconds>(retry_delay).count();
  707.             tr_logAddDebug(fmt::format("Couldn't bind to {}, retrying in {} seconds", addr_port_str, seconds));
  708.             return;
  709.         }
  710.  
  711.         tr_logAddError(fmt::format(
  712.             ngettext(
  713.                 "Couldn't bind to {address} after {count} attempt, giving up",
  714.                 "Couldn't bind to {address} after {count} attempts, giving up",
  715.                 ServerStartRetryCount),
  716.             fmt::arg("address", addr_port_str),
  717.             fmt::arg("count", ServerStartRetryCount)));
  718.     }
  719.     else
  720.     {
  721.         evhttp_set_gencb(httpd, handle_request, server);
  722.         server->httpd = httpd;
  723.  
  724.         tr_logAddInfo(fmt::format(_("Listening for RPC and Web requests on '{address}'"), fmt::arg("address", addr_port_str)));
  725.     }
  726.  
  727.     rpc_server_start_retry_cancel(server);
  728. }
  729.  
  730. static void stopServer(tr_rpc_server* server)
  731. {
  732.     auto const lock = server->session->unique_lock();
  733.  
  734.     rpc_server_start_retry_cancel(server);
  735.  
  736.     struct evhttp* httpd = server->httpd;
  737.  
  738.     if (httpd == nullptr)
  739.     {
  740.         return;
  741.     }
  742.  
  743.     auto const address = server->getBindAddress();
  744.  
  745.     server->httpd = nullptr;
  746.     evhttp_free(httpd);
  747.  
  748.     if (server->bindAddress->type == TR_RPC_AF_UNIX)
  749.     {
  750.         unlink(address.c_str() + std::size(TrUnixSocketPrefix));
  751.     }
  752.  
  753.     tr_logAddInfo(fmt::format(
  754.         _("Stopped listening for RPC and Web requests on '{address}'"),
  755.         fmt::arg("address", tr_rpc_address_with_port(server))));
  756. }
  757.  
  758. void tr_rpc_server::setEnabled(bool is_enabled)
  759. {
  760.     is_enabled_ = is_enabled;
  761.  
  762.     tr_runInEventThread(
  763.         this->session,
  764.         [this]()
  765.         {
  766.             if (!is_enabled_)
  767.             {
  768.                 stopServer(this);
  769.             }
  770.             else
  771.             {
  772.                 startServer(this);
  773.             }
  774.         });
  775. }
  776.  
  777. static void restartServer(tr_rpc_server* const server)
  778. {
  779.     if (server->isEnabled())
  780.     {
  781.         stopServer(server);
  782.         startServer(server);
  783.     }
  784. }
  785.  
  786. void tr_rpc_server::setPort(tr_port port) noexcept
  787. {
  788.     if (port_ == port)
  789.     {
  790.         return;
  791.     }
  792.  
  793.     port_ = port;
  794.  
  795.     if (isEnabled())
  796.     {
  797.         tr_runInEventThread(session, restartServer, this);
  798.     }
  799. }
  800.  
  801. void tr_rpc_server::setUrl(std::string_view url)
  802. {
  803.     url_ = url;
  804.     tr_logAddDebug(fmt::format(FMT_STRING("setting our URL to '{:s}'"), url_));
  805. }
  806.  
  807. static auto parseWhitelist(std::string_view whitelist)
  808. {
  809.     auto list = std::vector<std::string>{};
  810.  
  811.     while (!std::empty(whitelist))
  812.     {
  813.         auto const pos = whitelist.find_first_of(" ,;"sv);
  814.         auto const token = tr_strvStrip(whitelist.substr(0, pos));
  815.         list.emplace_back(token);
  816.         whitelist = pos == std::string_view::npos ? ""sv : whitelist.substr(pos + 1);
  817.  
  818.         if (token.find_first_of("+-"sv) != std::string_view::npos)
  819.         {
  820.             tr_logAddWarn(fmt::format(
  821.                 _("Added '{entry}' to host whitelist and it has a '+' or '-'! Are you using an old ACL by mistake?"),
  822.                 fmt::arg("entry", token)));
  823.         }
  824.         else
  825.         {
  826.             tr_logAddInfo(fmt::format(_("Added '{entry}' to host whitelist"), fmt::arg("entry", token)));
  827.         }
  828.     }
  829.  
  830.     return list;
  831. }
  832.  
  833. void tr_rpc_server::setWhitelist(std::string_view whitelist)
  834. {
  835.     this->whitelist_str_ = whitelist;
  836.     this->whitelist_ = parseWhitelist(whitelist);
  837. }
  838.  
  839. /****
  840. *****  PASSWORD
  841. ****/
  842.  
  843. void tr_rpc_server::setUsername(std::string_view username)
  844. {
  845.     username_ = username;
  846.     tr_logAddDebug(fmt::format(FMT_STRING("setting our username to '{:s}'"), username_));
  847. }
  848.  
  849. static bool isSalted(std::string_view password)
  850. {
  851.     return tr_ssha1_test(password);
  852. }
  853.  
  854. void tr_rpc_server::setPassword(std::string_view password) noexcept
  855. {
  856.     salted_password_ = isSalted(password) ? password : tr_ssha1(password);
  857.  
  858.     tr_logAddDebug(fmt::format(FMT_STRING("setting our salted password to '{:s}'"), salted_password_));
  859. }
  860.  
  861. void tr_rpc_server::setPasswordEnabled(bool enabled)
  862. {
  863.     is_password_enabled_ = enabled;
  864.     tr_logAddDebug(fmt::format("setting password-enabled to '{}'", enabled));
  865. }
  866.  
  867. std::string tr_rpc_server::getBindAddress() const
  868. {
  869.     auto buf = std::array<char, TrUnixAddrStrLen>{};
  870.     return tr_rpc_address_to_string(*this->bindAddress, std::data(buf), std::size(buf));
  871. }
  872.  
  873. void tr_rpc_server::setAntiBruteForceEnabled(bool enabled) noexcept
  874. {
  875.     is_anti_brute_force_enabled_ = enabled;
  876.  
  877.     if (!enabled)
  878.     {
  879.         login_attempts_ = 0;
  880.     }
  881. }
  882.  
  883. /****
  884. *****  LIFE CYCLE
  885. ****/
  886.  
  887. static void missing_settings_key(tr_quark const q)
  888. {
  889.     tr_logAddDebug(fmt::format("Couldn't find settings key '{}'", tr_quark_get_string_view(q)));
  890. }
  891.  
  892. tr_rpc_server::tr_rpc_server(tr_session* session_in, tr_variant* settings)
  893.     : compressor{ libdeflate_alloc_compressor(DeflateLevel), libdeflate_free_compressor }
  894.     , web_client_dir_{ tr_getWebClientDir(session_in) }
  895.     , bindAddress(std::make_unique<struct tr_rpc_address>())
  896.     , session{ session_in }
  897. {
  898.     auto i = int64_t{};
  899.     auto sv = std::string_view{};
  900.  
  901.     auto key = TR_KEY_rpc_enabled;
  902.  
  903.     if (auto val = bool{}; !tr_variantDictFindBool(settings, key, &val))
  904.     {
  905.         missing_settings_key(key);
  906.     }
  907.     else
  908.     {
  909.         this->is_enabled_ = val;
  910.     }
  911.  
  912.     key = TR_KEY_rpc_port;
  913.  
  914.     if (!tr_variantDictFindInt(settings, key, &i))
  915.     {
  916.         missing_settings_key(key);
  917.     }
  918.     else
  919.     {
  920.         this->port_.setHost(i);
  921.     }
  922.  
  923.     key = TR_KEY_rpc_url;
  924.  
  925.     if (!tr_variantDictFindStrView(settings, key, &sv))
  926.     {
  927.         missing_settings_key(key);
  928.     }
  929.     else if (std::empty(sv) || sv.back() != '/')
  930.     {
  931.         this->url_ = fmt::format(FMT_STRING("{:s}/"), sv);
  932.     }
  933.     else
  934.     {
  935.         this->url_ = sv;
  936.     }
  937.  
  938.     key = TR_KEY_rpc_whitelist_enabled;
  939.  
  940.     if (auto val = bool{}; !tr_variantDictFindBool(settings, key, &val))
  941.     {
  942.         missing_settings_key(key);
  943.     }
  944.     else
  945.     {
  946.         this->setWhitelistEnabled(val);
  947.     }
  948.  
  949.     key = TR_KEY_rpc_host_whitelist_enabled;
  950.  
  951.     if (auto val = bool{}; !tr_variantDictFindBool(settings, key, &val))
  952.     {
  953.         missing_settings_key(key);
  954.     }
  955.     else
  956.     {
  957.         this->isHostWhitelistEnabled = val;
  958.     }
  959.  
  960.     key = TR_KEY_rpc_host_whitelist;
  961.  
  962.     if (!tr_variantDictFindStrView(settings, key, &sv))
  963.     {
  964.         missing_settings_key(key);
  965.     }
  966.     else if (!std::empty(sv))
  967.     {
  968.         this->hostWhitelist = parseWhitelist(sv);
  969.     }
  970.  
  971.     key = TR_KEY_rpc_authentication_required;
  972.  
  973.     if (auto val = bool{}; !tr_variantDictFindBool(settings, key, &val))
  974.     {
  975.         missing_settings_key(key);
  976.     }
  977.     else
  978.     {
  979.         this->setPasswordEnabled(val);
  980.     }
  981.  
  982.     key = TR_KEY_rpc_whitelist;
  983.  
  984.     if (!tr_variantDictFindStrView(settings, key, &sv))
  985.     {
  986.         missing_settings_key(key);
  987.     }
  988.     else if (!std::empty(sv))
  989.     {
  990.         this->setWhitelist(sv);
  991.     }
  992.  
  993.     key = TR_KEY_rpc_username;
  994.  
  995.     if (!tr_variantDictFindStrView(settings, key, &sv))
  996.     {
  997.         missing_settings_key(key);
  998.     }
  999.     else
  1000.     {
  1001.         this->setUsername(sv);
  1002.     }
  1003.  
  1004.     key = TR_KEY_rpc_password;
  1005.  
  1006.     if (!tr_variantDictFindStrView(settings, key, &sv))
  1007.     {
  1008.         missing_settings_key(key);
  1009.     }
  1010.     else
  1011.     {
  1012.         this->setPassword(sv);
  1013.     }
  1014.  
  1015.     key = TR_KEY_anti_brute_force_enabled;
  1016.  
  1017.     if (auto val = bool{}; !tr_variantDictFindBool(settings, key, &val))
  1018.     {
  1019.         missing_settings_key(key);
  1020.     }
  1021.     else
  1022.     {
  1023.         this->setAntiBruteForceEnabled(val);
  1024.     }
  1025.  
  1026.     key = TR_KEY_anti_brute_force_threshold;
  1027.  
  1028.     if (!tr_variantDictFindInt(settings, key, &i))
  1029.     {
  1030.         missing_settings_key(key);
  1031.     }
  1032.     else
  1033.     {
  1034.         this->setAntiBruteForceLimit(i);
  1035.     }
  1036.  
  1037.     key = TR_KEY_rpc_socket_mode;
  1038.     bool is_missing_rpc_socket_mode_key = true;
  1039.  
  1040.     if (tr_variantDictFindStrView(settings, key, &sv))
  1041.     {
  1042.         /* Read the socket permission as a string representing an octal number. */
  1043.         is_missing_rpc_socket_mode_key = false;
  1044.         i = tr_parseNum<int>(sv, nullptr, 8).value_or(tr_rpc_server::DefaultRpcSocketMode);
  1045.     }
  1046.     else if (tr_variantDictFindInt(settings, key, &i))
  1047.     {
  1048.         /* Or as a base 10 integer to remain compatible with the old settings format. */
  1049.         is_missing_rpc_socket_mode_key = false;
  1050.     }
  1051.     if (is_missing_rpc_socket_mode_key)
  1052.     {
  1053.         missing_settings_key(key);
  1054.     }
  1055.     else
  1056.     {
  1057.         this->socket_mode_ = i;
  1058.     }
  1059.  
  1060.     key = TR_KEY_rpc_bind_address;
  1061.  
  1062.     if (!tr_variantDictFindStrView(settings, key, &sv))
  1063.     {
  1064.         missing_settings_key(key);
  1065.         bindAddress->set_inaddr_any();
  1066.     }
  1067.     else if (!tr_rpc_address_from_string(*bindAddress, sv))
  1068.     {
  1069.         tr_logAddWarn(fmt::format(
  1070.             _("The '{key}' setting is '{value}' but must be an IPv4 or IPv6 address or a Unix socket path. Using default value '0.0.0.0'"),
  1071.             fmt::format("key", tr_quark_get_string_view(key)),
  1072.             fmt::format("value", sv)));
  1073.         bindAddress->set_inaddr_any();
  1074.     }
  1075.  
  1076.     if (bindAddress->type == TR_RPC_AF_UNIX)
  1077.     {
  1078.         this->setWhitelistEnabled(false);
  1079.         this->isHostWhitelistEnabled = false;
  1080.     }
  1081.  
  1082.     if (this->isEnabled())
  1083.     {
  1084.         auto const rpc_uri = tr_rpc_address_with_port(this) + this->url_;
  1085.         tr_logAddInfo(fmt::format(_("Serving RPC and Web requests on {address}"), fmt::arg("address", rpc_uri)));
  1086.         tr_runInEventThread(session, startServer, this);
  1087.  
  1088.         if (this->isWhitelistEnabled())
  1089.         {
  1090.             tr_logAddInfo(_("Whitelist enabled"));
  1091.         }
  1092.  
  1093.         if (this->isPasswordEnabled())
  1094.         {
  1095.             tr_logAddInfo(_("Password required"));
  1096.         }
  1097.     }
  1098.  
  1099.     if (!std::empty(web_client_dir_))
  1100.     {
  1101.         tr_logAddInfo(fmt::format(_("Serving RPC and Web requests from '{path}'"), fmt::arg("path", web_client_dir_)));
  1102.     }
  1103. }
  1104.  
  1105. tr_rpc_server::~tr_rpc_server()
  1106. {
  1107.     stopServer(this);
  1108. }
Add Comment
Please, Sign In to add comment