diff --git a/agent/call-daemon.c b/agent/call-daemon.c index f907f40eb..0c3605274 100644 --- a/agent/call-daemon.c +++ b/agent/call-daemon.c @@ -1,684 +1,680 @@ /* 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_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. */ 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); } 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/call-pinentry.c b/agent/call-pinentry.c index bb55a3714..711758efc 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -1,2177 +1,2173 @@ /* call-pinentry.c - Spawn the pinentry to query stuff from the user * Copyright (C) 2001, 2002, 2004, 2007, 2008, * 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM # include # include # include # include #endif #include #include "agent.h" #include #include "../common/sysutils.h" #include "../common/i18n.h" #include "../common/zb32.h" #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif /* Because access to the pinentry must be serialized (it is and shall be a global mutually exclusive dialog) we better timeout pending requests after some time. 1 minute seem to be a reasonable time. */ #define LOCK_TIMEOUT (1*60) /* Define the number of bits to use for a generated pin. The * passphrase will be rendered as zbase32 which results for 150 bits * in a string of 30 characters. That fits nicely into the 5 * character blocking which pinentry can do. 128 bits would actually * be sufficient but can't be formatted nicely. */ #define DEFAULT_GENPIN_BITS 150 /* The assuan context of the current pinentry. */ static assuan_context_t entry_ctx; /* A list of features of the current pinentry. */ static struct { /* The Pinentry support RS+US tabbing. This means that a RS (0x1e) * starts a new tabbing block in which a US (0x1f) followed by a * colon marks a colon. A pinentry can use this to pretty print * name value pairs. */ unsigned int tabbing:1; } entry_features; /* A mutex used to serialize access to the pinentry. */ static npth_mutex_t entry_lock; /* The thread ID of the popup working thread. */ static npth_t popup_tid; /* A flag used in communication between the popup working thread and its stop function. */ static int popup_finished; /* Data to be passed to our callbacks, */ struct entry_parm_s { int lines; size_t size; unsigned char *buffer; int status; unsigned int constraints_flags; }; /* 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 Pth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_call_pinentry (void) { static int initialized; int err; if (!initialized) { err = npth_mutex_init (&entry_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_query_dump_state (void) { log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n", entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid); } /* Called to make sure that a popup window owned by the current connection gets closed. */ void agent_reset_query (ctrl_t ctrl) { if (entry_ctx && popup_tid && ctrl->pinentry_active) { agent_popup_message_stop (ctrl); } } /* Unlock the pinentry so that another thread can start one and disconnect that pinentry - we do this after the unlock so that a stalled pinentry does not block other threads. Fixme: We should have a timeout in Assuan for the disconnect operation. */ static gpg_error_t unlock_pinentry (ctrl_t ctrl, gpg_error_t rc) { assuan_context_t ctx = entry_ctx; int err; if (rc) { if (DBG_IPC) log_debug ("error calling pinentry: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc)); /* Change the source of the error to pinentry so that the final consumer of the error code knows that the problem is with pinentry. For backward compatibility we do not do that for some common error codes. */ switch (gpg_err_code (rc)) { case GPG_ERR_NO_PIN_ENTRY: case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: case GPG_ERR_ASS_UNKNOWN_INQUIRE: case GPG_ERR_ASS_TOO_MUCH_DATA: case GPG_ERR_NO_PASSPHRASE: case GPG_ERR_BAD_PASSPHRASE: case GPG_ERR_BAD_PIN: break; case GPG_ERR_CORRUPTED_PROTECTION: /* This comes from gpg-agent. */ break; default: rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc)); break; } } if (--ctrl->pinentry_active == 0) { entry_ctx = NULL; err = npth_mutex_unlock (&entry_lock); if (err) { log_error ("failed to release the entry lock: %s\n", strerror (err)); if (!rc) rc = gpg_error_from_errno (err); } assuan_release (ctx); } return rc; } /* Helper for at_fork_cb which can also be called by the parent to * show which envvars will be set. */ static void atfork_core (ctrl_t ctrl, int debug_mode) { int iterator = 0; const char *name, *assname, *value; while ((name = session_env_list_stdenvnames (&iterator, &assname))) { /* For all new envvars (!ASSNAME) and the two medium old ones * which do have an assuan name but are conveyed using * environment variables, update the environment of the forked * process. We also pass DISPLAY despite that --display is also * used when exec-ing the pinentry. The reason is that for * example the qt5ct tool does not have any arguments and thus * relies on the DISPLAY envvar. The use case here is a global * envvar like "QT_QPA_PLATFORMTHEME=qt5ct" which for example is * useful when using the Qt pinentry under GNOME or XFCE. */ if (!assname || (!opt.keep_display && !strcmp (name, "DISPLAY")) || !strcmp (name, "XAUTHORITY") || !strcmp (name, "PINENTRY_USER_DATA")) { value = session_env_getenv (ctrl->session_env, name); if (value) { if (debug_mode) log_debug ("pinentry: atfork used setenv(%s,%s)\n",name,value); else gnupg_setenv (name, value, 1); } } } } /* To make sure we leave no secrets in our image after forking of the pinentry, we use this callback. */ static void atfork_cb (void *opaque, int where) { ctrl_t ctrl = opaque; if (!where) { gcry_control (GCRYCTL_TERM_SECMEM); atfork_core (ctrl, 0); } } /* Status line callback for the FEATURES status. */ static gpg_error_t getinfo_features_cb (void *opaque, const char *line) { const char *args; char **tokens; int i; (void)opaque; if ((args = has_leading_keyword (line, "FEATURES"))) { tokens = strtokenize (args, " "); if (!tokens) return gpg_error_from_syserror (); for (i=0; tokens[i]; i++) if (!strcmp (tokens[i], "tabbing")) entry_features.tabbing = 1; xfree (tokens); } return 0; } static gpg_error_t getinfo_pid_cb (void *opaque, const void *buffer, size_t length) { unsigned long *pid = opaque; char pidbuf[50]; /* There is only the pid in the server's response. */ if (length >= sizeof pidbuf) length = sizeof pidbuf -1; if (length) { strncpy (pidbuf, buffer, length); pidbuf[length] = 0; *pid = strtoul (pidbuf, NULL, 10); } return 0; } /* Fork off the pin entry if this has not already been done. Note, that this function must always be used to acquire the lock for the pinentry - we will serialize _all_ pinentry calls. */ static gpg_error_t start_pinentry (ctrl_t ctrl) { int rc = 0; const char *full_pgmname; const char *pgmname; assuan_context_t ctx; const char *argv[5]; assuan_fd_t no_close_list[3]; int i; const char *tmpstr; unsigned long pinentry_pid; const char *value; struct timespec abstime; char *flavor_version; int err; if (ctrl->pinentry_active) { /* It's trying to use pinentry recursively. In this situation, the thread holds ENTRY_LOCK already. */ ctrl->pinentry_active++; return 0; } npth_clock_gettime (&abstime); abstime.tv_sec += LOCK_TIMEOUT; err = npth_mutex_timedlock (&entry_lock, &abstime); if (err) { if (err == ETIMEDOUT) rc = gpg_error (GPG_ERR_TIMEOUT); else rc = gpg_error_from_errno (rc); log_error (_("failed to acquire the pinentry lock: %s\n"), gpg_strerror (rc)); return rc; } if (entry_ctx) return 0; if (opt.verbose) log_info ("starting a new PIN Entry\n"); #ifdef HAVE_W32_SYSTEM fflush (stdout); fflush (stderr); #endif if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); #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 Wine implementation does not flush stdin,stdout and stderr - see above. Let's try to ignore the error. */ #ifndef HAVE_W32_SYSTEM return unlock_pinentry (ctrl, tmperr); #endif } full_pgmname = opt.pinentry_program; if (!full_pgmname || !*full_pgmname) full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY); if ( !(pgmname = strrchr (full_pgmname, '/'))) pgmname = full_pgmname; else pgmname++; /* OS X needs the entire file name in argv[0], so that it can locate the resource bundle. For other systems we stick to the usual convention of supplying only the name of the program. */ #ifdef __APPLE__ argv[0] = full_pgmname; #else /*!__APPLE__*/ argv[0] = pgmname; #endif /*__APPLE__*/ if (!opt.keep_display && (value = session_env_getenv (ctrl->session_env, "DISPLAY"))) { argv[1] = "--display"; argv[2] = value; argv[3] = NULL; } else argv[1] = 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_fd_from_posix_fd (fileno (stderr)); no_close_list[i] = ASSUAN_INVALID_FD; rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); return rc; } ctrl->pinentry_active = 1; entry_ctx = ctx; /* We don't want to log the pinentry communication to make the logs easier to read. We might want to add a new debug option to enable pinentry logging. */ #ifdef ASSUAN_NO_LOGGING assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry); #endif /* Connect to the pinentry and perform initial handshaking. Note that atfork is used to change the environment for pinentry. We start the server in detached mode to suppress the console window under Windows. */ rc = assuan_pipe_connect (entry_ctx, full_pgmname, argv, no_close_list, atfork_cb, ctrl, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { log_error ("can't connect to the PIN entry module '%s': %s\n", full_pgmname, gpg_strerror (rc)); return unlock_pinentry (ctrl, gpg_error (GPG_ERR_NO_PIN_ENTRY)); } if (DBG_IPC) log_debug ("connection to PIN entry established\n"); if (opt.debug_pinentry) atfork_core (ctrl, 1); /* Just show the envvars set after the fork. */ value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA"); if (value != NULL) { char *optstr; if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (ctrl, rc); } rc = assuan_transact (entry_ctx, opt.no_grab? "OPTION no-grab":"OPTION grab", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) { if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED || gpg_err_code (rc) == GPG_ERR_UNKNOWN_OPTION) { if (opt.verbose) log_info ("Option no-grab/grab is ignored by pinentry.\n"); /* Keep going even if the feature is not supported. */ } else return unlock_pinentry (ctrl, rc); } value = session_env_getenv (ctrl->session_env, "GPG_TTY"); if (value) { char *optstr; if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (ctrl, rc); } value = session_env_getenv (ctrl->session_env, "TERM"); if (value && *value) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (ctrl, rc); } if (ctrl->lc_ctype) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (ctrl, rc); } if (ctrl->lc_messages) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (ctrl, rc); } if (opt.allow_external_cache) { /* Indicate to the pinentry that it may read from an external cache. It is essential that the pinentry respect this. If the cached password is not up to date and retry == 1, then, using a version of GPG Agent that doesn't support this, won't issue another pin request and the user won't get a chance to correct the password. */ rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (ctrl, rc); } if (opt.allow_emacs_pinentry) { /* Indicate to the pinentry that it may read passphrase through Emacs minibuffer, if possible. */ rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (ctrl, rc); } { /* Provide a few default strings for use by the pinentries. This * may help a pinentry to avoid implementing localization code. * Note that gpg-agent has been set to utf-8 so that the strings * are in the expected encoding. */ static const struct { const char *key, *value; int what; } tbl[] = { /* TRANSLATORS: These are labels for buttons etc as used in * Pinentries. In your translation copy the text before the * second vertical bar verbatim; translate only the following * text. An underscore indicates that the next letter should be * used as an accelerator. Double the underscore to have * pinentry display a literal underscore. */ { "ok", N_("|pinentry-label|_OK") }, { "cancel", N_("|pinentry-label|_Cancel") }, { "yes", N_("|pinentry-label|_Yes") }, { "no", N_("|pinentry-label|_No") }, { "prompt", N_("|pinentry-label|PIN:") }, { "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 }, { "cf-visi",N_("Do you really want to make your " "passphrase visible on the screen?") }, { "tt-visi",N_("|pinentry-tt|Make passphrase visible") }, { "tt-hide",N_("|pinentry-tt|Hide passphrase") }, { "capshint", N_("Caps Lock is on") }, { NULL, NULL} }; char *optstr; int idx; const char *s, *s2; for (idx=0; tbl[idx].key; idx++) { if (!opt.allow_external_cache && tbl[idx].what == 1) continue; /* No need for it. */ s = L_(tbl[idx].value); if (*s == '|' && (s2=strchr (s+1,'|'))) s = s2+1; if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 ) return unlock_pinentry (ctrl, out_of_core ()); assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } } /* Tell the pinentry that we would prefer that the given character is used as the invisible character by the entry widget. */ if (opt.pinentry_invisible_char) { char *optstr; if ((optstr = xtryasprintf ("OPTION invisible-char=%s", opt.pinentry_invisible_char))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing and older pinentries do not support this feature. */ xfree (optstr); } } if (opt.pinentry_timeout) { char *optstr; if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing. */ xfree (optstr); } } /* Tell the pinentry the name of a file it shall touch after having messed with the tty. This is optional and only supported by newer pinentries and thus we do no error checking. */ tmpstr = opt.pinentry_touch_file; if (tmpstr && !strcmp (tmpstr, "/dev/null")) tmpstr = NULL; else if (!tmpstr) tmpstr = get_agent_socket_name (); if (tmpstr) { char *optstr; if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 ) ; else { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } } /* Tell Pinentry about our client. */ if (ctrl->client_pid) { char *optstr; const char *nodename = ""; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; if (!uname (&utsbuf)) nodename = utsbuf.nodename; #endif /*!HAVE_W32_SYSTEM*/ if ((optstr = xtryasprintf ("OPTION owner=%lu/%d %s", ctrl->client_pid, ctrl->client_uid, nodename))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing and older pinentries do not support this feature. */ xfree (optstr); } } /* Ask the pinentry for its version and flavor and store that as a * string in MB. This information is useful for helping users to * figure out Pinentry problems. Note that "flavor" may also return * a status line with the features; we use a dedicated handler for * that. */ { membuf_t mb; init_membuf (&mb, 256); if (assuan_transact (entry_ctx, "GETINFO flavor", put_membuf_cb, &mb, NULL, NULL, getinfo_features_cb, NULL)) put_membuf_str (&mb, "unknown"); put_membuf_str (&mb, " "); if (assuan_transact (entry_ctx, "GETINFO version", put_membuf_cb, &mb, NULL, NULL, NULL, NULL)) put_membuf_str (&mb, "unknown"); put_membuf_str (&mb, " "); if (assuan_transact (entry_ctx, "GETINFO ttyinfo", put_membuf_cb, &mb, NULL, NULL, NULL, NULL)) put_membuf_str (&mb, "? ? ?"); put_membuf (&mb, "", 1); flavor_version = get_membuf (&mb, NULL); } /* Now ask the Pinentry for its PID. If the Pinentry is new enough it will send the pid back and we will use an inquire to notify our client. The client may answer the inquiry either with END or with CAN to cancel the pinentry. */ rc = assuan_transact (entry_ctx, "GETINFO pid", getinfo_pid_cb, &pinentry_pid, NULL, NULL, NULL, NULL); if (rc) { log_info ("You may want to update to a newer pinentry\n"); rc = 0; } else if (!rc && pinentry_pid == (unsigned long)(-1L)) log_error ("pinentry did not return a PID\n"); else { rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version); if (gpg_err_code (rc) == GPG_ERR_CANCELED || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) return unlock_pinentry (ctrl, gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (rc))); rc = 0; } xfree (flavor_version); return rc; } /* Returns True if the pinentry is currently active. If WAITSECONDS is greater than zero the function will wait for this many seconds before returning. */ int pinentry_active_p (ctrl_t ctrl, int waitseconds) { int err; (void)ctrl; if (waitseconds > 0) { struct timespec abstime; int rc; npth_clock_gettime (&abstime); abstime.tv_sec += waitseconds; err = npth_mutex_timedlock (&entry_lock, &abstime); if (err) { if (err == ETIMEDOUT) rc = gpg_error (GPG_ERR_TIMEOUT); else rc = gpg_error (GPG_ERR_INTERNAL); return rc; } } else { err = npth_mutex_trylock (&entry_lock); if (err) return gpg_error (GPG_ERR_LOCKED); } err = npth_mutex_unlock (&entry_lock); if (err) log_error ("failed to release the entry lock at %d: %s\n", __LINE__, strerror (errno)); return 0; } static gpg_error_t getpin_cb (void *opaque, const void *buffer, size_t length) { struct entry_parm_s *parm = opaque; if (!buffer) return 0; /* we expect the pin to fit on one line */ if (parm->lines || length >= parm->size) return gpg_error (GPG_ERR_ASS_TOO_MUCH_DATA); /* fixme: we should make sure that the assuan buffer is allocated in secure memory or read the response byte by byte */ memcpy (parm->buffer, buffer, length); parm->buffer[length] = 0; parm->lines++; return 0; } static int all_digitsp( const char *s) { for (; *s && *s >= '0' && *s <= '9'; s++) ; return !*s; } /* Return a new malloced string by unescaping the string S. Escaping is percent escaping and '+'/space mapping. A binary Nul will silently be replaced by a 0xFF. Function returns NULL to indicate an out of memory status. Parsing stops at the end of the string or a white space character. */ static char * unescape_passphrase_string (const unsigned char *s) { char *buffer, *d; buffer = d = xtrymalloc_secure (strlen ((const char*)s)+1); if (!buffer) return NULL; while (*s && !spacep (s)) { if (*s == '%' && s[1] && s[2]) { s++; *d = xtoi_2 (s); if (!*d) *d = '\xff'; d++; s += 2; } else if (*s == '+') { *d++ = ' '; s++; } else *d++ = *s++; } *d = 0; return buffer; } /* Estimate the quality of the passphrase PW and return a value in the range 0..100. */ static int estimate_passphrase_quality (const char *pw) { int goodlength = opt.min_passphrase_len + opt.min_passphrase_len/3; int length; const char *s; if (goodlength < 1) return 0; for (length = 0, s = pw; *s; s++) if (!spacep (s)) length ++; if (length > goodlength) return 100; return ((length*10) / goodlength)*10; } /* Generate a random passphrase in zBase32 encoding (RFC-6189) to be * used by Pinentry to suggest a passphrase. */ static char * generate_pin (void) { unsigned int nbits = opt.min_passphrase_len * 8; size_t nbytes; void *rand; char *generated; if (nbits < 128) nbits = DEFAULT_GENPIN_BITS; nbytes = (nbits + 7) / 8; rand = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM); if (!rand) { log_error ("failed to generate random pin\n"); return NULL; } generated = zb32_encode (rand, nbits); gcry_free (rand); return generated; } /* Handle inquiries. */ struct inq_cb_parm_s { assuan_context_t ctx; unsigned int flags; /* CHECK_CONSTRAINTS_... */ int genpinhash_valid; char genpinhash[32]; /* Hash of the last generated pin. */ }; /* Return true if PIN is indentical to the last generated pin. */ static int is_generated_pin (struct inq_cb_parm_s *parm, const char *pin) { char hashbuf[32]; if (!parm->genpinhash_valid) return 0; if (!*pin) return 0; /* Note that we compare the hash so that we do not need to save the * generated PIN longer than needed. */ gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, pin, strlen (pin)); if (!memcmp (hashbuf, parm->genpinhash, 32)) return 1; /* yes, it is the same. */ return 0; } static gpg_error_t inq_cb (void *opaque, const char *line) { struct inq_cb_parm_s *parm = opaque; gpg_error_t err; const char *s; char *pin; if ((s = has_leading_keyword (line, "QUALITY"))) { char numbuf[20]; int percent; pin = unescape_passphrase_string (s); if (!pin) err = gpg_error_from_syserror (); else { percent = estimate_passphrase_quality (pin); if (check_passphrase_constraints (NULL, pin, parm->flags, NULL)) percent = -percent; snprintf (numbuf, sizeof numbuf, "%d", percent); err = assuan_send_data (parm->ctx, numbuf, strlen (numbuf)); xfree (pin); } } else if ((s = has_leading_keyword (line, "CHECKPIN"))) { char *errtext = NULL; size_t errtextlen; if (!opt.enforce_passphrase_constraints) { log_error ("unexpected inquiry 'CHECKPIN' without enforced " "passphrase constraints\n"); err = gpg_error (GPG_ERR_ASS_UNEXPECTED_CMD); goto leave; } pin = unescape_passphrase_string (s); if (!pin) err = gpg_error_from_syserror (); else { if (!is_generated_pin (parm, pin) && check_passphrase_constraints (NULL, pin,parm->flags, &errtext)) { if (errtext) { /* Unescape the percent-escaped errtext because assuan_send_data escapes it again. */ errtextlen = percent_unescape_inplace (errtext, 0); err = assuan_send_data (parm->ctx, errtext, errtextlen); } else { log_error ("passphrase check failed without error text\n"); err = gpg_error (GPG_ERR_GENERAL); } } else { err = assuan_send_data (parm->ctx, NULL, 0); } xfree (errtext); xfree (pin); } } else if ((s = has_leading_keyword (line, "GENPIN"))) { int wasconf; parm->genpinhash_valid = 0; pin = generate_pin (); if (!pin) { log_error ("failed to generate a passphrase\n"); err = gpg_error (GPG_ERR_GENERAL); goto leave; } wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (parm->ctx); err = assuan_send_data (parm->ctx, pin, strlen (pin)); if (!wasconf) assuan_end_confidential (parm->ctx); gcry_md_hash_buffer (GCRY_MD_SHA256, parm->genpinhash, pin, strlen (pin)); parm->genpinhash_valid = 1; xfree (pin); } else { log_error ("unsupported inquiry '%s' from pinentry\n", line); err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } leave: return err; } /* Helper to setup pinentry for genpin action. */ static gpg_error_t setup_genpin (ctrl_t ctrl) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *tmpstr, *tmpstr2; const char *tooltip; (void)ctrl; /* TRANSLATORS: This string is displayed by Pinentry as the label for generating a passphrase. */ tmpstr = try_percent_escape (L_("Suggest"), "\t\r\n\f\v"); snprintf (line, DIM(line), "SETGENPIN %s", tmpstr? tmpstr:""); xfree (tmpstr); err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == 103 /*(Old assuan error code)*/ || gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old Pinentry versions. */ else if (err) return err; tmpstr2 = gnupg_get_help_string ("pinentry.genpin.tooltip", 0); if (tmpstr2) tooltip = tmpstr2; else { /* TRANSLATORS: This string is a tooltip, shown by pinentry when hovering over the generate button. Please use an appropriate string to describe what this is about. The length of the tooltip is limited to about 900 characters. If you do not translate this entry, a default English text (see source) will be used. The strcmp thingy is there to detect a non-translated string. */ tooltip = L_("pinentry.genpin.tooltip"); if (!strcmp ("pinentry.genpin.tooltip", tooltip)) tooltip = "Suggest a random passphrase."; } tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v"); xfree (tmpstr2); snprintf (line, DIM(line), "SETGENPIN_TT %s", tmpstr? tmpstr:""); xfree (tmpstr); err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == 103 /*(Old assuan error code)*/ || gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old pinentry versions. */ else if (err) return err; return 0; } /* Helper to setup pinentry for formatted passphrase. */ static gpg_error_t setup_formatted_passphrase (ctrl_t ctrl) { static const struct { const char *key, *help_id, *value; } tbl[] = { { "hint", "pinentry.formatted_passphrase.hint", /* TRANSLATORS: This is a text shown by pinentry if the option for formatted passphrase is enabled. The length is limited to about 900 characters. */ N_("Note: The blanks are not part of the passphrase.") }, { NULL, NULL } }; gpg_error_t rc; char line[ASSUAN_LINELENGTH]; int idx; char *tmpstr; const char *s; char *escapedstr; (void)ctrl; if (opt.pinentry_formatted_passphrase) { snprintf (line, DIM(line), "OPTION formatted-passphrase"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return rc; for (idx=0; tbl[idx].key; idx++) { tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0); if (tmpstr) s = tmpstr; else s = L_(tbl[idx].value); escapedstr = try_percent_escape (s, "\t\r\n\f\v"); xfree (tmpstr); if (escapedstr && *escapedstr) { snprintf (line, DIM(line), "OPTION formatted-passphrase-%s=%s", tbl[idx].key, escapedstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } else rc = 0; xfree (escapedstr); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return rc; } } return 0; } /* Helper to setup pinentry for enforced passphrase constraints. */ static gpg_error_t setup_enforced_constraints (ctrl_t ctrl) { static const struct { const char *key, *help_id, *value; } tbl[] = { { "hint-short", "pinentry.constraints.hint.short", NULL }, { "hint-long", "pinentry.constraints.hint.long", NULL }, /* TRANSLATORS: This is a text shown by pinentry as title of a dialog telling the user that the entered new passphrase does not satisfy the passphrase constraints. Please keep it short. */ { "error-title", NULL, N_("Passphrase Not Allowed") }, { NULL, NULL } }; gpg_error_t rc; char line[ASSUAN_LINELENGTH]; int idx; char *tmpstr; const char *s; char *escapedstr; (void)ctrl; if (opt.enforce_passphrase_constraints) { snprintf (line, DIM(line), "OPTION constraints-enforce"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return rc; for (idx=0; tbl[idx].key; idx++) { tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0); if (tmpstr) s = tmpstr; else if (tbl[idx].value) s = L_(tbl[idx].value); else { log_error ("no help string found for %s\n", tbl[idx].help_id); continue; } escapedstr = try_percent_escape (s, "\t\r\n\f\v"); xfree (tmpstr); if (escapedstr && *escapedstr) { snprintf (line, DIM(line), "OPTION constraints-%s=%s", tbl[idx].key, escapedstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } else rc = 0; /* Ignore an empty string (would give an IPC error). */ xfree (escapedstr); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return rc; } } return 0; } /* Helper for agent_askpin and agent_get_passphrase. */ static gpg_error_t setup_qualitybar (ctrl_t ctrl) { int rc; char line[ASSUAN_LINELENGTH]; char *tmpstr, *tmpstr2; const char *tooltip; (void)ctrl; /* TRANSLATORS: This string is displayed by Pinentry as the label for the quality bar. */ tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v"); snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:""); xfree (tmpstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc == 103 /*(Old assuan error code)*/ || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old Pinentry versions. */ else if (rc) return rc; tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0); if (tmpstr2) tooltip = tmpstr2; else { /* TRANSLATORS: This string is a tooltip, shown by pinentry when hovering over the quality bar. Please use an appropriate string to describe what this is about. The length of the tooltip is limited to about 900 characters. If you do not translate this entry, a default english text (see source) will be used. */ tooltip = L_("pinentry.qualitybar.tooltip"); if (!strcmp ("pinentry.qualitybar.tooltip", tooltip)) tooltip = ("The quality of the text entered above.\n" "Please ask your administrator for " "details about the criteria."); } tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v"); xfree (tmpstr2); snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:""); xfree (tmpstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc == 103 /*(Old assuan error code)*/ || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old pinentry versions. */ else if (rc) return rc; return 0; } /* Check the button_info line for a close action. Also check for the PIN_REPEATED flag. */ static gpg_error_t pinentry_status_cb (void *opaque, const char *line) { unsigned int *flag = opaque; const char *args; if ((args = has_leading_keyword (line, "BUTTON_INFO"))) { if (!strcmp (args, "close")) *flag |= PINENTRY_STATUS_CLOSE_BUTTON; } else if (has_leading_keyword (line, "PIN_REPEATED")) { *flag |= PINENTRY_STATUS_PIN_REPEATED; } else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE")) { *flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE; } return 0; } /* Build a SETDESC command line. This is a dedicated function so that * it can remove control characters which are not supported by the * current Pinentry. */ static void build_cmd_setdesc (char *line, size_t linelen, const char *desc) { char *src, *dst; snprintf (line, linelen, "SETDESC %s", desc); if (!entry_features.tabbing) { /* Remove RS and US. */ for (src=dst=line; *src; src++) if (!strchr ("\x1e\x1f", *src)) *dst++ = *src; *dst = 0; } } /* Watch the socket's EOF condition, while checking finish of foreground thread. When EOF condition is detected, terminate the pinentry process behind the assuan pipe. */ static void * watch_sock (void *arg) { pid_t pid = assuan_get_pid (entry_ctx); while (1) { int err; fd_set fdset; struct timeval timeout = { 0, 500000 }; gnupg_fd_t sock = *(gnupg_fd_t *)arg; if (sock == GNUPG_INVALID_FD) return NULL; FD_ZERO (&fdset); FD_SET (FD2INT (sock), &fdset); err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout); if (err < 0) { if (errno == EINTR) continue; else return NULL; } /* Possibly, it's EOF. */ if (err > 0) break; } if (pid == (pid_t)(-1)) ; /* No pid available can't send a kill. */ #ifdef HAVE_W32_SYSTEM /* Older versions of assuan set PID to 0 on Windows to indicate an invalid value. */ else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) TerminateProcess ((HANDLE)pid, 1); #else else if (pid > 0) kill (pid, SIGINT); #endif return NULL; } static gpg_error_t watch_sock_start (gnupg_fd_t *sock_p, npth_t *thread_p) { npth_attr_t tattr; int err; err = npth_attr_init (&tattr); if (err) { log_error ("do_getpin: error npth_attr_init: %s\n", strerror (err)); return gpg_error_from_errno (err); } npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); err = npth_create (thread_p, &tattr, watch_sock, sock_p); npth_attr_destroy (&tattr); if (err) { log_error ("do_getpin: error spawning thread: %s\n", strerror (err)); return gpg_error_from_errno (err); } return 0; } static void watch_sock_end (gnupg_fd_t *sock_p, npth_t *thread_p) { int err; *sock_p = GNUPG_INVALID_FD; err = npth_join (*thread_p, NULL); if (err) log_error ("watch_sock_end: error joining thread: %s\n", strerror (err)); } /* Ask pinentry to get a pin by "GETPIN" command, spawning a thread detecting the socket's EOF. */ static gpg_error_t do_getpin (ctrl_t ctrl, struct entry_parm_s *parm) { gpg_error_t rc; gnupg_fd_t sock_watched = ctrl->thread_startup.fd; npth_t thread; int wasconf; struct inq_cb_parm_s inq_cb_parm; rc = watch_sock_start (&sock_watched, &thread); if (rc) return rc; inq_cb_parm.ctx = entry_ctx; inq_cb_parm.flags = parm->constraints_flags; inq_cb_parm.genpinhash_valid = 0; wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm, inq_cb, &inq_cb_parm, pinentry_status_cb, &parm->status); if (!wasconf) assuan_end_confidential (entry_ctx); if (!rc && parm->buffer && is_generated_pin (&inq_cb_parm, parm->buffer)) parm->status |= PINENTRY_STATUS_PASSWORD_GENERATED; else parm->status &= ~PINENTRY_STATUS_PASSWORD_GENERATED; /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); /* Change error code in case the window close button was clicked to cancel the operation. */ if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); watch_sock_end (&sock_watched, &thread); return rc; } /* Call the Entry and ask for the PIN. We do check for a valid PIN number here and repeat it as long as we have invalid formed numbers. KEYINFO and CACHE_MODE are used to tell pinentry something about the key. */ gpg_error_t agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *initial_errtext, struct pin_entry_info_s *pininfo, const char *keyinfo, cache_mode_t cache_mode) { gpg_error_t rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; const char *errtext = NULL; int is_pin = 0; int is_generated; if (opt.batch) return 0; /* fixme: we should return BAD PIN */ if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { unsigned char *passphrase; size_t size; *pininfo->pin = 0; /* Reset the PIN. */ rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size, pininfo->max_length - 1); if (rc) return rc; memcpy(&pininfo->pin, passphrase, size); xfree(passphrase); pininfo->pin[size] = 0; if (pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); } return rc; } return gpg_error(GPG_ERR_NO_PIN_ENTRY); } if (!pininfo || pininfo->max_length < 1) return gpg_error (GPG_ERR_INV_VALUE); if (!desc_text && pininfo->min_digits) desc_text = L_("Please enter your PIN, so that the secret key " "can be unlocked for this session"); else if (!desc_text) desc_text = L_("Please enter your passphrase, so that the secret key " "can be unlocked for this session"); if (prompt_text) is_pin = !!strstr (prompt_text, "PIN"); else is_pin = desc_text && strstr (desc_text, "PIN"); rc = start_pinentry (ctrl); if (rc) return rc; /* If we have a KEYINFO string and are normal, user, or ssh cache mode, we tell that the Pinentry so it may use it for own caching purposes. Most pinentries won't have this implemented and thus we do not error out in this case. */ if (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH)) snprintf (line, DIM(line), "SETKEYINFO %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); else snprintf (line, DIM(line), "SETKEYINFO --clear"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) return unlock_pinentry (ctrl, rc); build_cmd_setdesc (line, DIM(line), desc_text); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); snprintf (line, DIM(line), "SETPROMPT %s", prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); /* If a passphrase quality indicator has been requested and a minimum passphrase length has not been disabled, send the command to the pinentry. */ if (pininfo->with_qualitybar && opt.min_passphrase_len ) { rc = setup_qualitybar (ctrl); if (rc) return unlock_pinentry (ctrl, rc); } if (initial_errtext) { snprintf (line, DIM(line), "SETERROR %s", initial_errtext); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEATERROR %s", L_("does not match - try again")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) pininfo->with_repeat = 0; /* Pinentry does not support it. */ } pininfo->repeat_okay = 0; pininfo->status = 0; for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { memset (&parm, 0, sizeof parm); parm.size = pininfo->max_length; *pininfo->pin = 0; /* Reset the PIN. */ parm.buffer = (unsigned char*)pininfo->pin; parm.constraints_flags = pininfo->constraints_flags; if (errtext) { /* TRANSLATORS: The string is appended to an error message in the pinentry. The %s is the actual error message, the two %d give the current and maximum number of tries. */ snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"), errtext, pininfo->failed_tries+1, pininfo->max_tries); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); errtext = NULL; } if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } rc = do_getpin (ctrl, &parm); pininfo->status = parm.status; is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED); if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? L_("PIN too long") : L_("Passphrase too long"); else if (rc) return unlock_pinentry (ctrl, rc); if (!errtext && pininfo->min_digits && !is_generated) { /* do some basic checks on the entered PIN. */ if (!all_digitsp (pininfo->pin)) errtext = L_("Invalid characters in PIN"); else if (pininfo->max_digits && strlen (pininfo->pin) > pininfo->max_digits) errtext = L_("PIN too long"); else if (strlen (pininfo->pin) < pininfo->min_digits) errtext = L_("PIN too short"); } if (!errtext && pininfo->check_cb && !is_generated) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); /* When pinentry cache causes an error, return now. */ if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) return unlock_pinentry (ctrl, rc); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) { if (pininfo->cb_errtext) errtext = pininfo->cb_errtext; else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE || gpg_err_code (rc) == GPG_ERR_BAD_PIN) errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); } else if (rc) return unlock_pinentry (ctrl, rc); } if (!errtext) { if (pininfo->with_repeat && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) pininfo->repeat_okay = 1; return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ } if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) { /* The password was read from the cache. Don't count this against the retry count. */ pininfo->failed_tries --; } } return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN : GPG_ERR_BAD_PASSPHRASE)); } /* Ask for the passphrase using the supplied arguments. The returned passphrase needs to be freed by the caller. PININFO is optional and can be used to have constraints checking while the pinentry dialog is open (like what we do in agent_askpin). This is very similar to agent_askpin and we should eventually merge the two functions. */ int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar, const char *keyinfo, cache_mode_t cache_mode, struct pin_entry_info_s *pininfo) { int rc; int is_pin; int is_generated; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; *retpass = NULL; if (opt.batch) return gpg_error (GPG_ERR_BAD_PASSPHRASE); if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { unsigned char *passphrase; size_t size; if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK && pininfo) { *pininfo->pin = 0; /* Reset the PIN. */ rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size, pininfo->max_length - 1); if (rc) return rc; memcpy (&pininfo->pin, passphrase, size); wipememory (passphrase, size); xfree (passphrase); pininfo->pin[size] = 0; if (pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); } return rc; } else if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { /* Legacy variant w/o PININFO. */ return pinentry_loopback (ctrl, "PASSPHRASE", (unsigned char **)retpass, &size, MAX_PASSPHRASE_LEN); } return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; /* Set IS_PIN and if needed a default prompt. */ if (prompt) is_pin = !!strstr (prompt, "PIN"); else { is_pin = desc && strstr (desc, "PIN"); prompt = is_pin? L_("PIN:"): L_("Passphrase:"); } /* If we have a KEYINFO string and are normal, user, or ssh cache mode, we tell that the Pinentry so it may use it for own caching purposes. Most pinentries won't have this implemented and thus we do not error out in this case. */ if (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH)) snprintf (line, DIM(line), "SETKEYINFO %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); else snprintf (line, DIM(line), "SETKEYINFO --clear"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) return unlock_pinentry (ctrl, rc); if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); snprintf (line, DIM(line), "SETPROMPT %s", prompt); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); if ((with_qualitybar || (pininfo && pininfo->with_qualitybar)) && opt.min_passphrase_len) { rc = setup_qualitybar (ctrl); if (rc) return unlock_pinentry (ctrl, rc); } if (errtext) { snprintf (line, DIM(line), "SETERROR %s", errtext); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } rc = setup_formatted_passphrase (ctrl); if (rc) return unlock_pinentry (ctrl, rc); if (!pininfo) { /* Legacy method without PININFO. */ memset (&parm, 0, sizeof parm); parm.size = ASSUAN_LINELENGTH/2 - 5; parm.buffer = gcry_malloc_secure (parm.size+10); if (!parm.buffer) return unlock_pinentry (ctrl, out_of_core ()); rc = do_getpin (ctrl, &parm); if (rc) xfree (parm.buffer); else *retpass = parm.buffer; return unlock_pinentry (ctrl, rc); } /* We got PININFO. */ if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEATERROR %s", L_("does not match - try again")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) pininfo->with_repeat = 0; /* Pinentry does not support it. */ (void)setup_genpin (ctrl); rc = setup_enforced_constraints (ctrl); if (rc) return unlock_pinentry (ctrl, rc); } pininfo->repeat_okay = 0; pininfo->status = 0; for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { memset (&parm, 0, sizeof parm); parm.constraints_flags = pininfo->constraints_flags; parm.size = pininfo->max_length; parm.buffer = (unsigned char*)pininfo->pin; *pininfo->pin = 0; /* Reset the PIN. */ if (errtext) { /* TRANSLATORS: The string is appended to an error message in the pinentry. The %s is the actual error message, the two %d give the current and maximum number of tries. */ snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"), errtext, pininfo->failed_tries+1, pininfo->max_tries); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); errtext = NULL; } if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } rc = do_getpin (ctrl, &parm); pininfo->status = parm.status; is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED); if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? L_("PIN too long") : L_("Passphrase too long"); else if (rc) return unlock_pinentry (ctrl, rc); if (!errtext && pininfo->min_digits && !is_generated) { /* do some basic checks on the entered PIN. */ if (!all_digitsp (pininfo->pin)) errtext = L_("Invalid characters in PIN"); else if (pininfo->max_digits && strlen (pininfo->pin) > pininfo->max_digits) errtext = L_("PIN too long"); else if (strlen (pininfo->pin) < pininfo->min_digits) errtext = L_("PIN too short"); } if (!errtext && pininfo->check_cb && !is_generated) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); /* When pinentry cache causes an error, return now. */ if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) return unlock_pinentry (ctrl, rc); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) { if (pininfo->cb_errtext) errtext = pininfo->cb_errtext; else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE || gpg_err_code (rc) == GPG_ERR_BAD_PIN) errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); } else if (rc) return unlock_pinentry (ctrl, rc); } if (!errtext) { if (pininfo->with_repeat && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) pininfo->repeat_okay = 1; return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ } if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) { /* The password was read from the Pinentry's own cache. Don't count this against the retry count. */ pininfo->failed_tries--; } } return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN : GPG_ERR_BAD_PASSPHRASE)); } /* Pop up the PIN-entry, display the text and the prompt and ask the user to confirm this. We return 0 for success, ie. the user confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an other error. If WITH_CANCEL it true an extra cancel button is displayed to allow the user to easily return a GPG_ERR_CANCELED. if the Pinentry does not support this, the user can still cancel by closing the Pinentry window. */ int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *notok, int with_cancel) { int rc; char line[ASSUAN_LINELENGTH]; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return pinentry_loopback_confirm (ctrl, desc, 1, ok, notok); return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); if (rc) return unlock_pinentry (ctrl, rc); if (ok) { snprintf (line, DIM(line), "SETOK %s", ok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } if (notok) { /* Try to use the newer NOTOK feature if a cancel button is requested. If no cancel button is requested we keep on using the standard cancel. */ if (with_cancel) { snprintf (line, DIM(line), "SETNOTOK %s", notok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } else rc = GPG_ERR_ASS_UNKNOWN_CMD; if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) { snprintf (line, DIM(line), "SETCANCEL %s", notok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } if (rc) return unlock_pinentry (ctrl, rc); } { gnupg_fd_t sock_watched = ctrl->thread_startup.fd; npth_t thread; rc = watch_sock_start (&sock_watched, &thread); if (!rc) { rc = assuan_transact (entry_ctx, "CONFIRM", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); watch_sock_end (&sock_watched, &thread); } return unlock_pinentry (ctrl, rc); } } /* The thread running the popup message. */ static void * popup_message_thread (void *arg) { gpg_error_t rc; gnupg_fd_t sock_watched = *(gnupg_fd_t *)arg; npth_t thread; rc = watch_sock_start (&sock_watched, &thread); if (rc) return NULL; /* We use the --one-button hack instead of the MESSAGE command to allow the use of old Pinentries. Those old Pinentries will then show an additional Cancel button but that is mostly a visual annoyance. */ assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL, NULL, NULL, NULL); watch_sock_end (&sock_watched, &thread); popup_finished = 1; return NULL; } /* Pop up a message window similar to the confirm one but keep it open until agent_popup_message_stop has been called. It is crucial for the caller to make sure that the stop function gets called as soon as the message is not anymore required because the message is system modal and all other attempts to use the pinentry will fail (after a timeout). */ int agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn) { int rc; char line[ASSUAN_LINELENGTH]; npth_attr_t tattr; int err; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return pinentry_loopback_confirm (ctrl, desc, 0, ok_btn, NULL); return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); if (ok_btn) { snprintf (line, DIM(line), "SETOK %s", ok_btn); rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL); if (rc) return unlock_pinentry (ctrl, rc); } err = npth_attr_init (&tattr); if (err) return unlock_pinentry (ctrl, gpg_error_from_errno (err)); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); popup_finished = 0; err = npth_create (&popup_tid, &tattr, popup_message_thread, &ctrl->thread_startup.fd); npth_attr_destroy (&tattr); if (err) { rc = gpg_error_from_errno (err); log_error ("error spawning popup message handler: %s\n", strerror (err) ); return unlock_pinentry (ctrl, rc); } npth_setname_np (popup_tid, "popup-message"); return 0; } /* Close a popup window. */ void agent_popup_message_stop (ctrl_t ctrl) { int rc; pid_t pid; (void)ctrl; if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return; if (!popup_tid || !entry_ctx) { log_debug ("agent_popup_message_stop called with no active popup\n"); return; } pid = assuan_get_pid (entry_ctx); if (pid == (pid_t)(-1)) ; /* No pid available can't send a kill. */ else if (popup_finished) ; /* Already finished and ready for joining. */ #ifdef HAVE_W32_SYSTEM /* Older versions of assuan set PID to 0 on Windows to indicate an invalid value. */ else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) { HANDLE process = (HANDLE) pid; /* Arbitrary error code. */ TerminateProcess (process, 1); } #else else if (pid > 0) kill (pid, SIGINT); #endif /* Now wait for the thread to terminate. */ rc = npth_join (popup_tid, NULL); if (rc) log_debug ("agent_popup_message_stop: pth_join failed: %s\n", strerror (rc)); /* Thread IDs are opaque, but we try our best here by resetting it to the same content that a static global variable has. */ memset (&popup_tid, '\0', sizeof (popup_tid)); /* Now we can close the connection. */ unlock_pinentry (ctrl, 0); } int agent_clear_passphrase (ctrl_t ctrl, const char *keyinfo, cache_mode_t cache_mode) { int rc; char line[ASSUAN_LINELENGTH]; if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH))) return gpg_error (GPG_ERR_NOT_SUPPORTED); rc = start_pinentry (ctrl); if (rc) return rc; snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return unlock_pinentry (ctrl, rc); } diff --git a/common/call-gpg.c b/common/call-gpg.c index c1472e961..a4723ca43 100644 --- a/common/call-gpg.c +++ b/common/call-gpg.c @@ -1,753 +1,751 @@ /* call-gpg.c - Communication with the GPG * Copyright (C) 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include "call-gpg.h" #include "exechelp.h" #include "i18n.h" #include "logging.h" #include "membuf.h" #include "strlist.h" #include "util.h" 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_from_errno (int e) { return gpg_err_make (default_errsource, gpg_err_code_from_errno (e)); } /* Fire up a new GPG. Handle the server's initial greeting. Returns 0 on success and stores the assuan context at R_CTX. */ static gpg_error_t start_gpg (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, int input_fd, int output_fd, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx = NULL; const char *pgmname; const char **argv; assuan_fd_t no_close_list[5]; int i; char line[ASSUAN_LINELENGTH]; (void)ctrl; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (err)); return err; } /* The first time we are used, initialize the gpg_program variable. */ if ( !gpg_program || !*gpg_program ) gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); /* Compute argv[0]. */ if ( !(pgmname = strrchr (gpg_program, '/'))) pgmname = gpg_program; else pgmname++; if (fflush (NULL)) { err = my_error_from_syserror (); log_error ("error flushing pending output: %s\n", gpg_strerror (err)); return err; } argv = xtrycalloc (strlist_length (gpg_arguments) + 3, sizeof *argv); if (argv == NULL) { err = my_error_from_syserror (); return err; } i = 0; argv[i++] = pgmname; argv[i++] = "--server"; for (; gpg_arguments; gpg_arguments = gpg_arguments->next) argv[i++] = gpg_arguments->d; argv[i++] = NULL; i = 0; - 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)); if (input_fd != -1) no_close_list[i++] = assuan_fd_from_posix_fd (input_fd); if (output_fd != -1) no_close_list[i++] = assuan_fd_from_posix_fd (output_fd); no_close_list[i] = ASSUAN_INVALID_FD; /* Connect to GPG and perform initial handshaking. */ err = assuan_pipe_connect (ctx, gpg_program, argv, no_close_list, NULL, NULL, 0); if (err) { assuan_release (ctx); log_error ("can't connect to GPG: %s\n", gpg_strerror (err)); return gpg_error (GPG_ERR_NO_ENGINE); } if (input_fd != -1) { snprintf (line, sizeof line, "INPUT FD=%d", input_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending INPUT command: %s\n", gpg_strerror (err)); return err; } } if (output_fd != -1) { snprintf (line, sizeof line, "OUTPUT FD=%d", output_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending OUTPUT command: %s\n", gpg_strerror (err)); return err; } } *r_ctx = ctx; return 0; } /* Release the assuan context created by start_gpg. */ static void release_gpg (assuan_context_t ctx) { assuan_release (ctx); } /* The data passed to the writer_thread. */ struct writer_thread_parms { int fd; const void *data; size_t datalen; estream_t stream; gpg_error_t *err_addr; }; /* The thread started by start_writer. */ static void * writer_thread_main (void *arg) { gpg_error_t err = 0; struct writer_thread_parms *parm = arg; char _buffer[4096]; char *buffer; size_t length; if (parm->stream) { buffer = _buffer; err = es_read (parm->stream, buffer, sizeof _buffer, &length); if (err) { log_error ("reading stream failed: %s\n", gpg_strerror (err)); goto leave; } } else { buffer = (char *) parm->data; length = parm->datalen; } while (length) { ssize_t nwritten; nwritten = npth_write (parm->fd, buffer, length < 4096? length:4096); if (nwritten < 0) { if (errno == EINTR) continue; err = my_error_from_syserror (); break; /* Write error. */ } length -= nwritten; if (parm->stream) { if (length == 0) { err = es_read (parm->stream, buffer, sizeof _buffer, &length); if (err) { log_error ("reading stream failed: %s\n", gpg_strerror (err)); break; } if (length == 0) /* We're done. */ break; } } else buffer += nwritten; } leave: *parm->err_addr = err; if (close (parm->fd)) log_error ("closing writer fd %d failed: %s\n", parm->fd, strerror (errno)); xfree (parm); return NULL; } /* Fire up a thread to send (DATA,DATALEN) to the file descriptor FD. On success the thread receives the ownership over FD. The thread ID is stored at R_TID. WRITER_ERR is the address of an gpg_error_t variable to receive a possible write error after the thread has finished. */ static gpg_error_t start_writer (int fd, const void *data, size_t datalen, estream_t stream, npth_t *r_thread, gpg_error_t *err_addr) { gpg_error_t err; struct writer_thread_parms *parm; npth_attr_t tattr; npth_t thread; int ret; memset (r_thread, '\0', sizeof (*r_thread)); *err_addr = 0; parm = xtrymalloc (sizeof *parm); if (!parm) return my_error_from_syserror (); parm->fd = fd; parm->data = data; parm->datalen = datalen; parm->stream = stream; parm->err_addr = err_addr; npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); ret = npth_create (&thread, &tattr, writer_thread_main, parm); if (ret) { err = my_error_from_errno (ret); log_error ("error spawning writer thread: %s\n", gpg_strerror (err)); } else { npth_setname_np (thread, "fd-writer"); err = 0; *r_thread = thread; } npth_attr_destroy (&tattr); return err; } /* The data passed to the reader_thread. */ struct reader_thread_parms { int fd; membuf_t *mb; estream_t stream; gpg_error_t *err_addr; }; /* The thread started by start_reader. */ static void * reader_thread_main (void *arg) { gpg_error_t err = 0; struct reader_thread_parms *parm = arg; char buffer[4096]; int nread; while ( (nread = npth_read (parm->fd, buffer, sizeof buffer)) ) { if (nread < 0) { if (errno == EINTR) continue; err = my_error_from_syserror (); break; /* Read error. */ } if (parm->stream) { const char *p = buffer; size_t nwritten; while (nread) { err = es_write (parm->stream, p, nread, &nwritten); if (err) { log_error ("writing stream failed: %s\n", gpg_strerror (err)); goto leave; } nread -= nwritten; p += nwritten; } } else put_membuf (parm->mb, buffer, nread); } leave: *parm->err_addr = err; if (close (parm->fd)) log_error ("closing reader fd %d failed: %s\n", parm->fd, strerror (errno)); xfree (parm); return NULL; } /* Fire up a thread to receive data from the file descriptor FD. On success the thread receives the ownership over FD. The thread ID is stored at R_TID. After the thread has finished an error from the thread will be stored at ERR_ADDR. */ static gpg_error_t start_reader (int fd, membuf_t *mb, estream_t stream, npth_t *r_thread, gpg_error_t *err_addr) { gpg_error_t err; struct reader_thread_parms *parm; npth_attr_t tattr; npth_t thread; int ret; memset (r_thread, '\0', sizeof (*r_thread)); *err_addr = 0; parm = xtrymalloc (sizeof *parm); if (!parm) return my_error_from_syserror (); parm->fd = fd; parm->mb = mb; parm->stream = stream; parm->err_addr = err_addr; npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); ret = npth_create (&thread, &tattr, reader_thread_main, parm); if (ret) { err = my_error_from_errno (ret); log_error ("error spawning reader thread: %s\n", gpg_strerror (err)); } else { npth_setname_np (thread, "fd-reader"); err = 0; *r_thread = thread; } npth_attr_destroy (&tattr); return err; } /* Call GPG to encrypt a block of data. */ static gpg_error_t _gpg_encrypt (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, const void *plain, size_t plainlen, estream_t plain_stream, strlist_t keys, membuf_t *reader_mb, estream_t cipher_stream) { gpg_error_t err; assuan_context_t ctx = NULL; int outbound_fds[2] = { -1, -1 }; int inbound_fds[2] = { -1, -1 }; npth_t writer_thread = (npth_t)0; npth_t reader_thread = (npth_t)0; gpg_error_t writer_err, reader_err; char line[ASSUAN_LINELENGTH]; strlist_t sl; int ret; /* Make sure that either the stream interface xor the buffer interface is used. */ assert ((plain == NULL) != (plain_stream == NULL)); assert ((reader_mb == NULL) != (cipher_stream == NULL)); /* Create two pipes. */ err = gnupg_create_outbound_pipe (outbound_fds, NULL, 0); if (!err) err = gnupg_create_inbound_pipe (inbound_fds, NULL, 0); if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); goto leave; } /* Start GPG and send the INPUT and OUTPUT commands. */ err = start_gpg (ctrl, gpg_program, gpg_arguments, outbound_fds[0], inbound_fds[1], &ctx); if (err) goto leave; close (outbound_fds[0]); outbound_fds[0] = -1; close (inbound_fds[1]); inbound_fds[1] = -1; /* Start a writer thread to feed the INPUT command of the server. */ err = start_writer (outbound_fds[1], plain, plainlen, plain_stream, &writer_thread, &writer_err); if (err) return err; outbound_fds[1] = -1; /* The thread owns the FD now. */ /* Start a reader thread to eat from the OUTPUT command of the server. */ err = start_reader (inbound_fds[0], reader_mb, cipher_stream, &reader_thread, &reader_err); if (err) return err; outbound_fds[0] = -1; /* The thread owns the FD now. */ /* Run the encryption. */ for (sl = keys; sl; sl = sl->next) { snprintf (line, sizeof line, "RECIPIENT -- %s", sl->d); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("the engine's RECIPIENT command failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); goto leave; } } err = assuan_transact (ctx, "ENCRYPT", NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("the engine's ENCRYPT command failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); goto leave; } /* Wait for reader and return the data. */ ret = npth_join (reader_thread, NULL); if (ret) { err = my_error_from_errno (ret); log_error ("waiting for reader thread failed: %s\n", gpg_strerror (err)); goto leave; } /* FIXME: Not really valid, as npth_t is an opaque type. */ memset (&reader_thread, '\0', sizeof (reader_thread)); if (reader_err) { err = reader_err; log_error ("read error in reader thread: %s\n", gpg_strerror (err)); goto leave; } /* Wait for the writer to catch a writer error. */ ret = npth_join (writer_thread, NULL); if (ret) { err = my_error_from_errno (ret); log_error ("waiting for writer thread failed: %s\n", gpg_strerror (err)); goto leave; } memset (&writer_thread, '\0', sizeof (writer_thread)); if (writer_err) { err = writer_err; log_error ("write error in writer thread: %s\n", gpg_strerror (err)); goto leave; } leave: /* FIXME: Not valid, as npth_t is an opaque type. */ if (reader_thread) npth_detach (reader_thread); if (writer_thread) npth_detach (writer_thread); if (outbound_fds[0] != -1) close (outbound_fds[0]); if (outbound_fds[1] != -1) close (outbound_fds[1]); if (inbound_fds[0] != -1) close (inbound_fds[0]); if (inbound_fds[1] != -1) close (inbound_fds[1]); release_gpg (ctx); return err; } gpg_error_t gpg_encrypt_blob (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, const void *plain, size_t plainlen, strlist_t keys, void **r_ciph, size_t *r_ciphlen) { gpg_error_t err; membuf_t reader_mb; *r_ciph = NULL; *r_ciphlen = 0; /* Init the memory buffer to receive the encrypted stuff. */ init_membuf (&reader_mb, 4096); err = _gpg_encrypt (ctrl, gpg_program, gpg_arguments, plain, plainlen, NULL, keys, &reader_mb, NULL); if (! err) { /* Return the data. */ *r_ciph = get_membuf (&reader_mb, r_ciphlen); if (!*r_ciph) { err = my_error_from_syserror (); log_error ("error while storing the data in the reader thread: %s\n", gpg_strerror (err)); } } xfree (get_membuf (&reader_mb, NULL)); return err; } gpg_error_t gpg_encrypt_stream (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, estream_t plain_stream, strlist_t keys, estream_t cipher_stream) { return _gpg_encrypt (ctrl, gpg_program, gpg_arguments, NULL, 0, plain_stream, keys, NULL, cipher_stream); } /* Call GPG to decrypt a block of data. */ static gpg_error_t _gpg_decrypt (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, const void *ciph, size_t ciphlen, estream_t cipher_stream, membuf_t *reader_mb, estream_t plain_stream) { gpg_error_t err; assuan_context_t ctx = NULL; int outbound_fds[2] = { -1, -1 }; int inbound_fds[2] = { -1, -1 }; npth_t writer_thread = (npth_t)0; npth_t reader_thread = (npth_t)0; gpg_error_t writer_err, reader_err; int ret; /* Make sure that either the stream interface xor the buffer interface is used. */ assert ((ciph == NULL) != (cipher_stream == NULL)); assert ((reader_mb == NULL) != (plain_stream == NULL)); /* Create two pipes. */ err = gnupg_create_outbound_pipe (outbound_fds, NULL, 0); if (!err) err = gnupg_create_inbound_pipe (inbound_fds, NULL, 0); if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); goto leave; } /* Start GPG and send the INPUT and OUTPUT commands. */ err = start_gpg (ctrl, gpg_program, gpg_arguments, outbound_fds[0], inbound_fds[1], &ctx); if (err) goto leave; close (outbound_fds[0]); outbound_fds[0] = -1; close (inbound_fds[1]); inbound_fds[1] = -1; /* Start a writer thread to feed the INPUT command of the server. */ err = start_writer (outbound_fds[1], ciph, ciphlen, cipher_stream, &writer_thread, &writer_err); if (err) return err; outbound_fds[1] = -1; /* The thread owns the FD now. */ /* Start a reader thread to eat from the OUTPUT command of the server. */ err = start_reader (inbound_fds[0], reader_mb, plain_stream, &reader_thread, &reader_err); if (err) return err; outbound_fds[0] = -1; /* The thread owns the FD now. */ /* Run the decryption. */ err = assuan_transact (ctx, "DECRYPT", NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("the engine's DECRYPT command failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); goto leave; } /* Wait for reader and return the data. */ ret = npth_join (reader_thread, NULL); if (ret) { err = my_error_from_errno (ret); log_error ("waiting for reader thread failed: %s\n", gpg_strerror (err)); goto leave; } memset (&reader_thread, '\0', sizeof (reader_thread)); if (reader_err) { err = reader_err; log_error ("read error in reader thread: %s\n", gpg_strerror (err)); goto leave; } /* Wait for the writer to catch a writer error. */ ret = npth_join (writer_thread, NULL); if (ret) { err = my_error_from_errno (ret); log_error ("waiting for writer thread failed: %s\n", gpg_strerror (err)); goto leave; } memset (&writer_thread, '\0', sizeof (writer_thread)); if (writer_err) { err = writer_err; log_error ("write error in writer thread: %s\n", gpg_strerror (err)); goto leave; } leave: if (reader_thread) npth_detach (reader_thread); if (writer_thread) npth_detach (writer_thread); if (outbound_fds[0] != -1) close (outbound_fds[0]); if (outbound_fds[1] != -1) close (outbound_fds[1]); if (inbound_fds[0] != -1) close (inbound_fds[0]); if (inbound_fds[1] != -1) close (inbound_fds[1]); release_gpg (ctx); return err; } gpg_error_t gpg_decrypt_blob (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, const void *ciph, size_t ciphlen, void **r_plain, size_t *r_plainlen) { gpg_error_t err; membuf_t reader_mb; *r_plain = NULL; *r_plainlen = 0; /* Init the memory buffer to receive the encrypted stuff. */ init_membuf_secure (&reader_mb, 1024); err = _gpg_decrypt (ctrl, gpg_program, gpg_arguments, ciph, ciphlen, NULL, &reader_mb, NULL); if (! err) { /* Return the data. */ *r_plain = get_membuf (&reader_mb, r_plainlen); if (!*r_plain) { err = my_error_from_syserror (); log_error ("error while storing the data in the reader thread: %s\n", gpg_strerror (err)); } } xfree (get_membuf (&reader_mb, NULL)); return err; } gpg_error_t gpg_decrypt_stream (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, estream_t cipher_stream, estream_t plain_stream) { return _gpg_decrypt (ctrl, gpg_program, gpg_arguments, NULL, 0, cipher_stream, NULL, plain_stream); } diff --git a/g13/call-syshelp.c b/g13/call-syshelp.c index a69573bd1..8850c3779 100644 --- a/g13/call-syshelp.c +++ b/g13/call-syshelp.c @@ -1,631 +1,628 @@ /* call-syshelp.c - Communication with g13-syshelp * Copyright (C) 2015 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "g13.h" #include #include "../common/i18n.h" #include "g13tuple.h" #include "keyblob.h" #include "../common/membuf.h" #include "create.h" #include "call-syshelp.h" /* Local data for this module. A pointer to this is stored in the CTRL object of each connection. */ struct call_syshelp_s { assuan_context_t assctx; /* The Assuan context for the current g13-syshep connection. */ }; /* Parameter used with the CREATE command. */ struct create_parm_s { assuan_context_t ctx; ctrl_t ctrl; membuf_t plaintext; unsigned int expect_plaintext:1; unsigned int got_plaintext:1; }; /* Parameter used with the MOUNT command. */ struct mount_parm_s { assuan_context_t ctx; ctrl_t ctrl; const void *keyblob; size_t keybloblen; }; /* Fork off the syshelp tool if this has not already been done. On success stores the current Assuan context for the syshelp tool at R_CTX. */ static gpg_error_t start_syshelp (ctrl_t ctrl, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx; assuan_fd_t no_close_list[3]; int i; *r_ctx = NULL; if (ctrl->syshelp_local && (*r_ctx = ctrl->syshelp_local->assctx)) return 0; /* Already set. */ if (opt.verbose) log_info ("starting a new syshelp\n"); if (!ctrl->syshelp_local) { ctrl->syshelp_local = xtrycalloc (1, sizeof *ctrl->syshelp_local); if (!ctrl->syshelp_local) return gpg_error_from_syserror (); } if (es_fflush (NULL)) { err = gpg_error_from_syserror (); log_error ("error flushing pending output: %s\n", gpg_strerror (err)); return err; } - i = 0; - 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 (es_fileno (es_stderr)); - no_close_list[i] = ASSUAN_INVALID_FD; + no_close_list[0] = assuan_fd_from_posix_fd (es_fileno (es_stderr)); + no_close_list[1] = ASSUAN_INVALID_FD; err = assuan_new (&ctx); if (err) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (err)); return err; } /* Call userv to start g13-syshelp. This userv script needs to be * installed under the name "gnupg-g13-syshelp": * * if ( glob service-user root * ) * reset * suppress-args * execute /home/wk/b/gnupg/g13/g13-syshelp -v * else * error Nothing to do for this service-user * fi * quit */ { const char *argv[4]; argv[0] = "userv"; argv[1] = "root"; argv[2] = "gnupg-g13-syshelp"; argv[3] = NULL; err = assuan_pipe_connect (ctx, "/usr/bin/userv", argv, no_close_list, NULL, NULL, 0); } if (err) { log_error ("can't connect to '%s': %s %s\n", "g13-syshelp", gpg_strerror (err), gpg_strsource (err)); log_info ("(is userv and its gnupg-g13-syshelp script installed?)\n"); assuan_release (ctx); return err; } *r_ctx = ctrl->syshelp_local->assctx = ctx; if (DBG_IPC) log_debug ("connection to g13-syshelp established\n"); return 0; } /* Release local resources associated with CTRL. */ void call_syshelp_release (ctrl_t ctrl) { if (!ctrl) return; if (ctrl->syshelp_local) { assuan_release (ctrl->syshelp_local->assctx); ctrl->syshelp_local->assctx = NULL; xfree (ctrl->syshelp_local); ctrl->syshelp_local = NULL; } } /* Status callback for call_syshelp_find_device. */ static gpg_error_t finddevice_status_cb (void *opaque, const char *line) { char **r_blockdev = opaque; char *p; if ((p = has_leading_keyword (line, "BLOCKDEV")) && *p && !*r_blockdev) { *r_blockdev = xtrystrdup (p); if (!*r_blockdev) return gpg_error_from_syserror (); } return 0; } /* Send the FINDDEVICE command to the syshelper. On success the name * of the block device is stored at R_BLOCKDEV. */ gpg_error_t call_syshelp_find_device (ctrl_t ctrl, const char *name, char **r_blockdev) { gpg_error_t err; assuan_context_t ctx; char *line = NULL; char *blockdev = NULL; /* The result. */ *r_blockdev = NULL; err = start_syshelp (ctrl, &ctx); if (err) goto leave; line = xtryasprintf ("FINDDEVICE %s", name); if (!line) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, finddevice_status_cb, &blockdev); if (err) goto leave; if (!blockdev) { log_error ("status line for successful FINDDEVICE missing\n"); err = gpg_error (GPG_ERR_UNEXPECTED); goto leave; } *r_blockdev = blockdev; blockdev = NULL; leave: xfree (blockdev); xfree (line); return err; } static gpg_error_t getkeyblob_data_cb (void *opaque, const void *data, size_t datalen) { membuf_t *mb = opaque; if (data) put_membuf (mb, data, datalen); return 0; } /* Send the GTEKEYBLOB command to the syshelper. On success the * encrypted keyblpob is stored at (R_ENCKEYBLOB,R_ENCKEYBLOBLEN). */ gpg_error_t call_syshelp_get_keyblob (ctrl_t ctrl, void **r_enckeyblob, size_t *r_enckeybloblen) { gpg_error_t err; assuan_context_t ctx; membuf_t mb; *r_enckeyblob = NULL; *r_enckeybloblen = 0; init_membuf (&mb, 512); err = start_syshelp (ctrl, &ctx); if (err) goto leave; err = assuan_transact (ctx, "GETKEYBLOB", getkeyblob_data_cb, &mb, NULL, NULL, NULL, NULL); if (err) goto leave; *r_enckeyblob = get_membuf (&mb, r_enckeybloblen); if (!*r_enckeyblob) err = gpg_error_from_syserror (); leave: xfree (get_membuf (&mb, NULL)); return err; } /* Send the DEVICE command to the syshelper. FNAME is the name of the device. */ gpg_error_t call_syshelp_set_device (ctrl_t ctrl, const char *fname) { gpg_error_t err; assuan_context_t ctx; char *line = NULL; err = start_syshelp (ctrl, &ctx); if (err) goto leave; line = xtryasprintf ("DEVICE %s", fname); if (!line) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); leave: xfree (line); return err; } static gpg_error_t create_status_cb (void *opaque, const char *line) { struct create_parm_s *parm = opaque; if (has_leading_keyword (line, "PLAINTEXT_FOLLOWS")) parm->expect_plaintext = 1; return 0; } static gpg_error_t create_data_cb (void *opaque, const void *data, size_t datalen) { struct create_parm_s *parm = opaque; gpg_error_t err = 0; if (!parm->expect_plaintext) { log_error ("status line for data missing\n"); err = gpg_error (GPG_ERR_UNEXPECTED); } else if (data) { put_membuf (&parm->plaintext, data, datalen); } else { parm->expect_plaintext = 0; parm->got_plaintext = 1; } return err; } static gpg_error_t create_inq_cb (void *opaque, const char *line) { struct create_parm_s *parm = opaque; gpg_error_t err; if (has_leading_keyword (line, "ENCKEYBLOB")) { void *plaintext; size_t plaintextlen; if (!parm->got_plaintext) err = gpg_error (GPG_ERR_UNEXPECTED); else if (!(plaintext = get_membuf (&parm->plaintext, &plaintextlen))) err = gpg_error_from_syserror (); else { void *ciphertext; size_t ciphertextlen; log_printhex (plaintext, plaintextlen, "plain"); err = g13_encrypt_keyblob (parm->ctrl, plaintext, plaintextlen, &ciphertext, &ciphertextlen); wipememory (plaintext, plaintextlen); xfree (plaintext); if (err) log_error ("error encrypting keyblob: %s\n", gpg_strerror (err)); else { err = assuan_send_data (parm->ctx, ciphertext, ciphertextlen); xfree (ciphertext); if (err) log_error ("sending ciphertext to g13-syshelp failed: %s\n", gpg_strerror (err)); } } } else err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); return err; } /* Run the CREATE command on the current device. CONTTYPES gives the requested content type for the new container. */ gpg_error_t call_syshelp_run_create (ctrl_t ctrl, int conttype) { gpg_error_t err; assuan_context_t ctx; struct create_parm_s parm; memset (&parm, 0, sizeof parm); err = start_syshelp (ctrl, &ctx); if (err) goto leave; /* tty_get ("waiting for debugger"); */ /* tty_kill_prompt (); */ parm.ctx = ctx; parm.ctrl = ctrl; init_membuf (&parm.plaintext, 512); if (conttype == CONTTYPE_DM_CRYPT) { err = assuan_transact (ctx, "CREATE dm-crypt", create_data_cb, &parm, create_inq_cb, &parm, create_status_cb, &parm); } else { log_error ("invalid backend type %d given\n", conttype); err = GPG_ERR_INTERNAL; goto leave; } leave: xfree (get_membuf (&parm.plaintext, NULL)); return err; } static gpg_error_t mount_status_cb (void *opaque, const char *line) { struct mount_parm_s *parm = opaque; /* Nothing right now. */ (void)parm; (void)line; return 0; } /* Inquire callback for MOUNT and RESUME. */ static gpg_error_t mount_inq_cb (void *opaque, const char *line) { struct mount_parm_s *parm = opaque; gpg_error_t err; if (has_leading_keyword (line, "KEYBLOB")) { int setconfidential = !assuan_get_flag (parm->ctx, ASSUAN_CONFIDENTIAL); if (setconfidential) assuan_begin_confidential (parm->ctx); err = assuan_send_data (parm->ctx, parm->keyblob, parm->keybloblen); if (setconfidential) assuan_end_confidential (parm->ctx); if (err) log_error ("sending keyblob to g13-syshelp failed: %s\n", gpg_strerror (err)); } else err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); return err; } /* * Run the MOUNT command on the current device. CONTTYPES gives the * requested content type for the new container. MOUNTPOINT the * desired mount point or NULL for default. */ gpg_error_t call_syshelp_run_mount (ctrl_t ctrl, int conttype, const char *mountpoint, tupledesc_t tuples) { gpg_error_t err; assuan_context_t ctx; struct mount_parm_s parm; memset (&parm, 0, sizeof parm); err = start_syshelp (ctrl, &ctx); if (err) goto leave; /* tty_get ("waiting for debugger"); */ /* tty_kill_prompt (); */ parm.ctx = ctx; parm.ctrl = ctrl; if (conttype == CONTTYPE_DM_CRYPT) { ref_tupledesc (tuples); parm.keyblob = get_tupledesc_data (tuples, &parm.keybloblen); err = assuan_transact (ctx, "MOUNT dm-crypt", NULL, NULL, mount_inq_cb, &parm, mount_status_cb, &parm); unref_tupledesc (tuples); } else { (void)mountpoint; /* Not used. */ log_error ("invalid backend type %d given\n", conttype); err = GPG_ERR_INTERNAL; goto leave; } leave: return err; } /* * Run the UMOUNT command on the current device. CONTTYPES gives the * content type of the container (fixme: Do we really need this?). */ gpg_error_t call_syshelp_run_umount (ctrl_t ctrl, int conttype) { gpg_error_t err; assuan_context_t ctx; err = start_syshelp (ctrl, &ctx); if (err) goto leave; if (conttype == CONTTYPE_DM_CRYPT) { err = assuan_transact (ctx, "UMOUNT dm-crypt", NULL, NULL, NULL, NULL, NULL, NULL); } else { log_error ("invalid backend type %d given\n", conttype); err = GPG_ERR_INTERNAL; goto leave; } leave: return err; } /* * Run the SUSPEND command on the current device. CONTTYPES gives the * requested content type for the new container. */ gpg_error_t call_syshelp_run_suspend (ctrl_t ctrl, int conttype) { gpg_error_t err; assuan_context_t ctx; err = start_syshelp (ctrl, &ctx); if (err) goto leave; if (conttype == CONTTYPE_DM_CRYPT) { err = assuan_transact (ctx, "SUSPEND dm-crypt", NULL, NULL, NULL, NULL, NULL, NULL); } else { log_error ("invalid backend type %d given\n", conttype); err = GPG_ERR_INTERNAL; goto leave; } leave: return err; } /* Run the RESUME command on the current device. CONTTYPES gives the requested content type for the container. */ gpg_error_t call_syshelp_run_resume (ctrl_t ctrl, int conttype, tupledesc_t tuples) { gpg_error_t err; assuan_context_t ctx; struct mount_parm_s parm; memset (&parm, 0, sizeof parm); err = start_syshelp (ctrl, &ctx); if (err) goto leave; /* tty_get ("waiting for debugger"); */ /* tty_kill_prompt (); */ parm.ctx = ctx; parm.ctrl = ctrl; if (conttype == CONTTYPE_DM_CRYPT) { ref_tupledesc (tuples); parm.keyblob = get_tupledesc_data (tuples, &parm.keybloblen); err = assuan_transact (ctx, "RESUME dm-crypt", NULL, NULL, mount_inq_cb, &parm, NULL, NULL); unref_tupledesc (tuples); } else { log_error ("invalid backend type %d given\n", conttype); err = GPG_ERR_INTERNAL; goto leave; } leave: return err; } diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index dff367050..7a0dc65b9 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -1,2334 +1,2333 @@ /* 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; #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 /* Unix */ if (opt.verbose) log_info ("file '%s' opened in \"%s\" mode, fd=%d\n", name, mode, fd); set_int_var (varname, fd); #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; + no_close[1] = 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) { fp = popen (d->file, "r"); 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) { if (pclose (fp)) cancelled = 1; } 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; }