summaryrefslogtreecommitdiffstats
path: root/tcpd
diff options
context:
space:
mode:
Diffstat (limited to 'tcpd')
-rw-r--r--tcpd/configure.ac10
-rw-r--r--tcpd/couriertcpd.sgml47
-rw-r--r--tcpd/couriertls.sgml20
-rw-r--r--tcpd/libcouriergnutls.c238
-rw-r--r--tcpd/libcouriertls.c385
-rw-r--r--tcpd/libcouriertls.h6
-rw-r--r--tcpd/starttls.c10
-rw-r--r--tcpd/tcpd.c17
-rw-r--r--tcpd/tlsclient.c8
-rw-r--r--tcpd/tlsclient.h2
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;