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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user