unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

websocket_common.c (14403B)

      1 /*
      2  * websocket_common - Common WebSocket functions (RFC6455)
      3  * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
      4  * License: GPLv2 or later
      5  * The websocket module was sponsored by Aberrant Software Inc.
      6  */
      7    
      8 #include "unrealircd.h"
      9 
     10 ModuleHeader MOD_HEADER
     11   = {
     12 	"websocket_common",
     13 	"6.0.0",
     14 	"WebSocket support (RFC6455)",
     15 	"UnrealIRCd Team",
     16 	"unrealircd-6",
     17     };
     18 
     19 #if CHAR_MIN < 0
     20  #error "In UnrealIRCd char should always be unsigned. Check your compiler"
     21 #endif
     22 
     23 #ifndef WEBSOCKET_SEND_BUFFER_SIZE
     24  #define WEBSOCKET_SEND_BUFFER_SIZE 16384
     25 #endif
     26 
     27 #define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)
     28 
     29 /* used to parse http Forwarded header (RFC 7239) */
     30 #define IPLEN 48
     31 #define FHEADER_NAMELEN	20
     32 
     33 struct HTTPForwardedHeader
     34 {
     35 	int secure;
     36 	char hostname[HOSTLEN+1];
     37 	char ip[IPLEN+1];
     38 };
     39 
     40 /* Forward declarations - public functions */
     41 int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
     42 int _websocket_create_packet(int opcode, char **buf, int *len);
     43 int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
     44 int _websocket_create_packet_simple(int opcode, const char **buf, int *len);
     45 /* Forward declarations - other */
     46 int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len));
     47 int websocket_handle_packet_ping(Client *client, const char *buf, int len);
     48 int websocket_handle_packet_pong(Client *client, const char *buf, int len);
     49 int websocket_send_pong(Client *client, const char *buf, int len);
     50 const char *websocket_mdata_serialize(ModData *m);
     51 void websocket_mdata_unserialize(const char *str, ModData *m);
     52 void websocket_mdata_free(ModData *m);
     53 
     54 /* Global variables */
     55 ModDataInfo *websocket_md;
     56 static int ws_text_mode_available = 1;
     57 
     58 MOD_TEST()
     59 {
     60 	MARK_AS_OFFICIAL_MODULE(modinfo);
     61 	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, _websocket_handle_websocket);
     62 	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET, _websocket_create_packet);
     63 	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_EX, _websocket_create_packet_ex);
     64 	EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, _websocket_create_packet_simple);
     65 
     66 	/* Init first, since we manage sockets */
     67 	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT);
     68 
     69 	return MOD_SUCCESS;
     70 }
     71 
     72 MOD_INIT()
     73 {
     74 	ModDataInfo mreq;
     75 
     76 	MARK_AS_OFFICIAL_MODULE(modinfo);
     77 
     78 	memset(&mreq, 0, sizeof(mreq));
     79 	mreq.name = "websocket";
     80 	mreq.serialize = websocket_mdata_serialize;
     81 	mreq.unserialize = websocket_mdata_unserialize;
     82 	mreq.free = websocket_mdata_free;
     83 	mreq.sync = MODDATA_SYNC_EARLY;
     84 	mreq.type = MODDATATYPE_CLIENT;
     85 	websocket_md = ModDataAdd(modinfo->handle, mreq);
     86 
     87 	/* Unload last, since we manage sockets */
     88 	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD);
     89 
     90 	return MOD_SUCCESS;
     91 }
     92 
     93 MOD_LOAD()
     94 {
     95 	return MOD_SUCCESS;
     96 }
     97 
     98 MOD_UNLOAD()
     99 {
    100 	return MOD_SUCCESS;
    101 }
    102 
    103 int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len))
    104 {
    105 	int n;
    106 	char *ptr;
    107 	int length;
    108 	int length1 = WSU(client)->lefttoparselen;
    109 	char readbuf[MAXLINELENGTH];
    110 
    111 	length = length1 + length2;
    112 	if (length > sizeof(readbuf)-1)
    113 	{
    114 		dead_socket(client, "Illegal buffer stacking/Excess flood");
    115 		return 0;
    116 	}
    117 
    118 	if (length1 > 0)
    119 		memcpy(readbuf, WSU(client)->lefttoparse, length1);
    120 	memcpy(readbuf+length1, readbuf2, length2);
    121 
    122 	safe_free(WSU(client)->lefttoparse);
    123 	WSU(client)->lefttoparselen = 0;
    124 
    125 	ptr = readbuf;
    126 	do {
    127 		n = websocket_handle_packet(client, ptr, length, callback);
    128 		if (n < 0)
    129 			return -1; /* killed -- STOP processing */
    130 		if (n == 0)
    131 		{
    132 			/* Short read. Stop processing for now, but save data for next time */
    133 			safe_free(WSU(client)->lefttoparse);
    134 			WSU(client)->lefttoparse = safe_alloc(length);
    135 			WSU(client)->lefttoparselen = length;
    136 			memcpy(WSU(client)->lefttoparse, ptr, length);
    137 			return 0;
    138 		}
    139 		length -= n;
    140 		ptr += n;
    141 		if (length < 0)
    142 			abort(); /* less than 0 is impossible */
    143 	} while(length > 0);
    144 
    145 	return 0;
    146 }
    147 
    148 /** WebSocket packet handler.
    149  * For more information on the format, check out page 28 of RFC6455.
    150  * @returns The number of bytes processed (the size of the frame)
    151  *          OR 0 to indicate a possible short read (want more data)
    152  *          OR -1 in case of an error.
    153  */
    154 int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len))
    155 {
    156 	char opcode; /**< Opcode */
    157 	char masked; /**< Masked */
    158 	int len; /**< Length of the packet */
    159 	char maskkey[4]; /**< Key used for masking */
    160 	const char *p;
    161 	int total_packet_size;
    162 	char *payload = NULL;
    163 	static char payloadbuf[READBUF_SIZE];
    164 	int maskkeylen = 4;
    165 
    166 	if (length < 4)
    167 	{
    168 		/* WebSocket packet too short */
    169 		return 0;
    170 	}
    171 
    172 	/* fin    = readbuf[0] & 0x80; -- unused */
    173 	opcode = readbuf[0] & 0x7F;
    174 	masked = readbuf[1] & 0x80;
    175 	len    = readbuf[1] & 0x7F;
    176 	p = &readbuf[2]; /* point to next element */
    177 
    178 	/* actually 'fin' is unused.. we don't care. */
    179 
    180 	/* Masked. According to RFC6455 page 29:
    181 	 * "All frames sent from client to server have this bit set to 1."
    182 	 * But in practice i see that for PONG this may not always be
    183 	 * true, so let's make an exception for that...
    184 	 */
    185 	if (!masked && (opcode != WSOP_PONG))
    186 	{
    187 		dead_socket(client, "WebSocket packet not masked");
    188 		return -1; /* Having the masked bit set is required (RFC6455 p29) */
    189 	}
    190 
    191 	if (!masked)
    192 		maskkeylen = 0;
    193 
    194 	if (len == 127)
    195 	{
    196 		dead_socket(client, "WebSocket packet with insane size");
    197 		return -1; /* Packets requiring 64bit lengths are not supported. Would be insane. */
    198 	}
    199 
    200 	total_packet_size = len + 2 + maskkeylen; /* 2 for header, 4 for mask key, rest for payload */
    201 
    202 	/* Early (minimal) length check */
    203 	if (length < total_packet_size)
    204 	{
    205 		/* WebSocket frame too short */
    206 		return 0;
    207 	}
    208 
    209 	/* Len=126 is special. It indicates the data length is actually "126 or more" */
    210 	if (len == 126)
    211 	{
    212 		/* Extended payload length (16 bit). For packets of >=126 bytes */
    213 		len = (readbuf[2] << 8) + readbuf[3];
    214 		if (len < 126)
    215 		{
    216 			dead_socket(client, "WebSocket protocol violation (extended payload length too short)");
    217 			return -1; /* This is a violation (not a short read), see page 29 */
    218 		}
    219 		p += 2; /* advance pointer 2 bytes */
    220 
    221 		/* Need to check the length again, now it has changed: */
    222 		if (length < len + 4 + maskkeylen)
    223 		{
    224 			/* WebSocket frame too short */
    225 			return 0;
    226 		}
    227 		/* And update the packet size */
    228 		total_packet_size = len + 4 + maskkeylen; /* 4 for header, 4 for mask key, rest for payload */
    229 	}
    230 
    231 	if (masked)
    232 	{
    233 		memcpy(maskkey, p, maskkeylen);
    234 		p+= maskkeylen;
    235 	}
    236 
    237 	if (len > 0)
    238 	{
    239 		memcpy(payloadbuf, p, len);
    240 		payload = payloadbuf;
    241 	} /* else payload is NULL */
    242 
    243 	if (masked && (len > 0))
    244 	{
    245 		/* Unmask this thing (page 33, section 5.3) */
    246 		int n;
    247 		char v;
    248 		char *p;
    249 		for (p = payload, n = 0; n < len; n++)
    250 		{
    251 			v = *p;
    252 			*p++ = v ^ maskkey[n % 4];
    253 		}
    254 	}
    255 
    256 	switch(opcode)
    257 	{
    258 		case WSOP_CONTINUATION:
    259 		case WSOP_TEXT:
    260 		case WSOP_BINARY:
    261 			if (len > 0)
    262 			{
    263 				if (!callback(client, payload, len))
    264 					return -1; /* fatal error occured (such as flood kill) */
    265 			}
    266 			return total_packet_size;
    267 
    268 		case WSOP_CLOSE:
    269 			dead_socket(client, "Connection closed"); /* TODO: Improve I guess */
    270 			return -1;
    271 
    272 		case WSOP_PING:
    273 			if (websocket_handle_packet_ping(client, payload, len) < 0)
    274 				return -1;
    275 			return total_packet_size;
    276 
    277 		case WSOP_PONG:
    278 			if (websocket_handle_packet_pong(client, payload, len) < 0)
    279 				return -1;
    280 			return total_packet_size;
    281 
    282 		default:
    283 			dead_socket(client, "WebSocket: Unknown opcode");
    284 			return -1;
    285 	}
    286 
    287 	return -1; /* NOTREACHED */
    288 }
    289 
    290 int websocket_handle_packet_ping(Client *client, const char *buf, int len)
    291 {
    292 	if (len > 500)
    293 	{
    294 		dead_socket(client, "WebSocket: oversized PING request");
    295 		return -1;
    296 	}
    297 	websocket_send_pong(client, buf, len);
    298 	add_fake_lag(client, 1000); /* lag penalty of 1 second */
    299 	return 0;
    300 }
    301 
    302 int websocket_handle_packet_pong(Client *client, const char *buf, int len)
    303 {
    304 	/* We only care about pongs for RPC websocket connections.
    305 	 * Also, we don't verify the content, actually,
    306 	 * so don't use this for security like a pingpong cookie.
    307 	 */
    308 	if (IsRPC(client))
    309 	{
    310 		client->local->last_msg_received = TStime();
    311 		ClearPingSent(client);
    312 	}
    313 	return 0;
    314 }
    315 
    316 /** Create a simple websocket packet that is ready to be sent.
    317  * This is the simple version that is used ONLY for WSOP_PONG,
    318  * as it does not take \r\n into account.
    319  */
    320 int _websocket_create_packet_simple(int opcode, const char **buf, int *len)
    321 {
    322 	static char sendbuf[8192];
    323 
    324 	sendbuf[0] = opcode | 0x80; /* opcode & final */
    325 
    326 	if (*len > sizeof(sendbuf) - 8)
    327 		return -1; /* should never happen (safety) */
    328 
    329 	if (*len < 126)
    330 	{
    331 		/* Short payload */
    332 		sendbuf[1] = (char)*len;
    333 		memcpy(&sendbuf[2], *buf, *len);
    334 		*buf = sendbuf;
    335 		*len += 2;
    336 	} else {
    337 		/* Long payload */
    338 		sendbuf[1] = 126;
    339 		sendbuf[2] = (char)((*len >> 8) & 0xFF);
    340 		sendbuf[3] = (char)(*len & 0xFF);
    341 		memcpy(&sendbuf[4], *buf, *len);
    342 		*buf = sendbuf;
    343 		*len += 4;
    344 	}
    345 	return 0;
    346 }
    347 
    348 /** Create a websocket packet that is ready to be send.
    349  * This version takes into account stripping off \r and \n,
    350  * and possibly multi line due to labeled-response.
    351  * It is used for WSOP_TEXT and WSOP_BINARY.
    352  * The end result is one or more websocket frames,
    353  * all in a single packet *buf with size *len.
    354  *
    355  * This is the version that uses the specified buffer,
    356  * it is used from the JSON-RPC code,
    357  * and indirectly from websocket_create_packet().
    358  */
    359 int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
    360 {
    361 	char *s = *buf; /* points to start of current line */
    362 	char *s2; /* used for searching of end of current line */
    363 	char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
    364 	int bytes_to_copy;
    365 	char newline;
    366 	char *o = sendbuf; /* points to current byte within 'sendbuf' of output buffer */
    367 	int bytes_in_sendbuf = 0;
    368 	int bytes_single_frame;
    369 
    370 	/* Sending 0 bytes makes no sense, and the code below may assume >0, so reject this. */
    371 	if (*len == 0)
    372 		return -1;
    373 
    374 	do {
    375 		/* Find next \r or \n */
    376 		for (s2 = s; *s2 && (s2 <= lastbyte); s2++)
    377 		{
    378 			if ((*s2 == '\n') || (*s2 == '\r'))
    379 				break;
    380 		}
    381 
    382 		/* Now 's' points to start of line and 's2' points to beyond end of the line
    383 		 * (either at \r, \n or beyond the buffer).
    384 		 */
    385 		bytes_to_copy = s2 - s;
    386 
    387 		if (bytes_to_copy < 126)
    388 			bytes_single_frame = 2 + bytes_to_copy;
    389 		else if (bytes_to_copy < 65536)
    390 			bytes_single_frame = 4 + bytes_to_copy;
    391 		else
    392 			bytes_single_frame = 10 + bytes_to_copy;
    393 
    394 		if (bytes_in_sendbuf + bytes_single_frame > sendbufsize)
    395 		{
    396 			/* Overflow. This should never happen. */
    397 			unreal_log(ULOG_WARNING, "websocket", "BUG_WEBSOCKET_OVERFLOW", NULL,
    398 			           "[BUG] [websocket] Overflow prevented in _websocket_create_packet(): "
    399 			           "$bytes_in_sendbuf + $bytes_single_frame > $sendbuf_size",
    400 			           log_data_integer("bytes_in_sendbuf", bytes_in_sendbuf),
    401 			           log_data_integer("bytes_single_frame", bytes_single_frame),
    402 			           log_data_integer("sendbuf_size", sendbufsize));
    403 			return -1;
    404 		}
    405 
    406 		/* Create the new frame */
    407 		o[0] = opcode | 0x80; /* opcode & final */
    408 
    409 		if (bytes_to_copy < 126)
    410 		{
    411 			/* Short payload */
    412 			o[1] = (char)bytes_to_copy;
    413 			memcpy(&o[2], s, bytes_to_copy);
    414 		} else
    415 		if (bytes_to_copy < 65536)
    416 		{
    417 			/* Long payload */
    418 			o[1] = 126;
    419 			o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
    420 			o[3] = (char)(bytes_to_copy & 0xFF);
    421 			memcpy(&o[4], s, bytes_to_copy);
    422 		} else {
    423 			/* Longest payload */
    424 			// XXX: yeah we don't support sending more than 4GB.
    425 			o[1] = 127;
    426 			o[2] = 0;
    427 			o[3] = 0;
    428 			o[4] = 0;
    429 			o[5] = 0;
    430 			o[6] = (char)((bytes_to_copy >> 24) & 0xFF);
    431 			o[7] = (char)((bytes_to_copy >> 16) & 0xFF);
    432 			o[8] = (char)((bytes_to_copy >> 8) & 0xFF);
    433 			o[9] = (char)(bytes_to_copy & 0xFF);
    434 			memcpy(&o[10], s, bytes_to_copy);
    435 		}
    436 
    437 		/* Advance destination pointer and counter */
    438 		o += bytes_single_frame;
    439 		bytes_in_sendbuf += bytes_single_frame;
    440 
    441 		/* Advance source pointer and skip all trailing \n and \r */
    442 		for (s = s2; *s && (s <= lastbyte) && ((*s == '\n') || (*s == '\r')); s++);
    443 	} while(s <= lastbyte);
    444 
    445 	*buf = sendbuf;
    446 	*len = bytes_in_sendbuf;
    447 	return 0;
    448 }
    449 
    450 /** Create a websocket packet that is ready to be send.
    451  * This version takes into account stripping off \r and \n,
    452  * and possibly multi line due to labeled-response.
    453  * It is used for WSOP_TEXT and WSOP_BINARY.
    454  * The end result is one or more websocket frames,
    455  * all in a single packet *buf with size *len.
    456  *
    457  * This is the version that uses a static sendbuf buffer,
    458  * it is used from IRC websockets.
    459  */
    460 int _websocket_create_packet(int opcode, char **buf, int *len)
    461 {
    462 	static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
    463 	return _websocket_create_packet_ex(opcode, buf, len, sendbuf, sizeof(sendbuf));
    464 }
    465 
    466 /** Create and send a WSOP_PONG frame */
    467 int websocket_send_pong(Client *client, const char *buf, int len)
    468 {
    469 	const char *b = buf;
    470 	int l = len;
    471 
    472 	if (_websocket_create_packet_simple(WSOP_PONG, &b, &l) < 0)
    473 		return -1;
    474 
    475 	if (DBufLength(&client->local->sendQ) > get_sendq(client))
    476 	{
    477 		dead_socket(client, "Max SendQ exceeded");
    478 		return -1;
    479 	}
    480 
    481 	dbuf_put(&client->local->sendQ, b, l);
    482 	send_queued(client);
    483 	return 0;
    484 }
    485 
    486 /** UnrealIRCd internals: free WebSocketUser object. */
    487 void websocket_mdata_free(ModData *m)
    488 {
    489 	WebSocketUser *wsu = (WebSocketUser *)m->ptr;
    490 	if (wsu)
    491 	{
    492 		safe_free(wsu->handshake_key);
    493 		safe_free(wsu->lefttoparse);
    494 		safe_free(wsu->sec_websocket_protocol);
    495 		safe_free(wsu->forwarded);
    496 		safe_free(m->ptr);
    497 	}
    498 }
    499 
    500 /** This only serializes wsu->type atm */
    501 const char *websocket_mdata_serialize(ModData *m)
    502 {
    503 	static char buf[32];
    504 	WebSocketUser *wsu = m->ptr;
    505 
    506 	if (!wsu)
    507 		return NULL; /* not set */
    508 
    509 	snprintf(buf, sizeof(buf), "%d", wsu->type);
    510 	return buf;
    511 }
    512 
    513 /** This only sets wsu->type atm */
    514 void websocket_mdata_unserialize(const char *str, ModData *m)
    515 {
    516 	WebSocketUser *wsu;
    517 	if (m->ptr)
    518 		websocket_mdata_free(m);
    519 	if (BadPtr(str))
    520 		return; /* empty/freed */
    521 	m->ptr = wsu = safe_alloc(sizeof(WebSocketUser));
    522 	wsu->type = atoi(str);
    523 }