diff --git a/libtransmission/quark.c b/libtransmission/quark.c index 861050057..e19ac9a2f 100644 --- a/libtransmission/quark.c +++ b/libtransmission/quark.c @@ -288,6 +288,8 @@ static struct tr_key_struct const my_static[] = { "rpc-authentication-required", 27 }, { "rpc-bind-address", 16 }, { "rpc-enabled", 11 }, + { "rpc-host-whitelist", 18 }, + { "rpc-host-whitelist-enabled", 26 }, { "rpc-password", 12 }, { "rpc-port", 8 }, { "rpc-url", 7 }, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index d40ab75fa..9dc534560 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -290,6 +290,8 @@ enum TR_KEY_rpc_authentication_required, TR_KEY_rpc_bind_address, TR_KEY_rpc_enabled, + TR_KEY_rpc_host_whitelist, + TR_KEY_rpc_host_whitelist_enabled, TR_KEY_rpc_password, TR_KEY_rpc_port, TR_KEY_rpc_url, diff --git a/libtransmission/rpc-server.c b/libtransmission/rpc-server.c index 7c78f92ac..c4db2e64c 100644 --- a/libtransmission/rpc-server.c +++ b/libtransmission/rpc-server.c @@ -51,6 +51,7 @@ struct tr_rpc_server bool isEnabled; bool isPasswordEnabled; bool isWhitelistEnabled; + bool isHostWhitelistEnabled; tr_port port; char * url; struct in_addr bindAddress; @@ -62,6 +63,7 @@ struct tr_rpc_server char * password; char * whitelistStr; tr_list * whitelist; + tr_list* hostWhitelist; char * sessionId; time_t sessionIdExpiresAt; @@ -547,6 +549,48 @@ static bool isAddressAllowed(tr_rpc_server const* server, char const* address) return false; } +static bool isHostnameAllowed(tr_rpc_server const* server, struct evhttp_request* req) +{ + const char *host = evhttp_find_header(req->input_headers, "Host"); + char *hostname; + + // 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; + + // No host header, invalid request. + if (!host) + return false; + + // Host header might include the port. + 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) { @@ -636,6 +680,22 @@ static void handle_request(struct evhttp_request* req, void* arg) handle_upload (req, server); } #ifdef REQUIRE_SESSION_ID + else if (!isHostnameAllowed(server, req)) + { + char* tmp = tr_strdup_printf( + "

Transmission received your request, but the hostname was unrecognized.

" + "

To fix this, choose one of the following options:" + "

" + "

If you're editing settings.json, see the 'rpc-host-whitelist' and 'rpc-host-whitelist-enabled' entries.

" + "

This requirement has been added to help prevent " + "DNS Rebinding " + "attacks.

"); + send_simple_response(req, 421, tmp); + tr_free(tmp); + } else if (!test_session_id (server, req)) { const char * sessionId = get_current_session_id (server); @@ -647,7 +707,7 @@ static void handle_request(struct evhttp_request* req, void* arg) "
  • When you get this 409 error message, resend your request with the updated header" "

    " "

    This requirement has been added to help prevent " - "CSRF " + "CSRF " "attacks.

    " "

    %s: %s

    ", TR_RPC_SESSION_ID_HEADER, sessionId); @@ -875,19 +875,13 @@ char const* tr_rpcGetUrl(tr_rpc_server const* server) return server->url ? server->url : ""; } -void -tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr) +static void tr_rpcSetList(char const* whitelistStr, tr_list** list) { void * tmp; const char * walk; - /* 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))) + while ((tmp = tr_list_pop_front(list)) != NULL) tr_free (tmp); /* build the new whitelist entries */ @@ -866,7 +921,7 @@ void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr) const char * delimiters = " ,;"; const size_t 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) tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'! Are you using an old ACL by mistake?)", token); else @@ -889,6 +944,22 @@ void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr) } } +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); +} + const char* tr_rpcGetWhitelist (const tr_rpc_server * server) { @@ -904,6 +975,11 @@ bool tr_rpcGetWhitelistEnabled(tr_rpc_server const* server) return server->isWhitelistEnabled; } +void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled) +{ + server->isHostWhitelistEnabled = isEnabled; +} + /**** ***** PASSWORD ****/ @@ -1054,6 +1130,28 @@ tr_rpc_server* tr_rpcInit(tr_session* session, tr_variant* settings) else 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)) missing_settings_key (key); diff --git a/libtransmission/rpc-server.h b/libtransmission/rpc-server.h index 46e8a871f..ad1eb5204 100644 --- a/libtransmission/rpc-server.h +++ b/libtransmission/rpc-server.h @@ -49,6 +49,10 @@ void tr_rpcSetWhitelist (tr_rpc_server * server, const char* tr_rpcGetWhitelist (const tr_rpc_server * server); +void tr_rpcSetHostWhitelistEnabled (tr_rpc_server * server, bool isEnabled); + +void tr_rpcSetHostWhitelist (tr_rpc_server * server, char const * whitelist); + void tr_rpcSetPassword (tr_rpc_server * server, const char * password); diff --git a/libtransmission/session.c b/libtransmission/session.c index 86d054f7f..9a0b7c104 100644 --- a/libtransmission/session.c +++ b/libtransmission/session.c @@ -371,6 +371,8 @@ void tr_sessionGetDefaultSettings(tr_variant* d) tr_variantDictAddStr (d, TR_KEY_rpc_username, ""); tr_variantDictAddStr (d, TR_KEY_rpc_whitelist, TR_DEFAULT_RPC_WHITELIST); tr_variantDictAddBool (d, TR_KEY_rpc_whitelist_enabled, true); + tr_variantDictAddStr (d, TR_KEY_rpc_host_whitelist, TR_DEFAULT_RPC_HOST_WHITELIST); + tr_variantDictAddBool (d, TR_KEY_rpc_host_whitelist_enabled, true); tr_variantDictAddInt (d, TR_KEY_rpc_port, atoi (TR_DEFAULT_RPC_PORT_STR)); tr_variantDictAddStr (d, TR_KEY_rpc_url, TR_DEFAULT_RPC_URL_STR); tr_variantDictAddBool (d, TR_KEY_scrape_paused_torrents_enabled, true); diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index ac1871adb..08a24eca4 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -109,6 +109,7 @@ char const* tr_getDefaultDownloadDir(void); #define TR_DEFAULT_BIND_ADDRESS_IPV4 "0.0.0.0" #define TR_DEFAULT_BIND_ADDRESS_IPV6 "::" #define TR_DEFAULT_RPC_WHITELIST "127.0.0.1" +#define TR_DEFAULT_RPC_HOST_WHITELIST "" #define TR_DEFAULT_RPC_PORT_STR "9091" #define TR_DEFAULT_RPC_URL_STR "/transmission/" #define TR_DEFAULT_PEER_PORT_STR "51413" diff --git a/libtransmission/web.c b/libtransmission/web.c index cca888b66..a57c85457 100644 --- a/libtransmission/web.c +++ b/libtransmission/web.c @@ -678,6 +678,7 @@ char const* tr_webGetResponseStr(long code) case 415: return "Unsupported Media Type"; case 416: return "Requested Range Not Satisfiable"; case 417: return "Expectation Failed"; + case 421: return "Misdirected Request"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway";