/* https.c
 * HTTPS protocol client implementation
 * (c) 2002 Mikulas Patocka
 * This file is a part of the Links program, released under GPL.

 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.  If you delete this exception statement from all source
 * files in the program, then also delete it here.
 */

#include "links.h"

#ifndef PATH_MAX
#define PATH_MAX 255
#endif

#ifdef HAVE_SSL

#ifndef LINKS_CRT_FILE
#define LINKS_CRT_FILE		links.crt
#endif

#ifdef HAVE_BUILTIN_SSL_CERTIFICATES
#include "certs.inc"
#define N_SSL_CONTEXTS	2
#else
#define N_SSL_CONTEXTS	1
#endif

static int ssl_initialized = 0;
static SSL_CTX *contexts[N_SSL_CONTEXTS];

#ifdef HAVE_CRYPTO_SET_MEM_FUNCTIONS_1
#define file_line_arg
#define pass_file_line
#else
#define file_line_arg	, const char *file, int line
#define pass_file_line	, file, line
#endif

#ifdef HAVE_CRYPTO_SET_MEM_FUNCTIONS

static unsigned in_ssl_malloc_hook = 0;

static void *malloc_hook(size_t size file_line_arg)
{
	void *p;
	in_ssl_malloc_hook++;
#if defined(OS2_ADVANCED_HEAP)
	if (!size) size = 1;
	do p = os2_orig_malloc(size); while (!p && out_of_memory(0, NULL, 0));
#elif !defined(HAVE_OPENSSL_CLEANUP) || defined(HAVE_CRYPTO_SET_MEM_FUNCTIONS_1)
	if (!size) size = 1;
	do p = malloc(size); while (!p && out_of_memory(0, NULL, 0));
#elif defined(LEAK_DEBUG)
	p = debug_mem_alloc(cast_uchar file, line, size, 1);
#else
	p = mem_alloc_mayfail(size);
#endif
	in_ssl_malloc_hook--;
	return p;
}

static void *realloc_hook(void *ptr, size_t size file_line_arg)
{
	void *p;
	if (!ptr) return malloc_hook(size pass_file_line);
	in_ssl_malloc_hook++;
#if !defined(HAVE_OPENSSL_CLEANUP) || defined(HAVE_CRYPTO_SET_MEM_FUNCTIONS_1) || defined(OS2_ADVANCED_HEAP)
	if (!size) size = 1;
	do p = realloc(ptr, size); while (!p && out_of_memory(0, NULL, 0));
#elif defined(LEAK_DEBUG)
	p = debug_mem_realloc(cast_uchar file, line, ptr, size, 1);
#else
	p = mem_realloc_mayfail(ptr, size);
#endif
	in_ssl_malloc_hook--;
	return p;
}

static void free_hook(void *ptr file_line_arg)
{
	if (!ptr) return;
#if !defined(HAVE_OPENSSL_CLEANUP) || defined(HAVE_CRYPTO_SET_MEM_FUNCTIONS_1) || defined(OS2_ADVANCED_HEAP)
	free(ptr);
#elif defined(LEAK_DEBUG)
	debug_mem_free(cast_uchar file, line, ptr);
#else
	mem_free(ptr);
#endif
}

#endif

#if defined(HAVE_SSL_CERTIFICATES) && (defined(DOS) || defined(OS2) || defined(WIN) || defined(OPENVMS))
static int ssl_set_private_paths(SSL_CTX *ctx)
{
	unsigned char *path, *c;
	int r;
#ifdef OPENVMS
	path = stracpy(cast_uchar g_argv[0]);
#else
	path = stracpy(path_to_exe);
#endif
	for (c = path + strlen(cast_const_char path); c > path; c--) {
		if (dir_sep(c[-1])
#ifdef OPENVMS
		    || c[-1] == ']' || c[-1] == ':'
#endif
		    )
			break;
	}
	c[0] = 0;
	add_to_strn(&path, cast_uchar stringify(LINKS_CRT_FILE));
	r = SSL_CTX_load_verify_locations(ctx, cast_const_char path, NULL);
	mem_free(path);
	clear_ssl_errors(__LINE__);
	if (r != 1)
		return -1;
	return 0;
}
#else
#define ssl_set_private_paths(c)		(-1)
#endif

