summaryrefslogtreecommitdiffstats
path: root/tcpd
diff options
context:
space:
mode:
Diffstat (limited to 'tcpd')
-rw-r--r--tcpd/.gitignore8
-rw-r--r--tcpd/Makefile.am95
-rw-r--r--tcpd/README.couriertls68
-rw-r--r--tcpd/argparse.c51
-rw-r--r--tcpd/argparse.h20
-rw-r--r--tcpd/configure.in546
-rw-r--r--tcpd/couriertcpd.sgml974
-rw-r--r--tcpd/couriertls.sgml274
-rw-r--r--tcpd/libcouriergnutls.c2089
-rw-r--r--tcpd/libcouriertls.c1560
-rw-r--r--tcpd/libcouriertls.h362
-rw-r--r--tcpd/spipe.c92
-rw-r--r--tcpd/spipe.h14
-rw-r--r--tcpd/starttls.c807
-rw-r--r--tcpd/tcpd.c2151
-rw-r--r--tcpd/tcpdaccess.c68
-rw-r--r--tcpd/tcpremoteinfo.c163
-rw-r--r--tcpd/tcpremoteinfo.h30
-rw-r--r--tcpd/testsuite38
-rw-r--r--tcpd/testsuite.txt128
-rw-r--r--tcpd/tlscache.c714
-rw-r--r--tcpd/tlscache.h81
-rw-r--r--tcpd/tlscachetest.c72
-rw-r--r--tcpd/tlsclient.c541
-rw-r--r--tcpd/tlsclient.h54
-rw-r--r--tcpd/tlsinfo.c32
-rw-r--r--tcpd/tlspasswordcache.c954
-rw-r--r--tcpd/tlspasswordcache.h51
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&lt;tab&gt;deny
+</programlisting>
+ </informalexample>
+
+ <para><replaceable>netblock</replaceable> is an IP address, such as
+<literal>192.68.0.2</literal>. <token>&lt;tab&gt;</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&lt;tab&gt;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&lt;tab&gt;deny
+192.68.0.10&lt;tab&gt;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">
+*&lt;tab&gt;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&lt;tab&gt;<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&lt;tab&gt;deny
+</programlisting>
+ </informalexample>
+
+ <para>
+Netblocks must be specified using even-word boundaries only:</para>
+
+ <informalexample>
+ <programlisting format="linespecific">
+:3ffe&lt;tab&gt;deny
+</programlisting>
+ </informalexample>
+
+ <para>
+This will deny entire 3ffe::/16 (6bone network, which is phased out).
+</para>
+
+ <informalexample>
+ <programlisting format="linespecific">
+:2002:c0a8&lt;tab&gt;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&lt;tab&gt;allow,RELAYCLIENT
+192.68.0.10&lt;tab&gt;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(&timestamp);
+ 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(&timestamp);
+ 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 (&current_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(&current_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(&current_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