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

records exist or do not exist. [RT #33355]
(cherry picked from commit 26bb3b7a67)
This commit is contained in:
Mark Andrews 2013-04-30 13:49:41 +10:00
parent acfbdd2539
commit 7366376f57
17 changed files with 317 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

@ -150,19 +150,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:jk:L:m:n:qr:s:t:o:vw:DF:M:S:W:"))
"c:df:hi:jk:L:m:n:qr:s:t:o:vw:DF:M:S:T:W:"))
!= EOF) {
switch (c) {
case 'c':
@ -379,6 +381,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

@ -77,6 +77,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>
@ -101,6 +102,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>
@ -401,6 +403,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

@ -1229,6 +1229,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

@ -40,4 +40,18 @@ do
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

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

@ -63,7 +63,7 @@ SUBDIRS="acl additional allow_query addzone autosign builtin
formerr forward glue gost ixfr inline limits logfileconfig
lwresd masterfile masterformat metadata notify nsupdate pending
pkcs11 redirect resolver rndc rpz rrsetorder rsabigexponent
sortlist smartsign staticstub stub tkey tsig tsiggss unknown
smartsign sortlist spf staticstub stub tkey tsig tsiggss unknown
upforwd verify views wildcard xfer xferquota zonechecks"
# PERL will be an empty string if no perl interpreter was found.

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

@ -5217,6 +5217,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>
@ -7019,6 +7020,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>
@ -7054,6 +7061,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>
@ -10529,6 +10549,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>
@ -11174,6 +11195,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

@ -2472,6 +2472,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;
@ -2486,7 +2510,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);
@ -2564,7 +2588,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);
@ -2577,6 +2601,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

@ -528,6 +528,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 },
@ -1493,6 +1499,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 },