/*
** Copyright 2007-2009 Double Precision, Inc.
** See COPYING for distribution information.
*/
#include	"config.h"
#include	"argparse.h"
#include	"spipe.h"
#include	"libcouriertls.h"
#include	"tlscache.h"
#include	"soxwrap/soxwrap.h"
#include	<gnutls/gnutls.h>
#include	<gnutls/extra.h>
#include	<gnutls/x509.h>
#include	<gnutls/openpgp.h>
#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>

struct oid_name {
	const char *oid;
	const char *name;
};

static struct oid_name oid_name_list[]={
	{"2.5.4.0","objectClass"},
	{"2.5.4.2","knowledgeInformation"},
	{"2.5.4.3","cn"},
	{"2.5.4.4","sn"},
	{"2.5.4.5","serialNumber"},
	{"2.5.4.6","c"},
	{"2.5.4.7","l"},
	{"2.5.4.8","st"},
	{"2.5.4.9","street"},
	{"2.5.4.10","o"},
	{"2.5.4.11","ou"},
	{"2.5.4.12","title"},
	{"2.5.4.13","description"},
	{"2.5.4.14","searchGuide"},
	{"2.5.4.15","businessCategory"},
	{"2.5.4.16","postalAddress"},
	{"2.5.4.17","postalCode"},
	{"2.5.4.18","postOfficeBox"},
	{"2.5.4.19","physicalDeliveryOfficeName"},
	{"2.5.4.20","telephoneNumber"},
	{"2.5.4.21","telexNumber"},
	{"2.5.4.22","teletexTerminalIdentifier"},
	{"2.5.4.23","facsimileTelephoneNumber"},
	{"2.5.4.24","x121Address"},
	{"2.5.4.25","internationaliSDNNumber"},
	{"2.5.4.26","registeredAddress"},
	{"2.5.4.27","destinationIndicator"},
	{"2.5.4.28","preferredDeliveryMethod"},
	{"2.5.4.29","presentationAddress"},
	{"2.5.4.30","supportedApplicationContext"},
	{"2.5.4.31","member"},
	{"2.5.4.32","owner"},
	{"2.5.4.33","roleOccupant"},
	{"2.5.4.35","userPassword"},
	{"2.5.4.36","userCertificate"},
	{"2.5.4.37","cACertificate"},
	{"2.5.4.38","authorityRevocationList"},
	{"2.5.4.39","certificateRevocationList"},
	{"2.5.4.40","crossCertificatePair"},
	{"2.5.4.41","name"},
	{"2.5.4.42","givenName"},
	{"2.5.4.43","initials"},
	{"2.5.4.44","generationQualifier"},
	{"2.5.4.45","x500UniqueIdentifier"},
	{"2.5.4.46","dnQualifier"},
	{"2.5.4.47","enhancedSearchGuide"},
	{"2.5.4.48","protocolInformation"},
	{"2.5.4.49","distinguishedName"},
	{"2.5.4.50","uniqueMember"},
	{"2.5.4.51","houseIdentifier"},
	{"2.5.4.52","supportedAlgorithms"},
	{"2.5.4.53","deltaRevocationList"},
	{"2.5.4.54","dmdName"},
	{"2.5.4.65","pseudonym"},
	{"0.9.2342.19200300.100.1.3","mail"},
	{"0.9.2342.19200300.100.1.25","dc"},
	{"0.9.2342.19200300.100.1.1","uid"},
	{"1.3.6.1.1.3.1","uidObject"},
	{"1.2.840.113549.1.9.1","emailaddress"},
};

static const struct intmap {
	const char *name;
	int algo;
} all_ciphers[]={
	{ "AES256", GNUTLS_CIPHER_AES_256_CBC},
	{ "3DES", GNUTLS_CIPHER_3DES_CBC },
	{ "AES128", GNUTLS_CIPHER_AES_128_CBC},
	{ "ARC128", GNUTLS_CIPHER_ARCFOUR_128 },
	{ "ARC40", GNUTLS_CIPHER_ARCFOUR_40},
	{ "RC2", GNUTLS_CIPHER_RC2_40_CBC},
	{ "DES", GNUTLS_CIPHER_DES_CBC},
	{ "NULL", GNUTLS_CIPHER_NULL },
	{ NULL, 0},
},
	all_kxs[]={
		{ "DHERSA", GNUTLS_KX_DHE_RSA},
		{ "DHEDSS", GNUTLS_KX_DHE_DSS},
		{ "RSA", GNUTLS_KX_RSA},
		{ "SRP", GNUTLS_KX_SRP},
		{ "SRPRSA", GNUTLS_KX_SRP_RSA},
		{ "SRPDSS", GNUTLS_KX_SRP_DSS},
		{ "PSK", GNUTLS_KX_PSK},
		{ "DHEPSK", GNUTLS_KX_DHE_PSK},
		{ "ANONDH", GNUTLS_KX_ANON_DH},
		{ "RSAEXPORT", GNUTLS_KX_RSA_EXPORT},
		{ NULL, 0}
	}, all_comps[]={
		{ "DEFLATE", GNUTLS_COMP_DEFLATE},
		{ "LZO", GNUTLS_COMP_LZO},
		{ "NULL", GNUTLS_COMP_NULL},
		{ NULL, 0}
	}, all_certs[]={
		{ "X509", GNUTLS_CRT_X509},
		{ "OPENPGP", GNUTLS_CRT_OPENPGP},
		{ NULL, 0}
	};

struct ssl_context_t {
	int isserver;
	struct tls_info info_cpy;
	int *protocol_list;
	int cipher_list[sizeof(all_ciphers)/sizeof(all_ciphers[0])];
	int kx_list[sizeof(all_kxs)/sizeof(all_kxs[0])];
	int comp_list[sizeof(all_comps)/sizeof(all_comps[0])];
	int cert_list[sizeof(all_certs)/sizeof(all_certs[0])];

	char *certfile;
	int certfiledh;

	char *trustcerts;

	int verify_cert;
	int fail_if_no_cert;
};

struct ssl_handle_t {
	struct tls_info info_cpy;
	ssl_context ctx;
	gnutls_anon_client_credentials_t anonclientcred;
	gnutls_anon_server_credentials_t anonservercred;
	gnutls_certificate_credentials_t xcred;
	gnutls_dh_params_t dhparams;
	gnutls_session_t session;

	gnutls_x509_privkey x509_key;

	gnutls_openpgp_key_t pgp_crt;
	gnutls_openpgp_privkey_t pgp_key;
};

