unrealircd

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

websocket.c (20463B)

      1 /*
      2  * websocket - WebSocket support (RFC6455)
      3  * (C)Copyright 2016 Bram Matthys and the UnrealIRCd team
      4  * License: GPLv2 or later
      5  * This module was sponsored by Aberrant Software Inc.
      6  */
      7    
      8 #include "unrealircd.h"
      9 #include "dns.h"
     10 
     11 #define WEBSOCKET_VERSION "1.1.0"
     12 
     13 ModuleHeader MOD_HEADER
     14   = {
     15 	"websocket",
     16 	WEBSOCKET_VERSION,
     17 	"WebSocket support (RFC6455)",
     18 	"UnrealIRCd Team",
     19 	"unrealircd-6",
     20     };
     21 
     22 #if CHAR_MIN < 0
     23  #error "In UnrealIRCd char should always be unsigned. Check your compiler"
     24 #endif
     25 
     26 #ifndef WEBSOCKET_SEND_BUFFER_SIZE
     27  #define WEBSOCKET_SEND_BUFFER_SIZE 16384
     28 #endif
     29 
     30 #define WSU(client)	((WebSocketUser *)moddata_client(client, websocket_md).ptr)
     31 
     32 #define WEBSOCKET_PORT(client)	((client->local && client->local->listener) ? client->local->listener->websocket_options : 0)
     33 #define WEBSOCKET_TYPE(client)	(WSU(client)->type)
     34 
     35 /* used to parse http Forwarded header (RFC 7239) */
     36 #define IPLEN 48
     37 #define FHEADER_NAMELEN	20
     38 
     39 struct HTTPForwardedHeader
     40 {
     41 	int secure;
     42 	char hostname[HOSTLEN+1];
     43 	char ip[IPLEN+1];
     44 };
     45 
     46 /* Forward declarations */
     47 int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
     48 int websocket_config_posttest(int *);
     49 int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr);
     50 int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length);
     51 int websocket_handle_handshake(Client *client, const char *readbuf, int *length);
     52 int websocket_handshake_send_response(Client *client);
     53 int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2);
     54 int websocket_secure_connect(Client *client);
     55 struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input);
     56 int websocket_ip_compare(const char *ip1, const char *ip2);
     57 int websocket_handle_request(Client *client, WebRequest *web);
     58 
     59 /* Global variables */
     60 ModDataInfo *websocket_md;
     61 static int ws_text_mode_available = 1;
     62 
     63 MOD_TEST()
     64 {
     65 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, websocket_config_test);
     66 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, websocket_config_posttest);
     67 
     68 	/* Call MOD_INIT very early, since we manage sockets, but depend on websocket_common */
     69 	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_INIT+1);
     70 	return MOD_SUCCESS;
     71 }
     72 
     73 MOD_INIT()
     74 {
     75 	ModDataInfo mreq;
     76 
     77 	MARK_AS_OFFICIAL_MODULE(modinfo);
     78 
     79 	websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT);
     80 	if (!websocket_md)
     81 		config_warn("The 'websocket_common' module is not loaded, even though it was promised to be ???");
     82 
     83 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN_EX, 0, websocket_config_run_ex);
     84 	HookAdd(modinfo->handle, HOOKTYPE_PACKET, INT_MAX, websocket_packet_out);
     85 	HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, websocket_secure_connect);
     86 
     87 	/* Call MOD_LOAD very late, since we manage sockets, but depend on websocket_common */
     88 	ModuleSetOptions(modinfo->handle, MOD_OPT_PRIORITY, WEBSOCKET_MODULE_PRIORITY_UNLOAD-1);
     89 	return MOD_SUCCESS;
     90 }
     91 
     92 MOD_LOAD()
     93 {
     94 	if (non_utf8_nick_chars_in_use || (iConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY))
     95 		ws_text_mode_available = 0;
     96 	return MOD_SUCCESS;
     97 }
     98 
     99 MOD_UNLOAD()
    100 {
    101 	return MOD_SUCCESS;
    102 }
    103 
    104 int websocket_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    105 {
    106 	int errors = 0;
    107 	ConfigEntry *cep;
    108 	int has_type = 0;
    109 	static char errored_once_nick = 0;
    110 
    111 	if (type != CONFIG_LISTEN_OPTIONS)
    112 		return 0;
    113 
    114 	/* We are only interrested in listen::options::websocket.. */
    115 	if (!ce || !ce->name || strcmp(ce->name, "websocket"))
    116 		return 0;
    117 
    118 	for (cep = ce->items; cep; cep = cep->next)
    119 	{
    120 		if (!strcmp(cep->name, "type"))
    121 		{
    122 			CheckNull(cep);
    123 			has_type = 1;
    124 			if (!strcmp(cep->value, "text"))
    125 			{
    126 				if (non_utf8_nick_chars_in_use && !errored_once_nick)
    127 				{
    128 					/* This one is a hard error, since the consequences are grave */
    129 					config_error("You have a websocket listener with type 'text' AND your set::allowed-nickchars contains at least one non-UTF8 character set.");
    130 					config_error("This is a very BAD idea as this makes your websocket vulnerable to UTF8 conversion attacks. "
    131 					             "This can cause things like unkickable users and 'ghosts' for websocket users.");
    132 					config_error("You have 4 options: 1) Remove the websocket listener, 2) Use websocket type 'binary', "
    133 					             "3) Remove the non-UTF8 character set from set::allowed-nickchars, 4) Replace the non-UTF8 with an UTF8 character set in set::allowed-nickchars");
    134 					config_error("For more details see https://www.unrealircd.org/docs/WebSocket_support#websockets-and-non-utf8");
    135 					errored_once_nick = 1;
    136 					errors++;
    137 				}
    138 			}
    139 			else if (!strcmp(cep->value, "binary"))
    140 			{
    141 			}
    142 			else
    143 			{
    144 				config_error("%s:%i: listen::options::websocket::type must be either 'binary' or 'text' (not '%s')",
    145 					cep->file->filename, cep->line_number, cep->value);
    146 				errors++;
    147 			}
    148 		} else if (!strcmp(cep->name, "forward"))
    149 		{
    150 			if (!cep->value)
    151 			{
    152 				config_error_empty(cep->file->filename, cep->line_number, "listen::options::websocket::forward", cep->name);
    153 				errors++;
    154 				continue;
    155 			}
    156 			if (!is_valid_ip(cep->value))
    157 			{
    158 				config_error("%s:%i: invalid IP address '%s' in listen::options::websocket::forward", cep->file->filename, cep->line_number, cep->value);
    159 				errors++;
    160 				continue;
    161 			}
    162 		} else
    163 		{
    164 			config_error("%s:%i: unknown directive listen::options::websocket::%s",
    165 				cep->file->filename, cep->line_number, cep->name);
    166 			errors++;
    167 			continue;
    168 		}
    169 	}
    170 
    171 	if (!has_type)
    172 	{
    173 		config_error("%s:%i: websocket set, but type unspecified. Use something like: listen { ip *; port 443; websocket { type text; } }",
    174 			ce->file->filename, ce->line_number);
    175 		errors++;
    176 	}
    177 
    178 	*errs = errors;
    179 	return errors ? -1 : 1;
    180 }
    181 
    182 int websocket_config_run_ex(ConfigFile *cf, ConfigEntry *ce, int type, void *ptr)
    183 {
    184 	ConfigEntry *cep, *cepp;
    185 	ConfigItem_listen *l;
    186 	static char warned_once_channel = 0;
    187 
    188 	if (type != CONFIG_LISTEN_OPTIONS)
    189 		return 0;
    190 
    191 	/* We are only interrested in listen::options::websocket.. */
    192 	if (!ce || !ce->name || strcmp(ce->name, "websocket"))
    193 		return 0;
    194 
    195 	l = (ConfigItem_listen *)ptr;
    196 	l->webserver = safe_alloc(sizeof(WebServer));
    197 	l->webserver->handle_request = websocket_handle_request;
    198 	l->webserver->handle_body = websocket_handle_body_websocket;
    199 
    200 	for (cep = ce->items; cep; cep = cep->next)
    201 	{
    202 		if (!strcmp(cep->name, "type"))
    203 		{
    204 			if (!strcmp(cep->value, "binary"))
    205 				l->websocket_options = WEBSOCKET_TYPE_BINARY;
    206 			else if (!strcmp(cep->value, "text"))
    207 			{
    208 				l->websocket_options = WEBSOCKET_TYPE_TEXT;
    209 				if ((tempiConf.allowed_channelchars == ALLOWED_CHANNELCHARS_ANY) && !warned_once_channel)
    210 				{
    211 					/* This one is a warning, since the consequences are less grave than with nicks */
    212 					config_warn("You have a websocket listener with type 'text' AND your set::allowed-channelchars is set to 'any'.");
    213 					config_warn("This is not a recommended combination as this makes your websocket vulnerable to UTF8 conversion attacks. "
    214 					            "This can cause things like unpartable channels for websocket users.");
    215 					config_warn("It is highly recommended that you use set { allowed-channelchars utf8; }");
    216 					config_warn("For more details see https://www.unrealircd.org/docs/WebSocket_support#websockets-and-non-utf8");
    217 					warned_once_channel = 1;
    218 				}
    219 			}
    220 		} else if (!strcmp(cep->name, "forward"))
    221 		{
    222 			safe_strdup(l->websocket_forward, cep->value);
    223 		}
    224 	}
    225 	return 1;
    226 }
    227 
    228 int websocket_config_posttest(int *errs)
    229 {
    230 	int errors = 0;
    231 	char webserver_module = 1, websocket_common_module = 1;
    232 
    233 	if (!is_module_loaded("webserver"))
    234 	{
    235 		config_error("The 'websocket' module requires the 'webserver' module to be loaded, otherwise websocket connections will not work!");
    236 		webserver_module = 0;
    237 		errors++;
    238 	}
    239 
    240 	if (!is_module_loaded("websocket_common"))
    241 	{
    242 		config_error("The 'websocket' module requires the 'websocket_common' module to be loaded, otherwise websocket connections will not work!");
    243 		websocket_common_module = 0;
    244 		errors++;
    245 	}
    246 
    247 	/* Is nicer for the admin when these are grouped... */
    248 	if (!webserver_module)
    249 		config_error("Add the following line to your config file: loadmodule \"webserver\";");
    250 	if (!websocket_common_module)
    251 		config_error("Add the following line to your config file: loadmodule \"websocket_common\";");
    252 
    253 	*errs = errors;
    254 	return errors ? -1 : 1;
    255 }
    256 
    257 /* Add LF (if needed) to a buffer. Max 4K. */
    258 void add_lf_if_needed(char **buf, int *len)
    259 {
    260 	static char newbuf[MAXLINELENGTH];
    261 	char *b = *buf;
    262 	int l = *len;
    263 
    264 	if (l <= 0)
    265 		return; /* too short */
    266 
    267 	if (b[l - 1] == '\n')
    268 		return; /* already contains \n */
    269 
    270 	if (l >= sizeof(newbuf)-2)
    271 		l = sizeof(newbuf)-2; /* cut-off if necessary */
    272 
    273 	memcpy(newbuf, b, l);
    274 	newbuf[l] = '\n';
    275 	newbuf[l + 1] = '\0'; /* not necessary, but I like zero termination */
    276 	l++;
    277 	*buf = newbuf; /* new buffer */
    278 	*len = l; /* new length */
    279 }
    280 
    281 /** Called on decoded websocket frame (INPUT).
    282  * Should contain exactly 1 IRC line (command)
    283  */
    284 int websocket_irc_callback(Client *client, char *buf, int len)
    285 {
    286 	add_lf_if_needed(&buf, &len);
    287 	if (!process_packet(client, buf, len, 1)) /* Let UnrealIRCd handle this as usual */
    288 		return 0; /* client killed */
    289 	return 1;
    290 }
    291 
    292 int websocket_handle_body_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2)
    293 {
    294 	return websocket_handle_websocket(client, web, readbuf2, length2, websocket_irc_callback);
    295 }
    296 
    297 /** Outgoing packet hook.
    298  * This transforms the output to be Websocket-compliant, if necessary.
    299  */
    300 int websocket_packet_out(Client *from, Client *to, Client *intended_to, char **msg, int *length)
    301 {
    302 	static char utf8buf[510];
    303 
    304 	if (MyConnect(to) && !IsRPC(to) && websocket_md && WSU(to) && WSU(to)->handshake_completed)
    305 	{
    306 		if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_BINARY)
    307 			websocket_create_packet(WSOP_BINARY, msg, length);
    308 		else if (WEBSOCKET_TYPE(to) == WEBSOCKET_TYPE_TEXT)
    309 		{
    310 			/* Some more conversions are needed */
    311 			char *safe_msg = unrl_utf8_make_valid(*msg, utf8buf, sizeof(utf8buf), 1);
    312 			*msg = safe_msg;
    313 			*length = *msg ? strlen(safe_msg) : 0;
    314 			websocket_create_packet(WSOP_TEXT, msg, length);
    315 		}
    316 		return 0;
    317 	}
    318 	return 0;
    319 }
    320 
    321 #define FHEADER_STATE_NAME	0
    322 #define FHEADER_STATE_VALUE	1
    323 #define FHEADER_STATE_VALUE_QUOTED	2
    324 
    325 #define FHEADER_ACTION_APPEND	0
    326 #define FHEADER_ACTION_IGNORE	1
    327 #define FHEADER_ACTION_PROCESS	2
    328 
    329 /** If a valid Forwarded: http header is received from a trusted source (proxy server), this function will
    330   * extract remote IP address and secure (https) status from it. If more than one field with same name is received,
    331   * we'll accept the last one. This should work correctly with chained proxies. */
    332 struct HTTPForwardedHeader *websocket_parse_forwarded_header(char *input)
    333 {
    334 	static struct HTTPForwardedHeader forwarded;
    335 	int i, length;
    336 	int state = FHEADER_STATE_NAME, action = FHEADER_ACTION_APPEND;
    337 	char name[FHEADER_NAMELEN+1];
    338 	char value[IPLEN+1];
    339 	int name_length = 0;
    340 	int value_length = 0;
    341 	char c;
    342 	
    343 	memset(&forwarded, 0, sizeof(struct HTTPForwardedHeader));
    344 	
    345 	length = strlen(input);
    346 	for (i = 0; i < length; i++)
    347 	{
    348 		c = input[i];
    349 		switch (c)
    350 		{
    351 			case '"':
    352 				switch (state)
    353 				{
    354 					case FHEADER_STATE_NAME:
    355 						action = FHEADER_ACTION_APPEND;
    356 						break;
    357 					case FHEADER_STATE_VALUE:
    358 						action = FHEADER_ACTION_IGNORE;
    359 						state = FHEADER_STATE_VALUE_QUOTED;
    360 						break;
    361 					case FHEADER_STATE_VALUE_QUOTED:
    362 						action = FHEADER_ACTION_IGNORE;
    363 						state = FHEADER_STATE_VALUE;
    364 						break;
    365 				}
    366 				break;
    367 			case ',': case ';': case ' ':
    368 				switch (state)
    369 				{
    370 					case FHEADER_STATE_NAME: /* name without value */
    371 						name_length = 0;
    372 						action = FHEADER_ACTION_IGNORE;
    373 						break;
    374 					case FHEADER_STATE_VALUE: /* end of value */
    375 						action = FHEADER_ACTION_PROCESS;
    376 						break;
    377 					case FHEADER_STATE_VALUE_QUOTED: /* quoted character, process as normal */
    378 						action = FHEADER_ACTION_APPEND;
    379 						break;
    380 				}
    381 				break;
    382 			case '=':
    383 				switch (state)
    384 				{
    385 					case FHEADER_STATE_NAME: /* end of name */
    386 						name[name_length] = '\0';
    387 						state = FHEADER_STATE_VALUE;
    388 						action = FHEADER_ACTION_IGNORE;
    389 						break;
    390 					case FHEADER_STATE_VALUE: case FHEADER_STATE_VALUE_QUOTED: /* none of the values is expected to contain = but proceed anyway */
    391 						action = FHEADER_ACTION_APPEND;
    392 						break;
    393 				}
    394 				break;
    395 			default:
    396 				action = FHEADER_ACTION_APPEND;
    397 				break;
    398 		}
    399 		switch (action)
    400 		{
    401 			case FHEADER_ACTION_APPEND:
    402 				if (state == FHEADER_STATE_NAME)
    403 				{
    404 					if (name_length < FHEADER_NAMELEN)
    405 					{
    406 						name[name_length++] = c;
    407 					} else
    408 					{
    409 						/* truncate */
    410 					}
    411 				} else
    412 				{
    413 					if (value_length < IPLEN)
    414 					{
    415 						value[value_length++] = c;
    416 					} else
    417 					{
    418 						/* truncate */
    419 					}
    420 				}
    421 				break;
    422 			case FHEADER_ACTION_IGNORE: default:
    423 				break;
    424 			case FHEADER_ACTION_PROCESS:
    425 				value[value_length] = '\0';
    426 				name[name_length] = '\0';
    427 				if (!strcasecmp(name, "for"))
    428 				{
    429 					strlcpy(forwarded.ip, value, IPLEN+1);
    430 				} else if (!strcasecmp(name, "proto"))
    431 				{
    432 					if (!strcasecmp(value, "https"))
    433 					{
    434 						forwarded.secure = 1;
    435 					} else if (!strcasecmp(value, "http"))
    436 					{
    437 						forwarded.secure = 0;
    438 					} else
    439 					{
    440 						/* ignore unknown value */
    441 					}
    442 				} else
    443 				{
    444 					/* ignore unknown field name */
    445 				}
    446 				value_length = 0;
    447 				name_length = 0;
    448 				state = FHEADER_STATE_NAME;
    449 				break;
    450 		}
    451 	}
    452 	
    453 	return &forwarded;
    454 }
    455 
    456 /** We got a HTTP(S) request and we need to check if we can upgrade the connection
    457  * to a websocket connection.
    458  */
    459 int websocket_handle_request(Client *client, WebRequest *web)
    460 {
    461 	NameValuePrioList *r;
    462 	const char *key, *value;
    463 
    464 	/* Allocate a new WebSocketUser struct for this session */
    465 	moddata_client(client, websocket_md).ptr = safe_alloc(sizeof(WebSocketUser));
    466 	/* ...and set the default protocol (text or binary) */
    467 	WSU(client)->type = client->local->listener->websocket_options;
    468 
    469 	/** Now step through the lines.. **/
    470 	for (r = web->headers; r; r = r->next)
    471 	{
    472 		key = r->name;
    473 		value = r->value;
    474 		if (!strcasecmp(key, "Sec-WebSocket-Key"))
    475 		{
    476 			if (strchr(value, ':'))
    477 			{
    478 				/* This would cause unserialization issues. Should be base64 anyway */
    479 				webserver_send_response(client, 400, "Invalid characters in Sec-WebSocket-Key");
    480 				return -1;
    481 			}
    482 			safe_strdup(WSU(client)->handshake_key, value);
    483 		} else
    484 		if (!strcasecmp(key, "Sec-WebSocket-Protocol"))
    485 		{
    486 			/* Save it here, will be processed later */
    487 			safe_strdup(WSU(client)->sec_websocket_protocol, value);
    488 		} else
    489 		if (!strcasecmp(key, "Forwarded"))
    490 		{
    491 			/* will be processed later too */
    492 			safe_strdup(WSU(client)->forwarded, value);
    493 		}
    494 	}
    495 
    496 	/** Finally, validate the websocket request (handshake) and proceed or reject. */
    497 
    498 	/* Not websocket and webredir loaded? Let that module serve a redirect. */
    499 	if (!WSU(client)->handshake_key)
    500 	{
    501 		if (is_module_loaded("webredir"))
    502 		{
    503 			const char *parx[2] = { NULL, NULL };
    504 			do_cmd(client, NULL, "GET", 1, parx);
    505 		}
    506 		webserver_send_response(client, 404, "This port is for IRC WebSocket only");
    507 		return 0;
    508 	}
    509 
    510 	/* Sec-WebSocket-Protocol (optional) */
    511 	if (WSU(client)->sec_websocket_protocol)
    512 	{
    513 		char *p = NULL, *name;
    514 		int negotiated = 0;
    515 
    516 		for (name = strtoken(&p, WSU(client)->sec_websocket_protocol, ",");
    517 		     name;
    518 		     name = strtoken(&p, NULL, ","))
    519 		{
    520 			skip_whitespace(&name);
    521 			if (!strcmp(name, "binary.ircv3.net"))
    522 			{
    523 				negotiated = WEBSOCKET_TYPE_BINARY;
    524 				break; /* First hit wins */
    525 			} else
    526 			if (!strcmp(name, "text.ircv3.net") && ws_text_mode_available)
    527 			{
    528 				negotiated = WEBSOCKET_TYPE_TEXT;
    529 				break; /* First hit wins */
    530 			}
    531 		}
    532 		if (negotiated == WEBSOCKET_TYPE_BINARY)
    533 		{
    534 			WSU(client)->type = WEBSOCKET_TYPE_BINARY;
    535 			safe_strdup(WSU(client)->sec_websocket_protocol, "binary.ircv3.net");
    536 		} else
    537 		if (negotiated == WEBSOCKET_TYPE_TEXT)
    538 		{
    539 			WSU(client)->type = WEBSOCKET_TYPE_TEXT;
    540 			safe_strdup(WSU(client)->sec_websocket_protocol, "text.ircv3.net");
    541 		} else
    542 		{
    543 			/* Negotiation failed, fallback to the default (don't set it here) */
    544 			safe_free(WSU(client)->sec_websocket_protocol);
    545 		}
    546 	}
    547 
    548 	/* Check forwarded header (by k4be) */
    549 	if (WSU(client)->forwarded)
    550 	{
    551 		struct HTTPForwardedHeader *forwarded;
    552 		char oldip[64];
    553 
    554 		/* check for source ip */
    555 		if (BadPtr(client->local->listener->websocket_forward) || !websocket_ip_compare(client->local->listener->websocket_forward, client->ip))
    556 		{
    557 			unreal_log(ULOG_WARNING, "websocket", "UNAUTHORIZED_FORWARDED_HEADER", client, "Received unauthorized Forwarded header from $ip", log_data_string("ip", client->ip));
    558 			webserver_send_response(client, 403, "Forwarded: no access");
    559 			return 0;
    560 		}
    561 		/* parse the header */
    562 		forwarded = websocket_parse_forwarded_header(WSU(client)->forwarded);
    563 		/* check header values */
    564 		if (!is_valid_ip(forwarded->ip))
    565 		{
    566 			unreal_log(ULOG_WARNING, "websocket", "INVALID_FORWARDED_IP", client, "Received invalid IP in Forwarded header from $ip", log_data_string("ip", client->ip));
    567 			webserver_send_response(client, 400, "Forwarded: invalid IP");
    568 			return 0;
    569 		}
    570 		/* store data */
    571 		WSU(client)->secure = forwarded->secure;
    572 		strlcpy(oldip, client->ip, sizeof(oldip));
    573 		safe_strdup(client->ip, forwarded->ip);
    574 		/* Update client->local->hostp */
    575 		strlcpy(client->local->sockhost, forwarded->ip, sizeof(client->local->sockhost)); /* in case dns lookup fails or is disabled */
    576 		/* (free old) */
    577 		if (client->local->hostp)
    578 		{
    579 			unreal_free_hostent(client->local->hostp);
    580 			client->local->hostp = NULL;
    581 		}
    582 		/* (create new) */
    583 		if (!DONT_RESOLVE)
    584 		{
    585 			/* taken from socket.c */
    586 			struct hostent *he;
    587 			unrealdns_delreq_bycptr(client); /* in case the proxy ip is still in progress of being looked up */
    588 			ClearDNSLookup(client);
    589 			he = unrealdns_doclient(client); /* call this once more */
    590 			if (!client->local->hostp)
    591 			{
    592 				if (he)
    593 					client->local->hostp = he;
    594 				else
    595 					SetDNSLookup(client);
    596 			} else
    597 			{
    598 				/* Race condition detected, DNS has been done, continue with auth */
    599 			}
    600 		}
    601 		RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
    602 	}
    603 
    604 	websocket_handshake_send_response(client);
    605 	return 1;
    606 }
    607 
    608 int websocket_secure_connect(Client *client)
    609 {
    610 	/* Remove secure mode (-z) if the WEBIRC gateway did not ensure
    611 	 * us that their [client]--[webirc gateway] connection is also
    612 	 * secure (eg: using https)
    613 	 */
    614 	if (IsSecureConnect(client) && websocket_md && WSU(client) && WSU(client)->forwarded && !WSU(client)->secure)
    615 		client->umodes &= ~UMODE_SECURE;
    616 	return 0;
    617 }
    618 
    619 /** Complete the handshake by sending the appropriate HTTP 101 response etc. */
    620 int websocket_handshake_send_response(Client *client)
    621 {
    622 	char buf[512], hashbuf[64];
    623 	char sha1out[20]; /* 160 bits */
    624 
    625 	WSU(client)->handshake_completed = 1;
    626 
    627 	snprintf(buf, sizeof(buf), "%s%s", WSU(client)->handshake_key, WEBSOCKET_MAGIC_KEY);
    628 	sha1hash_binary(sha1out, buf, strlen(buf));
    629 	b64_encode(sha1out, sizeof(sha1out), hashbuf, sizeof(hashbuf));
    630 
    631 	snprintf(buf, sizeof(buf),
    632 	         "HTTP/1.1 101 Switching Protocols\r\n"
    633 	         "Upgrade: websocket\r\n"
    634 	         "Connection: Upgrade\r\n"
    635 	         "Sec-WebSocket-Accept: %s\r\n",
    636 	         hashbuf);
    637 
    638 	if (WSU(client)->sec_websocket_protocol)
    639 	{
    640 		/* using strlen() is safe here since above buffer will not
    641 		 * cause it to be >=512 and thus we won't get into negatives.
    642 		 */
    643 		snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
    644 		         "Sec-WebSocket-Protocol: %s\r\n",
    645 		         WSU(client)->sec_websocket_protocol);
    646 	}
    647 
    648 	strlcat(buf, "\r\n", sizeof(buf));
    649 
    650 	/* Caution: we bypass sendQ flood checking by doing it this way.
    651 	 * Risk is minimal, though, as we only permit limited text only
    652 	 * once per session.
    653 	 */
    654 	dbuf_put(&client->local->sendQ, buf, strlen(buf));
    655 	send_queued(client);
    656 
    657 	return 0;
    658 }
    659 
    660 /** Compare IP addresses (for authorization checking) */
    661 int websocket_ip_compare(const char *ip1, const char *ip2)
    662 {
    663 	uint32_t ip4[2];
    664 	uint16_t ip6[16];
    665 	int i;
    666 	if (inet_pton(AF_INET, ip1, &ip4[0]) == 1) /* IPv4 */
    667 	{
    668 		if (inet_pton(AF_INET, ip2, &ip4[1]) == 1) /* both are valid, let's compare */
    669 		{
    670 			return ip4[0] == ip4[1];
    671 		}
    672 		return 0;
    673 	}
    674 	if (inet_pton(AF_INET6, ip1, &ip6[0]) == 1) /* IPv6 */
    675 	{
    676 		if (inet_pton(AF_INET6, ip2, &ip6[8]) == 1)
    677 		{
    678 			for (i = 0; i < 8; i++)
    679 			{
    680 				if (ip6[i] != ip6[i+8])
    681 					return 0;
    682 			}
    683 			return 1;
    684 		}
    685 	}
    686 	return 0; /* neither valid IPv4 nor IPv6 */
    687 }
    688