From 65038e6852185c20413d8f6602218ee636413b77 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 13 Nov 2017 16:09:32 +0100 Subject: [PATCH] dirmngr: Keep track of domains used for WKD queries * 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 --- dirmngr/Makefile.am | 3 + dirmngr/dirmngr.c | 3 + dirmngr/dirmngr.h | 12 +++ dirmngr/domaininfo.c | 244 +++++++++++++++++++++++++++++++++++++++++++ dirmngr/server.c | 40 +++++++ 5 files changed, 302 insertions(+) create mode 100644 dirmngr/domaininfo.c diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index b404165ed..421a32533 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -16,6 +16,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, see . +# +# 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 \ diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 5317c214a..2b64655e8 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -17,6 +17,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0+ */ #include @@ -1871,6 +1873,7 @@ handle_signal (int signo) case SIGUSR1: cert_cache_print_stats (); + domaininfo_print_stats (); break; case SIGUSR2: diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index 1f660de10..b08e4fea7 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -17,6 +17,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * + * 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 index 000000000..393db8c78 --- /dev/null +++ b/dirmngr/domaininfo.c @@ -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 . + * + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include + +#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); +} diff --git a/dirmngr/server.c b/dirmngr/server.c index 7ed6cde15..18a5f7206 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -18,6 +18,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0+ */ #include @@ -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; + } } }