]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
3559. [func] Check that both forms of Sender Policy Framework
authorMark Andrews <marka@isc.org>
Tue, 30 Apr 2013 03:49:41 +0000 (13:49 +1000)
committerMark Andrews <marka@isc.org>
Tue, 30 Apr 2013 03:49:41 +0000 (13:49 +1000)
                        records exist or do not exist. [RT #33355]

17 files changed:
CHANGES
bin/check/named-checkconf.c
bin/check/named-checkzone.c
bin/check/named-checkzone.docbook
bin/named/config.c
bin/named/zoneconf.c
bin/tests/system/checkzone/tests.sh
bin/tests/system/checkzone/zones/spf.db [new file with mode: 0644]
bin/tests/system/conf.sh.in
bin/tests/system/spf/clean.sh [new file with mode: 0644]
bin/tests/system/spf/ns1/named.conf [new file with mode: 0644]
bin/tests/system/spf/ns1/spf.db [new file with mode: 0644]
bin/tests/system/spf/tests.sh [new file with mode: 0644]
doc/arm/Bv9ARM-book.xml
lib/dns/include/dns/zone.h
lib/dns/zone.c
lib/isccfg/namedconf.c

diff --git a/CHANGES b/CHANGES
index e50573af38e084f531874305435382120a6a90af..9863b78bfef827dee1e4514bf35761dd44b2595c 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -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]
index 53e6091fb9938523be153e13582ee7b18b794a4c..6e832dd6c3e0a28243d32d5fae99ec5a164fd034 100644 (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) {
index 8d978569e33cc9916f79050d26a0a62804139677..a13cfa7d5c02cf8728ad7f16260761fd34c71177 100644 (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;
index 934993dfce1d2d0d3943a048538cfc278d4c33eb..98d3fc3ff97c445a1449c743e7a3e35b8ab4e6d7 100644 (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>
       <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>
index 2de1db3786c674eeb850d1e00541948720a304de..93cbd460f64a740a6abf71cb0dab16b984543d27 100644 (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\
index cffe4c5c71f7e48de53da86ab45076f8018fc6d2..47565c7a9764da5d326c91157659471f3b7798b7 100644 (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);
index 59643b12cf8bb6418a06d8a394396a676aad48d8..a727b411c8573018ececc73a5ad6149fd5d413b6 100644 (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`
 
diff --git a/bin/tests/system/checkzone/zones/spf.db b/bin/tests/system/checkzone/zones/spf.db
new file mode 100644 (file)
index 0000000..ffa850a
--- /dev/null
@@ -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"
index e505d74d7ff9f38510f7da68fcf699ef14a7ad42..3093512d60f0d146dcc6ea7624e888b6dfd3bc5c 100644 (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"
 
diff --git a/bin/tests/system/spf/clean.sh b/bin/tests/system/spf/clean.sh
new file mode 100644 (file)
index 0000000..9c95122
--- /dev/null
@@ -0,0 +1,2 @@
+rm -f ns1/named.run
+rm -f ns1/named.memstats
diff --git a/bin/tests/system/spf/ns1/named.conf b/bin/tests/system/spf/ns1/named.conf
new file mode 100644 (file)
index 0000000..7d5dcfb
--- /dev/null
@@ -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;
+};
diff --git a/bin/tests/system/spf/ns1/spf.db b/bin/tests/system/spf/ns1/spf.db
new file mode 100644 (file)
index 0000000..ffa850a
--- /dev/null
@@ -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"
diff --git a/bin/tests/system/spf/tests.sh b/bin/tests/system/spf/tests.sh
new file mode 100644 (file)
index 0000000..6acd283
--- /dev/null
@@ -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
index c8fc5cfbc51d6cf0b0b41767a4a74ee7e0bbaf00..2df02e586d1635c6ce992d97ec0e3399e377711d 100644 (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>
index cedd63e05279a1990ccf0fa68b0035e1df11b012..1d1c6b3caed0213a6e560a1b18bdf0c135839093 100644 (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
 /*
index 15f573a708a9749443669f2271883268301b1ad5..592355a2b81a8d5a9ba5302ac38a64caff7cadcd 100644 (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);
index c65727f341d8fff6f156ce2c52bc93e2c56c47e3..b2c2946ca5c3d8b2b5cd2ca9131b5f6a1e7df0d4 100644 (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 },