...
 
Commits (3)
  • Sam Varshavchik's avatar
    2c63f813
  • Sam Varshavchik's avatar
    Framework for STS lookups. · 05fdbfcd
    Sam Varshavchik authored
    Use wget to download each domain's policy file.
    
    Cache domain policy files in $localstatedir/sts.
    
    Provide a library to retrieve the cached policy file, if available, or to
    download and cache it, safely. Provide the means to purge cached files,
    when there are too many of them.
    
    Testmxlookup reports the domain's policy, in addition to its MXes.
    
    Add options to testmxlookup to show the domain's policy file.
    05fdbfcd
  • Sam Varshavchik's avatar
    Implement STS. · 03a33037
    Sam Varshavchik authored
    03a33037
2019-02-03 Sam Varshavchik <mrsam@courier-mta.com>
* testmxlookup: implement the Strict Transport Security policy for
SMTP, as specified in RFC 8461. Add some diagnostic options to
testmxlookup.
2019-02-02 Sam Varshavchik <mrsam@courier-mta.com>
* courier/filters/verifyfilter.c: Remove the 'smtpfilter' symlink
to verifyfilter. Replace it with a shell script wrapper that loads
the environment variables and invokes verifyfilter.
* tcpd/libcouriertls.c: Add support for verifying certificate
subject alternative names.
* tcpd/tlsclient.c: Implement the override_vars parameter.
1.0.6
2019-01-30 Sam Varshavchik <mrsam@courier-mta.com>
......
......@@ -5,7 +5,7 @@ dnl distribution information.
AC_PREREQ(2.59)
AC_INIT(courier, 1.0.6, [courier-users@lists.sourceforge.net])
AC_INIT(courier, 1.0.6.20190203, [courier-users@lists.sourceforge.net])
version=$PACKAGE_VERSION
AC_CONFIG_SRCDIR(courier/courier.c)
AM_INIT_AUTOMAKE
......
......@@ -86,6 +86,7 @@ Requires(postun): /sbin/service
%endif
Requires: coreutils
Requires: wget
%if %alternatives
Obsoletes: courier-sendmail-wrapper
Provides: /usr/sbin/sendmail /usr/bin/mailq /usr/bin/rmail /usr/bin/newaliases
......@@ -101,6 +102,7 @@ BuildRequires: gawk
BuildRequires: perl
BuildRequires: make
BuildRequires: procps-ng
BuildRequires: wget
%{?_with_gpg2: %define with_gpg2 1}
%{!?_with_gpg2: %define with_gpg2 0}
......
......@@ -95,3 +95,4 @@
/webmlmdhandlerslist.H
/webmlmrc.dist
/webmlmrc.dist.in
/wget.h
......@@ -22,6 +22,7 @@ DISTCLEANFILES=uidgid
DEBUGLIB=@DEBUGLIB@
BUILT_SOURCES=prefix.h exec_prefix.h sysconfdir.h localstatedir.h \
wget.h \
libexecdir.h bindir.h sbindir.h datadir.h filtersocketdir.h \
uidgid.h configargs.h sort.h
......@@ -265,11 +266,23 @@ makedat: ../libs/makedat/makedat
# testmxlookup - look up MX records
testmxlookup_SOURCES=testmxlookup.c
testmxlookup_DEPENDENCIES=../libs/rfc1035/librfc1035.a ../libs/soxwrap/libsoxwrap.a ../libs/soxwrap/soxlibs.dep libs/libcourier.la \
../libs/md5/libmd5.la ../libs/random128/librandom128.la
testmxlookup_LDADD=../libs/rfc1035/librfc1035.a ../libs/soxwrap/libsoxwrap.a `cat ../libs/soxwrap/soxlibs.dep` libs/libcourier.la \
../libs/md5/libmd5.la ../libs/random128/librandom128.la \
testmxlookup_DEPENDENCIES= \
libs/libcourier.la \
../libs/numlib/libnumlib.la \
../libs/liblock/liblock.la \
../libs/rfc1035/librfc1035.a \
../libs/soxwrap/libsoxwrap.a ../libs/soxwrap/soxlibs.dep\
../libs/md5/libmd5.la \
../libs/random128/librandom128.la
testmxlookup_LDADD= \
libs/libcourier.la \
../libs/numlib/libnumlib.la \
../libs/liblock/liblock.la \
../libs/rfc1035/librfc1035.a \
../libs/soxwrap/libsoxwrap.a `cat ../libs/soxwrap/soxlibs.dep` \
../libs/md5/libmd5.la \
../libs/random128/librandom128.la \
@dblibrary@ @NETLIBS@ -lcourier-unicode @LIBIDN_LIBS@
# maildirmake - create a maildir
......@@ -454,6 +467,9 @@ sysconfdir.h: config.status
localstatedir.h: config.status
echo "#define LOCALSTATEDIR \"$(localstatedir)\"" >localstatedir.h
wget.h: config.status
echo "#define WGET \"$(WGET)\"" >wget.h
libexecdir.h: config.status
echo "#define LIBEXECDIR \"$(libexecdir)\"" >libexecdir.h
......@@ -581,6 +597,7 @@ install-exec-hook:
$(localstatedir) $(localstatedir)/tmp\
$(webmaildir)\
$(localstatedir)/track\
$(localstatedir)/sts\
$(localstatedir)/msgs $(localstatedir)/msgq ; do \
$(mkinstalldirs) $(DESTDIR)$$d || :; done
$(INSTALL_SCRIPT) imapd.rc $(DESTDIR)$(datadir)/imapd
......
/*
** Copyright 1998 - 2007 Double Precision, Inc.
** Copyright 1998 - 2019 Double Precision, Inc.
** See COPYING for distribution information.
*/
......@@ -18,6 +18,7 @@
#include "comstrinode.h"
#include "comstrtimestamp.h"
#include "comstrtotime.h"
#include "comsts.h"
#include <sys/types.h>
#if HAVE_SYS_STAT_H
......@@ -42,6 +43,7 @@ extern unsigned stat_nattempts;
extern unsigned stat_ndelivered;
extern unsigned long stat_nkdelivered;
extern unsigned stat_nkkdelivered;
extern int sts_cache_size_counter;
void msgq::logmsgid(msgq *q)
{
......@@ -56,6 +58,13 @@ int i;
++inprogress;
++stat_nattempts;
if (sts_cache_size_counter == 0)
/* So we can still recheck it, occasionally */
sts_cache_size_counter=1000;
if (--sts_cache_size_counter == 0)
sts_cache_size_counter=sts_expire();
/* If the message is due for cancellation, cancel it here */
if (strcmp(drvp->module->name, drvinfo::module_dsn->module->name) &&
......@@ -83,7 +92,7 @@ int i;
&ctf,
ri->addressesidx[i],
COMCTLFILE_DELINFO_REPLY, msg);
msg=ctf.lines[j]+1;
}
......@@ -574,7 +583,7 @@ int cancelled=0;
current_time += retrygamma * (1L << completedcnt);
}
std::string buf2;
ctlfile_nextattempt(&ctlinfo, current_time);
......
......@@ -47,6 +47,14 @@ then
fi
AC_SUBST(SETENV)
AC_PATH_PROGS(WGET, wget, /usr/bin/wget, $LPATH)
AC_SUBST(WGET)
if test ! -x "$WGET"
then
AC_MSG_ERROR(wget not found, please install the wget tool)
fi
AC_PATH_PROGS(COURIERAUTHCONFIG, courierauthconfig)
if test "$COURIERAUTHCONFIG" = ""
......
......@@ -30,6 +30,7 @@
#include "liblock/config.h"
#include "liblock/liblock.h"
#include "numlib/numlib.h"
#include "wget.h"
void courier_clear_all();
void courier_show_all();
......@@ -122,15 +123,20 @@ int main(int argc, char **argv)
if (strcmp(argv[1], "start") == 0)
{
pid_t p;
int waitstat;
char dummy;
/*
** Ok, courierd will close file descriptor 3 when it starts, so we
** put a pipe on there, and wait for it to close.
*/
int pipefd[2];
pid_t p;
int waitstat;
char dummy;
/*
** Ok, courierd will close file descriptor 3 when it starts,
** so we put a pipe on there, and wait for it to close.
*/
int pipefd[2];
if (access(WGET, 0) < 0)
{
fprintf(stderr, "%s not found\n", WGET);
}
close(3);
if (open("/dev/null", O_RDONLY) != 3 || pipe(pipefd))
......
/*
** Copyright 1998 - 2013 Double Precision, Inc.
** Copyright 1998 - 2019 Double Precision, Inc.
** See COPYING for distribution information.
*/
#include "cdpendelinfo.h"
......@@ -18,9 +18,11 @@
#include "comparseqid.h"
#include "comqueuename.h"
#include "comtrack.h"
#include "comsts.h"
#include "courierd.h"
#include "mydirent.h"
#include "numlib/numlib.h"
#include "comsts.h"
#include <sys/types.h>
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
......@@ -68,6 +70,7 @@ time_t retryalpha;
int retrybeta;
time_t retrygamma;
int retrymaxdelta;
int sts_cache_size_counter;
time_t queuefill;
time_t nextqueuefill;
......@@ -221,6 +224,7 @@ struct mybuf trigger_buf;
drvinfo::init();
libmail_changeuidgid(MAILUID, MAILGID);
signal(SIGPIPE, SIG_IGN);
respawnlo=config_time_respawnlo();
......@@ -241,6 +245,11 @@ struct mybuf trigger_buf;
rmdir(MSGQDIR);
mkdir(MSGQDIR, 0755);
trackpurge(TRACKDIR);
clog_msg_start_info();
clog_msg_str("Purging ");
clog_msg_str(STSDIR);
clog_msg_send();
sts_cache_size_counter=sts_expire();
if ((triggerw=open(triggername, O_WRONLY, 0)) < 0 ||
(triggerr=open(triggername, O_RDONLY, 0)) < 0)
......
......@@ -130,7 +130,9 @@
/submit.html
/submit.html.in
/testmxlookup.1
/testmxlookup.1.in
/testmxlookup.html
/testmxlookup.html.in
/webmlmd.1
/webmlmd.1.in
/webmlmd.html
......
......@@ -282,7 +282,7 @@ BUILT_SOURCES= \
preline.html.in preline.1.in \
sendmail.html sendmail.1 \
submit.html.in submit.8.in \
testmxlookup.html testmxlookup.1 \
testmxlookup.html.in testmxlookup.1.in \
webmlmd.html.in webmlmd.1.in
EXTRA_DIST=$(nohtmlin) $(BUILT_SOURCES) dotforward.1 pop3d-ssl.8
......@@ -426,11 +426,12 @@ sendmail.html: sendmail.sgml ../../libs/docbook/sgml2html
sendmail.1: sendmail.sgml ../../libs/docbook/sgml2man
../../libs/docbook/sgml2man sendmail.sgml sendmail.1 "--stringparam man.base.url.for.relative.links http://www.courier-mta.org/"
testmxlookup.html: testmxlookup.sgml ../../libs/docbook/sgml2html
../../libs/docbook/sgml2html testmxlookup.sgml testmxlookup.html
testmxlookup.html.in: testmxlookup.sgml ../../libs/docbook/sgml2html
../../libs/docbook/sgml2html testmxlookup.sgml testmxlookup.html.in
testmxlookup.1: testmxlookup.sgml ../../libs/docbook/sgml2man
../../libs/docbook/sgml2man testmxlookup.sgml testmxlookup.1 "--stringparam man.base.url.for.relative.links http://www.courier-mta.org/"
testmxlookup.1.in: testmxlookup.sgml ../../libs/docbook/sgml2man
../../libs/docbook/sgml2man testmxlookup.sgml testmxlookup.1.in "--stringparam man.base.url.for.relative.links http://www.courier-mta.org/"
mv testmxlookup.1 testmxlookup.1.in
dot-forward.html: dot-forward.sgml ../../libs/docbook/sgml2html
../../libs/docbook/sgml2html dot-forward.sgml dot-forward.html
......@@ -552,6 +553,12 @@ preline.html: preline.html.in
preline.1: preline.1.in
./config.status --file=$@
testmxlookup.html: testmxlookup.html.in
./config.status --file=$@
testmxlookup.1: testmxlookup.1.in
./config.status --file=$@
install-man-extra:
cd $(DESTDIR)$(man8dir) ; \
rm -f courierpop3login.8 makesmtpaccess-msa.8 esmtpd-msa.8; \
......
......@@ -2,8 +2,6 @@
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="generator" content="Bluefish 2.2.10" />
<meta name="generator" content="Bluefish 2.2.10" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="MSSmartTagsPreventParsing" content="TRUE" />
<meta name="author" content="Sam Varshavchik" />
......@@ -11,7 +9,7 @@
<link rel="icon" href="icon.gif" type="image/gif" />
</head>
<body>
<!-- Copyright 1998 - 2017 Double Precision, Inc. See COPYING for -->
<!-- Copyright 1998 - 2019 Double Precision, Inc. See COPYING for -->
<!-- distribution information. -->
<p>NOTE: a more readable HTML version of this INSTALL document can be found
in courier/doc/install.html.</p>
......@@ -439,6 +437,11 @@
<p>The PCRE library (<a href=
"http://www.pcre.org">http:/www.pcre.org</a>) is required.</p>
</li>
<li>
<p><strong>wget</strong></p>
<p>The <a href="https://www.gnu.org/software/wget/">wget command</a> must
be installed.</p>
</li>
<li>
<p><strong>GNU IDN library</strong></p>
<p>This library (<a href=
......@@ -473,8 +476,8 @@
</li>
<li>
<p><strong>OpenSSL</strong> or <strong>GnuTLS</strong></p>
<p>Support for SSL/TLS requires OpenSSL/GnuTLS. If OpenSSL or GnuTLS is
not installed, SSL/TLS features are disabled.</p>
<p>Support for SSL/TLS requires OpenSSL/GnuTLS. All features that require
SSL/TLS are disabled unless OpenSSL or GnuTLS is installed.</p>
</li>
<li>
<p><strong>OpenLDAP</strong></p>
......
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<!-- Copyright 1999 - 2011 Double Precision, Inc. See COPYING for -->
<!-- Copyright 1999 - 2019 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>
......@@ -16,37 +16,80 @@
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis sepchar=" ">
<command moreinfo="none">testmxlookup</command>
<arg choice="opt">@<replaceable>ip-address</replaceable></arg>
<arg choice="opt">--dnssec</arg>
<arg choice="opt">--udpsize <replaceable>n</replaceable></arg>
<cmdsynopsis>
<command>testmxlookup</command>
<group choice="opt">
<arg choice="plain">@<replaceable>ip-address</replaceable></arg>
<arg choice="plain">--dnssec</arg>
<arg choice="plain">--udpsize <replaceable>n</replaceable></arg>
<arg choice="plain">--sts</arg>
<arg choice="plain">--sts-override=<replaceable>mode</replaceable></arg>
<arg choice="plain">--sts-purge</arg>
</group>
<arg choice="req"><replaceable>domain</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>testmxlookup</command>
<group choice="req">
<arg choice="plain">--sts-expire</arg>
<arg choice="plain">--sts-cache-disable</arg>
<arg choice="plain">--sts-cache-enable</arg>
<arg choice="plain">--sts-cache-enable=<replaceable>size</replaceable></arg>
</group>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>DESCRIPTION</title>
<para>
<command moreinfo="none">testmxlookup</command> lists the names and IP addresses of mail
relays that receive mail for the <replaceable>domain</replaceable>.
This is useful in diagnosing mail delivery problems.</para>
<command>testmxlookup</command> reports the names and IP addresses of mail
relays that receive mail for the <replaceable>domain</replaceable>,
as well as the <replaceable>domain</replaceable> published
<acronym>STS</acronym> policy.
This is useful in diagnosing mail delivery problems.
</para>
<para>
<command moreinfo="none">testmxlookup</command> sends a DNS MX query for the specified domain,
followed by A/AAAA queries, if needed.
<command moreinfo="none">testmxlookup</command> lists the
hostname and the IP address of every mail relay, and its MX priority.</para>
<command>testmxlookup</command> sends a DNS MX query for the specified
domain, followed by A/AAAA queries, if needed.
<command>testmxlookup</command> lists the
hostname and the IP address of every mail relay, and its MX priority.
The domain's strict transport security
(<acronym>STS</acronym>) policy status, if one is published,
precedes the mail relay list.
</para>
<refsect2>
<title>DIAGNOSTICS</title>
<para>
The error message "Hard error" indicates that the domain does not exist,
or does not have any mail relays. The error message "Soft error" indicates a
temporary error condition (usually a network failure of some sorts, or the
local DNS server is down).</para>
The error message <quote>Hard error</quote> indicates that the
domain does not exist,
or does not have any mail relays. The error message "Soft error"
indicates a temporary error condition (usually a network failure of
some sorts, or the local DNS server is down).
</para>
<para>
<quote>STS: testing</quote> or
<quote>STS: enforcing</quote> preceding the list of mail relays
indicates that the domain publishes an <acronym>STS</acronym>
policy.
<quote>ERROR: STS Policy verification failed</quote> appearing
after an individual mail relay
indicates that the mail relay's name does not meet the domain's
<acronym>STS</acronym> policy.
</para>
<para>
<quote>STS: testing</quote> or
<quote>STS: enforcing</quote> by itself, with no further messages,
indicates that all listed mail relays comply with the listed
<acronym>STS</acronym> policy. If you are attempting to install
your own STS policy this is a simple means of checking its
validity.
</para>
</refsect2>
<refsect2>
......@@ -101,15 +144,143 @@ local DNS server is down).</para>
networks may impose lower limits).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>--sts</literal></term>
<listitem>
<para>
Do not issue an MX query, and display the domain's raw
<acronym>STS</acronym> policy file.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>--sts-cache-disable</literal></term>
<listitem>
<para>
Turn off <acronym>STS</acronym> lookups, checking, and
verification. <acronym>STS</acronym> is enabled by default,
but requires that a global systemwide list of
SSL certificate authorities is available, and
that <envar>TLS_TRUSTCERTS</envar> is specified in
@sysconfdir@/courierd. <acronym>STS</acronym> can be disabled,
if needed.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>--sts-cache-enable</literal></term>
<listitem>
<para>
Reenable <acronym>STS</acronym> lookups, checking, and
verification, and set the size of the internal cache to its
default value. Specify <quote><literal>=size</literal></quote>
to enable and set a non-default cache size, a positive value
indicating the approximate number of most recent domains
whose <acronym>STS</acronym> policies get cached internally.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>--sts-override=<replaceable>policy</replaceable></literal></term>
<listitem>
<para>
Override the domain's
<acronym>STS</acronym> enforcement mode.
<replaceable>policy</replaceable> is one of:
<quote>none</quote>,
<quote>testing</quote>, or
<quote>enforce</quote>, and overrides the cached
domain <acronym>STS</acronym> policy setting.
</para>
<note>
<para>
This is a diagnostic or a testing tool.
<application>Courier</application> may eventually purge
the cached policy setting, or the domain can update its
policy, replacing the overridden setting.
</para>
</note>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>--sts-purge</literal></term>
<listitem>
<para>
Remove the domain's cached <acronym>STS</acronym> policy,
and retrieve and cache the domain's policy, again.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>--sts-expire</literal></term>
<listitem>
<para>
Execute
<application>Courier</application>'s
<acronym>STS</acronym> policy expiration process. Nothing
happens unless
<filename>@localstatedir@/sts</filename>'s size exceeds the
configured cache size setting.
The oldest cached policy files get removed
to bring the cache size down to its maximum size.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect2>
<refsect2>
<title>STRICT TRANSPORT SECURITY</title>
<para>
<application>Courier</application>
automatically downloads and caches domains'
<acronym>STS</acronym> policy files by default, an an internal
cache with a default size of 1000 domains.
</para>
<note>
<para>
The cache size setting is approximate.
<application>Courier</application>
purges stale cache entries periodically, and the size of the
cache can temporarily exceed its set size, by as much as a factor
of two.
<filename>@localstatedir@/sts</filename>
must be owned by @mailuser@:@mailgroup@, and uses one file
per mail domain. The maximum cache size depends on the
capabilities of the underlying filesystem.
</para>
<para>
<command>testmxlookup</command> must be executed with sufficient
privileges to access the cache directory (by root, or by
@mailuser@). Without sufficient privileges
<command>testmxlookup</command> still attempts to use
the cache directory
even without write permissions on it, as long as it's
accessible, and attempts to download the STS policy for a domain
that's not already cached; but, of course, won't be able to
save the downloaded policy in the cache directory.
</para>
</note>
</refsect2>
</refsect1>
<refsect1>
<title>SEE ALSO</title>
<para>
<ulink url="courier.html"><citerefentry><refentrytitle>courier</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink>,
<ulink url="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035</ulink>.</para>
<ulink url="https://www.ietf.org/rfc/rfc1035.txt">RFC 1035</ulink>,
<ulink url="https://www.ietf.org/rfc/rfc8461.txt">RFC 8461</ulink>.
</para>
</refsect1>
</refentry>
......@@ -28,6 +28,7 @@
/verifyfilter.html.in
/verifyfilter
/verifyfilter.stamp
/verifysmtp
/verifysmtp.8
/verifysmtp.8.in
/verifysender
......
#
# Copyright 2000-2002 Double Precision, Inc. See COPYING for
# Copyright 2000-2019 Double Precision, Inc. See COPYING for
# distribution information.
SUBDIRS=libfilter perlfilter
......@@ -23,6 +23,7 @@ noinst_SCRIPTS=courierfilter.start
filterbindir=$(libexecdir)/filters
filterbin_PROGRAMS=dupfilter ratefilter verifyfilter
bin_SCRIPTS=verifysmtp
filtersocketdir=$(localstatedir)/filters
allfiltersocketdir=$(localstatedir)/allfilters
......@@ -112,8 +113,6 @@ install-data-hook:
chmod 500 $(DESTDIR)$(datadir)/filterctl
rm -f $(DESTDIR)$(sbindir)/filterctl
$(LN_S) $(datadir)/filterctl $(DESTDIR)$(sbindir)/filterctl
rm -f $(DESTDIR)$(bindir)/verifysmtp
ln $(DESTDIR)$(filterbindir)/verifyfilter $(DESTDIR)$(bindir)/verifysmtp
chmod 750 $(DESTDIR)$(filtersocketdir)
chmod 750 $(DESTDIR)$(allfiltersocketdir)
......
......@@ -139,4 +139,5 @@ AC_CONFIG_SUBDIRS(libfilter perlfilter)
AM_CONDITIONAL(HAVE_SGML, test -d ${srcdir}/../../libs/docbook)
AC_OUTPUT(Makefile filterctl courierfilter.start verifysender verifysenderfull)
AC_OUTPUT(Makefile filterctl courierfilter.start verifysender verifysenderfull
verifysmtp)
......@@ -16,6 +16,7 @@
#include "comctlfile.h"
#include "comtrack.h"
#include "threadlib/threadlib.h"
#include "numlib/numlib.h"
#include <sys/types.h>
#if HAVE_SYS_STAT_H
......@@ -464,9 +465,11 @@ void lookup(int argc, char **argv)
memset(&my_env, 0, sizeof(my_env));
while ((opt=getopt(argc, argv, "m:t:")) != -1)
while ((opt=getopt(argc, argv, "cm:t:")) != -1)
{
switch (opt) {
case 'c':
break;
case 't':
trackdir=optarg;
break;
......@@ -475,8 +478,7 @@ void lookup(int argc, char **argv)
break;
default:
fprintf(stderr,
"Usage: %s [-t trackingdirectory] [-m full|base|domain]\n",
argv[0]);
"Usage: verifysmtp [-t trackingdirectory] [-m full|base|domain]\n");
exit(1);
}
}
......@@ -736,16 +738,11 @@ int main(int argc, char **argv)
unsigned nthreads=10;
if (!lf_initializing())
if (argc > 1)
{
if (!getenv("COURIERTLS"))
{
putenv("COURIERTLS=" BINDIR "/couriertls");
putenv("ESMTP_USE_STARTTLS=1");
if (!getenv("TLS_VERIFYPEER"))
putenv("TLS_VERIFYPEER=NONE");
}
umask(022);
if (geteuid() == 0)
libmail_changeuidgid(MAILUID, MAILGID);
lookup(argc, argv);
}
......
......@@ -259,15 +259,17 @@ include '@sysconfdir@/maildroprcs/verifysenderfull'</programlisting>
</para>
<para>
When executed as a shell command or a local recipient mail filter the
<application>Courier</application> mail server's execution environment
is not available, and <command>verifysmtp</command> will not be
able to use
<application>Courier</application>'s configuration files, such as
<command>verifysmtp</command> is a shell script wrapper that imports
<application>Courier</application>'s environment from
<filename>@sysconfdir@/courierd</filename> then executes
<command>verifyfilter</command>.
Running as a non-daemon user, <command>verifysmtp</command>
may not be able to read some
<application>Courier</application>'s configuration files, and also
won't use several others like
<filename>smtproutes</filename>
and
<filename>authclient</filename>,
and several others.
<filename>authclient</filename>.
As such, <command>verifysmtp</command>'s behavior might differ from
Courier's, when sending mail.
</para>
......
#! @SHELL@
prefix="@prefix@"
exec_prefix="@exec_prefix@"
sysconfdir="@sysconfdir@"
libexecdir="@libexecdir@"
set -a
. @sysconfdir@/courierd
exec ${libexecdir}/filters/verifyfilter -c "$@"
......@@ -31,6 +31,7 @@ libcommon_la_LDFLAGS=-static
libcourier_la_SOURCES=rw.h rwint.h addrlower.c cgethostname.c courier_malloc.c \
cdefaultdelivery.c cdomaincmp.c cfilename.c cmaildropfilter.c \
comsts.c comsts2.c comsts.h \
cmaildropmda.c \
cread1l.c cme.c cdefaultdomain.c chelohost.c cfilteracct.c \
cmsgidhost.c courierdir.c islocal.c islocalt.c maxlongsize.h \
......
This diff is collapsed.
/*
** Copyright 2019 Double Precision, Inc.
** See COPYING for distribution information.
*/
#ifndef comsts_h
#define comsts_h
#include "localstatedir.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
#ifdef __cplusplus
extern "C" {
#endif
#if 0
}
#endif
#define STSDIR LOCALSTATEDIR "/sts"
/*
** STS policy for a domain.
**
** Loaded by sts_init(). If the domain does not have a policy, the id and
** policy values are null pointers.
*/
struct sts_id {
char *id;
char *policy;
int cached;
int tempfail;
time_t timestamp;
time_t expiration;
};
/*
** Download the STS policy for the domain and cache it. Use the cached
** policy, if one exists. If the cached policy is more than half to its
** expiration time, check if the domain has published a new policy. If not,
** reset the expiration clock; or download and cache the updated policy.
**
** The sts_id is cleared if there is no published STS policy for the domain,
** or if the cached policy expired (and it gets cleared).
*/
void sts_init(struct sts_id *id, const char *domain);
/*
** Deallocate an sts_id
*/
void sts_deinit(struct sts_id *id);
enum sts_mode {sts_mode_none, sts_mode_testing, sts_mode_enforce};
/*
** Return domain's STS mode.
*/
enum sts_mode get_sts_mode(struct sts_id *id);
/*
** Check if the given hostname is valid as per the STS policy.
**
** Returns 0 if we find a matching "mx" line.
**
** Returns a negative value, if not.
*/
int sts_mx_validate(struct sts_id *id, const char *domainname);
/*
** Expire old cache entries, returns the cache size.
*/
int sts_expire();
/*
** Manually override the domain's policy.
**
** mode: "none", "testing", or "enforce".
*/
void sts_policy_override(const char *domain, const char *mode);
/*
** Manually remove the cached policy for the domain.
*/
void sts_policy_purge(const char *domain);
/*
** Enable the STS cache with the default cache size.
*/
void sts_cache_enable();
/*
** Enable the STS cache and set its size.
*/
void sts_cache_size_set(int);
/*
** Disable the STS cache.
*/
void sts_cache_disable();
#if 0
{
#endif
#ifdef __cplusplus
}
#endif
#endif
/*
** Copyright 2019 Double Precision, Inc.
** See COPYING for distribution information.
*/
#include "courier.h"
#include "comsts.h"
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int sts_cache_size()
{
FILE *fp=fopen(STSDIR "/.cache_size", "r");
char buffer[1024];
int n;
strcpy(buffer, "1000"); /* Default cache size */
if (fp)
{
fgets(buffer, sizeof(buffer), fp);
fclose(fp);
}
n=atoi(buffer);
if (n < 0)
n=0;
return n;
}
void sts_cache_enable()
{
if (unlink(STSDIR "/.cache_size"))
perror(STSDIR);
}
void sts_cache_size_set(int n)
{
FILE *fp=fopen(STSDIR "/.cache_size", "w");
if (!fp || fprintf(fp, "%d\n", n) < 0 || fflush(fp) < 0 || fclose(fp))
perror(STSDIR);
}
void sts_cache_disable()
{
sts_cache_size_set(0);
}
struct sts_file_list {
struct sts_file_list *next;
char *filename;
time_t timestamp;
};
static int sort_by_timestamp(const void *a, const void *b)
{
const struct sts_file_list *const *fa=a, * const *fb=b;
return (*fa)->timestamp < (*fb)->timestamp ? -1
: (*fa)->timestamp > (*fb)->timestamp ? 1
: 0;
}
int sts_expire()
{
struct sts_file_list *list=0;
DIR *dirp;
struct dirent *de;
size_t cnt=0;
size_t i;
size_t max_size=sts_cache_size();
struct sts_file_list **array;
if (max_size == 0)
return max_size;
dirp=opendir(STSDIR);
if (!dirp)
return max_size;
while ((de=readdir(dirp)) != 0)
{
struct sts_file_list *n;
if (de->d_name[0] == '.')
continue;
n=courier_malloc(sizeof(struct sts_file_list));
if (!n)
break;
if ((n->filename=courier_malloc(sizeof(STSDIR "/") +
strlen(de->d_name))) == 0)
{
free(n);
break;
}
strcat(strcpy(n->filename, STSDIR "/"), de->d_name);
n->next=list;
list=n;
++cnt;
}
closedir(dirp);
if (cnt <= max_size)
return max_size;
if ((array=calloc(cnt, sizeof(*array))) != 0)
{
struct sts_file_list *p;
cnt=0;
for (p=list; p; p=p->next)
{
array[cnt]=p;
++cnt;
}
for (i=0; i<cnt; ++i)
{
struct stat stat_buf;
if (stat(array[i]->filename, &stat_buf) < 0)
{
array[i]->filename[0]=0;
stat_buf.st_mtime=0;
}
array[i]->timestamp=stat_buf.st_mtime;
}
qsort(array, cnt, sizeof(*array), sort_by_timestamp);
for (i=0; i<cnt-max_size; ++i)
{
if (array[i]->filename[0]) /* stat() error, above */
unlink(array[i]->filename);
}
free(array);
}
while (list)
{
struct sts_file_list *p;
p=list->next;
free(list->filename);
free(list);
list=p;
}
return max_size;
}
......@@ -72,6 +72,7 @@ courieresmtp_DEPENDENCIES=../../courier/libs/libmodule.la \
@SASLLIBS@ \
../../libs/tcpd/libtlsclient.la \
../../libs/numlib/libnumlib.la \
../../libs/liblock/liblock.la \
../../libs/waitlib/libwaitlib.la \
../../libs/tcpd/libspipe.la \
../../libs/md5/libmd5.la \
......@@ -89,6 +90,7 @@ commonLDADD=../../courier/libs/libmodule.la librewrite.la libesmtp.la \
@SASLLIBS@ \
../../libs/tcpd/libtlsclient.la \
../../libs/numlib/libnumlib.la \
../../libs/liblock/liblock.la \
../../libs/waitlib/libwaitlib.la \
../../libs/tcpd/libspipe.la \
@dblibrary@ \
......
......@@ -8,6 +8,7 @@
#endif
#include "libesmtp.h"
#include "smtproutes.h"
#include "comsts.h"
#include <errno.h>
#include <sys/types.h>
#include <sys/uio.h>
......@@ -38,6 +39,7 @@ static int esmtp_helo(struct esmtp_info *info, int using_tls,
static int esmtp_get_greeting(struct esmtp_info *info,
void *arg);
static int esmtp_enable_tls(struct esmtp_info *,
enum sts_mode,
const char *,
int,
void *arg);
......@@ -574,6 +576,7 @@ static int esmtp_helo(struct esmtp_info *info, int using_tls,
info->hascourier=0;
info->hasstarttls=0;
info->hassecurity_starttls=0;
info->is_tls_connection=0;
info->is_secure_connection=0;
......@@ -887,6 +890,7 @@ static void connection_closed(struct esmtp_info *info,
}
static int esmtp_enable_tls(struct esmtp_info *info,
enum sts_mode domain_sts_mode,
const char *hostname, int smtps,
void *arg)
{
......@@ -899,12 +903,13 @@ static int esmtp_enable_tls(struct esmtp_info *info,
char remotefd_buf[NUMBUFSIZE+30];
char miscbuf[NUMBUFSIZE];
static char *trustcert_buf=0;
static char *origcert_buf=0;
char *trustcert_buf=0;
char *vars[10];
int vars_n=0;
char *argvec[10];
int restore_origcert=0;
int n;
if (libmail_streampipe(pipefd))
......@@ -964,27 +969,18 @@ static int esmtp_enable_tls(struct esmtp_info *info,
p=getenv("ESMTP_TLS_VERIFY_DOMAIN");
if (domain_sts_mode == sts_mode_enforce)
p="1";
if ((info->smtproutes_flags & ROUTE_STARTTLS) != 0)
{
char *q, *r;
char *q;
/*
** Replace TLS_TRUSTCERTS with TLS_TRUSTSECURITYCERTS,
** until couriertls is execed.
*/
q=getenv("TLS_TRUSTCERTS");
r=malloc(strlen(q ? q:"")+40);
if (!r)
abort();
strcat(strcpy(r, "TLS_TRUSTCERTS="), q ? q:"");
if (origcert_buf)
free(origcert_buf);
origcert_buf=r;
restore_origcert=1;
p=getenv("TLS_TRUSTSECURITYCERTS");
if (!p || !*p)
{
......@@ -1006,12 +1002,8 @@ static int esmtp_enable_tls(struct esmtp_info *info,
if (!q)
abort();
strcat(strcpy(q, "TLS_TRUSTCERTS="), p);
putenv(q);
vars[vars_n++]=q;
p="1";
if (trustcert_buf)
free(trustcert_buf);
trustcert_buf=q;
}
if (p && atoi(p))
......@@ -1033,10 +1025,22 @@ static int esmtp_enable_tls(struct esmtp_info *info,
}
argvec[n]=0;
if (trustcert_buf)
{
vars[vars_n++]=trustcert_buf;
}
if (domain_sts_mode == sts_mode_enforce)
{
vars[vars_n++]="TLS_VERIFYPEER=PEER";
}
vars[vars_n]=0;
cinfo.override_vars=vars;
n=couriertls_start(argvec, &cinfo);
if (restore_origcert)
putenv(origcert_buf);
if (trustcert_buf)
free(trustcert_buf);
if (verify_domain)
free(verify_domain);
......@@ -1073,6 +1077,8 @@ static int esmtp_enable_tls(struct esmtp_info *info,
esmtp_init(info);
info->is_tls_connection=1;
/* Ask again for an EHLO, because the capabilities may differ now */
if (smtps)
......@@ -1410,23 +1416,52 @@ static int local_sock_address(struct esmtp_info *info,
return 0;
}
static int do_esmtp_connect_to(struct esmtp_info *info,
const char *connect_to,
struct sts_id *id,
enum sts_mode domain_sts_mode,
void *arg);
static int do_esmtp_connect(struct esmtp_info *info, void *arg)
{
const char *dest_domain=info->smtproute ? info->smtproute:info->host;
struct sts_id domain_sts_id;
int rc;
const char *tls_trustcerts=getenv("TLS_TRUSTCERTS");
/* If TLS is not enabled, disable STS */
sts_init(&domain_sts_id, (tls_trustcerts && *tls_trustcerts
? dest_domain:NULL));
rc=do_esmtp_connect_to(info, dest_domain, &domain_sts_id,
get_sts_mode(&domain_sts_id),
arg);
sts_deinit(&domain_sts_id);
return rc;
}
static int do_esmtp_connect_to(struct esmtp_info *info,
const char *connect_to,
struct sts_id *domain_sts_id,
enum sts_mode domain_sts_mode,
void *arg)
{
struct rfc1035_mxlist *mxlist, *p, *q;
int static_route= info->smtproute != NULL;
struct rfc1035_res res;
int rc;
const char *auth_key;
int connection_attempt_made;
errno=0; /* Detect network failures */
auth_key=info->smtproute ? info->smtproute:info->host;
rfc1035_init_resolv(&res);
rc=rfc1035_mxlist_create_x(&res,
auth_key,
connect_to,
RFC1035_MX_AFALLBACK |
RFC1035_MX_IGNORESOFTERR,
&mxlist);
......@@ -1522,6 +1557,10 @@ static int do_esmtp_connect(struct esmtp_info *info, void *arg)
sizeof(p->address), &port))
continue;
if (domain_sts_mode == sts_mode_enforce &&
sts_mx_validate(domain_sts_id, p->hostname) < 0)
continue;
connection_attempt_made=1;
info->sockfdaddr=addr;
......@@ -1556,6 +1595,7 @@ static int do_esmtp_connect(struct esmtp_info *info, void *arg)
if (info->smtproutes_flags & ROUTE_SMTPS)
{
if (esmtp_enable_tls(info,
domain_sts_mode,
p->hostname,
1,
arg))
......@@ -1597,11 +1637,17 @@ static int do_esmtp_connect(struct esmtp_info *info, void *arg)
(info,
"/SECURITY set, but TLS is not available",
'5', arg);
}
}
else if (domain_sts_mode == sts_mode_enforce
&& !info->hasstarttls)
{
rc=1;
}
else if (info->hasstarttls &&
esmtp_enable_tls
(info, p->hostname,
0, arg))
esmtp_enable_tls(info,
domain_sts_mode,
p->hostname,
0, arg))
{
/*
** Only keep track of broken
......@@ -1611,7 +1657,8 @@ static int do_esmtp_connect(struct esmtp_info *info, void *arg)
** Otherwise, it's on us.
*/
if (!(info->smtproutes_flags &
ROUTE_STARTTLS))
ROUTE_STARTTLS) &&
domain_sts_mode != sts_mode_enforce)
{
(*info->report_broken_starttls)
(info,
......@@ -1621,7 +1668,8 @@ static int do_esmtp_connect(struct esmtp_info *info, void *arg)
}
else
{
int rc=esmtp_auth(info, auth_key, arg);
int rc=esmtp_auth(info, connect_to,
arg);
if (rc == 0)
break; /* Good HELO */
......
......@@ -82,6 +82,11 @@ struct esmtp_info {
int hascourier;
int hasstarttls;
int hassecurity_starttls;
/* This connection has TLS enabled. */
int is_tls_connection;
/* TLS connection with Courier's SECURITY extension. */
int is_secure_connection;
void (*rewrite_func)(struct rw_info *, void (*)(struct rw_info *));
......
......@@ -16,6 +16,7 @@ PERMS="
@localstatedir@/tmp 770
@localstatedir@/msgs 750
@localstatedir@/msgq 750
@localstatedir@/sts 755
@localstatedir@/track 755
@sysconfdir@ 755 x
......
/*
** Copyright 1998 - 2011 Double Precision, Inc.
** Copyright 1998 - 2019 Double Precision, Inc.
** See COPYING for distribution information.
*/
......@@ -8,13 +8,17 @@
#include "rfc1035/config.h"
#include "rfc1035/rfc1035.h"
#include "rfc1035/rfc1035mxlist.h"
#include "comsts.h"
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "numlib/numlib.h"
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
static void setns(const char *p, struct rfc1035_res *res)
{
......@@ -46,10 +50,21 @@ int main(int argc, char **argv)
struct rfc1035_mxlist *mxlist, *p;
struct rfc1035_res res;
int rc;
int sts=0, sts_expire_opt=0, sts_purge_opt=0,
sts_cache_disable_opt=0, sts_cache_enable_opt=0;
const char *sts_override_opt=0;
struct sts_id domain_sts_id;
enum sts_mode domain_sts_mode;
int need_domain=1;
argn=1;
srand(time(NULL));
if (geteuid() == 0)
libmail_changeuidgid(MAILUID, MAILGID);
umask(022);
rfc1035_init_resolv(&res);
while (argn < argc)
{
......@@ -80,41 +95,204 @@ int main(int argc, char **argv)
continue;
}
if (strcmp(argv[argn], "--sts") == 0)
{
sts=1;
++argn;
continue;
}
if (strcmp(argv[argn], "--sts-expire") == 0)
{
sts_expire_opt=1;
++argn;
need_domain=0;
continue;
}
if (strncmp(argv[argn], "--sts-override=", 15) == 0)
{
sts_override_opt=argv[argn]+15;
++argn;
}
if (strcmp(argv[argn], "--sts-purge") == 0)
{
sts_purge_opt=1;
++argn;
continue;
}
if (strcmp(argv[argn], "--sts-cache-disable") == 0)
{
sts_cache_disable_opt=1;
++argn;
need_domain=0;
continue;
}
if (strcmp(argv[argn], "--sts-cache-enable") == 0)
{
sts_cache_enable_opt=-1;
++argn;
need_domain=0;
continue;
}
if (strncmp(argv[argn], "--sts-cache-enable=", 19) == 0)
{
sts_cache_enable_opt=atoi(argv[argn]+19);
if (sts_cache_enable_opt <= 0 ||
sts_cache_enable_opt > 999999)
{
fprintf(stderr, "Invalid option.\n");
exit(1);
}
++argn;
need_domain=0;
continue;
}
if (strcmp(argv[argn], "--sts-cache-disable") == 0)
{
sts_cache_disable_opt=1;
++argn;
need_domain=0;
continue;
}
break;
}
if (sts_expire_opt + sts_purge_opt + sts + (sts_override_opt ? 1:0)
+ (sts_cache_enable_opt ? 1:0) + sts_cache_disable_opt > 1
||
(argn >= argc ? 0:1) != need_domain)
{
fprintf(stderr,
"Usage: testmxlookup [options] [domain]\n"
" @ip-address override default name server\n"
" --dnssec enable DNSSEC\n"
" --udpsize n override eDNS payload size\n"
" --sts-cache-disable disable the STS policy cache\n"
" --sts-cache-enable enable the STS policy cache\n"
" --sts-cache-enable=n enable the STS policy cache"
" with a non-default size\n"
" --sts show STS policy for domain\n"
" --sts-expire manually expire the STS"
" cache\n"
" --sts-purge manually remove domain's"
" cached STS policy\n"
" --sts-override=[p] manually override domain's"
" cached STS policy (none,\n"
" testing, enforce)\n");
exit(1);
}
if (sts_expire_opt)
{
sts_expire();
exit(0);
}
if (sts_cache_disable_opt)
{
sts_cache_disable();
exit(0);
}
if (sts_cache_enable_opt)
{
if (sts_cache_enable_opt < 0)
{
sts_cache_enable();
}
else
{
sts_cache_size_set(sts_cache_enable_opt);
}
exit(0);
}
if (argn >= argc) exit(0);
q_name=argv[argn++];
if (sts_purge_opt)
{
sts_policy_purge(q_name);
printf("Reloading %s\n", q_name);
sts=1;
}
if (sts_override_opt)
{
sts_policy_override(q_name, sts_override_opt);
sts=1;
}
sts_init(&domain_sts_id, q_name);
if (sts)
{
if (!domain_sts_id.id)
{
fprintf(stderr, "%s: STS policy not found.\n",
q_name);
exit(1);
}
printf("STS Policy ID: %s%s\n", domain_sts_id.id,
(domain_sts_id.cached ? " (cached)":""));
printf("Timestamp : %s", ctime(&domain_sts_id.timestamp));
printf("Expiration : %s\n", ctime(&domain_sts_id.expiration));
printf("%s", domain_sts_id.policy);
sts_deinit(&domain_sts_id);
exit(0);
}
rc=rfc1035_mxlist_create_x(&res, q_name,
RFC1035_MX_AFALLBACK |
RFC1035_MX_IGNORESOFTERR,
&mxlist);
rfc1035_destroy_resolv(&res);
if (rc != RFC1035_MX_OK)
sts_deinit(&domain_sts_id);
switch (rc) {
case RFC1035_MX_OK:
break;
case RFC1035_MX_SOFTERR:
printf("%s: soft error.\n", q_name);
exit(0);
exit(1);
case RFC1035_MX_HARDERR:
printf("%s: hard error.\n", q_name);
exit(0);
exit(1);
case RFC1035_MX_INTERNAL:
printf("%s: internal error.\n", q_name);
exit(0);
exit(1);
case RFC1035_MX_BADDNS:
printf("%s: bad DNS records (recursive CNAME).\n", q_name);
exit(0);
exit(1);
}
printf("Domain %s:\n", q_name);
domain_sts_mode=get_sts_mode(&domain_sts_id);
switch (domain_sts_mode) {
case sts_mode_none:
break;
case sts_mode_testing:
printf("STS: testing\n");
break;
case sts_mode_enforce:
printf("STS: enforcing\n");
break;
}
for (p=mxlist; p; p=p->next)
{
RFC1035_ADDR addr;
char buf[RFC1035_NTOABUFSIZE];
RFC1035_ADDR addr;
char buf[RFC1035_NTOABUFSIZE];
if (rfc1035_sockaddrip(&p->address, sizeof(p->address),
&addr)<0)
......@@ -127,8 +305,15 @@ int main(int argc, char **argv)
config_islocal(p->hostname, NULL) ? " [ LOCAL ]":"",
strcmp(p->hostname, buf) ? "":" [ ERROR ]",
p->ad ? " (DNSSEC)":"");
if (domain_sts_mode != sts_mode_none &&
sts_mx_validate(&domain_sts_id, p->hostname))
printf("ERROR: STS Policy verification failed: %s\n",
p->hostname);
}
rfc1035_mxlist_free(mxlist);
sts_deinit(&domain_sts_id);
return (0);
}