#ifdef HAVE_BUILTIN_SSL_CERTIFICATES
static void ssl_load_private_certificates(SSL_CTX *ctx)
{
	int i;
	int errs = 0;
	int succeeded = 0;
	int total_certificates = (int)array_elements(certificates);
	X509_STORE *store = SSL_CTX_get_cert_store(ctx);
	if (!store)
		errs |= 1;
	else for (i = 0; i < total_certificates; i++) {
		BIO *bio;
		X509 *cert;
		unsigned char *data = cast_uchar certificates[i].data;
		int len = certificates[i].len;
		unsigned char *b64;
		if (data[len])
			internal_error("invalid builtin certificate %u", i);
#if 1
		b64 = base64_encode(data, len, cast_uchar "-----BEGIN CERTIFICATE-----\n", cast_uchar "-----END CERTIFICATE-----", 6);
		bio = BIO_new_mem_buf(b64, (int)strlen(cast_const_char b64));
#else
		{
			static_const unsigned char base64_chars[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
			int l, x;
			int col = 0;
			b64 = init_str();
			l = 0;
			add_to_str(&b64, &l, cast_uchar "-----BEGIN CERTIFICATE-----\n");
			for (x = 0; x < len; x += 3) {
				unsigned char out[4];
				out[0] = base64_chars[data[x] >> 2];
				out[1] = base64_chars[((data[x] << 4) & 63) | (data[x + 1] >> 4)];
				if (len - x > 1)
					out[2] = base64_chars[((data[x + 1] << 2) & 63) | (data[x + 2] >> 6)];
				else
					out[2] = '=';
				if (len - x > 2)
					out[3] = base64_chars[data[x + 2] & 63];
				else
					out[3] = '=';
				add_bytes_to_str(&b64, &l, out, 4);
				if (!((col += 4) & 63))
					add_chr_to_str(&b64, &l, '\n');
			}
			if (b64[l - 1] != '\n')
				add_chr_to_str(&b64, &l, '\n');
			add_to_str(&b64, &l, cast_uchar "-----END CERTIFICATE-----");
			bio = BIO_new_mem_buf(b64, l);
		}
#endif
		/*fprintf(stderr, "%s\n", b64);*/
		if (!bio) {
			errs |= 2;
			mem_free(b64);
			continue;
		}
		cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
		if (cert) {
			if (!X509_STORE_add_cert(store, cert)) {
				errs |= 8;
			} else {
				succeeded++;
			}
			X509_free(cert);
		} else {
			errs |= 4;
		}
		mem_free(b64);
		BIO_free(bio);
		clear_ssl_errors(__LINE__);
	}
	if (errs) {
		static_const char * const err_strings[4] = { "SSL_CTX_get_cert_store", "BIO_new_mem_buf", "PEM_read_bio_X509", "X509_STORE_add_cert" };
		struct session *ses;
		unsigned char *err_str = init_str();
		int err_strl = 0;
		unsigned char *numfail_str = init_str();
		int numfail_strl = 0;
		int e;
		ses = get_download_ses(NULL);
		for (e = 0; e < 4; e++) {
			if (errs & (1 << e)) {
				if (err_strl) add_to_str(&err_str, &err_strl, cast_uchar ", ");
				add_to_str(&err_str, &err_strl, cast_uchar err_strings[e]);
			}
		}
		add_num_to_str(&numfail_str, &numfail_strl, total_certificates - succeeded);
		add_chr_to_str(&numfail_str, &numfail_strl, '/');
		add_num_to_str(&numfail_str, &numfail_strl, total_certificates);
		if (!ses) {
			error("error initializing built-in certificates: %s, failed %s", err_str, numfail_str);
			mem_free(err_str);
			mem_free(numfail_str);
		} else {
			msg_box(ses->term, getml(err_str, numfail_str, NULL), TEXT_(T_SSL_ERROR), AL_CENTER, TEXT_(T_ERROR_INITIALIZING_BUILT_IN_CERTIFICATES), ": ", err_str, ", ", TEXT_(T_FAILED), " ", numfail_str, MSG_BOX_END, NULL, 1, TEXT_(T_CANCEL), msg_box_null, B_ENTER | B_ESC);
		}
	}
}
#endif

int ssl_asked_for_password;

static int ssl_password_callback(char *buf, int size, int rwflag, void *userdata)
{
	ssl_asked_for_password = 1;
	if (size > (int)strlen(cast_const_char ssl_options.client_cert_password))
		size = (int)strlen(cast_const_char ssl_options.client_cert_password);
	memcpy(buf, ssl_options.client_cert_password, size);
	return size;
}

links_ssl *getSSL(void)
{
	int idx;
	links_ssl *ssl;
	if (!ssl_initialized) {
		memset(contexts, 0, sizeof contexts);
#ifdef HAVE_CRYPTO_SET_MEM_FUNCTIONS
		CRYPTO_set_mem_functions(malloc_hook, realloc_hook, free_hook);
#endif

#if defined(HAVE_RAND_EGD) && defined(HAVE_RAND_FILE_NAME) && defined(HAVE_RAND_LOAD_FILE) && defined(HAVE_RAND_WRITE_FILE)
		{
			unsigned char f_randfile[PATH_MAX];
			const unsigned char *f = (const unsigned char *)RAND_file_name(cast_char f_randfile, sizeof(f_randfile));
			if (f && RAND_egd(cast_const_char f) < 0) {
				/* Not an EGD, so read and write to it */
				if (RAND_load_file(cast_const_char f_randfile, -1))
					RAND_write_file(cast_const_char f_randfile);
			}
		}
#endif

#if defined(HAVE_RAND_ADD)
		{
			unsigned char *os_pool;
			int os_pool_size;
			os_seed_random(&os_pool, &os_pool_size);
			if (os_pool_size) RAND_add(os_pool, os_pool_size, os_pool_size);
			mem_free(os_pool);
		}
#endif

#if defined(HAVE_OPENSSL_INIT_SSL)
		OPENSSL_init_ssl(0, NULL);
#elif defined(OpenSSL_add_ssl_algorithms)
		OpenSSL_add_ssl_algorithms();
#else
		SSLeay_add_ssl_algorithms();
#endif
		ssl_initialized = 1;
	}

	idx = 0;
#ifdef HAVE_BUILTIN_SSL_CERTIFICATES
	if (ssl_options.built_in_certificates || proxies.only_proxies)
		idx = 1;
#endif
	if (!contexts[idx]) {
		SSL_CTX *ctx;
		const SSL_METHOD *m;

		m = SSLv23_client_method();
		if (!m) return NULL;
		contexts[idx] = ctx = SSL_CTX_new((void *)m);
		if (!ctx) return NULL;
#ifndef SSL_OP_NO_COMPRESSION
#define SSL_OP_NO_COMPRESSION	0
#endif
		SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_COMPRESSION);
#ifdef SSL_MODE_ENABLE_PARTIAL_WRITE
		SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
#endif
#ifdef SSL_CTX_set_min_proto_version
#if defined(SSL3_VERSION)
		SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION);
#elif defined(TLS1_VERSION)
		SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION);
