Add options dnssec_request_domains, dnssec_require_domains to the dnslookup router
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 20 Apr 2014 15:44:52 +0000 (16:44 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 20 Apr 2014 16:52:22 +0000 (17:52 +0100)
Note there are no testsuite cases included.

TODO in this area:
- dnssec during verify-callouts
- dnssec during dnsdb expansions
- dnssec on the forward lookup of a verify=helo and verify=reverse_host_lookup
- observability of status of requested dnssec

12 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/src/dns.c
src/src/functions.h
src/src/host.c
src/src/lookups/dnsdb.c
src/src/match.c
src/src/routers/dnslookup.c
src/src/routers/dnslookup.h
src/src/routers/rf_lookup_hostlist.c
src/src/transports/smtp.c
src/src/verify.c

index 8609029..68ebf8a 100644 (file)
@@ -17622,6 +17622,29 @@ when there is a DNS lookup error.
 
 
 
+.option dnssec_request_domains dnslookup "domain list&!!" unset
+.cindex "MX record" "security"
+.cindex "DNSSEC" "MX lookup"
+.cindex "security" "MX lookup"
+.cindex "DNS" "DNSSEC"
+DNS lookups for domains matching &%dnssec_request_domains%& will be done with
+the dnssec request bit set.
+This applies to all of the SRV, MX A6, AAAA, A lookup sequence.
+
+
+
+.option dnssec_require_domains dnslookup "domain list&!!" unset
+.cindex "MX record" "security"
+.cindex "DNSSEC" "MX lookup"
+.cindex "security" "MX lookup"
+.cindex "DNS" "DNSSEC"
+DNS lookups for domains matching &%dnssec_request_domains%& will be done with
+the dnssec request bit set.  Any returns not having the Authenticated Data bit
+(AD bit) set wil be ignored and logged as a host-lookup failure.
+This applies to all of the SRV, MX A6, AAAA, A lookup sequence.
+
+
+
 .option mx_domains dnslookup "domain list&!!" unset
 .cindex "MX record" "required to exist"
 .cindex "SRV record" "required to exist"
index 56ff713..6252956 100644 (file)
@@ -84,6 +84,9 @@ TL/07 Add new dmarc expansion variable $dmarc_domain_policy to directly
 
 JH/13 Fix handling of $tls_cipher et.al. in (non-verify) transport.  Bug 1455.
 
+JH/14 New options dnssec_request_domains, dnssec_require_domains on the
+      dnslookup router (applying to the forward lookup).
+
 
 Exim version 4.82
 -----------------
index 185522e..a15eb03 100644 (file)
@@ -159,12 +159,13 @@ the first time we have been here, and set the resolver options.
 Arguments:
   qualify_single    TRUE to set the RES_DEFNAMES option
   search_parents    TRUE to set the RES_DNSRCH option
+  use_dnssec        TRUE to set the RES_USE_DNSSEC option
 
 Returns:            nothing
 */
 
 void
-dns_init(BOOL qualify_single, BOOL search_parents)
+dns_init(BOOL qualify_single, BOOL search_parents, BOOL use_dnssec)
 {
 res_state resp = os_get_dns_resolver_res();
 
@@ -206,6 +207,8 @@ if (dns_use_edns0 >= 0)
 #  ifndef RES_USE_EDNS0
 #   error Have RES_USE_DNSSEC but not RES_USE_EDNS0?  Something hinky ...
 #  endif
+if (use_dnssec)
+  resp->options |= RES_USE_DNSSEC;
 if (dns_dnssec_ok >= 0)
   {
   if (dns_use_edns0 == 0 && dns_dnssec_ok != 0)
@@ -228,6 +231,9 @@ if (dns_dnssec_ok >= 0)
   DEBUG(D_resolver)
     debug_printf("Unable to %sset DNSSEC without resolver support.\n",
         dns_dnssec_ok ? "" : "un");
+if (use_dnssec)
+  DEBUG(D_resolver)
+    debug_printf("Unable to set DNSSEC without resolver support.\n")
 # endif
 #endif /* DISABLE_DNSSEC */
 
@@ -1249,4 +1255,6 @@ else
 return yield;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of dns.c */
index be71345..599afd2 100644 (file)
@@ -115,7 +115,7 @@ extern BOOL    dkim_transport_write_message(address_item *, int, int,
 #endif
 extern dns_address *dns_address_from_rr(dns_answer *, dns_record *);
 extern void    dns_build_reverse(uschar *, uschar *);
-extern void    dns_init(BOOL, BOOL);
+extern void    dns_init(BOOL, BOOL, BOOL);
 extern int     dns_basic_lookup(dns_answer *, uschar *, int);
 extern BOOL    dns_is_secure(dns_answer *);
 extern int     dns_lookup(dns_answer *, uschar *, int, uschar **);
@@ -157,7 +157,7 @@ extern void    host_build_log_info(void);
 extern void    host_build_sender_fullhost(void);
 extern BOOL    host_find_byname(host_item *, uschar *, int, uschar **, BOOL);
 extern int     host_find_bydns(host_item *, uschar *, int, uschar *, uschar *,
-                 uschar *,uschar **, BOOL *);
+                 uschar *, uschar *, uschar *, uschar **, BOOL *);
 extern ip_address_item *host_find_interfaces(void);
 extern BOOL    host_is_in_net(uschar *, uschar *, int);
 extern BOOL    host_is_tls_on_connect_port(int);
index 785eea4..495a44d 100644 (file)
@@ -1622,7 +1622,7 @@ while ((ordername = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
   {
   if (strcmpic(ordername, US"bydns") == 0)
     {
-    dns_init(FALSE, FALSE);
+    dns_init(FALSE, FALSE, FALSE);     /*XXX dnssec? */
     dns_build_reverse(sender_host_address, buffer);
     rc = dns_lookup(&dnsa, buffer, T_PTR, NULL);
 
@@ -1919,7 +1919,8 @@ if (running_in_test_harness)
 some circumstances when the get..byname() function actually calls the DNS. */
 
 dns_init((flags & HOST_FIND_QUALIFY_SINGLE) != 0,
-         (flags & HOST_FIND_SEARCH_PARENTS) != 0);
+         (flags & HOST_FIND_SEARCH_PARENTS) != 0,
+        FALSE);        /*XXX dnssec? */
 
 /* In an IPv6 world, unless IPv6 has been disabled, we need to scan for both
 kinds of address, so go round the loop twice. Note that we have ensured that
@@ -2195,6 +2196,7 @@ Arguments:
   fully_qualified_name  if not NULL, return fully qualified name here if
                           the contents are different (i.e. it must be preset
                           to something)
+  dnnssec_require      if TRUE check the DNS result AD bit
 
 Returns:       HOST_FIND_FAILED     couldn't find A record
                HOST_FIND_AGAIN      try again later
@@ -2204,7 +2206,8 @@ Returns:       HOST_FIND_FAILED     couldn't find A record
 
 static int
 set_address_from_dns(host_item *host, host_item **lastptr,
-  uschar *ignore_target_hosts, BOOL allow_ip, uschar **fully_qualified_name)
+  uschar *ignore_target_hosts, BOOL allow_ip, uschar **fully_qualified_name,
+  BOOL dnssec_require)
 {
 dns_record *rr;
 host_item *thishostlast = NULL;    /* Indicates not yet filled in anything */
@@ -2287,6 +2290,12 @@ for (; i >= 0; i--)
     if (rc != DNS_NOMATCH && rc != DNS_NODATA) v6_find_again = TRUE;
     continue;
     }
+  if (dnssec_require && !dns_is_secure(&dnsa))
+    {
+    log_write(L_host_lookup_failed, LOG_MAIN, "dnssec fail on %s for %.256s",
+               i>1 ? "A6" : i>0 ? "AAAA" : "A", host->name);
+    continue;
+    }
 
   /* Lookup succeeded: fill in the given host item with the first non-ignored
   address found; create additional items for any others. A single A6 record
@@ -2433,6 +2442,8 @@ Arguments:
   srv_service           when SRV used, the service name
   srv_fail_domains      DNS errors for these domains => assume nonexist
   mx_fail_domains       DNS errors for these domains => assume nonexist
+  dnssec_request_domains => make dnssec request
+  dnssec_require_domains => ditto and nonexist failures
   fully_qualified_name  if not NULL, return fully-qualified name
   removed               set TRUE if local host was removed from the list
 
@@ -2450,6 +2461,7 @@ Returns:                HOST_FIND_FAILED  Failed to find the host or domain;
 int
 host_find_bydns(host_item *host, uschar *ignore_target_hosts, int whichrrs,
   uschar *srv_service, uschar *srv_fail_domains, uschar *mx_fail_domains,
+  uschar *dnssec_request_domains, uschar *dnssec_require_domains,
   uschar **fully_qualified_name, BOOL *removed)
 {
 host_item *h, *last;
@@ -2459,6 +2471,10 @@ int ind_type = 0;
 int yield;
 dns_answer dnsa;
 dns_scan dnss;
+BOOL dnssec_request = match_isinlist(host->name, &dnssec_request_domains,
+                                   0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK;
+BOOL dnssec_require = match_isinlist(host->name, &dnssec_require_domains,
+                                   0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) == OK;
 
 /* Set the default fully qualified name to the incoming name, initialize the
 resolver if necessary, set up the relevant options, and initialize the flag
@@ -2466,7 +2482,9 @@ that gets set for DNS syntax check errors. */
 
 if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
 dns_init((whichrrs & HOST_FIND_QUALIFY_SINGLE) != 0,
-         (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0);
+         (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0,
+        dnssec_request || dnssec_require
+        );
 host_find_failed_syntax = FALSE;
 
 /* First, if requested, look for SRV records. The service name is given; we
@@ -2494,13 +2512,19 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0)
   /* On DNS failures, we give the "try again" error unless the domain is
   listed as one for which we continue. */
 
+  if (rc == DNS_SUCCEED && dnssec_require && !dns_is_secure(&dnsa))
+    {
+    log_write(L_host_lookup_failed, LOG_MAIN,
+               "dnssec fail on SRV for %.256s", host->name);
+    rc = DNS_FAIL;
+    }
   if (rc == DNS_FAIL || rc == DNS_AGAIN)
     {
     #ifndef STAND_ALONE
     if (match_isinlist(host->name, &srv_fail_domains, 0, NULL, NULL, MCL_DOMAIN,
         TRUE, NULL) != OK)
     #endif
-      return HOST_FIND_AGAIN;
+      { yield = HOST_FIND_AGAIN; goto out; }
     DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
       "(domain in srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
     }
@@ -2517,16 +2541,29 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
   {
   ind_type = T_MX;
   rc = dns_lookup(&dnsa, host->name, ind_type, fully_qualified_name);
-  if (rc == DNS_NOMATCH) return HOST_FIND_FAILED;
-  if (rc == DNS_FAIL || rc == DNS_AGAIN)
-    {
-    #ifndef STAND_ALONE
-    if (match_isinlist(host->name, &mx_fail_domains, 0, NULL, NULL, MCL_DOMAIN,
-        TRUE, NULL) != OK)
-    #endif
-      return HOST_FIND_AGAIN;
-    DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
-      "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
+  switch (rc)
+    {
+    case DNS_NOMATCH:
+      yield = HOST_FIND_FAILED; goto out;
+
+    case DNS_SUCCEED:
+      if (!dnssec_require || dns_is_secure(&dnsa))
+       break;
+      log_write(L_host_lookup_failed, LOG_MAIN,
+                 "dnssec fail on MX for %.256s", host->name);
+      rc = DNS_FAIL;
+      /*FALLTRHOUGH*/
+
+    case DNS_FAIL:
+    case DNS_AGAIN:
+      #ifndef STAND_ALONE
+      if (match_isinlist(host->name, &mx_fail_domains, 0, NULL, NULL, MCL_DOMAIN,
+         TRUE, NULL) != OK)
+      #endif
+       { yield = HOST_FIND_AGAIN; goto out; }
+      DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
+       "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
+      break;
     }
   }
 
@@ -2539,14 +2576,15 @@ if (rc != DNS_SUCCEED)
   if ((whichrrs & HOST_FIND_BY_A) == 0)
     {
     DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n");
-    return HOST_FIND_FAILED;
+    yield = HOST_FIND_FAILED;
+    goto out;
     }
 
   last = host;        /* End of local chainlet */
   host->mx = MX_NONE;
   host->port = PORT_NONE;
   rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE,
-    fully_qualified_name);
+    fully_qualified_name, dnssec_require);
 
   /* If one or more address records have been found, check that none of them
   are local. Since we know the host items all have their IP addresses
@@ -2573,7 +2611,8 @@ if (rc != DNS_SUCCEED)
       }
     }
 
-  return rc;
+  yield = rc;
+  goto out;
   }
 
 /* We have found one or more MX or SRV records. Sort them according to
@@ -2757,7 +2796,8 @@ if (ind_type == T_SRV)
   if (host == last && host->name[0] == 0)
     {
     DEBUG(D_host_lookup) debug_printf("the single SRV record is \".\"\n");
-    return HOST_FIND_FAILED;
+    yield = HOST_FIND_FAILED;
+    goto out;
     }
 
   DEBUG(D_host_lookup)
@@ -2867,12 +2907,14 @@ otherwise invalid host names obtained from MX or SRV records can cause trouble
 if they happen to match something local. */
 
 yield = HOST_FIND_FAILED;    /* Default yield */
-dns_init(FALSE, FALSE);      /* Disable qualify_single and search_parents */
+dns_init(FALSE, FALSE,       /* Disable qualify_single and search_parents */
+        dnssec_request || dnssec_require);
 
 for (h = host; h != last->next; h = h->next)
   {
   if (h->address != NULL) continue;  /* Inserted by a multihomed host */
-  rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip, NULL);
+  rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip,
+    NULL, dnssec_require);
   if (rc != HOST_FOUND)
     {
     h->status = hstatus_unusable;
@@ -2981,6 +3023,9 @@ DEBUG(D_host_lookup)
     }
   }
 
+out:
+
+dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */
 return yield;
 }
 
@@ -3002,6 +3047,8 @@ int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A;
 BOOL byname = FALSE;
 BOOL qualify_single = TRUE;
 BOOL search_parents = FALSE;
+BOOL request_dnssec = FALSE;
+BOOL require_dnssec = FALSE;
 uschar **argv = USS cargv;
 uschar buffer[256];
 
@@ -3021,7 +3068,7 @@ if (argc > 1) primary_hostname = argv[1];
 
 /* So that debug level changes can be done first */
 
-dns_init(qualify_single, search_parents);
+dns_init(qualify_single, search_parents, FALSE);
 
 printf("Testing host lookup\n");
 printf("> ");
@@ -3047,10 +3094,14 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX;
   else if (Ustrcmp(buffer, "srv+mx+a") == 0)
     whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A;
-  else if (Ustrcmp(buffer, "qualify_single") == 0) qualify_single = TRUE;
+  else if (Ustrcmp(buffer, "qualify_single")    == 0) qualify_single = TRUE;
   else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE;
-  else if (Ustrcmp(buffer, "search_parents") == 0) search_parents = TRUE;
+  else if (Ustrcmp(buffer, "search_parents")    == 0) search_parents = TRUE;
   else if (Ustrcmp(buffer, "no_search_parents") == 0) search_parents = FALSE;
+  else if (Ustrcmp(buffer, "request_dnssec")    == 0) request_dnssec = TRUE;
+  else if (Ustrcmp(buffer, "no_request_dnssec") == 0) request_dnssec = FALSE;
+  else if (Ustrcmp(buffer, "require_dnssec")    == 0) require_dnssec = TRUE;
+  else if (Ustrcmp(buffer, "no_reqiret_dnssec") == 0) require_dnssec = FALSE;
   else if (Ustrcmp(buffer, "test_harness") == 0)
     running_in_test_harness = !running_in_test_harness;
   else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6;
@@ -3083,11 +3134,12 @@ while (Ufgets(buffer, 256, stdin) != NULL)
     if (qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
     if (search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
 
-    rc = byname?
-      host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE)
-      :
-      host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL,
-        &fully_qualified_name, NULL);
+    rc = byname
+      ? host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE)
+      : host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL,
+                       request_dnssec ? &h.name : NULL,
+                       require_dnssec ? &h.name : NULL,
+                       &fully_qualified_name, NULL);
 
     if (rc == HOST_FIND_FAILED) printf("Failed\n");
       else if (rc == HOST_FIND_AGAIN) printf("Again\n");
