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:
+226
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user