#elif defined(TLS1_1_VERSION)
		SSL_CTX_set_min_proto_version(ctx, TLS1_1_VERSION);
#elif defined(TLS1_2_VERSION)
		SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
#endif
#endif
		if (!idx) {
			if (ssl_set_private_paths(ctx))
				SSL_CTX_set_default_verify_paths(ctx);
		} else {
#ifdef HAVE_BUILTIN_SSL_CERTIFICATES
			ssl_load_private_certificates(ctx);
#endif
		}
#if defined(HAVE_X509_VERIFY_PARAM_SET_FLAGS) && defined(X509_V_FLAG_TRUSTED_FIRST)
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10003000
		X509_VERIFY_PARAM_set_flags(SSL_CTX_get_cert_store(ctx)->param, X509_V_FLAG_TRUSTED_FIRST);
#else
		/*X509_VERIFY_PARAM_set_flags(X509_STORE_get0_param(SSL_CTX_get_cert_store(ctx)), X509_V_FLAG_TRUSTED_FIRST);*/
#endif
#endif
		SSL_CTX_set_default_passwd_cb(ctx, ssl_password_callback);
	}
	ssl = mem_alloc_mayfail(sizeof(links_ssl));
	if (!ssl)
		return NULL;
	ssl->ctx = contexts[idx];
	ssl->ssl = SSL_new(ssl->ctx);
	clear_ssl_errors(__LINE__);
	if (!ssl->ssl) {
		mem_free(ssl);
		return NULL;
	}
	ssl->bytes_read = ssl->bytes_written = 0;
	ssl->session_set = 0;
	ssl->session_retrieved = 0;
	ssl->ca = NULL;
	return ssl;
}

