diff --git a/include/struct.h b/include/struct.h index 7523d08a4..5f7d4aa5b 100644 --- a/include/struct.h +++ b/include/struct.h @@ -1800,6 +1800,16 @@ typedef enum TransferEncoding { TRANSFER_ENCODING_CHUNKED=1 } TransferEncoding; +/* Used to parse http Forwarded header (RFC 7239)... */ +#define IPLEN 48 +typedef struct HTTPForwardedHeader HTTPForwardedHeader; +struct HTTPForwardedHeader +{ + int secure; + char hostname[HOSTLEN+1]; + char ip[IPLEN+1]; +}; + typedef struct WebRequest WebRequest; struct WebRequest { HttpMethod method; /**< GET/PUT/POST */ @@ -1817,6 +1827,7 @@ struct WebRequest { long long chunk_remaining; TransferEncoding transfer_encoding; long long config_max_request_buffer_size; /**< CONFIG: Maximum request length allowed */ + HTTPForwardedHeader *forwarded; /**< If using a proxy */ }; typedef struct WebServer WebServer; diff --git a/src/modules/webserver.c b/src/modules/webserver.c index 008c69a5e..f181f285d 100644 --- a/src/modules/webserver.c +++ b/src/modules/webserver.c @@ -32,21 +32,10 @@ ModuleHeader MOD_HEADER #define reset_handshake_timeout(client, delta) do { client->local->creationtime = TStime() - iConf.handshake_timeout + delta; } while(0) #define WSU(client) ((WebSocketUser *)moddata_client(client, websocket_md).ptr) -/* Used to parse http Forwarded header (RFC 7239)... */ -#define IPLEN 48 -#define FHEADER_NAMELEN 20 - -struct HTTPForwardedHeader -{ - int secure; - char hostname[HOSTLEN+1]; - char ip[IPLEN+1]; -}; - /* Forward declarations */ int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length); int webserver_packet_in(Client *client, const char *readbuf, int *length); -void webserver_mdata_free(ModData *m); +void webserver_webrequest_mdata_free(ModData *m); int webserver_handle_packet(Client *client, const char *readbuf, int length); int webserver_handle_handshake(Client *client, const char *readbuf, int *length); int webserver_handle_request_header(Client *client, const char *readbuf, int *length); @@ -81,7 +70,7 @@ MOD_INIT() mreq.name = "web"; mreq.serialize = NULL; mreq.unserialize = NULL; - mreq.free = webserver_mdata_free; + mreq.free = webserver_webrequest_mdata_free; mreq.sync = 0; mreq.type = MODDATATYPE_CLIENT; webserver_md = ModDataAdd(modinfo->handle, mreq); @@ -102,7 +91,7 @@ MOD_UNLOAD() } /** UnrealIRCd internals: free WebRequest object. */ -void webserver_mdata_free(ModData *m) +void webserver_webrequest_mdata_free(ModData *m) { WebRequest *wsu = (WebRequest *)m->ptr; if (wsu) @@ -111,6 +100,7 @@ void webserver_mdata_free(ModData *m) free_nvplist(wsu->headers); safe_free(wsu->lefttoparse); safe_free(wsu->request_buffer); + safe_free(wsu->forwarded); safe_free(m->ptr); } } @@ -694,182 +684,74 @@ int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, return 1; } -#define FHEADER_STATE_NAME 0 -#define FHEADER_STATE_VALUE 1 -#define FHEADER_STATE_VALUE_QUOTED 2 - -#define FHEADER_ACTION_APPEND 0 -#define FHEADER_ACTION_IGNORE 1 -#define FHEADER_ACTION_PROCESS 2 - /** If a valid Forwarded: http header is received from a trusted source (proxy server), this function will * extract remote IP address and secure (https) status from it. If more than one field with same name is received, * we'll accept the last one. This should work correctly with chained proxies. */ -struct HTTPForwardedHeader *do_parse_forwarded_header(const char *input) +void do_parse_forwarded_header(const char *input, HTTPForwardedHeader *forwarded) { - static struct HTTPForwardedHeader forwarded; - int i, length; - int state = FHEADER_STATE_NAME, action = FHEADER_ACTION_APPEND; - char name[FHEADER_NAMELEN+1]; - char value[IPLEN+1]; - int name_length = 0; - int value_length = 0; - char c; - - memset(&forwarded, 0, sizeof(struct HTTPForwardedHeader)); - - length = strlen(input); - for (i = 0; i < length; i++) + char buf[512]; + char *name, *value, *p = NULL; + + memset(forwarded, 0, sizeof(HTTPForwardedHeader)); + strlcpy(buf, input, sizeof(buf)); + + for (name = strtoken(&p, buf, ";"); name; name = strtoken(&p, NULL, ";")) { - c = input[i]; - switch (c) + value = strchr(name, '='); + if (value) + *value++ = '\0'; + if (!value) + continue; /* we don't use value-less items atm anyway */ + if (!strcmp(name, "for")) { - case '"': - switch (state) - { - case FHEADER_STATE_NAME: - action = FHEADER_ACTION_APPEND; - break; - case FHEADER_STATE_VALUE: - action = FHEADER_ACTION_IGNORE; - state = FHEADER_STATE_VALUE_QUOTED; - break; - case FHEADER_STATE_VALUE_QUOTED: - action = FHEADER_ACTION_IGNORE; - state = FHEADER_STATE_VALUE; - break; - } - break; - case ',': case ';': case ' ': - switch (state) - { - case FHEADER_STATE_NAME: /* name without value */ - name_length = 0; - action = FHEADER_ACTION_IGNORE; - break; - case FHEADER_STATE_VALUE: /* end of value */ - action = FHEADER_ACTION_PROCESS; - break; - case FHEADER_STATE_VALUE_QUOTED: /* quoted character, process as normal */ - action = FHEADER_ACTION_APPEND; - break; - } - break; - case '=': - switch (state) - { - case FHEADER_STATE_NAME: /* end of name */ - name[name_length] = '\0'; - state = FHEADER_STATE_VALUE; - action = FHEADER_ACTION_IGNORE; - break; - case FHEADER_STATE_VALUE: case FHEADER_STATE_VALUE_QUOTED: /* none of the values is expected to contain = but proceed anyway */ - action = FHEADER_ACTION_APPEND; - break; - } - break; - default: - action = FHEADER_ACTION_APPEND; - break; - } - switch (action) + strlcpy(forwarded->ip, value, sizeof(forwarded->ip)); + } else + if (!strcasecmp(name, "proto")) { - case FHEADER_ACTION_APPEND: - if (state == FHEADER_STATE_NAME) - { - if (name_length < FHEADER_NAMELEN) - { - name[name_length++] = c; - } else - { - /* truncate */ - } - } else - { - if (value_length < IPLEN) - { - value[value_length++] = c; - } else - { - /* truncate */ - } - } - break; - case FHEADER_ACTION_IGNORE: default: - break; - case FHEADER_ACTION_PROCESS: - value[value_length] = '\0'; - name[name_length] = '\0'; - if (!strcasecmp(name, "for")) - { - strlcpy(forwarded.ip, value, IPLEN+1); - } else if (!strcasecmp(name, "proto")) - { - if (!strcasecmp(value, "https")) - { - forwarded.secure = 1; - } else if (!strcasecmp(value, "http")) - { - forwarded.secure = 0; - } else - { - /* ignore unknown value */ - } - } else - { - /* ignore unknown field name */ - } - value_length = 0; - name_length = 0; - state = FHEADER_STATE_NAME; - break; + if (!strcasecmp(value, "https")) + { + forwarded->secure = 1; + } else if (!strcasecmp(value, "http")) + { + forwarded->secure = 0; + } else + { + /* ignore unknown value */ + } } } - // temporary workaround because above loop is faulty - value[value_length] = '\0'; - name[name_length] = '\0'; - if (!strcasecmp(name, "for")) - { - strlcpy(forwarded.ip, value, IPLEN+1); - } - - return &forwarded; } void webserver_handle_proxy(Client *client, ConfigItem_proxy *proxy) { - struct HTTPForwardedHeader *forwarded; + HTTPForwardedHeader *forwarded; char oldip[64]; NameValuePrioList *header; - const char *forwarded_line = NULL; - int forwarded_type = 0; + /* Set up 'forwarded' variable */ + if (WEB(client)->forwarded == NULL) + { + WEB(client)->forwarded = safe_alloc(sizeof(HTTPForwardedHeader)); + } else { + memset(WEB(client)->forwarded, 0, sizeof(HTTPForwardedHeader)); + } + forwarded = WEB(client)->forwarded; + + /* Go through the headers and parse them */ for (header = WEB(client)->headers; header; header = header->next) { if (!strcasecmp(header->name, "Forwarded") || !strcasecmp(header->name, "X-Forwarded")) - { - forwarded_line = header->value; - forwarded_type = 1; - break; - } + do_parse_forwarded_header(header->value, forwarded); } - if (!forwarded_line) - { - unreal_log(ULOG_WARNING, "websocket", "MISSING_PROXY_HEADER", client, - "Client on proxy $client.ip has matching proxy { } block " - "but the proxy did not send a forwarded header (or not one we could understand). " - "The IP of the user is now the proxy IP $client.ip (bad!)."); - return; - } - - /* parse the header */ - forwarded = do_parse_forwarded_header(forwarded_line); - /* check header values */ if (!is_valid_ip(forwarded->ip)) { - unreal_log(ULOG_DEBUG, "websocket", "INVALID_FORWARDED_IP", client, "Received invalid IP in Forwarded header from $ip", log_data_string("ip", client->ip)); + unreal_log(ULOG_WARNING, "websocket", "MISSING_PROXY_HEADER", client, + "Client on proxy $client.ip has matching proxy { } block " + "but the proxy did not send a valid forwarded header. " + "The IP of the user is now the proxy IP $client.ip (bad!)."); + // TODO: or should we reject the user entirely? return; } diff --git a/src/modules/websocket.c b/src/modules/websocket.c index 546756d95..002c267bf 100644 --- a/src/modules/websocket.c +++ b/src/modules/websocket.c @@ -28,6 +28,7 @@ ModuleHeader MOD_HEADER #endif #define WSU(client) ((WebSocketUser *)moddata_client(client, websocket_md).ptr) +#define WEB(client) ((WebRequest *)moddata_client(client, webserver_md).ptr) #define WEBSOCKET_PORT(client) ((client->local && client->local->listener) ? client->local->listener->websocket_options : 0) #define WEBSOCKET_TYPE(client) (WSU(client)->type) @@ -45,7 +46,8 @@ int websocket_handle_request(Client *client, WebRequest *web); int websocket_reconfigure_web_listener(ConfigItem_listen *listener); /* Global variables */ -ModDataInfo *websocket_md; +ModDataInfo *websocket_md = NULL; /* (by us) */ +ModDataInfo *webserver_md = NULL; /* (external module, looked up) */ static int ws_text_mode_available = 1; MOD_TEST() @@ -80,6 +82,8 @@ MOD_INIT() MOD_LOAD() { + webserver_md = findmoddata_byname("web", MODDATATYPE_CLIENT); + if (non_utf8_nick_chars_in_use || (iConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY)) ws_text_mode_available = 0; return MOD_SUCCESS; @@ -409,6 +413,10 @@ int websocket_handle_request(Client *client, WebRequest *web) } } + /* If using a proxy, set the secure flag depending on what the proxy said */ + if (WEB(client)->forwarded) + WSU(client)->secure = WEB(client)->forwarded->secure; + websocket_handshake_send_response(client); return 1; } diff --git a/src/modules/websocket_common.c b/src/modules/websocket_common.c index 96c76ce92..a5f0d42b2 100644 --- a/src/modules/websocket_common.c +++ b/src/modules/websocket_common.c @@ -26,17 +26,6 @@ ModuleHeader MOD_HEADER #define WSU(client) ((WebSocketUser *)moddata_client(client, websocket_md).ptr) -/* used to parse http Forwarded header (RFC 7239) */ -#define IPLEN 48 -#define FHEADER_NAMELEN 20 - -struct HTTPForwardedHeader -{ - int secure; - char hostname[HOSTLEN+1]; - char ip[IPLEN+1]; -}; - /* Forward declarations - public functions */ int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len)); int _websocket_create_packet(int opcode, char **buf, int *len);