]> 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 04:06:50 +0000 (14:06 +1000)
committerMark Andrews <marka@isc.org>
Tue, 30 Apr 2013 04:06:50 +0000 (14:06 +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 6200477c2be4fa30de1630647fe03c0ddd997de9..113074833df304ab36b6bda05276c40bfecba872 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 c7587c5c788217ebc7799e478ec1e5e267b415d1..a8911a1865bfd66513425528f9f9a06be6b5eff9 100644 (file)
@@ -276,6 +276,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 a9bf3a8bf77c73b6aad1cb4bd0e4c8f9326f133e..e64cf347e2e7b73c0a3a7cefe184cf2903f0603e 100644 (file)
@@ -144,17 +144,19 @@ main(int argc, char **argv) {
        if (progmode == progmode_compile) {
                zone_options |= (DNS_ZONEOPT_CHECKNS |
                                 DNS_ZONEOPT_FATALNS |
+                                DNS_ZONEOPT_CHECKSPF |
                                 DNS_ZONEOPT_CHECKNAMES |
                                 DNS_ZONEOPT_CHECKNAMESFAIL |
                                 DNS_ZONEOPT_CHECKWILDCARD);
-       }
+       } else
+               zone_options |= 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:qs:t:o:vw:DF:M:S:W:"))
+                                      "c:df:hi:jk:m:n:qs:t:o:vw:DF:M:S:T:W:"))
               != EOF) {
                switch (c) {
                case 'c':
@@ -343,6 +345,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 b6df56dd6eed3c5b20d823174d32cfdd90cab446..2aec66da32d8dcecf09938519ee97551215fa936 100644 (file)
@@ -73,6 +73,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>
@@ -96,6 +97,7 @@
       <arg><option>-o <replaceable class="parameter">filename</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 464c74a76e0ca20605fa7a305a83f0221303358d..87a9f20f028db011ae0a328350e71f20ea5b9e51 100644 (file)
@@ -136,6 +136,7 @@ options {\n\
        check-names slave warn;\n\
        check-names response ignore;\n\
        check-mx warn;\n\
+       check-spf warn;\n\
        acache-enable no;\n\
        acache-cleaning-interval 60;\n\
        max-acache-size 16M;\n\
index 68836c936d72da4b2fb631e5e9033f26f7667dfe..0990e28991bbd389326e0a6ed291bc0de5bb86e0 100644 (file)
@@ -724,6 +724,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 6155bbc9f0a452caa821064021baddefcb4c7202..c692921e92394034bca9d6a8d3b744d674b2f57a 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 c3360bdea57427b8c57505918c8ef2bb9c99d7f5..da095fb150ae08e3583dc0940067ce3a0abd6efe 100644 (file)
@@ -47,7 +47,7 @@ SUBDIRS="acl additional allow_query builtin cacheclean checkconf
         checknames checkzone database dlv dlz dname dnssec formerr
         forward glue ixfr limits logfileconfig lwresd masterfile
         masterformat notify nsupdate pending resolver rndc rrsetorder
-        sortlist stub tkey unknown upforwd views wildcard xfer
+        spf sortlist stub tkey unknown upforwd 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 d2e52776d2f357f0599795e868e76d54a08fb5ce..b3e758a9cc5158426411dda8b768bc4e4ad99525 100644 (file)
@@ -4839,6 +4839,7 @@ category notify { null; };
     <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-notify { <replaceable>address_match_list</replaceable> }; </optional>
     <optional> allow-query { <replaceable>address_match_list</replaceable> }; </optional>
     <optional> allow-query-on { <replaceable>address_match_list</replaceable> }; </optional>
@@ -6160,6 +6161,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>
 
@@ -6195,6 +6202,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>
@@ -8931,6 +8951,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>
@@ -9426,6 +9447,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 5bfe1485c0f879971bce48a6331d6caf28f2dfa8..29a4b9807769de7dc3e4648ff35dde16d97926ed 100644 (file)
@@ -70,6 +70,7 @@ typedef enum {
 #define DNS_ZONEOPT_TRYTCPREFRESH 0x01000000U  /*%< try tcp refresh on udp failure */
 #define DNS_ZONEOPT_NOTIFYTOSOA          0x02000000U   /*%< Notify the SOA MNAME */
 #define DNS_ZONEOPT_NSEC3TESTZONE 0x04000000U  /*%< nsec3-test-zone */
+#define DNS_ZONEOPT_CHECKSPF     0x80000000U   /*%< check SPF records */
 
 #ifndef NOMINUM_PUBLIC
 /*
index 60e636f664b27961fda7e40467600c2ba93950aa..3b2b828cb89dfb9868faacea222464918a39c543 100644 (file)
@@ -2012,6 +2012,30 @@ zone_check_glue(dns_zone_t *zone, dns_db_t *db, dns_name_t *name,
        return (answer);
 }
 
+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;
@@ -2026,7 +2050,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);
@@ -2103,7 +2127,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);
@@ -2116,6 +2140,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 80cc5af5acb7cd727fa11a982a03dc093a0cbf15..5303d20bccba7014a125f9dcb2826ce8cc9e41ff 100644 (file)
@@ -392,6 +392,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 },
@@ -924,6 +930,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 },