diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 4678cf359..d5fe939db 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1,3276 +1,3280 @@ /* gpg-agent.c - The GnuPG Agent * Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc. * Copyright (C) 2000-2016 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #include #include #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # ifndef WINVER # define WINVER 0x0500 /* Same as in common/sysutils.c */ # endif # ifdef HAVE_WINSOCK2_H # include # endif # include # include #else /*!HAVE_W32_SYSTEM*/ # include # include #endif /*!HAVE_W32_SYSTEM*/ #include #ifdef HAVE_SIGNAL_H # include #endif #include #define INCLUDED_BY_MAIN_MODULE 1 #define GNUPG_COMMON_NEED_AFLOCAL #include "agent.h" #include /* Malloc hooks and socket wrappers. */ +#include "../common/argparse.h" /* temporary hack. */ #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/gc-opt-flags.h" #include "../common/exechelp.h" #include "../common/asshelp.h" #include "../common/init.h" enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aGPGConfList, aGPGConfTest, aUseStandardSocketP, oOptions, oDebug, oDebugAll, oDebugLevel, oDebugWait, oDebugQuickRandom, oDebugPinentry, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oGrab, oNoGrab, oLogFile, oServer, oDaemon, oSupervised, oBatch, oPinentryProgram, oPinentryTouchFile, oPinentryInvisibleChar, oPinentryTimeout, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oScdaemonProgram, oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, oMaxCacheTTLSSH, oEnforcePassphraseConstraints, oMinPassphraseLen, oMinPassphraseNonalpha, oCheckPassphrasePattern, oMaxPassphraseDays, oEnablePassphraseHistory, oDisableExtendedKeyFormat, oEnableExtendedKeyFormat, oUseStandardSocket, oNoUseStandardSocket, oExtraSocket, oBrowserSocket, oFakedSystemTime, oIgnoreCacheForSigning, oAllowMarkTrusted, oNoAllowMarkTrusted, oAllowPresetPassphrase, oAllowLoopbackPinentry, oNoAllowLoopbackPinentry, oNoAllowExternalCache, oAllowEmacsPinentry, oKeepTTY, oKeepDISPLAY, oSSHSupport, oSSHFingerprintDigest, oPuttySupport, oDisableScdaemon, oDisableCheckOwnSocket, oS2KCount, oS2KCalibration, oAutoExpandSecmem, oListenBacklog, oWriteEnvFile, oNoop }; #ifndef ENAMETOOLONG # define ENAMETOOLONG EINVAL #endif static ARGPARSE_OPTS opts[] = { ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"), ARGPARSE_group (301, N_("@Options:\n ")), ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")), #ifndef HAVE_W32_SYSTEM ARGPARSE_s_n (oSupervised, "supervised", N_("run in supervised mode")), #endif ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"), ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_n (oGrab, "grab", "@"), /* FIXME: Add the below string for 2.3 */ /* N_("let PIN-Entry grab keyboard and mouse")), */ ARGPARSE_s_n (oNoGrab, "no-grab", "@"), ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")), ARGPARSE_s_s (oPinentryProgram, "pinentry-program", /* */ N_("|PGM|use PGM as the PIN-Entry program")), ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"), ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"), ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout", "@"), ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program", /* */ N_("|PGM|use PGM as the SCdaemon program") ), ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon", /* */ N_("do not use the SCdaemon") ), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oExtraSocket, "extra-socket", /* */ N_("|NAME|accept some commands via NAME")), ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_n (oBatch, "batch", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_n (oKeepTTY, "keep-tty", /* */ N_("ignore requests to change the TTY")), ARGPARSE_s_n (oKeepDISPLAY, "keep-display", /* */ N_("ignore requests to change the X display")), ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl", N_("|N|expire cached PINs after N seconds")), ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", "@" ), ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl", "@" ), ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh", "@" ), ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints", /* */ "@"), ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len", "@"), ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha", "@"), ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern", "@"), ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days", "@"), ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", "@"), ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing", /* */ N_("do not use the PIN cache when signing")), ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache", /* */ N_("disallow the use of an external password cache")), ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted", /* */ N_("disallow clients to mark keys as \"trusted\"")), ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"), ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase", /* */ N_("allow presetting passphrase")), ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry", N_("disallow caller to override the pinentry")), ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"), ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry", /* */ N_("allow passphrase to be prompted through Emacs")), ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")), ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest", N_("|ALGO|use ALGO to show ssh fingerprints")), ARGPARSE_s_n (oPuttySupport, "enable-putty-support", #ifdef HAVE_W32_SYSTEM /* */ N_("enable putty support") #else /* */ "@" #endif ), ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"), ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), ARGPARSE_s_u (oS2KCount, "s2k-count", "@"), ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"), ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"), ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), /* Dummy options for backward compatibility. */ ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"), ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"), ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"), /* Dummy options. */ ARGPARSE_end () /* End of list */ }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { 77, NULL } /* 77 := Do not exit on "help" or "?". */ }; #define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ #define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */ #define MAX_CACHE_TTL (120*60) /* 2 hours */ #define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */ #define MIN_PASSPHRASE_LEN (8) #define MIN_PASSPHRASE_NONALPHA (1) #define MAX_PASSPHRASE_DAYS (0) /* The timer tick used for housekeeping stuff. Note that on Windows * we use a SetWaitableTimer seems to signal earlier than about 2 * seconds. Thus we use 4 seconds on all platforms except for * Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check * our own socket in standard socket mode. If that value is 0 we * don't check at all. All values are in seconds. */ #if defined(HAVE_W32CE_SYSTEM) # define TIMERTICK_INTERVAL (60) # define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */ #else # define TIMERTICK_INTERVAL (4) # define CHECK_OWN_SOCKET_INTERVAL (60) #endif /* Flag indicating that the ssh-agent subsystem has been enabled. */ static int ssh_support; #ifdef HAVE_W32_SYSTEM /* Flag indicating that support for Putty has been enabled. */ static int putty_support; /* A magic value used with WM_COPYDATA. */ #define PUTTY_IPC_MAGIC 0x804e50ba /* To avoid surprises we limit the size of the mapped IPC file to this value. Putty currently (0.62) uses 8k, thus 16k should be enough for the foreseeable future. */ #define PUTTY_IPC_MAXLEN 16384 #endif /*HAVE_W32_SYSTEM*/ /* The list of open file descriptors at startup. Note that this list * has been allocated using the standard malloc. */ #ifndef HAVE_W32_SYSTEM static int *startup_fd_list; #endif /* The signal mask at startup and a flag telling whether it is valid. */ #ifdef HAVE_SIGPROCMASK static sigset_t startup_signal_mask; static int startup_signal_mask_valid; #endif /* Flag to indicate that a shutdown was requested. */ static int shutdown_pending; /* Counter for the currently running own socket checks. */ static int check_own_socket_running; /* Flags to indicate that check_own_socket shall not be called. */ static int disable_check_own_socket; /* Flag indicating that we are in supervised mode. */ static int is_supervised; /* Flag to inhibit socket removal in cleanup. */ static int inhibit_socket_removal; /* It is possible that we are currently running under setuid permissions */ static int maybe_setuid = 1; /* Name of the communication socket used for native gpg-agent requests. The second variable is either NULL or a malloced string with the real socket name in case it has been redirected. */ static char *socket_name; static char *redir_socket_name; /* Name of the optional extra socket used for native gpg-agent requests. */ static char *socket_name_extra; static char *redir_socket_name_extra; /* Name of the optional browser socket used for native gpg-agent requests. */ static char *socket_name_browser; static char *redir_socket_name_browser; /* Name of the communication socket used for ssh-agent protocol. */ static char *socket_name_ssh; static char *redir_socket_name_ssh; /* We need to keep track of the server's nonces (these are dummies for POSIX systems). */ static assuan_sock_nonce_t socket_nonce; static assuan_sock_nonce_t socket_nonce_extra; static assuan_sock_nonce_t socket_nonce_browser; static assuan_sock_nonce_t socket_nonce_ssh; /* Value for the listen() backlog argument. We use the same value for * all sockets - 64 is on current Linux half of the default maximum. * Let's try this as default. Change at runtime with --listen-backlog. */ static int listen_backlog = 64; /* Default values for options passed to the pinentry. */ static char *default_display; static char *default_ttyname; static char *default_ttytype; static char *default_lc_ctype; static char *default_lc_messages; static char *default_xauthority; /* Name of a config file, which will be reread on a HUP if it is not NULL. */ static char *config_filename; /* Helper to implement --debug-level */ static const char *debug_level; /* 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; /* The handle_tick() function may test whether a parent is still * running. We record the PID of the parent here or -1 if it should * be watched. */ static pid_t parent_pid = (pid_t)(-1); /* This flag is true if the inotify mechanism for detecting the * removal of the homedir is active. This flag is used to disable the * alternative but portable stat based check. */ static int have_homedir_inotify; /* Depending on how gpg-agent was started, the homedir inotify watch * may not be reliable. This flag is set if we assume that inotify * works reliable. */ static int reliable_homedir_inotify; /* Number of active connections. */ static int active_connections; /* This object is used to dispatch progress messages from Libgcrypt to * the right thread. Given that we will have at max only a few dozen * connections at a time, using a linked list is the easiest way to * handle this. */ struct progress_dispatch_s { struct progress_dispatch_s *next; /* The control object of the connection. If this is NULL no * connection is associated with this item and it is free for reuse * by new connections. */ ctrl_t ctrl; /* The thread id of (npth_self) of the connection. */ npth_t tid; /* The callback set by the connection. This is similar to the * Libgcrypt callback but with the control object passed as the * first argument. */ void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total); }; struct progress_dispatch_s *progress_dispatch_list; /* Local prototypes. */ static char *create_socket_name (char *standard_name, int with_homedir); static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin, char **r_redir_name, assuan_sock_nonce_t *nonce); static void create_directories (void); static void agent_libgcrypt_progress_cb (void *data, const char *what, int printchar, int current, int total); static void agent_init_default_ctrl (ctrl_t ctrl); static void agent_deinit_default_ctrl (ctrl_t ctrl); static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, gnupg_fd_t listen_fd_ssh); static void check_own_socket (void); static int check_for_running_agent (int silent); /* Pth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; /* Functions. */ /* Allocate a string describing a library version by calling a GETFNC. This function is expected to be called only once. GETFNC is expected to have a semantic like gcry_check_version (). */ static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; if (maybe_setuid) { gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ maybe_setuid = 0; } s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } /* Return strings describing this program. The case values are described in common/argparse.c:strusage. The values here override the default values given by strusage. */ static const char * my_strusage (int level) { static char *ver_gcry; const char *p; switch (level) { case 11: p = "@GPG_AGENT@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; case 1: case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n" "Secret key management for @GNUPG@\n"); break; default: p = NULL; } return p; } /* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. Note that we don't fail here, because it is important to keep gpg-agent running even after re-reading the options due to a SIGHUP. */ 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; else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE | DBG_CACHE_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); opt.debug = 0; /* Reset debugging, so that prior debug statements won't have an undesired effect. */ } if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } /* Helper for cleanup to remove one socket with NAME. REDIR_NAME is the corresponding real name if the socket has been redirected. */ static void remove_socket (char *name, char *redir_name) { if (name && *name) { if (redir_name) name = redir_name; gnupg_remove (name); *name = 0; } } /* Discover which inherited file descriptors correspond to which * services/sockets offered by gpg-agent, using the LISTEN_FDS and * LISTEN_FDNAMES convention. The understood labels are "ssh", * "extra", and "browser". "std" or other labels will be interpreted * as the standard socket. * * This function is designed to log errors when the expected file * descriptors don't make sense, but to do its best to continue to * work even in the face of minor misconfigurations. * * For more information on the LISTEN_FDS convention, see * sd_listen_fds(3) on certain Linux distributions. */ #ifndef HAVE_W32_SYSTEM static void map_supervised_sockets (gnupg_fd_t *r_fd, gnupg_fd_t *r_fd_extra, gnupg_fd_t *r_fd_browser, gnupg_fd_t *r_fd_ssh) { struct { const char *label; int **fdaddr; char **nameaddr; } tbl[] = { { "ssh", &r_fd_ssh, &socket_name_ssh }, { "browser", &r_fd_browser, &socket_name_browser }, { "extra", &r_fd_extra, &socket_name_extra }, { "std", &r_fd, &socket_name } /* (Must be the last item.) */ }; const char *envvar; char **fdnames; int nfdnames; int fd_count; *r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1; /* Print a warning if LISTEN_PID does not match outr pid. */ envvar = getenv ("LISTEN_PID"); if (!envvar) log_error ("no LISTEN_PID environment variable found in " "--supervised mode (ignoring)\n"); else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ()) log_error ("environment variable LISTEN_PID (%lu) does not match" " our pid (%lu) in --supervised mode (ignoring)\n", (unsigned long)strtoul (envvar, NULL, 10), (unsigned long)getpid ()); /* Parse LISTEN_FDNAMES into the array FDNAMES. */ envvar = getenv ("LISTEN_FDNAMES"); if (envvar) { fdnames = strtokenize (envvar, ":"); if (!fdnames) { log_error ("strtokenize failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); agent_exit (1); } for (nfdnames=0; fdnames[nfdnames]; nfdnames++) ; } else { fdnames = NULL; nfdnames = 0; } /* Parse LISTEN_FDS into fd_count or provide a replacement. */ envvar = getenv ("LISTEN_FDS"); if (envvar) fd_count = atoi (envvar); else if (fdnames) { log_error ("no LISTEN_FDS environment variable found in --supervised" " mode (relying on LISTEN_FDNAMES instead)\n"); fd_count = nfdnames; } else { log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables " "found in --supervised mode" " (assuming 1 active descriptor)\n"); fd_count = 1; } if (fd_count < 1) { log_error ("--supervised mode expects at least one file descriptor" " (was told %d, carrying on as though it were 1)\n", fd_count); fd_count = 1; } /* Assign the descriptors to the return values. */ if (!fdnames) { struct stat statbuf; if (fd_count != 1) log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1" " in --supervised mode." " (ignoring all sockets but the first one)\n", fd_count); if (fstat (3, &statbuf) == -1 && errno ==EBADF) log_fatal ("file descriptor 3 must be valid in --supervised mode" " if LISTEN_FDNAMES is not set\n"); *r_fd = 3; socket_name = gnupg_get_socket_name (3); } else if (fd_count != nfdnames) { log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match " "LISTEN_FDS (%d) in --supervised mode\n", nfdnames, fd_count); } else { int i, j, fd; char *name; for (i = 0; i < nfdnames; i++) { for (j = 0; j < DIM (tbl); j++) { if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1) { fd = 3 + i; if (**tbl[j].fdaddr == -1) { name = gnupg_get_socket_name (fd); if (name) { **tbl[j].fdaddr = fd; *tbl[j].nameaddr = name; log_info ("using fd %d for %s socket (%s)\n", fd, tbl[j].label, name); } else { log_error ("cannot listen on fd %d for %s socket\n", fd, tbl[j].label); close (fd); } } else { log_error ("cannot listen on more than one %s socket\n", tbl[j].label); close (fd); } break; } } } } xfree (fdnames); } #endif /*!HAVE_W32_SYSTEM*/ /* Cleanup code for this program. This is either called has an atexit handler or directly. */ static void cleanup (void) { static int done; if (done) return; done = 1; deinitialize_module_cache (); if (!is_supervised && !inhibit_socket_removal) { remove_socket (socket_name, redir_socket_name); if (opt.extra_socket > 1) remove_socket (socket_name_extra, redir_socket_name_extra); if (opt.browser_socket > 1) remove_socket (socket_name_browser, redir_socket_name_browser); remove_socket (socket_name_ssh, redir_socket_name_ssh); } } /* Handle options which are allowed to be reset after program start. Return true when the current option in PARGS could be handled and false if not. As a special feature, passing a value of NULL for PARGS, resets the options to the default. REREAD should be set true if it is not the initial option parsing. */ static int parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) { int i; if (!pargs) { /* reset mode */ opt.quiet = 0; opt.verbose = 0; opt.debug = 0; opt.no_grab = 1; opt.debug_pinentry = 0; opt.pinentry_program = NULL; opt.pinentry_touch_file = NULL; xfree (opt.pinentry_invisible_char); opt.pinentry_invisible_char = NULL; opt.pinentry_timeout = 0; opt.scdaemon_program = NULL; opt.def_cache_ttl = DEFAULT_CACHE_TTL; opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; opt.max_cache_ttl = MAX_CACHE_TTL; opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH; opt.enforce_passphrase_constraints = 0; opt.min_passphrase_len = MIN_PASSPHRASE_LEN; opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA; opt.check_passphrase_pattern = NULL; opt.max_passphrase_days = MAX_PASSPHRASE_DAYS; opt.enable_passphrase_history = 0; opt.enable_extended_key_format = 1; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 1; opt.allow_external_cache = 1; opt.allow_loopback_pinentry = 1; opt.allow_emacs_pinentry = 0; opt.disable_scdaemon = 0; disable_check_own_socket = 0; /* Note: When changing the next line, change also gpgconf_list. */ opt.ssh_fingerprint_digest = GCRY_MD_MD5; opt.s2k_count = 0; set_s2k_calibration_time (0); /* Set to default. */ 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 oDebugPinentry: opt.debug_pinentry = 1; 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 oNoGrab: opt.no_grab |= 1; break; case oGrab: opt.no_grab |= 2; break; case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break; case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break; case oPinentryInvisibleChar: xfree (opt.pinentry_invisible_char); opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; break; case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break; case oDisableScdaemon: opt.disable_scdaemon = 1; break; case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break; case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break; case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break; case oEnforcePassphraseConstraints: opt.enforce_passphrase_constraints=1; break; case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break; case oMinPassphraseNonalpha: opt.min_passphrase_nonalpha = pargs->r.ret_ulong; break; case oCheckPassphrasePattern: opt.check_passphrase_pattern = pargs->r.ret_str; break; case oMaxPassphraseDays: opt.max_passphrase_days = pargs->r.ret_ulong; break; case oEnablePassphraseHistory: opt.enable_passphrase_history = 1; break; case oEnableExtendedKeyFormat: opt.enable_extended_key_format = 2; break; case oDisableExtendedKeyFormat: if (opt.enable_extended_key_format != 2) opt.enable_extended_key_format = 0; break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break; case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break; case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break; case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break; case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break; case oNoAllowExternalCache: opt.allow_external_cache = 0; break; case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1; break; case oSSHFingerprintDigest: i = gcry_md_map_name (pargs->r.ret_str); if (!i) log_error (_("selected digest algorithm is invalid\n")); else opt.ssh_fingerprint_digest = i; break; case oS2KCount: opt.s2k_count = pargs->r.ret_ulong; break; case oS2KCalibration: set_s2k_calibration_time (pargs->r.ret_ulong); break; case oNoop: break; default: return 0; /* not handled */ } return 1; /* handled */ } /* Fixup some options after all have been processed. */ static void finalize_rereadable_options (void) { /* Hack to allow --grab to override --no-grab. */ if ((opt.no_grab & 2)) opt.no_grab = 0; } static void thread_init_once (void) { static int npth_initialized = 0; if (!npth_initialized) { npth_initialized++; npth_init (); } gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* Now that we have set the syscall clamp we need to tell Libgcrypt * that it should get them from libgpg-error. Note that Libgcrypt * has already been initialized but at that point nPth was not * initialized and thus Libgcrypt could not set its system call * clamp. */ #if GCRYPT_VERSION_NUMBER >= 0x010800 /* 1.8.0 */ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); #endif } static void initialize_modules (void) { thread_init_once (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); initialize_module_cache (); initialize_module_call_pinentry (); initialize_module_call_scd (); initialize_module_trustlist (); } /* The main entry point. */ int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; const char *shell; unsigned configlineno; int parse_debug = 0; int default_config =1; int pipe_server = 0; int is_daemon = 0; int nodetach = 0; int csh_style = 0; char *logfile = NULL; int debug_wait = 0; int gpgconf_list = 0; gpg_error_t err; struct assuan_malloc_hooks malloc_hooks; early_system_init (); /* Before we do anything else we save the list of currently open file descriptors and the signal mask. This info is required to do the exec call properly. We don't need it on Windows. */ #ifndef HAVE_W32_SYSTEM startup_fd_list = get_all_open_fds (); #endif /*!HAVE_W32_SYSTEM*/ #ifdef HAVE_SIGPROCMASK if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask)) startup_signal_mask_valid = 1; #endif /*HAVE_SIGPROCMASK*/ /* Set program name etc. */ set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to INIT_SECMEM() somewhere after the option parsing */ log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_sock_init (); assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH); setup_libassuan_logging (&opt.debug, NULL); setup_libgcrypt_logging (); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL); disable_core_dumps (); /* Set default options. */ parse_rereadable_options (NULL, 0); /* Reset them to default values. */ shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; /* Record some of the original environment strings. */ { const char *s; int idx; static const char *names[] = { "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL }; err = 0; opt.startup_env = session_env_new (); if (!opt.startup_env) err = gpg_error_from_syserror (); for (idx=0; !err && names[idx]; idx++) { s = getenv (names[idx]); if (s) err = session_env_setenv (opt.startup_env, names[idx], s); } if (!err) { s = gnupg_ttyname (0); if (s) err = session_env_setenv (opt.startup_env, "GPG_TTY", s); } if (err) log_fatal ("error recording startup environment: %s\n", gpg_strerror (err)); /* Fixme: Better use the locale function here. */ opt.startup_lc_ctype = getenv ("LC_CTYPE"); if (opt.startup_lc_ctype) opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype); opt.startup_lc_messages = getenv ("LC_MESSAGES"); if (opt.startup_lc_messages) opt.startup_lc_messages = xstrdup (opt.startup_lc_messages); } /* Check whether we have a config file on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) parse_debug++; else if (pargs.r_opt == oOptions) { /* yes there is one, so we do not try the default one, but read the option file when it is encountered at the commandline */ default_config = 0; } else if (pargs.r_opt == oNoOptions) default_config = 0; /* --no-options */ else if (pargs.r_opt == oHomedir) gnupg_set_homedir (pargs.r.ret_str); else if (pargs.r_opt == oDebugQuickRandom) { gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); } } /* Initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0); maybe_setuid = 0; /* Now we are now working under our real uid */ if (default_config) configname = make_filename (gnupg_homedir (), GPG_AGENT_NAME EXTSEP_S "conf", NULL); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* do not remove the args */ next_pass: if (configname) { configlineno = 0; configfp = fopen (configname, "r"); if (!configfp) { if (default_config) { if( parse_debug ) log_info (_("Note: no default option file '%s'\n"), configname ); /* Save the default conf file name so that reread_configuration is able to test whether the config file has been created in the meantime. */ xfree (config_filename); config_filename = configname; configname = NULL; } else { log_error (_("option file '%s': %s\n"), configname, strerror(errno) ); exit(2); } xfree (configname); configname = NULL; } if (parse_debug && configname ) log_info (_("reading options from '%s'\n"), configname ); default_config = 0; } while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) { if (parse_rereadable_options (&pargs, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case aGPGConfList: gpgconf_list = 1; break; case aGPGConfTest: gpgconf_list = 2; break; case aUseStandardSocketP: gpgconf_list = 3; break; case oBatch: opt.batch=1; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oOptions: /* config files may not be nested (silently ignore them) */ if (!configfp) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoGreeting: /* Dummy option. */ break; case oNoVerbose: opt.verbose = 0; break; case oNoOptions: break; /* no-options */ case oHomedir: gnupg_set_homedir (pargs.r.ret_str); 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 oServer: pipe_server = 1; break; case oDaemon: is_daemon = 1; break; case oSupervised: is_supervised = 1; break; case oDisplay: default_display = xstrdup (pargs.r.ret_str); break; case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break; case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break; case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break; case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str); break; case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str); break; case oUseStandardSocket: case oNoUseStandardSocket: obsolete_option (configname, configlineno, "use-standard-socket"); break; case oFakedSystemTime: { time_t faked_time = isotime2epoch (pargs.r.ret_str); if (faked_time == (time_t)(-1)) faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); gnupg_set_time (faked_time, 0); } break; case oKeepTTY: opt.keep_tty = 1; break; case oKeepDISPLAY: opt.keep_display = 1; break; case oSSHSupport: ssh_support = 1; break; case oPuttySupport: # ifdef HAVE_W32_SYSTEM putty_support = 1; # endif break; case oExtraSocket: opt.extra_socket = 1; /* (1 = points into argv) */ socket_name_extra = pargs.r.ret_str; break; case oBrowserSocket: opt.browser_socket = 1; /* (1 = points into argv) */ socket_name_browser = pargs.r.ret_str; break; case oAutoExpandSecmem: /* Try to enable this option. It will officially only be * supported by Libgcrypt 1.9 but 1.8.2 already supports it * on the quiet and thus we use the numeric value value. */ gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/, (unsigned int)pargs.r.ret_ulong, 0); break; case oListenBacklog: listen_backlog = pargs.r.ret_int; break; case oDebugQuickRandom: /* Only used by the first stage command line parser. */ break; case oWriteEnvFile: obsolete_option (configname, configlineno, "write-env-file"); break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose( configfp ); configfp = NULL; /* Keep a copy of the name so that it can be read on SIGHUP. */ if (config_filename != configname) { xfree (config_filename); config_filename = configname; } configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (log_get_errorcount(0)) exit(2); finalize_rereadable_options (); /* 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]); } #ifdef ENABLE_NLS /* gpg-agent usually does not output any messages because it runs in the background. For log files it is acceptable to have messages always encoded in utf-8. We switch here to utf-8, so that commands like --help still give native messages. It is far easier to switch only once instead of for every message and it actually helps when more then one thread is active (avoids an extra copy step). */ bind_textdomain_codeset (PACKAGE_GT, "UTF-8"); #endif if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised) { /* We have been called without any command and thus we merely check whether an agent is already running. We do this right here so that we don't clobber a logfile with this check but print the status directly to stderr. */ opt.debug = 0; set_debug (); check_for_running_agent (0); agent_exit (0); } if (is_supervised) ; else if (!opt.extra_socket) opt.extra_socket = 1; else if (socket_name_extra && (!strcmp (socket_name_extra, "none") || !strcmp (socket_name_extra, "/dev/null"))) { /* User requested not to create this socket. */ opt.extra_socket = 0; socket_name_extra = NULL; } if (is_supervised) ; else if (!opt.browser_socket) opt.browser_socket = 1; else if (socket_name_browser && (!strcmp (socket_name_browser, "none") || !strcmp (socket_name_browser, "/dev/null"))) { /* User requested not to create this socket. */ opt.browser_socket = 0; socket_name_browser = NULL; } set_debug (); if (atexit (cleanup)) { log_error ("atexit failed\n"); cleanup (); exit (1); } /* Try to create missing directories. */ create_directories (); if (debug_wait && pipe_server) { thread_init_once (); log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } if (gpgconf_list == 3) { /* We now use the standard socket always - return true for backward compatibility. */ agent_exit (0); } else if (gpgconf_list == 2) agent_exit (0); else if (gpgconf_list) { char *filename; char *filename_esc; /* List options and default values in the GPG Conf format. */ filename = make_filename (gnupg_homedir (), GPG_AGENT_NAME EXTSEP_S "conf", NULL); filename_esc = percent_escape (filename, NULL); es_printf ("%s-%s.conf:%lu:\"%s\n", GPGCONF_NAME, GPG_AGENT_NAME, GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename); xfree (filename_esc); es_printf ("verbose:%lu:\n" "quiet:%lu:\n" "debug-level:%lu:\"none:\n" "log-file:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME ); es_printf ("default-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL ); es_printf ("default-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL_SSH ); es_printf ("max-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL ); es_printf ("max-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH ); es_printf ("enforce-passphrase-constraints:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("min-passphrase-len:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN ); es_printf ("min-passphrase-nonalpha:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_NONALPHA); es_printf ("check-passphrase-pattern:%lu:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME); es_printf ("max-passphrase-days:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_PASSPHRASE_DAYS); es_printf ("enable-passphrase-history:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-grab:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("ignore-cache-for-signing:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-allow-external-cache:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-allow-mark-trusted:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("disable-scdaemon:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, "md5"); #ifdef HAVE_W32_SYSTEM es_printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE); #endif es_printf ("no-allow-loopback-pinentry:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("allow-emacs-pinentry:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("pinentry-timeout:%lu:0:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME); es_printf ("grab:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); agent_exit (0); } /* 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); } /* Make sure that we have a default ttyname. */ if (!default_ttyname && gnupg_ttyname (1)) default_ttyname = xstrdup (gnupg_ttyname (1)); if (!default_ttytype && getenv ("TERM")) default_ttytype = xstrdup (getenv ("TERM")); if (pipe_server) { /* This is the simple pipe based server */ ctrl_t ctrl; initialize_modules (); ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); agent_exit (1); } ctrl->session_env = session_env_new (); if (!ctrl->session_env) { log_error ("error allocating session environment block: %s\n", strerror (errno) ); xfree (ctrl); agent_exit (1); } agent_init_default_ctrl (ctrl); start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD); agent_deinit_default_ctrl (ctrl); xfree (ctrl); } else if (is_supervised) { #ifndef HAVE_W32_SYSTEM gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh; initialize_modules (); /* when supervised and sending logs to stderr, the process supervisor should handle log entry metadata (pid, name, timestamp) */ if (!logfile) log_set_prefix (NULL, 0); log_info ("%s %s starting in supervised mode.\n", strusage(11), strusage(13) ); /* See below in "regular server mode" on why we remove certain * envvars. */ if (!opt.keep_display) gnupg_unsetenv ("DISPLAY"); gnupg_unsetenv ("INSIDE_EMACS"); /* Virtually create the sockets. Note that we use -1 here * because the whole thing works only on Unix. */ map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh); if (fd == -1) log_fatal ("no standard socket provided\n"); #ifdef HAVE_SIGPROCMASK if (startup_signal_mask_valid) { if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) log_error ("error restoring signal mask: %s\n", strerror (errno)); } else log_info ("no saved signal mask\n"); #endif /*HAVE_SIGPROCMASK*/ log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n", fd, fd_extra, fd_browser, fd_ssh); handle_connections (fd, fd_extra, fd_browser, fd_ssh); #endif /*!HAVE_W32_SYSTEM*/ } else if (!is_daemon) ; /* NOTREACHED */ else { /* Regular server mode */ gnupg_fd_t fd; gnupg_fd_t fd_extra = GNUPG_INVALID_FD; gnupg_fd_t fd_browser = GNUPG_INVALID_FD; gnupg_fd_t fd_ssh = GNUPG_INVALID_FD; #ifndef HAVE_W32_SYSTEM pid_t pid; #endif /* Remove the DISPLAY variable so that a pinentry does not default to a specific display. There is still a default display when gpg-agent was started using --display or a client requested this using an OPTION command. Note, that we don't do this when running in reverse daemon mode (i.e. when exec the program given as arguments). */ #ifndef HAVE_W32_SYSTEM if (!opt.keep_display && !argc) gnupg_unsetenv ("DISPLAY"); #endif /* Remove the INSIDE_EMACS variable so that a pinentry does not always try to interact with Emacs. The variable is set when a client requested this using an OPTION command. */ gnupg_unsetenv ("INSIDE_EMACS"); /* Create the sockets. */ socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1); fd = create_server_socket (socket_name, 1, 0, &redir_socket_name, &socket_nonce); if (opt.extra_socket) { if (socket_name_extra) socket_name_extra = create_socket_name (socket_name_extra, 0); else socket_name_extra = create_socket_name /**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1); opt.extra_socket = 2; /* Indicate that it has been malloced. */ fd_extra = create_server_socket (socket_name_extra, 0, 0, &redir_socket_name_extra, &socket_nonce_extra); } if (opt.browser_socket) { if (socket_name_browser) socket_name_browser = create_socket_name (socket_name_browser, 0); else socket_name_browser= create_socket_name /**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1); opt.browser_socket = 2; /* Indicate that it has been malloced. */ fd_browser = create_server_socket (socket_name_browser, 0, 0, &redir_socket_name_browser, &socket_nonce_browser); } socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1); fd_ssh = create_server_socket (socket_name_ssh, 0, 1, &redir_socket_name_ssh, &socket_nonce_ssh); /* If we are going to exec a program in the parent, we record the PID, so that the child may check whether the program is still alive. */ if (argc) parent_pid = getpid (); fflush (NULL); #ifdef HAVE_W32_SYSTEM (void)csh_style; (void)nodetach; initialize_modules (); #else /*!HAVE_W32_SYSTEM*/ pid = fork (); if (pid == (pid_t)-1) { log_fatal ("fork failed: %s\n", strerror (errno) ); exit (1); } else if (pid) { /* We are the parent */ char *infostr_ssh_sock, *infostr_ssh_valid; /* Close the socket FD. */ close (fd); /* The signal mask might not be correct right now and thus we restore it. That is not strictly necessary but some programs falsely assume a cleared signal mask. */ #ifdef HAVE_SIGPROCMASK if (startup_signal_mask_valid) { if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) log_error ("error restoring signal mask: %s\n", strerror (errno)); } else log_info ("no saved signal mask\n"); #endif /*HAVE_SIGPROCMASK*/ /* Create the SSH info string if enabled. */ if (ssh_support) { if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s", socket_name_ssh) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu", (unsigned long)getpid()) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } } *socket_name = 0; /* Don't let cleanup() remove the socket - the child should do this from now on */ if (opt.extra_socket) *socket_name_extra = 0; if (opt.browser_socket) *socket_name_browser = 0; *socket_name_ssh = 0; if (argc) { /* Run the program given on the commandline. */ if (ssh_support && (putenv (infostr_ssh_sock) || putenv (infostr_ssh_valid))) { log_error ("failed to set environment: %s\n", strerror (errno) ); kill (pid, SIGTERM ); exit (1); } /* Close all the file descriptors except the standard ones and those open at startup. We explicitly don't close 0,1,2 in case something went wrong collecting them at startup. */ close_all_fds (3, startup_fd_list); /* Run the command. */ execvp (argv[0], argv); log_error ("failed to run the command: %s\n", strerror (errno)); kill (pid, SIGTERM); exit (1); } else { /* Print the environment string, so that the caller can use shell's eval to set it */ if (csh_style) { if (ssh_support) { *strchr (infostr_ssh_sock, '=') = ' '; es_printf ("setenv %s;\n", infostr_ssh_sock); } } else { if (ssh_support) { es_printf ("%s; export SSH_AUTH_SOCK;\n", infostr_ssh_sock); } } if (ssh_support) { xfree (infostr_ssh_sock); xfree (infostr_ssh_valid); } exit (0); } /*NOTREACHED*/ } /* End parent */ /* This is the child */ initialize_modules (); /* 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 (); exit (1); } } } if (setsid() == -1) { log_error ("setsid() failed: %s\n", strerror(errno) ); cleanup (); exit (1); } log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); opt.running_detached = 1; /* Unless we are running with a program given on the command * line we can assume that the inotify things works and thus * we can avoid the regular stat calls. */ if (!argc) reliable_homedir_inotify = 1; } { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } #endif /*!HAVE_W32_SYSTEM*/ if (gnupg_chdir (gnupg_daemon_rootdir ())) { log_error ("chdir to '%s' failed: %s\n", gnupg_daemon_rootdir (), strerror (errno)); exit (1); } log_info ("%s %s started\n", strusage(11), strusage(13) ); handle_connections (fd, fd_extra, fd_browser, fd_ssh); assuan_sock_close (fd); } return 0; } /* Exit entry point. This function should be called instead of a plain exit. */ void agent_exit (int rc) { /*FIXME: update_random_seed_file();*/ /* We run our cleanup handler because that may close cipher contexts stored in secure memory and thus this needs to be done before we explicitly terminate secure memory. */ cleanup (); #if 1 /* at this time a bit annoying */ if (opt.debug & DBG_MEMSTAT_VALUE) { gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); #endif gcry_control (GCRYCTL_TERM_SECMEM ); rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } /* This is our callback function for gcrypt progress messages. It is set once at startup and dispatches progress messages to the corresponding threads of the agent. */ static void agent_libgcrypt_progress_cb (void *data, const char *what, int printchar, int current, int total) { struct progress_dispatch_s *dispatch; npth_t mytid = npth_self (); (void)data; for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) if (dispatch->ctrl && dispatch->tid == mytid) break; if (dispatch && dispatch->cb) dispatch->cb (dispatch->ctrl, what, printchar, current, total); /* Libgcrypt < 1.8 does not know about nPth and thus when it reads * from /dev/random this will block the process. To mitigate this * problem we yield the thread when Libgcrypt tells us that it needs * more entropy. This way other threads have chance to run. */ #if GCRYPT_VERSION_NUMBER < 0x010800 /* 1.8.0 */ if (what && !strcmp (what, "need_entropy")) { #if GPGRT_VERSION_NUMBER < 0x011900 /* 1.25 */ /* In older gpg-error versions gpgrt_yield is buggy for use with * nPth and thus we need to resort to a sleep call. */ npth_usleep (1000); /* 1ms */ #else gpgrt_yield (); #endif } #endif } /* If a progress dispatcher callback has been associated with the * current connection unregister it. */ static void unregister_progress_cb (void) { struct progress_dispatch_s *dispatch; npth_t mytid = npth_self (); for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) if (dispatch->ctrl && dispatch->tid == mytid) break; if (dispatch) { dispatch->ctrl = NULL; dispatch->cb = NULL; } } /* Setup a progress callback CB for the current connection. Using a * CB of NULL disables the callback. */ void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total), ctrl_t ctrl) { struct progress_dispatch_s *dispatch, *firstfree; npth_t mytid = npth_self (); firstfree = NULL; for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) { if (dispatch->ctrl && dispatch->tid == mytid) break; if (!dispatch->ctrl && !firstfree) firstfree = dispatch; } if (!dispatch) /* None allocated: Reuse or allocate a new one. */ { if (firstfree) { dispatch = firstfree; } else if ((dispatch = xtrycalloc (1, sizeof *dispatch))) { dispatch->next = progress_dispatch_list; progress_dispatch_list = dispatch; } else { log_error ("error allocating new progress dispatcher slot: %s\n", gpg_strerror (gpg_error_from_syserror ())); return; } dispatch->ctrl = ctrl; dispatch->tid = mytid; } dispatch->cb = cb; } /* Each thread has its own local variables conveyed by a control structure usually identified by an argument named CTRL. This function is called immediately after allocating the control structure. Its purpose is to setup the default values for that structure. Note that some values may have already been set. */ static void agent_init_default_ctrl (ctrl_t ctrl) { log_assert (ctrl->session_env); /* Note we ignore malloc errors because we can't do much about it and the request will fail anyway shortly after this initialization. */ session_env_setenv (ctrl->session_env, "DISPLAY", default_display); session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname); session_env_setenv (ctrl->session_env, "TERM", default_ttytype); session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority); session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL); if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL; if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages) /**/ : NULL; ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET; } /* Release all resources allocated by default in the control structure. This is the counterpart to agent_init_default_ctrl. */ static void agent_deinit_default_ctrl (ctrl_t ctrl) { unregister_progress_cb (); session_env_release (ctrl->session_env); if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); if (ctrl->lc_messages) xfree (ctrl->lc_messages); } /* Because the ssh protocol does not send us information about the current TTY setting, we use this function to use those from startup or those explicitly set. This is also used for the restricted mode where we ignore requests to change the environment. */ gpg_error_t agent_copy_startup_env (ctrl_t ctrl) { gpg_error_t err = 0; int iterator = 0; const char *name, *value; while (!err && (name = session_env_list_stdenvnames (&iterator, NULL))) { if ((value = session_env_getenv (opt.startup_env, name))) err = session_env_setenv (ctrl->session_env, name, value); } if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype) if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype))) err = gpg_error_from_syserror (); if (!err && !ctrl->lc_messages && opt.startup_lc_messages) if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages))) err = gpg_error_from_syserror (); if (err) log_error ("error setting default session environment: %s\n", gpg_strerror (err)); return err; } /* Reread parts of the configuration. Note, that this function is obviously not thread-safe and should only be called from the PTH 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) { ARGPARSE_ARGS pargs; FILE *fp; unsigned int configlineno = 0; int dummy; if (!config_filename) return; /* No config file. */ fp = fopen (config_filename, "r"); if (!fp) { log_info (_("option file '%s': %s\n"), config_filename, strerror(errno) ); return; } parse_rereadable_options (NULL, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = 1; /* do not remove the args */ while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) ) { if (pargs.r_opt < -1) pargs.err = 1; /* Print a warning. */ else /* Try to parse this option - ignore unchangeable ones. */ parse_rereadable_options (&pargs, 1); } fclose (fp); finalize_rereadable_options (); set_debug (); } /* Return the file name of the socket we are using for native requests. */ const char * get_agent_socket_name (void) { const char *s = socket_name; return (s && *s)? s : NULL; } /* Return the file name of the socket we are using for SSH requests. */ const char * get_agent_ssh_socket_name (void) { const char *s = socket_name_ssh; return (s && *s)? s : NULL; } /* Return the number of active connections. */ int get_agent_active_connection_count (void) { return active_connections; } /* Under W32, this function returns the handle of the scdaemon notification event. Calling it the first time creates that event. */ #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) void * get_agent_scd_notify_event (void) { static HANDLE the_event = INVALID_HANDLE_VALUE; if (the_event == INVALID_HANDLE_VALUE) { HANDLE h, h2; SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; /* We need to use a manual reset event object due to the way our w32-pth wait function works: If we would use an automatic reset event we are not able to figure out which handle has been signaled because at the time we single out the signaled handles using WFSO the event has already been reset due to the WFMO. */ h = CreateEvent (&sa, TRUE, FALSE, NULL); if (!h) log_error ("can't create scd notify event: %s\n", w32_strerror (-1) ); else if (!DuplicateHandle (GetCurrentProcess(), h, GetCurrentProcess(), &h2, EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) { log_error ("setting synchronize for scd notify event failed: %s\n", w32_strerror (-1) ); CloseHandle (h); } else { CloseHandle (h); the_event = h2; } } return the_event; } #endif /*HAVE_W32_SYSTEM && !HAVE_W32CE_SYSTEM*/ /* Create a name for the socket in the home directory as using STANDARD_NAME. We also check for valid characters as well as against a maximum allowed length for a unix domain socket is done. The function terminates the process in case of an error. Returns: Pointer to an allocated string with the absolute name of the socket used. */ static char * create_socket_name (char *standard_name, int with_homedir) { char *name; if (with_homedir) name = make_filename (gnupg_socketdir (), standard_name, NULL); else name = make_filename (standard_name, NULL); if (strchr (name, PATHSEP_C)) { log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); agent_exit (2); } return name; } /* Create a Unix domain socket with NAME. Returns the file descriptor or terminates the process in case of an error. Note that this function needs to be used for the regular socket first (indicated by PRIMARY) and only then for the extra and the ssh sockets. If the socket has been redirected the name of the real socket is stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a Cygwin compatible socket is created (Windows only). */ static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin, char **r_redir_name, assuan_sock_nonce_t *nonce) { struct sockaddr *addr; struct sockaddr_un *unaddr; socklen_t len; gnupg_fd_t fd; int rc; xfree (*r_redir_name); *r_redir_name = NULL; fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); if (fd == ASSUAN_INVALID_FD) { log_error (_("can't create socket: %s\n"), strerror (errno)); *name = 0; /* Inhibit removal of the socket by cleanup(). */ agent_exit (2); } if (cygwin) assuan_sock_set_flag (fd, "cygwin", 1); unaddr = xmalloc (sizeof *unaddr); addr = (struct sockaddr*)unaddr; { int redirected; if (assuan_sock_set_sockaddr_un (name, addr, &redirected)) { if (errno == ENAMETOOLONG) log_error (_("socket name '%s' is too long\n"), name); else log_error ("error preparing socket '%s': %s\n", name, gpg_strerror (gpg_error_from_syserror ())); *name = 0; /* Inhibit removal of the socket by cleanup(). */ xfree (unaddr); agent_exit (2); } if (redirected) { *r_redir_name = xstrdup (unaddr->sun_path); if (opt.verbose) log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name); } } len = SUN_LEN (unaddr); rc = assuan_sock_bind (fd, addr, len); /* Our error code mapping on W32CE returns EEXIST thus we also test for this. */ if (rc == -1 && (errno == EADDRINUSE #ifdef HAVE_W32_SYSTEM || errno == EEXIST #endif )) { /* Check whether a gpg-agent is already running. We do this test only if this is the primary socket. For secondary sockets we assume that a test for gpg-agent has already been done and reuse the requested socket. Testing the ssh-socket is not possible because at this point, though we know the new Assuan socket, the Assuan server and thus the ssh-agent server is not yet operational; this would lead to a hang. */ if (primary && !check_for_running_agent (1)) { log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX); log_set_file (NULL); log_error (_("a gpg-agent is already running - " "not starting a new one\n")); *name = 0; /* Inhibit removal of the socket by cleanup(). */ assuan_sock_close (fd); xfree (unaddr); agent_exit (2); } gnupg_remove (unaddr->sun_path); rc = assuan_sock_bind (fd, addr, len); } if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce))) log_error (_("error getting nonce for the socket\n")); if (rc == -1) { /* We use gpg_strerror here because it allows us to get strings for some W32 socket error codes. */ log_error (_("error binding socket to '%s': %s\n"), unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ())); assuan_sock_close (fd); *name = 0; /* Inhibit removal of the socket by cleanup(). */ xfree (unaddr); agent_exit (2); } if (gnupg_chmod (unaddr->sun_path, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), unaddr->sun_path, strerror (errno)); if (listen (FD2INT(fd), listen_backlog ) == -1) { log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno)); *name = 0; /* Inhibit removal of the socket by cleanup(). */ assuan_sock_close (fd); xfree (unaddr); agent_exit (2); } if (opt.verbose) log_info (_("listening on socket '%s'\n"), unaddr->sun_path); xfree (unaddr); return fd; } /* Check that the directory for storing the private keys exists and create it if not. This function won't fail as it is only a convenience function and not strictly necessary. */ static void create_private_keys_directory (const char *home) { char *fname; struct stat statbuf; fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL); if (stat (fname, &statbuf) && errno == ENOENT) { if (gnupg_mkdir (fname, "-rwx")) log_error (_("can't create directory '%s': %s\n"), fname, strerror (errno) ); else if (!opt.quiet) log_info (_("directory '%s' created\n"), fname); } if (gnupg_chmod (fname, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), fname, strerror (errno)); xfree (fname); } /* Create the directory only if the supplied directory name is the same as the default one. This way we avoid to create arbitrary directories when a non-default home directory is used. To cope with HOME, we compare only the suffix if we see that the default homedir does start with a tilde. We don't stop here in case of problems because other functions will throw an error anyway.*/ static void create_directories (void) { struct stat statbuf; const char *defhome = standard_homedir (); char *home; home = make_filename (gnupg_homedir (), NULL); if ( stat (home, &statbuf) ) { if (errno == ENOENT) { if ( #ifdef HAVE_W32_SYSTEM ( !compare_filenames (home, defhome) ) #else (*defhome == '~' && (strlen (home) >= strlen (defhome+1) && !strcmp (home + strlen(home) - strlen (defhome+1), defhome+1))) || (*defhome != '~' && !strcmp (home, defhome) ) #endif ) { if (gnupg_mkdir (home, "-rwx")) log_error (_("can't create directory '%s': %s\n"), home, strerror (errno) ); else { if (!opt.quiet) log_info (_("directory '%s' created\n"), home); create_private_keys_directory (home); } } } else log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno)); } else if ( !S_ISDIR(statbuf.st_mode)) { log_error (_("can't use '%s' as home directory\n"), home); } else /* exists and is a directory. */ { create_private_keys_directory (home); } xfree (home); } /* This is the worker for the ticker. It is called every few seconds and may only do fast operations. */ static void handle_tick (void) { static time_t last_minute; struct stat statbuf; if (!last_minute) last_minute = time (NULL); /* If we are running as a child of another process, check whether the parent is still alive and shutdown if not. */ #ifndef HAVE_W32_SYSTEM if (parent_pid != (pid_t)(-1)) { if (kill (parent_pid, 0)) { shutdown_pending = 2; log_info ("parent process died - shutting down\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); agent_exit (0); } } #endif /*HAVE_W32_SYSTEM*/ /* Code to be run from time to time. */ #if CHECK_OWN_SOCKET_INTERVAL > 0 if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL)) { check_own_socket (); last_minute = time (NULL); } #endif /* Need to check for expired cache entries. */ agent_cache_housekeeping (); /* Check whether the homedir is still available. */ if (!shutdown_pending && (!have_homedir_inotify || !reliable_homedir_inotify) && stat (gnupg_homedir (), &statbuf) && errno == ENOENT) { shutdown_pending = 1; log_info ("homedir has been removed - shutting down\n"); } } /* A global function which allows us to call the reload stuff from other places too. This is only used when build for W32. */ void agent_sighup_action (void) { log_info ("SIGHUP received - " "re-reading configuration and flushing cache\n"); agent_flush_cache (0); reread_configuration (); agent_reload_trustlist (); /* We flush the module name cache so that after installing a "pinentry" binary that one can be used in case the "pinentry-basic" fallback was in use. */ gnupg_module_name_flush_some (); if (opt.disable_scdaemon) agent_card_killscd (); } /* A helper function to handle SIGUSR2. */ static void agent_sigusr2_action (void) { if (opt.verbose) log_info ("SIGUSR2 received - updating card event counter\n"); /* Nothing to check right now. We only increment a counter. */ bump_card_eventcounter (); } #ifndef HAVE_W32_SYSTEM /* The signal handler for this program. It is expected to be run in its own thread and not in the context of a signal handler. */ static void handle_signal (int signo) { switch (signo) { #ifndef HAVE_W32_SYSTEM case SIGHUP: agent_sighup_action (); break; case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); /* Fixme: We need to see how to integrate pth dumping into our logging system. */ /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ agent_query_dump_state (); agent_scd_dump_state (); break; case SIGUSR2: agent_sigusr2_action (); break; case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); else log_info ("SIGTERM received - still %i open connections\n", active_connections); shutdown_pending++; if (shutdown_pending > 2) { log_info ("shutdown forced\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); agent_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); agent_exit (0); break; #endif default: log_info ("signal %d received - no action defined\n", signo); } } #endif /* 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 (ctrl_t ctrl, assuan_sock_nonce_t *nonce) { if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), FD2INT(ctrl->thread_startup.fd), strerror (errno)); assuan_sock_close (ctrl->thread_startup.fd); xfree (ctrl); return -1; } else return 0; } #ifdef HAVE_W32_SYSTEM /* The window message processing function for Putty. Warning: This code runs as a native Windows thread. Use of our own functions needs to be bracket with pth_leave/pth_enter. */ static LRESULT CALLBACK putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { int ret = 0; int w32rc; COPYDATASTRUCT *cds; const char *mapfile; HANDLE maphd; PSID mysid = NULL; PSID mapsid = NULL; void *data = NULL; PSECURITY_DESCRIPTOR psd = NULL; ctrl_t ctrl = NULL; if (msg != WM_COPYDATA) { return DefWindowProc (hwnd, msg, wparam, lparam); } cds = (COPYDATASTRUCT*)lparam; if (cds->dwData != PUTTY_IPC_MAGIC) return 0; /* Ignore data with the wrong magic. */ mapfile = cds->lpData; if (!cds->cbData || mapfile[cds->cbData - 1]) return 0; /* Ignore empty and non-properly terminated strings. */ if (DBG_IPC) { npth_protect (); log_debug ("ssh map file '%s'", mapfile); npth_unprotect (); } maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile); if (DBG_IPC) { npth_protect (); log_debug ("ssh map handle %p\n", maphd); npth_unprotect (); } if (!maphd || maphd == INVALID_HANDLE_VALUE) return 0; npth_protect (); mysid = w32_get_user_sid (); if (!mysid) { log_error ("error getting my sid\n"); goto leave; } w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, &mapsid, NULL, NULL, NULL, &psd); if (w32rc) { log_error ("error getting sid of ssh map file: rc=%d", w32rc); goto leave; } if (DBG_IPC) { char *sidstr; if (!ConvertSidToStringSid (mysid, &sidstr)) sidstr = NULL; log_debug (" my sid: '%s'", sidstr? sidstr: "[error]"); LocalFree (sidstr); if (!ConvertSidToStringSid (mapsid, &sidstr)) sidstr = NULL; log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]"); LocalFree (sidstr); } if (!EqualSid (mysid, mapsid)) { log_error ("ssh map file has a non-matching sid\n"); goto leave; } data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (DBG_IPC) log_debug ("ssh IPC buffer at %p\n", data); if (!data) goto leave; /* log_printhex ("request:", data, 20); */ ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); goto leave; } ctrl->session_env = session_env_new (); if (!ctrl->session_env) { log_error ("error allocating session environment block: %s\n", strerror (errno) ); goto leave; } agent_init_default_ctrl (ctrl); if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN)) ret = 1; /* Valid ssh message has been constructed. */ agent_deinit_default_ctrl (ctrl); /* log_printhex (" reply:", data, 20); */ leave: xfree (ctrl); if (data) UnmapViewOfFile (data); xfree (mapsid); if (psd) LocalFree (psd); xfree (mysid); CloseHandle (maphd); npth_unprotect (); return ret; } #endif /*HAVE_W32_SYSTEM*/ #ifdef HAVE_W32_SYSTEM /* The thread handling Putty's IPC requests. */ static void * putty_message_thread (void *arg) { WNDCLASS wndwclass = {0, putty_message_proc, 0, 0, NULL, NULL, NULL, NULL, NULL, "Pageant"}; HWND hwnd; MSG msg; (void)arg; if (opt.verbose) log_info ("putty message loop thread started\n"); /* The message loop runs as thread independent from our nPth system. This also means that we need to make sure that we switch back to our system before calling any no-windows function. */ npth_unprotect (); /* First create a window to make sure that a message queue exists for this thread. */ if (!RegisterClass (&wndwclass)) { npth_protect (); log_error ("error registering Pageant window class"); return NULL; } hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0, 0, 0, 0, 0, HWND_MESSAGE, /* hWndParent */ NULL, /* hWndMenu */ NULL, /* hInstance */ NULL); /* lpParm */ if (!hwnd) { npth_protect (); log_error ("error creating Pageant window"); return NULL; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /* Back to nPth. */ npth_protect (); if (opt.verbose) log_info ("putty message loop thread stopped\n"); return NULL; } #endif /*HAVE_W32_SYSTEM*/ static void * do_start_connection_thread (ctrl_t ctrl) { active_connections++; agent_init_default_ctrl (ctrl); if (opt.verbose && !DBG_IPC) log_info (_("handler 0x%lx for fd %d started\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd); if (opt.verbose && !DBG_IPC) log_info (_("handler 0x%lx for fd %d terminated\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); active_connections--; return NULL; } /* This is the standard connection thread's main function. */ static void * start_connection_thread_std (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } return do_start_connection_thread (ctrl); } /* This is the extra socket connection thread's main function. */ static void * start_connection_thread_extra (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_extra)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } ctrl->restricted = 1; return do_start_connection_thread (ctrl); } /* This is the browser socket connection thread's main function. */ static void * start_connection_thread_browser (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_browser)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } ctrl->restricted = 2; return do_start_connection_thread (ctrl); } /* This is the ssh connection thread's main function. */ static void * start_connection_thread_ssh (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_ssh)) return NULL; active_connections++; agent_init_default_ctrl (ctrl); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d started\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); start_command_handler_ssh (ctrl, ctrl->thread_startup.fd); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d terminated\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); active_connections--; return NULL; } /* Connection handler loop. Wait for connection requests and spawn a thread after accepting a connection. */ static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, gnupg_fd_t listen_fd_ssh) { gpg_error_t err; npth_attr_t tattr; struct sockaddr_un paddr; socklen_t plen; fd_set fdset, read_fdset; int ret; gnupg_fd_t fd; int nfd; int saved_errno; struct timespec abstime; struct timespec curtime; struct timespec timeout; #ifdef HAVE_W32_SYSTEM HANDLE events[2]; unsigned int events_set; #endif int sock_inotify_fd = -1; int home_inotify_fd = -1; struct { const char *name; void *(*func) (void *arg); gnupg_fd_t l_fd; } listentbl[] = { { "std", start_connection_thread_std }, { "extra", start_connection_thread_extra }, { "browser", start_connection_thread_browser }, { "ssh", start_connection_thread_ssh } }; ret = npth_attr_init(&tattr); if (ret) log_fatal ("error allocating thread attributes: %s\n", strerror (ret)); npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); #ifndef HAVE_W32_SYSTEM 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 (); #else # ifdef HAVE_W32CE_SYSTEM /* Use a dummy event. */ sigs = 0; ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); # else events[0] = get_agent_scd_notify_event (); events[1] = INVALID_HANDLE_VALUE; # endif #endif if (disable_check_own_socket) sock_inotify_fd = -1; else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name))) { if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) log_info ("error enabling daemon termination by socket removal: %s\n", gpg_strerror (err)); } if (disable_check_own_socket) home_inotify_fd = -1; else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, gnupg_homedir ()))) { if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) log_info ("error enabling daemon termination by homedir removal: %s\n", gpg_strerror (err)); } else have_homedir_inotify = 1; /* On Windows we need to fire up a separate thread to listen for requests from Putty (an SSH client), so we can replace Putty's Pageant (its ssh-agent implementation). */ #ifdef HAVE_W32_SYSTEM if (putty_support) { npth_t thread; ret = npth_create (&thread, &tattr, putty_message_thread, NULL); if (ret) { log_error ("error spawning putty message loop: %s\n", strerror (ret)); } } #endif /*HAVE_W32_SYSTEM*/ /* Set a flag to tell call-scd.c that it may enable event notifications. */ opt.sigusr2_enabled = 1; FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); nfd = FD2INT (listen_fd); if (listen_fd_extra != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_extra), &fdset); if (FD2INT (listen_fd_extra) > nfd) nfd = FD2INT (listen_fd_extra); } if (listen_fd_browser != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_browser), &fdset); if (FD2INT (listen_fd_browser) > nfd) nfd = FD2INT (listen_fd_browser); } if (listen_fd_ssh != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_ssh), &fdset); if (FD2INT (listen_fd_ssh) > nfd) nfd = FD2INT (listen_fd_ssh); } if (sock_inotify_fd != -1) { FD_SET (sock_inotify_fd, &fdset); if (sock_inotify_fd > nfd) nfd = sock_inotify_fd; } if (home_inotify_fd != -1) { FD_SET (home_inotify_fd, &fdset); if (home_inotify_fd > nfd) nfd = home_inotify_fd; } listentbl[0].l_fd = listen_fd; listentbl[1].l_fd = listen_fd_extra; listentbl[2].l_fd = listen_fd_browser; listentbl[3].l_fd = listen_fd_ssh; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; for (;;) { /* Shutdown test. */ if (shutdown_pending) { if (active_connections == 0) 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. */ FD_ZERO (&fdset); nfd = -1; if (sock_inotify_fd != -1) { FD_SET (sock_inotify_fd, &fdset); nfd = sock_inotify_fd; } if (home_inotify_fd != -1) { FD_SET (home_inotify_fd, &fdset); if (home_inotify_fd > nfd) nfd = home_inotify_fd; } } /* POSIX says that fd_set should be implemented as a structure, thus a simple assignment is fine to copy the entire set. */ read_fdset = fdset; npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { /* Timeout. */ handle_tick (); npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); #ifndef HAVE_W32_SYSTEM ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, npth_sigev_sigmask ()); saved_errno = errno; { int signo; while (npth_sigev_get_pending (&signo)) handle_signal (signo); } #else ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, events, &events_set); saved_errno = errno; /* This is valid even if npth_eselect returns an error. */ if (events_set & 1) agent_sigusr2_action (); #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; /* The inotify fds are set even when a shutdown is pending (see * above). So we must handle them in any case. To avoid that * they trigger a second time we close them immediately. */ if (sock_inotify_fd != -1 && FD_ISSET (sock_inotify_fd, &read_fdset) && gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME)) { shutdown_pending = 1; close (sock_inotify_fd); sock_inotify_fd = -1; log_info ("socket file has been removed - shutting down\n"); } if (home_inotify_fd != -1 && FD_ISSET (home_inotify_fd, &read_fdset)) { shutdown_pending = 1; close (home_inotify_fd); home_inotify_fd = -1; log_info ("homedir has been removed - shutting down\n"); } if (!shutdown_pending) { int idx; ctrl_t ctrl; npth_t thread; for (idx=0; idx < DIM(listentbl); idx++) { if (listentbl[idx].l_fd == GNUPG_INVALID_FD) continue; if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset)) continue; plen = sizeof paddr; fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), (struct sockaddr *)&paddr, &plen)); if (fd == GNUPG_INVALID_FD) { log_error ("accept failed for %s: %s\n", listentbl[idx].name, strerror (errno)); } else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl))) { log_error ("error allocating connection data for %s: %s\n", listentbl[idx].name, strerror (errno) ); assuan_sock_close (fd); } else if ( !(ctrl->session_env = session_env_new ())) { log_error ("error allocating session env block for %s: %s\n", listentbl[idx].name, strerror (errno) ); xfree (ctrl); assuan_sock_close (fd); } else { ctrl->thread_startup.fd = fd; ret = npth_create (&thread, &tattr, listentbl[idx].func, ctrl); if (ret) { log_error ("error spawning connection handler for %s:" " %s\n", listentbl[idx].name, strerror (ret)); assuan_sock_close (fd); xfree (ctrl); } } } } } if (sock_inotify_fd != -1) close (sock_inotify_fd); if (home_inotify_fd != -1) close (home_inotify_fd); cleanup (); log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); npth_attr_destroy (&tattr); } /* Helper for check_own_socket. */ static gpg_error_t check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) { membuf_t *mb = opaque; put_membuf (mb, buffer, length); return 0; } /* The thread running the actual check. We need to run this in a separate thread so that check_own_thread can be called from the timer tick. */ static void * check_own_socket_thread (void *arg) { int rc; char *sockname = arg; assuan_context_t ctx = NULL; membuf_t mb; char *buffer; check_own_socket_running++; rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); goto leave; } assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1); rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); if (rc) { log_error ("can't connect my own socket: %s\n", gpg_strerror (rc)); goto leave; } init_membuf (&mb, 100); rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb, NULL, NULL, NULL, NULL); put_membuf (&mb, "", 1); buffer = get_membuf (&mb, NULL); if (rc || !buffer) { log_error ("sending command \"%s\" to my own socket failed: %s\n", "GETINFO pid", gpg_strerror (rc)); rc = 1; } else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ()) { log_error ("socket is now serviced by another server\n"); rc = 1; } else if (opt.verbose > 1) log_error ("socket is still served by this server\n"); xfree (buffer); leave: xfree (sockname); if (ctx) assuan_release (ctx); if (rc) { /* We may not remove the socket as it is now in use by another server. */ inhibit_socket_removal = 1; shutdown_pending = 2; log_info ("this process is useless - shutting down\n"); } check_own_socket_running--; return NULL; } /* Check whether we are still listening on our own socket. In case another gpg-agent process started after us has taken ownership of our socket, we would linger around without any real task. Thus we better check once in a while whether we are really needed. */ static void check_own_socket (void) { char *sockname; npth_t thread; npth_attr_t tattr; int err; if (disable_check_own_socket) return; if (check_own_socket_running || shutdown_pending) return; /* Still running or already shutting down. */ sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) return; /* Out of memory. */ err = npth_attr_init (&tattr); if (err) return; npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); if (err) log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); npth_attr_destroy (&tattr); } /* Figure out whether an agent is available and running. Prints an error if not. If SILENT is true, no messages are printed. Returns 0 if the agent is running. */ static int check_for_running_agent (int silent) { gpg_error_t err; char *sockname; assuan_context_t ctx = NULL; sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) return gpg_error_from_syserror (); err = assuan_new (&ctx); if (!err) err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); xfree (sockname); if (err) { if (!silent) log_error (_("no gpg-agent running in this session\n")); if (ctx) assuan_release (ctx); return -1; } if (!opt.quiet && !silent) log_info ("gpg-agent running and available\n"); assuan_release (ctx); return 0; } diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c index 7b322a0b7..496cee73d 100644 --- a/agent/preset-passphrase.c +++ b/agent/preset-passphrase.c @@ -1,267 +1,271 @@ /* preset-passphrase.c - A tool to preset a passphrase. * Copyright (C) 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include /* To initialize the sockets. fixme */ #endif #define INCLUDED_BY_MAIN_MODULE 1 #include "agent.h" #include "../common/simple-pwquery.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" +#include "../common/argparse.h" /* temporary hack. */ enum cmd_and_opt_values { aNull = 0, oVerbose = 'v', oPassphrase = 'P', oPreset = 'c', oForget = 'f', oNoVerbose = 500, oHomedir, aTest }; static const char *opt_passphrase; static ARGPARSE_OPTS opts[] = { { 301, NULL, 0, N_("@Options:\n ") }, { oVerbose, "verbose", 0, "verbose" }, { oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" }, { oPreset, "preset", 256, "preset passphrase"}, { oForget, "forget", 256, "forget passphrase"}, { oHomedir, "homedir", 2, "@" }, ARGPARSE_end () }; static const char * my_strusage (int level) { const char *p; switch (level) { case 11: p = "gpg-preset-passphrase (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpg-preset-passphrase [options] KEYGRIP (-h for help)\n"); break; case 41: p = _("Syntax: gpg-preset-passphrase [options] KEYGRIP\n" "Password cache maintenance\n"); break; default: p = NULL; } return p; } static void preset_passphrase (const char *keygrip) { int rc; char *line; /* FIXME: Use secure memory. */ char passphrase[500]; char *passphrase_esc; if (!opt_passphrase) { rc = read (0, passphrase, sizeof (passphrase) - 1); if (rc < 0) { log_error ("reading passphrase failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); return; } passphrase[rc] = '\0'; line = strchr (passphrase, '\n'); if (line) { if (line > passphrase && line[-1] == '\r') line--; *line = '\0'; } /* FIXME: How to handle empty passwords? */ } { const char *s = opt_passphrase ? opt_passphrase : passphrase; passphrase_esc = bin2hex (s, strlen (s), NULL); } if (!passphrase_esc) { log_error ("can not escape string: %s\n", gpg_strerror (gpg_error_from_syserror ())); return; } rc = asprintf (&line, "PRESET_PASSPHRASE %s -1 %s\n", keygrip, passphrase_esc); wipememory (passphrase_esc, strlen (passphrase_esc)); xfree (passphrase_esc); if (rc < 0) { log_error ("caching passphrase failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); return; } if (!opt_passphrase) wipememory (passphrase, sizeof (passphrase)); rc = simple_query (line); if (rc) { log_error ("caching passphrase failed: %s\n", gpg_strerror (rc)); return; } wipememory (line, strlen (line)); xfree (line); } static void forget_passphrase (const char *keygrip) { int rc; char *line; rc = asprintf (&line, "CLEAR_PASSPHRASE %s\n", keygrip); if (rc < 0) rc = gpg_error_from_syserror (); else rc = simple_query (line); if (rc) { log_error ("clearing passphrase failed: %s\n", gpg_strerror (rc)); return; } xfree (line); } int main (int argc, char **argv) { ARGPARSE_ARGS pargs; int cmd = 0; const char *keygrip = NULL; early_system_init (); set_strusage (my_strusage); log_set_prefix ("gpg-preset-passphrase", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* (do not remove the args) */ while (arg_parse (&pargs, opts) ) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oPreset: cmd = oPreset; break; case oForget: cmd = oForget; break; case oPassphrase: opt_passphrase = pargs.r.ret_str; break; default : pargs.err = 2; break; } } if (log_get_errorcount(0)) exit(2); if (argc == 1) keygrip = *argv; else usage (1); /* Tell simple-pwquery about the standard socket name. */ { char *tmp = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); simple_pw_set_socket (tmp); xfree (tmp); } if (cmd == oPreset) preset_passphrase (keygrip); else if (cmd == oForget) forget_passphrase (keygrip); else log_error ("one of the options --preset or --forget must be given\n"); agent_exit (0); return 8; /*NOTREACHED*/ } void agent_exit (int rc) { rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 4a307abc2..798c50e78 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -1,823 +1,827 @@ /* protect-tool.c - A tool to test the secret key protection * Copyright (C) 2002, 2003, 2004, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #include #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_LANGINFO_CODESET #include #endif #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #define INCLUDED_BY_MAIN_MODULE 1 #include "agent.h" #include "../common/i18n.h" #include "../common/get-passphrase.h" #include "../common/sysutils.h" #include "../common/init.h" +#include "../common/argparse.h" /* temporary hack. */ enum cmd_and_opt_values { aNull = 0, oVerbose = 'v', oArmor = 'a', oPassphrase = 'P', oProtect = 'p', oUnprotect = 'u', oNoVerbose = 500, oShadow, oShowShadowInfo, oShowKeygrip, oS2Kcalibration, oCanonical, oStore, oForce, oHaveCert, oNoFailOnExist, oHomedir, oPrompt, oStatusMsg, oDebugUseOCB, oAgentProgram }; struct rsa_secret_key_s { gcry_mpi_t n; /* public modulus */ gcry_mpi_t e; /* public exponent */ gcry_mpi_t d; /* exponent */ gcry_mpi_t p; /* prime p. */ gcry_mpi_t q; /* prime q. */ gcry_mpi_t u; /* inverse of p mod q. */ }; static int opt_armor; static int opt_canonical; static int opt_store; static int opt_force; static int opt_no_fail_on_exist; static int opt_have_cert; static const char *opt_passphrase; static char *opt_prompt; static int opt_status_msg; static const char *opt_agent_program; static int opt_debug_use_ocb; static char *get_passphrase (int promptno); static void release_passphrase (char *pw); static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (oProtect, "protect", "protect a private key"), ARGPARSE_c (oUnprotect, "unprotect", "unprotect a private key"), ARGPARSE_c (oShadow, "shadow", "create a shadow entry for a public key"), ARGPARSE_c (oShowShadowInfo, "show-shadow-info", "return the shadow info"), ARGPARSE_c (oShowKeygrip, "show-keygrip", "show the \"keygrip\""), ARGPARSE_c (oS2Kcalibration, "s2k-calibration", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", "verbose"), ARGPARSE_s_n (oArmor, "armor", "write output in advanced format"), ARGPARSE_s_n (oCanonical, "canonical", "write output in canonical format"), ARGPARSE_s_s (oPassphrase, "passphrase", "|STRING|use passphrase STRING"), ARGPARSE_s_n (oHaveCert, "have-cert", "certificate to export provided on STDIN"), ARGPARSE_s_n (oStore, "store", "store the created key in the appropriate place"), ARGPARSE_s_n (oForce, "force", "force overwriting"), ARGPARSE_s_n (oNoFailOnExist, "no-fail-on-exist", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oPrompt, "prompt", "|ESCSTRING|use ESCSTRING as prompt in pinentry"), ARGPARSE_s_n (oStatusMsg, "enable-status-msg", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_n (oDebugUseOCB, "debug-use-ocb", "@"), /* For hacking only. */ ARGPARSE_end () }; static const char * my_strusage (int level) { const char *p; switch (level) { case 11: p = "gpg-protect-tool (" GNUPG_NAME ")"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpg-protect-tool [options] (-h for help)\n"); break; case 41: p = _("Syntax: gpg-protect-tool [options] [args]\n" "Secret key maintenance tool\n"); break; default: p = NULL; } return p; } /* static void */ /* print_mpi (const char *text, gcry_mpi_t a) */ /* { */ /* char *buf; */ /* void *bufaddr = &buf; */ /* int rc; */ /* rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); */ /* if (rc) */ /* log_info ("%s: [error printing number: %s]\n", text, gpg_strerror (rc)); */ /* else */ /* { */ /* log_info ("%s: %s\n", text, buf); */ /* gcry_free (buf); */ /* } */ /* } */ static unsigned char * make_canonical (const char *fname, const char *buf, size_t buflen) { int rc; size_t erroff, len; gcry_sexp_t sexp; unsigned char *result; rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen); if (rc) { log_error ("invalid S-Expression in '%s' (off=%u): %s\n", fname, (unsigned int)erroff, gpg_strerror (rc)); return NULL; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len); log_assert (len); gcry_sexp_release (sexp); return result; } static char * make_advanced (const unsigned char *buf, size_t buflen) { int rc; size_t erroff, len; gcry_sexp_t sexp; char *result; rc = gcry_sexp_sscan (&sexp, &erroff, (const char*)buf, buflen); if (rc) { log_error ("invalid canonical S-Expression (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); return NULL; } len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); log_assert (len); result = xmalloc (len); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); log_assert (len); gcry_sexp_release (sexp); return result; } static char * read_file (const char *fname, size_t *r_length) { FILE *fp; char *buf; size_t buflen; if (!strcmp (fname, "-")) { size_t nread, bufsize = 0; fp = stdin; #ifdef HAVE_DOSISH_SYSTEM setmode ( fileno(fp) , O_BINARY ); #endif buf = NULL; buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xmalloc (bufsize); else buf = xrealloc (buf, bufsize); nread = fread (buf+buflen, 1, NCHUNK, fp); if (nread < NCHUNK && ferror (fp)) { log_error ("error reading '[stdin]': %s\n", strerror (errno)); xfree (buf); return NULL; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK } else { struct stat st; fp = fopen (fname, "rb"); if (!fp) { log_error ("can't open '%s': %s\n", fname, strerror (errno)); return NULL; } if (fstat (fileno(fp), &st)) { log_error ("can't stat '%s': %s\n", fname, strerror (errno)); fclose (fp); return NULL; } buflen = st.st_size; buf = xmalloc (buflen+1); if (fread (buf, buflen, 1, fp) != 1) { log_error ("error reading '%s': %s\n", fname, strerror (errno)); fclose (fp); xfree (buf); return NULL; } fclose (fp); } *r_length = buflen; return buf; } static unsigned char * read_key (const char *fname) { char *buf; size_t buflen; unsigned char *key; buf = read_file (fname, &buflen); if (!buf) return NULL; key = make_canonical (fname, buf, buflen); xfree (buf); return key; } static void read_and_protect (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; char *pw; key = read_key (fname); if (!key) return; pw = get_passphrase (1); rc = agent_protect (key, pw, &result, &resultlen, 0, opt_debug_use_ocb? 1 : -1); release_passphrase (pw); xfree (key); if (rc) { log_error ("protecting the key failed: %s\n", gpg_strerror (rc)); return; } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void read_and_unprotect (ctrl_t ctrl, const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; char *pw; gnupg_isotime_t protected_at; key = read_key (fname); if (!key) return; rc = agent_unprotect (ctrl, key, (pw=get_passphrase (1)), protected_at, &result, &resultlen); release_passphrase (pw); xfree (key); if (rc) { if (opt_status_msg) log_info ("[PROTECT-TOOL:] bad-passphrase\n"); log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc)); return; } if (opt.verbose) { if (*protected_at) log_info ("key protection done at %.4s-%.2s-%.2s %.2s:%.2s:%s\n", protected_at, protected_at+4, protected_at+6, protected_at+9, protected_at+11, protected_at+13); else log_info ("key protection done at [unknown]\n"); } if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void read_and_shadow (const char *fname) { int rc; unsigned char *key; unsigned char *result; size_t resultlen; unsigned char dummy_info[] = "(8:313233342:43)"; key = read_key (fname); if (!key) return; rc = agent_shadow_key (key, dummy_info, &result); xfree (key); if (rc) { log_error ("shadowing the key failed: %s\n", gpg_strerror (rc)); return; } resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL); log_assert (resultlen); if (opt_armor) { char *p = make_advanced (result, resultlen); xfree (result); if (!p) return; result = (unsigned char*)p; resultlen = strlen (p); } fwrite (result, resultlen, 1, stdout); xfree (result); } static void show_shadow_info (const char *fname) { int rc; unsigned char *key; const unsigned char *info; size_t infolen; key = read_key (fname); if (!key) return; rc = agent_get_shadow_info (key, &info); xfree (key); if (rc) { log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); return; } infolen = gcry_sexp_canon_len (info, 0, NULL,NULL); log_assert (infolen); if (opt_armor) { char *p = make_advanced (info, infolen); if (!p) return; fwrite (p, strlen (p), 1, stdout); xfree (p); } else fwrite (info, infolen, 1, stdout); } static void show_file (const char *fname) { unsigned char *key; size_t keylen; char *p; key = read_key (fname); if (!key) return; keylen = gcry_sexp_canon_len (key, 0, NULL,NULL); log_assert (keylen); if (opt_canonical) { fwrite (key, keylen, 1, stdout); } else { p = make_advanced (key, keylen); if (p) { fwrite (p, strlen (p), 1, stdout); xfree (p); } } xfree (key); } static void show_keygrip (const char *fname) { unsigned char *key; gcry_sexp_t private; unsigned char grip[20]; int i; key = read_key (fname); if (!key) return; if (gcry_sexp_new (&private, key, 0, 0)) { log_error ("gcry_sexp_new failed\n"); return; } xfree (key); if (!gcry_pk_get_keygrip (private, grip)) { log_error ("can't calculate keygrip\n"); return; } gcry_sexp_release (private); for (i=0; i < 20; i++) printf ("%02X", grip[i]); putchar ('\n'); } int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int cmd = 0; const char *fname; ctrl_t ctrl; early_system_init (); set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix ("gpg-protect-tool", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); setup_libgcrypt_logging (); gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* (do not remove the args) */ while (arg_parse (&pargs, opts) ) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oArmor: opt_armor=1; break; case oCanonical: opt_canonical=1; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oAgentProgram: opt_agent_program = pargs.r.ret_str; break; case oProtect: cmd = oProtect; break; case oUnprotect: cmd = oUnprotect; break; case oShadow: cmd = oShadow; break; case oShowShadowInfo: cmd = oShowShadowInfo; break; case oShowKeygrip: cmd = oShowKeygrip; break; case oS2Kcalibration: cmd = oS2Kcalibration; break; case oPassphrase: opt_passphrase = pargs.r.ret_str; break; case oStore: opt_store = 1; break; case oForce: opt_force = 1; break; case oNoFailOnExist: opt_no_fail_on_exist = 1; break; case oHaveCert: opt_have_cert = 1; break; case oPrompt: opt_prompt = pargs.r.ret_str; break; case oStatusMsg: opt_status_msg = 1; break; case oDebugUseOCB: opt_debug_use_ocb = 1; break; default: pargs.err = ARGPARSE_PRINT_ERROR; break; } } if (log_get_errorcount (0)) exit (2); fname = "-"; if (argc == 1) fname = *argv; else if (argc > 1) usage (1); /* Allocate an CTRL object. An empty object should be sufficient. */ ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno)); agent_exit (1); } /* Set the information which can't be taken from envvars. */ gnupg_prepare_get_passphrase (GPG_ERR_SOURCE_DEFAULT, opt.verbose, opt_agent_program, NULL, NULL, NULL); if (opt_prompt) opt_prompt = percent_plus_unescape (opt_prompt, 0); if (cmd == oProtect) read_and_protect (fname); else if (cmd == oUnprotect) read_and_unprotect (ctrl, fname); else if (cmd == oShadow) read_and_shadow (fname); else if (cmd == oShowShadowInfo) show_shadow_info (fname); else if (cmd == oShowKeygrip) show_keygrip (fname); else if (cmd == oS2Kcalibration) { if (!opt.verbose) opt.verbose++; /* We need to see something. */ get_standard_s2k_count (); } else show_file (fname); xfree (ctrl); agent_exit (0); return 8; /*NOTREACHED*/ } void agent_exit (int rc) { rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } /* Return the passphrase string and ask the agent if it has not been set from the command line PROMPTNO select the prompt to display: 0 = default 1 = taken from the option --prompt 2 = for unprotecting a pkcs#12 object 3 = for protecting a new pkcs#12 object 4 = for protecting an imported pkcs#12 in our system */ static char * get_passphrase (int promptno) { char *pw; int err; const char *desc; char *orig_codeset; int repeat = 0; if (opt_passphrase) return xstrdup (opt_passphrase); orig_codeset = i18n_switchto_utf8 (); if (promptno == 1 && opt_prompt) { desc = opt_prompt; } else if (promptno == 2) { desc = _("Please enter the passphrase to unprotect the " "PKCS#12 object."); } else if (promptno == 3) { desc = _("Please enter the passphrase to protect the " "new PKCS#12 object."); repeat = 1; } else if (promptno == 4) { desc = _("Please enter the passphrase to protect the " "imported object within the GnuPG system."); repeat = 1; } else desc = _("Please enter the passphrase or the PIN\n" "needed to complete this operation."); i18n_switchback (orig_codeset); err = gnupg_get_passphrase (NULL, NULL, _("Passphrase:"), desc, repeat, repeat, 1, &pw); if (err) { if (gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) log_info (_("cancelled\n")); else log_error (_("error while asking for the passphrase: %s\n"), gpg_strerror (err)); agent_exit (0); } log_assert (pw); return pw; } static void release_passphrase (char *pw) { if (pw) { wipememory (pw, strlen (pw)); xfree (pw); } } /* Stub function. */ int agent_key_available (const unsigned char *grip) { (void)grip; return -1; /* Not available. */ } char * agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) { (void)ctrl; (void)key; (void)cache_mode; return NULL; } gpg_error_t agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *initial_errtext, struct pin_entry_info_s *pininfo, const char *keyinfo, cache_mode_t cache_mode) { gpg_error_t err; unsigned char *passphrase; size_t size; (void)ctrl; (void)desc_text; (void)prompt_text; (void)initial_errtext; (void)keyinfo; (void)cache_mode; *pininfo->pin = 0; /* Reset the PIN. */ passphrase = get_passphrase (0); size = strlen (passphrase); if (size >= pininfo->max_length) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (&pininfo->pin, passphrase, size); xfree (passphrase); pininfo->pin[size] = 0; if (pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; err = pininfo->check_cb (pininfo); } else err = 0; return err; } /* Replacement for the function in findkey.c. Here we write the key * to stdout. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref) { char hexgrip[40+4+1]; char *p; (void)force; (void)serialno; (void)keyref; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); p = make_advanced (buffer, length); if (p) { printf ("# Begin dump of %s\n%s%s# End dump of %s\n", hexgrip, p, (*p && p[strlen(p)-1] == '\n')? "":"\n", hexgrip); xfree (p); } return 0; } diff --git a/common/argparse.c b/common/argparse.c index c2ba52e3f..b30499bcc 100644 --- a/common/argparse.c +++ b/common/argparse.c @@ -1,1660 +1,1663 @@ /* argparse.c - Argument Parser for option handling * Copyright (C) 1997-2001, 2006-2008, 2013-2017 Werner Koch * Copyright (C) 1998-2001, 2006-2008, 2012 Free Software Foundation, Inc. * Copyright (C) 2015-2017 g10 Code GmbH * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This file 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 Lesser General Public License * along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1+ */ /* This file may be used as part of GnuPG or standalone. A GnuPG build is detected by the presence of the macro GNUPG_MAJOR_VERSION. Some feature are only available in the GnuPG build mode. */ #ifdef HAVE_CONFIG_H #include #endif +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #include #include #include #include #include #include #include #ifdef GNUPG_MAJOR_VERSION # include "util.h" # include "common-defs.h" # include "i18n.h" # include "mischelp.h" # include "stringhelp.h" # include "logging.h" # include "utf8conv.h" #endif /*GNUPG_MAJOR_VERSION*/ #include "argparse.h" /* GnuPG uses GPLv3+ but a standalone version of this defaults to GPLv2+ because that is the license of this file. Change this if you include it in a program which uses GPLv3. If you don't want to set a copyright string for your usage() you may also hardcode it here. */ #ifndef GNUPG_MAJOR_VERSION # define ARGPARSE_GPL_VERSION 2 # define ARGPARSE_CRIGHT_STR "Copyright (C) YEAR NAME" #else /* Used by GnuPG */ # define ARGPARSE_GPL_VERSION 3 # define ARGPARSE_CRIGHT_STR "Copyright (C) 2018 Free Software Foundation, Inc." #endif /*GNUPG_MAJOR_VERSION*/ /* Replacements for standalone builds. */ #ifndef GNUPG_MAJOR_VERSION # ifndef _ # define _(a) (a) # endif # ifndef DIM # define DIM(v) (sizeof(v)/sizeof((v)[0])) # endif # define xtrymalloc(a) malloc ((a)) # define xtryrealloc(a,b) realloc ((a), (b)) # define xtrystrdup(a) strdup ((a)) # define xfree(a) free ((a)) # define log_error my_log_error # define log_bug my_log_bug # define trim_spaces(a) my_trim_spaces ((a)) # define map_static_macro_string(a) (a) #endif /*!GNUPG_MAJOR_VERSION*/ #define ARGPARSE_STR(v) #v #define ARGPARSE_STR2(v) ARGPARSE_STR(v) /* Replacements for standalone builds. */ #ifndef GNUPG_MAJOR_VERSION static void my_log_error (const char *fmt, ...) { va_list arg_ptr ; va_start (arg_ptr, fmt); fprintf (stderr, "%s: ", strusage (11)); vfprintf (stderr, fmt, arg_ptr); va_end (arg_ptr); } static void my_log_bug (const char *fmt, ...) { va_list arg_ptr ; va_start (arg_ptr, fmt); fprintf (stderr, "%s: Ohhhh jeeee: ", strusage (11)); vfprintf (stderr, fmt, arg_ptr); va_end (arg_ptr); abort (); } /* Return true if the native charset is utf-8. */ static int is_native_utf8 (void) { return 1; } static char * my_trim_spaces (char *str) { char *string, *p, *mark; string = str; /* Find first non space character. */ for (p=string; *p && isspace (*(unsigned char*)p) ; p++) ; /* Move characters. */ for ((mark = NULL); (*string = *p); string++, p++) if (isspace (*(unsigned char*)p)) { if (!mark) mark = string; } else mark = NULL; if (mark) *mark = '\0' ; /* Remove trailing spaces. */ return str ; } #endif /*!GNUPG_MAJOR_VERSION*/ /********************************* * @Summary arg_parse * #include "argparse.h" * * typedef struct { * char *argc; pointer to argc (value subject to change) * char ***argv; pointer to argv (value subject to change) * unsigned flags; Global flags (DO NOT CHANGE) * int err; print error about last option * 1 = warning, 2 = abort * int r_opt; return option * int r_type; type of return value (0 = no argument found) * union { * int ret_int; * long ret_long * ulong ret_ulong; * char *ret_str; * } r; Return values * struct { * int idx; * const char *last; * void *aliases; * } internal; DO NOT CHANGE * } ARGPARSE_ARGS; * * typedef struct { * int short_opt; * const char *long_opt; * unsigned flags; * } ARGPARSE_OPTS; * * int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts ); * * @Description * This is my replacement for getopt(). See the example for a typical usage. * Global flags are: * Bit 0 : Do not remove options form argv * Bit 1 : Do not stop at last option but return other args * with r_opt set to -1. * Bit 2 : Assume options and real args are mixed. * Bit 3 : Do not use -- to stop option processing. * Bit 4 : Do not skip the first arg. * Bit 5 : allow usage of long option with only one dash * Bit 6 : ignore --version * all other bits must be set to zero, this value is modified by the * function, so assume this is write only. * Local flags (for each option): * Bit 2-0 : 0 = does not take an argument * 1 = takes int argument * 2 = takes string argument * 3 = takes long argument * 4 = takes ulong argument * Bit 3 : argument is optional (r_type will the be set to 0) * Bit 4 : allow 0x etc. prefixed values. * Bit 6 : Ignore this option * Bit 7 : This is a command and not an option * You stop the option processing by setting opts to NULL, the function will * then return 0. * @Return Value * Returns the args.r_opt or 0 if ready * r_opt may be -2/-7 to indicate an unknown option/command. * @See Also * ArgExpand * @Notes * You do not need to process the options 'h', '--help' or '--version' * because this function includes standard help processing; but if you * specify '-h', '--help' or '--version' you have to do it yourself. * The option '--' stops argument processing; if bit 1 is set the function * continues to return normal arguments. * To process float args or unsigned args you must use a string args and do * the conversion yourself. * @Example * * ARGPARSE_OPTS opts[] = { * { 'v', "verbose", 0 }, * { 'd', "debug", 0 }, * { 'o', "output", 2 }, * { 'c', "cross-ref", 2|8 }, * { 'm', "my-option", 1|8 }, * { 300, "ignored-long-option, ARGPARSE_OP_IGNORE}, * { 500, "have-no-short-option-for-this-long-option", 0 }, * {0} }; * ARGPARSE_ARGS pargs = { &argc, &argv, 0 } * * while( ArgParse( &pargs, &opts) ) { * switch( pargs.r_opt ) { * case 'v': opt.verbose++; break; * case 'd': opt.debug++; break; * case 'o': opt.outfile = pargs.r.ret_str; break; * case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; * case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; * case 500: opt.a_long_one++; break * default : pargs.err = 1; break; -- force warning output -- * } * } * if( argc > 1 ) * log_fatal( "Too many args"); * */ typedef struct alias_def_s *ALIAS_DEF; struct alias_def_s { ALIAS_DEF next; char *name; /* malloced buffer with name, \0, value */ const char *value; /* ptr into name */ }; /* Object to store the names for the --ignore-invalid-option option. This is a simple linked list. */ typedef struct iio_item_def_s *IIO_ITEM_DEF; struct iio_item_def_s { IIO_ITEM_DEF next; char name[1]; /* String with the long option name. */ }; static const char *(*strusage_handler)( int ) = NULL; static int (*custom_outfnc) (int, const char *); static int set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s); static void show_help(ARGPARSE_OPTS *opts, unsigned flags); static void show_version(void); static int writestrings (int is_error, const char *string, ...) #if __GNUC__ >= 4 __attribute__ ((sentinel(0))) #endif ; void argparse_register_outfnc (int (*fnc)(int, const char *)) { custom_outfnc = fnc; } /* Write STRING and all following const char * arguments either to stdout or, if IS_ERROR is set, to stderr. The list of strings must be terminated by a NULL. */ static int writestrings (int is_error, const char *string, ...) { va_list arg_ptr; const char *s; int count = 0; if (string) { s = string; va_start (arg_ptr, string); do { if (custom_outfnc) custom_outfnc (is_error? 2:1, s); else fputs (s, is_error? stderr : stdout); count += strlen (s); } while ((s = va_arg (arg_ptr, const char *))); va_end (arg_ptr); } return count; } static void flushstrings (int is_error) { if (custom_outfnc) custom_outfnc (is_error? 2:1, NULL); else fflush (is_error? stderr : stdout); } static void initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno ) { if( !(arg->flags & (1<<15)) ) { /* Initialize this instance. */ arg->internal.idx = 0; arg->internal.last = NULL; arg->internal.inarg = 0; arg->internal.stopped = 0; arg->internal.aliases = NULL; arg->internal.cur_alias = NULL; arg->internal.iio_list = NULL; arg->err = 0; arg->flags |= 1<<15; /* Mark as initialized. */ if ( *arg->argc < 0 ) log_bug ("invalid argument for arg_parse\n"); } if (arg->err) { /* Last option was erroneous. */ const char *s; if (filename) { if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG ) s = _("argument not expected"); else if ( arg->r_opt == ARGPARSE_READ_ERROR ) s = _("read error"); else if ( arg->r_opt == ARGPARSE_KEYWORD_TOO_LONG ) s = _("keyword too long"); else if ( arg->r_opt == ARGPARSE_MISSING_ARG ) s = _("missing argument"); else if ( arg->r_opt == ARGPARSE_INVALID_ARG ) s = _("invalid argument"); else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND ) s = _("invalid command"); else if ( arg->r_opt == ARGPARSE_INVALID_ALIAS ) s = _("invalid alias definition"); else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE ) s = _("out of core"); else s = _("invalid option"); log_error ("%s:%u: %s\n", filename, *lineno, s); } else { s = arg->internal.last? arg->internal.last:"[??]"; if ( arg->r_opt == ARGPARSE_MISSING_ARG ) log_error (_("missing argument for option \"%.50s\"\n"), s); else if ( arg->r_opt == ARGPARSE_INVALID_ARG ) log_error (_("invalid argument for option \"%.50s\"\n"), s); else if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG ) log_error (_("option \"%.50s\" does not expect an argument\n"), s); else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND ) log_error (_("invalid command \"%.50s\"\n"), s); else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_OPTION ) log_error (_("option \"%.50s\" is ambiguous\n"), s); else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_COMMAND ) log_error (_("command \"%.50s\" is ambiguous\n"),s ); else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE ) log_error ("%s\n", _("out of core\n")); else log_error (_("invalid option \"%.50s\"\n"), s); } if (arg->err != ARGPARSE_PRINT_WARNING) exit (2); arg->err = 0; } /* Zero out the return value union. */ arg->r.ret_str = NULL; arg->r.ret_long = 0; } static void store_alias( ARGPARSE_ARGS *arg, char *name, char *value ) { /* TODO: replace this dummy function with a rea one * and fix the problems IRIX has with (ALIAS_DEV)arg.. * used as lvalue */ (void)arg; (void)name; (void)value; #if 0 ALIAS_DEF a = xmalloc( sizeof *a ); a->name = name; a->value = value; a->next = (ALIAS_DEF)arg->internal.aliases; (ALIAS_DEF)arg->internal.aliases = a; #endif } /* Return true if KEYWORD is in the ignore-invalid-option list. */ static int ignore_invalid_option_p (ARGPARSE_ARGS *arg, const char *keyword) { IIO_ITEM_DEF item = arg->internal.iio_list; for (; item; item = item->next) if (!strcmp (item->name, keyword)) return 1; return 0; } /* Add the keywords up to the next LF to the list of to be ignored options. After returning FP will either be at EOF or the next character read will be the first of a new line. The function returns 0 on success or true on malloc failure. */ static int ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp) { IIO_ITEM_DEF item; int c; char name[100]; int namelen = 0; int ready = 0; enum { skipWS, collectNAME, skipNAME, addNAME} state = skipWS; while (!ready) { c = getc (fp); if (c == '\n') ready = 1; else if (c == EOF) { c = '\n'; ready = 1; } again: switch (state) { case skipWS: if (!isascii (c) || !isspace(c)) { namelen = 0; state = collectNAME; goto again; } break; case collectNAME: if (isspace (c)) { state = addNAME; goto again; } else if (namelen < DIM(name)-1) name[namelen++] = c; else /* Too long. */ state = skipNAME; break; case skipNAME: if (isspace (c)) { state = skipWS; goto again; } break; case addNAME: name[namelen] = 0; if (!ignore_invalid_option_p (arg, name)) { item = xtrymalloc (sizeof *item + namelen); if (!item) return 1; strcpy (item->name, name); item->next = (IIO_ITEM_DEF)arg->internal.iio_list; arg->internal.iio_list = item; } state = skipWS; goto again; } } return 0; } /* Clear the entire ignore-invalid-option list. */ static void ignore_invalid_option_clear (ARGPARSE_ARGS *arg) { IIO_ITEM_DEF item, tmpitem; for (item = arg->internal.iio_list; item; item = tmpitem) { tmpitem = item->next; xfree (item); } arg->internal.iio_list = NULL; } /**************** * Get options from a file. * Lines starting with '#' are comment lines. * Syntax is simply a keyword and the argument. * Valid keywords are all keywords from the long_opt list without * the leading dashes. The special keywords "help", "warranty" and "version" * are not valid here. * The special keyword "alias" may be used to store alias definitions, * which are later expanded like long options. * The option * ignore-invalid-option OPTIONNAMEs * is recognized and updates a list of option which should be ignored if they * are not defined. * Caller must free returned strings. * If called with FP set to NULL command line args are parse instead. * * Q: Should we allow the syntax * keyword = value * and accept for boolean options a value of 1/0, yes/no or true/false? * Note: Abbreviation of options is here not allowed. */ int optfile_parse (FILE *fp, const char *filename, unsigned *lineno, ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) { int state, i, c; int idx=0; char keyword[100]; char *buffer = NULL; size_t buflen = 0; int in_alias=0; int unread_buf[3]; /* We use an int so that we can store EOF. */ int unread_buf_count = 0; if (!fp) /* Divert to arg_parse() in this case. */ return arg_parse (arg, opts); initialize (arg, filename, lineno); /* If the LINENO is zero we assume that we are at the start of a * file and we skip over a possible Byte Order Mark. */ if (!*lineno) { unread_buf[0] = getc (fp); unread_buf[1] = getc (fp); unread_buf[2] = getc (fp); if (unread_buf[0] != 0xef || unread_buf[1] != 0xbb || unread_buf[2] != 0xbf) unread_buf_count = 3; } /* Find the next keyword. */ state = i = 0; for (;;) { if (unread_buf_count) c = unread_buf[3 - unread_buf_count--]; else c = getc (fp); if (c == '\n' || c== EOF ) { if ( c != EOF ) ++*lineno; if (state == -1) break; else if (state == 2) { keyword[i] = 0; for (i=0; opts[i].short_opt; i++ ) { if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) break; } idx = i; arg->r_opt = opts[idx].short_opt; if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) { state = i = 0; continue; } else if (!opts[idx].short_opt ) { if (!strcmp (keyword, "ignore-invalid-option")) { /* No argument - ignore this meta option. */ state = i = 0; continue; } else if (ignore_invalid_option_p (arg, keyword)) { /* This invalid option is in the iio list. */ state = i = 0; continue; } arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND) ? ARGPARSE_INVALID_COMMAND : ARGPARSE_INVALID_OPTION); } else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) arg->r_type = 0; /* Does not take an arg. */ else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL) ) arg->r_type = 0; /* Arg is optional. */ else arg->r_opt = ARGPARSE_MISSING_ARG; break; } else if (state == 3) { /* No argument found. */ if (in_alias) arg->r_opt = ARGPARSE_MISSING_ARG; else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) arg->r_type = 0; /* Does not take an arg. */ else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL)) arg->r_type = 0; /* No optional argument. */ else arg->r_opt = ARGPARSE_MISSING_ARG; break; } else if (state == 4) { /* Has an argument. */ if (in_alias) { if (!buffer) arg->r_opt = ARGPARSE_UNEXPECTED_ARG; else { char *p; buffer[i] = 0; p = strpbrk (buffer, " \t"); if (p) { *p++ = 0; trim_spaces (p); } if (!p || !*p) { xfree (buffer); arg->r_opt = ARGPARSE_INVALID_ALIAS; } else { store_alias (arg, buffer, p); } } } else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK)) arg->r_opt = ARGPARSE_UNEXPECTED_ARG; else { char *p; if (!buffer) { keyword[i] = 0; buffer = xtrystrdup (keyword); if (!buffer) arg->r_opt = ARGPARSE_OUT_OF_CORE; } else buffer[i] = 0; if (buffer) { trim_spaces (buffer); p = buffer; if (*p == '"') { /* Remove quotes. */ p++; if (*p && p[strlen(p)-1] == '\"' ) p[strlen(p)-1] = 0; } if (!set_opt_arg (arg, opts[idx].flags, p)) xfree (buffer); else gpgrt_annotate_leaked_object (buffer); } } break; } else if (c == EOF) { ignore_invalid_option_clear (arg); if (ferror (fp)) arg->r_opt = ARGPARSE_READ_ERROR; else arg->r_opt = 0; /* EOF. */ break; } state = 0; i = 0; } else if (state == -1) ; /* Skip. */ else if (state == 0 && isascii (c) && isspace(c)) ; /* Skip leading white space. */ else if (state == 0 && c == '#' ) state = 1; /* Start of a comment. */ else if (state == 1) ; /* Skip comments. */ else if (state == 2 && isascii (c) && isspace(c)) { /* Check keyword. */ keyword[i] = 0; for (i=0; opts[i].short_opt; i++ ) if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword)) break; idx = i; arg->r_opt = opts[idx].short_opt; if ((opts[idx].flags & ARGPARSE_OPT_IGNORE)) { state = 1; /* Process like a comment. */ } else if (!opts[idx].short_opt) { if (!strcmp (keyword, "alias")) { in_alias = 1; state = 3; } else if (!strcmp (keyword, "ignore-invalid-option")) { if (ignore_invalid_option_add (arg, fp)) { arg->r_opt = ARGPARSE_OUT_OF_CORE; break; } state = i = 0; ++*lineno; } else if (ignore_invalid_option_p (arg, keyword)) state = 1; /* Process like a comment. */ else { arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND) ? ARGPARSE_INVALID_COMMAND : ARGPARSE_INVALID_OPTION); state = -1; /* Skip rest of line and leave. */ } } else state = 3; } else if (state == 3) { /* Skip leading spaces of the argument. */ if (!isascii (c) || !isspace(c)) { i = 0; keyword[i++] = c; state = 4; } } else if (state == 4) { /* Collect the argument. */ if (buffer) { if (i < buflen-1) buffer[i++] = c; else { char *tmp; size_t tmplen = buflen + 50; tmp = xtryrealloc (buffer, tmplen); if (tmp) { buflen = tmplen; buffer = tmp; buffer[i++] = c; } else { xfree (buffer); arg->r_opt = ARGPARSE_OUT_OF_CORE; break; } } } else if (i < DIM(keyword)-1) keyword[i++] = c; else { size_t tmplen = DIM(keyword) + 50; buffer = xtrymalloc (tmplen); if (buffer) { buflen = tmplen; memcpy(buffer, keyword, i); buffer[i++] = c; } else { arg->r_opt = ARGPARSE_OUT_OF_CORE; break; } } } else if (i >= DIM(keyword)-1) { arg->r_opt = ARGPARSE_KEYWORD_TOO_LONG; state = -1; /* Skip rest of line and leave. */ } else { keyword[i++] = c; state = 2; } } return arg->r_opt; } static int find_long_option( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts, const char *keyword ) { int i; size_t n; (void)arg; /* Would be better if we can do a binary search, but it is not possible to reorder our option table because we would mess up our help strings - What we can do is: Build a nice option lookup table when this function is first invoked */ if( !*keyword ) return -1; for(i=0; opts[i].short_opt; i++ ) if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) ) return i; #if 0 { ALIAS_DEF a; /* see whether it is an alias */ for( a = args->internal.aliases; a; a = a->next ) { if( !strcmp( a->name, keyword) ) { /* todo: must parse the alias here */ args->internal.cur_alias = a; return -3; /* alias available */ } } } #endif /* not found, see whether it is an abbreviation */ /* aliases may not be abbreviated */ n = strlen( keyword ); for(i=0; opts[i].short_opt; i++ ) { if( opts[i].long_opt && !strncmp( opts[i].long_opt, keyword, n ) ) { int j; for(j=i+1; opts[j].short_opt; j++ ) { if( opts[j].long_opt && !strncmp( opts[j].long_opt, keyword, n ) && !(opts[j].short_opt == opts[i].short_opt && opts[j].flags == opts[i].flags ) ) return -2; /* abbreviation is ambiguous */ } return i; } } return -1; /* Not found. */ } int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts) { int idx; int argc; char **argv; char *s, *s2; int i; /* Fill in missing standard options: help, version, warranty and * dump-options. */ ARGPARSE_OPTS help_opt = ARGPARSE_s_n (ARGPARSE_SHORTOPT_HELP, "help", "@"); ARGPARSE_OPTS version_opt = ARGPARSE_s_n (ARGPARSE_SHORTOPT_VERSION, "version", "@"); ARGPARSE_OPTS warranty_opt = ARGPARSE_s_n (ARGPARSE_SHORTOPT_WARRANTY, "warranty", "@"); ARGPARSE_OPTS dump_options_opt = ARGPARSE_s_n(ARGPARSE_SHORTOPT_DUMP_OPTIONS, "dump-options", "@"); int seen_help = 0; int seen_version = 0; int seen_warranty = 0; int seen_dump_options = 0; i = 0; while (opts[i].short_opt) { if (opts[i].long_opt) { if (!strcmp(opts[i].long_opt, help_opt.long_opt)) seen_help = 1; else if (!strcmp(opts[i].long_opt, version_opt.long_opt)) seen_version = 1; else if (!strcmp(opts[i].long_opt, warranty_opt.long_opt)) seen_warranty = 1; else if (!strcmp(opts[i].long_opt, dump_options_opt.long_opt)) seen_dump_options = 1; } i++; } if (! seen_help) opts[i++] = help_opt; if (! seen_version) opts[i++] = version_opt; if (! seen_warranty) opts[i++] = warranty_opt; if (! seen_dump_options) opts[i++] = dump_options_opt; initialize( arg, NULL, NULL ); argc = *arg->argc; argv = *arg->argv; idx = arg->internal.idx; if (!idx && argc && !(arg->flags & ARGPARSE_FLAG_ARG0)) { /* Skip the first argument. */ argc--; argv++; idx++; } next_one: if (!argc) { /* No more args. */ arg->r_opt = 0; goto leave; /* Ready. */ } s = *argv; arg->internal.last = s; if (arg->internal.stopped && (arg->flags & ARGPARSE_FLAG_ALL)) { arg->r_opt = ARGPARSE_IS_ARG; /* Not an option but an argument. */ arg->r_type = 2; arg->r.ret_str = s; argc--; argv++; idx++; /* set to next one */ } else if( arg->internal.stopped ) { arg->r_opt = 0; goto leave; /* Ready. */ } else if ( *s == '-' && s[1] == '-' ) { /* Long option. */ char *argpos; arg->internal.inarg = 0; if (!s[2] && !(arg->flags & ARGPARSE_FLAG_NOSTOP)) { /* Stop option processing. */ arg->internal.stopped = 1; arg->flags |= ARGPARSE_FLAG_STOP_SEEN; argc--; argv++; idx++; goto next_one; } argpos = strchr( s+2, '=' ); if ( argpos ) *argpos = 0; i = find_long_option ( arg, opts, s+2 ); if ( argpos ) *argpos = '='; if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_HELP) show_help (opts, arg->flags); else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_VERSION) { if (!(arg->flags & ARGPARSE_FLAG_NOVERSION)) { show_version (); exit(0); } } else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_WARRANTY) { writestrings (0, strusage (16), "\n", NULL); exit (0); } else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_DUMP_OPTIONS) { for (i=0; opts[i].short_opt; i++ ) { if (opts[i].long_opt && !(opts[i].flags & ARGPARSE_OPT_IGNORE)) writestrings (0, "--", opts[i].long_opt, "\n", NULL); } exit (0); } if ( i == -2 ) arg->r_opt = ARGPARSE_AMBIGUOUS_OPTION; else if ( i == -1 ) { arg->r_opt = ARGPARSE_INVALID_OPTION; arg->r.ret_str = s+2; } else arg->r_opt = opts[i].short_opt; if ( i < 0 ) ; else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) { if ( argpos ) { s2 = argpos+1; if ( !*s2 ) s2 = NULL; } else s2 = argv[1]; if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) { arg->r_type = ARGPARSE_TYPE_NONE; /* Argument is optional. */ } else if ( !s2 ) { arg->r_opt = ARGPARSE_MISSING_ARG; } else if ( !argpos && *s2 == '-' && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) { /* The argument is optional and the next seems to be an option. We do not check this possible option but assume no argument */ arg->r_type = ARGPARSE_TYPE_NONE; } else { set_opt_arg (arg, opts[i].flags, s2); if ( !argpos ) { argc--; argv++; idx++; /* Skip one. */ } } } else { /* Does not take an argument. */ if ( argpos ) arg->r_type = ARGPARSE_UNEXPECTED_ARG; else arg->r_type = 0; } argc--; argv++; idx++; /* Set to next one. */ } else if ( (*s == '-' && s[1]) || arg->internal.inarg ) { /* Short option. */ int dash_kludge = 0; i = 0; if ( !arg->internal.inarg ) { arg->internal.inarg++; if ( (arg->flags & ARGPARSE_FLAG_ONEDASH) ) { for (i=0; opts[i].short_opt; i++ ) if ( opts[i].long_opt && !strcmp (opts[i].long_opt, s+1)) { dash_kludge = 1; break; } } } s += arg->internal.inarg; if (!dash_kludge ) { for (i=0; opts[i].short_opt; i++ ) if ( opts[i].short_opt == *s ) break; } if ( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) ) show_help (opts, arg->flags); arg->r_opt = opts[i].short_opt; if (!opts[i].short_opt ) { arg->r_opt = (opts[i].flags & ARGPARSE_OPT_COMMAND)? ARGPARSE_INVALID_COMMAND:ARGPARSE_INVALID_OPTION; arg->internal.inarg++; /* Point to the next arg. */ arg->r.ret_str = s; } else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) ) { if ( s[1] && !dash_kludge ) { s2 = s+1; set_opt_arg (arg, opts[i].flags, s2); } else { s2 = argv[1]; if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) { arg->r_type = ARGPARSE_TYPE_NONE; } else if ( !s2 ) { arg->r_opt = ARGPARSE_MISSING_ARG; } else if ( *s2 == '-' && s2[1] && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) ) { /* The argument is optional and the next seems to be an option. We do not check this possible option but assume no argument. */ arg->r_type = ARGPARSE_TYPE_NONE; } else { set_opt_arg (arg, opts[i].flags, s2); argc--; argv++; idx++; /* Skip one. */ } } s = "x"; /* This is so that !s[1] yields false. */ } else { /* Does not take an argument. */ arg->r_type = ARGPARSE_TYPE_NONE; arg->internal.inarg++; /* Point to the next arg. */ } if ( !s[1] || dash_kludge ) { /* No more concatenated short options. */ arg->internal.inarg = 0; argc--; argv++; idx++; } } else if ( arg->flags & ARGPARSE_FLAG_MIXED ) { arg->r_opt = ARGPARSE_IS_ARG; arg->r_type = 2; arg->r.ret_str = s; argc--; argv++; idx++; /* Set to next one. */ } else { arg->internal.stopped = 1; /* Stop option processing. */ goto next_one; } leave: *arg->argc = argc; *arg->argv = argv; arg->internal.idx = idx; return arg->r_opt; } /* Returns: -1 on error, 0 for an integer type and 1 for a non integer type argument. */ static int set_opt_arg (ARGPARSE_ARGS *arg, unsigned flags, char *s) { int base = (flags & ARGPARSE_OPT_PREFIX)? 0 : 10; long l; switch ( (arg->r_type = (flags & ARGPARSE_TYPE_MASK)) ) { case ARGPARSE_TYPE_LONG: case ARGPARSE_TYPE_INT: errno = 0; l = strtol (s, NULL, base); if ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) { arg->r_opt = ARGPARSE_INVALID_ARG; return -1; } if (arg->r_type == ARGPARSE_TYPE_LONG) arg->r.ret_long = l; else if ( (l < 0 && l < INT_MIN) || l > INT_MAX ) { arg->r_opt = ARGPARSE_INVALID_ARG; return -1; } else arg->r.ret_int = (int)l; return 0; case ARGPARSE_TYPE_ULONG: while (isascii (*s) && isspace(*s)) s++; if (*s == '-') { arg->r.ret_ulong = 0; arg->r_opt = ARGPARSE_INVALID_ARG; return -1; } errno = 0; arg->r.ret_ulong = strtoul (s, NULL, base); if (arg->r.ret_ulong == ULONG_MAX && errno == ERANGE) { arg->r_opt = ARGPARSE_INVALID_ARG; return -1; } return 0; case ARGPARSE_TYPE_STRING: default: arg->r.ret_str = s; return 1; } } static size_t long_opt_strlen( ARGPARSE_OPTS *o ) { size_t n = strlen (o->long_opt); if ( o->description && *o->description == '|' ) { const char *s; int is_utf8 = is_native_utf8 (); s=o->description+1; if ( *s != '=' ) n++; /* For a (mostly) correct length calculation we exclude continuation bytes (10xxxxxx) if we are on a native utf8 terminal. */ for (; *s && *s != '|'; s++ ) if ( is_utf8 && (*s&0xc0) != 0x80 ) n++; } return n; } /**************** * Print formatted help. The description string has some special * meanings: * - A description string which is "@" suppresses help output for * this option * - a description,ine which starts with a '@' and is followed by * any other characters is printed as is; this may be used for examples * and such. * - A description which starts with a '|' outputs the string between this * bar and the next one as arguments of the long option. */ static void show_help (ARGPARSE_OPTS *opts, unsigned int flags) { const char *s; char tmp[2]; show_version (); writestrings (0, "\n", NULL); s = strusage (42); if (s && *s == '1') { s = strusage (40); writestrings (1, s, NULL); if (*s && s[strlen(s)] != '\n') writestrings (1, "\n", NULL); } s = strusage(41); writestrings (0, s, "\n", NULL); if ( opts[0].description ) { /* Auto format the option description. */ int i,j, indent; /* Get max. length of long options. */ for (i=indent=0; opts[i].short_opt; i++ ) { if ( opts[i].long_opt ) if ( !opts[i].description || *opts[i].description != '@' ) if ( (j=long_opt_strlen(opts+i)) > indent && j < 35 ) indent = j; } /* Example: " -v, --verbose Viele Sachen ausgeben" */ indent += 10; if ( *opts[0].description != '@' ) writestrings (0, "Options:", "\n", NULL); for (i=0; opts[i].short_opt; i++ ) { s = map_static_macro_string (_( opts[i].description )); if ( s && *s== '@' && !s[1] ) /* Hide this line. */ continue; if ( s && *s == '@' ) /* Unindented comment only line. */ { for (s++; *s; s++ ) { if ( *s == '\n' ) { if( s[1] ) writestrings (0, "\n", NULL); } else { tmp[0] = *s; tmp[1] = 0; writestrings (0, tmp, NULL); } } writestrings (0, "\n", NULL); continue; } j = 3; if ( opts[i].short_opt < 256 ) { tmp[0] = opts[i].short_opt; tmp[1] = 0; writestrings (0, " -", tmp, NULL ); if ( !opts[i].long_opt ) { if (s && *s == '|' ) { writestrings (0, " ", NULL); j++; for (s++ ; *s && *s != '|'; s++, j++ ) { tmp[0] = *s; tmp[1] = 0; writestrings (0, tmp, NULL); } if ( *s ) s++; } } } else writestrings (0, " ", NULL); if ( opts[i].long_opt ) { tmp[0] = opts[i].short_opt < 256?',':' '; tmp[1] = 0; j += writestrings (0, tmp, " --", opts[i].long_opt, NULL); if (s && *s == '|' ) { if ( *++s != '=' ) { writestrings (0, " ", NULL); j++; } for ( ; *s && *s != '|'; s++, j++ ) { tmp[0] = *s; tmp[1] = 0; writestrings (0, tmp, NULL); } if ( *s ) s++; } writestrings (0, " ", NULL); j += 3; } for (;j < indent; j++ ) writestrings (0, " ", NULL); if ( s ) { if ( *s && j > indent ) { writestrings (0, "\n", NULL); for (j=0;j < indent; j++ ) writestrings (0, " ", NULL); } for (; *s; s++ ) { if ( *s == '\n' ) { if ( s[1] ) { writestrings (0, "\n", NULL); for (j=0; j < indent; j++ ) writestrings (0, " ", NULL); } } else { tmp[0] = *s; tmp[1] = 0; writestrings (0, tmp, NULL); } } } writestrings (0, "\n", NULL); } if ( (flags & ARGPARSE_FLAG_ONEDASH) ) writestrings (0, "\n(A single dash may be used " "instead of the double ones)\n", NULL); } if ( (s=strusage(19)) ) { writestrings (0, "\n", NULL); writestrings (0, s, NULL); } flushstrings (0); exit(0); } static void show_version () { const char *s; int i; /* Version line. */ writestrings (0, strusage (11), NULL); if ((s=strusage (12))) writestrings (0, " (", s, ")", NULL); writestrings (0, " ", strusage (13), "\n", NULL); /* Additional version lines. */ for (i=20; i < 30; i++) if ((s=strusage (i))) writestrings (0, s, "\n", NULL); /* Copyright string. */ if ((s=strusage (14))) writestrings (0, s, "\n", NULL); /* Licence string. */ if( (s=strusage (10)) ) writestrings (0, s, "\n", NULL); /* Copying conditions. */ if ( (s=strusage(15)) ) writestrings (0, s, NULL); /* Thanks. */ if ((s=strusage(18))) writestrings (0, s, NULL); /* Additional program info. */ for (i=30; i < 40; i++ ) if ( (s=strusage (i)) ) writestrings (0, s, NULL); flushstrings (0); } void usage (int level) { const char *p; if (!level) { writestrings (1, strusage(11), " ", strusage(13), "; ", strusage (14), "\n", NULL); flushstrings (1); } else if (level == 1) { p = strusage (40); writestrings (1, p, NULL); if (*p && p[strlen(p)] != '\n') writestrings (1, "\n", NULL); exit (2); } else if (level == 2) { p = strusage (42); if (p && *p == '1') { p = strusage (40); writestrings (1, p, NULL); if (*p && p[strlen(p)] != '\n') writestrings (1, "\n", NULL); } writestrings (0, strusage(41), "\n", NULL); exit (0); } } /* Level * 0: Print copyright string to stderr * 1: Print a short usage hint to stderr and terminate * 2: Print a long usage hint to stdout and terminate * 10: Return license info string * 11: Return the name of the program * 12: Return optional name of package which includes this program. * 13: version string * 14: copyright string * 15: Short copying conditions (with LFs) * 16: Long copying conditions (with LFs) * 17: Optional printable OS name * 18: Optional thanks list (with LFs) * 19: Bug report info *20..29: Additional lib version strings. *30..39: Additional program info (with LFs) * 40: short usage note (with LF) * 41: long usage note (with LF) * 42: Flag string: * First char is '1': * The short usage notes needs to be printed * before the long usage note. */ const char * strusage( int level ) { const char *p = strusage_handler? strusage_handler(level) : NULL; if ( p ) return map_static_macro_string (p); switch ( level ) { case 10: #if ARGPARSE_GPL_VERSION == 3 p = ("License GPLv3+: GNU GPL version 3 or later " ""); #else p = ("License GPLv2+: GNU GPL version 2 or later " ""); #endif break; case 11: p = "foo"; break; case 13: p = "0.0"; break; case 14: p = ARGPARSE_CRIGHT_STR; break; case 15: p = "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n"; break; case 16: p = "This is free software; you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation; either version " ARGPARSE_STR2(ARGPARSE_GPL_VERSION) " of the License, or\n" "(at your option) any later version.\n\n" "It is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n\n" "You should have received a copy of the GNU General Public License\n" "along with this software. If not, see .\n"; break; case 40: /* short and long usage */ case 41: p = ""; break; } return p; } /* Set the usage handler. This function is basically a constructor. */ void set_strusage ( const char *(*f)( int ) ) { strusage_handler = f; } #ifdef TEST static struct { int verbose; int debug; char *outfile; char *crf; int myopt; int echo; int a_long_one; } opt; int main(int argc, char **argv) { ARGPARSE_OPTS opts[] = { ARGPARSE_x('v', "verbose", NONE, 0, "Laut sein"), ARGPARSE_s_n('e', "echo" , ("Zeile ausgeben, damit wir sehen, " "was wir eingegeben haben")), ARGPARSE_s_n('d', "debug", "Debug\nfalls mal etwas\nschief geht"), ARGPARSE_s_s('o', "output", 0 ), ARGPARSE_o_s('c', "cross-ref", "cross-reference erzeugen\n" ), /* Note that on a non-utf8 terminal the ß might garble the output. */ ARGPARSE_s_n('s', "street","|Straße|set the name of the street to Straße"), ARGPARSE_o_i('m', "my-option", 0), ARGPARSE_s_n(500, "a-long-option", 0 ), ARGPARSE_end() }; ARGPARSE_ARGS pargs = { &argc, &argv, (ARGPARSE_FLAG_ALL | ARGPARSE_FLAG_MIXED | ARGPARSE_FLAG_ONEDASH) }; int i; while (arg_parse (&pargs, opts)) { switch (pargs.r_opt) { case ARGPARSE_IS_ARG : printf ("arg='%s'\n", pargs.r.ret_str); break; case 'v': opt.verbose++; break; case 'e': opt.echo++; break; case 'd': opt.debug++; break; case 'o': opt.outfile = pargs.r.ret_str; break; case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break; case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break; case 500: opt.a_long_one++; break; default : pargs.err = ARGPARSE_PRINT_WARNING; break; } } for (i=0; i < argc; i++ ) printf ("%3d -> (%s)\n", i, argv[i] ); puts ("Options:"); if (opt.verbose) printf (" verbose=%d\n", opt.verbose ); if (opt.debug) printf (" debug=%d\n", opt.debug ); if (opt.outfile) printf (" outfile='%s'\n", opt.outfile ); if (opt.crf) printf (" crffile='%s'\n", opt.crf ); if (opt.myopt) printf (" myopt=%d\n", opt.myopt ); if (opt.a_long_one) printf (" a-long-one=%d\n", opt.a_long_one ); if (opt.echo) printf (" echo=%d\n", opt.echo ); return 0; } #endif /*TEST*/ /**** bottom of file ****/ diff --git a/common/util.h b/common/util.h index 756f32c03..e1d9fa6fd 100644 --- a/common/util.h +++ b/common/util.h @@ -1,402 +1,401 @@ /* util.h - Utility functions for GnuPG * Copyright (C) 2001, 2002, 2003, 2004, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * 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 copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #ifndef GNUPG_COMMON_UTIL_H #define GNUPG_COMMON_UTIL_H #include /* We need this for the memory function protos. */ #include /* We need errno. */ #include /* We need gpg_error_t and estream. */ /* These error codes are used but not defined in the required * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ #ifndef EXTERN_UNLESS_MAIN_MODULE # if !defined (INCLUDED_BY_MAIN_MODULE) # define EXTERN_UNLESS_MAIN_MODULE extern # else # define EXTERN_UNLESS_MAIN_MODULE # endif #endif /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) /* The length of the keygrip. This is a SHA-1 hash of the key * parameters as generated by gcry_pk_get_keygrip. */ #define KEYGRIP_LEN 20 /* The length of the unique blob identifier as used by the keyboxd. * This is the possible truncated fingerprint of the primary key. */ #define UBID_LEN 20 /* Get all the stuff from jnlib. */ #include "../common/logging.h" -#include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/dotlock.h" #include "../common/utf8conv.h" #include "../common/dynload.h" #include "../common/fwddecl.h" #include "../common/utilproto.h" #include "gettime.h" /* Redefine asprintf by our estream version which uses our own memory allocator.. */ #define asprintf gpgrt_asprintf #define vasprintf gpgrt_vasprintf /* Due to a bug in mingw32's snprintf related to the 'l' modifier and for increased portability we use our snprintf on all systems. */ #undef snprintf #define snprintf gpgrt_snprintf /* Replacements for macros not available with libgpg-error < 1.20. */ /* We need this type even if we are not using libreadline and or we did not include libreadline in the current file. */ #ifndef GNUPG_LIBREADLINE_H_INCLUDED typedef char **rl_completion_func_t (const char *, int, int); #endif /*!GNUPG_LIBREADLINE_H_INCLUDED*/ /* Handy malloc macros - please use only them. */ #define xtrymalloc(a) gcry_malloc ((a)) #define xtrymalloc_secure(a) gcry_malloc_secure ((a)) #define xtrycalloc(a,b) gcry_calloc ((a),(b)) #define xtrycalloc_secure(a,b) gcry_calloc_secure ((a),(b)) #define xtryrealloc(a,b) gcry_realloc ((a),(b)) #define xtrystrdup(a) gcry_strdup ((a)) #define xfree(a) gcry_free ((a)) #define xfree_fnc gcry_free #define xmalloc(a) gcry_xmalloc ((a)) #define xmalloc_secure(a) gcry_xmalloc_secure ((a)) #define xcalloc(a,b) gcry_xcalloc ((a),(b)) #define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b)) #define xrealloc(a,b) gcry_xrealloc ((a),(b)) #define xstrdup(a) gcry_xstrdup ((a)) /* For compatibility with gpg 1.4 we also define these: */ #define xmalloc_clear(a) gcry_xcalloc (1, (a)) #define xmalloc_secure_clear(a) gcry_xcalloc_secure (1, (a)) /* The default error source of the application. This is different from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the source file and thus is usable in code shared by applications. Defined by init.c. */ extern gpg_err_source_t default_errsource; /* Convenience function to return a gpg-error code for memory allocation failures. This function makes sure that an error will be returned even if accidentally ERRNO is not set. */ static inline gpg_error_t out_of_core (void) { return gpg_error_from_syserror (); } /*-- yesno.c --*/ int answer_is_yes (const char *s); int answer_is_yes_no_default (const char *s, int def_answer); int answer_is_yes_no_quit (const char *s); int answer_is_okay_cancel (const char *s, int def_answer); /*-- xreadline.c --*/ ssize_t read_line (FILE *fp, char **addr_of_buffer, size_t *length_of_buffer, size_t *max_length); /*-- b64enc.c and b64dec.c --*/ struct b64state { unsigned int flags; int idx; int quad_count; FILE *fp; estream_t stream; char *title; unsigned char radbuf[4]; u32 crc; int stop_seen:1; int invalid_encoding:1; gpg_error_t lasterr; }; gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title); gpg_error_t b64enc_start_es (struct b64state *state, estream_t fp, const char *title); gpg_error_t b64enc_write (struct b64state *state, const void *buffer, size_t nbytes); gpg_error_t b64enc_finish (struct b64state *state); gpg_error_t b64dec_start (struct b64state *state, const char *title); gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length, size_t *r_nbytes); gpg_error_t b64dec_finish (struct b64state *state); /*-- sexputil.c */ char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen); void log_printcanon (const char *text, const unsigned char *sexp, size_t sexplen); void log_printsexp (const char *text, gcry_sexp_t sexp); gpg_error_t make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip); int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); unsigned char *make_simple_sexp_from_hexstr (const char *line, size_t *nscanned); int hash_algo_from_sigval (const unsigned char *sigval); unsigned char *make_canon_sexp_from_rsa_pk (const void *m, size_t mlen, const void *e, size_t elen, size_t *r_len); gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_n, size_t *r_nlen, unsigned char const **r_e, size_t *r_elen); int get_pk_algo_from_key (gcry_sexp_t key); int get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen); char *pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); int hexcolon2bin (const char *string, void *buffer, size_t length); char *bin2hex (const void *buffer, size_t length, char *stringbuf); char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf); const char *hex2str (const char *hexstring, char *buffer, size_t bufsize, size_t *buflen); char *hex2str_alloc (const char *hexstring, size_t *r_count); unsigned int hex2fixedbuf (const char *hexstr, void *buffer, size_t bufsize); /*-- percent.c --*/ char *percent_plus_escape (const char *string); char *percent_data_escape (int plus, const char *prefix, const void *data, size_t datalen); char *percent_plus_unescape (const char *string, int nulrepl); char *percent_unescape (const char *string, int nulrepl); size_t percent_plus_unescape_inplace (char *string, int nulrepl); size_t percent_unescape_inplace (char *string, int nulrepl); /*-- openpgp-oid.c --*/ gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi); char *openpgp_oidbuf_to_str (const unsigned char *buf, size_t len); char *openpgp_oid_to_str (gcry_mpi_t a); int openpgp_oidbuf_is_ed25519 (const void *buf, size_t len); int openpgp_oid_is_ed25519 (gcry_mpi_t a); int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len); int openpgp_oid_is_cv25519 (gcry_mpi_t a); const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo); const char *openpgp_oid_to_curve (const char *oid, int canon); const char *openpgp_oid_or_name_to_curve (const char *oidname, int canon); const char *openpgp_enum_curves (int *idxp); const char *openpgp_is_curve_supported (const char *name, int *r_algo, unsigned int *r_nbits); const char *get_keyalgo_string (enum gcry_pk_algos algo, unsigned int nbits, const char *curve); /*-- homedir.c --*/ const char *standard_homedir (void); const char *default_homedir (void); void gnupg_set_homedir (const char *newdir); const char *gnupg_homedir (void); int gnupg_default_homedir_p (void); const char *gnupg_daemon_rootdir (void); const char *gnupg_socketdir (void); const char *gnupg_sysconfdir (void); const char *gnupg_bindir (void); const char *gnupg_libexecdir (void); const char *gnupg_libdir (void); const char *gnupg_datadir (void); const char *gnupg_localedir (void); const char *gnupg_cachedir (void); const char *gpg_agent_socket_name (void); const char *dirmngr_socket_name (void); char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info); /* All module names. We also include gpg and gpgsm for the sake for gpgconf. */ #define GNUPG_MODULE_NAME_AGENT 1 #define GNUPG_MODULE_NAME_PINENTRY 2 #define GNUPG_MODULE_NAME_SCDAEMON 3 #define GNUPG_MODULE_NAME_DIRMNGR 4 #define GNUPG_MODULE_NAME_PROTECT_TOOL 5 #define GNUPG_MODULE_NAME_CHECK_PATTERN 6 #define GNUPG_MODULE_NAME_GPGSM 7 #define GNUPG_MODULE_NAME_GPG 8 #define GNUPG_MODULE_NAME_CONNECT_AGENT 9 #define GNUPG_MODULE_NAME_GPGCONF 10 #define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11 #define GNUPG_MODULE_NAME_GPGV 12 #define GNUPG_MODULE_NAME_KEYBOXD 13 const char *gnupg_module_name (int which); void gnupg_module_name_flush_some (void); void gnupg_set_builddir (const char *newdir); /* A list of constants to identify protocols. This is used by tools * which need to distinguish between the different protocols * implemented by GnuPG. May be used as bit flags. */ #define GNUPG_PROTOCOL_OPENPGP 1 /* The one and only (gpg). */ #define GNUPG_PROTOCOL_CMS 2 /* The core of S/MIME (gpgsm) */ #define GNUPG_PROTOCOL_SSH_AGENT 4 /* Out ssh-agent implementation */ /*-- gpgrlhelp.c --*/ void gnupg_rl_initialize (void); /*-- helpfile.c --*/ char *gnupg_get_help_string (const char *key, int only_current_locale); /*-- localename.c --*/ const char *gnupg_messages_locale_name (void); /*-- miscellaneous.c --*/ /* This function is called at startup to tell libgcrypt to use our own logging subsystem. */ void setup_libgcrypt_logging (void); /* Print an out of core message and die. */ void xoutofcore (void); /* Same as estream_asprintf but die on memory failure. */ char *xasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* This is now an alias to estream_asprintf. */ char *xtryasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* Replacement for gcry_cipher_algo_name. */ const char *gnupg_cipher_algo_name (int algo); void obsolete_option (const char *configname, unsigned int configlineno, const char *name); const char *print_fname_stdout (const char *s); const char *print_fname_stdin (const char *s); void print_utf8_buffer3 (estream_t fp, const void *p, size_t n, const char *delim); void print_utf8_buffer2 (estream_t fp, const void *p, size_t n, int delim); void print_utf8_buffer (estream_t fp, const void *p, size_t n); void print_utf8_string (estream_t stream, const char *p); void print_hexstring (FILE *fp, const void *buffer, size_t length, int reserved); char *try_make_printable_string (const void *p, size_t n, int delim); char *make_printable_string (const void *p, size_t n, int delim); char *decode_c_string (const char *src); int is_file_compressed (const char *s, int *ret_rc); int match_multistr (const char *multistr,const char *match); int gnupg_compare_version (const char *a, const char *b); struct debug_flags_s { unsigned int flag; const char *name; }; int parse_debug_flag (const char *string, unsigned int *debugvar, const struct debug_flags_s *flags); /*-- Simple replacement functions. */ /* We use the gnupg_ttyname macro to be safe not to run into conflicts which an extisting but broken ttyname. */ #if !defined(HAVE_TTYNAME) || defined(HAVE_BROKEN_TTYNAME) # define gnupg_ttyname(n) _gnupg_ttyname ((n)) /* Systems without ttyname (W32) will merely return NULL. */ static inline char * _gnupg_ttyname (int fd) { (void)fd; return NULL; } #else /*HAVE_TTYNAME*/ # define gnupg_ttyname(n) ttyname ((n)) #endif /*HAVE_TTYNAME */ #ifdef HAVE_W32CE_SYSTEM #define getpid() GetCurrentProcessId () char *_gnupg_getenv (const char *name); /* See sysutils.c */ #define getenv(a) _gnupg_getenv ((a)) char *_gnupg_setenv (const char *name); /* See sysutils.c */ #define setenv(a,b,c) _gnupg_setenv ((a),(b),(c)) int _gnupg_isatty (int fd); #define gnupg_isatty(a) _gnupg_isatty ((a)) #else #define gnupg_isatty(a) isatty ((a)) #endif /*-- Macros to replace ctype ones to avoid locale problems. --*/ #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \ || (*(p) >= 'a' && *(p) <= 'z')) #define alnump(p) (alphap (p) || digitp (p)) #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) /* Note this isn't identical to a C locale isspace() without \f and \v, but works for the purposes used here. */ #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t') /* The atoi macros assume that the buffer has only valid digits. */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) #define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2)) #endif /*GNUPG_COMMON_UTIL_H*/ diff --git a/configure.ac b/configure.ac index 5fcbcd7e4..0815927ae 100644 --- a/configure.ac +++ b/configure.ac @@ -1,2148 +1,2151 @@ # configure.ac - for GnuPG 2.1 # Copyright (C) 1998-2018 Free Software Foundation, Inc. # Copyright (C) 1998-2018 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 . # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) min_automake_version="1.14" # To build a release you need to create a tag with the version number # (git tag -s gnupg-2.n.m) and run "./autogen.sh --force". Please # bump the version number immediately *after* the release and do # another commit and push so that the git magic is able to work. m4_define([mym4_package],[gnupg]) m4_define([mym4_major], [2]) m4_define([mym4_minor], [3]) m4_define([mym4_micro], [0]) # To start a new development series, i.e a new major or minor number # you need to mark an arbitrary commit before the first beta release # with an annotated tag. For example the 2.1 branch starts off with # the tag "gnupg-2.1-base". This is used as the base for counting # beta numbers before the first release of a series. # Below is m4 magic to extract and compute the git revision number, # the decimalized short revision number, a beta version string and a # flag indicating a development version (mym4_isbeta). Note that the # m4 processing is done by autoconf and not during the configure run. m4_define([mym4_verslist], m4_split(m4_esyscmd([./autogen.sh --find-version] \ mym4_package mym4_major mym4_minor mym4_micro),[:])) m4_define([mym4_isbeta], m4_argn(2, mym4_verslist)) m4_define([mym4_version], m4_argn(4, mym4_verslist)) m4_define([mym4_revision], m4_argn(7, mym4_verslist)) m4_define([mym4_revision_dec], m4_argn(8, mym4_verslist)) m4_esyscmd([echo ]mym4_version[>VERSION]) AC_INIT([mym4_package],[mym4_version], [https://bugs.gnupg.org]) # When changing the SWDB tag please also adjust the hard coded tags in # build-aux/speedo.mk and Makefile.am AC_DEFINE_UNQUOTED(GNUPG_SWDB_TAG, "gnupg24", [swdb tag for this branch]) NEED_GPG_ERROR_VERSION=1.38 NEED_LIBGCRYPT_API=1 NEED_LIBGCRYPT_VERSION=1.8.0 NEED_LIBASSUAN_API=2 NEED_LIBASSUAN_VERSION=2.5.0 NEED_KSBA_API=1 NEED_KSBA_VERSION=1.3.4 NEED_NTBTLS_API=1 NEED_NTBTLS_VERSION=0.1.0 NEED_NPTH_API=1 NEED_NPTH_VERSION=1.2 NEED_GNUTLS_VERSION=3.0 NEED_SQLITE_VERSION=3.7 development_version=mym4_isbeta PACKAGE=$PACKAGE_NAME PACKAGE_GT=${PACKAGE_NAME}2 VERSION=$PACKAGE_VERSION AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([sm/gpgsm.c]) AC_CONFIG_HEADER([config.h]) AM_INIT_AUTOMAKE([serial-tests dist-bzip2 no-dist-gzip]) AC_CANONICAL_HOST AB_INIT AC_GNU_SOURCE # Some status variables. have_gpg_error=no have_libgcrypt=no have_libassuan=no have_ksba=no have_ntbtls=no have_gnutls=no have_sqlite=no have_npth=no have_libusb=no have_system_resolver=no gnupg_have_ldap="n/a" use_zip=yes use_bzip2=yes use_exec=yes use_trust_models=yes use_tofu=yes use_libdns=yes card_support=yes use_ccid_driver=auto dirmngr_auto_start=yes use_tls_library=no large_secmem=no show_tor_support=no # gpg is a required part and can't be disabled anymore. build_gpg=yes GNUPG_BUILD_PROGRAM(gpgsm, yes) # The agent is a required part and can't be disabled anymore. build_agent=yes GNUPG_BUILD_PROGRAM(scdaemon, yes) GNUPG_BUILD_PROGRAM(g13, no) GNUPG_BUILD_PROGRAM(dirmngr, yes) GNUPG_BUILD_PROGRAM(keyboxd, yes) GNUPG_BUILD_PROGRAM(doc, yes) GNUPG_BUILD_PROGRAM(symcryptrun, no) # We use gpgtar to unpack test data, hence we always build it. If the # user opts out, we simply don't install it. GNUPG_BUILD_PROGRAM(gpgtar, yes) # We also install the gpg-wks-server tool by default but disable it # later for platforms where it can't be build. GNUPG_BUILD_PROGRAM(wks-tools, yes) AC_SUBST(PACKAGE) AC_SUBST(PACKAGE_GT) AC_SUBST(VERSION) AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of this package]) AC_DEFINE_UNQUOTED(PACKAGE_GT, "$PACKAGE_GT", [Name of this package for gettext]) AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version of this package]) AC_DEFINE_UNQUOTED(PACKAGE_BUGREPORT, "$PACKAGE_BUGREPORT", [Bug report address]) AC_DEFINE_UNQUOTED(NEED_LIBGCRYPT_VERSION, "$NEED_LIBGCRYPT_VERSION", [Required version of Libgcrypt]) AC_DEFINE_UNQUOTED(NEED_KSBA_VERSION, "$NEED_KSBA_VERSION", [Required version of Libksba]) AC_DEFINE_UNQUOTED(NEED_NTBTLS_VERSION, "$NEED_NTBTLS_VERSION", [Required version of NTBTLS]) # The default is to use the modules from this package and the few # other packages in a standard place; i.e where this package gets # installed. With these options it is possible to override these # ${prefix} depended values with fixed paths, which can't be replaced # at make time. See also am/cmacros.am and the defaults in AH_BOTTOM. AC_ARG_WITH(agent-pgm, [ --with-agent-pgm=PATH Use PATH as the default for the agent)], GNUPG_AGENT_PGM="$withval", GNUPG_AGENT_PGM="" ) AC_SUBST(GNUPG_AGENT_PGM) AM_CONDITIONAL(GNUPG_AGENT_PGM, test -n "$GNUPG_AGENT_PGM") show_gnupg_agent_pgm="(default)" test -n "$GNUPG_AGENT_PGM" && show_gnupg_agent_pgm="$GNUPG_AGENT_PGM" AC_ARG_WITH(pinentry-pgm, [ --with-pinentry-pgm=PATH Use PATH as the default for the pinentry)], GNUPG_PINENTRY_PGM="$withval", GNUPG_PINENTRY_PGM="" ) AC_SUBST(GNUPG_PINENTRY_PGM) AM_CONDITIONAL(GNUPG_PINENTRY_PGM, test -n "$GNUPG_PINENTRY_PGM") show_gnupg_pinentry_pgm="(default)" test -n "$GNUPG_PINENTRY_PGM" && show_gnupg_pinentry_pgm="$GNUPG_PINENTRY_PGM" AC_ARG_WITH(scdaemon-pgm, [ --with-scdaemon-pgm=PATH Use PATH as the default for the scdaemon)], GNUPG_SCDAEMON_PGM="$withval", GNUPG_SCDAEMON_PGM="" ) AC_SUBST(GNUPG_SCDAEMON_PGM) AM_CONDITIONAL(GNUPG_SCDAEMON_PGM, test -n "$GNUPG_SCDAEMON_PGM") show_gnupg_scdaemon_pgm="(default)" test -n "$GNUPG_SCDAEMON_PGM" && show_gnupg_scdaemon_pgm="$GNUPG_SCDAEMON_PGM" AC_ARG_WITH(dirmngr-pgm, [ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)], GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_PGM, test -n "$GNUPG_DIRMNGR_PGM") show_gnupg_dirmngr_pgm="(default)" test -n "$GNUPG_DIRMNGR_PGM" && show_gnupg_dirmngr_pgm="$GNUPG_DIRMNGR_PGM" AC_ARG_WITH(keyboxd-pgm, [ --with-keyboxd-pgm=PATH Use PATH as the default for the keyboxd)], GNUPG_KEYBOXD_PGM="$withval", GNUPG_KEYBOXD_PGM="" ) AC_SUBST(GNUPG_KEYBOXD_PGM) AM_CONDITIONAL(GNUPG_KEYBOXD_PGM, test -n "$GNUPG_KEYBOXD_PGM") show_gnupg_keyboxd_pgm="(default)" test -n "$GNUPG_KEYBOXD_PGM" && show_gnupg_keyboxd_pgm="$GNUPG_KEYBOXD_PGM" AC_ARG_WITH(protect-tool-pgm, [ --with-protect-tool-pgm=PATH Use PATH as the default for the protect-tool)], GNUPG_PROTECT_TOOL_PGM="$withval", GNUPG_PROTECT_TOOL_PGM="" ) AC_SUBST(GNUPG_PROTECT_TOOL_PGM) AM_CONDITIONAL(GNUPG_PROTECT_TOOL_PGM, test -n "$GNUPG_PROTECT_TOOL_PGM") show_gnupg_protect_tool_pgm="(default)" test -n "$GNUPG_PROTECT_TOOL_PGM" \ && show_gnupg_protect_tool_pgm="$GNUPG_PROTECT_TOOL_PGM" AC_ARG_WITH(dirmngr-ldap-pgm, [ --with-dirmngr-ldap-pgm=PATH Use PATH as the default for the dirmngr ldap wrapper)], GNUPG_DIRMNGR_LDAP_PGM="$withval", GNUPG_DIRMNGR_LDAP_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_LDAP_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_LDAP_PGM, test -n "$GNUPG_DIRMNGR_LDAP_PGM") show_gnupg_dirmngr_ldap_pgm="(default)" test -n "$GNUPG_DIRMNGR_LDAP_PGM" \ && show_gnupg_dirmngr_ldap_pgm="$GNUPG_DIRMNGR_LDAP_PGM" # # For a long time gpg 2.x was installed as gpg2. This changed with # 2.2. This option can be used to install gpg under the name gpg2. # AC_ARG_ENABLE(gpg-is-gpg2, AC_HELP_STRING([--enable-gpg-is-gpg2],[Set installed name of gpg to gpg2]), gpg_is_gpg2=$enableval) if test "$gpg_is_gpg2" = "yes"; then AC_DEFINE(USE_GPG2_HACK, 1, [Define to install gpg as gpg2]) fi AM_CONDITIONAL(USE_GPG2_HACK, test "$gpg_is_gpg2" = "yes") # SELinux support includes tracking of sensitive files to avoid # leaking their contents through processing these files by gpg itself AC_MSG_CHECKING([whether SELinux support is requested]) AC_ARG_ENABLE(selinux-support, AC_HELP_STRING([--enable-selinux-support], [enable SELinux support]), selinux_support=$enableval, selinux_support=no) AC_MSG_RESULT($selinux_support) AC_MSG_CHECKING([whether to allocate extra secure memory]) AC_ARG_ENABLE(large-secmem, AC_HELP_STRING([--enable-large-secmem], [allocate extra secure memory]), large_secmem=$enableval, large_secmem=no) AC_MSG_RESULT($large_secmem) if test "$large_secmem" = yes ; then SECMEM_BUFFER_SIZE=65536 else SECMEM_BUFFER_SIZE=32768 fi AC_DEFINE_UNQUOTED(SECMEM_BUFFER_SIZE,$SECMEM_BUFFER_SIZE, [Size of secure memory buffer]) AC_MSG_CHECKING([calibrated passphrase-stretching (s2k) duration]) AC_ARG_WITH(agent-s2k-calibration, AC_HELP_STRING([--with-agent-s2k-calibration=MSEC], [calibrate passphrase stretching (s2k) to MSEC milliseconds]), agent_s2k_calibration=$withval, agent_s2k_calibration=100) AC_MSG_RESULT($agent_s2k_calibration milliseconds) AC_DEFINE_UNQUOTED(AGENT_S2K_CALIBRATION, $agent_s2k_calibration, [Agent s2k calibration time (ms)]) AC_MSG_CHECKING([whether to enable trust models]) AC_ARG_ENABLE(trust-models, AC_HELP_STRING([--disable-trust-models], [disable all trust models except "always"]), use_trust_models=$enableval) AC_MSG_RESULT($use_trust_models) if test "$use_trust_models" = no ; then AC_DEFINE(NO_TRUST_MODELS, 1, [Define to include only trust-model always]) fi AC_MSG_CHECKING([whether to enable TOFU]) AC_ARG_ENABLE(tofu, AC_HELP_STRING([--disable-tofu], [disable the TOFU trust model]), use_tofu=$enableval, use_tofu=$use_trust_models) AC_MSG_RESULT($use_tofu) if test "$use_trust_models" = no && test "$use_tofu" = yes; then AC_MSG_ERROR([both --disable-trust-models and --enable-tofu given]) fi AC_MSG_CHECKING([whether to enable libdns]) AC_ARG_ENABLE(libdns, AC_HELP_STRING([--disable-libdns], [do not build with libdns support]), use_libdns=$enableval, use_libdns=yes) AC_MSG_RESULT($use_libdns) if test x"$use_libdns" = xyes ; then AC_DEFINE(USE_LIBDNS, 1, [Build with integrated libdns support]) fi AM_CONDITIONAL(USE_LIBDNS, test "$use_libdns" = yes) # # Options to disable algorithm # GNUPG_GPG_DISABLE_ALGO([rsa],[RSA public key]) # Elgamal is a MUST algorithm # DSA is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([ecdh],[ECDH public key]) GNUPG_GPG_DISABLE_ALGO([ecdsa],[ECDSA public key]) GNUPG_GPG_DISABLE_ALGO([eddsa],[EdDSA public key]) GNUPG_GPG_DISABLE_ALGO([idea],[IDEA cipher]) # 3DES is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([cast5],[CAST5 cipher]) GNUPG_GPG_DISABLE_ALGO([blowfish],[BLOWFISH cipher]) GNUPG_GPG_DISABLE_ALGO([aes128],[AES128 cipher]) GNUPG_GPG_DISABLE_ALGO([aes192],[AES192 cipher]) GNUPG_GPG_DISABLE_ALGO([aes256],[AES256 cipher]) GNUPG_GPG_DISABLE_ALGO([twofish],[TWOFISH cipher]) GNUPG_GPG_DISABLE_ALGO([camellia128],[CAMELLIA128 cipher]) GNUPG_GPG_DISABLE_ALGO([camellia192],[CAMELLIA192 cipher]) GNUPG_GPG_DISABLE_ALGO([camellia256],[CAMELLIA256 cipher]) GNUPG_GPG_DISABLE_ALGO([md5],[MD5 hash]) # SHA1 is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([rmd160],[RIPE-MD160 hash]) GNUPG_GPG_DISABLE_ALGO([sha224],[SHA-224 hash]) # SHA256 is a MUST algorithm for GnuPG. GNUPG_GPG_DISABLE_ALGO([sha384],[SHA-384 hash]) GNUPG_GPG_DISABLE_ALGO([sha512],[SHA-512 hash]) # Allow disabling of zip support. # This is in general not a good idea because according to rfc4880 OpenPGP # implementations SHOULD support ZLIB. AC_MSG_CHECKING([whether to enable the ZIP and ZLIB compression algorithm]) AC_ARG_ENABLE(zip, AC_HELP_STRING([--disable-zip], [disable the ZIP and ZLIB compression algorithm]), use_zip=$enableval) AC_MSG_RESULT($use_zip) # Allow disabling of bzib2 support. # It is defined only after we confirm the library is available later AC_MSG_CHECKING([whether to enable the BZIP2 compression algorithm]) AC_ARG_ENABLE(bzip2, AC_HELP_STRING([--disable-bzip2],[disable the BZIP2 compression algorithm]), use_bzip2=$enableval) AC_MSG_RESULT($use_bzip2) # Configure option to allow or disallow execution of external # programs, like a photo viewer. AC_MSG_CHECKING([whether to enable external program execution]) AC_ARG_ENABLE(exec, AC_HELP_STRING([--disable-exec],[disable all external program execution]), use_exec=$enableval) AC_MSG_RESULT($use_exec) if test "$use_exec" = no ; then AC_DEFINE(NO_EXEC,1,[Define to disable all external program execution]) fi if test "$use_exec" = yes ; then AC_MSG_CHECKING([whether to enable photo ID viewing]) AC_ARG_ENABLE(photo-viewers, [ --disable-photo-viewers disable photo ID viewers], [if test "$enableval" = no ; then AC_DEFINE(DISABLE_PHOTO_VIEWER,1,[define to disable photo viewing]) fi],enableval=yes) gnupg_cv_enable_photo_viewers=$enableval AC_MSG_RESULT($enableval) if test "$gnupg_cv_enable_photo_viewers" = yes ; then AC_MSG_CHECKING([whether to use a fixed photo ID viewer]) AC_ARG_WITH(photo-viewer, [ --with-photo-viewer=FIXED_VIEWER set a fixed photo ID viewer], [if test "$withval" = yes ; then withval=no elif test "$withval" != no ; then AC_DEFINE_UNQUOTED(FIXED_PHOTO_VIEWER,"$withval", [if set, restrict photo-viewer to this]) fi],withval=no) AC_MSG_RESULT($withval) fi fi # # Check for the key/uid cache size. This can't be zero, but can be # pretty small on embedded systems. This is used for the gpg part. # AC_MSG_CHECKING([for the size of the key and uid cache]) AC_ARG_ENABLE(key-cache, AC_HELP_STRING([--enable-key-cache=SIZE], [Set key cache to SIZE (default 4096)]),,enableval=4096) if test "$enableval" = "no"; then enableval=5 elif test "$enableval" = "yes" || test "$enableval" = ""; then enableval=4096 fi changequote(,)dnl key_cache_size=`echo "$enableval" | sed 's/[A-Za-z]//g'` changequote([,])dnl if test "$enableval" != "$key_cache_size" || test "$key_cache_size" -lt 5; then AC_MSG_ERROR([invalid key-cache size]) fi AC_MSG_RESULT($key_cache_size) AC_DEFINE_UNQUOTED(PK_UID_CACHE_SIZE,$key_cache_size, [Size of the key and UID caches]) # # Check whether we want to use Linux capabilities # AC_MSG_CHECKING([whether use of capabilities is requested]) AC_ARG_WITH(capabilities, [ --with-capabilities use linux capabilities [default=no]], [use_capabilities="$withval"],[use_capabilities=no]) AC_MSG_RESULT($use_capabilities) # # Check whether to disable the card support AC_MSG_CHECKING([whether smartcard support is requested]) AC_ARG_ENABLE(card-support, AC_HELP_STRING([--disable-card-support], [disable smartcard support]), card_support=$enableval) AC_MSG_RESULT($card_support) if test "$card_support" = yes ; then AC_DEFINE(ENABLE_CARD_SUPPORT,1,[Define to include smartcard support]) else build_scdaemon=no fi # # Allow disabling of internal CCID support. # It is defined only after we confirm the library is available later # AC_MSG_CHECKING([whether to enable the internal CCID driver]) AC_ARG_ENABLE(ccid-driver, AC_HELP_STRING([--disable-ccid-driver], [disable the internal CCID driver]), use_ccid_driver=$enableval) AC_MSG_RESULT($use_ccid_driver) AC_MSG_CHECKING([whether to auto start dirmngr]) AC_ARG_ENABLE(dirmngr-auto-start, AC_HELP_STRING([--disable-dirmngr-auto-start], [disable auto starting of the dirmngr]), dirmngr_auto_start=$enableval) AC_MSG_RESULT($dirmngr_auto_start) if test "$dirmngr_auto_start" = yes ; then AC_DEFINE(USE_DIRMNGR_AUTO_START,1, [Define to enable auto starting of the dirmngr]) fi # # To avoid double inclusion of config.h which might happen at some # places, we add the usual double inclusion protection at the top of # config.h. # AH_TOP([ #ifndef GNUPG_CONFIG_H_INCLUDED #define GNUPG_CONFIG_H_INCLUDED ]) # # Stuff which goes at the bottom of config.h. # AH_BOTTOM([ /* This is the major version number of GnuPG so that source included files can test for this. Note, that we use 2 here even for GnuPG 1.9.x. */ #define GNUPG_MAJOR_VERSION 2 /* Now to separate file name parts. Please note that the string version must not contain more than one character because the code assumes strlen()==1 */ #ifdef HAVE_DOSISH_SYSTEM #define DIRSEP_C '\\' #define DIRSEP_S "\\" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ';' #define PATHSEP_S ";" #define EXEEXT_S ".exe" #else #define DIRSEP_C '/' #define DIRSEP_S "/" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ':' #define PATHSEP_S ":" #define EXEEXT_S "" #endif /* This is the same as VERSION, but should be overridden if the platform cannot handle things like dots '.' in filenames. Set SAFE_VERSION_DOT and SAFE_VERSION_DASH to whatever SAFE_VERSION uses for dots and dashes. */ #define SAFE_VERSION VERSION #define SAFE_VERSION_DOT '.' #define SAFE_VERSION_DASH '-' /* Some global constants. * Note that the homedir must not end in a slash. */ #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_DRIVE_LETTERS # define GNUPG_DEFAULT_HOMEDIR "c:/gnupg" # else # define GNUPG_DEFAULT_HOMEDIR "/gnupg" # endif #elif defined(__VMS) #define GNUPG_DEFAULT_HOMEDIR "/SYS$LOGIN/gnupg" #else #define GNUPG_DEFAULT_HOMEDIR "~/.gnupg" #endif #define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d" #define GNUPG_PUBLIC_KEYS_DIR "public-keys.d" #define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d" #define GNUPG_CACHE_DIR "cache.d" #define GNUPG_DEF_COPYRIGHT_LINE \ - "Copyright (C) 2018 Free Software Foundation, Inc." + "Copyright (C) 2020 Free Software Foundation, Inc." /* For some systems (DOS currently), we hardcode the path here. For POSIX systems the values are constructed by the Makefiles, so that the values may be overridden by the make invocations; this is to comply with the GNU coding standards. Note that these values are only defaults. */ #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_DRIVE_LETTERS # define GNUPG_BINDIR "c:\\gnupg" # define GNUPG_LIBEXECDIR "c:\\gnupg" # define GNUPG_LIBDIR "c:\\gnupg" # define GNUPG_DATADIR "c:\\gnupg" # define GNUPG_SYSCONFDIR "c:\\gnupg" # else # define GNUPG_BINDIR "\\gnupg" # define GNUPG_LIBEXECDIR "\\gnupg" # define GNUPG_LIBDIR "\\gnupg" # define GNUPG_DATADIR "\\gnupg" # define GNUPG_SYSCONFDIR "\\gnupg" # endif #endif /* Derive some other constants. */ #if !(defined(HAVE_FORK) && defined(HAVE_PIPE) && defined(HAVE_WAITPID)) #define EXEC_TEMPFILE_ONLY #endif /* We didn't define endianness above, so get it from OS macros. This is intended for making fat binary builds on OS X. */ #if !defined(BIG_ENDIAN_HOST) && !defined(LITTLE_ENDIAN_HOST) #if defined(__BIG_ENDIAN__) #define BIG_ENDIAN_HOST 1 #elif defined(__LITTLE_ENDIAN__) #define LITTLE_ENDIAN_HOST 1 #else #error "No endianness found" #endif #endif /* Hack used for W32: ldap.m4 also tests for the ASCII version of ldap_start_tls_s because that is the actual symbol used in the library. winldap.h redefines it to our commonly used value, thus we define our usual macro here. */ #ifdef HAVE_LDAP_START_TLS_SA # ifndef HAVE_LDAP_START_TLS_S # define HAVE_LDAP_START_TLS_S 1 # endif #endif /* Enable the es_ macros from gpgrt. */ #define GPGRT_ENABLE_ES_MACROS 1 /* Enable the log_ macros from gpgrt. */ #define GPGRT_ENABLE_LOG_MACROS 1 +/* We want the argparse macros from gpgrt. */ +#define GPGRT_ENABLE_ARGPARSE_MACROS 1 + /* Tell libgcrypt not to use its own libgpg-error implementation. */ #define USE_LIBGPG_ERROR 1 /* Tell Libgcrypt not to include deprecated definitions. */ #define GCRYPT_NO_DEPRECATED 1 /* Our HTTP code is used in estream mode. */ #define HTTP_USE_ESTREAM 1 /* Under W32 we do an explicit socket initialization, thus we need to avoid the on-demand initialization which would also install an atexit handler. */ #define HTTP_NO_WSASTARTUP /* Under Windows we use the gettext code from libgpg-error. */ #define GPG_ERR_ENABLE_GETTEXT_MACROS /* Under WindowsCE we use the strerror replacement from libgpg-error. */ #define GPG_ERR_ENABLE_ERRNO_MACROS #endif /*GNUPG_CONFIG_H_INCLUDED*/ ]) AM_MAINTAINER_MODE AC_ARG_VAR(SYSROOT,[locate config scripts also below that directory]) # Checks for programs. AC_MSG_NOTICE([checking for programs]) AC_PROG_MAKE_SET AM_SANITY_CHECK missing_dir=`cd $ac_aux_dir && pwd` AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir) AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir) AM_MISSING_PROG(AUTOMAKE, automake, $missing_dir) AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir) AM_MISSING_PROG(MAKEINFO, makeinfo, $missing_dir) AM_SILENT_RULES AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AM_PROG_CC_C_O if test "x$ac_cv_prog_cc_c89" = "xno" ; then AC_MSG_ERROR([[No C-89 compiler found]]) fi AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_RANLIB AC_CHECK_TOOL(AR, ar, :) AC_PATH_PROG(PERL,"perl") AC_CHECK_TOOL(WINDRES, windres, :) AC_PATH_PROG(YAT2M, "yat2m", "./yat2m" ) AC_ARG_VAR(YAT2M, [tool to convert texi to man pages]) AM_CONDITIONAL(HAVE_YAT2M, test -n "$ac_cv_path_YAT2M") AC_ISC_POSIX AC_SYS_LARGEFILE # We need to compile and run a program on the build machine. A # comment in libgpg-error says that the AC_PROG_CC_FOR_BUILD macro in # the AC archive is broken for autoconf 2.57. Given that there is no # newer version of that macro, we assume that it is also broken for # autoconf 2.61 and thus we use a simple but usually sufficient # approach. AC_MSG_CHECKING(for cc for build) if test "$cross_compiling" = "yes"; then CC_FOR_BUILD="${CC_FOR_BUILD-cc}" else CC_FOR_BUILD="${CC_FOR_BUILD-$CC}" fi AC_MSG_RESULT($CC_FOR_BUILD) AC_ARG_VAR(CC_FOR_BUILD,[build system C compiler]) # We need to call this macro because other pkg-config macros are # not always used. PKG_PROG_PKG_CONFIG try_gettext=yes require_iconv=yes have_dosish_system=no have_w32_system=no have_w32ce_system=no have_android_system=no use_simple_gettext=no mmap_needed=yes require_pipe_to_unblock_pselect=yes case "${host}" in *-mingw32*) # special stuff for Windoze NT ac_cv_have_dev_random=no AC_DEFINE(USE_ONLY_8DOT3,1, [Set this to limit filenames to the 8.3 format]) AC_DEFINE(USE_SIMPLE_GETTEXT,1, [Because the Unix gettext has too much overhead on MingW32 systems and these systems lack Posix functions, we use a simplified version of gettext]) have_dosish_system=yes have_w32_system=yes require_iconv=no require_pipe_to_unblock_pselect=no case "${host}" in *-mingw32ce*) have_w32ce_system=yes ;; *) AC_DEFINE(HAVE_DRIVE_LETTERS,1, [Defined if the OS supports drive letters.]) ;; esac try_gettext="no" use_simple_gettext=yes mmap_needed=no build_wks_tools=no ;; i?86-emx-os2 | i?86-*-os2*emx ) # OS/2 with the EMX environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" build_wks_tools=no ;; i?86-*-msdosdjgpp*) # DOS with the DJGPP environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" build_wks_tools=no ;; *-*-hpux*) if test -z "$GCC" ; then CFLAGS="-Ae -D_HPUX_SOURCE $CFLAGS" fi ;; *-dec-osf4*) if test -z "$GCC" ; then # Suppress all warnings # to get rid of the unsigned/signed char mismatch warnings. CFLAGS="-w $CFLAGS" fi ;; *-dec-osf5*) if test -z "$GCC" ; then # Use the newer compiler `-msg_disable ptrmismatch1' to # get rid of the unsigned/signed char mismatch warnings. # Using this may hide other pointer mismatch warnings, but # it at least lets other warning classes through CFLAGS="-msg_disable ptrmismatch1 $CFLAGS" fi ;; m68k-atari-mint) ;; *-linux-android*) have_android_system=yes # Android is fully utf-8 and we do not want to use iconv to # keeps things simple require_iconv=no build_wks_tools=no ;; *-apple-darwin*) AC_DEFINE(_DARWIN_C_SOURCE, 900000L, Expose all libc features (__DARWIN_C_FULL).) ;; *-*-netbsd*) require_pipe_to_unblock_pselect=yes ;; *) ;; esac if test "$require_pipe_to_unblock_pselect" = yes; then AC_DEFINE(HAVE_PSELECT_NO_EINTR, 1, [Defined if we run on systems like NetBSD, where pselect cannot be unblocked by signal from a thread within the same process. We use pipe in this case, instead.]) fi if test "$have_dosish_system" = yes; then AC_DEFINE(HAVE_DOSISH_SYSTEM,1, [Defined if we run on some of the PCDOS like systems (DOS, Windoze. OS/2) with special properties like no file modes, case insensitive file names and preferred use of backslashes as directory name separators.]) fi AM_CONDITIONAL(HAVE_DOSISH_SYSTEM, test "$have_dosish_system" = yes) AM_CONDITIONAL(USE_SIMPLE_GETTEXT, test x"$use_simple_gettext" = xyes) if test "$have_w32_system" = yes; then AC_DEFINE(HAVE_W32_SYSTEM,1, [Defined if we run on a W32 API based system]) if test "$have_w32ce_system" = yes; then AC_DEFINE(HAVE_W32CE_SYSTEM,1,[Defined if we run on WindowsCE]) fi fi AM_CONDITIONAL(HAVE_W32_SYSTEM, test "$have_w32_system" = yes) AM_CONDITIONAL(HAVE_W32CE_SYSTEM, test "$have_w32ce_system" = yes) if test "$have_android_system" = yes; then AC_DEFINE(HAVE_ANDROID_SYSTEM,1, [Defined if we build for an Android system]) fi AM_CONDITIONAL(HAVE_ANDROID_SYSTEM, test "$have_android_system" = yes) # (These need to go after AC_PROG_CC so that $EXEEXT is defined) AC_DEFINE_UNQUOTED(EXEEXT,"$EXEEXT",[The executable file extension, if any]) # # Checks for libraries. # AC_MSG_NOTICE([checking for libraries]) # # libgpg-error is a library with error codes shared between GnuPG # related projects. # AM_PATH_GPG_ERROR("$NEED_GPG_ERROR_VERSION", have_gpg_error=yes,have_gpg_error=no) # # Libgcrypt is our generic crypto library # AM_PATH_LIBGCRYPT("$NEED_LIBGCRYPT_API:$NEED_LIBGCRYPT_VERSION", have_libgcrypt=yes,have_libgcrypt=no) # And, then, check if it's newer than 1.9.0. have_libgcrypt_newer=no if test $ok = yes; then if test "$major" -gt 1; then have_libgcrypt_newer=yes else if test "$major" -eq 1; then if test "$minor" -gt 9; then have_libgcrypt_newer=yes else if test "$minor" -eq 9; then if test "$micro" -ge 0; then have_libgcrypt_newer=yes fi fi fi fi fi fi AM_CONDITIONAL(HAVE_NEWER_LIBGCRYPT, [test $have_libgcrypt_newer = yes]) # # libassuan is used for IPC # AM_PATH_LIBASSUAN("$NEED_LIBASSUAN_API:$NEED_LIBASSUAN_VERSION", have_libassuan=yes,have_libassuan=no) if test "$have_libassuan" = "yes"; then AC_DEFINE_UNQUOTED(GNUPG_LIBASSUAN_VERSION, "$libassuan_version", [version of the libassuan library]) show_tor_support="only .onion" fi # # libksba is our X.509 support library # AM_PATH_KSBA("$NEED_KSBA_API:$NEED_KSBA_VERSION",have_ksba=yes,have_ksba=no) # # libusb allows us to use the integrated CCID smartcard reader driver. # # FiXME: Use GNUPG_CHECK_LIBUSB and modify to use separate AC_SUBSTs. if test "$use_ccid_driver" = auto || test "$use_ccid_driver" = yes; then case "${host}" in *-mingw32*) LIBUSB_NAME= LIBUSB_LIBS= LIBUSB_CPPFLAGS= ;; *-*-darwin*) LIBUSB_NAME=usb-1.0 LIBUSB_LIBS="-Wl,-framework,CoreFoundation -Wl,-framework,IOKit" ;; *-*-freebsd*) # FreeBSD has a native 1.0 compatible library by -lusb. LIBUSB_NAME=usb LIBUSB_LIBS= ;; *) LIBUSB_NAME=usb-1.0 LIBUSB_LIBS= ;; esac fi if test x"$LIBUSB_NAME" != x ; then AC_CHECK_LIB($LIBUSB_NAME, libusb_init, [ LIBUSB_LIBS="-l$LIBUSB_NAME $LIBUSB_LIBS" have_libusb=yes ]) AC_MSG_CHECKING([libusb include dir]) usb_incdir_found="no" for _incdir in "" "/usr/include/libusb-1.0" \ "/usr/local/include/libusb-1.0" "/usr/pkg/include/libusb-1.0"; do _libusb_save_cppflags=$CPPFLAGS if test -n "${_incdir}"; then CPPFLAGS="-I${_incdir} ${CPPFLAGS}" fi AC_PREPROC_IFELSE([AC_LANG_SOURCE([[@%:@include ]])], [usb_incdir=${_incdir}; usb_incdir_found="yes"], []) CPPFLAGS=${_libusb_save_cppflags} if test "$usb_incdir_found" = "yes"; then break fi done if test "$usb_incdir_found" = "yes"; then AC_MSG_RESULT([${usb_incdir}]) else AC_MSG_RESULT([not found]) usb_incdir="" have_libusb=no if test "$use_ccid_driver" != yes; then use_ccid_driver=no fi LIBUSB_LIBS="" fi if test "$have_libusb" = yes; then AC_DEFINE(HAVE_LIBUSB,1, [defined if libusb is available]) fi if test x"$usb_incdir" = x; then LIBUSB_CPPFLAGS="" else LIBUSB_CPPFLAGS="-I${usb_incdir}" fi fi AC_SUBST(LIBUSB_LIBS) AC_SUBST(LIBUSB_CPPFLAGS) # # Check whether it is necessary to link against libdl. # (For example to load libpcsclite) # gnupg_dlopen_save_libs="$LIBS" LIBS="" AC_SEARCH_LIBS(dlopen, c dl,,,) DL_LIBS=$LIBS AC_SUBST(DL_LIBS) LIBS="$gnupg_dlopen_save_libs" # Checks for g10 AC_ARG_ENABLE(sqlite, AC_HELP_STRING([--disable-sqlite], [disable the use of SQLITE]), try_sqlite=$enableval, try_sqlite=yes) if test x"$use_tofu" = xyes ; then if test x"$try_sqlite" = xyes ; then PKG_CHECK_MODULES([SQLITE3], [sqlite3 >= $NEED_SQLITE_VERSION], [have_sqlite=yes], [have_sqlite=no]) fi if test "$have_sqlite" = "yes"; then : AC_SUBST([SQLITE3_CFLAGS]) AC_SUBST([SQLITE3_LIBS]) else use_tofu=no build_keyboxd=no tmp=$(echo "$SQLITE3_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g') AC_MSG_WARN([[ *** *** Building without SQLite support - TOFU and Keyboxd disabled *** *** $tmp]]) fi fi AM_CONDITIONAL(SQLITE3, test "$have_sqlite" = "yes") if test x"$use_tofu" = xyes ; then AC_DEFINE(USE_TOFU, 1, [Enable to build the TOFU code]) fi # Checks for g13 AC_PATH_PROG(ENCFS, encfs, /usr/bin/encfs) AC_DEFINE_UNQUOTED(ENCFS, "${ENCFS}", [defines the filename of the encfs program]) AC_PATH_PROG(FUSERMOUNT, fusermount, /usr/bin/fusermount) AC_DEFINE_UNQUOTED(FUSERMOUNT, "${FUSERMOUNT}", [defines the filename of the fusermount program]) # Checks for dirmngr # # Checks for symcryptrun: # # libutil has openpty() and login_tty(). AC_CHECK_LIB(util, openpty, [ LIBUTIL_LIBS="$LIBUTIL_LIBS -lutil" AC_DEFINE(HAVE_LIBUTIL,1, [defined if libutil is available]) ]) AC_SUBST(LIBUTIL_LIBS) # shred is used to clean temporary plain text files. AC_PATH_PROG(SHRED, shred, /usr/bin/shred) AC_DEFINE_UNQUOTED(SHRED, "${SHRED}", [defines the filename of the shred program]) # # Check whether the nPth library is available # AM_PATH_NPTH("$NEED_NPTH_API:$NEED_NPTH_VERSION",have_npth=yes,have_npth=no) if test "$have_npth" = "yes"; then AC_DEFINE(HAVE_NPTH, 1, [Defined if the New Portable Thread Library is available]) AC_DEFINE(USE_NPTH, 1, [Defined if support for nPth is requested and nPth is available]) else AC_MSG_WARN([[ *** *** To support concurrent access for example in gpg-agent and the SCdaemon *** we need the support of the New Portable Threads Library. ***]]) fi # # Enable debugging of nPth # AC_ARG_ENABLE(npth-debug, AC_HELP_STRING([--enable-npth-debug], [build with debug version of npth]), [if test $enableval = yes ; then AC_DEFINE(NPTH_ENABLE_DEBUG,1, [Build with debug version of nPth]) fi]) # # NTBTLS is our TLS library. If it is not available we fall back to # GNUTLS. # AC_ARG_ENABLE(ntbtls, AC_HELP_STRING([--disable-ntbtls], [disable the use of NTBTLS as TLS library]), try_ntbtls=$enableval, try_ntbtls=yes) if test x"$try_ntbtls" = xyes ; then AM_PATH_NTBTLS("$NEED_NTBTLS_API:$NEED_NTBTLS_VERSION", [have_ntbtls=yes],[have_ntbtls=no]) fi if test "$have_ntbtls" = yes ; then use_tls_library=ntbtls AC_DEFINE(HTTP_USE_NTBTLS, 1, [Enable NTBTLS support in http.c]) else AC_ARG_ENABLE(gnutls, AC_HELP_STRING([--disable-gnutls], [disable GNUTLS as fallback TLS library]), try_gnutls=$enableval, try_gnutls=yes) if test x"$try_gnutls" = xyes ; then PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= $NEED_GNUTLS_VERSION], [have_gnutls=yes], [have_gnutls=no]) fi if test "$have_gnutls" = "yes"; then AC_SUBST([LIBGNUTLS_CFLAGS]) AC_SUBST([LIBGNUTLS_LIBS]) use_tls_library=gnutls AC_DEFINE(HTTP_USE_GNUTLS, 1, [Enable GNUTLS support in http.c]) else tmp=$(echo "$LIBGNUTLS_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g') build_dirmngr=no AC_MSG_WARN([[ *** *** Neither NTBTLS nor GNUTLS available - not building dirmngr. *** *** $tmp]]) fi fi # # Allow to set a fixed trust store file for system provided certificates. # AC_ARG_WITH([default-trust-store-file], [AC_HELP_STRING([--with-default-trust-store-file=FILE], [Use FILE as system trust store])], default_trust_store_file="$withval", default_trust_store_file="") if test x"$default_trust_store_file" = xno;then default_trust_store_file="" fi if test x"$default_trust_store_file" != x ; then AC_DEFINE_UNQUOTED([DEFAULT_TRUST_STORE_FILE], ["$default_trust_store_file"], [Use as default system trust store file]) fi AC_MSG_NOTICE([checking for networking options]) # # Must check for network library requirements before doing link tests # for ldap, for example. If ldap libs are static (or dynamic and without # ELF runtime link paths), then link will fail and LDAP support won't # be detected. # AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname, [NETLIBS="-lnsl $NETLIBS"])) AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt, [NETLIBS="-lsocket $NETLIBS"])) # # Check standard resolver functions. # if test "$build_dirmngr" = "yes"; then _dns_save_libs=$LIBS LIBS="" # Find the system resolver which can always be enabled with # the dirmngr option --standard-resolver. # the double underscore thing is a glibc-ism? AC_SEARCH_LIBS(res_query,resolv bind,, AC_SEARCH_LIBS(__res_query,resolv bind,,have_resolver=no)) AC_SEARCH_LIBS(dn_expand,resolv bind,, AC_SEARCH_LIBS(__dn_expand,resolv bind,,have_resolver=no)) # macOS renames dn_skipname into res_9_dn_skipname in , # and for some reason fools us into believing we don't need # -lresolv even if we do. Since the test program checking for the # symbol does not include , we need to check for the # renamed symbol explicitly. AC_SEARCH_LIBS(res_9_dn_skipname,resolv bind,, AC_SEARCH_LIBS(dn_skipname,resolv bind,, AC_SEARCH_LIBS(__dn_skipname,resolv bind,,have_resolver=no))) if test x"$have_resolver" != xno ; then # Make sure that the BIND 4 resolver interface is workable before # enabling any code that calls it. At some point I'll rewrite the # code to use the BIND 8 resolver API. # We might also want to use libdns instead. AC_MSG_CHECKING([whether the resolver is usable]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include #include #include #include ]], [[unsigned char answer[PACKETSZ]; res_query("foo.bar",C_IN,T_A,answer,PACKETSZ); dn_skipname(0,0); dn_expand(0,0,0,0,0); ]])],have_resolver=yes,have_resolver=no) AC_MSG_RESULT($have_resolver) # This is Apple-specific and somewhat bizarre as they changed the # define in bind 8 for some reason. if test x"$have_resolver" != xyes ; then AC_MSG_CHECKING( [whether I can make the resolver usable with BIND_8_COMPAT]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#define BIND_8_COMPAT #include #include #include #include ]], [[unsigned char answer[PACKETSZ]; res_query("foo.bar",C_IN,T_A,answer,PACKETSZ); dn_skipname(0,0); dn_expand(0,0,0,0,0); ]])],[have_resolver=yes ; need_compat=yes]) AC_MSG_RESULT($have_resolver) fi fi if test x"$have_resolver" = xyes ; then AC_DEFINE(HAVE_SYSTEM_RESOLVER,1,[The system's resolver is usable.]) DNSLIBS="$DNSLIBS $LIBS" if test x"$need_compat" = xyes ; then AC_DEFINE(BIND_8_COMPAT,1,[an Apple OSXism]) fi if test "$use_libdns" = yes; then show_tor_support=yes fi elif test "$use_libdns" = yes; then show_tor_support=yes else AC_MSG_WARN([[ *** *** The system's DNS resolver is not usable. *** Dirmngr functionality is limited. ***]]) show_tor_support="${show_tor_support} (no system resolver)" fi if test "$have_w32_system" = yes; then if test "$use_libdns" = yes; then DNSLIBS="$DNSLIBS -liphlpapi" fi fi LIBS=$_dns_save_libs fi AC_SUBST(DNSLIBS) # # Check for LDAP # # Note that running the check changes the variable # gnupg_have_ldap from "n/a" to "no" or "yes". AC_ARG_ENABLE(ldap, AC_HELP_STRING([--disable-ldap],[disable LDAP support]), [if test "$enableval" = "no"; then gnupg_have_ldap=no; fi]) if test "$gnupg_have_ldap" != "no" ; then if test "$build_dirmngr" = "yes" ; then GNUPG_CHECK_LDAP($NETLIBS) AC_CHECK_LIB(lber, ber_free, [ LBER_LIBS="$LBER_LIBS -llber" AC_DEFINE(HAVE_LBER,1, [defined if liblber is available]) have_lber=yes ]) fi fi AC_SUBST(LBER_LIBS) if test "$gnupg_have_ldap" = "no"; then AC_MSG_WARN([[ *** *** Building without LDAP support. *** No CRL access or X.509 certificate search available. ***]]) fi AM_CONDITIONAL(USE_LDAP, [test "$gnupg_have_ldap" = yes]) if test "$gnupg_have_ldap" = yes ; then AC_DEFINE(USE_LDAP,1,[Defined if LDAP is support]) fi # # Check for sendmail # # This isn't necessarily sendmail itself, but anything that gives a # sendmail-ish interface to the outside world. That includes Exim, # Postfix, etc. Basically, anything that can handle "sendmail -t". AC_ARG_WITH(mailprog, AC_HELP_STRING([--with-mailprog=NAME], [use "NAME -t" for mail transport]), ,with_mailprog=yes) if test x"$with_mailprog" = xyes ; then AC_PATH_PROG(SENDMAIL,sendmail,,$PATH:/usr/sbin:/usr/libexec:/usr/lib) elif test x"$with_mailprog" != xno ; then AC_MSG_CHECKING([for a mail transport program]) AC_SUBST(SENDMAIL,$with_mailprog) AC_MSG_RESULT($with_mailprog) fi AC_DEFINE_UNQUOTED(NAME_OF_SENDMAIL,"$SENDMAIL", [Tool with sendmail -t interface]) # # Construct a printable name of the OS # case "${host}" in *-mingw32ce*) PRINTABLE_OS_NAME="W32CE" ;; *-mingw32*) PRINTABLE_OS_NAME="MingW32" ;; *-*-cygwin*) PRINTABLE_OS_NAME="Cygwin" ;; i?86-emx-os2 | i?86-*-os2*emx ) PRINTABLE_OS_NAME="OS/2" ;; i?86-*-msdosdjgpp*) PRINTABLE_OS_NAME="MSDOS/DJGPP" try_dynload=no ;; *-linux*) PRINTABLE_OS_NAME="GNU/Linux" ;; *) PRINTABLE_OS_NAME=`uname -s || echo "Unknown"` ;; esac AC_DEFINE_UNQUOTED(PRINTABLE_OS_NAME, "$PRINTABLE_OS_NAME", [A human readable text with the name of the OS]) # # Checking for iconv # if test "$require_iconv" = yes; then AM_ICONV else LIBICONV= LTLIBICONV= AC_SUBST(LIBICONV) AC_SUBST(LTLIBICONV) fi # # Check for gettext # # This is "GNU gnupg" - The project-id script from gettext # needs this string # AC_MSG_NOTICE([checking for gettext]) AM_PO_SUBDIRS AM_GNU_GETTEXT_VERSION([0.17]) if test "$try_gettext" = yes; then AM_GNU_GETTEXT([external],[need-ngettext]) # gettext requires some extra checks. These really should be part of # the basic AM_GNU_GETTEXT macro. TODO: move other gettext-specific # function checks to here. AC_CHECK_FUNCS(strchr) else USE_NLS=no USE_INCLUDED_LIBINTL=no BUILD_INCLUDED_LIBINTL=no POSUB=po AC_SUBST(USE_NLS) AC_SUBST(USE_INCLUDED_LIBINTL) AC_SUBST(BUILD_INCLUDED_LIBINTL) AC_SUBST(POSUB) fi # We use HAVE_LANGINFO_CODESET in a couple of places. AM_LANGINFO_CODESET # Checks required for our use of locales gt_LC_MESSAGES # # SELinux support # if test "$selinux_support" = yes ; then AC_DEFINE(ENABLE_SELINUX_HACKS,1,[Define to enable SELinux support]) fi # # Checks for header files. # AC_MSG_NOTICE([checking for header files]) AC_HEADER_STDC AC_CHECK_HEADERS([string.h unistd.h langinfo.h termio.h locale.h getopt.h \ pty.h utmp.h pwd.h inttypes.h signal.h sys/select.h \ stdint.h signal.h util.h libutil.h termios.h \ ucred.h sys/ucred.h sys/sysmacros.h sys/mkdev.h]) AC_HEADER_TIME # # Checks for typedefs, structures, and compiler characteristics. # AC_MSG_NOTICE([checking for system characteristics]) AC_C_CONST AC_C_INLINE AC_C_VOLATILE AC_TYPE_SIZE_T AC_TYPE_MODE_T AC_TYPE_SIGNAL AC_DECL_SYS_SIGLIST gl_HEADER_SYS_SOCKET gl_TYPE_SOCKLEN_T AC_SEARCH_LIBS([inet_addr], [nsl]) AC_ARG_ENABLE(endian-check, AC_HELP_STRING([--disable-endian-check], [disable the endian check and trust the OS provided macros]), endiancheck=$enableval,endiancheck=yes) if test x"$endiancheck" = xyes ; then GNUPG_CHECK_ENDIAN fi # fixme: we should get rid of the byte type GNUPG_CHECK_TYPEDEF(byte, HAVE_BYTE_TYPEDEF) GNUPG_CHECK_TYPEDEF(ushort, HAVE_USHORT_TYPEDEF) GNUPG_CHECK_TYPEDEF(ulong, HAVE_ULONG_TYPEDEF) GNUPG_CHECK_TYPEDEF(u16, HAVE_U16_TYPEDEF) GNUPG_CHECK_TYPEDEF(u32, HAVE_U32_TYPEDEF) AC_CHECK_SIZEOF(unsigned short) AC_CHECK_SIZEOF(unsigned int) AC_CHECK_SIZEOF(unsigned long) AC_CHECK_SIZEOF(unsigned long long) AC_CHECK_SIZEOF(size_t) AC_HEADER_TIME AC_CHECK_SIZEOF(time_t,,[[ #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif ]]) GNUPG_TIME_T_UNSIGNED if test "$ac_cv_sizeof_unsigned_short" = "0" \ || test "$ac_cv_sizeof_unsigned_int" = "0" \ || test "$ac_cv_sizeof_unsigned_long" = "0"; then AC_MSG_WARN([Hmmm, something is wrong with the sizes - using defaults]); fi # # Checks for library functions. # AC_MSG_NOTICE([checking for library functions]) AC_CHECK_DECLS(getpagesize) AC_FUNC_FSEEKO AC_FUNC_VPRINTF AC_FUNC_FORK AC_CHECK_FUNCS([atexit canonicalize_file_name clock_gettime ctermid \ explicit_bzero fcntl flockfile fsync ftello \ ftruncate funlockfile getaddrinfo getenv getpagesize \ getpwnam getpwuid getrlimit getrusage gettimeofday \ gmtime_r inet_ntop inet_pton isascii lstat memicmp \ memmove memrchr mmap nl_langinfo pipe raise rand \ setenv setlocale setrlimit sigaction sigprocmask \ stat stpcpy strcasecmp strerror strftime stricmp \ strlwr strncasecmp strpbrk strsep strtol strtoul \ strtoull tcgetattr timegm times ttyname unsetenv \ wait4 waitpid ]) # On some systems (e.g. Solaris) nanosleep requires linking to librl. # Given that we use nanosleep only as an optimization over a select # based wait function we want it only if it is available in libc. _save_libs="$LIBS" AC_SEARCH_LIBS([nanosleep], [], [AC_DEFINE(HAVE_NANOSLEEP,1, [Define to 1 if you have the `nanosleep' function in libc.])]) LIBS="$_save_libs" # See whether libc supports the Linux inotify interface case "${host}" in *-*-linux*) AC_CHECK_FUNCS([inotify_init]) ;; esac if test "$have_android_system" = yes; then # On Android ttyname is a stub but prints an error message. AC_DEFINE(HAVE_BROKEN_TTYNAME,1, [Defined if ttyname does not work properly]) fi AC_CHECK_TYPES([struct sigaction, sigset_t],,,[#include ]) # Dirmngr requires mmap on Unix systems. if test $ac_cv_func_mmap != yes -a $mmap_needed = yes; then AC_MSG_ERROR([[Sorry, the current implementation requires mmap.]]) fi # # Check for the getsockopt SO_PEERCRED, etc. # AC_CHECK_MEMBERS([struct ucred.pid, struct ucred.cr_pid, struct sockpeercred.pid], [], [], [#include #include ]) # (Open)Solaris AC_CHECK_FUNCS([getpeerucred]) # # W32 specific test # GNUPG_FUNC_MKDIR_TAKES_ONE_ARG # # Sanity check regex. Tests adapted from mutt. # AC_MSG_CHECKING([whether regular expression support is requested]) AC_ARG_ENABLE(regex, AC_HELP_STRING([--disable-regex], [do not handle regular expressions in trust signatures]), use_regex=$enableval, use_regex=yes) AC_MSG_RESULT($use_regex) if test "$use_regex" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(regex, AC_HELP_STRING([--with-regex=DIR],[look for regex in DIR]), [ if test -d "$withval" ; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ],withval="") # Does the system have regex functions at all? AC_SEARCH_LIBS([regcomp], [regex]) AC_CHECK_FUNC(regcomp, gnupg_cv_have_regex=yes, gnupg_cv_have_regex=no) if test $gnupg_cv_have_regex = no; then use_regex=no else if test x"$cross_compiling" = xyes; then AC_MSG_WARN([cross compiling; assuming regexp library is not broken]) else AC_CACHE_CHECK([whether your system's regexp library is broken], [gnupg_cv_regex_broken], AC_TRY_RUN([ #include #include main() { regex_t blah ; regmatch_t p; p.rm_eo = p.rm_eo; return regcomp(&blah, "foo.*bar", REG_NOSUB) || regexec (&blah, "foobar", 0, NULL, 0); }], gnupg_cv_regex_broken=no, gnupg_cv_regex_broken=yes, gnupg_cv_regex_broken=yes)) if test $gnupg_cv_regex_broken = yes; then AC_MSG_WARN([your regex is broken - disabling regex use]) use_regex=no fi fi fi CPPFLAGS="${_cppflags}" LDFLAGS="${_ldflags}" fi if test "$use_regex" != yes ; then AC_DEFINE(DISABLE_REGEX,1, [Define to disable regular expression support]) fi AM_CONDITIONAL(DISABLE_REGEX, test x"$use_regex" != xyes) # # Do we have zlib? Must do it here because Solaris failed # when compiling a conftest (due to the "-lz" from LIBS). # Note that we combine zlib and bzlib2 in ZLIBS. # if test "$use_zip" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(zlib, [ --with-zlib=DIR use libz in DIR],[ if test -d "$withval"; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ]) AC_CHECK_HEADER(zlib.h, AC_CHECK_LIB(z, deflateInit2_, [ ZLIBS="-lz" AC_DEFINE(HAVE_ZIP,1, [Defined if ZIP and ZLIB are supported]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}) fi # # Check whether we can support bzip2 # if test "$use_bzip2" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(bzip2, AC_HELP_STRING([--with-bzip2=DIR],[look for bzip2 in DIR]), [ if test -d "$withval" ; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ],withval="") # Checking alongside stdio.h as an early version of bzip2 (1.0) # required stdio.h to be included before bzlib.h, and Solaris 9 is # woefully out of date. if test "$withval" != no ; then AC_CHECK_HEADER(bzlib.h, AC_CHECK_LIB(bz2,BZ2_bzCompressInit, [ have_bz2=yes ZLIBS="$ZLIBS -lbz2" AC_DEFINE(HAVE_BZIP2,1, [Defined if the bz2 compression library is available]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags},[#include ]) fi fi AM_CONDITIONAL(ENABLE_BZIP2_SUPPORT,test x"$have_bz2" = "xyes") AC_SUBST(ZLIBS) # Check for readline support GNUPG_CHECK_READLINE if test "$development_version" = yes; then AC_DEFINE(IS_DEVELOPMENT_VERSION,1, [Defined if this is not a regular release]) fi if test "$USE_MAINTAINER_MODE" = "yes"; then AC_DEFINE(MAINTAINER_MODE,1, [Defined if this build is in maintainer mode]) fi AM_CONDITIONAL(CROSS_COMPILING, test x$cross_compiling = xyes) GNUPG_CHECK_GNUMAKE # Add some extra libs here so that previous tests don't fail for # mysterious reasons - the final link step should bail out. # W32SOCKLIBS is also defined so that if can be used for tools not # requiring any network stuff but linking to code in libcommon which # tracks in winsock stuff (e.g. init_common_subsystems). if test "$have_w32_system" = yes; then if test "$have_w32ce_system" = yes; then W32SOCKLIBS="-lws2" else W32SOCKLIBS="-lws2_32" fi NETLIBS="${NETLIBS} ${W32SOCKLIBS}" fi AC_SUBST(NETLIBS) AC_SUBST(W32SOCKLIBS) # # Setup gcc specific options # USE_C99_CFLAGS= AC_MSG_NOTICE([checking for cc features]) if test "$GCC" = yes; then mycflags= mycflags_save=$CFLAGS # Check whether gcc does not emit a diagnositc for unknown -Wno-* # options. This is the case for gcc >= 4.6 AC_MSG_CHECKING([if gcc ignores unknown -Wno-* options]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6 ) #kickerror #endif]],[])],[_gcc_silent_wno=yes],[_gcc_silent_wno=no]) AC_MSG_RESULT($_gcc_silent_wno) # Note that it is okay to use CFLAGS here because these are just # warning options and the user should have a chance of overriding # them. if test "$USE_MAINTAINER_MODE" = "yes"; then mycflags="$mycflags -O3 -Wall -Wcast-align -Wshadow -Wstrict-prototypes" mycflags="$mycflags -Wformat -Wno-format-y2k -Wformat-security" if test x"$_gcc_silent_wno" = xyes ; then _gcc_wopt=yes else AC_MSG_CHECKING([if gcc supports -Wno-missing-field-initializers]) CFLAGS="-Wno-missing-field-initializers" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [_gcc_wopt=yes],[_gcc_wopt=no]) AC_MSG_RESULT($_gcc_wopt) fi if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -W -Wno-sign-compare -Wno-format-zero-length" mycflags="$mycflags -Wno-missing-field-initializers" fi AC_MSG_CHECKING([if gcc supports -Wdeclaration-after-statement]) CFLAGS="-Wdeclaration-after-statement" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wdeclaration-after-statement" fi AC_MSG_CHECKING([if gcc supports -Wlogical-op]) CFLAGS="-Wlogical-op -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wlogical-op" fi AC_MSG_CHECKING([if gcc supports -Wvla]) CFLAGS="-Wvla" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wvla" fi else mycflags="$mycflags -Wall" fi if test x"$_gcc_silent_wno" = xyes ; then _gcc_psign=yes else AC_MSG_CHECKING([if gcc supports -Wno-pointer-sign]) CFLAGS="-Wno-pointer-sign" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [_gcc_psign=yes],[_gcc_psign=no]) AC_MSG_RESULT($_gcc_psign) fi if test x"$_gcc_psign" = xyes ; then mycflags="$mycflags -Wno-pointer-sign" fi AC_MSG_CHECKING([if gcc supports -Wpointer-arith]) CFLAGS="-Wpointer-arith" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_psign=yes,_gcc_psign=no) AC_MSG_RESULT($_gcc_psign) if test x"$_gcc_psign" = xyes ; then mycflags="$mycflags -Wpointer-arith" fi CFLAGS="$mycflags $mycflags_save" if test "$use_libdns" = yes; then # dirmngr/dns.{c,h} require C99 and GNU extensions. */ USE_C99_CFLAGS="-std=gnu99" fi fi AC_SUBST(USE_C99_CFLAGS) # # This is handy for debugging so the compiler doesn't rearrange # things and eliminate variables. # AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable compiler optimization]), [if test $enableval = no ; then CFLAGS=`echo $CFLAGS | sed s/-O[[1-9]]\ /-O0\ /g` fi]) # # log_debug has certain requirements which might hamper portability. # Thus we use an option to enable it. # AC_MSG_CHECKING([whether to enable log_clock]) AC_ARG_ENABLE(log_clock, AC_HELP_STRING([--enable-log-clock], [enable log_clock timestamps]), enable_log_clock=$enableval, enable_log_clock=no) AC_MSG_RESULT($enable_log_clock) if test "$enable_log_clock" = yes ; then AC_DEFINE(ENABLE_LOG_CLOCK,1,[Defined to use log_clock timestamps]) fi # Add -Werror to CFLAGS. This hack can be used to avoid problems with # misbehaving autoconf tests in case the user supplied -Werror. # AC_ARG_ENABLE(werror, AC_HELP_STRING([--enable-werror], [append -Werror to CFLAGS]), [if test $enableval = yes ; then CFLAGS="$CFLAGS -Werror" fi]) # # Configure option --enable-all-tests # AC_MSG_CHECKING([whether "make check" shall run all tests]) AC_ARG_ENABLE(all-tests, AC_HELP_STRING([--enable-all-tests], [let "make check" run all tests]), run_all_tests=$enableval, run_all_tests=no) AC_MSG_RESULT($run_all_tests) if test "$run_all_tests" = "yes"; then AC_DEFINE(RUN_ALL_TESTS,1, [Defined if "make check" shall run all tests]) fi # # We do not want support for the GNUPG_BUILDDIR environment variable # in a released version. However, our regression tests suite requires # this and thus we build with support for it during "make distcheck". # This configure option implements this along with the top Makefile's # AM_DISTCHECK_CONFIGURE_FLAGS. # gnupg_builddir_envvar=no AC_ARG_ENABLE(gnupg-builddir-envvar,, gnupg_builddir_envvar=$enableval) if test x"$gnupg_builddir_envvar" = x"yes"; then AC_DEFINE(ENABLE_GNUPG_BUILDDIR_ENVVAR, 1, [This is only used with "make distcheck"]) fi # # To avoid problems with systemd cleaning up the /run/user directory, # this option will make GnuPG try to use /run/gnupg/user as socket dir # before /run/user # AC_ARG_ENABLE(run-gnupg-user-socket, AC_HELP_STRING([--enable-run-gnupg-user-socket], [try /run/gnupg/user for sockets prior to /run/user]), use_run_gnupg_user_socket=$enableval) if test x"$use_run_gnupg_user_socket" = x"yes"; then AC_DEFINE(USE_RUN_GNUPG_USER_SOCKET, 1, [If defined try /run/gnupg/user before /run/user]) fi # # Decide what to build # build_scdaemon_extra="" if test "$build_scdaemon" = "yes"; then if test $have_libusb = no; then build_scdaemon_extra="without internal CCID driver" fi if test -n "$build_scdaemon_extra"; then build_scdaemon_extra="(${build_scdaemon_extra})" fi fi # # Set variables for use by automake makefiles. # AM_CONDITIONAL(BUILD_GPG, test "$build_gpg" = "yes") AM_CONDITIONAL(BUILD_GPGSM, test "$build_gpgsm" = "yes") AM_CONDITIONAL(BUILD_AGENT, test "$build_agent" = "yes") AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes") AM_CONDITIONAL(BUILD_G13, test "$build_g13" = "yes") AM_CONDITIONAL(BUILD_DIRMNGR, test "$build_dirmngr" = "yes") AM_CONDITIONAL(BUILD_KEYBOXD, test "$build_keyboxd" = "yes") AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes") AM_CONDITIONAL(BUILD_SYMCRYPTRUN, test "$build_symcryptrun" = "yes") AM_CONDITIONAL(BUILD_GPGTAR, test "$build_gpgtar" = "yes") AM_CONDITIONAL(BUILD_WKS_TOOLS, test "$build_wks_tools" = "yes") AM_CONDITIONAL(ENABLE_CARD_SUPPORT, test "$card_support" = yes) AM_CONDITIONAL(NO_TRUST_MODELS, test "$use_trust_models" = no) AM_CONDITIONAL(USE_TOFU, test "$use_tofu" = yes) # # Set some defines for use gpgconf. # if test "$build_gpg" = yes ; then AC_DEFINE(BUILD_WITH_GPG,1,[Defined if GPG is to be build]) fi if test "$build_gpgsm" = yes ; then AC_DEFINE(BUILD_WITH_GPGSM,1,[Defined if GPGSM is to be build]) fi if test "$build_agent" = yes ; then AC_DEFINE(BUILD_WITH_AGENT,1,[Defined if GPG-AGENT is to be build]) fi if test "$build_scdaemon" = yes ; then AC_DEFINE(BUILD_WITH_SCDAEMON,1,[Defined if SCDAEMON is to be build]) fi if test "$build_dirmngr" = yes ; then AC_DEFINE(BUILD_WITH_DIRMNGR,1,[Defined if DIRMNGR is to be build]) fi if test "$build_keyboxd" = yes ; then AC_DEFINE(BUILD_WITH_KEYBOXD,1,[Defined if KEYBOXD is to be build]) fi if test "$build_g13" = yes ; then AC_DEFINE(BUILD_WITH_G13,1,[Defined if G13 is to be build]) fi # # Define Name strings # AC_DEFINE_UNQUOTED(GNUPG_NAME, "GnuPG", [The name of the project]) AC_DEFINE_UNQUOTED(GPG_NAME, "gpg", [The name of the OpenPGP tool]) AC_DEFINE_UNQUOTED(GPG_DISP_NAME, "GnuPG", [The displayed name of gpg]) AC_DEFINE_UNQUOTED(GPGSM_NAME, "gpgsm", [The name of the S/MIME tool]) AC_DEFINE_UNQUOTED(GPGSM_DISP_NAME, "GPGSM", [The displayed name of gpgsm]) AC_DEFINE_UNQUOTED(GPG_AGENT_NAME, "gpg-agent", [The name of the agent]) AC_DEFINE_UNQUOTED(GPG_AGENT_DISP_NAME, "GPG Agent", [The displayed name of gpg-agent]) AC_DEFINE_UNQUOTED(SCDAEMON_NAME, "scdaemon", [The name of the scdaemon]) AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon", [The displayed name of scdaemon]) AC_DEFINE_UNQUOTED(DIRMNGR_NAME, "dirmngr", [The name of the dirmngr]) AC_DEFINE_UNQUOTED(DIRMNGR_DISP_NAME, "DirMngr", [The displayed name of dirmngr]) AC_DEFINE_UNQUOTED(KEYBOXD_NAME, "keyboxd", [The name of the keyboxd]) AC_DEFINE_UNQUOTED(KEYBOXD_DISP_NAME, "Keyboxd", [The displayed name of keyboxd]) AC_DEFINE_UNQUOTED(G13_NAME, "g13", [The name of the g13 tool]) AC_DEFINE_UNQUOTED(G13_DISP_NAME, "G13", [The displayed name of g13]) AC_DEFINE_UNQUOTED(GPGCONF_NAME, "gpgconf", [The name of the gpgconf tool]) AC_DEFINE_UNQUOTED(GPGCONF_DISP_NAME, "GPGConf", [The displayed name of gpgconf]) AC_DEFINE_UNQUOTED(GPGTAR_NAME, "gpgtar", [The name of the gpgtar tool]) AC_DEFINE_UNQUOTED(GPG_AGENT_SOCK_NAME, "S.gpg-agent", [The name of the agent socket]) AC_DEFINE_UNQUOTED(GPG_AGENT_EXTRA_SOCK_NAME, "S.gpg-agent.extra", [The name of the agent socket for remote access]) AC_DEFINE_UNQUOTED(GPG_AGENT_BROWSER_SOCK_NAME, "S.gpg-agent.browser", [The name of the agent socket for browsers]) AC_DEFINE_UNQUOTED(GPG_AGENT_SSH_SOCK_NAME, "S.gpg-agent.ssh", [The name of the agent socket for ssh]) AC_DEFINE_UNQUOTED(DIRMNGR_INFO_NAME, "DIRMNGR_INFO", [The name of the dirmngr info envvar]) AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon", [The name of the SCdaemon socket]) AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd", [The name of the keyboxd socket]) AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr", [The name of the dirmngr socket]) AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER, "hkps://hkps.pool.sks-keyservers.net", [The default keyserver for dirmngr to use, if none is explicitly given]) AC_DEFINE_UNQUOTED(GPGEXT_GPG, "gpg", [The standard binary file suffix]) if test "$have_w32_system" = yes; then AC_DEFINE_UNQUOTED(GNUPG_REGISTRY_DIR, "Software\\\\GNU\\\\GnuPG", [The directory part of the W32 registry keys]) fi # # Provide information about the build. # BUILD_REVISION="mym4_revision" AC_SUBST(BUILD_REVISION) AC_DEFINE_UNQUOTED(BUILD_REVISION, "$BUILD_REVISION", [GIT commit id revision used to build this package]) changequote(,)dnl BUILD_VERSION=`echo "$VERSION" | sed 's/\([0-9.]*\).*/\1./'` changequote([,])dnl BUILD_VERSION="${BUILD_VERSION}mym4_revision_dec" BUILD_FILEVERSION=`echo "${BUILD_VERSION}" | tr . ,` AC_SUBST(BUILD_VERSION) AC_SUBST(BUILD_FILEVERSION) AC_ARG_ENABLE([build-timestamp], AC_HELP_STRING([--enable-build-timestamp], [set an explicit build timestamp for reproducibility. (default is the current time in ISO-8601 format)]), [if test "$enableval" = "yes"; then BUILD_TIMESTAMP=`date -u +%Y-%m-%dT%H:%M+0000 2>/dev/null || date` else BUILD_TIMESTAMP="$enableval" fi BUILD_HOSTNAME="$ac_hostname"], [BUILD_TIMESTAMP="" BUILD_HOSTNAME=""]) AC_SUBST(BUILD_TIMESTAMP) AC_DEFINE_UNQUOTED(BUILD_TIMESTAMP, "$BUILD_TIMESTAMP", [The time this package was configured for a build]) AC_SUBST(BUILD_HOSTNAME) # # Print errors here so that they are visible all # together and the user can acquire them all together. # die=no if test "$have_gpg_error" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgpg-error to build this program. ** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libgpg-error *** (at least version $NEED_GPG_ERROR_VERSION is required.) ***]]) fi if test "$have_libgcrypt" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgcrypt to build this program. ** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libgcrypt/ *** (at least version $NEED_LIBGCRYPT_VERSION (API $NEED_LIBGCRYPT_API) is required.) ***]]) fi if test "$have_libassuan" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libassuan to build this program. *** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libassuan/ *** (at least version $NEED_LIBASSUAN_VERSION (API $NEED_LIBASSUAN_API) is required). ***]]) fi if test "$have_ksba" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libksba to build this program. *** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libksba/ *** (at least version $NEED_KSBA_VERSION using API $NEED_KSBA_API is required). ***]]) fi if test "$gnupg_have_ldap" = yes; then if test "$have_w32ce_system" = yes; then AC_MSG_NOTICE([[ *** Note that CeGCC might be broken, a package fixing this is: *** http://files.kolab.org/local/windows-ce/ *** source/wldap32_0.1-mingw32ce.orig.tar.gz *** binary/wldap32-ce-arm-dev_0.1-1_all.deb ***]]) fi fi if test "$have_npth" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** It is now required to build with support for the *** New Portable Threads Library (nPth). Please install this *** library first. The library is for example available at *** https://gnupg.org/ftp/gcrypt/npth/ *** (at least version $NEED_NPTH_VERSION (API $NEED_NPTH_API) is required). ***]]) fi if test "$require_iconv" = yes; then if test "$am_func_iconv" != yes; then die=yes AC_MSG_NOTICE([[ *** *** The system does not provide a working iconv function. Please *** install a suitable library; for example GNU Libiconv which is *** available at: *** https://ftp.gnu.org/gnu/libiconv/ ***]]) fi fi if test "$use_ccid_driver" = yes; then if test "$have_libusb" != yes; then die=yes AC_MSG_NOTICE([[ *** *** You need libusb to build the internal ccid driver. Please *** install a libusb suitable for your system. ***]]) fi fi if test "$die" = "yes"; then AC_MSG_ERROR([[ *** *** Required libraries not found. Please consult the above messages *** and install them before running configure again. ***]]) fi AC_CONFIG_FILES([ m4/Makefile Makefile po/Makefile.in common/Makefile common/w32info-rc.h kbx/Makefile g10/Makefile sm/Makefile agent/Makefile scd/Makefile g13/Makefile dirmngr/Makefile tools/Makefile doc/Makefile tests/Makefile tests/gpgscm/Makefile tests/openpgp/Makefile tests/migrations/Makefile tests/gpgsm/Makefile tests/gpgme/Makefile tests/pkits/Makefile g10/gpg.w32-manifest ]) AC_OUTPUT echo " GnuPG v${VERSION} has been configured as follows: Revision: mym4_revision (mym4_revision_dec) Platform: $PRINTABLE_OS_NAME ($host) OpenPGP: $build_gpg S/MIME: $build_gpgsm Agent: $build_agent Smartcard: $build_scdaemon $build_scdaemon_extra G13: $build_g13 Dirmngr: $build_dirmngr Keyboxd: $build_keyboxd Gpgtar: $build_gpgtar WKS tools: $build_wks_tools Protect tool: $show_gnupg_protect_tool_pgm LDAP wrapper: $show_gnupg_dirmngr_ldap_pgm Default agent: $show_gnupg_agent_pgm Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm Default keyboxd: $show_gnupg_keyboxd_pgm Default dirmngr: $show_gnupg_dirmngr_pgm Dirmngr auto start: $dirmngr_auto_start Readline support: $gnupg_cv_have_readline LDAP support: $gnupg_have_ldap TLS support: $use_tls_library TOFU support: $use_tofu Tor support: $show_tor_support " if test x"$use_regex" != xyes ; then echo " Warning: No regular expression support available. OpenPGP trust signatures won't work. gpg-check-pattern will not be built. " fi if test "x${gpg_config_script_warn}" != x; then cat <. */ #include +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #include #include #include #include #include #include #include #include #include #include "../common/logging.h" #include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/asshelp.h" #include "../common/i18n.h" #include "../common/util.h" #include "../common/init.h" +#include "../common/argparse.h" /* temporary hack. */ /* Constants for the options. */ enum { oQuiet = 'q', oVerbose = 'v', oLocal = 'l', oUrl = 'u', oOCSP = 500, oPing, oCacheCert, oValidate, oLookup, oLoadCRL, oSquidMode, oPEM, oEscapedPEM, oForceDefaultResponder }; /* The list of options as used by the argparse.c code. */ static ARGPARSE_OPTS opts[] = { { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oOCSP, "ocsp", 0, N_("use OCSP instead of CRLs") }, { oPing, "ping", 0, N_("check whether a dirmngr is running")}, { oCacheCert,"cache-cert",0, N_("add a certificate to the cache")}, { oValidate, "validate", 0, N_("validate a certificate")}, { oLookup, "lookup", 0, N_("lookup a certificate")}, { oLocal, "local", 0, N_("lookup only locally stored certificates")}, { oUrl, "url", 0, N_("expect an URL for --lookup")}, { oLoadCRL, "load-crl", 0, N_("load a CRL into the dirmngr")}, { oSquidMode,"squid-mode",0, N_("special mode for use by Squid")}, { oPEM, "pem", 0, N_("expect certificates in PEM format")}, { oForceDefaultResponder, "force-default-responder", 0, N_("force the use of the default OCSP responder")}, ARGPARSE_end () }; /* The usual structure for the program flags. */ static struct { int quiet; int verbose; const char *dirmngr_program; int force_default_responder; int pem; int escaped_pem; /* PEM is additional percent encoded. */ int url; /* Expect an URL. */ int local; /* Lookup up only local certificates. */ int use_ocsp; } opt; /* Communication structure for the certificate inquire callback. */ struct inq_cert_parm_s { assuan_context_t ctx; const unsigned char *cert; size_t certlen; }; /* Base64 conversion tables. */ static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; static unsigned char asctobin[256]; /* runtime initialized */ /* Build the helptable for radix64 to bin conversion. */ static void init_asctobin (void) { static int initialized; int i; unsigned char *s; if (initialized) return; initialized = 1; for (i=0; i < 256; i++ ) asctobin[i] = 255; /* Used to detect invalid characters. */ for (s=bintoasc, i=0; *s; s++, i++) asctobin[*s] = i; } /* Prototypes. */ static gpg_error_t read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen); static gpg_error_t do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename); static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern); static gpg_error_t squid_loop_body (assuan_context_t ctx); /* Function called by argparse.c to display information. */ static const char * my_strusage (int level) { const char *p; switch(level) { case 11: p = "dirmngr-client (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: dirmngr-client [options] " "[certfile|pattern] (-h for help)\n"); break; case 41: p = _("Syntax: dirmngr-client [options] [certfile|pattern]\n" "Test an X.509 certificate against a CRL or do an OCSP check\n" "The process returns 0 if the certificate is valid, 1 if it is\n" "not valid and other error codes for general failures\n"); break; default: p = NULL; } return p; } int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; assuan_context_t ctx; gpg_error_t err; unsigned char *certbuf; size_t certbuflen = 0; int cmd_ping = 0; int cmd_cache_cert = 0; int cmd_validate = 0; int cmd_lookup = 0; int cmd_loadcrl = 0; int cmd_squid_mode = 0; early_system_init (); set_strusage (my_strusage); log_set_prefix ("dirmngr-client", GPGRT_LOG_WITH_PREFIX); /* For W32 we need to initialize the socket subsystem. Because we don't use Pth we need to do this explicit. */ #ifdef HAVE_W32_SYSTEM { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif /*HAVE_W32_SYSTEM*/ /* Init Assuan. */ assuan_set_assuan_log_prefix (log_get_prefix (NULL)); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); /* Setup I18N. */ i18n_init(); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* Do not remove the args. */ while (arg_parse (&pargs, opts) ) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oQuiet: opt.quiet++; break; case oOCSP: opt.use_ocsp++; break; case oPing: cmd_ping = 1; break; case oCacheCert: cmd_cache_cert = 1; break; case oValidate: cmd_validate = 1; break; case oLookup: cmd_lookup = 1; break; case oUrl: opt.url = 1; break; case oLocal: opt.local = 1; break; case oLoadCRL: cmd_loadcrl = 1; break; case oPEM: opt.pem = 1; break; case oSquidMode: opt.pem = 1; opt.escaped_pem = 1; cmd_squid_mode = 1; break; case oForceDefaultResponder: opt.force_default_responder = 1; break; default : pargs.err = 2; break; } } if (log_get_errorcount (0)) exit (2); if (cmd_ping) err = 0; else if (cmd_lookup || cmd_loadcrl) { if (!argc) usage (1); err = 0; } else if (cmd_squid_mode) { err = 0; if (argc) usage (1); } else if (!argc) { err = read_certificate (NULL, &certbuf, &certbuflen); if (err) log_error (_("error reading certificate from stdin: %s\n"), gpg_strerror (err)); } else if (argc == 1) { err = read_certificate (*argv, &certbuf, &certbuflen); if (err) log_error (_("error reading certificate from '%s': %s\n"), *argv, gpg_strerror (err)); } else { err = 0; usage (1); } if (log_get_errorcount (0)) exit (2); if (certbuflen > 20000) { log_error (_("certificate too large to make any sense\n")); exit (2); } err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.dirmngr_program ? opt.dirmngr_program : gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR), ! cmd_ping, opt.verbose, 0, NULL, NULL); if (err) { log_error (_("can't connect to the dirmngr: %s\n"), gpg_strerror (err)); exit (2); } if (cmd_ping) ; else if (cmd_squid_mode) { while (!(err = squid_loop_body (ctx))) ; if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; } else if (cmd_lookup) { int last_err = 0; for (; argc; argc--, argv++) { err = do_lookup (ctx, *argv); if (err) { log_error (_("lookup failed: %s\n"), gpg_strerror (err)); last_err = err; } } err = last_err; } else if (cmd_loadcrl) { int last_err = 0; for (; argc; argc--, argv++) { err = do_loadcrl (ctx, *argv); if (err) { log_error (_("loading CRL '%s' failed: %s\n"), *argv, gpg_strerror (err)); last_err = err; } } err = last_err; } else if (cmd_cache_cert) { err = do_cache (ctx, certbuf, certbuflen); xfree (certbuf); } else if (cmd_validate) { err = do_validate (ctx, certbuf, certbuflen); xfree (certbuf); } else { err = do_check (ctx, certbuf, certbuflen); xfree (certbuf); } assuan_release (ctx); if (cmd_ping) { if (!opt.quiet) log_info (_("a dirmngr daemon is up and running\n")); return 0; } else if (cmd_lookup|| cmd_loadcrl || cmd_squid_mode) return err? 1:0; else if (cmd_cache_cert) { if (err && gpg_err_code (err) == GPG_ERR_DUP_VALUE ) { if (!opt.quiet) log_info (_("certificate already cached\n")); } else if (err) { log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return 1; } return 0; } else if (cmd_validate && err) { log_error (_("validation of certificate failed: %s\n"), gpg_strerror (err)); return 1; } else if (!err) { if (!opt.quiet) log_info (_("certificate is valid\n")); return 0; } else if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) { if (!opt.quiet) log_info (_("certificate has been revoked\n")); return 1; } else { log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); return 2; } } /* Print status line from the assuan protocol. */ static gpg_error_t status_cb (void *opaque, const char *line) { (void)opaque; if (opt.verbose > 2) log_info (_("got status: '%s'\n"), line); return 0; } /* Print data as retrieved by the lookup function. */ static gpg_error_t data_cb (void *opaque, const void *buffer, size_t length) { gpg_error_t err; struct b64state *state = opaque; if (buffer) { err = b64enc_write (state, buffer, length); if (err) log_error (_("error writing base64 encoding: %s\n"), gpg_strerror (err)); } return 0; } /* Read the first PEM certificate from the file FNAME. If fname is NULL the next certificate is read from stdin. The certificate is returned in an alloced buffer whose address will be returned in RBUF and its length in RBUFLEN. */ static gpg_error_t read_pem_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) { FILE *fp; int c; int pos; int value; unsigned char *buf; size_t bufsize, buflen; enum { s_init, s_idle, s_lfseen, s_begin, s_b64_0, s_b64_1, s_b64_2, s_b64_3, s_waitend } state = s_init; init_asctobin (); fp = fname? fopen (fname, "r") : stdin; if (!fp) return gpg_error_from_errno (errno); pos = 0; value = 0; bufsize = 8192; buf = xmalloc (bufsize); buflen = 0; while ((c=getc (fp)) != EOF) { int escaped_c = 0; if (opt.escaped_pem) { if (c == '%') { char tmp[2]; if ((c = getc(fp)) == EOF) break; tmp[0] = c; if ((c = getc(fp)) == EOF) break; tmp[1] = c; if (!hexdigitp (tmp) || !hexdigitp (tmp+1)) { log_error ("invalid percent escape sequence\n"); state = s_idle; /* Force an error. */ /* Skip to end of line. */ while ( (c=getc (fp)) != EOF && c != '\n') ; goto ready; } c = xtoi_2 (tmp); escaped_c = 1; } else if (c == '\n') goto ready; /* Ready. */ } switch (state) { case s_idle: if (c == '\n') { state = s_lfseen; pos = 0; } break; case s_init: state = s_lfseen; /* fall through */ case s_lfseen: if (c != "-----BEGIN "[pos]) state = s_idle; else if (pos == 10) state = s_begin; else pos++; break; case s_begin: if (c == '\n') state = s_b64_0; break; case s_b64_0: case s_b64_1: case s_b64_2: case s_b64_3: { if (buflen >= bufsize) { bufsize += 8192; buf = xrealloc (buf, bufsize); } if (c == '-') state = s_waitend; else if ((c = asctobin[c & 0xff]) == 255 ) ; /* Just skip invalid base64 characters. */ else if (state == s_b64_0) { value = c << 2; state = s_b64_1; } else if (state == s_b64_1) { value |= (c>>4)&3; buf[buflen++] = value; value = (c<<4)&0xf0; state = s_b64_2; } else if (state == s_b64_2) { value |= (c>>2)&15; buf[buflen++] = value; value = (c<<6)&0xc0; state = s_b64_3; } else { value |= c&0x3f; buf[buflen++] = value; state = s_b64_0; } } break; case s_waitend: /* Note that we do not check that the base64 decoder has been left in the expected state. We assume that the PEM header is just fine. However we need to wait for the real LF and not a trailing percent escaped one. */ if (c== '\n' && !escaped_c) goto ready; break; default: BUG(); } } ready: if (fname) fclose (fp); if (state == s_init && c == EOF) { xfree (buf); return gpg_error (GPG_ERR_EOF); } else if (state != s_waitend) { log_error ("no certificate or invalid encoded\n"); xfree (buf); return gpg_error (GPG_ERR_INV_ARMOR); } *rbuf = buf; *rbuflen = buflen; return 0; } /* Read a binary certificate from the file FNAME. If fname is NULL the file is read from stdin. The certificate is returned in an alloced buffer whose address will be returned in RBUF and its length in RBUFLEN. */ static gpg_error_t read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) { gpg_error_t err; FILE *fp; unsigned char *buf; size_t nread, bufsize, buflen; if (opt.pem) return read_pem_certificate (fname, rbuf, rbuflen); else if (fname) { /* A filename has been given. Let's just assume it is in PEM format and decode it, and fall back to interpreting it as binary certificate if that fails. */ err = read_pem_certificate (fname, rbuf, rbuflen); if (! err) return 0; } fp = fname? fopen (fname, "rb") : stdin; if (!fp) return gpg_error_from_errno (errno); buf = NULL; bufsize = buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xmalloc (bufsize); else buf = xrealloc (buf, bufsize); nread = fread (buf+buflen, 1, NCHUNK, fp); if (nread < NCHUNK && ferror (fp)) { err = gpg_error_from_errno (errno); xfree (buf); if (fname) fclose (fp); return err; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK if (fname) fclose (fp); *rbuf = buf; *rbuflen = buflen; return 0; } /* Callback for the inquire fiunction to send back the certificate. */ static gpg_error_t inq_cert (void *opaque, const char *line) { struct inq_cert_parm_s *parm = opaque; gpg_error_t err; if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10])) { err = assuan_send_data (parm->ctx, parm->cert, parm->certlen); } else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back and empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else if (!strncmp (line, "SENDCERT_SKI", 12) && (line[12]==' ' || !line[12])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back an empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else if (!strncmp (line, "SENDISSUERCERT", 14) && (line[14] == ' ' || !line[14])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back an empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else { log_info (_("unsupported inquiry '%s'\n"), line); err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); /* Note that this error will let assuan_transact terminate immediately instead of return the error to the caller. It is not clear whether this is the desired behaviour - it may change in future. */ } return err; } /* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. Return a proper error code. */ static gpg_error_t do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, (opt.use_ocsp && opt.force_default_responder ? "CHECKOCSP --force-default-responder" : opt.use_ocsp? "CHECKOCSP" : "CHECKCRL"), NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. Return a proper error code. */ static gpg_error_t do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, "CACHECERT", NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Check the certificate CERT,CERTLEN for validity using dirmngrs internal validate feature. Return a proper error code. */ static gpg_error_t do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, "VALIDATE", NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Load a CRL into the dirmngr. */ static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename) { gpg_error_t err; const char *s; char *fname, *line, *p; if (opt.url) fname = xstrdup (filename); else { #ifdef HAVE_CANONICALIZE_FILE_NAME fname = canonicalize_file_name (filename); if (!fname) { log_error ("error canonicalizing '%s': %s\n", filename, strerror (errno)); return gpg_error (GPG_ERR_GENERAL); } #else fname = xstrdup (filename); #endif if (*fname != '/') { log_error (_("absolute file name expected\n")); return gpg_error (GPG_ERR_GENERAL); } } line = xmalloc (8 + 6 + strlen (fname) * 3 + 1); p = stpcpy (line, "LOADCRL "); if (opt.url) p = stpcpy (p, "--url "); for (s = fname; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); xfree (line); xfree (fname); return err; } /* Do a LDAP lookup using PATTERN and print the result in a base-64 encoded format. */ static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern) { gpg_error_t err; const unsigned char *s; char *line, *p; struct b64state state; if (opt.verbose) log_info (_("looking up '%s'\n"), pattern); err = b64enc_start (&state, stdout, NULL); if (err) return err; line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1); p = stpcpy (line, "LOOKUP "); if (opt.url) p = stpcpy (p, "--url "); if (opt.local) p = stpcpy (p, "--cache-only "); for (s=pattern; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; err = assuan_transact (ctx, line, data_cb, &state, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); err = b64enc_finish (&state); xfree (line); return err; } /* The body of an endless loop: Read a line from stdin, retrieve the certificate from it, validate it and print "ERR" or "OK" to stdout. Continue. */ static gpg_error_t squid_loop_body (assuan_context_t ctx) { gpg_error_t err; unsigned char *certbuf; size_t certbuflen = 0; err = read_pem_certificate (NULL, &certbuf, &certbuflen); if (gpg_err_code (err) == GPG_ERR_EOF) return err; if (err) { log_error (_("error reading certificate from stdin: %s\n"), gpg_strerror (err)); puts ("ERROR"); return 0; } err = do_check (ctx, certbuf, certbuflen); xfree (certbuf); if (!err) { if (opt.verbose) log_info (_("certificate is valid\n")); puts ("OK"); } else { if (!opt.quiet) { if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) log_info (_("certificate has been revoked\n")); else log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); } puts ("ERROR"); } fflush (stdout); return 0; } diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 9b80a20ba..9838b1c7b 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1,2357 +1,2361 @@ /* dirmngr.c - Keyserver and X.509 LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010, 2011 g10 Code GmbH * Copyright (C) 2014 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0+ */ #include +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #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" +#include "../common/argparse.h" /* temporary hack. */ #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, 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 ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ), ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ), #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_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_s_s (oDebugLevel, "debug-level", N_("|LEVEL|set the debugging level to LEVEL")), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write server mode logs to FILE")), ARGPARSE_s_n (oBatch, "batch", N_("run without asking a user")), ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")), ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")), ARGPARSE_s_n (oAllowVersionCheck, "allow-version-check", N_("allow online software version check")), ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")), ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")), ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp", N_("ignore HTTP CRL distribution points")), ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp", N_("ignore LDAP CRL distribution points")), ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url", N_("ignore certificate contained OCSP service URLs")), ARGPARSE_s_s (oHTTPProxy, "http-proxy", N_("|URL|redirect all HTTP requests to URL")), ARGPARSE_s_s (oLDAPProxy, "ldap-proxy", N_("|HOST|use HOST for LDAP queries")), ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy", N_("do not use fallback hosts with --ldap-proxy")), ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file", N_("|FILE|read LDAP server list from FILE")), ARGPARSE_s_n (oLDAPAddServers, "add-servers", N_("add new servers discovered in CRL distribution" " points to serverlist")), ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout", N_("|N|set LDAP timeout to N seconds")), ARGPARSE_s_s (oOCSPResponder, "ocsp-responder", N_("|URL|use OCSP responder at URL")), ARGPARSE_s_s (oOCSPSigner, "ocsp-signer", N_("|FPR|OCSP response signed by FPR")), ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"), ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"), ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"), ARGPARSE_s_i (oMaxReplies, "max-replies", N_("|N|do not return more than N items in one query")), ARGPARSE_s_s (oNameServer, "nameserver", "@"), ARGPARSE_s_s (oKeyServer, "keyserver", "@"), ARGPARSE_s_s (oHkpCaCert, "hkp-cacert", N_("|FILE|use the CA certificates in FILE for HKP over TLS")), ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via Tor")), ARGPARSE_s_n (oNoUseTor, "no-use-tor", "@"), ARGPARSE_s_n (oDisableIPv4, "disable-ipv4", "@"), ARGPARSE_s_n (oDisableIPv6, "disable-ipv6", "@"), ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */ ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/ ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"), ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"), ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"), ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", "@"), ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"), ARGPARSE_s_n (oStandardResolver, "standard-resolver", "@"), ARGPARSE_s_n (oRecursiveResolver, "recursive-resolver", "@"), ARGPARSE_s_i (oResolverTimeout, "resolver-timeout", "@"), ARGPARSE_s_i (oConnectTimeout, "connect-timeout", "@"), ARGPARSE_s_i (oConnectQuickTimeout, "connect-quick-timeout", "@"), ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), 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); #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); /* NPth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static const char * my_strusage( int level ) { const char *p; switch ( level ) { case 11: p = "@DIRMNGR@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)"); break; case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n" "Keyserver, CRL, and OCSP access for @GNUPG@\n"); break; default: p = NULL; } return p; } /* Callback from libksba to hash a provided buffer. Our current implementation does only allow SHA-1 for hashing. This may be extended by mapping the name, testing for algorithm 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 (ARGPARSE_ARGS *pargs, int reread) { if (!pargs) { /* Reset mode. */ opt.quiet = 0; opt.verbose = 0; opt.debug = 0; opt.ldap_wrapper_program = NULL; opt.disable_http = 0; opt.disable_ldap = 0; opt.honor_http_proxy = 0; opt.http_proxy = NULL; opt.ldap_proxy = NULL; opt.only_ldap_proxy = 0; opt.ignore_http_dp = 0; opt.ignore_ldap_dp = 0; opt.ignore_ocsp_service_url = 0; opt.allow_ocsp = 0; opt.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; ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; const char *shell; unsigned configlineno; int parse_debug = 0; int default_config =1; int greeting = 0; int nogreeting = 0; int nodetach = 0; int csh_style = 0; char *logfile = NULL; #if USE_LDAP char *ldapfile = NULL; #endif /*USE_LDAP*/ int debug_wait = 0; int rc; struct assuan_malloc_hooks malloc_hooks; early_system_init (); 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= 1|(1<<6); /* do not remove the args, ignore version */ while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) parse_debug++; else if (pargs.r_opt == oOptions) { /* Yes there is one, so we do not try the default one, but read the option file when it is encountered at the commandline */ default_config = 0; } else if (pargs.r_opt == oNoOptions) default_config = 0; /* --no-options */ else if (pargs.r_opt == oHomedir) { gnupg_set_homedir (pargs.r.ret_str); } } socket_name = dirmngr_socket_name (); if (default_config) configname = make_filename (gnupg_homedir (), DIRMNGR_NAME".conf", NULL ); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* do not remove the args */ next_pass: if (configname) { configlineno = 0; configfp = fopen (configname, "r"); if (!configfp) { if (default_config) { if( parse_debug ) log_info (_("Note: no default option file '%s'\n"), configname ); } else { log_error (_("option file '%s': %s\n"), configname, strerror(errno) ); exit(2); } xfree (configname); configname = NULL; } if (parse_debug && configname ) log_info (_("reading options from '%s'\n"), configname ); default_config = 0; } while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) { if (parse_rereadable_options (&pargs, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case aServer: case aDaemon: case aSupervised: case aShutdown: case aFlush: case aListCRLs: case aLoadCRL: case aFetchCRL: case aGPGConfList: case aGPGConfTest: cmd = pargs.r_opt; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oBatch: opt.batch=1; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oOptions: /* Config files may not be nested (silently ignore them) */ if (!configfp) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; break; case oNoOptions: break; /* no-options */ case oHomedir: /* Ignore this option here. */; break; case oNoDetach: nodetach = 1; break; case oLogFile: logfile = pargs.r.ret_str; break; case oCsh: csh_style = 1; break; case oSh: csh_style = 0; break; case oLDAPFile: # if USE_LDAP ldapfile = pargs.r.ret_str; # endif /*USE_LDAP*/ break; case oLDAPAddServers: opt.add_new_ldapservers = 1; break; case oLDAPTimeout: opt.ldaptimeout = pargs.r.ret_int; break; case oFakedSystemTime: gnupg_set_time ((time_t)pargs.r.ret_ulong, 0); break; case oForce: opt.force = 1; break; case oSocketName: socket_name = pargs.r.ret_str; break; case oListenBacklog: listen_backlog = pargs.r.ret_int; break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose (configfp); configfp = NULL; /* Keep a copy of the name so that it can be read on SIGHUP. */ opt.config_filename = configname; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (log_get_errorcount(0)) exit(2); if (nogreeting ) greeting = 0; if (!opt.homedir_cache) opt.homedir_cache = xstrdup (gnupg_homedir ()); if (greeting) { es_fprintf (es_stderr, "%s %s; %s\n", strusage(11), strusage(13), strusage(14) ); es_fprintf (es_stderr, "%s\n", strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION log_info ("NOTE: this is a development version!\n"); #endif /* 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) 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); xfree (ldapfile); } else opt.ldapservers = parse_ldapserver_file (ldapfile); #endif /*USE_LDAP*/ #ifndef HAVE_W32_SYSTEM /* We need to ignore the PIPE signal because the we might log to a socket and that code handles EPIPE properly. The ldap wrapper also requires us to ignore this silly signal. Assuan would set this signal to ignore anyway.*/ signal (SIGPIPE, SIG_IGN); #endif /* Ready. Now to our duties. */ if (!cmd) 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; char *filename_esc; /* First the configuration file. This is not an option, but it is vital information for GPG Conf. */ if (!opt.config_filename) opt.config_filename = make_filename (gnupg_homedir (), "dirmngr.conf", NULL ); filename = percent_escape (opt.config_filename, NULL); es_printf ("gpgconf-dirmngr.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT, filename); xfree (filename); es_printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT); es_printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE); /* --csh and --sh are mutually exclusive, something we can not express in GPG Conf. --options is only usable from the command line, really. --debug-all interacts with --debug, and having both of them is thus problematic. --no-detach is also only usable on the command line. --batch is unused. */ filename = make_filename (gnupg_homedir (), "dirmngr_ldapservers.conf", NULL); filename_esc = percent_escape (filename, NULL); es_printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename_esc); xfree (filename); es_printf ("ldaptimeout:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT); es_printf ("max-replies:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES); es_printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("allow-version-check:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE); es_printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE); /* Note: The next one is to fix a typo in gpgconf - should be removed eventually. */ es_printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("use-tor:%lu:\n", flags | GC_OPT_FLAG_NONE); 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 ("nameserver:%lu:\n", flags | GC_OPT_FLAG_NONE); es_printf ("resolver-timeout:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, 0); } 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) { char buffer[1024]; char *p; ldap_server_t server, serverstart, *serverend; int c; unsigned int lineno = 0; estream_t fp; fp = es_fopen (filename, "r"); if (!fp) { log_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) { ARGPARSE_ARGS pargs; FILE *fp; unsigned int configlineno = 0; int dummy; if (!opt.config_filename) return; /* No config file. */ fp = fopen (opt.config_filename, "r"); if (!fp) { log_error (_("option file '%s': %s\n"), opt.config_filename, strerror(errno) ); return; } parse_rereadable_options (NULL, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = 1; /* do not remove the args */ while (optfile_parse (fp, opt.config_filename, &configlineno, &pargs, opts) ) { if (pargs.r_opt < -1) pargs.err = 1; /* Print a warning. */ else /* Try to parse this option - ignore unchangeable ones. */ parse_rereadable_options (&pargs, 1); } fclose (fp); 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", strusage(11), strusage(13) ); cleanup (); dirmngr_exit (0); } break; case SIGINT: log_info (_("SIGINT received - immediate shutdown\n")); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); dirmngr_exit (0); break; default: log_info (_("signal %d received - no action defined\n"), signo); } } #endif /*!HAVE_W32_SYSTEM*/ /* Thread to do the housekeeping. */ static void * housekeeping_thread (void *arg) { static int sentinel; time_t curtime; 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", strusage(11), strusage(13)); } const char* dirmngr_get_current_socket_name (void) { if (socket_name) return socket_name; else return dirmngr_socket_name (); } diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c index 82d4a6ebc..a96722619 100644 --- a/dirmngr/dirmngr_ldap.c +++ b/dirmngr/dirmngr_ldap.c @@ -1,769 +1,773 @@ /* dirmngr-ldap.c - The LDAP helper for dirmngr. * Copyright (C) 2004 g10 Code GmbH * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #include #include #ifdef HAVE_W32_SYSTEM # include # include # include # include # include "ldap-url.h" #else /* For OpenLDAP, to enable the API that we're using. */ # define LDAP_DEPRECATED 1 # include #endif #include #include "../common/logging.h" #include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/i18n.h" #include "../common/util.h" #include "../common/init.h" +#include "../common/argparse.h" /* temporary hack. */ /* There is no need for the npth_unprotect and leave functions here; * thus we redefine them to nops. We keep them in the code just for * the case we ever want to reuse parts of the code in npth programs. */ static void npth_unprotect (void) { } static void npth_protect (void) { } #ifdef HAVE_W32_SYSTEM typedef LDAP_TIMEVAL my_ldap_timeval_t; #else typedef struct timeval my_ldap_timeval_t; #endif #define DEFAULT_LDAP_TIMEOUT 15 /* Arbitrary long timeout. */ /* Constants for the options. */ enum { oQuiet = 'q', oVerbose = 'v', oTimeout = 500, oMulti, oProxy, oHost, oPort, oUser, oPass, oEnvPass, oDN, oFilter, oAttr, oTls, oOnlySearchTimeout, oLogWithPID }; /* The list of options as used by the argparse.c code. */ static ARGPARSE_OPTS opts[] = { { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oTimeout, "timeout", 1, N_("|N|set LDAP timeout to N seconds")}, { oMulti, "multi", 0, N_("return all values in" " a record oriented format")}, { oProxy, "proxy", 2, N_("|NAME|ignore host part and connect through NAME")}, { oTls, "tls", 0, N_("force a TLS connection")}, { oHost, "host", 2, N_("|NAME|connect to host NAME")}, { oPort, "port", 1, N_("|N|connect to port N")}, { oUser, "user", 2, N_("|NAME|use user NAME for authentication")}, { oPass, "pass", 2, N_("|PASS|use password PASS" " for authentication")}, { oEnvPass, "env-pass", 0, N_("take password from $DIRMNGR_LDAP_PASS")}, { oDN, "dn", 2, N_("|STRING|query DN STRING")}, { oFilter, "filter", 2, N_("|STRING|use STRING as filter expression")}, { oAttr, "attr", 2, N_("|STRING|return the attribute STRING")}, { oOnlySearchTimeout, "only-search-timeout", 0, "@"}, { oLogWithPID,"log-with-pid", 0, "@"}, ARGPARSE_end () }; /* A structure with module options. This is not a static variable because if we are not build as a standalone binary, each thread using this module needs to handle its own values. */ struct my_opt_s { int quiet; int verbose; my_ldap_timeval_t timeout;/* Timeout for the LDAP search functions. */ unsigned int alarm_timeout; /* And for the alarm based timeout. */ int multi; int force_tls; estream_t outstream; /* Send output to this stream. */ /* Note that we can't use const for the strings because ldap_* are not defined that way. */ char *proxy; /* Host and Port override. */ char *user; /* Authentication user. */ char *pass; /* Authentication password. */ char *host; /* Override host. */ int port; /* Override port. */ char *dn; /* Override DN. */ char *filter;/* Override filter. */ char *attr; /* Override attribute. */ }; typedef struct my_opt_s *my_opt_t; /* Prototypes. */ #ifndef HAVE_W32_SYSTEM static void catch_alarm (int dummy); #endif static int process_url (my_opt_t myopt, const char *url); /* Function called by argparse.c to display information. */ static const char * my_strusage (int level) { const char *p; switch(level) { case 11: p = "dirmngr_ldap (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: dirmngr_ldap [options] [URL] (-h for help)\n"); break; case 41: p = _("Syntax: dirmngr_ldap [options] [URL]\n" "Internal LDAP helper for Dirmngr\n" "Interface and options may change without notice\n"); break; default: p = NULL; } return p; } int main (int argc, char **argv) { ARGPARSE_ARGS pargs; int any_err = 0; char *p; int only_search_timeout = 0; struct my_opt_s my_opt_buffer; my_opt_t myopt = &my_opt_buffer; char *malloced_buffer1 = NULL; memset (&my_opt_buffer, 0, sizeof my_opt_buffer); early_system_init (); set_strusage (my_strusage); log_set_prefix ("dirmngr_ldap", GPGRT_LOG_WITH_PREFIX); /* Setup I18N and common subsystems. */ i18n_init(); init_common_subsystems (&argc, &argv); es_set_binary (es_stdout); myopt->outstream = es_stdout; /* LDAP defaults */ myopt->timeout.tv_sec = DEFAULT_LDAP_TIMEOUT; myopt->timeout.tv_usec = 0; myopt->alarm_timeout = 0; /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* Do not remove the args. */ while (arg_parse (&pargs, opts) ) { switch (pargs.r_opt) { case oVerbose: myopt->verbose++; break; case oQuiet: myopt->quiet++; break; case oTimeout: myopt->timeout.tv_sec = pargs.r.ret_int; myopt->timeout.tv_usec = 0; myopt->alarm_timeout = pargs.r.ret_int; break; case oOnlySearchTimeout: only_search_timeout = 1; break; case oMulti: myopt->multi = 1; break; case oUser: myopt->user = pargs.r.ret_str; break; case oPass: myopt->pass = pargs.r.ret_str; break; case oEnvPass: myopt->pass = getenv ("DIRMNGR_LDAP_PASS"); break; case oProxy: myopt->proxy = pargs.r.ret_str; break; case oTls: myopt->force_tls = 1; break; case oHost: myopt->host = pargs.r.ret_str; break; case oPort: myopt->port = pargs.r.ret_int; break; case oDN: myopt->dn = pargs.r.ret_str; break; case oFilter: myopt->filter = pargs.r.ret_str; break; case oAttr: myopt->attr = pargs.r.ret_str; break; case oLogWithPID: { unsigned int oldflags; log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | GPGRT_LOG_WITH_PID); } break; default : pargs.err = ARGPARSE_PRINT_ERROR; break; } } if (only_search_timeout) myopt->alarm_timeout = 0; if (myopt->proxy) { malloced_buffer1 = xtrystrdup (myopt->proxy); if (!malloced_buffer1) { log_error ("error copying string: %s\n", strerror (errno)); return 1; } myopt->host = malloced_buffer1; p = strchr (myopt->host, ':'); if (p) { *p++ = 0; myopt->port = atoi (p); } if (!myopt->port) myopt->port = 389; /* make sure ports gets overridden. */ } if (myopt->port < 0 || myopt->port > 65535) log_error (_("invalid port number %d\n"), myopt->port); if (log_get_errorcount (0)) exit (2); if (argc < 1) usage (1); if (myopt->alarm_timeout) { #ifndef HAVE_W32_SYSTEM # if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION) struct sigaction act; act.sa_handler = catch_alarm; sigemptyset (&act.sa_mask); act.sa_flags = 0; if (sigaction (SIGALRM,&act,NULL)) # else if (signal (SIGALRM, catch_alarm) == SIG_ERR) # endif log_fatal ("unable to register timeout handler\n"); #endif } for (; argc; argc--, argv++) if (process_url (myopt, *argv)) any_err = 1; xfree (malloced_buffer1); return any_err; } #ifndef HAVE_W32_SYSTEM static void catch_alarm (int dummy) { (void)dummy; _exit (10); } #endif #ifdef HAVE_W32_SYSTEM static DWORD CALLBACK alarm_thread (void *arg) { HANDLE timer = arg; WaitForSingleObject (timer, INFINITE); _exit (10); return 0; } #endif static void set_timeout (my_opt_t myopt) { if (myopt->alarm_timeout) { #ifdef HAVE_W32_SYSTEM static HANDLE timer; LARGE_INTEGER due_time; /* A negative value is a relative time. */ due_time.QuadPart = (unsigned long long)-10000000 * myopt->alarm_timeout; if (!timer) { SECURITY_ATTRIBUTES sec_attr; DWORD tid; memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; /* Create a manual resettable timer. */ timer = CreateWaitableTimer (NULL, TRUE, NULL); /* Initially set the timer. */ SetWaitableTimer (timer, &due_time, 0, NULL, NULL, 0); if (CreateThread (&sec_attr, 0, alarm_thread, timer, 0, &tid)) log_error ("failed to create alarm thread\n"); } else /* Retrigger the timer. */ SetWaitableTimer (timer, &due_time, 0, NULL, NULL, 0); #else alarm (myopt->alarm_timeout); #endif } } /* Helper for fetch_ldap(). */ static int print_ldap_entries (my_opt_t myopt, LDAP *ld, LDAPMessage *msg, char *want_attr) { LDAPMessage *item; int any = 0; for (npth_unprotect (), item = ldap_first_entry (ld, msg), npth_protect (); item; npth_unprotect (), item = ldap_next_entry (ld, item), npth_protect ()) { BerElement *berctx; char *attr; if (myopt->verbose > 1) log_info (_("scanning result for attribute '%s'\n"), want_attr? want_attr : "[all]"); if (myopt->multi) { /* Write item marker. */ if (es_fwrite ("I\0\0\0\0", 5, 1, myopt->outstream) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); return -1; } } for (npth_unprotect (), attr = ldap_first_attribute (ld, item, &berctx), npth_protect (); attr; npth_unprotect (), attr = ldap_next_attribute (ld, item, berctx), npth_protect ()) { struct berval **values; int idx; if (myopt->verbose > 1) log_info (_(" available attribute '%s'\n"), attr); set_timeout (myopt); /* I case we want only one attribute we do a case insensitive compare without the optional extension (i.e. ";binary"). Case insensitive is not really correct but the best we can do. */ if (want_attr) { char *cp1, *cp2; int cmpres; cp1 = strchr (want_attr, ';'); if (cp1) *cp1 = 0; cp2 = strchr (attr, ';'); if (cp2) *cp2 = 0; cmpres = ascii_strcasecmp (want_attr, attr); if (cp1) *cp1 = ';'; if (cp2) *cp2 = ';'; if (cmpres) { ldap_memfree (attr); continue; /* Not found: Try next attribute. */ } } npth_unprotect (); values = ldap_get_values_len (ld, item, attr); npth_protect (); if (!values) { if (myopt->verbose) log_info (_("attribute '%s' not found\n"), attr); ldap_memfree (attr); continue; } if (myopt->verbose) { log_info (_("found attribute '%s'\n"), attr); if (myopt->verbose > 1) for (idx=0; values[idx]; idx++) log_info (" length[%d]=%d\n", idx, (int)values[0]->bv_len); } if (myopt->multi) { /* Write attribute marker. */ unsigned char tmp[5]; size_t n = strlen (attr); tmp[0] = 'A'; tmp[1] = (n >> 24); tmp[2] = (n >> 16); tmp[3] = (n >> 8); tmp[4] = (n); if (es_fwrite (tmp, 5, 1, myopt->outstream) != 1 || es_fwrite (attr, n, 1, myopt->outstream) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } } for (idx=0; values[idx]; idx++) { if (myopt->multi) { /* Write value marker. */ unsigned char tmp[5]; size_t n = values[0]->bv_len; tmp[0] = 'V'; tmp[1] = (n >> 24); tmp[2] = (n >> 16); tmp[3] = (n >> 8); tmp[4] = (n); if (es_fwrite (tmp, 5, 1, myopt->outstream) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } } if (es_fwrite (values[0]->bv_val, values[0]->bv_len, 1, myopt->outstream) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } any = 1; if (!myopt->multi) break; /* Print only the first value. */ } ldap_value_free_len (values); ldap_memfree (attr); if (want_attr || !myopt->multi) break; /* We only want to return the first attribute. */ } ber_free (berctx, 0); } if (myopt->verbose > 1 && any) log_info ("result has been printed\n"); return any?0:-1; } /* Helper for the URL based LDAP query. */ static int fetch_ldap (my_opt_t myopt, const char *url, const LDAPURLDesc *ludp) { LDAP *ld; LDAPMessage *msg; int rc = 0; char *host, *dn, *filter, *attrs[2], *attr; int port; int ret; int usetls; host = myopt->host? myopt->host : ludp->lud_host; port = myopt->port? myopt->port : ludp->lud_port; dn = myopt->dn? myopt->dn : ludp->lud_dn; filter = myopt->filter? myopt->filter : ludp->lud_filter; attrs[0] = myopt->attr? myopt->attr : ludp->lud_attrs? ludp->lud_attrs[0]:NULL; attrs[1] = NULL; attr = attrs[0]; if (!port && myopt->force_tls) port = 636; else if (!port) port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389; if (myopt->verbose) { log_info (_("processing url '%s'\n"), url); if (myopt->force_tls) log_info ("forcing tls\n"); else log_info ("not forcing tls\n"); if (myopt->user) log_info (_(" user '%s'\n"), myopt->user); if (myopt->pass) log_info (_(" pass '%s'\n"), *myopt->pass?"*****":""); if (host) log_info (_(" host '%s'\n"), host); log_info (_(" port %d\n"), port); if (dn) log_info (_(" DN '%s'\n"), dn); if (filter) log_info (_(" filter '%s'\n"), filter); if (myopt->multi && !myopt->attr && ludp->lud_attrs) { int i; for (i=0; ludp->lud_attrs[i]; i++) log_info (_(" attr '%s'\n"), ludp->lud_attrs[i]); } else if (attr) log_info (_(" attr '%s'\n"), attr); } if (!host || !*host) { log_error (_("no host name in '%s'\n"), url); return -1; } if (!myopt->multi && !attr) { log_error (_("no attribute given for query '%s'\n"), url); return -1; } if (!myopt->multi && !myopt->attr && ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1]) log_info (_("WARNING: using first attribute only\n")); set_timeout (myopt); usetls = (myopt->force_tls || (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))); #if HAVE_W32_SYSTEM if (1) { npth_unprotect (); ld = ldap_sslinit (host, port, usetls); npth_protect (); if (!ld) { ret = LdapGetLastError (); log_error (_("LDAP init to '%s:%d' failed: %s\n"), host, port, ldap_err2string (ret)); return -1; } } #else /*!W32*/ if (usetls) { char *uri; uri = xtryasprintf ("ldaps://%s:%d", host, port); if (!uri) { log_error (_("error allocating memory: %s\n"), gpg_strerror (gpg_error_from_syserror ())); return -1; } npth_unprotect (); ret = ldap_initialize (&ld, uri); npth_protect (); if (ret) { log_error (_("LDAP init to '%s' failed: %s\n"), uri, ldap_err2string (ret)); xfree (uri); return -1; } else if (myopt->verbose) log_info (_("LDAP init to '%s' done\n"), uri); xfree (uri); } else { /* Keep the old way so to avoid regressions. Eventually we * should really consider the supplied scheme and use only * ldap_initialize. */ npth_unprotect (); ld = ldap_init (host, port); npth_protect (); if (!ld) { log_error (_("LDAP init to '%s:%d' failed: %s\n"), host, port, strerror (errno)); return -1; } } #endif /*!W32*/ npth_unprotect (); /* Fixme: Can we use MYOPT->user or is it shared with other theeads?. */ ret = ldap_simple_bind_s (ld, myopt->user, myopt->pass); npth_protect (); #ifdef LDAP_VERSION3 if (ret == LDAP_PROTOCOL_ERROR) { /* Protocol error could mean that the server only supports v3. */ int version = LDAP_VERSION3; if (myopt->verbose) log_info ("protocol error; retrying bind with v3 protocol\n"); npth_unprotect (); ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &version); ret = ldap_simple_bind_s (ld, myopt->user, myopt->pass); npth_protect (); } #endif if (ret) { log_error (_("binding to '%s:%d' failed: %s\n"), host, port, ldap_err2string (ret)); ldap_unbind (ld); return -1; } set_timeout (myopt); npth_unprotect (); rc = ldap_search_st (ld, dn, ludp->lud_scope, filter, myopt->multi && !myopt->attr && ludp->lud_attrs? ludp->lud_attrs:attrs, 0, &myopt->timeout, &msg); npth_protect (); if (rc == LDAP_SIZELIMIT_EXCEEDED && myopt->multi) { if (es_fwrite ("E\0\0\0\x09truncated", 14, 1, myopt->outstream) != 1) { log_error (_("error writing to stdout: %s\n"), strerror (errno)); return -1; } } else if (rc) { log_error (_("searching '%s' failed: %s\n"), url, ldap_err2string (rc)); if (rc != LDAP_NO_SUCH_OBJECT) { /* FIXME: Need deinit (ld)? */ /* Hmmm: Do we need to released MSG in case of an error? */ return -1; } } rc = print_ldap_entries (myopt, ld, msg, myopt->multi? NULL:attr); ldap_msgfree (msg); ldap_unbind (ld); return rc; } /* Main processing. Take the URL and run the LDAP query. The result is printed to stdout, errors are logged to the log stream. */ static int process_url (my_opt_t myopt, const char *url) { int rc; LDAPURLDesc *ludp = NULL; if (!ldap_is_ldap_url (url)) { log_error (_("'%s' is not an LDAP URL\n"), url); return -1; } if (ldap_url_parse (url, &ludp)) { log_error (_("'%s' is an invalid LDAP URL\n"), url); return -1; } rc = fetch_ldap (myopt, url, ludp); ldap_free_urldesc (ludp); return rc; } diff --git a/g10/call-agent.c b/g10/call-agent.c index e638b83eb..98d965cbe 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1,3027 +1,3027 @@ /* call-agent.c - Divert GPG operations to the agent. * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. * Copyright (C) 2013-2015 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 this program; if not, see . */ #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "gpg.h" #include #include "../common/util.h" #include "../common/membuf.h" #include "options.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/sysutils.h" #include "call-agent.h" #include "../common/status.h" #include "../common/shareddefs.h" #include "../common/host2net.h" #include "../common/ttyio.h" #define CONTROL_D ('D' - 'A' + 1) static assuan_context_t agent_ctx = NULL; static int did_early_card_test; struct confirm_parm_s { char *desc; char *ok; char *notok; }; struct default_inq_parm_s { ctrl_t ctrl; assuan_context_t ctx; struct { u32 *keyid; u32 *mainkeyid; int pubkey_algo; } keyinfo; struct confirm_parm_s *confirm; }; struct cipher_parm_s { struct default_inq_parm_s *dflt; assuan_context_t ctx; unsigned char *ciphertext; size_t ciphertextlen; }; struct writecert_parm_s { struct default_inq_parm_s *dflt; const unsigned char *certdata; size_t certdatalen; }; struct writekey_parm_s { struct default_inq_parm_s *dflt; const unsigned char *keydata; size_t keydatalen; }; struct genkey_parm_s { struct default_inq_parm_s *dflt; const char *keyparms; const char *passphrase; }; struct import_key_parm_s { struct default_inq_parm_s *dflt; const void *key; size_t keylen; }; struct cache_nonce_parm_s { char **cache_nonce_addr; char **passwd_nonce_addr; }; static gpg_error_t learn_status_cb (void *opaque, const char *line); /* If RC is not 0, write an appropriate status message. */ static void status_sc_op_failure (int rc) { switch (gpg_err_code (rc)) { case 0: break; case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: write_status_text (STATUS_SC_OP_FAILURE, "1"); break; case GPG_ERR_BAD_PIN: write_status_text (STATUS_SC_OP_FAILURE, "2"); break; default: write_status (STATUS_SC_OP_FAILURE); break; } } /* This is the default inquiry callback. It mainly handles the Pinentry notifications. */ static gpg_error_t default_inq_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct default_inq_parm_s *parm = opaque; const char *s; if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) { err = gpg_proxy_pinentry_notify (parm->ctrl, line); if (err) log_error (_("failed to proxy %s inquiry to client\n"), "PINENTRY_LAUNCHED"); /* We do not pass errors to avoid breaking other code. */ } else if ((has_leading_keyword (line, "PASSPHRASE") || has_leading_keyword (line, "NEW_PASSPHRASE")) && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) { if (have_static_passphrase ()) { s = get_static_passphrase (); err = assuan_send_data (parm->ctx, s, strlen (s)); } else { char *pw; char buf[32]; if (parm->keyinfo.keyid) emit_status_need_passphrase (parm->ctrl, parm->keyinfo.keyid, parm->keyinfo.mainkeyid, parm->keyinfo.pubkey_algo); snprintf (buf, sizeof (buf), "%u", 100); write_status_text (STATUS_INQUIRE_MAXLEN, buf); pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: ")); cpr_kill_prompt (); if (*pw == CONTROL_D && !pw[1]) err = gpg_error (GPG_ERR_CANCELED); else err = assuan_send_data (parm->ctx, pw, strlen (pw)); xfree (pw); } } else if ((s = has_leading_keyword (line, "CONFIRM")) && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK && parm->confirm) { int ask = atoi (s); int yes; if (ask) { yes = cpr_get_answer_is_yes (NULL, parm->confirm->desc); if (yes) err = assuan_send_data (parm->ctx, NULL, 0); else err = gpg_error (GPG_ERR_NOT_CONFIRMED); } else { tty_printf ("%s", parm->confirm->desc); err = assuan_send_data (parm->ctx, NULL, 0); } } else log_debug ("ignoring gpg-agent inquiry '%s'\n", line); return err; } /* Print a warning if the server's version number is less than our version number. Returns an error code on a connection problem. */ static gpg_error_t warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode) { gpg_error_t err; char *serverversion; - const char *myversion = strusage (13); + const char *myversion = gpgrt_strusage (13); err = get_assuan_server_version (ctx, mode, &serverversion); if (err) log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED? GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR, _("error getting version from '%s': %s\n"), servername, gpg_strerror (err)); else if (compare_version_strings (serverversion, myversion) < 0) { char *warn; warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), servername, serverversion, myversion); if (!warn) err = gpg_error_from_syserror (); else { log_info (_("WARNING: %s\n"), warn); if (!opt.quiet) { log_info (_("Note: Outdated servers may lack important" " security fixes.\n")); log_info (_("Note: Use the command \"%s\" to restart them.\n"), "gpgconf --kill all"); } write_status_strings (STATUS_WARNING, "server_version_mismatch 0", " ", warn, NULL); xfree (warn); } } xfree (serverversion); return err; } #define FLAG_FOR_CARD_SUPPRESS_ERRORS 2 /* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting */ static int start_agent (ctrl_t ctrl, int flag_for_card) { int rc; (void)ctrl; /* Not yet used. */ /* Fixme: We need a context for each thread or serialize the access to the agent. */ if (agent_ctx) rc = 0; else { rc = start_new_gpg_agent (&agent_ctx, GPG_ERR_SOURCE_DEFAULT, opt.agent_program, opt.lc_ctype, opt.lc_messages, opt.session_env, opt.autostart, opt.verbose, DBG_IPC, NULL, NULL); if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT) { static int shown; if (!shown) { shown = 1; log_info (_("no gpg-agent running in this session\n")); } } else if (!rc && !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0))) { /* Tell the agent that we support Pinentry notifications. No error checking so that it will work also with older agents. */ assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); /* Tell the agent about what version we are aware. This is here used to indirectly enable GPG_ERR_FULLY_CANCELED. */ assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0", NULL, NULL, NULL, NULL, NULL, NULL); /* Pass on the pinentry mode. */ if (opt.pinentry_mode) { char *tmp = xasprintf ("OPTION pinentry-mode=%s", str_pinentry_mode (opt.pinentry_mode)); rc = assuan_transact (agent_ctx, tmp, NULL, NULL, NULL, NULL, NULL, NULL); xfree (tmp); if (rc) { log_error ("setting pinentry mode '%s' failed: %s\n", str_pinentry_mode (opt.pinentry_mode), gpg_strerror (rc)); write_status_error ("set_pinentry_mode", rc); } } /* Pass on the request origin. */ if (opt.request_origin) { char *tmp = xasprintf ("OPTION pretend-request-origin=%s", str_request_origin (opt.request_origin)); rc = assuan_transact (agent_ctx, tmp, NULL, NULL, NULL, NULL, NULL, NULL); xfree (tmp); if (rc) { log_error ("setting request origin '%s' failed: %s\n", str_request_origin (opt.request_origin), gpg_strerror (rc)); write_status_error ("set_request_origin", rc); } } /* In DE_VS mode under Windows we require that the JENT RNG * is active. */ #ifdef HAVE_W32_SYSTEM if (!rc && opt.compliance == CO_DE_VS) { if (assuan_transact (agent_ctx, "GETINFO jent_active", NULL, NULL, NULL, NULL, NULL, NULL)) { rc = gpg_error (GPG_ERR_FORBIDDEN); log_error (_("%s is not compliant with %s mode\n"), GPG_AGENT_NAME, gnupg_compliance_option_string (opt.compliance)); write_status_error ("random-compliance", rc); } } #endif /*HAVE_W32_SYSTEM*/ } } if (!rc && flag_for_card && !did_early_card_test) { /* Request the serial number of the card for an early test. */ struct agent_card_info_s info; memset (&info, 0, sizeof info); if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); if (!rc) rc = assuan_transact (agent_ctx, opt.flags.use_only_openpgp_card? "SCD SERIALNO openpgp" : "SCD SERIALNO", NULL, NULL, NULL, NULL, learn_status_cb, &info); if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) { switch (gpg_err_code (rc)) { case GPG_ERR_NOT_SUPPORTED: case GPG_ERR_NO_SCDAEMON: write_status_text (STATUS_CARDCTRL, "6"); break; case GPG_ERR_OBJ_TERM_STATE: write_status_text (STATUS_CARDCTRL, "7"); break; default: write_status_text (STATUS_CARDCTRL, "4"); log_info ("selecting card failed: %s\n", gpg_strerror (rc)); break; } } if (!rc && is_status_enabled () && info.serialno) { char *buf; buf = xasprintf ("3 %s", info.serialno); write_status_text (STATUS_CARDCTRL, buf); xfree (buf); } agent_release_card_info (&info); if (!rc) did_early_card_test = 1; } return rc; } /* Return a new malloced string by unescaping the string S. Escaping is percent escaping and '+'/space mapping. A binary nul will silently be replaced by a 0xFF. Function returns NULL to indicate an out of memory status. */ static char * unescape_status_string (const unsigned char *s) { return percent_plus_unescape (s, 0xff); } /* Take a 20 or 32 byte hexencoded string and put it into the provided * FPRLEN byte long buffer FPR in binary format. Returns the actual * used length of the FPR buffer or 0 on error. */ static unsigned int unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if ((*s && *s != ' ') || !(n == 40 || n == 64)) return 0; /* no fingerprint (invalid or wrong length). */ for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++) fpr[n] = xtoi_2 (s); return (n == 20 || n == 32)? n : 0; } /* Take the serial number from LINE and return it verbatim in a newly allocated string. We make sure that only hex characters are returned. */ static char * store_serialno (const char *line) { const char *s; char *p; for (s=line; hexdigitp (s); s++) ; p = xtrymalloc (s + 1 - line); if (p) { memcpy (p, line, s-line); p[s-line] = 0; } return p; } /* This is a dummy data line callback. */ static gpg_error_t dummy_data_cb (void *opaque, const void *buffer, size_t length) { (void)opaque; (void)buffer; (void)length; return 0; } /* A simple callback used to return the serialnumber of a card. */ static gpg_error_t get_serialno_cb (void *opaque, const char *line) { char **serialno = opaque; const char *keyword = line; const char *s; int keywordlen, n; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { if (*serialno) return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1)|| !(spacep (s) || !*s) ) return gpg_error (GPG_ERR_ASS_PARAMETER); *serialno = xtrymalloc (n+1); if (!*serialno) return out_of_core (); memcpy (*serialno, line, n); (*serialno)[n] = 0; } return 0; } /* Release the card info structure INFO. */ void agent_release_card_info (struct agent_card_info_s *info) { int i; if (!info) return; xfree (info->reader); info->reader = NULL; xfree (info->serialno); info->serialno = NULL; xfree (info->apptype); info->apptype = NULL; xfree (info->disp_name); info->disp_name = NULL; xfree (info->disp_lang); info->disp_lang = NULL; xfree (info->pubkey_url); info->pubkey_url = NULL; xfree (info->login_data); info->login_data = NULL; info->cafpr1len = info->cafpr2len = info->cafpr3len = 0; info->fpr1len = info->fpr2len = info->fpr3len = 0; for (i=0; i < DIM(info->private_do); i++) { xfree (info->private_do[i]); info->private_do[i] = NULL; } } static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct agent_card_info_s *parm = opaque; const char *keyword = line; int keywordlen; int i; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen)) { xfree (parm->reader); parm->reader = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { xfree (parm->serialno); parm->serialno = store_serialno (line); parm->is_v2 = (strlen (parm->serialno) >= 16 && xtoi_2 (parm->serialno+12) >= 2 ); } else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen)) { xfree (parm->apptype); parm->apptype = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) { xfree (parm->disp_name); parm->disp_name = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) { xfree (parm->disp_lang); parm->disp_lang = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; } else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen)) { xfree (parm->pubkey_url); parm->pubkey_url = unescape_status_string (line); } else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) { xfree (parm->login_data); parm->login_data = unescape_status_string (line); } else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen)) { parm->sig_counter = strtoul (line, NULL, 0); } else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen)) { char *p, *buf; buf = p = unescape_status_string (line); if (buf) { while (spacep (p)) p++; parm->chv1_cached = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; for (i=0; *p && i < 3; i++) { parm->chvmaxlen[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } for (i=0; *p && i < 3; i++) { parm->chvretry[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } xfree (buf); } } else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen)) { char *p, *p2, *buf; int abool; buf = p = unescape_status_string (line); if (buf) { for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) { p2 = strchr (p, '='); if (p2) { *p2++ = 0; abool = (*p2 == '1'); if (!strcmp (p, "ki")) parm->extcap.ki = abool; else if (!strcmp (p, "aac")) parm->extcap.aac = abool; else if (!strcmp (p, "bt")) parm->extcap.bt = abool; else if (!strcmp (p, "kdf")) parm->extcap.kdf = abool; else if (!strcmp (p, "si")) parm->status_indicator = strtoul (p2, NULL, 10); } } xfree (buf); } } else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1); else if (no == 2) parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2); else if (no == 3) parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3); } else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen)) { int no = atoi (line); while (* line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1time = strtoul (line, NULL, 10); else if (no == 2) parm->fpr2time = strtoul (line, NULL, 10); else if (no == 3) parm->fpr3time = strtoul (line, NULL, 10); } else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { const char *hexgrp = line; int no; while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (strncmp (line, "OPENPGP.", 8)) ; else if ((no = atoi (line+8)) == 1) unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1); else if (no == 2) unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2); else if (no == 3) unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3); } else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,sizeof parm->cafpr1); else if (no == 2) parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,sizeof parm->cafpr2); else if (no == 3) parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,sizeof parm->cafpr3); } else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen)) { int keyno = 0; int algo = PUBKEY_ALGO_RSA; int n = 0; sscanf (line, "%d %d %n", &keyno, &algo, &n); keyno--; if (keyno < 0 || keyno >= DIM (parm->key_attr)) return 0; parm->key_attr[keyno].algo = algo; if (algo == PUBKEY_ALGO_RSA) parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10); else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA) parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n, NULL, NULL); } else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11) && strchr("1234", keyword[11])) { int no = keyword[11] - '1'; log_assert (no >= 0 && no <= 3); xfree (parm->private_do[no]); parm->private_do[no] = unescape_status_string (line); } else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3)) { unsigned char *data = unescape_status_string (line); if (data[2] != 0x03) parm->kdf_do_enabled = 0; else if (data[22] != 0x85) parm->kdf_do_enabled = 1; else parm->kdf_do_enabled = 2; xfree (data); } else if (keywordlen == 5 && !memcmp (keyword, "UIF-", 4) && strchr("123", keyword[4])) { unsigned char *data; int no = keyword[4] - '1'; log_assert (no >= 0 && no <= 2); data = unescape_status_string (line); parm->uif[no] = (data[0] != 0xff); xfree (data); } return 0; } /* Call the scdaemon to learn about a smartcard. Note that in * contradiction to the function's name, gpg-agent's LEARN command is * used and not the low-level "SCD LEARN". * Used by: * card-util.c * keyedit_menu * card_store_key_with_backup (Woth force to remove secret key data) */ int agent_scd_learn (struct agent_card_info_s *info, int force) { int rc; struct default_inq_parm_s parm; struct agent_card_info_s dummyinfo; if (!info) info = &dummyinfo; memset (info, 0, sizeof *info); memset (&parm, 0, sizeof parm); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, force ? "LEARN --sendinfo --force" : "LEARN --sendinfo", dummy_data_cb, NULL, default_inq_cb, &parm, learn_status_cb, info); /* Also try to get the key attributes. */ if (!rc) agent_scd_getattr ("KEY-ATTR", info); if (info == &dummyinfo) agent_release_card_info (info); return rc; } struct keypairinfo_cb_parm_s { keypair_info_t kpinfo; keypair_info_t *kpinfo_tail; }; /* Callback for the agent_scd_keypairinfo function. */ static gpg_error_t scd_keypairinfo_status_cb (void *opaque, const char *line) { struct keypairinfo_cb_parm_s *parm = opaque; gpg_error_t err = 0; const char *keyword = line; int keywordlen; char *line_buffer = NULL; keypair_info_t kpi = NULL; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { /* The format of such a line is: * KEYPAIRINFO [usage] [keytime] */ char *fields[4]; int nfields; const char *hexgrp, *keyref, *usage; time_t atime; u32 keytime; line_buffer = xtrystrdup (line); if (!line_buffer) { err = gpg_error_from_syserror (); goto leave; } if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2) goto leave; /* not enough args - invalid status line - ignore */ hexgrp = fields[0]; keyref = fields[1]; if (nfields > 2) usage = fields[2]; else usage = ""; if (nfields > 3) { atime = parse_timestamp (fields[3], NULL); if (atime == (time_t)(-1)) atime = 0; keytime = atime; } else keytime = 0; kpi = xtrycalloc (1, sizeof *kpi); if (!kpi) { err = gpg_error_from_syserror (); goto leave; } if (*hexgrp == 'X' && !hexgrp[1]) *kpi->keygrip = 0; /* No hexgrip. */ else if (strlen (hexgrp) == 2*KEYGRIP_LEN) mem2str (kpi->keygrip, hexgrp, sizeof kpi->keygrip); else { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } if (!*keyref) { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } kpi->idstr = xtrystrdup (keyref); if (!kpi->idstr) { err = gpg_error_from_syserror (); goto leave; } /* Parse and set the usage. */ for (; *usage; usage++) { switch (*usage) { case 's': kpi->usage |= GCRY_PK_USAGE_SIGN; break; case 'c': kpi->usage |= GCRY_PK_USAGE_CERT; break; case 'a': kpi->usage |= GCRY_PK_USAGE_AUTH; break; case 'e': kpi->usage |= GCRY_PK_USAGE_ENCR; break; } } kpi->keytime = keytime; /* Append to the list. */ *parm->kpinfo_tail = kpi; parm->kpinfo_tail = &kpi->next; kpi = NULL; } leave: free_keypair_info (kpi); xfree (line_buffer); return err; } /* Read the keypairinfo lines of the current card directly from * scdaemon. The list is returned as a string made up of the keygrip, * a space and the keyref. The flags of the string carry the usage * bits. If KEYREF is not NULL, only a single string is returned * which matches the given keyref. */ gpg_error_t agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref, keypair_info_t *r_list) { gpg_error_t err; struct keypairinfo_cb_parm_s parm; struct default_inq_parm_s inq_parm; char line[ASSUAN_LINELENGTH]; *r_list = NULL; err= start_agent (ctrl, 1); if (err) return err; memset (&inq_parm, 0, sizeof inq_parm); inq_parm.ctx = agent_ctx; parm.kpinfo = NULL; parm.kpinfo_tail = &parm.kpinfo; if (keyref) snprintf (line, DIM(line), "SCD READKEY --info-only %s", keyref); else snprintf (line, DIM(line), "SCD LEARN --keypairinfo"); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &inq_parm, scd_keypairinfo_status_cb, &parm); if (!err && !parm.kpinfo) err = gpg_error (GPG_ERR_NO_DATA); if (err) free_keypair_info (parm.kpinfo); else *r_list = parm.kpinfo; return err; } /* Send an APDU to the current card. On success the status word is * stored at R_SW. With HEXAPDU being NULL only a RESET command is * send to scd. With HEXAPDU being the string "undefined" the command * "SERIALNO undefined" is send to scd. * Used by: * card-util.c */ gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw) { gpg_error_t err; /* Start the agent but not with the card flag so that we do not autoselect the openpgp application. */ err = start_agent (NULL, 0); if (err) return err; if (!hexapdu) { err = assuan_transact (agent_ctx, "SCD RESET", NULL, NULL, NULL, NULL, NULL, NULL); } else if (!strcmp (hexapdu, "undefined")) { err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", NULL, NULL, NULL, NULL, NULL, NULL); } else { char line[ASSUAN_LINELENGTH]; membuf_t mb; unsigned char *data; size_t datalen; init_membuf (&mb, 256); snprintf (line, DIM(line), "SCD APDU %s", hexapdu); err = assuan_transact (agent_ctx, line, put_membuf_cb, &mb, NULL, NULL, NULL, NULL); if (!err) { data = get_membuf (&mb, &datalen); if (!data) err = gpg_error_from_syserror (); else if (datalen < 2) /* Ooops */ err = gpg_error (GPG_ERR_CARD); else { *r_sw = buf16_to_uint (data+datalen-2); } xfree (data); } } return err; } /* Used by: * card_store_subkey * card_store_key_with_backup */ int agent_keytocard (const char *hexgrip, int keyno, int force, const char *serialno, const char *timestamp) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s", force?"--force ": "", hexgrip, serialno, keyno, timestamp); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); if (rc) return rc; return rc; } /* Object used with the agent_scd_getattr_one. */ struct getattr_one_parm_s { const char *keyword; /* Keyword to look for. */ char *data; /* Malloced and unescaped data. */ gpg_error_t err; /* Error code or 0 on success. */ }; /* Callback for agent_scd_getattr_one. */ static gpg_error_t getattr_one_status_cb (void *opaque, const char *line) { struct getattr_one_parm_s *parm = opaque; const char *s; if (parm->data) return 0; /* We want only the first occurrence. */ if ((s=has_leading_keyword (line, parm->keyword))) { parm->data = percent_plus_unescape (s, 0xff); if (!parm->data) parm->err = gpg_error_from_syserror (); } return 0; } /* Simplified version of agent_scd_getattr. This function returns * only the first occurrence of the attribute NAME and stores it at * R_VALUE. A nul in the result is silennly replaced by 0xff. On * error NULL is stored at R_VALUE. */ gpg_error_t agent_scd_getattr_one (const char *name, char **r_value) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s inqparm; struct getattr_one_parm_s parm; *r_value = NULL; if (!*name) return gpg_error (GPG_ERR_INV_VALUE); memset (&inqparm, 0, sizeof inqparm); inqparm.ctx = agent_ctx; memset (&parm, 0, sizeof parm); parm.keyword = name; /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "SCD GETATTR "), name); err = start_agent (NULL, 1); if (err) return err; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &inqparm, getattr_one_status_cb, &parm); if (!err && parm.err) err = parm.err; else if (!err && !parm.data) err = gpg_error (GPG_ERR_NO_DATA); if (!err) *r_value = parm.data; else xfree (parm.data); return err; } /* Call the agent to retrieve a data object. This function returns * the data in the same structure as used by the learn command. It is * allowed to update such a structure using this command. * * Used by: * build_sk_list * enum_secret_keys * get_signature_count * card-util.c * generate_keypair (KEY-ATTR) * card_store_key_with_backup (SERIALNO) * generate_card_subkeypair (KEY-ATTR) */ int agent_scd_getattr (const char *name, struct agent_card_info_s *info) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); if (!*name) return gpg_error (GPG_ERR_INV_VALUE); /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "SCD GETATTR "), name); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, learn_status_cb, info); return rc; } /* Send an setattr command to the SCdaemon. * Used by: * card-util.c */ gpg_error_t agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen) { gpg_error_t err; const unsigned char *value = value_arg; char line[ASSUAN_LINELENGTH]; char *p; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); if (!*name || !valuelen) return gpg_error (GPG_ERR_INV_VALUE); /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); p = stpcpy (stpcpy (line, "SCD SETATTR "), name); *p++ = ' '; for (; valuelen; value++, valuelen--) { if (p >= line + DIM(line)-5 ) return gpg_error (GPG_ERR_TOO_LARGE); if (*value < ' ' || *value == '+' || *value == '%') { sprintf (p, "%%%02X", *value); p += 3; } else if (*value == ' ') *p++ = '+'; else *p++ = *value; } *p = 0; err = start_agent (NULL, 1); if (!err) { parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); } status_sc_op_failure (err); return err; } /* Handle a CERTDATA inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the END command. */ static gpg_error_t inq_writecert_parms (void *opaque, const char *line) { int rc; struct writecert_parm_s *parm = opaque; if (has_leading_keyword (line, "CERTDATA")) { rc = assuan_send_data (parm->dflt->ctx, parm->certdata, parm->certdatalen); } else rc = default_inq_cb (parm->dflt, line); return rc; } /* Send a WRITECERT command to the SCdaemon. * Used by: * card-util.c */ int agent_scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen) { int rc; char line[ASSUAN_LINELENGTH]; struct writecert_parm_s parms; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; memset (&parms, 0, sizeof parms); snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr); dfltparm.ctx = agent_ctx; parms.dflt = &dfltparm; parms.certdata = certdata; parms.certdatalen = certdatalen; rc = assuan_transact (agent_ctx, line, NULL, NULL, inq_writecert_parms, &parms, NULL, NULL); return rc; } /* Status callback for the SCD GENKEY command. */ static gpg_error_t scd_genkey_cb (void *opaque, const char *line) { u32 *createtime = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) { *createtime = (u32)strtoul (line, NULL, 10); } else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) { write_status_text (STATUS_PROGRESS, line); } return 0; } /* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0, * the value will be passed to SCDAEMON with --timestamp option so that * the key is created with this. Otherwise, timestamp was generated by * SCDEAMON. On success, creation time is stored back to * CREATETIME. * Used by: * gen_card_key */ int agent_scd_genkey (int keyno, int force, u32 *createtime) { int rc; char line[ASSUAN_LINELENGTH]; gnupg_isotime_t tbuf; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; if (*createtime) epoch2isotime (tbuf, *createtime); else *tbuf = 0; snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d", *tbuf? "--timestamp=":"", tbuf, force? "--force":"", keyno); dfltparm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, scd_genkey_cb, createtime); status_sc_op_failure (rc); return rc; } /* Return the serial number of the card or an appropriate error. The * serial number is returned as a hexstring. With DEMAND the active * card is switched to the card with that serialno. * Used by: * card-util.c * build_sk_list * enum_secret_keys */ int agent_scd_serialno (char **r_serialno, const char *demand) { int err; char *serialno = NULL; char line[ASSUAN_LINELENGTH]; err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS)); if (err) return err; if (!demand) strcpy (line, "SCD SERIALNO"); else snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (err) { xfree (serialno); return err; } *r_serialno = serialno; return 0; } /* Send a READCERT command to the SCdaemon. * Used by: * card-util.c */ int agent_scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); *r_buf = NULL; rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; init_membuf (&data, 2048); snprintf (line, DIM(line), "SCD READCERT %s", certidstr); rc = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return rc; } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return gpg_error (GPG_ERR_ENOMEM); return 0; } /* Callback for the agent_scd_readkey function. */ static gpg_error_t readkey_status_cb (void *opaque, const char *line) { u32 *keytimep = opaque; gpg_error_t err = 0; const char *args; char *line_buffer = NULL; /* FIXME: Get that info from the KEYPAIRINFO line. */ if ((args = has_leading_keyword (line, "KEYPAIRINFO")) && !*keytimep) { /* The format of such a line is: * KEYPAIRINFO [usage] [keytime] * * Note that we use only the first valid KEYPAIRINFO line. More * lines are possible if a second card carries the same key. */ char *fields[4]; int nfields; time_t atime; line_buffer = xtrystrdup (line); if (!line_buffer) { err = gpg_error_from_syserror (); goto leave; } if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 4) goto leave; /* not enough args - ignore */ if (nfields > 3) { atime = parse_timestamp (fields[3], NULL); if (atime == (time_t)(-1)) atime = 0; *keytimep = atime; } else *keytimep = 0; } leave: xfree (line_buffer); return err; } /* This is a variant of agent_readkey which sends a READKEY command * directly Scdaemon. On success a new s-expression is stored at * R_RESULT. If R_KEYTIME is not NULL the key cresation time of an * OpenPGP card is stored there - if that is not known 0 is stored. * In the latter case it is allowed to pass NULL for R_RESULT. */ gpg_error_t agent_scd_readkey (ctrl_t ctrl, const char *keyrefstr, gcry_sexp_t *r_result, u32 *r_keytime) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; unsigned char *buf; size_t len, buflen; struct default_inq_parm_s dfltparm; u32 keytime; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctx = agent_ctx; if (r_result) *r_result = NULL; if (r_keytime) *r_keytime = 0; err = start_agent (ctrl, 1); if (err) return err; init_membuf (&data, 1024); snprintf (line, DIM(line), "SCD READKEY --info%s -- %s", r_result? "":"-only", keyrefstr); keytime = 0; err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, readkey_status_cb, &keytime); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &buflen); if (!buf) return gpg_error_from_syserror (); if (r_result) err = gcry_sexp_new (r_result, buf, buflen, 0); else err = 0; xfree (buf); if (!err && r_keytime) *r_keytime = keytime; return err; } struct card_cardlist_parm_s { int error; strlist_t list; }; /* Callback function for agent_card_cardlist. */ static gpg_error_t card_cardlist_cb (void *opaque, const char *line) { struct card_cardlist_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { const char *s; int n; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1) || *s) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); else add_to_strlist (&parm->list, line); } return 0; } /* Return a list of currently available cards. * Used by: * card-util.c * skclist.c */ int agent_scd_cardlist (strlist_t *result) { int err; char line[ASSUAN_LINELENGTH]; struct card_cardlist_parm_s parm; memset (&parm, 0, sizeof parm); *result = NULL; err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS); if (err) return err; strcpy (line, "SCD GETINFO card_list"); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, card_cardlist_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *result = parm.list; else free_strlist (parm.list); return 0; } struct card_keyinfo_parm_s { int error; keypair_info_t list; }; /* Callback function for agent_card_keylist. */ static gpg_error_t card_keyinfo_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct card_keyinfo_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen)) { const char *s; int n; keypair_info_t keyinfo; keypair_info_t *l_p = &parm->list; while ((*l_p)) l_p = &(*l_p)->next; keyinfo = xtrycalloc (1, sizeof *keyinfo); if (!keyinfo) { alloc_error: if (!parm->error) parm->error = gpg_error_from_syserror (); return 0; } for (n=0,s=line; hexdigitp (s); s++, n++) ; if (n != 40) { parm_error: if (!parm->error) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); return 0; } memcpy (keyinfo->keygrip, line, 40); keyinfo->keygrip[40] = 0; line = s; if (!*line) goto parm_error; while (spacep (line)) line++; if (*line++ != 'T') goto parm_error; if (!*line) goto parm_error; while (spacep (line)) line++; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n) goto parm_error; keyinfo->serialno = xtrymalloc (n+1); if (!keyinfo->serialno) goto alloc_error; memcpy (keyinfo->serialno, line, n); keyinfo->serialno[n] = 0; line = s; if (!*line) goto parm_error; while (spacep (line)) line++; if (!*line) goto parm_error; keyinfo->idstr = xtrystrdup (line); if (!keyinfo->idstr) goto alloc_error; *l_p = keyinfo; } return err; } /* Free a keypair info list. */ void free_keypair_info (keypair_info_t l) { keypair_info_t l_next; for (; l; l = l_next) { l_next = l->next; xfree (l->serialno); xfree (l->idstr); xfree (l); } } /* Call the scdaemon to check if a key of KEYGRIP is available, or retrieve list of available keys on cards. With CAP, we can limit keys with specified capability. On success, the allocated structure is stored at RESULT. On error, an error code is returned and NULL is stored at RESULT. */ gpg_error_t agent_scd_keyinfo (const char *keygrip, int cap, keypair_info_t *result) { int err; struct card_keyinfo_parm_s parm; char line[ASSUAN_LINELENGTH]; char *list_option; *result = NULL; switch (cap) { case 0: list_option = "--list"; break; case GCRY_PK_USAGE_SIGN: list_option = "--list=sign"; break; case GCRY_PK_USAGE_ENCR: list_option = "--list=encr"; break; case GCRY_PK_USAGE_AUTH: list_option = "--list=auth"; break; default: return gpg_error (GPG_ERR_INV_VALUE); } memset (&parm, 0, sizeof parm); snprintf (line, sizeof line, "SCD KEYINFO %s", keygrip ? keygrip : list_option); err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS); if (err) return err; err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, card_keyinfo_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *result = parm.list; else free_keypair_info (parm.list); return err; } /* Change the PIN of an OpenPGP card or reset the retry counter. * CHVNO 1: Change the PIN * 2: For v1 cards: Same as 1. * For v2 cards: Reset the PIN using the Reset Code. * 3: Change the admin PIN * 101: Set a new PIN and reset the retry counter * 102: For v1 cars: Same as 101. * For v2 cards: Set a new Reset Code. * SERIALNO is not used. * Used by: * card-util.c */ int agent_scd_change_pin (int chvno, const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; const char *reset = ""; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); (void)serialno; if (chvno >= 100) reset = "--reset"; chvno %= 100; rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); status_sc_op_failure (rc); return rc; } /* Perform a CHECKPIN operation. SERIALNO should be the serial * number of the card - optionally followed by the fingerprint; * however the fingerprint is ignored here. * Used by: * card-util.c */ int agent_scd_checkpin (const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); status_sc_op_failure (rc); return rc; } /* Note: All strings shall be UTF-8. On success the caller needs to free the string stored at R_PASSPHRASE. On error NULL will be stored at R_PASSPHRASE and an appropriate fpf error code returned. */ gpg_error_t agent_get_passphrase (const char *cache_id, const char *err_msg, const char *prompt, const char *desc_msg, int repeat, int check, char **r_passphrase) { int rc; char line[ASSUAN_LINELENGTH]; char *arg1 = NULL; char *arg2 = NULL; char *arg3 = NULL; char *arg4 = NULL; membuf_t data; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); *r_passphrase = NULL; rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; /* Check that the gpg-agent understands the repeat option. */ if (assuan_transact (agent_ctx, "GETINFO cmd_has_option GET_PASSPHRASE repeat", NULL, NULL, NULL, NULL, NULL, NULL)) return gpg_error (GPG_ERR_NOT_SUPPORTED); if (cache_id && *cache_id) if (!(arg1 = percent_plus_escape (cache_id))) goto no_mem; if (err_msg && *err_msg) if (!(arg2 = percent_plus_escape (err_msg))) goto no_mem; if (prompt && *prompt) if (!(arg3 = percent_plus_escape (prompt))) goto no_mem; if (desc_msg && *desc_msg) if (!(arg4 = percent_plus_escape (desc_msg))) goto no_mem; snprintf (line, DIM(line), "GET_PASSPHRASE --data --repeat=%d%s -- %s %s %s %s", repeat, check? " --check --qualitybar":"", arg1? arg1:"X", arg2? arg2:"X", arg3? arg3:"X", arg4? arg4:"X"); xfree (arg1); xfree (arg2); xfree (arg3); xfree (arg4); init_membuf_secure (&data, 64); rc = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (rc) xfree (get_membuf (&data, NULL)); else { put_membuf (&data, "", 1); *r_passphrase = get_membuf (&data, NULL); if (!*r_passphrase) rc = gpg_error_from_syserror (); } return rc; no_mem: rc = gpg_error_from_syserror (); xfree (arg1); xfree (arg2); xfree (arg3); xfree (arg4); return rc; } gpg_error_t agent_clear_passphrase (const char *cache_id) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); if (!cache_id || !*cache_id) return 0; rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id); return assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); } /* Ask the agent to pop up a confirmation dialog with the text DESC and an okay and cancel button. */ gpg_error_t gpg_agent_get_confirmation (const char *desc) { int rc; char *tmp; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; tmp = percent_plus_escape (desc); if (!tmp) return gpg_error_from_syserror (); snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp); xfree (tmp); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return rc; } /* Return the S2K iteration count as computed by gpg-agent. On error * print a warning and return a default value. */ unsigned long agent_get_s2k_count (void) { gpg_error_t err; membuf_t data; char *buf; unsigned long count = 0; err = start_agent (NULL, 0); if (err) goto leave; init_membuf (&data, 32); err = assuan_transact (agent_ctx, "GETINFO s2k_count", put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (err) xfree (get_membuf (&data, NULL)); else { put_membuf (&data, "", 1); buf = get_membuf (&data, NULL); if (!buf) err = gpg_error_from_syserror (); else { count = strtoul (buf, NULL, 10); xfree (buf); } } leave: if (err || count < 65536) { /* Don't print an error if an older agent is used. */ if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); /* Default to 65536 which was used up to 2.0.13. */ count = 65536; } return count; } struct keyinfo_data_parm_s { char *serialno; int is_smartcard; int passphrase_cached; int cleartext; int card_available; }; static gpg_error_t keyinfo_status_cb (void *opaque, const char *line) { struct keyinfo_data_parm_s *data = opaque; char *s; if ((s = has_leading_keyword (line, "KEYINFO")) && data) { /* Parse the arguments: * 0 1 2 3 4 5 * * * 6 7 8 * */ char *fields[9]; if (split_fields (s, fields, DIM (fields)) == 9) { data->is_smartcard = (fields[1][0] == 'T'); if (data->is_smartcard && !data->serialno && strcmp (fields[2], "-")) data->serialno = xtrystrdup (fields[2]); /* '1' for cached */ data->passphrase_cached = (fields[4][0] == '1'); /* 'P' for protected, 'C' for clear */ data->cleartext = (fields[5][0] == 'C'); /* 'A' for card is available */ data->card_available = (fields[8][0] == 'A'); } } return 0; } /* Ask the agent whether a secret key for the given public key is available. Returns 0 if not available. Bigger value is preferred. */ int agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *hexgrip; struct keyinfo_data_parm_s keyinfo; memset (&keyinfo, 0, sizeof keyinfo); err = start_agent (ctrl, 0); if (err) return err; err = hexkeygrip_from_pk (pk, &hexgrip); if (err) return err; snprintf (line, sizeof line, "KEYINFO %s", hexgrip); xfree (hexgrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, keyinfo_status_cb, &keyinfo); xfree (keyinfo.serialno); if (err) return 0; if (keyinfo.card_available) return 4; if (keyinfo.passphrase_cached) return 3; if (keyinfo.is_smartcard) return 2; return 1; } /* Ask the agent whether a secret key is available for any of the keys (primary or sub) in KEYBLOCK. Returns 0 if available. */ gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *p; kbnode_t kbctx, node; int nkeys; unsigned char grip[KEYGRIP_LEN]; err = start_agent (ctrl, 0); if (err) return err; err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was found in KEYBLOCK. */ p = stpcpy (line, "HAVEKEY"); for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); ) if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_KEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) { if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2)) { err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err != gpg_err_code (GPG_ERR_NO_SECKEY)) break; /* Seckey available or unexpected error - ready. */ p = stpcpy (line, "HAVEKEY"); nkeys = 0; } err = keygrip_from_pk (node->pkt->pkt.public_key, grip); if (err) return err; *p++ = ' '; bin2hex (grip, 20, p); p += 40; nkeys++; } if (!err && nkeys) err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return err; } /* Return the serial number for a secret key. If the returned serial number is NULL, the key is not stored on a smartcard. Caller needs to free R_SERIALNO. if r_cleartext is not NULL, the referenced int will be set to 1 if the agent's copy of the key is stored in the clear, or 0 otherwise */ gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno, int *r_cleartext) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct keyinfo_data_parm_s keyinfo; memset (&keyinfo, 0,sizeof keyinfo); *r_serialno = NULL; err = start_agent (ctrl, 0); if (err) return err; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, keyinfo_status_cb, &keyinfo); if (!err && keyinfo.serialno) { /* Sanity check for bad characters. */ if (strpbrk (keyinfo.serialno, ":\n\r")) err = GPG_ERR_INV_VALUE; } if (err) xfree (keyinfo.serialno); else { *r_serialno = keyinfo.serialno; if (r_cleartext) *r_cleartext = keyinfo.cleartext; } return err; } /* Status callback for agent_import_key, agent_export_key and agent_genkey. */ static gpg_error_t cache_nonce_status_cb (void *opaque, const char *line) { struct cache_nonce_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "CACHE_NONCE"))) { if (parm->cache_nonce_addr) { xfree (*parm->cache_nonce_addr); *parm->cache_nonce_addr = xtrystrdup (s); } } else if ((s = has_leading_keyword (line, "PASSWD_NONCE"))) { if (parm->passwd_nonce_addr) { xfree (*parm->passwd_nonce_addr); *parm->passwd_nonce_addr = xtrystrdup (s); } } else if ((s = has_leading_keyword (line, "PROGRESS"))) { if (opt.enable_progress_filter) write_status_text (STATUS_PROGRESS, s); } return 0; } /* Handle a KEYPARMS inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the end */ static gpg_error_t inq_genkey_parms (void *opaque, const char *line) { struct genkey_parm_s *parm = opaque; gpg_error_t err; if (has_leading_keyword (line, "KEYPARAM")) { err = assuan_send_data (parm->dflt->ctx, parm->keyparms, strlen (parm->keyparms)); } else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase) { err = assuan_send_data (parm->dflt->ctx, parm->passphrase, strlen (parm->passphrase)); } else err = default_inq_cb (parm->dflt, line); return err; } /* Call the agent to generate a new key. KEYPARMS is the usual S-expression giving the parameters of the key. gpg-agent passes it gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not to protect the generated key. If NO_PROTECTION is not set and PASSPHRASE is not NULL the agent is requested to protect the key with that passphrase instead of asking for one. */ gpg_error_t agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr, const char *keyparms, int no_protection, const char *passphrase, gcry_sexp_t *r_pubkey) { gpg_error_t err; struct genkey_parm_s gk_parm; struct cache_nonce_parm_s cn_parm; struct default_inq_parm_s dfltparm; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_pubkey = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (passwd_nonce_addr && *passwd_nonce_addr) ; /* A RESET would flush the passwd nonce cache. */ else { err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } init_membuf (&data, 1024); gk_parm.dflt = &dfltparm; gk_parm.keyparms = keyparms; gk_parm.passphrase = passphrase; snprintf (line, sizeof line, "GENKEY%s%s%s%s%s", no_protection? " --no-protection" : passphrase ? " --inq-passwd" : /* */ "", passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"", passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", cache_nonce_addr && *cache_nonce_addr? " ":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, inq_genkey_parms, &gk_parm, cache_nonce_status_cb, &cn_parm); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) err = gpg_error_from_syserror (); else { err = gcry_sexp_sscan (r_pubkey, NULL, buf, len); xfree (buf); } return err; } /* Call the agent to read the public key part for a given keygrip. * Values from FROMCARD: * 0 - Standard * 1 - The key is read from the current card * via the agent and a stub file is created. */ gpg_error_t agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, unsigned char **r_pubkey) { gpg_error_t err; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_pubkey = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; if (fromcard) snprintf (line, DIM(line), "READKEY --card -- %s", hexkeygrip); else snprintf (line, DIM(line), "READKEY -- %s", hexkeygrip); init_membuf (&data, 1024); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } *r_pubkey = buf; return 0; } /* Call the agent to do a sign operation using the key identified by the hex string KEYGRIP. DESC is a description of the key to be displayed if the agent needs to ask for the PIN. DIGEST and DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id used to compute the digest. If CACHE_NONCE is used the agent is advised to first try a passphrase associated with that nonce. */ gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *keygrip, const char *desc, u32 *keyid, u32 *mainkeyid, int pubkey_algo, unsigned char *digest, size_t digestlen, int digestalgo, gcry_sexp_t *r_sigval) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; *r_sigval = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (digestlen*2 + 50 > DIM(line)) return gpg_error (GPG_ERR_GENERAL); err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; snprintf (line, DIM(line), "SIGKEY %s", keygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, sizeof line, "SETHASH %d ", digestalgo); bin2hex (digest, digestlen, line + strlen (line)); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; init_membuf (&data, 1024); snprintf (line, sizeof line, "PKSIGN%s%s", cache_nonce? " -- ":"", cache_nonce? cache_nonce:""); if (DBG_CLOCK) log_clock ("enter signing"); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (DBG_CLOCK) log_clock ("leave signing"); if (err) xfree (get_membuf (&data, NULL)); else { unsigned char *buf; size_t len; buf = get_membuf (&data, &len); if (!buf) err = gpg_error_from_syserror (); else { err = gcry_sexp_sscan (r_sigval, NULL, buf, len); xfree (buf); } } return err; } /* Handle a CIPHERTEXT inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the END. */ static gpg_error_t inq_ciphertext_cb (void *opaque, const char *line) { struct cipher_parm_s *parm = opaque; int rc; if (has_leading_keyword (line, "CIPHERTEXT")) { assuan_begin_confidential (parm->ctx); rc = assuan_send_data (parm->dflt->ctx, parm->ciphertext, parm->ciphertextlen); assuan_end_confidential (parm->ctx); } else rc = default_inq_cb (parm->dflt, line); return rc; } /* Check whether there is any padding info from the agent. */ static gpg_error_t padding_info_cb (void *opaque, const char *line) { int *r_padding = opaque; const char *s; if ((s=has_leading_keyword (line, "PADDING"))) { *r_padding = atoi (s); } return 0; } /* Call the agent to do a decrypt operation using the key identified by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the success the decoded value is stored verbatim at R_BUF and its length at R_BUF; the callers needs to release it. KEYID, MAINKEYID and PUBKEY_ALGO are used to construct additional promots or status messages. The padding information is stored at R_PADDING with -1 for not known. */ gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, u32 *keyid, u32 *mainkeyid, int pubkey_algo, gcry_sexp_t s_ciphertext, unsigned char **r_buf, size_t *r_buflen, int *r_padding) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t n, len; char *p, *buf, *endp; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; if (!keygrip || strlen(keygrip) != 40 || !s_ciphertext || !r_buf || !r_buflen || !r_padding) return gpg_error (GPG_ERR_INV_VALUE); *r_buf = NULL; *r_padding = -1; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; snprintf (line, sizeof line, "SETKEY %s", keygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } init_membuf_secure (&data, 1024); { struct cipher_parm_s parm; parm.dflt = &dfltparm; parm.ctx = agent_ctx; err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen); if (err) return err; err = assuan_transact (agent_ctx, "PKDECRYPT", put_membuf_cb, &data, inq_ciphertext_cb, &parm, padding_info_cb, r_padding); xfree (parm.ciphertext); } if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); if (len == 0 || *buf != '(') { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } if (len < 12 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)" */ { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } while (buf[len-1] == 0) len--; if (buf[len-1] != ')') return gpg_error (GPG_ERR_INV_SEXP); len--; /* Drop the final close-paren. */ p = buf + 8; /* Skip leading parenthesis and the value tag. */ len -= 8; /* Count only the data of the second part. */ n = strtoul (p, &endp, 10); if (!n || *endp != ':') { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } endp++; if (endp-p+n > len) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */ } memmove (buf, endp, n); *r_buflen = n; *r_buf = buf; return 0; } /* Retrieve a key encryption key from the agent. With FOREXPORT true the key shall be used for export, with false for import. On success the new key is stored at R_KEY and its length at R_KEKLEN. */ gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen) { gpg_error_t err; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_kek = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "KEYWRAP_KEY %s", forexport? "--export":"--import"); init_membuf_secure (&data, 64); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); *r_kek = buf; *r_keklen = len; return 0; } /* Handle the inquiry for an IMPORT_KEY command. */ static gpg_error_t inq_import_key_parms (void *opaque, const char *line) { struct import_key_parm_s *parm = opaque; gpg_error_t err; if (has_leading_keyword (line, "KEYDATA")) { err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen); } else err = default_inq_cb (parm->dflt, line); return err; } /* Call the agent to import a key into the agent. */ gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, const void *key, size_t keylen, int unattended, int force, u32 *keyid, u32 *mainkeyid, int pubkey_algo) { gpg_error_t err; struct import_key_parm_s parm; struct cache_nonce_parm_s cn_parm; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } parm.dflt = &dfltparm; parm.key = key; parm.keylen = keylen; snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s", unattended? " --unattended":"", force? " --force":"", cache_nonce_addr && *cache_nonce_addr? " ":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, NULL, NULL, inq_import_key_parms, &parm, cache_nonce_status_cb, &cn_parm); return err; } /* Receive a secret key from the agent. HEXKEYGRIP is the hexified keygrip, DESC a prompt to be displayed with the agent's passphrase question (needs to be plus+percent escaped). if OPENPGP_PROTECTED is not zero, ensure that the key material is returned in RFC 4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is not NULL the agent is advised to first try a passphrase associated with that nonce. On success the key is stored as a canonical S-expression at R_RESULT and R_RESULTLEN. */ gpg_error_t agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int openpgp_protected, char **cache_nonce_addr, unsigned char **r_result, size_t *r_resultlen, u32 *keyid, u32 *mainkeyid, int pubkey_algo) { gpg_error_t err; struct cache_nonce_parm_s cn_parm; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; *r_result = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s", openpgp_protected ? "--openpgp ":"", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", hexkeygrip); init_membuf_secure (&data, 1024); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, cache_nonce_status_cb, &cn_parm); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); *r_result = buf; *r_resultlen = len; return 0; } /* Status callback for handling confirmation. */ static gpg_error_t confirm_status_cb (void *opaque, const char *line) { struct confirm_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "SETDESC"))) { xfree (parm->desc); parm->desc = unescape_status_string (s); } else if ((s = has_leading_keyword (line, "SETOK"))) { xfree (parm->ok); parm->ok = unescape_status_string (s); } else if ((s = has_leading_keyword (line, "SETNOTOK"))) { xfree (parm->notok); parm->notok = unescape_status_string (s); } return 0; } /* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC is not NULL, display DESC instead of the default description message. If FORCE is true the agent is advised not to ask for confirmation. */ gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int force) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; struct confirm_parm_s confirm_parm; memset (&confirm_parm, 0, sizeof confirm_parm); memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.confirm = &confirm_parm; err = start_agent (ctrl, 0); if (err) return err; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, DIM(line), "DELETE_KEY%s %s", force? " --force":"", hexkeygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, confirm_status_cb, &confirm_parm); xfree (confirm_parm.desc); xfree (confirm_parm.ok); xfree (confirm_parm.notok); return err; } /* Ask the agent to change the passphrase of the key identified by * HEXKEYGRIP. If DESC is not NULL, display DESC instead of the * default description message. If CACHE_NONCE_ADDR is not NULL the * agent is advised to first try a passphrase associated with that * nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use * the passphrase associated with that nonce for the new passphrase. * If VERIFY is true the passphrase is only verified. */ gpg_error_t agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify, char **cache_nonce_addr, char **passwd_nonce_addr) { gpg_error_t err; struct cache_nonce_parm_s cn_parm; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } if (verify) snprintf (line, DIM(line), "PASSWD %s%s --verify %s", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", hexkeygrip); else snprintf (line, DIM(line), "PASSWD %s%s %s%s %s", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"", passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", hexkeygrip); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = passwd_nonce_addr; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, cache_nonce_status_cb, &cn_parm); return err; } /* Return the version reported by gpg-agent. */ gpg_error_t agent_get_version (ctrl_t ctrl, char **r_version) { gpg_error_t err; err = start_agent (ctrl, 0); if (err) return err; err = get_assuan_server_version (agent_ctx, 0, r_version); return err; } diff --git a/g10/call-dirmngr.c b/g10/call-dirmngr.c index 4180589fa..02d1edb9f 100644 --- a/g10/call-dirmngr.c +++ b/g10/call-dirmngr.c @@ -1,1433 +1,1433 @@ /* call-dirmngr.c - GPG operations to the Dirmngr. * Copyright (C) 2011 Free Software Foundation, Inc. * Copyright (C) 2015 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 . */ #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H # include #endif #include "gpg.h" #include #include "../common/util.h" #include "../common/membuf.h" #include "options.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/keyserver.h" #include "../common/status.h" #include "call-dirmngr.h" /* Keys retrieved from the web key directory should be small. There * is only one UID and we can expect that the number of subkeys is * reasonable. So we set a generous limit of 256 KiB. */ #define MAX_WKD_RESULT_LENGTH (256 * 1024) /* Parameter structure used to gather status info. Note that it is * also used for WKD requests. */ struct ks_status_parm_s { const char *keyword; /* Look for this keyword or NULL for "SOURCE". */ char *source; }; /* Parameter structure used with the KS_SEARCH command. */ struct ks_search_parm_s { gpg_error_t lasterr; /* Last error code. */ membuf_t saveddata; /* Buffer to build complete lines. */ char *helpbuf; /* NULL or malloced buffer. */ size_t helpbufsize; /* Allocated size of HELPBUF. */ gpg_error_t (*data_cb)(void*, int, char*); /* Callback. */ void *data_cb_value; /* First argument for DATA_CB. */ struct ks_status_parm_s *stparm; /* Link to the status parameter. */ }; /* Parameter structure used with the KS_GET command. */ struct ks_get_parm_s { estream_t memfp; }; /* Parameter structure used with the KS_PUT command. */ struct ks_put_parm_s { assuan_context_t ctx; kbnode_t keyblock; /* The optional keyblock. */ const void *data; /* The key in OpenPGP binary format. */ size_t datalen; /* The length of DATA. */ }; /* Parameter structure used with the DNS_CERT command. */ struct dns_cert_parm_s { estream_t memfp; unsigned char *fpr; size_t fprlen; char *url; }; /* Data used to associate an session with dirmngr contexts. We can't use a simple one to one mapping because we sometimes need two connections to the dirmngr; for example while doing a listing and being in a data callback we may want to retrieve a key. The local dirmngr data takes care of this. At the end of the session the function dirmngr_deinit_session_data is called by gpg.c to cleanup these resources. Note that gpg.h defines a typedef dirmngr_local_t for this structure. */ struct dirmngr_local_s { /* Link to other contexts which are used simultaneously. */ struct dirmngr_local_s *next; /* The active Assuan context. */ assuan_context_t ctx; /* Flag set when the keyserver names have been send. */ int set_keyservers_done; /* Flag set to true while an operation is running on CTX. */ int is_active; }; /* Deinitialize all session data of dirmngr pertaining to CTRL. */ void gpg_dirmngr_deinit_session_data (ctrl_t ctrl) { dirmngr_local_t dml; while ((dml = ctrl->dirmngr_local)) { ctrl->dirmngr_local = dml->next; if (dml->is_active) log_error ("oops: trying to cleanup an active dirmngr context\n"); else assuan_release (dml->ctx); xfree (dml); } } /* Print a warning if the server's version number is less than our version number. Returns an error code on a connection problem. */ static gpg_error_t warn_version_mismatch (assuan_context_t ctx, const char *servername) { gpg_error_t err; char *serverversion; - const char *myversion = strusage (13); + const char *myversion = gpgrt_strusage (13); err = get_assuan_server_version (ctx, 0, &serverversion); if (err) log_error (_("error getting version from '%s': %s\n"), servername, gpg_strerror (err)); else if (compare_version_strings (serverversion, myversion) < 0) { char *warn; warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), servername, serverversion, myversion); if (!warn) err = gpg_error_from_syserror (); else { log_info (_("WARNING: %s\n"), warn); if (!opt.quiet) { log_info (_("Note: Outdated servers may lack important" " security fixes.\n")); log_info (_("Note: Use the command \"%s\" to restart them.\n"), "gpgconf --kill all"); } write_status_strings (STATUS_WARNING, "server_version_mismatch 0", " ", warn, NULL); xfree (warn); } } xfree (serverversion); return err; } /* Try to connect to the Dirmngr via a socket or spawn it if possible. Handle the server's initial greeting and set global options. */ static gpg_error_t create_context (ctrl_t ctrl, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx; *r_ctx = NULL; if (opt.disable_dirmngr) return gpg_error (GPG_ERR_NO_DIRMNGR); err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.dirmngr_program, opt.autostart, opt.verbose, DBG_IPC, NULL /*gpg_status2*/, ctrl); if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR) { static int shown; if (!shown) { shown = 1; log_info (_("no dirmngr running in this session\n")); } } else if (!err && !(err = warn_version_mismatch (ctx, DIRMNGR_NAME))) { char *line; /* Tell the dirmngr that we want to collect audit event. */ /* err = assuan_transact (agent_ctx, "OPTION audit-events=1", */ /* NULL, NULL, NULL, NULL, NULL, NULL); */ if (opt.keyserver_options.http_proxy) { line = xtryasprintf ("OPTION http-proxy=%s", opt.keyserver_options.http_proxy); if (!line) err = gpg_error_from_syserror (); else { err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); xfree (line); } } if (err) ; else if ((opt.keyserver_options.options & KEYSERVER_HONOR_KEYSERVER_URL)) { /* Tell the dirmngr that this possibly privacy invading option is in use. If Dirmngr is running in Tor mode, it will return an error. */ err = assuan_transact (ctx, "OPTION honor-keyserver-url-used", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_FORBIDDEN) log_error (_("keyserver option \"honor-keyserver-url\"" " may not be used in Tor mode\n")); else if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* Old dirmngr versions do not support this option. */ } } if (err) assuan_release (ctx); else { /* audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err); */ *r_ctx = ctx; } return err; } /* Get a context for accessing dirmngr. If no context is available a new one is created and - if required - dirmngr started. On success an assuan context is stored at R_CTX. This context may only be released by means of close_context. Note that NULL is stored at R_CTX on error. */ static gpg_error_t open_context (ctrl_t ctrl, assuan_context_t *r_ctx) { gpg_error_t err; dirmngr_local_t dml; *r_ctx = NULL; for (;;) { for (dml = ctrl->dirmngr_local; dml && dml->is_active; dml = dml->next) ; if (dml) { /* Found an inactive local session - return that. */ log_assert (!dml->is_active); /* But first do the per session init if not yet done. */ if (!dml->set_keyservers_done) { keyserver_spec_t ksi; /* Set all configured keyservers. We clear existing keyservers so that any keyserver configured in GPG overrides keyservers possibly still configured in Dirmngr for the session (Note that the keyserver list of a session in Dirmngr survives a RESET. */ for (ksi = opt.keyserver; ksi; ksi = ksi->next) { char *line; line = xtryasprintf ("KEYSERVER%s %s", ksi == opt.keyserver? " --clear":"", ksi->uri); if (!line) err = gpg_error_from_syserror (); else { err = assuan_transact (dml->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); xfree (line); } if (err) return err; } dml->set_keyservers_done = 1; } dml->is_active = 1; *r_ctx = dml->ctx; return 0; } dml = xtrycalloc (1, sizeof *dml); if (!dml) return gpg_error_from_syserror (); err = create_context (ctrl, &dml->ctx); if (err) { xfree (dml); return err; } /* To be on the nPth thread safe site we need to add it to a list; this is far easier than to have a lock for this function. It should not happen anyway but the code is free because we need it for the is_active check above. */ dml->next = ctrl->dirmngr_local; ctrl->dirmngr_local = dml; } } /* Close the assuan context CTX or return it to a pool of unused contexts. If CTX is NULL, the function does nothing. */ static void close_context (ctrl_t ctrl, assuan_context_t ctx) { dirmngr_local_t dml; if (!ctx) return; for (dml = ctrl->dirmngr_local; dml; dml = dml->next) { if (dml->ctx == ctx) { if (!dml->is_active) log_fatal ("closing inactive dirmngr context %p\n", ctx); dml->is_active = 0; return; } } log_fatal ("closing unknown dirmngr ctx %p\n", ctx); } /* Clear the set_keyservers_done flag on context CTX. */ static void clear_context_flags (ctrl_t ctrl, assuan_context_t ctx) { dirmngr_local_t dml; if (!ctx) return; for (dml = ctrl->dirmngr_local; dml; dml = dml->next) { if (dml->ctx == ctx) { if (!dml->is_active) log_fatal ("clear_context_flags on inactive dirmngr ctx %p\n", ctx); dml->set_keyservers_done = 0; return; } } log_fatal ("clear_context_flags on unknown dirmngr ctx %p\n", ctx); } /* Status callback for ks_list, ks_get, ks_search, and wkd_get */ static gpg_error_t ks_status_cb (void *opaque, const char *line) { struct ks_status_parm_s *parm = opaque; gpg_error_t err = 0; const char *s, *s2; const char *warn = NULL; int is_note = 0; if ((s = has_leading_keyword (line, parm->keyword? parm->keyword : "SOURCE"))) { /* Note that the arg for "S SOURCE" is the URL of a keyserver. */ if (!parm->source) { parm->source = xtrystrdup (s); if (!parm->source) err = gpg_error_from_syserror (); } } else if ((s = has_leading_keyword (line, "WARNING")) || (is_note = !!(s = has_leading_keyword (line, "NOTE")))) { if ((s2 = has_leading_keyword (s, "wkd_cached_result"))) { if (opt.verbose) warn = _("WKD uses a cached result"); } else if ((s2 = has_leading_keyword (s, "tor_not_running"))) warn = _("Tor is not running"); else if ((s2 = has_leading_keyword (s, "tor_config_problem"))) warn = _("Tor is not properly configured"); else if ((s2 = has_leading_keyword (s, "dns_config_problem"))) warn = _("DNS is not properly configured"); else if ((s2 = has_leading_keyword (s, "http_redirect"))) warn = _("unacceptable HTTP redirect from server"); else if ((s2 = has_leading_keyword (s, "http_redirect_cleanup"))) warn = _("unacceptable HTTP redirect from server was cleaned up"); else if ((s2 = has_leading_keyword (s, "tls_cert_error"))) warn = _("server uses an invalid certificate"); else warn = NULL; if (warn) { if (is_note) log_info (_("Note: %s\n"), warn); else log_info (_("WARNING: %s\n"), warn); if (s2) { while (*s2 && !spacep (s2)) s2++; while (*s2 && spacep (s2)) s2++; if (*s2) print_further_info ("%s", s2); } } } return err; } /* Run the "KEYSERVER" command to return the name of the used keyserver at R_KEYSERVER. */ gpg_error_t gpg_dirmngr_ks_list (ctrl_t ctrl, char **r_keyserver) { gpg_error_t err; assuan_context_t ctx; struct ks_status_parm_s stparm; memset (&stparm, 0, sizeof stparm); stparm.keyword = "KEYSERVER"; if (r_keyserver) *r_keyserver = NULL; err = open_context (ctrl, &ctx); if (err) return err; err = assuan_transact (ctx, "KEYSERVER", NULL, NULL, NULL, NULL, ks_status_cb, &stparm); if (err) goto leave; if (!stparm.source) { err = gpg_error (GPG_ERR_NO_KEYSERVER); goto leave; } if (r_keyserver) *r_keyserver = stparm.source; else xfree (stparm.source); stparm.source = NULL; leave: xfree (stparm.source); close_context (ctrl, ctx); return err; } /* Data callback for the KS_SEARCH command. */ static gpg_error_t ks_search_data_cb (void *opaque, const void *data, size_t datalen) { gpg_error_t err = 0; struct ks_search_parm_s *parm = opaque; const char *line, *s; size_t rawlen, linelen; char fixedbuf[256]; if (parm->lasterr) return 0; if (parm->stparm->source) { err = parm->data_cb (parm->data_cb_value, 1, parm->stparm->source); if (err) { parm->lasterr = err; return err; } /* Clear it so that we won't get back here unless the server accidentally sends a second source status line. Note that will not see all accidentally sent source lines because it depends on whether data lines have been send in between. */ xfree (parm->stparm->source); parm->stparm->source = NULL; } if (!data) return 0; /* Ignore END commands. */ put_membuf (&parm->saveddata, data, datalen); again: line = peek_membuf (&parm->saveddata, &rawlen); if (!line) { parm->lasterr = gpg_error_from_syserror (); return parm->lasterr; /* Tell the server about our problem. */ } if ((s = memchr (line, '\n', rawlen))) { linelen = s - line; /* That is the length excluding the LF. */ if (linelen + 1 < sizeof fixedbuf) { /* We can use the static buffer. */ memcpy (fixedbuf, line, linelen); fixedbuf[linelen] = 0; if (linelen && fixedbuf[linelen-1] == '\r') fixedbuf[linelen-1] = 0; err = parm->data_cb (parm->data_cb_value, 0, fixedbuf); } else { if (linelen + 1 >= parm->helpbufsize) { xfree (parm->helpbuf); parm->helpbufsize = linelen + 1 + 1024; parm->helpbuf = xtrymalloc (parm->helpbufsize); if (!parm->helpbuf) { parm->lasterr = gpg_error_from_syserror (); return parm->lasterr; } } memcpy (parm->helpbuf, line, linelen); parm->helpbuf[linelen] = 0; if (linelen && parm->helpbuf[linelen-1] == '\r') parm->helpbuf[linelen-1] = 0; err = parm->data_cb (parm->data_cb_value, 0, parm->helpbuf); } if (err) parm->lasterr = err; else { clear_membuf (&parm->saveddata, linelen+1); goto again; /* There might be another complete line. */ } } return err; } /* Run the KS_SEARCH command using the search string SEARCHSTR. All data lines are passed to the CB function. That function is called with CB_VALUE as its first argument, a 0 as second argument, and the decoded data line as third argument. The callback function may modify the data line and it is guaranteed that this data line is a complete line with a terminating 0 character but without the linefeed. NULL is passed to the callback to indicate EOF. */ gpg_error_t gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr, gpg_error_t (*cb)(void*, int, char *), void *cb_value) { gpg_error_t err; assuan_context_t ctx; struct ks_status_parm_s stparm; struct ks_search_parm_s parm; char line[ASSUAN_LINELENGTH]; err = open_context (ctrl, &ctx); if (err) return err; { char *escsearchstr = percent_plus_escape (searchstr); if (!escsearchstr) { err = gpg_error_from_syserror (); close_context (ctrl, ctx); return err; } snprintf (line, sizeof line, "KS_SEARCH -- %s", escsearchstr); xfree (escsearchstr); } memset (&stparm, 0, sizeof stparm); memset (&parm, 0, sizeof parm); init_membuf (&parm.saveddata, 1024); parm.data_cb = cb; parm.data_cb_value = cb_value; parm.stparm = &stparm; err = assuan_transact (ctx, line, ks_search_data_cb, &parm, NULL, NULL, ks_status_cb, &stparm); if (!err) err = cb (cb_value, 0, NULL); /* Send EOF. */ else if (parm.stparm->source) { /* Error but we received a SOURCE status. Tell via callback but * ignore errors. */ parm.data_cb (parm.data_cb_value, 1, parm.stparm->source); } xfree (get_membuf (&parm.saveddata, NULL)); xfree (parm.helpbuf); xfree (stparm.source); close_context (ctrl, ctx); return err; } /* Data callback for the KS_GET and KS_FETCH commands. */ static gpg_error_t ks_get_data_cb (void *opaque, const void *data, size_t datalen) { gpg_error_t err = 0; struct ks_get_parm_s *parm = opaque; size_t nwritten; if (!data) return 0; /* Ignore END commands. */ if (es_write (parm->memfp, data, datalen, &nwritten)) err = gpg_error_from_syserror (); return err; } /* Run the KS_GET command using the patterns in the array PATTERN. On success an estream object is returned to retrieve the keys. On error an error code is returned and NULL stored at R_FP. The pattern may only use search specification which a keyserver can use to retrieve keys. Because we know the format of the pattern we don't need to escape the patterns before sending them to the server. If QUICK is set the dirmngr is advised to use a shorter timeout. If R_SOURCE is not NULL the source of the data is stored as a malloced string there. If a source is not known NULL is stored. Note that this may even be returned after an error. If there are too many patterns the function returns an error. That could be fixed by issuing several search commands or by implementing a different interface. However with long keyids we are able to ask for (1000-10-1)/(2+8+1) = 90 keys at once. */ gpg_error_t gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, keyserver_spec_t override_keyserver, int quick, estream_t *r_fp, char **r_source) { gpg_error_t err; assuan_context_t ctx; struct ks_status_parm_s stparm; struct ks_get_parm_s parm; char *line = NULL; size_t linelen; membuf_t mb; int idx; memset (&stparm, 0, sizeof stparm); memset (&parm, 0, sizeof parm); *r_fp = NULL; if (r_source) *r_source = NULL; err = open_context (ctrl, &ctx); if (err) return err; /* If we have an override keyserver we first indicate that the next user of the context needs to again setup the global keyservers and them we send the override keyserver. */ if (override_keyserver) { clear_context_flags (ctrl, ctx); line = xtryasprintf ("KEYSERVER --clear %s", override_keyserver->uri); if (!line) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) goto leave; xfree (line); line = NULL; } /* Lump all patterns into one string. */ init_membuf (&mb, 1024); put_membuf_str (&mb, quick? "KS_GET --quick --" : "KS_GET --"); for (idx=0; pattern[idx]; idx++) { put_membuf (&mb, " ", 1); /* Append Delimiter. */ put_membuf_str (&mb, pattern[idx]); } put_membuf (&mb, "", 1); /* Append Nul. */ line = get_membuf (&mb, &linelen); if (!line) { err = gpg_error_from_syserror (); goto leave; } if (linelen + 2 >= ASSUAN_LINELENGTH) { err = gpg_error (GPG_ERR_TOO_MANY); goto leave; } parm.memfp = es_fopenmem (0, "rwb"); if (!parm.memfp) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (ctx, line, ks_get_data_cb, &parm, NULL, NULL, ks_status_cb, &stparm); if (err) goto leave; es_rewind (parm.memfp); *r_fp = parm.memfp; parm.memfp = NULL; leave: if (r_source && stparm.source) { *r_source = stparm.source; stparm.source = NULL; } es_fclose (parm.memfp); xfree (stparm.source); xfree (line); close_context (ctrl, ctx); return err; } /* Run the KS_FETCH and pass URL as argument. On success an estream object is returned to retrieve the keys. On error an error code is returned and NULL stored at R_FP. The url is expected to point to a small set of keys; in many cases only to one key. However, schemes like finger may return several keys. Note that the configured keyservers are ignored by the KS_FETCH command. */ gpg_error_t gpg_dirmngr_ks_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp) { gpg_error_t err; assuan_context_t ctx; struct ks_get_parm_s parm; char *line = NULL; memset (&parm, 0, sizeof parm); *r_fp = NULL; err = open_context (ctrl, &ctx); if (err) return err; line = strconcat ("KS_FETCH -- ", url, NULL); if (!line) { err = gpg_error_from_syserror (); goto leave; } if (strlen (line) + 2 >= ASSUAN_LINELENGTH) { err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } parm.memfp = es_fopenmem (0, "rwb"); if (!parm.memfp) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (ctx, line, ks_get_data_cb, &parm, NULL, NULL, NULL, NULL); if (err) goto leave; es_rewind (parm.memfp); *r_fp = parm.memfp; parm.memfp = NULL; leave: es_fclose (parm.memfp); xfree (line); close_context (ctrl, ctx); return err; } static void record_output (estream_t output, pkttype_t type, const char *validity, /* The public key length or -1. */ int pub_key_length, /* The public key algo or -1. */ int pub_key_algo, /* 2 ulongs or NULL. */ const u32 *keyid, /* The creation / expiration date or 0. */ u32 creation_date, u32 expiration_date, const char *userid) { const char *type_str = NULL; char *pub_key_length_str = NULL; char *pub_key_algo_str = NULL; char *keyid_str = NULL; char *creation_date_str = NULL; char *expiration_date_str = NULL; char *userid_escaped = NULL; switch (type) { case PKT_PUBLIC_KEY: type_str = "pub"; break; case PKT_PUBLIC_SUBKEY: type_str = "sub"; break; case PKT_USER_ID: type_str = "uid"; break; case PKT_SIGNATURE: type_str = "sig"; break; default: log_assert (! "Unhandled type."); } if (pub_key_length > 0) pub_key_length_str = xasprintf ("%d", pub_key_length); if (pub_key_algo != -1) pub_key_algo_str = xasprintf ("%d", pub_key_algo); if (keyid) keyid_str = xasprintf ("%08lX%08lX", (ulong) keyid[0], (ulong) keyid[1]); if (creation_date) creation_date_str = xstrdup (colon_strtime (creation_date)); if (expiration_date) expiration_date_str = xstrdup (colon_strtime (expiration_date)); /* Quote ':', '%', and any 8-bit characters. */ if (userid) { int r; int w = 0; int len = strlen (userid); /* A 100k character limit on the uid should be way more than enough. */ if (len > 100 * 1024) len = 100 * 1024; /* The minimum amount of space that we need. */ userid_escaped = xmalloc (len * 3 + 1); for (r = 0; r < len; r++) { if (userid[r] == ':' || userid[r]== '%' || (userid[r] & 0x80)) { sprintf (&userid_escaped[w], "%%%02X", (byte) userid[r]); w += 3; } else userid_escaped[w ++] = userid[r]; } userid_escaped[w] = '\0'; } es_fprintf (output, "%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:%s\n", type_str, validity ?: "", pub_key_length_str ?: "", pub_key_algo_str ?: "", keyid_str ?: "", creation_date_str ?: "", expiration_date_str ?: "", "" /* Certificate S/N */, "" /* Ownertrust. */, userid_escaped ?: "", "" /* Signature class. */, "" /* Key capabilities. */, "" /* Issuer certificate fingerprint. */, "" /* Flag field. */, "" /* S/N of a token. */, "" /* Hash algo. */, "" /* Curve name. */); xfree (userid_escaped); xfree (expiration_date_str); xfree (creation_date_str); xfree (keyid_str); xfree (pub_key_algo_str); xfree (pub_key_length_str); } /* Handle the KS_PUT inquiries. */ static gpg_error_t ks_put_inq_cb (void *opaque, const char *line) { struct ks_put_parm_s *parm = opaque; gpg_error_t err = 0; if (has_leading_keyword (line, "KEYBLOCK")) { if (parm->data) err = assuan_send_data (parm->ctx, parm->data, parm->datalen); } else if (has_leading_keyword (line, "KEYBLOCK_INFO")) { kbnode_t node; estream_t fp; /* Parse the keyblock and send info lines back to the server. */ fp = es_fopenmem (0, "rw,samethread"); if (!fp) err = gpg_error_from_syserror (); /* Note: the output format for the INFO block follows the colon format as described in doc/DETAILS. We don't actually reuse the functionality from g10/keylist.c to produce the output, because we don't need all of it and some of it is quite expensive to generate. The fields are (the starred fields are the ones we need): * Field 1 - Type of record * Field 2 - Validity * Field 3 - Key length * Field 4 - Public key algorithm * Field 5 - KeyID * Field 6 - Creation date * Field 7 - Expiration date Field 8 - Certificate S/N, UID hash, trust signature info Field 9 - Ownertrust * Field 10 - User-ID Field 11 - Signature class Field 12 - Key capabilities Field 13 - Issuer certificate fingerprint or other info Field 14 - Flag field Field 15 - S/N of a token Field 16 - Hash algorithm Field 17 - Curve name */ for (node = parm->keyblock; !err && node; node=node->next) { switch (node->pkt->pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: { PKT_public_key *pk = node->pkt->pkt.public_key; char validity[3]; int i; i = 0; if (pk->flags.revoked) validity[i ++] = 'r'; if (pk->has_expired) validity[i ++] = 'e'; validity[i] = '\0'; keyid_from_pk (pk, NULL); record_output (fp, node->pkt->pkttype, validity, nbits_from_pk (pk), pk->pubkey_algo, pk->keyid, pk->timestamp, pk->expiredate, NULL); } break; case PKT_USER_ID: { PKT_user_id *uid = node->pkt->pkt.user_id; if (!uid->attrib_data) { char validity[3]; int i; i = 0; if (uid->flags.revoked) validity[i ++] = 'r'; if (uid->flags.expired) validity[i ++] = 'e'; validity[i] = '\0'; record_output (fp, node->pkt->pkttype, validity, -1, -1, NULL, uid->created, uid->expiredate, uid->name); } } break; /* This bit is really for the benefit of people who store their keys in LDAP servers. It makes it easy to do queries for things like "all keys signed by Isabella". */ case PKT_SIGNATURE: { PKT_signature *sig = node->pkt->pkt.signature; if (IS_UID_SIG (sig)) record_output (fp, node->pkt->pkttype, NULL, -1, -1, sig->keyid, sig->timestamp, sig->expiredate, NULL); } break; default: continue; } /* Given that the last operation was an es_fprintf we should get the correct ERRNO if ferror indicates an error. */ if (es_ferror (fp)) err = gpg_error_from_syserror (); } /* Without an error and if we have an keyblock at all, send the data back. */ if (!err && parm->keyblock) { int rc; char buffer[512]; size_t nread; es_rewind (fp); while (!(rc=es_read (fp, buffer, sizeof buffer, &nread)) && nread) { err = assuan_send_data (parm->ctx, buffer, nread); if (err) break; } if (!err && rc) err = gpg_error_from_syserror (); } es_fclose (fp); } else return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); return err; } /* Send a key to the configured server. {DATA,DATLEN} contains the key in OpenPGP binary transport format. If KEYBLOCK is not NULL it has the internal representation of that key; this is for example used to convey meta data to LDAP keyservers. */ gpg_error_t gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, kbnode_t keyblock) { gpg_error_t err; assuan_context_t ctx; struct ks_put_parm_s parm; memset (&parm, 0, sizeof parm); /* We are going to parse the keyblock, thus we better make sure the all information is readily available. */ if (keyblock) merge_keys_and_selfsig (ctrl, keyblock); err = open_context (ctrl, &ctx); if (err) return err; parm.ctx = ctx; parm.keyblock = keyblock; parm.data = data; parm.datalen = datalen; err = assuan_transact (ctx, "KS_PUT", NULL, NULL, ks_put_inq_cb, &parm, NULL, NULL); close_context (ctrl, ctx); return err; } /* Data callback for the DNS_CERT and WKD_GET commands. */ static gpg_error_t dns_cert_data_cb (void *opaque, const void *data, size_t datalen) { struct dns_cert_parm_s *parm = opaque; gpg_error_t err = 0; size_t nwritten; if (!data) return 0; /* Ignore END commands. */ if (!parm->memfp) return 0; /* Data is not required. */ if (es_write (parm->memfp, data, datalen, &nwritten)) err = gpg_error_from_syserror (); return err; } /* Status callback for the DNS_CERT command. */ static gpg_error_t dns_cert_status_cb (void *opaque, const char *line) { struct dns_cert_parm_s *parm = opaque; gpg_error_t err = 0; const char *s; size_t nbytes; if ((s = has_leading_keyword (line, "FPR"))) { char *buf; if (!(buf = xtrystrdup (s))) err = gpg_error_from_syserror (); else if (parm->fpr) err = gpg_error (GPG_ERR_DUP_KEY); else if (!hex2str (buf, buf, strlen (buf)+1, &nbytes)) err = gpg_error_from_syserror (); else if (nbytes < 20) err = gpg_error (GPG_ERR_TOO_SHORT); else { parm->fpr = xtrymalloc (nbytes); if (!parm->fpr) err = gpg_error_from_syserror (); else memcpy (parm->fpr, buf, (parm->fprlen = nbytes)); } xfree (buf); } else if ((s = has_leading_keyword (line, "URL")) && *s) { if (parm->url) err = gpg_error (GPG_ERR_DUP_KEY); else if (!(parm->url = xtrystrdup (s))) err = gpg_error_from_syserror (); } return err; } /* Ask the dirmngr for a DNS CERT record. Depending on the found subtypes different return values are set: - For a PGP subtype a new estream with that key will be returned at R_KEY and the other return parameters are set to NULL/0. - For an IPGP subtype the fingerprint is stored as a malloced block at (R_FPR,R_FPRLEN). If an URL is available it is stored as a malloced string at R_URL; NULL is stored if there is no URL. If CERTTYPE is DNS_CERTTYPE_ANY this function returns the first CERT record found with a supported type; it is expected that only one CERT record is used. If CERTTYPE is one of the supported certtypes, only records with this certtype are considered and the first one found is returned. All R_* args are optional. If CERTTYPE is NULL the DANE method is used to fetch the key. */ gpg_error_t gpg_dirmngr_dns_cert (ctrl_t ctrl, const char *name, const char *certtype, estream_t *r_key, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { gpg_error_t err; assuan_context_t ctx; struct dns_cert_parm_s parm; char *line = NULL; memset (&parm, 0, sizeof parm); if (r_key) *r_key = NULL; if (r_fpr) *r_fpr = NULL; if (r_fprlen) *r_fprlen = 0; if (r_url) *r_url = NULL; err = open_context (ctrl, &ctx); if (err) return err; line = es_bsprintf ("DNS_CERT %s %s", certtype? certtype : "--dane", name); if (!line) { err = gpg_error_from_syserror (); goto leave; } if (strlen (line) + 2 >= ASSUAN_LINELENGTH) { err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } parm.memfp = es_fopenmem (0, "rwb"); if (!parm.memfp) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (ctx, line, dns_cert_data_cb, &parm, NULL, NULL, dns_cert_status_cb, &parm); if (err) goto leave; if (r_key) { es_rewind (parm.memfp); *r_key = parm.memfp; parm.memfp = NULL; } if (r_fpr && parm.fpr) { *r_fpr = parm.fpr; parm.fpr = NULL; } if (r_fprlen) *r_fprlen = parm.fprlen; if (r_url && parm.url) { *r_url = parm.url; parm.url = NULL; } leave: xfree (parm.fpr); xfree (parm.url); es_fclose (parm.memfp); xfree (line); close_context (ctrl, ctx); return err; } /* Ask the dirmngr for PKA info. On success the retrieved fingerprint is returned in a malloced buffer at R_FPR and its length is stored at R_FPRLEN. If an URL is available it is stored as a malloced string at R_URL. On error all return values are set to NULL/0. */ gpg_error_t gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { gpg_error_t err; assuan_context_t ctx; struct dns_cert_parm_s parm; char *line = NULL; memset (&parm, 0, sizeof parm); if (r_fpr) *r_fpr = NULL; if (r_fprlen) *r_fprlen = 0; if (r_url) *r_url = NULL; err = open_context (ctrl, &ctx); if (err) return err; line = es_bsprintf ("DNS_CERT --pka -- %s", userid); if (!line) { err = gpg_error_from_syserror (); goto leave; } if (strlen (line) + 2 >= ASSUAN_LINELENGTH) { err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } err = assuan_transact (ctx, line, dns_cert_data_cb, &parm, NULL, NULL, dns_cert_status_cb, &parm); if (err) goto leave; if (r_fpr && parm.fpr) { *r_fpr = parm.fpr; parm.fpr = NULL; } if (r_fprlen) *r_fprlen = parm.fprlen; if (r_url && parm.url) { *r_url = parm.url; parm.url = NULL; } leave: xfree (parm.fpr); xfree (parm.url); xfree (line); close_context (ctrl, ctx); return err; } /* Ask the dirmngr to retrieve a key via the Web Key Directory * protocol. If QUICK is set the dirmngr is advised to use a shorter * timeout. On success a new estream with the key stored at R_KEY and the * url of the lookup (if any) stored at R_URL. Note that */ gpg_error_t gpg_dirmngr_wkd_get (ctrl_t ctrl, const char *name, int quick, estream_t *r_key, char **r_url) { gpg_error_t err; assuan_context_t ctx; struct ks_status_parm_s stparm = { NULL }; struct dns_cert_parm_s parm = { NULL }; char *line = NULL; if (r_key) *r_key = NULL; if (r_url) *r_url = NULL; err = open_context (ctrl, &ctx); if (err) return err; line = es_bsprintf ("WKD_GET%s -- %s", quick?" --quick":"", name); if (!line) { err = gpg_error_from_syserror (); goto leave; } if (strlen (line) + 2 >= ASSUAN_LINELENGTH) { err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } parm.memfp = es_fopenmem (MAX_WKD_RESULT_LENGTH, "rwb"); if (!parm.memfp) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (ctx, line, dns_cert_data_cb, &parm, NULL, NULL, ks_status_cb, &stparm); if (gpg_err_code (err) == GPG_ERR_ENOSPC) err = gpg_error (GPG_ERR_TOO_LARGE); if (err) goto leave; if (r_key) { es_rewind (parm.memfp); *r_key = parm.memfp; parm.memfp = NULL; } if (r_url) { *r_url = stparm.source; stparm.source = NULL; } leave: xfree (stparm.source); xfree (parm.fpr); xfree (parm.url); es_fclose (parm.memfp); xfree (line); close_context (ctrl, ctx); return err; } diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c index d7d9e2da4..181f33fed 100644 --- a/g10/call-keyboxd.c +++ b/g10/call-keyboxd.c @@ -1,1234 +1,1234 @@ /* call-keyboxd.c - Access to the keyboxd storage server * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H # include #endif #include #include "gpg.h" #include #include "../common/util.h" #include "../common/membuf.h" #include "options.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/host2net.h" #include "../common/exechelp.h" #include "../common/status.h" #include "keydb.h" #include "keydb-private.h" /* For struct keydb_handle_s */ /* Data used to keep track of keybox daemon sessions. This allows us * to use several sessions with the keyboxd and also to re-use already * established sessions. Note that gpg.h defines the type * keyboxd_local_t for this structure. */ struct keyboxd_local_s { /* Link to other keyboxd contexts which are used simultaneously. */ struct keyboxd_local_s *next; /* The active Assuan context. */ assuan_context_t ctx; /* This object is used if fd-passing is used to convey the * keyblocks. */ struct { /* NULL or a stream used to receive data. */ estream_t fp; /* Condition variable to sync the datastream with the command. */ npth_mutex_t mutex; npth_cond_t cond; /* The found keyblock or the parsing error. */ kbnode_t found_keyblock; gpg_error_t found_err; } datastream; /* I/O buffer with the last search result or NULL. Used if * D-lines are used to convey the keyblocks. */ iobuf_t search_result; /* This flag set while an operation is running on this context. */ unsigned int is_active : 1; /* This flag is set to record that the standard per session init has * been done. */ unsigned int per_session_init_done : 1; /* Flag indicating that a search reset is required. */ unsigned int need_search_reset : 1; }; /* Local prototypes. */ static void *datastream_thread (void *arg); static void lock_datastream (keyboxd_local_t kbl) { int rc = npth_mutex_lock (&kbl->datastream.mutex); if (rc) log_fatal ("%s: failed to acquire mutex: %s\n", __func__, gpg_strerror (gpg_error_from_errno (rc))); } static void unlock_datastream (keyboxd_local_t kbl) { int rc = npth_mutex_unlock (&kbl->datastream.mutex); if (rc) log_fatal ("%s: failed to release mutex: %s\n", __func__, gpg_strerror (gpg_error_from_errno (rc))); } /* Deinitialize all session resources pertaining to the keyboxd. */ void gpg_keyboxd_deinit_session_data (ctrl_t ctrl) { keyboxd_local_t kbl; while ((kbl = ctrl->keyboxd_local)) { ctrl->keyboxd_local = kbl->next; if (kbl->is_active) log_error ("oops: trying to cleanup an active keyboxd context\n"); else { es_fclose (kbl->datastream.fp); kbl->datastream.fp = NULL; assuan_release (kbl->ctx); kbl->ctx = NULL; } xfree (kbl); } } /* Print a warning if the server's version number is less than our version number. Returns an error code on a connection problem. */ static gpg_error_t warn_version_mismatch (assuan_context_t ctx, const char *servername) { gpg_error_t err; char *serverversion; - const char *myversion = strusage (13); + const char *myversion = gpgrt_strusage (13); err = get_assuan_server_version (ctx, 0, &serverversion); if (err) log_error (_("error getting version from '%s': %s\n"), servername, gpg_strerror (err)); else if (compare_version_strings (serverversion, myversion) < 0) { char *warn; warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), servername, serverversion, myversion); if (!warn) err = gpg_error_from_syserror (); else { log_info (_("WARNING: %s\n"), warn); if (!opt.quiet) { log_info (_("Note: Outdated servers may lack important" " security fixes.\n")); log_info (_("Note: Use the command \"%s\" to restart them.\n"), "gpgconf --kill all"); } write_status_strings (STATUS_WARNING, "server_version_mismatch 0", " ", warn, NULL); xfree (warn); } } xfree (serverversion); return err; } /* Connect to the keybox daemon and launch it if necessary. Handle * the server's initial greeting and set global options. Returns a * new assuan context or an error. */ static gpg_error_t create_new_context (ctrl_t ctrl, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx; *r_ctx = NULL; err = start_new_keyboxd (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.keyboxd_program, opt.autostart, opt.verbose, DBG_IPC, NULL, ctrl); if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_KEYBOXD) { static int shown; if (!shown) { shown = 1; log_info (_("no keyboxd running in this session\n")); } } else if (!err && !(err = warn_version_mismatch (ctx, KEYBOXD_NAME))) { /* Place to emit global options. */ } if (err) assuan_release (ctx); else *r_ctx = ctx; return err; } /* Setup the pipe used for receiving data from the keyboxd. Store the * info on KBL. */ static gpg_error_t prepare_data_pipe (keyboxd_local_t kbl) { gpg_error_t err; int rc; int inpipe[2]; estream_t infp; npth_t thread; npth_attr_t tattr; err = gnupg_create_inbound_pipe (inpipe, &infp, 0); if (err) { log_error ("error creating inbound pipe: %s\n", gpg_strerror (err)); return err; /* That should not happen. */ } err = assuan_sendfd (kbl->ctx, INT2FD (inpipe[1])); if (err) { log_error ("sending sending fd %d to keyboxd: %s <%s>\n", inpipe[1], gpg_strerror (err), gpg_strsource (err)); es_fclose (infp); close (inpipe[1]); return 0; /* Server may not support fd-passing. */ } err = assuan_transact (kbl->ctx, "OUTPUT FD", NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_info ("keyboxd does not accept our fd: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); es_fclose (infp); return 0; } kbl->datastream.fp = infp; kbl->datastream.found_keyblock = NULL; kbl->datastream.found_err = 0; rc = npth_attr_init (&tattr); if (rc) { err = gpg_error_from_errno (rc); log_error ("error preparing thread for keyboxd: %s\n",gpg_strerror (err)); es_fclose (infp); kbl->datastream.fp = NULL; return err; } npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); rc = npth_create (&thread, &tattr, datastream_thread, kbl); if (rc) { err = gpg_error_from_errno (rc); log_error ("error spawning thread for keyboxd: %s\n", gpg_strerror (err)); npth_attr_destroy (&tattr); es_fclose (infp); kbl->datastream.fp = NULL; return err; } return 0; } /* Get a context for accessing keyboxd. If no context is available a * new one is created and if necessary keyboxd is started. R_KBL * receives a pointer to the local context object. */ static gpg_error_t open_context (ctrl_t ctrl, keyboxd_local_t *r_kbl) { gpg_error_t err; int rc; keyboxd_local_t kbl; *r_kbl = NULL; for (;;) { for (kbl = ctrl->keyboxd_local; kbl && kbl->is_active; kbl = kbl->next) ; if (kbl) { /* Found an inactive keyboxd session - return that. */ log_assert (!kbl->is_active); /* But first do the per session init if not yet done. */ if (!kbl->per_session_init_done) { err = prepare_data_pipe (kbl); if (err) return err; kbl->per_session_init_done = 1; } kbl->is_active = 1; kbl->need_search_reset = 1; *r_kbl = kbl; return 0; } /* None found. Create a new session and retry. */ kbl = xtrycalloc (1, sizeof *kbl); if (!kbl) return gpg_error_from_syserror (); rc = npth_mutex_init (&kbl->datastream.mutex, NULL); if (rc) { err = gpg_error_from_errno (rc); log_error ("error initializing mutex: %s\n", gpg_strerror (err)); xfree (kbl); return err; } rc = npth_cond_init (&kbl->datastream.cond, NULL); if (rc) { err = gpg_error_from_errno (rc); log_error ("error initializing condition: %s\n", gpg_strerror (err)); npth_mutex_destroy (&kbl->datastream.mutex); xfree (kbl); return err; } err = create_new_context (ctrl, &kbl->ctx); if (err) { npth_cond_destroy (&kbl->datastream.cond); npth_mutex_destroy (&kbl->datastream.mutex); xfree (kbl); return err; } /* For thread-saftey we add it to the list and retry; this is * easier than to employ a lock. */ kbl->next = ctrl->keyboxd_local; ctrl->keyboxd_local = kbl; } /*NOTREACHED*/ } /* Create a new database handle. A database handle is similar to a * file handle: it contains a local file position. This is used when * searching: subsequent searches resume where the previous search * left off. To rewind the position, use keydb_search_reset(). This * function returns NULL on error, sets ERRNO, and prints an error * diagnostic. Depending on --use-keyboxd either the old internal * keydb code is used (keydb.c) or, if set, the processing is diverted * to the keyboxd. */ /* FIXME: We should change the interface to return a gpg_error_t. */ KEYDB_HANDLE keydb_new (ctrl_t ctrl) { gpg_error_t err; KEYDB_HANDLE hd; if (DBG_CLOCK) log_clock ("keydb_new"); hd = xtrycalloc (1, sizeof *hd); if (!hd) { err = gpg_error_from_syserror (); goto leave; } if (!opt.use_keyboxd) { err = internal_keydb_init (hd); goto leave; } hd->use_keyboxd = 1; hd->ctrl = ctrl; err = open_context (ctrl, &hd->kbl); leave: if (err) { int rc; log_error (_("error opening key DB: %s\n"), gpg_strerror (err)); xfree (hd); hd = NULL; if (!(rc = gpg_err_code_to_errno (err))) rc = gpg_err_code_to_errno (GPG_ERR_EIO); gpg_err_set_errno (rc); } return hd; } /* Release a keydb handle. */ void keydb_release (KEYDB_HANDLE hd) { keyboxd_local_t kbl; if (!hd) return; if (DBG_CLOCK) log_clock ("keydb_release"); if (!hd->use_keyboxd) internal_keydb_deinit (hd); else { kbl = hd->kbl; if (DBG_CLOCK) log_clock ("close_context (found)"); if (!kbl->is_active) log_fatal ("closing inactive keyboxd context %p\n", kbl); kbl->is_active = 0; hd->kbl = NULL; hd->ctrl = NULL; } xfree (hd); } /* Take a lock if we are not using the keyboxd. */ gpg_error_t keydb_lock (KEYDB_HANDLE hd) { if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (!hd->use_keyboxd) return internal_keydb_lock (hd); return 0; } /* FIXME: This helper is duplicates code of partse_keyblock_image. */ static gpg_error_t keydb_get_keyblock_do_parse (iobuf_t iobuf, int pk_no, int uid_no, kbnode_t *r_keyblock) { gpg_error_t err; struct parse_packet_ctx_s parsectx; PACKET *pkt; kbnode_t keyblock = NULL; kbnode_t node, *tail; int in_cert, save_mode; int pk_count, uid_count; *r_keyblock = NULL; pkt = xtrymalloc (sizeof *pkt); if (!pkt) return gpg_error_from_syserror (); init_packet (pkt); init_parse_packet (&parsectx, iobuf); save_mode = set_packet_list_mode (0); in_cert = 0; tail = NULL; pk_count = uid_count = 0; while ((err = parse_packet (&parsectx, pkt)) != -1) { if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET) { free_packet (pkt, &parsectx); init_packet (pkt); continue; } if (err) { es_fflush (es_stdout); log_error ("parse_keyblock_image: read error: %s\n", gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_INV_PACKET) { free_packet (pkt, &parsectx); init_packet (pkt); continue; } err = gpg_error (GPG_ERR_INV_KEYRING); break; } /* Filter allowed packets. */ switch (pkt->pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: case PKT_SECRET_KEY: case PKT_SECRET_SUBKEY: case PKT_USER_ID: case PKT_ATTRIBUTE: case PKT_SIGNATURE: case PKT_RING_TRUST: break; /* Allowed per RFC. */ default: log_info ("skipped packet of type %d in keybox\n", (int)pkt->pkttype); free_packet(pkt, &parsectx); init_packet(pkt); continue; } /* Other sanity checks. */ if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY) { log_error ("parse_keyblock_image: first packet in a keybox blob " "is not a public key packet\n"); err = gpg_error (GPG_ERR_INV_KEYRING); break; } if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY || pkt->pkttype == PKT_SECRET_KEY)) { log_error ("parse_keyblock_image: " "multiple keyblocks in a keybox blob\n"); err = gpg_error (GPG_ERR_INV_KEYRING); break; } in_cert = 1; node = new_kbnode (pkt); switch (pkt->pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: case PKT_SECRET_KEY: case PKT_SECRET_SUBKEY: if (++pk_count == pk_no) node->flag |= 1; break; case PKT_USER_ID: if (++uid_count == uid_no) node->flag |= 2; break; default: break; } if (!keyblock) keyblock = node; else *tail = node; tail = &node->next; pkt = xtrymalloc (sizeof *pkt); if (!pkt) { err = gpg_error_from_syserror (); break; } init_packet (pkt); } set_packet_list_mode (save_mode); if (err == -1 && keyblock) err = 0; /* Got the entire keyblock. */ if (err) release_kbnode (keyblock); else { *r_keyblock = keyblock; } free_packet (pkt, &parsectx); deinit_parse_packet (&parsectx); xfree (pkt); return err; } /* The thread used to read from the data stream. This is running as * long as the connection and its datastream exists. */ static void * datastream_thread (void *arg) { keyboxd_local_t kbl = arg; gpg_error_t err; int rc; unsigned char lenbuf[4]; size_t nread, datalen; iobuf_t iobuf; int pk_no, uid_no; kbnode_t keyblock, tmpkeyblock; log_debug ("Datastream_thread started\n"); while (kbl->datastream.fp) { /* log_debug ("Datastream_thread waiting ...\n"); */ if (es_read (kbl->datastream.fp, lenbuf, 4, &nread)) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EAGAIN) continue; log_error ("error reading data length from keyboxd: %s\n", gpg_strerror (err)); gnupg_sleep (1); continue; } if (nread != 4) { err = gpg_error (GPG_ERR_EIO); log_error ("error reading data length from keyboxd: %s\n", "short read"); continue; } datalen = buf32_to_size_t (lenbuf); /* log_debug ("keyboxd announced %zu bytes\n", datalen); */ iobuf = iobuf_esopen (kbl->datastream.fp, "rb", 1, datalen); pk_no = uid_no = 0; /* FIXME: Get this from the keyboxd. */ err = keydb_get_keyblock_do_parse (iobuf, pk_no, uid_no, &keyblock); iobuf_close (iobuf); if (!err) { /* log_debug ("parsing datastream succeeded\n"); */ /* Thread-safe assignment to the result var: */ tmpkeyblock = kbl->datastream.found_keyblock; kbl->datastream.found_keyblock = keyblock; release_kbnode (tmpkeyblock); } else { /* log_debug ("parsing datastream failed: %s <%s>\n", */ /* gpg_strerror (err), gpg_strsource (err)); */ tmpkeyblock = kbl->datastream.found_keyblock; kbl->datastream.found_keyblock = NULL; kbl->datastream.found_err = err; release_kbnode (tmpkeyblock); } /* Tell the main thread. */ lock_datastream (kbl); rc = npth_cond_signal (&kbl->datastream.cond); if (rc) { err = gpg_error_from_errno (rc); log_error ("%s: signaling condition failed: %s\n", __func__, gpg_strerror (err)); } unlock_datastream (kbl); } log_debug ("Datastream_thread finished\n"); return NULL; } /* Return the keyblock last found by keydb_search() in *RET_KB. * * On success, the function returns 0 and the caller must free *RET_KB * using release_kbnode(). Otherwise, the function returns an error * code. * * The returned keyblock has the kbnode flag bit 0 set for the node * with the public key used to locate the keyblock or flag bit 1 set * for the user ID node. */ gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, kbnode_t *ret_kb) { gpg_error_t err; int pk_no, uid_no; *ret_kb = NULL; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (DBG_CLOCK) log_clock ("%s enter", __func__); if (!hd->use_keyboxd) { err = internal_keydb_get_keyblock (hd, ret_kb); goto leave; } if (hd->kbl->search_result) { pk_no = uid_no = 0; /*FIXME: Get this from the keyboxd. */ err = keydb_get_keyblock_do_parse (hd->kbl->search_result, pk_no, uid_no, ret_kb); /* In contrast to the old code we close the iobuf here and thus * this function may be called only once to get a keyblock. */ iobuf_close (hd->kbl->search_result); hd->kbl->search_result = NULL; } else if (hd->kbl->datastream.found_keyblock) { *ret_kb = hd->kbl->datastream.found_keyblock; hd->kbl->datastream.found_keyblock = NULL; err = 0; } else { err = gpg_error (GPG_ERR_VALUE_NOT_FOUND); goto leave; } leave: if (DBG_CLOCK) log_clock ("%s leave%s", __func__, err? " (failed)":""); return err; } /* Communication object for STORE commands. */ struct store_parm_s { assuan_context_t ctx; const void *data; /* The key in OpenPGP binary format. */ size_t datalen; /* The length of DATA. */ }; /* Handle the inquiries from the STORE command. */ static gpg_error_t store_inq_cb (void *opaque, const char *line) { struct store_parm_s *parm = opaque; gpg_error_t err = 0; if (has_leading_keyword (line, "BLOB")) { if (parm->data) err = assuan_send_data (parm->ctx, parm->data, parm->datalen); } else return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); return err; } /* Update the keyblock KB (i.e., extract the fingerprint and find the * corresponding keyblock in the keyring). * * This doesn't do anything if --dry-run was specified. * * Returns 0 on success. Otherwise, it returns an error code. Note: * if there isn't a keyblock in the keyring corresponding to KB, then * this function returns GPG_ERR_VALUE_NOT_FOUND. * * This function selects the matching record and modifies the current * file position to point to the record just after the selected entry. * Thus, if you do a subsequent search using HD, you should first do a * keydb_search_reset. Further, if the selected record is important, * you should use keydb_push_found_state and keydb_pop_found_state to * save and restore it. */ gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb) { gpg_error_t err; iobuf_t iobuf = NULL; struct store_parm_s parm = {NULL}; log_assert (kb); log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (!hd->use_keyboxd) { err = internal_keydb_update_keyblock (ctrl, hd, kb); goto leave; } if (opt.dry_run) { err = 0; goto leave; } err = build_keyblock_image (kb, &iobuf); if (err) goto leave; parm.ctx = hd->kbl->ctx; parm.data = iobuf_get_temp_buffer (iobuf); parm.datalen = iobuf_get_temp_length (iobuf); err = assuan_transact (hd->kbl->ctx, "STORE --update", NULL, NULL, store_inq_cb, &parm, NULL, NULL); leave: iobuf_close (iobuf); return err; } /* Insert a keyblock into one of the underlying keyrings or keyboxes. * * By default, the keyring / keybox from which the last search result * came is used. If there was no previous search result (or * keydb_search_reset was called), then the keyring / keybox where the * next search would start is used (i.e., the current file position). * In keyboxd mode the keyboxd decides where to store it. * * Note: this doesn't do anything if --dry-run was specified. * * Returns 0 on success. Otherwise, it returns an error code. */ gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb) { gpg_error_t err; iobuf_t iobuf = NULL; struct store_parm_s parm = {NULL}; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (!hd->use_keyboxd) { err = internal_keydb_insert_keyblock (hd, kb); goto leave; } if (opt.dry_run) { err = 0; goto leave; } err = build_keyblock_image (kb, &iobuf); if (err) goto leave; parm.ctx = hd->kbl->ctx; parm.data = iobuf_get_temp_buffer (iobuf); parm.datalen = iobuf_get_temp_length (iobuf); err = assuan_transact (hd->kbl->ctx, "STORE --insert", NULL, NULL, store_inq_cb, &parm, NULL, NULL); leave: iobuf_close (iobuf); return err; } /* Delete the currently selected keyblock. If you haven't done a * search yet on this database handle (or called keydb_search_reset), * then this function returns an error. * * Returns 0 on success or an error code, if an error occurred. */ gpg_error_t keydb_delete_keyblock (KEYDB_HANDLE hd) { gpg_error_t err; unsigned char hexubid[UBID_LEN * 2 + 1]; char line[ASSUAN_LINELENGTH]; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (!hd->use_keyboxd) { err = internal_keydb_delete_keyblock (hd); goto leave; } if (opt.dry_run) { err = 0; goto leave; } if (!hd->last_ubid_valid) { err = gpg_error (GPG_ERR_VALUE_NOT_FOUND); goto leave; } bin2hex (hd->last_ubid, UBID_LEN, hexubid); snprintf (line, sizeof line, "DELETE %s", hexubid); err = assuan_transact (hd->kbl->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); leave: return err; } /* Clears the current search result and resets the handle's position * so that the next search starts at the beginning of the database. * * Returns 0 on success and an error code if an error occurred. */ gpg_error_t keydb_search_reset (KEYDB_HANDLE hd) { gpg_error_t err; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (DBG_CLOCK) log_clock ("%s", __func__); if (DBG_CACHE) log_debug ("%s (hd=%p)", __func__, hd); if (!hd->use_keyboxd) { err = internal_keydb_search_reset (hd); goto leave; } /* All we need is to tell search that a reset is pending. Note that * keydb_new sets this flag as well. To comply with the * specification of keydb_delete_keyblock we also need to clear the * ubid flag so that after a reset a delete can't be performed. */ hd->kbl->need_search_reset = 1; hd->last_ubid_valid = 0; err = 0; leave: return err; } /* Status callback for SEARCH and NEXT operaions. */ static gpg_error_t search_status_cb (void *opaque, const char *line) { KEYDB_HANDLE hd = opaque; gpg_error_t err = 0; const char *s; if ((s = has_leading_keyword (line, "PUBKEY_INFO"))) { if (atoi (s) != PUBKEY_TYPE_OPGP) err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); else { hd->last_ubid_valid = 0; while (*s && !spacep (s)) s++; if (hex2fixedbuf (s, hd->last_ubid, sizeof hd->last_ubid)) hd->last_ubid_valid = 1; else err = gpg_error (GPG_ERR_INV_VALUE); } } return err; } /* Search the database for keys matching the search description. If * the DB contains any legacy keys, these are silently ignored. * * DESC is an array of search terms with NDESC entries. The search * terms are or'd together. That is, the next entry in the DB that * matches any of the descriptions will be returned. * * Note: this function resumes searching where the last search left * off (i.e., at the current file position). If you want to search * from the start of the database, then you need to first call * keydb_search_reset(). * * If no key matches the search description, returns * GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error * occurred, returns an error code. * * The returned key is considered to be selected and the raw data can, * for instance, be returned by calling keydb_get_keyblock(). */ gpg_error_t keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc, size_t *descindex) { gpg_error_t err; int i; char line[ASSUAN_LINELENGTH]; if (!hd) return gpg_error (GPG_ERR_INV_ARG); if (descindex) *descindex = 0; /* Make sure it is always set on return. */ if (DBG_CLOCK) log_clock ("%s enter", __func__); if (DBG_LOOKUP) { log_debug ("%s: %zd search descriptions:\n", __func__, ndesc); for (i = 0; i < ndesc; i ++) { char *t = keydb_search_desc_dump (&desc[i]); log_debug ("%s %d: %s\n", __func__, i, t); xfree (t); } } if (!hd->use_keyboxd) { err = internal_keydb_search (hd, desc, ndesc, descindex); goto leave; } /* Clear the result objects. */ if (hd->kbl->search_result) { iobuf_close (hd->kbl->search_result); hd->kbl->search_result = NULL; } if (hd->kbl->datastream.found_keyblock) { release_kbnode (hd->kbl->datastream.found_keyblock); hd->kbl->datastream.found_keyblock = NULL; } /* Check whether this is a NEXT search. */ if (!hd->kbl->need_search_reset) { /* No reset requested thus continue the search. The keyboxd * keeps the context of the search and thus the NEXT operates on * the last search pattern. This is how we always used the * keydb.c functions. In theory we were able to modify the * search pattern between searches but that is not anymore * supported by keyboxd and a cursory check does not show that * we actually made used of that misfeature. */ snprintf (line, sizeof line, "NEXT"); goto do_search; } hd->kbl->need_search_reset = 0; if (!ndesc) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } /* FIXME: Implement --multi */ switch (desc->mode) { case KEYDB_SEARCH_MODE_EXACT: snprintf (line, sizeof line, "SEARCH =%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SUBSTR: snprintf (line, sizeof line, "SEARCH *%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAIL: snprintf (line, sizeof line, "SEARCH <%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAILSUB: snprintf (line, sizeof line, "SEARCH @%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_MAILEND: snprintf (line, sizeof line, "SEARCH .%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_WORDS: snprintf (line, sizeof line, "SEARCH +%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SHORT_KID: snprintf (line, sizeof line, "SEARCH 0x%08lX", (ulong)desc->u.kid[1]); break; case KEYDB_SEARCH_MODE_LONG_KID: snprintf (line, sizeof line, "SEARCH 0x%08lX%08lX", (ulong)desc->u.kid[0], (ulong)desc->u.kid[1]); break; case KEYDB_SEARCH_MODE_FPR: { unsigned char hexfpr[MAX_FINGERPRINT_LEN * 2 + 1]; log_assert (desc[0].fprlen <= MAX_FINGERPRINT_LEN); bin2hex (desc[0].u.fpr, desc[0].fprlen, hexfpr); snprintf (line, sizeof line, "SEARCH 0x%s", hexfpr); } break; case KEYDB_SEARCH_MODE_ISSUER: snprintf (line, sizeof line, "SEARCH #/%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_ISSUER_SN: case KEYDB_SEARCH_MODE_SN: snprintf (line, sizeof line, "SEARCH #%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_SUBJECT: snprintf (line, sizeof line, "SEARCH /%s", desc[0].u.name); break; case KEYDB_SEARCH_MODE_KEYGRIP: { unsigned char hexgrip[KEYGRIP_LEN * 2 + 1]; bin2hex (desc[0].u.grip, KEYGRIP_LEN, hexgrip); snprintf (line, sizeof line, "SEARCH &%s", hexgrip); } break; case KEYDB_SEARCH_MODE_UBID: { unsigned char hexubid[UBID_LEN * 2 + 1]; bin2hex (desc[0].u.ubid, UBID_LEN, hexubid); snprintf (line, sizeof line, "SEARCH ^%s", hexubid); } break; case KEYDB_SEARCH_MODE_FIRST: snprintf (line, sizeof line, "SEARCH"); break; case KEYDB_SEARCH_MODE_NEXT: log_debug ("%s: mode next - we should not get to here!\n", __func__); snprintf (line, sizeof line, "NEXT"); break; default: err = gpg_error (GPG_ERR_INV_ARG); goto leave; } do_search: hd->last_ubid_valid = 0; if (hd->kbl->datastream.fp) { /* log_debug ("Sending command '%s'\n", line); */ err = assuan_transact (hd->kbl->ctx, line, NULL, NULL, NULL, NULL, search_status_cb, hd); if (err) { /* log_debug ("Finished command with error: %s\n", gpg_strerror (err)); */ /* Fixme: On unexpected errors we need a way to cancel the * data stream. Probably it will be best to close and * reopen it. */ } else { int rc; /* log_debug ("Finished command .. telling data stream\n"); */ lock_datastream (hd->kbl); if (!hd->kbl->datastream.found_keyblock) { /* log_debug ("%s: waiting on datastream_cond ...\n", __func__); */ rc = npth_cond_wait (&hd->kbl->datastream.cond, &hd->kbl->datastream.mutex); /* log_debug ("%s: waiting on datastream.cond done\n", __func__); */ if (rc) { err = gpg_error_from_errno (rc); log_error ("%s: waiting on condition failed: %s\n", __func__, gpg_strerror (err)); } } unlock_datastream (hd->kbl); } } else /* Slower D-line version if fd-passing was not successful. */ { membuf_t data; void *buffer; size_t len; init_membuf (&data, 8192); err = assuan_transact (hd->kbl->ctx, line, put_membuf_cb, &data, NULL, NULL, search_status_cb, hd); if (err) { xfree (get_membuf (&data, &len)); goto leave; } buffer = get_membuf (&data, &len); if (!buffer) { err = gpg_error_from_syserror (); goto leave; } hd->kbl->search_result = iobuf_temp_with_content (buffer, len); xfree (buffer); } /* if (hd->last_ubid_valid) */ /* log_printhex (hd->last_ubid, 20, "found UBID:"); */ leave: if (DBG_CLOCK) log_clock ("%s leave (%sfound)", __func__, err? "not ":""); return err; } diff --git a/g10/gpg.c b/g10/gpg.c index 89e29fea2..0c0e3a2d6 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -1,5655 +1,5666 @@ -/* gpg.c - The GnuPG utility (main for gpg) - * Copyright (C) 1998-2011 Free Software Foundation, Inc. - * Copyright (C) 1997-2017 Werner Koch - * Copyright (C) 2015-2017 g10 Code GmbH +/* gpg.c - The GnuPG OpenPGP tool + * Copyright (C) 1998-2020 Free Software Foundation, Inc. + * Copyright (C) 1997-2019 Werner Koch + * Copyright (C) 2015-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 this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #ifdef HAVE_STAT #include /* for stat() */ #endif #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif #include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpg.h" #include #include "../common/iobuf.h" #include "../common/util.h" #include "packet.h" #include "../common/membuf.h" #include "main.h" #include "options.h" #include "keydb.h" #include "trustdb.h" #include "filter.h" #include "../common/ttyio.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/status.h" #include "keyserver-internal.h" #include "exec.h" #include "../common/gc-opt-flags.h" #include "../common/asshelp.h" #include "call-dirmngr.h" #include "tofu.h" #include "objcache.h" #include "../common/init.h" #include "../common/mbox-util.h" #include "../common/shareddefs.h" #include "../common/compliance.h" #if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) #define MY_O_BINARY O_BINARY #ifndef S_IRGRP # define S_IRGRP 0 # define S_IWGRP 0 #endif #else #define MY_O_BINARY 0 #endif #ifdef __MINGW32__ int _dowildcard = -1; #endif enum cmd_and_opt_values { aNull = 0, oArmor = 'a', aDetachedSign = 'b', aSym = 'c', aDecrypt = 'd', aEncr = 'e', oRecipientFile = 'f', oHiddenRecipientFile = 'F', oInteractive = 'i', aListKeys = 'k', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oRecipient = 'r', oHiddenRecipient = 'R', aSign = 's', oTextmodeShort= 't', oLocalUser = 'u', oVerbose = 'v', oCompress = 'z', oSetNotation = 'N', aListSecretKeys = 'K', oBatch = 500, oMaxOutput, oInputSizeHint, oChunkSize, oSigNotation, oCertNotation, oShowNotation, oNoShowNotation, oKnownNotation, aEncrFiles, aEncrSym, aDecryptFiles, aClearsign, aStore, aQuickKeygen, aFullKeygen, aKeygen, aSignEncr, aSignEncrSym, aSignSym, aSignKey, aLSignKey, aQuickSignKey, aQuickLSignKey, aQuickAddUid, aQuickAddKey, aQuickRevUid, aQuickSetExpire, aQuickSetPrimaryUid, aListConfig, aListGcryptConfig, aGPGConfList, aGPGConfTest, aListPackets, aEditKey, aDeleteKeys, aDeleteSecretKeys, aDeleteSecretAndPublicKeys, aImport, aFastImport, aVerify, aVerifyFiles, aListSigs, aSendKeys, aRecvKeys, aLocateKeys, aLocateExtKeys, aSearchKeys, aRefreshKeys, aFetchKeys, aShowKeys, aExport, aExportSecret, aExportSecretSub, aExportSshKey, aCheckKeys, aGenRevoke, aDesigRevoke, aPrimegen, aPrintMD, aPrintMDs, aCheckTrustDB, aUpdateTrustDB, aFixTrustDB, aListTrustDB, aListTrustPath, aExportOwnerTrust, aImportOwnerTrust, aDeArmor, aEnArmor, aGenRandom, aRebuildKeydbCaches, aCardStatus, aCardEdit, aChangePIN, aPasswd, aServer, aTOFUPolicy, oMimemode, oTextmode, oNoTextmode, oExpert, oNoExpert, oDefSigExpire, oAskSigExpire, oNoAskSigExpire, oDefCertExpire, oAskCertExpire, oNoAskCertExpire, oDefCertLevel, oMinCertLevel, oAskCertLevel, oNoAskCertLevel, oFingerprint, oWithFingerprint, oWithSubkeyFingerprint, oWithICAOSpelling, oWithKeygrip, oWithKeyScreening, oWithSecret, oWithWKDHash, oWithColons, oWithKeyData, oWithKeyOrigin, oWithTofuInfo, oWithSigList, oWithSigCheck, oAnswerYes, oAnswerNo, oKeyring, oPrimaryKeyring, oSecretKeyring, oShowKeyring, oDefaultKey, oDefRecipient, oDefRecipientSelf, oNoDefRecipient, oTrySecretKey, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugIOLBF, oDebugSetIobufSize, oDebugAllowLargeChunks, oStatusFD, oStatusFile, oAttributeFD, oAttributeFile, oEmitVersion, oNoEmitVersion, oCompletesNeeded, oMarginalsNeeded, oMaxCertDepth, oLoadExtension, oCompliance, oGnuPG, oRFC2440, oRFC4880, oRFC4880bis, oOpenPGP, oPGP7, oPGP8, oDE_VS, oRFC2440Text, oNoRFC2440Text, oCipherAlgo, oAEADAlgo, oDigestAlgo, oCertDigestAlgo, oCompressAlgo, oCompressLevel, oBZ2CompressLevel, oBZ2DecompressLowmem, oPassphrase, oPassphraseFD, oPassphraseFile, oPassphraseRepeat, oPinentryMode, oCommandFD, oCommandFile, oQuickRandom, oNoVerbose, oTrustDBName, oNoSecmemWarn, oRequireSecmem, oNoRequireSecmem, oNoPermissionWarn, oNoArmor, oNoDefKeyring, oNoKeyring, oNoGreeting, oNoTTY, oNoOptions, oNoBatch, oHomedir, oSkipVerify, oSkipHiddenRecipients, oNoSkipHiddenRecipients, oAlwaysTrust, oTrustModel, oForceOwnertrust, oSetFilename, oForYourEyesOnly, oNoForYourEyesOnly, oSetPolicyURL, oSigPolicyURL, oCertPolicyURL, oShowPolicyURL, oNoShowPolicyURL, oSigKeyserverURL, oUseEmbeddedFilename, oNoUseEmbeddedFilename, oComment, oDefaultComment, oNoComments, oThrowKeyids, oNoThrowKeyids, oShowPhotos, oNoShowPhotos, oPhotoViewer, oForceAEAD, oS2KMode, oS2KDigest, oS2KCipher, oS2KCount, oDisplayCharset, oNotDashEscaped, oEscapeFrom, oNoEscapeFrom, oLockOnce, oLockMultiple, oLockNever, oKeyServer, oKeyServerOptions, oImportOptions, oImportFilter, oExportOptions, oExportFilter, oListOptions, oVerifyOptions, oTempDir, oExecPath, oEncryptTo, oHiddenEncryptTo, oNoEncryptTo, oEncryptToDefaultKey, oLoggerFD, oLoggerFile, oUtf8Strings, oNoUtf8Strings, oDisableCipherAlgo, oDisablePubkeyAlgo, oAllowNonSelfsignedUID, oNoAllowNonSelfsignedUID, oAllowFreeformUID, oNoAllowFreeformUID, oAllowSecretKeyImport, oEnableSpecialFilenames, oNoLiteral, oSetFilesize, oHonorHttpProxy, oFastListMode, oListOnly, oIgnoreTimeConflict, oIgnoreValidFrom, oIgnoreCrcError, oIgnoreMDCError, oShowSessionKey, oOverrideSessionKey, oOverrideSessionKeyFD, oNoRandomSeedFile, oAutoKeyRetrieve, oNoAutoKeyRetrieve, oUseAgent, oNoUseAgent, oGpgAgentInfo, oUseKeyboxd, oMergeOnly, oTryAllSecrets, oTrustedKey, oNoExpensiveTrustChecks, oFixedListMode, oLegacyListMode, oNoSigCache, oAutoCheckTrustDB, oNoAutoCheckTrustDB, oPreservePermissions, oDefaultPreferenceList, oDefaultKeyserverURL, oPersonalCipherPreferences, oPersonalAEADPreferences, oPersonalDigestPreferences, oPersonalCompressPreferences, oAgentProgram, oKeyboxdProgram, oDirmngrProgram, oDisableDirmngr, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oGroup, oUnGroup, oNoGroups, oStrict, oNoStrict, oMangleDosFilenames, oNoMangleDosFilenames, oEnableProgressFilter, oMultifile, oKeyidFormat, oExitOnStatusWriteError, oLimitCardInsertTries, oReaderPort, octapiDriver, opcscDriver, oDisableCCID, oRequireCrossCert, oNoRequireCrossCert, oAutoKeyLocate, oNoAutoKeyLocate, oEnableLargeRSA, oDisableLargeRSA, oEnableDSA2, oDisableDSA2, oAllowWeakDigestAlgos, oAllowWeakKeySignatures, oFakedSystemTime, oNoAutostart, oPrintPKARecords, oPrintDANERecords, oTOFUDefaultPolicy, oTOFUDBFormat, oDefaultNewKeyAlgo, oWeakDigest, oUnwrap, oOnlySignTextIDs, oDisableSignerUID, oSender, oKeyOrigin, oRequestOrigin, oNoSymkeyCache, oUseOnlyOpenPGPCard, oFullTimestrings, oNoop }; -static ARGPARSE_OPTS opts[] = { +static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aSign, "sign", N_("make a signature")), ARGPARSE_c (aClearsign, "clear-sign", N_("make a clear text signature")), ARGPARSE_c (aClearsign, "clearsign", "@"), ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")), ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")), ARGPARSE_c (aEncrFiles, "encrypt-files", "@"), ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")), ARGPARSE_c (aStore, "store", "@"), ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")), ARGPARSE_c (aDecryptFiles, "decrypt-files", "@"), ARGPARSE_c (aVerify, "verify" , N_("verify a signature")), ARGPARSE_c (aVerifyFiles, "verify-files" , "@" ), ARGPARSE_c (aListKeys, "list-keys", N_("list keys")), ARGPARSE_c (aListKeys, "list-public-keys", "@" ), ARGPARSE_c (aListSigs, "list-signatures", N_("list keys and signatures")), ARGPARSE_c (aListSigs, "list-sigs", "@"), ARGPARSE_c (aCheckKeys, "check-signatures", N_("list and check key signatures")), ARGPARSE_c (aCheckKeys, "check-sigs", "@"), ARGPARSE_c (oFingerprint, "fingerprint", N_("list keys and fingerprints")), ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")), ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")), ARGPARSE_c (aKeygen, "gen-key", "@"), ARGPARSE_c (aQuickKeygen, "quick-generate-key" , N_("quickly generate a new key pair")), ARGPARSE_c (aQuickKeygen, "quick-gen-key", "@"), ARGPARSE_c (aQuickAddUid, "quick-add-uid", N_("quickly add a new user-id")), ARGPARSE_c (aQuickAddUid, "quick-adduid", "@"), ARGPARSE_c (aQuickAddKey, "quick-add-key", "@"), ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"), ARGPARSE_c (aQuickRevUid, "quick-revoke-uid", N_("quickly revoke a user-id")), ARGPARSE_c (aQuickRevUid, "quick-revuid", "@"), ARGPARSE_c (aQuickSetExpire, "quick-set-expire", N_("quickly set a new expiration date")), ARGPARSE_c (aQuickSetPrimaryUid, "quick-set-primary-uid", "@"), ARGPARSE_c (aFullKeygen, "full-generate-key" , N_("full featured key pair generation")), ARGPARSE_c (aFullKeygen, "full-gen-key", "@"), ARGPARSE_c (aGenRevoke, "generate-revocation", N_("generate a revocation certificate")), ARGPARSE_c (aGenRevoke, "gen-revoke", "@"), ARGPARSE_c (aDeleteKeys,"delete-keys", N_("remove keys from the public keyring")), ARGPARSE_c (aDeleteSecretKeys, "delete-secret-keys", N_("remove keys from the secret keyring")), ARGPARSE_c (aQuickSignKey, "quick-sign-key" , N_("quickly sign a key")), ARGPARSE_c (aQuickLSignKey, "quick-lsign-key", N_("quickly sign a key locally")), ARGPARSE_c (aSignKey, "sign-key" ,N_("sign a key")), ARGPARSE_c (aLSignKey, "lsign-key" ,N_("sign a key locally")), ARGPARSE_c (aEditKey, "edit-key" ,N_("sign or edit a key")), ARGPARSE_c (aEditKey, "key-edit" ,"@"), ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")), ARGPARSE_c (aPasswd, "passwd", "@"), ARGPARSE_c (aDesigRevoke, "generate-designated-revocation", "@"), ARGPARSE_c (aDesigRevoke, "desig-revoke","@" ), ARGPARSE_c (aExport, "export" , N_("export keys") ), ARGPARSE_c (aSendKeys, "send-keys" , N_("export keys to a keyserver") ), ARGPARSE_c (aRecvKeys, "receive-keys" , N_("import keys from a keyserver") ), ARGPARSE_c (aRecvKeys, "recv-keys" , "@"), ARGPARSE_c (aSearchKeys, "search-keys" , N_("search for keys on a keyserver") ), ARGPARSE_c (aRefreshKeys, "refresh-keys", N_("update all keys from a keyserver")), ARGPARSE_c (aLocateKeys, "locate-keys", "@"), ARGPARSE_c (aLocateExtKeys, "locate-external-keys", "@"), ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ), ARGPARSE_c (aShowKeys, "show-keys" , "@" ), ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ), ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ), ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ), ARGPARSE_c (aImport, "import", N_("import/merge keys")), ARGPARSE_c (aFastImport, "fast-import", "@"), #ifdef ENABLE_CARD_SUPPORT ARGPARSE_c (aCardStatus, "card-status", N_("print the card status")), ARGPARSE_c (aCardEdit, "edit-card", N_("change data on a card")), ARGPARSE_c (aCardEdit, "card-edit", "@"), ARGPARSE_c (aChangePIN, "change-pin", N_("change a card's PIN")), #endif ARGPARSE_c (aListConfig, "list-config", "@"), ARGPARSE_c (aListGcryptConfig, "list-gcrypt-config", "@"), ARGPARSE_c (aGPGConfList, "gpgconf-list", "@" ), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@" ), ARGPARSE_c (aListPackets, "list-packets","@"), #ifndef NO_TRUST_MODELS ARGPARSE_c (aExportOwnerTrust, "export-ownertrust", "@"), ARGPARSE_c (aImportOwnerTrust, "import-ownertrust", "@"), ARGPARSE_c (aUpdateTrustDB,"update-trustdb", N_("update the trust database")), ARGPARSE_c (aCheckTrustDB, "check-trustdb", "@"), ARGPARSE_c (aFixTrustDB, "fix-trustdb", "@"), #endif ARGPARSE_c (aDeArmor, "dearmor", "@"), ARGPARSE_c (aDeArmor, "dearmour", "@"), ARGPARSE_c (aEnArmor, "enarmor", "@"), ARGPARSE_c (aEnArmor, "enarmour", "@"), ARGPARSE_c (aPrintMD, "print-md", N_("print message digests")), ARGPARSE_c (aPrimegen, "gen-prime", "@" ), ARGPARSE_c (aGenRandom,"gen-random", "@" ), ARGPARSE_c (aServer, "server", N_("run in server mode")), ARGPARSE_c (aTOFUPolicy, "tofu-policy", N_("|VALUE|set the TOFU policy for a key")), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), ARGPARSE_s_n (oArmor, "armour", "@"), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oHiddenRecipient, "hidden-recipient", "@"), ARGPARSE_s_s (oRecipientFile, "recipient-file", "@"), ARGPARSE_s_s (oHiddenRecipientFile, "hidden-recipient-file", "@"), ARGPARSE_s_s (oRecipient, "remote-user", "@"), /* (old option name) */ ARGPARSE_s_s (oDefRecipient, "default-recipient", "@"), ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", "@"), ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), ARGPARSE_s_s (oTempDir, "temp-directory", "@"), ARGPARSE_s_s (oExecPath, "exec-path", "@"), ARGPARSE_s_s (oEncryptTo, "encrypt-to", "@"), ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"), ARGPARSE_s_s (oHiddenEncryptTo, "hidden-encrypt-to", "@"), ARGPARSE_s_n (oEncryptToDefaultKey, "encrypt-to-default-key", "@"), ARGPARSE_s_s (oLocalUser, "local-user", N_("|USER-ID|use USER-ID to sign or decrypt")), ARGPARSE_s_s (oSender, "sender", "@"), ARGPARSE_s_s (oTrySecretKey, "try-secret-key", "@"), ARGPARSE_s_i (oCompress, NULL, N_("|N|set compress level to N (0 disables)")), ARGPARSE_s_i (oCompressLevel, "compress-level", "@"), ARGPARSE_s_i (oBZ2CompressLevel, "bzip2-compress-level", "@"), ARGPARSE_s_n (oBZ2DecompressLowmem, "bzip2-decompress-lowmem", "@"), ARGPARSE_s_n (oMimemode, "mimemode", "@"), ARGPARSE_s_n (oTextmodeShort, NULL, "@"), ARGPARSE_s_n (oTextmode, "textmode", N_("use canonical text mode")), ARGPARSE_s_n (oNoTextmode, "no-textmode", "@"), ARGPARSE_s_n (oExpert, "expert", "@"), ARGPARSE_s_n (oNoExpert, "no-expert", "@"), ARGPARSE_s_s (oDefSigExpire, "default-sig-expire", "@"), ARGPARSE_s_n (oAskSigExpire, "ask-sig-expire", "@"), ARGPARSE_s_n (oNoAskSigExpire, "no-ask-sig-expire", "@"), ARGPARSE_s_s (oDefCertExpire, "default-cert-expire", "@"), ARGPARSE_s_n (oAskCertExpire, "ask-cert-expire", "@"), ARGPARSE_s_n (oNoAskCertExpire, "no-ask-cert-expire", "@"), ARGPARSE_s_i (oDefCertLevel, "default-cert-level", "@"), ARGPARSE_s_i (oMinCertLevel, "min-cert-level", "@"), ARGPARSE_s_n (oAskCertLevel, "ask-cert-level", "@"), ARGPARSE_s_n (oNoAskCertLevel, "no-ask-cert-level", "@"), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_p_u (oMaxOutput, "max-output", "@"), ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"), ARGPARSE_s_i (oChunkSize, "chunk-size", "@"), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", "@"), ARGPARSE_s_n (oNoTTY, "no-tty", "@"), ARGPARSE_s_n (oForceAEAD, "force-aead", "@"), ARGPARSE_s_n (oDisableSignerUID, "disable-signer-uid", "@"), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_n (oInteractive, "interactive", N_("prompt before overwriting")), ARGPARSE_s_n (oBatch, "batch", "@"), ARGPARSE_s_n (oAnswerYes, "yes", "@"), ARGPARSE_s_n (oAnswerNo, "no", "@"), ARGPARSE_s_s (oKeyring, "keyring", "@"), ARGPARSE_s_s (oPrimaryKeyring, "primary-keyring", "@"), ARGPARSE_s_s (oSecretKeyring, "secret-keyring", "@"), ARGPARSE_s_n (oShowKeyring, "show-keyring", "@"), ARGPARSE_s_s (oDefaultKey, "default-key", "@"), ARGPARSE_s_s (oKeyServer, "keyserver", "@"), ARGPARSE_s_s (oKeyServerOptions, "keyserver-options", "@"), ARGPARSE_s_s (oKeyOrigin, "key-origin", "@"), ARGPARSE_s_s (oImportOptions, "import-options", "@"), ARGPARSE_s_s (oImportFilter, "import-filter", "@"), ARGPARSE_s_s (oExportOptions, "export-options", "@"), ARGPARSE_s_s (oExportFilter, "export-filter", "@"), ARGPARSE_s_s (oListOptions, "list-options", "@"), ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"), ARGPARSE_s_s (oDisplayCharset, "display-charset", "@"), ARGPARSE_s_s (oDisplayCharset, "charset", "@"), - ARGPARSE_s_s (oOptions, "options", "@"), + ARGPARSE_conffile (oOptions, "options", "@"), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_n (oDebugIOLBF, "debug-iolbf", "@"), ARGPARSE_s_u (oDebugSetIobufSize, "debug-set-iobuf-size", "@"), ARGPARSE_s_u (oDebugAllowLargeChunks, "debug-allow-large-chunks", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", "@"), ARGPARSE_s_s (oStatusFile, "status-file", "@"), ARGPARSE_s_i (oAttributeFD, "attribute-fd", "@"), ARGPARSE_s_s (oAttributeFile, "attribute-file", "@"), ARGPARSE_s_i (oCompletesNeeded, "completes-needed", "@"), ARGPARSE_s_i (oMarginalsNeeded, "marginals-needed", "@"), ARGPARSE_s_i (oMaxCertDepth, "max-cert-depth", "@" ), ARGPARSE_s_s (oTrustedKey, "trusted-key", "@"), ARGPARSE_s_s (oLoadExtension, "load-extension", "@"), /* Dummy. */ ARGPARSE_s_s (oCompliance, "compliance", "@"), ARGPARSE_s_n (oGnuPG, "gnupg", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp2", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp6", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp7", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp8", "@"), ARGPARSE_s_n (oRFC2440, "rfc2440", "@"), ARGPARSE_s_n (oRFC4880, "rfc4880", "@"), ARGPARSE_s_n (oRFC4880bis, "rfc4880bis", "@"), ARGPARSE_s_n (oOpenPGP, "openpgp", N_("use strict OpenPGP behavior")), ARGPARSE_s_n (oPGP7, "pgp6", "@"), ARGPARSE_s_n (oPGP7, "pgp7", "@"), ARGPARSE_s_n (oPGP8, "pgp8", "@"), ARGPARSE_s_n (oRFC2440Text, "rfc2440-text", "@"), ARGPARSE_s_n (oNoRFC2440Text, "no-rfc2440-text", "@"), ARGPARSE_s_i (oS2KMode, "s2k-mode", "@"), ARGPARSE_s_s (oS2KDigest, "s2k-digest-algo", "@"), ARGPARSE_s_s (oS2KCipher, "s2k-cipher-algo", "@"), ARGPARSE_s_i (oS2KCount, "s2k-count", "@"), ARGPARSE_s_s (oCipherAlgo, "cipher-algo", "@"), ARGPARSE_s_s (oAEADAlgo, "aead-algo", "@"), ARGPARSE_s_s (oDigestAlgo, "digest-algo", "@"), ARGPARSE_s_s (oCertDigestAlgo, "cert-digest-algo", "@"), ARGPARSE_s_s (oCompressAlgo,"compress-algo", "@"), ARGPARSE_s_s (oCompressAlgo, "compression-algo", "@"), /* Alias */ ARGPARSE_s_n (oThrowKeyids, "throw-keyids", "@"), ARGPARSE_s_n (oNoThrowKeyids, "no-throw-keyids", "@"), ARGPARSE_s_n (oShowPhotos, "show-photos", "@"), ARGPARSE_s_n (oNoShowPhotos, "no-show-photos", "@"), ARGPARSE_s_s (oPhotoViewer, "photo-viewer", "@"), ARGPARSE_s_s (oSetNotation, "set-notation", "@"), ARGPARSE_s_s (oSigNotation, "sig-notation", "@"), ARGPARSE_s_s (oCertNotation, "cert-notation", "@"), ARGPARSE_s_s (oKnownNotation, "known-notation", "@"), ARGPARSE_group (302, N_( "@\n(See the man page for a complete listing of all commands and options)\n" )), ARGPARSE_group (303, N_("@\nExamples:\n\n" " -se -r Bob [file] sign and encrypt for user Bob\n" " --clear-sign [file] make a clear text signature\n" " --detach-sign [file] make a detached signature\n" " --list-keys [names] show keys\n" " --fingerprint [names] show fingerprints\n")), /* More hidden commands and options. */ ARGPARSE_c (aPrintMDs, "print-mds", "@"), /* old */ #ifndef NO_TRUST_MODELS ARGPARSE_c (aListTrustDB, "list-trustdb", "@"), #endif /* Not yet used: ARGPARSE_c (aListTrustPath, "list-trust-path", "@"), */ ARGPARSE_c (aDeleteSecretAndPublicKeys, "delete-secret-and-public-keys", "@"), ARGPARSE_c (aRebuildKeydbCaches, "rebuild-keydb-caches", "@"), ARGPARSE_o_s (oPassphrase, "passphrase", "@"), ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"), ARGPARSE_s_s (oPassphraseFile, "passphrase-file", "@"), ARGPARSE_s_i (oPassphraseRepeat,"passphrase-repeat", "@"), ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"), ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"), ARGPARSE_s_i (oCommandFD, "command-fd", "@"), ARGPARSE_s_s (oCommandFile, "command-file", "@"), ARGPARSE_s_n (oQuickRandom, "debug-quick-random", "@"), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), #ifndef NO_TRUST_MODELS ARGPARSE_s_s (oTrustDBName, "trustdb-name", "@"), ARGPARSE_s_n (oAutoCheckTrustDB, "auto-check-trustdb", "@"), ARGPARSE_s_n (oNoAutoCheckTrustDB, "no-auto-check-trustdb", "@"), ARGPARSE_s_s (oForceOwnertrust, "force-ownertrust", "@"), #endif ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), ARGPARSE_s_n (oRequireSecmem, "require-secmem", "@"), ARGPARSE_s_n (oNoRequireSecmem, "no-require-secmem", "@"), ARGPARSE_s_n (oNoPermissionWarn, "no-permission-warning", "@"), ARGPARSE_s_n (oNoArmor, "no-armor", "@"), ARGPARSE_s_n (oNoArmor, "no-armour", "@"), ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"), ARGPARSE_s_n (oNoKeyring, "no-keyring", "@"), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), - ARGPARSE_s_n (oNoOptions, "no-options", "@"), + ARGPARSE_noconffile (oNoOptions, "no-options", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_n (oNoBatch, "no-batch", "@"), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oWithTofuInfo,"with-tofu-info", "@"), ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"), ARGPARSE_s_n (oWithSigList,"with-sig-list", "@"), ARGPARSE_s_n (oWithSigCheck,"with-sig-check", "@"), ARGPARSE_c (aListKeys, "list-key", "@"), /* alias */ ARGPARSE_c (aListSigs, "list-sig", "@"), /* alias */ ARGPARSE_c (aCheckKeys, "check-sig", "@"), /* alias */ ARGPARSE_c (aShowKeys, "show-key", "@"), /* alias */ ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"), ARGPARSE_s_n (oSkipHiddenRecipients, "skip-hidden-recipients", "@"), ARGPARSE_s_n (oNoSkipHiddenRecipients, "no-skip-hidden-recipients", "@"), ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */ #ifndef NO_TRUST_MODELS ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"), #endif ARGPARSE_s_s (oTrustModel, "trust-model", "@"), ARGPARSE_s_s (oTOFUDefaultPolicy, "tofu-default-policy", "@"), ARGPARSE_s_s (oSetFilename, "set-filename", "@"), ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"), ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"), ARGPARSE_s_s (oSetPolicyURL, "set-policy-url", "@"), ARGPARSE_s_s (oSigPolicyURL, "sig-policy-url", "@"), ARGPARSE_s_s (oCertPolicyURL, "cert-policy-url", "@"), ARGPARSE_s_n (oShowPolicyURL, "show-policy-url", "@"), ARGPARSE_s_n (oNoShowPolicyURL, "no-show-policy-url", "@"), ARGPARSE_s_s (oSigKeyserverURL, "sig-keyserver-url", "@"), ARGPARSE_s_n (oShowNotation, "show-notation", "@"), ARGPARSE_s_n (oNoShowNotation, "no-show-notation", "@"), ARGPARSE_s_s (oComment, "comment", "@"), ARGPARSE_s_n (oDefaultComment, "default-comment", "@"), ARGPARSE_s_n (oNoComments, "no-comments", "@"), ARGPARSE_s_n (oEmitVersion, "emit-version", "@"), ARGPARSE_s_n (oNoEmitVersion, "no-emit-version", "@"), ARGPARSE_s_n (oNoEmitVersion, "no-version", "@"), /* alias */ ARGPARSE_s_n (oNotDashEscaped, "not-dash-escaped", "@"), ARGPARSE_s_n (oEscapeFrom, "escape-from-lines", "@"), ARGPARSE_s_n (oNoEscapeFrom, "no-escape-from-lines", "@"), ARGPARSE_s_n (oLockOnce, "lock-once", "@"), ARGPARSE_s_n (oLockMultiple, "lock-multiple", "@"), ARGPARSE_s_n (oLockNever, "lock-never", "@"), ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), ARGPARSE_s_s (oLoggerFile, "log-file", "@"), ARGPARSE_s_s (oLoggerFile, "logger-file", "@"), /* 1.4 compatibility. */ ARGPARSE_s_n (oUseEmbeddedFilename, "use-embedded-filename", "@"), ARGPARSE_s_n (oNoUseEmbeddedFilename, "no-use-embedded-filename", "@"), ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"), ARGPARSE_s_n (oNoUtf8Strings, "no-utf8-strings", "@"), ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"), ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprint", "@"), ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"), ARGPARSE_s_n (oWithICAOSpelling, "with-icao-spelling", "@"), ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"), ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"), ARGPARSE_s_n (oWithSecret, "with-secret", "@"), ARGPARSE_s_n (oWithWKDHash, "with-wkd-hash", "@"), ARGPARSE_s_n (oWithKeyOrigin, "with-key-origin", "@"), ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"), ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"), ARGPARSE_s_n (oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", "@"), ARGPARSE_s_n (oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", "@"), ARGPARSE_s_n (oAllowFreeformUID, "allow-freeform-uid", "@"), ARGPARSE_s_n (oNoAllowFreeformUID, "no-allow-freeform-uid", "@"), ARGPARSE_s_n (oNoLiteral, "no-literal", "@"), ARGPARSE_p_u (oSetFilesize, "set-filesize", "@"), ARGPARSE_s_n (oFastListMode, "fast-list-mode", "@"), ARGPARSE_s_n (oFixedListMode, "fixed-list-mode", "@"), ARGPARSE_s_n (oLegacyListMode, "legacy-list-mode", "@"), ARGPARSE_s_n (oListOnly, "list-only", "@"), ARGPARSE_s_n (oPrintPKARecords, "print-pka-records", "@"), ARGPARSE_s_n (oPrintDANERecords, "print-dane-records", "@"), ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), ARGPARSE_s_n (oIgnoreValidFrom, "ignore-valid-from", "@"), ARGPARSE_s_n (oIgnoreCrcError, "ignore-crc-error", "@"), ARGPARSE_s_n (oIgnoreMDCError, "ignore-mdc-error", "@"), ARGPARSE_s_n (oShowSessionKey, "show-session-key", "@"), ARGPARSE_s_s (oOverrideSessionKey, "override-session-key", "@"), ARGPARSE_s_i (oOverrideSessionKeyFD, "override-session-key-fd", "@"), ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), ARGPARSE_s_n (oAutoKeyRetrieve, "auto-key-retrieve", "@"), ARGPARSE_s_n (oNoAutoKeyRetrieve, "no-auto-key-retrieve", "@"), ARGPARSE_s_n (oNoSigCache, "no-sig-cache", "@"), ARGPARSE_s_n (oMergeOnly, "merge-only", "@" ), ARGPARSE_s_n (oAllowSecretKeyImport, "allow-secret-key-import", "@"), ARGPARSE_s_n (oTryAllSecrets, "try-all-secrets", "@"), ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), ARGPARSE_s_n (oNoExpensiveTrustChecks, "no-expensive-trust-checks", "@"), ARGPARSE_s_n (oPreservePermissions, "preserve-permissions", "@"), ARGPARSE_s_s (oDefaultPreferenceList, "default-preference-list", "@"), ARGPARSE_s_s (oDefaultKeyserverURL, "default-keyserver-url", "@"), ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-preferences","@"), ARGPARSE_s_s (oPersonalAEADPreferences, "personal-aead-preferences","@"), ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-preferences","@"), ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-preferences", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_s (oWeakDigest, "weak-digest","@"), ARGPARSE_s_n (oUnwrap, "unwrap", "@"), ARGPARSE_s_n (oOnlySignTextIDs, "only-sign-text-ids", "@"), /* Aliases. I constantly mistype these, and assume other people do as well. */ ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-prefs", "@"), ARGPARSE_s_s (oPersonalAEADPreferences, "personal-aead-prefs", "@"), ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-prefs", "@"), ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_s (oGroup, "group", "@"), ARGPARSE_s_s (oUnGroup, "ungroup", "@"), ARGPARSE_s_n (oNoGroups, "no-groups", "@"), ARGPARSE_s_n (oStrict, "strict", "@"), ARGPARSE_s_n (oNoStrict, "no-strict", "@"), ARGPARSE_s_n (oMangleDosFilenames, "mangle-dos-filenames", "@"), ARGPARSE_s_n (oNoMangleDosFilenames, "no-mangle-dos-filenames", "@"), ARGPARSE_s_n (oEnableProgressFilter, "enable-progress-filter", "@"), ARGPARSE_s_n (oMultifile, "multifile", "@"), ARGPARSE_s_s (oKeyidFormat, "keyid-format", "@"), ARGPARSE_s_n (oExitOnStatusWriteError, "exit-on-status-write-error", "@"), ARGPARSE_s_i (oLimitCardInsertTries, "limit-card-insert-tries", "@"), ARGPARSE_s_n (oEnableLargeRSA, "enable-large-rsa", "@"), ARGPARSE_s_n (oDisableLargeRSA, "disable-large-rsa", "@"), ARGPARSE_s_n (oEnableDSA2, "enable-dsa2", "@"), ARGPARSE_s_n (oDisableDSA2, "disable-dsa2", "@"), ARGPARSE_s_n (oAllowWeakDigestAlgos, "allow-weak-digest-algos", "@"), ARGPARSE_s_s (oDefaultNewKeyAlgo, "default-new-key-algo", "@"), /* These two are aliases to help users of the PGP command line product use gpg with minimal pain. Many commands are common already as they seem to have borrowed commands from us. Now I'm returning the favor. */ ARGPARSE_s_s (oLocalUser, "sign-with", "@"), ARGPARSE_s_s (oRecipient, "user", "@"), ARGPARSE_s_n (oRequireCrossCert, "require-backsigs", "@"), ARGPARSE_s_n (oRequireCrossCert, "require-cross-certification", "@"), ARGPARSE_s_n (oNoRequireCrossCert, "no-require-backsigs", "@"), ARGPARSE_s_n (oNoRequireCrossCert, "no-require-cross-certification", "@"), /* New options. Fixme: Should go more to the top. */ ARGPARSE_s_s (oAutoKeyLocate, "auto-key-locate", "@"), ARGPARSE_s_n (oNoAutoKeyLocate, "no-auto-key-locate", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_n (oNoSymkeyCache, "no-symkey-cache", "@"), ARGPARSE_s_n (oUseKeyboxd, "use-keyboxd", "@"), ARGPARSE_s_n (oFullTimestrings, "full-timestrings", "@"), /* Options to override new security defaults. */ ARGPARSE_s_n (oAllowWeakKeySignatures, "allow-weak-key-signatures", "@"), /* Options which can be used in special circumstances. They are not * published and we hope they are never required. */ ARGPARSE_s_n (oUseOnlyOpenPGPCard, "use-only-openpgp-card", "@"), /* Dummy options with warnings. */ ARGPARSE_s_n (oUseAgent, "use-agent", "@"), ARGPARSE_s_n (oNoUseAgent, "no-use-agent", "@"), ARGPARSE_s_s (oGpgAgentInfo, "gpg-agent-info", "@"), ARGPARSE_s_s (oReaderPort, "reader-port", "@"), ARGPARSE_s_s (octapiDriver, "ctapi-driver", "@"), ARGPARSE_s_s (opcscDriver, "pcsc-driver", "@"), ARGPARSE_s_n (oDisableCCID, "disable-ccid", "@"), ARGPARSE_s_n (oHonorHttpProxy, "honor-http-proxy", "@"), ARGPARSE_s_s (oTOFUDBFormat, "tofu-db-format", "@"), /* Dummy options. */ ARGPARSE_s_n (oNoop, "sk-comments", "@"), ARGPARSE_s_n (oNoop, "no-sk-comments", "@"), ARGPARSE_s_n (oNoop, "compress-keys", "@"), ARGPARSE_s_n (oNoop, "compress-sigs", "@"), ARGPARSE_s_n (oNoop, "force-v3-sigs", "@"), ARGPARSE_s_n (oNoop, "no-force-v3-sigs", "@"), ARGPARSE_s_n (oNoop, "force-v4-certs", "@"), ARGPARSE_s_n (oNoop, "no-force-v4-certs", "@"), ARGPARSE_s_n (oNoop, "no-mdc-warning", "@"), ARGPARSE_s_n (oNoop, "force-mdc", "@"), ARGPARSE_s_n (oNoop, "no-force-mdc", "@"), ARGPARSE_s_n (oNoop, "disable-mdc", "@"), ARGPARSE_s_n (oNoop, "no-disable-mdc", "@"), ARGPARSE_s_n (oNoop, "allow-multisig-verification", "@"), ARGPARSE_s_n (oNoop, "allow-multiple-messages", "@"), ARGPARSE_s_n (oNoop, "no-allow-multiple-messages", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_PACKET_VALUE , "packet" }, { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_FILTER_VALUE , "filter" }, { DBG_IOBUF_VALUE , "iobuf" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_TRUST_VALUE , "trust" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_CLOCK_VALUE , "clock" }, { DBG_LOOKUP_VALUE , "lookup" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; #ifdef ENABLE_SELINUX_HACKS #define ALWAYS_ADD_KEYRINGS 1 #else #define ALWAYS_ADD_KEYRINGS 0 #endif /* The list of the default AKL methods. */ #define DEFAULT_AKL_LIST "local,wkd" int g10_errors_seen = 0; static int utf8_strings = 0; static int maybe_setuid = 1; static unsigned int opt_set_iobuf_size; static unsigned int opt_set_iobuf_size_used; static char *build_list( const char *text, char letter, const char *(*mapf)(int), int (*chkf)(int) ); static void set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ); static void print_mds( const char *fname, int algo ); static void add_notation_data( const char *string, int which ); static void add_policy_url( const char *string, int which ); static void add_keyserver_url( const char *string, int which ); static void emergency_cleanup (void); static void read_sessionkey_from_fd (int fd); /* NPth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; if (maybe_setuid) { gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ maybe_setuid = 0; } s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } static int build_list_pk_test_algo (int algo) { /* Show only one "RSA" string. If RSA_E or RSA_S is available RSA is also available. */ if (algo == PUBKEY_ALGO_RSA_E || algo == PUBKEY_ALGO_RSA_S) return GPG_ERR_DIGEST_ALGO; return openpgp_pk_test_algo (algo); } static const char * build_list_pk_algo_name (int algo) { return openpgp_pk_algo_name (algo); } static int build_list_cipher_test_algo (int algo) { return openpgp_cipher_test_algo (algo); } static const char * build_list_cipher_algo_name (int algo) { return openpgp_cipher_algo_name (algo); } static int build_list_aead_test_algo (int algo) { return openpgp_aead_test_algo (algo); } static const char * build_list_aead_algo_name (int algo) { return openpgp_aead_algo_name (algo); } static int build_list_md_test_algo (int algo) { /* By default we do not accept MD5 based signatures. To avoid confusion we do not announce support for it either. */ if (algo == DIGEST_ALGO_MD5) return GPG_ERR_DIGEST_ALGO; return openpgp_md_test_algo (algo); } static const char * build_list_md_algo_name (int algo) { return openpgp_md_algo_name (algo); } static const char * my_strusage( int level ) { static char *digests, *pubkeys, *ciphers, *zips, *aeads, *ver_gcry; const char *p; - switch( level ) { + switch( level ) { + case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPG@ (@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 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; #ifdef IS_DEVELOPMENT_VERSION case 25: p="NOTE: THIS IS A DEVELOPMENT VERSION!"; break; case 26: p="It is only intended for test purposes and should NOT be"; break; case 27: p="used in a production environment or with production keys!"; break; #endif case 1: case 40: p = _("Usage: @GPG@ [options] [files] (-h for help)"); break; case 41: p = _("Syntax: @GPG@ [options] [files]\n" "Sign, check, encrypt or decrypt\n" "Default operation depends on the input data\n"); break; case 31: p = "\nHome: "; break; #ifndef __riscos__ case 32: p = gnupg_homedir (); break; #else /* __riscos__ */ case 32: p = make_filename(gnupg_homedir (), NULL); break; #endif /* __riscos__ */ case 33: p = _("\nSupported algorithms:\n"); break; case 34: if (!pubkeys) pubkeys = build_list (_("Pubkey: "), 1, build_list_pk_algo_name, build_list_pk_test_algo ); p = pubkeys; break; case 35: if( !ciphers ) ciphers = build_list(_("Cipher: "), 'S', build_list_cipher_algo_name, build_list_cipher_test_algo ); p = ciphers; break; case 36: if (!aeads) aeads = build_list ("AEAD: ", 'A', build_list_aead_algo_name, build_list_aead_test_algo); p = aeads; break; case 37: if( !digests ) digests = build_list(_("Hash: "), 'H', build_list_md_algo_name, build_list_md_test_algo ); p = digests; break; case 38: if( !zips ) zips = build_list(_("Compression: "),'Z', compress_algo_to_string, check_compress_algo); p = zips; break; default: p = NULL; } return p; } static char * build_list (const char *text, char letter, const char * (*mapf)(int), int (*chkf)(int)) { membuf_t mb; int indent; int i, j, len; int limit; const char *s; char *string; if (maybe_setuid) gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ indent = utf8_charcount (text, -1); len = 0; init_membuf (&mb, 512); limit = (letter == 'A')? 4 : 110; for (i=0; i <= limit; i++ ) { if (!chkf (i) && (s = mapf (i))) { if (mb.len - len > 60) { put_membuf_str (&mb, ",\n"); len = mb.len; for (j=0; j < indent; j++) put_membuf_str (&mb, " "); } else if (mb.len) put_membuf_str (&mb, ", "); else put_membuf_str (&mb, text); put_membuf_str (&mb, s); if (opt.verbose && letter) { char num[20]; if (letter == 1) snprintf (num, sizeof num, " (%d)", i); else snprintf (num, sizeof num, " (%c%d)", letter, i); put_membuf_str (&mb, num); } } } if (mb.len) put_membuf_str (&mb, "\n"); put_membuf (&mb, "", 1); string = get_membuf (&mb, NULL); return xrealloc (string, strlen (string)+1); } static void wrong_args( const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), GPG_NAME, text); log_inc_errorcount (); g10_exit(2); } static char * make_username( const char *string ) { char *p; if( utf8_strings ) p = xstrdup(string); else p = native_to_utf8( string ); return p; } static void set_opt_session_env (const char *name, const char *value) { gpg_error_t err; err = session_env_setenv (opt.session_env, name, value); if (err) log_fatal ("error setting session environment: %s\n", gpg_strerror (err)); } /* 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 (const char *level) { int numok = (level && digitp (level)); int numlvl = numok? atoi (level) : 0; if (!level) ; else if (!strcmp (level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_MEMSTAT_VALUE; else if (!strcmp (level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE; else if (!strcmp (level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE |DBG_CACHE_VALUE|DBG_LOOKUP|DBG_FILTER_VALUE|DBG_PACKET_VALUE); else if (!strcmp (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"), level); g10_exit (2); } if ((opt.debug & DBG_MEMORY_VALUE)) memory_debug_mode = 1; if ((opt.debug & DBG_MEMSTAT_VALUE)) memory_stat_debug_mode = 1; if (DBG_MPI) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (DBG_CRYPTO) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); if ((opt.debug & DBG_IOBUF_VALUE)) iobuf_debug_mode = 1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); if (opt_set_iobuf_size || opt_set_iobuf_size_used) log_debug ("iobuf buffer size is %uk\n", iobuf_set_buffer_size (opt_set_iobuf_size)); } /* We set the screen dimensions for UI purposes. Do not allow screens smaller than 80x24 for the sake of simplicity. */ static void set_screen_dimensions(void) { #ifndef HAVE_W32_SYSTEM char *str; str=getenv("COLUMNS"); if(str) opt.screen_columns=atoi(str); str=getenv("LINES"); if(str) opt.screen_lines=atoi(str); #endif if(opt.screen_columns<80 || opt.screen_columns>255) opt.screen_columns=80; if(opt.screen_lines<24 || opt.screen_lines>255) opt.screen_lines=24; } /* Helper to open a file FNAME either for reading or writing to be used with --status-file etc functions. Not generally useful but it avoids the riscos specific functions and well some Windows people might like it too. Prints an error message and returns -1 on error. On success the file descriptor is returned. */ static int open_info_file (const char *fname, int for_write, int binary) { #ifdef __riscos__ return riscos_fdopenfile (fname, for_write); #elif defined (ENABLE_SELINUX_HACKS) /* We can't allow these even when testing for a secured filename because files to be secured might not yet been secured. This is similar to the option file but in that case it is unlikely that sensitive information may be retrieved by means of error messages. */ (void)fname; (void)for_write; (void)binary; return -1; #else int fd; if (binary) binary = MY_O_BINARY; /* if (is_secured_filename (fname)) */ /* { */ /* fd = -1; */ /* gpg_err_set_errno (EPERM); */ /* } */ /* else */ /* { */ do { if (for_write) fd = open (fname, O_CREAT | O_TRUNC | O_WRONLY | binary, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); else fd = open (fname, O_RDONLY | binary); } while (fd == -1 && errno == EINTR); /* } */ if ( fd == -1) log_error ( for_write? _("can't create '%s': %s\n") : _("can't open '%s': %s\n"), fname, strerror(errno)); return fd; #endif } static void set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ) { enum cmd_and_opt_values cmd = *ret_cmd; if( !cmd || cmd == new_cmd ) cmd = new_cmd; else if( cmd == aSign && new_cmd == aEncr ) cmd = aSignEncr; else if( cmd == aEncr && new_cmd == aSign ) cmd = aSignEncr; else if( cmd == aSign && new_cmd == aSym ) cmd = aSignSym; else if( cmd == aSym && new_cmd == aSign ) cmd = aSignSym; else if( cmd == aSym && new_cmd == aEncr ) cmd = aEncrSym; else if( cmd == aEncr && new_cmd == aSym ) cmd = aEncrSym; else if (cmd == aSignEncr && new_cmd == aSym) cmd = aSignEncrSym; else if (cmd == aSignSym && new_cmd == aEncr) cmd = aSignEncrSym; else if (cmd == aEncrSym && new_cmd == aSign) cmd = aSignEncrSym; else if( ( cmd == aSign && new_cmd == aClearsign ) || ( cmd == aClearsign && new_cmd == aSign ) ) cmd = aClearsign; else { log_error(_("conflicting commands\n")); g10_exit(2); } *ret_cmd = cmd; } static void add_group(char *string) { char *name,*value; struct groupitem *item; /* Break off the group name */ name=strsep(&string,"="); if(string==NULL) { log_error(_("no = sign found in group definition '%s'\n"),name); return; } trim_trailing_ws(name,strlen(name)); /* Does this group already exist? */ for(item=opt.grouplist;item;item=item->next) if(strcasecmp(item->name,name)==0) break; if(!item) { item=xmalloc(sizeof(struct groupitem)); item->name=name; item->next=opt.grouplist; item->values=NULL; opt.grouplist=item; } /* Break apart the values */ while ((value= strsep(&string," \t"))) { if (*value) add_to_strlist2(&item->values,value,utf8_strings); } } static void rm_group(char *name) { struct groupitem *item,*last=NULL; trim_trailing_ws(name,strlen(name)); for(item=opt.grouplist;item;last=item,item=item->next) { if(strcasecmp(item->name,name)==0) { if(last) last->next=item->next; else opt.grouplist=item->next; free_strlist(item->values); xfree(item); break; } } } /* We need to check three things. 0) The homedir. It must be x00, a directory, and owned by the user. 1) The options/gpg.conf file. Okay unless it or its containing directory is group or other writable or not owned by us. Disable exec in this case. 2) Extensions. Same as #1. Returns true if the item is unsafe. */ static int check_permissions (const char *path, int item) { #if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM) static int homedir_cache=-1; char *tmppath,*dir; struct stat statbuf,dirbuf; int homedir=0,ret=0,checkonly=0; int perm=0,own=0,enc_dir_perm=0,enc_dir_own=0; if(opt.no_perm_warn) return 0; log_assert(item==0 || item==1 || item==2); /* extensions may attach a path */ if(item==2 && path[0]!=DIRSEP_C) { if(strchr(path,DIRSEP_C)) tmppath=make_filename(path,NULL); else tmppath=make_filename(gnupg_libdir (),path,NULL); } else tmppath=xstrdup(path); /* If the item is located in the homedir, but isn't the homedir, don't continue if we already checked the homedir itself. This is to avoid user confusion with an extra options file warning which could be rectified if the homedir itself had proper permissions. */ if(item!=0 && homedir_cache>-1 && !ascii_strncasecmp (gnupg_homedir (), tmppath, strlen (gnupg_homedir ()))) { ret=homedir_cache; goto end; } /* It's okay if the file or directory doesn't exist */ if(stat(tmppath,&statbuf)!=0) { ret=0; goto end; } /* Now check the enclosing directory. Theoretically, we could walk this test up to the root directory /, but for the sake of sanity, I'm stopping at one level down. */ dir=make_dirname(tmppath); if(stat(dir,&dirbuf)!=0 || !S_ISDIR(dirbuf.st_mode)) { /* Weird error */ ret=1; goto end; } xfree(dir); /* Assume failure */ ret=1; if(item==0) { /* The homedir must be x00, a directory, and owned by the user. */ if(S_ISDIR(statbuf.st_mode)) { if(statbuf.st_uid==getuid()) { if((statbuf.st_mode & (S_IRWXG|S_IRWXO))==0) ret=0; else perm=1; } else own=1; homedir_cache=ret; } } else if(item==1 || item==2) { /* The options or extension file. Okay unless it or its containing directory is group or other writable or not owned by us or root. */ if(S_ISREG(statbuf.st_mode)) { if(statbuf.st_uid==getuid() || statbuf.st_uid==0) { if((statbuf.st_mode & (S_IWGRP|S_IWOTH))==0) { /* it's not writable, so make sure the enclosing directory is also not writable */ if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) { if((dirbuf.st_mode & (S_IWGRP|S_IWOTH))==0) ret=0; else enc_dir_perm=1; } else enc_dir_own=1; } else { /* it's writable, so the enclosing directory had better not let people get to it. */ if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) { if((dirbuf.st_mode & (S_IRWXG|S_IRWXO))==0) ret=0; else perm=enc_dir_perm=1; /* unclear which one to fix! */ } else enc_dir_own=1; } } else own=1; } } else BUG(); if(!checkonly) { if(own) { if(item==0) log_info(_("WARNING: unsafe ownership on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe ownership on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe ownership on" " extension '%s'\n"),tmppath); } if(perm) { if(item==0) log_info(_("WARNING: unsafe permissions on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe permissions on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe permissions on" " extension '%s'\n"),tmppath); } if(enc_dir_own) { if(item==0) log_info(_("WARNING: unsafe enclosing directory ownership on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe enclosing directory ownership on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe enclosing directory ownership on" " extension '%s'\n"),tmppath); } if(enc_dir_perm) { if(item==0) log_info(_("WARNING: unsafe enclosing directory permissions on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe enclosing directory permissions on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe enclosing directory permissions on" " extension '%s'\n"),tmppath); } } end: xfree(tmppath); if(homedir) homedir_cache=ret; return ret; #else /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/ (void)path; (void)item; return 0; #endif /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/ } /* Print the OpenPGP defined algo numbers. */ static void print_algo_numbers(int (*checker)(int)) { int i,first=1; for(i=0;i<=110;i++) { if(!checker(i)) { if(first) first=0; else es_printf (";"); es_printf ("%d",i); } } } static void print_algo_names(int (*checker)(int),const char *(*mapper)(int)) { int i,first=1; for(i=0;i<=110;i++) { if(!checker(i)) { if(first) first=0; else es_printf (";"); es_printf ("%s",mapper(i)); } } } /* In the future, we can do all sorts of interesting configuration output here. For now, just give "group" as the Enigmail folks need it, and pubkey, cipher, hash, and compress as they may be useful for frontends. */ static void list_config(char *items) { int show_all = !items; char *name = NULL; const char *s; struct groupitem *giter; int first, iter; if(!opt.with_colons) return; while(show_all || (name=strsep(&items," "))) { int any=0; if(show_all || ascii_strcasecmp(name,"group")==0) { for (giter = opt.grouplist; giter; giter = giter->next) { strlist_t sl; es_fprintf (es_stdout, "cfg:group:"); es_write_sanitized (es_stdout, giter->name, strlen(giter->name), ":", NULL); es_putc (':', es_stdout); for(sl=giter->values; sl; sl=sl->next) { es_write_sanitized (es_stdout, sl->d, strlen (sl->d), ":;", NULL); if(sl->next) es_printf(";"); } es_printf("\n"); } any=1; } if(show_all || ascii_strcasecmp(name,"version")==0) { es_printf("cfg:version:"); es_write_sanitized (es_stdout, VERSION, strlen(VERSION), ":", NULL); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"pubkey")==0) { es_printf ("cfg:pubkey:"); print_algo_numbers (build_list_pk_test_algo); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"pubkeyname")==0) { es_printf ("cfg:pubkeyname:"); print_algo_names (build_list_pk_test_algo, build_list_pk_algo_name); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"cipher")==0) { es_printf ("cfg:cipher:"); print_algo_numbers (build_list_cipher_test_algo); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp (name,"ciphername")) { es_printf ("cfg:ciphername:"); print_algo_names (build_list_cipher_test_algo, build_list_cipher_algo_name); es_printf ("\n"); any = 1; } if(show_all || ascii_strcasecmp(name,"digest")==0 || ascii_strcasecmp(name,"hash")==0) { es_printf ("cfg:digest:"); print_algo_numbers (build_list_md_test_algo); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp(name,"digestname") || !ascii_strcasecmp(name,"hashname")) { es_printf ("cfg:digestname:"); print_algo_names (build_list_md_test_algo, build_list_md_algo_name); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"compress")==0) { es_printf ("cfg:compress:"); print_algo_numbers(check_compress_algo); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp (name, "compressname") == 0) { es_printf ("cfg:compressname:"); print_algo_names (check_compress_algo, compress_algo_to_string); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp(name,"ccid-reader-id")) { /* We ignore this for GnuPG 1.4 backward compatibility. */ any=1; } if (show_all || !ascii_strcasecmp (name,"curve")) { es_printf ("cfg:curve:"); for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first=0) es_printf ("%s%s", first?"":";", s); es_printf ("\n"); any=1; } /* Curve OIDs are rarely useful and thus only printed if requested. */ if (name && !ascii_strcasecmp (name,"curveoid")) { es_printf ("cfg:curveoid:"); for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first = 0) { s = openpgp_curve_to_oid (s, NULL, NULL); es_printf ("%s%s", first?"":";", s? s:"[?]"); } es_printf ("\n"); any=1; } if(show_all) break; if(!any) log_error(_("unknown configuration item '%s'\n"),name); } } /* List options and default values in the GPG Conf format. This is a new tool distributed with gnupg 1.9.x but we also want some limited support in older gpg versions. The output is the name of the configuration file and a list of options available for editing by gpgconf. */ static void gpgconf_list (const char *configfile) { char *configfile_esc = percent_escape (configfile, NULL); es_printf ("%s-%s.conf:%lu:\"%s\n", GPGCONF_NAME, GPG_NAME, GC_OPT_FLAG_DEFAULT, configfile_esc ? configfile_esc : "/dev/null"); es_printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("default-key:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("try-secret-key:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("auto-key-locate:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("auto-key-retrieve:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); es_printf ("group:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("compliance:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "gnupg"); es_printf ("default-new-key-algo:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("trust-model:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("disable-dirmngr:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("max-cert-depth:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("completes-needed:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("marginals-needed:%lu:\n", GC_OPT_FLAG_NONE); /* The next one is an info only item and should match the macros at the top of keygen.c */ es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, get_default_pubkey_algo ()); xfree (configfile_esc); } static int parse_subpacket_list(char *list) { char *tok; byte subpackets[128],i; int count=0; if(!list) { /* No arguments means all subpackets */ memset(subpackets+1,1,sizeof(subpackets)-1); count=127; } else { memset(subpackets,0,sizeof(subpackets)); /* Merge with earlier copy */ if(opt.show_subpackets) { byte *in; for(in=opt.show_subpackets;*in;in++) { if(*in>127 || *in<1) BUG(); if(!subpackets[*in]) count++; subpackets[*in]=1; } } while((tok=strsep(&list," ,"))) { if(!*tok) continue; i=atoi(tok); if(i>127 || i<1) return 0; if(!subpackets[i]) count++; subpackets[i]=1; } } xfree(opt.show_subpackets); opt.show_subpackets=xmalloc(count+1); opt.show_subpackets[count--]=0; for(i=1;i<128 && count>=0;i++) if(subpackets[i]) opt.show_subpackets[count--]=i; return 1; } static int parse_list_options(char *str) { char *subpackets=""; /* something that isn't NULL */ struct parse_options lopts[]= { {"show-photos",LIST_SHOW_PHOTOS,NULL, N_("display photo IDs during key listings")}, {"show-usage",LIST_SHOW_USAGE,NULL, N_("show key usage information during key listings")}, {"show-policy-urls",LIST_SHOW_POLICY_URLS,NULL, N_("show policy URLs during signature listings")}, {"show-notations",LIST_SHOW_NOTATIONS,NULL, N_("show all notations during signature listings")}, {"show-std-notations",LIST_SHOW_STD_NOTATIONS,NULL, N_("show IETF standard notations during signature listings")}, {"show-standard-notations",LIST_SHOW_STD_NOTATIONS,NULL, NULL}, {"show-user-notations",LIST_SHOW_USER_NOTATIONS,NULL, N_("show user-supplied notations during signature listings")}, {"show-keyserver-urls",LIST_SHOW_KEYSERVER_URLS,NULL, N_("show preferred keyserver URLs during signature listings")}, {"show-uid-validity",LIST_SHOW_UID_VALIDITY,NULL, N_("show user ID validity during key listings")}, {"show-unusable-uids",LIST_SHOW_UNUSABLE_UIDS,NULL, N_("show revoked and expired user IDs in key listings")}, {"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL, N_("show revoked and expired subkeys in key listings")}, {"show-keyring",LIST_SHOW_KEYRING,NULL, N_("show the keyring name in key listings")}, {"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL, N_("show expiration dates during signature listings")}, {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, NULL}, {"show-only-fpr-mbox",LIST_SHOW_ONLY_FPR_MBOX, NULL, NULL}, {NULL,0,NULL,NULL} }; /* C99 allows for non-constant initializers, but we'd like to compile everywhere, so fill in the show-sig-subpackets argument here. Note that if the parse_options array changes, we'll have to change the subscript here. */ lopts[13].value=&subpackets; if(parse_options(str,&opt.list_options,lopts,1)) { if(opt.list_options&LIST_SHOW_SIG_SUBPACKETS) { /* Unset so users can pass multiple lists in. */ opt.list_options&=~LIST_SHOW_SIG_SUBPACKETS; if(!parse_subpacket_list(subpackets)) return 0; } else if(subpackets==NULL && opt.show_subpackets) { /* User did 'no-show-subpackets' */ xfree(opt.show_subpackets); opt.show_subpackets=NULL; } return 1; } else return 0; } /* Collapses argc/argv into a single string that must be freed */ static char * collapse_args(int argc,char *argv[]) { char *str=NULL; int i,first=1,len=0; for(i=0;imagic = SERVER_CONTROL_MAGIC; } /* This function is called to deinitialize a control object. It is not deallocated. */ static void gpg_deinit_default_ctrl (ctrl_t ctrl) { #ifdef USE_TOFU tofu_closedbs (ctrl); #endif gpg_dirmngr_deinit_session_data (ctrl); keydb_release (ctrl->cached_getkey_kdb); gpg_keyboxd_deinit_session_data (ctrl); } char * get_default_configname (void) { char *configname = NULL; char *name = xstrdup (GPG_NAME EXTSEP_S "conf-" SAFE_VERSION); char *ver = &name[strlen (GPG_NAME EXTSEP_S "conf-")]; do { if (configname) { char *tok; xfree (configname); configname = NULL; if ((tok = strrchr (ver, SAFE_VERSION_DASH))) *tok='\0'; else if ((tok = strrchr (ver, SAFE_VERSION_DOT))) *tok='\0'; else break; } configname = make_filename (gnupg_homedir (), name, NULL); } while (access (configname, R_OK)); xfree(name); if (! configname) configname = make_filename (gnupg_homedir (), GPG_NAME EXTSEP_S "conf", NULL); if (! access (configname, R_OK)) { /* Print a warning when both config files are present. */ char *p = make_filename (gnupg_homedir (), "options", NULL); if (! access (p, R_OK)) log_info (_("Note: old default options file '%s' ignored\n"), p); xfree (p); } else { /* Use the old default only if it exists. */ char *p = make_filename (gnupg_homedir (), "options", NULL); if (!access (p, R_OK)) { xfree (configname); configname = p; } else xfree (p); } return configname; } + int main (int argc, char **argv) { - ARGPARSE_ARGS pargs; + gpgrt_argparse_t pargs; IOBUF a; int rc=0; int orig_argc; char **orig_argv; const char *fname; char *username; int may_coredump; strlist_t sl; strlist_t remusr = NULL; strlist_t locusr = NULL; strlist_t nrings = NULL; armor_filter_context_t *afx = NULL; int detached_sig = 0; - FILE *configfp = NULL; - char *configname = NULL; - char *save_configname = NULL; - char *default_configname = NULL; - unsigned configlineno; - int parse_debug = 0; - int default_config = 1; + char *last_configname = NULL; + const char *configname = NULL; /* NULL or points to last_configname. + * NULL also indicates that we are + * processing options from the cmdline. */ + int debug_argparser = 0; int default_keyring = 1; int greeting = 0; int nogreeting = 0; char *logfile = NULL; int use_random_seed = 1; enum cmd_and_opt_values cmd = 0; const char *debug_level = NULL; #ifndef NO_TRUST_MODELS const char *trustdb_name = NULL; #endif /*!NO_TRUST_MODELS*/ char *def_cipher_string = NULL; char *def_aead_string = NULL; char *def_digest_string = NULL; char *compress_algo_string = NULL; char *cert_digest_string = NULL; char *s2k_cipher_string = NULL; char *s2k_digest_string = NULL; char *pers_cipher_list = NULL; char *pers_aead_list = NULL; char *pers_digest_list = NULL; char *pers_compress_list = NULL; int eyes_only=0; int multifile=0; int pwfd = -1; int ovrseskeyfd = -1; int fpr_maybe_cmd = 0; /* --fingerprint maybe a command. */ int any_explicit_recipient = 0; int default_akl = 1; int require_secmem = 0; int got_secmem = 0; struct assuan_malloc_hooks malloc_hooks; ctrl_t ctrl; static int print_dane_records; static int print_pka_records; static int allow_large_chunks; #ifdef __riscos__ opt.lock_once = 1; #endif /* __riscos__ */ /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to secmem_init() somewhere after the option parsing. */ early_system_init (); gnupg_reopen_std (GPG_NAME); trap_unaligned (); gnupg_rl_initialize (); - set_strusage (my_strusage); + gpgrt_set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix (GPG_NAME, GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); /* Use our own logging handler for Libcgrypt. */ setup_libgcrypt_logging (); /* Put random number into secure memory */ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps(); gnupg_init_signals (0, emergency_cleanup); dotlock_create (NULL, 0); /* Register lock file cleanup. */ /* Tell the compliance module who we are. */ gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPG); opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); opt.command_fd = -1; /* no command fd */ opt.compress_level = -1; /* defaults to standard compress level */ opt.bz2_compress_level = -1; /* defaults to standard compress level */ /* note: if you change these lines, look at oOpenPGP */ opt.def_cipher_algo = 0; opt.def_aead_algo = 0; opt.def_digest_algo = 0; opt.cert_digest_algo = 0; opt.compress_algo = -1; /* defaults to DEFAULT_COMPRESS_ALGO */ opt.s2k_mode = 3; /* iterated+salted */ opt.s2k_count = 0; /* Auto-calibrate when needed. */ opt.s2k_cipher_algo = DEFAULT_CIPHER_ALGO; opt.completes_needed = 1; opt.marginals_needed = 3; opt.max_cert_depth = 5; opt.escape_from = 1; opt.flags.require_cross_cert = 1; opt.import_options = IMPORT_REPAIR_KEYS; opt.export_options = EXPORT_ATTRIBUTES; opt.keyserver_options.import_options = (IMPORT_REPAIR_KEYS | IMPORT_REPAIR_PKS_SUBKEY_BUG | IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN); opt.keyserver_options.export_options = EXPORT_ATTRIBUTES; opt.keyserver_options.options = KEYSERVER_HONOR_PKA_RECORD; opt.verify_options = (LIST_SHOW_UID_VALIDITY | VERIFY_SHOW_POLICY_URLS | VERIFY_SHOW_STD_NOTATIONS | VERIFY_SHOW_KEYSERVER_URLS); opt.list_options = (LIST_SHOW_UID_VALIDITY | LIST_SHOW_USAGE); #ifdef NO_TRUST_MODELS opt.trust_model = TM_ALWAYS; #else opt.trust_model = TM_AUTO; #endif opt.tofu_default_policy = TOFU_POLICY_AUTO; opt.mangle_dos_filenames = 0; opt.min_cert_level = 2; set_screen_dimensions (); opt.keyid_format = KF_NONE; opt.def_sig_expire = "0"; opt.def_cert_expire = "0"; gnupg_set_homedir (NULL); opt.passphrase_repeat = 1; opt.emit_version = 0; opt.weak_digests = NULL; opt.compliance = CO_GNUPG; opt.flags.rfc4880bis = 1; - /* Check whether we have a config file on the command line. */ + /* Check special options given on the command line. */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); - while( arg_parse( &pargs, opts) ) { - if( pargs.r_opt == oDebug || pargs.r_opt == oDebugAll ) - parse_debug++; - else if (pargs.r_opt == oDebugIOLBF) - es_setvbuf (es_stdout, NULL, _IOLBF, 0); - else if( pargs.r_opt == oOptions ) { - /* yes there is one, so we do not try the default one, but - * read the option file when it is encountered at the commandline - */ - default_config = 0; - } - else if( pargs.r_opt == oNoOptions ) + while (gpgrt_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) { - default_config = 0; /* --no-options */ + case oDebug: + case oDebugAll: + debug_argparser++; + break; + + case oDebugIOLBF: + es_setvbuf (es_stdout, NULL, _IOLBF, 0); + break; + + case oNoOptions: + /* Set here here because the homedir would otherwise be + * created before main option parsing starts. */ opt.no_homedir_creation = 1; + break; + + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + + case oNoPermissionWarn: + opt.no_perm_warn = 1; + break; } - else if( pargs.r_opt == oHomedir ) - gnupg_set_homedir (pargs.r.ret_str); - else if( pargs.r_opt == oNoPermissionWarn ) - opt.no_perm_warn=1; - else if (pargs.r_opt == oStrict ) - { - /* Not used */ - } - else if (pargs.r_opt == oNoStrict ) - { - /* Not used */ - } - } + } + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); #ifdef HAVE_DOSISH_SYSTEM + /* FIXME: Do we still need this? No: gnupg_homedir calls + * make_filename which changes the slashed anyway. IsDBCSLeadByte still + * needed? See bug #561. */ if ( strchr (gnupg_homedir (), '\\') ) { char *d, *buf = xmalloc (strlen (gnupg_homedir ())+1); const char *s; for (d=buf, s = gnupg_homedir (); *s; s++) { *d++ = *s == '\\'? '/': *s; #ifdef HAVE_W32_SYSTEM if (s[1] && IsDBCSLeadByte (*s)) *d++ = *++s; #endif } *d = 0; gnupg_set_homedir (buf); } #endif /* Initialize the secure memory. */ if (!gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0)) got_secmem = 1; #if defined(HAVE_GETUID) && defined(HAVE_GETEUID) /* There should be no way to get to this spot while still carrying setuid privs. Just in case, bomb out if we are. */ if ( getuid () != geteuid () ) BUG (); #endif maybe_setuid = 0; /* Okay, we are now working under our real uid */ /* malloc hooks go here ... */ malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Set default options which require that malloc stuff is ready. */ additional_weak_digest ("MD5"); parse_auto_key_locate (DEFAULT_AKL_LIST); - /* Try for a version specific config file first */ - default_configname = get_default_configname (); - if (default_config) - configname = xstrdup (default_configname); - argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; - pargs.flags= ARGPARSE_FLAG_KEEP; + /* We are re-using the struct, thus the reset flag. We OR the + * flags so that the internal intialized flag won't be cleared. */ + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER); + /* FIXME: Add an option to allow for version specific config files. */ /* By this point we have a homedir, and cannot change it. */ check_permissions (gnupg_homedir (), 0); - next_pass: - if( configname ) { - if(check_permissions(configname,1)) - { - /* If any options file is unsafe, then disable any external - programs for keyserver calls or photo IDs. Since the - external program to call is set in the options file, a - unsafe options file can lead to an arbitrary program - being run. */ - - opt.exec_disable=1; - } - - configlineno = 0; - configfp = fopen( configname, "r" ); - if (configfp && is_secured_file (fileno (configfp))) - { - fclose (configfp); - configfp = NULL; - gpg_err_set_errno (EPERM); - } - if( !configfp ) { - if( default_config ) { - if( parse_debug ) - log_info(_("Note: no default option file '%s'\n"), - configname ); - } - else { - log_error(_("option file '%s': %s\n"), - configname, strerror(errno) ); - g10_exit(2); - } - xfree(configname); configname = NULL; - } - if( parse_debug && configname ) - log_info(_("reading options from '%s'\n"), configname ); - default_config = 0; - } - - while( optfile_parse( configfp, configname, &configlineno, - &pargs, opts) ) + /* The configuraton directories for use by gpgrt_argparser. */ + gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); + gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); + + + /* if( configname ) { */ + /* FIXME: Add callback to check config file permissions. */ + /* if(check_permissions(configname,1)) */ + /* { */ + /* /\* If any options file is unsafe, then disable any external */ + /* programs for keyserver calls or photo IDs. Since the */ + /* external program to call is set in the options file, a */ + /* unsafe options file can lead to an arbitrary program */ + /* being run. *\/ */ + + /* opt.exec_disable=1; */ + /* } */ + + /* configlineno = 0; */ + /* configfp = fopen( configname, "r" ); */ + /* if (configfp && is_secured_file (fileno (configfp))) */ + /* { */ + /* fclose (configfp); */ + /* configfp = NULL; */ + /* gpg_err_set_errno (EPERM); */ + /* } */ + /* if( !configfp ) { */ + /* if( default_config ) { */ + /* if( parse_debug ) */ + /* log_info(_("Note: no default option file '%s'\n"), */ + /* configname ); */ + /* } */ + /* else { */ + /* log_error(_("option file '%s': %s\n"), */ + /* configname, strerror(errno) ); */ + /* g10_exit(2); */ + /* } */ + /* xfree(configname); configname = NULL; */ + /* } */ + /* if( parse_debug && configname ) */ + /* log_info(_("reading options from '%s'\n"), configname ); */ + /* default_config = 0; */ + /* } */ + + while (gpgrt_argparser (&pargs, opts, GPG_NAME EXTSEP_S "conf" )) { - switch( pargs.r_opt ) + switch (pargs.r_opt) { + case 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; + break; + + /* case oOptions: */ + /* case oNoOptions: */ + /* We will never see these options here because + * gpgrt_argparse handles them for us. */ + /* break */ + case aListConfig: case aListGcryptConfig: case aGPGConfList: case aGPGConfTest: set_cmd (&cmd, pargs.r_opt); /* Do not register a keyring for these commands. */ default_keyring = -1; break; case aCheckKeys: case aListPackets: case aImport: case aFastImport: case aSendKeys: case aRecvKeys: case aSearchKeys: case aRefreshKeys: case aFetchKeys: case aExport: #ifdef ENABLE_CARD_SUPPORT case aCardStatus: case aCardEdit: case aChangePIN: #endif /* ENABLE_CARD_SUPPORT*/ case aListKeys: case aLocateKeys: case aLocateExtKeys: case aListSigs: case aExportSecret: case aExportSecretSub: case aExportSshKey: case aSym: case aClearsign: case aGenRevoke: case aDesigRevoke: case aPrimegen: case aGenRandom: case aPrintMD: case aPrintMDs: case aListTrustDB: case aCheckTrustDB: case aUpdateTrustDB: case aFixTrustDB: case aListTrustPath: case aDeArmor: case aEnArmor: case aSign: case aQuickSignKey: case aQuickLSignKey: case aSignKey: case aLSignKey: case aStore: case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: case aQuickRevUid: case aQuickSetExpire: case aQuickSetPrimaryUid: case aExportOwnerTrust: case aImportOwnerTrust: case aRebuildKeydbCaches: set_cmd (&cmd, pargs.r_opt); break; case aKeygen: case aFullKeygen: case aEditKey: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: case aDeleteKeys: case aPasswd: set_cmd (&cmd, pargs.r_opt); greeting=1; break; case aShowKeys: set_cmd (&cmd, pargs.r_opt); opt.import_options |= IMPORT_SHOW; opt.import_options |= IMPORT_DRY_RUN; opt.import_options &= ~IMPORT_REPAIR_KEYS; opt.list_options |= LIST_SHOW_UNUSABLE_UIDS; opt.list_options |= LIST_SHOW_UNUSABLE_SUBKEYS; opt.list_options |= LIST_SHOW_NOTATIONS; opt.list_options |= LIST_SHOW_POLICY_URLS; break; case aDetachedSign: detached_sig = 1; set_cmd( &cmd, aSign ); break; case aDecryptFiles: multifile=1; /* fall through */ case aDecrypt: set_cmd( &cmd, aDecrypt); break; case aEncrFiles: multifile=1; /* fall through */ case aEncr: set_cmd( &cmd, aEncr); break; case aVerifyFiles: multifile=1; /* fall through */ case aVerify: set_cmd( &cmd, aVerify); break; case aServer: set_cmd (&cmd, pargs.r_opt); opt.batch = 1; break; case aTOFUPolicy: set_cmd (&cmd, pargs.r_opt); break; case oArmor: opt.armor = 1; opt.no_armor=0; break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break; case oInputSizeHint: opt.input_size_hint = string_to_u64 (pargs.r.ret_str); break; case oChunkSize: opt.chunk_size = pargs.r.ret_int; break; case oQuiet: opt.quiet = 1; break; case oNoTTY: tty_no_terminal(1); break; case oDryRun: opt.dry_run = 1; break; case oInteractive: opt.interactive = 1; break; case oVerbose: opt.verbose++; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); opt.list_options|=LIST_SHOW_UNUSABLE_UIDS; opt.list_options|=LIST_SHOW_UNUSABLE_SUBKEYS; break; case oBatch: opt.batch = 1; nogreeting = 1; break; case oUseAgent: /* Dummy. */ break; case oNoUseAgent: - obsolete_option (configname, configlineno, "no-use-agent"); + obsolete_option (configname, pargs.lineno, "no-use-agent"); break; case oGpgAgentInfo: - obsolete_option (configname, configlineno, "gpg-agent-info"); + obsolete_option (configname, pargs.lineno, "gpg-agent-info"); break; case oUseKeyboxd: opt.use_keyboxd = 1; break; case oReaderPort: - obsolete_scdaemon_option (configname, configlineno, "reader-port"); + obsolete_scdaemon_option (configname, pargs.lineno, "reader-port"); break; case octapiDriver: - obsolete_scdaemon_option (configname, configlineno, "ctapi-driver"); + obsolete_scdaemon_option (configname, pargs.lineno, "ctapi-driver"); break; case opcscDriver: - obsolete_scdaemon_option (configname, configlineno, "pcsc-driver"); + obsolete_scdaemon_option (configname, pargs.lineno, "pcsc-driver"); break; case oDisableCCID: - obsolete_scdaemon_option (configname, configlineno, "disable-ccid"); + obsolete_scdaemon_option (configname, pargs.lineno, "disable-ccid"); break; case oHonorHttpProxy: - obsolete_option (configname, configlineno, "honor-http-proxy"); + obsolete_option (configname, pargs.lineno, "honor-http-proxy"); break; case oAnswerYes: opt.answer_yes = 1; break; case oAnswerNo: opt.answer_no = 1; break; case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break; case oPrimaryKeyring: sl = append_to_strlist (&nrings, pargs.r.ret_str); sl->flags = KEYDB_RESOURCE_FLAG_PRIMARY; break; case oShowKeyring: - deprecated_warning(configname,configlineno,"--show-keyring", + deprecated_warning(configname,pargs.lineno,"--show-keyring", "--list-options ","show-keyring"); opt.list_options|=LIST_SHOW_KEYRING; break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugIOLBF: break; /* Already set in pre-parse step. */ case oDebugSetIobufSize: opt_set_iobuf_size = pargs.r.ret_ulong; opt_set_iobuf_size_used = 1; break; case oDebugAllowLargeChunks: allow_large_chunks = 1; break; case oStatusFD: set_status_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) ); break; case oStatusFile: set_status_fd ( open_info_file (pargs.r.ret_str, 1, 0) ); break; case oAttributeFD: set_attrib_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) ); break; case oAttributeFile: set_attrib_fd ( open_info_file (pargs.r.ret_str, 1, 1) ); break; case oLoggerFD: log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oLoggerFile: logfile = pargs.r.ret_str; break; case oWithFingerprint: opt.with_fingerprint = 1; opt.fingerprint++; break; case oWithSubkeyFingerprint: opt.with_subkey_fingerprint = 1; break; case oWithICAOSpelling: opt.with_icao_spelling = 1; break; case oFingerprint: opt.fingerprint++; fpr_maybe_cmd = 1; break; case oWithKeygrip: opt.with_keygrip = 1; break; case oWithKeyScreening: opt.with_key_screening = 1; break; case oWithSecret: opt.with_secret = 1; break; case oWithWKDHash: opt.with_wkd_hash = 1; break; case oWithKeyOrigin: opt.with_key_origin = 1; break; case oSecretKeyring: /* Ignore this old option. */ break; - case oOptions: - /* config files may not be nested (silently ignore them) */ - if( !configfp ) { - xfree(configname); - configname = xstrdup(pargs.r.ret_str); - goto next_pass; - } - break; case oNoArmor: opt.no_armor=1; opt.armor=0; break; case oNoDefKeyring: if (default_keyring > 0) default_keyring = 0; break; case oNoKeyring: default_keyring = -1; break; case oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); opt.list_sigs=0; break; case oQuickRandom: gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); break; case oEmitVersion: opt.emit_version++; break; case oNoEmitVersion: opt.emit_version=0; break; case oCompletesNeeded: opt.completes_needed = pargs.r.ret_int; break; case oMarginalsNeeded: opt.marginals_needed = pargs.r.ret_int; break; case oMaxCertDepth: opt.max_cert_depth = pargs.r.ret_int; break; #ifndef NO_TRUST_MODELS case oTrustDBName: trustdb_name = pargs.r.ret_str; break; #endif /*!NO_TRUST_MODELS*/ case oDefaultKey: sl = add_to_strlist (&opt.def_secret_key, pargs.r.ret_str); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); - if (configfp) + if (configname) sl->flags |= PK_LIST_CONFIG; break; case oDefRecipient: if( *pargs.r.ret_str ) { xfree (opt.def_recipient); opt.def_recipient = make_username(pargs.r.ret_str); } break; case oDefRecipientSelf: xfree(opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 1; break; case oNoDefRecipient: xfree(opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 0; break; - case oNoOptions: opt.no_homedir_creation = 1; break; /* no-options */ case oHomedir: break; case oNoBatch: opt.batch = 0; break; case oWithTofuInfo: opt.with_tofu_info = 1; break; case oWithKeyData: opt.with_key_data=1; /*FALLTHRU*/ case oWithColons: opt.with_colons=':'; break; case oWithSigCheck: opt.check_sigs = 1; /*FALLTHRU*/ case oWithSigList: opt.list_sigs = 1; break; case oSkipVerify: opt.skip_verify=1; break; case oSkipHiddenRecipients: opt.skip_hidden_recipients = 1; break; case oNoSkipHiddenRecipients: opt.skip_hidden_recipients = 0; break; case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break; #ifndef NO_TRUST_MODELS /* There are many programs (like mutt) that call gpg with --always-trust so keep this option around for a long time. */ case oAlwaysTrust: opt.trust_model=TM_ALWAYS; break; case oTrustModel: parse_trust_model(pargs.r.ret_str); break; #endif /*!NO_TRUST_MODELS*/ case oTOFUDefaultPolicy: opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str); break; case oTOFUDBFormat: - obsolete_option (configname, configlineno, "tofu-db-format"); + obsolete_option (configname, pargs.lineno, "tofu-db-format"); break; case oForceOwnertrust: log_info(_("Note: %s is not for normal use!\n"), "--force-ownertrust"); opt.force_ownertrust=string_to_trust_value(pargs.r.ret_str); if(opt.force_ownertrust==-1) { log_error("invalid ownertrust '%s'\n",pargs.r.ret_str); opt.force_ownertrust=0; } break; case oLoadExtension: /* Dummy so that gpg 1.4 conf files can work. Should eventually be removed. */ break; case oCompliance: { int compliance = gnupg_parse_compliance_option (pargs.r.ret_str, compliance_options, DIM (compliance_options), opt.quiet); if (compliance < 0) g10_exit (1); set_compliance_option (compliance); } break; case oOpenPGP: case oRFC2440: case oRFC4880: case oRFC4880bis: case oPGP7: case oPGP8: case oGnuPG: set_compliance_option (pargs.r_opt); break; case oRFC2440Text: opt.rfc2440_text=1; break; case oNoRFC2440Text: opt.rfc2440_text=0; break; case oSetFilename: if(utf8_strings) opt.set_filename = pargs.r.ret_str; else opt.set_filename = native_to_utf8(pargs.r.ret_str); break; case oForYourEyesOnly: eyes_only = 1; break; case oNoForYourEyesOnly: eyes_only = 0; break; case oSetPolicyURL: add_policy_url(pargs.r.ret_str,0); add_policy_url(pargs.r.ret_str,1); break; case oSigPolicyURL: add_policy_url(pargs.r.ret_str,0); break; case oCertPolicyURL: add_policy_url(pargs.r.ret_str,1); break; case oShowPolicyURL: - deprecated_warning(configname,configlineno,"--show-policy-url", + deprecated_warning(configname,pargs.lineno,"--show-policy-url", "--list-options ","show-policy-urls"); - deprecated_warning(configname,configlineno,"--show-policy-url", + deprecated_warning(configname,pargs.lineno,"--show-policy-url", "--verify-options ","show-policy-urls"); opt.list_options|=LIST_SHOW_POLICY_URLS; opt.verify_options|=VERIFY_SHOW_POLICY_URLS; break; case oNoShowPolicyURL: - deprecated_warning(configname,configlineno,"--no-show-policy-url", + deprecated_warning(configname,pargs.lineno,"--no-show-policy-url", "--list-options ","no-show-policy-urls"); - deprecated_warning(configname,configlineno,"--no-show-policy-url", + deprecated_warning(configname,pargs.lineno,"--no-show-policy-url", "--verify-options ","no-show-policy-urls"); opt.list_options&=~LIST_SHOW_POLICY_URLS; opt.verify_options&=~VERIFY_SHOW_POLICY_URLS; break; case oSigKeyserverURL: add_keyserver_url(pargs.r.ret_str,0); break; case oUseEmbeddedFilename: opt.flags.use_embedded_filename=1; break; case oNoUseEmbeddedFilename: opt.flags.use_embedded_filename=0; break; case oComment: if(pargs.r.ret_str[0]) append_to_strlist(&opt.comments,pargs.r.ret_str); break; case oDefaultComment: - deprecated_warning(configname,configlineno, + deprecated_warning(configname,pargs.lineno, "--default-comment","--no-comments",""); /* fall through */ case oNoComments: free_strlist(opt.comments); opt.comments=NULL; break; case oThrowKeyids: opt.throw_keyids = 1; break; case oNoThrowKeyids: opt.throw_keyids = 0; break; case oShowPhotos: - deprecated_warning(configname,configlineno,"--show-photos", + deprecated_warning(configname,pargs.lineno,"--show-photos", "--list-options ","show-photos"); - deprecated_warning(configname,configlineno,"--show-photos", + deprecated_warning(configname,pargs.lineno,"--show-photos", "--verify-options ","show-photos"); opt.list_options|=LIST_SHOW_PHOTOS; opt.verify_options|=VERIFY_SHOW_PHOTOS; break; case oNoShowPhotos: - deprecated_warning(configname,configlineno,"--no-show-photos", + deprecated_warning(configname,pargs.lineno,"--no-show-photos", "--list-options ","no-show-photos"); - deprecated_warning(configname,configlineno,"--no-show-photos", + deprecated_warning(configname,pargs.lineno,"--no-show-photos", "--verify-options ","no-show-photos"); opt.list_options&=~LIST_SHOW_PHOTOS; opt.verify_options&=~VERIFY_SHOW_PHOTOS; break; case oPhotoViewer: opt.photo_viewer = pargs.r.ret_str; break; case oForceAEAD: opt.force_aead = 1; break; case oDisableSignerUID: opt.flags.disable_signer_uid = 1; break; case oS2KMode: opt.s2k_mode = pargs.r.ret_int; break; case oS2KDigest: s2k_digest_string = xstrdup(pargs.r.ret_str); break; case oS2KCipher: s2k_cipher_string = xstrdup(pargs.r.ret_str); break; case oS2KCount: if (pargs.r.ret_int) opt.s2k_count = encode_s2k_iterations (pargs.r.ret_int); else opt.s2k_count = 0; /* Auto-calibrate when needed. */ break; case oRecipient: case oHiddenRecipient: case oRecipientFile: case oHiddenRecipientFile: /* Store the recipient. Note that we also store the * option as private data in the flags. This is achieved * by shifting the option value to the left so to keep * enough space for the flags. */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); - if (configfp) + if (configname) sl->flags |= PK_LIST_CONFIG; if (pargs.r_opt == oHiddenRecipient || pargs.r_opt == oHiddenRecipientFile) sl->flags |= PK_LIST_HIDDEN; if (pargs.r_opt == oRecipientFile || pargs.r_opt == oHiddenRecipientFile) sl->flags |= PK_LIST_FROM_FILE; any_explicit_recipient = 1; break; case oEncryptTo: case oHiddenEncryptTo: /* Store an additional recipient. */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = ((pargs.r_opt << PK_LIST_SHIFT) | PK_LIST_ENCRYPT_TO); - if (configfp) + if (configname) sl->flags |= PK_LIST_CONFIG; if (pargs.r_opt == oHiddenEncryptTo) sl->flags |= PK_LIST_HIDDEN; break; case oNoEncryptTo: opt.no_encrypt_to = 1; break; case oEncryptToDefaultKey: - opt.encrypt_to_default_key = configfp ? 2 : 1; + opt.encrypt_to_default_key = configname ? 2 : 1; break; case oTrySecretKey: add_to_strlist2 (&opt.secret_keys_to_try, pargs.r.ret_str, utf8_strings); break; case oMimemode: opt.mimemode = opt.textmode = 1; break; case oTextmodeShort: opt.textmode = 2; break; case oTextmode: opt.textmode=1; break; case oNoTextmode: opt.textmode=opt.mimemode=0; break; case oExpert: opt.expert = 1; break; case oNoExpert: opt.expert = 0; break; case oDefSigExpire: if(*pargs.r.ret_str!='\0') { if(parse_expire_string(pargs.r.ret_str)==(u32)-1) log_error(_("'%s' is not a valid signature expiration\n"), pargs.r.ret_str); else opt.def_sig_expire=pargs.r.ret_str; } break; case oAskSigExpire: opt.ask_sig_expire = 1; break; case oNoAskSigExpire: opt.ask_sig_expire = 0; break; case oDefCertExpire: if(*pargs.r.ret_str!='\0') { if(parse_expire_string(pargs.r.ret_str)==(u32)-1) log_error(_("'%s' is not a valid signature expiration\n"), pargs.r.ret_str); else opt.def_cert_expire=pargs.r.ret_str; } break; case oAskCertExpire: opt.ask_cert_expire = 1; break; case oNoAskCertExpire: opt.ask_cert_expire = 0; break; case oDefCertLevel: opt.def_cert_level=pargs.r.ret_int; break; case oMinCertLevel: opt.min_cert_level=pargs.r.ret_int; break; case oAskCertLevel: opt.ask_cert_level = 1; break; case oNoAskCertLevel: opt.ask_cert_level = 0; break; case oLocalUser: /* store the local users */ sl = add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings ); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); - if (configfp) + if (configname) sl->flags |= PK_LIST_CONFIG; break; case oSender: { char *mbox = mailbox_from_userid (pargs.r.ret_str, 0); if (!mbox) log_error (_("\"%s\" is not a proper mail address\n"), pargs.r.ret_str); else { add_to_strlist (&opt.sender_list, mbox); xfree (mbox); } } break; case oCompress: /* this is the -z command line option */ opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int; break; case oCompressLevel: opt.compress_level = pargs.r.ret_int; break; case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break; case oBZ2DecompressLowmem: opt.bz2_decompress_lowmem=1; break; case oPassphrase: set_passphrase_from_string (pargs.r_type ? pargs.r.ret_str : ""); break; case oPassphraseFD: pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); break; case oPassphraseFile: pwfd = open_info_file (pargs.r.ret_str, 0, 1); break; case oPassphraseRepeat: opt.passphrase_repeat = pargs.r.ret_int; break; case oPinentryMode: opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str); if (opt.pinentry_mode == -1) log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str); break; case oRequestOrigin: opt.request_origin = parse_request_origin (pargs.r.ret_str); if (opt.request_origin == -1) log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str); break; case oCommandFD: opt.command_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); if (! gnupg_fd_valid (opt.command_fd)) log_error ("command-fd is invalid: %s\n", strerror (errno)); break; case oCommandFile: opt.command_fd = open_info_file (pargs.r.ret_str, 0, 1); break; case oCipherAlgo: def_cipher_string = xstrdup(pargs.r.ret_str); break; case oAEADAlgo: def_aead_string = xstrdup (pargs.r.ret_str); break; case oDigestAlgo: def_digest_string = xstrdup(pargs.r.ret_str); break; case oCompressAlgo: /* If it is all digits, stick a Z in front of it for later. This is for backwards compatibility with versions that took the compress algorithm number. */ { char *pt=pargs.r.ret_str; while(*pt) { if (!isascii (*pt) || !isdigit (*pt)) break; pt++; } if(*pt=='\0') { compress_algo_string=xmalloc(strlen(pargs.r.ret_str)+2); strcpy(compress_algo_string,"Z"); strcat(compress_algo_string,pargs.r.ret_str); } else compress_algo_string = xstrdup(pargs.r.ret_str); } break; case oCertDigestAlgo: cert_digest_string = xstrdup(pargs.r.ret_str); break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oRequireSecmem: require_secmem=1; break; case oNoRequireSecmem: require_secmem=0; break; case oNoPermissionWarn: opt.no_perm_warn=1; break; case oDisplayCharset: if( set_native_charset( pargs.r.ret_str ) ) log_error(_("'%s' is not a valid character set\n"), pargs.r.ret_str); break; case oNotDashEscaped: opt.not_dash_escaped = 1; break; case oEscapeFrom: opt.escape_from = 1; break; case oNoEscapeFrom: opt.escape_from = 0; break; case oLockOnce: opt.lock_once = 1; break; case oLockNever: dotlock_disable (); break; case oLockMultiple: #ifndef __riscos__ opt.lock_once = 0; #else /* __riscos__ */ riscos_not_implemented("lock-multiple"); #endif /* __riscos__ */ break; case oKeyServer: { keyserver_spec_t keyserver; keyserver = parse_keyserver_uri (pargs.r.ret_str, 0); if (!keyserver) log_error (_("could not parse keyserver URL\n")); else { /* We only support a single keyserver. Later ones override earlier ones. (Since we parse the config file first and then the command line arguments, the command line takes precedence.) */ if (opt.keyserver) free_keyserver_spec (opt.keyserver); opt.keyserver = keyserver; } } break; case oKeyServerOptions: if(!parse_keyserver_options(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid keyserver options\n"), - configname,configlineno); + configname,pargs.lineno); else log_error(_("invalid keyserver options\n")); } break; case oImportOptions: if(!parse_import_options(pargs.r.ret_str,&opt.import_options,1)) { if(configname) log_error(_("%s:%d: invalid import options\n"), - configname,configlineno); + configname,pargs.lineno); else log_error(_("invalid import options\n")); } break; case oImportFilter: rc = parse_and_set_import_filter (pargs.r.ret_str); if (rc) log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); break; case oExportOptions: if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1)) { if(configname) log_error(_("%s:%d: invalid export options\n"), - configname,configlineno); + configname,pargs.lineno); else log_error(_("invalid export options\n")); } break; case oExportFilter: rc = parse_and_set_export_filter (pargs.r.ret_str); if (rc) log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); break; case oListOptions: if(!parse_list_options(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid list options\n"), - configname,configlineno); + configname,pargs.lineno); else log_error(_("invalid list options\n")); } break; case oVerifyOptions: { struct parse_options vopts[]= { {"show-photos",VERIFY_SHOW_PHOTOS,NULL, N_("display photo IDs during signature verification")}, {"show-policy-urls",VERIFY_SHOW_POLICY_URLS,NULL, N_("show policy URLs during signature verification")}, {"show-notations",VERIFY_SHOW_NOTATIONS,NULL, N_("show all notations during signature verification")}, {"show-std-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, N_("show IETF standard notations during signature verification")}, {"show-standard-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, NULL}, {"show-user-notations",VERIFY_SHOW_USER_NOTATIONS,NULL, N_("show user-supplied notations during signature verification")}, {"show-keyserver-urls",VERIFY_SHOW_KEYSERVER_URLS,NULL, N_("show preferred keyserver URLs during signature verification")}, {"show-uid-validity",VERIFY_SHOW_UID_VALIDITY,NULL, N_("show user ID validity during signature verification")}, {"show-unusable-uids",VERIFY_SHOW_UNUSABLE_UIDS,NULL, N_("show revoked and expired user IDs in signature verification")}, {"show-primary-uid-only",VERIFY_SHOW_PRIMARY_UID_ONLY,NULL, N_("show only the primary user ID in signature verification")}, {"pka-lookups",VERIFY_PKA_LOOKUPS,NULL, N_("validate signatures with PKA data")}, {"pka-trust-increase",VERIFY_PKA_TRUST_INCREASE,NULL, N_("elevate the trust of signatures with valid PKA data")}, {NULL,0,NULL,NULL} }; if(!parse_options(pargs.r.ret_str,&opt.verify_options,vopts,1)) { if(configname) log_error(_("%s:%d: invalid verify options\n"), - configname,configlineno); + configname,pargs.lineno); else log_error(_("invalid verify options\n")); } } break; case oTempDir: opt.temp_dir=pargs.r.ret_str; break; case oExecPath: if(set_exec_path(pargs.r.ret_str)) log_error(_("unable to set exec-path to %s\n"),pargs.r.ret_str); else opt.exec_path_set=1; break; case oSetNotation: add_notation_data( pargs.r.ret_str, 0 ); add_notation_data( pargs.r.ret_str, 1 ); break; case oSigNotation: add_notation_data( pargs.r.ret_str, 0 ); break; case oCertNotation: add_notation_data( pargs.r.ret_str, 1 ); break; case oKnownNotation: register_known_notation (pargs.r.ret_str); break; case oShowNotation: - deprecated_warning(configname,configlineno,"--show-notation", + deprecated_warning(configname,pargs.lineno,"--show-notation", "--list-options ","show-notations"); - deprecated_warning(configname,configlineno,"--show-notation", + deprecated_warning(configname,pargs.lineno,"--show-notation", "--verify-options ","show-notations"); opt.list_options|=LIST_SHOW_NOTATIONS; opt.verify_options|=VERIFY_SHOW_NOTATIONS; break; case oNoShowNotation: - deprecated_warning(configname,configlineno,"--no-show-notation", + deprecated_warning(configname,pargs.lineno,"--no-show-notation", "--list-options ","no-show-notations"); - deprecated_warning(configname,configlineno,"--no-show-notation", + deprecated_warning(configname,pargs.lineno,"--no-show-notation", "--verify-options ","no-show-notations"); opt.list_options&=~LIST_SHOW_NOTATIONS; opt.verify_options&=~VERIFY_SHOW_NOTATIONS; break; case oUtf8Strings: utf8_strings = 1; break; case oNoUtf8Strings: utf8_strings = 0; break; case oDisableCipherAlgo: { int algo = string_to_cipher_algo (pargs.r.ret_str); gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oDisablePubkeyAlgo: { int algo = gcry_pk_map_name (pargs.r.ret_str); gcry_pk_ctl (GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oNoSigCache: opt.no_sig_cache = 1; break; case oAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid = 1; break; case oNoAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid=0; break; case oAllowFreeformUID: opt.allow_freeform_uid = 1; break; case oNoAllowFreeformUID: opt.allow_freeform_uid = 0; break; case oNoLiteral: opt.no_literal = 1; break; case oSetFilesize: opt.set_filesize = pargs.r.ret_ulong; break; case oFastListMode: opt.fast_list_mode = 1; break; case oFixedListMode: /* Dummy */ break; case oLegacyListMode: opt.legacy_list_mode = 1; break; case oPrintPKARecords: print_pka_records = 1; break; case oPrintDANERecords: print_dane_records = 1; break; case oListOnly: opt.list_only=1; break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oIgnoreValidFrom: opt.ignore_valid_from = 1; break; case oIgnoreCrcError: opt.ignore_crc_error = 1; break; case oIgnoreMDCError: opt.ignore_mdc_error = 1; break; case oNoRandomSeedFile: use_random_seed = 0; break; case oAutoKeyRetrieve: opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE; break; case oNoAutoKeyRetrieve: opt.keyserver_options.options &= ~KEYSERVER_AUTO_KEY_RETRIEVE; break; case oShowSessionKey: opt.show_session_key = 1; break; case oOverrideSessionKey: opt.override_session_key = pargs.r.ret_str; break; case oOverrideSessionKeyFD: ovrseskeyfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); break; case oMergeOnly: - deprecated_warning(configname,configlineno,"--merge-only", + deprecated_warning(configname,pargs.lineno,"--merge-only", "--import-options ","merge-only"); opt.import_options|=IMPORT_MERGE_ONLY; break; case oAllowSecretKeyImport: /* obsolete */ break; case oTryAllSecrets: opt.try_all_secrets = 1; break; case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break; case oEnableSpecialFilenames: enable_special_filenames (); break; case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break; case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break; case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break; case oPreservePermissions: opt.preserve_permissions=1; break; case oDefaultPreferenceList: opt.def_preference_list = pargs.r.ret_str; break; case oDefaultKeyserverURL: { keyserver_spec_t keyserver; keyserver = parse_keyserver_uri (pargs.r.ret_str,1 ); if (!keyserver) log_error (_("could not parse keyserver URL\n")); else free_keyserver_spec (keyserver); opt.def_keyserver_url = pargs.r.ret_str; } break; case oPersonalCipherPreferences: pers_cipher_list=pargs.r.ret_str; break; case oPersonalAEADPreferences: pers_aead_list = pargs.r.ret_str; break; case oPersonalDigestPreferences: pers_digest_list=pargs.r.ret_str; break; case oPersonalCompressPreferences: pers_compress_list=pargs.r.ret_str; break; case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break; case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oDisableDirmngr: opt.disable_dirmngr = 1; break; case oWeakDigest: additional_weak_digest(pargs.r.ret_str); break; case oUnwrap: opt.unwrap_encryption = 1; break; case oOnlySignTextIDs: opt.only_sign_text_ids = 1; break; case oDisplay: set_opt_session_env ("DISPLAY", pargs.r.ret_str); break; case oTTYname: set_opt_session_env ("GPG_TTY", pargs.r.ret_str); break; case oTTYtype: set_opt_session_env ("TERM", pargs.r.ret_str); break; case oXauthority: set_opt_session_env ("XAUTHORITY", pargs.r.ret_str); break; case oLCctype: opt.lc_ctype = pargs.r.ret_str; break; case oLCmessages: opt.lc_messages = pargs.r.ret_str; break; case oGroup: add_group(pargs.r.ret_str); break; case oUnGroup: rm_group(pargs.r.ret_str); break; case oNoGroups: while(opt.grouplist) { struct groupitem *iter=opt.grouplist; free_strlist(iter->values); opt.grouplist=opt.grouplist->next; xfree(iter); } break; case oStrict: case oNoStrict: /* Not used */ break; case oMangleDosFilenames: opt.mangle_dos_filenames = 1; break; case oNoMangleDosFilenames: opt.mangle_dos_filenames = 0; break; case oEnableProgressFilter: opt.enable_progress_filter = 1; break; case oMultifile: multifile=1; break; case oKeyidFormat: if(ascii_strcasecmp(pargs.r.ret_str,"short")==0) opt.keyid_format=KF_SHORT; else if(ascii_strcasecmp(pargs.r.ret_str,"long")==0) opt.keyid_format=KF_LONG; else if(ascii_strcasecmp(pargs.r.ret_str,"0xshort")==0) opt.keyid_format=KF_0xSHORT; else if(ascii_strcasecmp(pargs.r.ret_str,"0xlong")==0) opt.keyid_format=KF_0xLONG; else if(ascii_strcasecmp(pargs.r.ret_str,"none")==0) opt.keyid_format = KF_NONE; else log_error("unknown keyid-format '%s'\n",pargs.r.ret_str); break; case oExitOnStatusWriteError: opt.exit_on_status_write_error = 1; break; case oLimitCardInsertTries: opt.limit_card_insert_tries = pargs.r.ret_int; break; case oRequireCrossCert: opt.flags.require_cross_cert=1; break; case oNoRequireCrossCert: opt.flags.require_cross_cert=0; break; case oAutoKeyLocate: if (default_akl) { /* This is the first time --auto-key-locate is seen. * We need to reset the default akl. */ default_akl = 0; release_akl(); } if(!parse_auto_key_locate(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid auto-key-locate list\n"), - configname,configlineno); + configname,pargs.lineno); else log_error(_("invalid auto-key-locate list\n")); } break; case oNoAutoKeyLocate: release_akl(); break; case oKeyOrigin: if(!parse_key_origin (pargs.r.ret_str)) log_error (_("invalid argument for option \"%.50s\"\n"), "--key-origin"); break; case oEnableLargeRSA: #if SECMEM_BUFFER_SIZE >= 65536 opt.flags.large_rsa=1; #else if (configname) log_info("%s:%d: WARNING: gpg not built with large secure " "memory buffer. Ignoring enable-large-rsa\n", - configname,configlineno); + configname,pargs.lineno); else log_info("WARNING: gpg not built with large secure " "memory buffer. Ignoring --enable-large-rsa\n"); #endif /* SECMEM_BUFFER_SIZE >= 65536 */ break; case oDisableLargeRSA: opt.flags.large_rsa=0; break; case oEnableDSA2: opt.flags.dsa2=1; break; case oDisableDSA2: opt.flags.dsa2=0; break; case oAllowWeakDigestAlgos: opt.flags.allow_weak_digest_algos = 1; break; case oAllowWeakKeySignatures: opt.flags.allow_weak_key_signatures = 1; break; case oFakedSystemTime: { size_t len = strlen (pargs.r.ret_str); int freeze = 0; time_t faked_time; if (len > 0 && pargs.r.ret_str[len-1] == '!') { freeze = 1; pargs.r.ret_str[len-1] = '\0'; } faked_time = isotime2epoch (pargs.r.ret_str); if (faked_time == (time_t)(-1)) faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); gnupg_set_time (faked_time, freeze); } break; case oNoAutostart: opt.autostart = 0; break; case oNoSymkeyCache: opt.no_symkey_cache = 1; break; case oDefaultNewKeyAlgo: opt.def_new_key_algo = pargs.r.ret_str; break; case oUseOnlyOpenPGPCard: opt.flags.use_only_openpgp_card = 1; break; case oFullTimestrings: opt.flags.full_timestrings = 1; break; case oNoop: break; default: - if (configfp) + if (configname) pargs.err = ARGPARSE_PRINT_WARNING; else { pargs.err = ARGPARSE_PRINT_ERROR; /* The argparse function calls a plain exit and thus * we need to print a status here. */ write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL)); } break; } } - if (configfp) - { - fclose( configfp ); - configfp = NULL; - /* Remember the first config file name. */ - if (!save_configname) - save_configname = configname; - else - xfree(configname); - configname = NULL; - goto next_pass; - } - xfree(configname); configname = NULL; + gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ + if (log_get_errorcount (0)) { write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL)); g10_exit(2); } /* The command --gpgconf-list is pretty simple and may be called directly after the option parsing. */ if (cmd == aGPGConfList) { - gpgconf_list (save_configname ? save_configname : default_configname); + gpgconf_list (last_configname ? last_configname : "UNKNOWN"); g10_exit (0); } - xfree (save_configname); - xfree (default_configname); + xfree (last_configname); if (print_dane_records) log_error ("invalid option \"%s\"; use \"%s\" instead\n", "--print-dane-records", "--export-options export-dane"); if (print_pka_records) log_error ("invalid option \"%s\"; use \"%s\" instead\n", "--print-pks-records", "--export-options export-pka"); if (log_get_errorcount (0)) { write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); g10_exit(2); } if( nogreeting ) greeting = 0; if( greeting ) { es_fprintf (es_stderr, "%s %s; %s\n", - strusage(11), strusage(13), strusage(14) ); - es_fprintf (es_stderr, "%s\n", strusage(15) ); + gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14)); + es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION if (!opt.batch) { const char *s; - if((s=strusage(25))) + if((s=gpgrt_strusage(25))) log_info("%s\n",s); - if((s=strusage(26))) + if((s=gpgrt_strusage(26))) log_info("%s\n",s); - if((s=strusage(27))) + if((s=gpgrt_strusage(27))) log_info("%s\n",s); } #endif /* Init threading which is used by some helper functions. */ npth_init (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); if (logfile) { log_set_file (logfile); log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID )); } if (opt.verbose > 2) log_info ("using character set '%s'\n", get_native_charset ()); if( may_coredump && !opt.quiet ) log_info(_("WARNING: program may create a core file!\n")); if (opt.flags.rfc4880bis) { if (opt.verbose) log_info ("Note: RFC4880bis features are enabled.\n"); } else { opt.mimemode = 0; /* This will use text mode instead. */ } if (eyes_only) { if (opt.set_filename) log_info(_("WARNING: %s overrides %s\n"), "--for-your-eyes-only","--set-filename"); opt.set_filename="_CONSOLE"; } if (opt.no_literal) { log_info(_("Note: %s is not for normal use!\n"), "--no-literal"); if (opt.textmode) log_error(_("%s not allowed with %s!\n"), "--textmode", "--no-literal" ); if (opt.set_filename) log_error(_("%s makes no sense with %s!\n"), eyes_only?"--for-your-eyes-only":"--set-filename", "--no-literal" ); } if (opt.set_filesize) log_info(_("Note: %s is not for normal use!\n"), "--set-filesize"); if( opt.batch ) tty_batchmode( 1 ); 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"); } /* 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]); } gcry_control (GCRYCTL_RESUME_SECMEM_WARN); if(require_secmem && !got_secmem) { log_info(_("will not run with insecure memory due to %s\n"), "--require-secmem"); write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); g10_exit(2); } set_debug (debug_level); if (DBG_CLOCK) log_clock ("start"); /* Do these after the switch(), so they can override settings. */ if (PGP7) { /* That does not anymore work because we have no more support for v3 signatures. */ opt.escape_from=1; opt.ask_sig_expire=0; } else if(PGP8) { opt.escape_from=1; } if( def_cipher_string ) { opt.def_cipher_algo = string_to_cipher_algo (def_cipher_string); xfree(def_cipher_string); def_cipher_string = NULL; if ( openpgp_cipher_test_algo (opt.def_cipher_algo) ) log_error(_("selected cipher algorithm is invalid\n")); } if (def_aead_string) { opt.def_aead_algo = string_to_aead_algo (def_aead_string); xfree (def_aead_string); def_aead_string = NULL; if (openpgp_aead_test_algo (opt.def_aead_algo)) log_error(_("selected AEAD algorithm is invalid\n")); } if( def_digest_string ) { opt.def_digest_algo = string_to_digest_algo (def_digest_string); xfree(def_digest_string); def_digest_string = NULL; if ( openpgp_md_test_algo (opt.def_digest_algo) ) log_error(_("selected digest algorithm is invalid\n")); } if( compress_algo_string ) { opt.compress_algo = string_to_compress_algo(compress_algo_string); xfree(compress_algo_string); compress_algo_string = NULL; if( check_compress_algo(opt.compress_algo) ) log_error(_("selected compression algorithm is invalid\n")); } if( cert_digest_string ) { opt.cert_digest_algo = string_to_digest_algo (cert_digest_string); xfree(cert_digest_string); cert_digest_string = NULL; if (openpgp_md_test_algo(opt.cert_digest_algo)) log_error(_("selected certification digest algorithm is invalid\n")); } if( s2k_cipher_string ) { opt.s2k_cipher_algo = string_to_cipher_algo (s2k_cipher_string); xfree(s2k_cipher_string); s2k_cipher_string = NULL; if (openpgp_cipher_test_algo (opt.s2k_cipher_algo)) log_error(_("selected cipher algorithm is invalid\n")); } if( s2k_digest_string ) { opt.s2k_digest_algo = string_to_digest_algo (s2k_digest_string); xfree(s2k_digest_string); s2k_digest_string = NULL; if (openpgp_md_test_algo(opt.s2k_digest_algo)) log_error(_("selected digest algorithm is invalid\n")); } if( opt.completes_needed < 1 ) log_error(_("completes-needed must be greater than 0\n")); if( opt.marginals_needed < 2 ) log_error(_("marginals-needed must be greater than 1\n")); if( opt.max_cert_depth < 1 || opt.max_cert_depth > 255 ) log_error(_("max-cert-depth must be in the range from 1 to 255\n")); if(opt.def_cert_level<0 || opt.def_cert_level>3) log_error(_("invalid default-cert-level; must be 0, 1, 2, or 3\n")); if( opt.min_cert_level < 1 || opt.min_cert_level > 3 ) log_error(_("invalid min-cert-level; must be 1, 2, or 3\n")); switch( opt.s2k_mode ) { case 0: log_info(_("Note: simple S2K mode (0) is strongly discouraged\n")); break; case 1: case 3: break; default: log_error(_("invalid S2K mode; must be 0, 1 or 3\n")); } /* This isn't actually needed, but does serve to error out if the string is invalid. */ if(opt.def_preference_list && keygen_set_std_prefs(opt.def_preference_list,0)) log_error(_("invalid default preferences\n")); if(pers_cipher_list && keygen_set_std_prefs(pers_cipher_list,PREFTYPE_SYM)) log_error(_("invalid personal cipher preferences\n")); if (pers_aead_list && keygen_set_std_prefs (pers_aead_list, PREFTYPE_AEAD)) log_error(_("invalid personal AEAD preferences\n")); if(pers_digest_list && keygen_set_std_prefs(pers_digest_list,PREFTYPE_HASH)) log_error(_("invalid personal digest preferences\n")); if(pers_compress_list && keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP)) log_error(_("invalid personal compress preferences\n")); /* Check chunk size. Please fix also the man page if you change * the default. The limits are given by the specs. */ if (!opt.chunk_size) opt.chunk_size = 27; /* Default to the suggested max of 128 MiB. */ else if (opt.chunk_size < 6) { opt.chunk_size = 6; log_info (_("chunk size invalid - using %d\n"), opt.chunk_size); } else if (opt.chunk_size > (allow_large_chunks? 62 : 27)) { opt.chunk_size = (allow_large_chunks? 62 : 27); log_info (_("chunk size invalid - using %d\n"), opt.chunk_size); } /* We don't support all possible commands with multifile yet */ if(multifile) { char *cmdname; switch(cmd) { case aSign: cmdname="--sign"; break; case aSignEncr: cmdname="--sign --encrypt"; break; case aClearsign: cmdname="--clear-sign"; break; case aDetachedSign: cmdname="--detach-sign"; break; case aSym: cmdname="--symmetric"; break; case aEncrSym: cmdname="--symmetric --encrypt"; break; case aStore: cmdname="--store"; break; default: cmdname=NULL; break; } if(cmdname) log_error(_("%s does not yet work with %s\n"),cmdname,"--multifile"); } if( log_get_errorcount(0) ) { write_status_failure ("option-postprocessing", gpg_error(GPG_ERR_GENERAL)); g10_exit (2); } if(opt.compress_level==0) opt.compress_algo=COMPRESS_ALGO_NONE; /* Check our chosen algorithms against the list of legal algorithms. */ if(!GNUPG && !opt.flags.rfc4880bis) { const char *badalg=NULL; preftype_t badtype=PREFTYPE_NONE; if(opt.def_cipher_algo && !algo_available(PREFTYPE_SYM,opt.def_cipher_algo,NULL)) { badalg = openpgp_cipher_algo_name (opt.def_cipher_algo); badtype = PREFTYPE_SYM; } else if(opt.def_aead_algo && !algo_available(PREFTYPE_AEAD, opt.def_aead_algo, NULL)) { badalg = openpgp_aead_algo_name (opt.def_aead_algo); badtype = PREFTYPE_AEAD; } else if(opt.def_digest_algo && !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL)) { badalg = gcry_md_algo_name (opt.def_digest_algo); badtype = PREFTYPE_HASH; } else if(opt.cert_digest_algo && !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL)) { badalg = gcry_md_algo_name (opt.cert_digest_algo); badtype = PREFTYPE_HASH; } else if(opt.compress_algo!=-1 && !algo_available(PREFTYPE_ZIP,opt.compress_algo,NULL)) { badalg = compress_algo_to_string(opt.compress_algo); badtype = PREFTYPE_ZIP; } if(badalg) { switch(badtype) { case PREFTYPE_SYM: log_info (_("cipher algorithm '%s'" " may not be used in %s mode\n"), badalg, gnupg_compliance_option_string (opt.compliance)); break; case PREFTYPE_AEAD: log_info (_("AEAD algorithm '%s'" " may not be used in %s mode\n"), badalg, gnupg_compliance_option_string (opt.compliance)); break; case PREFTYPE_HASH: log_info (_("digest algorithm '%s'" " may not be used in %s mode\n"), badalg, gnupg_compliance_option_string (opt.compliance)); break; case PREFTYPE_ZIP: log_info (_("compression algorithm '%s'" " may not be used in %s mode\n"), badalg, gnupg_compliance_option_string (opt.compliance)); break; default: BUG(); } compliance_failure(); } } /* Check our chosen algorithms against the list of allowed * algorithms in the current compliance mode, and fail hard if it * is not. This is us being nice to the user informing her early * that the chosen algorithms are not available. We also check * and enforce this right before the actual operation. */ /* FIXME: We also need to check the AEAD algo. */ if (opt.def_cipher_algo && ! gnupg_cipher_is_allowed (opt.compliance, cmd == aEncr || cmd == aSignEncr || cmd == aEncrSym || cmd == aSym || cmd == aSignSym || cmd == aSignEncrSym, opt.def_cipher_algo, GCRY_CIPHER_MODE_NONE)) log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), openpgp_cipher_algo_name (opt.def_cipher_algo), gnupg_compliance_option_string (opt.compliance)); if (opt.def_digest_algo && ! gnupg_digest_is_allowed (opt.compliance, cmd == aSign || cmd == aSignEncr || cmd == aSignEncrSym || cmd == aSignSym || cmd == aClearsign, opt.def_digest_algo)) log_error (_("digest algorithm '%s' may not be used in %s mode\n"), gcry_md_algo_name (opt.def_digest_algo), gnupg_compliance_option_string (opt.compliance)); /* Fail hard. */ if (log_get_errorcount (0)) { write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); g10_exit (2); } /* Set the random seed file. */ if( use_random_seed ) { char *p = make_filename (gnupg_homedir (), "random_seed", NULL ); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); if (!access (p, F_OK)) register_secured_file (p); xfree(p); } /* If there is no command but the --fingerprint is given, default to the --list-keys command. */ if (!cmd && fpr_maybe_cmd) { set_cmd (&cmd, aListKeys); } if( opt.verbose > 1 ) set_packet_list_mode(1); /* Add the keyrings, but not for some special commands. We always * need to add the keyrings if we are running under SELinux, this * is so that the rings are added to the list of secured files. * We do not add any keyring if --no-keyring or --use-keyboxd has * been used. */ if (!opt.use_keyboxd && default_keyring >= 0 && (ALWAYS_ADD_KEYRINGS || (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest))) { if (!nrings || default_keyring > 0) /* Add default ring. */ keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG, KEYDB_RESOURCE_FLAG_DEFAULT); for (sl = nrings; sl; sl = sl->next ) keydb_add_resource (sl->d, sl->flags); } FREE_STRLIST(nrings); /* In loopback mode, never ask for the password multiple times. */ if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) { opt.passphrase_repeat = 0; } /* If no pinentry is expected shunt * gnupg_allow_set_foregound_window to avoid useless error * messages on Windows. */ if (opt.pinentry_mode != PINENTRY_MODE_ASK) { gnupg_inhibit_set_foregound_window (1); } if (cmd == aGPGConfTest) g10_exit(0); if (pwfd != -1) /* Read the passphrase now. */ read_passphrase_from_fd (pwfd); if (ovrseskeyfd != -1 ) /* Read the sessionkey now. */ read_sessionkey_from_fd (ovrseskeyfd); fname = argc? *argv : NULL; if(fname && utf8_strings) opt.flags.utf8_filename=1; ctrl = xcalloc (1, sizeof *ctrl); gpg_init_default_ctrl (ctrl); #ifndef NO_TRUST_MODELS switch (cmd) { case aPrimegen: case aPrintMD: case aPrintMDs: case aGenRandom: case aDeArmor: case aEnArmor: case aListConfig: case aListGcryptConfig: break; case aFixTrustDB: case aExportOwnerTrust: rc = setup_trustdb (0, trustdb_name); break; case aListTrustDB: rc = setup_trustdb (argc? 1:0, trustdb_name); break; case aKeygen: case aFullKeygen: case aQuickKeygen: rc = setup_trustdb (1, trustdb_name); break; default: /* If we are using TM_ALWAYS, we do not need to create the trustdb. */ rc = setup_trustdb (opt.trust_model != TM_ALWAYS, trustdb_name); break; } if (rc) log_error (_("failed to initialize the TrustDB: %s\n"), gpg_strerror (rc)); #endif /*!NO_TRUST_MODELS*/ switch (cmd) { case aStore: case aSym: case aSign: case aSignSym: case aClearsign: if (!opt.quiet && any_explicit_recipient) log_info (_("WARNING: recipients (-r) given " "without using public key encryption\n")); break; default: break; } /* Check for certain command whether we need to migrate a secring.gpg to the gpg-agent. */ switch (cmd) { case aListSecretKeys: case aSign: case aSignEncr: case aSignEncrSym: case aSignSym: case aClearsign: case aDecrypt: case aSignKey: case aLSignKey: case aEditKey: case aPasswd: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: case aQuickRevUid: case aQuickSetPrimaryUid: case aFullKeygen: case aKeygen: case aImport: case aExportSecret: case aExportSecretSub: case aGenRevoke: case aDesigRevoke: case aCardEdit: case aChangePIN: migrate_secring (ctrl); break; case aListKeys: if (opt.with_secret) migrate_secring (ctrl); break; default: break; } /* The command dispatcher. */ switch( cmd ) { case aServer: gpg_server (ctrl); break; case aStore: /* only store the file */ if( argc > 1 ) wrong_args("--store [filename]"); if( (rc = encrypt_store(fname)) ) { write_status_failure ("store", rc); log_error ("storing '%s' failed: %s\n", print_fname_stdin(fname),gpg_strerror (rc) ); } break; case aSym: /* encrypt the given file only with the symmetric cipher */ if( argc > 1 ) wrong_args("--symmetric [filename]"); if( (rc = encrypt_symmetric(fname)) ) { write_status_failure ("symencrypt", rc); log_error (_("symmetric encryption of '%s' failed: %s\n"), print_fname_stdin(fname),gpg_strerror (rc) ); } break; case aEncr: /* encrypt the given file */ if(multifile) encrypt_crypt_files (ctrl, argc, argv, remusr); else { if( argc > 1 ) wrong_args("--encrypt [filename]"); if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 0, NULL, -1)) ) { write_status_failure ("encrypt", rc); log_error("%s: encryption failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } } break; case aEncrSym: /* This works with PGP 8 in the sense that it acts just like a symmetric message. It doesn't work at all with 2 or 6. It might work with 7, but alas, I don't have a copy to test with right now. */ if( argc > 1 ) wrong_args("--symmetric --encrypt [filename]"); else if(opt.s2k_mode==0) log_error(_("you cannot use --symmetric --encrypt" " with --s2k-mode 0\n")); else if (PGP7) log_error(_("you cannot use --symmetric --encrypt" " in %s mode\n"), gnupg_compliance_option_string (opt.compliance)); else { if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 1, NULL, -1)) ) { write_status_failure ("encrypt", rc); log_error ("%s: encryption failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } } break; case aSign: /* sign the given file */ sl = NULL; if( detached_sig ) { /* sign all files */ for( ; argc; argc--, argv++ ) add_to_strlist( &sl, *argv ); } else { if( argc > 1 ) wrong_args("--sign [filename]"); if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } } if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 0, NULL, NULL))) { write_status_failure ("sign", rc); log_error ("signing failed: %s\n", gpg_strerror (rc) ); } free_strlist(sl); break; case aSignEncr: /* sign and encrypt the given file */ if( argc > 1 ) wrong_args("--sign --encrypt [filename]"); if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } else sl = NULL; if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 1, remusr, NULL))) { write_status_failure ("sign-encrypt", rc); log_error("%s: sign+encrypt failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } free_strlist(sl); break; case aSignEncrSym: /* sign and encrypt the given file */ if( argc > 1 ) wrong_args("--symmetric --sign --encrypt [filename]"); else if(opt.s2k_mode==0) log_error(_("you cannot use --symmetric --sign --encrypt" " with --s2k-mode 0\n")); else if (PGP7) log_error(_("you cannot use --symmetric --sign --encrypt" " in %s mode\n"), gnupg_compliance_option_string (opt.compliance)); else { if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } else sl = NULL; if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 2, remusr, NULL))) { write_status_failure ("sign-encrypt", rc); log_error("%s: symmetric+sign+encrypt failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } free_strlist(sl); } break; case aSignSym: /* sign and conventionally encrypt the given file */ if (argc > 1) wrong_args("--sign --symmetric [filename]"); rc = sign_symencrypt_file (ctrl, fname, locusr); if (rc) { write_status_failure ("sign-symencrypt", rc); log_error("%s: sign+symmetric failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } break; case aClearsign: /* make a clearsig */ if( argc > 1 ) wrong_args("--clear-sign [filename]"); if( (rc = clearsign_file (ctrl, fname, locusr, NULL)) ) { write_status_failure ("sign", rc); log_error("%s: clear-sign failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } break; case aVerify: if (multifile) { if ((rc = verify_files (ctrl, argc, argv))) log_error("verify files failed: %s\n", gpg_strerror (rc) ); } else { if ((rc = verify_signatures (ctrl, argc, argv))) log_error("verify signatures failed: %s\n", gpg_strerror (rc) ); } if (rc) write_status_failure ("verify", rc); break; case aDecrypt: if (multifile) decrypt_messages (ctrl, argc, argv); else { if( argc > 1 ) wrong_args("--decrypt [filename]"); if( (rc = decrypt_message (ctrl, fname) )) { write_status_failure ("decrypt", rc); log_error("decrypt_message failed: %s\n", gpg_strerror (rc) ); } } break; case aQuickSignKey: case aQuickLSignKey: { const char *fpr; if (argc < 1) wrong_args ("--quick-[l]sign-key fingerprint [userids]"); fpr = *argv++; argc--; sl = NULL; for( ; argc; argc--, argv++) append_to_strlist2 (&sl, *argv, utf8_strings); keyedit_quick_sign (ctrl, fpr, sl, locusr, (cmd == aQuickLSignKey)); free_strlist (sl); } break; case aSignKey: if( argc != 1 ) wrong_args("--sign-key user-id"); /* fall through */ case aLSignKey: if( argc != 1 ) wrong_args("--lsign-key user-id"); /* fall through */ sl=NULL; if(cmd==aSignKey) append_to_strlist(&sl,"sign"); else if(cmd==aLSignKey) append_to_strlist(&sl,"lsign"); else BUG(); append_to_strlist( &sl, "save" ); username = make_username( fname ); keyedit_menu (ctrl, username, locusr, sl, 0, 0 ); xfree(username); free_strlist(sl); break; case aEditKey: /* Edit a key signature */ if( !argc ) wrong_args("--edit-key user-id [commands]"); username = make_username( fname ); if( argc > 1 ) { sl = NULL; for( argc--, argv++ ; argc; argc--, argv++ ) append_to_strlist( &sl, *argv ); keyedit_menu (ctrl, username, locusr, sl, 0, 1 ); free_strlist(sl); } else keyedit_menu (ctrl, username, locusr, NULL, 0, 1 ); xfree(username); break; case aPasswd: if (argc != 1) wrong_args("--change-passphrase "); else { username = make_username (fname); keyedit_passwd (ctrl, username); xfree (username); } break; case aDeleteKeys: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: sl = NULL; /* I'm adding these in reverse order as add_to_strlist2 reverses them again, and it's easier to understand in the proper order :) */ for( ; argc; argc-- ) add_to_strlist2( &sl, argv[argc-1], utf8_strings ); delete_keys (ctrl, sl, cmd==aDeleteSecretKeys, cmd==aDeleteSecretAndPublicKeys); free_strlist(sl); break; case aCheckKeys: opt.check_sigs = 1; /* fall through */ case aListSigs: opt.list_sigs = 1; /* fall through */ case aListKeys: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); public_key_list (ctrl, sl, 0, 0); free_strlist(sl); break; case aListSecretKeys: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); secret_key_list (ctrl, sl); free_strlist(sl); break; case aLocateKeys: case aLocateExtKeys: sl = NULL; for (; argc; argc--, argv++) add_to_strlist2( &sl, *argv, utf8_strings ); if (cmd == aLocateExtKeys && akl_empty_or_only_local ()) { /* This is a kludge to let --locate-external-keys even * work if the config file has --no-auto-key-locate. This * better matches the expectations of the user. */ release_akl (); parse_auto_key_locate (DEFAULT_AKL_LIST); } public_key_list (ctrl, sl, 1, cmd == aLocateExtKeys); free_strlist (sl); break; case aQuickKeygen: { const char *x_algo, *x_usage, *x_expire; if (argc < 1 || argc > 4) wrong_args("--quick-generate-key USER-ID [ALGO [USAGE [EXPIRE]]]"); username = make_username (fname); argv++, argc--; x_algo = ""; x_usage = ""; x_expire = ""; if (argc) { x_algo = *argv++; argc--; if (argc) { x_usage = *argv++; argc--; if (argc) { x_expire = *argv++; argc--; } } } quick_generate_keypair (ctrl, username, x_algo, x_usage, x_expire); xfree (username); } break; case aKeygen: /* generate a key */ if( opt.batch ) { if( argc > 1 ) wrong_args("--generate-key [parameterfile]"); generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0); } else { if (opt.command_fd != -1 && argc) { if( argc > 1 ) wrong_args("--generate-key [parameterfile]"); opt.batch = 1; generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0); } else if (argc) wrong_args ("--generate-key"); else generate_keypair (ctrl, 0, NULL, NULL, 0); } break; case aFullKeygen: /* Generate a key with all options. */ if (opt.batch) { if (argc > 1) wrong_args ("--full-generate-key [parameterfile]"); generate_keypair (ctrl, 1, argc? *argv : NULL, NULL, 0); } else { if (argc) wrong_args("--full-generate-key"); generate_keypair (ctrl, 1, NULL, NULL, 0); } break; case aQuickAddUid: { const char *uid, *newuid; if (argc != 2) wrong_args ("--quick-add-uid USER-ID NEW-USER-ID"); uid = *argv++; argc--; newuid = *argv++; argc--; keyedit_quick_adduid (ctrl, uid, newuid); } break; case aQuickAddKey: { const char *x_fpr, *x_algo, *x_usage, *x_expire; if (argc < 1 || argc > 4) wrong_args ("--quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]"); x_fpr = *argv++; argc--; x_algo = ""; x_usage = ""; x_expire = ""; if (argc) { x_algo = *argv++; argc--; if (argc) { x_usage = *argv++; argc--; if (argc) { x_expire = *argv++; argc--; } } } keyedit_quick_addkey (ctrl, x_fpr, x_algo, x_usage, x_expire); } break; case aQuickRevUid: { const char *uid, *uidtorev; if (argc != 2) wrong_args ("--quick-revoke-uid USER-ID USER-ID-TO-REVOKE"); uid = *argv++; argc--; uidtorev = *argv++; argc--; keyedit_quick_revuid (ctrl, uid, uidtorev); } break; case aQuickSetExpire: { const char *x_fpr, *x_expire; if (argc < 2) wrong_args ("--quick-set-exipre FINGERPRINT EXPIRE [SUBKEY-FPRS]"); x_fpr = *argv++; argc--; x_expire = *argv++; argc--; keyedit_quick_set_expire (ctrl, x_fpr, x_expire, argv); } break; case aQuickSetPrimaryUid: { const char *uid, *primaryuid; if (argc != 2) wrong_args ("--quick-set-primary-uid USER-ID PRIMARY-USER-ID"); uid = *argv++; argc--; primaryuid = *argv++; argc--; keyedit_quick_set_primary (ctrl, uid, primaryuid); } break; case aFastImport: opt.import_options |= IMPORT_FAST; /* fall through */ case aImport: case aShowKeys: import_keys (ctrl, argc? argv:NULL, argc, NULL, opt.import_options, opt.key_origin, opt.key_origin_url); break; /* TODO: There are a number of command that use this same "make strlist, call function, report error, free strlist" pattern. Join them together here and avoid all that duplicated code. */ case aExport: case aSendKeys: case aRecvKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); if( cmd == aSendKeys ) rc = keyserver_export (ctrl, sl ); else if( cmd == aRecvKeys ) rc = keyserver_import (ctrl, sl ); else { export_stats_t stats = export_new_stats (); rc = export_pubkeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } if(rc) { if(cmd==aSendKeys) { write_status_failure ("send-keys", rc); log_error(_("keyserver send failed: %s\n"),gpg_strerror (rc)); } else if(cmd==aRecvKeys) { write_status_failure ("recv-keys", rc); log_error (_("keyserver receive failed: %s\n"), gpg_strerror (rc)); } else { write_status_failure ("export", rc); log_error (_("key export failed: %s\n"), gpg_strerror (rc)); } } free_strlist(sl); break; case aExportSshKey: if (argc != 1) wrong_args ("--export-ssh-key "); rc = export_ssh_key (ctrl, argv[0]); if (rc) { write_status_failure ("export-ssh-key", rc); log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc)); } break; case aSearchKeys: sl = NULL; for (; argc; argc--, argv++) append_to_strlist2 (&sl, *argv, utf8_strings); rc = keyserver_search (ctrl, sl); if (rc) { write_status_failure ("search-keys", rc); log_error (_("keyserver search failed: %s\n"), gpg_strerror (rc)); } free_strlist (sl); break; case aRefreshKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc = keyserver_refresh (ctrl, sl); if(rc) { write_status_failure ("refresh-keys", rc); log_error (_("keyserver refresh failed: %s\n"),gpg_strerror (rc)); } free_strlist(sl); break; case aFetchKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc = keyserver_fetch (ctrl, sl, opt.key_origin); if(rc) { write_status_failure ("fetch-keys", rc); log_error ("key fetch failed: %s\n",gpg_strerror (rc)); } free_strlist(sl); break; case aExportSecret: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); { export_stats_t stats = export_new_stats (); export_seckeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } free_strlist(sl); break; case aExportSecretSub: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); { export_stats_t stats = export_new_stats (); export_secsubkeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } free_strlist(sl); break; case aGenRevoke: if( argc != 1 ) wrong_args("--generate-revocation user-id"); username = make_username(*argv); gen_revoke (ctrl, username ); xfree( username ); break; case aDesigRevoke: if (argc != 1) wrong_args ("--generate-designated-revocation user-id"); username = make_username (*argv); gen_desig_revoke (ctrl, username, locusr); xfree (username); break; case aDeArmor: if( argc > 1 ) wrong_args("--dearmor [file]"); rc = dearmor_file( argc? *argv: NULL ); if( rc ) { write_status_failure ("dearmor", rc); log_error (_("dearmoring failed: %s\n"), gpg_strerror (rc)); } break; case aEnArmor: if( argc > 1 ) wrong_args("--enarmor [file]"); rc = enarmor_file( argc? *argv: NULL ); if( rc ) { write_status_failure ("enarmor", rc); log_error (_("enarmoring failed: %s\n"), gpg_strerror (rc)); } break; case aPrimegen: #if 0 /*FIXME*/ { int mode = argc < 2 ? 0 : atoi(*argv); if( mode == 1 && argc == 2 ) { mpi_print (es_stdout, generate_public_prime( atoi(argv[1]) ), 1); } else if( mode == 2 && argc == 3 ) { mpi_print (es_stdout, generate_elg_prime( 0, atoi(argv[1]), atoi(argv[2]), NULL,NULL ), 1); } else if( mode == 3 && argc == 3 ) { MPI *factors; mpi_print (es_stdout, generate_elg_prime( 1, atoi(argv[1]), atoi(argv[2]), NULL,&factors ), 1); es_putc ('\n', es_stdout); mpi_print (es_stdout, factors[0], 1 ); /* print q */ } else if( mode == 4 && argc == 3 ) { MPI g = mpi_alloc(1); mpi_print (es_stdout, generate_elg_prime( 0, atoi(argv[1]), atoi(argv[2]), g, NULL ), 1); es_putc ('\n', es_stdout); mpi_print (es_stdout, g, 1 ); mpi_free (g); } else wrong_args("--gen-prime mode bits [qbits] "); es_putc ('\n', es_stdout); } #endif wrong_args("--gen-prime not yet supported "); break; case aGenRandom: { int level = argc ? atoi(*argv):0; int count = argc > 1 ? atoi(argv[1]): 0; int endless = !count; if( argc < 1 || argc > 2 || level < 0 || level > 2 || count < 0 ) wrong_args("--gen-random 0|1|2 [count]"); while( endless || count ) { byte *p; /* We need a multiple of 3, so that in case of armored output we get a correct string. No linefolding is done, as it is best to leave this to other tools */ size_t n = !endless && count < 99? count : 99; p = gcry_random_bytes (n, level); #ifdef HAVE_DOSISH_SYSTEM setmode ( fileno(stdout), O_BINARY ); #endif if (opt.armor) { char *tmp = make_radix64_string (p, n); es_fputs (tmp, es_stdout); xfree (tmp); if (n%3 == 1) es_putc ('=', es_stdout); if (n%3) es_putc ('=', es_stdout); } else { es_fwrite( p, n, 1, es_stdout ); } xfree(p); if( !endless ) count -= n; } if (opt.armor) es_putc ('\n', es_stdout); } break; case aPrintMD: if( argc < 1) wrong_args("--print-md algo [files]"); { int all_algos = (**argv=='*' && !(*argv)[1]); int algo = all_algos? 0 : gcry_md_map_name (*argv); if( !algo && !all_algos ) log_error(_("invalid hash algorithm '%s'\n"), *argv ); else { argc--; argv++; if( !argc ) print_mds(NULL, algo); else { for(; argc; argc--, argv++ ) print_mds(*argv, algo); } } } break; case aPrintMDs: /* old option */ if( !argc ) print_mds(NULL,0); else { for(; argc; argc--, argv++ ) print_mds(*argv,0); } break; #ifndef NO_TRUST_MODELS case aListTrustDB: if( !argc ) list_trustdb (ctrl, es_stdout, NULL); else { for( ; argc; argc--, argv++ ) list_trustdb (ctrl, es_stdout, *argv ); } break; case aUpdateTrustDB: if( argc ) wrong_args("--update-trustdb"); update_trustdb (ctrl); break; case aCheckTrustDB: /* Old versions allowed for arguments - ignore them */ check_trustdb (ctrl); break; case aFixTrustDB: how_to_fix_the_trustdb (); break; case aListTrustPath: if( !argc ) wrong_args("--list-trust-path "); for( ; argc; argc--, argv++ ) { username = make_username( *argv ); list_trust_path( username ); xfree(username); } break; case aExportOwnerTrust: if( argc ) wrong_args("--export-ownertrust"); export_ownertrust (ctrl); break; case aImportOwnerTrust: if( argc > 1 ) wrong_args("--import-ownertrust [file]"); import_ownertrust (ctrl, argc? *argv:NULL ); break; #endif /*!NO_TRUST_MODELS*/ case aRebuildKeydbCaches: if (argc) wrong_args ("--rebuild-keydb-caches"); keydb_rebuild_caches (ctrl, 1); break; #ifdef ENABLE_CARD_SUPPORT case aCardStatus: if (argc == 0) card_status (ctrl, es_stdout, NULL); else if (argc == 1) card_status (ctrl, es_stdout, *argv); else wrong_args ("--card-status [serialno]"); break; case aCardEdit: if (argc) { sl = NULL; for (argc--, argv++ ; argc; argc--, argv++) append_to_strlist (&sl, *argv); card_edit (ctrl, sl); free_strlist (sl); } else card_edit (ctrl, NULL); break; case aChangePIN: if (!argc) change_pin (0,1); else if (argc == 1) change_pin (atoi (*argv),1); else wrong_args ("--change-pin [no]"); break; #endif /* ENABLE_CARD_SUPPORT*/ case aListConfig: { char *str=collapse_args(argc,argv); list_config(str); xfree(str); } break; case aListGcryptConfig: /* Fixme: It would be nice to integrate that with --list-config but unfortunately there is no way yet to have libgcrypt print it to an estream for further parsing. */ gcry_control (GCRYCTL_PRINT_CONFIG, stdout); break; case aTOFUPolicy: #ifdef USE_TOFU { int policy; int i; KEYDB_HANDLE hd; if (argc < 2) wrong_args ("--tofu-policy POLICY KEYID [KEYID...]"); policy = parse_tofu_policy (argv[0]); hd = keydb_new (ctrl); if (! hd) { write_status_failure ("tofu-driver", gpg_error(GPG_ERR_GENERAL)); g10_exit (1); } tofu_begin_batch_update (ctrl); for (i = 1; i < argc; i ++) { KEYDB_SEARCH_DESC desc; kbnode_t kb; rc = classify_user_id (argv[i], &desc, 0); if (rc) { log_error (_("error parsing key specification '%s': %s\n"), argv[i], gpg_strerror (rc)); write_status_failure ("tofu-driver", rc); g10_exit (1); } if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID || desc.mode == KEYDB_SEARCH_MODE_LONG_KID || desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP)) { log_error (_("'%s' does not appear to be a valid" " key ID, fingerprint or keygrip\n"), argv[i]); write_status_failure ("tofu-driver", gpg_error(GPG_ERR_GENERAL)); g10_exit (1); } rc = keydb_search_reset (hd); if (rc) { /* This should not happen, thus no need to tranalate the string. */ log_error ("keydb_search_reset failed: %s\n", gpg_strerror (rc)); write_status_failure ("tofu-driver", rc); g10_exit (1); } rc = keydb_search (hd, &desc, 1, NULL); if (rc) { log_error (_("key \"%s\" not found: %s\n"), argv[i], gpg_strerror (rc)); write_status_failure ("tofu-driver", rc); g10_exit (1); } rc = keydb_get_keyblock (hd, &kb); if (rc) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc)); write_status_failure ("tofu-driver", rc); g10_exit (1); } merge_keys_and_selfsig (ctrl, kb); if (tofu_set_policy (ctrl, kb, policy)) { write_status_failure ("tofu-driver", rc); g10_exit (1); } release_kbnode (kb); } tofu_end_batch_update (ctrl); keydb_release (hd); } #endif /*USE_TOFU*/ break; default: if (!opt.quiet) log_info (_("WARNING: no command supplied." " Trying to guess what you mean ...\n")); /*FALLTHRU*/ case aListPackets: if( argc > 1 ) wrong_args("[filename]"); /* Issue some output for the unix newbie */ if (!fname && !opt.outfile && gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout)) && gnupg_isatty (fileno (stderr))) log_info(_("Go ahead and type your message ...\n")); a = iobuf_open(fname); if (a && is_secured_file (iobuf_get_fd (a))) { iobuf_close (a); a = NULL; gpg_err_set_errno (EPERM); } if( !a ) log_error(_("can't open '%s'\n"), print_fname_stdin(fname)); else { if( !opt.no_armor ) { if( use_armor_filter( a ) ) { afx = new_armor_context (); push_armor_filter (afx, a); } } if( cmd == aListPackets ) { opt.list_packets=1; set_packet_list_mode(1); } rc = proc_packets (ctrl, NULL, a ); if( rc ) { write_status_failure ("-", rc); log_error ("processing message failed: %s\n", gpg_strerror (rc)); } iobuf_close(a); } break; } /* cleanup */ gpg_deinit_default_ctrl (ctrl); xfree (ctrl); release_armor_context (afx); FREE_STRLIST(remusr); FREE_STRLIST(locusr); g10_exit(0); return 8; /*NEVER REACHED*/ } /* Note: This function is used by signal handlers!. */ static void emergency_cleanup (void) { gcry_control (GCRYCTL_TERM_SECMEM ); } void g10_exit( int rc ) { /* If we had an error but not printed an error message, do it now. * Note that write_status_failure will never print a second failure * status line. */ if (rc) write_status_failure ("gpg-exit", gpg_error (GPG_ERR_GENERAL)); gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); if (DBG_CLOCK) log_clock ("stop"); if ( (opt.debug & DBG_MEMSTAT_VALUE) ) { keydb_dump_stats (); sig_check_dump_stats (); objcache_dump_stats (); gcry_control (GCRYCTL_DUMP_MEMORY_STATS); gcry_control (GCRYCTL_DUMP_RANDOM_STATS); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); gnupg_block_all_signals (); emergency_cleanup (); rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0; exit (rc); } /* Pretty-print hex hashes. This assumes at least an 80-character display, but there are a few other similar assumptions in the display code. */ static void print_hex (gcry_md_hd_t md, int algo, const char *fname) { int i,n,count,indent=0; const byte *p; if (fname) indent = es_printf("%s: ",fname); if (indent>40) { es_printf ("\n"); indent=0; } if (algo==DIGEST_ALGO_RMD160) indent += es_printf("RMD160 = "); else if (algo>0) indent += es_printf("%6s = ", gcry_md_algo_name (algo)); else algo = abs(algo); count = indent; p = gcry_md_read (md, algo); n = gcry_md_get_algo_dlen (algo); count += es_printf ("%02X",*p++); for(i=1;i79) { es_printf ("\n%*s",indent," "); count = indent; } else count += es_printf(" "); if (!(i%8)) count += es_printf(" "); } else if (n==20) { if(!(i%2)) { if(count+4>79) { es_printf ("\n%*s",indent," "); count=indent; } else count += es_printf(" "); } if (!(i%10)) count += es_printf(" "); } else { if(!(i%4)) { if (count+8>79) { es_printf ("\n%*s",indent," "); count=indent; } else count += es_printf(" "); } } count += es_printf("%02X",*p); } es_printf ("\n"); } static void print_hashline( gcry_md_hd_t md, int algo, const char *fname ) { int i, n; const byte *p; if ( fname ) { for (p = fname; *p; p++ ) { if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' ) es_printf ("%%%02X", *p ); else es_putc (*p, es_stdout); } } es_putc (':', es_stdout); es_printf ("%d:", algo); p = gcry_md_read (md, algo); n = gcry_md_get_algo_dlen (algo); for(i=0; i < n ; i++, p++ ) es_printf ("%02X", *p); es_fputs (":\n", es_stdout); } static void print_mds( const char *fname, int algo ) { estream_t fp; char buf[1024]; size_t n; gcry_md_hd_t md; if (!fname) { fp = es_stdin; es_set_binary (fp); } else { fp = es_fopen (fname, "rb" ); if (fp && is_secured_file (es_fileno (fp))) { es_fclose (fp); fp = NULL; gpg_err_set_errno (EPERM); } } if (!fp) { log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) ); return; } gcry_md_open (&md, 0, 0); if (algo) gcry_md_enable (md, algo); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) gcry_md_enable (md, GCRY_MD_MD5); gcry_md_enable (md, GCRY_MD_SHA1); if (!gcry_md_test_algo (GCRY_MD_RMD160)) gcry_md_enable (md, GCRY_MD_RMD160); if (!gcry_md_test_algo (GCRY_MD_SHA224)) gcry_md_enable (md, GCRY_MD_SHA224); if (!gcry_md_test_algo (GCRY_MD_SHA256)) gcry_md_enable (md, GCRY_MD_SHA256); if (!gcry_md_test_algo (GCRY_MD_SHA384)) gcry_md_enable (md, GCRY_MD_SHA384); if (!gcry_md_test_algo (GCRY_MD_SHA512)) gcry_md_enable (md, GCRY_MD_SHA512); } while ((n=es_fread (buf, 1, DIM(buf), fp))) gcry_md_write (md, buf, n); if (es_ferror(fp)) log_error ("%s: %s\n", fname?fname:"[stdin]", strerror(errno)); else { gcry_md_final (md); if (opt.with_colons) { if ( algo ) print_hashline (md, algo, fname); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) print_hashline( md, GCRY_MD_MD5, fname ); print_hashline( md, GCRY_MD_SHA1, fname ); if (!gcry_md_test_algo (GCRY_MD_RMD160)) print_hashline( md, GCRY_MD_RMD160, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA224)) print_hashline (md, GCRY_MD_SHA224, fname); if (!gcry_md_test_algo (GCRY_MD_SHA256)) print_hashline( md, GCRY_MD_SHA256, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA384)) print_hashline ( md, GCRY_MD_SHA384, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA512)) print_hashline ( md, GCRY_MD_SHA512, fname ); } } else { if (algo) print_hex (md, -algo, fname); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) print_hex (md, GCRY_MD_MD5, fname); print_hex (md, GCRY_MD_SHA1, fname ); if (!gcry_md_test_algo (GCRY_MD_RMD160)) print_hex (md, GCRY_MD_RMD160, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA224)) print_hex (md, GCRY_MD_SHA224, fname); if (!gcry_md_test_algo (GCRY_MD_SHA256)) print_hex (md, GCRY_MD_SHA256, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA384)) print_hex (md, GCRY_MD_SHA384, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA512)) print_hex (md, GCRY_MD_SHA512, fname ); } } } gcry_md_close (md); if (fp != es_stdin) es_fclose (fp); } /**************** * Check the supplied name,value string and add it to the notation * data to be used for signatures. which==0 for sig notations, and 1 * for cert notations. */ static void add_notation_data( const char *string, int which ) { struct notation *notation; notation=string_to_notation(string,utf8_strings); if(notation) { if(which) { notation->next=opt.cert_notations; opt.cert_notations=notation; } else { notation->next=opt.sig_notations; opt.sig_notations=notation; } } } static void add_policy_url( const char *string, int which ) { unsigned int i,critical=0; strlist_t sl; if(*string=='!') { string++; critical=1; } for(i=0;iflags |= 1; } static void add_keyserver_url( const char *string, int which ) { unsigned int i,critical=0; strlist_t sl; if(*string=='!') { string++; critical=1; } for(i=0;iflags |= 1; } static void read_sessionkey_from_fd (int fd) { int i, len; char *line; if (! gnupg_fd_valid (fd)) log_fatal ("override-session-key-fd is invalid: %s\n", strerror (errno)); for (line = NULL, i = len = 100; ; i++ ) { if (i >= len-1 ) { char *tmp = line; len += 100; line = xmalloc_secure (len); if (tmp) { memcpy (line, tmp, i); xfree (tmp); } else i=0; } if (read (fd, line + i, 1) != 1 || line[i] == '\n') break; } line[i] = 0; log_debug ("seskey: %s\n", line); gpgrt_annotate_leaked_object (line); opt.override_session_key = line; } diff --git a/g10/gpgv.c b/g10/gpgv.c index 88b348125..f2f14a676 100644 --- a/g10/gpgv.c +++ b/g10/gpgv.c @@ -1,789 +1,793 @@ /* gpgv.c - The GnuPG signature verify utility * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2005, 2006, * 2008, 2009, 2012 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #include #include #include #include #include #include #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #ifdef HAVE_LIBREADLINE #define GNUPG_LIBREADLINE_H_INCLUDED #include #endif #define INCLUDED_BY_MAIN_MODULE 1 #include "gpg.h" #include "../common/util.h" +#include "../common/argparse.h" /* temporary hack. */ #include "packet.h" #include "../common/iobuf.h" #include "main.h" #include "options.h" #include "keydb.h" #include "trustdb.h" #include "filter.h" #include "../common/ttyio.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/status.h" #include "call-agent.h" #include "../common/init.h" enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oOutput = 'o', oBatch = 500, oKeyring, oIgnoreTimeConflict, oStatusFD, oLoggerFD, oLoggerFile, oHomedir, oWeakDigest, oEnableSpecialFilenames, oDebug, aTest }; static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_s (oKeyring, "keyring", N_("|FILE|take the keys from the keyring FILE")), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", N_("make timestamp conflicts only a warning")), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), ARGPARSE_s_s (oLoggerFile, "log-file", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oWeakDigest, "weak-digest", N_("|ALGO|reject signatures made with ALGO")), ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_PACKET_VALUE , "packet" }, { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_FILTER_VALUE , "filter" }, { DBG_IOBUF_VALUE , "iobuf" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_TRUST_VALUE , "trust" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_CLOCK_VALUE , "clock" }, { DBG_LOOKUP_VALUE , "lookup" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; int g10_errors_seen = 0; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } static const char * my_strusage( int level ) { static char *ver_gcry; const char *p; switch (level) { case 11: p = "@GPG@v (GnuPG)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpgv [options] [files] (-h for help)"); break; case 41: p = _("Syntax: gpgv [options] [files]\n" "Check signatures against known trusted keys\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; default: p = NULL; } return p; } int main( int argc, char **argv ) { ARGPARSE_ARGS pargs; int rc=0; strlist_t sl; strlist_t nrings = NULL; unsigned configlineno; ctrl_t ctrl; early_system_init (); - set_strusage (my_strusage); + gpgrt_set_strusage (my_strusage); log_set_prefix ("gpgv", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); gnupg_init_signals (0, NULL); opt.command_fd = -1; /* no command fd */ opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE; opt.trust_model = TM_ALWAYS; opt.no_sig_cache = 1; opt.flags.require_cross_cert = 1; opt.batch = 1; opt.answer_yes = 1; opt.weak_digests = NULL; tty_no_terminal(1); tty_batchmode(1); dotlock_disable (); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); additional_weak_digest("MD5"); gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPG); pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* do not remove the args */ while (optfile_parse( NULL, NULL, &configlineno, &pargs, opts)) { switch (pargs.r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; opt.list_sigs=1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oStatusFD: set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oLoggerFD: log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oLoggerFile: log_set_file (pargs.r.ret_str); log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID) ); break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oWeakDigest: additional_weak_digest(pargs.r.ret_str); break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oEnableSpecialFilenames: enable_special_filenames (); break; default : pargs.err = ARGPARSE_PRINT_ERROR; break; } } if (log_get_errorcount (0)) g10_exit(2); if (opt.verbose > 1) set_packet_list_mode(1); /* Note: We open all keyrings in read-only mode. */ if (!nrings) /* No keyring given: use default one. */ keydb_add_resource ("trustedkeys" EXTSEP_S "kbx", (KEYDB_RESOURCE_FLAG_READONLY |KEYDB_RESOURCE_FLAG_GPGVDEF)); for (sl = nrings; sl; sl = sl->next) keydb_add_resource (sl->d, KEYDB_RESOURCE_FLAG_READONLY); FREE_STRLIST (nrings); ctrl = xcalloc (1, sizeof *ctrl); if ((rc = verify_signatures (ctrl, argc, argv))) log_error("verify signatures failed: %s\n", gpg_strerror (rc) ); keydb_release (ctrl->cached_getkey_kdb); xfree (ctrl); /* cleanup */ g10_exit (0); return 8; /*NOTREACHED*/ } void g10_exit( int rc ) { rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0; exit(rc ); } /* Stub: * We have to override the trustcheck from pkclist.c because * this utility assumes that all keys in the keyring are trustworthy */ int check_signatures_trust (ctrl_t ctrl, PKT_signature *sig) { (void)ctrl; (void)sig; return 0; } void read_trust_options (ctrl_t ctrl, byte *trust_model, ulong *created, ulong *nextcheck, byte *marginals, byte *completes, byte *cert_depth, byte *min_cert_level) { (void)ctrl; (void)trust_model; (void)created; (void)nextcheck; (void)marginals; (void)completes; (void)cert_depth; (void)min_cert_level; } /* Stub: * We don't have the trustdb , so we have to provide some stub functions * instead */ int cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk) { (void)ctrl; (void)pk; return 0; } void check_trustdb_stale (ctrl_t ctrl) { (void)ctrl; } int get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid) { (void)ctrl; (void)kb; (void)pk; (void)uid; return '?'; } unsigned int get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid, PKT_signature *sig, int may_ask) { (void)ctrl; (void)kb; (void)pk; (void)uid; (void)sig; (void)may_ask; return 0; } const char * trust_value_to_string (unsigned int value) { (void)value; return "err"; } const char * uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid) { (void)ctrl; (void)key; (void)uid; return "err"; } int get_ownertrust_info (ctrl_t ctrl, PKT_public_key *pk, int no_create) { (void)ctrl; (void)pk; (void)no_create; return '?'; } unsigned int get_ownertrust (ctrl_t ctrl, PKT_public_key *pk) { (void)ctrl; (void)pk; return TRUST_UNKNOWN; } /* Stubs: * Because we only work with trusted keys, it does not make sense to * get them from a keyserver */ struct keyserver_spec * keyserver_match (struct keyserver_spec *spec) { (void)spec; return NULL; } int keyserver_any_configured (ctrl_t ctrl) { (void)ctrl; return 0; } int keyserver_import_keyid (u32 *keyid, void *dummy, int quick) { (void)keyid; (void)dummy; (void)quick; return -1; } int keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len, struct keyserver_spec *keyserver, int quick) { (void)ctrl; (void)fprint; (void)fprint_len; (void)keyserver; (void)quick; return -1; } int keyserver_import_cert (const char *name) { (void)name; return -1; } int keyserver_import_pka (const char *name,unsigned char *fpr) { (void)name; (void)fpr; return -1; } gpg_error_t keyserver_import_wkd (ctrl_t ctrl, const char *name, int quick, unsigned char **fpr, size_t *fpr_len) { (void)ctrl; (void)name; (void)quick; (void)fpr; (void)fpr_len; return GPG_ERR_BUG; } int keyserver_import_name (const char *name,struct keyserver_spec *spec) { (void)name; (void)spec; return -1; } int keyserver_import_ldap (const char *name) { (void)name; return -1; } gpg_error_t read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) { (void)ctrl; (void)fname; (void)r_keyblock; return -1; } /* Stub: * No encryption here but mainproc links to these functions. */ gpg_error_t get_session_key (ctrl_t ctrl, struct pubkey_enc_list *k, DEK *dek) { (void)ctrl; (void)k; (void)dek; return GPG_ERR_GENERAL; } /* Stub: */ gpg_error_t get_override_session_key (DEK *dek, const char *string) { (void)dek; (void)string; return GPG_ERR_GENERAL; } /* Stub: */ int decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek) { (void)ctrl; (void)procctx; (void)ed; (void)dek; return GPG_ERR_GENERAL; } /* Stub: * No interactive commands, so we don't need the helptexts */ void display_online_help (const char *keyword) { (void)keyword; } /* Stub: * We don't use secret keys, but getkey.c links to this */ int check_secret_key (PKT_public_key *pk, int n) { (void)pk; (void)n; return GPG_ERR_GENERAL; } /* Stub: * No secret key, so no passphrase needed */ DEK * passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, int create, int nocache, const char *tmp, int *canceled) { (void)cipher_algo; (void)s2k; (void)create; (void)nocache; (void)tmp; if (canceled) *canceled = 0; return NULL; } void passphrase_clear_cache (const char *cacheid) { (void)cacheid; } struct keyserver_spec * parse_preferred_keyserver(PKT_signature *sig) { (void)sig; return NULL; } struct keyserver_spec * parse_keyserver_uri (const char *uri, int require_scheme, const char *configname, unsigned int configlineno) { (void)uri; (void)require_scheme; (void)configname; (void)configlineno; return NULL; } void free_keyserver_spec (struct keyserver_spec *keyserver) { (void)keyserver; } /* Stubs to avoid linking to photoid.c */ void show_photos (const struct user_attribute *attrs, int count, PKT_public_key *pk) { (void)attrs; (void)count; (void)pk; } int parse_image_header (const struct user_attribute *attr, byte *type, u32 *len) { (void)attr; (void)type; (void)len; return 0; } char * image_type_to_string (byte type, int string) { (void)type; (void)string; return NULL; } #ifdef ENABLE_CARD_SUPPORT int agent_scd_getattr (const char *name, struct agent_card_info_s *info) { (void)name; (void)info; return 0; } #endif /* ENABLE_CARD_SUPPORT */ /* We do not do any locking, so use these stubs here */ void dotlock_disable (void) { } dotlock_t dotlock_create (const char *file_to_lock, unsigned int flags) { (void)file_to_lock; (void)flags; return NULL; } void dotlock_destroy (dotlock_t h) { (void)h; } int dotlock_take (dotlock_t h, long timeout) { (void)h; (void)timeout; return 0; } int dotlock_release (dotlock_t h) { (void)h; return 0; } void dotlock_remove_lockfiles (void) { } int agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) { (void)ctrl; (void)pk; return 0; } gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) { (void)ctrl; (void)keyblock; return gpg_error (GPG_ERR_NO_SECKEY); } gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno, int *r_cleartext) { (void)ctrl; (void)hexkeygrip; (void)r_cleartext; *r_serialno = NULL; return gpg_error (GPG_ERR_NO_SECKEY); } gpg_error_t gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { (void)ctrl; (void)userid; if (r_fpr) *r_fpr = NULL; if (r_fprlen) *r_fprlen = 0; if (r_url) *r_url = NULL; return gpg_error (GPG_ERR_NOT_FOUND); } gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options, export_stats_t stats, kbnode_t *r_keyblock, void **r_data, size_t *r_datalen) { (void)ctrl; (void)keyspec; (void)options; (void)stats; *r_keyblock = NULL; *r_data = NULL; *r_datalen = 0; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } gpg_error_t tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, const char *user_id) { (void)ctrl; (void)fp; (void)pk; (void)user_id; return gpg_error (GPG_ERR_GENERAL); } gpg_error_t tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id, enum tofu_policy *policy) { (void)ctrl; (void)pk; (void)user_id; (void)policy; return gpg_error (GPG_ERR_GENERAL); } const char * tofu_policy_str (enum tofu_policy policy) { (void)policy; return "unknown"; } void tofu_begin_batch_update (ctrl_t ctrl) { (void)ctrl; } void tofu_end_batch_update (ctrl_t ctrl) { (void)ctrl; } gpg_error_t tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb) { (void) ctrl; (void) kb; return 0; } int get_revocation_reason (PKT_signature *sig, char **r_reason, char **r_comment, size_t *r_commentlen) { (void)sig; (void)r_commentlen; if (r_reason) *r_reason = NULL; if (r_comment) *r_comment = NULL; return 0; } diff --git a/g13/g13-syshelp.c b/g13/g13-syshelp.c index 4ce74ee4c..205456568 100644 --- a/g13/g13-syshelp.c +++ b/g13/g13-syshelp.c @@ -1,749 +1,753 @@ /* g13-syshelp.c - Helper for disk key management with GnuPG * Copyright (C) 2015 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include +/* We don't want to have the macros from gpgrt here until we have + * completely replaced this module by the one from gpgrt. */ +#undef GPGRT_ENABLE_ARGPARSE_MACROS #include #include #include #include #include #include #include #include #ifdef HAVE_PWD_H # include #endif #include #define INCLUDED_BY_MAIN_MODULE 1 #include "g13-syshelp.h" #include #include #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/init.h" #include "keyblob.h" +#include "../common/argparse.h" /* temporary hack. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oRecipient = 'r', aGPGConfList = 500, oDebug, oDebugLevel, oDebugAll, oDebugNone, oDebugWait, oDebugAllowCoreDump, oLogFile, oNoLogFile, oAuditLog, oOutput, oAgentProgram, oGpgProgram, oType, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oStatusFD, oLoggerFD, oNoVerbose, oNoSecmemWarn, oHomedir, oDryRun, oNoDetach, oNoRandomSeedFile, oFakedSystemTime }; static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", N_("|LEVEL|set the debugging level to LEVEL")), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_n (oDebugNone, "debug-none", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MOUNT_VALUE , "mount" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_IPC_VALUE , "ipc" }, { 0, NULL } }; /* The timer tick interval used by the idle task. */ #define TIMERTICK_INTERVAL_SEC (1) /* It is possible that we are currently running under setuid permissions. */ static int maybe_setuid = 1; /* Helper to implement --debug-level and --debug. */ static const char *debug_level; static unsigned int debug_value; /* Local prototypes. */ static void g13_syshelp_deinit_default_ctrl (ctrl_t ctrl); static void release_tab_items (tab_item_t tab); static tab_item_t parse_g13tab (const char *username); static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "@G13@-syshelp (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); break; case 1: case 40: p = _("Usage: @G13@-syshelp [options] [files] (-h for help)"); break; case 41: p = _("Syntax: @G13@-syshelp [options] [files]\n" "Helper to perform root-only tasks for g13\n"); break; case 31: p = "\nHome: "; break; case 32: p = gnupg_homedir (); break; default: p = NULL; break; } return p; } /* Setup the debugging. With a DEBUG_LEVEL of NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; and individual debugging flags will be added on top. */ 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|DBG_MOUNT_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE; else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE|DBG_MOUNT_VALUE|DBG_CRYPTO_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* if (numok) */ /* opt.debug &= ~(DBG_HASHING_VALUE); */ } else { log_error (_("invalid debug-level '%s' given\n"), debug_level); g13_exit(2); } opt.debug |= debug_value; if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug) opt.quiet = 0; if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } int main ( int argc, char **argv) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; gpg_error_t err = 0; /* const char *fname; */ int may_coredump; FILE *configfp = NULL; char *configname = NULL; unsigned configlineno; int parse_debug = 0; int no_more_options = 0; int default_config =1; char *logfile = NULL; /* int debug_wait = 0; */ int use_random_seed = 1; /* int nodetach = 0; */ /* int nokeysetup = 0; */ struct server_control_s ctrl; /*mtrace();*/ early_system_init (); gnupg_reopen_std (G13_NAME "-syshelp"); set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix (G13_NAME "-syshelp", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); /* Take extra care of the random pool. */ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps (); g13_init_signals (); dotlock_create (NULL, 0); /* Register locking cleanup. */ opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); /* Fixme: We enable verbose mode here because there is currently no way to do this when starting g13-syshelp. To fix that we should add a g13-syshelp.conf file in /etc/gnupg. */ opt.verbose = 1; /* First check whether we have a debug option on the commandline. */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) parse_debug++; } /* Initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); maybe_setuid = 0; /* Now we are now working under our real uid */ /* Setup malloc hooks. */ { struct assuan_malloc_hooks malloc_hooks; malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); } /* Prepare libassuan. */ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); /*assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);*/ setup_libassuan_logging (&opt.debug, NULL); /* Setup a default control structure for command line mode. */ memset (&ctrl, 0, sizeof ctrl); g13_syshelp_init_default_ctrl (&ctrl); ctrl.no_server = 1; ctrl.status_fd = -1; /* No status output. */ if (default_config ) configname = make_filename (gnupg_sysconfdir (), G13_NAME"-syshelp.conf", NULL); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags = 1; /* Do not remove the args. */ next_pass: if (configname) { configlineno = 0; configfp = fopen (configname, "r"); if (!configfp) { if (default_config) { if (parse_debug) log_info (_("NOTE: no default option file '%s'\n"), configname); } else { log_error (_("option file '%s': %s\n"), configname, strerror(errno)); g13_exit(2); } xfree (configname); configname = NULL; } if (parse_debug && configname) log_info (_("reading options from '%s'\n"), configname); default_config = 0; } while (!no_more_options && optfile_parse (configfp, configname, &configlineno, &pargs, opts)) { switch (pargs.r_opt) { case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oVerbose: opt.verbose++; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oLogFile: logfile = pargs.r.ret_str; break; case oNoLogFile: logfile = NULL; break; case oNoDetach: /*nodetach = 1; */break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oDebugAll: debug_value = ~0; break; case oDebugNone: debug_value = 0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugWait: /*debug_wait = pargs.r.ret_int; */break; case oDebugAllowCoreDump: may_coredump = enable_core_dumps (); break; case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break; case oLoggerFD: log_set_fd (pargs.r.ret_int ); break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oFakedSystemTime: { time_t faked_time = isotime2epoch (pargs.r.ret_str); if (faked_time == (time_t)(-1)) faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); gnupg_set_time (faked_time, 0); } break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oNoRandomSeedFile: use_random_seed = 0; break; default: pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR; break; } } if (configfp) { fclose (configfp); configfp = NULL; /* Keep a copy of the config filename. */ opt.config_filename = configname; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (!opt.config_filename) opt.config_filename = make_filename (gnupg_homedir (), G13_NAME".conf", NULL); if (log_get_errorcount(0)) g13_exit(2); /* Now that we have the options parsed we need to update the default control structure. */ g13_syshelp_init_default_ctrl (&ctrl); if (may_coredump && !opt.quiet) log_info (_("WARNING: program may create a core file!\n")); if (logfile) { log_set_file (logfile); log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } 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"); } /* Print any pending secure memory warnings. */ gcry_control (GCRYCTL_RESUME_SECMEM_WARN); /* Setup the debug flags for all subsystems. */ set_debug (); /* Install a regular exit handler to make real sure that the secure memory gets wiped out. */ g13_install_emergency_cleanup (); /* Terminate if we found any error until now. */ if (log_get_errorcount(0)) g13_exit (2); /* Set the standard GnuPG random seed file. */ if (use_random_seed) { char *p = make_filename (gnupg_homedir (), "random_seed", NULL); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); xfree(p); } /* Get the UID of the caller. */ #if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID) { const char *uidstr; struct passwd *pwd = NULL; uidstr = getenv ("USERV_UID"); /* Print a quick note if we are not started via userv. */ if (!uidstr) { if (getuid ()) { log_info ("WARNING: Not started via userv\n"); ctrl.fail_all_cmds = 1; } ctrl.client.uid = getuid (); } else { unsigned long myuid; errno = 0; myuid = strtoul (uidstr, NULL, 10); if (myuid == ULONG_MAX && errno) { log_info ("WARNING: Started via broken userv: %s\n", strerror (errno)); ctrl.fail_all_cmds = 1; ctrl.client.uid = getuid (); } else ctrl.client.uid = (uid_t)myuid; } pwd = getpwuid (ctrl.client.uid); if (!pwd || !*pwd->pw_name) { log_info ("WARNING: Name for UID not found: %s\n", strerror (errno)); ctrl.fail_all_cmds = 1; ctrl.client.uname = xstrdup ("?"); } else ctrl.client.uname = xstrdup (pwd->pw_name); /* Check that the user name does not contain a directory separator. */ if (strchr (ctrl.client.uname, '/')) { log_info ("WARNING: Invalid user name passed\n"); ctrl.fail_all_cmds = 1; } } #else /*!HAVE_PWD_H || !HAVE_GETPWUID*/ log_info ("WARNING: System does not support required syscalls\n"); ctrl.fail_all_cmds = 1; ctrl.client.uid = getuid (); ctrl.client.uname = xstrdup ("?"); #endif /*!HAVE_PWD_H || !HAVE_GETPWUID*/ /* Read the table entries for this user. */ if (!ctrl.fail_all_cmds && !(ctrl.client.tab = parse_g13tab (ctrl.client.uname))) ctrl.fail_all_cmds = 1; /* Start the server. */ err = syshelp_server (&ctrl); if (err) log_error ("server exited with error: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); /* Cleanup. */ g13_syshelp_deinit_default_ctrl (&ctrl); g13_exit (0); return 8; /*NOTREACHED*/ } /* Store defaults into the per-connection CTRL object. */ void g13_syshelp_init_default_ctrl (ctrl_t ctrl) { ctrl->conttype = CONTTYPE_DM_CRYPT; } /* Release all resources allocated by default in the CTRl object. */ static void g13_syshelp_deinit_default_ctrl (ctrl_t ctrl) { xfree (ctrl->client.uname); release_tab_items (ctrl->client.tab); } /* Release the list of g13tab itejms at TAB. */ static void release_tab_items (tab_item_t tab) { while (tab) { tab_item_t next = tab->next; xfree (tab->mountpoint); xfree (tab); tab = next; } } void g13_syshelp_i_know_what_i_am_doing (void) { const char * const yesfile = "Yes-g13-I-know-what-I-am-doing"; char *fname; fname = make_filename (gnupg_sysconfdir (), yesfile, NULL); if (access (fname, F_OK)) { log_info ("*******************************************************\n"); log_info ("* The G13 support for DM-Crypt is new and not matured.\n"); log_info ("* Bugs or improper use may delete all your disks!\n"); log_info ("* To confirm that you are ware of this risk, create\n"); log_info ("* the file '%s'.\n", fname); log_info ("*******************************************************\n"); exit (1); } xfree (fname); } /* Parse the /etc/gnupg/g13tab for user USERNAME. Return a table for the user on success. Return NULL on error and print diagnostics. */ static tab_item_t parse_g13tab (const char *username) { gpg_error_t err; int c, n; char line[512]; char *p; char *fname; estream_t fp; int lnr; char **words = NULL; tab_item_t table = NULL; tab_item_t *tabletail, ti; fname = make_filename (gnupg_sysconfdir (), G13_NAME"tab", NULL); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } tabletail = &table; err = 0; lnr = 0; while (es_fgets (line, DIM(line)-1, fp)) { lnr++; n = strlen (line); if (!n || line[n-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 (_("file '%s', line %d: %s\n"), fname, lnr, gpg_strerror (err)); continue; } line[--n] = 0; /* Chop the LF. */ if (n && line[n-1] == '\r') line[--n] = 0; /* Chop an optional CR. */ /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; if (!*p || *p == '#') continue; /* Parse the line. The format is * [