aboutsummaryrefslogtreecommitdiffstats
path: root/dirmngr
diff options
context:
space:
mode:
Diffstat (limited to 'dirmngr')
-rw-r--r--dirmngr/Makefile.am4
-rw-r--r--dirmngr/dirmngr.c25
-rw-r--r--dirmngr/dirmngr.h25
-rw-r--r--dirmngr/domaininfo.c291
-rw-r--r--dirmngr/ks-action.c3
-rw-r--r--dirmngr/misc.c7
-rw-r--r--dirmngr/server.c186
-rw-r--r--dirmngr/workqueue.c214
8 files changed, 720 insertions, 35 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/dirmngr.c b/dirmngr/dirmngr.c
index 5317c214a..631256ded 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>
@@ -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/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..6291a9a35 100644
--- a/dirmngr/misc.c
+++ b/dirmngr/misc.c
@@ -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);
+}