aboutsummaryrefslogtreecommitdiffstats
path: root/kbx/kbxserver.c
diff options
context:
space:
mode:
Diffstat (limited to 'kbx/kbxserver.c')
-rw-r--r--kbx/kbxserver.c775
1 files changed, 775 insertions, 0 deletions
diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c
new file mode 100644
index 000000000..929ee6116
--- /dev/null
+++ b/kbx/kbxserver.c
@@ -0,0 +1,775 @@
+/* kbxserver.c - Handle Assuan commands send to the keyboxd
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "keyboxd.h"
+#include <assuan.h>
+#include "../common/i18n.h"
+#include "../common/server-help.h"
+#include "../common/userids.h"
+#include "../common/asshelp.h"
+#include "../common/host2net.h"
+#include "frontend.h"
+
+
+
+#define PARM_ERROR(t) assuan_set_error (ctx, \
+ gpg_error (GPG_ERR_ASS_PARAMETER), (t))
+#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \
+ /**/: gpg_error (e))
+
+
+
+/* Control structure per connection. */
+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;
+
+ /* If this flag is set to true this process will be terminated after
+ * the end of this session. */
+ int stopme;
+
+ /* If the first both flags are set the assuan logging of data lines
+ * is suppressed. The count variable is used to show the number of
+ * non-logged bytes. */
+ size_t inhibit_data_logging_count;
+ unsigned int inhibit_data_logging : 1;
+ unsigned int inhibit_data_logging_now : 1;
+
+ /* This flag is set if the last search command was called with --more. */
+ unsigned int search_expecting_more : 1;
+
+ /* This flag is set if the last search command was successful. */
+ unsigned int search_any_found : 1;
+
+ /* The first is the current search description as parsed by the
+ * cmd_search. If more than one pattern is required, cmd_search
+ * also allocates and sets multi_search_desc and
+ * multi_search_desc_len. If a search description has ever been
+ * allocated the allocated size is stored at
+ * multi_search_desc_size. */
+ KEYBOX_SEARCH_DESC search_desc;
+ KEYBOX_SEARCH_DESC *multi_search_desc;
+ unsigned int multi_search_desc_size;
+ unsigned int multi_search_desc_len;
+
+ /* If not NULL write output to this stream instead of using D lines. */
+ estream_t outstream;
+};
+
+
+
+
+/* Return the assuan contxt from the local server info in CTRL. */
+static assuan_context_t
+get_assuan_ctx_from_ctrl (ctrl_t ctrl)
+{
+ if (!ctrl || !ctrl->server_local)
+ return NULL;
+ return ctrl->server_local->assuan_ctx;
+}
+
+
+/* If OUTPUT has been used prepare the output FD for use. This needs
+ * to be called by all functions which will in any way use
+ * kbxd_write_data_line later. Whether the output goes to the output
+ * stream is decided by this function. */
+static gpg_error_t
+prepare_outstream (ctrl_t ctrl)
+{
+ int fd;
+
+ log_assert (ctrl && ctrl->server_local);
+
+ if (ctrl->server_local->outstream)
+ return 0; /* Already enabled. */
+
+ fd = translate_sys2libc_fd
+ (assuan_get_output_fd (get_assuan_ctx_from_ctrl (ctrl)), 1);
+ if (fd == -1)
+ return 0; /* No Output command active. */
+
+ ctrl->server_local->outstream = es_fdopen_nc (fd, "w");
+ if (!ctrl->server_local->outstream)
+ return gpg_err_code_from_syserror ();
+ return 0;
+}
+
+
+/* The usual writen function; here with diagnostic output. */
+static gpg_error_t
+kbxd_writen (estream_t fp, const void *buffer, size_t length)
+{
+ gpg_error_t err;
+ size_t nwritten;
+
+ if (es_write (fp, buffer, length, &nwritten))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
+ }
+ else if (length != nwritten)
+ {
+ err = gpg_error (GPG_ERR_EIO);
+ log_error ("error writing OUTPUT: %s\n", "short write");
+ }
+ else
+ err = 0;
+
+ return err;
+}
+
+
+/* A wrapper around assuan_send_data which makes debugging the output
+ * in verbose mode easier. It also takes CTRL as argument. */
+gpg_error_t
+kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size)
+{
+ const char *buffer = buffer_arg;
+ assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl);
+ gpg_error_t err;
+
+ if (!ctx) /* Oops - no assuan context. */
+ return gpg_error (GPG_ERR_NOT_PROCESSED);
+
+ /* Write toa file descriptor if enabled. */
+ if (ctrl && ctrl->server_local && ctrl->server_local->outstream)
+ {
+ unsigned char lenbuf[4];
+
+ ulongtobuf (lenbuf, size);
+ err = kbxd_writen (ctrl->server_local->outstream, lenbuf, 4);
+ if (!err)
+ err = kbxd_writen (ctrl->server_local->outstream, buffer, size);
+ if (!err && es_fflush (ctrl->server_local->outstream))
+ {
+ err = gpg_error_from_syserror ();
+ log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
+ }
+
+ goto leave;
+ }
+
+ /* If we do not want logging, enable it here. */
+ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
+ ctrl->server_local->inhibit_data_logging_now = 1;
+
+ if (0 && opt.verbose && buffer && size)
+ {
+ /* Ease reading of output by limiting the line length. */
+ size_t n, nbytes;
+
+ nbytes = size;
+ do
+ {
+ n = nbytes > 64? 64 : nbytes;
+ err = assuan_send_data (ctx, buffer, n);
+ if (err)
+ {
+ gpg_err_set_errno (EIO);
+ goto leave;
+ }
+ buffer += n;
+ nbytes -= n;
+ if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */
+ {
+ gpg_err_set_errno (EIO);
+ goto leave;
+ }
+ }
+ while (nbytes);
+ }
+ else
+ {
+ err = assuan_send_data (ctx, buffer, size);
+ if (err)
+ {
+ gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */
+ goto leave;
+ }
+ }
+
+ leave:
+ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
+ {
+ ctrl->server_local->inhibit_data_logging_count += size;
+ ctrl->server_local->inhibit_data_logging_now = 0;
+ }
+
+ return err;
+}
+
+
+
+/* Helper to print a message while leaving a command. */
+static gpg_error_t
+leave_cmd (assuan_context_t ctx, gpg_error_t err)
+{
+ if (err && opt.verbose)
+ {
+ const char *name = assuan_get_command_name (ctx);
+ if (!name)
+ name = "?";
+ if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
+ log_error ("command '%s' failed: %s\n", name,
+ gpg_strerror (err));
+ else
+ log_error ("command '%s' failed: %s <%s>\n", name,
+ gpg_strerror (err), gpg_strsource (err));
+ }
+ return err;
+}
+
+
+
+/* Handle OPTION commands. */
+static gpg_error_t
+option_handler (assuan_context_t ctx, const char *key, const char *value)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+
+ if (!strcmp (key, "lc-messages"))
+ {
+ if (ctrl->lc_messages)
+ xfree (ctrl->lc_messages);
+ ctrl->lc_messages = xtrystrdup (value);
+ if (!ctrl->lc_messages)
+ return out_of_core ();
+ }
+ else
+ err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
+
+ return err;
+}
+
+
+
+static const char hlp_search[] =
+ "SEARCH [--no-data] [[--more] PATTERN]\n"
+ "\n"
+ "Search for the keys identified by PATTERN. With --more more\n"
+ "patterns to be used for the search are expected with the next\n"
+ "command. With --no-data only the search status is returned but\n"
+ "not the actual data. See also \"NEXT\".";
+static gpg_error_t
+cmd_search (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int opt_more, opt_no_data;
+ gpg_error_t err;
+ unsigned int n, k;
+
+ opt_no_data = has_option (line, "--no-data");
+ opt_more = has_option (line, "--more");
+ line = skip_options (line);
+
+ ctrl->server_local->search_any_found = 0;
+
+ if (!*line)
+ {
+ if (opt_more)
+ {
+ err = set_error (GPG_ERR_INV_ARG, "--more but no pattern");
+ goto leave;
+ }
+ else if (!*line && ctrl->server_local->search_expecting_more)
+ {
+ /* It would be too surprising to first set a pattern but
+ * finally add no pattern to search the entire DB. */
+ err = set_error (GPG_ERR_INV_ARG, "--more pending but no pattern");
+ goto leave;
+ }
+ else /* No pattern - return the first item. */
+ {
+ memset (&ctrl->server_local->search_desc, 0,
+ sizeof ctrl->server_local->search_desc);
+ ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_FIRST;
+ }
+ }
+ else
+ {
+ err = classify_user_id (line, &ctrl->server_local->search_desc, 0);
+ if (err)
+ goto leave;
+ }
+
+ if (opt_more || ctrl->server_local->search_expecting_more)
+ {
+ /* More pattern are expected - store the current one and return
+ * success. */
+ if (!ctrl->server_local->multi_search_desc_size)
+ {
+ n = 10;
+ ctrl->server_local->multi_search_desc
+ = xtrycalloc (n, sizeof *ctrl->server_local->multi_search_desc);
+ if (!ctrl->server_local->multi_search_desc)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ ctrl->server_local->multi_search_desc_size = n;
+ }
+
+ if (ctrl->server_local->multi_search_desc_len
+ == ctrl->server_local->multi_search_desc_size)
+ {
+ KEYBOX_SEARCH_DESC *desc;
+ n = ctrl->server_local->multi_search_desc_size + 10;
+ desc = xtrycalloc (n, sizeof *desc);
+ if (!desc)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ for (k=0; k < ctrl->server_local->multi_search_desc_size; k++)
+ desc[k] = ctrl->server_local->multi_search_desc[k];
+ xfree (ctrl->server_local->multi_search_desc);
+ ctrl->server_local->multi_search_desc = desc;
+ ctrl->server_local->multi_search_desc_size = n;
+ }
+ /* Actually store. */
+ ctrl->server_local->multi_search_desc
+ [ctrl->server_local->multi_search_desc_len++]
+ = ctrl->server_local->search_desc;
+
+ if (opt_more)
+ {
+ /* We need to be called aagain with more pattern. */
+ ctrl->server_local->search_expecting_more = 1;
+ goto leave;
+ }
+ ctrl->server_local->search_expecting_more = 0;
+ /* Continue with the actual search. */
+ }
+ else
+ ctrl->server_local->multi_search_desc_len = 0;
+
+ ctrl->server_local->inhibit_data_logging = 1;
+ ctrl->server_local->inhibit_data_logging_now = 0;
+ ctrl->server_local->inhibit_data_logging_count = 0;
+ ctrl->no_data_return = opt_no_data;
+ err = prepare_outstream (ctrl);
+ if (err)
+ ;
+ else if (ctrl->server_local->multi_search_desc_len)
+ err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc,
+ ctrl->server_local->multi_search_desc_len, 1);
+ else
+ err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 1);
+ if (err)
+ goto leave;
+
+ /* Set a flag for use by NEXT. */
+ ctrl->server_local->search_any_found = 1;
+
+ leave:
+ if (err)
+ ctrl->server_local->multi_search_desc_len = 0;
+ ctrl->no_data_return = 0;
+ ctrl->server_local->inhibit_data_logging = 0;
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_next[] =
+ "NEXT [--no-data]\n"
+ "\n"
+ "Get the next search result from a previus search.";
+static gpg_error_t
+cmd_next (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ int opt_no_data;
+ gpg_error_t err;
+
+ opt_no_data = has_option (line, "--no-data");
+ line = skip_options (line);
+
+ if (*line)
+ {
+ err = set_error (GPG_ERR_INV_ARG, "no args expected");
+ goto leave;
+ }
+
+ if (!ctrl->server_local->search_any_found)
+ {
+ err = set_error (GPG_ERR_NOTHING_FOUND, "no previous SEARCH");
+ goto leave;
+ }
+
+ ctrl->server_local->inhibit_data_logging = 1;
+ ctrl->server_local->inhibit_data_logging_now = 0;
+ ctrl->server_local->inhibit_data_logging_count = 0;
+ ctrl->no_data_return = opt_no_data;
+ err = prepare_outstream (ctrl);
+ if (err)
+ ;
+ else if (ctrl->server_local->multi_search_desc_len)
+ {
+ /* The next condition should never be tru but we better handle
+ * the first/next transition anyway. */
+ if (ctrl->server_local->multi_search_desc[0].mode
+ == KEYDB_SEARCH_MODE_FIRST)
+ ctrl->server_local->multi_search_desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
+
+ err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc,
+ ctrl->server_local->multi_search_desc_len, 0);
+ }
+ else
+ {
+ /* We need to do the transition from first to next here. */
+ if (ctrl->server_local->search_desc.mode == KEYDB_SEARCH_MODE_FIRST)
+ ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_NEXT;
+
+ err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0);
+ }
+ if (err)
+ goto leave;
+
+ leave:
+ ctrl->no_data_return = 0;
+ ctrl->server_local->inhibit_data_logging = 0;
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_getinfo[] =
+ "GETINFO <what>\n"
+ "\n"
+ "Multi purpose command to return certain information. \n"
+ "Supported values of WHAT are:\n"
+ "\n"
+ "version - Return the version of the program.\n"
+ "pid - Return the process id of the server.\n"
+ "socket_name - Return the name of the socket.\n"
+ "session_id - Return the current session_id.\n"
+ "getenv NAME - Return value of envvar NAME\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"))
+ {
+ const char *s = VERSION;
+ err = assuan_send_data (ctx, s, strlen (s));
+ }
+ else if (!strcmp (line, "pid"))
+ {
+ snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
+ err = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
+ else if (!strcmp (line, "socket_name"))
+ {
+ const char *s = get_kbxd_socket_name ();
+ if (!s)
+ s = "[none]";
+ 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 (!strncmp (line, "getenv", 6)
+ && (line[6] == ' ' || line[6] == '\t' || !line[6]))
+ {
+ line += 6;
+ while (*line == ' ' || *line == '\t')
+ line++;
+ if (!*line)
+ err = gpg_error (GPG_ERR_MISSING_VALUE);
+ else
+ {
+ const char *s = getenv (line);
+ if (!s)
+ err = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
+ else
+ err = assuan_send_data (ctx, s, strlen (s));
+ }
+ }
+ else
+ err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
+
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_killkeyboxd[] =
+ "KILLKEYBOXD\n"
+ "\n"
+ "This command allows a user - given sufficient permissions -\n"
+ "to kill this keyboxd process.\n";
+static gpg_error_t
+cmd_killkeyboxd (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)line;
+
+ ctrl->server_local->stopme = 1;
+ assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
+ return gpg_error (GPG_ERR_EOF);
+}
+
+
+static const char hlp_reloadkeyboxd[] =
+ "RELOADKEYBOXD\n"
+ "\n"
+ "This command is an alternative to SIGHUP\n"
+ "to reload the configuration.";
+static gpg_error_t
+cmd_reloadkeyboxd (assuan_context_t ctx, char *line)
+{
+ (void)ctx;
+ (void)line;
+
+ kbxd_sighup_action ();
+ return 0;
+}
+
+
+static const char hlp_output[] =
+ "OUTPUT FD[=<n>]\n"
+ "\n"
+ "Set the file descriptor to write the output data to N. If N is not\n"
+ "given and the operating system supports file descriptor passing, the\n"
+ "file descriptor currently in flight will be used.";
+
+
+/* Tell the assuan library about our commands. */
+static int
+register_commands (assuan_context_t ctx)
+{
+ static struct {
+ const char *name;
+ assuan_handler_t handler;
+ const char * const help;
+ } table[] = {
+ { "SEARCH", cmd_search, hlp_search },
+ { "NEXT", cmd_next, hlp_next },
+ { "GETINFO", cmd_getinfo, hlp_getinfo },
+ { "OUTPUT", NULL, hlp_output },
+ { "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd },
+ { "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd },
+ { NULL, NULL }
+ };
+ int i, j, rc;
+
+ for (i=j=0; table[i].name; i++)
+ {
+ rc = assuan_register_command (ctx, table[i].name, table[i].handler,
+ table[i].help);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+
+/* Note that we do not reset the list of configured keyservers. */
+static gpg_error_t
+reset_notify (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)line;
+ (void)ctrl;
+
+ return 0;
+}
+
+
+/* This function is called by our assuan log handler to test whether a
+ * log message shall really be printed. The function must return
+ * false to inhibit the logging of MSG. CAT gives the requested log
+ * category. MSG might be NULL. */
+int
+kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
+ const char *msg)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ (void)cat;
+ (void)msg;
+
+ if (!ctrl || !ctrl->server_local)
+ return 1; /* Can't decide - allow logging. */
+
+ if (!ctrl->server_local->inhibit_data_logging)
+ return 1; /* Not requested - allow logging. */
+
+ /* Disallow logging if *_now is true. */
+ return !ctrl->server_local->inhibit_data_logging_now;
+}
+
+
+/* Startup the server and run the main command loop. With FD = -1,
+ * use stdin/stdout. SESSION_ID is either 0 or a unique number
+ * identifying a session. */
+void
+kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id)
+{
+ static const char hello[] = "Keyboxd " VERSION " at your service";
+ static char *hello_line;
+ int rc;
+ assuan_context_t ctx;
+
+ ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
+ if (!ctrl->server_local)
+ {
+ log_error (_("can't allocate control structure: %s\n"),
+ gpg_strerror (gpg_error_from_syserror ()));
+ xfree (ctrl);
+ return;
+ }
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error (_("failed to allocate assuan context: %s\n"),
+ gpg_strerror (rc));
+ kbxd_exit (2);
+ }
+
+ if (fd == GNUPG_INVALID_FD)
+ {
+ assuan_fd_t filedes[2];
+
+ filedes[0] = assuan_fdopen (0);
+ filedes[1] = assuan_fdopen (1);
+ rc = assuan_init_pipe_server (ctx, filedes);
+ }
+ else
+ {
+ rc = assuan_init_socket_server (ctx, fd,
+ (ASSUAN_SOCKET_SERVER_ACCEPTED
+ |ASSUAN_SOCKET_SERVER_FDPASSING));
+ }
+
+ if (rc)
+ {
+ assuan_release (ctx);
+ log_error (_("failed to initialize the server: %s\n"),
+ gpg_strerror (rc));
+ kbxd_exit (2);
+ }
+
+ rc = register_commands (ctx);
+ if (rc)
+ {
+ log_error (_("failed to the register commands with Assuan: %s\n"),
+ gpg_strerror(rc));
+ kbxd_exit (2);
+ }
+
+
+ if (!hello_line)
+ {
+ hello_line = xtryasprintf
+ ("Home: %s\n"
+ "Config: %s\n"
+ "%s",
+ gnupg_homedir (),
+ /*opt.config_filename? opt.config_filename :*/ "[none]",
+ hello);
+ }
+
+ ctrl->server_local->assuan_ctx = ctx;
+ assuan_set_pointer (ctx, ctrl);
+
+ assuan_set_hello_line (ctx, hello_line);
+ assuan_register_option_handler (ctx, option_handler);
+ assuan_register_reset_notify (ctx, reset_notify);
+
+ ctrl->server_local->session_id = session_id;
+
+ /* The next call enable the use of status_printf. */
+ set_assuan_context_func (get_assuan_ctx_from_ctrl);
+
+ for (;;)
+ {
+ rc = assuan_accept (ctx);
+ if (rc == -1)
+ break;
+ if (rc)
+ {
+ log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc));
+ break;
+ }
+
+#ifndef HAVE_W32_SYSTEM
+ if (opt.verbose)
+ {
+ assuan_peercred_t peercred;
+
+ if (!assuan_get_peercred (ctx, &peercred))
+ log_info ("connection from process %ld (%ld:%ld)\n",
+ (long)peercred->pid, (long)peercred->uid,
+ (long)peercred->gid);
+ }
+#endif
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
+ continue;
+ }
+ }
+
+ assuan_close_output_fd (ctx);
+
+ set_assuan_context_func (NULL);
+ ctrl->server_local->assuan_ctx = NULL;
+ assuan_release (ctx);
+
+ if (ctrl->server_local->stopme)
+ kbxd_exit (0);
+
+ if (ctrl->refcount)
+ log_error ("oops: connection control structure still referenced (%d)\n",
+ ctrl->refcount);
+ else
+ {
+ xfree (ctrl->server_local->multi_search_desc);
+ xfree (ctrl->server_local);
+ ctrl->server_local = NULL;
+ }
+}