bool isEnabled;
bool isPasswordEnabled;
bool isWhitelistEnabled;
+ bool isHostWhitelistEnabled;
tr_port port;
char* url;
struct tr_address bindAddress;
char* password;
char* whitelistStr;
tr_list* whitelist;
+ tr_list* hostWhitelist;
int loginattempts;
bool isStreamInitialized;
return false;
}
+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. */
+ if (host == NULL)
+ {
+ return false;
+ }
+
+ /* Host header might include the port. */
+ char* const hostname = tr_strndup(host, strcspn(host, ":"));
+
+ /* localhost or ipaddress is always acceptable. */
+ if (strcmp(hostname, "localhost") == 0 || strcmp(hostname, "localhost.") == 0 || tr_addressIsIP(hostname))
+ {
+ tr_free(hostname);
+ return true;
+ }
+
+ /* Otherwise, hostname must be whitelisted. */
+ for (tr_list* l = server->hostWhitelist; l != NULL; l = l->next)
+ {
+ if (tr_wildmat(hostname, l->data))
+ {
+ tr_free(hostname);
+ return true;
+ }
+ }
+
+ tr_free(hostname);
+ return false;
+}
+
static bool test_session_id(struct tr_rpc_server* server, struct evhttp_request* req)
{
char const* ours = get_current_session_id(server);
#ifdef REQUIRE_SESSION_ID
+ 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);
+ }
else if (!test_session_id(server, req))
{
char const* sessionId = get_current_session_id(server);
"<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 "
- "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
+ "<a href=\"https://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
"attacks.</p>"
"<p><code>%s: %s</code></p>",
TR_RPC_SESSION_ID_HEADER, sessionId);
return server->url != NULL ? server->url : "";
}
-void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr)
+static void tr_rpcSetList(char const* whitelistStr, tr_list** list)
{
void* tmp;
- /* keep the string */
- tmp = server->whitelistStr;
- server->whitelistStr = tr_strdup(whitelistStr);
- tr_free(tmp);
-
/* clear out the old whitelist entries */
- while ((tmp = tr_list_pop_front(&server->whitelist)) != NULL)
+ while ((tmp = tr_list_pop_front(list)) != NULL)
{
tr_free(tmp);
}
size_t const len = strcspn(walk, delimiters);
char* token = tr_strndup(walk, len);
- tr_list_append(&server->whitelist, token);
+ tr_list_append(list, token);
if (strcspn(token, "+-") < len)
{
}
}
+void tr_rpcSetHostWhitelist(tr_rpc_server* server, char const* whitelistStr)
+{
+ tr_rpcSetList(whitelistStr, &server->hostWhitelist);
+}
+
+void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr)
+{
+ /* keep the string */
+ char* const tmp = server->whitelistStr;
+ server->whitelistStr = tr_strdup(whitelistStr);
+ tr_free(tmp);
+
+ tr_rpcSetList(whitelistStr, &server->whitelist);
+}
+
char const* tr_rpcGetWhitelist(tr_rpc_server const* server)
{
return server->whitelistStr != NULL ? server->whitelistStr : "";
return server->isWhitelistEnabled;
}
+void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled)
+{
+ server->isHostWhitelistEnabled = isEnabled;
+}
+
/****
***** PASSWORD
****/
tr_rpcSetWhitelistEnabled(s, boolVal);
}
+ key = TR_KEY_rpc_host_whitelist_enabled;
+
+ if (!tr_variantDictFindBool(settings, key, &boolVal))
+ {
+ missing_settings_key(key);
+ }
+ else
+ {
+ tr_rpcSetHostWhitelistEnabled(s, boolVal);
+ }
+
+ key = TR_KEY_rpc_host_whitelist;
+
+ if (!tr_variantDictFindStr(settings, key, &str, NULL) && str != NULL)
+ {
+ missing_settings_key(key);
+ }
+ else
+ {
+ tr_rpcSetHostWhitelist(s, str);
+ }
+
key = TR_KEY_rpc_authentication_required;
if (!tr_variantDictFindBool(settings, key, &boolVal))