void freeSSL(links_ssl *ssl)
{
	if (!ssl || ssl == DUMMY)
		return;
#ifdef SSL_SESSION_RESUME
	{
		int r;
		SSL_set_quiet_shutdown(ssl->ssl, 1);
		r = SSL_shutdown(ssl->ssl);
		if (r < 0)
			clear_ssl_errors(__LINE__);
	}
#endif
	SSL_free(ssl->ssl);
	if (ssl->ca) mem_free(ssl->ca);
	mem_free(ssl);
}

void ssl_finish(void)
{
	int i;
	for (i = 0; i < N_SSL_CONTEXTS; i++) {
		if (contexts[i]) {
			SSL_CTX_free(contexts[i]);
			contexts[i] = NULL;
		}
	}
	if (ssl_initialized) {
		clear_ssl_errors(__LINE__);
#ifdef HAVE_OPENSSL_CLEANUP
		OPENSSL_cleanup();
#endif
		ssl_initialized = 0;
	}
}

void https_func(struct connection *c)
{
	c->ssl = DUMMY;
	http_func(c);
}

#ifdef HAVE_SSL_CERTIFICATES

#ifdef SUPPORT_IPV6
static int is_numeric_ipv6_address(unsigned char *name, unsigned char address[16])
{
	unsigned char *n;
	int r;
	size_t nl = strlen(cast_const_char name);
	if (name[0] == '[' && name[nl - 1] == ']') {
		n = memacpy(name + 1, nl - 2);
	} else {
		n = stracpy(name);
	}
	r = numeric_ipv6_address(n, address, NULL);
	mem_free(n);
	return r;
}
#endif

#if !(defined(HAVE_X509_CHECK_HOST) && defined(HAVE_X509_CHECK_IP))

static int check_host_name(const unsigned char *templ, const unsigned char *host)
{
	int templ_len = (int)strlen(cast_const_char templ);
	int host_len = (int)strlen(cast_const_char host);
	unsigned char *wildcard;

	if (templ_len > 0 && templ[templ_len - 1] == '.') templ_len--;
	if (host_len > 0 && host[host_len - 1] == '.') host_len--;

	wildcard = memchr(templ, '*', templ_len);
	if (!wildcard) {
		if (templ_len == host_len && !casecmp(templ, host, templ_len))
			return 0;
		return -1;
	} else {
		int prefix_len, suffix_len;
		if (templ_len > host_len)
			return -1;
		prefix_len = (int)(wildcard - templ);
		suffix_len = (int)(templ + templ_len - (wildcard + 1));
		if (memchr(templ, '.', prefix_len))
			return -1;
		if (memchr(wildcard + 1, '*', suffix_len))
			return -1;
		if (casecmp(host, templ, prefix_len))
			return -1;
		if (memchr(host + prefix_len, '.', host_len - prefix_len - suffix_len))
			return -1;
		if (casecmp(host + host_len - suffix_len, wildcard + 1, suffix_len))
			return -1;
		return 0;
	}
}

#ifdef HAVE_ASN1_STRING_GET0_DATA
#define asn_string_data	ASN1_STRING_get0_data
#else
#define asn_string_data	ASN1_STRING_data
#endif

/*
 * This function is based on verifyhost in libcurl - I hope that it is correct.
 */
static int verify_ssl_host_name(X509 *server_cert, unsigned char *host)
{
	unsigned char ipv4_address[4];
#ifdef SUPPORT_IPV6
	unsigned char ipv6_address[16];
#endif
	unsigned char *address = NULL;
	int address_len = 0;
	int type = GEN_DNS;

	STACK_OF(GENERAL_NAME) *altnames;

	if (!numeric_ip_address(host, ipv4_address)) {
		address = ipv4_address;
		address_len = 4;
		type = GEN_IPADD;
	}
#ifdef SUPPORT_IPV6
	if (!is_numeric_ipv6_address(host, ipv6_address)) {
		address = ipv6_address;
		address_len = 16;
		type = GEN_IPADD;
	}
#endif

#if 1
	altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL);
	if (altnames) {
		int retval = 1;
		int i;
		int n_altnames = sk_GENERAL_NAME_num(altnames);
		for (i = 0; i < n_altnames; i++) {
			const GENERAL_NAME *altname = sk_GENERAL_NAME_value(altnames, i);
			const unsigned char *altname_ptr;
			int altname_len;
			if (altname->type != type) {
				if (altname->type == GEN_IPADD || altname->type == GEN_DNS || altname->type == GEN_URI)
					retval = S_INVALID_CERTIFICATE;
				continue;
			}
			altname_ptr = asn_string_data(altname->d.ia5);
			altname_len = ASN1_STRING_length(altname->d.ia5);
			if (type == GEN_IPADD) {
				if (altname_len == address_len && !memcmp(altname_ptr, address, address_len)) {
					retval = 0;
					break;
				}
			} else {
				if (altname_len == (int)strlen(cast_const_char altname_ptr) && !check_host_name(altname_ptr, host)) {
					retval = 0;
					break;
				}
			}
			retval = S_INVALID_CERTIFICATE;
		}
		GENERAL_NAMES_free(altnames);
		if (retval != 1)
			return retval;
	}
