dirmngr: Keep track of domains used for WKD queries
authorWerner Koch <wk@gnupg.org>
Mon, 13 Nov 2017 15:09:32 +0000 (16:09 +0100)
committerWerner Koch <wk@gnupg.org>
Mon, 13 Nov 2017 15:09:32 +0000 (16:09 +0100)
* dirmngr/domaininfo.c: New file.
* dirmngr/Makefile.am (dirmngr_SOURCES): Add file.
* dirmngr/server.c (cmd_wkd_get): Check whether the domain is already
known and tell domaininfo about the results.
--

This adds a registry for domain information to eventually avoid
useless queries for domains which do not support WKD.  The missing
part is a background task to check whether a queried domain supports
WKD at all and to expire old entries.

Signed-off-by: Werner Koch <wk@gnupg.org>
dirmngr/Makefile.am
dirmngr/dirmngr.c
dirmngr/dirmngr.h
dirmngr/domaininfo.c [new file with mode: 0644]
dirmngr/server.c

index b404165..421a325 100644 (file)
@@ -16,6 +16,8 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <https://www.gnu.org/licenses/>.
+#
+# SPDX-License-Identifier: GPL-3.0+
 
 ## Process this file with automake to produce Makefile.in
 
@@ -57,6 +59,7 @@ noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h
 
 dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c   \
        certcache.c certcache.h \
+       domaininfo.c \
        loadswdb.c \
        cdb.h cdblib.c misc.c dirmngr-err.h  \
        ocsp.c ocsp.h validate.c validate.h  \
index 5317c21..2b64655 100644 (file)
@@ -17,6 +17,8 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0+
  */
 
 #include <config.h>
@@ -1871,6 +1873,7 @@ handle_signal (int signo)
 
     case SIGUSR1:
       cert_cache_print_stats ();
+      domaininfo_print_stats ();
       break;
 
     case SIGUSR2:
index 1f660de..b08e4fe 100644 (file)
@@ -17,6 +17,8 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0+
  */
 
 #ifndef DIRMNGR_H
