]> 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:53:43 +0000 (13:53 +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 3f653ad4cb458edbb311f4d54a55f00f928edd58..5219b5520863911ec773650568c28094c96dbd56 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 c059db49452ed013ad494e62136e8f9c5cfa2e96..5e95034179f1db4296cb10157433fbb5ec9450d7 100644 (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;
index 85479e7d90169d99bbb58659c18bbca1cbe61b07..4d6413a2ff1ba9237f445e315c5d270a9ccf2e39 100644 (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>
       <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 b9b8dd810394e2c0ff28dee9bff2cf46fae2c718..fa349eeade2fe9aa5a94f195052eee348ee96a0b 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 839312c13d7eb62a2108e2ab83ecbd1a8d3cf029..7f36b143d4e528048125d7a28e7b6d255ea44a96 100644 (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);
index 757ba2e955207264a6fb3dc4654632cbafc4b21d..2353c145e7bc11d810c65e942cb339dd94fc6719 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 b8debdb83d2b3365a495dcf24961adb5d52dca37..2aadf9fc5f89bc32543511a1be170a3c342c515e 100644 (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.
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 4eb1ca33aeaf298b27124c88bb2abcae20d9dd0e..975f9202ba124804761688a2077c8759f4845219 100644 (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>
index 326d920fe32978870a1a1d29f898e194c8da5ac2..f91801f6fe3f542b2af989c6d9b42174a2d538da 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 8bc67e4e0b16026f474fcd7a307ce65c38f86115..00c569381ee5cc7f66844035d3f1631933338537 100644 (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);
index 854d1b4e5081c4330ab9d93629229b7451db5f71..431af746338b18378dcbe4ac6d69b8d3ed745c6b 100644 (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 },