static gpg_error_t create_pipe_and_estream (int filedes[2], estream_t *r_fp, int outbound, int nonblock, gpg_err_source_t errsource) { gpg_error_t err; if (pipe (filedes) == -1) { err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); filedes[0] = filedes[1] = -1; *r_fp = NULL; return err; } if (outbound) *r_fp = es_fdopen (filedes[0], nonblock? "r,nonblock" : "r"); else *r_fp = es_fdopen (filedes[1], nonblock? "w,nonblock" : "w"); if (!*r_fp) { err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error (_("error creating a stream for a pipe: %s\n"), gpg_strerror (err)); close (filedes[0]); close (filedes[1]); filedes[0] = filedes[1] = -1; return err; } return 0; }
void Kleo::QGpgMERefreshKeysJob::slotCancel() { if(mProcess) mProcess->kill(); mProcess = 0; mError = gpg_err_make(GPG_ERR_SOURCE_GPGSM, GPG_ERR_CANCELED); }
static int get_err_from_number (char *str, gpg_error_t *err) { unsigned long nr; char *tail; gpg_err_set_errno (0); nr = strtoul (str, &tail, 0); if (errno) return 0; if (nr > UINT_MAX) return 0; if (*tail) { unsigned long cnr = strtoul (tail + 1, &tail, 0); if (errno || *tail) return 0; if (nr >= GPG_ERR_SOURCE_DIM || cnr >= GPG_ERR_CODE_DIM) return 0; nr = gpg_err_make (nr, cnr); } *err = (unsigned int) nr; return 1; }
/* Return the next token of an canonical encoded S-expression. BUF is the pointer to the S-expression and BUFLEN is a pointer to the length of this S-expression (used to validate the syntax). Both are updated to reflect the new position. The token itself is returned as a pointer into the original buffer at TOK and TOKLEN. If a parentheses is the next token, TOK will be set to NULL. TOKLEN is checked to be within the bounds. On error an error code is returned and no pointer is not guaranteed to point to a meaningful value. DEPTH should be initialized to 0 and will reflect on return the actual depth of the tree. To detect the end of the S-expression it is advisable to check DEPTH after a successful return. depth = 0; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth) process_token (tok, toklen); if (err) handle_error (); */ gpg_error_t parse_sexp (unsigned char const **buf, size_t *buflen, int *depth, unsigned char const **tok, size_t *toklen) { const unsigned char *s; size_t n, vlen; s = *buf; n = *buflen; *tok = NULL; *toklen = 0; if (!n) return *depth ? gpg_err_make (default_errsource, GPG_ERR_INV_SEXP) : 0; if (*s == '(') { s++; n--; (*depth)++; *buf = s; *buflen = n; return 0; } if (*s == ')') { if (!*depth) return gpg_err_make (default_errsource, GPG_ERR_INV_SEXP); *toklen = 1; s++; n--; (*depth)--; *buf = s; *buflen = n; return 0; } for (vlen=0; n && *s && *s != ':' && (*s >= '0' && *s <= '9'); s++, n--) vlen = vlen*10 + (*s - '0'); if (!n || *s != ':') return gpg_err_make (default_errsource, GPG_ERR_INV_SEXP); s++; n--; if (vlen > n) return gpg_err_make (default_errsource, GPG_ERR_INV_SEXP); *tok = s; *toklen = vlen; s += vlen; n -= vlen; *buf = s; *buflen = n; return 0; }
/* Pop up the PINentry, display the text DESC and a button with the text OK_BTN (which may be NULL to use the default of "OK") and wait for the user to hit this button. The return value is not relevant. */ int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn) { int rc; char line[ASSUAN_LINELENGTH]; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) return gpg_error (GPG_ERR_CANCELED); rc = start_pinentry (ctrl); if (rc) return rc; if (desc) snprintf (line, DIM(line)-1, "SETDESC %s", desc); else snprintf (line, DIM(line)-1, "RESET"); line[DIM(line)-1] = 0; 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 (rc); if (ok_btn) { snprintf (line, DIM(line)-1, "SETOK %s", ok_btn); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); } rc = assuan_transact (entry_ctx, "CONFIRM --one-button", 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); return unlock_pinentry (rc); }
/* See exechelp.h for the description. */ gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode) { gpg_err_code_t ec; int i, status; if (r_exitcode) *r_exitcode = -1; if (pid == (pid_t)(-1)) return gpg_error (GPG_ERR_INV_VALUE); #ifdef USE_NPTH i = npth_waitpid (pid, &status, hang? 0:WNOHANG); #else while ((i=waitpid (pid, &status, hang? 0:WNOHANG)) == (pid_t)(-1) && errno == EINTR); #endif if (i == (pid_t)(-1)) { ec = gpg_err_code_from_errno (errno); log_error (_("waiting for process %d to terminate failed: %s\n"), (int)pid, strerror (errno)); } else if (!i) { ec = GPG_ERR_TIMEOUT; /* Still running. */ } else if (WIFEXITED (status) && WEXITSTATUS (status) == 127) { log_error (_("error running '%s': probably not installed\n"), pgmname); ec = GPG_ERR_CONFIGURATION; } else if (WIFEXITED (status) && WEXITSTATUS (status)) { if (!r_exitcode) log_error (_("error running '%s': exit status %d\n"), pgmname, WEXITSTATUS (status)); else *r_exitcode = WEXITSTATUS (status); ec = GPG_ERR_GENERAL; } else if (!WIFEXITED (status)) { log_error (_("error running '%s': terminated\n"), pgmname); ec = GPG_ERR_GENERAL; } else { if (r_exitcode) *r_exitcode = 0; ec = 0; } return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); }
GpgME::Error Kleo::HierarchicalKeyListJob::start( const QStringList & patterns, bool secretOnly ) { if ( secretOnly || patterns.empty() ) return GpgME::Error( gpg_err_make( GPG_ERR_SOURCE_GPGME, GPG_ERR_UNSUPPORTED_OPERATION ) ); qCopy( patterns.begin(), patterns.end(), std::inserter( mNextSet, mNextSet.begin() ) ); const GpgME::Error err = startAJob(); if ( err ) deleteLater(); return err; }
gpgme_error_t _gpgme_map_gnupg_error (char *err) { unsigned int i; /* Future version of GnuPG might return the error code directly, so we first test for a a numerical value and use that verbatim. Note that this numerical value might be followed by an underschore and the textual representation of the error code. */ if (*err >= '0' && *err <= '9') return strtoul (err, NULL, 10); /* Well, this is a token, use the mapping table to get the error. The drawback is that we won't receive an error source and have to use GPG as source. */ for (i = 0; i < DIM (gnupg_errors); i++) if (!strcmp (gnupg_errors[i].name, err)) return gpg_err_make (GPG_ERR_SOURCE_GPG, gnupg_errors[i].err); return gpg_err_make (GPG_ERR_SOURCE_GPG, GPG_ERR_GENERAL); }
/* Quick&dirty passphrase asking */ static gpg_error_t signature_getpassphrase(UNUSED(void *hook), const char *uid_hint, UNUSED(const char *info), int prev_was_bad, int fd) { char *msg; const char *p; msg = mprintf("%s needs a passphrase\nPlease enter passphrase%s:", (uid_hint!=NULL)?uid_hint:"key", (prev_was_bad!=0)?" again":""); if (msg == NULL) return gpg_err_make(GPG_ERR_SOURCE_USER_1, GPG_ERR_ENOMEM); p = getpass(msg); write(fd, p, strlen(p)); write(fd, "\n", 1); free(msg); return GPG_ERR_NO_ERROR; }
GpgME::Error Kleo::QGpgMERefreshKeysJob::startAProcess() { if(mPatternsToDo.empty()) return 0; // create and start gpgsm process: mProcess = new GnuPGProcessBase(this, "gpgsm -k --with-validation --force-crl-refresh --enable-crl-checks"); // FIXME: obbtain the path to gpgsm from gpgme, so we use the same instance. *mProcess << "gpgsm" << "-k" << "--with-validation" << "--force-crl-refresh" << "--enable-crl-checks"; unsigned int commandLineLength = MAX_CMD_LENGTH; commandLineLength -= strlen("gpgsm") + 1 + strlen("-k") + 1 + strlen("--with-validation") + 1 + strlen("--force-crl-refresh") + 1 + strlen("--enable-crl-checks") + 1; while(!mPatternsToDo.empty()) { const QCString pat = mPatternsToDo.front().utf8().stripWhiteSpace(); const unsigned int patLength = pat.length(); if(patLength >= commandLineLength) break; mPatternsToDo.pop_front(); if(pat.isEmpty()) continue; *mProcess << pat; commandLineLength -= patLength + 1; } mProcess->setUseStatusFD(true); connect(mProcess, SIGNAL(processExited(KProcess *)), SLOT(slotProcessExited(KProcess *))); connect(mProcess, SIGNAL(receivedStderr(KProcess *, char *, int)), SLOT(slotStderr(KProcess *, char *, int))); connect(mProcess, SIGNAL(status(Kleo::GnuPGProcessBase *, const QString &, const QStringList &)), SLOT(slotStatus(Kleo::GnuPGProcessBase *, const QString &, const QStringList &))); if(!mProcess->start(KProcess::NotifyOnExit, KProcess::Stderr)) { mError = gpg_err_make(GPG_ERR_SOURCE_GPGSM, GPG_ERR_ENOENT); // what else? deleteLater(); return mError; } else return 0; }
void Kleo::QGpgMERefreshKeysJob::slotProcessExited(KProcess *proc) { if(proc != mProcess) return; if(!mError && !mPatternsToDo.empty()) if(const GpgME::Error err = startAProcess()) mError = err; else return; emit done(); if(!mError && (!mProcess->normalExit() || mProcess->exitStatus() != 0)) mError = gpg_err_make(GPG_ERR_SOURCE_GPGSM, GPG_ERR_GENERAL); emit result(mError); deleteLater(); }
/* Map the SC error codes to the GNUPG ones */ gpg_error_t map_sc_err (int rc) { gpg_err_code_t e; switch (rc) { case 0: e = 0; break; #ifdef HAVE_OPENSC case SC_ERROR_NOT_SUPPORTED: e = GPG_ERR_NOT_SUPPORTED; break; case SC_ERROR_PKCS15_APP_NOT_FOUND: e = GPG_ERR_NO_PKCS15_APP; break; case SC_ERROR_OUT_OF_MEMORY: e = GPG_ERR_ENOMEM; break; case SC_ERROR_CARD_NOT_PRESENT: e = GPG_ERR_CARD_NOT_PRESENT; break; case SC_ERROR_CARD_REMOVED: e = GPG_ERR_CARD_REMOVED; break; case SC_ERROR_INVALID_CARD: e = GPG_ERR_INV_CARD; break; #endif default: e = GPG_ERR_CARD; break; } /* It does not make much sense to further distingusih the error source between OpenSC and SCD. Thus we use SCD as source here. */ return gpg_err_make (GPG_ERR_SOURCE_SCD, e); }
/* Helper */ static inline gpg_error_t my_error (gpg_err_code_t ec) { return gpg_err_make (default_errsource, ec); }
int main (int argc, char *argv[]) { int i = 1; int listmode = 0; const char *source_sym; const char *error_sym; gpg_error_t err; #ifndef GPG_ERR_INITIALIZED gpg_err_init (); #endif i18n_init (); if (argc == 1) { fprintf (stderr, _("Usage: %s GPG-ERROR [...]\n"), strrchr (argv[0],'/')? (strrchr (argv[0], '/')+1): argv[0]); exit (1); } else if (argc == 2 && !strcmp (argv[1], "--version")) { fputs ("gpg-error (" PACKAGE_NAME ") " PACKAGE_VERSION "\n", stdout); exit (0); } else if (argc == 2 && !strcmp (argv[1], "--list")) { listmode = 1; } if (listmode) { for (i=0; i < GPG_ERR_SOURCE_DIM; i++) { /* We use error code 1 because gpg_err_make requires a non-zero error code. */ err = gpg_err_make (i, 1); err -= 1; source_sym = gpg_strsource_sym (err); if (source_sym) printf ("%u = (%u, -) = (%s, -) = (%s, -)\n", err, gpg_err_source (err), source_sym, gpg_strsource (err)); } for (i=0; i < GPG_ERR_CODE_DIM; i++) { err = gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, i); error_sym = gpg_strerror_sym (err); if (error_sym) printf ("%u = (-, %u) = (-, %s) = (-, %s)\n", err, gpg_err_code (err), error_sym, gpg_strerror (err)); } i = argc; /* Don't run the usual stuff. */ } while (i < argc) { if (get_err_from_number (argv[i], &err) || get_err_from_symbol (argv[i], &err) || get_err_from_str (argv[i], &err)) { source_sym = gpg_strsource_sym (err); error_sym = gpg_strerror_sym (err); printf ("%u = (%u, %u) = (%s, %s) = (%s, %s)\n", err, gpg_err_source (err), gpg_err_code (err), source_sym ? source_sym : "-", error_sym ? error_sym : "-", gpg_strsource (err), gpg_strerror (err)); } else fprintf (stderr, _("%s: warning: could not recognize %s\n"), argv[0], argv[i]); i++; } exit (0); }
/* Fork and exec the PGMNAME, see exechelp.h for details. */ gpg_error_t gnupg_spawn_process (const char *pgmname, const char *argv[], gpg_err_source_t errsource, void (*preexec)(void), unsigned int flags, estream_t infp, estream_t *r_outfp, estream_t *r_errfp, pid_t *pid) { gpg_error_t err; int infd = -1; int outpipe[2] = {-1, -1}; int errpipe[2] = {-1, -1}; estream_t outfp = NULL; estream_t errfp = NULL; (void)flags; /* Currently not used. */ if (r_outfp) *r_outfp = NULL; if (r_errfp) *r_errfp = NULL; *pid = (pid_t)(-1); /* Always required. */ if (infp) { es_fflush (infp); es_rewind (infp); infd = es_fileno (infp); if (infd == -1) return gpg_err_make (errsource, GPG_ERR_INV_VALUE); } if (r_outfp) { err = create_pipe_and_estream (outpipe, &outfp, errsource); if (err) return err; } if (r_errfp) { err = create_pipe_and_estream (errpipe, &errfp, errsource); if (err) { if (outfp) es_fclose (outfp); else if (outpipe[0] != -1) close (outpipe[0]); if (outpipe[1] != -1) close (outpipe[1]); return err; } } *pid = fork (); if (*pid == (pid_t)(-1)) { err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error (_("error forking process: %s\n"), gpg_strerror (err)); if (outfp) es_fclose (outfp); else if (outpipe[0] != -1) close (outpipe[0]); if (outpipe[1] != -1) close (outpipe[1]); if (errfp) es_fclose (errfp); else if (errpipe[0] != -1) close (errpipe[0]); if (errpipe[1] != -1) close (errpipe[1]); return err; } if (!*pid) { /* This is the child. */ gcry_control (GCRYCTL_TERM_SECMEM); es_fclose (outfp); es_fclose (errfp); do_exec (pgmname, argv, infd, outpipe[1], errpipe[1], preexec); /*NOTREACHED*/ } /* This is the parent. */ if (outpipe[1] != -1) close (outpipe[1]); if (errpipe[1] != -1) close (errpipe[1]); if (r_outfp) *r_outfp = outfp; if (r_errfp) *r_errfp = errfp; return 0; }
static inline gpg_error_t my_error (int errcode) { return gpg_err_make (default_errsource, errcode); }
/* 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); return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (desc) snprintf (line, DIM(line)-1, "SETDESC %s", desc); else snprintf (line, DIM(line)-1, "RESET"); line[DIM(line)-1] = 0; 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 (rc); if (ok) { snprintf (line, DIM(line)-1, "SETOK %s", ok); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (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)-1, "SETNOTOK %s", notok); line[DIM(line)-1] = 0; 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)-1, "SETCANCEL %s", notok); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } if (rc) return unlock_pinentry (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); return unlock_pinentry (rc); }
void Kleo::QGpgMERefreshKeysJob::slotStatus(GnuPGProcessBase *proc, const QString &type, const QStringList &args) { if(proc != mProcess) return; QStringList::const_iterator it = args.begin(); bool ok = false; if(type == "ERROR") { if(args.size() < 2) { kdDebug(5150) << "Kleo::QGpgMERefreshKeysJob::slotStatus() not recognising ERROR with < 2 args!" << endl; return; } const int source = (*++it).toInt(&ok); if(!ok) { kdDebug(5150) << "Kleo::QGpgMERefreshKeysJob::slotStatus() expected number for first ERROR arg, got something else" << endl; return; } ok = false; const int code = (*++it).toInt(&ok); if(!ok) { kdDebug(5150) << "Kleo::QGpgMERefreshKeysJob::slotStatus() expected number for second ERROR arg, got something else" << endl; return; } mError = gpg_err_make((gpg_err_source_t)source, (gpg_err_code_t)code); } else if(type == "PROGRESS") { if(args.size() < 4) { kdDebug(5150) << "Kleo::QGpgMERefreshKeysJob::slotStatus() not recognising PROGRESS with < 4 args!" << endl; return; } const QString what = *++it; ++it; // don't use "type"... const int cur = (*++it).toInt(&ok); if(!ok) { kdDebug(5150) << "Kleo::QGpgMERefreshKeysJob::slotStatus() expected number for \"cur\", got something else" << endl; return; } ok = false; const int total = (*++it).toInt(&ok); if(!ok) { kdDebug(5150) << "Kleo::QGpgMERefreshKeysJob::slotStatus() expected number for \"total\", got something else" << endl; return; } emit progress(QGpgMEProgressTokenMapper::instance()->map(what, 0, cur, total), cur, total); } }
/* Helper */ static 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)); }
GpgME::KeyListResult Kleo::HierarchicalKeyListJob::exec(const QStringList &, bool, std::vector<GpgME::Key> &keys) { keys.clear(); return GpgME::KeyListResult(gpg_err_make(GPG_ERR_SOURCE_GPGME, GPG_ERR_UNSUPPORTED_OPERATION)); }
/* Try to connect to the dirmngr via a socket. On platforms supporting it, start it up if needed. Returns a new assuan context at R_CTX or an error code. */ gpg_error_t start_new_dirmngr (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *homedir, const char *dirmngr_program, int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { gpg_error_t err; assuan_context_t ctx; const char *sockname; int did_success_msg = 0; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("error allocating assuan context: %s\n", gpg_strerror (err)); return err; } sockname = dirmngr_socket_name (); err = assuan_socket_connect (ctx, sockname, 0, 0); #ifdef USE_DIRMNGR_AUTO_START if (err) { lock_spawn_t lock; const char *argv[2]; /* With no success try start a new Dirmngr. On most systems this will fail because the Dirmngr is expected to be a system service. However on Wince we don't distinguish users and thus we can start it. A future extension might be to use the userv system to start the Dirmngr as a system service. */ if (!dirmngr_program || !*dirmngr_program) dirmngr_program = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR); if (verbose) log_info (_("no running Dirmngr - starting '%s'\n"), dirmngr_program); if (status_cb) status_cb (status_cb_arg, STATUS_PROGRESS, "starting_dirmngr ? 0 0", NULL); if (fflush (NULL)) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error flushing pending output: %s\n", strerror (errno)); assuan_release (ctx); return tmperr; } argv[0] = "--daemon"; argv[1] = NULL; if (!(err = lock_spawning (&lock, homedir, "dirmngr", verbose)) && assuan_socket_connect (ctx, sockname, 0, 0)) { err = gnupg_spawn_process_detached (dirmngr_program, argv,NULL); if (err) log_error ("failed to start the dirmngr '%s': %s\n", dirmngr_program, gpg_strerror (err)); else { int i; for (i=0; i < SECS_TO_WAIT_FOR_DIRMNGR; i++) { if (verbose) log_info (_("waiting for the dirmngr " "to come up ... (%ds)\n"), SECS_TO_WAIT_FOR_DIRMNGR - i); gnupg_sleep (1); err = assuan_socket_connect (ctx, sockname, 0, 0); if (!err) { if (verbose) { log_info (_("connection to the dirmngr" " established\n")); did_success_msg = 1; } break; } } } } unlock_spawning (&lock, "dirmngr"); } #else (void)homedir; (void)dirmngr_program; (void)verbose; (void)status_cb; (void)status_cb_arg; #endif /*USE_DIRMNGR_AUTO_START*/ if (err) { log_error ("connecting dirmngr at '%s' failed: %s\n", sockname, gpg_strerror (err)); assuan_release (ctx); return gpg_err_make (errsource, GPG_ERR_NO_DIRMNGR); } if (debug && !did_success_msg) log_debug (_("connection to the dirmngr established\n")); *r_ctx = ctx; return 0; }
static GPGRT_INLINE gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); }
/* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting. Returns a new assuan context at R_CTX or an error code. */ gpg_error_t start_new_gpg_agent (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *homedir, const char *agent_program, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { /* If we ever failed to connect via a socket we will force the use of the pipe based server for the lifetime of the process. */ static int force_pipe_server = 0; gpg_error_t err = 0; char *infostr, *p; assuan_context_t ctx; int did_success_msg = 0; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("error allocating assuan context: %s\n", gpg_strerror (err)); return err; } restart: infostr = force_pipe_server? NULL : getenv ("GPG_AGENT_INFO"); if (!infostr || !*infostr) { char *sockname; const char *argv[3]; pid_t pid; int excode; /* First check whether we can connect at the standard socket. */ sockname = make_filename (homedir, "S.gpg-agent", NULL); err = assuan_socket_connect (ctx, sockname, 0, 0); if (err) { /* With no success start a new server. */ if (!agent_program || !*agent_program) agent_program = gnupg_module_name (GNUPG_MODULE_NAME_AGENT); if (verbose) log_info (_("no running gpg-agent - starting '%s'\n"), agent_program); if (status_cb) status_cb (status_cb_arg, STATUS_PROGRESS, "starting_agent ? 0 0", NULL); if (fflush (NULL)) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error flushing pending output: %s\n", strerror (errno)); xfree (sockname); assuan_release (ctx); return tmperr; } argv[0] = "--use-standard-socket-p"; argv[1] = NULL; err = gnupg_spawn_process_fd (agent_program, argv, -1, -1, -1, &pid); if (err) log_debug ("starting '%s' for testing failed: %s\n", agent_program, gpg_strerror (err)); else if ((err = gnupg_wait_process (agent_program, pid, 1, &excode))) { if (excode == -1) log_debug ("running '%s' for testing failed (wait): %s\n", agent_program, gpg_strerror (err)); } gnupg_release_process (pid); if (!err && !excode) { /* If the agent has been configured for use with a standard socket, an environment variable is not required and thus we we can savely start the agent here. */ lock_spawn_t lock; argv[0] = "--daemon"; argv[1] = "--use-standard-socket"; argv[2] = NULL; if (!(err = lock_spawning (&lock, homedir, "agent", verbose)) && assuan_socket_connect (ctx, sockname, 0, 0)) { err = gnupg_spawn_process_detached (agent_program, argv,NULL); if (err) log_error ("failed to start agent '%s': %s\n", agent_program, gpg_strerror (err)); else { int i; for (i=0; i < SECS_TO_WAIT_FOR_AGENT; i++) { if (verbose) log_info (_("waiting for the agent " "to come up ... (%ds)\n"), SECS_TO_WAIT_FOR_AGENT - i); gnupg_sleep (1); err = assuan_socket_connect (ctx, sockname, 0, 0); if (!err) { if (verbose) { log_info (_("connection to agent " "established\n")); did_success_msg = 1; } break; } } } } unlock_spawning (&lock, "agent"); } else { /* If using the standard socket is not the default we start the agent as a pipe server which gives us most of the required features except for passphrase caching etc. */ const char *pgmname; int no_close_list[3]; int i; if ( !(pgmname = strrchr (agent_program, '/'))) pgmname = agent_program; else pgmname++; argv[0] = pgmname; argv[1] = "--server"; argv[2] = 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)); no_close_list[i] = -1; /* Connect to the agent and perform initial handshaking. */ err = assuan_pipe_connect (ctx, agent_program, argv, no_close_list, NULL, NULL, 0); } } xfree (sockname); } else { int prot; int pid; infostr = xstrdup (infostr); if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr) { log_error (_("malformed GPG_AGENT_INFO environment variable\n")); xfree (infostr); force_pipe_server = 1; goto restart; } *p++ = 0; pid = atoi (p); while (*p && *p != PATHSEP_C) p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { log_error (_("gpg-agent protocol version %d is not supported\n"), prot); xfree (infostr); force_pipe_server = 1; goto restart; } err = assuan_socket_connect (ctx, infostr, pid, 0); xfree (infostr); if (gpg_err_code (err) == GPG_ERR_ASS_CONNECT_FAILED) { log_info (_("can't connect to the agent - trying fall back\n")); force_pipe_server = 1; goto restart; } } if (err) { log_error ("can't connect to the agent: %s\n", gpg_strerror (err)); assuan_release (ctx); return gpg_err_make (errsource, GPG_ERR_NO_AGENT); } if (debug && !did_success_msg) log_debug (_("connection to agent established\n")); err = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (!err) err = send_pinentry_environment (ctx, errsource, opt_lc_ctype, opt_lc_messages, session_env); if (err) { assuan_release (ctx); return err; } *r_ctx = ctx; return 0; }
/* 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; }
/* 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. */ int agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *initial_errtext, struct pin_entry_info_s *pininfo) { int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; const char *errtext = NULL; int is_pin = 0; int saveflag; int close_button; 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); 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 = _("Please enter your PIN, so that the secret key " "can be unlocked for this session"); else if (!desc_text) desc_text = _("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; snprintf (line, DIM(line)-1, "SETDESC %s", desc_text); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); snprintf (line, DIM(line)-1, "SETPROMPT %s", prompt_text? prompt_text : is_pin? "PIN:" : "Passphrase:"); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (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 (); if (rc) return unlock_pinentry (rc); } if (initial_errtext) { snprintf (line, DIM(line)-1, "SETERROR %s", initial_errtext); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); } 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; 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)-1, _("SETERROR %s (try %d of %d)"), errtext, pininfo->failed_tries+1, pininfo->max_tries); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); errtext = NULL; } saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); close_button = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, inq_quality, entry_ctx, close_button_status_cb, &close_button); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* 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 (close_button && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? _("PIN too long") : _("Passphrase too long"); else if (rc) return unlock_pinentry (rc); if (!errtext && pininfo->min_digits) { /* do some basic checks on the entered PIN. */ if (!all_digitsp (pininfo->pin)) errtext = _("Invalid characters in PIN"); else if (pininfo->max_digits && strlen (pininfo->pin) > pininfo->max_digits) errtext = _("PIN too long"); else if (strlen (pininfo->pin) < pininfo->min_digits) errtext = _("PIN too short"); } if (!errtext && pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); if (rc == -1 && 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? _("Bad PIN") : _("Bad Passphrase")); else if (rc) return unlock_pinentry (rc); } if (!errtext) return unlock_pinentry (0); /* okay, got a PIN or passphrase */ } return unlock_pinentry (gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN : GPG_ERR_BAD_PASSPHRASE)); }
/* Fork and exec the PGMNAME, see exechelp.h for details. */ gpg_error_t gnupg_spawn_process (const char *pgmname, const char *argv[], gpg_err_source_t errsource, void (*preexec)(void), unsigned int flags, estream_t infp, estream_t *r_outfp, estream_t *r_errfp, pid_t *pid) { gpg_error_t err; SECURITY_ATTRIBUTES sec_attr; PROCESS_INFORMATION pi = { NULL, /* Returns process handle. */ 0, /* Returns primary thread handle. */ 0, /* Returns pid. */ 0 /* Returns tid. */ }; STARTUPINFO si; int cr_flags; char *cmdline; HANDLE inhandle = INVALID_HANDLE_VALUE; HANDLE outpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; HANDLE errpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; estream_t outfp = NULL; estream_t errfp = NULL; HANDLE nullhd[3] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; int i; es_syshd_t syshd; if (r_outfp) *r_outfp = NULL; if (r_errfp) *r_errfp = NULL; *pid = (pid_t)(-1); /* Always required. */ if (infp) { es_fflush (infp); es_rewind (infp); es_syshd (infp, &syshd); switch (syshd.type) { case ES_SYSHD_FD: inhandle = (HANDLE)_get_osfhandle (syshd.u.fd); break; case ES_SYSHD_SOCK: inhandle = (HANDLE)_get_osfhandle (syshd.u.sock); break; case ES_SYSHD_HANDLE: inhandle = syshd.u.handle; break; default: inhandle = INVALID_HANDLE_VALUE; break; } if (inhandle == INVALID_HANDLE_VALUE) return gpg_err_make (errsource, GPG_ERR_INV_VALUE); /* FIXME: In case we can't get a system handle (e.g. due to es_fopencookie we should create a piper and a feeder thread. */ } if (r_outfp) { if (create_inheritable_pipe (outpipe, 1)) { err = gpg_err_make (errsource, GPG_ERR_GENERAL); log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); return err; } syshd.type = ES_SYSHD_HANDLE; syshd.u.handle = outpipe[0]; outfp = es_sysopen (&syshd, "r"); if (!outfp) { err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error (_("error creating a stream for a pipe: %s\n"), gpg_strerror (err)); CloseHandle (outpipe[0]); CloseHandle (outpipe[1]); outpipe[0] = outpipe[1] = INVALID_HANDLE_VALUE; return err; } } if (r_errfp) { if (create_inheritable_pipe (errpipe, 1)) { err = gpg_err_make (errsource, GPG_ERR_GENERAL); log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); return err; } syshd.type = ES_SYSHD_HANDLE; syshd.u.handle = errpipe[0]; errfp = es_sysopen (&syshd, "r"); if (!errfp) { err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error (_("error creating a stream for a pipe: %s\n"), gpg_strerror (err)); CloseHandle (errpipe[0]); CloseHandle (errpipe[1]); errpipe[0] = errpipe[1] = INVALID_HANDLE_VALUE; if (outfp) es_fclose (outfp); else if (outpipe[0] != INVALID_HANDLE_VALUE) CloseHandle (outpipe[0]); if (outpipe[1] != INVALID_HANDLE_VALUE) CloseHandle (outpipe[1]); return err; } } /* Prepare security attributes. */ memset (&sec_attr, 0, sizeof sec_attr ); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; /* Build the command line. */ err = build_w32_commandline (pgmname, argv, &cmdline); if (err) return err; if (inhandle != INVALID_HANDLE_VALUE) nullhd[0] = w32_open_null (0); if (outpipe[1] != INVALID_HANDLE_VALUE) nullhd[1] = w32_open_null (0); if (errpipe[1] != INVALID_HANDLE_VALUE) nullhd[2] = w32_open_null (0); /* Start the process. Note that we can't run the PREEXEC function because this might change our own environment. */ (void)preexec; memset (&si, 0, sizeof si); si.cb = sizeof (si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; si.hStdInput = inhandle == INVALID_HANDLE_VALUE? nullhd[0] : inhandle; si.hStdOutput = outpipe[1] == INVALID_HANDLE_VALUE? nullhd[1] : outpipe[1]; si.hStdError = errpipe[1] == INVALID_HANDLE_VALUE? nullhd[2] : errpipe[1]; cr_flags = (CREATE_DEFAULT_ERROR_MODE | ((flags & 128)? DETACHED_PROCESS : 0) | GetPriorityClass (GetCurrentProcess ()) | CREATE_SUSPENDED); /* log_debug ("CreateProcess, path=`%s' cmdline=`%s'\n", pgmname, cmdline); */ if (!CreateProcess (pgmname, /* Program to start. */ cmdline, /* Command line arguments. */ &sec_attr, /* Process security attributes. */ &sec_attr, /* Thread security attributes. */ TRUE, /* Inherit handles. */ cr_flags, /* Creation flags. */ NULL, /* Environment. */ NULL, /* Use current drive/directory. */ &si, /* Startup information. */ &pi /* Returns process information. */ )) { log_error ("CreateProcess failed: %s\n", w32_strerror (-1)); xfree (cmdline); if (outfp) es_fclose (outfp); else if (outpipe[0] != INVALID_HANDLE_VALUE) CloseHandle (outpipe[0]); if (outpipe[1] != INVALID_HANDLE_VALUE) CloseHandle (outpipe[1]); if (errfp) es_fclose (errfp); else if (errpipe[0] != INVALID_HANDLE_VALUE) CloseHandle (errpipe[0]); if (errpipe[1] != INVALID_HANDLE_VALUE) CloseHandle (errpipe[1]); return gpg_err_make (errsource, GPG_ERR_GENERAL); } xfree (cmdline); cmdline = NULL; /* Close the inherited handles to /dev/null. */ for (i=0; i < DIM (nullhd); i++) if (nullhd[i] != INVALID_HANDLE_VALUE) CloseHandle (nullhd[i]); /* Close the inherited ends of the pipes. */ if (outpipe[1] != INVALID_HANDLE_VALUE) CloseHandle (outpipe[1]); if (errpipe[1] != INVALID_HANDLE_VALUE) CloseHandle (errpipe[1]); /* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */ /* " dwProcessID=%d dwThreadId=%d\n", */ /* pi.hProcess, pi.hThread, */ /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ /* log_debug (" outfp=%p errfp=%p\n", outfp, errfp); */ /* Fixme: For unknown reasons AllowSetForegroundWindow returns an invalid argument error if we pass it the correct processID. As a workaround we use -1 (ASFW_ANY). */ if ( (flags & 64) ) gnupg_allow_set_foregound_window ((pid_t)(-1)/*pi.dwProcessId*/); /* Process has been created suspended; resume it now. */ ResumeThread (pi.hThread); CloseHandle (pi.hThread); if (r_outfp) *r_outfp = outfp; if (r_errfp) *r_errfp = errfp; *pid = handle_to_pid (pi.hProcess); return 0; }
/* See exechelp.h for a description. */ gpg_error_t gnupg_wait_processes (const char **pgmnames, pid_t *pids, size_t count, int hang, int *r_exitcodes) { gpg_err_code_t ec = 0; size_t i, left; for (i = 0; i < count; i++) { if (r_exitcodes) r_exitcodes[i] = -1; if (pids[i] == (pid_t)(-1)) return my_error (GPG_ERR_INV_VALUE); } left = count; while (left > 0) { pid_t pid; int status; #ifdef USE_NPTH pid = npth_waitpid (-1, &status, hang ? 0 : WNOHANG); #else while ((pid = waitpid (-1, &status, hang ? 0 : WNOHANG)) == (pid_t)(-1) && errno == EINTR); #endif if (pid == (pid_t)(-1)) { ec = gpg_err_code_from_errno (errno); log_error (_("waiting for processes to terminate failed: %s\n"), strerror (errno)); break; } else if (!pid) { ec = GPG_ERR_TIMEOUT; /* Still running. */ break; } else { for (i = 0; i < count; i++) if (pid == pids[i]) break; if (i == count) /* No match, ignore this pid. */ continue; /* Process PIDS[i] died. */ left -= 1; if (WIFEXITED (status) && WEXITSTATUS (status) == 127) { log_error (_("error running '%s': probably not installed\n"), pgmnames[i]); ec = GPG_ERR_CONFIGURATION; } else if (WIFEXITED (status) && WEXITSTATUS (status)) { if (!r_exitcodes) log_error (_("error running '%s': exit status %d\n"), pgmnames[i], WEXITSTATUS (status)); else r_exitcodes[i] = WEXITSTATUS (status); ec = GPG_ERR_GENERAL; } else if (!WIFEXITED (status)) { log_error (_("error running '%s': terminated\n"), pgmnames[i]); ec = GPG_ERR_GENERAL; } else { if (r_exitcodes) r_exitcodes[i] = 0; } } } return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); }
/* See exechelp.h for a description. */ gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode) { gpg_err_code_t ec; HANDLE proc = fd_to_handle (pid); int code; DWORD exc; if (r_exitcode) *r_exitcode = -1; if (pid == (pid_t)(-1)) return gpg_error (GPG_ERR_INV_VALUE); /* FIXME: We should do a pth_waitpid here. However this has not yet been implemented. A special W32 pth system call would even be better. */ code = WaitForSingleObject (proc, hang? INFINITE : 0); switch (code) { case WAIT_TIMEOUT: ec = GPG_ERR_TIMEOUT; break; case WAIT_FAILED: log_error (_("waiting for process %d to terminate failed: %s\n"), (int)pid, w32_strerror (-1)); ec = GPG_ERR_GENERAL; break; case WAIT_OBJECT_0: if (!GetExitCodeProcess (proc, &exc)) { log_error (_("error getting exit code of process %d: %s\n"), (int)pid, w32_strerror (-1) ); ec = GPG_ERR_GENERAL; } else if (exc) { log_error (_("error running `%s': exit status %d\n"), pgmname, (int)exc ); if (r_exitcode) *r_exitcode = (int)exc; ec = GPG_ERR_GENERAL; } else { if (r_exitcode) *r_exitcode = 0; ec = 0; } break; default: log_error ("WaitForSingleObject returned unexpected " "code %d for pid %d\n", code, (int)pid ); ec = GPG_ERR_GENERAL; break; } return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); }
/* Ask for the passphrase using the supplied arguments. The returned passphrase needs to be freed by the caller. */ int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar) { int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; int saveflag; int close_button; *retpass = NULL; if (opt.batch) return gpg_error (GPG_ERR_BAD_PASSPHRASE); 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) { size_t size; size_t len = ASSUAN_LINELENGTH/2; unsigned char *buffer = gcry_malloc_secure (len); rc = pinentry_loopback(ctrl, "PASSPHRASE", &buffer, &size, len); if (rc) xfree(buffer); else { buffer[size] = 0; *retpass = buffer; } return rc; } return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (!prompt) prompt = desc && strstr (desc, "PIN")? "PIN": _("Passphrase"); if (desc) snprintf (line, DIM(line)-1, "SETDESC %s", desc); else snprintf (line, DIM(line)-1, "RESET"); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); snprintf (line, DIM(line)-1, "SETPROMPT %s", prompt); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); if (with_qualitybar && opt.min_passphrase_len) { rc = setup_qualitybar (); if (rc) return unlock_pinentry (rc); } if (errtext) { snprintf (line, DIM(line)-1, "SETERROR %s", errtext); line[DIM(line)-1] = 0; rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); } 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 (out_of_core ()); saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); close_button = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, inq_quality, entry_ctx, close_button_status_cb, &close_button); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* 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 (close_button && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); if (rc) xfree (parm.buffer); else *retpass = parm.buffer; return unlock_pinentry (rc); }