@@ -248,4 +250,14 @@ gpg_error_t gnupg_http_tls_verify_cb (void *opaque,
 gpg_error_t dirmngr_load_swdb (ctrl_t ctrl, int force);
 
 
+/*-- domaininfo.c --*/
+void domaininfo_print_stats (void);
+int  domaininfo_is_wkd_not_supported (const char *domain);
+void domaininfo_set_no_name (const char *domain);
+void domaininfo_set_wkd_supported (const char *domain);
+void domaininfo_set_wkd_not_supported (const char *domain);
+void domaininfo_set_wkd_not_found (const char *domain);
+
+
+
 #endif /*DIRMNGR_H*/
diff --git a/dirmngr/domaininfo.c b/dirmngr/domaininfo.c
new file mode 100644 (file)
index 0000000..393db8c
--- /dev/null
@@ -0,0 +1,244 @@
+/* domaininfo.c - Gather statistics about accessed domains
+ * Copyright (C) 2017 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0+
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dirmngr.h"
+
+
+#define NO_OF_DOMAINBUCKETS 103
+
+/* Object to keep track of a domain name.  */
+struct domaininfo_s
+{
+  struct domaininfo_s *next;
+  unsigned int no_name:1;            /* Domain name not found.            */
+  unsigned int wkd_not_found:1;      /* A WKD query failed.               */
+  unsigned int wkd_supported:1;      /* One WKD entry was found.          */
+  unsigned int wkd_not_supported:1;  /* Definitely does not support WKD.  */
+  char name[1];
+};
+typedef struct domaininfo_s *domaininfo_t;
+
+/* And the hashed array.  */
+static domaininfo_t domainbuckets[NO_OF_DOMAINBUCKETS];
+
+
+/* The hash function we use.  Must not call a system function.  */
+static inline u32
+hash_domain (const char *domain)
+{
+  const unsigned char *s = (const unsigned char*)domain;
+  u32 hashval = 0;
+  u32 carry;
+
+  for (; *s; s++)
+    {
+      if (*s == '.')
+        continue;
+      hashval = (hashval << 4) + *s;
+      if ((carry = (hashval & 0xf0000000)))
+        {
+          hashval ^= (carry >> 24);
+          hashval ^= carry;
+        }
+    }
+
+  return hashval % NO_OF_DOMAINBUCKETS;
+}
+
+
+void
+domaininfo_print_stats (void)
+{
+  int bidx;
+  domaininfo_t di;
+  int count, no_name, wkd_not_found, wkd_supported, wkd_not_supported;
+
+  for (bidx = 0; bidx < NO_OF_DOMAINBUCKETS; bidx++)
+    {
+      count = no_name = wkd_not_found = wkd_supported = wkd_not_supported = 0;
+      for (di = domainbuckets[bidx]; di; di = di->next)
+        {
+          count++;
+          if (di->no_name)
+            no_name++;
+          if (di->wkd_not_found)
+            wkd_not_found++;
+          if (di->wkd_supported)
+            wkd_supported++;
+          if (di->wkd_not_supported)
+            wkd_not_supported++;
+        }
+      if (count)
+        log_info ("domaininfo: chain %3d length=%d nn=%d nf=%d s=%d ns=%d\n",
+                  bidx, count, no_name,
+                  wkd_not_found, wkd_supported, wkd_not_supported);
+    }
+}
+
+
+/* Return true if DOMAIN definitely does not support WKD.  Noet that
+ * DOMAIN is expected to be lowercase.  */
+int
+domaininfo_is_wkd_not_supported (const char *domain)
+{
+  domaininfo_t di;
+
+  for (di = domainbuckets[hash_domain (domain)]; di; di = di->next)
+    if (!strcmp (di->name, domain))
+      return !!di->wkd_not_supported;
+
+  return 0;  /* We don't know.  */
+}
+
+
+/* Core update function.  DOMAIN is expected to be lowercase.
+ * CALLBACK is called to update the existing or the newly inserted
+ * item.  */
+static void
+insert_or_update (const char *domain,
+                  void (*callback)(domaininfo_t di, int insert_mode))
+{
+  domaininfo_t di;
+  domaininfo_t di_new;
+  u32 hash;
+
+  hash = hash_domain (domain);
+  for (di = domainbuckets[hash]; di; di = di->next)
+    if (!strcmp (di->name, domain))
+      {
+        callback (di, 0);  /* Update */
+        return;
+      }
+
+  di_new = xtrycalloc (1, sizeof *di + strlen (domain));
+  if (!di_new)
+    return;  /* Out of core - we ignore this.  */
+
+  /* Need to do another lookup because the malloc is a system call and
+   * thus the hash array may have been changed by another thread.  */
+  for (di = domainbuckets[hash]; di; di = di->next)
+    if (!strcmp (di->name, domain))
+      {
+        callback (di, 0);  /* Update */
+        xfree (di_new);
+        return;
+      }
+
+  callback (di_new, 1);  /* Insert */
+  di = di_new;
+  di->next = domainbuckets[hash];
+  domainbuckets[hash] = di;
+}
+
+
+/* Helper for domaininfo_set_no_name.  */
+static void
+set_no_name_cb (domaininfo_t di, int insert_mode)
+{
+  (void)insert_mode;
+
+  di->no_name = 1;
+  /* Obviously the domain is in this case also not supported.  */
+  di->wkd_not_supported = 1;
+
+  /* The next should already be 0 but we clear it anyway in the case
+   * of a temporary DNS failure.  */
+  di->wkd_supported = 0;
+}
+
+
+/* Mark DOMAIN as not existent.  */
+void
+domaininfo_set_no_name (const char *domain)
+{
+  insert_or_update (domain, set_no_name_cb);
+}
+
+
+/* Helper for domaininfo_set_wkd_supported.  */
+static void
+set_wkd_supported_cb (domaininfo_t di, int insert_mode)
+{
+  (void)insert_mode;
+
+  di->wkd_supported = 1;
+  /* The next will already be set unless the domain enabled WKD in the
+   * meantime.  Thus we need to clear it.  */
+  di->wkd_not_supported = 0;
+}
+
+
+/* Mark DOMAIN as supporting WKD.  */
+void
+domaininfo_set_wkd_supported (const char *domain)
+{
+  insert_or_update (domain, set_wkd_supported_cb);
+}
+
+
+/* Helper for domaininfo_set_wkd_not_supported.  */
+static void
+set_wkd_not_supported_cb (domaininfo_t di, int insert_mode)
+{
+  (void)insert_mode;
+
+  di->wkd_not_supported = 1;
+  di->wkd_supported = 0;
+}
+
+
+/* Mark DOMAIN as not supporting WKD queries (e.g. no policy file).  */
+void
+domaininfo_set_wkd_not_supported (const char *domain)
+{
+  insert_or_update (domain, set_wkd_not_supported_cb);
+}
+
+
+
+/* Helper for domaininfo_set_wkd_not_found.  */
+static void
+set_wkd_not_found_cb (domaininfo_t di, int insert_mode)
+{
+  /* Set the not found flag but there is no need to do this if we
+   * already know that the domain either does not support WKD or we
+   * know that it supports WKD.  */
+  if (insert_mode)
+    di->wkd_not_found = 1;
+  else if (!di->wkd_not_supported && !di->wkd_supported)
+    di->wkd_not_found = 1;
+
+  /* Better clear this flag in case we had a DNS failure in the
+   * past.  */
+  di->no_name = 0;
+}
+
+
+/* Update a counter for DOMAIN to keep track of failed WKD queries.  */
+void
+domaininfo_set_wkd_not_found (const char *domain)
+{
+  insert_or_update (domain, set_wkd_not_found_cb);
+}
index 7ed6cde..18a5f72 100644 (file)
@@ -18,6 +18,8 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0+
  */
 
 #include <config.h>
@@ -833,11 +835,13 @@ cmd_wkd_get (assuan_context_t ctx, char *line)
   char *mbox = NULL;
   char *domainbuf = NULL;
   char *domain;     /* Points to mbox or domainbuf.  */
+  char *domain_orig;/* Points to mbox.  */
   char sha1buf[20];
   char *uri = NULL;
   char *encodedhash = NULL;
   int opt_submission_addr;
   int opt_policy_flags;
+  int is_wkd_query;   /* True if this is a real WKD query.  */
   int no_log = 0;
   char portstr[20] = { 0 };
 
@@ -846,6 +850,7 @@ cmd_wkd_get (assuan_context_t ctx, char *line)
   if (has_option (line, "--quick"))
     ctrl->timeout = opt.connect_quick_timeout;
   line = skip_options (line);
+  is_wkd_query = !(opt_policy_flags || opt_submission_addr);
 
   mbox = mailbox_from_userid (line);
   if (!mbox || !(domain = strchr (mbox, '@')))
@@ -854,6 +859,18 @@ cmd_wkd_get (assuan_context_t ctx, char *line)
       goto leave;
     }
   *domain++ = 0;