#endif

	{
		unsigned char *nulstr = cast_uchar "";
		unsigned char *peer_CN = nulstr;
		X509_NAME *name;
		int j, i = -1;

		retval = 1;

		name = X509_get_subject_name(server_cert);
		if (name)
			while ((j = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0)
				i = j;
		if (i >= 0) {
			ASN1_STRING *tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i));
			if (tmp) {
				if (ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
					j = ASN1_STRING_length(tmp);
					if (j >= 0) {
						peer_CN = OPENSSL_malloc(j + 1);
						if (peer_CN) {
							memcpy(peer_CN, asn_string_data(tmp), j);
							peer_CN[j] = '\0';
						}
					}
				} else {
					j = ASN1_STRING_to_UTF8(&peer_CN, tmp);
				}
				if (peer_CN && (int)strlen(cast_const_char peer_CN) != j) {
					retval = S_INVALID_CERTIFICATE;
				}
			}
		}
		if (peer_CN && peer_CN != nulstr) {
			if (retval == 1 && !check_host_name(peer_CN, host))
				retval = 0;
			OPENSSL_free(peer_CN);
		}
		if (retval != 1)
			return retval;
	}

	return S_INVALID_CERTIFICATE;
}

#else

static int verify_ssl_host_name(X509 *server_cert, unsigned char *host)
{
	int v;
	unsigned char ipv4_address[4];
#ifdef SUPPORT_IPV6
	unsigned char ipv6_address[16];
#endif

	if (!numeric_ip_address(host, ipv4_address)) {
		v = X509_check_ip(server_cert, ipv4_address, 4, 0);
	}
#ifdef SUPPORT_IPV6
	else if (!is_numeric_ipv6_address(host, ipv6_address)) {
		v = X509_check_ip(server_cert, ipv6_address, 16, 0);
	}
#endif
	else {
		v = X509_check_host(server_cert, cast_const_char host, strlen(cast_const_char host), 0, NULL);
	}

	return v == 1 ? 0 : S_INVALID_CERTIFICATE;
}

#endif

static unsigned char *extract_field(unsigned char *str, unsigned char *field)
{
	size_t len;
	unsigned char *f = cast_uchar strstr(cast_const_char str, cast_const_char field);
	if (!f)
		return NULL;
	f += strlen(cast_const_char field);
	len = strcspn(cast_const_char f, "/");
	return memacpy(f, len);
}

static unsigned char *extract_ca(unsigned char *str)
{
	unsigned char *c, *o;
	c = extract_field(str, cast_uchar "/C=");
	o = extract_field(str, cast_uchar "/O=");
	if (!o)
		o = extract_field(str, cast_uchar "/CN=");
	if (!o) {
		if (c) mem_free(c), c = NULL;
		o = stracpy(str);
	}
	if (c) {
		add_to_strn(&o, cast_uchar ", ");
		add_to_strn(&o, c);
		mem_free(c);
	}
	return o;
}

int verify_ssl_certificate(links_ssl *ssl, unsigned char *host)
{
	X509 *server_cert;
	int ret;

	if (ssl->ca != NULL)
		mem_free(ssl->ca), ssl->ca = NULL;

	if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
		clear_ssl_errors(__LINE__);
		return S_INVALID_CERTIFICATE;
	}
#ifdef HAVE_SSL_GET1_PEER_CERTIFICATE
	server_cert = SSL_get1_peer_certificate(ssl->ssl);
#else
	server_cert = SSL_get_peer_certificate(ssl->ssl);
