Commit 03a33037 authored by Sam Varshavchik's avatar Sam Varshavchik

Implement STS.

parent 05fdbfcd
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
......
......@@ -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
......
/*
** 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);
......
/*
** Copyright 1998 - 2013 Double Precision, Inc.
** Copyright 1998 - 2019 Double Precision, Inc.
** See COPYING for distribution information.
*/
#include "cdpendelinfo.h"
......@@ -18,6 +18,7 @@
#include "comparseqid.h"
#include "comqueuename.h"
#include "comtrack.h"
#include "comsts.h"
#include "courierd.h"
#include "mydirent.h"
#include "numlib/numlib.h"
......@@ -69,6 +70,7 @@ time_t retryalpha;
int retrybeta;
time_t retrygamma;
int retrymaxdelta;
int sts_cache_size_counter;
time_t queuefill;
time_t nextqueuefill;
......@@ -243,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)
......
......@@ -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>
......
......@@ -16,22 +16,37 @@
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis sepchar=" ">
<cmdsynopsis>
<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>
<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>testmxlookup</command> lists the names and IP addresses of mail
relays that receive mail for the <replaceable>domain</replaceable>.
<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>
......@@ -40,8 +55,9 @@
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.
The domain's strict transport security
(<acronym>STS</acronym>) policy status, if one is published,
precedes the mail relay list.
</para>
<refsect2>
......@@ -57,7 +73,7 @@
<para>
<quote>STS: testing</quote> or
<quote>STS: enforcing</quote> after the list of mail relays
<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
......@@ -65,6 +81,15 @@
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>
......@@ -130,6 +155,85 @@
</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>
......@@ -137,24 +241,10 @@
<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.
<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>
......@@ -169,6 +259,18 @@ echo 0 &gt;@localstatedir@/sts/.cache_size</programlisting>
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>
......
......@@ -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
......@@ -477,8 +478,7 @@ void lookup(int argc, char **argv)
break;
default:
fprintf(stderr,
"Usage: verifysmtp [-t trackingdirectory] [-m full|base|domain]\n",
argv[0]);
"Usage: verifysmtp [-t trackingdirectory] [-m full|base|domain]\n");
exit(1);
}
}
......@@ -740,6 +740,9 @@ int main(int argc, char **argv)
if (argc > 1)
{
umask(022);
if (geteuid() == 0)
libmail_changeuidgid(MAILUID, MAILGID);
lookup(argc, argv);
}
......
......@@ -31,7 +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 \
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 \
......
......@@ -17,6 +17,7 @@
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
static char *policy_filename_for_domain(const char *domain);
......@@ -28,41 +29,30 @@ static int get_new_policy(struct sts_id *id,
const char *domain);
static void save(struct sts_id *id, FILE *cached_policy_fp);
static int sts_init1(struct sts_id *id,
static int sts_init2(struct sts_id *id,
const char *domain,
const char *policy_filename,
FILE *cached_policy_fp,
int readwrite);
static int get_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_init(struct sts_id *id, const char *domain)
int readwrite,
void *ignore);
extern int sts_cache_size();
static void sts_init1(struct sts_id *id, const char *domain,
int (*process)(struct sts_id *id,
const char *domain,
const char *policy_filename,
FILE *cached_policy_fp,
int readwrite,
void *arg),
void *arg)
{
FILE *cached_policy_fp;
int rc;
char *policy_filename;
int readwrite;
int cache_size=get_cache_size();
int cache_size=sts_cache_size();
struct stat stat_buf1;
struct stat stat_buf2;
......@@ -100,8 +90,8 @@ void sts_init(struct sts_id *id, const char *domain)
break;
}
rc=sts_init1(id, domain, policy_filename, cached_policy_fp,
readwrite);
rc=(*process)(id, domain, policy_filename, cached_policy_fp,
readwrite, arg);
if (cached_policy_fp)
{
......@@ -115,17 +105,127 @@ void sts_init(struct sts_id *id, const char *domain)
sts_deinit(id);
}
void sts_init(struct sts_id *id, const char *domain)
{
sts_init1(id, domain, sts_init2, 0);
}
void sts_policy_purge(const char *domain)
{
char *policy_filename=policy_filename_for_domain(domain);
if (!policy_filename)
return;
if (unlink(policy_filename) < 0)
perror(policy_filename);
free(policy_filename);
}
static int sts_policy_override2(struct sts_id *id,
const char *domain,
const char *policy_filename,
FILE *cached_policy_fp,
int readwrite,
void *mode_ptr);
void sts_policy_override(const char *domain, const char *mode_str)
{
enum sts_mode mode;
struct sts_id dummy;
if (strcmp(mode_str, "none") == 0)
mode=sts_mode_none;
else if (strcmp(mode_str, "testing") == 0)
mode=sts_mode_testing;
else if (strcmp(mode_str, "enforce") == 0)
mode=sts_mode_enforce;
else
{
printf("Unknown STS policy: %s\n", mode_str);
return;
}
sts_init1(&dummy, domain, sts_policy_override2, &mode);
sts_deinit(&dummy);
}
static int sts_policy_override2(struct sts_id *id,
const char *domain,
const char *policy_filename,
FILE *cached_policy_fp,
int readwrite,
void *mode_ptr)
{
char *new_policy;
if (!cached_policy_fp || load(id, cached_policy_fp))
{
printf("%s: cached policy not found.\n", domain);
}
else if (!readwrite)
{
printf("%s: no write permission.\n", policy_filename);
}
else if ((new_policy=courier_malloc(strlen(id->policy)+
strlen(domain)+1024)) != 0)
{
char *put_p=new_policy;
const char *p;
char lastc='\n';
int skipping=0;
for (p=id->policy; *p; p++)
{
if (lastc == '\n')
skipping=strncmp(p, "mode:", 5) == 0 ||
strncmp(p, "info:", 5) == 0;
if (!skipping)
*put_p++=*p;
lastc=*p;
}
if (lastc != '\n')
*put_p++='\n';
sprintf(put_p,
"info: this policy has been manually overridden, "
"as follows\n"
"info: use \"testmxlookup --sts-purge %s\" to "
"restore the default policy:\n",
domain);
switch (*(enum sts_mode *)mode_ptr) {
case sts_mode_none:
strcat(put_p, "mode: none\n");
break;
case sts_mode_testing:
strcat(put_p, "mode: testing\n");
break;
case sts_mode_enforce:
strcat(put_p, "mode: enforce\n");
break;
}
free(id->policy);
id->policy=new_policy;
save(id, cached_policy_fp);
}
return 0;
}
static char *sts_getid(const char *domain);
static char *sts_download(const char *domain);
static unsigned max_age(struct sts_id *id);
static void reset_timestamp(struct sts_id *id);
static int sts_init1(struct sts_id *id,
static int sts_init2(struct sts_id *id,
const char *domain,
const char *policy_filename,
FILE *cached_policy_fp,
int readwrite)
int readwrite,
void *ignore)
{
int rc;
......@@ -814,110 +914,3 @@ static int load2(struct sts_id *id, FILE *fp)
return 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 **fa=a, **fb=b;
return (*fa)->timestamp < (*fb)->timestamp ? -1
: (*fa)->timestamp > (*fb)->timestamp ? 1
: 0;
}
void sts_expire()
{
struct sts_file_list *list=0;
DIR *dirp;
struct dirent *de;
size_t cnt=0;
size_t i;
size_t max_size=get_cache_size();
struct sts_file_list **array;
if (max_size == 0)
return;
dirp=opendir(STSDIR);
if (!dirp)
return;
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;
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;
}
}
......@@ -78,10 +78,42 @@ enum sts_mode get_sts_mode(struct sts_id *id);
int sts_mx_validate(struct sts_id *id, const char *domainname);
/*
** Expire old cache entries.
** Expire old cache entries, returns the cache size.
*/
void sts_expire();
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);
/*