summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMario Preksavec <mario at slackware dot hr>2019-05-17 23:20:52 +0700
committerWilly Sudiarto Raharjo <willysr@slackbuilds.org>2019-05-17 23:20:52 +0700
commitfe01e850a8271f3dc25a72e2143fef816623d03b (patch)
treec9301b38b0e998240048507289294a95cb2dd1a3
parent39262ecf08a2b14804e4c2bd488790ff6ded6e72 (diff)
downloadslackbuilds-fe01e850a8271f3dc25a72e2143fef816623d03b.tar.gz
network/opendmarc: Added (DMARC milter and library).
Signed-off-by: Willy Sudiarto Raharjo <willysr@slackbuilds.org>
-rw-r--r--network/opendmarc/README17
-rw-r--r--network/opendmarc/README.SLACKWARE26
-rw-r--r--network/opendmarc/doinst.sh26
-rw-r--r--network/opendmarc/opendmarc.SlackBuild162
-rw-r--r--network/opendmarc/opendmarc.info10
-rw-r--r--network/opendmarc/patches/fix-python-interpreter.diff11
-rw-r--r--network/opendmarc/patches/ticket137.patch51
-rw-r--r--network/opendmarc/patches/ticket146.patch110
-rw-r--r--network/opendmarc/patches/ticket153.patch35
-rw-r--r--network/opendmarc/patches/ticket159.patch77
-rw-r--r--network/opendmarc/patches/ticket180.patch280
-rw-r--r--network/opendmarc/patches/ticket182.patch18
-rw-r--r--network/opendmarc/patches/ticket183.patch13
-rw-r--r--network/opendmarc/patches/ticket184.patch17
-rw-r--r--network/opendmarc/patches/ticket193.patch193
-rw-r--r--network/opendmarc/patches/ticket203.patch26
-rw-r--r--network/opendmarc/patches/ticket204.patch31
-rw-r--r--network/opendmarc/patches/ticket205.patch38
-rw-r--r--network/opendmarc/patches/ticket207.patch49
-rw-r--r--network/opendmarc/patches/ticket208.patch116
-rw-r--r--network/opendmarc/patches/ticket212.patch18
-rw-r--r--network/opendmarc/patches/ticket227.patch40
-rw-r--r--network/opendmarc/patches/z00_ticket138_v3.patch83
-rw-r--r--network/opendmarc/patches/z01_changeSubjectFailureReport_v2.patch27
-rw-r--r--network/opendmarc/patches/z02_content-description.patch28
-rw-r--r--network/opendmarc/patches/z03_reportDestVerificationV2.patch487
-rw-r--r--network/opendmarc/patches/z04_moreHeadersFailureReportVsBeta1.patch352
-rw-r--r--network/opendmarc/patches/z06_use_envdomain_SPF_logging.patch13
-rw-r--r--network/opendmarc/slack-desc19
29 files changed, 2373 insertions, 0 deletions
diff --git a/network/opendmarc/README b/network/opendmarc/README
new file mode 100644
index 0000000000..ba50fb5889
--- /dev/null
+++ b/network/opendmarc/README
@@ -0,0 +1,17 @@
+OpenDMARC is a free open source software implementation of the DMARC
+specification.
+
+Enable MySQL support: WITH_MYSQL=yes ./opendmarc.SlackBuild
+
+Optional dependency (will be autodetected): libspf2
+
+You must have a opendmarc user to run this script:
+
+ # groupadd -g 362 opendmarc
+ # useradd -u 362 -d /var/run/opendmarc -s /bin/false -g opendmarc opendmarc
+
+And if you have postfix installed, add it to the group:
+
+ # usermod -a -G opendmarc postfix
+
+More information post-install can be found in README.SLACKWARE
diff --git a/network/opendmarc/README.SLACKWARE b/network/opendmarc/README.SLACKWARE
new file mode 100644
index 0000000000..ad1a492ec3
--- /dev/null
+++ b/network/opendmarc/README.SLACKWARE
@@ -0,0 +1,26 @@
+Starting the daemon on boot
+---------------------------
+
+You may wish to add these lines to /etc/rc.d/rc.local to start the service:
+
+ if [ -x /etc/rc.d/rc.opendmarc ]; then
+ /etc/rc.d/rc.opendmarc start
+ fi
+
+You may also add these lines to /etc/rc.d/rc.local_shutdown:
+
+ if [ -x /etc/rc.d/rc.opendmarc ]; then
+ /etc/rc.d/rc.opendmarc stop
+ fi
+
+Remember to give executable permission to /etc/rc.d/rc.local_shutdown:
+
+ chmod 0755 /etc/rc.d/rc.local_shutdown
+
+Hooking into postfix
+--------------------
+
+Make sure that the DMARC milter is declared after the DKIM milter in /etc/postfix/main.cf
+
+smtpd_milters = unix:/var/run/opendkim/opendkim.sock, unix:/var/run/opendmarc/opendmarc.sock
+non_smtpd_milters = unix:/var/run/opendkim/opendkim.sock, unix:/var/run/opendmarc/opendmarc.sock
diff --git a/network/opendmarc/doinst.sh b/network/opendmarc/doinst.sh
new file mode 100644
index 0000000000..f7eaa733f4
--- /dev/null
+++ b/network/opendmarc/doinst.sh
@@ -0,0 +1,26 @@
+config() {
+ NEW="$1"
+ OLD="$(dirname $NEW)/$(basename $NEW .new)"
+ # If there's no config file by that name, mv it over:
+ if [ ! -r $OLD ]; then
+ mv $NEW $OLD
+ elif [ "$(cat $OLD | md5sum)" = "$(cat $NEW | md5sum)" ]; then
+ # toss the redundant copy
+ rm $NEW
+ fi
+ # Otherwise, we leave the .new copy for the admin to consider...
+}
+
+preserve_perms() {
+ NEW="$1"
+ OLD="$(dirname $NEW)/$(basename $NEW .new)"
+ if [ -e $OLD ]; then
+ cp -a $OLD ${NEW}.incoming
+ cat $NEW > ${NEW}.incoming
+ mv ${NEW}.incoming $NEW
+ fi
+ config $NEW
+}
+
+preserve_perms etc/rc.d/rc.opendmarc.new
+config etc/opendmarc.conf.new
diff --git a/network/opendmarc/opendmarc.SlackBuild b/network/opendmarc/opendmarc.SlackBuild
new file mode 100644
index 0000000000..1747782641
--- /dev/null
+++ b/network/opendmarc/opendmarc.SlackBuild
@@ -0,0 +1,162 @@
+#!/bin/sh
+
+# Slackware build script for opendmarc
+
+# Copyright 2019 Mario Preksavec, Zagreb, Croatia
+# All rights reserved.
+#
+# Redistribution and use of this script, with or without modification, is
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of this script must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+PRGNAM=opendmarc
+VERSION=${VERSION:-1.3.2}
+BUILD=${BUILD:-1}
+TAG=${TAG:-_SBo}
+
+if [ -z "$ARCH" ]; then
+ case "$( uname -m )" in
+ i?86) ARCH=i586 ;;
+ arm*) ARCH=arm ;;
+ *) ARCH=$( uname -m ) ;;
+ esac
+fi
+
+CWD=$(pwd)
+TMP=${TMP:-/tmp/SBo}
+PKG=$TMP/package-$PRGNAM
+OUTPUT=${OUTPUT:-/tmp}
+
+if [ "$ARCH" = "i586" ]; then
+ SLKCFLAGS="-O2 -march=i586 -mtune=i686"
+ LIBDIRSUFFIX=""
+elif [ "$ARCH" = "i686" ]; then
+ SLKCFLAGS="-O2 -march=i686 -mtune=i686"
+ LIBDIRSUFFIX=""
+elif [ "$ARCH" = "x86_64" ]; then
+ SLKCFLAGS="-O2 -fPIC"
+ LIBDIRSUFFIX="64"
+else
+ SLKCFLAGS="-O2"
+ LIBDIRSUFFIX=""
+fi
+
+# Bail if user or group isn't valid on your system
+if ! grep ^$PRGNAM: /etc/passwd 2>&1 > /dev/null; then
+ cat << EOF
+
+ You must have a $PRGNAM user to run this script
+
+ # groupadd -g 362 $PRGNAM
+ # useradd -u 362 -d /var/run/$PRGNAM -s /bin/false -g $PRGNAM $PRGNAM
+
+EOF
+
+ if grep ^postfix: /etc/passwd 2>&1 > /dev/null; then
+ cat << EOF
+
+ You might want to add postfix user to the group
+
+ # usermod -a -G $PRGNAM postfix
+
+EOF
+ fi
+ exit
+fi
+
+case ${WITH_MYSQL,,} in
+ y) SQLARGS=--with-sql-backend ;;
+ *) SQLARGS= ;;
+esac
+
+set -e
+
+rm -rf $PKG
+mkdir -p $TMP $PKG $OUTPUT
+cd $TMP
+rm -rf $PRGNAM-$VERSION
+tar xvf $CWD/$PRGNAM-$VERSION.tar.gz
+cd $PRGNAM-$VERSION
+chown -R root:root .
+find -L . \
+ \( -perm 777 -o -perm 775 -o -perm 750 -o -perm 711 -o -perm 555 \
+ -o -perm 511 \) -exec chmod 755 {} \; -o \
+ \( -perm 666 -o -perm 664 -o -perm 640 -o -perm 600 -o -perm 444 \
+ -o -perm 440 -o -perm 400 \) -exec chmod 644 {} \;
+
+# http://batleth.sapienti-sat.org/projects/opendmarc/
+# ... and a few more
+for i in $CWD/patches/* ; do patch -p1 <$i ; done
+
+autoreconf -vif
+
+CFLAGS="$SLKCFLAGS" \
+CXXFLAGS="$SLKCFLAGS" \
+./configure \
+ --prefix=/usr \
+ --libdir=/usr/lib${LIBDIRSUFFIX} \
+ --sysconfdir=/etc \
+ --localstatedir=/var \
+ --mandir=/usr/man \
+ --docdir=/usr/doc/$PRGNAM-$VERSION \
+ --enable-static=no \
+ --with-spf \
+ $SQLARGS \
+ --build=$ARCH-slackware-linux
+
+make
+make install DESTDIR=$PKG
+
+# Install init script
+install -D -m0755 -oroot -groot contrib/init/generic/$PRGNAM $PKG/etc/rc.d/rc.$PRGNAM.new
+sed -e "s|^\(prefix=\).*|\1|" \
+ -e "s|^\(exec_prefix=\).*|\1/usr|" \
+ -e "s|/etc/sysconfig/opendmarc|/etc/default/opendmarc|" \
+ -i $PKG/etc/rc.d/rc.$PRGNAM.new
+
+# Configure defaults
+install -D -m0644 -oroot -groot $PRGNAM/$PRGNAM.conf.sample $PKG/etc/$PRGNAM.conf.new
+sed -e "s|^# \(AuthservID\) .*|\1 HOSTNAME|" \
+ -e "s|^# \(HistoryFile\) .*|\1 /var/run/$PRGNAM/$PRGNAM.dat|" \
+ -e "s|^# \(Socket\) .*|\1 unix:/var/run/$PRGNAM/$PRGNAM.sock|" \
+ -e "s|^# \(SPFIgnoreResults\) .*|\1 true|" \
+ -e "s|^# \(SPFSelfValidate\) .*|\1 true|" \
+ -e "s|^# \(Syslog\) .*|\1 true|" \
+ -e "s|^# \(UMask\) .*|\1 007|" \
+ -e "s|^# \(UserID\) .*|\1 $PRGNAM:$PRGNAM|" \
+ -i $PKG/etc/$PRGNAM.conf.new
+
+# Home directory for runtime data
+mkdir -p $PKG/var/run/$PRGNAM
+chown $PRGNAM:$PRGNAM $PKG/var/run/$PRGNAM
+
+find $PKG -print0 | xargs -0 file | grep -e "executable" -e "shared object" | grep ELF \
+ | cut -f 1 -d : | xargs strip --strip-unneeded 2> /dev/null || true
+
+find $PKG/usr/man -type f -exec gzip -9 {} \;
+for i in $( find $PKG/usr/man -type l ) ; do ln -s $( readlink $i ).gz $i.gz ; rm $i ; done
+
+mkdir -p $PKG/usr/doc/$PRGNAM-$VERSION
+cp -a RELEASE_NOTES $PKG/usr/doc/$PRGNAM-$VERSION
+cat $CWD/$PRGNAM.SlackBuild > $PKG/usr/doc/$PRGNAM-$VERSION/$PRGNAM.SlackBuild
+cat $CWD/README.SLACKWARE > $PKG/usr/doc/$PRGNAM-$VERSION/README.SLACKWARE
+
+mkdir -p $PKG/install
+cat $CWD/slack-desc > $PKG/install/slack-desc
+cat $CWD/doinst.sh > $PKG/install/doinst.sh
+
+cd $PKG
+/sbin/makepkg -l y -c n $OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.${PKGTYPE:-tgz}
diff --git a/network/opendmarc/opendmarc.info b/network/opendmarc/opendmarc.info
new file mode 100644
index 0000000000..061a20ed5e
--- /dev/null
+++ b/network/opendmarc/opendmarc.info
@@ -0,0 +1,10 @@
+PRGNAM="opendmarc"
+VERSION="1.3.2"
+HOMEPAGE="http://www.trusteddomain.org/opendmarc/"
+DOWNLOAD="https://sourceforge.net/projects/opendmarc/files/opendmarc-1.3.2.tar.gz"
+MD5SUM="2b4e9b8be7fe61800515cef1d7e6a905"
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+REQUIRES=""
+MAINTAINER="Mario Preksavec"
+EMAIL="mario at slackware dot hr"
diff --git a/network/opendmarc/patches/fix-python-interpreter.diff b/network/opendmarc/patches/fix-python-interpreter.diff
new file mode 100644
index 0000000000..e81a3328e3
--- /dev/null
+++ b/network/opendmarc/patches/fix-python-interpreter.diff
@@ -0,0 +1,11 @@
+Does not need to be forwarded. Upstream uses FreeBSD.
+Index: opendmarc-1.0.1+dfsg/contrib/rddmarc/dmarcfail.py
+===================================================================
+--- opendmarc-1.0.1+dfsg.orig/contrib/rddmarc/dmarcfail.py 2012-11-18 00:08:28.105858463 -0500
++++ opendmarc-1.0.1+dfsg/contrib/rddmarc/dmarcfail.py 2012-11-18 00:09:10.041857224 -0500
+@@ -1,4 +1,4 @@
+-#!/usr/local/bin/python
++#!/usr/bin/python
+ # $Header: /home/johnl/hack/dmarc/RCS/dmarcfail.py,v 1.1 2012/07/12 03:59:29 johnl Exp $
+ # parse DMARC failure reports, add it to the mysql database
+ # optional arguments are names of files containing ARF messages,
diff --git a/network/opendmarc/patches/ticket137.patch b/network/opendmarc/patches/ticket137.patch
new file mode 100644
index 0000000000..df6c062152
--- /dev/null
+++ b/network/opendmarc/patches/ticket137.patch
@@ -0,0 +1,51 @@
+From afc1615946cd127d9ea28e0892934251c6a00a84 Mon Sep 17 00:00:00 2001
+From: "Murray S. Kucherawy" <msk@trusteddomain.org>
+Date: Sat, 4 Mar 2017 08:03:22 -0800
+Subject: [PATCH] Fix bug #137: Handle base64 inside AR tokens that are values.
+ Problem reported by Joseph Coffland.
+
+---
+ RELEASE_NOTES | 2 ++
+ opendmarc/opendmarc-ar.c | 14 +++++++++++++-
+ 2 files changed, 15 insertions(+), 1 deletion(-)
+
+Index: opendmarc/RELEASE_NOTES
+===================================================================
+--- opendmarc.orig/RELEASE_NOTES 2018-12-17 01:38:44.570329334 -0500
++++ opendmarc/RELEASE_NOTES 2018-12-17 01:40:21.062333399 -0500
+@@ -3,6 +3,10 @@
+ This listing shows the versions of the OpenDMARC package, the date of
+ release, and a summary of the changes in that release.
+
++ 1.4.0 2017/??/??
++ Fix bug #137: Handle base64 inside AR tokens that are values.
++ Problem reported by Joseph Coffland.
++
+ 1.3.2 2016/12/19
+ Feature request #86: Change meaning of "RequiredHeaders" such that
+ header validity is always checked, but messages are only
+Index: opendmarc/opendmarc/opendmarc-ar.c
+===================================================================
+--- opendmarc.orig/opendmarc/opendmarc-ar.c 2018-12-17 01:38:44.570329334 -0500
++++ opendmarc/opendmarc/opendmarc-ar.c 2018-12-17 01:38:44.566329334 -0500
+@@ -602,7 +602,19 @@
+ ar->ares_result[n - 1].result_props = r;
+
+ prevstate = state;
+- state = 9;
++ if (c < ntoks - 1 && tokens[c + 1][1] == '\0')
++ {
++ if (tokens[c + 1][0] == ';')
++ state = 2;
++ else if (tokens[c + 1][0] == '=')
++ r--;
++ else
++ state = 9;
++ }
++ else
++ {
++ state = 9;
++ }
+
+ break;
+ }
diff --git a/network/opendmarc/patches/ticket146.patch b/network/opendmarc/patches/ticket146.patch
new file mode 100644
index 0000000000..c7f6f5748b
--- /dev/null
+++ b/network/opendmarc/patches/ticket146.patch
@@ -0,0 +1,110 @@
+diff --git a/reports/opendmarc-import.8.in b/reports/opendmarc-import.8.in
+index 8f55848..4e854ac 100644
+--- a/reports/opendmarc-import.8.in
++++ b/reports/opendmarc-import.8.in
+@@ -12,8 +12,6 @@ reads per-message data recorded by an instance of
+ and inserts it into an SQL database, for later use by
+ .B opendmarc-reports(8)
+ to generate aggregate reports.
+-
+-Records are read from standard input.
+ .SH OPTIONS
+ .TP
+ .I --dbhost=hostname
+@@ -44,6 +42,9 @@ the environment variable is not set.
+ .I --help
+ Prints a help message and terminates.
+ .TP
++.I --input=file
++Reads from the named file instead of from standard input (the default).
++.TP
+ .I --verbose
+ Increase the amount of verbosity written to standard output.
+ .TP
+diff --git a/reports/opendmarc-import.in b/reports/opendmarc-import.in
+index 5a28f2f..cccbace 100755
+--- a/reports/opendmarc-import.in
++++ b/reports/opendmarc-import.in
+@@ -35,11 +35,14 @@ my $def_dbuser = "opendmarc";
+ my $def_dbpasswd = "opendmarc";
+ my $def_dbport = "3306";
+ my $def_interval = "86400";
++my $def_inputfh = *STDIN;
+ my $dbhost;
+ my $dbname;
+ my $dbuser;
+ my $dbpasswd;
+ my $dbport;
++my $inputfile;
++my $inputfh;
+
+ my $dbscheme = "@SQL_BACKEND@";
+
+@@ -326,6 +329,7 @@ sub usage
+ print STDERR "\t--dbpasswd=passwd database password [$def_dbpasswd]\n";
+ print STDERR "\t--dbport=port database port [$def_dbport]\n";
+ print STDERR "\t--dbuser=user database user [$def_dbuser]\n";
++ print STDERR "\t--input=file input file [STDIN]\n";
+ print STDERR "\t--help print help and exit\n";
+ print STDERR "\t--verbose verbose output\n";
+ print STDERR "\t--version print version and exit\n";
+@@ -337,6 +341,7 @@ my $opt_retval = &Getopt::Long::GetOptions ('dbhost=s' => \$dbhost,
+ 'dbpasswd=s' => \$dbpasswd,
+ 'dbport=s' => \$dbport,
+ 'dbuser=s' => \$dbuser,
++ 'input=s' => \$inputfile,
+ 'help!' => \$helponly,
+ 'verbose!' => \$verbose,
+ 'version!' => \$showversion,
+@@ -428,6 +433,24 @@ if ($verbose)
+ print STDERR "$progname: started at " . localtime() . "\n";
+ }
+
++
++if (!defined($inputfile))
++{
++ $inputfh = $def_inputfh;
++}
++else
++{
++ open($inputfh, "<", $inputfile) or die "$progname: unable to open $inputfile: $!\n";
++ if ($verbose)
++ {
++ print STDERR "$progname: opened file $inputfile\n"
++ }
++}
++if (!flock($inputfh, LOCK_SH))
++{
++ print STDERR "$progname: warning: unable to establish read lock\n";
++}
++
+ my $dbi_dsn = "DBI:" . $dbscheme . ":database=" . $dbname .
+ ";host=" . $dbhost . ";port=" . $dbport;
+
+@@ -447,13 +470,10 @@ if ($verbose)
+ # Read history file from stdin.
+ #
+
++
+ $lineno = 0;
+-if (!flock(STDIN, LOCK_SH))
+-{
+- print STDERR "$progname: warning: unable to establish read lock\n";
+-}
+
+-while (<STDIN>)
++while (<$inputfh>)
+ {
+ $lineno++;
+
+@@ -592,6 +612,10 @@ if (defined($jobid))
+ update_db();
+ }
+
++if (defined($inputfile))
++{
++ close($inputfh);
++}
+ #
+ # all done!
+ #
diff --git a/network/opendmarc/patches/ticket153.patch b/network/opendmarc/patches/ticket153.patch
new file mode 100644
index 0000000000..1193409cb7
--- /dev/null
+++ b/network/opendmarc/patches/ticket153.patch
@@ -0,0 +1,35 @@
+Description: do not report same dkim result multiple times
+ in the same record object
+URL: https://sf.net/p/opendmarc/tickets/153/
+Author: Tomki
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+Index: opendmarc-1.3.2/reports/opendmarc-reports.in
+===================================================================
+--- opendmarc-1.3.2.orig/reports/opendmarc-reports.in 2017-03-13 19:01:56.496961757 -0400
++++ opendmarc-1.3.2/reports/opendmarc-reports.in 2017-03-13 19:01:56.492961756 -0400
+@@ -703,6 +703,7 @@
+ exit(1);
+ }
+
++ my %dkim_domain_result_cache = ();
+ while ($dbi_a = $dbi_d->fetchrow_arrayref())
+ {
+ undef $dkimdomain;
+@@ -716,11 +717,15 @@
+ $dkimresult = $dbi_a->[1];
+ }
+
+-
+ if (!defined($dkimdomain))
+ {
+ next;
+ }
++ if (defined($dkim_domain_result_cache{$dkimdomain}{$dkimresult}))
++ {
++ next; # no duplicate per-record auth_result dkim sections
++ }
++ $dkim_domain_result_cache{$dkimdomain}{$dkimresult}++;
+
+ switch ($dkimresult)
+ {
diff --git a/network/opendmarc/patches/ticket159.patch b/network/opendmarc/patches/ticket159.patch
new file mode 100644
index 0000000000..13a9043614
--- /dev/null
+++ b/network/opendmarc/patches/ticket159.patch
@@ -0,0 +1,77 @@
+diff --git a/configure.ac b/configure.ac
+index 255c449..27d7cd4 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -492,6 +492,7 @@ AC_OUTPUT([ Makefile
+ reports/opendmarc-expire.8
+ reports/opendmarc-import
+ reports/opendmarc-import.8
++ reports/opendmarc-importstats
+ reports/opendmarc-importstats.8
+ reports/opendmarc-params
+ reports/opendmarc-params.8
+diff --git a/reports/opendmarc-importstats b/reports/opendmarc-importstats
+deleted file mode 100755
+index 839a871..0000000
+--- a/reports/opendmarc-importstats
++++ /dev/null
+@@ -1,26 +0,0 @@
+-#!/bin/sh
+-##
+-## Copyright (c) 2012, The Trusted Domain Project. All rights reserved.
+-##
+-## opendmarc-importstats -- import opendmarc output to MySQL
+-##
+-## This is intended to be used via a crontab. If import is successful,
+-## this code exits quietly so there's no output. If it fails, it does
+-## "ls -l" on the temporary file, so that cron generates mail to whever
+-## ran the job.
+-
+-## setup
+-statsdb="/var/tmp/dmarc.dat"
+-# OPENDMARC_PASSWORD="password"; export OPENDMARC_PASSWORD
+-
+-if [ -s $statsdb ]
+-then
+- mv $statsdb ${statsdb}.OLD.$$
+-
+- if opendmarc-import < ${statsdb}.OLD.$$
+- then
+- rm ${statsdb}.OLD.$$
+- else
+- ls -l ${statsdb}.OLD.$$
+- fi
+-fi
+diff --git a/reports/opendmarc-importstats.in b/reports/opendmarc-importstats.in
+new file mode 100755
+index 0000000..3a28ee3
+--- /dev/null
++++ b/reports/opendmarc-importstats.in
+@@ -0,0 +1,27 @@
++#!/bin/sh
++##
++## Copyright (c) 2012, The Trusted Domain Project. All rights reserved.
++##
++## opendmarc-importstats -- import opendmarc output to MySQL
++##
++## This is intended to be used via a crontab. If import is successful,
++## this code exits quietly so there's no output. If it fails, it does
++## "ls -l" on the temporary file, so that cron generates mail to whever
++## ran the job.
++
++## setup
++statsdb="`grep ^HistoryFile @sysconfdir@/opendmarc.conf | sed 's/^HistoryFile\s\+//'`"
++[ -z "$statsdb" ] && exit 0
++# OPENDMARC_PASSWORD="password"; export OPENDMARC_PASSWORD
++
++if [ -s "$statsdb" ]
++then
++ mv "$statsdb" "${statsdb}.OLD.$$"
++
++ if opendmarc-import < "${statsdb}.OLD.$$"
++ then
++ rm "${statsdb}.OLD.$$"
++ else
++ ls -l "${statsdb}.OLD.$$"
++ fi
++fi
diff --git a/network/opendmarc/patches/ticket180.patch b/network/opendmarc/patches/ticket180.patch
new file mode 100644
index 0000000000..cd38c39c38
--- /dev/null
+++ b/network/opendmarc/patches/ticket180.patch
@@ -0,0 +1,280 @@
+diff --git a/opendmarc/opendmarc-config.h b/opendmarc/opendmarc-config.h
+index 7ba394b..28f605e 100644
+--- a/opendmarc/opendmarc-config.h
++++ b/opendmarc/opendmarc-config.h
+@@ -36,6 +36,7 @@ struct configdef dmarcf_config[] =
+ { "IgnoreHosts", CONFIG_TYPE_STRING, FALSE },
+ { "IgnoreMailFrom", CONFIG_TYPE_STRING, FALSE },
+ { "MilterDebug", CONFIG_TYPE_INTEGER, FALSE },
++ { "OverrideMLM", CONFIG_TYPE_STRING, FALSE },
+ { "PidFile", CONFIG_TYPE_STRING, FALSE },
+ { "PublicSuffixList", CONFIG_TYPE_STRING, FALSE },
+ { "RecordAllMessages", CONFIG_TYPE_BOOLEAN, FALSE },
+diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
+index ba04312..07e089d 100644
+--- a/opendmarc/opendmarc.c
++++ b/opendmarc/opendmarc.c
+@@ -168,6 +168,7 @@ struct dmarcf_config
+ char * conf_ignorelist;
+ char ** conf_trustedauthservids;
+ char ** conf_ignoredomains;
++ struct list * conf_overridemlm;
+ };
+
+ /* LIST -- basic linked list of strings */
+@@ -1221,6 +1222,18 @@ dmarcf_config_load(struct config *data, struct dmarcf_config *conf,
+ if (str != NULL)
+ dmarcf_mkarray(str, &conf->conf_ignoredomains);
+
++ str = NULL;
++ (void) config_get(data, "OverrideMLM", &str, sizeof str);
++ if (str != NULL)
++ {
++ if (!dmarcf_loadlist(str, &conf->conf_overridemlm))
++ {
++ fprintf(stderr,
++ "%s: can't load override MLM list from %s: %s\n",
++ progname, str, strerror(errno));
++ }
++ }
++
+ (void) config_get(data, "AuthservIDWithJobID",
+ &conf->conf_authservidwithjobid,
+ sizeof conf->conf_authservidwithjobid);
+@@ -2982,30 +2995,45 @@ mlfi_eom(SMFICTX *ctx)
+ case DMARC_POLICY_REJECT: /* Explicit reject */
+ aresult = "fail";
+
+- if (conf->conf_rejectfail && random() % 100 < pct)
++ if (conf->conf_overridemlm != NULL &&
++ (dmarcf_checkhost(cc->cctx_host, conf->conf_overridemlm) ||
++ (dmarcf_checkip((struct sockaddr *)&cc->cctx_ip, conf->conf_overridemlm))))
+ {
+- snprintf(replybuf, sizeof replybuf,
+- "rejected by DMARC policy for %s", pdomain);
+-
+- status = dmarcf_setreply(ctx, DMARC_REJECT_SMTP,
+- DMARC_REJECT_ESC, replybuf);
+- if (status != MI_SUCCESS && conf->conf_dolog)
++ if (conf->conf_dolog)
+ {
+- syslog(LOG_ERR, "%s: smfi_setreply() failed",
+- dfc->mctx_jobid);
++ syslog(LOG_INFO, "%s: overriding policy for mail from %s: MLM",
++ dfc->mctx_jobid, dfc->mctx_fromdomain);
+ }
+-
+- ret = SMFIS_REJECT;
+- result = DMARC_RESULT_REJECT;
++ ret = SMFIS_ACCEPT;
++ result = DMARC_RESULT_OVRD_MAILING_LIST;
+ }
+-
+- if (conf->conf_copyfailsto != NULL)
++ else
+ {
+- status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto);
+- if (status != MI_SUCCESS && conf->conf_dolog)
++ if (conf->conf_rejectfail && random() % 100 < pct)
++ {
++ snprintf(replybuf, sizeof replybuf,
++ "rejected by DMARC policy for %s", pdomain);
++
++ status = dmarcf_setreply(ctx, DMARC_REJECT_SMTP,
++ DMARC_REJECT_ESC, replybuf);
++ if (status != MI_SUCCESS && conf->conf_dolog)
++ {
++ syslog(LOG_ERR, "%s: smfi_setreply() failed",
++ dfc->mctx_jobid);
++ }
++
++ ret = SMFIS_REJECT;
++ result = DMARC_RESULT_REJECT;
++ }
++
++ if (conf->conf_copyfailsto != NULL)
+ {
+- syslog(LOG_ERR, "%s: smfi_addrcpt() failed",
+- dfc->mctx_jobid);
++ status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto);
++ if (status != MI_SUCCESS && conf->conf_dolog)
++ {
++ syslog(LOG_ERR, "%s: smfi_addrcpt() failed",
++ dfc->mctx_jobid);
++ }
+ }
+ }
+
+@@ -3014,30 +3042,45 @@ mlfi_eom(SMFICTX *ctx)
+ case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */
+ aresult = "fail";
+
+- if (conf->conf_rejectfail && random() % 100 < pct)
++ if (conf->conf_overridemlm != NULL &&
++ (dmarcf_checkhost(cc->cctx_host, conf->conf_overridemlm) ||
++ (dmarcf_checkip((struct sockaddr *)&cc->cctx_ip, conf->conf_overridemlm))))
+ {
+- snprintf(replybuf, sizeof replybuf,
+- "quarantined by DMARC policy for %s",
+- pdomain);
+-
+- status = smfi_quarantine(ctx, replybuf);
+- if (status != MI_SUCCESS && conf->conf_dolog)
++ if (conf->conf_dolog)
+ {
+- syslog(LOG_ERR, "%s: smfi_quarantine() failed",
+- dfc->mctx_jobid);
++ syslog(LOG_INFO, "%s: overriding policy for mail from %s: MLM",
++ dfc->mctx_jobid, dfc->mctx_fromdomain);
+ }
+-
+ ret = SMFIS_ACCEPT;
+- result = DMARC_RESULT_QUARANTINE;
++ result = DMARC_RESULT_OVRD_MAILING_LIST;
+ }
+-
+- if (conf->conf_copyfailsto != NULL)
++ else
+ {
+- status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto);
+- if (status != MI_SUCCESS && conf->conf_dolog)
++ if (conf->conf_rejectfail && random() % 100 < pct)
++ {
++ snprintf(replybuf, sizeof replybuf,
++ "quarantined by DMARC policy for %s",
++ pdomain);
++
++ status = smfi_quarantine(ctx, replybuf);
++ if (status != MI_SUCCESS && conf->conf_dolog)
++ {
++ syslog(LOG_ERR, "%s: smfi_quarantine() failed",
++ dfc->mctx_jobid);
++ }
++
++ ret = SMFIS_ACCEPT;
++ result = DMARC_RESULT_QUARANTINE;
++ }
++
++ if (conf->conf_copyfailsto != NULL)
+ {
+- syslog(LOG_ERR, "%s: smfi_addrcpt() failed",
+- dfc->mctx_jobid);
++ status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto);
++ if (status != MI_SUCCESS && conf->conf_dolog)
++ {
++ syslog(LOG_ERR, "%s: smfi_addrcpt() failed",
++ dfc->mctx_jobid);
++ }
+ }
+ }
+
+diff --git a/opendmarc/opendmarc.conf.5.in b/opendmarc/opendmarc.conf.5.in
+index bdf2550..9ee16ae 100644
+--- a/opendmarc/opendmarc.conf.5.in
++++ b/opendmarc/opendmarc.conf.5.in
+@@ -190,6 +190,14 @@ Sets the debug level to be requested from the milter library. The
+ default is 0.
+
+ .TP
++.I OverrideMLM (string)
++Specifies the path to a file that contains a list of hostnames, IP
++addresses, and/or CIDR expressions identifying hosts that run
++mailing lists. Mails from these systems will be accepted even if
++all DMARC tests fail. Such cases will be reported as "override/
++reason: MLM"
++
++.TP
+ .I PidFile (string)
+ Specifies the path to a file that should be created at process start
+ containing the process ID.
+diff --git a/opendmarc/opendmarc.conf.sample b/opendmarc/opendmarc.conf.sample
+index 97b210f..fbfa49d 100644
+--- a/opendmarc/opendmarc.conf.sample
++++ b/opendmarc/opendmarc.conf.sample
+@@ -212,6 +212,17 @@
+ #
+ # MilterDebug 0
+
++## OverrideMLM (path)
++## default (none)
++##
++## Specifies the path to a file that contains a list of hostnames, IP
++## addresses, and/or CIDR expressions identifying hosts that run
++## mailing lists. Mails from these systems will be accepted even if
++## all DMARC tests fail. Such cases will be reported as "override/
++## reason: MLM"
++#
++# OverrideMLM /usr/local/etc/opendmarc/overrideMLM.conf
++
+ ## PidFile path
+ ## default (none)
+ ##
+diff --git a/opendmarc/opendmarc.h b/opendmarc/opendmarc.h
+index c1d6593..f9b1e0b 100644
+--- a/opendmarc/opendmarc.h
++++ b/opendmarc/opendmarc.h
+@@ -52,6 +52,12 @@
+ #define DMARC_RESULT_ACCEPT 2
+ #define DMARC_RESULT_TEMPFAIL 3
+ #define DMARC_RESULT_QUARANTINE 4
++#define DMARC_RESULT_OVRD_FORWARDED 5
++#define DMARC_RESULT_OVRD_SAMPLED_OUT 6
++#define DMARC_RESULT_OVRD_TRUSTED_FORWARDER 7
++#define DMARC_RESULT_OVRD_MAILING_LIST 8
++#define DMARC_RESULT_OVRD_LOCAL_POLICY 9
++#define DMARC_RESULT_OVRD_OTHER 10
+
+ /* prototypes, etc., exported for test.c */
+ extern char *progname;
+diff --git a/reports/opendmarc-reports.in b/reports/opendmarc-reports.in
+index 2da1c31..a489c95 100755
+--- a/reports/opendmarc-reports.in
++++ b/reports/opendmarc-reports.in
+@@ -91,6 +91,8 @@ my $ipaddr;
+ my $fromdomain;
+ my $envdomain;
+ my $dkimdomain;
++my $reason;
++my $comment;
+
+ my $repdest;
+
+@@ -609,6 +611,8 @@ foreach (@$domainset)
+ while ($dbi_a = $dbi_s->fetchrow_arrayref())
+ {
+ undef $msgid;
++ undef $reason;
++ undef $comment;
+
+ if (defined($dbi_a->[0]))
+ {
+@@ -656,6 +660,12 @@ foreach (@$domainset)
+ case 1 { $dispstr = "reject"; }
+ case 2 { $dispstr = "none"; }
+ case 4 { $dispstr = "quarantine"; }
++ case 5 { $dispstr = "none"; $reason = "forwarded"; }
++ case 6 { $dispstr = "none"; $reason = "sampled_out"; }
++ case 7 { $dispstr = "none"; $reason = "trusted_forwarder"; }
++ case 8 { $dispstr = "none"; $reason = "mailing_list"; }
++ case 9 { $dispstr = "none"; $reason = "local_policy"; $comment = ""; }
++ case 10 { $dispstr = "none"; $reason = "other"; $comment = ""; }
+ else { $dispstr = "unknown"; }
+ }
+
+@@ -697,6 +707,16 @@ foreach (@$domainset)
+ print $tmpout " <disposition>$dispstr</disposition>\n";
+ print $tmpout " <dkim>$align_dkimstr</dkim>\n";
+ print $tmpout " <spf>$align_spfstr</spf>\n";
++ if (defined($reason))
++ {
++ print $tmpout " <reason>\n";
++ print $tmpout " <type>$reason</type>\n";
++ if (defined($comment))
++ {
++ print $tmpout " <comment>$comment</$comment>\n";
++ }
++ print $tmpout " </reason>\n";
++ }
+ print $tmpout " </policy_evaluated>\n";
+ print $tmpout " </row>\n";
+ print $tmpout " <identifiers>\n";
diff --git a/network/opendmarc/patches/ticket182.patch b/network/opendmarc/patches/ticket182.patch
new file mode 100644
index 0000000000..567115e344
--- /dev/null
+++ b/network/opendmarc/patches/ticket182.patch
@@ -0,0 +1,18 @@
+diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
+index 0b3457a..1c0e27a 100644
+--- a/opendmarc/opendmarc.c
++++ b/opendmarc/opendmarc.c
+@@ -1863,7 +1863,13 @@ mlfi_envfrom(SMFICTX *ctx, char **envfrom)
+
+ if (conf->conf_ignoreauthclients &&
+ dmarcf_getsymval(ctx, "{auth_authen}") != NULL)
++ {
++ if (curconf->conf_dolog)
++ {
++ syslog(LOG_INFO, "ignoring authenticated client, mailfrom=%s", envfrom[0]);
++ }
+ return SMFIS_ACCEPT;
++ }
+
+ dfc = (DMARCF_MSGCTX) malloc(sizeof(struct dmarcf_msgctx));
+ if (dfc == NULL)
diff --git a/network/opendmarc/patches/ticket183.patch b/network/opendmarc/patches/ticket183.patch
new file mode 100644
index 0000000000..f76ef697f7
--- /dev/null
+++ b/network/opendmarc/patches/ticket183.patch
@@ -0,0 +1,13 @@
+diff --git a/reports/opendmarc-reports.in b/reports/opendmarc-reports.in
+index 5dfa5ee..6826ff7 100755
+--- a/reports/opendmarc-reports.in
++++ b/reports/opendmarc-reports.in
+@@ -878,7 +878,7 @@ foreach (@$domainset)
+ $mailout .= "Content-Type: text/plain;\n";
+ $mailout .= "\n";
+ $mailout .= "This is a DMARC aggregate report for $domain\n";
+- $mailout .= "generated at " . localtime() . "\n";
++ $mailout .= "generated at " . strftime("%a, %b %e %Y %H:%M:%S %z (%Z)", localtime()) . "\n";
+ $mailout .= "\n";
+ $mailout .= "--$boundary\n";
+ $mailout .= "Content-Type: application/zip\n";
diff --git a/network/opendmarc/patches/ticket184.patch b/network/opendmarc/patches/ticket184.patch
new file mode 100644
index 0000000000..6cdd235dec
--- /dev/null
+++ b/network/opendmarc/patches/ticket184.patch
@@ -0,0 +1,17 @@
+diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
+index 2a60b92..07e21a4 100644
+--- a/opendmarc/opendmarc.c
++++ b/opendmarc/opendmarc.c
+@@ -2859,9 +2859,9 @@ mlfi_eom(SMFICTX *ctx)
+ dmarcf_dstring_printf(dfc->mctx_afrf,
+ "This is an authentication "
+ "failure report for an email "
+- "message received from IP\n"
+- "%s on %s.\n\n",
+- cc->cctx_ipstr, timebuf);
++ "message received from\n"
++ "%s [%s] on %s.\n\n",
++ cc->cctx_host, cc->cctx_ipstr, timebuf);
+
+ dmarcf_dstring_printf(dfc->mctx_afrf,
+ "--%s:%s\n"
diff --git a/network/opendmarc/patches/ticket193.patch b/network/opendmarc/patches/ticket193.patch
new file mode 100644
index 0000000000..1ee1911174
--- /dev/null
+++ b/network/opendmarc/patches/ticket193.patch
@@ -0,0 +1,193 @@
+Index: opendmarc/db/Makefile.am
+===================================================================
+--- opendmarc.orig/db/Makefile.am 2018-12-17 01:41:11.326335516 -0500
++++ opendmarc/db/Makefile.am 2018-12-17 01:41:11.318335516 -0500
+@@ -1,3 +1,3 @@
+ # Copyright (c) 2012, The Trusted Domain Project. All rights reserved.
+
+-dist_doc_DATA = README.schema schema.mysql
++dist_doc_DATA = README.schema schema.mysql README.update-db-schema.mysql update-db-schema.mysql
+Index: opendmarc/db/README.update-db-schema.mysql
+===================================================================
+--- /dev/null 1970-01-01 00:00:00.000000000 +0000
++++ opendmarc/db/README.update-db-schema.mysql 2018-12-17 01:41:11.318335516 -0500
+@@ -0,0 +1,8 @@
++
++To update your database to the current state use this script like this:
++
++ mysql -u <user> -p <passwd> --force < update-db-schema.mysql
++
++You might receive up to four errors about duplicate keys - this is expected if your database
++already has these keys (because you used the MySQL schema in the db sub-direcory instead of
++the obsolete schema in the reports sub-dirctory).
+Index: opendmarc/db/schema.mysql
+===================================================================
+--- opendmarc.orig/db/schema.mysql 2018-12-17 01:41:11.326335516 -0500
++++ opendmarc/db/schema.mysql 2018-12-17 01:41:11.318335516 -0500
+@@ -5,6 +5,7 @@
+
+ CREATE DATABASE IF NOT EXISTS opendmarc;
+ USE opendmarc;
++SET TIME_ZONE='+00:00';
+
+ -- A table for mapping domain names and their DMARC policies to IDs
+ CREATE TABLE IF NOT EXISTS domains (
+@@ -28,7 +29,7 @@
+ pct TINYINT NOT NULL,
+ locked TINYINT NOT NULL DEFAULT '0',
+ firstseen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+- lastsent TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
++ lastsent TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:01',
+
+ PRIMARY KEY(id),
+ KEY(lastsent),
+Index: opendmarc/db/update-db-schema.mysql
+===================================================================
+--- /dev/null 1970-01-01 00:00:00.000000000 +0000
++++ opendmarc/db/update-db-schema.mysql 2018-12-17 01:41:11.318335516 -0500
+@@ -0,0 +1,12 @@
++use opendmarc;
++SET TIME_ZONE="+00:00";
++ALTER TABLE ipaddr MODIFY COLUMN addr VARCHAR(64) NOT NULL;
++DELETE FROM ipaddr WHERE addr = NULL;
++ALTER TABLE messages MODIFY COLUMN spf TINYINT NOT NULL;
++ALTER TABLE requests ALTER COLUMN locked SET DEFAULT '0';
++ALTER TABLE requests ALTER COLUMN lastsent SET DEFAULT '1970-01-01 00:00:01';
++ALTER TABLE requests ADD UNIQUE KEY domain (domain);
++ALTER TABLE requests ADD KEY lastsent (lastsent);
++ALTER TABLE messages ADD KEY date (date);
++ALTER TABLE signatures ADD KEY message (message);
++
+Index: opendmarc/reports/opendmarc-expire.in
+===================================================================
+--- opendmarc.orig/reports/opendmarc-expire.in 2018-12-17 01:41:11.326335516 -0500
++++ opendmarc/reports/opendmarc-expire.in 2018-12-17 01:41:11.318335516 -0500
+@@ -210,6 +210,17 @@
+ print STDERR "$progname: connected to database\n";
+ }
+
++# switch to UTC to have a defined date behaviour
++$dbi_s = $dbi_h->prepare("SET TIME_ZONE='+00:00'");
++
++if (!$dbi_s->execute())
++{
++ print STDERR "$progname: failed to change to UTC: " . $dbi_h->errstr . "\n";
++ $dbi_s->finish;
++ $dbi_h->disconnect;
++ exit(1);
++}
++
+ #
+ # Expire messages
+ #
+@@ -340,7 +351,7 @@
+ print STDERR "$progname: expiring request data older than $maxage days\n";
+ }
+
+-$dbi_s = $dbi_h->prepare("DELETE FROM requests WHERE lastsent <= DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL ? DAY) AND NOT lastsent = '0000-00-00 00:00:00'");
++$dbi_s = $dbi_h->prepare("DELETE FROM requests WHERE lastsent <= DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL ? DAY) AND NOT lastsent <= '1970-01-01 00:00:01'");
+ $rows = $dbi_s->execute($maxage);
+ if (!$rows)
+ {
+Index: opendmarc/reports/opendmarc-import.in
+===================================================================
+--- opendmarc.orig/reports/opendmarc-import.in 2018-12-17 01:41:11.326335516 -0500
++++ opendmarc/reports/opendmarc-import.in 2018-12-17 01:41:11.322335516 -0500
+@@ -207,20 +207,18 @@
+ $envfrom_id = get_table_id($envdomain, "domains");
+ $pdomain_id = get_table_id($pdomain, "domains");
+ $ipaddr_id = get_table_id($ipaddr, "ipaddr", "addr");
+- $request_id = get_table_id($from_id, "requests", "domain");
+
+ if (!defined($rep_id) ||
+ !defined($from_id) ||
+ !defined($envfrom_id) ||
+ !defined($pdomain_id) ||
+- !defined($ipaddr_id) ||
+- !defined($request_id))
++ !defined($ipaddr_id))
+ {
+ return;
+ }
+
+- $dbi_s = $dbi_h->prepare("INSERT INTO messages (date, jobid, reporter, policy, disp, ip, env_domain, from_domain, spf, align_spf, align_dkim, sigcount) VALUES(FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+- if (!$dbi_s->execute($received, $jobid, $rep_id, $policy, $action, $ipaddr_id, $envfrom_id, $from_id, $spf, $align_spf, $align_dkim, $sigcount))
++ $dbi_s = $dbi_h->prepare("INSERT INTO messages (date, jobid, reporter, policy, disp, ip, env_domain, from_domain, policy_domain, spf, align_spf, align_dkim, sigcount) VALUES(FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
++ if (!$dbi_s->execute($received, $jobid, $rep_id, $policy, $action, $ipaddr_id, $envfrom_id, $from_id, $pdomain_id, $spf, $align_spf, $align_dkim, $sigcount))
+ {
+ print STDERR "$progname: failed to insert message: " . $dbi_h->errstr . "\n";
+ return;
+@@ -278,41 +276,51 @@
+ }
+ $dbi_s->finish;
+
+- if (get_value("requests", "locked", $request_id) != 1)
++ $dbi_t = $dbi_h->prepare("SELECT id FROM requests WHERE domain = ?");
++ if (!$dbi_t->execute($from_id))
+ {
+- if (scalar @rua > 0)
++ print STDERR "$progname: failed to retrieve table ID: " . $dbi_h->errstr . "\n";
++ return undef;
++ }
++
++ undef $request_id;
++ while ($dbi_a = $dbi_t->fetchrow_arrayref())
++ {
++ if (defined($dbi_a->[0]))
+ {
+- $repuri = join(",", @rua);
+- $dbi_s = $dbi_h->prepare("UPDATE requests SET repuri = ? WHERE id = ?");
++ $request_id = $dbi_a->[0];
++ }
++ }
+
+- if (!$dbi_s->execute($repuri, $request_id))
+- {
+- print STDERR "$progname: failed to update reporting URI for $fdomain: " . $dbi_h->errstr . "\n";
+- $dbi_s->finish;
+- return;
+- }
++ $dbi_t->finish;
+
+- $dbi_s->finish;
+- }
+- else
++ $repuri = join(",", @rua);
++
++ if (defined($request_id))
++ {
++ if (get_value("requests", "locked", $request_id) != 1)
+ {
+- $dbi_s = $dbi_h->prepare("UPDATE requests SET repuri = NULL WHERE id = ?");
++ $dbi_s = $dbi_h->prepare("UPDATE requests SET domain = ?, repuri = ?, adkim = ?, aspf = ?, policy = ?, spolicy = ?, pct = ? WHERE id = ?");
+
+- if (!$dbi_s->execute($request_id))
++ if (!$dbi_s->execute($from_id, $repuri, $adkim, $aspf, $p, $sp, $pct, $request_id))
+ {
+- print STDERR "$progname: failed to update reporting URI for $fdomain: " . $dbi_h->errstr . "\n";
++ print STDERR "$progname: failed to update policy data for $fdomain: " . $dbi_h->errstr . "\n";
+ $dbi_s->finish;
+ return;
+ }
+-
+- $dbi_s->finish;
+ }
++ else
++ {
++ print STDERR "$progname: policy data for $fdomain not updated, because they are locked\n";
++ }
++ }
++ else
++ {
++ $dbi_s = $dbi_h->prepare("insert requests SET domain = ?, repuri = ?, adkim = ?, aspf = ?, policy = ?, spolicy = ?, pct = ?");
+
+- $dbi_s = $dbi_h->prepare("UPDATE requests SET adkim = ?, aspf = ?, policy = ?, spolicy = ?, pct = ? WHERE id = ?");
+-
+- if (!$dbi_s->execute($adkim, $aspf, $p, $sp, $pct, $request_id))
++ if (!$dbi_s->execute($from_id, $repuri, $adkim, $aspf, $p, $sp, $pct))
+ {
+- print STDERR "$progname: failed to update policy data for $fdomain: " . $dbi_h->errstr . "\n";
++ print STDERR "$progname: failed to insert policy data for $fdomain: " . $dbi_h->errstr . "\n";
+ $dbi_s->finish;
+ return;
+ }
diff --git a/network/opendmarc/patches/ticket203.patch b/network/opendmarc/patches/ticket203.patch
new file mode 100644
index 0000000000..8aa5113176
--- /dev/null
+++ b/network/opendmarc/patches/ticket203.patch
@@ -0,0 +1,26 @@
+diff --git a/libopendmarc/opendmarc_policy.c b/libopendmarc/opendmarc_policy.c
+index 862c449..8048ec3 100644
+--- a/libopendmarc/opendmarc_policy.c
++++ b/libopendmarc/opendmarc_policy.c
+@@ -1087,6 +1087,10 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor
+ /*
+ * A possibly comma delimited list of URI of where to send reports.
+ */
++
++ if (pctx->rua_list != NULL)
++ return DMARC_PARSE_ERROR_BAD_VALUE;
++
+ for (xp = vp; *xp != '\0'; )
+ {
+ u_char xbuf[256];
+@@ -1115,6 +1119,10 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor
+ * A possibly comma delimited list of URI of where to send
+ * MARF reports.
+ */
++
++ if (pctx->ruf_list != NULL)
++ return DMARC_PARSE_ERROR_BAD_VALUE;
++
+ for (xp = vp; *xp != '\0'; )
+ {
+ u_char xbuf[256];
diff --git a/network/opendmarc/patches/ticket204.patch b/network/opendmarc/patches/ticket204.patch
new file mode 100644
index 0000000000..afbb45ad13
--- /dev/null
+++ b/network/opendmarc/patches/ticket204.patch
@@ -0,0 +1,31 @@
+Index: opendmarc/reports/opendmarc-import.in
+===================================================================
+--- opendmarc.orig/reports/opendmarc-import.in 2018-12-17 01:41:32.570336411 -0500
++++ opendmarc/reports/opendmarc-import.in 2018-12-17 01:41:32.566336411 -0500
+@@ -529,7 +529,7 @@
+ }
+
+ case "from" {
+- $fdomain = $value;
++ $fdomain = lc($value);
+ }
+
+ case "job" {
+@@ -567,7 +567,7 @@
+ }
+
+ case "mfrom" {
+- $envdomain = $value;
++ $envdomain = lc($value);
+ }
+
+ case "p" {
+@@ -579,7 +579,7 @@
+ }
+
+ case "pdomain" {
+- $pdomain = $value;
++ $pdomain = lc($value);
+ }
+
+ case "policy" {
diff --git a/network/opendmarc/patches/ticket205.patch b/network/opendmarc/patches/ticket205.patch
new file mode 100644
index 0000000000..11089408dc
--- /dev/null
+++ b/network/opendmarc/patches/ticket205.patch
@@ -0,0 +1,38 @@
+--- opendmarc-1.3.2/reports/opendmarc-reports.in 2017-02-16 16:15:01.000000000 +0100
++++ opendmarc-1.3.2_fix/reports/opendmarc-reports.in 2017-02-26 10:43:46.697335371 +0100
+@@ -157,6 +157,7 @@
+ print STDERR "\t--keepfiles keep xml files (in local directory)\n";
+ print STDERR "\t -n synonym for --test\n";
+ print STDERR "\t--nodomain=name omit a report for named domain\n";
++ print STDERR "\t--skipdomains=file list of domains to omit a report for\n";
+ print STDERR "\t--noupdate don't record report transmission\n";
+ print STDERR "\t--report-email reporting contact [$repemail]\n";
+ print STDERR "\t--report-org reporting organization [$repdom]\n";
+@@ -173,6 +174,19 @@
+ # set locale
+ setlocale(LC_ALL, 'C');
+
++sub loadskipdomains
++{
++ die "Could not open domains file $_[1]" unless open FILE,"<",$_[1];
++ while (my $line = <FILE>)
++ {
++ $line =~ s/\s*#.*//;
++ $line =~ s/^\s+//;
++ $line =~ s/\s+//;
++ push(@skipdomains, $line);
++ }
++ close FILE;
++}
++
+ # parse command line arguments
+ my $opt_retval = &Getopt::Long::GetOptions ('day!' => \$daybound,
+ 'dbhost=s' => \$dbhost,
+@@ -186,6 +200,7 @@
+ 'keepfiles' => \$keepfiles,
+ 'n|test' => \$testmode,
+ 'nodomain=s' => \@skipdomains,
++ 'skipdomains=s' => \&loadskipdomains,
+ 'report-email=s' => \$repemail,
+ 'report-org=s' => \$repdom,
+ 'smtp-server=s' => \$smtp_server,
diff --git a/network/opendmarc/patches/ticket207.patch b/network/opendmarc/patches/ticket207.patch
new file mode 100644
index 0000000000..e86b8c7704
--- /dev/null
+++ b/network/opendmarc/patches/ticket207.patch
@@ -0,0 +1,49 @@
+Index: opendmarc/reports/opendmarc-reports.in
+===================================================================
+--- opendmarc.orig/reports/opendmarc-reports.in 2018-12-17 01:41:49.094337107 -0500
++++ opendmarc/reports/opendmarc-reports.in 2018-12-17 01:41:49.090337107 -0500
+@@ -65,6 +65,7 @@
+ my $forcedomain;
+ my @skipdomains;
+
++my $poldomain;
+ my $policy;
+ my $spolicy;
+ my $policystr;
+@@ -441,7 +442,7 @@
+ next;
+ }
+
+- $dbi_s = $dbi_h->prepare("SELECT repuri, adkim, aspf, policy, spolicy, pct, UNIX_TIMESTAMP(lastsent) FROM requests WHERE domain = ?");
++ $dbi_s = $dbi_h->prepare("SELECT repuri, adkim, aspf, requests.policy, spolicy, pct, UNIX_TIMESTAMP(lastsent), domains.name FROM requests JOIN messages ON messages.from_domain=requests.domain LEFT JOIN domains ON messages.policy_domain = domains.id WHERE domain = ? GROUP BY policy_domain");
+ if (!$dbi_s->execute($domainid))
+ {
+ print STDERR "$progname: can't get reporting URI for domain $domain: " . $dbi_h->errstr . "\n";
+@@ -451,6 +452,7 @@
+ }
+
+ undef $repuri;
++ $poldomain=$domain;
+
+ while ($dbi_a = $dbi_s->fetchrow_arrayref())
+ {
+@@ -482,6 +484,10 @@
+ {
+ $lastsent = $dbi_a->[6];
+ }
++ if (defined($dbi_a->[7]))
++ {
++ $poldomain = $dbi_a->[7];
++ }
+ }
+
+ $dbi_s->finish;
+@@ -564,7 +570,7 @@
+ print $tmpout " </report_metadata>\n";
+
+ print $tmpout " <policy_published>\n";
+- print $tmpout " <domain>$domain</domain>\n";
++ print $tmpout " <domain>$poldomain</domain>\n";
+ print $tmpout " <adkim>$adkimstr</adkim>\n";
+ print $tmpout " <aspf>$aspfstr</aspf>\n";
+ print $tmpout " <p>$policystr</p>\n";
diff --git a/network/opendmarc/patches/ticket208.patch b/network/opendmarc/patches/ticket208.patch
new file mode 100644
index 0000000000..fc8c6455d8
--- /dev/null
+++ b/network/opendmarc/patches/ticket208.patch
@@ -0,0 +1,116 @@
+diff -ur opendmarc-1.3.2/opendmarc/opendmarc.c opendmarc-1.3.2_fix/opendmarc/opendmarc.c
+--- opendmarc-1.3.2/opendmarc/opendmarc.c 2017-03-04 14:28:39.000000000 +0100
++++ opendmarc-1.3.2_fix/opendmarc/opendmarc.c 2017-03-27 18:11:14.977304726 +0200
+@@ -168,7 +168,8 @@
+ char * conf_ignorelist;
+ char ** conf_trustedauthservids;
+ char ** conf_ignoredomains;
+ struct list * conf_overridemlm;
++ char ** conf_ignorereceivers;
+ };
+
+ /* LIST -- basic linked list of strings */
+@@ -1226,6 +1227,11 @@
+ if (str != NULL)
+ dmarcf_mkarray(str, &conf->conf_ignoredomains);
+
++ str = NULL;
++ (void) config_get(data, "IgnoreMailTo", &str, sizeof str);
++ if (str != NULL)
++ dmarcf_mkarray(str, &conf->conf_ignorereceivers);
++
+ (void) config_get(data, "AuthservIDWithJobID",
+ &conf->conf_authservidwithjobid,
+ sizeof conf->conf_authservidwithjobid);
+@@ -2015,6 +2021,7 @@
+ mlfi_eom(SMFICTX *ctx)
+ {
+ _Bool wspf = FALSE;
++ int skiphistory;
+ int c;
+ int pc;
+ int policy;
+@@ -3147,7 +3154,34 @@
+ ** Record activity in the history file.
+ */
+
+- if (conf->conf_historyfile != NULL &&
++ skiphistory = 0;
++ if (conf->conf_ignorereceivers != NULL)
++ {
++ struct dmarcf_header *to = dmarcf_findheader(dfc, "To", 0);
++ if (to != NULL)
++ {
++ char *val = to->hdr_value;
++ while (*val && !skiphistory)
++ {
++ memset(addrbuf, '\0', sizeof addrbuf);
++ strncpy(addrbuf, val, sizeof addrbuf - 1);
++ status = dmarcf_mail_parse(addrbuf, &user, &domain);
++ if (status == 0 && user != NULL && domain != NULL)
++ {
++ snprintf(replybuf, sizeof replybuf - 1, "%s@%s", user, domain);
++ if(dmarcf_match(replybuf, conf->conf_ignorereceivers, TRUE))
++ {
++ skiphistory = 1;
++ }
++ }
++ while(*val && *val != ',' && *val != ';')
++ ++val;
++ if(*val)
++ ++val;
++ }
++ }
++ }
++ if (!skiphistory && conf->conf_historyfile != NULL &&
+ (conf->conf_recordall || ostatus != DMARC_DNS_ERROR_NO_RECORD))
+ {
+ FILE *f;
+diff -ur opendmarc-1.3.2/opendmarc/opendmarc.conf.5.in opendmarc-1.3.2_fix/opendmarc/opendmarc.conf.5.in
+--- opendmarc-1.3.2/opendmarc/opendmarc.conf.5.in 2016-12-18 08:50:34.000000000 +0100
++++ opendmarc-1.3.2_fix/opendmarc/opendmarc.conf.5.in 2017-03-27 17:00:14.424955664 +0200
+@@ -185,6 +185,13 @@
+ no mail is ignored.
+
+ .TP
++.I IgnoreMailTo (string)
++Gives a list of mail addresses which aren't entered into the history file.
++This is useful to prevent exchanging single message reports. The
++list should be comma-separated. Matching against this list is
++case-insensitive. The default is an empty list, meaning no mail is ignored.
++
++.TP
+ .I MilterDebug (integer)
+ Sets the debug level to be requested from the milter library. The
+ default is 0.
+diff -ur opendmarc-1.3.2/opendmarc/opendmarc-config.h opendmarc-1.3.2_fix/opendmarc/opendmarc-config.h
+--- opendmarc-1.3.2/opendmarc/opendmarc-config.h 2016-12-18 08:50:34.000000000 +0100
++++ opendmarc-1.3.2_fix/opendmarc/opendmarc-config.h 2017-03-27 17:39:01.727649907 +0200
+@@ -35,6 +35,7 @@
+ { "IgnoreAuthenticatedClients", CONFIG_TYPE_BOOLEAN, FALSE },
+ { "IgnoreHosts", CONFIG_TYPE_STRING, FALSE },
+ { "IgnoreMailFrom", CONFIG_TYPE_STRING, FALSE },
++ { "IgnoreMailTo", CONFIG_TYPE_STRING, FALSE },
+ { "MilterDebug", CONFIG_TYPE_INTEGER, FALSE },
+ { "PidFile", CONFIG_TYPE_STRING, FALSE },
+ { "PublicSuffixList", CONFIG_TYPE_STRING, FALSE },
+diff -ur opendmarc-1.3.2/opendmarc/opendmarc.conf.sample opendmarc-1.3.2_fix/opendmarc/opendmarc.conf.sample
+--- opendmarc-1.3.2/opendmarc/opendmarc.conf.sample 2017-03-04 14:28:39.000000000 +0100
++++ opendmarc-1.3.2_fix/opendmarc/opendmarc.conf.sample 2017-03-27 17:39:32.594647158 +0200
+@@ -205,6 +205,16 @@
+ #
+ # IgnoreMailFrom example.com
+
++## IgnoreMailTo email[,...]
++## default (none)
++##
++## Gives a list of mail addresses which aren't entered into the history file.
++## This is useful to prevent exchanging mutual message reports. The
++## list should be comma-separated. Matching against this list is
++## case-insensitive. The default is an empty list, meaning no mail is ignored.
++#
++# IgnoreMailTo dmarc-ruf@example.com
++
+ ## MilterDebug (integer)
+ ## default 0
+ ##
diff --git a/network/opendmarc/patches/ticket212.patch b/network/opendmarc/patches/ticket212.patch
new file mode 100644
index 0000000000..cf8c01c731
--- /dev/null
+++ b/network/opendmarc/patches/ticket212.patch
@@ -0,0 +1,18 @@
+diff --git a/libopendmarc/opendmarc_tld.c b/libopendmarc/opendmarc_tld.c
+index 8ac45e8..0c04370 100644
+--- a/libopendmarc/opendmarc_tld.c
++++ b/libopendmarc/opendmarc_tld.c
+@@ -134,8 +134,11 @@
+ return (errno == 0) ? ENOMEM : errno;
+
+ fp = fopen(path_fname, "r");
+- if (fp == NULL)
+- return errno;
++ if (fp == NULL) {
++ ret = errno;
++ opendmarc_hash_shutdown(hashp);
++ return ret;
++ }
+
+ errno = 0;
+ while (fgets((char *)buf, sizeof buf, fp) != NULL)
diff --git a/network/opendmarc/patches/ticket227.patch b/network/opendmarc/patches/ticket227.patch
new file mode 100644
index 0000000000..b2786aef61
--- /dev/null
+++ b/network/opendmarc/patches/ticket227.patch
@@ -0,0 +1,40 @@
+diff --git a/libopendmarc/opendmarc_policy.c b/libopendmarc/opendmarc_policy.c
+index 36412e4..f151fda 100644
+--- a/libopendmarc/opendmarc_policy.c
++++ b/libopendmarc/opendmarc_policy.c
+@@ -1058,7 +1058,7 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor
+ *yp = '\0';
+
+ xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
+- if (xp != NULL || strlen((char *)xp) > 0)
++ if (xp != NULL && strlen((char *)xp) > 0)
+ {
+ /*
+ * Be generous. Accept, for example, "rf=a, aspf=afrf or any
+@@ -1100,7 +1100,7 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor
+ *yp = '\0';
+
+ xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
+- if (xp != NULL || strlen((char *)xp) > 0)
++ if (xp != NULL && strlen((char *)xp) > 0)
+ {
+ pctx->rua_list = opendmarc_util_pushargv(xp, pctx->rua_list,
+ &(pctx->rua_cnt));
+@@ -1132,7 +1132,7 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor
+ *yp = '\0';
+
+ xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
+- if (xp != NULL || strlen((char *)xp) > 0)
++ if (xp != NULL && strlen((char *)xp) > 0)
+ {
+ pctx->ruf_list = opendmarc_util_pushargv(xp, pctx->ruf_list,
+ &(pctx->ruf_cnt));
+@@ -1159,7 +1159,7 @@ opendmarc_policy_parse_dmarc(DMARC_POLICY_T *pctx, u_char *domain, u_char *recor
+ *yp = '\0';
+
+ xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
+- if (xp != NULL || strlen((char *)xp) > 0)
++ if (xp != NULL && strlen((char *)xp) > 0)
+ {
+ switch ((int)*xp)
+ {
diff --git a/network/opendmarc/patches/z00_ticket138_v3.patch b/network/opendmarc/patches/z00_ticket138_v3.patch
new file mode 100644
index 0000000000..4bcd4f92b4
--- /dev/null
+++ b/network/opendmarc/patches/z00_ticket138_v3.patch
@@ -0,0 +1,83 @@
+diff --git a/opendmarc/opendmarc-config.h b/opendmarc/opendmarc-config.h
+index 28f605e..ff4983d 100644
+--- a/opendmarc/opendmarc-config.h
++++ b/opendmarc/opendmarc-config.h
+@@ -32,6 +32,7 @@ struct configdef dmarcf_config[] =
+ { "FailureReportsOnNone", CONFIG_TYPE_BOOLEAN, FALSE },
+ { "FailureReportsSentBy", CONFIG_TYPE_STRING, FALSE },
+ { "HistoryFile", CONFIG_TYPE_STRING, FALSE },
++ { "HoldQuarantinedMessages", CONFIG_TYPE_BOOLEAN, FALSE },
+ { "IgnoreAuthenticatedClients", CONFIG_TYPE_BOOLEAN, FALSE },
+ { "IgnoreHosts", CONFIG_TYPE_STRING, FALSE },
+ { "IgnoreMailFrom", CONFIG_TYPE_STRING, FALSE },
+diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
+index 0179f4d..5aade55 100644
+--- a/opendmarc/opendmarc.c
++++ b/opendmarc/opendmarc.c
+@@ -155,6 +155,7 @@ struct dmarcf_config
+ _Bool conf_spfselfvalidate;
+ #endif /* WITH_SPF */
+ _Bool conf_ignoreauthclients;
++ _Bool conf_holdquarantinedmessages;
+ unsigned int conf_refcnt;
+ unsigned int conf_dnstimeout;
+ struct config * conf_data;
+@@ -1297,6 +1298,10 @@ dmarcf_config_load(struct config *data, struct dmarcf_config *conf,
+ &conf->conf_recordall,
+ sizeof conf->conf_recordall);
+
++ (void) config_get(data, "HoldQuarantinedMessages",
++ &conf->conf_holdquarantinedmessages,
++ sizeof conf->conf_holdquarantinedmessages);
++
+ (void) config_get(data, "IgnoreAuthenticatedClients",
+ &conf->conf_ignoreauthclients,
+ sizeof conf->conf_ignoreauthclients);
+@@ -3064,7 +3069,8 @@ mlfi_eom(SMFICTX *ctx)
+ }
+ else
+ {
+- if (conf->conf_rejectfail && random() % 100 < pct)
++ if (conf->conf_rejectfail && random() % 100 < pct &&
++ conf->conf_holdquarantinedmessages)
+ {
+ snprintf(replybuf, sizeof replybuf,
+ "quarantined by DMARC policy for %s",
+diff --git a/opendmarc/opendmarc.conf.5.in b/opendmarc/opendmarc.conf.5.in
+index 9ee16ae..565e992 100644
+--- a/opendmarc/opendmarc.conf.5.in
++++ b/opendmarc/opendmarc.conf.5.in
+@@ -167,6 +167,13 @@ rather periodically imported into a relational database from which the
+ aggregate reports can be extracted.
+
+ .TP
++.I HoldQuarantinedMessages (Boolean)
++If set to true, causes mail that fails the DMARC tests to get hold
++by the MTA if the purported sender of the message has a policy of
++"quarantine". Does nothing if the policy is either "none" or "reject".
++The default is "true".
++
++.TP
+ .I IgnoreAuthenticatedClients (Boolean)
+ If set, causes mail from authenticated clients (i.e., those that used
+ SMTP AUTH) to be ignored by the filter. The default is "false".
+diff --git a/opendmarc/opendmarc.conf.sample b/opendmarc/opendmarc.conf.sample
+index fbfa49d..a2e1da3 100644
+--- a/opendmarc/opendmarc.conf.sample
++++ b/opendmarc/opendmarc.conf.sample
+@@ -177,6 +177,15 @@
+ #
+ # HistoryFile /var/run/opendmarc.dat
+
++## HoldQuarantinedMessages { true | false }
++## default "true"
++##
++## If set to true, causes mail that fails the DMARC tests to get hold
++## by the MTA if the purported sender of the message has a policy of
++## "quarantine". Does nothing if the policy is either "none" or "reject".
++#
++# HoldQuarantinedMessages true
++
+ ## IgnoreAuthenticatedClients { true | false }
+ ## default "false"
+ ##
diff --git a/network/opendmarc/patches/z01_changeSubjectFailureReport_v2.patch b/network/opendmarc/patches/z01_changeSubjectFailureReport_v2.patch
new file mode 100644
index 0000000000..918283791b
--- /dev/null
+++ b/network/opendmarc/patches/z01_changeSubjectFailureReport_v2.patch
@@ -0,0 +1,27 @@
+diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
+index bdad10d..d29036f 100644
+--- a/opendmarc/opendmarc.c
++++ b/opendmarc/opendmarc.c
+@@ -3005,19 +3005,9 @@ mlfi_eom(SMFICTX *ctx)
+ dmarcf_dstring_printf(dfc->mctx_afrf, "Date: %s\n",
+ timebuf);
+
+- h = dmarcf_findheader(dfc, "subject", 0);
+- if (h == NULL)
+- {
+- dmarcf_dstring_printf(dfc->mctx_afrf,
+- "Subject: DMARC failure report for job %s\n",
+- dfc->mctx_jobid);
+- }
+- else
+- {
+- dmarcf_dstring_printf(dfc->mctx_afrf,
+- "Subject: FW: %s\n",
+- h->hdr_value);
+- }
++ dmarcf_dstring_printf(dfc->mctx_afrf,
++ "Subject: DMARC failure report for %s received from %s\n",
++ dfc->mctx_fromdomain, cc->cctx_host);
+
+ dmarcf_dstring_cat(dfc->mctx_afrf,
+ "MIME-Version: 1.0\n");
diff --git a/network/opendmarc/patches/z02_content-description.patch b/network/opendmarc/patches/z02_content-description.patch
new file mode 100644
index 0000000000..838def9c7b
--- /dev/null
+++ b/network/opendmarc/patches/z02_content-description.patch
@@ -0,0 +1,28 @@
+diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
+index 0645bcf..2a60b92 100644
+--- a/opendmarc/opendmarc.c
++++ b/opendmarc/opendmarc.c
+@@ -2852,6 +2852,7 @@ mlfi_eom(SMFICTX *ctx)
+
+ dmarcf_dstring_printf(dfc->mctx_afrf,
+ "--%s:%s\n"
++ "Content-Description: Notification\n"
+ "Content-Type: text/plain\n\n",
+ hostname, dfc->mctx_jobid);
+
+@@ -2864,6 +2865,7 @@ mlfi_eom(SMFICTX *ctx)
+
+ dmarcf_dstring_printf(dfc->mctx_afrf,
+ "--%s:%s\n"
++ "Content-Description: DMARC failure report\n"
+ "Content-Type: message/feedback-report\n\n",
+ hostname, dfc->mctx_jobid);
+
+@@ -2902,6 +2904,7 @@ mlfi_eom(SMFICTX *ctx)
+
+ dmarcf_dstring_printf(dfc->mctx_afrf,
+ "--%s:%s\n"
++ "Content-Description: Failed message headers\n"
+ "Content-Type: text/rfc822-headers\n\n",
+ hostname, dfc->mctx_jobid);
+
diff --git a/network/opendmarc/patches/z03_reportDestVerificationV2.patch b/network/opendmarc/patches/z03_reportDestVerificationV2.patch
new file mode 100644
index 0000000000..6e428b76ce
--- /dev/null
+++ b/network/opendmarc/patches/z03_reportDestVerificationV2.patch
@@ -0,0 +1,487 @@
+diff --git b/reports/opendmarc-reports.in a/reports/opendmarc-reports.in
+index 43be1ff..fff9f8d 100755
+--- b/reports/opendmarc-reports.in
++++ a/reports/opendmarc-reports.in
+@@ -24,6 +24,8 @@ use POSIX;
+ use MIME::Base64;
+ use Net::SMTP;
+ use Time::Local;
++use Net::DNS;
++use Domain::PublicSuffix;
+
+ require DBD::@SQL_BACKEND@;
+
+@@ -39,7 +41,6 @@ my $showversion = 0;
+ my $interval;
+
+ my $gen;
+-my $uri;
+
+ my $buf;
+
+@@ -95,8 +96,6 @@ my $dkimdomain;
+ my $reason;
+ my $comment;
+
+-my $repdest;
+-
+ my $smtpstatus;
+ my $smtpfail;
+
+@@ -140,6 +139,18 @@ my $smtp;
+
+ my $answer;
+
++my $suffix;
++my $publicsuffixlist = "/etc/opendmarc/public_suffix_list.dat";
++if (-r $publicsuffixlist) {
++ $suffix = Domain::PublicSuffix->new(
++ { 'data_file' => $publicsuffixlist }
++ );
++}
++else
++{
++ $suffix = Domain::PublicSuffix->new();
++}
++
+ ###
+ ### NO user-serviceable parts beyond this point
+ ###
+@@ -172,6 +183,71 @@ sub usage
+ print STDERR "\t--version print version and exit\n";
+ }
+
++sub check_size_restriction
++{
++ my ($destination, $size) = @_;
++ my $report_maxbytes = $report_maxbytes_global;
++
++ # check for max report size
++ if ($destination =~ m/^(\S+)!(\d{1,15})([kmgt])?$/i)
++ {
++ $destination = $1;
++ $report_maxbytes = $2;
++ if ($3)
++ {
++ my $letter = lc($3);
++ if ($letter eq 'k')
++ {
++ $report_maxbytes = $report_maxbytes * 1024;
++ }
++ if ($letter eq 'm')
++ {
++ $report_maxbytes = $report_maxbytes * 1048576;
++ }
++ if ($letter eq 'g')
++ {
++ $report_maxbytes = $report_maxbytes * (2**30);
++ }
++ if ($letter eq 't')
++ {
++ $report_maxbytes = $report_maxbytes * (2**40);
++ }
++ }
++
++ if ($size > $report_maxbytes)
++ {
++ return 0;
++ }
++ }
++ return 1;
++}
++
++sub check_uri
++{
++ my $uri = URI->new($_[0]);
++ if (!defined($uri) ||
++ !defined($uri->scheme) ||
++ $uri->opaque eq "")
++ {
++ print STDERR "$progname: can't parse reporting URI for domain $domain\n";
++ return "";
++ }
++ # ensure a scheme is present
++ elsif (!defined($uri->scheme))
++ {
++ if ($verbose >= 2)
++ {
++ print STDERR "$progname: unknown URI scheme in '$repuri' for domain $domain\n";
++ }
++ return "";
++ }
++ elsif ($uri->scheme eq "mailto")
++ {
++ return $uri->opaque;
++ }
++ return "";
++}
++
+ # set locale
+ setlocale(LC_ALL, 'C');
+
+@@ -798,86 +874,181 @@ foreach (@$domainset)
+ print STDERR "$progname: keeping report file \"$repfile\"\n";
+ }
+
++ if (!open($zipin, $zipfile))
++ {
++ print STDERR "$progname: can't read zipped report for $domain: $!\n";
++ next;
++ }
++ my $encoded_report;
++ while (read($zipin, $buf, 60*57))
++ {
++ $encoded_report .= encode_base64($buf);
++ }
++ close($zipin);
++ my $reportsize = length($encoded_report);
++
++ my $repdest = "";
++ my $repdest_fallback = "";
++
+ # decode the URI
+ @repuris = split(',', $repuri);
+
+ for $repuri (@repuris)
+ {
+- $uri = URI->new($repuri);
+- if (!defined($uri) ||
+- !defined($uri->scheme) ||
+- $uri->opaque eq "")
++ my $raw_address = check_uri($repuri);
++ if ($raw_address eq "")
+ {
+- print STDERR "$progname: can't parse reporting URI for domain $domain\n";
+ next;
+ }
+-
+- $repdest = $uri->opaque;
+- my $report_maxbytes = $report_maxbytes_global;
+-
+- # check for max report size
+- if ($repdest =~ m/^(\S+)!(\d{1,15})([kmgt])?$/i)
++ else
+ {
+- $repdest = $1;
+- $report_maxbytes = $2;
+- if ($3)
++ my $domain_orgdom = $suffix->get_root_domain(lc($domain));
++ my $address = $raw_address;
++ $address =~ s/!\d{1,15}([kmgt])?$//i;
++ my $repdestdomain = $address;
++ $repdestdomain =~ s/.*@//;
++ my $repdest_orgdom = $suffix->get_root_domain(lc($repdestdomain));
++
++ if (defined($domain_orgdom) && defined($repdest_orgdom) && $domain_orgdom eq $repdest_orgdom)
++ {
++ if (check_size_restriction($raw_address, $reportsize))
++ {
++ $repdest .= $address . ", ";
++ }
++ else
++ {
++ $repdest_fallback .= $address . ", ";
++ }
++ }
++ else
+ {
+- my $letter = lc($3);
+- if ($letter eq 'k')
++ # validate external report destinations:
++ my $replaced = 0; # external address replaced
++ my $authorized = 0; # external address authorized
++ my $temprepuri;
++ my $res = Net::DNS::Resolver->new(udp_timeout => 15);
++ my $reply = $res->query("$domain._report._dmarc.$repdestdomain", "TXT");
++ if ($reply)
+ {
+- $report_maxbytes = $report_maxbytes * 1024;
++ foreach my $txt ($reply->answer)
++ {
++ next unless $txt->type eq "TXT";
++ my @parts = split(';', $txt->txtdata);
++ my $type = shift @parts;
++ next unless $type =~ m/^\s*v\s*=\s*DMARC1\s*/;
++ $authorized = 1;
++ # just for debugging:
++ if ($txt->txtdata ne "v=DMARC1")
++ {
++ print STDERR "$progname: DEBUG: $domain._report._dmarc.$repdestdomain: query answer: ", $txt->txtdata, "\n";
++ }
++ foreach my $parts (@parts)
++ {
++ if ($parts =~ m/^\s*rua\s*=/)
++ {
++ $replaced = 1;
++ $parts =~ s/^\s*rua\s*=\s*//;
++ foreach my $tempuri (split(',', $parts))
++ {
++ $raw_address = check_uri($tempuri);
++ if ($raw_address eq "")
++ {
++ next;
++ }
++ my $uridomain = lc($raw_address);
++ $uridomain =~ s/.*@//;
++ $uridomain =~ s/!\d{15}([kmgt])?$//;
++ if ($repdestdomain eq $uridomain)
++ {
++ $address =~ s/!\d([kmgt])?$//i;
++ if ($verbose)
++ {
++ print STDERR "$progname: adding new reporting URI for domain $domain: $address\n";
++ }
++ if (check_size_restriction($raw_address, $reportsize))
++ {
++ $repdest .= $address . ", ";
++ }
++ else
++ {
++ $repdest_fallback .= $address . ", ";
++ }
++ }
++ else
++ {
++ if ($verbose)
++ {
++ print STDERR "$progname: ignoring new reporting URI due to differing host parts: $repdestdomain != $uridomain!\n";
++ }
++ }
++ }
++ # there should be only one part with "rua=", so stop here
++ last;
++ }
++ }
++ # there should be only one TXT record starting with "v=DMARC1", so stop here
++ last;
++ }
+ }
+- if ($letter eq 'm')
++ else
+ {
+- $report_maxbytes = $report_maxbytes * 1048576;
++ switch ($res->errorstring)
++ {
++ case "NXDOMAIN" { } # definitely not authorized
++ case "SERVFAIL" { $authorized = 1; } # not a definite answer, so be kind
++ case "query timed out" { $authorized = 1; } # not a definite answer, so be kind
++ else { $authorized = 1; } # for now we authorize anything else
++ }
+ }
+- if ($letter eq 'g')
++
++ if ($authorized && !$replaced)
+ {
+- $report_maxbytes = $report_maxbytes * (2**30);
++
++ $repdest .= $address . ", ";
+ }
+- if ($letter eq 't')
++ elsif (!$authorized)
+ {
+- $report_maxbytes = $report_maxbytes * (2**40);
++ if ($verbose)
++ {
++ print STDERR "$progname: $domain is NOT authorized to send reports to $address, dropping address! (" . $res->errorstring . ")\n";
++ }
++ next;
+ }
+ }
+ }
++ }
++ $repdest =~ s/, $//;
++ $repdest_fallback =~ s/, $//;
+
+- # Test mode, just report what would have been done
+- if ($testmode)
++ # Test mode, just report what would have been done
++ if ($testmode)
++ {
++ if ($repdest ne "")
+ {
+ print STDERR "$progname: would email $domain report for " .
+- "$rowcount records to " . $uri->opaque . "\n";
++ "$rowcount records to $repdest\n";
+ }
+- # ensure a scheme is present
+- elsif (!defined($uri->scheme))
++ elsif ($repdest_fallback ne "")
+ {
+- if ($verbose >= 2)
+- {
+- print STDERR "$progname: unknown URI scheme in '$repuri' for domain $domain\n";
+- }
+- next;
++ print STDERR "$progname: would email an error report for " .
++ "$domain to $repdest_fallback\n";
+ }
+- # send/post report
+- elsif ($uri->scheme eq "mailto")
++ }
++ else
++ {
++ if ($repdest ne "")
+ {
+- my $datestr;
+- my $report_id;
+-
+- if (!open($zipin, $zipfile))
+- {
+- print STDERR "$progname: can't read zipped report for $domain: $!\n";
+- next;
+- }
++ # send out the report:
++ $boundary = hostfqdn() . "/" . time();
+
+- $boundary = "report_section";
+-
+- $report_id = $domain . "-" . $now . "@" . $repdom;
+- $datestr = strftime("%a, %e %b %Y %H:%M:%S %z (%Z)",
+- localtime);
++ my $report_id = $domain . "-" . $now . "@" . $repdom;
++ my $datestr = strftime("%a, %e %b %Y %H:%M:%S %z (%Z)", localtime);
+
+ $mailout = "To: $repdest\n";
+ $mailout .= "From: $repemail\n";
+- $mailout .= "Subject: Report Domain: " . $domain . " Submitter: " . $repdom . " Report-ID: " . $report_id . "\n";
++ $mailout .= "Subject: Report Domain: " . $domain . "\n";
++ $mailout .= " Submitter: " . $repdom . "\n";
++ $mailout .= " Report-ID: " . $report_id . "\n";
+ $mailout .= "X-Mailer: " . $progname . " v" . $version ."\n";
+ $mailout .= "Date: " . $datestr . "\n";
+ $mailout .= "Message-ID: <$report_id>\n";
+@@ -898,52 +1069,100 @@ foreach (@$domainset)
+ $mailout .= "Content-Disposition: attachment; filename=\"$zipfile\"\n";
+ $mailout .= "Content-Transfer-Encoding: base64\n";
+ $mailout .= "\n";
++ $mailout .= $encoded_report;
++ $mailout .= "\n";
++ $mailout .= "--$boundary--\n";
++ $smtpstatus = "sent";
++ $smtpfail = 0;
++ if (!$smtp->mail($repemail) ||
++ !$smtp->to(split(', ', $repdest), {SkipBad => 1 }) ||
++ !$smtp->data() ||
++ !$smtp->datasend($mailout) ||
++ !$smtp->dataend())
++ {
++ $smtpfail = 1;
++ $smtpstatus = "failed to send";
++ }
+
+- while (read($zipin, $buf, 60*57))
++ if ($verbose || $smtpfail)
+ {
+- $mailout .= encode_base64($buf);
++ # now perl voodoo:
++ $answer = ${${*$smtp}{'net_cmd_resp'}}[1] || $smtp->message() || 'unknown error';
++ chomp($answer);
++ print STDERR "$progname: $smtpstatus report for $domain to $repdest ($answer)\n";
+ }
+
++ $smtp->reset();
++ }
++ elsif ($repdest_fallback ne "")
++ {
++ # send error report to $repdest_fallback:
++ if ($verbose)
++ {
++ print STDERR "$progname: emailing an error report for $domain to $repdest_fallback\n";
++ }
++ $boundary = hostfqdn() . "/" . time();
++
++ my $report_id = $domain . "-" . $now . "@" . $repdom;
++ my $datestr = strftime("%a, %e %b %Y %H:%M:%S %z (%Z)", localtime);
++
++ $mailout = "To: $repdest_fallback\n";
++ $mailout .= "From: $repemail\n";
++ $mailout .= "Subject: Error Report Domain: " . $domain . " Submitter: " . $repdom . " Report-ID: " . $report_id . "\n";
++ $mailout .= "X-Mailer: " . $progname . " v" . $version ."\n";
++ $mailout .= "Date: " . $datestr . "\n";
++ $mailout .= "Message-ID: <$report_id>\n";
++ $mailout .= "Auto-Submitted: auto-generated\n";
++ $mailout .= "MIME-Version: 1.0\n";
++ $mailout .= "Content-Type: multipart/report;\n";
++ $mailout .= " report-type=delivery-status;\n";
++ $mailout .= " boundary=\"$boundary\"\n";
++ $mailout .= "\n";
++ $mailout .= "This is a MIME-encapsulated message.\n";
++ $mailout .= "\n";
++ $mailout .= "--$boundary\n";
++ $mailout .= "Content-Description: DMARC Notification\n";
++ $mailout .= "Content-Type: text/plain\n";
++ $mailout .= "\n";
++ $mailout .= "This is a DMARC error report from host " . hostfqdn() . ".\n";
++ $mailout .= "\n";
++ $mailout .= "I'm sorry to have to inform you that a DMARC aggregate report\n";
++ $mailout .= "could not be delivered to any of your URIs mentioned in your DMARC\n";
++ $mailout .= "DNS resource records because of size limitations.\n";
++ $mailout .= "\n";
++ $mailout .= "--$boundary\n";
++ $mailout .= "Content-Description: DMARC Error Report\n";
++ $mailout .= "Content-Type: text/plain\n";
++ $mailout .= "\n";
++ $mailout .= "Report-Date: " . strftime("%a, %b %e %Y %H:%M:%S %z (%Z)", localtime()) . "\n";
++ $mailout .= "Report-Domain: $domain\n";
++ $mailout .= "Report-ID: $report_id\n";
++ $mailout .= "Report-Size: $reportsize\n";
++ $mailout .= "Submitter: $repdom\n";
++ $mailout .= "Submitting-URI: $repdest_fallback\n";
+ $mailout .= "\n";
+ $mailout .= "--$boundary--\n";
+- my $reportsize = length($mailout);
+-
+- if ($reportsize > $report_maxbytes)
++ $smtpstatus = "sent";
++ $smtpfail = 0;
++ if (!$smtp->mail($repemail) ||
++ !$smtp->to(split(', ', $repdest_fallback), { SkipBad => 1 }) ||
++ !$smtp->data() ||
++ !$smtp->datasend($mailout) ||
++ !$smtp->dataend())
+ {
+- # XXX -- generate an error report here
+- print STDERR "$progname: report was too large ($reportsize bytes) per limitation of URI " . $uri->opaque . " for domain $domain\n";
++ $smtpfail = 1;
++ $smtpstatus = "failed to send";
+ }
+- else
+- {
+- $smtpstatus = "sent";
+- $smtpfail = 0;
+- if (!$smtp->mail($repemail) ||
+- !$smtp->to($repdest) ||
+- !$smtp->data() ||
+- !$smtp->datasend($mailout) ||
+- !$smtp->dataend())
+- {
+- $smtpfail = 1;
+- $smtpstatus = "failed to send";
+- }
+
+- if ($verbose || $smtpfail)
+- {
+- # now perl voodoo:
+- $answer = ${${*$smtp}{'net_cmd_resp'}}[1] || $smtp->message() || 'unknown error';
+- chomp($answer);
+- print STDERR "$progname: $smtpstatus report for $domain to $repdest ($answer)\n";
+- }
++ if ($verbose || $smtpfail)
++ {
++ # now perl voodoo:
++ $answer = ${${*$smtp}{'net_cmd_resp'}}[1] || $smtp->message() || 'unknown error';
++ chomp($answer);
++ print STDERR "$progname: $smtpstatus failure notice for report for $domain to $repdest ($answer)\n";
+ }
+
+ $smtp->reset();
+-
+- close($zipin);
+- }
+- else
+- {
+- print STDERR "$progname: unsupported reporting URI scheme " . $uri->scheme . " for domain $domain\n";
+- next;
+ }
+ }
+
diff --git a/network/opendmarc/patches/z04_moreHeadersFailureReportVsBeta1.patch b/network/opendmarc/patches/z04_moreHeadersFailureReportVsBeta1.patch
new file mode 100644
index 0000000000..23dc08f467
--- /dev/null
+++ b/network/opendmarc/patches/z04_moreHeadersFailureReportVsBeta1.patch
@@ -0,0 +1,352 @@
+--- a/opendmarc/opendmarc.c 2016-12-21 18:01:21.322036404 +0100
++++ b/opendmarc/opendmarc.c 2016-12-21 18:01:48.893181823 +0100
+@@ -2057,6 +2057,7 @@
+ char *apolicy = NULL;
+ char *aresult = NULL;
+ char *adisposition = NULL;
++ char *deliveryresult = NULL;
+ char *hostname = NULL;
+ char *authservid = NULL;
+ char *spfaddr;
+@@ -2774,6 +2775,154 @@
+ }
+
+ /*
++ ** Enact policy based on DMARC results.
++ */
++
++ result = DMARC_RESULT_ACCEPT;
++
++ switch (policy)
++ {
++ case DMARC_POLICY_ABSENT: /* No DMARC record found */
++ case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */
++ aresult = "none";
++ ret = SMFIS_ACCEPT;
++ result = DMARC_RESULT_ACCEPT;
++ break;
++
++ case DMARC_POLICY_NONE: /* Alignment failed, but policy is none: */
++ aresult = "fail"; /* Accept and report */
++ ret = SMFIS_ACCEPT;
++ result = DMARC_RESULT_ACCEPT;
++ break;
++
++ case DMARC_POLICY_PASS: /* Explicit accept */
++ aresult = "pass";
++ ret = SMFIS_ACCEPT;
++ result = DMARC_RESULT_ACCEPT;
++ break;
++
++ case DMARC_POLICY_REJECT: /* Explicit reject */
++ aresult = "fail";
++
++ if (conf->conf_overridemlm != NULL &&
++ (dmarcf_checkhost(cc->cctx_host, conf->conf_overridemlm) ||
++ (dmarcf_checkip((struct sockaddr *)&cc->cctx_ip, conf->conf_overridemlm))))
++ {
++ if (conf->conf_dolog)
++ {
++ syslog(LOG_INFO, "%s: overriding policy for mail from %s: MLM",
++ dfc->mctx_jobid, dfc->mctx_fromdomain);
++ }
++ ret = SMFIS_ACCEPT;
++ result = DMARC_RESULT_OVRD_MAILING_LIST;
++ }
++ else
++ {
++ if (conf->conf_rejectfail && random() % 100 < pct)
++ {
++ snprintf(replybuf, sizeof replybuf,
++ "rejected by DMARC policy for %s", pdomain);
++
++ status = dmarcf_setreply(ctx, DMARC_REJECT_SMTP,
++ DMARC_REJECT_ESC, replybuf);
++ if (status != MI_SUCCESS && conf->conf_dolog)
++ {
++ syslog(LOG_ERR, "%s: smfi_setreply() failed",
++ dfc->mctx_jobid);
++ }
++
++ ret = SMFIS_REJECT;
++ result = DMARC_RESULT_REJECT;
++ }
++
++ if (conf->conf_copyfailsto != NULL)
++ {
++ status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto);
++ if (status != MI_SUCCESS && conf->conf_dolog)
++ {
++ syslog(LOG_ERR, "%s: smfi_addrcpt() failed",
++ dfc->mctx_jobid);
++ }
++ }
++ }
++
++ break;
++
++ case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */
++ aresult = "fail";
++
++ if (conf->conf_overridemlm != NULL &&
++ (dmarcf_checkhost(cc->cctx_host, conf->conf_overridemlm) ||
++ (dmarcf_checkip((struct sockaddr *)&cc->cctx_ip, conf->conf_overridemlm))))
++ {
++ if (conf->conf_dolog)
++ {
++ syslog(LOG_INFO, "%s: overriding policy for mail from %s: MLM",
++ dfc->mctx_jobid, dfc->mctx_fromdomain);
++ }
++ ret = SMFIS_ACCEPT;
++ result = DMARC_RESULT_OVRD_MAILING_LIST;
++ }
++ else
++ {
++ if (conf->conf_rejectfail && random() % 100 < pct &&
++ conf->conf_holdquarantinedmessages)
++ {
++ snprintf(replybuf, sizeof replybuf,
++ "quarantined by DMARC policy for %s",
++ pdomain);
++
++ status = smfi_quarantine(ctx, replybuf);
++ if (status != MI_SUCCESS && conf->conf_dolog)
++ {
++ syslog(LOG_ERR, "%s: smfi_quarantine() failed",
++ dfc->mctx_jobid);
++ }
++
++ ret = SMFIS_ACCEPT;
++ result = DMARC_RESULT_QUARANTINE;
++ }
++
++ if (conf->conf_copyfailsto != NULL)
++ {
++ status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto);
++ if (status != MI_SUCCESS && conf->conf_dolog)
++ {
++ syslog(LOG_ERR, "%s: smfi_addrcpt() failed",
++ dfc->mctx_jobid);
++ }
++ }
++ }
++
++ break;
++
++ default:
++ aresult = "temperror";
++ ret = SMFIS_TEMPFAIL;
++ result = DMARC_RESULT_TEMPFAIL;
++ break;
++ }
++
++ /* prepare human readable dispositon string for later processing */
++ switch (result)
++ {
++ case DMARC_RESULT_REJECT:
++ adisposition = "reject";
++ deliveryresult = "reject";
++ break;
++
++ case DMARC_RESULT_QUARANTINE:
++ adisposition = "quarantine";
++ deliveryresult = "policy";
++ break;
++
++ default:
++ adisposition = "none";
++ deliveryresult = "delivered";
++ break;
++ }
++
++ /*
+ ** Generate a failure report.
+ */
+
+@@ -2931,8 +3080,11 @@
+ "Auth-Failure: dmarc\n");
+
+ dmarcf_dstring_printf(dfc->mctx_afrf,
+- "Authentication-Results: %s; dmarc=fail header.from=%s\n",
+- authservid,
++ "Authentication-Results: %s;\n",
++ authservid);
++ dmarcf_dstring_printf(dfc->mctx_afrf,
++ " dmarc=%s (p=%s dis=%s) header.from=%s\n",
++ aresult, apolicy, adisposition,
+ dfc->mctx_fromdomain);
+
+ dmarcf_dstring_printf(dfc->mctx_afrf,
+@@ -2949,6 +3101,20 @@
+ cc->cctx_host);
+
+ dmarcf_dstring_printf(dfc->mctx_afrf,
++ "Source-Port: %u\n",
++ cc->cctx_ip.ss_family == AF_INET6 ? ntohs(((struct sockaddr_in6*) &cc->cctx_ip)->sin6_port) : ntohs(((struct sockaddr_in*) &cc->cctx_ip)->sin_port));
++
++ dmarcf_dstring_printf(dfc->mctx_afrf,
++ "Identity-Alignment: %s%s%s\n",
++ align_dkim == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? "dkim" : "",
++ ((align_dkim == DMARC_POLICY_DKIM_ALIGNMENT_PASS) && (align_spf == DMARC_POLICY_SPF_ALIGNMENT_PASS)) ? ", " : ((align_dkim != DMARC_POLICY_DKIM_ALIGNMENT_PASS) && (align_spf != DMARC_POLICY_SPF_ALIGNMENT_PASS)) ? "none" : "",
++ align_spf == DMARC_POLICY_SPF_ALIGNMENT_PASS ? "spf" : "");
++
++ dmarcf_dstring_printf(dfc->mctx_afrf,
++ "Delivery-Result: %s\n",
++ deliveryresult);
++
++ dmarcf_dstring_printf(dfc->mctx_afrf,
+ "Reported-Domain: %s\n\n",
+ dfc->mctx_fromdomain);
+
+@@ -3015,151 +3181,6 @@
+ }
+ }
+
+- /*
+- ** Enact policy based on DMARC results.
+- */
+-
+- result = DMARC_RESULT_ACCEPT;
+-
+- switch (policy)
+- {
+- case DMARC_POLICY_ABSENT: /* No DMARC record found */
+- case DMARC_FROM_DOMAIN_ABSENT: /* No From: domain */
+- aresult = "none";
+- ret = SMFIS_ACCEPT;
+- result = DMARC_RESULT_ACCEPT;
+- break;
+-
+- case DMARC_POLICY_NONE: /* Alignment failed, but policy is none: */
+- aresult = "fail"; /* Accept and report */
+- ret = SMFIS_ACCEPT;
+- result = DMARC_RESULT_ACCEPT;
+- break;
+-
+- case DMARC_POLICY_PASS: /* Explicit accept */
+- aresult = "pass";
+- ret = SMFIS_ACCEPT;
+- result = DMARC_RESULT_ACCEPT;
+- break;
+-
+- case DMARC_POLICY_REJECT: /* Explicit reject */
+- aresult = "fail";
+-
+- if (conf->conf_overridemlm != NULL &&
+- (dmarcf_checkhost(cc->cctx_host, conf->conf_overridemlm) ||
+- (dmarcf_checkip((struct sockaddr *)&cc->cctx_ip, conf->conf_overridemlm))))
+- {
+- if (conf->conf_dolog)
+- {
+- syslog(LOG_INFO, "%s: overriding policy for mail from %s: MLM",
+- dfc->mctx_jobid, dfc->mctx_fromdomain);
+- }
+- ret = SMFIS_ACCEPT;
+- result = DMARC_RESULT_OVRD_MAILING_LIST;
+- }
+- else
+- {
+- if (conf->conf_rejectfail && random() % 100 < pct)
+- {
+- snprintf(replybuf, sizeof replybuf,
+- "rejected by DMARC policy for %s", pdomain);
+-
+- status = dmarcf_setreply(ctx, DMARC_REJECT_SMTP,
+- DMARC_REJECT_ESC, replybuf);
+- if (status != MI_SUCCESS && conf->conf_dolog)
+- {
+- syslog(LOG_ERR, "%s: smfi_setreply() failed",
+- dfc->mctx_jobid);
+- }
+-
+- ret = SMFIS_REJECT;
+- result = DMARC_RESULT_REJECT;
+- }
+-
+- if (conf->conf_copyfailsto != NULL)
+- {
+- status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto);
+- if (status != MI_SUCCESS && conf->conf_dolog)
+- {
+- syslog(LOG_ERR, "%s: smfi_addrcpt() failed",
+- dfc->mctx_jobid);
+- }
+- }
+- }
+-
+- break;
+-
+- case DMARC_POLICY_QUARANTINE: /* Explicit quarantine */
+- aresult = "fail";
+-
+- if (conf->conf_overridemlm != NULL &&
+- (dmarcf_checkhost(cc->cctx_host, conf->conf_overridemlm) ||
+- (dmarcf_checkip((struct sockaddr *)&cc->cctx_ip, conf->conf_overridemlm))))
+- {
+- if (conf->conf_dolog)
+- {
+- syslog(LOG_INFO, "%s: overriding policy for mail from %s: MLM",
+- dfc->mctx_jobid, dfc->mctx_fromdomain);
+- }
+- ret = SMFIS_ACCEPT;
+- result = DMARC_RESULT_OVRD_MAILING_LIST;
+- }
+- else
+- {
+- if (conf->conf_rejectfail && random() % 100 < pct &&
+- conf->conf_holdquarantinedmessages)
+- {
+- snprintf(replybuf, sizeof replybuf,
+- "quarantined by DMARC policy for %s",
+- pdomain);
+-
+- status = smfi_quarantine(ctx, replybuf);
+- if (status != MI_SUCCESS && conf->conf_dolog)
+- {
+- syslog(LOG_ERR, "%s: smfi_quarantine() failed",
+- dfc->mctx_jobid);
+- }
+-
+- ret = SMFIS_ACCEPT;
+- result = DMARC_RESULT_QUARANTINE;
+- }
+-
+- if (conf->conf_copyfailsto != NULL)
+- {
+- status = dmarcf_addrcpt(ctx, conf->conf_copyfailsto);
+- if (status != MI_SUCCESS && conf->conf_dolog)
+- {
+- syslog(LOG_ERR, "%s: smfi_addrcpt() failed",
+- dfc->mctx_jobid);
+- }
+- }
+- }
+-
+- break;
+-
+- default:
+- aresult = "temperror";
+- ret = SMFIS_TEMPFAIL;
+- result = DMARC_RESULT_TEMPFAIL;
+- break;
+- }
+-
+- /* prepare human readable dispositon string for later processing */
+- switch (result)
+- {
+- case DMARC_RESULT_REJECT:
+- adisposition = "reject";
+- break;
+-
+- case DMARC_RESULT_QUARANTINE:
+- adisposition = "quarantine";
+- break;
+-
+- default:
+- adisposition = "none";
+- break;
+- }
+-
+ if (conf->conf_dolog)
+ {
+ syslog(LOG_INFO, "%s: %s %s", dfc->mctx_jobid,
diff --git a/network/opendmarc/patches/z06_use_envdomain_SPF_logging.patch b/network/opendmarc/patches/z06_use_envdomain_SPF_logging.patch
new file mode 100644
index 0000000000..3a702fe8c0
--- /dev/null
+++ b/network/opendmarc/patches/z06_use_envdomain_SPF_logging.patch
@@ -0,0 +1,13 @@
+diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
+index f5c30f9..29f3f93 100644
+--- a/opendmarc/opendmarc.c
++++ b/opendmarc/opendmarc.c
+@@ -2542,7 +2542,7 @@ mlfi_eom(SMFICTX *ctx)
+ &used_mfrom);
+ if (used_mfrom == TRUE)
+ {
+- use_domain = dfc->mctx_envfrom;
++ use_domain = dfc->mctx_envdomain;
+ spf_mode = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
+ }
+ else
diff --git a/network/opendmarc/slack-desc b/network/opendmarc/slack-desc
new file mode 100644
index 0000000000..3085453bb3
--- /dev/null
+++ b/network/opendmarc/slack-desc
@@ -0,0 +1,19 @@
+# HOW TO EDIT THIS FILE:
+# The "handy ruler" below makes it easier to edit a package description.
+# Line up the first '|' above the ':' following the base package name, and
+# the '|' on the right side marks the last column you can put a character in.
+# You must make exactly 11 lines for the formatting to be correct. It's also
+# customary to leave one space after the ':' except on otherwise blank lines.
+
+ |-----handy-ruler------------------------------------------------------|
+opendmarc: opendmarc (DMARC milter and library)
+opendmarc:
+opendmarc: OpenDMARC is a free open source software implementation of the DMARC
+opendmarc: specification.
+opendmarc:
+opendmarc: Homepage: http://www.trusteddomain.org/opendmarc/
+opendmarc:
+opendmarc:
+opendmarc:
+opendmarc:
+opendmarc: