diff options
author | saturneric <[email protected]> | 2023-12-02 14:01:16 +0000 |
---|---|---|
committer | saturneric <[email protected]> | 2023-12-02 14:01:16 +0000 |
commit | 0ec3358e4cfce7ad242e851c450292a25619221e (patch) | |
tree | 28a50308678ccbe0e0820d367f363df7df4e0be2 /src/pinentry/pinentry.cpp | |
parent | fix: add set owner struct action in keymanager menu (diff) | |
download | GpgFrontend-0ec3358e4cfce7ad242e851c450292a25619221e.tar.gz GpgFrontend-0ec3358e4cfce7ad242e851c450292a25619221e.zip |
feat: add buddled qt pinentry and make it works
Diffstat (limited to '')
-rw-r--r-- | src/pinentry/pinentry.cpp | 1696 |
1 files changed, 1696 insertions, 0 deletions
diff --git a/src/pinentry/pinentry.cpp b/src/pinentry/pinentry.cpp new file mode 100644 index 00000000..918944b9 --- /dev/null +++ b/src/pinentry/pinentry.cpp @@ -0,0 +1,1696 @@ +/* pinentry.c - The PIN entry support library + * Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015, 2016, 2021 g10 Code GmbH + * + * This file is part of PINENTRY. + * + * PINENTRY is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * PINENTRY 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifndef WINDOWS +#include <errno.h> +#endif +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#ifndef WINDOWS +#include <sys/utsname.h> +#endif +#ifndef WINDOWS +#include <locale.h> +#endif +#include <limits.h> +#ifdef WINDOWS +#include <windows.h> +#endif + +#include <assuan.h> + +#include "argparse.h" +#include "password_cache.h" +#include "pinentry.h" +#include "secmem_util.h" + +#ifdef WINDOWS +#define getpid() GetCurrentProcessId() +#endif + +/* Keep the name of our program here. */ +static char this_pgmname[50]; + +struct pinentry pinentry; + +static const char *flavor_flag; + +/* Because gtk_init removes the --display arg from the command lines + * and our command line parser is called after gtk_init (so that it + * does not see gtk specific options) we don't have a way to get hold + * of the --display option. Our solution is to remember --display in + * the call to pinentry_have_display and set it then in our + * parser. */ +static char *remember_display; + +static void pinentry_reset(int use_defaults) { + /* GPG Agent sets these options once when it starts the pinentry. + Don't reset them. */ + int grab = pinentry.grab; + char *ttyname = pinentry.ttyname; + char *ttytype = pinentry.ttytype_l; + char *ttyalert = pinentry.ttyalert; + char *lc_ctype = pinentry.lc_ctype; + char *lc_messages = pinentry.lc_messages; + int allow_external_password_cache = pinentry.allow_external_password_cache; + char *default_ok = pinentry.default_ok; + char *default_cancel = pinentry.default_cancel; + char *default_prompt = pinentry.default_prompt; + char *default_pwmngr = pinentry.default_pwmngr; + char *default_cf_visi = pinentry.default_cf_visi; + char *default_tt_visi = pinentry.default_tt_visi; + char *default_tt_hide = pinentry.default_tt_hide; + char *default_capshint = pinentry.default_capshint; + char *touch_file = pinentry.touch_file; + unsigned long owner_pid = pinentry.owner_pid; + int owner_uid = pinentry.owner_uid; + char *owner_host = pinentry.owner_host; + int constraints_enforce = pinentry.constraints_enforce; + char *constraints_hint_short = pinentry.constraints_hint_short; + char *constraints_hint_long = pinentry.constraints_hint_long; + char *constraints_error_title = pinentry.constraints_error_title; + + /* These options are set from the command line. Don't reset + them. */ + int debug = pinentry.debug; + char *display = pinentry.display; + int parent_wid = pinentry.parent_wid; + + pinentry_color_t color_fg = pinentry.color_fg; + int color_fg_bright = pinentry.color_fg_bright; + pinentry_color_t color_bg = pinentry.color_bg; + pinentry_color_t color_so = pinentry.color_so; + int color_so_bright = pinentry.color_so_bright; + pinentry_color_t color_ok = pinentry.color_ok; + int color_ok_bright = pinentry.color_ok_bright; + pinentry_color_t color_qualitybar = pinentry.color_qualitybar; + int color_qualitybar_bright = pinentry.color_qualitybar_bright; + + int timeout = pinentry.timeout; + + char *invisible_char = pinentry.invisible_char; + + /* Free any allocated memory. */ + if (use_defaults) { + free(pinentry.ttyname); + free(pinentry.ttytype_l); + free(pinentry.ttyalert); + free(pinentry.lc_ctype); + free(pinentry.lc_messages); + free(pinentry.default_ok); + free(pinentry.default_cancel); + free(pinentry.default_prompt); + free(pinentry.default_pwmngr); + free(pinentry.default_cf_visi); + free(pinentry.default_tt_visi); + free(pinentry.default_tt_hide); + free(pinentry.default_capshint); + free(pinentry.touch_file); + free(pinentry.owner_host); + free(pinentry.display); + free(pinentry.constraints_hint_short); + free(pinentry.constraints_hint_long); + free(pinentry.constraints_error_title); + } + + free(pinentry.title); + free(pinentry.description); + free(pinentry.error); + free(pinentry.prompt); + free(pinentry.ok); + free(pinentry.notok); + free(pinentry.cancel); + secmem_free(pinentry.pin); + free(pinentry.repeat_passphrase); + free(pinentry.repeat_error_string); + free(pinentry.quality_bar); + free(pinentry.quality_bar_tt); + free(pinentry.formatted_passphrase_hint); + free(pinentry.keyinfo); + free(pinentry.specific_err_info); + + /* Reset the pinentry structure. */ + memset(&pinentry, 0, sizeof(pinentry)); + + /* Restore options without a default we want to preserve. */ + pinentry.invisible_char = invisible_char; + + /* Restore other options or set defaults. */ + + if (use_defaults) { + /* Pinentry timeout in seconds. */ + pinentry.timeout = 60; + + /* Global grab. */ + pinentry.grab = 1; + + pinentry.color_fg = PINENTRY_COLOR_DEFAULT; + pinentry.color_fg_bright = 0; + pinentry.color_bg = PINENTRY_COLOR_DEFAULT; + pinentry.color_so = PINENTRY_COLOR_DEFAULT; + pinentry.color_so_bright = 0; + pinentry.color_ok = PINENTRY_COLOR_DEFAULT; + pinentry.color_ok_bright = 0; + pinentry.color_qualitybar = PINENTRY_COLOR_DEFAULT; + pinentry.color_qualitybar_bright = 0; + + pinentry.owner_uid = -1; + } else /* Restore the options. */ + { + pinentry.grab = grab; + pinentry.ttyname = ttyname; + pinentry.ttytype_l = ttytype; + pinentry.ttyalert = ttyalert; + pinentry.lc_ctype = lc_ctype; + pinentry.lc_messages = lc_messages; + pinentry.allow_external_password_cache = allow_external_password_cache; + pinentry.default_ok = default_ok; + pinentry.default_cancel = default_cancel; + pinentry.default_prompt = default_prompt; + pinentry.default_pwmngr = default_pwmngr; + pinentry.default_cf_visi = default_cf_visi; + pinentry.default_tt_visi = default_tt_visi; + pinentry.default_tt_hide = default_tt_hide; + pinentry.default_capshint = default_capshint; + pinentry.touch_file = touch_file; + pinentry.owner_pid = owner_pid; + pinentry.owner_uid = owner_uid; + pinentry.owner_host = owner_host; + pinentry.constraints_enforce = constraints_enforce; + pinentry.constraints_hint_short = constraints_hint_short; + pinentry.constraints_hint_long = constraints_hint_long; + pinentry.constraints_error_title = constraints_error_title; + + pinentry.debug = debug; + pinentry.display = display; + pinentry.parent_wid = parent_wid; + + pinentry.color_fg = color_fg; + pinentry.color_fg_bright = color_fg_bright; + pinentry.color_bg = color_bg; + pinentry.color_so = color_so; + pinentry.color_so_bright = color_so_bright; + pinentry.color_ok = color_ok; + pinentry.color_ok_bright = color_ok_bright; + pinentry.color_qualitybar = color_qualitybar; + pinentry.color_qualitybar_bright = color_qualitybar_bright; + + pinentry.timeout = timeout; + } +} + +static gpg_error_t pinentry_assuan_reset_handler(assuan_context_t ctx, + char *line) { + (void)ctx; + (void)line; + + pinentry_reset(0); + + return 0; +} + +/* Copy TEXT or TEXTLEN to BUFFER and escape as required. Return a + pointer to the end of the new buffer. Note that BUFFER must be + large enough to keep the entire text; allocataing it 3 times of + TEXTLEN is sufficient. */ +static char *copy_and_escape(char *buffer, const void *text, size_t textlen) { + int i; + const unsigned char *s = (unsigned char *)text; + char *p = buffer; + + for (i = 0; i < textlen; i++) { + if (s[i] < ' ' || s[i] == '+') { + snprintf(p, 4, "%%%02X", s[i]); + p += 3; + } else if (s[i] == ' ') + *p++ = '+'; + else + *p++ = s[i]; + } + return p; +} + +/* Perform percent unescaping in STRING and return the new valid length + of the string. A terminating Nul character is inserted at the end of + the unescaped string. + */ +static size_t do_unescape_inplace(char *s) { + unsigned char *p, *p0; + + p = p0 = (unsigned char *)s; + while (*s) { + if (*s == '%' && s[1] && s[2]) { + s++; + *p++ = xtoi_2(s); + s += 2; + } else + *p++ = *s++; + } + *p = 0; + + return (p - p0); +} + +/* Return a malloced copy of the commandline for PID. If this is not + * possible NULL is returned. */ +#ifndef WINDOWS +static char *get_cmdline(unsigned long pid) { + char buffer[200]; + FILE *fp; + size_t i, n; + + snprintf(buffer, sizeof buffer, "/proc/%lu/cmdline", pid); + + fp = fopen(buffer, "rb"); + if (!fp) return NULL; + n = fread(buffer, 1, sizeof buffer - 1, fp); + if (n < sizeof buffer - 1 && ferror(fp)) { + /* Some error occurred. */ + fclose(fp); + return NULL; + } + fclose(fp); + if (n == 0) return NULL; + /* Arguments are delimited by Nuls. We should do proper quoting but + * that can be a bit complicated, thus we simply replace the Nuls by + * spaces. */ + for (i = 0; i < n; i++) + if (!buffer[i] && i < n - 1) buffer[i] = ' '; + buffer[i] = 0; /* Make sure the last byte is the string terminator. */ + + return strdup(buffer); +} +#endif /*!WINDOWS*/ + +/* Atomically ask the kernel for information about process PID. + * Return a malloc'ed copy of the process name as long as the process + * uid matches UID. If it cannot determine that the process has uid + * UID, it returns NULL. + * + * This is not as informative as get_cmdline, but it verifies that the + * process does belong to the user in question. + */ +#ifndef WINDOWS +static char *get_pid_name_for_uid(unsigned long pid, int uid) { + char buffer[400]; + FILE *fp; + size_t end, n; + char *uidstr; + + snprintf(buffer, sizeof buffer, "/proc/%lu/status", pid); + + fp = fopen(buffer, "rb"); + if (!fp) return NULL; + n = fread(buffer, 1, sizeof buffer - 1, fp); + if (n < sizeof buffer - 1 && ferror(fp)) { + /* Some error occurred. */ + fclose(fp); + return NULL; + } + fclose(fp); + if (n == 0) return NULL; + buffer[n] = 0; + /* Fixme: Is it specified that "Name" is always the first line? For + * robustness I would prefer to have a real parser here. -wk */ + if (strncmp(buffer, "Name:\t", 6)) return NULL; + end = strcspn(buffer + 6, "\n") + 6; + buffer[end] = 0; + + /* check that uid matches what we expect */ + uidstr = strstr(buffer + end + 1, "\nUid:\t"); + if (!uidstr) return NULL; + if (atoi(uidstr + 6) != uid) return NULL; + + return strdup(buffer + 6); +} +#endif /*!WINDOWS*/ + +const char *pinentry_get_pgmname(void) { return this_pgmname; } + +/* Return a malloced string with the title. The caller mus free the + * string. If no title is available or the title string has an error + * NULL is returned. */ +char *pinentry_get_title(pinentry_t pe) { + char *title; + + if (pe->title) title = strdup(pe->title); +#ifndef WINDOWS + else if (pe->owner_pid) { + char buf[200]; + struct utsname utsbuf; + char *pidname = NULL; + char *cmdline = NULL; + + if (pe->owner_host && !uname(&utsbuf) && + !strcmp(utsbuf.nodename, pe->owner_host)) { + pidname = get_pid_name_for_uid(pe->owner_pid, pe->owner_uid); + if (pidname) cmdline = get_cmdline(pe->owner_pid); + } + + if (pe->owner_host && (cmdline || pidname)) + snprintf(buf, sizeof buf, "[%lu]@%s (%s)", pe->owner_pid, pe->owner_host, + cmdline ? cmdline : pidname); + else if (pe->owner_host) + snprintf(buf, sizeof buf, "[%lu]@%s", pe->owner_pid, pe->owner_host); + else + snprintf(buf, sizeof buf, "[%lu] <unknown host>", pe->owner_pid); + free(pidname); + free(cmdline); + title = strdup(buf); + } +#endif /*!WINDOWS*/ + else + title = strdup(this_pgmname); + + return title; +} + +/* Run a quality inquiry for PASSPHRASE of LENGTH. (We need LENGTH + because not all backends might be able to return a proper + C-string.). Returns: A value between -100 and 100 to give an + estimate of the passphrase's quality. Negative values are use if + the caller won't even accept that passphrase. Note that we expect + just one data line which should not be escaped in any represent a + numeric signed decimal value. Extra data is currently ignored but + should not be send at all. */ +int pinentry_inq_quality(pinentry_t pin, const char *passphrase, + size_t length) { + assuan_context_t ctx = (assuan_context_t)pin->ctx_assuan; + const char prefix[] = "INQUIRE QUALITY "; + char *command; + char *line; + size_t linelen; + int gotvalue = 0; + int value = 0; + int rc; + + if (!ctx) return 0; /* Can't run the callback. */ + + if (length > 300) + length = 300; /* Limit so that it definitely fits into an Assuan + line. */ + + command = (char *)secmem_malloc(strlen(prefix) + 3 * length + 1); + if (!command) return 0; + strcpy(command, prefix); + copy_and_escape(command + strlen(command), passphrase, length); + rc = assuan_write_line(ctx, command); + secmem_free(command); + if (rc) { + fprintf(stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); + return 0; + } + + for (;;) { + do { + rc = assuan_read_line(ctx, &line, &linelen); + if (rc) { + fprintf(stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); + return 0; + } + } while (*line == '#' || !linelen); + if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && + (!line[3] || line[3] == ' ')) + break; /* END command received*/ + if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' && + (!line[3] || line[3] == ' ')) + break; /* CAN command received*/ + if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && + (!line[3] || line[3] == ' ')) + break; /* ERR command received*/ + if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue; + gotvalue = 1; + value = atoi(line + 2); + } + if (value < -100) + value = -100; + else if (value > 100) + value = 100; + + return value; +} + +/* Run a checkpin inquiry */ +char *pinentry_inq_checkpin(pinentry_t pin, const char *passphrase, + size_t length) { + assuan_context_t ctx = (assuan_context_t)pin->ctx_assuan; + const char prefix[] = "INQUIRE CHECKPIN "; + char *command; + char *line; + size_t linelen; + int gotvalue = 0; + char *value = NULL; + int rc; + + if (!ctx) return 0; /* Can't run the callback. */ + + if (length > 300) + length = 300; /* Limit so that it definitely fits into an Assuan + line. */ + + command = (char *)secmem_malloc(strlen(prefix) + 3 * length + 1); + if (!command) return 0; + strcpy(command, prefix); + copy_and_escape(command + strlen(command), passphrase, length); + rc = assuan_write_line(ctx, command); + secmem_free(command); + if (rc) { + fprintf(stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); + return 0; + } + + for (;;) { + do { + rc = assuan_read_line(ctx, &line, &linelen); + if (rc) { + fprintf(stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); + return 0; + } + } while (*line == '#' || !linelen); + if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && + (!line[3] || line[3] == ' ')) + break; /* END command received*/ + if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' && + (!line[3] || line[3] == ' ')) + break; /* CAN command received*/ + if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && + (!line[3] || line[3] == ' ')) + break; /* ERR command received*/ + if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue; + gotvalue = 1; + value = strdup(line + 2); + } + + return value; +} + +/* Run a genpin inquiry */ +char *pinentry_inq_genpin(pinentry_t pin) { + assuan_context_t ctx = (assuan_context_t)pin->ctx_assuan; + const char prefix[] = "INQUIRE GENPIN"; + char *line; + size_t linelen; + int gotvalue = 0; + char *value = NULL; + int rc; + + if (!ctx) return 0; /* Can't run the callback. */ + + rc = assuan_write_line(ctx, prefix); + if (rc) { + fprintf(stderr, "ASSUAN WRITE LINE failed: rc=%d\n", rc); + return 0; + } + + for (;;) { + do { + rc = assuan_read_line(ctx, &line, &linelen); + if (rc) { + fprintf(stderr, "ASSUAN READ LINE failed: rc=%d\n", rc); + free(value); + return 0; + } + } while (*line == '#' || !linelen); + if (line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && + (!line[3] || line[3] == ' ')) + break; /* END command received*/ + if (line[0] == 'C' && line[1] == 'A' && line[2] == 'N' && + (!line[3] || line[3] == ' ')) + break; /* CAN command received*/ + if (line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && + (!line[3] || line[3] == ' ')) + break; /* ERR command received*/ + if (line[0] != 'D' || line[1] != ' ' || linelen < 3 || gotvalue) continue; + gotvalue = 1; + value = strdup(line + 2); + } + + return value; +} + +/* Try to make room for at least LEN bytes in the pinentry. Returns + new buffer on success and 0 on failure or when the old buffer is + sufficient. */ +char *pinentry_setbufferlen(pinentry_t pin, int len) { + char *newp; + + if (pin->pin_len) + assert(pin->pin); + else + assert(!pin->pin); + + if (len < 2048) len = 2048; + + if (len <= pin->pin_len) return pin->pin; + + newp = (char *)secmem_realloc(pin->pin, len); + if (newp) { + pin->pin = newp; + pin->pin_len = len; + } else { + secmem_free(pin->pin); + pin->pin = 0; + pin->pin_len = 0; + } + return newp; +} + +static void pinentry_setbuffer_clear(pinentry_t pin) { + if (!pin->pin) { + assert(pin->pin_len == 0); + return; + } + + assert(pin->pin_len > 0); + + secmem_free(pin->pin); + pin->pin = NULL; + pin->pin_len = 0; +} + +static void pinentry_setbuffer_init(pinentry_t pin) { + pinentry_setbuffer_clear(pin); + pinentry_setbufferlen(pin, 0); +} + +/* passphrase better be alloced with secmem_alloc. */ +void pinentry_setbuffer_use(pinentry_t pin, char *passphrase, int len) { + if (!passphrase) { + assert(len == 0); + pinentry_setbuffer_clear(pin); + + return; + } + + if (passphrase && len == 0) len = strlen(passphrase) + 1; + + if (pin->pin) secmem_free(pin->pin); + + pin->pin = passphrase; + pin->pin_len = len; +} + +static struct assuan_malloc_hooks assuan_malloc_hooks = { + secmem_malloc, secmem_realloc, secmem_free}; + +/* Initialize the secure memory subsystem, drop privileges and return. + Must be called early. */ +void pinentry_init(const char *pgmname) { + /* Store away our name. */ + if (strlen(pgmname) > sizeof this_pgmname - 2) abort(); + strcpy(this_pgmname, pgmname); + + gpgrt_check_version(NULL); + + /* Initialize secure memory. 1 is too small, so the default size + will be used. */ + secmem_init(1); + secmem_set_flags(SECMEM_WARN); + drop_privs(); + + if (atexit(secmem_term)) { + /* FIXME: Could not register at-exit function, bail out. */ + } + + assuan_set_malloc_hooks(&assuan_malloc_hooks); +} + +/* Simple test to check whether DISPLAY is set or the option --display + was given. Used to decide whether the GUI or curses should be + initialized. */ +int pinentry_have_display(int argc, char **argv) { + int found = 0; + + for (; argc; argc--, argv++) { + if (!strcmp(*argv, "--display")) { + if (argv[1] && !remember_display) { + remember_display = strdup(argv[1]); + if (!remember_display) { +#ifndef WINDOWS + fprintf(stderr, "%s: %s\n", this_pgmname, strerror(errno)); +#endif + exit(EXIT_FAILURE); + } + } + found = 1; + break; + } else if (!strncmp(*argv, "--display=", 10)) { + if (!remember_display) { + remember_display = strdup(*argv + 10); + if (!remember_display) { +#ifndef WINDOWS + fprintf(stderr, "%s: %s\n", this_pgmname, strerror(errno)); +#endif + exit(EXIT_FAILURE); + } + } + found = 1; + break; + } + } + +#ifndef WINDOWS + { + const char *s; + s = getenv("DISPLAY"); + if (s && *s) found = 1; + } +#endif + + return found; +} + +/* Print usage information and and provide strings for help. */ +static const char *my_strusage(int level) { + const char *p; + + switch (level) { + case 11: + p = this_pgmname; + break; + case 12: + p = "pinentry"; + break; + case 13: + p = 0; + break; + case 14: + p = "Copyright (C) 2023 Saturneric"; + break; + case 19: + p = "Please report bugs to <[email protected]>.\n"; + break; + case 1: + case 40: { + static char *str; + + if (!str) { + size_t n = 50 + strlen(this_pgmname); + str = static_cast<char *>(malloc(n)); + if (str) { + snprintf(str, n, "Usage: %s [options] (-h for help)", this_pgmname); + } + } + p = str; + } break; + case 41: + p = "Ask securely for a secret and print it to stdout."; + break; + + case 42: + p = "1"; /* Flag print 40 as part of 41. */ + break; + + default: + p = NULL; + break; + } + return p; +} + +char *parse_color(char *arg, pinentry_color_t *color_p, int *bright_p) { + static struct { + const char *name; + pinentry_color_t color; + } colors[] = { + {"none", PINENTRY_COLOR_NONE}, {"default", PINENTRY_COLOR_DEFAULT}, + {"black", PINENTRY_COLOR_BLACK}, {"red", PINENTRY_COLOR_RED}, + {"green", PINENTRY_COLOR_GREEN}, {"yellow", PINENTRY_COLOR_YELLOW}, + {"blue", PINENTRY_COLOR_BLUE}, {"magenta", PINENTRY_COLOR_MAGENTA}, + {"cyan", PINENTRY_COLOR_CYAN}, {"white", PINENTRY_COLOR_WHITE}}; + + int i; + char *new_arg; + pinentry_color_t color = PINENTRY_COLOR_DEFAULT; + + if (!arg) return NULL; + + new_arg = strchr(arg, ','); + if (new_arg) new_arg++; + + if (bright_p) { + const char *bname[] = {"bright-", "bright", "bold-", "bold"}; + + *bright_p = 0; + for (i = 0; i < sizeof(bname) / sizeof(bname[0]); i++) + if (!strncasecmp(arg, bname[i], strlen(bname[i]))) { + *bright_p = 1; + arg += strlen(bname[i]); + } + } + + for (i = 0; i < sizeof(colors) / sizeof(colors[0]); i++) + if (!strncasecmp(arg, colors[i].name, strlen(colors[i].name))) + color = colors[i].color; + + *color_p = color; + return new_arg; +} + +/* Parse the command line options. May exit the program if only help + or version output is requested. */ +void pinentry_parse_opts(int argc, char *argv[]) { + static ARGPARSE_OPTS opts[] = { + ARGPARSE_s_n('d', "debug", "Turn on debugging output"), + ARGPARSE_s_s('D', "display", "|DISPLAY|Set the X display"), + ARGPARSE_s_s('T', "ttyname", "|FILE|Set the tty terminal node name"), + ARGPARSE_s_s('N', "ttytype", "|NAME|Set the tty terminal type"), + ARGPARSE_s_s('C', "lc-ctype", "|STRING|Set the tty LC_CTYPE value"), + ARGPARSE_s_s('M', "lc-messages", "|STRING|Set the tty LC_MESSAGES value"), + ARGPARSE_s_i('o', "timeout", + "|SECS|Timeout waiting for input after this many seconds"), + ARGPARSE_s_n('g', "no-global-grab", + "Grab keyboard only while window is focused"), + ARGPARSE_s_u('W', "parent-wid", "Parent window ID (for positioning)"), + ARGPARSE_s_s('c', "colors", "|STRING|Set custom colors for ncurses"), + ARGPARSE_s_s('a', "ttyalert", + "|STRING|Set the alert mode (none, beep or flash)"), + ARGPARSE_end()}; + ARGPARSE_ARGS pargs = {&argc, &argv, 0}; + + set_strusage(my_strusage); + + pinentry_reset(1); + + while (arg_parse(&pargs, opts)) { + switch (pargs.r_opt) { + case 'd': + pinentry.debug = 1; + break; + case 'g': + pinentry.grab = 0; + break; + + case 'D': + /* Note, this is currently not used because the GUI engine + has already been initialized when parsing these options. */ + pinentry.display = strdup(pargs.r.ret_str); + if (!pinentry.display) { +#ifndef WINDOWS + fprintf(stderr, "%s: %s\n", this_pgmname, strerror(errno)); +#endif + exit(EXIT_FAILURE); + } + break; + case 'T': + pinentry.ttyname = strdup(pargs.r.ret_str); + if (!pinentry.ttyname) { +#ifndef WINDOWS + fprintf(stderr, "%s: %s\n", this_pgmname, strerror(errno)); +#endif + exit(EXIT_FAILURE); + } + break; + case 'N': + pinentry.ttytype_l = strdup(pargs.r.ret_str); + if (!pinentry.ttytype_l) { +#ifndef WINDOWS + fprintf(stderr, "%s: %s\n", this_pgmname, strerror(errno)); +#endif + exit(EXIT_FAILURE); + } + break; + case 'C': + pinentry.lc_ctype = strdup(pargs.r.ret_str); + if (!pinentry.lc_ctype) { +#ifndef WINDOWS + fprintf(stderr, "%s: %s\n", this_pgmname, strerror(errno)); +#endif + exit(EXIT_FAILURE); + } + break; + case 'M': + pinentry.lc_messages = strdup(pargs.r.ret_str); + if (!pinentry.lc_messages) { +#ifndef WINDOWS + fprintf(stderr, "%s: %s\n", this_pgmname, strerror(errno)); +#endif + exit(EXIT_FAILURE); + } + break; + case 'W': + pinentry.parent_wid = pargs.r.ret_ulong; + break; + + case 'c': { + char *tmpstr = pargs.r.ret_str; + + tmpstr = + parse_color(tmpstr, &pinentry.color_fg, &pinentry.color_fg_bright); + tmpstr = parse_color(tmpstr, &pinentry.color_bg, NULL); + tmpstr = + parse_color(tmpstr, &pinentry.color_so, &pinentry.color_so_bright); + tmpstr = + parse_color(tmpstr, &pinentry.color_ok, &pinentry.color_ok_bright); + tmpstr = parse_color(tmpstr, &pinentry.color_qualitybar, + &pinentry.color_qualitybar_bright); + } break; + + case 'o': + pinentry.timeout = pargs.r.ret_int; + break; + + case 'a': + pinentry.ttyalert = strdup(pargs.r.ret_str); + if (!pinentry.ttyalert) { +#ifndef WINDOWS + fprintf(stderr, "%s: %s\n", this_pgmname, strerror(errno)); +#endif + exit(EXIT_FAILURE); + } + break; + + default: + pargs.err = ARGPARSE_PRINT_WARNING; + break; + } + } + + if (!pinentry.display && remember_display) { + pinentry.display = remember_display; + remember_display = NULL; + } +} + +/* Set the optional flag used with getinfo. */ +void pinentry_set_flavor_flag(const char *string) { flavor_flag = string; } + +static gpg_error_t option_handler(assuan_context_t ctx, const char *key, + const char *value) { + (void)ctx; + + if (!strcmp(key, "no-grab") && !*value) + pinentry.grab = 0; + else if (!strcmp(key, "grab") && !*value) + pinentry.grab = 1; + else if (!strcmp(key, "debug-wait")) { +#ifndef WINDOWS + fprintf(stderr, "%s: waiting for debugger - my pid is %u ...\n", + this_pgmname, (unsigned int)getpid()); + sleep(*value ? atoi(value) : 5); + fprintf(stderr, "%s: ... okay\n", this_pgmname); +#endif + } else if (!strcmp(key, "display")) { + if (pinentry.display) free(pinentry.display); + pinentry.display = strdup(value); + if (!pinentry.display) return gpg_error_from_syserror(); + } else if (!strcmp(key, "ttyname")) { + if (pinentry.ttyname) free(pinentry.ttyname); + pinentry.ttyname = strdup(value); + if (!pinentry.ttyname) return gpg_error_from_syserror(); + } else if (!strcmp(key, "ttytype")) { + if (pinentry.ttytype_l) free(pinentry.ttytype_l); + pinentry.ttytype_l = strdup(value); + if (!pinentry.ttytype_l) return gpg_error_from_syserror(); + } else if (!strcmp(key, "ttyalert")) { + if (pinentry.ttyalert) free(pinentry.ttyalert); + pinentry.ttyalert = strdup(value); + if (!pinentry.ttyalert) return gpg_error_from_syserror(); + } else if (!strcmp(key, "lc-ctype")) { + if (pinentry.lc_ctype) free(pinentry.lc_ctype); + pinentry.lc_ctype = strdup(value); + if (!pinentry.lc_ctype) return gpg_error_from_syserror(); + } else if (!strcmp(key, "lc-messages")) { + if (pinentry.lc_messages) free(pinentry.lc_messages); + pinentry.lc_messages = strdup(value); + if (!pinentry.lc_messages) return gpg_error_from_syserror(); + } else if (!strcmp(key, "owner")) { + long along; + char *endp; + + free(pinentry.owner_host); + pinentry.owner_host = NULL; + pinentry.owner_uid = -1; + pinentry.owner_pid = 0; + + errno = 0; + along = strtol(value, &endp, 10); + if (along && !errno) { + pinentry.owner_pid = (unsigned long)along; + if (*endp) { + errno = 0; + if (*endp == '/') { /* we have a uid */ + endp++; + along = strtol(endp, &endp, 10); + if (along >= 0 && !errno) pinentry.owner_uid = (int)along; + } + if (endp) { + while (*endp == ' ') endp++; + if (*endp) { + pinentry.owner_host = strdup(endp); + for (endp = pinentry.owner_host; *endp && *endp != ' '; endp++) + ; + *endp = 0; + } + } + } + } + } else if (!strcmp(key, "parent-wid")) { + pinentry.parent_wid = atoi(value); + /* FIXME: Use strtol and add some error handling. */ + } else if (!strcmp(key, "touch-file")) { + if (pinentry.touch_file) free(pinentry.touch_file); + pinentry.touch_file = strdup(value); + if (!pinentry.touch_file) return gpg_error_from_syserror(); + } else if (!strcmp(key, "default-ok")) { + pinentry.default_ok = strdup(value); + if (!pinentry.default_ok) return gpg_error_from_syserror(); + } else if (!strcmp(key, "default-cancel")) { + pinentry.default_cancel = strdup(value); + if (!pinentry.default_cancel) return gpg_error_from_syserror(); + } else if (!strcmp(key, "default-prompt")) { + pinentry.default_prompt = strdup(value); + if (!pinentry.default_prompt) return gpg_error_from_syserror(); + } else if (!strcmp(key, "default-pwmngr")) { + pinentry.default_pwmngr = strdup(value); + if (!pinentry.default_pwmngr) return gpg_error_from_syserror(); + } else if (!strcmp(key, "default-cf-visi")) { + pinentry.default_cf_visi = strdup(value); + if (!pinentry.default_cf_visi) return gpg_error_from_syserror(); + } else if (!strcmp(key, "default-tt-visi")) { + pinentry.default_tt_visi = strdup(value); + if (!pinentry.default_tt_visi) return gpg_error_from_syserror(); + } else if (!strcmp(key, "default-tt-hide")) { + pinentry.default_tt_hide = strdup(value); + if (!pinentry.default_tt_hide) return gpg_error_from_syserror(); + } else if (!strcmp(key, "default-capshint")) { + pinentry.default_capshint = strdup(value); + if (!pinentry.default_capshint) return gpg_error_from_syserror(); + } else if (!strcmp(key, "allow-external-password-cache") && !*value) { + pinentry.allow_external_password_cache = 1; + pinentry.tried_password_cache = 0; + } else if (!strcmp(key, "allow-emacs-prompt") && !*value) { +#ifdef INSIDE_EMACS + pinentry_enable_emacs_cmd_handler(); +#endif + } else if (!strcmp(key, "invisible-char")) { + if (pinentry.invisible_char) free(pinentry.invisible_char); + pinentry.invisible_char = strdup(value); + if (!pinentry.invisible_char) return gpg_error_from_syserror(); + } else if (!strcmp(key, "formatted-passphrase") && !*value) { + pinentry.formatted_passphrase = 1; + } else if (!strcmp(key, "formatted-passphrase-hint")) { + if (pinentry.formatted_passphrase_hint) + free(pinentry.formatted_passphrase_hint); + pinentry.formatted_passphrase_hint = strdup(value); + if (!pinentry.formatted_passphrase_hint) return gpg_error_from_syserror(); + do_unescape_inplace(pinentry.formatted_passphrase_hint); + } else if (!strcmp(key, "constraints-enforce") && !*value) + pinentry.constraints_enforce = 1; + else if (!strcmp(key, "constraints-hint-short")) { + if (pinentry.constraints_hint_short) free(pinentry.constraints_hint_short); + pinentry.constraints_hint_short = strdup(value); + if (!pinentry.constraints_hint_short) return gpg_error_from_syserror(); + do_unescape_inplace(pinentry.constraints_hint_short); + } else if (!strcmp(key, "constraints-hint-long")) { + if (pinentry.constraints_hint_long) free(pinentry.constraints_hint_long); + pinentry.constraints_hint_long = strdup(value); + if (!pinentry.constraints_hint_long) return gpg_error_from_syserror(); + do_unescape_inplace(pinentry.constraints_hint_long); + } else if (!strcmp(key, "constraints-error-title")) { + if (pinentry.constraints_error_title) + free(pinentry.constraints_error_title); + pinentry.constraints_error_title = strdup(value); + if (!pinentry.constraints_error_title) return gpg_error_from_syserror(); + do_unescape_inplace(pinentry.constraints_error_title); + } else + return gpg_error(GPG_ERR_UNKNOWN_OPTION); + return 0; +} + +/* Note, that it is sufficient to allocate the target string D as + long as the source string S, i.e.: strlen(s)+1; */ +static void strcpy_escaped(char *d, const char *s) { + while (*s) { + if (*s == '%' && s[1] && s[2]) { + s++; + *d++ = xtoi_2(s); + s += 2; + } else + *d++ = *s++; + } + *d = 0; +} + +static void write_status_error(assuan_context_t ctx, pinentry_t pe) { + char buf[500]; + const char *pgm; + + pgm = strchr(this_pgmname, '-'); + if (pgm && pgm[1]) + pgm++; + else + pgm = this_pgmname; + + snprintf(buf, sizeof buf, "%s.%s %d %s", pgm, + pe->specific_err_loc ? pe->specific_err_loc : "?", pe->specific_err, + pe->specific_err_info ? pe->specific_err_info : ""); + assuan_write_status(ctx, "ERROR", buf); +} + +static gpg_error_t cmd_setdesc(assuan_context_t ctx, char *line) { + char *newd; + + (void)ctx; + + newd = (char *)malloc(strlen(line) + 1); + if (!newd) return gpg_error_from_syserror(); + + strcpy_escaped(newd, line); + if (pinentry.description) free(pinentry.description); + pinentry.description = newd; + return 0; +} + +static gpg_error_t cmd_setprompt(assuan_context_t ctx, char *line) { + char *newp; + + (void)ctx; + + newp = (char *)malloc(strlen(line) + 1); + if (!newp) return gpg_error_from_syserror(); + + strcpy_escaped(newp, line); + if (pinentry.prompt) free(pinentry.prompt); + pinentry.prompt = newp; + return 0; +} + +/* The data provided at LINE may be used by pinentry implementations + to identify a key for caching strategies of its own. The empty + string and --clear mean that the key does not have a stable + identifier. */ +static gpg_error_t cmd_setkeyinfo(assuan_context_t ctx, char *line) { + (void)ctx; + + if (pinentry.keyinfo) free(pinentry.keyinfo); + + if (*line && strcmp(line, "--clear") != 0) + pinentry.keyinfo = strdup(line); + else + pinentry.keyinfo = NULL; + + return 0; +} + +static gpg_error_t cmd_setrepeat(assuan_context_t ctx, char *line) { + char *p; + + (void)ctx; + + p = (char *)malloc(strlen(line) + 1); + if (!p) return gpg_error_from_syserror(); + + strcpy_escaped(p, line); + free(pinentry.repeat_passphrase); + pinentry.repeat_passphrase = p; + return 0; +} + +static gpg_error_t cmd_setrepeatok(assuan_context_t ctx, char *line) { + char *p; + + (void)ctx; + + p = (char *)malloc(strlen(line) + 1); + if (!p) return gpg_error_from_syserror(); + + strcpy_escaped(p, line); + free(pinentry.repeat_ok_string); + pinentry.repeat_ok_string = p; + return 0; +} + +static gpg_error_t cmd_setrepeaterror(assuan_context_t ctx, char *line) { + char *p; + + (void)ctx; + + p = (char *)malloc(strlen(line) + 1); + if (!p) return gpg_error_from_syserror(); + + strcpy_escaped(p, line); + free(pinentry.repeat_error_string); + pinentry.repeat_error_string = p; + return 0; +} + +static gpg_error_t cmd_seterror(assuan_context_t ctx, char *line) { + char *newe; + + (void)ctx; + + newe = (char *)malloc(strlen(line) + 1); + if (!newe) return gpg_error_from_syserror(); + + strcpy_escaped(newe, line); + if (pinentry.error) free(pinentry.error); + pinentry.error = newe; + return 0; +} + +static gpg_error_t cmd_setok(assuan_context_t ctx, char *line) { + char *newo; + + (void)ctx; + + newo = (char *)malloc(strlen(line) + 1); + if (!newo) return gpg_error_from_syserror(); + + strcpy_escaped(newo, line); + if (pinentry.ok) free(pinentry.ok); + pinentry.ok = newo; + return 0; +} + +static gpg_error_t cmd_setnotok(assuan_context_t ctx, char *line) { + char *newo; + + (void)ctx; + + newo = (char *)malloc(strlen(line) + 1); + if (!newo) return gpg_error_from_syserror(); + + strcpy_escaped(newo, line); + if (pinentry.notok) free(pinentry.notok); + pinentry.notok = newo; + return 0; +} + +static gpg_error_t cmd_setcancel(assuan_context_t ctx, char *line) { + char *newc; + + (void)ctx; + + newc = (char *)malloc(strlen(line) + 1); + if (!newc) return gpg_error_from_syserror(); + + strcpy_escaped(newc, line); + if (pinentry.cancel) free(pinentry.cancel); + pinentry.cancel = newc; + return 0; +} + +static gpg_error_t cmd_settimeout(assuan_context_t ctx, char *line) { + (void)ctx; + + if (line && *line) pinentry.timeout = atoi(line); + + return 0; +} + +static gpg_error_t cmd_settitle(assuan_context_t ctx, char *line) { + char *newt; + + (void)ctx; + + newt = (char *)malloc(strlen(line) + 1); + if (!newt) return gpg_error_from_syserror(); + + strcpy_escaped(newt, line); + if (pinentry.title) free(pinentry.title); + pinentry.title = newt; + return 0; +} + +static gpg_error_t cmd_setqualitybar(assuan_context_t ctx, char *line) { + char *newval; + + (void)ctx; + + if (!*line) line = "Quality:"; + + newval = (char *)malloc(strlen(line) + 1); + if (!newval) return gpg_error_from_syserror(); + + strcpy_escaped(newval, line); + if (pinentry.quality_bar) free(pinentry.quality_bar); + pinentry.quality_bar = newval; + return 0; +} + +/* Set the tooltip to be used for a quality bar. */ +static gpg_error_t cmd_setqualitybar_tt(assuan_context_t ctx, char *line) { + char *newval; + + (void)ctx; + + if (*line) { + newval = (char *)malloc(strlen(line) + 1); + if (!newval) return gpg_error_from_syserror(); + + strcpy_escaped(newval, line); + } else + newval = NULL; + if (pinentry.quality_bar_tt) free(pinentry.quality_bar_tt); + pinentry.quality_bar_tt = newval; + return 0; +} + +/* Set the tooltip to be used for a generate action. */ +static gpg_error_t cmd_setgenpin_tt(assuan_context_t ctx, char *line) { + char *newval; + + (void)ctx; + + if (*line) { + newval = (char *)malloc(strlen(line) + 1); + if (!newval) return gpg_error_from_syserror(); + + strcpy_escaped(newval, line); + } else + newval = NULL; + if (pinentry.genpin_tt) free(pinentry.genpin_tt); + pinentry.genpin_tt = newval; + return 0; +} + +/* Set the label to be used for a generate action. */ +static gpg_error_t cmd_setgenpin_label(assuan_context_t ctx, char *line) { + char *newval; + + (void)ctx; + + if (*line) { + newval = (char *)malloc(strlen(line) + 1); + if (!newval) return gpg_error_from_syserror(); + + strcpy_escaped(newval, line); + } else + newval = NULL; + if (pinentry.genpin_label) free(pinentry.genpin_label); + pinentry.genpin_label = newval; + return 0; +} + +static gpg_error_t cmd_getpin(assuan_context_t ctx, char *line) { + int result; + int set_prompt = 0; + int just_read_password_from_cache = 0; + + (void)line; + + pinentry_setbuffer_init(&pinentry); + if (!pinentry.pin) return gpg_error(GPG_ERR_ENOMEM); + + pinentry.confirm = 0; + + /* Try reading from the password cache. */ + if (/* If repeat passphrase is set, then we don't want to read from + the cache. */ + !pinentry.repeat_passphrase + /* Are we allowed to read from the cache? */ + && pinentry.allow_external_password_cache && + pinentry.keyinfo + /* Only read from the cache if we haven't already tried it. */ + && !pinentry.tried_password_cache + /* If the last read resulted in an error, then don't read from + the cache. */ + && !pinentry.error) { + char *password; + int give_up_on_password_store = 0; + + pinentry.tried_password_cache = 1; + + password = + password_cache_lookup(pinentry.keyinfo, &give_up_on_password_store); + if (give_up_on_password_store) pinentry.allow_external_password_cache = 0; + + if (password) + /* There is a cached password. Try it. */ + { + int len = strlen(password) + 1; + if (len > pinentry.pin_len) len = pinentry.pin_len; + + memcpy(pinentry.pin, password, len); + pinentry.pin[len] = '\0'; + + secmem_free(password); + + pinentry.pin_from_cache = 1; + + assuan_write_status(ctx, "PASSWORD_FROM_CACHE", ""); + + /* Result is the length of the password not including the + NUL terminator. */ + result = len - 1; + + just_read_password_from_cache = 1; + + goto out; + } + } + + /* The password was not cached (or we are not allowed to / cannot + use the cache). Prompt the user. */ + pinentry.pin_from_cache = 0; + + if (!pinentry.prompt) { + pinentry.prompt = const_cast<char *>( + pinentry.default_prompt ? pinentry.default_prompt : "PIN:"); + set_prompt = 1; + } + pinentry.locale_err = 0; + pinentry.specific_err = 0; + pinentry.specific_err_loc = NULL; + free(pinentry.specific_err_info); + pinentry.specific_err_info = NULL; + pinentry.close_button = 0; + pinentry.repeat_okay = 0; + pinentry.one_button = 0; + pinentry.ctx_assuan = ctx; + result = (*pinentry_cmd_handler)(&pinentry); + pinentry.ctx_assuan = NULL; + if (pinentry.error) { + free(pinentry.error); + pinentry.error = NULL; + } + if (pinentry.repeat_passphrase) { + free(pinentry.repeat_passphrase); + pinentry.repeat_passphrase = NULL; + } + if (set_prompt) pinentry.prompt = NULL; + + pinentry.quality_bar = 0; /* Reset it after the command. */ + + if (pinentry.close_button) assuan_write_status(ctx, "BUTTON_INFO", "close"); + + if (result < 0) { + pinentry_setbuffer_clear(&pinentry); + if (pinentry.specific_err) { + write_status_error(ctx, &pinentry); + + if (gpg_err_code(pinentry.specific_err) == GPG_ERR_FULLY_CANCELED) + assuan_set_flag(ctx, ASSUAN_FORCE_CLOSE, 1); + + return pinentry.specific_err; + } + return (pinentry.locale_err ? gpg_error(GPG_ERR_LOCALE_PROBLEM) + : gpg_error(GPG_ERR_CANCELED)); + } + +out: + if (result) { + if (pinentry.repeat_okay) assuan_write_status(ctx, "PIN_REPEATED", ""); + assuan_begin_confidential(ctx); + result = assuan_send_data(ctx, pinentry.pin, strlen(pinentry.pin)); + if (!result) result = assuan_send_data(ctx, NULL, 0); + assuan_end_confidential(ctx); + + if (/* GPG Agent says it's okay. */ + pinentry.allow_external_password_cache && + pinentry.keyinfo + /* We didn't just read it from the cache. */ + && !just_read_password_from_cache + /* And the user said it's okay. */ + && pinentry.may_cache_password) + /* Cache the password. */ + password_cache_save(pinentry.keyinfo, pinentry.pin); + } + + pinentry_setbuffer_clear(&pinentry); + + return result; +} + +/* Note that the option --one-button is a hack to allow the use of old + pinentries while the caller is ignoring the result. Given that + options have never been used or flagged as an error the new option + is an easy way to enable the messsage mode while not requiring to + update pinentry or to have the caller test for the message + command. New applications which are free to require an updated + pinentry should use MESSAGE instead. */ +static gpg_error_t cmd_confirm(assuan_context_t ctx, char *line) { + int result; + + pinentry.one_button = !!strstr(line, "--one-button"); + pinentry.quality_bar = 0; + pinentry.close_button = 0; + pinentry.locale_err = 0; + pinentry.specific_err = 0; + pinentry.specific_err_loc = NULL; + free(pinentry.specific_err_info); + pinentry.specific_err_info = NULL; + pinentry.canceled = 0; + pinentry.confirm = 1; + pinentry_setbuffer_clear(&pinentry); + result = (*pinentry_cmd_handler)(&pinentry); + if (pinentry.error) { + free(pinentry.error); + pinentry.error = NULL; + } + + if (pinentry.close_button) assuan_write_status(ctx, "BUTTON_INFO", "close"); + + if (result > 0) return 0; /* OK */ + + if (pinentry.specific_err) { + write_status_error(ctx, &pinentry); + + if (gpg_err_code(pinentry.specific_err) == GPG_ERR_FULLY_CANCELED) + assuan_set_flag(ctx, ASSUAN_FORCE_CLOSE, 1); + + return pinentry.specific_err; + } + + if (pinentry.locale_err) return gpg_error(GPG_ERR_LOCALE_PROBLEM); + + if (pinentry.one_button) return 0; /* OK */ + + if (pinentry.canceled) return gpg_error(GPG_ERR_CANCELED); + return gpg_error(GPG_ERR_NOT_CONFIRMED); +} + +static gpg_error_t cmd_message(assuan_context_t ctx, char *line) { + (void)line; + + return cmd_confirm(ctx, "--one-button"); +} + +/* Return a staically allocated string with information on the mode, + * uid, and gid of DEVICE. On error "?" is returned if DEVICE is + * NULL, "-" is returned. */ +static const char *device_stat_string(const char *device) { +#ifdef HAVE_STAT + static char buf[40]; + struct stat st; + + if (!device || !*device) return "-"; + + if (stat(device, &st)) return "?"; /* Error */ + snprintf(buf, sizeof buf, "%lo/%lu/%lu", (unsigned long)st.st_mode, + (unsigned long)st.st_uid, (unsigned long)st.st_gid); + return buf; +#else + return "-"; +#endif +} + +/* GETINFO <what> + + Multipurpose function to return a variety of information. + Supported values for WHAT are: + + version - Return the version of the program. + pid - Return the process id of the server. + flavor - Return information about the used pinentry flavor + ttyinfo - Return DISPLAY, ttyinfo and an emacs pinentry status + */ +static gpg_error_t cmd_getinfo(assuan_context_t ctx, char *line) { + int rc; + const char *s; + char buffer[150]; + + if (!strcmp(line, "version")) { + s = 0; + rc = assuan_send_data(ctx, s, strlen(s)); + } else if (!strcmp(line, "pid")) { + snprintf(buffer, sizeof buffer, "%lu", (unsigned long)getpid()); + rc = assuan_send_data(ctx, buffer, strlen(buffer)); + } else if (!strcmp(line, "flavor")) { + if (!strncmp(this_pgmname, "pinentry-", 9) && this_pgmname[9]) + s = this_pgmname + 9; + else + s = this_pgmname; + + snprintf(buffer, sizeof buffer, "%s%s%s", s, flavor_flag ? ":" : "", + flavor_flag ? flavor_flag : ""); + rc = assuan_send_data(ctx, buffer, strlen(buffer)); + /* if (!rc) */ + /* rc = assuan_write_status (ctx, "FEATURES", "tabbing foo bar"); */ + } else if (!strcmp(line, "ttyinfo")) { + char emacs_status[10]; +#ifdef INSIDE_EMACS + snprintf(emacs_status, sizeof emacs_status, "%d", pinentry_emacs_status()); +#else + strcpy(emacs_status, "-"); +#endif + snprintf(buffer, sizeof buffer, "%s %s %s %s %lu/%lu %s", + pinentry.ttyname ? pinentry.ttyname : "-", + pinentry.ttytype_l ? pinentry.ttytype_l : "-", + pinentry.display ? pinentry.display : "-", + device_stat_string(pinentry.ttyname), +#ifdef HAVE_DOSISH_SYSTEM + 0l, 0l, +#else + (unsigned long)geteuid(), (unsigned long)getegid(), +#endif + emacs_status); + rc = assuan_send_data(ctx, buffer, strlen(buffer)); + } else + rc = gpg_error(GPG_ERR_ASS_PARAMETER); + return rc; +} + +/* CLEARPASSPHRASE <cacheid> + + Clear the cache passphrase associated with the key identified by + cacheid. + */ +static gpg_error_t cmd_clear_passphrase(assuan_context_t ctx, char *line) { + (void)ctx; + + if (!line) return gpg_error(GPG_ERR_ASS_INV_VALUE); + + /* Remove leading and trailing white space. */ + while (*line == ' ') line++; + while (line[strlen(line) - 1] == ' ') line[strlen(line) - 1] = 0; + + switch (password_cache_clear(line)) { + case 1: + return 0; + case 0: + return gpg_error(GPG_ERR_ASS_INV_VALUE); + default: + return gpg_error(GPG_ERR_ASS_GENERAL); + } +} + +/* Tell the assuan library about our commands. */ +static gpg_error_t register_commands(assuan_context_t ctx) { + static struct { + const char *name; + gpg_error_t (*handler)(assuan_context_t, char *line); + } table[] = {{"SETDESC", cmd_setdesc}, + {"SETPROMPT", cmd_setprompt}, + {"SETKEYINFO", cmd_setkeyinfo}, + {"SETREPEAT", cmd_setrepeat}, + {"SETREPEATERROR", cmd_setrepeaterror}, + {"SETREPEATOK", cmd_setrepeatok}, + {"SETERROR", cmd_seterror}, + {"SETOK", cmd_setok}, + {"SETNOTOK", cmd_setnotok}, + {"SETCANCEL", cmd_setcancel}, + {"GETPIN", cmd_getpin}, + {"CONFIRM", cmd_confirm}, + {"MESSAGE", cmd_message}, + {"SETQUALITYBAR", cmd_setqualitybar}, + {"SETQUALITYBAR_TT", cmd_setqualitybar_tt}, + {"SETGENPIN", cmd_setgenpin_label}, + {"SETGENPIN_TT", cmd_setgenpin_tt}, + {"GETINFO", cmd_getinfo}, + {"SETTITLE", cmd_settitle}, + {"SETTIMEOUT", cmd_settimeout}, + {"CLEARPASSPHRASE", cmd_clear_passphrase}, + {NULL}}; + int i, j; + gpg_error_t rc; + + for (i = j = 0; table[i].name; i++) { + rc = assuan_register_command(ctx, table[i].name, table[i].handler, NULL); + if (rc) return rc; + } + return 0; +} + +int pinentry_loop2(int infd, int outfd) { + gpg_error_t rc; + assuan_fd_t filedes[2]; + assuan_context_t ctx; + + /* Extra check to make sure we have dropped privs. */ +#ifndef HAVE_DOSISH_SYSTEM + if (getuid() != geteuid()) abort(); +#endif + + rc = assuan_new(&ctx); + if (rc) { + fprintf(stderr, "server context creation failed: %s\n", gpg_strerror(rc)); + return -1; + } + + /* For now we use a simple pipe based server so that we can work + from scripts. We will later add options to run as a daemon and + wait for requests on a Unix domain socket. */ + filedes[0] = assuan_fdopen(infd); + filedes[1] = assuan_fdopen(outfd); + rc = assuan_init_pipe_server(ctx, filedes); + if (rc) { + fprintf(stderr, "%s: failed to initialize the server: %s\n", this_pgmname, + gpg_strerror(rc)); + return -1; + } + rc = register_commands(ctx); + if (rc) { + fprintf(stderr, "%s: failed to the register commands with Assuan: %s\n", + this_pgmname, gpg_strerror(rc)); + return -1; + } + + assuan_register_option_handler(ctx, option_handler); +#if 0 + assuan_set_log_stream (ctx, stderr); +#endif + assuan_register_reset_notify(ctx, pinentry_assuan_reset_handler); + + for (;;) { + rc = assuan_accept(ctx); + if (rc == -1) + break; + else if (rc) { + fprintf(stderr, "%s: Assuan accept problem: %s\n", this_pgmname, + gpg_strerror(rc)); + break; + } + + rc = assuan_process(ctx); + if (rc) { + fprintf(stderr, "%s: Assuan processing failed: %s\n", this_pgmname, + gpg_strerror(rc)); + continue; + } + } + + assuan_release(ctx); + return 0; +} + +/* Start the pinentry event loop. The program will start to process + Assuan commands until it is finished or an error occurs. If an + error occurs, -1 is returned. Otherwise, 0 is returned. */ +int pinentry_loop(void) { return pinentry_loop2(STDIN_FILENO, STDOUT_FILENO); } |