diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c
index fb9a70135..31833ebd6 100644
--- a/dirmngr/dirmngr.c
+++ b/dirmngr/dirmngr.c
@@ -1,2104 +1,2112 @@
/* dirmngr.c - Keyserver and X.509 LDAP access
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010, 2011 g10 Code GmbH
* Copyright (C) 2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef HAVE_W32_SYSTEM
#include
#include
#endif
#include
#include
#ifdef HAVE_SIGNAL_H
# include
#endif
#include
#include "dirmngr-err.h"
#if HTTP_USE_NTBTLS
# include
#elif HTTP_USE_GNUTLS
# include
#endif /*HTTP_USE_GNUTLS*/
#define GNUPG_COMMON_NEED_AFLOCAL
#include "dirmngr.h"
#include
#include "certcache.h"
#include "crlcache.h"
#include "crlfetch.h"
#include "misc.h"
#if USE_LDAP
# include "ldapserver.h"
#endif
#include "asshelp.h"
#if USE_LDAP
# include "ldap-wrapper.h"
#endif
#include "../common/init.h"
#include "gc-opt-flags.h"
/* The plain Windows version uses the windows service system. For
example to start the service you may use "sc start dirmngr".
WindowsCE does not support this; the service system over there is
based on a single process with all services being DLLs - we can't
support this easily. */
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
# define USE_W32_SERVICE 1
#endif
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values {
aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aServer,
aDaemon,
aService,
aListCRLs,
aLoadCRL,
aFetchCRL,
aShutdown,
aFlush,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oDebugLevel,
oGnutlsDebug,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oLogFile,
oBatch,
oDisableHTTP,
oDisableLDAP,
oIgnoreLDAPDP,
oIgnoreHTTPDP,
oIgnoreOCSPSvcUrl,
oHonorHTTPProxy,
oHTTPProxy,
oLDAPProxy,
oOnlyLDAPProxy,
oLDAPFile,
oLDAPTimeout,
oLDAPAddServers,
oOCSPResponder,
oOCSPSigner,
oOCSPMaxClockSkew,
oOCSPMaxPeriod,
oOCSPCurrentPeriod,
oMaxReplies,
oHkpCaCert,
oFakedSystemTime,
oForce,
oAllowOCSP,
oSocketName,
oLDAPWrapperProgram,
oHTTPWrapperProgram,
oIgnoreCertExtension,
oUseTor,
aTest
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ),
ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ),
#ifdef USE_W32_SERVICE
ARGPARSE_c (aService, "service", N_("run as windows service (background)")),
#endif
ARGPARSE_c (aListCRLs, "list-crls", N_("list the contents of the CRL cache")),
ARGPARSE_c (aLoadCRL, "load-crl", N_("|FILE|load CRL from FILE into cache")),
ARGPARSE_c (aFetchCRL, "fetch-crl", N_("|URL|fetch a CRL from URL")),
ARGPARSE_c (aShutdown, "shutdown", N_("shutdown the dirmngr")),
ARGPARSE_c (aFlush, "flush", N_("flush the cache")),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write server mode logs to FILE")),
ARGPARSE_s_n (oBatch, "batch", N_("run without asking a user")),
ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")),
ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")),
ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")),
ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")),
ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp",
N_("ignore HTTP CRL distribution points")),
ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp",
N_("ignore LDAP CRL distribution points")),
ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url",
N_("ignore certificate contained OCSP service URLs")),
ARGPARSE_s_s (oHTTPProxy, "http-proxy",
N_("|URL|redirect all HTTP requests to URL")),
ARGPARSE_s_s (oLDAPProxy, "ldap-proxy",
N_("|HOST|use HOST for LDAP queries")),
ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy",
N_("do not use fallback hosts with --ldap-proxy")),
ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file",
N_("|FILE|read LDAP server list from FILE")),
ARGPARSE_s_n (oLDAPAddServers, "add-servers",
N_("add new servers discovered in CRL distribution"
" points to serverlist")),
ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout",
N_("|N|set LDAP timeout to N seconds")),
ARGPARSE_s_s (oOCSPResponder, "ocsp-responder",
N_("|URL|use OCSP responder at URL")),
ARGPARSE_s_s (oOCSPSigner, "ocsp-signer",
N_("|FPR|OCSP response signed by FPR")),
ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"),
ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"),
ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"),
ARGPARSE_s_i (oMaxReplies, "max-replies",
N_("|N|do not return more than N items in one query")),
ARGPARSE_s_s (oHkpCaCert, "hkp-cacert",
N_("|FILE|use the CA certificates in FILE for HKP over TLS")),
ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via TOR")),
ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */
ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"),
ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"),
ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"),
ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", "@"),
ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"),
ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing "
"of all commands and options)\n")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_X509_VALUE , "x509" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
#define DEFAULT_MAX_REPLIES 10
#define DEFAULT_LDAP_TIMEOUT 100 /* arbitrary large timeout */
/* For the cleanup handler we need to keep track of the socket's name. */
static const char *socket_name;
/* If the socket has been redirected, this is the name of the
redirected socket.. */
static const char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Only if this flag has been set will we remove the socket file. */
static int cleanup_socket;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* Helper to implement --debug-level. */
static const char *debug_level;
/* Helper to set the NTBTLS or GNUTLS log level. */
static int opt_gnutls_debug = -1;
/* Flag indicating that a shutdown has been requested. */
static volatile int shutdown_pending;
/* Counter for the active connections. */
static int active_connections;
/* The timer tick used for housekeeping stuff. For Windows we use a
longer period as the SetWaitableTimer seems to signal earlier than
the 2 seconds. All values are in seconds. */
#if defined(HAVE_W32CE_SYSTEM)
# define TIMERTICK_INTERVAL (60)
#elif defined(HAVE_W32_SYSTEM)
# define TIMERTICK_INTERVAL (4)
#else
# define TIMERTICK_INTERVAL (2)
#endif
#define HOUSEKEEPING_INTERVAL (600)
/* This union is used to avoid compiler warnings in case a pointer is
64 bit and an int 32 bit. We store an integer in a pointer and get
it back later (npth_getspecific et al.). */
union int_and_ptr_u
{
int aint;
assuan_fd_t afd;
void *aptr;
};
/* The key used to store the current file descriptor in the thread
local storage. We use this in conjunction with the
log_set_pid_suffix_cb feature. */
#ifndef HAVE_W32_SYSTEM
static int my_tlskey_current_fd;
#endif
/* Prototypes. */
static void cleanup (void);
#if USE_LDAP
static ldap_server_t parse_ldapserver_file (const char* filename);
#endif /*USE_LDAP*/
static fingerprint_list_t parse_ocsp_signer (const char *string);
static void handle_connections (assuan_fd_t listen_fd);
/* NPth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static const char *
my_strusage( int level )
{
const char *p;
switch ( level )
{
case 11: p = "@DIRMNGR@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 49: p = PACKAGE_BUGREPORT; break;
case 1:
case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n"
"Keyserver, CRL, and OCSP access for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Callback from libksba to hash a provided buffer. Our current
implementation does only allow SHA-1 for hashing. This may be
extended by mapping the name, testing for algorithm availibility
and adjust the length checks accordingly. */
static gpg_error_t
my_ksba_hash_buffer (void *arg, const char *oid,
const void *buffer, size_t length, size_t resultsize,
unsigned char *result, size_t *resultlen)
{
(void)arg;
if (oid && strcmp (oid, "1.3.14.3.2.26"))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (resultsize < 20)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
gcry_md_hash_buffer (2, result, buffer, length);
*resultlen = 20;
return 0;
}
/* GNUTLS log function callback. */
#ifdef HTTP_USE_GNUTLS
static void
my_gnutls_log (int level, const char *text)
{
int n;
n = strlen (text);
while (n && text[n-1] == '\n')
n--;
log_debug ("gnutls:L%d: %.*s\n", level, n, text);
}
#endif /*HTTP_USE_GNUTLS*/
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE);
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE
|DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
log_info (_("valid debug levels are: %s\n"),
"none, basic, advanced, expert, guru");
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
{
opt.verbose = 1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
}
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
#if HTTP_USE_NTBTLS
if (opt_gnutls_debug >= 0)
{
ntbtls_set_debug (opt_gnutls_debug, NULL, NULL);
}
#elif HTTP_USE_GNUTLS
if (opt_gnutls_debug >= 0)
{
gnutls_global_set_log_function (my_gnutls_log);
gnutls_global_set_log_level (opt_gnutls_debug);
}
#endif /*HTTP_USE_GNUTLS*/
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] "), DIRMNGR_NAME);
es_fputs (text, es_stderr);
es_putc ('\n', es_stderr);
dirmngr_exit (2);
}
/* Helper to stop the reaper thread for the ldap wrapper. */
static void
shutdown_reaper (void)
{
#if USE_LDAP
ldap_wrapper_wait_connections ();
#endif
}
/* Handle options which are allowed to be reset after program start.
Return true if the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
{
if (!pargs)
{ /* Reset mode. */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.ldap_wrapper_program = NULL;
opt.disable_http = 0;
opt.disable_ldap = 0;
opt.honor_http_proxy = 0;
opt.http_proxy = NULL;
opt.ldap_proxy = NULL;
opt.only_ldap_proxy = 0;
opt.ignore_http_dp = 0;
opt.ignore_ldap_dp = 0;
opt.ignore_ocsp_service_url = 0;
opt.allow_ocsp = 0;
opt.ocsp_responder = NULL;
opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */
opt.ocsp_max_period = 90 * 86400; /* 90 days. */
opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */
opt.max_replies = DEFAULT_MAX_REPLIES;
while (opt.ocsp_signer)
{
fingerprint_list_t tmp = opt.ocsp_signer->next;
xfree (opt.ocsp_signer);
opt.ocsp_signer = tmp;
}
FREE_STRLIST (opt.ignored_cert_extensions);
http_register_tls_ca (NULL);
/* We do not allow resetting of opt.use_tor at runtime. */
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oGnutlsDebug: opt_gnutls_debug = pargs->r.ret_int; break;
case oLogFile:
if (!reread)
return 0; /* Not handled. */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oLDAPWrapperProgram:
opt.ldap_wrapper_program = pargs->r.ret_str;
break;
case oHTTPWrapperProgram:
opt.http_wrapper_program = pargs->r.ret_str;
break;
case oDisableHTTP: opt.disable_http = 1; break;
case oDisableLDAP: opt.disable_ldap = 1; break;
case oHonorHTTPProxy: opt.honor_http_proxy = 1; break;
case oHTTPProxy: opt.http_proxy = pargs->r.ret_str; break;
case oLDAPProxy: opt.ldap_proxy = pargs->r.ret_str; break;
case oOnlyLDAPProxy: opt.only_ldap_proxy = 1; break;
case oIgnoreHTTPDP: opt.ignore_http_dp = 1; break;
case oIgnoreLDAPDP: opt.ignore_ldap_dp = 1; break;
case oIgnoreOCSPSvcUrl: opt.ignore_ocsp_service_url = 1; break;
case oAllowOCSP: opt.allow_ocsp = 1; break;
case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break;
case oOCSPSigner:
opt.ocsp_signer = parse_ocsp_signer (pargs->r.ret_str);
break;
case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break;
case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break;
case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break;
case oMaxReplies: opt.max_replies = pargs->r.ret_int; break;
case oHkpCaCert:
http_register_tls_ca (pargs->r.ret_str);
break;
case oIgnoreCertExtension:
add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str);
break;
case oUseTor: opt.use_tor = 1; break;
default:
return 0; /* Not handled. */
}
return 1; /* Handled. */
}
#ifdef USE_W32_SERVICE
/* The global status of our service. */
SERVICE_STATUS_HANDLE service_handle;
SERVICE_STATUS service_status;
DWORD WINAPI
w32_service_control (DWORD control, DWORD event_type, LPVOID event_data,
LPVOID context)
{
(void)event_type;
(void)event_data;
(void)context;
/* event_type and event_data are not used here. */
switch (control)
{
case SERVICE_CONTROL_SHUTDOWN:
/* For shutdown we will try to force termination. */
service_status.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus (service_handle, &service_status);
shutdown_pending = 3;
break;
case SERVICE_CONTROL_STOP:
service_status.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus (service_handle, &service_status);
shutdown_pending = 1;
break;
default:
break;
}
return 0;
}
#endif /*USE_W32_SERVICE*/
#ifndef HAVE_W32_SYSTEM
static int
pid_suffix_callback (unsigned long *r_suffix)
{
union int_and_ptr_u value;
memset (&value, 0, sizeof value);
value.aptr = npth_getspecific (my_tlskey_current_fd);
*r_suffix = value.aint;
return (*r_suffix != -1); /* Use decimal representation. */
}
#endif /*!HAVE_W32_SYSTEM*/
#ifdef USE_W32_SERVICE
# define main real_main
#endif
int
main (int argc, char **argv)
{
#ifdef USE_W32_SERVICE
# undef main
#endif
enum cmd_and_opt_values cmd = 0;
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned configlineno;
int parse_debug = 0;
int default_config =1;
int greeting = 0;
int nogreeting = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
#if USE_LDAP
char *ldapfile = NULL;
#endif /*USE_LDAP*/
int debug_wait = 0;
int rc;
int homedir_seen = 0;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
#ifdef USE_W32_SERVICE
/* The option will be set by main() below if we should run as a
system daemon. */
if (opt.system_service)
{
service_handle
= RegisterServiceCtrlHandlerEx ("DirMngr",
&w32_service_control, NULL /*FIXME*/);
if (service_handle == 0)
log_error ("failed to register service control handler: ec=%d",
(int) GetLastError ());
service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
service_status.dwCurrentState = SERVICE_START_PENDING;
service_status.dwControlsAccepted
= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
service_status.dwWin32ExitCode = NO_ERROR;
service_status.dwServiceSpecificExitCode = NO_ERROR;
service_status.dwCheckPoint = 0;
service_status.dwWaitHint = 10000; /* 10 seconds timeout. */
SetServiceStatus (service_handle, &service_status);
}
#endif /*USE_W32_SERVICE*/
set_strusage (my_strusage);
log_set_prefix (DIRMNGR_NAME, 1|4);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
npth_init ();
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
/* Check that the libraries are suitable. Do it here because
the option parsing may need services of the libraries. */
if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
log_fatal (_("%s is too old (need %s, have %s)\n"), "libgcrypt",
NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
if (!ksba_check_version (NEED_KSBA_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba",
NEED_KSBA_VERSION, ksba_check_version (NULL) );
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL);
/* Init TLS library. */
#if HTTP_USE_NTBTLS
if (!ntbtls_check_version (NEED_NTBTLS_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "ntbtls",
NEED_NTBTLS_VERSION, ntbtls_check_version (NULL) );
#elif HTTP_USE_GNUTLS
rc = gnutls_global_init ();
if (rc)
log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
#endif /*HTTP_USE_GNUTLS*/
/* Init Assuan. */
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_assuan_log_prefix (log_get_prefix (NULL));
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug);
setup_libgcrypt_logging ();
/* Setup defaults. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
opt.homedir = default_homedir ();
/* Now with NPth running we can set the logging callback. Our
windows implementation does not yet feature the NPth TLS
functions. */
#ifndef HAVE_W32_SYSTEM
if (npth_key_create (&my_tlskey_current_fd, NULL) == 0)
if (npth_setspecific (my_tlskey_current_fd, NULL) == 0)
log_set_pid_suffix_cb (pid_suffix_callback);
#endif /*!HAVE_W32_SYSTEM*/
/* Reset rereadable options to default values. */
parse_rereadable_options (NULL, 0);
/* LDAP defaults. */
opt.add_new_ldapservers = 0;
opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT;
/* Other defaults. */
/* Check whether we have a config file given on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* Yes there is one, so we do not try the default one, but
read the option file when it is encountered at the
commandline */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
{
opt.homedir = pargs.r.ret_str;
homedir_seen = 1;
}
else if (pargs.r_opt == aDaemon)
opt.system_daemon = 1;
else if (pargs.r_opt == aService)
{
/* Redundant. The main function takes care of it. */
opt.system_service = 1;
opt.system_daemon = 1;
}
#ifdef HAVE_W32_SYSTEM
else if (pargs.r_opt == aGPGConfList || pargs.r_opt == aGPGConfTest)
/* We set this so we switch to the system configuration
directory below. This is a crutch to solve the problem
that the user configuration is never used on Windows. Also
see below at aGPGConfList. */
opt.system_daemon = 1;
#endif
}
/* If --daemon has been given on the command line but not --homedir,
we switch to /etc/gnupg as default home directory. Note, that
this also overrides the GNUPGHOME environment variable. */
if (opt.system_daemon && !homedir_seen)
{
#ifdef HAVE_W32CE_SYSTEM
opt.homedir = DIRSEP_S "gnupg";
#else
opt.homedir = gnupg_sysconfdir ();
#endif
opt.homedir_cache = gnupg_cachedir ();
socket_name = dirmngr_sys_socket_name ();
}
else if (dirmngr_user_socket_name ())
socket_name = dirmngr_user_socket_name ();
else
socket_name = dirmngr_sys_socket_name ();
if (default_config)
configname = make_filename (opt.homedir, DIRMNGR_NAME".conf", NULL );
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if( parse_debug )
log_info (_("Note: no default option file '%s'\n"),
configname );
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aServer:
case aDaemon:
case aService:
case aShutdown:
case aFlush:
case aListCRLs:
case aLoadCRL:
case aFetchCRL:
case aGPGConfList:
case aGPGConfTest:
cmd = pargs.r_opt;
break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oOptions:
/* Config files may not be nested (silently ignore them) */
if (!configfp)
{
xfree(configname);
configname = xstrdup(pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: /* Ignore this option here. */; break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oLDAPFile:
# if USE_LDAP
ldapfile = pargs.r.ret_str;
# endif /*USE_LDAP*/
break;
case oLDAPAddServers: opt.add_new_ldapservers = 1; break;
case oLDAPTimeout:
opt.ldaptimeout = pargs.r.ret_int;
break;
case oFakedSystemTime:
gnupg_set_time ((time_t)pargs.r.ret_ulong, 0);
break;
case oForce: opt.force = 1; break;
case oSocketName: socket_name = pargs.r.ret_str; break;
default : pargs.err = configfp? 1:2; break;
}
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the name so that it can be read on SIGHUP. */
opt.config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
if (nogreeting )
greeting = 0;
if (!opt.homedir_cache)
opt.homedir_cache = opt.homedir;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
+ if (opt.use_tor)
+ {
+ log_info ("WARNING: ***************************************\n");
+ log_info ("WARNING: TOR mode (--use-tor) DOES NOT YET WORK!\n");
+ log_info ("WARNING: ***************************************\n");
+ }
+
+
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (!access ("/etc/"DIRMNGR_NAME, F_OK) && !strncmp (opt.homedir, "/etc/", 5))
log_info
("NOTE: DirMngr is now a proper part of %s. The configuration and"
" other directory names changed. Please check that no other version"
" of dirmngr is still installed. To disable this warning, remove the"
" directory '/etc/dirmngr'.\n", GNUPG_NAME);
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
set_debug ();
/* Get LDAP server list from file. */
#if USE_LDAP
if (!ldapfile)
{
ldapfile = make_filename (opt.homedir,
opt.system_daemon?
"ldapservers.conf":"dirmngr_ldapservers.conf",
NULL);
opt.ldapservers = parse_ldapserver_file (ldapfile);
xfree (ldapfile);
}
else
opt.ldapservers = parse_ldapserver_file (ldapfile);
#endif /*USE_LDAP*/
#ifndef HAVE_W32_SYSTEM
/* We need to ignore the PIPE signal because the we might log to a
socket and that code handles EPIPE properly. The ldap wrapper
also requires us to ignore this silly signal. Assuan would set
this signal to ignore anyway.*/
signal (SIGPIPE, SIG_IGN);
#endif
/* Ready. Now to our duties. */
if (!cmd && opt.system_service)
cmd = aDaemon;
else if (!cmd)
cmd = aServer;
rc = 0;
if (cmd == aServer)
{
/* Note that this server mode is mainly useful for debugging. */
if (argc)
wrong_args ("--server");
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, 2|4);
}
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
#if USE_LDAP
ldap_wrapper_launch_thread ();
#endif /*USE_LDAP*/
cert_cache_init ();
crl_cache_init ();
start_command_handler (ASSUAN_INVALID_FD);
shutdown_reaper ();
}
else if (cmd == aDaemon)
{
assuan_fd_t fd;
pid_t pid;
int len;
struct sockaddr_un serv_addr;
if (argc)
wrong_args ("--daemon");
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
#ifndef HAVE_W32_SYSTEM
if (strchr (socket_name, ':'))
{
log_error (_("colons are not allowed in the socket name\n"));
dirmngr_exit (1);
}
#endif
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
cleanup ();
dirmngr_exit (1);
}
#if ASSUAN_VERSION_NUMBER >= 0x020104 /* >= 2.1.4 */
{
int redirected;
if (assuan_sock_set_sockaddr_un (socket_name,
(struct sockaddr*)&serv_addr,
&redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), socket_name);
else
log_error ("error preparing socket '%s': %s\n",
socket_name,
gpg_strerror (gpg_error_from_syserror ()));
dirmngr_exit (1);
}
if (redirected)
{
redir_socket_name = xstrdup (serv_addr.sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n",
socket_name, redir_socket_name);
}
}
#else /* Assuan < 2.1.4 */
memset (&serv_addr, 0, sizeof serv_addr);
serv_addr.sun_family = AF_UNIX;
if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path )
{
log_error (_("socket name '%s' is too long\n"), socket_name);
dirmngr_exit (1);
}
strcpy (serv_addr.sun_path, socket_name);
#endif /* Assuan < 2.1.4 */
len = SUN_LEN (&serv_addr);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Fixme: We should test whether a dirmngr is already running. */
gnupg_remove (redir_socket_name? redir_socket_name : socket_name);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
}
if (rc != -1
&& (rc = assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, len, &socket_nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
serv_addr.sun_path,
gpg_strerror (gpg_error_from_errno (errno)));
assuan_sock_close (fd);
dirmngr_exit (1);
}
cleanup_socket = 1;
if (listen (FD2INT (fd), 5) == -1)
{
log_error (_("listen() failed: %s\n"), strerror (errno));
assuan_sock_close (fd);
dirmngr_exit (1);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), serv_addr.sun_path);
es_fflush (NULL);
/* Note: We keep the dirmngr_info output only for the sake of
existing scripts which might use this to detect a successful
start of the dirmngr. */
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
pid = getpid ();
es_printf ("set %s=%s;%lu;1\n",
DIRMNGR_INFO_NAME, socket_name, (ulong) pid);
#else
pid = fork();
if (pid == (pid_t)-1)
{
log_fatal (_("error forking process: %s\n"), strerror (errno));
dirmngr_exit (1);
}
if (pid)
{ /* We are the parent */
char *infostr;
/* Don't let cleanup() remove the socket - the child is
responsible for doing that. */
cleanup_socket = 0;
close (fd);
/* Create the info string: :: */
if (asprintf (&infostr, "%s=%s:%lu:1",
DIRMNGR_INFO_NAME, serv_addr.sun_path, (ulong)pid ) < 0)
{
log_error (_("out of core\n"));
kill (pid, SIGTERM);
dirmngr_exit (1);
}
/* Print the environment string, so that the caller can use
shell's eval to set it. But see above. */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export %s;\n", infostr, DIRMNGR_INFO_NAME);
}
free (infostr);
exit (0);
/*NEVER REACHED*/
} /* end parent */
/*
This is the child
*/
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
close (i);
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
dirmngr_exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
if (chdir("/"))
{
log_error ("chdir to / failed: %s\n", strerror (errno));
dirmngr_exit (1);
}
}
#endif
#if USE_LDAP
ldap_wrapper_launch_thread ();
#endif /*USE_LDAP*/
cert_cache_init ();
crl_cache_init ();
#ifdef USE_W32_SERVICE
if (opt.system_service)
{
service_status.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (service_handle, &service_status);
}
#endif
handle_connections (fd);
assuan_sock_close (fd);
shutdown_reaper ();
#ifdef USE_W32_SERVICE
if (opt.system_service)
{
service_status.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (service_handle, &service_status);
}
#endif
}
else if (cmd == aListCRLs)
{
/* Just list the CRL cache and exit. */
if (argc)
wrong_args ("--list-crls");
#if USE_LDAP
ldap_wrapper_launch_thread ();
#endif /*USE_LDAP*/
crl_cache_init ();
crl_cache_list (es_stdout);
}
else if (cmd == aLoadCRL)
{
struct server_control_s ctrlbuf;
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
#if USE_LDAP
ldap_wrapper_launch_thread ();
#endif /*USE_LDAP*/
cert_cache_init ();
crl_cache_init ();
if (!argc)
rc = crl_cache_load (&ctrlbuf, NULL);
else
{
for (; !rc && argc; argc--, argv++)
rc = crl_cache_load (&ctrlbuf, *argv);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFetchCRL)
{
ksba_reader_t reader;
struct server_control_s ctrlbuf;
if (argc != 1)
wrong_args ("--fetch-crl URL");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
#if USE_LDAP
ldap_wrapper_launch_thread ();
#endif /*USE_LDAP*/
cert_cache_init ();
crl_cache_init ();
rc = crl_fetch (&ctrlbuf, argv[0], &reader);
if (rc)
log_error (_("fetching CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
else
{
rc = crl_cache_insert (&ctrlbuf, argv[0], reader);
if (rc)
log_error (_("processing CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
crl_close_reader (reader);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFlush)
{
/* Delete cache and exit. */
if (argc)
wrong_args ("--flush");
rc = crl_cache_flush();
}
else if (cmd == aGPGConfTest)
dirmngr_exit (0);
else if (cmd == aGPGConfList)
{
unsigned long flags = 0;
char *filename;
char *filename_esc;
#ifdef HAVE_W32_SYSTEM
/* On Windows systems, dirmngr always runs as system daemon, and
the per-user configuration is never used. So we short-cut
everything to use the global system configuration of dirmngr
above, and here we set the no change flag to make these
read-only. */
flags |= GC_OPT_FLAG_NO_CHANGE;
#endif
/* First the configuration file. This is not an option, but it
is vital information for GPG Conf. */
if (!opt.config_filename)
opt.config_filename = make_filename (opt.homedir,
"dirmngr.conf", NULL );
filename = percent_escape (opt.config_filename, NULL);
es_printf ("gpgconf-dirmngr.conf:%lu:\"%s\n",
GC_OPT_FLAG_DEFAULT, filename);
xfree (filename);
es_printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT);
es_printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE);
/* --csh and --sh are mutually exclusive, something we can not
express in GPG Conf. --options is only usable from the
command line, really. --debug-all interacts with --debug,
and having both of them is thus problematic. --no-detach is
also only usable on the command line. --batch is unused. */
filename = make_filename (opt.homedir,
opt.system_daemon?
"ldapservers.conf":"dirmngr_ldapservers.conf",
NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT,
filename_esc);
xfree (filename_esc);
xfree (filename);
es_printf ("ldaptimeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT);
es_printf ("max-replies:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES);
es_printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE);
es_printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
/* Note: The next one is to fix a typo in gpgconf - should be
removed eventually. */
es_printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("use-tor:%lu:\n", flags | GC_OPT_FLAG_NONE);
}
cleanup ();
return !!rc;
}
#ifdef USE_W32_SERVICE
static void WINAPI
call_real_main (DWORD argc, LPSTR *argv)
{
real_main (argc, argv);
}
int
main (int argc, char *argv[])
{
int i;
/* Find out if we run in daemon mode or on the command line. */
for (i = 1; i < argc; i++)
if (!strcmp (argv[i], "--service"))
{
opt.system_service = 1;
opt.system_daemon = 1;
break;
}
if (!opt.system_service)
return real_main (argc, argv);
else
{
SERVICE_TABLE_ENTRY DispatchTable [] =
{
{ "DirMngr", &call_real_main },
{ NULL, NULL }
};
if (!StartServiceCtrlDispatcher (DispatchTable))
return 1;
return 0;
}
}
#endif /*USE_W32_SERVICE*/
static void
cleanup (void)
{
crl_cache_deinit ();
cert_cache_deinit (1);
#if USE_LDAP
ldapserver_list_free (opt.ldapservers);
#endif /*USE_LDAP*/
opt.ldapservers = NULL;
if (cleanup_socket)
{
cleanup_socket = 0;
if (redir_socket_name)
gnupg_remove (redir_socket_name);
else if (socket_name && *socket_name)
gnupg_remove (socket_name);
}
}
void
dirmngr_exit (int rc)
{
cleanup ();
exit (rc);
}
void
dirmngr_init_default_ctrl (ctrl_t ctrl)
{
if (opt.http_proxy)
ctrl->http_proxy = xstrdup (opt.http_proxy);
}
void
dirmngr_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->http_proxy);
ctrl->http_proxy = NULL;
}
/* Create a list of LDAP servers from the file FILENAME. Returns the
list or NULL in case of errors.
The format fo such a file is line oriented where empty lines and
lines starting with a hash mark are ignored. All other lines are
assumed to be colon seprated with these fields:
1. field: Hostname
2. field: Portnumber
3. field: Username
4. field: Password
5. field: Base DN
*/
#if USE_LDAP
static ldap_server_t
parse_ldapserver_file (const char* filename)
{
char buffer[1024];
char *p;
ldap_server_t server, serverstart, *serverend;
int c;
unsigned int lineno = 0;
estream_t fp;
fp = es_fopen (filename, "r");
if (!fp)
{
log_error (_("error opening '%s': %s\n"), filename, strerror (errno));
return NULL;
}
serverstart = NULL;
serverend = &serverstart;
while (es_fgets (buffer, sizeof buffer, fp))
{
lineno++;
if (!*buffer || buffer[strlen(buffer)-1] != '\n')
{
if (*buffer && es_feof (fp))
; /* Last line not terminated - continue. */
else
{
log_error (_("%s:%u: line too long - skipped\n"),
filename, lineno);
while ( (c=es_fgetc (fp)) != EOF && c != '\n')
; /* Skip until end of line. */
continue;
}
}
/* Skip empty and comment lines.*/
for (p=buffer; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
/* Parse the colon separated fields. */
server = ldapserver_parse_one (buffer, filename, lineno);
if (server)
{
*serverend = server;
serverend = &server->next;
}
}
if (es_ferror (fp))
log_error (_("error reading '%s': %s\n"), filename, strerror (errno));
es_fclose (fp);
return serverstart;
}
#endif /*USE_LDAP*/
static fingerprint_list_t
parse_ocsp_signer (const char *string)
{
gpg_error_t err;
char *fname;
estream_t fp;
char line[256];
char *p;
fingerprint_list_t list, *list_tail, item;
unsigned int lnr = 0;
int c, i, j;
int errflag = 0;
/* Check whether this is not a filename and treat it as a direct
fingerprint specification. */
if (!strpbrk (string, "/.~\\"))
{
item = xcalloc (1, sizeof *item);
for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++)
if ( string[i] != ':' )
item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (string+i) || !string[i]))
{
log_error (_("%s:%u: invalid fingerprint detected\n"),
"--ocsp-signer", 0);
xfree (item);
return NULL;
}
return item;
}
/* Well, it is a filename. */
if (*string == '/' || (*string == '~' && string[1] == '/'))
fname = make_filename (string, NULL);
else
{
if (string[0] == '.' && string[1] == '/' )
string += 2;
fname = make_filename (opt.homedir, string, NULL);
}
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
xfree (fname);
return NULL;
}
list = NULL;
list_tail = &list;
for (;;)
{
if (!es_fgets (line, DIM(line)-1, fp) )
{
if (!es_feof (fp))
{
err = gpg_error_from_syserror ();
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
}
es_fclose (fp);
if (errflag)
{
while (list)
{
fingerprint_list_t tmp = list->next;
xfree (list);
list = tmp;
}
}
xfree (fname);
return list; /* Ready. */
}
lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line. */
while ( (c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
/* */: GPG_ERR_INCOMPLETE_LINE);
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
continue;
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
item = xcalloc (1, sizeof *item);
*list_tail = item;
list_tail = &item->next;
for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
if ( p[i] != ':' )
item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (p+i) || p[i] == '\n'))
{
log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr);
errflag = 1;
}
i++;
while (spacep (p+i))
i++;
if (p[i] && p[i] != '\n')
log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr);
}
/*NOTREACHED*/
}
/*
Stuff used in daemon mode.
*/
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the NPTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
allocated or points into the process' original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
ARGPARSE_ARGS pargs;
FILE *fp;
unsigned int configlineno = 0;
int dummy;
if (!opt.config_filename)
return; /* No config file. */
fp = fopen (opt.config_filename, "r");
if (!fp)
{
log_error (_("option file '%s': %s\n"),
opt.config_filename, strerror(errno) );
return;
}
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = 1; /* do not remove the args */
while (optfile_parse (fp, opt.config_filename, &configlineno, &pargs, opts) )
{
if (pargs.r_opt < -1)
pargs.err = 1; /* Print a warning. */
else /* Try to parse this option - ignore unchangeable ones. */
parse_rereadable_options (&pargs, 1);
}
fclose (fp);
set_debug ();
}
/* A global function which allows us to trigger the reload stuff from
other places. */
void
dirmngr_sighup_action (void)
{
log_info (_("SIGHUP received - "
"re-reading configuration and flushing caches\n"));
reread_configuration ();
cert_cache_deinit (0);
crl_cache_deinit ();
cert_cache_init ();
crl_cache_init ();
}
/* The signal handler. */
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
dirmngr_sighup_action ();
break;
case SIGUSR1:
cert_cache_print_stats ();
break;
case SIGUSR2:
log_info (_("SIGUSR2 received - no action defined\n"));
break;
case SIGTERM:
if (!shutdown_pending)
log_info (_("SIGTERM received - shutting down ...\n"));
else
log_info (_("SIGTERM received - still %d active connections\n"),
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info (_("shutdown forced\n"));
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
dirmngr_exit (0);
}
break;
case SIGINT:
log_info (_("SIGINT received - immediate shutdown\n"));
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
dirmngr_exit (0);
break;
default:
log_info (_("signal %d received - no action defined\n"), signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Thread to do the housekeeping. */
static void *
housekeeping_thread (void *arg)
{
static int sentinel;
time_t curtime;
(void)arg;
curtime = gnupg_get_time ();
if (sentinel)
{
log_info ("housekeeping is already going on\n");
return NULL;
}
sentinel++;
if (opt.verbose)
log_info ("starting housekeeping\n");
ks_hkp_housekeeping (curtime);
if (opt.verbose)
log_info ("ready with housekeeping\n");
sentinel--;
return NULL;
}
#if GPGRT_GCC_HAVE_PUSH_PRAGMA
# pragma GCC push_options
# pragma GCC optimize ("no-strict-overflow")
#endif
static int
time_for_housekeeping_p (time_t curtime)
{
static time_t last_housekeeping;
if (!last_housekeeping)
last_housekeeping = curtime;
if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime
|| last_housekeeping > curtime /*(be prepared for y2038)*/)
{
last_housekeeping = curtime;
return 1;
}
return 0;
}
#if GPGRT_GCC_HAVE_PUSH_PRAGMA
# pragma GCC pop_options
#endif
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
/* Under Windows we don't use signals and need a way for the loop to
check for the shutdown flag. */
#ifdef HAVE_W32_SYSTEM
if (shutdown_pending)
log_info (_("SIGTERM received - shutting down ...\n"));
if (shutdown_pending > 2)
{
log_info (_("shutdown forced\n"));
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
dirmngr_exit (0);
}
#endif /*HAVE_W32_SYSTEM*/
if (time_for_housekeeping_p (gnupg_get_time ()))
{
npth_t thread;
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
log_error ("error preparing housekeeping thread: %s\n", strerror (err));
else
{
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, housekeeping_thread, NULL);
if (err)
log_error ("error spawning housekeeping thread: %s\n",
strerror (err));
npth_attr_destroy (&tattr);
}
}
}
/* Check the nonce on a new connection. This is a NOP unless we are
using our Unix domain socket emulation under Windows. */
static int
check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT (fd), strerror (errno));
assuan_sock_close (fd);
return -1;
}
else
return 0;
}
/* Helper to call a connection's main fucntion. */
static void *
start_connection_thread (void *arg)
{
union int_and_ptr_u argval;
gnupg_fd_t fd;
memset (&argval, 0, sizeof argval);
argval.aptr = arg;
fd = argval.afd;
if (check_nonce (fd, &socket_nonce))
{
log_error ("handler nonce check FAILED\n");
return NULL;
}
#ifndef HAVE_W32_SYSTEM
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
active_connections++;
if (opt.verbose)
log_info (_("handler for fd %d started\n"), FD2INT (fd));
start_command_handler (fd);
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"), FD2INT (fd));
active_connections--;
#ifndef HAVE_W32_SYSTEM
argval.afd = ASSUAN_INVALID_FD;
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
return NULL;
}
/* Main loop in daemon mode. */
static void
handle_connections (assuan_fd_t listen_fd)
{
npth_attr_t tattr;
#ifndef HAVE_W32_SYSTEM
int signo;
#endif
struct sockaddr_un paddr;
socklen_t plen = sizeof( paddr );
gnupg_fd_t fd;
int nfd, ret;
fd_set fdset, read_fdset;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int saved_errno;
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM /* FIXME */
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#endif
/* Setup the fdset. It has only one member. This is because we use
pth_select instead of pth_accept to properly sync timeouts with
to full second. */
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2INT (listen_fd);
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
/* Main loop. */
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections)
break; /* ready */
/* Do not accept new connections but keep on running the
loop to cope with the timer events. */
FD_ZERO (&fdset);
}
/* Take a copy of the fdset. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, npth_sigev_sigmask());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, NULL, NULL);
saved_errno = errno;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
if (!shutdown_pending && FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listen_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else
{
char threadname[50];
union int_and_ptr_u argval;
npth_t thread;
memset (&argval, 0, sizeof argval);
argval.afd = fd;
snprintf (threadname, sizeof threadname-1,
"conn fd=%d", FD2INT(fd));
threadname[sizeof threadname -1] = 0;
ret = npth_create (&thread, &tattr,
start_connection_thread, argval.aptr);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret) );
assuan_sock_close (fd);
}
npth_setname_np (thread, threadname);
}
fd = GNUPG_INVALID_FD;
}
}
npth_attr_destroy (&tattr);
cleanup ();
log_info ("%s %s stopped\n", strusage(11), strusage(13));
}
diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi
index d62e2d59c..7757acf10 100644
--- a/doc/dirmngr.texi
+++ b/doc/dirmngr.texi
@@ -1,1077 +1,1083 @@
@c Copyright (C) 2002 Klar"alvdalens Datakonsult AB
@c Copyright (C) 2004, 2005, 2006, 2007 g10 Code GmbH
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking DIRMNGR
@chapter Invoking DIRMNGR
@cindex DIRMNGR command options
@cindex command options
@cindex options, DIRMNGR command
@manpage dirmngr.8
@ifset manverb
.B dirmngr
\- CRL and OCSP daemon
@end ifset
@mansect synopsis
@ifset manverb
.B dirmngr
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@mansect description
Since version 2.1 of GnuPG, @command{dirmngr} takes care of accessing
the OpenPGP keyservers. As with previous versions it is also used as
a server for managing and downloading certificate revocation lists
(CRLs) for X.509 certificates, downloading X.509 certificates, and
providing access to OCSP providers. Dirmngr is invoked internally by
@command{gpg}, @command{gpgsm}, or via the @command{gpg-connect-agent}
tool.
For historical reasons it is also possible to start @command{dirmngr}
in a system daemon mode which uses a different directory layout.
However, this mode is deprecated and may eventually be removed.
@manpause
@noindent
@xref{Option Index},for an index to @command{DIRMNGR}'s commands and
options.
@mancont
@menu
* Dirmngr Commands:: List of all commands.
* Dirmngr Options:: List of all options.
* Dirmngr Configuration:: Configuration files.
* Dirmngr Signals:: Use of signals.
* Dirmngr Examples:: Some usage examples.
* Dirmngr Protocol:: The protocol dirmngr uses.
@end menu
@node Dirmngr Commands
@section Commands
@mansect commands
Commands are not distinguished from options except for the fact that
only one command is allowed.
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Not that you cannot abbreviate this command.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@item --server
@opindex server
Run in server mode and wait for commands on the @code{stdin}. The
default mode is to create a socket and listen for commands there.
This is only used for testing.
@item --daemon
@opindex daemon
Run in background daemon mode and listen for commands on a socket.
Note that this also changes the default home directory and enables the
internal certificate validation code. This mode is deprecated.
@item --list-crls
@opindex list-crls
List the contents of the CRL cache on @code{stdout}. This is probably
only useful for debugging purposes.
@item --load-crl @var{file}
@opindex load-crl
This command requires a filename as additional argument, and it will
make Dirmngr try to import the CRL in @var{file} into it's cache.
Note, that this is only possible if Dirmngr is able to retrieve the
CA's certificate directly by its own means. In general it is better
to use @code{gpgsm}'s @code{--call-dirmngr loadcrl filename} command
so that @code{gpgsm} can help dirmngr.
@item --fetch-crl @var{url}
@opindex fetch-crl
This command requires an URL as additional argument, and it will make
dirmngr try to retrieve an import the CRL from that @var{url} into
it's cache. This is mainly useful for debugging purposes. The
@command{dirmngr-client} provides the same feature for a running dirmngr.
@item --shutdown
@opindex shutdown
This commands shuts down an running instance of Dirmngr. This command
has currently no effect.
@item --flush
@opindex flush
This command removes all CRLs from Dirmngr's cache. Client requests
will thus trigger reading of fresh CRLs.
@end table
@mansect options
@node Dirmngr Options
@section Option Summary
@table @gnupgtabopt
@item --options @var{file}
@opindex options
Reads configuration from @var{file} instead of from the default
per-user configuration file. The default configuration file is named
@file{dirmngr.conf} and expected in the home directory.
@item --homedir @var{dir}
@opindex options
Set the name of the home directory to @var{dir}. This option is only
effective when used on the command line. The default depends on the
running mode:
@table @asis
@item With @code{--daemon} given on the commandline
the directory named @file{@value{SYSCONFDIR}} is used for configuration files
and @file{@value{LOCALCACHEDIR}} for cached CRLs.
@item Without @code{--daemon} given on the commandline
the directory named @file{.gnupg} directly below the home directory
of the user unless the environment variable @code{GNUPGHOME} has been set
in which case its value will be used. All kind of data is stored below
this directory.
@end table
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @sc{dirmngr}, such as @option{-vv}.
@item --log-file @var{file}
@opindex log-file
Append all logging output to @var{file}. This is very helpful in
seeing what the agent actually does.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be a
numeric value or by a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
This option is only useful for debugging and the behaviour may change at
any time without notice. FLAGS are bit encoded and may be given in
usual C-Syntax.
@item --debug-all
@opindex debug-all
Same as @code{--debug=0xffffffff}
@item --gnutls-debug @var{level}
@opindex gnutls-debug
Enable debugging of GNUTLS at @var{level}.
@item --debug-wait @var{n}
@opindex debug-wait
When running in server mode, wait @var{n} seconds before entering the
actual processing loop and print the pid. This gives time to attach a
debugger.
@item -s
@itemx --sh
@itemx -c
@itemx --csh
@opindex s
@opindex sh
@opindex c
@opindex csh
Format the info output in daemon mode for use with the standard Bourne
shell respective the C-shell . The default ist to guess it based on the
environment variable @code{SHELL} which is in almost all cases
sufficient.
@item --force
@opindex force
Enabling this option forces loading of expired CRLs; this is only
useful for debugging.
+@item --use-tor
+@opindex use-tor
+This options is not yet functional! It will eventually switch GnuPG
+into a TOR mode to route all network access via TOR (an anonymity
+network).
+
@item --disable-ldap
@opindex disable-ldap
Entirely disables the use of LDAP.
@item --disable-http
@opindex disable-http
Entirely disables the use of HTTP.
@item --ignore-http-dp
@opindex ignore-http-dp
When looking for the location of a CRL, the to be tested certificate
usually contains so called @dfn{CRL Distribution Point} (DP) entries
which are URLs describing the way to access the CRL. The first found DP
entry is used. With this option all entries using the @acronym{HTTP}
scheme are ignored when looking for a suitable DP.
@item --ignore-ldap-dp
@opindex ignore-ldap-dp
This is similar to @option{--ignore-http-dp} but ignores entries using
the @acronym{LDAP} scheme. Both options may be combined resulting in
ignoring DPs entirely.
@item --ignore-ocsp-service-url
@opindex ignore-ocsp-service-url
Ignore all OCSP URLs contained in the certificate. The effect is to
force the use of the default responder.
@item --honor-http-proxy
@opindex honor-http-proxy
If the environment variable @env{http_proxy} has been set, use its
value to access HTTP servers.
@item --http-proxy @var{host}[:@var{port}]
@opindex http-proxy
Use @var{host} and @var{port} to access HTTP servers. The use of this
option overrides the environment variable @env{http_proxy} regardless
whether @option{--honor-http-proxy} has been set.
@item --ldap-proxy @var{host}[:@var{port}]
@opindex ldap-proxy
Use @var{host} and @var{port} to connect to LDAP servers. If @var{port}
is ommitted, port 389 (standard LDAP port) is used. This overrides any
specified host and port part in a LDAP URL and will also be used if host
and port have been ommitted from the URL.
@item --only-ldap-proxy
@opindex only-ldap-proxy
Never use anything else but the LDAP "proxy" as configured with
@option{--ldap-proxy}. Usually @command{dirmngr} tries to use other
configured LDAP server if the connection using the "proxy" failed.
@item --ldapserverlist-file @var{file}
@opindex ldapserverlist-file
Read the list of LDAP servers to consult for CRLs and certificates from
file instead of the default per-user ldap server list file. The default
value for @var{file} is @file{dirmngr_ldapservers.conf} or
@file{ldapservers.conf} when running in @option{--daemon} mode.
This server list file contains one LDAP server per line in the format
@sc{hostname:port:username:password:base_dn}
Lines starting with a @samp{#} are comments.
Note that as usual all strings entered are expected to be UTF-8 encoded.
Obviously this will lead to problems if the password has orginally been
encoded as Latin-1. There is no other solution here than to put such a
password in the binary encoding into the file (i.e. non-ascii characters
won't show up readable).@footnote{The @command{gpgconf} tool might be
helpful for frontends as it allows to edit this configuration file using
percent escaped strings.}
@item --ldaptimeout @var{secs}
@opindex ldaptimeout
Specify the number of seconds to wait for an LDAP query before timing
out. The default is currently 100 seconds. 0 will never timeout.
@item --add-servers
@opindex add-servers
This options makes dirmngr add any servers it discovers when validating
certificates against CRLs to the internal list of servers to consult for
certificates and CRLs.
This options is useful when trying to validate a certificate that has
a CRL distribution point that points to a server that is not already
listed in the ldapserverlist. Dirmngr will always go to this server and
try to download the CRL, but chances are high that the certificate used
to sign the CRL is located on the same server. So if dirmngr doesn't add
that new server to list, it will often not be able to verify the
signature of the CRL unless the @code{--add-servers} option is used.
Note: The current version of dirmngr has this option disabled by default.
@item --allow-ocsp
@opindex allow-ocsp
This option enables OCSP support if requested by the client.
OCSP requests are rejected by default because they may violate the
privacy of the user; for example it is possible to track the time when
a user is reading a mail.
@item --ocsp-responder @var{url}
@opindex ocsp-responder
Use @var{url} as the default OCSP Responder if the certificate does
not contain information about an assigned responder. Note, that
@code{--ocsp-signer} must also be set to a valid certificate.
@item --ocsp-signer @var{fpr}|@var{file}
@opindex ocsp-signer
Use the certificate with the fingerprint @var{fpr} to check the
responses of the default OCSP Responder. Alternativly a filename can be
given in which case the respinse is expected to be signed by one of the
certificates described in that file. Any argument which contains a
slash, dot or tilde is considered a filename. Usual filename expansion
takes place: A tilde at the start followed by a slash is replaced by the
content of @env{HOME}, no slash at start describes a relative filename
which will be searched at the home directory. To make sure that the
@var{file} is searched in the home directory, either prepend the name
with "./" or use a name which contains a dot.
If a response has been signed by a certificate described by these
fingerprints no further check upon the validity of this certificate is
done.
The format of the @var{FILE} is a list of SHA-1 fingerprint, one per
line with optional colons between the bytes. Empty lines and lines
prefix with a hash mark are ignored.
@item --ocsp-max-clock-skew @var{n}
@opindex ocsp-max-clock-skew
The number of seconds a skew between the OCSP responder and them local
clock is accepted. Default is 600 (20 minutes).
@item --ocsp-max-period @var{n}
@opindex ocsp-max-period
Seconds a response is at maximum considered valid after the time given
in the thisUpdate field. Default is 7776000 (90 days).
@item --ocsp-current-period @var{n}
@opindex ocsp-current-period
The number of seconds an OCSP response is considered valid after the
time given in the NEXT_UPDATE datum. Default is 10800 (3 hours).
@item --max-replies @var{n}
@opindex max-replies
Do not return more that @var{n} items in one query. The default is
10.
@item --ignore-cert-extension @var{oid}
@opindex ignore-cert-extension
Add @var{oid} to the list of ignored certificate extensions. The
@var{oid} is expected to be in dotted decimal form, like
@code{2.5.29.3}. This option may be used more than once. Critical
flagged certificate extensions matching one of the OIDs in the list
are treated as if they are actually handled and thus the certificate
won't be rejected due to an unknown critical extension. Use this
option with care because extensions are usually flagged as critical
for a reason.
@item --hkp-cacert @var{file}
Use the root certificates in @var{file} for verification of the TLS
certificates used with @code{hkps} (keyserver access over TLS). If
the file is in PEM format a suffix of @code{.pem} is expected for
@var{file}. This option may be given multiple times to add more
root certificates.
@end table
@c
@c Dirmngr Configuration
@c
@mansect files
@node Dirmngr Configuration
@section Configuration
Dirmngr makes use of several directories when running in daemon mode:
@table @file
@item ~/.gnupg
@itemx /etc/gnupg
The first is the standard home directory for all configuration files.
In the deprecated system daemon mode the second directory is used instead.
@item /etc/gnupg/trusted-certs
This directory should be filled with certificates of Root CAs you
are trusting in checking the CRLs and signing OCSP Reponses.
Usually these are the same certificates you use with the applications
making use of dirmngr. It is expected that each of these certificate
files contain exactly one @acronym{DER} encoded certificate in a file
with the suffix @file{.crt} or @file{.der}. @command{dirmngr} reads
those certificates on startup and when given a SIGHUP. Certificates
which are not readable or do not make up a proper X.509 certificate
are ignored; see the log file for details.
Applications using dirmngr (e.g. gpgsm) can request these
certificates to complete a trust chain in the same way as with the
extra-certs directory (see below).
Note that for OCSP responses the certificate specified using the option
@option{--ocsp-signer} is always considered valid to sign OCSP requests.
@item /etc/gnupg/extra-certs
This directory may contain extra certificates which are preloaded
into the interal cache on startup. Applications using dirmngr (e.g. gpgsm)
can request cached certificates to complete a trust chain.
This is convenient in cases you have a couple intermediate CA certificates
or certificates ususally used to sign OCSP reponses.
These certificates are first tried before going
out to the net to look for them. These certificates must also be
@acronym{DER} encoded and suffixed with @file{.crt} or @file{.der}.
@item @value{LOCALRUNDIR}
This directory is only used in the deprecated system daemon mode. It
keeps the socket file for accessing @command{dirmngr} services. The
name of the socket file will be @file{S.dirmngr}. Make sure that this
directory has the proper permissions to let @command{dirmngr} create
the socket file and that eligible users may read and write to that
socket.
@item ~/.gnupg/crls.d
@itemx @value{LOCALCACHEDIR}/crls.d
The first directory is used to store cached CRLs. The @file{crls.d}
part will be created by dirmngr if it does not exists but you need to
make sure that the upper directory exists. The second directory is
used instead in the deprecated systems daemon mode.
@end table
@manpause
To be able to see what's going on you should create the configure file
@file{~/gnupg/dirmngr.conf} with at least one line:
@example
log-file ~/dirmngr.log
@end example
To be able to perform OCSP requests you probably want to add the line:
@example
allow-ocsp
@end example
To make sure that new options are read and that after the installation
of a new GnuPG versions the installed dirmngr is running, you may want
to kill an existing dirmngr first:
@example
gpgconf --kill dirmngr
@end example
You may check the log file to see whether all desired root
certificates have been loaded correctly.
@c
@c Dirmngr Signals
@c
@mansect signals
@node Dirmngr Signals
@section Use of signals.
A running @command{dirmngr} may be controlled by signals, i.e. using
the @command{kill} command to send a signal to the process.
Here is a list of supported signals:
@table @gnupgtabopt
@item SIGHUP
@cpindex SIGHUP
This signals flushes all internally cached CRLs as well as any cached
certificates. Then the certificate cache is reinitialized as on
startup. Options are re-read from the configuration file. Instead of
sending this signal it is better to use
@example
gpgconf --reload dirmngr
@end example
@item SIGTERM
@cpindex SIGTERM
Shuts down the process but waits until all current requests are
fulfilled. If the process has received 3 of these signals and requests
are still pending, a shutdown is forced. You may also use
@example
gpgconf --kill dirmngr
@end example
instead of this signal
@item SIGINT
@cpindex SIGINT
Shuts down the process immediately.
@item SIGUSR1
@cpindex SIGUSR1
This prints some caching statistics to the log file.
@end table
@c
@c Examples
@c
@mansect examples
@node Dirmngr Examples
@section Examples
Here is an example on how to show dirmngr's internal table of OpenPGP
keyserver addresses. The output is intended for debugging purposes
and not part of a defined API.
@example
gpg-connect-agent --dirmngr 'keyserver --hosttable' /bye
@end example
To inhibit the use of a particular host you have noticed in one of the
keyserver pools, you may use
@example
gpg-connect-agent --dirmngr 'keyserver --dead pgpkeys.bnd.de' /bye
@end example
The description of the @code{keyserver} command can be printed using
@example
gpg-connect-agent --dirmngr 'help keyserver' /bye
@end example
@c
@c Assuan Protocol
@c
@manpause
@node Dirmngr Protocol
@section Dirmngr's Assuan Protocol
Assuan is the IPC protocol used to access dirmngr. This is a
description of the commands implemented by dirmngr.
@menu
* Dirmngr LOOKUP:: Look up a certificate via LDAP
* Dirmngr ISVALID:: Validate a certificate using a CRL or OCSP.
* Dirmngr CHECKCRL:: Validate a certificate using a CRL.
* Dirmngr CHECKOCSP:: Validate a certificate using OCSP.
* Dirmngr CACHECERT:: Put a certificate into the internal cache.
* Dirmngr VALIDATE:: Validate a certificate for debugging.
@end menu
@node Dirmngr LOOKUP
@subsection Return the certificate(s) found
Lookup certificate. To allow multiple patterns (which are ORed)
quoting is required: Spaces are to be translated into "+" or into
"%20"; obviously this requires that the usual escape quoting rules
are applied. The server responds with:
@example
S: D
S: END
S: D
S: END
S: OK
@end example
In this example 2 certificates are returned. The server may return
any number of certificates; OK will also be returned when no
certificates were found. The dirmngr might return a status line
@example
S: S TRUNCATED
@end example
To indicate that the output was truncated to N items due to a
limitation of the server or by an arbitrary set limit.
The option @option{--url} may be used if instead of a search pattern a
complete URL to the certificate is known:
@example
C: LOOKUP --url CN%3DWerner%20Koch,o%3DIntevation%20GmbH,c%3DDE?userCertificate
@end example
If the option @option{--cache-only} is given, no external lookup is done
so that only certificates from the cache are returned.
With the option @option{--single}, the first and only the first match
will be returned. Unless option @option{--cache-only} is also used, no
local lookup will be done in this case.
@node Dirmngr ISVALID
@subsection Validate a certificate using a CRL or OCSP
@example
ISVALID [--only-ocsp] [--force-default-responder] @var{certid}|@var{certfpr}
@end example
Check whether the certificate described by the @var{certid} has been
revoked. Due to caching, the Dirmngr is able to answer immediately in
most cases.
The @var{certid} is a hex encoded string consisting of two parts,
delimited by a single dot. The first part is the SHA-1 hash of the
issuer name and the second part the serial number.
Alternatively the certificate's SHA-1 fingerprint @var{certfpr} may be
given in which case an OCSP request is done before consulting the CRL.
If the option @option{--only-ocsp} is given, no fallback to a CRL check
will be used. If the option @option{--force-default-responder} is
given, only the default OCSP responder will be used and any other
methods of obtaining an OCSP responder URL won't be used.
@noindent
Common return values are:
@table @code
@item GPG_ERR_NO_ERROR (0)
This is the positive answer: The certificate is not revoked and we have
an up-to-date revocation list for that certificate. If OCSP was used
the responder confirmed that the certificate has not been revoked.
@item GPG_ERR_CERT_REVOKED
This is the negative answer: The certificate has been revoked. Either
it is in a CRL and that list is up to date or an OCSP responder informed
us that it has been revoked.
@item GPG_ERR_NO_CRL_KNOWN
No CRL is known for this certificate or the CRL is not valid or out of
date.
@item GPG_ERR_NO_DATA
The OCSP responder returned an ``unknown'' status. This means that it
is not aware of the certificate's status.
@item GPG_ERR_NOT_SUPPORTED
This is commonly seen if OCSP support has not been enabled in the
configuration.
@end table
If DirMngr has not enough information about the given certificate (which
is the case for not yet cached certificates), it will will inquire the
missing data:
@example
S: INQUIRE SENDCERT
C: D
C: END
@end example
A client should be aware that DirMngr may ask for more than one
certificate.
If Dirmngr has a certificate but the signature of the certificate
could not been validated because the root certificate is not known to
dirmngr as trusted, it may ask back to see whether the client trusts
this the root certificate:
@example
S: INQUIRE ISTRUSTED
C: D 1
C: END
@end example
Only this answer will let Dirmngr consider the CRL as valid.
@node Dirmngr CHECKCRL
@subsection Validate a certificate using a CRL
Check whether the certificate with FINGERPRINT (SHA-1 hash of the
entire X.509 certificate blob) is valid or not by consulting the CRL
responsible for this certificate. If the fingerprint has not been
given or the certificate is not know, the function inquires the
certificate using:
@example
S: INQUIRE TARGETCERT
C: D
C: END
@end example
Thus the caller is expected to return the certificate for the request
(which should match FINGERPRINT) as a binary blob. Processing then
takes place without further interaction; in particular dirmngr tries
to locate other required certificate by its own mechanism which
includes a local certificate store as well as a list of trusted root
certificates.
@noindent
The return code is 0 for success; i.e. the certificate has not been
revoked or one of the usual error codes from libgpg-error.
@node Dirmngr CHECKOCSP
@subsection Validate a certificate using OCSP
@example
CHECKOCSP [--force-default-responder] [@var{fingerprint}]
@end example
Check whether the certificate with @var{fingerprint} (the SHA-1 hash of
the entire X.509 certificate blob) is valid by consulting the appropiate
OCSP responder. If the fingerprint has not been given or the
certificate is not known by Dirmngr, the function inquires the
certificate using:
@example
S: INQUIRE TARGETCERT
C: D
C: END
@end example
Thus the caller is expected to return the certificate for the request
(which should match @var{fingerprint}) as a binary blob. Processing
then takes place without further interaction; in particular dirmngr
tries to locate other required certificates by its own mechanism which
includes a local certificate store as well as a list of trusted root
certificates.
If the option @option{--force-default-responder} is given, only the
default OCSP responder is used. This option is the per-command variant
of the global option @option{--ignore-ocsp-service-url}.
@noindent
The return code is 0 for success; i.e. the certificate has not been
revoked or one of the usual error codes from libgpg-error.
@node Dirmngr CACHECERT
@subsection Put a certificate into the internal cache
Put a certificate into the internal cache. This command might be
useful if a client knows in advance certificates required for a test and
wnats to make sure they get added to the internal cache. It is also
helpful for debugging. To get the actual certificate, this command
immediately inquires it using
@example
S: INQUIRE TARGETCERT
C: D
C: END
@end example
Thus the caller is expected to return the certificate for the request
as a binary blob.
@noindent
The return code is 0 for success; i.e. the certificate has not been
succesfully cached or one of the usual error codes from libgpg-error.
@node Dirmngr VALIDATE
@subsection Validate a certificate for debugging
Validate a certificate using the certificate validation function used
internally by dirmngr. This command is only useful for debugging. To
get the actual certificate, this command immediately inquires it using
@example
S: INQUIRE TARGETCERT
C: D
C: END
@end example
Thus the caller is expected to return the certificate for the request
as a binary blob.
@mansect see also
@ifset isman
@command{gpgsm}(1),
@command{dirmngr-client}(1)
@end ifset
@include see-also-note.texi
@c
@c !!! UNDER CONSTRUCTION !!!
@c
@c
@c @section Verifying a Certificate
@c
@c There are several ways to request services from Dirmngr. Almost all of
@c them are done using the Assuan protocol. What we describe here is the
@c Assuan command CHECKCRL as used for example by the dirmnr-client tool if
@c invoked as
@c
@c @example
@c dirmngr-client foo.crt
@c @end example
@c
@c This command will send an Assuan request to an already running Dirmngr
@c instance. foo.crt is expected to be a standard X.509 certificate and
@c dirmngr will receive the Assuan command
@c
@c @example
@c CHECKCRL @var [{fingerprint}]
@c @end example
@c
@c @var{fingerprint} is optional and expected to be the SHA-1 has of the
@c DER encoding of the certificate under question. It is to be HEX
@c encoded. The rationale for sending the fingerprint is that it allows
@c dirmngr to reply immediatly if it has already cached such a request. If
@c this is not the case and no certificate has been found in dirmngr's
@c internal certificate storage, dirmngr will request the certificate using
@c the Assuan inquiry
@c
@c @example
@c INQUIRE TARGETCERT
@c @end example
@c
@c The caller (in our example dirmngr-client) is then expected to return
@c the certificate for the request (which should match @var{fingerprint})
@c as a binary blob.
@c
@c Dirmngr now passes control to @code{crl_cache_cert_isvalid}. This
@c function checks whether a CRL item exists for target certificate. These
@c CRL items are kept in a database of already loaded and verified CRLs.
@c This mechanism is called the CRL cache. Obviously timestamps are kept
@c there with each item to cope with the expiration date of the CRL. The
@c possible return values are: @code{0} to indicate that a valid CRL is
@c available for the certificate and the certificate itself is not listed
@c in this CRL, @code{GPG_ERR_CERT_REVOKED} to indicate that the certificate is
@c listed in the CRL or @code{GPG_ERR_NO_CRL_KNOWN} in cases where no CRL or no
@c information is available. The first two codes are immediatly returned to
@c the caller and the processing of this request has been done.
@c
@c Only the @code{GPG_ERR_NO_CRL_KNOWN} needs more attention: Dirmngr now
@c calls @code{clr_cache_reload_crl} and if this succeeds calls
@c @code{crl_cache_cert_isvald) once more. All further errors are
@c immediately returned to the caller.
@c
@c @code{crl_cache_reload_crl} is the actual heart of the CRL management.
@c It locates the corresponding CRL for the target certificate, reads and
@c verifies this CRL and stores it in the CRL cache. It works like this:
@c
@c * Loop over all crlDPs in the target certificate.
@c * If the crlDP is invalid immediately terminate the loop.
@c * Loop over all names in the current crlDP.
@c * If the URL scheme is unknown or not enabled
@c (--ignore-http-dp, --ignore-ldap-dp) continues with
@c the next name.
@c * @code{crl_fetch} is called to actually retrieve the CRL.
@c In case of problems this name is ignore and we continue with
@c the next name. Note that @code{crl_fetch} does only return
@c a descriptor for the CRL for further reading so does the CRL
@c does not yet end up in memory.
@c * @code{crl_cache_insert} is called with that descriptor to
@c actually read the CRL into the cache. See below for a
@c description of this function. If there is any error (e.g. read
@c problem, CRL not correctly signed or verification of signature
@c not possible), this descriptor is rejected and we continue
@c with the next name. If the CRL has been successfully loaded,
@c the loop is terminated.
@c * If no crlDP has been found in the previous loop use a default CRL.
@c Note, that if any crlDP has been found but loading of the CRL failed,
@c this condition is not true.
@c * Try to load a CRL from all configured servers (ldapservers.conf)
@c in turn. The first server returning a CRL is used.
@c * @code(crl_cache_insert) is then used to actually insert the CRL
@c into the cache. If this failed we give up immediatley without
@c checking the rest of the servers from the first step.
@c * Ready.
@c
@c
@c The @code{crl_cache_insert} function takes care of reading the bulk of
@c the CRL, parsing it and checking the signature. It works like this: A
@c new database file is created using a temporary file name. The CRL
@c parsing machinery is started and all items of the CRL are put into
@c this database file. At the end the issuer certificate of the CRL
@c needs to be retrieved. Three cases are to be distinguished:
@c
@c a) An authorityKeyIdentifier with an issuer and serialno exits: The
@c certificate is retrieved using @code{find_cert_bysn}. If
@c the certificate is in the certificate cache, it is directly
@c returned. Then the requester (i.e. the client who requested the
@c CRL check) is asked via the Assuan inquiry ``SENDCERT'' whether
@c he can provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested issuer and seriano (This is
@c needed because the LDAP layer may return several certificates as
@c LDAP as no standard way to retrieve by serial number).
@c
@c b) An authorityKeyIdentifier with a key ID exists: The certificate is
@c retrieved using @code{find_cert_bysubject}. If the certificate is
@c in the certificate cache, it is directly returned. Then the
@c requester is asked via the Assuan inquiry ``SENDCERT_SKI'' whether
@c he can provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested subject and key ID.
@c
@c c) No authorityKeyIdentifier exits: The certificate is retrieved
@c using @code{find_cert_bysubject} without the key ID argument. If
@c the certificate is in the certificate cache the first one with a
@c matching subject is is directly returned. Then the requester is
@c asked via the Assuan inquiry ``SENDCERT'' and an exact
@c specification of the subject whether he can
@c provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested subject; the first certificate
@c with a matching subject is then returned.
@c
@c If no certificate was found, the function returns with the error
@c GPG_ERR_MISSING_CERT. Now the signature is verified. If this fails,
@c the erro is returned. On success the @code{validate_cert_chain} is
@c used to verify that the certificate is actually valid.
@c
@c Here we may encounter a recursive situation:
@c @code{validate_cert_chain} needs to look at other certificates and
@c also at CRLs to check whether tehse other certificates and well, the
@c CRL issuer certificate itself are not revoked. FIXME: We need to make
@c sure that @code{validate_cert_chain} does not try to lookup the CRL we
@c are currently processing. This would be a catch-22 and may indicate a
@c broken PKI. However, due to overlapping expiring times and imprecise
@c clocks thsi may actually happen.
@c
@c For historical reasons the Assuan command ISVALID is a bit different
@c to CHECKCRL but this is mainly due to different calling conventions.
@c In the end the same fucntionality is used, albeit hidden by a couple
@c of indirection and argument and result code mangling. It furthere
@c ingetrages OCSP checking depending on options are the way it is
@c called. GPGSM still uses this command but might eventuall switch over
@c to CHECKCRL and CHECKOCSP so that ISVALID can be retired.
@c
@c
@c @section Validating a certificate
@c
@c We describe here how the internal function @code{validate_cert_chain}
@c works. Note that mainly testing purposes this functionality may be
@c called directly using @cmd{dirmngr-client --validate @file{foo.crt}}.
@c
@c For backward compatibility this function returns success if Dirmngr is
@c not used as a system daemon. Thus not validating the certicates at
@c all. FIXME: This is definitely not correct and should be fixed ASAP.
@c
@c The function takes the target certificate and a mode argument as
@c parameters and returns an error code and optionally the closes
@c expiration time of all certificates in the chain.
@c
@c We first check that the certificate may be used for the requested
@c purpose (i.e. OCSP or CRL signing). If this is not the case
@c GPG_ERR_WRONG_KEY_USAGE is returned.
@c
@c The next step is to find the trust anchor (root certificate) and to
@c assemble the chain in memory: Starting with the target certificate,
@c the expiration time is checked against the current date, unknown
@c critical extensions are detected and certificate policies are matched
@c (We only allow 2.289.9.9 but I have no clue about that OID and from
@c where I got it - it does not even seem to be assigned - debug cruft?).
@c
@c Now if this certificate is a self-signed one, we have reached the
@c trust anchor. In this case we check that the signature is good, the
@c certificate is allowed to act as a CA, that it is a trusted one (by
@c checking whether it is has been put into the trusted-certs
@c configuration directory) and finally prepend into to our list
@c representing the certificate chain. This steps ends then.
@c
@c If it is not a self-signed certificate, we check that the chain won't
@c get too long (current limit is 100), if this is the case we terminate
@c with the error GPG_ERR_BAD_CERT_CHAIN.
@c
@c Now the issuer's certificate is looked up: If an
@c authorityKeyIdentifier is available, this one is used to locate the
@c certificate either using issuer and serialnumber or subject DN
@c (i.e. the issuer's DN) and the keyID. The functions
@c @code{find_cert_bysn) and @code{find_cert_bysubject} are used
@c respectively. The have already been described above under the
@c description of @code{crl_cache_insert}. If no certificate was found
@c or with no authorityKeyIdentifier, only the cache is consulted using
@c @code{get_cert_bysubject}. The latter is is done under the assumption
@c that a matching certificate has explicitly been put into the
@c certificate cache. If the issuer's certificate could not be found,
@c the validation terminates with the error code @code{GPG_ERR_MISSING_CERT}.
@c
@c If the issuer's certificate has been found, the signature of the
@c actual certificate is checked and in case this fails the error
@c #code{GPG_ERR_BAD_CERT_CHAIN} is returned. If the signature checks out, the
@c maximum cahin length of the issueing certificate is checked as well as
@c the capiblity of the certificate (i.e. whether he may be used for
@c certificate signing). Then the certificate is prepended to our list
@c representing the certificate chain. Finally the loop is continued now
@c with the issuer's certificate as the current certificate.
@c
@c After the end of the loop and if no error as been encountered
@c (i.e. the certificate chain has been assempled correctly), a check is
@c done whether any certificate expired or a critical policy has not been
@c met. In any of these cases the validation terminates with an
@c appropriate error.
@c
@c Finally the function @code{check_revocations} is called to verify no
@c certificate in the assempled chain has been revoked: This is an
@c recursive process because a CRL has to be checked for each certificate
@c in the chain except for the root certificate, of which we already know
@c that it is trusted and we avoid checking a CRL here due to common
@c setup problems and the assumption that a revoked root certifcate has
@c been removed from the list of trusted certificates.
@c
@c
@c
@c
@c @section Looking up certificates through LDAP.
@c
@c This describes the LDAP layer to retrieve certificates.
@c the functions @code{ca_cert_fetch} and @code{fetch_next_ksba_cert} are
@c used for this. The first one starts a search and the second one is
@c used to retrieve certificate after certificate.
@c
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c
index cf53ebc61..464b89b78 100644
--- a/tools/gpgconf-comp.c
+++ b/tools/gpgconf-comp.c
@@ -1,3779 +1,3779 @@
/* gpgconf-comp.c - Configuration utility for GnuPG.
* Copyright (C) 2004, 2007, 2008, 2009, 2010,
* 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GnuPG; if not, see .
*/
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_SIGNAL_H
# include
#endif
#include
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN 1
# include
#else
# include
# include
#endif
/* For log_logv(), asctimestamp(), gnupg_get_time (). */
#include "util.h"
#include "i18n.h"
#include "exechelp.h"
#include "gc-opt-flags.h"
#include "gpgconf.h"
/* There is a problem with gpg 1.4 under Windows: --gpgconf-list
returns a plain filename without escaping. As long as we have not
fixed that we need to use gpg2. */
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
#define GPGNAME "gpg2"
#else
#define GPGNAME GPG_NAME
#endif
/* TODO:
Components: Add more components and their options.
Robustness: Do more validation. Call programs to do validation for us.
Add options to change backend binary path.
Extract binary path for some backends from gpgsm/gpg config.
*/
#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ))
void gc_error (int status, int errnum, const char *fmt, ...) \
__attribute__ ((format (printf, 3, 4)));
#endif
/* Output a diagnostic message. If ERRNUM is not 0, then the output
is followed by a colon, a white space, and the error string for the
error number ERRNUM. In any case the output is finished by a
newline. The message is prepended by the program name, a colon,
and a whitespace. The output may be further formatted or
redirected by the jnlib logging facility. */
void
gc_error (int status, int errnum, const char *fmt, ...)
{
va_list arg_ptr;
va_start (arg_ptr, fmt);
log_logv (GPGRT_LOG_ERROR, fmt, arg_ptr);
va_end (arg_ptr);
if (errnum)
log_printf (": %s\n", strerror (errnum));
else
log_printf ("\n");
if (status)
{
log_printf (NULL);
log_printf ("fatal error (exit status %i)\n", status);
exit (status);
}
}
/* Forward declaration. */
static void gpg_agent_runtime_change (int killflag);
static void scdaemon_runtime_change (int killflag);
static void dirmngr_runtime_change (int killflag);
/* Backend configuration. Backends are used to decide how the default
and current value of an option can be determined, and how the
option can be changed. To every option in every component belongs
exactly one backend that controls and determines the option. Some
backends are programs from the GPG system. Others might be
implemented by GPGConf itself. If you change this enum, don't
forget to update GC_BACKEND below. */
typedef enum
{
/* Any backend, used for find_option (). */
GC_BACKEND_ANY,
/* The Gnu Privacy Guard. */
GC_BACKEND_GPG,
/* The Gnu Privacy Guard for S/MIME. */
GC_BACKEND_GPGSM,
/* The GPG Agent. */
GC_BACKEND_GPG_AGENT,
/* The GnuPG SCDaemon. */
GC_BACKEND_SCDAEMON,
/* The GnuPG directory manager. */
GC_BACKEND_DIRMNGR,
/* The LDAP server list file for the director manager. */
GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST,
/* The Pinentry (not a part of GnuPG, proper). */
GC_BACKEND_PINENTRY,
/* The number of the above entries. */
GC_BACKEND_NR
} gc_backend_t;
/* To be able to implement generic algorithms for the various
backends, we collect all information about them in this struct. */
static struct
{
/* The name of the backend. */
const char *name;
/* The name of the program that acts as the backend. Some backends
don't have an associated program, but are implemented directly by
GPGConf. In this case, PROGRAM is NULL. */
char *program;
/* The module name (GNUPG_MODULE_NAME_foo) as defined by
../common/util.h. This value is used to get the actual installed
path of the program. 0 is used if no backend program is
available. */
char module_name;
/* The runtime change callback. If KILLFLAG is true the component
is killed and not just reloaded. */
void (*runtime_change) (int killflag);
/* The option name for the configuration filename of this backend.
This must be an absolute filename. It can be an option from a
different backend (but then ordering of the options might
matter). Note: This must be unique among all components. */
const char *option_config_filename;
/* If this is a file backend rather than a program backend, then
this is the name of the option associated with the file. */
const char *option_name;
} gc_backend[GC_BACKEND_NR] =
{
{ NULL }, /* GC_BACKEND_ANY dummy entry. */
{ GPG_DISP_NAME, GPGNAME, GNUPG_MODULE_NAME_GPG,
NULL, GPGCONF_NAME "-" GPG_NAME ".conf" },
{ GPGSM_DISP_NAME, GPGSM_NAME, GNUPG_MODULE_NAME_GPGSM,
NULL, GPGCONF_NAME "-" GPGSM_NAME ".conf" },
{ GPG_AGENT_DISP_NAME, GPG_AGENT_NAME, GNUPG_MODULE_NAME_AGENT,
gpg_agent_runtime_change, GPGCONF_NAME"-" GPG_AGENT_NAME ".conf" },
{ SCDAEMON_DISP_NAME, SCDAEMON_NAME, GNUPG_MODULE_NAME_SCDAEMON,
scdaemon_runtime_change, GPGCONF_NAME"-" SCDAEMON_NAME ".conf" },
{ DIRMNGR_DISP_NAME, DIRMNGR_NAME, GNUPG_MODULE_NAME_DIRMNGR,
dirmngr_runtime_change, GPGCONF_NAME "-" DIRMNGR_NAME ".conf" },
{ DIRMNGR_DISP_NAME " LDAP Server List", NULL, 0,
NULL, "ldapserverlist-file", "LDAP Server" },
{ "Pinentry", "pinentry", GNUPG_MODULE_NAME_PINENTRY,
NULL, GPGCONF_NAME "-pinentry.conf" },
};
/* Option configuration. */
/* An option might take an argument, or not. Argument types can be
basic or complex. Basic types are generic and easy to validate.
Complex types provide more specific information about the intended
use, but can be difficult to validate. If you add to this enum,
don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE
NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL
INTERFACE. */
typedef enum
{
/* Basic argument types. */
/* No argument. */
GC_ARG_TYPE_NONE = 0,
/* A String argument. */
GC_ARG_TYPE_STRING = 1,
/* A signed integer argument. */
GC_ARG_TYPE_INT32 = 2,
/* An unsigned integer argument. */
GC_ARG_TYPE_UINT32 = 3,
/* ADD NEW BASIC TYPE ENTRIES HERE. */
/* Complex argument types. */
/* A complete filename. */
GC_ARG_TYPE_FILENAME = 32,
/* An LDAP server in the format
HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */
GC_ARG_TYPE_LDAP_SERVER = 33,
/* A 40 character fingerprint. */
GC_ARG_TYPE_KEY_FPR = 34,
/* A user ID or key ID or fingerprint for a certificate. */
GC_ARG_TYPE_PUB_KEY = 35,
/* A user ID or key ID or fingerprint for a certificate with a key. */
GC_ARG_TYPE_SEC_KEY = 36,
/* A alias list made up of a key, an equal sign and a space
separated list of values. */
GC_ARG_TYPE_ALIAS_LIST = 37,
/* ADD NEW COMPLEX TYPE ENTRIES HERE. */
/* The number of the above entries. */
GC_ARG_TYPE_NR
} gc_arg_type_t;
/* For every argument, we record some information about it in the
following struct. */
static struct
{
/* For every argument type exists a basic argument type that can be
used as a fallback for input and validation purposes. */
gc_arg_type_t fallback;
/* Human-readable name of the type. */
const char *name;
} gc_arg_type[GC_ARG_TYPE_NR] =
{
/* The basic argument types have their own types as fallback. */
{ GC_ARG_TYPE_NONE, "none" },
{ GC_ARG_TYPE_STRING, "string" },
{ GC_ARG_TYPE_INT32, "int32" },
{ GC_ARG_TYPE_UINT32, "uint32" },
/* Reserved basic type entries for future extension. */
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
/* The complex argument types have a basic type as fallback. */
{ GC_ARG_TYPE_STRING, "filename" },
{ GC_ARG_TYPE_STRING, "ldap server" },
{ GC_ARG_TYPE_STRING, "key fpr" },
{ GC_ARG_TYPE_STRING, "pub key" },
{ GC_ARG_TYPE_STRING, "sec key" },
{ GC_ARG_TYPE_STRING, "alias list" },
};
/* Every option has an associated expert level, than can be used to
hide advanced and expert options from beginners. If you add to
this list, don't forget to update GC_LEVEL below. YOU MUST NOT
CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE
EXTERNAL INTERFACE. */
typedef enum
{
/* The basic options should always be displayed. */
GC_LEVEL_BASIC,
/* The advanced options may be hidden from beginners. */
GC_LEVEL_ADVANCED,
/* The expert options should only be displayed to experts. */
GC_LEVEL_EXPERT,
/* The invisible options should normally never be displayed. */
GC_LEVEL_INVISIBLE,
/* The internal options are never exported, they mark options that
are recorded for internal use only. */
GC_LEVEL_INTERNAL,
/* ADD NEW ENTRIES HERE. */
/* The number of the above entries. */
GC_LEVEL_NR
} gc_expert_level_t;
/* A description for each expert level. */
static struct
{
const char *name;
} gc_level[] =
{
{ "basic" },
{ "advanced" },
{ "expert" },
{ "invisible" },
{ "internal" }
};
/* Option flags. The flags which are used by the backends are defined
by gc-opt-flags.h, included above.
YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE
PART OF THE EXTERNAL INTERFACE. */
/* Some entries in the option list are not options, but mark the
beginning of a new group of options. These entries have the GROUP
flag set. */
#define GC_OPT_FLAG_GROUP (1UL << 0)
/* The ARG_OPT flag for an option indicates that the argument is
optional. This is never set for GC_ARG_TYPE_NONE options. */
#define GC_OPT_FLAG_ARG_OPT (1UL << 1)
/* The LIST flag for an option indicates that the option can occur
several times. A comma separated list of arguments is used as the
argument value. */
#define GC_OPT_FLAG_LIST (1UL << 2)
/* A human-readable description for each flag. */
static struct
{
const char *name;
} gc_flag[] =
{
{ "group" },
{ "optional arg" },
{ "list" },
{ "runtime" },
{ "default" },
{ "default desc" },
{ "no arg desc" },
{ "no change" }
};
/* To each option, or group marker, the information in the GC_OPTION
struct is provided. If you change this, don't forget to update the
option list of each component. */
struct gc_option
{
/* If this is NULL, then this is a terminator in an array of unknown
length. Otherwise, if this entry is a group marker (see FLAGS),
then this is the name of the group described by this entry.
Otherwise it is the name of the option described by this
entry. The name must not contain a colon. */
const char *name;
/* The option flags. If the GROUP flag is set, then this entry is a
group marker, not an option, and only the fields LEVEL,
DESC_DOMAIN and DESC are valid. In all other cases, this entry
describes a new option and all fields are valid. */
unsigned long flags;
/* The expert level. This field is valid for options and groups. A
group has the expert level of the lowest-level option in the
group. */
gc_expert_level_t level;
/* A gettext domain in which the following description can be found.
If this is NULL, then DESC is not translated. Valid for groups
and options.
Note that we try to keep the description of groups within the
gnupg domain.
IMPORTANT: If you add a new domain please make sure to add a code
set switching call to the function my_dgettext further below. */
const char *desc_domain;
/* A gettext description for this group or option. If it starts
with a '|', then the string up to the next '|' describes the
argument, and the description follows the second '|'.
In general enclosing these description in N_() is not required
because the description should be identical to the one in the
help menu of the respective program. */
const char *desc;
/* The following fields are only valid for options. */
/* The type of the option argument. */
gc_arg_type_t arg_type;
/* The backend that implements this option. */
gc_backend_t backend;
/* The following fields are set to NULL at startup (because all
option's are declared as static variables). They are at the end
of the list so that they can be omitted from the option
declarations. */
/* This is true if the option is supported by this version of the
backend. */
int active;
/* The default value for this option. This is NULL if the option is
not present in the backend, the empty string if no default is
available, and otherwise a quoted string. */
char *default_value;
/* The default argument is only valid if the "optional arg" flag is
set, and specifies the default argument (value) that is used if
the argument is omitted. */
char *default_arg;
/* The current value of this option. */
char *value;
/* The new flags for this option. The only defined flag is actually
GC_OPT_FLAG_DEFAULT, and it means that the option should be
deleted. In this case, NEW_VALUE is NULL. */
unsigned long new_flags;
/* The new value of this option. */
char *new_value;
};
typedef struct gc_option gc_option_t;
/* Use this macro to terminate an option list. */
#define GC_OPTION_NULL { NULL }
#ifndef BUILD_WITH_AGENT
#define gc_options_gpg_agent NULL
#else
/* The options of the GC_COMPONENT_GPG_AGENT component. */
static gc_option_t gc_options_gpg_agent[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-" GPG_AGENT_NAME ".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not use the SCdaemon",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "enable ssh support",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "enable putty support",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT },
{ "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "default-cache-ttl", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg",
"|N|expire cached PINs after N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|expire SSH keys after N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "max-cache-ttl", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set maximum PIN cache lifetime to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set maximum SSH key lifetime to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED,
"gnupg", "allow passphrase to be prompted through Emacs",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-external-cache", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg", "disallow the use of an external password cache",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg", "disallow clients to mark keys as \"trusted\"",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg", "allow caller to override the pinentry",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "do not grab keyboard and mouse",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Passphrase policy",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options enforcing a passphrase policy") },
{ "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow to bypass the passphrase policy"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-len", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|set minimal required length for new passphrases to N"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|require at least N non-alpha characters for a new passphrase"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT,
"gnupg", N_("|FILE|check new passphrases against pattern in FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "max-passphrase-days", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|expire the passphrase after N days"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "enable-passphrase-history", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow the reuse of old passphrases"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_AGENT*/
#ifndef BUILD_WITH_SCDAEMON
#define gc_options_scdaemon NULL
#else
/* The options of the GC_COMPONENT_SCDAEMON component. */
static gc_option_t gc_options_scdaemon[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"SCDAEMON_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "reader-port", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "|N|connect to reader at port N",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "ctapi-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use NAME as ct-API driver",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "pcsc-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use NAME as PC/SC driver",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "disable-ccid", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "do not use the internal CCID driver",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "do not use a reader's pinpad",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "enable-pinpad-varlen",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "use variable length input for pinpad",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "card-timeout", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "|N|disconnect the card after N seconds of inactivity",
GC_ARG_TYPE_UINT32, GC_BACKEND_SCDAEMON },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "log-file", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write a log to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "deny-admin", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "deny the use of admin card commands",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_SCDAEMON*/
#ifndef BUILD_WITH_GPG
#define gc_options_gpg NULL
#else
/* The options of the GC_COMPONENT_GPG component. */
static gc_option_t gc_options_gpg[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"GPG_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|use NAME as default secret key"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|encrypt to user ID NAME as well"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED,
"gnupg", N_("|SPEC|set up email aliases"),
GC_ARG_TYPE_ALIAS_LIST, GC_BACKEND_GPG },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
{ "default_pubkey_algo",
(GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
/* { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */
/* NULL, NULL, */
/* GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, */
{ "Keyserver",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration for Keyservers") },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|URL|use keyserver at URL"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("allow PKA lookups (DNS requests)"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|MECHANISMS|use MECHANISMS to locate keys by mail address"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_GPG*/
#ifndef BUILD_WITH_GPGSM
#define gc_options_gpgsm NULL
#else
/* The options of the GC_COMPONENT_GPGSM component. */
static gc_option_t gc_options_gpgsm[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"GPGSM_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|use NAME as default secret key"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|encrypt to user ID NAME as well"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "use system's dirmngr if available",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("disable all access to the dirmngr"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|NAME|use encoding NAME for PKCS#12 passphrases"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", N_("|SPEC|use this keyserver to lookup keys"),
GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_GPGSM },
{ "default_pubkey_algo",
(GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPGSM },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "never consult a CRL",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("do not check CRLs for root certificates"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "check validity using OCSP",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|N|number of certificates to include",
GC_ARG_TYPE_INT32, GC_BACKEND_GPGSM },
{ "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not check certificate policies",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "fetch missing issuer certificates",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use cipher algorithm NAME",
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_GPGSM*/
#ifndef BUILD_WITH_DIRMNGR
#define gc_options_dirmngr NULL
#else
/* The options of the GC_COMPONENT_DIRMNGR component. */
static gc_option_t gc_options_dirmngr[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"DIRMNGR_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"dirmngr", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Format",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the format of the output") },
{ "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "sh-style command output",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "csh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "csh-style command output",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"dirmngr", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"dirmngr", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "no-detach", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "do not detach from the console",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "Enforcement",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the interactivity and enforcement") },
{ "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "run without asking a user",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "force loading of outdated CRLs",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "TOR",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the use of TOR") },
- { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+ { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
"dirmngr", "route all network traffic via TOR",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "HTTP",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Configuration for HTTP servers") },
{ "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "inhibit the use of HTTP",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore HTTP CRL distribution points",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|URL|redirect all HTTP requests to URL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("use system's HTTP proxy setting"),
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "LDAP",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration of LDAP servers to use") },
{ "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "inhibit the use of LDAP",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore LDAP CRL distribution points",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|HOST|use HOST for LDAP queries",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "do not use fallback hosts with --ldap-proxy",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "add new servers discovered in CRL distribution points"
" to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|N|set LDAP timeout to N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
/* The following entry must not be removed, as it is required for
the GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST. */
{ "ldapserverlist-file",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
"dirmngr", "|FILE|read LDAP server list from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
/* This entry must come after at least one entry for
GC_BACKEND_DIRMNGR in this component, so that the entry for
"ldapserverlist-file will be initialized before this one. */
{ "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", N_("LDAP server list"),
GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST },
{ "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|N|do not return more than N items in one query",
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "OCSP",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Configuration for OCSP") },
{ "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "allow sending OCSP requests",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore certificate contained OCSP service URLs",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|URL|use OCSP responder at URL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|FPR|OCSP response signed by FPR",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_DIRMNGR*/
/* The options of the GC_COMPONENT_PINENTRY component. */
static gc_option_t gc_options_pinentry[] =
{
/* A dummy option to allow gc_component_list_components to find the
pinentry backend. Needs to be a conf file. */
{ GPGCONF_NAME"-pinentry.conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_PINENTRY },
GC_OPTION_NULL
};
/* Component system. Each component is a set of options that can be
configured at the same time. If you change this, don't forget to
update GC_COMPONENT below. */
typedef enum
{
/* The classic GPG for OpenPGP. */
GC_COMPONENT_GPG,
/* The GPG Agent. */
GC_COMPONENT_GPG_AGENT,
/* The Smardcard Daemon. */
GC_COMPONENT_SCDAEMON,
/* GPG for S/MIME. */
GC_COMPONENT_GPGSM,
/* The LDAP Directory Manager for CRLs. */
GC_COMPONENT_DIRMNGR,
/* The external Pinentry. */
GC_COMPONENT_PINENTRY,
/* The number of components. */
GC_COMPONENT_NR
} gc_component_t;
/* The information associated with each component. */
static struct
{
/* The name of this component. Must not contain a colon (':')
character. */
const char *name;
/* The gettext domain for the description DESC. If this is NULL,
then the description is not translated. */
const char *desc_domain;
/* The description for this domain. */
const char *desc;
/* The list of options for this component, terminated by
GC_OPTION_NULL. */
gc_option_t *options;
} gc_component[] =
{
{ "gpg", "gnupg", N_("GPG for OpenPGP"), gc_options_gpg },
{ "gpg-agent","gnupg", N_("GPG Agent"), gc_options_gpg_agent },
{ "scdaemon", "gnupg", N_("Smartcard Daemon"), gc_options_scdaemon },
{ "gpgsm", "gnupg", N_("GPG for S/MIME"), gc_options_gpgsm },
{ "dirmngr", "gnupg", N_("Key Acquirer"), gc_options_dirmngr },
{ "pinentry", "gnupg", N_("PIN and Passphrase Entry"), gc_options_pinentry }
};
/* Structure used to collect error output of the backend programs. */
struct error_line_s;
typedef struct error_line_s *error_line_t;
struct error_line_s
{
error_line_t next; /* Link to next item. */
const char *fname; /* Name of the config file (points into BUFFER). */
unsigned int lineno; /* Line number of the config file. */
const char *errtext; /* Text of the error message (points into BUFFER). */
char buffer[1]; /* Helper buffer. */
};
/* Engine specific support. */
static void
gpg_agent_runtime_change (int killflag)
{
gpg_error_t err;
const char *pgmname;
const char *argv[3];
pid_t pid;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
argv[0] = "--no-autostart";
argv[1] = killflag? "KILLAGENT" : "RELOADAGENT";
argv[2] = NULL;
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[1], gpg_strerror (err));
gnupg_release_process (pid);
}
static void
scdaemon_runtime_change (int killflag)
{
gpg_error_t err;
const char *pgmname;
const char *argv[7];
pid_t pid;
(void)killflag; /* For scdaemon kill and reload are synonyms. */
/* We use "GETINFO app_running" to see whether the agent is already
running and kill it only in this case. This avoids an explicit
starting of the agent in case it is not yet running. There is
obviously a race condition but that should not harm too much. */
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
argv[0] = "-s";
argv[1] = "--no-autostart";
argv[2] = "GETINFO scd_running";
argv[3] = "/if ${! $?}";
argv[4] = "scd killscd";
argv[5] = "/end";
argv[6] = NULL;
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[4], gpg_strerror (err));
gnupg_release_process (pid);
}
static void
dirmngr_runtime_change (int killflag)
{
gpg_error_t err;
const char *pgmname;
const char *argv[4];
pid_t pid;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
argv[0] = "--no-autostart";
argv[1] = "--dirmngr";
argv[2] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR";
argv[3] = NULL;
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[2], gpg_strerror (err));
gnupg_release_process (pid);
}
/* Launch the gpg-agent or the dirmngr if not already running. */
gpg_error_t
gc_component_launch (int component)
{
gpg_error_t err;
const char *pgmname;
const char *argv[3];
int i;
pid_t pid;
if (!(component == GC_COMPONENT_GPG_AGENT
|| component == GC_COMPONENT_DIRMNGR))
{
es_fputs (_("Component not suitable for launching"), es_stderr);
es_putc ('\n', es_stderr);
exit (1);
}
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
i = 0;
if (component == GC_COMPONENT_DIRMNGR)
argv[i++] = "--dirmngr";
argv[i++] = "NOP";
argv[i] = NULL;
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s%s%s': %s",
pgmname,
component == GC_COMPONENT_DIRMNGR? " --dirmngr":"",
" NOP",
gpg_strerror (err));
gnupg_release_process (pid);
return err;
}
/* Unconditionally restart COMPONENT. */
void
gc_component_kill (int component)
{
int runtime[GC_BACKEND_NR];
gc_option_t *option;
gc_backend_t backend;
/* Set a flag for the backends to be reloaded. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
runtime[backend] = 0;
if (component >= 0)
{
assert (component < GC_COMPONENT_NR);
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
/* Do the restart for the selected backends. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (1);
}
}
/* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */
void
gc_component_reload (int component)
{
int runtime[GC_BACKEND_NR];
gc_option_t *option;
gc_backend_t backend;
/* Set a flag for the backends to be reloaded. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
runtime[backend] = 0;
if (component == -1)
{
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
}
else
{
assert (component < GC_COMPONENT_NR);
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
/* Do the reload for all selected backends. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (0);
}
}
/* More or less Robust version of dgettext. It has the side effect of
switching the codeset to utf-8 because this is what we want to
output. In theory it is posible to keep the orginal code set and
switch back for regular disgnostic output (redefine "_(" for that)
but given the natur of this tool, being something invoked from
other pograms, it does not make much sense. */
static const char *
my_dgettext (const char *domain, const char *msgid)
{
#ifdef USE_SIMPLE_GETTEXT
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
gettext_use_utf8 (1);
}
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
/* FIXME: we have no dgettext, thus we can't switch. */
text = (char*)gettext (msgid);
return text ? text : msgid;
}
else
return msgid;
#elif defined(ENABLE_NLS)
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
bind_textdomain_codeset (PACKAGE_GT, "utf-8");
bindtextdomain (DIRMNGR_NAME, LOCALEDIR);
bind_textdomain_codeset (DIRMNGR_NAME, "utf-8");
}
/* Note: This is a hack to actually use the gnupg2 domain as
long we are in a transition phase where gnupg 1.x and 1.9 may
coexist. */
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
text = dgettext (domain, msgid);
return text ? text : msgid;
}
else
return msgid;
#else
(void)domain;
return msgid;
#endif
}
/* Percent-Escape special characters. The string is valid until the
next invocation of the function. */
char *
gc_percent_escape (const char *src)
{
static char *esc_str;
static int esc_str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (esc_str_len < new_len)
{
char *new_esc_str = realloc (esc_str, new_len);
if (!new_esc_str)
gc_error (1, errno, "can not escape string");
esc_str = new_esc_str;
esc_str_len = new_len;
}
dst = esc_str;
while (*src)
{
if (*src == '%')
{
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = '5';
}
else if (*src == ':')
{
/* The colon is used as field separator. */
*(dst++) = '%';
*(dst++) = '3';
*(dst++) = 'a';
}
else if (*src == ',')
{
/* The comma is used as list separator. */
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = 'c';
}
else
*(dst++) = *(src);
src++;
}
*dst = '\0';
return esc_str;
}
/* Percent-Deescape special characters. The string is valid until the
next invocation of the function. */
static char *
percent_deescape (const char *src)
{
static char *str;
static int str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (str_len < new_len)
{
char *new_str = realloc (str, new_len);
if (!new_str)
gc_error (1, errno, "can not deescape string");
str = new_str;
str_len = new_len;
}
dst = str;
while (*src)
{
if (*src == '%')
{
int val = hextobyte (src + 1);
if (val < 0)
gc_error (1, 0, "malformed end of string %s", src);
*(dst++) = (char) val;
src += 3;
}
else
*(dst++) = *(src++);
}
*dst = '\0';
return str;
}
/* List all components that are available. */
void
gc_component_list_components (estream_t out)
{
gc_component_t component;
gc_option_t *option;
gc_backend_t backend;
int backend_seen[GC_BACKEND_NR];
const char *desc;
const char *pgmname;
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
if (option)
{
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
pgmname = "";
for (; option && option->name; option++)
{
if ((option->flags & GC_OPT_FLAG_GROUP))
continue;
backend = option->backend;
if (backend_seen[backend])
continue;
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (gc_backend[backend].program
&& !gc_backend[backend].module_name)
continue;
pgmname = gnupg_module_name (gc_backend[backend].module_name);
break;
}
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].name, gc_percent_escape (desc));
es_fprintf (out, "%s\n", gc_percent_escape (pgmname));
}
}
}
static int
all_digits_p (const char *p, size_t len)
{
if (!len)
return 0; /* No. */
for (; len; len--, p++)
if (!isascii (*p) || !isdigit (*p))
return 0; /* No. */
return 1; /* Yes. */
}
/* Collect all error lines from stream FP. Only lines prefixed with
TAG are considered. Returns a list of error line items (which may
be empty). There is no error return. */
static error_line_t
collect_error_output (estream_t fp, const char *tag)
{
char buffer[1024];
char *p, *p2, *p3;
int c, cont_line;
unsigned int pos;
error_line_t eitem, errlines, *errlines_tail;
size_t taglen = strlen (tag);
errlines = NULL;
errlines_tail = &errlines;
pos = 0;
cont_line = 0;
while ((c=es_getc (fp)) != EOF)
{
buffer[pos++] = c;
if (pos >= sizeof buffer - 5 || c == '\n')
{
buffer[pos - (c == '\n')] = 0;
if (cont_line)
; /*Ignore continuations of previous line. */
else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':')
{
/* "gpgsm: foo:4: bla" */
/* Yep, we are interested in this line. */
p = buffer + taglen + 1;
while (*p == ' ' || *p == '\t')
p++;
trim_trailing_spaces (p); /* Get rid of extra CRs. */
if (!*p)
; /* Empty lines are ignored. */
else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':'))
&& all_digits_p (p2+1, p3 - (p2+1)))
{
/* Line in standard compiler format. */
p3++;
while (*p3 == ' ' || *p3 == '\t')
p3++;
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = eitem->buffer;
eitem->buffer[p2-p] = 0;
eitem->errtext = eitem->buffer + (p3 - p);
/* (we already checked that there are only ascii
digits followed by a colon) */
eitem->lineno = 0;
for (p2++; isdigit (*p2); p2++)
eitem->lineno = eitem->lineno*10 + (*p2 - '0');
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
else
{
/* Other error output. */
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = NULL;
eitem->errtext = eitem->buffer;
eitem->lineno = 0;
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
}
pos = 0;
/* If this was not a complete line mark that we are in a
continuation. */
cont_line = (c != '\n');
}
}
/* We ignore error lines not terminated by a LF. */
return errlines;
}
/* Check the options of a single component. Returns 0 if everything
is OK. */
int
gc_component_check_options (int component, estream_t out, const char *conf_file)
{
gpg_error_t err;
unsigned int result;
int backend_seen[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
const char *pgmname;
const char *argv[4];
int i;
pid_t pid;
int exitcode;
estream_t errfp;
error_line_t errlines;
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
option = gc_component[component].options;
for (; option && option->name; option++)
{
if ((option->flags & GC_OPT_FLAG_GROUP))
continue;
backend = option->backend;
if (backend_seen[backend])
continue;
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (!gc_backend[backend].program)
continue;
if (!gc_backend[backend].module_name)
continue;
break;
}
if (! option || ! option->name)
return 0;
pgmname = gnupg_module_name (gc_backend[backend].module_name);
i = 0;
if (conf_file)
{
argv[i++] = "--options";
argv[i++] = conf_file;
}
if (component == GC_COMPONENT_PINENTRY)
argv[i++] = "--version";
else
argv[i++] = "--gpgconf-test";
argv[i++] = NULL;
result = 0;
errlines = NULL;
err = gnupg_spawn_process (pgmname, argv, GPG_ERR_SOURCE_DEFAULT, NULL, 0,
NULL, NULL, &errfp, &pid);
if (err)
result |= 1; /* Program could not be run. */
else
{
errlines = collect_error_output (errfp,
gc_component[component].name);
if (gnupg_wait_process (pgmname, pid, 1, &exitcode))
{
if (exitcode == -1)
result |= 1; /* Program could not be run or it
terminated abnormally. */
result |= 2; /* Program returned an error. */
}
gnupg_release_process (pid);
es_fclose (errfp);
}
/* If the program could not be run, we can't tell whether
the config file is good. */
if (result & 1)
result |= 2;
if (out)
{
const char *desc;
error_line_t errptr;
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].name, gc_percent_escape (desc));
es_fputs (gc_percent_escape (pgmname), out);
es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2));
for (errptr = errlines; errptr; errptr = errptr->next)
{
if (errptr != errlines)
es_fputs ("\n:::::", out); /* Continuation line. */
if (errptr->fname)
es_fputs (gc_percent_escape (errptr->fname), out);
es_putc (':', out);
if (errptr->fname)
es_fprintf (out, "%u", errptr->lineno);
es_putc (':', out);
es_fputs (gc_percent_escape (errptr->errtext), out);
es_putc (':', out);
}
es_putc ('\n', out);
}
while (errlines)
{
error_line_t tmp = errlines->next;
xfree (errlines);
errlines = tmp;
}
return result;
}
/* Check all components that are available. */
void
gc_check_programs (estream_t out)
{
gc_component_t component;
for (component = 0; component < GC_COMPONENT_NR; component++)
gc_component_check_options (component, out, NULL);
}
/* Find the component with the name NAME. Returns -1 if not
found. */
int
gc_component_find (const char *name)
{
gc_component_t idx;
for (idx = 0; idx < GC_COMPONENT_NR; idx++)
{
if (gc_component[idx].options
&& !strcmp (name, gc_component[idx].name))
return idx;
}
return -1;
}
/* List the option OPTION. */
static void
list_one_option (const gc_option_t *option, estream_t out)
{
const char *desc = NULL;
char *arg_name = NULL;
if (option->desc)
{
desc = my_dgettext (option->desc_domain, option->desc);
if (*desc == '|')
{
const char *arg_tail = strchr (&desc[1], '|');
if (arg_tail)
{
int arg_len = arg_tail - &desc[1];
arg_name = xmalloc (arg_len + 1);
memcpy (arg_name, &desc[1], arg_len);
arg_name[arg_len] = '\0';
desc = arg_tail + 1;
}
}
}
/* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS
PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY
FIELDS. */
/* The name field. */
es_fprintf (out, "%s", option->name);
/* The flags field. */
es_fprintf (out, ":%lu", option->flags);
if (opt.verbose)
{
es_putc (' ', out);
if (!option->flags)
es_fprintf (out, "none");
else
{
unsigned long flags = option->flags;
unsigned long flag = 0;
unsigned long first = 1;
while (flags)
{
if (flags & 1)
{
if (first)
first = 0;
else
es_putc (',', out);
es_fprintf (out, "%s", gc_flag[flag].name);
}
flags >>= 1;
flag++;
}
}
}
/* The level field. */
es_fprintf (out, ":%u", option->level);
if (opt.verbose)
es_fprintf (out, " %s", gc_level[option->level].name);
/* The description field. */
es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : "");
/* The type field. */
es_fprintf (out, ":%u", option->arg_type);
if (opt.verbose)
es_fprintf (out, " %s", gc_arg_type[option->arg_type].name);
/* The alternate type field. */
es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback);
if (opt.verbose)
es_fprintf (out, " %s",
gc_arg_type[gc_arg_type[option->arg_type].fallback].name);
/* The argument name field. */
es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : "");
xfree (arg_name);
/* The default value field. */
es_fprintf (out, ":%s", option->default_value ? option->default_value : "");
/* The default argument field. */
es_fprintf (out, ":%s", option->default_arg ? option->default_arg : "");
/* The value field. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& (option->flags & GC_OPT_FLAG_LIST)
&& option->value)
/* The special format "1,1,1,1,...,1" is converted to a number
here. */
es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2));
else
es_fprintf (out, ":%s", option->value ? option->value : "");
/* ADD NEW FIELDS HERE. */
es_putc ('\n', out);
}
/* List all options of the component COMPONENT. */
void
gc_component_list_options (int component, estream_t out)
{
const gc_option_t *option = gc_component[component].options;
while (option && option->name)
{
/* Do not output unknown or internal options. */
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& (!option->active || option->level == GC_LEVEL_INTERNAL))
{
option++;
continue;
}
if (option->flags & GC_OPT_FLAG_GROUP)
{
const gc_option_t *group_option = option + 1;
gc_expert_level_t level = GC_LEVEL_NR;
/* The manual states that the group level is always the
minimum of the levels of all contained options. Due to
different active options, and because it is hard to
maintain manually, we calculate it here. The value in
the global static table is ignored. */
while (group_option->name)
{
if (group_option->flags & GC_OPT_FLAG_GROUP)
break;
if (group_option->level < level)
level = group_option->level;
group_option++;
}
/* Check if group is empty. */
if (level != GC_LEVEL_NR)
{
gc_option_t opt_copy;
/* Fix up the group level. */
memcpy (&opt_copy, option, sizeof (opt_copy));
opt_copy.level = level;
list_one_option (&opt_copy, out);
}
}
else
list_one_option (option, out);
option++;
}
}
/* Find the option NAME in component COMPONENT, for the backend
BACKEND. If BACKEND is GC_BACKEND_ANY, any backend will match. */
static gc_option_t *
find_option (gc_component_t component, const char *name,
gc_backend_t backend)
{
gc_option_t *option = gc_component[component].options;
while (option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& !strcmp (option->name, name)
&& (backend == GC_BACKEND_ANY || option->backend == backend))
break;
option++;
}
return option->name ? option : NULL;
}
/* Determine the configuration filename for the component COMPONENT
and backend BACKEND. */
static char *
get_config_filename (gc_component_t component, gc_backend_t backend)
{
char *filename = NULL;
gc_option_t *option = find_option
(component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY);
assert (option);
assert (option->arg_type == GC_ARG_TYPE_FILENAME);
assert (!(option->flags & GC_OPT_FLAG_LIST));
if (!option->active || !option->default_value)
gc_error (1, 0, "Option %s, needed by backend %s, was not initialized",
gc_backend[backend].option_config_filename,
gc_backend[backend].name);
if (option->value && *option->value)
filename = percent_deescape (&option->value[1]);
else if (option->default_value && *option->default_value)
filename = percent_deescape (&option->default_value[1]);
else
filename = "";
#if HAVE_W32CE_SYSTEM
if (!(filename[0] == '/' || filename[0] == '\\'))
#elif defined(HAVE_DOSISH_SYSTEM)
if (!(filename[0]
&& filename[1] == ':'
&& (filename[2] == '/' || filename[2] == '\\')))
#else
if (filename[0] != '/')
#endif
gc_error (1, 0, "Option %s, needed by backend %s, is not absolute",
gc_backend[backend].option_config_filename,
gc_backend[backend].name);
return filename;
}
/* Retrieve the options for the component COMPONENT from backend
BACKEND, which we already know is a program-type backend. */
static void
retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
{
gpg_error_t err;
const char *pgmname;
const char *argv[2];
estream_t outfp;
int exitcode;
pid_t pid;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
estream_t config;
char *config_filename;
pgmname = (gc_backend[backend].module_name
? gnupg_module_name (gc_backend[backend].module_name)
: gc_backend[backend].program );
argv[0] = "--gpgconf-list";
argv[1] = NULL;
err = gnupg_spawn_process (pgmname, argv, GPG_ERR_SOURCE_DEFAULT, NULL, 0,
NULL, &outfp, NULL, &pid);
if (err)
{
gc_error (1, 0, "could not gather active options from '%s': %s",
pgmname, gpg_strerror (err));
}
while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0)
{
gc_option_t *option;
char *linep;
unsigned long flags = 0;
char *default_value = NULL;
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s from %s",
line, pgmname);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s from %s",
line, pgmname);
linep = end;
}
/* Extract default value, if present. Default to empty if
not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
if (flags & GC_OPT_FLAG_DEFAULT)
default_value = linep;
linep = end;
}
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line, backend);
if (option)
{
if (option->active)
gc_error (1, errno, "option %s returned twice from %s",
line, pgmname);
option->active = 1;
option->flags |= flags;
if (default_value && *default_value)
option->default_value = xstrdup (default_value);
}
}
if (length < 0 || es_ferror (outfp))
gc_error (1, errno, "error reading from %s", pgmname);
if (es_fclose (outfp))
gc_error (1, errno, "error closing %s", pgmname);
err = gnupg_wait_process (pgmname, pid, 1, &exitcode);
if (err)
gc_error (1, 0, "running %s failed (exitcode=%d): %s",
pgmname, exitcode, gpg_strerror (err));
gnupg_release_process (pid);
/* At this point, we can parse the configuration file. */
config_filename = get_config_filename (component, backend);
config = es_fopen (config_filename, "r");
if (!config)
gc_error (0, errno, "warning: can not open config file %s",
config_filename);
else
{
while ((length = es_read_line (config, &line, &line_len, NULL)) > 0)
{
char *name;
char *value;
gc_option_t *option;
name = line;
while (*name == ' ' || *name == '\t')
name++;
if (!*name || *name == '#' || *name == '\r' || *name == '\n')
continue;
value = name;
while (*value && *value != ' ' && *value != '\t'
&& *value != '#' && *value != '\r' && *value != '\n')
value++;
if (*value == ' ' || *value == '\t')
{
char *end;
*(value++) = '\0';
while (*value == ' ' || *value == '\t')
value++;
end = value;
while (*end && *end != '#' && *end != '\r' && *end != '\n')
end++;
while (end > value && (end[-1] == ' ' || end[-1] == '\t'))
end--;
*end = '\0';
}
else
*value = '\0';
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line, backend);
if (option)
{
char *opt_value;
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
if (*value)
gc_error (0, 0,
"warning: ignoring argument %s for option %s",
value, name);
opt_value = xstrdup ("1");
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
opt_value = xasprintf ("\"%s", gc_percent_escape (value));
else
{
/* FIXME: Verify that the number is sane. */
opt_value = xstrdup (value);
}
/* Now enter the option into the table. */
if (!(option->flags & GC_OPT_FLAG_LIST))
{
if (option->value)
free (option->value);
option->value = opt_value;
}
else
{
if (!option->value)
option->value = opt_value;
else
{
char *opt_val = opt_value;
option->value = xasprintf ("%s,%s", option->value,
opt_val);
xfree (opt_value);
}
}
}
}
if (length < 0 || es_ferror (config))
gc_error (1, errno, "error reading from %s", config_filename);
if (es_fclose (config))
gc_error (1, errno, "error closing %s", config_filename);
}
xfree (line);
}
/* Retrieve the options for the component COMPONENT from backend
BACKEND, which we already know is of type file list. */
static void
retrieve_options_from_file (gc_component_t component, gc_backend_t backend)
{
gc_option_t *list_option;
gc_option_t *config_option;
char *list_filename;
FILE *list_file;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
char *list = NULL;
list_option = find_option (component,
gc_backend[backend].option_name, GC_BACKEND_ANY);
assert (list_option);
assert (!list_option->active);
list_filename = get_config_filename (component, backend);
list_file = fopen (list_filename, "r");
if (!list_file)
gc_error (0, errno, "warning: can not open list file %s", list_filename);
else
{
while ((length = read_line (list_file, &line, &line_len, NULL)) > 0)
{
char *start;
char *end;
char *new_list;
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (!*start || *start == '#' || *start == '\r' || *start == '\n')
continue;
end = start;
while (*end && *end != '#' && *end != '\r' && *end != '\n')
end++;
/* Walk back to skip trailing white spaces. Looks evil, but
works because of the conditions on START and END imposed
at this point (END is at least START + 1, and START is
not a whitespace character). */
while (*(end - 1) == ' ' || *(end - 1) == '\t')
end--;
*end = '\0';
/* FIXME: Oh, no! This is so lame! Should use realloc and
really append. */
if (list)
{
new_list = xasprintf ("%s,\"%s", list, gc_percent_escape (start));
xfree (list);
list = new_list;
}
else
list = xasprintf ("\"%s", gc_percent_escape (start));
}
if (length < 0 || ferror (list_file))
gc_error (1, errno, "can not read list file %s", list_filename);
}
list_option->active = 1;
list_option->value = list;
/* Fix up the read-only flag. */
config_option = find_option
(component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY);
if (config_option->flags & GC_OPT_FLAG_NO_CHANGE)
list_option->flags |= GC_OPT_FLAG_NO_CHANGE;
if (list_file && fclose (list_file))
gc_error (1, errno, "error closing %s", list_filename);
xfree (line);
}
/* Retrieve the currently active options and their defaults from all
involved backends for this component. Using -1 for component will
retrieve all options from all components. */
void
gc_component_retrieve_options (int component)
{
int process_all = 0;
int backend_seen[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
if (component == GC_COMPONENT_PINENTRY)
return; /* Dummy module for now. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
if (component == -1)
{
process_all = 1;
component = 0;
assert (component < GC_COMPONENT_NR);
}
do
{
option = gc_component[component].options;
while (option && option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP))
{
backend = option->backend;
if (backend_seen[backend])
{
option++;
continue;
}
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (gc_backend[backend].program)
retrieve_options_from_program (component, backend);
else
retrieve_options_from_file (component, backend);
}
option++;
}
}
while (process_all && ++component < GC_COMPONENT_NR);
}
/* Perform a simple validity check based on the type. Return in
NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of
type GC_ARG_TYPE_NONE. */
static void
option_check_validity (gc_option_t *option, unsigned long flags,
char *new_value, unsigned long *new_value_nr)
{
char *arg;
if (!option->active)
gc_error (1, 0, "option %s not supported by backend %s",
option->name, gc_backend[option->backend].name);
if (option->new_flags || option->new_value)
gc_error (1, 0, "option %s already changed", option->name);
if (flags & GC_OPT_FLAG_DEFAULT)
{
if (*new_value)
gc_error (1, 0, "argument %s provided for deleted option %s",
new_value, option->name);
return;
}
/* GC_ARG_TYPE_NONE options have special list treatment. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
char *tail;
gpg_err_set_errno (0);
*new_value_nr = strtoul (new_value, &tail, 0);
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*tail)
gc_error (1, 0, "garbage after argument for option %s",
option->name);
if (!(option->flags & GC_OPT_FLAG_LIST))
{
if (*new_value_nr != 1)
gc_error (1, 0, "argument for non-list option %s of type 0 "
"(none) must be 1", option->name);
}
else
{
if (*new_value_nr == 0)
gc_error (1, 0, "argument for option %s of type 0 (none) "
"must be positive", option->name);
}
return;
}
arg = new_value;
do
{
if (*arg == '\0' || *arg == ',')
{
if (!(option->flags & GC_OPT_FLAG_ARG_OPT))
gc_error (1, 0, "argument required for option %s", option->name);
if (*arg == ',' && !(option->flags & GC_OPT_FLAG_LIST))
gc_error (1, 0, "list found for non-list option %s", option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING)
{
if (*arg != '"')
gc_error (1, 0, "string argument for option %s must begin "
"with a quote (\") character", option->name);
/* FIXME: We do not allow empty string arguments for now, as
we do not quote arguments in configuration files, and
thus no argument is indistinguishable from the empty
string. */
if (arg[1] == '\0' || arg[1] == ',')
gc_error (1, 0, "empty string argument for option %s is "
"currently not allowed. Please report this!",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32)
{
long res;
gpg_err_set_errno (0);
res = strtol (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && *arg != ',')
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32)
{
unsigned long res;
gpg_err_set_errno (0);
res = strtoul (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && *arg != ',')
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
arg = strchr (arg, ',');
if (arg)
arg++;
}
while (arg && *arg);
}
#ifdef HAVE_W32_SYSTEM
int
copy_file (const char *src_name, const char *dst_name)
{
#define BUF_LEN 4096
char buffer[BUF_LEN];
int len;
FILE *src;
FILE *dst;
src = fopen (src_name, "r");
if (src == NULL)
return -1;
dst = fopen (dst_name, "w");
if (dst == NULL)
{
int saved_err = errno;
fclose (src);
gpg_err_set_errno (saved_err);
return -1;
}
do
{
int written;
len = fread (buffer, 1, BUF_LEN, src);
if (len == 0)
break;
written = fwrite (buffer, 1, len, dst);
if (written != len)
break;
}
while (!feof (src) && !ferror (src) && !ferror (dst));
if (ferror (src) || ferror (dst) || !feof (src))
{
int saved_errno = errno;
fclose (src);
fclose (dst);
unlink (dst_name);
gpg_err_set_errno (saved_errno);
return -1;
}
if (fclose (dst))
gc_error (1, errno, "error closing %s", dst_name);
if (fclose (src))
gc_error (1, errno, "error closing %s", src_name);
return 0;
}
#endif /* HAVE_W32_SYSTEM */
/* Create and verify the new configuration file for the specified
backend and component. Returns 0 on success and -1 on error. */
static int
change_options_file (gc_component_t component, gc_backend_t backend,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
FILE *src_file = NULL;
FILE *dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
char *arg;
char *cur_arg = NULL;
option = find_option (component,
gc_backend[backend].option_name, GC_BACKEND_ANY);
assert (option);
assert (option->active);
assert (gc_arg_type[option->arg_type].fallback != GC_ARG_TYPE_NONE);
/* FIXME. Throughout the function, do better error reporting. */
/* Note that get_config_filename() calls percent_deescape(), so we
call this before processing the arguments. */
dest_filename = xstrdup (get_config_filename (component, backend));
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
arg = option->new_value;
if (arg && arg[0] == '\0')
arg = NULL;
else if (arg)
{
char *end;
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
cur_arg = percent_deescape (arg);
if (end)
{
*end = ',';
arg = end + 1;
}
else
arg = NULL;
}
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
{
xfree (dest_filename);
return -1;
}
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL. */
fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = fopen (dest_filename, "r");
if (!dest_file)
goto change_file_one_err;
while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char *endp;
char saved_end;
endp = start;
end = endp;
/* Search for the end of the line. */
while (*endp && *endp != '#' && *endp != '\r' && *endp != '\n')
{
endp++;
if (*endp && *endp != ' ' && *endp != '\t'
&& *endp != '\r' && *endp != '\n' && *endp != '#')
end = endp + 1;
}
saved_end = *end;
*end = '\0';
if ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| !cur_arg || strcmp (start, cur_arg))
disable = 1;
else
{
/* Find next argument. */
if (arg)
{
char *arg_end;
arg++;
arg_end = strchr (arg, ',');
if (arg_end)
*arg_end = '\0';
cur_arg = percent_deescape (arg);
if (arg_end)
{
*arg_end = ',';
arg = arg_end + 1;
}
else
arg = NULL;
}
else
cur_arg = NULL;
}
*end = saved_end;
}
if (disable)
{
if (!in_marker)
{
fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (ferror (src_file))
goto change_file_one_err;
fprintf (src_file, "# %s", line);
if (ferror (src_file))
goto change_file_one_err;
}
}
else
{
fprintf (src_file, "%s", line);
if (ferror (src_file))
goto change_file_one_err;
}
}
if (length < 0 || ferror (dest_file))
goto change_file_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
fprintf (src_file, "\n%s\n", marker);
if (ferror (src_file))
goto change_file_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the arguments we are going to add.
Now, dump the new arguments and write the end marker, possibly
followed by the rest of the original file. */
while (cur_arg)
{
fprintf (src_file, "%s\n", cur_arg);
/* Find next argument. */
if (arg)
{
char *end;
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
cur_arg = percent_deescape (arg);
if (end)
{
*end = ',';
arg = end + 1;
}
else
arg = NULL;
}
else
cur_arg = NULL;
}
fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (ferror (src_file))
goto change_file_one_err;
if (!in_marker)
{
fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (ferror (src_file))
goto change_file_one_err;
fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (ferror (src_file))
goto change_file_one_err;
fprintf (src_file, "# never change anything below these lines.\n");
if (ferror (src_file))
goto change_file_one_err;
}
if (dest_file)
{
while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0)
{
fprintf (src_file, "%s", line);
if (ferror (src_file))
goto change_file_one_err;
}
if (length < 0 || ferror (dest_file))
goto change_file_one_err;
}
xfree (line);
line = NULL;
res = fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = fclose (dest_file);
if (res)
return -1;
}
return 0;
change_file_one_err:
xfree (line);
res = errno;
if (src_file)
{
fclose (src_file);
close (fd);
}
if (dest_file)
fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Create and verify the new configuration file for the specified
backend and component. Returns 0 on success and -1 on error. */
static int
change_options_program (gc_component_t component, gc_backend_t backend,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
FILE *src_file = NULL;
FILE *dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
/* Special hack for gpg, see below. */
int utf8strings_seen = 0;
/* FIXME. Throughout the function, do better error reporting. */
dest_filename = xstrdup (get_config_filename (component, backend));
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
return -1;
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL. */
fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = fopen (dest_filename, "r");
if (!dest_file)
goto change_one_err;
while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
else if (backend == GC_BACKEND_GPG && in_marker
&& ! strcmp ("utf8-strings\n", line))
{
/* Strip duplicated entries. */
if (utf8strings_seen)
disable = 1;
else
utf8strings_seen = 1;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char saved_end;
end = start;
while (*end && *end != ' ' && *end != '\t'
&& *end != '\r' && *end != '\n' && *end != '#')
end++;
saved_end = *end;
*end = '\0';
option = find_option (component, start, backend);
*end = saved_end;
if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| option->new_value))
disable = 1;
}
if (disable)
{
if (!in_marker)
{
fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (ferror (src_file))
goto change_one_err;
fprintf (src_file, "# %s", line);
if (ferror (src_file))
goto change_one_err;
}
}
else
{
fprintf (src_file, "%s", line);
if (ferror (src_file))
goto change_one_err;
}
}
if (length < 0 || ferror (dest_file))
goto change_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
fprintf (src_file, "\n%s\n", marker);
if (ferror (src_file))
goto change_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the options we are going to change.
Now, dump the changed options (except for those we are going to
revert to their default), and write the end marker, possibly
followed by the rest of the original file. */
/* We have to turn on UTF8 strings for GnuPG. */
if (backend == GC_BACKEND_GPG && ! utf8strings_seen)
fprintf (src_file, "utf8-strings\n");
option = gc_component[component].options;
while (option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& option->backend == backend
&& option->new_value)
{
char *arg = option->new_value;
do
{
if (*arg == '\0' || *arg == ',')
{
fprintf (src_file, "%s\n", option->name);
if (ferror (src_file))
goto change_one_err;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_NONE)
{
assert (*arg == '1');
fprintf (src_file, "%s\n", option->name);
if (ferror (src_file))
goto change_one_err;
arg++;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
{
char *end;
assert (*arg == '"');
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
fprintf (src_file, "%s %s\n", option->name,
percent_deescape (arg));
if (ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
else
{
char *end;
end = strchr (arg, ',');
if (end)
*end = '\0';
fprintf (src_file, "%s %s\n", option->name, arg);
if (ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
assert (arg == NULL || *arg == '\0' || *arg == ',');
if (arg && *arg == ',')
arg++;
}
while (arg && *arg);
}
option++;
}
fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (ferror (src_file))
goto change_one_err;
if (!in_marker)
{
fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (ferror (src_file))
goto change_one_err;
fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (ferror (src_file))
goto change_one_err;
fprintf (src_file, "# never change anything below these lines.\n");
if (ferror (src_file))
goto change_one_err;
}
if (dest_file)
{
while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0)
{
fprintf (src_file, "%s", line);
if (ferror (src_file))
goto change_one_err;
}
if (length < 0 || ferror (dest_file))
goto change_one_err;
}
xfree (line);
line = NULL;
res = fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = fclose (dest_file);
if (res)
return -1;
}
return 0;
change_one_err:
xfree (line);
res = errno;
if (src_file)
{
fclose (src_file);
close (fd);
}
if (dest_file)
fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Common code for gc_component_change_options and
gc_process_gpgconf_conf. */
static void
change_one_value (gc_option_t *option, int *runtime,
unsigned long flags, char *new_value)
{
unsigned long new_value_nr = 0;
option_check_validity (option, flags, new_value, &new_value_nr);
if (option->flags & GC_OPT_FLAG_RUNTIME)
runtime[option->backend] = 1;
option->new_flags = flags;
if (!(flags & GC_OPT_FLAG_DEFAULT))
{
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& (option->flags & GC_OPT_FLAG_LIST))
{
char *str;
/* We convert the number to a list of 1's for convenient
list handling. */
assert (new_value_nr > 0);
option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1);
str = option->new_value;
*(str++) = '1';
while (--new_value_nr > 0)
{
*(str++) = ',';
*(str++) = '1';
}
*(str++) = '\0';
}
else
option->new_value = xstrdup (new_value);
}
}
/* Read the modifications from IN and apply them. If IN is NULL the
modifications are expected to already have been set to the global
table. */
void
gc_component_change_options (int component, estream_t in, estream_t out)
{
int err = 0;
int runtime[GC_BACKEND_NR];
char *src_filename[GC_BACKEND_NR];
char *dest_filename[GC_BACKEND_NR];
char *orig_filename[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
if (component == GC_COMPONENT_PINENTRY)
return; /* Dummy component for now. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
runtime[backend] = 0;
src_filename[backend] = NULL;
dest_filename[backend] = NULL;
orig_filename[backend] = NULL;
}
if (in)
{
/* Read options from the file IN. */
while ((length = es_read_line (in, &line, &line_len, NULL)) > 0)
{
char *linep;
unsigned long flags = 0;
char *new_value = "";
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s", line);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s", line);
linep = end;
}
/* Don't allow setting of the no change flag. */
flags &= ~GC_OPT_FLAG_NO_CHANGE;
/* Extract default value, if present. Default to empty if not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
new_value = linep;
linep = end;
}
option = find_option (component, line, GC_BACKEND_ANY);
if (!option)
gc_error (1, 0, "unknown option %s", line);
if ((option->flags & GC_OPT_FLAG_NO_CHANGE))
{
gc_error (0, 0, "ignoring new value for option %s",
option->name);
continue;
}
change_one_value (option, runtime, flags, new_value);
}
}
/* Now that we have collected and locally verified the changes,
write them out to new configuration files, verify them
externally, and then commit them. */
option = gc_component[component].options;
while (option && option->name)
{
/* Go on if we have already seen this backend, or if there is
nothing to do. */
if (src_filename[option->backend]
|| !(option->new_flags || option->new_value))
{
option++;
continue;
}
if (gc_backend[option->backend].program)
{
err = change_options_program (component, option->backend,
&src_filename[option->backend],
&dest_filename[option->backend],
&orig_filename[option->backend]);
if (! err)
{
/* External verification. */
err = gc_component_check_options (component, out,
src_filename[option->backend]);
if (err)
{
gc_error (0, 0,
_("External verification of component %s failed"),
gc_component[component].name);
gpg_err_set_errno (EINVAL);
}
}
}
else
err = change_options_file (component, option->backend,
&src_filename[option->backend],
&dest_filename[option->backend],
&orig_filename[option->backend]);
if (err)
break;
option++;
}
if (! err && ! opt.dry_run)
{
int i;
for (i = 0; i < GC_BACKEND_NR; i++)
{
if (src_filename[i])
{
/* FIXME: Make a verification here. */
assert (dest_filename[i]);
if (orig_filename[i])
{
#ifdef HAVE_W32_SYSTEM
/* There is no atomic update on W32. */
err = unlink (dest_filename[i]);
#endif /* HAVE_W32_SYSTEM */
if (!err)
err = rename (src_filename[i], dest_filename[i]);
}
else
{
#ifdef HAVE_W32_SYSTEM
/* We skip the unlink if we expect the file not to
be there. */
err = rename (src_filename[i], dest_filename[i]);
#else /* HAVE_W32_SYSTEM */
/* This is a bit safer than rename() because we
expect DEST_FILENAME not to be there. If it
happens to be there, this will fail. */
err = link (src_filename[i], dest_filename[i]);
if (!err)
err = unlink (src_filename[i]);
#endif /* !HAVE_W32_SYSTEM */
}
if (err)
break;
src_filename[i] = NULL;
}
}
}
if (err || opt.dry_run)
{
int i;
int saved_errno = errno;
/* An error occured or a dry-run is requested. */
for (i = 0; i < GC_BACKEND_NR; i++)
{
if (src_filename[i])
{
/* The change was not yet committed. */
unlink (src_filename[i]);
if (orig_filename[i])
unlink (orig_filename[i]);
}
else
{
/* The changes were already committed. FIXME: This is a
tad dangerous, as we don't know if we don't overwrite
a version of the file that is even newer than the one
we just installed. */
if (orig_filename[i])
{
#ifdef HAVE_W32_SYSTEM
/* There is no atomic update on W32. */
unlink (dest_filename[i]);
#endif /* HAVE_W32_SYSTEM */
rename (orig_filename[i], dest_filename[i]);
}
else
unlink (dest_filename[i]);
}
}
if (err)
gc_error (1, saved_errno, "could not commit changes");
/* Fall-through for dry run. */
goto leave;
}
/* If it all worked, notify the daemons of the changes. */
if (opt.runtime)
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (0);
}
/* Move the per-process backup file into its place. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
if (orig_filename[backend])
{
char *backup_filename;
assert (dest_filename[backend]);
backup_filename = xasprintf ("%s.%s.bak",
dest_filename[backend], GPGCONF_NAME);
#ifdef HAVE_W32_SYSTEM
/* There is no atomic update on W32. */
unlink (backup_filename);
#endif /* HAVE_W32_SYSTEM */
rename (orig_filename[backend], backup_filename);
}
leave:
xfree (line);
}
/* Check whether USER matches the current user of one of its group.
This function may change USER. Returns true is there is a
match. */
static int
key_matches_user_or_group (char *user)
{
char *group;
if (*user == '*' && user[1] == 0)
return 1; /* A single asterisk matches all users. */
group = strchr (user, ':');
if (group)
*group++ = 0;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we don't support groups. */
if (group && *group)
gc_error (0, 0, _("Note that group specifications are ignored\n"));
#ifndef HAVE_W32CE_SYSTEM
if (*user)
{
static char *my_name;
if (!my_name)
{
char tmp[1];
DWORD size = 1;
GetUserNameA (tmp, &size);
my_name = xmalloc (size);
if (!GetUserNameA (my_name, &size))
gc_error (1,0, "error getting current user name: %s",
w32_strerror (-1));
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
#endif /*HAVE_W32CE_SYSTEM*/
#else /*!HAVE_W32_SYSTEM*/
/* First check whether the user matches. */
if (*user)
{
static char *my_name;
if (!my_name)
{
struct passwd *pw = getpwuid ( getuid () );
if (!pw)
gc_error (1, errno, "getpwuid failed for current user");
my_name = xstrdup (pw->pw_name);
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
/* If that failed, check whether a group matches. */
if (group && *group)
{
static char *my_group;
static char **my_supgroups;
int n;
if (!my_group)
{
struct group *gr = getgrgid ( getgid () );
if (!gr)
gc_error (1, errno, "getgrgid failed for current user");
my_group = xstrdup (gr->gr_name);
}
if (!strcmp (group, my_group))
return 1; /* Found. */
if (!my_supgroups)
{
int ngids;
gid_t *gids;
ngids = getgroups (0, NULL);
gids = xcalloc (ngids+1, sizeof *gids);
ngids = getgroups (ngids, gids);
if (ngids < 0)
gc_error (1, errno, "getgroups failed for current user");
my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups);
for (n=0; n < ngids; n++)
{
struct group *gr = getgrgid ( gids[n] );
if (!gr)
gc_error (1, errno, "getgrgid failed for supplementary group");
my_supgroups[n] = xstrdup (gr->gr_name);
}
xfree (gids);
}
for (n=0; my_supgroups[n]; n++)
if (!strcmp (group, my_supgroups[n]))
return 1; /* Found. */
}
#endif /*!HAVE_W32_SYSTEM*/
return 0; /* No match. */
}
/* Read and process the global configuration file for gpgconf. This
optional file is used to update our internal tables at runtime and
may also be used to set new default values. If FNAME is NULL the
default name will be used. With UPDATE set to true the internal
tables are actually updated; if not set, only a syntax check is
done. If DEFAULTS is true the global options are written to the
configuration files. If LISTFP is set, no changes are done but the
configuration file is printed to LISTFP in a colon separated format.
Returns 0 on success or if the config file is not present; -1 is
returned on error. */
int
gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
estream_t listfp)
{
int result = 0;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
FILE *config;
int lineno = 0;
int in_rule = 0;
int got_match = 0;
int runtime[GC_BACKEND_NR];
int backend_id, component_id;
char *fname;
if (fname_arg)
fname = xstrdup (fname_arg);
else
fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf",
NULL);
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
runtime[backend_id] = 0;
config = fopen (fname, "r");
if (!config)
{
/* Do not print an error if the file is not available, except
when running in syntax check mode. */
if (errno != ENOENT || !update)
{
gc_error (0, errno, "can not open global config file '%s'", fname);
result = -1;
}
xfree (fname);
return result;
}
while ((length = read_line (config, &line, &line_len, NULL)) > 0)
{
char *key, *component, *option, *flags, *value;
char *empty;
gc_option_t *option_info = NULL;
char *p;
int is_continuation;
lineno++;
key = line;
while (*key == ' ' || *key == '\t')
key++;
if (!*key || *key == '#' || *key == '\r' || *key == '\n')
continue;
is_continuation = (key != line);
/* Parse the key field. */
if (!is_continuation && got_match)
break; /* Finish after the first match. */
else if (!is_continuation)
{
in_rule = 0;
for (p=key+1; *p && !strchr (" \t\r\n", *p); p++)
;
if (!*p)
{
gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno);
result = -1;
continue;
}
*p++ = 0;
component = p;
}
else if (!in_rule)
{
gc_error (0, 0, "continuation but no rule at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
else
{
component = key;
key = NULL;
}
in_rule = 1;
/* Parse the component. */
while (*component == ' ' || *component == '\t')
component++;
for (p=component; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == component)
{
gc_error (0, 0, "missing component at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
empty = p;
*p++ = 0;
option = p;
component_id = gc_component_find (component);
if (component_id < 0)
{
gc_error (0, 0, "unknown component at '%s', line %d",
fname, lineno);
result = -1;
}
/* Parse the option name. */
while (*option == ' ' || *option == '\t')
option++;
for (p=option; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == option)
{
gc_error (0, 0, "missing option at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
*p++ = 0;
flags = p;
if ( component_id != -1)
{
option_info = find_option (component_id, option, GC_BACKEND_ANY);
if (!option_info)
{
gc_error (0, 0, "unknown option at '%s', line %d",
fname, lineno);
result = -1;
}
}
/* Parse the optional flags. */
while (*flags == ' ' || *flags == '\t')
flags++;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
gc_error (0, 0, "syntax error in rule at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
*p++ = 0;
value = p;
}
else /* No flags given. */
{
value = flags;
flags = NULL;
}
/* Parse the optional value. */
while (*value == ' ' || *value == '\t')
value++;
for (p=value; *p && !strchr ("\r\n", *p); p++)
;
if (p == value)
value = empty; /* No value given; let it point to an empty string. */
else
{
/* Strip trailing white space. */
*p = 0;
for (p--; p > value && (*p == ' ' || *p == '\t'); p--)
*p = 0;
}
/* Check flag combinations. */
if (!flags)
;
else if (!strcmp (flags, "default"))
{
if (*value)
{
gc_error (0, 0, "flag \"default\" may not be combined "
"with a value at '%s', line %d",
fname, lineno);
result = -1;
}
}
else if (!strcmp (flags, "change"))
;
else if (!strcmp (flags, "no-change"))
;
else
{
gc_error (0, 0, "unknown flag at '%s', line %d",
fname, lineno);
result = -1;
}
/* In list mode we print out all records. */
if (listfp && !result)
{
/* If this is a new ruleset, print a key record. */
if (!is_continuation)
{
char *group = strchr (key, ':');
if (group)
{
*group++ = 0;
if ((p = strchr (group, ':')))
*p = 0; /* We better strip any extra stuff. */
}
es_fprintf (listfp, "k:%s:", gc_percent_escape (key));
es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):"");
}
/* All other lines are rule records. */
es_fprintf (listfp, "r:::%s:%s:%s:",
gc_component[component_id].name,
option_info->name? option_info->name : "",
flags? flags : "");
if (value != empty)
es_fprintf (listfp, "\"%s", gc_percent_escape (value));
es_putc ('\n', listfp);
}
/* Check whether the key matches but do this only if we are not
running in syntax check mode. */
if ( update
&& !result && !listfp
&& (got_match || (key && key_matches_user_or_group (key))) )
{
int newflags = 0;
got_match = 1;
/* Apply the flags from gpgconf.conf. */
if (!flags)
;
else if (!strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
else if (!strcmp (flags, "no-change"))
option_info->flags |= GC_OPT_FLAG_NO_CHANGE;
else if (!strcmp (flags, "change"))
option_info->flags &= ~GC_OPT_FLAG_NO_CHANGE;
if (defaults)
{
/* Here we explicitly allow to update the value again. */
if (newflags)
{
option_info->new_flags = 0;
}
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (option_info, runtime, newflags, value);
}
}
}
if (length < 0 || ferror (config))
{
gc_error (0, errno, "error reading from '%s'", fname);
result = -1;
}
if (fclose (config))
gc_error (0, errno, "error closing '%s'", fname);
xfree (line);
/* If it all worked, process the options. */
if (!result && update && defaults && !listfp)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
(*gc_backend[backend_id].runtime_change) (0);
}
}
xfree (fname);
return result;
}