/* Setup the environment so that the pinentry is able to get all required information. This is used prior to an exec of the protect-tool. */ void setup_pinentry_env (void) { #ifndef HAVE_W32_SYSTEM char *lc; const char *name, *value; int iterator; /* Try to make sure that GPG_TTY has been set. This is needed if we call for example the protect-tools with redirected stdin and thus it won't be able to ge a default by itself. Try to do it here but print a warning. */ value = session_env_getenv (opt.session_env, "GPG_TTY"); if (value) gnupg_setenv ("GPG_TTY", value, 1); else if (!(lc=getenv ("GPG_TTY")) || !*lc) { log_error (_("GPG_TTY has not been set - " "using maybe bogus default\n")); lc = gnupg_ttyname (0); if (!lc) lc = "/dev/tty"; gnupg_setenv ("GPG_TTY", lc, 1); } if (opt.lc_ctype) gnupg_setenv ("LC_CTYPE", opt.lc_ctype, 1); #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) else if ( (lc = setlocale (LC_CTYPE, "")) ) gnupg_setenv ("LC_CTYPE", lc, 1); #endif if (opt.lc_messages) gnupg_setenv ("LC_MESSAGES", opt.lc_messages, 1); #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) else if ( (lc = setlocale (LC_MESSAGES, "")) ) gnupg_setenv ("LC_MESSAGES", lc, 1); #endif iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { if (!strcmp (name, "GPG_TTY")) continue; /* Already set. */ value = session_env_getenv (opt.session_env, name); if (value) gnupg_setenv (name, value, 1); } #endif /*!HAVE_W32_SYSTEM*/ }
/* 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) { int iterator = 0; const char *name, *assname, *value; gcry_control (GCRYCTL_TERM_SECMEM); 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. */ if (!assname || !strcmp (name, "XAUTHORITY") || !strcmp (name, "PINENTRY_USER_DATA")) { value = session_env_getenv (ctrl->session_env, name); if (value) 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; #ifndef HAVE_W32_SYSTEM struct sigaction sa; /* Pop up message should be able to be killed by SIGINT. */ sigemptyset (&sa.sa_mask); sa.sa_handler = SIG_DFL; sa.sa_flags = 0; sigaction (SIGINT, &sa, NULL); sigprocmask (SIG_SETMASK, &sa.sa_mask, NULL); /* Unblock all signals. */ #endif if (!where) { int iterator = 0; const char *name, *assname, *value; gcry_control (GCRYCTL_TERM_SECMEM); 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. */ if (!assname || !strcmp (name, "XAUTHORITY") || !strcmp (name, "PINENTRY_USER_DATA")) { value = session_env_getenv (ctrl->session_env, name); if (value) setenv (name, value, 1); } } } }
/* Fork off the pin entry if this has not already been done. Note, that this function must always be used to aquire the lock for the pinentry - we will serialize _all_ pinentry calls. */ static int start_pinentry (ctrl_t ctrl) { int rc = 0; const char *pgmname; assuan_context_t ctx; const char *argv[5]; int no_close_list[3]; int i; const char *tmpstr; unsigned long pinentry_pid; const char *value; struct timespec abstime; int err; 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; } entry_owner = ctrl; 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 Wime implementaion does not flush stdin,stdout and stderr - see above. Lets try to ignore the error. */ #ifndef HAVE_W32_SYSTEM return unlock_pinentry (tmperr); #endif } if (!opt.pinentry_program || !*opt.pinentry_program) opt.pinentry_program = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY); pgmname = opt.pinentry_program; if ( !(pgmname = strrchr (opt.pinentry_program, '/'))) pgmname = opt.pinentry_program; 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] = opt.pinentry_program; #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] = -1; rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); return rc; } /* 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, 1); #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 (ctx, opt.pinentry_program, 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", opt.pinentry_program, gpg_strerror (rc)); assuan_release (ctx); return unlock_pinentry (gpg_error (GPG_ERR_NO_PIN_ENTRY)); } entry_ctx = ctx; if (DBG_ASSUAN) log_debug ("connection to PIN entry established\n"); rc = assuan_transact (entry_ctx, opt.no_grab? "OPTION no-grab":"OPTION grab", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (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 (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } value = session_env_getenv (ctrl->session_env, "TERM"); if (value) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (ctrl->lc_ctype) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (ctrl->lc_messages) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } { /* Provide a few default strings for use by the pinentries. This may help a pinentry to avoid implementing localization code. */ static struct { const char *key, *value; } tbl[] = { /* TRANSLATORS: These are labels for buttons etc used in Pinentries. An underscore indicates that the next letter should be used as an accelerator. Double the underscore for a literal one. The actual to be translated text starts after the second vertical bar. */ { "ok", N_("|pinentry-label|_OK") }, { "cancel", N_("|pinentry-label|_Cancel") }, { "prompt", N_("|pinentry-label|PIN:") }, { NULL, NULL} }; char *optstr; int idx; const char *s, *s2; for (idx=0; tbl[idx].key; idx++) { s = _(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 (out_of_core ()); assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); 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); } } /* 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 && (pid_t)pinentry_pid == (pid_t)(-1)) log_error ("pinentry did not return a PID\n"); else { rc = agent_inq_pinentry_launched (ctrl, pinentry_pid); if (gpg_err_code (rc) == GPG_ERR_CANCELED || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) return unlock_pinentry (gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (rc))); rc = 0; } return 0; }