mirror of https://github.com/pissnet/pissircd.git
512 lines
14 KiB
C
512 lines
14 KiB
C
/*
|
|
* websocket_common - Common WebSocket functions (RFC6455)
|
|
* (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
|
|
* License: GPLv2 or later
|
|
* The websocket module was sponsored by Aberrant Software Inc.
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
|
|
ModuleHeader MOD_HEADER
|
|
= {
|
|
"websocket_common",
|
|
"6.1.4",
|
|
"WebSocket support (RFC6455)",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
#if CHAR_MIN < 0
|
|
#error "In UnrealIRCd char should always be unsigned. Check your compiler"
|
|
#endif
|
|
|
|
#ifndef WEBSOCKET_SEND_BUFFER_SIZE
|
|
#define WEBSOCKET_SEND_BUFFER_SIZE 16384
|
|
#endif
|
|
|
|
#define WSU(client) ((WebSocketUser *)moddata_client(client, websocket_md).ptr)
|
|
|
|
/* Forward declarations - public functions */
|
|
int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
|
|
int _websocket_create_packet(int opcode, char **buf, int *len);
|
|
int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
|
|
int _websocket_create_packet_simple(int opcode, const char **buf, int *len);
|
|
/* Forward declarations - other */
|
|
int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len));
|
|
int websocket_handle_packet_ping(Client *client, const char *buf, int len);
|
|
int websocket_handle_packet_pong(Client *client, const char *buf, int len);
|
|
int websocket_send_pong(Client *client, const char *buf, int len);
|
|
const char *websocket_mdata_serialize(ModData *m);
|
|
void websocket_mdata_unserialize(const char *str, ModData *m);
|
|
void websocket_mdata_free(ModData *m);
|
|
|
|
/* Global variables */
|
|
ModDataInfo *websocket_md;
|
|
static int ws_text_mode_available = 1;
|
|
|
|
MOD_TEST()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, _websocket_handle_websocket);
|
|
EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET, _websocket_create_packet);
|
|
EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_EX, _websocket_create_packet_ex);
|
|
EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, _websocket_create_packet_simple);
|
|
|
|
/* Init first, since we manage sockets */
|
|
ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT);
|
|
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
ModDataInfo mreq;
|
|
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
|
|
memset(&mreq, 0, sizeof(mreq));
|
|
mreq.name = "websocket";
|
|
mreq.serialize = websocket_mdata_serialize;
|
|
mreq.unserialize = websocket_mdata_unserialize;
|
|
mreq.free = websocket_mdata_free;
|
|
mreq.sync = MODDATA_SYNC_EARLY;
|
|
mreq.type = MODDATATYPE_CLIENT;
|
|
websocket_md = ModDataAdd(modinfo->handle, mreq);
|
|
|
|
/* Unload last, since we manage sockets */
|
|
ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD);
|
|
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len))
|
|
{
|
|
int n;
|
|
char *ptr;
|
|
int length;
|
|
int length1 = WSU(client)->lefttoparselen;
|
|
char readbuf[MAXLINELENGTH];
|
|
|
|
length = length1 + length2;
|
|
if (length > sizeof(readbuf)-1)
|
|
{
|
|
dead_socket(client, "Illegal buffer stacking/Excess flood");
|
|
return 0;
|
|
}
|
|
|
|
if (length1 > 0)
|
|
memcpy(readbuf, WSU(client)->lefttoparse, length1);
|
|
memcpy(readbuf+length1, readbuf2, length2);
|
|
|
|
safe_free(WSU(client)->lefttoparse);
|
|
WSU(client)->lefttoparselen = 0;
|
|
|
|
ptr = readbuf;
|
|
do {
|
|
n = websocket_handle_packet(client, ptr, length, callback);
|
|
if (n < 0)
|
|
return -1; /* killed -- STOP processing */
|
|
if (n == 0)
|
|
{
|
|
/* Short read. Stop processing for now, but save data for next time */
|
|
safe_free(WSU(client)->lefttoparse);
|
|
WSU(client)->lefttoparse = safe_alloc(length);
|
|
WSU(client)->lefttoparselen = length;
|
|
memcpy(WSU(client)->lefttoparse, ptr, length);
|
|
return 0;
|
|
}
|
|
length -= n;
|
|
ptr += n;
|
|
if (length < 0)
|
|
abort(); /* less than 0 is impossible */
|
|
} while(length > 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** WebSocket packet handler.
|
|
* For more information on the format, check out page 28 of RFC6455.
|
|
* @returns The number of bytes processed (the size of the frame)
|
|
* OR 0 to indicate a possible short read (want more data)
|
|
* OR -1 in case of an error.
|
|
*/
|
|
int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len))
|
|
{
|
|
char opcode; /**< Opcode */
|
|
char masked; /**< Masked */
|
|
int len; /**< Length of the packet */
|
|
char maskkey[4]; /**< Key used for masking */
|
|
const char *p;
|
|
int total_packet_size;
|
|
char *payload = NULL;
|
|
static char payloadbuf[MAXLINELENGTH];
|
|
int maskkeylen = 4;
|
|
|
|
if (length < 4)
|
|
{
|
|
/* WebSocket packet too short */
|
|
return 0;
|
|
}
|
|
|
|
/* fin = readbuf[0] & 0x80; -- unused */
|
|
opcode = readbuf[0] & 0x7F;
|
|
masked = readbuf[1] & 0x80;
|
|
len = readbuf[1] & 0x7F;
|
|
p = &readbuf[2]; /* point to next element */
|
|
|
|
/* actually 'fin' is unused.. we don't care. */
|
|
|
|
/* Masked. According to RFC6455 page 29:
|
|
* "All frames sent from client to server have this bit set to 1."
|
|
* But in practice i see that for PONG this may not always be
|
|
* true, so let's make an exception for that...
|
|
*/
|
|
if (!masked && (opcode != WSOP_PONG))
|
|
{
|
|
dead_socket(client, "WebSocket packet not masked");
|
|
return -1; /* Having the masked bit set is required (RFC6455 p29) */
|
|
}
|
|
|
|
if (!masked)
|
|
maskkeylen = 0;
|
|
|
|
if (len == 127)
|
|
{
|
|
dead_socket(client, "WebSocket packet with insane size");
|
|
return -1; /* Packets requiring 64bit lengths are not supported. Would be insane. */
|
|
}
|
|
|
|
total_packet_size = len + 2 + maskkeylen; /* 2 for header, 4 for mask key, rest for payload */
|
|
|
|
/* Early (minimal) length check */
|
|
if (length < total_packet_size)
|
|
{
|
|
/* WebSocket frame too short */
|
|
return 0;
|
|
}
|
|
|
|
/* Len=126 is special. It indicates the data length is actually "126 or more" */
|
|
if (len == 126)
|
|
{
|
|
/* Extended payload length (16 bit). For packets of >=126 bytes */
|
|
len = (readbuf[2] << 8) + readbuf[3];
|
|
if (len < 126)
|
|
{
|
|
dead_socket(client, "WebSocket protocol violation (extended payload length too short)");
|
|
return -1; /* This is a violation (not a short read), see page 29 */
|
|
}
|
|
p += 2; /* advance pointer 2 bytes */
|
|
|
|
/* Need to check the length again, now it has changed: */
|
|
if (length < len + 4 + maskkeylen)
|
|
{
|
|
/* WebSocket frame too short */
|
|
return 0;
|
|
}
|
|
/* And update the packet size */
|
|
total_packet_size = len + 4 + maskkeylen; /* 4 for header, 4 for mask key, rest for payload */
|
|
}
|
|
|
|
if (masked)
|
|
{
|
|
memcpy(maskkey, p, maskkeylen);
|
|
p+= maskkeylen;
|
|
}
|
|
|
|
if (len > 0)
|
|
{
|
|
memcpy(payloadbuf, p, len);
|
|
payload = payloadbuf;
|
|
} /* else payload is NULL */
|
|
|
|
if (masked && (len > 0))
|
|
{
|
|
/* Unmask this thing (page 33, section 5.3) */
|
|
int n;
|
|
char v;
|
|
char *p;
|
|
for (p = payload, n = 0; n < len; n++)
|
|
{
|
|
v = *p;
|
|
*p++ = v ^ maskkey[n % 4];
|
|
}
|
|
}
|
|
|
|
switch(opcode)
|
|
{
|
|
case WSOP_CONTINUATION:
|
|
case WSOP_TEXT:
|
|
case WSOP_BINARY:
|
|
if (len > 0)
|
|
{
|
|
if (!callback(client, payload, len))
|
|
return -1; /* fatal error occured (such as flood kill) */
|
|
}
|
|
return total_packet_size;
|
|
|
|
case WSOP_CLOSE:
|
|
dead_socket(client, "Connection closed"); /* TODO: Improve I guess */
|
|
return -1;
|
|
|
|
case WSOP_PING:
|
|
if (websocket_handle_packet_ping(client, payload, len) < 0)
|
|
return -1;
|
|
return total_packet_size;
|
|
|
|
case WSOP_PONG:
|
|
if (websocket_handle_packet_pong(client, payload, len) < 0)
|
|
return -1;
|
|
return total_packet_size;
|
|
|
|
default:
|
|
dead_socket(client, "WebSocket: Unknown opcode");
|
|
return -1;
|
|
}
|
|
|
|
return -1; /* NOTREACHED */
|
|
}
|
|
|
|
int websocket_handle_packet_ping(Client *client, const char *buf, int len)
|
|
{
|
|
if (len > 500)
|
|
{
|
|
dead_socket(client, "WebSocket: oversized PING request");
|
|
return -1;
|
|
}
|
|
websocket_send_pong(client, buf, len);
|
|
add_fake_lag(client, 1000); /* lag penalty of 1 second */
|
|
return 0;
|
|
}
|
|
|
|
int websocket_handle_packet_pong(Client *client, const char *buf, int len)
|
|
{
|
|
/* We only care about pongs for RPC websocket connections.
|
|
* Also, we don't verify the content, actually,
|
|
* so don't use this for security like a pingpong cookie.
|
|
*/
|
|
if (IsRPC(client))
|
|
{
|
|
client->local->last_msg_received = TStime();
|
|
ClearPingSent(client);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Create a simple websocket packet that is ready to be sent.
|
|
* This is the simple version that is used ONLY for WSOP_PONG,
|
|
* as it does not take \r\n into account.
|
|
*/
|
|
int _websocket_create_packet_simple(int opcode, const char **buf, int *len)
|
|
{
|
|
static char sendbuf[8192];
|
|
|
|
sendbuf[0] = opcode | 0x80; /* opcode & final */
|
|
|
|
if (*len > sizeof(sendbuf) - 8)
|
|
return -1; /* should never happen (safety) */
|
|
|
|
if (*len < 126)
|
|
{
|
|
/* Short payload */
|
|
sendbuf[1] = (char)*len;
|
|
memcpy(&sendbuf[2], *buf, *len);
|
|
*buf = sendbuf;
|
|
*len += 2;
|
|
} else {
|
|
/* Long payload */
|
|
sendbuf[1] = 126;
|
|
sendbuf[2] = (char)((*len >> 8) & 0xFF);
|
|
sendbuf[3] = (char)(*len & 0xFF);
|
|
memcpy(&sendbuf[4], *buf, *len);
|
|
*buf = sendbuf;
|
|
*len += 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Create a websocket packet that is ready to be send.
|
|
* This version takes into account stripping off \r and \n,
|
|
* and possibly multi line due to labeled-response.
|
|
* It is used for WSOP_TEXT and WSOP_BINARY.
|
|
* The end result is one or more websocket frames,
|
|
* all in a single packet *buf with size *len.
|
|
*
|
|
* This is the version that uses the specified buffer,
|
|
* it is used from the JSON-RPC code,
|
|
* and indirectly from websocket_create_packet().
|
|
*/
|
|
int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
|
|
{
|
|
char *s = *buf; /* points to start of current line */
|
|
char *s2; /* used for searching of end of current line */
|
|
char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
|
|
int bytes_to_copy;
|
|
char newline;
|
|
char *o = sendbuf; /* points to current byte within 'sendbuf' of output buffer */
|
|
int bytes_in_sendbuf = 0;
|
|
int bytes_single_frame;
|
|
|
|
/* Sending 0 bytes makes no sense, and the code below may assume >0, so reject this. */
|
|
if (*len == 0)
|
|
return -1;
|
|
|
|
do {
|
|
/* Find next \r or \n */
|
|
for (s2 = s; *s2 && (s2 <= lastbyte); s2++)
|
|
{
|
|
if ((*s2 == '\n') || (*s2 == '\r'))
|
|
break;
|
|
}
|
|
|
|
/* Now 's' points to start of line and 's2' points to beyond end of the line
|
|
* (either at \r, \n or beyond the buffer).
|
|
*/
|
|
bytes_to_copy = s2 - s;
|
|
|
|
if (bytes_to_copy < 126)
|
|
bytes_single_frame = 2 + bytes_to_copy;
|
|
else if (bytes_to_copy < 65536)
|
|
bytes_single_frame = 4 + bytes_to_copy;
|
|
else
|
|
bytes_single_frame = 10 + bytes_to_copy;
|
|
|
|
if (bytes_in_sendbuf + bytes_single_frame > sendbufsize)
|
|
{
|
|
/* Overflow. This should never happen. */
|
|
unreal_log(ULOG_WARNING, "websocket", "BUG_WEBSOCKET_OVERFLOW", NULL,
|
|
"[BUG] [websocket] Overflow prevented in _websocket_create_packet(): "
|
|
"$bytes_in_sendbuf + $bytes_single_frame > $sendbuf_size",
|
|
log_data_integer("bytes_in_sendbuf", bytes_in_sendbuf),
|
|
log_data_integer("bytes_single_frame", bytes_single_frame),
|
|
log_data_integer("sendbuf_size", sendbufsize));
|
|
return -1;
|
|
}
|
|
|
|
/* Create the new frame */
|
|
o[0] = opcode | 0x80; /* opcode & final */
|
|
|
|
if (bytes_to_copy < 126)
|
|
{
|
|
/* Short payload */
|
|
o[1] = (char)bytes_to_copy;
|
|
memcpy(&o[2], s, bytes_to_copy);
|
|
} else
|
|
if (bytes_to_copy < 65536)
|
|
{
|
|
/* Long payload */
|
|
o[1] = 126;
|
|
o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
|
|
o[3] = (char)(bytes_to_copy & 0xFF);
|
|
memcpy(&o[4], s, bytes_to_copy);
|
|
} else {
|
|
/* Longest payload */
|
|
// XXX: yeah we don't support sending more than 4GB.
|
|
o[1] = 127;
|
|
o[2] = 0;
|
|
o[3] = 0;
|
|
o[4] = 0;
|
|
o[5] = 0;
|
|
o[6] = (char)((bytes_to_copy >> 24) & 0xFF);
|
|
o[7] = (char)((bytes_to_copy >> 16) & 0xFF);
|
|
o[8] = (char)((bytes_to_copy >> 8) & 0xFF);
|
|
o[9] = (char)(bytes_to_copy & 0xFF);
|
|
memcpy(&o[10], s, bytes_to_copy);
|
|
}
|
|
|
|
/* Advance destination pointer and counter */
|
|
o += bytes_single_frame;
|
|
bytes_in_sendbuf += bytes_single_frame;
|
|
|
|
/* Advance source pointer and skip all trailing \n and \r */
|
|
for (s = s2; *s && (s <= lastbyte) && ((*s == '\n') || (*s == '\r')); s++);
|
|
} while(s <= lastbyte);
|
|
|
|
*buf = sendbuf;
|
|
*len = bytes_in_sendbuf;
|
|
return 0;
|
|
}
|
|
|
|
/** Create a websocket packet that is ready to be send.
|
|
* This version takes into account stripping off \r and \n,
|
|
* and possibly multi line due to labeled-response.
|
|
* It is used for WSOP_TEXT and WSOP_BINARY.
|
|
* The end result is one or more websocket frames,
|
|
* all in a single packet *buf with size *len.
|
|
*
|
|
* This is the version that uses a static sendbuf buffer,
|
|
* it is used from IRC websockets.
|
|
*/
|
|
int _websocket_create_packet(int opcode, char **buf, int *len)
|
|
{
|
|
static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
|
|
return _websocket_create_packet_ex(opcode, buf, len, sendbuf, sizeof(sendbuf));
|
|
}
|
|
|
|
/** Create and send a WSOP_PONG frame */
|
|
int websocket_send_pong(Client *client, const char *buf, int len)
|
|
{
|
|
const char *b = buf;
|
|
int l = len;
|
|
|
|
if (_websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
|
|
return -1;
|
|
|
|
if (DBufLength(&client->local->sendQ) > get_sendq(client))
|
|
{
|
|
dead_socket(client, "Max SendQ exceeded");
|
|
return -1;
|
|
}
|
|
|
|
dbuf_put(&client->local->sendQ, b, l);
|
|
send_queued(client);
|
|
return 0;
|
|
}
|
|
|
|
/** UnrealIRCd internals: free WebSocketUser object. */
|
|
void websocket_mdata_free(ModData *m)
|
|
{
|
|
WebSocketUser *wsu = (WebSocketUser *)m->ptr;
|
|
if (wsu)
|
|
{
|
|
safe_free(wsu->handshake_key);
|
|
safe_free(wsu->lefttoparse);
|
|
safe_free(wsu->sec_websocket_protocol);
|
|
safe_free(m->ptr);
|
|
}
|
|
}
|
|
|
|
/** This only serializes wsu->type atm */
|
|
const char *websocket_mdata_serialize(ModData *m)
|
|
{
|
|
static char buf[32];
|
|
WebSocketUser *wsu = m->ptr;
|
|
|
|
if (!wsu)
|
|
return NULL; /* not set */
|
|
|
|
snprintf(buf, sizeof(buf), "%d", wsu->type);
|
|
return buf;
|
|
}
|
|
|
|
/** This only sets wsu->type atm */
|
|
void websocket_mdata_unserialize(const char *str, ModData *m)
|
|
{
|
|
WebSocketUser *wsu;
|
|
if (m->ptr)
|
|
websocket_mdata_free(m);
|
|
if (BadPtr(str))
|
|
return; /* empty/freed */
|
|
m->ptr = wsu = safe_alloc(sizeof(WebSocketUser));
|
|
wsu->type = atoi(str);
|
|
}
|