diff --git a/agent/call-daemon.c b/agent/call-daemon.c index 26c25fcd9..f907f40eb 100644 --- a/agent/call-daemon.c +++ b/agent/call-daemon.c @@ -1,687 +1,684 @@ /* call-daemon - Common code for the call-XXX.c modules * Copyright (C) 2001, 2002, 2005, 2007, 2010, * 2011 Free Software Foundation, Inc. * Copyright (C) 2013 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #include #ifndef HAVE_W32_SYSTEM #include #endif #include #include "agent.h" #include #include "../common/strlist.h" /* Daemon type to module mapping. Make sure that they are added in the * same order as given by the daemon_type enum. */ static const int daemon_modules[DAEMON_MAX_TYPE] = { GNUPG_MODULE_NAME_SCDAEMON, GNUPG_MODULE_NAME_TPM2DAEMON }; /* Definition of module local data of the CTRL structure. */ struct daemon_local_s { /* We keep a list of all allocated context with an anchor at DAEMON_LOCAL_LIST (see below). */ struct daemon_local_s *next_local; /* Link back to the global structure. */ struct daemon_global_s *g; assuan_context_t ctx; /* NULL or session context for the daemon used with this connection. */ unsigned int in_use: 1; /* CTX is in use. */ unsigned int invalid:1; /* CTX is invalid, should be released. */ }; /* Primary holder of all the started daemons */ struct daemon_global_s { /* To keep track of all active daemon contexts, we keep a linked list anchored at this variable. */ struct daemon_local_s *local_list; /* A malloced string with the name of the socket to be used for additional connections. May be NULL if not provided by daemon. */ char *socket_name; /* The context of the primary connection. This is also used as a flag to indicate whether the daemon has been started. */ assuan_context_t primary_ctx; /* To allow reuse of the primary connection, the following flag is set to true if the primary context has been reset and is not in use by any connection. */ int primary_ctx_reusable; }; static struct daemon_global_s daemon_global[DAEMON_MAX_TYPE]; /* A Mutex used inside the start_daemon function. */ static npth_mutex_t start_daemon_lock; /* Communication object for wait_child_thread. */ struct wait_child_thread_parm_s { enum daemon_type type; pid_t pid; }; /* Thread to wait for daemon termination and cleanup of resources. */ static void * wait_child_thread (void *arg) { int err; struct wait_child_thread_parm_s *parm = arg; enum daemon_type type = parm->type; pid_t pid = parm->pid; #ifndef HAVE_W32_SYSTEM int wstatus; #endif const char *name = opt.daemon_program[type]; struct daemon_global_s *g = &daemon_global[type]; struct daemon_local_s *sl; xfree (parm); /* We have copied all data to the stack. */ #ifdef HAVE_W32_SYSTEM npth_unprotect (); /* Note that although we use a pid_t here, it is actually a HANDLE. */ WaitForSingleObject ((HANDLE)pid, INFINITE); npth_protect (); log_info ("daemon %s finished\n", name); #else /* !HAVE_W32_SYSTEM*/ again: npth_unprotect (); err = waitpid (pid, &wstatus, 0); npth_protect (); if (err < 0) { if (errno == EINTR) goto again; log_error ("waitpid for %s failed: %s\n", name, strerror (errno)); return NULL; } else { if (WIFEXITED (wstatus)) log_info ("daemon %s finished (status %d)\n", name, WEXITSTATUS (wstatus)); else if (WIFSIGNALED (wstatus)) log_info ("daemon %s killed by signal %d\n", name, WTERMSIG (wstatus)); else { if (WIFSTOPPED (wstatus)) log_info ("daemon %s stopped by signal %d\n", name, WSTOPSIG (wstatus)); goto again; } } #endif /*!HAVE_W32_SYSTEM*/ agent_flush_cache (1); /* Flush the PIN cache. */ err = npth_mutex_lock (&start_daemon_lock); if (err) { log_error ("failed to acquire the start_daemon lock: %s\n", strerror (err)); } else { assuan_set_flag (g->primary_ctx, ASSUAN_NO_WAITPID, 1); for (sl = g->local_list; sl; sl = sl->next_local) { sl->invalid = 1; if (!sl->in_use && sl->ctx) { assuan_release (sl->ctx); sl->ctx = NULL; } } g->primary_ctx = NULL; g->primary_ctx_reusable = 0; xfree (g->socket_name); g->socket_name = NULL; err = npth_mutex_unlock (&start_daemon_lock); if (err) log_error ("failed to release the start_daemon lock" " after waitpid for %s: %s\n", name, strerror (err)); } return NULL; } /* This function shall be called after having accessed the daemon. It * is currently not very useful but gives an opportunity to keep track * of connections currently calling daemon. Note that the "lock" * operation is done by the daemon_start function which must be called * and error checked before any daemon operation. CTRL is the usual * connection context and RC the error code to be passed through the * function. */ gpg_error_t daemon_unlock (enum daemon_type type, ctrl_t ctrl, gpg_error_t rc) { gpg_error_t err; if (ctrl->d_local[type]->in_use == 0) { log_error ("%s: CTX for type %d is not in use\n", __func__, (int)type); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } err = npth_mutex_lock (&start_daemon_lock); if (err) { log_error ("failed to acquire the start_daemon lock: %s\n", strerror (err)); return gpg_error (GPG_ERR_INTERNAL); } ctrl->d_local[type]->in_use = 0; if (ctrl->d_local[type]->invalid) { assuan_release (ctrl->d_local[type]->ctx); ctrl->d_local[type]->ctx = NULL; ctrl->d_local[type]->invalid = 0; } err = npth_mutex_unlock (&start_daemon_lock); if (err) { log_error ("failed to release the start_daemon lock: %s\n", strerror (err)); return gpg_error (GPG_ERR_INTERNAL); } return rc; } /* To make sure we leave no secrets in our image after forking of the daemon, we use this callback. */ static void atfork_cb (void *opaque, int where) { (void)opaque; if (!where) gcry_control (GCRYCTL_TERM_SECMEM); } /* Fork off the daemon if this has not already been done. Lock the * daemon and make sure that a proper context has been setup in CTRL. * This function might also lock the daemon, which means that the * caller must call unlock_daemon after this function has returned * success and the actual Assuan transaction been done. */ gpg_error_t daemon_start (enum daemon_type type, ctrl_t ctrl) { gpg_error_t err = 0; const char *pgmname; assuan_context_t ctx = NULL; const char *argv[5]; assuan_fd_t no_close_list[3]; int i; int rc; char *abs_homedir = NULL; struct daemon_global_s *g = &daemon_global[type]; const char *name = gnupg_module_name (daemon_modules[type]); log_assert (type < DAEMON_MAX_TYPE); /* if this fails, you forgot to add your new type to daemon_modules */ log_assert (DAEMON_MAX_TYPE == DIM (daemon_modules)); if (opt.disable_daemon[type]) return gpg_error (GPG_ERR_NOT_SUPPORTED); if (ctrl->d_local[type] && ctrl->d_local[type]->ctx) { ctrl->d_local[type]->in_use = 1; return 0; /* Okay, the context is fine. */ } if (ctrl->d_local[type] && ctrl->d_local[type]->in_use) { log_error ("%s: CTX of type %d is in use\n", __func__, type); return gpg_error (GPG_ERR_INTERNAL); } /* We need to serialize the access to scd_local_list and primary_scd_ctx. */ rc = npth_mutex_lock (&start_daemon_lock); if (rc) { log_error ("failed to acquire the start_daemon lock: %s\n", strerror (rc)); return gpg_error (GPG_ERR_INTERNAL); } /* If this is the first call for this session, setup the local data structure. */ if (!ctrl->d_local[type]) { ctrl->d_local[type] = xtrycalloc (1, sizeof *ctrl->d_local[type]); if (!ctrl->d_local[type]) { err = gpg_error_from_syserror (); rc = npth_mutex_unlock (&start_daemon_lock); if (rc) log_error ("failed to release the start_daemon lock: %s\n", strerror (rc)); return err; } ctrl->d_local[type]->g = g; ctrl->d_local[type]->next_local = g->local_list; g->local_list = ctrl->d_local[type]; /* FIXME: CHECK the G thing */ } ctrl->d_local[type]->in_use = 1; /* Check whether the pipe server has already been started and in this case either reuse a lingering pipe connection or establish a new socket based one. */ if (g->primary_ctx && g->primary_ctx_reusable) { ctx = g->primary_ctx; g->primary_ctx_reusable = 0; if (opt.verbose) log_info ("new connection to %s daemon established (reusing)\n", name); goto leave; } rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); err = rc; goto leave; } if (g->socket_name) { rc = assuan_socket_connect (ctx, g->socket_name, 0, 0); if (rc) { log_error ("can't connect to socket '%s': %s\n", g->socket_name, gpg_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_info ("new connection to %s daemon established\n", name); goto leave; } if (g->primary_ctx) { log_info ("%s daemon is running but won't accept further connections\n", name); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } /* Nope, it has not been started. Fire it up now. */ if (opt.verbose) log_info ("no running %s daemon - starting it\n", name); agent_flush_cache (1); /* Make sure the PIN cache is flushed. */ if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM err = gpg_error_from_syserror (); #endif log_error ("error flushing pending output: %s\n", strerror (errno)); /* At least Windows XP fails here with EBADF. According to docs and Wine an fflush(NULL) is the same as _flushall. However the Wime implementation does not flush stdin,stdout and stderr - see above. Lets try to ignore the error. */ #ifndef HAVE_W32_SYSTEM goto leave; #endif } /* If the daemon program has not been specified switch to the standard. */ if (!opt.daemon_program[type] || !*opt.daemon_program[type]) opt.daemon_program[type] = gnupg_module_name (daemon_modules[type]); if ( !(pgmname = strrchr (opt.daemon_program[type], '/'))) pgmname = opt.daemon_program[type]; else pgmname++; argv[0] = pgmname; argv[1] = "--multi-server"; if (gnupg_default_homedir_p ()) argv[2] = NULL; else { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) { log_error ("error building filename: %s\n", gpg_strerror (gpg_error_from_syserror ())); goto leave; } argv[2] = "--homedir"; argv[3] = abs_homedir; argv[4] = NULL; } i=0; if (!opt.running_detached) { if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); } no_close_list[i] = ASSUAN_INVALID_FD; /* Connect to the daemon and perform initial handshaking. Use detached flag so that under Windows DAEMON does not show up a new window. */ rc = assuan_pipe_connect (ctx, opt.daemon_program[type], argv, no_close_list, atfork_cb, NULL, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { log_error ("can't connect to the daemon %s: %s\n", name, gpg_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_info ("first connection to daemon %s established\n", name); /* Get the name of the additional socket opened by daemon. */ { membuf_t data; unsigned char *databuf; size_t datalen; xfree (g->socket_name); g->socket_name = NULL; init_membuf (&data, 256); assuan_transact (ctx, "GETINFO socket_name", put_membuf_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (databuf && datalen) { g->socket_name = xtrymalloc (datalen + 1); if (!g->socket_name) log_error ("warning: can't store socket name: %s\n", strerror (errno)); else { memcpy (g->socket_name, databuf, datalen); g->socket_name[datalen] = 0; if (DBG_IPC) log_debug ("additional connections at '%s'\n", g->socket_name); } } xfree (databuf); } - /* Tell the daemon we want him to send us an event signal. We - don't support this for W32CE. */ -#ifndef HAVE_W32CE_SYSTEM + /* Tell the daemon we want him to send us an event signal. */ if (opt.sigusr2_enabled) { char buf[100]; #ifdef HAVE_W32_SYSTEM snprintf (buf, sizeof buf, "OPTION event-signal=%lx", (unsigned long)get_agent_daemon_notify_event ()); #else snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); #endif assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); } -#endif /*HAVE_W32CE_SYSTEM*/ g->primary_ctx = ctx; g->primary_ctx_reusable = 0; { npth_t thread; npth_attr_t tattr; struct wait_child_thread_parm_s *wctp; wctp = xtrymalloc (sizeof *wctp); if (!wctp) { err = gpg_error_from_syserror (); log_error ("error preparing wait_child_thread: %s\n", strerror (err)); goto leave; } wctp->type = type; wctp->pid = assuan_get_pid (g->primary_ctx); err = npth_attr_init (&tattr); if (!err) { npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); err = npth_create (&thread, &tattr, wait_child_thread, wctp); if (err) log_error ("error spawning wait_child_thread: %s\n", strerror (err)); npth_attr_destroy (&tattr); } else xfree (wctp); } leave: rc = npth_mutex_unlock (&start_daemon_lock); if (rc) log_error ("failed to release the start_daemon lock: %s\n", strerror (rc)); xfree (abs_homedir); if (err) { daemon_unlock (type, ctrl, err); if (ctx) assuan_release (ctx); } else { ctrl->d_local[type]->ctx = ctx; ctrl->d_local[type]->invalid = 0; } return err; } /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because NPth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_daemon (void) { static int initialized; int err; if (!initialized) { err = npth_mutex_init (&start_daemon_lock, NULL); if (err) log_fatal ("error initializing mutex: %s\n", strerror (err)); initialized = 1; } } /* This function may be called to print information pertaining to the current state of this module to the log. */ void agent_daemon_dump_state (void) { int i; for (i = 0; i < DAEMON_MAX_TYPE; i++) { struct daemon_global_s *g = &daemon_global[i]; log_info ("%s: name %s primary_ctx=%p pid=%ld reusable=%d\n", __func__, gnupg_module_name (daemon_modules[i]), g->primary_ctx, (long)assuan_get_pid (g->primary_ctx), g->primary_ctx_reusable); if (g->socket_name) log_info ("%s: socket='%s'\n", __func__, g->socket_name); } } /* Check whether the daemon is active. This is a fast check without * any locking and might give a wrong result if another thread is * about to start the daemon or the daemon is about to be stopped. */ int agent_daemon_check_running (enum daemon_type type) { return !!daemon_global[type].primary_ctx; } /* Send a kill command to the daemon of TYPE */ void agent_kill_daemon (enum daemon_type type) { struct daemon_global_s *g = &daemon_global[type]; if (g->primary_ctx == NULL) return; /* FIXME: This assumes SCdaemon; we should add a new command * (e.g. SHUTDOWN) so that there is no need to have a daemon * specific command. */ assuan_transact (g->primary_ctx, "KILLSCD", NULL, NULL, NULL, NULL, NULL, NULL); agent_flush_cache (1); /* 1 := Flush the PIN cache. */ } /* Reset the daemons if they have been used. Actually it is not a reset but a cleanup of resources used by the current connection. */ void agent_reset_daemon (ctrl_t ctrl) { int i; int rc; rc = npth_mutex_lock (&start_daemon_lock); if (rc) { log_error ("failed to acquire the start_daemon lock: %s\n", strerror (rc)); return; } for (i = 0; i < DAEMON_MAX_TYPE; i++) if (ctrl->d_local[i]) { struct daemon_global_s *g = ctrl->d_local[i]->g; if (ctrl->d_local[i]->ctx) { /* For the primary connection we send a reset and keep * that connection open for reuse. */ if (ctrl->d_local[i]->ctx == g->primary_ctx) { /* Send a RESTART to the daemon. This is required for the primary connection as a kind of virtual EOF; we don't have another way to tell it that the next command should be viewed as if a new connection has been made. For the non-primary connections this is not needed as we simply close the socket. We don't check for an error here because the RESTART may fail for example if the daemon has already been terminated. Anyway, we need to set the reusable flag to make sure that the aliveness check can clean it up. */ assuan_transact (g->primary_ctx, "RESTART", NULL, NULL, NULL, NULL, NULL, NULL); g->primary_ctx_reusable = 1; } else /* Secondary connections. */ assuan_release (ctrl->d_local[i]->ctx); ctrl->d_local[i]->ctx = NULL; } /* Remove the local context from our list and release it. */ if (!g->local_list) BUG (); else if (g->local_list == ctrl->d_local[i]) g->local_list = ctrl->d_local[i]->next_local; else { struct daemon_local_s *sl; for (sl=g->local_list; sl->next_local; sl = sl->next_local) if (sl->next_local == ctrl->d_local[i]) break; if (!sl->next_local) BUG (); sl->next_local = ctrl->d_local[i]->next_local; } xfree (ctrl->d_local[i]); ctrl->d_local[i] = NULL; } rc = npth_mutex_unlock (&start_daemon_lock); if (rc) log_error ("failed to release the start_daemon lock: %s\n", strerror (rc)); } assuan_context_t daemon_type_ctx (enum daemon_type type, ctrl_t ctrl) { return ctrl->d_local[type]->ctx; } diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index d2ea91307..468427933 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1,3298 +1,3287 @@ /* gpg-agent.c - The GnuPG Agent * Copyright (C) 2000-2020 Free Software Foundation, Inc. * Copyright (C) 2000-2019 Werner Koch * Copyright (C) 2015-2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #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/comopt.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, oNoOptions, oHomedir, oNoDetach, oGrab, oNoGrab, oLogFile, oServer, oDaemon, oSupervised, oBatch, oPinentryProgram, oPinentryTouchFile, oPinentryInvisibleChar, oPinentryTimeout, oPinentryFormattedPassphrase, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oScdaemonProgram, oTpm2daemonProgram, oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, oMaxCacheTTLSSH, oEnforcePassphraseConstraints, oMinPassphraseLen, oMinPassphraseNonalpha, oCheckPassphrasePattern, oCheckSymPassphrasePattern, oMaxPassphraseDays, oEnablePassphraseHistory, oDisableExtendedKeyFormat, oEnableExtendedKeyFormat, oStealSocket, 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 gpgrt_opt_t opts[] = { ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"), ARGPARSE_header (NULL, N_("Options used for startup")), 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", "@"), #endif ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_n (oStealSocket, "steal-socket", "@"), 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 (oHomedir, "homedir", "@"), ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_noconffile (oNoOptions, "no-options", "@"), ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_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_s (oLogFile, "log-file", /* */ N_("|FILE|write server mode logs to FILE")), ARGPARSE_header ("Configuration", N_("Options controlling the configuration")), ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon", /* */ N_("do not use the SCdaemon") ), ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program", /* */ N_("|PGM|use PGM as the SCdaemon program") ), ARGPARSE_s_s (oTpm2daemonProgram, "tpm2daemon-program", /* */ N_("|PGM|use PGM as the tpm2daemon program") ), 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_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_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_i (oListenBacklog, "listen-backlog", "@"), ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_header ("Security", N_("Options controlling the security")), ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl", N_("|N|expire cached PINs after N seconds")), ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", /* */ N_("|N|expire SSH keys after N seconds")), ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl", /* */ N_("|N|set maximum PIN cache lifetime to N seconds")), ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh", /* */ N_("|N|set maximum SSH key lifetime to N seconds")), 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_u (oS2KCount, "s2k-count", "@"), ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"), ARGPARSE_header ("Passphrase policy", N_("Options enforcing a passphrase policy")), ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints", N_("do not allow bypassing the passphrase policy")), ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len", N_("|N|set minimal required length for new passphrases to N")), ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha", N_("|N|require at least N non-alpha" " characters for a new passphrase")), ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern", N_("|FILE|check new passphrases against pattern in FILE")), ARGPARSE_s_s (oCheckSymPassphrasePattern, "check-sym-passphrase-pattern", "@"), ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days", N_("|N|expire the passphrase after N days")), ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", N_("do not allow the reuse of old passphrases")), ARGPARSE_header ("Pinentry", N_("Options controlling the PIN-Entry")), ARGPARSE_s_n (oBatch, "batch", N_("never use the PIN-entry")), 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 (oGrab, "grab", N_("let PIN-Entry grab keyboard and mouse")), ARGPARSE_s_n (oNoGrab, "no-grab", "@"), 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", N_("|N|set the Pinentry timeout to N seconds")), ARGPARSE_s_n (oPinentryFormattedPassphrase, "pinentry-formatted-passphrase", "@"), ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry", N_("allow passphrase to be prompted through Emacs")), /* 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 + * seconds. Thus we use 4 seconds on all platforms. + * 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 +#define TIMERTICK_INTERVAL (4) +#define CHECK_OWN_SOCKET_INTERVAL (60) /* 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 indicating to start the daemon even if one already runs. */ static int steal_socket; /* 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 was last read on startup or if missing * the name of the standard config file. Any value here enabled the * rereading of the standard config files on SIGHUP. */ 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 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPG_AGENT@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 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 (gpgrt_argparse_t *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.pinentry_formatted_passphrase = 0; memset (opt.daemon_program, 0, sizeof opt.daemon_program); 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.check_sym_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; memset (opt.disable_daemon, 0, sizeof opt.disable_daemon); disable_check_own_socket = 0; /* Note: When changing the next line, change also gpgconf_list. */ opt.ssh_fingerprint_digest = GCRY_MD_SHA256; opt.s2k_count = 0; set_s2k_calibration_time (0); /* Set to default. */ return 1; } switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags); break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs->r.ret_str; break; case oDebugPinentry: opt.debug_pinentry = 1; break; case oLogFile: if (!reread) return 0; /* not handled */ if (!current_logfile || !pargs->r.ret_str || strcmp (current_logfile, pargs->r.ret_str)) { log_set_file (pargs->r.ret_str); xfree (current_logfile); current_logfile = xtrystrdup (pargs->r.ret_str); } break; case oNoGrab: opt.no_grab |= 1; break; case oGrab: opt.no_grab |= 2; break; case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break; case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break; case oPinentryInvisibleChar: xfree (opt.pinentry_invisible_char); opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; break; case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; case oPinentryFormattedPassphrase: opt.pinentry_formatted_passphrase = 1; break; case oTpm2daemonProgram: opt.daemon_program[DAEMON_TPM2D] = pargs->r.ret_str; break; case oScdaemonProgram: opt.daemon_program[DAEMON_SCD] = pargs->r.ret_str; break; case oDisableScdaemon: opt.disable_daemon[DAEMON_SCD] = 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 oCheckSymPassphrasePattern: opt.check_sym_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. */ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); } static void initialize_modules (void) { thread_init_once (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); initialize_module_cache (); initialize_module_call_pinentry (); initialize_module_daemon (); initialize_module_trustlist (); } /* The main entry point. */ int main (int argc, char **argv) { gpgrt_argparse_t pargs; int orig_argc; char **orig_argv; char *last_configname = NULL; const char *configname = NULL; int debug_argparser = 0; const char *shell; 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. */ gpgrt_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= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oDebug: case oDebugAll: debug_argparser++; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oDebugQuickRandom: gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); break; } } /* Reset the flags. */ pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); /* 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 */ /* The configuraton directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; /* We are re-using the struct, thus the reset flag. We OR the * flags so that the internal intialized flag won't be cleared. */ pargs.flags |= (ARGPARSE_FLAG_RESET | ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER); while (gpgrt_argparser (&pargs, opts, GPG_AGENT_NAME EXTSEP_S "conf")) { if (pargs.r_opt == ARGPARSE_CONFFILE) { if (debug_argparser) log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); if (pargs.r_type) { xfree (last_configname); last_configname = xstrdup (pargs.r.ret_str); configname = last_configname; } else configname = NULL; continue; } if (parse_rereadable_options (&pargs, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case 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 oNoVerbose: opt.verbose = 0; break; 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 oStealSocket: steal_socket = 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, pargs.lineno, "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, pargs.lineno, "write-env-file"); break; default: if (configname) pargs.err = ARGPARSE_PRINT_WARNING; else pargs.err = ARGPARSE_PRINT_ERROR; break; } } /* 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]); } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (!last_configname) config_filename = gpgrt_fnameconcat (gnupg_homedir (), GPG_AGENT_NAME EXTSEP_S "conf", NULL); else { config_filename = last_configname; last_configname = NULL; } if (log_get_errorcount(0)) exit(2); finalize_rereadable_options (); /* Get a default log file from common.conf. */ if (!logfile && !parse_comopt (GNUPG_MODULE_NAME_AGENT, debug_argparser)) { logfile = comopt.logfile; comopt.logfile = NULL; } #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 && !opt.quiet) log_info(_("WARNING: \"%s\" is a deprecated option\n"), "--supervised"); 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) { /* Note: If an option is runtime changeable, please set the * respective flag in the gpgconf-comp.c table. */ es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); es_printf ("default-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL ); es_printf ("default-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL_SSH ); es_printf ("max-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL ); es_printf ("max-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL_SSH ); es_printf ("min-passphrase-len:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_LEN ); es_printf ("min-passphrase-nonalpha:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_NONALPHA); es_printf ("check-passphrase-pattern:%lu:\n", GC_OPT_FLAG_DEFAULT); es_printf ("check-sym-passphrase-pattern:%lu:\n", GC_OPT_FLAG_DEFAULT); es_printf ("max-passphrase-days:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, MAX_PASSPHRASE_DAYS); es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "sha256"); 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", gpgrt_strusage(11), gpgrt_strusage(13) ); /* See below in "regular server mode" on why we remove certain * envvars. */ if (!opt.keep_display) gnupg_unsetenv ("DISPLAY"); gnupg_unsetenv ("INSIDE_EMACS"); /* Virtually create the sockets. Note that we use -1 here * because the whole thing works only on Unix. */ map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh); if (fd == -1) log_fatal ("no standard socket provided\n"); #ifdef HAVE_SIGPROCMASK if (startup_signal_mask_valid) { if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) log_error ("error restoring signal mask: %s\n", strerror (errno)); } else log_info ("no saved signal mask\n"); #endif /*HAVE_SIGPROCMASK*/ log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n", fd, fd_extra, fd_browser, fd_ssh); handle_connections (fd, fd_extra, fd_browser, fd_ssh); #endif /*!HAVE_W32_SYSTEM*/ } else if (!is_daemon) ; /* NOTREACHED */ else { /* Regular server mode */ gnupg_fd_t fd; gnupg_fd_t fd_extra = GNUPG_INVALID_FD; gnupg_fd_t fd_browser = GNUPG_INVALID_FD; gnupg_fd_t fd_ssh = GNUPG_INVALID_FD; #ifndef HAVE_W32_SYSTEM pid_t pid; #endif /* Remove the DISPLAY variable so that a pinentry does not default to a specific display. There is still a default display when gpg-agent was started using --display or a client requested this using an OPTION command. Note, that we don't do this when running in reverse daemon mode (i.e. when exec the program given as arguments). */ #ifndef HAVE_W32_SYSTEM if (!opt.keep_display && !argc) gnupg_unsetenv ("DISPLAY"); #endif /* Remove the INSIDE_EMACS variable so that a pinentry does not always try to interact with Emacs. The variable is set when a client requested this using an OPTION command. */ gnupg_unsetenv ("INSIDE_EMACS"); /* Create the sockets. */ socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1); fd = create_server_socket (socket_name, 1, 0, &redir_socket_name, &socket_nonce); if (opt.extra_socket) { if (socket_name_extra) socket_name_extra = create_socket_name (socket_name_extra, 0); else socket_name_extra = create_socket_name /**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1); opt.extra_socket = 2; /* Indicate that it has been malloced. */ fd_extra = create_server_socket (socket_name_extra, 0, 0, &redir_socket_name_extra, &socket_nonce_extra); } if (opt.browser_socket) { if (socket_name_browser) socket_name_browser = create_socket_name (socket_name_browser, 0); else socket_name_browser= create_socket_name /**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1); opt.browser_socket = 2; /* Indicate that it has been malloced. */ fd_browser = create_server_socket (socket_name_browser, 0, 0, &redir_socket_name_browser, &socket_nonce_browser); } socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1); fd_ssh = create_server_socket (socket_name_ssh, 0, 1, &redir_socket_name_ssh, &socket_nonce_ssh); /* If we are going to exec a program in the parent, we record the PID, so that the child may check whether the program is still alive. */ if (argc) parent_pid = getpid (); fflush (NULL); #ifdef HAVE_W32_SYSTEM (void)csh_style; (void)nodetach; initialize_modules (); #else /*!HAVE_W32_SYSTEM*/ pid = fork (); if (pid == (pid_t)-1) { log_fatal ("fork failed: %s\n", strerror (errno) ); exit (1); } else if (pid) { /* We are the parent */ char *infostr_ssh_sock, *infostr_ssh_valid; /* Close the socket FD. */ close (fd); /* The signal mask might not be correct right now and thus we restore it. That is not strictly necessary but some programs falsely assume a cleared signal mask. */ #ifdef HAVE_SIGPROCMASK if (startup_signal_mask_valid) { if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) log_error ("error restoring signal mask: %s\n", strerror (errno)); } else log_info ("no saved signal mask\n"); #endif /*HAVE_SIGPROCMASK*/ /* Create the SSH info string if enabled. */ if (ssh_support) { if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s", socket_name_ssh) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu", (unsigned long)getpid()) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } } *socket_name = 0; /* Don't let cleanup() remove the socket - the child should do this from now on */ if (opt.extra_socket) *socket_name_extra = 0; if (opt.browser_socket) *socket_name_browser = 0; *socket_name_ssh = 0; if (argc) { /* Run the program given on the commandline. */ if (ssh_support && (putenv (infostr_ssh_sock) || putenv (infostr_ssh_valid))) { log_error ("failed to set environment: %s\n", strerror (errno) ); kill (pid, SIGTERM ); exit (1); } /* Close all the file descriptors except the standard ones and those open at startup. We explicitly don't close 0,1,2 in case something went wrong collecting them at startup. */ close_all_fds (3, startup_fd_list); /* Run the command. */ execvp (argv[0], argv); log_error ("failed to run the command: %s\n", strerror (errno)); kill (pid, SIGTERM); exit (1); } else { /* Print the environment string, so that the caller can use shell's eval to set it */ if (csh_style) { if (ssh_support) { *strchr (infostr_ssh_sock, '=') = ' '; es_printf ("setenv %s;\n", infostr_ssh_sock); } } else { if (ssh_support) { es_printf ("%s; export SSH_AUTH_SOCK;\n", infostr_ssh_sock); } } if (ssh_support) { xfree (infostr_ssh_sock); xfree (infostr_ssh_valid); } exit (0); } /*NOTREACHED*/ } /* End parent */ /* This is the child */ initialize_modules (); /* Detach from tty and put process into a new session */ if (!nodetach ) { int i; unsigned int oldflags; /* Close stdin, stdout and stderr unless it is the log stream */ for (i=0; i <= 2; i++) { if (!log_test_fd (i) && i != fd ) { if ( ! close (i) && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) { log_error ("failed to open '%s': %s\n", "/dev/null", strerror (errno)); cleanup (); exit (1); } } } if (setsid() == -1) { log_error ("setsid() failed: %s\n", strerror(errno) ); cleanup (); exit (1); } log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); opt.running_detached = 1; /* Unless we are running with a program given on the command * line we can assume that the inotify things works and thus * we can avoid the regular stat calls. */ if (!argc) reliable_homedir_inotify = 1; } { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } #endif /*!HAVE_W32_SYSTEM*/ if (gnupg_chdir (gnupg_daemon_rootdir ())) { log_error ("chdir to '%s' failed: %s\n", gnupg_daemon_rootdir (), strerror (errno)); exit (1); } log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_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); } /* If a progress dispatcher callback has been associated with the * current connection unregister it. */ static void unregister_progress_cb (void) { struct progress_dispatch_s *dispatch; npth_t mytid = npth_self (); for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) if (dispatch->ctrl && dispatch->tid == mytid) break; if (dispatch) { dispatch->ctrl = NULL; dispatch->cb = NULL; } } /* Setup a progress callback CB for the current connection. Using a * CB of NULL disables the callback. */ void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total), ctrl_t ctrl) { struct progress_dispatch_s *dispatch, *firstfree; npth_t mytid = npth_self (); firstfree = NULL; for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) { if (dispatch->ctrl && dispatch->tid == mytid) break; if (!dispatch->ctrl && !firstfree) firstfree = dispatch; } if (!dispatch) /* None allocated: Reuse or allocate a new one. */ { if (firstfree) { dispatch = firstfree; } else if ((dispatch = xtrycalloc (1, sizeof *dispatch))) { dispatch->next = progress_dispatch_list; progress_dispatch_list = dispatch; } else { log_error ("error allocating new progress dispatcher slot: %s\n", gpg_strerror (gpg_error_from_syserror ())); return; } dispatch->ctrl = ctrl; dispatch->tid = mytid; } dispatch->cb = cb; } /* Each thread has its own local variables conveyed by a control structure usually identified by an argument named CTRL. This function is called immediately after allocating the control structure. Its purpose is to setup the default values for that structure. Note that some values may have already been set. */ static void agent_init_default_ctrl (ctrl_t ctrl) { log_assert (ctrl->session_env); /* Note we ignore malloc errors because we can't do much about it and the request will fail anyway shortly after this initialization. */ session_env_setenv (ctrl->session_env, "DISPLAY", default_display); session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname); session_env_setenv (ctrl->session_env, "TERM", default_ttytype); session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority); session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL); if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL; if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages) /**/ : NULL; ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET; } /* Release all resources allocated by default in the control structure. This is the counterpart to agent_init_default_ctrl. */ static void agent_deinit_default_ctrl (ctrl_t ctrl) { unregister_progress_cb (); session_env_release (ctrl->session_env); xfree (ctrl->digest.data); ctrl->digest.data = NULL; if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); if (ctrl->lc_messages) xfree (ctrl->lc_messages); } /* Because the ssh protocol does not send us information about the current TTY setting, we use this function to use those from startup or those explicitly set. This is also used for the restricted mode where we ignore requests to change the environment. */ gpg_error_t agent_copy_startup_env (ctrl_t ctrl) { gpg_error_t err = 0; int iterator = 0; const char *name, *value; while (!err && (name = session_env_list_stdenvnames (&iterator, NULL))) { if ((value = session_env_getenv (opt.startup_env, name))) err = session_env_setenv (ctrl->session_env, name, value); } if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype) if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype))) err = gpg_error_from_syserror (); if (!err && !ctrl->lc_messages && opt.startup_lc_messages) if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages))) err = gpg_error_from_syserror (); if (err) log_error ("error setting default session environment: %s\n", gpg_strerror (err)); return err; } /* Reread parts of the configuration. Note, that this function is obviously not thread-safe and should only be called from the PTH signal handler. Fixme: Due to the way the argument parsing works, we create a memory leak here for all string type arguments. There is currently no clean way to tell whether the memory for the argument has been allocated or points into the process's original arguments. Unless we have a mechanism to tell this, we need to live on with this. */ static void reread_configuration (void) { gpgrt_argparse_t pargs; char *twopart; int dummy; int logfile_seen = 0; if (!config_filename) return; /* No config file. */ twopart = strconcat (GPG_AGENT_NAME EXTSEP_S "conf" PATHSEP_S, config_filename, NULL); if (!twopart) return; /* Out of core. */ parse_rereadable_options (NULL, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = (ARGPARSE_FLAG_KEEP |ARGPARSE_FLAG_SYS |ARGPARSE_FLAG_USER); while (gpgrt_argparser (&pargs, opts, twopart) ) { if (pargs.r_opt == ARGPARSE_CONFFILE) { log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); } else if (pargs.r_opt < -1) pargs.err = ARGPARSE_PRINT_WARNING; else /* Try to parse this option - ignore unchangeable ones. */ { if (pargs.r_opt == oLogFile) logfile_seen = 1; parse_rereadable_options (&pargs, 1); } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ xfree (twopart); finalize_rereadable_options (); set_debug (); /* Get a default log file from common.conf. */ if (!logfile_seen && !parse_comopt (GNUPG_MODULE_NAME_AGENT, !!opt.debug)) { if (!current_logfile || !comopt.logfile || strcmp (current_logfile, comopt.logfile)) { log_set_file (comopt.logfile); xfree (current_logfile); current_logfile = comopt.logfile? xtrystrdup (comopt.logfile) : NULL; } } } /* 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) +#if defined(HAVE_W32_SYSTEM) void * get_agent_daemon_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*/ +#endif /*HAVE_W32_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. */ + /* At least our error code mapping on Windows-CE used to return + * EEXIST thus we better test for this on Windows . */ 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)) { if (steal_socket) log_info (N_("trying to steal socket from running %s\n"), "gpg-agent"); else { 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 (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 (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); /* 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", gpgrt_strusage(11), gpgrt_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) && 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 (0); reread_configuration (); agent_reload_trustlist (); /* We flush the module name cache so that after installing a "pinentry" binary that one can be used in case the "pinentry-basic" fallback was in use. */ gnupg_module_name_flush_some (); if (opt.disable_daemon[DAEMON_SCD]) agent_kill_daemon (DAEMON_SCD); } /* 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_daemon_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", gpgrt_strusage(11), gpgrt_strusage(13)); cleanup (); agent_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); cleanup (); agent_exit (0); break; #endif default: log_info ("signal %d received - no action defined\n", signo); } } #endif /* Check the nonce on a new connection. This is a NOP unless we are using our Unix domain socket emulation under Windows. */ static int check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) { if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), FD2INT(ctrl->thread_startup.fd), strerror (errno)); assuan_sock_close (ctrl->thread_startup.fd); xfree (ctrl); return -1; } else return 0; } #ifdef HAVE_W32_SYSTEM /* The window message processing function for Putty. Warning: This code runs as a native Windows thread. Use of our own functions needs to be bracket with pth_leave/pth_enter. */ static LRESULT CALLBACK putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { int ret = 0; int w32rc; COPYDATASTRUCT *cds; const char *mapfile; HANDLE maphd; PSID mysid = NULL; PSID mapsid = NULL; void *data = NULL; PSECURITY_DESCRIPTOR psd = NULL; ctrl_t ctrl = NULL; if (msg != WM_COPYDATA) { return DefWindowProc (hwnd, msg, wparam, lparam); } cds = (COPYDATASTRUCT*)lparam; if (cds->dwData != PUTTY_IPC_MAGIC) return 0; /* Ignore data with the wrong magic. */ mapfile = cds->lpData; if (!cds->cbData || mapfile[cds->cbData - 1]) return 0; /* Ignore empty and non-properly terminated strings. */ if (DBG_IPC) { npth_protect (); log_debug ("ssh map file '%s'", mapfile); npth_unprotect (); } maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile); if (DBG_IPC) { npth_protect (); log_debug ("ssh map handle %p\n", maphd); npth_unprotect (); } if (!maphd || maphd == INVALID_HANDLE_VALUE) return 0; npth_protect (); mysid = w32_get_user_sid (); if (!mysid) { log_error ("error getting my sid\n"); goto leave; } w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, &mapsid, NULL, NULL, NULL, &psd); if (w32rc) { log_error ("error getting sid of ssh map file: rc=%d", w32rc); goto leave; } if (DBG_IPC) { char *sidstr; if (!ConvertSidToStringSid (mysid, &sidstr)) sidstr = NULL; log_debug (" my sid: '%s'", sidstr? sidstr: "[error]"); LocalFree (sidstr); if (!ConvertSidToStringSid (mapsid, &sidstr)) sidstr = NULL; log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]"); LocalFree (sidstr); } if (!EqualSid (mysid, mapsid)) { log_error ("ssh map file has a non-matching sid\n"); goto leave; } data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (DBG_IPC) log_debug ("ssh IPC buffer at %p\n", data); if (!data) goto leave; /* log_printhex ("request:", data, 20); */ ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); goto leave; } ctrl->session_env = session_env_new (); if (!ctrl->session_env) { log_error ("error allocating session environment block: %s\n", strerror (errno) ); goto leave; } agent_init_default_ctrl (ctrl); if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN)) ret = 1; /* Valid ssh message has been constructed. */ agent_deinit_default_ctrl (ctrl); /* log_printhex (" reply:", data, 20); */ leave: xfree (ctrl); if (data) UnmapViewOfFile (data); xfree (mapsid); if (psd) LocalFree (psd); xfree (mysid); CloseHandle (maphd); npth_unprotect (); return ret; } #endif /*HAVE_W32_SYSTEM*/ #ifdef HAVE_W32_SYSTEM /* The thread handling Putty's IPC requests. */ static void * putty_message_thread (void *arg) { WNDCLASS wndwclass = {0, putty_message_proc, 0, 0, NULL, NULL, NULL, NULL, NULL, "Pageant"}; HWND hwnd; MSG msg; (void)arg; if (opt.verbose) log_info ("putty message loop thread started\n"); /* The message loop runs as thread independent from our nPth system. This also means that we need to make sure that we switch back to our system before calling any no-windows function. */ npth_unprotect (); /* First create a window to make sure that a message queue exists for this thread. */ if (!RegisterClass (&wndwclass)) { npth_protect (); log_error ("error registering Pageant window class"); return NULL; } hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0, 0, 0, 0, 0, HWND_MESSAGE, /* hWndParent */ NULL, /* hWndMenu */ NULL, /* hInstance */ NULL); /* lpParm */ if (!hwnd) { npth_protect (); log_error ("error creating Pageant window"); return NULL; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /* Back to nPth. */ npth_protect (); if (opt.verbose) log_info ("putty message loop thread stopped\n"); return NULL; } #endif /*HAVE_W32_SYSTEM*/ static void * do_start_connection_thread (ctrl_t ctrl) { active_connections++; agent_init_default_ctrl (ctrl); if (opt.verbose > 1 && !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 > 1 && !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_daemon_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)); gnupg_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"), gpgrt_strusage(11), gpgrt_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) { xfree (sockname); return; } npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); if (err) log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); npth_attr_destroy (&tattr); } /* Figure out whether an agent is available and running. Prints an error if not. If SILENT is true, no messages are printed. Returns 0 if the agent is running. */ static int check_for_running_agent (int silent) { gpg_error_t err; char *sockname; assuan_context_t ctx = NULL; sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) return gpg_error_from_syserror (); err = assuan_new (&ctx); if (!err) err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); xfree (sockname); if (err) { if (!silent) log_error (_("no gpg-agent running in this session\n")); if (ctx) assuan_release (ctx); return -1; } if (!opt.quiet && !silent) log_info ("gpg-agent running and available\n"); assuan_release (ctx); return 0; } diff --git a/agent/protect.c b/agent/protect.c index 30ee1345d..bc44fe107 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -1,1848 +1,1842 @@ /* protect.c - Un/Protect a secret key * Copyright (C) 1998-2003, 2007, 2009, 2011 Free Software Foundation, Inc. * Copyright (C) 1998-2003, 2007, 2009, 2011, 2013-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 #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #else # include #endif #include "agent.h" #include "cvt-openpgp.h" #include "../common/sexp-parse.h" #include "../common/openpgpdefs.h" /* For s2k functions. */ /* The protection mode for encryption. The supported modes for decryption are listed in agent_unprotect(). */ #define PROT_CIPHER GCRY_CIPHER_AES128 #define PROT_CIPHER_STRING "aes" #define PROT_CIPHER_KEYLEN (128/8) /* A table containing the information needed to create a protected private key. */ static const struct { const char *algo; const char *parmlist; int prot_from, prot_to; int ecc_hack; } protect_info[] = { { "rsa", "nedpqu", 2, 5 }, { "dsa", "pqgyx", 4, 4 }, { "elg", "pgyx", 3, 3 }, { "ecdsa","pabgnqd", 6, 6, 1 }, { "ecdh", "pabgnqd", 6, 6, 1 }, { "ecc", "pabgnqd", 6, 6, 1 }, { NULL } }; /* The number of milliseconds we use in the S2K function and the * calibrated count value. A count value of zero indicates that the * calibration has not yet been done or needs to be done again. */ static unsigned int s2k_calibration_time = AGENT_S2K_CALIBRATION; static unsigned long s2k_calibrated_count; /* A helper object for time measurement. */ struct calibrate_time_s { #ifdef HAVE_W32_SYSTEM FILETIME creation_time, exit_time, kernel_time, user_time; #else clock_t ticks; #endif }; static int hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned long s2kcount, unsigned char *key, size_t keylen); /* * Determine if we can use clock_gettime with CLOCK_THREAD_CPUTIME_ID, * at compile time. */ #if defined (CLOCK_THREAD_CPUTIME_ID) # if _POSIX_THREAD_CPUTIME > 0 # define USE_CLOCK_GETTIME 1 # elif _POSIX_THREAD_CPUTIME == 0 /* * In this case, we should check sysconf with _POSIX_THREAD_CPUTIME at * run time. As heuristics, for system with newer GNU C library, we * can assume it is available. */ # if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 17 # define USE_CLOCK_GETTIME 1 # endif # endif #else #undef USE_CLOCK_GETTIME #endif /* Get the process time and store it in DATA. */ static void calibrate_get_time (struct calibrate_time_s *data) { #ifdef HAVE_W32_SYSTEM -# ifdef HAVE_W32CE_SYSTEM - GetThreadTimes (GetCurrentThread (), - &data->creation_time, &data->exit_time, - &data->kernel_time, &data->user_time); -# else GetProcessTimes (GetCurrentProcess (), &data->creation_time, &data->exit_time, &data->kernel_time, &data->user_time); -# endif #elif defined (USE_CLOCK_GETTIME) struct timespec tmp; clock_gettime (CLOCK_THREAD_CPUTIME_ID, &tmp); data->ticks = (clock_t)(((unsigned long long)tmp.tv_sec * 1000000000 + tmp.tv_nsec) * CLOCKS_PER_SEC / 1000000000); #else data->ticks = clock (); #endif } static unsigned long calibrate_elapsed_time (struct calibrate_time_s *starttime) { struct calibrate_time_s stoptime; calibrate_get_time (&stoptime); #ifdef HAVE_W32_SYSTEM { unsigned long long t1, t2; t1 = (((unsigned long long)starttime->kernel_time.dwHighDateTime << 32) + starttime->kernel_time.dwLowDateTime); t1 += (((unsigned long long)starttime->user_time.dwHighDateTime << 32) + starttime->user_time.dwLowDateTime); t2 = (((unsigned long long)stoptime.kernel_time.dwHighDateTime << 32) + stoptime.kernel_time.dwLowDateTime); t2 += (((unsigned long long)stoptime.user_time.dwHighDateTime << 32) + stoptime.user_time.dwLowDateTime); return (unsigned long)((t2 - t1)/10000); } #else return (unsigned long)((((double) (stoptime.ticks - starttime->ticks)) /CLOCKS_PER_SEC)*1000); #endif } /* Run a test hashing for COUNT and return the time required in milliseconds. */ static unsigned long calibrate_s2k_count_one (unsigned long count) { int rc; char keybuf[PROT_CIPHER_KEYLEN]; struct calibrate_time_s starttime; calibrate_get_time (&starttime); rc = hash_passphrase ("123456789abcdef0", GCRY_MD_SHA1, 3, "saltsalt", count, keybuf, sizeof keybuf); if (rc) BUG (); return calibrate_elapsed_time (&starttime); } /* Measure the time we need to do the hash operations and deduce an S2K count which requires roughly some targeted amount of time. */ static unsigned long calibrate_s2k_count (void) { unsigned long count; unsigned long ms; for (count = 65536; count; count *= 2) { ms = calibrate_s2k_count_one (count); if (opt.verbose > 1) log_info ("S2K calibration: %lu -> %lums\n", count, ms); if (ms > s2k_calibration_time) break; } count = (unsigned long)(((double)count / ms) * s2k_calibration_time); count /= 1024; count *= 1024; if (count < 65536) count = 65536; if (opt.verbose) { ms = calibrate_s2k_count_one (count); log_info ("S2K calibration: %lu -> %lums\n", count, ms); } return count; } /* Set the calibration time. This may be called early at startup or * at any time. Thus it should one set variables. */ void set_s2k_calibration_time (unsigned int milliseconds) { if (!milliseconds) milliseconds = AGENT_S2K_CALIBRATION; else if (milliseconds > 60 * 1000) milliseconds = 60 * 1000; /* Cap at 60 seconds. */ s2k_calibration_time = milliseconds; s2k_calibrated_count = 0; /* Force re-calibration. */ } /* Return the calibrated S2K count. This is only public for the use * of the Assuan getinfo s2k_count_cal command. */ unsigned long get_calibrated_s2k_count (void) { if (!s2k_calibrated_count) s2k_calibrated_count = calibrate_s2k_count (); /* Enforce a lower limit. */ return s2k_calibrated_count < 65536 ? 65536 : s2k_calibrated_count; } /* Return the standard S2K count. */ unsigned long get_standard_s2k_count (void) { if (opt.s2k_count) return opt.s2k_count < 65536 ? 65536 : opt.s2k_count; return get_calibrated_s2k_count (); } /* Return the milliseconds required for the standard S2K * operation. */ unsigned long get_standard_s2k_time (void) { return calibrate_s2k_count_one (get_standard_s2k_count ()); } /* Same as get_standard_s2k_count but return the count in the encoding as described by rfc4880. */ unsigned char get_standard_s2k_count_rfc4880 (void) { unsigned long iterations; unsigned int count; unsigned char result; unsigned char c=0; iterations = get_standard_s2k_count (); if (iterations >= 65011712) return 255; /* Need count to be in the range 16-31 */ for (count=iterations>>6; count>=32; count>>=1) c++; result = (c<<4)|(count-16); if (S2K_DECODE_COUNT(result) < iterations) result++; return result; } /* Calculate the MIC for a private key or shared secret S-expression. SHA1HASH should point to a 20 byte buffer. This function is suitable for all algorithms. */ static gpg_error_t calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash) { const unsigned char *hash_begin, *hash_end; const unsigned char *s; size_t n; int is_shared_secret; s = plainkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "private-key")) is_shared_secret = 0; else if (smatch (&s, n, "shared-secret")) is_shared_secret = 1; else return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); hash_begin = s; if (!is_shared_secret) { s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* Skip the algorithm name. */ } while (*s == '(') { s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; if ( *s != ')' ) return gpg_error (GPG_ERR_INV_SEXP); s++; } if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); s++; hash_end = s; gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, hash_begin, hash_end - hash_begin); return 0; } /* Encrypt the parameter block starting at PROTBEGIN with length PROTLEN using the utf8 encoded key PASSPHRASE and return the entire encrypted block in RESULT or return with an error code. SHA1HASH is the 20 byte SHA-1 hash required for the integrity code. The parameter block is expected to be an incomplete canonical encoded S-Expression of the form (example in advanced format): (d #046129F..[some bytes not shown]..81#) (p #00e861b..[some bytes not shown]..f1#) (q #00f7a7c..[some bytes not shown]..61#) (u #304559a..[some bytes not shown]..9b#) the returned block is the S-Expression: (protected mode (parms) encrypted_octet_string) */ static int do_encryption (const unsigned char *hashbegin, size_t hashlen, const unsigned char *protbegin, size_t protlen, const char *passphrase, const char *timestamp_exp, size_t timestamp_exp_len, unsigned char **result, size_t *resultlen, unsigned long s2k_count, int use_ocb) { gcry_cipher_hd_t hd; const char *modestr; unsigned char hashvalue[20]; int blklen, enclen, outlen; unsigned char *iv = NULL; unsigned int ivsize; /* Size of the buffer allocated for IV. */ const unsigned char *s2ksalt; /* Points into IV. */ int rc; char *outbuf = NULL; char *p; int saltpos, ivpos, encpos; s2ksalt = iv; /* Silence compiler warning. */ *resultlen = 0; *result = NULL; modestr = (use_ocb? "openpgp-s2k3-ocb-aes" /* */: "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"); rc = gcry_cipher_open (&hd, PROT_CIPHER, use_ocb? GCRY_CIPHER_MODE_OCB : GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_SECURE); if (rc) return rc; /* We need to work on a copy of the data because this makes it * easier to add the trailer and the padding and more important we * have to prefix the text with 2 parenthesis. In CBC mode we * have to allocate enough space for: * * (()(4:hash4:sha120:)) + padding * * we always append a full block of random bytes as padding but * encrypt only what is needed for a full blocksize. In OCB mode we * have to allocate enough space for just: * * (()) */ blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER); if (use_ocb) { /* (( )) */ outlen = 2 + protlen + 2 ; enclen = outlen + 16 /* taglen */; outbuf = gcry_malloc_secure (enclen); } else { /* (( )( 4:hash 4:sha1 20: )) */ outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen; enclen = outlen/blklen * blklen; outbuf = gcry_malloc_secure (outlen); } if (!outbuf) { rc = out_of_core (); goto leave; } /* Allocate a buffer for the nonce and the salt. */ if (!rc) { /* Allocate random bytes to be used as IV, padding and s2k salt * or in OCB mode for a nonce and the s2k salt. The IV/nonce is * set later because for OCB we need to set the key first. */ ivsize = (use_ocb? 12 : (blklen*2)) + 8; iv = xtrymalloc (ivsize); if (!iv) rc = gpg_error_from_syserror (); else { gcry_create_nonce (iv, ivsize); s2ksalt = iv + ivsize - 8; } } /* Hash the passphrase and set the key. */ if (!rc) { unsigned char *key; size_t keylen = PROT_CIPHER_KEYLEN; key = gcry_malloc_secure (keylen); if (!key) rc = out_of_core (); else { rc = hash_passphrase (passphrase, GCRY_MD_SHA1, 3, s2ksalt, s2k_count? s2k_count:get_standard_s2k_count(), key, keylen); if (!rc) rc = gcry_cipher_setkey (hd, key, keylen); xfree (key); } } if (rc) goto leave; /* Set the IV/nonce. */ rc = gcry_cipher_setiv (hd, iv, use_ocb? 12 : blklen); if (rc) goto leave; if (use_ocb) { /* In OCB Mode we use only the public key parameters as AAD. */ rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin); if (!rc) rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len); if (!rc) rc = gcry_cipher_authenticate (hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin)); } else { /* Hash the entire expression for CBC mode. Because * TIMESTAMP_EXP won't get protected, we can't simply hash a * continuous buffer but need to call md_write several times. */ gcry_md_hd_t md; rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 ); if (!rc) { gcry_md_write (md, hashbegin, protbegin - hashbegin); gcry_md_write (md, protbegin, protlen); gcry_md_write (md, timestamp_exp, timestamp_exp_len); gcry_md_write (md, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin)); memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20); gcry_md_close (md); } } /* Encrypt. */ if (!rc) { p = outbuf; *p++ = '('; *p++ = '('; memcpy (p, protbegin, protlen); p += protlen; if (use_ocb) { *p++ = ')'; *p++ = ')'; } else { memcpy (p, ")(4:hash4:sha120:", 17); p += 17; memcpy (p, hashvalue, 20); p += 20; *p++ = ')'; *p++ = ')'; memcpy (p, iv+blklen, blklen); /* Add padding. */ p += blklen; } log_assert ( p - outbuf == outlen); if (use_ocb) { gcry_cipher_final (hd); rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0); if (!rc) { log_assert (outlen + 16 == enclen); rc = gcry_cipher_gettag (hd, outbuf + outlen, 16); } } else { rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0); } } if (rc) goto leave; /* Release cipher handle and check for errors. */ gcry_cipher_close (hd); /* Now allocate the buffer we want to return. This is (protected openpgp-s2k3-sha1-aes-cbc ((sha1 salt no_of_iterations) 16byte_iv) encrypted_octet_string) in canoncical format of course. We use asprintf and %n modifier and dummy values as placeholders. */ { char countbuf[35]; snprintf (countbuf, sizeof countbuf, "%lu", s2k_count ? s2k_count : get_standard_s2k_count ()); p = xtryasprintf ("(9:protected%d:%s((4:sha18:%n_8bytes_%u:%s)%d:%n%*s)%d:%n%*s)", (int)strlen (modestr), modestr, &saltpos, (unsigned int)strlen (countbuf), countbuf, use_ocb? 12 : blklen, &ivpos, use_ocb? 12 : blklen, "", enclen, &encpos, enclen, ""); if (!p) { gpg_error_t tmperr = out_of_core (); xfree (iv); xfree (outbuf); return tmperr; } } *resultlen = strlen (p); *result = (unsigned char*)p; memcpy (p+saltpos, s2ksalt, 8); memcpy (p+ivpos, iv, use_ocb? 12 : blklen); memcpy (p+encpos, outbuf, enclen); xfree (iv); xfree (outbuf); return 0; leave: gcry_cipher_close (hd); xfree (iv); xfree (outbuf); return rc; } /* Protect the key encoded in canonical format in PLAINKEY. We assume a valid S-Exp here. With USE_UCB set to -1 the default scheme is used (ie. either CBC or OCB), set to 0 the old CBC mode is used, and set to 1 OCB is used. */ int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen, unsigned long s2k_count, int use_ocb) { int rc; const char *parmlist; int prot_from_idx, prot_to_idx; const unsigned char *s; const unsigned char *hash_begin, *hash_end; const unsigned char *prot_begin, *prot_end, *real_end; size_t n; int c, infidx, i; char timestamp_exp[35]; unsigned char *protected; size_t protectedlen; int depth = 0; unsigned char *p; int have_curve = 0; if (use_ocb == -1) use_ocb = !!opt.enable_extended_key_format; /* Create an S-expression with the protected-at timestamp. */ memcpy (timestamp_exp, "(12:protected-at15:", 19); gnupg_get_isotime (timestamp_exp+19); timestamp_exp[19+15] = ')'; /* Parse original key. */ s = plainkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); depth++; hash_begin = s; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); for (infidx=0; protect_info[infidx].algo && !smatch (&s, n, protect_info[infidx].algo); infidx++) ; if (!protect_info[infidx].algo) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); /* The parser below is a complete mess: To make it robust for ECC use we should reorder the s-expression to include only what we really need and thus guarantee the right order for saving stuff. This should be done before calling this function and maybe with the help of the new gcry_sexp_extract_param. */ parmlist = protect_info[infidx].parmlist; prot_from_idx = protect_info[infidx].prot_from; prot_to_idx = protect_info[infidx].prot_to; prot_begin = prot_end = NULL; for (i=0; (c=parmlist[i]); i++) { if (i == prot_from_idx) prot_begin = s; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (n != 1 || c != *s) { if (n == 5 && !memcmp (s, "curve", 5) && !i && protect_info[infidx].ecc_hack) { /* This is a private ECC key but the first parameter is the name of the curve. We change the parameter list here to the one we expect in this case. */ have_curve = 1; parmlist = "?qd"; prot_from_idx = 2; prot_to_idx = 2; } else if (n == 5 && !memcmp (s, "flags", 5) && i == 1 && have_curve) { /* "curve" followed by "flags": Change again. */ parmlist = "??qd"; prot_from_idx = 3; prot_to_idx = 3; } else return gpg_error (GPG_ERR_INV_SEXP); } s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s +=n; /* skip value */ if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; if (i == prot_to_idx) prot_end = s; s++; } if (*s != ')' || !prot_begin || !prot_end ) return gpg_error (GPG_ERR_INV_SEXP); depth--; hash_end = s; s++; /* Skip to the end of the S-expression. */ log_assert (depth == 1); rc = sskip (&s, &depth); if (rc) return rc; log_assert (!depth); real_end = s-1; rc = do_encryption (hash_begin, hash_end - hash_begin + 1, prot_begin, prot_end - prot_begin + 1, passphrase, timestamp_exp, sizeof (timestamp_exp), &protected, &protectedlen, s2k_count, use_ocb); if (rc) return rc; /* Now create the protected version of the key. Note that the 10 extra bytes are for the inserted "protected-" string (the beginning of the plaintext reads: "((11:private-key(" ). The 35 term is the space for (12:protected-at15:). */ *resultlen = (10 + (prot_begin-plainkey) + protectedlen + 35 + (real_end-prot_end)); *result = p = xtrymalloc (*resultlen); if (!p) { gpg_error_t tmperr = out_of_core (); xfree (protected); return tmperr; } memcpy (p, "(21:protected-", 14); p += 14; memcpy (p, plainkey+4, prot_begin - plainkey - 4); p += prot_begin - plainkey - 4; memcpy (p, protected, protectedlen); p += protectedlen; memcpy (p, timestamp_exp, 35); p += 35; memcpy (p, prot_end+1, real_end - prot_end); p += real_end - prot_end; log_assert ( p - *result == *resultlen); xfree (protected); return 0; } /* Do the actual decryption and check the return list for consistency. */ static gpg_error_t do_decryption (const unsigned char *aad_begin, size_t aad_len, const unsigned char *aadhole_begin, size_t aadhole_len, const unsigned char *protected, size_t protectedlen, const char *passphrase, const unsigned char *s2ksalt, unsigned long s2kcount, const unsigned char *iv, size_t ivlen, int prot_cipher, int prot_cipher_keylen, int is_ocb, unsigned char **result) { int rc; int blklen; gcry_cipher_hd_t hd; unsigned char *outbuf; size_t reallen; blklen = gcry_cipher_get_algo_blklen (prot_cipher); if (is_ocb) { /* OCB does not require a multiple of the block length but we * check that it is long enough for the 128 bit tag and that we * have the 96 bit nonce. */ if (protectedlen < (4 + 16) || ivlen != 12) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); } else { if (protectedlen < 4 || (protectedlen%blklen)) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); } rc = gcry_cipher_open (&hd, prot_cipher, is_ocb? GCRY_CIPHER_MODE_OCB : GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_SECURE); if (rc) return rc; outbuf = gcry_malloc_secure (protectedlen); if (!outbuf) rc = out_of_core (); /* Hash the passphrase and set the key. */ if (!rc) { unsigned char *key; key = gcry_malloc_secure (prot_cipher_keylen); if (!key) rc = out_of_core (); else { rc = hash_passphrase (passphrase, GCRY_MD_SHA1, 3, s2ksalt, s2kcount, key, prot_cipher_keylen); if (!rc) rc = gcry_cipher_setkey (hd, key, prot_cipher_keylen); xfree (key); } } /* Set the IV/nonce. */ if (!rc) { rc = gcry_cipher_setiv (hd, iv, ivlen); } /* Decrypt. */ if (!rc) { if (is_ocb) { rc = gcry_cipher_authenticate (hd, aad_begin, aadhole_begin - aad_begin); if (!rc) rc = gcry_cipher_authenticate (hd, aadhole_begin + aadhole_len, aad_len - (aadhole_begin+aadhole_len - aad_begin)); if (!rc) { gcry_cipher_final (hd); rc = gcry_cipher_decrypt (hd, outbuf, protectedlen - 16, protected, protectedlen - 16); } if (!rc) { rc = gcry_cipher_checktag (hd, protected + protectedlen - 16, 16); if (gpg_err_code (rc) == GPG_ERR_CHECKSUM) { /* Return Bad Passphrase instead of checksum error */ rc = gpg_error (GPG_ERR_BAD_PASSPHRASE); } } } else { rc = gcry_cipher_decrypt (hd, outbuf, protectedlen, protected, protectedlen); } } /* Release cipher handle and check for errors. */ gcry_cipher_close (hd); if (rc) { xfree (outbuf); return rc; } /* Do a quick check on the data structure. */ if (*outbuf != '(' && outbuf[1] != '(') { xfree (outbuf); return gpg_error (GPG_ERR_BAD_PASSPHRASE); } /* Check that we have a consistent S-Exp. */ reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL); if (!reallen || (reallen + blklen < protectedlen) ) { xfree (outbuf); return gpg_error (GPG_ERR_BAD_PASSPHRASE); } *result = outbuf; return 0; } /* Merge the parameter list contained in CLEARTEXT with the original * protect lists PROTECTEDKEY by replacing the list at REPLACEPOS. * Return the new list in RESULT and the MIC value in the 20 byte * buffer SHA1HASH; if SHA1HASH is NULL no MIC will be computed. * CUTOFF and CUTLEN will receive the offset and the length of the * resulting list which should go into the MIC calculation but then be * removed. */ static gpg_error_t merge_lists (const unsigned char *protectedkey, size_t replacepos, const unsigned char *cleartext, unsigned char *sha1hash, unsigned char **result, size_t *resultlen, size_t *cutoff, size_t *cutlen) { size_t n, newlistlen; unsigned char *newlist, *p; const unsigned char *s; const unsigned char *startpos, *endpos; int i, rc; *result = NULL; *resultlen = 0; *cutoff = 0; *cutlen = 0; if (replacepos < 26) return gpg_error (GPG_ERR_BUG); /* Estimate the required size of the resulting list. We have a large safety margin of >20 bytes (FIXME: MIC hash from CLEARTEXT and the removed "protected-" */ newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL); if (!newlistlen) return gpg_error (GPG_ERR_BUG); n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL); if (!n) return gpg_error (GPG_ERR_BUG); newlistlen += n; newlist = gcry_malloc_secure (newlistlen); if (!newlist) return out_of_core (); /* Copy the initial segment */ strcpy ((char*)newlist, "(11:private-key"); p = newlist + 15; memcpy (p, protectedkey+15+10, replacepos-15-10); p += replacepos-15-10; /* Copy the cleartext. */ s = cleartext; if (*s != '(' && s[1] != '(') { xfree (newlist); return gpg_error (GPG_ERR_BUG); /*we already checked this */ } s += 2; startpos = s; while ( *s == '(' ) { s++; n = snext (&s); if (!n) goto invalid_sexp; s += n; n = snext (&s); if (!n) goto invalid_sexp; s += n; if ( *s != ')' ) goto invalid_sexp; s++; } if ( *s != ')' ) goto invalid_sexp; endpos = s; s++; /* Intermezzo: Get the MIC if requested. */ if (sha1hash) { if (*s != '(') goto invalid_sexp; s++; n = snext (&s); if (!smatch (&s, n, "hash")) goto invalid_sexp; n = snext (&s); if (!smatch (&s, n, "sha1")) goto invalid_sexp; n = snext (&s); if (n != 20) goto invalid_sexp; memcpy (sha1hash, s, 20); s += n; if (*s != ')') goto invalid_sexp; } /* Append the parameter list. */ memcpy (p, startpos, endpos - startpos); p += endpos - startpos; /* Skip over the protected list element in the original list. */ s = protectedkey + replacepos; log_assert (*s == '('); s++; i = 1; rc = sskip (&s, &i); if (rc) goto failure; /* Record the position of the optional protected-at expression. */ if (*s == '(') { const unsigned char *save_s = s; s++; n = snext (&s); if (smatch (&s, n, "protected-at")) { i = 1; rc = sskip (&s, &i); if (rc) goto failure; *cutlen = s - save_s; } s = save_s; } startpos = s; i = 2; /* we are inside this level */ rc = sskip (&s, &i); if (rc) goto failure; log_assert (s[-1] == ')'); endpos = s; /* one behind the end of the list */ /* Append the rest. */ if (*cutlen) *cutoff = p - newlist; memcpy (p, startpos, endpos - startpos); p += endpos - startpos; /* ready */ *result = newlist; *resultlen = newlistlen; return 0; failure: wipememory (newlist, newlistlen); xfree (newlist); return rc; invalid_sexp: wipememory (newlist, newlistlen); xfree (newlist); return gpg_error (GPG_ERR_INV_SEXP); } /* Unprotect the key encoded in canonical format. We assume a valid S-Exp here. If a protected-at item is available, its value will be stored at protected_at unless this is NULL. */ gpg_error_t agent_unprotect (ctrl_t ctrl, const unsigned char *protectedkey, const char *passphrase, gnupg_isotime_t protected_at, unsigned char **result, size_t *resultlen) { static const struct { const char *name; /* Name of the protection method. */ int algo; /* (A zero indicates the "openpgp-native" hack.) */ int keylen; /* Used key length in bytes. */ unsigned int is_ocb:1; } algotable[] = { { "openpgp-s2k3-sha1-aes-cbc", GCRY_CIPHER_AES128, (128/8)}, { "openpgp-s2k3-sha1-aes256-cbc", GCRY_CIPHER_AES256, (256/8)}, { "openpgp-s2k3-ocb-aes", GCRY_CIPHER_AES128, (128/8), 1}, { "openpgp-native", 0, 0 } }; int rc; const unsigned char *s; const unsigned char *protect_list; size_t n; int infidx, i; unsigned char sha1hash[20], sha1hash2[20]; const unsigned char *s2ksalt; unsigned long s2kcount; const unsigned char *iv; int prot_cipher, prot_cipher_keylen; int is_ocb; const unsigned char *aad_begin, *aad_end, *aadhole_begin, *aadhole_end; const unsigned char *prot_begin; unsigned char *cleartext; unsigned char *final; size_t finallen; size_t cutoff, cutlen; if (protected_at) *protected_at = 0; s = protectedkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "protected-private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); { aad_begin = aad_end = s; aad_end++; i = 1; rc = sskip (&aad_end, &i); if (rc) return rc; } s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); for (infidx=0; protect_info[infidx].algo && !smatch (&s, n, protect_info[infidx].algo); infidx++) ; if (!protect_info[infidx].algo) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); /* See whether we have a protected-at timestamp. */ protect_list = s; /* Save for later. */ if (protected_at) { while (*s == '(') { prot_begin = s; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "protected-at")) { n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (n != 15) return gpg_error (GPG_ERR_UNKNOWN_SEXP); memcpy (protected_at, s, 15); protected_at[15] = 0; break; } s += n; i = 1; rc = sskip (&s, &i); if (rc) return rc; } } /* Now find the list with the protected information. Here is an example for such a list: (protected openpgp-s2k3-sha1-aes-cbc ((sha1 ) ) ) */ s = protect_list; for (;;) { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); prot_begin = s; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "protected")) break; s += n; i = 1; rc = sskip (&s, &i); if (rc) return rc; } /* found */ { aadhole_begin = aadhole_end = prot_begin; aadhole_end++; i = 1; rc = sskip (&aadhole_end, &i); if (rc) return rc; } n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); /* Lookup the protection algo. */ prot_cipher = 0; /* (avoid gcc warning) */ prot_cipher_keylen = 0; /* (avoid gcc warning) */ is_ocb = 0; for (i=0; i < DIM (algotable); i++) if (smatch (&s, n, algotable[i].name)) { prot_cipher = algotable[i].algo; prot_cipher_keylen = algotable[i].keylen; is_ocb = algotable[i].is_ocb; break; } if (i == DIM (algotable)) return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); if (!prot_cipher) /* This is "openpgp-native". */ { gcry_sexp_t s_prot_begin; rc = gcry_sexp_sscan (&s_prot_begin, NULL, prot_begin, gcry_sexp_canon_len (prot_begin, 0,NULL,NULL)); if (rc) return rc; rc = convert_from_openpgp_native (ctrl, s_prot_begin, passphrase, &final); gcry_sexp_release (s_prot_begin); if (!rc) { *result = final; *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); } return rc; } if (*s != '(' || s[1] != '(') return gpg_error (GPG_ERR_INV_SEXP); s += 2; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "sha1")) return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); n = snext (&s); if (n != 8) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); s2ksalt = s; s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); /* We expect a list close as next, so we can simply use strtoul() here. We might want to check that we only have digits - but this is nothing we should worry about */ if (s[n] != ')' ) return gpg_error (GPG_ERR_INV_SEXP); /* Old versions of gpg-agent used the funny floating point number in a byte encoding as specified by OpenPGP. However this is not needed and thus we now store it as a plain unsigned integer. We can easily distinguish the old format by looking at its value: Less than 256 is an old-style encoded number; other values are plain integers. In any case we check that they are at least 65536 because we never used a lower value in the past and we should have a lower limit. */ s2kcount = strtoul ((const char*)s, NULL, 10); if (!s2kcount) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); if (s2kcount < 256) s2kcount = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6); if (s2kcount < 65536) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); s += n; s++; /* skip list end */ n = snext (&s); if (is_ocb) { if (n != 12) /* Wrong size of the nonce. */ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); } else { if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); } iv = s; s += n; if (*s != ')' ) return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); cleartext = NULL; /* Avoid cc warning. */ rc = do_decryption (aad_begin, aad_end - aad_begin, aadhole_begin, aadhole_end - aadhole_begin, s, n, passphrase, s2ksalt, s2kcount, iv, is_ocb? 12:16, prot_cipher, prot_cipher_keylen, is_ocb, &cleartext); if (rc) return rc; rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext, is_ocb? NULL : sha1hash, &final, &finallen, &cutoff, &cutlen); /* Albeit cleartext has been allocated in secure memory and thus xfree will wipe it out, we do an extra wipe just in case somethings goes badly wrong. */ wipememory (cleartext, n); xfree (cleartext); if (rc) return rc; if (!is_ocb) { rc = calculate_mic (final, sha1hash2); if (!rc && memcmp (sha1hash, sha1hash2, 20)) rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION); if (rc) { wipememory (final, finallen); xfree (final); return rc; } } /* Now remove the part which is included in the MIC but should not go into the final thing. */ if (cutlen) { memmove (final+cutoff, final+cutoff+cutlen, finallen-cutoff-cutlen); finallen -= cutlen; } *result = final; *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); return 0; } /* Check the type of the private key, this is one of the constants: PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the value 0), PRIVATE_KEY_CLEAR for an unprotected private key. PRIVATE_KEY_PROTECTED for an protected private key or PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored elsewhere. Finally PRIVATE_KEY_OPENPGP_NONE may be returned is the key is still in the openpgp-native format but without protection. */ int agent_private_key_type (const unsigned char *privatekey) { const unsigned char *s; size_t n; int i; s = privatekey; if (*s != '(') return PRIVATE_KEY_UNKNOWN; s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; if (smatch (&s, n, "protected-private-key")) { /* We need to check whether this is openpgp-native protected with the protection method "none". In that case we return a different key type so that the caller knows that there is no need to ask for a passphrase. */ if (*s != '(') return PRIVATE_KEY_PROTECTED; /* Unknown sexp - assume protected. */ s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ s += n; /* Skip over the algo */ /* Find the (protected ...) list. */ for (;;) { if (*s != '(') return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ if (smatch (&s, n, "protected")) break; s += n; i = 1; if (sskip (&s, &i)) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ } /* Found - Is this openpgp-native? */ n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ if (smatch (&s, n, "openpgp-native")) /* Yes. */ { if (*s != '(') return PRIVATE_KEY_UNKNOWN; /* Unknown sexp. */ s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ s += n; /* Skip over "openpgp-private-key". */ /* Find the (protection ...) list. */ for (;;) { if (*s != '(') return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ if (smatch (&s, n, "protection")) break; s += n; i = 1; if (sskip (&s, &i)) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ } /* Found - Is the mode "none"? */ n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ if (smatch (&s, n, "none")) return PRIVATE_KEY_OPENPGP_NONE; /* Yes. */ } return PRIVATE_KEY_PROTECTED; } if (smatch (&s, n, "shadowed-private-key")) return PRIVATE_KEY_SHADOWED; if (smatch (&s, n, "private-key")) return PRIVATE_KEY_CLEAR; return PRIVATE_KEY_UNKNOWN; } /* Transform a passphrase into a suitable key of length KEYLEN and store this key in the caller provided buffer KEY. The caller must provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on that mode an S2KSALT of 8 random bytes and an S2KCOUNT. Returns an error code on failure. */ static int hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned long s2kcount, unsigned char *key, size_t keylen) { /* The key derive function does not support a zero length string for the passphrase in the S2K modes. Return a better suited error code than GPG_ERR_INV_DATA. */ if (!passphrase || !*passphrase) return gpg_error (GPG_ERR_NO_PASSPHRASE); return gcry_kdf_derive (passphrase, strlen (passphrase), s2kmode == 3? GCRY_KDF_ITERSALTED_S2K : s2kmode == 1? GCRY_KDF_SALTED_S2K : s2kmode == 0? GCRY_KDF_SIMPLE_S2K : GCRY_KDF_NONE, hashalgo, s2ksalt, 8, s2kcount, keylen, key); } gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned int s2kcount, unsigned char *key, size_t keylen) { return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt, S2K_DECODE_COUNT (s2kcount), key, keylen); } /* Create an canonical encoded S-expression with the shadow info from a card's SERIALNO and the IDSTRING. */ unsigned char * make_shadow_info (const char *serialno, const char *idstring) { const char *s; char *info, *p; char numbuf[20]; size_t n; for (s=serialno, n=0; *s && s[1]; s += 2) n++; info = p = xtrymalloc (1 + sizeof numbuf + n + sizeof numbuf + strlen (idstring) + 1 + 1); if (!info) return NULL; *p++ = '('; p = stpcpy (p, smklen (numbuf, sizeof numbuf, n, NULL)); for (s=serialno; *s && s[1]; s += 2) *(unsigned char *)p++ = xtoi_2 (s); p = stpcpy (p, smklen (numbuf, sizeof numbuf, strlen (idstring), NULL)); p = stpcpy (p, idstring); *p++ = ')'; *p = 0; return (unsigned char *)info; } /* Create a shadow key from a public key. We use the shadow protocol "t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting S-expression is returned in an allocated buffer RESULT will point to. The input parameters are expected to be valid canonicalized S-expressions */ int agent_shadow_key_type (const unsigned char *pubkey, const unsigned char *shadow_info, const unsigned char *type, unsigned char **result) { const unsigned char *s; const unsigned char *point; size_t n; int depth = 0; char *p; size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL); size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL); if (!pubkey_len || !shadow_info_len) return gpg_error (GPG_ERR_INV_VALUE); s = pubkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "public-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* skip over the algorithm name */ while (*s != ')') { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s +=n; /* skip value */ if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; s++; } point = s; /* insert right before the point */ depth--; s++; log_assert (depth == 1); /* Calculate required length by taking in account: the "shadowed-" prefix, the "shadowed", shadow type as well as some parenthesis */ n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1; *result = xtrymalloc (n); p = (char*)*result; if (!p) return out_of_core (); p = stpcpy (p, "(20:shadowed-private-key"); /* (10:public-key ...)*/ memcpy (p, pubkey+14, point - (pubkey+14)); p += point - (pubkey+14); p += sprintf (p, "(8:shadowed%d:%s", (int)strlen(type), type); memcpy (p, shadow_info, shadow_info_len); p += shadow_info_len; *p++ = ')'; memcpy (p, point, pubkey_len - (point - pubkey)); p += pubkey_len - (point - pubkey); return 0; } int agent_shadow_key (const unsigned char *pubkey, const unsigned char *shadow_info, unsigned char **result) { return agent_shadow_key_type (pubkey, shadow_info, "t1-v1", result); } /* Parse a canonical encoded shadowed key and return a pointer to the inner list with the shadow_info and the shadow type */ gpg_error_t agent_get_shadow_info_type (const unsigned char *shadowkey, unsigned char const **shadow_info, unsigned char **shadow_type) { const unsigned char *s, *saved_s; size_t n, saved_n; int depth = 0; s = shadowkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "shadowed-private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* skip over the algorithm name */ for (;;) { if (*s == ')') return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "shadowed")) break; s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s +=n; /* skip value */ if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; s++; } /* Found the shadowed list, S points to the protocol */ n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); saved_s = s; saved_n = n; if (smatch (&s, n, "t1-v1") || smatch(&s, n, "tpm2-v1")) { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); if (shadow_info) *shadow_info = s; } else return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); s = saved_s; n = saved_n; if (shadow_type) { char *buf = xtrymalloc(n+1); if (!buf) return gpg_error_from_syserror (); memcpy (buf, s, n); buf[n] = '\0'; *shadow_type = buf; } return 0; } gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey, unsigned char const **shadow_info) { return agent_get_shadow_info_type (shadowkey, shadow_info, NULL); } int agent_is_tpm2_key (gcry_sexp_t s_skey) { unsigned char *buf; unsigned char *type; size_t len; gpg_error_t err; err = make_canon_sexp (s_skey, &buf, &len); if (err) return 0; err = agent_get_shadow_info_type (buf, NULL, &type); xfree (buf); if (err) return 0; err = strcmp (type, "tpm2-v1") == 0; xfree (type); return err; } gpg_error_t agent_get_shadow_type (const unsigned char *shadowkey, unsigned char **shadow_type) { return agent_get_shadow_info_type (shadowkey, NULL, shadow_type); } /* Parse the canonical encoded SHADOW_INFO S-expression. On success the hex encoded serial number is returned as a malloced strings at R_HEXSN and the Id string as a malloced string at R_IDSTR. On error an error code is returned and NULL is stored at the result parameters addresses. If the serial number or the ID string is not required, NULL may be passed for them. Note that R_PINLEN is currently not used by any caller. */ gpg_error_t parse_shadow_info (const unsigned char *shadow_info, char **r_hexsn, char **r_idstr, int *r_pinlen) { const unsigned char *s; size_t n; if (r_hexsn) *r_hexsn = NULL; if (r_idstr) *r_idstr = NULL; if (r_pinlen) *r_pinlen = 0; s = shadow_info; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (r_hexsn) { *r_hexsn = bin2hex (s, n, NULL); if (!*r_hexsn) return gpg_error_from_syserror (); } s += n; n = snext (&s); if (!n) { if (r_hexsn) { xfree (*r_hexsn); *r_hexsn = NULL; } return gpg_error (GPG_ERR_INV_SEXP); } if (r_idstr) { *r_idstr = xtrymalloc (n+1); if (!*r_idstr) { if (r_hexsn) { xfree (*r_hexsn); *r_hexsn = NULL; } return gpg_error_from_syserror (); } memcpy (*r_idstr, s, n); (*r_idstr)[n] = 0; } /* Parse the optional PINLEN. */ n = snext (&s); if (!n) return 0; if (r_pinlen) { char *tmpstr = xtrymalloc (n+1); if (!tmpstr) { if (r_hexsn) { xfree (*r_hexsn); *r_hexsn = NULL; } if (r_idstr) { xfree (*r_idstr); *r_idstr = NULL; } return gpg_error_from_syserror (); } memcpy (tmpstr, s, n); tmpstr[n] = 0; *r_pinlen = (int)strtol (tmpstr, NULL, 10); xfree (tmpstr); } return 0; } diff --git a/common/init.c b/common/init.c index b9f05f359..269119ff1 100644 --- a/common/init.c +++ b/common/init.c @@ -1,384 +1,287 @@ /* init.c - Various initializations * Copyright (C) 2007 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 . */ #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif -#ifdef HAVE_W32CE_SYSTEM -# include /* For _assuan_w32ce_finish_pipe. */ -#endif #include #include "util.h" #include "i18n.h" #include "w32help.h" /* This object is used to register memory cleanup functions. Technically they are not needed but they can avoid frequent questions about un-released memory. Note that we use the system malloc and not any wrappers. */ struct mem_cleanup_item_s; typedef struct mem_cleanup_item_s *mem_cleanup_item_t; struct mem_cleanup_item_s { mem_cleanup_item_t next; void (*func) (void); }; static mem_cleanup_item_t mem_cleanup_list; /* The default error source of the application. This is different from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the source file and thus is usable in code shared by applications. Note that we need to initialize it because otherwise some linkers (OS X at least) won't find the symbol when linking the t-*.c files. */ gpg_err_source_t default_errsource = 0; -#ifdef HAVE_W32CE_SYSTEM -static void parse_std_file_handles (int *argcp, char ***argvp); -static void -sleep_on_exit (void) -{ - /* The sshd on CE swallows some of the command output. Sleeping a - while usually helps. */ - Sleep (400); -} -#endif /*HAVE_W32CE_SYSTEM*/ #if HAVE_W32_SYSTEM static void prepare_w32_commandline (int *argcp, char ***argvp); #endif /*HAVE_W32_SYSTEM*/ static void run_mem_cleanup (void) { mem_cleanup_item_t next; while (mem_cleanup_list) { next = mem_cleanup_list->next; mem_cleanup_list->func (); free (mem_cleanup_list); mem_cleanup_list = next; } } void register_mem_cleanup_func (void (*func)(void)) { mem_cleanup_item_t item; for (item = mem_cleanup_list; item; item = item->next) if (item->func == func) return; /* Function has already been registered. */ item = malloc (sizeof *item); if (item) { item->func = func; item->next = mem_cleanup_list; mem_cleanup_list = item; } } /* If STRING is not NULL write string to es_stdout or es_stderr. MODE must be 1 or 2. If STRING is NULL flush the respective stream. */ static int writestring_via_estream (int mode, const char *string) { if (mode == 1 || mode == 2) { if (string) return es_fputs (string, mode == 1? es_stdout : es_stderr); else return es_fflush (mode == 1? es_stdout : es_stderr); } else return -1; } /* This function should be the first called after main. */ void early_system_init (void) { } /* This function is to be used early at program startup to make sure that some subsystems are initialized. This is in particular important for W32 to initialize the sockets so that our socket emulation code used directly as well as in libassuan may be used. It should best be called before any I/O is done so that setup required for logging is ready. ARGCP and ARGVP are the addresses of the parameters given to main. This function may modify them. This function should be called only via the macro init_common_subsystems. CAUTION: This might be called while running suid(root). */ void _init_common_subsystems (gpg_err_source_t errsource, int *argcp, char ***argvp) { /* Store the error source in a global variable. */ default_errsource = errsource; atexit (run_mem_cleanup); /* Try to auto set the character set. */ set_native_charset (NULL); #ifdef HAVE_W32_SYSTEM /* For W32 we need to initialize the socket layer. This is because we use recv and send in libassuan as well as at some other places. */ { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif -#ifdef HAVE_W32CE_SYSTEM - /* Register the sleep exit function before the estream init so that - the sleep will be called after the estream registered atexit - function which flushes the left open estream streams and in - particular es_stdout. */ - atexit (sleep_on_exit); -#endif - if (!gcry_check_version (NEED_LIBGCRYPT_VERSION)) { log_fatal (_("%s is too old (need %s, have %s)\n"), "libgcrypt", NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL)); } /* Initialize the Estream library. */ gpgrt_init (); gpgrt_set_alloc_func (gcry_realloc); -#ifdef HAVE_W32CE_SYSTEM - /* Special hack for Windows CE: We extract some options from arg - to setup the standard handles. */ - parse_std_file_handles (argcp, argvp); -#endif #ifdef HAVE_W32_SYSTEM /* We want gettext to always output UTF-8 and we put the console in * utf-8 mode. */ gettext_use_utf8 (1); if (!SetConsoleCP (CP_UTF8) || !SetConsoleOutputCP (CP_UTF8)) { /* Don't show the error if the program does not have a console. * This is for example the case for daemons. */ int rc = GetLastError (); if (rc != ERROR_INVALID_HANDLE) { log_info ("SetConsoleCP failed: %s\n", w32_strerror (rc)); log_info ("Warning: Garbled console data possible\n"); } } #endif /* Access the standard estreams as early as possible. If we don't do this the original stdio streams may have been closed when _es_get_std_stream is first use and in turn it would connect to the bit bucket. */ { int i; for (i=0; i < 3; i++) (void)_gpgrt_get_std_stream (i); } /* --version et al shall use estream as well. */ gpgrt_set_usage_outfnc (writestring_via_estream); /* Register our string mapper with gpgrt. */ gpgrt_set_fixed_string_mapper (map_static_macro_string); /* Logging shall use the standard socket directory as fallback. */ log_set_socket_dir_cb (gnupg_socketdir); #if HAVE_W32_SYSTEM /* For Standard Windows we use our own parser for the command line * so that we can return an array of utf-8 encoded strings. */ prepare_w32_commandline (argcp, argvp); #else (void)argcp; (void)argvp; #endif } -/* WindowsCE uses a very strange way of handling the standard streams. - There is a function SetStdioPath to associate a standard stream - with a file or a device but what we really want is to use pipes as - standard streams. Despite that we implement pipes using a device, - we would have some limitations on the number of open pipes due to - the 3 character limit of device file name. Thus we don't take this - path. Another option would be to install a file system driver with - support for pipes; this would allow us to get rid of the device - name length limitation. However, with GnuPG we can get away be - redefining the standard streams and passing the handles to be used - on the command line. This has also the advantage that it makes - creating a process much easier and does not require the - SetStdioPath set and restore game. The caller needs to pass the - rendezvous ids using up to three options: - - -&S0= -&S1= -&S2= - - They are all optional but they must be the first arguments on the - command line. Parsing stops as soon as an invalid option is found. - These rendezvous ids are then used to finish the pipe creation.*/ -#ifdef HAVE_W32CE_SYSTEM -static void -parse_std_file_handles (int *argcp, char ***argvp) -{ - int argc = *argcp; - char **argv = *argvp; - const char *s; - assuan_fd_t fd; - int i; - int fixup = 0; - - if (!argc) - return; - - for (argc--, argv++; argc; argc--, argv++) - { - s = *argv; - if (*s == '-' && s[1] == '&' && s[2] == 'S' - && (s[3] == '0' || s[3] == '1' || s[3] == '2') - && s[4] == '=' - && (strchr ("-01234567890", s[5]) || !strcmp (s+5, "null"))) - { - if (s[5] == 'n') - fd = ASSUAN_INVALID_FD; - else - fd = _assuan_w32ce_finish_pipe (atoi (s+5), s[3] != '0'); - _es_set_std_fd (s[3] - '0', (int)fd); - fixup++; - } - else - break; - } - - if (fixup) - { - argc = *argcp; - argc -= fixup; - *argcp = argc; - - argv = *argvp; - for (i=1; i < argc; i++) - argv[i] = argv[i + fixup]; - for (; i < argc + fixup; i++) - argv[i] = NULL; - } - - -} -#endif /*HAVE_W32CE_SYSTEM*/ - - /* For Windows we need to parse the command line so that we can * provide an UTF-8 encoded argv. If there is any Unicode character * we return a new array but if there is no Unicode character we do * nothing. */ #ifdef HAVE_W32_SYSTEM static void prepare_w32_commandline (int *r_argc, char ***r_argv) { const wchar_t *wcmdline, *ws; char *cmdline; int argc; char **argv; const char *s; int i, globing, itemsalloced; s = gpgrt_strusage (95); globing = (s && *s == '1'); wcmdline = GetCommandLineW (); if (!wcmdline) { log_error ("GetCommandLineW failed\n"); return; /* Ooops. */ } if (!globing) { /* If globbing is not enabled we use our own parser only if * there are any non-ASCII characters. */ for (ws=wcmdline; *ws; ws++) if (!iswascii (*ws)) break; if (!*ws) return; /* No Unicode - return directly. */ } cmdline = wchar_to_utf8 (wcmdline); if (!cmdline) { log_error ("parsing command line failed: %s\n", strerror (errno)); return; /* Ooops. */ } gpgrt_annotate_leaked_object (cmdline); argv = w32_parse_commandline (cmdline, globing, &argc, &itemsalloced); if (!argv) { log_error ("parsing command line failed: %s\n", "internal error"); return; /* Ooops. */ } gpgrt_annotate_leaked_object (argv); if (itemsalloced) { for (i=0; i < argc; i++) gpgrt_annotate_leaked_object (argv[i]); } *r_argv = argv; *r_argc = argc; } #endif /*HAVE_W32_SYSTEM*/ diff --git a/common/iobuf.c b/common/iobuf.c index 05486b976..2137604a9 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -1,2977 +1,2961 @@ /* iobuf.c - File Handling for OpenPGP. * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2006, 2007, 2008, * 2009, 2010, 2011 Free Software Foundation, Inc. * 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 #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif #ifdef __riscos__ # include # include #endif /* __riscos__ */ #include #include "util.h" #include "sysutils.h" #include "iobuf.h" /*-- Begin configurable part. --*/ /* The standard size of the internal buffers. */ #define DEFAULT_IOBUF_BUFFER_SIZE (64*1024) /* To avoid a potential DoS with compression packets we better limit the number of filters in a chain. */ #define MAX_NESTING_FILTER 64 /* The threshold for switching to use external buffers directly instead of the internal buffers. */ #define IOBUF_ZEROCOPY_THRESHOLD_SIZE 1024 /*-- End configurable part. --*/ /* The size of the iobuffers. This can be changed using the * iobuf_set_buffer_size function. */ static unsigned int iobuf_buffer_size = DEFAULT_IOBUF_BUFFER_SIZE; #ifdef HAVE_W32_SYSTEM -# ifdef HAVE_W32CE_SYSTEM -# define FD_FOR_STDIN (es_fileno (es_stdin)) -# define FD_FOR_STDOUT (es_fileno (es_stdout)) -# else -# define FD_FOR_STDIN (GetStdHandle (STD_INPUT_HANDLE)) -# define FD_FOR_STDOUT (GetStdHandle (STD_OUTPUT_HANDLE)) -# endif +# define FD_FOR_STDIN (GetStdHandle (STD_INPUT_HANDLE)) +# define FD_FOR_STDOUT (GetStdHandle (STD_OUTPUT_HANDLE)) #else /*!HAVE_W32_SYSTEM*/ # define FD_FOR_STDIN (0) # define FD_FOR_STDOUT (1) #endif /*!HAVE_W32_SYSTEM*/ /* The context used by the file filter. */ typedef struct { gnupg_fd_t fp; /* Open file pointer or handle. */ int keep_open; int no_cache; int eof_seen; int delayed_rc; int print_only_name; /* Flags indicating that fname is not a real file. */ char fname[1]; /* Name of the file. */ } file_filter_ctx_t; /* The context used by the estream filter. */ typedef struct { estream_t fp; /* Open estream handle. */ int keep_open; int no_cache; int eof_seen; int use_readlimit; /* Take care of the readlimit. */ size_t readlimit; /* Number of bytes left to read. */ int print_only_name; /* Flags indicating that fname is not a real file. */ char fname[1]; /* Name of the file. */ } file_es_filter_ctx_t; /* Object to control the "close cache". */ struct close_cache_s { struct close_cache_s *next; gnupg_fd_t fp; char fname[1]; }; typedef struct close_cache_s *close_cache_t; static close_cache_t close_cache; int iobuf_debug_mode; #ifdef HAVE_W32_SYSTEM typedef struct { int sock; int keep_open; int no_cache; int eof_seen; int print_only_name; /* Flag indicating that fname is not a real file. */ char fname[1]; /* Name of the file */ } sock_filter_ctx_t; #endif /*HAVE_W32_SYSTEM*/ /* The first partial length header block must be of size 512 to make * it easier (and more efficient) we use a min. block size of 512 for * all chunks (but the last one) */ #define OP_MIN_PARTIAL_CHUNK 512 #define OP_MIN_PARTIAL_CHUNK_2POW 9 /* The context we use for the block filter (used to handle OpenPGP length information header). */ typedef struct { int use; size_t size; size_t count; int partial; /* 1 = partial header, 2 in last partial packet. */ char *buffer; /* Used for partial header. */ size_t buflen; /* Used size of buffer. */ int first_c; /* First character of a partial header (which is > 0). */ int eof; } block_filter_ctx_t; /* Local prototypes. */ static int underflow (iobuf_t a, int clear_pending_eof); static int underflow_target (iobuf_t a, int clear_pending_eof, size_t target); static int translate_file_handle (int fd, int for_write); /* Sends any pending data to the filter's FILTER function. Note: this works on the filter and not on the whole pipeline. That is, iobuf_flush doesn't necessarily cause data to be written to any underlying file; it just causes any data buffered at the filter A to be sent to A's filter function. If A is a IOBUF_OUTPUT_TEMP filter, then this also enlarges the buffer by iobuf_buffer_size. May only be called on an IOBUF_OUTPUT or IOBUF_OUTPUT_TEMP filters. */ static int filter_flush (iobuf_t a); /* This is a replacement for strcmp. Under W32 it does not distinguish between backslash and slash. */ static int fd_cache_strcmp (const char *a, const char *b) { #ifdef HAVE_DOSISH_SYSTEM for (; *a && *b; a++, b++) { if (*a != *b && !((*a == '/' && *b == '\\') || (*a == '\\' && *b == '/')) ) break; } return *(const unsigned char *)a - *(const unsigned char *)b; #else return strcmp (a, b); #endif } /* * Invalidate (i.e. close) a cached iobuf */ static int fd_cache_invalidate (const char *fname) { close_cache_t cc; int rc = 0; assert (fname); if (DBG_IOBUF) log_debug ("fd_cache_invalidate (%s)\n", fname); for (cc = close_cache; cc; cc = cc->next) { if (cc->fp != GNUPG_INVALID_FD && !fd_cache_strcmp (cc->fname, fname)) { if (DBG_IOBUF) log_debug (" did (%s)\n", cc->fname); #ifdef HAVE_W32_SYSTEM if (!CloseHandle (cc->fp)) rc = -1; #else rc = close (cc->fp); #endif cc->fp = GNUPG_INVALID_FD; } } return rc; } /* Try to sync changes to the disk. This is to avoid data loss during a system crash in write/close/rename cycle on some file systems. */ static int fd_cache_synchronize (const char *fname) { int err = 0; #ifdef HAVE_FSYNC close_cache_t cc; if (DBG_IOBUF) log_debug ("fd_cache_synchronize (%s)\n", fname); for (cc=close_cache; cc; cc = cc->next ) { if (cc->fp != GNUPG_INVALID_FD && !fd_cache_strcmp (cc->fname, fname)) { if (DBG_IOBUF) log_debug (" did (%s)\n", cc->fname); err = fsync (cc->fp); } } #else (void)fname; #endif /*HAVE_FSYNC*/ return err; } static gnupg_fd_t direct_open (const char *fname, const char *mode, int mode700) { #ifdef HAVE_W32_SYSTEM unsigned long da, cd, sm; HANDLE hfile; (void)mode700; /* Note, that we do not handle all mode combinations */ /* According to the ReactOS source it seems that open() of the * standard MSW32 crt does open the file in shared mode which is * something new for MS applications ;-) */ if (strchr (mode, '+')) { if (fd_cache_invalidate (fname)) return GNUPG_INVALID_FD; da = GENERIC_READ | GENERIC_WRITE; cd = OPEN_EXISTING; sm = FILE_SHARE_READ | FILE_SHARE_WRITE; } else if (strchr (mode, 'w')) { if (fd_cache_invalidate (fname)) return GNUPG_INVALID_FD; da = GENERIC_WRITE; cd = CREATE_ALWAYS; sm = FILE_SHARE_WRITE; } else { da = GENERIC_READ; cd = OPEN_EXISTING; sm = FILE_SHARE_READ; } /* We always use the Unicode version because it supports file names * longer than MAX_PATH. (requires gpgrt 1.45) */ if (1) { wchar_t *wfname = gpgrt_fname_to_wchar (fname); if (wfname) { hfile = CreateFileW (wfname, da, sm, NULL, cd, FILE_ATTRIBUTE_NORMAL, NULL); xfree (wfname); } else hfile = INVALID_HANDLE_VALUE; } return hfile; #else /*!HAVE_W32_SYSTEM*/ int oflag; int cflag = S_IRUSR | S_IWUSR; if (!mode700) cflag |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; /* Note, that we do not handle all mode combinations */ if (strchr (mode, '+')) { if (fd_cache_invalidate (fname)) return GNUPG_INVALID_FD; oflag = O_RDWR; } else if (strchr (mode, 'w')) { if (fd_cache_invalidate (fname)) return GNUPG_INVALID_FD; oflag = O_WRONLY | O_CREAT | O_TRUNC; } else { oflag = O_RDONLY; } #ifdef O_BINARY if (strchr (mode, 'b')) oflag |= O_BINARY; #endif #ifdef __riscos__ { struct stat buf; /* Don't allow iobufs on directories */ if (!stat (fname, &buf) && S_ISDIR (buf.st_mode) && !S_ISREG (buf.st_mode)) return __set_errno (EISDIR); } #endif return open (fname, oflag, cflag); #endif /*!HAVE_W32_SYSTEM*/ } /* * Instead of closing an FD we keep it open and cache it for later reuse * Note that this caching strategy only works if the process does not chdir. */ static void fd_cache_close (const char *fname, gnupg_fd_t fp) { close_cache_t cc; assert (fp); if (!fname || !*fname) { #ifdef HAVE_W32_SYSTEM CloseHandle (fp); #else close (fp); #endif if (DBG_IOBUF) log_debug ("fd_cache_close (%d) real\n", (int)fp); return; } /* try to reuse a slot */ for (cc = close_cache; cc; cc = cc->next) { if (cc->fp == GNUPG_INVALID_FD && !fd_cache_strcmp (cc->fname, fname)) { cc->fp = fp; if (DBG_IOBUF) log_debug ("fd_cache_close (%s) used existing slot\n", fname); return; } } /* add a new one */ if (DBG_IOBUF) log_debug ("fd_cache_close (%s) new slot created\n", fname); cc = xcalloc (1, sizeof *cc + strlen (fname)); strcpy (cc->fname, fname); cc->fp = fp; cc->next = close_cache; close_cache = cc; } /* * Do a direct_open on FNAME but first try to reuse one from the fd_cache */ static gnupg_fd_t fd_cache_open (const char *fname, const char *mode) { close_cache_t cc; assert (fname); for (cc = close_cache; cc; cc = cc->next) { if (cc->fp != GNUPG_INVALID_FD && !fd_cache_strcmp (cc->fname, fname)) { gnupg_fd_t fp = cc->fp; cc->fp = GNUPG_INVALID_FD; if (DBG_IOBUF) log_debug ("fd_cache_open (%s) using cached fp\n", fname); #ifdef HAVE_W32_SYSTEM if (SetFilePointer (fp, 0, NULL, FILE_BEGIN) == 0xffffffff) { log_error ("rewind file failed on handle %p: ec=%d\n", fp, (int) GetLastError ()); fp = GNUPG_INVALID_FD; } #else if (lseek (fp, 0, SEEK_SET) == (off_t) - 1) { log_error ("can't rewind fd %d: %s\n", fp, strerror (errno)); fp = GNUPG_INVALID_FD; } #endif return fp; } } if (DBG_IOBUF) log_debug ("fd_cache_open (%s) not cached\n", fname); return direct_open (fname, mode, 0); } static int file_filter (void *opaque, int control, iobuf_t chain, byte * buf, size_t * ret_len) { file_filter_ctx_t *a = opaque; gnupg_fd_t f = a->fp; size_t size = *ret_len; size_t nbytes = 0; int rc = 0; (void)chain; /* Not used. */ if (control == IOBUFCTRL_UNDERFLOW) { log_assert (size); /* We need a buffer. */ if (a->eof_seen) { rc = -1; *ret_len = 0; } else if (a->delayed_rc) { rc = a->delayed_rc; a->delayed_rc = 0; if (rc == -1) a->eof_seen = -1; *ret_len = 0; } else { #ifdef HAVE_W32_SYSTEM unsigned long nread; nbytes = 0; if (!ReadFile (f, buf, size, &nread, NULL)) { int ec = (int) GetLastError (); if (ec != ERROR_BROKEN_PIPE) { rc = gpg_error_from_errno (ec); log_error ("%s: read error: ec=%d\n", a->fname, ec); } } else if (!nread) { a->eof_seen = 1; rc = -1; } else { nbytes = nread; } #else int n; nbytes = 0; read_more: do { n = read (f, buf + nbytes, size - nbytes); } while (n == -1 && errno == EINTR); if (n > 0) { nbytes += n; if (nbytes < size) goto read_more; } else if (!n) /* eof */ { if (nbytes) a->delayed_rc = -1; else { a->eof_seen = 1; rc = -1; } } else /* error */ { rc = gpg_error_from_syserror (); if (gpg_err_code (rc) != GPG_ERR_EPIPE) log_error ("%s: read error: %s\n", a->fname, gpg_strerror (rc)); if (nbytes) { a->delayed_rc = rc; rc = 0; } } #endif *ret_len = nbytes; } } else if (control == IOBUFCTRL_FLUSH) { if (size) { #ifdef HAVE_W32_SYSTEM byte *p = buf; unsigned long n; nbytes = size; do { if (size && !WriteFile (f, p, nbytes, &n, NULL)) { int ec = (int) GetLastError (); rc = gpg_error_from_errno (ec); log_error ("%s: write error: ec=%d\n", a->fname, ec); break; } p += n; nbytes -= n; } while (nbytes); nbytes = p - buf; #else byte *p = buf; int n; nbytes = size; do { do { n = write (f, p, nbytes); } while (n == -1 && errno == EINTR); if (n > 0) { p += n; nbytes -= n; } } while (n != -1 && nbytes); if (n == -1) { rc = gpg_error_from_syserror (); log_error ("%s: write error: %s\n", a->fname, strerror (errno)); } nbytes = p - buf; #endif } *ret_len = nbytes; } else if (control == IOBUFCTRL_INIT) { a->eof_seen = 0; a->delayed_rc = 0; a->keep_open = 0; a->no_cache = 0; } else if (control == IOBUFCTRL_DESC) { mem2str (buf, "file_filter(fd)", *ret_len); } else if (control == IOBUFCTRL_FREE) { if (f != FD_FOR_STDIN && f != FD_FOR_STDOUT) { if (DBG_IOBUF) log_debug ("%s: close fd/handle %d\n", a->fname, FD2INT (f)); if (!a->keep_open) fd_cache_close (a->no_cache ? NULL : a->fname, f); } xfree (a); /* We can free our context now. */ } return rc; } /* Similar to file_filter but using the estream system. */ static int file_es_filter (void *opaque, int control, iobuf_t chain, byte * buf, size_t * ret_len) { file_es_filter_ctx_t *a = opaque; estream_t f = a->fp; size_t size = *ret_len; size_t nbytes = 0; int rc = 0; (void)chain; /* Not used. */ if (control == IOBUFCTRL_UNDERFLOW) { assert (size); /* We need a buffer. */ if (a->eof_seen) { rc = -1; *ret_len = 0; } else if (a->use_readlimit) { nbytes = 0; if (!a->readlimit) { /* eof */ a->eof_seen = 1; rc = -1; } else { if (size > a->readlimit) size = a->readlimit; rc = es_read (f, buf, size, &nbytes); if (rc == -1) { /* error */ rc = gpg_error_from_syserror (); log_error ("%s: read error: %s\n", a->fname,strerror (errno)); } else if (!nbytes) { /* eof */ a->eof_seen = 1; rc = -1; } else a->readlimit -= nbytes; } *ret_len = nbytes; } else { nbytes = 0; rc = es_read (f, buf, size, &nbytes); if (rc == -1) { /* error */ rc = gpg_error_from_syserror (); log_error ("%s: read error: %s\n", a->fname, strerror (errno)); } else if (!nbytes) { /* eof */ a->eof_seen = 1; rc = -1; } *ret_len = nbytes; } } else if (control == IOBUFCTRL_FLUSH) { if (size) { byte *p = buf; size_t nwritten; nbytes = size; do { nwritten = 0; if (es_write (f, p, nbytes, &nwritten)) { rc = gpg_error_from_syserror (); log_error ("%s: write error: %s\n", a->fname, strerror (errno)); break; } p += nwritten; nbytes -= nwritten; } while (nbytes); nbytes = p - buf; } *ret_len = nbytes; } else if (control == IOBUFCTRL_INIT) { a->eof_seen = 0; a->no_cache = 0; } else if (control == IOBUFCTRL_DESC) { mem2str (buf, "estream_filter", *ret_len); } else if (control == IOBUFCTRL_FREE) { if (f != es_stdin && f != es_stdout) { if (DBG_IOBUF) log_debug ("%s: es_fclose %p\n", a->fname, f); if (!a->keep_open) es_fclose (f); } f = NULL; xfree (a); /* We can free our context now. */ } return rc; } #ifdef HAVE_W32_SYSTEM /* Because network sockets are special objects under Lose32 we have to use a dedicated filter for them. */ static int sock_filter (void *opaque, int control, iobuf_t chain, byte * buf, size_t * ret_len) { sock_filter_ctx_t *a = opaque; size_t size = *ret_len; size_t nbytes = 0; int rc = 0; (void)chain; if (control == IOBUFCTRL_UNDERFLOW) { assert (size); /* need a buffer */ if (a->eof_seen) { rc = -1; *ret_len = 0; } else { int nread; nread = recv (a->sock, buf, size, 0); if (nread == SOCKET_ERROR) { int ec = (int) WSAGetLastError (); rc = gpg_error_from_errno (ec); log_error ("socket read error: ec=%d\n", ec); } else if (!nread) { a->eof_seen = 1; rc = -1; } else { nbytes = nread; } *ret_len = nbytes; } } else if (control == IOBUFCTRL_FLUSH) { if (size) { byte *p = buf; int n; nbytes = size; do { n = send (a->sock, p, nbytes, 0); if (n == SOCKET_ERROR) { int ec = (int) WSAGetLastError (); rc = gpg_error_from_errno (ec); log_error ("socket write error: ec=%d\n", ec); break; } p += n; nbytes -= n; } while (nbytes); nbytes = p - buf; } *ret_len = nbytes; } else if (control == IOBUFCTRL_INIT) { a->eof_seen = 0; a->keep_open = 0; a->no_cache = 0; } else if (control == IOBUFCTRL_DESC) { mem2str (buf, "sock_filter", *ret_len); } else if (control == IOBUFCTRL_FREE) { if (!a->keep_open) closesocket (a->sock); xfree (a); /* we can free our context now */ } return rc; } #endif /*HAVE_W32_SYSTEM*/ /**************** * This is used to implement the block write mode. * Block reading is done on a byte by byte basis in readbyte(), * without a filter */ static int block_filter (void *opaque, int control, iobuf_t chain, byte * buffer, size_t * ret_len) { block_filter_ctx_t *a = opaque; char *buf = (char *)buffer; size_t size = *ret_len; int c, needed, rc = 0; char *p; if (control == IOBUFCTRL_UNDERFLOW) { size_t n = 0; p = buf; assert (size); /* need a buffer */ if (a->eof) /* don't read any further */ rc = -1; while (!rc && size) { if (!a->size) { /* get the length bytes */ if (a->partial == 2) { a->eof = 1; if (!n) rc = -1; break; } else if (a->partial) { /* These OpenPGP introduced huffman like encoded length * bytes are really a mess :-( */ if (a->first_c) { c = a->first_c; a->first_c = 0; } else if ((c = iobuf_get (chain)) == -1) { log_error ("block_filter: 1st length byte missing\n"); rc = GPG_ERR_BAD_DATA; break; } if (c < 192) { a->size = c; a->partial = 2; if (!a->size) { a->eof = 1; if (!n) rc = -1; break; } } else if (c < 224) { a->size = (c - 192) * 256; if ((c = iobuf_get (chain)) == -1) { log_error ("block_filter: 2nd length byte missing\n"); rc = GPG_ERR_BAD_DATA; break; } a->size += c + 192; a->partial = 2; if (!a->size) { a->eof = 1; if (!n) rc = -1; break; } } else if (c == 255) { size_t len = 0; int i; for (i = 0; i < 4; i++) if ((c = iobuf_get (chain)) == -1) break; else len = ((len << 8) | c); if (i < 4) { log_error ("block_filter: invalid 4 byte length\n"); rc = GPG_ERR_BAD_DATA; break; } a->size = len; a->partial = 2; if (!a->size) { a->eof = 1; if (!n) rc = -1; break; } } else { /* Next partial body length. */ a->size = 1 << (c & 0x1f); } /* log_debug("partial: ctx=%p c=%02x size=%u\n", a, c, a->size); */ } else BUG (); } while (!rc && size && a->size) { needed = size < a->size ? size : a->size; c = iobuf_read (chain, p, needed); if (c < needed) { if (c == -1) c = 0; log_error ("block_filter %p: read error (size=%lu,a->size=%lu)\n", a, (ulong) size + c, (ulong) a->size + c); rc = GPG_ERR_BAD_DATA; } else { size -= c; a->size -= c; p += c; n += c; } } } *ret_len = n; } else if (control == IOBUFCTRL_FLUSH) { if (a->partial) { /* the complicated openpgp scheme */ size_t blen, n, nbytes = size + a->buflen; assert (a->buflen <= OP_MIN_PARTIAL_CHUNK); if (nbytes < OP_MIN_PARTIAL_CHUNK) { /* not enough to write a partial block out; so we store it */ if (!a->buffer) a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK); memcpy (a->buffer + a->buflen, buf, size); a->buflen += size; } else { /* okay, we can write out something */ /* do this in a loop to use the most efficient block lengths */ p = buf; do { /* find the best matching block length - this is limited * by the size of the internal buffering */ for (blen = OP_MIN_PARTIAL_CHUNK * 2, c = OP_MIN_PARTIAL_CHUNK_2POW + 1; blen <= nbytes; blen *= 2, c++) ; blen /= 2; c--; /* write the partial length header */ assert (c <= 0x1f); /*;-) */ c |= 0xe0; iobuf_put (chain, c); if ((n = a->buflen)) { /* write stuff from the buffer */ assert (n == OP_MIN_PARTIAL_CHUNK); if (iobuf_write (chain, a->buffer, n)) rc = gpg_error_from_syserror (); a->buflen = 0; nbytes -= n; } if ((n = nbytes) > blen) n = blen; if (n && iobuf_write (chain, p, n)) rc = gpg_error_from_syserror (); p += n; nbytes -= n; } while (!rc && nbytes >= OP_MIN_PARTIAL_CHUNK); /* store the rest in the buffer */ if (!rc && nbytes) { assert (!a->buflen); assert (nbytes < OP_MIN_PARTIAL_CHUNK); if (!a->buffer) a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK); memcpy (a->buffer, p, nbytes); a->buflen = nbytes; } } } else BUG (); } else if (control == IOBUFCTRL_INIT) { if (DBG_IOBUF) log_debug ("init block_filter %p\n", a); if (a->partial) a->count = 0; else if (a->use == IOBUF_INPUT) a->count = a->size = 0; else a->count = a->size; /* force first length bytes */ a->eof = 0; a->buffer = NULL; a->buflen = 0; } else if (control == IOBUFCTRL_DESC) { mem2str (buf, "block_filter", *ret_len); } else if (control == IOBUFCTRL_FREE) { if (a->use == IOBUF_OUTPUT) { /* write the end markers */ if (a->partial) { u32 len; /* write out the remaining bytes without a partial header * the length of this header may be 0 - but if it is * the first block we are not allowed to use a partial header * and frankly we can't do so, because this length must be * a power of 2. This is _really_ complicated because we * have to check the possible length of a packet prior * to it's creation: a chain of filters becomes complicated * and we need a lot of code to handle compressed packets etc. * :-((((((( */ /* construct header */ len = a->buflen; /*log_debug("partial: remaining length=%u\n", len ); */ if (len < 192) rc = iobuf_put (chain, len); else if (len < 8384) { if (!(rc = iobuf_put (chain, ((len - 192) / 256) + 192))) rc = iobuf_put (chain, ((len - 192) % 256)); } else { /* use a 4 byte header */ if (!(rc = iobuf_put (chain, 0xff))) if (!(rc = iobuf_put (chain, (len >> 24) & 0xff))) if (!(rc = iobuf_put (chain, (len >> 16) & 0xff))) if (!(rc = iobuf_put (chain, (len >> 8) & 0xff))) rc = iobuf_put (chain, len & 0xff); } if (!rc && len) rc = iobuf_write (chain, a->buffer, len); if (rc) { log_error ("block_filter: write error: %s\n", strerror (errno)); rc = gpg_error_from_syserror (); } xfree (a->buffer); a->buffer = NULL; a->buflen = 0; } else BUG (); } else if (a->size) { log_error ("block_filter: pending bytes!\n"); } if (DBG_IOBUF) log_debug ("free block_filter %p\n", a); xfree (a); /* we can free our context now */ } return rc; } /* Change the default size for all IOBUFs to KILOBYTE. This needs to * be called before any iobufs are used and can only be used once. * Returns the current value. Using 0 has no effect except for * returning the current value. */ unsigned int iobuf_set_buffer_size (unsigned int kilobyte) { static int used; if (!used && kilobyte) { if (kilobyte < 4) kilobyte = 4; else if (kilobyte > 16*1024) kilobyte = 16*1024; iobuf_buffer_size = kilobyte * 1024; used = 1; } return iobuf_buffer_size / 1024; } #define MAX_IOBUF_DESC 32 /* * Fill the buffer by the description of iobuf A. * The buffer size should be MAX_IOBUF_DESC (or larger). * Returns BUF as (const char *). */ static const char * iobuf_desc (iobuf_t a, byte *buf) { size_t len = MAX_IOBUF_DESC; if (! a || ! a->filter) memcpy (buf, "?", 2); else a->filter (a->filter_ov, IOBUFCTRL_DESC, NULL, buf, &len); return buf; } static void print_chain (iobuf_t a) { if (!DBG_IOBUF) return; for (; a; a = a->chain) { byte desc[MAX_IOBUF_DESC]; log_debug ("iobuf chain: %d.%d '%s' filter_eof=%d start=%d len=%d\n", a->no, a->subno, iobuf_desc (a, desc), a->filter_eof, (int) a->d.start, (int) a->d.len); } } int iobuf_print_chain (iobuf_t a) { print_chain (a); return 0; } iobuf_t iobuf_alloc (int use, size_t bufsize) { iobuf_t a; static int number = 0; assert (use == IOBUF_INPUT || use == IOBUF_INPUT_TEMP || use == IOBUF_OUTPUT || use == IOBUF_OUTPUT_TEMP); if (bufsize == 0) { log_bug ("iobuf_alloc() passed a bufsize of 0!\n"); bufsize = iobuf_buffer_size; } a = xcalloc (1, sizeof *a); a->use = use; a->d.buf = xmalloc (bufsize); a->d.size = bufsize; a->e_d.buf = NULL; a->e_d.len = 0; a->e_d.used = 0; a->e_d.preferred = 0; a->no = ++number; a->subno = 0; a->real_fname = NULL; return a; } int iobuf_close (iobuf_t a) { iobuf_t a_chain; size_t dummy_len = 0; int rc = 0; for (; a; a = a_chain) { byte desc[MAX_IOBUF_DESC]; int rc2 = 0; a_chain = a->chain; if (a->use == IOBUF_OUTPUT && (rc = filter_flush (a))) log_error ("filter_flush failed on close: %s\n", gpg_strerror (rc)); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: close '%s'\n", a->no, a->subno, iobuf_desc (a, desc)); if (a->filter && (rc2 = a->filter (a->filter_ov, IOBUFCTRL_FREE, a->chain, NULL, &dummy_len))) log_error ("IOBUFCTRL_FREE failed on close: %s\n", gpg_strerror (rc)); if (! rc && rc2) /* Whoops! An error occurred. Save it in RC if we haven't already recorded an error. */ rc = rc2; xfree (a->real_fname); if (a->d.buf) { memset (a->d.buf, 0, a->d.size); /* erase the buffer */ xfree (a->d.buf); } xfree (a); } return rc; } int iobuf_cancel (iobuf_t a) { const char *s; iobuf_t a2; int rc; #if defined(HAVE_W32_SYSTEM) || defined(__riscos__) char *remove_name = NULL; #endif if (a && a->use == IOBUF_OUTPUT) { s = iobuf_get_real_fname (a); if (s && *s) { #if defined(HAVE_W32_SYSTEM) || defined(__riscos__) remove_name = xstrdup (s); #else remove (s); #endif } } /* send a cancel message to all filters */ for (a2 = a; a2; a2 = a2->chain) { size_t dummy = 0; if (a2->filter) a2->filter (a2->filter_ov, IOBUFCTRL_CANCEL, a2->chain, NULL, &dummy); } rc = iobuf_close (a); #if defined(HAVE_W32_SYSTEM) || defined(__riscos__) if (remove_name) { /* Argg, MSDOS does not allow removing open files. So * we have to do it here */ -#ifdef HAVE_W32CE_SYSTEM - wchar_t *wtmp = utf8_to_wchar (remove_name); - if (wtmp) - DeleteFile (wtmp); - xfree (wtmp); -#else remove (remove_name); -#endif + xfree (remove_name); } #endif return rc; } iobuf_t iobuf_temp (void) { return iobuf_alloc (IOBUF_OUTPUT_TEMP, iobuf_buffer_size); } iobuf_t iobuf_temp_with_content (const char *buffer, size_t length) { iobuf_t a; int i; a = iobuf_alloc (IOBUF_INPUT_TEMP, length); assert (length == a->d.size); /* memcpy (a->d.buf, buffer, length); */ for (i=0; i < length; i++) a->d.buf[i] = buffer[i]; a->d.len = length; return a; } int iobuf_is_pipe_filename (const char *fname) { if (!fname || (*fname=='-' && !fname[1]) ) return 1; return check_special_filename (fname, 0, 1) != -1; } static iobuf_t do_open (const char *fname, int special_filenames, int use, const char *opentype, int mode700) { iobuf_t a; gnupg_fd_t fp; file_filter_ctx_t *fcx; size_t len = 0; int print_only = 0; int fd; byte desc[MAX_IOBUF_DESC]; assert (use == IOBUF_INPUT || use == IOBUF_OUTPUT); if (special_filenames /* NULL or '-'. */ && (!fname || (*fname == '-' && !fname[1]))) { if (use == IOBUF_INPUT) { fp = FD_FOR_STDIN; fname = "[stdin]"; } else { fp = FD_FOR_STDOUT; fname = "[stdout]"; } print_only = 1; } else if (!fname) return NULL; else if (special_filenames && (fd = check_special_filename (fname, 0, 1)) != -1) return iobuf_fdopen (translate_file_handle (fd, use == IOBUF_INPUT ? 0 : 1), opentype); else { if (use == IOBUF_INPUT) fp = fd_cache_open (fname, opentype); else fp = direct_open (fname, opentype, mode700); if (fp == GNUPG_INVALID_FD) return NULL; } a = iobuf_alloc (use, iobuf_buffer_size); fcx = xmalloc (sizeof *fcx + strlen (fname)); fcx->fp = fp; fcx->print_only_name = print_only; strcpy (fcx->fname, fname); if (!print_only) a->real_fname = xstrdup (fname); a->filter = file_filter; a->filter_ov = fcx; file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: open '%s' desc=%s fd=%d\n", a->no, a->subno, fname, iobuf_desc (a, desc), FD2INT (fcx->fp)); return a; } iobuf_t iobuf_open (const char *fname) { return do_open (fname, 1, IOBUF_INPUT, "rb", 0); } iobuf_t iobuf_create (const char *fname, int mode700) { return do_open (fname, 1, IOBUF_OUTPUT, "wb", mode700); } iobuf_t iobuf_openrw (const char *fname) { return do_open (fname, 0, IOBUF_OUTPUT, "r+b", 0); } static iobuf_t do_iobuf_fdopen (int fd, const char *mode, int keep_open) { iobuf_t a; gnupg_fd_t fp; file_filter_ctx_t *fcx; size_t len = 0; fp = INT2FD (fd); a = iobuf_alloc (strchr (mode, 'w') ? IOBUF_OUTPUT : IOBUF_INPUT, iobuf_buffer_size); fcx = xmalloc (sizeof *fcx + 20); fcx->fp = fp; fcx->print_only_name = 1; fcx->keep_open = keep_open; sprintf (fcx->fname, "[fd %d]", fd); a->filter = file_filter; a->filter_ov = fcx; file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: fdopen%s '%s'\n", a->no, a->subno, keep_open? "_nc":"", fcx->fname); iobuf_ioctl (a, IOBUF_IOCTL_NO_CACHE, 1, NULL); return a; } iobuf_t iobuf_fdopen (int fd, const char *mode) { return do_iobuf_fdopen (fd, mode, 0); } iobuf_t iobuf_fdopen_nc (int fd, const char *mode) { return do_iobuf_fdopen (fd, mode, 1); } iobuf_t iobuf_esopen (estream_t estream, const char *mode, int keep_open, size_t readlimit) { iobuf_t a; file_es_filter_ctx_t *fcx; size_t len = 0; a = iobuf_alloc (strchr (mode, 'w') ? IOBUF_OUTPUT : IOBUF_INPUT, iobuf_buffer_size); fcx = xtrymalloc (sizeof *fcx + 30); fcx->fp = estream; fcx->print_only_name = 1; fcx->keep_open = keep_open; fcx->readlimit = readlimit; fcx->use_readlimit = !!readlimit; snprintf (fcx->fname, 30, "[fd %p]", estream); a->filter = file_es_filter; a->filter_ov = fcx; file_es_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: esopen%s '%s'\n", a->no, a->subno, keep_open? "_nc":"", fcx->fname); return a; } iobuf_t iobuf_sockopen (int fd, const char *mode) { iobuf_t a; #ifdef HAVE_W32_SYSTEM sock_filter_ctx_t *scx; size_t len; a = iobuf_alloc (strchr (mode, 'w') ? IOBUF_OUTPUT : IOBUF_INPUT, iobuf_buffer_size); scx = xmalloc (sizeof *scx + 25); scx->sock = fd; scx->print_only_name = 1; sprintf (scx->fname, "[sock %d]", fd); a->filter = sock_filter; a->filter_ov = scx; sock_filter (scx, IOBUFCTRL_INIT, NULL, NULL, &len); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: sockopen '%s'\n", a->no, a->subno, scx->fname); iobuf_ioctl (a, IOBUF_IOCTL_NO_CACHE, 1, NULL); #else a = iobuf_fdopen (fd, mode); #endif return a; } int iobuf_ioctl (iobuf_t a, iobuf_ioctl_t cmd, int intval, void *ptrval) { byte desc[MAX_IOBUF_DESC]; if (cmd == IOBUF_IOCTL_KEEP_OPEN) { /* Keep system filepointer/descriptor open. This was used in the past by http.c; this ioctl is not directly used anymore. */ if (DBG_IOBUF) log_debug ("iobuf-%d.%d: ioctl '%s' keep_open=%d\n", a ? a->no : -1, a ? a->subno : -1, iobuf_desc (a, desc), intval); for (; a; a = a->chain) if (!a->chain && a->filter == file_filter) { file_filter_ctx_t *b = a->filter_ov; b->keep_open = intval; return 0; } #ifdef HAVE_W32_SYSTEM else if (!a->chain && a->filter == sock_filter) { sock_filter_ctx_t *b = a->filter_ov; b->keep_open = intval; return 0; } #endif } else if (cmd == IOBUF_IOCTL_INVALIDATE_CACHE) { if (DBG_IOBUF) log_debug ("iobuf-*.*: ioctl '%s' invalidate\n", ptrval ? (char *) ptrval : "?"); if (!a && !intval && ptrval) { if (fd_cache_invalidate (ptrval)) return -1; return 0; } } else if (cmd == IOBUF_IOCTL_NO_CACHE) { if (DBG_IOBUF) log_debug ("iobuf-%d.%d: ioctl '%s' no_cache=%d\n", a ? a->no : -1, a ? a->subno : -1, iobuf_desc (a, desc), intval); for (; a; a = a->chain) if (!a->chain && a->filter == file_filter) { file_filter_ctx_t *b = a->filter_ov; b->no_cache = intval; return 0; } #ifdef HAVE_W32_SYSTEM else if (!a->chain && a->filter == sock_filter) { sock_filter_ctx_t *b = a->filter_ov; b->no_cache = intval; return 0; } #endif } else if (cmd == IOBUF_IOCTL_FSYNC) { /* Do a fsync on the open fd and return any errors to the caller of iobuf_ioctl. Note that we work on a file name here. */ if (DBG_IOBUF) log_debug ("iobuf-*.*: ioctl '%s' fsync\n", ptrval? (const char*)ptrval:""); if (!a && !intval && ptrval) { return fd_cache_synchronize (ptrval); } } return -1; } /**************** * Register an i/o filter. */ int iobuf_push_filter (iobuf_t a, int (*f) (void *opaque, int control, iobuf_t chain, byte * buf, size_t * len), void *ov) { return iobuf_push_filter2 (a, f, ov, 0); } int iobuf_push_filter2 (iobuf_t a, int (*f) (void *opaque, int control, iobuf_t chain, byte * buf, size_t * len), void *ov, int rel_ov) { iobuf_t b; size_t dummy_len = 0; int rc = 0; if (a->use == IOBUF_OUTPUT && (rc = filter_flush (a))) return rc; if (a->subno >= MAX_NESTING_FILTER) { log_error ("i/o filter too deeply nested - corrupted data?\n"); return GPG_ERR_BAD_DATA; } /* We want to create a new filter and put it in front of A. A simple implementation would do: b = iobuf_alloc (...); b->chain = a; return a; This is a bit problematic: A is the head of the pipeline and there are potentially many pointers to it. Requiring the caller to update all of these pointers is a burden. An alternative implementation would add a level of indirection. For instance, we could use a pipeline object, which contains a pointer to the first filter in the pipeline. This is not what we do either. Instead, we allocate a new buffer (B) and copy the first filter's state into that and use the initial buffer (A) for the new filter. One limitation of this approach is that it is not practical to maintain a pointer to a specific filter's state. Before: A | v 0x100 0x200 +----------+ +----------+ | filter x |--------->| filter y |---->.... +----------+ +----------+ After: B | v 0x300 +----------+ A | filter x | | +----------+ v 0x100 ^ v 0x200 +----------+ +----------+ | filter w | | filter y |---->.... +----------+ +----------+ Note: filter x's address changed from 0x100 to 0x300, but A still points to the head of the pipeline. */ b = xmalloc (sizeof *b); memcpy (b, a, sizeof *b); /* fixme: it is stupid to keep a copy of the name at every level * but we need the name somewhere because the name known by file_filter * may have been released when we need the name of the file */ b->real_fname = a->real_fname ? xstrdup (a->real_fname) : NULL; /* remove the filter stuff from the new stream */ a->filter = NULL; a->filter_ov = NULL; a->filter_ov_owner = 0; a->filter_eof = 0; if (a->use == IOBUF_OUTPUT_TEMP) /* A TEMP filter buffers any data sent to it; it does not forward any data down the pipeline. If we add a new filter to the pipeline, it shouldn't also buffer data. It should send it downstream to be buffered. Thus, the correct type for a filter added in front of an IOBUF_OUTPUT_TEMP filter is IOBUF_OUPUT, not IOBUF_OUTPUT_TEMP. */ { a->use = IOBUF_OUTPUT; /* When pipeline is written to, the temp buffer's size is increased accordingly. We don't need to allocate a 10 MB buffer for a non-terminal filter. Just use the default size. */ a->d.size = iobuf_buffer_size; } else if (a->use == IOBUF_INPUT_TEMP) /* Same idea as above. */ { a->use = IOBUF_INPUT; a->d.size = iobuf_buffer_size; } /* The new filter (A) gets a new buffer. If the pipeline is an output or temp pipeline, then giving the buffer to the new filter means that data that was written before the filter was pushed gets sent to the filter. That's clearly wrong. If the pipeline is an input pipeline, then giving the buffer to the new filter (A) means that data that has read from (B), but not yet read from the pipeline won't be processed by the new filter (A)! That's certainly not what we want. */ a->d.buf = xmalloc (a->d.size); a->d.len = 0; a->d.start = 0; /* disable nlimit for the new stream */ a->ntotal = b->ntotal + b->nbytes; a->nlimit = a->nbytes = 0; a->nofast = 0; /* make a link from the new stream to the original stream */ a->chain = b; /* setup the function on the new stream */ a->filter = f; a->filter_ov = ov; a->filter_ov_owner = rel_ov; a->subno = b->subno + 1; if (DBG_IOBUF) { byte desc[MAX_IOBUF_DESC]; log_debug ("iobuf-%d.%d: push '%s'\n", a->no, a->subno, iobuf_desc (a, desc)); print_chain (a); } /* now we can initialize the new function if we have one */ if (a->filter && (rc = a->filter (a->filter_ov, IOBUFCTRL_INIT, a->chain, NULL, &dummy_len))) log_error ("IOBUFCTRL_INIT failed: %s\n", gpg_strerror (rc)); return rc; } /**************** * Remove an i/o filter. */ int iobuf_pop_filter (iobuf_t a, int (*f) (void *opaque, int control, iobuf_t chain, byte * buf, size_t * len), void *ov) { iobuf_t b; size_t dummy_len = 0; int rc = 0; byte desc[MAX_IOBUF_DESC]; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: pop '%s'\n", a->no, a->subno, iobuf_desc (a, desc)); if (a->use == IOBUF_INPUT_TEMP || a->use == IOBUF_OUTPUT_TEMP) { /* This should be the last filter in the pipeline. */ assert (! a->chain); return 0; } if (!a->filter) { /* this is simple */ b = a->chain; assert (b); xfree (a->d.buf); xfree (a->real_fname); memcpy (a, b, sizeof *a); xfree (b); return 0; } for (b = a; b; b = b->chain) if (b->filter == f && (!ov || b->filter_ov == ov)) break; if (!b) log_bug ("iobuf_pop_filter(): filter function not found\n"); /* flush this stream if it is an output stream */ if (a->use == IOBUF_OUTPUT && (rc = filter_flush (b))) { log_error ("filter_flush failed in iobuf_pop_filter: %s\n", gpg_strerror (rc)); return rc; } /* and tell the filter to free it self */ if (b->filter && (rc = b->filter (b->filter_ov, IOBUFCTRL_FREE, b->chain, NULL, &dummy_len))) { log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc)); return rc; } if (b->filter_ov && b->filter_ov_owner) { xfree (b->filter_ov); b->filter_ov = NULL; } /* and see how to remove it */ if (a == b && !b->chain) log_bug ("can't remove the last filter from the chain\n"); else if (a == b) { /* remove the first iobuf from the chain */ /* everything from b is copied to a. This is save because * a flush has been done on the to be removed entry */ b = a->chain; xfree (a->d.buf); xfree (a->real_fname); memcpy (a, b, sizeof *a); xfree (b); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: popped filter\n", a->no, a->subno); } else if (!b->chain) { /* remove the last iobuf from the chain */ log_bug ("Ohh jeee, trying to remove a head filter\n"); } else { /* remove an intermediate iobuf from the chain */ log_bug ("Ohh jeee, trying to remove an intermediate filter\n"); } return rc; } /**************** * read underflow: read at least one byte into the buffer and return * the first byte or -1 on EOF. */ static int underflow (iobuf_t a, int clear_pending_eof) { return underflow_target (a, clear_pending_eof, 1); } /**************** * read underflow: read TARGET bytes into the buffer and return * the first byte or -1 on EOF. */ static int underflow_target (iobuf_t a, int clear_pending_eof, size_t target) { size_t len; int rc; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: underflow: buffer size: %d; still buffered: %d => space for %d bytes\n", a->no, a->subno, (int) a->d.size, (int) (a->d.len - a->d.start), (int) (a->d.size - (a->d.len - a->d.start))); if (a->use == IOBUF_INPUT_TEMP) /* By definition, there isn't more data to read into the buffer. */ return -1; assert (a->use == IOBUF_INPUT); a->e_d.used = 0; /* If there is still some buffered data, then move it to the start of the buffer and try to fill the end of the buffer. (This is useful if we are called from iobuf_peek().) */ assert (a->d.start <= a->d.len); a->d.len -= a->d.start; if (a->d.len) memmove (a->d.buf, &a->d.buf[a->d.start], a->d.len); a->d.start = 0; if (a->d.len < target && a->filter_eof) /* The last time we tried to read from this filter, we got an EOF. We couldn't return the EOF, because there was buffered data. Since there is no longer any buffered data, return the error. */ { if (DBG_IOBUF) log_debug ("iobuf-%d.%d: underflow: eof (pending eof)\n", a->no, a->subno); if (! clear_pending_eof) return -1; if (a->chain) /* A filter follows this one. Free this filter. */ { iobuf_t b = a->chain; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: filter popped (pending EOF returned)\n", a->no, a->subno); xfree (a->d.buf); xfree (a->real_fname); memcpy (a, b, sizeof *a); xfree (b); print_chain (a); } else a->filter_eof = 0; /* for the top level filter */ return -1; /* return one(!) EOF */ } if (a->d.len == 0 && a->error) /* The last time we tried to read from this filter, we got an error. We couldn't return the error, because there was buffered data. Since there is no longer any buffered data, return the error. */ { if (DBG_IOBUF) log_debug ("iobuf-%d.%d: pending error (%s) returned\n", a->no, a->subno, gpg_strerror (a->error)); return -1; } if (a->filter && ! a->filter_eof && ! a->error) /* We have a filter function and the last time we tried to read we didn't get an EOF or an error. Try to fill the buffer. */ { /* Be careful to account for any buffered data. */ len = a->d.size - a->d.len; if (a->e_d.preferred && a->d.len < IOBUF_ZEROCOPY_THRESHOLD_SIZE && (IOBUF_ZEROCOPY_THRESHOLD_SIZE - a->d.len) < len) { if (DBG_IOBUF) log_debug ("iobuf-%d.%d: limit buffering as external drain is " "preferred\n", a->no, a->subno); len = IOBUF_ZEROCOPY_THRESHOLD_SIZE - a->d.len; } if (len == 0) /* There is no space for more data. Don't bother calling A->FILTER. */ rc = 0; else { /* If no buffered data and drain buffer has been setup, and drain * buffer is largish, read data directly to drain buffer. */ if (a->d.len == 0 && a->e_d.buf && a->e_d.len >= IOBUF_ZEROCOPY_THRESHOLD_SIZE) { len = a->e_d.len; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: underflow: A->FILTER (%lu bytes, to external drain)\n", a->no, a->subno, (ulong)len); rc = a->filter (a->filter_ov, IOBUFCTRL_UNDERFLOW, a->chain, a->e_d.buf, &len); a->e_d.used = len; len = 0; } else { if (DBG_IOBUF) log_debug ("iobuf-%d.%d: underflow: A->FILTER (%lu bytes)\n", a->no, a->subno, (ulong)len); rc = a->filter (a->filter_ov, IOBUFCTRL_UNDERFLOW, a->chain, &a->d.buf[a->d.len], &len); } } a->d.len += len; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: A->FILTER() returned rc=%d (%s), read %lu bytes%s\n", a->no, a->subno, rc, rc == 0 ? "ok" : rc == -1 ? "EOF" : gpg_strerror (rc), (ulong)(a->e_d.used ? a->e_d.used : len), a->e_d.used ? " (to external buffer)" : ""); /* if( a->no == 1 ) */ /* log_hexdump (" data:", a->d.buf, len); */ if (rc == -1) /* EOF. */ { size_t dummy_len = 0; /* Tell the filter to free itself */ if ((rc = a->filter (a->filter_ov, IOBUFCTRL_FREE, a->chain, NULL, &dummy_len))) log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc)); /* Free everything except for the internal buffer. */ if (a->filter_ov && a->filter_ov_owner) xfree (a->filter_ov); a->filter_ov = NULL; a->filter = NULL; a->filter_eof = 1; if (clear_pending_eof && a->d.len == 0 && a->e_d.used == 0 && a->chain) /* We don't need to keep this filter around at all: - we got an EOF - we have no buffered data - a filter follows this one. Unlink this filter. */ { iobuf_t b = a->chain; if (DBG_IOBUF) log_debug ("iobuf-%d.%d: pop in underflow (nothing buffered, got EOF)\n", a->no, a->subno); xfree (a->d.buf); xfree (a->real_fname); memcpy (a, b, sizeof *a); xfree (b); print_chain (a); return -1; } else if (a->d.len == 0 && a->e_d.used == 0) /* We can't unlink this filter (it is the only one in the pipeline), but we can immediately return EOF. */ return -1; } else if (rc) /* Record the error. */ { a->error = rc; if (a->d.len == 0 && a->e_d.used == 0) /* There is no buffered data. Immediately return EOF. */ return -1; } } assert (a->d.start <= a->d.len); if (a->e_d.used > 0) return 0; if (a->d.start < a->d.len) return a->d.buf[a->d.start++]; /* EOF. */ return -1; } static int filter_flush (iobuf_t a) { int external_used = 0; byte *src_buf; size_t src_len; size_t len; int rc; a->e_d.used = 0; if (a->use == IOBUF_OUTPUT_TEMP) { /* increase the temp buffer */ size_t newsize = a->d.size + iobuf_buffer_size; if (DBG_IOBUF) log_debug ("increasing temp iobuf from %lu to %lu\n", (ulong) a->d.size, (ulong) newsize); a->d.buf = xrealloc (a->d.buf, newsize); a->d.size = newsize; return 0; } else if (a->use != IOBUF_OUTPUT) log_bug ("flush on non-output iobuf\n"); else if (!a->filter) log_bug ("filter_flush: no filter\n"); if (a->d.len == 0 && a->e_d.buf && a->e_d.len > 0) { src_buf = a->e_d.buf; src_len = a->e_d.len; external_used = 1; } else { src_buf = a->d.buf; src_len = a->d.len; external_used = 0; } len = src_len; rc = a->filter (a->filter_ov, IOBUFCTRL_FLUSH, a->chain, src_buf, &len); if (!rc && len != src_len) { log_info ("filter_flush did not write all!\n"); rc = GPG_ERR_INTERNAL; } else if (rc) a->error = rc; a->d.len = 0; if (external_used) a->e_d.used = len; return rc; } int iobuf_readbyte (iobuf_t a) { int c; if (a->use == IOBUF_OUTPUT || a->use == IOBUF_OUTPUT_TEMP) { log_bug ("iobuf_readbyte called on a non-INPUT pipeline!\n"); return -1; } assert (a->d.start <= a->d.len); if (a->nlimit && a->nbytes >= a->nlimit) return -1; /* forced EOF */ if (a->d.start < a->d.len) { c = a->d.buf[a->d.start++]; } else if ((c = underflow (a, 1)) == -1) return -1; /* EOF */ assert (a->d.start <= a->d.len); /* Note: if underflow doesn't return EOF, then it returns the first byte that was read and advances a->d.start appropriately. */ a->nbytes++; return c; } int iobuf_read (iobuf_t a, void *buffer, unsigned int buflen) { unsigned char *buf = (unsigned char *)buffer; int c, n; if (a->use == IOBUF_OUTPUT || a->use == IOBUF_OUTPUT_TEMP) { log_bug ("iobuf_read called on a non-INPUT pipeline!\n"); return -1; } if (a->nlimit) { /* Handle special cases. */ for (n = 0; n < buflen; n++) { if ((c = iobuf_readbyte (a)) == -1) { if (!n) return -1; /* eof */ break; } if (buf) { *buf = c; buf++; } } return n; } a->e_d.buf = NULL; a->e_d.len = 0; /* Hint for how full to fill iobuf internal drain buffer. */ a->e_d.preferred = (a->use != IOBUF_INPUT_TEMP) && (buf && buflen >= IOBUF_ZEROCOPY_THRESHOLD_SIZE); n = 0; do { if (n < buflen && a->d.start < a->d.len) /* Drain the buffer. */ { unsigned size = a->d.len - a->d.start; if (size > buflen - n) size = buflen - n; if (buf) memcpy (buf, a->d.buf + a->d.start, size); n += size; a->d.start += size; if (buf) buf += size; } if (n < buflen) /* Draining the internal buffer didn't fill BUFFER. Call underflow to read more data into the filter's internal buffer. */ { if (a->use != IOBUF_INPUT_TEMP && buf && n < buflen) { /* Setup external drain buffer for faster moving of data * (avoid memcpy). */ a->e_d.buf = buf; a->e_d.len = (buflen - n) / IOBUF_ZEROCOPY_THRESHOLD_SIZE * IOBUF_ZEROCOPY_THRESHOLD_SIZE; if (a->e_d.len == 0) a->e_d.buf = NULL; if (a->e_d.buf && DBG_IOBUF) log_debug ("iobuf-%d.%d: reading to external buffer, %lu bytes\n", a->no, a->subno, (ulong)a->e_d.len); } if ((c = underflow (a, 1)) == -1) /* EOF. If we managed to read something, don't return EOF now. */ { a->e_d.buf = NULL; a->e_d.len = 0; a->nbytes += n; return n ? n : -1 /*EOF*/; } if (a->e_d.buf && a->e_d.used > 0) { /* Drain buffer was used, 'c' only contains return code * 0 or -1. */ n += a->e_d.used; buf += a->e_d.used; } else { if (buf) *buf++ = c; n++; } a->e_d.buf = NULL; a->e_d.len = 0; } } while (n < buflen); a->nbytes += n; return n; } int iobuf_peek (iobuf_t a, byte * buf, unsigned buflen) { int n = 0; assert (buflen > 0); assert (a->use == IOBUF_INPUT || a->use == IOBUF_INPUT_TEMP); if (buflen > a->d.size) /* We can't peek more than we can buffer. */ buflen = a->d.size; /* Try to fill the internal buffer with enough data to satisfy the request. */ while (buflen > a->d.len - a->d.start) { if (underflow_target (a, 0, buflen) == -1) /* EOF. We can't read any more. */ break; /* Underflow consumes the first character (it's the return value). unget() it by resetting the "file position". */ assert (a->d.start == 1); a->d.start = 0; } n = a->d.len - a->d.start; if (n > buflen) n = buflen; if (n == 0) /* EOF. */ return -1; memcpy (buf, &a->d.buf[a->d.start], n); return n; } int iobuf_writebyte (iobuf_t a, unsigned int c) { int rc; if (a->use == IOBUF_INPUT || a->use == IOBUF_INPUT_TEMP) { log_bug ("iobuf_writebyte called on an input pipeline!\n"); return -1; } if (a->d.len == a->d.size) if ((rc=filter_flush (a))) return rc; assert (a->d.len < a->d.size); a->d.buf[a->d.len++] = c; return 0; } int iobuf_write (iobuf_t a, const void *buffer, unsigned int buflen) { const unsigned char *buf = (const unsigned char *)buffer; int rc; if (a->use == IOBUF_INPUT || a->use == IOBUF_INPUT_TEMP) { log_bug ("iobuf_write called on an input pipeline!\n"); return -1; } a->e_d.buf = NULL; a->e_d.len = 0; /* Hint for how full to fill iobuf internal drain buffer. */ a->e_d.preferred = (a->use != IOBUF_OUTPUT_TEMP) && (buflen >= IOBUF_ZEROCOPY_THRESHOLD_SIZE); do { if ((a->use != IOBUF_OUTPUT_TEMP) && a->d.len == 0 && buflen >= IOBUF_ZEROCOPY_THRESHOLD_SIZE) { /* Setup external drain buffer for faster moving of data * (avoid memcpy). */ a->e_d.buf = (byte *)buf; a->e_d.len = buflen / IOBUF_ZEROCOPY_THRESHOLD_SIZE * IOBUF_ZEROCOPY_THRESHOLD_SIZE; if (a->e_d.len == 0) a->e_d.buf = NULL; if (a->e_d.buf && DBG_IOBUF) log_debug ("iobuf-%d.%d: writing from external buffer, %lu bytes\n", a->no, a->subno, (ulong)a->e_d.len); } if (a->e_d.buf == NULL && buflen && a->d.len < a->d.size) { unsigned size; if (a->e_d.preferred && a->d.len < IOBUF_ZEROCOPY_THRESHOLD_SIZE) size = IOBUF_ZEROCOPY_THRESHOLD_SIZE - a->d.len; else size = a->d.size - a->d.len; if (size > buflen) size = buflen; memcpy (a->d.buf + a->d.len, buf, size); buflen -= size; buf += size; a->d.len += size; } if (buflen) { rc = filter_flush (a); if (rc) { a->e_d.buf = NULL; a->e_d.len = 0; return rc; } } if (a->e_d.buf && a->e_d.used > 0) { buf += a->e_d.used; buflen -= a->e_d.used; } a->e_d.buf = NULL; a->e_d.len = 0; } while (buflen); return 0; } int iobuf_writestr (iobuf_t a, const char *buf) { if (a->use == IOBUF_INPUT || a->use == IOBUF_INPUT_TEMP) { log_bug ("iobuf_writestr called on an input pipeline!\n"); return -1; } return iobuf_write (a, buf, strlen (buf)); } int iobuf_write_temp (iobuf_t dest, iobuf_t source) { assert (source->use == IOBUF_OUTPUT || source->use == IOBUF_OUTPUT_TEMP); assert (dest->use == IOBUF_OUTPUT || dest->use == IOBUF_OUTPUT_TEMP); iobuf_flush_temp (source); return iobuf_write (dest, source->d.buf, source->d.len); } size_t iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen) { byte desc[MAX_IOBUF_DESC]; size_t n; while (1) { int rc = filter_flush (a); if (rc) log_bug ("Flushing iobuf %d.%d (%s) from iobuf_temp_to_buffer failed. Ignoring.\n", a->no, a->subno, iobuf_desc (a, desc)); if (! a->chain) break; a = a->chain; } n = a->d.len; if (n > buflen) n = buflen; memcpy (buffer, a->d.buf, n); return n; } /* Copies the data from the input iobuf SOURCE to the output iobuf DEST until either an error is encountered or EOF is reached. Returns the number of bytes copies. */ size_t iobuf_copy (iobuf_t dest, iobuf_t source) { char *temp; size_t temp_size; size_t nread; size_t nwrote = 0; size_t max_read = 0; int err; assert (source->use == IOBUF_INPUT || source->use == IOBUF_INPUT_TEMP); assert (dest->use == IOBUF_OUTPUT || source->use == IOBUF_OUTPUT_TEMP); if (iobuf_error (dest)) return -1; /* Use iobuf buffer size for temporary buffer. */ temp_size = iobuf_set_buffer_size(0) * 1024; temp = xmalloc (temp_size); while (1) { nread = iobuf_read (source, temp, temp_size); if (nread == -1) /* EOF. */ break; if (nread > max_read) max_read = nread; err = iobuf_write (dest, temp, nread); if (err) break; nwrote += nread; } /* Burn the buffer. */ if (max_read) wipememory (temp, max_read); xfree (temp); return nwrote; } void iobuf_flush_temp (iobuf_t temp) { if (temp->use == IOBUF_INPUT || temp->use == IOBUF_INPUT_TEMP) log_bug ("iobuf_flush_temp called on an input pipeline!\n"); while (temp->chain) iobuf_pop_filter (temp, temp->filter, NULL); } void iobuf_set_limit (iobuf_t a, off_t nlimit) { if (nlimit) a->nofast = 1; else a->nofast = 0; a->nlimit = nlimit; a->ntotal += a->nbytes; a->nbytes = 0; } off_t iobuf_get_filelength (iobuf_t a, int *overflow) { if (overflow) *overflow = 0; /* Hmmm: file_filter may have already been removed */ for ( ; a->chain; a = a->chain ) ; if (a->filter != file_filter) return 0; { file_filter_ctx_t *b = a->filter_ov; gnupg_fd_t fp = b->fp; #if defined(HAVE_W32_SYSTEM) ulong size; static int (* __stdcall get_file_size_ex) (void *handle, LARGE_INTEGER *r_size); static int get_file_size_ex_initialized; if (!get_file_size_ex_initialized) { void *handle; handle = dlopen ("kernel32.dll", RTLD_LAZY); if (handle) { get_file_size_ex = dlsym (handle, "GetFileSizeEx"); if (!get_file_size_ex) dlclose (handle); } get_file_size_ex_initialized = 1; } if (get_file_size_ex) { /* This is a newer system with GetFileSizeEx; we use this then because it seem that GetFileSize won't return a proper error in case a file is larger than 4GB. */ LARGE_INTEGER exsize; if (get_file_size_ex (fp, &exsize)) { if (!exsize.u.HighPart) return exsize.u.LowPart; if (overflow) *overflow = 1; return 0; } } else { if ((size=GetFileSize (fp, NULL)) != 0xffffffff) return size; } log_error ("GetFileSize for handle %p failed: %s\n", fp, w32_strerror (-1)); #else /*!HAVE_W32_SYSTEM*/ { struct stat st; if ( !fstat (FD2INT (fp), &st) ) return st.st_size; log_error("fstat() failed: %s\n", strerror(errno) ); } #endif /*!HAVE_W32_SYSTEM*/ } return 0; } int iobuf_get_fd (iobuf_t a) { for (; a->chain; a = a->chain) ; if (a->filter != file_filter) return -1; { file_filter_ctx_t *b = a->filter_ov; gnupg_fd_t fp = b->fp; return FD2INT (fp); } } off_t iobuf_tell (iobuf_t a) { return a->ntotal + a->nbytes; } #if !defined(HAVE_FSEEKO) && !defined(fseeko) #ifdef HAVE_LIMITS_H # include #endif #ifndef LONG_MAX # define LONG_MAX ((long) ((unsigned long) -1 >> 1)) #endif #ifndef LONG_MIN # define LONG_MIN (-1 - LONG_MAX) #endif /**************** * A substitute for fseeko, for hosts that don't have it. */ static int fseeko (FILE * stream, off_t newpos, int whence) { while (newpos != (long) newpos) { long pos = newpos < 0 ? LONG_MIN : LONG_MAX; if (fseek (stream, pos, whence) != 0) return -1; newpos -= pos; whence = SEEK_CUR; } return fseek (stream, (long) newpos, whence); } #endif int iobuf_seek (iobuf_t a, off_t newpos) { file_filter_ctx_t *b = NULL; if (a->use == IOBUF_OUTPUT || a->use == IOBUF_INPUT) { /* Find the last filter in the pipeline. */ for (; a->chain; a = a->chain) ; if (a->filter != file_filter) return -1; b = a->filter_ov; #ifdef HAVE_W32_SYSTEM if (SetFilePointer (b->fp, newpos, NULL, FILE_BEGIN) == 0xffffffff) { log_error ("SetFilePointer failed on handle %p: ec=%d\n", b->fp, (int) GetLastError ()); return -1; } #else if (lseek (b->fp, newpos, SEEK_SET) == (off_t) - 1) { log_error ("can't lseek: %s\n", strerror (errno)); return -1; } #endif /* Discard the buffer it is not a temp stream. */ a->d.len = 0; } a->d.start = 0; a->nbytes = 0; a->nlimit = 0; a->nofast = 0; a->ntotal = newpos; a->error = 0; /* It is impossible for A->CHAIN to be non-NULL. If A is an INPUT or OUTPUT buffer, then we find the last filter, which is defined as A->CHAIN being NULL. If A is a TEMP filter, then A must be the only filter in the pipe: when iobuf_push_filter adds a filter to the front of a pipeline, it sets the new filter to be an OUTPUT filter if the pipeline is an OUTPUT or TEMP pipeline and to be an INPUT filter if the pipeline is an INPUT pipeline. Thus, only the last filter in a TEMP pipeline can be a */ /* remove filters, but the last */ if (a->chain) log_debug ("iobuf_pop_filter called in iobuf_seek - please report\n"); while (a->chain) iobuf_pop_filter (a, a->filter, NULL); return 0; } const char * iobuf_get_real_fname (iobuf_t a) { if (a->real_fname) return a->real_fname; /* the old solution */ for (; a; a = a->chain) if (!a->chain && a->filter == file_filter) { file_filter_ctx_t *b = a->filter_ov; return b->print_only_name ? NULL : b->fname; } return NULL; } const char * iobuf_get_fname (iobuf_t a) { for (; a; a = a->chain) if (!a->chain && a->filter == file_filter) { file_filter_ctx_t *b = a->filter_ov; return b->fname; } return NULL; } const char * iobuf_get_fname_nonnull (iobuf_t a) { const char *fname; fname = iobuf_get_fname (a); return fname? fname : "[?]"; } /**************** * Enable or disable partial body length mode (RFC 4880 4.2.2.4). * * If LEN is 0, this disables partial block mode by popping the * partial body length filter, which must be the most recently * added filter. * * If LEN is non-zero, it pushes a partial body length filter. If * this is a read filter, LEN must be the length byte from the first * chunk and A should be position just after this first partial body * length header. */ void iobuf_set_partial_body_length_mode (iobuf_t a, size_t len) { if (!len) /* Disable partial body length mode. */ { if (a->use == IOBUF_INPUT) log_debug ("iobuf_pop_filter called in set_partial_block_mode" " - please report\n"); log_assert (a->filter == block_filter); iobuf_pop_filter (a, block_filter, NULL); } else /* Enabled partial body length mode. */ { block_filter_ctx_t *ctx = xcalloc (1, sizeof *ctx); ctx->use = a->use; ctx->partial = 1; ctx->size = 0; ctx->first_c = len; iobuf_push_filter (a, block_filter, ctx); } } unsigned int iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, unsigned *length_of_buffer, unsigned *max_length) { int c; char *buffer = (char *)*addr_of_buffer; unsigned length = *length_of_buffer; unsigned nbytes = 0; unsigned maxlen = *max_length; char *p; /* The code assumes that we have space for at least a newline and a NUL character in the buffer. This requires at least 2 bytes. We don't complicate the code by handling the stupid corner case, but simply assert that it can't happen. */ assert (!buffer || length >= 2 || maxlen >= 2); if (!buffer || length <= 1) /* must allocate a new buffer */ { length = 256 <= maxlen ? 256 : maxlen; buffer = xrealloc (buffer, length); *addr_of_buffer = (unsigned char *)buffer; *length_of_buffer = length; } p = buffer; while (1) { if (!a->nofast && a->d.start < a->d.len && nbytes < length - 1) /* Fast path for finding '\n' by using standard C library's optimized memchr. */ { unsigned size = a->d.len - a->d.start; byte *newline_pos; if (size > length - 1 - nbytes) size = length - 1 - nbytes; newline_pos = memchr (a->d.buf + a->d.start, '\n', size); if (newline_pos) { /* Found newline, copy buffer and return. */ size = (newline_pos - (a->d.buf + a->d.start)) + 1; memcpy (p, a->d.buf + a->d.start, size); p += size; nbytes += size; a->d.start += size; a->nbytes += size; break; } else { /* No newline, copy buffer and continue. */ memcpy (p, a->d.buf + a->d.start, size); p += size; nbytes += size; a->d.start += size; a->nbytes += size; } } else { c = iobuf_readbyte (a); if (c == -1) break; *p++ = c; nbytes++; if (c == '\n') break; } if (nbytes == length - 1) /* We don't have enough space to add a \n and a \0. Increase the buffer size. */ { if (length == maxlen) /* We reached the buffer's size limit! */ { /* Skip the rest of the line. */ while ((c = iobuf_get (a)) != -1 && c != '\n') ; /* p is pointing at the last byte in the buffer. We always terminate the line with "\n\0" so overwrite the previous byte with a \n. */ assert (p > buffer); p[-1] = '\n'; /* Indicate truncation. */ *max_length = 0; break; } length += length < 1024 ? 256 : 1024; if (length > maxlen) length = maxlen; buffer = xrealloc (buffer, length); *addr_of_buffer = (unsigned char *)buffer; *length_of_buffer = length; p = buffer + nbytes; } } /* Add the terminating NUL. */ *p = 0; /* Return the number of characters written to the buffer including the newline, but not including the terminating NUL. */ return nbytes; } static int translate_file_handle (int fd, int for_write) { -#if defined(HAVE_W32CE_SYSTEM) - /* This is called only with one of the special filenames. Under - W32CE the FD here is not a file descriptor but a rendezvous id, - thus we need to finish the pipe first. */ - fd = _assuan_w32ce_finish_pipe (fd, for_write); -#elif defined(HAVE_W32_SYSTEM) +#if defined(HAVE_W32_SYSTEM) { int x; (void)for_write; if (fd == 0) x = (int) GetStdHandle (STD_INPUT_HANDLE); else if (fd == 1) x = (int) GetStdHandle (STD_OUTPUT_HANDLE); else if (fd == 2) x = (int) GetStdHandle (STD_ERROR_HANDLE); else x = fd; if (x == -1) log_debug ("GetStdHandle(%d) failed: ec=%d\n", fd, (int) GetLastError ()); fd = x; } #else (void)for_write; #endif return fd; } void iobuf_skip_rest (iobuf_t a, unsigned long n, int partial) { if ( partial ) { for (;;) { if (a->nofast || a->d.start >= a->d.len) { if (iobuf_readbyte (a) == -1) { break; } } else { unsigned long count = a->d.len - a->d.start; a->nbytes += count; a->d.start = a->d.len; } } } else { unsigned long remaining = n; while (remaining > 0) { if (a->nofast || a->d.start >= a->d.len) { if (iobuf_readbyte (a) == -1) { break; } --remaining; } else { unsigned long count = a->d.len - a->d.start; if (count > remaining) { count = remaining; } a->nbytes += count; a->d.start += count; remaining -= count; } } } } diff --git a/common/mischelp.c b/common/mischelp.c index 81dd501f8..68fd2bc24 100644 --- a/common/mischelp.c +++ b/common/mischelp.c @@ -1,210 +1,188 @@ /* mischelp.c - Miscellaneous helper functions * Copyright (C) 1998, 2000, 2001, 2006, 2007 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #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 "util.h" #include "common-defs.h" #include "stringhelp.h" #include "utf8conv.h" #include "mischelp.h" void wipememory (void *ptr, size_t len) { #if defined(HAVE_W32_SYSTEM) && defined(SecureZeroMemory) SecureZeroMemory (ptr, len); #elif defined(HAVE_EXPLICIT_BZERO) explicit_bzero (ptr, len); #else /* Prevent compiler from optimizing away the call to memset by accessing memset through volatile pointer. */ static void *(*volatile memset_ptr)(void *, int, size_t) = (void *)memset; memset_ptr (ptr, 0, len); #endif } /* Check whether the files NAME1 and NAME2 are identical. This is for example achieved by comparing the inode numbers of the files. */ int same_file_p (const char *name1, const char *name2) { int yes; /* First try a shortcut. */ if (!compare_filenames (name1, name2)) yes = 1; else { #ifdef HAVE_W32_SYSTEM HANDLE file1, file2; BY_HANDLE_FILE_INFORMATION info1, info2; -#ifdef HAVE_W32CE_SYSTEM - { - wchar_t *wname = utf8_to_wchar (name1); - if (wname) - file1 = CreateFile (wname, 0, 0, NULL, OPEN_EXISTING, 0, NULL); - else - file1 = INVALID_HANDLE_VALUE; - xfree (wname); - } -#else file1 = CreateFile (name1, 0, 0, NULL, OPEN_EXISTING, 0, NULL); -#endif if (file1 == INVALID_HANDLE_VALUE) yes = 0; /* If we can't open the file, it is not the same. */ else { -#ifdef HAVE_W32CE_SYSTEM - { - wchar_t *wname = utf8_to_wchar (name2); - if (wname) - file2 = CreateFile (wname, 0, 0, NULL, OPEN_EXISTING, 0, NULL); - else - file2 = INVALID_HANDLE_VALUE; - xfree (wname); - } -#else file2 = CreateFile (name2, 0, 0, NULL, OPEN_EXISTING, 0, NULL); -#endif if (file2 == INVALID_HANDLE_VALUE) yes = 0; /* If we can't open the file, it is not the same. */ else { yes = (GetFileInformationByHandle (file1, &info1) && GetFileInformationByHandle (file2, &info2) && info1.dwVolumeSerialNumber==info2.dwVolumeSerialNumber && info1.nFileIndexHigh == info2.nFileIndexHigh && info1.nFileIndexLow == info2.nFileIndexLow); CloseHandle (file2); } CloseHandle (file1); } #else /*!HAVE_W32_SYSTEM*/ struct stat info1, info2; yes = (!stat (name1, &info1) && !stat (name2, &info2) && info1.st_dev == info2.st_dev && info1.st_ino == info2.st_ino); #endif /*!HAVE_W32_SYSTEM*/ } return yes; } /* timegm() is a GNU function that might not be available everywhere. It's basically the inverse of gmtime() - you give it a struct tm, and get back a time_t. It differs from mktime() in that it handles the case where the struct tm is UTC and the local environment isn't. Note, that this replacement implementation might not be thread-safe! Some BSDs don't handle the putenv("foo") case properly, so we use unsetenv if the platform has it to remove environment variables. */ #ifndef HAVE_TIMEGM time_t timegm (struct tm *tm) { #ifdef HAVE_W32_SYSTEM /* This one is thread safe. */ SYSTEMTIME st; FILETIME ft; unsigned long long cnsecs; st.wYear = tm->tm_year + 1900; st.wMonth = tm->tm_mon + 1; st.wDay = tm->tm_mday; st.wHour = tm->tm_hour; st.wMinute = tm->tm_min; st.wSecond = tm->tm_sec; st.wMilliseconds = 0; /* Not available. */ st.wDayOfWeek = 0; /* Ignored. */ /* System time is UTC thus the conversion is pretty easy. */ if (!SystemTimeToFileTime (&st, &ft)) { gpg_err_set_errno (EINVAL); return (time_t)(-1); } cnsecs = (((unsigned long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime); cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ return (time_t)(cnsecs / 10000000ULL); #else /* (Non thread safe implementation!) */ time_t answer; char *zone; zone=getenv("TZ"); putenv("TZ=UTC"); tzset(); answer=mktime(tm); if(zone) { static char *old_zone; if (!old_zone) { old_zone = malloc(3+strlen(zone)+1); if (old_zone) { strcpy(old_zone,"TZ="); strcat(old_zone,zone); } } if (old_zone) putenv (old_zone); } else gnupg_unsetenv("TZ"); tzset(); return answer; #endif } #endif /*!HAVE_TIMEGM*/ diff --git a/common/stringhelp.c b/common/stringhelp.c index 3e56d664d..6959299e4 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -1,1799 +1,1793 @@ /* stringhelp.c - standard string helper functions * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, * 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2014 Werner Koch * Copyright (C) 2015, 2021 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) */ #include #include #include #include #include #include #ifdef HAVE_PWD_H # include #endif #include #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif #include #include "util.h" #include "common-defs.h" #include "utf8conv.h" #include "sysutils.h" #include "stringhelp.h" #define tohex_lower(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'a')) /* Sometimes we want to avoid mixing slashes and backslashes on W32 and prefer backslashes. There is usual no problem with mixing them, however a very few W32 API calls can't grok plain slashes. Printing filenames with mixed slashes also looks a bit strange. This function has no effext on POSIX. */ static inline char * change_slashes (char *name) { #ifdef HAVE_DOSISH_SYSTEM char *p; if (strchr (name, '\\')) { for (p=name; *p; p++) if (*p == '/') *p = '\\'; } #endif /*HAVE_DOSISH_SYSTEM*/ return name; } /* * Check whether STRING starts with KEYWORD. The keyword is * delimited by end of string, a space or a tab. Returns NULL if not * found or a pointer into STRING to the next non-space character * after the KEYWORD (which may be end of string). */ char * has_leading_keyword (const char *string, const char *keyword) { size_t n = strlen (keyword); if (!strncmp (string, keyword, n) && (!string[n] || string[n] == ' ' || string[n] == '\t')) { string += n; while (*string == ' ' || *string == '\t') string++; return (char*)string; } return NULL; } /* * Look for the substring SUB in buffer and return a pointer to that * substring in BUFFER or NULL if not found. * Comparison is case-insensitive. */ const char * memistr (const void *buffer, size_t buflen, const char *sub) { const unsigned char *buf = buffer; const unsigned char *t = (const unsigned char *)buffer; const unsigned char *s = (const unsigned char *)sub; size_t n = buflen; for ( ; n ; t++, n-- ) { if ( toupper (*t) == toupper (*s) ) { for ( buf=t++, buflen = n--, s++; n && toupper (*t) == toupper (*s); t++, s++, n-- ) ; if (!*s) return (const char*)buf; t = buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } const char * ascii_memistr ( const void *buffer, size_t buflen, const char *sub ) { const unsigned char *buf = buffer; const unsigned char *t = (const unsigned char *)buf; const unsigned char *s = (const unsigned char *)sub; size_t n = buflen; for ( ; n ; t++, n-- ) { if (ascii_toupper (*t) == ascii_toupper (*s) ) { for ( buf=t++, buflen = n--, s++; n && ascii_toupper (*t) == ascii_toupper (*s); t++, s++, n-- ) ; if (!*s) return (const char*)buf; t = (const unsigned char *)buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } /* This function is similar to strncpy(). However it won't copy more * than N - 1 characters and makes sure that a '\0' is appended. With * N given as 0, nothing will happen. With DEST given as NULL, memory * will be allocated using xmalloc (i.e. if it runs out of core the * function terminates). Returns DEST or a pointer to the allocated * memory. */ char * mem2str (char *dest, const void *src, size_t n) { char *d; const char *s; if (n) { if (!dest) dest = xmalloc (n); d = dest; s = src ; for (n--; n && *s; n--) *d++ = *s++; *d = '\0' ; } return dest; } /**************** * remove leading and trailing white spaces */ char * trim_spaces( char *str ) { char *string, *p, *mark; string = str; /* find first non space character */ for( p=string; *p && isspace( *(byte*)p ) ; p++ ) ; /* move characters */ for( (mark = NULL); (*string = *p); string++, p++ ) if( isspace( *(byte*)p ) ) { if( !mark ) mark = string ; } else mark = NULL ; if( mark ) *mark = '\0' ; /* remove trailing spaces */ return str ; } /* Same as trim_spaces but only consider, space, tab, cr and lf as space. */ char * ascii_trim_spaces (char *str) { char *string, *p, *mark; string = str; /* Find first non-ascii space character. */ for (p=string; *p && ascii_isspace (*p); p++) ; /* Move characters. */ for (mark=NULL; (*string = *p); string++, p++ ) { if (ascii_isspace (*p)) { if (!mark) mark = string; } else mark = NULL ; } if (mark) *mark = '\0' ; /* Remove trailing spaces. */ return str ; } /**************** * remove trailing white spaces */ char * trim_trailing_spaces( char *string ) { char *p, *mark; for( mark = NULL, p = string; *p; p++ ) { if( isspace( *(byte*)p ) ) { if( !mark ) mark = p; } else mark = NULL; } if( mark ) *mark = '\0' ; return string ; } unsigned trim_trailing_chars( byte *line, unsigned len, const char *trimchars ) { byte *p, *mark; unsigned n; for(mark=NULL, p=line, n=0; n < len; n++, p++ ) { if( strchr(trimchars, *p ) ) { if( !mark ) mark = p; } else mark = NULL; } if( mark ) { *mark = 0; return mark - line; } return len; } /**************** * remove trailing white spaces and return the length of the buffer */ unsigned trim_trailing_ws( byte *line, unsigned len ) { return trim_trailing_chars( line, len, " \t\r\n" ); } size_t length_sans_trailing_chars (const unsigned char *line, size_t len, const char *trimchars ) { const unsigned char *p, *mark; size_t n; for( mark=NULL, p=line, n=0; n < len; n++, p++ ) { if (strchr (trimchars, *p )) { if( !mark ) mark = p; } else mark = NULL; } if (mark) return mark - line; return len; } /* * Return the length of line ignoring trailing white-space. */ size_t length_sans_trailing_ws (const unsigned char *line, size_t len) { return length_sans_trailing_chars (line, len, " \t\r\n"); } /* * Extract from a given path the filename component. This function * terminates the process on memory shortage. */ char * make_basename(const char *filepath, const char *inputpath) { #ifdef __riscos__ return riscos_make_basename(filepath, inputpath); #else char *p; (void)inputpath; /* Only required for riscos. */ if ( !(p=strrchr(filepath, '/')) ) #ifdef HAVE_DOSISH_SYSTEM if ( !(p=strrchr(filepath, '\\')) ) #endif #ifdef HAVE_DRIVE_LETTERS if ( !(p=strrchr(filepath, ':')) ) #endif { return xstrdup(filepath); } return xstrdup(p+1); #endif } /* * Extract from a given filename the path prepended to it. If there * isn't a path prepended to the filename, a dot is returned ('.'). * This function terminates the process on memory shortage. */ char * make_dirname(const char *filepath) { char *dirname; int dirname_length; char *p; if ( !(p=strrchr(filepath, '/')) ) #ifdef HAVE_DOSISH_SYSTEM if ( !(p=strrchr(filepath, '\\')) ) #endif #ifdef HAVE_DRIVE_LETTERS if ( !(p=strrchr(filepath, ':')) ) #endif { return xstrdup("."); } dirname_length = p-filepath; dirname = xmalloc(dirname_length+1); strncpy(dirname, filepath, dirname_length); dirname[dirname_length] = 0; return dirname; } static char * get_pwdir (int xmode, const char *name) { char *result = NULL; #ifdef HAVE_PWD_H struct passwd *pwd = NULL; if (name) { #ifdef HAVE_GETPWNAM /* Fixme: We should use getpwnam_r if available. */ pwd = getpwnam (name); #endif } else { #ifdef HAVE_GETPWUID /* Fixme: We should use getpwuid_r if available. */ pwd = getpwuid (getuid()); #endif } if (pwd) { if (xmode) result = xstrdup (pwd->pw_dir); else result = xtrystrdup (pwd->pw_dir); } #else /*!HAVE_PWD_H*/ /* No support at all. */ (void)xmode; (void)name; #endif /*HAVE_PWD_H*/ return result; } /* xmode 0 := Return NULL on error 1 := Terminate on error 2 := Make sure that name is absolute; return NULL on error 3 := Make sure that name is absolute; terminate on error */ static char * do_make_filename (int xmode, const char *first_part, va_list arg_ptr) { const char *argv[32]; int argc; size_t n; int skip = 1; char *home_buffer = NULL; char *name, *home, *p; int want_abs; want_abs = !!(xmode & 2); xmode &= 1; n = strlen (first_part) + 1; argc = 0; while ( (argv[argc] = va_arg (arg_ptr, const char *)) ) { n += strlen (argv[argc]) + 1; if (argc >= DIM (argv)-1) { if (xmode) BUG (); gpg_err_set_errno (EINVAL); return NULL; } argc++; } n++; home = NULL; if (*first_part == '~') { if (first_part[1] == '/' || !first_part[1]) { /* This is the "~/" or "~" case. */ home = getenv("HOME"); if (!home) home = home_buffer = get_pwdir (xmode, NULL); if (home && *home) n += strlen (home); } else { /* This is the "~username/" or "~username" case. */ char *user; if (xmode) user = xstrdup (first_part+1); else { user = xtrystrdup (first_part+1); if (!user) return NULL; } p = strchr (user, '/'); if (p) *p = 0; skip = 1 + strlen (user); home = home_buffer = get_pwdir (xmode, user); xfree (user); if (home) n += strlen (home); else skip = 1; } } if (xmode) name = xmalloc (n); else { name = xtrymalloc (n); if (!name) { xfree (home_buffer); return NULL; } } if (home) p = stpcpy (stpcpy (name, home), first_part + skip); else p = stpcpy (name, first_part); xfree (home_buffer); for (argc=0; argv[argc]; argc++) { /* Avoid a leading double slash if the first part was "/". */ if (!argc && name[0] == '/' && !name[1]) p = stpcpy (p, argv[argc]); else p = stpcpy (stpcpy (p, "/"), argv[argc]); } if (want_abs) { #ifdef HAVE_DRIVE_LETTERS p = strchr (name, ':'); if (p) p++; else p = name; #else p = name; #endif if (*p != '/' #ifdef HAVE_DRIVE_LETTERS && *p != '\\' #endif ) { home = gnupg_getcwd (); if (!home) { if (xmode) { fprintf (stderr, "\nfatal: getcwd failed: %s\n", strerror (errno)); exit(2); } xfree (name); return NULL; } n = strlen (home) + 1 + strlen (name) + 1; if (xmode) home_buffer = xmalloc (n); else { home_buffer = xtrymalloc (n); if (!home_buffer) { xfree (home); xfree (name); return NULL; } } if (p == name) p = home_buffer; else /* Windows case. */ { memcpy (home_buffer, p, p - name + 1); p = home_buffer + (p - name + 1); } /* Avoid a leading double slash if the cwd is "/". */ if (home[0] == '/' && !home[1]) strcpy (stpcpy (p, "/"), name); else strcpy (stpcpy (stpcpy (p, home), "/"), name); xfree (home); xfree (name); name = home_buffer; /* Let's do a simple compression to catch the most common case of using "." for gpg's --homedir option. */ n = strlen (name); if (n > 2 && name[n-2] == '/' && name[n-1] == '.') name[n-2] = 0; } } return change_slashes (name); } /* Construct a filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function terminates the process on memory shortage. */ char * make_filename (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (1, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct a filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function may return NULL on error. */ char * make_filename_try (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (0, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct an absolute filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function terminates the process on memory shortage. */ char * make_absfilename (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (3, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct an absolute filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function may return NULL on error. */ char * make_absfilename_try (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (2, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Compare whether the filenames are identical. This is a special version of strcmp() taking the semantics of filenames in account. Note that this function works only on the supplied names without considering any context like the current directory. See also same_file_p(). */ int compare_filenames (const char *a, const char *b) { #ifdef HAVE_DOSISH_SYSTEM for ( ; *a && *b; a++, b++ ) { if (*a != *b && (toupper (*(const unsigned char*)a) != toupper (*(const unsigned char*)b) ) && !((*a == '/' && *b == '\\') || (*a == '\\' && *b == '/'))) break; } if ((*a == '/' && *b == '\\') || (*a == '\\' && *b == '/')) return 0; else return (toupper (*(const unsigned char*)a) - toupper (*(const unsigned char*)b)); #else return strcmp(a,b); #endif } /* Convert a base-10 number in STRING into a 64 bit unsigned int * value. Leading white spaces are skipped but no error checking is * done. Thus it is similar to atoi(). */ uint64_t string_to_u64 (const char *string) { uint64_t val = 0; while (spacep (string)) string++; for (; digitp (string); string++) { val *= 10; val += *string - '0'; } return val; } /* Convert 2 hex characters at S to a byte value. Return this value or -1 if there is an error. */ int hextobyte (const char *s) { int c; if ( *s >= '0' && *s <= '9' ) c = 16 * (*s - '0'); else if ( *s >= 'A' && *s <= 'F' ) c = 16 * (10 + *s - 'A'); else if ( *s >= 'a' && *s <= 'f' ) c = 16 * (10 + *s - 'a'); else return -1; s++; if ( *s >= '0' && *s <= '9' ) c += *s - '0'; else if ( *s >= 'A' && *s <= 'F' ) c += 10 + *s - 'A'; else if ( *s >= 'a' && *s <= 'f' ) c += 10 + *s - 'a'; else return -1; return c; } /* Given a string containing an UTF-8 encoded text, return the number of characters in this string. It differs from strlen in that it only counts complete UTF-8 characters. SIZE is the maximum length of the string in bytes. If SIZE is -1, then a NUL character is taken to be the end of the string. Note, that this function does not take combined characters into account. */ size_t utf8_charcount (const char *s, int len) { size_t n; if (len == 0) return 0; for (n=0; *s; s++) { if ( (*s&0xc0) != 0x80 ) /* Exclude continuation bytes: 10xxxxxx */ n++; if (len != -1) { len --; if (len == 0) break; } } return n; } /**************************************************** ********** W32 specific functions **************** ****************************************************/ #ifdef HAVE_W32_SYSTEM const char * w32_strerror (int ec) { static char strerr[256]; if (ec == -1) ec = (int)GetLastError (); -#ifdef HAVE_W32CE_SYSTEM - /* There is only a wchar_t FormatMessage. It does not make much - sense to play the conversion game; we print only the code. */ - snprintf (strerr, sizeof strerr, "ec=%d", (int)GetLastError ()); -#else FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, ec, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), strerr, DIM (strerr)-1, NULL); { /* Strip the CR,LF - we want just the string. */ size_t n = strlen (strerr); if (n > 2 && strerr[n-2] == '\r' && strerr[n-1] == '\n' ) strerr[n-2] = 0; } -#endif return strerr; } #endif /*HAVE_W32_SYSTEM*/ /**************************************************** ******** Locale insensitive ctype functions ******** ****************************************************/ /* FIXME: replace them by a table lookup and macros */ int ascii_isupper (int c) { return c >= 'A' && c <= 'Z'; } int ascii_islower (int c) { return c >= 'a' && c <= 'z'; } int ascii_toupper (int c) { if (c >= 'a' && c <= 'z') c &= ~0x20; return c; } int ascii_tolower (int c) { if (c >= 'A' && c <= 'Z') c |= 0x20; return c; } /* Lowercase all ASCII characters in S. */ char * ascii_strlwr (char *s) { char *p = s; for (p=s; *p; p++ ) if (isascii (*p) && *p >= 'A' && *p <= 'Z') *p |= 0x20; return s; } /* Upcase all ASCII characters in S. */ char * ascii_strupr (char *s) { char *p = s; for (p=s; *p; p++ ) if (isascii (*p) && *p >= 'a' && *p <= 'z') *p &= ~0x20; return s; } int ascii_strcasecmp( const char *a, const char *b ) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (*a != *b && ascii_toupper(*a) != ascii_toupper(*b)) break; } return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); } int ascii_strncasecmp (const char *a, const char *b, size_t n) { const unsigned char *p1 = (const unsigned char *)a; const unsigned char *p2 = (const unsigned char *)b; unsigned char c1, c2; if (p1 == p2 || !n ) return 0; do { c1 = ascii_tolower (*p1); c2 = ascii_tolower (*p2); if ( !--n || c1 == '\0') break; ++p1; ++p2; } while (c1 == c2); return c1 - c2; } int ascii_memcasecmp (const void *a_arg, const void *b_arg, size_t n ) { const char *a = a_arg; const char *b = b_arg; if (a == b) return 0; for ( ; n; n--, a++, b++ ) { if( *a != *b && ascii_toupper (*a) != ascii_toupper (*b) ) return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); } return 0; } int ascii_strcmp( const char *a, const char *b ) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (*a != *b ) break; } return *a == *b? 0 : (*(signed char *)a - *(signed char *)b); } void * ascii_memcasemem (const void *haystack, size_t nhaystack, const void *needle, size_t nneedle) { if (!nneedle) return (void*)haystack; /* finding an empty needle is really easy */ if (nneedle <= nhaystack) { const char *a = haystack; const char *b = a + nhaystack - nneedle; for (; a <= b; a++) { if ( !ascii_memcasecmp (a, needle, nneedle) ) return (void *)a; } } return NULL; } /********************************************* ********** missing string functions ********* *********************************************/ #ifndef HAVE_STPCPY char * stpcpy(char *a,const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } #endif #ifndef HAVE_STRPBRK /* Find the first occurrence in S of any character in ACCEPT. Code taken from glibc-2.6/string/strpbrk.c (LGPLv2.1+) and modified. */ char * strpbrk (const char *s, const char *accept) { while (*s != '\0') { const char *a = accept; while (*a != '\0') if (*a++ == *s) return (char *) s; ++s; } return NULL; } #endif /*!HAVE_STRPBRK*/ #ifndef HAVE_STRSEP /* Code taken from glibc-2.2.1/sysdeps/generic/strsep.c. */ char * strsep (char **stringp, const char *delim) { char *begin, *end; begin = *stringp; if (begin == NULL) return NULL; /* A frequent case is when the delimiter string contains only one character. Here we don't need to call the expensive 'strpbrk' function and instead work using 'strchr'. */ if (delim[0] == '\0' || delim[1] == '\0') { char ch = delim[0]; if (ch == '\0') end = NULL; else { if (*begin == ch) end = begin; else if (*begin == '\0') end = NULL; else end = strchr (begin + 1, ch); } } else /* Find the end of the token. */ end = strpbrk (begin, delim); if (end) { /* Terminate the token and set *STRINGP past NUL character. */ *end++ = '\0'; *stringp = end; } else /* No more delimiters; this is the last token. */ *stringp = NULL; return begin; } #endif /*HAVE_STRSEP*/ #ifndef HAVE_STRLWR char * strlwr(char *s) { char *p; for(p=s; *p; p++ ) *p = tolower(*p); return s; } #endif #ifndef HAVE_STRCASECMP int strcasecmp( const char *a, const char *b ) { for( ; *a && *b; a++, b++ ) { if( *a != *b && toupper(*a) != toupper(*b) ) break; } return *(const byte*)a - *(const byte*)b; } #endif /**************** * mingw32/cpd has a memicmp() */ #ifndef HAVE_MEMICMP int memicmp( const char *a, const char *b, size_t n ) { for( ; n; n--, a++, b++ ) if( *a != *b && toupper(*(const byte*)a) != toupper(*(const byte*)b) ) return *(const byte *)a - *(const byte*)b; return 0; } #endif #ifndef HAVE_MEMRCHR void * memrchr (const void *buffer, int c, size_t n) { const unsigned char *p = buffer; for (p += n; n ; n--) if (*--p == c) return (void *)p; return NULL; } #endif /*HAVE_MEMRCHR*/ /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL all characters in EXTRA are also escaped. */ static char * do_percent_escape (const char *str, const char *extra, int die) { int i, j; char *ptr; if (!str) return NULL; for (i=j=0; str[i]; i++) if (str[i] == ':' || str[i] == '%' || str[i] == '\n' || (extra && strchr (extra, str[i]))) j++; if (die) ptr = xmalloc (i + 2 * j + 1); else { ptr = xtrymalloc (i + 2 * j + 1); if (!ptr) return NULL; } i = 0; while (*str) { if (*str == ':') { ptr[i++] = '%'; ptr[i++] = '3'; ptr[i++] = 'a'; } else if (*str == '%') { ptr[i++] = '%'; ptr[i++] = '2'; ptr[i++] = '5'; } else if (*str == '\n') { /* The newline is problematic in a line-based format. */ ptr[i++] = '%'; ptr[i++] = '0'; ptr[i++] = 'a'; } else if (extra && strchr (extra, *str)) { ptr[i++] = '%'; ptr[i++] = tohex_lower ((*str>>4)&15); ptr[i++] = tohex_lower (*str&15); } else ptr[i++] = *str; str++; } ptr[i] = '\0'; return ptr; } /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL all characters in EXTRA are also escaped. This function terminates the process on memory shortage. */ char * percent_escape (const char *str, const char *extra) { return do_percent_escape (str, extra, 1); } /* Same as percent_escape but return NULL instead of exiting on memory error. */ char * try_percent_escape (const char *str, const char *extra) { return do_percent_escape (str, extra, 0); } /* Same as strconcat but takes a va_list. Returns EINVAL if the list * is too long, all other errors are due to an ENOMEM condition. */ char * vstrconcat (const char *s1, va_list arg_ptr) { const char *argv[48]; size_t argc; size_t needed; char *buffer, *p; argc = 0; argv[argc++] = s1; needed = strlen (s1); while (((argv[argc] = va_arg (arg_ptr, const char *)))) { needed += strlen (argv[argc]); if (argc >= DIM (argv)-1) { gpg_err_set_errno (EINVAL); return NULL; } argc++; } needed++; buffer = xtrymalloc (needed); if (buffer) { for (p = buffer, argc=0; argv[argc]; argc++) p = stpcpy (p, argv[argc]); } return buffer; } /* Concatenate the string S1 with all the following strings up to a NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ char * strconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = xtrystrdup (""); else { va_start (arg_ptr, s1); result = vstrconcat (s1, arg_ptr); va_end (arg_ptr); } return result; } /* Same as strconcat but terminate the process with an error message if something goes wrong. */ char * xstrconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = xstrdup (""); else { va_start (arg_ptr, s1); result = vstrconcat (s1, arg_ptr); va_end (arg_ptr); } if (!result) { if (errno == EINVAL) fputs ("\nfatal: too many args for xstrconcat\n", stderr); else fputs ("\nfatal: out of memory\n", stderr); exit (2); } return result; } /* Split a string into fields at DELIM. REPLACEMENT is the character to replace the delimiter with (normally: '\0' so that each field is NUL terminated). The caller is responsible for freeing the result. Note: this function modifies STRING! If you need the original value, then you should pass a copy to this function. If malloc fails, this function returns NULL. */ char ** strsplit (char *string, char delim, char replacement, int *count) { int fields = 1; char *t; char **result; /* First, count the number of fields. */ for (t = strchr (string, delim); t; t = strchr (t + 1, delim)) fields ++; result = xtrycalloc ((fields + 1), sizeof (*result)); if (! result) return NULL; result[0] = string; fields = 1; for (t = strchr (string, delim); t; t = strchr (t + 1, delim)) { result[fields ++] = t + 1; *t = replacement; } if (count) *count = fields; return result; } /* Tokenize STRING using the set of delimiters in DELIM. Leading * spaces and tabs are removed from all tokens. The caller must xfree * the result. * * Returns: A malloced and NULL delimited array with the tokens. On * memory error NULL is returned and ERRNO is set. */ static char ** do_strtokenize (const char *string, const char *delim, int trim) { const char *s; size_t fields; size_t bytes, n; char *buffer; char *p, *px, *pend; char **result; /* Count the number of fields. */ for (fields = 1, s = strpbrk (string, delim); s; s = strpbrk (s + 1, delim)) fields++; fields++; /* Add one for the terminating NULL. */ /* Allocate an array for all fields, a terminating NULL, and space for a copy of the string. */ bytes = fields * sizeof *result; if (bytes / sizeof *result != fields) { gpg_err_set_errno (ENOMEM); return NULL; } n = strlen (string) + 1; bytes += n; if (bytes < n) { gpg_err_set_errno (ENOMEM); return NULL; } result = xtrymalloc (bytes); if (!result) return NULL; buffer = (char*)(result + fields); /* Copy and parse the string. */ strcpy (buffer, string); for (n = 0, p = buffer; (pend = strpbrk (p, delim)); p = pend + 1) { *pend = 0; if (trim) { while (spacep (p)) p++; for (px = pend - 1; px >= p && spacep (px); px--) *px = 0; } result[n++] = p; } if (trim) { while (spacep (p)) p++; for (px = p + strlen (p) - 1; px >= p && spacep (px); px--) *px = 0; } result[n++] = p; result[n] = NULL; log_assert ((char*)(result + n + 1) == buffer); return result; } /* Tokenize STRING using the set of delimiters in DELIM. Leading * spaces and tabs are removed from all tokens. The caller must xfree * the result. * * Returns: A malloced and NULL delimited array with the tokens. On * memory error NULL is returned and ERRNO is set. */ char ** strtokenize (const char *string, const char *delim) { return do_strtokenize (string, delim, 1); } /* Same as strtokenize but does not trim leading and trailing spaces * from the fields. */ char ** strtokenize_nt (const char *string, const char *delim) { return do_strtokenize (string, delim, 0); } /* Split a string into space delimited fields and remove leading and * trailing spaces from each field. A pointer to each field is stored * in ARRAY. Stop splitting at ARRAYSIZE fields. The function * modifies STRING. The number of parsed fields is returned. * Example: * * char *fields[2]; * if (split_fields (string, fields, DIM (fields)) < 2) * return // Not enough args. * foo (fields[0]); * foo (fields[1]); */ int split_fields (char *string, const char **array, int arraysize) { int n = 0; const char *p; char *pend; for (p = string; *p == ' '; p++) ; do { if (n == arraysize) break; array[n++] = p; pend = strchr (p, ' '); if (!pend) break; *pend++ = 0; for (p = pend; *p == ' '; p++) ; } while (*p); return n; } /* Split a string into colon delimited fields A pointer to each field * is stored in ARRAY. Stop splitting at ARRAYSIZE fields. The * function modifies STRING. The number of parsed fields is returned. * Note that leading and trailing spaces are not removed from the fields. * Example: * * char *fields[2]; * if (split_fields (string, fields, DIM (fields)) < 2) * return // Not enough args. * foo (fields[0]); * foo (fields[1]); */ int split_fields_colon (char *string, const char **array, int arraysize) { int n = 0; const char *p; char *pend; p = string; do { if (n == arraysize) break; array[n++] = p; pend = strchr (p, ':'); if (!pend) break; *pend++ = 0; p = pend; } while (*p); return n; } /* Version number parsing. */ /* This function parses the first portion of the version number S and stores it in *NUMBER. On success, this function returns a pointer into S starting with the first character, which is not part of the initial number portion; on failure, NULL is returned. */ static const char* parse_version_number (const char *s, int *number) { int val = 0; if (*s == '0' && digitp (s+1)) return NULL; /* Leading zeros are not allowed. */ for (; digitp (s); s++) { val *= 10; val += *s - '0'; } *number = val; return val < 0 ? NULL : s; } /* This function breaks up the complete string-representation of the version number S, which is of the following structure: .[.]. The major, minor, and micro number components will be stored in *MAJOR, *MINOR and *MICRO. If MICRO is not given 0 is used instead. On success, the last component, the patch level, will be returned; in failure, NULL will be returned. */ static const char * parse_version_string (const char *s, int *major, int *minor, int *micro) { s = parse_version_number (s, major); if (!s || *s != '.') return NULL; s++; s = parse_version_number (s, minor); if (!s) return NULL; if (*s == '.') { s++; s = parse_version_number (s, micro); if (!s) return NULL; } else *micro = 0; return s; /* Patchlevel. */ } /* Compare the version string MY_VERSION to the version string * REQ_VERSION. Returns -1, 0, or 1 if MY_VERSION is found, * respectively, to be less than, to match, or be greater than * REQ_VERSION. This function works for three and two part version * strings; for a two part version string the micro part is assumed to * be 0. Patch levels are compared as strings. If a version number * is invalid INT_MIN is returned. If REQ_VERSION is given as NULL * the function returns 0 if MY_VERSION is parsable version string. */ int compare_version_strings (const char *my_version, const char *req_version) { int my_major, my_minor, my_micro; int rq_major, rq_minor, rq_micro; const char *my_patch, *rq_patch; int result; if (!my_version) return INT_MIN; my_patch = parse_version_string (my_version, &my_major, &my_minor, &my_micro); if (!my_patch) return INT_MIN; if (!req_version) return 0; /* MY_VERSION can be parsed. */ rq_patch = parse_version_string (req_version, &rq_major, &rq_minor,&rq_micro); if (!rq_patch) return INT_MIN; if (my_major == rq_major) { if (my_minor == rq_minor) { if (my_micro == rq_micro) result = strcmp (my_patch, rq_patch); else result = my_micro - rq_micro; } else result = my_minor - rq_minor; } else result = my_major - rq_major; return !result? 0 : result < 0 ? -1 : 1; } /* Format a string so that it fits within about TARGET_COLS columns. * TEXT_IN is copied to a new buffer, which is returned. Normally, * target_cols will be 72 and max_cols is 80. On error NULL is * returned and ERRNO is set. */ char * format_text (const char *text_in, int target_cols, int max_cols) { /* const int do_debug = 0; */ /* The character under consideration. */ char *p; /* The start of the current line. */ char *line; /* The last space that we saw. */ char *last_space = NULL; int last_space_cols = 0; int copied_last_space = 0; char *text; text = xtrystrdup (text_in); if (!text) return NULL; p = line = text; while (1) { /* The number of columns including any trailing space. */ int cols; p = p + strcspn (p, "\n "); if (! p) /* P now points to the NUL character. */ p = &text[strlen (text)]; if (*p == '\n') /* Pass through any newlines. */ { p ++; line = p; last_space = NULL; last_space_cols = 0; copied_last_space = 1; continue; } /* Have a space or a NUL. Note: we don't count the trailing space. */ cols = utf8_charcount (line, (uintptr_t) p - (uintptr_t) line); if (cols < target_cols) { if (! *p) /* Nothing left to break. */ break; last_space = p; last_space_cols = cols; p ++; /* Skip any immediately following spaces. If we break: "... foo bar ..." between "foo" and "bar" then we want: "... foo\nbar ...", which means that the left space has to be the first space after foo, not the last space before bar. */ while (*p == ' ') p ++; } else { int cols_with_left_space; int cols_with_right_space; int left_penalty; int right_penalty; cols_with_left_space = last_space_cols; cols_with_right_space = cols; /* if (do_debug) */ /* log_debug ("Breaking: '%.*s'\n", */ /* (int) ((uintptr_t) p - (uintptr_t) line), line); */ /* The number of columns away from TARGET_COLS. We prefer to underflow than to overflow. */ left_penalty = target_cols - cols_with_left_space; right_penalty = 2 * (cols_with_right_space - target_cols); if (cols_with_right_space > max_cols) /* Add a large penalty for each column that exceeds max_cols. */ right_penalty += 4 * (cols_with_right_space - max_cols); /* if (do_debug) */ /* log_debug ("Left space => %d cols (penalty: %d); " */ /* "right space => %d cols (penalty: %d)\n", */ /* cols_with_left_space, left_penalty, */ /* cols_with_right_space, right_penalty); */ if (last_space_cols && left_penalty <= right_penalty) { /* Prefer the left space. */ /* if (do_debug) */ /* log_debug ("Breaking at left space.\n"); */ p = last_space; } else { /* if (do_debug) */ /* log_debug ("Breaking at right space.\n"); */ } if (! *p) break; *p = '\n'; p ++; if (*p == ' ') { int spaces; for (spaces = 1; p[spaces] == ' '; spaces ++) ; memmove (p, &p[spaces], strlen (&p[spaces]) + 1); } line = p; last_space = NULL; last_space_cols = 0; copied_last_space = 0; } } /* Chop off any trailing space. */ trim_trailing_chars (text, strlen (text), " "); /* If we inserted the trailing newline, then remove it. */ if (! copied_last_space && *text && text[strlen (text) - 1] == '\n') text[strlen (text) - 1] = '\0'; return text; } /* Substitute environment variables in STRING and return a new string. * On error the function returns NULL. */ char * substitute_envvars (const char *string) { char *line, *p, *pend; const char *value; size_t valuelen, n; char *result = NULL; result = line = xtrystrdup (string); if (!result) return NULL; /* Ooops */ while (*line) { p = strchr (line, '$'); if (!p) goto leave; /* No or no more variables. */ if (p[1] == '$') /* Escaped dollar sign. */ { memmove (p, p+1, strlen (p+1)+1); line = p + 1; continue; } if (p[1] == '{') { int count = 0; for (pend=p+2; *pend; pend++) { if (*pend == '{') count++; else if (*pend == '}') { if (--count < 0) break; } } if (!*pend) goto leave; /* Unclosed - don't substitute. */ } else { for (pend = p+1; *pend && (alnump (pend) || *pend == '_'); pend++) ; } if (p[1] == '{' && *pend == '}') { int save = *pend; *pend = 0; value = getenv (p+2); *pend++ = save; } else { int save = *pend; *pend = 0; value = getenv (p+1); *pend = save; } if (!value) value = ""; valuelen = strlen (value); if (valuelen <= pend - p) { memcpy (p, value, valuelen); p += valuelen; n = pend - p; if (n) memmove (p, p+n, strlen (p+n)+1); line = p; } else { char *src = result; char *dst; dst = xtrymalloc (strlen (src) + valuelen + 1); if (!dst) { xfree (result); return NULL; } n = p - src; memcpy (dst, src, n); memcpy (dst + n, value, valuelen); n += valuelen; strcpy (dst + n, pend); line = dst + n; xfree (result); result = dst; } } leave: return result; } diff --git a/common/sysutils.c b/common/sysutils.c index 1342d1016..21b6eefb2 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -1,1875 +1,1801 @@ /* 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 #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_PWD_H # include # include #endif /*HAVE_PWD_H*/ #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 #include "util.h" #include "i18n.h" #include "sysutils.h" #define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A')) /* The object used with our opendir functions. We need to define our * own so that we can properly handle Unicode on Windows. */ struct gnupg_dir_s { #ifdef HAVE_W32_SYSTEM _WDIR *dir; /* The system's DIR pointer. */ #else DIR *dir; /* The system's DIR pointer. */ #endif struct gnupg_dirent_s dirent; /* The current dirent. */ size_t namesize; /* If not 0 the allocated size of dirent.d_name. */ char name[256]; /* Only used if NAMESIZE is 0. */ }; /* Flag to tell whether special file names are enabled. See gpg.c for * an explanation of these file names. */ static int allow_special_filenames; #ifdef HAVE_W32_SYSTEM /* State of gnupg_inhibit_set_foregound_window. */ static int inhibit_set_foregound_window; #endif 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*/ /* Helper for gnupg_w32_set_errno. */ #ifdef HAVE_W32_SYSTEM static int map_w32_to_errno (DWORD w32_err) { switch (w32_err) { case 0: return 0; case ERROR_FILE_NOT_FOUND: return ENOENT; case ERROR_PATH_NOT_FOUND: return ENOENT; case ERROR_ACCESS_DENIED: return EPERM; /* ReactOS uses EACCES ("Permission denied") and * is likely right because they used an * undocumented function to associate the error * codes. However we have always used EPERM * ("Operation not permitted", e.g. function is * required to be called by root) and we better * stick to that to avoid surprising bugs. */ case ERROR_INVALID_HANDLE: return EBADF; case ERROR_INVALID_BLOCK: return ENOMEM; case ERROR_NOT_ENOUGH_MEMORY: return ENOMEM; case ERROR_NO_DATA: return EPIPE; case ERROR_ALREADY_EXISTS: return EEXIST; /* This mapping has been taken from reactOS. */ case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; case ERROR_ARENA_TRASHED: return ENOMEM; case ERROR_BAD_ENVIRONMENT: return E2BIG; case ERROR_BAD_FORMAT: return ENOEXEC; case ERROR_INVALID_DRIVE: return ENOENT; case ERROR_CURRENT_DIRECTORY: return EACCES; case ERROR_NOT_SAME_DEVICE: return EXDEV; case ERROR_NO_MORE_FILES: return ENOENT; case ERROR_WRITE_PROTECT: return EACCES; case ERROR_BAD_UNIT: return EACCES; case ERROR_NOT_READY: return EACCES; case ERROR_BAD_COMMAND: return EACCES; case ERROR_CRC: return EACCES; case ERROR_BAD_LENGTH: return EACCES; case ERROR_SEEK: return EACCES; case ERROR_NOT_DOS_DISK: return EACCES; case ERROR_SECTOR_NOT_FOUND: return EACCES; case ERROR_OUT_OF_PAPER: return EACCES; case ERROR_WRITE_FAULT: return EACCES; case ERROR_READ_FAULT: return EACCES; case ERROR_GEN_FAILURE: return EACCES; case ERROR_SHARING_VIOLATION: return EACCES; case ERROR_LOCK_VIOLATION: return EACCES; case ERROR_WRONG_DISK: return EACCES; case ERROR_SHARING_BUFFER_EXCEEDED: return EACCES; case ERROR_BAD_NETPATH: return ENOENT; case ERROR_NETWORK_ACCESS_DENIED: return EACCES; case ERROR_BAD_NET_NAME: return ENOENT; case ERROR_FILE_EXISTS: return EEXIST; case ERROR_CANNOT_MAKE: return EACCES; case ERROR_FAIL_I24: return EACCES; case ERROR_NO_PROC_SLOTS: return EAGAIN; case ERROR_DRIVE_LOCKED: return EACCES; case ERROR_BROKEN_PIPE: return EPIPE; case ERROR_DISK_FULL: return ENOSPC; case ERROR_INVALID_TARGET_HANDLE: return EBADF; case ERROR_WAIT_NO_CHILDREN: return ECHILD; case ERROR_CHILD_NOT_COMPLETE: return ECHILD; case ERROR_DIRECT_ACCESS_HANDLE: return EBADF; case ERROR_SEEK_ON_DEVICE: return EACCES; case ERROR_DIR_NOT_EMPTY: return ENOTEMPTY; case ERROR_NOT_LOCKED: return EACCES; case ERROR_BAD_PATHNAME: return ENOENT; case ERROR_MAX_THRDS_REACHED: return EAGAIN; case ERROR_LOCK_FAILED: return EACCES; case ERROR_INVALID_STARTING_CODESEG: return ENOEXEC; case ERROR_INVALID_STACKSEG: return ENOEXEC; case ERROR_INVALID_MODULETYPE: return ENOEXEC; case ERROR_INVALID_EXE_SIGNATURE: return ENOEXEC; case ERROR_EXE_MARKED_INVALID: return ENOEXEC; case ERROR_BAD_EXE_FORMAT: return ENOEXEC; case ERROR_ITERATED_DATA_EXCEEDS_64k: return ENOEXEC; case ERROR_INVALID_MINALLOCSIZE: return ENOEXEC; case ERROR_DYNLINK_FROM_INVALID_RING: return ENOEXEC; case ERROR_IOPL_NOT_ENABLED: return ENOEXEC; case ERROR_INVALID_SEGDPL: return ENOEXEC; case ERROR_AUTODATASEG_EXCEEDS_64k: return ENOEXEC; case ERROR_RING2SEG_MUST_BE_MOVABLE: return ENOEXEC; case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: return ENOEXEC; case ERROR_INFLOOP_IN_RELOC_CHAIN: return ENOEXEC; case ERROR_FILENAME_EXCED_RANGE: return ENOENT; case ERROR_NESTING_NOT_ALLOWED: return EAGAIN; case ERROR_NOT_ENOUGH_QUOTA: return ENOMEM; default: return EIO; } } #endif /*HAVE_W32_SYSTEM*/ /* Set ERRNO from the Windows error. EC may be -1 to use the last error. */ #ifdef HAVE_W32_SYSTEM void gnupg_w32_set_errno (int ec) { /* FIXME: Replace by gpgrt_w32_set_errno. */ if (ec == -1) ec = GetLastError (); _set_errno (map_w32_to_errno (ec)); } #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) +#if 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 ((intptr_t)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 +#ifdef 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) + if (!n || n > MAX_PATH || strlen (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 = buffer + strlen (buffer); 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 ((intptr_t)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 } /* Inhibit calls to AllowSetForegroundWindow on Windows. Calling this * with YES set to true calls to gnupg_allow_set_foregound_window are * shunted. */ void gnupg_inhibit_set_foregound_window (int yes) { #ifdef HAVE_W32_SYSTEM inhibit_set_foregound_window = yes; #else (void)yes; #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) +#if defined(HAVE_W32_SYSTEM) else if (inhibit_set_foregound_window) ; 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_W32_SYSTEM int rc; wchar_t *wfname; wfname = utf8_to_wchar (fname); if (!wfname) rc = 0; else { rc = DeleteFileW (wfname); if (!rc) gnupg_w32_set_errno (-1); xfree (wfname); } if (!rc) return -1; return 0; #else return remove (fname); #endif } /* Helper for gnupg_rename_file. */ #ifdef HAVE_W32_SYSTEM static int w32_rename (const char *oldname, const char *newname) { if (any8bitchar (oldname) || any8bitchar (newname)) { wchar_t *woldname, *wnewname; int ret; woldname = utf8_to_wchar (oldname); if (!woldname) return -1; wnewname = utf8_to_wchar (newname); if (!wnewname) { xfree (wnewname); return -1; } ret = _wrename (woldname, wnewname); xfree (wnewname); xfree (woldname); return ret; } else return rename (oldname, newname); } #endif /*HAVE_W32_SYSTEM*/ /* 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 (w32_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) { /* Note that gpgrt_mkdir also sets ERRNO in addition to returing an * gpg-error style error code. */ return gpgrt_mkdir (name, modestr); } /* A simple wrapper around chdir. NAME is expected to be utf8 * encoded. */ int gnupg_chdir (const char *name) { /* Note that gpgrt_chdir also sets ERRNO in addition to returning an * gpg-error style error code. */ return gpgrt_chdir (name); } /* A wrapper around rmdir. NAME is expected to be utf8 encoded. */ int gnupg_rmdir (const char *name) { #ifdef HAVE_W32_SYSTEM int rc; wchar_t *wfname; wfname = utf8_to_wchar (name); if (!wfname) rc = 0; else { rc = RemoveDirectoryW (wfname); if (!rc) gnupg_w32_set_errno (-1); xfree (wfname); } if (!rc) return -1; return 0; #else return rmdir (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 +#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*/ +#endif /*W32*/ -# ifdef HAVE_SETENV +#ifdef HAVE_SETENV return setenv (name, value, overwrite); -# else /*!HAVE_SETENV*/ +#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*/ +#endif /*!HAVE_SETENV*/ } int gnupg_unsetenv (const char *name) { -#ifdef HAVE_W32CE_SYSTEM - (void)name; - return 0; -#else /*!W32CE*/ -# ifdef HAVE_W32_SYSTEM +#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*/ +#endif /*W32*/ -# ifdef HAVE_UNSETENV +#ifdef HAVE_UNSETENV return unsetenv (name); -# else /*!HAVE_UNSETENV*/ +#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*/ +#endif /*!HAVE_UNSETENV*/ } /* Return the current working directory as a malloced string. Return NULL and sets ERRNO on error. */ char * gnupg_getcwd (void) { return gpgrt_getcwd (); } /* 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) { return gpgrt_access (name, mode); } /* 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 } /* A wrapper around opendir to handle Unicode file names under * Windows. This assumes the mingw toolchain. */ gnupg_dir_t gnupg_opendir (const char *name) { #ifdef HAVE_W32_SYSTEM _WDIR *dir; wchar_t *wname; #else DIR *dir; #endif gnupg_dir_t gdir; #ifdef HAVE_W32_SYSTEM /* Note: See gpgtar-create for an alternative implementation which * could be used here to avoid a mingw dependency. */ wname = utf8_to_wchar (name); if (!wname) return NULL; dir = _wopendir (wname); xfree (wname); #else dir = opendir (name); #endif if (!dir) return NULL; gdir = xtrymalloc (sizeof *gdir); if (!gdir) { int save_errno = errno; #ifdef HAVE_W32_SYSTEM _wclosedir (dir); #else closedir (dir); #endif gpg_err_set_errno (save_errno); return NULL; } gdir->dir = dir; gdir->namesize = 0; gdir->dirent.d_name = gdir->name; return gdir; } gnupg_dirent_t gnupg_readdir (gnupg_dir_t gdir) { #ifdef HAVE_W32_SYSTEM char *namebuffer = NULL; struct _wdirent *de; #else struct dirent *de; #endif size_t n; gnupg_dirent_t gde; const char *name; if (!gdir) { gpg_err_set_errno (EINVAL); return 0; } #ifdef HAVE_W32_SYSTEM de = _wreaddir (gdir->dir); if (!de) return NULL; namebuffer = wchar_to_utf8 (de->d_name); if (!namebuffer) return NULL; name = namebuffer; #else de = readdir (gdir->dir); if (!de) return NULL; name = de->d_name; #endif gde = &gdir->dirent; n = strlen (name); if (gdir->namesize) { /* Use allocated buffer. */ if (n+1 >= gdir->namesize || !gde->d_name) { gdir->namesize = n + 256; xfree (gde->d_name); gde->d_name = xtrymalloc (gdir->namesize); if (!gde->d_name) return NULL; /* ERRNO is already set. */ } strcpy (gde->d_name, name); } else if (n+1 >= sizeof (gdir->name)) { /* Switch to allocated buffer. */ gdir->namesize = n + 256; gde->d_name = xtrymalloc (gdir->namesize); if (!gde->d_name) return NULL; /* ERRNO is already set. */ strcpy (gde->d_name, name); } else { /* Use static buffer. */ gde->d_name = gdir->name; strcpy (gde->d_name, name); } #ifdef HAVE_W32_SYSTEM xfree (namebuffer); #endif return gde; } int gnupg_closedir (gnupg_dir_t gdir) { #ifdef HAVE_W32_SYSTEM _WDIR *dir; #else DIR *dir; #endif if (!gdir) return 0; dir = gdir->dir; if (gdir->namesize) xfree (gdir->dirent.d_name); xfree (gdir); #ifdef HAVE_W32_SYSTEM return _wclosedir (dir); #else return closedir (dir); #endif } /* Try to set an envvar. Print only a notice on error. */ #ifndef HAVE_W32_SYSTEM static void try_set_envvar (const char *name, const char *value, int silent) { if (gnupg_setenv (name, value, 1)) if (!silent) log_info ("error setting envvar %s to '%s': %s\n", name, value, gpg_strerror (my_error_from_syserror ())); } #endif /*!HAVE_W32_SYSTEM*/ /* Switch to USER which is either a name or an UID. This is a nop * under Windows. Note that in general it is only possible to switch * to another user id if the process is running under root. if silent * is set no diagnostics are printed. */ gpg_error_t gnupg_chuid (const char *user, int silent) { #ifdef HAVE_W32_SYSTEM (void)user; /* Not implemented for Windows - ignore. */ (void)silent; return 0; #elif HAVE_PWD_H /* A proper Unix */ unsigned long ul; struct passwd *pw; struct stat st; char *endp; gpg_error_t err; gpg_err_set_errno (0); ul = strtoul (user, &endp, 10); if (errno || endp == user || *endp) pw = getpwnam (user); /* Not a number; assume USER is a name. */ else pw = getpwuid ((uid_t)ul); if (!pw) { if (!silent) log_error ("user '%s' not found\n", user); return my_error (GPG_ERR_NOT_FOUND); } /* Try to set some envvars even if we are already that user. */ if (!stat (pw->pw_dir, &st)) try_set_envvar ("HOME", pw->pw_dir, silent); try_set_envvar ("USER", pw->pw_name, silent); try_set_envvar ("LOGNAME", pw->pw_name, silent); #ifdef _AIX try_set_envvar ("LOGIN", pw->pw_name, silent); #endif if (getuid () == pw->pw_uid) return 0; /* We are already this user. */ /* If we need to switch set PATH to a standard value and make sure * GNUPGHOME is not set. */ try_set_envvar ("PATH", "/usr/local/bin:/usr/bin:/bin", silent); if (gnupg_unsetenv ("GNUPGHOME")) if (!silent) log_info ("error unsetting envvar %s: %s\n", "GNUPGHOME", gpg_strerror (gpg_error_from_syserror ())); if (initgroups (pw->pw_name, pw->pw_gid)) { err = my_error_from_syserror (); if (!silent) log_error ("error setting supplementary groups for '%s': %s\n", pw->pw_name, gpg_strerror (err)); return err; } if (setuid (pw->pw_uid)) { err = my_error_from_syserror (); log_error ("error switching to user '%s': %s\n", pw->pw_name, gpg_strerror (err)); return err; } return 0; #else /*!HAVE_PWD_H */ if (!silent) log_info ("system is missing passwd querying functions\n"); return my_error (GPG_ERR_NOT_IMPLEMENTED); #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/t-dotlock.c b/common/t-dotlock.c index 48f2e1f67..994ef1be3 100644 --- a/common/t-dotlock.c +++ b/common/t-dotlock.c @@ -1,314 +1,308 @@ /* t-dotlock.c - Module test for dotlock.c * Copyright (C) 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* Note: This is a standalone test program which does not rely on any GnuPG helper files. However, it may also be build as part of the GnuPG build system. */ #ifdef HAVE_CONFIG_H # include #endif /* Some quick replacements for stuff we usually expect to be defined in config.h. Define HAVE_POSIX_SYSTEM for better readability. */ #if !defined (HAVE_DOSISH_SYSTEM) && defined(_WIN32) # define HAVE_DOSISH_SYSTEM 1 #endif #if !defined (HAVE_DOSISH_SYSTEM) && !defined (HAVE_POSIX_SYSTEM) # define HAVE_POSIX_SYSTEM 1 #endif #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # include "windows.h" #else #include #endif #include "dotlock.h" #ifdef HAVE_W32_SYSTEM #define DIM(v) (sizeof(v)/sizeof((v)[0])) const char * w32_strerror (int ec) { static char strerr[256]; if (ec == -1) ec = (int)GetLastError (); -#ifdef HAVE_W32CE_SYSTEM - /* There is only a wchar_t FormatMessage. It does not make much - sense to play the conversion game; we print only the code. */ - snprintf (strerr, sizeof strerr, "ec=%d", (int)GetLastError ()); -#else FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, ec, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), strerr, DIM (strerr)-1, NULL); { /* Strip the CR,LF - we want just the string. */ size_t n = strlen (strerr); if (n > 2 && strerr[n-2] == '\r' && strerr[n-1] == '\n' ) strerr[n-2] = 0; } -#endif return strerr; } static wchar_t * cp_to_wchar (const char *string, unsigned int codepage) { int n; size_t nbytes; wchar_t *result; n = MultiByteToWideChar (codepage, 0, string, -1, NULL, 0); if (n < 0) { return NULL; } nbytes = (size_t)(n+1) * sizeof(*result); if (nbytes / sizeof(*result) != (n+1)) { return NULL; } result = malloc (nbytes); if (!result) return NULL; n = MultiByteToWideChar (codepage, 0, string, -1, result, n); if (n < 0) { free (result); result = NULL; } return result; } wchar_t * utf8_to_wchar (const char *string) { return cp_to_wchar (string, CP_UTF8); } char * stpcpy(char *a,const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } static char * do_strconcat (const char *s1, va_list arg_ptr) { const char *argv[48]; size_t argc; size_t needed; char *buffer, *p; argc = 0; argv[argc++] = s1; needed = strlen (s1); while (((argv[argc] = va_arg (arg_ptr, const char *)))) { needed += strlen (argv[argc]); if (argc >= DIM (argv)-1) { return NULL; } argc++; } needed++; buffer = malloc (needed); if (buffer) { for (p = buffer, argc=0; argv[argc]; argc++) p = stpcpy (p, argv[argc]); } return buffer; } /* Concatenate the string S1 with all the following strings up to a NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ char * strconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = calloc (1, 1); else { va_start (arg_ptr, s1); result = do_strconcat (s1, arg_ptr); va_end (arg_ptr); } return result; } #endif /*HAVE_W32_SYSTEM*/ #include "dotlock.c" #define PGM "t-dotlock" #ifndef HAVE_W32_SYSTEM static volatile int ctrl_c_pending_flag; static void control_c_handler (int signo) { (void)signo; ctrl_c_pending_flag = 1; } #endif static int ctrl_c_pending (void) { #if HAVE_W32_SYSTEM static int count; return (++count > 9); #else return ctrl_c_pending_flag; #endif } static void die (const char *format, ...) { va_list arg_ptr; va_start (arg_ptr, format); fprintf (stderr, PGM "[%lu]: ", (unsigned long)getpid ()); vfprintf (stderr, format, arg_ptr); putc ('\n', stderr); va_end (arg_ptr); exit (1); } static void inf (const char *format, ...) { va_list arg_ptr; va_start (arg_ptr, format); fprintf (stderr, PGM "[%lu]: ", (unsigned long)getpid ()); vfprintf (stderr, format, arg_ptr); putc ('\n', stderr); va_end (arg_ptr); } static void lock_and_unlock (const char *fname) { dotlock_t h; unsigned long usec; h = dotlock_create (fname, 0); if (!h) die ("error creating lock file for '%s': %s", fname, strerror (errno)); inf ("lock created"); do { #ifdef HAVE_W32_SYSTEM usec = 10000; #else getrandom (&usec, sizeof (usec), 0); usec &= 0xffff; usec |= 0x0f00; #endif if (dotlock_take (h, -1)) die ("error taking lock"); inf ("lock taken"); usleep (usec); if (dotlock_release (h)) die ("error releasing lock"); inf ("lock released"); usleep (usec); } while (!ctrl_c_pending ()); dotlock_destroy (h); inf ("lock destroyed"); } int main (int argc, char **argv) { const char *fname; if (argc > 1 && !strcmp (argv[1], "--one-shot")) { ctrl_c_pending_flag = 1; argc--; } if (argc > 1) fname = argv[argc-1]; else { #ifdef HAVE_W32_SYSTEM fname = "t-dotâ’¶lock.tmp"; #else fname = "t-dotlock.tmp"; #endif } #ifndef HAVE_W32_SYSTEM { struct sigaction nact; nact.sa_handler = control_c_handler; nact.sa_flags = 0; sigaction (SIGINT, &nact, NULL); } #endif dotlock_create (NULL, 0); /* Initialize (optional). */ lock_and_unlock (fname); return 0; } /* Local Variables: compile-command: "cc -Wall -O2 -D_FILE_OFFSET_BITS=64 -o t-dotlock t-dotlock.c" End: */ diff --git a/common/t-stringhelp.c b/common/t-stringhelp.c index 4086f8c3d..6723d65b6 100644 --- a/common/t-stringhelp.c +++ b/common/t-stringhelp.c @@ -1,1326 +1,1321 @@ /* t-stringhelp.c - Regression tests for stringhelp.c * Copyright (C) 2007 Free Software Foundation, Inc. * 2015, 2021 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) */ #include #include #include #include #include #ifdef HAVE_PWD_H # include #endif #include #include #include #include "t-support.h" #include "sysutils.h" #include "stringhelp.h" static char *home_buffer; const char * gethome (void) { if (!home_buffer) { char *home = getenv("HOME"); if(home) home_buffer = xstrdup (home); #if defined(HAVE_GETPWUID) && defined(HAVE_PWD_H) else { struct passwd *pwd; pwd = getpwuid (getuid()); if (pwd) home_buffer = xstrdup (pwd->pw_dir); } #endif } return home_buffer; } static char * mygetcwd (void) { char *buffer; size_t size = 100; for (;;) { buffer = xmalloc (size+1); -#ifdef HAVE_W32CE_SYSTEM - strcpy (buffer, "/"); /* Always "/". */ - return buffer; -#else if (getcwd (buffer, size) == buffer) { #ifdef HAVE_W32_SYSTEM char *p; for (p = buffer; *p; p++) if (*p == '\\') *p = '/'; #endif return buffer; } xfree (buffer); if (errno != ERANGE) { fprintf (stderr,"error getting current cwd: %s\n", strerror (errno)); exit (2); } size *= 2; -#endif } } static void test_percent_escape (void) { char *result; static struct { const char *extra; const char *value; const char *expected; } tests[] = { { NULL, "", "" }, { NULL, "%", "%25" }, { NULL, "%%", "%25%25" }, { NULL, " %", " %25" }, { NULL, ":", "%3a" }, { NULL, " :", " %3a" }, { NULL, ": ", "%3a " }, { NULL, " : ", " %3a " }, { NULL, "::", "%3a%3a" }, { NULL, ": :", "%3a %3a" }, { NULL, "%:", "%25%3a" }, { NULL, ":%", "%3a%25" }, { "\\\n:", ":%", "%3a%25" }, { "\\\n:", "\\:%", "%5c%3a%25" }, { "\\\n:", "\n:%", "%0a%3a%25" }, { "\\\n:", "\xff:%", "\xff%3a%25" }, { "\\\n:", "\xfe:%", "\xfe%3a%25" }, { "\\\n:", "\x01:%", "\x01%3a%25" }, { "\x01", "\x01:%", "%01%3a%25" }, { "\xfe", "\xfe:%", "%fe%3a%25" }, { "\xfe", "\xff:%", "\xff%3a%25" }, { NULL, NULL, NULL } }; int testno; result = percent_escape (NULL, NULL); if (result) fail (0); for (testno=0; tests[testno].value; testno++) { result = percent_escape (tests[testno].value, tests[testno].extra); if (!result) fail (testno); else if (strcmp (result, tests[testno].expected)) fail (testno); xfree (result); } } static void test_compare_filenames (void) { struct { const char *a; const char *b; int result; } tests[] = { { "", "", 0 }, { "", "a", -1 }, { "a", "", 1 }, { "a", "a", 0 }, { "a", "aa", -1 }, { "aa", "a", 1 }, { "a", "b", -1 }, #ifdef HAVE_W32_SYSTEM { "a", "A", 0 }, { "A", "a", 0 }, { "foo/bar", "foo\\bar", 0 }, { "foo\\bar", "foo/bar", 0 }, { "foo\\", "foo/", 0 }, { "foo/", "foo\\", 0 }, #endif /*HAVE_W32_SYSTEM*/ { NULL, NULL, 0} }; int testno, result; for (testno=0; tests[testno].a; testno++) { result = compare_filenames (tests[testno].a, tests[testno].b); result = result < 0? -1 : result > 0? 1 : 0; if (result != tests[testno].result) fail (testno); } } static void test_strconcat (void) { char *out; out = strconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", NULL); if (!out) fail (0); else xfree (out); out = strconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", NULL); if (out) fail (0); else if (errno != EINVAL) fail (0); out = strconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL); if (out) fail (0); else if (errno != EINVAL) fail (0); xfree (out); #if __GNUC__ < 4 /* gcc 4.0 has a sentinel attribute. */ out = strconcat (NULL); if (!out || *out) fail (1); #endif out = strconcat (NULL, NULL); if (!out || *out) fail (1); xfree (out); out = strconcat ("", NULL); if (!out || *out) fail (1); xfree (out); out = strconcat ("", "", NULL); if (!out || *out) fail (2); xfree (out); out = strconcat ("a", "b", NULL); if (!out || strcmp (out, "ab")) fail (3); xfree (out); out = strconcat ("a", "b", "c", NULL); if (!out || strcmp (out, "abc")) fail (3); xfree (out); out = strconcat ("a", "b", "cc", NULL); if (!out || strcmp (out, "abcc")) fail (4); xfree (out); out = strconcat ("a1", "b1", "c1", NULL); if (!out || strcmp (out, "a1b1c1")) fail (4); xfree (out); out = strconcat ("", " long b ", "", "--even-longer--", NULL); if (!out || strcmp (out, " long b --even-longer--")) fail (5); xfree (out); out = strconcat ("", " long b ", "", "--even-longer--", NULL); if (!out || strcmp (out, " long b --even-longer--")) fail (5); xfree (out); } static void test_xstrconcat (void) { char *out; out = xstrconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", NULL); if (!out) fail (0); xfree (out); #if __GNUC__ < 4 /* gcc 4.0 has a sentinel attribute. */ out = xstrconcat (NULL); if (!out) fail (1); #endif out = xstrconcat (NULL, NULL); if (!out) fail (1); xfree (out); out = xstrconcat ("", NULL); if (!out || *out) fail (1); xfree (out); out = xstrconcat ("", "", NULL); if (!out || *out) fail (2); xfree (out); out = xstrconcat ("a", "b", NULL); if (!out || strcmp (out, "ab")) fail (3); xfree (out); out = xstrconcat ("a", "b", "c", NULL); if (!out || strcmp (out, "abc")) fail (3); xfree (out); out = xstrconcat ("a", "b", "cc", NULL); if (!out || strcmp (out, "abcc")) fail (4); xfree (out); out = xstrconcat ("a1", "b1", "c1", NULL); if (!out || strcmp (out, "a1b1c1")) fail (4); xfree (out); out = xstrconcat ("", " long b ", "", "--even-longer--", NULL); if (!out || strcmp (out, " long b --even-longer--")) fail (5); xfree (out); out = xstrconcat ("", " long b ", "", "--even-longer--", NULL); if (!out || strcmp (out, " long b --even-longer--")) fail (5); xfree (out); } static void test_make_filename_try (void) { char *out; const char *home = gethome (); size_t homelen = home? strlen (home):0; out = make_filename_try ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", NULL); if (out) fail (0); else if (errno != EINVAL) fail (0); xfree (out); out = make_filename_try ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", NULL); if (out) fail (0); else if (errno != EINVAL) fail (0); xfree (out); out = make_filename_try ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", NULL); if (!out || strcmp (out, "1/2/3/4/5/6/7/8/9/10/" "1/2/3/4/5/6/7/8/9/10/" "1/2/3/4/5/6/7/8/9/10/" "1/2")) fail (0); xfree (out); out = make_filename_try ("foo", "~/bar", "baz/cde", NULL); if (!out || strcmp (out, "foo/~/bar/baz/cde")) fail (1); xfree (out); out = make_filename_try ("foo", "~/bar", "baz/cde/", NULL); if (!out || strcmp (out, "foo/~/bar/baz/cde/")) fail (1); xfree (out); out = make_filename_try ("/foo", "~/bar", "baz/cde/", NULL); if (!out || strcmp (out, "/foo/~/bar/baz/cde/")) fail (1); xfree (out); out = make_filename_try ("//foo", "~/bar", "baz/cde/", NULL); if (!out || strcmp (out, "//foo/~/bar/baz/cde/")) fail (1); xfree (out); out = make_filename_try ("", "~/bar", "baz/cde", NULL); if (!out || strcmp (out, "/~/bar/baz/cde")) fail (1); xfree (out); out = make_filename_try ("~/foo", "bar", NULL); if (!out) fail (2); else if (home) { if (strlen (out) < homelen + 7) fail (2); else if (strncmp (out, home, homelen)) fail (2); else if (strcmp (out+homelen, "/foo/bar")) fail (2); } else { if (strcmp (out, "~/foo/bar")) fail (2); } xfree (out); out = make_filename_try ("~", "bar", NULL); if (!out) fail (2); else if (home) { if (strlen (out) < homelen + 3) fail (2); else if (strncmp (out, home, homelen)) fail (2); else if (strcmp (out+homelen, "/bar")) fail (2); } else { if (strcmp (out, "~/bar")) fail (2); } xfree (out); } static void test_make_absfilename_try (void) { char *out; char *cwd = mygetcwd (); size_t cwdlen = strlen (cwd); out = make_absfilename_try ("foo", "bar", NULL); if (!out) fail (0); else if (strlen (out) < cwdlen + 7) fail (0); else if (strncmp (out, cwd, cwdlen)) fail (0); else if (strcmp (out+cwdlen, "/foo/bar")) fail (0); xfree (out); out = make_absfilename_try ("./foo", NULL); if (!out) fail (1); else if (strlen (out) < cwdlen + 5) fail (1); else if (strncmp (out, cwd, cwdlen)) fail (1); else if (strcmp (out+cwdlen, "/./foo")) fail (1); xfree (out); out = make_absfilename_try (".", NULL); if (!out) fail (2); else if (strlen (out) < cwdlen) fail (2); else if (strncmp (out, cwd, cwdlen)) fail (2); else if (strcmp (out+cwdlen, "")) fail (2); xfree (out); xfree (cwd); } static void test_strsplit (void) { struct { const char *s; char delim; char replacement; const char *fields_expected[10]; } tv[] = { { "a:bc:cde:fghi:jklmn::foo:", ':', '\0', { "a", "bc", "cde", "fghi", "jklmn", "", "foo", "", NULL } }, { ",a,bc,,def,", ',', '!', { "!a!bc!!def!", "a!bc!!def!", "bc!!def!", "!def!", "def!", "", NULL } }, { "", ':', ',', { "", NULL } } }; int tidx; for (tidx = 0; tidx < DIM(tv); tidx++) { char *s2; int field_count; char **fields; int field_count_expected; int i; /* Count the fields. */ for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; /* We need to copy s since strsplit modifies it in place. */ s2 = xstrdup (tv[tidx].s); fields = strsplit (s2, tv[tidx].delim, tv[tidx].replacement, &field_count); if (field_count != field_count_expected) fail (tidx * 1000); for (i = 0; i < field_count_expected; i ++) if (strcmp (tv[tidx].fields_expected[i], fields[i]) != 0) { printf ("For field %d, expected '%s', but got '%s'\n", i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } xfree (fields); xfree (s2); } } static void test_strtokenize (void) { struct { const char *s; const char *delim; const char *fields_expected[10]; } tv[] = { { "", ":", { "", NULL } }, { "a", ":", { "a", NULL } }, { ":", ":", { "", "", NULL } }, { "::", ":", { "", "", "", NULL } }, { "a:b:c", ":", { "a", "b", "c", NULL } }, { "a:b:", ":", { "a", "b", "", NULL } }, { "a:b", ":", { "a", "b", NULL } }, { "aa:b:cd", ":", { "aa", "b", "cd", NULL } }, { "aa::b:cd", ":", { "aa", "", "b", "cd", NULL } }, { "::b:cd", ":", { "", "", "b", "cd", NULL } }, { "aa: : b:cd ", ":", { "aa", "", "b", "cd", NULL } }, { " aa: : b: cd ", ":", { "aa", "", "b", "cd", NULL } }, { " ", ":", { "", NULL } }, { " :", ":", { "", "", NULL } }, { " : ", ":", { "", "", NULL } }, { ": ", ":", { "", "", NULL } }, { ": x ", ":", { "", "x", NULL } }, { "a:bc:cde:fghi:jklmn::foo:", ":", { "a", "bc", "cde", "fghi", "jklmn", "", "foo", "", NULL } }, { ",a,bc,,def,", ",", { "", "a", "bc", "", "def", "", NULL } }, { " a ", " ", { "", "a", "", NULL } }, { " ", " ", { "", "", NULL } }, { "", " ", { "", NULL } } }; int tidx; for (tidx = 0; tidx < DIM(tv); tidx++) { char **fields; int field_count; int field_count_expected; int i; for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; fields = strtokenize (tv[tidx].s, tv[tidx].delim); if (!fields) fail (tidx * 1000); else { for (field_count = 0; fields[field_count]; field_count++) ; if (field_count != field_count_expected) fail (tidx * 1000); else { for (i = 0; i < field_count_expected; i++) if (strcmp (tv[tidx].fields_expected[i], fields[i])) { printf ("For field %d, expected '%s', but got '%s'\n", i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } } } xfree (fields); } } static void test_strtokenize_nt (void) { struct { const char *s; const char *delim; const char *fields_expected[10]; } tv[] = { { "", ":", { "", NULL } }, { "a", ":", { "a", NULL } }, { ":", ":", { "", "", NULL } }, { "::", ":", { "", "", "", NULL } }, { "a:b:c", ":", { "a", "b", "c", NULL } }, { "a:b:", ":", { "a", "b", "", NULL } }, { "a:b", ":", { "a", "b", NULL } }, { "aa:b:cd", ":", { "aa", "b", "cd", NULL } }, { "aa::b:cd", ":", { "aa", "", "b", "cd", NULL } }, { "::b:cd", ":", { "", "", "b", "cd", NULL } }, { "aa: : b:cd ", ":", { "aa", " ", " b", "cd ", NULL } }, { " aa: : b: cd ", ":", { " aa", " ", " b", " cd ", NULL } }, { " ", ":", { " ", NULL } }, { " :", ":", { " ", "", NULL } }, { " : ", ":", { " ", " ", NULL } }, { ": ", ":", { "", " ", NULL } }, { ": x ", ":", { "", " x ", NULL } }, { "a:bc:cde:fghi:jklmn::foo:", ":", { "a", "bc", "cde", "fghi", "jklmn", "", "foo", "", NULL } }, { ",a,bc,,def,", ",", { "", "a", "bc", "", "def", "", NULL } }, { " a ", " ", { "", "a", "", NULL } }, { " ", " ", { "", "", NULL } }, { "", " ", { "", NULL } } }; int tidx; for (tidx = 0; tidx < DIM(tv); tidx++) { char **fields; int field_count; int field_count_expected; int i; for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; fields = strtokenize_nt (tv[tidx].s, tv[tidx].delim); if (!fields) fail (tidx * 1000); else { for (field_count = 0; fields[field_count]; field_count++) ; if (field_count != field_count_expected) fail (tidx * 1000); else { for (i = 0; i < field_count_expected; i++) if (strcmp (tv[tidx].fields_expected[i], fields[i])) { printf ("For field %d, expected '%s', but got '%s'\n", i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } } } xfree (fields); } } static void test_split_fields (void) { struct { const char *s; int nfields; const char *fields_expected[10]; } tv[] = { { "a bc cde fghi jklmn foo ", 6, { "a", "bc", "cde", "fghi", "jklmn", "foo", NULL } }, { " a bc def ", 2, { "a", "bc", "def", NULL } }, { " a bc def ", 3, { "a", "bc", "def", NULL } }, { " a bc def ", 4, { "a", "bc", "def", NULL } }, { "", 0, { NULL } } }; int tidx; const char *fields[10]; int field_count_expected, nfields, field_count, i; char *s2; for (tidx = 0; tidx < DIM(tv); tidx++) { nfields = tv[tidx].nfields; log_assert (nfields <= DIM (fields)); /* Count the fields. */ for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; if (field_count_expected > nfields) field_count_expected = nfields; /* We need to copy s since split_fields modifies in place. */ s2 = xstrdup (tv[tidx].s); field_count = split_fields (s2, fields, nfields); if (field_count != field_count_expected) { printf ("%s: tidx %d: expected %d, got %d\n", __func__, tidx, field_count_expected, field_count); fail (tidx * 1000); } else { for (i = 0; i < field_count_expected; i ++) if (strcmp (tv[tidx].fields_expected[i], fields[i])) { printf ("%s: tidx %d, field %d: expected '%s', got '%s'\n", __func__, tidx, i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } } xfree (s2); } } static void test_split_fields_colon (void) { struct { const char *s; int nfields; const char *fields_expected[10]; } tv[] = { { "a:bc:cde:fghi:jklmn: foo ", 6, { "a", "bc", "cde", "fghi", "jklmn", " foo ", NULL } }, { " a:bc: def ", 2, { " a", "bc", NULL } }, { " a:bc :def ", 3, { " a", "bc ", "def ", NULL } }, { " a:bc: def ", 4, { " a", "bc", " def ", NULL } }, { "", 0, { NULL } } }; int tidx; const char *fields[10]; int field_count_expected, nfields, field_count, i; char *s2; for (tidx = 0; tidx < DIM(tv); tidx++) { nfields = tv[tidx].nfields; log_assert (nfields <= DIM (fields)); /* Count the fields. */ for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; if (field_count_expected > nfields) field_count_expected = nfields; /* We need to copy s since split_fields modifies in place. */ s2 = xstrdup (tv[tidx].s); field_count = split_fields_colon (s2, fields, nfields); if (field_count != field_count_expected) { printf ("%s: tidx %d: expected %d, got %d\n", __func__, tidx, field_count_expected, field_count); fail (tidx * 1000); } else { for (i = 0; i < field_count_expected; i ++) if (strcmp (tv[tidx].fields_expected[i], fields[i])) { printf ("%s: tidx %d, field %d: expected '%s', got '%s'\n", __func__, tidx, i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } } xfree (s2); } } static char * stresc (char *s) { char *p; int l = strlen (s) + 1; for (p = s; *p; p ++) if (*p == '\n') l ++; p = xmalloc (l); for (l = 0; *s; s ++, l ++) { if (*s == ' ') p[l] = '_'; else if (*p == '\n') { p[l ++] = '\\'; p[l ++] = 'n'; p[l] = '\n'; } else p[l] = *s; } p[l] = *s; return p; } static void test_format_text (void) { struct test { int target_cols, max_cols; char *input; char *expected; }; struct test tests[] = { { 10, 12, "", "", }, { 10, 12, " ", "", }, { 10, 12, " ", "", }, { 10, 12, " \n ", " \n", }, { 10, 12, " \n \n ", " \n \n", }, { 10, 12, "0123456789 0123456789 0", "0123456789\n0123456789\n0", }, { 10, 12, " 0123456789 0123456789 0 ", " 0123456789\n0123456789\n0", }, { 10, 12, "01 34 67 90 23 56 89 12 45 67 89 1", "01 34 67\n90 23 56\n89 12 45\n67 89 1" }, { 10, 12, "01 34 67 90 23 56 89 12 45 67 89 1", "01 34 67\n90 23 56\n89 12 45\n67 89 1" }, { 72, 80, "Warning: if you think you've seen more than 10 messages " "signed by this key, then this key might be a forgery! " "Carefully examine the email address for small variations " "(e.g., additional white space). If the key is suspect, " "then use 'gpg --tofu-policy bad \"FINGERPRINT\"' to mark it as being bad.\n", "Warning: if you think you've seen more than 10 messages signed by this\n" "key, then this key might be a forgery! Carefully examine the email\n" "address for small variations (e.g., additional white space). If the key\n" "is suspect, then use 'gpg --tofu-policy bad \"FINGERPRINT\"' to mark it as\n" "being bad.\n" }, { 72, 80, "Normally, there is only a single key associated with an email " "address. 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 key, you should talk to or " "call the person to make sure this new key is legitimate.", "Normally, there is only a single key associated with an email " "address.\nHowever, people sometimes generate a new key if " "their key is too old or\nthey think it might be compromised. " "Alternatively, a new key may indicate\na man-in-the-middle " "attack! Before accepting this key, you should talk\nto or " "call the person to make sure this new key is legitimate.", } }; int i; int failed = 0; for (i = 0; i < sizeof (tests) / sizeof (tests[0]); i ++) { struct test *test = &tests[i]; char *result = format_text (test->input, test->target_cols, test->max_cols); if (!result) { fail (1); exit (2); } if (strcmp (result, test->expected) != 0) { printf ("%s: Test #%d failed.\nExpected: '%s'\nResult: '%s'\n", __func__, i + 1, stresc (test->expected), stresc (result)); failed ++; } xfree (result); } if (failed) fail(0); } static void test_compare_version_strings (void) { struct { const char *a; const char *b; int okay; } tests[] = { { "1.0.0", "1.0.0", 0 }, { "1.0.0-", "1.0.0", 1 }, { "1.0.0-1", "1.0.0", 1 }, { "1.0.0.1", "1.0.0", 1 }, { "1.0.0", "1.0.1", -1 }, { "1.0.0-", "1.0.1", -1 }, { "1.0.0-1", "1.0.1", -1 }, { "1.0.0.1", "1.0.1", -1 }, { "1.0.0", "1.1.0", -1 }, { "1.0.0-", "1.1.0", -1 }, { "1.0.0-1", "1.1.0", -1 }, { "1.0.0.1", "1.1.0", -1 }, { "1.0.0", "1.0.0-", -1 }, { "1.0.0", "1.0.0-1", -1 }, { "1.0.0", "1.0.0.1", -1 }, { "1.1.0", "1.0.0", 1 }, { "1.1.1", "1.1.0", 1 }, { "1.1.2", "1.1.2", 0 }, { "1.1.2", "1.0.2", 1 }, { "1.1.2", "0.0.2", 1 }, { "1.1.2", "1.1.3", -1 }, { "0.99.1", "0.9.9", 1 }, { "0.9.1", "0.91.0", -1 }, { "1.5.3", "1.5", 1 }, { "1.5.0", "1.5", 0 }, { "1.4.99", "1.5", -1 }, { "1.5", "1.4.99", 1 }, { "1.5", "1.5.0", 0 }, { "1.5", "1.5.1", -1 }, { "1.5.3-x17", "1.5-23", 1 }, { "1.5.3a", "1.5.3", 1 }, { "1.5.3a", "1.5.3b", -1 }, { "3.1.4-ab", "3.1.4-ab", 0 }, { "3.1.4-ab", "3.1.4-ac", -1 }, { "3.1.4-ac", "3.1.4-ab", 1 }, { "3.1.4-ab", "3.1.4-abb", -1 }, { "3.1.4-abb", "3.1.4-ab", 1 }, { "", "", INT_MIN }, { NULL, "", INT_MIN }, { "1.2.3", "", INT_MIN }, { "1.2.3", "2", INT_MIN }, /* Test cases for validity of A. */ { "", NULL, INT_MIN }, { "1", NULL, INT_MIN }, { "1.", NULL, 0 }, { "1.0", NULL, 0 }, { "1.0.", NULL, 0 }, { "a1.2", NULL, INT_MIN }, { NULL, NULL, INT_MIN } }; int idx; int res; for (idx=0; idx < DIM(tests); idx++) { res = compare_version_strings (tests[idx].a, tests[idx].b); /* printf ("test %d: '%s' '%s' %d -> %d\n", */ /* idx, tests[idx].a, tests[idx].b, tests[idx].okay, res); */ if (res != tests[idx].okay) fail (idx); } } static void test_substitute_envvars (void) { struct { const char *name; const char *value; } envvars[] = { { "HOME", "/home/joe" }, { "AVAR", "avar" }, { "AVAR1", "avarx" }, { "AVAR2", "avarxy" }, { "AVAR3", "avarxyz" }, { "AVAR0", "ava" }, { "MY_VAR", "my_vars_value" }, { "STRANGE{X}VAR", "strange{x}vars-value" }, { "ZERO", "" } }; struct { const char *string; const char *result; } tests[] = { { "foo bar", "foo bar" }, { "foo $HOME", "foo /home/joe" }, { "foo $HOME ", "foo /home/joe " }, { "foo $HOME$$", "foo /home/joe$" }, { "foo ${HOME}/.ssh", "foo /home/joe/.ssh" }, { "foo $HOME/.ssh", "foo /home/joe/.ssh" }, { "foo $HOME_/.ssh", "foo /.ssh" }, { "foo $HOME/.ssh/$MY_VAR:1", "foo /home/joe/.ssh/my_vars_value:1" }, { "foo $HOME${MY_VAR}:1", "foo /home/joemy_vars_value:1" }, { "${STRANGE{X}VAR}-bla", "strange{x}vars-value-bla" }, { "${STRANGE{X}{VAR}-bla", /* missing "}" */ "${STRANGE{X}{VAR}-bla" }, { "zero->$ZERO<-", "zero-><-" }, { "->$AVAR.$AVAR1.$AVAR2.$AVAR3.$AVAR0<-", "->avar.avarx.avarxy.avarxyz.ava<-" }, { "", "" } }; int idx; char *res; for (idx=0; idx < DIM(envvars); idx++) if (gnupg_setenv (envvars[idx].name, envvars[idx].value, 1)) { fprintf (stderr,"error setting envvar '%s' to '%s': %s\n", envvars[idx].name, envvars[idx].value, strerror (errno)); exit (2); } for (idx=0; idx < DIM(tests); idx++) { res = substitute_envvars (tests[idx].string); if (!res) { fprintf (stderr,"error substituting '%s' (test %d): %s\n", tests[idx].string, idx, strerror (errno)); exit (2); } if (strcmp (res, tests[idx].result)) { fprintf (stderr, "substituted '%s'\n", tests[idx].string); fprintf (stderr, " wanted '%s'\n", tests[idx].result); fprintf (stderr, " got '%s'\n", res); fail (idx); } xfree (res); } } int main (int argc, char **argv) { (void)argc; (void)argv; test_percent_escape (); test_compare_filenames (); test_strconcat (); test_xstrconcat (); test_make_filename_try (); test_make_absfilename_try (); test_strsplit (); test_strtokenize (); test_strtokenize_nt (); test_split_fields (); test_split_fields_colon (); test_compare_version_strings (); test_format_text (); test_substitute_envvars (); xfree (home_buffer); return !!errcount; } diff --git a/common/t-sysutils.c b/common/t-sysutils.c index 79f8385ac..9908c16cd 100644 --- a/common/t-sysutils.c +++ b/common/t-sysutils.c @@ -1,89 +1,85 @@ /* t-sysutils.c - Module test for sysutils.c * Copyright (C) 2007 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 "util.h" #include "sysutils.h" -#ifdef HAVE_W32CE_SYSTEM -# define rewind(f) do { fseek (f, 0, SEEK_SET); clearerr (f); } while (0) -#endif - #define pass() do { ; } while(0) #define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\ __FILE__,__LINE__, (a)); \ errcount++; \ } while(0) static int verbose; static int errcount; static void test_gnupg_tmpfile (void) { FILE *fparr[10]; int fparridx; int idx; FILE *fp; char buffer[100]; #define ASTRING "fooooooooooooooo\n" /* Needs to be shorter than BUFFER. */ for (fparridx=0; fparridx < DIM (fparr); fparridx++) { fp = gnupg_tmpfile (); fparr[fparridx] = fp; if (!fp) fail (fparridx); else { fputs ( ASTRING, fp); rewind (fp); if (!fgets (buffer, sizeof (buffer), fp)) fail (fparridx); if (strcmp (buffer, ASTRING)) fail (fparridx); if (fgets (buffer, sizeof (buffer), fp)) fail (fparridx); } } for (idx=0; idx < fparridx; idx++) { if (fparr[idx]) fclose (fparr[idx]); } } int main (int argc, char **argv) { if (argc > 1 && !strcmp (argv[1], "--verbose")) verbose = 1; test_gnupg_tmpfile (); /* Fixme: Add tests for setenv and unsetenv. */ return !!errcount; } diff --git a/common/utf8conv.c b/common/utf8conv.c index 1f01841c7..8b66e7f41 100644 --- a/common/utf8conv.c +++ b/common/utf8conv.c @@ -1,861 +1,857 @@ /* utf8conf.c - UTF8 character set conversion * Copyright (C) 1994, 1998, 1999, 2000, 2001, 2003, 2006, * 2008, 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #include #include #include #include #include #ifdef HAVE_LANGINFO_CODESET #include #endif #include #if HAVE_W32_SYSTEM # /* Tell libgpg-error to provide the iconv macros. */ # define GPGRT_ENABLE_W32_ICONV_MACROS 1 #elif HAVE_ANDROID_SYSTEM # /* No iconv support. */ #else # include #endif #include "util.h" #include "common-defs.h" #include "i18n.h" #include "stringhelp.h" #include "utf8conv.h" #ifdef HAVE_W32_SYSTEM #include #endif #ifndef MB_LEN_MAX #define MB_LEN_MAX 16 #endif static const char *active_charset_name = "iso-8859-1"; static int no_translation; /* Set to true if we let simply pass through. */ static int use_iconv; /* iconv conversion functions required. */ #ifdef HAVE_ANDROID_SYSTEM /* Fake stuff to get things building. */ typedef void *iconv_t; #define ICONV_CONST static iconv_t iconv_open (const char *tocode, const char *fromcode) { (void)tocode; (void)fromcode; return (iconv_t)(-1); } static size_t iconv (iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft) { (void)cd; (void)inbuf; (void)inbytesleft; (void)outbuf; (void)outbytesleft; return (size_t)(0); } static int iconv_close (iconv_t cd) { (void)cd; return 0; } #endif /*HAVE_ANDROID_SYSTEM*/ /* Error handler for iconv failures. This is needed to not clutter the output with repeated diagnostics about a missing conversion. */ static void handle_iconv_error (const char *to, const char *from, int use_fallback) { if (errno == EINVAL) { static int shown1, shown2; int x; if (to && !strcmp (to, "utf-8")) { x = shown1; shown1 = 1; } else { x = shown2; shown2 = 1; } if (!x) log_info (_("conversion from '%s' to '%s' not available\n"), from, to); } else { static int shown; if (!shown) log_info (_("iconv_open failed: %s\n"), strerror (errno)); shown = 1; } if (use_fallback) { /* To avoid further error messages we fallback to UTF-8 for the native encoding. Nowadays this seems to be the best bet in case of errors from iconv or nl_langinfo. */ active_charset_name = "utf-8"; no_translation = 1; use_iconv = 0; } } int set_native_charset (const char *newset) { const char *full_newset; if (!newset) { #ifdef HAVE_ANDROID_SYSTEM newset = "utf-8"; #elif defined HAVE_W32_SYSTEM static char codepage[30]; unsigned int cpno; const char *aliases; /* We are a console program thus we need to use the GetConsoleOutputCP function and not the GetACP which would give the codepage for a GUI program. Note this is not a bulletproof detection because GetConsoleCP might return a different one for console input. Not sure how to cope with that. If the console Code page is not known we fall back to the system code page. */ -#ifndef HAVE_W32CE_SYSTEM cpno = GetConsoleOutputCP (); if (!cpno) -#endif cpno = GetACP (); sprintf (codepage, "CP%u", cpno ); /* Resolve alias. We use a long string string and not the usual array to optimize if the code is taken to a DSO. Taken from libiconv 1.9.2. */ newset = codepage; for (aliases = ("CP936" "\0" "GBK" "\0" "CP1361" "\0" "JOHAB" "\0" "CP20127" "\0" "ASCII" "\0" "CP20866" "\0" "KOI8-R" "\0" "CP21866" "\0" "KOI8-RU" "\0" "CP28591" "\0" "ISO-8859-1" "\0" "CP28592" "\0" "ISO-8859-2" "\0" "CP28593" "\0" "ISO-8859-3" "\0" "CP28594" "\0" "ISO-8859-4" "\0" "CP28595" "\0" "ISO-8859-5" "\0" "CP28596" "\0" "ISO-8859-6" "\0" "CP28597" "\0" "ISO-8859-7" "\0" "CP28598" "\0" "ISO-8859-8" "\0" "CP28599" "\0" "ISO-8859-9" "\0" "CP28605" "\0" "ISO-8859-15" "\0" "CP65001" "\0" "UTF-8" "\0"); *aliases; aliases += strlen (aliases) + 1, aliases += strlen (aliases) + 1) { if (!strcmp (codepage, aliases) ||(*aliases == '*' && !aliases[1])) { newset = aliases + strlen (aliases) + 1; break; } } #else /*!HAVE_W32_SYSTEM && !HAVE_ANDROID_SYSTEM*/ #ifdef HAVE_LANGINFO_CODESET newset = nl_langinfo (CODESET); #else /*!HAVE_LANGINFO_CODESET*/ /* Try to get the used charset from environment variables. */ static char codepage[30]; const char *lc, *dot, *mod; strcpy (codepage, "iso-8859-1"); lc = getenv ("LC_ALL"); if (!lc || !*lc) { lc = getenv ("LC_CTYPE"); if (!lc || !*lc) lc = getenv ("LANG"); } if (lc && *lc) { dot = strchr (lc, '.'); if (dot) { mod = strchr (++dot, '@'); if (!mod) mod = dot + strlen (dot); if (mod - dot < sizeof codepage && dot != mod) { memcpy (codepage, dot, mod - dot); codepage [mod - dot] = 0; } } } newset = codepage; #endif /*!HAVE_LANGINFO_CODESET*/ #endif /*!HAVE_W32_SYSTEM && !HAVE_ANDROID_SYSTEM*/ } full_newset = newset; if (strlen (newset) > 3 && !ascii_memcasecmp (newset, "iso", 3)) { newset += 3; if (*newset == '-' || *newset == '_') newset++; } /* Note that we silently assume that plain ASCII is actually meant as Latin-1. This makes sense because many Unix system don't have their locale set up properly and thus would get annoying error messages and we have to handle all the "bug" reports. Latin-1 has traditionally been the character set used for 8 bit characters on Unix systems. */ if ( !*newset || !ascii_strcasecmp (newset, "8859-1" ) || !ascii_strcasecmp (newset, "646" ) || !ascii_strcasecmp (newset, "ASCII" ) || !ascii_strcasecmp (newset, "ANSI_X3.4-1968" ) ) { active_charset_name = "iso-8859-1"; no_translation = 0; use_iconv = 0; } else if ( !ascii_strcasecmp (newset, "utf8" ) || !ascii_strcasecmp(newset, "utf-8") ) { active_charset_name = "utf-8"; no_translation = 1; use_iconv = 0; } else { iconv_t cd; cd = iconv_open (full_newset, "utf-8"); if (cd == (iconv_t)-1) { handle_iconv_error (full_newset, "utf-8", 0); return -1; } iconv_close (cd); cd = iconv_open ("utf-8", full_newset); if (cd == (iconv_t)-1) { handle_iconv_error ("utf-8", full_newset, 0); return -1; } iconv_close (cd); active_charset_name = full_newset; no_translation = 0; use_iconv = 1; } return 0; } const char * get_native_charset () { return active_charset_name; } /* Return true if the native charset is utf-8. */ int is_native_utf8 (void) { return no_translation; } /* Convert string, which is in native encoding to UTF8 and return a new allocated UTF-8 string. This function terminates the process on memory shortage. */ char * native_to_utf8 (const char *orig_string) { const unsigned char *string = (const unsigned char *)orig_string; const unsigned char *s; char *buffer; unsigned char *p; size_t length = 0; if (no_translation) { /* Already utf-8 encoded. */ buffer = xstrdup (orig_string); } else if (!use_iconv) { /* For Latin-1 we can avoid the iconv overhead. */ for (s = string; *s; s++) { length++; if (*s & 0x80) length++; } buffer = xmalloc (length + 1); for (p = (unsigned char *)buffer, s = string; *s; s++) { if ( (*s & 0x80 )) { *p++ = 0xc0 | ((*s >> 6) & 3); *p++ = 0x80 | (*s & 0x3f); } else *p++ = *s; } *p = 0; } else { /* Need to use iconv. */ iconv_t cd; const char *inptr; char *outptr; size_t inbytes, outbytes; cd = iconv_open ("utf-8", active_charset_name); if (cd == (iconv_t)-1) { handle_iconv_error ("utf-8", active_charset_name, 1); return native_to_utf8 (string); } for (s=string; *s; s++ ) { length++; if ((*s & 0x80)) length += 5; /* We may need up to 6 bytes for the utf8 output. */ } buffer = xmalloc (length + 1); inptr = string; inbytes = strlen (string); outptr = buffer; outbytes = length; if ( iconv (cd, (ICONV_CONST char **)&inptr, &inbytes, &outptr, &outbytes) == (size_t)-1) { static int shown; if (!shown) log_info (_("conversion from '%s' to '%s' failed: %s\n"), active_charset_name, "utf-8", strerror (errno)); shown = 1; /* We don't do any conversion at all but use the strings as is. */ strcpy (buffer, string); } else /* Success. */ { *outptr = 0; /* We could realloc the buffer now but I doubt that it makes much sense given that it will get freed anyway soon after. */ } iconv_close (cd); } return buffer; } static char * do_utf8_to_native (const char *string, size_t length, int delim, int with_iconv) { int nleft; int i; unsigned char encbuf[8]; int encidx; const unsigned char *s; size_t n; char *buffer = NULL; char *p = NULL; unsigned long val = 0; size_t slen; int resync = 0; /* First pass (p==NULL): count the extended utf-8 characters. */ /* Second pass (p!=NULL): create string. */ for (;;) { for (slen = length, nleft = encidx = 0, n = 0, s = (const unsigned char *)string; slen; s++, slen--) { if (resync) { if (!(*s < 128 || (*s >= 0xc0 && *s <= 0xfd))) { /* Still invalid. */ if (p) { sprintf (p, "\\x%02x", *s); p += 4; } n += 4; continue; } resync = 0; } if (!nleft) { if (!(*s & 0x80)) { /* Plain ascii. */ if ( delim != -1 && (*s < 0x20 || *s == 0x7f || *s == delim || (delim && *s == '\\'))) { n++; if (p) *p++ = '\\'; switch (*s) { case '\n': n++; if ( p ) *p++ = 'n'; break; case '\r': n++; if ( p ) *p++ = 'r'; break; case '\f': n++; if ( p ) *p++ = 'f'; break; case '\v': n++; if ( p ) *p++ = 'v'; break; case '\b': n++; if ( p ) *p++ = 'b'; break; case 0: n++; if ( p ) *p++ = '0'; break; default: n += 3; if (p) { sprintf (p, "x%02x", *s); p += 3; } break; } } else { if (p) *p++ = *s; n++; } } else if ((*s & 0xe0) == 0xc0) /* 110x xxxx */ { val = *s & 0x1f; nleft = 1; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xf0) == 0xe0) /* 1110 xxxx */ { val = *s & 0x0f; nleft = 2; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xf8) == 0xf0) /* 1111 0xxx */ { val = *s & 0x07; nleft = 3; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xfc) == 0xf8) /* 1111 10xx */ { val = *s & 0x03; nleft = 4; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xfe) == 0xfc) /* 1111 110x */ { val = *s & 0x01; nleft = 5; encidx = 0; encbuf[encidx++] = *s; } else /* Invalid encoding: print as \xNN. */ { if (p) { sprintf (p, "\\x%02x", *s); p += 4; } n += 4; resync = 1; } } else if (*s < 0x80 || *s >= 0xc0) /* Invalid utf-8 */ { if (p) { for (i = 0; i < encidx; i++) { sprintf (p, "\\x%02x", encbuf[i]); p += 4; } sprintf (p, "\\x%02x", *s); p += 4; } n += 4 + 4 * encidx; nleft = 0; encidx = 0; resync = 1; } else { encbuf[encidx++] = *s; val <<= 6; val |= *s & 0x3f; if (!--nleft) /* Ready. */ { if (no_translation) { if (p) { for (i = 0; i < encidx; i++) *p++ = encbuf[i]; } n += encidx; encidx = 0; } else if (with_iconv) { /* Our strategy for using iconv is a bit strange but it better keeps compatibility with previous versions in regard to how invalid encodings are displayed. What we do is to keep the utf-8 as is and have the real translation step then at the end. Yes, I know that this is ugly. However we are short of the 1.4 release and for this branch we should not mess too much around with iconv things. One reason for this is that we don't know enough about non-GNU iconv implementation and want to minimize the risk of breaking the code on too many platforms. */ if ( p ) { for (i=0; i < encidx; i++ ) *p++ = encbuf[i]; } n += encidx; encidx = 0; } else /* Latin-1 case. */ { if (val >= 0x80 && val < 256) { /* We can simply print this character */ n++; if (p) *p++ = val; } else { /* We do not have a translation: print utf8. */ if (p) { for (i = 0; i < encidx; i++) { sprintf (p, "\\x%02x", encbuf[i]); p += 4; } } n += encidx * 4; encidx = 0; } } } } } if (!buffer) { /* Allocate the buffer after the first pass. */ buffer = p = xmalloc (n + 1); } else if (with_iconv) { /* Note: See above for comments. */ iconv_t cd; const char *inptr; char *outbuf, *outptr; size_t inbytes, outbytes; *p = 0; /* Terminate the buffer. */ cd = iconv_open (active_charset_name, "utf-8"); if (cd == (iconv_t)-1) { handle_iconv_error (active_charset_name, "utf-8", 1); xfree (buffer); return utf8_to_native (string, length, delim); } /* Allocate a new buffer large enough to hold all possible encodings. */ n = p - buffer + 1; inbytes = n - 1;; inptr = buffer; outbytes = n * MB_LEN_MAX; if (outbytes / MB_LEN_MAX != n) BUG (); /* Actually an overflow. */ outbuf = outptr = xmalloc (outbytes); if ( iconv (cd, (ICONV_CONST char **)&inptr, &inbytes, &outptr, &outbytes) == (size_t)-1) { static int shown; if (!shown) log_info (_("conversion from '%s' to '%s' failed: %s\n"), "utf-8", active_charset_name, strerror (errno)); shown = 1; /* Didn't worked out. Try again but without iconv. */ xfree (buffer); buffer = NULL; xfree (outbuf); outbuf = do_utf8_to_native (string, length, delim, 0); } else /* Success. */ { *outptr = 0; /* Make sure it is a string. */ /* We could realloc the buffer now but I doubt that it makes much sense given that it will get freed anyway soon after. */ xfree (buffer); } iconv_close (cd); return outbuf; } else /* Not using iconv. */ { *p = 0; /* Make sure it is a string. */ return buffer; } } } /* Convert string, which is in UTF-8 to native encoding. Replace illegal encodings by some "\xnn" and quote all control characters. A character with value DELIM will always be quoted, it must be a vanilla ASCII character. A DELIM value of -1 is special: it disables all quoting of control characters. This function terminates the process on memory shortage. */ char * utf8_to_native (const char *string, size_t length, int delim) { return do_utf8_to_native (string, length, delim, use_iconv); } /* Wrapper function for iconv_open, required for W32 as we dlopen that library on that system. */ jnlib_iconv_t jnlib_iconv_open (const char *tocode, const char *fromcode) { return (jnlib_iconv_t)iconv_open (tocode, fromcode); } /* Wrapper function for iconv, required for W32 as we dlopen that library on that system. */ size_t jnlib_iconv (jnlib_iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft) { return iconv ((iconv_t)cd, (ICONV_CONST char**)inbuf, inbytesleft, outbuf, outbytesleft); } /* Wrapper function for iconv_close, required for W32 as we dlopen that library on that system. */ int jnlib_iconv_close (jnlib_iconv_t cd) { return iconv_close ((iconv_t)cd); } #ifdef HAVE_W32_SYSTEM /* Return a malloced string encoded for CODEPAGE from the wide char input string STRING. Caller must free this value. Returns NULL and sets ERRNO on failure. Calling this function with STRING set to NULL is not defined. */ static char * wchar_to_cp (const wchar_t *string, unsigned int codepage) { int n; char *result; n = WideCharToMultiByte (codepage, 0, string, -1, NULL, 0, NULL, NULL); if (n < 0) { gpg_err_set_errno (EINVAL); return NULL; } result = xtrymalloc (n+1); if (!result) return NULL; n = WideCharToMultiByte (codepage, 0, string, -1, result, n, NULL, NULL); if (n < 0) { xfree (result); gpg_err_set_errno (EINVAL); result = NULL; } return result; } /* Return a malloced wide char string from a CODEPAGE encoded input string STRING. Caller must free this value. Returns NULL and sets ERRNO on failure. Calling this function with STRING set to NULL is not defined. */ static wchar_t * cp_to_wchar (const char *string, unsigned int codepage) { int n; size_t nbytes; wchar_t *result; n = MultiByteToWideChar (codepage, 0, string, -1, NULL, 0); if (n < 0) { gpg_err_set_errno (EINVAL); return NULL; } nbytes = (size_t)(n+1) * sizeof(*result); if (nbytes / sizeof(*result) != (n+1)) { gpg_err_set_errno (ENOMEM); return NULL; } result = xtrymalloc (nbytes); if (!result) return NULL; n = MultiByteToWideChar (codepage, 0, string, -1, result, n); if (n < 0) { xfree (result); gpg_err_set_errno (EINVAL); result = NULL; } return result; } /* Get the current codepage as used by wchar_to_native and * native_to_char. Note that these functions intentionally do not use * iconv based conversion machinery. */ static unsigned int get_w32_codepage (void) { static unsigned int cp; if (!cp) { -#ifndef HAVE_W32CE_SYSTEM cp = GetConsoleOutputCP (); if (!cp) -#endif cp = GetACP (); } return cp; } /* Return a malloced string encoded in the active code page from the * wide char input string STRING. Caller must free this value. * Returns NULL and sets ERRNO on failure. Calling this function with * STRING set to NULL is not defined. */ char * wchar_to_native (const wchar_t *string) { return wchar_to_cp (string, get_w32_codepage ()); } /* Return a malloced wide char string from native encoded input * string STRING. Caller must free this value. Returns NULL and sets * ERRNO on failure. Calling this function with STRING set to NULL is * not defined. */ wchar_t * native_to_wchar (const char *string) { return cp_to_wchar (string, get_w32_codepage ()); } /* Return a malloced string encoded in UTF-8 from the wide char input * string STRING. Caller must free this value. Returns NULL and sets * ERRNO on failure. Calling this function with STRING set to NULL is * not defined. */ char * wchar_to_utf8 (const wchar_t *string) { return wchar_to_cp (string, CP_UTF8); } /* Return a malloced wide char string from an UTF-8 encoded input * string STRING. Caller must free this value. Returns NULL and sets * ERRNO on failure. Calling this function with STRING set to NULL is * not defined. */ wchar_t * utf8_to_wchar (const char *string) { return cp_to_wchar (string, CP_UTF8); } #endif /*HAVE_W32_SYSTEM*/ diff --git a/common/util.h b/common/util.h index 43cb2fd3e..4327153bc 100644 --- a/common/util.h +++ b/common/util.h @@ -1,429 +1,418 @@ /* util.h - Utility functions for GnuPG * Copyright (C) 2001, 2002, 2003, 2004, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #ifndef GNUPG_COMMON_UTIL_H #define GNUPG_COMMON_UTIL_H #include /* We need this for the memory function protos. */ #include /* We need errno. */ #include /* We need gpg_error_t and estream. */ /* These error codes are used but not defined in the required * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ #ifndef EXTERN_UNLESS_MAIN_MODULE # if !defined (INCLUDED_BY_MAIN_MODULE) # define EXTERN_UNLESS_MAIN_MODULE extern # else # define EXTERN_UNLESS_MAIN_MODULE # endif #endif /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) /* The length of the keygrip. This is a SHA-1 hash of the key * parameters as generated by gcry_pk_get_keygrip. */ #define KEYGRIP_LEN 20 /* The length of the unique blob identifier as used by the keyboxd. * This is the possible truncated fingerprint of the primary key. */ #define UBID_LEN 20 /* Get all the stuff from jnlib. */ #include "../common/logging.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/dotlock.h" #include "../common/utf8conv.h" #include "../common/dynload.h" #include "../common/fwddecl.h" #include "../common/utilproto.h" #include "gettime.h" /* Redefine asprintf by our estream version which uses our own memory allocator.. */ #define asprintf gpgrt_asprintf #define vasprintf gpgrt_vasprintf /* Due to a bug in mingw32's snprintf related to the 'l' modifier and for increased portability we use our snprintf on all systems. */ #undef snprintf #define snprintf gpgrt_snprintf /* Replacements for macros not available with libgpg-error < 1.20. */ /* We need this type even if we are not using libreadline and or we did not include libreadline in the current file. */ #ifndef GNUPG_LIBREADLINE_H_INCLUDED typedef char **rl_completion_func_t (const char *, int, int); #endif /*!GNUPG_LIBREADLINE_H_INCLUDED*/ /* Handy malloc macros - please use only them. */ #define xtrymalloc(a) gcry_malloc ((a)) #define xtrymalloc_secure(a) gcry_malloc_secure ((a)) #define xtrycalloc(a,b) gcry_calloc ((a),(b)) #define xtrycalloc_secure(a,b) gcry_calloc_secure ((a),(b)) #define xtryrealloc(a,b) gcry_realloc ((a),(b)) #define xtryreallocarray(a,b,c,d) gpgrt_reallocarray ((a),(b),(c),(d)) #define xtrystrdup(a) gcry_strdup ((a)) #define xfree(a) gcry_free ((a)) #define xfree_fnc gcry_free #define xmalloc(a) gcry_xmalloc ((a)) #define xmalloc_secure(a) gcry_xmalloc_secure ((a)) #define xcalloc(a,b) gcry_xcalloc ((a),(b)) #define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b)) #define xrealloc(a,b) gcry_xrealloc ((a),(b)) #define xstrdup(a) gcry_xstrdup ((a)) /* See also the xreallocarray prototype below. */ /* For compatibility with gpg 1.4 we also define these: */ #define xmalloc_clear(a) gcry_xcalloc (1, (a)) #define xmalloc_secure_clear(a) gcry_xcalloc_secure (1, (a)) /* The default error source of the application. This is different from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the source file and thus is usable in code shared by applications. Defined by init.c. */ extern gpg_err_source_t default_errsource; /* Convenience function to return a gpg-error code for memory allocation failures. This function makes sure that an error will be returned even if accidentally ERRNO is not set. */ static inline gpg_error_t out_of_core (void) { return gpg_error_from_syserror (); } /*-- yesno.c --*/ int answer_is_yes (const char *s); int answer_is_yes_no_default (const char *s, int def_answer); int answer_is_yes_no_quit (const char *s); int answer_is_okay_cancel (const char *s, int def_answer); /*-- xreadline.c --*/ ssize_t read_line (FILE *fp, char **addr_of_buffer, size_t *length_of_buffer, size_t *max_length); /*-- b64enc.c and b64dec.c --*/ struct b64state { unsigned int flags; int idx; int quad_count; FILE *fp; estream_t stream; char *title; unsigned char radbuf[4]; u32 crc; int stop_seen:1; int invalid_encoding:1; gpg_error_t lasterr; }; gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title); gpg_error_t b64enc_start_es (struct b64state *state, estream_t fp, const char *title); gpg_error_t b64enc_write (struct b64state *state, const void *buffer, size_t nbytes); gpg_error_t b64enc_finish (struct b64state *state); gpg_error_t b64dec_start (struct b64state *state, const char *title); gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length, size_t *r_nbytes); gpg_error_t b64dec_finish (struct b64state *state); /*-- sexputil.c */ char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen); void log_printcanon (const char *text, const unsigned char *sexp, size_t sexplen); void log_printsexp (const char *text, gcry_sexp_t sexp); gpg_error_t make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip); int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); int cmp_canon_sexp (const unsigned char *a, size_t alen, const unsigned char *b, size_t blen, int (*tcmp)(void *ctx, int depth, const unsigned char *aval, size_t avallen, const unsigned char *bval, size_t bvallen), void *tcmpctx); unsigned char *make_simple_sexp_from_hexstr (const char *line, size_t *nscanned); int hash_algo_from_sigval (const unsigned char *sigval); unsigned char *make_canon_sexp_from_rsa_pk (const void *m, size_t mlen, const void *e, size_t elen, size_t *r_len); gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_n, size_t *r_nlen, unsigned char const **r_e, size_t *r_elen); gpg_error_t get_ecc_q_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_q, size_t *r_qlen); gpg_error_t uncompress_ecc_q_in_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char **r_newkeydata, size_t *r_newkeydatalen); int get_pk_algo_from_key (gcry_sexp_t key); int get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen); char *pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid); const char *pubkey_algo_to_string (int algo); const char *hash_algo_to_string (int algo); const char *cipher_mode_to_string (int mode); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); int hexcolon2bin (const char *string, void *buffer, size_t length); char *bin2hex (const void *buffer, size_t length, char *stringbuf); char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf); const char *hex2str (const char *hexstring, char *buffer, size_t bufsize, size_t *buflen); char *hex2str_alloc (const char *hexstring, size_t *r_count); unsigned int hex2fixedbuf (const char *hexstr, void *buffer, size_t bufsize); /*-- percent.c --*/ char *percent_plus_escape (const char *string); char *percent_data_escape (int plus, const char *prefix, const void *data, size_t datalen); char *percent_plus_unescape (const char *string, int nulrepl); char *percent_unescape (const char *string, int nulrepl); size_t percent_plus_unescape_inplace (char *string, int nulrepl); size_t percent_unescape_inplace (char *string, int nulrepl); /*-- openpgp-oid.c --*/ gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi); char *openpgp_oidbuf_to_str (const unsigned char *buf, size_t len); char *openpgp_oid_to_str (gcry_mpi_t a); int openpgp_oidbuf_is_ed25519 (const void *buf, size_t len); int openpgp_oid_is_ed25519 (gcry_mpi_t a); int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len); int openpgp_oid_is_cv25519 (gcry_mpi_t a); int openpgp_oid_is_cv448 (gcry_mpi_t a); int openpgp_oid_is_ed448 (gcry_mpi_t a); const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo); const char *openpgp_oid_to_curve (const char *oid, int canon); const char *openpgp_oid_or_name_to_curve (const char *oidname, int canon); const char *openpgp_enum_curves (int *idxp); const char *openpgp_is_curve_supported (const char *name, int *r_algo, unsigned int *r_nbits); const char *get_keyalgo_string (enum gcry_pk_algos algo, unsigned int nbits, const char *curve); /*-- homedir.c --*/ const char *standard_homedir (void); void gnupg_set_homedir (const char *newdir); void gnupg_maybe_make_homedir (const char *fname, int quiet); const char *gnupg_homedir (void); int gnupg_default_homedir_p (void); const char *gnupg_daemon_rootdir (void); const char *gnupg_socketdir (void); const char *gnupg_sysconfdir (void); const char *gnupg_bindir (void); const char *gnupg_libexecdir (void); const char *gnupg_libdir (void); const char *gnupg_datadir (void); const char *gnupg_localedir (void); const char *gpg_agent_socket_name (void); const char *dirmngr_socket_name (void); const char *keyboxd_socket_name (void); char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info); /* All module names. We also include gpg and gpgsm for the sake for gpgconf. */ #define GNUPG_MODULE_NAME_AGENT 1 #define GNUPG_MODULE_NAME_PINENTRY 2 #define GNUPG_MODULE_NAME_SCDAEMON 3 #define GNUPG_MODULE_NAME_DIRMNGR 4 #define GNUPG_MODULE_NAME_PROTECT_TOOL 5 #define GNUPG_MODULE_NAME_CHECK_PATTERN 6 #define GNUPG_MODULE_NAME_GPGSM 7 #define GNUPG_MODULE_NAME_GPG 8 #define GNUPG_MODULE_NAME_CONNECT_AGENT 9 #define GNUPG_MODULE_NAME_GPGCONF 10 #define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11 #define GNUPG_MODULE_NAME_GPGV 12 #define GNUPG_MODULE_NAME_KEYBOXD 13 #define GNUPG_MODULE_NAME_TPM2DAEMON 14 const char *gnupg_module_name (int which); void gnupg_module_name_flush_some (void); void gnupg_set_builddir (const char *newdir); /* A list of constants to identify protocols. This is used by tools * which need to distinguish between the different protocols * implemented by GnuPG. May be used as bit flags. */ #define GNUPG_PROTOCOL_OPENPGP 1 /* The one and only (gpg). */ #define GNUPG_PROTOCOL_CMS 2 /* The core of S/MIME (gpgsm) */ #define GNUPG_PROTOCOL_SSH_AGENT 4 /* Out ssh-agent implementation */ /*-- gpgrlhelp.c --*/ void gnupg_rl_initialize (void); /*-- helpfile.c --*/ char *gnupg_get_help_string (const char *key, int only_current_locale); /*-- localename.c --*/ const char *gnupg_messages_locale_name (void); /*-- miscellaneous.c --*/ /* This function is called at startup to tell libgcrypt to use our own logging subsystem. */ void setup_libgcrypt_logging (void); /* Print an out of core message and die. */ void xoutofcore (void); /* Wrapper aroung gpgrt_reallocarray. Uses the gpgrt alloc function * which redirects to the Libgcrypt versions via * init_common_subsystems. Thus this can be used interchangeable with * the other alloc functions. */ void *xreallocarray (void *a, size_t oldnmemb, size_t nmemb, size_t size); /* Same as estream_asprintf but die on memory failure. */ char *xasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* This is now an alias to estream_asprintf. */ char *xtryasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* Replacement for gcry_cipher_algo_name. */ const char *gnupg_cipher_algo_name (int algo); void obsolete_option (const char *configname, unsigned int configlineno, const char *name); const char *print_fname_stdout (const char *s); const char *print_fname_stdin (const char *s); void print_utf8_buffer3 (estream_t fp, const void *p, size_t n, const char *delim); void print_utf8_buffer2 (estream_t fp, const void *p, size_t n, int delim); void print_utf8_buffer (estream_t fp, const void *p, size_t n); void print_utf8_string (estream_t stream, const char *p); void print_hexstring (FILE *fp, const void *buffer, size_t length, int reserved); char *try_make_printable_string (const void *p, size_t n, int delim); char *make_printable_string (const void *p, size_t n, int delim); char *decode_c_string (const char *src); int is_file_compressed (const char *s, int *ret_rc); int match_multistr (const char *multistr,const char *match); int gnupg_compare_version (const char *a, const char *b); struct debug_flags_s { unsigned int flag; const char *name; }; int parse_debug_flag (const char *string, unsigned int *debugvar, const struct debug_flags_s *flags); /*-- Simple replacement functions. */ /* We use the gnupg_ttyname macro to be safe not to run into conflicts which an extisting but broken ttyname. */ #if !defined(HAVE_TTYNAME) || defined(HAVE_BROKEN_TTYNAME) # define gnupg_ttyname(n) _gnupg_ttyname ((n)) /* Systems without ttyname (W32) will merely return NULL. */ static inline char * _gnupg_ttyname (int fd) { (void)fd; return NULL; } #else /*HAVE_TTYNAME*/ # define gnupg_ttyname(n) ttyname ((n)) #endif /*HAVE_TTYNAME */ -#ifdef HAVE_W32CE_SYSTEM -#define getpid() GetCurrentProcessId () -char *_gnupg_getenv (const char *name); /* See sysutils.c */ -#define getenv(a) _gnupg_getenv ((a)) -char *_gnupg_setenv (const char *name); /* See sysutils.c */ -#define setenv(a,b,c) _gnupg_setenv ((a),(b),(c)) -int _gnupg_isatty (int fd); -#define gnupg_isatty(a) _gnupg_isatty ((a)) -#else #define gnupg_isatty(a) isatty ((a)) -#endif - /*-- Macros to replace ctype ones to avoid locale problems. --*/ #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \ || (*(p) >= 'a' && *(p) <= 'z')) #define alnump(p) (alphap (p) || digitp (p)) #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) /* Note this isn't identical to a C locale isspace() without \f and \v, but works for the purposes used here. */ #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t') /* The atoi macros assume that the buffer has only valid digits. */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) #define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2)) #endif /*GNUPG_COMMON_UTIL_H*/ diff --git a/common/w32help.h b/common/w32help.h index a79081f8e..33000acc7 100644 --- a/common/w32help.h +++ b/common/w32help.h @@ -1,66 +1,51 @@ /* w32help.h - W32 speicif functions * Copyright (C) 2007 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #ifndef GNUPG_COMMON_W32HELP_H #define GNUPG_COMMON_W32HELP_H /*-- w32-cmdline.c --*/ /* This module is also part of the Unix tests. */ char **w32_parse_commandline (char *cmdline, int globing, int *r_argv, int *r_itemsalloced); #ifdef HAVE_W32_SYSTEM /*-- w32-reg.c --*/ char *read_w32_registry_string (const char *root, const char *dir, const char *name ); char *read_w32_reg_string (const char *key); -/* Other stuff. */ -#ifdef HAVE_W32CE_SYSTEM -/* Setmode is missing in cegcc but available since CE 5.0. */ -int _setmode (int handle, int mode); -# define setmode(a,b) _setmode ((a),(b)) - -static inline int -umask (int a) -{ - (void)a; - return 0; -} - - -#endif /*HAVE_W32CE_SYSTEM*/ #endif /*HAVE_W32_SYSTEM*/ #endif /*GNUPG_COMMON_MISCHELP_H*/ diff --git a/g10/cpr.c b/g10/cpr.c index 002656b82..9bfdd3c34 100644 --- a/g10/cpr.c +++ b/g10/cpr.c @@ -1,712 +1,710 @@ /* status.c - Status message and command-fd interface * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, * 2004, 2005, 2006, 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 #ifdef HAVE_SIGNAL_H # include #endif #include "gpg.h" #include "../common/util.h" #include "../common/status.h" #include "../common/ttyio.h" #include "options.h" #include "main.h" #include "../common/i18n.h" #define CONTROL_D ('D' - 'A' + 1) /* The stream to output the status information. Output is disabled if this is NULL. */ static estream_t statusfp; static void progress_cb (void *ctx, const char *what, int printchar, int current, int total) { char buf[50]; (void)ctx; if ( printchar == '\n' && !strcmp (what, "primegen") ) snprintf (buf, sizeof buf, "%.20s X 100 100", what ); else snprintf (buf, sizeof buf, "%.20s %c %d %d", what, printchar=='\n'?'X':printchar, current, total ); write_status_text (STATUS_PROGRESS, buf); } /* Return true if the status message NO may currently be issued. We need this to avoid synchronization problem while auto retrieving a key. There it may happen that a status NODATA is issued for a non available key and the user may falsely interpret this has a missing signature. */ static int status_currently_allowed (int no) { if (!glo_ctrl.in_auto_key_retrieve) return 1; /* Yes. */ /* We allow some statis anyway, so that import statistics are correct and to avoid problems if the retrieval subsystem will prompt the user. */ switch (no) { case STATUS_GET_BOOL: case STATUS_GET_LINE: case STATUS_GET_HIDDEN: case STATUS_GOT_IT: case STATUS_IMPORTED: case STATUS_IMPORT_OK: case STATUS_IMPORT_CHECK: case STATUS_IMPORT_RES: return 1; /* Yes. */ default: break; } return 0; /* No. */ } void set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (statusfp && statusfp != es_stdout && statusfp != es_stderr ) es_fclose (statusfp); statusfp = NULL; if (fd == -1) return; if (! gnupg_fd_valid (fd)) log_fatal ("status-fd is invalid: %s\n", strerror (errno)); 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, strerror (errno)); } last_fd = fd; gcry_set_progress_handler (progress_cb, NULL); } int is_status_enabled () { return !!statusfp; } void write_status ( int no ) { write_status_text( no, NULL ); } /* Write a status line with code NO followed by the string TEXT and * directly followed by the remaining strings up to a NULL. Embedded * CR and LFs in the strings (but not in TEXT) are C-style escaped.*/ void write_status_strings (int no, const char *text, ...) { va_list arg_ptr; const char *s; if (!statusfp || !status_currently_allowed (no) ) return; /* Not enabled or allowed. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if ( text ) { es_putc ( ' ', statusfp); va_start (arg_ptr, text); s = text; do { for (; *s; s++) { if (*s == '\n') es_fputs ("\\n", statusfp); else if (*s == '\r') es_fputs ("\\r", statusfp); else es_fputc (*(const byte *)s, statusfp); } } while ((s = va_arg (arg_ptr, const char*))); va_end (arg_ptr); } es_putc ('\n', statusfp); if (es_fflush (statusfp) && opt.exit_on_status_write_error) g10_exit (0); } /* Write a status line with code NO followed by the remaining * arguments which must be a list of strings terminated by a NULL. * Embedded CR and LFs in the strings are C-style escaped. All * strings are printed with a space as delimiter. */ gpg_error_t write_status_strings2 (ctrl_t dummy, int no, ...) { va_list arg_ptr; const char *s; (void)dummy; if (!statusfp || !status_currently_allowed (no) ) return 0; /* Not enabled or allowed. */ va_start (arg_ptr, no); es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); while ((s = va_arg (arg_ptr, const char*))) { if (*s) es_putc (' ', statusfp); for (; *s; s++) { if (*s == '\n') es_fputs ("\\n", statusfp); else if (*s == '\r') es_fputs ("\\r", statusfp); else es_fputc (*(const byte *)s, statusfp); } } es_putc ('\n', statusfp); va_end (arg_ptr); if (es_fflush (statusfp) && opt.exit_on_status_write_error) g10_exit (0); return 0; } void write_status_text (int no, const char *text) { write_status_strings (no, text, NULL); } /* Write a status line with code NO followed by the output of the * printf style FORMAT. Embedded CR and LFs are C-style escaped. */ void write_status_printf (int no, const char *format, ...) { va_list arg_ptr; char *buf; if (!statusfp || !status_currently_allowed (no) ) return; /* Not enabled or allowed. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if (format) { es_putc ( ' ', statusfp); va_start (arg_ptr, format); buf = gpgrt_vbsprintf (format, arg_ptr); if (!buf) log_error ("error printing status line: %s\n", gpg_strerror (gpg_err_code_from_syserror ())); else { if (strpbrk (buf, "\r\n")) { const byte *s; for (s=buf; *s; s++) { if (*s == '\n') es_fputs ("\\n", statusfp); else if (*s == '\r') es_fputs ("\\r", statusfp); else es_fputc (*s, statusfp); } } else es_fputs (buf, statusfp); gpgrt_free (buf); } va_end (arg_ptr); } es_putc ('\n', statusfp); if (es_fflush (statusfp) && opt.exit_on_status_write_error) g10_exit (0); } /* Write an ERROR status line using a full gpg-error error value. */ void write_status_error (const char *where, gpg_error_t err) { if (!statusfp || !status_currently_allowed (STATUS_ERROR)) return; /* Not enabled or allowed. */ es_fprintf (statusfp, "[GNUPG:] %s %s %u\n", get_status_string (STATUS_ERROR), where, err); if (es_fflush (statusfp) && opt.exit_on_status_write_error) g10_exit (0); } /* Same as above but outputs the error code only. */ void write_status_errcode (const char *where, int errcode) { if (!statusfp || !status_currently_allowed (STATUS_ERROR)) return; /* Not enabled or allowed. */ es_fprintf (statusfp, "[GNUPG:] %s %s %u\n", get_status_string (STATUS_ERROR), where, gpg_err_code (errcode)); if (es_fflush (statusfp) && opt.exit_on_status_write_error) g10_exit (0); } /* Write a FAILURE status line. */ void write_status_failure (const char *where, gpg_error_t err) { static int any_failure_printed; if (!statusfp || !status_currently_allowed (STATUS_FAILURE)) return; /* Not enabled or allowed. */ if (any_failure_printed) return; any_failure_printed = 1; es_fprintf (statusfp, "[GNUPG:] %s %s %u\n", get_status_string (STATUS_FAILURE), where, err); if (es_fflush (statusfp) && opt.exit_on_status_write_error) g10_exit (0); } /* * Write a status line with a buffer using %XX escapes. If WRAP is > * 0 wrap the line after this length. If STRING is not NULL it will * be prepended to the buffer, no escaping is done for string. * A wrap of -1 forces spaces not to be encoded as %20. */ void write_status_text_and_buffer (int no, const char *string, const char *buffer, size_t len, int wrap) { const char *s, *text; int esc, first; int lower_limit = ' '; size_t n, count, dowrap; if (!statusfp || !status_currently_allowed (no)) return; /* Not enabled or allowed. */ if (wrap == -1) { lower_limit--; wrap = 0; } text = get_status_string (no); count = dowrap = first = 1; do { if (dowrap) { es_fprintf (statusfp, "[GNUPG:] %s ", text); count = dowrap = 0; if (first && string) { es_fputs (string, statusfp); count += strlen (string); /* Make sure that there is a space after the string. */ if (*string && string[strlen (string)-1] != ' ') { es_putc (' ', statusfp); count++; } } first = 0; } for (esc=0, s=buffer, n=len; n && !esc; s++, n--) { if (*s == '%' || *(const byte*)s <= lower_limit || *(const byte*)s == 127 ) esc = 1; if (wrap && ++count > wrap) { dowrap=1; break; } } if (esc) { s--; n++; } if (s != buffer) es_fwrite (buffer, s-buffer, 1, statusfp); if ( esc ) { es_fprintf (statusfp, "%%%02X", *(const byte*)s ); s++; n--; } buffer = s; len = n; if (dowrap && len) es_putc ('\n', statusfp); } while (len); es_putc ('\n',statusfp); if (es_fflush (statusfp) && opt.exit_on_status_write_error) g10_exit (0); } void write_status_buffer (int no, const char *buffer, size_t len, int wrap) { write_status_text_and_buffer (no, NULL, buffer, len, wrap); } /* Print the BEGIN_SIGNING status message. If MD is not NULL it is used to retrieve the hash algorithms used for the message. */ void write_status_begin_signing (gcry_md_hd_t md) { if (md) { char buf[100]; size_t buflen; int i, ga; buflen = 0; for (i=1; i <= 110; i++) { ga = map_md_openpgp_to_gcry (i); if (ga && gcry_md_is_enabled (md, ga) && buflen+10 < DIM(buf)) { snprintf (buf+buflen, DIM(buf) - buflen, "%sH%d", buflen? " ":"",i); buflen += strlen (buf+buflen); } } write_status_text (STATUS_BEGIN_SIGNING, buf); } else write_status ( STATUS_BEGIN_SIGNING ); } static int myread(int fd, void *buf, size_t count) { int rc; do { rc = read( fd, buf, count ); } while (rc == -1 && errno == EINTR); if (!rc && count) { static int eof_emmited=0; if ( eof_emmited < 3 ) { *(char*)buf = CONTROL_D; rc = 1; eof_emmited++; } else /* Ctrl-D not caught - do something reasonable */ { #ifdef HAVE_DOSISH_SYSTEM -#ifndef HAVE_W32CE_SYSTEM raise (SIGINT); /* Nothing to hangup under DOS. */ -#endif #else raise (SIGHUP); /* No more input data. */ #endif } } return rc; } /* Request a string from the client over the command-fd. If GETBOOL is set the function returns a static string (do not free) if the entered value was true or NULL if the entered value was false. */ static char * do_get_from_fd ( const char *keyword, int hidden, int getbool ) { int i, len; char *string; if (statusfp != es_stdout) es_fflush (es_stdout); write_status_text (getbool? STATUS_GET_BOOL : hidden? STATUS_GET_HIDDEN : STATUS_GET_LINE, keyword); for (string = NULL, i = len = 200; ; i++ ) { if (i >= len-1 ) { /* On the first iteration allocate a new buffer. If that * buffer is too short at further iterations do a poor man's * realloc. */ char *save = string; len += 100; string = hidden? xmalloc_secure ( len ) : xmalloc ( len ); if (save) { memcpy (string, save, i); xfree (save); } else i = 0; } /* Fixme: why not use our read_line function here? */ if ( myread( opt.command_fd, string+i, 1) != 1 || string[i] == '\n' ) break; else if ( string[i] == CONTROL_D ) { /* Found ETX - Cancel the line and return a sole ETX. */ string[0] = CONTROL_D; i = 1; break; } } string[i] = 0; write_status (STATUS_GOT_IT); if (getbool) /* Fixme: is this correct??? */ { char *rv = (string[0] == 'Y' || string[0] == 'y') ? "" : NULL; xfree (string); return rv; } return string; } int cpr_enabled() { if( opt.command_fd != -1 ) return 1; return 0; } char * cpr_get_no_help( const char *keyword, const char *prompt ) { char *p; if( opt.command_fd != -1 ) return do_get_from_fd ( keyword, 0, 0 ); for(;;) { p = tty_get( prompt ); return p; } } char * cpr_get( const char *keyword, const char *prompt ) { char *p; if( opt.command_fd != -1 ) return do_get_from_fd ( keyword, 0, 0 ); for(;;) { p = tty_get( prompt ); if( *p=='?' && !p[1] && !(keyword && !*keyword)) { xfree(p); display_online_help( keyword ); } else return p; } } char * cpr_get_utf8( const char *keyword, const char *prompt ) { char *p; p = cpr_get( keyword, prompt ); if( p ) { char *utf8 = native_to_utf8( p ); xfree( p ); p = utf8; } return p; } char * cpr_get_hidden( const char *keyword, const char *prompt ) { char *p; if( opt.command_fd != -1 ) return do_get_from_fd ( keyword, 1, 0 ); for(;;) { p = tty_get_hidden( prompt ); if( *p == '?' && !p[1] ) { xfree(p); display_online_help( keyword ); } else return p; } } void cpr_kill_prompt(void) { if( opt.command_fd != -1 ) return; tty_kill_prompt(); return; } int cpr_get_answer_is_yes_def (const char *keyword, const char *prompt, int def_yes) { int yes; char *p; if( opt.command_fd != -1 ) return !!do_get_from_fd ( keyword, 0, 1 ); for(;;) { p = tty_get( prompt ); trim_spaces(p); /* it is okay to do this here */ if( *p == '?' && !p[1] ) { xfree(p); display_online_help( keyword ); } else { tty_kill_prompt(); yes = answer_is_yes_no_default (p, def_yes); xfree(p); return yes; } } } int cpr_get_answer_is_yes (const char *keyword, const char *prompt) { return cpr_get_answer_is_yes_def (keyword, prompt, 0); } int cpr_get_answer_yes_no_quit( const char *keyword, const char *prompt ) { int yes; char *p; if( opt.command_fd != -1 ) return !!do_get_from_fd ( keyword, 0, 1 ); for(;;) { p = tty_get( prompt ); trim_spaces(p); /* it is okay to do this here */ if( *p == '?' && !p[1] ) { xfree(p); display_online_help( keyword ); } else { tty_kill_prompt(); yes = answer_is_yes_no_quit(p); xfree(p); return yes; } } } int cpr_get_answer_okay_cancel (const char *keyword, const char *prompt, int def_answer) { int yes; char *answer = NULL; char *p; if( opt.command_fd != -1 ) answer = do_get_from_fd ( keyword, 0, 0 ); if (answer) { yes = answer_is_okay_cancel (answer, def_answer); xfree (answer); return yes; } for(;;) { p = tty_get( prompt ); trim_spaces(p); /* it is okay to do this here */ if (*p == '?' && !p[1]) { xfree(p); display_online_help (keyword); } else { tty_kill_prompt(); yes = answer_is_okay_cancel (p, def_answer); xfree(p); return yes; } } } diff --git a/g10/decrypt.c b/g10/decrypt.c index 9589aff58..065601b7b 100644 --- a/g10/decrypt.c +++ b/g10/decrypt.c @@ -1,282 +1,277 @@ /* decrypt.c - decrypt and verify data * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, * 2007, 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 "gpg.h" #include "options.h" #include "packet.h" #include "../common/status.h" #include "../common/iobuf.h" #include "keydb.h" #include "../common/util.h" #include "main.h" #include "../common/status.h" #include "../common/i18n.h" /* Assume that the input is an encrypted message and decrypt * (and if signed, verify the signature on) it. * This command differs from the default operation, as it never * writes to the filename which is included in the file and it * rejects files which don't begin with an encrypted message. */ int decrypt_message (ctrl_t ctrl, const char *filename) { IOBUF fp; armor_filter_context_t *afx = NULL; progress_filter_context_t *pfx; int rc; pfx = new_progress_context (); /* Open the message file. */ fp = iobuf_open (filename); if (fp && is_secured_file (iobuf_get_fd (fp))) { iobuf_close (fp); fp = NULL; gpg_err_set_errno (EPERM); } if ( !fp ) { rc = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), print_fname_stdin(filename), gpg_strerror (rc)); release_progress_context (pfx); return rc; } handle_progress (pfx, fp, filename); if ( !opt.no_armor ) { if ( use_armor_filter( fp ) ) { afx = new_armor_context (); push_armor_filter ( afx, fp ); } } if (!opt.outfile) { opt.outfile = "-"; opt.flags.dummy_outfile = 1; } else opt.flags.dummy_outfile = 0; rc = proc_encryption_packets (ctrl, NULL, fp ); if (opt.flags.dummy_outfile) opt.outfile = NULL; iobuf_close (fp); release_armor_context (afx); release_progress_context (pfx); return rc; } /* Same as decrypt_message but takes a file descriptor for input and output. */ gpg_error_t decrypt_message_fd (ctrl_t ctrl, int input_fd, int output_fd) { #ifdef HAVE_W32_SYSTEM /* No server mode yet. */ (void)ctrl; (void)input_fd; (void)output_fd; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #else gpg_error_t err; IOBUF fp; armor_filter_context_t *afx = NULL; progress_filter_context_t *pfx; if (opt.outfp) return gpg_error (GPG_ERR_BUG); pfx = new_progress_context (); /* Open the message file. */ fp = iobuf_fdopen_nc (FD2INT(input_fd), "rb"); if (fp && is_secured_file (iobuf_get_fd (fp))) { iobuf_close (fp); fp = NULL; gpg_err_set_errno (EPERM); } if (!fp) { char xname[64]; err = gpg_error_from_syserror (); snprintf (xname, sizeof xname, "[fd %d]", input_fd); log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err)); release_progress_context (pfx); return err; } -#ifdef HAVE_W32CE_SYSTEM -#warning Need to fix this if we want to use g13 - opt.outfp = NULL; -#else opt.outfp = es_fdopen_nc (output_fd, "wb"); -#endif if (!opt.outfp) { char xname[64]; err = gpg_error_from_syserror (); snprintf (xname, sizeof xname, "[fd %d]", output_fd); log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err)); iobuf_close (fp); release_progress_context (pfx); return err; } if (!opt.no_armor) { if (use_armor_filter (fp)) { afx = new_armor_context (); push_armor_filter ( afx, fp ); } } err = proc_encryption_packets (ctrl, NULL, fp ); iobuf_close (fp); es_fclose (opt.outfp); opt.outfp = NULL; release_armor_context (afx); release_progress_context (pfx); return err; #endif } void decrypt_messages (ctrl_t ctrl, int nfiles, char *files[]) { IOBUF fp; progress_filter_context_t *pfx; char *p, *output = NULL; int rc=0,use_stdin=0; unsigned int lno=0; if (opt.outfile) { log_error(_("--output doesn't work for this command\n")); return; } pfx = new_progress_context (); if(!nfiles) use_stdin=1; for(;;) { char line[2048]; char *filename=NULL; if(use_stdin) { if(fgets(line, DIM(line), stdin)) { lno++; if (!*line || line[strlen(line)-1] != '\n') log_error("input line %u too long or missing LF\n", lno); else { line[strlen(line)-1] = '\0'; filename=line; } } } else { if(nfiles) { filename=*files; nfiles--; files++; } } if(filename==NULL) break; print_file_status(STATUS_FILE_START, filename, 3); output = make_outfile_name(filename); if (!output) goto next_file; fp = iobuf_open(filename); if (fp) iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL); if (fp && is_secured_file (iobuf_get_fd (fp))) { iobuf_close (fp); fp = NULL; gpg_err_set_errno (EPERM); } if (!fp) { log_error(_("can't open '%s'\n"), print_fname_stdin(filename)); goto next_file; } handle_progress (pfx, fp, filename); if (!opt.no_armor) { if (use_armor_filter(fp)) { armor_filter_context_t *afx = new_armor_context (); rc = push_armor_filter (afx, fp); if (rc) log_error("failed to push armor filter"); release_armor_context (afx); } } rc = proc_packets (ctrl,NULL, fp); iobuf_close(fp); if (rc) log_error("%s: decryption failed: %s\n", print_fname_stdin(filename), gpg_strerror (rc)); p = get_last_passphrase(); set_next_passphrase(p); xfree (p); next_file: /* Note that we emit file_done even after an error. */ write_status( STATUS_FILE_DONE ); xfree(output); reset_literals_seen(); } set_next_passphrase(NULL); release_progress_context (pfx); } diff --git a/g10/exec.c b/g10/exec.c index f612c850a..75b39e0fe 100644 --- a/g10/exec.c +++ b/g10/exec.c @@ -1,173 +1,169 @@ /* exec.c - generic call-a-program code * Copyright (C) 2001, 2002, 2003, 2004, 2005 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 #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif #include #include "gpg.h" #include "options.h" #include "../common/i18n.h" #include "exec.h" #ifdef NO_EXEC int set_exec_path(const char *path) { return GPG_ERR_GENERAL; } #else #if defined (_WIN32) /* This is a nicer system() for windows that waits for programs to return before returning control to the caller. I hate helpful computers. */ int w32_system(const char *command) { if (!strncmp (command, "!ShellExecute ", 14)) { SHELLEXECUTEINFOW see; wchar_t *wname; int waitms; command = command + 14; while (spacep (command)) command++; waitms = atoi (command); if (waitms < 0) waitms = 0; else if (waitms > 60*1000) waitms = 60000; while (*command && !spacep (command)) command++; while (spacep (command)) command++; wname = utf8_to_wchar (command); if (!wname) return -1; memset (&see, 0, sizeof see); see.cbSize = sizeof see; see.fMask = (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI | SEE_MASK_NO_CONSOLE); see.lpVerb = L"open"; see.lpFile = (LPCWSTR)wname; see.nShow = SW_SHOW; if (DBG_EXTPROG) log_debug ("running ShellExecuteEx(open,'%s')\n", command); if (!ShellExecuteExW (&see)) { if (DBG_EXTPROG) log_debug ("ShellExecuteEx failed: rc=%d\n", (int)GetLastError ()); xfree (wname); return -1; } if (DBG_EXTPROG) log_debug ("ShellExecuteEx succeeded (hProcess=%p,hInstApp=%d)\n", see.hProcess, (int)see.hInstApp); if (!see.hProcess) { gnupg_usleep (waitms*1000); if (DBG_EXTPROG) log_debug ("ShellExecuteEx ready (wait=%dms)\n", waitms); } else { WaitForSingleObject (see.hProcess, INFINITE); if (DBG_EXTPROG) log_debug ("ShellExecuteEx ready\n"); } CloseHandle (see.hProcess); xfree (wname); } else { char *string; wchar_t *wstring; PROCESS_INFORMATION pi; STARTUPINFOW si; /* We must use a copy of the command as CreateProcess modifies * this argument. */ string = xstrdup (command); wstring = utf8_to_wchar (string); xfree (string); if (!wstring) return -1; memset (&pi, 0, sizeof(pi)); memset (&si, 0, sizeof(si)); si.cb = sizeof (si); if (!CreateProcessW (NULL, wstring, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi)) { xfree (wstring); return -1; } /* Wait for the child to exit */ WaitForSingleObject (pi.hProcess, INFINITE); CloseHandle (pi.hProcess); CloseHandle (pi.hThread); xfree (wstring); } return 0; } #endif /*_W32*/ /* Replaces current $PATH */ int set_exec_path(const char *path) { -#ifdef HAVE_W32CE_SYSTEM -#warning Change this code to use common/exechelp.c -#else char *p; p=xmalloc(5+strlen(path)+1); strcpy(p,"PATH="); strcat(p,path); if(DBG_EXTPROG) log_debug("set_exec_path: %s\n",p); /* Notice that path is never freed. That is intentional due to the way putenv() works. This leaks a few bytes if we call set_exec_path multiple times. */ if(putenv(p)!=0) return GPG_ERR_GENERAL; else return 0; -#endif } #endif /* ! NO_EXEC */ diff --git a/g10/tdbio.c b/g10/tdbio.c index a6ce5ce00..50dccca05 100644 --- a/g10/tdbio.c +++ b/g10/tdbio.c @@ -1,1933 +1,1903 @@ /* 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 because * dotlock does not implement 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 active. * 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 (!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) || 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 record 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, mo=%d, f=%02x\n", rec->r.trust.ownertrust, rec->r.trust.depth, rec->r.trust.validlist, rec->r.trust.min_ownertrust, rec->r.trust.flags); 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, f=%d, m=%d\n", rec->r.valid.validity, rec->r.valid.next, rec->r.valid.full_count, rec->r.valid.marginal_count); 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++; rec->r.trust.flags = *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++ = rec->r.trust.flags; 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[20]; fpr20_from_pk (pk, fingerprint); 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/kbx/keyboxd.c b/kbx/keyboxd.c index 76e360b18..88a350a08 100644 --- a/kbx/keyboxd.c +++ b/kbx/keyboxd.c @@ -1,1856 +1,1843 @@ /* keyboxd.c - The GnuPG Keybox Daemon * Copyright (C) 2000-2020 Free Software Foundation, Inc. * Copyright (C) 2000-2019 Werner Koch * Copyright (C) 2015-2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0+ */ #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 # 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 "keyboxd.h" #include /* Malloc hooks and socket wrappers. */ #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/init.h" #include "../common/gc-opt-flags.h" #include "../common/exechelp.h" #include "../common/comopt.h" #include "frontend.h" /* Urrgs: Put this into a separate header - but it needs assuan.h first. */ extern int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, const char *msg); enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oNoVerbose = 500, aGPGConfList, aGPGConfTest, oOptions, oDebug, oDebugAll, oDebugWait, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oStealSocket, oLogFile, oServer, oDaemon, oFakedSystemTime, oListenBacklog, oDisableCheckOwnSocket, oDummy }; static gpgrt_opt_t opts[] = { ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_header (NULL, N_("Options used for startup")), ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_n (oStealSocket, "steal-socket", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")), ARGPARSE_header ("Configuration", N_("Options controlling the configuration")), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), 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" }, { DBG_CLOCK_VALUE , "clock" }, { DBG_LOOKUP_VALUE , "lookup" }, { 77, NULL } /* 77 := Do not exit on "help" or "?". */ }; /* 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 + * seconds. Thus we use 4 seconds on all platforms. + * 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 /* 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; /* Flag indicating to start the daemon even if one already runs. */ static int steal_socket; /* Counter for the currently running own socket checks. */ static int check_own_socket_running; /* Flag to indicate that we shall not watch our own socket. */ static int disable_check_own_socket; /* Flag to inhibit socket removal in cleanup. */ static int inhibit_socket_removal; /* Name of the communication socket used for client requests. */ static char *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. 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; /* Name of a config file, which will be reread on a HUP if it is not NULL. */ static char *config_filename; /* 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; /* 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 keyboxd 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 cygwin, assuan_sock_nonce_t *nonce); static void create_directories (void); static void kbxd_libgcrypt_progress_cb (void *data, const char *what, int printchar, int current, int total); static void kbxd_init_default_ctrl (ctrl_t ctrl); static void kbxd_deinit_default_ctrl (ctrl_t ctrl); static void handle_connections (gnupg_fd_t listen_fd); static void check_own_socket (void); static int check_for_running_kbxd (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*)) { return xstrconcat (libname, " ", getfnc (NULL), NULL); } /* Return strings describing this program. The case values are * described in Libgpg-error. 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 9: p = "GPL-3.0-or-later"; break; case 11: p = "keyboxd (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; case 1: case 40: p = _("Usage: keyboxd [options] (-h for help)"); break; case 41: p = _("Syntax: keyboxd [options] [command [args]]\n" "Public key management for @GNUPG@\n"); break; default: p = NULL; } return p; } /* Setup the debugging. Note that we don't fail here, because it is * important to keep keyboxd running even after re-reading the options * due to a SIGHUP. */ static void set_debug (void) { 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. */ static void remove_socket (char *name) { if (name && *name) { gnupg_remove (name); *name = 0; } } /* 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; if (!inhibit_socket_removal) remove_socket (socket_name); } /* 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 (gpgrt_argparse_t *pargs, int reread) { if (!pargs) { /* reset mode */ opt.quiet = 0; opt.verbose = 0; opt.debug = 0; disable_check_own_socket = 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 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; default: return 0; /* not handled */ } return 1; /* handled */ } /* Fixup some options after all have been processed. */ static void finalize_rereadable_options (void) { } 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. */ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); } static void initialize_modules (void) { thread_init_once (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); } /* The main entry point. */ int main (int argc, char **argv ) { gpgrt_argparse_t pargs; int orig_argc; char **orig_argv; char *last_configname = NULL; char *configname = NULL; int debug_argparser = 0; int pipe_server = 0; int is_daemon = 0; int nodetach = 0; char *logfile = NULL; int gpgconf_list = 0; int debug_wait = 0; 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. */ gpgrt_set_strusage (my_strusage); log_set_prefix ("keyboxd", 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); 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, kbxd_assuan_log_monitor); setup_libgcrypt_logging (); gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL); /* Set default options. */ parse_rereadable_options (NULL, 0); /* Reset them to default values. */ /* Check whether we have a config file on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oDebug: case oDebugAll: debug_argparser++; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; } } /* Reset the flags. */ pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); /* The configuraton directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags |= (ARGPARSE_FLAG_RESET | ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER); while (gpgrt_argparser (&pargs, opts, "keyboxd" EXTSEP_S "conf")) { if (pargs.r_opt == ARGPARSE_CONFFILE) { if (debug_argparser) log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); if (pargs.r_type) { xfree (last_configname); last_configname = xstrdup (pargs.r.ret_str); configname = last_configname; } else configname = NULL; continue; } if (parse_rereadable_options (&pargs, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case aGPGConfList: gpgconf_list = 1; break; case aGPGConfTest: gpgconf_list = 2; break; case oDebugWait: debug_wait = pargs.r.ret_int; 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 oStealSocket: steal_socket = 1; break; case oLogFile: logfile = pargs.r.ret_str; break; case oServer: pipe_server = 1; break; case oDaemon: is_daemon = 1; 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 oListenBacklog: listen_backlog = pargs.r.ret_int; break; default: if (configname) pargs.err = ARGPARSE_PRINT_WARNING; else pargs.err = ARGPARSE_PRINT_ERROR; break; } } gpgrt_argparse (NULL, &pargs, NULL); if (!last_configname) config_filename = gpgrt_fnameconcat (gnupg_homedir (), "keyboxd" EXTSEP_S "conf", NULL); else { config_filename = last_configname; last_configname = NULL; } if (log_get_errorcount(0)) exit (2); /* Get a default log file from common.conf. */ if (!logfile && !parse_comopt (GNUPG_MODULE_NAME_KEYBOXD, debug_argparser)) { logfile = comopt.logfile; comopt.logfile = NULL; } 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 /* keyboxd 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) { /* We have been called without any command and thus we merely * check whether an instance of us 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_kbxd (0); kbxd_exit (0); } set_debug (); if (atexit (cleanup)) { log_error ("atexit failed\n"); cleanup (); exit (1); } /* Try to create missing directories. */ create_directories (); if (debug_wait && pipe_server) { thread_init_once (); log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } if (gpgconf_list == 2) kbxd_exit (0); else if (gpgconf_list) { kbxd_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); } 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) ); kbxd_exit (1); } kbxd_init_default_ctrl (ctrl); /* kbxd_set_database (ctrl, "pubring.kbx", 0); */ kbxd_set_database (ctrl, "pubring.db", 0); kbxd_start_command_handler (ctrl, GNUPG_INVALID_FD, 0); kbxd_deinit_default_ctrl (ctrl); xfree (ctrl); } else if (!is_daemon) ; /* NOTREACHED */ else { /* Regular daemon mode. */ gnupg_fd_t fd; #ifndef HAVE_W32_SYSTEM pid_t pid; #endif /* Create the sockets. */ socket_name = create_socket_name (KEYBOXD_SOCK_NAME, 1); fd = create_server_socket (socket_name, 0, &socket_nonce); fflush (NULL); #ifdef HAVE_W32_SYSTEM (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 */ /* 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*/ *socket_name = 0; /* Don't let cleanup() remove the socket - the child should do this from now on */ 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; /* Because we don't support running a program on the command * line we can assume that the inotify things works and thus * we can avoid the regular stat calls. */ 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); } { ctrl_t ctrl; ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); kbxd_exit (1); } kbxd_init_default_ctrl (ctrl); /* kbxd_set_database (ctrl, "pubring.kbx", 0); */ kbxd_set_database (ctrl, "pubring.db", 0); kbxd_deinit_default_ctrl (ctrl); xfree (ctrl); } log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_strusage(13)); handle_connections (fd); assuan_sock_close (fd); } return 0; } /* Exit entry point. This function should be called instead of a plain exit. */ void kbxd_exit (int rc) { /* As usual we run our cleanup handler. */ cleanup (); /* at this time a bit annoying */ if ((opt.debug & DBG_MEMSTAT_VALUE)) gcry_control (GCRYCTL_DUMP_MEMORY_STATS ); 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 ours. */ static void kbxd_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); } /* 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 kbxd_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 kbxd_init_default_ctrl (ctrl_t ctrl) { ctrl->magic = SERVER_CONTROL_MAGIC; } /* Release all resources allocated by default in the control structure. This is the counterpart to kbxd_init_default_ctrl. */ static void kbxd_deinit_default_ctrl (ctrl_t ctrl) { if (!ctrl) return; kbxd_release_session_info (ctrl); ctrl->magic = 0xdeadbeef; unregister_progress_cb (); xfree (ctrl->lc_messages); } /* Reread parts of the configuration. Note, that this function is * obviously not thread-safe and should only be called from the PTH * signal handler. * * Fixme: Due to the way the argument parsing works, we create a * memory leak here for all string type arguments. There is currently * no clean way to tell whether the memory for the argument has been * allocated or points into the process's original arguments. Unless * we have a mechanism to tell this, we need to live on with this. */ static void reread_configuration (void) { gpgrt_argparse_t pargs; char *twopart; int dummy; int logfile_seen = 0; if (!config_filename) goto finish; /* No config file. */ twopart = strconcat ("keyboxd" EXTSEP_S "conf" PATHSEP_S, config_filename, NULL); if (!twopart) return; /* Out of core. */ parse_rereadable_options (NULL, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = (ARGPARSE_FLAG_KEEP |ARGPARSE_FLAG_SYS |ARGPARSE_FLAG_USER); while (gpgrt_argparser (&pargs, opts, twopart)) { if (pargs.r_opt == ARGPARSE_CONFFILE) { log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); } else if (pargs.r_opt < -1) pargs.err = ARGPARSE_PRINT_WARNING; else /* Try to parse this option - ignore unchangeable ones. */ { if (pargs.r_opt == oLogFile) logfile_seen = 1; parse_rereadable_options (&pargs, 1); } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ xfree (twopart); finalize_rereadable_options (); set_debug (); finish: /* Get a default log file from common.conf. */ if (!logfile_seen && !parse_comopt (GNUPG_MODULE_NAME_KEYBOXD, !!opt.debug)) { if (!current_logfile || !comopt.logfile || strcmp (current_logfile, comopt.logfile)) { log_set_file (comopt.logfile); xfree (current_logfile); current_logfile = comopt.logfile? xtrystrdup (comopt.logfile) : NULL; } } } /* Return the file name of the socket we are using for requests. */ const char * get_kbxd_socket_name (void) { const char *s = socket_name; return (s && *s)? s : NULL; } /* Return the number of active connections. */ int get_kbxd_active_connection_count (void) { return active_connections; } /* 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. The * function returns a 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); kbxd_exit (2); } return name; } /* Create a Unix domain socket with NAME. Returns the file descriptor * or terminates the process in case of an error. If CYGWIN is set a * Cygwin compatible socket is created (Windows only). */ static gnupg_fd_t create_server_socket (char *name, int cygwin, assuan_sock_nonce_t *nonce) { struct sockaddr *addr; struct sockaddr_un *unaddr; socklen_t len; gnupg_fd_t fd; int rc; 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(). */ kbxd_exit (2); } if (cygwin) assuan_sock_set_flag (fd, "cygwin", 1); unaddr = xmalloc (sizeof *unaddr); addr = (struct sockaddr*)unaddr; if (assuan_sock_set_sockaddr_un (name, addr, NULL)) { 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); kbxd_exit (2); } 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 keyboxd is already running. */ if (!check_for_running_kbxd (1)) { if (steal_socket) log_info (N_("trying to steal socket from running %s\n"), "keyboxd"); else { log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX); log_set_file (NULL); log_error (_("a keyboxd is already running - " "not starting a new one\n")); *name = 0; /* Inhibit removal of the socket by cleanup(). */ assuan_sock_close (fd); xfree (unaddr); kbxd_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); kbxd_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); kbxd_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 public 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_public_keys_directory (const char *home) { char *fname; struct stat statbuf; fname = make_filename (home, GNUPG_PUBLIC_KEYS_DIR, NULL); if (gnupg_stat (fname, &statbuf) && errno == ENOENT) { if (gnupg_mkdir (fname, "-rwxr-x")) 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, "-rwxr-x")) 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 (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); } } } 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_public_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); /* 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 /* Check whether the homedir is still available. */ if (!shutdown_pending && (!have_homedir_inotify || !reliable_homedir_inotify) && 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 kbxd_sighup_action (void) { log_info ("SIGHUP received - " "re-reading configuration and flushing cache\n"); reread_configuration (); } /* A helper function to handle SIGUSR2. */ static void kbxd_sigusr2_action (void) { if (opt.verbose) log_info ("SIGUSR2 received - no action\n"); /* Nothing to do right now. */ } #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) { case SIGHUP: kbxd_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 ()); */ break; case SIGUSR2: kbxd_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", gpgrt_strusage(11), gpgrt_strusage(13) ); cleanup (); kbxd_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); cleanup (); kbxd_exit (0); break; 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; } static void * do_start_connection_thread (ctrl_t ctrl) { static unsigned int last_session_id; unsigned int session_id; active_connections++; kbxd_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)); session_id = ++last_session_id; if (!session_id) session_id = ++last_session_id; kbxd_start_command_handler (ctrl, ctrl->thread_startup.fd, session_id); if (opt.verbose && !DBG_IPC) log_info (_("handler 0x%lx for fd %d terminated\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); kbxd_deinit_default_ctrl (ctrl); xfree (ctrl); active_connections--; return NULL; } /* This is the standard connection thread's main function. */ static void * start_connection_thread (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); } /* Connection handler loop. Wait for connection requests and spawn a * thread after accepting a connection. */ static void handle_connections (gnupg_fd_t listen_fd) { 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 }, }; 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] = 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; FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); nfd = FD2INT (listen_fd); 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; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; 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 keyboxd 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; } } 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)) kbxd_sigusr2_action (); #endif if (ret == -1 && saved_errno != EINTR) { log_error (_("npth_pselect failed: %s - waiting 1s\n"), strerror (saved_errno)); gnupg_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, KEYBOXD_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 { 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"), gpgrt_strusage(11), gpgrt_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 keyboxd 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 (), KEYBOXD_SOCK_NAME, NULL); if (!sockname) return; /* Out of memory. */ err = npth_attr_init (&tattr); if (err) { xfree (sockname); 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 a keyboxd 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_kbxd (int silent) { gpg_error_t err; char *sockname; assuan_context_t ctx = NULL; sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_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 keyboxd running in this session\n")); if (ctx) assuan_release (ctx); return -1; } if (!opt.quiet && !silent) log_info ("keyboxd running and available\n"); assuan_release (ctx); return 0; } diff --git a/sm/server.c b/sm/server.c index 2a6d7c381..e44856ab9 100644 --- a/sm/server.c +++ b/sm/server.c @@ -1,1577 +1,1564 @@ /* server.c - Server mode and main entry point * Copyright (C) 2001-2010 Free Software Foundation, Inc. * Copyright (C) 2001-2011, 2013-2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include "../common/sysutils.h" #include "../common/server-help.h" #include "../common/asshelp.h" #include "../common/shareddefs.h" #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* The filepointer for status message used in non-server mode */ static FILE *statusfp; /* Data used to assuciate an Assuan context with local server data */ struct server_local_s { assuan_context_t assuan_ctx; int message_fd; int list_internal; int list_external; int list_to_output; /* Write keylistings to the output fd. */ int enable_audit_log; /* Use an audit log. */ certlist_t recplist; certlist_t signerlist; certlist_t default_recplist; /* As set by main() - don't release. */ int allow_pinentry_notify; /* Set if pinentry notifications should be passed back to the client. */ int no_encrypt_to; /* Local version of option. */ }; /* Cookie definition for assuan data line output. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; static int command_has_option (const char *cmd, const char *cmdopt); /* Note that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void strcpy_escaped_plus (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 (s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 0; } /* A write handler used by es_fopencookie to write assuan data lines. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, buffer, size)) { gpg_err_set_errno (EIO); return -1; } return (gpgrt_ssize_t)size; } static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, NULL, 0)) { gpg_err_set_errno (EIO); return -1; } return 0; } static void close_message_fd (ctrl_t ctrl) { if (ctrl->server_local->message_fd != -1) { -#ifdef HAVE_W32CE_SYSTEM -#warning Is this correct for W32/W32CE? -#endif close (ctrl->server_local->message_fd); ctrl->server_local->message_fd = -1; } } /* Start a new audit session if this has been enabled. */ static gpg_error_t start_audit_session (ctrl_t ctrl) { audit_release (ctrl->audit); ctrl->audit = NULL; if (ctrl->server_local->enable_audit_log && !(ctrl->audit = audit_new ()) ) return gpg_error_from_syserror (); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (opt.session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (opt.session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { err = session_env_setenv (opt.session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { err = session_env_setenv (opt.session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { xfree (opt.lc_ctype); opt.lc_ctype = xtrystrdup (value); if (!opt.lc_ctype) err = gpg_error_from_syserror (); } else if (!strcmp (key, "lc-messages")) { xfree (opt.lc_messages); opt.lc_messages = xtrystrdup (value); if (!opt.lc_messages) err = gpg_error_from_syserror (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (opt.session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "include-certs")) { int i = *value? atoi (value) : -1; if (ctrl->include_certs < -2) err = gpg_error (GPG_ERR_ASS_PARAMETER); else ctrl->include_certs = i; } else if (!strcmp (key, "list-mode")) { int i = *value? atoi (value) : 0; if (!i || i == 1) /* default and mode 1 */ { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 0; } else if (i == 2) { ctrl->server_local->list_internal = 0; ctrl->server_local->list_external = 1; } else if (i == 3) { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 1; } else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "list-to-output")) { int i = *value? atoi (value) : 0; ctrl->server_local->list_to_output = i; } else if (!strcmp (key, "with-validation")) { int i = *value? atoi (value) : 0; ctrl->with_validation = i; } else if (!strcmp (key, "with-secret")) { int i = *value? atoi (value) : 0; ctrl->with_secret = i; } else if (!strcmp (key, "validation-model")) { int i = gpgsm_parse_validation_model (value); if ( i >= 0 && i <= 2 ) ctrl->validation_model = i; else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "with-key-data")) { opt.with_key_data = 1; } else if (!strcmp (key, "enable-audit-log")) { int i = *value? atoi (value) : 0; ctrl->server_local->enable_audit_log = i; } else if (!strcmp (key, "allow-pinentry-notify")) { ctrl->server_local->allow_pinentry_notify = 1; } else if (!strcmp (key, "with-ephemeral-keys")) { int i = *value? atoi (value) : 0; ctrl->with_ephemeral_keys = i; } else if (!strcmp (key, "no-encrypt-to")) { ctrl->server_local->no_encrypt_to = 1; } else if (!strcmp (key, "offline")) { /* We ignore this option if gpgsm has been started with --disable-dirmngr (which also sets offline). */ if (!opt.disable_dirmngr) { int i = *value? !!atoi (value) : 1; ctrl->offline = i; } } else if (!strcmp (key, "request-origin")) { if (!opt.request_origin) { int i = parse_request_origin (value); if (i == -1) err = gpg_error (GPG_ERR_INV_VALUE); else opt.request_origin = i; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; gpgsm_release_certlist (ctrl->server_local->recplist); gpgsm_release_certlist (ctrl->server_local->signerlist); ctrl->server_local->recplist = NULL; ctrl->server_local->signerlist = NULL; close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static gpg_error_t input_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->autodetect_encoding = 0; ctrl->is_pem = 0; ctrl->is_base64 = 0; if (strstr (line, "--armor")) ctrl->is_pem = 1; else if (strstr (line, "--base64")) ctrl->is_base64 = 1; else if (strstr (line, "--binary")) ; else ctrl->autodetect_encoding = 1; return 0; } static gpg_error_t output_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->create_pem = 0; ctrl->create_base64 = 0; if (strstr (line, "--armor")) ctrl->create_pem = 1; else if (strstr (line, "--base64")) ctrl->create_base64 = 1; /* just the raw output */ return 0; } static const char hlp_recipient[] = "RECIPIENT \n" "\n" "Set the recipient for the encryption. USERID shall be the\n" "internal representation of the key; the server may accept any other\n" "way of specification [we will support this]. If this is a valid and\n" "trusted recipient the server does respond with OK, otherwise the\n" "return is an ERR with the reason why the recipient can't be used,\n" "the encryption will then not be done for this recipient. If the\n" "policy is not to encrypt at all if not all recipients are valid, the\n" "client has to take care of this. All RECIPIENT commands are\n" "cumulative until a RESET or an successful ENCRYPT command."; static gpg_error_t cmd_recipient (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; if (!ctrl->audit) rc = start_audit_session (ctrl); else rc = 0; if (!rc) rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_signer[] = "SIGNER \n" "\n" "Set the signer's keys for the signature creation. USERID should\n" "be the internal representation of the key; the server may accept any\n" "other way of specification [we will support this]. If this is a\n" "valid and usable signing key the server does respond with OK,\n" "otherwise it returns an ERR with the reason why the key can't be\n" "used, the signing will then not be done for this key. If the policy\n" "is not to sign at all if not all signer keys are valid, the client\n" "has to take care of this. All SIGNER commands are cumulative until\n" "a RESET but they are *not* reset by an SIGN command because it can\n" "be expected that set of signers are used for more than one sign\n" "operation."; static gpg_error_t cmd_signer (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), line, NULL); /* For compatibility reasons we also issue the old code after the new one. */ gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_encrypt[] = "ENCRYPT \n" "\n" "Do the actual encryption process. Takes the plaintext from the INPUT\n" "command, writes to the ciphertext to the file descriptor set with\n" "the OUTPUT command, take the recipients form all the recipients set\n" "so far. If this command fails the clients should try to delete all\n" "output currently done or otherwise mark it as invalid. GPGSM does\n" "ensure that there won't be any security problem with leftover data\n" "on the output in this case.\n" "\n" "This command should in general not fail, as all necessary checks\n" "have been done while setting the recipients. The input and output\n" "pipes are closed."; static gpg_error_t cmd_encrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); certlist_t cl; int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); /* Now add all encrypt-to marked recipients from the default list. */ rc = 0; if (!opt.no_encrypt_to && !ctrl->server_local->no_encrypt_to) { for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next) if (cl->is_encrypt_to) rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert, &ctrl->server_local->recplist, 1); } if (!rc) rc = ctrl->audit? 0 : start_audit_session (ctrl); if (!rc) rc = gpgsm_encrypt (assuan_get_pointer (ctx), ctrl->server_local->recplist, inp_fd, out_fp); es_fclose (out_fp); gpgsm_release_certlist (ctrl->server_local->recplist); ctrl->server_local->recplist = NULL; /* Close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_decrypt[] = "DECRYPT\n" "\n" "This performs the decrypt operation after doing some check on the\n" "internal state. (e.g. that only needed data has been set). Because\n" "it utilizes the GPG-Agent for the session key decryption, there is\n" "no need to ask the client for a protecting passphrase - GPG-Agent\n" "does take care of this by requesting this from the user."; static gpg_error_t cmd_decrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); es_fclose (out_fp); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_verify[] = "VERIFY\n" "\n" "This does a verify operation on the message send to the input FD.\n" "The result is written out using status lines. If an output FD was\n" "given, the signed text will be written to that.\n" "\n" "If the signature is a detached one, the server will inquire about\n" "the signed material and the client must provide it."; static gpg_error_t cmd_verify (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp = NULL; (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); if (out_fd != -1) { out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_verify (assuan_get_pointer (ctx), fd, ctrl->server_local->message_fd, out_fp); es_fclose (out_fp); /* Close and reset the fd. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_sign[] = "SIGN [--detached]\n" "\n" "Sign the data set with the INPUT command and write it to the sink\n" "set by OUTPUT. With \"--detached\", a detached signature is\n" "created (surprise)."; static gpg_error_t cmd_sign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int detached; int rc; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); detached = has_option (line, "--detached"); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, inp_fd, detached, out_fp); es_fclose (out_fp); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_import[] = "IMPORT [--re-import]\n" "\n" "Import the certificates read form the input-fd, return status\n" "message for each imported one. The import checks the validity of\n" "the certificate but not of the entire chain. It is possible to\n" "import expired certificates.\n" "\n" "With the option --re-import the input data is expected to a be a LF\n" "separated list of fingerprints. The command will re-import these\n" "certificates, meaning that they are made permanent by removing\n" "their ephemeral flag."; static gpg_error_t cmd_import (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int reimport = has_option (line, "--re-import"); (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); rc = gpgsm_import (assuan_get_pointer (ctx), fd, reimport); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_export[] = "EXPORT [--data [--armor|--base64]] [--secret [--(raw|pkcs12)] [--] \n" "\n" "Export the certificates selected by PATTERN. With --data the output\n" "is returned using Assuan D lines; the default is to use the sink given\n" "by the last \"OUTPUT\" command. The options --armor or --base64 encode \n" "the output using the PEM respective a plain base-64 format; the default\n" "is a binary format which is only suitable for a single certificate.\n" "With --secret the secret key is exported using the PKCS#8 format,\n" "with --raw using PKCS#1, and with --pkcs12 as full PKCS#12 container."; static gpg_error_t cmd_export (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int use_data; int opt_secret; int opt_raw = 0; int opt_pkcs12 = 0; use_data = has_option (line, "--data"); if (use_data) { /* We need to override any possible setting done by an OUTPUT command. */ ctrl->create_pem = has_option (line, "--armor"); ctrl->create_base64 = has_option (line, "--base64"); } opt_secret = has_option (line, "--secret"); if (opt_secret) { opt_raw = has_option (line, "--raw"); opt_pkcs12 = has_option (line, "--pkcs12"); } line = skip_options (line); /* Break the line down into an strlist_t. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (opt_secret) { if (!list) return set_error (GPG_ERR_NO_DATA, "No key given"); if (!*list->d) { free_strlist (list); return set_error (GPG_ERR_NO_DATA, "No key given"); } if (list->next) return set_error (GPG_ERR_TOO_MANY, "Only one key allowed"); } if (use_data) { estream_t stream; stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!stream) { free_strlist (list); return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, stream, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, stream); es_fclose (stream); } else { int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp; if (fd == -1) { free_strlist (list); return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); } out_fp = es_fdopen_nc (fd, "w"); if (!out_fp) { free_strlist (list); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, out_fp, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, out_fp); es_fclose (out_fp); } free_strlist (list); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static const char hlp_delkeys[] = "DELKEYS \n" "\n" "Delete the certificates specified by PATTERNS. Each pattern shall be\n" "a percent-plus escaped certificate specification. Usually a\n" "fingerprint will be used for this."; static gpg_error_t cmd_delkeys (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int rc; /* break the line down into an strlist_t */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } rc = gpgsm_delete (ctrl, list); free_strlist (list); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_output[] = "OUTPUT FD[=]\n" "\n" "Set the file descriptor to write the output data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"INPUT\" and \"MESSAGE\" commands."; static const char hlp_input[] = "INPUT FD[=]\n" "\n" "Set the file descriptor to read the input data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"MESSAGE\" and \"OUTPUT\" commands."; static const char hlp_message[] = "MESSAGE FD[=]\n" "\n" "Set the file descriptor to read the message for a detached\n" "signatures to N. If N is not given and the operating system\n" "supports file descriptor passing, the file descriptor currently in\n" "flight will be used. See also the \"INPUT\" and \"OUTPUT\" commands."; static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { int rc; gnupg_fd_t sysfd; int fd; ctrl_t ctrl = assuan_get_pointer (ctx); rc = assuan_command_parse_fd (ctx, line, &sysfd); if (rc) return rc; -#ifdef HAVE_W32CE_SYSTEM - sysfd = _assuan_w32ce_finish_pipe ((int)sysfd, 0); - if (sysfd == INVALID_HANDLE_VALUE) - return set_error (gpg_err_code_from_syserror (), - "rvid conversion failed"); -#endif fd = translate_sys2libc_fd (sysfd, 0); if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); ctrl->server_local->message_fd = fd; return 0; } static const char hlp_listkeys[] = "LISTKEYS [] []\n" "LISTSECRETKEYS [] []\n" "DUMPKEYS [] []\n" "DUMPSECRETKEYS [] []\n" "\n" "List all certificates or only those specified by PATTERNS. Each\n" "pattern shall be a percent-plus escaped certificate specification.\n" "The \"SECRET\" versions of the command filter the output to include\n" "only certificates where the secret key is available or a corresponding\n" "smartcard has been registered. The \"DUMP\" versions of the command\n" "are only useful for debugging. The output format is a percent escaped\n" "colon delimited listing as described in the manual.\n" "Supported values for OPTIONS are:\n" " -- Stop option processing\n" " --issuer-der PATTERN is a DER of the serialnumber as hexstring;\n" " the issuer is then inquired with \"ISSUER_DER\".\n" "\n" "These Assuan \"OPTION\" command keys effect the output::\n" "\n" " \"list-mode\" set to 0: List only local certificates (default).\n" " 1: Ditto.\n" " 2: List only external certificates.\n" " 3: List local and external certificates.\n" "\n" " \"with-validation\" set to true: Validate each certificate.\n" "\n" " \"with-ephemeral-key\" set to true: Always include ephemeral\n" " certificates.\n" "\n" " \"list-to-output\" set to true: Write output to the file descriptor\n" " given by the last \"OUTPUT\" command."; static int do_listkeys (assuan_context_t ctx, char *line, int mode) { ctrl_t ctrl = assuan_get_pointer (ctx); estream_t fp; char *p; size_t n; strlist_t list, sl; unsigned int listmode; gpg_error_t err; int opt_issuer_der; opt_issuer_der = has_option (line, "--issuer-der"); line = skip_options (line); /* Break the line down into an strlist. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (opt_issuer_der && (!list || list->next)) { free_strlist (list); return set_error (GPG_ERR_INV_ARG, "only one arg for --issuer-der please"); } if (opt_issuer_der) { unsigned char *value = NULL; size_t valuelen; char *issuer; err = assuan_inquire (ctx, "ISSUER_DER", &value, &valuelen, 0); if (err) { free_strlist (list); return err; } if (!valuelen) { xfree (value); free_strlist (list); return gpg_error (GPG_ERR_MISSING_VALUE); } err = ksba_dn_der2str (value, valuelen, &issuer); xfree (value); if (err) { free_strlist (list); return err; } /* ksba_dn_der2str seems to always append "\\0A". Trim that. */ n = strlen (issuer); if (n > 3 && !strcmp (issuer + n - 3, "\\0A")) issuer[n-3] = 0; p = strconcat ("#", list->d, "/", issuer, NULL); if (!p) { err = gpg_error_from_syserror (); ksba_free (issuer); free_strlist (list); return err; } ksba_free (issuer); free_strlist (list); list = NULL; if (!add_to_strlist_try (&list, p)) { err = gpg_error_from_syserror (); xfree (p); return err; } xfree (p); } if (ctrl->server_local->list_to_output) { int outfd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if ( outfd == -1 ) { free_strlist (list); return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); } fp = es_fdopen_nc (outfd, "w"); if (!fp) { free_strlist (list); return set_error (gpg_err_code_from_syserror (), "es_fdopen() failed"); } } else { fp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!fp) { free_strlist (list); return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } } ctrl->with_colons = 1; listmode = mode; if (ctrl->server_local->list_internal) listmode |= (1<<6); if (ctrl->server_local->list_external) listmode |= (1<<7); err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); free_strlist (list); es_fclose (fp); if (ctrl->server_local->list_to_output) assuan_close_output_fd (ctx); return err; } static gpg_error_t cmd_listkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 3); } static gpg_error_t cmd_dumpkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 259); } static gpg_error_t cmd_listsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 2); } static gpg_error_t cmd_dumpsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 258); } static const char hlp_genkey[] = "GENKEY\n" "\n" "Read the parameters in native format from the input fd and write a\n" "certificate request to the output."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t in_stream, out_stream; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); in_stream = es_fdopen_nc (inp_fd, "r"); if (!in_stream) return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed"); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { es_fclose (in_stream); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = gpgsm_genkey (ctrl, in_stream, out_stream); es_fclose (out_stream); es_fclose (in_stream); /* close and reset the fds */ assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_getauditlog[] = "GETAUDITLOG [--data] [--html]\n" "\n" "If --data is used, the output is send using D-lines and not to the\n" "file descriptor given by an OUTPUT command.\n" "\n" "If --html is used the output is formatted as an XHTML block. This is\n" "designed to be incorporated into a HTML document."; static gpg_error_t cmd_getauditlog (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int out_fd; estream_t out_stream; int opt_data, opt_html; int rc; opt_data = has_option (line, "--data"); opt_html = has_option (line, "--html"); /* Not needed: line = skip_options (line); */ if (!ctrl->audit) return gpg_error (GPG_ERR_NO_DATA); if (opt_data) { out_stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!out_stream) return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } else { out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed"); } } audit_print_result (ctrl->audit, out_stream, opt_html); rc = 0; es_fclose (out_stream); /* Close and reset the fd. */ if (!opt_data) assuan_close_output_fd (ctx); return rc; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " agent-check - Return success if the agent is running.\n" " cmd_has_option CMD OPT\n" " - Returns OK if the command CMD implements the option OPT.\n" " offline - Returns OK if the connection is in offline mode."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "agent-check")) { rc = gpgsm_agent_send_nop (ctrl); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) rc = gpg_error (GPG_ERR_FALSE); } } } } else if (!strcmp (line, "offline")) { rc = ctrl->offline? 0 : gpg_error (GPG_ERR_FALSE); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } static const char hlp_passwd[] = "PASSWD \n" "\n" "Change the passphrase of the secret key for USERID."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; char *grip = NULL; line = skip_options (line); err = gpgsm_find_cert (ctrl, line, NULL, &cert, 0); if (err) ; else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) err = gpg_error (GPG_ERR_INTERNAL); else { char *desc = gpgsm_format_keydesc (cert); err = gpgsm_agent_passwd (ctrl, grip, desc); xfree (desc); } xfree (grip); ksba_cert_release (cert); return err; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "IMPORT")) { if (!strcmp (cmdopt, "re-import")) return 1; } return 0; } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "RECIPIENT", cmd_recipient, hlp_recipient }, { "SIGNER", cmd_signer, hlp_signer }, { "ENCRYPT", cmd_encrypt, hlp_encrypt }, { "DECRYPT", cmd_decrypt, hlp_decrypt }, { "VERIFY", cmd_verify, hlp_verify }, { "SIGN", cmd_sign, hlp_sign }, { "IMPORT", cmd_import, hlp_import }, { "EXPORT", cmd_export, hlp_export }, { "INPUT", NULL, hlp_input }, { "OUTPUT", NULL, hlp_output }, { "MESSAGE", cmd_message, hlp_message }, { "LISTKEYS", cmd_listkeys, hlp_listkeys }, { "DUMPKEYS", cmd_dumpkeys, hlp_listkeys }, { "LISTSECRETKEYS",cmd_listsecretkeys, hlp_listkeys }, { "DUMPSECRETKEYS",cmd_dumpsecretkeys, hlp_listkeys }, { "GENKEY", cmd_genkey, hlp_genkey }, { "DELKEYS", cmd_delkeys, hlp_delkeys }, { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "PASSWD", cmd_passwd, hlp_passwd }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Startup the server. DEFAULT_RECPLIST is the list of recipients as set from the command line or config file. We only require those marked as encrypt-to. */ void gpgsm_server (certlist_t default_recplist) { int rc; assuan_fd_t filedes[2]; assuan_context_t ctx; struct server_control_s ctrl; static const char hello[] = ("GNU Privacy Guard's S/M server " VERSION " ready"); memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); /* We use a pipe based server so that we can work from scripts. assuan_init_pipe_server will automagically detect when we are called with a socketpair and ignore FILEDES in this case. */ -#ifdef HAVE_W32CE_SYSTEM - #define SERVER_STDIN es_fileno(es_stdin) - #define SERVER_STDOUT es_fileno(es_stdout) -#else #define SERVER_STDIN 0 #define SERVER_STDOUT 1 -#endif + filedes[0] = assuan_fdopen (SERVER_STDIN); filedes[1] = assuan_fdopen (SERVER_STDOUT); rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = assuan_init_pipe_server (ctx, filedes); if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to the register commands with Assuan: %s\n", gpg_strerror(rc)); gpgsm_exit (2); } if (opt.verbose || opt.debug) { char *tmp; /* Fixme: Use the really used socket name. */ if (asprintf (&tmp, "Home: %s\n" "Config: %s\n" "DirmngrInfo: %s\n" "%s", gnupg_homedir (), opt.config_filename, dirmngr_socket_name (), hello) > 0) { assuan_set_hello_line (ctx, tmp); free (tmp); } } else assuan_set_hello_line (ctx, hello); assuan_register_reset_notify (ctx, reset_notify); assuan_register_input_notify (ctx, input_notify); assuan_register_output_notify (ctx, output_notify); assuan_register_option_handler (ctx, option_handler); assuan_set_pointer (ctx, &ctrl); ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); ctrl.server_local->assuan_ctx = ctx; ctrl.server_local->message_fd = -1; ctrl.server_local->list_internal = 1; ctrl.server_local->list_external = 0; ctrl.server_local->default_recplist = default_recplist; for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } gpgsm_release_certlist (ctrl.server_local->recplist); ctrl.server_local->recplist = NULL; gpgsm_release_certlist (ctrl.server_local->signerlist); ctrl.server_local->signerlist = NULL; xfree (ctrl.server_local); audit_release (ctrl.audit); ctrl.audit = NULL; gpgsm_deinit_default_ctrl (&ctrl); assuan_release (ctx); } gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, no); if (ctrl->no_server && ctrl->status_fd == -1) ; /* No status wanted. */ else if (ctrl->no_server) { if (!statusfp) { if (ctrl->status_fd == 1) statusfp = stdout; else if (ctrl->status_fd == 2) statusfp = stderr; else statusfp = fdopen (ctrl->status_fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", ctrl->status_fd, strerror(errno)); } } fputs ("[GNUPG:] ", statusfp); fputs (get_status_string (no), statusfp); while ( (text = va_arg (arg_ptr, const char*) )) { putc ( ' ', statusfp ); for (; *text; text++) { if (*text == '\n') fputs ( "\\n", statusfp ); else if (*text == '\r') fputs ( "\\r", statusfp ); else putc ( *(const byte *)text, statusfp ); } } putc ('\n', statusfp); fflush (statusfp); } else { err = vprint_assuan_status_strings (ctrl->server_local->assuan_ctx, get_status_string (no), arg_ptr); } va_end (arg_ptr); return err; } gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text) { return gpgsm_status2 (ctrl, no, text, NULL); } gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec) { char buf[30]; sprintf (buf, "%u", (unsigned int)ec); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } gpg_error_t gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text, gpg_error_t err) { char buf[30]; snprintf (buf, sizeof buf, "%u", err); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } /* Helper to notify the client about Pinentry events. Because that might disturb some older clients, this is only done when enabled via an option. Returns an gpg error code. */ gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); } diff --git a/tools/Makefile.am b/tools/Makefile.am index 9e9e13eda..a6979ebf1 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,228 +1,210 @@ # Makefile.am - Tools directory # Copyright (C) 2003, 2007 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 . EXTRA_DIST = \ Manifest watchgnupg.c no-libgcrypt.c \ addgnupghome applygnupgdefaults \ lspgpot mail-signed-keys convert-from-106 sockprox.c \ ccidmon.c ChangeLog-2011 \ gpg-connect-agent-w32info.rc gpg-connect-agent.w32-manifest.in \ gpgconf-w32info.rc gpgconf.w32-manifest.in \ gpgtar-w32info.rc gpgtar.w32-manifest.in \ gpg-check-pattern-w32info.rc gpg-check-pattern.w32-manifest.in \ gpg-wks-client-w32info.rc gpg-wks-client.w32-manifest.in \ gpg-card-w32info.rc gpg-card.w32-manifest.in AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am if HAVE_W32_SYSTEM gpg_connect_agent_rc_objs = gpg-connect-agent-w32info.o gpgconf_rc_objs = gpgconf-w32info.o gpg_card_rc_objs = gpg-card-w32info.o gpgtar_rc_objs = gpgtar-w32info.o gpg_check_pattern_rc_objs = gpg-check-pattern-w32info.o gpg_wks_client_rc_objs = gpg-wks-client-w32info.o resource_objs += $(gpg_connect_agent_rc_objs) resource_objs += $(gpgconf_rc_objs) resource_objs += $(gpg_card_tool_rc_objs) resource_objs += $(gpgtar_rc_objs) resource_objs += $(gpg_check_pattern_rc_objs) resource_objs += $(gpg_wks_client_rc_objs) endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) sbin_SCRIPTS = addgnupghome applygnupgdefaults if BUILD_WKS_TOOLS gpg_wks_server = gpg-wks-server else gpg_wks_server = endif bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card gpg-wks-client if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} gpgsplit else bin_PROGRAMS += gpgconf-w32 endif libexec_PROGRAMS = gpg-check-pattern gpg-pair-tool -if !HAVE_W32CE_SYSTEM noinst_PROGRAMS = clean-sat make-dns-cert -endif -if !HAVE_W32CE_SYSTEM if BUILD_GPGTAR bin_PROGRAMS += gpgtar else noinst_PROGRAMS += gpgtar endif -endif common_libs = $(libcommon) commonpth_libs = $(libcommonpth) -# Some modules require PTH under W32CE. -if HAVE_W32CE_SYSTEM -maybe_commonpth_libs = $(commonpth_libs) -else -maybe_commonpth_libs = $(common_libs) -endif - -if HAVE_W32CE_SYSTEM -pwquery_libs = -else pwquery_libs = ../common/libsimple-pwquery.a -endif -if HAVE_W32CE_SYSTEM -opt_libassuan_libs = $(LIBASSUAN_LIBS) -endif regexp_libs = ../regexp/libregexp.a gpgsplit_LDADD = $(common_libs) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(ZLIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c # common sucks in gpg-error, will they, nil they (some compilers # do not eliminate the supposed-to-be-unused-inline-functions). -gpgconf_LDADD = $(maybe_commonpth_libs) $(opt_libassuan_libs) \ +gpgconf_LDADD = $(common_libs) \ $(LIBINTL) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(NETLIBS) \ $(LIBICONV) $(W32SOCKLIBS) \ $(gpgconf_rc_objs) gpgconf_LDFLAGS = $(extra_bin_ldflags) gpgconf_w32_SOURCES = $(gpgconf_SOURCES) gpgconf_w32_LDADD = $(gpgconf_LDADD) gpgconf_w32_LDFLAGS = $(gpgconf_LDFLAGS) -Wl,-subsystem,windows gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h gpgparsemail_LDADD = watchgnupg_SOURCES = watchgnupg.c watchgnupg_LDADD = $(NETLIBS) gpg_connect_agent_SOURCES = gpg-connect-agent.c gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ $(gpg_connect_agent_rc_objs) gpg_card_SOURCES = \ gpg-card.c \ gpg-card.h \ card-call-scd.c \ card-keys.c \ card-yubikey.c \ card-misc.c gpg_card_LDADD = \ ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ $(gpg_card_rc_objs) gpg_check_pattern_SOURCES = gpg-check-pattern.c gpg_check_pattern_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_check_pattern_LDADD = $(common_libs) $(regexp_libs) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ $(LIBICONV) \ $(gpg_check_pattern_rc_objs) gpgtar_SOURCES = \ gpgtar.c gpgtar.h \ gpgtar-create.c \ gpgtar-extract.c \ gpgtar-list.c gpgtar_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) gpgtar_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ $(gpgtar_rc_objs) gpg_wks_server_SOURCES = \ gpg-wks-server.c \ gpg-wks.h \ wks-util.c \ wks-receive.c \ rfc822parse.c rfc822parse.h \ mime-parser.c mime-parser.h \ mime-maker.c mime-maker.h \ send-mail.c send-mail.h gpg_wks_server_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_wks_server_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) gpg_wks_client_SOURCES = \ gpg-wks-client.c \ gpg-wks.h \ wks-util.c \ wks-receive.c \ rfc822parse.c rfc822parse.h \ mime-parser.c mime-parser.h \ mime-maker.h mime-maker.c \ send-mail.c send-mail.h \ call-dirmngr.c call-dirmngr.h gpg_wks_client_CFLAGS = $(LIBASSUAN_CFLAGS) $(LIBGCRYPT_CFLAGS) \ $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_wks_client_LDADD = $(libcommon) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) $(NETLIBS) \ $(gpg_wks_client_rc_objs) gpg_pair_tool_SOURCES = \ gpg-pair-tool.c gpg_pair_tool_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_pair_tool_LDADD = $(libcommon) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) # Instead of a symlink we install a simple wrapper script for the new # gpg-wks-client location. We assume bin is a sibling of libexec. install-exec-local: $(mkinstalldirs) $(DESTDIR)$(libexecdir) (set -e ;\ if [ "$(libexecdir)" != "$(bindir)" ]; then \ printf '#!/bin/sh\nexec "$(bindir)/gpg-wks-client" "$$@"\n' \ > $(DESTDIR)$(libexecdir)/gpg-wks-client ;\ chmod +x $(DESTDIR)$(libexecdir)/gpg-wks-client ;\ fi ) uninstall-local: (if [ "$(libexecdir)" != "$(bindir)" ]; then \ rm $(DESTDIR)$(libexecdir)/gpg-wks-client || true ;\ fi ) # Make sure that all libs are build before we use them. This is # important for things like make -j2. $(PROGRAMS): $(common_libs) $(pwquery_libs) ../common/libgpgrl.a diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index 6bbdc816b..dff367050 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -1,2343 +1,2334 @@ /* gpg-connect-agent.c - Tool to connect to the agent. * Copyright (C) 2005, 2007, 2008, 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include #include "../common/i18n.h" #include "../common/util.h" #include "../common/asshelp.h" #include "../common/sysutils.h" #include "../common/membuf.h" #include "../common/ttyio.h" #ifdef HAVE_W32_SYSTEM # include "../common/exechelp.h" #endif #include "../common/init.h" #define CONTROL_D ('D' - 'A' + 1) #define octdigitp(p) (*(p) >= '0' && *(p) <= '7') #define HISTORYNAME ".gpg-connect_history" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oRawSocket = 'S', oTcpSocket = 'T', oExec = 'E', oRun = 'r', oSubst = 's', oUnBuffered = 'u', oNoVerbose = 500, oHomedir, oAgentProgram, oDirmngrProgram, oKeyboxdProgram, oHex, oDecode, oNoExtConnect, oDirmngr, oKeyboxd, oUIServer, oNoHistory, oNoAutostart, oChUid, oNoop }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("quiet")), ARGPARSE_s_n (oHex, "hex", N_("print data out hex encoded")), ARGPARSE_s_n (oDecode,"decode", N_("decode received data lines")), ARGPARSE_s_n (oDirmngr,"dirmngr", N_("connect to the dirmngr")), ARGPARSE_s_n (oKeyboxd,"keyboxd", N_("connect to the keyboxd")), ARGPARSE_s_n (oUIServer, "uiserver", "@"), ARGPARSE_s_s (oRawSocket, "raw-socket", N_("|NAME|connect to Assuan socket NAME")), ARGPARSE_s_s (oTcpSocket, "tcp-socket", N_("|ADDR|connect to Assuan server at ADDR")), ARGPARSE_s_n (oExec, "exec", N_("run the Assuan server given on the command line")), ARGPARSE_s_n (oNoExtConnect, "no-ext-connect", N_("do not use extended connect mode")), ARGPARSE_s_s (oRun, "run", N_("|FILE|run commands from FILE on startup")), ARGPARSE_s_n (oSubst, "subst", N_("run /subst on startup")), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), ARGPARSE_s_n (oNoHistory,"no-history", "do not use the command history file"), ARGPARSE_s_s (oHomedir, "homedir", "@" ), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_s_n (oUnBuffered, "unbuffered", N_("set stdin/out unbuffered")), ARGPARSE_end () }; /* We keep all global options in the structure OPT. */ struct { int verbose; /* Verbosity level. */ int quiet; /* Be extra quiet. */ int autostart; /* Start the server if not running. */ const char *homedir; /* Configuration directory name */ const char *agent_program; /* Value of --agent-program. */ const char *dirmngr_program; /* Value of --dirmngr-program. */ const char *keyboxd_program; /* Value of --keyboxd-program. */ int hex; /* Print data lines in hex format. */ int decode; /* Decode received data lines. */ int use_dirmngr; /* Use the dirmngr and not gpg-agent. */ int use_keyboxd; /* Use the keyboxd and not gpg-agent. */ int use_uiserver; /* Use the standard UI server. */ const char *raw_socket; /* Name of socket to connect in raw mode. */ const char *tcp_socket; /* Name of server to connect in tcp mode. */ int exec; /* Run the pgm given on the command line. */ unsigned int connect_flags; /* Flags used for connecting. */ int enable_varsubst; /* Set if variable substitution is enabled. */ int trim_leading_spaces; int no_history; int unbuffered; /* Set if unbuffered mode for stdin/out is preferred. */ } opt; /* Definitions for /definq commands and a global linked list with all the definitions. */ struct definq_s { struct definq_s *next; char *name; /* Name of inquiry or NULL for any name. */ int is_var; /* True if FILE is a variable name. */ int is_prog; /* True if FILE is a program to run. */ char file[1]; /* Name of file or program. */ }; typedef struct definq_s *definq_t; static definq_t definq_list; static definq_t *definq_list_tail = &definq_list; /* Variable definitions and glovbal table. */ struct variable_s { struct variable_s *next; char *value; /* Malloced value - always a string. */ char name[1]; /* Name of the variable. */ }; typedef struct variable_s *variable_t; static variable_t variable_table; /* To implement loops we store entire lines in a linked list. */ struct loopline_s { struct loopline_s *next; char line[1]; }; typedef struct loopline_s *loopline_t; /* This is used to store the pid of the server. */ static pid_t server_pid = (pid_t)(-1); /* The current datasink file or NULL. */ static estream_t current_datasink; /* A list of open file descriptors. */ static struct { int inuse; #ifdef HAVE_W32_SYSTEM HANDLE handle; #endif } open_fd_table[256]; /*-- local prototypes --*/ static char *substitute_line_copy (const char *buffer); static int read_and_print_response (assuan_context_t ctx, int withhash, int *r_goterr); static assuan_context_t start_agent (void); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPG@-connect-agent (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: @GPG@-connect-agent [options] (-h for help)"); break; case 41: p = _("Syntax: @GPG@-connect-agent [options]\n" "Connect to a running agent and send commands\n"); break; case 31: p = "\nHome: "; break; case 32: p = gnupg_homedir (); break; case 33: p = "\n"; break; default: p = NULL; break; } return p; } /* Unescape STRING and returned the malloced result. The surrounding quotes must already be removed from STRING. */ static char * unescape_string (const char *string) { const unsigned char *s; int esc; size_t n; char *buffer; unsigned char *d; n = 0; for (s = (const unsigned char*)string, esc=0; *s; s++) { if (esc) { switch (*s) { case 'b': case 't': case 'v': case 'n': case 'f': case 'r': case '"': case '\'': case '\\': n++; break; case 'x': if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) n++; break; default: if (s[1] && s[2] && octdigitp (s) && octdigitp (s+1) && octdigitp (s+2)) n++; break; } esc = 0; } else if (*s == '\\') esc = 1; else n++; } buffer = xmalloc (n+1); d = (unsigned char*)buffer; for (s = (const unsigned char*)string, esc=0; *s; s++) { if (esc) { switch (*s) { case 'b': *d++ = '\b'; break; case 't': *d++ = '\t'; break; case 'v': *d++ = '\v'; break; case 'n': *d++ = '\n'; break; case 'f': *d++ = '\f'; break; case 'r': *d++ = '\r'; break; case '"': *d++ = '\"'; break; case '\'': *d++ = '\''; break; case '\\': *d++ = '\\'; break; case 'x': if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) { s++; *d++ = xtoi_2 (s); s++; } break; default: if (s[1] && s[2] && octdigitp (s) && octdigitp (s+1) && octdigitp (s+2)) { *d++ = (atoi_1 (s)*64) + (atoi_1 (s+1)*8) + atoi_1 (s+2); s += 2; } break; } esc = 0; } else if (*s == '\\') esc = 1; else *d++ = *s; } *d = 0; return buffer; } /* Do the percent unescaping and return a newly malloced string. If WITH_PLUS is set '+' characters will be changed to space. */ static char * unpercent_string (const char *string, int with_plus) { const unsigned char *s; unsigned char *buffer, *p; size_t n; n = 0; for (s=(const unsigned char *)string; *s; s++) { if (*s == '%' && s[1] && s[2]) { s++; n++; s++; } else if (with_plus && *s == '+') n++; else n++; } buffer = xmalloc (n+1); p = buffer; for (s=(const unsigned char *)string; *s; s++) { if (*s == '%' && s[1] && s[2]) { s++; *p++ = xtoi_2 (s); s++; } else if (with_plus && *s == '+') *p++ = ' '; else *p++ = *s; } *p = 0; return (char*)buffer; } static const char * set_var (const char *name, const char *value) { variable_t var; for (var = variable_table; var; var = var->next) if (!strcmp (var->name, name)) break; if (!var) { var = xmalloc (sizeof *var + strlen (name)); var->value = NULL; strcpy (var->name, name); var->next = variable_table; variable_table = var; } xfree (var->value); var->value = value? xstrdup (value) : NULL; return var->value; } static void set_int_var (const char *name, int value) { char numbuf[35]; snprintf (numbuf, sizeof numbuf, "%d", value); set_var (name, numbuf); } /* Return the value of a variable. That value is valid until a variable of the name is changed. Return NULL if not found. Note that envvars are copied to our variable list at the first access and not at oprogram start. */ static const char * get_var (const char *name) { variable_t var; const char *s; if (!*name) return ""; for (var = variable_table; var; var = var->next) if (!strcmp (var->name, name)) break; if (!var && (s = getenv (name))) return set_var (name, s); if (!var || !var->value) return NULL; return var->value; } /* Perform some simple arithmetic operations. Caller must release the return value. On error the return value is NULL. */ static char * arithmetic_op (int operator, const char *operands) { long result, value; char numbuf[35]; while ( spacep (operands) ) operands++; if (!*operands) return NULL; result = strtol (operands, NULL, 0); while (*operands && !spacep (operands) ) operands++; if (operator == '!') result = !result; while (*operands) { while ( spacep (operands) ) operands++; if (!*operands) break; value = strtol (operands, NULL, 0); while (*operands && !spacep (operands) ) operands++; switch (operator) { case '+': result += value; break; case '-': result -= value; break; case '*': result *= value; break; case '/': if (!value) return NULL; result /= value; break; case '%': if (!value) return NULL; result %= value; break; case '!': result = !value; break; case '|': result = result || value; break; case '&': result = result && value; break; default: log_error ("unknown arithmetic operator '%c'\n", operator); return NULL; } } snprintf (numbuf, sizeof numbuf, "%ld", result); return xstrdup (numbuf); } /* Extended version of get_var. This returns a malloced string and understand the function syntax: "func args". Defined functions are get - Return a value described by the next argument: cwd - The current working directory. homedir - The gnupg homedir. sysconfdir - GnuPG's system configuration directory. bindir - GnuPG's binary directory. libdir - GnuPG's library directory. libexecdir - GnuPG's library directory for executable files. datadir - GnuPG's data directory. serverpid - The PID of the current server. unescape ARGS Remove C-style escapes from string. Note that "\0" and "\x00" terminate the string implicitly. Use "\x7d" to represent the closing brace. The args start right after the first space after the function name. unpercent ARGS unpercent+ ARGS Remove percent style ecaping from string. Note that "%00 terminates the string implicitly. Use "%7d" to represetn the closing brace. The args start right after the first space after the function name. "unpercent+" also maps '+' to space. percent ARGS percent+ ARGS Escape the args using the percent style. Tabs, formfeeds, linefeeds, carriage return, and the plus sign are also escaped. "percent+" also maps spaces to plus characters. errcode ARG Assuming ARG is an integer, return the gpg-error code. errsource ARG Assuming ARG is an integer, return the gpg-error source. errstring ARG Assuming ARG is an integer return a formatted fpf error string. Example: get_var_ext ("get sysconfdir") -> "/etc/gnupg" */ static char * get_var_ext (const char *name) { static int recursion_count; const char *s; char *result; char *p; char *free_me = NULL; int intvalue; if (recursion_count > 50) { log_error ("variables nested too deeply\n"); return NULL; } recursion_count++; free_me = opt.enable_varsubst? substitute_line_copy (name) : NULL; if (free_me) name = free_me; for (s=name; *s && !spacep (s); s++) ; if (!*s) { s = get_var (name); result = s? xstrdup (s): NULL; } else if ( (s - name) == 3 && !strncmp (name, "get", 3)) { while ( spacep (s) ) s++; if (!strcmp (s, "cwd")) { result = gnupg_getcwd (); if (!result) log_error ("getcwd failed: %s\n", strerror (errno)); } else if (!strcmp (s, "homedir")) result = xstrdup (gnupg_homedir ()); else if (!strcmp (s, "sysconfdir")) result = xstrdup (gnupg_sysconfdir ()); else if (!strcmp (s, "bindir")) result = xstrdup (gnupg_bindir ()); else if (!strcmp (s, "libdir")) result = xstrdup (gnupg_libdir ()); else if (!strcmp (s, "libexecdir")) result = xstrdup (gnupg_libexecdir ()); else if (!strcmp (s, "datadir")) result = xstrdup (gnupg_datadir ()); else if (!strcmp (s, "serverpid")) result = xasprintf ("%d", (int)server_pid); else { log_error ("invalid argument '%s' for variable function 'get'\n", s); log_info ("valid are: cwd, " "{home,bin,lib,libexec,data}dir, serverpid\n"); result = NULL; } } else if ( (s - name) == 8 && !strncmp (name, "unescape", 8)) { s++; result = unescape_string (s); } else if ( (s - name) == 9 && !strncmp (name, "unpercent", 9)) { s++; result = unpercent_string (s, 0); } else if ( (s - name) == 10 && !strncmp (name, "unpercent+", 10)) { s++; result = unpercent_string (s, 1); } else if ( (s - name) == 7 && !strncmp (name, "percent", 7)) { s++; result = percent_escape (s, "+\t\r\n\f\v"); } else if ( (s - name) == 8 && !strncmp (name, "percent+", 8)) { s++; result = percent_escape (s, "+\t\r\n\f\v"); for (p=result; *p; p++) if (*p == ' ') *p = '+'; } else if ( (s - name) == 7 && !strncmp (name, "errcode", 7)) { s++; intvalue = (int)strtol (s, NULL, 0); result = xasprintf ("%d", gpg_err_code (intvalue)); } else if ( (s - name) == 9 && !strncmp (name, "errsource", 9)) { s++; intvalue = (int)strtol (s, NULL, 0); result = xasprintf ("%d", gpg_err_source (intvalue)); } else if ( (s - name) == 9 && !strncmp (name, "errstring", 9)) { s++; intvalue = (int)strtol (s, NULL, 0); result = xasprintf ("%s <%s>", gpg_strerror (intvalue), gpg_strsource (intvalue)); } else if ( (s - name) == 1 && strchr ("+-*/%!|&", *name)) { result = arithmetic_op (*name, s+1); } else { log_error ("unknown variable function '%.*s'\n", (int)(s-name), name); result = NULL; } xfree (free_me); recursion_count--; return result; } /* Substitute variables in LINE and return a new allocated buffer if required. The function might modify LINE if the expanded version fits into it. */ static char * substitute_line (char *buffer) { char *line = buffer; char *p, *pend; const char *value; size_t valuelen, n; char *result = NULL; char *freeme = NULL; while (*line) { p = strchr (line, '$'); if (!p) return result; /* No more variables. */ if (p[1] == '$') /* Escaped dollar sign. */ { memmove (p, p+1, strlen (p+1)+1); line = p + 1; continue; } if (p[1] == '{') { int count = 0; for (pend=p+2; *pend; pend++) { if (*pend == '{') count++; else if (*pend == '}') { if (--count < 0) break; } } if (!*pend) return result; /* Unclosed - don't substitute. */ } else { for (pend=p+1; *pend && !spacep (pend) && *pend != '$' ; pend++) ; } if (p[1] == '{' && *pend == '}') { int save = *pend; *pend = 0; freeme = get_var_ext (p+2); value = freeme; *pend++ = save; } else if (*pend) { int save = *pend; *pend = 0; value = get_var (p+1); *pend = save; } else value = get_var (p+1); if (!value) value = ""; valuelen = strlen (value); if (valuelen <= pend - p) { memcpy (p, value, valuelen); p += valuelen; n = pend - p; if (n) memmove (p, p+n, strlen (p+n)+1); line = p; } else { char *src = result? result : buffer; char *dst; dst = xmalloc (strlen (src) + valuelen + 1); n = p - src; memcpy (dst, src, n); memcpy (dst + n, value, valuelen); n += valuelen; strcpy (dst + n, pend); line = dst + n; xfree (result); result = dst; } xfree (freeme); freeme = NULL; } return result; } /* Same as substitute_line but do not modify BUFFER. */ static char * substitute_line_copy (const char *buffer) { char *result, *p; p = xstrdup (buffer?buffer:""); result = substitute_line (p); if (!result) result = p; else xfree (p); return result; } static void assign_variable (char *line, int syslet) { char *name, *p, *tmp, *free_me, *buffer; /* Get the name. */ name = line; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; if (!*p) set_var (name, NULL); /* Remove variable. */ else if (syslet) { free_me = opt.enable_varsubst? substitute_line_copy (p) : NULL; if (free_me) p = free_me; buffer = xmalloc (4 + strlen (p) + 1); strcpy (stpcpy (buffer, "get "), p); tmp = get_var_ext (buffer); xfree (buffer); set_var (name, tmp); xfree (tmp); xfree (free_me); } else { tmp = opt.enable_varsubst? substitute_line_copy (p) : NULL; if (tmp) { set_var (name, tmp); xfree (tmp); } else set_var (name, p); } } static void show_variables (void) { variable_t var; for (var = variable_table; var; var = var->next) if (var->value) printf ("%-20s %s\n", var->name, var->value); } /* Store an inquire response pattern. Note, that this function may change the content of LINE. We assume that leading white spaces are already removed. */ static void add_definq (char *line, int is_var, int is_prog) { definq_t d; char *name, *p; /* Get name. */ name = line; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; d = xmalloc (sizeof *d + strlen (p) ); strcpy (d->file, p); d->is_var = is_var; d->is_prog = is_prog; if ( !strcmp (name, "*")) d->name = NULL; else d->name = xstrdup (name); d->next = NULL; *definq_list_tail = d; definq_list_tail = &d->next; } /* Show all inquiry definitions. */ static void show_definq (void) { definq_t d; for (d=definq_list; d; d = d->next) if (d->name) printf ("%-20s %c %s\n", d->name, d->is_var? 'v' : d->is_prog? 'p':'f', d->file); for (d=definq_list; d; d = d->next) if (!d->name) printf ("%-20s %c %s\n", "*", d->is_var? 'v': d->is_prog? 'p':'f', d->file); } /* Clear all inquiry definitions. */ static void clear_definq (void) { while (definq_list) { definq_t tmp = definq_list->next; xfree (definq_list->name); xfree (definq_list); definq_list = tmp; } definq_list_tail = &definq_list; } static void do_sendfd (assuan_context_t ctx, char *line) { estream_t fp; char *name, *mode, *p; int rc, fd; /* Get file name. */ name = line; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; /* Get mode. */ mode = p; if (!*mode) mode = "r"; else { for (p=mode; *p && !spacep (p); p++) ; if (*p) *p++ = 0; } /* Open and send. */ fp = es_fopen (name, mode); if (!fp) { log_error ("can't open '%s' in \"%s\" mode: %s\n", name, mode, strerror (errno)); return; } fd = es_fileno (fp); if (opt.verbose) log_error ("file '%s' opened in \"%s\" mode, fd=%d\n", name, mode, fd); rc = assuan_sendfd (ctx, INT2FD (fd) ); if (rc) log_error ("sending descriptor %d failed: %s\n", fd, gpg_strerror (rc)); es_fclose (fp); } static void do_recvfd (assuan_context_t ctx, char *line) { (void)ctx; (void)line; log_info ("This command has not yet been implemented\n"); } static void do_open (char *line) { estream_t fp; char *varname, *name, *mode, *p; int fd; #ifdef HAVE_W32_SYSTEM if (server_pid == (pid_t)(-1)) { log_error ("the pid of the server is unknown\n"); log_info ("use command \"/serverpid\" first\n"); return; } #endif /* Get variable name. */ varname = line; for (p=varname; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; /* Get file name. */ name = p; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; /* Get mode. */ mode = p; if (!*mode) mode = "r"; else { for (p=mode; *p && !spacep (p); p++) ; if (*p) *p++ = 0; } /* Open and send. */ fp = es_fopen (name, mode); if (!fp) { log_error ("can't open '%s' in \"%s\" mode: %s\n", name, mode, strerror (errno)); return; } fd = dup (es_fileno (fp)); if (fd >= 0 && fd < DIM (open_fd_table)) { open_fd_table[fd].inuse = 1; -#ifdef HAVE_W32CE_SYSTEM -# warning fixme: implement our pipe emulation. -#endif -#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) +#if defined(HAVE_W32_SYSTEM) { HANDLE prochandle, handle, newhandle; handle = (void*)_get_osfhandle (fd); prochandle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, server_pid); if (!prochandle) { log_error ("failed to open the server process\n"); close (fd); return; } if (!DuplicateHandle (GetCurrentProcess(), handle, prochandle, &newhandle, 0, TRUE, DUPLICATE_SAME_ACCESS )) { log_error ("failed to duplicate the handle\n"); close (fd); CloseHandle (prochandle); return; } CloseHandle (prochandle); open_fd_table[fd].handle = newhandle; } if (opt.verbose) log_info ("file '%s' opened in \"%s\" mode, fd=%d (libc=%d)\n", name, mode, (int)open_fd_table[fd].handle, fd); set_int_var (varname, (int)open_fd_table[fd].handle); -#else +#else /* Unix */ if (opt.verbose) log_info ("file '%s' opened in \"%s\" mode, fd=%d\n", name, mode, fd); set_int_var (varname, fd); -#endif +#endif /* Unix */ } else { log_error ("can't put fd %d into table\n", fd); if (fd != -1) close (fd); /* Table was full. */ } es_fclose (fp); } static void do_close (char *line) { int fd = atoi (line); #ifdef HAVE_W32_SYSTEM int i; for (i=0; i < DIM (open_fd_table); i++) if ( open_fd_table[i].inuse && open_fd_table[i].handle == (void*)fd) break; if (i < DIM (open_fd_table)) fd = i; else { log_error ("given fd (system handle) has not been opened\n"); return; } #endif if (fd < 0 || fd >= DIM (open_fd_table)) { log_error ("invalid fd\n"); return; } if (!open_fd_table[fd].inuse) { log_error ("given fd has not been opened\n"); return; } #ifdef HAVE_W32_SYSTEM CloseHandle (open_fd_table[fd].handle); /* Close duped handle. */ #endif close (fd); open_fd_table[fd].inuse = 0; } static void do_showopen (void) { int i; for (i=0; i < DIM (open_fd_table); i++) if (open_fd_table[i].inuse) { #ifdef HAVE_W32_SYSTEM printf ("%-15d (libc=%d)\n", (int)open_fd_table[i].handle, i); #else printf ("%-15d\n", i); #endif } } static gpg_error_t getinfo_pid_cb (void *opaque, const void *buffer, size_t length) { membuf_t *mb = opaque; put_membuf (mb, buffer, length); return 0; } /* Get the pid of the server and store it locally. */ static void do_serverpid (assuan_context_t ctx) { int rc; membuf_t mb; char *buffer; init_membuf (&mb, 100); rc = assuan_transact (ctx, "GETINFO pid", getinfo_pid_cb, &mb, NULL, NULL, NULL, NULL); put_membuf (&mb, "", 1); buffer = get_membuf (&mb, NULL); if (rc || !buffer) log_error ("command \"%s\" failed: %s\n", "GETINFO pid", gpg_strerror (rc)); else { server_pid = (pid_t)strtoul (buffer, NULL, 10); if (opt.verbose) log_info ("server's PID is %lu\n", (unsigned long)server_pid); } xfree (buffer); } /* Return true if the command is either "HELP" or "SCD HELP". */ static int help_cmd_p (const char *line) { if (!ascii_strncasecmp (line, "SCD", 3) && (spacep (line+3) || !line[3])) { for (line += 3; spacep (line); line++) ; } return (!ascii_strncasecmp (line, "HELP", 4) && (spacep (line+4) || !line[4])); } /* gpg-connect-agent's entry point. */ int main (int argc, char **argv) { gpgrt_argparse_t pargs; int no_more_options = 0; assuan_context_t ctx; char *line, *p; char *tmpline; size_t linesize; int rc; int cmderr; const char *opt_run = NULL; gpgrt_stream_t script_fp = NULL; int use_tty, keep_line; struct { int collecting; loopline_t head; loopline_t *tail; loopline_t current; unsigned int nestlevel; int oneshot; char *condition; } loopstack[20]; int loopidx; char **cmdline_commands = NULL; char *historyname = NULL; static const char *changeuser; early_system_init (); gnupg_rl_initialize (); gpgrt_set_strusage (my_strusage); log_set_prefix ("gpg-connect-agent", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (0); gnupg_init_signals (0, NULL); opt.autostart = 1; opt.connect_flags = 1; /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; while (!no_more_options && gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: gnupg_set_homedir (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 oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break; case oNoAutostart: opt.autostart = 0; break; case oNoHistory: opt.no_history = 1; break; case oHex: opt.hex = 1; break; case oDecode: opt.decode = 1; break; case oDirmngr: opt.use_dirmngr = 1; break; case oKeyboxd: opt.use_keyboxd = 1; break; case oUIServer: opt.use_uiserver = 1; break; case oRawSocket: opt.raw_socket = pargs.r.ret_str; break; case oTcpSocket: opt.tcp_socket = pargs.r.ret_str; break; case oExec: opt.exec = 1; break; case oNoExtConnect: opt.connect_flags &= ~(1); break; case oRun: opt_run = pargs.r.ret_str; break; case oSubst: opt.enable_varsubst = 1; opt.trim_leading_spaces = 1; break; case oChUid: changeuser = pargs.r.ret_str; break; case oUnBuffered: opt.unbuffered = 1; break; default: pargs.err = 2; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (changeuser && gnupg_chuid (changeuser, 0)) log_inc_errorcount (); /* Force later termination. */ if (log_get_errorcount (0)) exit (2); /* --uiserver is a shortcut for a specific raw socket. This comes in particular handy on Windows. */ if (opt.use_uiserver) { opt.raw_socket = make_absfilename (gnupg_homedir (), "S.uiserver", NULL); } /* 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]); } use_tty = (gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout))); if (opt.exec) { if (!argc) { log_error (_("option \"%s\" requires a program " "and optional arguments\n"), "--exec" ); exit (1); } } else if (argc) cmdline_commands = argv; if (opt.exec && opt.raw_socket) { opt.raw_socket = NULL; log_info (_("option \"%s\" ignored due to \"%s\"\n"), "--raw-socket", "--exec"); } if (opt.exec && opt.tcp_socket) { opt.tcp_socket = NULL; log_info (_("option \"%s\" ignored due to \"%s\"\n"), "--tcp-socket", "--exec"); } if (opt.tcp_socket && opt.raw_socket) { opt.tcp_socket = NULL; log_info (_("option \"%s\" ignored due to \"%s\"\n"), "--tcp-socket", "--raw-socket"); } if (opt_run && !(script_fp = gpgrt_fopen (opt_run, "r"))) { log_error ("cannot open run file '%s': %s\n", opt_run, strerror (errno)); exit (1); } if (opt.exec) { assuan_fd_t no_close[3]; no_close[0] = assuan_fd_from_posix_fd (es_fileno (es_stderr)); no_close[1] = assuan_fd_from_posix_fd (log_get_fd ()); no_close[2] = ASSUAN_INVALID_FD; rc = assuan_new (&ctx); if (rc) { log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); exit (1); } rc = assuan_pipe_connect (ctx, *argv, (const char **)argv, no_close, NULL, NULL, (opt.connect_flags & 1) ? ASSUAN_PIPE_CONNECT_FDPASSING : 0); if (rc) { log_error ("assuan_pipe_connect_ext failed: %s\n", gpg_strerror (rc)); exit (1); } if (opt.verbose) log_info ("server '%s' started\n", *argv); } else if (opt.raw_socket) { rc = assuan_new (&ctx); if (rc) { log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); exit (1); } rc = assuan_socket_connect (ctx, opt.raw_socket, 0, (opt.connect_flags & 1) ? ASSUAN_SOCKET_CONNECT_FDPASSING : 0); if (rc) { log_error ("can't connect to socket '%s': %s\n", opt.raw_socket, gpg_strerror (rc)); exit (1); } if (opt.verbose) log_info ("connection to socket '%s' established\n", opt.raw_socket); } else if (opt.tcp_socket) { char *url; url = xstrconcat ("assuan://", opt.tcp_socket, NULL); rc = assuan_new (&ctx); if (rc) { log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); exit (1); } rc = assuan_socket_connect (ctx, opt.tcp_socket, 0, 0); if (rc) { log_error ("can't connect to server '%s': %s\n", opt.tcp_socket, gpg_strerror (rc)); exit (1); } if (opt.verbose) log_info ("connection to socket '%s' established\n", url); xfree (url); } else ctx = start_agent (); /* See whether there is a line pending from the server (in case assuan did not run the initial handshaking). */ if (assuan_pending_line (ctx)) { rc = read_and_print_response (ctx, 0, &cmderr); if (rc) log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) ); } if (!script_fp && opt.unbuffered) { gpgrt_setvbuf (gpgrt_stdin, NULL, _IONBF, 0); setvbuf (stdout, NULL, _IONBF, 0); } for (loopidx=0; loopidx < DIM (loopstack); loopidx++) loopstack[loopidx].collecting = 0; loopidx = -1; line = NULL; linesize = 0; keep_line = 1; for (;;) { int n; size_t maxlength = 2048; assert (loopidx < (int)DIM (loopstack)); if (loopidx >= 0 && loopstack[loopidx].current) { keep_line = 0; xfree (line); line = xstrdup (loopstack[loopidx].current->line); n = strlen (line); /* Never go beyond of the final /end. */ if (loopstack[loopidx].current->next) loopstack[loopidx].current = loopstack[loopidx].current->next; else if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4))) ; else log_fatal ("/end command vanished\n"); } else if (cmdline_commands && *cmdline_commands && !script_fp) { keep_line = 0; xfree (line); line = xstrdup (*cmdline_commands); cmdline_commands++; n = strlen (line); if (n >= maxlength) maxlength = 0; } else if (use_tty && !script_fp) { keep_line = 0; xfree (line); if (!historyname && !opt.no_history) { historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL); if (tty_read_history (historyname, 500)) log_info ("error reading '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); } line = tty_get ("> "); n = strlen (line); if (n==1 && *line == CONTROL_D) n = 0; if (n >= maxlength) maxlength = 0; } else { if (!keep_line) { xfree (line); line = NULL; linesize = 0; keep_line = 1; } n = gpgrt_read_line (script_fp ? script_fp : gpgrt_stdin, &line, &linesize, &maxlength); } if (n < 0) { log_error (_("error reading input: %s\n"), strerror (errno)); if (script_fp) { gpgrt_fclose (script_fp); script_fp = NULL; log_error ("stopping script execution\n"); continue; } exit (1); } if (!n) { /* EOF */ if (script_fp) { gpgrt_fclose (script_fp); script_fp = NULL; if (opt.verbose) log_info ("end of script\n"); continue; } break; } if (!maxlength) { log_error (_("line too long - skipped\n")); continue; } if (memchr (line, 0, n)) log_info (_("line shortened due to embedded Nul character\n")); if (line[n-1] == '\n') line[n-1] = 0; if (opt.trim_leading_spaces) { const char *s = line; while (spacep (s)) s++; if (s != line) { for (p=line; *s;) *p++ = *s++; *p = 0; n = p - line; } } if (loopidx+1 >= 0 && loopstack[loopidx+1].collecting) { loopline_t ll; ll = xmalloc (sizeof *ll + strlen (line)); ll->next = NULL; strcpy (ll->line, line); *loopstack[loopidx+1].tail = ll; loopstack[loopidx+1].tail = &ll->next; if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4))) loopstack[loopidx+1].nestlevel--; else if (!strncmp (line, "/while", 6) && (!line[6]||spacep(line+6))) loopstack[loopidx+1].nestlevel++; if (loopstack[loopidx+1].nestlevel) continue; /* We reached the corresponding /end. */ loopstack[loopidx+1].collecting = 0; loopidx++; } if (*line == '/') { /* Handle control commands. */ char *cmd = line+1; for (p=cmd; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; if (!strcmp (cmd, "let")) { assign_variable (p, 0); } else if (!strcmp (cmd, "slet")) { /* Deprecated - never used in a released version. */ assign_variable (p, 1); } else if (!strcmp (cmd, "showvar")) { show_variables (); } else if (!strcmp (cmd, "definq")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { add_definq (tmpline, 1, 0); xfree (tmpline); } else add_definq (p, 1, 0); } else if (!strcmp (cmd, "definqfile")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { add_definq (tmpline, 0, 0); xfree (tmpline); } else add_definq (p, 0, 0); } else if (!strcmp (cmd, "definqprog")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { add_definq (tmpline, 0, 1); xfree (tmpline); } else add_definq (p, 0, 1); } else if (!strcmp (cmd, "datafile")) { const char *fname; if (current_datasink) { if (current_datasink != es_stdout) es_fclose (current_datasink); current_datasink = NULL; } tmpline = opt.enable_varsubst? substitute_line (p) : NULL; fname = tmpline? tmpline : p; if (fname && !strcmp (fname, "-")) current_datasink = es_stdout; else if (fname && *fname) { current_datasink = es_fopen (fname, "wb"); if (!current_datasink) log_error ("can't open '%s': %s\n", fname, strerror (errno)); } xfree (tmpline); } else if (!strcmp (cmd, "showdef")) { show_definq (); } else if (!strcmp (cmd, "cleardef")) { clear_definq (); } else if (!strcmp (cmd, "echo")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { puts (tmpline); xfree (tmpline); } else puts (p); } else if (!strcmp (cmd, "sendfd")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { do_sendfd (ctx, tmpline); xfree (tmpline); } else do_sendfd (ctx, p); continue; } else if (!strcmp (cmd, "recvfd")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { do_recvfd (ctx, tmpline); xfree (tmpline); } else do_recvfd (ctx, p); continue; } else if (!strcmp (cmd, "open")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { do_open (tmpline); xfree (tmpline); } else do_open (p); } else if (!strcmp (cmd, "close")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { do_close (tmpline); xfree (tmpline); } else do_close (p); } else if (!strcmp (cmd, "showopen")) { do_showopen (); } else if (!strcmp (cmd, "serverpid")) { do_serverpid (ctx); } else if (!strcmp (cmd, "hex")) opt.hex = 1; else if (!strcmp (cmd, "nohex")) opt.hex = 0; else if (!strcmp (cmd, "decode")) opt.decode = 1; else if (!strcmp (cmd, "nodecode")) opt.decode = 0; else if (!strcmp (cmd, "subst")) { opt.enable_varsubst = 1; opt.trim_leading_spaces = 1; } else if (!strcmp (cmd, "nosubst")) opt.enable_varsubst = 0; else if (!strcmp (cmd, "run")) { char *p2; for (p2=p; *p2 && !spacep (p2); p2++) ; if (*p2) *p2++ = 0; while (spacep (p2)) p++; if (*p2) { log_error ("syntax error in run command\n"); if (script_fp) { gpgrt_fclose (script_fp); script_fp = NULL; } } else if (script_fp) { log_error ("cannot nest run commands - stop\n"); gpgrt_fclose (script_fp); script_fp = NULL; } else if (!(script_fp = gpgrt_fopen (p, "r"))) { log_error ("cannot open run file '%s': %s\n", p, strerror (errno)); } else if (opt.verbose) log_info ("running commands from '%s'\n", p); } else if (!strcmp (cmd, "while")) { if (loopidx+2 >= (int)DIM(loopstack)) { log_error ("blocks are nested too deep\n"); /* We should better die or break all loop in this case as recovering from this error won't be easy. */ } else { loopstack[loopidx+1].head = NULL; loopstack[loopidx+1].tail = &loopstack[loopidx+1].head; loopstack[loopidx+1].current = NULL; loopstack[loopidx+1].nestlevel = 1; loopstack[loopidx+1].oneshot = 0; loopstack[loopidx+1].condition = xstrdup (p); loopstack[loopidx+1].collecting = 1; } } else if (!strcmp (cmd, "if")) { if (loopidx+2 >= (int)DIM(loopstack)) { log_error ("blocks are nested too deep\n"); } else { /* Note that we need to evaluate the condition right away and not just at the end of the block as we do with a WHILE. */ loopstack[loopidx+1].head = NULL; loopstack[loopidx+1].tail = &loopstack[loopidx+1].head; loopstack[loopidx+1].current = NULL; loopstack[loopidx+1].nestlevel = 1; loopstack[loopidx+1].oneshot = 1; loopstack[loopidx+1].condition = substitute_line_copy (p); loopstack[loopidx+1].collecting = 1; } } else if (!strcmp (cmd, "end")) { if (loopidx < 0) log_error ("stray /end command encountered - ignored\n"); else { char *tmpcond; const char *value; long condition; /* Evaluate the condition. */ tmpcond = xstrdup (loopstack[loopidx].condition); if (loopstack[loopidx].oneshot) { xfree (loopstack[loopidx].condition); loopstack[loopidx].condition = xstrdup ("0"); } tmpline = substitute_line (tmpcond); value = tmpline? tmpline : tmpcond; /* "true" or "yes" are commonly used to mean TRUE; all other strings will evaluate to FALSE due to the strtoul. */ if (!ascii_strcasecmp (value, "true") || !ascii_strcasecmp (value, "yes")) condition = 1; else condition = strtol (value, NULL, 0); xfree (tmpline); xfree (tmpcond); if (condition) { /* Run loop. */ loopstack[loopidx].current = loopstack[loopidx].head; } else { /* Cleanup. */ while (loopstack[loopidx].head) { loopline_t tmp = loopstack[loopidx].head->next; xfree (loopstack[loopidx].head); loopstack[loopidx].head = tmp; } loopstack[loopidx].tail = NULL; loopstack[loopidx].current = NULL; loopstack[loopidx].nestlevel = 0; loopstack[loopidx].collecting = 0; loopstack[loopidx].oneshot = 0; xfree (loopstack[loopidx].condition); loopstack[loopidx].condition = NULL; loopidx--; } } } else if (!strcmp (cmd, "bye") || !strcmp (cmd, "quit")) { break; } else if (!strcmp (cmd, "sleep")) { gnupg_sleep (1); } else if (!strcmp (cmd, "history")) { if (!strcmp (p, "--clear")) { tty_read_history (NULL, 0); } else log_error ("Only \"/history --clear\" is supported\n"); } else if (!strcmp (cmd, "help")) { puts ( "Available commands:\n" "/echo ARGS Echo ARGS.\n" "/let NAME VALUE Set variable NAME to VALUE.\n" "/showvar Show all variables.\n" "/definq NAME VAR Use content of VAR for inquiries with NAME.\n" "/definqfile NAME FILE Use content of FILE for inquiries with NAME.\n" "/definqprog NAME PGM Run PGM for inquiries with NAME.\n" "/datafile [NAME] Write all D line content to file NAME.\n" "/showdef Print all definitions.\n" "/cleardef Delete all definitions.\n" "/sendfd FILE MODE Open FILE and pass descriptor to server.\n" "/recvfd Receive FD from server and print.\n" "/open VAR FILE MODE Open FILE and assign the file descriptor to VAR.\n" "/close FD Close file with descriptor FD.\n" "/showopen Show descriptors of all open files.\n" "/serverpid Retrieve the pid of the server.\n" "/[no]hex Enable hex dumping of received data lines.\n" "/[no]decode Enable decoding of received data lines.\n" "/[no]subst Enable variable substitution.\n" "/run FILE Run commands from FILE.\n" "/if VAR Begin conditional block controlled by VAR.\n" "/while VAR Begin loop controlled by VAR.\n" "/end End loop or condition\n" "/history Manage the history\n" "/bye Terminate gpg-connect-agent.\n" "/help Print this help."); } else log_error (_("unknown command '%s'\n"), cmd ); continue; } if (opt.verbose && script_fp) puts (line); tmpline = opt.enable_varsubst? substitute_line (line) : NULL; if (tmpline) { rc = assuan_write_line (ctx, tmpline); xfree (tmpline); } else rc = assuan_write_line (ctx, line); if (rc) { log_info (_("sending line failed: %s\n"), gpg_strerror (rc) ); break; } if (*line == '#' || !*line) continue; /* Don't expect a response for a comment line. */ rc = read_and_print_response (ctx, help_cmd_p (line), &cmderr); if (rc) log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) ); if ((rc || cmderr) && script_fp) { log_error ("stopping script execution\n"); gpgrt_fclose (script_fp); script_fp = NULL; } /* FIXME: If the last command was BYE or the server died for some other reason, we won't notice until we get the next input command. Probing the connection with a non-blocking read could help to notice termination or other problems early. */ } if (opt.verbose) log_info ("closing connection to %s\n", opt.use_dirmngr? "dirmngr" : opt.use_keyboxd? "keyboxd" : "agent"); if (historyname && tty_write_history (historyname)) log_info ("error writing '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); /* XXX: We would like to release the context here, but libassuan nicely says good bye to the server, which results in a SIGPIPE if the server died. Unfortunately, libassuan does not ignore SIGPIPE when used with UNIX sockets, hence we simply leak the context here. */ if (0) assuan_release (ctx); else gpgrt_annotate_leaked_object (ctx); xfree (historyname); xfree (line); return 0; } /* Handle an Inquire from the server. Return False if it could not be handled; in this case the caller shll complete the operation. LINE is the complete line as received from the server. This function may change the content of LINE. */ static int handle_inquire (assuan_context_t ctx, char *line) { const char *name; definq_t d; /* FIXME: Due to the use of popen we can't easily switch to estream. */ FILE *fp = NULL; char buffer[1024]; int rc, n; int cancelled = 0; /* Skip the command and trailing spaces. */ for (; *line && !spacep (line); line++) ; while (spacep (line)) line++; /* Get the name. */ name = line; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; /* Now match it against our list. The second loop is there to detect the match-all entry. */ for (d=definq_list; d; d = d->next) if (d->name && !strcmp (d->name, name)) break; if (!d) for (d=definq_list; d; d = d->next) if (!d->name) break; if (!d) { if (opt.verbose) log_info ("no handler for inquiry '%s' found\n", name); return 0; } if (d->is_var) { char *tmpvalue = get_var_ext (d->file); if (tmpvalue) rc = assuan_send_data (ctx, tmpvalue, strlen (tmpvalue)); else rc = assuan_send_data (ctx, "", 0); xfree (tmpvalue); if (rc) log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); } else { if (d->is_prog) { -#ifdef HAVE_W32CE_SYSTEM - fp = NULL; -#else fp = popen (d->file, "r"); -#endif if (!fp) log_error ("error executing '%s': %s\n", d->file, strerror (errno)); else if (opt.verbose) log_error ("handling inquiry '%s' by running '%s'\n", name, d->file); } else { fp = fopen (d->file, "rb"); if (!fp) log_error ("error opening '%s': %s\n", d->file, strerror (errno)); else if (opt.verbose) log_error ("handling inquiry '%s' by returning content of '%s'\n", name, d->file); } if (!fp) return 0; while ( (n = fread (buffer, 1, sizeof buffer, fp)) ) { rc = assuan_send_data (ctx, buffer, n); if (rc) { log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); break; } } if (ferror (fp)) log_error ("error reading from '%s': %s\n", d->file, strerror (errno)); } if (d->is_var) ; else if (d->is_prog) { -#ifndef HAVE_W32CE_SYSTEM if (pclose (fp)) cancelled = 1; -#endif } else fclose (fp); rc = assuan_send_data (ctx, NULL, cancelled); if (rc) log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); return 1; } /* Read all response lines from server and print them. Returns 0 on success or an assuan error code. If WITHHASH istrue, comment lines are printed. Sets R_GOTERR to true if the command did not returned OK. */ static int read_and_print_response (assuan_context_t ctx, int withhash, int *r_goterr) { char *line; size_t linelen; gpg_error_t rc; int i, j; int need_lf = 0; *r_goterr = 0; for (;;) { do { rc = assuan_read_line (ctx, &line, &linelen); if (rc) return rc; if ((withhash || opt.verbose > 1) && *line == '#') { fwrite (line, linelen, 1, stdout); putchar ('\n'); } } while (*line == '#' || !linelen); if (linelen >= 1 && line[0] == 'D' && line[1] == ' ') { if (current_datasink) { const unsigned char *s; int c = 0; for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ ) { if (*s == '%' && j+2 < linelen) { s++; j++; c = xtoi_2 ( s ); s++; j++; } else c = *s; es_putc (c, current_datasink); } } else if (opt.hex) { for (i=2; i < linelen; ) { int save_i = i; printf ("D[%04X] ", i-2); for (j=0; j < 16 ; j++, i++) { if (j == 8) putchar (' '); if (i < linelen) printf (" %02X", ((unsigned char*)line)[i]); else fputs (" ", stdout); } fputs (" ", stdout); i= save_i; for (j=0; j < 16; j++, i++) { unsigned int c = ((unsigned char*)line)[i]; if ( i >= linelen ) putchar (' '); else if (isascii (c) && isprint (c) && !iscntrl (c)) putchar (c); else putchar ('.'); } putchar ('\n'); } } else if (opt.decode) { const unsigned char *s; int need_d = 1; int c = 0; for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ ) { if (need_d) { fputs ("D ", stdout); need_d = 0; } if (*s == '%' && j+2 < linelen) { s++; j++; c = xtoi_2 ( s ); s++; j++; } else c = *s; if (c == '\n') need_d = 1; putchar (c); } need_lf = (c != '\n'); } else { fwrite (line, linelen, 1, stdout); putchar ('\n'); } } else { if (need_lf) { if (!current_datasink || current_datasink != es_stdout) putchar ('\n'); need_lf = 0; } if (linelen >= 1 && line[0] == 'S' && (line[1] == '\0' || line[1] == ' ')) { if (!current_datasink || current_datasink != es_stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { if (!current_datasink || current_datasink != es_stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } set_int_var ("?", 0); return 0; } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { int errval; errval = strtol (line+3, NULL, 10); if (!errval) errval = -1; set_int_var ("?", errval); if (!current_datasink || current_datasink != es_stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } *r_goterr = 1; return 0; } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { if (!current_datasink || current_datasink != es_stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } if (!handle_inquire (ctx, line)) assuan_write_line (ctx, "CANCEL"); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (line[3] == '\0' || line[3] == ' ')) { if (!current_datasink || current_datasink != es_stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } /* Received from server, thus more responses are expected. */ } else return gpg_error (GPG_ERR_ASS_INV_RESPONSE); } } } /* Connect to the agent and send the standard options. */ static assuan_context_t start_agent (void) { gpg_error_t err; assuan_context_t ctx; session_env_t session_env; session_env = session_env_new (); if (!session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); if (opt.use_dirmngr) err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.dirmngr_program, opt.autostart, !opt.quiet, 0, NULL, NULL); else if (opt.use_keyboxd) err = start_new_keyboxd (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.keyboxd_program, opt.autostart, !opt.quiet, 0, NULL, NULL); else err = start_new_gpg_agent (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.agent_program, NULL, NULL, session_env, opt.autostart, !opt.quiet, 0, NULL, NULL); session_env_release (session_env); if (err) { if (!opt.autostart && (gpg_err_code (err) == (opt.use_dirmngr? GPG_ERR_NO_DIRMNGR : opt.use_keyboxd? GPG_ERR_NO_KEYBOXD : GPG_ERR_NO_AGENT))) { /* In the no-autostart case we don't make gpg-connect-agent fail on a missing server. */ log_info (opt.use_dirmngr? _("no dirmngr running in this session\n"): opt.use_keyboxd? _("no keybox daemon running in this session\n"): _("no gpg-agent running in this session\n")); exit (0); } else { log_error (_("error sending standard options: %s\n"), gpg_strerror (err)); exit (1); } } return ctx; } diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 704bdca16..7cd45c7e9 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1,3604 +1,3607 @@ /* gpgconf-comp.c - Configuration utility for GnuPG. * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc. * Copyright (C) 2016 Werner Koch * Copyright (C) 2020, 2021 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GnuPG; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN 1 # include #else # include # include #endif #include "../common/util.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/status.h" #include "../common/gc-opt-flags.h" #include "gpgconf.h" #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )) void gc_error (int status, int errnum, const char *fmt, ...) \ __attribute__ ((format (printf, 3, 4))); #endif /* Output a diagnostic message. If ERRNUM is not 0, then the output is followed by a colon, a white space, and the error string for the error number ERRNUM. In any case the output is finished by a newline. The message is prepended by the program name, a colon, and a whitespace. The output may be further formatted or redirected by the jnlib logging facility. */ void gc_error (int status, int errnum, const char *fmt, ...) { va_list arg_ptr; va_start (arg_ptr, fmt); log_logv (GPGRT_LOGLVL_ERROR, fmt, arg_ptr); va_end (arg_ptr); if (errnum) log_printf (": %s\n", strerror (errnum)); else log_printf ("\n"); if (status) { log_printf (NULL); log_printf ("fatal error (exit status %i)\n", status); gpgconf_failure (gpg_error_from_errno (errnum)); } } /* Forward declaration. */ static void gpg_agent_runtime_change (int killflag); static void scdaemon_runtime_change (int killflag); #ifdef BUILD_WITH_TPM2D static void tpm2daemon_runtime_change (int killflag); #endif static void dirmngr_runtime_change (int killflag); static void keyboxd_runtime_change (int killflag); /* STRING_ARRAY is a malloced array with malloced strings. It is used * a space to store strings so that other objects may point to these * strings. It shall never be shrinked or any items changes. * STRING_ARRAY itself may be reallocated to increase the size of the * table. STRING_ARRAY_USED is the number of items currently used, * STRING_ARRAY_SIZE is the number of calloced slots. */ static char **string_array; static size_t string_array_used; static size_t string_array_size; /* Option configuration. */ /* An option might take an argument, or not. Argument types can be basic or complex. Basic types are generic and easy to validate. Complex types provide more specific information about the intended use, but can be difficult to validate. If you add to this enum, don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* Basic argument types. */ /* No argument. */ GC_ARG_TYPE_NONE = 0, /* A String argument. */ GC_ARG_TYPE_STRING = 1, /* A signed integer argument. */ GC_ARG_TYPE_INT32 = 2, /* An unsigned integer argument. */ GC_ARG_TYPE_UINT32 = 3, /* ADD NEW BASIC TYPE ENTRIES HERE. */ /* Complex argument types. */ /* A complete filename. */ GC_ARG_TYPE_FILENAME = 32, /* An LDAP server in the format HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */ GC_ARG_TYPE_LDAP_SERVER = 33, /* A 40 character fingerprint. */ GC_ARG_TYPE_KEY_FPR = 34, /* A user ID or key ID or fingerprint for a certificate. */ GC_ARG_TYPE_PUB_KEY = 35, /* A user ID or key ID or fingerprint for a certificate with a key. */ GC_ARG_TYPE_SEC_KEY = 36, /* A alias list made up of a key, an equal sign and a space separated list of values. */ GC_ARG_TYPE_ALIAS_LIST = 37, /* ADD NEW COMPLEX TYPE ENTRIES HERE. */ /* The number of the above entries. */ GC_ARG_TYPE_NR } gc_arg_type_t; /* For every argument, we record some information about it in the following struct. */ static const struct { /* For every argument type exists a basic argument type that can be used as a fallback for input and validation purposes. */ gc_arg_type_t fallback; /* Human-readable name of the type. */ const char *name; } gc_arg_type[GC_ARG_TYPE_NR] = { /* The basic argument types have their own types as fallback. */ { GC_ARG_TYPE_NONE, "none" }, { GC_ARG_TYPE_STRING, "string" }, { GC_ARG_TYPE_INT32, "int32" }, { GC_ARG_TYPE_UINT32, "uint32" }, /* Reserved basic type entries for future extension. */ { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, /* The complex argument types have a basic type as fallback. */ { GC_ARG_TYPE_STRING, "filename" }, { GC_ARG_TYPE_STRING, "ldap server" }, { GC_ARG_TYPE_STRING, "key fpr" }, { GC_ARG_TYPE_STRING, "pub key" }, { GC_ARG_TYPE_STRING, "sec key" }, { GC_ARG_TYPE_STRING, "alias list" }, }; /* Every option has an associated expert level, than can be used to hide advanced and expert options from beginners. If you add to this list, don't forget to update GC_LEVEL below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* The basic options should always be displayed. */ GC_LEVEL_BASIC, /* The advanced options may be hidden from beginners. */ GC_LEVEL_ADVANCED, /* The expert options should only be displayed to experts. */ GC_LEVEL_EXPERT, /* The invisible options should normally never be displayed. */ GC_LEVEL_INVISIBLE, /* The internal options are never exported, they mark options that are recorded for internal use only. */ GC_LEVEL_INTERNAL, /* ADD NEW ENTRIES HERE. */ /* The number of the above entries. */ GC_LEVEL_NR } gc_expert_level_t; /* A description for each expert level. */ static const struct { const char *name; } gc_level[] = { { "basic" }, { "advanced" }, { "expert" }, { "invisible" }, { "internal" } }; /* Option flags. The flags which are used by the components are defined by gc-opt-flags.h, included above. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ /* Some entries in the emitted option list are not options, but mark the beginning of a new group of options. These entries have the GROUP flag set. Note that this is internally also known as a header line. */ #define GC_OPT_FLAG_GROUP (1UL << 0) /* The ARG_OPT flag for an option indicates that the argument is optional. This is never set for GC_ARG_TYPE_NONE options. */ #define GC_OPT_FLAG_ARG_OPT (1UL << 1) /* The LIST flag for an option indicates that the option can occur several times. A comma separated list of arguments is used as the argument value. */ #define GC_OPT_FLAG_LIST (1UL << 2) /* The RUNTIME flag for an option indicates that the option can be changed at runtime. */ #define GC_OPT_FLAG_RUNTIME (1UL << 3) /* A human-readable description for each flag. */ static const struct { const char *name; } gc_flag[] = { { "group" }, { "optional arg" }, { "list" }, { "runtime" }, { "default" }, { "default desc" }, { "no arg desc" }, { "no change" } }; /* Each option we want to support in gpgconf has the needed * information in a static list per componenet. This struct describes * the info for a single option. */ struct known_option_s { /* If this is NULL, then this is a terminator in an array of unknown * length. Otherwise it is the name of the option described by this * entry. The name must not contain a colon. */ const char *name; /* The option flags. */ unsigned long flags; /* The expert level. */ gc_expert_level_t level; /* The complex type of the option argument; the default of 0 is used * for a standard type as returned by --dump-option-table. */ gc_arg_type_t arg_type; }; typedef struct known_option_s known_option_t; /* The known options of the GC_COMPONENT_GPG_AGENT component. */ static known_option_t known_options_gpg_agent[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ssh-fingerprint-digest", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, /**/ GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "max-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "no-allow-external-cache", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "min-passphrase-len", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, /**/ GC_ARG_TYPE_FILENAME }, { "check-sym-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, /**/ GC_ARG_TYPE_FILENAME }, { "max-passphrase-days", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "pinentry-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { NULL } }; /* The known options of the GC_COMPONENT_SCDAEMON component. */ static known_option_t known_options_scdaemon[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "reader-port", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "ctapi-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "pcsc-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "disable-ccid", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "enable-pinpad-varlen", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "card-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "application-priority", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { NULL } }; #ifdef BUILD_WITH_TPM2D /* The known options of the GC_COMPONENT_TPM2DAEMON component. */ static known_option_t known_options_tpm2daemon[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "parent", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { NULL } }; #endif /* The known options of the GC_COMPONENT_GPG component. */ static known_option_t known_options_gpg[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED, GC_ARG_TYPE_ALIAS_LIST}, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "trust-model", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "auto-key-import", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "max-cert-depth", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "completes-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "marginals-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, /* The next items are pseudo options which we read via --gpgconf-list. * The meta information is taken from the table below. */ { "default_pubkey_algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "compliance_de_vs", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "use_keyboxd", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { NULL } }; static const char *known_pseudo_options_gpg[] = {/* v-- ARGPARSE_TYPE_STRING */ "default_pubkey_algo:0:2:@:", /* A basic compliance check for gpg. We use gpg here but the * result is valid for all components. * v-- ARGPARSE_TYPE_INT */ "compliance_de_vs:0:1:@:", /* True is use_keyboxd is enabled. That option can be set in * common.conf but is not direcly supported by gpgconf. Thus we * only allow to read it out. * v-- ARGPARSE_TYPE_INT */ "use_keyboxd:0:1:@:", NULL }; /* The known options of the GC_COMPONENT_GPGSM component. */ static known_option_t known_options_gpgsm[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_INVISIBLE, GC_ARG_TYPE_LDAP_SERVER }, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, /* Pseudo option follows. See also table below. */ { "default_pubkey_algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { NULL } }; static const char *known_pseudo_options_gpgsm[] = {/* v-- ARGPARSE_TYPE_STRING */ "default_pubkey_algo:0:2:@:", NULL }; /* The known options of the GC_COMPONENT_DIRMNGR component. */ static known_option_t known_options_dirmngr[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ldapserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, GC_ARG_TYPE_LDAP_SERVER }, { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { NULL } }; /* The known options of the GC_COMPONENT_KEYBOXD component. */ static known_option_t known_options_keyboxd[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { NULL } }; /* The known options of the GC_COMPONENT_PINENTRY component. */ static known_option_t known_options_pinentry[] = { { NULL } }; /* Our main option info object. We copy all required information from the * gpgrt_opt_t items but convert the flags value to bit flags. */ struct gc_option_s { const char *name; /* The same as gpgrt_opt_t.long_opt. */ const char *desc; /* The same as gpgrt_opt_t.description. */ unsigned int is_header:1; /* This is a header item. */ unsigned int is_list:1; /* This is a list style option. */ unsigned int opt_arg:1; /* The option's argument is optional. */ unsigned int runtime:1; /* The option is runtime changeable. */ unsigned int gpgconf_list:1; /* Mentioned by --gpgconf-list. */ unsigned int has_default:1; /* The option has a default value. */ unsigned int def_in_desc:1; /* The default is in the descrition. */ unsigned int no_arg_desc:1; /* The argument has a default ???. */ unsigned int no_change:1; /* User shall not change the option. */ unsigned int attr_ignore:1; /* The ARGPARSE_ATTR_IGNORE. */ unsigned int attr_force:1; /* The ARGPARSE_ATTR_FORCE. */ /* The expert level - copied from known_options. */ gc_expert_level_t level; /* The complex type - copied from known_options. */ gc_arg_type_t arg_type; /* The default value for this option. This is NULL if the option is not present in the component, the empty string if no default is available, and otherwise a quoted string. This is currently malloced.*/ char *default_value; /* The current value of this option. */ char *value; /* The new flags for this option. The only defined flag is actually GC_OPT_FLAG_DEFAULT, and it means that the option should be deleted. In this case, NEW_VALUE is NULL. */ unsigned long new_flags; /* The new value of this option. */ char *new_value; }; typedef struct gc_option_s gc_option_t; /* The information associated with each component. */ static struct { /* The name of the component. Some components don't have an * associated program, but are implemented directly by GPGConf. In * this case, PROGRAM is NULL. */ char *program; /* The displayed name of this component. Must not contain a colon * (':') character. */ const char *name; /* The gettext domain for the description DESC. If this is NULL, then the description is not translated. */ const char *desc_domain; /* The description of this component. */ const char *desc; /* The module name (GNUPG_MODULE_NAME_foo) as defined by * ../common/util.h. This value is used to get the actual installed * path of the program. 0 is used if no program for the component * is available. */ char module_name; /* The name for the configuration filename of this component. */ const char *option_config_filename; /* The static table of known options for this component. */ known_option_t *known_options; /* The static table of known pseudo options for this component or NULL. */ const char **known_pseudo_options; /* The runtime change callback. If KILLFLAG is true the component is killed and not just reloaded. */ void (*runtime_change) (int killflag); /* The table of known options as read from the component including * header lines and such. This is suitable to be passed to * gpgrt_argparser. Will be filled in by * retrieve_options_from_program. */ gpgrt_opt_t *opt_table; /* The full table including data from OPT_TABLE. The end of the * table is marked by NULL entry for NAME. Will be filled in by * retrieve_options_from_program. */ gc_option_t *options; } gc_component[GC_COMPONENT_NR] = { /* Note: The order of the items must match the order given in the * gc_component_id_t enumeration. The order is often used by * frontends to display the backend options thus do not change the * order without considering the user experience. */ { NULL }, /* DUMMY for GC_COMPONENT_ANY */ { GPG_NAME, GPG_DISP_NAME, "gnupg", N_("OpenPGP"), GNUPG_MODULE_NAME_GPG, GPG_NAME ".conf", known_options_gpg, known_pseudo_options_gpg }, { GPGSM_NAME, GPGSM_DISP_NAME, "gnupg", N_("S/MIME"), GNUPG_MODULE_NAME_GPGSM, GPGSM_NAME ".conf", known_options_gpgsm, known_pseudo_options_gpgsm }, { KEYBOXD_NAME, KEYBOXD_DISP_NAME, "gnupg", N_("Public Keys"), GNUPG_MODULE_NAME_KEYBOXD, KEYBOXD_NAME ".conf", known_options_keyboxd, NULL, keyboxd_runtime_change }, { GPG_AGENT_NAME, GPG_AGENT_DISP_NAME, "gnupg", N_("Private Keys"), GNUPG_MODULE_NAME_AGENT, GPG_AGENT_NAME ".conf", known_options_gpg_agent, NULL, gpg_agent_runtime_change }, { SCDAEMON_NAME, SCDAEMON_DISP_NAME, "gnupg", N_("Smartcards"), GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf", known_options_scdaemon, NULL, scdaemon_runtime_change}, #ifdef BUILD_WITH_TPM2D { TPM2DAEMON_NAME, TPM2DAEMON_DISP_NAME, "gnupg", N_("TPM"), GNUPG_MODULE_NAME_TPM2DAEMON, TPM2DAEMON_NAME ".conf", known_options_tpm2daemon, NULL, tpm2daemon_runtime_change}, #else { NULL }, /* DUMMY to keep the table in-sync with enums */ #endif { DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"), GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf", known_options_dirmngr, NULL, dirmngr_runtime_change }, { "pinentry", "Pinentry", "gnupg", N_("Passphrase Entry"), GNUPG_MODULE_NAME_PINENTRY, NULL, known_options_pinentry } }; /* Structure used to collect error output of the component programs. */ struct error_line_s; typedef struct error_line_s *error_line_t; struct error_line_s { error_line_t next; /* Link to next item. */ const char *fname; /* Name of the config file (points into BUFFER). */ unsigned int lineno; /* Line number of the config file. */ const char *errtext; /* Text of the error message (points into BUFFER). */ char buffer[1]; /* Helper buffer. */ }; /* Initialization and finalization. */ static void gc_option_free (gc_option_t *o) { if (o == NULL || o->name == NULL) return; xfree (o->value); gc_option_free (o + 1); } static void gc_components_free (void) { int i; for (i = 0; i < DIM (gc_component); i++) gc_option_free (gc_component[i].options); } void gc_components_init (void) { atexit (gc_components_free); } /* Engine specific support. */ static void gpg_agent_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[5]; pid_t pid = (pid_t)(-1); int i = 0; int cmdidx; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "--no-autostart"; cmdidx = i; argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT"; argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_release_process (pid); } static void scdaemon_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[9]; pid_t pid = (pid_t)(-1); int i = 0; int cmdidx; (void)killflag; /* For scdaemon kill and reload are synonyms. */ /* We use "GETINFO app_running" to see whether the agent is already running and kill it only in this case. This avoids an explicit starting of the agent in case it is not yet running. There is obviously a race condition but that should not harm too much. */ pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "-s"; argv[i++] = "--no-autostart"; argv[i++] = "GETINFO scd_running"; argv[i++] = "/if ${! $?}"; cmdidx = i; argv[i++] = "scd killscd"; argv[i++] = "/end"; argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_release_process (pid); } #ifdef BUILD_WITH_TPM2D static void tpm2daemon_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[9]; pid_t pid = (pid_t)(-1); int i = 0; int cmdidx; (void)killflag; /* For scdaemon kill and reload are synonyms. */ /* We use "GETINFO app_running" to see whether the agent is already running and kill it only in this case. This avoids an explicit starting of the agent in case it is not yet running. There is obviously a race condition but that should not harm too much. */ pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "-s"; argv[i++] = "--no-autostart"; argv[i++] = "GETINFO tpm2d_running"; argv[i++] = "/if ${! $?}"; cmdidx = i; argv[i++] = "scd killtpm2cd"; argv[i++] = "/end"; argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_release_process (pid); } #endif static void dirmngr_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[6]; pid_t pid = (pid_t)(-1); int i = 0; int cmdidx; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "--no-autostart"; argv[i++] = "--dirmngr"; cmdidx = i; argv[i++] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR"; argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_release_process (pid); } static void keyboxd_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[6]; pid_t pid = (pid_t)(-1); int i = 0; int cmdidx; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); argv[i++] = "--no-autostart"; argv[i++] = "--keyboxd"; cmdidx = i; argv[i++] = killflag? "KILLKEYBOXD" : "RELOADKEYBOXD"; if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_release_process (pid); } /* Launch the gpg-agent or the dirmngr if not already running. */ gpg_error_t gc_component_launch (int component) { gpg_error_t err; const char *pgmname; const char *argv[6]; int i; pid_t pid; if (component < 0) { err = gc_component_launch (GC_COMPONENT_GPG_AGENT); if (!err) err = gc_component_launch (GC_COMPONENT_KEYBOXD); if (!err) err = gc_component_launch (GC_COMPONENT_DIRMNGR); return err; } if (!(component == GC_COMPONENT_GPG_AGENT || component == GC_COMPONENT_KEYBOXD || component == GC_COMPONENT_DIRMNGR)) { log_error ("%s\n", _("Component not suitable for launching")); gpgconf_failure (0); } if (gc_component_check_options (component, NULL, NULL)) { log_error (_("Configuration file of component %s is broken\n"), gc_component[component].name); if (!opt.quiet) log_info (_("Note: Use the command \"%s%s\" to get details.\n"), gc_component[component].program ? gc_component[component].program : gc_component[component].name, " --gpgconf-test"); gpgconf_failure (0); } pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); i = 0; if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } if (component == GC_COMPONENT_DIRMNGR) argv[i++] = "--dirmngr"; else if (component == GC_COMPONENT_KEYBOXD) argv[i++] = "--keyboxd"; argv[i++] = "NOP"; argv[i] = NULL; log_assert (i < DIM(argv)); err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s%s%s': %s", pgmname, component == GC_COMPONENT_DIRMNGR? " --dirmngr" : component == GC_COMPONENT_KEYBOXD? " --keyboxd":"", " NOP", gpg_strerror (err)); gnupg_release_process (pid); return err; } static void do_runtime_change (int component, int killflag) { int runtime[GC_COMPONENT_NR] = { 0 }; if (component < 0) { for (component = 0; component < GC_COMPONENT_NR; component++) runtime [component] = 1; } else { log_assert (component >= 0 && component < GC_COMPONENT_NR); runtime [component] = 1; } /* Do the restart for the selected components. */ for (component = GC_COMPONENT_NR-1; component >= 0; component--) { if (runtime[component] && gc_component[component].runtime_change) (*gc_component[component].runtime_change) (killflag); } } /* Unconditionally restart COMPONENT. */ void gc_component_kill (int component) { do_runtime_change (component, 1); } /* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */ void gc_component_reload (int component) { do_runtime_change (component, 0); } /* More or less Robust version of dgettext. It has the side effect of switching the codeset to utf-8 because this is what we want to output. In theory it is possible to keep the original code set and switch back for regular diagnostic output (redefine "_(" for that) but given the nature of this tool, being something invoked from other programs, it does not make much sense. */ static const char * my_dgettext (const char *domain, const char *msgid) { if (!msgid || !*msgid) return msgid; /* Shortcut form "" which has the PO files meta data. */ #ifdef USE_SIMPLE_GETTEXT if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; gettext_use_utf8 (1); } if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; /* FIXME: we have no dgettext, thus we can't switch. */ text = (char*)gettext (msgid); return text ? text : msgid; } else return msgid; #elif defined(ENABLE_NLS) if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; bind_textdomain_codeset (PACKAGE_GT, "utf-8"); bindtextdomain (DIRMNGR_NAME, gnupg_localedir ()); bind_textdomain_codeset (DIRMNGR_NAME, "utf-8"); } /* Note: This is a hack to actually use the gnupg2 domain as long we are in a transition phase where gnupg 1.x and 1.9 may coexist. */ if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; text = dgettext (domain, msgid); return text ? text : msgid; } else return msgid; #else (void)domain; return msgid; #endif } /* Percent-Escape special characters. The string is valid until the next invocation of the function. */ char * gc_percent_escape (const char *src) { static char *esc_str; static int esc_str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (esc_str_len < new_len) { char *new_esc_str = xrealloc (esc_str, new_len); esc_str = new_esc_str; esc_str_len = new_len; } dst = esc_str; while (*src) { if (*src == '%') { *(dst++) = '%'; *(dst++) = '2'; *(dst++) = '5'; } else if (*src == ':') { /* The colon is used as field separator. */ *(dst++) = '%'; *(dst++) = '3'; *(dst++) = 'a'; } else if (*src == ',') { /* The comma is used as list separator. */ *(dst++) = '%'; *(dst++) = '2'; *(dst++) = 'c'; } else if (*src == '\n') { /* The newline is problematic in a line-based format. */ *(dst++) = '%'; *(dst++) = '0'; *(dst++) = 'a'; } else *(dst++) = *(src); src++; } *dst = '\0'; return esc_str; } /* Percent-Deescape special characters. The string is valid until the next invocation of the function. */ static char * percent_deescape (const char *src) { static char *str; static int str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (str_len < new_len) { char *new_str = xrealloc (str, new_len); str = new_str; str_len = new_len; } dst = str; while (*src) { if (*src == '%') { int val = hextobyte (src + 1); if (val < 0) gc_error (1, 0, "malformed end of string %s", src); *(dst++) = (char) val; src += 3; } else *(dst++) = *(src++); } *dst = '\0'; return str; } /* List all components that are available. */ void gc_component_list_components (estream_t out) { gc_component_id_t component; const char *desc; const char *pgmname; for (component = 0; component < GC_COMPONENT_NR; component++) { if (!gc_component[component].program) continue; if (gc_component[component].module_name) pgmname = gnupg_module_name (gc_component[component].module_name); else pgmname = ""; desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].program, gc_percent_escape (desc)); es_fprintf (out, "%s\n", gc_percent_escape (pgmname)); } } static int all_digits_p (const char *p, size_t len) { if (!len) return 0; /* No. */ for (; len; len--, p++) if (!isascii (*p) || !isdigit (*p)) return 0; /* No. */ return 1; /* Yes. */ } /* Collect all error lines from stream FP. Only lines prefixed with TAG are considered. Returns a list of error line items (which may be empty). There is no error return. */ static error_line_t collect_error_output (estream_t fp, const char *tag) { char buffer[1024]; char *p, *p2, *p3; int c, cont_line; unsigned int pos; error_line_t eitem, errlines, *errlines_tail; size_t taglen = strlen (tag); errlines = NULL; errlines_tail = &errlines; pos = 0; cont_line = 0; while ((c=es_getc (fp)) != EOF) { buffer[pos++] = c; if (pos >= sizeof buffer - 5 || c == '\n') { buffer[pos - (c == '\n')] = 0; if (cont_line) ; /*Ignore continuations of previous line. */ else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':') { /* "gpgsm: foo:4: bla" */ /* Yep, we are interested in this line. */ p = buffer + taglen + 1; while (*p == ' ' || *p == '\t') p++; trim_trailing_spaces (p); /* Get rid of extra CRs. */ if (!*p) ; /* Empty lines are ignored. */ else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':')) && all_digits_p (p2+1, p3 - (p2+1))) { /* Line in standard compiler format. */ p3++; while (*p3 == ' ' || *p3 == '\t') p3++; eitem = xmalloc (sizeof *eitem + strlen (p)); eitem->next = NULL; strcpy (eitem->buffer, p); eitem->fname = eitem->buffer; eitem->buffer[p2-p] = 0; eitem->errtext = eitem->buffer + (p3 - p); /* (we already checked that there are only ascii digits followed by a colon) */ eitem->lineno = 0; for (p2++; isdigit (*p2); p2++) eitem->lineno = eitem->lineno*10 + (*p2 - '0'); *errlines_tail = eitem; errlines_tail = &eitem->next; } else { /* Other error output. */ eitem = xmalloc (sizeof *eitem + strlen (p)); eitem->next = NULL; strcpy (eitem->buffer, p); eitem->fname = NULL; eitem->errtext = eitem->buffer; eitem->lineno = 0; *errlines_tail = eitem; errlines_tail = &eitem->next; } } pos = 0; /* If this was not a complete line mark that we are in a continuation. */ cont_line = (c != '\n'); } } /* We ignore error lines not terminated by a LF. */ return errlines; } /* Check the options of a single component. If CONF_FILE is NULL the * standard config file is used. If OUT is not NULL the output is * written to that stream. Returns 0 if everything is OK. */ int gc_component_check_options (int component, estream_t out, const char *conf_file) { gpg_error_t err; unsigned int result; const char *pgmname; const char *argv[6]; int i; pid_t pid; int exitcode; estream_t errfp; error_line_t errlines; log_assert (component >= 0 && component < GC_COMPONENT_NR); if (!gc_component[component].program) return 0; if (!gc_component[component].module_name) return 0; pgmname = gnupg_module_name (gc_component[component].module_name); i = 0; if (!gnupg_default_homedir_p () && component != GC_COMPONENT_PINENTRY) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } if (conf_file) { argv[i++] = "--options"; argv[i++] = conf_file; } if (component == GC_COMPONENT_PINENTRY) argv[i++] = "--version"; else argv[i++] = "--gpgconf-test"; argv[i] = NULL; log_assert (i < DIM(argv)); result = 0; errlines = NULL; err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, NULL, NULL, &errfp, &pid); if (err) result |= 1; /* Program could not be run. */ else { errlines = collect_error_output (errfp, gc_component[component].name); if (gnupg_wait_process (pgmname, pid, 1, &exitcode)) { if (exitcode == -1) result |= 1; /* Program could not be run or it terminated abnormally. */ result |= 2; /* Program returned an error. */ } gnupg_release_process (pid); es_fclose (errfp); } /* If the program could not be run, we can't tell whether the config file is good. */ if (result & 1) result |= 2; if (out) { const char *desc; error_line_t errptr; desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].program, gc_percent_escape (desc)); es_fputs (gc_percent_escape (pgmname), out); es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2)); for (errptr = errlines; errptr; errptr = errptr->next) { if (errptr != errlines) es_fputs ("\n:::::", out); /* Continuation line. */ if (errptr->fname) es_fputs (gc_percent_escape (errptr->fname), out); es_putc (':', out); if (errptr->fname) es_fprintf (out, "%u", errptr->lineno); es_putc (':', out); es_fputs (gc_percent_escape (errptr->errtext), out); es_putc (':', out); } es_putc ('\n', out); } while (errlines) { error_line_t tmp = errlines->next; xfree (errlines); errlines = tmp; } return result; } /* Check all components that are available. */ void gc_check_programs (estream_t out) { gc_component_id_t component; for (component = 0; component < GC_COMPONENT_NR; component++) gc_component_check_options (component, out, NULL); } /* Find the component with the name NAME. Returns -1 if not found. */ int gc_component_find (const char *name) { gc_component_id_t idx; for (idx = 0; idx < GC_COMPONENT_NR; idx++) { if (gc_component[idx].program && !strcmp (name, gc_component[idx].program)) return idx; } return -1; } /* List the option OPTION. */ static void list_one_option (gc_component_id_t component, const gc_option_t *option, estream_t out) { const char *desc = NULL; char *arg_name = NULL; unsigned long flags; const char *desc_domain = gc_component[component].desc_domain; /* Don't show options with the ignore attribute. */ if (option->attr_ignore && !option->attr_force) return; if (option->desc) { desc = my_dgettext (desc_domain, option->desc); if (*desc == '|') { const char *arg_tail = strchr (&desc[1], '|'); if (arg_tail) { int arg_len = arg_tail - &desc[1]; arg_name = xmalloc (arg_len + 1); memcpy (arg_name, &desc[1], arg_len); arg_name[arg_len] = '\0'; desc = arg_tail + 1; } } } /* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY FIELDS. */ /* The name field. */ es_fprintf (out, "%s", option->name); /* The flags field. */ flags = 0; if (option->is_header) flags |= GC_OPT_FLAG_GROUP; if (option->is_list) flags |= GC_OPT_FLAG_LIST; if (option->runtime) flags |= GC_OPT_FLAG_RUNTIME; if (option->has_default) flags |= GC_OPT_FLAG_DEFAULT; if (option->def_in_desc) flags |= GC_OPT_FLAG_DEF_DESC; if (option->no_arg_desc) flags |= GC_OPT_FLAG_NO_ARG_DESC; if (option->no_change) flags |= GC_OPT_FLAG_NO_CHANGE; if (option->attr_force) flags |= GC_OPT_FLAG_NO_CHANGE; es_fprintf (out, ":%lu", flags); if (opt.verbose) { es_putc (' ', out); if (!flags) es_fprintf (out, "none"); else { unsigned long flag = 0; unsigned long first = 1; while (flags) { if (flags & 1) { if (first) first = 0; else es_putc (',', out); es_fprintf (out, "%s", gc_flag[flag].name); } flags >>= 1; flag++; } } } /* The level field. */ es_fprintf (out, ":%u", option->level); if (opt.verbose) es_fprintf (out, " %s", gc_level[option->level].name); /* The description field. */ es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : ""); /* The type field. */ es_fprintf (out, ":%u", option->arg_type); if (opt.verbose) es_fprintf (out, " %s", gc_arg_type[option->arg_type].name); /* The alternate type field. */ es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback); if (opt.verbose) es_fprintf (out, " %s", gc_arg_type[gc_arg_type[option->arg_type].fallback].name); /* The argument name field. */ es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : ""); xfree (arg_name); /* The default value field. */ es_fprintf (out, ":%s", option->default_value ? option->default_value : ""); /* The default argument field. This was never used and is thus empty. */ es_fprintf (out, ":"); /* The value field. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && option->is_list && option->value) { /* The special format "1,1,1,1,...,1" is converted to a number here. */ es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2)); } else es_fprintf (out, ":%s", option->value ? option->value : ""); /* ADD NEW FIELDS HERE. */ es_putc ('\n', out); } /* List all options of the component COMPONENT. */ void gc_component_list_options (int component, estream_t out) { const gc_option_t *option = gc_component[component].options; for ( ; option && option->name; option++) { /* Do not output unknown or internal options. */ if (!option->is_header && option->level == GC_LEVEL_INTERNAL) continue; if (option->is_header) { const gc_option_t *group_option = option + 1; gc_expert_level_t level = GC_LEVEL_NR; /* The manual states that the group level is always the minimum of the levels of all contained options. Due to different active options, and because it is hard to maintain manually, we calculate it here. The value in the global static table is ignored. */ for ( ; group_option->name; group_option++) { if (group_option->is_header) break; if (group_option->level < level) level = group_option->level; } /* Check if group is empty. */ if (level != GC_LEVEL_NR) { gc_option_t opt_copy; /* Fix up the group level. */ opt_copy = *option; opt_copy.level = level; list_one_option (component, &opt_copy, out); } } else list_one_option (component, option, out); } } /* Return true if the option NAME is known and that we want it as * gpgconf managed option. */ static known_option_t * is_known_option (gc_component_id_t component, const char *name) { known_option_t *option = gc_component[component].known_options; if (option) { for (; option->name; option++) if (!strcmp (option->name, name)) break; } return (option && option->name)? option : NULL; } /* Find the option NAME in component COMPONENT. Returns pointer to * the option descriptor or NULL if not found. */ static gc_option_t * find_option (gc_component_id_t component, const char *name) { gc_option_t *option = gc_component[component].options; if (option) { for (; option->name; option++) { if (!option->is_header && !strcmp (option->name, name)) return option; } } return NULL; } struct read_line_wrapper_parm_s { const char *pgmname; estream_t fp; char *line; size_t line_len; const char **extra_lines; int extra_lines_idx; char *extra_line_buffer; }; /* Helper for retrieve_options_from_program. */ static ssize_t read_line_wrapper (struct read_line_wrapper_parm_s *parm) { ssize_t length; const char *extra_line; if (parm->fp) { length = es_read_line (parm->fp, &parm->line, &parm->line_len, NULL); if (length > 0) return length; if (length < 0 || es_ferror (parm->fp)) gc_error (1, errno, "error reading from %s", parm->pgmname); if (es_fclose (parm->fp)) gc_error (1, errno, "error closing %s", parm->pgmname); /* EOF seen. */ parm->fp = NULL; } /* Return the made up lines. */ if (!parm->extra_lines || !(extra_line = parm->extra_lines[parm->extra_lines_idx])) return -1; /* This is really the EOF. */ parm->extra_lines_idx++; xfree (parm->extra_line_buffer); parm->extra_line_buffer = xstrdup (extra_line); return strlen (parm->extra_line_buffer); } /* Retrieve the options for the component COMPONENT. With * ONLY_INSTALLED set components which are not installed are silently * ignored. */ static void retrieve_options_from_program (gc_component_id_t component, int only_installed) { gpg_error_t err; const char *pgmname; const char *argv[2]; estream_t outfp; int exitcode; pid_t pid; known_option_t *known_option; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; const char *config_name; gpgrt_argparse_t pargs; int dummy_argc; char *twopartconfig_name = NULL; gpgrt_opt_t *opt_table = NULL; /* A malloced option table. */ size_t opt_table_used = 0; /* Its current length. */ size_t opt_table_size = 0; /* Its allocated length. */ gc_option_t *opt_info = NULL; /* A malloced options table. */ size_t opt_info_used = 0; /* Its current length. */ size_t opt_info_size = 0; /* Its allocated length. */ int i; struct read_line_wrapper_parm_s read_line_parm; int pseudo_count; pgmname = (gc_component[component].module_name ? gnupg_module_name (gc_component[component].module_name) : gc_component[component].program ); if (only_installed && gnupg_access (pgmname, X_OK)) { return; /* The component is not installed. */ } /* First we need to read the option table from the program. */ argv[0] = "--dump-option-table"; argv[1] = NULL; err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, NULL, &outfp, NULL, &pid); if (err) { gc_error (1, 0, "could not gather option table from '%s': %s", pgmname, gpg_strerror (err)); } read_line_parm.pgmname = pgmname; read_line_parm.fp = outfp; read_line_parm.line = line; read_line_parm.line_len = line_len = 0; read_line_parm.extra_line_buffer = NULL; read_line_parm.extra_lines = gc_component[component].known_pseudo_options; read_line_parm.extra_lines_idx = 0; pseudo_count = 0; while ((length = read_line_wrapper (&read_line_parm)) > 0) { const char *fields[4]; const char *optname, *optdesc; unsigned int optflags; int short_opt; gc_arg_type_t arg_type; int pseudo = 0; if (read_line_parm.extra_line_buffer) { line = read_line_parm.extra_line_buffer; pseudo = 1; pseudo_count++; } else line = read_line_parm.line; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; if (split_fields_colon (line, fields, DIM (fields)) < 4) { gc_error (0,0, "WARNING: invalid line in option table of '%s'\n", pgmname); continue; } optname = fields[0]; short_opt = atoi (fields[1]); if (short_opt < 1 && !pseudo) { gc_error (0,0, "WARNING: bad short option in option table of '%s'\n", pgmname); continue; } optflags = strtoul (fields[2], NULL, 10); if ((optflags & ARGPARSE_OPT_HEADER)) known_option = NULL; /* We want all header-only options. */ else if ((known_option = is_known_option (component, optname))) ; /* Yes we want this one. */ else continue; /* No need to store this option description. */ /* The +1 here is to make sure that we will have a zero item at * the end of the table. */ if (opt_table_used + 1 >= opt_table_size) { /* Note that this also does the initial allocation. */ opt_table_size += 128; opt_table = xreallocarray (opt_table, opt_table_used, opt_table_size, sizeof *opt_table); } /* The +1 here is to make sure that we will have a zero item at * the end of the table. */ if (opt_info_used + 1 >= opt_info_size) { /* Note that this also does the initial allocation. */ opt_info_size += 128; opt_info = xreallocarray (opt_info, opt_info_used, opt_info_size, sizeof *opt_info); } /* The +1 here accounts for the two items we are going to add to * the global string table. */ if (string_array_used + 1 >= string_array_size) { string_array_size += 256; string_array = xreallocarray (string_array, string_array_used, string_array_size, sizeof *string_array); } optname = string_array[string_array_used++] = xstrdup (fields[0]); optdesc = string_array[string_array_used++] = xstrdup (fields[3]); /* Create an option table which can then be supplied to * gpgrt_parser. Unfortunately there is no private pointer in * the public option table struct so that we can't add extra * data we need here. Thus we need to build up another table * for such info and for ease of use we also copy the tehre the * data from the option table. It is not possible to use the * known_option_s for this because that one does not carry * header lines and it might also be problematic to use such * static tables for caching options and default values. */ if (!pseudo) { opt_table[opt_table_used].long_opt = optname; opt_table[opt_table_used].short_opt = short_opt; opt_table[opt_table_used].description = optdesc; opt_table[opt_table_used].flags = optflags; opt_table_used++; } /* Note that as per argparser specs the opt_table uses "@" to * specifify an empty description. In the DESC script of * options (opt_info_t) we want to have a real empty string. */ opt_info[opt_info_used].name = optname; if (*optdesc == '@' && !optdesc[1]) opt_info[opt_info_used].desc = optdesc+1; else opt_info[opt_info_used].desc = optdesc; /* Unfortunately we need to remap the types. */ switch ((optflags & ARGPARSE_TYPE_MASK)) { case ARGPARSE_TYPE_INT: arg_type = GC_ARG_TYPE_INT32; break; case ARGPARSE_TYPE_LONG: arg_type = GC_ARG_TYPE_INT32; break; case ARGPARSE_TYPE_ULONG: arg_type = GC_ARG_TYPE_UINT32; break; case ARGPARSE_TYPE_STRING: arg_type = GC_ARG_TYPE_STRING; break; default: arg_type = GC_ARG_TYPE_NONE; break; } opt_info[opt_info_used].arg_type = arg_type; if (pseudo) /* Pseudo options are always no_change. */ opt_info[opt_info_used].no_change = 1; if ((optflags & ARGPARSE_OPT_HEADER)) opt_info[opt_info_used].is_header = 1; if (known_option) { if ((known_option->flags & GC_OPT_FLAG_LIST)) opt_info[opt_info_used].is_list = 1; /* FIXME: The next can also be taken from opt_table->flags. * We need to check the code whether both specifications match. */ if ((known_option->flags & GC_OPT_FLAG_ARG_OPT)) opt_info[opt_info_used].opt_arg = 1; if ((known_option->flags & GC_OPT_FLAG_RUNTIME)) opt_info[opt_info_used].runtime = 1; opt_info[opt_info_used].level = known_option->level; /* Override the received argtype by a complex type. */ if (known_option->arg_type) opt_info[opt_info_used].arg_type = known_option->arg_type; } opt_info_used++; } xfree (read_line_parm.extra_line_buffer); line = read_line_parm.line; line_len = read_line_parm.line_len; log_assert (opt_table_used + pseudo_count == opt_info_used); err = gnupg_wait_process (pgmname, pid, 1, &exitcode); if (err) gc_error (1, 0, "running %s failed (exitcode=%d): %s", pgmname, exitcode, gpg_strerror (err)); gnupg_release_process (pid); /* Make the gpgrt option table and the internal option table available. */ gc_component[component].opt_table = opt_table; gc_component[component].options = opt_info; /* Now read the default options. */ argv[0] = "--gpgconf-list"; argv[1] = NULL; err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, NULL, &outfp, NULL, &pid); if (err) { gc_error (1, 0, "could not gather active options from '%s': %s", pgmname, gpg_strerror (err)); } while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) { char *linep; unsigned long flags = 0; char *default_value = NULL; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; gpg_err_set_errno (0); flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s from %s", line, pgmname); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s from %s", line, pgmname); linep = end; } /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; if ((flags & GC_OPT_FLAG_DEFAULT)) default_value = linep; linep = end; } /* Look up the option in the component and install the configuration data. */ option = find_option (component, line); if (option) { if (option->gpgconf_list) gc_error (1, errno, "option %s returned twice from \"%s --gpgconf-list\"", line, pgmname); option->gpgconf_list = 1; if ((flags & GC_OPT_FLAG_DEFAULT)) option->has_default = 1; if ((flags & GC_OPT_FLAG_DEF_DESC)) option->def_in_desc = 1; if ((flags & GC_OPT_FLAG_NO_ARG_DESC)) option->no_arg_desc = 1; if ((flags & GC_OPT_FLAG_NO_CHANGE)) option->no_change = 1; if (default_value && *default_value) option->default_value = xstrdup (default_value); } } if (length < 0 || es_ferror (outfp)) gc_error (1, errno, "error reading from %s", pgmname); if (es_fclose (outfp)) gc_error (1, errno, "error closing %s", pgmname); err = gnupg_wait_process (pgmname, pid, 1, &exitcode); if (err) gc_error (1, 0, "running %s failed (exitcode=%d): %s", pgmname, exitcode, gpg_strerror (err)); gnupg_release_process (pid); /* At this point, we can parse the configuration file. */ config_name = gc_component[component].option_config_filename; if (!config_name) gc_error (1, 0, "name of config file for %s is not known\n", pgmname); if (!gnupg_default_homedir_p ()) { /* This is not the default homedir. We need to take an absolute * config name for the user config file; gpgrt_argparser * fortunately supports this. */ char *tmp = make_filename (gnupg_homedir (), config_name, NULL); twopartconfig_name = xstrconcat (config_name, PATHSEP_S, tmp, NULL); xfree (tmp); config_name = twopartconfig_name; } memset (&pargs, 0, sizeof pargs); dummy_argc = 0; pargs.argc = &dummy_argc; pargs.flags = (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER | ARGPARSE_FLAG_WITHATTR); if (opt.verbose) pargs.flags |= ARGPARSE_FLAG_VERBOSE; while (gpgrt_argparser (&pargs, opt_table, config_name)) { char *opt_value; if (pargs.r_opt == ARGPARSE_CONFFILE) { /* log_debug ("current conffile='%s'\n", */ /* pargs.r_type? pargs.r.ret_str: "[cmdline]"); */ continue; } if ((pargs.r_type & ARGPARSE_OPT_IGNORE)) continue; /* We only have the short option. Search in the option table * for the long option name. */ for (i=0; opt_table[i].short_opt; i++) if (opt_table[i].short_opt == pargs.r_opt) break; if (!opt_table[i].short_opt || !opt_table[i].long_opt) continue; /* No or only a short option - ignore. */ /* Look up the option from the config file in our list of * supported options. */ option= find_option (component, opt_table[i].long_opt); if (!option) continue; /* We don't want to handle this option. */ /* Set the force and ignore attributes. The idea is that there * is no way to clear them again, thus we set them when first * encountered. */ if ((pargs.r_type & ARGPARSE_ATTR_FORCE)) option->attr_force = 1; if ((pargs.r_type & ARGPARSE_ATTR_IGNORE)) option->attr_ignore = 1; /* If an option has been ignored, there is no need to return * that option with gpgconf --list-options. */ if (option->attr_ignore) continue; switch ((pargs.r_type & ARGPARSE_TYPE_MASK)) { case ARGPARSE_TYPE_INT: opt_value = xasprintf ("%d", pargs.r.ret_int); break; case ARGPARSE_TYPE_LONG: opt_value = xasprintf ("%ld", pargs.r.ret_long); break; case ARGPARSE_TYPE_ULONG: opt_value = xasprintf ("%lu", pargs.r.ret_ulong); break; case ARGPARSE_TYPE_STRING: if (!pargs.r.ret_str) opt_value = xstrdup ("\"(none)"); /* We should not see this. */ else opt_value = xasprintf ("\"%s", gc_percent_escape (pargs.r.ret_str)); break; default: /* ARGPARSE_TYPE_NONE or any unknown type. */ opt_value = xstrdup ("1"); /* Make sure we have some value. */ break; } /* Now enter the value read from the config file into the table. */ if (!option->is_list) { xfree (option->value); option->value = opt_value; } else if (!option->value) /* LIST but first item. */ option->value = opt_value; else { char *old = option->value; option->value = xstrconcat (old, ",", opt_value, NULL); xfree (old); xfree (opt_value); } } xfree (line); xfree (twopartconfig_name); } /* Retrieve the currently active options and their defaults for this component. Using -1 for component will retrieve all options from all installed components. */ void gc_component_retrieve_options (int component) { int process_all = 0; if (component == -1) { process_all = 1; component = 0; } do { if (component == GC_COMPONENT_PINENTRY) continue; /* Skip this dummy component. */ if (gc_component[component].program) retrieve_options_from_program (component, process_all); } while (process_all && ++component < GC_COMPONENT_NR); } /* Perform a simple validity check based on the type. Return in * NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of * type GC_ARG_TYPE_NONE. If VERBATIM is set the profile parsing mode * is used. */ static void option_check_validity (gc_component_id_t component, gc_option_t *option, unsigned long flags, char *new_value, unsigned long *new_value_nr, int verbatim) { char *arg; (void)component; if (option->new_flags || option->new_value) gc_error (1, 0, "option %s already changed", option->name); if (flags & GC_OPT_FLAG_DEFAULT) { if (*new_value) gc_error (1, 0, "argument %s provided for deleted option %s", new_value, option->name); return; } /* GC_ARG_TYPE_NONE options have special list treatment. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { char *tail; gpg_err_set_errno (0); *new_value_nr = strtoul (new_value, &tail, 0); if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*tail) gc_error (1, 0, "garbage after argument for option %s", option->name); if (!option->is_list) { if (*new_value_nr != 1) gc_error (1, 0, "argument for non-list option %s of type 0 " "(none) must be 1", option->name); } else { if (*new_value_nr == 0) gc_error (1, 0, "argument for option %s of type 0 (none) " "must be positive", option->name); } return; } arg = new_value; do { if (*arg == '\0' || (*arg == ',' && !verbatim)) { if (!option->opt_arg) gc_error (1, 0, "argument required for option %s", option->name); if (*arg == ',' && !verbatim && !option->is_list) gc_error (1, 0, "list found for non-list option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { if (*arg != '"' && !verbatim) gc_error (1, 0, "string argument for option %s must begin " "with a quote (\") character", option->name); /* FIXME: We do not allow empty string arguments for now, as we do not quote arguments in configuration files, and thus no argument is indistinguishable from the empty string. */ if (arg[1] == '\0' || (arg[1] == ',' && !verbatim)) gc_error (1, 0, "empty string argument for option %s is " "currently not allowed. Please report this!", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32) { long res; gpg_err_set_errno (0); res = strtol (arg, &arg, 0); (void) res; if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && (*arg != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32) { unsigned long res; gpg_err_set_errno (0); res = strtoul (arg, &arg, 0); (void) res; if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && (*arg != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } arg = verbatim? strchr (arg, ',') : NULL; if (arg) arg++; } while (arg && *arg); } #ifdef HAVE_W32_SYSTEM int copy_file (const char *src_name, const char *dst_name) { #define BUF_LEN 4096 char buffer[BUF_LEN]; int len; gpgrt_stream_t src; gpgrt_stream_t dst; src = gpgrt_fopen (src_name, "r"); if (src == NULL) return -1; dst = gpgrt_fopen (dst_name, "w"); if (dst == NULL) { int saved_err = errno; gpgrt_fclose (src); gpg_err_set_errno (saved_err); return -1; } do { int written; len = gpgrt_fread (buffer, 1, BUF_LEN, src); if (len == 0) break; written = gpgrt_fwrite (buffer, 1, len, dst); if (written != len) break; } while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst)); if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src)) { int saved_errno = errno; gpgrt_fclose (src); gpgrt_fclose (dst); unlink (dst_name); gpg_err_set_errno (saved_errno); return -1; } if (gpgrt_fclose (dst)) gc_error (1, errno, "error closing %s", dst_name); if (gpgrt_fclose (src)) gc_error (1, errno, "error closing %s", src_name); return 0; } #endif /* HAVE_W32_SYSTEM */ /* Create and verify the new configuration file for the specified * component. Returns 0 on success and -1 on error. If * VERBATIM is set the profile mode is used. This function may store * pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and * ORIG_FILENAMEP. Those must be freed by the caller. The strings * refer to three versions of the configuration file: * * SRC_FILENAME: The updated configuration is written to this file. * DEST_FILENAME: Name of the configuration file read by the * component. * ORIG_FILENAME: A backup of the previous configuration file. * * To apply the configuration change, rename SRC_FILENAME to * DEST_FILENAME. To revert to the previous configuration, rename * ORIG_FILENAME to DEST_FILENAME. */ static int change_options_program (gc_component_id_t component, char **src_filenamep, char **dest_filenamep, char **orig_filenamep, int verbatim) { static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###"; /* True if we are within the marker in the config file. */ int in_marker = 0; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; int res; int fd; gpgrt_stream_t src_file = NULL; gpgrt_stream_t dest_file = NULL; char *src_filename; char *dest_filename; char *orig_filename; /* Special hack for gpg, see below. */ int utf8strings_seen = 0; /* FIXME. Throughout the function, do better error reporting. */ if (!gc_component[component].option_config_filename) gc_error (1, 0, "name of config file for %s is not known\n", gc_component[component].name); dest_filename = make_absfilename (gnupg_homedir (), gc_component[component].option_config_filename, NULL); src_filename = xasprintf ("%s.%s.%i.new", dest_filename, GPGCONF_NAME, (int)getpid ()); orig_filename = xasprintf ("%s.%s.%i.bak", dest_filename, GPGCONF_NAME, (int)getpid ()); #ifdef HAVE_W32_SYSTEM res = copy_file (dest_filename, orig_filename); #else res = link (dest_filename, orig_filename); #endif if (res < 0 && errno != ENOENT) { xfree (dest_filename); xfree (src_filename); xfree (orig_filename); return -1; } if (res < 0) { xfree (orig_filename); orig_filename = NULL; } /* We now initialize the return strings, so the caller can do the cleanup for us. */ *src_filenamep = src_filename; *dest_filenamep = dest_filename; *orig_filenamep = orig_filename; /* Use open() so that we can use O_EXCL. * FIXME: gpgrt has an x flag for quite some time now - use that. */ fd = gnupg_open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) return -1; src_file = gpgrt_fdopen (fd, "w"); res = errno; if (!src_file) { gpg_err_set_errno (res); return -1; } /* Only if ORIG_FILENAME is not NULL did the configuration file exist already. In this case, we will copy its content into the new configuration file, changing it to our liking in the process. */ if (orig_filename) { dest_file = gpgrt_fopen (dest_filename, "r"); if (!dest_file) goto change_one_err; while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { int disable = 0; char *start; if (!strncmp (marker, line, sizeof (marker) - 1)) { if (!in_marker) in_marker = 1; else break; } else if (component == GC_COMPONENT_GPG && in_marker && ! strcmp ("utf8-strings\n", line)) { /* Strip duplicated entries. */ if (utf8strings_seen) disable = 1; else utf8strings_seen = 1; } start = line; while (*start == ' ' || *start == '\t') start++; if (*start && *start != '\r' && *start != '\n' && *start != '#') { char *end; char saved_end; end = start; while (*end && *end != ' ' && *end != '\t' && *end != '\r' && *end != '\n' && *end != '#') end++; saved_end = *end; *end = '\0'; option = find_option (component, start); *end = saved_end; if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT) || option->new_value)) disable = 1; } if (disable) { if (!in_marker) { gpgrt_fprintf (src_file, "# %s disabled this option here at %s\n", GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# %s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } else { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } if (!in_marker) { /* There was no marker. This is the first time we edit the file. We add our own marker at the end of the file and proceed. Note that we first write a newline, this guards us against files which lack the newline at the end of the last line, while it doesn't hurt us in all other cases. */ gpgrt_fprintf (src_file, "\n%s\n", marker); if (gpgrt_ferror (src_file)) goto change_one_err; } /* At this point, we have copied everything up to the end marker into the new file, except for the options we are going to change. Now, dump the changed options (except for those we are going to revert to their default), and write the end marker, possibly followed by the rest of the original file. */ /* We have to turn on UTF8 strings for GnuPG. */ if (component == GC_COMPONENT_GPG && ! utf8strings_seen) gpgrt_fprintf (src_file, "utf8-strings\n"); option = gc_component[component].options; for ( ; option->name; option++) { if (!option->is_header && option->new_value) { char *arg = option->new_value; do { if (*arg == '\0' || *arg == ',') { gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { log_assert (*arg == '1'); gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; arg++; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { char *end; if (!verbatim) { log_assert (*arg == '"'); arg++; end = strchr (arg, ','); if (end) *end = '\0'; } else end = NULL; gpgrt_fprintf (src_file, "%s %s\n", option->name, verbatim? arg : percent_deescape (arg)); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } else { char *end; end = strchr (arg, ','); if (end) *end = '\0'; gpgrt_fprintf (src_file, "%s %s\n", option->name, arg); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } log_assert (arg == NULL || *arg == '\0' || *arg == ','); if (arg && *arg == ',') arg++; } while (arg && *arg); } } gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; if (!in_marker) { gpgrt_fprintf (src_file, "# %s edited this configuration file.\n", GPGCONF_DISP_NAME); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# never change anything below these lines.\n"); if (gpgrt_ferror (src_file)) goto change_one_err; } if (dest_file) { while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } xfree (line); line = NULL; res = gpgrt_fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } close (fd); if (dest_file) { res = gpgrt_fclose (dest_file); if (res) return -1; } return 0; change_one_err: xfree (line); res = errno; if (src_file) { gpgrt_fclose (src_file); close (fd); } if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } /* Common code for gc_component_change_options and * gc_process_gpgconf_conf. If VERBATIM is set the profile parsing * mode is used. */ static void change_one_value (gc_component_id_t component, gc_option_t *option, int *r_runtime, unsigned long flags, char *new_value, int verbatim) { unsigned long new_value_nr = 0; option_check_validity (component, option, flags, new_value, &new_value_nr, verbatim); if (option->runtime) *r_runtime = 1; option->new_flags = flags; if (!(flags & GC_OPT_FLAG_DEFAULT)) { if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && option->is_list) { char *str; /* We convert the number to a list of 1's for convenient list handling. */ log_assert (new_value_nr > 0); option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1); str = option->new_value; *(str++) = '1'; while (--new_value_nr > 0) { *(str++) = ','; *(str++) = '1'; } *(str++) = '\0'; } else option->new_value = xstrdup (new_value); } } /* Read the modifications from IN and apply them. If IN is NULL the modifications are expected to already have been set to the global table. If VERBATIM is set the profile mode is used. */ void gc_component_change_options (int component, estream_t in, estream_t out, int verbatim) { int err = 0; int block = 0; int runtime = 0; char *src_filename = NULL; char *dest_filename = NULL; char *orig_filename = NULL; gc_option_t *option; char *line = NULL; size_t line_len = 0; ssize_t length; if (component == GC_COMPONENT_PINENTRY) return; /* Dummy component for now. */ if (in) { /* Read options from the file IN. */ while ((length = es_read_line (in, &line, &line_len, NULL)) > 0) { char *linep; unsigned long flags = 0; char *new_value = ""; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; gpg_err_set_errno (0); flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s", line); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s", line); linep = end; } /* Don't allow setting of the no change flag. */ flags &= ~GC_OPT_FLAG_NO_CHANGE; /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; new_value = linep; linep = end; } option = find_option (component, line); if (!option) gc_error (1, 0, "unknown option %s", line); if (option->no_change) { gc_error (0, 0, "ignoring new value for option %s", option->name); continue; } change_one_value (component, option, &runtime, flags, new_value, 0); } if (length < 0 || gpgrt_ferror (in)) gc_error (1, errno, "error reading stream 'in'"); } /* Now that we have collected and locally verified the changes, write them out to new configuration files, verify them externally, and then commit them. */ option = gc_component[component].options; while (option && option->name) { /* Go on if there is nothing to do. */ if (src_filename || !(option->new_flags || option->new_value)) { option++; continue; } if (gc_component[component].program) { err = change_options_program (component, &src_filename, &dest_filename, &orig_filename, verbatim); if (! err) { /* External verification. */ err = gc_component_check_options (component, out, src_filename); if (err) { gc_error (0, 0, _("External verification of component %s failed"), gc_component[component].name); gpg_err_set_errno (EINVAL); } } } if (err) break; option++; } /* We are trying to atomically commit all changes. Unfortunately, we cannot rely on gnupg_rename_file to manage the signals for us, doing so would require us to pass NULL as BLOCK to any subsequent call to it. Instead, we just manage the signal handling manually. */ block = 1; gnupg_block_all_signals (); if (!err && !opt.dry_run) { if (src_filename) { /* FIXME: Make a verification here. */ log_assert (dest_filename); if (orig_filename) err = gnupg_rename_file (src_filename, dest_filename, NULL); else { #ifdef HAVE_W32_SYSTEM /* We skip the unlink if we expect the file not to be * there. */ err = gnupg_rename_file (src_filename, dest_filename, NULL); #else /* HAVE_W32_SYSTEM */ /* This is a bit safer than rename() because we expect * DEST_FILENAME not to be there. If it happens to be * there, this will fail. */ err = link (src_filename, dest_filename); if (!err) err = unlink (src_filename); #endif /* !HAVE_W32_SYSTEM */ } if (!err) { xfree (src_filename); src_filename = NULL; } } } if (err || opt.dry_run) { int saved_errno = errno; /* An error occurred or a dry-run is requested. */ if (src_filename) { /* The change was not yet committed. */ unlink (src_filename); if (orig_filename) unlink (orig_filename); } else { /* The changes were already committed. FIXME: This is a tad dangerous, as we don't know if we don't overwrite a version of the file that is even newer than the one we just installed. */ if (orig_filename) gnupg_rename_file (orig_filename, dest_filename, NULL); else unlink (dest_filename); } if (err) gc_error (1, saved_errno, "could not commit changes"); /* Fall-through for dry run. */ goto leave; } /* If it all worked, notify the daemons of the changes. */ if (opt.runtime) do_runtime_change (component, 0); /* Move the per-process backup file into its place. */ if (orig_filename) { char *backup_filename; log_assert (dest_filename); backup_filename = xasprintf ("%s.%s.bak", dest_filename, GPGCONF_NAME); gnupg_rename_file (orig_filename, backup_filename, NULL); xfree (backup_filename); } leave: if (block) gnupg_unblock_all_signals (); xfree (line); xfree (src_filename); xfree (dest_filename); xfree (orig_filename); } /* Check whether USER matches the current user or one of its group. This function may change USER. Returns true is there is a match. */ static int key_matches_user_or_group (char *user) { char *group; if (*user == '*' && user[1] == 0) return 1; /* A single asterisk matches all users. */ group = strchr (user, ':'); if (group) *group++ = 0; #ifdef HAVE_W32_SYSTEM /* Under Windows we don't support groups. */ if (group && *group) gc_error (0, 0, _("Note that group specifications are ignored\n")); -#ifndef HAVE_W32CE_SYSTEM + if (*user) { static char *my_name; if (!my_name) { char tmp[1]; DWORD size = 1; GetUserNameA (tmp, &size); my_name = xmalloc (size); if (!GetUserNameA (my_name, &size)) gc_error (1,0, "error getting current user name: %s", w32_strerror (-1)); } if (!strcmp (user, my_name)) return 1; /* Found. */ } -#endif /*HAVE_W32CE_SYSTEM*/ + #else /*!HAVE_W32_SYSTEM*/ + /* First check whether the user matches. */ if (*user) { static char *my_name; if (!my_name) { struct passwd *pw = getpwuid ( getuid () ); if (!pw) gc_error (1, errno, "getpwuid failed for current user"); my_name = xstrdup (pw->pw_name); } if (!strcmp (user, my_name)) return 1; /* Found. */ } /* If that failed, check whether a group matches. */ if (group && *group) { static char *my_group; static char **my_supgroups; int n; if (!my_group) { struct group *gr = getgrgid ( getgid () ); if (!gr) gc_error (1, errno, "getgrgid failed for current user"); my_group = xstrdup (gr->gr_name); } if (!strcmp (group, my_group)) return 1; /* Found. */ if (!my_supgroups) { int ngids; gid_t *gids; ngids = getgroups (0, NULL); gids = xcalloc (ngids+1, sizeof *gids); ngids = getgroups (ngids, gids); if (ngids < 0) gc_error (1, errno, "getgroups failed for current user"); my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups); for (n=0; n < ngids; n++) { struct group *gr = getgrgid ( gids[n] ); if (!gr) gc_error (1, errno, "getgrgid failed for supplementary group"); my_supgroups[n] = xstrdup (gr->gr_name); } xfree (gids); } for (n=0; my_supgroups[n]; n++) if (!strcmp (group, my_supgroups[n])) return 1; /* Found. */ } + #endif /*!HAVE_W32_SYSTEM*/ + return 0; /* No match. */ } /* Read and process the global configuration file for gpgconf. This optional file is used to update our internal tables at runtime and may also be used to set new default values. If FNAME is NULL the default name will be used. With UPDATE set to true the internal tables are actually updated; if not set, only a syntax check is done. If DEFAULTS is true the global options are written to the configuration files. If LISTFP is set, no changes are done but the configuration file is printed to LISTFP in a colon separated format. Returns 0 on success or if the config file is not present; -1 is returned on error. */ int gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults, estream_t listfp) { int result = 0; char *line = NULL; size_t line_len = 0; ssize_t length; gpgrt_stream_t config; int lineno = 0; int in_rule = 0; int got_match = 0; int runtime[GC_COMPONENT_NR] = { 0 }; int component_id; char *fname; if (fname_arg) fname = xstrdup (fname_arg); else fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf", NULL); config = gpgrt_fopen (fname, "r"); if (!config) { /* Do not print an error if the file is not available, except when running in syntax check mode. */ if (errno != ENOENT || !update) { gc_error (0, errno, "can't open global config file '%s'", fname); result = -1; } xfree (fname); return result; } while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0) { char *key, *compname, *option, *flags, *value; char *empty; gc_option_t *option_info = NULL; char *p; int is_continuation; lineno++; key = line; while (*key == ' ' || *key == '\t') key++; if (!*key || *key == '#' || *key == '\r' || *key == '\n') continue; is_continuation = (key != line); /* Parse the key field. */ if (!is_continuation && got_match) break; /* Finish after the first match. */ else if (!is_continuation) { in_rule = 0; for (p=key+1; *p && !strchr (" \t\r\n", *p); p++) ; if (!*p) { gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno); result = -1; gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing rule", GPG_ERR_SYNTAX, fname, lineno); continue; } *p++ = 0; compname = p; } else if (!in_rule) { gc_error (0, 0, "continuation but no rule at '%s', line %d", fname, lineno); result = -1; continue; } else { compname = key; key = NULL; } in_rule = 1; /* Parse the component. */ while (*compname == ' ' || *compname == '\t') compname++; for (p=compname; *p && !strchr (" \t\r\n", *p); p++) ; if (p == compname) { gc_error (0, 0, "missing component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " " missing component", GPG_ERR_NO_NAME, fname, lineno); result = -1; continue; } empty = p; *p++ = 0; option = p; component_id = gc_component_find (compname); if (component_id < 0) { gc_error (0, 0, "unknown component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown component", GPG_ERR_UNKNOWN_NAME, fname, lineno); result = -1; } /* Parse the option name. */ while (*option == ' ' || *option == '\t') option++; for (p=option; *p && !strchr (" \t\r\n", *p); p++) ; if (p == option) { gc_error (0, 0, "missing option at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing option", GPG_ERR_INV_NAME, fname, lineno); result = -1; continue; } *p++ = 0; flags = p; if ( component_id != -1) { /* We need to make sure that we got the option list for the * component. */ if (!gc_component[component_id].options) gc_component_retrieve_options (component_id); option_info = find_option (component_id, option); if (!option_info) { gc_error (0, 0, "unknown option '%s' at '%s', line %d", option, fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown option", GPG_ERR_UNKNOWN_OPTION, fname, lineno); result = -1; } } /* Parse the optional flags. */ while (*flags == ' ' || *flags == '\t') flags++; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { gc_error (0, 0, "syntax error in rule at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "syntax error in rule", GPG_ERR_SYNTAX, fname, lineno); result = -1; continue; } *p++ = 0; value = p; } else /* No flags given. */ { value = flags; flags = NULL; } /* Parse the optional value. */ while (*value == ' ' || *value == '\t') value++; for (p=value; *p && !strchr ("\r\n", *p); p++) ; if (p == value) value = empty; /* No value given; let it point to an empty string. */ else { /* Strip trailing white space. */ *p = 0; for (p--; p > value && (*p == ' ' || *p == '\t'); p--) *p = 0; } /* Check flag combinations. */ if (!flags) ; else if (!strcmp (flags, "default")) { if (*value) { gc_error (0, 0, "flag \"default\" may not be combined " "with a value at '%s', line %d", fname, lineno); result = -1; } } else if (!strcmp (flags, "change")) ; else if (!strcmp (flags, "no-change")) ; else { gc_error (0, 0, "unknown flag at '%s', line %d", fname, lineno); result = -1; } /* In list mode we print out all records. */ if (listfp && !result) { /* If this is a new ruleset, print a key record. */ if (!is_continuation) { char *group = strchr (key, ':'); if (group) { *group++ = 0; if ((p = strchr (group, ':'))) *p = 0; /* We better strip any extra stuff. */ } es_fprintf (listfp, "k:%s:", gc_percent_escape (key)); es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):""); } /* All other lines are rule records. */ es_fprintf (listfp, "r:::%s:%s:%s:", gc_component[component_id].name, option_info->name? option_info->name : "", flags? flags : ""); if (value != empty) es_fprintf (listfp, "\"%s", gc_percent_escape (value)); es_putc ('\n', listfp); } /* Check whether the key matches but do this only if we are not running in syntax check mode. */ if ( update && !result && !listfp && (got_match || (key && key_matches_user_or_group (key))) ) { int newflags = 0; got_match = 1; /* Apply the flags from gpgconf.conf. */ if (!flags) ; else if (!strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; else if (!strcmp (flags, "no-change")) option_info->no_change = 1; else if (!strcmp (flags, "change")) option_info->no_change = 0; if (defaults) { /* Here we explicitly allow updating the value again. */ if (newflags) { option_info->new_flags = 0; } if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (component_id, option_info, runtime, newflags, value, 0); } } } if (length < 0 || gpgrt_ferror (config)) { gc_error (0, errno, "error reading from '%s'", fname); result = -1; } if (gpgrt_fclose (config)) gc_error (0, errno, "error closing '%s'", fname); xfree (line); /* If it all worked, process the options. */ if (!result && update && defaults && !listfp) { /* We need to switch off the runtime update, so that we can do it later all at once. */ int save_opt_runtime = opt.runtime; opt.runtime = 0; for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) { gc_component_change_options (component_id, NULL, NULL, 0); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) if (runtime[component_id] && gc_component[component_id].runtime_change) (*gc_component[component_id].runtime_change) (0); } } xfree (fname); return result; } /* * Apply the profile FNAME to all known configure files. */ gpg_error_t gc_apply_profile (const char *fname) { gpg_error_t err; char *fname_buffer = NULL; char *line = NULL; size_t line_len = 0; ssize_t length; estream_t fp; int lineno = 0; int runtime[GC_COMPONENT_NR] = { 0 }; int component_id = -1; int skip_section = 0; int error_count = 0; int newflags; if (!fname) fname = "-"; if (!(!strcmp (fname, "-") || strchr (fname, '/') #ifdef HAVE_W32_SYSTEM || strchr (fname, '\\') #endif || strchr (fname, '.'))) { /* FNAME looks like a standard profile name. Check whether one * is installed and use that instead of the given file name. */ fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S, fname, ".prf", NULL); if (!gnupg_access (fname_buffer, F_OK)) fname = fname_buffer; } fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); return err; } if (opt.verbose) log_info ("applying profile '%s'\n", fname); err = 0; while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0) { char *name, *flags, *value; gc_option_t *option_info = NULL; char *p; lineno++; name = line; while (*name == ' ' || *name == '\t') name++; if (!*name || *name == '#' || *name == '\r' || *name == '\n') continue; trim_trailing_spaces (name); /* Check whether this is a new section. */ if (*name == '[') { name++; skip_section = 0; /* New section: Get the name of the component. */ p = strchr (name, ']'); if (!p) { error_count++; log_info ("%s:%d:%d: error: syntax error in section tag\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } *p++ = 0; if (*p) log_info ("%s:%d:%d: warning: garbage after section tag\n", fname, lineno, (int)(p - line)); trim_spaces (name); component_id = gc_component_find (name); if (component_id < 0) { log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n", fname, lineno, (int)(name - line), name ); skip_section = 1; } continue; } if (skip_section) continue; if (component_id < 0) { error_count++; log_info ("%s:%d:%d: error: not in a valid section\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } /* Parse the option name. */ for (p = name; *p && !spacep (p); p++) ; *p++ = 0; value = p; option_info = find_option (component_id, name); if (!option_info) { error_count++; log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n", fname, lineno, (int)(name - line), name, gc_component[component_id].name); continue; } /* Parse the optional flags. */ trim_spaces (value); flags = value; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { log_info ("%s:%d:%d: warning: invalid flag specification\n", fname, lineno, (int)(p - line)); continue; } *p++ = 0; value = p; trim_spaces (value); } else /* No flags given. */ flags = NULL; /* Set required defaults. */ if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE && !*value) value = "1"; /* Check and save this option. */ newflags = 0; if (flags && !strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; if (newflags) option_info->new_flags = 0; if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (component_id, option_info, runtime, newflags, value, 1); } if (length < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); error_count++; log_error (_("%s:%u: read error: %s\n"), fname, lineno, gpg_strerror (err)); } if (es_fclose (fp)) log_error (_("error closing '%s'\n"), fname); if (error_count) log_error (_("error parsing '%s'\n"), fname); xfree (line); /* If it all worked, process the options. */ if (!err) { /* We need to switch off the runtime update, so that we can do it later all at once. */ int save_opt_runtime = opt.runtime; opt.runtime = 0; for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) { gc_component_change_options (component_id, NULL, NULL, 1); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) if (runtime[component_id] && gc_component[component_id].runtime_change) (*gc_component[component_id].runtime_change) (0); } } xfree (fname_buffer); return err; }