mirror of
https://github.com/pissnet/pissircd.git
synced 2025-07-13 06:32:24 +01:00

This to replace the scattered IP setting. It is very important to always use set_client_ip() from this point. Everywhere! Also, in addition to client->ip, this adds client->rawip that contains the IP in network byte order. In older UnrealIRCd versions we always had the raw IP but not the IP as a string, so we moved to IP as a string, but it can be useful to have both in terms of optimizations. Of course, then the client->ip and client->rawip always need to 100% match, hence the set_client_ip(). This also changes IsIPV6() to do A BUGFIX, it changes it from: * if local user is the user connected over IPv6? Otherwise, does it have ':' in the IP? To: * check if the IPv6 flag is set (which is set if IP contains ':') This may seem insignificant but it means that for spoofed IP addresses, such as WEBIRC or transparant proxy, we use the correct transport. Previously, if the proxy was IPv6 then even if the spoofed user was using IPv4, the ident check would still be tried over IPv6. That sort of fun. From now in, in such a situation client->local->socket_type will be SOCKET_TYPE_IPV6 but since client->ip (and rawip) will contain IPv4 the IsIPV6() will actually return false, as it should be. Also, in the HOOKTYPE_IP_CHANGE, enforce that if HOOK_DENY is returned, the the user is killed by dead_link(). The user must be killed because that is what we expect, and you cannot use exit_client() because from some code paths that would be too much freed structures / hassle, as a comment in src/modules/connect-flood.c correctly states: /* There are two reasons why we can't use exit_client() here: * 1) Because the HOOKTYPE_IP_CHANGE call may be too deep. * Eg: read_packet -> webserver_packet_in -> * webserver_handle_request_header -> webserver_handle_request -> * RunHook().... and then returning without touching anything * after an exit_client() would not be feasible. * 2) Because in HOOKTYPE_ACCEPT we always need to use dead_socket * if we want to print a friendly message to TLS users. */
870 lines
22 KiB
C
870 lines
22 KiB
C
/*
|
|
* Webserver
|
|
* (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
|
|
* License: GPLv2 or later
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
|
|
ModuleHeader MOD_HEADER
|
|
= {
|
|
"webserver",
|
|
"1.0.0",
|
|
"Webserver",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
#if CHAR_MIN < 0
|
|
#error "In UnrealIRCd char should always be unsigned. Check your compiler"
|
|
#endif
|
|
|
|
/* How many seconds to wait with closing after sending the response */
|
|
#define WEB_CLOSE_TIME 1
|
|
|
|
/* The "Server: xyz" in the response */
|
|
#define WEB_SOFTWARE "UnrealIRCd"
|
|
|
|
/* Macros */
|
|
#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)
|
|
|
|
/* 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_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);
|
|
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; /* (by us) */
|
|
ModDataInfo *websocket_md; /* (external module, looked up)*/
|
|
|
|
MOD_TEST()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_SEND_RESPONSE, _webserver_send_response);
|
|
EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_CLOSE_CLIENT, _webserver_close_client);
|
|
EfunctionAdd(modinfo->handle, EFUNC_WEBSERVER_HANDLE_BODY, _webserver_handle_body);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
ModDataInfo mreq;
|
|
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
|
|
//HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, webserver_packet_out);
|
|
HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, webserver_packet_in);
|
|
|
|
memset(&mreq, 0, sizeof(mreq));
|
|
mreq.name = "web";
|
|
mreq.serialize = NULL;
|
|
mreq.unserialize = NULL;
|
|
mreq.free = webserver_webrequest_mdata_free;
|
|
mreq.sync = 0;
|
|
mreq.type = MODDATATYPE_CLIENT;
|
|
webserver_md = ModDataAdd(modinfo->handle, mreq);
|
|
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT);
|
|
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
/** UnrealIRCd internals: free WebRequest object. */
|
|
void webserver_webrequest_mdata_free(ModData *m)
|
|
{
|
|
WebRequest *wsu = (WebRequest *)m->ptr;
|
|
if (wsu)
|
|
{
|
|
safe_free(wsu->uri);
|
|
free_nvplist(wsu->headers);
|
|
safe_free(wsu->lefttoparse);
|
|
safe_free(wsu->request_buffer);
|
|
safe_free(wsu->forwarded);
|
|
safe_free(m->ptr);
|
|
}
|
|
}
|
|
|
|
/** Outgoing packet hook.
|
|
* Do we need this?
|
|
*/
|
|
int webserver_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
|
|
{
|
|
static char utf8buf[510];
|
|
|
|
if (MyConnect(to) && WEB(to))
|
|
{
|
|
// TODO: Inhibit all?
|
|
// Websocket can override though?
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
HttpMethod webserver_get_method(const char *buf)
|
|
{
|
|
if (str_starts_with_case_sensitive(buf, "HEAD "))
|
|
return HTTP_METHOD_HEAD;
|
|
if (str_starts_with_case_sensitive(buf, "GET "))
|
|
return HTTP_METHOD_GET;
|
|
if (str_starts_with_case_sensitive(buf, "PUT "))
|
|
return HTTP_METHOD_PUT;
|
|
if (str_starts_with_case_sensitive(buf, "POST "))
|
|
return HTTP_METHOD_POST;
|
|
return HTTP_METHOD_NONE; /* invalid */
|
|
}
|
|
|
|
void webserver_possible_request(Client *client, const char *buf, int len)
|
|
{
|
|
HttpMethod method;
|
|
|
|
if (len < 8)
|
|
return;
|
|
|
|
/* Probably redundant, but just to be sure, if already tagged, then don't change it! */
|
|
if (WEB(client))
|
|
return;
|
|
|
|
method = webserver_get_method(buf);
|
|
if (method == HTTP_METHOD_NONE)
|
|
return; /* invalid */
|
|
|
|
moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest));
|
|
WEB(client)->method = method;
|
|
|
|
/* Set some default values: */
|
|
WEB(client)->content_length = -1;
|
|
WEB(client)->config_max_request_buffer_size = 4096; /* 4k */
|
|
}
|
|
|
|
/** Incoming packet hook. This processes web requests.
|
|
* NOTE The different return values:
|
|
* -1 means: don't touch this client anymore, it has or might have been killed!
|
|
* 0 means: don't process this data, but you can read another packet if you want
|
|
* >0 means: process this data (regular IRC data, non-web stuff)
|
|
*/
|
|
int webserver_packet_in(Client *client, const char *readbuf, int *length)
|
|
{
|
|
if ((client->local->traffic.messages_received == 0) && WEBSERVER(client))
|
|
webserver_possible_request(client, readbuf, *length);
|
|
|
|
if (!WEB(client))
|
|
return 1; /* "normal" IRC client */
|
|
|
|
if (!WEBSERVER(client))
|
|
return 0; /* handler is gone!? */
|
|
|
|
if (WEB(client)->request_header_parsed)
|
|
return WEBSERVER(client)->handle_body(client, WEB(client), readbuf, *length);
|
|
|
|
/* else.. */
|
|
return webserver_handle_request_header(client, readbuf, length);
|
|
}
|
|
|
|
/** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */
|
|
int webserver_handshake_helper(char *buffer, int len, char **key, char **value, char **lastloc, int *lastloc_len, int *end_of_request)
|
|
{
|
|
static char buf[32768], *nextptr;
|
|
static int buflen;
|
|
char *p;
|
|
char *k = NULL, *v = NULL;
|
|
int foundlf = 0;
|
|
|
|
if (buffer)
|
|
{
|
|
/* Initialize */
|
|
if (len > sizeof(buf) - 1)
|
|
len = sizeof(buf) - 1;
|
|
buflen = len;
|
|
|
|
memcpy(buf, buffer, len);
|
|
buf[len] = '\0';
|
|
nextptr = buf;
|
|
}
|
|
|
|
*end_of_request = 0;
|
|
*lastloc_len = 0;
|
|
|
|
p = nextptr;
|
|
|
|
if (!p)
|
|
{
|
|
*key = *value = NULL;
|
|
return 0; /* done processing data */
|
|
}
|
|
|
|
if (!strncmp(p, "\n", 1) || !strncmp(p, "\r\n", 2))
|
|
{
|
|
*key = *value = NULL;
|
|
*end_of_request = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Note: p *could* point to the NUL byte ('\0') */
|
|
|
|
/* Special handling for GET line itself. */
|
|
if (webserver_get_method(p) != HTTP_METHOD_NONE)
|
|
{
|
|
k = "REQUEST";
|
|
p = strchr(p, ' ') + 1; /* space (0x20) is guaranteed to be there, see strncmp above */
|
|
v = p; /* SET VALUE */
|
|
nextptr = NULL; /* set to "we are done" in case next for loop fails */
|
|
for (; *p; p++)
|
|
{
|
|
if (*p == ' ')
|
|
{
|
|
*p = '\0'; /* terminate before "HTTP/1.X" part */
|
|
}
|
|
else if (*p == '\r')
|
|
{
|
|
*p = '\0'; /* eat silently, but don't consider EOL */
|
|
}
|
|
else if (*p == '\n')
|
|
{
|
|
*p = '\0';
|
|
nextptr = p+1; /* safe, there is data or at least a \0 there */
|
|
break;
|
|
}
|
|
}
|
|
*key = k;
|
|
*value = v;
|
|
return 1;
|
|
}
|
|
|
|
/* Header parsing starts here.
|
|
* Example line "Host: www.unrealircd.org"
|
|
*/
|
|
k = p; /* SET KEY */
|
|
|
|
/* First check if the line contains a terminating \n. If not, don't process it
|
|
* as it may have been a cut header.
|
|
*/
|
|
for (; *p; p++)
|
|
{
|
|
if (*p == '\n')
|
|
{
|
|
foundlf = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundlf)
|
|
{
|
|
*key = *value = NULL;
|
|
*lastloc = k;
|
|
*lastloc_len = buflen - (k - buf);
|
|
/* unreal_log(ULOG_DEBUG, "webserver", "WEBSERVER_FRAMING", NULL,
|
|
"Framing: processed $bytes_processed, remaining $bytes_remaining of $bytes_total",
|
|
log_data_integer("bytes_processed", (int)(k - buf)),
|
|
log_data_integer("bytes_remaining", *lastloc_len),
|
|
log_data_integer("bytes_total", buflen)); */
|
|
return 0;
|
|
}
|
|
|
|
p = k;
|
|
|
|
for (; *p; p++)
|
|
{
|
|
if ((*p == '\n') || (*p == '\r'))
|
|
{
|
|
/* Reached EOL but 'value' not found */
|
|
*p = '\0';
|
|
break;
|
|
}
|
|
if (*p == ':')
|
|
{
|
|
*p++ = '\0';
|
|
if (*p++ != ' ')
|
|
break; /* missing mandatory space after ':' */
|
|
|
|
v = p; /* SET VALUE */
|
|
nextptr = NULL; /* set to "we are done" in case next for loop fails */
|
|
for (; *p; p++)
|
|
{
|
|
if (*p == '\r')
|
|
{
|
|
*p = '\0'; /* eat silently, but don't consider EOL */
|
|
}
|
|
else if (*p == '\n')
|
|
{
|
|
*p = '\0';
|
|
nextptr = p+1; /* safe, there is data or at least a \0 there */
|
|
break;
|
|
}
|
|
}
|
|
/* A key-value pair was succesfully parsed, return it */
|
|
*key = k;
|
|
*value = v;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Fatal parse error */
|
|
*key = *value = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/** Check if there is any data at the end of the request */
|
|
char *find_end_of_request(char *header, int totalsize, int *remaining_bytes)
|
|
{
|
|
char *nextframe1;
|
|
char *nextframe2;
|
|
char *nextframe = NULL;
|
|
|
|
// find first occurance, yeah this is just stupid, but it works.
|
|
nextframe1 = strstr(header, "\r\n\r\n"); // = +4
|
|
nextframe2 = strstr(header, "\n\n"); // = +2
|
|
if (nextframe1 && nextframe2)
|
|
{
|
|
if (nextframe1 < nextframe2)
|
|
{
|
|
nextframe = nextframe1 + 4;
|
|
} else {
|
|
nextframe = nextframe2 + 2;
|
|
}
|
|
} else
|
|
if (nextframe1)
|
|
{
|
|
nextframe = nextframe1 + 4;
|
|
} else
|
|
if (nextframe2)
|
|
{
|
|
nextframe = nextframe2 + 2;
|
|
}
|
|
if (nextframe)
|
|
{
|
|
*remaining_bytes = totalsize - (nextframe - header);
|
|
if (*remaining_bytes > 0)
|
|
return nextframe;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/** Handle HTTP request
|
|
*/
|
|
int webserver_handle_request_header(Client *client, const char *readbuf, int *length)
|
|
{
|
|
char *key, *value;
|
|
int r, end_of_request;
|
|
char *netbuf;
|
|
char *lastloc = NULL;
|
|
int lastloc_len = 0;
|
|
int totalsize;
|
|
|
|
totalsize = WEB(client)->lefttoparselen + *length;
|
|
netbuf = safe_alloc(totalsize+1);
|
|
if (WEB(client)->lefttoparse)
|
|
{
|
|
memcpy(netbuf, WEB(client)->lefttoparse, WEB(client)->lefttoparselen);
|
|
memcpy(netbuf + WEB(client)->lefttoparselen, readbuf, *length);
|
|
} else {
|
|
memcpy(netbuf, readbuf, *length);
|
|
}
|
|
safe_free(WEB(client)->lefttoparse);
|
|
WEB(client)->lefttoparselen = 0;
|
|
|
|
// remember to always safe_free(netbuf); below BEFORE RETURNING !!!
|
|
|
|
/** Now step through the lines.. **/
|
|
for (r = webserver_handshake_helper(netbuf, totalsize, &key, &value, &lastloc, &lastloc_len, &end_of_request);
|
|
r;
|
|
r = webserver_handshake_helper(NULL, 0, &key, &value, &lastloc, &lastloc_len, &end_of_request))
|
|
{
|
|
if (BadPtr(value))
|
|
continue; /* skip empty values */
|
|
|
|
if (!strcasecmp(key, "REQUEST"))
|
|
{
|
|
safe_strdup(WEB(client)->uri, value);
|
|
} else
|
|
{
|
|
if (!strcasecmp(key, "Content-Length"))
|
|
{
|
|
WEB(client)->content_length = atoll(value);
|
|
} else
|
|
if (!strcasecmp(key, "Transfer-Encoding"))
|
|
{
|
|
if (!strcasecmp(value, "chunked"))
|
|
WEB(client)->transfer_encoding = TRANSFER_ENCODING_CHUNKED;
|
|
}
|
|
add_nvplist(&WEB(client)->headers, WEB(client)->num_headers, key, value);
|
|
}
|
|
}
|
|
|
|
if (end_of_request)
|
|
{
|
|
int n;
|
|
int remaining_bytes = 0;
|
|
char *nextframe;
|
|
|
|
/* Some sanity checks */
|
|
if (!WEB(client)->uri)
|
|
{
|
|
webserver_send_response(client, 400, "Malformed HTTP request");
|
|
safe_free(netbuf);
|
|
return -1;
|
|
}
|
|
|
|
WEB(client)->request_header_parsed = 1;
|
|
parse_proxy_header(client);
|
|
n = WEBSERVER(client)->handle_request(client, WEB(client));
|
|
if ((n <= 0) || IsDead(client))
|
|
{
|
|
safe_free(netbuf);
|
|
return n; /* byebye */
|
|
}
|
|
|
|
/* There could be data directly after the request header (eg for
|
|
* a POST or PUT), check for it here so it isn't lost.
|
|
*/
|
|
nextframe = find_end_of_request(netbuf, totalsize, &remaining_bytes);
|
|
if (nextframe)
|
|
{
|
|
int n = WEBSERVER(client)->handle_body(client, WEB(client), nextframe, remaining_bytes);
|
|
safe_free(netbuf);
|
|
return n;
|
|
}
|
|
safe_free(netbuf);
|
|
return 0;
|
|
}
|
|
|
|
if (lastloc && lastloc_len)
|
|
{
|
|
/* Last line was cut somewhere, save it for next round. */
|
|
WEB(client)->lefttoparselen = lastloc_len;
|
|
WEB(client)->lefttoparse = safe_alloc(lastloc_len);
|
|
memcpy(WEB(client)->lefttoparse, lastloc, lastloc_len);
|
|
}
|
|
safe_free(netbuf);
|
|
return 0; /* don't let UnrealIRCd process this */
|
|
}
|
|
|
|
/** Send a HTTP(S) response.
|
|
* @param client Client to send to
|
|
* @param status HTTP status code
|
|
* @param msg The message body.
|
|
* @note if 'msgs' is NULL then don't close the connection.
|
|
*/
|
|
void _webserver_send_response(Client *client, int status, char *msg)
|
|
{
|
|
char buf[512];
|
|
char *statusmsg = "???";
|
|
|
|
if (status == 200)
|
|
statusmsg = "OK";
|
|
else if (status == 201)
|
|
statusmsg = "Created";
|
|
else if (status == 500)
|
|
statusmsg = "Internal Server Error";
|
|
else if (status == 400)
|
|
statusmsg = "Bad Request";
|
|
else if (status == 401)
|
|
statusmsg = "Unauthorized";
|
|
else if (status == 403)
|
|
statusmsg = "Forbidden";
|
|
else if (status == 404)
|
|
statusmsg = "Not Found";
|
|
else if (status == 416)
|
|
statusmsg = "Range Not Satisfiable";
|
|
|
|
snprintf(buf, sizeof(buf),
|
|
"HTTP/1.1 %d %s\r\nServer: %s\r\nConnection: close\r\n\r\n",
|
|
status, statusmsg, WEB_SOFTWARE);
|
|
if (msg)
|
|
{
|
|
strlcat(buf, msg, sizeof(buf));
|
|
strlcat(buf, "\n", sizeof(buf));
|
|
}
|
|
|
|
dbuf_put(&client->local->sendQ, buf, strlen(buf));
|
|
if (msg)
|
|
webserver_close_client(client);
|
|
}
|
|
|
|
/** Close a web client softly, after data has been sent. */
|
|
void _webserver_close_client(Client *client)
|
|
{
|
|
send_queued(client);
|
|
if (DBufLength(&client->local->sendQ) == 0)
|
|
{
|
|
exit_client(client, NULL, "End of request");
|
|
//dead_socket(client, "");
|
|
} else {
|
|
send_queued(client);
|
|
reset_handshake_timeout(client, WEB_CLOSE_TIME);
|
|
}
|
|
}
|
|
|
|
int webserver_handle_body_append_buffer(Client *client, const char *buf, int len)
|
|
{
|
|
/* Guard.. */
|
|
if (len <= 0)
|
|
{
|
|
dead_socket(client, "HTTP request error");
|
|
return 0;
|
|
}
|
|
|
|
if (WEB(client)->request_buffer)
|
|
{
|
|
long long newsize = WEB(client)->request_buffer_size + len + 1;
|
|
if (newsize > WEB(client)->config_max_request_buffer_size)
|
|
{
|
|
/* We would overflow */
|
|
unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client,
|
|
"[webserver] Client $client: request body too large ($length)",
|
|
log_data_integer("length", newsize));
|
|
dead_socket(client, "");
|
|
return 0;
|
|
}
|
|
WEB(client)->request_buffer = realloc(WEB(client)->request_buffer, newsize);
|
|
} else
|
|
{
|
|
if (len + 1 > WEB(client)->config_max_request_buffer_size)
|
|
{
|
|
/* We would overflow */
|
|
unreal_log(ULOG_WARNING, "webserver", "HTTP_BODY_TOO_LARGE", client,
|
|
"[webserver] Client $client: request body too large ($length)",
|
|
log_data_integer("length", len+1));
|
|
dead_socket(client, "");
|
|
return 0;
|
|
}
|
|
WEB(client)->request_buffer = malloc(len+1);
|
|
}
|
|
memcpy(WEB(client)->request_buffer + WEB(client)->request_buffer_size, buf, len);
|
|
WEB(client)->request_buffer_size += len;
|
|
WEB(client)->request_buffer[WEB(client)->request_buffer_size] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
/** Handle HTTP body parsing, eg for a PUT request, concatting it all together.
|
|
* @param client The client
|
|
* @param web The WEB(client)
|
|
* @param readbuf Packet in the read buffer
|
|
* @param pktsize Packet size of the read buffer
|
|
* @return 1 to continue processing, 0 if client is killed.
|
|
*/
|
|
int _webserver_handle_body(Client *client, WebRequest *web, const char *readbuf, int pktsize)
|
|
{
|
|
char *buf;
|
|
long long n;
|
|
char *free_this_buffer = NULL;
|
|
|
|
if (WEB(client)->transfer_encoding == TRANSFER_ENCODING_NONE)
|
|
{
|
|
if (!webserver_handle_body_append_buffer(client, readbuf, pktsize))
|
|
return 0;
|
|
|
|
if ((WEB(client)->content_length >= 0) &&
|
|
(WEB(client)->request_buffer_size >= WEB(client)->content_length))
|
|
{
|
|
WEB(client)->request_body_complete = 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Fill 'buf' nd set 'buflen' with what we had + what we have now.
|
|
* Makes things easy.
|
|
*/
|
|
if (WEB(client)->lefttoparse)
|
|
{
|
|
n = WEB(client)->lefttoparselen + pktsize;
|
|
free_this_buffer = buf = safe_alloc(n);
|
|
memcpy(buf, WEB(client)->lefttoparse, WEB(client)->lefttoparselen);
|
|
memcpy(buf+WEB(client)->lefttoparselen, readbuf, pktsize);
|
|
safe_free(WEB(client)->lefttoparse);
|
|
WEB(client)->lefttoparselen = 0;
|
|
} else {
|
|
n = pktsize;
|
|
free_this_buffer = buf = safe_alloc(n);
|
|
memcpy(buf, readbuf, n);
|
|
}
|
|
|
|
/* Chunked transfers.. yayyyy.. */
|
|
while (n > 0)
|
|
{
|
|
if (WEB(client)->chunk_remaining > 0)
|
|
{
|
|
/* Eat it */
|
|
int eat = MIN(WEB(client)->chunk_remaining, n);
|
|
if (!webserver_handle_body_append_buffer(client, buf, eat))
|
|
{
|
|
/* fatal error such as size exceeded */
|
|
safe_free(free_this_buffer);
|
|
return 0;
|
|
}
|
|
n -= eat;
|
|
buf += eat;
|
|
WEB(client)->chunk_remaining -= eat;
|
|
} else
|
|
{
|
|
int gotlf = 0;
|
|
int i;
|
|
|
|
/* First check if it is a (trailing) empty line,
|
|
* eg from a previous chunk. Skip over.
|
|
*/
|
|
if ((n >= 2) && !strncmp(buf, "\r\n", 2))
|
|
{
|
|
buf += 2;
|
|
n -= 2;
|
|
} else
|
|
if ((n >= 1) && !strncmp(buf, "\n", 1))
|
|
{
|
|
buf++;
|
|
n--;
|
|
}
|
|
|
|
/* Now we are (possibly) at the chunk size line,
|
|
* this is or example '7f' + newline.
|
|
* So first, check if we have a newline at all.
|
|
*/
|
|
for (i=0; i < n; i++)
|
|
{
|
|
if (buf[i] == '\n')
|
|
{
|
|
gotlf = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!gotlf)
|
|
{
|
|
/* The line telling us the chunk size is incomplete,
|
|
* as it does not contain an \n. Wait for more data
|
|
* from the network socket.
|
|
*/
|
|
if (n > 0)
|
|
{
|
|
/* Store what we have first.. */
|
|
WEB(client)->lefttoparselen = n;
|
|
WEB(client)->lefttoparse = safe_alloc(n);
|
|
memcpy(WEB(client)->lefttoparse, buf, n);
|
|
}
|
|
safe_free(free_this_buffer);
|
|
return 1; /* WE WANT MORE! */
|
|
}
|
|
buf[i] = '\0'; /* cut at LF */
|
|
i++; /* point to next data */
|
|
WEB(client)->chunk_remaining = strtol(buf, NULL, 16);
|
|
if (WEB(client)->chunk_remaining < 0)
|
|
{
|
|
unreal_log(ULOG_WARNING, "webserver", "WEB_NEGATIVE_CHUNK", client,
|
|
"Webrequest from $client: Negative chunk encountered");
|
|
safe_free(free_this_buffer);
|
|
dead_socket(client, "");
|
|
return 0;
|
|
}
|
|
if (WEB(client)->chunk_remaining == 0)
|
|
{
|
|
/* DONE! */
|
|
WEB(client)->request_body_complete = 1;
|
|
safe_free(free_this_buffer);
|
|
return 1;
|
|
}
|
|
buf += i;
|
|
n -= i;
|
|
}
|
|
}
|
|
|
|
safe_free(free_this_buffer);
|
|
return 1;
|
|
}
|
|
|
|
/** 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.
|
|
*/
|
|
void do_parse_forwarded_header(const char *input, HTTPForwardedHeader *forwarded)
|
|
{
|
|
char *buf = NULL;
|
|
char *name, *value, *p = NULL, *x;
|
|
|
|
safe_strdup(buf, input);
|
|
|
|
for (name = strtoken(&p, buf, ";,"); name; name = strtoken(&p, NULL, ";,"))
|
|
{
|
|
skip_whitespace(&name);
|
|
value = strchr(name, '=');
|
|
if (value)
|
|
*value++ = '\0';
|
|
if (!value)
|
|
continue; /* we don't use value-less items atm anyway */
|
|
|
|
/* Remove quotes - if any */
|
|
if (*value == '"')
|
|
{
|
|
value++;
|
|
x = strchr(value, '"');
|
|
if (x)
|
|
*x = '\0';
|
|
}
|
|
if (!strcasecmp(name, "for"))
|
|
{
|
|
/* IPv6 is in brackets, so cut it off */
|
|
if (*value == '[')
|
|
{
|
|
value++;
|
|
char *x = strchr(value, ']');
|
|
if (x)
|
|
*x = '\0';
|
|
/* ^^ this cuts off everything after ']', which
|
|
* may also cut off the optional local port,
|
|
* but that is fine: we don't use it.
|
|
*/
|
|
} else
|
|
if ((x = strchr(value, ':')))
|
|
{
|
|
/* For non-IPv6 ip:port, cut off at the ':',
|
|
* so we only have IP.
|
|
*/
|
|
*x = '\0';
|
|
}
|
|
strlcpy(forwarded->ip, value, sizeof(forwarded->ip));
|
|
} else
|
|
if (!strcasecmp(name, "proto"))
|
|
{
|
|
if (!strcasecmp(value, "https"))
|
|
{
|
|
forwarded->secure = 1;
|
|
} else if (!strcasecmp(value, "http"))
|
|
{
|
|
forwarded->secure = 0;
|
|
} else
|
|
{
|
|
/* ignore unknown value */
|
|
}
|
|
}
|
|
}
|
|
safe_free(buf);
|
|
}
|
|
|
|
/** If a valid X-Forwarded-For 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 IP is received, we'll accept the last one.
|
|
*/
|
|
void do_parse_x_forwarded_for_header(const char *input, HTTPForwardedHeader *forwarded)
|
|
{
|
|
char *buf = NULL;
|
|
char *name, *value, *p = NULL;
|
|
|
|
safe_strdup(buf, input);
|
|
|
|
for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
|
|
{
|
|
skip_whitespace(&name);
|
|
strlcpy(forwarded->ip, name, sizeof(forwarded->ip));
|
|
}
|
|
|
|
safe_free(buf);
|
|
}
|
|
|
|
void do_parse_x_forwarded_proto_header(const char *value, HTTPForwardedHeader *forwarded)
|
|
{
|
|
if (!strcmp(value, "https"))
|
|
forwarded->secure = 1;
|
|
else if (!strcmp(value, "http"))
|
|
forwarded->secure = 0;
|
|
}
|
|
|
|
void webserver_handle_proxy(Client *client, ConfigItem_proxy *proxy)
|
|
{
|
|
HTTPForwardedHeader *forwarded;
|
|
NameValuePrioList *header;
|
|
|
|
/* 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 (proxy->type == PROXY_FORWARDED)
|
|
{
|
|
if (!strcasecmp(header->name, "Forwarded"))
|
|
do_parse_forwarded_header(header->value, forwarded);
|
|
} else
|
|
if (proxy->type == PROXY_X_FORWARDED)
|
|
{
|
|
if (!strcasecmp(header->name, "X-Forwarded-For"))
|
|
do_parse_x_forwarded_for_header(header->value, forwarded);
|
|
else if (!strcasecmp(header->name, "X-Forwarded-Proto"))
|
|
do_parse_x_forwarded_proto_header(header->value, forwarded);
|
|
} else
|
|
if (proxy->type == PROXY_CLOUDFLARE)
|
|
{
|
|
/* This is a mix of CF-Connecting-IP and X-Forwarded-Proto */
|
|
if (!strcasecmp(header->name, "CF-Connecting-IP"))
|
|
do_parse_x_forwarded_for_header(header->value, forwarded);
|
|
else if (!strcasecmp(header->name, "X-Forwarded-Proto"))
|
|
do_parse_x_forwarded_proto_header(header->value, forwarded);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUGMODE
|
|
unreal_log(ULOG_DEBUG, "webserver", "FORWARDING_INFO", client,
|
|
"For client $client.details forwarding IP is $ip and is $secure",
|
|
log_data_string("ip", forwarded->ip),
|
|
log_data_string("secure", forwarded->secure ? "secure" : "insecure"));
|
|
#endif
|
|
|
|
/* check header values */
|
|
if (!is_valid_ip(forwarded->ip))
|
|
{
|
|
unreal_log(ULOG_WARNING, "webserver", "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;
|
|
}
|
|
|
|
/* store data / set new IP */
|
|
if (!set_client_ip(client, forwarded->ip))
|
|
return; /* Killed */
|
|
set_sockhost(client, forwarded->ip); /* in case dns lookup fails or is disabled */
|
|
|
|
/* restart DNS & ident lookups */
|
|
start_dns_and_ident_lookup(client);
|
|
}
|
|
|
|
/** 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 (IsWebProxy(proxy->type) &&
|
|
user_allowed_by_security_group(client, proxy->mask))
|
|
{
|
|
webserver_handle_proxy(client, proxy);
|
|
return;
|
|
}
|
|
}
|
|
}
|