diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index 421a32533..43f59bd45 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -60,6 +60,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 \ + 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 2b64655e8..9cb02036e 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1134,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 @@ -1939,7 +1939,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); @@ -2034,6 +2037,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; @@ -2055,12 +2060,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 b08e4fea7..5189f93b1 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -228,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); @@ -258,6 +260,15 @@ 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/server.c b/dirmngr/server.c index 18a5f7206..1fbd007a0 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -90,6 +90,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; @@ -125,6 +128,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); + @@ -992,8 +998,12 @@ cmd_wkd_get (assuan_context_t ctx, char *line) 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); + if (is_wkd_query) /* 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; @@ -1014,6 +1024,20 @@ cmd_wkd_get (assuan_context_t ctx, char *line) } +/* 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) +{ + if (!ctrl || !domain) + return "check_wkd_support"; + + log_debug ("FIXME: Implement %s\n", __func__); + + return NULL; +} + + static const char hlp_ldapserver[] = "LDAPSERVER \n" @@ -2428,12 +2452,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")) { @@ -2442,8 +2469,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)); } @@ -2452,6 +2477,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; @@ -2487,6 +2517,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"); @@ -2614,9 +2649,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; @@ -2693,6 +2729,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); @@ -2792,8 +2830,7 @@ 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) { @@ -2823,6 +2860,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 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 . + * + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include +#include +#include + +#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); +}