diff options
Diffstat (limited to 'dirmngr')
-rw-r--r-- | dirmngr/Makefile.am | 4 | ||||
-rw-r--r-- | dirmngr/crlcache.c | 2 | ||||
-rw-r--r-- | dirmngr/dirmngr.c | 29 | ||||
-rw-r--r-- | dirmngr/dirmngr.h | 25 | ||||
-rw-r--r-- | dirmngr/domaininfo.c | 291 | ||||
-rw-r--r-- | dirmngr/http.c | 10 | ||||
-rw-r--r-- | dirmngr/ks-action.c | 3 | ||||
-rw-r--r-- | dirmngr/misc.c | 9 | ||||
-rw-r--r-- | dirmngr/server.c | 186 | ||||
-rw-r--r-- | dirmngr/workqueue.c | 214 |
10 files changed, 729 insertions, 44 deletions
diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index b404165ed..43f59bd45 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 <https://www.gnu.org/licenses/>. +# +# SPDX-License-Identifier: GPL-3.0+ ## Process this file with automake to produce Makefile.in @@ -57,6 +59,8 @@ 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 \ + workqueue.c \ loadswdb.c \ cdb.h cdblib.c misc.c dirmngr-err.h \ ocsp.c ocsp.h validate.c validate.h \ diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index 6eeeb8dd3..8687c7bf4 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -1346,7 +1346,7 @@ cache_isvalid (ctrl_t ctrl, const char *issuer_hash, { log_error (_("WARNING: invalid cache record length for S/N ")); log_printf ("0x"); - log_printhex ("", sn, snlen); + log_printhex (sn, snlen, ""); } else if (opt.verbose) { diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 5317c214a..25716e2b5 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 <https://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0+ */ #include <config.h> @@ -779,12 +781,12 @@ my_ntbtls_log_handler (void *opaque, int level, const char *fmt, va_list argv) (void)opaque; if (level == -1) - log_logv_with_prefix (GPGRT_LOG_INFO, "ntbtls: ", fmt, argv); + log_logv_prefix (GPGRT_LOGLVL_INFO, "ntbtls: ", fmt, argv); else { char prefix[10+20]; snprintf (prefix, sizeof prefix, "ntbtls(%d): ", level); - log_logv_with_prefix (GPGRT_LOG_DEBUG, prefix, fmt, argv); + log_logv_prefix (GPGRT_LOGLVL_DEBUG, prefix, fmt, argv); } } #endif @@ -1132,7 +1134,7 @@ main (int argc, char **argv) cert_cache_init (hkp_cacert_filenames); crl_cache_init (); http_register_netactivity_cb (netactivity_action); - start_command_handler (ASSUAN_INVALID_FD); + start_command_handler (ASSUAN_INVALID_FD, 0); shutdown_reaper (); } #ifndef HAVE_W32_SYSTEM @@ -1191,6 +1193,14 @@ main (int argc, char **argv) current_logfile = xstrdup (logfile); } + if (debug_wait) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + #ifndef HAVE_W32_SYSTEM if (strchr (socket_name, ':')) { @@ -1871,6 +1881,7 @@ handle_signal (int signo) case SIGUSR1: cert_cache_print_stats (); + domaininfo_print_stats (); break; case SIGUSR2: @@ -1936,7 +1947,10 @@ housekeeping_thread (void *arg) network_activity_seen = 0; if (opt.allow_version_check) dirmngr_load_swdb (&ctrlbuf, 0); + workqueue_run_global_tasks (&ctrlbuf, 1); } + else + workqueue_run_global_tasks (&ctrlbuf, 0); dirmngr_deinit_default_ctrl (&ctrlbuf); @@ -2031,6 +2045,8 @@ check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce) static void * start_connection_thread (void *arg) { + static unsigned int last_session_id; + unsigned int session_id; union int_and_ptr_u argval; gnupg_fd_t fd; @@ -2052,12 +2068,17 @@ start_connection_thread (void *arg) if (opt.verbose) log_info (_("handler for fd %d started\n"), FD2INT (fd)); - start_command_handler (fd); + session_id = ++last_session_id; + if (!session_id) + session_id = ++last_session_id; + start_command_handler (fd, session_id); if (opt.verbose) log_info (_("handler for fd %d terminated\n"), FD2INT (fd)); active_connections--; + workqueue_run_post_session_tasks (session_id); + #ifndef HAVE_W32_SYSTEM argval.afd = ASSUAN_INVALID_FD; npth_setspecific (my_tlskey_current_fd, argval.aptr); diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index 1f660de10..5189f93b1 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 <https://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0+ */ #ifndef DIRMNGR_H @@ -226,9 +228,11 @@ ksba_cert_t get_cert_local_ski (ctrl_t ctrl, gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr); int dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, const char *msg); -void start_command_handler (gnupg_fd_t fd); +void start_command_handler (gnupg_fd_t fd, unsigned int session_id); gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...); gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text); +gpg_error_t dirmngr_status_helpf (ctrl_t ctrl, const char *format, + ...) GPGRT_ATTR_PRINTF(2,3); gpg_error_t dirmngr_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) GPGRT_ATTR_PRINTF(3,4); @@ -248,4 +252,23 @@ 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); + +/*-- workqueue.c --*/ +typedef const char *(*wqtask_t)(ctrl_t ctrl, const char *args); + +void workqueue_dump_queue (ctrl_t ctrl); +gpg_error_t workqueue_add_task (wqtask_t func, const char *args, + unsigned int session_id, int need_network); +void workqueue_run_global_tasks (ctrl_t ctrl, int with_network); +void workqueue_run_post_session_tasks (unsigned int session_id); + + + #endif /*DIRMNGR_H*/ diff --git a/dirmngr/domaininfo.c b/dirmngr/domaininfo.c new file mode 100644 index 000000000..a2effffef --- /dev/null +++ b/dirmngr/domaininfo.c @@ -0,0 +1,291 @@ +/* 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" + + +/* Number of bucket for the hash array and limit for the length of a + * bucket chain. For debugging values of 13 and 10 are more suitable + * and a command like + * for j in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \ + * for i in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \ + * gpg-connect-agent --dirmngr "wkd_get foo@$i.$j.gnupg.net" /bye \ + * >/dev/null ; done; done + * will quickly add a couple of domains. + */ +#define NO_OF_DOMAINBUCKETS 103 +#define MAX_DOMAINBUCKET_LEN 20 + + +/* 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; + int len, minlen, maxlen; + + count = no_name = wkd_not_found = wkd_supported = wkd_not_supported = 0; + maxlen = 0; + minlen = -1; + for (bidx = 0; bidx < NO_OF_DOMAINBUCKETS; bidx++) + { + len = 0; + for (di = domainbuckets[bidx]; di; di = di->next) + { + count++; + len++; + 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 (len > maxlen) + maxlen = len; + if (minlen == -1 || len < minlen) + minlen = len; + } + log_info ("domaininfo: items=%d chainlen=%d..%d nn=%d nf=%d ns=%d s=%d\n", + count, + minlen > 0? minlen : 0, + maxlen, + no_name, wkd_not_found, wkd_not_supported, wkd_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; + domaininfo_t di_cut; + u32 hash; + int count; + + 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. */ + strcpy (di_new->name, domain); + + /* Need to do another lookup because the malloc is a system call and + * thus the hash array may have been changed by another thread. */ + di_cut = NULL; + for (count=0, di = domainbuckets[hash]; di; di = di->next, count++) + if (!strcmp (di->name, domain)) + { + callback (di, 0); /* Update */ + xfree (di_new); + return; + } + + /* Before we insert we need to check whether the chain gets too long. */ + di_cut = NULL; + if (count >= MAX_DOMAINBUCKET_LEN) + { + for (count=0, di = domainbuckets[hash]; di; di = di->next, count++) + if (count >= MAX_DOMAINBUCKET_LEN/2) + { + di_cut = di->next; + di->next = NULL; + break; + } + } + + /* Insert */ + callback (di_new, 1); + di = di_new; + di->next = domainbuckets[hash]; + domainbuckets[hash] = di; + + /* Remove the rest of the cutted chain. */ + while (di_cut) + { + di = di_cut->next; + xfree (di_cut); + di_cut = 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/http.c b/dirmngr/http.c index 8e778dfa2..cc7f5a553 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -1053,7 +1053,7 @@ http_start_data (http_t hd) if (!hd->in_data) { if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_with_string ("\r\n", "http.c:request-header:"); + log_debug_string ("\r\n", "http.c:request-header:"); es_fputs ("\r\n", hd->fp_write); es_fflush (hd->fp_write); hd->in_data = 1; @@ -1844,7 +1844,7 @@ send_request (http_t hd, const char *httphost, const char *auth, return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_with_string (request, "http.c:request:"); + log_debug_string (request, "http.c:request:"); cookie = xtrycalloc (1, sizeof *cookie); if (! cookie) @@ -2159,7 +2159,7 @@ send_request (http_t hd, const char *httphost, const char *auth, } if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_with_string (request, "http.c:request:"); + log_debug_string (request, "http.c:request:"); /* First setup estream so that we can write even the first line using estream. This is also required for the sake of gnutls. */ @@ -2195,7 +2195,7 @@ send_request (http_t hd, const char *httphost, const char *auth, for (;headers; headers=headers->next) { if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_with_string (headers->d, "http.c:request-header:"); + log_debug_string (headers->d, "http.c:request-header:"); if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write)) || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write))) { @@ -2446,7 +2446,7 @@ parse_response (http_t hd) return GPG_ERR_EOF; if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) - log_debug_with_string (line, "http.c:response:\n"); + log_debug_string (line, "http.c:response:\n"); } while (!*line); diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index 857aab166..38cd02feb 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -296,7 +296,8 @@ ks_action_get (ctrl_t ctrl, uri_item_t keyservers, /* Retrieve keys from URL and write the result to the provided output - stream OUTFP. */ + * stream OUTFP. If OUTFP is NULL the data is written to the bit + * bucket. */ gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp) { diff --git a/dirmngr/misc.c b/dirmngr/misc.c index 1716141a6..1270b834d 100644 --- a/dirmngr/misc.c +++ b/dirmngr/misc.c @@ -284,7 +284,7 @@ dump_string (const char *string) else { log_printf ( "[ "); - log_printhex (NULL, string, strlen (string)); + log_printhex (string, strlen (string), NULL); log_printf ( " ]"); } } @@ -636,7 +636,9 @@ armor_data (char **r_string, const void *data, size_t datalen) return 0; } -/* Copy all data from IN to OUT. */ + +/* Copy all data from IN to OUT. OUT may be NULL to use this fucntion + * as a dummy reader. */ gpg_error_t copy_stream (estream_t in, estream_t out) { @@ -647,9 +649,8 @@ copy_stream (estream_t in, estream_t out) { if (!nread) return 0; /* EOF */ - if (es_write (out, buffer, nread, NULL)) + if (out && es_write (out, buffer, nread, NULL)) break; - } return gpg_error_from_syserror (); } diff --git a/dirmngr/server.c b/dirmngr/server.c index 7ed6cde15..3d0768b2a 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 <https://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0+ */ #include <config.h> @@ -78,7 +80,8 @@ #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) -#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) +#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \ + /**/: gpg_error (e)) @@ -88,6 +91,9 @@ struct server_local_s /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; + /* The session id (a counter). */ + unsigned int session_id; + /* Per-session LDAP servers. */ ldap_server_t ldapservers; @@ -123,6 +129,9 @@ static es_cookie_io_functions_t data_line_cookie_functions = }; +/* Local prototypes */ +static const char *task_check_wkd_support (ctrl_t ctrl, const char *domain); + @@ -820,24 +829,22 @@ cmd_dns_cert (assuan_context_t ctx, char *line) -static const char hlp_wkd_get[] = - "WKD_GET [--submission-address|--policy-flags] <user_id>\n" - "\n" - "Return the key or other info for <user_id>\n" - "from the Web Key Directory."; +/* Core of cmd_wkd_get and task_check_wkd_support. If CTX is NULL + * this function will not write anything to the assuan output. */ static gpg_error_t -cmd_wkd_get (assuan_context_t ctx, char *line) +proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) { - ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; 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 +853,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 +862,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) @@ -872,7 +892,8 @@ cmd_wkd_get (assuan_context_t ctx, char *line) domainlen = strlen (domain); for (i = 0; i < srvscount; i++) { - log_debug ("srv: trying '%s:%hu'\n", srvs[i].target, srvs[i].port); + if (DBG_DNS) + log_debug ("srv: trying '%s:%hu'\n", srvs[i].target, srvs[i].port); targetlen = strlen (srvs[i].target); if ((targetlen > domainlen + 1 && srvs[i].target[targetlen - domainlen - 1] == '.' @@ -949,19 +970,52 @@ cmd_wkd_get (assuan_context_t ctx, char *line) { estream_t outfp; - outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); - if (!outfp) + outfp = ctx? es_fopencookie (ctx, "w", data_line_cookie_functions) : NULL; + if (!outfp && ctx) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { - if (no_log) - ctrl->server_local->inhibit_data_logging = 1; - ctrl->server_local->inhibit_data_logging_now = 0; - ctrl->server_local->inhibit_data_logging_count = 0; + if (ctrl->server_local) + { + if (no_log) + ctrl->server_local->inhibit_data_logging = 1; + ctrl->server_local->inhibit_data_logging_now = 0; + ctrl->server_local->inhibit_data_logging_count = 0; + } err = ks_action_fetch (ctrl, uri, outfp); es_fclose (outfp); - ctrl->server_local->inhibit_data_logging = 0; + if (ctrl->server_local) + 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 && ctrl->server_local) + { + /* Mark that and schedule a check. */ + domaininfo_set_wkd_not_found (domain_orig); + workqueue_add_task (task_check_wkd_support, domain_orig, + ctrl->server_local->session_id, 1); + } + 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; + } } } @@ -970,10 +1024,50 @@ cmd_wkd_get (assuan_context_t ctx, char *line) xfree (encodedhash); xfree (mbox); xfree (domainbuf); + return err; +} + + +static const char hlp_wkd_get[] = + "WKD_GET [--submission-address|--policy-flags] <user_id>\n" + "\n" + "Return the key or other info for <user_id>\n" + "from the Web Key Directory."; +static gpg_error_t +cmd_wkd_get (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + + err = proc_wkd_get (ctrl, ctx, line); + return leave_cmd (ctx, err); } +/* A task to check whether DOMAIN supports WKD. This is done by + * checking whether the policy flags file can be read. */ +static const char * +task_check_wkd_support (ctrl_t ctrl, const char *domain) +{ + char *string; + + if (!ctrl || !domain) + return "check_wkd_support"; + + string = strconcat ("--policy-flags foo@", domain, NULL); + if (!string) + log_error ("%s: %s\n", __func__, gpg_strerror (gpg_error_from_syserror ())); + else + { + proc_wkd_get (ctrl, NULL, string); + xfree (string); + } + + return NULL; +} + + static const char hlp_ldapserver[] = "LDAPSERVER <data>\n" @@ -2388,12 +2482,15 @@ static const char hlp_getinfo[] = "pid - Return the process id of the server.\n" "tor - Return OK if running in Tor mode\n" "dnsinfo - Return info about the DNS resolver\n" - "socket_name - Return the name of the socket.\n"; + "socket_name - Return the name of the socket.\n" + "session_id - Return the current session_id.\n" + "workqueue - Inspect the work queue\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; + char numbuf[50]; if (!strcmp (line, "version")) { @@ -2402,8 +2499,6 @@ cmd_getinfo (assuan_context_t ctx, char *line) } else if (!strcmp (line, "pid")) { - char numbuf[50]; - snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } @@ -2412,6 +2507,11 @@ cmd_getinfo (assuan_context_t ctx, char *line) const char *s = dirmngr_get_current_socket_name (); err = assuan_send_data (ctx, s, strlen (s)); } + else if (!strcmp (line, "session_id")) + { + snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id); + err = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } else if (!strcmp (line, "tor")) { int use_tor; @@ -2447,6 +2547,11 @@ cmd_getinfo (assuan_context_t ctx, char *line) } err = 0; } + else if (!strcmp (line, "workqueue")) + { + workqueue_dump_queue (ctrl); + err = 0; + } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); @@ -2574,9 +2679,10 @@ dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, /* Startup the server and run the main command loop. With FD = -1, - use stdin/stdout. */ + * use stdin/stdout. SESSION_ID is either 0 or a unique number + * identifying a session. */ void -start_command_handler (assuan_fd_t fd) +start_command_handler (assuan_fd_t fd, unsigned int session_id) { static const char hello[] = "Dirmngr " VERSION " at your service"; static char *hello_line; @@ -2653,6 +2759,8 @@ start_command_handler (assuan_fd_t fd) assuan_register_option_handler (ctx, option_handler); assuan_register_reset_notify (ctx, reset_notify); + ctrl->server_local->session_id = session_id; + for (;;) { rc = assuan_accept (ctx); @@ -2722,12 +2830,12 @@ dirmngr_status (ctrl_t ctrl, const char *keyword, ...) gpg_error_t err = 0; va_list arg_ptr; const char *text; + assuan_context_t ctx; va_start (arg_ptr, keyword); - if (ctrl->server_local) + if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx)) { - assuan_context_t ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; @@ -2752,16 +2860,15 @@ dirmngr_status (ctrl_t ctrl, const char *keyword, ...) } -/* Print a help status line. TEXTLEN gives the length of the text - from TEXT to be printed. The function splits text at LFs. */ +/* Print a help status line. The function splits text at LFs. */ gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text) { gpg_error_t err = 0; + assuan_context_t ctx; - if (ctrl->server_local) + if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx)) { - assuan_context_t ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; @@ -2783,6 +2890,26 @@ dirmngr_status_help (ctrl_t ctrl, const char *text) } +/* Print a help status line using a printf like format. The function + * splits text at LFs. */ +gpg_error_t +dirmngr_status_helpf (ctrl_t ctrl, const char *format, ...) +{ + va_list arg_ptr; + gpg_error_t err; + char *buf; + + va_start (arg_ptr, format); + buf = es_vbsprintf (format, arg_ptr); + err = buf? 0 : gpg_error_from_syserror (); + va_end (arg_ptr); + if (!err) + err = dirmngr_status_help (ctrl, buf); + es_free (buf); + return err; +} + + /* This function is similar to print_assuan_status but takes a CTRL * arg instead of an assuan context as first argument. */ gpg_error_t @@ -2791,7 +2918,10 @@ dirmngr_status_printf (ctrl_t ctrl, const char *keyword, { gpg_error_t err; va_list arg_ptr; - assuan_context_t ctx = ctrl->server_local->assuan_ctx; + assuan_context_t ctx; + + if (!ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) + return 0; va_start (arg_ptr, format); err = vprint_assuan_status (ctx, keyword, format, arg_ptr); diff --git a/dirmngr/workqueue.c b/dirmngr/workqueue.c new file mode 100644 index 000000000..2cb8573e8 --- /dev/null +++ b/dirmngr/workqueue.c @@ -0,0 +1,214 @@ +/* workqueue.c - Maintain a queue of background tasks + * 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" + + +/* An object for one item in the workqueue. */ +struct wqitem_s +{ + struct wqitem_s *next; + + /* This flag is set if the task requires network access. */ + unsigned int need_network:1; + + /* The id of the session which created this task. If this is 0 the + * task is not associated with a specific session. */ + unsigned int session_id; + + /* The function to perform the backgrount task. */ + wqtask_t func; + + /* A string with the string argument for that task. */ + char args[1]; +}; +typedef struct wqitem_s *wqitem_t; + + +/* The workque is a simple linked list. */ +static wqitem_t workqueue; + + +/* Dump the queue using Assuan status comments. */ +void +workqueue_dump_queue (ctrl_t ctrl) +{ + wqitem_t saved_workqueue; + wqitem_t item; + unsigned int count; + + /* Temporay detach the entiere workqueue so that other threads don't + * get into our way. */ + saved_workqueue = workqueue; + workqueue = NULL; + + for (count=0, item = saved_workqueue; item; item = item->next) + count++; + + dirmngr_status_helpf (ctrl, "wq: number of entries: %u", count); + for (item = saved_workqueue; item; item = item->next) + dirmngr_status_helpf (ctrl, "wq: sess=%u net=%d %s(\"%.100s%s\")", + item->session_id, item->need_network, + item->func? item->func (NULL, NULL): "nop", + item->args, strlen (item->args) > 100? "[...]":""); + + /* Restore then workqueue. Actually we append the saved queue do a + * possibly updated workqueue. */ + if (!(item=workqueue)) + workqueue = saved_workqueue; + else + { + while (item->next) + item = item->next; + item->next = saved_workqueue; + } +} + + +/* Append the task (FUNC,ARGS) to the work queue. FUNC shall return + * its name when called with (NULL, NULL). */ +gpg_error_t +workqueue_add_task (wqtask_t func, const char *args, unsigned int session_id, + int need_network) +{ + wqitem_t item, wi; + + item = xtrycalloc (1, sizeof *item + strlen (args)); + if (!item) + return gpg_error_from_syserror (); + strcpy (item->args, args); + item->func = func; + item->session_id = session_id; + item->need_network = !!need_network; + + if (!(wi=workqueue)) + workqueue = item; + else + { + while (wi->next) + wi = wi->next; + wi->next = item; + } + return 0; +} + + +/* Run the task described by ITEM. ITEM must have been detached from + * the workqueue; its ownership is transferred to this fucntion. */ +static void +run_a_task (ctrl_t ctrl, wqitem_t item) +{ + log_assert (!item->next); + + if (opt.verbose) + log_info ("session %u: running %s(\"%s%s\")\n", + item->session_id, + item->func? item->func (NULL, NULL): "nop", + item->args, strlen (item->args) > 100? "[...]":""); + if (item->func) + item->func (ctrl, item->args); + + xfree (item); +} + + +/* Run tasks not associated with a session. This is called from the + * ticker every few minutes. If WITH_NETWORK is not set tasks which + * require the network are not run. */ +void +workqueue_run_global_tasks (ctrl_t ctrl, int with_network) +{ + wqitem_t item, prev; + + with_network = !!with_network; + + if (opt.verbose) + log_info ("running scheduled tasks%s\n", with_network?" (with network)":""); + + for (;;) + { + prev = NULL; + for (item = workqueue; item; prev = item, item = item->next) + if (!item->session_id + && (!item->need_network || (item->need_network && with_network))) + break; + if (!item) + break; /* No more tasks to run. */ + + /* Detach that item from the workqueue. */ + if (!prev) + workqueue = item->next; + else + prev->next = item->next; + item->next = NULL; + + /* Run the task. */ + run_a_task (ctrl, item); + } +} + + +/* Run tasks scheduled for running after a session. Those tasks are + * identified by the SESSION_ID. */ +void +workqueue_run_post_session_tasks (unsigned int session_id) +{ + struct server_control_s ctrlbuf; + ctrl_t ctrl = NULL; + wqitem_t item, prev; + + if (!session_id) + return; + + for (;;) + { + prev = NULL; + for (item = workqueue; item; prev = item, item = item->next) + if (item->session_id == session_id) + break; + if (!item) + break; /* No more tasks for this session. */ + + /* Detach that item from the workqueue. */ + if (!prev) + workqueue = item->next; + else + prev->next = item->next; + item->next = NULL; + + /* Create a CTRL object the first time we need it. */ + if (!ctrl) + { + memset (&ctrlbuf, 0, sizeof ctrlbuf); + ctrl = &ctrlbuf; + dirmngr_init_default_ctrl (ctrl); + } + + /* Run the task. */ + run_a_task (ctrl, item); + } + + dirmngr_deinit_default_ctrl (ctrl); +} |