Commit 26bb3b7a authored by Mark Andrews's avatar Mark Andrews

3559. [func] Check that both forms of Sender Policy Framework

                        records exist or do not exist. [RT #33355]
parent e658a663
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]
3557. [bug] Reloading redirect zones was broken. [RT #33292]
......
......@@ -294,6 +294,18 @@ configure_zone(const char *vclass, const char *view,
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) {
......
......@@ -155,19 +155,21 @@ main(int argc, char **argv) {
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:jJ:k:L:m:n:qr:s:t:o:vw:DF:M:S:W:"))
"c:df:hi:jJ:k:L:m:n:qr:s:t:o:vw:DF:M:S:T:W:"))
!= EOF) {
switch (c) {
case 'c':
......@@ -389,6 +391,18 @@ main(int argc, char **argv) {
}
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;
......
......@@ -80,6 +80,7 @@
<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>
......@@ -105,6 +106,7 @@
<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>
......@@ -419,6 +421,18 @@
</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>
......
......@@ -151,6 +151,7 @@ options {\n\
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\
......
......@@ -1259,6 +1259,17 @@ ns_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
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);
......
......@@ -50,6 +50,21 @@ cmp -s test.changed.db test.out1.db || ret=1
mv -f test.orig.db.jnl test.journal
$CHECKZONE -D -J test.journal -o test.out2.db test test.orig.db > /dev/null 2>&1 || ret=1
cmp -s test.changed.db test.out2.db || ret=1
n=`expr $n + 1`
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
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`
......
; 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"
......@@ -64,7 +64,7 @@ SUBDIRS="acl additional allow_query addzone autosign builtin
dsdigest dscp ecdsa formerr forward glue gost ixfr inline limits
logfileconfig lwresd masterfile masterformat metadata
notify nsupdate pending pkcs11 redirect resolver rndc rpz
rrl rrsetorder rsabigexponent sortlist smartsign staticstub
rrl rrsetorder rsabigexponent smartsign sortlist spf staticstub
statistics stub tkey tsig tsiggss unknown upforwd verify
views wildcard xfer xferquota zonechecks"
......
rm -f ns1/named.run
rm -f ns1/named.memstats
/*
* 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;
};
; 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"
#!/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
......@@ -5343,6 +5343,7 @@ badresp:1,adberr:0,findfail:0,valfail:0]
<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>
......@@ -7212,6 +7213,12 @@ options {
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>
......@@ -7247,6 +7254,19 @@ options {
</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>
......@@ -10982,6 +11002,7 @@ view "external" {
<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>
......@@ -11631,6 +11652,16 @@ zone <replaceable>zone_name</replaceable> <optional><replaceable>class</replacea
</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>
......
......@@ -87,6 +87,7 @@ typedef enum {
#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
/*
......
......@@ -2610,6 +2610,30 @@ zone_check_dup(dns_zone_t *zone, dns_db_t *db) {
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;
......@@ -2624,7 +2648,7 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) {
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);
......@@ -2702,7 +2726,7 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) {
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);
......@@ -2715,6 +2739,50 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) {
}
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);
......
......@@ -549,6 +549,12 @@ static cfg_type_t cfg_type_checkmode = {
&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 },
......@@ -1588,6 +1594,7 @@ zone_clauses[] = {
{ "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 },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment