Commit 05fdbfcd authored by Sam Varshavchik's avatar Sam Varshavchik

Framework for STS lookups.

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.
parent 2c63f813
......@@ -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
......
......@@ -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))
......
......@@ -21,6 +21,7 @@
#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>
......@@ -221,6 +222,7 @@ struct mybuf trigger_buf;
drvinfo::init();
libmail_changeuidgid(MAILUID, MAILGID);
signal(SIGPIPE, SIG_IGN);
respawnlo=config_time_respawnlo();
......
......@@ -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; \
......
<!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>
......@@ -17,10 +17,11 @@
<refsynopsisdiv>
<cmdsynopsis sepchar=" ">
<command moreinfo="none">testmxlookup</command>
<command>testmxlookup</command>
<arg choice="opt">@<replaceable>ip-address</replaceable></arg>
<arg choice="opt">--dnssec</arg>
<arg choice="opt">--udpsize <replaceable>n</replaceable></arg>
<arg choice="opt">--sts</arg>
<arg choice="req"><replaceable>domain</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -29,24 +30,41 @@
<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> 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>
<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 list is followed by the domain's strict transport security
(<acronym>STS</acronym>) policy status, if one is published.
</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> after 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>
</refsect2>
<refsect2>
......@@ -101,15 +119,66 @@ 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>
</variablelist>
</refsect2>
<refsect2>
<title>STRICT TRANSPORT SECURITY</title>
<para>
Courier automatically download and caches domains'
<acronym>STS</acronym> policy files by default.
<filename>@localstatedir@/sts/.cache_size</filename>
sets the size of the <acronym>STS</acronym> cache, 1000 domains by
default, and <acronym>STS</acronym> checking can be turned off by
setting the cache size to 0:
</para>
<blockquote>
<informalexample>
<programlisting>
echo 0 &gt;@localstatedir@/sts/.cache_size</programlisting>
</informalexample>
</blockquote>
<para>
<command>rm</command> it to reenable default <acronym>STS</acronym>
cache settings.
</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>
</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>
......@@ -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 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.
*/
void sts_expire();
#if 0
{
#endif
#ifdef __cplusplus
}
#endif
#endif
......@@ -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,18 @@ int main(int argc, char **argv)
struct rfc1035_mxlist *mxlist, *p;
struct rfc1035_res res;
int rc;
int sts=0, sts_expire_opt=0;
struct sts_id domain_sts_id;
enum sts_mode domain_sts_mode;
argn=1;
srand(time(NULL));
if (geteuid() == 0)
libmail_changeuidgid(MAILUID, MAILGID);
umask(022);
rfc1035_init_resolv(&res);
while (argn < argc)
{
......@@ -80,41 +92,84 @@ 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;
continue;
}
break;
}
if (sts_expire_opt)
{
sts_expire();
exit(0);
}
if (argn >= argc) exit(0);
q_name=argv[argn++];
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);
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 +182,25 @@ 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);
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;
}
sts_deinit(&domain_sts_id);
return (0);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment