]> 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:55:38 +0000 (13:55 +1000)
                        records exist or do not exist. [RT #33355]
(cherry picked from commit 26bb3b7a67b833f0a18072567de036226890ca1a)

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 73120a632da83b45a33662d9c08451eca957a41f..466ef0a706776b9312e40cc19e3b45da7acd55ac 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]
 
 3556.  [maint]         Added AAAA for D.ROOT-SERVERS.NET.
index 49038503c5fa584d2dc662c8cea9e89a691717f1..07710e7ec3fdeaf6ad9e45679a00fd02c8134085 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 ce48200953c2761f8f5b919e1d6fae1592bfed85..0ddb606bedfa576f29ddb4165499f30fa15efdf4 100644 (file)
@@ -145,19 +145,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: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':
@@ -363,6 +365,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 33dc15e47095c1f2ed87a58041c6429addf1c1bd..0c7b9ae48698baa86e33848dabefbd35b723c870 100644 (file)
@@ -75,6 +75,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>
@@ -98,6 +99,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>
         </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 2cf6dd3fe3f7b7b16fa5ce927f42bf96128ad6aa..48bcee6b6b05d1edba853debf2aa42f2f2c40f5d 100644 (file)
@@ -150,6 +150,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 2e20e28a0f690e234af4f0b1a492a71c7959cb7a..404c238f02a476a72564c03b9cfc83060175bc29 100644 (file)
@@ -1122,6 +1122,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 aa9ccba9c5295095db3c053682b7b1cccb93623a..90f7f53309224458b7955a7fa8180959e081a940 100644 (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
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 b5c6b85180c48b057134b813165a60b32b92b568..16ade3468d8abf42742bd28f1ab8599be38197eb 100644 (file)
@@ -59,7 +59,7 @@ SUBDIRS="acl additional allow_query addzone autosign builtin
         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"
 
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 4a81a81ea3722070ed2c5f10a016d3cd2045ab0c..f789f0277401a7169e55e46ae35b6171867db699 100644 (file)
@@ -5099,6 +5099,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>
@@ -6848,6 +6849,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>
 
@@ -6883,6 +6890,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>
@@ -10268,6 +10288,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>
@@ -10833,6 +10854,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 35b3efe572823542ebb1030cdbeb31364f2a616e..6b9911d5897daabf46fe6c41093d298ed8e80c01 100644 (file)
@@ -78,6 +78,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 c7d574bcd8c820ba5a819bd5ddf142f3e1489235..6269f99f50b74683714c9c11be2037e66fe55148 100644 (file)
@@ -2217,6 +2217,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;
@@ -2231,7 +2255,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);
@@ -2309,7 +2333,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);
@@ -2322,6 +2346,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 c6303d48690ade08e1af7b90326ed04283669b51..287ce141f4c7eb5dfcf7e8bbbb794985a19dac4c 100644 (file)
@@ -526,6 +526,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 },
@@ -1461,6 +1467,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 },