pissircd/src/modules/websocket_common.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);
}