static void nonsslerror(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 const char *safe_getenv(ssl_context context, const char *n,
			       const char *def)
{
	const char *v=(*context->info_cpy.getconfigvar)
		(n, context->info_cpy.app_data);

	if (!v)	v="";

	if (!*v)
		v=def;
	return (v);
}

static char **splitwords(const char *var, size_t *nwords)
{
	char *buf=strdup(var);
	char **ptrbuf;

	char *p;
	size_t cnt;

	char *saveptr;

	if (!buf)
		return NULL;

	cnt=1;

	for (p=buf; (p=strtok_r(p, ":", &saveptr)) != NULL; p=NULL)
		++cnt;

	ptrbuf=malloc(cnt*sizeof(char *));

	if (!ptrbuf)
	{
		free(buf);
		return NULL;
	}

	cnt=0;
	strcpy(buf, var);

	for (p=buf; (p=strtok_r(p, ":", &saveptr)) != NULL; p=NULL)
	{
		ptrbuf[cnt++]=p;

		while (*p)
		{
			if (*p == '-')
				*p=' ';
			++p;
		}
	}

	ptrbuf[cnt]=0;
	if (cnt == 0)
		free(buf);
	*nwords=cnt;

	return ptrbuf;
}

static void splitwords_free(char **words)
{
	if (words[0])
		free(words[0]);
	free(words);
}

static void addremove(int *cipher_list_out,
		      int removeit,
		      int n)
{
	if (removeit)
	{
		int *p=cipher_list_out;

		while (*cipher_list_out)
		{
			if (*cipher_list_out != n)
				*p++ = *cipher_list_out;
			++cipher_list_out;
		}
		*p=0;
		return;
	}

	while (*cipher_list_out)
	{
		if (*cipher_list_out == n)
			return;
		++cipher_list_out;
	}

	*cipher_list_out++ =n;
	*cipher_list_out=0;
}

static void parse_map(const struct intmap *mapptr,
		      char **tokens,
		      int *tokens_out,
		      int is_ciphers)
{
	size_t i;

	*tokens_out=0;

	for (i=0; tokens[i]; i++)
	{
		size_t j;
		int removeit=0;
		const char *name=tokens[i];

		if (*name == '-')
		{
			++name;
			removeit=1;
		}

		for (j=0; mapptr[j].name; j++)
		{
			if (strcmp(name, mapptr[j].name) == 0)
			{
				addremove(tokens_out, removeit,
					  mapptr[j].algo);
				continue;
			}

			if (is_ciphers && strcmp(name, "HIGH") == 0 &&
			    gnutls_cipher_get_key_size(mapptr[j].algo)
				    > 128 / 8)
			{
				addremove(tokens_out, removeit,
					  mapptr[j].algo);
				continue;
			}

			if (is_ciphers && strcmp(name, "MEDIUM") == 0 &&
			    gnutls_cipher_get_key_size(mapptr[j].algo)
				    == 128 / 8)
			{
				addremove(tokens_out, removeit,
					  mapptr[j].algo);
				continue;
			}

			if (is_ciphers && strcmp(name, "LOW") == 0 &&
			    gnutls_cipher_get_key_size(mapptr[j].algo)
				    < 128 / 8 &&
			    gnutls_cipher_get_key_size(mapptr[j].algo)
			    > 0)
			{
				addremove(tokens_out, removeit,
					  mapptr[j].algo);
				continue;
			}

			if (strcmp(name, "ALL") == 0 &&
			    (!is_ciphers ||
			     gnutls_cipher_get_key_size(mapptr[j].algo)
			     > 0))
			{
				addremove(tokens_out, removeit,
					  mapptr[j].algo);
				continue;
			}
		}
	}
}

ssl_context tls_create(int isserver, const struct tls_info *info)
{
	static int first=1;

	char **words;
	size_t n, i;
	ssl_context p=malloc(sizeof(struct ssl_context_t));
	char *certfile=NULL, *dhcertfile=NULL;

	if (!p)
		return NULL;

	memset(p, 0, sizeof(*p));

	p->isserver=isserver;
	p->info_cpy=*info;
	p->info_cpy.certificate_verified=0;

	if (first)
	{
		if (gnutls_check_version(LIBGNUTLS_VERSION) == NULL)
		{
			fprintf(stderr, "GnuTLS version mismatch\n");
			free(p);
			errno=EINVAL;
			return (NULL);
		}

		first=0;

		if (gnutls_global_init() < 0)
		{
			fprintf(stderr, "gnutls_global_init() failed\n");
			free(p);
			errno=EINVAL;
			return (NULL);
		}

		if (gnutls_global_init_extra() < 0)
		{
			gnutls_global_deinit();
			fprintf(stderr, "gnutls_global_init() failed\n");
			free(p);
			errno=EINVAL;
			return (NULL);
		}
	}

	if (!(words=splitwords(safe_getenv(p, "TLS_PROTOCOL",
					   "TLS1_1:TLS1:SSL3"), &n)))
	{
		tls_destroy(p);
		return NULL;
	}

	if ((p->protocol_list=malloc((n+1)*sizeof(int))) == NULL)
	{
		splitwords_free(words);

		tls_destroy(p);
		return NULL;
	}

	for (n=i=0; words[i]; ++i)
	{
		if (strcmp(words[i], "SSL3") == 0)
		{
			p->protocol_list[n++]=GNUTLS_SSL3;
		}
		else if (strcmp(words[i], "TLS1") == 0)
		{
			p->protocol_list[n++]=GNUTLS_TLS1_0;
		}
		else if (strcmp(words[i], "TLS1_1") == 0)
		{
			p->protocol_list[n++]=GNUTLS_TLS1_1;
		}
	}
	p->protocol_list[n]=0;

	splitwords_free(words);

	if (!(words=splitwords(safe_getenv(p, "TLS_CIPHER_LIST",
					   "HIGH:MEDIUM"), &n)))
	{
		tls_destroy(p);
		return NULL;
	}

	parse_map(all_ciphers, words, p->cipher_list, 1);
	splitwords_free(words);

	if (!(words=splitwords(safe_getenv(p, "TLS_KX_LIST", "ALL"), &n)))
	{
		tls_destroy(p);
		return NULL;
	}

	parse_map(all_kxs, words, p->kx_list, 0);
	splitwords_free(words);

	if (!(words=splitwords(safe_getenv(p, "TLS_COMPRESSION", "ALL"), &n)))
	{
		tls_destroy(p);
		return NULL;
	}

	parse_map(all_comps, words, p->comp_list, 0);
	splitwords_free(words);

	if (!(words=splitwords(safe_getenv(p, "TLS_CERTS", "X509"), &n)))
	{
		tls_destroy(p);
		return NULL;
	}

	parse_map(all_certs, words, p->cert_list, 0);
	splitwords_free(words);


	if ((certfile=strdup(safe_getenv(p, "TLS_CERTFILE", ""))) == NULL ||
	    (dhcertfile=strdup(safe_getenv(p, "TLS_DHCERTFILE", "")))
	    == NULL ||
	    (p->trustcerts=strdup(safe_getenv(p, "TLS_TRUSTCERTS", "")))
	    == NULL)
	{
		if (certfile)
			free(certfile);
		if (dhcertfile)
			free(dhcertfile);
		tls_destroy(p);
		return NULL;
	}

	if (*dhcertfile)
	{
		p->certfile=dhcertfile;
		p->certfiledh=1;
		dhcertfile=NULL;
	}
	else if (*certfile)
	{
		p->certfile=certfile;
		p->certfiledh=0;
		certfile=NULL;
	}

	if (certfile)
		free(certfile);
	if (dhcertfile)
		free(dhcertfile);

	switch (*safe_getenv(p, "TLS_VERIFYPEER", "P")) {
	case 'n':
	case 'N':
		p->verify_cert=0;
		p->fail_if_no_cert=0;
		break;
	case 'p':
	case 'P':		/* PEER */
		p->verify_cert=1;
		p->fail_if_no_cert=0;
		break;
	case 'r':
	case 'R':		/* REQUIREPEER */
		p->verify_cert=1;
		p->fail_if_no_cert=1;
		break;
	}

	if (info->peer_verify_domain)
		p->verify_cert=p->fail_if_no_cert=1;

	{
		const char *filename=safe_getenv(p, "TLS_CACHEFILE", "");
		const char *cachesize=safe_getenv(p, "TLS_CACHESIZE", "");
		off_t cachesize_l;

		if (filename && *filename)
		{
			cachesize_l= cachesize ? (off_t)atol(cachesize):0;

			if (cachesize_l <= 0)
				cachesize_l=512L * 1024;
			if ((p->info_cpy.tlscache=tls_cache_open(filename,
								 cachesize_l))
			    == NULL)
			{
				nonsslerror(&p->info_cpy, filename);
				tls_destroy(p);
				return NULL;
			}
		}
	}

#if 0
	int session_timeout=atoi(safe_getenv(ctx, "TLS_TIMEOUT"));
#endif
	return p;
}

void tls_destroy(ssl_context p)
{
	if (p->protocol_list)
		free(p->protocol_list);

	if (p->certfile)
		free(p->certfile);

	if (p->trustcerts)
		free(p->trustcerts);

	if (p->info_cpy.tlscache)
		tls_cache_close(p->info_cpy.tlscache);
	free(p);
}

int tls_certificate_verified(ssl_handle ssl)
{
	return ssl->info_cpy.certificate_verified;
}

static int read_cert_dir(const char *cert_dir,
			 int (*cb_func)(const char *filename,
					struct stat *stat_buf,
					void *arg),
			 void *arg)
{
	DIR *dirp;
	struct dirent *de;
	int rc=0;

	if ((dirp=opendir(cert_dir)) == NULL)
		return 0;

	while ((de=readdir(dirp)) != NULL)
	{
		char *buf;
		struct stat stat_buf;

		if (de->d_name[0] == '.')
			continue;

		buf=malloc(strlen(cert_dir)+strlen(de->d_name)+2);

		if (!buf)
			continue;

		strcat(strcat(strcpy(buf, cert_dir), "/"), de->d_name);

		if (lstat(buf, &stat_buf) < 0 || !S_ISREG(stat_buf.st_mode))
		{
			free(buf);
			continue;
		}

		rc=(*cb_func)(buf, &stat_buf, arg);
		free(buf);
		if (rc)
			break;
	}
	closedir(dirp);
	return rc;
}

static int cnt_cert_size(const char *filename,
			 struct stat *stat_buf,
			 void *arg)
{
	*(size_t *)arg += stat_buf->st_size;
	return 0;
}

struct cert_buf_ptr {
	char *ptr;
	size_t cnt;
};

static int save_cert_to_buf(const char *filename,
			    struct stat *stat_buf,
			    void *arg)
{
	struct cert_buf_ptr *p=(struct cert_buf_ptr *)arg;
	FILE *fp;

	if (p->cnt < stat_buf->st_size)
		return 1;

	fp=fopen(filename, "r");

	if (fp)
	{
		if (stat_buf->st_size &&
		    fread(p->ptr, stat_buf->st_size, 1, fp) != 1)
		{
			fclose(fp);
			return 1;
		}
		fclose(fp);
	}
	p->ptr += stat_buf->st_size;
	p->cnt -= stat_buf->st_size;
	return 0;
}


static int add_certificates(gnutls_certificate_credentials_t xcred,
			    const char *certfile)
{
	struct stat stat_buf;
	struct cert_buf_ptr ptr;
	gnutls_datum_t datum_ptr;

	if (!certfile || !*certfile || stat(certfile, &stat_buf) < 0)
		return 0;

	if (S_ISREG(stat_buf.st_mode))
	{
		return gnutls_certificate_set_x509_trust_file(xcred, certfile,
							      GNUTLS_X509_FMT_PEM);
	}

	if (!S_ISDIR(stat_buf.st_mode))
		return 0;

	ptr.cnt=0;

	if (read_cert_dir(certfile, cnt_cert_size, &ptr.cnt))
		return 0;

	datum_ptr.data=malloc(ptr.cnt+1);
	datum_ptr.size=ptr.cnt;

	if (!datum_ptr.data)
		return 0;

	ptr.ptr=(char *)datum_ptr.data;

	if (read_cert_dir(certfile, save_cert_to_buf, &ptr) ||
	    ptr.cnt)
	{
		free(datum_ptr.data);
		return 0;
	}
	*ptr.ptr=0;

	gnutls_certificate_set_x509_trust_mem(xcred, &datum_ptr,
					      GNUTLS_X509_FMT_PEM);
	free(datum_ptr.data);

	return 0;
}

static void tls_free_session_keys(ssl_handle ssl)
{
	if (ssl->x509_key)
		gnutls_x509_privkey_deinit(ssl->x509_key);

	if (ssl->pgp_crt)
		gnutls_openpgp_key_deinit(ssl->pgp_crt);

	if (ssl->pgp_key)
		gnutls_openpgp_privkey_deinit(ssl->pgp_key);

	ssl->x509_key=NULL;
	ssl->pgp_crt=NULL;
	ssl->pgp_key=NULL;

}

static void tls_free_session(ssl_handle ssl)
{
	gnutls_deinit(ssl->session);
	gnutls_certificate_free_credentials(ssl->xcred);
	gnutls_anon_free_client_credentials(ssl->anonclientcred);
	gnutls_anon_free_server_credentials(ssl->anonservercred);
	gnutls_dh_params_deinit(ssl->dhparams);
	tls_free_session_keys(ssl);
	free(ssl);
}

static int chk_error(int rc, ssl_handle ssl, int fd, fd_set *r, fd_set *w,
		     int *result_rc)
{
	if (rc == GNUTLS_E_SUCCESS)
	{
		if (result_rc)
			*result_rc=0;
		return 0;
	}

	if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED)
		return 1;

	if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
	{
		const char *alert=
			gnutls_alert_get_name(gnutls_alert_get(ssl->session));
		(*ssl->info_cpy.tls_err_msg)(alert, ssl->info_cpy.app_data);

		if (result_rc)
			*result_rc= -1;
		return 0;
	}

	if (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED)
	{
		fd_set *p=gnutls_record_get_direction(ssl->session)
			? w:r;

		if (p)
			FD_SET(fd, p);

		if (result_rc)
			*result_rc=1;
		return 0;
	}

	if (result_rc)
	{
		(*ssl->info_cpy.tls_err_msg)(gnutls_strerror(rc),
					     ssl->info_cpy.app_data);
		*result_rc= -1;
	}
	return 0;
}

