diff options
Diffstat (limited to 'tcpd')
| -rw-r--r-- | tcpd/.gitignore | 8 | ||||
| -rw-r--r-- | tcpd/Makefile.am | 95 | ||||
| -rw-r--r-- | tcpd/README.couriertls | 68 | ||||
| -rw-r--r-- | tcpd/argparse.c | 51 | ||||
| -rw-r--r-- | tcpd/argparse.h | 20 | ||||
| -rw-r--r-- | tcpd/configure.in | 546 | ||||
| -rw-r--r-- | tcpd/couriertcpd.sgml | 974 | ||||
| -rw-r--r-- | tcpd/couriertls.sgml | 274 | ||||
| -rw-r--r-- | tcpd/libcouriergnutls.c | 2089 | ||||
| -rw-r--r-- | tcpd/libcouriertls.c | 1560 | ||||
| -rw-r--r-- | tcpd/libcouriertls.h | 362 | ||||
| -rw-r--r-- | tcpd/spipe.c | 92 | ||||
| -rw-r--r-- | tcpd/spipe.h | 14 | ||||
| -rw-r--r-- | tcpd/starttls.c | 807 | ||||
| -rw-r--r-- | tcpd/tcpd.c | 2151 | ||||
| -rw-r--r-- | tcpd/tcpdaccess.c | 68 | ||||
| -rw-r--r-- | tcpd/tcpremoteinfo.c | 163 | ||||
| -rw-r--r-- | tcpd/tcpremoteinfo.h | 30 | ||||
| -rw-r--r-- | tcpd/testsuite | 38 | ||||
| -rw-r--r-- | tcpd/testsuite.txt | 128 | ||||
| -rw-r--r-- | tcpd/tlscache.c | 714 | ||||
| -rw-r--r-- | tcpd/tlscache.h | 81 | ||||
| -rw-r--r-- | tcpd/tlscachetest.c | 72 | ||||
| -rw-r--r-- | tcpd/tlsclient.c | 541 | ||||
| -rw-r--r-- | tcpd/tlsclient.h | 54 | ||||
| -rw-r--r-- | tcpd/tlsinfo.c | 32 | ||||
| -rw-r--r-- | tcpd/tlspasswordcache.c | 954 | ||||
| -rw-r--r-- | tcpd/tlspasswordcache.h | 51 | 
28 files changed, 12037 insertions, 0 deletions
| diff --git a/tcpd/.gitignore b/tcpd/.gitignore new file mode 100644 index 0000000..5b92120 --- /dev/null +++ b/tcpd/.gitignore @@ -0,0 +1,8 @@ +/couriertcpd +/couriertcpd.1 +/couriertcpd.html +/couriertls +/couriertls.1 +/couriertls.config +/couriertls.html +/tlscachetest diff --git a/tcpd/Makefile.am b/tcpd/Makefile.am new file mode 100644 index 0000000..81c405d --- /dev/null +++ b/tcpd/Makefile.am @@ -0,0 +1,95 @@ +# +# Copyright 1998 - 2008 Double Precision, Inc.  See COPYING for +# distribution information. + + +DISTCLEANFILES=couriertls.config + +noinst_PROGRAMS=@COURIERTCPD@ @STARTTLS@ tlscachetest +EXTRA_PROGRAMS=couriertls couriertcpd +EXTRA_LTLIBRARIES=libcouriertls.la libcouriertlsopenssl.la libcouriertlsgnutls.la + +BUILT_SOURCES=  couriertcpd.html couriertcpd.1 \ +		couriertls.html couriertls.1 + +EXTRA_DIST=$(BUILT_SOURCES) couriertls.html README.couriertls \ +	testsuite testsuite.txt + +noinst_LTLIBRARIES=libspipe.la @BUILDLIBCOURIERTLS@ @LIBCOURIERTLSOPENSSL@ @LIBCOURIERTLSGNUTLS@ libtlsclient.la + +libcouriertls_la_SOURCES=tlscache.c tlscache.h +libcouriertls_la_LIBADD=@TLSLIBRARY@ +libcouriertls_la_DEPENDENCIES=@TLSLIBRARY@ + +couriertcpd_SOURCES=argparse.c argparse.h \ +	tcpd.c tcpdaccess.c tcpremoteinfo.c tcpremoteinfo.h + +couriertcpd_DEPENDENCIES= libspipe.la \ +	../rfc1035/librfc1035.a \ +	@dblibrary@ \ +	../liblock/liblock.la\ +	../numlib/libnumlib.la\ +	../waitlib/libwaitlib.a\ +	../soxwrap/libsoxwrap.a\ +	../md5/libmd5.la ../random128/librandom128.la + +couriertcpd_t=@NETLIBS@ @soxdep@ + +couriertcpd_LDADD= libspipe.la \ +	../rfc1035/librfc1035.a \ +        @dblibrary@ \ +	../liblock/liblock.la\ +        ../numlib/libnumlib.la\ +        ../waitlib/libwaitlib.a\ +	../soxwrap/libsoxwrap.a\ +	../md5/libmd5.la ../random128/librandom128.la \ +	$(couriertcpd_t:%=-Wl,%) +couriertcpd_LDFLAGS=-static + + +tlscachetest_SOURCES=tlscachetest.c +tlscachetest_DEPENDENCIES=../numlib/libnumlib.la ../liblock/liblock.la +tlscachetest_LDADD=../numlib/libnumlib.la ../liblock/liblock.la +tlscachetest_LDFLAGS=-static + +libspipe_la_SOURCES=spipe.c spipe.h + +couriertls_SOURCES=starttls.c argparse.c argparse.h + +couriertls_t= @soxdep@ + +couriertls_DEPENDENCIES=libcouriertls.la libspipe.la ../rfc1035/librfc1035.a \ +	../md5/libmd5.la ../random128/librandom128.la \ +	../numlib/libnumlib.la ../liblock/liblock.la \ +	../soxwrap/libsoxwrap.a +couriertls_LDADD=libcouriertls.la libspipe.la ../rfc1035/librfc1035.a \ +	../md5/libmd5.la ../random128/librandom128.la \ +	../numlib/libnumlib.la ../liblock/liblock.la \ +	../soxwrap/libsoxwrap.a $(couriertls_t:%=-Wl,%) +couriertls_LDFLAGS=-static + +libcouriertlsopenssl_la_SOURCES=libcouriertls.c libcouriertls.h tlsinfo.c +libcouriertlsopenssl_la_LIBADD=@openssldep@ + +libcouriertlsgnutls_la_SOURCES=libcouriergnutls.c tlsinfo.c +libcouriertlsgnutls_la_LIBADD=@gnutlsdep@ + +libtlsclient_la_SOURCES=tlsclient.c tlsclient.h \ +	tlspasswordcache.c tlspasswordcache.h + +if HAVE_SGML +couriertcpd.html: couriertcpd.sgml ../docbook/sgml2html +	../docbook/sgml2html couriertcpd.sgml couriertcpd.html + +couriertcpd.1: couriertcpd.sgml ../docbook/sgml2html +	../docbook/sgml2man couriertcpd.sgml couriertcpd.1 + +couriertls.html: couriertls.sgml ../docbook/sgml2html +	../docbook/sgml2html couriertls.sgml couriertls.html + +couriertls.1: couriertls.sgml ../docbook/sgml2html +	../docbook/sgml2man couriertls.sgml couriertls.1 +endif + +check-am: +	sh $(srcdir)/testsuite 2>&1 | cmp -s - $(srcdir)/testsuite.txt diff --git a/tcpd/README.couriertls b/tcpd/README.couriertls new file mode 100644 index 0000000..21187f9 --- /dev/null +++ b/tcpd/README.couriertls @@ -0,0 +1,68 @@ +# couriertls uses the following variables to initialize SSL/TLS: +# +# WARNING: Peer certificate verification has NOT yet been tested.  Proceed +# at your own risk.  Only the basic SSL/TLS functionality is known to be +# working. Keep this in mind as you play with the following variables. +# +# TLS_PROTOCOL sets the protocol version.  The possible versions are: +# +# SSL2 - SSLv2 +# SSL3 - SSLv3 +# TLS1 - TLS1 + +TLS_PROTOCOL=TLS1 + +# TLS_CIPHER_LIST optionally sets the list of ciphers to be used by the +# OpenSSL library.  In most situations you can leave TLS_CIPHER_LIST +# undefined +# +# TLS_CIPHER_LIST="ALL:!ADH:RC4+RSA:+SSLv2:@STRENGTH" + +# TLS_TIMEOUT is currently not implemented, and reserved for future use. +# This is supposed to be an inactivity timeout, but its not yet implemented. + +# TLS_DHCERTFILE - PEM file that stores our Diffie-Hellman cipher pair. +# When OpenSSL is compiled to use Diffie-Hellman ciphers instead of RSA +# you must generate a DH pair that will be used.  In most situations the +# DH pair is to be treated as confidential, and the file specified by +# TLS_DHCERTFILE must not be world-readable. +# +# TLS_DHCERTFILE= + +# TLS_CERTFILE - certificate to use.  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_CERTFILE= + + +# TLS_PEERCERTDIR, TLS_OURCACERT - when it is required that all peer +# certificates are signed by a specific certificate authority, set +# TLS_OURCACERT to the name of the file containing the certificate authority +# root key, and set TLS_PEERCERTDIR to the name of the directory containing +# the allowed certificates. +# +# TLS_PEERCERTDIR= +# TLS_OURCACERT= + +# +# TLS_VERIFYPEER - how to verify peer certificates.  The possible values of +# this setting are: +# +# NONE - do not verify anything +# +# PEER - verify the peer certificate, if one's presented +# +# REQUIREPEER - require a peer certificate, fail if one's not presented +# +# SSL/TLS servers will usually set TLS_VERIFYPEER to NONE.  SSL/TLS clients +# will usually set TLS_VERIFYPEER to REQUIREPEER. +# +# TLS_VERIFYPEER=PEER + +# TLS_ALLOWSELFSIGNEDCERT - this is an alternative to clients using +# TLS_VERIFYPEER=NONE.  TLS_ALLOWSELFSIGNEDCERT ignores server certificates +# that are not signed by a recognized certificate authority.  This allows +# clients to simply verify that a server certificate is available. +# +# TLS_ALLOWSELFSIGNEDCERT=1 diff --git a/tcpd/argparse.c b/tcpd/argparse.c new file mode 100644 index 0000000..dcb56b4 --- /dev/null +++ b/tcpd/argparse.c @@ -0,0 +1,51 @@ +#include	"argparse.h" +#include	<string.h> +#include	<stdlib.h> +#include	<stdio.h> + +/* +** Copyright 2000 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + + +int argparse(int argc, char **argv, struct args *s) +{ +int	argn=1; +int	i; + +	while (argn < argc) +	{ +	const char *p; +	int	l=0; + +		if ( argv[argn][0] != '-')	break; +		if ( argv[argn][1] == 0) +		{ +			++argn; +			break; +		} +		for (i=0; s[i].name; i++) +		{ +			l=strlen(s[i].name); +			if (strncmp(s[i].name, argv[argn]+1, l) == 0 && +				(argv[argn][l+1] == 0 || +				argv[argn][l+1] == '='))	break; +		} +		if (s[i].name == 0) +		{ +			fprintf(stderr, "%s: Invalid option: %s\n", +				argv[0], argv[argn]); +			exit(1); +		} +		p=argv[argn]+1+l; +		if (*p)	++p; +		if (s[i].valuep) +			*s[i].valuep=p; +		else +			(*s[i].funcp)(p); +		++argn; +	} +	return (argn); +} diff --git a/tcpd/argparse.h b/tcpd/argparse.h new file mode 100644 index 0000000..6727108 --- /dev/null +++ b/tcpd/argparse.h @@ -0,0 +1,20 @@ +#ifndef	argparse_h +#define	argparse_h + +/* +** Copyright 2000 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +#include	"config.h" + +struct args { +	const char *name; +	const char **valuep; +	void (*funcp)(const char *); +	} ; + +int argparse(int argc, char **, struct args *); + +#endif diff --git a/tcpd/configure.in b/tcpd/configure.in new file mode 100644 index 0000000..3077c02 --- /dev/null +++ b/tcpd/configure.in @@ -0,0 +1,546 @@ +dnl Process this file with autoconf to produce a configure script. +dnl +dnl Copyright 1998 - 2008 Double Precision, Inc.  See COPYING for +dnl distribution information. + +AC_INIT(couriertcpd, 0.11, [courier-users@lists.sourceforge.net]) + +>confdefs.h  # Kill PACKAGE_ macros + +AC_CONFIG_SRCDIR(tcpd.c) +AC_CONFIG_AUX_DIR(../..) +AM_INIT_AUTOMAKE([foreign no-define]) +LPATH="$PATH:/usr/local/bin" +AM_CONFIG_HEADER(config.h) + +dnl Checks for programs. +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LN_S +AC_LIBTOOL_DLOPEN +AM_PROG_LIBTOOL + +SPATH="$LPATH:/usr/kerberos/bin" + +AC_PATH_PROG(SED, sed, sed, $SPATH) +if test "$SED" = "sed" +then +	AC_MSG_ERROR(sed not found.) +fi +AC_SUBST(SED) + +AC_PATH_PROG(SETENV, env, env, $SPATH) +if test "$SETENV" = "env" +then +	AC_MSG_ERROR(env not found.) +fi +AC_SUBST(SETENV) + +AC_PATH_PROG(PKGCONFIG, pkg-config, [], $SPATH) + +if test x$GCC = xyes +then +	CFLAGS="-Wall $CFLAGS" +fi +if test x$GXX = xyes +then +	CXXFLAGS="-Wall $CXXFLAGS" +fi + +CFLAGS="-I$srcdir/.. -I.. -I$srcdir/../.. -I../.. $CFLAGS" +CXXFLAGS="-I$srcdir/.. -I.. -I$srcdir/../.. -I../.. $CXXFLAGS" + +AC_PATH_PROG(KRB5CONFIG, krb5-config, krb5-config, $SPATH) + +# +# +# We may need libdl for later, not just shared check. +# + +saveLIBS="$LIBS" +AC_CHECK_LIB(dl, dlopen, [ LIBDL="-ldl" ]) +LIBS="$saveLIBS" + +dnl Determine whether transport libraries are created static or shared + +AC_SUBST(LIBDL) + +. ../../dbobj.config +if test "$dblibrary" != "" +then +	dblibrary="../../$dblibrary" +fi +AC_SUBST(dblibrary) + +COURIERTCPD="" +if test "$dblibrary" != "" +then +	COURIERTCPD=couriertcpd$EXEEXT +fi + +AC_SUBST(COURIERTCPD) + +LIBS="$saveLIBS" + +USENSL=no +saveLIBS="$LIBS" +AC_CHECK_LIB(socket,socket,result=yes,result=no) +if test $result = yes; then +        NETLIBS="-lsocket" +else +        AC_CHECK_LIB(socket,socket,result=yes,result=no,-lnsl) +        if test $result = yes; then +                NETLIBS = "-lsocket -lnsl" +                USENSL=yes +        else +                AC_CHECK_LIB(socket,connect,result=yes,result=no) +                if test $result = yes; then +                        NETLIBS="-lsocket" +                else +                        AC_CHECK_LIB(socket,connect,result=yes,result=no,-lnsl) +                        if test $result = yes; then +                                NETLIBS="-lsocket -lnsl" +                                USENSL=yes +                        fi +                fi +        fi +fi + +if test $USENSL != yes; then +	LIBS="$LIBS $NETLIBS" +	AC_TRY_LINK_FUNC(inet_addr, [ : ], +	[ +	        AC_CHECK_LIB(nsl,inet_addr,result=yes,result=no) +	        if test $result = yes; then +	                NETLIBS="$NETLIBS -lnsl" +	        fi +	]) +fi + +AC_SUBST(NETLIBS) + +dnl Checks for header files. +AC_HEADER_STDC +AC_HEADER_DIRENT +AC_CHECK_HEADERS(sys/types.h sys/time.h sys/stat.h sys/wait.h sys/select.h unistd.h fcntl.h sys/ioctl.h) +AC_HEADER_TIME + +dnl Checks for typedefs, structures, and compiler characteristics. + +AC_PID_T +AC_TYPE_UID_T +AC_TYPE_SIGNAL +AC_SYS_LARGEFILE + +AC_CACHE_CHECK([for socklen_t], +	tcpd_cv_hassocklen_t, +  +AC_COMPILE_IFELSE([ +AC_LANG_SOURCE( [ +#include <sys/types.h> +#include <sys/socket.h> +  +socklen_t sl_t; +],[ +	accept(0, 0, &sl_t); +])], +	tcpd_cv_hassocklen_t=yes, +	tcpd_cv_hassocklen_t=no) +) +  +socklen_t="int" +  +if test $tcpd_cv_hassocklen_t = yes +then +	: +else +	AC_DEFINE_UNQUOTED(socklen_t, int, [ Default definition for socklen_t ]) +fi + +dnl Checks for library functions. + +AC_CHECK_FUNCS(setpgrp setpgid) +AC_CHECK_FUNC(setpgrp, +	[ +	AC_FUNC_SETPGRP +	] +) + +AC_ARG_WITH(tcpddns, [  --without-tcpddns       Use resolver instead of DNS lookups in couriertcpd ], tcpddns="$withval", tcpddns="Y") + +case "$tcpddns" in +n*|N*|0*) +	;; +*) +	AC_DEFINE_UNQUOTED(TCPDUSERFC1035,1, +	[ Whether to use librfc1035.a to resolve via DNS ]) +	;; +esac + +saveLIBS="$LIBS" +LIBS="$LIBS $NETLIBS" +AC_CHECK_FUNC(socketpair, have_socketpair=yes, have_socketpair=no) + +AC_ARG_WITH(spipe, +[  --with-spipe=socketpair Create stream pipes as BSD socketpairs +  --with-spipe=streams    Create stream pipes as SVR3 streams +  --with-spipe=pipe       Create stream pipes as SVR4 streams], +spipe="$withval", spipe="") + +AC_CACHE_CHECK([for SVR3 stream pipes],tcpd_cv_svr3, +AC_TRY_COMPILE( [ +#include <sys/types.h> +#include <sys/stream.h> +#include <stropts.h> +#include <fcntl.h> +],[ { +        struct strfdinsert      ins; +        queue_t                 *pointer; +	int i=I_FDINSERT; +} +], tcpd_cv_svr3=yes, tcpd_cv_svr3=no)) + +AC_CACHE_CHECK( [for SVR4 stream pipes],tcpd_cv_svr4, + +AC_TRY_RUN( +[ + +changequote(<<,>>) + +#include	<stdio.h> +#include	<unistd.h> + +int main(int argc, char **argv) +{ +int	pipefd[2]; +char	c; + +	c=0; +	if (pipe(pipefd) == 0 && write(pipefd[0], &c, 1) == 1 && +		read(pipefd[1], &c, 1) == 1 && +		write(pipefd[1], &c, 1) == 1 && +		read(pipefd[0], &c, 1) == 1) +	{ +		exit (0); +	} +	exit (1); +	return (1); +} +changequote([,]) +] +, +tcpd_cv_svr4=yes, +tcpd_cv_svr4=no, +tcpd_cv_svr4="n/a") + +) + +LIBS="$saveLIBS" + +if test "$spipe" = "" +then +	if test "$have_socketpair" = "yes" +	then +		spipe="socketpair" +	fi +fi + +if test "$spipe" = "" +then +	if test "$tcpd_cv_svr3" = "yes" +	then +		spipe="streams" +	fi +fi + +if test "$spipe" = "" +then +	if test "$tcpd_cv_svr4" = "yes" +	then +		spipe="pipe" +	fi +fi + +case "$spipe" in +socketpair) +		AC_DEFINE_UNQUOTED(HAVE_SPIPE_SOCKETPAIR, 1, +		[ Whether to use socketpair() to create a bidirectional pipe ]) +		;; +streams) +		AC_DEFINE_UNQUOTED(HAVE_SPIPE_SVR3, 1, +		[ Whether to use streams to create a vidirectional pipe ]) +		;; +pipe) +		AC_DEFINE_UNQUOTED(HAVE_SPIPE_SVR4, 1, +		[ Whether pipe() is SvR4-style that's a bidirectional pipe ]) +		;; +*) +	AC_MSG_ERROR(Cannot determine stream pipe support.  If cross-compiling use --with-spipe=pipe to get SVR4 stream pipes.) +		;; +esac + + +AC_MSG_CHECKING([for stream pipes]) +AC_MSG_RESULT($spipe) + +AC_CHECK_HEADER(openssl/ssl.h,have_ssl_h=yes,have_ssl_h=no) +AC_CHECK_LIB(ssl, SSL_load_error_strings, have_ssl=yes, have_ssl=no, +	-lcrypto) + +if test "$have_ssl" = "yes" +then +	if test "$have_ssl_h" = "no" +	then +		AC_MSG_WARN(OpenSSL runtime libraries installed but the header files are missing) +		AC_MSG_WARN(disabling OpenSSL support.) +		have_ssl="no" +	fi +fi + +KRBFLAGS="" +if test "$have_ssl" = "yes" +then +	LIBCOURIERTLSOPENSSL=libcouriertlsopenssl.la + +	if test "$KRB5CONFIG" != "krb5-config" +	then +		AC_MSG_CHECKING(whether OpenSSL requires Kerberos) +		AC_TRY_COMPILE( [ +#include <openssl/ssl.h> +], [ int x=1; ], :, +		 [ +			KRBFLAGS=`$KRB5CONFIG --cflags` +			CFLAGS="$CFLAGS $KRBFLAGS" + + +			AC_TRY_COMPILE( [ +#include <openssl/ssl.h> +], [ int x=1; ], [ KRBLIBS="`$KRB5CONFIG --libs`" ], +   AC_MSG_ERROR(OpenSSL test build failed) ) +			] +) + +		if test "$KRBLIBS" = "" +		then +			AC_MSG_RESULT(no) +		else +			AC_MSG_RESULT(yes) +		fi +	fi +	openssldep="-lssl -lcrypto $KRBLIBS $NETLIBS ../random128/librandom128.la" +fi + +# GnuTLS + +have_gnutls=no + +AC_MSG_CHECKING([for GnuTLS]) + +if test "$PKGCONFIG" != "" +then +	if $PKGCONFIG --modversion gnutls >/dev/null 2>&1 +	then +		save_CPPFLAGS="$CPPFLAGS" +		CPPFLAGS="$CPPFLAGS `$PKGCONFIG --cflags gnutls`" + +		AC_TRY_COMPILE( [ +#include <gnutls/gnutls.h> +], +	[ +	], +		[ have_gnutls=yes +		], + +		[ +			have_gnutls="no: \#include <gnutls/gnutls.h> failed" +		]) + +		CPPFLAGS="$save_CPPFLAGS" +	else +		have_gnutls="no: pkgconfig --modversion gnutls failed" +	fi +else +	have_gnutls="no: pkg-config not found" +fi + +AC_MSG_RESULT($have_gnutls) + +if test "$have_gnutls" = "yes" +then +	LIBCOURIERTLSGNUTLS="libcouriertlsgnutls.la" +	CPPFLAGS="$CPPFLAGS `$PKGCONFIG --cflags gnutls`" +	have_gnutls3=no +	PKG_CHECK_EXISTS([gnutls >= 3.0], [have_gnutls3=yes]) +	if test "x$have_gnutls3" = "xno" +	then +		gnutlsdep="`$PKGCONFIG --libs gnutls` -lgnutls-extra" +	else +		gnutlsdep="`$PKGCONFIG --libs gnutls`" +		AC_DEFINE([HAVE_GNUTLS3], [1], [Use GnuTLS3]) +	fi +fi + +AC_CHECK_SIZEOF(gnutls_transport_ptr_t,0, [ +AC_INCLUDES_DEFAULT +#include <gnutls/gnutls.h> +]) + +AC_CHECK_SIZEOF(long,0) + +cast_to_ptr_t="" + +if test "$gnutls_transport_ptr_t_SIZEOF" = "$long_SIZEOF" +then +	if test "$gnutls_transport_ptr_t_SIZEOF" != 0 +	then +		cast_to_ptr_t="(long)" +	fi +fi + +AC_DEFINE_UNQUOTED(GNUTLS_CAST_PTR_T, $cast_to_ptr_t,[How to cast a file descriptor to gnutls_transport_ptr_t]) + +echo "cppflags='$KRBFLAGS'" >couriertls.config + +AC_SUBST(openssldep) +AC_SUBST(gnutlsdep) + +soxdep="`cat ../soxwrap/soxlibs.dep`" +AC_SUBST(soxdep) + + +# Choose GnuTLS or OpenSSL + +AC_ARG_WITH(gnutls, [ --with-gnutls     Use GnuTLS even if OpenSSL is available], +		    [ + +if test "$withval" = "yes" +then +	if test "$have_gnutls" = "yes" +	then +		have_ssl="no" +	else +		AC_MSG_ERROR(Cannot find GnuTLS) +	fi +else +	have_gnutls="no" +fi +], +	[ + +	if test "$have_ssl" = "yes" +	then +		have_gnutls="no" +	fi +]) + +if test "$have_gnutls" = "yes" +then +	TLSLIBRARY="$LIBCOURIERTLSGNUTLS" +	STARTTLS=couriertls$EXEEXT +	BUILDLIBCOURIERTLS=libcouriertls.la + +	CRYPTLIBS="" + +	AC_MSG_CHECKING([for libgcrypt]) + +	save_LIBS="$LIBS" +	LIBS="$LIBS -lgcrypt" +	AC_TRY_LINK( [ +#include <gcrypt.h> + +], [ +	gcry_cipher_open(NULL, 0, 0, 0); +], [ +	AC_MSG_RESULT(yes) +	AC_DEFINE_UNQUOTED(HAVE_GCRYPT, 1, +				[ Whether libgcrypt is installed ]) +	CRYPTLIBS="$CRYPTLIBS -lgcrypt" +], [ +AC_MSG_RESULT(no) +]) + +	AC_MSG_CHECKING([for libgpg-error]) +	LIBS="$LIBS -lgpg-error" +	AC_TRY_LINK( [ +#include <gcrypt.h> + +], [ +	gpg_err_code_from_errno(0); +], [ +	AC_MSG_RESULT(yes) +	CRYPTLIBS="$CRYPTLIBS -lgpg-error" +], [ +AC_MSG_RESULT(no) +]) + +	LIBS="$save_LIBS" + +	echo "ssllib=gnutls" >>couriertls.config +else +	if test "$have_ssl" = "yes" +	then + +		save_LIBS="$LIBS" +		LIBS="-lcrypto $KRBLIBS $LIBS" + +		AC_MSG_CHECKING(for OpenSSL 0.9.7) +		AC_TRY_LINK( [ +#include <openssl/evp.h> +#include <openssl/rand.h> +], +[ +EVP_CIPHER_CTX ctx; +char dummy[1]; +unsigned char a[1], b[1]; + +EVP_CIPHER_CTX_init(&ctx); +EVP_EncryptInit_ex(&ctx, EVP_des_cbc(), NULL, a, b); +RAND_pseudo_bytes(dummy, 1); + +], [ +   CRYPTLIBS="-lcrypto $KRBLIBS" +   AC_MSG_RESULT(yes) +   AC_DEFINE_UNQUOTED(HAVE_OPENSSL097, 1, +				       [ Whether OpenSSL 0.9.7 is installed ]) +], [ +   AC_MSG_RESULT(no) +] +) + +		LIBS="$save_LIBS" + + +		TLSLIBRARY="$LIBCOURIERTLSOPENSSL" +		STARTTLS=couriertls$EXEEXT +		BUILDLIBCOURIERTLS=libcouriertls.la +		echo "ssllib=openssl" >>couriertls.config +	fi +fi +echo "couriertls=$STARTTLS" >>couriertls.config +echo "cryptlibs=\"`echo $CRYPTLIBS`\"" >>couriertls.config + +# Do not built the other SSL library.  ... Except if I'm the maintainer + +if test ! -d "$srcdir/CVS" +then +	if test "$have_ssl" = "yes" +	then +		LIBCOURIERTLSGNUTLS="" +		fi + +	if test "$have_gnutls" = "yes" +	then +		LIBCOURIERTLSOPENSSL="" +	fi +fi + +AC_SUBST(LIBCOURIERTLSOPENSSL) +AC_SUBST(LIBCOURIERTLSGNUTLS) + +AC_SUBST(CRYPTLIBS) +AC_SUBST(STARTTLS) +AC_SUBST(BUILDLIBCOURIERTLS) +AC_SUBST(TLSLIBRARY) + +AM_CONDITIONAL(HAVE_SGML, test -d ${srcdir}/../docbook) +AC_OUTPUT(Makefile) diff --git a/tcpd/couriertcpd.sgml b/tcpd/couriertcpd.sgml new file mode 100644 index 0000000..b62a616 --- /dev/null +++ b/tcpd/couriertcpd.sgml @@ -0,0 +1,974 @@ +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<!-- Copyright 2000-2009 Double Precision, Inc.  See COPYING for --> +<!-- distribution information. --> +<refentry> +  <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info> + +  <refmeta> +    <refentrytitle>couriertcpd</refentrytitle> +    <manvolnum>1</manvolnum> +    <refmiscinfo>Double Precision, Inc.</refmiscinfo> +  </refmeta> + +  <refnamediv> +    <refname>couriertcpd</refname> +    <refpurpose>the <application>Courier</application> mail server +TCP server daemon</refpurpose> +  </refnamediv> + +  <refsynopsisdiv> +    <cmdsynopsis sepchar=" "> +      <command>couriertcpd</command> +      <arg choice="opt" rep="norepeat">-pid=<replaceable>pidfile</replaceable></arg> +      <arg rep="repeat" choice="opt"><replaceable>option</replaceable></arg> +      <arg choice="req" rep="norepeat"><replaceable>list</replaceable></arg> +      <arg choice="req" rep="norepeat"><replaceable>program</replaceable></arg> +      <arg choice="req" rep="repeat"><replaceable>arg</replaceable></arg> +    </cmdsynopsis> + +    <cmdsynopsis sepchar=" "> +      <command>couriertcpd</command> +      <arg choice="req" rep="norepeat">-pid=<replaceable>pidfile</replaceable></arg> +      <arg choice="req" rep="norepeat">-stop</arg> +    </cmdsynopsis> + +    <cmdsynopsis sepchar=" "> +      <command>couriertcpd</command> +      <arg choice="req" rep="norepeat">-pid=<replaceable>pidfile</replaceable></arg> +      <arg choice="req" rep="norepeat">-restart</arg> +    </cmdsynopsis> +  </refsynopsisdiv> + +  <refsect1> +    <title>DESCRIPTION</title> + +    <para> +<command>couriertcpd</command> accepts incoming network connections, and runs +<command>program</command> after establishing each network connection. The +<command>program</command>'s standard input and output are set to the network +connection.</para> + +    <para> +<replaceable>list</replaceable> is a comma-separated list of TCP port numbers +where incoming +connections are created. <command>program</command> is the program to +run. If <command>program</command> requires any +arguments, they are specified on the command line, after +<command>program</command> itself.</para> + +    <para> +Before running <command>program</command>, <command>couriertcpd</command> +initializes +several environment variables that describe the network connection. The +environment inherited by <command>program</command> will be the environment +inherited by <command>couriertcpd</command>, plus any additional environment +variables initialized by <command>couriertcpd</command>. It is also possible to +reject certain network connections. Several options are available to specify +which network connections will be rejected.</para> + +  </refsect1> + +  <refsect1> +    <title>OPTIONS</title> + +    <variablelist> +      <varlistentry> +	<term>-access=<replaceable>filename</replaceable></term> +	<listitem> +	  <para> +Specifies an optional access +file. The access file lists the IP addresses from which connections +should be accepted or rejected. The access file is also used to +initialize environment variables based on the IP address of the +connection. <replaceable>filename</replaceable> is a GDBM or DB database file +that's usually +created by a script from one or more text files. See "ACCESS FILE" below for +more information.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-accesslocal</term> +	<listitem> +	  <para> +Lookup the local interface IP and port in the access file, in addition to +looking up the remote IP. This gives a mechanism for setting environment +variables depending on which IP address and/or port the client connected to. +In the access file, "1.2.3.4.25" matches connections to IP address 1.2.3.4 +port 25; "1.2.3.4" matches connections to IP address 1.2.3.4 on any port; +and "*.25" matches connections to port 25 on any IP address.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-address=<replaceable>n.n.n.n</replaceable></term> +	<listitem> +	  <para> +Accept network connections only to IP address +<replaceable>n.n.n.n</replaceable>. If not specified, +<command>couriertcpd</command> +accepts connections to any IP address that the system accepts connections +on. If the system has multiple network interfaces with separate IP +addresses, this option makes <command>couriertcpd</command> accept connections +only to one specific IP address. Most systems have multiple network +interfaces: the loopback interface, plus the local network interface, so +that <literal>-address=127.0.0.1</literal> accepts connections only from the +local system. When multiple port numbers are specified, it is also +possible to selectively bind different network addresses to each port +number when <replaceable>list</replaceable> specifies more than one port +number. See "<ulink url="#list">Multiple port list</ulink>" below for more +information.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-block=<replaceable>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> +	<listitem> +	  <para> +Initialize the environment variable <replaceable>var</replaceable> if both of +the following +conditions are true: <replaceable>var</replaceable> is not already initialized; +the connecting IP address can be found in a DNS-based access list. See +DNS ACCESS LISTS, below. +Multiple <option>-block</option> and +<option>-allow</option> options can be specified.</para> + +	  <para> +	    <option>-block</option> and <option>-allow</option> are very +	    similar, differing only in minor semantics. +	    <option>-block</option>'s semantics are more appropriate for +	    using DNS access list to block access, and +	    <option>-allow</option>'s semantics are more appropriate for +	    using DNS access list to whitelist IP addresses and exempt them +	    even if they appear in other +	    <option>-block</option>ed zones. +	  </para> +	</listitem> +      </varlistentry> + +      <varlistentry> +        <term>-denymsg=<replaceable>text</replaceable></term> +        <listitem> +           <para> +Specifies an optional message to be returned to the client if the +<parameter>-access</parameter> option rejects them. +The default is to drop the TCP +connection without sending back any messages.</para> +        </listitem> +      </varlistentry> + +      <varlistentry> +	<term>-drop=<replaceable>var</replaceable></term> +	<listitem> +	  <para> +	    If the environment variable <replaceable>var</replaceable> is set to +	    a nonempty value, terminate immediately. Do not run the +	    <command>program</command> to handle the connection. +	    See DNS ACCESS LISTS, below, for more information. +	    <replaceable>var</replaceable> defaults to +	    <quote>BLOCK</quote>, if not specified. +	  </para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-group=<replaceable>group</replaceable></term> +	<listitem> +	  <para> +Set <command>couriertcpd</command>'s its +group ID. <replaceable>group</replaceable> may be specified numerically, or by +its name. Only the superuser may use <option>-group</option>.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-listen=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +Length of the queue which holds pending connections. +<replaceable>n</replaceable> is a number. If not specified, the system default +is used.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-maxperc=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +Maximum number of connections accepted +from the same C network block. Using this option is recommended, because +connection slots are limited. Without this option, the same C network +block can potentially use up all available connection slots.</para> +	</listitem> +      </varlistentry> + +  <varlistentry> +	<term>-maxperip=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +Maximum number of connections +accepted from the same IP address.  Use both the <option>-maxperc</option> +and <option>-maxperip</option> options to fine tune connection limits. For +example, when <command>couriertcpd</command> is listening on the SMTP port it +makes sense to set an upper limit on the number of connections from the +same C block. Domains that send a large amount of mail often have +multiple servers sending outbound mail from the same C block, so it makes +sense to set limits on individual C blocks. On the other hand, if +<command>couriertcpd</command> is listening on the POP3 port it makes more +sense to set limits on individual IP addresses.  If a C block of +addresses is assigned to a dialup modem pool, it is certainly possible to +have many IP addresses within the same C block have connections to the +POP3 server at the same time.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-maxprocs=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +Maximum number of connection slots, +or the maximum number of processes started. This effectively specifies +the maximum number of connections accepted at the same time. After the +maximum number of connections has been opened, <command>couriertcpd</command> +waits for an existing connection to close, before accepting any more +connections.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-warn=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +Log a <errorcode>LOG_WARNING</errorcode> message to +syslog when the number of active processes exceeds +<replaceable>n</replaceable>.  The default is 90% of +<replaceable>maxprocs</replaceable>. <command>couriertcpd</command> logs a +<errorcode>LOG_ALERT</errorcode> syslog message when the number of active +processes +reaches the maximum.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-nodnslookup</term> +	<listitem> +	  <para> +Do not look up the hostname associated with connecting IP address and the +local addres, do not initialize the +<envar>TCPREMOTEHOST</envar> or <envar>TCPLOCALHOST</envar> environment +variables (see below).</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-noidentlookup</term> +	<listitem> +	  <para> +Do not perform an <emphasis>ident</emphasis> +lookup, and do not initialize the <envar>TCPREMOTEINFO</envar> environment +variable.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-pid=<replaceable>filename</replaceable></term> +	<listitem> +	  <para> +If given, <command>couriertcpd</command> puts itself into the background +and saves its process ID in this file, usually +somewhere in <filename>/var/run</filename>.</para> +<para>This option must also be present when using the <option>-restart</option> +and <option>-stop</option> options.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-restart</term> +	<listitem> +	  <para> +Send a SIGHUP to an existing <command>couriertcpd</command> process.  Specify +the same <option>-pid</option> +argument as the one that was used to start <command>couriertcpd</command>. The +process ID is read from the <option>-pid</option> file, and the +<command>couriertcpd</command> receives a SIGHUP signal.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-stderr=socket</term> +	<listitem> +	  <para> +Set <command>program</command>'s standard error to +the network connection, just like its standard input and output.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-stderr=<replaceable>logfile</replaceable></term> +	<listitem> +	  <para> +Set <command>program</command>'s standard +error to the specified file, <filename>logfile</filename>. +The file is created, if necessary, and is opened in append mode.</para> +	</listitem> +      </varlistentry> + + +      <varlistentry> +	<term>-stderrlogger=<replaceable>logprogram</replaceable></term> +	<listitem> +	  <para> +Set <command>program</command>'s +standard error to a pipe, which is read by <command>logprogram</command>. +Only one instance of +<replaceable>logger</replaceable> is started, which receives standard error +from every +instance of <command>program</command>. +The specified <replaceable>logger</replaceable> is executed with +the output end of the stderr pipe connected as standard input. +<replaceable>logprogram</replaceable> is +executed with one argument - <command>program</command>'s name.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-stderrloggername=name</term> +	<listitem> +	  <para> +Use <replaceable>name</replaceable> as the argument to +<replaceable>logprogram</replaceable>, instead of the +<command>program</command>'s name.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-stop</term> +	<listitem> +	  <para> +Stop (kill) an existing <command>couriertcpd</command> +process.  Specify the same <option>-pid</option> argument as the one that was +used to start <command>couriertcpd</command>. The process ID is read from the +<option>-pid</option> file, and the <command>couriertcpd</command> process is +killed. All child processes of <command>couriertcpd</command> will receive a +SIGTERM signal.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-user=<replaceable>user</replaceable></term> +	<listitem> +	  <para> +Set <command>couriertcpd</command>'s user +ID. Also, the group ID is set to the user's group ID. Using both +<option>-group</option> and <option>-user</option> is not necessary. Only the +superuser can specify <option>-user</option>.</para> +	</listitem> +      </varlistentry> +    </variablelist> + +  </refsect1> + +  <refsect1> + +    <title>MULTIPLE PORT LIST</title> + +    <anchor id="list"/> + +    <para> +The <replaceable>list</replaceable> argument can be a comma-separated list of +multiple port +numbers. <command>couriertcpd</command> will create network connections on any +listed port. Each port number can be optionally specified as "address.port", +for example:</para> + +    <informalexample> +      <programlisting format="linespecific"> +couriertcpd -pid=/var/run/smtp.pid 127.0.0.1.25,999 <replaceable>program</replaceable> +</programlisting> +    </informalexample> + +<para> +This instance accepts network connections to either port 25 or port 999, +however connections on port 25 are created only on the IP address 127.0.0.1, +the loopback interface.</para> + +    <para>Whenever an IP address is not specified, network connections are +accepted +to any IP address (called "wildcarding"). On IPv6-capable systems, +<command>couriertcpd</command> will attempt to create two incoming network +connection ports, if an IP address is not specified. After creating the first +port as an IPv6 wildcard port, couriertcpd will then attept to create an IPv4 +wildcard port, with the same port number. Some BSD-derived systems must use +separate IPv6 and IPv4 wildcard ports to create incoming network connections. +Most other systems only need an IPv6 port to create both IPv6 and IPv4 +incoming network connections. <command>couriertcpd</command> quietly ignores a +failure to create an IPv4 wildcard port, as long as an IPv6 wildcard was +succesfully created.</para> + +    <para> +The <option>-address</option> option can be used to default a specific IP +address for every listed port number.  For example:</para> + +    <informalexample> +      <programlisting format="linespecific"> +couriertcpd -pid=/var/run/smtp.pid 127.0.0.1.25,127.0.0.1.999 <replaceable>program</replaceable> +</programlisting> +    </informalexample> + +<para> +and</para> + +    <informalexample> +      <programlisting format="linespecific"> +couriertcpd -pid=/var/run/smtp.pid -address=127.0.0.1 25,999 <replaceable>program</replaceable> +</programlisting> +    </informalexample> + +<para> +will create network connections on ports 25 and 999 of the IP address +127.0.0.1.</para> + +  </refsect1> + +  <refsect1> + +    <title>ACCESS FILE</title> + +    <para> +The access file lists IP addresses that <command>couriertcpd</command> will +accept or reject connections from. An access file is optional. Without an +access file <command>couriertcpd</command> accepts a connection from any IP +address.</para> + +    <para> +Both IPv4 and IPv6 addresses can be specified, if IPv6 support is +available. A non-standard syntax is currently used to specify IPv6 addresses. +This is subject to change in the near future. IPv6 support is currently +considered to be experimental.</para> + +    <para> +The access file is a binary database file that's usually created by a +script, such as +<ulink url="makesmtpaccess.html"><citerefentry><refentrytitle>makesmtpaccess</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>, +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> +      <title>Rejecting and accepting connections by IP address</title> + +      <para> +The following line instructs <command>couriertcpd</command> to reject all +connections from an IP address range:</para> + +      <informalexample> +	<programlisting format="linespecific"> +netblock<tab>deny +</programlisting> +      </informalexample> + +      <para><replaceable>netblock</replaceable> is an IP address, such as +<literal>192.68.0.2</literal>. <token><tab></token> +is the ASCII tab character. There MUST be exactly one tab character after the +IP address and the word "deny".</para> + +      <para> +You can also block connections from an entire network C block:</para> + +      <informalexample> +	<programlisting format="linespecific"> +192.68.0<tab>deny +</programlisting> +      </informalexample> + +      <para> +This blocks connections from IP addresses <literal>192.68.0.0</literal> +through <literal>192.68.0.255</literal>. +Blocking connections from an entire B or A network block works the same +way.</para> + +<para> +Use the word "<literal>allow</literal>" instead of "<literal>deny</literal>" +to explicitly allow connections +from that IP address or netblock. For example:</para> + +      <informalexample> +	<programlisting format="linespecific"> +192.68.0<tab>deny +192.68.0.10<tab>allow +</programlisting> +      </informalexample> + +      <para> +This blocks all connections from <literal>192.68.0.0</literal> to +<literal>192.68.0.255</literal> except for <literal>192.68.0.10</literal>. +These two lines can occur in any order. <command>couriertcpd</command> +always uses the line with the most specific IP address.</para> + +      <para> +If the IP address of the connection is not found in the access file the +connection is accepted by default. The following line causes unlisted +connections to be rejected:</para> + +      <informalexample> +	<programlisting format="linespecific"> +*<tab>deny +</programlisting> +      </informalexample> + +    </refsect2> + +    <refsect2> +      <title>IPv6 addresses</title> + +      <note> +	<para> +IPv6 support in the access file is experimental, and is subject to +change in a future release.  The following syntax is subject to change at any +time.</para> +      </note> + +      <para> +The access file can also specify IPv6 addresses, if IPv6 support is +available.  The existing IPv4 address format is used for IPv6-mapped IPv4 +addresses, and no changes are required.  For all other IPv6 addresses use the +following format:</para> + +      <informalexample> +	<programlisting format="linespecific"> +:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh<tab><replaceable>action</replaceable> +</programlisting> +      </informalexample> + +<para> +The IPv6 address must begin with :. The initial : character is not really +a part of the IPv6 address, it is only used to designate this record as an +IPv6 address, allowing an access file to contain a mixture of IPv4 and IPv6 +addresses. The IPv6 address follows the initial : character, and it must be +spelled out <emphasis>using zero-padded lowercase hexadecimal +digits</emphasis>. +For example:</para> + +      <informalexample> +	<programlisting format="linespecific"> +:0000:0000:0000:0000:0000:f643:00a2:9354<tab>deny +</programlisting> +      </informalexample> + +      <para> +Netblocks must be specified using even-word boundaries only:</para> + +      <informalexample> +	<programlisting format="linespecific"> +:3ffe<tab>deny +</programlisting> +      </informalexample> + +      <para> +This will deny entire 3ffe::/16 (6bone network, which is phased out). +</para> + +      <informalexample> +	<programlisting format="linespecific"> +:2002:c0a8<tab>deny +</programlisting> +      </informalexample> + +      <para> +This will deny 2002:c0a8::/32 (6to4 addresses derived from private +address space).</para> + +    </refsect2> + +    <refsect2> +      <title>Setting environment variables</title> + +      <para> +<literal>allow</literal> can be optionally followed by a list of environment +variable +assignments, separated by commas. The environment variables are set before +executing <command>program</command> or checking +access lists (see below). For example:</para> + +      <informalexample> +	<programlisting format="linespecific"> +192.68.0<tab>allow,RELAYCLIENT +192.68.0.10<tab>allow,RELAYCLIENT,SIZELIMIT=1000000 +</programlisting> +      </informalexample> + +      <para> +This sets <envar>RELAYCLIENT</envar> environment variable for connections +from the <literal>192.68.0</literal> block. In addition to that, the <envar>SIZELIMIT</envar> +environment variable is set to <literal>1000000</literal> if the connection comes from the IP +address <literal>192.68.0.10</literal>.</para> + +      <para> +Note that <envar>RELAYCLIENT</envar> must be explicitly specified for the IP +address <literal>192.68.0.10</literal>. The first line is NOT used for +connections from this IP +address. <command>couriertcpd</command> only reads one entry from the access +file, the entry for the most specific IP address.</para> + +    </refsect2> + +    <refsect2> + +      <title>DNS ACCESS LISTS</title> + +      <para> +	An alternative to listing banned IP addresses in access files +	is to use an external DNS-based IP access list. +      </para> + +      <para> +	There is no provision to support IPv6-based lists, because none yet +	exist. IPv6-based access list support will be added in the +	future. +      </para> + +      <para> +	<command>couriertcpd</command>'s default configuration +	does not automatically reject connections from banned IP address +	unless the <option>-drop</option> option is present. +	Instead, +	<command>couriertcpd</command> sets an environment variable +	if the connecting address has a hit in the DNS access list. +	The +	<application>Courier</application> +	mail server rejects all mail if the connection's environment has +	the environment variable <envar>BLOCK</envar> set to a non-empty +	string, and it just so happens that +	<option>-block</option> and <option>-allow</option> set the +	<envar>BLOCK</envar> environment variable by default. +      </para> + +      <blockquote> +	<informalexample> +	  <programlisting> +-allow=dnswl.example.com -block=dnsbl.example.com</programlisting> +	</informalexample> +      </blockquote> + +      <para> +	<option>-allow</option> and <option>-block</option>'s parameter gives +	the DNS zone where the access list query gets performed. +	In this example, +	<command>couriertcpd</command> makes a DNS query for +	<quote>d.c.b.a.dnswl.example.com</quote>, then, if necessary, for +	<quote>d.c.b.a.dnsbl.example.com</quote>, for a connection from the +	IP address <replaceable>a.b.c.d</replaceable>. +      </para> + +      <para> +	If the DNS query succeeds (more details below), +	<option>-allow</option> sets the environment variable to an empty +	string, and <option>-block</option> sets the environment variable +	from the <literal>TXT</literal> record in the DNS response, or to +	<quote>Access denied.</quote> if the DNS access list did not return +	a <literal>TXT</literal> record. It should be possible to use +	<command>couriertcpd</command> with DNS access lists that use either +	<literal>A</literal> or <literal>TXT</literal> records. +      </para> + +      <para> +	The DNS zone parameter to <option>-allow</option> and +	<option>-block</option> has up to three additional components, +	which must be given in the following order, if more than one optional +	component gets specified: +      </para> + +      <blockquote> +	<informalexample> +	  <programlisting> +-allow=dnswl.example.com,BLOCK2</programlisting> +	</informalexample> +      </blockquote> + +      <para> +	The environment variable that gets set by the DNS access list query +	can be changed from the default of <envar>BLOCK</envar> to something +	else, <envar>BLOCK2</envar> in this example. +	The <application>Courier</application> mail server pays attention +	only to <envar>BLOCK</envar>, this is for the benefit of local or +	custom hacks, which want to leverage <command>couriertcpd</command>'s +	DNS access list lookup facilities, but want it for other purposes. +      </para> + +      <blockquote> +	<informalexample> +	  <programlisting> +-block=dnsbl.example.com/127.0.0.2</programlisting> +	</informalexample> +      </blockquote> + +      <para> +	<command>couriertcpd</command>'s DNS access list lookup normally +	ignores the contents of the actual <literal>A</literal> record in +	the DNS access list, however some DNS access lists may use different +	<literal>A</literal> record to indicate different kinds of records. +	Given an explicit IP address to <command>couriertcpd</command> +	results in the environment variable getting set only if the +	lookup returned the matching <literal>A</literal> record. +	An <literal>A</literal> record must exist in the DNS access list, in +	addition to any <literal>TXT</literal> record. If an explicit IP +	address is not given, any <literal>A</literal> or <literal>TXT</literal> +	record sets +	<option>-allow</option> +	and +	<option>-block</option>'s +	environment variable. +      </para> + +      <blockquote> +	<informalexample> +	  <programlisting> +-block=dnsbl.example.com,BLOCK,Go away</programlisting> +	</informalexample> +      </blockquote> + +      <para> +	The last component specifies a custom message that overrides any +	<literal>TXT</literal> record in the DNS access list. +	Note that this is a single parameter to +	<application>couriertcpd</application>, so the parameter must be +	quoted if it contains any spaces or special +	shell metacharacters. +      </para> + +      <para> +	The custom message parameter gets specified for the +	<option>-block</option>, option. +	<option>-allow</option> also allows takes this parameter, but it +	has a different meaning. If its set, even if it's an empty string, +	<command>couriertcpd</command> looks for +	<literal>TXT</literal> records in the DNS access list that's +	used as a whitelist, in addition to the <literal>A</literal> +	records (using the <quote>any</quote> query): +      </para> + +      <blockquote> +	<informalexample> +	  <programlisting> +-allow=dnswl.example.com,BLOCK,</programlisting> +	</informalexample> +      </blockquote> + +      <para> +	Without this parameter <command>couriertcpd</command> +	queries for <literal>A</literal> records only. +      </para> + +      <para> +	Finally, +	a literal IP address, if given, must always follow the variable name: +      </para> + +      <blockquote> +	<informalexample> +	  <programlisting> +-block=dnsbl.example.com,BLOCK/127.0.0.2,Go away</programlisting> +	</informalexample> +      </blockquote> + +      <para> +	<option>-block</option> normally searches the DNS access list for either +	<literal>A</literal> or <literal>TXT</literal> records using the +	<quote>any</quote> DNS query. Sometimes this can cause problems, or +	not work at all, with older DNS servers. Specifying a custom message +	results in <option>-block</option> executing an ordinary +	<literal>A</literal> DNS query. +	<option>-allow</option> always uses an <literal>A</literal> query. +      </para> +    </refsect2> + +    <refsect2> +      <title>MULTIPLE DNS LISTS</title> + +      <para> +	Multiple <option>-block</option> +	and <option>-allow</option> +	options can be given. The connecting IP address +	gets looked up in multiple access lists. This is implemented as +	follows.</para> + +      <para> +	<command>couriertcpd</command> processes all +	<option>-block</option> +	and <option>-allow</option> options in list order. +	If each option's environment variable +	(<envar>BLOCK</envar> or something else) is already set, +	<command>couriertcpd</command> skips the DNS access list lookup. +	Therefore, when multiple options use the same environment variable, +	the first DNS access list it exists in will set the environment +	variable, and the remaining ones get ignored, but any remaining +	<option>-block</option>s +	and <option>-allow</option>s for different environment variables still +	get processed. +      </para> + +      <para> +	It follows that, in general, <option>-allow</option> options should +	always be listed first, before any <option>-block</option>s; but it's +	also possible to implement a complicated policy with some +	<option>-allow</option>s, then some +	<option>-block</option>s, then more +	<option>-allow</option>s and +	<option>-block</option>s. +      </para> +    </refsect2> + +    <refsect2> +      <title>ADDITIONAL DNS ACCESS LIST VARIABLES</title> + +      <para> +	Three additional environment variables may get set in conjunction with +	a successful DNS access list lookup: +      </para> + +      <variablelist> +	<varlistentry> +	  <term>BLOCK_IP</term> +	  <para> +	    The contents of the <literal>A</literal> record in the DNS +	    access list, if one exists (this is not set for DNS access lists +	    that use TXT record). +	  </para> +	</varlistentry> + +	<varlistentry> +	  <term>BLOCK_TXT</term> +	  <para> +	    The contents of the <literal>TXT</literal> record in the DNS +	    access list, if one exists. This will generally be the same as +	    <envar>BLOCK</envar> for <option>-block</option>s, but will +	    also provide the contents of the <literal>TXT</literal> record +	    for <option>-allow</option>s (if it has a dummy custom message +	    portion) which always set +	    <envar>BLOCK</envar> to an empty string. +	  </para> +	</varlistentry> + +	<varlistentry> +	  <term>BLOCK_ZONE</term> +	  <para> +	    The DNS zone of the succesfull access list lookup, like +	    <quote>dnsbl.example.com</quote>. +	  </para> +	</varlistentry> +      </variablelist> + +      <para> +	<option>-block</option> and +	<option>-allow</option> options that specify a custom environment +	variable name follow the same naming convention, of appending +	<quote>_IP</quote>, <quote>_TXT</quote>, and <quote>_ZONE</quote> +	suffix to the name of the custom environment variable. +      </para> +    </refsect2> + +    <refsect2> +      <title>USING DNS WHITELISTS WITH SPF</title> + +      <para> +	Including <quote>allowok</quote> keyword in an SPF setting automatically +	passes the SPF check for senders whose IP address is found in +	an <option>-allow</option>-ed access list. +	See +	<ulink url="courier.html"><citerefentry> +	    <refentrytitle>courier</refentrytitle> +	    <manvolnum>8</manvolnum> +	  </citerefentry> +	</ulink>. +      </para> +    </refsect2> +  </refsect1> + +  <refsect1> +    <title>ENVIRONMENT VARIABLES</title> + +    <para> +<command>couriertcpd</command> also initializes the following environment +variables prior to running <command>program</command>:</para> +    <variablelist> +      <varlistentry> +	<term>TCPLOCALHOST</term> +	<listitem> +	  <para> +The name of the host on the local end of +the network connection, looked up in DNS. <envar>TCPLOCALHOST</envar> will +not be set if the IP address of the network connection's local end cannot +be found in DNS, or if <option>-nodnslookup</option> option is specified. +<envar>TCPLOCALHOST</envar> will be set to the string +<errorcode>softdnserr</errorcode> if the DNS lookup fails with a temporary +error +(so you cannot tell if the IP address has a valid host name associated +with it), or if the reverse and forward DNS lookups do not match. +<envar>TCPLOCALHOST</envar> will not be set if the reverse DNS lookup fails +completely.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TCPLOCALIP</term> +	<listitem> +	  <para> +The IP address of the local end of the network connection.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TCPLOCALPORT</term> +	<listitem> +	  <para> +Rhe number of the port of the local end of the network connection.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TCPREMOTEHOST</term> +	<listitem> +	  <para> +The hostname of the connecting host. Like +<envar>TCPLOCALHOST</envar>, but for the connecting IP address.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TCPREMOTEIP</term> +	<listitem> +	  <para> +Connecting IP address.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TCPREMOTEINFO</term> +	<listitem> +	  <para> +Identification string received from the +IDENT server on the remote IP address. Not set if the IDENT server +returned an error, or if the <option>-noidentlookup</option> option was +specified.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TCPREMOTEPORT</term> +	<listitem> +	  <para> +TCP port of the remote end of the network connection.</para> +	</listitem> +      </varlistentry> +    </variablelist> +  </refsect1> + +  <refsect1> +    <title>SEE ALSO</title> + +    <para> +<ulink url="courier.html"><citerefentry><refentrytitle>courier</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>.</para> +  </refsect1> +</refentry> diff --git a/tcpd/couriertls.sgml b/tcpd/couriertls.sgml new file mode 100644 index 0000000..63e5e40 --- /dev/null +++ b/tcpd/couriertls.sgml @@ -0,0 +1,274 @@ +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<!-- Copyright 2000-2009 Double Precision, Inc.  See COPYING for --> +<!-- distribution information. --> +<refentry> +  <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info> + +  <refmeta> +    <refentrytitle>couriertls</refentrytitle> +    <manvolnum>1</manvolnum> +    <refmiscinfo>Double Precision, Inc.</refmiscinfo> +  </refmeta> + +  <refnamediv> +    <refname>couriertls</refname> +    <refpurpose>the <application moreinfo="none">Courier</application> mail server +TLS/SSL protocol wrapper</refpurpose> +  </refnamediv> + +  <refsynopsisdiv> +    <cmdsynopsis sepchar=" "> +      <command moreinfo="none">couriertls</command> +      <arg rep="repeat" choice="opt"><replaceable>option</replaceable></arg> +      <arg choice="req" rep="norepeat"><replaceable>program</replaceable></arg> +      <arg choice="req" rep="repeat"><replaceable>arg</replaceable></arg> +    </cmdsynopsis> +  </refsynopsisdiv> + +  <refsect1> +    <title>DESCRIPTION</title> + +    <para> +The <command moreinfo="none">couriertls</command> program is used by applications to encrypt a +network connection using SSL/TLS, without having the application deal with the +gory details of SSL/TLS. <command moreinfo="none">couriertls</command> is used by the +<application moreinfo="none">Courier</application> mail server +IMAP and ESMTP servers.</para> + +    <para> +<command moreinfo="none">couriertls</command> is not usually run directly from the commandline. +An application typically creates a network connection, then runs +<command moreinfo="none">couriertls</command> with appropriate options to encrypt the network +connection with SSL/TLS.</para> +  </refsect1> + +  <refsect1> +    <title>OPTIONS</title> + +    <variablelist> +      <varlistentry> +	<term>-host=<replaceable>host</replaceable>, -port=<replaceable>port</replaceable></term> +	<listitem> +	  <para> +These options are +used instead of <option>-remotefd</option>, mostly for debugging purposes. +<command moreinfo="none">couriertls</command> connects to the specified server and immediately +starts SSL/TLS negotation when the connection is established.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-localfd=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +Read and write data to encrypt via SSL/TLS from file descriptor +<replaceable>n</replaceable>.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-statusfd=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +Write SSL negotiation status to file +descriptor <replaceable>n</replaceable>, then close this file descriptor. +If SSL starts +succesfully, reading on <replaceable>n</replaceable> gets an immediate EOF. +Otherwise, a +single line of text - the error message - is read; the file descriptor is +closed; and <command moreinfo="none">couriertls</command> terminates.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-printx509=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +Print the x509 certificate on file +descriptor <replaceable>n</replaceable> then close it.  The x509 certificate is printed before +SSL/TLS encryption starts.  The application may immediately read the +certificate after running <command moreinfo="none">couriertls</command>, until the file +descriptor is closed.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-remotefd=<replaceable>n</replaceable></term> +	<listitem> +	  <para> +File descriptor <replaceable>n</replaceable> is the network connection +where SSL/TLS encryption is to be used.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-server</term> +	<listitem> +	  <para> +Negotiate server side of the SSL/TLS connection. +If this option is not used the client side of the SSL/TLS connection is +negotiated.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-tcpd</term> +	<listitem> +	  <para> +<command moreinfo="none">couriertls</command> is being called from +<command moreinfo="none">couriertcpd</command>, and the remote socket is present on descriptors +0 and 1.  <option>-tcpd</option> means, basically, the same as +<option>-remotefd=0</option>, but <command moreinfo="none">couriertls</command> closes file +descriptor 1, and redirects file descriptor 1 to file descriptor 2.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-verify=<replaceable>domain</replaceable></term> +	<listitem> +	  <para> +Verify that <replaceable>domain</replaceable> is set in +the CN field of the trusted X.509 certificate presented by the SSL/TLS +peer. TLS_TRUSTCERTS must be initialized (see below), and the certificate +must be signed by one of the trusted certificates. The CN field can +contain a wildcard: <literal moreinfo="none">CN=*.example</literal> will match +<option>-verify=foo.example.com</option>. For +SSL/TLS clients, +<envar>TLS_VERIFYPEER</envar> must be set to PEER (see below).</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>-protocol=<replaceable>proto</replaceable></term> +	<listitem> +	  <para> +Send <replaceable>proto</replaceable> protocol +commands before enabling SSL/TLS on the remote connection. <replaceable>proto</replaceable> is +either "<literal moreinfo="none">smtp</literal>" or "<literal moreinfo="none">imap</literal>". +This is a debugging option that can be used to +troubleshoot SSL/TLS with a remote IMAP or SMTP server.</para> +	</listitem> +      </varlistentry> +    </variablelist> + +    <para> +If the <option>-remotefd=<replaceable>n</replaceable></option> option is not +specified, the rest of +the command line specifies the program to run -- and its arguments -- whose +standard input and output is encrypted via SSL/TLS over the network +connection.  If the program is not specified, the standard input and output of +<command moreinfo="none">couriertls</command> itself is encrypted.</para> + +  </refsect1> + +  <refsect1> +    <title>ENVIRONMENT VARIABLES</title> + +<para> +<command moreinfo="none">couriertls</command> reads the following environment variables in +order to configure the SSL/TLS protocol:</para> + +    <variablelist> +      <varlistentry> +	<term>TLS_PROTOCOL=<replaceable>proto</replaceable></term> +	<listitem> +	  <para> +Set the protocol version.  The possible versions are: +<literal moreinfo="none">SSL2</literal>, <literal moreinfo="none">SSL3</literal>, +<literal moreinfo="none">TLS1</literal>.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TLS_CIPHER_LIST=<replaceable>cipherlist</replaceable></term> +	<listitem> +	  <para> +Optionally set the list of protocol ciphers to be used. +See OpenSSL's documentation for more information.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TLS_TIMEOUT=<replaceable>seconds</replaceable></term> +	<listitem> +	  <para> +Currently not implemented, and +reserved for future use.  This is supposed to be an inactivity timeout, +but it's not yet implemented.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TLS_DHCERTFILE=<replaceable>filename</replaceable></term> +	<listitem> +	  <para> +PEM file that stores our +Diffie-Hellman cipher pair. When OpenSSL is compiled to use Diffie-Hellman +ciphers instead of RSA you must generate a DH pair that will be used.  In +most situations the DH pair is to be treated as confidential, and +<replaceable>filename</replaceable> must not be world-readable.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TLS_CERTFILE=<replaceable>filename</replaceable></term> +	<listitem> +	  <para> +The certificate to use. +<envar>TLS_CERTFILE</envar> is required for SSL/TLS servers, and is optional +for SSL/TLS clients. +<replaceable>filename</replaceable> must not be world-readable.</para> +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TLS_TRUSTCERTS=<replaceable>pathname</replaceable></term> +	<listitem> +	  <para> +Load trusted root certificates +from <replaceable>pathname</replaceable>.  <replaceable>pathname</replaceable> +can be a file or a directory. If a +file, the file should contain a list of trusted certificates, in PEM +format. If a directory, the directory should contain the trusted +certificates, in PEM format, one per file and hashed using OpenSSL's +<command moreinfo="none">c_rehash</command> script. <envar>TLS_TRUSTCERTS</envar> is used by +SSL/TLS clients (by +specifying the <option>-domain</option> option) and by SSL/TLS servers +(<envar>TLS_VERIFYPEER</envar> is set to <literal moreinfo="none">PEER</literal> or +<literal moreinfo="none">REQUIREPEER</literal>).</para>  +	</listitem> +      </varlistentry> + +      <varlistentry> +	<term>TLS_VERIFYPEER=<replaceable>level</replaceable></term> +	<listitem> +	  <para> +Whether to verify peer's +X.509 certificate.  The exact meaning of this option depends upon whether +<command moreinfo="none">couriertls</command> is used in the client or server mode. +In server mode: +<literal moreinfo="none">NONE</literal> - do not request an X.509 certificate from the client; +<literal moreinfo="none">PEER</literal> - request an optional X.509 certificate from the +client, if the client returns one, +the SSL/TLS connection is shut down unless the certificate is signed by a +trusted certificate authority (see TLS_TRUSTCERTS); +<literal moreinfo="none">REQUIREPEER</literal> - same as +PEER, except that the SSL/TLS connects is also shut down if the client +does not return the optional X.509 certificate.  In client mode: +<literal moreinfo="none">NONE</literal> - ignore the server's X.509 certificate; +<literal moreinfo="none">PEER</literal> - verify the server's +X.509 certificate according to the <option>-domain</option> option, +(see above).</para> +	</listitem> +      </varlistentry> +    </variablelist> +  </refsect1> + +  <refsect1> +    <title>SEE ALSO</title> + +    <para> +<ulink url="couriertcpd.html"><citerefentry><refentrytitle>couriertcpd</refentrytitle><manvolnum>1</manvolnum></citerefentry></ulink>, +<ulink url="courier.html"><citerefentry><refentrytitle>courier</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>.</para> +  </refsect1> +</refentry> diff --git a/tcpd/libcouriergnutls.c b/tcpd/libcouriergnutls.c new file mode 100644 index 0000000..35d6c71 --- /dev/null +++ b/tcpd/libcouriergnutls.c @@ -0,0 +1,2089 @@ +/* +** 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> +#ifndef HAVE_GNUTLS3 +#include	<gnutls/extra.h> +#endif +#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"}, +}; + + +struct ssl_context_t { +	int isserver; +	struct tls_info info_cpy; +	const char *priority_list; + +	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 void log_2stderr( int level, const char *s) +{ +	fprintf(stderr, "%s", s); +} + +ssl_context tls_create(int isserver, const struct tls_info *info) +{ +	static int first=1; + +	ssl_context p=malloc(sizeof(struct ssl_context_t)); +	char *certfile=NULL, *dhcertfile=NULL; +	char debug_flag; + +	if (!p) +		return NULL; + +	memset(p, 0, sizeof(*p)); + +	p->isserver=isserver; +	p->info_cpy=*info; +	p->info_cpy.certificate_verified=0; + +	debug_flag=*safe_getenv(p, "TLS_DEBUG", ""); + +	if (first) +	{ +		if (gnutls_check_version(LIBGNUTLS_VERSION) == NULL) +		{ +			fprintf(stderr, "GnuTLS version mismatch\n"); +			free(p); +			errno=EINVAL; +			return (NULL); +		} + +		first=0; + +		if (debug_flag) +		{ +			gnutls_global_set_log_function(log_2stderr); +			gnutls_global_set_log_level(9); +		} + +		if (gnutls_global_init() < 0) +		{ +			fprintf(stderr, "gnutls_global_init() failed\n"); +			free(p); +			errno=EINVAL; +			return (NULL); +		} + +#ifndef HAVE_GNUTLS3 +		if (gnutls_global_init_extra() < 0) +		{ +			gnutls_global_deinit(); +			fprintf(stderr, "gnutls_global_init() failed\n"); +			free(p); +			errno=EINVAL; +			return (NULL); +		} +#endif +	} + +	p->priority_list=safe_getenv(p, "TLS_PRIORITY", +				     "NORMAL:-CTYPE-OPENPGP"); + +	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->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_retr2_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->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; + +		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, +			   const gnutls_datum_t * req_ca_rdn, int nreqs, +			   const gnutls_pk_algorithm_t * sign_algos, +			   int sign_algos_length, gnutls_retr2_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->cert_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_max_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->cert_type, +						vhost_buf, 1); + +		if (certfilename) +			break; +	} + +	if (!certfilename) +	{ +		if (ssl->ctx->certfile) +			certfilename=check_cert(ssl->ctx->certfile, +						st->cert_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_retr2_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->cert_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_retr2_st *st) +{ +	ssl_handle ssl=(ssl_handle)gnutls_session_get_ptr(session); +	int rc; +	char *certfilename=NULL; + +	rc= 0; +	st->cert_type=gnutls_certificate_type_get(session); + +	if (ssl->ctx->certfile) +		certfilename=check_cert(ssl->ctx->certfile, +					st->cert_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_priority_set_direct(ssl->session, ctx->priority_list, +				       NULL) < 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 || + +	    (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_set_retrieve_function(ssl->xcred, +							 get_server_cert); +	} +	else gnutls_certificate_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; +} diff --git a/tcpd/libcouriertls.c b/tcpd/libcouriertls.c new file mode 100644 index 0000000..6eee8b0 --- /dev/null +++ b/tcpd/libcouriertls.c @@ -0,0 +1,1560 @@ +/* +** Copyright 2000-2009 Double Precision, Inc. +** See COPYING for distribution information. +*/ +#include	"config.h" +#include	"argparse.h" +#include	"spipe.h" +#define COURIERTCPD_EXPOSE_OPENSSL 1 +#include	"libcouriertls.h" +#include	<openssl/rand.h> +#include	<openssl/x509.h> +#include	"tlscache.h" +#include	"rfc1035/rfc1035.h" +#include	"soxwrap/soxwrap.h" +#include	"random128/random128.h" +#ifdef  getc +#undef  getc +#endif +#include	<stdio.h> +#include	<string.h> +#include	<stdlib.h> +#include	<ctype.h> +#include	<netdb.h> +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif +#if	HAVE_UNISTD_H +#include	<unistd.h> +#endif +#if	HAVE_FCNTL_H +#include	<fcntl.h> +#endif +#include	<errno.h> +#if	HAVE_SYS_TYPES_H +#include	<sys/types.h> +#endif +#if	HAVE_SYS_STAT_H +#include	<sys/stat.h> +#endif +#include	<sys/socket.h> +#include	<arpa/inet.h> + +#include	<sys/time.h> + +/***** TODO *****/ + +/* #define TLSCACHEDEBUG */ + +static const char *safe_getenv(const struct tls_info *info, const char *n) +{ +	const char *v=(*info->getconfigvar)(n, info->app_data); + +	if (!v)	v=""; +	return (v); +} + +static int get_peer_verify_level(const struct tls_info *info) +{ +	int peer_verify_level=SSL_VERIFY_PEER; +		/* SSL_VERIFY_NONE */ +		/* SSL_VERIFY_PEER */ +		/* SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT */ +	const char *s=safe_getenv(info, "TLS_VERIFYPEER"); + +	if (info->peer_verify_domain) +		return SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + +	switch (*s)	{ +	case 'n': +	case 'N':		/* NONE */ +		peer_verify_level=SSL_VERIFY_NONE; +		break; +	case 'p': +	case 'P':		/* PEER */ +		peer_verify_level=SSL_VERIFY_PEER; +		break; +	case 'r': +	case 'R':		/* REQUIREPEER */ +		peer_verify_level= +			SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT; +		break; +	} +	return (peer_verify_level); +} + +static int ssl_verify_callback(int goodcert, X509_STORE_CTX *x509) +{ +	SSL *ssl= +		X509_STORE_CTX_get_ex_data(x509, +					   SSL_get_ex_data_X509_STORE_CTX_idx() +					   ); +	struct tls_info *info=SSL_get_app_data(ssl); + +	if (info->peer_verify_domain || get_peer_verify_level(info)) +	{ +		if (!goodcert) +			return (0); + +		info->certificate_verified=1; +	} + +	return (1); +} + +static int verifypeer(const struct tls_info *info, SSL *ssl) +{ +	X509 *x=NULL; +	X509_NAME *subj=NULL; +	int nentries, j; +	char domain[256]; +	char *p; +	char errmsg[1000]; + +	if (!info->peer_verify_domain) +		return (1); + +	if (info->isserver) +	{ +		x=SSL_get_peer_certificate(ssl); + +		if (x) +			subj=X509_get_subject_name(x); +	} +	else +	{ +		STACK_OF(X509) *peer_cert_chain=SSL_get_peer_cert_chain(ssl); + +		if (peer_cert_chain && peer_cert_chain->stack.num > 0) +		{ +			X509 *xx=(X509 *)peer_cert_chain->stack.data[0]; + +			if (xx) +				subj=X509_get_subject_name(xx); +		} +	} + +	 +	nentries=0; +	if (subj) +		nentries=X509_NAME_entry_count(subj); + +	domain[0]=0; +	for (j=0; j<nentries; j++) +	{ +		const char *obj_name; +		X509_NAME_ENTRY *e; +		ASN1_OBJECT *o; +		ASN1_STRING *d; + +		int dlen; +		unsigned char *ddata; + +		e=X509_NAME_get_entry(subj, j); +		if (!e) +			continue; + +		o=X509_NAME_ENTRY_get_object(e); +		d=X509_NAME_ENTRY_get_data(e); + +		if (!o || !d) +			continue; + +		obj_name=OBJ_nid2sn(OBJ_obj2nid(o)); + +		dlen=ASN1_STRING_length(d); +		ddata=ASN1_STRING_data(d); + +		if (strcasecmp(obj_name, "CN") == 0) +		{ +			if (dlen >= sizeof(domain)-1) +				dlen=sizeof(domain)-1; + +			memcpy(domain, ddata, dlen); +			domain[dlen]=0; +		} +	} + +	if (x) +		X509_free(x); +	p=domain; + +	if (*p == '*') +	{ +		int	pl, l; + +		pl=strlen(++p); +		l=strlen(info->peer_verify_domain); + +		if (*p == '.' && pl <= l && +		    strcasecmp(info->peer_verify_domain+l-pl, p) == 0) +			return (1); +	} +	else if (strcasecmp(info->peer_verify_domain, p) == 0) +		return (1); + +	strcpy(errmsg, "couriertls: Mismatched SSL certificate: CN="); +	strcat(errmsg, domain); +	strcat(errmsg, " (expected "); +	strncat(errmsg, info->peer_verify_domain, 256); +	strcat(errmsg, ")"); +	(*info->tls_err_msg)(errmsg, info->app_data); +	return (0); +} + +#ifndef NO_RSA + +static RSA *rsa_callback(SSL *s, int export, int keylength) +{ +	return (RSA_generate_key(keylength,RSA_F4,NULL,NULL)); +} + +#endif + +static void nonsslerror(const struct tls_info *info, const char *pfix) +{ +	char errmsg[256]; + +	strcpy(errmsg, "couriertls: "); +	strncat(errmsg, pfix, 200); +	strcat(errmsg, ": "); +	strncat(errmsg, strerror(errno), 255 - strlen(errmsg)); + +	(*info->tls_err_msg)(errmsg, info->app_data); +} + +static void sslerror(const struct tls_info *info, const char *pfix, int rc) +{ +	char errmsg[256]; +	char errmsgbuf2[300]; +	int errnum=ERR_get_error(); + +	if (errnum == 0) +	{ +		if (rc == 0) +		{ +			(*info->tls_err_msg)("DEBUG: Unexpected SSL connection shutdown.", +					     info->app_data); +			return; +		} + +		nonsslerror(info, pfix); +		return; +	} + +	ERR_error_string_n(errnum, errmsg, sizeof(errmsg)-1); + +	errmsg[sizeof(errmsg)-1]=0; + +	strcpy(errmsgbuf2, "couriertls: "); +	strncat(errmsgbuf2, pfix, 200); +	strcat(errmsgbuf2, ": "); +	strncat(errmsgbuf2, errmsg, 299 - strlen(errmsgbuf2)); + +	(*info->tls_err_msg)(errmsgbuf2, info->app_data); +} + +static void init_session_cache(struct tls_info *, SSL_CTX *); + +static int process_rsacertfile(SSL_CTX *ctx, const char *filename) +{ +#ifndef	NO_RSA + +	const struct tls_info *info=SSL_CTX_get_app_data(ctx); + +	SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback); + +	if(!SSL_CTX_use_certificate_chain_file(ctx, filename)) +	{ +		sslerror(info, filename, -1); +		return (0); +	} + +	if(!SSL_CTX_use_RSAPrivateKey_file(ctx, filename, SSL_FILETYPE_PEM)) +	{ +		sslerror(info, filename, -1); +		return (0); +	} +#endif +	return (1); +} + + +static int process_dhcertfile(SSL_CTX *ctx, const char *filename) +{ +#ifndef	NO_DH + +	const struct tls_info *info=SSL_CTX_get_app_data(ctx); +	BIO	*bio; +	DH	*dh; +	int	cert_done=0; + +	if(!SSL_CTX_use_certificate_chain_file(ctx, filename)) +	{ +		sslerror(info, filename, -1); +		return (0); +	} + +	if ((bio=BIO_new_file(filename, "r")) != 0) +	{ +		if ((dh=PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) != 0) +		{ +			SSL_CTX_set_tmp_dh(ctx, dh); +			cert_done=1; +			DH_free(dh); +		} +		else +			sslerror(info, filename, -1); +		BIO_free(bio); +	} +	else +		sslerror(info, filename, -1); + +	if (!cert_done) +	{ +		(*info->tls_err_msg)("couriertls: DH init failed!", +				     info->app_data); + +		return (0); +	} + +	if(!SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM)) +	{ +		sslerror(info, filename, -1); +		return (0); +	} +#endif +	return (1); +} + +static int process_certfile(SSL_CTX *ctx, const char *certfile, const char *ip, +			    int (*func)(SSL_CTX *, const char *)) +{ +	if (ip && *ip) +	{ +		char *test_file; + +		if (strncmp(ip, "::ffff:", 7) == 0 && strchr(ip, '.')) +			return (process_certfile(ctx, certfile, ip+7, func)); + +		test_file= malloc(strlen(certfile)+strlen(ip)+2); + +		strcpy(test_file, certfile); +		strcat(test_file, "."); +		strcat(test_file, ip); + +		if (access(test_file, R_OK) == 0) +		{ +			int rc= (*func)(ctx, test_file); + +			free(test_file); +			return rc; +		} +		free(test_file); +	} + +	return (*func)(ctx, certfile); +} + +static int client_cert_cb(ssl_handle ssl, X509 **x509, EVP_PKEY **pkey) +{ +	struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl); +	int i; +	const char *pem_cert; +	size_t pem_cert_size; +	STACK_OF(X509_NAME) *client_cas; +	int cert_num=0; +	int rc; + +	if (info->getpemclientcert4ca == NULL) +		return 0; + +	rc=0; +	client_cas=SSL_get_client_CA_list(ssl); + +	if (info->loadpemclientcert4ca) +		(*info->loadpemclientcert4ca)(info->app_data); + +	for (cert_num=0; (*info->getpemclientcert4ca)(cert_num, &pem_cert, +						      &pem_cert_size, +						      info->app_data); +	     ++cert_num) +	{ +		BIO *certbio; +		int err; +		X509 *x; + +		ERR_clear_error(); + +		certbio=BIO_new_mem_buf((void *)pem_cert, pem_cert_size); + +		if (!certbio) +		{ +			rc= -1; +			break; +		} + +		x=PEM_read_bio_X509(certbio, x509, NULL, NULL); + +		if (!x) +		{ +			BIO_free(certbio); +			continue; +		} + +		for (i=0; client_cas && i<client_cas->stack.num; i++) +		{ +			X509_NAME *cert=(X509_NAME *)client_cas->stack.data[i]; + +			if (X509_NAME_cmp(cert, +					  x->cert_info->issuer) == 0) +				break; +		} + +		if (!client_cas || i >= client_cas->stack.num) +		{ +			BIO_free(certbio); +			continue; +		} + +		while ((x=PEM_read_bio_X509(certbio, NULL, +					    NULL, 0)) != NULL) +		{ +			if (SSL_CTX_add_extra_chain_cert(SSL_get_SSL_CTX(ssl), +							 x) +			    != 1) +			{ +				X509_free(x); +				rc= -1; +				break; +			} +		} + +		err = ERR_peek_last_error(); +		if (rc || ERR_GET_LIB(err) != ERR_LIB_PEM || +		    ERR_GET_REASON(err) != PEM_R_NO_START_LINE) +		{ +			BIO_free(certbio); +			continue; +		} +		BIO_free(certbio); + +		ERR_clear_error(); + +		certbio=BIO_new_mem_buf((void *)pem_cert, pem_cert_size); + +		if (!certbio) +		{ +			rc= -1; +			break; +		} + +		if (!PEM_read_bio_PrivateKey(certbio, pkey, NULL, NULL)) +		{ +			BIO_free(certbio); +			continue; +		} + +		BIO_free(certbio); +		rc=1; +		break; +	} +	ERR_clear_error(); +	(*info->releasepemclientcert4ca)(info->app_data); +	return rc; +} + +SSL_CTX *tls_create(int isserver, const struct tls_info *info) +{ +	SSL_CTX *ctx; +	const char *protocol=safe_getenv(info, "TLS_PROTOCOL"); +	const char *ssl_cipher_list=safe_getenv(info, "TLS_CIPHER_LIST"); +	int session_timeout=atoi(safe_getenv(info, "TLS_TIMEOUT")); +	const char *dhcertfile=safe_getenv(info, "TLS_DHCERTFILE"); +	const char *certfile=safe_getenv(info, "TLS_CERTFILE"); +	const char *s; +	struct stat stat_buf; +	const char *peer_cert_dir=NULL; +	const char *peer_cert_file=NULL; +	int n; +	struct tls_info *info_copy; + +	if (!*ssl_cipher_list) +		ssl_cipher_list=NULL; + +	if (!*dhcertfile) +		dhcertfile=NULL; + +	if (!*certfile) +		certfile=NULL; + +	s=safe_getenv(info, "TLS_TRUSTCERTS"); +	if (s && stat(s, &stat_buf) == 0) +	{ +		if (S_ISDIR(stat_buf.st_mode)) +			peer_cert_dir=s; +		else +			peer_cert_file=s; +	} +	else if (info->peer_verify_domain) +	{ +		errno=ENOENT; +		nonsslerror(info, "TLS_TRUSTCERTS not set"); +		return (NULL); +	} + +	{ +		static int first=1; + +		if (first) +		{ +			first=0; +			SSL_load_error_strings(); +			SSLeay_add_ssl_algorithms(); + +			while (RAND_status() != 1) +			{ +				const char *p=random128(); +				size_t l=strlen(p); + +				RAND_add(p, l, l/16); +			} +		} +	} + + +	info_copy=malloc(sizeof(struct tls_info)); + +	if (info_copy == NULL) +	{ +		nonsslerror(info, "malloc"); +		return (NULL); +	} + +	memcpy(info_copy, info, sizeof(*info_copy)); +	info_copy->isserver=isserver; +	info_copy->certificate_verified=0; + +	if (!protocol || !*protocol) +		protocol="SSL23"; + +	ctx=SSL_CTX_new(protocol && strcmp(protocol, "SSL3") == 0 +			? SSLv3_method(): +			protocol && strcmp(protocol, "SSL23") == 0 +			? SSLv23_method(): +			TLSv1_method()); + +	if (!ctx) +	{ +		free(info_copy); +		nonsslerror(info, "SSL_CTX_NEW"); +		return (0); +	} +	SSL_CTX_set_app_data(ctx, info_copy); +	SSL_CTX_set_options(ctx, SSL_OP_ALL); + +	if (!ssl_cipher_list) +		ssl_cipher_list="SSLv3:TLSv1:HIGH:!LOW:!MEDIUM:!EXP:!NULL:!aNULL@STRENGTH"; + +	SSL_CTX_set_cipher_list(ctx, ssl_cipher_list); +	SSL_CTX_set_timeout(ctx, session_timeout); + +	info_copy->tlscache=NULL; +	init_session_cache(info_copy, ctx); + + +	s = safe_getenv(info, "TCPLOCALIP"); + +	if (certfile && !process_certfile(ctx, certfile, s, +					  process_rsacertfile)) +	{ +		tls_destroy(ctx); +		return (NULL); +	} + +	if (dhcertfile && !process_certfile(ctx, dhcertfile, s, +					    process_dhcertfile)) +	{ +		tls_destroy(ctx); +		return (NULL); +	} + +	SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH); + +	n=atoi(safe_getenv(info, "TLS_INTCACHESIZE")); + +	if (n > 0) +		SSL_CTX_sess_set_cache_size(ctx, n); + +	if (peer_cert_dir || peer_cert_file) +	{ +		if ((!SSL_CTX_set_default_verify_paths(ctx)) +			|| (!SSL_CTX_load_verify_locations(ctx, peer_cert_file, +				peer_cert_dir))) +		{ +			sslerror(info, peer_cert_file ? +				 peer_cert_file:peer_cert_dir, -1); +			tls_destroy(ctx); +			return (0); +		} + +		if (isserver && peer_cert_file) +		{ +			SSL_CTX_set_client_CA_list(ctx, +						   SSL_load_client_CA_file +						   (peer_cert_file)); +		} + +		if (isserver && peer_cert_dir) +		{ +			DIR *dirp; +			struct dirent *de; +			X509 *x; + +			dirp=opendir(peer_cert_dir); +			while (dirp && (de=readdir(dirp)) != NULL) +			{ +				const char *p; +				char *q; +				FILE *fp; + +				p=strrchr(de->d_name, '.'); +				if (!p || !p[1]) +					continue; +				while (*++p) +				{ +					if (strchr("0123456789", *p) == NULL) +						break; +				} +				if (*p) +					continue; + +				q=malloc(strlen(peer_cert_dir) +					 +strlen(de->d_name) + 4); +				if (!q) +				{ +					nonsslerror(info, "malloc"); +					exit(1); +				} + +				strcat(strcat(strcpy(q, peer_cert_dir), +					      "/"), de->d_name); + +				fp=fopen(q, "r"); +				if (!fp) +				{ +					nonsslerror(info, q); +					exit(1); +				} +				free(q); + +				while ((x=PEM_read_X509(fp, NULL, NULL, NULL))) +				{ +					SSL_CTX_add_client_CA(ctx,x); +					X509_free(x); +				} +				fclose(fp); +			} +			if (dirp) +				closedir(dirp); +                } +	} +	SSL_CTX_set_verify(ctx, get_peer_verify_level(info), +			   ssl_verify_callback); +	if (!isserver) +		SSL_CTX_set_client_cert_cb(ctx, client_cert_cb); +	return (ctx); +} + +void tls_destroy(SSL_CTX *ctx) +{ +	struct tls_info *info=SSL_CTX_get_app_data(ctx); + +	SSL_CTX_flush_sessions(ctx, 0); /* OpenSSL bug, 2002-08-07 */ + +	SSL_CTX_free(ctx); + +	if (info->tlscache) +	{ +		tls_cache_close(info->tlscache); +		info->tlscache=NULL; +	} +	free(info); +} + +static int cache_add(SSL *ssl, SSL_SESSION *sess); + +static SSL_SESSION *cache_get(SSL *ssl, unsigned char *id, int id_len, +			      int *copyflag); +static void cache_del(SSL_CTX *ctx, SSL_SESSION *ssl); + +static void init_session_cache(struct tls_info *info, SSL_CTX *ctx) +{ +	const char *filename=safe_getenv(info, "TLS_CACHEFILE"); +	const char *cachesize=safe_getenv(info, "TLS_CACHESIZE"); +	off_t cachesize_l; + +	if (!filename || !*filename) +	{ +		SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); +		return; +	} + +	if (info->tlscache == NULL) +	{ +		cachesize_l= cachesize ? (off_t)atol(cachesize):0; + +		if (cachesize_l <= 0) +			cachesize_l=512L * 1024; +		if ((info->tlscache=tls_cache_open(filename, cachesize_l)) +		    == NULL) +		{ +			nonsslerror(info, filename); +			return; +		} +	} + +        SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH); +	SSL_CTX_sess_set_new_cb(ctx, cache_add); +	SSL_CTX_sess_set_get_cb(ctx, cache_get); +	SSL_CTX_sess_set_remove_cb(ctx, cache_del); +} + +static int cache_add(SSL *ssl, SSL_SESSION *sess) +{ +	struct tls_info *info=SSL_get_app_data(ssl); +	unsigned char buffer[BUFSIZ]; +	unsigned char *ucp; +	time_t timeout= (time_t)SSL_SESSION_get_time(sess) +		+ SSL_SESSION_get_timeout(sess); +	void *session_id=(void *)sess->session_id; +	size_t session_id_len=sess->session_id_length; +	size_t sess_len=i2d_SSL_SESSION(sess, NULL); + +	if (sizeof(timeout) + sizeof(session_id_len) + session_id_len + +	    sess_len > sizeof(buffer)) +	{ +		fprintf(stderr, "WARN: starttls.c: buffer not big enough to cache SSL_SESSION\n"); +		return (0);	/* Too big */ +	} + +	memcpy(buffer, &timeout, sizeof(timeout)); +	memcpy(buffer+sizeof(timeout), &session_id_len, +	       sizeof(session_id_len)); +	memcpy(buffer+sizeof(timeout)+sizeof(session_id_len), +	       session_id, session_id_len); +	ucp=buffer+sizeof(timeout)+ +		sizeof(session_id_len)+session_id_len; + +	i2d_SSL_SESSION(sess, &ucp); +	if (tls_cache_add(info->tlscache, (char *)buffer, +			  (size_t)(sizeof(timeout) + +				   sizeof(session_id_len) + +				   session_id_len + sess_len))) +		perror("ALERT: tls_cache_add: "); + +#ifdef TLSCACHEDEBUG +	fprintf(stderr, "INFO: TLSCACHE: added\n"); +#endif +	return 0; +} + +struct walk_info { +	unsigned char *id; +	int id_len; +	int *copyflag; +	SSL_SESSION *ret; +	time_t now; +}; + +static int get_func(void *rec, size_t recsize, +		    int *doupdate, void *arg); + +static SSL_SESSION *cache_get(SSL *ssl, unsigned char *id, int id_len, +			      int *copyflag) +{ +	const struct tls_info *info=SSL_get_app_data(ssl); +	struct walk_info wi; + +	wi.id=id; +	wi.id_len=id_len; +	wi.copyflag=copyflag; +	wi.ret=NULL; +	time(&wi.now); +	if (tls_cache_walk(info->tlscache, get_func, &wi) < 0) +		perror("ALERT: tls_cache_walk: "); + +#ifdef TLSCACHEDEBUG +	fprintf(stderr, "INFO: TLSCACHE: session %s\n", +		wi.ret ? "found":"not found"); +#endif +	if (wi.ret) +		SSL_set_session_id_context(ssl, id, id_len); +	return wi.ret; +} + +static int get_func(void *rec, size_t recsize, +		    int *doupdate, void *arg) +{ +	unsigned char *recp=(unsigned char *)rec; +	struct walk_info *wi=(struct walk_info *)arg; +	time_t timeout; +	size_t session_id_len; + +	unsigned char *sess; + +	if (recsize < sizeof(timeout)+sizeof(session_id_len)) +		return (0); + +	memcpy(&timeout, recp, sizeof(timeout)); + +	if (timeout <= wi->now) +		return (0); + +	memcpy(&session_id_len, recp + sizeof(timeout), +	       sizeof(session_id_len)); + +	if (session_id_len != (size_t)wi->id_len || +	    memcmp(recp + sizeof(timeout) + sizeof(session_id_len), +		   wi->id, session_id_len)) +		return (0); + +	sess=recp + sizeof(timeout) + sizeof(session_id_len) + session_id_len; + +	wi->ret=d2i_SSL_SESSION(NULL, (const unsigned char **) +				&sess, recsize - sizeof(timeout) - +				sizeof(session_id_len) - session_id_len); + +	*wi->copyflag=0; +	return 1; +} + +static int del_func(void *rec, size_t recsize, +		    int *doupdate, void *arg); + +static void cache_del(SSL_CTX *ctx, SSL_SESSION *sess) +{ +	const struct tls_info *info=SSL_CTX_get_app_data(ctx); +	struct walk_info wi; + +	wi.now=0; + +	wi.id=(unsigned char *)sess->session_id; +	wi.id_len=sess->session_id_length; +	if (tls_cache_walk(info->tlscache, del_func, &wi) < 0) +		perror("ALERT: tls_cache_walk: "); +} + +static int del_func(void *rec, size_t recsize, +		    int *doupdate, void *arg) +{ +	unsigned char *recp=(unsigned char *)rec; +	struct walk_info *wi=(struct walk_info *)arg; +	time_t timeout; +	size_t session_id_len; + +	if (recsize < sizeof(timeout)+sizeof(session_id_len)) +		return (0); + +	memcpy(&timeout, recp, sizeof(timeout)); + +	if (timeout <= wi->now) +		return (0); + +	memcpy(&session_id_len, recp + sizeof(timeout), +	       sizeof(session_id_len)); + +	if (session_id_len != (size_t)wi->id_len || +	    memcmp(recp + sizeof(timeout) + sizeof(session_id_len), +		   wi->id, session_id_len)) +		return (0); + +	timeout=0; +	memcpy(recp, &timeout, sizeof(timeout)); +	*doupdate=1; +#ifdef TLSCACHEDEBUG +	fprintf(stderr, "INFO: TLSCACHE: deleted\n"); +#endif +	return (1); +} + + +/* ----------------------------------------------------------------- */ + +SSL *tls_connect(SSL_CTX *ctx, int fd) +{ +	struct tls_info *info=SSL_CTX_get_app_data(ctx); +	SSL *ssl; +	int rc; + +	/* +	**  Initialize a tls_transfer_info object. +	*/ + +	if (fcntl(fd, F_SETFL, O_NONBLOCK)) +	{ +		nonsslerror(info, "fcntl"); +		return (NULL); +	} + +#ifdef  SO_KEEPALIVE + +	{ +	int	dummy; + +		dummy=1; + +		if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, +			(const char *)&dummy, sizeof(dummy)) < 0) +                { +                        nonsslerror(info, "setsockopt"); +			return (NULL); +                } +	} +#endif + +#ifdef  SO_LINGER +	{ +	struct linger l; + +		l.l_onoff=0; +		l.l_linger=0; + +		if (setsockopt(fd, SOL_SOCKET, SO_LINGER, +			(const char *)&l, sizeof(l)) < 0) +		{ +			nonsslerror(info, "setsockopt"); +			return (NULL); +		} +	} +#endif + +	if (!(ssl=SSL_new(ctx))) +	{ +		sslerror(info, "SSL_new", -1); +		return (NULL); +	} + +	SSL_set_app_data(ssl, info); + +	SSL_set_fd(ssl, fd); +	info->accept_interrupted=0; +	info->connect_interrupted=0; + +	if (info->isserver) +	{ +		SSL_set_accept_state(ssl); +		if ((rc=SSL_accept(ssl)) > 0) +		{ +			if (!verifypeer(info, ssl)) +			{ +				tls_disconnect(ssl, fd); +				return (NULL); +			} + +			if (info->connect_callback != NULL && +			    !(*info->connect_callback)(ssl, info->app_data)) +			{ +				tls_disconnect(ssl, fd); +				return (NULL); +			} + +			return ssl; +		} +		info->accept_interrupted=1; +	} +	else +	{ +		SSL_set_connect_state(ssl); + +		if ((rc=SSL_connect(ssl)) > 0) +		{ +			if (!verifypeer(info, ssl)) +			{ +				tls_disconnect(ssl, fd); +				return (NULL); +			} + +			if (info->connect_callback != NULL && +			    !(*info->connect_callback)(ssl, info->app_data)) +			{ +				tls_disconnect(ssl, fd); +				return (NULL); +			} +			return (ssl); +		} +		info->connect_interrupted=1; +	} + +	switch (SSL_get_error(ssl, rc))	{ +	case SSL_ERROR_WANT_WRITE: +	case SSL_ERROR_WANT_READ: +		break; +	default: +		sslerror(info, "connect", rc); +		tls_disconnect(ssl, fd); +		return NULL; +	} + +	return (ssl); +} + +void tls_disconnect(SSL *ssl, int fd) +{ +	fcntl(fd, F_SETFL, 0); +	SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); +	SSL_free(ssl); +	ERR_remove_state(0); +} + +/* --------------------------------------- */ + +int	tls_transfer(struct tls_transfer_info *t, SSL *ssl, int fd, +		     fd_set *r, fd_set *w) +{ +	struct tls_info *info=SSL_get_app_data(ssl); +	int n; + +	if (info->connect_interrupted) +	{ +		n=SSL_connect(ssl); + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_NONE: +			info->connect_interrupted=0; +			break; +		case SSL_ERROR_WANT_WRITE: +			FD_SET(fd, w); +			return (1); +		case SSL_ERROR_WANT_READ: +			FD_SET(fd, r); +			return (1); +		default: +			info->connect_interrupted=0; +			t->shutdown=1; +			sslerror(info, "connect", n); +			return (-1); +		} + +		if (!verifypeer(info, ssl)) +		{ +			info->connect_interrupted=0; +			t->shutdown=1; +			return (-1); +		} +		if (info->connect_callback != NULL && +		    !(*info->connect_callback)(ssl, info->app_data)) +		{ +			info->connect_interrupted=0; +			t->shutdown=1; +			return (-1); +		} +	} +	else if (info->accept_interrupted) +	{ +		n=SSL_accept(ssl); + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_NONE: +			info->accept_interrupted=0; +			break; +		case SSL_ERROR_WANT_WRITE: +			FD_SET(fd, w); +			return (1); +		case SSL_ERROR_WANT_READ: +			FD_SET(fd, r); +			return (1); +		default: +			info->accept_interrupted=0; +			t->shutdown=1; +			sslerror(info, "accept", n); +			return (-1); +		} + +		if (!verifypeer(info, ssl)) +		{ +			info->accept_interrupted=0; +			t->shutdown=1; +			return (-1); +		} + +		if (info->connect_callback != NULL && +		    !(*info->connect_callback)(ssl, info->app_data)) +		{ +			info->accept_interrupted=0; +			t->shutdown=1; +			return (-1); +		} +	} + +	if (t->shutdown) +		return -1; + +	if (t->shutdown_interrupted && !t->read_interrupted && +	    !t->write_interrupted) +	{ +		n=SSL_shutdown(ssl); +		if (n > 0) +		{ +			t->shutdown_interrupted=0; +			t->shutdown=1; +			return -1; +		} + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_WANT_WRITE: +			FD_SET(fd, w); +			break; +		case SSL_ERROR_WANT_READ: +			FD_SET(fd, r); +			break; +		default: +			t->shutdown_interrupted=0; +			t->shutdown= -1; +			return -1; +		} +		return 1; +	} + +	if (!t->write_interrupted && t->readleft > 0) +	{ +		n=SSL_read(ssl, t->readptr, t->readleft); + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_NONE: +			break; +		case SSL_ERROR_WANT_WRITE: +			t->read_interrupted=1; +			FD_SET(fd, w); +			return (1); +		case SSL_ERROR_WANT_READ: +			FD_SET(fd, r); +			n=0; +			break; +		case SSL_ERROR_WANT_X509_LOOKUP: +			n=0; +			break; +		case SSL_ERROR_ZERO_RETURN: +			t->shutdown=1; +			return (-1); +		default: +			sslerror(info, "read", n); +			return (-1); +		} +		t->read_interrupted=0; +		t->readptr += n; +		t->readleft -= n; + +		if (n > 0) +			return (0); +	} + +	if (!t->read_interrupted && t->writeleft > 0) +	{ +		n=SSL_write(ssl, t->writeptr, t->writeleft); + +		switch (SSL_get_error(ssl, n))	{ +		case SSL_ERROR_NONE: +			break; +		case SSL_ERROR_WANT_WRITE: +			FD_SET(fd, w); +			n=0; +			break; +		case SSL_ERROR_WANT_READ: +			t->write_interrupted=1; +			FD_SET(fd, r); +			return (1); +		case SSL_ERROR_ZERO_RETURN: +			t->shutdown=1; +			return (-1); +		case SSL_ERROR_WANT_X509_LOOKUP: +			n=0; +			break; +		default: +			return (-1); +		} +		t->write_interrupted=0; +		t->writeptr += n; +		t->writeleft -= n; + +		if (n > 0) +			return (0); +	} + +	return (1); +} + +int tls_connecting(SSL *ssl) +{ +	struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl); + +	return info->accept_interrupted || info->connect_interrupted; +} + +int tls_certificate_verified(ssl_handle ssl) +{ +	struct tls_info *info=(struct tls_info *)SSL_get_app_data(ssl); + +	return info->certificate_verified; +} + +#define MAXDOMAINSIZE	256 + +static time_t asn1toTime(ASN1_TIME *asn1Time) +{ +	struct tm tm; +	int offset; + +	if (asn1Time == NULL || asn1Time->length < 13) +		return 0; + +	memset(&tm, 0, sizeof(tm)); + +#define N2(n)	((asn1Time->data[n]-'0')*10 + asn1Time->data[(n)+1]-'0') + +#define CPY(f,n) (tm.f=N2(n)) + +	CPY(tm_year,0); + +	if(tm.tm_year < 50) +		tm.tm_year += 100; /* Sux */ + +	CPY(tm_mon, 2); +	--tm.tm_mon; +	CPY(tm_mday, 4); +	CPY(tm_hour, 6); +	CPY(tm_min, 8); +	CPY(tm_sec, 10); + +	offset=0; + +	if (asn1Time->data[12] != 'Z') +	{ +		if (asn1Time->length < 17) +			return 0; + +		offset=N2(13)*3600+N2(15)*60; + +		if (asn1Time->data[12] == '-') +			offset= -offset; +	} + +#undef N2 +#undef CPY + +	return mktime(&tm)-offset; +} + + +static void dump_x509(X509 *x509, +		      void (*dump_func)(const char *, int cnt, void *), +		      void *dump_arg) +{ +	X509_NAME *subj=X509_get_subject_name(x509); +	int nentries, j; +	time_t timestamp; +	static const char gcc_shutup[]="%Y-%m-%d %H:%M:%S"; + +	if (!subj) +		return; + +	(*dump_func)("Subject:\n", -1, dump_arg); + +	nentries=X509_NAME_entry_count(subj); +	for (j=0; j<nentries; j++) +	{ +		const char *obj_name; +		X509_NAME_ENTRY *e; +		ASN1_OBJECT *o; +		ASN1_STRING *d; + +		int dlen; +		unsigned char *ddata; + +		e=X509_NAME_get_entry(subj, j); +		if (!e) +			continue; + +		o=X509_NAME_ENTRY_get_object(e); +		d=X509_NAME_ENTRY_get_data(e); + +		if (!o || !d) +			continue; + +		obj_name=OBJ_nid2sn(OBJ_obj2nid(o)); + +		dlen=ASN1_STRING_length(d); +		ddata=ASN1_STRING_data(d); +	 +		(*dump_func)("   ", -1, dump_arg); +		(*dump_func)(obj_name, -1, dump_arg); +		(*dump_func)("=", 1, dump_arg); +		(*dump_func)((const char *)ddata, dlen, dump_arg); +		(*dump_func)("\n", 1, dump_arg); +		 +	} +	(*dump_func)("\n", 1, dump_arg); + +	timestamp=asn1toTime(X509_get_notBefore(x509)); + +	if (timestamp) +	{ +		struct tm *tm=localtime(×tamp); +		char buffer[500]; + +		buffer[strftime(buffer, sizeof(buffer)-1, gcc_shutup, +				tm)]=0; + +		(*dump_func)("Not-Before: ", -1, dump_arg); +		(*dump_func)(buffer, -1, dump_arg); +		(*dump_func)("\n", 1, dump_arg); +	} + +	timestamp=asn1toTime(X509_get_notAfter(x509)); +	if (timestamp) +	{ +		struct tm *tm=localtime(×tamp); +		char buffer[500]; + +		buffer[strftime(buffer, sizeof(buffer)-1, gcc_shutup, +				tm)]=0; + +		(*dump_func)("Not-After: ", -1, dump_arg); +		(*dump_func)(buffer, -1, dump_arg); +		(*dump_func)("\n", 1, dump_arg); +	} +} + +void tls_dump_connection_info(ssl_handle ssl, +			      int server, +			      void (*dump_func)(const char *, int cnt, void *), +			      void *dump_arg) +{ +	const SSL_CIPHER *cipher; + +	{ +		STACK_OF(X509) *peer_cert_chain=SSL_get_peer_cert_chain(ssl); +		int i; + +		if (server) +		{ +			X509 *x=SSL_get_peer_certificate(ssl); + +			if (x) +			{ +				dump_x509(x, dump_func, dump_arg); +				X509_free(x); +			} +		} + +		for (i=0; peer_cert_chain && i<peer_cert_chain->stack.num; i++) +			dump_x509((X509 *)peer_cert_chain->stack.data[i], +				  dump_func, dump_arg); +	} + +	cipher=SSL_get_current_cipher(ssl); + +	if (cipher) +	{ +		const char *c; + +		c=SSL_CIPHER_get_version(cipher); +		if (c) +		{ +			(*dump_func)("Version: ", -1, dump_arg); +			(*dump_func)(c, -1, dump_arg); +			(*dump_func)("\n", 1, dump_arg); +		} + +		{ +			char buf[10]; + +			(*dump_func)("Bits: ", -1, dump_arg); + +			snprintf(buf, sizeof(buf), "%d", +				 SSL_CIPHER_get_bits(cipher, NULL)); +			buf[sizeof(buf)-1]=0; + +			(*dump_func)(buf, -1, dump_arg); +			(*dump_func)("\n", 1, dump_arg); +		} + +		c=SSL_CIPHER_get_name(cipher); + +		if (c) +		{ +			(*dump_func)("Cipher: ", -1, dump_arg); +			(*dump_func)(c, -1, dump_arg); +			(*dump_func)("\n", 1, dump_arg); +		} +	} +} + +char *tls_get_encryption_desc(ssl_handle ssl) +{ +	char protocolbuf[256]; +	const SSL_CIPHER *cipher; +	const char *c, *d; + +	cipher=SSL_get_current_cipher(ssl); + +	c=cipher ? SSL_CIPHER_get_version(cipher):NULL; +	d=cipher ? SSL_CIPHER_get_name(cipher):NULL; + +	snprintf(protocolbuf, sizeof(protocolbuf), +		 "%s,%dbits,%s", +		 c ? c:"unknown", +		 cipher ? SSL_CIPHER_get_bits(cipher, NULL):0, +		 d ? d:"unknown"); +	protocolbuf[sizeof(protocolbuf)-1]=0; +	return strdup(protocolbuf); +} + + +/* ------------------- */ + +int tls_validate_pem_cert(const char *buf, size_t buf_size) +{ +	int rc; +	BIO *certbio; +	int err; +	EVP_PKEY *pk; +	X509 *x; + +	ERR_clear_error(); + +	rc=0; +	certbio=BIO_new_mem_buf((void *)buf, buf_size); + +	if (!certbio) +		return (0); + +	x=PEM_read_bio_X509(certbio, NULL, NULL, NULL); + +	if (x) +	{ +		X509_free(x); + +		while ((x=PEM_read_bio_X509(certbio, NULL, NULL, NULL)) != NULL) +			X509_free(x); + +		err = ERR_peek_last_error(); +                if (ERR_GET_LIB(err) == ERR_LIB_PEM && +		    ERR_GET_REASON(err) == PEM_R_NO_START_LINE) +		{ +			rc=1; +		} +	} + +	ERR_clear_error(); +	BIO_free(certbio); + +	certbio=BIO_new_mem_buf((void *)buf, buf_size); + +	if (!certbio) +		return (0); + +	if (!(pk=PEM_read_bio_PrivateKey(certbio, NULL, NULL, NULL))) +	{ +		BIO_free(certbio); +		ERR_clear_error(); +		return 0; +	} + +	EVP_PKEY_free(pk); +	return rc; +} + +static size_t conv_name_to_rfc2553(const char *p, char *q) +{ +#define PUTC(c) if (q) *q++=(c); ++n + +	size_t n=0; +	const char *sep=""; + +	while (*p) +	{ +		if (*p == '/') +		{ +			++p; +			continue; +		} + +		while (*sep) +		{ +			PUTC(*sep); +			++sep; +		} +		sep=","; + +		while (*p && *p != '/') +		{ +			if (*p == '\\' && p[1]) +				++p; +			if (*p == '\\' || *p == ',') +			{ +				PUTC('\\'); +			} +			PUTC(*p); +			++p; +		} +	} +	PUTC(0); +#undef PUTC + +	return n; +} + +char *tls_cert_name(const char *buf, size_t buf_size) +{ +	BIO *certbio; +	char *p, *q; +	X509 *x; +	size_t cnt; + +	certbio=BIO_new_mem_buf((void *)buf, buf_size); + +	if (!certbio) +	{ +		ERR_clear_error(); +		return (0); +	} + +	x=PEM_read_bio_X509(certbio, NULL, NULL, NULL); +	p=0; +	q=0; + +	if (x) +	{ +		p=X509_NAME_oneline(x->cert_info->subject, NULL, 0); +		X509_free(x); +	} +	ERR_clear_error(); +	BIO_free(certbio); + +	if (p) +	{ +		cnt=conv_name_to_rfc2553(p, NULL); + +		q=malloc(cnt); + +		if (q) +			conv_name_to_rfc2553(p, q); +		free(p); +	} + +	return q; +} diff --git a/tcpd/libcouriertls.h b/tcpd/libcouriertls.h new file mode 100644 index 0000000..17faabc --- /dev/null +++ b/tcpd/libcouriertls.h @@ -0,0 +1,362 @@ +/* +** Copyright 2002-2008 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#ifndef libcouriertls_h +#define libcouriertls_h + +#include	"config.h" +#include	<stdio.h> +#include	<string.h> +#include	<sys/types.h> +#include	<sys/time.h> +#if	HAVE_SYS_SELECT_H +#include	<sys/select.h> +#endif +#if	HAVE_UNISTD_H +#include	<unistd.h> +#endif +#if	HAVE_FCNTL_H +#include	<fcntl.h> +#endif + +#ifdef COURIERTCPD_EXPOSE_OPENSSL + +#define	DEBUG_SAFESTACK	1	/* For openssl 0.9.6 */ + +#include	<openssl/ssl.h> +#include	<openssl/err.h> + +typedef SSL_CTX *ssl_context; +typedef SSL *ssl_handle; +#else + +struct ssl_context_t; +struct ssl_handle_t; + +typedef struct ssl_context_t *ssl_context; +typedef struct ssl_handle_t *ssl_handle; +#endif + +#ifdef  __cplusplus +extern "C" { +#endif + + +/* +**                   High level TLS interface library +** +** This library implements a higher level OpenSSL API, taking care of any gory +** low level details. +*/ + +/* +** This tls_info structure must be initialized before calling tls_create(). +*/ + +struct CACHE; + +struct tls_info { + +	/* +	** For SSL/TLS clients that wish to validate the server's certificate, +	** set 'peer_verify_domain' to the required certificate CN field. +	** Leave peer_verify_domain at NULL if server's certificate should +	** not be verified. +	*/ + +	const char *peer_verify_domain; + +	/* +	** If the following function is not NULL, it will be called +	** after SSL negotiation completes.  If this function returns 0, +	** the SSL connection gets torn down. +	*/ + +	int (*connect_callback)(ssl_handle , void *); + +	/* +	** When libcouriertls.a feels the urge to report an error, this +	** function gets called with an error message. +	*/ + +	void (*tls_err_msg)(const char *err_msg, +			    void *app_data /* see below */); + +	/* +	** The plethora of OpenSSL configuration settings are wrapped up +	** in this function.  libcouriertls.a will call the following function +	** to obtain a particular configuration setting, represented by a +	** label.  The function should return the setting's configuration +	** value, as a text string. +	** +	** In most cases, calling getenv() will be sufficient here. +	** See below for a complete list of currently defined configuration +	** settings. +	*/ + +	const char * (*getconfigvar)(const char *, void *); + +	/* +	** Retrieve client SSL TLS certificates. If this function pointer is +	** not NULL, this callback gets invoked repeatedly, with the first +	** parameter starting at zero and increasing until the callback +	** function returns 0. +	** +	** A non-zero return means that the callback function initialized +	** *cert_array_ret and *cert_array_size_ret to a pointer to a +	** PEM-formatted client SSL certificate, and its size in bytes. +	** The PEM file should contain a "BEGIN CERTIFICATE" followed by a +	** "BEGIN * PRIVATE KEY" (passphrase-protected keys and certs are +	** not yet supported. A zero return means that a cert/key is not +	** available. +	** +	** The first parameter is the certificate index number. 0 puts +	** the first available client certificate into cert_array_ret +	** and cert_array_size_ret and returns non-zero, or a zero return +	** if no SSL client certificates are available. If a client certificate +	** is returned, the callback function MAY get invoked again with +	** the first parameter set to 1, to retrieve the second client +	** certificate (if available). This continues until the callback +	** function returns zero, or until the returned SSL certificate's +	** issuer matches the acceptable issuers, as requested by the peer. +	** +	** The callback function cannot expect that all available SSL client +	** certs will get retrieved. If a suitable cert is found, no more +	** will be requested. +	*/ + +	int (*getpemclientcert4ca)(size_t i, +				   const char **cert_array_ret, +				   size_t *cert_array_size_ret, +				   void *dummy_arg); +	/* +	** If this callback function is defined, it gets invoked before +	** the first SSL client certificate gets requested. Its typical +	** purpose is to load all the available client certificates into +	** readily-available memory buffer of some sorts. +	*/ +	void (*loadpemclientcert4ca)(void *dummy_arg); + +	/* +	** Once a suitable SSL certificate is returned, or after all +	** certificates are returned (getpemclientcert4ca returned zero), +	** this function gets invoked. Its typical purpose is to unload +	** all memory buffers used by loaded client certs, and release all +	** memory allocated by loadpemclient4ca. +	*/ + +	void (*releasepemclientcert4ca)(void *dummy_arg); + +	/* +	** app_data is a transparent pointer that's passed along as the last +	** argument to the callback functions above. +	*/ + +	void *app_data; + +	/* +	** Everything below is internal data. +	*/ +	struct CACHE *tlscache; +	int isserver; + +	int connect_interrupted; +	int accept_interrupted; + +	int certificate_verified; +}; + +/* +** tls_get_default_info() returns a default tls_info structure, with their +** default values. +*/ +const struct tls_info *tls_get_default_info(); + +/* +** Create or destroy an SSL context.  'isserver' is non-zero for a server +** context, zero for a client context.  tls_create() makes a copy of the +** tls_info structure, for its own internal use.  The functions and data +** in the tls_info structure will continue to be used until the context is +** destroyed, from the internally-maintained copy, though. +** +** Do not call tls_destroy until all sessions are similarly destroyed. +*/ + +ssl_context tls_create(int isserver, const struct tls_info *); +void tls_destroy(ssl_context ctx); + +/* +** SSL connect/disconnect.  tls_connect() creates a new SSL connection on +** an existing file descriptor. +*/ + +ssl_handle tls_connect(ssl_context ctx, int fd); + +void tls_disconnect(ssl_handle ssl, int fd); + +/* +** Return non-zero if connection is still in progress +*/ + +int tls_connecting(ssl_handle ); + +/* +** Return non-zero if the certificate was verified +*/ + +int tls_certificate_verified(ssl_handle); + +/* +** Once an SSL/TLS session is established, use the following structure to +** read or write to the SSL/TLS socket.  The tls_transfer function reads +** and/or writes to the SSL/TLS socket, simultaneously. +** +** To read SSL data, set readptr to point to the buffer, and readleft to the +** # of bytes in the buffer.  Both readptr and readleft are updated if data +** was read from the socket by tls_transfer().  tls_transfer() will not read +** from the socket if readleft is zero. +** +** To write SSL data, set writeptr and writeleft to point to the buffer to +** be written out.  tls_transfer() will update writeptr/writeleft, if it +** wrote succesfully.  tls_transfer() may end up writing out only a portion +** of the buffer.  Do not reset writeptr and writeleft until tls_transfer() +** updates writeleft to 0, which indicates that the data has been written out +** succesfully. +** +** A tls_transfer_info object is initialized by tls_connect(). +*/ + +struct tls_transfer_info { + +	char *readptr; +	size_t readleft; + +	const char *writeptr; +	size_t writeleft; + +	int read_interrupted; +	int write_interrupted; + +	int shutdown; +	int shutdown_interrupted; +}; + +#define tls_transfer_init(i) memset((i), 0, sizeof(*i)); + +/* +**  Read and/or write from the SSL/TLS socket.  tls_transfer() updates +**  the info object, after reading or writing 'ssl', on 'fd'. +** +**  tls_transfer returns 0 if the read/write operation was processed +**  succesfully (the write operation may not be written out in entirety, +**  check writeleft). +** +**  tls_transfer returns a negative value if there was an SSL/TLS protocol +**  error, or the SSL/TLS connection closed. The SSL connection should be +**  destroyed by calling tls_disconnect(). +** +**  tls_transfer returns a positive value if tls_transfer() could not complete +**  a read or a write operation because no data was available on +**  the socket and/or the socket's output buffer is full. +**  The file descriptor sets 'r' and 'w' are updated to indicate the +**  desired socket state, and the tls_inprogress() macro will return true. +** +**  tls_transfer returns a positive value, and tls_inprogress() macro will +**  return false if there was nothing to read or write on the socket +**  (both readleft and writeleft were zero). +*/ + +int	tls_transfer(struct tls_transfer_info *info, ssl_handle ssl, int fd, +		     fd_set *r, fd_set *w); + +#define tls_inprogress(s) ((s)->read_interrupted || (s)->write_interrupted || \ +			(s)->shutdown_interrupted) + +void tls_dump_connection_info(ssl_handle ssl, +			      int server, +			      void (*dump_func)(const char *, int cnt, void *), +			      void *dump_arg); + +char *tls_get_encryption_desc(ssl_handle ssl); + +char *tls_cert_name(const char *buf, size_t buf_size); + +/* +** Start orderly SSL/TLS connection disconnect. +*/ + +#define tls_closing(s) ((s)->shutdown_interrupted=1) +#define tls_isclosing(s) ((s)->shutdown_interrupted) +#define tls_isclosed(s) ((s)->shutdown) + + +int tls_validate_pem_cert(const char *buf, size_t buf_size); + +#ifdef  __cplusplus +} +#endif + +/******************  Configuration variables ****************************** + + +TLS_PROTOCOL sets the protocol version.  The possible versions are: + +SSL2 - SSLv2 +SSL3 - SSLv3 +TLS1 - TLS1 + + +TLS_CIPHER_LIST optionally sets the list of ciphers to be used by the +OpenSSL library.  In most situations you can leave TLS_CIPHER_LIST +undefined. + +TLS_TIMEOUT - session timeout fot TLS1 + +TLS_DHCERTFILE - PEM file that stores our Diffie-Hellman cipher pair. +When OpenSSL is compiled to use Diffie-Hellman ciphers instead of RSA +you must generate a DH pair that will be used.  In most situations the +DH pair is to be treated as confidential, and the file specified by +TLS_DHCERTFILE must not be world-readable. + +TLS_CERTFILE - PEM file that stores the RSA secret key and certificate. +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_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 +directory, the directory should contain the trusted certificates, +in PEM format, one per file and hashed using OpenSSL's c_rehash +script. TLS_TRUSTCERTS is used by SSL/TLS clients (by specifying +the -domain option) and by SSL/TLS servers (TLS_VERIFYPEER is set +to PEER or REQUIREPEER). + +TLS_VERIFYPEER - how to verify client certificates.  The possible values of +this setting are: + +NONE - do not verify anything +PEER - verify the client certificate, if one's presented +REQUIREPEER - require a client certificate, fail if one's not presented + +TLS_CACHEFILE - if defined, specifies the pathname to a file that is used to +SSL/TLS sessions.  Some clients that open multiple SSL connections can take +advantage of SSL/TLS session caching. + +TLS_CACHESIZE - the size of the TLS_CACHEFILE to create, if it does not +exist. + +TLS_INTCACHESIZE - the size of the internal OpenSSL SSL session cache. +OpenSSL's documentations states that the default size is 20,000 sessions. +Use this configuration setting to reduce the default size in order to reduce +the memory footprint of SSL-enabled processes. + +TCPLOCALIP - the local IP address.  Used in server settings.  If +TLS_CERTFILE/TLS_DHCERTFILE does not exist, append ".TCPLOCALIP" and try +again. + +*************************************************************************/ + +#endif diff --git a/tcpd/spipe.c b/tcpd/spipe.c new file mode 100644 index 0000000..d60a508 --- /dev/null +++ b/tcpd/spipe.c @@ -0,0 +1,92 @@ +/* +** Copyright 2000 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +#include	"config.h" +#include	"spipe.h" + +#if HAVE_SPIPE_SOCKETPAIR + +#include <sys/types.h> +#include <sys/socket.h> + +int libmail_streampipe(int fd[2]) +{ +	return (socketpair(PF_UNIX, SOCK_STREAM, 0, fd)); +} +#endif + +#if HAVE_SPIPE_SVR3 + +/* This is, basically, Stevens */ + +#include	<sys/types.h> +#include	<sys/stream.h>		/* defines queue_t */ +#include	<stropts.h>		/* defines struct strfdinsert */ +#include	<fcntl.h> + +#define	SPX_DEVICE	"/dev/spx" + +int					/* return 0 if OK, -1 on error */ +libmail_streampipe(int fd[2]) +		/* two file descriptors returned through here */ +{ +	struct strfdinsert	ins; +	queue_t			*pointer; + +	/* +	 * First open the stream clone device "/dev/spx" twice, +	 * obtaining the two file descriptors. +	 */ + +	if ( (fd[0] = open(SPX_DEVICE, O_RDWR)) < 0) +		return(-1); + +	if ( (fd[1] = open(SPX_DEVICE, O_RDWR)) < 0) { +		close(fd[0]); +		return(-1); +	} + +	/* +	 * Now link these two streams together with an I_FDINSERT ioctl. +	 */ + +	ins.ctlbuf.buf     = (char *) &pointer;	/* no ctl info, just the ptr */ +	ins.ctlbuf.maxlen  = sizeof(queue_t *); +	ins.ctlbuf.len     = sizeof(queue_t *); + +	ins.databuf.buf    = (char *) 0;	/* no data to send */ +	ins.databuf.len    = -1; /* magic: must be -1, not 0, for stream pipe */ +	ins.databuf.maxlen = 0; + +	ins.fildes = fd[1];	/* the fd to connect with fd[0] */ +	ins.flags  = 0;		/* nonpriority message */ +	ins.offset = 0;		/* offset of pointer in control buffer */ + +	if (ioctl(fd[0], I_FDINSERT, (char * ) &ins) < 0) { +		close(fd[0]); +		close(fd[1]); +		return(-1); +	} + +	return(0);		/* all OK */ +} + +#endif + +#if HAVE_SPIPE_SVR4 + +#include	<stdio.h> +#if	HAVE_UNISTD_H +#include	<unistd.h> +#endif +#include	<stdlib.h> + +int	libmail_streampipe(int fd[2]) +{ +	return (pipe(fd)); +} + +#endif diff --git a/tcpd/spipe.h b/tcpd/spipe.h new file mode 100644 index 0000000..7f1fecb --- /dev/null +++ b/tcpd/spipe.h @@ -0,0 +1,14 @@ +#ifndef	spipe_h +#define	spipe_h + +/* +** Copyright 2000-2001 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#ifdef  __cplusplus +extern "C" +#endif +int libmail_streampipe(int [2]); + +#endif diff --git a/tcpd/starttls.c b/tcpd/starttls.c new file mode 100644 index 0000000..3e6f08e --- /dev/null +++ b/tcpd/starttls.c @@ -0,0 +1,807 @@ +/* +** Copyright 2000-2008 Double Precision, Inc. +** See COPYING for distribution information. +*/ +#include	"config.h" +#include	"argparse.h" +#include	"spipe.h" + +#include	"libcouriertls.h" +#include	"tlscache.h" +#include	"rfc1035/rfc1035.h" +#include	"soxwrap/soxwrap.h" +#ifdef  getc +#undef  getc +#endif +#include	<stdio.h> +#include	<string.h> +#include	<stdlib.h> +#include	<ctype.h> +#include	<netdb.h> +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif +#if	HAVE_UNISTD_H +#include	<unistd.h> +#endif +#if	HAVE_FCNTL_H +#include	<fcntl.h> +#endif +#include	<errno.h> +#if	HAVE_SYS_TYPES_H +#include	<sys/types.h> +#endif +#if	HAVE_SYS_STAT_H +#include	<sys/stat.h> +#endif +#include	<sys/socket.h> +#include	<arpa/inet.h> + +#if TIME_WITH_SYS_TIME +#include        <sys/time.h> +#include        <time.h> +#else +#if HAVE_SYS_TIME_H +#include        <sys/time.h> +#else +#include        <time.h> +#endif +#endif +#include	<locale.h> + + +/* Command-line options: */ +const char *clienthost=0; +const char *clientport=0; + +const char *server=0; +const char *localfd=0; +const char *remotefd=0; +const char *statusfd=0; +const char *tcpd=0; +const char *peer_verify_domain=0; +const char *fdprotocol=0; +static FILE *errfp; +static FILE *statusfp; + +const char *printx509=0; + +static void ssl_errmsg(const char *errmsg, void *dummy) +{ +	fprintf(errfp, "%s\n", errmsg); +} + +static void nonsslerror(const char *pfix) +{ +	fprintf(errfp, "%s: %s\n", pfix, strerror(errno)); +} + +void docopy(ssl_handle ssl, int sslfd, int stdinfd, int stdoutfd) +{ +	struct tls_transfer_info transfer_info; + +	char from_ssl_buf[BUFSIZ], to_ssl_buf[BUFSIZ]; +	char *fromptr; +	int rc; + +	fd_set	fdr, fdw; +	int	maxfd=sslfd; + +	if (fcntl(stdinfd, F_SETFL, O_NONBLOCK) +	    || fcntl(stdoutfd, F_SETFL, O_NONBLOCK) +	    ) +	{ +		nonsslerror("fcntl"); +		return; +	} + +	if (maxfd < stdinfd)	maxfd=stdinfd; +	if (maxfd < stdoutfd)	maxfd=stdoutfd; + +	tls_transfer_init(&transfer_info); + +	transfer_info.readptr=fromptr=from_ssl_buf; + +	for (;;) +	{ +		if (transfer_info.readptr == fromptr) +		{ +			transfer_info.readptr=fromptr=from_ssl_buf; +			transfer_info.readleft=sizeof(from_ssl_buf); +		} +		else +			transfer_info.readleft=0; + +		FD_ZERO(&fdr); +		FD_ZERO(&fdw); + +		rc=tls_transfer(&transfer_info, ssl, sslfd, &fdr, &fdw); + +		if (rc == 0) +			continue; +		if (rc < 0) +			break; + +		if (!tls_inprogress(&transfer_info)) +		{ +			if (transfer_info.readptr > fromptr) +				FD_SET(stdoutfd, &fdw); + +			if (transfer_info.writeleft == 0) +				FD_SET(stdinfd, &fdr); +		} + +		if (select(maxfd+1, &fdr, &fdw, 0, 0) <= 0) +		{ +			if (errno != EINTR) +			{ +				nonsslerror("select"); +				break; +			} +			continue; +		} + +		if (FD_ISSET(stdoutfd, &fdw) && +		    transfer_info.readptr > fromptr) +		{ +			rc=write(stdoutfd, fromptr, +				 transfer_info.readptr - fromptr); + +			if (rc <= 0) +				break; + +			fromptr += rc; +		} + +		if (FD_ISSET(stdinfd, &fdr) && transfer_info.writeleft == 0) +		{ +			rc=read(stdinfd, to_ssl_buf, sizeof(to_ssl_buf)); +			if (rc <= 0) +				break; + +			transfer_info.writeptr=to_ssl_buf; +			transfer_info.writeleft=rc; +		} +	} + +	tls_closing(&transfer_info); + +	for (;;) +	{ +		FD_ZERO(&fdr); +		FD_ZERO(&fdw); + +		if (tls_transfer(&transfer_info, ssl, sslfd, &fdr, &fdw) < 0) +			break; + +		if (select(maxfd+1, &fdr, &fdw, 0, 0) <= 0) +		{ +			if (errno != EINTR) +			{ +				nonsslerror("select"); +				break; +			} +			continue; +		} +	} +} + +struct dump_capture_subject { +	char line[1024]; +	int line_size; + +	int set_subject; +	int seen_subject; +	int in_subject; +	FILE *fp; +}; + +static void dump_to_fp(const char *p, int cnt, void *arg) +{ +	struct dump_capture_subject *dcs=(struct dump_capture_subject *)arg; +	char *n, *v; +	char namebuf[64]; + +	if (cnt < 0) +		cnt=strlen(p); + +	if (dcs->fp && fwrite(p, cnt, 1, dcs->fp) != 1) +		; /* NOOP */ + +	while (cnt) +	{ +		if (*p != '\n') +		{ +			if (dcs->line_size < sizeof(dcs->line)-1) +				dcs->line[dcs->line_size++]=*p; + +			++p; +			--cnt; +			continue; +		} +		dcs->line[dcs->line_size]=0; +		++p; +		--cnt; +		dcs->line_size=0; + +		if (strncmp(dcs->line, "Subject:", 8) == 0) +		{ +			if (dcs->seen_subject) +				continue; + +			dcs->seen_subject=1; +			dcs->in_subject=1; +			continue; +		} + +		if (!dcs->in_subject) +			continue; + +		if (dcs->line[0] != ' ') +		{ +			dcs->in_subject=0; +			continue; +		} + +		for (n=dcs->line; *n; n++) +			if (*n != ' ') +				break; + +		for (v=n; *v; v++) +		{ +			*v=toupper(*v); +			if (*v == '=') +			{ +				*v++=0; +				break; +			} +		} + +		namebuf[snprintf(namebuf, sizeof(namebuf)-1, +				 "TLS_SUBJECT_%s", n)]=0; + +		if (dcs->set_subject) +			setenv(namebuf, v, 1); +	} +} + +static int verify_connection(ssl_handle ssl, void *dummy) +{ +	FILE	*printx509_fp=NULL; +	int	printx509_fd=0; +	char	*buf; + +	struct dump_capture_subject dcs; + +	memset(&dcs, 0, sizeof(dcs)); + +	if (printx509) +	{ +		printx509_fd=atoi(printx509); + +		printx509_fp=fdopen(printx509_fd, "w"); +                if (!printx509_fp) +                        nonsslerror("fdopen"); +	} + +	dcs.fp=printx509_fp; + +	dcs.set_subject=0; + +	if (tls_certificate_verified(ssl)) +		dcs.set_subject=1; + +	tls_dump_connection_info(ssl, server ? 1:0, dump_to_fp, &dcs); + +	if (printx509_fp) +	{ +		fclose(printx509_fp); +	} + +	if (statusfp) +	{ +		fclose(statusfp); +		statusfp=NULL; +		errfp=stderr; +	} + +	buf=tls_get_encryption_desc(ssl); + +	setenv("TLS_CONNECTED_PROTOCOL", +	       buf ? buf:"(unknown)", 1); + +	if (buf) +		free(buf); +	return 1; +} + +/* ----------------------------------------------------------------------- */ + +static void startclient(int argn, int argc, char **argv, int fd, +	int *stdin_fd, int *stdout_fd) +{ +pid_t	p; +int	streampipe[2]; + +	if (localfd) +	{ +		*stdin_fd= *stdout_fd= atoi(localfd); +		return; +	} + +	if (argn >= argc)	return;		/* Interactive */ + +	if (libmail_streampipe(streampipe)) +	{ +		nonsslerror("libmail_streampipe"); +		exit(1); +	} +	if ((p=fork()) == -1) +	{ +		nonsslerror("fork"); +		close(streampipe[0]); +		close(streampipe[1]); +		exit(1); +	} +	if (p == 0) +	{ +	char **argvec; +	int n; + +		close(fd);	/* Child process doesn't need it */ +		dup2(streampipe[1], 0); +		dup2(streampipe[1], 1); +		close(streampipe[0]); +		close(streampipe[1]); + +		argvec=malloc(sizeof(char *)*(argc-argn+1)); +		if (!argvec) +		{ +			nonsslerror("malloc"); +			exit(1); +		} +		for (n=0; n<argc-argn; n++) +			argvec[n]=argv[argn+n]; +		argvec[n]=0; +		execvp(argvec[0], argvec); +		nonsslerror(argvec[0]); +		exit(1); +	} +	close(streampipe[1]); + +	*stdin_fd= *stdout_fd= streampipe[0]; +} + +static int connectremote(const char *host, const char *port) +{ +int	fd; + +RFC1035_ADDR addr; +int	af; +RFC1035_ADDR *addrs; +unsigned	naddrs, n; + +RFC1035_NETADDR addrbuf; +const struct sockaddr *saddr; +int     saddrlen; +int	port_num; + +	port_num=atoi(port); +	if (port_num <= 0) +	{ +	struct servent *servent; + +		servent=getservbyname(port, "tcp"); + +		if (!servent) +		{ +			fprintf(errfp, "%s: invalid port.\n", port); +			return (-1); +		} +		port_num=servent->s_port; +	} +	else +		port_num=htons(port_num); + +	if (rfc1035_aton(host, &addr) == 0) /* An explicit IP addr */ +	{ +		if ((addrs=malloc(sizeof(addr))) == 0) +		{ +			nonsslerror("malloc"); +			return (-1); +		} +		memcpy(addrs, &addr, sizeof(addr)); +		naddrs=1; +	} +	else +	{ +		struct rfc1035_res res; +		int rc; + +		rfc1035_init_resolv(&res); +		rc=rfc1035_a(&res, host, &addrs, &naddrs); +		rfc1035_destroy_resolv(&res); + +		if (rc) +		{ +			fprintf(errfp, "%s: not found.\n", host); +			return (-1); +		} +	} + +        if ((fd=rfc1035_mksocket(SOCK_STREAM, 0, &af)) < 0) +        { +                nonsslerror("socket"); +                return (-1); +        } + +	for (n=0; n<naddrs; n++) +	{ +		if (rfc1035_mkaddress(af, &addrbuf, addrs+n, port_num, +			&saddr, &saddrlen))	continue; + +		if (sox_connect(fd, saddr, saddrlen) == 0) +			break; +	} +	free(addrs); + +	if (n >= naddrs) +	{ +		close(fd); +		nonsslerror("connect"); +		return (-1); +	} + +	return (fd); +} + +static int connect_completed(ssl_handle ssl, int fd) +{ +	struct tls_transfer_info transfer_info; +	tls_transfer_init(&transfer_info); + +	while (tls_connecting(ssl)) +	{ +		fd_set	fdr, fdw; + +		FD_ZERO(&fdr); +		FD_ZERO(&fdw); +		if (tls_transfer(&transfer_info, ssl, +				 fd, &fdr, &fdw) < 0) +			return (0); + +		if (!tls_connecting(ssl)) +			break; + +		if (select(fd+1, &fdr, &fdw, 0, 0) <= 0) +		{ +			if (errno != EINTR) +			{ +				nonsslerror("select"); +				return (0); +			} +		} +	} +	return (1); +} + +static int dossl(int fd, int argn, int argc, char **argv) +{ +	ssl_context ctx; +	ssl_handle ssl; + +	int	stdin_fd, stdout_fd; +	struct tls_info info= *tls_get_default_info(); + +	info.peer_verify_domain=peer_verify_domain; +	info.tls_err_msg=ssl_errmsg; +	info.connect_callback= &verify_connection; +	info.app_data=NULL; + +	ctx=tls_create(server ? 1:0, &info); +	if (ctx == 0)	return (1); + +	ssl=tls_connect(ctx, fd); + +	if (!ssl) +	{ +		close(fd); +		return (1); +	} + +	if (!connect_completed(ssl, fd)) +	{ +		tls_disconnect(ssl, fd); +		close(fd); +		tls_destroy(ctx); +		return 1; +	} + +	stdin_fd=0; +	stdout_fd=1; + +	startclient(argn, argc, argv, fd, &stdin_fd, &stdout_fd); + +	docopy(ssl, fd, stdin_fd, stdout_fd); + +	tls_disconnect(ssl, fd); +	close(fd); +	tls_destroy(ctx); +	return (0); +} + +struct protoreadbuf { +	char buffer[512]; +	char *bufptr; +	int bufleft; + +	char line[256]; +} ; + +#define PRB_INIT(p) ( (p)->bufptr=0, (p)->bufleft=0) + +static char protoread(int fd, struct protoreadbuf *prb) +{ +	fd_set fds; +	struct timeval tv; + +	FD_ZERO(&fds); +	FD_SET(fd, &fds); + +	tv.tv_sec=60; +	tv.tv_usec=0; + +	if (select(fd+1, &fds, NULL, NULL, &tv) <= 0) +	{ +		nonsslerror("select"); +		exit(1); +	} + +	if ( (prb->bufleft=read(fd, prb->buffer, sizeof(prb->buffer))) <= 0) +	{ +		errno=ECONNRESET; +		nonsslerror("read"); +		exit(1); +	} + +	prb->bufptr= prb->buffer; + +	--prb->bufleft; +	return (*prb->bufptr++); +} + +#define PRB_GETCH(fd,prb) ( (prb)->bufleft-- > 0 ? *(prb)->bufptr++:\ +				protoread( (fd), (prb))) + +static const char *prb_getline(int fd, struct protoreadbuf *prb) +{ +	int i=0; +	char c; + +	while ((c=PRB_GETCH(fd, prb)) != '\n') +	{ +		if ( i < sizeof (prb->line)-1) +			prb->line[i++]=c; +	} +	prb->line[i]=0; +	return (prb->line); +} + +static void prb_write(int fd, struct protoreadbuf *prb, const char *p) +{ +	printf("%s", p); +	while (*p) +	{ +		int l=write(fd, p, strlen(p)); + +		if (l <= 0) +		{ +			nonsslerror("write"); +			exit(1); +		} +		p += l; +	} +} + +static int goodimap(const char *p) +{ +	if (*p == 'x' && p[1] && isspace((int)(unsigned char)p[1])) +		++p; +	else +	{ +		if (*p != '*') +			return (0); +		++p; +	} +	while (*p && isspace((int)(unsigned char)*p)) +		++p; +	if (strncasecmp(p, "BAD", 3) == 0) +	{ +		exit(1); +	} + +	if (strncasecmp(p, "BYE", 3) == 0) +	{ +		exit(1); +	} + +	if (strncasecmp(p, "NO", 2) == 0) +	{ +		exit(1); +	} + +	return (strncasecmp(p, "OK", 2) == 0); +} + +static void imap_proto(int fd) +{ +	struct protoreadbuf prb; +	const char *p; + +	PRB_INIT(&prb); + +	do +	{ +		p=prb_getline(fd, &prb); +		printf("%s\n", p); + +	} while (!goodimap(p)); + +	prb_write(fd, &prb, "x STARTTLS\r\n"); + +	do +	{ +		p=prb_getline(fd, &prb); +		printf("%s\n", p); +	} while (!goodimap(p)); +} + +static void pop3_proto(int fd) +{ +	struct protoreadbuf prb; +	const char *p; + +	PRB_INIT(&prb); + +	p=prb_getline(fd, &prb); +	printf("%s\n", p); + +	prb_write(fd, &prb, "STLS\r\n"); + +	p=prb_getline(fd, &prb); +	printf("%s\n", p); +} + +static void smtp_proto(int fd) +{ +	struct protoreadbuf prb; +	const char *p; + +	char hostname[1024]; + +	PRB_INIT(&prb); + +	do +	{ +		p=prb_getline(fd, &prb); +		printf("%s\n", p); +	} while ( ! ( isdigit((int)(unsigned char)p[0]) &&  +		      isdigit((int)(unsigned char)p[1]) && +		      isdigit((int)(unsigned char)p[2]) && +		      (p[3] == 0 || isspace((int)(unsigned char)p[3])))); +	if (strchr("123", *p) == 0) +		exit(1); + +	hostname[sizeof(hostname)-1]=0; +	if (gethostname(hostname, sizeof(hostname)-1) < 0) +		strcpy(hostname, "localhost"); + +	prb_write(fd, &prb, "EHLO "); +	prb_write(fd, &prb, hostname); +	prb_write(fd, &prb, "\r\n"); +	do +	{ +		p=prb_getline(fd, &prb); +		printf("%s\n", p); +	} while ( ! ( isdigit((int)(unsigned char)p[0]) &&  +		      isdigit((int)(unsigned char)p[1]) && +		      isdigit((int)(unsigned char)p[2]) && +		      (p[3] == 0 || isspace((int)(unsigned char)p[3])))); +	if (strchr("123", *p) == 0) +		exit(1); + +	prb_write(fd, &prb, "STARTTLS\r\n"); + +	do +	{ +		p=prb_getline(fd, &prb); +		printf("%s\n", p); +	} while ( ! ( isdigit((int)(unsigned char)p[0]) &&  +		      isdigit((int)(unsigned char)p[1]) && +		      isdigit((int)(unsigned char)p[2]) && +		      (p[3] == 0 || isspace((int)(unsigned char)p[3])))); +	if (strchr("123", *p) == 0) +		exit(1); + +} + +int main(int argc, char **argv) +{ +int	argn; +int	fd; +static struct args arginfo[] = { +	{ "host", &clienthost }, +	{ "localfd", &localfd}, +	{ "port", &clientport }, +	{ "printx509", &printx509}, +	{ "remotefd", &remotefd}, +	{ "server", &server}, +	{ "tcpd", &tcpd}, +	{ "verify", &peer_verify_domain}, +	{ "statusfd", &statusfd}, +	{ "protocol", &fdprotocol}, +	{0}}; +void (*protocol_func)(int)=0; + +	setlocale(LC_ALL, ""); +	errfp=stderr; + +	argn=argparse(argc, argv, arginfo); + +	if (statusfd) +		statusfp=fdopen(atoi(statusfd), "w"); + +	if (statusfp) +		errfp=statusfp; + +	if (fdprotocol) +	{ +		if (strcmp(fdprotocol, "smtp") == 0) +			protocol_func= &smtp_proto; +		else if (strcmp(fdprotocol, "imap") == 0) +			protocol_func= &imap_proto; +		else if (strcmp(fdprotocol, "pop3") == 0) +			protocol_func= &pop3_proto; +		else +		{ +			fprintf(stderr, "--protocol=%s - unknown protocol.\n", +				fdprotocol); +			exit(1); +		} +	} + +	if (tcpd) +	{ +		dup2(2, 1); +		fd=0; +	} +	else if (remotefd) +		fd=atoi(remotefd); +	else if (clienthost && clientport) +		fd=connectremote(clienthost, clientport); +	else +	{ +		fprintf(errfp, "%s: specify remote location.\n", +			argv[0]); +		return (1); +	} + +	if (fd < 0)	return (1); +	if (protocol_func) +		(*protocol_func)(fd); + +	return (dossl(fd, argn, argc, argv)); +} diff --git a/tcpd/tcpd.c b/tcpd/tcpd.c new file mode 100644 index 0000000..d3959b7 --- /dev/null +++ b/tcpd/tcpd.c @@ -0,0 +1,2151 @@ +/* +** Copyright 1998 - 2013 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if	HAVE_CONFIG_H +#include	"config.h" +#endif + +/* +** OK - the poop is that if we include socks.h after stdio.h, SOCKSfwrite +** does not get prototyped. +** If we include socks.h before stdio.h, gcc will complain about getc being +** redefined.  The easiest solution is to simply undef getc, because we +** don't use it here. +*/ + +#include	"soxwrap/soxwrap.h" +#include	<stdio.h> + + +#include	<sys/types.h> +#include	<sys/time.h> +#include	<arpa/inet.h> +#include	<pwd.h> +#include	<grp.h> +#include	<stdlib.h> +#include	<string.h> +#include	<ctype.h> +#include	<stdio.h> +#include	<errno.h> +#include	<signal.h> +#if	HAVE_SYS_STAT_H +#include	<sys/stat.h> +#endif +#if	HAVE_FCNTL_H +#include	<fcntl.h> +#endif +#if	HAVE_SYS_IOCTL_H +#include	<sys/ioctl.h> +#endif +#if HAVE_UNISTD_H +#include	<unistd.h> +#endif +#include	"waitlib/waitlib.h" +#include	"rfc1035/rfc1035.h" +#include	"liblock/config.h" +#include	"liblock/liblock.h" +#include	"tcpremoteinfo.h" +#include	"numlib/numlib.h" +#include	"argparse.h" + +#include	<netdb.h> + + +static const char *accessarg=0; +static const char *accesslocal=0; +static const char *denymsgarg=0; +static const char *listenarg=0; +static const char *ipaddrarg=0; +static const char *userarg=0; +static const char *grouparg=0; +static const char *maxprocsarg=0; +static const char *warnarg=0; +static const char *maxperiparg=0; +static const char *maxpercarg=0; +static const char *droparg=0; +static const char *nodnslookup=0; +static const char *noidentlookup=0; +static const char *stderrarg=0; +static const char *stderrloggerarg=0; +static const char *pidarg=0; +static const char *proxyarg=0; +static const char *restartarg=0; +static const char *stoparg=0; +static const char *stderrloggername=0; + +static char *lockfilename; + +static void setup_block(const char *); +static void setup_allow(const char *); + +static struct args arginfo[]={ +	{"access", &accessarg}, +	{"accesslocal", &accesslocal}, +	{"denymsg", &denymsgarg}, +	{"drop", &droparg}, +	{"address", &ipaddrarg}, +	{"block", 0, setup_block}, +	{"allow", 0, setup_allow}, +	{"group", &grouparg}, +	{"listen", &listenarg}, +	{"maxperc", &maxpercarg}, +	{"maxperip", &maxperiparg}, +	{"maxprocs", &maxprocsarg}, +	{"warn", &warnarg}, +	{"nodnslookup", &nodnslookup}, +	{"noidentlookup", &noidentlookup}, +	{"pid", &pidarg}, +	{"restart", &restartarg}, +	{"stderr", &stderrarg}, +	{"stderrlogger", &stderrloggerarg}, +	{"stderrloggername", &stderrloggername}, +	{"stop", &stoparg}, +	{"user", &userarg}, +	{"proxy", &proxyarg}, +	{0} +	} ; + +/* Ports we're listening on: */ + +static struct portinfo { +	struct portinfo *next; +	const char *ipaddr;	/* Specific IP addr, or 0 */ +	const char *servname;	/* Service name/port */ + +	int fd1, fd2;		/* BSD may need both IPv4 and IPv6 sockets */ +} *fdlist=0; +static int maxfd; + +static int nprocs, maxperc, maxperip, nwarn; +static pid_t *pids; +static time_t last_alert=0, last_warn=0; +static RFC1035_ADDR *addrs; + +static int sighup_received=0; + +struct blocklist_s { +	struct blocklist_s *next; +	char *zone;             /* zone to lookup */ +	char *display_zone;     /* zone for var_ZONE */ +	char *var; +	struct in_addr ia;	/* 0, anything */ +	char *msg;		/* NULL, query for TXT record */ +	int allow;		/* This is an -allow entry */ +	} *blocklist=0; + +extern int openaccess(const char *); +extern void closeaccess(); +extern char *chkaccess(const char *); + +/* +** Process -block and -allow parameters +*/ +static void setup_block_allow(const char *blockinfo, int isallow) +{ +struct blocklist_s *newbl=(struct blocklist_s *)malloc(sizeof(*blocklist)); +char	*p; +struct blocklist_s **blptr; +const char *ip; + +	for (blptr= &blocklist; *blptr; blptr=&(*blptr)->next) +		; + +	if (!newbl || (newbl->zone=malloc(strlen(blockinfo)+1)) == 0) +	{ +		perror("malloc"); +		exit(1); +	} + +	*blptr=newbl; +	newbl->next=0; +	newbl->allow=isallow; + +	strcpy(newbl->zone, blockinfo); + +	newbl->var=0; +	newbl->msg=0; +	newbl->display_zone=0; +	newbl->ia.s_addr=INADDR_ANY; + +	/* Look for var or IP address */ + +	for (p=newbl->zone; *p; ++p) +	{ +		if (*p == '=') +		{ +			*p++=0; +			newbl->display_zone=p; +			continue; +		} + +		if (*p == '/') +			break; + +		if (*p == ',') +		{ +			*p++=0; +			newbl->var=p; +			break; +		} +	} + +	if (newbl->display_zone == 0) +		newbl->display_zone = newbl->zone; + +	ip=0; + +	for (; *p; p++) +	{ +		if (*p == ',') +			break; + +		if (*p == '/') +		{ +			*p++=0; +			ip=p; +			break; +		} +	} + +	for (; *p; p++) +	{ +		if (*p == ',') +		{ +			*p++=0; +			newbl->msg=p; +			break; +		} +	} + +	if (ip) +	{ +		rfc1035_aton_ipv4(ip, &newbl->ia); +	} +} + +static void setup_block(const char *blockinfo) +{ +	setup_block_allow(blockinfo, 0); +} + +static void setup_allow(const char *blockinfo) +{ +	setup_block_allow(blockinfo, 1); +} + +static int isid(const char *p) +{ +	while (*p) +	{ +		if (*p < '0' || *p > '9')	return (0); +		++p; +	} +	return (1); +} + +static RETSIGTYPE sigexit(int n) +{ +	kill( -getpid(), SIGTERM); +	_exit(0); + +#if RETSIGTYPE != void +	return (0) +#endif +} + +static RETSIGTYPE sighup(int n) +{ +	sighup_received=1; + +	signal(SIGHUP, sighup); + +#if RETSIGTYPE != void +	return (0) +#endif +} + +/* +** Initialize a single listening socket +*/ + +static struct portinfo *createport(const char *a, const char *s) +{ +	struct portinfo *p=(struct portinfo *)malloc(sizeof(struct portinfo)); + +	if (!p) +	{ +		perror("malloc"); +		return (NULL); +	} + +	p->next=fdlist; +	fdlist=p; +	p->ipaddr=a; +	p->servname=s; +	p->fd1=p->fd2= -1; +	return (p); +} + +static int parseaddr(const char *p) +{ +	char *buf=strdup(p); +	char *q, *a, *s; + +	if (!buf) +	{ +		perror("malloc"); +		return (-1); +	} + +	for (q=buf; (q=strtok(q, ",")) != NULL; q=0) +	{ +		if ((s=strrchr(q, '.')) != 0) +		{ +			*s++=0; +			a=q; +		} +		else +		{ +			a=0; +			s=q; +		} + +		if (createport(a, s) == NULL) +			return (-1); +	} + +	if (ipaddrarg) +	{ +		struct portinfo *p; + +		for (p=fdlist; p; p=p->next) +		{ +			if (p->ipaddr && strcmp(p->ipaddr, "0")) +				continue; +			p->ipaddr=ipaddrarg; +		} +	} + +	return (0); +} + +/* +** Create one socket, bound to a specific host/port +*/ + +static int mksocket(const char *ipaddrarg,	/* Host/IP address */ +		    const char *servname,	/* Service/port */ +		    int flags) + +#define MKS_USEAFINET4 1 +#define MKS_ERROK 2 + +{ +	struct servent *servptr; +	int	port; +	int	fd; + +	RFC1035_ADDR addr; +	RFC1035_NETADDR  netaddr; +	const struct sockaddr *sinaddr; +	int	sinaddrlen; + +#if	RFC1035_IPV6 +	struct sockaddr_in6	sin6; +#endif + +	struct sockaddr_in	sin4; + +	int	af; + +	servptr=getservbyname(servname, "tcp"); +	if (servptr) +		port=servptr->s_port; +	else +	{ +		port=atoi(servname); +		if (port <= 0 || port > 65535) +		{ +			fprintf(stderr, "Invalid port: %s\n", servname); +			return (-1); +		} +		port=htons(port); +	} + +	/* Create an IPv6 or an IPv4 socket */ + +#if	RFC1035_IPV6 +	if (flags & MKS_USEAFINET4) +	{ +		fd=socket(PF_INET, SOCK_STREAM, 0); +		af=AF_INET; +	} +	else +#endif +		fd=rfc1035_mksocket(SOCK_STREAM, 0, &af); + +	if (fd < 0) +	{ +		perror("socket"); +		return (-1); +	} + +	/* Figure out what to bind based on what socket we created */ + +	if (ipaddrarg && strcmp(ipaddrarg, "0")) +	{ +		if (rfc1035_aton(ipaddrarg, &addr) < 0) +		{ +			fprintf(stderr,"Invalid IP address: %s\n", ipaddrarg); +			close(fd); +			return (-1); +		} + +		if (rfc1035_mkaddress(af, &netaddr, &addr, port, &sinaddr, +				&sinaddrlen)) +		{ +			fprintf(stderr,"Unable to bind IP address: %s\n", +				ipaddrarg); +			close(fd); +			return (-1); +		} +	} +	else	/* Bind default address */ +	{ +#if	RFC1035_IPV6 +		if (af == AF_INET6) +		{ +			memset(&sin6, 0, sizeof(sin6)); +			sin6.sin6_family=AF_INET6; +			sin6.sin6_addr=in6addr_any; +			sin6.sin6_port=port; +			sinaddr=(const struct sockaddr *)&sin6; +			sinaddrlen=sizeof(sin6); +		} +		else +#endif +			if (af == AF_INET) +			{ +				sin4.sin_family=AF_INET; +				sin4.sin_addr.s_addr=INADDR_ANY; +				sin4.sin_port=port; +				sinaddr=(const struct sockaddr *)&sin4; +				sinaddrlen=sizeof(sin4); +			} +			else +			{ +				errno=EAFNOSUPPORT; +				perror("socket"); +				close(fd); +				return (-1); +			} +	} + +	{ +		int dummy=1; + +		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, +		       (const char *)&dummy, sizeof(dummy)) < 0) +		{ +			perror("setsockopt"); +		} +	} + +	if (fcntl(fd, F_SETFD, FD_CLOEXEC)) +	{ +		perror("fcntl"); +		close(fd); +		return (-1); +	} + +	if (fcntl(fd, F_SETFL, O_NONBLOCK)) +	{ +		perror("fcntl"); +		close(fd); +		return (-1); +	} + +	if (sox_bind(fd, (struct sockaddr *)sinaddr, sinaddrlen) < 0) +	{ +		if (flags & MKS_ERROK) +		{ +			close(fd); +			return (-2); +		} + +		perror("bind"); +		close(fd); +		return (-1); +	} + +	if (sox_listen(fd, +#ifdef	SOMAXCONN +		       SOMAXCONN +#else +		       5 +#endif +		       )) +	{ +		if (flags && MKS_ERROK) +		{ +			close(fd); +			return (-2); +		} +		perror("listen"); +		close(fd); +		return (-1); +	} +	return (fd); +} + +static int mksockets() +{ +	struct portinfo *p; + +	maxfd= -1; + +	for (p=fdlist; p; p=p->next) +	{ +		int fd; +		struct in_addr addr; +		int fd_flag=0; + +		if (p->ipaddr && strcmp(p->ipaddr, "0")) +		{ +			/* FreeBSD needs AF_INET binds for IPv4 addys */ + +			if (rfc1035_aton_ipv4(p->ipaddr, &addr) == 0) +			{ +				fd_flag=MKS_USEAFINET4; +			} +		} + +		fd=mksocket(p->ipaddr, p->servname, fd_flag); + +		if (fd < 0) +			break; + +		p->fd1=fd; + +		if (fd > maxfd) +			maxfd=fd; + +		/* BSD requires both an IPv6 and an IPv4 socket */ + +#if	RFC1035_IPV6 +		if (p->ipaddr == 0 || strcmp(p->ipaddr, "0") == 0) +		{ +			fd=mksocket(p->ipaddr, p->servname, +				    (MKS_USEAFINET4|MKS_ERROK)); + +			if (fd == -2) +				continue;	/* Ok if bind failed */ +			if (fd < 0) +				break; + +			if (fd > maxfd) +				maxfd=fd; +			p->fd2=fd; +		} +#endif + +	} + +	if (p)	/* Clean up after ourselves, after an error */ +	{ +		for (p=fdlist; p; p=p->next) +		{ +			if (p->fd1 >= 0) +				close(p->fd1); +			if (p->fd2 >= 0) +				close(p->fd2); +		} +		return (-1); +	} + +	return (0); +} + +static int init(int argc, char **argv) +{ +int	argn; + +struct	group *gr; +int	i; +gid_t	gid=0; +const	char *servname; +int	forced=0; +int	lockfd=-1; +  +	argn=argparse(argc, argv, arginfo); + +	if ((stoparg || restartarg) && pidarg == 0) +	{ +		fprintf(stderr, "%s: -pid argument is required.\n", argv[0]); +		return (-1); +	} + +	if (pidarg) +	{ +		lockfilename=malloc(strlen(pidarg)+sizeof(".lock")); +		if (!lockfilename) +		{ +			perror("malloc"); +			return (-1); +		} +		strcat(strcpy(lockfilename, pidarg), ".lock"); +	} + +	if (stoparg) +	{ +		ll_daemon_stop(lockfilename, pidarg); +		exit(0); +	} + +	if (restartarg) +	{ +		ll_daemon_restart(lockfilename, pidarg); +		exit(0); +	} + +	if (argc - argn < 2) +	{ +		fprintf(stderr, "Usage: %s [options] port prog arg1 arg2...\n", +			argv[0]); +		return (-1); +	} + +	if (pidarg) { /* -start implied for backwards compatibility */ +		lockfd=ll_daemon_start(lockfilename); +		if (lockfd < 0) +		{ +			perror("ll_daemon_start"); +			return (-1); +		} +	} + +	servname=argv[argn++]; + +	if (parseaddr(servname)) +	{ +		close(lockfd); +		return (-1); +	} + +	if (mksockets()) +	{ +		close(lockfd); +		return (-1); +	} + +	signal(SIGINT, sigexit); +	signal(SIGHUP, sighup); +	signal(SIGTERM, sigexit); + +#if 0 +	{ +	int	fd2; +	int	dummy; + +		perror("bind"); +		if (!forcebindarg || errno != EADDRINUSE) +		{ +			sox_close(fd); +			return (-1); +		} + +		/* Poke around */ + +		if ((fd2=rfc1035_mksocket(SOCK_STREAM, 0, &dummy)) < 0) +			/* Better get same socket as fd */ +		{ +			perror("socket"); +			sox_close(fd); +			return (-1); +		} + +		if (sox_connect(fd2, (struct sockaddr *)sinaddr, +			sinaddrlen) == 0) +		{ +			sox_close(fd2); +			sox_close(fd); +			return (-1); +		} +		sox_close(fd2); +		savepid(); +		sleep(60); +		forced=1; +	} +#endif + +	if (pidarg) +		ll_daemon_started(pidarg, lockfd); + +	if (grouparg) +	{ +		if (isid(grouparg)) +			gid=atoi(grouparg); +		else if ((gr=getgrnam(grouparg)) == 0) +		{ +			fprintf(stderr, "Group not found: %s\n", grouparg); +			close(lockfd); +			return (-1); +		} +		else	gid=gr->gr_gid; + +		libmail_changegroup(gid); +	} + +	if (userarg) +	{ +	uid_t	uid; + +		if (isid(userarg)) +		{ +			uid=atoi(userarg); +			libmail_changeuidgid(uid, getgid()); +		} +		else +		{ +		gid_t	g=getgid(), *gp=0; + +			if (grouparg)	gp= &g; +			libmail_changeusername(userarg, gp); +		} +	} + +	if (pidarg && ll_daemon_resetio()) +	{ +		perror("ll_daemon_resetio"); +		close(lockfd); +		return (-1); +	} + +	if (stderrloggerarg) +	{ +	pid_t	p; +	int	waitstat; +	int	pipefd[2]; +	const char *progname=argv[argn]; + +		if (pipe(pipefd) < 0) +		{ +			perror("pipe"); +			return (-1); +		} + +		signal(SIGCHLD, SIG_DFL); +		while ((p=fork()) == -1) +		{ +			sleep(5); +		} + +		if (p == 0) +		{ +			signal(SIGHUP, SIG_IGN); +			sox_close(0); +			sox_dup(pipefd[0]); +			sox_close(pipefd[0]); +			sox_close(pipefd[1]); +			sox_close(1); +			open("/dev/null", O_WRONLY); +			sox_close(2); +			sox_dup(1); +			closeaccess(); +			while ((p=fork()) == -1) +			{ +				sleep(5); +			} +			if (p == 0) +			{ +			const char *p=strrchr(progname, '/'); + +				if (p)	++p; +				else p=progname; + +				if (stderrloggername && *stderrloggername) +					p=stderrloggername; + +				execl(stderrloggerarg, stderrloggerarg, +						p, (char *)0); +				perror(stderrloggerarg); +				_exit(5); +			} +			_exit(0); +		} +		sox_close(2); +		sox_dup(pipefd[1]); +		sox_close(pipefd[0]); +		sox_close(pipefd[1]); +		while (wait(&waitstat) != p) +			; +	} +	else if (stderrarg) +	{ +	int	fd=open(stderrarg, O_WRONLY|O_APPEND|O_CREAT, 0660); + +		if (!fd) +		{ +			perror(stderrarg); +			return (-1); +		} +		sox_close(2); +		sox_dup(fd); +		sox_close(fd); +	} + +	nprocs=40; +	if (maxprocsarg) +	{ +		nprocs=atoi(maxprocsarg); +		if (nprocs <= 0) +		{ +			fprintf(stderr, "Invalid -maxprocsarg option.\n"); +			return (-1); +		} +	} + +	nwarn= nprocs - (nprocs / 10 + 1); + +	if (warnarg) +	{ +		int c=atoi(warnarg); + +		if (c >= 0 && c <= nprocs) +			nwarn=c; +	} + +	if ((pids=malloc(sizeof(*pids)*nprocs)) == 0) +	{ +		perror("malloc"); +		return (-1); +	} +	if ((addrs=malloc(sizeof(*addrs)*nprocs)) == 0) +	{ +		free(pids); +		perror("malloc"); +		return (-1); +	} + + +	for (i=0; i<nprocs; i++) +		pids[i]= -1; + +	maxperc=nprocs; +	maxperip=4; + +	if (maxpercarg) +	{ +		maxperc=atoi(maxpercarg); +		if (maxperc <= 0) +		{ +			fprintf(stderr, "Invalid -maxperc option.\n"); +			free(pids); +			free(addrs); +			return (-1); +		} +	} +	if (maxperiparg) +	{ +		maxperip=atoi(maxperiparg); +		if (maxperip <= 0) +		{ +			fprintf(stderr, "Invalid -maxperip option.\n"); +			free(pids); +			free(addrs); +			return (-1); +		} +	} +	if (forced) +	{ +		fprintf(stderr, "couriertcpd: ready.\n"); +		fflush(stderr); +	} +	return (argn); +} + +static void run(int, const RFC1035_ADDR *, int, const char *, char **); + +static void doreap(pid_t p, int wait_stat) +{ +int	n; + +	for (n=0; n<nprocs; n++) +		if (p == pids[n]) +		{ +			pids[n]= -1; +			break; +		} +} + +static RETSIGTYPE childsig(int signum) +{ +	signum=signum; +	wait_reap(doreap, childsig); +#if RETSIGTYPE != void +	return (0); +#endif +} + +static int doallowaccess(char *, int); + +#if	RFC1035_IPV6 + +static int allowaccess(const RFC1035_ADDR *sin, int port) +{ +char	buf[RFC1035_MAXNAMESIZE+1+6]; +char	*q; +int	i; + +	if (IN6_IS_ADDR_V4MAPPED(sin)) +	{ +	const char *p=inet_ntop(AF_INET6, sin, buf, sizeof(buf)-6); + +		if (p && (q=strrchr(buf, ':')) != 0) +			return (doallowaccess(q+1, port)); +		return (1); +	} +	q=buf; +	for (i=0; i<sizeof(*sin); i += 2) +	{ +#define	B(i) ((unsigned long)((unsigned char *)sin)[i]) +	unsigned long n=(B(i) << 8) | B(i+1); +#undef	B +		sprintf(q, ":%04lx", n); +		q += 5; +	} +	*q=0; +	return (doallowaccess(buf, port)); +} + +#else +static int allowaccess(const RFC1035_ADDR *sin, int port) +{ +char	buf[RFC1035_NTOABUFSIZE+6]; + +	rfc1035_ntoa(sin, buf); +	return (doallowaccess(buf, port)); +} +#endif + +static int doallowaccess(char *buf, int port) +{ +char	*accessptr; +char *p, *q, *r; +int	quote=0; +int	l; + +	if (accessarg == 0)	return (1); + +	if (port) snprintf(buf+strlen(buf), 7, ".%d", ntohs(port)); +	while ((accessptr= *buf ? chkaccess(buf):0) == 0) +	{ +		if ((accessptr=strrchr(buf, '.')) == 0 +#if RFC1035_IPV6 +			&& (accessptr=strrchr(buf, ':')) == 0 +#endif +			) +		{ +			if (port) +			{ +				snprintf(buf, 8, "*.%d", ntohs(port)); +				if ((accessptr=chkaccess(buf)) != 0) +					break; +			} +			if ((accessptr=chkaccess("*")) != 0) +				break; +			return (1); +		} +		*accessptr=0; +	} + +	if (strncmp(accessptr, "deny", 4) == 0) +	{ +		free(accessptr); +		return (0); +	} + +	p=accessptr; +	if (strncmp(accessptr, "allow", 5) == 0) +	{ +		p += 5; +		if (*p == ',')	++p; +	} + +	while ( p && *p ) +	{ +		q=p; +		r=q; +		while (*p) +		{ +			if (*p == ',' && !quote) +			{ +				*p++=0; +				break; +			} +			if (!quote && (*p == '"' || *p == '\'')) +			{ +				quote=*p; +				p++; +				continue; +			} +			if (quote && *p == quote) +			{ +				quote=0; +				p++; +				continue; +			} +			*r++=*p++; +		} +		*r=0; +		if (strchr(q, '=') == 0) +		{ +		char	*r=malloc(strlen(q)+2); + +			if (!r) +			{ +				perror("malloc"); +				return (0); +			} +			q=strcat(strcpy(r, q), "="); +		} + +		while (*q && isspace((int)(unsigned char)*q))	++q; +		while ((l=strlen(q)) > 0 +		       && isspace((int)(unsigned char)q[l-1])) +			q[--l]=0; +		putenv(q); +	} +	return (1); +} + +/* Wait until we have at least one available slot left */ + +static int getfreeslot(int *pidptr) +{ +	int n; + +	for (;;) +	{ +		wait_block(); + +		for (n=0; n<nprocs; n++) +		{ +			if (pids[*pidptr] == (pid_t)-1)	break; +			if (++*pidptr >= nprocs)	*pidptr=0; +		} +		if (pids[*pidptr] != (pid_t)-1) +		{ +			wait_forchild(doreap, childsig); +			continue; +		} +		break; +	} +	wait_clear(childsig); +	return (*pidptr); +} + +static void accepted(int, int, RFC1035_NETADDR *, int, const char *, char **); + +static int doit(int argn, int argc, char **argv) +{ +	char	**ptrs; +	int	pidptr; +	struct portinfo *pi; +	fd_set fdr, fdrcopy; +	int	dummy; + +	ptrs=(char **)malloc((argc-argn+1) * sizeof(char *)); +	if (!ptrs) +	{ +		perror("malloc"); +		return (-1); +	} +	for (dummy=0; dummy<argc-argn; dummy++) +	{ +		ptrs[dummy]=argv[argn+dummy]; +	} +	ptrs[dummy]=0; + +	if (listenarg) +	{ +		dummy=atoi(listenarg); +		if (dummy <= 0) +		{ +			fprintf(stderr, "Invalid -listen option.\n"); +			exit(1); +		} +	} + +	FD_ZERO(&fdrcopy); +	for (pi=fdlist; pi; pi=pi->next) +	{ +		if (pi->fd1 >= 0) +			FD_SET(pi->fd1, &fdrcopy); + +		if (pi->fd2 >= 0) +			FD_SET(pi->fd2, &fdrcopy); +	} + +	pidptr=0; + +	signal(SIGCHLD, childsig); + +#if	HAVE_SETPGRP +#if	SETPGRP_VOID +	setpgrp(); +#else +	setpgrp(0, 0); +#endif +#else +#if	HAVE_SETPGID +	setpgid(0, 0); +#endif +#endif +#ifdef  TIOCNOTTY + +	{ +	int fd=open("/dev/tty", O_RDWR); + +		if (fd >= 0) +		{ +			ioctl(fd, TIOCNOTTY, 0); +			close(fd); +		} +	} +#endif + +	signal(SIGPIPE, SIG_IGN); +	for (;;) +	{ +		int n; +		int sockfd; +		RFC1035_NETADDR	sin; +		socklen_t	sinl; + +		fdr=fdrcopy; + +		if (select(maxfd+1, &fdr, NULL, NULL, NULL) <= 0) +		{ +			if (errno != EINTR) +				perror("accept"); +			continue; +		} + +		for (pi=fdlist; pi; pi=pi->next) +		{ +			if (pi->fd1 >= 0 && FD_ISSET(pi->fd1, &fdr) && +			    ((n=getfreeslot(&pidptr)), +			     (sinl = sizeof(sin)), +			     (sockfd=sox_accept(pi->fd1, +						(struct sockaddr *)&sin, +						&sinl))) >= 0) +			{ +				accepted(n, sockfd, &sin, sinl, +					 argv[argn], ptrs); +			} + +			if (pi->fd2 >= 0 && FD_ISSET(pi->fd2, &fdr) && +			    ((n=getfreeslot(&pidptr)), +			     (sinl = sizeof(sin)),  +			     (sockfd=sox_accept(pi->fd2, +						(struct sockaddr *)&sin, +						&sinl))) >= 0) +			{ +				accepted(n, sockfd, &sin, sinl, +					 argv[argn], ptrs); +			} +		} +	} +} + +static void denied(int sockfd) +{ +	if (denymsgarg) { +		if (write(sockfd, denymsgarg, strlen(denymsgarg)) < 0 || +		    write(sockfd, "\n", 1) < 0) +		{ +			sox_close(sockfd); +			_exit(1); +		} +	} +	sox_close(sockfd); +	_exit(0); +} + +static void accepted(int n, int sockfd, RFC1035_NETADDR *sin, int sinl, +		     const char *prog, +		     char **args) +{ +	RFC1035_ADDR addr; +	int	addrport; +#ifdef	SO_LINGER +	int	dummy; +	struct	linger l; +#endif +	pid_t	p; +	int cnt; + +	if (rfc1035_sockaddrip(sin, sinl, &addr) +	    || rfc1035_sockaddrport(sin, sinl, &addrport)) +	{ +		sox_close(sockfd); +		return; +	} + +	/* Turn off the CLOEXEC and NONBLOCK bits */ + +	if (fcntl(sockfd, F_SETFD, 0)) +	{ +		perror("fcntl"); +		sox_close(sockfd); +		return; +	} + +	if (fcntl(sockfd, F_SETFL, 0)) +	{ +		perror("fcntl"); +		sox_close(sockfd); +		return; +	} + +	if (sighup_received) +	{ +		sighup_received=0; +		if (accessarg) +		{ +			closeaccess(); +			if (openaccess(accessarg)) +				perror(accessarg); +		} +	} + +#ifdef	SO_KEEPALIVE +	dummy=1; +	if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, +		       (const char *)&dummy, sizeof(dummy)) < 0) +	{ +		perror("setsockopt"); +	} +#endif + +#ifdef	SO_LINGER +	l.l_onoff=0; +	l.l_linger=0; + +	if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, +		       (const char *)&l, sizeof(l)) < 0) +	{ +		perror("setsockopt"); +	} +#endif +	wait_block(); +	if ((p=fork()) == -1) +	{ +		perror("fork"); +		sox_close(sockfd); +		return; +	} + +	if (p == 0) +	{ +		wait_restore(childsig); + +		if (accesslocal) /* Lookup local interface address too? */ +		{ +		RFC1035_NETADDR lsin; +		RFC1035_ADDR laddr; +		int	lport; +		socklen_t	i=sizeof(lsin); +		 +			if (sox_getsockname(sockfd, (struct sockaddr *)&lsin, &i) == 0 && +				rfc1035_sockaddrip(&lsin, i, &laddr) == 0 && +				rfc1035_sockaddrport(&lsin, i, &lport) == 0 && +				allowaccess(&laddr,lport) == 0) +			{ +				sox_close(sockfd); +				_exit(0); +			} +		} + +		if (allowaccess(&addr,0) == 0) +		{ +			denied(sockfd); +		} + +		run(sockfd, &addr, addrport, prog, args); +	} +	pids[n]=p; + +	memcpy(addrs+n, &addr, sizeof(addr)); +	sox_close(sockfd); +	wait_clear(childsig); + +	for (cnt=n=0; n<nprocs; n++) +		if (pids[n] != (pid_t)-1) +			++cnt; + +	if (cnt == nprocs) +	{ +		time_t t; + +		time(&t); +		if (last_alert == 0 || last_alert > t || last_alert < t - 60) +		{ +			last_alert=t; +			fprintf(stderr, +				"ALERT: %d maximum active connections.\n", +				nprocs); +		} +	} +	else if (cnt >= nwarn) +	{ +		time_t t; + +		time(&t); +		if (last_warn == 0 || last_warn > t || last_warn < t - 60) +		{ +			last_warn=t; +			fprintf(stderr, "WARN: %d active connections.\n", +				cnt); +		} +	} +} + +static void mysetenv(const char *name, const char *val) +{ +char	*p=malloc(strlen(name)+strlen(val)+2); + +	if (!p) +	{ +		perror("malloc"); +		_exit(1); +	} +	putenv(strcat(strcat(strcpy(p, name), "="), val)); +} + +/* +** Convert IP address to host name.  Make sure the IP address resolves +** backwards and forwards. +*/ + +static void ip2host(const RFC1035_ADDR *addr, const char *env) +{ +const char *remotehost="softdnserr"; +char	buf[RFC1035_MAXNAMESIZE+1]; + +#if TCPDUSERFC1035 +struct rfc1035_res res; +#endif + +	if (nodnslookup)	return; + +	rfc1035_ntoa(addr, buf); + +#if TCPDUSERFC1035 + +	rfc1035_init_resolv(&res); + +	if (rfc1035_ptr(&res, addr, buf) != 0) +	{ +		if (errno == ENOENT) +			remotehost=0; +	} +	else +	{ +		RFC1035_ADDR *ias; +		unsigned nias, n; + +		if (rfc1035_a(&res, buf, &ias, &nias) != 0) +		{ +			if (errno == ENOENT) +				remotehost=0; +		} +		else +		{ +			remotehost=0; +			for (n=0; n<nias; n++) +			{ +			char	a[RFC1035_MAXNAMESIZE]; +			char	b[RFC1035_MAXNAMESIZE]; + +				rfc1035_ntoa(&ias[n], a); +				rfc1035_ntoa(addr, b); + +				if (strcmp(a, b) == 0) +				{ +					remotehost=buf; +				} +			} +		} +	} +	rfc1035_destroy_resolv(&res); + +#else + +	{ +	struct hostent *he; +	unsigned n; +	struct	in_addr in; + +#if	RFC1035_IPV6 + +		if (IN6_IS_ADDR_V4MAPPED(addr)) +			memcpy(&in, (char *)addr + 12, 4); +		else	return; +#else +		in= *addr; +#endif + +		he=gethostbyaddr( (char *)&in, sizeof(in), AF_INET); +		if (!he) +		{ +			switch (h_errno)	{ +			case HOST_NOT_FOUND: +			case NO_DATA: +				remotehost=0; +				break; +			} +		} +		else +		{ +			strcpy(buf, he->h_name); +			he=gethostbyname(buf); +			if (!he) +			{ +				switch (h_errno)	{ +				case HOST_NOT_FOUND: +				case NO_DATA: +					remotehost=0; +					break; +				} +			} +			else for (n=0, remotehost=0; he->h_addr_list[n]; n++) +			{ +			struct in_addr hin; + +				if (he->h_addrtype != AF_INET || +					he->h_length < sizeof(hin)) +					break; +				memcpy((char *)&hin, he->h_addr_list[n], +					sizeof(hin)); +				if (hin.s_addr == in.s_addr) +				{ +					remotehost=buf; +					break; +				} +			} + +		} +	} +#endif +	if (remotehost) +		mysetenv(env, remotehost); +} + +static void mkmymsg(const char *varname, const char *msg) +{ +const char *p=getenv("TCPREMOTEIP"); +char *q=malloc(strlen(msg)+1+strlen(p)); +char *r; + +	if (!q) +	{ +		perror("malloc"); +		exit(1); +	} + +	for (r=q; *msg; msg++) +	{ +		if (*msg == '@') +		{ +			strcpy(r, p); +			while (*r)	r++; +			++msg; +			break; +		} +		*r++=*msg; +	} +	while (*msg) +		*r++=*msg++; +	*r=0; +	mysetenv(varname, q); +	free(q); +} + +static void set_allow_variable(const char *varname, const char *msg) +{ +	static int found = 0; +	char buf[32]; +	mysetenv(varname, ""); /* Whitelist */ + +	sprintf(buf, "ALLOW_%d", found++); +	mysetenv(buf, varname); + +	(void)msg; /* not used, could tweak behavior of -allow */ +} + +static void set_txt_response(const char *varname, +		      const char *txt) +{ +	char *p=malloc(strlen(varname)+20); + +	strcat(strcpy(p, varname), "_TXT"); +	mysetenv(p, txt); +	free(p); + +} + +static void set_zone(const char *varname, +	      const char *zone) +{ +	char *p=malloc(strlen(varname)+20); + +	strcat(strcpy(p, varname), "_ZONE"); +	mysetenv(p, zone); +	free(p); + +} + + +static void set_a_response(const char *varname, +		    const struct in_addr *in) +{ +	char	buf[RFC1035_NTOABUFSIZE+6]; +	char *p; + +	rfc1035_ntoa_ipv4(in, buf); + +	p=malloc(strlen(varname)+20); + +	strcat(strcpy(p, varname), "_IP"); +	mysetenv(p, buf); +	free(p); +} + +static int is_a_rr(struct rfc1035_reply *replyp, +		   const struct rfc1035_rr *rrptr, +		   const char *wanted_hostname) +{ +	char	buf[RFC1035_MAXNAMESIZE+1]; + +	/* +	** Go through the DNS response, and check every A record +	** in there. +	*/ + +	rfc1035_replyhostname(replyp, rrptr->rrname, buf); +	if (rfc1035_hostnamecmp(buf, wanted_hostname))	return 0; + +	if (rrptr->rrtype != RFC1035_TYPE_A) +		return 0; + +	return 1; +} + +/* +** Process TXT records in DNSBL lookup response. +*/ + +static int search_txt_records(struct rfc1035_res *res, +			       int allow, +			       const char *varname, +			       struct rfc1035_reply *replyp, +			       const char *wanted_hostname) +{ +	char	buf[RFC1035_MAXNAMESIZE+1]; +	int j; + +	if ((j=rfc1035_replysearch_all(res, +				       replyp, wanted_hostname, +				       RFC1035_TYPE_TXT, +				       RFC1035_CLASS_IN, 0)) >= 0) +	{ +		rfc1035_rr_gettxt(replyp->allrrs[j], 0, buf); + +		if (buf[0]) /* is this necessary? */ +		{ +			if (!allow) +				mysetenv(varname, buf); +			set_txt_response(varname, buf); +			return 1; +		} +	} +	return 0; +} + +/* +** check_blocklist is called once for each blocklist query to process. +*/ + +static void docheckblocklist(struct blocklist_s *p, const char *nameptr) +{ +	const char *q; +	const char *varname=p->var; +	char	hostname[RFC1035_MAXNAMESIZE+1]; +	int	wanttxt; +	struct rfc1035_reply *replyp; +	struct rfc1035_res res; +	unsigned int i; +	int found; + +	hostname[0]=0; +	strncat(hostname, nameptr, RFC1035_MAXNAMESIZE); + +	if (!varname)	varname="BLOCK"; + +	if ((q=getenv(varname)) != 0)	return; +					/* Env var already set */ + +	rfc1035_init_resolv(&res); + +	/* +	** The third parameter has opposite meanings.  For -block, the last +	** component specifies a custom message that overrides any TXT record +	** in the DNS access list.  For -allow, it simply asks for TXT records +	** to be fetched, for use by external software. +	*/ + +	if (p->allow) +		wanttxt = p->msg != 0; +	else +		wanttxt = (p->msg == 0 || *p->msg == 0); + +	(void)rfc1035_resolve_cname(&res, +			hostname, +			wanttxt ? RFC1035_TYPE_ANY:RFC1035_TYPE_A, +			RFC1035_CLASS_IN, &replyp, 0); + +	if (!replyp) +	{ +		rfc1035_destroy_resolv(&res); +		return; +	} + +	found=0; + +	for (i=0; i<replyp->ancount+replyp->nscount+replyp->arcount; i++) +	{ +		if (!is_a_rr(replyp, replyp->allrrs[i], hostname)) +			continue; + +		if (p->ia.s_addr != INADDR_ANY && +		    p->ia.s_addr != replyp->allrrs[i]->rr.inaddr.s_addr) +			continue; + +		set_zone(varname, p->display_zone); +		set_a_response(varname, &replyp->allrrs[i]->rr.inaddr); + +		/* +		** The -block option was kind enough to supply the +		** error message. +		*/ + +		if (!p->allow && p->msg && *p->msg) +		{ +			mkmymsg(varname, p->msg); +			continue; +		} + +		/* +		** search_txt_records takes care of setting varname for +		** -blocks, and we must set it for -allows. +		*/ + +		if (p->allow) +			set_allow_variable(varname, p->msg); + +		if (!search_txt_records(&res, p->allow, varname, replyp, +					hostname) && !p->allow) +		{ +			/* +			** Even though we did not find a TXT record, we're here +			** because of an A record, so for -blocks, we must +			** set varname to something. +			*/ +			mysetenv(varname, "Access denied."); +		} + +		found=1; +		break; +	} + +	/* +	** Last chance: if all we got is a TXT record, and we were not looking +	** for a specific IP address, then take what we've got. +	*/ + +	if (p->ia.s_addr == INADDR_ANY && !found) +	{ +		if (search_txt_records(&res, p->allow, varname, replyp, +				       hostname)) +		{ +			/* +			** search_txt_record takes care of setting varname +			** for -blocks, and we must do it for -allows +			*/ +			if (p->allow) +				mysetenv(varname, ""); /* Whitelist */ +			set_zone(varname, p->display_zone); +		} +	} + +	rfc1035_replyfree(replyp); +	rfc1035_destroy_resolv(&res); +} + +static void check_blocklist_ipv4(struct blocklist_s *p, +	const struct in_addr *ia) +{ +unsigned a,b,c,d; +char	hostname[RFC1035_MAXNAMESIZE+1]; +const unsigned char *q=(const unsigned char *)ia; + +	/* Calculate DNS query hostname */ + +	a=q[0]; +	b=q[1]; +	c=q[2]; +	d=q[3]; + +	/* Silently ignore exceedingly long zones */ +	if (snprintf(hostname, sizeof hostname, +		"%u.%u.%u.%u.%s", d, c, b, a, p->zone) <= RFC1035_MAXNAMESIZE) +			docheckblocklist(p, hostname); +} + +#if	RFC1035_IPV6 + +static void check_blocklist(struct blocklist_s *p, const RFC1035_ADDR *ia) +{ +	char	hostname[RFC1035_MAXNAMESIZE+1]; + +	/* +	** 16 byte IPv6 address. 32 nybbles. Each nybble followed by a dot: +	** 64 characters. +	*/ + +	char	decimal_address[65]; +	char	bytebuf[5]; +	int i; + +	if (IN6_IS_ADDR_V4MAPPED(ia)) +	{ +	struct in_addr ia4; + +		memcpy(&ia4, (const char *)ia + 12, 4); +		check_blocklist_ipv4(p, &ia4); +	} + +	decimal_address[0]=0; + +	for (i=0; i<16; ++i) +	{ +		unsigned char byte=((struct in6_addr *)ia)->s6_addr[15-i]; + +		sprintf(bytebuf, "%x.%x.",(byte & 0x0F), ((byte >> 4) & 0x0F)); +		strcat(decimal_address, bytebuf); +	} + +	/* Silently ignore exceedingly long zones */ +	if (snprintf(hostname, sizeof hostname, +		     "%s%s", decimal_address, p->zone) <= RFC1035_MAXNAMESIZE) +		docheckblocklist(p, hostname); +} + +#else +static void check_blocklist(struct blocklist_s *p, const RFC1035_ADDR *ia) +{ +	check_blocklist_ipv4(p, ia); +} +#endif + +static void check_drop(int sockfd) +{ +	const char *p, *q; +	char *r; + +	p=droparg; + +	if (p && !*p) +		p="BLOCK"; + +	for (; p && *p; q=p) +	{ +		if (*p == ',') +		{ +			q= ++p; +			continue; +		} + +		for (q=p; *q; ++q) +			if (*q == ',') +				break; + +		r=malloc(q-p+1); + +		if (!r) +		{ +			perror("malloc"); +			_exit(1); +		} + +		memcpy(r, p, q-p); +		r[q-p]=0; + +		p=getenv(r); +		free(r); + +		if (p && *p) +		{ +			fprintf(stderr, +				"WARN: dropped blocked connection from %s\n", +				getenv("TCPREMOTEIP")); +			denied(sockfd); +		} +	} +} + +static void proxy(); + +static void run(int fd, const RFC1035_ADDR *addr, int addrport, +	const char *prog, char **argv) +{ +RFC1035_NETADDR lsin; +RFC1035_ADDR laddr; +int	lport; + +socklen_t	i; +int	ipcnt, ccnt; +char	buf[RFC1035_MAXNAMESIZE+128]; +struct blocklist_s *bl; +const char *remoteinfo; +const char *p; + +	i=sizeof(lsin); +	if (sox_getsockname(fd, (struct sockaddr *)&lsin, &i) || +		rfc1035_sockaddrip(&lsin, i, &laddr) || +		rfc1035_sockaddrport(&lsin, i, &lport)) +	{ +		fprintf(stderr, "getsockname failed.\n"); +		exit(1); +	} + +	if (!noidentlookup && (remoteinfo=tcpremoteinfo( +		&laddr, lport, +		addr, addrport, 0)) != 0) +	{ +	char	*q=malloc(sizeof("TCPREMOTEINFO=")+strlen(remoteinfo)); + +		if (!q) +		{ +			perror("malloc"); +			_exit(1); +		} + +		strcat(strcpy(q, "TCPREMOTEINFO="), remoteinfo); +		putenv(q); +	} + +/* check if it's an exception to the global ip limit */ +	if( (p=getenv("MAXCPERIP")) != NULL ) +	{ +		int j = atoi(p); + +		if( j > 0 ) +			maxperip = j; +	} + +	for (i=0, ipcnt=ccnt=0; i<nprocs; i++) +	{ +	RFC1035_ADDR *psin; +	int	j; + +		if (pids[i] == (pid_t)-1)	continue; + +		psin=addrs+i; + +		for (j=0; j<sizeof(*addr); j++) +			if ( ((char *)addr)[j] != ((char *)psin)[j]) +				break; + +		if (j >= sizeof(*addr) && +			++ipcnt >= maxperip) +		{ +			rfc1035_ntoa(addr, buf); +			fprintf(stderr,"ALERT: Maximum connection limit reached for %s\n",buf); +			_exit(0);	/* Too many from same IP address */ +		} + +		if ( j >= sizeof(*addr)-1 && +			++ccnt >= maxperc) +			_exit(0);	/* Too many from same netblock */ + +	} + +	rfc1035_ntoa(addr, buf); +	mysetenv("TCPREMOTEIP", buf); +	sprintf(buf, "%d", ntohs(addrport)); +	mysetenv("TCPREMOTEPORT", buf); +	ip2host(addr, "TCPREMOTEHOST"); + +	rfc1035_ntoa(&laddr, buf); +	mysetenv("TCPLOCALIP", buf); +	sprintf(buf, "%d", ntohs(lport)); +	mysetenv("TCPLOCALPORT", buf); +	ip2host(&laddr, "TCPLOCALHOST"); + +	for (bl=blocklist; bl; bl=bl->next) +		check_blocklist(bl, addr); + +	check_drop(fd); +	sox_close(0); +	sox_close(1); +	sox_dup(fd); +	sox_dup(fd); +	sox_close(fd); +	if (stderrarg && strcmp(stderrarg, "socket") == 0) +	{ +		sox_close(2); +		sox_dup(1); +	} +	proxy(); +	signal(SIGPIPE, SIG_DFL); + +	execv(prog, argv); +	perror(prog); +	exit(1); +} + +int main(int argc, char **argv) +{ +int argn=init(argc, argv); +int rc; + +	if (argn < 0) +	{ +		exit(1); +	} +	if (accessarg && openaccess(accessarg)) +		perror(accessarg); +	if (accesslocal && !accessarg) +		fprintf(stderr,"-accesslocal requires -access\n"); +	rc=doit(argn, argc, argv); +	kill( -getpid(), SIGTERM); +	exit(rc); +	return (0); +} + +#if 1 + +static void proxy() +{ +} + +#else + +/* +** SOCKSv5 does not support wildcards binds, for now, so there's no +** reason to manually proxy anything, yet. +*/ + + +/*************************************************************************** + +Manual proxy, to support SOCKS encryption.  Because encrypted connection to +the SOCKS server is supported transparently in libsocks5, we can't just +run the app, because we'll lose libsocks5's intercept of read/write, et al + +***************************************************************************/ + +struct proxybuf { +	char buffer[BUFSIZ]; +	char	*p; +	int buffered; +	int rfd, wfd; +	} ; + +static void proxy_init(struct proxybuf *b, int r, int w) +{ +	b->buffered=0; +	b->rfd=r; +	b->wfd=w; +} + +static int proxy_setfd(struct proxybuf *b, fd_set *r, fd_set *w, int *max) +{ +	/* If we have something buffered, write it out */ + +	if (b->buffered) +	{ +		FD_SET(b->wfd, w); +		if (b->wfd > *max)	*max=b->wfd; +		return (1); +	} + +	if (b->rfd < 0) +	{ +		if (b->wfd >= 0) +		{ +			sox_close(b->wfd); +			b->wfd= -1; +		} +		return (0);	/* Nothing else to do */ +	} + +	if (b->rfd > *max)	*max=b->rfd; +	FD_SET(b->rfd, r); +	return (1); +} + +static void proxy_dofd(struct proxybuf *b, fd_set *r, fd_set *w) +{ +	if (b->buffered) +	{ +		if (FD_ISSET(b->wfd, w)) +		{ +		int	n=sox_write(b->wfd, b->p, b->buffered); + +			if (n <= 0) +			{ +				sox_close(b->rfd); +				sox_close(b->wfd); +				b->rfd=b->wfd= -1; +				b->buffered=0; +				return; +			} +			b->p += n; +			b->buffered -= n; +		} +		return; +	} + +	if (b->rfd >= 0 && FD_ISSET(b->rfd, r)) +	{ +	int	n=sox_read(b->rfd, b->buffer, sizeof(b->buffer)); + +		if (n <= 0) +		{ +			sox_close(b->rfd); +			sox_close(b->wfd); +			b->rfd=b->wfd= -1; +			b->buffered=0; +			return; +		} +		b->p = b->buffer; +		b->buffered=n; +	} +} + +static void proxy_do(struct proxybuf *); + +static void proxy() +{ +int	pipefd0[2], pipefd1[2], pipefd2[2]; +pid_t	p, p2; +int	waitstat; +struct proxybuf proxy_[3]; + +	if (!proxyarg)	return; + +	if (pipe(pipefd0) || pipe(pipefd1) || pipe(pipefd2)) +	{ +		perror("pipe"); +		exit(1); +	} + +	p=fork(); +	if (p == -1) +	{ +		perror("fork"); +		exit(1); +	} + +	/* +	** The parent goes on its merry way, but first makes sure that the +	** child process is OK. +	*/ + +	if (p) +	{ +		while ((p2=wait(&waitstat)) != p) +		{ +			if (p2 == -1 && errno != EINTR) +			{ +				perror("wait"); +				exit(1); +			} +		} +		if (waitstat) +			exit(0); +		sox_close(0); +		sox_close(1); +		sox_close(2); +		errno=EINVAL; +		if (sox_dup(pipefd0[0]) != 0 || +			sox_dup(pipefd1[1]) != 1 || +			sox_dup(pipefd2[1]) != 2) +		{ +			perror("dup(app)"); +			exit(1); +		} +		sox_close(pipefd0[0]); +		sox_close(pipefd0[1]); +		sox_close(pipefd1[0]); +		sox_close(pipefd1[1]); +		sox_close(pipefd2[0]); +		sox_close(pipefd2[1]); +		return; +	} + +	p=fork(); +	if (p == -1)	exit(1); +	if (p)	exit(0); + +	sox_close(pipefd0[0]); +	sox_close(pipefd1[1]); +	sox_close(pipefd2[1]); + +	proxy_init(&proxy_[0], 0, pipefd0[1]); +	proxy_init(&proxy_[1], pipefd1[0], 1); +	proxy_init(&proxy_[2], pipefd2[0], 2); +	proxy_do(proxy_); +	exit(0); +} + +static void proxy_do(struct proxybuf *p) +{ +fd_set	r, w; + +	for (;;) +	{ +	int	m=0; +	int	rc0, rc1, rc2; + +		FD_ZERO(&r); +		FD_ZERO(&w); + +		rc0=proxy_setfd(p, &r, &w, &m); +		rc1=proxy_setfd(p+1, &r, &w, &m); +		rc2=proxy_setfd(p+2, &r, &w, &m); + +		if (rc0 == 0 && rc1 == 0 && rc2 == 0) +			break; + +		if (rc0 == 0 || rc1 == 0 || rc2 == 0) +			alarm(10); + +		if (select(m+1, &r, &w, 0, 0) < 0) +		{ +			perror("select"); +			break; +		} + +		proxy_dofd(p, &r, &w); +		proxy_dofd(p+1, &r, &w); +		proxy_dofd(p+2, &r, &w); +	} +} +#endif diff --git a/tcpd/tcpdaccess.c b/tcpd/tcpdaccess.c new file mode 100644 index 0000000..9c5b9f6 --- /dev/null +++ b/tcpd/tcpdaccess.c @@ -0,0 +1,68 @@ +/* +** Copyright 1998 - 2000 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if	HAVE_CONFIG_H +#include	"config.h" +#endif +#include	<stdio.h> +#include	<errno.h> +#include	<string.h> +#include	<stdlib.h> +#include	<ctype.h> +#include	"dbobj.h" + + +static struct dbobj db; +static int db_isopen=0, db_isinit=0; + +int openaccess(const char *filename) +{ +	if (!db_isinit) +	{ +		dbobj_init(&db); +		db_isinit=1; +	} +	if (db_isopen) +	{ +		dbobj_close(&db); +		db_isopen=0; +	} + +	if (dbobj_open(&db, filename, "R")) +		return (-1); +	db_isopen=1; +	return (0); +} + +void closeaccess() +{ +	if (!db_isopen)	return; +	dbobj_close(&db); +	db_isopen=0; +} + +char *chkaccess(const char *ip) +{ +size_t	l; +char	*p, *q; + +	if (!db_isopen)	return (0); + + +	p=dbobj_fetch(&db, ip, strlen(ip), &l, ""); + +	if (!p)	return (0); +	q=(char *)malloc(l+1); +	if (!q) +	{ +		perror("malloc"); +		free(p); +		return (0); +	} +	memcpy(q, p, l); +	q[l]=0; +	free(p); +	return (q); +} diff --git a/tcpd/tcpremoteinfo.c b/tcpd/tcpremoteinfo.c new file mode 100644 index 0000000..b91a3e7 --- /dev/null +++ b/tcpd/tcpremoteinfo.c @@ -0,0 +1,163 @@ +/* +** Copyright 1998 - 2001 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if	HAVE_CONFIG_H +#include	"config.h" +#endif +#include	"tcpremoteinfo.h" +#include	"soxwrap/sconnect.h" + +#if HAVE_UNISTD_H +#include	<unistd.h> +#endif +#if HAVE_FCNTL_H +#include	<fcntl.h> +#endif +#include	<stdio.h> +#include	<errno.h> +#include	<string.h> + +#include	"soxwrap/soxwrap.h" + + +const char *tcpremoteinfo(const RFC1035_ADDR *laddr, int lport, +	const RFC1035_ADDR *raddr, int rport, const char **ostype) +{ +int	fd; +time_t	current_time, max_time; +fd_set	fds; +struct	timeval	tv; +static char buf[512]; +char	*bufptr; +int	bufleft, n; +char	*p; +char	*q; +RFC1035_NETADDR	sin; +const struct sockaddr *addr; +int	addrlen; + +	fd=rfc1035_mksocket(SOCK_STREAM, 0, &n); +	if (fd < 0)	return (0); + +	if (rfc1035_mkaddress(n, &sin, laddr, 0, &addr, &addrlen) < 0) +	{ +		close(fd); +		return (0); +	} + +	if (sox_bind(fd, addr, addrlen) < 0) +	{ +		sox_close(fd); +		return (0); +	} + +	time (¤t_time); +	max_time=current_time+30; + +	if (rfc1035_mkaddress(n, &sin, raddr, htons(113), &addr, &addrlen) < 0) +	{ +		sox_close(fd); +		return (0); +	} + +	if (s_connect(fd, addr, addrlen, max_time - current_time) < 0) +	{ +		sox_close(fd); +		return (0); +	} + +	sprintf(buf, "%d,%d\r\n", ntohs(rport), ntohs(lport)); +	bufptr=buf; +	bufleft=strlen(buf); +	while (bufleft) +	{ +		time(¤t_time); +		if (current_time >= max_time) +		{ +			sox_close(fd); +			return (0); +		} + +		FD_ZERO(&fds); +		FD_SET(fd, &fds); +		tv.tv_sec=max_time-current_time; +		tv.tv_usec=0; +		if (sox_select(fd+1, 0, &fds, 0, &tv) != 1 || +			!FD_ISSET(fd, &fds)) +		{ +			sox_close(fd); +			return (0); +		} +		n=sox_write(fd, bufptr, bufleft); +		if (n <= 0) +		{ +			sox_close(fd); +			return (0); +		} +		bufptr += n; +		bufleft -= n; +	} + +	bufptr=buf; +	bufleft=sizeof(buf); +	do +	{ +		if (bufleft == 0) +		{ +			sox_close(fd); +			return (0); +		} + +		time(¤t_time); +		if (current_time >= max_time) +		{ +			sox_close(fd); +			return (0); +		} + +		FD_ZERO(&fds); +		FD_SET(fd, &fds); +		tv.tv_sec=max_time-current_time; +		tv.tv_usec=0; +		if (sox_select(fd+1, &fds, 0, 0, &tv) != 1 || +			!FD_ISSET(fd, &fds)) +		{ +			sox_close(fd); +			return (0); +		} + +		n=sox_read(fd, bufptr, bufleft); +		if (n <= 0) +		{ +			sox_close(fd); +			return (0); +		} +		bufptr += n; +		bufleft -= n; +	} while (bufptr[-1] != '\n'); +	sox_close(fd); +	bufptr[-1]=0; +	--bufptr; +	if (bufptr > buf && bufptr[-1] == '\r') +		bufptr[-1]=0; + +	if ((p=strchr(buf, ':')) == 0) +		return (0); + +	q=++p; +	if ((p=strchr(p, ':')) == 0) +		return (0); + +	*p++=0; +	q=strtok(q, " \t"); +	if (!q || strcmp(q, "USERID"))	return (0); +	if (ostype)	*ostype=p; +	if ((p=strchr(p, ':')) == 0) +		return (0); +	*p++=0; +	while (*p && (*p == ' ' || *p == '\t'))	p++; +	return (p); +} + diff --git a/tcpd/tcpremoteinfo.h b/tcpd/tcpremoteinfo.h new file mode 100644 index 0000000..95ba3d9 --- /dev/null +++ b/tcpd/tcpremoteinfo.h @@ -0,0 +1,30 @@ +#ifndef	tcpremoteinfo_h +#define	tcpremoteinfo_h + +/* +** Copyright 1998 - 1999 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +/* +*/ + +#include	<sys/types.h> +#include	<sys/socket.h> +#include	<netinet/in.h> +#include	<arpa/inet.h> +#include	"rfc1035/rfc1035.h" + +#ifdef	__cplusplus +extern "C" { +#endif + +const char *tcpremoteinfo(const RFC1035_ADDR *, int,		/* Local */ +	const RFC1035_ADDR *, int,				/* Remote */ +	const char **); + +#ifdef	__cplusplus +} ; +#endif + +#endif diff --git a/tcpd/testsuite b/tcpd/testsuite new file mode 100644 index 0000000..73fab6b --- /dev/null +++ b/tcpd/testsuite @@ -0,0 +1,38 @@ +# Copyright 2002 Double Precision, Inc. +# See COPYING for distribution information. +# + +doit() { +  echo "--./tlscachetest $1--" +  ./tlscachetest "$1" +} + +rm -f test.dat + +doit +00000000 +doit +11111111 +doit +22222222 +doit +33333333 +doit +44444444 +doit +55555555 +doit +66666666 +doit +77777777 +doit +88888888 +doit +99999999 +doit +000000000 +doit +111111111 +doit +222222222 +doit +333333333 +doit +444444444 +doit +55555555 +doit +66666666 +doit +77777777 +doit +88888888 +doit +99999999 +doit +00000000 +doit +11111111 +doit +22222222 +doit -00000000-77777777 +doit -11111111-55555555 + +rm -f test.dat diff --git a/tcpd/testsuite.txt b/tcpd/testsuite.txt new file mode 100644 index 0000000..1c640a5 --- /dev/null +++ b/tcpd/testsuite.txt @@ -0,0 +1,128 @@ +--./tlscachetest +00000000-- +00000000 +--./tlscachetest +11111111-- +11111111 +00000000 +--./tlscachetest +22222222-- +22222222 +11111111 +00000000 +--./tlscachetest +33333333-- +33333333 +22222222 +11111111 +00000000 +--./tlscachetest +44444444-- +44444444 +33333333 +22222222 +11111111 +00000000 +--./tlscachetest +55555555-- +55555555 +44444444 +33333333 +22222222 +11111111 +--./tlscachetest +66666666-- +66666666 +55555555 +44444444 +33333333 +22222222 +--./tlscachetest +77777777-- +77777777 +66666666 +55555555 +44444444 +33333333 +--./tlscachetest +88888888-- +88888888 +77777777 +66666666 +55555555 +44444444 +--./tlscachetest +99999999-- +99999999 +88888888 +77777777 +66666666 +55555555 +--./tlscachetest +000000000-- +000000000 +99999999 +88888888 +77777777 +--./tlscachetest +111111111-- +111111111 +000000000 +99999999 +88888888 +--./tlscachetest +222222222-- +222222222 +111111111 +000000000 +99999999 +--./tlscachetest +333333333-- +333333333 +222222222 +111111111 +000000000 +--./tlscachetest +444444444-- +444444444 +333333333 +222222222 +111111111 +--./tlscachetest +55555555-- +55555555 +444444444 +333333333 +222222222 +--./tlscachetest +66666666-- +66666666 +55555555 +444444444 +333333333 +--./tlscachetest +77777777-- +77777777 +66666666 +55555555 +444444444 +--./tlscachetest +88888888-- +88888888 +77777777 +66666666 +55555555 +--./tlscachetest +99999999-- +99999999 +88888888 +77777777 +66666666 +--./tlscachetest +00000000-- +00000000 +99999999 +88888888 +77777777 +--./tlscachetest +11111111-- +11111111 +00000000 +99999999 +88888888 +--./tlscachetest +22222222-- +22222222 +11111111 +00000000 +99999999 +88888888 +--./tlscachetest -00000000-77777777-- +22222222 +11111111 +77777777 +99999999 +88888888 +--./tlscachetest -11111111-55555555-- +22222222 +55555555 +77777777 +99999999 +88888888 diff --git a/tcpd/tlscache.c b/tcpd/tlscache.c new file mode 100644 index 0000000..0a4cd5d --- /dev/null +++ b/tcpd/tlscache.c @@ -0,0 +1,714 @@ +/* +** Copyright 2002 Double Precision, Inc. +** See COPYING for distribution information. +*/ +#include	"config.h" +#include	"numlib/numlib.h" +#include	"liblock/config.h" +#include	"liblock/liblock.h" +#include	<stdio.h> +#include	<string.h> +#include	<stdlib.h> +#include	<errno.h> +#include	<ctype.h> + +#if HAVE_SYS_STAT_H +#include	<sys/stat.h> +#endif +#if HAVE_FCNTL_H +#include	<fcntl.h> +#endif + +#include	"tlscache.h" + + +/* +** The cache file begins with the following record: +*/ + +struct hdr { +	off_t filesize;		/* Size of the file, don't trust fstat */ +	off_t head, tail;	/* Head and tail ptrs */ +	off_t first;		/* See below */ +}; + +#ifndef TLSCACHEMINSIZE +#define TLSCACHEMINSIZE 16384 +#endif + +#define BORK(p) BORK2(__LINE__, (p)) + +static void BORK2(int l, const char *p) +{ +	fprintf(stderr, "ALERT: tlscache.c(%d): corruption detected in %s\n", +		(l), (p)); +} + +/* +** Cached SSL session objects are written starting at the end of the file +** the file, growing to the beginning of the file.  The head pointer +** points to the most recently added cached object.  When the beginning of +** the file is reached, it's wrapped around. +** +** After the wraparound, old SSL session objects are freed. starting with +** the tail ptr, to make room for newer object.  There may be unused space +** between struct hdr, and the first cached object, the 'first' pointer +** helps me find the first cached object, when searching. +** When searching for a cached object, begin at the head ptr (most recent, +** and continue until we find an object referenced by the tail ptr). +** +** Each cached object carries the following header. +*/ + +struct obj { +	size_t prev_size;	/* Size of the previous cached object, +				** this must be the first member of this +				** struct. +				*/ + +	size_t my_size;		/* Size of this cached object */ +}; + +/* Read cnt number of bytes, or else */ + +static int my_read(int fd, void *buffer, size_t cnt) +{ +	char *p=(char *)buffer; + +	while (cnt > 0) +	{ +		int n=read(fd, p, cnt); + +		if (n <= 0) +			return n; +		p += n; +		cnt -= n; +	} +	return 1; +} + +/* Write cnt number of bytes, or else */ + +static int my_write(int fd, const void *buffer, size_t cnt) +{ +	const char *p=(const char *)buffer; + +	while (cnt > 0) +	{ +		int n=write(fd, p, cnt); + +		if (n <= 0) +			return -1; +		p += n; +		cnt -= n; +	} +	return 0; +} + +static int init(struct CACHE *, off_t s); + +void tls_cache_close(struct CACHE *p) +{ +	if (p->filename != NULL) +		free(p->filename); +	if (p->fd >= 0) +		close(p->fd); +	free(p); +} + +/* +** Open a cache file, creating one if necessary +*/ + +struct CACHE *tls_cache_open(const char *filename, off_t req_size) +{ +	struct CACHE *p=malloc(sizeof(struct CACHE)); +	struct hdr h; +	int rc; + +	if (!p) return NULL; + +	if ((p->fd=open(filename, O_RDWR|O_CREAT, 0600)) < 0) +	{ +		free(p); +		return NULL; +	} + +	if ((p->filename=strdup(filename)) == NULL) +	{ +		close(p->fd); +		free(p); +		return (NULL); +	} + +	rc=my_read(p->fd, &h, sizeof(h)); + +	if (rc < 0) +	{ +		tls_cache_close(p); +		return (NULL); +	} + +	if (rc == 0 || h.filesize == 0) +	{ +		/* Once again, but this time lock it */ + +		if (ll_lock_ex(p->fd) < 0 || +		    lseek(p->fd, 0, SEEK_SET) < 0) +		{ +			tls_cache_close(p); +			return (NULL); +		} + +		rc=my_read(p->fd, &h, sizeof(h)); + +		if (rc < 0) +		{ +			tls_cache_close(p); +			return (NULL); +		} + +		if (rc == 0 || h.filesize == 0) +		{ +			if (init(p, req_size)) +			{ +				tls_cache_close(p); +				return (NULL); +			} +		} +		ll_unlock_ex(p->fd); +	} +	return p; +} + +static int doadd(struct CACHE *p, const char *val, size_t vallen); + +int tls_cache_add(struct CACHE *p, const char *val, size_t vallen) +{ +	int rc; + +	if (p->fd < 0) +		return (0);	/* Previous error invalidated obj */ + +	if (ll_lock_ex(p->fd) < 0) +	{ +		close(p->fd); +		p->fd= -1; +		return (-1); +	} + +	rc=doadd(p, val, vallen); + +	if (rc < 0 && p->fd >= 0) +	{ +		close(p->fd); +		p->fd= -1; +		unlink(p->filename);	/* Blow it away, something's wrong */ +		perror("ALERT: tlscache.c: "); +		fprintf(stderr, "ALERT: tlscache.c: removing %s\n", +			p->filename); +	} + +	if (p->fd >= 0 && ll_unlock_ex(p->fd) < 0) +	{ +		close(p->fd); +		p->fd= -1; +		rc= -1; +	} + +	if (rc != 0) +		rc= -1; + +	return rc; +} + +/* +** Read the header, and do a simple sanity check +*/ + +static int readhdr(struct CACHE *p, struct hdr *h) +{ +	if (lseek(p->fd, 0, SEEK_SET) < 0 || +	    my_read(p->fd, h, sizeof(*h)) <= 0) +	{ +		BORK(p->filename); +		return (-1); +	} + +	if (h->filesize < TLSCACHEMINSIZE || h->head >= h->filesize || +	    h->tail >= h->filesize || h->first >= h->filesize || +	    h->head < 0 || h->tail < 0 || h->first < 0 || +	    h->first > h->head || h->first > h->tail) +	{ +		BORK(p->filename); +		return (-1);	/* Sanity check */ +	} +	return (0); +} + +static int writehdr(struct CACHE *p, const struct hdr *h) +{ +	if (lseek(p->fd, 0, SEEK_SET) < 0 || +	    my_write(p->fd, h, sizeof(*h)) < 0) +	{ +		BORK(p->filename); +		return -1; +	} +	return 0; +} + +/* +** Read the header of a cached object, and do a sanity check +*/ + +static int readobj(struct CACHE *p, off_t w, +		   const struct hdr *h, struct obj *o) +{ +	if (lseek(p->fd, w, SEEK_SET) < 0 || +	    my_read(p->fd, o, sizeof(*o)) <= 0) +	{ +		BORK(p->filename); +		return (-1); +	} + +	if (o->prev_size < sizeof(*o) || o->my_size < sizeof(*o) || +	    o->prev_size >= h->filesize || o->my_size >= h->filesize || +	    h->filesize - w < o->my_size) +	{ +		BORK(p->filename); +		errno=EIO; +		return (-1);	/* Sanity check */ +	} +	return 0; +} + + +static int doadd(struct CACHE *p, const char *val, size_t vallen) +{ +	struct hdr h; +	struct obj o; +	int timer=0; +	int first=0; +	char *buf; + +	if (readhdr(p, &h)) +		return -1; + +	/* Keep trying to allocate sufficient space in the cache file */ + +	for (;;) +	{ +		if (++timer > 100) +		{ +			BORK(p->filename); +			errno=EIO; +			return (-1);	/* Sanity check */ +		} + +		if (h.head == 0 && h.tail == 0)	/* First time */ +		{ +			if (vallen + sizeof(struct obj) + +			    sizeof(struct hdr) > h.filesize) +			{ +				errno=ENOSPC; +				return 1; +			} + +			/* First cached object goes at the end */ + +			h.head=h.tail=h.filesize - vallen - sizeof(struct obj); +			first=1; +			break; +		} + +		if (h.head <= h.tail)	/* Not wrapped around */ +		{ +			if (h.head >= sizeof(struct hdr) + +			    sizeof(struct obj) + vallen) +			{ +				h.head -= sizeof(struct obj) + vallen; +				break; +				/* Room earlier in the file */ +			} + +			/* +			** No room before, we must now wrap around.  Find +			** where the last object ends, and see if there's +			** enough room between the end of the last object, +			** and the end of the file, to save the new object. +			*/ + +			if (readobj(p, h.tail, &h, &o) < 0) +				return -1; + +			if (h.filesize - h.tail - o.my_size >= +			    sizeof(struct obj) + vallen) +			{ +				h.first=h.head; +				h.head=h.filesize - vallen - +					sizeof(struct obj); +				/* Room to wrap around */ + +				break; +			} +		} +		else	/* We're currently wrapped around, so all the free +			** space is from tail to head. +			*/ +		{ +			if (readobj(p, h.tail, &h, &o) < 0) +				return -1; + +			if (h.head >= h.tail + o.my_size + +			    sizeof(struct obj) + vallen) +			{ +				h.head -= sizeof(struct obj) + vallen; +				break; +			} +		} + +		if (h.head == h.tail)	/* Sanity check */ +		{ +			errno=ENOSPC; +			return 1; +		} + +		/* Pop one off tail */ + +		if (readobj(p, h.tail, &h, &o)) +			return -1; + +		if (sizeof(h) + o.prev_size <= h.tail) +		{ +			h.tail -= o.prev_size; + +			if (writehdr(p, &h)) +				return (-1); +			continue; +		} + +		if (h.tail != h.first) +		{ +			BORK(p->filename); +			errno=EIO; +			return (-1);	/* Sanity check */ +		} + +		h.first=0; +		h.tail=h.filesize - o.prev_size; + +		if (h.tail < h.first) +		{ +			BORK(p->filename); +			errno=EIO; +			return (-1);	/* Sanity check */ +		} + +		if (writehdr(p, &h)) +			return (-1); +	} + +	buf=malloc(vallen + sizeof(o) + sizeof(o.prev_size)); + +	if (!buf) +		return (1); + +	o.prev_size=0; +	o.my_size=vallen + sizeof(o); +	memcpy(buf, &o, sizeof(o)); +	memcpy(buf + sizeof(o), val, vallen); +	o.prev_size=o.my_size; +	memcpy(buf + sizeof(o) + vallen, &o.prev_size, sizeof(o.prev_size)); + +	if (lseek(p->fd, h.head, SEEK_SET) < 0) +		return (-1); + +	if (h.head + sizeof(o) + vallen < h.filesize) +	{ +		if (my_write(p->fd, buf, sizeof(o)+vallen+sizeof(o.prev_size)) < 0) +			return -1; +	} +	else +	{ +		if (my_write(p->fd, buf, sizeof(o)+vallen) < 0 || +		    (!first && (lseek(p->fd, h.first, SEEK_SET) < 0 || +				my_write(p->fd, &o.prev_size, +					 sizeof(o.prev_size)) < 0))) +			return -1; +	} +				 +	return writehdr(p, &h); +} + +static int init(struct CACHE *p, off_t size) +{ +	char buffer[BUFSIZ]; +	off_t c; +	struct hdr h; + +	if (size < TLSCACHEMINSIZE) +	{ +		errno=EINVAL; +		return -1; +	} + +	if (lseek(p->fd, 0, SEEK_SET) < 0) +		return -1; + +	memset(buffer, 0, sizeof(buffer)); + +	c=size; + +	while (c > 0) +	{ +		int i; + +		off_t n=c; + +		if (n > sizeof(buffer)) +			n=sizeof(buffer); + +		i=write(p->fd, buffer, n); + +		if (i <= 0) +			return -1; + +		c -= i; +	} + +	memset(&h, 0, sizeof(h)); +	h.filesize=size; + +	if (lseek(p->fd, 0, SEEK_SET) < 0 || +	    my_write(p->fd, &h, sizeof(h))) +		return (-1); +	return (0); +} + +static int dowalk(struct CACHE *cache, +		  int (*walk_func)(void *rec, size_t recsize, +				   int *doupdate, +				   void *arg), +		  void *arg); + +int tls_cache_walk(struct CACHE *p, +		   int (*walk_func)(void *rec, size_t recsize, +				    int *doupdate, +				    void *arg), +		   void *arg) +{ +	int rc; + +	if (p->fd < 0) +		return (0);	/* Previous error invalidated obj */ + +	if (ll_lockfd(p->fd, ll_readlock||ll_whence_start|ll_wait, 0, 0) < 0) +	{ +		/* Some locking methods don't support readonly locks */ + +		if (ll_lock_ex(p->fd) < 0) +		{ +			close(p->fd); +			p->fd= -1; +			return (-1); +		} +	} + +	rc=dowalk(p, walk_func, arg); + +	if (rc < 0 && p->fd >= 0) +	{ +		close(p->fd); +		p->fd= -1; +		unlink(p->filename); +		perror("ALERT: tlscache.c: "); +		fprintf(stderr, "ALERT: tlscache.c: removing %s\n", +			p->filename); +	} + +	if (p->fd >= 0 && ll_unlock_ex(p->fd) < 0) +	{ +		close(p->fd); +		p->fd= -1; +		rc= -1; +	} + +	return rc; +} + +/* Buffered reads when searching, for speed */ + +struct walkbuf { +	char buffer[BUFSIZ]; +	char *bufptr; +	int left; +}; + +static int buf_read(int fd, struct walkbuf *w, +		    const void *buffer, size_t cnt) +{ +	char *p=(char *)buffer; + +	while (cnt > 0) +	{ +		if (w->left <= 0) +		{ +			w->left=read(fd, w->buffer, sizeof(w->buffer)); + +			if (w->left <= 0) +				return -1; +			w->bufptr=w->buffer; +		} + +		*p++ = *w->bufptr++; +		--w->left; +		--cnt; +	} +	return 1; +} + +static int dowalk(struct CACHE *p, +		  int (*walk_func)(void *rec, size_t recsize, +				   int *doupdate, void *arg), +		  void *arg) +{ +	struct hdr h; +	struct obj o; +	char *buf=NULL; +	size_t bufsize=0; +	int rc; +	int counter; +	struct walkbuf wb; +	int updateflag; + +	off_t pos; +	off_t lastpos; + +	if (readhdr(p, &h)) +		return -1; + +	if (h.head == 0 && h.tail == 0)	/* First time */ +		return (0); + +	pos=h.head; +	if (lseek(p->fd, pos, SEEK_SET) < 0) +		return (-1); + +	counter=0; +	wb.left=0; +	for (;;) +	{ +		if (++counter > h.filesize / sizeof(o)) +		{ +			BORK(p->filename); +			return (-1); +		} + +		if (h.filesize - pos < sizeof(o)) +		{ +			BORK(p->filename); +			errno=EIO; +			if (buf) +				free(buf); +			return (-1);	/* Sanity check */ +		} + +		if (buf_read(p->fd, &wb, &o, sizeof(o)) <= 0) +		{ +			if (buf) +				free(buf); +			return (-1); +		} + +		if (h.filesize - pos < o.my_size || o.my_size < sizeof(o)) +		{ +			BORK(p->filename); +			errno=EIO; +			if (buf) +				free(buf); +			return (-1);	/* Sanity check */ +		} + +		if (buf == NULL || bufsize < o.my_size - sizeof(o)+1) +		{ +			char *newbuf; + +			bufsize=o.my_size - sizeof(o)+1; + +			if ((newbuf=buf ? realloc(buf, bufsize): +			     malloc(bufsize)) == NULL) +			{ +				free(buf); +				return (-1); +			} +			buf=newbuf; +		} + +		if (buf_read(p->fd, &wb, buf, o.my_size - sizeof(o)) <= 0) +		{ +			free(buf); +			return (-1); +		} + +		updateflag=0; + +		rc= (*walk_func)(buf, o.my_size - sizeof(o), &updateflag, arg); + +		if (updateflag && rc >= 0) +		{ +			if (lseek(p->fd, pos + sizeof(o), SEEK_SET) < 0 || +			    my_write(p->fd, buf, o.my_size - sizeof(o)) < 0) +			{ +				free(buf); +				return (-1); +			} +			wb.left=0; +		} + +		if (rc != 0) +		{ +			free(buf); +			if (rc < 0) +				rc=1; +			return (rc); +		} + +		if (pos == h.tail) +			break; + +		lastpos=pos; +		pos += o.my_size; + +		if (pos < h.filesize) +		{ +			if (lastpos < h.tail && pos > h.tail) +			{ +				BORK(p->filename); +				errno=EIO; +				free(buf); +				return (-1); +			} +		} +		else +		{ +			pos=h.first; +			if (h.first < sizeof(h)) +			{ +				BORK(p->filename); +				free(buf); +				errno=EIO; +				return (-1); +			} + +			if (lseek(p->fd, pos, SEEK_SET) < 0) +			{ +				free(buf); +				return (-1); +			} +			wb.left=0; +		} +	} +	if (buf) +		free(buf); +	return (0); +} diff --git a/tcpd/tlscache.h b/tcpd/tlscache.h new file mode 100644 index 0000000..5789ca1 --- /dev/null +++ b/tcpd/tlscache.h @@ -0,0 +1,81 @@ +/* +** Copyright 2002 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#ifndef tlscache_h +#define tlscache_h + +#include	"config.h" + +#include	<sys/types.h> +#include	<unistd.h> + + +/* +** This module implements a cache for SSL sessions, however the interface +** is generic enough to accomodate caching of any kind of small object. +** The cache is a disk file, that contains a circular cache (when the +** end of the file is reached, we wrap around and begin adding new cached +** object to the beginning of the file, overwriting the oldest one). +** +** Well, that's the general idea, but technically it's the other way around. +** The cache begins at the end of the file, and grows towards the beginning +** of the file, then wraps around to the end of the file again.  The cache +** is searched by reading the file starting with wherever the current add +** position is, then wrapping around if necessary.  Hence, the cache file +** is searched starting with the most recently added object; which is the +** expected usage pattern. +** +** File locking is used to implement concurrency. +*/ + +struct CACHE { +	int fd; +	char *filename; +}; + +/* +** Open a cache file.  If it doesn't exist, create one with the indicated +** size (in bytes). +*/ + +struct CACHE *tls_cache_open(const char *filename, +			     off_t req_size);	/* In bytes */ + +/* +** Close and deallocate the CACHE object. +*/ +void tls_cache_close(struct CACHE *p); + +/* +** Cache a new object, val, vallen bytes long. +*/ + +int tls_cache_add(struct CACHE *p, const char *val, size_t vallen); + +/* +** Read the cache file.  walk_func is a callback function that's repeatedly +** called for each cached object.  walk_func should return 0 to continue +** with the next cached object; a positive value to stop reading the cache +** (object found); a negative value to stopr eading the cache and remove it +** (if it's corrupted, for some reason).  walk_func receives 'arg', a +** transparent pointer. +** +** tls_cache_walk returns 0 when all cached objects were read, or the non-0 +** return value from the callback function.  tls_cache_walk will return -1 +** if it itself encounters an error. +** +** The callback function may modify rec, and set *doupdate to non-zero in +** order to update the cached record (the return code is still processed in +** the normal way).  The updated record will be saved if the callback function +** terminate with 0 or a positive return code. +*/ + +int tls_cache_walk(struct CACHE *p, +		   int (*walk_func)(void *rec, size_t recsize, +				    int *doupdate, void *arg), +		   void *arg); + + +#endif diff --git a/tcpd/tlscachetest.c b/tcpd/tlscachetest.c new file mode 100644 index 0000000..ce58d1d --- /dev/null +++ b/tcpd/tlscachetest.c @@ -0,0 +1,72 @@ +/* +** Copyright 2002-2006 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#define TLSCACHEMINSIZE (sizeof(struct hdr) + 5 * (sizeof(struct obj)+8)) +#include "tlscache.c" + + +static int printcache(void *rec, size_t recsize, int *doupdate, +		      void *arg) +{ +	if (fwrite((const char *)rec, recsize, 1, stdout) == 1) +		printf("\n"); +	return 0; +} + +static int replacecache(void *rec, size_t recsize, int *doupdate, +		      void *arg) +{ +	const char *p=(const char *)arg; +	const char *q; + +	if ((q=strchr(p, '-')) == NULL || strlen(q+1) != q-p) +		return (0); + +	if (recsize == q-p && memcmp(rec, p, q-p) == 0) +	{ +		memcpy(rec, q+1, q-p); +		*doupdate=1; +	} +	return 0; +} + +int main(int argc, char **argv) +{ +	struct CACHE *p=tls_cache_open("test.dat", TLSCACHEMINSIZE); + +	if (!p) +	{ +		perror("test.dat"); +		return (-1); +	} + +	if (argc > 1) +	{ +		char *s=argv[1]; + +		if (*s == '+') +		{ +			++s; +			if (tls_cache_add(p, s, strlen(s))) +			{ +				perror("tls_cache_add"); +			} +		} + +		if (*s == '-') +		{ +			if (tls_cache_walk(p, replacecache, s+1) < 0) +			{ +				perror("tls_cache_walk"); +				exit(1); +			} +		} +	} + +	if (tls_cache_walk(p, printcache, NULL) < 0) +		perror("tls_cache_walk"); +	tls_cache_close(p); +	return (0); +} diff --git a/tcpd/tlsclient.c b/tcpd/tlsclient.c new file mode 100644 index 0000000..f68bb16 --- /dev/null +++ b/tcpd/tlsclient.c @@ -0,0 +1,541 @@ +/* +** Copyright 2001-2008 Double Precision, Inc. +** See COPYING for distribution information. +*/ +#include	"config.h" +#include	"numlib/numlib.h" +#include	<stdio.h> +#include	<string.h> +#include	<stdlib.h> +#include	<errno.h> +#include	<ctype.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> +#include	<sys/time.h> +#if	HAVE_SYS_TYPES_H +#include	<sys/types.h> +#endif +#if	HAVE_SYS_STAT_H +#include	<sys/stat.h> +#endif +#if	HAVE_SYS_WAIT_H +#include	<sys/wait.h> +#endif +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif + +#include	"tlsclient.h" + + + +#define ERRMSG(s) (cinfo->errmsg[0]=0, \ +		strncat(cinfo->errmsg, (s), sizeof(cinfo->errmsg)-3)) + +#define SYSERRMSG (strncat(strcpy(cinfo->errmsg, "Failed: "), \ +		strerror(errno), sizeof(cinfo->errmsg)-15)) + +void couriertls_init(struct couriertls_info *cinfo) +{ +	memset(cinfo, 0, sizeof(*cinfo)); +	cinfo->cipher=cinfo->version="Unknown"; +} + +/* +** Convenient function to start couriertls, return any client certificate +** error message, and the x509 certificate info. +*/ + +static int do_couriertls_start(char **, struct couriertls_info *); + +int couriertls_start(char **args, struct couriertls_info *cinfo) +{ +	int rc=do_couriertls_start(args, cinfo); +	int l; +	char *p; + +	if (rc && cinfo->errmsg[0] == 0) +		strcpy(cinfo->errmsg, "Failed to initialize TLS/SSL\n"); + +	l=strlen(cinfo->errmsg); + +	while (l > 0 && cinfo->errmsg[l-1] == '\n') +		--l; +	cinfo->errmsg[l]=0; + +	if (rc || cinfo->x509info == 0) +		return (rc); + +	cinfo->x509info[cinfo->x509info_len]=0; +	p=strtok(cinfo->x509info, "\r\n"); + +	while (p) +	{ +		int i; + +		for (i=0; p[i]; i++) +			if (!isalpha(p[i])) +				break; + +		if (p[i] != ':') +		{ +			p=strtok(NULL, "\r\n"); +			continue; +		} +		p[i++]=0; + +		/* +		** IMPORTANT: UCase *MUST* match the output of couriertls. +		** I'd love to use strcasecmp, here, but certain glibc +		** locale break the standard case of lower ascii chset +		** range. +		*/ + +		if (strcmp(p, "Subject") == 0) +		{ +			struct tls_subject *subj, *subj2; +			struct tls_subjitem **itemptr; + +			p += i; + +			for (subj=cinfo->first_subject; subj && subj->next; +			     subj=subj->next) +				; + +			subj2=(struct tls_subject *) +				malloc(sizeof(struct tls_subject)); +			if (!subj2) +			{ +				SYSERRMSG; +				return (-1); +			} + +			if (subj) +				subj->next=subj2; +			else +				cinfo->first_subject=subj2; + +			subj2->next=0; +			subj2->firstitem=0; +			itemptr= &subj2->firstitem; + +			while ( p && (*p == 0 +				      || isspace((int)(unsigned char)*p))) +			{ +				while (*p && isspace((int)(unsigned char)*p)) +					++p; +				for (i=0; p[i]; i++) +					if (!isalpha((int)(unsigned char)p[i])) +						break; +				if (p[i] != '=') +				{ +					p=strtok(NULL, "\r\n"); +					continue; +				} +				p[i++]=0; + +				*itemptr= (struct tls_subjitem *) +					malloc(sizeof (struct tls_subjitem)); + +				if (!*itemptr) +				{ +					SYSERRMSG; +					return (-1); +				} + +				(*itemptr)->name=p; +				(*itemptr)->value=p+i; +				(*itemptr)->nextitem=0; + +				itemptr= &(*itemptr)->nextitem; +				p=strtok(NULL, "\r\n"); +			} +			continue; +		} + +		if (strcmp(p, "Cipher") == 0) +		{ +			p += i; +			while (*p && isspace((int)(unsigned char)*p)) +				++p; +			cinfo->cipher=p; +		} +		else if (strcmp(p, "Version") == 0) +		{ +			p += i; +			while (*p && isspace((int)(unsigned char)*p)) +				++p; +			cinfo->version=p; +		} +		else if (strcmp(p, "Bits") == 0) +		{ +			p += i; +			while (*p && isspace((int)(unsigned char)*p)) +				++p; +			cinfo->bits=atoi(p); +		} +		p=strtok(NULL, "\r\n"); +	} + +	return (0); +} + +const char *couriertls_get_subject(struct couriertls_info *cinfo, +				   const char *subject) +{ +	struct tls_subject *subj; +	struct tls_subjitem *item, *p; + +	if ((subj=cinfo->first_subject) == 0) +		return NULL; + +	p=NULL; + +	for (item=subj->firstitem; item; item=item->nextitem) +	{ +		const char *a=item->name; +		const char *b=subject; + +		while (*a && *b) +		{ +			int ca= *a++; +			int cb= *b++; + +			/* Locale muddies things up, do this by hand */ + +			if (ca >= 'a' && ca <= 'z') +				ca -= 'a' - 'A'; + +			if (cb >= 'a' && cb <= 'z') +				cb -= 'a' - 'A'; + +			if (ca != cb) +				break; +		} + +		if (!*a && !*b) +			p=item; +		/* +		** We want the last one, to match the behavior when couriertls +		** passes this stuff via the environment. +		*/ +	} + +	if (p) +		return p->value; +	return (0); +} + +void couriertls_export_subject_environment(struct couriertls_info *cinfo) +{ +	struct tls_subject *subj; +	struct tls_subjitem *item; + +	if ((subj=cinfo->first_subject) == 0) +		return; + +	for (item=subj->firstitem; item; item=item->nextitem) +	{ +		char *a=malloc(strlen(item->name)+20); +		const char *b=item->value; +		char *p; + +		if (!a) continue; + +		strcat(strcpy(a, "TLS_SUBJECT_"), item->name); + +		for (p=a; *p; p++) +			if (*p >= 'a' && *p <= 'z') +				*p -= 'a' - 'A'; + +		setenv(a, b, 1); +		free(a); +	} +} + +static int do_couriertls_start(char **args, struct couriertls_info *cinfo) +{ +	pid_t p, p2; +	int waitstat; +	char **argvec; +	int nargs; +	char readbuf[BUFSIZ]; +	fd_set fdr; +	int statuspipe_fd[2]; +	int x509_fd[2]; + + +	/* Create the pipes, and run couriertls */ + +	for (nargs=0; args[nargs]; nargs++) +		; + +	argvec=malloc(sizeof(char *)*(nargs+10)); +	if (!argvec) +	{ +		SYSERRMSG; +		return (-1); +	} + +	if (pipe(statuspipe_fd) < 0) +	{ +		free(argvec); +		SYSERRMSG; +		return (-1); +	} + +	if (pipe(x509_fd) < 0) +	{ +		close(statuspipe_fd[0]); +		close(statuspipe_fd[1]); +		free(argvec); +		SYSERRMSG; +		return (-1); +	} + +	if ((p=fork()) < 0) +	{ +		close(x509_fd[0]); +		close(x509_fd[1]); +		close(statuspipe_fd[0]); +		close(statuspipe_fd[1]); +		free(argvec); +		SYSERRMSG; +		return (-1); +	} + +	/* Child process starts another child process, which runs couriertls */ + +	if (p == 0) +	{ +		static const char msg[]="500 Unable to start couriertls - insufficient resources.\n"; + +		FILE *fp; +		char miscbuf[NUMBUFSIZE]; +		char statusfd_buf[NUMBUFSIZE+40]; +		char x509fd_buf[NUMBUFSIZE+40]; +		const char *s; + +		close(statuspipe_fd[0]); +		close(x509_fd[0]); + +		fp=fdopen(statuspipe_fd[1], "w"); + +		if (!fp) +		{ +			if (write(statuspipe_fd[1], msg, sizeof(msg)-1) < 0) +				; /* Ignore */ +			exit(0); +		} + +		if ((p=fork()) != 0) +		{ +			if (p < 0) +			{ +				fprintf(fp, +					"500 Unable to start couriertls: %s\n", +					strerror(errno)); +				fflush(fp); +			} +			exit(0); +		} + +		argvec[0]="couriertls"; +		argvec[1]=strcat(strcpy(statusfd_buf, "-statusfd="), +				 libmail_str_size_t(statuspipe_fd[1], miscbuf)); +		argvec[2]=strcat(strcpy(x509fd_buf, "-printx509="), +				 libmail_str_size_t(x509_fd[1], miscbuf)); +		for (nargs=0; (argvec[nargs+3]=args[nargs]) != 0; nargs++) +			; + +		s=getenv("COURIERTLS"); +		if (!s || !*s) +			s="couriertls"; + +		execv(s, argvec); +		fprintf(fp, "500 Unable to start couriertls: %s\n", +			strerror(errno)); +		fflush(fp); +		exit(0); +	} + +	/* The parent wait for the first child to exit */ + +	close(statuspipe_fd[1]); +	close(x509_fd[1]); + +	while ((p2=wait(&waitstat)) != p) +		if (p2 < 0 && errno == ECHILD) +			break; + +	if (p2 != p || !WIFEXITED(waitstat) || WEXITSTATUS(waitstat)) +	{ +		close(statuspipe_fd[0]); +		close(x509_fd[0]); +		ERRMSG("500 Error starting couriertls."); +		return (-1); +	} + +	/* Now, we need to read from two pipes simultaneously, and save the +	** results. +	*/ + +	while (statuspipe_fd[0] >= 0 || x509_fd[0] >= 0) +	{ +		FD_ZERO(&fdr); +		if (statuspipe_fd[0] >= 0) +			FD_SET(statuspipe_fd[0], &fdr); +		if (x509_fd[0] >= 0) +			FD_SET(x509_fd[0], &fdr); +		if (select( (statuspipe_fd[0] > x509_fd[0] ? +			     statuspipe_fd[0]:x509_fd[0])+1, +			    &fdr, NULL, NULL, NULL) < 0) +		{ +			close(statuspipe_fd[0]); +			close(x509_fd[0]); +			SYSERRMSG; +			return (-1); +		} + +		if (statuspipe_fd[0] >= 0 && FD_ISSET(statuspipe_fd[0], &fdr)) +		{ +			int n=read(statuspipe_fd[0], readbuf, +				   sizeof(readbuf)-1); + +			if (n <= 0) +			{ +				close(statuspipe_fd[0]); +				statuspipe_fd[0]= -1; +			} +			else +			{ +				int l=strlen(cinfo->errmsg); + +				readbuf[n]=0; +				if (l < sizeof(cinfo->errmsg)-2) +					strncat(cinfo->errmsg, readbuf, +						sizeof(cinfo->errmsg)-2-l); +			} +		} + +		if (x509_fd[0] >= 0 && FD_ISSET(x509_fd[0], &fdr)) +		{ +			int n=read(x509_fd[0], readbuf, sizeof(readbuf)); + +			if (n <= 0) +			{ +				close(x509_fd[0]); +				x509_fd[0]= -1; +			} +			else +			{ +				if (n + cinfo->x509info_len >= +				    cinfo->x509info_size) +				{ +					size_t news=n+cinfo->x509info_len +						+ 1024; +					char *newp= cinfo->x509info ? +						realloc(cinfo->x509info, news) +						: malloc(news); + +					if (!newp) +					{ +						SYSERRMSG; +						close(x509_fd[0]); +						x509_fd[0]= -1; +						continue; +					} +					cinfo->x509info=newp; +					cinfo->x509info_size=news; +				} + +				memcpy(cinfo->x509info + cinfo->x509info_len, +				       readbuf, n); +				cinfo->x509info_len += n; +			} +		} + +	} +	return (cinfo->errmsg[0] ? -1:0); +} + +void couriertls_destroy(struct couriertls_info *info) +{ +	struct tls_subject *subj; +	struct tls_subjitem *subjitem; + +	if (info->x509info) +		free(info->x509info); + +	while ((subj=info->first_subject) != 0) +	{ +		info->first_subject=subj->next; +		while ((subjitem=subj->firstitem) != 0) +		{ +			subj->firstitem=subjitem->nextitem; +			free(subjitem); +		} +		free(subj); +	} +} + +#if 0 +int main(int argc, char **argv) +{ +	struct couriertls_info cinfo; +	struct tls_subject *subj; +	struct tls_subjitem *subjitem; + +	couriertls_init(&cinfo); + +	if (couriertls_start(argv+1, &cinfo)) +	{ +		printf("ERROR: %s\n", +		       cinfo.errmsg[0] ? cinfo.errmsg:"unknown error"); +		exit(0); +	} + +	printf("version=%s, cipher=%s, bits=%d\n", cinfo.cipher, +	       cinfo.version, cinfo.bits); + +	for (subj=cinfo.first_subject; subj; subj=subj->next) +	{ +		printf("Subject: "); + +		for (subjitem=subj->firstitem; subjitem; +		     subjitem=subjitem->nextitem) +		{ +			printf("/%s=%s", subjitem->name, +			       subjitem->value); +		} +		printf("\n"); +	} +	couriertls_destroy(&cinfo); +	sleep(300); +	exit(0); +} +#endif diff --git a/tcpd/tlsclient.h b/tcpd/tlsclient.h new file mode 100644 index 0000000..1619449 --- /dev/null +++ b/tcpd/tlsclient.h @@ -0,0 +1,54 @@ +#ifndef	tlsclient_h +#define	tlsclient_h + +/* +** Copyright 2000-2001 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +#ifdef  __cplusplus +extern "C" { +#endif + +#include "config.h" +#include <sys/types.h> +#include <stdlib.h> + +struct tls_subjitem { +	struct tls_subjitem *nextitem; +	const char *name; +	const char *value; +} ; + +struct tls_subject { +	struct tls_subject *next; +	struct tls_subjitem *firstitem; +} ; + +struct couriertls_info { +	char errmsg[128]; +	char *x509info; +	size_t x509info_len; +	size_t x509info_size; + +	struct tls_subject *first_subject; + +	const char *cipher; +	const char *version; +	int bits; +} ; + +void couriertls_init(struct couriertls_info *); +int couriertls_start(char **, struct couriertls_info *); + +const char *couriertls_get_subject(struct couriertls_info *, const char *subject); +void couriertls_export_subject_environment(struct couriertls_info *); + +void couriertls_destroy(struct couriertls_info *); + +#ifdef  __cplusplus +} +#endif + +#endif diff --git a/tcpd/tlsinfo.c b/tcpd/tlsinfo.c new file mode 100644 index 0000000..97b61c4 --- /dev/null +++ b/tcpd/tlsinfo.c @@ -0,0 +1,32 @@ +/* +** Copyright 2002 Double Precision, Inc. +** See COPYING for distribution information. +*/ +#include	"config.h" +#include	"libcouriertls.h" +#include	<stdio.h> +#include	<string.h> +#include	<stdlib.h> +#if	HAVE_UNISTD_H +#include	<unistd.h> +#endif + + +static const char *getenv_wrapper(const char *varname, void *dummy) +{ +	return getenv(varname); +} + +static void report_stderr(const char *errmsg, void *dummy) +{ +	fprintf(stderr, "%s\n", errmsg); +} + +static const struct tls_info default_info +	= { NULL, NULL, report_stderr, getenv_wrapper, NULL }; + +const struct tls_info *tls_get_default_info() +{ +	return &default_info; +} + diff --git a/tcpd/tlspasswordcache.c b/tcpd/tlspasswordcache.c new file mode 100644 index 0000000..5f3ca2b --- /dev/null +++ b/tcpd/tlspasswordcache.c @@ -0,0 +1,954 @@ +/* +** Copyright 2003-2007 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#include "config.h" +#include "tlspasswordcache.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <md5/md5.h> + +#define PASSFILEFORMAT 1 + +#if HAVE_OPENSSL097 +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/rand.h> + +static void sslerror(EVP_CIPHER_CTX *ctx, const char *pfix) +{ +        char errmsg[256]; +        int errnum=ERR_get_error(); +  +        ERR_error_string_n(errnum, errmsg, sizeof(errmsg)-1); + +	fprintf(stderr, "%s: %s\n", pfix, errmsg); +} + + +#endif + +#if HAVE_GCRYPT + +#include <gcrypt.h> + +#define RAND_pseudo_bytes(a,b) (gcry_create_nonce((a),(b)), 0) + +typedef struct { +	enum gcry_cipher_algos algo; +	enum gcry_cipher_modes mode; +} EVP_CIPHER; + +#define EVP_MAX_IV_LENGTH 256 + +const EVP_CIPHER *EVP_des_cbc() +{ +	static const EVP_CIPHER des_cbc={GCRY_CIPHER_DES, +					 GCRY_CIPHER_MODE_CBC}; + +	return &des_cbc; +} + +typedef struct { +	const EVP_CIPHER *cipher; +	gcry_error_t err; +	gcry_cipher_hd_t handle; + +	int padding; +	char *blkbuf; +	size_t blksize; + +	size_t blkptr; + +} EVP_CIPHER_CTX; + +static void sslerror(EVP_CIPHER_CTX *ctx, const char *pfix) +{ +	fprintf(stderr, "%s: %s\n", pfix, gcry_strerror(ctx->err)); +} + +static void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *ctx) +{ +	memset(ctx, 0, sizeof(*ctx)); +} + +static void EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *ctx) +{ +	if (ctx->handle) +	{ +		gcry_cipher_close(ctx->handle); +		ctx->handle=NULL; +	} + +	if (ctx->blkbuf) +	{ +		free(ctx->blkbuf); +		ctx->blkbuf=NULL; +	} +} + +static int EVP_CIPHER_iv_length(const EVP_CIPHER *cipher) +{ +	size_t l=0; + +	gcry_cipher_algo_info(cipher->algo, GCRYCTL_GET_BLKLEN, NULL, &l); +	return l; +} + +static int EVP_CIPHER_key_length(const EVP_CIPHER *cipher) +{ +	size_t l=0; + +	gcry_cipher_algo_info(cipher->algo, GCRYCTL_GET_KEYLEN, NULL, &l); +	return l; +} + +static int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, +			      void *impl, unsigned char *key, unsigned char *iv) +{ +	EVP_CIPHER_CTX_cleanup(ctx); +	ctx->cipher=cipher; +	ctx->err=gcry_cipher_open(&ctx->handle, +				  cipher->algo, +				  cipher->mode, 0); + +	if (!ctx->err) +		ctx->err=gcry_cipher_setkey(ctx->handle, key, +					    EVP_CIPHER_key_length(cipher)); + +	if (!ctx->err) +		ctx->err=gcry_cipher_setiv(ctx->handle, iv, +					   (ctx->blksize= +					    EVP_CIPHER_iv_length(cipher))); + +	if (!ctx->err) +		if ((ctx->blkbuf=malloc(ctx->blksize)) == NULL) +			ctx->err=gpg_err_code_from_errno(errno); + +	ctx->blkptr=0; +	ctx->padding=1; + +	return ctx->err == 0; +} + +static int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, +			     int *outl, unsigned char *in, int inl) +{ +	*outl=0; + +	while (inl > 0) +	{ +		size_t cp= (size_t)inl < (ctx->blksize - ctx->blkptr) +			? (size_t)inl:(ctx->blksize - ctx->blkptr); + +		if (ctx->blkptr == 0 && inl > ctx->blksize*2) +		{ +			cp=(inl / ctx->blksize - 1) * ctx->blksize; + +			if ((ctx->err=gcry_cipher_encrypt(ctx->handle, +							  out, cp, +							  in, cp)) +			    != 0) +				return 0; + +			out += cp; +			*outl += cp; +			in += cp; +			inl -= cp; +			continue; +		} + +		memcpy(ctx->blkbuf + ctx->blkptr, in, cp); + +		in += cp; +		inl -= cp; + +		ctx->blkptr += cp; + +		if (ctx->blkptr == ctx->blksize) +		{ +			if ((ctx->err=gcry_cipher_encrypt(ctx->handle, +							  out, ctx->blksize, +							  ctx->blkbuf, +							  ctx->blksize)) != 0) +				return 0; +			out += ctx->blksize; +			*outl += ctx->blksize; +			ctx->blkptr=0; +		} +	} +	return 1; +} + +static int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, +			       int *outl) +{ +	if (ctx->padding) +	{ +		unsigned char pad=ctx->blksize - ctx->blkptr; + +		*outl=0; + +		if (pad == 0) +			pad=ctx->blksize; + +		do +		{ +			int n_outl; + +			if (!EVP_EncryptUpdate(ctx, out, &n_outl, &pad, 1)) +				return 0; +				 +			out += n_outl; +			*outl += n_outl; +		} +		while (ctx->blkptr); +	} +	else if (ctx->blksize != ctx->blkptr) +	{ +		ctx->err=GPG_ERR_BAD_DATA; +		return 0; +	} + +	return 1; +} + +static int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, +			      void *impl, unsigned char *key, +			      unsigned char *iv) +{ +	return EVP_EncryptInit_ex(ctx, type, impl, key, iv); +} + +static int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, +			     int *outl, unsigned char *in, int inl) +{ +	*outl=0; + +	while (inl > 0) +	{ +		size_t cp; + +		if (ctx->blkptr == 0 && inl > ctx->blksize * 3) +		{ +			cp=(inl / ctx->blksize - 2) * ctx->blksize; + +			if ((ctx->err=gcry_cipher_decrypt(ctx->handle, +							  out, cp, +							  in, cp)) +			    != 0) +				return 0; + +			out += cp; +			*outl += cp; +			in += cp; +			inl -= cp; +			continue; +		} + +		if (ctx->blkptr == ctx->blksize) +		{ +			if ((ctx->err=gcry_cipher_decrypt(ctx->handle, +							  out, ctx->blksize, +							  ctx->blkbuf, +							  ctx->blksize)) != 0) +				return 0; +			out += ctx->blksize; +			*outl += ctx->blksize; +			ctx->blkptr=0; +		} + +		cp= (size_t)inl < (ctx->blksize - ctx->blkptr) +			? (size_t)inl:(ctx->blksize - ctx->blkptr); + +		memcpy(ctx->blkbuf + ctx->blkptr, in, cp); + +		in += cp; +		inl -= cp; + +		ctx->blkptr += cp; + +	} +	return 1; +} + +static int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, +			       int *outl) +{ +	unsigned char lastval; +	int cnt; + +	if (ctx->blkptr != ctx->blksize) +	{ +		ctx->err=GPG_ERR_BAD_DATA; +		return 0; +	} + +	if ((ctx->err=gcry_cipher_decrypt(ctx->handle, +					  ctx->blkbuf, +					  ctx->blksize, +					  NULL, 0)) != 0) +		return 0; + +	if (ctx->padding) +	{ +		lastval=ctx->blkbuf[ctx->blksize-1]; + +		if (lastval > 0 && lastval <= ctx->blksize) +		{ +			char n; + +			for (n=0; n<lastval; n++) +				if (ctx->blkbuf[ctx->blksize-1-n] != lastval) +					lastval=0; +		} +		else +			lastval=0; + +		if (!lastval) +		{ +			ctx->err=GPG_ERR_BAD_DATA; +			return 0; +		} +	} +	else +	{ +		lastval=0; +	} + +	cnt=ctx->blksize-lastval; +	if (cnt) +		memcpy(outm, ctx->blkbuf, cnt); +	*outl=cnt; +	return 1; +} + + +#define HAVE_OPENSSL097 1 +#endif + +#if HAVE_OPENSSL097 + +#if BUFSIZ < 8192 +#undef BUFSIZ +#define BUFSIZ 8192 +#endif + +int tlspassword_init() +{ +	return 1; +} + +static int save_string(EVP_CIPHER_CTX *, +		       const char *, char *, +		       int (*)(const char *, size_t, void *), +		       void *); + +int tlspassword_save( const char * const *urls, +		      const char * const *pwds, +		      const char *mpw, +		      int (*writefunc)(const char *, size_t, void *), +		      void *writefuncarg) +{ +	char buf[BUFSIZ]; +	char *p; +	int l; +	int wl; + +	unsigned char iv1_buf[16]; +	unsigned char iv2_buf[16]; +	MD5_DIGEST md5_password; +	int iv_len, key_len; +	EVP_CIPHER_CTX ctx; +	const EVP_CIPHER *des=EVP_des_cbc(); + +	md5_digest(mpw, strlen(mpw), md5_password); + +	EVP_CIPHER_CTX_init(&ctx); +	iv_len=EVP_CIPHER_iv_length(des); +	key_len=EVP_CIPHER_key_length(des); + +	if (RAND_pseudo_bytes(iv1_buf, sizeof(iv1_buf)) < 0 || +	    RAND_pseudo_bytes(iv2_buf, sizeof(iv2_buf)) < 0) +	{ +		fprintf(stderr, +			"tlspassword_save: internal error - " +			"RAND_pseudo_bytes() failed.\n"); +		EVP_CIPHER_CTX_cleanup(&ctx); +		errno=EIO; +		return -1; +	} + +	if (iv_len + key_len > sizeof(iv1_buf) +	    || iv_len + key_len != sizeof(iv2_buf) +	    || key_len != sizeof(md5_password)/2) +	{ +		fprintf(stderr, +			"tlspassword_save: internal error - " +			"unexpected key sizes.\n"); +		EVP_CIPHER_CTX_cleanup(&ctx); +		errno=EIO; +		return -1; +	} + +	p=buf+3; + +	if (!EVP_EncryptInit_ex(&ctx, des, NULL, +				(unsigned char *)md5_password, +				iv1_buf) || +	    !EVP_EncryptUpdate(&ctx, (unsigned char *)p, &l, +			       (unsigned char *)md5_password + key_len, +			       sizeof(md5_password)-key_len) || +	    !EVP_EncryptUpdate(&ctx, (unsigned char *)(p += l), &l, +			       iv2_buf, +			       iv_len + key_len) || +	    !EVP_EncryptFinal_ex(&ctx, (unsigned char *)(p += l), &l)) + +	{ +		sslerror(&ctx, "EVP_EncryptInit_ex"); +		EVP_CIPHER_CTX_cleanup(&ctx); +		errno=EIO; +		return -1; +	} + +	p += l; + +	wl= p - buf - 3; + +	buf[0]=PASSFILEFORMAT; +	buf[1]= wl / 256; +	buf[2]= wl % 256; + +	l=(*writefunc)(buf, 3, writefuncarg); + +	if (l == 0) +		l=(*writefunc)((const char *)iv1_buf, iv_len, writefuncarg); + +	if (l == 0) +		l=(*writefunc)(buf+3, wl, writefuncarg); + +	if (l) +		return l; + +#if 0 +	{ +		int i; + +		printf("KEY: "); + +		for (i=0; i<key_len + iv_len; i++) +			printf("%02X", (int)(unsigned char)iv2_buf[i]); +		printf("\n"); +	} +#endif + +	if (!EVP_EncryptInit_ex(&ctx, des, NULL, +				(unsigned char *)&iv2_buf, +				(unsigned char *)&iv2_buf + key_len)) +	{ +		sslerror(&ctx, "EVP_EncryptInit_ex"); +		EVP_CIPHER_CTX_cleanup(&ctx); +		errno=EIO; +		return -1; +	} + +	for (l=0; urls[l]; l++) +	{ +		int n=save_string(&ctx, urls[l], buf, writefunc, writefuncarg); + +		if (n) +			return n; + +		n=save_string(&ctx, pwds[l], buf, writefunc, writefuncarg); + +		if (n) +			return n; +	} + +	if (!EVP_EncryptFinal_ex(&ctx, (unsigned char *)buf, &l)) +	{ +		sslerror(&ctx, "EVP_EncryptInit_ex"); +		EVP_CIPHER_CTX_cleanup(&ctx); +		errno=EIO; +		return -1; +	} + +	if (l) +		l=(*writefunc)(buf, l, writefuncarg); + +	EVP_CIPHER_CTX_cleanup(&ctx); +	return l; +} + +static int save_string(EVP_CIPHER_CTX *ctx, +		       const char *str, char *buf, +		       int (*writefunc)(const char *, size_t, void *), +		       void *writefuncarg) +{ +	int l; +	size_t len=strlen(str); +	unsigned char b[2]; + +	if (len >= 256 * 256) +	{ +		fprintf(stderr, +			"tlspassword_save: internal error - " +			"key sizes too large.\n"); +		errno=EINVAL; +		return -1; +	} + +	b[0]=len / 256; +	b[1]=len % 256; + +	if (!EVP_EncryptUpdate(ctx, (unsigned char *)buf, &l, b, 2)) +	{ +		sslerror(ctx, "EVP_EncryptUpdate"); +		return -1; +	} + +	if (l) +	{ +		l=(*writefunc)(buf, l, writefuncarg); + +		if (l) +			return l; +	} + +	while (len) +	{ +		size_t n=len; + +		if (n > BUFSIZ / 4) +			n=BUFSIZ/4; + +		if (!EVP_EncryptUpdate(ctx, (unsigned char *)buf, &l, +				       (unsigned char *)str, n)) +		{ +			sslerror(ctx, "EVP_EncryptUpdate"); +			return -1; +		} + +		if (l) +		{ +			l=(*writefunc)(buf, l, writefuncarg); + +			if (l) +				return l; +		} + +		str += n; +		len -= n; +	} + +	return 0; +} + +struct tempstring_list { +	struct tempstring_list *next; +	char *url; +	char *pw; +}; + +struct tlspassword_readinfo { +	char buf[BUFSIZ / 2]; +	char *bufptr; +	size_t bufleft; + +	int (*readfunc)(char *, size_t, void *); +	void *readfuncarg; + +	struct tempstring_list *tl_list, *tl_last; + +	int (*readhandler)(struct tlspassword_readinfo *, char *, int); + +	unsigned int stringhi; +	char *stringptr; +	size_t stringleft; +	size_t nstrings; +}; + + +static int tlspassword_read(struct tlspassword_readinfo *p, +			    char *buf, +			    size_t nbytes) +{ +	while (nbytes) +	{ +		size_t c; + +		if (p->bufleft == 0) +		{ +			int n= (*p->readfunc)(p->buf, sizeof(p->buf), +					      p->readfuncarg); + +			if (n <= 0) +				return -1; +			p->bufptr=p->buf; +			p->bufleft=n; +		} + +		c=nbytes; + +		if (c > p->bufleft) +			c=p->bufleft; + +		memcpy(buf, p->bufptr, c); +		p->bufptr += c; +		p->bufleft -= c; +		nbytes -= c; +	} + +	return 0; +} + +static void tlspassword_readcleanup(struct tlspassword_readinfo *p) +{ +	while (p->tl_list) +	{ +		struct tempstring_list *t=p->tl_list; + +		p->tl_list=t->next; +		if (t->url) +			free(t->url); +		if (t->pw) +			free(t->pw); +		free(t); +	} +} + +static int read_stringhi(struct tlspassword_readinfo *, char *, int); + +int tlspassword_load( int (*callback)(char *, size_t, void *), +		      void *callback_arg, + +		      const char *mpw, + +		      void (*readfunc)(const char * const *, +				       const char * const *, +				       void *), +		      void *readfunc_arg) +{ +	char buf[BUFSIZ]; +	int outl; +	char *p; + +	MD5_DIGEST md5_password; +	int iv_len, key_len; +	EVP_CIPHER_CTX ctx; +	const EVP_CIPHER *des=EVP_des_cbc(); +	struct tlspassword_readinfo readinfo; +	char header[3]; +	size_t l; +	char iv1_buf[EVP_MAX_IV_LENGTH]; +	struct tempstring_list *tl; +	const char **urls, **pws; + +	readinfo.bufleft=0; +	readinfo.readfunc=callback; +	readinfo.readfuncarg=callback_arg; +	readinfo.tl_list=NULL; +	readinfo.tl_last=NULL; + +	md5_digest(mpw, strlen(mpw), md5_password); + +	EVP_CIPHER_CTX_init(&ctx); +	iv_len=EVP_CIPHER_iv_length(des); +	key_len=EVP_CIPHER_key_length(des); + +	if (tlspassword_read(&readinfo, header, 3) || +	    tlspassword_read(&readinfo, iv1_buf, iv_len)) +	{ +		EVP_CIPHER_CTX_cleanup(&ctx); +		return -1; +	} +	if (header[0] != PASSFILEFORMAT) +	{ +		errno=EINVAL; +		EVP_CIPHER_CTX_cleanup(&ctx); +		return -1; +	} + +	if ((l=(size_t)(unsigned char)header[1] * 256 +	     + (unsigned char)header[2]) > sizeof(buf) / 4) +	{ +		errno=EINVAL; +		EVP_CIPHER_CTX_cleanup(&ctx); +		return -1; +	} + +	if (tlspassword_read(&readinfo, buf, l)) +		return -1; + +	p=buf + sizeof(buf)/2; +	if (!EVP_DecryptInit_ex(&ctx, des, NULL, +				(unsigned char *)md5_password, +				(unsigned char *)&iv1_buf) || +	    !EVP_DecryptUpdate(&ctx, (unsigned char *)p, &outl, +			       (unsigned char *)buf, l) || +	    !EVP_DecryptFinal_ex(&ctx, (unsigned char *)(p += outl), &outl)) +	{ +		errno=EINVAL; +		EVP_CIPHER_CTX_cleanup(&ctx); +		return -1; +	} + +	p += outl; + +	if (p - (buf +sizeof(buf)/2) != sizeof(md5_password) + iv_len +	    || memcmp(buf + sizeof(buf)/2, (char *)(&md5_password) + key_len, +		      sizeof(md5_password)-key_len)) +	{ +		errno=EINVAL; +		EVP_CIPHER_CTX_cleanup(&ctx); +		return -1; +	} + +#if 0 +	{ +		int i; + +		printf("KEY: "); + +		for (i=0; i<key_len + iv_len; i++) +			printf("%02X", (int)(unsigned char)(p-iv_len-key_len)[i]); +		printf("\n"); +	} +#endif + +	if (!EVP_DecryptInit_ex(&ctx, des, NULL, +				(unsigned char *)(p-iv_len-key_len), +				(unsigned char *)(p-iv_len))) +	{ +		errno=EINVAL; +		EVP_CIPHER_CTX_cleanup(&ctx); +		return -1; +	} + +	readinfo.nstrings=0; +	readinfo.readhandler= &read_stringhi; +	for (;;) +	{ +		if (readinfo.bufleft == 0) +		{ +			outl= (*readinfo.readfunc)(readinfo.buf, +						   sizeof(readinfo.buf), +						   readinfo.readfuncarg); + +			if (outl == 0) +				break; + +			if (outl < 0) +			{ +				tlspassword_readcleanup(&readinfo); +				errno=EINVAL; +				EVP_CIPHER_CTX_cleanup(&ctx); +				return -1; +			} + +			readinfo.bufptr=readinfo.buf; +			readinfo.bufleft=outl; +		} + +		if (!EVP_DecryptUpdate(&ctx, (unsigned char *)buf, &outl, +				       (unsigned char *) +				       readinfo.bufptr, readinfo.bufleft)) +		{ +			tlspassword_readcleanup(&readinfo); +			errno=EINVAL; +			EVP_CIPHER_CTX_cleanup(&ctx); +			return -1; +		} +		readinfo.bufleft=0; + +		p=buf; +		while (outl) +		{ +			int n= (*readinfo.readhandler)(&readinfo, p, outl); + +			if (n < 0) +			{ +				tlspassword_readcleanup(&readinfo); +				EVP_CIPHER_CTX_cleanup(&ctx); +				return -1; +			} + +			p += n; +			outl -= n; +		} +	} + +	if (!EVP_DecryptFinal_ex(&ctx, (unsigned char *)buf, &outl)) +	{ +		tlspassword_readcleanup(&readinfo); +		errno=EINVAL; +		EVP_CIPHER_CTX_cleanup(&ctx); +		return -1; +	} + +	p=buf; +	while (outl) +	{ +		int n= (*readinfo.readhandler)(&readinfo, p, outl); + +		if (n < 0) +		{ +			tlspassword_readcleanup(&readinfo); +			errno=EINVAL; +			EVP_CIPHER_CTX_cleanup(&ctx); +			return -1; +		} + +		p += n; +		outl -= n; +	} + +	if (readinfo.tl_list && readinfo.tl_list->pw == NULL) +		/* Odd # of strings -- no good */ +	{ +		tlspassword_readcleanup(&readinfo); +		errno=EINVAL; +		EVP_CIPHER_CTX_cleanup(&ctx); +		return (-1); +	} + +	if ((urls=malloc((readinfo.nstrings+1) * sizeof(char *))) == NULL || +	    (pws=malloc((readinfo.nstrings+1) * sizeof(char *))) == NULL) +	{ +		if (urls) +			free(urls); + +		tlspassword_readcleanup(&readinfo); +		EVP_CIPHER_CTX_cleanup(&ctx); +		return (-1); +	} + +	l=0; +	for (tl=readinfo.tl_list; tl; tl=tl->next) +	{ +		urls[l]=tl->url; +		pws[l]=tl->pw; +		l++; +	} + +	urls[l]=NULL; +	pws[l]=NULL; + +	(*readfunc)(urls, pws, readfunc_arg); + +	free(urls); +	free(pws); + +	tlspassword_readcleanup(&readinfo); +	EVP_CIPHER_CTX_cleanup(&ctx); +	return 0; +} + +static int read_stringlo(struct tlspassword_readinfo *info, +			 char *p, int n); + +static int read_string(struct tlspassword_readinfo *info, +		       char *p, int n); + +static int read_stringhi(struct tlspassword_readinfo *info, +			 char *p, int n) +{ +	info->stringhi=(unsigned char)*p; +	info->stringhi *= 256; + +	info->readhandler=read_stringlo; +	return 1; +} + +static int read_stringlo(struct tlspassword_readinfo *info, +			 char *p, int n) +{ +	struct tempstring_list *t; + +	info->readhandler=read_string; +	info->stringleft=info->stringhi + (unsigned char)*p; + +	if (info->tl_last && +	    info->tl_last->pw == NULL) /* This string is the pw */ +	{ +		info->tl_last->pw=malloc(info->stringleft+1); +		if (!info->tl_last->pw) +			return -1; + +		info->stringptr=info->tl_last->pw; +		return 1; +	} + +	if ((t=(struct tempstring_list *)malloc(sizeof(struct tempstring_list)) +	     ) == NULL || (t->url=malloc(info->stringleft+1)) == NULL) +	{ +		if (t) free(t); +		return -1; +	} + +	if (info->tl_last) +		info->tl_last->next=t; +	else +		info->tl_list=t; +	info->tl_last=t; +	info->stringptr=t->url; +	t->next=NULL; +	t->pw=NULL; +	++info->nstrings; +	return 1; +} + +static int read_string(struct tlspassword_readinfo *info, char *p, int n) +{ +	if (n > info->stringleft) +		n=info->stringleft; + +	memcpy(info->stringptr, p, n); +	info->stringptr += n; +	info->stringleft -= n; + +	if (info->stringleft == 0) +	{ +		info->readhandler=read_stringhi; +		*info->stringptr=0; +	} + +	return n; +} + +#else + + +int tlspassword_init() +{ +	return 0; +} + + +int tlspassword_save( const char * const *urls, +		      const char * const *pwds, +		      const char *mpw, +		      int (*writefunc)(const char *, size_t, void *), +		      void *writefuncarg) +{ +	errno=EIO; +	return -1; +} + +int tlspassword_load( int (*readfunc)(char *, size_t, void *), +		      void *readfuncarg, + +		      const char *mpw, +		      void (*callback)(const char * const *, +				       const char * const *, +				       void *), +		      void *callback_arg) +{ +	errno=EIO; +	return -1; +} +#endif diff --git a/tcpd/tlspasswordcache.h b/tcpd/tlspasswordcache.h new file mode 100644 index 0000000..c794580 --- /dev/null +++ b/tcpd/tlspasswordcache.h @@ -0,0 +1,51 @@ +/* +** Copyright 2003 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#ifndef tlspasswordcache_h +#define tlspasswordcache_h + +#include	"config.h" + +#include	<unistd.h> + + +#ifdef  __cplusplus +extern "C" { +#endif + + + +	/* +	** This module implements a password cache - an encrypted password +	** store. OpenSSL 0.9.7 is required. +	*/ + +int tlspassword_init(); /* Returns 0 if OpenSSL 0.9.7 is installed */ + +int tlspassword_save( const char * const *, /* NULL-terminated URL list */ +		      const char * const *, /* NULL-terminated password list */ + +		      const char *,	/* Master password */ + +		      int (*)(const char *, size_t, void *), +		      /* Output function receives encrypted data */ +		      void *); /* Passthrough arg to output function */ + +int tlspassword_load( int (*)(char *, size_t, void *), /* Input function */ +		      void *, /* Passthrough arg to input function */ + +		      const char *,	/* Master password */ + +		      void(*)(const char * const *, +			      const char * const *, +			      void *), /* Callback function - decrypted pwds */ +		      void *); /* Passthrough arg to callback function */ + + +#ifdef  __cplusplus +} +#endif + +#endif | 