@@ -3146,4 +3198,6 @@ return 0;
 }
 #endif  /* STAND_ALONE */
 
+/* vi: aw ai sw=2
+*/
 /* End of host.c */
index e2e0f7f..a1eb2b6 100644 (file)
@@ -241,7 +241,7 @@ if ((equals = Ustrchr(keystring, '=')) != NULL)
 
 /* Initialize the resolver in case this is the first time it has been used. */
 
-dns_init(FALSE, FALSE);
+dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
 
 /* The remainder of the string must be a list of domains. As long as the lookup
 for at least one of them succeeds, we return success. Failure means that none
index 66ae3dd..97a0982 100644 (file)
@@ -221,6 +221,8 @@ if (cb->at_is_special && pattern[0] == '@')
       NULL,                /* service name not relevant */
       NULL,                /* srv_fail_domains not relevant */
       NULL,                /* mx_fail_domains not relevant */
+      NULL,                /* no dnssec request XXX ? */
+      NULL,                /* no dnssec require XXX ? */
       NULL,                /* no feedback FQDN */
       &removed);           /* feedback if local removed */
 
index 057a2a1..c8fd3f9 100644 (file)
@@ -18,6 +18,10 @@ optionlist dnslookup_router_options[] = {
       (void *)(offsetof(dnslookup_router_options_block, check_secondary_mx)) },
   { "check_srv",          opt_stringptr,
       (void *)(offsetof(dnslookup_router_options_block, check_srv)) },