static int verify_client(ssl_handle ssl, int fd)
{
	unsigned int status;
	int rc;
	const gnutls_datum_t *cert_list;
	unsigned int cert_list_size;

	if (!ssl->ctx->verify_cert)
		return 0;

	cert_list = gnutls_certificate_get_peers(ssl->session, &cert_list_size);
	if (cert_list == NULL || cert_list_size == 0)
	{
		if (ssl->ctx->fail_if_no_cert)
		{
			(*ssl->info_cpy.tls_err_msg)
				("No certificate supplied by peer",
				 ssl->info_cpy.app_data);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}
		return 0;
	}

	status=0;
	rc=gnutls_certificate_verify_peers2(ssl->session, &status);

	if (rc)
	{
		(*ssl->info_cpy.tls_err_msg)
			("Peer certificate verification failed",
			 ssl->info_cpy.app_data);
		return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
	}

	if (status)
	{
		(*ssl->info_cpy.tls_err_msg)
			(status & GNUTLS_CERT_REVOKED ?
			 "Peer's certificate is revoked":
			 status & GNUTLS_CERT_SIGNER_NOT_FOUND ?
			 "Peer's certificate not signed by a trusted authority":
			 status & GNUTLS_CERT_SIGNER_NOT_CA ?
			 "Invalid peer certificate authority":
			 status & GNUTLS_CERT_INSECURE_ALGORITHM ?
			 "Peer's certificate does not use a secure checksum":
			 "Invalid peer certificate",
			 ssl->info_cpy.app_data);
		return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
	}

	if (gnutls_certificate_type_get(ssl->session) == GNUTLS_CRT_X509)
	{
		gnutls_x509_crt_t cert;

		if (gnutls_x509_crt_init(&cert) < 0)
		{
			(*ssl->info_cpy.tls_err_msg)
				("Error initializing certificate",
				 ssl->info_cpy.app_data);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}

		if (gnutls_x509_crt_import(cert, &cert_list[0],
					   GNUTLS_X509_FMT_DER) < 0)
		{
			(*ssl->info_cpy.tls_err_msg)
				("Error parsing certificate",
				 ssl->info_cpy.app_data);
			gnutls_x509_crt_deinit (cert);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}


		if (gnutls_x509_crt_get_expiration_time(cert) < time(NULL))
		{
			(*ssl->info_cpy.tls_err_msg)
				("Expired certificate",
				 ssl->info_cpy.app_data);
			gnutls_x509_crt_deinit (cert);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}

		if (gnutls_x509_crt_get_activation_time(cert) > time(NULL))
		{
			(*ssl->info_cpy.tls_err_msg)
				("Certificate not activated",
				 ssl->info_cpy.app_data);
			gnutls_x509_crt_deinit (cert);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}

		if (ssl->info_cpy.peer_verify_domain &&
		    *ssl->info_cpy.peer_verify_domain &&
		    !gnutls_x509_crt_check_hostname(cert,
						    ssl->info_cpy
						    .peer_verify_domain
						    ))
		{
			char hostname[256];
			size_t hostname_size=sizeof(hostname);
			const char *errmsg_txt="Certificate owner mismatch: ";
			char *errmsg_buf;

			if (gnutls_x509_crt_get_dn_by_oid(cert,
							  "2.5.4.3", 0,
							  0, hostname,
							  &hostname_size) < 0)
				strcpy(hostname,"(unknown)");

			errmsg_buf=malloc(strlen(errmsg_txt)+
					  strlen(hostname)+10);

			if (errmsg_buf)
				strcat(strcpy(errmsg_buf, errmsg_txt),
				       hostname);

			(*ssl->info_cpy.tls_err_msg)
				(errmsg_buf ? errmsg_buf: strerror(errno),
				 ssl->info_cpy.app_data);
			gnutls_x509_crt_deinit (cert);

			if (errmsg_buf)
				free(errmsg_buf);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}

		gnutls_x509_crt_deinit (cert);
	}
	else if (gnutls_certificate_type_get(ssl->session)==GNUTLS_CRT_OPENPGP)
	{
		gnutls_openpgp_key_t cert;

		if (gnutls_openpgp_key_init(&cert) < 0)
		{
			(*ssl->info_cpy.tls_err_msg)
				("Error initializing certificate",
				 ssl->info_cpy.app_data);
			gnutls_openpgp_key_deinit(cert);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}

		if (gnutls_openpgp_key_import(cert, &cert_list[0],
					      GNUTLS_OPENPGP_FMT_RAW) < 0)
		{
			(*ssl->info_cpy.tls_err_msg)
				("Error parsing certificate",
				 ssl->info_cpy.app_data);
			gnutls_openpgp_key_deinit (cert);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}

		if (gnutls_openpgp_key_get_creation_time(cert) > time(NULL))
		{
			(*ssl->info_cpy.tls_err_msg)
				("Certificate not activated",
				 ssl->info_cpy.app_data);
			gnutls_openpgp_key_deinit (cert);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}

		if (gnutls_openpgp_key_get_expiration_time(cert) < time(NULL))
		{
			(*ssl->info_cpy.tls_err_msg)
				("Expired certificate",
				 ssl->info_cpy.app_data);
			gnutls_openpgp_key_deinit (cert);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}

		if (ssl->info_cpy.peer_verify_domain &&
		    *ssl->info_cpy.peer_verify_domain &&
		    !gnutls_openpgp_key_check_hostname(cert,
						       ssl->info_cpy
						       .peer_verify_domain))
						      
		{
			char *hostname;
			size_t hostnamesiz=0;
			const char *errmsg_txt=
				"Certificate owner mismatch: ";
			char *errmsg_buf;

			gnutls_openpgp_key_get_name(cert, 0, NULL,
						    &hostnamesiz);

			hostname=malloc(hostnamesiz);

			if (hostname)
			{
				*hostname=0;
				gnutls_openpgp_key_get_name(cert,
							    0, hostname,
							    &hostnamesiz);
			}

			errmsg_buf=malloc(strlen(errmsg_txt)+
					  strlen(hostname ?
						 hostname:"")+100);

			if (errmsg_buf)
				strcat(strcpy(errmsg_buf, errmsg_txt),
					       hostname ?
					       hostname:"(unknown)");

			(*ssl->info_cpy.tls_err_msg)
				(errmsg_buf ? errmsg_buf:strerror(errno),
				 ssl->info_cpy.app_data);
			if (errmsg_buf)
				free(errmsg_buf);
			if (hostname)
				free(hostname);
			gnutls_openpgp_key_deinit (cert);
			return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
		}
		gnutls_openpgp_key_deinit (cert);
	}
	else
	{
		(*ssl->info_cpy.tls_err_msg)
			("No certificate supplied by peer",
			 ssl->info_cpy.app_data);
		return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
	}

	ssl->info_cpy.certificate_verified=1;
	return 0;
}

