diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 034f7d5b4..3f07b2eac 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1,2329 +1,2395 @@ /* dirmngr.c - Keyserver and X.509 LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003-2004, 2006-2007, 2008, 2010-2011, 2020 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #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 #ifdef HAVE_INOTIFY_INIT # include #endif /*HAVE_INOTIFY_INIT*/ #include #include "dirmngr-err.h" #if HTTP_USE_NTBTLS # include #elif HTTP_USE_GNUTLS # include #endif /*HTTP_USE_GNUTLS*/ #define INCLUDED_BY_MAIN_MODULE 1 #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 "../common/asshelp.h" #if USE_LDAP # include "ldap-wrapper.h" #endif #include "../common/init.h" #include "../common/gc-opt-flags.h" #include "dns-stuff.h" #include "http-common.h" #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, aSupervised, aListCRLs, aLoadCRL, aFetchCRL, aShutdown, aFlush, aGPGConfList, aGPGConfTest, + aGPGConfVersions, oOptions, oDebug, oDebugAll, oDebugWait, oDebugLevel, oGnutlsDebug, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oLogFile, oBatch, oDisableHTTP, oDisableLDAP, oDisableIPv4, oDisableIPv6, oIgnoreLDAPDP, oIgnoreHTTPDP, oIgnoreOCSPSvcUrl, oHonorHTTPProxy, oHTTPProxy, oLDAPProxy, oOnlyLDAPProxy, oLDAPFile, oLDAPTimeout, oLDAPAddServers, oOCSPResponder, oOCSPSigner, oOCSPMaxClockSkew, oOCSPMaxPeriod, oOCSPCurrentPeriod, oMaxReplies, oHkpCaCert, oFakedSystemTime, oForce, oAllowOCSP, oAllowVersionCheck, oSocketName, oLDAPWrapperProgram, oHTTPWrapperProgram, oIgnoreCertExtension, oUseTor, oNoUseTor, oKeyServer, oNameServer, oDisableCheckOwnSocket, oStandardResolver, oRecursiveResolver, oResolverTimeout, oConnectTimeout, oConnectQuickTimeout, oListenBacklog, aTest }; static gpgrt_opt_t opts[] = { ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), + ARGPARSE_c (aGPGConfVersions, "gpgconf-versions", "@"), 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)") ), #ifndef HAVE_W32_SYSTEM ARGPARSE_c (aSupervised, "supervised", N_("run in supervised mode")), #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_header (NULL, N_("Options used for startup")), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_noconffile (oNoOptions, "no-options", "@"), ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", N_("|LEVEL|set the debugging level to LEVEL")), 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_s (oLogFile, "log-file", N_("|FILE|write server mode logs to FILE")), ARGPARSE_header ("Configuration", N_("Options controlling the configuration")), ARGPARSE_s_n (oAllowVersionCheck, "allow-version-check", N_("allow online software version check")), ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), ARGPARSE_s_i (oMaxReplies, "max-replies", N_("|N|do not return more than N items in one query")), ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/ ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"), ARGPARSE_header ("Network", N_("Network related options")), ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via Tor")), ARGPARSE_s_n (oNoUseTor, "no-use-tor", "@"), ARGPARSE_s_n (oDisableIPv4, "disable-ipv4", "@"), ARGPARSE_s_n (oDisableIPv6, "disable-ipv6", "@"), ARGPARSE_s_n (oStandardResolver, "standard-resolver", "@"), ARGPARSE_s_n (oRecursiveResolver, "recursive-resolver", "@"), ARGPARSE_s_i (oResolverTimeout, "resolver-timeout", "@"), ARGPARSE_s_s (oNameServer, "nameserver", "@"), ARGPARSE_s_i (oConnectTimeout, "connect-timeout", "@"), ARGPARSE_s_i (oConnectQuickTimeout, "connect-quick-timeout", "@"), ARGPARSE_header ("Keyserver", N_("Configuration for Keyservers")), ARGPARSE_s_s (oKeyServer, "keyserver", N_("|URL|use keyserver at URL")), ARGPARSE_s_s (oHkpCaCert, "hkp-cacert", N_("|FILE|use the CA certificates in FILE for HKP over TLS")), ARGPARSE_header ("HTTP", N_("Configuration for HTTP servers")), ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")), ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp", N_("ignore HTTP CRL distribution points")), ARGPARSE_s_s (oHTTPProxy, "http-proxy", N_("|URL|redirect all HTTP requests to URL")), ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", N_("use system's HTTP proxy setting")), ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"), ARGPARSE_header ("LDAP", N_("Configuration of LDAP servers to use")), ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")), ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp", N_("ignore LDAP CRL distribution points")), 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_header ("OCSP", N_("Configuration for OCSP")), ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")), ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url", N_("ignore certificate contained OCSP service URLs")), 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_header (NULL, N_("Other options")), ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")), ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */ ARGPARSE_header (NULL, ""), /* Stop the header group. */ /* Not yet used options. */ ARGPARSE_s_n (oBatch, "batch", "@"), ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"), 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_DNS_VALUE , "dns" }, { DBG_NETWORK_VALUE, "network" }, { DBG_LOOKUP_VALUE , "lookup" }, { DBG_EXTPROG_VALUE, "extprog" }, { 77, NULL } /* 77 := Do not exit on "help" or "?". */ }; #define DEFAULT_MAX_REPLIES 10 #define DEFAULT_LDAP_TIMEOUT 15 /* seconds */ #define DEFAULT_CONNECT_TIMEOUT (15*1000) /* 15 seconds */ #define DEFAULT_CONNECT_QUICK_TIMEOUT ( 2*1000) /* 2 seconds */ /* 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; /* Value for the listen() backlog argument. * Change at runtime with --listen-backlog. */ static int listen_backlog = 64; /* 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; /* Flags to indicate that we shall not watch our own socket. */ static int disable_check_own_socket; /* Flag to control the Tor mode. */ static enum { TOR_MODE_AUTO = 0, /* Switch to NO or YES */ TOR_MODE_NEVER, /* Never use Tor. */ TOR_MODE_NO, /* Do not use Tor */ TOR_MODE_YES, /* Use Tor */ TOR_MODE_FORCE /* Force using Tor */ } tor_mode; /* Counter for the active connections. */ static int active_connections; /* This flag is set by any network access and used by the housekeeping * thread to run background network tasks. */ static int network_activity_seen; /* A list of filenames registered with --hkp-cacert. */ static strlist_t hkp_cacert_filenames; /* The timer tick used for housekeeping stuff. The second constant is used when a shutdown is pending. */ #define TIMERTICK_INTERVAL (60) #define TIMERTICK_INTERVAL_SHUTDOWN (4) /* How oft to run the housekeeping. */ #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 npth_key_t my_tlskey_current_fd; #endif /* Prototypes. */ static void cleanup (void); #if USE_LDAP static ldap_server_t parse_ldapserver_file (const char* filename, int ienoent); #endif /*USE_LDAP*/ static fingerprint_list_t parse_ocsp_signer (const char *string); static void netactivity_action (void); static void handle_connections (assuan_fd_t listen_fd); +static void gpgconf_versions (void); + /* NPth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static const char * my_strusage( int level ) { const char *p; switch ( level ) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@DIRMNGR@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; 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 availability 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 set_tor_mode (void) { if (dirmngr_use_tor ()) { /* Enable Tor mode and when called again force a new circuit * (e.g. on SIGHUP). */ enable_dns_tormode (1); if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1)) { log_error ("error enabling Tor mode: %s\n", strerror (errno)); log_info ("(is your Libassuan recent enough?)\n"); } } else disable_dns_tormode (); } /* Return true if Tor shall be used. */ int dirmngr_use_tor (void) { if (tor_mode == TOR_MODE_AUTO) { /* Figure out whether Tor is running. */ assuan_fd_t sock; sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR); if (sock == ASSUAN_INVALID_FD) tor_mode = TOR_MODE_NO; else { tor_mode = TOR_MODE_YES; assuan_sock_close (sock); } } if (tor_mode == TOR_MODE_FORCE) return 2; /* Use Tor (using 2 to indicate force mode) */ else if (tor_mode == TOR_MODE_YES) return 1; /* Use Tor */ else return 0; /* Do not use Tor. */ } 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 (gpgrt_argparse_t *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.allow_version_check = 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); FREE_STRLIST (hkp_cacert_filenames); FREE_STRLIST (opt.keyserver); /* Note: We do not allow resetting of TOR_MODE_FORCE at runtime. */ if (tor_mode != TOR_MODE_FORCE) tor_mode = TOR_MODE_AUTO; disable_check_own_socket = 0; enable_standard_resolver (0); set_dns_timeout (0); opt.connect_timeout = 0; opt.connect_quick_timeout = 0; 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 oDisableCheckOwnSocket: disable_check_own_socket = 1; 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 oDisableIPv4: opt.disable_ipv4 = 1; break; case oDisableIPv6: opt.disable_ipv6 = 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 oAllowVersionCheck: opt.allow_version_check = 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: { /* We need to register the filenames with gnutls (http.c) and * also for our own cert cache. */ char *tmpname; /* Do tilde expansion and make path absolute. */ tmpname = make_absfilename (pargs->r.ret_str, NULL); http_register_tls_ca (tmpname); add_to_strlist (&hkp_cacert_filenames, pargs->r.ret_str); xfree (tmpname); } break; case oIgnoreCertExtension: add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str); break; case oUseTor: tor_mode = TOR_MODE_FORCE; break; case oNoUseTor: if (tor_mode != TOR_MODE_FORCE) tor_mode = TOR_MODE_NEVER; break; case oStandardResolver: enable_standard_resolver (1); break; case oRecursiveResolver: enable_recursive_resolver (1); break; case oKeyServer: if (*pargs->r.ret_str) add_to_strlist (&opt.keyserver, pargs->r.ret_str); break; case oNameServer: set_dns_nameserver (pargs->r.ret_str); break; case oResolverTimeout: set_dns_timeout (pargs->r.ret_int); break; case oConnectTimeout: opt.connect_timeout = pargs->r.ret_ulong * 1000; break; case oConnectQuickTimeout: opt.connect_quick_timeout = pargs->r.ret_ulong * 1000; break; default: return 0; /* Not handled. */ } set_dns_verbose (opt.verbose, !!DBG_DNS); http_set_verbose (opt.verbose, !!DBG_NETWORK); set_dns_disable_ipv4 (opt.disable_ipv4); set_dns_disable_ipv6 (opt.disable_ipv6); return 1; /* Handled. */ } /* This function is called after option parsing to adjust some values * and call option setup functions. */ static void post_option_parsing (void) { /* It would be too surpirsing if the quick timeout is larger than * the standard value. */ if (opt.connect_quick_timeout > opt.connect_timeout) opt.connect_quick_timeout = opt.connect_timeout; set_debug (); } #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*/ #if HTTP_USE_NTBTLS static void my_ntbtls_log_handler (void *opaque, int level, const char *fmt, va_list argv) { (void)opaque; if (level == -1) log_logv_prefix (GPGRT_LOGLVL_INFO, "ntbtls: ", fmt, argv); else { char prefix[10+20]; snprintf (prefix, sizeof prefix, "ntbtls(%d): ", level); log_logv_prefix (GPGRT_LOGLVL_DEBUG, prefix, fmt, argv); } } #endif static void thread_init (void) { npth_init (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* 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*/ } int main (int argc, char **argv) { enum cmd_and_opt_values cmd = 0; gpgrt_argparse_t pargs; int orig_argc; char **orig_argv; char *last_configname = NULL; const char *configname = NULL; const char *shell; int debug_argparser = 0; 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; struct assuan_malloc_hooks malloc_hooks; early_system_init (); gpgrt_set_strusage (my_strusage); log_set_prefix (DIRMNGR_NAME, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); 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 (!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_sock_init (); setup_libassuan_logging (&opt.debug, dirmngr_assuan_log_monitor); setup_libgcrypt_logging (); #if HTTP_USE_NTBTLS ntbtls_set_log_handler (my_ntbtls_log_handler, NULL); #endif /* Setup defaults. */ shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; /* Reset rereadable options to default values. */ parse_rereadable_options (NULL, 0); /* Default TCP timeouts. */ opt.connect_timeout = DEFAULT_CONNECT_TIMEOUT; opt.connect_quick_timeout = DEFAULT_CONNECT_QUICK_TIMEOUT; /* 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= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oDebug: case oDebugAll: debug_argparser++; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; } } /* Reset the flags. */ pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); socket_name = dirmngr_socket_name (); /* The configuraton directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); /* We are re-using the struct, thus the reset flag. We OR the * flags so that the internal intialized flag won't be cleared. */ argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags |= (ARGPARSE_FLAG_RESET | ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER); while (gpgrt_argparser (&pargs, opts, DIRMNGR_NAME EXTSEP_S "conf")) { if (pargs.r_opt == ARGPARSE_CONFFILE) { if (debug_argparser) log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); if (pargs.r_type) { xfree (last_configname); last_configname = xstrdup (pargs.r.ret_str); configname = last_configname; } else configname = NULL; continue; } if (parse_rereadable_options (&pargs, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case aServer: case aDaemon: case aSupervised: case aShutdown: case aFlush: case aListCRLs: case aLoadCRL: case aFetchCRL: case aGPGConfList: case aGPGConfTest: + case aGPGConfVersions: 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 oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; break; 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; case oListenBacklog: listen_backlog = pargs.r.ret_int; break; default: if (configname) pargs.err = ARGPARSE_PRINT_WARNING; else pargs.err = ARGPARSE_PRINT_ERROR; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (!last_configname) opt.config_filename = gpgrt_fnameconcat (gnupg_homedir (), DIRMNGR_NAME EXTSEP_S "conf", NULL); else { opt.config_filename = last_configname; last_configname = NULL; } if (log_get_errorcount(0)) exit(2); if (nogreeting ) greeting = 0; if (!opt.homedir_cache) opt.homedir_cache = xstrdup (gnupg_homedir ()); if (greeting) { es_fprintf (es_stderr, "%s %s; %s\n", gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14)); es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15)); } #ifdef IS_DEVELOPMENT_VERSION log_info ("NOTE: this is a development version!\n"); #endif /* 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 (gnupg_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"); } /* Note that we do not run set_tor_mode in --gpgconf-list mode * because it will attempt to connect to the tor client and that can * be time consuming. */ post_option_parsing (); - if (cmd != aGPGConfTest && cmd != aGPGConfList) + if (cmd != aGPGConfTest && cmd != aGPGConfList && cmd != aGPGConfVersions) set_tor_mode (); /* Get LDAP server list from file. */ #if USE_LDAP if (!ldapfile) { ldapfile = make_filename (gnupg_homedir (), "dirmngr_ldapservers.conf", NULL); opt.ldapservers = parse_ldapserver_file (ldapfile, 1); xfree (ldapfile); } else opt.ldapservers = parse_ldapserver_file (ldapfile, 0); #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) 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, GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } if (debug_wait) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); ks_hkp_init (); http_register_netactivity_cb (netactivity_action); start_command_handler (ASSUAN_INVALID_FD, 0); shutdown_reaper (); } #ifndef HAVE_W32_SYSTEM else if (cmd == aSupervised) { /* In supervised mode, we expect file descriptor 3 to be an already opened, listening socket. We will also not detach from the controlling process or close stderr; the supervisor should handle all of that. */ struct stat statbuf; if (fstat (3, &statbuf) == -1 && errno == EBADF) { log_error ("file descriptor 3 must be validin --supervised mode\n"); dirmngr_exit (1); } socket_name = gnupg_get_socket_name (3); /* 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); } else log_set_prefix (NULL, 0); thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); ks_hkp_init (); http_register_netactivity_cb (netactivity_action); handle_connections (3); shutdown_reaper (); } #endif /*HAVE_W32_SYSTEM*/ 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); } if (debug_wait) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } #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); } { 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); } } 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 (gnupg_chmod (serv_addr.sun_path, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), serv_addr.sun_path, strerror (errno)); if (listen (FD2INT (fd), listen_backlog) == -1) { log_error ("listen(fd,%d) failed: %s\n", listen_backlog, 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 ) { if ( !close (i) && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) { log_error ("failed to open '%s': %s\n", "/dev/null", strerror (errno)); cleanup (); dirmngr_exit (1); } } } 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; } #endif if (!nodetach ) { if (gnupg_chdir (gnupg_daemon_rootdir ())) { log_error ("chdir to '%s' failed: %s\n", gnupg_daemon_rootdir (), strerror (errno)); dirmngr_exit (1); } } thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); ks_hkp_init (); http_register_netactivity_cb (netactivity_action); handle_connections (fd); shutdown_reaper (); } else if (cmd == aListCRLs) { /* Just list the CRL cache and exit. */ if (argc) wrong_args ("--list-crls"); 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); thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); ks_hkp_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); thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); ks_hkp_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_esc; es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT); 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); filename_esc = percent_escape (get_default_keyserver (0), NULL); es_printf ("keyserver:%lu:\"%s:\n", flags | GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename_esc); es_printf ("resolver-timeout:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, 0); } + else if (cmd == aGPGConfVersions) + gpgconf_versions (); + cleanup (); return !!rc; } static void cleanup (void) { crl_cache_deinit (); cert_cache_deinit (1); reload_dns_stuff (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) { ctrl->magic = SERVER_CONTROL_MAGIC; if (opt.http_proxy) ctrl->http_proxy = xstrdup (opt.http_proxy); ctrl->http_no_crl = 1; ctrl->timeout = opt.connect_timeout; } void dirmngr_deinit_default_ctrl (ctrl_t ctrl) { if (!ctrl) return; ctrl->magic = 0xdeadbeef; 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 of 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, int ignore_enoent) { 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) { if (ignore_enoent && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) ; else log_info ("failed to open '%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 (gnupg_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's original arguments. Unless we have a mechanism to tell this, we need to live on with this. */ static void reread_configuration (void) { gpgrt_argparse_t pargs; char *twopart; int dummy; if (!opt.config_filename) return; /* No config file. */ twopart = strconcat (DIRMNGR_NAME EXTSEP_S "conf" PATHSEP_S, opt.config_filename, NULL); if (!twopart) return; /* Out of core. */ parse_rereadable_options (NULL, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = (ARGPARSE_FLAG_KEEP |ARGPARSE_FLAG_SYS |ARGPARSE_FLAG_USER); while (gpgrt_argparser (&pargs, opts, twopart)) { if (pargs.r_opt == ARGPARSE_CONFFILE) { log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); } else if (pargs.r_opt < -1) pargs.err = ARGPARSE_PRINT_WARNING; else /* Try to parse this option - ignore unchangeable ones. */ parse_rereadable_options (&pargs, 1); } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ xfree (twopart); post_option_parsing (); } /* 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 (); set_tor_mode (); cert_cache_deinit (0); crl_cache_deinit (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); reload_dns_stuff (0); ks_hkp_reload (); } /* This function is called if some network activity was done. At this * point we know the we have a network and we can decide whether to * run scheduled background tasks soon. The function should return * quickly and only trigger actions for another thread. */ static void netactivity_action (void) { network_activity_seen = 1; } /* 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 (); domaininfo_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", gpgrt_strusage(11), gpgrt_strusage(13)); cleanup (); dirmngr_exit (0); } break; case SIGINT: log_info (_("SIGINT received - immediate shutdown\n")); log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_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; struct server_control_s ctrlbuf; (void)arg; curtime = gnupg_get_time (); if (sentinel) { log_info ("housekeeping is already going on\n"); return NULL; } sentinel++; if (opt.verbose > 1) log_info ("starting housekeeping\n"); memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); dns_stuff_housekeeping (); ks_hkp_housekeeping (curtime); if (network_activity_seen) { network_activity_seen = 0; if (opt.allow_version_check) dirmngr_load_swdb (&ctrlbuf, 0); workqueue_run_global_tasks (&ctrlbuf, 1); } else workqueue_run_global_tasks (&ctrlbuf, 0); dirmngr_deinit_default_ctrl (&ctrlbuf); if (opt.verbose > 1) log_info ("ready with housekeeping\n"); sentinel--; return NULL; } /* We try to enable correct overflow handling for signed int (commonly * used for time_t). With gcc 4.2 -fno-strict-overflow was introduced * and used here as a pragma. Later gcc versions (gcc 6?) removed * this as a pragma and -fwrapv was then suggested as a replacement * for -fno-strict-overflow. */ #if GPGRT_HAVE_PRAGMA_GCC_PUSH # pragma GCC push_options # pragma GCC optimize ("wrapv") #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_HAVE_PRAGMA_GCC_PUSH # 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) { struct stat statbuf; 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 whether the homedir is still available. */ if (!shutdown_pending && stat (gnupg_homedir (), &statbuf) && errno == ENOENT) { shutdown_pending = 1; log_info ("homedir has been removed - shutting down\n"); } } /* 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 function. */ static void * start_connection_thread (void *arg) { static unsigned int last_session_id; unsigned int session_id; union int_and_ptr_u argval; gnupg_fd_t fd; 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)); session_id = ++last_session_id; if (!session_id) session_id = ++last_session_id; start_command_handler (fd, session_id); if (opt.verbose) log_info (_("handler for fd %d terminated\n"), FD2INT (fd)); active_connections--; workqueue_run_post_session_tasks (session_id); #ifndef HAVE_W32_SYSTEM argval.afd = ASSUAN_INVALID_FD; npth_setspecific (my_tlskey_current_fd, argval.aptr); #endif return NULL; } #ifdef HAVE_INOTIFY_INIT /* Read an inotify event and return true if it matches NAME. */ static int my_inotify_is_name (int fd, const char *name) { union { struct inotify_event ev; char _buf[sizeof (struct inotify_event) + 100 + 1]; } buf; int n; const char *s; s = strrchr (name, '/'); if (s && s[1]) name = s + 1; n = npth_read (fd, &buf, sizeof buf); if (n < sizeof (struct inotify_event)) return 0; if (buf.ev.len < strlen (name)+1) return 0; if (strcmp (buf.ev.name, name)) return 0; /* Not the desired file. */ return 1; /* Found. */ } #endif /*HAVE_INOTIFY_INIT*/ /* Main loop in daemon mode. Note that LISTEN_FD will be owned by * this function. */ 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 ); int nfd, ret; fd_set fdset, read_fdset; struct timespec abstime; struct timespec curtime; struct timespec timeout; int saved_errno; int my_inotify_fd = -1; 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 #ifdef HAVE_INOTIFY_INIT if (disable_check_own_socket) my_inotify_fd = -1; else if ((my_inotify_fd = inotify_init ()) == -1) log_info ("error enabling fast daemon termination: %s\n", strerror (errno)); else { /* We need to watch the directory for the file because there * won't be an IN_DELETE_SELF for a socket file. */ char *slash = strrchr (socket_name, '/'); log_assert (slash && slash[1]); *slash = 0; if (inotify_add_watch (my_inotify_fd, socket_name, IN_DELETE) == -1) { close (my_inotify_fd); my_inotify_fd = -1; } *slash = '/'; } #endif /*HAVE_INOTIFY_INIT*/ /* 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); if (my_inotify_fd != -1) { FD_SET (my_inotify_fd, &fdset); if (my_inotify_fd > nfd) nfd = my_inotify_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. * * Note that we do not close the listening socket because a * client trying to connect to that socket would instead * restart a new dirmngr instance - which is unlikely the * intention of a shutdown. */ /* assuan_sock_close (listen_fd); */ /* listen_fd = -1; */ FD_ZERO (&fdset); nfd = -1; if (my_inotify_fd != -1) { FD_SET (my_inotify_fd, &fdset); nfd = my_inotify_fd; } } /* Take a copy of the fdset. */ read_fdset = fdset; npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { /* Timeout. When a shutdown is pending we use a shorter * interval to handle the shutdown more quickly. */ handle_tick (); npth_clock_gettime (&abstime); abstime.tv_sec += (shutdown_pending ? TIMERTICK_INTERVAL_SHUTDOWN : 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) { /* Do not anymore accept connections. */ continue; } #ifdef HAVE_INOTIFY_INIT if (my_inotify_fd != -1 && FD_ISSET (my_inotify_fd, &read_fdset) && my_inotify_is_name (my_inotify_fd, socket_name)) { shutdown_pending = 1; log_info ("socket file has been removed - shutting down\n"); } #endif /*HAVE_INOTIFY_INIT*/ if (FD_ISSET (FD2INT (listen_fd), &read_fdset)) { gnupg_fd_t fd; 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, "conn fd=%d", FD2INT(fd)); 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); } } } #ifdef HAVE_INOTIFY_INIT if (my_inotify_fd != -1) close (my_inotify_fd); #endif /*HAVE_INOTIFY_INIT*/ npth_attr_destroy (&tattr); if (listen_fd != GNUPG_INVALID_FD) assuan_sock_close (listen_fd); cleanup (); log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); } const char* dirmngr_get_current_socket_name (void) { if (socket_name) return socket_name; else return dirmngr_socket_name (); } + + + +/* Parse the revision part from the extended version blurb. */ +static const char * +get_revision_from_blurb (const char *blurb, int *r_len) +{ + const char *s = blurb? blurb : ""; + int n; + + for (; *s; s++) + if (*s == '\n' && s[1] == '(') + break; + if (s) + { + s += 2; + for (n=0; s[n] && s[n] != ' '; n++) + ; + } + else + { + s = "?"; + n = 1; + } + *r_len = n; + return s; +} + + +/* Print versions of dirmngr and used libraries. This is used by + * "gpgconf --show-versions" so that there is no need to link gpgconf + * against all these libraries. This is an internal API and should + * not be relied upon. */ +static void +gpgconf_versions (void) +{ + const char *s; + int n; + + /* Unfortunately Npth has no way to get the version. */ + + s = get_revision_from_blurb (assuan_check_version ("\x01\x01"), &n); + es_fprintf (es_stdout, "* Libassuan %s (%.*s)\n\n", + assuan_check_version (NULL), n, s); + + es_fprintf (es_stdout, "* KSBA %s \n\n", + ksba_check_version (NULL)); + +#ifdef HTTP_USE_NTBTLS + s = get_revision_from_blurb (ntbtls_check_version ("\x01\x01"), &n); + es_fprintf (es_stdout, "* NTBTLS %s (%.*s)\n\n", + ntbtls_check_version (NULL), n, s); +#elif HTTP_USE_GNUTLS + es_fprintf (es_stdout, "* GNUTLS %s\n\n", + gnutls_check_version (NULL)); +#endif + +} diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 3583abe57..919bcb357 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1,3408 +1,3400 @@ /* gpgconf-comp.c - Configuration utility for GnuPG. * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc. * Copyright (C) 2016 Werner Koch * Copyright (C) 2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GnuPG; if not, see . */ #if HAVE_CONFIG_H #include #endif #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 #include "../common/util.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/status.h" #include "../common/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 #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_LOGLVL_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); gpgconf_failure (gpg_error_from_errno (errnum)); } } /* Forward declaration. */ static void gpg_agent_runtime_change (int killflag); static void scdaemon_runtime_change (int killflag); static void dirmngr_runtime_change (int killflag); static void keyboxd_runtime_change (int killflag); /* STRING_ARRAY is a malloced array with malloced strings. It is used * a space to store strings so that other objects may point to these * strings. It shall never be shrinked or any items changes. * STRING_ARRAY itself may be reallocated to increase the size of the * table. STRING_ARRAY_USED is the number of items currently used, * STRING_ARRAY_SIZE is the number of calloced slots. */ static char **string_array; static size_t string_array_used; static size_t string_array_size; /* 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 const 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 const struct { const char *name; } gc_level[] = { { "basic" }, { "advanced" }, { "expert" }, { "invisible" }, { "internal" } }; /* Option flags. The flags which are used by the components 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 emitted option list are not options, but mark the beginning of a new group of options. These entries have the GROUP flag set. Note that this is internally also known as a header line. */ #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) /* The RUNTIME flag for an option indicates that the option can be changed at runtime. */ #define GC_OPT_FLAG_RUNTIME (1UL << 3) /* A human-readable description for each flag. */ static const struct { const char *name; } gc_flag[] = { { "group" }, { "optional arg" }, { "list" }, { "runtime" }, { "default" }, { "default desc" }, { "no arg desc" }, { "no change" } }; /* Each option we want to support in gpgconf has the needed * information in a static list per componenet. This struct describes * the info for a single option. */ struct known_option_s { /* If this is NULL, then this is a terminator in an array of unknown * length. 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. */ unsigned long flags; /* The expert level. */ gc_expert_level_t level; /* The complex type of the option argument; the default of 0 is used * for a standard type as returned by --dump-option-table. */ gc_arg_type_t arg_type; }; typedef struct known_option_s known_option_t; /* The known options of the GC_COMPONENT_GPG_AGENT component. */ static known_option_t known_options_gpg_agent[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ssh-fingerprint-digest", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, /**/ GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "max-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "no-allow-external-cache", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "min-passphrase-len", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, /**/ GC_ARG_TYPE_FILENAME }, { "max-passphrase-days", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "pinentry-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { NULL } }; /* The known options of the GC_COMPONENT_SCDAEMON component. */ static known_option_t known_options_scdaemon[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "reader-port", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "ctapi-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "pcsc-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "disable-ccid", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "enable-pinpad-varlen", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "card-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "application-priority", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { NULL } }; /* The known options of the GC_COMPONENT_GPG component. */ static known_option_t known_options_gpg[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED, GC_ARG_TYPE_ALIAS_LIST}, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "trust-model", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "auto-key-import", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "max-cert-depth", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "completes-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "marginals-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, /* The next is a pseudo option which we read via --gpgconf-list */ { "default_pubkey_algo", (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE }, { NULL } }; /* The known options of the GC_COMPONENT_GPGSM component. */ static known_option_t known_options_gpgsm[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, GC_ARG_TYPE_LDAP_SERVER }, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, /* Pseudo option follows. */ { "default_pubkey_algo", (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE }, { NULL } }; /* The known options of the GC_COMPONENT_DIRMNGR component. */ static known_option_t known_options_dirmngr[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { NULL } }; /* The known options of the GC_COMPONENT_KEYBOXD component. */ static known_option_t known_options_keyboxd[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { NULL } }; /* The known options of the GC_COMPONENT_PINENTRY component. */ static known_option_t known_options_pinentry[] = { { NULL } }; /* Our main option info object. We copy all required information from the * gpgrt_opt_t items but convert the flags value to bit flags. */ struct gc_option_s { const char *name; /* The same as gpgrt_opt_t.long_opt. */ const char *desc; /* The same as gpgrt_opt_t.description. */ unsigned int is_header:1; /* This is a header item. */ unsigned int is_list:1; /* This is a list style option. */ unsigned int opt_arg:1; /* The option's argument is optional. */ unsigned int runtime:1; /* The option is runtime changeable. */ unsigned int gpgconf_list:1; /* Mentioned by --gpgconf-list. */ unsigned int has_default:1; /* The option has a default value. */ unsigned int def_in_desc:1; /* The default is in the descrition. */ unsigned int no_arg_desc:1; /* The argument has a default ???. */ unsigned int no_change:1; /* User shall not change the option. */ unsigned int attr_ignore:1; /* The ARGPARSE_ATTR_IGNORE. */ unsigned int attr_force:1; /* The ARGPARSE_ATTR_FORCE. */ /* The expert level - copied from known_options. */ gc_expert_level_t level; /* The complex type - copied from known_options. */ gc_arg_type_t arg_type; /* The default value for this option. This is NULL if the option is not present in the component, the empty string if no default is available, and otherwise a quoted string. This is currently malloced.*/ char *default_value; /* 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_s gc_option_t; /* The information associated with each component. */ static struct { /* The name of the component. Some components don't have an * associated program, but are implemented directly by GPGConf. In * this case, PROGRAM is NULL. */ char *program; /* The displayed 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 of this component. */ const char *desc; /* 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 program for the component * is available. */ char module_name; /* The name for the configuration filename of this component. */ const char *option_config_filename; /* The static table of known options for this component. */ known_option_t *known_options; /* The runtime change callback. If KILLFLAG is true the component is killed and not just reloaded. */ void (*runtime_change) (int killflag); /* The table of known options as read from the component including * header lines and such. This is suitable to be passed to * gpgrt_argparser. Will be filled in by * retrieve_options_from_program. */ gpgrt_opt_t *opt_table; /* The full table including data from OPT_TABLE. The end of the * table is marked by NULL entry for NAME. Will be filled in by * retrieve_options_from_program. */ gc_option_t *options; } gc_component[GC_COMPONENT_NR] = { /* Note: The order of the items must match the order given in the * gc_component_id_t enumeration. The order is often used by * frontends to display the backend options thus do not change the * order without considering the user experience. */ { NULL }, /* DUMMY for GC_COMPONENT_ANY */ { GPG_NAME, GPG_DISP_NAME, "gnupg", N_("OpenPGP"), GNUPG_MODULE_NAME_GPG, GPG_NAME ".conf", known_options_gpg }, { GPGSM_NAME, GPGSM_DISP_NAME, "gnupg", N_("S/MIME"), GNUPG_MODULE_NAME_GPGSM, GPGSM_NAME ".conf", known_options_gpgsm }, { KEYBOXD_NAME, KEYBOXD_DISP_NAME, "gnupg", N_("Public Keys"), GNUPG_MODULE_NAME_KEYBOXD, KEYBOXD_NAME ".conf", known_options_keyboxd, keyboxd_runtime_change }, { GPG_AGENT_NAME, GPG_AGENT_DISP_NAME, "gnupg", N_("Private Keys"), GNUPG_MODULE_NAME_AGENT, GPG_AGENT_NAME ".conf", known_options_gpg_agent, gpg_agent_runtime_change }, { SCDAEMON_NAME, SCDAEMON_DISP_NAME, "gnupg", N_("Smartcards"), GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf", known_options_scdaemon, scdaemon_runtime_change}, { DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"), GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf", known_options_dirmngr, dirmngr_runtime_change }, { "pinentry", "Pinentry", "gnupg", N_("Passphrase Entry"), GNUPG_MODULE_NAME_PINENTRY, NULL, known_options_pinentry } }; /* Structure used to collect error output of the component 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. */ }; /* Initialization and finalization. */ static void gc_option_free (gc_option_t *o) { if (o == NULL || o->name == NULL) return; xfree (o->value); gc_option_free (o + 1); } static void gc_components_free (void) { int i; for (i = 0; i < DIM (gc_component); i++) gc_option_free (gc_component[i].options); } void gc_components_init (void) { atexit (gc_components_free); } /* Engine specific support. */ static void gpg_agent_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[5]; pid_t pid = (pid_t)(-1); int i = 0; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "--no-autostart"; argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT"; argv[i++] = NULL; if (!err) 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 = 0; const char *pgmname; const char *argv[9]; pid_t pid = (pid_t)(-1); int i = 0; (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); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "-s"; argv[i++] = "--no-autostart"; argv[i++] = "GETINFO scd_running"; argv[i++] = "/if ${! $?}"; argv[i++] = "scd killscd"; argv[i++] = "/end"; argv[i++] = NULL; if (!err) 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 = 0; const char *pgmname; const char *argv[6]; pid_t pid = (pid_t)(-1); pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); argv[0] = "--no-autostart"; argv[1] = "--dirmngr"; argv[2] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR"; if (gnupg_default_homedir_p ()) argv[3] = NULL; else { argv[3] = "--homedir"; argv[4] = gnupg_homedir (); argv[5] = NULL; } if (!err) 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); } static void keyboxd_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[6]; pid_t pid = (pid_t)(-1); pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); argv[0] = "--no-autostart"; argv[1] = "--keyboxd"; argv[2] = killflag? "KILLKEYBOXD" : "RELOADKEYBOXD"; if (gnupg_default_homedir_p ()) argv[3] = NULL; else { argv[3] = "--homedir"; argv[4] = gnupg_homedir (); argv[5] = NULL; } if (!err) 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[5]; int i; pid_t pid; if (component < 0) { err = gc_component_launch (GC_COMPONENT_GPG_AGENT); if (!err) err = gc_component_launch (GC_COMPONENT_KEYBOXD); if (!err) err = gc_component_launch (GC_COMPONENT_DIRMNGR); return err; } if (!(component == GC_COMPONENT_GPG_AGENT || component == GC_COMPONENT_KEYBOXD || component == GC_COMPONENT_DIRMNGR)) { log_error ("%s\n", _("Component not suitable for launching")); gpgconf_failure (0); } if (gc_component_check_options (component, NULL, NULL)) { log_error (_("Configuration file of component %s is broken\n"), gc_component[component].name); if (!opt.quiet) log_info (_("Note: Use the command \"%s%s\" to get details.\n"), gc_component[component].name, " --gpgconf-test"); gpgconf_failure (0); } pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); i = 0; if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } if (component == GC_COMPONENT_DIRMNGR) argv[i++] = "--dirmngr"; else if (component == GC_COMPONENT_KEYBOXD) argv[i++] = "--keyboxd"; 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" : component == GC_COMPONENT_KEYBOXD? " --keyboxd":"", " NOP", gpg_strerror (err)); gnupg_release_process (pid); return err; } static void do_runtime_change (int component, int killflag) { int runtime[GC_COMPONENT_NR] = { 0 }; if (component < 0) { for (component = 0; component < GC_COMPONENT_NR; component++) runtime [component] = 1; } else { log_assert (component >= 0 && component < GC_COMPONENT_NR); runtime [component] = 1; } /* Do the restart for the selected components. */ for (component = GC_COMPONENT_NR-1; component >= 0; component--) { if (runtime[component] && gc_component[component].runtime_change) (*gc_component[component].runtime_change) (killflag); } } /* Unconditionally restart COMPONENT. */ void gc_component_kill (int component) { do_runtime_change (component, 1); } /* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */ void gc_component_reload (int component) { do_runtime_change (component, 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 possible to keep the original code set and switch back for regular diagnostic output (redefine "_(" for that) but given the nature of this tool, being something invoked from other programs, 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 = xrealloc (esc_str, new_len); 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 if (*src == '\n') { /* The newline is problematic in a line-based format. */ *(dst++) = '%'; *(dst++) = '0'; *(dst++) = 'a'; } 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 = xrealloc (str, new_len); 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_id_t component; const char *desc; const char *pgmname; for (component = 0; component < GC_COMPONENT_NR; component++) { if (!gc_component[component].program) continue; if (gc_component[component].module_name) pgmname = gnupg_module_name (gc_component[component].module_name); else pgmname = ""; desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].program, 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. If CONF_FILE is NULL the * standard config file is used. If OUT is not NULL the output is * written to that stream. 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; const char *pgmname; const char *argv[5]; int i; pid_t pid; int exitcode; estream_t errfp; error_line_t errlines; log_assert (component >= 0 && component < GC_COMPONENT_NR); if (!gc_component[component].program) return 0; if (!gc_component[component].module_name) return 0; pgmname = gnupg_module_name (gc_component[component].module_name); i = 0; if (!gnupg_default_homedir_p () && component != GC_COMPONENT_PINENTRY) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } 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, NULL, 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].program, 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_id_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_id_t idx; for (idx = 0; idx < GC_COMPONENT_NR; idx++) { if (gc_component[idx].program && !strcmp (name, gc_component[idx].program)) return idx; } return -1; } /* List the option OPTION. */ static void list_one_option (gc_component_id_t component, const gc_option_t *option, estream_t out) { const char *desc = NULL; char *arg_name = NULL; unsigned long flags; const char *desc_domain = gc_component[component].desc_domain; if (option->desc) { desc = my_dgettext (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. */ flags = 0; if (option->is_header) flags |= GC_OPT_FLAG_GROUP; if (option->is_list) flags |= GC_OPT_FLAG_LIST; if (option->runtime) flags |= GC_OPT_FLAG_RUNTIME; if (option->has_default) flags |= GC_OPT_FLAG_DEFAULT; if (option->def_in_desc) flags |= GC_OPT_FLAG_DEF_DESC; if (option->no_arg_desc) flags |= GC_OPT_FLAG_NO_ARG_DESC; if (option->no_change) flags |= GC_OPT_FLAG_NO_CHANGE; es_fprintf (out, ":%lu", flags); if (opt.verbose) { es_putc (' ', out); if (!flags) es_fprintf (out, "none"); else { 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. This was never used and is thus empty. */ es_fprintf (out, ":"); /* The value field. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && option->is_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; for ( ; option && option->name; option++) { /* Do not output unknown or internal options. */ if (!option->is_header && option->level == GC_LEVEL_INTERNAL) continue; if (option->is_header) { 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. */ for ( ; group_option->name; group_option++) { if (group_option->is_header) break; if (group_option->level < level) level = group_option->level; } /* Check if group is empty. */ if (level != GC_LEVEL_NR) { gc_option_t opt_copy; /* Fix up the group level. */ opt_copy = *option; opt_copy.level = level; list_one_option (component, &opt_copy, out); } } else list_one_option (component, option, out); } } /* Return true if the option NAME is known and that we want it as * gpgconf managed option. */ static known_option_t * is_known_option (gc_component_id_t component, const char *name) { known_option_t *option = gc_component[component].known_options; if (option) { for (; option->name; option++) if (!strcmp (option->name, name)) break; } return (option && option->name)? option : NULL; } /* Find the option NAME in component COMPONENT. Returns pointer to * the option descriptor or NULL if not found. */ static gc_option_t * find_option (gc_component_id_t component, const char *name) { gc_option_t *option = gc_component[component].options; if (option) { for (; option->name; option++) { if (!option->is_header && !strcmp (option->name, name)) return option; } } return NULL; } /* Retrieve the options for the component COMPONENT. With * ONLY_INSTALLED set components which are not installed are silently * ignored. */ static void retrieve_options_from_program (gc_component_id_t component, int only_installed) { gpg_error_t err; const char *pgmname; const char *argv[2]; estream_t outfp; int exitcode; pid_t pid; known_option_t *known_option; gc_option_t *option; char *line = NULL; size_t line_len = 0; ssize_t length; const char *config_name; gpgrt_argparse_t pargs; int dummy_argc; char *twopartconfig_name = NULL; gpgrt_opt_t *opt_table = NULL; /* A malloced option table. */ size_t opt_table_used = 0; /* Its current length. */ size_t opt_table_size = 0; /* Its allocated length. */ gc_option_t *opt_info = NULL; /* A malloced options table. */ size_t opt_info_used = 0; /* Its current length. */ size_t opt_info_size = 0; /* Its allocated length. */ int i; pgmname = (gc_component[component].module_name ? gnupg_module_name (gc_component[component].module_name) : gc_component[component].program ); if (only_installed && access (pgmname, X_OK)) { return; /* The component is not installed. */ } /* First we need to read the option table from the program. */ argv[0] = "--dump-option-table"; argv[1] = NULL; err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, NULL, &outfp, NULL, &pid); if (err) { gc_error (1, 0, "could not gather option table from '%s': %s", pgmname, gpg_strerror (err)); } while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) { const char *fields[4]; const char *optname, *optdesc; unsigned int optflags; int short_opt; gc_arg_type_t arg_type; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; if (split_fields_colon (line, fields, DIM (fields)) < 4) { gc_error (0,0, "WARNING: invalid line in option table of '%s'\n", pgmname); continue; } optname = fields[0]; short_opt = atoi (fields[1]); if (short_opt < 1) { gc_error (0,0, "WARNING: bad short option in option table of '%s'\n", pgmname); continue; } optflags = strtoul (fields[2], NULL, 10); if ((optflags & ARGPARSE_OPT_HEADER)) known_option = NULL; /* We want all header-only options. */ else if ((known_option = is_known_option (component, optname))) ; /* Yes we want this one. */ else continue; /* No need to store this option description. */ /* The +1 here is to make sure that we will have a zero item at * the end of the table. */ if (opt_table_used + 1 >= opt_table_size) { /* Note that this also does the initial allocation. */ opt_table_size += 128; opt_table = xreallocarray (opt_table, opt_table_used, opt_table_size, sizeof *opt_table); } /* The +1 here is to make sure that we will have a zero item at * the end of the table. */ if (opt_info_used + 1 >= opt_info_size) { /* Note that this also does the initial allocation. */ opt_info_size += 128; opt_info = xreallocarray (opt_info, opt_info_used, opt_info_size, sizeof *opt_info); } /* The +1 here accounts for the two items we are going to add to * the global string table. */ if (string_array_used + 1 >= string_array_size) { string_array_size += 256; string_array = xreallocarray (string_array, string_array_used, string_array_size, sizeof *string_array); } optname = string_array[string_array_used++] = xstrdup (fields[0]); optdesc = string_array[string_array_used++] = xstrdup (fields[3]); /* Create an option table which can then be supplied to * gpgrt_parser. Unfortunately there is no private pointer in * the public option table struct so that we can't add extra * data we need here. Thus we need to build up another table * for such info and for ease of use we also copy the tehre the * data from the option table. It is not possible to use the * known_option_s for this because that one does not carry * header lines and it might also be problematic to use such * static tables for caching options and default values. */ opt_table[opt_table_used].long_opt = optname; opt_table[opt_table_used].short_opt = short_opt; opt_table[opt_table_used].description = optdesc; opt_table[opt_table_used].flags = optflags; opt_table_used++; /* Note that as per argparser specs the opt_table uses "@" to * specifify an empty description. In the DESC script of * options (opt_info_t) we want to have a real empty string. */ opt_info[opt_info_used].name = optname; if (*optdesc == '@' && !optdesc[1]) opt_info[opt_info_used].desc = optdesc+1; else opt_info[opt_info_used].desc = optdesc; /* Unfortunately we need to remap the types. */ switch ((optflags & ARGPARSE_TYPE_MASK)) { case ARGPARSE_TYPE_INT: arg_type = GC_ARG_TYPE_INT32; break; case ARGPARSE_TYPE_LONG: arg_type = GC_ARG_TYPE_INT32; break; case ARGPARSE_TYPE_ULONG: arg_type = GC_ARG_TYPE_UINT32; break; case ARGPARSE_TYPE_STRING: arg_type = GC_ARG_TYPE_STRING; break; default: arg_type = GC_ARG_TYPE_NONE; break; } opt_info[opt_info_used].arg_type = arg_type; if ((optflags & ARGPARSE_OPT_HEADER)) opt_info[opt_info_used].is_header = 1; if (known_option) { if ((known_option->flags & GC_OPT_FLAG_LIST)) opt_info[opt_info_used].is_list = 1; /* FIXME: The next can also be taken from opt_table->flags. * We need to check the code whether both specifications match. */ if ((known_option->flags & GC_OPT_FLAG_ARG_OPT)) opt_info[opt_info_used].opt_arg = 1; if ((known_option->flags & GC_OPT_FLAG_RUNTIME)) opt_info[opt_info_used].runtime = 1; opt_info[opt_info_used].level = known_option->level; /* Override the received argtype by a complex type. */ if (known_option->arg_type) opt_info[opt_info_used].arg_type = known_option->arg_type; } opt_info_used++; } 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); log_assert (opt_table_used == opt_info_used); 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); /* Make the gpgrt option table and the internal option table available. */ gc_component[component].opt_table = opt_table; gc_component[component].options = opt_info; /* Now read the default options. */ argv[0] = "--gpgconf-list"; argv[1] = NULL; err = gnupg_spawn_process (pgmname, argv, NULL, 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) { 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); if (option) { if (option->gpgconf_list) gc_error (1, errno, "option %s returned twice from \"%s --gpgconf-list\"", line, pgmname); option->gpgconf_list = 1; if ((flags & GC_OPT_FLAG_DEFAULT)) option->has_default = 1; if ((flags & GC_OPT_FLAG_DEF_DESC)) option->def_in_desc = 1; if ((flags & GC_OPT_FLAG_NO_ARG_DESC)) option->no_arg_desc = 1; if ((flags & GC_OPT_FLAG_NO_CHANGE)) option->no_change = 1; 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_name = gc_component[component].option_config_filename; if (!config_name) gc_error (1, 0, "name of config file for %s is not known\n", pgmname); if (!gnupg_default_homedir_p ()) { /* This is not the default homedir. We need to take an absolute * config name for the user config file; gpgrt_argparser * fortunately supports this. */ char *tmp = make_filename (gnupg_homedir (), config_name, NULL); twopartconfig_name = xstrconcat (config_name, PATHSEP_S, tmp, NULL); xfree (tmp); config_name = twopartconfig_name; } memset (&pargs, 0, sizeof pargs); dummy_argc = 0; pargs.argc = &dummy_argc; pargs.flags = (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER | ARGPARSE_FLAG_WITHATTR); if (opt.verbose) pargs.flags |= ARGPARSE_FLAG_VERBOSE; while (gpgrt_argparser (&pargs, opt_table, config_name)) { char *opt_value; if (pargs.r_type & ARGPARSE_OPT_IGNORE) { /* log_debug ("ignored\n"); */ continue; } if (pargs.r_opt == ARGPARSE_CONFFILE) { /* log_debug ("current conffile='%s'\n", */ /* pargs.r_type? pargs.r.ret_str: "[cmdline]"); */ continue; } /* We only have the short option. Search in the option table * for the long option name. */ for (i=0; opt_table[i].short_opt; i++) if (opt_table[i].short_opt == pargs.r_opt) break; if (!opt_table[i].short_opt || !opt_table[i].long_opt) continue; /* No or only a short option - ignore. */ /* Look up the option from the config file in our list of * supported options. */ option= find_option (component, opt_table[i].long_opt); if (!option) continue; /* We don't want to handle this option. */ option->attr_ignore = !!(pargs.r_type & ARGPARSE_ATTR_IGNORE); option->attr_force = !!(pargs.r_type & ARGPARSE_ATTR_FORCE); switch ((pargs.r_type & ARGPARSE_TYPE_MASK)) { case ARGPARSE_TYPE_INT: opt_value = xasprintf ("%d", pargs.r.ret_int); break; case ARGPARSE_TYPE_LONG: opt_value = xasprintf ("%ld", pargs.r.ret_long); break; case ARGPARSE_TYPE_ULONG: opt_value = xasprintf ("%lu", pargs.r.ret_ulong); break; case ARGPARSE_TYPE_STRING: opt_value = xasprintf ("\"%s", gc_percent_escape (pargs.r.ret_str)); break; default: /* ARGPARSE_TYPE_NONE or any unknown type. */ opt_value = xstrdup ("1"); /* Make sure we have some value. */ break; } /* Now enter the value read from the config file into the table. */ if (!option->is_list) { xfree (option->value); option->value = opt_value; } else if (!option->value) /* LIST but first item. */ option->value = opt_value; else { char *old = option->value; option->value = xstrconcat (old, ",", opt_value, NULL); xfree (old); xfree (opt_value); } } xfree (line); xfree (twopartconfig_name); } /* Retrieve the currently active options and their defaults for this component. Using -1 for component will retrieve all options from all installed components. */ void gc_component_retrieve_options (int component) { int process_all = 0; if (component == -1) { process_all = 1; component = 0; } do { if (component == GC_COMPONENT_PINENTRY) continue; /* Skip this dummy component. */ if (gc_component[component].program) retrieve_options_from_program (component, process_all); } 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. If VERBATIM is set the profile parsing mode * is used. */ static void option_check_validity (gc_component_id_t component, gc_option_t *option, unsigned long flags, char *new_value, unsigned long *new_value_nr, int verbatim) { char *arg; (void)component; 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->is_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 == ',' && !verbatim)) { if (!option->opt_arg) gc_error (1, 0, "argument required for option %s", option->name); if (*arg == ',' && !verbatim && !option->is_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 != '"' && !verbatim) 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] == ',' && !verbatim)) 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 != ',' || verbatim)) 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 != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } arg = verbatim? strchr (arg, ',') : NULL; 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; gpgrt_stream_t src; gpgrt_stream_t dst; src = gpgrt_fopen (src_name, "r"); if (src == NULL) return -1; dst = gpgrt_fopen (dst_name, "w"); if (dst == NULL) { int saved_err = errno; gpgrt_fclose (src); gpg_err_set_errno (saved_err); return -1; } do { int written; len = gpgrt_fread (buffer, 1, BUF_LEN, src); if (len == 0) break; written = gpgrt_fwrite (buffer, 1, len, dst); if (written != len) break; } while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst)); if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src)) { int saved_errno = errno; gpgrt_fclose (src); gpgrt_fclose (dst); unlink (dst_name); gpg_err_set_errno (saved_errno); return -1; } if (gpgrt_fclose (dst)) gc_error (1, errno, "error closing %s", dst_name); if (gpgrt_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 * component. Returns 0 on success and -1 on error. If * VERBATIM is set the profile mode is used. This function may store * pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and * ORIG_FILENAMEP. Those must be freed by the caller. The strings * refer to three versions of the configuration file: * * SRC_FILENAME: The updated configuration is written to this file. * DEST_FILENAME: Name of the configuration file read by the * component. * ORIG_FILENAME: A backup of the previous configuration file. * * To apply the configuration change, rename SRC_FILENAME to * DEST_FILENAME. To revert to the previous configuration, rename * ORIG_FILENAME to DEST_FILENAME. */ static int change_options_program (gc_component_id_t component, char **src_filenamep, char **dest_filenamep, char **orig_filenamep, int verbatim) { 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; gpgrt_stream_t src_file = NULL; gpgrt_stream_t 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. */ if (!gc_component[component].option_config_filename) gc_error (1, 0, "name of config file for %s is not known\n", gc_component[component].name); dest_filename = make_absfilename (gnupg_homedir (), gc_component[component].option_config_filename, NULL); 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) { xfree (dest_filename); xfree (src_filename); xfree (orig_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 = gpgrt_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 = gpgrt_fopen (dest_filename, "r"); if (!dest_file) goto change_one_err; while ((length = gpgrt_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 (component == GC_COMPONENT_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); *end = saved_end; if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT) || option->new_value)) disable = 1; } if (disable) { if (!in_marker) { gpgrt_fprintf (src_file, "# %s disabled this option here at %s\n", GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# %s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } else { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } if (length < 0 || gpgrt_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. */ gpgrt_fprintf (src_file, "\n%s\n", marker); if (gpgrt_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 (component == GC_COMPONENT_GPG && ! utf8strings_seen) gpgrt_fprintf (src_file, "utf8-strings\n"); option = gc_component[component].options; for ( ; option->name; option++) { if (!option->is_header && option->new_value) { char *arg = option->new_value; do { if (*arg == '\0' || *arg == ',') { gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { log_assert (*arg == '1'); gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; arg++; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { char *end; if (!verbatim) { log_assert (*arg == '"'); arg++; end = strchr (arg, ','); if (end) *end = '\0'; } else end = NULL; gpgrt_fprintf (src_file, "%s %s\n", option->name, verbatim? arg : percent_deescape (arg)); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } else { char *end; end = strchr (arg, ','); if (end) *end = '\0'; gpgrt_fprintf (src_file, "%s %s\n", option->name, arg); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } log_assert (arg == NULL || *arg == '\0' || *arg == ','); if (arg && *arg == ',') arg++; } while (arg && *arg); } } gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; if (!in_marker) { gpgrt_fprintf (src_file, "# %s edited this configuration file.\n", GPGCONF_DISP_NAME); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# never change anything below these lines.\n"); if (gpgrt_ferror (src_file)) goto change_one_err; } if (dest_file) { while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } xfree (line); line = NULL; res = gpgrt_fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } close (fd); if (dest_file) { res = gpgrt_fclose (dest_file); if (res) return -1; } return 0; change_one_err: xfree (line); res = errno; if (src_file) { gpgrt_fclose (src_file); close (fd); } if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } /* Common code for gc_component_change_options and * gc_process_gpgconf_conf. If VERBATIM is set the profile parsing * mode is used. */ static void change_one_value (gc_component_id_t component, gc_option_t *option, int *r_runtime, unsigned long flags, char *new_value, int verbatim) { unsigned long new_value_nr = 0; option_check_validity (component, option, flags, new_value, &new_value_nr, verbatim); if (option->runtime) *r_runtime = 1; option->new_flags = flags; if (!(flags & GC_OPT_FLAG_DEFAULT)) { if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && option->is_list) { char *str; /* We convert the number to a list of 1's for convenient list handling. */ log_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. If VERBATIM is set the profile mode is used. */ void gc_component_change_options (int component, estream_t in, estream_t out, int verbatim) { int err = 0; int block = 0; int runtime = 0; char *src_filename = NULL; char *dest_filename = NULL; char *orig_filename = NULL; 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. */ 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); if (!option) gc_error (1, 0, "unknown option %s", line); if (option->no_change) { gc_error (0, 0, "ignoring new value for option %s", option->name); continue; } change_one_value (component, option, &runtime, flags, new_value, 0); } if (length < 0 || gpgrt_ferror (in)) gc_error (1, errno, "error reading stream 'in'"); } /* 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 there is nothing to do. */ if (src_filename || !(option->new_flags || option->new_value)) { option++; continue; } if (gc_component[component].program) { err = change_options_program (component, &src_filename, &dest_filename, &orig_filename, verbatim); if (! err) { /* External verification. */ err = gc_component_check_options (component, out, src_filename); if (err) { gc_error (0, 0, _("External verification of component %s failed"), gc_component[component].name); gpg_err_set_errno (EINVAL); } } } if (err) break; option++; } /* We are trying to atomically commit all changes. Unfortunately, we cannot rely on gnupg_rename_file to manage the signals for us, doing so would require us to pass NULL as BLOCK to any subsequent call to it. Instead, we just manage the signal handling manually. */ block = 1; gnupg_block_all_signals (); if (!err && !opt.dry_run) { if (src_filename) { /* FIXME: Make a verification here. */ log_assert (dest_filename); if (orig_filename) err = gnupg_rename_file (src_filename, dest_filename, NULL); else { #ifdef HAVE_W32_SYSTEM /* We skip the unlink if we expect the file not to be * there. */ err = gnupg_rename_file (src_filename, dest_filename, NULL); #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, dest_filename); if (!err) err = unlink (src_filename); #endif /* !HAVE_W32_SYSTEM */ } if (!err) { xfree (src_filename); src_filename = NULL; } } } if (err || opt.dry_run) { int saved_errno = errno; /* An error occurred or a dry-run is requested. */ if (src_filename) { /* The change was not yet committed. */ unlink (src_filename); if (orig_filename) unlink (orig_filename); } 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) gnupg_rename_file (orig_filename, dest_filename, NULL); else unlink (dest_filename); } 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) do_runtime_change (component, 0); /* Move the per-process backup file into its place. */ if (orig_filename) { char *backup_filename; log_assert (dest_filename); backup_filename = xasprintf ("%s.%s.bak", dest_filename, GPGCONF_NAME); gnupg_rename_file (orig_filename, backup_filename, NULL); xfree (backup_filename); } leave: if (block) gnupg_unblock_all_signals (); xfree (line); xfree (src_filename); xfree (dest_filename); xfree (orig_filename); } /* Check whether USER matches the current user or 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; gpgrt_stream_t config; int lineno = 0; int in_rule = 0; int got_match = 0; int runtime[GC_COMPONENT_NR] = { 0 }; int component_id; char *fname; if (fname_arg) fname = xstrdup (fname_arg); else fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf", NULL); config = gpgrt_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't open global config file '%s'", fname); result = -1; } xfree (fname); return result; } while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0) { char *key, *compname, *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; gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing rule", GPG_ERR_SYNTAX, fname, lineno); continue; } *p++ = 0; compname = p; } else if (!in_rule) { gc_error (0, 0, "continuation but no rule at '%s', line %d", fname, lineno); result = -1; continue; } else { compname = key; key = NULL; } in_rule = 1; /* Parse the component. */ while (*compname == ' ' || *compname == '\t') compname++; for (p=compname; *p && !strchr (" \t\r\n", *p); p++) ; if (p == compname) { gc_error (0, 0, "missing component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " " missing component", GPG_ERR_NO_NAME, fname, lineno); result = -1; continue; } empty = p; *p++ = 0; option = p; component_id = gc_component_find (compname); if (component_id < 0) { gc_error (0, 0, "unknown component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown component", GPG_ERR_UNKNOWN_NAME, 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); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing option", GPG_ERR_INV_NAME, fname, lineno); result = -1; continue; } *p++ = 0; flags = p; if ( component_id != -1) { /* We need to make sure that we got the option list for the * component. */ if (!gc_component[component_id].options) gc_component_retrieve_options (component_id); option_info = find_option (component_id, option); if (!option_info) { gc_error (0, 0, "unknown option '%s' at '%s', line %d", option, fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown option", GPG_ERR_UNKNOWN_OPTION, 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); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "syntax error in rule", GPG_ERR_SYNTAX, 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->no_change = 1; else if (!strcmp (flags, "change")) option_info->no_change = 0; if (defaults) { /* Here we explicitly allow updating 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 (component_id, option_info, runtime, newflags, value, 0); } } } if (length < 0 || gpgrt_ferror (config)) { gc_error (0, errno, "error reading from '%s'", fname); result = -1; } if (gpgrt_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, 0); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) if (runtime[component_id] && gc_component[component_id].runtime_change) (*gc_component[component_id].runtime_change) (0); } } xfree (fname); return result; } /* * Apply the profile FNAME to all known configure files. */ gpg_error_t gc_apply_profile (const char *fname) { gpg_error_t err; char *fname_buffer = NULL; char *line = NULL; size_t line_len = 0; ssize_t length; estream_t fp; int lineno = 0; int runtime[GC_COMPONENT_NR] = { 0 }; int component_id = -1; int skip_section = 0; int error_count = 0; int newflags; if (!fname) fname = "-"; if (!(!strcmp (fname, "-") || strchr (fname, '/') #ifdef HAVE_W32_SYSTEM || strchr (fname, '\\') #endif || strchr (fname, '.'))) { /* FNAME looks like a standard profile name. Check whether one * is installed and use that instead of the given file name. */ fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S, fname, ".prf", NULL); if (!access (fname_buffer, F_OK)) fname = fname_buffer; } fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); return err; } if (opt.verbose) log_info ("applying profile '%s'\n", fname); err = 0; while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0) { char *name, *flags, *value; gc_option_t *option_info = NULL; char *p; lineno++; name = line; while (*name == ' ' || *name == '\t') name++; if (!*name || *name == '#' || *name == '\r' || *name == '\n') continue; trim_trailing_spaces (name); /* Check whether this is a new section. */ if (*name == '[') { name++; skip_section = 0; /* New section: Get the name of the component. */ p = strchr (name, ']'); if (!p) { error_count++; log_info ("%s:%d:%d: error: syntax error in section tag\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } *p++ = 0; if (*p) log_info ("%s:%d:%d: warning: garbage after section tag\n", fname, lineno, (int)(p - line)); trim_spaces (name); component_id = gc_component_find (name); if (component_id < 0) { log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n", fname, lineno, (int)(name - line), name ); skip_section = 1; } continue; } if (skip_section) continue; if (component_id < 0) { error_count++; log_info ("%s:%d:%d: error: not in a valid section\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } /* Parse the option name. */ for (p = name; *p && !spacep (p); p++) ; *p++ = 0; value = p; option_info = find_option (component_id, name); if (!option_info) { error_count++; log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n", fname, lineno, (int)(name - line), name, gc_component[component_id].name); continue; } /* Parse the optional flags. */ trim_spaces (value); flags = value; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { log_info ("%s:%d:%d: warning: invalid flag specification\n", fname, lineno, (int)(p - line)); continue; } *p++ = 0; value = p; trim_spaces (value); } else /* No flags given. */ flags = NULL; /* Set required defaults. */ if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE && !*value) value = "1"; /* Check and save this option. */ newflags = 0; if (flags && !strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; if (newflags) option_info->new_flags = 0; if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (component_id, option_info, runtime, newflags, value, 1); } if (length < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); error_count++; log_error (_("%s:%u: read error: %s\n"), fname, lineno, gpg_strerror (err)); } if (es_fclose (fp)) log_error (_("error closing '%s'\n"), fname); if (error_count) log_error (_("error parsing '%s'\n"), fname); xfree (line); /* If it all worked, process the options. */ if (!err) { /* 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, 1); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) if (runtime[component_id] && gc_component[component_id].runtime_change) (*gc_component[component_id].runtime_change) (0); } } xfree (fname_buffer); return err; } diff --git a/tools/gpgconf.c b/tools/gpgconf.c index f5f57f577..0b7348d1d 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,963 +1,1131 @@ /* gpgconf.c - Configuration utility for GnuPG * Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc. * Copyright (C) 2016 g10 Code GmbH. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpgconf.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/status.h" +#include "../common/exechelp.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', oRuntime = 'r', oComponent = 'c', oNull = '0', oNoVerbose = 500, oHomedir, oBuilddir, oStatusFD, oShowSocket, oChUid, aListComponents, aCheckPrograms, aListOptions, aChangeOptions, aCheckOptions, aApplyDefaults, aListConfig, aCheckConfig, aQuerySWDB, aListDirs, aLaunch, aKill, aCreateSocketDir, aRemoveSocketDir, aApplyProfile, aReload, + aShowVersions, aShowCodepages }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, { aListComponents, "list-components", 256, N_("list all components") }, { aCheckPrograms, "check-programs", 256, N_("check all programs") }, { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, { aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") }, { aApplyDefaults, "apply-defaults", 256, N_("apply global default values") }, { aApplyProfile, "apply-profile", 256, N_("|FILE|update configuration files using FILE") }, { aListDirs, "list-dirs", 256, N_("get the configuration directories for @GPGCONF@") }, { aListConfig, "list-config", 256, N_("list global configuration file") }, { aCheckConfig, "check-config", 256, N_("check global configuration file") }, { aQuerySWDB, "query-swdb", 256, N_("query the software version database") }, { aReload, "reload", 256, N_("reload all or a given component")}, { aLaunch, "launch", 256, N_("launch a given component")}, { aKill, "kill", 256, N_("kill a given component")}, { aCreateSocketDir, "create-socketdir", 256, "@"}, { aRemoveSocketDir, "remove-socketdir", 256, "@"}, + ARGPARSE_c (aShowVersions, "show-versions", "@"), ARGPARSE_c (aShowCodepages, "show-codepages", "@"), { 301, NULL, 0, N_("@\nOptions:\n ") }, { oOutput, "output", 2, N_("use as output file") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") }, ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), /* hidden options */ { oHomedir, "homedir", 2, "@" }, { oBuilddir, "build-prefix", 2, "@" }, { oNull, "null", 0, "@" }, { oNoVerbose, "no-verbose", 0, "@"}, ARGPARSE_s_n (oShowSocket, "show-socket", "@"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end(), }; /* The stream to output the status information. Status Output is disabled if * this is NULL. */ static estream_t statusfp; +static void show_versions (estream_t fp); + + /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGCONF@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPGCONF@ [options]\n" "Manage configuration options for tools of the @GNUPG@ system\n"); break; default: p = NULL; break; } return p; } /* Return the fp for the output. This is usually stdout unless --output has been used. In the latter case this function opens that file. */ static estream_t get_outfp (estream_t *fp) { if (!*fp) { if (opt.outfile) { *fp = es_fopen (opt.outfile, "w"); if (!*fp) gc_error (1, errno, "can not open '%s'", opt.outfile); } else *fp = es_stdout; } return *fp; } /* Set the status FD. */ static void set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (statusfp && statusfp != es_stdout && statusfp != es_stderr) es_fclose (statusfp); statusfp = NULL; if (fd == -1) return; if (fd == 1) statusfp = es_stdout; else if (fd == 2) statusfp = es_stderr; else statusfp = es_fdopen (fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", fd, gpg_strerror (gpg_error_from_syserror ())); } last_fd = fd; } /* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void gpgconf_write_status (int no, const char *format, ...) { va_list arg_ptr; if (!statusfp) return; /* Not enabled. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if (format) { es_putc (' ', statusfp); va_start (arg_ptr, format); es_vfprintf (statusfp, format, arg_ptr); va_end (arg_ptr); } es_putc ('\n', statusfp); } static void list_dirs (estream_t fp, char **names) { static struct { const char *name; const char *(*fnc)(void); const char *extra; } list[] = { { "sysconfdir", gnupg_sysconfdir, NULL }, { "bindir", gnupg_bindir, NULL }, { "libexecdir", gnupg_libexecdir, NULL }, { "libdir", gnupg_libdir, NULL }, { "datadir", gnupg_datadir, NULL }, { "localedir", gnupg_localedir, NULL }, { "socketdir", gnupg_socketdir, NULL }, { "dirmngr-socket", dirmngr_socket_name, NULL,}, { "keyboxd-socket", keyboxd_socket_name, NULL,}, { "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME }, { "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME }, { "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME }, { "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME }, { "homedir", gnupg_homedir, NULL } }; int idx, j; char *tmp; const char *s; for (idx = 0; idx < DIM (list); idx++) { s = list[idx].fnc (); if (list[idx].extra) { tmp = make_filename (s, list[idx].extra, NULL); s = tmp; } else tmp = NULL; if (!names) es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s)); else { for (j=0; names[j]; j++) if (!strcmp (names[j], list[idx].name)) { es_fputs (s, fp); es_putc (opt.null? '\0':'\n', fp); } } xfree (tmp); } #ifdef HAVE_W32_SYSTEM tmp = read_w32_registry_string (NULL, GNUPG_REGISTRY_DIR, "HomeDir"); if (tmp) { es_fflush (fp); log_info ("Warning: homedir taken from registry key (%s %s)\n", GNUPG_REGISTRY_DIR, "HomeDir"); xfree (tmp); } #endif /*HAVE_W32_SYSTEM*/ } /* Check whether NAME is valid argument for query_swdb(). Valid names * start with a letter and contain only alphanumeric characters or an * underscore. */ static int valid_swdb_name_p (const char *name) { if (!name || !*name || !alphap (name)) return 0; for (name++; *name; name++) if (!alnump (name) && *name != '_') return 0; return 1; } /* Query the SWDB file. If necessary and possible this functions asks * the dirmngr to load an updated version of that file. The caller * needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and * optional the currently installed version in CURRENT_VERSION. The * output written to OUT is a colon delimited line with these fields: * * name :: The name of the package * curvers:: The installed version if given. * status :: This value tells the status of the software package * '-' :: No information available * (error or CURRENT_VERSION not given) * '?' :: Unknown NAME * 'u' :: Update available * 'c' :: The version is Current * 'n' :: The current version is already Newer than the * available one. * urgency :: If the value is greater than zero an urgent update is required. * error :: 0 on success or an gpg_err_code_t * Common codes seen: * GPG_ERR_TOO_OLD :: The SWDB file is to old to be used. * GPG_ERR_ENOENT :: The SWDB file is not available. * GPG_ERR_BAD_SIGNATURE :: Corrupted SWDB file. * filedate:: Date of the swdb file (yyyymmddThhmmss) * verified:: Date we checked the validity of the file (yyyyymmddThhmmss) * version :: The version string from the swdb. * reldate :: Release date of that version (yyyymmddThhmmss) * size :: Size of the package in bytes. * hash :: SHA-2 hash of the package. * */ static void query_swdb (estream_t out, const char *name, const char *current_version) { gpg_error_t err; const char *search_name; char *fname = NULL; estream_t fp = NULL; char *line = NULL; char *self_version = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; const char *fields[2]; char *p; gnupg_isotime_t filedate = {0}; gnupg_isotime_t verified = {0}; char *value_ver = NULL; gnupg_isotime_t value_date = {0}; char *value_size = NULL; char *value_sha2 = NULL; unsigned long value_size_ul = 0; int status, i; if (!valid_swdb_name_p (name)) { log_error ("error in package name '%s': %s\n", name, gpg_strerror (GPG_ERR_INV_NAME)); goto leave; } if (!strcmp (name, "gnupg")) search_name = GNUPG_SWDB_TAG; else if (!strcmp (name, "gnupg1")) search_name = "gnupg1"; else search_name = name; if (!current_version && !strcmp (name, "gnupg")) { /* Use our own version but string a possible beta string. */ self_version = xstrdup (PACKAGE_VERSION); p = strchr (self_version, '-'); if (p) *p = 0; current_version = self_version; } if (current_version && (strchr (current_version, ':') || compare_version_strings (current_version, NULL))) { log_error ("error in version string '%s': %s\n", current_version, gpg_strerror (GPG_ERR_INV_ARG)); goto leave; } fname = make_filename (gnupg_homedir (), "swdb.lst", NULL); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Note that the parser uses the first occurrence of a matching * values and ignores possible duplicated values. */ maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; if (split_fields (line, fields, DIM (fields)) < DIM(fields)) continue; /* Skip empty lines and names w/o a value. */ if (*fields[0] == '#') continue; /* Skip comments. */ /* Record the meta data. */ if (!*filedate && !strcmp (fields[0], ".filedate")) { string2isotime (filedate, fields[1]); continue; } if (!*verified && !strcmp (fields[0], ".verified")) { string2isotime (verified, fields[1]); continue; } /* Tokenize the name. */ p = strrchr (fields[0], '_'); if (!p) continue; /* Name w/o an underscore. */ *p++ = 0; /* Wait for the requested name. */ if (!strcmp (fields[0], search_name)) { if (!strcmp (p, "ver") && !value_ver) value_ver = xstrdup (fields[1]); else if (!strcmp (p, "date") && !*value_date) string2isotime (value_date, fields[1]); else if (!strcmp (p, "size") && !value_size) value_size = xstrdup (fields[1]); else if (!strcmp (p, "sha2") && !value_sha2) value_sha2 = xstrdup (fields[1]); } } if (len < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (!*filedate || !*verified) { err = gpg_error (GPG_ERR_INV_TIME); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); goto leave; } if (!value_ver) { es_fprintf (out, "%s:%s:?:::::::::\n", name, current_version? current_version : ""); goto leave; } if (value_size) { gpg_err_set_errno (0); value_size_ul = strtoul (value_size, &p, 10); if (errno) value_size_ul = 0; else if (*p == 'k') value_size_ul *= 1024; } err = 0; status = '-'; if (compare_version_strings (value_ver, NULL)) err = gpg_error (GPG_ERR_INV_VALUE); else if (!current_version) ; else if (!(i = compare_version_strings (value_ver, current_version))) status = 'c'; else if (i > 0) status = 'u'; else status = 'n'; es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n", name, current_version? current_version : "", status, err, filedate, verified, value_ver, value_date, value_size_ul, value_sha2? value_sha2 : ""); leave: xfree (value_ver); xfree (value_size); xfree (value_sha2); xfree (line); es_fclose (fp); xfree (fname); xfree (self_version); } /* gpgconf main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; const char *fname; int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; int show_socket = 0; const char *changeuser = NULL; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); gpgrt_set_strusage (my_strusage); log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); gc_components_init (); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; while (!no_more_options && gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oRuntime: opt.runtime = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break; case oNull: opt.null = 1; break; case oStatusFD: set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oShowSocket: show_socket = 1; break; case oChUid: changeuser = pargs.r.ret_str; break; case aListDirs: case aListComponents: case aCheckPrograms: case aListOptions: case aChangeOptions: case aCheckOptions: case aApplyDefaults: case aApplyProfile: case aListConfig: case aCheckConfig: case aQuerySWDB: case aReload: case aLaunch: case aKill: case aCreateSocketDir: case aRemoveSocketDir: + case aShowVersions: case aShowCodepages: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) gpgconf_failure (GPG_ERR_USER_2); /* 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]); } fname = argc ? *argv : NULL; /* If requested switch to the requested user or die. */ if (changeuser && (err = gnupg_chuid (changeuser, 0))) gpgconf_failure (err); /* Set the configuraton directories for use by gpgrt_argparser. We * don't have a configuration file for this program but we have code * which reads the component's config files. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); switch (cmd) { case aListComponents: default: /* List all components. */ gc_component_list_components (get_outfp (&outfp)); break; case aCheckPrograms: /* Check all programs. */ gc_check_programs (get_outfp (&outfp)); break; case aListOptions: case aChangeOptions: case aCheckOptions: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } else { int idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } if (cmd == aCheckOptions) gc_component_check_options (idx, get_outfp (&outfp), NULL); else { gc_component_retrieve_options (idx); if (gc_process_gpgconf_conf (NULL, 1, 0, NULL)) gpgconf_failure (0); if (cmd == aListOptions) gc_component_list_options (idx, get_outfp (&outfp)); else if (cmd == aChangeOptions) gc_component_change_options (idx, es_stdin, get_outfp (&outfp), 0); } } break; case aLaunch: case aKill: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } else if (!strcmp (fname, "all")) { if (cmd == aLaunch) { if (gc_component_launch (-1)) gpgconf_failure (0); } else { gc_component_kill (-1); } } else { /* Launch/Kill a given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } else if (cmd == aLaunch) { err = gc_component_launch (idx); if (show_socket) { char *names[2]; if (idx == GC_COMPONENT_GPG_AGENT) names[0] = "agent-socket"; else if (idx == GC_COMPONENT_DIRMNGR) names[0] = "dirmngr-socket"; else if (idx == GC_COMPONENT_KEYBOXD) names[0] = "keyboxd-socket"; else names[0] = NULL; names[1] = NULL; get_outfp (&outfp); list_dirs (outfp, names); } if (err) gpgconf_failure (0); } else { /* We don't error out if the kill failed because this command should do nothing if the component is not running. */ gc_component_kill (idx); } } break; case aReload: if (!fname || !strcmp (fname, "all")) { /* Reload all. */ gc_component_reload (-1); } else { /* Reload given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } else { gc_component_reload (idx); } } break; case aListConfig: if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp))) gpgconf_failure (0); break; case aCheckConfig: if (gc_process_gpgconf_conf (fname, 0, 0, NULL)) gpgconf_failure (0); break; case aApplyDefaults: if (fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("No argument allowed"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } gc_component_retrieve_options (-1); if (gc_process_gpgconf_conf (NULL, 1, 1, NULL)) gpgconf_failure (0); break; case aApplyProfile: gc_component_retrieve_options (-1); if (gc_apply_profile (fname)) gpgconf_failure (0); break; case aListDirs: /* Show the system configuration directories for gpgconf. */ get_outfp (&outfp); list_dirs (outfp, argc? argv : NULL); break; case aQuerySWDB: /* Query the software version database. */ if (!fname || argc > 2) { es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n", GPGCONF_NAME); gpgconf_failure (GPG_ERR_USER_2); } get_outfp (&outfp); query_swdb (outfp, fname, argc > 1? argv[1] : NULL); break; case aCreateSocketDir: { char *socketdir; unsigned int flags; /* Make sure that the top /run/user/UID/gnupg dir has been * created. */ gnupg_socketdir (); /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 64) && !opt.dry_run) { /* No sub dir - create it. */ if (gnupg_mkdir (socketdir, "-rwx")) gc_error (1, errno, "error creating '%s'", socketdir); /* Try again. */ xfree (socketdir); socketdir = _gnupg_socketdir_internal (1, &flags); } /* Give some info. */ if ( (flags & ~32) || opt.verbose || opt.dry_run) { log_info ("socketdir is '%s'\n", socketdir); if ((flags & 1)) log_info ("\tgeneral error\n"); if ((flags & 2)) log_info ("\tno /run/user dir\n"); if ((flags & 4)) log_info ("\tbad permissions\n"); if ((flags & 8)) log_info ("\tbad permissions (subdir)\n"); if ((flags & 16)) log_info ("\tmkdir failed\n"); if ((flags & 32)) log_info ("\tnon-default homedir\n"); if ((flags & 64)) log_info ("\tno such subdir\n"); if ((flags & 128)) log_info ("\tusing homedir as fallback\n"); } if ((flags & ~32) && !opt.dry_run) gc_error (1, 0, "error creating socket directory"); xfree (socketdir); } break; case aRemoveSocketDir: { char *socketdir; unsigned int flags; /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 128)) log_info ("ignoring request to remove non /run/user socket dir\n"); else if (opt.dry_run) ; else if (rmdir (socketdir)) { /* If the director is not empty we first try to delete * socket files. */ err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY || gpg_err_code (err) == GPG_ERR_EEXIST) { static const char * const names[] = { GPG_AGENT_SOCK_NAME, GPG_AGENT_EXTRA_SOCK_NAME, GPG_AGENT_BROWSER_SOCK_NAME, GPG_AGENT_SSH_SOCK_NAME, SCDAEMON_SOCK_NAME, KEYBOXD_SOCK_NAME, DIRMNGR_SOCK_NAME }; int i; char *p; for (i=0; i < DIM(names); i++) { p = strconcat (socketdir , "/", names[i], NULL); if (p) gnupg_remove (p); xfree (p); } if (rmdir (socketdir)) gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } else if (gpg_err_code (err) == GPG_ERR_ENOENT) gc_error (0, 0, "warning: removing '%s' failed: %s", socketdir, gpg_strerror (err)); else gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } xfree (socketdir); } break; + case aShowVersions: + { + get_outfp (&outfp); + show_versions (outfp); + } + break; + case aShowCodepages: #ifdef HAVE_W32_SYSTEM { get_outfp (&outfp); es_fprintf (outfp, "Console: CP%u\n", GetConsoleOutputCP ()); es_fprintf (outfp, "ANSI: CP%u\n", GetACP ()); es_fprintf (outfp, "OEM: CP%u\n", GetOEMCP ()); } #endif break; } if (outfp != es_stdout) if (es_fclose (outfp)) gc_error (1, errno, "error closing '%s'", opt.outfile); if (log_get_errorcount (0)) gpgconf_failure (0); else gpgconf_write_status (STATUS_SUCCESS, NULL); return 0; } void gpgconf_failure (gpg_error_t err) { log_flush (); if (!err) err = gpg_error (GPG_ERR_GENERAL); gpgconf_write_status (STATUS_FAILURE, "- %u", gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err); exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1); } + + + +/* Parse the revision part from the extended version blurb. */ +static const char * +get_revision_from_blurb (const char *blurb, int *r_len) +{ + const char *s = blurb? blurb : ""; + int n; + + for (; *s; s++) + if (*s == '\n' && s[1] == '(') + break; + if (s) + { + s += 2; + for (n=0; s[n] && s[n] != ' '; n++) + ; + } + else + { + s = "?"; + n = 1; + } + *r_len = n; + return s; +} + + +static void +show_version_gnupg (estream_t fp) +{ + es_fprintf (fp, "* GnuPG %s (%s)\n%s\n", + gpgrt_strusage (13), BUILD_REVISION, gpgrt_strusage (17)); +#ifdef HAVE_W32_SYSTEM + { + OSVERSIONINFO osvi = { sizeof (osvi) }; + + GetVersionEx (&osvi); + es_fprintf (fp, "Windows %lu.%lu build %lu%s%s%s\n", + (unsigned long)osvi.dwMajorVersion, + (unsigned long)osvi.dwMinorVersion, + (unsigned long)osvi.dwBuildNumber, + *osvi.szCSDVersion? " (":"", + osvi.szCSDVersion, + *osvi.szCSDVersion? ")":"" + ); + } +#endif /*HAVE_W32_SYSTEM*/ +} + + +static void +show_version_libgcrypt (estream_t fp) +{ + const char *s; + int n; + + s = get_revision_from_blurb (gcry_check_version ("\x01\x01"), &n); + es_fprintf (fp, "* Libgcrypt %s (%.*s)\n", + gcry_check_version (NULL), n, s); +#if GCRYPT_VERSION_NUMBER >= 0x010800 + s = gcry_get_config (0, NULL); + if (s) + es_fputs (s, fp); +#endif +} + + +static void +show_version_gpgrt (estream_t fp) +{ + const char *s; + int n; + + s = get_revision_from_blurb (gpg_error_check_version ("\x01\x01"), &n); + es_fprintf (fp, "* GpgRT %s (%.*s)\n", + gpg_error_check_version (NULL), n, s); +} + + +/* Printing version information for other libraries is problematic + * because we don't want to link gpgconf to all these libraries. The + * best solution is delegating this to dirmngr which uses libassuan, + * libksba, libnpth and ntbtls anyway. */ +static void +show_versions_via_dirmngr (estream_t fp) +{ + gpg_error_t err; + const char *pgmname; + const char *argv[2]; + estream_t outfp; + pid_t pid; + char *line = NULL; + size_t line_len = 0; + ssize_t length; + int exitcode; + + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR); + argv[0] = "--gpgconf-versions"; + argv[1] = NULL; + err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, + NULL, &outfp, NULL, &pid); + if (err) + { + log_error ("error spawning %s: %s", pgmname, gpg_strerror (err)); + es_fprintf (fp, "[error: can't get further info]\n"); + return; + } + + while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) + { + /* Strip newline and carriage return, if present. */ + while (length > 0 + && (line[length - 1] == '\n' || line[length - 1] == '\r')) + line[--length] = '\0'; + es_fprintf (fp, "%s\n", line); + } + if (length < 0 || es_ferror (outfp)) + { + err = gpg_error_from_syserror (); + log_error ("error reading from %s: %s\n", pgmname, gpg_strerror (err)); + } + if (es_fclose (outfp)) + { + err = gpg_error_from_syserror (); + log_error ("error closing output stream of %s: %s\n", + pgmname, gpg_strerror (err)); + } + + err = gnupg_wait_process (pgmname, pid, 1, &exitcode); + if (err) + { + log_error ("running %s failed (exitcode=%d): %s\n", + pgmname, exitcode, gpg_strerror (err)); + es_fprintf (fp, "[error: can't get further info]\n"); + } + gnupg_release_process (pid); + xfree (line); +} + + +/* Show all kind of version information. */ +static void +show_versions (estream_t fp) +{ + show_version_gnupg (fp); + es_fputc ('\n', fp); + show_version_libgcrypt (fp); + es_fputc ('\n', fp); + show_version_gpgrt (fp); + es_fputc ('\n', fp); + show_versions_via_dirmngr (fp); +}