2014-03-11 14:26:39 +01:00
|
|
|
|
/* server.c - LDAP and Keyserver access server
|
|
|
|
|
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
|
2015-03-28 17:23:56 +01:00
|
|
|
|
* Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011, 2015 g10 Code GmbH
|
2016-04-27 08:20:25 +02:00
|
|
|
|
* Copyright (C) 2014, 2015, 2016 Werner Koch
|
2017-06-19 12:37:52 +02:00
|
|
|
|
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
|
|
|
|
|
*
|
|
|
|
|
* This file is part of GnuPG.
|
|
|
|
|
*
|
2014-03-11 14:26:39 +01:00
|
|
|
|
* GnuPG is free software; you can redistribute it and/or modify
|
2010-06-09 18:53:51 +02:00
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
2014-03-11 14:26:39 +01:00
|
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
2010-06-09 18:53:51 +02:00
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
2014-03-11 14:26:39 +01:00
|
|
|
|
* GnuPG is distributed in the hope that it will be useful,
|
2010-06-09 18:53:51 +02:00
|
|
|
|
* 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
|
2016-11-05 12:02:19 +01:00
|
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
2017-11-13 16:09:32 +01:00
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-3.0+
|
2010-06-09 18:53:51 +02:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#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 "dirmngr.h"
|
|
|
|
|
#include <assuan.h>
|
|
|
|
|
|
|
|
|
|
#include "crlcache.h"
|
|
|
|
|
#include "crlfetch.h"
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#if USE_LDAP
|
|
|
|
|
# include "ldapserver.h"
|
|
|
|
|
#endif
|
2010-06-09 18:53:51 +02:00
|
|
|
|
#include "ocsp.h"
|
|
|
|
|
#include "certcache.h"
|
|
|
|
|
#include "validate.h"
|
|
|
|
|
#include "misc.h"
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#if USE_LDAP
|
|
|
|
|
# include "ldap-wrapper.h"
|
|
|
|
|
#endif
|
2011-01-10 14:30:17 +01:00
|
|
|
|
#include "ks-action.h"
|
2011-04-12 16:30:08 +02:00
|
|
|
|
#include "ks-engine.h" /* (ks_hkp_print_hosttable) */
|
2015-04-13 12:02:40 +02:00
|
|
|
|
#if USE_LDAP
|
|
|
|
|
# include "ldap-parse-uri.h"
|
|
|
|
|
#endif
|
2015-10-21 10:29:02 +02:00
|
|
|
|
#include "dns-stuff.h"
|
2017-03-07 12:21:23 +01:00
|
|
|
|
#include "../common/mbox-util.h"
|
|
|
|
|
#include "../common/zb32.h"
|
|
|
|
|
#include "../common/server-help.h"
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
/* To avoid DoS attacks we limit the size of a certificate to
|
2015-09-09 15:41:25 +02:00
|
|
|
|
something reasonable. The DoS was actually only an issue back when
|
|
|
|
|
Dirmngr was a system service and not a user service. */
|
|
|
|
|
#define MAX_CERT_LENGTH (16*1024)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2017-02-17 16:39:48 +01:00
|
|
|
|
/* The limit for the CERTLIST inquiry. We allow for up to 20
|
|
|
|
|
* certificates but also take PEM encoding into account. */
|
|
|
|
|
#define MAX_CERTLIST_LENGTH ((MAX_CERT_LENGTH * 20 * 4)/3)
|
|
|
|
|
|
2011-01-20 14:12:53 +01:00
|
|
|
|
/* The same goes for OpenPGP keyblocks, but here we need to allow for
|
|
|
|
|
much longer blocks; a 200k keyblock is not too unusual for keys
|
2015-08-23 21:16:39 +02:00
|
|
|
|
with a lot of signatures (e.g. 0x5b0358a2). 9C31503C6D866396 even
|
2015-09-09 15:41:25 +02:00
|
|
|
|
has 770 KiB as of 2015-08-23. To avoid adding a runtime option we
|
|
|
|
|
now use 20MiB which should really be enough. Well, a key with
|
|
|
|
|
several pictures could be larger (the parser as a 18MiB limit for
|
|
|
|
|
attribute packets) but it won't be nice to the keyservers to send
|
|
|
|
|
them such large blobs. */
|
|
|
|
|
#define MAX_KEYBLOCK_LENGTH (20*1024*1024)
|
2011-01-20 14:12:53 +01:00
|
|
|
|
|
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
#define PARM_ERROR(t) assuan_set_error (ctx, \
|
|
|
|
|
gpg_error (GPG_ERR_ASS_PARAMETER), (t))
|
2017-11-14 16:24:12 +01:00
|
|
|
|
#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \
|
|
|
|
|
/**/: gpg_error (e))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Control structure per connection. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
struct server_local_s
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
/* Data used to associate an Assuan context with local server data */
|
|
|
|
|
assuan_context_t assuan_ctx;
|
|
|
|
|
|
2017-11-14 13:42:18 +01:00
|
|
|
|
/* The session id (a counter). */
|
|
|
|
|
unsigned int session_id;
|
|
|
|
|
|
2011-01-10 14:30:17 +01:00
|
|
|
|
/* Per-session LDAP servers. */
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ldap_server_t ldapservers;
|
2010-08-12 13:43:46 +02:00
|
|
|
|
|
2015-03-28 17:23:56 +01:00
|
|
|
|
/* Per-session list of keyservers. */
|
|
|
|
|
uri_item_t keyservers;
|
|
|
|
|
|
2010-08-12 13:43:46 +02:00
|
|
|
|
/* If this flag is set to true this dirmngr process will be
|
|
|
|
|
terminated after the end of this session. */
|
|
|
|
|
int stopme;
|
2015-12-02 11:49:41 +01:00
|
|
|
|
|
|
|
|
|
/* State variable private to is_tor_running. */
|
|
|
|
|
int tor_state;
|
2016-09-05 12:50:35 +02:00
|
|
|
|
|
|
|
|
|
/* 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;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
/* Cookie definition for assuan data line output. */
|
2015-11-27 18:30:14 +01:00
|
|
|
|
static gpgrt_ssize_t data_line_cookie_write (void *cookie,
|
|
|
|
|
const void *buffer, size_t size);
|
2010-08-06 15:52:01 +02:00
|
|
|
|
static int data_line_cookie_close (void *cookie);
|
|
|
|
|
static es_cookie_io_functions_t data_line_cookie_functions =
|
|
|
|
|
{
|
|
|
|
|
NULL,
|
|
|
|
|
data_line_cookie_write,
|
|
|
|
|
NULL,
|
|
|
|
|
data_line_cookie_close
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2017-11-14 13:42:18 +01:00
|
|
|
|
/* Local prototypes */
|
|
|
|
|
static const char *task_check_wkd_support (ctrl_t ctrl, const char *domain);
|
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Accessor for the local ldapservers variable. */
|
|
|
|
|
ldap_server_t
|
|
|
|
|
get_ldapservers_from_ctrl (ctrl_t ctrl)
|
|
|
|
|
{
|
|
|
|
|
if (ctrl && ctrl->server_local)
|
|
|
|
|
return ctrl->server_local->ldapservers;
|
|
|
|
|
else
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 11:49:41 +01:00
|
|
|
|
/* Release an uri_item_t list. */
|
|
|
|
|
static void
|
|
|
|
|
release_uri_item_list (uri_item_t list)
|
|
|
|
|
{
|
|
|
|
|
while (list)
|
|
|
|
|
{
|
|
|
|
|
uri_item_t tmp = list->next;
|
|
|
|
|
http_release_parsed_uri (list->parsed_uri);
|
|
|
|
|
xfree (list);
|
|
|
|
|
list = tmp;
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2011-01-10 14:30:17 +01:00
|
|
|
|
/* Release all configured keyserver info from CTRL. */
|
|
|
|
|
void
|
|
|
|
|
release_ctrl_keyservers (ctrl_t ctrl)
|
|
|
|
|
{
|
2015-03-28 17:23:56 +01:00
|
|
|
|
if (! ctrl->server_local)
|
|
|
|
|
return;
|
|
|
|
|
|
2015-12-02 11:49:41 +01:00
|
|
|
|
release_uri_item_list (ctrl->server_local->keyservers);
|
|
|
|
|
ctrl->server_local->keyservers = NULL;
|
2011-01-10 14:30:17 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-08-06 15:52:01 +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)
|
|
|
|
|
{
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-23 15:42:56 +02:00
|
|
|
|
|
|
|
|
|
/* This is a wrapper around assuan_send_data which makes debugging the
|
|
|
|
|
output in verbose mode easier. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
data_line_write (assuan_context_t ctx, const void *buffer_arg, size_t size)
|
2010-08-06 15:52:01 +02:00
|
|
|
|
{
|
2016-09-05 12:50:35 +02:00
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
2014-03-11 14:27:58 +01:00
|
|
|
|
const char *buffer = buffer_arg;
|
2015-04-23 15:42:56 +02:00
|
|
|
|
gpg_error_t err;
|
2010-08-06 15:52:01 +02:00
|
|
|
|
|
2017-02-20 22:19:50 +01:00
|
|
|
|
/* If we do not want logging, enable it here. */
|
2016-09-05 12:50:35 +02:00
|
|
|
|
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
|
|
|
|
|
ctrl->server_local->inhibit_data_logging_now = 1;
|
|
|
|
|
|
2014-03-11 14:27:58 +01:00
|
|
|
|
if (opt.verbose && buffer && size)
|
2010-08-06 15:52:01 +02:00
|
|
|
|
{
|
2014-03-11 14:27:58 +01:00
|
|
|
|
/* Ease reading of output by sending a physical line at each LF. */
|
|
|
|
|
const char *p;
|
|
|
|
|
size_t n, nbytes;
|
|
|
|
|
|
|
|
|
|
nbytes = size;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
p = memchr (buffer, '\n', nbytes);
|
|
|
|
|
n = p ? (p - buffer) + 1 : nbytes;
|
2015-04-23 15:42:56 +02:00
|
|
|
|
err = assuan_send_data (ctx, buffer, n);
|
|
|
|
|
if (err)
|
2014-03-11 14:27:58 +01:00
|
|
|
|
{
|
|
|
|
|
gpg_err_set_errno (EIO);
|
2016-09-05 12:50:35 +02:00
|
|
|
|
goto leave;
|
2014-03-11 14:27:58 +01:00
|
|
|
|
}
|
|
|
|
|
buffer += n;
|
|
|
|
|
nbytes -= n;
|
2015-04-23 15:42:56 +02:00
|
|
|
|
if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */
|
2014-03-11 14:27:58 +01:00
|
|
|
|
{
|
|
|
|
|
gpg_err_set_errno (EIO);
|
2016-09-05 12:50:35 +02:00
|
|
|
|
goto leave;
|
2014-03-11 14:27:58 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
while (nbytes);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-04-23 15:42:56 +02:00
|
|
|
|
err = assuan_send_data (ctx, buffer, size);
|
|
|
|
|
if (err)
|
2014-03-11 14:27:58 +01:00
|
|
|
|
{
|
2015-04-23 15:42:56 +02:00
|
|
|
|
gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */
|
2016-09-05 12:50:35 +02:00
|
|
|
|
goto leave;
|
2014-03-11 14:27:58 +01:00
|
|
|
|
}
|
2010-08-06 15:52:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-05 12:50:35 +02:00
|
|
|
|
leave:
|
|
|
|
|
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
|
|
|
|
|
{
|
|
|
|
|
ctrl->server_local->inhibit_data_logging_now = 0;
|
|
|
|
|
ctrl->server_local->inhibit_data_logging_count += size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err;
|
2010-08-06 15:52:01 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-23 15:42:56 +02:00
|
|
|
|
|
|
|
|
|
/* A write handler used by es_fopencookie to write assuan data
|
|
|
|
|
lines. */
|
2015-11-27 18:30:14 +01:00
|
|
|
|
static gpgrt_ssize_t
|
2015-04-23 15:42:56 +02:00
|
|
|
|
data_line_cookie_write (void *cookie, const void *buffer, size_t size)
|
|
|
|
|
{
|
|
|
|
|
assuan_context_t ctx = cookie;
|
|
|
|
|
|
|
|
|
|
if (data_line_write (ctx, buffer, size))
|
|
|
|
|
return -1;
|
2015-11-27 18:30:14 +01:00
|
|
|
|
return (gpgrt_ssize_t)size;
|
2015-04-23 15:42:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
static int
|
|
|
|
|
data_line_cookie_close (void *cookie)
|
|
|
|
|
{
|
|
|
|
|
assuan_context_t ctx = cookie;
|
|
|
|
|
|
2016-09-05 12:50:35 +02:00
|
|
|
|
if (DBG_IPC)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
|
|
|
|
|
if (ctrl && ctrl->server_local
|
|
|
|
|
&& ctrl->server_local->inhibit_data_logging
|
|
|
|
|
&& ctrl->server_local->inhibit_data_logging_count)
|
|
|
|
|
log_debug ("(%zu bytes sent via D lines not shown)\n",
|
|
|
|
|
ctrl->server_local->inhibit_data_logging_count);
|
|
|
|
|
}
|
2010-08-06 15:52:01 +02:00
|
|
|
|
if (assuan_send_data (ctx, NULL, 0))
|
|
|
|
|
{
|
|
|
|
|
gpg_err_set_errno (EIO);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
/* Copy the % and + escaped string S into the buffer D and replace the
|
|
|
|
|
escape sequences. Note, that it is sufficient to allocate the
|
|
|
|
|
target string D as long as the source string S, i.e.: strlen(s)+1.
|
2011-01-10 14:30:17 +01:00
|
|
|
|
Note further that if S contains an escaped binary Nul the resulting
|
2010-06-09 18:53:51 +02:00
|
|
|
|
string D will contain the 0 as well as all other characters but it
|
|
|
|
|
will be impossible to know whether this is the original EOS or a
|
|
|
|
|
copied Nul. */
|
|
|
|
|
static void
|
|
|
|
|
strcpy_escaped_plus (char *d, const unsigned char *s)
|
|
|
|
|
{
|
|
|
|
|
while (*s)
|
|
|
|
|
{
|
|
|
|
|
if (*s == '%' && s[1] && s[2])
|
|
|
|
|
{
|
|
|
|
|
s++;
|
|
|
|
|
*d++ = xtoi_2 ( s);
|
|
|
|
|
s += 2;
|
|
|
|
|
}
|
|
|
|
|
else if (*s == '+')
|
|
|
|
|
*d++ = ' ', s++;
|
|
|
|
|
else
|
|
|
|
|
*d++ = *s++;
|
|
|
|
|
}
|
|
|
|
|
*d = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-07-26 10:58:15 +02:00
|
|
|
|
/* This function returns true if a Tor server is running. The status
|
|
|
|
|
* is cached for the current connection. */
|
2015-12-02 11:49:41 +01:00
|
|
|
|
static int
|
|
|
|
|
is_tor_running (ctrl_t ctrl)
|
|
|
|
|
{
|
2016-07-14 10:40:15 +02:00
|
|
|
|
/* Check whether we can connect to the proxy. */
|
2015-12-02 11:49:41 +01:00
|
|
|
|
|
|
|
|
|
if (!ctrl || !ctrl->server_local)
|
|
|
|
|
return 0; /* Ooops. */
|
|
|
|
|
|
|
|
|
|
if (!ctrl->server_local->tor_state)
|
|
|
|
|
{
|
|
|
|
|
assuan_fd_t sock;
|
|
|
|
|
|
|
|
|
|
sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR);
|
|
|
|
|
if (sock == ASSUAN_INVALID_FD)
|
|
|
|
|
ctrl->server_local->tor_state = -1; /* Not running. */
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
assuan_sock_close (sock);
|
|
|
|
|
ctrl->server_local->tor_state = 1; /* Running. */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return (ctrl->server_local->tor_state > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-05-16 20:58:58 +02:00
|
|
|
|
/* Return an error if the assuan context does not belong to the owner
|
2014-03-11 16:19:41 +01:00
|
|
|
|
of the process or to root. On error FAILTEXT is set as Assuan
|
|
|
|
|
error string. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
check_owner_permission (assuan_context_t ctx, const char *failtext)
|
|
|
|
|
{
|
|
|
|
|
#ifdef HAVE_W32_SYSTEM
|
|
|
|
|
/* Under Windows the dirmngr is always run under the control of the
|
|
|
|
|
user. */
|
|
|
|
|
(void)ctx;
|
|
|
|
|
(void)failtext;
|
|
|
|
|
#else
|
|
|
|
|
gpg_err_code_t ec;
|
|
|
|
|
assuan_peercred_t cred;
|
|
|
|
|
|
|
|
|
|
ec = gpg_err_code (assuan_get_peercred (ctx, &cred));
|
|
|
|
|
if (!ec && cred->uid && cred->uid != getuid ())
|
|
|
|
|
ec = GPG_ERR_EPERM;
|
|
|
|
|
if (ec)
|
|
|
|
|
return set_error (ec, failtext);
|
|
|
|
|
#endif
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
/* Common code for get_cert_local and get_issuer_cert_local. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static ksba_cert_t
|
2010-06-09 18:53:51 +02:00
|
|
|
|
do_get_cert_local (ctrl_t ctrl, const char *name, const char *command)
|
|
|
|
|
{
|
|
|
|
|
unsigned char *value;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
int rc;
|
|
|
|
|
char *buf;
|
|
|
|
|
ksba_cert_t cert;
|
|
|
|
|
|
2017-02-18 16:09:16 +01:00
|
|
|
|
buf = name? strconcat (command, " ", name, NULL) : xtrystrdup (command);
|
|
|
|
|
if (!buf)
|
|
|
|
|
rc = gpg_error_from_syserror ();
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else
|
2017-02-18 16:09:16 +01:00
|
|
|
|
{
|
|
|
|
|
rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
|
|
|
|
|
&value, &valuelen, MAX_CERT_LENGTH);
|
|
|
|
|
xfree (buf);
|
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire(%s) failed: %s\n"),
|
|
|
|
|
command, gpg_strerror (rc));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!valuelen)
|
|
|
|
|
{
|
|
|
|
|
xfree (value);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc = ksba_cert_new (&cert);
|
|
|
|
|
if (!rc)
|
|
|
|
|
{
|
|
|
|
|
rc = ksba_cert_init_from_mem (cert, value, valuelen);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
ksba_cert_release (cert);
|
|
|
|
|
cert = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
xfree (value);
|
|
|
|
|
return cert;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-02-16 10:35:18 +01:00
|
|
|
|
/* Ask back to return a certificate for NAME, given as a regular gpgsm
|
|
|
|
|
* certificate identifier (e.g. fingerprint or one of the other
|
|
|
|
|
* methods). Alternatively, NULL may be used for NAME to return the
|
|
|
|
|
* current target certificate. Either return the certificate in a
|
|
|
|
|
* KSBA object or NULL if it is not available. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
ksba_cert_t
|
2010-06-09 18:53:51 +02:00
|
|
|
|
get_cert_local (ctrl_t ctrl, const char *name)
|
|
|
|
|
{
|
|
|
|
|
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
|
|
|
|
|
{
|
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("get_cert_local called w/o context\n");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return do_get_cert_local (ctrl, name, "SENDCERT");
|
|
|
|
|
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
|
|
|
|
|
2017-02-16 10:35:18 +01:00
|
|
|
|
/* Ask back to return the issuing certificate for NAME, given as a
|
|
|
|
|
* regular gpgsm certificate identifier (e.g. fingerprint or one
|
|
|
|
|
* of the other methods). Alternatively, NULL may be used for NAME to
|
|
|
|
|
* return the current target certificate. Either return the certificate
|
|
|
|
|
* in a KSBA object or NULL if it is not available. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
ksba_cert_t
|
2010-06-09 18:53:51 +02:00
|
|
|
|
get_issuing_cert_local (ctrl_t ctrl, const char *name)
|
|
|
|
|
{
|
|
|
|
|
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
|
|
|
|
|
{
|
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("get_issuing_cert_local called w/o context\n");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return do_get_cert_local (ctrl, name, "SENDISSUERCERT");
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-16 10:35:18 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
/* Ask back to return a certificate with subject NAME and a
|
2017-02-16 10:35:18 +01:00
|
|
|
|
* subjectKeyIdentifier of KEYID. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
ksba_cert_t
|
2010-06-09 18:53:51 +02:00
|
|
|
|
get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid)
|
|
|
|
|
{
|
|
|
|
|
unsigned char *value;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
int rc;
|
|
|
|
|
char *buf;
|
|
|
|
|
ksba_cert_t cert;
|
|
|
|
|
char *hexkeyid;
|
|
|
|
|
|
|
|
|
|
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
|
|
|
|
|
{
|
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("get_cert_local_ski called w/o context\n");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
if (!name || !keyid)
|
|
|
|
|
{
|
|
|
|
|
log_debug ("get_cert_local_ski called with insufficient arguments\n");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hexkeyid = serial_hex (keyid);
|
|
|
|
|
if (!hexkeyid)
|
|
|
|
|
{
|
|
|
|
|
log_debug ("serial_hex() failed\n");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-16 10:19:59 +01:00
|
|
|
|
buf = strconcat ("SENDCERT_SKI ", hexkeyid, " /", name, NULL);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!buf)
|
|
|
|
|
{
|
|
|
|
|
log_error ("can't allocate enough memory: %s\n", strerror (errno));
|
|
|
|
|
xfree (hexkeyid);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
xfree (hexkeyid);
|
|
|
|
|
|
|
|
|
|
rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
|
|
|
|
|
&value, &valuelen, MAX_CERT_LENGTH);
|
|
|
|
|
xfree (buf);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire(%s) failed: %s\n"), "SENDCERT_SKI",
|
|
|
|
|
gpg_strerror (rc));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!valuelen)
|
|
|
|
|
{
|
|
|
|
|
xfree (value);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc = ksba_cert_new (&cert);
|
|
|
|
|
if (!rc)
|
|
|
|
|
{
|
|
|
|
|
rc = ksba_cert_init_from_mem (cert, value, valuelen);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
ksba_cert_release (cert);
|
|
|
|
|
cert = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
xfree (value);
|
|
|
|
|
return cert;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Ask the client via an inquiry to check the istrusted status of the
|
|
|
|
|
certificate specified by the hexified fingerprint HEXFPR. Returns
|
|
|
|
|
0 if the certificate is trusted by the client or an error code. */
|
|
|
|
|
gpg_error_t
|
|
|
|
|
get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr)
|
|
|
|
|
{
|
|
|
|
|
unsigned char *value;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
int rc;
|
|
|
|
|
char request[100];
|
|
|
|
|
|
|
|
|
|
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx
|
|
|
|
|
|| !hexfpr)
|
|
|
|
|
return gpg_error (GPG_ERR_INV_ARG);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr);
|
|
|
|
|
rc = assuan_inquire (ctrl->server_local->assuan_ctx, request,
|
|
|
|
|
&value, &valuelen, 100);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire(%s) failed: %s\n"),
|
|
|
|
|
request, gpg_strerror (rc));
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
/* The expected data is: "1" or "1 cruft" (not a C-string). */
|
|
|
|
|
if (valuelen && *value == '1' && (valuelen == 1 || spacep (value+1)))
|
|
|
|
|
rc = 0;
|
|
|
|
|
else
|
|
|
|
|
rc = gpg_error (GPG_ERR_NOT_TRUSTED);
|
|
|
|
|
xfree (value);
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Ask the client to return the certificate associated with the
|
|
|
|
|
current command. This is sometimes needed because the client usually
|
|
|
|
|
sends us just the cert ID, assuming that the request can be
|
|
|
|
|
satisfied from the cache, where the cert ID is used as key. */
|
|
|
|
|
static int
|
|
|
|
|
inquire_cert_and_load_crl (assuan_context_t ctx)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
unsigned char *value = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ksba_cert_t cert = NULL;
|
|
|
|
|
|
|
|
|
|
err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
/* { */
|
|
|
|
|
/* FILE *fp = fopen ("foo.der", "r"); */
|
|
|
|
|
/* value = xmalloc (2000); */
|
|
|
|
|
/* valuelen = fread (value, 1, 2000, fp); */
|
|
|
|
|
/* fclose (fp); */
|
|
|
|
|
/* } */
|
|
|
|
|
|
|
|
|
|
if (!valuelen) /* No data returned; return a comprehensible error. */
|
|
|
|
|
return gpg_error (GPG_ERR_MISSING_CERT);
|
|
|
|
|
|
|
|
|
|
err = ksba_cert_new (&cert);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = ksba_cert_init_from_mem (cert, value, valuelen);
|
|
|
|
|
if(err)
|
|
|
|
|
goto leave;
|
|
|
|
|
xfree (value); value = NULL;
|
|
|
|
|
|
|
|
|
|
err = crl_cache_reload_crl (ctrl, cert);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
ksba_cert_release (cert);
|
|
|
|
|
xfree (value);
|
|
|
|
|
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);
|
2015-04-21 17:14:53 +02:00
|
|
|
|
gpg_error_t err = 0;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
if (!strcmp (key, "force-crl-refresh"))
|
|
|
|
|
{
|
|
|
|
|
int i = *value? atoi (value) : 0;
|
|
|
|
|
ctrl->force_crl_refresh = i;
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp (key, "audit-events"))
|
|
|
|
|
{
|
|
|
|
|
int i = *value? atoi (value) : 0;
|
|
|
|
|
ctrl->audit_events = i;
|
|
|
|
|
}
|
2015-04-21 17:14:53 +02:00
|
|
|
|
else if (!strcmp (key, "http-proxy"))
|
|
|
|
|
{
|
|
|
|
|
xfree (ctrl->http_proxy);
|
|
|
|
|
if (!*value || !strcmp (value, "none"))
|
|
|
|
|
ctrl->http_proxy = NULL;
|
|
|
|
|
else if (!(ctrl->http_proxy = xtrystrdup (value)))
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
}
|
2015-09-18 16:17:11 +02:00
|
|
|
|
else if (!strcmp (key, "honor-keyserver-url-used"))
|
|
|
|
|
{
|
2015-10-21 18:14:24 +02:00
|
|
|
|
/* Return an error if we are running in Tor mode. */
|
2017-02-01 17:54:14 +01:00
|
|
|
|
if (dirmngr_use_tor ())
|
2015-09-18 16:17:11 +02:00
|
|
|
|
err = gpg_error (GPG_ERR_FORBIDDEN);
|
|
|
|
|
}
|
2017-02-21 09:37:07 +01:00
|
|
|
|
else if (!strcmp (key, "http-crl"))
|
|
|
|
|
{
|
|
|
|
|
int i = *value? atoi (value) : 0;
|
|
|
|
|
ctrl->http_no_crl = !i;
|
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else
|
2015-04-21 17:14:53 +02:00
|
|
|
|
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2015-04-21 17:14:53 +02:00
|
|
|
|
return err;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-21 17:14:53 +02:00
|
|
|
|
|
2015-04-23 15:42:56 +02:00
|
|
|
|
|
|
|
|
|
static const char hlp_dns_cert[] =
|
|
|
|
|
"DNS_CERT <subtype> <name>\n"
|
|
|
|
|
"DNS_CERT --pka <user_id>\n"
|
2015-10-06 19:59:56 +02:00
|
|
|
|
"DNS_CERT --dane <user_id>\n"
|
2015-04-23 15:42:56 +02:00
|
|
|
|
"\n"
|
|
|
|
|
"Return the CERT record for <name>. <subtype> is one of\n"
|
|
|
|
|
" * Return the first record of any supported subtype\n"
|
|
|
|
|
" PGP Return the first record of subtype PGP (3)\n"
|
|
|
|
|
" IPGP Return the first record of subtype IPGP (6)\n"
|
2016-04-27 08:20:25 +02:00
|
|
|
|
"If the content of a certificate is available (PGP) it is returned\n"
|
2015-04-23 15:42:56 +02:00
|
|
|
|
"by data lines. Fingerprints and URLs are returned via status lines.\n"
|
2015-10-06 19:59:56 +02:00
|
|
|
|
"In --pka mode the fingerprint and if available an URL is returned.\n"
|
|
|
|
|
"In --dane mode the key is returned from RR type 61";
|
2015-04-23 15:42:56 +02:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_dns_cert (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
2018-07-25 14:35:04 +02:00
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
2015-04-23 15:42:56 +02:00
|
|
|
|
gpg_error_t err = 0;
|
2015-10-06 19:59:56 +02:00
|
|
|
|
int pka_mode, dane_mode;
|
2015-04-23 15:42:56 +02:00
|
|
|
|
char *mbox = NULL;
|
|
|
|
|
char *namebuf = NULL;
|
|
|
|
|
char *encodedhash = NULL;
|
|
|
|
|
const char *name;
|
|
|
|
|
int certtype;
|
|
|
|
|
char *p;
|
|
|
|
|
void *key = NULL;
|
|
|
|
|
size_t keylen;
|
|
|
|
|
unsigned char *fpr = NULL;
|
|
|
|
|
size_t fprlen;
|
|
|
|
|
char *url = NULL;
|
|
|
|
|
|
|
|
|
|
pka_mode = has_option (line, "--pka");
|
2015-10-06 19:59:56 +02:00
|
|
|
|
dane_mode = has_option (line, "--dane");
|
2015-04-23 15:42:56 +02:00
|
|
|
|
line = skip_options (line);
|
2015-10-06 19:59:56 +02:00
|
|
|
|
|
|
|
|
|
if (pka_mode && dane_mode)
|
|
|
|
|
{
|
|
|
|
|
err = PARM_ERROR ("either --pka or --dane may be given");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pka_mode || dane_mode)
|
2015-04-23 15:42:56 +02:00
|
|
|
|
; /* No need to parse here - we do this later. */
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
p = strchr (line, ' ');
|
|
|
|
|
if (!p)
|
|
|
|
|
{
|
|
|
|
|
err = PARM_ERROR ("missing arguments");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
*p++ = 0;
|
|
|
|
|
if (!strcmp (line, "*"))
|
|
|
|
|
certtype = DNS_CERTTYPE_ANY;
|
|
|
|
|
else if (!strcmp (line, "IPGP"))
|
|
|
|
|
certtype = DNS_CERTTYPE_IPGP;
|
|
|
|
|
else if (!strcmp (line, "PGP"))
|
|
|
|
|
certtype = DNS_CERTTYPE_PGP;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = PARM_ERROR ("unknown subtype");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
while (spacep (p))
|
|
|
|
|
p++;
|
|
|
|
|
line = p;
|
|
|
|
|
if (!*line)
|
|
|
|
|
{
|
|
|
|
|
err = PARM_ERROR ("name missing");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-06 19:59:56 +02:00
|
|
|
|
if (pka_mode || dane_mode)
|
2015-04-23 15:42:56 +02:00
|
|
|
|
{
|
2015-10-06 19:59:56 +02:00
|
|
|
|
char *domain; /* Points to mbox. */
|
|
|
|
|
char hashbuf[32]; /* For SHA-1 and SHA-256. */
|
2015-04-23 15:42:56 +02:00
|
|
|
|
|
2015-10-06 19:59:56 +02:00
|
|
|
|
/* We lowercase ascii characters but the DANE I-D does not allow
|
|
|
|
|
this. FIXME: Check after the release of the RFC whether to
|
|
|
|
|
change this. */
|
2018-11-12 07:44:33 +01:00
|
|
|
|
mbox = mailbox_from_userid (line, 0);
|
2015-04-23 15:42:56 +02:00
|
|
|
|
if (!mbox || !(domain = strchr (mbox, '@')))
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
*domain++ = 0;
|
|
|
|
|
|
2015-10-06 19:59:56 +02:00
|
|
|
|
if (pka_mode)
|
2015-04-23 15:42:56 +02:00
|
|
|
|
{
|
2015-10-06 19:59:56 +02:00
|
|
|
|
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox));
|
|
|
|
|
encodedhash = zb32_encode (hashbuf, 8*20);
|
|
|
|
|
if (!encodedhash)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
namebuf = strconcat (encodedhash, "._pka.", domain, NULL);
|
|
|
|
|
if (!namebuf)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
name = namebuf;
|
|
|
|
|
certtype = DNS_CERTTYPE_IPGP;
|
2015-04-23 15:42:56 +02:00
|
|
|
|
}
|
2015-10-06 19:59:56 +02:00
|
|
|
|
else
|
2015-04-23 15:42:56 +02:00
|
|
|
|
{
|
2015-10-06 19:59:56 +02:00
|
|
|
|
/* Note: The hash is truncated to 28 bytes and we lowercase
|
|
|
|
|
the result only for aesthetic reasons. */
|
|
|
|
|
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox));
|
|
|
|
|
encodedhash = bin2hex (hashbuf, 28, NULL);
|
|
|
|
|
if (!encodedhash)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
ascii_strlwr (encodedhash);
|
|
|
|
|
namebuf = strconcat (encodedhash, "._openpgpkey.", domain, NULL);
|
|
|
|
|
if (!namebuf)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
name = namebuf;
|
|
|
|
|
certtype = DNS_CERTTYPE_RR61;
|
2015-04-23 15:42:56 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
name = line;
|
|
|
|
|
|
2018-07-25 14:35:04 +02:00
|
|
|
|
err = get_dns_cert (ctrl, name, certtype, &key, &keylen, &fpr, &fprlen, &url);
|
2015-04-23 15:42:56 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
if (key)
|
|
|
|
|
{
|
|
|
|
|
err = data_line_write (ctx, key, keylen);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fpr)
|
|
|
|
|
{
|
|
|
|
|
char *tmpstr;
|
|
|
|
|
|
|
|
|
|
tmpstr = bin2hex (fpr, fprlen, NULL);
|
|
|
|
|
if (!tmpstr)
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = assuan_write_status (ctx, "FPR", tmpstr);
|
|
|
|
|
xfree (tmpstr);
|
|
|
|
|
}
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (url)
|
|
|
|
|
{
|
|
|
|
|
err = assuan_write_status (ctx, "URL", url);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (key);
|
|
|
|
|
xfree (fpr);
|
|
|
|
|
xfree (url);
|
|
|
|
|
xfree (mbox);
|
|
|
|
|
xfree (namebuf);
|
|
|
|
|
xfree (encodedhash);
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-04-27 08:20:25 +02:00
|
|
|
|
|
2017-11-14 16:24:12 +01:00
|
|
|
|
/* Core of cmd_wkd_get and task_check_wkd_support. If CTX is NULL
|
|
|
|
|
* this function will not write anything to the assuan output. */
|
2016-04-27 08:20:25 +02:00
|
|
|
|
static gpg_error_t
|
2017-11-14 16:24:12 +01:00
|
|
|
|
proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line)
|
2016-04-27 08:20:25 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
char *mbox = NULL;
|
2017-01-08 18:42:50 +01:00
|
|
|
|
char *domainbuf = NULL;
|
2018-11-13 11:35:39 +01:00
|
|
|
|
char *domain; /* Points to mbox or domainbuf. This is used to
|
|
|
|
|
* connect to the host. */
|
|
|
|
|
char *domain_orig;/* Points to mbox. This is the used for the
|
|
|
|
|
* query; i.e. the domain part of the
|
|
|
|
|
* addrspec. */
|
2016-04-27 08:20:25 +02:00
|
|
|
|
char sha1buf[20];
|
|
|
|
|
char *uri = NULL;
|
|
|
|
|
char *encodedhash = NULL;
|
2016-06-28 15:50:38 +02:00
|
|
|
|
int opt_submission_addr;
|
2016-09-02 16:52:17 +02:00
|
|
|
|
int opt_policy_flags;
|
2017-11-13 16:09:32 +01:00
|
|
|
|
int is_wkd_query; /* True if this is a real WKD query. */
|
2016-09-05 12:50:35 +02:00
|
|
|
|
int no_log = 0;
|
2017-01-08 18:42:50 +01:00
|
|
|
|
char portstr[20] = { 0 };
|
2018-11-13 11:35:39 +01:00
|
|
|
|
int subdomain_mode = 0;
|
2016-04-27 08:20:25 +02:00
|
|
|
|
|
2016-06-28 15:50:38 +02:00
|
|
|
|
opt_submission_addr = has_option (line, "--submission-address");
|
2016-09-02 16:52:17 +02:00
|
|
|
|
opt_policy_flags = has_option (line, "--policy-flags");
|
2017-06-08 09:30:48 +02:00
|
|
|
|
if (has_option (line, "--quick"))
|
|
|
|
|
ctrl->timeout = opt.connect_quick_timeout;
|
2016-04-27 08:20:25 +02:00
|
|
|
|
line = skip_options (line);
|
2017-11-13 16:09:32 +01:00
|
|
|
|
is_wkd_query = !(opt_policy_flags || opt_submission_addr);
|
2016-04-27 08:20:25 +02:00
|
|
|
|
|
2018-11-12 07:44:33 +01:00
|
|
|
|
mbox = mailbox_from_userid (line, 0);
|
2016-04-27 08:20:25 +02:00
|
|
|
|
if (!mbox || !(domain = strchr (mbox, '@')))
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
*domain++ = 0;
|
2017-11-13 16:09:32 +01:00
|
|
|
|
domain_orig = domain;
|
|
|
|
|
|
2018-11-13 11:35:39 +01:00
|
|
|
|
|
|
|
|
|
/* Let's check whether we already know that the domain does not
|
2017-11-13 16:09:32 +01:00
|
|
|
|
* support WKD. */
|
|
|
|
|
if (is_wkd_query)
|
|
|
|
|
{
|
|
|
|
|
if (domaininfo_is_wkd_not_supported (domain_orig))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_NO_DATA);
|
2019-12-06 20:04:56 +01:00
|
|
|
|
dirmngr_status_printf (ctrl, "NOTE", "wkd_cached_result %u", err);
|
2017-11-13 16:09:32 +01:00
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-04-27 08:20:25 +02:00
|
|
|
|
|
2018-11-13 11:35:39 +01:00
|
|
|
|
|
|
|
|
|
/* First try the new "openpgp" subdomain. We check that the domain
|
|
|
|
|
* is valid because it is later used as an unescaped filename part
|
|
|
|
|
* of the URI. */
|
|
|
|
|
if (is_valid_domain_name (domain_orig))
|
|
|
|
|
{
|
|
|
|
|
dns_addrinfo_t aibuf;
|
|
|
|
|
|
|
|
|
|
domainbuf = strconcat ( "openpgpkey.", domain_orig, NULL);
|
|
|
|
|
if (!domainbuf)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* FIXME: We should put a cache into dns-stuff because the same
|
|
|
|
|
* query (with a different port and socket type, though) will be
|
|
|
|
|
* done later by http function. */
|
|
|
|
|
err = resolve_dns_name (ctrl, domainbuf, 0, 0, 0, &aibuf, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
err = 0;
|
|
|
|
|
xfree (domainbuf);
|
|
|
|
|
domainbuf = NULL;
|
|
|
|
|
}
|
|
|
|
|
else /* Got a subdomain. */
|
|
|
|
|
{
|
|
|
|
|
free_dns_addrinfo (aibuf);
|
|
|
|
|
subdomain_mode = 1;
|
|
|
|
|
domain = domainbuf;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check for SRV records unless we have a subdomain. */
|
|
|
|
|
if (!subdomain_mode)
|
2017-01-08 18:42:50 +01:00
|
|
|
|
{
|
|
|
|
|
struct srventry *srvs;
|
|
|
|
|
unsigned int srvscount;
|
|
|
|
|
size_t domainlen, targetlen;
|
|
|
|
|
int i;
|
|
|
|
|
|
2018-07-25 14:35:04 +02:00
|
|
|
|
err = get_dns_srv (ctrl, domain, "openpgpkey", NULL, &srvs, &srvscount);
|
2017-01-08 18:42:50 +01:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2018-07-27 12:23:38 +02:00
|
|
|
|
/* Check for rogue DNS names. */
|
|
|
|
|
for (i = 0; i < srvscount; i++)
|
|
|
|
|
{
|
|
|
|
|
if (!is_valid_domain_name (srvs[i].target))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_DNS_ADDRESS);
|
|
|
|
|
log_error ("rogue openpgpkey SRV record for '%s'\n", domain);
|
|
|
|
|
xfree (srvs);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-08 18:42:50 +01:00
|
|
|
|
/* Find the first target which also ends in DOMAIN or is equal
|
|
|
|
|
* to DOMAIN. */
|
|
|
|
|
domainlen = strlen (domain);
|
|
|
|
|
for (i = 0; i < srvscount; i++)
|
|
|
|
|
{
|
2017-11-14 16:24:12 +01:00
|
|
|
|
if (DBG_DNS)
|
|
|
|
|
log_debug ("srv: trying '%s:%hu'\n", srvs[i].target, srvs[i].port);
|
2017-01-08 18:42:50 +01:00
|
|
|
|
targetlen = strlen (srvs[i].target);
|
|
|
|
|
if ((targetlen > domainlen + 1
|
|
|
|
|
&& srvs[i].target[targetlen - domainlen - 1] == '.'
|
|
|
|
|
&& !ascii_strcasecmp (srvs[i].target + targetlen - domainlen,
|
|
|
|
|
domain))
|
|
|
|
|
|| (targetlen == domainlen
|
|
|
|
|
&& !ascii_strcasecmp (srvs[i].target, domain)))
|
|
|
|
|
{
|
|
|
|
|
/* found. */
|
|
|
|
|
domainbuf = xtrystrdup (srvs[i].target);
|
|
|
|
|
if (!domainbuf)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
xfree (srvs);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
domain = domainbuf;
|
|
|
|
|
if (srvs[i].port)
|
|
|
|
|
snprintf (portstr, sizeof portstr, ":%hu", srvs[i].port);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
xfree (srvs);
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-13 11:35:39 +01:00
|
|
|
|
/* Prepare the hash of the local part. */
|
2016-04-27 08:20:25 +02:00
|
|
|
|
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox));
|
|
|
|
|
encodedhash = zb32_encode (sha1buf, 8*20);
|
|
|
|
|
if (!encodedhash)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-28 15:50:38 +02:00
|
|
|
|
if (opt_submission_addr)
|
|
|
|
|
{
|
|
|
|
|
uri = strconcat ("https://",
|
|
|
|
|
domain,
|
2017-01-08 18:42:50 +01:00
|
|
|
|
portstr,
|
2018-11-13 11:35:39 +01:00
|
|
|
|
"/.well-known/openpgpkey/",
|
|
|
|
|
subdomain_mode? domain_orig : "",
|
|
|
|
|
subdomain_mode? "/" : "",
|
|
|
|
|
"submission-address",
|
2016-06-28 15:50:38 +02:00
|
|
|
|
NULL);
|
|
|
|
|
}
|
2016-09-02 16:52:17 +02:00
|
|
|
|
else if (opt_policy_flags)
|
|
|
|
|
{
|
|
|
|
|
uri = strconcat ("https://",
|
|
|
|
|
domain,
|
2017-01-08 18:42:50 +01:00
|
|
|
|
portstr,
|
2018-11-13 11:35:39 +01:00
|
|
|
|
"/.well-known/openpgpkey/",
|
|
|
|
|
subdomain_mode? domain_orig : "",
|
|
|
|
|
subdomain_mode? "/" : "",
|
|
|
|
|
"policy",
|
2016-09-02 16:52:17 +02:00
|
|
|
|
NULL);
|
|
|
|
|
}
|
2016-06-28 15:50:38 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
2018-10-22 20:13:08 +02:00
|
|
|
|
char *escapedmbox;
|
|
|
|
|
|
|
|
|
|
escapedmbox = http_escape_string (mbox, "%;?&=");
|
|
|
|
|
if (escapedmbox)
|
2017-07-24 18:21:11 +02:00
|
|
|
|
{
|
2018-10-22 20:13:08 +02:00
|
|
|
|
uri = strconcat ("https://",
|
|
|
|
|
domain,
|
|
|
|
|
portstr,
|
2018-11-13 11:35:39 +01:00
|
|
|
|
"/.well-known/openpgpkey/",
|
|
|
|
|
subdomain_mode? domain_orig : "",
|
|
|
|
|
subdomain_mode? "/" : "",
|
|
|
|
|
"hu/",
|
2018-10-22 20:13:08 +02:00
|
|
|
|
encodedhash,
|
|
|
|
|
"?l=",
|
|
|
|
|
escapedmbox,
|
|
|
|
|
NULL);
|
|
|
|
|
xfree (escapedmbox);
|
|
|
|
|
no_log = 1;
|
|
|
|
|
if (uri)
|
|
|
|
|
{
|
|
|
|
|
err = dirmngr_status_printf (ctrl, "SOURCE", "https://%s%s",
|
|
|
|
|
domain, portstr);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2017-07-24 18:21:11 +02:00
|
|
|
|
}
|
2016-06-28 15:50:38 +02:00
|
|
|
|
}
|
2016-04-27 08:20:25 +02:00
|
|
|
|
if (!uri)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Setup an output stream and perform the get. */
|
|
|
|
|
{
|
|
|
|
|
estream_t outfp;
|
|
|
|
|
|
2017-11-14 16:24:12 +01:00
|
|
|
|
outfp = ctx? es_fopencookie (ctx, "w", data_line_cookie_functions) : NULL;
|
|
|
|
|
if (!outfp && ctx)
|
2016-06-28 15:50:38 +02:00
|
|
|
|
err = set_error (GPG_ERR_ASS_GENERAL,
|
|
|
|
|
"error setting up a data stream");
|
2016-04-27 08:20:25 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
2017-11-14 16:24:12 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2016-04-27 08:20:25 +02:00
|
|
|
|
err = ks_action_fetch (ctrl, uri, outfp);
|
|
|
|
|
es_fclose (outfp);
|
2017-11-14 16:24:12 +01:00
|
|
|
|
if (ctrl->server_local)
|
|
|
|
|
ctrl->server_local->inhibit_data_logging = 0;
|
|
|
|
|
|
2017-11-13 16:09:32 +01:00
|
|
|
|
/* 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:
|
2017-11-14 16:24:12 +01:00
|
|
|
|
if (is_wkd_query && ctrl->server_local)
|
2017-11-14 13:42:18 +01:00
|
|
|
|
{
|
2017-11-14 16:24:12 +01:00
|
|
|
|
/* Mark that and schedule a check. */
|
2017-11-14 13:42:18 +01:00
|
|
|
|
domaininfo_set_wkd_not_found (domain_orig);
|
|
|
|
|
workqueue_add_task (task_check_wkd_support, domain_orig,
|
|
|
|
|
ctrl->server_local->session_id, 1);
|
|
|
|
|
}
|
2017-11-13 16:09:32 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2016-04-27 08:20:25 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (uri);
|
|
|
|
|
xfree (encodedhash);
|
|
|
|
|
xfree (mbox);
|
2017-01-08 18:42:50 +01:00
|
|
|
|
xfree (domainbuf);
|
2017-11-14 16:24:12 +01:00
|
|
|
|
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);
|
|
|
|
|
|
2016-04-27 08:20:25 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-11-14 13:42:18 +01:00
|
|
|
|
/* 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)
|
|
|
|
|
{
|
2017-11-14 16:24:12 +01:00
|
|
|
|
char *string;
|
|
|
|
|
|
2017-11-14 13:42:18 +01:00
|
|
|
|
if (!ctrl || !domain)
|
|
|
|
|
return "check_wkd_support";
|
|
|
|
|
|
2017-11-14 16:24:12 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
2017-11-14 13:42:18 +01:00
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-04-23 15:42:56 +02:00
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static const char hlp_ldapserver[] =
|
2021-05-26 15:49:14 +02:00
|
|
|
|
"LDAPSERVER [--clear] <data>\n"
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"\n"
|
|
|
|
|
"Add a new LDAP server to the list of configured LDAP servers.\n"
|
2021-05-26 14:48:27 +02:00
|
|
|
|
"DATA is in the same format as expected in the configure file.\n"
|
2021-05-26 15:49:14 +02:00
|
|
|
|
"An optional prefix \"ldap:\" is allowed. With no args all\n"
|
|
|
|
|
"configured ldapservers are listed. Option --clear removes all\n"
|
|
|
|
|
"servers configured in this session.";
|
2010-06-09 18:53:51 +02:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_ldapserver (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#if USE_LDAP
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
ldap_server_t server;
|
|
|
|
|
ldap_server_t *last_next_p;
|
2021-05-26 15:49:14 +02:00
|
|
|
|
int clear_flag;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2021-05-26 15:49:14 +02:00
|
|
|
|
clear_flag = has_option (line, "--clear");
|
|
|
|
|
line = skip_options (line);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
while (spacep (line))
|
|
|
|
|
line++;
|
2021-05-26 15:49:14 +02:00
|
|
|
|
|
|
|
|
|
if (clear_flag)
|
|
|
|
|
{
|
|
|
|
|
#if USE_LDAP
|
|
|
|
|
ldapserver_list_free (ctrl->server_local->ldapservers);
|
|
|
|
|
#endif /*USE_LDAP*/
|
|
|
|
|
ctrl->server_local->ldapservers = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!*line && clear_flag)
|
|
|
|
|
return leave_cmd (ctx, 0);
|
|
|
|
|
|
|
|
|
|
if (!*line)
|
|
|
|
|
{
|
|
|
|
|
/* List all ldapservers. */
|
|
|
|
|
struct ldapserver_iter ldapserver_iter;
|
|
|
|
|
char *tmpstr;
|
|
|
|
|
char portstr[20];
|
|
|
|
|
|
|
|
|
|
for (ldapserver_iter_begin (&ldapserver_iter, ctrl);
|
|
|
|
|
!ldapserver_iter_end_p (&ldapserver_iter);
|
|
|
|
|
ldapserver_iter_next (&ldapserver_iter))
|
|
|
|
|
{
|
|
|
|
|
server = ldapserver_iter.server;
|
|
|
|
|
if (server->port)
|
|
|
|
|
snprintf (portstr, sizeof portstr, "%d", server->port);
|
|
|
|
|
else
|
|
|
|
|
*portstr = 0;
|
|
|
|
|
|
|
|
|
|
tmpstr = xtryasprintf ("ldap:%s:%s:%s:%s:%s:%s%s:",
|
|
|
|
|
server->host? server->host : "",
|
|
|
|
|
portstr,
|
|
|
|
|
server->user? server->user : "",
|
2021-05-28 15:20:57 +02:00
|
|
|
|
server->pass? "*****": "",
|
2021-05-26 15:49:14 +02:00
|
|
|
|
server->base? server->base : "",
|
|
|
|
|
server->starttls ? "starttls" :
|
|
|
|
|
server->ldap_over_tls ? "ldaptls" : "none",
|
|
|
|
|
server->ntds ? ",ntds" : "");
|
|
|
|
|
if (!tmpstr)
|
|
|
|
|
return leave_cmd (ctx, gpg_error_from_syserror ());
|
|
|
|
|
dirmngr_status (ctrl, "LDAPSERVER", tmpstr, NULL);
|
|
|
|
|
xfree (tmpstr);
|
|
|
|
|
}
|
|
|
|
|
return leave_cmd (ctx, 0);
|
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2021-05-26 14:48:27 +02:00
|
|
|
|
/* Skip an "ldap:" prefix unless it is a valid ldap url. */
|
|
|
|
|
if (!strncmp (line, "ldap:", 5) && !(line[5] == '/' && line[6] == '/'))
|
|
|
|
|
line += 5;
|
|
|
|
|
|
|
|
|
|
server = ldapserver_parse_one (line, NULL, 0);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (! server)
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, gpg_error (GPG_ERR_INV_ARG));
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
last_next_p = &ctrl->server_local->ldapservers;
|
|
|
|
|
while (*last_next_p)
|
|
|
|
|
last_next_p = &(*last_next_p)->next;
|
|
|
|
|
*last_next_p = server;
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, 0);
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#else
|
|
|
|
|
(void)line;
|
|
|
|
|
return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED));
|
|
|
|
|
#endif
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static const char hlp_isvalid[] =
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"ISVALID [--only-ocsp] [--force-default-responder]"
|
2018-04-24 11:40:51 +02:00
|
|
|
|
" <certificate_id> [<certificate_fpr>]\n"
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"\n"
|
|
|
|
|
"This command checks whether the certificate identified by the\n"
|
|
|
|
|
"certificate_id is valid. This is done by consulting CRLs or\n"
|
|
|
|
|
"whatever has been configured. Note, that the returned error codes\n"
|
|
|
|
|
"are from gpg-error.h. The command may callback using the inquire\n"
|
|
|
|
|
"function. See the manual for details.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n"
|
|
|
|
|
"delimited by a single dot. The first part is the SHA-1 hash of the\n"
|
|
|
|
|
"issuer name and the second part the serial number.\n"
|
|
|
|
|
"\n"
|
2018-04-24 11:40:51 +02:00
|
|
|
|
"If an OCSP check is desired CERTIFICATE_FPR with the hex encoded\n"
|
|
|
|
|
"fingerprint of the certificate is required. In this case an OCSP\n"
|
|
|
|
|
"request is done before consulting the CRL.\n"
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"\n"
|
|
|
|
|
"If the option --only-ocsp is given, no fallback to a CRL check will\n"
|
|
|
|
|
"be used.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"If the option --force-default-responder is given, only the default\n"
|
|
|
|
|
"OCSP responder will be used and any other methods of obtaining an\n"
|
|
|
|
|
"OCSP responder URL won't be used.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_isvalid (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
2018-04-24 11:40:51 +02:00
|
|
|
|
char *issuerhash, *serialno, *fpr;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
gpg_error_t err;
|
|
|
|
|
int did_inquire = 0;
|
|
|
|
|
int ocsp_mode = 0;
|
|
|
|
|
int only_ocsp;
|
|
|
|
|
int force_default_responder;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
only_ocsp = has_option (line, "--only-ocsp");
|
|
|
|
|
force_default_responder = has_option (line, "--force-default-responder");
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
2018-04-24 11:40:51 +02:00
|
|
|
|
/* We need to work on a copy of the line because that same Assuan
|
|
|
|
|
* context may be used for an inquiry. That is because Assuan
|
|
|
|
|
* reuses its line buffer. */
|
|
|
|
|
issuerhash = xstrdup (line);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
serialno = strchr (issuerhash, '.');
|
2018-04-24 11:40:51 +02:00
|
|
|
|
if (!serialno)
|
|
|
|
|
{
|
|
|
|
|
xfree (issuerhash);
|
|
|
|
|
return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID")));
|
|
|
|
|
}
|
|
|
|
|
*serialno++ = 0;
|
|
|
|
|
if (strlen (issuerhash) != 40)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
2018-04-24 11:40:51 +02:00
|
|
|
|
xfree (issuerhash);
|
|
|
|
|
return leave_cmd (ctx, PARM_ERROR ("cert ID is too short"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fpr = strchr (serialno, ' ');
|
|
|
|
|
while (fpr && spacep (fpr))
|
|
|
|
|
fpr++;
|
|
|
|
|
if (fpr && *fpr)
|
|
|
|
|
{
|
|
|
|
|
char *endp = strchr (fpr, ' ');
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (endp)
|
|
|
|
|
*endp = 0;
|
2018-04-24 11:40:51 +02:00
|
|
|
|
if (strlen (fpr) != 40)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
xfree (issuerhash);
|
2018-04-24 11:40:51 +02:00
|
|
|
|
return leave_cmd (ctx, PARM_ERROR ("fingerprint too short"));
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
ocsp_mode = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
again:
|
|
|
|
|
if (ocsp_mode)
|
|
|
|
|
{
|
2018-04-24 11:40:51 +02:00
|
|
|
|
/* Note, that we currently ignore the supplied fingerprint FPR;
|
|
|
|
|
* instead ocsp_isvalid does an inquire to ask for the cert.
|
|
|
|
|
* The fingerprint may eventually be used to lookup the
|
|
|
|
|
* certificate in a local cache. */
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!opt.allow_ocsp)
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
|
|
|
|
|
else
|
|
|
|
|
err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder);
|
2018-04-24 11:40:51 +02:00
|
|
|
|
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_CONFIGURATION
|
|
|
|
|
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR)
|
|
|
|
|
{
|
|
|
|
|
/* No default responder configured - fallback to CRL. */
|
|
|
|
|
if (!only_ocsp)
|
|
|
|
|
log_info ("falling back to CRL check\n");
|
|
|
|
|
ocsp_mode = 0;
|
|
|
|
|
goto again;
|
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
else if (only_ocsp)
|
|
|
|
|
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
else
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
switch (crl_cache_isvalid (ctrl,
|
|
|
|
|
issuerhash, serialno,
|
|
|
|
|
ctrl->force_crl_refresh))
|
|
|
|
|
{
|
|
|
|
|
case CRL_CACHE_VALID:
|
|
|
|
|
err = 0;
|
|
|
|
|
break;
|
|
|
|
|
case CRL_CACHE_INVALID:
|
|
|
|
|
err = gpg_error (GPG_ERR_CERT_REVOKED);
|
|
|
|
|
break;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
case CRL_CACHE_DONTKNOW:
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (did_inquire)
|
|
|
|
|
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
|
|
|
|
|
else if (!(err = inquire_cert_and_load_crl (ctx)))
|
|
|
|
|
{
|
|
|
|
|
did_inquire = 1;
|
|
|
|
|
goto again;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
case CRL_CACHE_CANTUSE:
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
log_fatal ("crl_cache_isvalid returned invalid code\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xfree (issuerhash);
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* If the line contains a SHA-1 fingerprint as the first argument,
|
|
|
|
|
return the FPR vuffer on success. The function checks that the
|
|
|
|
|
fingerprint consists of valid characters and prints and error
|
|
|
|
|
message if it does not and returns NULL. Fingerprints are
|
|
|
|
|
considered optional and thus no explicit error is returned. NULL is
|
2011-02-04 12:57:53 +01:00
|
|
|
|
also returned if there is no fingerprint at all available.
|
2010-06-09 18:53:51 +02:00
|
|
|
|
FPR must be a caller provided buffer of at least 20 bytes.
|
|
|
|
|
|
|
|
|
|
Note that colons within the fingerprint are allowed to separate 2
|
|
|
|
|
hex digits; this allows for easier cutting and pasting using the
|
|
|
|
|
usual fingerprint rendering.
|
|
|
|
|
*/
|
|
|
|
|
static unsigned char *
|
|
|
|
|
get_fingerprint_from_line (const char *line, unsigned char *fpr)
|
|
|
|
|
{
|
|
|
|
|
const char *s;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (s=line, i=0; *s && *s != ' '; s++ )
|
|
|
|
|
{
|
|
|
|
|
if ( hexdigitp (s) && hexdigitp (s+1) )
|
|
|
|
|
{
|
|
|
|
|
if ( i >= 20 )
|
|
|
|
|
return NULL; /* Fingerprint too long. */
|
|
|
|
|
fpr[i++] = xtoi_2 (s);
|
|
|
|
|
s++;
|
|
|
|
|
}
|
|
|
|
|
else if ( *s != ':' )
|
|
|
|
|
return NULL; /* Invalid. */
|
|
|
|
|
}
|
|
|
|
|
if ( i != 20 )
|
|
|
|
|
return NULL; /* Fingerprint to short. */
|
|
|
|
|
return fpr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static const char hlp_checkcrl[] =
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"CHECKCRL [<fingerprint>]\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
|
|
|
|
|
"entire X.509 certificate blob) is valid or not by consulting the\n"
|
|
|
|
|
"CRL responsible for this certificate. If the fingerprint has not\n"
|
|
|
|
|
"been given or the certificate is not known, the function \n"
|
|
|
|
|
"inquires the certificate using an\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" INQUIRE TARGETCERT\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"and the caller is expected to return the certificate for the\n"
|
|
|
|
|
"request (which should match FINGERPRINT) as a binary blob.\n"
|
|
|
|
|
"Processing then takes place without further interaction; in\n"
|
|
|
|
|
"particular dirmngr tries to locate other required certificate by\n"
|
|
|
|
|
"its own mechanism which includes a local certificate store as well\n"
|
|
|
|
|
"as a list of trusted root certificates.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"The return value is the usual gpg-error code or 0 for ducesss;\n"
|
|
|
|
|
"i.e. the certificate validity has been confirmed by a valid CRL.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_checkcrl (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
unsigned char fprbuffer[20], *fpr;
|
|
|
|
|
ksba_cert_t cert;
|
|
|
|
|
|
|
|
|
|
fpr = get_fingerprint_from_line (line, fprbuffer);
|
|
|
|
|
cert = fpr? get_cert_byfpr (fpr) : NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!cert)
|
|
|
|
|
{
|
|
|
|
|
/* We do not have this certificate yet or the fingerprint has
|
|
|
|
|
not been given. Inquire it from the client. */
|
|
|
|
|
unsigned char *value = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
|
|
|
|
|
&value, &valuelen, MAX_CERT_LENGTH);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!valuelen) /* No data returned; return a comprehensible error. */
|
|
|
|
|
err = gpg_error (GPG_ERR_MISSING_CERT);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = ksba_cert_new (&cert);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = ksba_cert_init_from_mem (cert, value, valuelen);
|
|
|
|
|
}
|
|
|
|
|
xfree (value);
|
|
|
|
|
if(err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert (cert);
|
|
|
|
|
|
|
|
|
|
err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh);
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN)
|
|
|
|
|
{
|
|
|
|
|
err = crl_cache_reload_crl (ctrl, cert);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = crl_cache_cert_isvalid (ctrl, cert, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
ksba_cert_release (cert);
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static const char hlp_checkocsp[] =
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"CHECKOCSP [--force-default-responder] [<fingerprint>]\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
|
|
|
|
|
"entire X.509 certificate blob) is valid or not by asking an OCSP\n"
|
|
|
|
|
"responder responsible for this certificate. The optional\n"
|
|
|
|
|
"fingerprint may be used for a quick check in case an OCSP check has\n"
|
|
|
|
|
"been done for this certificate recently (we always cache OCSP\n"
|
|
|
|
|
"responses for a couple of minutes). If the fingerprint has not been\n"
|
|
|
|
|
"given or there is no cached result, the function inquires the\n"
|
|
|
|
|
"certificate using an\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" INQUIRE TARGETCERT\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"and the caller is expected to return the certificate for the\n"
|
|
|
|
|
"request (which should match FINGERPRINT) as a binary blob.\n"
|
|
|
|
|
"Processing then takes place without further interaction; in\n"
|
|
|
|
|
"particular dirmngr tries to locate other required certificates by\n"
|
|
|
|
|
"its own mechanism which includes a local certificate store as well\n"
|
2016-04-27 08:20:25 +02:00
|
|
|
|
"as a list of trusted root certificates.\n"
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"\n"
|
|
|
|
|
"If the option --force-default-responder is given, only the default\n"
|
|
|
|
|
"OCSP responder will be used and any other methods of obtaining an\n"
|
|
|
|
|
"OCSP responder URL won't be used.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"The return value is the usual gpg-error code or 0 for ducesss;\n"
|
|
|
|
|
"i.e. the certificate validity has been confirmed by a valid CRL.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_checkocsp (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
unsigned char fprbuffer[20], *fpr;
|
|
|
|
|
ksba_cert_t cert;
|
|
|
|
|
int force_default_responder;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
force_default_responder = has_option (line, "--force-default-responder");
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
fpr = get_fingerprint_from_line (line, fprbuffer);
|
|
|
|
|
cert = fpr? get_cert_byfpr (fpr) : NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!cert)
|
|
|
|
|
{
|
|
|
|
|
/* We do not have this certificate yet or the fingerprint has
|
|
|
|
|
not been given. Inquire it from the client. */
|
|
|
|
|
unsigned char *value = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
|
|
|
|
|
&value, &valuelen, MAX_CERT_LENGTH);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!valuelen) /* No data returned; return a comprehensible error. */
|
|
|
|
|
err = gpg_error (GPG_ERR_MISSING_CERT);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = ksba_cert_new (&cert);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = ksba_cert_init_from_mem (cert, value, valuelen);
|
|
|
|
|
}
|
|
|
|
|
xfree (value);
|
|
|
|
|
if(err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert (cert);
|
|
|
|
|
|
|
|
|
|
if (!opt.allow_ocsp)
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
|
|
|
|
|
else
|
|
|
|
|
err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
ksba_cert_release (cert);
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
lookup_cert_by_url (assuan_context_t ctx, const char *url)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
unsigned char *value = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
/* Fetch single certificate given it's URL. */
|
|
|
|
|
err = fetch_cert_by_url (ctrl, url, &value, &valuelen);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Send the data, flush the buffer and then send an END. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
err = assuan_send_data (ctx, value, valuelen);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_send_data (ctx, NULL, 0);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_write_line (ctx, "END");
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (err)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
log_error (_("error sending data: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Send the certificate, flush the buffer and then send an END. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
return_one_cert (void *opaque, ksba_cert_t cert)
|
|
|
|
|
{
|
|
|
|
|
assuan_context_t ctx = opaque;
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
const unsigned char *der;
|
|
|
|
|
size_t derlen;
|
|
|
|
|
|
|
|
|
|
der = ksba_cert_get_image (cert, &derlen);
|
|
|
|
|
if (!der)
|
|
|
|
|
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
|
|
|
|
|
else
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
err = assuan_send_data (ctx, der, derlen);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_send_data (ctx, NULL, 0);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_write_line (ctx, "END");
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (err)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
log_error (_("error sending data: %s\n"), gpg_strerror (err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Lookup certificates from the internal cache or using the ldap
|
|
|
|
|
servers. */
|
|
|
|
|
static int
|
2011-02-04 12:57:53 +01:00
|
|
|
|
lookup_cert_by_pattern (assuan_context_t ctx, char *line,
|
2010-06-09 18:53:51 +02:00
|
|
|
|
int single, int cache_only)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
char *p;
|
|
|
|
|
strlist_t sl, list = NULL;
|
|
|
|
|
int truncated = 0, truncation_forced = 0;
|
|
|
|
|
int count = 0;
|
|
|
|
|
int local_count = 0;
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#if USE_LDAP
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
unsigned char *value = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
struct ldapserver_iter ldapserver_iter;
|
|
|
|
|
cert_fetch_context_t fetch_context;
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#endif /*USE_LDAP*/
|
2010-06-09 18:53:51 +02:00
|
|
|
|
int any_no_data = 0;
|
|
|
|
|
|
|
|
|
|
/* Break the line down into an STRLIST */
|
|
|
|
|
for (p=line; *p; line = p)
|
|
|
|
|
{
|
|
|
|
|
while (*p && *p != ' ')
|
|
|
|
|
p++;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (*p)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
*p++ = 0;
|
|
|
|
|
|
|
|
|
|
if (*line)
|
|
|
|
|
{
|
|
|
|
|
sl = xtrymalloc (sizeof *sl + strlen (line));
|
|
|
|
|
if (!sl)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_errno (errno);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
memset (sl, 0, sizeof *sl);
|
|
|
|
|
strcpy_escaped_plus (sl->d, line);
|
|
|
|
|
sl->next = list;
|
|
|
|
|
list = sl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-28 03:06:33 +02:00
|
|
|
|
/* First look through the internal cache. The certificates returned
|
2010-06-09 18:53:51 +02:00
|
|
|
|
here are not counted towards the truncation limit. */
|
|
|
|
|
if (single && !cache_only)
|
|
|
|
|
; /* Do not read from the local cache in this case. */
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for (sl=list; sl; sl = sl->next)
|
|
|
|
|
{
|
|
|
|
|
err = get_certs_bypattern (sl->d, return_one_cert, ctx);
|
|
|
|
|
if (!err)
|
|
|
|
|
local_count++;
|
|
|
|
|
if (!err && single)
|
2011-02-04 12:57:53 +01:00
|
|
|
|
goto ready;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2021-06-08 08:46:59 +02:00
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_NO_DATA
|
|
|
|
|
|| gpg_err_code (err) == GPG_ERR_NOT_FOUND)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
err = 0;
|
|
|
|
|
if (cache_only)
|
|
|
|
|
any_no_data = 1;
|
|
|
|
|
}
|
|
|
|
|
else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only)
|
|
|
|
|
{
|
|
|
|
|
/* No real fault because the internal pattern lookup
|
|
|
|
|
can't yet cope with all types of pattern. */
|
|
|
|
|
err = 0;
|
|
|
|
|
}
|
|
|
|
|
if (err)
|
|
|
|
|
goto ready;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Loop over all configured servers unless we want only the
|
|
|
|
|
certificates from the cache. */
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#if USE_LDAP
|
2010-06-09 18:53:51 +02:00
|
|
|
|
for (ldapserver_iter_begin (&ldapserver_iter, ctrl);
|
|
|
|
|
!cache_only && !ldapserver_iter_end_p (&ldapserver_iter)
|
|
|
|
|
&& ldapserver_iter.server->host && !truncation_forced;
|
|
|
|
|
ldapserver_iter_next (&ldapserver_iter))
|
|
|
|
|
{
|
|
|
|
|
ldap_server_t ldapserver = ldapserver_iter.server;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (DBG_LOOKUP)
|
2011-02-04 12:57:53 +01:00
|
|
|
|
log_debug ("cmd_lookup: trying %s:%d base=%s\n",
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ldapserver->host, ldapserver->port,
|
|
|
|
|
ldapserver->base?ldapserver->base : "[default]");
|
|
|
|
|
|
|
|
|
|
/* Fetch certificates matching pattern */
|
|
|
|
|
err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver);
|
|
|
|
|
if ( gpg_err_code (err) == GPG_ERR_NO_DATA )
|
|
|
|
|
{
|
|
|
|
|
if (DBG_LOOKUP)
|
|
|
|
|
log_debug ("cmd_lookup: no data\n");
|
|
|
|
|
err = 0;
|
|
|
|
|
any_no_data = 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Fetch the certificates for this query. */
|
|
|
|
|
while (!truncation_forced)
|
|
|
|
|
{
|
|
|
|
|
xfree (value); value = NULL;
|
|
|
|
|
err = fetch_next_cert (fetch_context, &value, &valuelen);
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_NO_DATA )
|
|
|
|
|
{
|
|
|
|
|
err = 0;
|
|
|
|
|
any_no_data = 1;
|
|
|
|
|
break; /* Ready. */
|
|
|
|
|
}
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_TRUNCATED)
|
|
|
|
|
{
|
|
|
|
|
truncated = 1;
|
|
|
|
|
err = 0;
|
|
|
|
|
break; /* Ready. */
|
|
|
|
|
}
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_EOF)
|
|
|
|
|
{
|
|
|
|
|
err = 0;
|
|
|
|
|
break; /* Ready. */
|
|
|
|
|
}
|
|
|
|
|
if (!err && !value)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_BUG);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("fetch_next_cert failed: %s\n"),
|
|
|
|
|
gpg_strerror (err));
|
|
|
|
|
end_cert_fetch (fetch_context);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (DBG_LOOKUP)
|
|
|
|
|
log_debug ("cmd_lookup: returning one cert%s\n",
|
|
|
|
|
truncated? " (truncated)":"");
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
/* Send the data, flush the buffer and then send an END line
|
|
|
|
|
as a certificate delimiter. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
err = assuan_send_data (ctx, value, valuelen);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_send_data (ctx, NULL, 0);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = assuan_write_line (ctx, "END");
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (err)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
log_error (_("error sending data: %s\n"), gpg_strerror (err));
|
|
|
|
|
end_cert_fetch (fetch_context);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (++count >= opt.max_replies )
|
|
|
|
|
{
|
|
|
|
|
truncation_forced = 1;
|
|
|
|
|
log_info (_("max_replies %d exceeded\n"), opt.max_replies );
|
|
|
|
|
}
|
|
|
|
|
if (single)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
end_cert_fetch (fetch_context);
|
|
|
|
|
}
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#endif /*USE_LDAP*/
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
ready:
|
|
|
|
|
if (truncated || truncation_forced)
|
|
|
|
|
{
|
|
|
|
|
char str[50];
|
|
|
|
|
|
|
|
|
|
sprintf (str, "%d", count);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
assuan_write_status (ctx, "TRUNCATED", str);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!err && !count && !local_count && any_no_data)
|
|
|
|
|
err = gpg_error (GPG_ERR_NO_DATA);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
free_strlist (list);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static const char hlp_lookup[] =
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"LOOKUP [--url] [--single] [--cache-only] <pattern>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Lookup certificates matching PATTERN. With --url the pattern is\n"
|
|
|
|
|
"expected to be one URL.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"If --url is not given: To allow for multiple patterns (which are ORed)\n"
|
|
|
|
|
"quoting is required: Spaces are translated to \"+\" or \"%20\";\n"
|
|
|
|
|
"obviously this requires that the usual escape quoting rules are applied.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"If --url is given no special escaping is required because URLs are\n"
|
|
|
|
|
"already escaped this way.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"If --single is given the first and only the first match will be\n"
|
|
|
|
|
"returned. If --cache-only is _not_ given, no local query will be\n"
|
|
|
|
|
"done.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"If --cache-only is given no external lookup is done so that only\n"
|
|
|
|
|
"certificates from the cache may get returned.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_lookup (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
int lookup_url, single, cache_only;
|
|
|
|
|
|
|
|
|
|
lookup_url = has_leading_option (line, "--url");
|
|
|
|
|
single = has_leading_option (line, "--single");
|
|
|
|
|
cache_only = has_leading_option (line, "--cache-only");
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
if (lookup_url && cache_only)
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_FOUND);
|
|
|
|
|
else if (lookup_url && single)
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
|
|
|
|
|
else if (lookup_url)
|
|
|
|
|
err = lookup_cert_by_url (ctx, line);
|
|
|
|
|
else
|
|
|
|
|
err = lookup_cert_by_pattern (ctx, line, single, cache_only);
|
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char hlp_loadcrl[] =
|
|
|
|
|
"LOADCRL [--url] <filename|url>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Load the CRL in the file with name FILENAME into our cache. Note\n"
|
|
|
|
|
"that FILENAME should be given with an absolute path because\n"
|
|
|
|
|
"Dirmngrs cwd is not known. With --url the CRL is directly loaded\n"
|
|
|
|
|
"from the given URL.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"This command is usually used by gpgsm using the invocation \"gpgsm\n"
|
|
|
|
|
"--call-dirmngr loadcrl <filename>\". A direct invocation of Dirmngr\n"
|
|
|
|
|
"is not useful because gpgsm might need to callback gpgsm to ask for\n"
|
|
|
|
|
"the CA's certificate.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_loadcrl (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
int use_url = has_leading_option (line, "--url");
|
|
|
|
|
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
if (use_url)
|
|
|
|
|
{
|
|
|
|
|
ksba_reader_t reader;
|
|
|
|
|
|
|
|
|
|
err = crl_fetch (ctrl, line, &reader);
|
|
|
|
|
if (err)
|
2012-06-05 19:29:22 +02:00
|
|
|
|
log_error (_("fetching CRL from '%s' failed: %s\n"),
|
2010-06-09 18:53:51 +02:00
|
|
|
|
line, gpg_strerror (err));
|
|
|
|
|
else
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
err = crl_cache_insert (ctrl, line, reader);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (err)
|
2012-06-05 19:29:22 +02:00
|
|
|
|
log_error (_("processing CRL from '%s' failed: %s\n"),
|
2010-06-09 18:53:51 +02:00
|
|
|
|
line, gpg_strerror (err));
|
|
|
|
|
crl_close_reader (reader);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
char *buf;
|
|
|
|
|
|
|
|
|
|
buf = xtrymalloc (strlen (line)+1);
|
|
|
|
|
if (!buf)
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
strcpy_escaped_plus (buf, line);
|
|
|
|
|
err = crl_cache_load (ctrl, buf);
|
|
|
|
|
xfree (buf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char hlp_listcrls[] =
|
|
|
|
|
"LISTCRLS\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"List the content of all CRLs in a readable format. This command is\n"
|
|
|
|
|
"usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n"
|
|
|
|
|
"listcrls\". It may also be used directly using \"dirmngr\n"
|
|
|
|
|
"--list-crls\".";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_listcrls (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
2010-08-06 15:52:01 +02:00
|
|
|
|
estream_t fp;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
(void)line;
|
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
fp = es_fopencookie (ctx, "w", data_line_cookie_functions);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!fp)
|
2010-08-06 15:52:01 +02:00
|
|
|
|
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = crl_cache_list (fp);
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
}
|
|
|
|
|
return leave_cmd (ctx, err);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static const char hlp_cachecert[] =
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"CACHECERT\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Put a certificate into the internal cache. This command might be\n"
|
|
|
|
|
"useful if a client knows in advance certificates required for a\n"
|
2010-08-12 13:43:46 +02:00
|
|
|
|
"test and wants to make sure they get added to the internal cache.\n"
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"It is also helpful for debugging. To get the actual certificate,\n"
|
|
|
|
|
"this command immediately inquires it using\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" INQUIRE TARGETCERT\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"and the caller is expected to return the certificate for the\n"
|
|
|
|
|
"request as a binary blob.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_cachecert (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
ksba_cert_t cert = NULL;
|
|
|
|
|
unsigned char *value = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
(void)line;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
|
|
|
|
|
&value, &valuelen, MAX_CERT_LENGTH);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!valuelen) /* No data returned; return a comprehensible error. */
|
|
|
|
|
err = gpg_error (GPG_ERR_MISSING_CERT);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = ksba_cert_new (&cert);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = ksba_cert_init_from_mem (cert, value, valuelen);
|
|
|
|
|
}
|
|
|
|
|
xfree (value);
|
|
|
|
|
if(err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = cache_cert (cert);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
ksba_cert_release (cert);
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char hlp_validate[] =
|
2017-02-17 21:31:33 +01:00
|
|
|
|
"VALIDATE [--systrust] [--tls] [--no-crl]\n"
|
2010-06-09 18:53:51 +02:00
|
|
|
|
"\n"
|
|
|
|
|
"Validate a certificate using the certificate validation function\n"
|
|
|
|
|
"used internally by dirmngr. This command is only useful for\n"
|
|
|
|
|
"debugging. To get the actual certificate, this command immediately\n"
|
|
|
|
|
"inquires it using\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" INQUIRE TARGETCERT\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"and the caller is expected to return the certificate for the\n"
|
2017-02-17 16:39:48 +01:00
|
|
|
|
"request as a binary blob. The option --tls modifies this by asking\n"
|
|
|
|
|
"for list of certificates with\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" INQUIRE CERTLIST\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Here the first certificate is the target certificate, the remaining\n"
|
2018-04-25 09:43:18 +02:00
|
|
|
|
"certificates are suggested intermediary certificates. All certificates\n"
|
2017-02-17 16:39:48 +01:00
|
|
|
|
"need to be PEM encoded.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"The option --systrust changes the behaviour to include the system\n"
|
2017-02-17 21:31:33 +01:00
|
|
|
|
"provided root certificates as trust anchors. The option --no-crl\n"
|
|
|
|
|
"skips CRL checks";
|
2010-06-09 18:53:51 +02:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_validate (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
ksba_cert_t cert = NULL;
|
2017-02-17 16:39:48 +01:00
|
|
|
|
certlist_t certlist = NULL;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
unsigned char *value = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2017-02-17 21:31:33 +01:00
|
|
|
|
int systrust_mode, tls_mode, no_crl;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2017-02-17 16:39:48 +01:00
|
|
|
|
systrust_mode = has_option (line, "--systrust");
|
|
|
|
|
tls_mode = has_option (line, "--tls");
|
2017-02-17 21:31:33 +01:00
|
|
|
|
no_crl = has_option (line, "--no-crl");
|
2017-02-17 16:39:48 +01:00
|
|
|
|
line = skip_options (line);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2017-02-17 16:39:48 +01:00
|
|
|
|
if (tls_mode)
|
|
|
|
|
err = assuan_inquire (ctrl->server_local->assuan_ctx, "CERTLIST",
|
|
|
|
|
&value, &valuelen, MAX_CERTLIST_LENGTH);
|
|
|
|
|
else
|
|
|
|
|
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
|
|
|
|
|
&value, &valuelen, MAX_CERT_LENGTH);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!valuelen) /* No data returned; return a comprehensible error. */
|
|
|
|
|
err = gpg_error (GPG_ERR_MISSING_CERT);
|
2017-02-17 16:39:48 +01:00
|
|
|
|
else if (tls_mode)
|
|
|
|
|
{
|
|
|
|
|
estream_t fp;
|
|
|
|
|
|
|
|
|
|
fp = es_fopenmem_init (0, "rb", value, valuelen);
|
|
|
|
|
if (!fp)
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = read_certlist_from_stream (&certlist, fp);
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
if (!err && !certlist)
|
|
|
|
|
err = gpg_error (GPG_ERR_MISSING_CERT);
|
|
|
|
|
if (!err)
|
|
|
|
|
{
|
2018-04-25 09:43:18 +02:00
|
|
|
|
/* Extract the first certificate from the list. */
|
2017-02-17 16:39:48 +01:00
|
|
|
|
cert = certlist->cert;
|
|
|
|
|
ksba_cert_ref (cert);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
err = ksba_cert_new (&cert);
|
|
|
|
|
if (!err)
|
|
|
|
|
err = ksba_cert_init_from_mem (cert, value, valuelen);
|
|
|
|
|
}
|
|
|
|
|
xfree (value);
|
|
|
|
|
if(err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2017-02-17 16:39:48 +01:00
|
|
|
|
if (!tls_mode)
|
|
|
|
|
{
|
|
|
|
|
/* If we have this certificate already in our cache, use the
|
|
|
|
|
* cached version for validation because this will take care of
|
|
|
|
|
* any cached results. We don't need to do this in tls mode
|
|
|
|
|
* because this has already been done for certificate in a
|
|
|
|
|
* certlist_t. */
|
|
|
|
|
unsigned char fpr[20];
|
|
|
|
|
ksba_cert_t tmpcert;
|
|
|
|
|
|
|
|
|
|
cert_compute_fpr (cert, fpr);
|
|
|
|
|
tmpcert = get_cert_byfpr (fpr);
|
|
|
|
|
if (tmpcert)
|
|
|
|
|
{
|
|
|
|
|
ksba_cert_release (cert);
|
|
|
|
|
cert = tmpcert;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Quick hack to make verification work by inserting the supplied
|
|
|
|
|
* certs into the cache. */
|
|
|
|
|
if (tls_mode && certlist)
|
|
|
|
|
{
|
|
|
|
|
certlist_t cl;
|
|
|
|
|
|
|
|
|
|
for (cl = certlist->next; cl; cl = cl->next)
|
|
|
|
|
cache_cert (cl->cert);
|
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2017-02-17 21:31:33 +01:00
|
|
|
|
err = validate_cert_chain (ctrl, cert, NULL,
|
2017-02-21 14:55:04 +01:00
|
|
|
|
(VALIDATE_FLAG_TRUST_CONFIG
|
|
|
|
|
| (tls_mode ? VALIDATE_FLAG_TLS : 0)
|
|
|
|
|
| (systrust_mode ? VALIDATE_FLAG_TRUST_SYSTEM : 0)
|
2017-02-17 21:31:33 +01:00
|
|
|
|
| (no_crl ? VALIDATE_FLAG_NOCRLCHECK : 0)),
|
|
|
|
|
NULL);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
ksba_cert_release (cert);
|
2017-02-17 16:39:48 +01:00
|
|
|
|
release_certlist (certlist);
|
2010-08-06 15:52:01 +02:00
|
|
|
|
return leave_cmd (ctx, err);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
|
2011-01-10 14:30:17 +01:00
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
/* Parse an keyserver URI and store it in a new uri item which is
|
|
|
|
|
returned at R_ITEM. On error return an error code. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
make_keyserver_item (const char *uri, uri_item_t *r_item)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
uri_item_t item;
|
2021-05-26 14:48:27 +02:00
|
|
|
|
const char *s;
|
2021-05-28 15:20:57 +02:00
|
|
|
|
char *tmpstr = NULL;
|
2015-10-05 17:44:20 +02:00
|
|
|
|
|
|
|
|
|
*r_item = NULL;
|
2018-04-26 12:28:53 +02:00
|
|
|
|
|
|
|
|
|
/* We used to have DNS CNAME redirection from the URLs below to
|
|
|
|
|
* sks-keyserver. pools. The idea was to allow for a quick way to
|
|
|
|
|
* switch to a different set of pools. The problem with that
|
|
|
|
|
* approach is that TLS needs to verify the hostname and - because
|
|
|
|
|
* DNS is not secured - it can only check the user supplied hostname
|
|
|
|
|
* and not a hostname from a CNAME RR. Thus the final server all
|
|
|
|
|
* need to have certificates with the actual pool name as well as
|
|
|
|
|
* for keys.gnupg.net - that would render the advantage of
|
|
|
|
|
* keys.gnupg.net useless and so we better give up on this. Because
|
|
|
|
|
* the keys.gnupg.net URL are still in widespread use we do a static
|
|
|
|
|
* mapping here.
|
|
|
|
|
*/
|
|
|
|
|
if (!strcmp (uri, "hkps://keys.gnupg.net")
|
|
|
|
|
|| !strcmp (uri, "keys.gnupg.net"))
|
2021-06-25 19:15:24 +02:00
|
|
|
|
uri = "hkps://keyserver.ubuntu.com";
|
2018-04-26 12:28:53 +02:00
|
|
|
|
else if (!strcmp (uri, "https://keys.gnupg.net"))
|
2021-06-25 19:15:24 +02:00
|
|
|
|
uri = "hkps://keyserver.ubuntu.com";
|
2018-04-26 12:28:53 +02:00
|
|
|
|
else if (!strcmp (uri, "hkp://keys.gnupg.net"))
|
2021-06-25 19:15:24 +02:00
|
|
|
|
uri = "hkp://pgp.surf.nl";
|
2018-04-26 12:28:53 +02:00
|
|
|
|
else if (!strcmp (uri, "http://keys.gnupg.net"))
|
2021-06-25 19:15:24 +02:00
|
|
|
|
uri = "hkp://pgp.surf.nl:80";
|
2018-04-26 12:28:53 +02:00
|
|
|
|
else if (!strcmp (uri, "hkps://http-keys.gnupg.net")
|
|
|
|
|
|| !strcmp (uri, "http-keys.gnupg.net"))
|
2021-06-25 19:15:24 +02:00
|
|
|
|
uri = "hkps://keyserver.ubuntu.com";
|
2018-04-26 12:28:53 +02:00
|
|
|
|
else if (!strcmp (uri, "https://http-keys.gnupg.net"))
|
2021-06-25 19:15:24 +02:00
|
|
|
|
uri = "hkps://keyserver.ubuntu.com";
|
2018-04-26 12:28:53 +02:00
|
|
|
|
else if (!strcmp (uri, "hkp://http-keys.gnupg.net"))
|
2021-06-25 19:15:24 +02:00
|
|
|
|
uri = "hkp://pgp.surf.nl";
|
2018-04-26 12:28:53 +02:00
|
|
|
|
else if (!strcmp (uri, "http://http-keys.gnupg.net"))
|
2021-06-25 19:15:24 +02:00
|
|
|
|
uri = "hkp://pgp.surf.nl:80";
|
2018-04-26 12:28:53 +02:00
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
item = xtrymalloc (sizeof *item + strlen (uri));
|
|
|
|
|
if (!item)
|
|
|
|
|
return gpg_error_from_syserror ();
|
|
|
|
|
|
|
|
|
|
item->next = NULL;
|
|
|
|
|
item->parsed_uri = NULL;
|
|
|
|
|
strcpy (item->uri, uri);
|
|
|
|
|
|
|
|
|
|
#if USE_LDAP
|
2021-05-26 14:48:27 +02:00
|
|
|
|
if (!strncmp (uri, "ldap:", 5) && !(uri[5] == '/' && uri[6] == '/'))
|
|
|
|
|
{
|
|
|
|
|
/* Special ldap scheme given. This differs from a valid ldap
|
|
|
|
|
* scheme in that no double slash follows.. Use http_parse_uri
|
|
|
|
|
* to put it as opaque value into parsed_uri. */
|
|
|
|
|
tmpstr = strconcat ("opaque:", uri+5, NULL);
|
|
|
|
|
if (!tmpstr)
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
else
|
2021-05-28 15:20:57 +02:00
|
|
|
|
err = http_parse_uri (&item->parsed_uri, tmpstr, 0);
|
2021-05-26 14:48:27 +02:00
|
|
|
|
}
|
|
|
|
|
else if ((s=strchr (uri, ':')) && !(s[1] == '/' && s[2] == '/'))
|
|
|
|
|
{
|
|
|
|
|
/* No valid scheme given. Use http_parse_uri to put the string
|
|
|
|
|
* as opaque value into parsed_uri. */
|
|
|
|
|
tmpstr = strconcat ("opaque:", uri, NULL);
|
|
|
|
|
if (!tmpstr)
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
else
|
2021-05-28 15:20:57 +02:00
|
|
|
|
err = http_parse_uri (&item->parsed_uri, tmpstr, 0);
|
2021-05-26 14:48:27 +02:00
|
|
|
|
}
|
|
|
|
|
else if (ldap_uri_p (uri))
|
|
|
|
|
{
|
2021-05-28 15:20:57 +02:00
|
|
|
|
int fixup = 0;
|
2021-05-26 14:48:27 +02:00
|
|
|
|
/* Fixme: We should get rid of that parser and replace it with
|
|
|
|
|
* our generic (http) URI parser. */
|
2021-05-28 15:20:57 +02:00
|
|
|
|
|
|
|
|
|
/* If no port has been specified and the scheme ist ldaps we use
|
|
|
|
|
* our idea of the default port because the standard LDAP URL
|
|
|
|
|
* parser would use 636 here. This is because we redefined
|
|
|
|
|
* ldaps to mean starttls. */
|
|
|
|
|
#ifdef HAVE_W32_SYSTEM
|
|
|
|
|
if (!strcmp (uri, "ldap:///"))
|
|
|
|
|
fixup = 1;
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
if (!http_parse_uri (&item->parsed_uri,uri,HTTP_PARSE_NO_SCHEME_CHECK))
|
|
|
|
|
{
|
|
|
|
|
if (!item->parsed_uri->port
|
|
|
|
|
&& !strcmp (item->parsed_uri->scheme, "ldaps"))
|
|
|
|
|
fixup = 2;
|
|
|
|
|
http_release_parsed_uri (item->parsed_uri);
|
|
|
|
|
item->parsed_uri = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-26 14:48:27 +02:00
|
|
|
|
err = ldap_parse_uri (&item->parsed_uri, uri);
|
2021-05-28 15:20:57 +02:00
|
|
|
|
if (!err && fixup == 1)
|
|
|
|
|
item->parsed_uri->ad_current = 1;
|
|
|
|
|
else if (!err && fixup == 2)
|
|
|
|
|
item->parsed_uri->port = 389;
|
2021-05-26 14:48:27 +02:00
|
|
|
|
}
|
2015-10-05 17:44:20 +02:00
|
|
|
|
else
|
2021-05-28 15:20:57 +02:00
|
|
|
|
#endif /* USE_LDAP */
|
2015-10-05 17:44:20 +02:00
|
|
|
|
{
|
2021-05-25 16:08:56 +02:00
|
|
|
|
err = http_parse_uri (&item->parsed_uri, uri, HTTP_PARSE_NO_SCHEME_CHECK);
|
2015-10-05 17:44:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-28 15:20:57 +02:00
|
|
|
|
xfree (tmpstr);
|
2015-10-05 17:44:20 +02:00
|
|
|
|
if (err)
|
|
|
|
|
xfree (item);
|
|
|
|
|
else
|
|
|
|
|
*r_item = item;
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* If no keyserver is stored in CTRL but a global keyserver has been
|
|
|
|
|
set, put that global keyserver into CTRL. We need use this
|
|
|
|
|
function to help migrate from the old gpg based keyserver
|
|
|
|
|
configuration to the new dirmngr based configuration. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
ensure_keyserver (ctrl_t ctrl)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
uri_item_t item;
|
2015-12-02 11:49:41 +01:00
|
|
|
|
uri_item_t onion_items = NULL;
|
|
|
|
|
uri_item_t plain_items = NULL;
|
|
|
|
|
uri_item_t ui;
|
|
|
|
|
strlist_t sl;
|
2015-10-05 17:44:20 +02:00
|
|
|
|
|
|
|
|
|
if (ctrl->server_local->keyservers)
|
|
|
|
|
return 0; /* Already set for this session. */
|
|
|
|
|
if (!opt.keyserver)
|
2016-10-28 00:30:59 +02:00
|
|
|
|
{
|
|
|
|
|
/* No global option set. Fall back to default: */
|
|
|
|
|
return make_keyserver_item (DIRMNGR_DEFAULT_KEYSERVER,
|
|
|
|
|
&ctrl->server_local->keyservers);
|
|
|
|
|
}
|
2015-10-05 17:44:20 +02:00
|
|
|
|
|
2015-12-02 11:49:41 +01:00
|
|
|
|
for (sl = opt.keyserver; sl; sl = sl->next)
|
|
|
|
|
{
|
|
|
|
|
err = make_keyserver_item (sl->d, &item);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
if (item->parsed_uri->onion)
|
|
|
|
|
{
|
|
|
|
|
item->next = onion_items;
|
|
|
|
|
onion_items = item;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
item->next = plain_items;
|
|
|
|
|
plain_items = item;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-28 03:06:33 +02:00
|
|
|
|
/* Decide which to use. Note that the session has no keyservers
|
2015-12-02 11:49:41 +01:00
|
|
|
|
yet set. */
|
|
|
|
|
if (onion_items && !onion_items->next && plain_items && !plain_items->next)
|
|
|
|
|
{
|
|
|
|
|
/* If there is just one onion and one plain keyserver given, we take
|
|
|
|
|
only one depending on whether Tor is running or not. */
|
|
|
|
|
if (is_tor_running (ctrl))
|
|
|
|
|
{
|
|
|
|
|
ctrl->server_local->keyservers = onion_items;
|
|
|
|
|
onion_items = NULL;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ctrl->server_local->keyservers = plain_items;
|
|
|
|
|
plain_items = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (!is_tor_running (ctrl))
|
|
|
|
|
{
|
|
|
|
|
/* Tor is not running. It does not make sense to add Onion
|
|
|
|
|
addresses. */
|
|
|
|
|
ctrl->server_local->keyservers = plain_items;
|
|
|
|
|
plain_items = NULL;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* In all other cases add all keyservers. */
|
|
|
|
|
ctrl->server_local->keyservers = onion_items;
|
|
|
|
|
onion_items = NULL;
|
|
|
|
|
for (ui = ctrl->server_local->keyservers; ui && ui->next; ui = ui->next)
|
|
|
|
|
;
|
|
|
|
|
if (ui)
|
|
|
|
|
ui->next = plain_items;
|
|
|
|
|
else
|
|
|
|
|
ctrl->server_local->keyservers = plain_items;
|
|
|
|
|
plain_items = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
release_uri_item_list (onion_items);
|
|
|
|
|
release_uri_item_list (plain_items);
|
2015-10-05 17:44:20 +02:00
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-01-10 14:30:17 +01:00
|
|
|
|
static const char hlp_keyserver[] =
|
2014-03-11 16:19:41 +01:00
|
|
|
|
"KEYSERVER [<options>] [<uri>|<host>]\n"
|
|
|
|
|
"Options are:\n"
|
|
|
|
|
" --help\n"
|
|
|
|
|
" --clear Remove all configured keyservers\n"
|
|
|
|
|
" --resolve Resolve HKP host names and rotate\n"
|
|
|
|
|
" --hosttable Print table of known hosts and pools\n"
|
|
|
|
|
" --dead Mark <host> as dead\n"
|
|
|
|
|
" --alive Mark <host> as alive\n"
|
2011-01-10 14:30:17 +01:00
|
|
|
|
"\n"
|
|
|
|
|
"If called without arguments list all configured keyserver URLs.\n"
|
|
|
|
|
"If called with an URI add this as keyserver. Note that keyservers\n"
|
|
|
|
|
"are configured on a per-session base. A default keyserver may already be\n"
|
|
|
|
|
"present, thus the \"--clear\" option must be used to get full control.\n"
|
|
|
|
|
"If \"--clear\" and an URI are used together the clear command is\n"
|
|
|
|
|
"obviously executed first. A RESET command does not change the list\n"
|
|
|
|
|
"of configured keyservers.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_keyserver (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
2014-03-11 14:26:39 +01:00
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
int clear_flag, add_flag, help_flag, host_flag, resolve_flag;
|
2014-03-11 16:19:41 +01:00
|
|
|
|
int dead_flag, alive_flag;
|
2011-01-10 14:30:17 +01:00
|
|
|
|
uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it
|
|
|
|
|
is always initialized. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2011-01-10 14:30:17 +01:00
|
|
|
|
clear_flag = has_option (line, "--clear");
|
2011-02-09 17:48:00 +01:00
|
|
|
|
help_flag = has_option (line, "--help");
|
2014-03-11 14:26:39 +01:00
|
|
|
|
resolve_flag = has_option (line, "--resolve");
|
|
|
|
|
host_flag = has_option (line, "--hosttable");
|
2014-03-11 16:19:41 +01:00
|
|
|
|
dead_flag = has_option (line, "--dead");
|
|
|
|
|
alive_flag = has_option (line, "--alive");
|
2011-01-10 14:30:17 +01:00
|
|
|
|
line = skip_options (line);
|
|
|
|
|
add_flag = !!*line;
|
|
|
|
|
|
2011-02-09 17:48:00 +01:00
|
|
|
|
if (help_flag)
|
|
|
|
|
{
|
|
|
|
|
err = ks_action_help (ctrl, line);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-11 14:26:39 +01:00
|
|
|
|
if (resolve_flag)
|
|
|
|
|
{
|
2015-10-05 17:44:20 +02:00
|
|
|
|
err = ensure_keyserver (ctrl);
|
2018-01-27 19:46:19 +01:00
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
assuan_set_error (ctx, err,
|
|
|
|
|
"Bad keyserver configuration in dirmngr.conf");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = ks_action_resolve (ctrl, ctrl->server_local->keyservers);
|
2014-03-11 14:26:39 +01:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-11 16:19:41 +01:00
|
|
|
|
if (alive_flag && dead_flag)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
if (dead_flag)
|
|
|
|
|
{
|
|
|
|
|
err = check_owner_permission (ctx, "no permission to use --dead");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
if (alive_flag || dead_flag)
|
|
|
|
|
{
|
|
|
|
|
if (!*line)
|
|
|
|
|
{
|
|
|
|
|
err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = ks_hkp_mark_host (ctrl, line, alive_flag);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-12 16:30:08 +02:00
|
|
|
|
if (host_flag)
|
|
|
|
|
{
|
2014-03-11 14:26:39 +01:00
|
|
|
|
err = ks_hkp_print_hosttable (ctrl);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2011-04-12 16:30:08 +02:00
|
|
|
|
}
|
2014-03-11 16:19:41 +01:00
|
|
|
|
if (resolve_flag || host_flag || alive_flag || dead_flag)
|
2014-03-11 14:26:39 +01:00
|
|
|
|
goto leave;
|
2011-04-12 16:30:08 +02:00
|
|
|
|
|
2011-01-10 14:30:17 +01:00
|
|
|
|
if (add_flag)
|
|
|
|
|
{
|
2015-10-05 17:44:20 +02:00
|
|
|
|
err = make_keyserver_item (line, &item);
|
2011-01-10 14:30:17 +01:00
|
|
|
|
if (err)
|
2015-10-05 17:44:20 +02:00
|
|
|
|
goto leave;
|
2011-01-10 14:30:17 +01:00
|
|
|
|
}
|
|
|
|
|
if (clear_flag)
|
|
|
|
|
release_ctrl_keyservers (ctrl);
|
|
|
|
|
if (add_flag)
|
|
|
|
|
{
|
2015-03-28 17:23:56 +01:00
|
|
|
|
item->next = ctrl->server_local->keyservers;
|
|
|
|
|
ctrl->server_local->keyservers = item;
|
2011-01-10 14:30:17 +01:00
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
if (!add_flag && !clear_flag && !help_flag)
|
2011-01-10 14:30:17 +01:00
|
|
|
|
{
|
2015-10-05 17:44:20 +02:00
|
|
|
|
/* List configured keyservers. However, we first add a global
|
|
|
|
|
keyserver. */
|
2011-01-10 14:30:17 +01:00
|
|
|
|
uri_item_t u;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
err = ensure_keyserver (ctrl);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
assuan_set_error (ctx, err,
|
|
|
|
|
"Bad keyserver configuration in dirmngr.conf");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-28 17:23:56 +01:00
|
|
|
|
for (u=ctrl->server_local->keyservers; u; u = u->next)
|
2011-01-10 14:30:17 +01:00
|
|
|
|
dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL);
|
|
|
|
|
}
|
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char hlp_ks_search[] =
|
|
|
|
|
"KS_SEARCH {<pattern>}\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Search the configured OpenPGP keyservers (see command KEYSERVER)\n"
|
|
|
|
|
"for keys matching PATTERN";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_ks_search (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
strlist_t list, sl;
|
|
|
|
|
char *p;
|
|
|
|
|
estream_t outfp;
|
|
|
|
|
|
2017-06-08 09:30:48 +02:00
|
|
|
|
if (has_option (line, "--quick"))
|
|
|
|
|
ctrl->timeout = opt.connect_quick_timeout;
|
2011-01-10 14:30:17 +01:00
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
|
|
|
|
/* Break the line down into an strlist. Each pattern is
|
|
|
|
|
percent-plus escaped. */
|
|
|
|
|
list = NULL;
|
|
|
|
|
for (p=line; *p; line = p)
|
|
|
|
|
{
|
|
|
|
|
while (*p && *p != ' ')
|
|
|
|
|
p++;
|
|
|
|
|
if (*p)
|
|
|
|
|
*p++ = 0;
|
|
|
|
|
if (*line)
|
|
|
|
|
{
|
|
|
|
|
sl = xtrymalloc (sizeof *sl + strlen (line));
|
|
|
|
|
if (!sl)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
sl->flags = 0;
|
|
|
|
|
strcpy_escaped_plus (sl->d, line);
|
|
|
|
|
sl->next = list;
|
|
|
|
|
list = sl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
err = ensure_keyserver (ctrl);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2011-01-10 14:30:17 +01:00
|
|
|
|
/* Setup an output stream and perform the search. */
|
|
|
|
|
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
|
|
|
|
|
if (!outfp)
|
|
|
|
|
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-03-28 17:23:56 +01:00
|
|
|
|
err = ks_action_search (ctrl, ctrl->server_local->keyservers,
|
|
|
|
|
list, outfp);
|
2011-01-10 14:30:17 +01:00
|
|
|
|
es_fclose (outfp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
2014-12-22 12:34:57 +01:00
|
|
|
|
free_strlist (list);
|
2011-01-10 14:30:17 +01:00
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-01-18 12:51:16 +01:00
|
|
|
|
|
|
|
|
|
static const char hlp_ks_get[] =
|
2021-04-16 20:21:23 +02:00
|
|
|
|
"KS_GET [--quick] [--ldap] {<pattern>}\n"
|
2011-01-18 12:51:16 +01:00
|
|
|
|
"\n"
|
|
|
|
|
"Get the keys matching PATTERN from the configured OpenPGP keyservers\n"
|
2014-03-17 15:39:33 +01:00
|
|
|
|
"(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n"
|
2021-04-16 20:21:23 +02:00
|
|
|
|
"or an exact name indicated by the '=' prefix. Option --quick uses a\n"
|
|
|
|
|
"shorter timeout; --ldap will use only ldap servers";
|
2011-01-18 12:51:16 +01:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_ks_get (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
strlist_t list, sl;
|
|
|
|
|
char *p;
|
|
|
|
|
estream_t outfp;
|
2021-04-16 20:21:23 +02:00
|
|
|
|
int ldap_only;
|
2011-01-18 12:51:16 +01:00
|
|
|
|
|
2017-06-08 09:30:48 +02:00
|
|
|
|
if (has_option (line, "--quick"))
|
|
|
|
|
ctrl->timeout = opt.connect_quick_timeout;
|
2021-04-16 20:21:23 +02:00
|
|
|
|
ldap_only = has_option (line, "--ldap");
|
2011-01-18 12:51:16 +01:00
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
2015-03-19 11:14:52 +01:00
|
|
|
|
/* Break the line into a strlist. Each pattern is by
|
2011-01-18 12:51:16 +01:00
|
|
|
|
definition percent-plus escaped. However we only support keyids
|
|
|
|
|
and fingerprints and thus the client has no need to apply the
|
|
|
|
|
escaping. */
|
|
|
|
|
list = NULL;
|
|
|
|
|
for (p=line; *p; line = p)
|
|
|
|
|
{
|
|
|
|
|
while (*p && *p != ' ')
|
|
|
|
|
p++;
|
|
|
|
|
if (*p)
|
|
|
|
|
*p++ = 0;
|
|
|
|
|
if (*line)
|
|
|
|
|
{
|
|
|
|
|
sl = xtrymalloc (sizeof *sl + strlen (line));
|
|
|
|
|
if (!sl)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
sl->flags = 0;
|
|
|
|
|
strcpy_escaped_plus (sl->d, line);
|
|
|
|
|
sl->next = list;
|
|
|
|
|
list = sl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
err = ensure_keyserver (ctrl);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2011-01-18 12:51:16 +01:00
|
|
|
|
/* Setup an output stream and perform the get. */
|
|
|
|
|
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
|
|
|
|
|
if (!outfp)
|
|
|
|
|
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-09-05 12:50:35 +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;
|
2021-04-16 20:21:23 +02:00
|
|
|
|
err = ks_action_get (ctrl, ctrl->server_local->keyservers,
|
|
|
|
|
list, ldap_only, outfp);
|
2011-01-18 12:51:16 +01:00
|
|
|
|
es_fclose (outfp);
|
2016-09-05 12:50:35 +02:00
|
|
|
|
ctrl->server_local->inhibit_data_logging = 0;
|
2011-01-18 12:51:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
2014-12-22 12:34:57 +01:00
|
|
|
|
free_strlist (list);
|
2011-01-18 12:51:16 +01:00
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-02-08 21:11:19 +01:00
|
|
|
|
static const char hlp_ks_fetch[] =
|
|
|
|
|
"KS_FETCH <URL>\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Get the key(s) from URL.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_ks_fetch (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
estream_t outfp;
|
|
|
|
|
|
2017-06-08 09:30:48 +02:00
|
|
|
|
if (has_option (line, "--quick"))
|
|
|
|
|
ctrl->timeout = opt.connect_quick_timeout;
|
2011-02-08 21:11:19 +01:00
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
2016-04-27 08:20:25 +02:00
|
|
|
|
err = ensure_keyserver (ctrl); /* FIXME: Why do we needs this here? */
|
2015-10-05 17:44:20 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2011-02-08 21:11:19 +01:00
|
|
|
|
/* Setup an output stream and perform the get. */
|
|
|
|
|
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
|
|
|
|
|
if (!outfp)
|
|
|
|
|
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-09-05 12:50:35 +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;
|
2011-02-08 21:11:19 +01:00
|
|
|
|
err = ks_action_fetch (ctrl, line, outfp);
|
|
|
|
|
es_fclose (outfp);
|
2016-09-05 12:50:35 +02:00
|
|
|
|
ctrl->server_local->inhibit_data_logging = 0;
|
2011-02-08 21:11:19 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
leave:
|
2011-02-08 21:11:19 +01:00
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-01-20 14:12:53 +01:00
|
|
|
|
|
|
|
|
|
static const char hlp_ks_put[] =
|
|
|
|
|
"KS_PUT\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Send a key to the configured OpenPGP keyservers. The actual key material\n"
|
|
|
|
|
"is then requested by Dirmngr using\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" INQUIRE KEYBLOCK\n"
|
|
|
|
|
"\n"
|
2015-03-19 11:02:46 +01:00
|
|
|
|
"The client shall respond with a binary version of the keyblock (e.g.,\n"
|
|
|
|
|
"the output of `gpg --export KEYID'). For LDAP\n"
|
2011-01-20 14:12:53 +01:00
|
|
|
|
"keyservers Dirmngr may ask for meta information of the provided keyblock\n"
|
|
|
|
|
"using:\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" INQUIRE KEYBLOCK_INFO\n"
|
|
|
|
|
"\n"
|
2015-03-19 11:02:46 +01:00
|
|
|
|
"The client shall respond with a colon delimited info lines (the output\n"
|
2020-12-14 15:18:01 +01:00
|
|
|
|
"of 'gpg --list-keys --with-colons KEYID').\n";
|
2011-01-20 14:12:53 +01:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_ks_put (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
unsigned char *value = NULL;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
size_t valuelen;
|
2011-01-20 14:12:53 +01:00
|
|
|
|
unsigned char *info = NULL;
|
|
|
|
|
size_t infolen;
|
|
|
|
|
|
|
|
|
|
/* No options for now. */
|
|
|
|
|
line = skip_options (line);
|
|
|
|
|
|
2015-10-05 17:44:20 +02:00
|
|
|
|
err = ensure_keyserver (ctrl);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2011-01-20 14:12:53 +01:00
|
|
|
|
/* Ask for the key material. */
|
|
|
|
|
err = assuan_inquire (ctx, "KEYBLOCK",
|
|
|
|
|
&value, &valuelen, MAX_KEYBLOCK_LENGTH);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2011-01-20 14:12:53 +01:00
|
|
|
|
if (!valuelen) /* No data returned; return a comprehensible error. */
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_MISSING_CERT);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 15:18:01 +01:00
|
|
|
|
/* Ask for the key meta data. */
|
2011-01-20 14:12:53 +01:00
|
|
|
|
err = assuan_inquire (ctx, "KEYBLOCK_INFO",
|
|
|
|
|
&info, &infolen, MAX_KEYBLOCK_LENGTH);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Send the key. */
|
2015-03-28 17:23:56 +01:00
|
|
|
|
err = ks_action_put (ctrl, ctrl->server_local->keyservers,
|
|
|
|
|
value, valuelen, info, infolen);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2011-01-20 14:12:53 +01:00
|
|
|
|
leave:
|
|
|
|
|
xfree (info);
|
|
|
|
|
xfree (value);
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-11-16 21:22:39 +01:00
|
|
|
|
|
|
|
|
|
static const char hlp_loadswdb[] =
|
|
|
|
|
"LOADSWDB [--force]\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Load and verify the swdb.lst from the Net.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_loadswdb (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
|
|
|
|
|
err = dirmngr_load_swdb (ctrl, has_option (line, "--force"));
|
|
|
|
|
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static const char hlp_getinfo[] =
|
2010-08-06 15:52:01 +02:00
|
|
|
|
"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"
|
2015-10-21 18:14:24 +02:00
|
|
|
|
"tor - Return OK if running in Tor mode\n"
|
2015-12-03 18:05:32 +01:00
|
|
|
|
"dnsinfo - Return info about the DNS resolver\n"
|
2017-11-14 13:42:18 +01:00
|
|
|
|
"socket_name - Return the name of the socket.\n"
|
|
|
|
|
"session_id - Return the current session_id.\n"
|
2018-04-12 11:24:54 +02:00
|
|
|
|
"workqueue - Inspect the work queue\n"
|
|
|
|
|
"getenv NAME - Return value of envvar NAME\n";
|
2010-08-06 15:52:01 +02:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_getinfo (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
2015-12-02 11:49:41 +01:00
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
2010-08-06 15:52:01 +02:00
|
|
|
|
gpg_error_t err;
|
2017-11-14 13:42:18 +01:00
|
|
|
|
char numbuf[50];
|
2010-08-06 15:52:01 +02:00
|
|
|
|
|
|
|
|
|
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"))
|
|
|
|
|
{
|
2016-10-26 22:37:06 +02:00
|
|
|
|
const char *s = dirmngr_get_current_socket_name ();
|
2016-08-18 11:23:40 +02:00
|
|
|
|
err = assuan_send_data (ctx, s, strlen (s));
|
2010-08-06 15:52:01 +02:00
|
|
|
|
}
|
2017-11-14 13:42:18 +01:00
|
|
|
|
else if (!strcmp (line, "session_id"))
|
|
|
|
|
{
|
|
|
|
|
snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id);
|
|
|
|
|
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
|
|
|
|
|
}
|
2015-09-18 16:17:11 +02:00
|
|
|
|
else if (!strcmp (line, "tor"))
|
|
|
|
|
{
|
2017-02-01 17:54:14 +01:00
|
|
|
|
int use_tor;
|
|
|
|
|
|
|
|
|
|
use_tor = dirmngr_use_tor ();
|
|
|
|
|
if (use_tor)
|
2015-11-26 17:35:56 +01:00
|
|
|
|
{
|
2015-12-02 11:49:41 +01:00
|
|
|
|
if (!is_tor_running (ctrl))
|
|
|
|
|
err = assuan_write_status (ctx, "NO_TOR", "Tor not running");
|
2015-11-27 12:21:45 +01:00
|
|
|
|
else
|
2015-12-02 11:49:41 +01:00
|
|
|
|
err = 0;
|
2015-11-27 12:21:45 +01:00
|
|
|
|
if (!err)
|
2017-02-01 17:54:14 +01:00
|
|
|
|
assuan_set_okay_line (ctx, use_tor == 1 ? "- Tor mode is enabled"
|
|
|
|
|
/**/ : "- Tor mode is enforced");
|
2015-11-26 17:35:56 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
err = set_error (GPG_ERR_FALSE, "Tor mode is NOT enabled");
|
2015-09-18 16:17:11 +02:00
|
|
|
|
}
|
2015-12-03 18:05:32 +01:00
|
|
|
|
else if (!strcmp (line, "dnsinfo"))
|
|
|
|
|
{
|
2016-12-01 15:45:51 +01:00
|
|
|
|
if (standard_resolver_p ())
|
|
|
|
|
assuan_set_okay_line
|
|
|
|
|
(ctx, "- Forced use of System resolver (w/o Tor support)");
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-12-14 15:36:25 +01:00
|
|
|
|
#ifdef USE_LIBDNS
|
|
|
|
|
assuan_set_okay_line (ctx, (recursive_resolver_p ()
|
|
|
|
|
? "- Libdns recursive resolver"
|
|
|
|
|
: "- Libdns stub resolver"));
|
|
|
|
|
#else
|
2016-12-01 15:45:51 +01:00
|
|
|
|
assuan_set_okay_line (ctx, "- System resolver (w/o Tor support)");
|
2016-12-14 15:36:25 +01:00
|
|
|
|
#endif
|
2016-12-01 15:45:51 +01:00
|
|
|
|
}
|
2015-12-03 18:05:32 +01:00
|
|
|
|
err = 0;
|
|
|
|
|
}
|
2017-11-14 13:42:18 +01:00
|
|
|
|
else if (!strcmp (line, "workqueue"))
|
|
|
|
|
{
|
|
|
|
|
workqueue_dump_queue (ctrl);
|
|
|
|
|
err = 0;
|
|
|
|
|
}
|
2018-04-12 11:24:54 +02:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-08-06 15:52:01 +02:00
|
|
|
|
else
|
|
|
|
|
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
|
|
|
|
|
|
|
|
|
|
return leave_cmd (ctx, err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2010-08-12 13:43:46 +02:00
|
|
|
|
|
|
|
|
|
static const char hlp_killdirmngr[] =
|
|
|
|
|
"KILLDIRMNGR\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"This command allows a user - given sufficient permissions -\n"
|
|
|
|
|
"to kill this dirmngr process.\n";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_killdirmngr (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
|
|
|
|
|
(void)line;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2016-08-18 11:23:40 +02:00
|
|
|
|
ctrl->server_local->stopme = 1;
|
|
|
|
|
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
|
2020-09-10 10:49:54 +02:00
|
|
|
|
return 0;
|
2010-08-12 13:43:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char hlp_reloaddirmngr[] =
|
|
|
|
|
"RELOADDIRMNGR\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"This command is an alternative to SIGHUP\n"
|
|
|
|
|
"to reload the configuration.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_reloaddirmngr (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
(void)ctx;
|
|
|
|
|
(void)line;
|
|
|
|
|
|
|
|
|
|
dirmngr_sighup_action ();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-10-24 10:40:42 +02:00
|
|
|
|
static const char hlp_flushcrls[] =
|
|
|
|
|
"FLUSHCRLS\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Remove all cached CRLs from memory and\n"
|
|
|
|
|
"the file system.";
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
cmd_flushcrls (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
(void)line;
|
|
|
|
|
|
|
|
|
|
return leave_cmd (ctx, crl_cache_flush () ? GPG_ERR_GENERAL : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
|
2010-06-09 18:53:51 +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[] = {
|
2015-04-23 15:42:56 +02:00
|
|
|
|
{ "DNS_CERT", cmd_dns_cert, hlp_dns_cert },
|
2016-04-27 08:20:25 +02:00
|
|
|
|
{ "WKD_GET", cmd_wkd_get, hlp_wkd_get },
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{ "LDAPSERVER", cmd_ldapserver, hlp_ldapserver },
|
|
|
|
|
{ "ISVALID", cmd_isvalid, hlp_isvalid },
|
|
|
|
|
{ "CHECKCRL", cmd_checkcrl, hlp_checkcrl },
|
|
|
|
|
{ "CHECKOCSP", cmd_checkocsp, hlp_checkocsp },
|
|
|
|
|
{ "LOOKUP", cmd_lookup, hlp_lookup },
|
|
|
|
|
{ "LOADCRL", cmd_loadcrl, hlp_loadcrl },
|
|
|
|
|
{ "LISTCRLS", cmd_listcrls, hlp_listcrls },
|
|
|
|
|
{ "CACHECERT", cmd_cachecert, hlp_cachecert },
|
|
|
|
|
{ "VALIDATE", cmd_validate, hlp_validate },
|
2011-01-10 14:30:17 +01:00
|
|
|
|
{ "KEYSERVER", cmd_keyserver, hlp_keyserver },
|
|
|
|
|
{ "KS_SEARCH", cmd_ks_search, hlp_ks_search },
|
2011-01-18 12:51:16 +01:00
|
|
|
|
{ "KS_GET", cmd_ks_get, hlp_ks_get },
|
2011-02-08 21:11:19 +01:00
|
|
|
|
{ "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch },
|
2011-01-20 14:12:53 +01:00
|
|
|
|
{ "KS_PUT", cmd_ks_put, hlp_ks_put },
|
2010-08-06 15:52:01 +02:00
|
|
|
|
{ "GETINFO", cmd_getinfo, hlp_getinfo },
|
2016-11-16 21:22:39 +01:00
|
|
|
|
{ "LOADSWDB", cmd_loadswdb, hlp_loadswdb },
|
2010-08-12 13:43:46 +02:00
|
|
|
|
{ "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr },
|
|
|
|
|
{ "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr },
|
2018-10-24 10:40:42 +02:00
|
|
|
|
{ "FLUSHCRLS", cmd_flushcrls, hlp_flushcrls },
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{ 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-01-10 14:30:17 +01:00
|
|
|
|
/* Note that we do not reset the list of configured keyservers. */
|
2010-06-09 18:53:51 +02:00
|
|
|
|
static gpg_error_t
|
|
|
|
|
reset_notify (assuan_context_t ctx, char *line)
|
|
|
|
|
{
|
|
|
|
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
|
|
|
|
(void)line;
|
|
|
|
|
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#if USE_LDAP
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ldapserver_list_free (ctrl->server_local->ldapservers);
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#endif /*USE_LDAP*/
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ctrl->server_local->ldapservers = NULL;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-09-05 12:50:35 +02:00
|
|
|
|
/* 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
|
|
|
|
|
dirmngr_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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-03-19 11:14:52 +01:00
|
|
|
|
/* Startup the server and run the main command loop. With FD = -1,
|
2017-11-14 13:42:18 +01:00
|
|
|
|
* use stdin/stdout. SESSION_ID is either 0 or a unique number
|
|
|
|
|
* identifying a session. */
|
2010-06-09 18:53:51 +02:00
|
|
|
|
void
|
2017-11-14 13:42:18 +01:00
|
|
|
|
start_command_handler (assuan_fd_t fd, unsigned int session_id)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
static const char hello[] = "Dirmngr " VERSION " at your service";
|
|
|
|
|
static char *hello_line;
|
|
|
|
|
int rc;
|
|
|
|
|
assuan_context_t ctx;
|
|
|
|
|
ctrl_t ctrl;
|
|
|
|
|
|
|
|
|
|
ctrl = xtrycalloc (1, sizeof *ctrl);
|
|
|
|
|
if (ctrl)
|
|
|
|
|
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
|
|
|
|
|
if (!ctrl || !ctrl->server_local)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("can't allocate control structure: %s\n"),
|
|
|
|
|
strerror (errno));
|
|
|
|
|
xfree (ctrl);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
dirmngr_init_default_ctrl (ctrl);
|
|
|
|
|
|
|
|
|
|
rc = assuan_new (&ctx);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("failed to allocate assuan context: %s\n"),
|
|
|
|
|
gpg_strerror (rc));
|
|
|
|
|
dirmngr_exit (2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fd == ASSUAN_INVALID_FD)
|
|
|
|
|
{
|
|
|
|
|
assuan_fd_t filedes[2];
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
assuan_release (ctx);
|
|
|
|
|
log_error (_("failed to initialize the server: %s\n"),
|
|
|
|
|
gpg_strerror(rc));
|
|
|
|
|
dirmngr_exit (2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc = register_commands (ctx);
|
|
|
|
|
if (rc)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("failed to the register commands with Assuan: %s\n"),
|
|
|
|
|
gpg_strerror(rc));
|
|
|
|
|
dirmngr_exit (2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!hello_line)
|
|
|
|
|
{
|
2016-06-07 10:59:46 +02:00
|
|
|
|
hello_line = xtryasprintf
|
|
|
|
|
("Home: %s\n"
|
|
|
|
|
"Config: %s\n"
|
|
|
|
|
"%s",
|
|
|
|
|
gnupg_homedir (),
|
|
|
|
|
opt.config_filename? opt.config_filename : "[none]",
|
|
|
|
|
hello);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2017-11-14 13:42:18 +01:00
|
|
|
|
ctrl->server_local->session_id = session_id;
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
for (;;)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2015-12-02 11:49:41 +01:00
|
|
|
|
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#if USE_LDAP
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ldap_wrapper_connection_cleanup (ctrl);
|
|
|
|
|
|
|
|
|
|
ldapserver_list_free (ctrl->server_local->ldapservers);
|
2014-10-17 15:59:45 +02:00
|
|
|
|
#endif /*USE_LDAP*/
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ctrl->server_local->ldapservers = NULL;
|
|
|
|
|
|
2015-12-02 11:49:41 +01:00
|
|
|
|
release_ctrl_keyservers (ctrl);
|
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ctrl->server_local->assuan_ctx = NULL;
|
|
|
|
|
assuan_release (ctx);
|
|
|
|
|
|
2010-08-12 13:43:46 +02:00
|
|
|
|
if (ctrl->server_local->stopme)
|
|
|
|
|
dirmngr_exit (0);
|
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (ctrl->refcount)
|
|
|
|
|
log_error ("oops: connection control structure still referenced (%d)\n",
|
|
|
|
|
ctrl->refcount);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
release_ctrl_ocsp_certs (ctrl);
|
|
|
|
|
xfree (ctrl->server_local);
|
2015-04-21 17:14:53 +02:00
|
|
|
|
dirmngr_deinit_default_ctrl (ctrl);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
xfree (ctrl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Send a status line back to the client. KEYWORD is the status
|
2011-02-09 17:48:00 +01:00
|
|
|
|
keyword, the optional string arguments are blank separated added to
|
2010-06-09 18:53:51 +02:00
|
|
|
|
the line, the last argument must be a NULL. */
|
|
|
|
|
gpg_error_t
|
|
|
|
|
dirmngr_status (ctrl_t ctrl, const char *keyword, ...)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
va_list arg_ptr;
|
2017-11-14 16:24:12 +01:00
|
|
|
|
assuan_context_t ctx;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
va_start (arg_ptr, keyword);
|
|
|
|
|
|
2017-11-14 16:24:12 +01:00
|
|
|
|
if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
2018-02-14 12:21:23 +01:00
|
|
|
|
err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
va_end (arg_ptr);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-11-14 13:42:18 +01:00
|
|
|
|
/* Print a help status line. The function splits text at LFs. */
|
2011-02-09 17:48:00 +01:00
|
|
|
|
gpg_error_t
|
|
|
|
|
dirmngr_status_help (ctrl_t ctrl, const char *text)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err = 0;
|
2017-11-14 16:24:12 +01:00
|
|
|
|
assuan_context_t ctx;
|
2011-02-09 17:48:00 +01:00
|
|
|
|
|
2017-11-14 16:24:12 +01:00
|
|
|
|
if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx))
|
2011-02-09 17:48:00 +01:00
|
|
|
|
{
|
|
|
|
|
char buf[950], *p;
|
|
|
|
|
size_t n;
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
p = buf;
|
|
|
|
|
n = 0;
|
|
|
|
|
for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++)
|
|
|
|
|
*p++ = *text++;
|
|
|
|
|
if (*text == '\n')
|
|
|
|
|
text++;
|
|
|
|
|
*p = 0;
|
|
|
|
|
err = assuan_write_status (ctx, "#", buf);
|
|
|
|
|
}
|
|
|
|
|
while (!err && *text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-24 18:14:37 +02:00
|
|
|
|
|
2017-11-14 13:42:18 +01:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-07-24 18:14:37 +02:00
|
|
|
|
/* This function is similar to print_assuan_status but takes a CTRL
|
|
|
|
|
* arg instead of an assuan context as first argument. */
|
|
|
|
|
gpg_error_t
|
|
|
|
|
dirmngr_status_printf (ctrl_t ctrl, const char *keyword,
|
|
|
|
|
const char *format, ...)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
va_list arg_ptr;
|
2017-11-14 16:24:12 +01:00
|
|
|
|
assuan_context_t ctx;
|
|
|
|
|
|
2018-07-25 14:35:04 +02:00
|
|
|
|
if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx))
|
2017-11-14 16:24:12 +01:00
|
|
|
|
return 0;
|
2017-07-24 18:14:37 +02:00
|
|
|
|
|
|
|
|
|
va_start (arg_ptr, format);
|
|
|
|
|
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
|
|
|
|
|
va_end (arg_ptr);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-03-12 14:27:50 +01:00
|
|
|
|
/* Send a tick progress indicator back. Fixme: This is only done for
|
2011-01-10 14:30:17 +01:00
|
|
|
|
the currently active channel. */
|
2010-06-09 18:53:51 +02:00
|
|
|
|
gpg_error_t
|
|
|
|
|
dirmngr_tick (ctrl_t ctrl)
|
|
|
|
|
{
|
|
|
|
|
static time_t next_tick = 0;
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
time_t now = time (NULL);
|
|
|
|
|
|
|
|
|
|
if (!next_tick)
|
|
|
|
|
{
|
|
|
|
|
next_tick = now + 1;
|
|
|
|
|
}
|
|
|
|
|
else if ( now > next_tick )
|
|
|
|
|
{
|
|
|
|
|
if (ctrl)
|
|
|
|
|
{
|
|
|
|
|
err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
/* Take this as in indication for a cancel request. */
|
|
|
|
|
err = gpg_error (GPG_ERR_CANCELED);
|
|
|
|
|
}
|
|
|
|
|
now = time (NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next_tick = now + 1;
|
|
|
|
|
}
|
|
|
|
|
return err;
|
|
|
|
|
}
|