static int dohandshake(ssl_handle ssl, int fd, fd_set *r, fd_set *w)
{
	int rc;

	while (chk_error(gnutls_handshake(ssl->session),
			 ssl, fd, r, w, &rc))
		;

	if (rc == 0)
	{
		ssl->info_cpy.connect_interrupted=0;

		
		if (verify_client(ssl, fd))
			return -1;

		if (ssl->info_cpy.connect_callback != NULL &&
		    !(*ssl->info_cpy.connect_callback)(ssl,
						       ssl->info_cpy.app_data))
			return (-1);
	}
	return rc;
}

static char *check_cert(const char *filename,
			gnutls_certificate_type_t cert_type,
			const char *req_dn,
			int isvirtual)
{
	if (!filename || !*filename)
		return NULL;

	while (*req_dn)
	{
		char *p=malloc(strlen(filename)+strlen(req_dn)+10);

		if (!p)
			return NULL;

		strcat(strcat(strcpy(p, filename), "."), req_dn);

		if (cert_type == GNUTLS_CRT_OPENPGP)
			strcat(p, ".pgp");

		if (access(p, R_OK) == 0)
			return p;

		free(p);

		if (!isvirtual)
			break;

		if ((req_dn=strchr(req_dn, '.')) == NULL)
			break;
		++req_dn;
	}

	{
		char *p=malloc(strlen(filename)+10);

		if (!p)
			return NULL;

		strcpy(p, filename);

		if (cert_type == GNUTLS_CRT_OPENPGP)
			strcat(p, ".pgp");

		if (access(p, R_OK) == 0)
			return p;

		free(p);
	}
	return NULL;
}

static int read_file(const char *file,
		     gnutls_datum *filebuf)
{
	FILE *fp;
	struct stat stat_buf;

	filebuf->data=NULL;

	if ((fp=fopen(file, "r")) == NULL ||
	    fstat(fileno(fp), &stat_buf) < 0)
	{
		if (fp)
			fclose(fp);
		return GNUTLS_E_FILE_ERROR;
	}

	if ((filebuf->data=malloc(stat_buf.st_size)) == NULL)
	{
		fclose(fp);
		return GNUTLS_E_MEMORY_ERROR;
	}

	if (fread(filebuf->data, filebuf->size=stat_buf.st_size, 1, fp) != 1)
	{
		if (fp)
			fclose(fp);
		return GNUTLS_E_FILE_ERROR;
	}
	return 0;
}

static void release_file(gnutls_datum *filebuf)
{
	if (filebuf->data)
		free(filebuf->data);
	filebuf->data=NULL;
}

static int set_cert(ssl_handle ssl,
		    gnutls_session_t session,
		    gnutls_retr_st *st,
		    const char *certfilename)
{
	int rc;
	gnutls_datum filebuf;
	unsigned int cert_cnt;

	st->ncerts=0;
	st->deinit_all=0;
	tls_free_session_keys(ssl);

	if ((rc=read_file(certfilename, &filebuf)) < 0)
		return rc;

	switch (st->type) {
	case GNUTLS_CRT_X509:

		cert_cnt=0;

		if ((rc=gnutls_x509_privkey_init(&ssl->x509_key)) < 0 ||
		    (rc=gnutls_x509_privkey_import(ssl->x509_key, &filebuf,
						   GNUTLS_X509_FMT_PEM)) < 0)
			break;

		rc=gnutls_x509_crt_list_import(NULL, &cert_cnt,
					       &filebuf,
					       GNUTLS_X509_FMT_PEM,
					       GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);

		if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
			break;

		st->ncerts=cert_cnt+1;
		st->cert.x509=gnutls_malloc(st->ncerts*sizeof(*st->cert.x509));

		rc=gnutls_x509_crt_list_import(st->cert.x509, &st->ncerts,
					       &filebuf,
					       GNUTLS_X509_FMT_PEM, 0);

		if (rc < 0)
		{
			st->ncerts=0;
			gnutls_free(st->cert.x509);
			st->cert.x509=0;
			break;
		}
		st->ncerts=rc;
		st->key.x509=ssl->x509_key;
		ssl->x509_key=0;
		st->deinit_all=1;

		break;
	case GNUTLS_CRT_OPENPGP:
		if ((rc=gnutls_openpgp_key_init(&ssl->pgp_crt)) < 0 ||
		    (rc=gnutls_openpgp_privkey_init(&ssl->pgp_key)) < 0 ||
		    (rc=gnutls_openpgp_key_import(ssl->pgp_crt, &filebuf,
						  GNUTLS_OPENPGP_FMT_BASE64))
		    < 0 ||
		    (rc=gnutls_openpgp_privkey_import(ssl->pgp_key, &filebuf,
						      GNUTLS_OPENPGP_FMT_BASE64,
						      NULL, 0)) < 0)
			break;
		st->cert.pgp=ssl->pgp_crt;
		st->ncerts=1;
		st->key.pgp=ssl->pgp_key;
		break;
	default:
		break;
	}

	release_file(&filebuf);
	return 0;
}

