[packages/opendmarc] Rel 4; init scripts, subdomain dmarc fixes, PSL usage

arekm arekm at pld-linux.org
Wed May 13 15:53:18 CEST 2026


commit 3378ced5c2e467af89be417e783fc592a0d3cba8
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date:   Wed May 13 15:52:46 2026 +0200

    Rel 4; init scripts, subdomain dmarc fixes, PSL usage

 issue-54-activate.patch |  18 ++
 issue-54.patch          | 449 ++++++++++++++++++++++++++++++++++++++++++++++++
 opendmarc.init          |  99 +++++++++++
 opendmarc.service       |  16 ++
 opendmarc.spec          |  43 +++--
 opendmarc.sysconfig     |   5 +
 pld-defaults.patch      |  31 ++++
 7 files changed, 648 insertions(+), 13 deletions(-)
---
diff --git a/opendmarc.spec b/opendmarc.spec
index 8bd95f2..b723c0f 100644
--- a/opendmarc.spec
+++ b/opendmarc.spec
@@ -1,17 +1,22 @@
-# TODO
-# - pldize initscript
+#
+# Conditional build:
+%bcond_without	tests		# build-time test suite
+
 %define		ver 1-4-2
 %define		ver_dot	%(echo %{ver} | tr '-' '.')
 Summary:	DMARC milter and library
 Summary(pl.UTF-8):	Milter i biblioteka DMARC
 Name:		opendmarc
 Version:	%{ver_dot}
-Release:	3
+Release:	4
 License:	BSD
 Group:		Daemons
 Source0:	https://github.com/trusteddomainproject/OpenDMARC/archive/refs/tags/rel-%{name}-%{ver}.tar.gz
 # Source0-md5:	658d951db84a0305b0c5d9312eff5b64
 Source1:	%{name}.tmpfiles
+Source2:	%{name}.init
+Source3:	%{name}.sysconfig
+Source4:	%{name}.service
 Patch0:		ticket168.patch
 Patch1:		ticket193.patch
 Patch2:		ticket204.patch
@@ -29,6 +34,9 @@ Patch13:	check-correct-domain.patch
 Patch14:	arcares-segfaults.patch
 Patch15:	parse-arc-leaks.patch
 Patch16:	cve-2024-25768.patch
+Patch17:	pld-defaults.patch
+Patch18:	issue-54.patch
+Patch19:	issue-54-activate.patch
 URL:		http://www.trusteddomain.org/opendmarc.html
 BuildRequires:	autoconf >= 2.61
 BuildRequires:	automake
@@ -45,6 +53,7 @@ Requires(pre):	/usr/sbin/useradd
 Requires(postun):	/usr/sbin/groupdel
 Requires(postun):	/usr/sbin/userdel
 Requires:	libopendmarc = %{version}-%{release}
+Requires:	publicsuffix-list
 Provides:	group(opendmarc)
 Provides:	user(opendmarc)
 BuildRoot:	%{tmpdir}/%{name}-%{version}-root-%(id -u -n)
@@ -112,6 +121,9 @@ do tworzenia aplikacji wykorzystujących bibliotekę libopendmarc.
 %patch -P14 -p1
 %patch -P15 -p1
 %patch -P16 -p1
+%patch -P17 -p1
+%patch -P18 -p1
+%patch -P19 -p1
 
 %build
 %{__libtoolize}
@@ -127,23 +139,23 @@ do tworzenia aplikacji wykorzystujących bibliotekę libopendmarc.
 	--with-spf2-lib=%{_libdir}
 %{__make}
 
+%if %{with tests}
+%{__make} check
+%endif
+
 %install
 rm -rf $RPM_BUILD_ROOT
-install -d $RPM_BUILD_ROOT{%{_sysconfdir},/etc/rc.d/init.d,%{systemdtmpfilesdir}} \
+install -d $RPM_BUILD_ROOT{%{_sysconfdir}/sysconfig,/etc/rc.d/init.d,%{systemdtmpfilesdir},%{systemdunitdir}} \
 	$RPM_BUILD_ROOT%{_localstatedir}/{run,spool}/%{name}
 
 %{__make} install \
 	DESTDIR=$RPM_BUILD_ROOT
 
