1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-06-12 17:14:46 +02:00

Split 'websocket' module up in 'webserver' and 'websocket'

This commit is contained in:
Bram Matthys
2022-06-06 07:51:05 +02:00
parent 0c32151be1
commit 2397fb8a49
10 changed files with 569 additions and 250 deletions
+4
View File
@@ -407,6 +407,7 @@ DLL_FILES=\
src/modules/watch.dll \
src/modules/webirc.dll \
src/modules/webredir.dll \
src/modules/webserver.dll \
src/modules/websocket.dll \
src/modules/whois.dll \
src/modules/who_old.dll \
@@ -1297,6 +1298,9 @@ src/modules/webirc.dll: src/modules/webirc.c $(INCLUDES)
src/modules/webredir.dll: src/modules/webredir.c $(INCLUDES)
$(CC) $(MODCFLAGS) src/modules/webredir.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/webredir.pdb $(MODLFLAGS)
src/modules/webserver.dll: src/modules/webserver.c $(INCLUDES)
$(CC) $(MODCFLAGS) src/modules/webserver.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/webserver.pdb $(MODLFLAGS)
src/modules/websocket.dll: src/modules/websocket.c $(INCLUDES)
$(CC) $(MODCFLAGS) src/modules/websocket.c /Fesrc/modules/ /Fosrc/modules/ /Fdsrc/modules/websocket.pdb $(MODLFLAGS)
+3
View File
@@ -141,6 +141,9 @@ set {
// }
//}
// Load the webserver module, needed for websocket (see next)
loadmodule "webserver";
// This adds websocket support. For more information, see:
// https://www.unrealircd.org/docs/WebSocket_support
loadmodule "websocket";
+4
View File
@@ -836,6 +836,8 @@ extern MODVAR char *(*get_chmodes_for_user)(Client *client, const char *flags);
extern MODVAR WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name);
extern MODVAR int (*make_oper)(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost);
extern MODVAR int (*unreal_match_iplist)(Client *client, NameList *l);
extern MODVAR void (*webserver_send_response)(Client *client, int status, char *msg);
extern MODVAR void (*webserver_close_client)(Client *client);
/* /Efuncs */
/* TLS functions */
@@ -872,6 +874,8 @@ extern int del_silence_default_handler(Client *client, const char *mask);
extern int is_silenced_default_handler(Client *client, Client *acptr);
extern void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized);
extern int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost);
extern void webserver_send_response_default_handler(Client *client, int status, char *msg);
extern void webserver_close_client_default_handler(Client *client);
/* End of default handlers for efunctions */
extern MODVAR MOTDFile opermotd, svsmotd, motd, botmotd, smotd, rules;
+2
View File
@@ -2442,6 +2442,8 @@ enum EfunctionType {
EFUNC_WHOIS_GET_POLICY,
EFUNC_MAKE_OPER,
EFUNC_UNREAL_MATCH_IPLIST,
EFUNC_WEBSERVER_SEND_RESPONSE,
EFUNC_WEBSERVER_CLOSE_CLIENT,
};
/* Module flags */
+29
View File
@@ -1693,6 +1693,34 @@ struct ConfigItem_tld {
u_short options;
};
#define WEB_OPT_ENABLE 0x1
typedef enum HttpMethod {
HTTP_METHOD_NONE = 0, /**< No valid HTTP request (yet) */
HTTP_METHOD_HEAD = 1, /**< HEAD request */
HTTP_METHOD_GET = 2, /**< GET request */
HTTP_METHOD_PUT = 3, /**< PUT request */
HTTP_METHOD_POST = 4, /**< POST request */
} HttpMethod;
typedef struct WebRequest WebRequest;
struct WebRequest {
HttpMethod method; /**< GET/PUT/POST */
char *uri; /**< Requested resource, eg "/api" */
NameValuePrioList *headers; /**< HTTP request headers */
int num_headers; /**< Number of HTTP request headers (also used for sorting the list) */
char request_header_parsed; /**< Done parsing? */
char *lefttoparse; /**< Leftover buffer to parse */
int lefttoparselen; /**< Length of lefttoparse buffer */
int pending_close; /**< Set to 1 when connection should be closed as soon as all data is sent (sendq==0) */
};
typedef struct WebServer WebServer;
struct WebServer {
int (*handle_request)(Client *client, WebRequest *web);
int (*handle_data)(Client *client, WebRequest *web, const char *buf, int length);
};
struct ConfigItem_listen {
ConfigItem_listen *prev, *next;
ConfigFlag flag;
@@ -1704,6 +1732,7 @@ struct ConfigItem_listen {
int fd;
SSL_CTX *ssl_ctx;
TLSOptions *tls_options;
WebServer *webserver;
int websocket_options; /* should be in module, but lazy */
char *websocket_forward;
};
+4
View File
@@ -137,6 +137,8 @@ char *(*get_chmodes_for_user)(Client *client, const char *flags);
WhoisConfigDetails (*whois_get_policy)(Client *client, Client *target, const char *name);
int (*make_oper)(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost);
int (*unreal_match_iplist)(Client *client, NameList *l);
void (*webserver_send_response)(Client *client, int status, char *msg);
void (*webserver_close_client)(Client *client);
Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)())
{
@@ -409,4 +411,6 @@ void efunctions_init(void)
efunc_init_function(EFUNC_WHOIS_GET_POLICY, whois_get_policy, NULL);
efunc_init_function(EFUNC_MAKE_OPER, make_oper, make_oper_default_handler);
efunc_init_function(EFUNC_UNREAL_MATCH_IPLIST, unreal_match_iplist, NULL);
efunc_init_function(EFUNC_WEBSERVER_SEND_RESPONSE, webserver_send_response, webserver_send_response_default_handler);
efunc_init_function(EFUNC_WEBSERVER_CLOSE_CLIENT, webserver_close_client, webserver_close_client_default_handler);
}
+8
View File
@@ -1395,6 +1395,14 @@ int make_oper_default_handler(Client *client, const char *operblock_name, const
return 0;
}
extern void webserver_send_response_default_handler(Client *client, int status, char *msg)
{
}
extern void webserver_close_client_default_handler(Client *client)
{
}
/** my_timegm: mktime()-like function which will use GMT/UTC.
* Strangely enough there is no standard function for this.
* On some *NIX OS's timegm() may be available, sometimes only
+1 -1
View File
@@ -59,7 +59,7 @@ MODULES= \
botmotd.so lusers.so names.so svsnolag.so addmotd.so \
svslusers.so starttls.so webredir.so cap.so \
sasl.so md.so certfp.so \
tls_antidos.so webirc.so websocket.so \
tls_antidos.so webirc.so webserver.so websocket.so \
blacklist.so jointhrottle.so \
antirandom.so hideserver.so jumpserver.so \
ircops.so staff.so nocodes.so \
+457
View File
@@ -0,0 +1,457 @@
/*
* Webserver
* (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
* License: GPLv2 or later
*/
#include "unrealircd.h"
#include "dns.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)
/* 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);
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);
/* Global variables */
ModDataInfo *webserver_md;
MOD_TEST()
{
EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_SEND_RESPONSE, _webserver_send_response);
EfunctionAddVoid(modinfo->handle, EFUNC_WEBSERVER_CLOSE_CLIENT, _webserver_close_client);
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_mdata_free;
mreq.sync = 0;
mreq.type = MODDATATYPE_CLIENT;
webserver_md = ModDataAdd(modinfo->handle, mreq);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
/** UnrealIRCd internals: free WebRequest object. */
void webserver_mdata_free(ModData *m)
{
WebRequest *wsu = (WebRequest *)m->ptr;
if (wsu)
{
safe_free(wsu->uri);
safe_free(wsu->lefttoparse);
free_nvplist(wsu->headers);
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;
}
void webserver_possible_request(Client *client, const char *buf, int len)
{
if (len < 8)
return;
/* Probably redundant, but just to be sure, if already tagged, then don't change it! */
if (WEB(client))
return;
if (!strncmp(buf, "HEAD ", 5))
{
moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest));
WEB(client)->method = HTTP_METHOD_HEAD;
} else
if (!strncmp(buf, "GET ", 4))
{
moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest));
WEB(client)->method = HTTP_METHOD_GET;
} else
if (!strncmp(buf, "PUT ", 4))
{
moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest));
WEB(client)->method = HTTP_METHOD_PUT;
} else
if (!strncmp(buf, "POST ", 5))
{
moddata_client(client, webserver_md).ptr = safe_alloc(sizeof(WebRequest));
WEB(client)->method = HTTP_METHOD_POST;
}
}
/** 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 (WEB(client)->request_header_parsed)
return WEBSERVER(client)->handle_data(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 *end_of_request)
{
static char buf[4096], *nextptr;
char *p;
char *k = NULL, *v = NULL;
int foundlf = 0;
if (buffer)
{
/* Initialize */
if (len > sizeof(buf) - 1)
len = sizeof(buf) - 1;
memcpy(buf, buffer, len);
buf[len] = '\0';
nextptr = buf;
}
*end_of_request = 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 (!strncmp(p, "HEAD ", 5) ||!strncmp(p, "GET ", 4) || !strncmp(p, "PUT ", 4) || !strncmp(p, "POST ", 5))
{
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;
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
* Yes, I'm going to assume that the header fits in one packet and one packet only.
*/
int webserver_handle_request_header(Client *client, const char *readbuf, int *length)
{
char *key, *value;
int r, end_of_request;
static char netbuf[16384];
static char netbuf2[16384];
char *lastloc = NULL;
int n, maxcopy, nprefix=0;
int totalsize;
/* Totally paranoid: */
memset(netbuf, 0, sizeof(netbuf));
memset(netbuf2, 0, sizeof(netbuf2));
/** Frame re-assembling starts here **/
if (WEB(client)->lefttoparse)
{
strlcpy(netbuf, WEB(client)->lefttoparse, sizeof(netbuf));
nprefix = strlen(netbuf);
}
maxcopy = sizeof(netbuf) - nprefix - 1;
/* (Need to do some manual checking here as strlen() can't be safely used
* on readbuf. Same is true for strlncat since it uses strlen().)
*/
n = *length;
if (n > maxcopy)
n = maxcopy;
if (n <= 0)
{
webserver_close_client(client); // Oversized line
return -1;
}
memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */
totalsize = n + nprefix;
netbuf[totalsize] = '\0';
memcpy(netbuf2, netbuf, totalsize+1); // copy, including the "always present \0 at the end just in case we use strstr etc".
safe_free(WEB(client)->lefttoparse);
/** Now step through the lines.. **/
for (r = webserver_handshake_helper(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request);
r;
r = webserver_handshake_helper(NULL, 0, &key, &value, &lastloc, &end_of_request))
{
if (!strcasecmp(key, "REQUEST"))
{
// TODO: guard for a 16k URI ? ;D
safe_strdup(WEB(client)->uri, value);
} else
{
add_nvplist(&WEB(client)->headers, WEB(client)->num_headers, key, value);
}
}
if (end_of_request)
{
int n;
int remaining_bytes = 0;
char *nextframe;
WEB(client)->request_header_parsed = 1;
n = WEBSERVER(client)->handle_request(client, WEB(client));
if ((n <= 0) || IsDead(client))
return n; /* byebye */
/* There could be data directly after the request header (eg for a PUT),
* check for it here so it isn't lost.
*/
nextframe = find_end_of_request(netbuf2, totalsize, &remaining_bytes);
if (nextframe)
return WEBSERVER(client)->handle_data(client, WEB(client), nextframe, remaining_bytes);
return 0;
}
if (lastloc)
{
/* Last line was cut somewhere, save it for next round. */
safe_strdup(WEB(client)->lefttoparse, lastloc);
}
return 0; /* don't let UnrealIRCd process this */
}
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 == 400)
statusmsg = "Bad Request";
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%s\n",
status, statusmsg, WEB_SOFTWARE, msg);
dbuf_put(&client->local->sendQ, buf, strlen(buf));
send_queued(client);
webserver_close_client(client);
}
/** Close a web client softly, after data has been sent. */
void _webserver_close_client(Client *client)
{
if (DBufLength(&client->local->sendQ) == 0)
{
dead_socket(client, "");
} else {
send_queued(client);
reset_handshake_timeout(client, WEB_CLOSE_TIME);
}
}
+57 -249
View File
@@ -74,7 +74,6 @@ struct HTTPForwardedHeader
int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length);
int websocket_packet_in(Client *client, const char *readbuf, int *length);
void websocket_mdata_free(ModData *m);
int websocket_handle_packet(Client *client, const char *readbuf, int length);
int websocket_handle_handshake(Client *client, const char *readbuf, int *length);
@@ -86,6 +85,8 @@ int websocket_send_pong(Client *client, const char *buf, int len);
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_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
int websocket_handle_request(Client *client, WebRequest *web);
/* Global variables */
ModDataInfo *websocket_md;
@@ -105,7 +106,6 @@ MOD_INIT()
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, websocket_config_run_ex);
HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, websocket_packet_out);
HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, INT_MIN, websocket_packet_in);
HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, websocket_secure_connect);
memset(&mreq, 0, sizeof(mreq));
@@ -224,6 +224,9 @@ int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr
return 0;
l = (ConfigItem_listen *)ptr;
l->webserver = safe_alloc(sizeof(WebServer));
l->webserver->handle_request = websocket_handle_request;
l->webserver->handle_data = websocket_handle_websocket;
for (cep = ce->items; cep; cep = cep->next)
{
@@ -291,7 +294,7 @@ int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **m
return 0;
}
int websocket_handle_websocket(Client *client, const char *readbuf2, int length2)
int websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2)
{
int n;
char *ptr;
@@ -336,170 +339,6 @@ int websocket_handle_websocket(Client *client, const char *readbuf2, int length2
return 0;
}
/** Incoming packet hook.
* This processes websocket frames, if this is a websocket connection.
* 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-websocket stuff)
*/
int websocket_packet_in(Client *client, const char *readbuf, int *length)
{
if ((client->local->traffic.messages_received == 0) &&
WEBSOCKET_PORT(client) &&
!WSU(client) &&
(*length > 8) &&
!strncmp(readbuf, "GET ", 4))
{
/* Allocate a new WebSocketUser struct for this session */
moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
WSU(client)->get = 1;
WSU(client)->type = client->local->listener->websocket_options; /* the default, unless the client chooses otherwise */
}
if (!WSU(client))
return 1; /* "normal" IRC client */
if (WSU(client)->handshake_completed)
return websocket_handle_websocket(client, readbuf, *length);
/* else.. */
return websocket_handle_handshake(client, readbuf, length);
}
/** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */
int websocket_handshake_helper(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request)
{
static char buf[4096], *nextptr;
char *p;
char *k = NULL, *v = NULL;
int foundlf = 0;
if (buffer)
{
/* Initialize */
if (len > sizeof(buf) - 1)
len = sizeof(buf) - 1;
memcpy(buf, buffer, len);
buf[len] = '\0';
nextptr = buf;
}
*end_of_request = 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 (!strncmp(p, "GET ", 4))
{
k = "GET";
p += 4;
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;
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;
}
#define FHEADER_STATE_NAME 0
#define FHEADER_STATE_VALUE 1
#define FHEADER_STATE_VALUE_QUOTED 2
@@ -635,9 +474,49 @@ struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input)
return &forwarded;
}
/** Finally, validate the websocket request (handshake) and proceed or reject. */
int websocket_handshake_valid(Client *client)
/** We got a HTTP(S) request and we need to check if we can upgrade the connection
* to a websocket connection.
*/
int websocket_handle_request(Client *client, WebRequest *web)
{
NameValuePrioList *r;
const char *key, *value;
/* Allocate a new WebSocketUser struct for this session */
moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
/* ...and set the default protocol (text or binary) */
WSU(client)->type = client->local->listener->websocket_options;
/** Now step through the lines.. **/
for (r = web->headers; r; r = r->next)
{
key = r->name;
value = r->value;
if (!strcasecmp(key, "Sec-WebSocket-Key"))
{
if (strchr(value, ':'))
{
/* This would cause unserialization issues. Should be base64 anyway */
webserver_send_response(client, 400, "Invalid characters in Sec-WebSocket-Key");
return -1;
}
safe_strdup(WSU(client)->handshake_key, value);
} else
if (!strcasecmp(key, "Sec-WebSocket-Protocol"))
{
/* Save it here, will be processed later */
safe_strdup(WSU(client)->sec_websocket_protocol, value);
} else
if (!strcasecmp(key, "Forwarded"))
{
/* will be processed later too */
safe_strdup(WSU(client)->forwarded, value);
}
}
/** Finally, validate the websocket request (handshake) and proceed or reject. */
/* Not websocket and webredir loaded? Let that module serve a redirect. */
if (!WSU(client)->handshake_key)
{
if (is_module_loaded("webredir"))
@@ -645,9 +524,11 @@ int websocket_handshake_valid(Client *client)
const char *parx[2] = { NULL, NULL };
do_cmd(client, NULL, "GET", 1, parx);
}
dead_socket(client, "Invalid WebSocket request");
webserver_send_response(client, 404, "This port is for IRC WebSocket only");
return 0;
}
/* Sec-WebSocket-Protocol (optional) */
if (WSU(client)->sec_websocket_protocol)
{
char *p = NULL, *name;
@@ -684,6 +565,8 @@ int websocket_handshake_valid(Client *client)
safe_free(WSU(client)->sec_websocket_protocol);
}
}
/* Check forwarded header (by k4be) */
if (WSU(client)->forwarded)
{
struct HTTPForwardedHeader *forwarded;
@@ -693,7 +576,7 @@ int websocket_handshake_valid(Client *client)
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));
dead_socket(client, "Forwarded: no access");
webserver_send_response(client, 403, "Forwarded: no access");
return 0;
}
/* parse the header */
@@ -702,7 +585,7 @@ int websocket_handshake_valid(Client *client)
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));
dead_socket(client, "Forwarded: invalid IP");
webserver_send_response(client, 400, "Forwarded: invalid IP");
return 0;
}
/* store data */
@@ -738,6 +621,8 @@ int websocket_handshake_valid(Client *client)
}
RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
}
websocket_handshake_send_response(client);
return 1;
}
@@ -752,83 +637,6 @@ int websocket_secure_connect(Client *client)
return 0;
}
/** Handle client GET WebSocket handshake.
* Yes, I'm going to assume that the header fits in one packet and one packet only.
*/
int websocket_handle_handshake(Client *client, const char *readbuf, int *length)
{
char *key, *value;
int r, end_of_request;
char netbuf[2048];
char *lastloc = NULL;
int n, maxcopy, nprefix=0;
/** Frame re-assembling starts here **/
*netbuf = '\0';
if (WSU(client)->lefttoparse)
{
strlcpy(netbuf, WSU(client)->lefttoparse, sizeof(netbuf));
nprefix = strlen(netbuf);
}
maxcopy = sizeof(netbuf) - nprefix - 1;
/* (Need to some manual checking here as strlen() can't be safely used
* on readbuf. Same is true for strlncat since it uses strlen().)
*/
n = *length;
if (n > maxcopy)
n = maxcopy;
if (n <= 0)
{
dead_socket(client, "Oversized line");
return -1;
}
memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */
netbuf[n+nprefix] = '\0';
safe_free(WSU(client)->lefttoparse);
/** Now step through the lines.. **/
for (r = websocket_handshake_helper(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request);
r;
r = websocket_handshake_helper(NULL, 0, &key, &value, &lastloc, &end_of_request))
{
if (!strcasecmp(key, "Sec-WebSocket-Key"))
{
if (strchr(value, ':'))
{
/* This would cause unserialization issues. Should be base64 anyway */
dead_socket(client, "Invalid characters in Sec-WebSocket-Key");
return -1;
}
safe_strdup(WSU(client)->handshake_key, value);
} else
if (!strcasecmp(key, "Sec-WebSocket-Protocol"))
{
/* Save it here, will be processed later */
safe_strdup(WSU(client)->sec_websocket_protocol, value);
} else
if (!strcasecmp(key, "Forwarded"))
{
/* will be processed later too */
safe_strdup(WSU(client)->forwarded, value);
}
}
if (end_of_request)
{
if (!websocket_handshake_valid(client) || IsDead(client))
return -1;
websocket_handshake_send_response(client);
return 0;
}
if (lastloc)
{
/* Last line was cut somewhere, save it for next round. */
safe_strdup(WSU(client)->lefttoparse, lastloc);
}
return 0; /* don't let UnrealIRCd process this */
}
/** Complete the handshake by sending the appropriate HTTP 101 response etc. */
int websocket_handshake_send_response(Client *client)
{