static int get_server_cert(gnutls_session_t session,
			   gnutls_retr_st *st)
{
	ssl_handle ssl=(ssl_handle)gnutls_session_get_ptr(session);
	int vhost_idx;
	char *vhost_buf;
	size_t vhost_max_size=0;
	size_t vhost_size;
	unsigned int type=GNUTLS_NAME_DNS;
	char *certfilename=NULL;
	int rc;

	st->type=gnutls_certificate_type_get(session);

	for (vhost_idx=0; vhost_size=0,
		     gnutls_server_name_get(session, NULL, &vhost_size, &type,
					    vhost_idx) ==
		     GNUTLS_E_SHORT_MEMORY_BUFFER; ++vhost_idx)
	{
		if (++vhost_size > vhost_max_size)
			vhost_max_size=vhost_size;
	}

	vhost_buf=malloc(vhost_size);

	if (!vhost_buf)
		return GNUTLS_E_MEMORY_ERROR;

	for (vhost_idx=0; vhost_size=vhost_max_size,
		     gnutls_server_name_get(session, vhost_buf, &vhost_size,
					    &type,
					    vhost_idx) == GNUTLS_E_SUCCESS;
	     ++vhost_idx)
	{
		if (ssl->ctx->certfile)
			certfilename=check_cert(ssl->ctx->certfile,
						st->type,
						vhost_buf, 1);

		if (certfilename)
			break;
	}

	if (!certfilename)
	{
		if (ssl->ctx->certfile)
			certfilename=check_cert(ssl->ctx->certfile,
						st->type,
						safe_getenv(ssl->ctx,
							    "TCPLOCALIP", ""),
						0);
	}

	if (!certfilename)
		return 0;

	rc=set_cert(ssl, session, st, certfilename);
	free(certfilename);
	return rc;
}