-install -p contrib/init/redhat/%{name} $RPM_BUILD_ROOT/etc/rc.d/init.d/%{name}
+install -p %{SOURCE2} $RPM_BUILD_ROOT/etc/rc.d/init.d/%{name}
+cp -p %{SOURCE3} $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/%{name}
+cp -p %{SOURCE4} $RPM_BUILD_ROOT%{systemdunitdir}/%{name}.service
 cp -p opendmarc/%{name}.conf.sample $RPM_BUILD_ROOT%{_sysconfdir}/%{name}.conf
 
-# Set some basic settings in the default config file
-perl -pi -e 's|^# (HistoryFile /var/run)/(opendmarc.dat)|$1/opendmarc/$2/;
-             s|^# (Socket )|$1|;
-             s|^# (UserId )|$1|;
-            ' $RPM_BUILD_ROOT%{_sysconfdir}/%{name}.conf
-
 cp -p %{SOURCE1} $RPM_BUILD_ROOT%{systemdtmpfilesdir}/%{name}.conf
 
 install -d $RPM_BUILD_ROOT%{_includedir}/%{name}
@@ -163,18 +175,21 @@ rm -rf $RPM_BUILD_ROOT
 %post
 /sbin/chkconfig --add %{name}
 %service %{name} restart
+%systemd_post %{name}.service
 
 %preun
-if [ $1 -eq 0 ]; then
-	/sbin/chkconfig --del %{name}
+if [ "$1" = "0" ]; then
 	%service %{name} stop
+	/sbin/chkconfig --del %{name}
 fi
+%systemd_preun %{name}.service
 
 %postun
 if [ "$1" = "0" ]; then
 	%userremove opendmarc
 	%groupremove opendmarc
 fi
+%systemd_reload
 
 %post	-n libopendmarc -p /sbin/ldconfig
 %postun	-n libopendmarc -p /sbin/ldconfig
@@ -184,6 +199,7 @@ fi
 %doc CONTRIBUTING INSTALL README README.md RELEASE_NOTES
 %doc db/README.schema db/schema.mysql
 %config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/%{name}.conf
+%config(noreplace) %verify(not md5 mtime size) %{_sysconfdir}/sysconfig/%{name}
 %attr(754,root,root) /etc/rc.d/init.d/%{name}
 %attr(755,root,root) %{_sbindir}/opendmarc
 %attr(755,root,root) %{_sbindir}/opendmarc-check
@@ -194,6 +210,7 @@ fi
 %attr(755,root,root) %{_sbindir}/opendmarc-reports
 %{_mandir}/man5/opendmarc.conf.5*
 %{_mandir}/man8/opendmarc*.8*
+%{systemdunitdir}/%{name}.service
 %{systemdtmpfilesdir}/%{name}.conf
 %dir %attr(700,opendmarc,opendmarc) %{_localstatedir}/spool/%{name}
 %dir %attr(700,opendmarc,opendmarc) %{_localstatedir}/run/%{name}
