2070 lines
51 KiB
C
2070 lines
51 KiB
C
/* pinentry.c - The PIN entry support library
|
||
* Copyright (C) 2002, 2003, 2007, 2008, 2010, 2015, 2016 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 HAVE_W32CE_SYSTEM
|
||
# include <errno.h>
|
||
#endif
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#include <unistd.h>
|
||
#include <assert.h>
|
||
#ifndef HAVE_W32_SYSTEM
|
||
# include <sys/utsname.h>
|
||
#endif
|
||
#ifndef HAVE_W32CE_SYSTEM
|
||
# include <locale.h>
|
||
#endif
|
||
#ifdef HAVE_LANGINFO_H
|
||
#include <langinfo.h>
|
||
#endif
|
||
#include <limits.h>
|
||
#ifdef HAVE_W32CE_SYSTEM
|
||
# include <windows.h>
|
||
#endif
|
||
|
||
#undef WITH_UTF8_CONVERSION
|
||
#if defined FALLBACK_CURSES || defined PINENTRY_CURSES || defined PINENTRY_GTK
|
||
# include <iconv.h>
|
||
# define WITH_UTF8_CONVERSION 1
|
||
#endif
|
||
|
||
#include <assuan.h>
|
||
|
||
#include "memory.h"
|
||
#include "secmem-util.h"
|
||
#include "argparse.h"
|
||
#include "pinentry.h"
|
||
#include "password-cache.h"
|
||
|
||
#ifdef INSIDE_EMACS
|
||
# include "pinentry-emacs.h"
|
||
#endif
|
||
#ifdef FALLBACK_CURSES
|
||
# include "pinentry-curses.h"
|
||
#endif
|
||
|
||
#ifdef HAVE_W32CE_SYSTEM
|
||
#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 --disable in
|
||
* the call to pinentry_have_display and set it then in our
|
||
* parser. */
|
||
static char *remember_display;
|
||
|
||
/* Flag to remember whether a warning has been printed. */
|
||
#ifdef WITH_UTF8_CONVERSION
|
||
static int lc_ctype_unknown_warning;
|
||
#endif
|
||
|
||
|
||
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 *touch_file = pinentry.touch_file;
|
||
unsigned long owner_pid = pinentry.owner_pid;
|
||
int owner_uid = pinentry.owner_uid;
|
||
char *owner_host = pinentry.owner_host;
|
||
|
||
/* 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;
|
||
|
||
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.touch_file);
|
||
free (pinentry.owner_host);
|
||
free (pinentry.display);
|
||
}
|
||
|
||
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.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.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.touch_file = touch_file;
|
||
pinentry.owner_pid = owner_pid;
|
||
pinentry.owner_uid = owner_uid;
|
||
pinentry.owner_host = owner_host;
|
||
|
||
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.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;
|
||
}
|
||
|
||
|
||
|
||
#ifdef WITH_UTF8_CONVERSION
|
||
char *
|
||
pinentry_utf8_to_local (const char *lc_ctype, const char *text)
|
||
{
|
||
iconv_t cd;
|
||
const char *input = text;
|
||
size_t input_len = strlen (text) + 1;
|
||
char *output;
|
||
size_t output_len;
|
||
char *output_buf;
|
||
size_t processed;
|
||
char *old_ctype;
|
||
char *target_encoding;
|
||
|
||
/* If no locale setting could be determined, simply copy the
|
||
string. */
|
||
if (!lc_ctype)
|
||
{
|
||
if (! lc_ctype_unknown_warning)
|
||
{
|
||
fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n",
|
||
this_pgmname);
|
||
lc_ctype_unknown_warning = 1;
|
||
}
|
||
return strdup (text);
|
||
}
|
||
|
||
old_ctype = strdup (setlocale (LC_CTYPE, NULL));
|
||
if (!old_ctype)
|
||
return NULL;
|
||
setlocale (LC_CTYPE, lc_ctype);
|
||
target_encoding = nl_langinfo (CODESET);
|
||
if (!target_encoding)
|
||
target_encoding = "?";
|
||
setlocale (LC_CTYPE, old_ctype);
|
||
free (old_ctype);
|
||
|
||
/* This is overkill, but simplifies the iconv invocation greatly. */
|
||
output_len = input_len * MB_LEN_MAX;
|
||
output_buf = output = malloc (output_len);
|
||
if (!output)
|
||
return NULL;
|
||
|
||
cd = iconv_open (target_encoding, "UTF-8");
|
||
if (cd == (iconv_t) -1)
|
||
{
|
||
fprintf (stderr, "%s: can't convert from UTF-8 to %s: %s\n",
|
||
this_pgmname, target_encoding, strerror (errno));
|
||
free (output_buf);
|
||
return NULL;
|
||
}
|
||
processed = iconv (cd, (ICONV_CONST char **)&input, &input_len,
|
||
&output, &output_len);
|
||
iconv_close (cd);
|
||
if (processed == (size_t) -1 || input_len)
|
||
{
|
||
fprintf (stderr, "%s: error converting from UTF-8 to %s: %s\n",
|
||
this_pgmname, target_encoding, strerror (errno));
|
||
free (output_buf);
|
||
return NULL;
|
||
}
|
||
return output_buf;
|
||
}
|
||
#endif /*WITH_UTF8_CONVERSION*/
|
||
|
||
|
||
/* Convert TEXT which is encoded according to LC_CTYPE to UTF-8. With
|
||
SECURE set to true, use secure memory for the returned buffer.
|
||
Return NULL on error. */
|
||
#ifdef WITH_UTF8_CONVERSION
|
||
char *
|
||
pinentry_local_to_utf8 (char *lc_ctype, char *text, int secure)
|
||
{
|
||
char *old_ctype;
|
||
char *source_encoding;
|
||
iconv_t cd;
|
||
const char *input = text;
|
||
size_t input_len = strlen (text) + 1;
|
||
char *output;
|
||
size_t output_len;
|
||
char *output_buf;
|
||
size_t processed;
|
||
|
||
/* If no locale setting could be determined, simply copy the
|
||
string. */
|
||
if (!lc_ctype)
|
||
{
|
||
if (! lc_ctype_unknown_warning)
|
||
{
|
||
fprintf (stderr, "%s: no LC_CTYPE known - assuming UTF-8\n",
|
||
this_pgmname);
|
||
lc_ctype_unknown_warning = 1;
|
||
}
|
||
output_buf = secure? secmem_malloc (input_len) : malloc (input_len);
|
||
if (output_buf)
|
||
strcpy (output_buf, input);
|
||
return output_buf;
|
||
}
|
||
|
||
old_ctype = strdup (setlocale (LC_CTYPE, NULL));
|
||
if (!old_ctype)
|
||
return NULL;
|
||
setlocale (LC_CTYPE, lc_ctype);
|
||
source_encoding = nl_langinfo (CODESET);
|
||
setlocale (LC_CTYPE, old_ctype);
|
||
free (old_ctype);
|
||
|
||
/* This is overkill, but simplifies the iconv invocation greatly. */
|
||
output_len = input_len * MB_LEN_MAX;
|
||
output_buf = output = secure? secmem_malloc (output_len):malloc (output_len);
|
||
if (!output)
|
||
return NULL;
|
||
|
||
cd = iconv_open ("UTF-8", source_encoding);
|
||
if (cd == (iconv_t) -1)
|
||
{
|
||
fprintf (stderr, "%s: can't convert from %s to UTF-8: %s\n",
|
||
this_pgmname, source_encoding? source_encoding : "?",
|
||
strerror (errno));
|
||
if (secure)
|
||
secmem_free (output_buf);
|
||
else
|
||
free (output_buf);
|
||
return NULL;
|
||
}
|
||
processed = iconv (cd, (ICONV_CONST char **)&input, &input_len,
|
||
&output, &output_len);
|
||
iconv_close (cd);
|
||
if (processed == (size_t) -1 || input_len)
|
||
{
|
||
fprintf (stderr, "%s: error converting from %s to UTF-8: %s\n",
|
||
this_pgmname, source_encoding? source_encoding : "?",
|
||
strerror (errno));
|
||
if (secure)
|
||
secmem_free (output_buf);
|
||
else
|
||
free (output_buf);
|
||
return NULL;
|
||
}
|
||
return output_buf;
|
||
}
|
||
#endif /*WITH_UTF8_CONVERSION*/
|
||
|
||
|
||
/* 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;
|
||
}
|
||
|
||
|
||
|
||
/* Return a malloced copy of the commandline for PID. If this is not
|
||
* possible NULL is returned. */
|
||
#ifndef HAVE_W32_SYSTEM
|
||
static char *
|
||
get_cmdline (unsigned long pid)
|
||
{
|
||
char buffer[200];
|
||
FILE *fp;
|
||
size_t i, n;
|
||
|
||
snprintf (buffer, sizeof buffer, "/proc/%lu/cmdline", pid);
|
||
buffer[sizeof buffer - 1] = 0;
|
||
|
||
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 /*!HAVE_W32_SYSTEM*/
|
||
|
||
|
||
/* 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 HAVE_W32_SYSTEM
|
||
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);
|
||
buffer[sizeof buffer - 1] = 0;
|
||
|
||
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;
|
||
/* 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 /*!HAVE_W32_SYSTEM*/
|
||
|
||
|
||
/* 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 HAVE_W32_SYSTEM
|
||
else if (pe->owner_pid)
|
||
{
|
||
char buf[200];
|
||
struct utsname utsbuf;
|
||
char *pidname = NULL;
|
||
char *cmdline = NULL;
|
||
|
||
if (pe->owner_host &&
|
||
!uname (&utsbuf) && utsbuf.nodename &&
|
||
!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);
|
||
buf[sizeof buf - 1] = 0;
|
||
free (pidname);
|
||
free (cmdline);
|
||
title = strdup (buf);
|
||
}
|
||
#endif /*!HAVE_W32_SYSTEM*/
|
||
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 = 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 = 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 genpin inquiry */
|
||
char *
|
||
pinentry_inq_genpin (pinentry_t pin)
|
||
{
|
||
assuan_context_t ctx = 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);
|
||
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 = 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 HAVE_W32CE_SYSTEM
|
||
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 HAVE_W32CE_SYSTEM
|
||
fprintf (stderr, "%s: %s\n", this_pgmname, strerror (errno));
|
||
#endif
|
||
exit (EXIT_FAILURE);
|
||
}
|
||
}
|
||
found = 1;
|
||
break;
|
||
}
|
||
}
|
||
|
||
#ifndef HAVE_W32CE_SYSTEM
|
||
{
|
||
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 = PACKAGE_VERSION; break;
|
||
case 14: p = "Copyright (C) 2016 g10 Code GmbH"; break;
|
||
case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break;
|
||
case 1:
|
||
case 40:
|
||
{
|
||
static char *str;
|
||
|
||
if (!str)
|
||
{
|
||
size_t n = 50 + strlen (this_pgmname);
|
||
str = malloc (n);
|
||
if (str)
|
||
{
|
||
snprintf (str, n, "Usage: %s [options] (-h for help)",
|
||
this_pgmname);
|
||
str[n-1] = 0;
|
||
}
|
||
}
|
||
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 HAVE_W32CE_SYSTEM
|
||
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 HAVE_W32CE_SYSTEM
|
||
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 HAVE_W32CE_SYSTEM
|
||
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 HAVE_W32CE_SYSTEM
|
||
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 HAVE_W32CE_SYSTEM
|
||
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);
|
||
}
|
||
break;
|
||
|
||
case 'o':
|
||
pinentry.timeout = pargs.r.ret_int;
|
||
break;
|
||
|
||
case 'a':
|
||
pinentry.ttyalert = strdup (pargs.r.ret_str);
|
||
if (!pinentry.ttyalert)
|
||
{
|
||
#ifndef HAVE_W32CE_SYSTEM
|
||
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 HAVE_W32_SYSTEM
|
||
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, "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
|
||
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 : "");
|
||
buf[sizeof buf -1] = 0;
|
||
assuan_write_status (ctx, "ERROR", buf);
|
||
}
|
||
|
||
|
||
static gpg_error_t
|
||
cmd_setdesc (assuan_context_t ctx, char *line)
|
||
{
|
||
char *newd;
|
||
|
||
(void)ctx;
|
||
|
||
newd = 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 = 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 = 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_setrepeaterror (assuan_context_t ctx, char *line)
|
||
{
|
||
char *p;
|
||
|
||
(void)ctx;
|
||
|
||
p = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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);
|
||
|
||
/* 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 = 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", "");
|
||
result = assuan_send_data (ctx, pinentry.pin, strlen(pinentry.pin));
|
||
if (!result)
|
||
result = assuan_send_data (ctx, NULL, 0);
|
||
|
||
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_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 = VERSION;
|
||
rc = assuan_send_data (ctx, s, strlen (s));
|
||
}
|
||
else if (!strcmp (line, "pid"))
|
||
{
|
||
|
||
snprintf (buffer, sizeof buffer, "%lu", (unsigned long)getpid ());
|
||
buffer[sizeof buffer -1] = 0;
|
||
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 : "");
|
||
buffer[sizeof buffer -1] = 0;
|
||
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
|
||
);
|
||
buffer[sizeof buffer -1] = 0;
|
||
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 },
|
||
{ "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);
|
||
}
|