#endif
	if (!server_cert) {
		clear_ssl_errors(__LINE__);
		return S_INVALID_CERTIFICATE;
	}
	ret = verify_ssl_host_name(server_cert, host);
	if (!ret) {
#ifdef SSL_GET0_VERIFIED_CHAIN
		STACK_OF(X509) *certs = SSL_get0_verified_chain(ssl->ssl);
#else
		STACK_OF(X509) *certs = SSL_get_peer_cert_chain(ssl->ssl);
#endif
		if (certs) {
			int num = sk_X509_num(certs);
			int i;
			unsigned char *last_ca = NULL;
			unsigned char *cas = init_str();
			int casl = 0;
			for (i = num - 1; i >= 0; i--) {
				unsigned char space[3072];
				unsigned char *n;
				X509 *cert = sk_X509_value(certs, i);
				X509_NAME *name;
				name = X509_get_issuer_name(cert);
				n = cast_uchar X509_NAME_oneline(name, cast_char space, 3072);
				if (n) {
					unsigned char *ca = extract_ca(n);
					if (!last_ca || strcmp(cast_const_char ca, cast_const_char last_ca)) {
						if (casl)
							add_to_str(&cas, &casl, CERT_RIGHT_ARROW);
						add_to_str(&cas, &casl, ca);
						if (last_ca)
							mem_free(last_ca);
						last_ca = ca;
					} else {
						mem_free(ca);
					}
				}
			}
			if (last_ca)
				mem_free(last_ca);
			if (casl)
				ssl->ca = cas;
			else
				mem_free(cas);
		}
	}
	X509_free(server_cert);
	clear_ssl_errors(__LINE__);
	return ret;
}

int verify_ssl_cipher(links_ssl *ssl)
{
	unsigned char *method;
	unsigned char *cipher;
	method = cast_uchar SSL_get_version(ssl->ssl);
	if (!strncmp(cast_const_char method, "SSL", 3))
		return S_INSECURE_CIPHER;
	if (SSL_get_cipher_bits(ssl->ssl, NULL) < 112)
		return S_INSECURE_CIPHER;
	cipher = cast_uchar SSL_get_cipher_name(ssl->ssl);
	if (cipher) {
		if (strstr(cast_const_char cipher, "RC4"))
			return S_INSECURE_CIPHER;
		if (strstr(cast_const_char cipher, "NULL"))
			return S_INSECURE_CIPHER;
	}
	return 0;
}

#endif

int ssl_not_reusable(links_ssl *ssl)
{
	unsigned char *cipher;
	if (!ssl || ssl == DUMMY)
		return 0;
	ssl->bytes_read = (ssl->bytes_read + 4095) & ~4095;
	ssl->bytes_written = (ssl->bytes_written + 4095) & ~4095;
	cipher = cast_uchar SSL_get_cipher_name(ssl->ssl);
	if (cipher) {
		if (strstr(cast_const_char cipher, "RC4-") ||
		    strstr(cast_const_char cipher, "DES-") ||
		    strstr(cast_const_char cipher, "RC2-") ||
		    strstr(cast_const_char cipher, "IDEA-") ||
		    strstr(cast_const_char cipher, "GOST-")) {
			return ssl->bytes_read + ssl->bytes_written >= 1 << 20;
		}
	}
	return 0;
}

unsigned char *get_cipher_string(links_ssl *ssl)
{
	unsigned char *version, *cipher;
	unsigned char *s = init_str();
	int l = 0;

	add_num_to_str(&s, &l, SSL_get_cipher_bits(ssl->ssl, NULL));
	add_to_str(&s, &l, cast_uchar "-bit");

	version = cast_uchar SSL_get_version(ssl->ssl);
	if (version) {
		add_chr_to_str(&s, &l, ' ');
		add_to_str(&s, &l, version);
	}
	cipher = cast_uchar SSL_get_cipher_name(ssl->ssl);
	if (cipher) {
		add_chr_to_str(&s, &l, ' ');
		add_to_str(&s, &l, cipher);
	}
#if defined(SSL_SESSION_RESUME) && 0
	if (SSL_session_reused(ssl->ssl)) {
		add_to_str(&s, &l, cast_uchar " (reused session)");
	}
#endif
	return s;
}

#ifdef SSL_SESSION_RESUME

struct session_cache_entry {
	list_entry_1st
	uttime absolute_time;
	SSL_CTX *ctx;
	SSL_SESSION *session;
	int port;
	list_entry_last
	unsigned char host[1];
};