diff --git a/issue-54-activate.patch b/issue-54-activate.patch
new file mode 100644
index 0000000..bcf6649
--- /dev/null
+++ b/issue-54-activate.patch
@@ -0,0 +1,18 @@
+PLD-only: activate the Public Suffix List in the shipped
+opendmarc.conf.sample.  With PSL configured, opendmarc computes the
+organizational domain via RFC 7489 §6.6.3 instead of falling back to
+the label-walk heuristic added in issue-54.patch.  The path points at
+the file shipped by the publicsuffix-list package, which is pulled in
+via a Requires: on the opendmarc subpackage.
+
+--- a/opendmarc/opendmarc.conf.sample
++++ b/opendmarc/opendmarc.conf.sample
+@@ -293,7 +293,7 @@
+ ##  domain will be evaluated.  This file should be periodically updated.
+ ##  One location to retrieve the file from is https://publicsuffix.org/list/
+ #
+-# PublicSuffixList path
++PublicSuffixList /usr/share/publicsuffix/public_suffix_list.dat
+
+ ##  RecordAllMessages { true | false }
+ ##  	default "false"
diff --git a/issue-54.patch b/issue-54.patch
new file mode 100644
index 0000000..7d27c5c
--- /dev/null
+++ b/issue-54.patch
@@ -0,0 +1,449 @@
+PLD note: upstream commit 7d7457b also touches opendmarc/opendmarc.c to log
+a syslog warning when the label-walk fallback was used.  That hunk dereferences
+cc->cctx_dmarc->org_domain_from_fallback directly, but DMARC_POLICY_T is opaque
+in the public dmarc.h header (opendmarc.c does not include opendmarc_internal.h
+and should not).  The milter hunk has been dropped here; the RFC fix itself
+lives in libopendmarc/opendmarc_policy.c.  The warning can be reinstated once
+upstream exposes an accessor.
+
+From 7d7457bfc703700c90c4551e334b7a734566c1f6 Mon Sep 17 00:00:00 2001
+From: Dan Mahoney <github at gushiorg>
+Date: Mon, 11 May 2026 07:30:17 -0700
+Subject: [PATCH 12/12] Fix subdomain DMARC fallback to parent domain (issue
+ #54)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Per RFC 7489 §6.6.3, when a subdomain has no _dmarc record, the
+organizational domain's DMARC record should be consulted.
+
+The existing code already handled this when a public suffix list (PSL)
+is configured: opendmarc_get_tld() returns the organizational domain
+(e.g. "example.com" for "sub.example.com") and the code queries
+_dmarc.<org-domain>.
+
+The bug: when no PSL is loaded, opendmarc_get_tld() returns the input
+domain unchanged.  The fallback then re-queried _dmarc.<same-domain>,
+which also had no record, and returned DMARC_DNS_ERROR_NO_RECORD
+unconditionally.  The milter then reported dmarc=none regardless of
+whether a parent domain had a DMARC record.
+
+Fix: restructure the fallback in opendmarc_policy_query_dmarc():
+
+  - If the PSL identifies a different organizational domain, query only
+    that domain and stop (preserving the existing RFC-compliant path).
+
+  - If the PSL returns the same domain as the query (no PSL loaded, or
+    no PSL match), walk up the label tree one level at a time, stopping
+    before bare TLDs.  This is not strictly RFC 7489-compliant (which
+    requires a PSL to locate the organizational domain boundary), but it
+    correctly handles the common case where no PSL is configured and a
+    parent domain has a DMARC record.
+
+When the label-walk fallback is used, a LOG_WARNING is emitted via
+syslog to advise the operator that the result may not be RFC-compliant
+and that PublicSuffixList should be set in opendmarc.conf.
+
+Add regression tests (test_subdomain_fallback) using the fake-DNS
+mechanism to verify the fix without live DNS:
+  - Subdomain with no record falls back to parent's p=reject (no PSL)
+  - Domain with its own record is not affected
+  - Deeply nested subdomain walks up to grandparent (no PSL)
+  - No record at any level returns DMARC_DNS_ERROR_NO_RECORD correctly
+  - PSL-based fallback continues to work when TLD file is loaded
+---
+ libopendmarc/opendmarc_internal.h            |   1 +
+ libopendmarc/opendmarc_policy.c              |  58 +++-
+ libopendmarc/tests/Makefile.am               |   5 +-
+ libopendmarc/tests/test_subdomain_fallback.c | 262 +++++++++++++++++++
+ opendmarc/opendmarc.c                        |  16 ++
+ 5 files changed, 338 insertions(+), 4 deletions(-)
+ create mode 100644 libopendmarc/tests/test_subdomain_fallback.c
+
+diff --git a/libopendmarc/opendmarc_internal.h b/libopendmarc/opendmarc_internal.h
+index c48d6d3..8d37480 100644
+--- a/libopendmarc/opendmarc_internal.h
++++ b/libopendmarc/opendmarc_internal.h
+@@ -159,6 +159,7 @@ typedef struct dmarc_policy_t {
+ 	 */
+ 	u_char *	from_domain;		/* Input: From: header domain */
+ 	u_char *	organizational_domain;
++	int		org_domain_from_fallback; /* Non-zero if PSL was absent and label-walk was used */
+ 
+ 	/*
+ 	 * Found in the _dmarc record or supplied to us.
+diff --git a/libopendmarc/opendmarc_policy.c b/libopendmarc/opendmarc_policy.c
+index 32053db..e81dd1c 100644
+--- a/libopendmarc/opendmarc_policy.c
++++ b/libopendmarc/opendmarc_policy.c
+@@ -798,7 +798,13 @@ opendmarc_policy_query_dmarc(DMARC_POLICY_T *pctx, u_char *domain)
+ 	tld_reply = opendmarc_get_tld(domain, tld, sizeof tld);
+ 	if (tld_reply != 0)
+ 		goto dns_failed;
+-	if (strlen(tld) > 0)
++
++	/*
++	 * If the PSL identified an organizational domain distinct from the
++	 * queried domain, try exactly that domain and stop.  Per RFC 7489
++	 * §6.6.3 we look at one domain: the organizational domain.
++	 */
++	if (strlen(tld) > 0 && strcasecmp((char *)tld, (char *)domain) != 0)
+ 	{
+ 		pctx->organizational_domain = strdup(tld);
+ 
+@@ -811,8 +817,7 @@ opendmarc_policy_query_dmarc(DMARC_POLICY_T *pctx, u_char *domain)
+ 		if (bp != NULL)
+ 			goto got_record;
+ 		/*
+-		 * Was a CNAME was found that the resolver did
+-		 * not follow on its own?
++		 * Was a CNAME found that the resolver did not follow on its own?
+ 		 */
+ 		if (bp == NULL && *buf != '\0')
+ 		{
+@@ -820,6 +825,53 @@ opendmarc_policy_query_dmarc(DMARC_POLICY_T *pctx, u_char *domain)
+ 			if (--loop_count != 0)
+ 				goto query_again2;
+ 		}
++		/* Organizational domain has no DMARC record; do not try further. */
++		goto dns_failed;
++	}
++
++	/*
++	 * No PSL was loaded, or the PSL could not identify an organizational
++	 * domain boundary (it returned the input domain unchanged).  Walk up
++	 * the label tree as a best-effort fallback, stopping before bare TLDs.
++	 * This is not strictly per RFC 7489 (which requires a PSL to determine
++	 * the organizational domain), but it handles the common case where no
++	 * PSL is configured and a parent domain has a DMARC record.
++	 * Configure PublicSuffixList in opendmarc.conf for RFC-compliant behavior.
++	 */
++	{
++		u_char *cur = (u_char *)domain;
++		u_char *dot;
++
++		while ((dot = (u_char *)strchr((char *)cur, '.')) != NULL)
++		{
++			cur = dot + 1;
++
++			/* Stop before bare TLDs (labels with no further dot). */
++			if (strchr((char *)cur, '.') == NULL)
++				break;
++
++			loop_count = DNS_MAX_RETRIES;
++			(void) strlcpy(copy, "_dmarc.", sizeof copy);
++			(void) strlcat(copy, cur, sizeof copy);
++query_again3:
++			(void) memset(buf, '\0', sizeof buf);
++			bp = dmarc_dns_get_record(copy, &dns_reply, buf, sizeof buf);
++			if (bp != NULL)
++			{
++				pctx->organizational_domain = strdup(cur);
++				pctx->org_domain_from_fallback = 1;
++				goto got_record;
++			}
++			/*
++			 * Was a CNAME found that the resolver did not follow on its own?
++			 */
++			if (bp == NULL && *buf != '\0')
++			{
++				(void) strlcpy(copy, buf, sizeof copy);
++				if (--loop_count != 0)
++					goto query_again3;
++			}
++		}
+ 	}
+ dns_failed:
+ 	switch (dns_reply)
+diff --git a/libopendmarc/tests/Makefile.am b/libopendmarc/tests/Makefile.am
+index b8b01bc..56b3e96 100644
+--- a/libopendmarc/tests/Makefile.am
++++ b/libopendmarc/tests/Makefile.am
+@@ -8,7 +8,8 @@ check_PROGRAMS		 = test_tld \
+ 			   test_dmarc_fetch \
+ 			   test_xml_parse \
+ 			   test_parse_to_buf \
+-			   test_alignment
++			   test_alignment \
++			   test_subdomain_fallback
+ if LIVE_TESTS
+ #check_PROGRAMS           += test_dns_lookup
+ #test_dns_lookup_SOURCES  = test_dns_lookup.c
+@@ -33,6 +34,8 @@ test_xml_parse_SOURCES   = test_xml_parse.c
+ 
+ test_alignment_SOURCES   = test_alignment.c
+ 
++test_subdomain_fallback_SOURCES = test_subdomain_fallback.c
++
+ TESTS = $(check_PROGRAMS)
+ 
+ EXTRA_DIST	= testfiles/effective_tld_names.dat \
+diff --git a/libopendmarc/tests/test_subdomain_fallback.c b/libopendmarc/tests/test_subdomain_fallback.c
+new file mode 100644
+index 0000000..3457524
+--- /dev/null
++++ b/libopendmarc/tests/test_subdomain_fallback.c
+@@ -0,0 +1,262 @@
++/*
++** test_subdomain_fallback.c -- regression test for issue #54
++**
++** When a subdomain has no _dmarc record, opendmarc_policy_query_dmarc()
++** must fall back to the organizational/parent domain's DMARC record
++** per RFC 7489 §6.6.3.
++**
++** Before the fix, when no PublicSuffixList was configured, opendmarc_get_tld()
++** returned the queried domain unchanged.  The fallback then re-queried the
++** same _dmarc name, which also had no record, and the function returned
++** DMARC_DNS_ERROR_NO_RECORD regardless of whether a parent had a record.
++**
++** The fix adds a label-walking fallback for the no-PSL case, and keeps the
++** existing PSL-based single-lookup path for the case where a PSL is loaded.
++**
++** Distinct domain names are used for each test to avoid cross-contamination
++** of the global fake-DNS table (entries persist for the process lifetime).
++*/
++
++#include "../opendmarc_internal.h"
++#include "../dmarc.h"
++
++#define TESTFILE "testfiles/effective_tld_names.dat"
++
++#define CHECK(cond, msg)	\
++	do {			\
++		count++;	\
++		if (cond) {	\
++			pass++;	\
++		} else {	\
++			printf("\t%s(%d): %s: FAIL\n", __FILE__, __LINE__, msg); \
++			fails++;	\
++		}		\
++	} while (0)
++
++int
++main(int argc, char **argv)
++{
++	int pass = 0, fails = 0, count = 0;
++	DMARC_POLICY_T *pctx;
++	OPENDMARC_STATUS_T status;
++	int p;
++	u_char utilized[256];
++	char *srcdir;
++
++	srcdir = getenv("srcdir");
++	if (srcdir != NULL)
++	{
++		if (chdir(srcdir) != 0)
++		{
++			perror(srcdir);
++			return 1;
++		}
++	}
++
++	/*
++	 * Set up the fake DNS table.  Once any entry is added, dmarc_dns_get_record()
++	 * consults only this table for all lookups; names absent from the table
++	 * return NO_DATA (simulating "no record found").
++	 *
++	 * Use a distinct domain per test to avoid cross-contamination.
++	 */
++
++	/* Test domains and their fake records. */
++	opendmarc_dns_fake_record("_dmarc.parent1.example",
++	    "v=DMARC1; p=reject; rua=mailto:dmarc at parent1.example");
++	/* _dmarc.sub.parent1.example: absent from table → NO_DATA */
++
++	opendmarc_dns_fake_record("_dmarc.own-record.example",
++	    "v=DMARC1; p=quarantine");
++
++	opendmarc_dns_fake_record("_dmarc.parent3.example",
++	    "v=DMARC1; p=none; sp=reject");
++	/* _dmarc.a.b.parent3.example and _dmarc.b.parent3.example: absent → NO_DATA */
++
++	/* _dmarc.sub.nodmarc.example and _dmarc.nodmarc.example: absent → NO_DATA */
++
++	/* PSL test: bcx.com is recognized as registrable by the test TLD file. */
++	opendmarc_dns_fake_record("_dmarc.bcx.com",
++	    "v=DMARC1; p=reject");
++	/* _dmarc.sub.bcx.com: absent → NO_DATA */
++
++	/*
++	 * === Test 1 ===
++	 * No PSL.  Subdomain has no record; parent has p=reject.
++	 *
++	 * Before the fix: returned DMARC_DNS_ERROR_NO_RECORD.
++	 * After the fix:  label-walking finds _dmarc.parent1.example → DMARC_PARSE_OKAY.
++	 */
++	pctx = opendmarc_policy_connect_init((u_char *)"1.2.3.4", 0);
++	if (pctx == NULL) { fprintf(stderr, "connect_init failed\n"); return 1; }
++
++	(void) opendmarc_policy_store_from_domain(pctx, (u_char *)"sub.parent1.example");
++	status = opendmarc_policy_query_dmarc(pctx, (u_char *)"sub.parent1.example");
++
++	CHECK(status == DMARC_PARSE_OKAY,
++	    "no-PSL subdomain fallback: query should return DMARC_PARSE_OKAY");
++	if (status == DMARC_PARSE_OKAY)
++	{
++		opendmarc_policy_fetch_p(pctx, &p);
++		CHECK(p == DMARC_RECORD_P_REJECT,
++		    "no-PSL subdomain fallback: inherited p= should be reject");
++
++		(void) memset(utilized, '\0', sizeof utilized);
++		opendmarc_policy_fetch_utilized_domain(pctx, utilized, sizeof utilized);
++		CHECK(strcasecmp((char *)utilized, "parent1.example") == 0,
++		    "no-PSL subdomain fallback: utilized domain should be parent1.example");
++	}
++
++	pctx = opendmarc_policy_connect_shutdown(pctx);
++
++	/*
++	 * === Test 2 ===
++	 * Domain with its own DMARC record — no fallback should occur.
++	 */
++	pctx = opendmarc_policy_connect_init((u_char *)"1.2.3.4", 0);
++	if (pctx == NULL) { fprintf(stderr, "connect_init failed\n"); return 1; }
++
++	(void) opendmarc_policy_store_from_domain(pctx, (u_char *)"own-record.example");
++	status = opendmarc_policy_query_dmarc(pctx, (u_char *)"own-record.example");
++
++	CHECK(status == DMARC_PARSE_OKAY,
++	    "domain with own record: query should return DMARC_PARSE_OKAY");
++	if (status == DMARC_PARSE_OKAY)
++	{
++		opendmarc_policy_fetch_p(pctx, &p);
++		CHECK(p == DMARC_RECORD_P_QUARANTINE,
++		    "domain with own record: p= should be quarantine");
++	}
++
++	pctx = opendmarc_policy_connect_shutdown(pctx);
++
++	/*
++	 * === Test 3 ===
++	 * No PSL.  Deeply nested subdomain: a.b.parent3.example.
++	 * Neither _dmarc.b.parent3.example nor _dmarc.a.b.parent3.example exist.
++	 * Label walk should reach _dmarc.parent3.example (p=none, sp=reject).
++	 */
++	pctx = opendmarc_policy_connect_init((u_char *)"1.2.3.4", 0);
++	if (pctx == NULL) { fprintf(stderr, "connect_init failed\n"); return 1; }
++
++	(void) opendmarc_policy_store_from_domain(pctx, (u_char *)"a.b.parent3.example");
++	status = opendmarc_policy_query_dmarc(pctx, (u_char *)"a.b.parent3.example");
++
++	CHECK(status == DMARC_PARSE_OKAY,
++	    "no-PSL deeply nested subdomain: query should return DMARC_PARSE_OKAY");
++	if (status == DMARC_PARSE_OKAY)
++	{
++		opendmarc_policy_fetch_p(pctx, &p);
++		CHECK(p == DMARC_RECORD_P_NONE,
++		    "no-PSL deeply nested subdomain: p= from parent should be none");
++
++		(void) memset(utilized, '\0', sizeof utilized);
++		opendmarc_policy_fetch_utilized_domain(pctx, utilized, sizeof utilized);
++		CHECK(strcasecmp((char *)utilized, "parent3.example") == 0,
++		    "no-PSL deeply nested subdomain: utilized domain should be parent3.example");
++	}
++
++	pctx = opendmarc_policy_connect_shutdown(pctx);
++
++	/*
++	 * === Test 4 ===
++	 * No PSL.  No DMARC record at subdomain or any parent.
++	 * Should return DMARC_DNS_ERROR_NO_RECORD (graceful failure).
++	 */
++	pctx = opendmarc_policy_connect_init((u_char *)"1.2.3.4", 0);
++	if (pctx == NULL) { fprintf(stderr, "connect_init failed\n"); return 1; }
++
++	(void) opendmarc_policy_store_from_domain(pctx, (u_char *)"sub.nodmarc.example");
++	status = opendmarc_policy_query_dmarc(pctx, (u_char *)"sub.nodmarc.example");
++
++	CHECK(status == DMARC_DNS_ERROR_NO_RECORD,
++	    "no-PSL no record anywhere: should return DMARC_DNS_ERROR_NO_RECORD");
++
++	pctx = opendmarc_policy_connect_shutdown(pctx);
++
++	printf("Subdomain fallback (no PSL): pass=%d, fail=%d\n", pass, fails);
++
++	/*
++	 * === Tests 5-6: repeat key cases with PSL loaded ===
++	 *
++	 * With a PSL, opendmarc_get_tld() returns the correct organizational
++	 * domain (e.g. bcx.com for sub.bcx.com), so the existing PSL-based
++	 * code path is used rather than the new label-walking fallback.
++	 */
++	if (opendmarc_tld_read_file(TESTFILE, "//", "*.", "!") != 0)
++	{
++		printf("PSL tests: %s: could not read TLD file, skipping.\n", TESTFILE);
++	}
++	else
++	{
++		int psl_pass = 0, psl_fails = 0, psl_count = 0;
++
++#undef CHECK
++#define CHECK(cond, msg)	\
++		do {			\
++			psl_count++;	\
++			if (cond) {	\
++				psl_pass++;	\
++			} else {	\
++				printf("\t%s(%d): %s: FAIL\n", __FILE__, __LINE__, msg); \
++				psl_fails++;	\
++			}		\
++		} while (0)
++
++		/*
++		 * Test 5: PSL loaded; sub.bcx.com has no record, bcx.com has p=reject.
++		 * opendmarc_get_tld("sub.bcx.com") → "bcx.com" (different), so the
++		 * PSL path queries _dmarc.bcx.com and finds the record.
++		 */
++		pctx = opendmarc_policy_connect_init((u_char *)"1.2.3.4", 0);
++		if (pctx == NULL) { fprintf(stderr, "connect_init failed\n"); return 1; }
++
++		(void) opendmarc_policy_store_from_domain(pctx, (u_char *)"sub.bcx.com");
++		status = opendmarc_policy_query_dmarc(pctx, (u_char *)"sub.bcx.com");
++
++		CHECK(status == DMARC_PARSE_OKAY,
++		    "PSL subdomain fallback: query should return DMARC_PARSE_OKAY");
++		if (status == DMARC_PARSE_OKAY)
++		{
++			opendmarc_policy_fetch_p(pctx, &p);
++			CHECK(p == DMARC_RECORD_P_REJECT,
++			    "PSL subdomain fallback: inherited p= should be reject");
++
++			(void) memset(utilized, '\0', sizeof utilized);
++			opendmarc_policy_fetch_utilized_domain(pctx, utilized, sizeof utilized);
++			CHECK(strcasecmp((char *)utilized, "bcx.com") == 0,
++			    "PSL subdomain fallback: utilized domain should be bcx.com");
++		}
++
++		pctx = opendmarc_policy_connect_shutdown(pctx);
++
++		/*
++		 * Test 6: PSL loaded; domain with its own record — no fallback.
++		 */
++		pctx = opendmarc_policy_connect_init((u_char *)"1.2.3.4", 0);
++		if (pctx == NULL) { fprintf(stderr, "connect_init failed\n"); return 1; }
++
++		(void) opendmarc_policy_store_from_domain(pctx, (u_char *)"own-record.example");
++		status = opendmarc_policy_query_dmarc(pctx, (u_char *)"own-record.example");
++
++		CHECK(status == DMARC_PARSE_OKAY,
++		    "PSL domain with own record: query should return DMARC_PARSE_OKAY");
++		if (status == DMARC_PARSE_OKAY)
++		{
++			opendmarc_policy_fetch_p(pctx, &p);
++			CHECK(p == DMARC_RECORD_P_QUARANTINE,
++			    "PSL domain with own record: p= should be quarantine");
++		}
++
++		pctx = opendmarc_policy_connect_shutdown(pctx);
++
++		printf("Subdomain fallback (with PSL): pass=%d, fail=%d\n",
++		    psl_pass, psl_fails);
++
++		pass  += psl_pass;
++		fails += psl_fails;
++	}
++
++	printf("Subdomain fallback overall: pass=%d, fail=%d\n", pass, fails);
++	return fails;
++}
diff --git a/opendmarc.init b/opendmarc.init
new file mode 100644
index 0000000..4f7b8cb
--- /dev/null
+++ b/opendmarc.init
@@ -0,0 +1,99 @@
+#!/bin/sh
+# opendmarc	DMARC (Domain-based Message Authentication, Reporting & Conformance) milter
+# chkconfig:	345 86 14
+# description: OpenDMARC implements the DMARC milter spec for domain auth
+#              and a milter-based filter application that can plug in to
+#              any milter-aware MTA.
+# processname: opendmarc
+# pidfile:	/var/run/opendmarc/opendmarc.pid
+# config:	/etc/opendmarc.conf
+
+# Source function library
+. /etc/rc.d/init.d/functions
+
+prog="/usr/sbin/opendmarc"
+svname="opendmarc"
+
+sysconfig="/etc/sysconfig/$svname"
+lockfile="/var/lock/subsys/$svname"
+pidfile="/var/run/$svname/$svname.pid"
+conffile="/etc/$svname.conf"
+
+# Get service config
+[ -f $sysconfig ] && . $sysconfig
+
+start() {
+	# Check if the service is already running?
+	if [ ! -f $lockfile ]; then
+		msg_starting "$svname"
+		daemon $prog -c $conffile -P $pidfile $OPTIONS
+		RETVAL=$?
+		[ $RETVAL -eq 0 ] && touch $lockfile
+	else
+		msg_already_running "$svname"
+	fi
+}
+
+stop() {
+	# Stop daemons.
+	if [ -f $lockfile ]; then
+		msg_stopping "$svname"
+		killproc -p $pidfile $prog
+		RETVAL=$?
+		rm -f $lockfile $pidfile >/dev/null 2>&1
+	else
+		msg_not_running "$svname"
+	fi
+}
+
+reload() {
+	if [ -f $lockfile ]; then
+		msg_reloading "$svname"
+		killproc -p $pidfile $prog -USR1
+		RETVAL=$?
+	else
+		msg_not_running "$svname"
+		RETVAL=7
+	fi
+}
+
+condrestart() {
+	if [ ! -f $lockfile ]; then
+		msg_not_running "$svname"
+		RETVAL=$1
+		return
+	fi
+	stop
+	start
+}
+
+RETVAL=0
+# See how we were called.
+case "$1" in
+  start)
+	start
+	;;
+  stop)
+	stop
+	;;
+  restart)
+	stop
+	start
+	;;
+  try-restart)
+	condrestart 0
+	;;
+  force-reload|reload)
+	reload
+	;;
+  status)
+	status --pidfile $pidfile $svname
+	RETVAL=$?
+	;;
+  *)
+	msg_usage "$0 {start|stop|restart|try-restart|reload|force-reload|status}"
+	exit 3
+	;;
+esac
+
+exit $RETVAL
diff --git a/opendmarc.service b/opendmarc.service
new file mode 100644
index 0000000..b6854ad
--- /dev/null
+++ b/opendmarc.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Domain-based Message Authentication, Reporting & Conformance (DMARC) Milter
+Documentation=man:opendmarc(8) man:opendmarc.conf(5) man:opendmarc-import(8) man:opendmarc-reports(8)
+After=network.target nss-lookup.target
+
+[Service]
+Type=forking
+PIDFile=/var/run/opendmarc/opendmarc.pid
+EnvironmentFile=-/etc/sysconfig/opendmarc
+ExecStart=/usr/sbin/opendmarc -c /etc/opendmarc.conf -P /var/run/opendmarc/opendmarc.pid $OPTIONS
+ExecReload=/bin/kill -USR1 $MAINPID
+User=opendmarc
+Group=opendmarc
+
+[Install]
+WantedBy=multi-user.target
diff --git a/opendmarc.sysconfig b/opendmarc.sysconfig
new file mode 100644
index 0000000..81a9a24
--- /dev/null
+++ b/opendmarc.sysconfig
@@ -0,0 +1,5 @@
+# Configuration for the opendmarc init script.
+# Sourced by /etc/rc.d/init.d/opendmarc.
+
+# Extra arguments to pass to the daemon (in addition to -c and -P).
+#OPTIONS=
diff --git a/pld-defaults.patch b/pld-defaults.patch
new file mode 100644
index 0000000..14058c0
--- /dev/null
+++ b/pld-defaults.patch
@@ -0,0 +1,31 @@
+PLD-only: enable PLD-appropriate defaults in the shipped
+opendmarc.conf.sample.  HistoryFile lands under the per-package state
+directory; Socket and UserID are uncommented so the milter is usable
+out of the box.
+
+--- a/opendmarc/opendmarc.conf.sample
++++ b/opendmarc/opendmarc.conf.sample
+@@ -211,7 +211,7 @@
+ ##  rather periodically imported into a relational database from which the
+ ##  aggregate reports can be extracted by a tool such as opendmarc-import(8).
+ #
+-# HistoryFile /var/run/opendmarc.dat
++HistoryFile /var/run/opendmarc/opendmarc.dat
+
+ ##  HoldQuarantinedMessages { true | false }
+ ##  	default "false"
+@@ -357,7 +357,7 @@
+ ##  either in the configuration file or on the command line.  If an IP
+ ##  address is used, it must be enclosed in square brackets.
+ #
+-# Socket inet:8893 at localhost
++Socket inet:8893 at localhost
+
+ ##  SoftwareHeader { true | false }
+ ##  	default "false"
+@@ -436,4 +436,4 @@
+ ##  The process will be assigned all of the groups and primary group ID of
+ ##  the named userid unless an alternate group is specified.
+ #
+-# UserID opendmarc
++UserID opendmarc
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/opendmarc.git/commitdiff/3378ced5c2e467af89be417e783fc592a0d3cba8



More information about the pld-cvs-commit mailing list