static int pick_client_cert(gnutls_session_t session,
			    const gnutls_datum_t * req_ca_rdn, int nreqs,
			    const gnutls_pk_algorithm_t * sign_algos,
			    int sign_algos_length, gnutls_retr_st *st)
{
	ssl_handle ssl=(ssl_handle)gnutls_session_get_ptr(session);
	int i, j;
	const char *cert_array;
	size_t cert_array_size;
	int rc=0;

	if (ssl->info_cpy.getpemclientcert4ca == NULL)
		return 0;

	if (st->type != GNUTLS_CRT_X509)
		return 0;

	if (ssl->info_cpy.loadpemclientcert4ca)
		(*ssl->info_cpy.loadpemclientcert4ca)(ssl->info_cpy.app_data);

	for (j=0; (*ssl->info_cpy.getpemclientcert4ca)(j, &cert_array,
						       &cert_array_size,
						       ssl->info_cpy.app_data);
	     ++j)
	{
		gnutls_datum_t data;
		unsigned int cert_cnt=0;
		gnutls_x509_crt_t *certbuf;
		size_t issuer_buf_size=0;
		char *issuer_rdn;
		gnutls_x509_privkey pk;

		data.data=(unsigned char *)cert_array;
		data.size=cert_array_size;
		gnutls_x509_privkey_init(&pk);
		if (gnutls_x509_privkey_import(pk, &data,
					       GNUTLS_X509_FMT_PEM)
		    != GNUTLS_E_SUCCESS)
		{
			gnutls_x509_privkey_deinit(pk);
			continue;
		}

		data.data=(void *)cert_array;
		data.size=cert_array_size;;

		gnutls_x509_crt_list_import(NULL, &cert_cnt, &data,
					    GNUTLS_X509_FMT_PEM,
					    GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
		if (cert_cnt == 0)
		{
			gnutls_x509_privkey_deinit(pk);
			continue;
		}

		certbuf=gnutls_malloc(sizeof(*certbuf)*cert_cnt);

		if (!certbuf)
		{
			gnutls_x509_privkey_deinit(pk);
			continue;
		}

		if (gnutls_x509_crt_list_import(certbuf, &cert_cnt, &data,
						GNUTLS_X509_FMT_PEM,
						GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED) < 0)
		{
			free(certbuf);
			gnutls_x509_privkey_deinit(pk);
			continue;
		}


		gnutls_x509_crt_get_issuer_dn(certbuf[0], NULL,
					      &issuer_buf_size);

		++issuer_buf_size;

		issuer_rdn=gnutls_malloc(issuer_buf_size+1);

		if (gnutls_x509_crt_get_issuer_dn(certbuf[0], issuer_rdn,
						  &issuer_buf_size)
		    != GNUTLS_E_SUCCESS)
		{
			gnutls_free(issuer_rdn);
			issuer_rdn=0;
		}
		else
			issuer_rdn[issuer_buf_size]=0;

		for (i=0; issuer_rdn && i<nreqs; i++)
		{
			size_t buf_size=0;
			char *ca_rdn;

			gnutls_x509_rdn_get(&req_ca_rdn[i], NULL, &buf_size);

			++buf_size;

			ca_rdn=gnutls_malloc(buf_size+1);

			if (gnutls_x509_rdn_get(&req_ca_rdn[i], ca_rdn, &buf_size) !=
			    GNUTLS_E_SUCCESS)
			{
				gnutls_free(ca_rdn);
				continue;
			}

			ca_rdn[buf_size]=0;

			if (strcmp(ca_rdn, issuer_rdn) == 0)
				break;
			gnutls_free(ca_rdn);
		}

		st->ncerts=0;
		if (issuer_rdn && i < nreqs)
		{
			st->cert.x509=certbuf;
			st->ncerts=cert_cnt;
			st->deinit_all=1;
			st->key.x509=pk;
			cert_cnt=0;
			rc=1;
		}
		else
		{
			gnutls_x509_privkey_deinit(pk);
			while (cert_cnt)
				gnutls_x509_crt_deinit(certbuf[--cert_cnt]);
			gnutls_free(certbuf);
		}
		gnutls_free(issuer_rdn);
		if (rc)
			break;
	}

	return rc;
}

static int get_client_cert(gnutls_session_t session,
			   const gnutls_datum_t * req_ca_rdn, int nreqs,
			   const gnutls_pk_algorithm_t * sign_algos,
			   int sign_algos_length, gnutls_retr_st *st)

{
	ssl_handle ssl=(ssl_handle)gnutls_session_get_ptr(session);
	int rc;
	char *certfilename=NULL;

	rc= 0;
	st->type=gnutls_certificate_type_get(session);

	if (ssl->ctx->certfile)
		certfilename=check_cert(ssl->ctx->certfile,
					st->type, "", 0);

	st->ncerts=0;
	st->deinit_all=0;

	if (certfilename)
	{
		rc=set_cert(ssl, session, st, certfilename);
		free(certfilename);
	}
	else
	{
		rc=pick_client_cert(session, req_ca_rdn, nreqs, sign_algos,
				    sign_algos_length, st);
		if (rc > 0)
			rc=0;
	}
	return rc;
}

static int read_dh_params(gnutls_dh_params_t dhparams,
			  const char *filename)
{
	int rc;

	gnutls_datum_t filebuf;

	rc=read_file(filename, &filebuf);

	if (rc == 0)
	{
		rc=gnutls_dh_params_import_pkcs3(dhparams, &filebuf,
						 GNUTLS_X509_FMT_PEM);
		release_file(&filebuf);
	}
	return rc;
}

static int db_store_func(void *dummy, gnutls_datum_t key,
			 gnutls_datum_t data)
{
	char *p=malloc(key.size + data.size + sizeof(int));

	if (!p)
		return -1;

	memcpy(p, &key.size, sizeof(key.size));
	memcpy(p+sizeof(key.size), key.data, key.size);
	memcpy(p+sizeof(key.size)+key.size, data.data, data.size);

	tls_cache_add(((ssl_handle)dummy)->info_cpy.tlscache, p,
		      key.size + data.size + sizeof(int));
	free(p);
	return 0;
}

static int do_cache_remove(void *rec, size_t recsize, int *doupdate, void *arg)
{
	char *recptr=rec;
	gnutls_datum_t *key=(gnutls_datum_t *)arg;

	if (recsize >= key->size + sizeof(key->size))
	{
		gnutls_datum_t dummy;

		memcpy(&dummy.size, recptr, sizeof(dummy.size));

		if (dummy.size == key->size &&
		    memcmp(recptr + sizeof(key->size),
			   key->data, key->size) == 0)
		{
			dummy.size= -1;
			memcpy(recptr, &dummy.size, sizeof(dummy.size));
			*doupdate=1;
			return 1;
		}
	}
	return 0;
}
	
static int db_remove_func(void *dummy, gnutls_datum_t key)
{
	tls_cache_walk(((ssl_handle)dummy)->info_cpy.tlscache,
		       do_cache_remove, &key);
	return 0;
}

struct db_retrieve_s {
	gnutls_datum_t ret;
	gnutls_datum_t *key;
};

static int do_cache_retrieve(void *rec, size_t recsize, int *doupdate,
			     void *arg)
{
	char *recptr=rec;
	struct db_retrieve_s *ret=(struct db_retrieve_s *)arg;

	if (recsize >= ret->key->size + sizeof(ret->key->size))
	{
		gnutls_datum_t dummy;

		memcpy(&dummy.size, recptr, sizeof(dummy.size));

		if (dummy.size == ret->key->size &&
		    memcmp(recptr+sizeof(dummy.size),
			   ret->key->data,
			   ret->key->size) == 0)
		{
			ret->ret.size=recsize-sizeof(dummy.size)-ret->key->size;

			ret->ret.data=gnutls_malloc(ret->ret.size);

			if (ret->ret.data)
				memcpy(ret->ret.data,
				       (void *)(recptr+sizeof(dummy.size)
						+ret->key->size),
				       ret->ret.size);
			else
				ret->ret.size=0;

			return 1;
		}
	}
	return 0;
}

static gnutls_datum_t db_retrieve_func(void *dummy, gnutls_datum_t key)
{
	struct db_retrieve_s drs;

	drs.ret.data=NULL;
	drs.ret.size=0;
	drs.key= &key;

	tls_cache_walk(((ssl_handle)dummy)->info_cpy.tlscache,
		       do_cache_retrieve, &drs);
	return drs.ret;
}

ssl_handle tls_connect(ssl_context ctx, int fd)
{
	ssl_handle ssl=malloc(sizeof(struct ssl_handle_t));

	if (!ssl)
		return NULL;

	memset(ssl, 0, sizeof(*ssl));

	ssl->info_cpy=ctx->info_cpy;
	ssl->ctx=ctx;

	if (ctx->info_cpy.peer_verify_domain && !*ctx->trustcerts)
	{
		errno=ENOENT;
		(*ctx->info_cpy.tls_err_msg)( "TLS_TRUSTCERTS not set",
					      ctx->info_cpy.app_data);
		free(ssl);
		return NULL;
	}

        if (fcntl(fd, F_SETFL, O_NONBLOCK))
        {
                nonsslerror(&ctx->info_cpy, "fcntl");
                return (NULL);
        }

#ifdef  SO_KEEPALIVE

        {
        int     dummy;

                dummy=1;

                if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
                        (const char *)&dummy, sizeof(dummy)) < 0)
                {
                        nonsslerror(&ctx->info_cpy, "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(&ctx->info_cpy, "setsockopt");
                        return (NULL);
                }
        }
#endif

	if (gnutls_anon_allocate_client_credentials(&ssl->anonclientcred) < 0)
	{
		free(ssl);
		return NULL;
	}

	if (gnutls_anon_allocate_server_credentials(&ssl->anonservercred) < 0)
	{
		gnutls_anon_free_client_credentials(ssl->anonclientcred);
		free(ssl);
		return NULL;
	}

	if (gnutls_certificate_allocate_credentials(&ssl->xcred) < 0)
	{
		gnutls_anon_free_server_credentials(ssl->anonservercred);
		gnutls_anon_free_client_credentials(ssl->anonclientcred);
		free(ssl);
		return NULL;
	}

	if (gnutls_dh_params_init(&ssl->dhparams) < 0)
	{
		gnutls_certificate_free_credentials(ssl->xcred);
		gnutls_anon_free_server_credentials(ssl->anonservercred);
		gnutls_anon_free_client_credentials(ssl->anonclientcred);
		free(ssl);
		return NULL;
	}

	if (gnutls_init (&ssl->session,
			 ctx->isserver ? GNUTLS_SERVER:GNUTLS_CLIENT) < 0)
	{
		gnutls_certificate_free_credentials(ssl->xcred);
		gnutls_anon_free_server_credentials(ssl->anonservercred);
		gnutls_anon_free_client_credentials(ssl->anonclientcred);
		free(ssl);
		return NULL;
	}

	{
		const char *p=getenv("TLS_MIN_DH_BITS");
		unsigned int n=atoi(p ? p:"0");

		if (n)
			gnutls_dh_set_prime_bits(ssl->session, n);
	}

	gnutls_session_set_ptr(ssl->session, ssl);

        gnutls_handshake_set_private_extensions(ssl->session, 1);
        gnutls_certificate_set_verify_flags(ssl->xcred, 
                                            GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT |
                                            
                                            /*
                                            GNUTLS_VERIFY_DO_NOT_ALLOW_SAME |
                                            GNUTLS_VERIFY_ALLOW_ANY_X509_V1_CA_C
RT |

                                            GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD2 |
                                            GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD5 |*/
                                            0);

        gnutls_certificate_set_verify_limits(ssl->xcred, 16384, 10);

	if (gnutls_set_default_priority (ssl->session) < 0 ||
	    gnutls_kx_set_priority (ssl->session, ctx->kx_list) < 0 ||
	    gnutls_cipher_set_priority(ssl->session, ctx->cipher_list) < 0 ||
	    gnutls_compression_set_priority(ssl->session, ctx->comp_list) < 0 ||
	    gnutls_protocol_set_priority(ssl->session, ctx->protocol_list) < 0||
	    (ctx->certfiledh && read_dh_params(ssl->dhparams,
					       ctx->certfile) < 0) ||
	    add_certificates(ssl->xcred, ctx->trustcerts) < 0 ||
#if 0
	    add_certificates(ssl->xcred, ctx->certfile) < 0 ||
	    add_certificates(ssl->xcred, ctx->dhcertfile) < 0 ||
#endif
	    gnutls_credentials_set(ssl->session, GNUTLS_CRD_ANON,
				   ctx->isserver
				   ? (void *)ssl->anonservercred
				   : (void *)ssl->anonclientcred)
	    < 0 ||
	    gnutls_credentials_set(ssl->session, GNUTLS_CRD_CERTIFICATE,
				   ssl->xcred) < 0 ||

	    gnutls_certificate_type_set_priority(ssl->session,
						 ctx->cert_list) < 0 ||
	    (ctx->info_cpy.peer_verify_domain &&
	     gnutls_server_name_set(ssl->session, GNUTLS_NAME_DNS,
				    ctx->info_cpy.peer_verify_domain,
				    strlen(ctx->info_cpy.peer_verify_domain))
	     < 0)
	    )
	{
		tls_free_session(ssl);
		return NULL;
	}

	if (ctx->certfiledh)
	{
		gnutls_certificate_set_dh_params(ssl->xcred, ssl->dhparams);

		gnutls_anon_set_server_dh_params(ssl->anonservercred,
						 ssl->dhparams);
	}

	if (ctx->isserver)
	{
		if (ctx->verify_cert)
			gnutls_certificate_server_set_request(ssl->session,
							      ctx->fail_if_no_cert ?
							      GNUTLS_CERT_REQUIRE:
							      GNUTLS_CERT_REQUEST);
		gnutls_certificate_server_set_retrieve_function(ssl->xcred,
								get_server_cert
								);
	}
	else gnutls_certificate_client_set_retrieve_function(ssl->xcred,
							     get_client_cert);

	gnutls_transport_set_ptr(ssl->session,(gnutls_transport_ptr_t)
				 GNUTLS_CAST_PTR_T fd);

	if (ssl->ctx->info_cpy.tlscache)
	{
		gnutls_db_set_ptr(ssl->session, ssl);

		gnutls_db_set_cache_expiration(ssl->session, 3600);

		gnutls_db_set_remove_function(ssl->session,
					      db_remove_func);
		gnutls_db_set_retrieve_function(ssl->session,
						db_retrieve_func);
		gnutls_db_set_store_function(ssl->session,
					     db_store_func);
	}

	ssl->info_cpy.connect_interrupted=1;

	if (dohandshake(ssl, fd, NULL, NULL) < 0)
	{
		tls_disconnect(ssl, fd);
		return NULL;
	}

	return ssl;
}