static struct list_head session_cache = { &session_cache, &session_cache };

static struct session_cache_entry *find_session_cache_entry(SSL_CTX *ctx, unsigned char *host, int port)
{
	struct session_cache_entry *sce;
	struct list_head *lsce;
	foreach(struct session_cache_entry, sce, lsce, session_cache)
		if (sce->ctx == ctx && !strcmp(cast_const_char sce->host, cast_const_char host))
			return sce;
	return NULL;
}

SSL_SESSION *get_session_cache_entry(SSL_CTX *ctx, unsigned char *host, int port)
{
	struct session_cache_entry *sce = find_session_cache_entry(ctx, host, port);
	if (!sce)
		return NULL;
	if (get_absolute_time() - sce->absolute_time > SESSION_TIMEOUT)
		return NULL;
	return sce->session;
}

static void set_session_cache_entry(SSL_CTX *ctx, unsigned char *host, int port, SSL_SESSION *s)
{
	struct session_cache_entry *sce = find_session_cache_entry(ctx, host, port);
	size_t sl;
	if (sce) {
		SSL_SESSION_free(sce->session);
		if (s) {
			sce->session = s;
		} else {
			del_from_list(sce);
			mem_free(sce);
		}
		return;
	}
	if (!s)
		return;
	sl = strlen(cast_const_char host);
	if (sl > MAXINT - sizeof(struct session_cache_entry)) return;
	sce = mem_alloc(sizeof(struct session_cache_entry) + sl);
	sce->absolute_time = get_absolute_time();
	sce->ctx = ctx;
	sce->session = s;
	sce->port = port;
	strcpy(cast_char sce->host, cast_const_char host);
	add_to_list(session_cache, sce);
}

void retrieve_ssl_session(struct connection *c)
{
	if (c->ssl && !c->ssl->session_retrieved && !proxies.only_proxies) {
		SSL_SESSION *s;
		unsigned char *orig_url, *h;
		int p;

		if (c->no_tls /*|| SSL_session_reused(c->ssl->ssl)*/) {
			s = NULL;
			c->ssl->session_retrieved = 1;
		} else {
			s = SSL_get1_session(c->ssl->ssl);
		}
#ifdef HAVE_SSL_SESSION_IS_RESUMABLE
		if (s && !SSL_SESSION_is_resumable(s)) {
			SSL_SESSION_free(s);
			s = NULL;
		}
#endif
		orig_url = remove_proxy_prefix(c->url);
		h = get_host_name(orig_url);
		p = get_port(orig_url);
		if (s)
			c->ssl->session_retrieved = 1;
		set_session_cache_entry(c->ssl->ctx, h, p, s);
		mem_free(h);
		clear_ssl_errors(__LINE__);
	}
}

static int shrink_session_cache(int u)
{
	uttime now = get_absolute_time();
	struct session_cache_entry *d;
	struct list_head *ld;
	int f = 0;
#ifdef HAVE_CRYPTO_SET_MEM_FUNCTIONS
	if (in_ssl_malloc_hook++)
		goto ret;
#endif
	if (u == SH_FREE_SOMETHING && !list_empty(session_cache)) {
		d = list_struct(session_cache.prev, struct session_cache_entry);
		goto delete_last;
	}
	foreach(struct session_cache_entry, d, ld, session_cache) if (u == SH_FREE_ALL || now - d->absolute_time > SESSION_TIMEOUT) {
delete_last:
		ld = d->list_entry.prev;
		del_from_list(d);
		SSL_SESSION_free(d->session);
		mem_free(d);
		f = ST_SOMETHING_FREED;
	}
#ifdef HAVE_CRYPTO_SET_MEM_FUNCTIONS
ret:
	in_ssl_malloc_hook--;
#endif
	return f | (list_empty(session_cache) ? ST_CACHE_EMPTY : 0);
}

unsigned long session_info(int type)
{
	switch (type) {
		case CI_FILES:
			return list_size(&session_cache);
		default:
			internal_error("session_info: bad request");
	}
	return 0;
}

void init_session_cache(void)
{
	register_cache_upcall(shrink_session_cache, 0, cast_uchar "session");
}

#endif

#else

void https_func(struct connection *c)
{
	setcstate(c, S_NO_SSL);
	abort_connection(c);
}

#endif