+  { "dnssec_request_domains",         opt_stringptr,
+      (void *)(offsetof(dnslookup_router_options_block, dnssec_request_domains)) },
+  { "dnssec_require_domains",         opt_stringptr,
+      (void *)(offsetof(dnslookup_router_options_block, dnssec_require_domains)) },
   { "mx_domains",         opt_stringptr,
       (void *)(offsetof(dnslookup_router_options_block, mx_domains)) },
   { "mx_fail_domains",    opt_stringptr,
@@ -53,7 +57,9 @@ dnslookup_router_options_block dnslookup_router_option_defaults = {
   NULL,            /* mx_domains */
   NULL,            /* mx_fail_domains */
   NULL,            /* srv_fail_domains */
-  NULL             /* check_srv */
+  NULL,            /* check_srv */
+  NULL,            /* dnssec_request_domains */
+  NULL             /* dnssec_require_domains */
 };
 
 
@@ -261,7 +267,9 @@ for (;;)
     }
 
   rc = host_find_bydns(&h, rblock->ignore_target_hosts, flags, srv_service,
-    ob->srv_fail_domains, ob->mx_fail_domains, &fully_qualified_name, &removed);
+    ob->srv_fail_domains, ob->mx_fail_domains,
+    ob->dnssec_request_domains, ob->dnssec_require_domains,
+    &fully_qualified_name, &removed);
   if (removed) setflag(addr, af_local_host_removed);
 
   /* If host found with only address records, test for the domain's being in
index b0c3843..518b7f4 100644 (file)
@@ -17,6 +17,8 @@ typedef struct {
   uschar *mx_fail_domains;
   uschar *srv_fail_domains;
   uschar *check_srv;
+  uschar *dnssec_request_domains;
+  uschar *dnssec_require_domains;
 } dnslookup_router_options_block;
 
 /* Data for reading the private options. */
