unrealircd

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

url_unreal.c (27767B)

      1 /*
      2  *   Unreal Internet Relay Chat Daemon, src/url.c
      3  *   (C) 2021 Bram Matthys and the UnrealIRCd team
      4  *
      5  *   This program is free software; you can redistribute it and/or modify
      6  *   it under the terms of the GNU General Public License as published by
      7  *   the Free Software Foundation; either version 1, or (at your option)
      8  *   any later version.
      9  *
     10  *   This program is distributed in the hope that it will be useful,
     11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  *   GNU General Public License for more details.
     14  *
     15  *   You should have received a copy of the GNU General Public License
     16  *   along with this program; if not, write to the Free Software
     17  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     18  */
     19 
     20 #include "unrealircd.h"
     21 #include "dns.h"
     22 
     23 /* Structs */
     24 
     25 /* Stores information about the async transfer.
     26  * Used to maintain information about the transfer
     27  * to trigger the callback upon completion.
     28  */
     29 typedef struct Download Download;
     30 
     31 struct Download
     32 {
     33 	Download *prev, *next;
     34 	vFP callback;
     35 	void *callback_data;
     36 	FILE *file_fd;		/**< File open for writing (otherwise NULL) */
     37 	char filename[PATH_MAX];
     38 	char *url; /*< must be free()d by url_do_transfers_async() */
     39 	char errorbuf[512];
     40 	time_t cachetime;
     41 	char *hostname;		/**< Parsed hostname (from 'url') */
     42 	int port;		/**< Parsed port (from 'url') */
     43 	char *username;
     44 	char *password;
     45 	char *document;		/**< Parsed document (from 'url') */
     46 	char *ip;		/**< Resolved IP */
     47 	int ipv6;
     48 	SSL *ssl;
     49 	int fd;			/**< Socket */
     50 	int connected;
     51 	int got_response;
     52 	int http_status_code;
     53 	char *lefttoparse;
     54 	long long lefttoparselen; /* size of data in lefttoparse (note: not used for first header parsing) */
     55 	time_t last_modified;
     56 	time_t download_started;
     57 	int dns_refcnt;
     58 	TransferEncoding transfer_encoding;
     59 	long chunk_remaining;
     60 	/* for redirects: */
     61 	int redirects_remaining;
     62 	char *redirect_new_location;
     63 	char *redirect_original_url;
     64 };
     65 
     66 /* Variables */
     67 Download *downloads = NULL;
     68 SSL_CTX *https_ctx = NULL;
     69 
     70 /* Forward declarations */
     71 void url_resolve_cb(void *arg, int status, int timeouts, struct hostent *he);
     72 void unreal_https_initiate_connect(Download *handle);
     73 int url_parse(const char *url, char **host, int *port, char **username, char **password, char **document);
     74 SSL_CTX *https_new_ctx(void);
     75 void unreal_https_connect_handshake(int fd, int revents, void *data);
     76 int https_connect(Download *handle);
     77 int https_fatal_tls_error(int ssl_error, int my_errno, Download *handle);
     78 void https_connect_send_header(Download *handle);
     79 void https_receive_response(int fd, int revents, void *data);
     80 int https_handle_response_header(Download *handle, char *readbuf, int n);
     81 int https_handle_response_file(Download *handle, char *readbuf, int n);
     82 void https_done(Download *handle);
     83 void https_done_cached(Download *handle);
     84 void https_redirect(Download *handle);
     85 int https_parse_header(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request);
     86 char *url_find_end_of_request(char *header, int totalsize, int *remaining_bytes);
     87 void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)  __attribute__((format(printf,2,3)));
     88 
     89 void url_free_handle(Download *handle)
     90 {
     91 	DelListItem(handle, downloads);
     92 	if (handle->fd > 0)
     93 	{
     94 		fd_close(handle->fd);
     95 		fd_unnotify(handle->fd);
     96 	}
     97 	if (handle->file_fd)
     98 		fclose(handle->file_fd);
     99 	safe_free(handle->url);
    100 	safe_free(handle->hostname);
    101 	safe_free(handle->username);
    102 	safe_free(handle->password);
    103 	safe_free(handle->document);
    104 	safe_free(handle->ip);
    105 	if (handle->ssl)
    106 		SSL_free(handle->ssl);
    107 	safe_free(handle->lefttoparse);
    108 	safe_free(handle->redirect_new_location);
    109 	safe_free(handle->redirect_original_url);
    110 	safe_free(handle);
    111 }
    112 
    113 void url_cancel_handle_by_callback_data(void *ptr)
    114 {
    115 	Download *d, *d_next;
    116 
    117 	for (d = downloads; d; d = d_next)
    118 	{
    119 		d_next = d->next;
    120 		if (d->callback_data == ptr)
    121 		{
    122 			d->callback = NULL;
    123 			d->callback_data = NULL;
    124 		}
    125 	}
    126 }
    127 
    128 void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)
    129 {
    130 	va_list vl;
    131 	va_start(vl, pattern);
    132 	vsnprintf(handle->errorbuf, sizeof(handle->errorbuf), pattern, vl);
    133 	va_end(vl);
    134 	if (handle->callback)
    135 		handle->callback(handle->url, NULL, handle->errorbuf, 0, handle->callback_data);
    136 	url_free_handle(handle);
    137 }
    138 
    139 void download_file_async(const char *url, time_t cachetime, vFP callback, void *callback_data, char *original_url, int maxredirects)
    140 {
    141 	char *file;
    142 	const char *filename;
    143 	char *tmp;
    144 	Download *handle = NULL;
    145 	int ipv6 = 0;
    146 	char *host;
    147 	int port;
    148 	char *username;
    149 	char *password;
    150 	char *document;
    151 
    152 	handle = safe_alloc(sizeof(Download));
    153 	handle->download_started = TStime();
    154 	handle->callback = callback;
    155 	handle->callback_data = callback_data;
    156 	handle->cachetime = cachetime;
    157 	safe_strdup(handle->url, url);
    158 	safe_strdup(handle->redirect_original_url, original_url);
    159 	handle->redirects_remaining = maxredirects;
    160 	AddListItem(handle, downloads);
    161 
    162 	if (strncmp(url, "https://", 8))
    163 	{
    164 		https_cancel(handle, "Only https:// is supported (either rebuild UnrealIRCd with curl support or use https)");
    165 		return;
    166 	}
    167 	if (!url_parse(url, &host, &port, &username, &password, &document))
    168 	{
    169 		https_cancel(handle, "Failed to parse HTTP url");
    170 		return;
    171 	}
    172 
    173 	safe_strdup(handle->hostname, host);
    174 	handle->port = port;
    175 	safe_strdup(handle->username, username);
    176 	safe_strdup(handle->password, password);
    177 	safe_strdup(handle->document, document);
    178 
    179 	file = url_getfilename(url);
    180 	filename = unreal_getfilename(file);
    181 	tmp = unreal_mktemp(TMPDIR, filename ? filename : "download.conf");
    182 
    183 	handle->file_fd = fopen(tmp, "wb");
    184 	if (!handle->file_fd)
    185 	{
    186 		https_cancel(handle, "Cannot create '%s': %s", tmp, strerror(ERRNO));
    187 		safe_free(file);
    188 		return;
    189 	}
    190 
    191 	strlcpy(handle->filename, tmp, sizeof(handle->filename));
    192 	safe_free(file);
    193 
    194 
    195 	// todo: allocate handle, select en weetikt allemaal
    196 	// add to some global struct linkedlist, for timeouts
    197 	// register in i/o
    198 
    199 	if (is_valid_ip(handle->hostname))
    200 	{
    201 		/* Nothing to resolve, eg https://127.0.0.1/ */
    202 		safe_strdup(handle->ip, handle->hostname);
    203 		unreal_https_initiate_connect(handle);
    204 	} else {
    205 		/* Hostname, so start resolving... */
    206 		handle->dns_refcnt++;
    207 		ares_gethostbyname(resolver_channel, handle->hostname, AF_INET, url_resolve_cb, handle);
    208 		// TODO: check return value?
    209 	}
    210 }
    211 
    212 void url_resolve_cb(void *arg, int status, int timeouts, struct hostent *he)
    213 {
    214 	Download *handle = (Download *)arg;
    215 	int n;
    216 	struct hostent *he2;
    217 	char ipbuf[HOSTLEN+1];
    218 	const char *ip = NULL;
    219 
    220 	handle->dns_refcnt--;
    221 
    222 	if ((status != 0) || !he->h_addr_list || !he->h_addr_list[0])
    223 	{
    224 		https_cancel(handle, "Unable to resolve hostname '%s'", handle->hostname);
    225 		return;
    226 	}
    227 
    228 	if (!he->h_addr_list[0] || (he->h_length != (handle->ipv6 ? 16 : 4)) ||
    229 	    !(ip = inetntop(handle->ipv6 ? AF_INET6 : AF_INET, he->h_addr_list[0], ipbuf, sizeof(ipbuf))))
    230 	{
    231 		/* Illegal response -- fatal */
    232 		https_cancel(handle, "Unable to resolve hostname '%s'", handle->hostname);
    233 		return;
    234 	}
    235 
    236 	/* Ok, since we got here, it seems things were actually succesfull */
    237 
    238 	safe_strdup(handle->ip, ip);
    239 
    240 	unreal_https_initiate_connect(handle);
    241 }
    242 
    243 void unreal_https_initiate_connect(Download *handle)
    244 {
    245 	// todo: allocate handle, select en weetikt allemaal
    246 	// add to some global struct linkedlist, for timeouts
    247 	// register in i/o
    248 
    249 	if (!handle->ip)
    250 	{
    251 		https_cancel(handle, "No IP address found to connect to");
    252 		return;
    253 	}
    254 
    255 	handle->fd = fd_socket(handle->ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "HTTPS");
    256 	if (handle->fd < 0)
    257 	{
    258 		https_cancel(handle, "Could not create socket: %s", strerror(ERRNO));
    259 		return;
    260 	}
    261 	set_sock_opts(handle->fd, NULL, handle->ipv6);
    262 	if (!unreal_connect(handle->fd, handle->ip, handle->port, handle->ipv6))
    263 	{
    264 		https_cancel(handle, "Could not connect: %s", strerror(ERRNO));
    265 		return;
    266 	}
    267 
    268 	fd_setselect(handle->fd, FD_SELECT_WRITE, unreal_https_connect_handshake, handle);
    269 }
    270 
    271 // based on unreal_tls_client_handshake()
    272 void unreal_https_connect_handshake(int fd, int revents, void *data)
    273 {
    274 	Download *handle = data;
    275 	handle->ssl = SSL_new(https_ctx);
    276 	if (!handle->ssl)
    277 	{
    278 		https_cancel(handle, "Failed to setup SSL");
    279 		return;
    280 	}
    281 	SSL_set_fd(handle->ssl, handle->fd);
    282 	SSL_set_connect_state(handle->ssl);
    283 	SSL_set_nonblocking(handle->ssl);
    284 	SSL_set_tlsext_host_name(handle->ssl, handle->hostname);
    285 
    286 	if (https_connect(handle) < 0)
    287 	{
    288 		/* Some fatal error already */
    289 		https_cancel(handle, "TLS_connect() failed early");
    290 		return;
    291 	}
    292 
    293 	/* Is now connecting... */
    294 }
    295 
    296 SSL_CTX *https_new_ctx(void)
    297 {
    298 	SSL_CTX *ctx_client;
    299 	char buf1[512], buf2[512];
    300 	char *curl_ca_bundle = buf1;
    301 
    302 	SSL_load_error_strings();
    303 	SSLeay_add_ssl_algorithms();
    304 
    305 	ctx_client = SSL_CTX_new(SSLv23_client_method());
    306 	if (!ctx_client)
    307 		return NULL;
    308 #ifdef HAS_SSL_CTX_SET_MIN_PROTO_VERSION
    309 	SSL_CTX_set_min_proto_version(ctx_client, TLS1_2_VERSION);
    310 #endif
    311 	SSL_CTX_set_options(ctx_client, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1);
    312 
    313 	/* Verify peer certificate */
    314 	snprintf(buf1, sizeof(buf1), "%s/tls/curl-ca-bundle.crt", CONFDIR);
    315 	if (!file_exists(buf1))
    316 	{
    317 		snprintf(buf2, sizeof(buf2), "%s/doc/conf/tls/curl-ca-bundle.crt", BUILDDIR);
    318 		if (!file_exists(buf2))
    319 		{
    320 			unreal_log(ULOG_ERROR, "url", "CA_BUNDLE_NOT_FOUND", NULL,
    321 			           "Neither $filename1 nor $filename2 exist.\n"
    322 			           "Cannot use built-in https client without curl-ca-bundle.crt\n",
    323 			           log_data_string("filename1", buf1),
    324 			           log_data_string("filename2", buf2));
    325 			exit(-1);
    326 		}
    327 		curl_ca_bundle = buf2;
    328 	}
    329 	SSL_CTX_load_verify_locations(ctx_client, curl_ca_bundle, NULL);
    330 	SSL_CTX_set_verify(ctx_client, SSL_VERIFY_PEER, NULL);
    331 
    332 	/* Limit ciphers as well */
    333 	SSL_CTX_set_cipher_list(ctx_client, UNREALIRCD_DEFAULT_CIPHERS);
    334 
    335 	return ctx_client;
    336 }
    337 
    338 // Based on unreal_tls_connect_retry
    339 void https_connect_retry(int fd, int revents, void *data)
    340 {
    341 	Download *handle = data;
    342 	https_connect(handle);
    343 }
    344 
    345 // Based on unreal_tls_connect()
    346 int https_connect(Download *handle)
    347 {
    348 	int ssl_err;
    349 	char *errstr;
    350 
    351 	if ((ssl_err = SSL_connect(handle->ssl)) <= 0)
    352 	{
    353 		ssl_err = SSL_get_error(handle->ssl, ssl_err);
    354 		switch(ssl_err)
    355 		{
    356 			case SSL_ERROR_SYSCALL:
    357 				if (ERRNO == P_EINTR || ERRNO == P_EWOULDBLOCK || ERRNO == P_EAGAIN)
    358 				{
    359 					/* Hmmm. This implementation is different than in unreal_tls_accept().
    360 					 * One of them must be wrong -- better check! (TODO)
    361 					 */
    362 					fd_setselect(handle->fd, FD_SELECT_READ|FD_SELECT_WRITE, https_connect_retry, handle);
    363 					return 0;
    364 				}
    365 				return https_fatal_tls_error(ssl_err, ERRNO, handle);
    366 			case SSL_ERROR_WANT_READ:
    367 				fd_setselect(handle->fd, FD_SELECT_READ, https_connect_retry, handle);
    368 				fd_setselect(handle->fd, FD_SELECT_WRITE, NULL, handle);
    369 				return 0;
    370 			case SSL_ERROR_WANT_WRITE:
    371 				fd_setselect(handle->fd, FD_SELECT_READ, NULL, handle);
    372 				fd_setselect(handle->fd, FD_SELECT_WRITE, https_connect_retry, handle);
    373 				return 0;
    374 			default:
    375 				return https_fatal_tls_error(ssl_err, ERRNO, handle);
    376 		}
    377 		/* NOTREACHED */
    378 		return -1;
    379 	}
    380 
    381 	/* We are connected now. */
    382 
    383 	if (!verify_certificate(handle->ssl, handle->hostname, &errstr))
    384 	{
    385 		https_cancel(handle, "TLS Certificate error for server: %s", errstr);
    386 		return -1;
    387 	}
    388 	https_connect_send_header(handle);
    389 	return 1;
    390 }
    391 
    392 /**
    393  * Report a fatal TLS error and terminate the download.
    394  *
    395  * @param ssl_error The error as from OpenSSL.
    396  * @param where The location, one of the SAFE_SSL_* defines.
    397  * @param my_errno A preserved value of errno to pass to ssl_error_str().
    398  * @param client The client the error is associated with.
    399  */
    400 int https_fatal_tls_error(int ssl_error, int my_errno, Download *handle)
    401 {
    402 	const char *ssl_errstr;
    403 	unsigned long additional_errno = ERR_get_error();
    404 	char additional_info[256];
    405 	const char *one, *two;
    406 
    407 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
    408 	/* Fetch additional error information from OpenSSL 3.0.0+ */
    409 	two = ERR_reason_error_string(additional_errno);
    410 	if (two && *two)
    411 	{
    412 		snprintf(additional_info, sizeof(additional_info), ": %s", two);
    413 	} else {
    414 		*additional_info = '\0';
    415 	}
    416 #else
    417 	/* Fetch additional error information from OpenSSL. This is new as of Nov 2017 (4.0.16+) */
    418 	one = ERR_func_error_string(additional_errno);
    419 	two = ERR_reason_error_string(additional_errno);
    420 	if (one && *one && two && *two)
    421 	{
    422 		snprintf(additional_info, sizeof(additional_info), ": %s: %s", one, two);
    423 	} else {
    424 		*additional_info = '\0';
    425 	}
    426 #endif
    427 
    428 	ssl_errstr = ssl_error_str(ssl_error, my_errno);
    429 
    430 	https_cancel(handle, "%s [%s]", ssl_errstr, additional_info);
    431 	return -1;
    432 }
    433 
    434 // copied 100% from modulemanager parse_url()
    435 int url_parse(const char *url, char **hostname, int *port, char **username, char **password, char **document)
    436 {
    437 	char *p, *p2;
    438 	static char hostbuf[256];
    439 	static char documentbuf[512];
    440 
    441 	*hostname = *username = *password = *document = NULL;
    442 	*port = 443;
    443 
    444 	if (strncmp(url, "https://", 8))
    445 		return 0;
    446 	url += 8; /* skip over https:// part */
    447 
    448 	p = strchr(url, '/');
    449 	if (!p)
    450 		return 0;
    451 
    452 	strlncpy(hostbuf, url, sizeof(hostbuf), p - url);
    453 
    454 	strlcpy(documentbuf, p, sizeof(documentbuf));
    455 
    456 	*hostname = hostbuf;
    457 	*document = documentbuf;
    458 
    459 	/* Actually we may still need to extract the port */
    460 	p = strchr(hostbuf, '@');
    461 	if (p)
    462 	{
    463 		*p++ = '\0';
    464 
    465 		*username = hostbuf;
    466 		p2 = strchr(hostbuf, ':');
    467 		if (p2)
    468 		{
    469 			*p2++ = '\0';
    470 			*password = p2;
    471 		}
    472 		*hostname = p;
    473 	}
    474 	p = strchr(*hostname, ':');
    475 	if (p)
    476 	{
    477 		*p++ = '\0';
    478 		*port = atoi(p);
    479 	}
    480 
    481 	return 1;
    482 }
    483 
    484 void https_connect_send_header(Download *handle)
    485 {
    486 	char buf[1024];
    487 	char hostandport[512];
    488 	int ssl_err;
    489 	char *host;
    490 	int port;
    491 	char *document;
    492 
    493 	handle->connected = 1;
    494 	snprintf(hostandport, sizeof(hostandport), "%s:%d", handle->hostname, handle->port);
    495 
    496 	/* Prepare the header */
    497 	snprintf(buf, sizeof(buf), "GET %s HTTP/1.1\r\n"
    498 	                    "User-Agent: UnrealIRCd %s\r\n"
    499 	                    "Host: %s\r\n"
    500 	                    "Connection: close\r\n",
    501 	                    handle->document,
    502 	                    VERSIONONLY,
    503 	                    hostandport);
    504 	if (handle->username && handle->password)
    505 	{
    506 		char wbuf[128];
    507 		char obuf[256];
    508 		char header[512];
    509 
    510 		snprintf(wbuf, sizeof(wbuf), "%s:%s", handle->username, handle->password);
    511 		if (b64_encode(wbuf, strlen(wbuf), obuf, sizeof(obuf)-1) > 0)
    512 		{
    513 			snprintf(header, sizeof(header), "Authorization: Basic %s\r\n", obuf);
    514 			strlcat(buf, header, sizeof(buf));
    515 		}
    516 	}
    517 	if (handle->cachetime > 0)
    518 	{
    519 		const char *datestr = rfc2616_time(handle->cachetime);
    520 		if (datestr)
    521 		{
    522 			// snprintf_append...
    523 			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
    524 				 "If-Modified-Since: %s\r\n", datestr);
    525 		}
    526 	}
    527 	strlcat(buf, "\r\n", sizeof(buf));
    528 
    529 	ssl_err = SSL_write(handle->ssl, buf, strlen(buf));
    530 	if (ssl_err < 0)
    531 	{
    532 		https_fatal_tls_error(ssl_err, ERRNO, handle);
    533 		return;
    534 	}
    535 	fd_setselect(handle->fd, FD_SELECT_WRITE, NULL, handle);
    536 	fd_setselect(handle->fd, FD_SELECT_READ, https_receive_response, handle);
    537 }
    538 
    539 void https_receive_response(int fd, int revents, void *data)
    540 {
    541 	Download *handle = data;
    542 	int n;
    543 	char readbuf[2048];
    544 
    545 	n = SSL_read(handle->ssl, readbuf, sizeof(readbuf)-1);
    546 	if (n == 0)
    547 	{
    548 		/* Graceful close */
    549 		https_done(handle);
    550 		return;
    551 	}
    552 	if (n < 0)
    553 	{
    554 		int ssl_err = SSL_get_error(handle->ssl, n);
    555 		switch (ssl_err)
    556 		{
    557 			case SSL_ERROR_WANT_WRITE:
    558 				fd_setselect(fd, FD_SELECT_READ, NULL, handle);
    559 				fd_setselect(fd, FD_SELECT_WRITE, https_receive_response, handle);
    560 				return;
    561 			case SSL_ERROR_WANT_READ:
    562 				/* Wants to read more data; let it call us next time again */
    563 				return;
    564 			case SSL_ERROR_SYSCALL:
    565 			case SSL_ERROR_SSL:
    566 			default:
    567 				https_fatal_tls_error(ssl_err, ERRNO, handle);
    568 				return;
    569 		}
    570 		return;
    571 	}
    572 	readbuf[n] = '\0';
    573 
    574 	//fprintf(stderr, "Got: '%s'\n", readbuf);
    575 
    576 	if (!handle->got_response)
    577 	{
    578 		https_handle_response_header(handle, readbuf, n);
    579 		return;
    580 	} else
    581 	if (handle->got_response)
    582 	{
    583 		if (!https_handle_response_file(handle, readbuf, n))
    584 			return; /* handle is already freed! */
    585 	}
    586 }
    587 
    588 // Based on websocket_handle_handshake()
    589 int https_handle_response_header(Download *handle, char *readbuf, int n)
    590 {
    591 	char *key, *value;
    592 	int r, end_of_request;
    593 	char netbuf[4096], netbuf2[4096];
    594 	char *lastloc = NULL;
    595 	int maxcopy, nprefix=0;
    596 	int totalsize;
    597 
    598 	/* Yeah, totally paranoid: */
    599 	memset(netbuf, 0, sizeof(netbuf));
    600 	memset(netbuf2, 0, sizeof(netbuf2));
    601 
    602 	/** Frame re-assembling starts here **/
    603 	*netbuf = '\0';
    604 	if (handle->lefttoparse)
    605 	{
    606 		strlcpy(netbuf, handle->lefttoparse, sizeof(netbuf));
    607 		nprefix = strlen(netbuf);
    608 	}
    609 	maxcopy = sizeof(netbuf) - nprefix - 1;
    610 	/* (Need to some manual checking here as strlen() can't be safely used
    611 	 *  on readbuf. Same is true for strlncat since it uses strlen().)
    612 	 */
    613 	if (n > maxcopy)
    614 		n = maxcopy;
    615 	if (n <= 0)
    616 	{
    617 		https_cancel(handle, "Oversized line in HTTP response");
    618 		return 0;
    619 	}
    620 	memcpy(netbuf+nprefix, readbuf, n); /* SAFE: see checking above */
    621 	totalsize = n + nprefix;
    622 	netbuf[totalsize] = '\0';
    623 	memcpy(netbuf2, netbuf, totalsize+1); // copy, including the "always present \0 at the end just in case we use strstr etc".
    624 	safe_free(handle->lefttoparse);
    625 
    626 	/** Now step through the lines.. **/
    627 	for (r = https_parse_header(netbuf, strlen(netbuf), &key, &value, &lastloc, &end_of_request);
    628 	     r;
    629 	     r = https_parse_header(NULL, 0, &key, &value, &lastloc, &end_of_request))
    630 	{
    631 		// do something actually with the header here ;)
    632 		if (!strcasecmp(key, "RESPONSE"))
    633 		{
    634 			handle->http_status_code = atoi(value);
    635 			if (handle->http_status_code == 304)
    636 			{
    637 				/* 304 Not Modified: cache hit */
    638 				https_done_cached(handle);
    639 				return 0;
    640 			}
    641 			else if ((handle->http_status_code >= 301) && (handle->http_status_code <= 308))
    642 			{
    643 				/* Redirect */
    644 				if (handle->redirects_remaining == 0)
    645 				{
    646 					https_cancel(handle, "Too many HTTP redirects (%d)", DOWNLOAD_MAX_REDIRECTS);
    647 					return 0;
    648 				}
    649 				/* Let it continue.. we handle it later, as we need to
    650 				 * receive the "Location" header as well.
    651 				 */
    652 			}
    653 			else if (handle->http_status_code != 200)
    654 			{
    655 				/* HTTP Failure code */
    656 				https_cancel(handle, "HTTP Error: %s", value);
    657 				return 0;
    658 			}
    659 		} else
    660 		if (!strcasecmp(key, "Last-Modified") && value)
    661 		{
    662 			handle->last_modified = rfc2616_time_to_unix_time(value);
    663 		} else
    664 		if (!strcasecmp(key, "Location") && value)
    665 		{
    666 			safe_strdup(handle->redirect_new_location, value);
    667 		} else
    668 		if (!strcasecmp(key, "Transfer-Encoding") && value)
    669 		{
    670 			if (value && !strcasecmp(value, "chunked"))
    671 				handle->transfer_encoding = TRANSFER_ENCODING_CHUNKED;
    672 		}
    673 		//fprintf(stderr, "\nHEADER '%s'\n\n", key);
    674 	}
    675 
    676 	if (end_of_request)
    677 	{
    678 		int remaining_bytes = 0;
    679 		char *nextframe;
    680 
    681 		safe_free(handle->lefttoparse);
    682 		handle->got_response = 1;
    683 
    684 		if (handle->http_status_code == 0)
    685 		{
    686 			https_cancel(handle, "Invalid HTTP response");
    687 			return 0;
    688 		}
    689 		if (handle->http_status_code != 200)
    690 		{
    691 			if (handle->redirect_new_location)
    692 			{
    693 				https_redirect(handle);
    694 				return 0; /* this old request dies */
    695 			} else {
    696 				https_cancel(handle, "HTTP Redirect encountered but no URL specified!?");
    697 				return 0;
    698 			}
    699 		}
    700 
    701 		nextframe = url_find_end_of_request(netbuf2, totalsize, &remaining_bytes);
    702 		if (nextframe)
    703 		{
    704 			if (!https_handle_response_file(handle, nextframe, remaining_bytes))
    705 				return 0;
    706 		}
    707 	}
    708 
    709 	if (lastloc)
    710 	{
    711 		/* Last line was cut somewhere, save it for next round. */
    712 		safe_strdup(handle->lefttoparse, lastloc);
    713 	}
    714 
    715 	return 1;
    716 }
    717 
    718 int https_handle_response_file(Download *handle, char *readbuf, int pktsize)
    719 {
    720 	char *buf;
    721 	long long n;
    722 	char *free_this_buffer = NULL;
    723 
    724 	// TODO we fail to check for write errors ;)
    725 	// TODO: Makes sense to track if we got everything? :D
    726 
    727 	if (handle->transfer_encoding == TRANSFER_ENCODING_NONE)
    728 	{
    729 		/* Ohh.. so easy! */
    730 		fwrite(readbuf, 1, pktsize, handle->file_fd);
    731 		return 1;
    732 	}
    733 
    734 	/* Fill 'buf' nd set 'buflen' with what we had + what we have now.
    735 	 * Makes things easy.
    736 	 */
    737 	if (handle->lefttoparse)
    738 	{
    739 		n = handle->lefttoparselen + pktsize;
    740 		free_this_buffer = buf = safe_alloc(n);
    741 		memcpy(buf, handle->lefttoparse, handle->lefttoparselen);
    742 		memcpy(buf+handle->lefttoparselen, readbuf, pktsize);
    743 		safe_free(handle->lefttoparse);
    744 		handle->lefttoparselen = 0;
    745 	} else {
    746 		n = pktsize;
    747 		buf = readbuf;
    748 	}
    749 
    750 	/* Chunked transfers.. yayyyy.. */
    751 	while (n > 0)
    752 	{
    753 		if (handle->chunk_remaining > 0)
    754 		{
    755 			/* Eat it */
    756 			int eat = MIN(handle->chunk_remaining, n);
    757 			fwrite(buf, 1, eat, handle->file_fd);
    758 			n -= eat;
    759 			buf += eat;
    760 			handle->chunk_remaining -= eat;
    761 		} else
    762 		{
    763 			int gotlf = 0;
    764 			int i;
    765 
    766 			/* First check if it is a (trailing) empty line,
    767 			 * eg from a previous chunk. Skip over.
    768 			 */
    769 			if ((n >= 2) && !strncmp(buf, "\r\n", 2))
    770 			{
    771 				buf += 2;
    772 				n -= 2;
    773 			} else
    774 			if ((n >= 1) && !strncmp(buf, "\n", 1))
    775 			{
    776 				buf++;
    777 				n--;
    778 			}
    779 
    780 			/* Now we are (possibly) at the chunk size line,
    781 			 * this is or example '7f' + newline.
    782 			 * So first, check if we have a newline at all.
    783 			 */
    784 			for (i=0; i < n; i++)
    785 			{
    786 				if (buf[i] == '\n')
    787 				{
    788 					gotlf = 1;
    789 					break;
    790 				}
    791 			}
    792 			if (!gotlf)
    793 			{
    794 				/* The line telling us the chunk size is incomplete,
    795 				 * as it does not contain an \n. Wait for more data
    796 				 * from the network socket.
    797 				 */
    798 				if (n > 0)
    799 				{
    800 					/* Store what we have first.. */
    801 					handle->lefttoparselen = n;
    802 					handle->lefttoparse = safe_alloc(n);
    803 					memcpy(handle->lefttoparse, buf, n);
    804 				}
    805 				safe_free(free_this_buffer);
    806 				return 1; /* WE WANT MORE! */
    807 			}
    808 			buf[i] = '\0'; /* cut at LF */
    809 			i++; /* point to next data */
    810 			handle->chunk_remaining = strtol(buf, NULL, 16);
    811 			if (handle->chunk_remaining < 0)
    812 			{
    813 				https_cancel(handle, "Negative chunk encountered (%ld)", handle->chunk_remaining);
    814 				safe_free(free_this_buffer);
    815 				return 0;
    816 			}
    817 			if (handle->chunk_remaining == 0)
    818 			{
    819 				https_done(handle);
    820 				safe_free(free_this_buffer);
    821 				return 0;
    822 			}
    823 			buf += i;
    824 			n -= i;
    825 		}
    826 	}
    827 
    828 	safe_free(free_this_buffer);
    829 	return 1;
    830 }
    831 
    832 void https_done(Download *handle)
    833 {
    834 	char *url = handle->redirect_original_url ? handle->redirect_original_url : handle->url;
    835 
    836 	fclose(handle->file_fd);
    837 	handle->file_fd = NULL;
    838 
    839 	if (!handle->callback)
    840 		; /* No special action, request was cancelled */
    841 	else if (!handle->got_response)
    842 		handle->callback(url, NULL, "HTTPS response not received", 0, handle->callback_data);
    843 	else
    844 	{
    845 		if (handle->last_modified > 0)
    846 			unreal_setfilemodtime(handle->filename, handle->last_modified);
    847 		handle->callback(url, handle->filename, NULL, 0, handle->callback_data);
    848 	}
    849 	url_free_handle(handle);
    850 	return;
    851 }
    852 
    853 void https_done_cached(Download *handle)
    854 {
    855 	char *url = handle->redirect_original_url ? handle->redirect_original_url : handle->url;
    856 
    857 	fclose(handle->file_fd);
    858 	handle->file_fd = NULL;
    859 	if (handle->callback)
    860 		handle->callback(url, NULL, NULL, 1, handle->callback_data);
    861 	url_free_handle(handle);
    862 }
    863 
    864 void https_redirect(Download *handle)
    865 {
    866 	if (handle->redirects_remaining == 0)
    867 	{
    868 		https_cancel(handle, "Too many HTTP redirects (%d)", DOWNLOAD_MAX_REDIRECTS);
    869 		return;
    870 	}
    871 	handle->redirects_remaining--;
    872 
    873 	if (handle->callback)
    874 	{
    875 		/* If still an outstanding request (not cancelled), follow the redirect.. */
    876 		download_file_async(handle->redirect_new_location, handle->cachetime, handle->callback, handle->callback_data,
    877 				    handle->url, handle->redirects_remaining);
    878 	}
    879 	/* Don't call the hook, just free this, the new redirect from above will call the hook later */
    880 	url_free_handle(handle);
    881 }
    882 
    883 /** Helper function to parse the HTTP header consisting of multiple 'Key: value' pairs */
    884 int https_parse_header(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request)
    885 {
    886 	static char buf[4096], *nextptr;
    887 	char *p;
    888 	char *k = NULL, *v = NULL;
    889 	int foundlf = 0;
    890 
    891 	if (buffer)
    892 	{
    893 		/* Initialize */
    894 		if (len > sizeof(buf) - 1)
    895 			len = sizeof(buf) - 1;
    896 
    897 		memcpy(buf, buffer, len);
    898 		buf[len] = '\0';
    899 		nextptr = buf;
    900 	}
    901 
    902 	*end_of_request = 0;
    903 
    904 	p = nextptr;
    905 
    906 	if (!p)
    907 	{
    908 		*key = *value = NULL;
    909 		return 0; /* done processing data */
    910 	}
    911 
    912 	if (!strncmp(p, "\n", 1) || !strncmp(p, "\r\n", 2))
    913 	{
    914 		*key = *value = NULL;
    915 		*end_of_request = 1;
    916 		// new compared to websocket handling:
    917 		if (*p == '\n')
    918 			*lastloc = p + 1;
    919 		else
    920 			*lastloc = p + 2;
    921 		return 0;
    922 	}
    923 
    924 	/* Note: p *could* point to the NUL byte ('\0') */
    925 
    926 	/* Special handling for response line itself. */
    927 	if (!strncmp(p, "HTTP/1", 6) && (strlen(p)>=13))
    928 	{
    929 		k = "RESPONSE";
    930 		p += 9;
    931 		v = p; /* SET VALUE */
    932 		nextptr = NULL; /* set to "we are done" in case next for loop fails */
    933 		for (; *p; p++)
    934 		{
    935 			if (*p == '\r')
    936 			{
    937 				*p = '\0'; /* eat silently, but don't consider EOL */
    938 			}
    939 			else if (*p == '\n')
    940 			{
    941 				*p = '\0';
    942 				nextptr = p+1; /* safe, there is data or at least a \0 there */
    943 				break;
    944 			}
    945 		}
    946 		*key = k;
    947 		*value = v;
    948 		return 1;
    949 	}
    950 
    951 	/* Header parsing starts here.
    952 	 * Example line "Host: www.unrealircd.org"
    953 	 */
    954 	k = p; /* SET KEY */
    955 
    956 	/* First check if the line contains a terminating \n. If not, don't process it
    957 	 * as it may have been a cut header.
    958 	 */
    959 	for (; *p; p++)
    960 	{
    961 		if (*p == '\n')
    962 		{
    963 			foundlf = 1;
    964 			break;
    965 		}
    966 	}
    967 
    968 	if (!foundlf)
    969 	{
    970 		*key = *value = NULL;
    971 		*lastloc = k;
    972 		return 0;
    973 	}
    974 
    975 	p = k;
    976 
    977 	for (; *p; p++)
    978 	{
    979 		if ((*p == '\n') || (*p == '\r'))
    980 		{
    981 			/* Reached EOL but 'value' not found */
    982 			*p = '\0';
    983 			break;
    984 		}
    985 		if (*p == ':')
    986 		{
    987 			*p++ = '\0';
    988 			if (*p++ != ' ')
    989 				break; /* missing mandatory space after ':' */
    990 
    991 			v = p; /* SET VALUE */
    992 			nextptr = NULL; /* set to "we are done" in case next for loop fails */
    993 			for (; *p; p++)
    994 			{
    995 				if (*p == '\r')
    996 				{
    997 					*p = '\0'; /* eat silently, but don't consider EOL */
    998 				}
    999 				else if (*p == '\n')
   1000 				{
   1001 					*p = '\0';
   1002 					nextptr = p+1; /* safe, there is data or at least a \0 there */
   1003 					break;
   1004 				}
   1005 			}
   1006 			/* A key-value pair was succesfully parsed, return it */
   1007 			*key = k;
   1008 			*value = v;
   1009 			return 1;
   1010 		}
   1011 	}
   1012 
   1013 	/* Fatal parse error */
   1014 	*key = *value = NULL;
   1015 	return 0;
   1016 }
   1017 
   1018 /** Check if there is any data at the end of the request */
   1019 char *url_find_end_of_request(char *header, int totalsize, int *remaining_bytes)
   1020 {
   1021 	char *nextframe1;
   1022 	char *nextframe2;
   1023 	char *nextframe = NULL;
   1024 
   1025 	// find first occurance, yeah this is just stupid, but it works.
   1026 	nextframe1 = strstr(header, "\r\n\r\n"); // = +4
   1027 	nextframe2 = strstr(header, "\n\n");     // = +2
   1028 	if (nextframe1 && nextframe2)
   1029 	{
   1030 		if (nextframe1 < nextframe2)
   1031 		{
   1032 			nextframe = nextframe1 + 4;
   1033 		} else {
   1034 			nextframe = nextframe2 + 2;
   1035 		}
   1036 	} else
   1037 	if (nextframe1)
   1038 	{
   1039 		nextframe = nextframe1 + 4;
   1040 	} else
   1041 	if (nextframe2)
   1042 	{
   1043 		nextframe = nextframe2 + 2;
   1044 	}
   1045 	if (nextframe)
   1046 	{
   1047 		*remaining_bytes = totalsize - (nextframe - header);
   1048 		if (*remaining_bytes > 0)
   1049 			return nextframe;
   1050 	}
   1051 	return NULL;
   1052 }
   1053 
   1054 /* Handle timeouts. */
   1055 EVENT(url_socket_timeout)
   1056 {
   1057 	Download *d, *d_next;
   1058 	for (d = downloads; d; d = d_next)
   1059 	{
   1060 		d_next = d->next;
   1061 		if (d->dns_refcnt)
   1062 			continue; /* can't touch this... */
   1063 		if (!d->connected && (TStime() - d->download_started > DOWNLOAD_CONNECT_TIMEOUT))
   1064 		{
   1065 			https_cancel(d, "Connect or DNS timeout after %ld seconds", (long)DOWNLOAD_CONNECT_TIMEOUT);
   1066 			continue;
   1067 		}
   1068 		if (d->connected && (TStime() - d->download_started > DOWNLOAD_TRANSFER_TIMEOUT))
   1069 		{
   1070 			https_cancel(d, "Download timeout after %ld seconds", (long)DOWNLOAD_TRANSFER_TIMEOUT);
   1071 			continue;
   1072 		}
   1073 	}
   1074 }
   1075 
   1076 void url_init(void)
   1077 {
   1078 	https_ctx = https_new_ctx();
   1079 	if (!https_ctx)
   1080 	{
   1081 		unreal_log(ULOG_ERROR, "url", "HTTPS_NEW_CTX_FAILED", NULL,
   1082 			   "Unable to initialize SSL context");
   1083 		exit(-1);
   1084 	}
   1085 	EventAdd(NULL, "url_socket_timeout", url_socket_timeout, NULL, 500, 0);
   1086 }