void tls_disconnect(ssl_handle ssl, int fd)
{
	fcntl(fd, F_SETFL, 0);
	gnutls_bye(ssl->session, GNUTLS_SHUT_RDWR);
	tls_free_session(ssl);
}

int	tls_transfer(struct tls_transfer_info *t, ssl_handle ssl, int fd,
		     fd_set *r, fd_set *w)
{
	if (ssl->info_cpy.connect_interrupted)
	{
		if (dohandshake(ssl, fd, r, w) < 0)
			return -1;

		return 0;
	}

	if (t->shutdown)
		return -1;

	if (t->shutdown_interrupted)
	{
		while (chk_error(gnutls_bye(ssl->session, GNUTLS_SHUT_RDWR),
				 ssl, fd, r, w, NULL))
			;

		if ((r && FD_ISSET(fd, r)) ||
		    (w && FD_ISSET(fd, w)))
		{
			return 1;
		}

			
		t->shutdown_interrupted=0;
		t->shutdown= -1;
		return -1;
	}

	if(0) printf("readleft=%d writeleft=%d\nread_interrupted=%d read_interrupted=%d\n",
		    (int)t->readleft,(int)t->writeleft,
		    t->read_interrupted,t->write_interrupted);

	if (!t->write_interrupted && t->readleft > 0 && t->writeleft == 0)
	{
		int rc;
		ssize_t n;

		do
		{
			n=gnutls_record_recv(ssl->session, t->readptr,
					     t->readleft);

			if (n >= 0)
			{
				if (n == 0)
				{
					t->shutdown=1;
					return -1;
				}

				t->readptr += n;
				t->readleft -= n;
				return 0;
			}

			if ((int)n == GNUTLS_E_REHANDSHAKE)
			{
				ssl->info_cpy.connect_interrupted=1;

				return tls_transfer(t, ssl, fd, r, w);
			}

		} while (chk_error((int)n, ssl, fd, r, w, &rc));

		if (rc < 0)
		{
			t->shutdown_interrupted=1;
			return tls_transfer(t, ssl, fd, r, w);
		}
	} else if (t->writeleft > 0)
	{
		int rc;
		ssize_t n;

		t->write_interrupted=0;

		do
		{
			n=gnutls_record_send(ssl->session, (void *)t->writeptr,
					     t->writeleft);

			if (n >= 0)
			{
				if (n == 0)
				{
					t->shutdown=1;
					return -1;
				}

				t->writeptr += n;
				t->writeleft -= n;
				return 0;
			}

			if ((int)n == GNUTLS_E_REHANDSHAKE)
			{
				t->write_interrupted=1;
				ssl->info_cpy.connect_interrupted=1;

				return tls_transfer(t, ssl, fd, r, w);
			}

		} while (chk_error((int)n, ssl, fd, r, w, &rc));

		if (rc < 0)
		{
			t->shutdown=1;
			return -1;
		}
		t->write_interrupted=1;
	}
	else
	{
		FD_SET(fd, r);
		FD_SET(fd, w);
	}
	return (1);
}

int tls_connecting(ssl_handle ssl)
{
	return ssl->info_cpy.connect_interrupted;
}

static const char *dump_dn(gnutls_x509_crt_t cert,
			   int (*get_dn_func)(gnutls_x509_crt_t cert, int indx,
					      void *oid, size_t * sizeof_oid),
			   int (*get_dnval_func)(gnutls_x509_crt_t cert,
						 const char *oid, int indx,
						 unsigned int raw_flag,
						 void *buf, size_t *sizeof_buf),
			   void (*dump_func)(const char *, int cnt, void *),
			   void *dump_arg)
{
	int idx;
	size_t bufsiz;
	size_t maxnamesize;
	size_t maxvalsize;
	char *oidname;
	char *oidval;
	int oidcnt;

	maxnamesize=0;
	maxvalsize=0;

	oidcnt=0;

	while (bufsiz=0, (*get_dn_func)(cert, oidcnt, NULL, &bufsiz)
	       == GNUTLS_E_SHORT_MEMORY_BUFFER)
	{
		if (bufsiz > maxnamesize)
			maxnamesize=bufsiz;
		++oidcnt;
	}

	oidname=malloc(maxnamesize);

	if (!oidname)
		return strerror(errno);

	for (idx=0; idx<oidcnt; ++idx)
	{
		int vidx;
		int rc;

		bufsiz=maxnamesize;

		if ((rc=(*get_dn_func)(cert, idx, oidname, &bufsiz)) < 0)
		{
			free(oidname);
			return gnutls_strerror(rc);
		}
	
		vidx=0;

		while (bufsiz=0,
		       (*get_dnval_func)(cert, oidname, vidx, 0,
					 NULL, &bufsiz)
		       == GNUTLS_E_SHORT_MEMORY_BUFFER)
		{
			if (bufsiz > maxvalsize)
				maxvalsize=bufsiz;
			++vidx;
		}
	}

	oidval=malloc(maxvalsize);

	if (!oidval)
	{
		free(oidname);
		return strerror(errno);
	}

	for (idx=0; idx<oidcnt; ++idx)
	{
		int vidx;
		int rc;
		size_t i;
		const char *oidname_str;

		bufsiz=maxnamesize;

		if ((rc=(*get_dn_func)(cert, idx, oidname, &bufsiz)) < 0)
		{
			free(oidval);
			free(oidname);
			return gnutls_strerror(rc);
		}

		oidname_str=oidname;

		for (i=0; i<sizeof(oid_name_list)/sizeof(oid_name_list[0]);
		     ++i)
		{
			if (strcmp(oid_name_list[i].oid, oidname) == 0)
			{
				oidname_str=oid_name_list[i].name;
				break;
			}
		}

		vidx=0;

		while (bufsiz=maxvalsize,
		       (*get_dnval_func)(cert, oidname, vidx, 0,
					 oidval, &bufsiz) >= 0)
		{
			(*dump_func)("   ", -1, dump_arg);
			(*dump_func)(oidname_str, -1, dump_arg);
			(*dump_func)("=", -1, dump_arg);
			(*dump_func)(oidval, -1, dump_arg);
			(*dump_func)("\n", -1, dump_arg);
			++vidx;
		}
	}
	
	free(oidval);
	free(oidname);
	return NULL;
}

static void print_time(const char *name, time_t t,
		       void (*dump_func)(const char *, int cnt, void *),
		       void *dump_arg)
{
	struct tm *tmptr=gmtime(&t);
	char buf[256];

	strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tmptr);

	(*dump_func)(name, -1, dump_arg);
	(*dump_func)(": ", 2, dump_arg);
	(*dump_func)(buf, -1, dump_arg);
	(*dump_func)("\n", 1, dump_arg);
}

static void tls_dump_connection_info_x509(ssl_handle ssl,
					  int server,
					  void (*dump_func)(const char *,
							    int cnt, void *),
					  void *dump_arg);

static void dump_cipher_name(gnutls_session_t session,
			     void (*dump_func)(const char *,
					       int cnt, void *),
			     void *dump_arg);