index eadcd5d..0eae31e 100644 (file)
@@ -94,6 +94,8 @@ for (h = addr->host_list; h != NULL; h = next_h)
         NULL,                           /* SRV service not relevant */
         NULL,                           /* failing srv domains not relevant */
         NULL,                           /* no special mx failing domains */
+       NULL,                           /* no dnssec request XXX ? */
+       NULL,                           /* no dnssec require XXX ? */
         NULL,                           /* fully_qualified_name */
         NULL);                          /* indicate local host removed */
     }
@@ -117,7 +119,9 @@ for (h = addr->host_list; h != NULL; h = next_h)
     BOOL removed;
     DEBUG(D_route|D_host_lookup) debug_printf("doing DNS lookup\n");
     rc = host_find_bydns(h, ignore_target_hosts, HOST_FIND_BY_A, NULL, NULL,
-      NULL, &canonical_name, &removed);
+      NULL,
+      NULL, NULL,      /*XXX dnssec? */
+      &canonical_name, &removed);
     if (rc == HOST_FOUND)
       {
       if (removed) setflag(addr, af_local_host_removed);
index 0aa95a4..57b66b8 100644 (file)
@@ -2816,6 +2816,7 @@ for (cutoff_retry = 0; expired &&
         rc = host_find_byname(host, NULL, flags, &canonical_name, TRUE);
       else
         rc = host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
+         NULL, NULL,   /*XXX todo: smtp tpt hosts_require_dnssec */
           &canonical_name, NULL);
 
       /* Update the host (and any additional blocks, resulting from
index 690bb8f..f799ff1 100644 (file)
@@ -1750,6 +1750,7 @@ while (addr_new != NULL)
                 (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE);
               else
                 (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
+                 NULL, NULL,   /*XXX todo: dnssec */
                   &canonical_name, NULL);
               }
             }
@@ -3546,7 +3547,7 @@ revadd[0] = 0;
 
 /* In case this is the first time the DNS resolver is being used. */
 
-dns_init(FALSE, FALSE);
+dns_init(FALSE, FALSE, FALSE); /*XXX dnssec? */
 
 /* Loop through all the domains supplied, until something matches */