diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index e92e28a69..bb9ddb3cb 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1,3291 +1,3291 @@ /* 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 #include #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/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 handeld */ 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. */ if (!gpgconf_list) 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 tye 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) { 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' 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_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)); } else { /* The file exists or another error. Make sure we have sensible * permissions. We enforce rwx for user but keep existing group * permissions. Permissions for other are always cleared. */ 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 (gnupg_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); /* Check whether the scdaemon has died and cleanup in this case. */ agent_scd_check_aliveness (); /* 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) + && gnupg_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 (); 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 (data, 20, "request:"); */ 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 (data, 20, " reply:"); */ 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/common/mkdir_p.c b/common/mkdir_p.c index c26cfee01..6fb98c370 100644 --- a/common/mkdir_p.c +++ b/common/mkdir_p.c @@ -1,187 +1,187 @@ /* mkdir_p.c - Create a directory and any missing parents. * Copyright (C) 2015 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 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. * * 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 General Public License * along with this program; if not, see . */ #include #include #include #include #include #include "util.h" #include "stringhelp.h" #include "logging.h" #include "sysutils.h" #include "mkdir_p.h" gpg_error_t gnupg_amkdir_p (const char **directory_components) { gpg_error_t err = 0; int count; char **dirs; int i; for (count = 0; directory_components[count]; count ++) ; /* log_debug ("%s: %d directory components.\n", __func__, count); */ dirs = xtrycalloc (count, sizeof *dirs); if (!dirs) return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); for (i = 0; directory_components[i]; i ++) { if (i == 0) dirs[i] = make_filename_try (directory_components[i], NULL); else dirs[i] = make_filename_try (dirs[i-1], directory_components[i], NULL); if (!dirs[i]) { err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); goto out; } /* log_debug ("%s: Directory %d: `%s'.\n", __func__, i, dirs[i]); */ } for (i = count - 1; i >= 0; i --) { struct stat s; /* log_debug ("%s: stat(%s)\n", __func__, dirs[i]); */ - if (!stat (dirs[i], &s)) + if (!gnupg_stat (dirs[i], &s)) { if ( ! S_ISDIR (s.st_mode)) { /* log_debug ("%s: %s exists, but is not a directory!\n", */ /* __func__, dirs[i]); */ err = gpg_err_make (default_errsource, GPG_ERR_ENOTDIR); goto out; } else { /* Got a directory. */ /* log_debug ("%s: %s exists and is a directory!\n", */ /* __func__, dirs[i]); */ err = 0; break; } } else if (errno == ENOENT) /* This directory does not exist yet. Continue walking up the hierarchy. */ { /* log_debug ("%s: %s does not exist!\n", */ /* __func__, dirs[i]); */ continue; } else /* Some other error code. Die. Note: this could be ENOTDIR (we return this above), which means that a component of the path prefix is not a directory. */ { /* log_debug ("%s: stat(%s) => %s!\n", */ /* __func__, dirs[i], strerror (errno)); */ err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); goto out; } } assert (i >= -1); /* DIRS[I] exists. Start with the following entry. */ i ++; for (; i < count; i ++) { /* log_debug ("Creating directory: %s\n", dirs[i]); */ if (gnupg_mkdir (dirs[i], "-rwx")) { err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); goto out; } } out: for (i = 0; i < count; i ++) xfree (dirs[i]); xfree (dirs); /* log_debug ("%s: Returning %s\n", __func__, gpg_strerror (rc)); */ return err; } gpg_error_t gnupg_mkdir_p (const char *directory_component, ...) { va_list ap; gpg_error_t err = 0; int i; int space = 1; const char **dirs; dirs = xtrymalloc (space * sizeof (char *)); if (!dirs) return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); dirs[0] = directory_component; va_start (ap, directory_component); for (i = 1; dirs[i - 1]; i ++) { if (i == space) { const char **tmp_dirs; space = 2 * space; tmp_dirs = xtryrealloc (dirs, space * sizeof (char *)); if (!tmp_dirs) { err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); break; } dirs = tmp_dirs; } dirs[i] = va_arg (ap, char *); } va_end (ap); if (!err) err = gnupg_amkdir_p (dirs); xfree (dirs); return err; } diff --git a/common/sysutils.c b/common/sysutils.c index e06be057f..842efb975 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -1,1437 +1,1480 @@ /* sysutils.c - system helpers * Copyright (C) 1991-2001, 2003-2004, * 2006-2008 Free Software Foundation, Inc. * Copyright (C) 2013-2016 Werner Koch * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it 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. * * 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 General Public License * along with this program; if not, see . */ #include #ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */ # undef HAVE_NPTH # undef USE_NPTH #endif #include #include #include #include #include #include #ifdef HAVE_STAT # include #endif #if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 # include # include #endif #include #ifdef HAVE_SETRLIMIT # include # include #endif #ifdef HAVE_W32_SYSTEM # if WINVER < 0x0500 # define WINVER 0x0500 /* Required for AllowSetForegroundWindow. */ # endif # ifdef HAVE_WINSOCK2_H # include # endif # include #else /*!HAVE_W32_SYSTEM*/ # include # include #endif #ifdef HAVE_INOTIFY_INIT # include #endif /*HAVE_INOTIFY_INIT*/ #ifdef HAVE_NPTH # include #endif #include #include #include "util.h" #include "i18n.h" #include "sysutils.h" #define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A')) /* Flag to tell whether special file names are enabled. See gpg.c for * an explanation of these file names. */ static int allow_special_filenames; static GPGRT_INLINE gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } static GPGRT_INLINE gpg_error_t my_error (int e) { return gpg_err_make (default_errsource, (e)); } #if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 #warning using trap_unaligned static int setsysinfo(unsigned long op, void *buffer, unsigned long size, int *start, void *arg, unsigned long flag) { return syscall(__NR_osf_setsysinfo, op, buffer, size, start, arg, flag); } void trap_unaligned(void) { unsigned int buf[2]; buf[0] = SSIN_UACPROC; buf[1] = UAC_SIGBUS | UAC_NOPRINT; setsysinfo(SSI_NVPAIRS, buf, 1, 0, 0, 0); } #else void trap_unaligned(void) { /* dummy */ } #endif int disable_core_dumps (void) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else # ifdef HAVE_SETRLIMIT struct rlimit limit; /* We only set the current limit unless we were not able to retrieve the old value. */ if (getrlimit (RLIMIT_CORE, &limit)) limit.rlim_max = 0; limit.rlim_cur = 0; if( !setrlimit (RLIMIT_CORE, &limit) ) return 0; if( errno != EINVAL && errno != ENOSYS ) log_fatal (_("can't disable core dumps: %s\n"), strerror(errno) ); #endif return 1; #endif } int enable_core_dumps (void) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else # ifdef HAVE_SETRLIMIT struct rlimit limit; if (getrlimit (RLIMIT_CORE, &limit)) return 1; limit.rlim_cur = limit.rlim_max; setrlimit (RLIMIT_CORE, &limit); return 1; /* We always return true because this function is merely a debugging aid. */ # endif return 1; #endif } #ifdef HAVE_W32_SYSTEM static int any8bitchar (const char *string) { if (string) for ( ; *string; string++) if ((*string & 0x80)) return 1; return 0; } #endif /*HAVE_W32_SYSTEM*/ /* Allow the use of special "-&nnn" style file names. */ void enable_special_filenames (void) { allow_special_filenames = 1; } /* Return a string which is used as a kind of process ID. */ const byte * get_session_marker (size_t *rlen) { static byte marker[SIZEOF_UNSIGNED_LONG*2]; static int initialized; if (!initialized) { gcry_create_nonce (marker, sizeof marker); initialized = 1; } *rlen = sizeof (marker); return marker; } /* Return a random number in an unsigned int. */ unsigned int get_uint_nonce (void) { unsigned int value; gcry_create_nonce (&value, sizeof value); return value; } #if 0 /* not yet needed - Note that this will require inclusion of cmacros.am in Makefile.am */ int check_permissions(const char *path,int extension,int checkonly) { #if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM) char *tmppath; struct stat statbuf; int ret=1; int isdir=0; if(opt.no_perm_warn) return 0; if(extension && path[0]!=DIRSEP_C) { if(strchr(path,DIRSEP_C)) tmppath=make_filename(path,NULL); else tmppath=make_filename(GNUPG_LIBDIR,path,NULL); } else tmppath=m_strdup(path); /* It's okay if the file doesn't exist */ if(stat(tmppath,&statbuf)!=0) { ret=0; goto end; } isdir=S_ISDIR(statbuf.st_mode); /* Per-user files must be owned by the user. Extensions must be owned by the user or root. */ if((!extension && statbuf.st_uid != getuid()) || (extension && statbuf.st_uid!=0 && statbuf.st_uid!=getuid())) { if(!checkonly) log_info(_("Warning: unsafe ownership on %s \"%s\"\n"), isdir?"directory":extension?"extension":"file",path); goto end; } /* This works for both directories and files - basically, we don't care what the owner permissions are, so long as the group and other permissions are 0 for per-user files, and non-writable for extensions. */ if((extension && (statbuf.st_mode & (S_IWGRP|S_IWOTH)) !=0) || (!extension && (statbuf.st_mode & (S_IRWXG|S_IRWXO)) != 0)) { char *dir; /* However, if the directory the directory/file is in is owned by the user and is 700, then this is not a problem. 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,&statbuf)==0 && statbuf.st_uid==getuid() && S_ISDIR(statbuf.st_mode) && (statbuf.st_mode & (S_IRWXG|S_IRWXO))==0) { xfree (dir); ret=0; goto end; } m_free(dir); if(!checkonly) log_info(_("Warning: unsafe permissions on %s \"%s\"\n"), isdir?"directory":extension?"extension":"file",path); goto end; } ret=0; end: m_free(tmppath); return ret; #endif /* HAVE_STAT && !HAVE_DOSISH_SYSTEM */ return 0; } #endif /* Wrapper around the usual sleep function. This one won't wake up before the sleep time has really elapsed. When build with Pth it merely calls pth_sleep and thus suspends only the current thread. */ void gnupg_sleep (unsigned int seconds) { #ifdef USE_NPTH npth_sleep (seconds); #else /* Fixme: make sure that a sleep won't wake up to early. */ # ifdef HAVE_W32_SYSTEM Sleep (seconds*1000); # else sleep (seconds); # endif #endif } /* Wrapper around the platforms usleep function. This one won't wake * up before the sleep time has really elapsed. When build with nPth * it merely calls npth_usleep and thus suspends only the current * thread. */ void gnupg_usleep (unsigned int usecs) { #if defined(USE_NPTH) npth_usleep (usecs); #elif defined(HAVE_W32_SYSTEM) Sleep ((usecs + 999) / 1000); #elif defined(HAVE_NANOSLEEP) if (usecs) { struct timespec req; struct timespec rem; req.tv_sec = usecs / 1000000; req.tv_nsec = (usecs % 1000000) * 1000; while (nanosleep (&req, &rem) < 0 && errno == EINTR) req = rem; } #else /*Standard Unix*/ if (usecs) { struct timeval tv; tv.tv_sec = usecs / 1000000; tv.tv_usec = usecs % 1000000; select (0, NULL, NULL, NULL, &tv); } #endif } /* This function is a NOP for POSIX systems but required under Windows as the file handles as returned by OS calls (like CreateFile) are different from the libc file descriptors (like open). This function translates system file handles to libc file handles. FOR_WRITE gives the direction of the handle. */ int translate_sys2libc_fd (gnupg_fd_t fd, int for_write) { #if defined(HAVE_W32CE_SYSTEM) (void)for_write; return (int) fd; #elif defined(HAVE_W32_SYSTEM) int x; if (fd == GNUPG_INVALID_FD) return -1; /* Note that _open_osfhandle is currently defined to take and return a long. */ x = _open_osfhandle ((long)fd, for_write ? 1 : 0); if (x == -1) log_error ("failed to translate osfhandle %p\n", (void *) fd); return x; #else /*!HAVE_W32_SYSTEM */ (void)for_write; return fd; #endif } /* This is the same as translate_sys2libc_fd but takes an integer which is assumed to be such an system handle. On WindowsCE the passed FD is a rendezvous ID and the function finishes the pipe creation. */ int translate_sys2libc_fd_int (int fd, int for_write) { #if HAVE_W32CE_SYSTEM fd = (int) _assuan_w32ce_finish_pipe (fd, for_write); return translate_sys2libc_fd ((void*)fd, for_write); #elif HAVE_W32_SYSTEM if (fd <= 2) return fd; /* Do not do this for error, stdin, stdout, stderr. */ return translate_sys2libc_fd ((void*)fd, for_write); #else (void)for_write; return fd; #endif } /* Check whether FNAME has the form "-&nnnn", where N is a non-zero * number. Returns this number or -1 if it is not the case. If the * caller wants to use the file descriptor for writing FOR_WRITE shall * be set to 1. If NOTRANSLATE is set the Windows specific mapping is * not done. */ int check_special_filename (const char *fname, int for_write, int notranslate) { if (allow_special_filenames && fname && *fname == '-' && fname[1] == '&') { int i; fname += 2; for (i=0; digitp (fname+i); i++ ) ; if (!fname[i]) return notranslate? atoi (fname) /**/ : translate_sys2libc_fd_int (atoi (fname), for_write); } return -1; } /* Replacement for tmpfile(). This is required because the tmpfile function of Windows' runtime library is broken, insecure, ignores TMPDIR and so on. In addition we create a file with an inheritable handle. */ FILE * gnupg_tmpfile (void) { #ifdef HAVE_W32_SYSTEM int attempts, n; #ifdef HAVE_W32CE_SYSTEM wchar_t buffer[MAX_PATH+7+12+1]; # define mystrlen(a) wcslen (a) wchar_t *name, *p; #else char buffer[MAX_PATH+7+12+1]; # define mystrlen(a) strlen (a) char *name, *p; #endif HANDLE file; int pid = GetCurrentProcessId (); unsigned int value; int i; SECURITY_ATTRIBUTES sec_attr; memset (&sec_attr, 0, sizeof sec_attr ); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = TRUE; n = GetTempPath (MAX_PATH+1, buffer); if (!n || n > MAX_PATH || mystrlen (buffer) > MAX_PATH) { gpg_err_set_errno (ENOENT); return NULL; } p = buffer + mystrlen (buffer); #ifdef HAVE_W32CE_SYSTEM wcscpy (p, L"_gnupg"); p += 7; #else p = stpcpy (p, "_gnupg"); #endif /* We try to create the directory but don't care about an error as it may already exist and the CreateFile would throw an error anyway. */ CreateDirectory (buffer, NULL); *p++ = '\\'; name = p; for (attempts=0; attempts < 10; attempts++) { p = name; value = (GetTickCount () ^ ((pid<<16) & 0xffff0000)); for (i=0; i < 8; i++) { *p++ = tohex (((value >> 28) & 0x0f)); value <<= 4; } #ifdef HAVE_W32CE_SYSTEM wcscpy (p, L".tmp"); #else strcpy (p, ".tmp"); #endif file = CreateFile (buffer, GENERIC_READ | GENERIC_WRITE, 0, &sec_attr, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); if (file != INVALID_HANDLE_VALUE) { FILE *fp; #ifdef HAVE_W32CE_SYSTEM int fd = (int)file; fp = _wfdopen (fd, L"w+b"); #else int fd = _open_osfhandle ((long)file, 0); if (fd == -1) { CloseHandle (file); return NULL; } fp = fdopen (fd, "w+b"); #endif if (!fp) { int save = errno; close (fd); gpg_err_set_errno (save); return NULL; } return fp; } Sleep (1); /* One ms as this is the granularity of GetTickCount. */ } gpg_err_set_errno (ENOENT); return NULL; #undef mystrlen #else /*!HAVE_W32_SYSTEM*/ return tmpfile (); #endif /*!HAVE_W32_SYSTEM*/ } /* Make sure that the standard file descriptors are opened. Obviously some folks close them before an exec and the next file we open will get one of them assigned and thus any output (i.e. diagnostics) end up in that file (e.g. the trustdb). Not actually a gpg problem as this will happen with almost all utilities when called in a wrong way. However we try to minimize the damage here and raise awareness of the problem. Must be called before we open any files! */ void gnupg_reopen_std (const char *pgmname) { #ifdef F_GETFD int did_stdin = 0; int did_stdout = 0; int did_stderr = 0; FILE *complain; if (fcntl (STDIN_FILENO, F_GETFD) == -1 && errno ==EBADF) { if (open ("/dev/null",O_RDONLY) == STDIN_FILENO) did_stdin = 1; else did_stdin = 2; } if (fcntl (STDOUT_FILENO, F_GETFD) == -1 && errno == EBADF) { if (open ("/dev/null",O_WRONLY) == STDOUT_FILENO) did_stdout = 1; else did_stdout = 2; } if (fcntl (STDERR_FILENO, F_GETFD)==-1 && errno==EBADF) { if (open ("/dev/null", O_WRONLY) == STDERR_FILENO) did_stderr = 1; else did_stderr = 2; } /* It's hard to log this sort of thing since the filehandle we would complain to may be closed... */ if (!did_stderr) complain = stderr; else if (!did_stdout) complain = stdout; else complain = NULL; if (complain) { if (did_stdin == 1) fprintf (complain, "%s: WARNING: standard input reopened\n", pgmname); if (did_stdout == 1) fprintf (complain, "%s: WARNING: standard output reopened\n", pgmname); if (did_stderr == 1) fprintf (complain, "%s: WARNING: standard error reopened\n", pgmname); if (did_stdin == 2 || did_stdout == 2 || did_stderr == 2) fprintf(complain,"%s: fatal: unable to reopen standard input," " output, or error\n", pgmname); } if (did_stdin == 2 || did_stdout == 2 || did_stderr == 2) exit (3); #else /* !F_GETFD */ (void)pgmname; #endif } /* Hack required for Windows. */ void gnupg_allow_set_foregound_window (pid_t pid) { if (!pid) log_info ("%s called with invalid pid %lu\n", "gnupg_allow_set_foregound_window", (unsigned long)pid); #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) else if (!AllowSetForegroundWindow ((pid_t)pid == (pid_t)(-1)?ASFW_ANY:pid)) log_info ("AllowSetForegroundWindow(%lu) failed: %s\n", (unsigned long)pid, w32_strerror (-1)); #endif } int gnupg_remove (const char *fname) { #ifdef HAVE_W32CE_SYSTEM int rc; wchar_t *wfname; wfname = utf8_to_wchar (fname); if (!wfname) rc = 0; else { rc = DeleteFile (wfname); xfree (wfname); } if (!rc) return -1; /* ERRNO is automagically provided by gpg-error.h. */ return 0; #else return remove (fname); #endif } /* Wrapper for rename(2) to handle Windows peculiarities. If * BLOCK_SIGNALS is not NULL and points to a variable set to true, all * signals will be blocked by calling gnupg_block_all_signals; the * caller needs to call gnupg_unblock_all_signals if that variable is * still set to true on return. */ gpg_error_t gnupg_rename_file (const char *oldname, const char *newname, int *block_signals) { gpg_error_t err = 0; if (block_signals && *block_signals) gnupg_block_all_signals (); #ifdef HAVE_DOSISH_SYSTEM { int wtime = 0; gnupg_remove (newname); again: if (rename (oldname, newname)) { if (GetLastError () == ERROR_SHARING_VIOLATION) { /* Another process has the file open. We do not use a * lock for read but instead we wait until the other * process has closed the file. This may take long but * that would also be the case with a dotlock approach for * read and write. Note that we don't need this on Unix * due to the inode concept. * * So let's wait until the rename has worked. The retry * intervals are 50, 100, 200, 400, 800, 50ms, ... */ if (!wtime || wtime >= 800) wtime = 50; else wtime *= 2; if (wtime >= 800) log_info (_("waiting for file '%s' to become accessible ...\n"), oldname); Sleep (wtime); goto again; } err = my_error_from_syserror (); } } #else /* Unix */ { #ifdef __riscos__ gnupg_remove (newname); #endif if (rename (oldname, newname) ) err = my_error_from_syserror (); } #endif /* Unix */ if (block_signals && *block_signals && err) { gnupg_unblock_all_signals (); *block_signals = 0; } if (err) log_error (_("renaming '%s' to '%s' failed: %s\n"), oldname, newname, gpg_strerror (err)); return err; } #ifndef HAVE_W32_SYSTEM static mode_t modestr_to_mode (const char *modestr, mode_t oldmode) { static struct { char letter; mode_t value; } table[] = { { '-', 0 }, { 'r', S_IRUSR }, { 'w', S_IWUSR }, { 'x', S_IXUSR }, { 'r', S_IRGRP }, { 'w', S_IWGRP }, { 'x', S_IXGRP }, { 'r', S_IROTH }, { 'w', S_IWOTH }, { 'x', S_IXOTH } }; int idx; mode_t mode = 0; /* For now we only support a string as used by ls(1) and no octal * numbers. The first character must be a dash. */ for (idx=0; idx < 10 && *modestr; idx++, modestr++) { if (*modestr == table[idx].letter) mode |= table[idx].value; else if (*modestr == '.') { if (!idx) ; /* Skip the dummy. */ else if ((oldmode & table[idx].value)) mode |= (oldmode & table[idx].value); else mode &= ~(oldmode & table[idx].value); } else if (*modestr != '-') break; } return mode; } #endif /* A wrapper around mkdir which takes a string for the mode argument. This makes it easier to handle the mode argument which is not defined on all systems. The format of the modestring is "-rwxrwxrwx" '-' is a don't care or not set. 'r', 'w', 'x' are read allowed, write allowed, execution allowed with the first group for the user, the second for the group and the third for all others. If the string is shorter than above the missing mode characters are meant to be not set. */ int gnupg_mkdir (const char *name, const char *modestr) { #if GPG_ERROR_VERSION_NUMBER < 0x011c00 /* 1.28 */ #ifdef HAVE_W32CE_SYSTEM wchar_t *wname; (void)modestr; wname = utf8_to_wchar (name); if (!wname) return -1; if (!CreateDirectoryW (wname, NULL)) { xfree (wname); return -1; /* ERRNO is automagically provided by gpg-error.h. */ } xfree (wname); return 0; #elif MKDIR_TAKES_ONE_ARG (void)modestr; /* Note: In the case of W32 we better use CreateDirectory and try to set appropriate permissions. However using mkdir is easier because this sets ERRNO. */ return mkdir (name); #else return mkdir (name, modestr_to_mode (modestr, 0)); #endif #else /* Note that gpgrt_mkdir also sets ERRNO in addition to returing an * gpg-error style error code. */ return gpgrt_mkdir (name, modestr); #endif } /* A simple wrapper around chdir. NAME is expected to be utf8 * encoded. */ int gnupg_chdir (const char *name) { #if GPG_ERROR_VERSION_NUMBER < 0x011c00 /* 1.28 */ return chdir (name); #else /* Use the improved version from libgpg_error. */ /* Note that gpgrt_chdir also sets ERRNO in addition to returning a * gpg-error style error code. */ return gpgrt_chdir (name); #endif } /* A wrapper around chmod which takes a string for the mode argument. This makes it easier to handle the mode argument which is not defined on all systems. The format of the modestring is the same as for gnupg_mkdir with extra feature that a '.' keeps the original mode bit. */ int gnupg_chmod (const char *name, const char *modestr) { #ifdef HAVE_W32_SYSTEM (void)name; (void)modestr; return 0; #else mode_t oldmode; if (strchr (modestr, '.')) { /* Get the old mode so that a '.' can copy that bit. */ struct stat st; if (stat (name, &st)) return -1; oldmode = st.st_mode; } else oldmode = 0; return chmod (name, modestr_to_mode (modestr, oldmode)); #endif } /* Our version of mkdtemp. The API is identical to POSIX.1-2008 version. We do not use a system provided mkdtemp because we have a good RNG instantly available and this way we don't have diverging versions. */ char * gnupg_mkdtemp (char *tmpl) { /* A lower bound on the number of temporary files to attempt to generate. The maximum total number of temporary file names that can exist for a given template is 62**6 (5*36**3 for Windows). It should never be necessary to try all these combinations. Instead if a reasonable number of names is tried (we define reasonable as 62**3 or 5*36**3) fail to give the system administrator the chance to remove the problems. */ #ifdef HAVE_W32_SYSTEM static const char letters[] = "abcdefghijklmnopqrstuvwxyz0123456789"; # define NUMBER_OF_LETTERS 36 # define ATTEMPTS_MIN (5 * 36 * 36 * 36) #else static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; # define NUMBER_OF_LETTERS 62 # define ATTEMPTS_MIN (62 * 62 * 62) #endif int len; char *XXXXXX; uint64_t value; unsigned int count; int save_errno = errno; /* The number of times to attempt to generate a temporary file. To conform to POSIX, this must be no smaller than TMP_MAX. */ #if ATTEMPTS_MIN < TMP_MAX unsigned int attempts = TMP_MAX; #else unsigned int attempts = ATTEMPTS_MIN; #endif len = strlen (tmpl); if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) { gpg_err_set_errno (EINVAL); return NULL; } /* This is where the Xs start. */ XXXXXX = &tmpl[len - 6]; /* Get a random start value. */ gcry_create_nonce (&value, sizeof value); /* Loop until a directory was created. */ for (count = 0; count < attempts; value += 7777, ++count) { uint64_t v = value; /* Fill in the random bits. */ XXXXXX[0] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[1] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[2] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[3] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[4] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[5] = letters[v % NUMBER_OF_LETTERS]; if (!gnupg_mkdir (tmpl, "-rwx")) { gpg_err_set_errno (save_errno); return tmpl; } if (errno != EEXIST) return NULL; } /* We got out of the loop because we ran out of combinations to try. */ gpg_err_set_errno (EEXIST); return NULL; } int gnupg_setenv (const char *name, const char *value, int overwrite) { #ifdef HAVE_W32CE_SYSTEM (void)name; (void)value; (void)overwrite; return 0; #else /*!W32CE*/ # ifdef HAVE_W32_SYSTEM /* Windows maintains (at least) two sets of environment variables. One set can be accessed by GetEnvironmentVariable and SetEnvironmentVariable. This set is inherited by the children. The other set is maintained in the C runtime, and is accessed using getenv and putenv. We try to keep them in sync by modifying both sets. */ { int exists; char tmpbuf[10]; exists = GetEnvironmentVariable (name, tmpbuf, sizeof tmpbuf); if ((! exists || overwrite) && !SetEnvironmentVariable (name, value)) { gpg_err_set_errno (EINVAL); /* (Might also be ENOMEM.) */ return -1; } } # endif /*W32*/ # ifdef HAVE_SETENV return setenv (name, value, overwrite); # else /*!HAVE_SETENV*/ if (! getenv (name) || overwrite) { char *buf; (void)overwrite; if (!name || !value) { gpg_err_set_errno (EINVAL); return -1; } buf = strconcat (name, "=", value, NULL); if (!buf) return -1; # if __GNUC__ # warning no setenv - using putenv but leaking memory. # endif return putenv (buf); } return 0; # endif /*!HAVE_SETENV*/ #endif /*!W32CE*/ } int gnupg_unsetenv (const char *name) { #ifdef HAVE_W32CE_SYSTEM (void)name; return 0; #else /*!W32CE*/ # ifdef HAVE_W32_SYSTEM /* Windows maintains (at least) two sets of environment variables. One set can be accessed by GetEnvironmentVariable and SetEnvironmentVariable. This set is inherited by the children. The other set is maintained in the C runtime, and is accessed using getenv and putenv. We try to keep them in sync by modifying both sets. */ if (!SetEnvironmentVariable (name, NULL)) { gpg_err_set_errno (EINVAL); /* (Might also be ENOMEM.) */ return -1; } # endif /*W32*/ # ifdef HAVE_UNSETENV return unsetenv (name); # else /*!HAVE_UNSETENV*/ { char *buf; if (!name) { gpg_err_set_errno (EINVAL); return -1; } buf = xtrystrdup (name); if (!buf) return -1; # if __GNUC__ # warning no unsetenv - trying putenv but leaking memory. # endif return putenv (buf); } # endif /*!HAVE_UNSETENV*/ #endif /*!W32CE*/ } /* Return the current working directory as a malloced string. Return NULL and sets ERRNO on error. */ char * gnupg_getcwd (void) { #if GPGRT_VERSION_NUMBER < 0x012800 /* 1.40 */ /* We use the old code which is okay despite that it does not * support Unicode on Windows. For Windows this doesn't matter * because we use the latest gpgrt anyway. */ char *buffer; size_t size = 100; for (;;) { buffer = xtrymalloc (size+1); if (!buffer) return NULL; # ifdef HAVE_W32CE_SYSTEM strcpy (buffer, "/"); /* Always "/". */ return buffer; # else if (getcwd (buffer, size) == buffer) return buffer; xfree (buffer); if (errno != ERANGE) return NULL; size *= 2; # endif } #else return gpgrt_getcwd (); #endif } /* A simple wrapper around access. NAME is expected to be utf8 * encoded. This function returns an error code and sets ERRNO. */ gpg_err_code_t gnupg_access (const char *name, int mode) { #if GPGRT_VERSION_NUMBER < 0x012800 /* 1.40 */ # ifdef HAVE_W32_SYSTEM wchar_t *wfname; wfname = utf8_to_wchar (fname); if (!wfname) ec = gpg_err_code_from_syserror (); else { ec = _waccess (wfname, mode)? gpg_err_code_from_syserror () : 0; xfree (wfname); } # else return access (name, mode)? gpg_err_code_from_syserror () : 0; # endif #else /* gpgrt 1.40 or newer. */ return gpgrt_access (name, mode); #endif } + +/* A wrapper around stat to handle Unicode file names under Windows. */ +#ifdef HAVE_STAT +int +gnupg_stat (const char *name, struct stat *statbuf) +{ +# ifdef HAVE_W32_SYSTEM + if (any8bitchar (name)) + { + wchar_t *wname; + struct _stat32 st32; + int ret; + + wname = utf8_to_wchar (name); + if (!wname) + return -1; + ret = _wstat (wname, &st32); + xfree (wname); + if (!ret) + { + statbuf->st_dev = st32.st_dev; + statbuf->st_ino = st32.st_ino; + statbuf->st_mode = st32.st_mode; + statbuf->st_nlink = st32.st_nlink; + statbuf->st_uid = st32.st_uid; + statbuf->st_gid = st32.st_gid; + statbuf->st_rdev = st32.st_rdev; + statbuf->st_size = st32.st_size; + statbuf->st_atime = st32.st_atime; + statbuf->st_mtime = st32.st_mtime; + statbuf->st_ctime = st32.st_ctime; + } + return ret; + } + else + return stat (name, statbuf); +# else + return stat (name, statbuf); +# endif +} +#endif /*HAVE_STAT*/ + + /* A wrapper around open to handle Unicode file names under Windows. */ int gnupg_open (const char *name, int flags, unsigned int mode) { #ifdef HAVE_W32_SYSTEM if (any8bitchar (name)) { wchar_t *wname; int ret; wname = utf8_to_wchar (name); if (!wname) return -1; ret = _wopen (wname, flags, mode); xfree (wname); return ret; } else return open (name, flags, mode); #else return open (name, flags, mode); #endif } #ifdef HAVE_W32CE_SYSTEM /* There is a isatty function declaration in cegcc but it does not make sense, thus we redefine it. */ int _gnupg_isatty (int fd) { (void)fd; return 0; } #endif #ifdef HAVE_W32CE_SYSTEM /* Replacement for getenv which takes care of the our use of getenv. The code is not thread safe but we expect it to work in all cases because it is called for the first time early enough. */ char * _gnupg_getenv (const char *name) { static int initialized; static char *assuan_debug; if (!initialized) { assuan_debug = read_w32_registry_string (NULL, "\\Software\\GNU\\libassuan", "debug"); initialized = 1; } if (!strcmp (name, "ASSUAN_DEBUG")) return assuan_debug; else return NULL; } #endif /*HAVE_W32CE_SYSTEM*/ #ifdef HAVE_W32_SYSTEM /* Return the user's security identifier from the current process. */ PSID w32_get_user_sid (void) { int okay = 0; HANDLE proc = NULL; HANDLE token = NULL; TOKEN_USER *user = NULL; PSID sid = NULL; DWORD tokenlen, sidlen; proc = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); if (!proc) goto leave; if (!OpenProcessToken (proc, TOKEN_QUERY, &token)) goto leave; if (!GetTokenInformation (token, TokenUser, NULL, 0, &tokenlen) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto leave; user = xtrymalloc (tokenlen); if (!user) goto leave; if (!GetTokenInformation (token, TokenUser, user, tokenlen, &tokenlen)) goto leave; if (!IsValidSid (user->User.Sid)) goto leave; sidlen = GetLengthSid (user->User.Sid); sid = xtrymalloc (sidlen); if (!sid) goto leave; if (!CopySid (sidlen, sid, user->User.Sid)) goto leave; okay = 1; leave: xfree (user); if (token) CloseHandle (token); if (proc) CloseHandle (proc); if (!okay) { xfree (sid); sid = NULL; } return sid; } #endif /*HAVE_W32_SYSTEM*/ /* Support for inotify under Linux. */ /* Store a new inotify file handle for FNAME at R_FD or return an * error code. This file descriptor watch the removal of FNAME. */ gpg_error_t gnupg_inotify_watch_delete_self (int *r_fd, const char *fname) { #if HAVE_INOTIFY_INIT gpg_error_t err; int fd; *r_fd = -1; if (!fname) return my_error (GPG_ERR_INV_VALUE); fd = inotify_init (); if (fd == -1) return my_error_from_syserror (); if (inotify_add_watch (fd, fname, IN_DELETE_SELF) == -1) { err = my_error_from_syserror (); close (fd); return err; } *r_fd = fd; return 0; #else /*!HAVE_INOTIFY_INIT*/ (void)fname; *r_fd = -1; return my_error (GPG_ERR_NOT_SUPPORTED); #endif /*!HAVE_INOTIFY_INIT*/ } /* Store a new inotify file handle for SOCKET_NAME at R_FD or return * an error code. */ gpg_error_t gnupg_inotify_watch_socket (int *r_fd, const char *socket_name) { #if HAVE_INOTIFY_INIT gpg_error_t err; char *fname; int fd; char *p; *r_fd = -1; if (!socket_name) return my_error (GPG_ERR_INV_VALUE); fname = xtrystrdup (socket_name); if (!fname) return my_error_from_syserror (); fd = inotify_init (); if (fd == -1) { err = my_error_from_syserror (); xfree (fname); return err; } /* We need to watch the directory for the file because there won't * be an IN_DELETE_SELF for a socket file. To handle a removal of * the directory we also watch the directory itself. */ p = strrchr (fname, '/'); if (p) *p = 0; if (inotify_add_watch (fd, fname, (IN_DELETE|IN_DELETE_SELF|IN_EXCL_UNLINK)) == -1) { err = my_error_from_syserror (); close (fd); xfree (fname); return err; } xfree (fname); *r_fd = fd; return 0; #else /*!HAVE_INOTIFY_INIT*/ (void)socket_name; *r_fd = -1; return my_error (GPG_ERR_NOT_SUPPORTED); #endif /*!HAVE_INOTIFY_INIT*/ } /* Read an inotify event and return true if it matches NAME or if it * sees an IN_DELETE_SELF event for the directory of NAME. */ int gnupg_inotify_has_name (int fd, const char *name) { #if USE_NPTH && HAVE_INOTIFY_INIT #define BUFSIZE_FOR_INOTIFY (sizeof (struct inotify_event) + 255 + 1) union { struct inotify_event ev; char _buf[sizeof (struct inotify_event) + 255 + 1]; } buf; struct inotify_event *evp; int n; n = npth_read (fd, &buf, sizeof buf); /* log_debug ("notify read: n=%d\n", n); */ evp = &buf.ev; while (n >= sizeof (struct inotify_event)) { /* log_debug (" mask=%x len=%u name=(%s)\n", */ /* evp->mask, (unsigned int)evp->len, evp->len? evp->name:""); */ if ((evp->mask & IN_UNMOUNT)) { /* log_debug (" found (dir unmounted)\n"); */ return 3; /* Directory was unmounted. */ } if ((evp->mask & IN_DELETE_SELF)) { /* log_debug (" found (dir removed)\n"); */ return 2; /* Directory was removed. */ } if ((evp->mask & IN_DELETE)) { if (evp->len >= strlen (name) && !strcmp (evp->name, name)) { /* log_debug (" found (file removed)\n"); */ return 1; /* File was removed. */ } } n -= sizeof (*evp) + evp->len; evp = (struct inotify_event *)(void *) ((char *)evp + sizeof (*evp) + evp->len); } #else /*!(USE_NPTH && HAVE_INOTIFY_INIT)*/ (void)fd; (void)name; #endif /*!(USE_NPTH && HAVE_INOTIFY_INIT)*/ return 0; /* Not found. */ } /* Return a malloc'ed string that is the path to the passed * unix-domain socket (or return NULL if this is not a valid * unix-domain socket). We use a plain int here because it is only * used on Linux. * * FIXME: This function needs to be moved to libassuan. */ #ifndef HAVE_W32_SYSTEM char * gnupg_get_socket_name (int fd) { struct sockaddr_un un; socklen_t len = sizeof(un); char *name = NULL; if (getsockname (fd, (struct sockaddr*)&un, &len) != 0) log_error ("could not getsockname(%d): %s\n", fd, gpg_strerror (my_error_from_syserror ())); else if (un.sun_family != AF_UNIX) log_error ("file descriptor %d is not a unix-domain socket\n", fd); else if (len <= offsetof (struct sockaddr_un, sun_path)) log_error ("socket name not present for file descriptor %d\n", fd); else if (len > sizeof(un)) log_error ("socket name for file descriptor %d was truncated " "(passed %zu bytes, wanted %u)\n", fd, sizeof(un), len); else { size_t namelen = len - offsetof (struct sockaddr_un, sun_path); /* log_debug ("file descriptor %d has path %s (%zu octets)\n", fd, */ /* un.sun_path, namelen); */ name = xtrymalloc (namelen + 1); if (!name) log_error ("failed to allocate memory for name of fd %d: %s\n", fd, gpg_strerror (my_error_from_syserror ())); else { memcpy (name, un.sun_path, namelen); name[namelen] = 0; } } return name; } #endif /*!HAVE_W32_SYSTEM*/ /* Check whether FD is valid. */ int gnupg_fd_valid (int fd) { int d = dup (fd); if (d < 0) return 0; close (d); return 1; } diff --git a/common/sysutils.h b/common/sysutils.h index aabd15f8b..1862926ef 100644 --- a/common/sysutils.h +++ b/common/sysutils.h @@ -1,92 +1,98 @@ /* sysutils.h - System utility functions for Gnupg * Copyright (C) 2002 Free Software Foundation, Inc. * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it 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. * * 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 General Public License * along with this program; if not, see . */ #ifndef GNUPG_COMMON_SYSUTILS_H #define GNUPG_COMMON_SYSUTILS_H /* Because we use system handles and not libc low level file descriptors on W32, we need to declare them as HANDLE (which actually is a plain pointer). This is required to eventually support 64 bits Windows systems. */ #ifdef HAVE_W32_SYSTEM typedef void *gnupg_fd_t; #define GNUPG_INVALID_FD ((void*)(-1)) #define INT2FD(s) ((void *)(s)) #define FD2INT(h) ((unsigned int)(h)) #else typedef int gnupg_fd_t; #define GNUPG_INVALID_FD (-1) #define INT2FD(s) (s) #define FD2INT(h) (h) #endif +#ifdef HAVE_STAT +# include +#endif void trap_unaligned (void); int disable_core_dumps (void); int enable_core_dumps (void); void enable_special_filenames (void); const unsigned char *get_session_marker (size_t *rlen); unsigned int get_uint_nonce (void); /*int check_permissions (const char *path,int extension,int checkonly);*/ void gnupg_sleep (unsigned int seconds); void gnupg_usleep (unsigned int usecs); int translate_sys2libc_fd (gnupg_fd_t fd, int for_write); int translate_sys2libc_fd_int (int fd, int for_write); int check_special_filename (const char *fname, int for_write, int notranslate); FILE *gnupg_tmpfile (void); void gnupg_reopen_std (const char *pgmname); void gnupg_allow_set_foregound_window (pid_t pid); int gnupg_remove (const char *fname); gpg_error_t gnupg_rename_file (const char *oldname, const char *newname, int *block_signals); int gnupg_mkdir (const char *name, const char *modestr); int gnupg_chdir (const char *name); int gnupg_chmod (const char *name, const char *modestr); char *gnupg_mkdtemp (char *template); int gnupg_setenv (const char *name, const char *value, int overwrite); int gnupg_unsetenv (const char *name); char *gnupg_getcwd (void); gpg_err_code_t gnupg_access (const char *name, int mode); +#ifdef HAVE_STAT +int gnupg_stat (const char *name, struct stat *statbuf); +#endif /*HAVE_STAT*/ int gnupg_open (const char *name, int flags, unsigned int mode); char *gnupg_get_socket_name (int fd); int gnupg_fd_valid (int fd); gpg_error_t gnupg_inotify_watch_delete_self (int *r_fd, const char *fname); gpg_error_t gnupg_inotify_watch_socket (int *r_fd, const char *socket_name); int gnupg_inotify_has_name (int fd, const char *name); #ifdef HAVE_W32_SYSTEM void *w32_get_user_sid (void); #include "../common/w32help.h" #endif /*HAVE_W32_SYSTEM*/ #endif /*GNUPG_COMMON_SYSUTILS_H*/ diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index e8d5e0332..6468ce38f 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -1,2761 +1,2761 @@ /* crlcache.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2008 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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 2 of the License, or * (at your option) any later version. * * DirMngr 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 . */ /* 1. To keep track of the CRLs actually cached and to store the meta information of the CRLs a simple record oriented text file is used. Fields in the file are colon (':') separated and values containing colons or linefeeds are percent escaped (e.g. a colon itself is represented as "%3A"). The first field is a record type identifier, so that the file is useful to keep track of other meta data too. The name of the file is "DIR.txt". 1.1. Comment record Field 1: Constant beginning with "#". Other fields are not defined and such a record is simply skipped during processing. 1.2. Version record Field 1: Constant "v" Field 2: Version number of this file. Must be 1. This record must be the first non-comment record and there shall only exist one record of this type. 1.3. CRL cache record Field 1: Constant "c", "u" or "i". A "c" or "u" indicate a valid cache entry, however "u" requires that a user root certificate check needs to be done. An "i" indicates an invalid cache entry which should not be used but still exists so that it can be updated at NEXT_UPDATE. Field 2: Hexadecimal encoded SHA-1 hash of the issuer DN using uppercase letters. Field 3: Issuer DN in RFC-2253 notation. Field 4: URL used to retrieve the corresponding CRL. Field 5: 15 character ISO timestamp with THIS_UPDATE. Field 6: 15 character ISO timestamp with NEXT_UPDATE. Field 7: Hexadecimal encoded MD-5 hash of the DB file to detect accidental modified (i.e. deleted and created) cache files. Field 8: optional CRL number as a hex string. Field 9: AuthorityKeyID.issuer, each Name separated by 0x01 Field 10: AuthorityKeyID.serial Field 11: Hex fingerprint of trust anchor if field 1 is 'u'. 2. Layout of the standard CRL Cache DB file: We use records of variable length with this structure n bytes Serialnumber (binary) used as key thus there is no need to store the length explicitly with DB2. 1 byte Reason for revocation (currently the KSBA reason flags are used) 15 bytes ISO date of revocation (e.g. 19980815T142000) Note that there is no terminating 0 stored. The filename used is the hexadecimal (using uppercase letters) SHA-1 hash value of the issuer DN prefixed with a "crl-" and suffixed with a ".db". Thus the length of the filename is 47. */ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #endif #ifdef MKDIR_TAKES_ONE_ARG #undef mkdir #define mkdir(a,b) mkdir(a) #endif #include "dirmngr.h" #include "validate.h" #include "certcache.h" #include "crlcache.h" #include "crlfetch.h" #include "misc.h" #include "cdb.h" /* Change this whenever the format changes */ #define DBDIR_D "crls.d" #define DBDIRFILE "DIR.txt" #define DBDIRVERSION 1 /* The number of DB files we may have open at one time. We need to limit this because there is no guarantee that the number of issuers has a upper limit. We are currently using mmap, so it is a good idea anyway to limit the number of opened cache files. */ #define MAX_OPEN_DB_FILES 5 #ifndef O_BINARY # define O_BINARY 0 #endif static const char oidstr_crlNumber[] = "2.5.29.20"; /* static const char oidstr_issuingDistributionPoint[] = "2.5.29.28"; */ static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35"; /* Definition of one cached item. */ struct crl_cache_entry_s { struct crl_cache_entry_s *next; int deleted; /* True if marked for deletion. */ int mark; /* Internally used by update_dir. */ unsigned int lineno;/* A 0 indicates a new entry. */ char *release_ptr; /* The actual allocated memory. */ char *url; /* Points into RELEASE_PTR. */ char *issuer; /* Ditto. */ char *issuer_hash; /* Ditto. */ char *dbfile_hash; /* MD5 sum of the cache file, points into RELEASE_PTR.*/ int invalid; /* Can't use this CRL. */ int user_trust_req; /* User supplied root certificate required. */ char *check_trust_anchor; /* Malloced fingerprint. */ ksba_isotime_t this_update; ksba_isotime_t next_update; ksba_isotime_t last_refresh; /* Use for the force_crl_refresh feature. */ char *crl_number; char *authority_issuer; char *authority_serialno; struct cdb *cdb; /* The cache file handle or NULL if not open. */ unsigned int cdb_use_count; /* Current use count. */ unsigned int cdb_lru_count; /* Used for LRU purposes. */ int dbfile_checked; /* Set to true if the dbfile_hash value has been checked one. */ }; /* Definition of the entire cache object. */ struct crl_cache_s { crl_cache_entry_t entries; }; typedef struct crl_cache_s *crl_cache_t; /* Prototypes. */ static crl_cache_entry_t find_entry (crl_cache_entry_t first, const char *issuer_hash); /* The currently loaded cache object. This is usually initialized right at startup. */ static crl_cache_t current_cache; /* Return the current cache object or bail out if it is has not yet been initialized. */ static crl_cache_t get_current_cache (void) { if (!current_cache) log_fatal ("CRL cache has not yet been initialized\n"); return current_cache; } /* Create ae directory if it does not yet exists. Returns on success, or -1 on error. */ static int create_directory_if_needed (const char *name) { DIR *dir; char *fname; fname = make_filename (opt.homedir_cache, name, NULL); dir = opendir (fname); if (!dir) { log_info (_("creating directory '%s'\n"), fname); if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR) ) { int save_errno = errno; log_error (_("error creating directory '%s': %s\n"), fname, strerror (errno)); xfree (fname); gpg_err_set_errno (save_errno); return -1; } } else closedir (dir); xfree (fname); return 0; } /* Remove all files from the cache directory. If FORCE is not true, some sanity checks on the filenames are done. Return 0 if everything went fine. */ static int cleanup_cache_dir (int force) { char *dname = make_filename (opt.homedir_cache, DBDIR_D, NULL); DIR *dir; struct dirent *de; int problem = 0; if (!force) { /* Very minor sanity checks. */ if (!strcmp (dname, "~/") || !strcmp (dname, "/" )) { log_error (_("ignoring database dir '%s'\n"), dname); xfree (dname); return -1; } } dir = opendir (dname); if (!dir) { log_error (_("error reading directory '%s': %s\n"), dname, strerror (errno)); xfree (dname); return -1; } while ((de = readdir (dir))) { if (strcmp (de->d_name, "." ) && strcmp (de->d_name, "..")) { char *cdbname = make_filename (dname, de->d_name, NULL); int okay; struct stat sbuf; if (force) okay = 1; else - okay = (!stat (cdbname, &sbuf) && S_ISREG (sbuf.st_mode)); + okay = (!gnupg_stat (cdbname, &sbuf) && S_ISREG (sbuf.st_mode)); if (okay) { log_info (_("removing cache file '%s'\n"), cdbname); if (gnupg_remove (cdbname)) { log_error ("failed to remove '%s': %s\n", cdbname, strerror (errno)); problem = -1; } } else log_info (_("not removing file '%s'\n"), cdbname); xfree (cdbname); } } xfree (dname); closedir (dir); return problem; } /* Read the next line from the file FP and return the line in an malloced buffer. Return NULL on error or EOF. There is no limitation os the line length. The trailing linefeed has been removed, the function will read the last line of a file, even if that is not terminated by a LF. */ static char * next_line_from_file (estream_t fp, gpg_error_t *r_err) { char buf[300]; char *largebuf = NULL; size_t buflen; size_t len = 0; unsigned char *p; int c; char *tmpbuf; *r_err = 0; p = buf; buflen = sizeof buf - 1; while ((c=es_getc (fp)) != EOF && c != '\n') { if (len >= buflen) { if (!largebuf) { buflen += 1024; largebuf = xtrymalloc ( buflen + 1 ); if (!largebuf) { *r_err = gpg_error_from_syserror (); return NULL; } memcpy (largebuf, buf, len); } else { buflen += 1024; tmpbuf = xtryrealloc (largebuf, buflen + 1); if (!tmpbuf) { *r_err = gpg_error_from_syserror (); xfree (largebuf); return NULL; } largebuf = tmpbuf; } p = largebuf; } p[len++] = c; } if (c == EOF && !len) return NULL; p[len] = 0; if (largebuf) tmpbuf = xtryrealloc (largebuf, len+1); else tmpbuf = xtrystrdup (buf); if (!tmpbuf) { *r_err = gpg_error_from_syserror (); xfree (largebuf); } return tmpbuf; } /* Release one cache entry. */ static void release_one_cache_entry (crl_cache_entry_t entry) { if (entry) { if (entry->cdb) { int fd = cdb_fileno (entry->cdb); cdb_free (entry->cdb); xfree (entry->cdb); if (close (fd)) log_error (_("error closing cache file: %s\n"), strerror(errno)); } xfree (entry->release_ptr); xfree (entry->check_trust_anchor); xfree (entry); } } /* Release the CACHE object. */ static void release_cache (crl_cache_t cache) { crl_cache_entry_t entry, entry2; if (!cache) return; for (entry = cache->entries; entry; entry = entry2) { entry2 = entry->next; release_one_cache_entry (entry); } cache->entries = NULL; xfree (cache); } /* Open the dir file FNAME or create a new one if it does not yet exist. */ static estream_t open_dir_file (const char *fname) { estream_t fp; fp = es_fopen (fname, "r"); if (!fp) { log_error (_("failed to open cache dir file '%s': %s\n"), fname, strerror (errno)); /* Make sure that the directory exists, try to create if otherwise. */ if (create_directory_if_needed (NULL) || create_directory_if_needed (DBDIR_D)) return NULL; fp = es_fopen (fname, "w"); if (!fp) { log_error (_("error creating new cache dir file '%s': %s\n"), fname, strerror (errno)); return NULL; } es_fprintf (fp, "v:%d:\n", DBDIRVERSION); if (es_ferror (fp)) { log_error (_("error writing new cache dir file '%s': %s\n"), fname, strerror (errno)); es_fclose (fp); return NULL; } if (es_fclose (fp)) { log_error (_("error closing new cache dir file '%s': %s\n"), fname, strerror (errno)); return NULL; } log_info (_("new cache dir file '%s' created\n"), fname); fp = es_fopen (fname, "r"); if (!fp) { log_error (_("failed to re-open cache dir file '%s': %s\n"), fname, strerror (errno)); return NULL; } } return fp; } /* Helper for open_dir. */ static gpg_error_t check_dir_version (estream_t *fpadr, const char *fname, unsigned int *lineno, int cleanup_on_mismatch) { char *line; gpg_error_t lineerr = 0; estream_t fp = *fpadr; int created = 0; retry: while ((line = next_line_from_file (fp, &lineerr))) { ++*lineno; if (*line == 'v' && line[1] == ':') break; else if (*line != '#') { log_error (_("first record of '%s' is not the version\n"), fname); xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } xfree (line); } if (lineerr) return lineerr; /* The !line catches the case of an empty DIR file. We handle this the same as a non-matching version. */ if (!line || strtol (line+2, NULL, 10) != DBDIRVERSION) { if (!created && cleanup_on_mismatch) { log_error (_("old version of cache directory - cleaning up\n")); es_fclose (fp); *fpadr = NULL; if (!cleanup_cache_dir (1)) { *lineno = 0; fp = *fpadr = open_dir_file (fname); if (!fp) { xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } created = 1; goto retry; } } log_error (_("old version of cache directory - giving up\n")); xfree (line); return gpg_error (GPG_ERR_CONFIGURATION); } xfree (line); return 0; } /* Open the dir file and read in all available information. Store that in a newly allocated cache object and return that if everything worked out fine. Create the cache directory and the dir if it does not yet exist. Remove all files in that directory if the version does not match. */ static gpg_error_t open_dir (crl_cache_t *r_cache) { crl_cache_t cache; char *fname; char *line = NULL; gpg_error_t lineerr = 0; estream_t fp; crl_cache_entry_t entry, *entrytail; unsigned int lineno; gpg_error_t err = 0; int anyerr = 0; cache = xtrycalloc (1, sizeof *cache); if (!cache) return gpg_error_from_syserror (); fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL); lineno = 0; fp = open_dir_file (fname); if (!fp) { err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } err = check_dir_version (&fp, fname, &lineno, 1); if (err) goto leave; /* Read in all supported entries from the dir file. */ cache->entries = NULL; entrytail = &cache->entries; xfree (line); while ((line = next_line_from_file (fp, &lineerr))) { int fieldno; char *p, *endp; lineno++; if ( *line == 'c' || *line == 'u' || *line == 'i' ) { entry = xtrycalloc (1, sizeof *entry); if (!entry) { err = gpg_error_from_syserror (); goto leave; } entry->lineno = lineno; entry->release_ptr = line; if (*line == 'i') { entry->invalid = atoi (line+1); if (entry->invalid < 1) entry->invalid = 1; } else if (*line == 'u') entry->user_trust_req = 1; for (fieldno=1, p = line; p; p = endp, fieldno++) { endp = strchr (p, ':'); if (endp) *endp++ = '\0'; switch (fieldno) { case 1: /* record type */ break; case 2: entry->issuer_hash = p; break; case 3: entry->issuer = unpercent_string (p); break; case 4: entry->url = unpercent_string (p); break; case 5: strncpy (entry->this_update, p, 15); entry->this_update[15] = 0; break; case 6: strncpy (entry->next_update, p, 15); entry->next_update[15] = 0; break; case 7: entry->dbfile_hash = p; break; case 8: if (*p) entry->crl_number = p; break; case 9: if (*p) entry->authority_issuer = unpercent_string (p); break; case 10: if (*p) entry->authority_serialno = unpercent_string (p); break; case 11: if (*p) entry->check_trust_anchor = xtrystrdup (p); break; default: if (*p) log_info (_("extra field detected in crl record of " "'%s' line %u\n"), fname, lineno); break; } } if (!entry->issuer_hash) { log_info (_("invalid line detected in '%s' line %u\n"), fname, lineno); xfree (entry); entry = NULL; } else if (find_entry (cache->entries, entry->issuer_hash)) { /* Fixme: The duplicate checking used is not very effective for large numbers of issuers. */ log_info (_("duplicate entry detected in '%s' line %u\n"), fname, lineno); xfree (entry); entry = NULL; } else { line = NULL; *entrytail = entry; entrytail = &entry->next; } } else if (*line == '#') ; else log_info (_("unsupported record type in '%s' line %u skipped\n"), fname, lineno); if (line) xfree (line); } if (lineerr) { err = lineerr; log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (es_ferror (fp)) { log_error (_("error reading '%s': %s\n"), fname, strerror (errno)); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } /* Now do some basic checks on the data. */ for (entry = cache->entries; entry; entry = entry->next) { assert (entry->lineno); if (strlen (entry->issuer_hash) != 40) { anyerr++; log_error (_("invalid issuer hash in '%s' line %u\n"), fname, entry->lineno); } else if ( !*entry->issuer ) { anyerr++; log_error (_("no issuer DN in '%s' line %u\n"), fname, entry->lineno); } else if ( check_isotime (entry->this_update) || check_isotime (entry->next_update)) { anyerr++; log_error (_("invalid timestamp in '%s' line %u\n"), fname, entry->lineno); } /* Checks not leading to an immediate fail. */ if (strlen (entry->dbfile_hash) != 32) log_info (_("WARNING: invalid cache file hash in '%s' line %u\n"), fname, entry->lineno); } if (anyerr) { log_error (_("detected errors in cache dir file\n")); log_info (_("please check the reason and manually delete that file\n")); err = gpg_error (GPG_ERR_CONFIGURATION); } leave: es_fclose (fp); xfree (line); xfree (fname); if (err) { release_cache (cache); cache = NULL; } *r_cache = cache; return err; } static void write_percented_string (const char *s, estream_t fp) { for (; *s; s++) if (*s == ':') es_fputs ("%3A", fp); else if (*s == '\n') es_fputs ("%0A", fp); else if (*s == '\r') es_fputs ("%0D", fp); else es_putc (*s, fp); } static void write_dir_line_crl (estream_t fp, crl_cache_entry_t e) { if (e->invalid) es_fprintf (fp, "i%d", e->invalid); else if (e->user_trust_req) es_putc ('u', fp); else es_putc ('c', fp); es_putc (':', fp); es_fputs (e->issuer_hash, fp); es_putc (':', fp); write_percented_string (e->issuer, fp); es_putc (':', fp); write_percented_string (e->url, fp); es_putc (':', fp); es_fwrite (e->this_update, 15, 1, fp); es_putc (':', fp); es_fwrite (e->next_update, 15, 1, fp); es_putc (':', fp); es_fputs (e->dbfile_hash, fp); es_putc (':', fp); if (e->crl_number) es_fputs (e->crl_number, fp); es_putc (':', fp); if (e->authority_issuer) write_percented_string (e->authority_issuer, fp); es_putc (':', fp); if (e->authority_serialno) es_fputs (e->authority_serialno, fp); es_putc (':', fp); if (e->check_trust_anchor && e->user_trust_req) es_fputs (e->check_trust_anchor, fp); es_putc ('\n', fp); } /* Update the current dir file using the cache. */ static gpg_error_t update_dir (crl_cache_t cache) { char *fname = NULL; char *tmpfname = NULL; char *line = NULL; gpg_error_t lineerr = 0; estream_t fp; estream_t fpout = NULL; crl_cache_entry_t e; unsigned int lineno; gpg_error_t err = 0; fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL); /* Fixme: Take an update file lock here. */ for (e= cache->entries; e; e = e->next) e->mark = 1; lineno = 0; fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_errno (errno); log_error (_("failed to open cache dir file '%s': %s\n"), fname, strerror (errno)); goto leave; } err = check_dir_version (&fp, fname, &lineno, 0); if (err) goto leave; es_rewind (fp); lineno = 0; /* Create a temporary DIR file. */ { char *tmpbuf, *p; const char *nodename; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; #endif #ifdef HAVE_W32_SYSTEM nodename = "unknown"; #else if (uname (&utsbuf)) nodename = "unknown"; else nodename = utsbuf.nodename; #endif gpgrt_asprintf (&tmpbuf, "DIR-tmp-%s-%u-%p.txt.tmp", nodename, (unsigned int)getpid (), &tmpbuf); if (!tmpbuf) { err = gpg_error_from_errno (errno); log_error (_("failed to create temporary cache dir file '%s': %s\n"), tmpfname, strerror (errno)); goto leave; } for (p=tmpbuf; *p; p++) if (*p == '/') *p = '.'; tmpfname = make_filename (opt.homedir_cache, DBDIR_D, tmpbuf, NULL); xfree (tmpbuf); } fpout = es_fopen (tmpfname, "w"); if (!fpout) { err = gpg_error_from_errno (errno); log_error (_("failed to create temporary cache dir file '%s': %s\n"), tmpfname, strerror (errno)); goto leave; } while ((line = next_line_from_file (fp, &lineerr))) { lineno++; if (*line == 'c' || *line == 'u' || *line == 'i') { /* Extract the issuer hash field. */ char *fieldp, *endp; fieldp = strchr (line, ':'); endp = fieldp? strchr (++fieldp, ':') : NULL; if (endp) { /* There should be no percent within the issuer hash field, thus we can compare it pretty easily. */ *endp = 0; e = find_entry ( cache->entries, fieldp); *endp = ':'; /* Restore original line. */ if (e && e->deleted) { /* Marked for deletion, so don't write it. */ e->mark = 0; } else if (e) { /* Yep, this is valid entry we know about; write it out */ write_dir_line_crl (fpout, e); e->mark = 0; } else { /* We ignore entries we don't have in our cache because they may have been added in the meantime by other instances of dirmngr. */ es_fprintf (fpout, "# Next line added by " "another process; our pid is %lu\n", (unsigned long)getpid ()); es_fputs (line, fpout); es_putc ('\n', fpout); } } else { es_fputs ("# Invalid line detected: ", fpout); es_fputs (line, fpout); es_putc ('\n', fpout); } } else { /* Write out all non CRL lines as they are. */ es_fputs (line, fpout); es_putc ('\n', fpout); } xfree (line); } if (!es_ferror (fp) && !es_ferror (fpout) && !lineerr) { /* Write out the remaining entries. */ for (e= cache->entries; e; e = e->next) if (e->mark) { if (!e->deleted) write_dir_line_crl (fpout, e); e->mark = 0; } } if (lineerr) { err = lineerr; log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (es_ferror (fp)) { err = gpg_error_from_errno (errno); log_error (_("error reading '%s': %s\n"), fname, strerror (errno)); } if (es_ferror (fpout)) { err = gpg_error_from_errno (errno); log_error (_("error writing '%s': %s\n"), tmpfname, strerror (errno)); } if (err) goto leave; /* Rename the files. */ es_fclose (fp); fp = NULL; if (es_fclose (fpout)) { err = gpg_error_from_errno (errno); log_error (_("error closing '%s': %s\n"), tmpfname, strerror (errno)); goto leave; } fpout = NULL; #ifdef HAVE_W32_SYSTEM /* No atomic mv on W32 systems. */ gnupg_remove (fname); #endif if (rename (tmpfname, fname)) { err = gpg_error_from_errno (errno); log_error (_("error renaming '%s' to '%s': %s\n"), tmpfname, fname, strerror (errno)); goto leave; } leave: /* Fixme: Relinquish update lock. */ xfree (line); es_fclose (fp); xfree (fname); if (fpout) { es_fclose (fpout); if (err && tmpfname) gnupg_remove (tmpfname); } xfree (tmpfname); return err; } /* Create the filename for the cache file from the 40 byte ISSUER_HASH string. Caller must release the return string. */ static char * make_db_file_name (const char *issuer_hash) { char bname[50]; assert (strlen (issuer_hash) == 40); memcpy (bname, "crl-", 4); memcpy (bname + 4, issuer_hash, 40); strcpy (bname + 44, ".db"); return make_filename (opt.homedir_cache, DBDIR_D, bname, NULL); } /* Hash the file FNAME and return the MD5 digest in MD5BUFFER. The caller must allocate MD%buffer wityh at least 16 bytes. Returns 0 on success. */ static int hash_dbfile (const char *fname, unsigned char *md5buffer) { estream_t fp; char *buffer; size_t n; gcry_md_hd_t md5; gpg_error_t err; buffer = xtrymalloc (65536); fp = buffer? es_fopen (fname, "rb") : NULL; if (!fp) { log_error (_("can't hash '%s': %s\n"), fname, strerror (errno)); xfree (buffer); return -1; } err = gcry_md_open (&md5, GCRY_MD_MD5, 0); if (err) { log_error (_("error setting up MD5 hash context: %s\n"), gpg_strerror (err)); xfree (buffer); es_fclose (fp); return -1; } /* We better hash some information about the cache file layout in. */ sprintf (buffer, "%.100s/%.100s:%d", DBDIR_D, DBDIRFILE, DBDIRVERSION); gcry_md_write (md5, buffer, strlen (buffer)); for (;;) { n = es_fread (buffer, 1, 65536, fp); if (n < 65536 && es_ferror (fp)) { log_error (_("error hashing '%s': %s\n"), fname, strerror (errno)); xfree (buffer); es_fclose (fp); gcry_md_close (md5); return -1; } if (!n) break; gcry_md_write (md5, buffer, n); } es_fclose (fp); xfree (buffer); gcry_md_final (md5); memcpy (md5buffer, gcry_md_read (md5, GCRY_MD_MD5), 16); gcry_md_close (md5); return 0; } /* Compare the file FNAME against the dexified MD5 hash MD5HASH and return 0 if they match. */ static int check_dbfile (const char *fname, const char *md5hexvalue) { unsigned char buffer1[16], buffer2[16]; if (strlen (md5hexvalue) != 32) { log_error (_("invalid formatted checksum for '%s'\n"), fname); return -1; } unhexify (buffer1, md5hexvalue); if (hash_dbfile (fname, buffer2)) return -1; return memcmp (buffer1, buffer2, 16); } /* Open the cache file for ENTRY. This function implements a caching strategy and might close unused cache files. It is required to use unlock_db_file after using the file. */ static struct cdb * lock_db_file (crl_cache_t cache, crl_cache_entry_t entry) { char *fname; int fd; int open_count; crl_cache_entry_t e; if (entry->cdb) { entry->cdb_use_count++; return entry->cdb; } for (open_count = 0, e = cache->entries; e; e = e->next) { if (e->cdb) open_count++; /* log_debug ("CACHE: cdb=%p use_count=%u lru_count=%u\n", */ /* e->cdb,e->cdb_use_count,e->cdb_lru_count); */ } /* If there are too many file open, find the least recent used DB file and close it. Note that for Pth thread safeness we need to use a loop here. */ while (open_count >= MAX_OPEN_DB_FILES ) { crl_cache_entry_t last_e = NULL; unsigned int last_lru = (unsigned int)(-1); for (e = cache->entries; e; e = e->next) if (e->cdb && !e->cdb_use_count && e->cdb_lru_count < last_lru) { last_lru = e->cdb_lru_count; last_e = e; } if (!last_e) { log_error (_("too many open cache files; can't open anymore\n")); return NULL; } /* log_debug ("CACHE: closing file at cdb=%p\n", last_e->cdb); */ fd = cdb_fileno (last_e->cdb); cdb_free (last_e->cdb); xfree (last_e->cdb); last_e->cdb = NULL; if (close (fd)) log_error (_("error closing cache file: %s\n"), strerror(errno)); open_count--; } fname = make_db_file_name (entry->issuer_hash); if (opt.verbose) log_info (_("opening cache file '%s'\n"), fname ); if (!entry->dbfile_checked) { if (!check_dbfile (fname, entry->dbfile_hash)) entry->dbfile_checked = 1; /* Note, in case of an error we don't print an error here but let require the caller to do that check. */ } entry->cdb = xtrycalloc (1, sizeof *entry->cdb); if (!entry->cdb) { xfree (fname); return NULL; } fd = gnupg_open (fname, O_RDONLY | O_BINARY, 0); if (fd == -1) { log_error (_("error opening cache file '%s': %s\n"), fname, strerror (errno)); xfree (entry->cdb); entry->cdb = NULL; xfree (fname); return NULL; } if (cdb_init (entry->cdb, fd)) { log_error (_("error initializing cache file '%s' for reading: %s\n"), fname, strerror (errno)); xfree (entry->cdb); entry->cdb = NULL; close (fd); xfree (fname); return NULL; } xfree (fname); entry->cdb_use_count = 1; entry->cdb_lru_count = 0; return entry->cdb; } /* Unlock a cache file, so that it can be reused. */ static void unlock_db_file (crl_cache_t cache, crl_cache_entry_t entry) { if (!entry->cdb) log_error (_("calling unlock_db_file on a closed file\n")); else if (!entry->cdb_use_count) log_error (_("calling unlock_db_file on an unlocked file\n")); else { entry->cdb_use_count--; entry->cdb_lru_count++; } /* If the entry was marked for deletion in the meantime do it now. We do this for the sake of Pth thread safeness. */ if (!entry->cdb_use_count && entry->deleted) { crl_cache_entry_t eprev, enext; enext = entry->next; for (eprev = cache->entries; eprev && eprev->next != entry; eprev = eprev->next) ; assert (eprev); if (eprev == cache->entries) cache->entries = enext; else eprev->next = enext; /* FIXME: Do we leak ENTRY? */ } } /* Find ISSUER_HASH in our cache FIRST. This may be used to enumerate the linked list we use to keep the CRLs of an issuer. */ static crl_cache_entry_t find_entry (crl_cache_entry_t first, const char *issuer_hash) { while (first && (first->deleted || strcmp (issuer_hash, first->issuer_hash))) first = first->next; return first; } /* Create a new CRL cache. This function is usually called only once. never fail. */ void crl_cache_init(void) { crl_cache_t cache = NULL; gpg_error_t err; if (current_cache) { log_error ("crl cache has already been initialized - not doing twice\n"); return; } err = open_dir (&cache); if (err) log_fatal (_("failed to create a new cache object: %s\n"), gpg_strerror (err)); current_cache = cache; } /* Remove the cache information and all its resources. Note that we still keep the cache on disk. */ void crl_cache_deinit (void) { if (current_cache) { release_cache (current_cache); current_cache = NULL; } } /* Delete the cache from disk and memory. Return 0 on success.*/ int crl_cache_flush (void) { int rc; crl_cache_deinit (); rc = cleanup_cache_dir (0)? -1 : 0; crl_cache_init (); return rc; } /* Check whether the certificate identified by ISSUER_HASH and SN/SNLEN is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ static crl_cache_result_t cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const unsigned char *sn, size_t snlen, int force_refresh) { crl_cache_t cache = get_current_cache (); crl_cache_result_t retval; struct cdb *cdb; int rc; crl_cache_entry_t entry; gnupg_isotime_t current_time; size_t n; (void)ctrl; entry = find_entry (cache->entries, issuer_hash); if (!entry) { log_info (_("no CRL available for issuer id %s\n"), issuer_hash ); return CRL_CACHE_DONTKNOW; } gnupg_get_isotime (current_time); if (strcmp (entry->next_update, current_time) < 0 ) { log_info (_("cached CRL for issuer id %s too old; update required\n"), issuer_hash); return CRL_CACHE_DONTKNOW; } if (force_refresh) { gnupg_isotime_t tmptime; if (*entry->last_refresh) { gnupg_copy_time (tmptime, entry->last_refresh); add_seconds_to_isotime (tmptime, 30 * 60); if (strcmp (tmptime, current_time) < 0 ) { log_info (_("force-crl-refresh active and %d minutes passed for" " issuer id %s; update required\n"), 30, issuer_hash); return CRL_CACHE_DONTKNOW; } } else { log_info (_("force-crl-refresh active for" " issuer id %s; update required\n"), issuer_hash); return CRL_CACHE_DONTKNOW; } } if (entry->invalid) { log_info (_("available CRL for issuer ID %s can't be used\n"), issuer_hash); return CRL_CACHE_CANTUSE; } cdb = lock_db_file (cache, entry); if (!cdb) return CRL_CACHE_DONTKNOW; /* Hmmm, not the best error code. */ if (!entry->dbfile_checked) { log_error (_("cached CRL for issuer id %s tampered; we need to update\n") , issuer_hash); unlock_db_file (cache, entry); return CRL_CACHE_DONTKNOW; } rc = cdb_find (cdb, sn, snlen); if (rc == 1) { n = cdb_datalen (cdb); if (n != 16) { log_error (_("WARNING: invalid cache record length for S/N ")); log_printf ("0x"); log_printhex (sn, snlen, ""); } else if (opt.verbose) { unsigned char record[16]; char *tmp = hexify_data (sn, snlen, 1); if (cdb_read (cdb, record, n, cdb_datapos (cdb))) log_error (_("problem reading cache record for S/N %s: %s\n"), tmp, strerror (errno)); else log_info (_("S/N %s is not valid; reason=%02X date=%.15s\n"), tmp, *record, record+1); xfree (tmp); } retval = CRL_CACHE_INVALID; } else if (!rc) { if (opt.verbose) { char *serialno = hexify_data (sn, snlen, 1); log_info (_("S/N %s is valid, it is not listed in the CRL\n"), serialno ); xfree (serialno); } retval = CRL_CACHE_VALID; } else { log_error (_("error getting data from cache file: %s\n"), strerror (errno)); retval = CRL_CACHE_DONTKNOW; } if (entry->user_trust_req && (retval == CRL_CACHE_VALID || retval == CRL_CACHE_INVALID)) { if (!entry->check_trust_anchor) { log_error ("inconsistent data on user trust check\n"); retval = CRL_CACHE_CANTUSE; } else if (get_istrusted_from_client (ctrl, entry->check_trust_anchor)) { if (opt.verbose) log_info ("no system trust and client does not trust either\n"); retval = CRL_CACHE_CANTUSE; } else { /* Okay, the CRL is considered valid by the client and thus we can return the result as is. */ } } unlock_db_file (cache, entry); return retval; } /* Check whether the certificate identified by ISSUER_HASH and SERIALNO is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ crl_cache_result_t crl_cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const char *serialno, int force_refresh) { crl_cache_result_t result; unsigned char snbuf_buffer[50]; unsigned char *snbuf; size_t n; n = strlen (serialno)/2+1; if (n < sizeof snbuf_buffer - 1) snbuf = snbuf_buffer; else { snbuf = xtrymalloc (n); if (!snbuf) return CRL_CACHE_DONTKNOW; } n = unhexify (snbuf, serialno); result = cache_isvalid (ctrl, issuer_hash, snbuf, n, force_refresh); if (snbuf != snbuf_buffer) xfree (snbuf); return result; } /* Check whether the certificate CERT is valid; i.e. not listed in our cache. With FORCE_REFRESH set to true, a new CRL will be retrieved even if the cache has not yet expired. We use a 30 minutes threshold here so that invoking this function several times won't load the CRL over and over. */ gpg_error_t crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert, int force_refresh) { gpg_error_t err; crl_cache_result_t result; unsigned char issuerhash[20]; char issuerhash_hex[41]; ksba_sexp_t serial; unsigned char *sn; size_t snlen; char *endp, *tmp; int i; /* Compute the hash value of the issuer name. */ tmp = ksba_cert_get_issuer (cert, 0); if (!tmp) { log_error ("oops: issuer missing in certificate\n"); return gpg_error (GPG_ERR_INV_CERT_OBJ); } gcry_md_hash_buffer (GCRY_MD_SHA1, issuerhash, tmp, strlen (tmp)); xfree (tmp); for (i=0,tmp=issuerhash_hex; i < 20; i++, tmp += 2) sprintf (tmp, "%02X", issuerhash[i]); /* Get the serial number. */ serial = ksba_cert_get_serial (cert); if (!serial) { log_error ("oops: S/N missing in certificate\n"); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn = serial; if (*sn != '(') { log_error ("oops: invalid S/N\n"); xfree (serial); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn++; snlen = strtoul (sn, &endp, 10); sn = endp; if (*sn != ':') { log_error ("oops: invalid S/N\n"); xfree (serial); return gpg_error (GPG_ERR_INV_CERT_OBJ); } sn++; /* Check the cache. */ result = cache_isvalid (ctrl, issuerhash_hex, sn, snlen, force_refresh); switch (result) { case CRL_CACHE_VALID: err = 0; break; case CRL_CACHE_INVALID: err = gpg_error (GPG_ERR_CERT_REVOKED); break; case CRL_CACHE_DONTKNOW: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; case CRL_CACHE_CANTUSE: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; default: log_fatal ("cache_isvalid returned invalid status code %d\n", result); } xfree (serial); return err; } /* Return the hash algorithm's algo id from its name given in the * non-null termnated string in (buffer,buflen). Returns 0 on failure * or if the algo is not known. */ static int hash_algo_from_buffer (const void *buffer, size_t buflen) { char *string; int algo; string = xtrymalloc (buflen + 1); if (!string) { log_error (_("out of core\n")); return 0; } memcpy (string, buffer, buflen); string[buflen] = 0; algo = gcry_md_map_name (string); if (!algo) log_error ("unknown digest algorithm '%s' used in certificate\n", string); xfree (string); return algo; } /* Return an unsigned integer from the non-null termnated string * (buffer,buflen). Returns 0 on failure. */ static unsigned int uint_from_buffer (const void *buffer, size_t buflen) { char *string; unsigned int val; string = xtrymalloc (buflen + 1); if (!string) { log_error (_("out of core\n")); return 0; } memcpy (string, buffer, buflen); string[buflen] = 0; val = strtoul (string, NULL, 10); xfree (string); return val; } /* Prepare a hash context for the signature verification. Input is the CRL and the output is the hash context MD as well as the uses algorithm identifier ALGO. */ static gpg_error_t start_sig_check (ksba_crl_t crl, gcry_md_hd_t *md, int *algo, int *use_pss) { gpg_error_t err; const char *algoid; *use_pss = 0; algoid = ksba_crl_get_digest_algo (crl); if (algoid && !strcmp (algoid, "1.2.840.113549.1.1.10")) { /* Parse rsaPSS parameter. */ gcry_buffer_t ioarray[1] = { {0} }; ksba_sexp_t pssparam; size_t n; gcry_sexp_t psssexp; pssparam = ksba_crl_get_sig_val (crl); n = gcry_sexp_canon_len (pssparam, 0, NULL, NULL); if (!n) { ksba_free (pssparam); log_error (_("got an invalid S-expression from libksba\n")); return gpg_error (GPG_ERR_INV_SEXP); } err = gcry_sexp_sscan (&psssexp, NULL, pssparam, n); ksba_free (pssparam); if (err) { log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); return err; } err = gcry_sexp_extract_param (psssexp, "sig-val", "&'hash-algo'", ioarray, NULL); gcry_sexp_release (psssexp); if (err) { log_error ("extracting params from PSS failed: %s\n", gpg_strerror (err)); return err; } *algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len); xfree (ioarray[0].data); *use_pss = 1; } else *algo = gcry_md_map_name (algoid); if (!*algo) { log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?"); return gpg_error (GPG_ERR_DIGEST_ALGO); } err = gcry_md_open (md, *algo, 0); if (err) { log_error (_("gcry_md_open for algorithm %d failed: %s\n"), *algo, gcry_strerror (err)); return err; } if (DBG_HASHING) gcry_md_debug (*md, "hash.cert"); ksba_crl_set_hash_function (crl, HASH_FNC, *md); return 0; } /* Finish a hash context and verify the signature. This function should return 0 on a good signature, GPG_ERR_BAD_SIGNATURE if the signature does not verify or any other error code. CRL is the CRL object we are working on, MD the hash context and ISSUER_CERT the certificate of the CRL issuer. This function takes ownership of MD. */ static gpg_error_t finish_sig_check (ksba_crl_t crl, gcry_md_hd_t md, int algo, ksba_cert_t issuer_cert, int use_pss) { gpg_error_t err; ksba_sexp_t sigval = NULL, pubkey = NULL; size_t n; gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL; unsigned int saltlen = 0; /* (used only with use_pss) */ /* This also stops debugging on the MD. */ gcry_md_final (md); /* Get and convert the signature value. */ sigval = ksba_crl_get_sig_val (crl); n = gcry_sexp_canon_len (sigval, 0, NULL, NULL); if (!n) { log_error (_("got an invalid S-expression from libksba\n")); err = gpg_error (GPG_ERR_INV_SEXP); goto leave; } err = gcry_sexp_sscan (&s_sig, NULL, sigval, n); if (err) { log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } if (use_pss) { /* Parse rsaPSS parameter which we should find in S_SIG. */ gcry_buffer_t ioarray[2] = { {0}, {0} }; ksba_sexp_t pssparam; gcry_sexp_t psssexp; int hashalgo; pssparam = ksba_crl_get_sig_val (crl); n = gcry_sexp_canon_len (pssparam, 0, NULL, NULL); if (!n) { ksba_free (pssparam); log_error (_("got an invalid S-expression from libksba\n")); err = gpg_error (GPG_ERR_INV_SEXP); goto leave; } err = gcry_sexp_sscan (&psssexp, NULL, pssparam, n); ksba_free (pssparam); if (err) { log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } err = gcry_sexp_extract_param (psssexp, "sig-val", "&'hash-algo''salt-length'", ioarray+0, ioarray+1, NULL); gcry_sexp_release (psssexp); if (err) { log_error ("extracting params from PSS failed: %s\n", gpg_strerror (err)); goto leave; } hashalgo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len); saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len); xfree (ioarray[0].data); xfree (ioarray[1].data); if (hashalgo != algo) { log_error ("hash algo mismatch: %d announced but %d used\n", algo, hashalgo); return gpg_error (GPG_ERR_INV_CRL); } /* Add some restrictions; see ../sm/certcheck.c for details. */ switch (algo) { case GCRY_MD_SHA1: case GCRY_MD_SHA256: case GCRY_MD_SHA384: case GCRY_MD_SHA512: case GCRY_MD_SHA3_256: case GCRY_MD_SHA3_384: case GCRY_MD_SHA3_512: break; default: log_error ("PSS hash algorithm '%s' rejected\n", gcry_md_algo_name (algo)); return gpg_error (GPG_ERR_DIGEST_ALGO); } if (gcry_md_get_algo_dlen (algo) != saltlen) { log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n", gcry_md_algo_name (algo), saltlen); return gpg_error (GPG_ERR_DIGEST_ALGO); } } /* Get and convert the public key for the issuer certificate. */ if (DBG_X509) dump_cert ("crl_issuer_cert", issuer_cert); pubkey = ksba_cert_get_public_key (issuer_cert); n = gcry_sexp_canon_len (pubkey, 0, NULL, NULL); if (!n) { log_error (_("got an invalid S-expression from libksba\n")); err = gpg_error (GPG_ERR_INV_SEXP); goto leave; } err = gcry_sexp_sscan (&s_pkey, NULL, pubkey, n); if (err) { log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } /* Create an S-expression with the actual hash value. */ if (use_pss) { err = gcry_sexp_build (&s_hash, NULL, "(data (flags pss)" "(hash %s %b)" "(salt-length %u))", hash_algo_to_string (algo), (int)gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo), saltlen); } else { err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", hash_algo_to_string (algo), (int)gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo)); } if (err) { log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } /* Pass this on to the signature verification. */ err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err)); leave: xfree (sigval); xfree (pubkey); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); gcry_md_close (md); return err; } /* Call this to match a start_sig_check that can not be completed normally. Takes ownership of MD if MD is not NULL. */ static void abort_sig_check (ksba_crl_t crl, gcry_md_hd_t md) { (void)crl; if (md) gcry_md_close (md); } /* Workhorse of the CRL loading machinery. The CRL is read using the CRL object and stored in the data base file DB with the name FNAME (only used for printing error messages). That DB should be a temporary one and not the actual one. If the function fails the caller should delete this temporary database file. CTRL is required to retrieve certificates using the general dirmngr callback service. R_CRLISSUER returns an allocated string with the crl-issuer DN, THIS_UPDATE and NEXT_UPDATE are filled with the corresponding data from the CRL. Note that these values might get set even if the CRL processing fails at a later step; thus the caller should free *R_ISSUER even if the function returns with an error. R_TRUST_ANCHOR is set on exit to NULL or a string with the hexified fingerprint of the root certificate, if checking this certificate for trustiness is required. */ static int crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl, struct cdb_make *cdb, const char *fname, char **r_crlissuer, ksba_isotime_t thisupdate, ksba_isotime_t nextupdate, char **r_trust_anchor) { gpg_error_t err; ksba_stop_reason_t stopreason; ksba_cert_t crlissuer_cert = NULL; gcry_md_hd_t md = NULL; int algo = 0; int use_pss = 0; size_t n; (void)fname; *r_crlissuer = NULL; *thisupdate = *nextupdate = 0; *r_trust_anchor = NULL; /* Start of the KSBA parser loop. */ do { err = ksba_crl_parse (crl, &stopreason); if (err) { log_error (_("ksba_crl_parse failed: %s\n"), gpg_strerror (err) ); goto failure; } switch (stopreason) { case KSBA_SR_BEGIN_ITEMS: { err = start_sig_check (crl, &md, &algo, &use_pss); if (err) goto failure; err = ksba_crl_get_update_times (crl, thisupdate, nextupdate); if (err) { log_error (_("error getting update times of CRL: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); goto failure; } if (opt.verbose || !*nextupdate) log_info (_("update times of this CRL: this=%s next=%s\n"), thisupdate, nextupdate); if (!*nextupdate) { log_info (_("nextUpdate not given; " "assuming a validity period of one day\n")); gnupg_copy_time (nextupdate, thisupdate); add_seconds_to_isotime (nextupdate, 86400); } } break; case KSBA_SR_GOT_ITEM: { ksba_sexp_t serial; const unsigned char *p; ksba_isotime_t rdate; ksba_crl_reason_t reason; int rc; unsigned char record[1+15]; err = ksba_crl_get_item (crl, &serial, rdate, &reason); if (err) { log_error (_("error getting CRL item: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); ksba_free (serial); goto failure; } p = serial_to_buffer (serial, &n); if (!p) BUG (); record[0] = (reason & 0xff); memcpy (record+1, rdate, 15); rc = cdb_make_add (cdb, p, n, record, 1+15); if (rc) { err = gpg_error_from_errno (errno); log_error (_("error inserting item into " "temporary cache file: %s\n"), strerror (errno)); goto failure; } ksba_free (serial); } break; case KSBA_SR_END_ITEMS: break; case KSBA_SR_READY: { char *crlissuer; ksba_name_t authid; ksba_sexp_t authidsn; ksba_sexp_t keyid; /* We need to look for the issuer only after having read all items. The issuer itselfs comes before the items but the optional authorityKeyIdentifier comes after the items. */ err = ksba_crl_get_issuer (crl, &crlissuer); if( err ) { log_error (_("no CRL issuer found in CRL: %s\n"), gpg_strerror (err) ); err = gpg_error (GPG_ERR_INV_CRL); goto failure; } /* Note: This should be released by ksba_free, not xfree. May need a memory reallocation dance. */ *r_crlissuer = crlissuer; /* (Do it here so we don't need to free it later) */ if (!ksba_crl_get_auth_key_id (crl, &keyid, &authid, &authidsn)) { const char *s; if (opt.verbose) log_info (_("locating CRL issuer certificate by " "authorityKeyIdentifier\n")); s = ksba_name_enum (authid, 0); if (s && *authidsn) crlissuer_cert = find_cert_bysn (ctrl, s, authidsn); if (!crlissuer_cert && keyid) crlissuer_cert = find_cert_bysubject (ctrl, crlissuer, keyid); if (!crlissuer_cert) { log_info ("CRL issuer certificate "); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } if (authidsn) { log_printf ("(#"); dump_serial (authidsn); log_printf ("/"); dump_string (s); log_printf (") "); } log_printf ("not found\n"); } ksba_name_release (authid); xfree (authidsn); xfree (keyid); } else crlissuer_cert = find_cert_bysubject (ctrl, crlissuer, NULL); err = 0; if (!crlissuer_cert) { err = gpg_error (GPG_ERR_MISSING_CERT); goto failure; } err = finish_sig_check (crl, md, algo, crlissuer_cert, use_pss); md = NULL; /* Closed. */ if (err) { log_error (_("CRL signature verification failed: %s\n"), gpg_strerror (err)); goto failure; } err = validate_cert_chain (ctrl, crlissuer_cert, NULL, (VALIDATE_FLAG_TRUST_CONFIG | VALIDATE_FLAG_CRL | VALIDATE_FLAG_RECURSIVE), r_trust_anchor); if (err) { log_error (_("error checking validity of CRL " "issuer certificate: %s\n"), gpg_strerror (err)); goto failure; } } break; default: log_debug ("crl_parse_insert: unknown stop reason\n"); err = gpg_error (GPG_ERR_BUG); goto failure; } } while (stopreason != KSBA_SR_READY); assert (!err); failure: abort_sig_check (crl, md); ksba_cert_release (crlissuer_cert); return err; } /* Return the crlNumber extension as an allocated hex string or NULL if there is none. */ static char * get_crl_number (ksba_crl_t crl) { gpg_error_t err; ksba_sexp_t number; char *string; err = ksba_crl_get_crl_number (crl, &number); if (err) return NULL; string = serial_hex (number); ksba_free (number); return string; } /* Return the authorityKeyIdentifier or NULL if it is not available. The issuer name may consists of several parts - they are delimted by 0x01. */ static char * get_auth_key_id (ksba_crl_t crl, char **serialno) { gpg_error_t err; ksba_name_t name; ksba_sexp_t sn; int idx; const char *s; char *string; size_t length; *serialno = NULL; err = ksba_crl_get_auth_key_id (crl, NULL, &name, &sn); if (err) return NULL; *serialno = serial_hex (sn); ksba_free (sn); if (!name) return xstrdup (""); length = 0; for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); length += strlen (p?p:s) + 1; xfree (p); } string = xtrymalloc (length+1); if (string) { *string = 0; for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); if (*string) strcat (string, "\x01"); strcat (string, p?p:s); xfree (p); } } ksba_name_release (name); return string; } /* Insert the CRL retrieved using URL into the cache specified by CACHE. The CRL itself will be read from the stream FP and is expected in binary format. Called by: crl_cache_load cmd_loadcrl --load-crl crl_cache_reload_crl cmd_isvalid cmd_checkcrl cmd_loadcrl --fetch-crl */ gpg_error_t crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader) { crl_cache_t cache = get_current_cache (); gpg_error_t err, err2; ksba_crl_t crl; char *fname = NULL; char *newfname = NULL; struct cdb_make cdb; int fd_cdb = -1; char *issuer = NULL; char *issuer_hash = NULL; ksba_isotime_t thisupdate, nextupdate; crl_cache_entry_t entry = NULL; crl_cache_entry_t e; gnupg_isotime_t current_time; char *checksum = NULL; int invalidate_crl = 0; int idx; const char *oid; int critical; char *trust_anchor = NULL; /* FIXME: We should acquire a mutex for the URL, so that we don't simultaneously enter the same CRL twice. However this needs to be interweaved with the checking function.*/ err2 = 0; err = ksba_crl_new (&crl); if (err) { log_error (_("ksba_crl_new failed: %s\n"), gpg_strerror (err)); goto leave; } err = ksba_crl_set_reader (crl, reader); if ( err ) { log_error (_("ksba_crl_set_reader failed: %s\n"), gpg_strerror (err)); goto leave; } /* Create a temporary cache file to load the CRL into. */ { char *tmpfname, *p; const char *nodename; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; #endif #ifdef HAVE_W32_SYSTEM nodename = "unknown"; #else if (uname (&utsbuf)) nodename = "unknown"; else nodename = utsbuf.nodename; #endif gpgrt_asprintf (&tmpfname, "crl-tmp-%s-%u-%p.db.tmp", nodename, (unsigned int)getpid (), &tmpfname); if (!tmpfname) { err = gpg_error_from_syserror (); goto leave; } for (p=tmpfname; *p; p++) if (*p == '/') *p = '.'; fname = make_filename (opt.homedir_cache, DBDIR_D, tmpfname, NULL); xfree (tmpfname); if (!gnupg_remove (fname)) log_info (_("removed stale temporary cache file '%s'\n"), fname); else if (errno != ENOENT) { err = gpg_error_from_syserror (); log_error (_("problem removing stale temporary cache file '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } } fd_cdb = gnupg_open (fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); if (fd_cdb == -1) { err = gpg_error_from_errno (errno); log_error (_("error creating temporary cache file '%s': %s\n"), fname, strerror (errno)); goto leave; } cdb_make_start(&cdb, fd_cdb); err = crl_parse_insert (ctrl, crl, &cdb, fname, &issuer, thisupdate, nextupdate, &trust_anchor); if (err) { log_error (_("crl_parse_insert failed: %s\n"), gpg_strerror (err)); /* Error in cleanup ignored. */ cdb_make_finish (&cdb); goto leave; } /* Finish the database. */ if (cdb_make_finish (&cdb)) { err = gpg_error_from_errno (errno); log_error (_("error finishing temporary cache file '%s': %s\n"), fname, strerror (errno)); goto leave; } if (close (fd_cdb)) { err = gpg_error_from_errno (errno); log_error (_("error closing temporary cache file '%s': %s\n"), fname, strerror (errno)); goto leave; } fd_cdb = -1; /* Create a checksum. */ { unsigned char md5buf[16]; if (hash_dbfile (fname, md5buf)) { err = gpg_error (GPG_ERR_CHECKSUM); goto leave; } checksum = hexify_data (md5buf, 16, 0); } /* Check whether that new CRL is still not expired. */ gnupg_get_isotime (current_time); if (strcmp (nextupdate, current_time) < 0 ) { if (opt.force) log_info (_("WARNING: new CRL still too old; it expired on %s " "- loading anyway\n"), nextupdate); else { log_error (_("new CRL still too old; it expired on %s\n"), nextupdate); if (!err2) err2 = gpg_error (GPG_ERR_CRL_TOO_OLD); invalidate_crl |= 1; } } /* Check for unknown critical extensions. */ for (idx=0; !(err=ksba_crl_get_extension (crl, idx, &oid, &critical, NULL, NULL)); idx++) { if (!critical || !strcmp (oid, oidstr_authorityKeyIdentifier) || !strcmp (oid, oidstr_crlNumber) ) continue; log_error (_("unknown critical CRL extension %s\n"), oid); if (!err2) err2 = gpg_error (GPG_ERR_INV_CRL); invalidate_crl |= 2; } if (gpg_err_code (err) == GPG_ERR_EOF || gpg_err_code (err) == GPG_ERR_NO_DATA ) err = 0; if (err) { log_error (_("error reading CRL extensions: %s\n"), gpg_strerror (err)); err = gpg_error (GPG_ERR_INV_CRL); } /* Create an hex encoded SHA-1 hash of the issuer DN to be used as the key for the cache. */ issuer_hash = hashify_data (issuer, strlen (issuer)); /* Create an ENTRY. */ entry = xtrycalloc (1, sizeof *entry); if (!entry) { err = gpg_error_from_syserror (); goto leave; } entry->release_ptr = xtrymalloc (strlen (issuer_hash) + 1 + strlen (issuer) + 1 + strlen (url) + 1 + strlen (checksum) + 1); if (!entry->release_ptr) { err = gpg_error_from_syserror (); xfree (entry); entry = NULL; goto leave; } entry->issuer_hash = entry->release_ptr; entry->issuer = stpcpy (entry->issuer_hash, issuer_hash) + 1; entry->url = stpcpy (entry->issuer, issuer) + 1; entry->dbfile_hash = stpcpy (entry->url, url) + 1; strcpy (entry->dbfile_hash, checksum); gnupg_copy_time (entry->this_update, thisupdate); gnupg_copy_time (entry->next_update, nextupdate); gnupg_copy_time (entry->last_refresh, current_time); entry->crl_number = get_crl_number (crl); entry->authority_issuer = get_auth_key_id (crl, &entry->authority_serialno); entry->invalid = invalidate_crl; entry->user_trust_req = !!trust_anchor; entry->check_trust_anchor = trust_anchor; trust_anchor = NULL; /* Check whether we already have an entry for this issuer and mark it as deleted. We better use a loop, just in case duplicates got somehow into the list. */ for (e = cache->entries; (e=find_entry (e, entry->issuer_hash)); e = e->next) e->deleted = 1; /* Rename the temporary DB to the real name. */ newfname = make_db_file_name (entry->issuer_hash); if (opt.verbose) log_info (_("creating cache file '%s'\n"), newfname); /* Just in case close unused matching files. Actually we need this only under Windows but saving file descriptors is never bad. */ { int any; do { any = 0; for (e = cache->entries; e; e = e->next) if (!e->cdb_use_count && e->cdb && !strcmp (e->issuer_hash, entry->issuer_hash)) { int fd = cdb_fileno (e->cdb); cdb_free (e->cdb); xfree (e->cdb); e->cdb = NULL; if (close (fd)) log_error (_("error closing cache file: %s\n"), strerror(errno)); any = 1; break; } } while (any); } #ifdef HAVE_W32_SYSTEM gnupg_remove (newfname); #endif if (rename (fname, newfname)) { err = gpg_error_from_syserror (); log_error (_("problem renaming '%s' to '%s': %s\n"), fname, newfname, gpg_strerror (err)); goto leave; } xfree (fname); fname = NULL; /*(let the cleanup code not try to remove it)*/ /* Link the new entry in. */ entry->next = cache->entries; cache->entries = entry; entry = NULL; err = update_dir (cache); if (err) { log_error (_("updating the DIR file failed - " "cache entry will get lost with the next program start\n")); err = 0; /* Keep on running. */ } leave: release_one_cache_entry (entry); if (fd_cdb != -1) close (fd_cdb); if (fname) { gnupg_remove (fname); xfree (fname); } xfree (newfname); ksba_crl_release (crl); xfree (issuer); xfree (issuer_hash); xfree (checksum); xfree (trust_anchor); return err ? err : err2; } /* Print one cached entry E in a human readable format to stream FP. Return 0 on success. */ static gpg_error_t list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp) { struct cdb_find cdbfp; struct cdb *cdb; int rc; int warn = 0; const unsigned char *s; es_fputs ("--------------------------------------------------------\n", fp ); es_fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url ); es_fprintf (fp, " Issuer:\t%s\n", e->issuer ); es_fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash ); es_fprintf (fp, " This Update:\t%s\n", e->this_update ); es_fprintf (fp, " Next Update:\t%s\n", e->next_update ); es_fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none"); es_fprintf (fp, " AuthKeyId :\t%s\n", e->authority_serialno? e->authority_serialno:"none"); if (e->authority_serialno && e->authority_issuer) { es_fputs (" \t", fp); for (s=e->authority_issuer; *s; s++) if (*s == '\x01') es_fputs ("\n \t", fp); else es_putc (*s, fp); es_putc ('\n', fp); } es_fprintf (fp, " Trust Check:\t%s\n", !e->user_trust_req? "[system]" : e->check_trust_anchor? e->check_trust_anchor:"[missing]"); if ((e->invalid & 1)) es_fprintf (fp, _(" ERROR: The CRL will not be used " "because it was still too old after an update!\n")); if ((e->invalid & 2)) es_fprintf (fp, _(" ERROR: The CRL will not be used " "due to an unknown critical extension!\n")); if ((e->invalid & ~3)) es_fprintf (fp, _(" ERROR: The CRL will not be used\n")); cdb = lock_db_file (cache, e); if (!cdb) return gpg_error (GPG_ERR_GENERAL); if (!e->dbfile_checked) es_fprintf (fp, _(" ERROR: This cached CRL may have been tampered with!\n")); es_putc ('\n', fp); rc = cdb_findinit (&cdbfp, cdb, NULL, 0); while (!rc && (rc=cdb_findnext (&cdbfp)) > 0 ) { unsigned char keyrecord[256]; unsigned char record[16]; int reason; int any = 0; cdbi_t n; cdbi_t i; rc = 0; n = cdb_datalen (cdb); if (n != 16) { log_error (_(" WARNING: invalid cache record length\n")); warn = 1; continue; } if (cdb_read (cdb, record, n, cdb_datapos (cdb))) { log_error (_("problem reading cache record: %s\n"), strerror (errno)); warn = 1; continue; } n = cdb_keylen (cdb); if (n > sizeof keyrecord) n = sizeof keyrecord; if (cdb_read (cdb, keyrecord, n, cdb_keypos (cdb))) { log_error (_("problem reading cache key: %s\n"), strerror (errno)); warn = 1; continue; } reason = *record; es_fputs (" ", fp); for (i = 0; i < n; i++) es_fprintf (fp, "%02X", keyrecord[i]); es_fputs (":\t reasons( ", fp); if (reason & KSBA_CRLREASON_UNSPECIFIED) es_fputs( "unspecified ", fp ), any = 1; if (reason & KSBA_CRLREASON_KEY_COMPROMISE ) es_fputs( "key_compromise ", fp ), any = 1; if (reason & KSBA_CRLREASON_CA_COMPROMISE ) es_fputs( "ca_compromise ", fp ), any = 1; if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED ) es_fputs( "affiliation_changed ", fp ), any = 1; if (reason & KSBA_CRLREASON_SUPERSEDED ) es_fputs( "superseded", fp ), any = 1; if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION ) es_fputs( "cessation_of_operation", fp ), any = 1; if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD ) es_fputs( "certificate_hold", fp ), any = 1; if (reason && !any) es_fputs( "other", fp ); es_fprintf (fp, ") rdate: %.15s\n", record+1); } if (rc) log_error (_("error reading cache entry from db: %s\n"), strerror (rc)); unlock_db_file (cache, e); es_fprintf (fp, _("End CRL dump\n") ); es_putc ('\n', fp); return (rc||warn)? gpg_error (GPG_ERR_GENERAL) : 0; } /* Print the contents of the CRL CACHE in a human readable format to stream FP. */ gpg_error_t crl_cache_list (estream_t fp) { crl_cache_t cache = get_current_cache (); crl_cache_entry_t entry; gpg_error_t err = 0; for (entry = cache->entries; entry && !entry->deleted && !err; entry = entry->next ) err = list_one_crl_entry (cache, entry, fp); return err; } /* Load the CRL containing the file named FILENAME into our CRL cache. */ gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename) { gpg_error_t err; estream_t fp; ksba_reader_t reader; fp = es_fopen (filename, "rb"); if (!fp) { err = gpg_error_from_errno (errno); log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); return err; } err = create_estream_ksba_reader (&reader, fp); if (!err) { err = crl_cache_insert (ctrl, filename, reader); ksba_reader_release (reader); } es_fclose (fp); return err; } /* Locate the corresponding CRL for the certificate CERT, read and verify the CRL and store it in the cache. */ gpg_error_t crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert) { gpg_error_t err; ksba_reader_t reader = NULL; char *issuer = NULL; ksba_name_t distpoint = NULL; ksba_name_t issuername = NULL; char *distpoint_uri = NULL; char *issuername_uri = NULL; int any_dist_point = 0; int seq; /* Loop over all distribution points, get the CRLs and put them into the cache. */ if (opt.verbose) log_info ("checking distribution points\n"); seq = 0; while ( !(err = ksba_cert_get_crl_dist_point (cert, seq++, &distpoint, &issuername, NULL ))) { int name_seq; gpg_error_t last_err = 0; if (!distpoint && !issuername) { if (opt.verbose) log_info ("no issuer name and no distribution point\n"); break; /* Not allowed; i.e. an invalid certificate. We give up here and hope that the default method returns a suitable CRL. */ } xfree (issuername_uri); issuername_uri = NULL; /* Get the URIs. We do this in a loop to iterate over all names in the crlDP. */ for (name_seq=0; ksba_name_enum (distpoint, name_seq); name_seq++) { xfree (distpoint_uri); distpoint_uri = NULL; distpoint_uri = ksba_name_get_uri (distpoint, name_seq); if (!distpoint_uri) continue; if (!strncmp (distpoint_uri, "ldap:", 5) || !strncmp (distpoint_uri, "ldaps:", 6)) { if (opt.ignore_ldap_dp) continue; } else if (!strncmp (distpoint_uri, "http:", 5) || !strncmp (distpoint_uri, "https:", 6)) { if (opt.ignore_http_dp) continue; } else continue; /* Skip unknown schemes. */ any_dist_point = 1; if (opt.verbose) log_info ("fetching CRL from '%s'\n", distpoint_uri); err = crl_fetch (ctrl, distpoint_uri, &reader); if (err) { log_error (_("crl_fetch via DP failed: %s\n"), gpg_strerror (err)); last_err = err; continue; /* with the next name. */ } if (opt.verbose) log_info ("inserting CRL (reader %p)\n", reader); err = crl_cache_insert (ctrl, distpoint_uri, reader); if (err) { log_error (_("crl_cache_insert via DP failed: %s\n"), gpg_strerror (err)); last_err = err; continue; /* with the next name. */ } last_err = 0; break; /* Ready. */ } if (last_err) { err = last_err; goto leave; } ksba_name_release (distpoint); distpoint = NULL; /* We don't do anything with issuername_uri yet but we keep the code for documentation. */ issuername_uri = ksba_name_get_uri (issuername, 0); ksba_name_release (issuername); issuername = NULL; /* Close the reader. */ crl_close_reader (reader); reader = NULL; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; /* If we did not found any distpoint, try something reasonable. */ if (!any_dist_point ) { if (opt.verbose) log_info ("no distribution point - trying issuer name\n"); crl_close_reader (reader); reader = NULL; issuer = ksba_cert_get_issuer (cert, 0); if (!issuer) { log_error ("oops: issuer missing in certificate\n"); err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } if (opt.verbose) log_info ("fetching CRL from default location\n"); err = crl_fetch_default (ctrl, issuer, &reader); if (err) { log_error ("crl_fetch via issuer failed: %s\n", gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("inserting CRL (reader %p)\n", reader); err = crl_cache_insert (ctrl, "default location(s)", reader); if (err) { log_error (_("crl_cache_insert via issuer failed: %s\n"), gpg_strerror (err)); goto leave; } } leave: crl_close_reader (reader); xfree (distpoint_uri); xfree (issuername_uri); ksba_name_release (distpoint); ksba_name_release (issuername); ksba_free (issuer); return err; } diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 8be614c8d..4a29bf8ad 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1,2415 +1,2415 @@ /* 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 #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif #include #include #ifdef HAVE_SIGNAL_H # include #endif #ifdef HAVE_INOTIFY_INIT # include #endif /*HAVE_INOTIFY_INIT*/ #include #include "dirmngr-err.h" #if HTTP_USE_NTBTLS # include #elif HTTP_USE_GNUTLS # include #endif /*HTTP_USE_GNUTLS*/ #define INCLUDED_BY_MAIN_MODULE 1 #define GNUPG_COMMON_NEED_AFLOCAL #include "dirmngr.h" #include #include "certcache.h" #include "crlcache.h" #include "crlfetch.h" #include "misc.h" #if USE_LDAP # include "ldapserver.h" #endif #include "../common/asshelp.h" #if USE_LDAP # include "ldap-wrapper.h" #endif #include "../common/init.h" #include "../common/gc-opt-flags.h" #include "dns-stuff.h" #include "http-common.h" #ifndef ENAMETOOLONG # define ENAMETOOLONG EINVAL #endif enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aServer, aDaemon, aSupervised, aListCRLs, aLoadCRL, aFetchCRL, aShutdown, aFlush, aGPGConfList, aGPGConfTest, aGPGConfVersions, oOptions, oDebug, oDebugAll, oDebugWait, oDebugLevel, oGnutlsDebug, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oLogFile, oBatch, oDisableHTTP, oDisableLDAP, oDisableIPv4, oDisableIPv6, oIgnoreLDAPDP, oIgnoreHTTPDP, oIgnoreOCSPSvcUrl, oHonorHTTPProxy, oHTTPProxy, oLDAPProxy, oOnlyLDAPProxy, oLDAPFile, oLDAPTimeout, oLDAPAddServers, oOCSPResponder, oOCSPSigner, oOCSPMaxClockSkew, oOCSPMaxPeriod, oOCSPCurrentPeriod, oMaxReplies, oHkpCaCert, oFakedSystemTime, oForce, oAllowOCSP, oAllowVersionCheck, oSocketName, oLDAPWrapperProgram, oHTTPWrapperProgram, oIgnoreCertExtension, oUseTor, oNoUseTor, oKeyServer, oNameServer, oDisableCheckOwnSocket, oStandardResolver, oRecursiveResolver, oResolverTimeout, oConnectTimeout, oConnectQuickTimeout, oListenBacklog, aTest }; static 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_c (aGPGConfVersions, "gpgconf-versions", "@"), 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 registred with --hkp-cacert. */ static strlist_t hkp_cacert_filenames; /* The timer tick used for housekeeping stuff. The second constant is used when a shutdown is pending. */ #define TIMERTICK_INTERVAL (60) #define TIMERTICK_INTERVAL_SHUTDOWN (4) /* How oft to run the housekeeping. */ #define HOUSEKEEPING_INTERVAL (600) /* This union is used to avoid compiler warnings in case a pointer is 64 bit and an int 32 bit. We store an integer in a pointer and get it back later (npth_getspecific et al.). */ union int_and_ptr_u { int aint; assuan_fd_t afd; void *aptr; }; /* The key used to store the current file descriptor in the thread local storage. We use this in conjunction with the log_set_pid_suffix_cb feature. */ #ifndef HAVE_W32_SYSTEM static npth_key_t my_tlskey_current_fd; #endif /* Prototypes. */ static void cleanup (void); #if USE_LDAP static ldap_server_t parse_ldapserver_file (const char* filename, int ienoent); #endif /*USE_LDAP*/ static fingerprint_list_t parse_ocsp_signer (const char *string); static void netactivity_action (void); static void handle_connections (assuan_fd_t listen_fd); static void gpgconf_versions (void); /* NPth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static const char * my_strusage( int level ) { const char *p; switch ( level ) { case 11: p = "@DIRMNGR@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)"); break; case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n" "Keyserver, CRL, and OCSP access for @GNUPG@\n"); break; default: p = NULL; } return p; } /* Callback from libksba to hash a provided buffer. Our current implementation does only allow SHA-1 for hashing. This may be extended by mapping the name, testing for algorithm availibility and adjust the length checks accordingly. */ static gpg_error_t my_ksba_hash_buffer (void *arg, const char *oid, const void *buffer, size_t length, size_t resultsize, unsigned char *result, size_t *resultlen) { (void)arg; if (oid && strcmp (oid, "1.3.14.3.2.26")) return gpg_error (GPG_ERR_NOT_SUPPORTED); if (resultsize < 20) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); gcry_md_hash_buffer (2, result, buffer, length); *resultlen = 20; return 0; } /* GNUTLS log function callback. */ #ifdef HTTP_USE_GNUTLS static void my_gnutls_log (int level, const char *text) { int n; n = strlen (text); while (n && text[n-1] == '\n') n--; log_debug ("gnutls:L%d: %.*s\n", level, n, text); } #endif /*HTTP_USE_GNUTLS*/ /* Setup the debugging. With a LEVEL of NULL only the active debug flags are propagated to the subsystems. With LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. */ static void set_debug (void) { int numok = (debug_level && digitp (debug_level)); int numlvl = numok? atoi (debug_level) : 0; if (!debug_level) ; else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE); else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level '%s' given\n"), debug_level); log_info (_("valid debug levels are: %s\n"), "none, basic, advanced, expert, guru"); opt.debug = 0; /* Reset debugging, so that prior debug statements won't have an undesired effect. */ } if (opt.debug && !opt.verbose) { opt.verbose = 1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); } if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); #if HTTP_USE_NTBTLS if (opt_gnutls_debug >= 0) { ntbtls_set_debug (opt_gnutls_debug, NULL, NULL); } #elif HTTP_USE_GNUTLS if (opt_gnutls_debug >= 0) { gnutls_global_set_log_function (my_gnutls_log); gnutls_global_set_log_level (opt_gnutls_debug); } #endif /*HTTP_USE_GNUTLS*/ if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } static void set_tor_mode (void) { if (dirmngr_use_tor ()) { /* Enable Tor mode and when called again force a new curcuit * (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 fucntion 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 (); set_tor_mode (); } #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_with_prefix (GPGRT_LOG_INFO, "ntbtls: ", fmt, argv); else { char prefix[10+20]; snprintf (prefix, sizeof prefix, "ntbtls(%d): ", level); log_logv_with_prefix (GPGRT_LOG_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: case aGPGConfVersions: cmd = pargs.r_opt; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oBatch: opt.batch=1; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case 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 (!gnupg_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"); } post_option_parsing (); /* Get LDAP server list from file. */ #if USE_LDAP if (!ldapfile) { ldapfile = make_filename (gnupg_homedir (), "dirmngr_ldapservers.conf", NULL); opt.ldapservers = parse_ldapserver_file (ldapfile, 1); xfree (ldapfile); } else opt.ldapservers = parse_ldapserver_file (ldapfile, 0); #endif /*USE_LDAP*/ #ifndef HAVE_W32_SYSTEM /* We need to ignore the PIPE signal because the we might log to a socket and that code handles EPIPE properly. The ldap wrapper also requires us to ignore this silly signal. Assuan would set this signal to ignore anyway.*/ signal (SIGPIPE, SIG_IGN); #endif /* Ready. Now to our duties. */ if (!cmd) cmd = aServer; rc = 0; if (cmd == aServer) { /* Note that this server mode is mainly useful for debugging. */ if (argc) wrong_args ("--server"); if (logfile) { log_set_file (logfile); log_set_prefix (NULL, GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } if (debug_wait) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } thread_init (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); 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 (); 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 (); 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 (); 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 (); 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); } else if (cmd == aGPGConfVersions) gpgconf_versions (); cleanup (); return !!rc; } static void cleanup (void) { crl_cache_deinit (); cert_cache_deinit (1); reload_dns_stuff (1); #if USE_LDAP ldapserver_list_free (opt.ldapservers); #endif /*USE_LDAP*/ opt.ldapservers = NULL; if (cleanup_socket) { cleanup_socket = 0; if (redir_socket_name) gnupg_remove (redir_socket_name); else if (socket_name && *socket_name) gnupg_remove (socket_name); } } void dirmngr_exit (int rc) { cleanup (); exit (rc); } void dirmngr_init_default_ctrl (ctrl_t ctrl) { ctrl->magic = SERVER_CONTROL_MAGIC; if (opt.http_proxy) ctrl->http_proxy = xstrdup (opt.http_proxy); ctrl->http_no_crl = 1; ctrl->timeout = opt.connect_timeout; } void dirmngr_deinit_default_ctrl (ctrl_t ctrl) { if (!ctrl) return; ctrl->magic = 0xdeadbeef; xfree (ctrl->http_proxy); ctrl->http_proxy = NULL; } /* Create a list of LDAP servers from the file FILENAME. Returns the list or NULL in case of errors. The format fo such a file is line oriented where empty lines and lines starting with a hash mark are ignored. All other lines are assumed to be colon seprated with these fields: 1. field: Hostname 2. field: Portnumber 3. field: Username 4. field: Password 5. field: Base DN */ #if USE_LDAP static ldap_server_t parse_ldapserver_file (const char* filename, int ignore_enoent) { char buffer[1024]; char *p; ldap_server_t server, serverstart, *serverend; int c; unsigned int lineno = 0; estream_t fp; fp = es_fopen (filename, "r"); if (!fp) { if (errno == ENOENT) { if (!ignore_enoent) log_info ("No ldapserver file at: '%s'\n", filename); } else log_error (_("error opening '%s': %s\n"), filename, strerror (errno)); return NULL; } serverstart = NULL; serverend = &serverstart; while (es_fgets (buffer, sizeof buffer, fp)) { lineno++; if (!*buffer || buffer[strlen(buffer)-1] != '\n') { if (*buffer && es_feof (fp)) ; /* Last line not terminated - continue. */ else { log_error (_("%s:%u: line too long - skipped\n"), filename, lineno); while ( (c=es_fgetc (fp)) != EOF && c != '\n') ; /* Skip until end of line. */ continue; } } /* Skip empty and comment lines.*/ for (p=buffer; spacep (p); p++) ; if (!*p || *p == '\n' || *p == '#') continue; /* Parse the colon separated fields. */ server = ldapserver_parse_one (buffer, filename, lineno); if (server) { *serverend = server; serverend = &server->next; } } if (es_ferror (fp)) log_error (_("error reading '%s': %s\n"), filename, strerror (errno)); es_fclose (fp); return serverstart; } #endif /*USE_LDAP*/ static fingerprint_list_t parse_ocsp_signer (const char *string) { gpg_error_t err; char *fname; estream_t fp; char line[256]; char *p; fingerprint_list_t list, *list_tail, item; unsigned int lnr = 0; int c, i, j; int errflag = 0; /* Check whether this is not a filename and treat it as a direct fingerprint specification. */ if (!strpbrk (string, "/.~\\")) { item = xcalloc (1, sizeof *item); for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++) if ( string[i] != ':' ) item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i]; item->hexfpr[j] = 0; if (j != 40 || !(spacep (string+i) || !string[i])) { log_error (_("%s:%u: invalid fingerprint detected\n"), "--ocsp-signer", 0); xfree (item); return NULL; } return item; } /* Well, it is a filename. */ if (*string == '/' || (*string == '~' && string[1] == '/')) fname = make_filename (string, NULL); else { if (string[0] == '.' && string[1] == '/' ) string += 2; fname = make_filename (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' 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 (); 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; } #if GPGRT_GCC_HAVE_PUSH_PRAGMA # pragma GCC push_options # pragma GCC optimize ("no-strict-overflow") #endif static int time_for_housekeeping_p (time_t curtime) { static time_t last_housekeeping; if (!last_housekeeping) last_housekeeping = curtime; if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime || last_housekeeping > curtime /*(be prepared for y2038)*/) { last_housekeeping = curtime; return 1; } return 0; } #if GPGRT_GCC_HAVE_PUSH_PRAGMA # pragma GCC pop_options #endif /* This is the worker for the ticker. It is called every few seconds and may only do fast operations. */ static void handle_tick (void) { 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) + && gnupg_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 (); } /* Parse the revision part from the extended version blurb. */ static const char * get_revision_from_blurb (const char *blurb, int *r_len) { const char *s = blurb? blurb : ""; int n; for (; *s; s++) if (*s == '\n' && s[1] == '(') break; if (s) { s += 2; for (n=0; s[n] && s[n] != ' '; n++) ; } else { s = "?"; n = 1; } *r_len = n; return s; } /* Print versions of dirmngr and used libraries. This is used by * "gpgconf --show-versions" so that there is no need to link gpgconf * against all these libraries. This is an internal API and should * not be relied upon. */ static void gpgconf_versions (void) { const char *s; int n; /* Unfortunately Npth has no way to get the version. */ s = get_revision_from_blurb (assuan_check_version ("\x01\x01"), &n); es_fprintf (es_stdout, "* Libassuan %s (%.*s)\n\n", assuan_check_version (NULL), n, s); es_fprintf (es_stdout, "* KSBA %s \n\n", ksba_check_version (NULL)); #ifdef HTTP_USE_NTBTLS s = get_revision_from_blurb (ntbtls_check_version ("\x01\x01"), &n); es_fprintf (es_stdout, "* NTBTLS %s (%.*s)\n\n", ntbtls_check_version (NULL), n, s); #elif HTTP_USE_GNUTLS es_fprintf (es_stdout, "* GNUTLS %s\n\n", gnutls_check_version (NULL)); #endif } diff --git a/g10/gpg.c b/g10/gpg.c index 96c79b953..b5f1ba796 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -1,5540 +1,5540 @@ /* gpg.c - The GnuPG utility (main for gpg) * Copyright (C) 1998-2019 Free Software Foundation, Inc. * Copyright (C) 1997-2019 Werner Koch * Copyright (C) 2015-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 . */ #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 #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 "../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, 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, oWithSecret, oWithWKDHash, oWithColons, oWithKeyData, oWithKeyOrigin, oWithTofuInfo, oWithSigList, oWithSigCheck, oAnswerYes, oAnswerNo, oKeyring, oPrimaryKeyring, oSecretKeyring, oShowKeyring, oDefaultKey, oDefRecipient, oDefRecipientSelf, oNoDefRecipient, oTrySecretKey, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugIOLBF, oStatusFD, oStatusFile, oAttributeFD, oAttributeFile, oEmitVersion, oNoEmitVersion, oCompletesNeeded, oMarginalsNeeded, oMaxCertDepth, oLoadExtension, oCompliance, oGnuPG, oRFC2440, oRFC4880, oRFC4880bis, oOpenPGP, oPGP6, oPGP7, oPGP8, oDE_VS, oRFC2440Text, oNoRFC2440Text, oCipherAlgo, 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, 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, oAutoKeyImport, oNoAutoKeyImport, oUseAgent, oNoUseAgent, oGpgAgentInfo, oMergeOnly, oTryAllSecrets, oTrustedKey, oNoExpensiveTrustChecks, oFixedListMode, oLegacyListMode, oNoSigCache, oAutoCheckTrustDB, oNoAutoCheckTrustDB, oPreservePermissions, oDefaultPreferenceList, oDefaultKeyserverURL, oPersonalCipherPreferences, oPersonalDigestPreferences, oPersonalCompressPreferences, oAgentProgram, 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, oAllowMultisigVerification, oEnableLargeRSA, oDisableLargeRSA, oEnableDSA2, oDisableDSA2, oAllowMultipleMessages, oNoAllowMultipleMessages, oAllowWeakDigestAlgos, oAllowWeakKeySignatures, oFakedSystemTime, oNoAutostart, oPrintPKARecords, oPrintDANERecords, oTOFUDefaultPolicy, oTOFUDBFormat, oDefaultNewKeyAlgo, oWeakDigest, oUnwrap, oOnlySignTextIDs, oDisableSignerUID, oSender, oKeyOrigin, oRequestOrigin, oNoSymkeyCache, oUseOnlyOpenPGPCard, oIncludeKeyBlock, oNoIncludeKeyBlock, oNoop }; static ARGPARSE_OPTS 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_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", "@"), ARGPARSE_s_n (oNoTTY, "no-tty", "@"), 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_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_n (oDebugIOLBF, "debug-iolbf", "@"), 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 (oPGP6, "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 (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_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 (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 (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 (oPersonalDigestPreferences, "personal-digest-prefs", "@"), ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"), ARGPARSE_s_s (oAgentProgram, "agent-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 (oAllowMultisigVerification, "allow-multisig-verification", "@"), 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 (oAllowMultipleMessages, "allow-multiple-messages", "@"), ARGPARSE_s_n (oNoAllowMultipleMessages, "no-allow-multiple-messages", "@"), 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 (oIncludeKeyBlock, "include-key-block", "@"), ARGPARSE_s_n (oNoIncludeKeyBlock, "no-include-key-block", "@"), ARGPARSE_s_n (oAutoKeyImport, "auto-key-import", "@"), ARGPARSE_s_n (oNoAutoKeyImport, "no-auto-key-import", "@"), /* 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_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 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); 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_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, *ver_gcry; const char *p; switch( level ) { case 11: p = "@GPG@ (@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 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( !digests ) digests = build_list(_("Hash: "), 'H', build_list_md_algo_name, build_list_md_test_algo ); p = digests; break; case 37: 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; 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); for (i=0; i <= 110; 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); } /* 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 = gnupg_open (fname, O_CREAT | O_TRUNC | O_WRONLY | binary, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); else fd = gnupg_open (fname, O_RDONLY | binary, 0); } 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) + if (gnupg_stat (tmppath,&statbuf)) { 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)) + if (gnupg_stat (dir,&dirbuf) || !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); 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-import:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("include-key-block:%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); } 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; 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; 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_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_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; #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); 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_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; /* Check whether we have a config file 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 ) { default_config = 0; /* --no-options */ opt.no_homedir_creation = 1; } 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 */ } } #ifdef HAVE_DOSISH_SYSTEM 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; /* 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) ) { switch( pargs.r_opt ) { 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 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"); break; case oGpgAgentInfo: obsolete_option (configname, configlineno, "gpg-agent-info"); break; case oReaderPort: obsolete_scdaemon_option (configname, configlineno, "reader-port"); break; case octapiDriver: obsolete_scdaemon_option (configname, configlineno, "ctapi-driver"); break; case opcscDriver: obsolete_scdaemon_option (configname, configlineno, "pcsc-driver"); break; case oDisableCCID: obsolete_scdaemon_option (configname, configlineno, "disable-ccid"); break; case oHonorHttpProxy: obsolete_option (configname, configlineno, "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", "--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 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 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) 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"); 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 oPGP6: 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", "--list-options ","show-policy-urls"); deprecated_warning(configname,configlineno,"--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", "--list-options ","no-show-policy-urls"); deprecated_warning(configname,configlineno,"--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, "--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", "--list-options ","show-photos"); deprecated_warning(configname,configlineno,"--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", "--list-options ","no-show-photos"); deprecated_warning(configname,configlineno,"--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 oDisableSignerUID: opt.flags.disable_signer_uid = 1; break; case oIncludeKeyBlock: opt.flags.include_key_block = 1; break; case oNoIncludeKeyBlock: opt.flags.include_key_block = 0; 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) 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) 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; 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) sl->flags |= PK_LIST_CONFIG; break; case oSender: { char *mbox = mailbox_from_userid (pargs.r.ret_str); 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 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); 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); 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); 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); 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); 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", "--list-options ","show-notations"); deprecated_warning(configname,configlineno,"--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", "--list-options ","no-show-notations"); deprecated_warning(configname,configlineno,"--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 oAutoKeyImport: opt.flags.auto_key_import = 1; break; case oNoAutoKeyImport: opt.flags.auto_key_import = 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", "--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 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 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); 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); 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 oAllowMultisigVerification: case oAllowMultipleMessages: opt.flags.allow_multiple_messages=1; break; case oNoAllowMultipleMessages: opt.flags.allow_multiple_messages=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 oNoop: break; default: if (configfp) pargs.err = ARGPARSE_PRINT_WARNING; else { pargs.err = ARGPARSE_PRINT_ERROR; /* The argparse fucntion 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; 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); g10_exit (0); } xfree (save_configname); xfree (default_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) ); } #ifdef IS_DEVELOPMENT_VERSION if (!opt.batch) { const char *s; if((s=strusage(25))) log_info("%s\n",s); if((s=strusage(26))) log_info("%s\n",s); if((s=strusage(27))) log_info("%s\n",s); } #endif /* FIXME: We should use logging to a file only in server mode; however we have not yet implemetyed that. Thus we try to get away with --batch as indication for logging to file required. */ if (logfile && opt.batch) { 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) log_info ("WARNING: using experimental features from RFC4880bis!\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(PGP6) { /* 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(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_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_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")); /* 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) { 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_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_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. */ 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 (!gnupg_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 has been used. */ if (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); if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) /* In loopback mode, never ask for the password multiple times. */ { opt.passphrase_repeat = 0; } 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(PGP6 || 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(PGP6 || 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; /* Print a note if the user did not specify any key. */ if (!argc && !opt.quiet) log_info (_("Note: %s\n"), gpg_strerror (GPG_ERR_NO_KEY)); /* 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; /* Wee 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 levae 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 (); 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_FPR16 || desc.mode == KEYDB_SEARCH_MODE_FPR20 || 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 (); gcry_control (GCRYCTL_DUMP_MEMORY_STATS); gcry_control (GCRYCTL_DUMP_RANDOM_STATS); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); 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/keyring.c b/g10/keyring.c index 6ed9e2b41..8c31ccc87 100644 --- a/g10/keyring.c +++ b/g10/keyring.c @@ -1,1747 +1,1747 @@ /* keyring.c - keyring file handling * Copyright (C) 1998-2010 Free Software Foundation, Inc. * Copyright (C) 1997-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 #include #include #include #include #include #include #include #include "gpg.h" #include "../common/util.h" #include "keyring.h" #include "packet.h" #include "keydb.h" #include "options.h" #include "main.h" /*for check_key_signature()*/ #include "../common/i18n.h" #include "../kbx/keybox.h" typedef struct keyring_resource *KR_RESOURCE; struct keyring_resource { struct keyring_resource *next; int read_only; dotlock_t lockhd; int is_locked; int did_full_scan; char fname[1]; }; typedef struct keyring_resource const * CONST_KR_RESOURCE; static KR_RESOURCE kr_resources; struct keyring_handle { CONST_KR_RESOURCE resource; struct { CONST_KR_RESOURCE kr; IOBUF iobuf; int eof; int error; } current; struct { CONST_KR_RESOURCE kr; off_t offset; size_t pk_no; size_t uid_no; unsigned int n_packets; /*used for delete and update*/ } found, saved_found; struct { char *name; char *pattern; } word_match; }; /* The number of extant handles. */ static int active_handles; static int do_copy (int mode, const char *fname, KBNODE root, off_t start_offset, unsigned int n_packets ); /* We keep a cache of entries that we have entered in the DB. This includes not only public keys, but also subkeys. Note: we'd like to keep the offset of the items that are present, however, this doesn't work, because another concurrent GnuPG process could modify the keyring. */ struct key_present { struct key_present *next; u32 kid[2]; }; /* For the hash table, we use separate chaining with linked lists. This means that we have an array of N linked lists (buckets), which is indexed by KEYID[1] mod N. Elements present in the keyring will be on the list; elements not present in the keyring will not be on the list. Note: since the hash table stores both present and not present information, it cannot be used until we complete a full scan of the keyring. This is indicated by key_present_hash_ready. */ typedef struct key_present **key_present_hash_t; static key_present_hash_t key_present_hash; static int key_present_hash_ready; #define KEY_PRESENT_HASH_BUCKETS 2048 /* Allocate a new value for a key present hash table. */ static struct key_present * key_present_value_new (void) { struct key_present *k; k = xmalloc_clear (sizeof *k); return k; } /* Allocate a new key present hash table. */ static key_present_hash_t key_present_hash_new (void) { struct key_present **tbl; tbl = xmalloc_clear (KEY_PRESENT_HASH_BUCKETS * sizeof *tbl); return tbl; } /* Return whether the value described by KID if it is in the hash table. Otherwise, return NULL. */ static struct key_present * key_present_hash_lookup (key_present_hash_t tbl, u32 *kid) { struct key_present *k; for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next) if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) return k; return NULL; } /* Add the key to the hash table TBL if it is not already present. */ static void key_present_hash_update (key_present_hash_t tbl, u32 *kid) { struct key_present *k; for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next) { if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) return; } k = key_present_value_new (); k->kid[0] = kid[0]; k->kid[1] = kid[1]; k->next = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))] = k; } /* Add all the keys (public and subkeys) present in the keyblock to the hash TBL. */ static void key_present_hash_update_from_kb (key_present_hash_t tbl, KBNODE node) { for (; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { u32 aki[2]; keyid_from_pk (node->pkt->pkt.public_key, aki); key_present_hash_update (tbl, aki); } } } /* * Register a filename for plain keyring files. ptr is set to a * pointer to be used to create a handles etc, or the already-issued * pointer if it has already been registered. The function returns 1 * if a new keyring was registered. */ int keyring_register_filename (const char *fname, int read_only, void **ptr) { KR_RESOURCE kr; if (active_handles) /* There are open handles. */ BUG (); for (kr=kr_resources; kr; kr = kr->next) { if (same_file_p (kr->fname, fname)) { /* Already registered. */ if (read_only) kr->read_only = 1; *ptr=kr; return 0; } } kr = xmalloc (sizeof *kr + strlen (fname)); strcpy (kr->fname, fname); kr->read_only = read_only; kr->lockhd = NULL; kr->is_locked = 0; kr->did_full_scan = 0; /* keep a list of all issued pointers */ kr->next = kr_resources; kr_resources = kr; /* create the offset table the first time a function here is used */ if (!key_present_hash) key_present_hash = key_present_hash_new (); *ptr=kr; return 1; } int keyring_is_writable (void *token) { KR_RESOURCE r = token; return r? (r->read_only || !gnupg_access (r->fname, W_OK)) : 0; } /* Create a new handle for the resource associated with TOKEN. On error NULL is returned and ERRNO is set. The returned handle must be released using keyring_release (). */ KEYRING_HANDLE keyring_new (void *token) { KEYRING_HANDLE hd; KR_RESOURCE resource = token; log_assert (resource); hd = xtrycalloc (1, sizeof *hd); if (!hd) return hd; hd->resource = resource; active_handles++; return hd; } void keyring_release (KEYRING_HANDLE hd) { if (!hd) return; log_assert (active_handles > 0); active_handles--; xfree (hd->word_match.name); xfree (hd->word_match.pattern); iobuf_close (hd->current.iobuf); xfree (hd); } /* Save the current found state in HD for later retrieval by keybox_pop_found_state. Only one state may be saved. */ void keyring_push_found_state (KEYRING_HANDLE hd) { hd->saved_found = hd->found; hd->found.kr = NULL; } /* Restore the saved found state in HD. */ void keyring_pop_found_state (KEYRING_HANDLE hd) { hd->found = hd->saved_found; hd->saved_found.kr = NULL; } const char * keyring_get_resource_name (KEYRING_HANDLE hd) { if (!hd || !hd->resource) return NULL; return hd->resource->fname; } /* * Lock the keyring with the given handle, or unlock if YES is false. * We ignore the handle and lock all registered files. */ int keyring_lock (KEYRING_HANDLE hd, int yes) { KR_RESOURCE kr; int rc = 0; (void)hd; if (yes) { /* first make sure the lock handles are created */ for (kr=kr_resources; kr; kr = kr->next) { if (!keyring_is_writable(kr)) continue; if (!kr->lockhd) { kr->lockhd = dotlock_create (kr->fname, 0); if (!kr->lockhd) { log_info ("can't allocate lock for '%s'\n", kr->fname ); rc = GPG_ERR_GENERAL; } } } if (rc) return rc; /* and now set the locks */ for (kr=kr_resources; kr; kr = kr->next) { if (!keyring_is_writable(kr)) continue; if (kr->is_locked) continue; #ifdef HAVE_W32_SYSTEM /* Under Windows we need to CloseHandle the file before we * try to lock it. This is because another process might * have taken the lock and is using keybox_file_rename to * rename the base file. How if our dotlock_take below is * waiting for the lock but we have the base file still * open, keybox_file_rename will never succeed as we are * in a deadlock. */ iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)kr->fname); #endif /*HAVE_W32_SYSTEM*/ if (dotlock_take (kr->lockhd, -1) ) { log_info ("can't lock '%s'\n", kr->fname ); rc = GPG_ERR_GENERAL; } else kr->is_locked = 1; } } if (rc || !yes) { for (kr=kr_resources; kr; kr = kr->next) { if (!keyring_is_writable(kr)) continue; if (!kr->is_locked) continue; if (dotlock_release (kr->lockhd)) log_info ("can't unlock '%s'\n", kr->fname ); else kr->is_locked = 0; } } return rc; } /* * Return the last found keyblock. Caller must free it. * The returned keyblock has the kbode 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. */ int keyring_get_keyblock (KEYRING_HANDLE hd, KBNODE *ret_kb) { PACKET *pkt; struct parse_packet_ctx_s parsectx; int rc; KBNODE keyblock = NULL, node, lastnode; IOBUF a; int in_cert = 0; int pk_no = 0; int uid_no = 0; int save_mode; if (ret_kb) *ret_kb = NULL; if (!hd->found.kr) return -1; /* no successful search */ a = iobuf_open (hd->found.kr->fname); if (!a) { log_error(_("can't open '%s'\n"), hd->found.kr->fname); return GPG_ERR_KEYRING_OPEN; } if (iobuf_seek (a, hd->found.offset) ) { log_error ("can't seek '%s'\n", hd->found.kr->fname); iobuf_close(a); return GPG_ERR_KEYRING_OPEN; } pkt = xmalloc (sizeof *pkt); init_packet (pkt); init_parse_packet (&parsectx, a); hd->found.n_packets = 0; lastnode = NULL; save_mode = set_packet_list_mode(0); while ((rc=parse_packet (&parsectx, pkt)) != -1) { hd->found.n_packets = parsectx.n_parsed_packets; if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET) { free_packet (pkt, &parsectx); init_packet (pkt); continue; } if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY) { if (in_cert) /* It is not this key that is problematic, but the following key. */ { rc = 0; hd->found.n_packets --; } else /* Upper layer needs to handle this. */ { } break; } if (rc) { log_error ("keyring_get_keyblock: read error: %s\n", gpg_strerror (rc) ); rc = 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: break; /* Allowed per RFC. */ case PKT_RING_TRUST: case PKT_OLD_COMMENT: case PKT_COMMENT: case PKT_GPG_CONTROL: break; /* Allowed by us. */ default: log_info ("skipped packet of type %d in keyring\n", (int)pkt->pkttype); free_packet(pkt, &parsectx); init_packet(pkt); continue; } if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY || pkt->pkttype == PKT_SECRET_KEY)) { hd->found.n_packets--; /* fix counter */ break; /* ready */ } in_cert = 1; node = new_kbnode (pkt); if (!keyblock) keyblock = lastnode = node; else { lastnode->next = node; lastnode = node; } switch (pkt->pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: case PKT_SECRET_KEY: case PKT_SECRET_SUBKEY: if (++pk_no == hd->found.pk_no) node->flag |= 1; break; case PKT_USER_ID: if (++uid_no == hd->found.uid_no) node->flag |= 2; break; default: break; } pkt = xmalloc (sizeof *pkt); init_packet(pkt); } set_packet_list_mode(save_mode); if (rc == -1 && keyblock) rc = 0; /* got the entire keyblock */ if (rc || !ret_kb) release_kbnode (keyblock); else { *ret_kb = keyblock; } free_packet (pkt, &parsectx); deinit_parse_packet (&parsectx); xfree (pkt); iobuf_close(a); /* Make sure that future search operations fail immediately when * we know that we are working on a invalid keyring */ if (gpg_err_code (rc) == GPG_ERR_INV_KEYRING) hd->current.error = rc; return rc; } int keyring_update_keyblock (KEYRING_HANDLE hd, KBNODE kb) { int rc; if (!hd->found.kr) return -1; /* no successful prior search */ if (hd->found.kr->read_only) return gpg_error (GPG_ERR_EACCES); if (!hd->found.n_packets) { /* need to know the number of packets - do a dummy get_keyblock*/ rc = keyring_get_keyblock (hd, NULL); if (rc) { log_error ("re-reading keyblock failed: %s\n", gpg_strerror (rc)); return rc; } if (!hd->found.n_packets) BUG (); } /* The open iobuf isn't needed anymore and in fact is a problem when it comes to renaming the keyring files on some operating systems, so close it here */ iobuf_close(hd->current.iobuf); hd->current.iobuf = NULL; /* do the update */ rc = do_copy (3, hd->found.kr->fname, kb, hd->found.offset, hd->found.n_packets ); if (!rc) { if (key_present_hash) { key_present_hash_update_from_kb (key_present_hash, kb); } /* better reset the found info */ hd->found.kr = NULL; hd->found.offset = 0; } return rc; } int keyring_insert_keyblock (KEYRING_HANDLE hd, KBNODE kb) { int rc; const char *fname; if (!hd) fname = NULL; else if (hd->found.kr) { fname = hd->found.kr->fname; if (hd->found.kr->read_only) return gpg_error (GPG_ERR_EACCES); } else if (hd->current.kr) { fname = hd->current.kr->fname; if (hd->current.kr->read_only) return gpg_error (GPG_ERR_EACCES); } else fname = hd->resource? hd->resource->fname:NULL; if (!fname) return GPG_ERR_GENERAL; /* Close this one otherwise we will lose the position for * a next search. Fixme: it would be better to adjust the position * after the write opertions. */ iobuf_close (hd->current.iobuf); hd->current.iobuf = NULL; /* do the insert */ rc = do_copy (1, fname, kb, 0, 0 ); if (!rc && key_present_hash) { key_present_hash_update_from_kb (key_present_hash, kb); } return rc; } int keyring_delete_keyblock (KEYRING_HANDLE hd) { int rc; if (!hd->found.kr) return -1; /* no successful prior search */ if (hd->found.kr->read_only) return gpg_error (GPG_ERR_EACCES); if (!hd->found.n_packets) { /* need to know the number of packets - do a dummy get_keyblock*/ rc = keyring_get_keyblock (hd, NULL); if (rc) { log_error ("re-reading keyblock failed: %s\n", gpg_strerror (rc)); return rc; } if (!hd->found.n_packets) BUG (); } /* close this one otherwise we will lose the position for * a next search. Fixme: it would be better to adjust the position * after the write opertions. */ iobuf_close (hd->current.iobuf); hd->current.iobuf = NULL; /* do the delete */ rc = do_copy (2, hd->found.kr->fname, NULL, hd->found.offset, hd->found.n_packets ); if (!rc) { /* better reset the found info */ hd->found.kr = NULL; hd->found.offset = 0; /* Delete is a rare operations, so we don't remove the keys * from the offset table */ } return rc; } /* * Start the next search on this handle right at the beginning */ int keyring_search_reset (KEYRING_HANDLE hd) { log_assert (hd); iobuf_close (hd->current.iobuf); hd->current.iobuf = NULL; hd->current.eof = 0; hd->current.error = 0; hd->found.kr = NULL; hd->found.offset = 0; if (hd->current.kr) iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)hd->current.kr->fname); hd->current.kr = NULL; return 0; } static int prepare_search (KEYRING_HANDLE hd) { if (hd->current.error) { /* If the last key was a legacy key, we simply ignore the error so that we can easily use search_next. */ if (gpg_err_code (hd->current.error) == GPG_ERR_LEGACY_KEY) { if (DBG_LOOKUP) log_debug ("%s: last error was GPG_ERR_LEGACY_KEY, clearing\n", __func__); hd->current.error = 0; } else { if (DBG_LOOKUP) log_debug ("%s: returning last error: %s\n", __func__, gpg_strerror (hd->current.error)); return hd->current.error; /* still in error state */ } } if (hd->current.kr && !hd->current.eof) { if ( !hd->current.iobuf ) { if (DBG_LOOKUP) log_debug ("%s: missing iobuf!\n", __func__); return GPG_ERR_GENERAL; /* Position invalid after a modify. */ } return 0; /* okay */ } if (!hd->current.kr && hd->current.eof) { if (DBG_LOOKUP) log_debug ("%s: EOF!\n", __func__); return -1; /* still EOF */ } if (!hd->current.kr) { /* start search with first keyring */ hd->current.kr = hd->resource; if (!hd->current.kr) { if (DBG_LOOKUP) log_debug ("%s: keyring not available!\n", __func__); hd->current.eof = 1; return -1; /* keyring not available */ } log_assert (!hd->current.iobuf); } else { /* EOF */ if (DBG_LOOKUP) log_debug ("%s: EOF\n", __func__); iobuf_close (hd->current.iobuf); hd->current.iobuf = NULL; hd->current.kr = NULL; hd->current.eof = 1; return -1; } hd->current.eof = 0; hd->current.iobuf = iobuf_open (hd->current.kr->fname); if (!hd->current.iobuf) { hd->current.error = gpg_error_from_syserror (); log_error(_("can't open '%s'\n"), hd->current.kr->fname ); return hd->current.error; } return 0; } /* A map of the all characters valid used for word_match() * Valid characters are in this table converted to uppercase. * because the upper 128 bytes have special meaning, we assume * that they are all valid. * Note: We must use numerical values here in case that this program * will be converted to those little blue HAL9000s with their strange * EBCDIC character set (user ids are UTF-8). * wk 2000-04-13: Hmmm, does this really make sense, given the fact that * we can run gpg now on a S/390 running GNU/Linux, where the code * translation is done by the device drivers? */ static const byte word_match_chars[256] = { /* 00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* 38 */ 0x38, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 40 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, /* 50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, /* 58 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 68 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, /* 70 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, /* 78 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, /* 88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, /* 90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, /* 98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, /* a0 */ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, /* a8 */ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, /* b0 */ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, /* b8 */ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, /* c0 */ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, /* c8 */ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, /* d0 */ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, /* d8 */ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, /* e0 */ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, /* e8 */ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, /* f0 */ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, /* f8 */ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; /**************** * Do a word match (original user id starts with a '+'). * The pattern is already tokenized to a more suitable format: * There are only the real words in it delimited by one space * and all converted to uppercase. * * Returns: 0 if all words match. * * Note: This algorithm is a straightforward one and not very * fast. It works for UTF-8 strings. The uidlen should * be removed but due to the fact that old versions of * pgp don't use UTF-8 we still use the length; this should * be fixed in parse-packet (and replace \0 by some special * UTF-8 encoding) */ static int word_match( const byte *uid, size_t uidlen, const byte *pattern ) { size_t wlen, n; const byte *p; const byte *s; for( s=pattern; *s; ) { do { /* skip leading delimiters */ while( uidlen && !word_match_chars[*uid] ) uid++, uidlen--; /* get length of the word */ n = uidlen; p = uid; while( n && word_match_chars[*p] ) p++, n--; wlen = p - uid; /* and compare against the current word from pattern */ for(n=0, p=uid; n < wlen && s[n] != ' ' && s[n] ; n++, p++ ) { if( word_match_chars[*p] != s[n] ) break; } if( n == wlen && (s[n] == ' ' || !s[n]) ) break; /* found */ uid += wlen; uidlen -= wlen; } while( uidlen ); if( !uidlen ) return -1; /* not found */ /* advance to next word in pattern */ for(; *s != ' ' && *s ; s++ ) ; if( *s ) s++ ; } return 0; /* found */ } /**************** * prepare word word_match; that is parse the name and * build the pattern. * caller has to free the returned pattern */ static char* prepare_word_match (const byte *name) { byte *pattern, *p; int c; /* the original length is always enough for the pattern */ p = pattern = xmalloc(strlen(name)+1); do { /* skip leading delimiters */ while( *name && !word_match_chars[*name] ) name++; /* copy as long as we don't have a delimiter and convert * to uppercase. * fixme: how can we handle utf8 uppercasing */ for( ; *name && (c=word_match_chars[*name]); name++ ) *p++ = c; *p++ = ' '; /* append pattern delimiter */ } while( *name ); p[-1] = 0; /* replace last pattern delimiter by EOS */ return pattern; } static int compare_name (int mode, const char *name, const char *uid, size_t uidlen) { int i; const char *s, *se; if (mode == KEYDB_SEARCH_MODE_EXACT) { for (i=0; name[i] && uidlen; i++, uidlen--) if (uid[i] != name[i]) break; if (!uidlen && !name[i]) return 0; /* found */ } else if (mode == KEYDB_SEARCH_MODE_SUBSTR) { if (ascii_memistr( uid, uidlen, name )) return 0; } else if ( mode == KEYDB_SEARCH_MODE_MAIL || mode == KEYDB_SEARCH_MODE_MAILSUB || mode == KEYDB_SEARCH_MODE_MAILEND) { int have_angles = 1; for (i=0, s= uid; i < uidlen && *s != '<'; s++, i++) ; if (i == uidlen) { /* The UID is a plain addr-spec (cf. RFC2822 section 4.3). */ have_angles = 0; s = uid; i = 0; } if (i < uidlen) { if (have_angles) { /* skip opening delim and one char and look for the closing one*/ s++; i++; for (se=s+1, i++; i < uidlen && *se != '>'; se++, i++) ; } else se = s + uidlen; if (i < uidlen) { i = se - s; if (mode == KEYDB_SEARCH_MODE_MAIL) { if( strlen(name)-2 == i && !ascii_memcasecmp( s, name+1, i) ) return 0; } else if (mode == KEYDB_SEARCH_MODE_MAILSUB) { if( ascii_memistr( s, i, name ) ) return 0; } else { /* email from end */ /* nyi */ } } } } else if (mode == KEYDB_SEARCH_MODE_WORDS) return word_match (uid, uidlen, name); else BUG(); return -1; /* not found */ } /* * Search through the keyring(s), starting at the current position, * for a keyblock which contains one of the keys described in the DESC array. */ int keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc, size_t *descindex, int ignore_legacy) { int rc; PACKET pkt; struct parse_packet_ctx_s parsectx; int save_mode; off_t offset, main_offset; size_t n; int need_uid, need_words, need_keyid, need_fpr, any_skip; int pk_no, uid_no; int initial_skip; int scanned_from_start; int use_key_present_hash; PKT_user_id *uid = NULL; PKT_public_key *pk = NULL; u32 aki[2]; /* figure out what information we need */ need_uid = need_words = need_keyid = need_fpr = any_skip = 0; for (n=0; n < ndesc; n++) { switch (desc[n].mode) { case KEYDB_SEARCH_MODE_EXACT: case KEYDB_SEARCH_MODE_SUBSTR: case KEYDB_SEARCH_MODE_MAIL: case KEYDB_SEARCH_MODE_MAILSUB: case KEYDB_SEARCH_MODE_MAILEND: need_uid = 1; break; case KEYDB_SEARCH_MODE_WORDS: need_uid = 1; need_words = 1; break; case KEYDB_SEARCH_MODE_SHORT_KID: case KEYDB_SEARCH_MODE_LONG_KID: need_keyid = 1; break; case KEYDB_SEARCH_MODE_FPR16: case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: need_fpr = 1; break; case KEYDB_SEARCH_MODE_FIRST: /* always restart the search in this mode */ keyring_search_reset (hd); break; default: break; } if (desc[n].skipfnc) { any_skip = 1; need_keyid = 1; } } if (DBG_LOOKUP) log_debug ("%s: need_uid = %d; need_words = %d; need_keyid = %d; need_fpr = %d; any_skip = %d\n", __func__, need_uid, need_words, need_keyid, need_fpr, any_skip); rc = prepare_search (hd); if (rc) { if (DBG_LOOKUP) log_debug ("%s: prepare_search failed: %s (%d)\n", __func__, gpg_strerror (rc), gpg_err_code (rc)); return rc; } use_key_present_hash = !!key_present_hash; if (!use_key_present_hash) { if (DBG_LOOKUP) log_debug ("%s: no offset table.\n", __func__); } else if (!key_present_hash_ready) { if (DBG_LOOKUP) log_debug ("%s: initializing offset table. (need_keyid: %d => 1)\n", __func__, need_keyid); need_keyid = 1; } else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID) { struct key_present *oi; if (DBG_LOOKUP) log_debug ("%s: look up by long key id, checking cache\n", __func__); oi = key_present_hash_lookup (key_present_hash, desc[0].u.kid); if (!oi) { /* We know that we don't have this key */ if (DBG_LOOKUP) log_debug ("%s: cache says not present\n", __func__); hd->found.kr = NULL; hd->current.eof = 1; return -1; } /* We could now create a positive search status and return. * However the problem is that another instance of gpg may * have changed the keyring so that the offsets are not valid * anymore - therefore we don't do it */ } if (need_words) { const char *name = NULL; log_debug ("word search mode does not yet work\n"); /* FIXME: here is a long standing bug in our function and in addition we just use the first search description */ for (n=0; n < ndesc && !name; n++) { if (desc[n].mode == KEYDB_SEARCH_MODE_WORDS) name = desc[n].u.name; } log_assert (name); if ( !hd->word_match.name || strcmp (hd->word_match.name, name) ) { /* name changed */ xfree (hd->word_match.name); xfree (hd->word_match.pattern); hd->word_match.name = xstrdup (name); hd->word_match.pattern = prepare_word_match (name); } /* name = hd->word_match.pattern; */ } init_packet(&pkt); save_mode = set_packet_list_mode(0); hd->found.kr = NULL; main_offset = 0; pk_no = uid_no = 0; initial_skip = 1; /* skip until we see the start of a keyblock */ scanned_from_start = iobuf_tell (hd->current.iobuf) == 0; if (DBG_LOOKUP) log_debug ("%s: %ssearching from start of resource.\n", __func__, scanned_from_start ? "" : "not "); init_parse_packet (&parsectx, hd->current.iobuf); while (1) { byte afp[MAX_FINGERPRINT_LEN]; size_t an; rc = search_packet (&parsectx, &pkt, &offset, need_uid); if (ignore_legacy && gpg_err_code (rc) == GPG_ERR_LEGACY_KEY) { free_packet (&pkt, &parsectx); continue; } if (rc) break; if (pkt.pkttype == PKT_PUBLIC_KEY || pkt.pkttype == PKT_SECRET_KEY) { main_offset = offset; pk_no = uid_no = 0; initial_skip = 0; } if (initial_skip) { free_packet (&pkt, &parsectx); continue; } pk = NULL; uid = NULL; if ( pkt.pkttype == PKT_PUBLIC_KEY || pkt.pkttype == PKT_PUBLIC_SUBKEY || pkt.pkttype == PKT_SECRET_KEY || pkt.pkttype == PKT_SECRET_SUBKEY) { pk = pkt.pkt.public_key; ++pk_no; if (need_fpr) { fingerprint_from_pk (pk, afp, &an); while (an < 20) /* fill up to 20 bytes */ afp[an++] = 0; } if (need_keyid) keyid_from_pk (pk, aki); if (use_key_present_hash && !key_present_hash_ready && scanned_from_start) key_present_hash_update (key_present_hash, aki); } else if (pkt.pkttype == PKT_USER_ID) { uid = pkt.pkt.user_id; ++uid_no; } for (n=0; n < ndesc; n++) { switch (desc[n].mode) { case KEYDB_SEARCH_MODE_NONE: BUG (); break; case KEYDB_SEARCH_MODE_EXACT: case KEYDB_SEARCH_MODE_SUBSTR: case KEYDB_SEARCH_MODE_MAIL: case KEYDB_SEARCH_MODE_MAILSUB: case KEYDB_SEARCH_MODE_MAILEND: case KEYDB_SEARCH_MODE_WORDS: if ( uid && !compare_name (desc[n].mode, desc[n].u.name, uid->name, uid->len)) goto found; break; case KEYDB_SEARCH_MODE_SHORT_KID: if (pk && desc[n].u.kid[1] == aki[1]) goto found; break; case KEYDB_SEARCH_MODE_LONG_KID: if (pk && desc[n].u.kid[0] == aki[0] && desc[n].u.kid[1] == aki[1]) goto found; break; case KEYDB_SEARCH_MODE_FPR16: if (pk && !memcmp (desc[n].u.fpr, afp, 16)) goto found; break; case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: if (pk && !memcmp (desc[n].u.fpr, afp, 20)) goto found; break; case KEYDB_SEARCH_MODE_FIRST: if (pk) goto found; break; case KEYDB_SEARCH_MODE_NEXT: if (pk) goto found; break; default: rc = GPG_ERR_INV_ARG; goto found; } } free_packet (&pkt, &parsectx); continue; found: if (rc) goto real_found; if (DBG_LOOKUP) log_debug ("%s: packet starting at offset %lld matched descriptor %zu\n" , __func__, (long long)offset, n); /* Record which desc we matched on. Note this value is only meaningful if this function returns with no errors. */ if(descindex) *descindex=n; for (n=any_skip?0:ndesc; n < ndesc; n++) { if (desc[n].skipfnc && desc[n].skipfnc (desc[n].skipfncvalue, aki, uid_no)) { if (DBG_LOOKUP) log_debug ("%s: skipping match: desc %zd's skip function returned TRUE\n", __func__, n); break; } } if (n == ndesc) goto real_found; free_packet (&pkt, &parsectx); } real_found: if (!rc) { if (DBG_LOOKUP) log_debug ("%s: returning success\n", __func__); hd->found.offset = main_offset; hd->found.kr = hd->current.kr; hd->found.pk_no = pk? pk_no : 0; hd->found.uid_no = uid? uid_no : 0; } else if (rc == -1) { if (DBG_LOOKUP) log_debug ("%s: no matches (EOF)\n", __func__); hd->current.eof = 1; /* if we scanned all keyrings, we are sure that * all known key IDs are in our offtbl, mark that. */ if (use_key_present_hash && !key_present_hash_ready && scanned_from_start) { KR_RESOURCE kr; /* First set the did_full_scan flag for this keyring. */ for (kr=kr_resources; kr; kr = kr->next) { if (hd->resource == kr) { kr->did_full_scan = 1; break; } } /* Then check whether all flags are set and if so, mark the offtbl ready */ for (kr=kr_resources; kr; kr = kr->next) { if (!kr->did_full_scan) break; } if (!kr) key_present_hash_ready = 1; } } else { if (DBG_LOOKUP) log_debug ("%s: error encountered during search: %s (%d)\n", __func__, gpg_strerror (rc), rc); hd->current.error = rc; } free_packet (&pkt, &parsectx); deinit_parse_packet (&parsectx); set_packet_list_mode(save_mode); return rc; } static int create_tmp_file (const char *template, char **r_bakfname, char **r_tmpfname, IOBUF *r_fp) { gpg_error_t err; mode_t oldmask; err = keybox_tmp_names (template, 1, r_bakfname, r_tmpfname); if (err) return err; /* Create the temp file with limited access. Note that the umask call is not anymore needed because iobuf_create now takes care of it. However, it does not harm and thus we keep it. */ oldmask = umask (077); if (is_secured_filename (*r_tmpfname)) { *r_fp = NULL; gpg_err_set_errno (EPERM); } else *r_fp = iobuf_create (*r_tmpfname, 1); umask (oldmask); if (!*r_fp) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), *r_tmpfname, gpg_strerror (err)); xfree (*r_tmpfname); *r_tmpfname = NULL; xfree (*r_bakfname); *r_bakfname = NULL; } return err; } static int rename_tmp_file (const char *bakfname, const char *tmpfname, const char *fname) { int rc = 0; int block = 0; /* Invalidate close caches. */ if (iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)tmpfname )) { rc = gpg_error_from_syserror (); goto fail; } iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)bakfname ); iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname ); /* First make a backup file. */ block = 1; rc = gnupg_rename_file (fname, bakfname, &block); if (rc) goto fail; /* then rename the file */ rc = gnupg_rename_file (tmpfname, fname, NULL); if (block) { gnupg_unblock_all_signals (); block = 0; } if (rc) { register_secured_file (fname); goto fail; } /* Now make sure the file has the same permissions as the original */ #ifndef HAVE_DOSISH_SYSTEM { struct stat statbuf; statbuf.st_mode=S_IRUSR | S_IWUSR; - if (!stat (bakfname, &statbuf) && !chmod (fname, statbuf.st_mode)) + if (!gnupg_stat (bakfname, &statbuf) && !chmod (fname, statbuf.st_mode)) ; else log_error ("WARNING: unable to restore permissions to '%s': %s", fname, strerror(errno)); } #endif return 0; fail: if (block) gnupg_unblock_all_signals (); return rc; } static int write_keyblock (IOBUF fp, KBNODE keyblock) { KBNODE kbctx = NULL, node; int rc; while ( (node = walk_kbnode (keyblock, &kbctx, 0)) ) { if ( (rc = build_packet_and_meta (fp, node->pkt) )) { log_error ("build_packet(%d) failed: %s\n", node->pkt->pkttype, gpg_strerror (rc) ); return rc; } } return 0; } /* * Walk over all public keyrings, check the signatures and replace the * keyring with a new one where the signature cache is then updated. * This is only done for the public keyrings. */ int keyring_rebuild_cache (ctrl_t ctrl, void *token, int noisy) { KEYRING_HANDLE hd; KEYDB_SEARCH_DESC desc; KBNODE keyblock = NULL, node; const char *lastresname = NULL, *resname; IOBUF tmpfp = NULL; char *tmpfilename = NULL; char *bakfilename = NULL; int rc; ulong count = 0, sigcount = 0; hd = keyring_new (token); if (!hd) return gpg_error_from_syserror (); memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FIRST; rc=keyring_lock (hd, 1); if(rc) goto leave; for (;;) { rc = keyring_search (hd, &desc, 1, NULL, 1 /* ignore_legacy */); if (rc) break; /* ready. */ desc.mode = KEYDB_SEARCH_MODE_NEXT; resname = keyring_get_resource_name (hd); if (lastresname != resname ) { /* we have switched to a new keyring - commit changes */ if (tmpfp) { if (iobuf_close (tmpfp)) { rc = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", tmpfilename, strerror (errno)); goto leave; } /* because we have switched resources, we can be sure that * the original file is closed */ tmpfp = NULL; } /* Static analyzer note: BAKFILENAME is never NULL here because it is controlled by LASTRESNAME. */ rc = lastresname? rename_tmp_file (bakfilename, tmpfilename, lastresname) : 0; xfree (tmpfilename); tmpfilename = NULL; xfree (bakfilename); bakfilename = NULL; if (rc) goto leave; lastresname = resname; if (noisy && !opt.quiet) log_info (_("caching keyring '%s'\n"), resname); rc = create_tmp_file (resname, &bakfilename, &tmpfilename, &tmpfp); if (rc) goto leave; } release_kbnode (keyblock); rc = keyring_get_keyblock (hd, &keyblock); if (rc) { if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY) continue; /* Skip legacy keys. */ if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_VERSION) continue; /* Skip keys with unknown version. */ log_error ("keyring_get_keyblock failed: %s\n", gpg_strerror (rc)); goto leave; } if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY) { /* We had a few reports about corrupted keyrings; if we have been called directly from the command line we delete such a keyblock instead of bailing out. */ log_error ("unexpected keyblock found (pkttype=%d)%s\n", keyblock->pkt->pkttype, noisy? " - deleted":""); if (noisy) continue; log_info ("Hint: backup your keys and try running '%s'\n", "gpg --rebuild-keydb-caches"); rc = gpg_error (GPG_ERR_INV_KEYRING); goto leave; } if (keyblock->pkt->pkt.public_key->version < 4) { /* We do not copy/cache v3 keys or any other unknown packets. It is better to remove them from the keyring. The code required to keep them in the keyring would be too complicated. Given that we do not touch the old secring.gpg a suitable backup for decryption of v3 stuff using an older gpg version will always be available. Note: This test is actually superfluous because we already acted upon GPG_ERR_LEGACY_KEY. */ } else { /* Check all signature to set the signature's cache flags. */ for (node=keyblock; node; node=node->next) { /* Note that this doesn't cache the result of a revocation issued by a designated revoker. This is because the pk in question does not carry the revkeys as we haven't merged the key and selfsigs. It is questionable whether this matters very much since there are very very few designated revoker revocation packets out there. */ if (node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig=node->pkt->pkt.signature; if(!opt.no_sig_cache && sig->flags.checked && sig->flags.valid && (openpgp_md_test_algo(sig->digest_algo) || openpgp_pk_test_algo(sig->pubkey_algo))) sig->flags.checked=sig->flags.valid=0; else check_key_signature (ctrl, keyblock, node, NULL); sigcount++; } } /* Write the keyblock to the temporary file. */ rc = write_keyblock (tmpfp, keyblock); if (rc) goto leave; if ( !(++count % 50) && noisy && !opt.quiet) log_info (ngettext("%lu keys cached so far (%lu signature)\n", "%lu keys cached so far (%lu signatures)\n", sigcount), count, sigcount); } } /* end main loop */ if (rc == -1) rc = 0; if (rc) { log_error ("keyring_search failed: %s\n", gpg_strerror (rc)); goto leave; } if (noisy || opt.verbose) { log_info (ngettext("%lu key cached", "%lu keys cached", count), count); log_printf (ngettext(" (%lu signature)\n", " (%lu signatures)\n", sigcount), sigcount); } if (tmpfp) { if (iobuf_close (tmpfp)) { rc = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", tmpfilename, strerror (errno)); goto leave; } /* because we have switched resources, we can be sure that * the original file is closed */ tmpfp = NULL; } rc = lastresname? rename_tmp_file (bakfilename, tmpfilename, lastresname) : 0; xfree (tmpfilename); tmpfilename = NULL; xfree (bakfilename); bakfilename = NULL; leave: if (tmpfp) iobuf_cancel (tmpfp); xfree (tmpfilename); xfree (bakfilename); release_kbnode (keyblock); keyring_lock (hd, 0); keyring_release (hd); return rc; } /**************** * Perform insert/delete/update operation. * mode 1 = insert * 2 = delete * 3 = update */ static int do_copy (int mode, const char *fname, KBNODE root, off_t start_offset, unsigned int n_packets ) { gpg_err_code_t ec; IOBUF fp, newfp; int rc=0; char *bakfname = NULL; char *tmpfname = NULL; /* Open the source file. Because we do a rename, we have to check the permissions of the file */ if ((ec = gnupg_access (fname, W_OK))) return gpg_error (ec); fp = iobuf_open (fname); if (mode == 1 && !fp && errno == ENOENT) { /* insert mode but file does not exist: create a new file */ KBNODE kbctx, node; mode_t oldmask; oldmask=umask(077); if (is_secured_filename (fname)) { newfp = NULL; gpg_err_set_errno (EPERM); } else newfp = iobuf_create (fname, 1); umask(oldmask); if( !newfp ) { rc = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), fname, strerror(errno)); return rc; } if( !opt.quiet ) log_info(_("%s: keyring created\n"), fname ); kbctx=NULL; while ( (node = walk_kbnode( root, &kbctx, 0 )) ) { if( (rc = build_packet( newfp, node->pkt )) ) { log_error("build_packet(%d) failed: %s\n", node->pkt->pkttype, gpg_strerror (rc) ); iobuf_cancel(newfp); return rc; } } if( iobuf_close(newfp) ) { rc = gpg_error_from_syserror (); log_error ("%s: close failed: %s\n", fname, strerror(errno)); return rc; } return 0; /* ready */ } if( !fp ) { rc = gpg_error_from_syserror (); log_error(_("can't open '%s': %s\n"), fname, strerror(errno) ); goto leave; } /* Create the new file. */ rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp); if (rc) { iobuf_close(fp); goto leave; } if( mode == 1 ) { /* insert */ /* copy everything to the new file */ rc = copy_all_packets (fp, newfp); if( rc != -1 ) { log_error("%s: copy to '%s' failed: %s\n", fname, tmpfname, gpg_strerror (rc) ); iobuf_close(fp); iobuf_cancel(newfp); goto leave; } } if( mode == 2 || mode == 3 ) { /* delete or update */ /* copy first part to the new file */ rc = copy_some_packets( fp, newfp, start_offset ); if( rc ) { /* should never get EOF here */ log_error ("%s: copy to '%s' failed: %s\n", fname, tmpfname, gpg_strerror (rc) ); iobuf_close(fp); iobuf_cancel(newfp); goto leave; } /* skip this keyblock */ log_assert( n_packets ); rc = skip_some_packets( fp, n_packets ); if( rc ) { log_error("%s: skipping %u packets failed: %s\n", fname, n_packets, gpg_strerror (rc)); iobuf_close(fp); iobuf_cancel(newfp); goto leave; } } if( mode == 1 || mode == 3 ) { /* insert or update */ rc = write_keyblock (newfp, root); if (rc) { iobuf_close(fp); iobuf_cancel(newfp); goto leave; } } if( mode == 2 || mode == 3 ) { /* delete or update */ /* copy the rest */ rc = copy_all_packets( fp, newfp ); if( rc != -1 ) { log_error("%s: copy to '%s' failed: %s\n", fname, tmpfname, gpg_strerror (rc) ); iobuf_close(fp); iobuf_cancel(newfp); goto leave; } } /* close both files */ if( iobuf_close(fp) ) { rc = gpg_error_from_syserror (); log_error("%s: close failed: %s\n", fname, strerror(errno) ); goto leave; } if( iobuf_close(newfp) ) { rc = gpg_error_from_syserror (); log_error("%s: close failed: %s\n", tmpfname, strerror(errno) ); goto leave; } rc = rename_tmp_file (bakfname, tmpfname, fname); leave: xfree(bakfname); xfree(tmpfname); return rc; } diff --git a/g10/misc.c b/g10/misc.c index 22ed47e7c..c344ff244 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -1,1862 +1,1862 @@ /* misc.c - miscellaneous functions * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2014 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 #include #include #endif #ifdef HAVE_SETRLIMIT #include #include #include #endif #ifdef ENABLE_SELINUX_HACKS #include #endif #ifdef HAVE_W32_SYSTEM #include #include #ifdef HAVE_WINSOCK2_H # define WIN32_LEAN_AND_MEAN 1 # include #endif #include #include #ifndef CSIDL_APPDATA #define CSIDL_APPDATA 0x001a #endif #ifndef CSIDL_LOCAL_APPDATA #define CSIDL_LOCAL_APPDATA 0x001c #endif #ifndef CSIDL_FLAG_CREATE #define CSIDL_FLAG_CREATE 0x8000 #endif #endif /*HAVE_W32_SYSTEM*/ #include "gpg.h" #ifdef HAVE_W32_SYSTEM # include "../common/status.h" #endif /*HAVE_W32_SYSTEM*/ #include "../common/util.h" #include "main.h" #include "photoid.h" #include "options.h" #include "call-agent.h" #include "../common/i18n.h" #include "../common/zb32.h" /* FIXME: Libgcrypt 1.9 will support EAX. Until we name this a * requirement we hardwire the enum used for EAX. */ #define MY_GCRY_CIPHER_MODE_EAX 14 #ifdef ENABLE_SELINUX_HACKS /* A object and a global variable to keep track of files marked as secured. */ struct secured_file_item { struct secured_file_item *next; ino_t ino; dev_t dev; }; static struct secured_file_item *secured_files; #endif /*ENABLE_SELINUX_HACKS*/ /* For the sake of SELinux we want to restrict access through gpg to certain files we keep under our own control. This function registers such a file and is_secured_file may then be used to check whether a file has ben registered as secured. */ void register_secured_file (const char *fname) { #ifdef ENABLE_SELINUX_HACKS struct stat buf; struct secured_file_item *sf; /* Note that we stop immediately if something goes wrong here. */ - if (stat (fname, &buf)) + if (gnupg_stat (fname, &buf)) log_fatal (_("fstat of '%s' failed in %s: %s\n"), fname, "register_secured_file", strerror (errno)); /* log_debug ("registering '%s' i=%lu.%lu\n", fname, */ /* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */ for (sf=secured_files; sf; sf = sf->next) { if (sf->ino == buf.st_ino && sf->dev == buf.st_dev) return; /* Already registered. */ } sf = xmalloc (sizeof *sf); sf->ino = buf.st_ino; sf->dev = buf.st_dev; sf->next = secured_files; secured_files = sf; #else /*!ENABLE_SELINUX_HACKS*/ (void)fname; #endif /*!ENABLE_SELINUX_HACKS*/ } /* Remove a file registered as secure. */ void unregister_secured_file (const char *fname) { #ifdef ENABLE_SELINUX_HACKS struct stat buf; struct secured_file_item *sf, *sfprev; - if (stat (fname, &buf)) + if (gnupg_stat (fname, &buf)) { log_error (_("fstat of '%s' failed in %s: %s\n"), fname, "unregister_secured_file", strerror (errno)); return; } /* log_debug ("unregistering '%s' i=%lu.%lu\n", fname, */ /* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */ for (sfprev=NULL,sf=secured_files; sf; sfprev=sf, sf = sf->next) { if (sf->ino == buf.st_ino && sf->dev == buf.st_dev) { if (sfprev) sfprev->next = sf->next; else secured_files = sf->next; xfree (sf); return; } } #else /*!ENABLE_SELINUX_HACKS*/ (void)fname; #endif /*!ENABLE_SELINUX_HACKS*/ } /* Return true if FD is corresponds to a secured file. Using -1 for FS is allowed and will return false. */ int is_secured_file (int fd) { #ifdef ENABLE_SELINUX_HACKS struct stat buf; struct secured_file_item *sf; if (fd == -1) return 0; /* No file descriptor so it can't be secured either. */ /* Note that we print out a error here and claim that a file is secure if something went wrong. */ if (fstat (fd, &buf)) { log_error (_("fstat(%d) failed in %s: %s\n"), fd, "is_secured_file", strerror (errno)); return 1; } /* log_debug ("is_secured_file (%d) i=%lu.%lu\n", fd, */ /* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */ for (sf=secured_files; sf; sf = sf->next) { if (sf->ino == buf.st_ino && sf->dev == buf.st_dev) return 1; /* Yes. */ } #else /*!ENABLE_SELINUX_HACKS*/ (void)fd; #endif /*!ENABLE_SELINUX_HACKS*/ return 0; /* No. */ } /* Return true if FNAME is corresponds to a secured file. Using NULL, "" or "-" for FS is allowed and will return false. This function is used before creating a file, thus it won't fail if the file does not exist. */ int is_secured_filename (const char *fname) { #ifdef ENABLE_SELINUX_HACKS struct stat buf; struct secured_file_item *sf; if (iobuf_is_pipe_filename (fname) || !*fname) return 0; /* Note that we print out a error here and claim that a file is secure if something went wrong. */ - if (stat (fname, &buf)) + if (gnupg_stat (fname, &buf)) { if (errno == ENOENT || errno == EPERM || errno == EACCES) return 0; log_error (_("fstat of '%s' failed in %s: %s\n"), fname, "is_secured_filename", strerror (errno)); return 1; } /* log_debug ("is_secured_filename (%s) i=%lu.%lu\n", fname, */ /* (unsigned long)buf.st_dev, (unsigned long)buf.st_ino); */ for (sf=secured_files; sf; sf = sf->next) { if (sf->ino == buf.st_ino && sf->dev == buf.st_dev) return 1; /* Yes. */ } #else /*!ENABLE_SELINUX_HACKS*/ (void)fname; #endif /*!ENABLE_SELINUX_HACKS*/ return 0; /* No. */ } u16 checksum_u16( unsigned n ) { u16 a; a = (n >> 8) & 0xff; a += n & 0xff; return a; } u16 checksum( byte *p, unsigned n ) { u16 a; for(a=0; n; n-- ) a += *p++; return a; } u16 checksum_mpi (gcry_mpi_t a) { u16 csum; byte *buffer; size_t nbytes; if ( gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0, &nbytes, a) ) BUG (); /* Fixme: For numbers not in secure memory we should use a stack * based buffer and only allocate a larger one if mpi_print returns * an error. */ buffer = (gcry_is_secure(a)? gcry_xmalloc_secure (nbytes) : gcry_xmalloc (nbytes)); if ( gcry_mpi_print (GCRYMPI_FMT_PGP, buffer, nbytes, NULL, a) ) BUG (); csum = checksum (buffer, nbytes); xfree (buffer); return csum; } void print_pubkey_algo_note (pubkey_algo_t algo) { if(algo >= 100 && algo <= 110) { static int warn=0; if(!warn) { warn=1; es_fflush (es_stdout); log_info (_("WARNING: using experimental public key algorithm %s\n"), openpgp_pk_algo_name (algo)); } } else if (algo == PUBKEY_ALGO_ELGAMAL) { es_fflush (es_stdout); log_info (_("WARNING: Elgamal sign+encrypt keys are deprecated\n")); } } void print_cipher_algo_note (cipher_algo_t algo) { if(algo >= 100 && algo <= 110) { static int warn=0; if(!warn) { warn=1; es_fflush (es_stdout); log_info (_("WARNING: using experimental cipher algorithm %s\n"), openpgp_cipher_algo_name (algo)); } } } void print_digest_algo_note (digest_algo_t algo) { const enum gcry_md_algos galgo = map_md_openpgp_to_gcry (algo); const struct weakhash *weak; if(algo >= 100 && algo <= 110) { static int warn=0; if(!warn) { warn=1; es_fflush (es_stdout); log_info (_("WARNING: using experimental digest algorithm %s\n"), gcry_md_algo_name (galgo)); } } else for (weak = opt.weak_digests; weak != NULL; weak = weak->next) if (weak->algo == galgo) { es_fflush (es_stdout); log_info (_("WARNING: digest algorithm %s is deprecated\n"), gcry_md_algo_name (galgo)); } } void print_digest_rejected_note (enum gcry_md_algos algo) { struct weakhash* weak; int show = 1; for (weak = opt.weak_digests; weak; weak = weak->next) if (weak->algo == algo) { if (weak->rejection_shown) show = 0; else weak->rejection_shown = 1; break; } if (show) { es_fflush (es_stdout); log_info (_("Note: signatures using the %s algorithm are rejected\n"), gcry_md_algo_name(algo)); } } void print_sha1_keysig_rejected_note (void) { static int shown; if (shown) return; shown = 1; es_fflush (es_stdout); log_info (_("Note: third-party key signatures using" " the %s algorithm are rejected\n"), gcry_md_algo_name (GCRY_MD_SHA1)); print_further_info ("use option \"%s\" to override", "--allow-weak-key-signatures"); } /* Print a message * "(reported error: %s)\n * in verbose mode to further explain an error. If the error code has * the value IGNORE_EC no message is printed. A message is also not * printed if ERR is 0. */ void print_reported_error (gpg_error_t err, gpg_err_code_t ignore_ec) { if (!opt.verbose) return; if (!gpg_err_code (err)) ; else if (gpg_err_code (err) == ignore_ec) ; else if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_info (_("(reported error: %s)\n"), gpg_strerror (err)); else log_info (_("(reported error: %s <%s>)\n"), gpg_strerror (err), gpg_strsource (err)); } /* Print a message * "(further info: %s)\n * in verbose mode to further explain an error. That message is * intended to help debug a problem and should not be translated. */ void print_further_info (const char *format, ...) { va_list arg_ptr; if (!opt.verbose) return; log_info (_("(further info: ")); va_start (arg_ptr, format); log_logv (GPGRT_LOG_CONT, format, arg_ptr); va_end (arg_ptr); log_printf (")\n"); } /* Map OpenPGP algo numbers to those used by Libgcrypt. We need to do this for algorithms we implemented in Libgcrypt after they become part of OpenPGP. */ enum gcry_cipher_algos map_cipher_openpgp_to_gcry (cipher_algo_t algo) { switch (algo) { case CIPHER_ALGO_NONE: return GCRY_CIPHER_NONE; #ifdef GPG_USE_IDEA case CIPHER_ALGO_IDEA: return GCRY_CIPHER_IDEA; #else case CIPHER_ALGO_IDEA: return 0; #endif case CIPHER_ALGO_3DES: return GCRY_CIPHER_3DES; #ifdef GPG_USE_CAST5 case CIPHER_ALGO_CAST5: return GCRY_CIPHER_CAST5; #else case CIPHER_ALGO_CAST5: return 0; #endif #ifdef GPG_USE_BLOWFISH case CIPHER_ALGO_BLOWFISH: return GCRY_CIPHER_BLOWFISH; #else case CIPHER_ALGO_BLOWFISH: return 0; #endif #ifdef GPG_USE_AES128 case CIPHER_ALGO_AES: return GCRY_CIPHER_AES; #else case CIPHER_ALGO_AES: return 0; #endif #ifdef GPG_USE_AES192 case CIPHER_ALGO_AES192: return GCRY_CIPHER_AES192; #else case CIPHER_ALGO_AES192: return 0; #endif #ifdef GPG_USE_AES256 case CIPHER_ALGO_AES256: return GCRY_CIPHER_AES256; #else case CIPHER_ALGO_AES256: return 0; #endif #ifdef GPG_USE_TWOFISH case CIPHER_ALGO_TWOFISH: return GCRY_CIPHER_TWOFISH; #else case CIPHER_ALGO_TWOFISH: return 0; #endif #ifdef GPG_USE_CAMELLIA128 case CIPHER_ALGO_CAMELLIA128: return GCRY_CIPHER_CAMELLIA128; #else case CIPHER_ALGO_CAMELLIA128: return 0; #endif #ifdef GPG_USE_CAMELLIA192 case CIPHER_ALGO_CAMELLIA192: return GCRY_CIPHER_CAMELLIA192; #else case CIPHER_ALGO_CAMELLIA192: return 0; #endif #ifdef GPG_USE_CAMELLIA256 case CIPHER_ALGO_CAMELLIA256: return GCRY_CIPHER_CAMELLIA256; #else case CIPHER_ALGO_CAMELLIA256: return 0; #endif default: return 0; } } /* The inverse function of above. */ static cipher_algo_t map_cipher_gcry_to_openpgp (enum gcry_cipher_algos algo) { switch (algo) { case GCRY_CIPHER_NONE: return CIPHER_ALGO_NONE; case GCRY_CIPHER_IDEA: return CIPHER_ALGO_IDEA; case GCRY_CIPHER_3DES: return CIPHER_ALGO_3DES; case GCRY_CIPHER_CAST5: return CIPHER_ALGO_CAST5; case GCRY_CIPHER_BLOWFISH: return CIPHER_ALGO_BLOWFISH; case GCRY_CIPHER_AES: return CIPHER_ALGO_AES; case GCRY_CIPHER_AES192: return CIPHER_ALGO_AES192; case GCRY_CIPHER_AES256: return CIPHER_ALGO_AES256; case GCRY_CIPHER_TWOFISH: return CIPHER_ALGO_TWOFISH; case GCRY_CIPHER_CAMELLIA128: return CIPHER_ALGO_CAMELLIA128; case GCRY_CIPHER_CAMELLIA192: return CIPHER_ALGO_CAMELLIA192; case GCRY_CIPHER_CAMELLIA256: return CIPHER_ALGO_CAMELLIA256; default: return 0; } } /* Map Gcrypt public key algorithm numbers to those used by OpenPGP. FIXME: This mapping is used at only two places - we should get rid of it. */ pubkey_algo_t map_pk_gcry_to_openpgp (enum gcry_pk_algos algo) { switch (algo) { case GCRY_PK_EDDSA: return PUBKEY_ALGO_EDDSA; case GCRY_PK_ECDSA: return PUBKEY_ALGO_ECDSA; case GCRY_PK_ECDH: return PUBKEY_ALGO_ECDH; default: return algo < 110 ? (pubkey_algo_t)algo : 0; } } /* Return the block length of an OpenPGP cipher algorithm. */ int openpgp_cipher_blocklen (cipher_algo_t algo) { /* We use the numbers from OpenPGP to be sure that we get the right block length. This is so that the packet parsing code works even for unknown algorithms (for which we assume 8 due to tradition). NOTE: If you change the returned blocklen above 16, check the callers because they may use a fixed size buffer of that size. */ switch (algo) { case CIPHER_ALGO_AES: case CIPHER_ALGO_AES192: case CIPHER_ALGO_AES256: case CIPHER_ALGO_TWOFISH: case CIPHER_ALGO_CAMELLIA128: case CIPHER_ALGO_CAMELLIA192: case CIPHER_ALGO_CAMELLIA256: return 16; default: return 8; } } /**************** * Wrapper around the libgcrypt function with additional checks on * the OpenPGP contraints for the algo ID. */ int openpgp_cipher_test_algo (cipher_algo_t algo) { enum gcry_cipher_algos ga; ga = map_cipher_openpgp_to_gcry (algo); if (!ga) return gpg_error (GPG_ERR_CIPHER_ALGO); return gcry_cipher_test_algo (ga); } /* Map the OpenPGP cipher algorithm whose ID is contained in ALGORITHM to a string representation of the algorithm name. For unknown algorithm IDs this function returns "?". */ const char * openpgp_cipher_algo_name (cipher_algo_t algo) { switch (algo) { case CIPHER_ALGO_IDEA: return "IDEA"; case CIPHER_ALGO_3DES: return "3DES"; case CIPHER_ALGO_CAST5: return "CAST5"; case CIPHER_ALGO_BLOWFISH: return "BLOWFISH"; case CIPHER_ALGO_AES: return "AES"; case CIPHER_ALGO_AES192: return "AES192"; case CIPHER_ALGO_AES256: return "AES256"; case CIPHER_ALGO_TWOFISH: return "TWOFISH"; case CIPHER_ALGO_CAMELLIA128: return "CAMELLIA128"; case CIPHER_ALGO_CAMELLIA192: return "CAMELLIA192"; case CIPHER_ALGO_CAMELLIA256: return "CAMELLIA256"; case CIPHER_ALGO_NONE: default: return "?"; } } /* Return 0 if ALGO is supported. Return an error if not. */ gpg_error_t openpgp_aead_test_algo (aead_algo_t algo) { /* FIXME: We currently have no easy way to test whether libgcrypt * implements a mode. The only way we can do this is to open a * cipher context with that mode and close it immediately. That is * a bit costly. So we look at the libgcrypt version and assume * nothing has been patched out. */ switch (algo) { case AEAD_ALGO_NONE: break; case AEAD_ALGO_EAX: #if GCRYPT_VERSION_NUMBER < 0x010900 break; #else return 0; #endif case AEAD_ALGO_OCB: return 0; } return gpg_error (GPG_ERR_INV_CIPHER_MODE); } /* Map the OpenPGP AEAD algorithm with ID ALGO to a string * representation of the algorithm name. For unknown algorithm IDs * this function returns "?". */ const char * openpgp_aead_algo_name (aead_algo_t algo) { switch (algo) { case AEAD_ALGO_NONE: break; case AEAD_ALGO_EAX: return "EAX"; case AEAD_ALGO_OCB: return "OCB"; } return "?"; } /* Return information for the AEAD algorithm ALGO. The corresponding * Libgcrypt ciphermode is stored at R_MODE and the required number of * octets for the nonce at R_NONCELEN. On error and error code is * returned. Note that the taglen is always 128 bits. */ gpg_error_t openpgp_aead_algo_info (aead_algo_t algo, enum gcry_cipher_modes *r_mode, unsigned int *r_noncelen) { switch (algo) { case AEAD_ALGO_OCB: *r_mode = GCRY_CIPHER_MODE_OCB; *r_noncelen = 15; break; case AEAD_ALGO_EAX: *r_mode = MY_GCRY_CIPHER_MODE_EAX; *r_noncelen = 16; break; default: log_error ("unsupported AEAD algo %d\n", algo); return gpg_error (GPG_ERR_INV_CIPHER_MODE); } return 0; } /* Return 0 if ALGO is a supported OpenPGP public key algorithm. */ int openpgp_pk_test_algo (pubkey_algo_t algo) { return openpgp_pk_test_algo2 (algo, 0); } /* Return 0 if ALGO is a supported OpenPGP public key algorithm and allows the usage USE. */ int openpgp_pk_test_algo2 (pubkey_algo_t algo, unsigned int use) { enum gcry_pk_algos ga = 0; size_t use_buf = use; switch (algo) { #ifdef GPG_USE_RSA case PUBKEY_ALGO_RSA: ga = GCRY_PK_RSA; break; case PUBKEY_ALGO_RSA_E: ga = GCRY_PK_RSA_E; break; case PUBKEY_ALGO_RSA_S: ga = GCRY_PK_RSA_S; break; #else case PUBKEY_ALGO_RSA: break; case PUBKEY_ALGO_RSA_E: break; case PUBKEY_ALGO_RSA_S: break; #endif case PUBKEY_ALGO_ELGAMAL_E: ga = GCRY_PK_ELG; break; case PUBKEY_ALGO_DSA: ga = GCRY_PK_DSA; break; #ifdef GPG_USE_ECDH case PUBKEY_ALGO_ECDH: ga = GCRY_PK_ECC; break; #else case PUBKEY_ALGO_ECDH: break; #endif #ifdef GPG_USE_ECDSA case PUBKEY_ALGO_ECDSA: ga = GCRY_PK_ECC; break; #else case PUBKEY_ALGO_ECDSA: break; #endif #ifdef GPG_USE_EDDSA case PUBKEY_ALGO_EDDSA: ga = GCRY_PK_ECC; break; #else case PUBKEY_ALGO_EDDSA: break; #endif case PUBKEY_ALGO_ELGAMAL: /* Dont't allow type 20 keys unless in rfc2440 mode. */ if (RFC2440) ga = GCRY_PK_ELG; break; default: break; } if (!ga) return gpg_error (GPG_ERR_PUBKEY_ALGO); /* Elgamal in OpenPGP used to support signing and Libgcrypt still * does. However, we removed the signing capability from gpg ages * ago. This function should reflect this so that errors are thrown * early and not only when we try to sign using Elgamal. */ if (ga == GCRY_PK_ELG && (use & (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG))) return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); /* Now check whether Libgcrypt has support for the algorithm. */ return gcry_pk_algo_info (ga, GCRYCTL_TEST_ALGO, NULL, &use_buf); } int openpgp_pk_algo_usage ( int algo ) { int use = 0; /* They are hardwired in gpg 1.0. */ switch ( algo ) { case PUBKEY_ALGO_RSA: use = (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_ENC | PUBKEY_USAGE_AUTH); break; case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_ECDH: use = PUBKEY_USAGE_ENC; break; case PUBKEY_ALGO_RSA_S: use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG; break; case PUBKEY_ALGO_ELGAMAL: if (RFC2440) use = PUBKEY_USAGE_ENC; break; case PUBKEY_ALGO_ELGAMAL_E: use = PUBKEY_USAGE_ENC; break; case PUBKEY_ALGO_DSA: use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_AUTH; break; case PUBKEY_ALGO_ECDSA: case PUBKEY_ALGO_EDDSA: use = PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG | PUBKEY_USAGE_AUTH; default: break; } return use; } /* Map the OpenPGP pubkey algorithm whose ID is contained in ALGO to a string representation of the algorithm name. For unknown algorithm IDs this function returns "?". */ const char * openpgp_pk_algo_name (pubkey_algo_t algo) { switch (algo) { case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_RSA_S: return "RSA"; case PUBKEY_ALGO_ELGAMAL: case PUBKEY_ALGO_ELGAMAL_E: return "ELG"; case PUBKEY_ALGO_DSA: return "DSA"; case PUBKEY_ALGO_ECDH: return "ECDH"; case PUBKEY_ALGO_ECDSA: return "ECDSA"; case PUBKEY_ALGO_EDDSA: return "EDDSA"; default: return "?"; } } /* Explicit mapping of OpenPGP digest algos to Libgcrypt. */ /* FIXME: We do not yes use it everywhere. */ enum gcry_md_algos map_md_openpgp_to_gcry (digest_algo_t algo) { switch (algo) { #ifdef GPG_USE_MD5 case DIGEST_ALGO_MD5: return GCRY_MD_MD5; #else case DIGEST_ALGO_MD5: return 0; #endif case DIGEST_ALGO_SHA1: return GCRY_MD_SHA1; #ifdef GPG_USE_RMD160 case DIGEST_ALGO_RMD160: return GCRY_MD_RMD160; #else case DIGEST_ALGO_RMD160: return 0; #endif #ifdef GPG_USE_SHA224 case DIGEST_ALGO_SHA224: return GCRY_MD_SHA224; #else case DIGEST_ALGO_SHA224: return 0; #endif case DIGEST_ALGO_SHA256: return GCRY_MD_SHA256; #ifdef GPG_USE_SHA384 case DIGEST_ALGO_SHA384: return GCRY_MD_SHA384; #else case DIGEST_ALGO_SHA384: return 0; #endif #ifdef GPG_USE_SHA512 case DIGEST_ALGO_SHA512: return GCRY_MD_SHA512; #else case DIGEST_ALGO_SHA512: return 0; #endif default: return 0; } } /* Return 0 if ALGO is suitable and implemented OpenPGP hash algorithm. */ int openpgp_md_test_algo (digest_algo_t algo) { enum gcry_md_algos ga; ga = map_md_openpgp_to_gcry (algo); if (!ga) return gpg_error (GPG_ERR_DIGEST_ALGO); return gcry_md_test_algo (ga); } /* Map the OpenPGP digest algorithm whose ID is contained in ALGO to a string representation of the algorithm name. For unknown algorithm IDs this function returns "?". */ const char * openpgp_md_algo_name (int algo) { switch (algo) { case DIGEST_ALGO_MD5: return "MD5"; case DIGEST_ALGO_SHA1: return "SHA1"; case DIGEST_ALGO_RMD160: return "RIPEMD160"; case DIGEST_ALGO_SHA256: return "SHA256"; case DIGEST_ALGO_SHA384: return "SHA384"; case DIGEST_ALGO_SHA512: return "SHA512"; case DIGEST_ALGO_SHA224: return "SHA224"; } return "?"; } static unsigned long get_signature_count (PKT_public_key *pk) { #ifdef ENABLE_CARD_SUPPORT struct agent_card_info_s info; (void)pk; if (!agent_scd_getattr ("SIG-COUNTER",&info)) return info.sig_counter; else return 0; #else (void)pk; return 0; #endif } /* Expand %-strings. Returns a string which must be xfreed. Returns NULL if the string cannot be expanded (too large). */ char * pct_expando(const char *string,struct expando_args *args) { const char *ch=string; int idx=0,maxlen=0,done=0; u32 pk_keyid[2]={0,0},sk_keyid[2]={0,0}; char *ret=NULL; if(args->pk) keyid_from_pk(args->pk,pk_keyid); if(args->pksk) keyid_from_pk (args->pksk, sk_keyid); /* This is used so that %k works in photoid command strings in --list-secret-keys (which of course has a sk, but no pk). */ if(!args->pk && args->pksk) keyid_from_pk (args->pksk, pk_keyid); while(*ch!='\0') { if(!done) { /* 8192 is way bigger than we'll need here */ if(maxlen>=8192) goto fail; maxlen+=1024; ret=xrealloc(ret,maxlen); } done=0; if(*ch=='%') { switch(*(ch+1)) { case 's': /* short key id */ if(idx+8namehash) { char *tmp = zb32_encode (args->namehash, 8*20); if (tmp) { if (idx + strlen (tmp) < maxlen) { strcpy (ret+idx, tmp); idx += strlen (tmp); } xfree (tmp); done = 1; } } break; case 'c': /* signature count from card, if any. */ if(idx+10pksk)); idx+=strlen(&ret[idx]); done=1; } break; case 'f': /* Fingerprint of key being signed */ case 'p': /* Fingerprint of the primary key making the signature. */ case 'g': /* Fingerprint of the key making the signature. */ { byte array[MAX_FINGERPRINT_LEN]; size_t len; int i; if ((*(ch+1))=='f' && args->pk) fingerprint_from_pk (args->pk, array, &len); else if ((*(ch+1))=='p' && args->pksk) { if(args->pksk->flags.primary) fingerprint_from_pk (args->pksk, array, &len); else if (args->pksk->main_keyid[0] || args->pksk->main_keyid[1]) { /* Not the primary key: Find the fingerprint of the primary key. */ PKT_public_key *pk= xmalloc_clear(sizeof(PKT_public_key)); if (!get_pubkey_fast (pk,args->pksk->main_keyid)) fingerprint_from_pk (pk, array, &len); else memset (array, 0, (len=MAX_FINGERPRINT_LEN)); free_public_key (pk); } else /* Oops: info about the primary key missing. */ memset(array,0,(len=MAX_FINGERPRINT_LEN)); } else if((*(ch+1))=='g' && args->pksk) fingerprint_from_pk (args->pksk, array, &len); else memset(array,0,(len=MAX_FINGERPRINT_LEN)); if(idx+(len*2)validity_info && idx+1validity_info; ret[idx]='\0'; done=1; } break; /* The text string types */ case 't': case 'T': case 'V': { const char *str=NULL; switch(*(ch+1)) { case 't': /* e.g. "jpg" */ str=image_type_to_string(args->imagetype,0); break; case 'T': /* e.g. "image/jpeg" */ str=image_type_to_string(args->imagetype,2); break; case 'V': /* e.g. "full", "expired", etc. */ str=args->validity_string; break; } if(str && idx+strlen(str)='A' && file[0]<='Z') || (file[0]>='a' && file[0]<='z')) && file[1]==':') #else || file[0]=='/' #endif ) return access(file,mode); else { /* At least as large as, but most often larger than we need. */ char *buffer=xmalloc(strlen(envpath)+1+strlen(file)+1); char *split,*item,*path=xstrdup(envpath); split=path; while((item=strsep(&split,PATHSEP_S))) { strcpy(buffer,item); strcat(buffer,"/"); strcat(buffer,file); ret=access(buffer,mode); if(ret==0) break; } xfree(path); xfree(buffer); } return ret; } /* Return the number of public key parameters as used by OpenPGP. */ int pubkey_get_npkey (pubkey_algo_t algo) { switch (algo) { case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_RSA_S: return 2; case PUBKEY_ALGO_ELGAMAL_E: return 3; case PUBKEY_ALGO_DSA: return 4; case PUBKEY_ALGO_ECDH: return 3; case PUBKEY_ALGO_ECDSA: return 2; case PUBKEY_ALGO_ELGAMAL: return 3; case PUBKEY_ALGO_EDDSA: return 2; default: return 0; } } /* Return the number of secret key parameters as used by OpenPGP. */ int pubkey_get_nskey (pubkey_algo_t algo) { switch (algo) { case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_RSA_S: return 6; case PUBKEY_ALGO_ELGAMAL_E: return 4; case PUBKEY_ALGO_DSA: return 5; case PUBKEY_ALGO_ECDH: return 4; case PUBKEY_ALGO_ECDSA: return 3; case PUBKEY_ALGO_ELGAMAL: return 4; case PUBKEY_ALGO_EDDSA: return 3; default: return 0; } } /* Temporary helper. */ int pubkey_get_nsig (pubkey_algo_t algo) { switch (algo) { case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_RSA_S: return 1; case PUBKEY_ALGO_ELGAMAL_E: return 0; case PUBKEY_ALGO_DSA: return 2; case PUBKEY_ALGO_ECDH: return 0; case PUBKEY_ALGO_ECDSA: return 2; case PUBKEY_ALGO_ELGAMAL: return 2; case PUBKEY_ALGO_EDDSA: return 2; default: return 0; } } /* Temporary helper. */ int pubkey_get_nenc (pubkey_algo_t algo) { switch (algo) { case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_RSA_S: return 1; case PUBKEY_ALGO_ELGAMAL_E: return 2; case PUBKEY_ALGO_DSA: return 0; case PUBKEY_ALGO_ECDH: return 2; case PUBKEY_ALGO_ECDSA: return 0; case PUBKEY_ALGO_ELGAMAL: return 2; case PUBKEY_ALGO_EDDSA: return 0; default: return 0; } } /* Temporary helper. */ unsigned int pubkey_nbits( int algo, gcry_mpi_t *key ) { int rc, nbits; gcry_sexp_t sexp; if (algo == PUBKEY_ALGO_DSA && key[0] && key[1] && key[2] && key[3]) { rc = gcry_sexp_build (&sexp, NULL, "(public-key(dsa(p%m)(q%m)(g%m)(y%m)))", key[0], key[1], key[2], key[3] ); } else if ((algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E) && key[0] && key[1] && key[2]) { rc = gcry_sexp_build (&sexp, NULL, "(public-key(elg(p%m)(g%m)(y%m)))", key[0], key[1], key[2] ); } else if (is_RSA (algo) && key[0] && key[1]) { rc = gcry_sexp_build (&sexp, NULL, "(public-key(rsa(n%m)(e%m)))", key[0], key[1] ); } else if ((algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_EDDSA) && key[0] && key[1]) { char *curve = openpgp_oid_to_str (key[0]); if (!curve) rc = gpg_error_from_syserror (); else { rc = gcry_sexp_build (&sexp, NULL, "(public-key(ecc(curve%s)(q%m)))", curve, key[1]); xfree (curve); } } else return 0; if (rc) BUG (); nbits = gcry_pk_get_nbits (sexp); gcry_sexp_release (sexp); return nbits; } int mpi_print (estream_t fp, gcry_mpi_t a, int mode) { int n = 0; size_t nwritten; if (!a) return es_fprintf (fp, "[MPI_NULL]"); if (!mode) { unsigned int n1; n1 = gcry_mpi_get_nbits(a); n += es_fprintf (fp, "[%u bits]", n1); } else if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) { unsigned int nbits; unsigned char *p = gcry_mpi_get_opaque (a, &nbits); if (!p) n += es_fprintf (fp, "[invalid opaque value]"); else { if (!es_write_hexstring (fp, p, (nbits + 7)/8, 0, &nwritten)) n += nwritten; } } else { unsigned char *buffer; size_t buflen; if (gcry_mpi_aprint (GCRYMPI_FMT_USG, &buffer, &buflen, a)) BUG (); if (!es_write_hexstring (fp, buffer, buflen, 0, &nwritten)) n += nwritten; gcry_free (buffer); } return n; } /* pkey[1] or skey[1] is Q for ECDSA, which is an uncompressed point, i.e. 04 */ unsigned int ecdsa_qbits_from_Q (unsigned int qbits) { if ((qbits%8) > 3) { log_error (_("ECDSA public key is expected to be in SEC encoding " "multiple of 8 bits\n")); return 0; } qbits -= qbits%8; qbits /= 2; return qbits; } /* Ignore signatures and certifications made over certain digest * algorithms by default, MD5 is considered weak. This allows users * to deprecate support for other algorithms as well. */ void additional_weak_digest (const char* digestname) { struct weakhash *weak = NULL; const enum gcry_md_algos algo = string_to_digest_algo(digestname); if (algo == GCRY_MD_NONE) { log_error (_("unknown weak digest '%s'\n"), digestname); return; } /* Check to ensure it's not already present. */ for (weak = opt.weak_digests; weak; weak = weak->next) if (algo == weak->algo) return; /* Add it to the head of the list. */ weak = xmalloc(sizeof(*weak)); weak->algo = algo; weak->rejection_shown = 0; weak->next = opt.weak_digests; opt.weak_digests = weak; } diff --git a/g10/openfile.c b/g10/openfile.c index fd9edb66b..86ed1dc79 100644 --- a/g10/openfile.c +++ b/g10/openfile.c @@ -1,421 +1,421 @@ /* openfile.c * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009, * 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 #include #include #include #include #include #include #include #include #include "gpg.h" #include "../common/util.h" #include "../common/ttyio.h" #include "options.h" #include "main.h" #include "../common/status.h" #include "../common/i18n.h" #ifdef HAVE_W32_SYSTEM #define NAME_OF_DEV_NULL "nul" #else #define NAME_OF_DEV_NULL "/dev/null" #endif #if defined (HAVE_DRIVE_LETTERS) || defined (__riscos__) #define CMP_FILENAME(a,b) ascii_strcasecmp( (a), (b) ) #else #define CMP_FILENAME(a,b) strcmp( (a), (b) ) #endif /* FIXME: Implement opt.interactive. */ /* * Check whether FNAME exists and ask if it's okay to overwrite an * existing one. * Returns: True: it's okay to overwrite or the file does not exist * False: Do not overwrite */ int overwrite_filep( const char *fname ) { if ( iobuf_is_pipe_filename (fname) ) return 1; /* Writing to stdout is always okay. */ if ( access( fname, F_OK ) ) return 1; /* Does not exist. */ if ( !compare_filenames (fname, NAME_OF_DEV_NULL) ) return 1; /* Does not do any harm. */ if (opt.answer_yes) return 1; if (opt.answer_no || opt.batch) return 0; /* Do not overwrite. */ tty_printf (_("File '%s' exists. "), fname); if (cpr_enabled ()) tty_printf ("\n"); if (cpr_get_answer_is_yes ("openfile.overwrite.okay", _("Overwrite? (y/N) ")) ) return 1; return 0; } /* * Strip known extensions from iname and return a newly allocated * filename. Return NULL if we can't do that. */ char * make_outfile_name (const char *iname) { size_t n; if (iobuf_is_pipe_filename (iname)) return xstrdup ("-"); n = strlen (iname); if (n > 4 && (!CMP_FILENAME(iname+n-4, EXTSEP_S GPGEXT_GPG) || !CMP_FILENAME(iname+n-4, EXTSEP_S "pgp") || !CMP_FILENAME(iname+n-4, EXTSEP_S "sig") || !CMP_FILENAME(iname+n-4, EXTSEP_S "asc"))) { char *buf = xstrdup (iname); buf[n-4] = 0; return buf; } else if (n > 5 && !CMP_FILENAME(iname+n-5, EXTSEP_S "sign")) { char *buf = xstrdup (iname); buf[n-5] = 0; return buf; } log_info (_("%s: unknown suffix\n"), iname); return NULL; } /* Ask for an output filename; use the given one as default. Return NULL if no file has been given or if it is not possible to ask the user. NAME is the template len which might contain enbedded Nuls. NAMELEN is its actual length. */ char * ask_outfile_name( const char *name, size_t namelen ) { size_t n; const char *s; char *prompt; char *fname; char *defname; if ( opt.batch ) return NULL; defname = name && namelen? make_printable_string (name, namelen, 0) : NULL; s = _("Enter new filename"); n = strlen(s) + (defname?strlen (defname):0) + 10; prompt = xmalloc (n); if (defname) snprintf (prompt, n, "%s [%s]: ", s, defname ); else snprintf (prompt, n, "%s: ", s ); tty_enable_completion(NULL); fname = cpr_get ("openfile.askoutname", prompt ); cpr_kill_prompt (); tty_disable_completion (); xfree (prompt); if ( !*fname ) { xfree (fname); fname = defname; defname = NULL; } xfree (defname); if (fname) trim_spaces (fname); return fname; } /* * Make an output filename for the inputfile INAME. * Returns an IOBUF and an errorcode * Mode 0 = use ".gpg" * 1 = use ".asc" * 2 = use ".sig" * 3 = use ".rev" * * If INP_FD is not -1 the function simply creates an IOBUF for that * file descriptor and ignore INAME and MODE. Note that INP_FD won't * be closed if the returned IOBUF is closed. With RESTRICTEDPERM a * file will be created with mode 700 if possible. */ int open_outfile (int inp_fd, const char *iname, int mode, int restrictedperm, iobuf_t *a) { int rc = 0; *a = NULL; if (inp_fd != -1) { char xname[64]; *a = iobuf_fdopen_nc (inp_fd, "wb"); if (!*a) { rc = gpg_error_from_syserror (); snprintf (xname, sizeof xname, "[fd %d]", inp_fd); log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (rc)); } else if (opt.verbose) { snprintf (xname, sizeof xname, "[fd %d]", inp_fd); log_info (_("writing to '%s'\n"), xname); } } else if (iobuf_is_pipe_filename (iname) && !opt.outfile) { *a = iobuf_create (NULL, 0); if ( !*a ) { rc = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), "[stdout]", strerror(errno) ); } else if ( opt.verbose ) log_info (_("writing to stdout\n")); } else { char *buf = NULL; const char *name; if (opt.dry_run) name = NAME_OF_DEV_NULL; else if (opt.outfile) name = opt.outfile; else { #ifdef USE_ONLY_8DOT3 if (opt.mangle_dos_filenames) { /* It is quite common for DOS systems to have only one dot in a filename. If we have something like this, we simple replace the suffix except in cases where the suffix is larger than 3 characters and not the same as the new one. We don't map the filenames to 8.3 because this is a duty of the file system. */ char *dot; const char *newsfx; newsfx = (mode==1 ? ".asc" : mode==2 ? ".sig" : mode==3 ? ".rev" : ".gpg"); buf = xmalloc (strlen(iname)+4+1); strcpy (buf, iname); dot = strchr (buf, '.' ); if ( dot && dot > buf && dot[1] && strlen(dot) <= 4 && CMP_FILENAME (newsfx, dot) ) strcpy (dot, newsfx); else if (dot && !dot[1]) /* Do not duplicate a dot. */ strcpy (dot, newsfx+1); else strcat (buf, newsfx); } if (!buf) #endif /* USE_ONLY_8DOT3 */ { buf = xstrconcat (iname, (mode==1 ? EXTSEP_S "asc" : mode==2 ? EXTSEP_S "sig" : mode==3 ? EXTSEP_S "rev" : /* */ EXTSEP_S GPGEXT_GPG), NULL); } name = buf; } rc = 0; while ( !overwrite_filep (name) ) { char *tmp = ask_outfile_name (NULL, 0); if ( !tmp || !*tmp ) { xfree (tmp); rc = gpg_error (GPG_ERR_EEXIST); break; } xfree (buf); name = buf = tmp; } if ( !rc ) { if (is_secured_filename (name) ) { *a = NULL; gpg_err_set_errno (EPERM); } else *a = iobuf_create (name, restrictedperm); if (!*a) { rc = gpg_error_from_syserror (); log_error(_("can't create '%s': %s\n"), name, strerror(errno) ); } else if( opt.verbose ) log_info (_("writing to '%s'\n"), name ); } xfree(buf); } if (*a) iobuf_ioctl (*a, IOBUF_IOCTL_NO_CACHE, 1, NULL); return rc; } /* Find a matching data file for the signature file SIGFILENAME and return it as a malloced string. If no matching data file is found, return NULL. */ char * get_matching_datafile (const char *sigfilename) { char *fname = NULL; size_t len; if (iobuf_is_pipe_filename (sigfilename)) return NULL; len = strlen (sigfilename); if (len > 4 && (!strcmp (sigfilename + len - 4, EXTSEP_S "sig") || (len > 5 && !strcmp(sigfilename + len - 5, EXTSEP_S "sign")) || !strcmp(sigfilename + len - 4, EXTSEP_S "asc"))) { fname = xstrdup (sigfilename); fname[len-(fname[len-1]=='n'?5:4)] = 0 ; if (gnupg_access (fname, R_OK )) { /* Not found or other error. */ xfree (fname); fname = NULL; } } return fname; } /* * Try to open a file without the extension ".sig" or ".asc" * Return NULL if such a file is not available. */ iobuf_t open_sigfile (const char *sigfilename, progress_filter_context_t *pfx) { iobuf_t a = NULL; char *buf; buf = get_matching_datafile (sigfilename); if (buf) { a = iobuf_open (buf); if (a && is_secured_file (iobuf_get_fd (a))) { iobuf_close (a); a = NULL; gpg_err_set_errno (EPERM); } if (a) log_info (_("assuming signed data in '%s'\n"), buf); if (a && pfx) handle_progress (pfx, a, buf); xfree (buf); } return a; } void try_make_homedir (const char *fname) { const char *defhome = standard_homedir (); /* 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 do compare only the suffix if we see that the default homedir does start with a tilde. */ if ( opt.dry_run || opt.no_homedir_creation ) return; if ( #ifdef HAVE_W32_SYSTEM ( !compare_filenames (fname, defhome) ) #else ( *defhome == '~' && (strlen(fname) >= strlen (defhome+1) && !strcmp(fname+strlen(fname)-strlen(defhome+1), defhome+1 ) )) || (*defhome != '~' && !compare_filenames( fname, defhome ) ) #endif ) { if (gnupg_mkdir (fname, "-rwx")) log_fatal ( _("can't create directory '%s': %s\n"), fname, strerror(errno) ); else if (!opt.quiet ) log_info ( _("directory '%s' created\n"), fname ); } } /* Get and if needed create a string with the directory used to store openpgp revocations. */ char * get_openpgp_revocdir (const char *home) { char *fname; struct stat statbuf; fname = make_filename (home, GNUPG_OPENPGP_REVOC_DIR, NULL); - if (stat (fname, &statbuf) && errno == ENOENT) + if (gnupg_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); } return fname; } diff --git a/g10/tdbio.c b/g10/tdbio.c index b91a843ff..9bae2ed32 100644 --- a/g10/tdbio.c +++ b/g10/tdbio.c @@ -1,1933 +1,1933 @@ /* tdbio.c - trust database I/O operations * Copyright (C) 1998-2002, 2012 Free Software Foundation, Inc. * Copyright (C) 1998-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 #include #include #include #include #include #include #include #include #include "gpg.h" #include "../common/status.h" #include "../common/iobuf.h" #include "../common/util.h" #include "options.h" #include "main.h" #include "../common/i18n.h" #include "trustdb.h" #include "tdbio.h" #if defined(HAVE_DOSISH_SYSTEM) && !defined(ftruncate) #define ftruncate chsize #endif #if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) #define MY_O_BINARY O_BINARY #else #define MY_O_BINARY 0 #endif /* We use ERRNO despite that the cegcc provided open/read/write functions don't set ERRNO - at least show that ERRNO does not make sense. */ #ifdef HAVE_W32CE_SYSTEM #undef strerror #define strerror(a) ("[errno not available]") #endif /* * Yes, this is a very simple implementation. We should really * use a page aligned buffer and read complete pages. * To implement a simple trannsaction system, this is sufficient. */ typedef struct cache_ctrl_struct *CACHE_CTRL; struct cache_ctrl_struct { CACHE_CTRL next; struct { unsigned used:1; unsigned dirty:1; } flags; ulong recno; char data[TRUST_RECORD_LEN]; }; /* Size of the cache. The SOFT value is the general one. While in a transaction this may not be sufficient and thus we may increase it then up to the HARD limit. */ #define MAX_CACHE_ENTRIES_SOFT 200 #define MAX_CACHE_ENTRIES_HARD 10000 /* The cache is controlled by these variables. */ static CACHE_CTRL cache_list; static int cache_entries; static int cache_is_dirty; /* An object to pass information to cmp_krec_fpr. */ struct cmp_krec_fpr_struct { int pubkey_algo; const char *fpr; int fprlen; }; /* An object used to pass information to cmp_[s]dir. */ struct cmp_xdir_struct { int pubkey_algo; u32 keyid[2]; }; /* The name of the trustdb file. */ static char *db_name; /* The handle for locking the trustdb file and a counter to record how * often this lock has been taken. That counter is required becuase * dotlock does not implemen recursive locks. */ static dotlock_t lockhandle; static unsigned int is_locked; /* The file descriptor of the trustdb. */ static int db_fd = -1; /* A flag indicating that a transaction is active. */ /* static int in_transaction; Not yet used. */ static void open_db (void); static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type); /* * Take a lock on the trustdb file name. I a lock file can't be * created the function terminates the process. Except for a * different return code the function does nothing if the lock has * already been taken. * * Returns: True if lock already exists, False if the lock has * actually been taken. */ static int take_write_lock (void) { int rc; if (!lockhandle) lockhandle = dotlock_create (db_name, 0); if (!lockhandle) log_fatal ( _("can't create lock for '%s'\n"), db_name ); if (!is_locked) { if (dotlock_take (lockhandle, -1) ) log_fatal ( _("can't lock '%s'\n"), db_name ); rc = 0; } else rc = 1; if (opt.lock_once) is_locked = 1; else is_locked++; return rc; } /* * Release a lock from the trustdb file unless the global option * --lock-once has been used. */ static void release_write_lock (void) { if (opt.lock_once) return; /* Don't care; here IS_LOCKED is fixed to 1. */ if (!is_locked) { log_error ("Ooops, tdbio:release_write_lock with no lock held\n"); return; } if (--is_locked) return; if (dotlock_release (lockhandle)) log_error ("Oops, tdbio:release_write_locked failed\n"); } /************************************* ************* record cache ********** *************************************/ /* * Get the data from the record cache and return a pointer into that * cache. Caller should copy the returned data. NULL is returned on * a cache miss. */ static const char * get_record_from_cache (ulong recno) { CACHE_CTRL r; for (r = cache_list; r; r = r->next) { if (r->flags.used && r->recno == recno) return r->data; } return NULL; } /* * Write a cached item back to the trustdb file. * * Returns: 0 on success or an error code. */ static int write_cache_item (CACHE_CTRL r) { gpg_error_t err; int n; if (lseek (db_fd, r->recno * TRUST_RECORD_LEN, SEEK_SET) == -1) { err = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: lseek failed: %s\n"), r->recno, strerror (errno)); return err; } n = write (db_fd, r->data, TRUST_RECORD_LEN); if (n != TRUST_RECORD_LEN) { err = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"), r->recno, n, strerror (errno) ); return err; } r->flags.dirty = 0; return 0; } /* * Put data into the cache. This function may flush * some cache entries if the cache is filled up. * * Returns: 0 on success or an error code. */ static int put_record_into_cache (ulong recno, const char *data) { CACHE_CTRL r, unused; int dirty_count = 0; int clean_count = 0; /* See whether we already cached this one. */ for (unused = NULL, r = cache_list; r; r = r->next) { if (!r->flags.used) { if (!unused) unused = r; } else if (r->recno == recno) { if (!r->flags.dirty) { /* Hmmm: should we use a copy and compare? */ if (memcmp (r->data, data, TRUST_RECORD_LEN)) { r->flags.dirty = 1; cache_is_dirty = 1; } } memcpy (r->data, data, TRUST_RECORD_LEN); return 0; } if (r->flags.used) { if (r->flags.dirty) dirty_count++; else clean_count++; } } /* Not in the cache: add a new entry. */ if (unused) { /* Reuse this entry. */ r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* See whether we reached the limit. */ if (cache_entries < MAX_CACHE_ENTRIES_SOFT) { /* No: Put into cache. */ r = xmalloc (sizeof *r); r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; r->next = cache_list; cache_list = r; cache_is_dirty = 1; cache_entries++; return 0; } /* Cache is full: discard some clean entries. */ if (clean_count) { int n; /* We discard a third of the clean entries. */ n = clean_count / 3; if (!n) n = 1; for (unused = NULL, r = cache_list; r; r = r->next) { if (r->flags.used && !r->flags.dirty) { if (!unused) unused = r; r->flags.used = 0; cache_entries--; if (!--n) break; } } /* Now put into the cache. */ log_assert (unused); r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* No clean entries: We have to flush some dirty entries. */ #if 0 /* Transactions are not yet used. */ if (in_transaction) { /* But we can't do this while in a transaction. Thus we * increase the cache size instead. */ if (cache_entries < MAX_CACHE_ENTRIES_HARD) { if (opt.debug && !(cache_entries % 100)) log_debug ("increasing tdbio cache size\n"); r = xmalloc (sizeof *r); r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; r->next = cache_list; cache_list = r; cache_is_dirty = 1; cache_entries++; return 0; } /* Hard limit for the cache size reached. */ log_info (_("trustdb transaction too large\n")); return GPG_ERR_RESOURCE_LIMIT; } #endif if (dirty_count) { int n; /* Discard some dirty entries. */ n = dirty_count / 5; if (!n) n = 1; take_write_lock (); for (unused = NULL, r = cache_list; r; r = r->next) { if (r->flags.used && r->flags.dirty) { int rc; rc = write_cache_item (r); if (rc) return rc; if (!unused) unused = r; r->flags.used = 0; cache_entries--; if (!--n) break; } } release_write_lock (); /* Now put into the cache. */ log_assert (unused); r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* We should never reach this. */ BUG(); } /* Return true if the cache is dirty. */ int tdbio_is_dirty() { return cache_is_dirty; } /* * Flush the cache. This cannot be used while in a transaction. */ int tdbio_sync() { CACHE_CTRL r; int did_lock = 0; if( db_fd == -1 ) open_db(); #if 0 /* Transactions are not yet used. */ if( in_transaction ) log_bug("tdbio: syncing while in transaction\n"); #endif if( !cache_is_dirty ) return 0; if (!take_write_lock ()) did_lock = 1; for( r = cache_list; r; r = r->next ) { if( r->flags.used && r->flags.dirty ) { int rc = write_cache_item( r ); if( rc ) return rc; } } cache_is_dirty = 0; if (did_lock) release_write_lock (); return 0; } #if 0 /* Not yet used. */ /* * Simple transactions system: * Everything between begin_transaction and end/cancel_transaction * is not immediately written but at the time of end_transaction. * * NOTE: The transaction code is disabled in the 1.2 branch, as it is * not yet used. */ int tdbio_begin_transaction () /* Not yet used. */ { int rc; if (in_transaction) log_bug ("tdbio: nested transactions\n"); /* Flush everything out. */ rc = tdbio_sync(); if (rc) return rc; in_transaction = 1; return 0; } int tdbio_end_transaction () /* Not yet used. */ { int rc; if (!in_transaction) log_bug ("tdbio: no active transaction\n"); take_write_lock (); gnupg_block_all_signals (); in_transaction = 0; rc = tdbio_sync(); gnupg_unblock_all_signals(); release_write_lock (); return rc; } int tdbio_cancel_transaction () /* Not yet used. */ { CACHE_CTRL r; if (!in_transaction) log_bug ("tdbio: no active transaction\n"); /* Remove all dirty marked entries, so that the original ones are * read back the next time. */ if (cache_is_dirty) { for (r = cache_list; r; r = r->next) { if (r->flags.used && r->flags.dirty) { r->flags.used = 0; cache_entries--; } } cache_is_dirty = 0; } in_transaction = 0; return 0; } #endif /* Not yet used. */ /******************************************************** **************** cached I/O functions ****************** ********************************************************/ /* The cleanup handler for this module. */ static void cleanup (void) { if (is_locked) { if (!dotlock_release (lockhandle)) is_locked = 0; } } /* * Update an existing trustdb record. The caller must call * tdbio_sync. * * Returns: 0 on success or an error code. */ int tdbio_update_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; int opt_tm; /* Never store a TOFU trust model in the trustdb. Use PGP instead. */ opt_tm = opt.trust_model; if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) opt_tm = TM_PGP; memset (&rec, 0, sizeof rec); rc = tdbio_read_record (0, &rec, RECTYPE_VER); if (!rc) { rec.r.ver.created = make_timestamp(); rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; rec.r.ver.trust_model = opt_tm; rec.r.ver.min_cert_level = opt.min_cert_level; rc = tdbio_write_record (ctrl, &rec); } return rc; } /* * Create and write the trustdb version record. * This is called with the writelock activ. * Returns: 0 on success or an error code. */ static int create_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; int opt_tm; /* Never store a TOFU trust model in the trustdb. Use PGP instead. */ opt_tm = opt.trust_model; if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) opt_tm = TM_PGP; memset (&rec, 0, sizeof rec); rec.r.ver.version = 3; rec.r.ver.created = make_timestamp (); rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; if (opt_tm == TM_PGP || opt_tm == TM_CLASSIC) rec.r.ver.trust_model = opt_tm; else rec.r.ver.trust_model = TM_PGP; rec.r.ver.min_cert_level = opt.min_cert_level; rec.rectype = RECTYPE_VER; rec.recnum = 0; rc = tdbio_write_record (ctrl, &rec); if (!rc) tdbio_sync (); if (!rc) create_hashtable (ctrl, &rec, 0); return rc; } /* * Set the file name for the trustdb to NEW_DBNAME and if CREATE is * true create that file. If NEW_DBNAME is NULL a default name is * used, if the it does not contain a path component separator ('/') * the global GnuPG home directory is used. * * Returns: 0 on success or an error code. * * On the first call this function registers an atexit handler. * */ int tdbio_set_dbname (ctrl_t ctrl, const char *new_dbname, int create, int *r_nofile) { char *fname, *p; struct stat statbuf; static int initialized = 0; int save_slash; if (!initialized) { atexit (cleanup); initialized = 1; } *r_nofile = 0; if (!new_dbname) { fname = make_filename (gnupg_homedir (), "trustdb" EXTSEP_S GPGEXT_GPG, NULL); } else if (*new_dbname != DIRSEP_C ) { if (strchr (new_dbname, DIRSEP_C)) fname = make_filename (new_dbname, NULL); else fname = make_filename (gnupg_homedir (), new_dbname, NULL); } else { fname = xstrdup (new_dbname); } xfree (db_name); db_name = fname; /* Quick check for (likely) case where there already is a * trustdb.gpg. This check is not required in theory, but it helps * in practice avoiding costly operations of preparing and taking * the lock. */ - if (!stat (fname, &statbuf) && statbuf.st_size > 0) + if (!gnupg_stat (fname, &statbuf) && statbuf.st_size > 0) { /* OK, we have the valid trustdb.gpg already. */ return 0; } else if (!create) { *r_nofile = 1; return 0; } /* Here comes: No valid trustdb.gpg AND CREATE==1 */ /* * Make sure the directory exists. This should be done before * acquiring the lock, which assumes the existence of the directory. */ p = strrchr (fname, DIRSEP_C); #if HAVE_W32_SYSTEM { /* Windows may either have a slash or a backslash. Take care of it. */ char *pp = strrchr (fname, '/'); if (!p || pp > p) p = pp; } #endif /*HAVE_W32_SYSTEM*/ log_assert (p); save_slash = *p; *p = 0; if (gnupg_access (fname, F_OK)) { try_make_homedir (fname); if (gnupg_access (fname, F_OK)) log_fatal (_("%s: directory does not exist!\n"), fname); } *p = save_slash; take_write_lock (); if (gnupg_access (fname, R_OK) - || stat (fname, &statbuf) + || gnupg_stat (fname, &statbuf) || statbuf.st_size == 0) { estream_t fp; TRUSTREC rec; int rc; mode_t oldmask; #ifdef HAVE_W32CE_SYSTEM /* We know how the cegcc implementation of access works ;-). */ if (GetLastError () == ERROR_FILE_NOT_FOUND) gpg_err_set_errno (ENOENT); else gpg_err_set_errno (EIO); #endif /*HAVE_W32CE_SYSTEM*/ if (errno && errno != ENOENT) log_fatal ( _("can't access '%s': %s\n"), fname, strerror (errno)); oldmask = umask (077); if (is_secured_filename (fname)) { fp = NULL; gpg_err_set_errno (EPERM); } else fp = es_fopen (fname, "wb"); umask(oldmask); if (!fp) log_fatal (_("can't create '%s': %s\n"), fname, strerror (errno)); es_fclose (fp); db_fd = gnupg_open (db_name, O_RDWR | MY_O_BINARY, 0); if (db_fd == -1) log_fatal (_("can't open '%s': %s\n"), db_name, strerror (errno)); rc = create_version_record (ctrl); if (rc) log_fatal (_("%s: failed to create version record: %s"), fname, gpg_strerror (rc)); /* Read again to check that we are okay. */ if (tdbio_read_record (0, &rec, RECTYPE_VER)) log_fatal (_("%s: invalid trustdb created\n"), db_name); if (!opt.quiet) log_info (_("%s: trustdb created\n"), db_name); } release_write_lock (); return 0; } /* * Return the full name of the trustdb. */ const char * tdbio_get_dbname () { return db_name; } /* * Open the trustdb. This may only be called if it has not yet been * opened and after a successful call to tdbio_set_dbname. On return * the trustdb handle (DB_FD) is guaranteed to be open. */ static void open_db () { TRUSTREC rec; log_assert( db_fd == -1 ); #ifdef HAVE_W32CE_SYSTEM { DWORD prevrc = 0; wchar_t *wname = utf8_to_wchar (db_name); if (wname) { db_fd = (int)CreateFile (wname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); xfree (wname); } if (db_fd == -1) log_fatal ("can't open '%s': %d, %d\n", db_name, (int)prevrc, (int)GetLastError ()); } #else /*!HAVE_W32CE_SYSTEM*/ db_fd = gnupg_open (db_name, O_RDWR | MY_O_BINARY, 0); if (db_fd == -1 && (errno == EACCES #ifdef EROFS || errno == EROFS #endif ) ) { /* Take care of read-only trustdbs. */ db_fd = gnupg_open (db_name, O_RDONLY | MY_O_BINARY, 0); if (db_fd != -1 && !opt.quiet) log_info (_("Note: trustdb not writable\n")); } if ( db_fd == -1 ) log_fatal( _("can't open '%s': %s\n"), db_name, strerror(errno) ); #endif /*!HAVE_W32CE_SYSTEM*/ register_secured_file (db_name); /* Read the version record. */ if (tdbio_read_record (0, &rec, RECTYPE_VER ) ) log_fatal( _("%s: invalid trustdb\n"), db_name ); } /* * Append a new empty hashtable to the trustdb. TYPE gives the type * of the hash table. The only defined type is 0 for a trust hash. * On return the hashtable has been created, written, the version * record update, and the data flushed to the disk. On a fatal error * the function terminates the process. */ static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type) { TRUSTREC rec; off_t offset; ulong recnum; int i, n, rc; offset = lseek (db_fd, 0, SEEK_END); if (offset == -1) log_fatal ("trustdb: lseek to end failed: %s\n", strerror(errno)); recnum = offset / TRUST_RECORD_LEN; log_assert (recnum); /* This is will never be the first record. */ if (!type) vr->r.ver.trusthashtbl = recnum; /* Now write the records making up the hash table. */ n = (256+ITEMS_PER_HTBL_RECORD-1) / ITEMS_PER_HTBL_RECORD; for (i=0; i < n; i++, recnum++) { memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HTBL; rec.recnum = recnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_fatal (_("%s: failed to create hashtable: %s\n"), db_name, gpg_strerror (rc)); } /* Update the version record and flush. */ rc = tdbio_write_record (ctrl, vr); if (!rc) rc = tdbio_sync (); if (rc) log_fatal (_("%s: error updating version record: %s\n"), db_name, gpg_strerror (rc)); } /* * Check whether open trustdb matches the global trust options given * for this process. On a read problem the process is terminated. * * Return: 1 for yes, 0 for no. */ int tdbio_db_matches_options() { static int yes_no = -1; if (yes_no == -1) { TRUSTREC vr; int rc; int opt_tm, tm; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if( rc ) log_fatal( _("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); /* Consider tofu and pgp the same. */ tm = vr.r.ver.trust_model; if (tm == TM_TOFU || tm == TM_TOFU_PGP) tm = TM_PGP; opt_tm = opt.trust_model; if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP) opt_tm = TM_PGP; yes_no = vr.r.ver.marginals == opt.marginals_needed && vr.r.ver.completes == opt.completes_needed && vr.r.ver.cert_depth == opt.max_cert_depth && tm == opt_tm && vr.r.ver.min_cert_level == opt.min_cert_level; } return yes_no; } /* * Read and return the trust model identifier from the trustdb. On a * read problem the process is terminated. */ byte tdbio_read_model (void) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER ); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); return vr.r.ver.trust_model; } /* * Read and return the nextstamp value from the trustdb. On a read * problem the process is terminated. */ ulong tdbio_read_nextcheck () { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); return vr.r.ver.nextcheck; } /* * Write the STAMP nextstamp timestamp to the trustdb. On a read or * write problem the process is terminated. * * Return: True if the stamp actually changed. */ int tdbio_write_nextcheck (ctrl_t ctrl, ulong stamp) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); if (vr.r.ver.nextcheck == stamp) return 0; vr.r.ver.nextcheck = stamp; rc = tdbio_write_record (ctrl, &vr); if (rc) log_fatal (_("%s: error writing version record: %s\n"), db_name, gpg_strerror (rc)); return 1; } /* * Return the record number of the trusthash table or create one if it * does not yet exist. On a read or write problem the process is * terminated. * * Return: record number */ static ulong get_trusthashrec (ctrl_t ctrl) { static ulong trusthashtbl; /* Record number of the trust hashtable. */ (void)ctrl; if (!trusthashtbl) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER ); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); if (!vr.r.ver.trusthashtbl) { /* Oops: the trustdb is corrupt because the hashtable is * always created along with the version record. However, * if something went initially wrong it may happen that * there is just the version record. We try to fix it here. * If we can't do that we return 0 - this is the version * record and thus the actual read will detect the mismatch * and bail out. Note that create_hashtable updates VR. */ take_write_lock (); if (lseek (db_fd, 0, SEEK_END) == TRUST_RECORD_LEN) create_hashtable (ctrl, &vr, 0); release_write_lock (); } trusthashtbl = vr.r.ver.trusthashtbl; } return trusthashtbl; } /* * Update a hashtable in the trustdb. TABLE gives the start of the * table, KEY and KEYLEN are the key, NEWRECNUM is the record number * to insert into the table. * * Return: 0 on success or an error code. */ static int upd_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong newrecnum) { TRUSTREC lastrec, rec; ulong hashrec, item; int msb; int level = 0; int rc, i; hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL); if (rc) { log_error ("upd_hashtable: read failed: %s\n", gpg_strerror (rc)); return rc; } item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) /* Insert a new item into the hash table. */ { rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error ("upd_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } } else if (item != newrecnum) /* Must do an update. */ { lastrec = rec; rc = tdbio_read_record (item, &rec, 0); if (rc) { log_error ("upd_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if (rec.rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections.\n"); return GPG_ERR_TRUSTDB; } goto next_level; } else if (rec.rectype == RECTYPE_HLST) /* Extend the list. */ { /* Check whether the key is already in this list. */ for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec.r.hlst.rnum[i] == newrecnum) { return 0; /* Okay, already in the list. */ } } if (rec.r.hlst.next) { rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("upd_hashtable: read hlst failed: %s\n", gpg_strerror (rc) ); return rc; } } else break; /* key is not in the list */ } /* Find the next free entry and put it in. */ for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (!rec.r.hlst.rnum[i]) { /* Empty slot found. */ rec.r.hlst.rnum[i] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("upd_hashtable: write hlst failed: %s\n", gpg_strerror (rc)); return rc; /* Done. */ } } if (rec.r.hlst.next) { /* read the next reord of the list. */ rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("upd_hashtable: read hlst failed: %s\n", gpg_strerror (rc)); return rc; } } else { /* Append a new record to the list. */ rec.r.hlst.next = item = tdbio_new_recnum (ctrl); rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error ("upd_hashtable: write hlst failed: %s\n", gpg_strerror (rc)); return rc; } memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HLST; rec.recnum = item; rec.r.hlst.rnum[0] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("upd_hashtable: write ext hlst failed: %s\n", gpg_strerror (rc)); return rc; /* Done. */ } } /* end loop over list slots */ } else if (rec.rectype == RECTYPE_TRUST) /* Insert a list record. */ { if (rec.recnum == newrecnum) { return 0; } item = rec.recnum; /* Save number of key record. */ memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HLST; rec.recnum = tdbio_new_recnum (ctrl); rec.r.hlst.rnum[0] = item; /* Old key record */ rec.r.hlst.rnum[1] = newrecnum; /* and new key record */ rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error( "upd_hashtable: write new hlst failed: %s\n", gpg_strerror (rc) ); return rc; } /* Update the hashtable record. */ lastrec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = rec.recnum; rc = tdbio_write_record (ctrl, &lastrec); if (rc) log_error ("upd_hashtable: update htbl failed: %s\n", gpg_strerror (rc)); return rc; /* Ready. */ } else { log_error ("hashtbl %lu: %lu/%d points to an invalid record %lu\n", table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item); if (opt.verbose > 1) list_trustdb (ctrl, es_stderr, NULL); return GPG_ERR_TRUSTDB; } } return 0; } /* * Drop an entry from a hashtable. TABLE gives the start of the * table, KEY and KEYLEN are the key. * * Return: 0 on success or an error code. */ static int drop_from_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong recnum) { TRUSTREC rec; ulong hashrec, item; int msb; int level = 0; int rc, i; hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL ); if (rc) { log_error ("drop_from_hashtable: read failed: %s\n", gpg_strerror (rc)); return rc; } item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) return 0; /* Not found - forget about it. */ if (item == recnum) /* Table points direct to the record. */ { rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = 0; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("drop_from_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } rc = tdbio_read_record (item, &rec, 0); if (rc) { log_error ("drop_from_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if (rec.rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections.\n"); return GPG_ERR_TRUSTDB; } goto next_level; } if (rec.rectype == RECTYPE_HLST) { for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec.r.hlst.rnum[i] == recnum) { rec.r.hlst.rnum[i] = 0; /* Mark as free. */ rc = tdbio_write_record (ctrl, &rec); if (rc) log_error("drop_from_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } } if (rec.r.hlst.next) { rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("drop_from_hashtable: read hlst failed: %s\n", gpg_strerror (rc)); return rc; } } else return 0; /* Key not in table. */ } } log_error ("hashtbl %lu: %lu/%d points to wrong record %lu\n", table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item); return GPG_ERR_TRUSTDB; } /* * Lookup a record via the hashtable TABLE by (KEY,KEYLEN) and return * the result in REC. The return value of CMP() should be True if the * record is the desired one. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ static gpg_error_t lookup_hashtable (ulong table, const byte *key, size_t keylen, int (*cmpfnc)(const void*, const TRUSTREC *), const void *cmpdata, TRUSTREC *rec ) { int rc; ulong hashrec, item; int msb; int level = 0; if (!table) { rc = gpg_error (GPG_ERR_INV_RECORD); log_error("lookup_hashtable failed: %s\n", "request for record 0"); return rc; } hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, rec, RECTYPE_HTBL); if (rc) { log_error("lookup_hashtable failed: %s\n", gpg_strerror (rc) ); return rc; } item = rec->r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) return gpg_error (GPG_ERR_NOT_FOUND); rc = tdbio_read_record (item, rec, 0); if (rc) { log_error( "hashtable read failed: %s\n", gpg_strerror (rc) ); return rc; } if (rec->rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections\n"); return GPG_ERR_TRUSTDB; } goto next_level; } else if (rec->rectype == RECTYPE_HLST) { for (;;) { int i; for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec->r.hlst.rnum[i]) { TRUSTREC tmp; rc = tdbio_read_record (rec->r.hlst.rnum[i], &tmp, 0); if (rc) { log_error ("lookup_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if ((*cmpfnc)(cmpdata, &tmp)) { *rec = tmp; return 0; } } } if (rec->r.hlst.next) { rc = tdbio_read_record (rec->r.hlst.next, rec, RECTYPE_HLST); if (rc) { log_error ("lookup_hashtable: read hlst failed: %s\n", gpg_strerror (rc) ); return rc; } } else return gpg_error (GPG_ERR_NOT_FOUND); } } if ((*cmpfnc)(cmpdata, rec)) return 0; /* really found */ return gpg_error (GPG_ERR_NOT_FOUND); /* no: not found */ } /* * Update the trust hash table TR or create the table if it does not * exist. * * Return: 0 on success or an error code. */ static int update_trusthashtbl (ctrl_t ctrl, TRUSTREC *tr) { return upd_hashtable (ctrl, get_trusthashrec (ctrl), tr->r.trust.fingerprint, 20, tr->recnum); } /* * Dump the trustdb record REC to stream FP. */ void tdbio_dump_record (TRUSTREC *rec, estream_t fp) { int i; ulong rnum = rec->recnum; es_fprintf (fp, "rec %5lu, ", rnum); switch (rec->rectype) { case 0: es_fprintf (fp, "blank\n"); break; case RECTYPE_VER: es_fprintf (fp, "version, td=%lu, f=%lu, m/c/d=%d/%d/%d tm=%d mcl=%d nc=%lu (%s)\n", rec->r.ver.trusthashtbl, rec->r.ver.firstfree, rec->r.ver.marginals, rec->r.ver.completes, rec->r.ver.cert_depth, rec->r.ver.trust_model, rec->r.ver.min_cert_level, rec->r.ver.nextcheck, strtimestamp(rec->r.ver.nextcheck) ); break; case RECTYPE_FREE: es_fprintf (fp, "free, next=%lu\n", rec->r.free.next); break; case RECTYPE_HTBL: es_fprintf (fp, "htbl,"); for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) es_fprintf (fp, " %lu", rec->r.htbl.item[i]); es_putc ('\n', fp); break; case RECTYPE_HLST: es_fprintf (fp, "hlst, next=%lu,", rec->r.hlst.next); for (i=0; i < ITEMS_PER_HLST_RECORD; i++) es_fprintf (fp, " %lu", rec->r.hlst.rnum[i]); es_putc ('\n', fp); break; case RECTYPE_TRUST: es_fprintf (fp, "trust "); for (i=0; i < 20; i++) es_fprintf (fp, "%02X", rec->r.trust.fingerprint[i]); es_fprintf (fp, ", ot=%d, d=%d, vl=%lu\n", rec->r.trust.ownertrust, rec->r.trust.depth, rec->r.trust.validlist); break; case RECTYPE_VALID: es_fprintf (fp, "valid "); for (i=0; i < 20; i++) es_fprintf(fp, "%02X", rec->r.valid.namehash[i]); es_fprintf (fp, ", v=%d, next=%lu\n", rec->r.valid.validity, rec->r.valid.next); break; default: es_fprintf (fp, "unknown type %d\n", rec->rectype ); break; } } /* * Read the record with number RECNUM into the structure REC. If * EXPECTED is not 0 reading any other record type will return an * error. * * Return: 0 on success or an error code. */ int tdbio_read_record (ulong recnum, TRUSTREC *rec, int expected) { byte readbuf[TRUST_RECORD_LEN]; const byte *buf, *p; gpg_error_t err = 0; int n, i; if (db_fd == -1) open_db (); buf = get_record_from_cache( recnum ); if (!buf) { if (lseek (db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1) { err = gpg_error_from_syserror (); log_error (_("trustdb: lseek failed: %s\n"), strerror (errno)); return err; } n = read (db_fd, readbuf, TRUST_RECORD_LEN); if (!n) { return gpg_error (GPG_ERR_EOF); } else if (n != TRUST_RECORD_LEN) { err = gpg_error_from_syserror (); log_error (_("trustdb: read failed (n=%d): %s\n"), n, strerror(errno)); return err; } buf = readbuf; } rec->recnum = recnum; rec->dirty = 0; p = buf; rec->rectype = *p++; if (expected && rec->rectype != expected) { log_error ("%lu: read expected rec type %d, got %d\n", recnum, expected, rec->rectype); return gpg_error (GPG_ERR_TRUSTDB); } p++; /* Skip reserved byte. */ switch (rec->rectype) { case 0: /* unused (free) record */ break; case RECTYPE_VER: /* version record */ if (memcmp(buf+1, GPGEXT_GPG, 3)) { log_error (_("%s: not a trustdb file\n"), db_name ); err = gpg_error (GPG_ERR_TRUSTDB); } else { p += 2; /* skip "gpg" */ rec->r.ver.version = *p++; rec->r.ver.marginals = *p++; rec->r.ver.completes = *p++; rec->r.ver.cert_depth = *p++; rec->r.ver.trust_model = *p++; rec->r.ver.min_cert_level = *p++; p += 2; rec->r.ver.created = buf32_to_ulong(p); p += 4; rec->r.ver.nextcheck = buf32_to_ulong(p); p += 4; p += 4; p += 4; rec->r.ver.firstfree = buf32_to_ulong(p); p += 4; p += 4; rec->r.ver.trusthashtbl = buf32_to_ulong(p); if (recnum) { log_error( _("%s: version record with recnum %lu\n"), db_name, (ulong)recnum ); err = gpg_error (GPG_ERR_TRUSTDB); } else if (rec->r.ver.version != 3) { log_error( _("%s: invalid file version %d\n"), db_name, rec->r.ver.version ); err = gpg_error (GPG_ERR_TRUSTDB); } } break; case RECTYPE_FREE: rec->r.free.next = buf32_to_ulong(p); break; case RECTYPE_HTBL: for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) { rec->r.htbl.item[i] = buf32_to_ulong(p); p += 4; } break; case RECTYPE_HLST: rec->r.hlst.next = buf32_to_ulong(p); p += 4; for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { rec->r.hlst.rnum[i] = buf32_to_ulong(p); p += 4; } break; case RECTYPE_TRUST: memcpy (rec->r.trust.fingerprint, p, 20); p+=20; rec->r.trust.ownertrust = *p++; rec->r.trust.depth = *p++; rec->r.trust.min_ownertrust = *p++; p++; rec->r.trust.validlist = buf32_to_ulong(p); break; case RECTYPE_VALID: memcpy (rec->r.valid.namehash, p, 20); p+=20; rec->r.valid.validity = *p++; rec->r.valid.next = buf32_to_ulong(p); p += 4; rec->r.valid.full_count = *p++; rec->r.valid.marginal_count = *p++; break; default: log_error ("%s: invalid record type %d at recnum %lu\n", db_name, rec->rectype, (ulong)recnum); err = gpg_error (GPG_ERR_TRUSTDB); break; } return err; } /* * Write the record from the struct REC. * * Return: 0 on success or an error code. */ int tdbio_write_record (ctrl_t ctrl, TRUSTREC *rec) { byte buf[TRUST_RECORD_LEN]; byte *p; int rc = 0; int i; ulong recnum = rec->recnum; if (db_fd == -1) open_db (); memset (buf, 0, TRUST_RECORD_LEN); p = buf; *p++ = rec->rectype; p++; switch (rec->rectype) { case 0: /* unused record */ break; case RECTYPE_VER: /* version record */ if (recnum) BUG (); memcpy(p-1, GPGEXT_GPG, 3 ); p += 2; *p++ = rec->r.ver.version; *p++ = rec->r.ver.marginals; *p++ = rec->r.ver.completes; *p++ = rec->r.ver.cert_depth; *p++ = rec->r.ver.trust_model; *p++ = rec->r.ver.min_cert_level; p += 2; ulongtobuf(p, rec->r.ver.created); p += 4; ulongtobuf(p, rec->r.ver.nextcheck); p += 4; p += 4; p += 4; ulongtobuf(p, rec->r.ver.firstfree ); p += 4; p += 4; ulongtobuf(p, rec->r.ver.trusthashtbl ); p += 4; break; case RECTYPE_FREE: ulongtobuf(p, rec->r.free.next); p += 4; break; case RECTYPE_HTBL: for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) { ulongtobuf( p, rec->r.htbl.item[i]); p += 4; } break; case RECTYPE_HLST: ulongtobuf( p, rec->r.hlst.next); p += 4; for (i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { ulongtobuf( p, rec->r.hlst.rnum[i]); p += 4; } break; case RECTYPE_TRUST: memcpy (p, rec->r.trust.fingerprint, 20); p += 20; *p++ = rec->r.trust.ownertrust; *p++ = rec->r.trust.depth; *p++ = rec->r.trust.min_ownertrust; p++; ulongtobuf( p, rec->r.trust.validlist); p += 4; break; case RECTYPE_VALID: memcpy (p, rec->r.valid.namehash, 20); p += 20; *p++ = rec->r.valid.validity; ulongtobuf( p, rec->r.valid.next); p += 4; *p++ = rec->r.valid.full_count; *p++ = rec->r.valid.marginal_count; break; default: BUG(); } rc = put_record_into_cache (recnum, buf); if (rc) ; else if (rec->rectype == RECTYPE_TRUST) rc = update_trusthashtbl (ctrl, rec); return rc; } /* * Delete the record at record number RECNUm from the trustdb. * * Return: 0 on success or an error code. */ int tdbio_delete_record (ctrl_t ctrl, ulong recnum) { TRUSTREC vr, rec; int rc; /* Must read the record fist, so we can drop it from the hash tables */ rc = tdbio_read_record (recnum, &rec, 0); if (rc) ; else if (rec.rectype == RECTYPE_TRUST) { rc = drop_from_hashtable (ctrl, get_trusthashrec (ctrl), rec.r.trust.fingerprint, 20, rec.recnum); } if (rc) return rc; /* Now we can change it to a free record. */ rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); rec.recnum = recnum; rec.rectype = RECTYPE_FREE; rec.r.free.next = vr.r.ver.firstfree; vr.r.ver.firstfree = recnum; rc = tdbio_write_record (ctrl, &rec); if (!rc) rc = tdbio_write_record (ctrl, &vr); return rc; } /* * Create a new record and return its record number. */ ulong tdbio_new_recnum (ctrl_t ctrl) { off_t offset; ulong recnum; TRUSTREC vr, rec; int rc; /* Look for unused records. */ rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal( _("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); if (vr.r.ver.firstfree) { recnum = vr.r.ver.firstfree; rc = tdbio_read_record (recnum, &rec, RECTYPE_FREE); if (rc) log_fatal (_("%s: error reading free record: %s\n"), db_name, gpg_strerror (rc)); /* Update dir record. */ vr.r.ver.firstfree = rec.r.free.next; rc = tdbio_write_record (ctrl, &vr); if (rc) log_fatal (_("%s: error writing dir record: %s\n"), db_name, gpg_strerror (rc)); /* Zero out the new record. */ memset (&rec, 0, sizeof rec); rec.rectype = 0; /* Mark as unused record (actually already done my the memset). */ rec.recnum = recnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_fatal (_("%s: failed to zero a record: %s\n"), db_name, gpg_strerror (rc)); } else /* Not found - append a new record. */ { offset = lseek (db_fd, 0, SEEK_END); if (offset == (off_t)(-1)) log_fatal ("trustdb: lseek to end failed: %s\n", strerror (errno)); recnum = offset / TRUST_RECORD_LEN; log_assert (recnum); /* This will never be the first record */ /* We must write a record, so that the next call to this * function returns another recnum. */ memset (&rec, 0, sizeof rec); rec.rectype = 0; /* unused record */ rec.recnum = recnum; rc = 0; if (lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1) { rc = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: lseek failed: %s\n"), recnum, strerror (errno)); } else { int n; n = write (db_fd, &rec, TRUST_RECORD_LEN); if (n != TRUST_RECORD_LEN) { rc = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"), recnum, n, gpg_strerror (rc)); } } if (rc) log_fatal (_("%s: failed to append a record: %s\n"), db_name, gpg_strerror (rc)); } return recnum ; } /* Helper function for tdbio_search_trust_byfpr. */ static int cmp_trec_fpr ( const void *fpr, const TRUSTREC *rec ) { return (rec->rectype == RECTYPE_TRUST && !memcmp (rec->r.trust.fingerprint, fpr, 20)); } /* * Given a 20 byte FINGERPRINT search its trust record and return * that at REC. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ gpg_error_t tdbio_search_trust_byfpr (ctrl_t ctrl, const byte *fingerprint, TRUSTREC *rec) { int rc; /* Locate the trust record using the hash table */ rc = lookup_hashtable (get_trusthashrec (ctrl), fingerprint, 20, cmp_trec_fpr, fingerprint, rec ); return rc; } /* * Given a primary public key object PK search its trust record and * return that at REC. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ gpg_error_t tdbio_search_trust_bypk (ctrl_t ctrl, PKT_public_key *pk, TRUSTREC *rec) { byte fingerprint[MAX_FINGERPRINT_LEN]; size_t fingerlen; fingerprint_from_pk( pk, fingerprint, &fingerlen ); for (; fingerlen < 20; fingerlen++) fingerprint[fingerlen] = 0; return tdbio_search_trust_byfpr (ctrl, fingerprint, rec); } /* * Terminate the process with a message about a corrupted trustdb. */ void tdbio_invalid (void) { log_error (_("Error: The trustdb is corrupted.\n")); how_to_fix_the_trustdb (); g10_exit (2); } diff --git a/g10/tofu.c b/g10/tofu.c index 63f941e51..9cdcfaa61 100644 --- a/g10/tofu.c +++ b/g10/tofu.c @@ -1,4033 +1,4033 @@ /* tofu.c - TOFU trust model. * Copyright (C) 2015, 2016 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* TODO: - Format the fingerprints nicely when printing (similar to gpg --list-keys) */ #include #include #include #include #include #include #include "gpg.h" #include "../common/types.h" #include "../common/logging.h" #include "../common/stringhelp.h" #include "options.h" #include "../common/mbox-util.h" #include "../common/i18n.h" #include "../common/ttyio.h" #include "trustdb.h" #include "../common/mkdir_p.h" #include "gpgsql.h" #include "../common/status.h" #include "tofu.h" #define CONTROL_L ('L' - 'A' + 1) /* Number of days with signed / ecnrypted messages required to * indicate that enough history is available for basic trust. */ #define BASIC_TRUST_THRESHOLD 4 /* Number of days with signed / encrypted messages required to * indicate that a lot of history is available. */ #define FULL_TRUST_THRESHOLD 21 /* A struct with data pertaining to the tofu DB. There is one such struct per session and it is cached in session's ctrl structure. To initialize this or get the current singleton, call opendbs(). There is no need to explicitly release it; cleanup is done when the CTRL object is released. */ struct tofu_dbs_s { sqlite3 *db; char *want_lock_file; time_t want_lock_file_ctime; struct { sqlite3_stmt *savepoint_batch; sqlite3_stmt *savepoint_batch_commit; sqlite3_stmt *record_binding_get_old_policy; sqlite3_stmt *record_binding_update; sqlite3_stmt *get_policy_select_policy_and_conflict; sqlite3_stmt *get_trust_bindings_with_this_email; sqlite3_stmt *get_trust_gather_other_user_ids; sqlite3_stmt *get_trust_gather_signature_stats; sqlite3_stmt *get_trust_gather_encryption_stats; sqlite3_stmt *register_already_seen; sqlite3_stmt *register_signature; sqlite3_stmt *register_encryption; } s; int in_batch_transaction; int in_transaction; time_t batch_update_started; }; #define STRINGIFY(s) STRINGIFY2(s) #define STRINGIFY2(s) #s /* The grouping parameters when collecting signature statistics. */ /* If a message is signed a couple of hours in the future, just assume some clock skew. */ #define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60) /* Days. */ #define TIME_AGO_UNIT_SMALL (24 * 60 * 60) #define TIME_AGO_SMALL_THRESHOLD (7 * TIME_AGO_UNIT_SMALL) /* Months. */ #define TIME_AGO_UNIT_MEDIUM (30 * 24 * 60 * 60) #define TIME_AGO_MEDIUM_THRESHOLD (2 * TIME_AGO_UNIT_MEDIUM) /* Years. */ #define TIME_AGO_UNIT_LARGE (365 * 24 * 60 * 60) #define TIME_AGO_LARGE_THRESHOLD (2 * TIME_AGO_UNIT_LARGE) /* Local prototypes. */ static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch); static char *email_from_user_id (const char *user_id); static int show_statistics (tofu_dbs_t dbs, const char *fingerprint, const char *email, enum tofu_policy policy, estream_t outfp, int only_status_fd, time_t now); const char * tofu_policy_str (enum tofu_policy policy) { switch (policy) { case TOFU_POLICY_NONE: return "none"; case TOFU_POLICY_AUTO: return "auto"; case TOFU_POLICY_GOOD: return "good"; case TOFU_POLICY_UNKNOWN: return "unknown"; case TOFU_POLICY_BAD: return "bad"; case TOFU_POLICY_ASK: return "ask"; default: return "???"; } } /* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level (e.g., TRUST_BAD) in light of the current configuration. */ int tofu_policy_to_trust_level (enum tofu_policy policy) { if (policy == TOFU_POLICY_AUTO) /* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY. */ policy = opt.tofu_default_policy; switch (policy) { case TOFU_POLICY_AUTO: /* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default to marginal trust. */ return TRUST_MARGINAL; case TOFU_POLICY_GOOD: return TRUST_FULLY; case TOFU_POLICY_UNKNOWN: return TRUST_UNKNOWN; case TOFU_POLICY_BAD: return TRUST_NEVER; case TOFU_POLICY_ASK: return TRUST_UNKNOWN; default: log_bug ("Bad value for trust policy: %d\n", opt.tofu_default_policy); return 0; } } /* Start a transaction on DB. If ONLY_BATCH is set, then this will start a batch transaction if we haven't started a batch transaction and one has been requested. */ static gpg_error_t begin_transaction (ctrl_t ctrl, int only_batch) { tofu_dbs_t dbs = ctrl->tofu.dbs; int rc; char *err = NULL; log_assert (dbs); /* If we've been in batch update mode for a while (on average, more * than 500 ms), to prevent starving other gpg processes, we drop * and retake the batch lock. * * Note: gnupg_get_time has a one second resolution, if we wanted a * higher resolution, we could use npth_clock_gettime. */ if (/* No real transactions. */ dbs->in_transaction == 0 /* There is an open batch transaction. */ && dbs->in_batch_transaction /* And some time has gone by since it was started. */ && dbs->batch_update_started != gnupg_get_time ()) { struct stat statbuf; /* If we are in a batch update, then batch updates better have been enabled. */ log_assert (ctrl->tofu.batch_updated_wanted); /* Check if another process wants to run. (We just ignore any * stat failure. A waiter might have to wait a bit longer, but * otherwise there should be no impact.) */ - if (stat (dbs->want_lock_file, &statbuf) == 0 + if (gnupg_stat (dbs->want_lock_file, &statbuf) == 0 && statbuf.st_ctime != dbs->want_lock_file_ctime) { end_transaction (ctrl, 2); /* Yield to allow another process a chance to run. Note: * testing suggests that anything less than a 100ms tends to * not result in the other process getting the lock. */ gnupg_usleep (100000); } else dbs->batch_update_started = gnupg_get_time (); } if (/* We don't have an open batch transaction. */ !dbs->in_batch_transaction && (/* Batch mode is enabled or we are starting a new transaction. */ ctrl->tofu.batch_updated_wanted || dbs->in_transaction == 0)) { struct stat statbuf; /* We are in batch mode, but we don't have an open batch * transaction. Since the batch save point must be the outer * save point, it must be taken before the inner save point. */ log_assert (dbs->in_transaction == 0); rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch, NULL, NULL, &err, "begin immediate transaction;", GPGSQL_ARG_END); if (rc) { log_error (_("error beginning transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } dbs->in_batch_transaction = 1; dbs->batch_update_started = gnupg_get_time (); - if (stat (dbs->want_lock_file, &statbuf) == 0) + if (gnupg_stat (dbs->want_lock_file, &statbuf) == 0) dbs->want_lock_file_ctime = statbuf.st_ctime; } if (only_batch) return 0; log_assert (dbs->in_transaction >= 0); dbs->in_transaction ++; rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, "savepoint inner%d;", dbs->in_transaction); if (rc) { log_error (_("error beginning transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } /* Commit a transaction. If ONLY_BATCH is 1, then this only ends the * batch transaction if we have left batch mode. If ONLY_BATCH is 2, * this commits any open batch transaction even if we are still in * batch mode. */ static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch) { tofu_dbs_t dbs = ctrl->tofu.dbs; int rc; char *err = NULL; if (only_batch || (! only_batch && dbs->in_transaction == 1)) { if (!dbs) return 0; /* Shortcut to allow for easier cleanup code. */ /* If we are releasing the batch transaction, then we better not be in a normal transaction. */ if (only_batch) log_assert (dbs->in_transaction == 0); if (/* Batch mode disabled? */ (!ctrl->tofu.batch_updated_wanted || only_batch == 2) /* But, we still have an open batch transaction? */ && dbs->in_batch_transaction) { /* The batch transaction is still in open, but we've left * batch mode. */ dbs->in_batch_transaction = 0; dbs->in_transaction = 0; rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch_commit, NULL, NULL, &err, "commit transaction;", GPGSQL_ARG_END); if (rc) { log_error (_("error committing transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } if (only_batch) return 0; } log_assert (dbs); log_assert (dbs->in_transaction > 0); rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, "release inner%d;", dbs->in_transaction); dbs->in_transaction --; if (rc) { log_error (_("error committing transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } static gpg_error_t rollback_transaction (ctrl_t ctrl) { tofu_dbs_t dbs = ctrl->tofu.dbs; int rc; char *err = NULL; log_assert (dbs); log_assert (dbs->in_transaction > 0); /* Be careful to not undo any progress made by closed transactions in batch mode. */ rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, "rollback to inner%d;", dbs->in_transaction); dbs->in_transaction --; if (rc) { log_error (_("error rolling back transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } void tofu_begin_batch_update (ctrl_t ctrl) { ctrl->tofu.batch_updated_wanted ++; } void tofu_end_batch_update (ctrl_t ctrl) { log_assert (ctrl->tofu.batch_updated_wanted > 0); ctrl->tofu.batch_updated_wanted --; end_transaction (ctrl, 1); } /* Suspend any extant batch transaction (it is safe to call this even no batch transaction has been started). Note: you cannot suspend a batch transaction if you are in a normal transaction. The batch transaction can be resumed explicitly by calling tofu_resume_batch_transaction or implicitly by starting a normal transaction. */ static void tofu_suspend_batch_transaction (ctrl_t ctrl) { end_transaction (ctrl, 2); } /* Resume a batch transaction if there is no extant batch transaction and one has been requested using tofu_begin_batch_transaction. */ static void tofu_resume_batch_transaction (ctrl_t ctrl) { begin_transaction (ctrl, 1); } /* Wrapper around strtol which prints a warning in case of a * conversion error. On success the converted value is stored at * R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE * and an error code is returned. */ static gpg_error_t string_to_long (long *r_value, const char *string, long fallback, int line) { gpg_error_t err; char *tail = NULL; gpg_err_set_errno (0); *r_value = strtol (string, &tail, 0); if (errno || !(!strcmp (tail, ".0") || !*tail)) { err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA); log_debug ("%s:%d: strtol failed for TOFU DB data; returned string" " (string='%.10s%s'; tail='%.10s%s'): %s\n", __FILE__, line, string, string && strlen(string) > 10 ? "..." : "", tail, tail && strlen(tail) > 10 ? "..." : "", gpg_strerror (err)); *r_value = fallback; } else err = 0; return err; } /* Wrapper around strtoul which prints a warning in case of a * conversion error. On success the converted value is stored at * R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE * and an error code is returned. */ static gpg_error_t string_to_ulong (unsigned long *r_value, const char *string, unsigned long fallback, int line) { gpg_error_t err; char *tail = NULL; gpg_err_set_errno (0); *r_value = strtoul (string, &tail, 0); if (errno || !(!strcmp (tail, ".0") || !*tail)) { err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA); log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string" " (string='%.10s%s'; tail='%.10s%s'): %s\n", __FILE__, line, string, string && strlen(string) > 10 ? "..." : "", tail, tail && strlen(tail) > 10 ? "..." : "", gpg_strerror (err)); *r_value = fallback; } else err = 0; return err; } /* Collect results of a select count (*) ...; style query. Aborts if the argument is not a valid integer (or real of the form X.0). */ static int get_single_unsigned_long_cb (void *cookie, int argc, char **argv, char **azColName) { unsigned long int *count = cookie; (void) azColName; log_assert (argc == 1); if (string_to_ulong (count, argv[0], 0, __LINE__)) return 1; /* Abort. */ return 0; } static int get_single_unsigned_long_cb2 (void *cookie, int argc, char **argv, char **azColName, sqlite3_stmt *stmt) { (void) stmt; return get_single_unsigned_long_cb (cookie, argc, argv, azColName); } /* We expect a single integer column whose name is "version". COOKIE must point to an int. This function always aborts. On error or a if the version is bad, sets *VERSION to -1. */ static int version_check_cb (void *cookie, int argc, char **argv, char **azColName) { int *version = cookie; if (argc != 1 || strcmp (azColName[0], "version") != 0) { *version = -1; return 1; } if (strcmp (argv[0], "1") == 0) *version = 1; else { log_error (_("unsupported TOFU database version: %s\n"), argv[0]); *version = -1; } /* Don't run again. */ return 1; } static int check_utks (sqlite3 *db) { int rc; char *err = NULL; struct key_item *utks; struct key_item *ki; int utk_count; char *utks_string = NULL; char keyid_str[16+1]; long utks_unchanged = 0; /* An early version of the v1 format did not include the list of * known ultimately trusted keys. * * This list is used to detect when the set of ultimately trusted * keys changes. We need to detect this to invalidate the effective * policy, which can change if an ultimately trusted key is added or * removed. */ rc = sqlite3_exec (db, "create table if not exists ultimately_trusted_keys" " (keyid);\n", NULL, NULL, &err); if (rc) { log_error ("error creating 'ultimately_trusted_keys' TOFU table: %s\n", err); sqlite3_free (err); goto out; } utks = tdb_utks (); for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++) ; if (utk_count) { /* Build a list of keyids of the form "XXX","YYY","ZZZ". */ int len = (1 + 16 + 1 + 1) * utk_count; int o = 0; utks_string = xmalloc (len); *utks_string = 0; for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++) { utks_string[o ++] = '\''; format_keyid (ki->kid, KF_LONG, keyid_str, sizeof (keyid_str)); memcpy (&utks_string[o], keyid_str, 16); o += 16; utks_string[o ++] = '\''; utks_string[o ++] = ','; } utks_string[o - 1] = 0; log_assert (o == len); } rc = gpgsql_exec_printf (db, get_single_unsigned_long_cb, &utks_unchanged, &err, "select" /* Removed UTKs? (Known UTKs in current UTKs.) */ " ((select count(*) from ultimately_trusted_keys" " where (keyid in (%s))) == %d)" " and" /* New UTKs? */ " ((select count(*) from ultimately_trusted_keys" " where keyid not in (%s)) == 0);", utks_string ? utks_string : "", utk_count, utks_string ? utks_string : ""); xfree (utks_string); if (rc) { log_error (_("TOFU DB error")); print_further_info ("checking if ultimately trusted keys changed: %s", err); sqlite3_free (err); goto out; } if (utks_unchanged) goto out; if (DBG_TRUST) log_debug ("TOFU: ultimately trusted keys changed.\n"); /* Given that the set of ultimately trusted keys * changed, clear any cached policies. */ rc = gpgsql_exec_printf (db, NULL, NULL, &err, "update bindings set effective_policy = %d;", TOFU_POLICY_NONE); if (rc) { log_error (_("TOFU DB error")); print_further_info ("clearing cached policies: %s", err); sqlite3_free (err); goto out; } /* Now, update the UTK table. */ rc = sqlite3_exec (db, "drop table ultimately_trusted_keys;", NULL, NULL, &err); if (rc) { log_error (_("TOFU DB error")); print_further_info ("dropping ultimately_trusted_keys: %s", err); sqlite3_free (err); goto out; } rc = sqlite3_exec (db, "create table if not exists" " ultimately_trusted_keys (keyid);\n", NULL, NULL, &err); if (rc) { log_error (_("TOFU DB error")); print_further_info ("creating ultimately_trusted_keys: %s", err); sqlite3_free (err); goto out; } for (ki = utks; ki; ki = ki->next) { format_keyid (ki->kid, KF_LONG, keyid_str, sizeof (keyid_str)); rc = gpgsql_exec_printf (db, NULL, NULL, &err, "insert into ultimately_trusted_keys values ('%s');", keyid_str); if (rc) { log_error (_("TOFU DB error")); print_further_info ("updating ultimately_trusted_keys: %s", err); sqlite3_free (err); goto out; } } out: return rc; } /* If the DB is new, initialize it. Otherwise, check the DB's version. Return 0 if the database is okay and 1 otherwise. */ static int initdb (sqlite3 *db) { char *err = NULL; int rc; unsigned long int count; int version = -1; rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err); if (rc) { log_error (_("error beginning transaction on TOFU database: %s\n"), err); sqlite3_free (err); return 1; } /* If the DB has no tables, then assume this is a new DB that needs to be initialized. */ rc = sqlite3_exec (db, "select count(*) from sqlite_master where type='table';", get_single_unsigned_long_cb, &count, &err); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("query available tables"); sqlite3_free (err); goto out; } else if (count != 0) /* Assume that the DB is already initialized. Make sure the version is okay. */ { rc = sqlite3_exec (db, "select version from version;", version_check_cb, &version, &err); if (rc == SQLITE_ABORT && version == 1) /* Happy, happy, joy, joy. */ { sqlite3_free (err); rc = 0; goto out; } else if (rc == SQLITE_ABORT && version == -1) /* Unsupported version. */ { /* An error message was already displayed. */ sqlite3_free (err); goto out; } else if (rc) /* Some error. */ { log_error (_("error determining TOFU database's version: %s\n"), err); sqlite3_free (err); goto out; } else { /* Unexpected success. This can only happen if there are no rows. (select returned 0, but expected ABORT.) */ log_error (_("error determining TOFU database's version: %s\n"), gpg_strerror (GPG_ERR_NO_DATA)); rc = 1; goto out; } } /* Create the version table. */ rc = sqlite3_exec (db, "create table version (version INTEGER);", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database: %s\n"), err); print_further_info ("create version"); sqlite3_free (err); goto out; } /* Initialize the version table, which contains a single integer value. */ rc = sqlite3_exec (db, "insert into version values (1);", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database: %s\n"), err); print_further_info ("insert version"); sqlite3_free (err); goto out; } /* The list of bindings and auxiliary data. * * OID is a unique ID identifying this binding (and used by the * signatures table, see below). Note: OIDs will never be * reused. * * FINGERPRINT: The key's fingerprint. * * EMAIL: The normalized email address. * * USER_ID: The unmodified user id from which EMAIL was extracted. * * TIME: The time this binding was first observed. * * POLICY: The trust policy (TOFU_POLICY_BAD, etc. as an integer). * * CONFLICT is either NULL or a fingerprint. Assume that we have * a binding <0xdeadbeef, foo@example.com> and then we observe * <0xbaddecaf, foo@example.com>. There two bindings conflict * (they have the same email address). When we observe the * latter binding, we warn the user about the conflict and ask * for a policy decision about the new binding. We also change * the old binding's policy to ask if it was auto. So that we * know why this occurred, we also set conflict to 0xbaddecaf. */ rc = gpgsql_exec_printf (db, NULL, NULL, &err, "create table bindings\n" " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n" " fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,\n" " policy INTEGER CHECK (policy in (%d, %d, %d, %d, %d)),\n" " conflict STRING,\n" " unique (fingerprint, email));\n" "create index bindings_fingerprint_email\n" " on bindings (fingerprint, email);\n" "create index bindings_email on bindings (email);\n", TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK); if (rc) { log_error (_("error initializing TOFU database: %s\n"), err); print_further_info ("create bindings"); sqlite3_free (err); goto out; } /* The signatures that we have observed. * * BINDING refers to a record in the bindings table, which * describes the binding (i.e., this is a foreign key that * references bindings.oid). * * SIG_DIGEST is the digest stored in the signature. * * SIG_TIME is the timestamp stored in the signature. * * ORIGIN is a free-form string that describes who fed this * signature to GnuPG (e.g., email:claws). * * TIME is the time this signature was registered. */ rc = sqlite3_exec (db, "create table signatures " " (binding INTEGER NOT NULL, sig_digest TEXT," " origin TEXT, sig_time INTEGER, time INTEGER," " primary key (binding, sig_digest, origin));", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database: %s\n"), err); print_further_info ("create signatures"); sqlite3_free (err); goto out; } out: if (! rc) { /* Early version of the v1 format did not include the encryption table. Add it. */ rc = sqlite3_exec (db, "create table if not exists encryptions" " (binding INTEGER NOT NULL," " time INTEGER);" "create index if not exists encryptions_binding" " on encryptions (binding);\n", NULL, NULL, &err); if (rc) { log_error ("error creating 'encryptions' TOFU table: %s\n", err); sqlite3_free (err); } } if (! rc) { /* The effective policy for a binding. If a key is ultimately * trusted, then the effective policy of all of its bindings is * good. Likewise if a key is signed by an ultimately trusted * key, etc. If the effective policy is NONE, then we need to * recompute the effective policy. Otherwise, the effective * policy is considered to be up to date, i.e., effective_policy * is a cache of the computed policy. */ rc = gpgsql_exec_printf (db, NULL, NULL, &err, "alter table bindings" " add column effective_policy INTEGER" " DEFAULT %d" " CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));", TOFU_POLICY_NONE, TOFU_POLICY_NONE, TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK); if (rc) { if (rc == SQLITE_ERROR) /* Almost certainly "duplicate column name", which we can * safely ignore. */ rc = 0; else log_error ("adding column effective_policy to bindings DB: %s\n", err); sqlite3_free (err); } } if (! rc) rc = check_utks (db); if (rc) { rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err); if (rc) { log_error (_("error rolling back transaction on TOFU database: %s\n"), err); sqlite3_free (err); } return 1; } else { rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err); if (rc) { log_error (_("error committing transaction on TOFU database: %s\n"), err); sqlite3_free (err); return 1; } return 0; } } static int busy_handler (void *cookie, int call_count) { ctrl_t ctrl = cookie; tofu_dbs_t dbs = ctrl->tofu.dbs; (void) call_count; /* Update the want-lock-file time stamp (specifically, the ctime) so * that the current owner knows that we (well, someone) want the * lock. */ if (dbs) { /* Note: we don't fail if we can't create the lock file: this * process will have to wait a bit longer, but otherwise nothing * horrible should happen. */ estream_t fp; fp = es_fopen (dbs->want_lock_file, "w"); if (! fp) log_debug ("TOFU: Error opening '%s': %s\n", dbs->want_lock_file, strerror (errno)); else es_fclose (fp); } /* Call again. */ return 1; } /* Create a new DB handle. Returns NULL on error. */ /* FIXME: Change to return an error code for better reporting by the caller. */ static tofu_dbs_t opendbs (ctrl_t ctrl) { char *filename; sqlite3 *db; int rc; if (!ctrl->tofu.dbs) { filename = make_filename (gnupg_homedir (), "tofu.db", NULL); rc = sqlite3_open (filename, &db); if (rc) { log_error (_("error opening TOFU database '%s': %s\n"), filename, sqlite3_errmsg (db)); /* Even if an error occurs, DB is guaranteed to be valid. */ sqlite3_close (db); db = NULL; } /* If a DB is locked wait up to 5 seconds for the lock to be cleared before failing. */ if (db) { sqlite3_busy_timeout (db, 5 * 1000); sqlite3_busy_handler (db, busy_handler, ctrl); } if (db && initdb (db)) { sqlite3_close (db); db = NULL; } if (db) { ctrl->tofu.dbs = xmalloc_clear (sizeof *ctrl->tofu.dbs); ctrl->tofu.dbs->db = db; ctrl->tofu.dbs->want_lock_file = xasprintf ("%s-want-lock", filename); } xfree (filename); } else log_assert (ctrl->tofu.dbs->db); return ctrl->tofu.dbs; } /* Release all of the resources associated with the DB handle. */ void tofu_closedbs (ctrl_t ctrl) { tofu_dbs_t dbs; sqlite3_stmt **statements; dbs = ctrl->tofu.dbs; if (!dbs) return; /* Not initialized. */ log_assert (dbs->in_transaction == 0); end_transaction (ctrl, 2); /* Arghh, that is a surprising use of the struct. */ for (statements = (void *) &dbs->s; (void *) statements < (void *) &(&dbs->s)[1]; statements ++) sqlite3_finalize (*statements); sqlite3_close (dbs->db); xfree (dbs->want_lock_file); xfree (dbs); ctrl->tofu.dbs = NULL; } /* Collect results of a select min (foo) ...; style query. Aborts if the argument is not a valid integer (or real of the form X.0). */ static int get_single_long_cb (void *cookie, int argc, char **argv, char **azColName) { long *count = cookie; (void) azColName; log_assert (argc == 1); if (string_to_long (count, argv[0], 0, __LINE__)) return 1; /* Abort. */ return 0; } static int get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName, sqlite3_stmt *stmt) { (void) stmt; return get_single_long_cb (cookie, argc, argv, azColName); } /* Record (or update) a trust policy about a (possibly new) binding. If SHOW_OLD is set, the binding's old policy is displayed. */ static gpg_error_t record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email, const char *user_id, enum tofu_policy policy, enum tofu_policy effective_policy, const char *conflict, int set_conflict, int show_old, time_t now) { char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0); gpg_error_t rc; char *err = NULL; if (! (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK)) log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy); if (DBG_TRUST || show_old) { /* Get the old policy. Since this is just for informational * purposes, there is no need to start a transaction or to die * if there is a failure. */ /* policy_old needs to be a long and not an enum tofu_policy, because we pass it by reference to get_single_long_cb2, which expects a long. */ long policy_old = TOFU_POLICY_NONE; rc = gpgsql_stepx (dbs->db, &dbs->s.record_binding_get_old_policy, get_single_long_cb2, &policy_old, &err, "select policy from bindings where fingerprint = ? and email = ?", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_END); if (rc) { log_debug ("TOFU: Error reading from binding database" " (reading policy for ): %s\n", fingerprint, email, err); sqlite3_free (err); } if (policy_old != TOFU_POLICY_NONE) (show_old ? log_info : log_debug) ("Changing TOFU trust policy for binding" " from %s to %s.\n", fingerprint, show_old ? user_id : email, tofu_policy_str (policy_old), tofu_policy_str (policy)); else (show_old ? log_info : log_debug) ("Setting TOFU trust policy for new binding" " to %s.\n", fingerprint, show_old ? user_id : email, tofu_policy_str (policy)); } if (opt.dry_run) { log_info ("TOFU database update skipped due to --dry-run\n"); rc = 0; goto leave; } rc = gpgsql_stepx (dbs->db, &dbs->s.record_binding_update, NULL, NULL, &err, "insert or replace into bindings\n" " (oid, fingerprint, email, user_id, time," " policy, conflict, effective_policy)\n" " values (\n" /* If we don't explicitly reuse the OID, then SQLite will * reallocate a new one. We just need to search for the OID * based on the fingerprint and email since they are unique. */ " (select oid from bindings where fingerprint = ? and email = ?),\n" " ?, ?, ?, ?, ?," /* If SET_CONFLICT is 0, then preserve conflict's current value. */ " case ?" " when 0 then" " (select conflict from bindings where fingerprint = ? and email = ?)" " else ?" " end," " ?);", /* oid subquery. */ GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, /* values 2 through 6. */ GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, user_id, GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_INT, (int) policy, /* conflict subquery. */ GPGSQL_ARG_INT, set_conflict ? 1 : 0, GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, conflict ? conflict : "", GPGSQL_ARG_INT, (int) effective_policy, GPGSQL_ARG_END); if (rc) { log_error (_("error updating TOFU database: %s\n"), err); print_further_info (" insert bindings = %s", fingerprint, email, tofu_policy_str (policy)); sqlite3_free (err); goto leave; } leave: xfree (fingerprint_pp); return rc; } /* Collect the strings returned by a query in a simple string list. Any NULL values are converted to the empty string. If a result has 3 rows and each row contains two columns, then the results are added to the list as follows (the value is parentheses is the 1-based index in the final list): row 1, col 2 (6) row 1, col 1 (5) row 2, col 2 (4) row 2, col 1 (3) row 3, col 2 (2) row 3, col 1 (1) This is because add_to_strlist pushes the results onto the front of the list. The end result is that the rows are backwards, but the columns are in the expected order. */ static int strings_collect_cb (void *cookie, int argc, char **argv, char **azColName) { int i; strlist_t *strlist = cookie; (void) azColName; for (i = argc - 1; i >= 0; i --) add_to_strlist (strlist, argv[i] ? argv[i] : ""); return 0; } static int strings_collect_cb2 (void *cookie, int argc, char **argv, char **azColName, sqlite3_stmt *stmt) { (void) stmt; return strings_collect_cb (cookie, argc, argv, azColName); } /* Auxiliary data structure to collect statistics about signatures. */ struct signature_stats { struct signature_stats *next; /* The user-assigned policy for this binding. */ enum tofu_policy policy; /* How long ago the signature was created (rounded to a multiple of TIME_AGO_UNIT_SMALL, etc.). */ long time_ago; /* Number of signatures during this time. */ unsigned long count; /* If the corresponding key/user id has been expired / revoked. */ int is_expired; int is_revoked; /* The key that generated this signature. */ char fingerprint[1]; }; static void signature_stats_free (struct signature_stats *stats) { while (stats) { struct signature_stats *next = stats->next; xfree (stats); stats = next; } } static void signature_stats_prepend (struct signature_stats **statsp, const char *fingerprint, enum tofu_policy policy, long time_ago, unsigned long count) { struct signature_stats *stats = xmalloc_clear (sizeof (*stats) + strlen (fingerprint)); stats->next = *statsp; *statsp = stats; strcpy (stats->fingerprint, fingerprint); stats->policy = policy; stats->time_ago = time_ago; stats->count = count; } /* Process rows that contain the four columns: . */ static int signature_stats_collect_cb (void *cookie, int argc, char **argv, char **azColName, sqlite3_stmt *stmt) { struct signature_stats **statsp = cookie; int i = 0; enum tofu_policy policy; long time_ago; unsigned long count; long along; (void) azColName; (void) stmt; i ++; if (string_to_long (&along, argv[i], 0, __LINE__)) return 1; /* Abort */ policy = along; i ++; if (! argv[i]) time_ago = 0; else { if (string_to_long (&time_ago, argv[i], 0, __LINE__)) return 1; /* Abort. */ } i ++; /* If time_ago is NULL, then we had no messages, but we still have a single row, which count(*) turns into 1. */ if (! argv[i - 1]) count = 0; else { if (string_to_ulong (&count, argv[i], 0, __LINE__)) return 1; /* Abort */ } i ++; log_assert (argc == i); signature_stats_prepend (statsp, argv[0], policy, time_ago, count); return 0; } /* Format the first part of a conflict message and return that as a * malloced string. Returns NULL on error. */ static char * format_conflict_msg_part1 (int policy, strlist_t conflict_set, const char *email) { estream_t fp; char *fingerprint; char *tmpstr, *text; log_assert (conflict_set); fingerprint = conflict_set->d; fp = es_fopenmem (0, "rw,samethread"); if (!fp) log_fatal ("error creating memory stream: %s\n", gpg_strerror (gpg_error_from_syserror())); if (policy == TOFU_POLICY_NONE) { es_fprintf (fp, _("This is the first time the email address \"%s\" is " "being used with key %s."), email, fingerprint); es_fputs (" ", fp); } else if (policy == TOFU_POLICY_ASK && conflict_set->next) { int conflicts = strlist_length (conflict_set); es_fprintf (fp, ngettext("The email address \"%s\" is associated with %d key!", "The email address \"%s\" is associated with %d keys!", conflicts), email, conflicts); if (opt.verbose) es_fprintf (fp, _(" Since this binding's policy was 'auto', it has been " "changed to 'ask'.")); es_fputs (" ", fp); } es_fprintf (fp, _("Please indicate whether this email address should" " be associated with key %s or whether you think someone" " is impersonating \"%s\"."), fingerprint, email); es_fputc ('\n', fp); es_fputc (0, fp); if (es_fclose_snatch (fp, (void **)&tmpstr, NULL)) log_fatal ("error snatching memory stream\n"); text = format_text (tmpstr, 72, 80); es_free (tmpstr); return text; } /* Return 1 if A signed B and B signed A. */ static int cross_sigs (const char *email, kbnode_t a, kbnode_t b) { int i; PKT_public_key *a_pk = a->pkt->pkt.public_key; PKT_public_key *b_pk = b->pkt->pkt.public_key; char a_keyid[33]; char b_keyid[33]; if (DBG_TRUST) { format_keyid (pk_main_keyid (a_pk), KF_LONG, a_keyid, sizeof (a_keyid)); format_keyid (pk_main_keyid (b_pk), KF_LONG, b_keyid, sizeof (b_keyid)); } for (i = 0; i < 2; i ++) { /* See if SIGNER signed SIGNEE. */ kbnode_t signer = i == 0 ? a : b; kbnode_t signee = i == 0 ? b : a; PKT_public_key *signer_pk = signer->pkt->pkt.public_key; u32 *signer_kid = pk_main_keyid (signer_pk); kbnode_t n; int saw_email = 0; /* Iterate over SIGNEE's keyblock and see if there is a valid signature from SIGNER. */ for (n = signee; n; n = n->next) { PKT_signature *sig; if (n->pkt->pkttype == PKT_USER_ID) { if (saw_email) /* We're done: we've processed all signatures on the user id. */ break; else { /* See if this is the matching user id. */ PKT_user_id *user_id = n->pkt->pkt.user_id; char *email2 = email_from_user_id (user_id->name); if (strcmp (email, email2) == 0) saw_email = 1; xfree (email2); } } if (! saw_email) continue; if (n->pkt->pkttype != PKT_SIGNATURE) continue; sig = n->pkt->pkt.signature; if (! (sig->sig_class == 0x10 || sig->sig_class == 0x11 || sig->sig_class == 0x12 || sig->sig_class == 0x13)) /* Not a signature over a user id. */ continue; /* SIG is on SIGNEE's keyblock. If SIG was generated by the signer, then it's a match. */ if (keyid_cmp (sig->keyid, signer_kid) == 0) /* Match! */ break; } if (! n) /* We didn't find a signature from signer over signee. */ { if (DBG_TRUST) log_debug ("No cross sig between %s and %s\n", a_keyid, b_keyid); return 0; } } /* A signed B and B signed A. */ if (DBG_TRUST) log_debug ("Cross sig between %s and %s\n", a_keyid, b_keyid); return 1; } /* Return whether the key was signed by an ultimately trusted key. */ static int signed_by_utk (const char *email, kbnode_t a) { kbnode_t n; int saw_email = 0; for (n = a; n; n = n->next) { PKT_signature *sig; if (n->pkt->pkttype == PKT_USER_ID) { if (saw_email) /* We're done: we've processed all signatures on the user id. */ break; else { /* See if this is the matching user id. */ PKT_user_id *user_id = n->pkt->pkt.user_id; char *email2 = email_from_user_id (user_id->name); if (strcmp (email, email2) == 0) saw_email = 1; xfree (email2); } } if (! saw_email) continue; if (n->pkt->pkttype != PKT_SIGNATURE) continue; sig = n->pkt->pkt.signature; if (! (sig->sig_class == 0x10 || sig->sig_class == 0x11 || sig->sig_class == 0x12 || sig->sig_class == 0x13)) /* Not a signature over a user id. */ continue; /* SIG is on SIGNEE's keyblock. If SIG was generated by the signer, then it's a match. */ if (tdb_keyid_is_utk (sig->keyid)) { /* Match! */ if (DBG_TRUST) log_debug ("TOFU: %s is signed by an ultimately trusted key.\n", pk_keyid_str (a->pkt->pkt.public_key)); return 1; } } if (DBG_TRUST) log_debug ("TOFU: %s is NOT signed by an ultimately trusted key.\n", pk_keyid_str (a->pkt->pkt.public_key)); return 0; } enum { BINDING_NEW = 1 << 0, BINDING_CONFLICT = 1 << 1, BINDING_EXPIRED = 1 << 2, BINDING_REVOKED = 1 << 3 }; /* Ask the user about the binding. There are three ways we could end * up here: * * - This is a new binding and there is a conflict * (policy == TOFU_POLICY_NONE && conflict_set_count > 1), * * - This is a new binding and opt.tofu_default_policy is set to * ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy == * TOFU_POLICY_ASK), or, * * - The policy is ask (the user deferred last time) (policy == * TOFU_POLICY_ASK). * * Note: this function must not be called while in a transaction! * * CONFLICT_SET includes all of the conflicting bindings * with FINGERPRINT first. FLAGS is a bit-wise or of * BINDING_NEW, etc. */ static void ask_about_binding (ctrl_t ctrl, enum tofu_policy *policy, int *trust_level, strlist_t conflict_set, const char *fingerprint, const char *email, const char *user_id, time_t now) { tofu_dbs_t dbs; strlist_t iter; int conflict_set_count = strlist_length (conflict_set); char *sqerr = NULL; int rc; estream_t fp; strlist_t other_user_ids = NULL; struct signature_stats *stats = NULL; struct signature_stats *stats_iter = NULL; char *prompt = NULL; const char *choices; dbs = ctrl->tofu.dbs; log_assert (dbs); log_assert (dbs->in_transaction == 0); fp = es_fopenmem (0, "rw,samethread"); if (!fp) log_fatal ("error creating memory stream: %s\n", gpg_strerror (gpg_error_from_syserror())); { char *text = format_conflict_msg_part1 (*policy, conflict_set, email); if (!text) /* FIXME: Return the error all the way up. */ log_fatal ("format failed: %s\n", gpg_strerror (gpg_error_from_syserror())); es_fputs (text, fp); es_fputc ('\n', fp); xfree (text); } begin_transaction (ctrl, 0); /* Find other user ids associated with this key and whether the * bindings are marked as good or bad. */ rc = gpgsql_stepx (dbs->db, &dbs->s.get_trust_gather_other_user_ids, strings_collect_cb2, &other_user_ids, &sqerr, "select user_id, policy from bindings where fingerprint = ?;", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END); if (rc) { log_error (_("error gathering other user IDs: %s\n"), sqerr); sqlite3_free (sqerr); sqerr = NULL; rc = gpg_error (GPG_ERR_GENERAL); } if (other_user_ids) { strlist_t strlist_iter; es_fprintf (fp, _("This key's user IDs:\n")); for (strlist_iter = other_user_ids; strlist_iter; strlist_iter = strlist_iter->next) { char *other_user_id = strlist_iter->d; char *other_thing; enum tofu_policy other_policy; log_assert (strlist_iter->next); strlist_iter = strlist_iter->next; other_thing = strlist_iter->d; other_policy = atoi (other_thing); es_fprintf (fp, " %s (", other_user_id); es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy)); es_fprintf (fp, ")\n"); } es_fprintf (fp, "\n"); free_strlist (other_user_ids); } /* Get the stats for all the keys in CONFLICT_SET. */ strlist_rev (&conflict_set); for (iter = conflict_set; iter && ! rc; iter = iter->next) { #define STATS_SQL(table, time, sign) \ "select fingerprint, policy, time_ago, count(*)\n" \ " from\n" \ " (select bindings.*,\n" \ " "sign" case\n" \ " when delta ISNULL then 1\n" \ /* From the future (but if its just a couple of hours in the \ * future don't turn it into a warning)? Or should we use \ * small, medium or large units? (Note: whatever we do, we \ * keep the value in seconds. Then when we group, everything \ * that rounds to the same number of seconds is grouped.) */ \ " when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \ " when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \ " then 3\n" \ " when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \ " then 4\n" \ " when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \ " then 5\n" \ " else 6\n" \ " end time_ago,\n" \ " delta time_ago_raw\n" \ " from bindings\n" \ " left join\n" \ " (select *,\n" \ " cast(? - " time " as real) delta\n" \ " from " table ") ss\n" \ " on ss.binding = bindings.oid)\n" \ " where email = ? and fingerprint = ?\n" \ " group by time_ago\n" \ /* Make sure the current key is first. */ \ " order by time_ago desc;\n" /* Use the time when we saw the signature, not when the signature was created as that can be forged. */ rc = gpgsql_stepx (dbs->db, &dbs->s.get_trust_gather_signature_stats, signature_stats_collect_cb, &stats, &sqerr, STATS_SQL ("signatures", "time", ""), GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, iter->d, GPGSQL_ARG_END); if (rc) { rc = gpg_error (GPG_ERR_GENERAL); break; } if (!stats || strcmp (iter->d, stats->fingerprint) != 0) /* No stats for this binding. Add a dummy entry. */ signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1); rc = gpgsql_stepx (dbs->db, &dbs->s.get_trust_gather_encryption_stats, signature_stats_collect_cb, &stats, &sqerr, STATS_SQL ("encryptions", "time", "-"), GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, iter->d, GPGSQL_ARG_END); if (rc) { rc = gpg_error (GPG_ERR_GENERAL); break; } #undef STATS_SQL if (!stats || strcmp (iter->d, stats->fingerprint) != 0 || stats->time_ago > 0) /* No stats for this binding. Add a dummy entry. */ signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1); } end_transaction (ctrl, 0); strlist_rev (&conflict_set); if (rc) { strlist_t strlist_iter; log_error (_("error gathering signature stats: %s\n"), sqerr); sqlite3_free (sqerr); sqerr = NULL; es_fprintf (fp, ngettext("The email address \"%s\" is" " associated with %d key:\n", "The email address \"%s\" is" " associated with %d keys:\n", conflict_set_count), email, conflict_set_count); for (strlist_iter = conflict_set; strlist_iter; strlist_iter = strlist_iter->next) es_fprintf (fp, " %s\n", strlist_iter->d); } else { char *key = NULL; strlist_t binding; int seen_in_past = 0; int encrypted = 1; es_fprintf (fp, _("Statistics for keys" " with the email address \"%s\":\n"), email); for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next) { #if 0 log_debug ("%s: time_ago: %ld; count: %ld\n", stats_iter->fingerprint, stats_iter->time_ago, stats_iter->count); #endif if (stats_iter->time_ago > 0 && encrypted) { /* We've change from the encrypted stats to the verified * stats. Reset SEEN_IN_PAST. */ encrypted = 0; seen_in_past = 0; } if (! key || strcmp (key, stats_iter->fingerprint)) { int this_key; char *key_pp; key = stats_iter->fingerprint; this_key = strcmp (key, fingerprint) == 0; key_pp = format_hexfingerprint (key, NULL, 0); es_fprintf (fp, " %s (", key_pp); /* Find the associated binding. */ for (binding = conflict_set; binding; binding = binding->next) if (strcmp (key, binding->d) == 0) break; log_assert (binding); if ((binding->flags & BINDING_REVOKED)) { es_fprintf (fp, _("revoked")); es_fprintf (fp, ", "); } else if ((binding->flags & BINDING_EXPIRED)) { es_fprintf (fp, _("expired")); es_fprintf (fp, ", "); } if (this_key) es_fprintf (fp, _("this key")); else es_fprintf (fp, _("policy: %s"), tofu_policy_str (stats_iter->policy)); es_fputs ("):\n", fp); xfree (key_pp); seen_in_past = 0; show_statistics (dbs, stats_iter->fingerprint, email, TOFU_POLICY_ASK, NULL, 1, now); } if (labs(stats_iter->time_ago) == 1) { /* The 1 in this case is the NULL entry. */ log_assert (stats_iter->count == 1); stats_iter->count = 0; } seen_in_past += stats_iter->count; es_fputs (" ", fp); if (!stats_iter->count) { if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Verified %d message.", "Verified %d messages.", seen_in_past), seen_in_past); else es_fprintf (fp, ngettext("Encrypted %d message.", "Encrypted %d messages.", seen_in_past), seen_in_past); } else if (labs(stats_iter->time_ago) == 2) { if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Verified %d message in the future.", "Verified %d messages in the future.", seen_in_past), seen_in_past); else es_fprintf (fp, ngettext("Encrypted %d message in the future.", "Encrypted %d messages in the future.", seen_in_past), seen_in_past); /* Reset it. */ seen_in_past = 0; } else { if (labs(stats_iter->time_ago) == 3) { int days = 1 + stats_iter->time_ago / TIME_AGO_UNIT_SMALL; if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Messages verified over the past %d day: %d.", "Messages verified over the past %d days: %d.", days), days, seen_in_past); else es_fprintf (fp, ngettext("Messages encrypted over the past %d day: %d.", "Messages encrypted over the past %d days: %d.", days), days, seen_in_past); } else if (labs(stats_iter->time_ago) == 4) { int months = 1 + stats_iter->time_ago / TIME_AGO_UNIT_MEDIUM; if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Messages verified over the past %d month: %d.", "Messages verified over the past %d months: %d.", months), months, seen_in_past); else es_fprintf (fp, ngettext("Messages encrypted over the past %d month: %d.", "Messages encrypted over the past %d months: %d.", months), months, seen_in_past); } else if (labs(stats_iter->time_ago) == 5) { int years = 1 + stats_iter->time_ago / TIME_AGO_UNIT_LARGE; if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Messages verified over the past %d year: %d.", "Messages verified over the past %d years: %d.", years), years, seen_in_past); else es_fprintf (fp, ngettext("Messages encrypted over the past %d year: %d.", "Messages encrypted over the past %d years: %d.", years), years, seen_in_past); } else if (labs(stats_iter->time_ago) == 6) { if (stats_iter->time_ago > 0) es_fprintf (fp, _("Messages verified in the past: %d."), seen_in_past); else es_fprintf (fp, _("Messages encrypted in the past: %d."), seen_in_past); } else log_assert (! "Broken SQL.\n"); } es_fputs ("\n", fp); } } if (conflict_set_count > 1 || (conflict_set->flags & BINDING_CONFLICT)) { /* This is a conflict. */ /* TRANSLATORS: Please translate the text found in the source * file below. We don't directly internationalize that text so * that we can tweak it without breaking translations. */ const char *text = _("TOFU detected a binding conflict"); char *textbuf; if (!strcmp (text, "TOFU detected a binding conflict")) { /* No translation. Use the English text. */ text = "Normally, an email address is associated with a single key. " "However, people sometimes generate a new key if " "their key is too old or they think it might be compromised. " "Alternatively, a new key may indicate a man-in-the-middle " "attack! Before accepting this association, you should talk to or " "call the person to make sure this new key is legitimate."; } textbuf = format_text (text, 72, 80); es_fprintf (fp, "\n%s\n", textbuf? textbuf : "[OUT OF CORE!]"); xfree (textbuf); } es_fputc ('\n', fp); /* Add a NUL terminator. */ es_fputc (0, fp); if (es_fclose_snatch (fp, (void **) &prompt, NULL)) log_fatal ("error snatching memory stream\n"); /* I think showing the large message once is sufficient. If we * would move it right before the cpr_get many lines will scroll * away and the user might not realize that he merely entered a * wrong choice (because he does not see that either). As a small * benefit we allow C-L to redisplay everything. */ tty_printf ("%s", prompt); /* Suspend any transaction: it could take a while until the user responds. */ tofu_suspend_batch_transaction (ctrl); while (1) { char *response; /* TRANSLATORS: Two letters (normally the lower and upper case * version of the hotkey) for each of the five choices. If * there is only one choice in your language, repeat it. */ choices = _("gG" "aA" "uU" "rR" "bB"); if (strlen (choices) != 10) log_bug ("Bad TOFU conflict translation! Please report."); response = cpr_get ("tofu.conflict", _("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? ")); trim_spaces (response); cpr_kill_prompt (); if (*response == CONTROL_L) tty_printf ("%s", prompt); else if (!response[0]) /* Default to unknown. Don't save it. */ { tty_printf (_("Defaulting to unknown.\n")); *policy = TOFU_POLICY_UNKNOWN; break; } else if (!response[1]) { char *choice = strchr (choices, *response); if (choice) { int c = ((size_t) choice - (size_t) choices) / 2; switch (c) { case 0: /* Good. */ *policy = TOFU_POLICY_GOOD; *trust_level = tofu_policy_to_trust_level (*policy); break; case 1: /* Accept once. */ *policy = TOFU_POLICY_ASK; *trust_level = tofu_policy_to_trust_level (TOFU_POLICY_GOOD); break; case 2: /* Unknown. */ *policy = TOFU_POLICY_UNKNOWN; *trust_level = tofu_policy_to_trust_level (*policy); break; case 3: /* Reject once. */ *policy = TOFU_POLICY_ASK; *trust_level = tofu_policy_to_trust_level (TOFU_POLICY_BAD); break; case 4: /* Bad. */ *policy = TOFU_POLICY_BAD; *trust_level = tofu_policy_to_trust_level (*policy); break; default: log_bug ("c should be between 0 and 4 but it is %d!", c); } if (record_binding (dbs, fingerprint, email, user_id, *policy, TOFU_POLICY_NONE, NULL, 0, 0, now)) { /* If there's an error registering the * binding, don't save the signature. */ *trust_level = _tofu_GET_TRUST_ERROR; } break; } } xfree (response); } tofu_resume_batch_transaction (ctrl); xfree (prompt); signature_stats_free (stats); } /* Return the set of keys that conflict with the binding (including the binding itself, which will be first in the list). For each returned key also sets BINDING_NEW, etc. */ static strlist_t build_conflict_set (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint, const char *email) { gpg_error_t rc; char *sqerr; strlist_t conflict_set = NULL; int conflict_set_count; strlist_t iter; kbnode_t *kb_all; KEYDB_HANDLE hd; int i; /* Get the fingerprints of any bindings that share the email address * and whether the bindings have a known conflict. * * Note: if the binding in question is in the DB, it will also be * returned. Thus, if the result set is empty, then is a new binding. */ rc = gpgsql_stepx (dbs->db, &dbs->s.get_trust_bindings_with_this_email, strings_collect_cb2, &conflict_set, &sqerr, "select" /* A binding should only appear once, but try not to break in the * case of corruption. */ " fingerprint || case sum(conflict NOTNULL) when 0 then '' else '!' end" " from bindings where email = ?" " group by fingerprint" /* Make sure the current key comes first in the result list (if it is present). */ " order by fingerprint = ? asc, fingerprint desc;", GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END); if (rc) { log_error (_("error reading TOFU database: %s\n"), sqerr); print_further_info ("listing fingerprints"); sqlite3_free (sqerr); rc = gpg_error (GPG_ERR_GENERAL); return NULL; } /* Set BINDING_CONFLICT if the binding has a known conflict. This * allows us to distinguish between bindings where the user * explicitly set the policy to ask and bindings where we set the * policy to ask due to a conflict. */ for (iter = conflict_set; iter; iter = iter->next) { int l = strlen (iter->d); if (!(l == 2 * MAX_FINGERPRINT_LEN || l == 2 * MAX_FINGERPRINT_LEN + 1)) { log_error (_("TOFU db corruption detected.\n")); print_further_info ("fingerprint '%s' is not %d characters long", iter->d, 2 * MAX_FINGERPRINT_LEN); } if (l >= 1 && iter->d[l - 1] == '!') { iter->flags |= BINDING_CONFLICT; /* Remove the !. */ iter->d[l - 1] = 0; } } /* If the current binding has not yet been recorded, add it to the * list. (The order by above ensures that if it is present, it will * be first.) */ if (! (conflict_set && strcmp (conflict_set->d, fingerprint) == 0)) { add_to_strlist (&conflict_set, fingerprint); conflict_set->flags |= BINDING_NEW; } conflict_set_count = strlist_length (conflict_set); /* Eliminate false conflicts. */ if (conflict_set_count == 1) /* We only have a single key. There are no false conflicts to eliminate. But, we do need to set the flags. */ { if (pk->has_expired) conflict_set->flags |= BINDING_EXPIRED; if (pk->flags.revoked) conflict_set->flags |= BINDING_REVOKED; return conflict_set; } /* If two keys have cross signatures, then they are controlled by * the same person and thus are not in conflict. */ kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count); hd = keydb_new (); for (i = 0, iter = conflict_set; i < conflict_set_count; i ++, iter = iter->next) { char *fp = iter->d; KEYDB_SEARCH_DESC desc; kbnode_t kb; PKT_public_key *binding_pk; kbnode_t n; int found_user_id; rc = keydb_search_reset (hd); if (rc) { log_error ("resetting keydb failed: %s\n", gpg_strerror (rc)); continue; } rc = classify_user_id (fp, &desc, 0); if (rc) { log_error (_("error parsing key specification '%s': %s\n"), fp, gpg_strerror (rc)); continue; } rc = keydb_search (hd, &desc, 1, NULL); if (rc) { /* Note: it is entirely possible that we don't have the key corresponding to an entry in the TOFU DB. This can happen if we merge two TOFU DBs, but not the key rings. */ log_info (_("key \"%s\" not found: %s\n"), fp, gpg_strerror (rc)); continue; } rc = keydb_get_keyblock (hd, &kb); if (rc) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc)); print_further_info ("fingerprint: %s", fp); continue; } merge_keys_and_selfsig (ctrl, kb); log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); kb_all[i] = kb; /* Since we have the key block, use this opportunity to figure * out if the binding is expired or revoked. */ binding_pk = kb->pkt->pkt.public_key; /* The binding is always expired/revoked if the key is * expired/revoked. */ if (binding_pk->has_expired) iter->flags |= BINDING_EXPIRED; if (binding_pk->flags.revoked) iter->flags |= BINDING_REVOKED; /* The binding is also expired/revoked if the user id is * expired/revoked. */ n = kb; found_user_id = 0; while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id) { PKT_user_id *user_id2 = n->pkt->pkt.user_id; char *email2; if (user_id2->attrib_data) continue; email2 = email_from_user_id (user_id2->name); if (strcmp (email, email2) == 0) { found_user_id = 1; if (user_id2->flags.revoked) iter->flags |= BINDING_REVOKED; if (user_id2->flags.expired) iter->flags |= BINDING_EXPIRED; } xfree (email2); } if (! found_user_id) { log_info (_("TOFU db corruption detected.\n")); print_further_info ("user id '%s' not on key block '%s'", email, fingerprint); } } keydb_release (hd); /* Now that we have the key blocks, check for cross sigs. */ { int j; strlist_t *prevp; strlist_t iter_next; int *die; log_assert (conflict_set_count > 0); die = xtrycalloc (conflict_set_count, sizeof *die); if (!die) { /*err = gpg_error_from_syserror ();*/ xoutofcore (); /* Fixme: Let the function return an error. */ } for (i = 0; i < conflict_set_count; i ++) { /* Look for cross sigs between this key (i == 0) or a key * that has cross sigs with i == 0 (i.e., transitively) */ if (! (i == 0 || die[i])) continue; for (j = i + 1; j < conflict_set_count; j ++) /* Be careful: we might not have a key block for a key. */ if (kb_all[i] && kb_all[j] && cross_sigs (email, kb_all[i], kb_all[j])) die[j] = 1; } /* Free unconflicting bindings (and all of the key blocks). */ for (iter = conflict_set, prevp = &conflict_set, i = 0; iter; iter = iter_next, i ++) { iter_next = iter->next; release_kbnode (kb_all[i]); if (die[i]) { *prevp = iter_next; iter->next = NULL; free_strlist (iter); conflict_set_count --; } else { prevp = &iter->next; } } /* We shouldn't have removed the head. */ log_assert (conflict_set); log_assert (conflict_set_count >= 1); xfree (die); } xfree (kb_all); if (DBG_TRUST) { log_debug ("binding conflicts:\n", fingerprint, email); for (iter = conflict_set; iter; iter = iter->next) { log_debug (" %s:%s%s%s%s\n", iter->d, (iter->flags & BINDING_NEW) ? " new" : "", (iter->flags & BINDING_CONFLICT) ? " known_conflict" : "", (iter->flags & BINDING_EXPIRED) ? " expired" : "", (iter->flags & BINDING_REVOKED) ? " revoked" : ""); } } return conflict_set; } /* Return the effective policy for the binding * (email has already been normalized). Returns * _tofu_GET_POLICY_ERROR if an error occurs. Returns any conflict * information in *CONFLICT_SETP if CONFLICT_SETP is not NULL and the * returned policy is TOFU_POLICY_ASK (consequently, if there is a * conflict, but the user set the policy to good *CONFLICT_SETP will * empty). Note: as per build_conflict_set, which is used to build * the conflict information, the conflict information includes the * current user id as the first element of the linked list. * * This function registers the binding in the bindings table if it has * not yet been registered. */ static enum tofu_policy get_policy (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint, const char *user_id, const char *email, strlist_t *conflict_setp, time_t now) { int rc; char *err = NULL; strlist_t results = NULL; enum tofu_policy policy = _tofu_GET_POLICY_ERROR; enum tofu_policy effective_policy_orig = TOFU_POLICY_NONE; enum tofu_policy effective_policy = _tofu_GET_POLICY_ERROR; long along; char *conflict_orig = NULL; char *conflict = NULL; strlist_t conflict_set = NULL; int conflict_set_count; /* Check if the binding is known (TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is still TOFU_POLICY_NONE after executing the query, then the result set was empty.) */ rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict, strings_collect_cb2, &results, &err, "select policy, conflict, effective_policy from bindings\n" " where fingerprint = ? and email = ?", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_END); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("reading the policy"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } if (strlist_length (results) == 0) { /* No results. Use the defaults. */ policy = TOFU_POLICY_NONE; effective_policy = TOFU_POLICY_NONE; } else if (strlist_length (results) == 3) { /* Parse and sanity check the results. */ if (string_to_long (&along, results->d, 0, __LINE__)) { log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_BAD_DATA)); print_further_info ("bad value for policy: %s", results->d); goto out; } policy = along; if (! (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK)) { log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_DB_CORRUPTED)); print_further_info ("invalid value for policy (%d)", policy); effective_policy = _tofu_GET_POLICY_ERROR; goto out; } if (*results->next->d) conflict = xstrdup (results->next->d); if (string_to_long (&along, results->next->next->d, 0, __LINE__)) { log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_BAD_DATA)); print_further_info ("bad value for effective policy: %s", results->next->next->d); goto out; } effective_policy = along; if (! (effective_policy == TOFU_POLICY_NONE || effective_policy == TOFU_POLICY_AUTO || effective_policy == TOFU_POLICY_GOOD || effective_policy == TOFU_POLICY_UNKNOWN || effective_policy == TOFU_POLICY_BAD || effective_policy == TOFU_POLICY_ASK)) { log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_DB_CORRUPTED)); print_further_info ("invalid value for effective_policy (%d)", effective_policy); effective_policy = _tofu_GET_POLICY_ERROR; goto out; } } else { /* The result has the wrong form. */ log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_BAD_DATA)); print_further_info ("reading policy: expected 3 columns, got %d\n", strlist_length (results)); goto out; } /* Save the effective policy and conflict so we know if we changed * them. */ effective_policy_orig = effective_policy; conflict_orig = conflict; /* Unless there is a conflict, if the effective policy is cached, * just return it. The reason we don't do this when there is a * conflict is because of the following scenario: assume A and B * conflict and B has signed A's key. Now, later we import A's * signature on B. We need to recheck A, but the signature was on * B, i.e., when B changes, we invalidate B's effective policy, but * we also need to invalidate A's effective policy. Instead, we * assume that conflicts are rare and don't optimize for them, which * would complicate the code. */ if (effective_policy != TOFU_POLICY_NONE && !conflict) goto out; /* If the user explicitly set the policy, then respect that. */ if (policy != TOFU_POLICY_AUTO && policy != TOFU_POLICY_NONE) { effective_policy = policy; goto out; } /* Unless proven wrong, assume the effective policy is 'auto'. */ effective_policy = TOFU_POLICY_AUTO; /* See if the key is ultimately trusted. */ { u32 kid[2]; keyid_from_pk (pk, kid); if (tdb_keyid_is_utk (kid)) { effective_policy = TOFU_POLICY_GOOD; goto out; } } /* See if the key is signed by an ultimately trusted key. */ { int fingerprint_raw_len = strlen (fingerprint) / 2; char fingerprint_raw[20]; int len = 0; if (fingerprint_raw_len != sizeof fingerprint_raw || ((len = hex2bin (fingerprint, fingerprint_raw, fingerprint_raw_len)) != strlen (fingerprint))) { if (DBG_TRUST) log_debug ("TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)\n", fingerprint, strlen (fingerprint), len); } else { int lookup_err; kbnode_t kb; lookup_err = get_pubkey_byfprint (ctrl, NULL, &kb, fingerprint_raw, fingerprint_raw_len); if (lookup_err) { if (DBG_TRUST) log_debug ("TOFU: Looking up %s: %s\n", fingerprint, gpg_strerror (lookup_err)); } else { int is_signed_by_utk = signed_by_utk (email, kb); release_kbnode (kb); if (is_signed_by_utk) { effective_policy = TOFU_POLICY_GOOD; goto out; } } } } /* Check for any conflicts / see if a previously discovered conflict * disappeared. The latter can happen if the conflicting bindings * are now cross signed, for instance. */ conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email); conflict_set_count = strlist_length (conflict_set); if (conflict_set_count == 0) { /* build_conflict_set should always at least return the current binding. Something went wrong. */ effective_policy = _tofu_GET_POLICY_ERROR; goto out; } if (conflict_set_count == 1 && (conflict_set->flags & BINDING_NEW)) { /* We've never observed a binding with this email address and we * have a default policy, which is not to ask the user. */ /* If we've seen this binding, then we've seen this email and * policy couldn't possibly be TOFU_POLICY_NONE. */ log_assert (policy == TOFU_POLICY_NONE); if (DBG_TRUST) log_debug ("TOFU: New binding , no conflict.\n", fingerprint, email); effective_policy = TOFU_POLICY_AUTO; goto out; } if (conflict_set_count == 1 && (conflict_set->flags & BINDING_CONFLICT)) { /* No known conflicts now, but there was a conflict. This means * at some point, there was a conflict and we changed this * binding's policy to ask and set the conflicting key. The * conflict can go away if there is not a cross sig between the * two keys. In this case, just silently clear the conflict and * reset the policy to auto. */ if (DBG_TRUST) log_debug ("TOFU: binding had a conflict, but it's been resolved (probably via cross sig).\n", fingerprint, email); effective_policy = TOFU_POLICY_AUTO; conflict = NULL; goto out; } if (conflict_set_count == 1) { /* No conflicts and never marked as conflicting. */ log_assert (!conflict); effective_policy = TOFU_POLICY_AUTO; goto out; } /* There is a conflicting key. */ log_assert (conflict_set_count > 1); effective_policy = TOFU_POLICY_ASK; conflict = xstrdup (conflict_set->next->d); out: log_assert (policy == _tofu_GET_POLICY_ERROR || policy == TOFU_POLICY_NONE || policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK); /* Everything but NONE. */ log_assert (effective_policy == _tofu_GET_POLICY_ERROR || effective_policy == TOFU_POLICY_AUTO || effective_policy == TOFU_POLICY_GOOD || effective_policy == TOFU_POLICY_UNKNOWN || effective_policy == TOFU_POLICY_BAD || effective_policy == TOFU_POLICY_ASK); if (effective_policy != TOFU_POLICY_ASK && conflict) conflict = NULL; /* If we don't have a record of this binding, its effective policy * changed, or conflict changed, update the DB. */ if (effective_policy != _tofu_GET_POLICY_ERROR && (/* New binding. */ policy == TOFU_POLICY_NONE /* effective_policy changed. */ || effective_policy != effective_policy_orig /* conflict changed. */ || (conflict != conflict_orig && (!conflict || !conflict_orig || strcmp (conflict, conflict_orig) != 0)))) { if (record_binding (dbs, fingerprint, email, user_id, policy == TOFU_POLICY_NONE ? TOFU_POLICY_AUTO : policy, effective_policy, conflict, 1, 0, now) != 0) log_error ("error setting TOFU binding's policy" " to %s\n", tofu_policy_str (policy)); } /* If the caller wants the set of conflicts, return it. */ if (effective_policy == TOFU_POLICY_ASK && conflict_setp) { if (! conflict_set) conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email); *conflict_setp = conflict_set; } else { free_strlist (conflict_set); if (conflict_setp) *conflict_setp = NULL; } xfree (conflict_orig); if (conflict != conflict_orig) xfree (conflict); free_strlist (results); return effective_policy; } /* Return the trust level (TRUST_NEVER, etc.) for the binding * (email is already normalized). If no policy * is registered, returns TOFU_POLICY_NONE. If an error occurs, * returns _tofu_GET_TRUST_ERROR. * * PK is the public key object for FINGERPRINT. * * USER_ID is the unadulterated user id. * * If MAY_ASK is set, then we may interact with the user. This is * necessary if there is a conflict or the binding's policy is * TOFU_POLICY_ASK. In the case of a conflict, we set the new * conflicting binding's policy to TOFU_POLICY_ASK. In either case, * we return TRUST_UNDEFINED. Note: if MAY_ASK is set, then this * function must not be called while in a transaction! */ static enum tofu_policy get_trust (ctrl_t ctrl, PKT_public_key *pk, const char *fingerprint, const char *email, const char *user_id, int may_ask, enum tofu_policy *policyp, strlist_t *conflict_setp, time_t now) { tofu_dbs_t dbs = ctrl->tofu.dbs; int in_transaction = 0; enum tofu_policy policy; int rc; char *sqerr = NULL; strlist_t conflict_set = NULL; int trust_level = TRUST_UNKNOWN; strlist_t iter; log_assert (dbs); if (may_ask) log_assert (dbs->in_transaction == 0); if (opt.batch) may_ask = 0; log_assert (pk_is_primary (pk)); /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust levels. */ log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN && _tofu_GET_TRUST_ERROR != TRUST_EXPIRED && _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED && _tofu_GET_TRUST_ERROR != TRUST_NEVER && _tofu_GET_TRUST_ERROR != TRUST_MARGINAL && _tofu_GET_TRUST_ERROR != TRUST_FULLY && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE); begin_transaction (ctrl, 0); in_transaction = 1; /* We need to call get_policy even if the key is ultimately trusted * to make sure the binding has been registered. */ policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, &conflict_set, now); if (policy == TOFU_POLICY_ASK) /* The conflict set should always contain at least one element: * the current key. */ log_assert (conflict_set); else /* If the policy is not TOFU_POLICY_ASK, then conflict_set will be * NULL. */ log_assert (! conflict_set); /* If the key is ultimately trusted, there is nothing to do. */ { u32 kid[2]; keyid_from_pk (pk, kid); if (tdb_keyid_is_utk (kid)) { trust_level = TRUST_ULTIMATE; policy = TOFU_POLICY_GOOD; goto out; } } if (policy == TOFU_POLICY_AUTO) { policy = opt.tofu_default_policy; if (DBG_TRUST) log_debug ("TOFU: binding 's policy is" " auto (default: %s).\n", fingerprint, email, tofu_policy_str (opt.tofu_default_policy)); if (policy == TOFU_POLICY_ASK) /* The default policy is ASK, but there is no conflict (policy * was 'auto'). In this case, we need to make sure the * conflict set includes at least the current user id. */ { add_to_strlist (&conflict_set, fingerprint); } } switch (policy) { case TOFU_POLICY_AUTO: case TOFU_POLICY_GOOD: case TOFU_POLICY_UNKNOWN: case TOFU_POLICY_BAD: /* The saved judgement is auto -> auto, good, unknown or bad. * We don't need to ask the user anything. */ if (DBG_TRUST) log_debug ("TOFU: Known binding 's policy: %s\n", fingerprint, email, tofu_policy_str (policy)); trust_level = tofu_policy_to_trust_level (policy); goto out; case TOFU_POLICY_ASK: /* We need to ask the user what to do. */ break; case _tofu_GET_POLICY_ERROR: trust_level = _tofu_GET_TRUST_ERROR; goto out; default: log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy); } /* We get here if: * * 1. The saved policy is auto and the default policy is ask * (get_policy() == TOFU_POLICY_AUTO * && opt.tofu_default_policy == TOFU_POLICY_ASK) * * 2. The saved policy is ask (either last time the user selected * accept once or reject once or there was a conflict and this * binding's policy was changed from auto to ask) * (policy == TOFU_POLICY_ASK). */ log_assert (policy == TOFU_POLICY_ASK); if (may_ask) { /* We can't be in a normal transaction in ask_about_binding. */ end_transaction (ctrl, 0); in_transaction = 0; /* If we get here, we need to ask the user about the binding. */ ask_about_binding (ctrl, &policy, &trust_level, conflict_set, fingerprint, email, user_id, now); } else { trust_level = TRUST_UNDEFINED; } /* Mark any conflicting bindings that have an automatic policy as * now requiring confirmation. Note: we do this after we ask for * confirmation so that when the current policy is printed, it is * correct. */ if (! in_transaction) { begin_transaction (ctrl, 0); in_transaction = 1; } /* The conflict set should always contain at least one element: * the current key. */ log_assert (conflict_set); for (iter = conflict_set->next; iter; iter = iter->next) { /* We don't immediately set the effective policy to 'ask, because */ rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &sqerr, "update bindings set effective_policy = %d, conflict = %Q" " where email = %Q and fingerprint = %Q and effective_policy != %d;", TOFU_POLICY_NONE, fingerprint, email, iter->d, TOFU_POLICY_ASK); if (rc) { log_error (_("error changing TOFU policy: %s\n"), sqerr); print_further_info ("binding: ", fingerprint, user_id); sqlite3_free (sqerr); sqerr = NULL; rc = gpg_error (GPG_ERR_GENERAL); } else if (DBG_TRUST) log_debug ("Set %s to conflict with %s\n", iter->d, fingerprint); } out: if (in_transaction) end_transaction (ctrl, 0); if (policyp) *policyp = policy; if (conflict_setp) *conflict_setp = conflict_set; else free_strlist (conflict_set); return trust_level; } /* Return a malloced string of the form * "7~months" * The caller should replace all '~' in the returned string by a space * and also free the returned string. * * This is actually a bad hack which may not work correctly with all * languages. */ static char * time_ago_str (long long int t) { /* It would be nice to use a macro to do this, but gettext works on the unpreprocessed code. */ #define MIN_SECS (60) #define HOUR_SECS (60 * MIN_SECS) #define DAY_SECS (24 * HOUR_SECS) #define WEEK_SECS (7 * DAY_SECS) #define MONTH_SECS (30 * DAY_SECS) #define YEAR_SECS (365 * DAY_SECS) if (t > 2 * YEAR_SECS) { long long int c = t / YEAR_SECS; return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c); } if (t > 2 * MONTH_SECS) { long long int c = t / MONTH_SECS; return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c); } if (t > 2 * WEEK_SECS) { long long int c = t / WEEK_SECS; return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c); } if (t > 2 * DAY_SECS) { long long int c = t / DAY_SECS; return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c); } if (t > 2 * HOUR_SECS) { long long int c = t / HOUR_SECS; return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c); } if (t > 2 * MIN_SECS) { long long int c = t / MIN_SECS; return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c); } return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t); } /* If FP is NULL, write TOFU_STATS status line. If FP is not NULL * write a "tfs" record to that stream. */ static void write_stats_status (estream_t fp, enum tofu_policy policy, unsigned long signature_count, unsigned long signature_first_seen, unsigned long signature_most_recent, unsigned long signature_days, unsigned long encryption_count, unsigned long encryption_first_done, unsigned long encryption_most_recent, unsigned long encryption_days) { int summary; int validity; unsigned long days_sq; /* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the sum of the magnitudes (m = a + b) to ensure a balance between verified signatures and encrypted messages. */ days_sq = signature_days * signature_days + encryption_days * encryption_days; if (days_sq < 1) validity = 1; /* Key without history. */ else if (days_sq < (2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD)) validity = 2; /* Key with too little history. */ else if (days_sq < (2 * FULL_TRUST_THRESHOLD) * (2 * FULL_TRUST_THRESHOLD)) validity = 3; /* Key with enough history for basic trust. */ else validity = 4; /* Key with a lot of history. */ if (policy == TOFU_POLICY_ASK) summary = 0; /* Key requires attention. */ else summary = validity; if (fp) { es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n", summary, signature_count, encryption_count, tofu_policy_str (policy), signature_first_seen, signature_most_recent, encryption_first_done, encryption_most_recent, validity, signature_days, encryption_days); } else { write_status_printf (STATUS_TOFU_STATS, "%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu", summary, signature_count, encryption_count, tofu_policy_str (policy), signature_first_seen, signature_most_recent, encryption_first_done, encryption_most_recent, validity, signature_days, encryption_days); } } /* Note: If OUTFP is not NULL, this function merely prints a "tfs" record * to OUTFP. * * POLICY is the key's policy (as returned by get_policy). * * Returns 0 if ONLY_STATUS_FD is set. Otherwise, returns whether * the caller should call show_warning after iterating over all user * ids. */ static int show_statistics (tofu_dbs_t dbs, const char *fingerprint, const char *email, enum tofu_policy policy, estream_t outfp, int only_status_fd, time_t now) { char *fingerprint_pp; int rc; strlist_t strlist = NULL; char *err = NULL; unsigned long signature_first_seen = 0; unsigned long signature_most_recent = 0; unsigned long signature_count = 0; unsigned long signature_days = 0; unsigned long encryption_first_done = 0; unsigned long encryption_most_recent = 0; unsigned long encryption_count = 0; unsigned long encryption_days = 0; int show_warning = 0; if (only_status_fd && ! is_status_enabled ()) return 0; fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0); /* Get the signature stats. */ rc = gpgsql_exec_printf (dbs->db, strings_collect_cb, &strlist, &err, "select count (*), coalesce (min (signatures.time), 0),\n" " coalesce (max (signatures.time), 0)\n" " from signatures\n" " left join bindings on signatures.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q;", fingerprint, email); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("getting signature statistics"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } rc = gpgsql_exec_printf (dbs->db, strings_collect_cb, &strlist, &err, "select count (*) from\n" " (select round(signatures.time / (24 * 60 * 60)) day\n" " from signatures\n" " left join bindings on signatures.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q\n" " group by day);", fingerprint, email); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("getting signature statistics (by day)"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } if (strlist) { /* We expect exactly 4 elements. */ log_assert (strlist->next); log_assert (strlist->next->next); log_assert (strlist->next->next->next); log_assert (! strlist->next->next->next->next); string_to_ulong (&signature_days, strlist->d, -1, __LINE__); string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__); string_to_ulong (&signature_first_seen, strlist->next->next->d, -1, __LINE__); string_to_ulong (&signature_most_recent, strlist->next->next->next->d, -1, __LINE__); free_strlist (strlist); strlist = NULL; } /* Get the encryption stats. */ rc = gpgsql_exec_printf (dbs->db, strings_collect_cb, &strlist, &err, "select count (*), coalesce (min (encryptions.time), 0),\n" " coalesce (max (encryptions.time), 0)\n" " from encryptions\n" " left join bindings on encryptions.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q;", fingerprint, email); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("getting encryption statistics"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } rc = gpgsql_exec_printf (dbs->db, strings_collect_cb, &strlist, &err, "select count (*) from\n" " (select round(encryptions.time / (24 * 60 * 60)) day\n" " from encryptions\n" " left join bindings on encryptions.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q\n" " group by day);", fingerprint, email); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("getting encryption statistics (by day)"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } if (strlist) { /* We expect exactly 4 elements. */ log_assert (strlist->next); log_assert (strlist->next->next); log_assert (strlist->next->next->next); log_assert (! strlist->next->next->next->next); string_to_ulong (&encryption_days, strlist->d, -1, __LINE__); string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__); string_to_ulong (&encryption_first_done, strlist->next->next->d, -1, __LINE__); string_to_ulong (&encryption_most_recent, strlist->next->next->next->d, -1, __LINE__); free_strlist (strlist); strlist = NULL; } if (!outfp) write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint, email, strlen (email), 0); write_stats_status (outfp, policy, signature_count, signature_first_seen, signature_most_recent, signature_days, encryption_count, encryption_first_done, encryption_most_recent, encryption_days); if (!outfp && !only_status_fd) { estream_t fp; char *msg; fp = es_fopenmem (0, "rw,samethread"); if (! fp) log_fatal ("error creating memory stream: %s\n", gpg_strerror (gpg_error_from_syserror())); if (signature_count == 0 && encryption_count == 0) { es_fprintf (fp, _("%s: Verified 0~signatures and encrypted 0~messages."), email); } else { if (signature_count == 0) es_fprintf (fp, _("%s: Verified 0 signatures."), email); else { /* Note: Translation not possible with that wording. */ char *ago_str = time_ago_str (now - signature_first_seen); es_fprintf (fp, "%s: Verified %ld~signatures in the past %s.", email, signature_count, ago_str); xfree (ago_str); } es_fputs (" ", fp); if (encryption_count == 0) es_fprintf (fp, _("Encrypted 0 messages.")); else { char *ago_str = time_ago_str (now - encryption_first_done); /* Note: Translation not possible with this kind of * composition. */ es_fprintf (fp, "Encrypted %ld~messages in the past %s.", encryption_count, ago_str); xfree (ago_str); } } if (opt.verbose) { es_fputs (" ", fp); es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy)); } es_fputs ("\n", fp); { char *tmpmsg, *p; es_fputc (0, fp); if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL)) log_fatal ("error snatching memory stream\n"); msg = format_text (tmpmsg, 72, 80); if (!msg) /* FIXME: Return the error all the way up. */ log_fatal ("format failed: %s\n", gpg_strerror (gpg_error_from_syserror())); es_free (tmpmsg); /* Print a status line but suppress the trailing LF. * Spaces are not percent escaped. */ if (*msg) write_status_buffer (STATUS_TOFU_STATS_LONG, msg, strlen (msg)-1, -1); /* Remove the non-breaking space markers. */ for (p=msg; *p; p++) if (*p == '~') *p = ' '; } log_string (GPGRT_LOG_INFO, msg); xfree (msg); if (policy == TOFU_POLICY_AUTO) { if (signature_count == 0) log_info (_("Warning: we have yet to see" " a message signed using this key and user id!\n")); else if (signature_count == 1) log_info (_("Warning: we've only seen one message" " signed using this key and user id!\n")); if (encryption_count == 0) log_info (_("Warning: you have yet to encrypt" " a message to this key!\n")); else if (encryption_count == 1) log_info (_("Warning: you have only encrypted" " one message to this key!\n")); /* Cf. write_stats_status */ if ((encryption_count * encryption_count + signature_count * signature_count) < ((2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD))) show_warning = 1; } } out: xfree (fingerprint_pp); return show_warning; } static void show_warning (const char *fingerprint, strlist_t user_id_list) { char *set_policy_command; char *text; char *tmpmsg; set_policy_command = xasprintf ("gpg --tofu-policy bad %s", fingerprint); tmpmsg = xasprintf (ngettext ("Warning: if you think you've seen more signatures " "by this key and user id, then this key might be a " "forgery! Carefully examine the email address for small " "variations. If the key is suspect, then use\n" " %s\n" "to mark it as being bad.\n", "Warning: if you think you've seen more signatures " "by this key and these user ids, then this key might be a " "forgery! Carefully examine the email addresses for small " "variations. If the key is suspect, then use\n" " %s\n" "to mark it as being bad.\n", strlist_length (user_id_list)), set_policy_command); text = format_text (tmpmsg, 72, 80); if (!text) /* FIXME: Return the error all the way up. */ log_fatal ("format failed: %s\n", gpg_strerror (gpg_error_from_syserror())); xfree (tmpmsg); log_string (GPGRT_LOG_INFO, text); xfree (text); es_free (set_policy_command); } /* Extract the email address from a user id and normalize it. If the user id doesn't contain an email address, then we use the whole user_id and normalize that. The returned string must be freed. */ static char * email_from_user_id (const char *user_id) { char *email = mailbox_from_userid (user_id); if (! email) { /* Hmm, no email address was provided or we are out of core. Just take the lower-case version of the whole user id. It could be a hostname, for instance. */ email = ascii_strlwr (xstrdup (user_id)); } return email; } /* Register the signature with the bindings , for each USER_ID in USER_ID_LIST. The fingerprint is taken from the primary key packet PK. SIG_DIGEST_BIN is the binary representation of the message's digest. SIG_DIGEST_BIN_LEN is its length. SIG_TIME is the time that the signature was generated. ORIGIN is a free-formed string describing the origin of the signature. If this was from an email and the Claws MUA was used, then this should be something like: "email:claws". If this is NULL, the default is simply "unknown". If MAY_ASK is 1, then this function may interact with the user. This is necessary if there is a conflict or the binding's policy is TOFU_POLICY_ASK. This function returns 0 on success and an error code if an error occurred. */ gpg_error_t tofu_register_signature (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, const byte *sig_digest_bin, int sig_digest_bin_len, time_t sig_time, const char *origin) { time_t now = gnupg_get_time (); gpg_error_t rc; tofu_dbs_t dbs; char *fingerprint = NULL; strlist_t user_id; char *email = NULL; char *sqlerr = NULL; char *sig_digest = NULL; unsigned long c; dbs = opendbs (ctrl); if (! dbs) { rc = gpg_error (GPG_ERR_GENERAL); log_error (_("error opening TOFU database: %s\n"), gpg_strerror (rc)); return rc; } /* We do a query and then an insert. Make sure they are atomic by wrapping them in a transaction. */ rc = begin_transaction (ctrl, 0); if (rc) return rc; log_assert (pk_is_primary (pk)); sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len); if (!sig_digest) { rc = gpg_error_from_syserror (); goto leave; } fingerprint = hexfingerprint (pk, NULL, 0); if (!fingerprint) { rc = gpg_error_from_syserror (); goto leave; } if (! origin) origin = "unknown"; /* The default origin is simply "unknown". */ for (user_id = user_id_list; user_id; user_id = user_id->next) { email = email_from_user_id (user_id->d); if (DBG_TRUST) log_debug ("TOFU: Registering signature %s with binding" " \n", sig_digest, fingerprint, email); /* Make sure the binding exists and record any TOFU conflicts. */ if (get_trust (ctrl, pk, fingerprint, email, user_id->d, 0, NULL, NULL, now) == _tofu_GET_TRUST_ERROR) { rc = gpg_error (GPG_ERR_GENERAL); xfree (email); break; } /* If we've already seen this signature before, then don't add it again. */ rc = gpgsql_stepx (dbs->db, &dbs->s.register_already_seen, get_single_unsigned_long_cb2, &c, &sqlerr, "select count (*)\n" " from signatures left join bindings\n" " on signatures.binding = bindings.oid\n" " where fingerprint = ? and email = ? and sig_time = ?\n" " and sig_digest = ?", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_LONG_LONG, (long long) sig_time, GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_END); if (rc) { log_error (_("error reading TOFU database: %s\n"), sqlerr); print_further_info ("checking existence"); sqlite3_free (sqlerr); rc = gpg_error (GPG_ERR_GENERAL); } else if (c > 1) /* Duplicates! This should not happen. In particular, because is the primary key! */ log_debug ("SIGNATURES DB contains duplicate records" " ." " Please report.\n", fingerprint, email, (unsigned long) sig_time, sig_digest, origin); else if (c == 1) { if (DBG_TRUST) log_debug ("Already observed the signature and binding" " \n", fingerprint, email, (unsigned long) sig_time, sig_digest, origin); } else if (opt.dry_run) { log_info ("TOFU database update skipped due to --dry-run\n"); } else /* This is the first time that we've seen this signature and binding. Record it. */ { if (DBG_TRUST) log_debug ("TOFU: Saving signature" " \n", fingerprint, email, sig_digest); log_assert (c == 0); rc = gpgsql_stepx (dbs->db, &dbs->s.register_signature, NULL, NULL, &sqlerr, "insert into signatures\n" " (binding, sig_digest, origin, sig_time, time)\n" " values\n" " ((select oid from bindings\n" " where fingerprint = ? and email = ?),\n" " ?, ?, ?, ?);", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_STRING, origin, GPGSQL_ARG_LONG_LONG, (long long) sig_time, GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_END); if (rc) { log_error (_("error updating TOFU database: %s\n"), sqlerr); print_further_info ("insert signatures"); sqlite3_free (sqlerr); rc = gpg_error (GPG_ERR_GENERAL); } } xfree (email); if (rc) break; } leave: if (rc) rollback_transaction (ctrl); else rc = end_transaction (ctrl, 0); xfree (fingerprint); xfree (sig_digest); return rc; } gpg_error_t tofu_register_encryption (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, int may_ask) { time_t now = gnupg_get_time (); gpg_error_t rc = 0; tofu_dbs_t dbs; kbnode_t kb = NULL; int free_user_id_list = 0; char *fingerprint = NULL; strlist_t user_id; char *sqlerr = NULL; int in_batch = 0; dbs = opendbs (ctrl); if (! dbs) { rc = gpg_error (GPG_ERR_GENERAL); log_error (_("error opening TOFU database: %s\n"), gpg_strerror (rc)); return rc; } if (/* We need the key block to find the primary key. */ ! pk_is_primary (pk) /* We need the key block to find all user ids. */ || ! user_id_list) kb = get_pubkeyblock (ctrl, pk->keyid); /* Make sure PK is a primary key. */ if (! pk_is_primary (pk)) pk = kb->pkt->pkt.public_key; if (! user_id_list) { /* Use all non-revoked user ids. Do use expired user ids. */ kbnode_t n = kb; while ((n = find_next_kbnode (n, PKT_USER_ID))) { PKT_user_id *uid = n->pkt->pkt.user_id; if (uid->flags.revoked) continue; add_to_strlist (&user_id_list, uid->name); } free_user_id_list = 1; if (! user_id_list) log_info (_("WARNING: Encrypting to %s, which has no " "non-revoked user ids\n"), keystr (pk->keyid)); } fingerprint = hexfingerprint (pk, NULL, 0); if (!fingerprint) { rc = gpg_error_from_syserror (); goto leave; } tofu_begin_batch_update (ctrl); in_batch = 1; tofu_resume_batch_transaction (ctrl); for (user_id = user_id_list; user_id; user_id = user_id->next) { char *email = email_from_user_id (user_id->d); strlist_t conflict_set = NULL; enum tofu_policy policy; /* Make sure the binding exists and that we recognize any conflicts. */ int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d, may_ask, &policy, &conflict_set, now); if (tl == _tofu_GET_TRUST_ERROR) { /* An error. */ rc = gpg_error (GPG_ERR_GENERAL); xfree (email); goto leave; } /* If there is a conflict and MAY_ASK is true, we need to show * the TOFU statistics for the current binding and the * conflicting bindings. But, if we are not in batch mode, then * they have already been printed (this is required to make sure * the information is available to the caller before cpr_get is * called). */ if (policy == TOFU_POLICY_ASK && may_ask && opt.batch) { strlist_t iter; /* The conflict set should contain at least the current * key. */ log_assert (conflict_set); for (iter = conflict_set; iter; iter = iter->next) show_statistics (dbs, iter->d, email, TOFU_POLICY_ASK, NULL, 1, now); } free_strlist (conflict_set); rc = gpgsql_stepx (dbs->db, &dbs->s.register_encryption, NULL, NULL, &sqlerr, "insert into encryptions\n" " (binding, time)\n" " values\n" " ((select oid from bindings\n" " where fingerprint = ? and email = ?),\n" " ?);", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_END); if (rc) { log_error (_("error updating TOFU database: %s\n"), sqlerr); print_further_info ("insert encryption"); sqlite3_free (sqlerr); rc = gpg_error (GPG_ERR_GENERAL); } xfree (email); } leave: if (in_batch) tofu_end_batch_update (ctrl); release_kbnode (kb); if (free_user_id_list) free_strlist (user_id_list); xfree (fingerprint); return rc; } /* Combine a trust level returned from the TOFU trust model with a trust level returned by the PGP trust model. This is primarily of interest when the trust model is tofu+pgp (TM_TOFU_PGP). This function ors together the upper bits (the values not covered by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.). */ int tofu_wot_trust_combine (int tofu_base, int wot_base) { int tofu = tofu_base & TRUST_MASK; int wot = wot_base & TRUST_MASK; int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK); log_assert (tofu == TRUST_UNKNOWN || tofu == TRUST_EXPIRED || tofu == TRUST_UNDEFINED || tofu == TRUST_NEVER || tofu == TRUST_MARGINAL || tofu == TRUST_FULLY || tofu == TRUST_ULTIMATE); log_assert (wot == TRUST_UNKNOWN || wot == TRUST_EXPIRED || wot == TRUST_UNDEFINED || wot == TRUST_NEVER || wot == TRUST_MARGINAL || wot == TRUST_FULLY || wot == TRUST_ULTIMATE); /* We first consider negative trust policys. These trump positive trust policies. */ if (tofu == TRUST_NEVER || wot == TRUST_NEVER) /* TRUST_NEVER trumps everything else. */ return upper | TRUST_NEVER; if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED) /* TRUST_EXPIRED trumps everything but TRUST_NEVER. */ return upper | TRUST_EXPIRED; /* Now we only have positive or neutral trust policies. We take the max. */ if (tofu == TRUST_ULTIMATE) return upper | TRUST_ULTIMATE | TRUST_FLAG_TOFU_BASED; if (wot == TRUST_ULTIMATE) return upper | TRUST_ULTIMATE; if (tofu == TRUST_FULLY) return upper | TRUST_FULLY | TRUST_FLAG_TOFU_BASED; if (wot == TRUST_FULLY) return upper | TRUST_FULLY; if (tofu == TRUST_MARGINAL) return upper | TRUST_MARGINAL | TRUST_FLAG_TOFU_BASED; if (wot == TRUST_MARGINAL) return upper | TRUST_MARGINAL; if (tofu == TRUST_UNDEFINED) return upper | TRUST_UNDEFINED | TRUST_FLAG_TOFU_BASED; if (wot == TRUST_UNDEFINED) return upper | TRUST_UNDEFINED; return upper | TRUST_UNKNOWN; } /* Write a "tfs" record for a --with-colons listing. */ gpg_error_t tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, const char *user_id) { time_t now = gnupg_get_time (); gpg_error_t err = 0; tofu_dbs_t dbs; char *fingerprint; char *email = NULL; enum tofu_policy policy; if (!*user_id) return 0; /* No TOFU stats possible for an empty ID. */ dbs = opendbs (ctrl); if (!dbs) { err = gpg_error (GPG_ERR_GENERAL); log_error (_("error opening TOFU database: %s\n"), gpg_strerror (err)); return err; } fingerprint = hexfingerprint (pk, NULL, 0); if (!fingerprint) { err = gpg_error_from_syserror (); goto leave; } email = email_from_user_id (user_id); policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, NULL, now); show_statistics (dbs, fingerprint, email, policy, fp, 0, now); leave: xfree (email); xfree (fingerprint); return err; } /* Return the validity (TRUST_NEVER, etc.) of the bindings , for each USER_ID in USER_ID_LIST. If USER_ID_LIST->FLAG is set, then the id is considered to be expired. PK is the primary key packet. If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user will be prompted to choose a policy. If MAY_ASK is 0 and the policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned. Returns TRUST_UNDEFINED if an error occurs. Fixme: eturn an error code */ int tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, int may_ask) { time_t now = gnupg_get_time (); tofu_dbs_t dbs; char *fingerprint = NULL; strlist_t user_id; int trust_level = TRUST_UNKNOWN; int bindings = 0; int bindings_valid = 0; int need_warning = 0; int had_conflict = 0; dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); return TRUST_UNDEFINED; } fingerprint = hexfingerprint (pk, NULL, 0); if (!fingerprint) log_fatal ("%s: malloc failed\n", __func__); tofu_begin_batch_update (ctrl); /* Start the batch transaction now. */ tofu_resume_batch_transaction (ctrl); for (user_id = user_id_list; user_id; user_id = user_id->next, bindings ++) { char *email = email_from_user_id (user_id->d); strlist_t conflict_set = NULL; enum tofu_policy policy; /* Always call get_trust to make sure the binding is registered. */ int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d, may_ask, &policy, &conflict_set, now); if (tl == _tofu_GET_TRUST_ERROR) { /* An error. */ trust_level = TRUST_UNDEFINED; xfree (email); goto die; } if (DBG_TRUST) log_debug ("TOFU: validity for : %s%s.\n", fingerprint, email, trust_value_to_string (tl), user_id->flags ? " (but expired)" : ""); if (user_id->flags) tl = TRUST_EXPIRED; if (tl != TRUST_EXPIRED) bindings_valid ++; if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED) { /* If policy is ask, then we already printed out the * conflict information in ask_about_binding or will do so * in a moment. */ if (policy != TOFU_POLICY_ASK) need_warning |= show_statistics (dbs, fingerprint, email, policy, NULL, 0, now); /* If there is a conflict and MAY_ASK is true, we need to * show the TOFU statistics for the current binding and the * conflicting bindings. But, if we are not in batch mode, * then they have already been printed (this is required to * make sure the information is available to the caller * before cpr_get is called). */ if (policy == TOFU_POLICY_ASK && opt.batch) { strlist_t iter; /* The conflict set should contain at least the current * key. */ log_assert (conflict_set); had_conflict = 1; for (iter = conflict_set; iter; iter = iter->next) show_statistics (dbs, iter->d, email, TOFU_POLICY_ASK, NULL, 1, now); } } free_strlist (conflict_set); if (tl == TRUST_NEVER) trust_level = TRUST_NEVER; else if (tl == TRUST_EXPIRED) /* Ignore expired bindings in the trust calculation. */ ; else if (tl > trust_level) { /* The expected values: */ log_assert (tl == TRUST_UNKNOWN || tl == TRUST_UNDEFINED || tl == TRUST_MARGINAL || tl == TRUST_FULLY || tl == TRUST_ULTIMATE); /* We assume the following ordering: */ log_assert (TRUST_UNKNOWN < TRUST_UNDEFINED); log_assert (TRUST_UNDEFINED < TRUST_MARGINAL); log_assert (TRUST_MARGINAL < TRUST_FULLY); log_assert (TRUST_FULLY < TRUST_ULTIMATE); trust_level = tl; } xfree (email); } if (need_warning && ! had_conflict) show_warning (fingerprint, user_id_list); die: tofu_end_batch_update (ctrl); xfree (fingerprint); if (bindings_valid == 0) { if (DBG_TRUST) log_debug ("no (of %d) valid bindings." " Can't get TOFU validity for this set of user ids.\n", bindings); return TRUST_NEVER; } return trust_level; } /* Set the policy for all non-revoked user ids in the keyblock KB to POLICY. If no key is available with the specified key id, then this function returns GPG_ERR_NO_PUBKEY. Returns 0 on success and an error code otherwise. */ gpg_error_t tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy) { gpg_error_t err = 0; time_t now = gnupg_get_time (); tofu_dbs_t dbs; PKT_public_key *pk; char *fingerprint = NULL; log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); pk = kb->pkt->pkt.public_key; dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); return gpg_error (GPG_ERR_GENERAL); } if (DBG_TRUST) log_debug ("Setting TOFU policy for %s to %s\n", keystr (pk->keyid), tofu_policy_str (policy)); if (! pk_is_primary (pk)) log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__); fingerprint = hexfingerprint (pk, NULL, 0); if (!fingerprint) return gpg_error_from_syserror (); begin_transaction (ctrl, 0); for (; kb; kb = kb->next) { PKT_user_id *user_id; char *email; if (kb->pkt->pkttype != PKT_USER_ID) continue; user_id = kb->pkt->pkt.user_id; if (user_id->flags.revoked) /* Skip revoked user ids. (Don't skip expired user ids, the expiry can be changed.) */ continue; email = email_from_user_id (user_id->name); err = record_binding (dbs, fingerprint, email, user_id->name, policy, TOFU_POLICY_NONE, NULL, 0, 1, now); if (err) { log_error ("error setting policy for key %s, user id \"%s\": %s", fingerprint, email, gpg_strerror (err)); xfree (email); break; } xfree (email); } if (err) rollback_transaction (ctrl); else end_transaction (ctrl, 0); xfree (fingerprint); return err; } /* Return the TOFU policy for the specified binding in *POLICY. If no policy has been set for the binding, sets *POLICY to TOFU_POLICY_NONE. PK is a primary public key and USER_ID is a user id. Returns 0 on success and an error code otherwise. */ gpg_error_t tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id, enum tofu_policy *policy) { time_t now = gnupg_get_time (); tofu_dbs_t dbs; char *fingerprint; char *email; /* Make sure PK is a primary key. */ log_assert (pk_is_primary (pk)); dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); return gpg_error (GPG_ERR_GENERAL); } fingerprint = hexfingerprint (pk, NULL, 0); if (!fingerprint) return gpg_error_from_syserror (); email = email_from_user_id (user_id->name); *policy = get_policy (ctrl, dbs, pk, fingerprint, user_id->name, email, NULL, now); xfree (email); xfree (fingerprint); if (*policy == _tofu_GET_POLICY_ERROR) return gpg_error (GPG_ERR_GENERAL); return 0; } gpg_error_t tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb) { tofu_dbs_t dbs; PKT_public_key *pk; char *fingerprint; char *sqlerr = NULL; int rc; /* Make sure PK is a primary key. */ setup_main_keyids (kb); pk = kb->pkt->pkt.public_key; log_assert (pk_is_primary (pk)); dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); return gpg_error (GPG_ERR_GENERAL); } fingerprint = hexfingerprint (pk, NULL, 0); if (!fingerprint) return gpg_error_from_syserror (); rc = gpgsql_stepx (dbs->db, NULL, NULL, NULL, &sqlerr, "update bindings set effective_policy = ?" " where fingerprint = ?;", GPGSQL_ARG_INT, (int) TOFU_POLICY_NONE, GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END); xfree (fingerprint); if (rc == _tofu_GET_POLICY_ERROR) return gpg_error (GPG_ERR_GENERAL); return 0; } diff --git a/g13/backend.c b/g13/backend.c index 71cd6ffc1..a0a26752c 100644 --- a/g13/backend.c +++ b/g13/backend.c @@ -1,296 +1,296 @@ /* backend.c - Dispatcher to the various backends. * Copyright (C) 2009 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 #include #include #include #include #include #include #include "g13.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "keyblob.h" #include "backend.h" #include "be-encfs.h" #include "be-truecrypt.h" #include "be-dmcrypt.h" #include "call-syshelp.h" #define no_such_backend(a) _no_such_backend ((a), __func__) static gpg_error_t _no_such_backend (int conttype, const char *func) { log_error ("invalid backend %d given in %s - this is most likely a bug\n", conttype, func); return gpg_error (GPG_ERR_INTERNAL); } /* Parse NAME and return the corresponding content type. If the name is not known, a error message is printed and zero returned. If NAME is NULL the supported backend types are listed and 0 is returned. */ int be_parse_conttype_name (const char *name) { static struct { const char *name; int conttype; } names[] = { { "encfs", CONTTYPE_ENCFS }, { "dm-crypt", CONTTYPE_DM_CRYPT } }; int i; if (!name) { log_info ("Known backend types:\n"); for (i=0; i < DIM (names); i++) log_info (" %s\n", names[i].name); return 0; } for (i=0; i < DIM (names); i++) { if (!strcmp (names[i].name, name)) return names[i].conttype; } log_error ("invalid backend type '%s' given\n", name); return 0; } /* Return true if CONTTYPE is supported by us. */ int be_is_supported_conttype (int conttype) { switch (conttype) { case CONTTYPE_ENCFS: case CONTTYPE_DM_CRYPT: return 1; default: return 0; } } /* Create a lock file for the container FNAME and store the lock at * R_LOCK and return 0. On error return an error code and store NULL * at R_LOCK. */ gpg_error_t be_take_lock_for_create (ctrl_t ctrl, const char *fname, dotlock_t *r_lock) { gpg_error_t err; dotlock_t lock = NULL; struct stat sb; *r_lock = NULL; /* A DM-crypt container requires special treatment by using the syshelper functions. */ if (ctrl->conttype == CONTTYPE_DM_CRYPT) { /* */ err = call_syshelp_set_device (ctrl, fname); goto leave; } /* A quick check to see that no container with that name already exists. */ if (!gnupg_access (fname, F_OK)) { err = gpg_error (GPG_ERR_EEXIST); goto leave; } /* Take a lock and proceed with the creation. If there is a lock we immediately return an error because for creation it does not make sense to wait. */ lock = dotlock_create (fname, 0); if (!lock) { err = gpg_error_from_syserror (); goto leave; } if (dotlock_take (lock, 0)) { err = gpg_error_from_syserror (); goto leave; } /* Check again that the file does not exist. */ - err = stat (fname, &sb)? 0 : gpg_error (GPG_ERR_EEXIST); + err = gnupg_stat (fname, &sb)? 0 : gpg_error (GPG_ERR_EEXIST); leave: if (!err) { *r_lock = lock; lock = NULL; } dotlock_destroy (lock); return err; } /* If the backend requires a separate file or directory for the container, return its name by computing it from FNAME which gives the g13 filename. The new file name is allocated and stored at R_NAME, if this is expected to be a directory true is stored at R_ISDIR. If no detached name is expected or an error occurs NULL is stored at R_NAME. The function returns 0 on success or an error code. */ gpg_error_t be_get_detached_name (int conttype, const char *fname, char **r_name, int *r_isdir) { *r_name = NULL; *r_isdir = 0; switch (conttype) { case CONTTYPE_ENCFS: return be_encfs_get_detached_name (fname, r_name, r_isdir); case CONTTYPE_DM_CRYPT: return 0; default: return no_such_backend (conttype); } } gpg_error_t be_create_new_keys (int conttype, membuf_t *mb) { switch (conttype) { case CONTTYPE_ENCFS: return be_encfs_create_new_keys (mb); case CONTTYPE_TRUECRYPT: return be_truecrypt_create_new_keys (mb); case CONTTYPE_DM_CRYPT: return 0; default: return no_such_backend (conttype); } } /* Dispatcher to the backend's create function. */ gpg_error_t be_create_container (ctrl_t ctrl, int conttype, const char *fname, int fd, tupledesc_t tuples, unsigned int *r_id) { (void)fd; /* Not yet used. */ switch (conttype) { case CONTTYPE_ENCFS: return be_encfs_create_container (ctrl, fname, tuples, r_id); case CONTTYPE_DM_CRYPT: return be_dmcrypt_create_container (ctrl); default: return no_such_backend (conttype); } } /* Dispatcher to the backend's mount function. */ gpg_error_t be_mount_container (ctrl_t ctrl, int conttype, const char *fname, const char *mountpoint, tupledesc_t tuples, unsigned int *r_id) { switch (conttype) { case CONTTYPE_ENCFS: return be_encfs_mount_container (ctrl, fname, mountpoint, tuples, r_id); case CONTTYPE_DM_CRYPT: return be_dmcrypt_mount_container (ctrl, fname, mountpoint, tuples); default: return no_such_backend (conttype); } } /* Dispatcher to the backend's umount function. */ gpg_error_t be_umount_container (ctrl_t ctrl, int conttype, const char *fname) { switch (conttype) { case CONTTYPE_ENCFS: return gpg_error (GPG_ERR_NOT_SUPPORTED); case CONTTYPE_DM_CRYPT: return be_dmcrypt_umount_container (ctrl, fname); default: return no_such_backend (conttype); } } /* Dispatcher to the backend's suspend function. */ gpg_error_t be_suspend_container (ctrl_t ctrl, int conttype, const char *fname) { switch (conttype) { case CONTTYPE_ENCFS: return gpg_error (GPG_ERR_NOT_SUPPORTED); case CONTTYPE_DM_CRYPT: return be_dmcrypt_suspend_container (ctrl, fname); default: return no_such_backend (conttype); } } /* Dispatcher to the backend's resume function. */ gpg_error_t be_resume_container (ctrl_t ctrl, int conttype, const char *fname, tupledesc_t tuples) { switch (conttype) { case CONTTYPE_ENCFS: return gpg_error (GPG_ERR_NOT_SUPPORTED); case CONTTYPE_DM_CRYPT: return be_dmcrypt_resume_container (ctrl, fname, tuples); default: return no_such_backend (conttype); } } diff --git a/g13/create.c b/g13/create.c index d773dfa81..bc31f4e32 100644 --- a/g13/create.c +++ b/g13/create.c @@ -1,302 +1,302 @@ /* create.c - Create a new crypto container * Copyright (C) 2009 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 #include #include #include #include #include #include #include #include "g13.h" #include "../common/i18n.h" #include "create.h" #include "keyblob.h" #include "backend.h" #include "g13tuple.h" #include "../common/call-gpg.h" /* Create a new blob with all the session keys and other meta information which are to be stored encrypted in the crypto container header. On success the malloced blob is stored at R_BLOB and its length at R_BLOBLEN. On error an error code is returned and (R_BLOB,R_BLOBLEN) are set to (NULL,0). The format of this blob is a sequence of tag-length-value tuples. All tuples have this format: 2 byte TAG Big endian unsigned integer (0..65535) described by the KEYBLOB_TAG_ constants. 2 byte LENGTH Big endian unsigned integer (0..65535) giving the length of the value. length bytes VALUE The value described by the tag. The first tag in a keyblob must be a BLOBVERSION. The other tags depend on the type of the container as described by the CONTTYPE tag. See keyblob.h for details. */ static gpg_error_t create_new_keyblob (ctrl_t ctrl, int is_detached, void **r_blob, size_t *r_bloblen) { gpg_error_t err; unsigned char twobyte[2]; membuf_t mb; *r_blob = NULL; *r_bloblen = 0; init_membuf_secure (&mb, 512); append_tuple (&mb, KEYBLOB_TAG_BLOBVERSION, "\x01", 1); twobyte[0] = (ctrl->conttype >> 8); twobyte[1] = (ctrl->conttype); append_tuple (&mb, KEYBLOB_TAG_CONTTYPE, twobyte, 2); if (is_detached) append_tuple (&mb, KEYBLOB_TAG_DETACHED, NULL, 0); err = be_create_new_keys (ctrl->conttype, &mb); if (err) goto leave; /* Just for testing. */ append_tuple (&mb, KEYBLOB_TAG_FILLER, "filler", 6); *r_blob = get_membuf (&mb, r_bloblen); if (!*r_blob) { err = gpg_error_from_syserror (); *r_bloblen = 0; } else log_debug ("used keyblob size is %zu\n", *r_bloblen); leave: xfree (get_membuf (&mb, NULL)); return err; } /* Encrypt the keyblob (KEYBLOB,KEYBLOBLEN) and store the result at (R_ENCBLOB, R_ENCBLOBLEN). Returns 0 on success or an error code. On error R_EKYBLOB is set to NULL. Depending on the keys set in CTRL the result is a single OpenPGP binary message, a single special OpenPGP packet encapsulating a CMS message or a concatenation of both with the CMS packet being the last. */ gpg_error_t g13_encrypt_keyblob (ctrl_t ctrl, void *keyblob, size_t keybloblen, void **r_encblob, size_t *r_encbloblen) { gpg_error_t err; /* FIXME: For now we only implement OpenPGP. */ err = gpg_encrypt_blob (ctrl, opt.gpg_program, opt.gpg_arguments, keyblob, keybloblen, ctrl->recipients, r_encblob, r_encbloblen); return err; } /* Write a new file under the name FILENAME with the keyblob and an appropriate header. This function is called with a lock file in place and after checking that the filename does not exists. */ static gpg_error_t write_keyblob (const char *filename, const void *keyblob, size_t keybloblen) { gpg_error_t err; estream_t fp; unsigned char packet[32]; size_t headerlen, paddinglen; fp = es_fopen (filename, "wbx"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error creating new container '%s': %s\n", filename, gpg_strerror (err)); return err; } /* Allow for an least 8 times larger keyblob to accommodate for future key changes. Round it up to 4096 byte. */ headerlen = ((32 + 8 * keybloblen + 16) + 4095) / 4096 * 4096; paddinglen = headerlen - 32 - keybloblen; assert (paddinglen >= 16); packet[0] = (0xc0|61); /* CTB for the private packet type 0x61. */ packet[1] = 0xff; /* 5 byte length packet, value 20. */ packet[2] = 0; packet[3] = 0; packet[4] = 0; packet[5] = 26; memcpy (packet+6, "GnuPG/G13", 10); /* Packet subtype. */ packet[16] = 1; /* G13 packet format version. */ packet[17] = 0; /* Reserved. */ packet[18] = 0; /* Reserved. */ packet[19] = 0; /* OS Flag. */ packet[20] = (headerlen >> 24); /* Total length of header. */ packet[21] = (headerlen >> 16); packet[22] = (headerlen >> 8); packet[23] = (headerlen); packet[24] = 1; /* Number of header copies. */ packet[25] = 0; /* Number of header copies at the end. */ packet[26] = 0; /* Reserved. */ packet[27] = 0; /* Reserved. */ packet[28] = 0; /* Reserved. */ packet[29] = 0; /* Reserved. */ packet[30] = 0; /* Reserved. */ packet[31] = 0; /* Reserved. */ if (es_fwrite (packet, 32, 1, fp) != 1) goto writeerr; if (es_fwrite (keyblob, keybloblen, 1, fp) != 1) goto writeerr; /* Write the padding. */ packet[0] = (0xc0|61); /* CTB for Private packet type 0x61. */ packet[1] = 0xff; /* 5 byte length packet, value 20. */ packet[2] = (paddinglen-6) >> 24; packet[3] = (paddinglen-6) >> 16; packet[4] = (paddinglen-6) >> 8; packet[5] = (paddinglen-6); memcpy (packet+6, "GnuPG/PAD", 10); /* Packet subtype. */ if (es_fwrite (packet, 16, 1, fp) != 1) goto writeerr; memset (packet, 0, 32); for (paddinglen-=16; paddinglen >= 32; paddinglen -= 32) if (es_fwrite (packet, 32, 1, fp) != 1) goto writeerr; if (paddinglen) if (es_fwrite (packet, paddinglen, 1, fp) != 1) goto writeerr; if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", filename, gpg_strerror (err)); remove (filename); return err; } return 0; writeerr: err = gpg_error_from_syserror (); log_error ("error writing header to '%s': %s\n", filename, gpg_strerror (err)); es_fclose (fp); remove (filename); return err; } /* Create a new container under the name FILENAME and initialize it using the current settings. If the file already exists an error is returned. */ gpg_error_t g13_create_container (ctrl_t ctrl, const char *filename) { gpg_error_t err; dotlock_t lock; void *keyblob = NULL; size_t keybloblen; void *enckeyblob = NULL; size_t enckeybloblen; char *detachedname = NULL; int detachedisdir; tupledesc_t tuples = NULL; unsigned int dummy_rid; if (!ctrl->recipients) return gpg_error (GPG_ERR_NO_PUBKEY); err = be_take_lock_for_create (ctrl, filename, &lock); if (err) goto leave; /* And a possible detached file or directory may not exist either. */ err = be_get_detached_name (ctrl->conttype, filename, &detachedname, &detachedisdir); if (err) goto leave; if (detachedname) { struct stat sb; - if (!stat (detachedname, &sb)) + if (!gnupg_stat (detachedname, &sb)) { err = gpg_error (GPG_ERR_EEXIST); goto leave; } } if (ctrl->conttype != CONTTYPE_DM_CRYPT) { /* Create a new keyblob. */ err = create_new_keyblob (ctrl, !!detachedname, &keyblob, &keybloblen); if (err) goto leave; /* Encrypt that keyblob. */ err = g13_encrypt_keyblob (ctrl, keyblob, keybloblen, &enckeyblob, &enckeybloblen); if (err) goto leave; /* Put a copy of the keyblob into a tuple structure. */ err = create_tupledesc (&tuples, keyblob, keybloblen); if (err) goto leave; keyblob = NULL; /* if (opt.verbose) */ /* dump_keyblob (tuples); */ /* Write out the header, the encrypted keyblob and some padding. */ err = write_keyblob (filename, enckeyblob, enckeybloblen); if (err) goto leave; } /* Create and append the container. FIXME: We should pass the estream object in addition to the filename, so that the backend can append the container to the g13 file. */ err = be_create_container (ctrl, ctrl->conttype, filename, -1, tuples, &dummy_rid); leave: destroy_tupledesc (tuples); xfree (detachedname); xfree (enckeyblob); xfree (keyblob); dotlock_destroy (lock); return err; } diff --git a/g13/mount.c b/g13/mount.c index bee47fbd5..45b60806c 100644 --- a/g13/mount.c +++ b/g13/mount.c @@ -1,265 +1,265 @@ /* mount.c - Mount a crypto container * Copyright (C) 2009 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 #include #include #include #include #include #include #include #include "g13.h" #include "../common/i18n.h" #include "mount.h" #include "keyblob.h" #include "backend.h" #include "g13tuple.h" #include "mountinfo.h" #include "runner.h" #include "../common/host2net.h" #include "server.h" /*(g13_keyblob_decrypt)*/ #include "../common/sysutils.h" #include "call-syshelp.h" /* Mount the container with name FILENAME at MOUNTPOINT. */ gpg_error_t g13_mount_container (ctrl_t ctrl, const char *filename, const char *mountpoint) { gpg_error_t err; dotlock_t lock; int needs_syshelp = 0; void *enckeyblob = NULL; size_t enckeybloblen; void *keyblob = NULL; size_t keybloblen; tupledesc_t tuples = NULL; size_t n; const unsigned char *value; int conttype; unsigned int rid; char *mountpoint_buffer = NULL; char *blockdev_buffer = NULL; /* Decide whether we need to use the g13-syshelp. */ err = call_syshelp_find_device (ctrl, filename, &blockdev_buffer); if (!err) { needs_syshelp = 1; filename = blockdev_buffer; } else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) { log_error ("error finding device '%s': %s <%s>\n", filename, gpg_strerror (err), gpg_strsource (err)); return err; } else { /* A quick check to see whether we can the container exists. */ if (gnupg_access (filename, R_OK)) return gpg_error_from_syserror (); } if (!mountpoint) { mountpoint_buffer = xtrystrdup ("/tmp/g13-XXXXXX"); if (!mountpoint_buffer) return gpg_error_from_syserror (); if (!gnupg_mkdtemp (mountpoint_buffer)) { err = gpg_error_from_syserror (); log_error (_("can't create directory '%s': %s\n"), "/tmp/g13-XXXXXX", gpg_strerror (err)); xfree (mountpoint_buffer); return err; } mountpoint = mountpoint_buffer; } err = 0; if (needs_syshelp) lock = NULL; else { /* Try to take a lock. */ lock = dotlock_create (filename, 0); if (!lock) { xfree (mountpoint_buffer); return gpg_error_from_syserror (); } if (dotlock_take (lock, 0)) { err = gpg_error_from_syserror (); goto leave; } } /* Check again that the file exists. */ if (!needs_syshelp) { struct stat sb; - if (stat (filename, &sb)) + if (gnupg_stat (filename, &sb)) { err = gpg_error_from_syserror (); goto leave; } } /* Read the encrypted keyblob. */ if (needs_syshelp) { err = call_syshelp_set_device (ctrl, filename); if (err) goto leave; err = call_syshelp_get_keyblob (ctrl, &enckeyblob, &enckeybloblen); } else err = g13_keyblob_read (filename, &enckeyblob, &enckeybloblen); if (err) goto leave; /* Decrypt that keyblob and store it in a tuple descriptor. */ err = g13_keyblob_decrypt (ctrl, enckeyblob, enckeybloblen, &keyblob, &keybloblen); if (err) goto leave; xfree (enckeyblob); enckeyblob = NULL; err = create_tupledesc (&tuples, keyblob, keybloblen); if (!err) keyblob = NULL; else { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) log_error ("unknown keyblob version\n"); goto leave; } if (opt.verbose) dump_tupledesc (tuples); value = find_tuple (tuples, KEYBLOB_TAG_CONTTYPE, &n); if (!value || n != 2) conttype = 0; else conttype = (value[0] << 8 | value[1]); if (!be_is_supported_conttype (conttype)) { log_error ("content type %d is not supported\n", conttype); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = be_mount_container (ctrl, conttype, filename, mountpoint, tuples, &rid); if (err) ; else if (conttype == CONTTYPE_DM_CRYPT) g13_request_shutdown (); else { /* Unless this is a DM-CRYPT mount we put it into our mounttable so that we can manage the mounts ourselves. For dm-crypt we do not keep a process to monitor he mounts (for now). */ err = mountinfo_add_mount (filename, mountpoint, conttype, rid, !!mountpoint_buffer); /* Fixme: What shall we do if this fails? Add a provisional mountinfo entry first and remove it on error? */ if (!err) { char *tmp = percent_plus_escape (mountpoint); if (!tmp) err = gpg_error_from_syserror (); else { g13_status (ctrl, STATUS_MOUNTPOINT, tmp, NULL); xfree (tmp); } } } leave: destroy_tupledesc (tuples); xfree (keyblob); xfree (enckeyblob); dotlock_destroy (lock); xfree (mountpoint_buffer); xfree (blockdev_buffer); return err; } /* Unmount the container with name FILENAME or the one mounted at MOUNTPOINT. If both are given the FILENAME takes precedence. */ gpg_error_t g13_umount_container (ctrl_t ctrl, const char *filename, const char *mountpoint) { gpg_error_t err; char *blockdev; if (!filename && !mountpoint) return gpg_error (GPG_ERR_ENOENT); /* Decide whether we need to use the g13-syshelp. */ err = call_syshelp_find_device (ctrl, filename, &blockdev); if (!err) { /* Need to employ the syshelper to umount the file system. */ /* FIXME: We should get the CONTTYPE from the blockdev. */ err = be_umount_container (ctrl, CONTTYPE_DM_CRYPT, blockdev); if (!err) { /* if (conttype == CONTTYPE_DM_CRYPT) */ g13_request_shutdown (); } } else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) { log_error ("error finding device '%s': %s <%s>\n", filename, gpg_strerror (err), gpg_strsource (err)); } else { /* Not in g13tab - kill the runner process for this mount. */ unsigned int rid; runner_t runner; err = mountinfo_find_mount (filename, mountpoint, &rid); if (err) return err; runner = runner_find_by_rid (rid); if (!runner) { log_error ("runner %u not found\n", rid); return gpg_error (GPG_ERR_NOT_FOUND); } runner_cancel (runner); runner_release (runner); } xfree (blockdev); return err; } diff --git a/g13/sh-dmcrypt.c b/g13/sh-dmcrypt.c index 09c4d967b..4320ac394 100644 --- a/g13/sh-dmcrypt.c +++ b/g13/sh-dmcrypt.c @@ -1,1042 +1,1042 @@ /* sh-dmcrypt.c - The DM-Crypt part for g13-syshelp * Copyright (C) 2015, 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 #include #include #include #include #include #include #include #ifdef HAVE_SYS_MKDEV_H #include #endif #ifdef HAVE_SYS_SYSMACROS_H # include #endif #ifdef HAVE_STAT # include #endif #include #include "g13-syshelp.h" #include #include "../common/i18n.h" #include "g13tuple.h" #include "../common/exectool.h" #include "keyblob.h" /* The standard disk block size (logical). */ #define SECTOR_SIZE 512 /* The physical block size used by modern devices. */ #define PHY_SECTOR_SIZE (SECTOR_SIZE*8) /* 4 KiB */ /* The length of the crypto setup area in sectors. 16 KiB is a nice multiple of a modern block size and should be sufficient for all kind of extra public key encryption packets. */ #define SETUP_AREA_SECTORS 32 /* 16 KiB */ /* The number of header block copies stored at the begin and end of the device. */ #define HEADER_SETUP_AREA_COPIES 2 #define FOOTER_SETUP_AREA_COPIES 2 /* The length in blocks of the space we put at the start and at the end of the device. This space is used to store N copies of the setup area for the actual encrypted container in between. */ #define HEADER_SECTORS (SETUP_AREA_SECTORS * HEADER_SETUP_AREA_COPIES) #define FOOTER_SECTORS (SETUP_AREA_SECTORS * FOOTER_SETUP_AREA_COPIES) /* Minimim size of the encrypted space in blocks. This is more or less an arbitrary value. */ #define MIN_ENCRYPTED_SPACE 32 /* Some consistency checks for the above constants. */ #if (PHY_SECTOR_SIZE % SECTOR_SIZE) # error the physical secotor size should be a multiple of 512 #endif #if ((SETUP_AREA_SECTORS*SECTOR_SIZE) % PHY_SECTOR_SIZE) # error The setup area size should be a multiple of the phy. sector size. #endif /* * Check whether the block device DEVNAME is used by device mapper. * If EXPECT_BUSY is set no error message is printed if the device is * busy. Returns: 0 if the device is good and not yet used by DM. */ static gpg_error_t check_blockdev (const char *devname, int expect_busy) { gpg_error_t err; struct stat sb; unsigned int devmajor, devminor; char *result = NULL; char **lines = NULL; char **fields = NULL; int lno, count; - if (stat (devname, &sb)) + if (gnupg_stat (devname, &sb)) { err = gpg_error_from_syserror (); log_error ("error stating '%s': %s\n", devname, gpg_strerror (err)); return err; } if (!S_ISBLK (sb.st_mode)) { err = gpg_error (GPG_ERR_ENOTBLK); log_error ("can't use '%s': %s\n", devname, gpg_strerror (err)); return err; } devmajor = major (sb.st_rdev); devminor = minor (sb.st_rdev); { const char *argv[2]; argv[0] = "deps"; argv[1] = NULL; err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL); } if (err) { log_error ("error running '%s' to search for '%s': %s\n", "dmsetup deps", devname, gpg_strerror (err)); goto leave; } lines = strsplit (result, '\n', 0, NULL); if (!lines) { err = gpg_error_from_syserror (); goto leave; } if (lines[0] && !strcmp (lines[0], "No devices found")) ; else { for (lno=0; lines[lno]; lno++) { unsigned int xmajor, xminor; if (!*lines[lno]) continue; xfree (fields); fields = strsplit (lines[lno], ':', 0, &count); if (!fields) { err = gpg_error_from_syserror (); goto leave; } if (count < 3 || sscanf (fields[2], " (%u,%u)", &xmajor, &xminor) != 2) { log_error ("error running '%s' to search for '%s': %s\n", "dmsetup deps", devname, "unexpected output"); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } if (xmajor == devmajor && xminor == devminor) { if (!expect_busy) log_error ("device '%s' (%u:%u)" " already in use by device mapper\n", devname, devmajor, devminor); err = gpg_error (GPG_ERR_EBUSY); goto leave; } } } leave: xfree (fields); xfree (lines); xfree (result); return err; } /* Return a malloced buffer with the prefix of the setup area. This is the data written right before the encrypted keyblob. Return NULL on error and sets ERRNO. */ static void * mk_setup_area_prefix (size_t *r_length) { unsigned char *packet; size_t setuparealen; packet = xtrymalloc (32); if (!packet) return NULL; *r_length = 32; setuparealen = SETUP_AREA_SECTORS * SECTOR_SIZE; packet[0] = (0xc0|61); /* CTB for the private packet type 0x61. */ packet[1] = 0xff; /* 5 byte length packet, value 20. */ packet[2] = 0; packet[3] = 0; packet[4] = 0; packet[5] = 26; memcpy (packet+6, "GnuPG/G13", 10); /* Packet subtype. */ packet[16] = 1; /* G13 packet format version. */ packet[17] = 0; /* Reserved. */ packet[18] = 0; /* Reserved. */ packet[19] = 1; /* OS Flag = Linux */ packet[20] = (setuparealen >> 24); /* Total length of header. */ packet[21] = (setuparealen >> 16); packet[22] = (setuparealen >> 8); packet[23] = (setuparealen); packet[24] = HEADER_SETUP_AREA_COPIES; packet[25] = FOOTER_SETUP_AREA_COPIES; packet[26] = 0; /* Reserved. */ packet[27] = 0; /* Reserved. */ packet[28] = 0; /* Reserved. */ packet[29] = 0; /* Reserved. */ packet[30] = 0; /* Reserved. */ packet[31] = 0; /* Reserved. */ return packet; } /* Create a new g13 styloe DM-Crypt container on devoce DEVNAME. */ gpg_error_t sh_dmcrypt_create_container (ctrl_t ctrl, const char *devname, estream_t devfp) { gpg_error_t err; char *header_space; size_t header_space_size, header_space_used; size_t paddinglen; char *targetname = NULL; size_t nread; char *p; char hexkey[16*2+1]; char *table = NULL; unsigned long long nblocks; char *result = NULL; unsigned char twobyte[2]; membuf_t keyblob; void *keyblob_buf = NULL; size_t keyblob_len; size_t n; const char *s; unsigned char *packet; int copy; if (!ctrl->devti) return gpg_error (GPG_ERR_INV_ARG); g13_syshelp_i_know_what_i_am_doing (); header_space_size = SETUP_AREA_SECTORS * SECTOR_SIZE; header_space = xtrymalloc (header_space_size); if (!header_space) return gpg_error_from_syserror (); /* Start building the keyblob. */ init_membuf (&keyblob, 512); append_tuple (&keyblob, KEYBLOB_TAG_BLOBVERSION, "\x01", 1); n = CONTTYPE_DM_CRYPT; twobyte[0] = (n >> 8); twobyte[1] = n; append_tuple (&keyblob, KEYBLOB_TAG_CONTTYPE, twobyte, 2); { gnupg_isotime_t tbuf; gnupg_get_isotime (tbuf); append_tuple (&keyblob, KEYBLOB_TAG_CREATED, tbuf, strlen (tbuf)); } /* Rewind device stream. */ if (es_fseeko (devfp, 0, SEEK_SET)) { err = gpg_error_from_syserror (); log_error ("error seeking to begin of '%s': %s\n", devname, gpg_strerror (err)); goto leave; } es_clearerr (devfp); /* Extra check that the device is empty. */ if (es_read (devfp, header_space, header_space_size, &nread)) err = gpg_error_from_syserror (); else if (nread != header_space_size) err = gpg_error (GPG_ERR_TOO_SHORT); else err = 0; if (err) { log_error ("error reading header space of '%s': %s\n", devname, gpg_strerror (err)); goto leave; } for (p=header_space; nread && !*p; nread--, p++) ; if (nread) { log_error ("header space of '%s' already used - use %s to override\n", devname, "--force"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* Check that the device is not used by device mapper. */ err = check_blockdev (devname, 0); if (err) goto leave; /* Compute the number of blocks. */ err = sh_blockdev_getsz (devname, &nblocks); if (err) { log_error ("error getting size of '%s': %s\n", devname, gpg_strerror (err)); goto leave; } if (nblocks <= HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS) { log_error ("device '%s' is too small (min=%d blocks)\n", devname, HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS); err = gpg_error (GPG_ERR_TOO_SHORT); goto leave; } append_tuple_uint (&keyblob, KEYBLOB_TAG_CONT_NSEC, nblocks); nblocks -= HEADER_SECTORS + FOOTER_SECTORS; append_tuple_uint (&keyblob, KEYBLOB_TAG_ENC_NSEC, nblocks); append_tuple_uint (&keyblob, KEYBLOB_TAG_ENC_OFF, HEADER_SECTORS); /* Device mapper needs a name for the device: Take it from the label or use "0". */ targetname = strconcat ("g13-", ctrl->client.uname, "-", ctrl->devti->label? ctrl->devti->label : "0", NULL); if (!targetname) { err = gpg_error_from_syserror (); goto leave; } /* Create the key. */ { char key[16]; gcry_randomize (key, sizeof key, GCRY_STRONG_RANDOM); append_tuple (&keyblob, KEYBLOB_TAG_ENCKEY, key, sizeof key); bin2hex (key, 16, hexkey); wipememory (key, 16); /* Add a 2*(4+16) byte filler to conceal the fact that we use AES-128. If we ever want to switch to 256 bit we can resize that filler to keep the keyblob at the same size. */ append_tuple (&keyblob, KEYBLOB_TAG_FILLER, key, sizeof key); append_tuple (&keyblob, KEYBLOB_TAG_FILLER, key, sizeof key); } /* Build dmcrypt table. */ s = "aes-cbc-essiv:sha256"; append_tuple (&keyblob, KEYBLOB_TAG_ALGOSTR, s, strlen (s)); table = es_bsprintf ("0 %llu crypt %s %s 0 %s %d", nblocks, s, hexkey, devname, HEADER_SECTORS); if (!table) { err = gpg_error_from_syserror (); goto leave; } wipememory (hexkey, sizeof hexkey); /* Add a copy of the setup area prefix to the keyblob. */ p = mk_setup_area_prefix (&n); if (!p) { err = gpg_error_from_syserror (); goto leave; } append_tuple (&keyblob, KEYBLOB_TAG_HDRCOPY, p, n); assert (n < header_space_size); memcpy (header_space, p, n); header_space_used = n; /* Turn the keyblob into a buffer and callback to encrypt it. */ keyblob_buf = get_membuf (&keyblob, &keyblob_len); if (!keyblob_buf) { err = gpg_error_from_syserror (); goto leave; } err = sh_encrypt_keyblob (ctrl, keyblob_buf, keyblob_len, &p, &n); if (err) { log_error ("encrypting the keyblob failed: %s\n", gpg_strerror (err)); goto leave; } log_debug ("plain setuparea=%p %zu bytes\n", keyblob_buf, keyblob_len); wipememory (keyblob_buf, keyblob_len); xfree (keyblob_buf); keyblob_buf = NULL; log_debug ("encry setuparea=%p %zu bytes\n", p, n); if (n >= header_space_size || (header_space_used + n) >= header_space_size) { err = gpg_error (GPG_ERR_TOO_LARGE); log_error ("setup area would overflow: %s\n", gpg_strerror (err)); goto leave; } memcpy (header_space + header_space_used, p, n); header_space_used += n; /* Write the padding. */ packet = header_space + header_space_used; paddinglen = header_space_size - header_space_used; if (paddinglen < 16) { err = gpg_error (GPG_ERR_TOO_LARGE); log_error ("setup area too short for padding: %s\n", gpg_strerror (err)); goto leave; } packet[0] = (0xc0|61); /* CTB for Private packet type 0x61. */ packet[1] = 0xff; /* 5 byte length packet, value 20. */ packet[2] = (paddinglen-6) >> 24; packet[3] = (paddinglen-6) >> 16; packet[4] = (paddinglen-6) >> 8; packet[5] = (paddinglen-6); packet += 6; paddinglen -= 6; header_space_used += 6; for ( ;paddinglen >= 10; paddinglen -= 10, packet += 10, header_space_used += 10) memcpy (packet, "GnuPG/PAD", 10); for ( ;paddinglen; paddinglen--, packet++, header_space_used++) *packet = 0; if (header_space_used != header_space_size) BUG (); /* Create the container. */ { const char *argv[3]; argv[0] = "create"; argv[1] = targetname; argv[2] = NULL; log_debug ("now running \"dmsetup create %s\"\n", targetname); log_debug (" with table='%s'\"\n", table); err = gnupg_exec_tool ("/sbin/dmsetup", argv, table, &result, NULL); } if (err) { log_error ("error running dmsetup for '%s': %s\n", devname, gpg_strerror (err)); goto leave; } if (result && *result) log_debug ("dmsetup result: %s\n", result); /* Write the setup area. */ if (es_fseeko (devfp, 0, SEEK_SET)) { err = gpg_error_from_syserror (); log_error ("error seeking to begin of '%s': %s\n", devname, gpg_strerror (err)); goto leave; } es_clearerr (devfp); for (copy = 0; copy < HEADER_SETUP_AREA_COPIES; copy++) { size_t nwritten; if (es_write (devfp, header_space, header_space_size, &nwritten)) { err = gpg_error_from_syserror (); break; } else if (nwritten != header_space_size) { err = gpg_error (GPG_ERR_TOO_SHORT); break; } } if (err) { log_error ("error writing header space copy %d of '%s': %s\n", copy, devname, gpg_strerror (err)); goto leave; } if (es_fseeko (devfp, (- header_space_size * FOOTER_SETUP_AREA_COPIES), SEEK_END)) { err = gpg_error_from_syserror (); log_error ("error seeking to end of '%s': %s\n", devname, gpg_strerror (err)); goto leave; } es_clearerr (devfp); for (copy = 0; copy < FOOTER_SETUP_AREA_COPIES; copy++) { size_t nwritten; if (es_write (devfp, header_space, header_space_size, &nwritten)) { err = gpg_error_from_syserror (); break; } else if (nwritten != header_space_size) { err = gpg_error (GPG_ERR_TOO_SHORT); break; } } if (!err && es_fflush (devfp)) err = gpg_error_from_syserror (); if (err) { log_error ("error writing footer space copy %d of '%s': %s\n", copy, devname, gpg_strerror (err)); goto leave; } leave: wipememory (hexkey, sizeof hexkey); if (table) { wipememory (table, strlen (table)); xfree (table); } if (keyblob_buf) { wipememory (keyblob_buf, keyblob_len); xfree (keyblob_buf); } xfree (get_membuf (&keyblob, NULL)); xfree (targetname); xfree (result); xfree (header_space); return err; } /* Mount a DM-Crypt container on device DEVNAME taking keys and other * meta data from KEYBLOB. */ gpg_error_t sh_dmcrypt_mount_container (ctrl_t ctrl, const char *devname, tupledesc_t keyblob) { gpg_error_t err; char *targetname_abs = NULL; const char *targetname; char hexkey[16*2+1]; char *table = NULL; unsigned long long nblocks, nblocks2; char *result = NULL; size_t n; const char *s; const char *algostr; size_t algostrlen; if (!ctrl->devti) return gpg_error (GPG_ERR_INV_ARG); g13_syshelp_i_know_what_i_am_doing (); /* Check that the device is not yet used by device mapper. */ err = check_blockdev (devname, 0); if (err) goto leave; /* Compute the number of blocks and compare them to the value provided as meta data. */ err = sh_blockdev_getsz (devname, &nblocks); if (err) { log_error ("error getting size of '%s': %s\n", devname, gpg_strerror (err)); goto leave; } err = find_tuple_uint (keyblob, KEYBLOB_TAG_CONT_NSEC, &nblocks2); if (err) { log_error ("error getting size from keyblob: %s\n", gpg_strerror (err)); goto leave; } if (nblocks != nblocks2) { log_error ("inconsistent size of container: expected==%llu got=%llu\n", nblocks2, nblocks); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } if (nblocks <= HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS) { log_error ("device '%s' is too small (min=%d blocks)\n", devname, HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS); err = gpg_error (GPG_ERR_TOO_SHORT); goto leave; } nblocks -= HEADER_SECTORS + FOOTER_SECTORS; err = find_tuple_uint (keyblob, KEYBLOB_TAG_ENC_NSEC, &nblocks2); if (err) { log_error ("error getting enc size from keyblob: %s\n", gpg_strerror (err)); goto leave; } if (nblocks != nblocks2) { log_error ("inconsistent size of enc data: expected==%llu got=%llu\n", nblocks2, nblocks); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } /* Check that the offset is consistent. */ err = find_tuple_uint (keyblob, KEYBLOB_TAG_ENC_OFF, &nblocks2); if (err) { log_error ("error getting enc offset from keyblob: %s\n", gpg_strerror (err)); goto leave; } if (nblocks2 != HEADER_SECTORS) { log_error ("inconsistent offset of enc data: expected==%llu got=%d\n", nblocks2, HEADER_SECTORS); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } /* Device mapper needs a name for the device: Take it from the label or use "0". */ targetname_abs = strconcat ("/dev/mapper/", "g13-", ctrl->client.uname, "-", ctrl->devti->label? ctrl->devti->label : "0", NULL); if (!targetname_abs) { err = gpg_error_from_syserror (); goto leave; } targetname = strrchr (targetname_abs, '/'); if (!targetname) BUG (); targetname++; /* Get the algorithm string. */ algostr = find_tuple (keyblob, KEYBLOB_TAG_ALGOSTR, &algostrlen); if (!algostr || algostrlen > 100) { log_error ("algo string not found in keyblob or too long\n"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } /* Get the key. */ s = find_tuple (keyblob, KEYBLOB_TAG_ENCKEY, &n); if (!s || n != 16) { if (!s) log_error ("no key found in keyblob\n"); else log_error ("unexpected size of key (%zu)\n", n); err = gpg_error (GPG_ERR_INV_KEYLEN); goto leave; } bin2hex (s, 16, hexkey); /* Build dmcrypt table. */ table = es_bsprintf ("0 %llu crypt %.*s %s 0 %s %d", nblocks, (int)algostrlen, algostr, hexkey, devname, HEADER_SECTORS); wipememory (hexkey, sizeof hexkey); if (!table) { err = gpg_error_from_syserror (); goto leave; } /* Load the table. */ { const char *argv[3]; argv[0] = "create"; argv[1] = targetname; argv[2] = NULL; log_debug ("now running \"dmsetup create %s\"\n", targetname); err = gnupg_exec_tool ("/sbin/dmsetup", argv, table, &result, NULL); } if (err) { log_error ("error running dmsetup for '%s': %s\n", devname, gpg_strerror (err)); goto leave; } if (result && *result) log_debug ("dmsetup result: %s\n", result); xfree (result); result = NULL; /* Mount if a mountpoint has been given. */ if (ctrl->devti->mountpoint) { const char *argv[3]; argv[0] = targetname_abs; argv[1] = ctrl->devti->mountpoint; argv[2] = NULL; log_debug ("now running \"mount %s %s\"\n", targetname_abs, ctrl->devti->mountpoint); err = gnupg_exec_tool ("/bin/mount", argv, NULL, &result, NULL); if (err) { log_error ("error running mount: %s\n", gpg_strerror (err)); goto leave; } if (result && *result) /* (We should not see output to stdout). */ log_info ("WARNING: mount returned data on stdout! (%s)\n", result); } leave: wipememory (hexkey, sizeof hexkey); if (table) { wipememory (table, strlen (table)); xfree (table); } xfree (targetname_abs); xfree (result); return err; } /* Unmount a DM-Crypt container on device DEVNAME and wipe the keys. */ gpg_error_t sh_dmcrypt_umount_container (ctrl_t ctrl, const char *devname) { gpg_error_t err; char *targetname_abs = NULL; char *result = NULL; if (!ctrl->devti) return gpg_error (GPG_ERR_INV_ARG); g13_syshelp_i_know_what_i_am_doing (); /* Check that the device is used by device mapper. */ err = check_blockdev (devname, 1); if (gpg_err_code (err) != GPG_ERR_EBUSY) { log_error ("device '%s' is not used by the device mapper: %s\n", devname, gpg_strerror (err)); goto leave; } /* Fixme: Check that this is really a g13 partition. */ /* Device mapper needs a name for the device: Take it from the label or use "0". */ targetname_abs = strconcat ("/dev/mapper/", "g13-", ctrl->client.uname, "-", ctrl->devti->label? ctrl->devti->label : "0", NULL); if (!targetname_abs) { err = gpg_error_from_syserror (); goto leave; } /* Run the regular umount command. */ { const char *argv[2]; argv[0] = targetname_abs; argv[1] = NULL; log_debug ("now running \"umount %s\"\n", targetname_abs); err = gnupg_exec_tool ("/bin/umount", argv, NULL, &result, NULL); } if (err) { log_error ("error running umount: %s\n", gpg_strerror (err)); if (1) { /* Try to show some info about processes using the partition. */ const char *argv[3]; argv[0] = "-mv"; argv[1] = targetname_abs; argv[2] = NULL; gnupg_exec_tool ("/bin/fuser", argv, NULL, &result, NULL); } goto leave; } if (result && *result) /* (We should not see output to stdout). */ log_info ("WARNING: umount returned data on stdout! (%s)\n", result); xfree (result); result = NULL; /* Run the dmsetup remove command. */ { const char *argv[3]; argv[0] = "remove"; argv[1] = targetname_abs; argv[2] = NULL; log_debug ("now running \"dmsetup remove %s\"\n", targetname_abs); err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL); } if (err) { log_error ("error running \"dmsetup remove %s\": %s\n", targetname_abs, gpg_strerror (err)); goto leave; } if (result && *result) log_debug ("dmsetup result: %s\n", result); xfree (result); result = NULL; leave: xfree (targetname_abs); xfree (result); return err; } /* Suspend a DM-Crypt container on device DEVNAME and wipe the keys. */ gpg_error_t sh_dmcrypt_suspend_container (ctrl_t ctrl, const char *devname) { gpg_error_t err; char *targetname_abs = NULL; const char *targetname; char *result = NULL; if (!ctrl->devti) return gpg_error (GPG_ERR_INV_ARG); g13_syshelp_i_know_what_i_am_doing (); /* Check that the device is used by device mapper. */ err = check_blockdev (devname, 1); if (gpg_err_code (err) != GPG_ERR_EBUSY) { log_error ("device '%s' is not used by the device mapper: %s\n", devname, gpg_strerror (err)); goto leave; } /* Fixme: Check that this is really a g13 partition. */ /* Device mapper needs a name for the device: Take it from the label or use "0". */ targetname_abs = strconcat ("/dev/mapper/", "g13-", ctrl->client.uname, "-", ctrl->devti->label? ctrl->devti->label : "0", NULL); if (!targetname_abs) { err = gpg_error_from_syserror (); goto leave; } targetname = strrchr (targetname_abs, '/'); if (!targetname) BUG (); targetname++; /* Send the suspend command. */ { const char *argv[3]; argv[0] = "suspend"; argv[1] = targetname; argv[2] = NULL; log_debug ("now running \"dmsetup suspend %s\"\n", targetname); err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL); } if (err) { log_error ("error running \"dmsetup suspend %s\": %s\n", targetname, gpg_strerror (err)); goto leave; } if (result && *result) log_debug ("dmsetup result: %s\n", result); xfree (result); result = NULL; /* Send the wipe key command. */ { const char *argv[5]; argv[0] = "message"; argv[1] = targetname; argv[2] = "0"; argv[3] = "key wipe"; argv[4] = NULL; log_debug ("now running \"dmsetup message %s 0 key wipe\"\n", targetname); err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL); } if (err) { log_error ("error running \"dmsetup message %s 0 key wipe\": %s\n", targetname, gpg_strerror (err)); goto leave; } if (result && *result) log_debug ("dmsetup result: %s\n", result); xfree (result); result = NULL; leave: xfree (targetname_abs); xfree (result); return err; } /* Resume a DM-Crypt container on device DEVNAME taking keys and other * meta data from KEYBLOB. */ gpg_error_t sh_dmcrypt_resume_container (ctrl_t ctrl, const char *devname, tupledesc_t keyblob) { gpg_error_t err; char *targetname_abs = NULL; const char *targetname; char hexkey[8+16*2+1]; /* 8 is used to prepend "key set ". */ char *table = NULL; char *result = NULL; size_t n; const char *s; const char *algostr; size_t algostrlen; if (!ctrl->devti) return gpg_error (GPG_ERR_INV_ARG); g13_syshelp_i_know_what_i_am_doing (); /* Check that the device is used by device mapper. */ err = check_blockdev (devname, 1); if (gpg_err_code (err) != GPG_ERR_EBUSY) { log_error ("device '%s' is not used by the device mapper: %s\n", devname, gpg_strerror (err)); goto leave; } /* Device mapper needs a name for the device: Take it from the label or use "0". */ targetname_abs = strconcat ("/dev/mapper/", "g13-", ctrl->client.uname, "-", ctrl->devti->label? ctrl->devti->label : "0", NULL); if (!targetname_abs) { err = gpg_error_from_syserror (); goto leave; } targetname = strrchr (targetname_abs, '/'); if (!targetname) BUG (); targetname++; /* Get the algorithm string. */ algostr = find_tuple (keyblob, KEYBLOB_TAG_ALGOSTR, &algostrlen); if (!algostr || algostrlen > 100) { log_error ("algo string not found in keyblob or too long\n"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } /* Get the key. */ s = find_tuple (keyblob, KEYBLOB_TAG_ENCKEY, &n); if (!s || n != 16) { if (!s) log_error ("no key found in keyblob\n"); else log_error ("unexpected size of key (%zu)\n", n); err = gpg_error (GPG_ERR_INV_KEYLEN); goto leave; } strcpy (hexkey, "key set "); bin2hex (s, 16, hexkey+8); /* Send the key */ { const char *argv[4]; argv[0] = "message"; argv[1] = targetname; argv[2] = "0"; argv[3] = NULL; log_debug ("now running \"dmsetup message %s 0 [key set]\"\n", targetname); err = gnupg_exec_tool ("/sbin/dmsetup", argv, hexkey, &result, NULL); } wipememory (hexkey, sizeof hexkey); if (err) { log_error ("error running \"dmsetup message %s 0 [key set]\": %s\n", devname, gpg_strerror (err)); goto leave; } if (result && *result) log_debug ("dmsetup result: %s\n", result); xfree (result); result = NULL; /* Send the resume command. */ { const char *argv[3]; argv[0] = "resume"; argv[1] = targetname; argv[2] = NULL; log_debug ("now running \"dmsetup resume %s\"\n", targetname); err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL); } if (err) { log_error ("error running \"dmsetup resume %s\": %s\n", targetname, gpg_strerror (err)); goto leave; } if (result && *result) log_debug ("dmsetup result: %s\n", result); xfree (result); result = NULL; leave: wipememory (hexkey, sizeof hexkey); if (table) { wipememory (table, strlen (table)); xfree (table); } xfree (targetname_abs); xfree (result); return err; } diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 5ec670e57..56768aa7e 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -1,1591 +1,1591 @@ /* gpg-wks-client.c - A client for the Web Key Service protocols. * Copyright (C) 2016 Werner Koch * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * 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 Lesser 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 . */ #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/mbox-util.h" #include "../common/name-value.h" #include "call-dirmngr.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oOutput = 'o', oDirectory = 'C', oDebug = 500, aSupported, aCheck, aCreate, aReceive, aRead, aInstallKey, aRemoveKey, aPrintWKDHash, aPrintWKDURL, oGpgProgram, oSend, oFakeSubmissionAddr, oStatusFD, oWithColons, oDummy }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, ("@Commands:\n ")), ARGPARSE_c (aSupported, "supported", ("check whether provider supports WKS")), ARGPARSE_c (aCheck, "check", ("check whether a key is available")), ARGPARSE_c (aCreate, "create", ("create a publication request")), ARGPARSE_c (aReceive, "receive", ("receive a MIME confirmation request")), ARGPARSE_c (aRead, "read", ("receive a plain text confirmation request")), ARGPARSE_c (aInstallKey, "install-key", "install a key into a directory"), ARGPARSE_c (aRemoveKey, "remove-key", "remove a key from a directory"), ARGPARSE_c (aPrintWKDHash, "print-wkd-hash", "Print the WKD identifier for the given user ids"), ARGPARSE_c (aPrintWKDURL, "print-wkd-url", "Print the WKD URL for the given user id"), ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_s (oDirectory, "directory", "@"), ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MIME_VALUE , "mime" }, { DBG_PARSER_VALUE , "parser" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* Value of the option --fake-submission-addr. */ const char *fake_submission_addr; static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; static gpg_error_t proc_userid_from_stdin (gpg_error_t (*func)(const char *), const char *text); static gpg_error_t command_supported (char *userid); static gpg_error_t command_check (char *userid); static gpg_error_t command_send (const char *fingerprint, const char *userid); static gpg_error_t encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, const char *fingerprint); static gpg_error_t read_confirmation_request (estream_t msg); static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t fp, unsigned int flags); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "gpg-wks-client"; break; case 12: p = "@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-wks-client [command] [options] [args] (-h for help)"); break; case 41: p = ("Syntax: gpg-wks-client [command] [options] [args]\n" "Client for the Web Key Service\n"); break; default: p = NULL; break; } return p; } static void wrong_args (const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text); exit (2); } /* Command line parsing. */ static enum cmd_and_opt_values parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) { enum cmd_and_opt_values cmd = 0; int no_more_options = 0; while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: 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 oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oDirectory: opt.directory = pargs->r.ret_str; break; case oSend: opt.use_sendmail = 1; break; case oOutput: opt.output = pargs->r.ret_str; break; case oFakeSubmissionAddr: fake_submission_addr = pargs->r.ret_str; break; case oStatusFD: wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; case oWithColons: opt.with_colons = 1; break; case aSupported: case aCreate: case aReceive: case aRead: case aCheck: case aInstallKey: case aRemoveKey: case aPrintWKDHash: case aPrintWKDURL: cmd = pargs->r_opt; break; default: pargs->err = 2; break; } } return cmd; } /* gpg-wks-client main. */ int main (int argc, char **argv) { gpg_error_t err, delayed_err; ARGPARSE_ARGS pargs; enum cmd_and_opt_values cmd; gnupg_reopen_std ("gpg-wks-client"); set_strusage (my_strusage); log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; cmd = parse_arguments (&pargs, opts); if (log_get_errorcount (0)) exit (2); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (("NOTE: '%s' is not considered an option\n"), argv[i]); } /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.directory) opt.directory = "openpgpkey"; /* Tell call-dirmngr what options we want. */ set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1); /* Check that the top directory exists. */ if (cmd == aInstallKey || cmd == aRemoveKey) { struct stat sb; - if (stat (opt.directory, &sb)) + if (gnupg_stat (opt.directory, &sb)) { err = gpg_error_from_syserror (); log_error ("error accessing directory '%s': %s\n", opt.directory, gpg_strerror (err)); goto leave; } if (!S_ISDIR(sb.st_mode)) { log_error ("error accessing directory '%s': %s\n", opt.directory, "not a directory"); err = gpg_error (GPG_ERR_ENOENT); goto leave; } } /* Run the selected command. */ switch (cmd) { case aSupported: if (opt.with_colons) { for (; argc; argc--, argv++) command_supported (*argv); err = 0; } else { if (argc != 1) wrong_args ("--supported DOMAIN"); err = command_supported (argv[0]); if (err && gpg_err_code (err) != GPG_ERR_FALSE) log_error ("checking support failed: %s\n", gpg_strerror (err)); } break; case aCreate: if (argc != 2) wrong_args ("--create FINGERPRINT USER-ID"); err = command_send (argv[0], argv[1]); if (err) log_error ("creating request failed: %s\n", gpg_strerror (err)); break; case aReceive: if (argc) wrong_args ("--receive < MIME-DATA"); err = wks_receive (es_stdin, command_receive_cb, NULL); if (err) log_error ("processing mail failed: %s\n", gpg_strerror (err)); break; case aRead: if (argc) wrong_args ("--read < WKS-DATA"); err = read_confirmation_request (es_stdin); if (err) log_error ("processing mail failed: %s\n", gpg_strerror (err)); break; case aCheck: if (argc != 1) wrong_args ("--check USER-ID"); err = command_check (argv[0]); break; case aInstallKey: if (!argc) err = wks_cmd_install_key (NULL, NULL); else if (argc == 2) err = wks_cmd_install_key (*argv, argv[1]); else wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); break; case aRemoveKey: if (argc != 1) wrong_args ("--remove-key USER-ID"); err = wks_cmd_remove_key (*argv); break; case aPrintWKDHash: case aPrintWKDURL: if (!argc) { if (cmd == aPrintWKDHash) err = proc_userid_from_stdin (wks_cmd_print_wkd_hash, "printing WKD hash"); else err = proc_userid_from_stdin (wks_cmd_print_wkd_url, "printing WKD URL"); } else { for (err = delayed_err = 0; !err && argc; argc--, argv++) { if (cmd == aPrintWKDHash) err = wks_cmd_print_wkd_hash (*argv); else err = wks_cmd_print_wkd_url (*argv); if (gpg_err_code (err) == GPG_ERR_INV_USER_ID) { /* Diagnostic already printed. */ delayed_err = err; err = 0; } else if (err) log_error ("printing hash failed: %s\n", gpg_strerror (err)); } if (!err) err = delayed_err; } break; default: usage (1); err = 0; break; } leave: if (err) wks_write_status (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) wks_write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else wks_write_status (STATUS_SUCCESS, NULL); return (err || log_get_errorcount (0))? 1:0; } /* Read user ids from stdin and call FUNC for each user id. TEXT is * used for error messages. */ static gpg_error_t proc_userid_from_stdin (gpg_error_t (*func)(const char *), const char *text) { gpg_error_t err = 0; gpg_error_t delayed_err = 0; char line[2048]; size_t n = 0; /* If we are on a terminal disable buffering to get direct response. */ if (gnupg_isatty (es_fileno (es_stdin)) && gnupg_isatty (es_fileno (es_stdout))) { es_setvbuf (es_stdin, NULL, _IONBF, 0); es_setvbuf (es_stdout, NULL, _IOLBF, 0); } while (es_fgets (line, sizeof line - 1, es_stdin)) { n = strlen (line); if (!n || line[n-1] != '\n') { err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); log_error ("error reading stdin: %s\n", gpg_strerror (err)); break; } trim_spaces (line); err = func (line); if (gpg_err_code (err) == GPG_ERR_INV_USER_ID) { delayed_err = err; err = 0; } else if (err) log_error ("%s failed: %s\n", text, gpg_strerror (err)); } if (es_ferror (es_stdin)) { err = gpg_error_from_syserror (); log_error ("error reading stdin: %s\n", gpg_strerror (err)); goto leave; } leave: if (!err) err = delayed_err; return err; } /* Add the user id UID to the key identified by FINGERPRINT. */ static gpg_error_t add_user_id (const char *fingerprint, const char *uid) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--quick-add-uid"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, uid); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("adding user id failed: %s\n", gpg_strerror (err)); goto leave; } leave: xfree (argv); return err; } struct decrypt_stream_parm_s { char *fpr; char *mainfpr; int otrust; }; static void decrypt_stream_status_cb (void *opaque, const char *keyword, char *args) { struct decrypt_stream_parm_s *decinfo = opaque; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); if (!strcmp (keyword, "DECRYPTION_KEY") && !decinfo->fpr) { char *fields[3]; if (split_fields (args, fields, DIM (fields)) >= 3) { decinfo->fpr = xstrdup (fields[0]); decinfo->mainfpr = xstrdup (fields[1]); decinfo->otrust = *fields[2]; } } } /* Decrypt the INPUT stream to a new stream which is stored at success * at R_OUTPUT. */ static gpg_error_t decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo, estream_t input) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t output; *r_output = NULL; memset (decinfo, 0, sizeof *decinfo); output = es_fopenmem (0, "w+b"); if (!output) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); /* We limit the output to 64 KiB to avoid DoS using compression * tricks. A regular client will anyway only send a minimal key; * that is one w/o key signatures and attribute packets. */ ccparray_put (&ccp, "--max-output=0x10000"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--decrypt"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, NULL, output, decrypt_stream_status_cb, decinfo); if (!err && (!decinfo->fpr || !decinfo->mainfpr || !decinfo->otrust)) err = gpg_error (GPG_ERR_INV_ENGINE); if (err) { log_error ("decryption failed: %s\n", gpg_strerror (err)); goto leave; } else if (opt.verbose) log_info ("decryption succeeded\n"); es_rewind (output); *r_output = output; output = NULL; leave: if (err) { xfree (decinfo->fpr); xfree (decinfo->mainfpr); memset (decinfo, 0, sizeof *decinfo); } es_fclose (output); xfree (argv); return err; } /* Return the submission address for the address or just the domain in * ADDRSPEC. The submission address is stored as a malloced string at * R_SUBMISSION_ADDRESS. At R_POLICY the policy flags of the domain * are stored. The caller needs to free them with wks_free_policy. * The function returns an error code on failure to find a submission * address or policy file. Note: The function may store NULL at * R_SUBMISSION_ADDRESS but return success to indicate that the web * key directory is supported but not the web key service. As per WKD * specs a policy file is always required and will thus be return on * success. */ static gpg_error_t get_policy_and_sa (const char *addrspec, int silent, policy_flags_t *r_policy, char **r_submission_address) { gpg_error_t err; estream_t mbuf = NULL; const char *domain; const char *s; policy_flags_t policy = NULL; char *submission_to = NULL; *r_submission_address = NULL; *r_policy = NULL; domain = strchr (addrspec, '@'); if (domain) domain++; if (opt.with_colons) { s = domain? domain : addrspec; es_write_sanitized (es_stdout, s, strlen (s), ":", NULL); es_putc (':', es_stdout); } /* We first try to get the submission address from the policy file * (this is the new method). If both are available we check that * they match and print a warning if not. In the latter case we * keep on using the one from the submission-address file. */ err = wkd_get_policy_flags (addrspec, &mbuf); if (err && gpg_err_code (err) != GPG_ERR_NO_DATA && gpg_err_code (err) != GPG_ERR_NO_NAME) { if (!opt.with_colons) log_error ("error reading policy flags for '%s': %s\n", domain, gpg_strerror (err)); goto leave; } if (!mbuf) { if (!opt.with_colons) log_error ("provider for '%s' does NOT support the Web Key Directory\n", addrspec); err = gpg_error (GPG_ERR_FALSE); goto leave; } policy = xtrycalloc (1, sizeof *policy); if (!policy) err = gpg_error_from_syserror (); else err = wks_parse_policy (policy, mbuf, 1); es_fclose (mbuf); mbuf = NULL; if (err) goto leave; err = wkd_get_submission_address (addrspec, &submission_to); if (err && !policy->submission_address) { if (!silent && !opt.with_colons) log_error (_("error looking up submission address for domain '%s'" ": %s\n"), domain, gpg_strerror (err)); if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons) log_error (_("this domain probably doesn't support WKS.\n")); goto leave; } if (submission_to && policy->submission_address && ascii_strcasecmp (submission_to, policy->submission_address)) log_info ("Warning: different submission addresses (sa=%s, po=%s)\n", submission_to, policy->submission_address); if (!submission_to && policy->submission_address) { submission_to = xtrystrdup (policy->submission_address); if (!submission_to) { err = gpg_error_from_syserror (); goto leave; } } leave: *r_submission_address = submission_to; submission_to = NULL; *r_policy = policy; policy = NULL; if (opt.with_colons) { if (*r_policy && !*r_submission_address) es_fprintf (es_stdout, "1:0::"); else if (*r_policy && *r_submission_address) es_fprintf (es_stdout, "1:1::"); else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE || gpg_err_code (err) == GPG_ERR_NO_DATA || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)) es_fprintf (es_stdout, "0:0:%d:", err); else es_fprintf (es_stdout, "0:0::"); if (*r_policy) { es_fprintf (es_stdout, "%u:%u:%u:", (*r_policy)->protocol_version, (*r_policy)->auth_submit, (*r_policy)->mailbox_only); } es_putc ('\n', es_stdout); } xfree (submission_to); wks_free_policy (policy); xfree (policy); es_fclose (mbuf); return err; } /* Check whether the provider supports the WKS protocol. */ static gpg_error_t command_supported (char *userid) { gpg_error_t err; char *addrspec = NULL; char *submission_to = NULL; policy_flags_t policy = NULL; if (!strchr (userid, '@')) { char *tmp = xstrconcat ("foo@", userid, NULL); addrspec = mailbox_from_userid (tmp); xfree (tmp); } else addrspec = mailbox_from_userid (userid); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } /* Get the submission address. */ err = get_policy_and_sa (addrspec, 1, &policy, &submission_to); if (err || !submission_to) { if (!submission_to || gpg_err_code (err) == GPG_ERR_FALSE || gpg_err_code (err) == GPG_ERR_NO_DATA || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST ) { /* FALSE is returned if we already figured out that even the * Web Key Directory is not supported and thus printed an * error message. */ if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE && !opt.with_colons) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) log_info ("provider for '%s' does NOT support WKS\n", addrspec); else log_info ("provider for '%s' does NOT support WKS (%s)\n", addrspec, gpg_strerror (err)); } err = gpg_error (GPG_ERR_FALSE); if (!opt.with_colons) log_inc_errorcount (); } goto leave; } if (opt.verbose && !opt.with_colons) log_info ("provider for '%s' supports WKS\n", addrspec); leave: wks_free_policy (policy); xfree (policy); xfree (submission_to); xfree (addrspec); return err; } /* Check whether the key for USERID is available in the WKD. */ static gpg_error_t command_check (char *userid) { gpg_error_t err; char *addrspec = NULL; estream_t key = NULL; char *fpr = NULL; uidinfo_list_t mboxes = NULL; uidinfo_list_t sl; int found = 0; addrspec = mailbox_from_userid (userid); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } /* Get the submission address. */ err = wkd_get_key (addrspec, &key); switch (gpg_err_code (err)) { case 0: if (opt.verbose) log_info ("public key for '%s' found via WKD\n", addrspec); /* Fixme: Check that the key contains the user id. */ break; case GPG_ERR_NO_DATA: /* No such key. */ if (opt.verbose) log_info ("public key for '%s' NOT found via WKD\n", addrspec); err = gpg_error (GPG_ERR_NO_PUBKEY); log_inc_errorcount (); break; case GPG_ERR_UNKNOWN_HOST: if (opt.verbose) log_info ("error looking up '%s' via WKD: %s\n", addrspec, gpg_strerror (err)); err = gpg_error (GPG_ERR_NOT_SUPPORTED); break; default: log_error ("error looking up '%s' via WKD: %s\n", addrspec, gpg_strerror (err)); break; } if (err) goto leave; /* Look closer at the key. */ err = wks_list_key (key, &fpr, &mboxes); if (err) { log_error ("error parsing key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (opt.verbose) log_info ("fingerprint: %s\n", fpr); for (sl = mboxes; sl; sl = sl->next) { if (sl->mbox && !strcmp (sl->mbox, addrspec)) found = 1; if (opt.verbose) { log_info (" user-id: %s\n", sl->uid); log_info (" created: %s\n", asctimestamp (sl->created)); if (sl->mbox) log_info (" addr-spec: %s\n", sl->mbox); } } if (!found) { log_error ("public key for '%s' has no user id with the mail address\n", addrspec); err = gpg_error (GPG_ERR_CERT_REVOKED); } leave: xfree (fpr); free_uidinfo_list (mboxes); es_fclose (key); xfree (addrspec); return err; } /* Locate the key by fingerprint and userid and send a publication * request. */ static gpg_error_t command_send (const char *fingerprint, const char *userid) { gpg_error_t err; KEYDB_SEARCH_DESC desc; char *addrspec = NULL; estream_t key = NULL; estream_t keyenc = NULL; char *submission_to = NULL; mime_maker_t mime = NULL; policy_flags_t policy = NULL; int no_encrypt = 0; int posteo_hack = 0; const char *domain; uidinfo_list_t uidlist = NULL; uidinfo_list_t uid, thisuid; time_t thistime; if (classify_user_id (fingerprint, &desc, 1) || !(desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_FPR20)) { log_error (_("\"%s\" is not a fingerprint\n"), fingerprint); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } addrspec = mailbox_from_userid (userid); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } err = wks_get_key (&key, fingerprint, addrspec, 0); if (err) goto leave; domain = strchr (addrspec, '@'); log_assert (domain); domain++; /* Get the submission address. */ if (fake_submission_addr) { policy = xcalloc (1, sizeof *policy); submission_to = xstrdup (fake_submission_addr); err = 0; } else { err = get_policy_and_sa (addrspec, 0, &policy, &submission_to); if (err) goto leave; if (!submission_to) { log_error (_("this domain probably doesn't support WKS.\n")); err = gpg_error (GPG_ERR_NO_DATA); goto leave; } } log_info ("submitting request to '%s'\n", submission_to); if (policy->auth_submit) log_info ("no confirmation required for '%s'\n", addrspec); /* In case the key has several uids with the same addr-spec we will * use the newest one. */ err = wks_list_key (key, NULL, &uidlist); if (err) { log_error ("error parsing key: %s\n",gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } thistime = 0; thisuid = NULL; for (uid = uidlist; uid; uid = uid->next) { if (!uid->mbox) continue; /* Should not happen anyway. */ if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox)) continue; /* UID has more than just the mailbox. */ if (uid->created > thistime) { thistime = uid->created; thisuid = uid; } } if (!thisuid) thisuid = uidlist; /* This is the case for a missing timestamp. */ if (opt.verbose) log_info ("submitting key with user id '%s'\n", thisuid->uid); /* If we have more than one user id we need to filter the key to * include only THISUID. */ if (uidlist->next) { estream_t newkey; es_rewind (key); err = wks_filter_uid (&newkey, key, thisuid->uid, 0); if (err) { log_error ("error filtering key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } es_fclose (key); key = newkey; } if (policy->mailbox_only && (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox))) { log_info ("Warning: policy requires 'mailbox-only'" " - adding user id '%s'\n", addrspec); err = add_user_id (fingerprint, addrspec); if (err) goto leave; /* Need to get the key again. This time we request filtering * for the full user id, so that we do not need check and filter * the key again. */ es_fclose (key); key = NULL; err = wks_get_key (&key, fingerprint, addrspec, 1); if (err) goto leave; } /* Hack to support posteo but let them disable this by setting the * new policy-version flag. */ if (policy->protocol_version < 3 && !ascii_strcasecmp (domain, "posteo.de")) { log_info ("Warning: Using draft-1 method for domain '%s'\n", domain); no_encrypt = 1; posteo_hack = 1; } /* Encrypt the key part. */ if (!no_encrypt) { es_rewind (key); err = encrypt_response (&keyenc, key, submission_to, fingerprint); if (err) goto leave; es_fclose (key); key = NULL; } /* Send the key. */ err = mime_maker_new (&mime, NULL); if (err) goto leave; err = mime_maker_add_header (mime, "From", addrspec); if (err) goto leave; err = mime_maker_add_header (mime, "To", submission_to); if (err) goto leave; err = mime_maker_add_header (mime, "Subject", "Key publishing request"); if (err) goto leave; /* Tell server which draft we support. */ err = mime_maker_add_header (mime, "Wks-Draft-Version", STR2(WKS_DRAFT_VERSION)); if (err) goto leave; if (no_encrypt) { void *data; size_t datalen, n; if (posteo_hack) { /* Needs a multipart/mixed with one(!) attachment. It does * not grok a non-multipart mail. */ err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed"); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; } err = mime_maker_add_header (mime, "Content-type", "application/pgp-keys"); if (err) goto leave; if (es_fclose_snatch (key, &data, &datalen)) { err = gpg_error_from_syserror (); goto leave; } key = NULL; /* We need to skip over the first line which has a content-type * header not needed here. */ for (n=0; n < datalen ; n++) if (((const char *)data)[n] == '\n') { n++; break; } err = mime_maker_add_body_data (mime, (char*)data + n, datalen - n); xfree (data); if (err) goto leave; } else { err = mime_maker_add_header (mime, "Content-Type", "multipart/encrypted; " "protocol=\"application/pgp-encrypted\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-encrypted"); if (err) goto leave; err = mime_maker_add_body (mime, "Version: 1\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/octet-stream"); if (err) goto leave; err = mime_maker_add_stream (mime, &keyenc); if (err) goto leave; } err = wks_send_mime (mime); leave: mime_maker_release (mime); xfree (submission_to); free_uidinfo_list (uidlist); es_fclose (keyenc); es_fclose (key); wks_free_policy (policy); xfree (policy); xfree (addrspec); return err; } static void encrypt_response_status_cb (void *opaque, const char *keyword, char *args) { gpg_error_t *failure = opaque; char *fields[2]; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); if (!strcmp (keyword, "FAILURE")) { if (split_fields (args, fields, DIM (fields)) >= 2 && !strcmp (fields[0], "encrypt")) *failure = strtoul (fields[1], NULL, 10); } } /* Encrypt the INPUT stream to a new stream which is stored at success * at R_OUTPUT. Encryption is done for ADDRSPEC and for FINGERPRINT * (so that the sent message may later be inspected by the user). We * currently retrieve that key from the WKD, DANE, or from "local". * "local" is last to prefer the latest key version but use a local * copy in case we are working offline. It might be useful for the * server to send the fingerprint of its encryption key - or even the * entire key back. */ static gpg_error_t encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, const char *fingerprint) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t output; gpg_error_t gpg_err = 0; *r_output = NULL; output = es_fopenmem (0, "w+b"); if (!output) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ if (fake_submission_addr) ccparray_put (&ccp, "--auto-key-locate=clear,local"); else ccparray_put (&ccp, "--auto-key-locate=clear,wkd,dane,local"); ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, addrspec); ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, "--encrypt"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, NULL, output, encrypt_response_status_cb, &gpg_err); if (err) { if (gpg_err) err = gpg_err; log_error ("encryption failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (output); *r_output = output; output = NULL; leave: es_fclose (output); xfree (argv); return err; } static gpg_error_t send_confirmation_response (const char *sender, const char *address, const char *nonce, int encrypt, const char *fingerprint) { gpg_error_t err; estream_t body = NULL; estream_t bodyenc = NULL; mime_maker_t mime = NULL; body = es_fopenmem (0, "w+b"); if (!body) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } /* It is fine to use 8 bit encoding because that is encrypted and * only our client will see it. */ if (encrypt) { es_fputs ("Content-Type: application/vnd.gnupg.wks\n" "Content-Transfer-Encoding: 8bit\n" "\n", body); } es_fprintf (body, ("type: confirmation-response\n" "sender: %s\n" "address: %s\n" "nonce: %s\n"), sender, address, nonce); es_rewind (body); if (encrypt) { err = encrypt_response (&bodyenc, body, sender, fingerprint); if (err) goto leave; es_fclose (body); body = NULL; } err = mime_maker_new (&mime, NULL); if (err) goto leave; err = mime_maker_add_header (mime, "From", address); if (err) goto leave; err = mime_maker_add_header (mime, "To", sender); if (err) goto leave; err = mime_maker_add_header (mime, "Subject", "Key publication confirmation"); if (err) goto leave; err = mime_maker_add_header (mime, "Wks-Draft-Version", STR2(WKS_DRAFT_VERSION)); if (err) goto leave; if (encrypt) { err = mime_maker_add_header (mime, "Content-Type", "multipart/encrypted; " "protocol=\"application/pgp-encrypted\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-encrypted"); if (err) goto leave; err = mime_maker_add_body (mime, "Version: 1\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/octet-stream"); if (err) goto leave; err = mime_maker_add_stream (mime, &bodyenc); if (err) goto leave; } else { err = mime_maker_add_header (mime, "Content-Type", "application/vnd.gnupg.wks"); if (err) goto leave; err = mime_maker_add_stream (mime, &body); if (err) goto leave; } err = wks_send_mime (mime); leave: mime_maker_release (mime); es_fclose (bodyenc); es_fclose (body); return err; } /* Reply to a confirmation request. The MSG has already been * decrypted and we only need to send the nonce back. MAINFPR is * either NULL or the primary key fingerprint of the key used to * decrypt the request. */ static gpg_error_t process_confirmation_request (estream_t msg, const char *mainfpr) { gpg_error_t err; nvc_t nvc; nve_t item; const char *value, *sender, *address, *fingerprint, *nonce; err = nvc_parse (&nvc, NULL, msg); if (err) { log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err)); goto leave; } if (DBG_MIME) { log_debug ("request follows:\n"); nvc_write (nvc, log_get_stream ()); } /* Check that this is a confirmation request. */ if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item)) && !strcmp (value, "confirmation-request"))) { if (item && value) log_error ("received unexpected wks message '%s'\n", value); else log_error ("received invalid wks message: %s\n", "'type' missing"); err = gpg_error (GPG_ERR_UNEXPECTED_MSG); goto leave; } /* Get the fingerprint. */ if (!((item = nvc_lookup (nvc, "fingerprint:")) && (value = nve_value (item)) && strlen (value) >= 40)) { log_error ("received invalid wks message: %s\n", "'fingerprint' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } fingerprint = value; /* Check that the fingerprint matches the key used to decrypt the * message. In --read mode or with the old format we don't have the * decryption key; thus we can't bail out. */ if (!mainfpr || ascii_strcasecmp (mainfpr, fingerprint)) { log_info ("target fingerprint: %s\n", fingerprint); log_info ("but decrypted with: %s\n", mainfpr); log_error ("confirmation request not decrypted with target key\n"); if (mainfpr) { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } } /* Get the address. */ if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item)) && is_valid_mailbox (value))) { log_error ("received invalid wks message: %s\n", "'address' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } address = value; /* FIXME: Check that the "address" matches the User ID we want to * publish. */ /* Get the sender. */ if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item)) && is_valid_mailbox (value))) { log_error ("received invalid wks message: %s\n", "'sender' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } sender = value; /* FIXME: Check that the "sender" matches the From: address. */ /* Get the nonce. */ if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item)) && strlen (value) > 16)) { log_error ("received invalid wks message: %s\n", "'nonce' missing or too short"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } nonce = value; /* Send the confirmation. If no key was found, try again without * encryption. */ err = send_confirmation_response (sender, address, nonce, 1, fingerprint); if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) { log_info ("no encryption key found - sending response in the clear\n"); err = send_confirmation_response (sender, address, nonce, 0, NULL); } leave: nvc_release (nvc); return err; } /* Read a confirmation request and decrypt it if needed. This * function may not be used with a mail or MIME message but only with * the actual encrypted or plaintext WKS data. */ static gpg_error_t read_confirmation_request (estream_t msg) { gpg_error_t err; int c; estream_t plaintext = NULL; /* We take a really simple approach to check whether MSG is * encrypted: We know that an encrypted message is always armored * and thus starts with a few dashes. It is even sufficient to * check for a single dash, because that can never be a proper first * WKS data octet. We need to skip leading spaces, though. */ while ((c = es_fgetc (msg)) == ' ' || c == '\t' || c == '\r' || c == '\n') ; if (c == EOF) { log_error ("can't process an empty message\n"); return gpg_error (GPG_ERR_INV_DATA); } if (es_ungetc (c, msg) != c) { log_error ("error ungetting octet from message\n"); return gpg_error (GPG_ERR_INTERNAL); } if (c != '-') err = process_confirmation_request (msg, NULL); else { struct decrypt_stream_parm_s decinfo; err = decrypt_stream (&plaintext, &decinfo, msg); if (err) log_error ("decryption failed: %s\n", gpg_strerror (err)); else if (decinfo.otrust != 'u') { err = gpg_error (GPG_ERR_WRONG_SECKEY); log_error ("key used to decrypt the confirmation request" " was not generated by us\n"); } else err = process_confirmation_request (plaintext, decinfo.mainfpr); xfree (decinfo.fpr); xfree (decinfo.mainfpr); } es_fclose (plaintext); return err; } /* Called from the MIME receiver to process the plain text data in MSG. */ static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t msg, unsigned int flags) { gpg_error_t err; (void)opaque; (void)flags; if (!strcmp (mediatype, "application/vnd.gnupg.wks")) err = read_confirmation_request (msg); else { log_info ("ignoring unexpected message of type '%s'\n", mediatype); err = gpg_error (GPG_ERR_UNEXPECTED_MSG); } return err; } diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index 6d18f499f..6e5e9eb82 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -1,1986 +1,1986 @@ /* gpg-wks-server.c - A server for the Web Key Service protocols. * Copyright (C) 2016, 2018 Werner Koch * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * 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 Lesser 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 . */ /* The Web Key Service I-D defines an update protocol to store a * public key in the Web Key Directory. The current specification is * draft-koch-openpgp-webkey-service-05.txt. */ #include #include #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/zb32.h" #include "../common/mbox-util.h" #include "../common/name-value.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" /* The time we wait for a confirmation response. */ #define PENDING_TTL (86400 * 3) /* 3 days. */ /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oOutput = 'o', oDirectory = 'C', oDebug = 500, aReceive, aCron, aListDomains, aInstallKey, aRevokeKey, aRemoveKey, aCheck, oGpgProgram, oSend, oFrom, oHeader, oWithDir, oWithFile, oDummy }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, ("@Commands:\n ")), ARGPARSE_c (aReceive, "receive", ("receive a submission or confirmation")), ARGPARSE_c (aCron, "cron", ("run regular jobs")), ARGPARSE_c (aListDomains, "list-domains", ("list configured domains")), ARGPARSE_c (aCheck, "check", ("check whether a key is installed")), ARGPARSE_c (aCheck, "check-key", "@"), ARGPARSE_c (aInstallKey, "install-key", "install a key from FILE into the WKD"), ARGPARSE_c (aRemoveKey, "remove-key", "remove a key from the WKD"), ARGPARSE_c (aRevokeKey, "revoke-key", "mark a key as revoked"), ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), ARGPARSE_s_s (oDirectory, "directory", "|DIR|use DIR as top directory"), ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"), ARGPARSE_s_s (oHeader, "header" , "|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"), ARGPARSE_s_n (oWithDir, "with-dir", "@"), ARGPARSE_s_n (oWithFile, "with-file", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MIME_VALUE , "mime" }, { DBG_PARSER_VALUE , "parser" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* State for processing a message. */ struct server_ctx_s { char *fpr; uidinfo_list_t mboxes; /* List with addr-specs taken from the UIDs. */ unsigned int draft_version_2:1; /* Client supports the draft 2. */ }; typedef struct server_ctx_s *server_ctx_t; /* Flag for --with-dir. */ static int opt_with_dir; /* Flag for --with-file. */ static int opt_with_file; /* Prototypes. */ static gpg_error_t get_domain_list (strlist_t *r_list); static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t fp, unsigned int flags); static gpg_error_t command_list_domains (void); static gpg_error_t command_revoke_key (const char *mailaddr); static gpg_error_t command_check_key (const char *mailaddr); static gpg_error_t command_cron (void); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "gpg-wks-server"; break; case 12: p = "@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-wks-server command [options] (-h for help)"); break; case 41: p = ("Syntax: gpg-wks-server command [options]\n" "Server for the Web Key Service protocol\n"); break; default: p = NULL; break; } return p; } static void wrong_args (const char *text) { es_fprintf (es_stderr, "usage: %s [options] %s\n", strusage (11), text); exit (2); } /* Command line parsing. */ static enum cmd_and_opt_values parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) { enum cmd_and_opt_values cmd = 0; int no_more_options = 0; while (!no_more_options && optfile_parse (NULL, NULL, NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: 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 oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oDirectory: opt.directory = pargs->r.ret_str; break; case oFrom: opt.default_from = pargs->r.ret_str; break; case oHeader: append_to_strlist (&opt.extra_headers, pargs->r.ret_str); break; case oSend: opt.use_sendmail = 1; break; case oOutput: opt.output = pargs->r.ret_str; break; case oWithDir: opt_with_dir = 1; break; case oWithFile: opt_with_file = 1; break; case aReceive: case aCron: case aListDomains: case aCheck: case aInstallKey: case aRemoveKey: case aRevokeKey: cmd = pargs->r_opt; break; default: pargs->err = 2; break; } } return cmd; } /* gpg-wks-server main. */ int main (int argc, char **argv) { gpg_error_t err, firsterr; ARGPARSE_ARGS pargs; enum cmd_and_opt_values cmd; gnupg_reopen_std ("gpg-wks-server"); set_strusage (my_strusage); log_set_prefix ("gpg-wks-server", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ init_common_subsystems (&argc, &argv); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; cmd = parse_arguments (&pargs, opts); if (log_get_errorcount (0)) exit (2); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (("NOTE: '%s' is not considered an option\n"), argv[i]); } /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.directory) opt.directory = "/var/lib/gnupg/wks"; /* Check for syntax errors in the --header option to avoid later * error messages with a not easy to find cause */ if (opt.extra_headers) { strlist_t sl; for (sl = opt.extra_headers; sl; sl = sl->next) { err = mime_maker_add_header (NULL, sl->d, NULL); if (err) log_error ("syntax error in \"--header %s\": %s\n", sl->d, gpg_strerror (err)); } } if (log_get_errorcount (0)) exit (2); /* Check that we have a working directory. */ #if defined(HAVE_STAT) { struct stat sb; - if (stat (opt.directory, &sb)) + if (gnupg_stat (opt.directory, &sb)) { err = gpg_error_from_syserror (); log_error ("error accessing directory '%s': %s\n", opt.directory, gpg_strerror (err)); exit (2); } if (!S_ISDIR(sb.st_mode)) { log_error ("error accessing directory '%s': %s\n", opt.directory, "not a directory"); exit (2); } if (sb.st_uid != getuid()) { log_error ("directory '%s' not owned by user\n", opt.directory); exit (2); } if ((sb.st_mode & (S_IROTH|S_IWOTH))) { log_error ("directory '%s' has too relaxed permissions\n", opt.directory); log_info ("Fix by running: chmod o-rw '%s'\n", opt.directory); exit (2); } } #else /*!HAVE_STAT*/ log_fatal ("program build w/o stat() call\n"); #endif /*!HAVE_STAT*/ /* Run the selected command. */ switch (cmd) { case aReceive: if (argc) wrong_args ("--receive"); err = wks_receive (es_stdin, command_receive_cb, NULL); break; case aCron: if (argc) wrong_args ("--cron"); err = command_cron (); break; case aListDomains: err = command_list_domains (); break; case aInstallKey: if (!argc) err = wks_cmd_install_key (NULL, NULL); else if (argc == 2) err = wks_cmd_install_key (*argv, argv[1]); else wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); break; case aRemoveKey: if (argc != 1) wrong_args ("--remove-key USER-ID"); err = wks_cmd_remove_key (*argv); break; case aRevokeKey: if (argc != 1) wrong_args ("--revoke-key USER-ID"); err = command_revoke_key (*argv); break; case aCheck: if (!argc) wrong_args ("--check USER-IDs"); firsterr = 0; for (; argc; argc--, argv++) { err = command_check_key (*argv); if (!firsterr) firsterr = err; } err = firsterr; break; default: usage (1); err = gpg_error (GPG_ERR_BUG); break; } if (err) log_error ("command failed: %s\n", gpg_strerror (err)); return log_get_errorcount (0)? 1:0; } /* Take the key in KEYFILE and write it to OUTFILE in binary encoding. * If ADDRSPEC is given only matching user IDs are included in the * output. */ static gpg_error_t copy_key_as_binary (const char *keyfile, const char *outfile, const char *addrspec) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; char *filterexp = NULL; if (addrspec) { filterexp = es_bsprintf ("keep-uid=mbox = %s", addrspec); if (!filterexp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--yes"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--no-keyring"); ccparray_put (&ccp, "--output"); ccparray_put (&ccp, outfile); ccparray_put (&ccp, "--import-options=import-export"); if (filterexp) { ccparray_put (&ccp, "--import-filter"); ccparray_put (&ccp, filterexp); } ccparray_put (&ccp, "--import"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, keyfile); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("%s failed: %s\n", __func__, gpg_strerror (err)); goto leave; } leave: xfree (filterexp); xfree (argv); return err; } /* Take the key in KEYFILE and write it to DANEFILE using the DANE * output format. */ static gpg_error_t copy_key_as_dane (const char *keyfile, const char *danefile) { gpg_error_t err; ccparray_t ccp; const char **argv; ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--yes"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--no-keyring"); ccparray_put (&ccp, "--output"); ccparray_put (&ccp, danefile); ccparray_put (&ccp, "--export-options=export-dane"); ccparray_put (&ccp, "--import-options=import-export"); ccparray_put (&ccp, "--import"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, keyfile); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("%s failed: %s\n", __func__, gpg_strerror (err)); goto leave; } leave: xfree (argv); return err; } static void encrypt_stream_status_cb (void *opaque, const char *keyword, char *args) { (void)opaque; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); } /* Encrypt the INPUT stream to a new stream which is stored at success * at R_OUTPUT. Encryption is done for the key in file KEYFIL. */ static gpg_error_t encrypt_stream (estream_t *r_output, estream_t input, const char *keyfile) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t output; *r_output = NULL; output = es_fopenmem (0, "w+b"); if (!output) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--no-keyring"); ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ ccparray_put (&ccp, "--recipient-file"); ccparray_put (&ccp, keyfile); ccparray_put (&ccp, "--encrypt"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, NULL, output, encrypt_stream_status_cb, NULL); if (err) { log_error ("encryption failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (output); *r_output = output; output = NULL; leave: es_fclose (output); xfree (argv); return err; } static void sign_stream_status_cb (void *opaque, const char *keyword, char *args) { (void)opaque; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); } /* Sign the INPUT stream to a new stream which is stored at success at * R_OUTPUT. A detached signature is created using the key specified * by USERID. */ static gpg_error_t sign_stream (estream_t *r_output, estream_t input, const char *userid) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t output; *r_output = NULL; output = es_fopenmem (0, "w+b"); if (!output) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "--local-user"); ccparray_put (&ccp, userid); ccparray_put (&ccp, "--detach-sign"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, NULL, output, sign_stream_status_cb, NULL); if (err) { log_error ("signing failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (output); *r_output = output; output = NULL; leave: es_fclose (output); xfree (argv); return err; } /* Get the submission address for address MBOX. Caller must free the * value. If no address can be found NULL is returned. */ static char * get_submission_address (const char *mbox) { gpg_error_t err; const char *domain; char *fname, *line, *p; size_t n; estream_t fp; domain = strchr (mbox, '@'); if (!domain) return NULL; domain++; fname = make_filename_try (opt.directory, domain, "submission-address", NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("make_filename failed in %s: %s\n", __func__, gpg_strerror (err)); return NULL; } fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOENT) log_info ("Note: no specific submission address configured" " for domain '%s'\n", domain); else log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); return NULL; } line = NULL; n = 0; if (es_getline (&line, &n, fp) < 0) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); xfree (line); es_fclose (fp); xfree (fname); return NULL; } es_fclose (fp); xfree (fname); p = strchr (line, '\n'); if (p) *p = 0; trim_spaces (line); if (!is_valid_mailbox (line)) { log_error ("invalid submission address for domain '%s' detected\n", domain); xfree (line); return NULL; } return line; } /* Get the policy flags for address MBOX and store them in POLICY. */ static gpg_error_t get_policy_flags (policy_flags_t policy, const char *mbox) { gpg_error_t err; const char *domain; char *fname; estream_t fp; memset (policy, 0, sizeof *policy); domain = strchr (mbox, '@'); if (!domain) return gpg_error (GPG_ERR_INV_USER_ID); domain++; fname = make_filename_try (opt.directory, domain, "policy", NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("make_filename failed in %s: %s\n", __func__, gpg_strerror (err)); return err; } fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = 0; else log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); return err; } err = wks_parse_policy (policy, fp, 0); es_fclose (fp); xfree (fname); return err; } /* We store the key under the name of the nonce we will then send to * the user. On success the nonce is stored at R_NONCE and the file * name at R_FNAME. */ static gpg_error_t store_key_as_pending (const char *dir, estream_t key, char **r_nonce, char **r_fname) { gpg_error_t err; char *dname = NULL; char *fname = NULL; char *nonce = NULL; estream_t outfp = NULL; char buffer[1024]; size_t nbytes, nwritten; *r_nonce = NULL; *r_fname = NULL; dname = make_filename_try (dir, "pending", NULL); if (!dname) { err = gpg_error_from_syserror (); goto leave; } /* Create the nonce. We use 20 bytes so that we don't waste a * character in our zBase-32 encoding. Using the gcrypt's nonce * function is faster than using the strong random function; this is * Good Enough for our purpose. */ log_assert (sizeof buffer > 20); gcry_create_nonce (buffer, 20); nonce = zb32_encode (buffer, 8 * 20); memset (buffer, 0, 20); /* Not actually needed but it does not harm. */ if (!nonce) { err = gpg_error_from_syserror (); goto leave; } fname = strconcat (dname, "/", nonce, NULL); if (!fname) { err = gpg_error_from_syserror (); goto leave; } /* With 128 bits of random we can expect that no other file exists * under this name. We use "x" to detect internal errors. */ outfp = es_fopen (fname, "wbx,mode=-rw"); if (!outfp) { err = gpg_error_from_syserror (); log_error ("error creating '%s': %s\n", fname, gpg_strerror (err)); goto leave; } es_rewind (key); for (;;) { if (es_read (key, buffer, sizeof buffer, &nbytes)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", es_fname_get (key), gpg_strerror (err)); break; } if (!nbytes) { err = 0; goto leave; /* Ready. */ } if (es_write (outfp, buffer, nbytes, &nwritten)) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } else if (nwritten != nbytes) { err = gpg_error (GPG_ERR_EIO); log_error ("error writing '%s': %s\n", fname, "short write"); goto leave; } } leave: if (err) { es_fclose (outfp); gnupg_remove (fname); } else if (es_fclose (outfp)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); } if (!err) { *r_nonce = nonce; *r_fname = fname; } else { xfree (nonce); xfree (fname); } xfree (dname); return err; } /* Send a confirmation request. DIR is the directory used for the * address MBOX. NONCE is the nonce we want to see in the response to * this mail. FNAME the name of the file with the key. */ static gpg_error_t send_confirmation_request (server_ctx_t ctx, const char *mbox, const char *nonce, const char *keyfile) { gpg_error_t err; estream_t body = NULL; estream_t bodyenc = NULL; estream_t signeddata = NULL; estream_t signature = NULL; mime_maker_t mime = NULL; char *from_buffer = NULL; const char *from; strlist_t sl; from = from_buffer = get_submission_address (mbox); if (!from) { from = opt.default_from; if (!from) { log_error ("no sender address found for '%s'\n", mbox); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } log_info ("Note: using default sender address '%s'\n", from); } body = es_fopenmem (0, "w+b"); if (!body) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } if (!ctx->draft_version_2) { /* It is fine to use 8 bit encoding because that is encrypted and * only our client will see it. */ es_fputs ("Content-Type: application/vnd.gnupg.wks\n" "Content-Transfer-Encoding: 8bit\n" "\n", body); } es_fprintf (body, ("type: confirmation-request\n" "sender: %s\n" "address: %s\n" "fingerprint: %s\n" "nonce: %s\n"), from, mbox, ctx->fpr, nonce); es_rewind (body); err = encrypt_stream (&bodyenc, body, keyfile); if (err) goto leave; es_fclose (body); body = NULL; err = mime_maker_new (&mime, NULL); if (err) goto leave; err = mime_maker_add_header (mime, "From", from); if (err) goto leave; err = mime_maker_add_header (mime, "To", mbox); if (err) goto leave; err = mime_maker_add_header (mime, "Subject", "Confirm your key publication"); if (err) goto leave; err = mime_maker_add_header (mime, "Wks-Draft-Version", STR2(WKS_DRAFT_VERSION)); if (err) goto leave; /* Help Enigmail to identify messages. Note that this is in no way * secured. */ err = mime_maker_add_header (mime, "WKS-Phase", "confirm"); if (err) goto leave; for (sl = opt.extra_headers; sl; sl = sl->next) { err = mime_maker_add_header (mime, sl->d, NULL); if (err) goto leave; } if (!ctx->draft_version_2) { err = mime_maker_add_header (mime, "Content-Type", "multipart/encrypted; " "protocol=\"application/pgp-encrypted\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-encrypted"); if (err) goto leave; err = mime_maker_add_body (mime, "Version: 1\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/octet-stream"); if (err) goto leave; err = mime_maker_add_stream (mime, &bodyenc); if (err) goto leave; } else { unsigned int partid; /* FIXME: Add micalg. */ err = mime_maker_add_header (mime, "Content-Type", "multipart/signed; " "protocol=\"application/pgp-signature\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed"); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; partid = mime_maker_get_partid (mime); err = mime_maker_add_header (mime, "Content-Type", "text/plain"); if (err) goto leave; err = mime_maker_add_body (mime, "This message has been send to confirm your request\n" "to publish your key. If you did not request a key\n" "publication, simply ignore this message.\n" "\n" "Most mail software can handle this kind of message\n" "automatically and thus you would not have seen this\n" "message. It seems that your client does not fully\n" "support this service. The web page\n" "\n" " https://gnupg.org/faq/wkd.html\n" "\n" "explains how you can process this message anyway in\n" "a few manual steps.\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/vnd.gnupg.wks"); if (err) goto leave; err = mime_maker_add_stream (mime, &bodyenc); if (err) goto leave; err = mime_maker_end_container (mime); if (err) goto leave; /* mime_maker_dump_tree (mime); */ err = mime_maker_get_part (mime, partid, &signeddata); if (err) goto leave; err = sign_stream (&signature, signeddata, from); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-signature"); if (err) goto leave; err = mime_maker_add_stream (mime, &signature); if (err) goto leave; } err = wks_send_mime (mime); leave: mime_maker_release (mime); es_fclose (signature); es_fclose (signeddata); es_fclose (bodyenc); es_fclose (body); xfree (from_buffer); return err; } /* Store the key given by KEY into the pending directory and send a * confirmation requests. */ static gpg_error_t process_new_key (server_ctx_t ctx, estream_t key) { gpg_error_t err; uidinfo_list_t sl; const char *s; char *dname = NULL; char *nonce = NULL; char *fname = NULL; struct policy_flags_s policybuf; memset (&policybuf, 0, sizeof policybuf); /* First figure out the user id from the key. */ xfree (ctx->fpr); free_uidinfo_list (ctx->mboxes); err = wks_list_key (key, &ctx->fpr, &ctx->mboxes); if (err) goto leave; log_assert (ctx->fpr); log_info ("fingerprint: %s\n", ctx->fpr); for (sl = ctx->mboxes; sl; sl = sl->next) { if (sl->mbox) log_info (" addr-spec: %s\n", sl->mbox); } /* Walk over all user ids and send confirmation requests for those * we support. */ for (sl = ctx->mboxes; sl; sl = sl->next) { if (!sl->mbox) continue; s = strchr (sl->mbox, '@'); log_assert (s && s[1]); xfree (dname); dname = make_filename_try (opt.directory, s+1, NULL); if (!dname) { err = gpg_error_from_syserror (); goto leave; } if (gnupg_access (dname, W_OK)) { log_info ("skipping address '%s': Domain not configured\n", sl->mbox); continue; } if (get_policy_flags (&policybuf, sl->mbox)) { log_info ("skipping address '%s': Bad policy flags\n", sl->mbox); continue; } if (policybuf.auth_submit) { /* Bypass the confirmation stuff and publish the key as is. */ log_info ("publishing address '%s'\n", sl->mbox); /* FIXME: We need to make sure that we do this only for the * address in the mail. */ log_debug ("auth-submit not yet working!\n"); } else { log_info ("storing address '%s'\n", sl->mbox); xfree (nonce); xfree (fname); err = store_key_as_pending (dname, key, &nonce, &fname); if (err) goto leave; err = send_confirmation_request (ctx, sl->mbox, nonce, fname); if (err) goto leave; } } leave: if (nonce) wipememory (nonce, strlen (nonce)); xfree (nonce); xfree (fname); xfree (dname); wks_free_policy (&policybuf); return err; } /* Send a message to tell the user at MBOX that their key has been * published. FNAME the name of the file with the key. */ static gpg_error_t send_congratulation_message (const char *mbox, const char *keyfile) { gpg_error_t err; estream_t body = NULL; estream_t bodyenc = NULL; mime_maker_t mime = NULL; char *from_buffer = NULL; const char *from; strlist_t sl; from = from_buffer = get_submission_address (mbox); if (!from) { from = opt.default_from; if (!from) { log_error ("no sender address found for '%s'\n", mbox); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } log_info ("Note: using default sender address '%s'\n", from); } body = es_fopenmem (0, "w+b"); if (!body) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } /* It is fine to use 8 bit encoding because that is encrypted and * only our client will see it. */ es_fputs ("Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "\n", body); es_fprintf (body, "Hello!\n\n" "The key for your address '%s' has been published\n" "and can now be retrieved from the Web Key Directory.\n" "\n" "For more information on this system see:\n" "\n" " https://gnupg.org/faq/wkd.html\n" "\n" "Best regards\n" "\n" " Gnu Key Publisher\n\n\n" "-- \n" "The GnuPG Project welcomes donations: %s\n", mbox, "https://gnupg.org/donate"); es_rewind (body); err = encrypt_stream (&bodyenc, body, keyfile); if (err) goto leave; es_fclose (body); body = NULL; err = mime_maker_new (&mime, NULL); if (err) goto leave; err = mime_maker_add_header (mime, "From", from); if (err) goto leave; err = mime_maker_add_header (mime, "To", mbox); if (err) goto leave; err = mime_maker_add_header (mime, "Subject", "Your key has been published"); if (err) goto leave; err = mime_maker_add_header (mime, "Wks-Draft-Version", STR2(WKS_DRAFT_VERSION)); if (err) goto leave; err = mime_maker_add_header (mime, "WKS-Phase", "done"); if (err) goto leave; for (sl = opt.extra_headers; sl; sl = sl->next) { err = mime_maker_add_header (mime, sl->d, NULL); if (err) goto leave; } err = mime_maker_add_header (mime, "Content-Type", "multipart/encrypted; " "protocol=\"application/pgp-encrypted\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-encrypted"); if (err) goto leave; err = mime_maker_add_body (mime, "Version: 1\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/octet-stream"); if (err) goto leave; err = mime_maker_add_stream (mime, &bodyenc); if (err) goto leave; err = wks_send_mime (mime); leave: mime_maker_release (mime); es_fclose (bodyenc); es_fclose (body); xfree (from_buffer); return err; } /* Check that we have send a request with NONCE and publish the key. */ static gpg_error_t check_and_publish (server_ctx_t ctx, const char *address, const char *nonce) { gpg_error_t err; char *fname = NULL; char *fnewname = NULL; estream_t key = NULL; char *hash = NULL; const char *domain; const char *s; uidinfo_list_t sl; char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ /* FIXME: There is a bug in name-value.c which adds white space for * the last pair and thus we strip the nonce here until this has * been fixed. */ char *nonce2 = xstrdup (nonce); trim_trailing_spaces (nonce2); nonce = nonce2; domain = strchr (address, '@'); log_assert (domain && domain[1]); domain++; fname = make_filename_try (opt.directory, domain, "pending", nonce, NULL); if (!fname) { err = gpg_error_from_syserror (); goto leave; } /* Try to open the file with the key. */ key = es_fopen (fname, "rb"); if (!key) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOENT) { log_info ("no pending request for '%s'\n", address); err = gpg_error (GPG_ERR_NOT_FOUND); } else log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } /* We need to get the fingerprint from the key. */ xfree (ctx->fpr); free_uidinfo_list (ctx->mboxes); err = wks_list_key (key, &ctx->fpr, &ctx->mboxes); if (err) goto leave; log_assert (ctx->fpr); log_info ("fingerprint: %s\n", ctx->fpr); for (sl = ctx->mboxes; sl; sl = sl->next) if (sl->mbox) log_info (" addr-spec: %s\n", sl->mbox); /* Check that the key has 'address' as a user id. We use * case-insensitive matching because the client is expected to * return the address verbatim. */ for (sl = ctx->mboxes; sl; sl = sl->next) if (sl->mbox && !strcmp (sl->mbox, address)) break; if (!sl) { log_error ("error publishing key: '%s' is not a user ID of %s\n", address, ctx->fpr); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } /* Hash user ID and create filename. */ err = wks_compute_hu_fname (&fnewname, address); if (err) goto leave; /* Publish. */ err = copy_key_as_binary (fname, fnewname, address); if (err) { err = gpg_error_from_syserror (); log_error ("copying '%s' to '%s' failed: %s\n", fname, fnewname, gpg_strerror (err)); goto leave; } /* Make sure it is world readable. */ if (gnupg_chmod (fnewname, "-rwxr--r--")) log_error ("can't set permissions of '%s': %s\n", fnewname, gpg_strerror (gpg_err_code_from_syserror())); log_info ("key %s published for '%s'\n", ctx->fpr, address); send_congratulation_message (address, fnewname); /* Try to publish as DANE record if the DANE directory exists. */ xfree (fname); fname = fnewname; fnewname = make_filename_try (opt.directory, domain, "dane", NULL); if (!fnewname) { err = gpg_error_from_syserror (); goto leave; } if (!gnupg_access (fnewname, W_OK)) { /* Yes, we have a dane directory. */ s = strchr (address, '@'); log_assert (s); gcry_md_hash_buffer (GCRY_MD_SHA256, shaxbuf, address, s - address); xfree (hash); hash = bin2hex (shaxbuf, 28, NULL); if (!hash) { err = gpg_error_from_syserror (); goto leave; } xfree (fnewname); fnewname = make_filename_try (opt.directory, domain, "dane", hash, NULL); if (!fnewname) { err = gpg_error_from_syserror (); goto leave; } err = copy_key_as_dane (fname, fnewname); if (err) goto leave; log_info ("key %s published for '%s' (DANE record)\n", ctx->fpr, address); } leave: es_fclose (key); xfree (hash); xfree (fnewname); xfree (fname); xfree (nonce2); return err; } /* Process a confirmation response in MSG. */ static gpg_error_t process_confirmation_response (server_ctx_t ctx, estream_t msg) { gpg_error_t err; nvc_t nvc; nve_t item; const char *value, *sender, *address, *nonce; err = nvc_parse (&nvc, NULL, msg); if (err) { log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err)); goto leave; } if (opt.debug) { log_debug ("response follows:\n"); nvc_write (nvc, log_get_stream ()); } /* Check that this is a confirmation response. */ if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item)) && !strcmp (value, "confirmation-response"))) { if (item && value) log_error ("received unexpected wks message '%s'\n", value); else log_error ("received invalid wks message: %s\n", "'type' missing"); err = gpg_error (GPG_ERR_UNEXPECTED_MSG); goto leave; } /* Get the sender. */ if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item)) && is_valid_mailbox (value))) { log_error ("received invalid wks message: %s\n", "'sender' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } sender = value; (void)sender; /* FIXME: Do we really need the sender?. */ /* Get the address. */ if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item)) && is_valid_mailbox (value))) { log_error ("received invalid wks message: %s\n", "'address' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } address = value; /* Get the nonce. */ if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item)) && strlen (value) > 16)) { log_error ("received invalid wks message: %s\n", "'nonce' missing or too short"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } nonce = value; err = check_and_publish (ctx, address, nonce); leave: nvc_release (nvc); return err; } /* Called from the MIME receiver to process the plain text data in MSG . */ static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t msg, unsigned int flags) { gpg_error_t err; struct server_ctx_s ctx; (void)opaque; memset (&ctx, 0, sizeof ctx); if ((flags & WKS_RECEIVE_DRAFT2)) ctx.draft_version_2 = 1; if (!strcmp (mediatype, "application/pgp-keys")) err = process_new_key (&ctx, msg); else if (!strcmp (mediatype, "application/vnd.gnupg.wks")) err = process_confirmation_response (&ctx, msg); else { log_info ("ignoring unexpected message of type '%s'\n", mediatype); err = gpg_error (GPG_ERR_UNEXPECTED_MSG); } xfree (ctx.fpr); free_uidinfo_list (ctx.mboxes); return err; } /* Return a list of all configured domains. Each list element is the * top directory for the domain. To figure out the actual domain * name strrchr(name, '/') can be used. */ static gpg_error_t get_domain_list (strlist_t *r_list) { gpg_error_t err; DIR *dir = NULL; char *fname = NULL; struct dirent *dentry; struct stat sb; strlist_t list = NULL; *r_list = NULL; dir = opendir (opt.directory); if (!dir) { err = gpg_error_from_syserror (); goto leave; } while ((dentry = readdir (dir))) { if (*dentry->d_name == '.') continue; if (!strchr (dentry->d_name, '.')) continue; /* No dot - can't be a domain subdir. */ xfree (fname); fname = make_filename_try (opt.directory, dentry->d_name, NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("make_filename failed in %s: %s\n", __func__, gpg_strerror (err)); goto leave; } - if (stat (fname, &sb)) + if (gnupg_stat (fname, &sb)) { err = gpg_error_from_syserror (); log_error ("error accessing '%s': %s\n", fname, gpg_strerror (err)); continue; } if (!S_ISDIR(sb.st_mode)) continue; if (!add_to_strlist_try (&list, fname)) { err = gpg_error_from_syserror (); log_error ("add_to_strlist failed in %s: %s\n", __func__, gpg_strerror (err)); goto leave; } } err = 0; *r_list = list; list = NULL; leave: free_strlist (list); if (dir) closedir (dir); xfree (fname); return err; } static gpg_error_t expire_one_domain (const char *top_dirname, const char *domain) { gpg_error_t err; char *dirname; char *fname = NULL; DIR *dir = NULL; struct dirent *dentry; struct stat sb; time_t now = gnupg_get_time (); dirname = make_filename_try (top_dirname, "pending", NULL); if (!dirname) { err = gpg_error_from_syserror (); log_error ("make_filename failed in %s: %s\n", __func__, gpg_strerror (err)); goto leave; } dir = opendir (dirname); if (!dir) { err = gpg_error_from_syserror (); log_error (("can't access directory '%s': %s\n"), dirname, gpg_strerror (err)); goto leave; } while ((dentry = readdir (dir))) { if (*dentry->d_name == '.') continue; xfree (fname); fname = make_filename_try (dirname, dentry->d_name, NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("make_filename failed in %s: %s\n", __func__, gpg_strerror (err)); goto leave; } if (strlen (dentry->d_name) != 32) { log_info ("garbage file '%s' ignored\n", fname); continue; } - if (stat (fname, &sb)) + if (gnupg_stat (fname, &sb)) { err = gpg_error_from_syserror (); log_error ("error accessing '%s': %s\n", fname, gpg_strerror (err)); continue; } if (S_ISDIR(sb.st_mode)) { log_info ("garbage directory '%s' ignored\n", fname); continue; } if (sb.st_mtime + PENDING_TTL < now) { if (opt.verbose) log_info ("domain %s: removing pending key '%s'\n", domain, dentry->d_name); if (remove (fname)) { err = gpg_error_from_syserror (); /* In case the file has just been renamed or another * processes is cleaning up, we don't print a diagnostic * for ENOENT. */ if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); } } } err = 0; leave: if (dir) closedir (dir); xfree (dirname); xfree (fname); return err; } /* Scan spool directories and expire too old pending keys. */ static gpg_error_t expire_pending_confirmations (strlist_t domaindirs) { gpg_error_t err = 0; strlist_t sl; const char *domain; for (sl = domaindirs; sl; sl = sl->next) { domain = strrchr (sl->d, '/'); log_assert (domain); domain++; expire_one_domain (sl->d, domain); } return err; } /* List all configured domains. */ static gpg_error_t command_list_domains (void) { static struct { const char *name; const char *perm; } requireddirs[] = { { "pending", "-rwx" }, { "hu", "-rwxr-xr-x" } }; gpg_err_code_t ec; gpg_error_t err; strlist_t domaindirs; strlist_t sl; const char *domain; char *fname = NULL; int i; estream_t fp; err = get_domain_list (&domaindirs); if (err) { log_error ("error reading list of domains: %s\n", gpg_strerror (err)); return err; } for (sl = domaindirs; sl; sl = sl->next) { domain = strrchr (sl->d, '/'); log_assert (domain); domain++; if (opt_with_dir) es_printf ("%s %s\n", domain, sl->d); else es_printf ("%s\n", domain); /* Check that the required directories are there. */ for (i=0; i < DIM (requireddirs); i++) { xfree (fname); fname = make_filename_try (sl->d, requireddirs[i].name, NULL); if (!fname) { err = gpg_error_from_syserror (); goto leave; } if ((ec = gnupg_access (fname, W_OK))) { err = gpg_error (ec); if (gpg_err_code (err) == GPG_ERR_ENOENT) { if (gnupg_mkdir (fname, requireddirs[i].perm)) { err = gpg_error_from_syserror (); log_error ("domain %s: error creating subdir '%s': %s\n", domain, requireddirs[i].name, gpg_strerror (err)); } else log_info ("domain %s: subdir '%s' created\n", domain, requireddirs[i].name); } else if (err) log_error ("domain %s: problem with subdir '%s': %s\n", domain, requireddirs[i].name, gpg_strerror (err)); } } /* Print a warning if the submission address is not configured. */ xfree (fname); fname = make_filename_try (sl->d, "submission-address", NULL); if (!fname) { err = gpg_error_from_syserror (); goto leave; } if ((ec = gnupg_access (fname, F_OK))) { err = gpg_error (ec); if (gpg_err_code (err) == GPG_ERR_ENOENT) log_error ("domain %s: submission address not configured\n", domain); else log_error ("domain %s: problem with '%s': %s\n", domain, fname, gpg_strerror (err)); } /* Check the syntax of the optional policy file. */ xfree (fname); fname = make_filename_try (sl->d, "policy", NULL); if (!fname) { err = gpg_error_from_syserror (); goto leave; } fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOENT) { fp = es_fopen (fname, "w"); if (!fp) log_error ("domain %s: can't create policy file: %s\n", domain, gpg_strerror (err)); else es_fclose (fp); fp = NULL; } else log_error ("domain %s: error in policy file: %s\n", domain, gpg_strerror (err)); } else { struct policy_flags_s policy; err = wks_parse_policy (&policy, fp, 0); es_fclose (fp); wks_free_policy (&policy); } } err = 0; leave: xfree (fname); free_strlist (domaindirs); return err; } /* Run regular maintenance jobs. */ static gpg_error_t command_cron (void) { gpg_error_t err; strlist_t domaindirs; err = get_domain_list (&domaindirs); if (err) { log_error ("error reading list of domains: %s\n", gpg_strerror (err)); return err; } err = expire_pending_confirmations (domaindirs); free_strlist (domaindirs); return err; } /* Check whether the key with USER_ID is installed. */ static gpg_error_t command_check_key (const char *userid) { gpg_err_code_t ec; gpg_error_t err; char *addrspec = NULL; char *fname = NULL; err = wks_fname_from_userid (userid, 0, &fname, &addrspec); if (err) goto leave; if ((ec = gnupg_access (fname, R_OK))) { err = gpg_error (ec); if (opt_with_file) es_printf ("%s n %s\n", addrspec, fname); if (gpg_err_code (err) == GPG_ERR_ENOENT) { if (!opt.quiet) log_info ("key for '%s' is NOT installed\n", addrspec); log_inc_errorcount (); err = 0; } else log_error ("error stating '%s': %s\n", fname, gpg_strerror (err)); goto leave; } if (opt_with_file) es_printf ("%s i %s\n", addrspec, fname); if (opt.verbose) log_info ("key for '%s' is installed\n", addrspec); err = 0; leave: xfree (fname); xfree (addrspec); return err; } /* Revoke the key with mail address MAILADDR. */ static gpg_error_t command_revoke_key (const char *mailaddr) { /* Remove should be different from removing but we have not yet * defined a suitable way to do this. */ return wks_cmd_remove_key (mailaddr); } diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index 8a54c70ee..17c24c3d5 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -1,1073 +1,1073 @@ /* gpgtar-create.c - Create a TAR archive * 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 #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # include #else /*!HAVE_W32_SYSTEM*/ # include # include # include #endif /*!HAVE_W32_SYSTEM*/ #include #include "../common/i18n.h" #include "../common/exectool.h" #include "../common/sysutils.h" #include "../common/ccparray.h" #include "gpgtar.h" #ifndef HAVE_LSTAT -#define lstat(a,b) stat ((a), (b)) +#define lstat(a,b) gnupg_stat ((a), (b)) #endif /* Object to control the file scanning. */ struct scanctrl_s; typedef struct scanctrl_s *scanctrl_t; struct scanctrl_s { tar_header_t flist; tar_header_t *flist_tail; int nestlevel; }; /* On Windows convert name to UTF8 and return it; caller must release * the result. On Unix or if ALREADY_UTF8 is set, this function is a * mere xtrystrcopy. On failure NULL is returned and ERRNO set. */ static char * name_to_utf8 (const char *name, int already_utf8) { #ifdef HAVE_W32_SYSTEM wchar_t *wstring; char *result; if (already_utf8) result = xtrystrdup (name); else { wstring = native_to_wchar (name); if (!wstring) return NULL; result = wchar_to_utf8 (wstring); xfree (wstring); } return result; #else /*!HAVE_W32_SYSTEM */ (void)already_utf8; return xtrystrdup (name); #endif /*!HAVE_W32_SYSTEM */ } /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the W32 version. */ #ifdef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_w32 (tar_header_t hdr) { char *p; wchar_t *wfname; WIN32_FILE_ATTRIBUTE_DATA fad; DWORD attr; for (p=hdr->name; *p; p++) if (*p == '/') *p = '\\'; wfname = utf8_to_wchar (hdr->name); for (p=hdr->name; *p; p++) if (*p == '\\') *p = '/'; if (!wfname) { log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1)); return gpg_error_from_syserror (); } if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad)) { log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1)); xfree (wfname); return gpg_error_from_syserror (); } xfree (wfname); attr = fad.dwFileAttributes; if ((attr & FILE_ATTRIBUTE_NORMAL)) hdr->typeflag = TF_REGULAR; else if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->typeflag = TF_DIRECTORY; else if ((attr & FILE_ATTRIBUTE_DEVICE)) hdr->typeflag = TF_NOTSUP; else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY))) hdr->typeflag = TF_NOTSUP; else hdr->typeflag = TF_REGULAR; /* Map some attributes to USTAR defined mode bits. */ hdr->mode = 0640; /* User may read and write, group only read. */ if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->mode |= 0110; /* Dirs are user and group executable. */ if ((attr & FILE_ATTRIBUTE_READONLY)) hdr->mode &= ~0200; /* Clear the user write bit. */ if ((attr & FILE_ATTRIBUTE_HIDDEN)) hdr->mode &= ~0707; /* Clear all user and other bits. */ if ((attr & FILE_ATTRIBUTE_SYSTEM)) hdr->mode |= 0004; /* Make it readable by other. */ /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = (fad.nFileSizeHigh * (unsigned long long)(MAXDWORD+1) + fad.nFileSizeLow); hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32) | fad.ftLastWriteTime.dwLowDateTime); if (!hdr->mtime) hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32) | fad.ftCreationTime.dwLowDateTime); hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */ return 0; } #endif /*HAVE_W32_SYSTEM*/ /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the POSIX version. */ #ifndef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_posix (tar_header_t hdr) { gpg_error_t err; struct stat sbuf; if (lstat (hdr->name, &sbuf)) { err = gpg_error_from_syserror (); log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } if (S_ISREG (sbuf.st_mode)) hdr->typeflag = TF_REGULAR; else if (S_ISDIR (sbuf.st_mode)) hdr->typeflag = TF_DIRECTORY; else if (S_ISCHR (sbuf.st_mode)) hdr->typeflag = TF_CHARDEV; else if (S_ISBLK (sbuf.st_mode)) hdr->typeflag = TF_BLOCKDEV; else if (S_ISFIFO (sbuf.st_mode)) hdr->typeflag = TF_FIFO; else if (S_ISLNK (sbuf.st_mode)) hdr->typeflag = TF_SYMLINK; else hdr->typeflag = TF_NOTSUP; /* FIXME: Save DEV and INO? */ /* Set the USTAR defined mode bits using the system macros. */ if (sbuf.st_mode & S_IRUSR) hdr->mode |= 0400; if (sbuf.st_mode & S_IWUSR) hdr->mode |= 0200; if (sbuf.st_mode & S_IXUSR) hdr->mode |= 0100; if (sbuf.st_mode & S_IRGRP) hdr->mode |= 0040; if (sbuf.st_mode & S_IWGRP) hdr->mode |= 0020; if (sbuf.st_mode & S_IXGRP) hdr->mode |= 0010; if (sbuf.st_mode & S_IROTH) hdr->mode |= 0004; if (sbuf.st_mode & S_IWOTH) hdr->mode |= 0002; if (sbuf.st_mode & S_IXOTH) hdr->mode |= 0001; #ifdef S_IXUID if (sbuf.st_mode & S_IXUID) hdr->mode |= 04000; #endif #ifdef S_IXGID if (sbuf.st_mode & S_IXGID) hdr->mode |= 02000; #endif #ifdef S_ISVTX if (sbuf.st_mode & S_ISVTX) hdr->mode |= 01000; #endif hdr->nlink = sbuf.st_nlink; hdr->uid = sbuf.st_uid; hdr->gid = sbuf.st_gid; /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = sbuf.st_size; hdr->mtime = sbuf.st_mtime; return 0; } #endif /*!HAVE_W32_SYSTEM*/ /* Add a new entry. The name of a directory entry is ENTRYNAME; if that is NULL, DNAME is the name of the directory itself. Under Windows ENTRYNAME shall have backslashes replaced by standard slashes. */ static gpg_error_t add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) { gpg_error_t err; tar_header_t hdr; char *p; size_t dnamelen = strlen (dname); log_assert (dnamelen); hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1 + (entryname? strlen (entryname) : 0) + 1); if (!hdr) return gpg_error_from_syserror (); p = stpcpy (hdr->name, dname); if (entryname) { if (dname[dnamelen-1] != '/') *p++ = '/'; strcpy (p, entryname); } else { if (hdr->name[dnamelen-1] == '/') hdr->name[dnamelen-1] = 0; } #ifdef HAVE_DOSISH_SYSTEM err = fillup_entry_w32 (hdr); #else err = fillup_entry_posix (hdr); #endif if (err) xfree (hdr); else { if (opt.verbose) gpgtar_print_header (hdr, log_get_stream ()); *scanctrl->flist_tail = hdr; scanctrl->flist_tail = &hdr->next; } return 0; } static gpg_error_t scan_directory (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; #ifdef HAVE_W32_SYSTEM WIN32_FIND_DATAW fi; HANDLE hd = INVALID_HANDLE_VALUE; char *p; if (!*dname) return 0; /* An empty directory name has no entries. */ { char *fname; wchar_t *wfname; fname = xtrymalloc (strlen (dname) + 2 + 2 + 1); if (!fname) { err = gpg_error_from_syserror (); goto leave; } if (!strcmp (dname, "/")) strcpy (fname, "/*"); /* Trailing slash is not allowed. */ else if (!strcmp (dname, ".")) strcpy (fname, "*"); else if (*dname && dname[strlen (dname)-1] == '/') strcpy (stpcpy (fname, dname), "*"); else if (*dname && dname[strlen (dname)-1] != '*') strcpy (stpcpy (fname, dname), "/*"); else strcpy (fname, dname); for (p=fname; *p; p++) if (*p == '/') *p = '\\'; wfname = utf8_to_wchar (fname); xfree (fname); if (!wfname) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); goto leave; } hd = FindFirstFileW (wfname, &fi); if (hd == INVALID_HANDLE_VALUE) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); xfree (wfname); goto leave; } xfree (wfname); } do { char *fname = wchar_to_utf8 (fi.cFileName); if (!fname) { err = gpg_error_from_syserror (); log_error ("error converting filename: %s\n", w32_strerror (-1)); break; } for (p=fname; *p; p++) if (*p == '\\') *p = '/'; if (!strcmp (fname, "." ) || !strcmp (fname, "..")) err = 0; /* Skip self and parent dir entry. */ else if (!strncmp (dname, "./", 2) && dname[2]) err = add_entry (dname+2, fname, scanctrl); else err = add_entry (dname, fname, scanctrl); xfree (fname); } while (!err && FindNextFileW (hd, &fi)); if (err) ; else if (GetLastError () == ERROR_NO_MORE_FILES) err = 0; else { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); } leave: if (hd != INVALID_HANDLE_VALUE) FindClose (hd); #else /*!HAVE_W32_SYSTEM*/ DIR *dir; struct dirent *de; if (!*dname) return 0; /* An empty directory name has no entries. */ dir = opendir (dname); if (!dir) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); return err; } while ((de = readdir (dir))) { if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, "..")) continue; /* Skip self and parent dir entry. */ err = add_entry (dname, de->d_name, scanctrl); if (err) goto leave; } leave: closedir (dir); #endif /*!HAVE_W32_SYSTEM*/ return err; } static gpg_error_t scan_recursive (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; tar_header_t hdr, *start_tail, *stop_tail; if (scanctrl->nestlevel > 200) { log_error ("directories too deeply nested\n"); return gpg_error (GPG_ERR_RESOURCE_LIMIT); } scanctrl->nestlevel++; assert (scanctrl->flist_tail); start_tail = scanctrl->flist_tail; scan_directory (dname, scanctrl); stop_tail = scanctrl->flist_tail; hdr = *start_tail; for (; hdr && hdr != *stop_tail; hdr = hdr->next) if (hdr->typeflag == TF_DIRECTORY) { if (opt.verbose > 1) log_info ("scanning directory '%s'\n", hdr->name); scan_recursive (hdr->name, scanctrl); } scanctrl->nestlevel--; return err; } /* Returns true if PATTERN is acceptable. */ static int pattern_valid_p (const char *pattern) { if (!*pattern) return 0; if (*pattern == '.' && pattern[1] == '.') return 0; if (*pattern == '/' #ifdef HAVE_DOSISH_SYSTEM || *pattern == '\\' #endif ) return 0; /* Absolute filenames are not supported. */ #ifdef HAVE_DRIVE_LETTERS if (((*pattern >= 'a' && *pattern <= 'z') || (*pattern >= 'A' && *pattern <= 'Z')) && pattern[1] == ':') return 0; /* Drive letter are not allowed either. */ #endif /*HAVE_DRIVE_LETTERS*/ return 1; /* Okay. */ } static void store_xoctal (char *buffer, size_t length, unsigned long long value) { char *p, *pend; size_t n; unsigned long long v; assert (length > 1); v = value; n = length; p = pend = buffer + length; *--p = 0; /* Nul byte. */ n--; do { *--p = '0' + (v % 8); v /= 8; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = '0'; } else /* Does not fit into the field. Store as binary number. */ { v = value; n = length; p = pend = buffer + length; do { *--p = v; v /= 256; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = 0; if (*p & 0x80) BUG (); *p |= 0x80; /* Set binary flag. */ } else BUG (); } } static void store_uname (char *buffer, size_t length, unsigned long uid) { static int initialized; static unsigned long lastuid; static char lastuname[32]; if (!initialized || uid != lastuid) { #ifdef HAVE_W32_SYSTEM mem2str (lastuname, uid? "user":"root", sizeof lastuname); #else struct passwd *pw = getpwuid (uid); lastuid = uid; initialized = 1; if (pw) mem2str (lastuname, pw->pw_name, sizeof lastuname); else { log_info ("failed to get name for uid %lu\n", uid); *lastuname = 0; } #endif } mem2str (buffer, lastuname, length); } static void store_gname (char *buffer, size_t length, unsigned long gid) { static int initialized; static unsigned long lastgid; static char lastgname[32]; if (!initialized || gid != lastgid) { #ifdef HAVE_W32_SYSTEM mem2str (lastgname, gid? "users":"root", sizeof lastgname); #else struct group *gr = getgrgid (gid); lastgid = gid; initialized = 1; if (gr) mem2str (lastgname, gr->gr_name, sizeof lastgname); else { log_info ("failed to get name for gid %lu\n", gid); *lastgname = 0; } #endif } mem2str (buffer, lastgname, length); } static gpg_error_t build_header (void *record, tar_header_t hdr) { gpg_error_t err; struct ustar_raw_header *raw = record; size_t namelen, n; unsigned long chksum; unsigned char *p; memset (record, 0, RECORDSIZE); /* Store name and prefix. */ namelen = strlen (hdr->name); if (namelen < sizeof raw->name) memcpy (raw->name, hdr->name, namelen); else { n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix; for (n--; n ; n--) if (hdr->name[n] == '/') break; if (namelen - n < sizeof raw->name) { /* Note that the N is < sizeof prefix and that the delimiting slash is not stored. */ memcpy (raw->prefix, hdr->name, n); memcpy (raw->name, hdr->name+n+1, namelen - n); } else { err = gpg_error (GPG_ERR_TOO_LARGE); log_error ("error storing file '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } } store_xoctal (raw->mode, sizeof raw->mode, hdr->mode); store_xoctal (raw->uid, sizeof raw->uid, hdr->uid); store_xoctal (raw->gid, sizeof raw->gid, hdr->gid); store_xoctal (raw->size, sizeof raw->size, hdr->size); store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime); switch (hdr->typeflag) { case TF_REGULAR: raw->typeflag[0] = '0'; break; case TF_HARDLINK: raw->typeflag[0] = '1'; break; case TF_SYMLINK: raw->typeflag[0] = '2'; break; case TF_CHARDEV: raw->typeflag[0] = '3'; break; case TF_BLOCKDEV: raw->typeflag[0] = '4'; break; case TF_DIRECTORY: raw->typeflag[0] = '5'; break; case TF_FIFO: raw->typeflag[0] = '6'; break; default: return gpg_error (GPG_ERR_NOT_SUPPORTED); } memcpy (raw->magic, "ustar", 6); raw->version[0] = '0'; raw->version[1] = '0'; store_uname (raw->uname, sizeof raw->uname, hdr->uid); store_gname (raw->gname, sizeof raw->gname, hdr->gid); #ifndef HAVE_W32_SYSTEM if (hdr->typeflag == TF_SYMLINK) { int nread; nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1); if (nread < 0) { err = gpg_error_from_syserror (); log_error ("error reading symlink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } raw->linkname[nread] = 0; } #endif /*HAVE_W32_SYSTEM*/ /* Compute the checksum. */ memset (raw->checksum, ' ', sizeof raw->checksum); chksum = 0; p = record; for (n=0; n < RECORDSIZE; n++) chksum += *p++; store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum); raw->checksum[7] = ' '; return 0; } static gpg_error_t write_file (estream_t stream, tar_header_t hdr) { gpg_error_t err; char record[RECORDSIZE]; estream_t infp; size_t nread, nbytes; int any; err = build_header (record, hdr); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) { log_info ("skipping unsupported file '%s'\n", hdr->name); err = 0; } return err; } if (hdr->typeflag == TF_REGULAR) { infp = es_fopen (hdr->name, "rb"); if (!infp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s - skipped\n", hdr->name, gpg_strerror (err)); return err; } } else infp = NULL; err = write_record (stream, record); if (err) goto leave; if (hdr->typeflag == TF_REGULAR) { hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE; any = 0; while (hdr->nrecords--) { nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE); if (!nbytes) nbytes = RECORDSIZE; nread = es_fread (record, 1, nbytes, infp); if (nread != nbytes) { err = gpg_error_from_syserror (); log_error ("error reading file '%s': %s%s\n", hdr->name, gpg_strerror (err), any? " (file shrunk?)":""); goto leave; } any = 1; err = write_record (stream, record); if (err) goto leave; } nread = es_fread (record, 1, 1, infp); if (nread) log_info ("note: file '%s' has grown\n", hdr->name); } leave: if (err) es_fclose (infp); else if ((err = es_fclose (infp))) log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } static gpg_error_t write_eof_mark (estream_t stream) { gpg_error_t err; char record[RECORDSIZE]; memset (record, 0, sizeof record); err = write_record (stream, record); if (!err) err = write_record (stream, record); return err; } /* Create a new tarball using the names in the array INPATTERN. If INPATTERN is NULL take the pattern as null terminated strings from stdin or from the file specified by FILES_FROM. If NULL_NAMES is set the filenames in such a file are delimited by a binary Nul and not by a LF. */ gpg_error_t gpgtar_create (char **inpattern, const char *files_from, int null_names, int encrypt, int sign) { gpg_error_t err = 0; struct scanctrl_s scanctrl_buffer; scanctrl_t scanctrl = &scanctrl_buffer; tar_header_t hdr, *start_tail; estream_t files_from_stream = NULL; estream_t outstream = NULL; estream_t cipher_stream = NULL; int eof_seen = 0; memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; /* { unsigned int cpno, cpno2, cpno3; */ /* cpno = GetConsoleOutputCP (); */ /* cpno2 = GetACP (); */ /* cpno3 = GetOEMCP (); */ /* log_debug ("Codepages: Console: %u ANSI: %u OEM: %u\n", */ /* cpno, cpno2, cpno3); */ /* } */ if (!inpattern) { if (!files_from || !strcmp (files_from, "-")) { files_from = "-"; files_from_stream = es_stdin; if (null_names) es_set_binary (es_stdin); } else if (!(files_from_stream=es_fopen (files_from, null_names? "rb":"r"))) { err = gpg_error_from_syserror (); log_error ("error opening '%s': %s\n", files_from, gpg_strerror (err)); return err; } } if (opt.directory && gnupg_chdir (opt.directory)) { err = gpg_error_from_syserror (); log_error ("chdir to '%s' failed: %s\n", opt.directory, gpg_strerror (err)); return err; } while (!eof_seen) { char *pat, *p; int skip_this = 0; if (inpattern) { const char *pattern = *inpattern; if (!pattern) break; /* End of array. */ inpattern++; if (!*pattern) continue; pat = name_to_utf8 (pattern, 0); } else /* Read Nul or LF delimited pattern from files_from_stream. */ { int c; char namebuf[4096]; size_t n = 0; for (;;) { if ((c = es_getc (files_from_stream)) == EOF) { if (es_ferror (files_from_stream)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", files_from, gpg_strerror (err)); goto leave; } c = null_names ? 0 : '\n'; eof_seen = 1; } if (n >= sizeof namebuf - 1) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", files_from, "filename too long"); } } else namebuf[n++] = c; if (null_names) { if (!c) { namebuf[n] = 0; break; } } else /* Shall be LF delimited. */ { if (!c) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", files_from, "filename with embedded Nul"); } } else if ( c == '\n' ) { namebuf[n] = 0; ascii_trim_spaces (namebuf); n = strlen (namebuf); break; } } } if (skip_this || n < 2) continue; pat = name_to_utf8 (namebuf, opt.utf8strings); } if (!pat) { err = gpg_error_from_syserror (); log_error ("memory allocation problem: %s\n", gpg_strerror (err)); goto leave; } for (p=pat; *p; p++) if (*p == '\\') *p = '/'; if (opt.verbose > 1) log_info ("scanning '%s'\n", pat); start_tail = scanctrl->flist_tail; if (skip_this || !pattern_valid_p (pat)) log_error ("skipping invalid name '%s'\n", pat); else if (!add_entry (pat, NULL, scanctrl) && *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY)) scan_recursive (pat, scanctrl); xfree (pat); } if (files_from_stream && files_from_stream != es_stdin) es_fclose (files_from_stream); if (opt.outfile) { if (!strcmp (opt.outfile, "-")) outstream = es_stdout; else outstream = es_fopen (opt.outfile, "wb"); if (!outstream) { err = gpg_error_from_syserror (); goto leave; } } else { outstream = es_stdout; } if (outstream == es_stdout) es_set_binary (es_stdout); if (encrypt || sign) { cipher_stream = outstream; outstream = es_fopenmem (0, "rwb"); if (! outstream) { err = gpg_error_from_syserror (); goto leave; } } for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { err = write_file (outstream, hdr); if (err) goto leave; } err = write_eof_mark (outstream); if (err) goto leave; if (encrypt || sign) { strlist_t arg; ccparray_t ccp; const char **argv; err = es_fseek (outstream, 0, SEEK_SET); if (err) goto leave; /* '--encrypt' may be combined with '--symmetric', but 'encrypt' is set either way. Clear it if no recipients are specified. XXX: Fix command handling. */ if (opt.symmetric && opt.recipients == NULL) encrypt = 0; ccparray_init (&ccp, 0); if (encrypt) ccparray_put (&ccp, "--encrypt"); if (sign) ccparray_put (&ccp, "--sign"); if (opt.user) { ccparray_put (&ccp, "--local-user"); ccparray_put (&ccp, opt.user); } if (opt.symmetric) ccparray_put (&ccp, "--symmetric"); for (arg = opt.recipients; arg; arg = arg->next) { ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, arg->d); } for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, outstream, NULL, cipher_stream, NULL, NULL); xfree (argv); if (err) goto leave; } leave: if (!err) { gpg_error_t first_err; if (outstream != es_stdout) first_err = es_fclose (outstream); else first_err = es_fflush (outstream); outstream = NULL; if (cipher_stream != es_stdout) err = es_fclose (cipher_stream); else err = es_fflush (cipher_stream); cipher_stream = NULL; if (! err) err = first_err; } if (err) { log_error ("creating tarball '%s' failed: %s\n", opt.outfile ? opt.outfile : "-", gpg_strerror (err)); if (outstream && outstream != es_stdout) es_fclose (outstream); if (cipher_stream && cipher_stream != es_stdout) es_fclose (cipher_stream); if (opt.outfile) gnupg_remove (opt.outfile); } scanctrl->flist_tail = NULL; while ( (hdr = scanctrl->flist) ) { scanctrl->flist = hdr->next; xfree (hdr); } return err; } diff --git a/tools/wks-util.c b/tools/wks-util.c index cb1ecdaa2..085301fea 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -1,1232 +1,1232 @@ /* wks-utils.c - Common helper functions for wks tools * Copyright (C) 2016 g10 Code GmbH * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * 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 Lesser General Public License for more details. */ #include #include #include #include #include #include #include #include "../common/util.h" #include "../common/status.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/zb32.h" #include "../common/userids.h" #include "../common/mbox-util.h" #include "../common/sysutils.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" /* The stream to output the status information. Output is disabled if this is NULL. */ static estream_t statusfp; /* Set the status FD. */ void wks_set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (statusfp && statusfp != es_stdout && statusfp != es_stderr) es_fclose (statusfp); statusfp = NULL; if (fd == -1) return; if (fd == 1) statusfp = es_stdout; else if (fd == 2) statusfp = es_stderr; else statusfp = es_fdopen (fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", fd, gpg_strerror (gpg_error_from_syserror ())); } last_fd = fd; } /* Write a status line with code NO followed by the outout of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void wks_write_status (int no, const char *format, ...) { va_list arg_ptr; if (!statusfp) return; /* Not enabled. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if (format) { es_putc (' ', statusfp); va_start (arg_ptr, format); es_vfprintf (statusfp, format, arg_ptr); va_end (arg_ptr); } es_putc ('\n', statusfp); } /* Append UID to LIST and return the new item. On success LIST is * updated. On error ERRNO is set and NULL returned. */ static uidinfo_list_t append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created) { uidinfo_list_t r, sl; sl = xtrymalloc (sizeof *sl + strlen (uid)); if (!sl) return NULL; strcpy (sl->uid, uid); sl->created = created; sl->mbox = mailbox_from_userid (uid); sl->next = NULL; if (!*list) *list = sl; else { for (r = *list; r->next; r = r->next ) ; r->next = sl; } return sl; } /* Free the list of uid infos at LIST. */ void free_uidinfo_list (uidinfo_list_t list) { while (list) { uidinfo_list_t tmp = list->next; xfree (list->mbox); xfree (list); list = tmp; } } struct get_key_status_parm_s { const char *fpr; int found; int count; }; static void get_key_status_cb (void *opaque, const char *keyword, char *args) { struct get_key_status_parm_s *parm = opaque; /*log_debug ("%s: %s\n", keyword, args);*/ if (!strcmp (keyword, "EXPORTED")) { parm->count++; if (!ascii_strcasecmp (args, parm->fpr)) parm->found = 1; } } /* Get a key by fingerprint from gpg's keyring and make sure that the * mail address ADDRSPEC is included in the key. If EXACT is set the * returned user id must match Addrspec exactly and not just in the * addr-spec (mailbox) part. The key is returned as a new memory * stream at R_KEY. */ gpg_error_t wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec, int exact) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; estream_t key = NULL; struct get_key_status_parm_s parm; char *filterexp = NULL; memset (&parm, 0, sizeof parm); *r_key = NULL; key = es_fopenmem (0, "w+b"); if (!key) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } /* Prefix the key with the MIME content type. */ es_fputs ("Content-Type: application/pgp-keys\n" "\n", key); filterexp = es_bsprintf ("keep-uid=%s= %s", exact? "uid":"mbox", addrspec); if (!filterexp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "--export-options=export-minimal"); ccparray_put (&ccp, "--export-filter"); ccparray_put (&ccp, filterexp); ccparray_put (&ccp, "--export"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } parm.fpr = fingerprint; err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, NULL, key, get_key_status_cb, &parm); if (!err && parm.count > 1) err = gpg_error (GPG_ERR_TOO_MANY); else if (!err && !parm.found) err = gpg_error (GPG_ERR_NOT_FOUND); if (err) { log_error ("export failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (key); *r_key = key; key = NULL; leave: es_fclose (key); xfree (argv); xfree (filterexp); return err; } /* Helper for wks_list_key and wks_filter_uid. */ static void key_status_cb (void *opaque, const char *keyword, char *args) { (void)opaque; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); } /* Run gpg on KEY and store the primary fingerprint at R_FPR and the * list of mailboxes at R_MBOXES. Returns 0 on success; on error NULL * is stored at R_FPR and R_MBOXES and an error code is returned. * R_FPR may be NULL if the fingerprint is not needed. */ gpg_error_t wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t listing; char *line = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; char **fields = NULL; int nfields; int lnr; char *fpr = NULL; uidinfo_list_t mboxes = NULL; if (r_fpr) *r_fpr = NULL; *r_mboxes = NULL; /* Open a memory stream. */ listing = es_fopenmem (0, "w+b"); if (!listing) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--with-colons"); ccparray_put (&ccp, "--dry-run"); ccparray_put (&ccp, "--import-options=import-minimal,import-show"); ccparray_put (&ccp, "--import"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, key, NULL, listing, key_status_cb, NULL); if (err) { log_error ("import failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (listing); lnr = 0; maxlen = 2048; /* Set limit. */ while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0) { lnr++; if (!maxlen) { log_error ("received line too long\n"); err = gpg_error (GPG_ERR_LINE_TOO_LONG); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; /* log_debug ("line '%s'\n", line); */ xfree (fields); fields = strtokenize (line, ":"); if (!fields) { err = gpg_error_from_syserror (); log_error ("strtokenize failed: %s\n", gpg_strerror (err)); goto leave; } for (nfields = 0; fields[nfields]; nfields++) ; if (!nfields) { err = gpg_error (GPG_ERR_INV_ENGINE); goto leave; } if (!strcmp (fields[0], "sec")) { /* gpg may return "sec" as the first record - but we do not * accept secret keys. */ err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (lnr == 1 && strcmp (fields[0], "pub")) { /* First record is not a public key. */ err = gpg_error (GPG_ERR_INV_ENGINE); goto leave; } if (lnr > 1 && !strcmp (fields[0], "pub")) { /* More than one public key. */ err = gpg_error (GPG_ERR_TOO_MANY); goto leave; } if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb")) break; /* We can stop parsing here. */ if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr) { fpr = xtrystrdup (fields[9]); if (!fpr) { err = gpg_error_from_syserror (); goto leave; } } else if (!strcmp (fields[0], "uid") && nfields > 9) { /* Fixme: Unescape fields[9] */ if (!append_to_uidinfo_list (&mboxes, fields[9], parse_timestamp (fields[5], NULL))) { err = gpg_error_from_syserror (); goto leave; } } } if (len < 0 || es_ferror (listing)) { err = gpg_error_from_syserror (); log_error ("error reading memory stream\n"); goto leave; } if (!fpr) { err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (r_fpr) { *r_fpr = fpr; fpr = NULL; } *r_mboxes = mboxes; mboxes = NULL; leave: xfree (fpr); free_uidinfo_list (mboxes); xfree (fields); es_free (line); xfree (argv); es_fclose (listing); return err; } /* Run gpg as a filter on KEY and write the output to a new stream * stored at R_NEWKEY. The new key will contain only the user id UID. * Returns 0 on success. Only one key is expected in KEY. If BINARY * is set the resulting key is returned as a binary (non-armored) * keyblock. */ gpg_error_t wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid, int binary) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; estream_t newkey; char *filterexp = NULL; *r_newkey = NULL; /* Open a memory stream. */ newkey = es_fopenmem (0, "w+b"); if (!newkey) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } /* Prefix the key with the MIME content type. */ if (!binary) es_fputs ("Content-Type: application/pgp-keys\n" "\n", newkey); filterexp = es_bsprintf ("keep-uid=uid= %s", uid); if (!filterexp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); if (!binary) ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "--import-options=import-export"); ccparray_put (&ccp, "--import-filter"); ccparray_put (&ccp, filterexp); ccparray_put (&ccp, "--import"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, key, NULL, newkey, key_status_cb, NULL); if (err) { log_error ("import/export failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (newkey); *r_newkey = newkey; newkey = NULL; leave: xfree (filterexp); xfree (argv); es_fclose (newkey); return err; } /* Helper to write mail to the output(s). */ gpg_error_t wks_send_mime (mime_maker_t mime) { gpg_error_t err; estream_t mail; /* Without any option we take a short path. */ if (!opt.use_sendmail && !opt.output) { es_set_binary (es_stdout); return mime_maker_make (mime, es_stdout); } mail = es_fopenmem (0, "w+b"); if (!mail) { err = gpg_error_from_syserror (); return err; } err = mime_maker_make (mime, mail); if (!err && opt.output) { es_rewind (mail); err = send_mail_to_file (mail, opt.output); } if (!err && opt.use_sendmail) { es_rewind (mail); err = send_mail (mail); } es_fclose (mail); return err; } /* Parse the policy flags by reading them from STREAM and storing them * into FLAGS. If IGNORE_UNKNOWN is set unknown keywords are * ignored. */ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown) { enum tokens { TOK_SUBMISSION_ADDRESS, TOK_MAILBOX_ONLY, TOK_DANE_ONLY, TOK_AUTH_SUBMIT, TOK_MAX_PENDING, TOK_PROTOCOL_VERSION }; static struct { const char *name; enum tokens token; } keywords[] = { { "submission-address", TOK_SUBMISSION_ADDRESS }, { "mailbox-only", TOK_MAILBOX_ONLY }, { "dane-only", TOK_DANE_ONLY }, { "auth-submit", TOK_AUTH_SUBMIT }, { "max-pending", TOK_MAX_PENDING }, { "protocol-version", TOK_PROTOCOL_VERSION } }; gpg_error_t err = 0; int lnr = 0; char line[1024]; char *p, *keyword, *value; int i, n; memset (flags, 0, sizeof *flags); while (es_fgets (line, DIM(line)-1, stream) ) { lnr++; n = strlen (line); if (!n || line[n-1] != '\n') { err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); break; } trim_trailing_spaces (line); /* Skip empty and comment lines. */ for (p=line; spacep (p); p++) ; if (!*p || *p == '#') continue; if (*p == ':') { err = gpg_error (GPG_ERR_SYNTAX); break; } keyword = p; value = NULL; if ((p = strchr (p, ':'))) { /* Colon found: Keyword with value. */ *p++ = 0; for (; spacep (p); p++) ; if (!*p) { err = gpg_error (GPG_ERR_MISSING_VALUE); break; } value = p; } for (i=0; i < DIM (keywords); i++) if (!ascii_strcasecmp (keywords[i].name, keyword)) break; if (!(i < DIM (keywords))) { if (ignore_unknown) continue; err = gpg_error (GPG_ERR_INV_NAME); break; } switch (keywords[i].token) { case TOK_SUBMISSION_ADDRESS: if (!value || !*value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } xfree (flags->submission_address); flags->submission_address = xtrystrdup (value); if (!flags->submission_address) { err = gpg_error_from_syserror (); goto leave; } break; case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break; case TOK_DANE_ONLY: flags->dane_only = 1; break; case TOK_AUTH_SUBMIT: flags->auth_submit = 1; break; case TOK_MAX_PENDING: if (!value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* FIXME: Define whether these are seconds, hours, or days * and decide whether to allow other units. */ flags->max_pending = atoi (value); break; case TOK_PROTOCOL_VERSION: if (!value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } flags->protocol_version = atoi (value); break; } } if (!err && !es_feof (stream)) err = gpg_error_from_syserror (); leave: if (err) log_error ("error reading '%s', line %d: %s\n", es_fname_get (stream), lnr, gpg_strerror (err)); return err; } void wks_free_policy (policy_flags_t policy) { if (policy) { xfree (policy->submission_address); memset (policy, 0, sizeof *policy); } } /* Write the content of SRC to the new file FNAME. */ static gpg_error_t write_to_file (estream_t src, const char *fname) { gpg_error_t err; estream_t dst; char buffer[4096]; size_t nread, written; dst = es_fopen (fname, "wb"); if (!dst) return gpg_error_from_syserror (); do { nread = es_fread (buffer, 1, sizeof buffer, src); if (!nread) break; written = es_fwrite (buffer, 1, nread, dst); if (written != nread) break; } while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst)); if (!es_feof (src) || es_ferror (src) || es_ferror (dst)) { err = gpg_error_from_syserror (); es_fclose (dst); gnupg_remove (fname); return err; } if (es_fclose (dst)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); return err; } return 0; } /* Return the filename and optionally the addrspec for USERID at * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. If * HASH_ONLY is set only the has is returned at R_FNAME and no file is * created. */ gpg_error_t wks_fname_from_userid (const char *userid, int hash_only, char **r_fname, char **r_addrspec) { gpg_error_t err; char *addrspec = NULL; const char *domain; char *hash = NULL; const char *s; char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ *r_fname = NULL; if (r_addrspec) *r_addrspec = NULL; addrspec = mailbox_from_userid (userid); if (!addrspec) { if (opt.verbose || hash_only) log_info ("\"%s\" is not a proper mail address\n", userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } domain = strchr (addrspec, '@'); log_assert (domain); domain++; /* Hash user ID and create filename. */ s = strchr (addrspec, '@'); log_assert (s); gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec); hash = zb32_encode (shaxbuf, 8*20); if (!hash) { err = gpg_error_from_syserror (); goto leave; } if (hash_only) { *r_fname = hash; hash = NULL; err = 0; } else { *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); if (!*r_fname) err = gpg_error_from_syserror (); else err = 0; } leave: if (r_addrspec && addrspec) *r_addrspec = addrspec; else xfree (addrspec); xfree (hash); return err; } /* Compute the the full file name for the key with ADDRSPEC and return * it at R_FNAME. */ gpg_error_t wks_compute_hu_fname (char **r_fname, const char *addrspec) { gpg_error_t err; char *hash; const char *domain; char sha1buf[20]; char *fname; struct stat sb; *r_fname = NULL; domain = strchr (addrspec, '@'); if (!domain || !domain[1] || domain == addrspec) return gpg_error (GPG_ERR_INV_ARG); domain++; gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); hash = zb32_encode (sha1buf, 8*20); if (!hash) return gpg_error_from_syserror (); /* Try to create missing directories below opt.directory. */ fname = make_filename_try (opt.directory, domain, NULL); - if (fname && stat (fname, &sb) + if (fname && gnupg_stat (fname, &sb) && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose) log_info ("directory '%s' created\n", fname); xfree (fname); fname = make_filename_try (opt.directory, domain, "hu", NULL); - if (fname && stat (fname, &sb) + if (fname && gnupg_stat (fname, &sb) && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose) log_info ("directory '%s' created\n", fname); xfree (fname); /* Create the filename. */ fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); err = fname? 0 : gpg_error_from_syserror (); if (err) xfree (fname); else *r_fname = fname; /* Okay. */ xfree (hash); return err; } /* Make sure that a policy file exists for addrspec. Directories must * already exist. */ static gpg_error_t ensure_policy_file (const char *addrspec) { gpg_err_code_t ec; gpg_error_t err; const char *domain; char *fname; estream_t fp; domain = strchr (addrspec, '@'); if (!domain || !domain[1] || domain == addrspec) return gpg_error (GPG_ERR_INV_ARG); domain++; /* Create the filename. */ fname = make_filename_try (opt.directory, domain, "policy", NULL); err = fname? 0 : gpg_error_from_syserror (); if (err) goto leave; /* First a quick check whether it already exists. */ if (!(ec = gnupg_access (fname, F_OK))) { err = 0; /* File already exists. */ goto leave; } err = gpg_error (ec); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = 0; else { log_error ("domain %s: problem with '%s': %s\n", domain, fname, gpg_strerror (err)); goto leave; } /* Now create the file. */ fp = es_fopen (fname, "wxb"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EEXIST) err = 0; /* Was created between the gnupg_access() and es_fopen(). */ else log_error ("domain %s: error creating '%s': %s\n", domain, fname, gpg_strerror (err)); goto leave; } es_fprintf (fp, "# Policy flags for domain %s\n", domain); if (es_ferror (fp) || es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("policy file '%s' created\n", fname); /* Make sure the policy file world readable. */ if (gnupg_chmod (fname, "-rw-rw-r--")) { err = gpg_error_from_syserror (); log_error ("can't set permissions of '%s': %s\n", fname, gpg_strerror (err)); goto leave; } leave: xfree (fname); return err; } /* Helper form wks_cmd_install_key. */ static gpg_error_t install_key_from_spec_file (const char *fname) { gpg_error_t err; estream_t fp; char *line = NULL; size_t linelen = 0; size_t maxlen = 2048; char *fields[2]; unsigned int lnr = 0; if (!fname || !strcmp (fname, "")) fp = es_stdin; else fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } while (es_read_line (fp, &line, &linelen, &maxlen) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } lnr++; trim_spaces (line); if (!*line || *line == '#') continue; if (split_fields (line, fields, DIM(fields)) < 2) { log_error ("error reading '%s': syntax error at line %u\n", fname, lnr); continue; } err = wks_cmd_install_key (fields[0], fields[1]); if (err) goto leave; } if (es_ferror (fp)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } leave: if (fp != es_stdin) es_fclose (fp); es_free (line); return err; } /* Install a single key into the WKD by reading FNAME and extracting * USERID. If USERID is NULL FNAME is expected to be a list of fpr * mbox lines and for each line the respective key will be * installed. */ gpg_error_t wks_cmd_install_key (const char *fname, const char *userid) { gpg_error_t err; KEYDB_SEARCH_DESC desc; estream_t fp = NULL; char *addrspec = NULL; char *fpr = NULL; uidinfo_list_t uidlist = NULL; uidinfo_list_t uid, thisuid; time_t thistime; char *huname = NULL; int any; if (!userid) return install_key_from_spec_file (fname); addrspec = mailbox_from_userid (userid); if (!addrspec) { log_error ("\"%s\" is not a proper mail address\n", userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } if (!classify_user_id (fname, &desc, 1) && (desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_FPR20)) { /* FNAME looks like a fingerprint. Get the key from the * standard keyring. */ err = wks_get_key (&fp, fname, addrspec, 0); if (err) { log_error ("error getting key '%s' (uid='%s'): %s\n", fname, addrspec, gpg_strerror (err)); goto leave; } } else /* Take it from the file */ { fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } /* List the key so that we can figure out the newest UID with the * requested addrspec. */ err = wks_list_key (fp, &fpr, &uidlist); if (err) { log_error ("error parsing key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } thistime = 0; thisuid = NULL; any = 0; for (uid = uidlist; uid; uid = uid->next) { if (!uid->mbox) continue; /* Should not happen anyway. */ if (ascii_strcasecmp (uid->mbox, addrspec)) continue; /* Not the requested addrspec. */ any = 1; if (uid->created > thistime) { thistime = uid->created; thisuid = uid; } } if (!thisuid) thisuid = uidlist; /* This is the case for a missing timestamp. */ if (!any) { log_error ("public key in '%s' has no mail address '%s'\n", fname, addrspec); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } if (opt.verbose) log_info ("using key with user id '%s'\n", thisuid->uid); { estream_t fp2; es_rewind (fp); err = wks_filter_uid (&fp2, fp, thisuid->uid, 1); if (err) { log_error ("error filtering key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } es_fclose (fp); fp = fp2; } /* Hash user ID and create filename. */ err = wks_compute_hu_fname (&huname, addrspec); if (err) goto leave; /* Now that wks_compute_hu_fname has created missing directories we * can create a policy file if it does not exist. */ err = ensure_policy_file (addrspec); if (err) goto leave; /* Publish. */ err = write_to_file (fp, huname); if (err) { log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err)); goto leave; } /* Make sure it is world readable. */ if (gnupg_chmod (huname, "-rwxr--r--")) log_error ("can't set permissions of '%s': %s\n", huname, gpg_strerror (gpg_err_code_from_syserror())); if (!opt.quiet) log_info ("key %s published for '%s'\n", fpr, addrspec); leave: xfree (huname); free_uidinfo_list (uidlist); xfree (fpr); xfree (addrspec); es_fclose (fp); return err; } /* Remove the key with mail address in USERID. */ gpg_error_t wks_cmd_remove_key (const char *userid) { gpg_error_t err; char *addrspec = NULL; char *fname = NULL; err = wks_fname_from_userid (userid, 0, &fname, &addrspec); if (err) goto leave; if (gnupg_remove (fname)) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOENT) { if (!opt.quiet) log_info ("key for '%s' is not installed\n", addrspec); log_inc_errorcount (); err = 0; } else log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("key for '%s' removed\n", addrspec); err = 0; leave: xfree (fname); xfree (addrspec); return err; } /* Print the WKD hash for the user id to stdout. */ gpg_error_t wks_cmd_print_wkd_hash (const char *userid) { gpg_error_t err; char *addrspec, *fname; err = wks_fname_from_userid (userid, 1, &fname, &addrspec); if (err) return err; es_printf ("%s %s\n", fname, addrspec); xfree (fname); xfree (addrspec); return err; } /* Print the WKD URL for the user id to stdout. */ gpg_error_t wks_cmd_print_wkd_url (const char *userid) { gpg_error_t err; char *addrspec, *fname; char *domain; err = wks_fname_from_userid (userid, 1, &fname, &addrspec); if (err) return err; domain = strchr (addrspec, '@'); if (domain) *domain++ = 0; es_printf ("https://openpgpkey.%s/.well-known/openpgpkey/%s/hu/%s?l=%s\n", domain, domain, fname, addrspec); xfree (fname); xfree (addrspec); return err; }