void tls_dump_connection_info(ssl_handle ssl,
			      int server,
			      void (*dump_func)(const char *, int cnt, void *),
			      void *dump_arg)
{
	if (gnutls_certificate_type_get (ssl->session) == GNUTLS_CRT_X509)
		tls_dump_connection_info_x509(ssl, server, dump_func,
					      dump_arg);

	(*dump_func)("Version: ", -1, dump_arg);
	(*dump_func)
		(gnutls_protocol_get_name(gnutls_protocol_get_version(ssl->session)),
		 -1, dump_arg);
	(*dump_func)("\n", 1, dump_arg);

	{
		char buf[10];

		(*dump_func)("Bits: ", -1, dump_arg);

		snprintf(buf, sizeof(buf), "%d", (int)
			 gnutls_cipher_get_key_size(gnutls_cipher_get(ssl->session))
			 *8);
		buf[sizeof(buf)-1]=0;

		(*dump_func)(buf, -1, dump_arg);
		(*dump_func)("\n", 1, dump_arg);
	}

	(*dump_func)("Cipher: ", -1, dump_arg);
	dump_cipher_name(ssl->session, dump_func, dump_arg);
	(*dump_func)("\n", 1, dump_arg);
}

static void dump_cipher_name(gnutls_session_t session,
			     void (*dump_func)(const char *,
					       int cnt, void *),
			     void *dump_arg)
{
	gnutls_kx_algorithm_t kx_algo;
	gnutls_cipher_algorithm_t cipher_algo;
	gnutls_mac_algorithm_t mac_algo;
	const char *cipher_name;

	kx_algo=gnutls_kx_get(session);
	cipher_algo=gnutls_cipher_get(session);
	mac_algo=gnutls_mac_get(session);
	cipher_name=gnutls_cipher_suite_get_name(kx_algo, cipher_algo,
						 mac_algo);

	if (cipher_name)
		(*dump_func)(cipher_name, -1, dump_arg);
	else
	{
		gnutls_compression_method_t comp;

		(*dump_func)(gnutls_kx_get_name(kx_algo), -1, dump_arg);
		
		(*dump_func)("-", 1, dump_arg);
		(*dump_func)(gnutls_certificate_type_get_name(gnutls_certificate_type_get(session)),
			     -1, dump_arg);

		(*dump_func)("-", 1, dump_arg);
		(*dump_func)(gnutls_cipher_get_name(cipher_algo), -1,
			     dump_arg);

		if ((comp=gnutls_compression_get(session))
		    != GNUTLS_COMP_NULL)
		{
			(*dump_func)("/", 1, dump_arg);
			(*dump_func)(gnutls_compression_get_name(comp),
				     -1, dump_arg);
		}

		(*dump_func)("-", 1, dump_arg);
		(*dump_func)(gnutls_mac_get_name(gnutls_mac_get(session)),
			     -1, dump_arg);
	}
}

static void tls_dump_connection_info_x509(ssl_handle ssl,
					  int server,
					  void (*dump_func)(const char *,
							    int cnt, void *),
					  void *dump_arg)
{
	const gnutls_datum_t *cert_list;
	unsigned int cert_list_size;
	gnutls_x509_crt_t *cert;

	cert_list=gnutls_certificate_get_peers(ssl->session, &cert_list_size);

	if (cert_list)
	{
		unsigned int i;

		cert=malloc(sizeof (*cert) * cert_list_size);

		for (i = 0; i<cert_list_size; i++)
		{
			gnutls_x509_crt_init(&cert[i]);
			gnutls_x509_crt_import(cert[i],
					       &cert_list[i],
					       GNUTLS_X509_FMT_DER);
		}

		for (i = 0; i < cert_list_size; i++)
		{
			time_t notbefore;
			time_t notafter;

			(*dump_func)("Subject:\n", -1, dump_arg);

			dump_dn(cert[i],
				gnutls_x509_crt_get_dn_oid,
				gnutls_x509_crt_get_dn_by_oid,
				dump_func, dump_arg);
			(*dump_func)("\n", 1, dump_arg);


#if 0
			(*dump_func)("Issuer:\n", -1, dump_arg);

			dump_dn(cert[i],
				gnutls_x509_crt_get_issuer_dn_oid,
				gnutls_x509_crt_get_issuer_dn_by_oid,
				dump_func, dump_arg);
			(*dump_func)("\n", 1, dump_arg);
#endif

			notbefore=gnutls_x509_crt_get_activation_time(cert[i]);
			notafter=gnutls_x509_crt_get_expiration_time(cert[i]);
			print_time("Not-Before", notbefore,
				   dump_func, dump_arg);
			print_time("Not-After",  notafter,
				   dump_func, dump_arg);
		}

		for (i = 0; i < cert_list_size; i++)
			gnutls_x509_crt_deinit(cert[i]);
		free(cert);
	}
}

static void gen_encryption_desc(gnutls_session_t session,
				void (*dump_func)(const char *,
						  int cnt, void *),
				void *dump_arg);

static void cnt_desc_size(const char *str, int s, void *ptr)
{
	if (s < 0)
		s=strlen(str);

	*(size_t *)ptr += s;
}

static void save_desc(const char *str, int s, void *ptr)
{
	if (s < 0)
		s=strlen(str);

	memcpy(*(char **)ptr, str, s);
	*(char **)ptr += s;
}

char *tls_get_encryption_desc(ssl_handle ssl)
{
	size_t n=1;
	char *buf;

	gen_encryption_desc(ssl->session, cnt_desc_size, &n);

	buf=malloc(n);

	if (buf)
	{
		char *ptr=buf;
		gen_encryption_desc(ssl->session, save_desc, &ptr);
		*ptr=0;
	}
	return buf;
}

static void gen_encryption_desc(gnutls_session_t session,
				void (*dump_func)(const char *,
						  int cnt, void *),
				void *dump_arg)
{
	char buf[10];

	(*dump_func)(gnutls_protocol_get_name(gnutls_protocol_get_version(session)),
		     -1, dump_arg);
	(*dump_func)(",", 1, dump_arg);
	snprintf(buf, sizeof(buf), "%d",
		 (int)gnutls_cipher_get_key_size(gnutls_cipher_get(session))
		 *8);
	buf[sizeof(buf)-1]=0;
	(*dump_func)(buf, -1, dump_arg);
	(*dump_func)("bits,", -1, dump_arg);
	dump_cipher_name(session, dump_func, dump_arg);
}


/* ------------------- */

int tls_validate_pem_cert(const char *buf, size_t buf_size)
{
	gnutls_datum_t dat;
	unsigned int cert_cnt=0;
 	gnutls_x509_crt_t *certbuf;

	dat.data=(void *)buf;
	dat.size=buf_size;

	gnutls_x509_crt_list_import(NULL, &cert_cnt, &dat,
				    GNUTLS_X509_FMT_PEM,
				    GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);

	if (cert_cnt == 0)
		return 0;
	certbuf=malloc(sizeof(*certbuf)*cert_cnt);

	if (!certbuf)
		return 0;

	if (gnutls_x509_crt_list_import(certbuf, &cert_cnt, &dat,
					GNUTLS_X509_FMT_PEM,
					GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED) < 0)
		return 0;

	while (cert_cnt)
		gnutls_x509_crt_deinit(certbuf[--cert_cnt]);
	free(certbuf);
	return (1);
}

char *tls_cert_name(const char *buf, size_t buf_size)
{
	gnutls_datum_t dat;
	unsigned int cert_cnt=0;
 	gnutls_x509_crt_t *certbuf;
	char *p=0;
	size_t p_size;


	dat.data=(void *)buf;
	dat.size=buf_size;

	gnutls_x509_crt_list_import(NULL, &cert_cnt, &dat,
				    GNUTLS_X509_FMT_PEM,
				    GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);

	if (cert_cnt == 0)
		return 0;
	certbuf=malloc(sizeof(*certbuf)*cert_cnt);

	if (!certbuf)
		return 0;

	if (gnutls_x509_crt_list_import(certbuf, &cert_cnt, &dat,
					GNUTLS_X509_FMT_PEM,
					GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED) < 0)
		return 0;

	p_size=0;
	gnutls_x509_crt_get_dn(certbuf[0], NULL, &p_size);
	++p_size;
	p=malloc(p_size+1);

	if (p)
	{
		if (gnutls_x509_crt_get_dn(certbuf[0], p, &p_size)
		    != GNUTLS_E_SUCCESS)
		{
			free(p);
			p=0;
		}
		else p[p_size]=0;
	}

	while (cert_cnt)
		gnutls_x509_crt_deinit(certbuf[--cert_cnt]);
	free(certbuf);
	return p;
}
