diff options
Diffstat (limited to 'tcpd')
| -rw-r--r-- | tcpd/configure.ac | 10 | ||||
| -rw-r--r-- | tcpd/couriertcpd.sgml | 47 | ||||
| -rw-r--r-- | tcpd/couriertls.sgml | 20 | ||||
| -rw-r--r-- | tcpd/libcouriergnutls.c | 238 | ||||
| -rw-r--r-- | tcpd/libcouriertls.c | 385 | ||||
| -rw-r--r-- | tcpd/libcouriertls.h | 6 | ||||
| -rw-r--r-- | tcpd/starttls.c | 10 | ||||
| -rw-r--r-- | tcpd/tcpd.c | 17 | ||||
| -rw-r--r-- | tcpd/tlsclient.c | 8 | ||||
| -rw-r--r-- | tcpd/tlsclient.h | 2 | 
10 files changed, 600 insertions, 143 deletions
| diff --git a/tcpd/configure.ac b/tcpd/configure.ac index 8f6d1fc..0aa6f8a 100644 --- a/tcpd/configure.ac +++ b/tcpd/configure.ac @@ -15,6 +15,7 @@ AC_CONFIG_HEADERS(config.h)  dnl Checks for programs.  AC_PROG_CC +AC_PROG_CC_C99  AC_PROG_INSTALL  AC_PROG_LN_S  AC_LIBTOOL_DLOPEN @@ -165,6 +166,13 @@ fi  dnl Checks for library functions. +PKG_CHECK_MODULES(LIBIDN, libidn >= 0.0.0, [libidn=yes], [libidn=no]) + +if test "$libidn" != "yes" +then +	AC_MSG_ERROR([libidn not found]) +fi +  AC_CHECK_FUNCS(setpgrp setpgid)  AC_CHECK_FUNC(setpgrp,  	[ @@ -545,7 +553,7 @@ RAND_bytes(dummy, 1);  ]  )  		LIBS="-lssl $LIBS" -		AC_CHECK_FUNCS(TLSv1_1_method TLSv1_2_method) +		AC_CHECK_FUNCS(TLSv1_1_method TLSv1_2_method X509_VERIFY_PARAM_set1_host)  		LIBS="$save_LIBS"  		AC_TRY_COMPILE( [ diff --git a/tcpd/couriertcpd.sgml b/tcpd/couriertcpd.sgml index 1d10157..4a835e3 100644 --- a/tcpd/couriertcpd.sgml +++ b/tcpd/couriertcpd.sgml @@ -1,7 +1,7 @@  <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> -<!-- Copyright 2000-2015 Double Precision, Inc.  See COPYING for --> +<!-- Copyright 2000-2020 Double Precision, Inc.  See COPYING for -->  <!-- distribution information. --> -<refentry> +<refentry id="couriertcpd">    <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info>    <refmeta> @@ -39,7 +39,7 @@ TCP server daemon</refpurpose>      </cmdsynopsis>    </refsynopsisdiv> -  <refsect1> +  <refsect1 id="couriertcpd_description">      <title>DESCRIPTION</title>      <para> @@ -68,7 +68,7 @@ which network connections will be rejected.</para>    </refsect1> -  <refsect1> +  <refsect1 id="couriertcpd_options">      <title>OPTIONS</title>      <variablelist> @@ -122,9 +122,9 @@ information.</para>        </varlistentry>        <varlistentry> -	<term>-block=<replaceable>zone</replaceable>[,<replaceable>var</replaceable>[/<replaceable>n.n.n.n</replaceable>][,<replaceable>msg</replaceable>]] +	<term>-block=<replaceable>zone</replaceable>[=<replaceable>display_zone</replaceable>][,<replaceable>var</replaceable>[/<replaceable>n.n.n.n</replaceable>][,<replaceable>msg</replaceable>]]  	  or -	-allow=<replaceable>zone</replaceable>[,<replaceable>var</replaceable>[/<replaceable>n.n.n.n</replaceable>[,]]]</term> +	-allow=<replaceable>zone</replaceable>[=<replaceable>display_zone</replaceable>][,<replaceable>var</replaceable>[/<replaceable>n.n.n.n</replaceable>[,]]]</term>  	<listitem>  	  <para>  Initialize the environment variable <replaceable>var</replaceable> if both of @@ -378,8 +378,7 @@ superuser can specify <option>-user</option>.</para>    </refsect1> -  <refsect1> - +  <refsect1 id="couriertcpd_multiple_port_list">      <title>MULTIPLE PORT LIST</title>      <anchor id="list"/> @@ -440,8 +439,7 @@ will create network connections on ports 25 and 999 of the IP address    </refsect1> -  <refsect1> - +  <refsect1 id="couriertcpd_access_file">      <title>ACCESS FILE</title>      <para> @@ -465,7 +463,7 @@ from one or more plain text  files. Blank lines in the text file are ignored. Lines that start with the #  character are also ignored.</para> -    <refsect2> +    <refsect2 id="couriertcpd_rejecting_and_accepting_connections_by_ip_address">        <title>Rejecting and accepting connections by IP address</title>        <para> @@ -529,7 +527,7 @@ connections to be rejected:</para>      </refsect2> -    <refsect2> +    <refsect2 id="couriertcpd_ipv6_addresses">        <title>IPv6 addresses</title>        <note> @@ -638,8 +636,7 @@ file, the entry for the most specific IP address.</para>        </para>      </refsect2> -    <refsect2> - +    <refsect2 id="couriertcpd_dns_access_lists">        <title>DNS ACCESS LISTS</title>        <para> @@ -681,6 +678,15 @@ file, the entry for the most specific IP address.</para>        </para>        <para> +	An optional <quote>=<replaceable>display_zone</replaceable></quote> +	follows the DNS zone. This sets the contents of +	<varname>BLOCK_ZONE</varname> DNS access list variable (see +	below), which defaults to the DNS zone name. This is only useful +	with <option>-allow</option>, since <option>-block</option> rejects +	the message, so nothing gets set anyway. +      </para> + +      <para>  	For IPv6 addresses, the DNS query consists of individual hexadecimal  	nybbles (in reverse order, like the IPv4 query).        </para> @@ -816,7 +822,7 @@ file, the entry for the most specific IP address.</para>        </para>      </refsect2> -    <refsect2> +    <refsect2 id="couriertcpd_multiple_dns_lists">        <title>MULTIPLE DNS LISTS</title>        <para> @@ -852,7 +858,7 @@ file, the entry for the most specific IP address.</para>        </para>      </refsect2> -    <refsect2> +    <refsect2 id="couriertcpd_additional_dns_access_list_variables">        <title>ADDITIONAL DNS ACCESS LIST VARIABLES</title>        <para> @@ -887,7 +893,8 @@ file, the entry for the most specific IP address.</para>  	  <term>BLOCK_ZONE</term>  	  <para>  	    The DNS zone of the succesfull access list lookup, like -	    <quote>dnsbl.example.com</quote>. +	    <quote>dnsbl.example.com</quote>, or an explicit +	    display zone name.  	  </para>  	</varlistentry>        </variablelist> @@ -901,7 +908,7 @@ file, the entry for the most specific IP address.</para>        </para>      </refsect2> -    <refsect2> +    <refsect2 id="couriertcpd_using_dns_whitelists_with_spf">        <title>USING DNS WHITELISTS WITH SPF</title>        <para> @@ -918,7 +925,7 @@ file, the entry for the most specific IP address.</para>      </refsect2>    </refsect1> -  <refsect1> +  <refsect1 id="couriertcpd_environment_variables">      <title>ENVIRONMENT VARIABLES</title>      <para> @@ -997,7 +1004,7 @@ TCP port of the remote end of the network connection.</para>      </variablelist>    </refsect1> -  <refsect1> +  <refsect1 id="couriertcpd_see_also">      <title>SEE ALSO</title>      <para> diff --git a/tcpd/couriertls.sgml b/tcpd/couriertls.sgml index a7a8e72..82669e2 100644 --- a/tcpd/couriertls.sgml +++ b/tcpd/couriertls.sgml @@ -1,7 +1,7 @@  <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">  <!-- Copyright 2000-2018 Double Precision, Inc.  See COPYING for -->  <!-- distribution information. --> -<refentry> +<refentry id="couriertls">    <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info>    <refmeta> @@ -25,7 +25,7 @@ TLS/SSL protocol wrapper</refpurpose>      </cmdsynopsis>    </refsynopsisdiv> -  <refsect1> +  <refsect1 id="couriertls_description">      <title>DESCRIPTION</title>      <para> @@ -42,7 +42,7 @@ An application typically creates a network connection, then runs  connection with SSL/TLS.</para>    </refsect1> -  <refsect1> +  <refsect1 id="couriertls_options">      <title>OPTIONS</title>      <variablelist> @@ -175,7 +175,7 @@ If the program is not specified, the standard input and output of    </refsect1> -  <refsect1> +  <refsect1 id="couriertls_environment_variables">      <title>ENVIRONMENT VARIABLES</title>  <para> @@ -236,6 +236,16 @@ for SSL/TLS clients.        </varlistentry>        <varlistentry> +	<term>TLS_PRIVATE_KEYFILE=<replaceable>filename</replaceable></term> +	<listitem> +	  <para> +SSL/TLS private key for decrypting client data. +<envar>TLS_PRIVATE_KEY</envar> is optional because <term>TLS_CERTFILE</term> is generated including cert and private key both. +<replaceable>filename</replaceable> must not be world-readable, and must be accessible without a pass-phrase, i.e. it must not be encrypted.</para> +	</listitem> +      </varlistentry> + +      <varlistentry>  	<term>TLS_TRUSTCERTS=<replaceable>pathname</replaceable></term>  	<listitem>  	  <para> @@ -278,7 +288,7 @@ X.509 certificate according to the <option>-domain</option> option,      </variablelist>    </refsect1> -  <refsect1> +  <refsect1 id="couriertls_see_also">      <title>SEE ALSO</title>      <para> diff --git a/tcpd/libcouriergnutls.c b/tcpd/libcouriergnutls.c index f3c34d4..5fc0ef9 100644 --- a/tcpd/libcouriergnutls.c +++ b/tcpd/libcouriergnutls.c @@ -1,5 +1,5 @@  /* -** Copyright 2007-2018 Double Precision, Inc. +** Copyright 2007-2019 Double Precision, Inc.  ** See COPYING for distribution information.  */  #include	"config.h" @@ -18,6 +18,7 @@  #include	<stdlib.h>  #include	<ctype.h>  #include	<netdb.h> +#include	<idna.h>  #if HAVE_DIRENT_H  #include <dirent.h>  #define NAMLEN(dirent) strlen((dirent)->d_name) @@ -126,6 +127,7 @@ struct ssl_context_t {  	const char *priority_list;  	char *certfile; +	char *keyfile;  	char *dhfile;  	char *trustcerts; @@ -267,6 +269,17 @@ ssl_context tls_create(int isserver, const struct tls_info *info)  			free(certfile);  	} +	if ((certfile=strdup(safe_getenv(p, "TLS_PRIVATE_KEYFILE", ""))) != NULL && +	    *certfile) +	{ +		p->keyfile=certfile; +	} +	else +	{ +		if (certfile) +			free(certfile); +	} +  	switch (*safe_getenv(p, "TLS_VERIFYPEER", "P")) {  	case 'n':  	case 'N': @@ -320,6 +333,8 @@ void tls_destroy(ssl_context p)  {  	if (p->certfile)  		free(p->certfile); +	if (p->keyfile) +		free(p->keyfile);  	if (p->dhfile)  		free(p->dhfile);  	if (p->trustcerts) @@ -363,7 +378,7 @@ static int read_cert_dir(const char *cert_dir,  		strcat(strcat(strcpy(buf, cert_dir), "/"), de->d_name); -		if (lstat(buf, &stat_buf) < 0 || !S_ISREG(stat_buf.st_mode)) +		if (stat(buf, &stat_buf) < 0 || !S_ISREG(stat_buf.st_mode))  		{  			free(buf);  			continue; @@ -539,6 +554,37 @@ static int chk_error(int rc, ssl_handle ssl, int fd, fd_set *r, fd_set *w,  	return 0;  } +static int name_check(ssl_handle ssl, +		      gnutls_x509_crt_t cert) +{ +	char *idn_domain; +	const char *p; +	int rc; + +	if (idna_to_unicode_8z8z(ssl->info_cpy.peer_verify_domain, +				 &idn_domain, 0) != IDNA_SUCCESS) +		idn_domain=0; + +	p=idn_domain ? idn_domain:ssl->info_cpy.peer_verify_domain; + +	rc=gnutls_x509_crt_check_hostname(cert, p); + +	if (idn_domain) +	{ +		free(idn_domain); + +		/* Now try the ACE-encoded hostname for a filename. */ + +		if (rc == 0) +		{ +			p=ssl->info_cpy.peer_verify_domain; +			rc=gnutls_x509_crt_check_hostname(cert, p); +		} +	} +	return rc; +} + +  static int verify_client(ssl_handle ssl, int fd)  {  	unsigned int status; @@ -632,10 +678,7 @@ static int verify_client(ssl_handle ssl, int fd)  		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 -						    )) +		    !name_check(ssl, cert))  		{  			char hostname[256];  			size_t hostname_size=sizeof(hostname); @@ -703,10 +746,10 @@ static int dohandshake(ssl_handle ssl, int fd, fd_set *r, fd_set *w)  	return rc;  } -static char *check_cert(const char *filename, -			gnutls_certificate_type_t cert_type, -			const char *req_dn, -			int isvirtual) +static char *check_cert_unicode(const char *filename, +				gnutls_certificate_type_t cert_type, +				const char *req_dn, +				int isvirtual)  {  	if (!filename || !*filename)  		return NULL; @@ -733,6 +776,12 @@ static char *check_cert(const char *filename,  		++req_dn;  	} +	/* +	** We're called with a hostname first. Don't check the defualt +	** filename, we'll be called again with an IP address +	*/ + +	if (!isvirtual)  	{  		char *p=malloc(strlen(filename)+10); @@ -749,6 +798,41 @@ static char *check_cert(const char *filename,  	return NULL;  } +static char *check_cert(const char *filename, +			gnutls_certificate_type_t cert_type, +			const char *req_dn, +			int isvirtual) +{ +	if (isvirtual) +	{ +		char *p; +		char *retfile; + +		if (idna_to_ascii_8z(req_dn, &p, 0) != IDNA_SUCCESS) +			p=0; + +		if (p) +		{ +			retfile=check_cert_unicode(filename, cert_type, p, +						    isvirtual); +			free(p); + +			if (retfile) +				return retfile; +		} +	} + +	return check_cert_unicode(filename, cert_type, req_dn, isvirtual); +} + +static char *check_key(const char *filename, +			gnutls_certificate_type_t cert_type, +			const char *req_dn, +			int isvirtual) +{ +	return check_cert(filename, cert_type, req_dn, isvirtual); +} +  static int read_file(const char *file,  		     gnutls_datum_t *filebuf)  { @@ -790,10 +874,12 @@ static void release_file(gnutls_datum_t *filebuf)  static int set_cert(ssl_handle ssl,  		    gnutls_session_t session,  		    gnutls_retr2_st *st, -		    const char *certfilename) +		    const char *certfilename, +		    const char *keyfilename)  {  	int rc;  	gnutls_datum_t filebuf; +	gnutls_datum_t keyfilebuf;  	unsigned int cert_cnt;  	st->ncerts=0; @@ -803,15 +889,34 @@ static int set_cert(ssl_handle ssl,  	if ((rc=read_file(certfilename, &filebuf)) < 0)  		return rc; +	if (keyfilename) +	{ +		if ((rc=read_file(keyfilename, &keyfilebuf)) < 0) +		{ +			release_file(&filebuf); +			return rc; +		} +	} +  	switch (st->cert_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; +		if (keyfilename) +		{ +			if ((rc=gnutls_x509_privkey_init(&ssl->x509_key)) < 0 || +			    (rc=gnutls_x509_privkey_import(ssl->x509_key, &keyfilebuf, +							   GNUTLS_X509_FMT_PEM)) < 0) +				break; +		} +		else +		{ +			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, @@ -846,6 +951,8 @@ static int set_cert(ssl_handle ssl,  	}  	release_file(&filebuf); +	if (keyfilename) +		release_file(&keyfilebuf);  	return 0;  } @@ -862,6 +969,7 @@ static int get_server_cert(gnutls_session_t session,  	size_t vhost_size;  	unsigned int type=GNUTLS_NAME_DNS;  	char *certfilename=NULL; +	char *keyfilename=NULL;  	int rc;  	st->cert_type=gnutls_certificate_type_get(session); @@ -887,28 +995,51 @@ static int get_server_cert(gnutls_session_t session,  	     ++vhost_idx)  	{  		char *p; +		char *utf8; +		char *namebuf; + +		/* Convert to UTF8 */ +		if (idna_to_unicode_8z8z(vhost_buf, &utf8, 0) +		    != IDNA_SUCCESS) +			utf8=0; -		for (p=vhost_buf; *p; p++) +		namebuf=utf8 ? utf8:vhost_buf; + +		for (p=namebuf; *p; p++)  			if (*p == '/')  				*p='.'; /* Script kiddie check */  		if (ssl->ctx->certfile)  			certfilename=check_cert(ssl->ctx->certfile,  						st->cert_type, -						vhost_buf, 1); +						namebuf, 1); + +		if (ssl->ctx->keyfile) +			keyfilename=check_key(ssl->ctx->keyfile, +						st->cert_type, +						namebuf, 1); +		if (utf8) +			free(utf8);  		if (certfilename)  			break;  	}  	if (!certfilename)  	{ +		const char *ip= +			safe_getenv(ssl->ctx, +				    "TCPLOCALIP", ""); + +		if (strncmp(ip, "::ffff:", 7) == 0 && strchr(ip, '.')) +			ip += 7; +  		if (ssl->ctx->certfile)  			certfilename=check_cert(ssl->ctx->certfile, -						st->cert_type, -						safe_getenv(ssl->ctx, -							    "TCPLOCALIP", ""), -						0); +						st->cert_type, ip, 0); +		if (ssl->ctx->keyfile) +			keyfilename=check_key(ssl->ctx->keyfile, +					      st->cert_type, ip, 0);  	}  	if (!certfilename) @@ -918,8 +1049,10 @@ static int get_server_cert(gnutls_session_t session,  		return 0;  	} -	rc=set_cert(ssl, session, st, certfilename); +	rc=set_cert(ssl, session, st, certfilename, keyfilename);  	free(certfilename); +	if (keyfilename) +		free(keyfilename);  	return rc;  } @@ -1072,6 +1205,7 @@ static int get_client_cert(gnutls_session_t session,  	ssl_handle ssl=(ssl_handle)gnutls_session_get_ptr(session);  	int rc;  	char *certfilename=NULL; +	char *keyfilename=NULL;  	rc= 0;  	st->cert_type=gnutls_certificate_type_get(session); @@ -1080,12 +1214,16 @@ static int get_client_cert(gnutls_session_t session,  		certfilename=check_cert(ssl->ctx->certfile,  					st->cert_type, "", 0); +	if (ssl->ctx->keyfile) +		keyfilename=check_key(ssl->ctx->keyfile, +					st->cert_type, "", 0); +  	st->ncerts=0;  	st->deinit_all=0;  	if (certfilename)  	{ -		rc=set_cert(ssl, session, st, certfilename); +		rc=set_cert(ssl, session, st, certfilename, keyfilename);  		free(certfilename);  	}  	else @@ -1095,6 +1233,10 @@ static int get_client_cert(gnutls_session_t session,  		if (rc > 0)  			rc=0;  	} + +	if (keyfilename) +		free(keyfilename); +  	return rc;  } @@ -1225,6 +1367,26 @@ static gnutls_datum_t db_retrieve_func(void *dummy, gnutls_datum_t key)  	return drs.ret;  } +static int name_set(ssl_handle ssl, ssl_context ctx) +{ +	char *idn_domain; +	const char *p; +	int rc; + +	if (idna_to_unicode_8z8z(ctx->info_cpy.peer_verify_domain, +				 &idn_domain, 0) != IDNA_SUCCESS) +		idn_domain=0; + +	p=idn_domain ? idn_domain:ctx->info_cpy.peer_verify_domain; + +	rc=gnutls_server_name_set(ssl->session, GNUTLS_NAME_DNS, +				  p, strlen(p)); + +	if (idn_domain) +		free(idn_domain); +	return rc; +} +  ssl_handle tls_connect(ssl_context ctx, int fd)  {  	ssl_handle ssl=malloc(sizeof(struct ssl_handle_t)); @@ -1366,10 +1528,7 @@ RT |  				   ssl->xcred) < 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) +	     name_set(ssl, ctx) < 0)  	    )  	{  		tls_free_session(ssl); @@ -1806,6 +1965,8 @@ static void tls_dump_connection_info_x509(ssl_handle ssl,  		{  			time_t notbefore;  			time_t notafter; +			char buffer[256]; +			unsigned j;  			(*dump_func)("Subject:\n", -1, dump_arg); @@ -1815,6 +1976,29 @@ static void tls_dump_connection_info_x509(ssl_handle ssl,  				dump_func, dump_arg);  			(*dump_func)("\n", 1, dump_arg); +			for (j=0; ; ++j) +			{ +				size_t s=sizeof(buffer); +				enum gnutls_x509_subject_alt_name_t t; + +				t=gnutls_x509_crt_get_subject_alt_name +					(cert[i], j, buffer, &s, 0); + +				if (t == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) +					break; + +				if (t != GNUTLS_SAN_DNSNAME) +					continue; + +				if (s == sizeof(buffer)) +					--s; /* The API is not clear */ + +				buffer[s]=0; +				(*dump_func)("Subject-Alt-Name-DNS: ", -1, +					     dump_arg); +				(*dump_func)(buffer, -1, dump_arg); +				(*dump_func)("\n", -1, dump_arg); +			}  #if 0  			(*dump_func)("Issuer:\n", -1, dump_arg); diff --git a/tcpd/libcouriertls.c b/tcpd/libcouriertls.c index 199015e..1f5a40f 100644 --- a/tcpd/libcouriertls.c +++ b/tcpd/libcouriertls.c @@ -9,6 +9,7 @@  #include	"libcouriertls.h"  #include	<openssl/rand.h>  #include	<openssl/x509.h> +#include	<openssl/x509v3.h>  #include	"tlscache.h"  #include	"rfc1035/rfc1035.h"  #include	"soxwrap/soxwrap.h" @@ -21,6 +22,7 @@  #include	<stdlib.h>  #include	<ctype.h>  #include	<netdb.h> +#include	<idna.h>  #if HAVE_DIRENT_H  #include <dirent.h>  #define NAMLEN(dirent) strlen((dirent)->d_name) @@ -140,38 +142,151 @@ static int ssl_verify_callback(int goodcert, X509_STORE_CTX *x509)  	return (1);  } +#if HAVE_X509_VERIFY_PARAM_SET1_HOST  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) +	return SSL_get_verify_result(ssl) == X509_V_OK; +} +#else + +static int hostmatch_utf8(const char *a, const char *b) +{ +	while (*a || *b)  	{ -		x=SSL_get_peer_certificate(ssl); +		char ca=*a; +		char cb=*b; + +		if (ca >= 'A' && ca <= 'Z') +			ca += 'a'-'A'; +		if (cb >= 'A' && cb <= 'Z') +			cb += 'a'-'A'; +		if (ca != cb) +			return 0; -		if (x) -			subj=X509_get_subject_name(x); +		++a; +		++b;  	} -	else +	return 1; +} + +static int hostmatch(const struct tls_info *info, const char *domain) +{ +	const char *p=domain; +	const char *verify_domain=info->peer_verify_domain; +	char *idn_domain1; +	char *idn_domain2; +	int rc; + +	if (*p == '*')  	{ -		STACK_OF(X509) *peer_cert_chain=SSL_get_peer_cert_chain(ssl); +		++p; -		if (peer_cert_chain && sk_X509_num(peer_cert_chain) > 0) +		if (*p != '.') +			return 0; + +		while (*verify_domain)  		{ -			X509 *xx=sk_X509_value(peer_cert_chain, 0); +			if (*verify_domain++ == '.') +				break; +		} +	} + +	if (idna_to_unicode_8z8z(verify_domain, &idn_domain1, 0) +	    != IDNA_SUCCESS) +		idn_domain1=0; + +	if (idna_to_unicode_8z8z(p, &idn_domain2, 0) +	    != IDNA_SUCCESS) +		idn_domain2=0; + +	rc=hostmatch_utf8(idn_domain1 ? idn_domain1:info->peer_verify_domain, +			  idn_domain2 ? idn_domain2:p); + +	if (idn_domain1) +		free(idn_domain1); +	if (idn_domain2) +		free(idn_domain2); +	return rc; +} + +static int verifypeer1(const struct tls_info *info, X509 *x, +		       STACK_OF(GENERAL_NAME) *subject_alt_names); + +static int verifypeer(const struct tls_info *info, SSL *ssl) +{ +	X509 *x=NULL; +	int rc; +	STACK_OF(GENERAL_NAME) *subject_alt_names; + +	if (!info->peer_verify_domain) +		return (1); + +	if (SSL_get_verify_result(ssl) != X509_V_OK) +		return (1); + +	x=SSL_get_peer_certificate(ssl); -			if (xx) -				subj=X509_get_subject_name(xx); +	if (!x) +		return (0); + +	subject_alt_names= +		X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + +	rc=verifypeer1(info, x, subject_alt_names); + +	if (subject_alt_names) +		GENERAL_NAMES_free(subject_alt_names); + +	X509_free(x); + +	return rc; +} + +static int verifypeer1(const struct tls_info *info, X509 *x, +		       STACK_OF(GENERAL_NAME) *subject_alt_names) +{ +	X509_NAME *subj=NULL; +	int nentries, j; +	char domain[256]; +char errmsg[1000]; + +	if(subject_alt_names) +	{ +		int n=sk_GENERAL_NAME_num(subject_alt_names); +		int i; + +		for (i=0; i<n; i++) +		{ +			const GENERAL_NAME *gn= +				sk_GENERAL_NAME_value(subject_alt_names, i); +			const char *str; +			int l; + +			if (gn->type != GEN_DNS) +				continue; + +#ifdef HAVE_OPENSSL110 +			str = (const char *)ASN1_STRING_get0_data(gn->d.ia5); +#else +			str = (const char *)ASN1_STRING_data(gn->d.ia5); +#endif +			l=ASN1_STRING_length(gn->d.ia5); + +			if (l >= sizeof(domain)-1) +				l=sizeof(domain)-1; + +			memcpy(domain, str, l); +			domain[l]=0; + +			if (hostmatch(info, domain)) +				return 1;  		}  	} +	subj=X509_get_subject_name(x);  	nentries=0;  	if (subj) @@ -216,23 +331,8 @@ static int verifypeer(const struct tls_info *info, SSL *ssl)  		}  	} -	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); +	if (domain[0] && hostmatch(info, domain)) +		return 1;  	strcpy(errmsg, "couriertls: Mismatched SSL certificate: CN=");  	strcat(errmsg, domain); @@ -242,11 +342,15 @@ static int verifypeer(const struct tls_info *info, SSL *ssl)  	(*info->tls_err_msg)(errmsg, info->app_data);  	return (0);  } +#endif  static void nonsslerror(const struct tls_info *info, const char *pfix)  {  	char errmsg[256]; +	if (errno == 0) +		return; +  	strcpy(errmsg, "couriertls: ");  	strncat(errmsg, pfix, 200);  	strcat(errmsg, ": "); @@ -332,10 +436,83 @@ static void load_dh_params(SSL_CTX *ctx, const char *filename,  		sslerror(info, filename, -1);  } +static int check_readable_file(const char *filename) +{ +	return (access(filename, R_OK) == 0) ? 1 : 0; +} + +#ifdef HAVE_OPENSSL_SNI +static char *get_servername_concated_readable_file(const char *filename, +				   const char *servername, +				   struct tls_info *info) +{ +	char *filename_buffer; +	char *p; + +	if (!filename || !servername) return NULL; +	if (!*filename || !*servername) return NULL; + +	filename_buffer=malloc(strlen(filename)+strlen(servername)+2); +	if (!filename_buffer) +	{ +		nonsslerror(info, "malloc"); +		exit(1); +	} + +	strcat(strcpy(filename_buffer, filename), "."); + +	p=filename_buffer + strlen(filename_buffer); + +	while ((*p=*servername) != 0) +	{ +		if (*p == '/') +			*p='.'; /* Script kiddie check */ +		++p; +		++servername; +	} +	if (check_readable_file(filename_buffer)) { +		return filename_buffer; +	} + +	free(filename_buffer); +	return NULL; +} +#endif + +static char *get_ip_concated_readable_file(SSL_CTX *ctx, const char *filename, const char *ip) +{ +	char *test_file; +	const struct tls_info *info=SSL_CTX_get_app_data(ctx); + +	if (!filename || !ip) return NULL; +	if (!*filename || !*ip) return NULL; + +	test_file= malloc(strlen(filename)+strlen(ip)+2); +	if (!test_file) +	{ +		nonsslerror(info, "malloc"); +		exit(1); +	} + +	strcpy(test_file, filename); +	strcat(test_file, "."); +	strcat(test_file, ip); + +	if (check_readable_file(test_file)) { +		return test_file; +	} + +	free(test_file); +	return NULL; +} +  static int read_certfile(SSL_CTX *ctx, const char *filename, +			      const char *private_key_filename,  			      int *cert_file_flags)  {  	const struct tls_info *info=SSL_CTX_get_app_data(ctx); +	const char *privatekey_file = (private_key_filename == NULL) +				    ? filename : private_key_filename;  	if(!SSL_CTX_use_certificate_chain_file(ctx, filename))  	{ @@ -345,7 +522,7 @@ static int read_certfile(SSL_CTX *ctx, const char *filename,  	load_dh_params(ctx, filename, cert_file_flags); -	if(!SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM)) +	if(!SSL_CTX_use_PrivateKey_file(ctx, privatekey_file, SSL_FILETYPE_PEM))  	{  		sslerror(info, filename, -1);  		return (0); @@ -353,36 +530,39 @@ static int read_certfile(SSL_CTX *ctx, const char *filename,  	return (1);  } -static int process_certfile(SSL_CTX *ctx, const char *certfile, const char *ip, +static int process_certfile(SSL_CTX *ctx, const char *certfile, +			    const char *private_key_file, +			    const char *ip,  			    int (*func)(SSL_CTX *, const char *, +					const char *,  					int *),  			    int *cert_file_flags)  {  	if (ip && *ip)  	{  		char *test_file; +		char *test_private_key_file;  		if (strncmp(ip, "::ffff:", 7) == 0 && strchr(ip, '.')) -			return (process_certfile(ctx, certfile, ip+7, func, cert_file_flags)); +			return (process_certfile(ctx, certfile, private_key_file, ip+7, func, cert_file_flags)); -		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) +		test_file= get_ip_concated_readable_file(ctx, certfile, ip); +		test_private_key_file = get_ip_concated_readable_file(ctx, private_key_file, ip); +		if (test_file != NULL)  		{ -			int rc= (*func)(ctx, test_file, +			int rc= (*func)(ctx, test_file, test_private_key_file,  					cert_file_flags);  			free(test_file); +			if (test_private_key_file) free(test_private_key_file); +  			return rc;  		} -		free(test_file); +		if (test_private_key_file) free(test_private_key_file);  	} -	return (*func)(ctx, certfile, cert_file_flags); +	private_key_file = check_readable_file(private_key_file) ? private_key_file : NULL; +	return (*func)(ctx, certfile, private_key_file, cert_file_flags);  }  static int client_cert_cb(ssl_handle ssl, X509 **x509, EVP_PKEY **pkey) @@ -502,33 +682,17 @@ static int server_cert_cb(ssl_handle ssl, int *ad, void *arg)  	const char *servername=SSL_get_servername(ssl,  						  TLSEXT_NAMETYPE_host_name);  	const char *certfile=safe_getenv(info, "TLS_CERTFILE"); +	const char *private_keyfile=safe_getenv(info, "TLS_PRIVATE_KEYFILE");  	int cert_file_flags=0; -	char *buffer; -	char *p; +	char *cert_file_buffer; +	char *private_keyfile_buffer;  	if (!servername || !certfile)  		return SSL_TLSEXT_ERR_OK; -	buffer=malloc(strlen(certfile)+strlen(servername)+2); -	if (!buffer) -	{ -		nonsslerror(info, "malloc"); -		exit(1); -	} - -	strcat(strcpy(buffer, certfile), "."); - -	p=buffer + strlen(buffer); - -	while ((*p=*servername) != 0) -	{ -		if (*p == '/') -			*p='.'; /* Script kiddie check */ -		++p; -		++servername; -	} - -	if (access(buffer, R_OK) == 0) +	cert_file_buffer = get_servername_concated_readable_file(certfile, servername, info); +	private_keyfile_buffer = get_servername_concated_readable_file(private_keyfile, servername, info); +	if (cert_file_buffer != NULL)  	{  		SSL_CTX *orig_ctx=SSL_get_SSL_CTX(ssl);  		SSL_CTX *temp_ctx=tls_create_int(1, info, 1); @@ -541,7 +705,7 @@ static int server_cert_cb(ssl_handle ssl, int *ad, void *arg)  			exit(1);  		}  		SSL_set_SSL_CTX(ssl, temp_ctx); -		rc=read_certfile(orig_ctx, buffer, &cert_file_flags); +		rc=read_certfile(orig_ctx, cert_file_buffer, private_keyfile_buffer, &cert_file_flags);  		SSL_set_SSL_CTX(ssl, orig_ctx);  		tls_destroy(temp_ctx);  		if (!rc) @@ -551,7 +715,8 @@ static int server_cert_cb(ssl_handle ssl, int *ad, void *arg)  			exit(1);  		}  	} -	free(buffer); +	if (cert_file_buffer) free(cert_file_buffer); +	if (private_keyfile_buffer) free(private_keyfile_buffer);  #endif  	return SSL_TLSEXT_ERR_OK; @@ -571,6 +736,7 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info,  	int session_timeout=atoi(safe_getenv(info, "TLS_TIMEOUT"));  	const char *dhparamsfile=safe_getenv(info, "TLS_DHPARAMS");  	const char *certfile=safe_getenv(info, "TLS_CERTFILE"); +	const char *private_keyfile=safe_getenv(info, "TLS_PRIVATE_KEYFILE");  	const char *s;  	struct stat stat_buf;  	const char *peer_cert_dir=NULL; @@ -588,6 +754,9 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info,  	if (!*certfile)  		certfile=NULL; +	if (!*private_keyfile) +		private_keyfile=NULL; +  	if (!*dhparamsfile)  		dhparamsfile=NULL; @@ -697,7 +866,7 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info,  	if (dhparamsfile)  		load_dh_params(ctx, dhparamsfile, &cert_file_flags); -	if (certfile && !process_certfile(ctx, certfile, s, +	if (certfile && !process_certfile(ctx, certfile, private_keyfile, s,  					  read_certfile,  					  &cert_file_flags))  	{ @@ -765,13 +934,11 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info,  					      "/"), de->d_name);  				fp=fopen(q, "r"); -				if (!fp) -				{ -					nonsslerror(info, q); -					exit(1); -				}  				free(q); +				if (!fp) +					continue; +  				while ((x=PEM_read_X509(fp, NULL, NULL, NULL)))  				{  					SSL_CTX_add_client_CA(ctx,x); @@ -1113,14 +1280,39 @@ SSL *tls_connect(SSL_CTX *ctx, int fd)  	}  	else  	{ +		char *idn_domain1=0; + +		if (info->peer_verify_domain) +		{ +			if (idna_to_unicode_8z8z(info->peer_verify_domain, +						 &idn_domain1, 0) +			    != IDNA_SUCCESS) +				idn_domain1=0; +		}  		SSL_set_connect_state(ssl);  #ifdef HAVE_OPENSSL_SNI  		if (info->peer_verify_domain)  		{ -			SSL_set_tlsext_host_name(ssl, info->peer_verify_domain); +			SSL_set_tlsext_host_name(ssl, +						 idn_domain1 ? idn_domain1 +						 : (char *) +						 info->peer_verify_domain); +		} +#endif +#if HAVE_X509_VERIFY_PARAM_SET1_HOST +		if (info->peer_verify_domain) +		{ +			X509_VERIFY_PARAM *param = SSL_get0_param(ssl); +			X509_VERIFY_PARAM_set1_host(param, +						    idn_domain1 ? +						    idn_domain1 : +						    info->peer_verify_domain, +						    0);  		}  #endif +		if (idn_domain1) +			free(idn_domain1);  		if ((rc=SSL_connect(ssl)) > 0)  		{ @@ -1173,6 +1365,8 @@ int	tls_transfer(struct tls_transfer_info *t, SSL *ssl, int fd,  	struct tls_info *info=SSL_get_app_data(ssl);  	int n; +	errno=0; +  	if (info->connect_interrupted)  	{  		n=SSL_connect(ssl); @@ -1411,6 +1605,8 @@ static void dump_x509(X509 *x509,  	X509_NAME *subj=X509_get_subject_name(x509);  	int nentries, j;  	time_t timestamp; +	STACK_OF(GENERAL_NAME) *subject_alt_names; +  	static const char gcc_shutup[]="%Y-%m-%d %H:%M:%S";  	if (!subj) @@ -1457,6 +1653,39 @@ static void dump_x509(X509 *x509,  	}  	(*dump_func)("\n", 1, dump_arg); +	subject_alt_names= +		X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL); + +	if (subject_alt_names) +	{ +		int n=sk_GENERAL_NAME_num(subject_alt_names); +		int i; + +		for (i=0; i<n; i++) +		{ +			const GENERAL_NAME *gn= +				sk_GENERAL_NAME_value(subject_alt_names, i); +			const char *domain; +			int l; + +			if (gn->type != GEN_DNS) +				continue; + +#ifdef HAVE_OPENSSL110 +			domain = (const char *)ASN1_STRING_get0_data(gn->d.ia5); +#else +			domain = (const char *)ASN1_STRING_data(gn->d.ia5); +#endif +			l=ASN1_STRING_length(gn->d.ia5); + +			(*dump_func)("Subject-Alt-Name-DNS: ", -1, dump_arg); +			(*dump_func)(domain, l, dump_arg); +			(*dump_func)("\n", -1, dump_arg); +		} + +		GENERAL_NAMES_free(subject_alt_names); +	} +  	timestamp=asn1toTime(X509_get_notBefore(x509));  	if (timestamp) diff --git a/tcpd/libcouriertls.h b/tcpd/libcouriertls.h index 17faabc..a45f910 100644 --- a/tcpd/libcouriertls.h +++ b/tcpd/libcouriertls.h @@ -325,6 +325,12 @@ TLS_CERTFILE is required for SSL/TLS servers, and is optional for SSL/TLS  clients.  TLS_CERTFILE is usually treated as confidential, and must not be  world-readable. +TLS_PRIVATE_KEYFILE - SSL/TLS private key for decrypting peer data. +By default, courier generates SSL/TLS certifice including private key +and install it in TLS_CERTFILE path, so TLS_PRIVATE_KEYFILE is completely +optional. If TLS_PRIVATE_KEYFILE is not set (default), TLS_CERTFILE is +treated as certificate including private key file. +  TLS_TRUSTCERTS=pathname - load trusted root certificates from pathname.  pathname can be a file or a directory. If a file, the file should  contain a list of trusted certificates, in PEM format. If a diff --git a/tcpd/starttls.c b/tcpd/starttls.c index cb3bd2c..e7b805f 100644 --- a/tcpd/starttls.c +++ b/tcpd/starttls.c @@ -542,11 +542,6 @@ static int dossl(int fd, int argn, int argc, char **argv)  	stdin_fd=0;  	stdout_fd=1; -	startclient(argn, argc, argv, fd, &stdin_fd, &stdout_fd); - -	if (username) -		libmail_changeusername(username, 0); -  	ctx=tls_create(server ? 1:0, &info);  	if (ctx == 0)	return (1); @@ -566,6 +561,11 @@ static int dossl(int fd, int argn, int argc, char **argv)  		return 1;  	} +	startclient(argn, argc, argv, fd, &stdin_fd, &stdout_fd); + +	if (username) +		libmail_changeusername(username, 0); +  	docopy(ssl, fd, stdin_fd, stdout_fd);  	tls_disconnect(ssl, fd); diff --git a/tcpd/tcpd.c b/tcpd/tcpd.c index 1d1947a..e263ea1 100644 --- a/tcpd/tcpd.c +++ b/tcpd/tcpd.c @@ -1,5 +1,5 @@  /* -** Copyright 1998 - 2013 Double Precision, Inc. +** Copyright 1998 - 2020 Double Precision, Inc.  ** See COPYING for distribution information.  */ @@ -1143,7 +1143,7 @@ static int doit(int argn, int argc, char **argv)  		if (select(maxfd+1, &fdr, NULL, NULL, NULL) <= 0)  		{  			if (errno != EINTR) -				perror("accept"); +				perror("select");  			continue;  		} @@ -1603,6 +1603,8 @@ static void docheckblocklist(struct blocklist_s *p, const char *nameptr)  	struct rfc1035_res res;  	unsigned int i;  	int found; +	unsigned char query_A[] = {RFC1035_TYPE_A, 0}, +		query_A_and_TXT[] = {RFC1035_TYPE_A, RFC1035_TYPE_TXT, 0};  	hostname[0]=0;  	strncat(hostname, nameptr, RFC1035_MAXNAMESIZE); @@ -1626,9 +1628,9 @@ static void docheckblocklist(struct blocklist_s *p, const char *nameptr)  	else  		wanttxt = p->msg && strcmp(p->msg, "*") == 0; -	(void)rfc1035_resolve_cname(&res, +	(void)rfc1035_resolve_cname_multiple(&res,  			hostname, -			wanttxt ? RFC1035_TYPE_TXT:RFC1035_TYPE_A, +			wanttxt ? query_A_and_TXT:query_A,  			RFC1035_CLASS_IN, &replyp, 0);  	if (!replyp) @@ -1670,7 +1672,8 @@ static void docheckblocklist(struct blocklist_s *p, const char *nameptr)  		if (p->allow)  			set_allow_variable(varname, p->msg); -		if (!search_txt_records(&res, p->allow, varname, replyp, +		if (replyp->next && +			!search_txt_records(&res, p->allow, varname, replyp->next,  					hostname) && !p->allow)  		{  			size_t l=strlen(p->zone)+40; @@ -1702,9 +1705,9 @@ static void docheckblocklist(struct blocklist_s *p, const char *nameptr)  	** for a specific IP address, then take what we've got.  	*/ -	if (p->ia.s_addr == INADDR_ANY && !found) +	if (p->ia.s_addr == INADDR_ANY && !found && replyp->next)  	{ -		if (search_txt_records(&res, p->allow, varname, replyp, +		if (search_txt_records(&res, p->allow, varname, replyp->next,  				       hostname))  		{  			/* diff --git a/tcpd/tlsclient.c b/tcpd/tlsclient.c index 7efa574..c7be1f3 100644 --- a/tcpd/tlsclient.c +++ b/tcpd/tlsclient.c @@ -379,6 +379,14 @@ static int do_couriertls_start(char **args, struct couriertls_info *cinfo)  		if (!s || !*s)  			s="couriertls"; +		if (cinfo->override_vars) +		{ +			size_t i; + +			for (i=0; cinfo->override_vars[i]; ++i) +				putenv(cinfo->override_vars[i]); +		} +  		execv(s, argvec);  		fprintf(fp, "500 Unable to start couriertls: %s\n",  			strerror(errno)); diff --git a/tcpd/tlsclient.h b/tcpd/tlsclient.h index b8eadbc..b624209 100644 --- a/tcpd/tlsclient.h +++ b/tcpd/tlsclient.h @@ -32,6 +32,8 @@ struct couriertls_info {  	size_t x509info_len;  	size_t x509info_size; +	char **override_vars; +  	struct tls_subject *first_subject;  	const char *cipher; | 