+  domain_orig = domain;
+
+  /* First check whether we already know that the domain does not
+   * support WKD.  */
+  if (is_wkd_query)
+    {
+      if (domaininfo_is_wkd_not_supported (domain_orig))
+        {
+          err = gpg_error (GPG_ERR_NO_DATA);
+          goto leave;
+        }
+    }
 
   /* Check for SRV records.  */
   if (1)
@@ -962,6 +979,29 @@ cmd_wkd_get (assuan_context_t ctx, char *line)
         err = ks_action_fetch (ctrl, uri, outfp);
         es_fclose (outfp);
         ctrl->server_local->inhibit_data_logging = 0;
+        /* Register the result under the domain name of MBOX. */
+        switch (gpg_err_code (err))
+          {
+          case 0:
+            domaininfo_set_wkd_supported (domain_orig);
+            break;
+
+          case GPG_ERR_NO_NAME:
+            /* There is no such domain.  */
+            domaininfo_set_no_name (domain_orig);
+            break;
+
+          case GPG_ERR_NO_DATA:
+            if (is_wkd_query) /* Mark that - we will latter do a check.  */
+              domaininfo_set_wkd_not_found (domain_orig);
+            else if (opt_policy_flags) /* No policy file - no support.  */
+              domaininfo_set_wkd_not_supported (domain_orig);
+            break;
+
+          default:
+            /* Don't register other errors.  */
+            break;
+          }
       }
   }