2018-09-05 17:00:17 +02:00
|
|
|
|
/* kbxserver.c - Handle Assuan commands send to the keyboxd
|
2019-08-06 16:07:33 +02:00
|
|
|
|
* Copyright (C) 2019 g10 Code GmbH
|
2018-09-05 17:00:17 +02:00
|
|
|
|
*
|
|
|
|
|
* 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"
|
2019-08-06 16:07:33 +02:00
|
|
|
|
#include "../common/userids.h"
|
|
|
|
|
#include "../common/asshelp.h"
|
2019-09-10 15:52:12 +02:00
|
|
|
|
#include "../common/host2net.h"
|
2019-08-06 16:07:33 +02:00
|
|
|
|
#include "frontend.h"
|
2018-09-05 17:00:17 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
|
2019-08-06 16:07:33 +02:00
|
|
|
|
/* 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;
|
2019-09-10 15:52:12 +02:00
|
|
|
|
|
|
|
|
|
/* If not NULL write output to this stream instead of using D lines. */
|
|
|
|
|
estream_t outstream;
|
2018-09-05 17:00:17 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-08-06 16:07:33 +02:00
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-09-10 15:52:12 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-08-06 16:07:33 +02:00
|
|
|
|
/* 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);
|
|
|
|
|
|
2019-09-10 15:52:12 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-06 16:07:33 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
|
2019-09-10 15:52:12 +02:00
|
|
|
|
if (0 && opt.verbose && buffer && size)
|
2019-08-06 16:07:33 +02:00
|
|
|
|
{
|
|
|
|
|
/* 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;
|
2019-09-10 15:52:12 +02:00
|
|
|
|
ctrl->server_local->inhibit_data_logging_now = 0;
|
2019-08-06 16:07:33 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-09-05 17:00:17 +02:00
|
|
|
|
|
|
|
|
|
/* Helper to print a message while leaving a command. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
leave_cmd (assuan_context_t ctx, gpg_error_t err)
|
|
|
|
|
{
|
2019-09-27 09:24:58 +02:00
|
|
|
|
if (err && opt.verbose)
|
2018-09-05 17:00:17 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
|
2019-08-06 16:07:33 +02:00
|
|
|
|
if (!strcmp (key, "lc-messages"))
|
2018-09-05 17:00:17 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-08-06 16:07:33 +02:00
|
|
|
|
static const char hlp_search[] =
|
|
|
|
|
"SEARCH [--no-data] [[--more] PATTERN]\n"
|
2018-09-05 17:00:17 +02:00
|
|
|
|
"\n"
|
2019-08-06 16:07:33 +02:00
|
|
|
|
"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\".";
|
2018-09-05 17:00:17 +02:00
|
|
|
|
static gpg_error_t
|
2019-08-06 16:07:33 +02:00
|
|
|
|
cmd_search (assuan_context_t ctx, char *line)
|
2018-09-05 17:00:17 +02:00
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
2019-08-06 16:07:33 +02:00
|
|
|
|
int opt_more, opt_no_data;
|
2018-09-05 17:00:17 +02:00
|
|
|
|
gpg_error_t err;
|
2019-08-06 16:07:33 +02:00
|
|
|
|
unsigned int n, k;
|
2018-09-05 17:00:17 +02:00
|
|
|
|
|
2019-08-06 16:07:33 +02:00
|
|
|
|
opt_no_data = has_option (line, "--no-data");
|
|
|
|
|
opt_more = has_option (line, "--more");
|
|
|
|
|
line = skip_options (line);
|
2018-09-05 17:00:17 +02:00
|
|
|
|
|
2019-08-06 16:07:33 +02:00
|
|
|
|
ctrl->server_local->search_any_found = 0;
|
2018-09-05 17:00:17 +02:00
|
|
|
|
|
2019-09-09 09:01:28 +02:00
|
|
|
|
if (!*line)
|
2019-08-06 16:07:33 +02:00
|
|
|
|
{
|
2019-09-09 09:01:28 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
2019-08-06 16:07:33 +02:00
|
|
|
|
}
|
2019-09-09 09:01:28 +02:00
|
|
|
|
else
|
2019-08-06 16:07:33 +02:00
|
|
|
|
{
|
2019-09-09 09:01:28 +02:00
|
|
|
|
err = classify_user_id (line, &ctrl->server_local->search_desc, 0);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2019-08-06 16:07:33 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2019-09-10 15:52:12 +02:00
|
|
|
|
ctrl->server_local->inhibit_data_logging = 1;
|
|
|
|
|
ctrl->server_local->inhibit_data_logging_now = 0;
|
|
|
|
|
ctrl->server_local->inhibit_data_logging_count = 0;
|
2019-08-06 16:07:33 +02:00
|
|
|
|
ctrl->no_data_return = opt_no_data;
|
2019-09-10 15:52:12 +02:00
|
|
|
|
err = prepare_outstream (ctrl);
|
|
|
|
|
if (err)
|
|
|
|
|
;
|
|
|
|
|
else if (ctrl->server_local->multi_search_desc_len)
|
2019-08-06 16:07:33 +02:00
|
|
|
|
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;
|
2019-09-10 15:52:12 +02:00
|
|
|
|
ctrl->server_local->inhibit_data_logging = 0;
|
2019-08-06 16:07:33 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char hlp_next[] =
|
|
|
|
|
"NEXT [--no-data]\n"
|
|
|
|
|
"\n"
|
Spelling cleanup.
No functional changes, just fixing minor spelling issues.
---
Most of these were identified from the command line by running:
codespell \
--ignore-words-list fpr,stati,keyserver,keyservers,asign,cas,iff,ifset \
--skip '*.po,ChangeLog*,help.*.txt,*.jpg,*.eps,*.pdf,*.png,*.gpg,*.asc' \
doc g13 g10 kbx agent artwork scd tests tools am common dirmngr sm \
NEWS README README.maint TODO
Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
2020-02-18 09:34:42 -05:00
|
|
|
|
"Get the next search result from a previous search.";
|
2019-08-06 16:07:33 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:52:12 +02:00
|
|
|
|
ctrl->server_local->inhibit_data_logging = 1;
|
|
|
|
|
ctrl->server_local->inhibit_data_logging_now = 0;
|
|
|
|
|
ctrl->server_local->inhibit_data_logging_count = 0;
|
2019-08-06 16:07:33 +02:00
|
|
|
|
ctrl->no_data_return = opt_no_data;
|
2019-09-10 15:52:12 +02:00
|
|
|
|
err = prepare_outstream (ctrl);
|
|
|
|
|
if (err)
|
|
|
|
|
;
|
|
|
|
|
else if (ctrl->server_local->multi_search_desc_len)
|
2019-09-09 14:25:02 +02:00
|
|
|
|
{
|
|
|
|
|
/* 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);
|
|
|
|
|
}
|
2019-08-06 16:07:33 +02:00
|
|
|
|
else
|
2019-09-09 14:25:02 +02:00
|
|
|
|
{
|
|
|
|
|
/* 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);
|
|
|
|
|
}
|
2019-08-06 16:07:33 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
ctrl->no_data_return = 0;
|
2019-09-10 15:52:12 +02:00
|
|
|
|
ctrl->server_local->inhibit_data_logging = 0;
|
2018-09-05 17:00:17 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-10-01 20:09:42 +02:00
|
|
|
|
static const char hlp_store[] =
|
2019-11-28 09:39:35 +01:00
|
|
|
|
"STORE [--update|--insert]\n"
|
2019-10-01 20:09:42 +02:00
|
|
|
|
"\n"
|
|
|
|
|
"Insert a key into the database. Whether to insert or update\n"
|
|
|
|
|
"the key is decided by looking at the primary key's fingerprint.\n"
|
2019-11-28 09:39:35 +01:00
|
|
|
|
"With option --update the key must already exist.\n"
|
|
|
|
|
"With option --insert the key must not already exist.\n"
|
|
|
|
|
"The actual key material is requested by this function using\n"
|
2019-10-01 20:09:42 +02:00
|
|
|
|
" INQUIRE BLOB";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_store (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
2019-11-28 09:39:35 +01:00
|
|
|
|
int opt_update, opt_insert;
|
|
|
|
|
enum kbxd_store_modes mode;
|
2019-10-01 20:09:42 +02:00
|
|
|
|
gpg_error_t err;
|
|
|
|
|
unsigned char *value = NULL;
|
|
|
|
|
size_t valuelen;
|
|
|
|
|
|
|
|
|
|
opt_update = has_option (line, "--update");
|
2019-11-28 09:39:35 +01:00
|
|
|
|
opt_insert = has_option (line, "--insert");
|
2019-10-01 20:09:42 +02:00
|
|
|
|
line = skip_options (line);
|
|
|
|
|
if (*line)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_ARG, "no args expected");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2019-11-28 09:39:35 +01:00
|
|
|
|
if (opt_update && !opt_insert)
|
|
|
|
|
mode = KBXD_STORE_UPDATE;
|
|
|
|
|
else if (!opt_update && opt_insert)
|
|
|
|
|
mode = KBXD_STORE_INSERT;
|
|
|
|
|
else
|
|
|
|
|
mode = KBXD_STORE_AUTO;
|
2019-10-01 20:09:42 +02:00
|
|
|
|
|
|
|
|
|
/* Ask for the key material. */
|
|
|
|
|
err = assuan_inquire (ctx, "BLOB", &value, &valuelen, 0);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!valuelen) /* No data received. */
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_MISSING_VALUE);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-28 09:39:35 +01:00
|
|
|
|
err = kbxd_store (ctrl, value, valuelen, mode);
|
2019-10-01 20:09:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (value);
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-11-28 11:19:33 +01:00
|
|
|
|
static const char hlp_delete[] =
|
|
|
|
|
"DELETE <ubid> \n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Delete a key into the database. The UBID identifies the key.\n";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_delete (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
int n;
|
|
|
|
|
unsigned char ubid[UBID_LEN];
|
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
if (!*line)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_ARG, "UBID missing");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Skip an optional UBID identifier character. */
|
|
|
|
|
if (*line == '^' && line[1])
|
|
|
|
|
line++;
|
|
|
|
|
if ((n=hex2bin (line, ubid, UBID_LEN)) < 0)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_USER_ID, "invalid UBID");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
if (line[n])
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_ARG, "garbage after UBID");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = kbxd_delete (ctrl, ubid);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-01 20:09:42 +02:00
|
|
|
|
|
2018-09-05 17:00:17 +02:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-09-10 15:52:12 +02:00
|
|
|
|
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.";
|
|
|
|
|
|
2018-09-05 17:00:17 +02:00
|
|
|
|
|
|
|
|
|
/* 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[] = {
|
2019-08-06 16:07:33 +02:00
|
|
|
|
{ "SEARCH", cmd_search, hlp_search },
|
|
|
|
|
{ "NEXT", cmd_next, hlp_next },
|
2019-10-01 20:09:42 +02:00
|
|
|
|
{ "STORE", cmd_store, hlp_store },
|
2019-11-28 11:19:33 +01:00
|
|
|
|
{ "DELETE", cmd_delete, hlp_delete },
|
2018-09-05 17:00:17 +02:00
|
|
|
|
{ "GETINFO", cmd_getinfo, hlp_getinfo },
|
2019-09-10 15:52:12 +02:00
|
|
|
|
{ "OUTPUT", NULL, hlp_output },
|
2018-09-05 17:00:17 +02:00
|
|
|
|
{ "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
|
|
|
|
|
{
|
2019-09-10 15:52:12 +02:00
|
|
|
|
rc = assuan_init_socket_server (ctx, fd,
|
|
|
|
|
(ASSUAN_SOCKET_SERVER_ACCEPTED
|
|
|
|
|
|ASSUAN_SOCKET_SERVER_FDPASSING));
|
2018-09-05 17:00:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:52:12 +02:00
|
|
|
|
assuan_close_output_fd (ctx);
|
2018-09-05 17:00:17 +02:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
{
|
2019-08-06 16:07:33 +02:00
|
|
|
|
xfree (ctrl->server_local->multi_search_desc);
|
2018-09-05 17:00:17 +02:00
|
|
|
|
xfree (ctrl->server_local);
|
|
|
|
|
ctrl->server_local = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|