+3559. [func] Check that both forms of Sender Policy Framework
+ records exist or do not exist. [RT #33355]
+
3558. [bug] IXFR of a DLZ stored zone was broken. [RT #33331]
3556. [maint] Added AAAA for D.ROOT-SERVERS.NET.
zone_options &= ~DNS_ZONEOPT_CHECKSIBLING;
}
+ obj = NULL;
+ if (get_maps(maps, "check-spf", &obj)) {
+ if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
+ zone_options |= DNS_ZONEOPT_CHECKSPF;
+ } else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
+ zone_options &= ~DNS_ZONEOPT_CHECKSPF;
+ } else
+ INSIST(0);
+ } else {
+ zone_options |= DNS_ZONEOPT_CHECKSPF;
+ }
+
obj = NULL;
if (get_checknames(maps, &obj)) {
if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
if (progmode == progmode_compile) {
zone_options |= (DNS_ZONEOPT_CHECKNS |
DNS_ZONEOPT_FATALNS |
+ DNS_ZONEOPT_CHECKSPF |
DNS_ZONEOPT_CHECKDUPRR |
DNS_ZONEOPT_CHECKNAMES |
DNS_ZONEOPT_CHECKNAMESFAIL |
DNS_ZONEOPT_CHECKWILDCARD);
} else
- zone_options |= DNS_ZONEOPT_CHECKDUPRR;
+ zone_options |= (DNS_ZONEOPT_CHECKDUPRR |
+ DNS_ZONEOPT_CHECKSPF);
#define ARGCMP(X) (strcmp(isc_commandline_argument, X) == 0)
isc_commandline_errprint = ISC_FALSE;
while ((c = isc_commandline_parse(argc, argv,
- "c:df:hi:jk:m:n:qr:s:t:o:vw:DF:M:S:W:"))
+ "c:df:hi:jk:m:n:qr:s:t:o:vw:DF:M:S:T:W:"))
!= EOF) {
switch (c) {
case 'c':
}
break;
+ case 'T':
+ if (ARGCMP("warn")) {
+ zone_options |= DNS_ZONEOPT_CHECKSPF;
+ } else if (ARGCMP("ignore")) {
+ zone_options &= ~DNS_ZONEOPT_CHECKSPF;
+ } else {
+ fprintf(stderr, "invalid argument to -T: %s\n",
+ isc_commandline_argument);
+ exit(1);
+ }
+ break;
+
case 'W':
if (ARGCMP("warn"))
zone_options |= DNS_ZONEOPT_CHECKWILDCARD;
<arg><option>-s <replaceable class="parameter">style</replaceable></option></arg>
<arg><option>-S <replaceable class="parameter">mode</replaceable></option></arg>
<arg><option>-t <replaceable class="parameter">directory</replaceable></option></arg>
+ <arg><option>-T <replaceable class="parameter">mode</replaceable></option></arg>
<arg><option>-w <replaceable class="parameter">directory</replaceable></option></arg>
<arg><option>-D</option></arg>
<arg><option>-W <replaceable class="parameter">mode</replaceable></option></arg>
<arg><option>-r <replaceable class="parameter">mode</replaceable></option></arg>
<arg><option>-s <replaceable class="parameter">style</replaceable></option></arg>
<arg><option>-t <replaceable class="parameter">directory</replaceable></option></arg>
+ <arg><option>-T <replaceable class="parameter">mode</replaceable></option></arg>
<arg><option>-w <replaceable class="parameter">directory</replaceable></option></arg>
<arg><option>-D</option></arg>
<arg><option>-W <replaceable class="parameter">mode</replaceable></option></arg>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>-T <replaceable class="parameter">mode</replaceable></term>
+ <listitem>
+ <para>
+ Check if Sender Policy Framework records (TXT and SPF)
+ both exist or both don't exist. A warning is issued
+ if they don't match. Possible modes are
+ <command>"warn"</command> (default), <command>"ignore"</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term>-w <replaceable class="parameter">directory</replaceable></term>
<listitem>
check-names response ignore;\n\
check-dup-records warn;\n\
check-mx warn;\n\
+ check-spf warn;\n\
acache-enable no;\n\
acache-cleaning-interval 60;\n\
max-acache-size 16M;\n\
dns_zone_setoption(zone, DNS_ZONEOPT_CHECKSIBLING,
cfg_obj_asboolean(obj));
+ obj = NULL;
+ result = ns_config_get(maps, "check-spf", &obj);
+ INSIST(result == ISC_R_SUCCESS && obj != NULL);
+ if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
+ check = ISC_TRUE;
+ } else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
+ check = ISC_FALSE;
+ } else
+ INSIST(0);
+ dns_zone_setoption(zone, DNS_ZONEOPT_CHECKSPF, check);
+
obj = NULL;
result = ns_config_get(maps, "zero-no-soa-ttl", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
status=`expr $status + $ret`
done
+echo "I:checking with spf warnings ($n)"
+ret=0
+$CHECKZONE example zones/spf.db > test.out1.$n 2>&1 || ret=1
+$CHECKZONE -T ignore example zones/spf.db > test.out2.$n 2>&1 || ret=1
+grep "'x.example' found SPF/TXT" test.out1.$n > /dev/null || ret=1
+grep "'y.example' found SPF/SPF" test.out1.$n > /dev/null || ret=1
+grep "'example' found SPF/" test.out1.$n > /dev/null && ret=1
+grep "'x.example' found SPF/" test.out2.$n > /dev/null && ret=1
+grep "'y.example' found SPF/" test.out2.$n > /dev/null && ret=1
+grep "'example' found SPF/" test.out2.$n > /dev/null && ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
exit $status
--- /dev/null
+; Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+@ 0 IN SOA . . 0 0 0 0 0
+@ 0 IN NS .
+@ 0 IN TXT "v=spf1 -all"
+@ 0 IN SPF "v=spf1 -all"
+x 0 IN TXT "v=spf1"
+y 0 IN SPF "v=spf1"
+y 0 IN TXT "a non spf record"
dlvauto dlz dlzexternal dname dns64 dnssec ecdsa formerr
forward glue gost ixfr limits logfileconfig lwresd masterfile
masterformat metadata notify nsupdate pending pkcs11
- resolver rndc rpz rrsetorder sortlist smartsign staticstub
+ resolver rndc rpz rrsetorder sortlist smartsign spf staticstub
stub tkey tsig tsiggss unknown upforwd views wildcard xfer
xferquota zonechecks"
--- /dev/null
+rm -f ns1/named.run
+rm -f ns1/named.memstats
--- /dev/null
+/*
+ * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+controls { /* empty */ };
+
+options {
+ query-source address 10.53.0.1;
+ notify-source 10.53.0.1;
+ transfer-source 10.53.0.1;
+ port 5300;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion no;
+ notify yes;
+ ixfr-from-differences yes;
+};
+
+zone "spf" {
+ type master;
+ file "spf.db";
+};
+
+zone "warn" {
+ type master;
+ file "spf.db";
+ check-spf warn;
+};
+
+zone "nowarn" {
+ type master;
+ file "spf.db";
+ check-spf ignore;
+};
--- /dev/null
+; Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+@ 0 IN SOA . . 0 0 0 0 0
+@ 0 IN NS .
+@ 0 IN TXT "v=spf1 -all"
+@ 0 IN SPF "v=spf1 -all"
+x 0 IN TXT "v=spf1"
+y 0 IN SPF "v=spf1"
+y 0 IN TXT "a non spf record"
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+n=1
+status=0
+
+echo "I:checking that SPF warnings have been correctly generated ($n)"
+ret=0
+
+grep "zone spf/IN: loaded serial 0" ns1/named.run > /dev/null || ret=1
+grep "'x.spf' found SPF/TXT" ns1/named.run > /dev/null || ret=1
+grep "'y.spf' found SPF/SPF" ns1/named.run > /dev/null || ret=1
+grep "'spf' found SPF/" ns1/named.run > /dev/null && ret=1
+
+grep "zone warn/IN: loaded serial 0" ns1/named.run > /dev/null || ret=1
+grep "'x.warn' found SPF/TXT" ns1/named.run > /dev/null || ret=1
+grep "'y.warn' found SPF/SPF" ns1/named.run > /dev/null || ret=1
+grep "'warn' found SPF/" ns1/named.run > /dev/null && ret=1
+
+grep "zone nowarn/IN: loaded serial 0" ns1/named.run > /dev/null || ret=1
+grep "'x.nowarn' found SPF/" ns1/named.run > /dev/null && ret=1
+grep "'y.nowarn' found SPF/" ns1/named.run > /dev/null && ret=1
+grep "'nowarn' found SPF/" ns1/named.run > /dev/null && ret=1
+n=`expr $n + 1`
+if [ $ret != 0 ]; then echo "I:failed"; fi
+status=`expr $status + $ret`
+
+echo "I:exit status: $status"
+exit $status
<optional> check-mx-cname ( <replaceable>warn</replaceable> | <replaceable>fail</replaceable> | <replaceable>ignore</replaceable> ); </optional>
<optional> check-srv-cname ( <replaceable>warn</replaceable> | <replaceable>fail</replaceable> | <replaceable>ignore</replaceable> ); </optional>
<optional> check-sibling <replaceable>yes_or_no</replaceable>; </optional>
+ <optional> check-spf ( <replaceable>warn</replaceable> | <replaceable>fail</replaceable> | <replaceable>ignore</replaceable> ); </optional>
<optional> allow-new-zones { <replaceable>yes_or_no</replaceable> }; </optional>
<optional> allow-notify { <replaceable>address_match_list</replaceable> }; </optional>
<optional> allow-query { <replaceable>address_match_list</replaceable> }; </optional>
checks use <command>named-checkzone</command>).
The default is <command>yes</command>.
</para>
+ <para>
+ Check that the two forms of Sender Policy Framework
+ records (TXT and SPF) either both exist or both
+ don't exist. Warnings are emitted it they don't
+ and be suppressed with <command>check-spf</command>.
+ </para>
</listitem>
</varlistentry>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><command>check-spf</command></term>
+ <listitem>
+ <para>
+ When performing integrity checks, check that the
+ two forms of Sender Policy Framwork records (TXT
+ and SPF) both exist or both don't exist and issue
+ a warning if not met. The default is
+ <command>warn</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><command>zero-no-soa-ttl</command></term>
<listitem>
<optional> check-names (<constant>warn</constant>|<constant>fail</constant>|<constant>ignore</constant>) ; </optional>
<optional> check-mx (<constant>warn</constant>|<constant>fail</constant>|<constant>ignore</constant>) ; </optional>
<optional> check-wildcard <replaceable>yes_or_no</replaceable>; </optional>
+ <optional> check-spf ( <replaceable>warn</replaceable> | <replaceable>fail</replaceable> | <replaceable>ignore</replaceable> ); </optional>
<optional> check-integrity <replaceable>yes_or_no</replaceable> ; </optional>
<optional> dialup <replaceable>dialup_option</replaceable> ; </optional>
<optional> file <replaceable>string</replaceable> ; </optional>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><command>check-spf</command></term>
+ <listitem>
+ <para>
+ See the description of
+ <command>check-spf</command> in <xref linkend="boolean_options"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><command>check-wildcard</command></term>
<listitem>
#define DNS_ZONEOPT_DNSKEYKSKONLY 0x10000000U /*%< dnssec-dnskey-kskonly */
#define DNS_ZONEOPT_CHECKDUPRR 0x20000000U /*%< check-dup-records */
#define DNS_ZONEOPT_CHECKDUPRRFAIL 0x40000000U /*%< fatal check-dup-records failures */
+#define DNS_ZONEOPT_CHECKSPF 0x80000000U /*%< check SPF records */
#ifndef NOMINUM_PUBLIC
/*
return (ok);
}
+static isc_boolean_t
+isspf(const dns_rdata_t *rdata) {
+ char buf[1024];
+ const unsigned char *data = rdata->data;
+ unsigned int rdl = rdata->length, i = 0, tl, len;
+
+ while (rdl > 0U) {
+ len = tl = *data;
+ ++data;
+ --rdl;
+ INSIST(tl <= rdl);
+ if (len > sizeof(buf) - i - 1)
+ len = sizeof(buf) - i - 1;
+ memcpy(buf + i, data, len);
+ i += len;
+ data += tl;
+ rdl -= tl;
+ }
+ buf[i] = 0;
+ if (strncmp(buf, "v=spf1", 6) == 0 && (buf[6] == 0 || buf[6] == ' '))
+ return (ISC_TRUE);
+ return (ISC_FALSE);
+}
+
static isc_boolean_t
integrity_checks(dns_zone_t *zone, dns_db_t *db) {
dns_dbiterator_t *dbiterator = NULL;
dns_name_t *name;
dns_name_t *bottom;
isc_result_t result;
- isc_boolean_t ok = ISC_TRUE;
+ isc_boolean_t ok = ISC_TRUE, have_spf, have_txt;
dns_fixedname_init(&fixed);
name = dns_fixedname_name(&fixed);
result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_srv,
0, 0, &rdataset, NULL);
if (result != ISC_R_SUCCESS)
- goto next;
+ goto checkspf;
result = dns_rdataset_first(&rdataset);
while (result == ISC_R_SUCCESS) {
dns_rdataset_current(&rdataset, &rdata);
}
dns_rdataset_disassociate(&rdataset);
+ checkspf:
+ /*
+ * Check if there is a type TXT spf record without a type SPF
+ * RRset being present.
+ */
+ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKSPF))
+ goto next;
+ if (zone->rdclass != dns_rdataclass_in)
+ goto next;
+ have_spf = have_txt = ISC_FALSE;
+ result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_spf,
+ 0, 0, &rdataset, NULL);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&rdataset);
+ have_spf = ISC_TRUE;
+ }
+ result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_txt,
+ 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS)
+ goto notxt;
+ result = dns_rdataset_first(&rdataset);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(&rdataset, &rdata);
+ have_txt = isspf(&rdata);
+ dns_rdata_reset(&rdata);
+ if (have_txt)
+ break;
+ result = dns_rdataset_next(&rdataset);
+ }
+ dns_rdataset_disassociate(&rdataset);
+
+ notxt:
+ if (have_spf != have_txt) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ const char *found = have_txt ? "TXT" : "SPF";
+ const char *need = have_txt ? "SPF" : "TXT";
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_zone_log(zone, ISC_LOG_WARNING, "'%s' found SPF/%s "
+ "record but no SPF/%s record found, add "
+ "matching type %s record", namebuf, found,
+ need, need);
+ }
+
next:
dns_db_detachnode(db, &node);
result = dns_dbiterator_next(dbiterator);
&cfg_rep_string, &checkmode_enums
};
+static const char *warn_enums[] = { "warn", "ignore", NULL };
+static cfg_type_t cfg_type_warn = {
+ "warn", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &warn_enums
+};
+
static cfg_tuplefielddef_t checknames_fields[] = {
{ "type", &cfg_type_checktype, 0 },
{ "mode", &cfg_type_checkmode, 0 },
{ "check-mx", &cfg_type_checkmode, 0 },
{ "check-mx-cname", &cfg_type_checkmode, 0 },
{ "check-sibling", &cfg_type_boolean, 0 },
+ { "check-spf", &cfg_type_warn, 0 },
{ "check-srv-cname", &cfg_type_checkmode, 0 },
{ "check-wildcard", &cfg_type_boolean, 0 },
{ "dialup", &cfg_type_dialuptype, 0 },