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

records exist or do not exist. [RT #33355]
This commit is contained in:
Mark Andrews 2013-04-30 13:49:41 +10:00
parent e658a6635d
commit 26bb3b7a67
17 changed files with 318 additions and 5 deletions

View file

@ -1,3 +1,6 @@
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]

View file

@ -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) {

View file

@ -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;

View file

@ -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>

View file

@ -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\

View file

@ -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);

View file

@ -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`

View file

@ -0,0 +1,21 @@
; 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"

View file

@ -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"

View file

@ -0,0 +1,2 @@
rm -f ns1/named.run
rm -f ns1/named.memstats

View file

@ -0,0 +1,47 @@
/*
* 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;
};

View file

@ -0,0 +1,21 @@
; 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"

View file

@ -0,0 +1,45 @@
#!/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

View file

@ -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>

View file

@ -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
/*

View file

@ -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);

View file

@ -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 },