diff options
Diffstat (limited to 'tcpd/libcouriertls.c')
| -rw-r--r-- | tcpd/libcouriertls.c | 1560 | 
1 files changed, 1560 insertions, 0 deletions
| diff --git a/tcpd/libcouriertls.c b/tcpd/libcouriertls.c new file mode 100644 index 0000000..6eee8b0 --- /dev/null +++ b/tcpd/libcouriertls.c @@ -0,0 +1,1560 @@ +/* +** Copyright 2000-2009 Double Precision, Inc. +** See COPYING for distribution information. +*/ +#include	"config.h" +#include	"argparse.h" +#include	"spipe.h" +#define COURIERTCPD_EXPOSE_OPENSSL 1 +#include	"libcouriertls.h" +#include	<openssl/rand.h> +#include	<openssl/x509.h> +#include	"tlscache.h" +#include	"rfc1035/rfc1035.h" +#include	"soxwrap/soxwrap.h" +#include	"random128/random128.h" +#ifdef  getc +#undef  getc +#endif +#include	<stdio.h> +#include	<string.h> +#include	<stdlib.h> +#include	<ctype.h> +#include	<netdb.h> +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif +#if	HAVE_UNISTD_H +#include	<unistd.h> +#endif +#if	HAVE_FCNTL_H +#include	<fcntl.h> +#endif +#include	<errno.h> +#if	HAVE_SYS_TYPES_H +#include	<sys/types.h> +#endif +#if	HAVE_SYS_STAT_H +#include	<sys/stat.h> +#endif +#include	<sys/socket.h> +#include	<arpa/inet.h> + +#include	<sys/time.h> + +/***** TODO *****/ + +/* #define TLSCACHEDEBUG */ + +static const char *safe_getenv(const struct tls_info *info, const char *n) +{ +	const char *v=(*info->getconfigvar)(n, info->app_data); + +	if (!v)	v=""; +	return (v); +} + +static int get_peer_verify_level(const struct tls_info *info) +{ +	int peer_verify_level=SSL_VERIFY_PEER; +		/* SSL_VERIFY_NONE */ +		/* SSL_VERIFY_PEER */ +		/* SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT */ +	const char *s=safe_getenv(info, "TLS_VERIFYPEER"); + +	if (info->peer_verify_domain) +		return SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + +	switch (*s)	{ +	case 'n': +	case 'N':		/* NONE */ +		peer_verify_level=SSL_VERIFY_NONE; +		break; +	case 'p': +	case 'P':		/* PEER */ +		peer_verify_level=SSL_VERIFY_PEER; +		break; +	case 'r': +	case 'R':		/* REQUIREPEER */ +		peer_verify_level= +			SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT; +		break; +	} +	return (peer_verify_level); +} + +static int ssl_verify_callback(int goodcert, X509_STORE_CTX *x509) +{ +	SSL *ssl= +		X509_STORE_CTX_get_ex_data(x509, +					   SSL_get_ex_data_X509_STORE_CTX_idx() +					   ); +	struct tls_info *info=SSL_get_app_data(ssl); + +	if (info->peer_verify_domain || get_peer_verify_level(info)) +	{ +		if (!goodcert) +			return (0); + +		info->certificate_verified=1; +	} + +	return (1); +} + +static int verifypeer(const struct tls_info *info, SSL *ssl) +{ +	X509 *x=NULL; +	X509_NAME *subj=NULL; +	int nentries, j; +	char domain[256]; +	char *p; +	char errmsg[1000]; + +	if (!info->peer_verify_domain) +		return (1); + +	if (info->isserver) +	{ +		x=SSL_get_peer_certificate(ssl); + +		if (x) +			subj=X509_get_subject_name(x); +	} +	else +	{ +		STACK_OF(X509) *peer_cert_chain=SSL_get_peer_cert_chain(ssl); + +		if (peer_cert_chain && peer_cert_chain->stack.num > 0) +		{ +			X509 *xx=(X509 *)peer_cert_chain->stack.data[0]; + +			if (xx) +				subj=X509_get_subject_name(xx); +		} +	} + +	 +	nentries=0; +	if (subj) +		nentries=X509_NAME_entry_count(subj); + +	domain[0]=0; +	for (j=0; j<nentries; j++) +	{ +		const char *obj_name; +		X509_NAME_ENTRY *e; +		ASN1_OBJECT *o; +		ASN1_STRING *d; + +		int dlen; +		unsigned char *ddata; + +		e=X509_NAME_get_entry(subj, j); +		if (!e) +			continue; + +		o=X509_NAME_ENTRY_get_object(e); +		d=X509_NAME_ENTRY_get_data(e); + +		if (!o || !d) +			continue; + +		obj_name=OBJ_nid2sn(OBJ_obj2nid(o)); + +		dlen=ASN1_STRING_length(d); +		ddata=ASN1_STRING_data(d); + +		if (strcasecmp(obj_name, "CN") == 0) +		{ +			if (dlen >= sizeof(domain)-1) +				dlen=sizeof(domain)-1; + +			memcpy(domain, ddata, dlen); +			domain[dlen]=0; +		} +	} + +	if (x) +		X509_free(x); +	p=domain; + +	if (*p == '*') +	{ +		int	pl, l; + +		pl=strlen(++p); +		l=strlen(info->peer_verify_domain); + +		if (*p == '.' && pl <= l && +		    strcasecmp(info->peer_verify_domain+l-pl, p) == 0) +			return (1); +	} +	else if (strcasecmp(info->peer_verify_domain, p) == 0) +		return (1); + +	strcpy(errmsg, "couriertls: Mismatched SSL certificate: CN="); +	strcat(errmsg, domain); +	strcat(errmsg, " (expected "); +	strncat(errmsg, info->peer_verify_domain, 256); +	strcat(errmsg, ")"); +	(*info->tls_err_msg)(errmsg, info->app_data); +	return (0); +} + +#ifndef NO_RSA + +static RSA *rsa_callback(SSL *s, int export, int keylength) +{ +	return (RSA_generate_key(keylength,RSA_F4,NULL,NULL)); +} + +#endif + +static void nonsslerror(const struct tls_info *info, const char *pfix) +{ +	char errmsg[256]; + +	strcpy(errmsg, "couriertls: "); +	strncat(errmsg, pfix, 200); +	strcat(errmsg, ": "); +	strncat(errmsg, strerror(errno), 255 - strlen(errmsg)); + +	(*info->tls_err_msg)(errmsg, info->app_data); +} + +static void sslerror(const struct tls_info *info, const char *pfix, int rc) +{ +	char errmsg[256]; +	char errmsgbuf2[300]; +	int errnum=ERR_get_error(); + +	if (errnum == 0) +	{ +		if (rc == 0) +		{ +			(*info->tls_err_msg)("DEBUG: Unexpected SSL connection shutdown.", +					     info->app_data); +			return; +		} + +		nonsslerror(info, pfix); +		return; +	} + +	ERR_error_string_n(errnum, errmsg, sizeof(errmsg)-1); + +	errmsg[sizeof(errmsg)-1]=0; + +	strcpy(errmsgbuf2, "couriertls: "); +	strncat(errmsgbuf2, pfix, 200); +	strcat(errmsgbuf2, ": "); +	strncat(errmsgbuf2, errmsg, 299 - strlen(errmsgbuf2)); + +	(*info->tls_err_msg)(errmsgbuf2, info->app_data); +} + +static void init_session_cache(struct tls_info *, SSL_CTX *); + +static int process_rsacertfile(SSL_CTX *ctx, const char *filename) +{ +#ifndef	NO_RSA + +	const struct tls_info *info=SSL_CTX_get_app_data(ctx); + +	SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback); + +	if(!SSL_CTX_use_certificate_chain_file(ctx, filename)) +	{ +		sslerror(info, filename, -1); +		return (0); +	} + +	if(!SSL_CTX_use_RSAPrivateKey_file(ctx, filename, SSL_FILETYPE_PEM)) +	{ +		sslerror(info, filename, -1); +		return (0); +	} +#endif +	return (1); +} + + +static int process_dhcertfile(SSL_CTX *ctx, const char *filename) +{ +#ifndef	NO_DH + +	const struct tls_info *info=SSL_CTX_get_app_data(ctx); +	BIO	*bio; +	DH	*dh; +	int	cert_done=0; + +	if(!SSL_CTX_use_certificate_chain_file(ctx, filename)) +	{ +		sslerror(info, filename, -1); +		return (0); +	} + +	if ((bio=BIO_new_file(filename, "r")) != 0) +	{ +		if ((dh=PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) != 0) +		{ +			SSL_CTX_set_tmp_dh(ctx, dh); +			cert_done=1; +			DH_free(dh); +		} +		else +			sslerror(info, filename, -1); +		BIO_free(bio); +	} +	else +		sslerror(info, filename, -1); + +	if (!cert_done) +	{ +		(*info->tls_err_msg)("couriertls: DH init failed!", +				     info->app_data); + +		return (0); +	} + +	if(!SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM)) +	{ +		sslerror(info, filename, -1); +		return (0); +	} +#endif +	return (1); +} + +static int process_certfile(SSL_CTX *ctx, const char *certfile, const char *ip, +			    int (*func)(SSL_CTX *, const char *)) +{ +	if (ip && *ip) +	{ +		char *test_file; + +		if (strncmp(ip, "::ffff:", 7) == 0 && strchr(ip, '.')) +			return (process_certfile(ctx, certfile, ip+7, func)); + +		test_file= malloc(strlen(certfile)+strlen(ip)+2); + +		strcpy(test_file, certfile); +		strcat(test_file, "."); +		strcat(test_file, ip); + +		if (access(test_file, R_OK) == 0) +		{ +			int rc= (*func)(ctx, test_file); + +			free(test_file); +			return rc; +		} +		free(test_file); +	} + +	return (*func)(ctx, certfile); +} + +static int client_cert_cb(ssl_handle ssl, X509 **x509, EVP_PKEY **pkey) +{ +	struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl); +	int i; +	const char *pem_cert; +	size_t pem_cert_size; +	STACK_OF(X509_NAME) *client_cas; +	int cert_num=0; +	int rc; + +	if (info->getpemclientcert4ca == NULL) +		return 0; + +	rc=0; +	client_cas=SSL_get_client_CA_list(ssl); + +	if (info->loadpemclientcert4ca) +		(*info->loadpemclientcert4ca)(info->app_data); + +	for (cert_num=0; (*info->getpemclientcert4ca)(cert_num, &pem_cert, +						      &pem_cert_size, +						      info->app_data); +	     ++cert_num) +	{ +		BIO *certbio; +		int err; +		X509 *x; + +		ERR_clear_error(); + +		certbio=BIO_new_mem_buf((void *)pem_cert, pem_cert_size); + +		if (!certbio) +		{ +			rc= -1; +			break; +		} + +		x=PEM_read_bio_X509(certbio, x509, NULL, NULL); + +		if (!x) +		{ +			BIO_free(certbio); +			continue; +		} + +		for (i=0; client_cas && i<client_cas->stack.num; i++) +		{ +			X509_NAME *cert=(X509_NAME *)client_cas->stack.data[i]; + +			if (X509_NAME_cmp(cert, +					  x->cert_info->issuer) == 0) +				break; +		} + +		if (!client_cas || i >= client_cas->stack.num) +		{ +			BIO_free(certbio); +			continue; +		} + +		while ((x=PEM_read_bio_X509(certbio, NULL, +					    NULL, 0)) != NULL) +		{ +			if (SSL_CTX_add_extra_chain_cert(SSL_get_SSL_CTX(ssl), +							 x) +			    != 1) +			{ +				X509_free(x); +				rc= -1; +				break; +			} +		} + +		err = ERR_peek_last_error(); +		if (rc || ERR_GET_LIB(err) != ERR_LIB_PEM || +		    ERR_GET_REASON(err) != PEM_R_NO_START_LINE) +		{ +			BIO_free(certbio); +			continue; +		} +		BIO_free(certbio); + +		ERR_clear_error(); + +		certbio=BIO_new_mem_buf((void *)pem_cert, pem_cert_size); + +		if (!certbio) +		{ +			rc= -1; +			break; +		} + +		if (!PEM_read_bio_PrivateKey(certbio, pkey, NULL, NULL)) +		{ +			BIO_free(certbio); +			continue; +		} + +		BIO_free(certbio); +		rc=1; +		break; +	} +	ERR_clear_error(); +	(*info->releasepemclientcert4ca)(info->app_data); +	return rc; +} + +SSL_CTX *tls_create(int isserver, const struct tls_info *info) +{ +	SSL_CTX *ctx; +	const char *protocol=safe_getenv(info, "TLS_PROTOCOL"); +	const char *ssl_cipher_list=safe_getenv(info, "TLS_CIPHER_LIST"); +	int session_timeout=atoi(safe_getenv(info, "TLS_TIMEOUT")); +	const char *dhcertfile=safe_getenv(info, "TLS_DHCERTFILE"); +	const char *certfile=safe_getenv(info, "TLS_CERTFILE"); +	const char *s; +	struct stat stat_buf; +	const char *peer_cert_dir=NULL; +	const char *peer_cert_file=NULL; +	int n; +	struct tls_info *info_copy; + +	if (!*ssl_cipher_list) +		ssl_cipher_list=NULL; + +	if (!*dhcertfile) +		dhcertfile=NULL; + +	if (!*certfile) +		certfile=NULL; + +	s=safe_getenv(info, "TLS_TRUSTCERTS"); +	if (s && stat(s, &stat_buf) == 0) +	{ +		if (S_ISDIR(stat_buf.st_mode)) +			peer_cert_dir=s; +		else +			peer_cert_file=s; +	} +	else if (info->peer_verify_domain) +	{ +		errno=ENOENT; +		nonsslerror(info, "TLS_TRUSTCERTS not set"); +		return (NULL); +	} + +	{ +		static int first=1; + +		if (first) +		{ +			first=0; +			SSL_load_error_strings(); +			SSLeay_add_ssl_algorithms(); + +			while (RAND_status() != 1) +			{ +				const char *p=random128(); +				size_t l=strlen(p); + +				RAND_add(p, l, l/16); +			} +		} +	} + + +	info_copy=malloc(sizeof(struct tls_info)); + +	if (info_copy == NULL) +	{ +		nonsslerror(info, "malloc"); +		return (NULL); +	} + +	memcpy(info_copy, info, sizeof(*info_copy)); +	info_copy->isserver=isserver; +	info_copy->certificate_verified=0; + +	if (!protocol || !*protocol) +		protocol="SSL23"; + +	ctx=SSL_CTX_new(protocol && strcmp(protocol, "SSL3") == 0 +			? SSLv3_method(): +			protocol && strcmp(protocol, "SSL23") == 0 +			? SSLv23_method(): +			TLSv1_method()); + +	if (!ctx) +	{ +		free(info_copy); +		nonsslerror(info, "SSL_CTX_NEW"); +		return (0); +	} +	SSL_CTX_set_app_data(ctx, info_copy); +	SSL_CTX_set_options(ctx, SSL_OP_ALL); + +	if (!ssl_cipher_list) +		ssl_cipher_list="SSLv3:TLSv1:HIGH:!LOW:!MEDIUM:!EXP:!NULL:!aNULL@STRENGTH"; + +	SSL_CTX_set_cipher_list(ctx, ssl_cipher_list); +	SSL_CTX_set_timeout(ctx, session_timeout); + +	info_copy->tlscache=NULL; +	init_session_cache(info_copy, ctx); + + +	s = safe_getenv(info, "TCPLOCALIP"); + +	if (certfile && !process_certfile(ctx, certfile, s, +					  process_rsacertfile)) +	{ +		tls_destroy(ctx); +		return (NULL); +	} + +	if (dhcertfile && !process_certfile(ctx, dhcertfile, s, +					    process_dhcertfile)) +	{ +		tls_destroy(ctx); +		return (NULL); +	} + +	SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH); + +	n=atoi(safe_getenv(info, "TLS_INTCACHESIZE")); + +	if (n > 0) +		SSL_CTX_sess_set_cache_size(ctx, n); + +	if (peer_cert_dir || peer_cert_file) +	{ +		if ((!SSL_CTX_set_default_verify_paths(ctx)) +			|| (!SSL_CTX_load_verify_locations(ctx, peer_cert_file, +				peer_cert_dir))) +		{ +			sslerror(info, peer_cert_file ? +				 peer_cert_file:peer_cert_dir, -1); +			tls_destroy(ctx); +			return (0); +		} + +		if (isserver && peer_cert_file) +		{ +			SSL_CTX_set_client_CA_list(ctx, +						   SSL_load_client_CA_file +						   (peer_cert_file)); +		} + +		if (isserver && peer_cert_dir) +		{ +			DIR *dirp; +			struct dirent *de; +			X509 *x; + +			dirp=opendir(peer_cert_dir); +			while (dirp && (de=readdir(dirp)) != NULL) +			{ +				const char *p; +				char *q; +				FILE *fp; + +				p=strrchr(de->d_name, '.'); +				if (!p || !p[1]) +					continue; +				while (*++p) +				{ +					if (strchr("0123456789", *p) == NULL) +						break; +				} +				if (*p) +					continue; + +				q=malloc(strlen(peer_cert_dir) +					 +strlen(de->d_name) + 4); +				if (!q) +				{ +					nonsslerror(info, "malloc"); +					exit(1); +				} + +				strcat(strcat(strcpy(q, peer_cert_dir), +					      "/"), de->d_name); + +				fp=fopen(q, "r"); +				if (!fp) +				{ +					nonsslerror(info, q); +					exit(1); +				} +				free(q); + +				while ((x=PEM_read_X509(fp, NULL, NULL, NULL))) +				{ +					SSL_CTX_add_client_CA(ctx,x); +					X509_free(x); +				} +				fclose(fp); +			} +			if (dirp) +				closedir(dirp); +                } +	} +	SSL_CTX_set_verify(ctx, get_peer_verify_level(info), +			   ssl_verify_callback); +	if (!isserver) +		SSL_CTX_set_client_cert_cb(ctx, client_cert_cb); +	return (ctx); +} + +void tls_destroy(SSL_CTX *ctx) +{ +	struct tls_info *info=SSL_CTX_get_app_data(ctx); + +	SSL_CTX_flush_sessions(ctx, 0); /* OpenSSL bug, 2002-08-07 */ + +	SSL_CTX_free(ctx); + +	if (info->tlscache) +	{ +		tls_cache_close(info->tlscache); +		info->tlscache=NULL; +	} +	free(info); +} + +static int cache_add(SSL *ssl, SSL_SESSION *sess); + +static SSL_SESSION *cache_get(SSL *ssl, unsigned char *id, int id_len, +			      int *copyflag); +static void cache_del(SSL_CTX *ctx, SSL_SESSION *ssl); + +static void init_session_cache(struct tls_info *info, SSL_CTX *ctx) +{ +	const char *filename=safe_getenv(info, "TLS_CACHEFILE"); +	const char *cachesize=safe_getenv(info, "TLS_CACHESIZE"); +	off_t cachesize_l; + +	if (!filename || !*filename) +	{ +		SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); +		return; +	} + +	if (info->tlscache == NULL) +	{ +		cachesize_l= cachesize ? (off_t)atol(cachesize):0; + +		if (cachesize_l <= 0) +			cachesize_l=512L * 1024; +		if ((info->tlscache=tls_cache_open(filename, cachesize_l)) +		    == NULL) +		{ +			nonsslerror(info, filename); +			return; +		} +	} + +        SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH); +	SSL_CTX_sess_set_new_cb(ctx, cache_add); +	SSL_CTX_sess_set_get_cb(ctx, cache_get); +	SSL_CTX_sess_set_remove_cb(ctx, cache_del); +} + +static int cache_add(SSL *ssl, SSL_SESSION *sess) +{ +	struct tls_info *info=SSL_get_app_data(ssl); +	unsigned char buffer[BUFSIZ]; +	unsigned char *ucp; +	time_t timeout= (time_t)SSL_SESSION_get_time(sess) +		+ SSL_SESSION_get_timeout(sess); +	void *session_id=(void *)sess->session_id; +	size_t session_id_len=sess->session_id_length; +	size_t sess_len=i2d_SSL_SESSION(sess, NULL); + +	if (sizeof(timeout) + sizeof(session_id_len) + session_id_len + +	    sess_len > sizeof(buffer)) +	{ +		fprintf(stderr, "WARN: starttls.c: buffer not big enough to cache SSL_SESSION\n"); +		return (0);	/* Too big */ +	} + +	memcpy(buffer, &timeout, sizeof(timeout)); +	memcpy(buffer+sizeof(timeout), &session_id_len, +	       sizeof(session_id_len)); +	memcpy(buffer+sizeof(timeout)+sizeof(session_id_len), +	       session_id, session_id_len); +	ucp=buffer+sizeof(timeout)+ +		sizeof(session_id_len)+session_id_len; + +	i2d_SSL_SESSION(sess, &ucp); +	if (tls_cache_add(info->tlscache, (char *)buffer, +			  (size_t)(sizeof(timeout) + +				   sizeof(session_id_len) + +				   session_id_len + sess_len))) +		perror("ALERT: tls_cache_add: "); + +#ifdef TLSCACHEDEBUG +	fprintf(stderr, "INFO: TLSCACHE: added\n"); +#endif +	return 0; +} + +struct walk_info { +	unsigned char *id; +	int id_len; +	int *copyflag; +	SSL_SESSION *ret; +	time_t now; +}; + +static int get_func(void *rec, size_t recsize, +		    int *doupdate, void *arg); + +static SSL_SESSION *cache_get(SSL *ssl, unsigned char *id, int id_len, +			      int *copyflag) +{ +	const struct tls_info *info=SSL_get_app_data(ssl); +	struct walk_info wi; + +	wi.id=id; +	wi.id_len=id_len; +	wi.copyflag=copyflag; +	wi.ret=NULL; +	time(&wi.now); +	if (tls_cache_walk(info->tlscache, get_func, &wi) < 0) +		perror("ALERT: tls_cache_walk: "); + +#ifdef TLSCACHEDEBUG +	fprintf(stderr, "INFO: TLSCACHE: session %s\n", +		wi.ret ? "found":"not found"); +#endif +	if (wi.ret) +		SSL_set_session_id_context(ssl, id, id_len); +	return wi.ret; +} + +static int get_func(void *rec, size_t recsize, +		    int *doupdate, void *arg) +{ +	unsigned char *recp=(unsigned char *)rec; +	struct walk_info *wi=(struct walk_info *)arg; +	time_t timeout; +	size_t session_id_len; + +	unsigned char *sess; + +	if (recsize < sizeof(timeout)+sizeof(session_id_len)) +		return (0); + +	memcpy(&timeout, recp, sizeof(timeout)); + +	if (timeout <= wi->now) +		return (0); + +	memcpy(&session_id_len, recp + sizeof(timeout), +	       sizeof(session_id_len)); + +	if (session_id_len != (size_t)wi->id_len || +	    memcmp(recp + sizeof(timeout) + sizeof(session_id_len), +		   wi->id, session_id_len)) +		return (0); + +	sess=recp + sizeof(timeout) + sizeof(session_id_len) + session_id_len; + +	wi->ret=d2i_SSL_SESSION(NULL, (const unsigned char **) +				&sess, recsize - sizeof(timeout) - +				sizeof(session_id_len) - session_id_len); + +	*wi->copyflag=0; +	return 1; +} + +static int del_func(void *rec, size_t recsize, +		    int *doupdate, void *arg); + +static void cache_del(SSL_CTX *ctx, SSL_SESSION *sess) +{ +	const struct tls_info *info=SSL_CTX_get_app_data(ctx); +	struct walk_info wi; + +	wi.now=0; + +	wi.id=(unsigned char *)sess->session_id; +	wi.id_len=sess->session_id_length; +	if (tls_cache_walk(info->tlscache, del_func, &wi) < 0) +		perror("ALERT: tls_cache_walk: "); +} + +static int del_func(void *rec, size_t recsize, +		    int *doupdate, void *arg) +{ +	unsigned char *recp=(unsigned char *)rec; +	struct walk_info *wi=(struct walk_info *)arg; +	time_t timeout; +	size_t session_id_len; + +	if (recsize < sizeof(timeout)+sizeof(session_id_len)) +		return (0); + +	memcpy(&timeout, recp, sizeof(timeout)); + +	if (timeout <= wi->now) +		return (0); + +	memcpy(&session_id_len, recp + sizeof(timeout), +	       sizeof(session_id_len)); + +	if (session_id_len != (size_t)wi->id_len || +	    memcmp(recp + sizeof(timeout) + sizeof(session_id_len), +		   wi->id, session_id_len)) +		return (0); + +	timeout=0; +	memcpy(recp, &timeout, sizeof(timeout)); +	*doupdate=1; +#ifdef TLSCACHEDEBUG +	fprintf(stderr, "INFO: TLSCACHE: deleted\n"); +#endif +	return (1); +} + + +/* ----------------------------------------------------------------- */ + +SSL *tls_connect(SSL_CTX *ctx, int fd) +{ +	struct tls_info *info=SSL_CTX_get_app_data(ctx); +	SSL *ssl; +	int rc; + +	/* +	**  Initialize a tls_transfer_info object. +	*/ + +	if (fcntl(fd, F_SETFL, O_NONBLOCK)) +	{ +		nonsslerror(info, "fcntl"); +		return (NULL); +	} + +#ifdef  SO_KEEPALIVE + +	{ +	int	dummy; + +		dummy=1; + +		if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, +			(const char *)&dummy, sizeof(dummy)) < 0) +                { +                        nonsslerror(info, "setsockopt"); +			return (NULL); +                } +	} +#endif + +#ifdef  SO_LINGER +	{ +	struct linger l; + +		l.l_onoff=0; +		l.l_linger=0; + +		if (setsockopt(fd, SOL_SOCKET, SO_LINGER, +			(const char *)&l, sizeof(l)) < 0) +		{ +			nonsslerror(info, "setsockopt"); +			return (NULL); +		} +	} +#endif + +	if (!(ssl=SSL_new(ctx))) +	{ +		sslerror(info, "SSL_new", -1); +		return (NULL); +	} + +	SSL_set_app_data(ssl, info); + +	SSL_set_fd(ssl, fd); +	info->accept_interrupted=0; +	info->connect_interrupted=0; + +	if (info->isserver) +	{ +		SSL_set_accept_state(ssl); +		if ((rc=SSL_accept(ssl)) > 0) +		{ +			if (!verifypeer(info, ssl)) +			{ +				tls_disconnect(ssl, fd); +				return (NULL); +			} + +			if (info->connect_callback != NULL && +			    !(*info->connect_callback)(ssl, info->app_data)) +			{ +				tls_disconnect(ssl, fd); +				return (NULL); +			} + +			return ssl; +		} +		info->accept_interrupted=1; +	} +	else +	{ +		SSL_set_connect_state(ssl); + +		if ((rc=SSL_connect(ssl)) > 0) +		{ +			if (!verifypeer(info, ssl)) +			{ +				tls_disconnect(ssl, fd); +				return (NULL); +			} + +			if (info->connect_callback != NULL && +			    !(*info->connect_callback)(ssl, info->app_data)) +			{ +				tls_disconnect(ssl, fd); +				return (NULL); +			} +			return (ssl); +		} +		info->connect_interrupted=1; +	} + +	switch (SSL_get_error(ssl, rc))	{ +	case SSL_ERROR_WANT_WRITE: +	case SSL_ERROR_WANT_READ: +		break; +	default: +		sslerror(info, "connect", rc); +		tls_disconnect(ssl, fd); +		return NULL; +	} + +	return (ssl); +} + +void tls_disconnect(SSL *ssl, int fd) +{ +	fcntl(fd, F_SETFL, 0); +	SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); +	SSL_free(ssl); +	ERR_remove_state(0); +} + +/* --------------------------------------- */ + +int	tls_transfer(struct tls_transfer_info *t, SSL *ssl, int fd, +		     fd_set *r, fd_set *w) +{ +	struct tls_info *info=SSL_get_app_data(ssl); +	int n; + +	if (info->connect_interrupted) +	{ +		n=SSL_connect(ssl); + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_NONE: +			info->connect_interrupted=0; +			break; +		case SSL_ERROR_WANT_WRITE: +			FD_SET(fd, w); +			return (1); +		case SSL_ERROR_WANT_READ: +			FD_SET(fd, r); +			return (1); +		default: +			info->connect_interrupted=0; +			t->shutdown=1; +			sslerror(info, "connect", n); +			return (-1); +		} + +		if (!verifypeer(info, ssl)) +		{ +			info->connect_interrupted=0; +			t->shutdown=1; +			return (-1); +		} +		if (info->connect_callback != NULL && +		    !(*info->connect_callback)(ssl, info->app_data)) +		{ +			info->connect_interrupted=0; +			t->shutdown=1; +			return (-1); +		} +	} +	else if (info->accept_interrupted) +	{ +		n=SSL_accept(ssl); + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_NONE: +			info->accept_interrupted=0; +			break; +		case SSL_ERROR_WANT_WRITE: +			FD_SET(fd, w); +			return (1); +		case SSL_ERROR_WANT_READ: +			FD_SET(fd, r); +			return (1); +		default: +			info->accept_interrupted=0; +			t->shutdown=1; +			sslerror(info, "accept", n); +			return (-1); +		} + +		if (!verifypeer(info, ssl)) +		{ +			info->accept_interrupted=0; +			t->shutdown=1; +			return (-1); +		} + +		if (info->connect_callback != NULL && +		    !(*info->connect_callback)(ssl, info->app_data)) +		{ +			info->accept_interrupted=0; +			t->shutdown=1; +			return (-1); +		} +	} + +	if (t->shutdown) +		return -1; + +	if (t->shutdown_interrupted && !t->read_interrupted && +	    !t->write_interrupted) +	{ +		n=SSL_shutdown(ssl); +		if (n > 0) +		{ +			t->shutdown_interrupted=0; +			t->shutdown=1; +			return -1; +		} + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_WANT_WRITE: +			FD_SET(fd, w); +			break; +		case SSL_ERROR_WANT_READ: +			FD_SET(fd, r); +			break; +		default: +			t->shutdown_interrupted=0; +			t->shutdown= -1; +			return -1; +		} +		return 1; +	} + +	if (!t->write_interrupted && t->readleft > 0) +	{ +		n=SSL_read(ssl, t->readptr, t->readleft); + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_NONE: +			break; +		case SSL_ERROR_WANT_WRITE: +			t->read_interrupted=1; +			FD_SET(fd, w); +			return (1); +		case SSL_ERROR_WANT_READ: +			FD_SET(fd, r); +			n=0; +			break; +		case SSL_ERROR_WANT_X509_LOOKUP: +			n=0; +			break; +		case SSL_ERROR_ZERO_RETURN: +			t->shutdown=1; +			return (-1); +		default: +			sslerror(info, "read", n); +			return (-1); +		} +		t->read_interrupted=0; +		t->readptr += n; +		t->readleft -= n; + +		if (n > 0) +			return (0); +	} + +	if (!t->read_interrupted && t->writeleft > 0) +	{ +		n=SSL_write(ssl, t->writeptr, t->writeleft); + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_NONE: +			break; +		case SSL_ERROR_WANT_WRITE: +			FD_SET(fd, w); +			n=0; +			break; +		case SSL_ERROR_WANT_READ: +			t->write_interrupted=1; +			FD_SET(fd, r); +			return (1); +		case SSL_ERROR_ZERO_RETURN: +			t->shutdown=1; +			return (-1); +		case SSL_ERROR_WANT_X509_LOOKUP: +			n=0; +			break; +		default: +			return (-1); +		} +		t->write_interrupted=0; +		t->writeptr += n; +		t->writeleft -= n; + +		if (n > 0) +			return (0); +	} + +	return (1); +} + +int tls_connecting(SSL *ssl) +{ +	struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl); + +	return info->accept_interrupted || info->connect_interrupted; +} + +int tls_certificate_verified(ssl_handle ssl) +{ +	struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl); + +	return info->certificate_verified; +} + +#define MAXDOMAINSIZE	256 + +static time_t asn1toTime(ASN1_TIME *asn1Time) +{ +	struct tm tm; +	int offset; + +	if (asn1Time == NULL || asn1Time->length < 13) +		return 0; + +	memset(&tm, 0, sizeof(tm)); + +#define N2(n)	((asn1Time->data[n]-'0')*10 + asn1Time->data[(n)+1]-'0') + +#define CPY(f,n) (tm.f=N2(n)) + +	CPY(tm_year,0); + +	if(tm.tm_year < 50) +		tm.tm_year += 100; /* Sux */ + +	CPY(tm_mon, 2); +	--tm.tm_mon; +	CPY(tm_mday, 4); +	CPY(tm_hour, 6); +	CPY(tm_min, 8); +	CPY(tm_sec, 10); + +	offset=0; + +	if (asn1Time->data[12] != 'Z') +	{ +		if (asn1Time->length < 17) +			return 0; + +		offset=N2(13)*3600+N2(15)*60; + +		if (asn1Time->data[12] == '-') +			offset= -offset; +	} + +#undef N2 +#undef CPY + +	return mktime(&tm)-offset; +} + + +static void dump_x509(X509 *x509, +		      void (*dump_func)(const char *, int cnt, void *), +		      void *dump_arg) +{ +	X509_NAME *subj=X509_get_subject_name(x509); +	int nentries, j; +	time_t timestamp; +	static const char gcc_shutup[]="%Y-%m-%d %H:%M:%S"; + +	if (!subj) +		return; + +	(*dump_func)("Subject:\n", -1, dump_arg); + +	nentries=X509_NAME_entry_count(subj); +	for (j=0; j<nentries; j++) +	{ +		const char *obj_name; +		X509_NAME_ENTRY *e; +		ASN1_OBJECT *o; +		ASN1_STRING *d; + +		int dlen; +		unsigned char *ddata; + +		e=X509_NAME_get_entry(subj, j); +		if (!e) +			continue; + +		o=X509_NAME_ENTRY_get_object(e); +		d=X509_NAME_ENTRY_get_data(e); + +		if (!o || !d) +			continue; + +		obj_name=OBJ_nid2sn(OBJ_obj2nid(o)); + +		dlen=ASN1_STRING_length(d); +		ddata=ASN1_STRING_data(d); +	 +		(*dump_func)("   ", -1, dump_arg); +		(*dump_func)(obj_name, -1, dump_arg); +		(*dump_func)("=", 1, dump_arg); +		(*dump_func)((const char *)ddata, dlen, dump_arg); +		(*dump_func)("\n", 1, dump_arg); +		 +	} +	(*dump_func)("\n", 1, dump_arg); + +	timestamp=asn1toTime(X509_get_notBefore(x509)); + +	if (timestamp) +	{ +		struct tm *tm=localtime(×tamp); +		char buffer[500]; + +		buffer[strftime(buffer, sizeof(buffer)-1, gcc_shutup, +				tm)]=0; + +		(*dump_func)("Not-Before: ", -1, dump_arg); +		(*dump_func)(buffer, -1, dump_arg); +		(*dump_func)("\n", 1, dump_arg); +	} + +	timestamp=asn1toTime(X509_get_notAfter(x509)); +	if (timestamp) +	{ +		struct tm *tm=localtime(×tamp); +		char buffer[500]; + +		buffer[strftime(buffer, sizeof(buffer)-1, gcc_shutup, +				tm)]=0; + +		(*dump_func)("Not-After: ", -1, dump_arg); +		(*dump_func)(buffer, -1, dump_arg); +		(*dump_func)("\n", 1, dump_arg); +	} +} + +void tls_dump_connection_info(ssl_handle ssl, +			      int server, +			      void (*dump_func)(const char *, int cnt, void *), +			      void *dump_arg) +{ +	const SSL_CIPHER *cipher; + +	{ +		STACK_OF(X509) *peer_cert_chain=SSL_get_peer_cert_chain(ssl); +		int i; + +		if (server) +		{ +			X509 *x=SSL_get_peer_certificate(ssl); + +			if (x) +			{ +				dump_x509(x, dump_func, dump_arg); +				X509_free(x); +			} +		} + +		for (i=0; peer_cert_chain && i<peer_cert_chain->stack.num; i++) +			dump_x509((X509 *)peer_cert_chain->stack.data[i], +				  dump_func, dump_arg); +	} + +	cipher=SSL_get_current_cipher(ssl); + +	if (cipher) +	{ +		const char *c; + +		c=SSL_CIPHER_get_version(cipher); +		if (c) +		{ +			(*dump_func)("Version: ", -1, dump_arg); +			(*dump_func)(c, -1, dump_arg); +			(*dump_func)("\n", 1, dump_arg); +		} + +		{ +			char buf[10]; + +			(*dump_func)("Bits: ", -1, dump_arg); + +			snprintf(buf, sizeof(buf), "%d", +				 SSL_CIPHER_get_bits(cipher, NULL)); +			buf[sizeof(buf)-1]=0; + +			(*dump_func)(buf, -1, dump_arg); +			(*dump_func)("\n", 1, dump_arg); +		} + +		c=SSL_CIPHER_get_name(cipher); + +		if (c) +		{ +			(*dump_func)("Cipher: ", -1, dump_arg); +			(*dump_func)(c, -1, dump_arg); +			(*dump_func)("\n", 1, dump_arg); +		} +	} +} + +char *tls_get_encryption_desc(ssl_handle ssl) +{ +	char protocolbuf[256]; +	const SSL_CIPHER *cipher; +	const char *c, *d; + +	cipher=SSL_get_current_cipher(ssl); + +	c=cipher ? SSL_CIPHER_get_version(cipher):NULL; +	d=cipher ? SSL_CIPHER_get_name(cipher):NULL; + +	snprintf(protocolbuf, sizeof(protocolbuf), +		 "%s,%dbits,%s", +		 c ? c:"unknown", +		 cipher ? SSL_CIPHER_get_bits(cipher, NULL):0, +		 d ? d:"unknown"); +	protocolbuf[sizeof(protocolbuf)-1]=0; +	return strdup(protocolbuf); +} + + +/* ------------------- */ + +int tls_validate_pem_cert(const char *buf, size_t buf_size) +{ +	int rc; +	BIO *certbio; +	int err; +	EVP_PKEY *pk; +	X509 *x; + +	ERR_clear_error(); + +	rc=0; +	certbio=BIO_new_mem_buf((void *)buf, buf_size); + +	if (!certbio) +		return (0); + +	x=PEM_read_bio_X509(certbio, NULL, NULL, NULL); + +	if (x) +	{ +		X509_free(x); + +		while ((x=PEM_read_bio_X509(certbio, NULL, NULL, NULL)) != NULL) +			X509_free(x); + +		err = ERR_peek_last_error(); +                if (ERR_GET_LIB(err) == ERR_LIB_PEM && +		    ERR_GET_REASON(err) == PEM_R_NO_START_LINE) +		{ +			rc=1; +		} +	} + +	ERR_clear_error(); +	BIO_free(certbio); + +	certbio=BIO_new_mem_buf((void *)buf, buf_size); + +	if (!certbio) +		return (0); + +	if (!(pk=PEM_read_bio_PrivateKey(certbio, NULL, NULL, NULL))) +	{ +		BIO_free(certbio); +		ERR_clear_error(); +		return 0; +	} + +	EVP_PKEY_free(pk); +	return rc; +} + +static size_t conv_name_to_rfc2553(const char *p, char *q) +{ +#define PUTC(c) if (q) *q++=(c); ++n + +	size_t n=0; +	const char *sep=""; + +	while (*p) +	{ +		if (*p == '/') +		{ +			++p; +			continue; +		} + +		while (*sep) +		{ +			PUTC(*sep); +			++sep; +		} +		sep=","; + +		while (*p && *p != '/') +		{ +			if (*p == '\\' && p[1]) +				++p; +			if (*p == '\\' || *p == ',') +			{ +				PUTC('\\'); +			} +			PUTC(*p); +			++p; +		} +	} +	PUTC(0); +#undef PUTC + +	return n; +} + +char *tls_cert_name(const char *buf, size_t buf_size) +{ +	BIO *certbio; +	char *p, *q; +	X509 *x; +	size_t cnt; + +	certbio=BIO_new_mem_buf((void *)buf, buf_size); + +	if (!certbio) +	{ +		ERR_clear_error(); +		return (0); +	} + +	x=PEM_read_bio_X509(certbio, NULL, NULL, NULL); +	p=0; +	q=0; + +	if (x) +	{ +		p=X509_NAME_oneline(x->cert_info->subject, NULL, 0); +		X509_free(x); +	} +	ERR_clear_error(); +	BIO_free(certbio); + +	if (p) +	{ +		cnt=conv_name_to_rfc2553(p, NULL); + +		q=malloc(cnt); + +		if (q) +			conv_name_to_rfc2553(p, q); +		free(p); +	} + +	return q; +} | 
