1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-04 06:03:12 +02:00

Move webserver proxy handling from the websocket to the webserver module.

This now requires a proxy { } block -- docs follow soon

This uses part of k4be's code still, to do the parsing,
so still only "Forwarded" and quick workaround for bug
when for=XXX is the final item.
This commit is contained in:
Bram Matthys
2023-05-26 13:04:51 +02:00
parent c537a72c10
commit d2f45fcaaf
2 changed files with 226 additions and 211 deletions
+226 -1
View File
@@ -30,6 +30,18 @@ ModuleHeader MOD_HEADER
#define WEB(client) ((WebRequest *)moddata_client(client, webserver_md).ptr)
#define WEBSERVER(client) ((client->local && client->local->listener) ? client->local->listener->webserver : NULL)
#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);
@@ -41,9 +53,11 @@ int webserver_handle_request_header(Client *client, const char *readbuf, int *le
void _webserver_send_response(Client *client, int status, char *msg);
void _webserver_close_client(Client *client);
int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int length);
void parse_proxy_header(Client *client);
/* Global variables */
ModDataInfo *webserver_md;
ModDataInfo *webserver_md; /* (by us) */
ModDataInfo *websocket_md; /* (external module, looked up)*/
MOD_TEST()
{
@@ -77,6 +91,8 @@ MOD_INIT()
MOD_LOAD()
{
websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT);
return MOD_SUCCESS;
}
@@ -426,6 +442,7 @@ int webserver_handle_request_header(Client *client, const char *readbuf, int *le
}
WEB(client)->request_header_parsed = 1;
parse_proxy_header(client);
n = WEBSERVER(client)->handle_request(client, WEB(client));
if ((n <= 0) || IsDead(client))
return n; /* byebye */
@@ -676,3 +693,211 @@ int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf,
safe_free(free_this_buffer);
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)
{
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++)
{
c = input[i];
switch (c)
{
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)
{
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;
}
}
// 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;
char oldip[64];
NameValuePrioList *header;
const char *forwarded_line = NULL;
int forwarded_type = 0;
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;
}
}
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));
return;
}
/* store data / set new IP */
// WSU(client)->secure = forwarded->secure; TODO: FIXME
strlcpy(oldip, client->ip, sizeof(oldip));
safe_strdup(client->ip, forwarded->ip);
strlcpy(client->local->sockhost, forwarded->ip, sizeof(client->local->sockhost)); /* in case dns lookup fails or is disabled */
/* restart DNS & ident lookups */
start_dns_and_ident_lookup(client);
RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
}
/** Parse proxy headers (if any) and run proxy ip change routines (if needed).
* This is called after receiving the HTTP request, right before passing
* the request on to next handler (eg. the 'websocket' module).
*/
void parse_proxy_header(Client *client)
{
ConfigItem_proxy *proxy;
for (proxy = conf_proxy; proxy; proxy = proxy->next)
{
if ((proxy->type == PROXY_WEB) && user_allowed_by_security_group(client, proxy->mask))
{
webserver_handle_proxy(client, proxy);
return;
}
}
}
-210
View File
@@ -32,17 +32,6 @@ ModuleHeader MOD_HEADER
#define WEBSOCKET_PORT(client) ((client->local && client->local->listener) ? client->local->listener->websocket_options : 0)
#define WEBSOCKET_TYPE(client) (WSU(client)->type)
/* 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 websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
int websocket_config_posttest(int *);
@@ -52,8 +41,6 @@ int websocket_handle_handshake(Client *client, const char *readbuf, int *length)
int websocket_handshake_send_response(Client *client);
int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
int websocket_secure_connect(Client *client);
struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input);
int websocket_ip_compare(const char *ip1, const char *ip2);
int websocket_handle_request(Client *client, WebRequest *web);
int websocket_reconfigure_web_listener(ConfigItem_listen *listener);
@@ -330,141 +317,6 @@ int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **m
return 0;
}
#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 *websocket_parse_forwarded_header(char *input)
{
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++)
{
c = input[i];
switch (c)
{
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)
{
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;
}
}
return &forwarded;
}
/** We got a HTTP(S) request and we need to check if we can upgrade the connection
* to a websocket connection.
*/
@@ -557,38 +409,6 @@ int websocket_handle_request(Client *client, WebRequest *web)
}
}
/* Check forwarded header (by k4be) */
if (WSU(client)->forwarded)
{
struct HTTPForwardedHeader *forwarded;
char oldip[64];
/* check for source ip */
if (BadPtr(client->local->listener->websocket_forward) || !websocket_ip_compare(client->local->listener->websocket_forward, client->ip))
{
unreal_log(ULOG_WARNING, "websocket", "UNAUTHORIZED_FORWARDED_HEADER", client, "Received unauthorized Forwarded header from $ip", log_data_string("ip", client->ip));
webserver_send_response(client, 403, "Forwarded: no access");
return 0;
}
/* parse the header */
forwarded = websocket_parse_forwarded_header(WSU(client)->forwarded);
/* check header values */
if (!is_valid_ip(forwarded->ip))
{
unreal_log(ULOG_WARNING, "websocket", "INVALID_FORWARDED_IP", client, "Received invalid IP in Forwarded header from $ip", log_data_string("ip", client->ip));
webserver_send_response(client, 400, "Forwarded: invalid IP");
return 0;
}
/* store data / set new IP */
WSU(client)->secure = forwarded->secure;
strlcpy(oldip, client->ip, sizeof(oldip));
safe_strdup(client->ip, forwarded->ip);
strlcpy(client->local->sockhost, forwarded->ip, sizeof(client->local->sockhost)); /* in case dns lookup fails or is disabled */
/* restart DNS & ident lookups */
start_dns_and_ident_lookup(client);
RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
}
websocket_handshake_send_response(client);
return 1;
}
@@ -644,33 +464,3 @@ int websocket_handshake_send_response(Client *client)
return 0;
}
/** Compare IP addresses (for authorization checking) */
int websocket_ip_compare(const char *ip1, const char *ip2)
{
uint32_t ip4[2];
uint16_t ip6[16];
int i;
if (inet_pton(AF_INET, ip1, &ip4[0]) == 1) /* IPv4 */
{
if (inet_pton(AF_INET, ip2, &ip4[1]) == 1) /* both are valid, let's compare */
{
return ip4[0] == ip4[1];
}
return 0;
}
if (inet_pton(AF_INET6, ip1, &ip6[0]) == 1) /* IPv6 */
{
if (inet_pton(AF_INET6, ip2, &ip6[8]) == 1)
{
for (i = 0; i < 8; i++)
{
if (ip6[i] != ip6[i+8])
return 0;
}
return 1;
}
}
return 0; /* neither valid IPv4 nor IPv6 */
}