diff --git a/pinentry/pinentry-curses.c b/pinentry/pinentry-curses.c
index a3fe2e2..1c3008a 100644
--- a/pinentry/pinentry-curses.c
+++ b/pinentry/pinentry-curses.c
@@ -1,1216 +1,1226 @@
/* pinentry-curses.c - A secure curses dialog for PIN entry, library version
* Copyright (C) 2002, 2015 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 .
* SPDX-License-Identifier: GPL-2.0+
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_UTIME_H
#include
#endif /*HAVE_UTIME_H*/
#include
#ifdef HAVE_WCHAR_H
#include
#endif /*HAVE_WCHAR_H*/
#include
#include "pinentry.h"
#if GPG_ERROR_VERSION_NUMBER < 0x011900 /* 1.25 */
# define GPG_ERR_WINDOW_TOO_SMALL 301
# define GPG_ERR_MISSING_ENVVAR 303
#endif
/* FIXME: We should allow configuration of these button labels and in
any case use the default_ok, default_cancel values if available.
However, I have no clue about curses and localization. */
#define STRING_OK ""
#define STRING_NOTOK ""
#define STRING_CANCEL ""
#define USE_COLORS (has_colors () && COLOR_PAIRS >= 2)
static short pinentry_color[] = { -1, -1, COLOR_BLACK, COLOR_RED,
COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE,
COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE };
static int init_screen;
#ifndef HAVE_DOSISH_SYSTEM
static int timed_out;
#endif
typedef enum
{
DIALOG_POS_NONE,
DIALOG_POS_PIN,
DIALOG_POS_OK,
DIALOG_POS_NOTOK,
DIALOG_POS_CANCEL
}
dialog_pos_t;
struct dialog
{
dialog_pos_t pos;
int pin_y;
int pin_x;
/* Width of the PIN field. */
int pin_size;
/* Cursor location in PIN field. */
int pin_loc;
int pin_max;
/* Length of PIN. */
int pin_len;
int got_input;
int no_echo;
int ok_y;
int ok_x;
char *ok;
int cancel_y;
int cancel_x;
char *cancel;
int notok_y;
int notok_x;
char *notok;
pinentry_t pinentry;
};
typedef struct dialog *dialog_t;
#ifdef HAVE_NCURSESW
typedef wchar_t CH;
#define STRLEN(x) wcslen (x)
#define STRWIDTH(x) wcswidth (x, wcslen (x))
#define ADDCH(x) addnwstr (&x, 1);
#define CHWIDTH(x) wcwidth (x)
#define NULLCH L'\0'
#define NLCH L'\n'
#define SPCH L' '
#else
typedef char CH;
#define STRLEN(x) strlen (x)
#define STRWIDTH(x) strlen (x)
#define ADDCH(x) addch ((unsigned char) x)
#define CHWIDTH(x) 1
#define NULLCH '\0'
#define NLCH '\n'
#define SPCH ' '
#endif
/* Return the next line up to MAXWIDTH columns wide in START and LEN.
Return value is the width needed for the line.
The first invocation should have 0 as *LEN. If the line ends with
a \n, it is a normal line that will be continued. If it is a '\0'
the end of the text is reached after this line. In all other cases
there is a forced line break. A full line is returned and will be
continued in the next line. */
static int
collect_line (int maxwidth, CH **start_p, int *len_p)
{
int last_space = 0;
int len = *len_p;
int width = 0;
CH *end;
/* Skip to next line. */
*start_p += len;
/* Skip leading space. */
while (**start_p == SPCH)
(*start_p)++;
end = *start_p;
len = 0;
while (width < maxwidth - 1 && *end != NULLCH && *end != NLCH)
{
if (*end == SPCH)
last_space = len;
width += CHWIDTH (*end);
len++;
end++;
}
if (*end != NULLCH && *end != NLCH && last_space != 0)
{
/* We reached the end of the available space, but still have
characters to go in this line. We can break the line into
two parts at a space. */
len = last_space;
(*start_p)[len] = NLCH;
}
*len_p = len + 1;
return width;
}
#ifdef HAVE_NCURSESW
static CH *
utf8_to_local (char *lc_ctype, char *string)
{
mbstate_t ps;
size_t len;
char *local;
const char *p;
wchar_t *wcs = NULL;
char *old_ctype = NULL;
local = pinentry_utf8_to_local (lc_ctype, string);
if (!local)
return NULL;
old_ctype = strdup (setlocale (LC_CTYPE, NULL));
setlocale (LC_CTYPE, lc_ctype? lc_ctype : "");
p = local;
memset (&ps, 0, sizeof(mbstate_t));
len = mbsrtowcs (NULL, &p, strlen (string), &ps);
if (len == (size_t)-1)
{
free (local);
goto leave;
}
wcs = calloc (len + 1, sizeof(wchar_t));
if (!wcs)
{
free (local);
goto leave;
}
p = local;
memset (&ps, 0, sizeof(mbstate_t));
mbsrtowcs (wcs, &p, len, &ps);
free (local);
leave:
if (old_ctype)
{
setlocale (LC_CTYPE, old_ctype);
free (old_ctype);
}
return wcs;
}
#else
static CH *
utf8_to_local (const char *lc_ctype, const char *string)
{
return pinentry_utf8_to_local (lc_ctype, string);
}
#endif
static int
dialog_create (pinentry_t pinentry, dialog_t dialog)
{
int err = 0;
int size_y;
int size_x;
int y;
int x;
int ypos;
int xpos;
int description_x = 0;
int error_x = 0;
CH *description = NULL;
CH *error = NULL;
CH *prompt = NULL;
dialog->pinentry = pinentry;
#define COPY_OUT(what) \
do \
if (pinentry->what) \
{ \
what = utf8_to_local (pinentry->lc_ctype, pinentry->what); \
if (!what) \
{ \
err = 1; \
pinentry->specific_err = gpg_error (GPG_ERR_LOCALE_PROBLEM); \
pinentry->specific_err_loc = "dialog_create_copy"; \
goto out; \
} \
} \
while (0)
COPY_OUT (description);
COPY_OUT (error);
COPY_OUT (prompt);
/* There is no pinentry->default_notok. Map it to
pinentry->notok. */
#define default_notok notok
#define MAKE_BUTTON(which,default) \
do \
{ \
char *new = NULL; \
if (pinentry->default_##which || pinentry->which) \
{ \
int len; \
char *msg; \
int i, j; \
\
msg = pinentry->which; \
if (! msg) \
msg = pinentry->default_##which; \
len = strlen (msg); \
\
new = malloc (len + 3); \
if (!new) \
{ \
err = 1; \
pinentry->specific_err = gpg_error_from_syserror (); \
pinentry->specific_err_loc = "dialog_create_mk_button"; \
goto out; \
} \
\
new[0] = '<'; \
for (i = 0, j = 1; i < len; i ++, j ++) \
{ \
if (msg[i] == '_') \
{ \
i ++; \
if (msg[i] == 0) \
/* _ at end of string. */ \
break; \
} \
new[j] = msg[i]; \
} \
\
new[j] = '>'; \
new[j + 1] = 0; \
} \
dialog->which = pinentry_utf8_to_local (pinentry->lc_ctype, \
new ? new : default); \
+ free (new); \
if (!dialog->which) \
{ \
err = 1; \
pinentry->specific_err = gpg_error (GPG_ERR_LOCALE_PROBLEM); \
pinentry->specific_err_loc = "dialog_create_utf8conv"; \
goto out; \
} \
} \
while (0)
MAKE_BUTTON (ok, STRING_OK);
if (!pinentry->one_button)
MAKE_BUTTON (cancel, STRING_CANCEL);
else
dialog->cancel = NULL;
if (!pinentry->one_button && pinentry->notok)
MAKE_BUTTON (notok, STRING_NOTOK);
else
dialog->notok = NULL;
getmaxyx (stdscr, size_y, size_x);
/* Check if all required lines fit on the screen. */
y = 1; /* Top frame. */
if (description)
{
CH *start = description;
int len = 0;
do
{
int width = collect_line (size_x - 4, &start, &len);
if (width > description_x)
description_x = width;
y++;
}
while (start[len - 1]);
y++;
}
if (pinentry->pin)
{
if (error)
{
CH *p = error;
int err_x = 0;
while (*p)
{
if (*(p++) == '\n')
{
if (err_x > error_x)
error_x = err_x;
y++;
err_x = 0;
}
else
err_x++;
}
if (err_x > error_x)
error_x = err_x;
y += 2; /* Error message. */
}
y += 2; /* Pin entry field. */
}
y += 2; /* OK/Cancel and bottom frame. */
if (y > size_y)
{
err = 1;
pinentry->specific_err = gpg_error (size_y < 0? GPG_ERR_MISSING_ENVVAR
/* */ : GPG_ERR_WINDOW_TOO_SMALL);
pinentry->specific_err_loc = "dialog_create";
goto out;
}
/* Check if all required columns fit on the screen. */
x = 0;
if (description)
{
int new_x = description_x;
if (new_x > size_x - 4)
new_x = size_x - 4;
if (new_x > x)
x = new_x;
}
if (pinentry->pin)
{
#define MIN_PINENTRY_LENGTH 40
int new_x;
if (error)
{
new_x = error_x;
if (new_x > size_x - 4)
new_x = size_x - 4;
if (new_x > x)
x = new_x;
}
new_x = MIN_PINENTRY_LENGTH;
if (prompt)
{
new_x += STRWIDTH (prompt) + 1; /* One space after prompt. */
}
if (new_x > size_x - 4)
new_x = size_x - 4;
if (new_x > x)
x = new_x;
}
/* We position the buttons after the first, second and third fourth
of the width. Account for rounding. */
if (x < 3 * strlen (dialog->ok))
x = 3 * strlen (dialog->ok);
if (dialog->cancel)
if (x < 3 * strlen (dialog->cancel))
x = 3 * strlen (dialog->cancel);
if (dialog->notok)
if (x < 3 * strlen (dialog->notok))
x = 3 * strlen (dialog->notok);
/* Add the frame. */
x += 4;
if (x > size_x)
{
err = 1;
pinentry->specific_err = gpg_error (size_x < 0? GPG_ERR_MISSING_ENVVAR
/* */ : GPG_ERR_WINDOW_TOO_SMALL);
pinentry->specific_err_loc = "dialog_create";
goto out;
}
dialog->pos = DIALOG_POS_NONE;
dialog->pin_max = pinentry->pin_len;
dialog->pin_loc = 0;
dialog->pin_len = 0;
ypos = (size_y - y) / 2;
xpos = (size_x - x) / 2;
move (ypos, xpos);
addch (ACS_ULCORNER);
hline (0, x - 2);
move (ypos, xpos + x - 1);
addch (ACS_URCORNER);
move (ypos + 1, xpos + x - 1);
vline (0, y - 2);
move (ypos + y - 1, xpos);
addch (ACS_LLCORNER);
hline (0, x - 2);
move (ypos + y - 1, xpos + x - 1);
addch (ACS_LRCORNER);
ypos++;
if (description)
{
CH *start = description;
int len = 0;
do
{
int i;
move (ypos, xpos);
addch (ACS_VLINE);
addch (' ');
collect_line (size_x - 4, &start, &len);
for (i = 0; i < len - 1; i++)
{
ADDCH (start[i]);
}
if (start[len - 1] != NULLCH && start[len - 1] != NLCH)
ADDCH (start[len - 1]);
ypos++;
}
while (start[len - 1]);
move (ypos, xpos);
addch (ACS_VLINE);
ypos++;
}
if (pinentry->pin)
{
int i;
if (error)
{
CH *p = error;
i = 0;
while (*p)
{
move (ypos, xpos);
addch (ACS_VLINE);
addch (' ');
if (USE_COLORS && pinentry->color_so != PINENTRY_COLOR_NONE)
{
attroff (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0));
attron (COLOR_PAIR (2) | (pinentry->color_so_bright ? A_BOLD : 0));
}
else
standout ();
for (;*p && *p != NLCH; p++)
if (i < x - 4)
{
i++;
ADDCH (*p);
}
if (USE_COLORS && pinentry->color_so != PINENTRY_COLOR_NONE)
{
attroff (COLOR_PAIR (2) | (pinentry->color_so_bright ? A_BOLD : 0));
attron (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0));
}
else
standend ();
if (*p == '\n')
p++;
i = 0;
ypos++;
}
move (ypos, xpos);
addch (ACS_VLINE);
ypos++;
}
move (ypos, xpos);
addch (ACS_VLINE);
addch (' ');
dialog->pin_y = ypos;
dialog->pin_x = xpos + 2;
dialog->pin_size = x - 4;
if (prompt)
{
CH *p = prompt;
i = STRWIDTH (prompt);
if (i > x - 4 - MIN_PINENTRY_LENGTH)
i = x - 4 - MIN_PINENTRY_LENGTH;
dialog->pin_x += i + 1;
dialog->pin_size -= i + 1;
i = STRLEN (prompt);
while (i-- > 0)
{
ADDCH (*(p++));
}
addch (' ');
}
for (i = 0; i < dialog->pin_size; i++)
addch ('_');
ypos++;
move (ypos, xpos);
addch (ACS_VLINE);
ypos++;
}
move (ypos, xpos);
addch (ACS_VLINE);
if (dialog->cancel || dialog->notok)
{
dialog->ok_y = ypos;
/* Calculating the left edge of the left button, rounding down. */
dialog->ok_x = xpos + 2 + ((x - 4) / 3 - strlen (dialog->ok)) / 2;
move (dialog->ok_y, dialog->ok_x);
addstr (dialog->ok);
if (! pinentry->pin && dialog->notok)
{
dialog->notok_y = ypos;
/* Calculating the left edge of the middle button, rounding up. */
dialog->notok_x = xpos + x / 2 - strlen (dialog->notok) / 2;
move (dialog->notok_y, dialog->notok_x);
addstr (dialog->notok);
}
if (dialog->cancel)
{
dialog->cancel_y = ypos;
/* Calculating the left edge of the right button, rounding up. */
dialog->cancel_x = xpos + x - 2 - ((x - 4) / 3 + strlen (dialog->cancel)) / 2;
move (dialog->cancel_y, dialog->cancel_x);
addstr (dialog->cancel);
}
}
else
{
dialog->ok_y = ypos;
/* Calculating the left edge of the OK button, rounding down. */
dialog->ok_x = xpos + x / 2 - strlen (dialog->ok) / 2;
move (dialog->ok_y, dialog->ok_x);
addstr (dialog->ok);
}
dialog->got_input = 0;
dialog->no_echo = 0;
out:
if (description)
free (description);
if (error)
free (error);
if (prompt)
free (prompt);
return err;
}
static void
set_cursor_state (int on)
{
static int normal_state = -1;
static int on_last;
if (normal_state < 0 && !on)
{
normal_state = curs_set (0);
on_last = on;
}
else if (on != on_last)
{
curs_set (on ? normal_state : 0);
on_last = on;
}
}
static int
dialog_switch_pos (dialog_t diag, dialog_pos_t new_pos)
{
if (new_pos != diag->pos)
{
switch (diag->pos)
{
case DIALOG_POS_OK:
move (diag->ok_y, diag->ok_x);
addstr (diag->ok);
break;
case DIALOG_POS_NOTOK:
if (diag->notok)
{
move (diag->notok_y, diag->notok_x);
addstr (diag->notok);
}
break;
case DIALOG_POS_CANCEL:
if (diag->cancel)
{
move (diag->cancel_y, diag->cancel_x);
addstr (diag->cancel);
}
break;
default:
break;
}
diag->pos = new_pos;
switch (diag->pos)
{
case DIALOG_POS_PIN:
move (diag->pin_y, diag->pin_x + diag->pin_loc);
set_cursor_state (1);
break;
case DIALOG_POS_OK:
set_cursor_state (0);
move (diag->ok_y, diag->ok_x);
standout ();
addstr (diag->ok);
standend ();
move (diag->ok_y, diag->ok_x);
break;
case DIALOG_POS_NOTOK:
if (diag->notok)
{
set_cursor_state (0);
move (diag->notok_y, diag->notok_x);
standout ();
addstr (diag->notok);
standend ();
move (diag->notok_y, diag->notok_x);
}
break;
case DIALOG_POS_CANCEL:
if (diag->cancel)
{
set_cursor_state (0);
move (diag->cancel_y, diag->cancel_x);
standout ();
addstr (diag->cancel);
standend ();
move (diag->cancel_y, diag->cancel_x);
}
break;
case DIALOG_POS_NONE:
set_cursor_state (0);
break;
}
refresh ();
}
return 0;
}
/* XXX Assume that field width is at least > 5. */
static void
dialog_input (dialog_t diag, int alt, int chr)
{
int old_loc = diag->pin_loc;
assert (diag->pinentry->pin);
assert (diag->pos == DIALOG_POS_PIN);
if (alt && chr == KEY_BACKSPACE)
/* Remap alt-backspace to control-W. */
chr = 'w' - 'a' + 1;
switch (chr)
{
case KEY_BACKSPACE:
/* control-h. */
case 'h' - 'a' + 1:
/* ASCII DEL. What Mac OS X apparently emits when the "delete"
(backspace) key is pressed. */
case 127:
if (diag->pin_len > 0)
{
diag->pin_len--;
diag->pin_loc--;
if (diag->pin_loc == 0 && diag->pin_len > 0)
{
diag->pin_loc = diag->pin_size - 5;
if (diag->pin_loc > diag->pin_len)
diag->pin_loc = diag->pin_len;
}
}
else if (!diag->got_input)
{
diag->no_echo = 1;
move (diag->pin_y, diag->pin_x);
addstr ("[no echo]");
}
break;
case 'l' - 'a' + 1: /* control-l */
/* Refresh the screen. */
endwin ();
refresh ();
break;
case 'u' - 'a' + 1: /* control-u */
/* Erase the whole line. */
if (diag->pin_len > 0)
{
diag->pin_len = 0;
diag->pin_loc = 0;
}
break;
case 'w' - 'a' + 1: /* control-w. */
while (diag->pin_len > 0
&& diag->pinentry->pin[diag->pin_len - 1] == ' ')
{
diag->pin_len --;
diag->pin_loc --;
if (diag->pin_loc < 0)
{
diag->pin_loc += diag->pin_size;
if (diag->pin_loc > diag->pin_len)
diag->pin_loc = diag->pin_len;
}
}
while (diag->pin_len > 0
&& diag->pinentry->pin[diag->pin_len - 1] != ' ')
{
diag->pin_len --;
diag->pin_loc --;
if (diag->pin_loc < 0)
{
diag->pin_loc += diag->pin_size;
if (diag->pin_loc > diag->pin_len)
diag->pin_loc = diag->pin_len;
}
}
break;
default:
if (chr > 0 && chr < 256 && diag->pin_len < diag->pin_max)
{
/* Make sure there is enough room for this character and a
following NUL byte. */
if (! pinentry_setbufferlen (diag->pinentry, diag->pin_len + 2))
{
/* Bail. Here we use a simple approach. It would be
better to have a pinentry_bug function. */
assert (!"setbufferlen failed");
abort ();
}
diag->pinentry->pin[diag->pin_len] = (char) chr;
diag->pin_len++;
diag->pin_loc++;
if (diag->pin_loc == diag->pin_size && diag->pin_len < diag->pin_max)
{
diag->pin_loc = 5;
if (diag->pin_loc < diag->pin_size - (diag->pin_max + 1 - diag->pin_len))
diag->pin_loc = diag->pin_size - (diag->pin_max + 1 - diag->pin_len);
}
}
break;
}
diag->got_input = 1;
if (!diag->no_echo)
{
if (old_loc < diag->pin_loc)
{
move (diag->pin_y, diag->pin_x + old_loc);
while (old_loc++ < diag->pin_loc)
addch ('*');
}
else if (old_loc > diag->pin_loc)
{
move (diag->pin_y, diag->pin_x + diag->pin_loc);
while (old_loc-- > diag->pin_loc)
addch ('_');
}
move (diag->pin_y, diag->pin_x + diag->pin_loc);
}
}
static int
dialog_run (pinentry_t pinentry, const char *tty_name, const char *tty_type)
{
int confirm_mode = !pinentry->pin;
struct dialog diag;
FILE *ttyfi = NULL;
FILE *ttyfo = NULL;
SCREEN *screen = 0;
int done = 0;
char *pin_utf8;
int alt = 0;
#ifndef HAVE_DOSISH_SYSTEM
int no_input = 1;
#endif
#ifdef HAVE_NCURSESW
char *old_ctype = NULL;
if (pinentry->lc_ctype)
{
old_ctype = strdup (setlocale (LC_CTYPE, NULL));
setlocale (LC_CTYPE, pinentry->lc_ctype);
}
else
setlocale (LC_CTYPE, "");
#endif
/* Open the desired terminal if necessary. */
if (tty_name)
{
ttyfi = fopen (tty_name, "r");
if (!ttyfi)
{
pinentry->specific_err = gpg_error_from_syserror ();
pinentry->specific_err_loc = "open_tty_for_read";
+#ifdef HAVE_NCURSESW
+ free (old_ctype);
+#endif
return confirm_mode? 0 : -1;
}
ttyfo = fopen (tty_name, "w");
if (!ttyfo)
{
int err = errno;
fclose (ttyfi);
errno = err;
pinentry->specific_err = gpg_error_from_syserror ();
pinentry->specific_err_loc = "open_tty_for_write";
+#ifdef HAVE_NCURSESW
+ free (old_ctype);
+#endif
return confirm_mode? 0 : -1;
}
screen = newterm (tty_type, ttyfo, ttyfi);
set_term (screen);
}
else
{
if (!init_screen)
{
if (!(isatty(fileno(stdin)) && isatty(fileno(stdout))))
{
errno = ENOTTY;
pinentry->specific_err = gpg_error_from_syserror ();
pinentry->specific_err_loc = "isatty";
+#ifdef HAVE_NCURSESW
+ free (old_ctype);
+#endif
return confirm_mode? 0 : -1;
}
init_screen = 1;
initscr ();
}
else
clear ();
}
keypad (stdscr, TRUE); /* Enable keyboard mapping. */
nonl (); /* Tell curses not to do NL->CR/NL on output. */
cbreak (); /* Take input chars one at a time, no wait for \n. */
noecho (); /* Don't echo input - in color. */
if (pinentry->ttyalert)
{
if (! strcmp(pinentry->ttyalert, "beep"))
beep ();
else if (! strcmp(pinentry->ttyalert, "flash"))
flash ();
}
if (has_colors ())
{
start_color ();
#ifdef NCURSES_VERSION
use_default_colors ();
#endif
if (pinentry->color_so == PINENTRY_COLOR_DEFAULT)
{
pinentry->color_so = PINENTRY_COLOR_RED;
pinentry->color_so_bright = 1;
}
if (COLOR_PAIRS >= 2)
{
init_pair (1, pinentry_color[pinentry->color_fg],
pinentry_color[pinentry->color_bg]);
init_pair (2, pinentry_color[pinentry->color_so],
pinentry_color[pinentry->color_bg]);
bkgd (COLOR_PAIR (1));
attron (COLOR_PAIR (1) | (pinentry->color_fg_bright ? A_BOLD : 0));
}
}
refresh ();
/* Create the dialog. */
if (dialog_create (pinentry, &diag))
{
/* Note: pinentry->specific_err has already been set. */
endwin ();
if (screen)
delscreen (screen);
#ifdef HAVE_NCURSESW
if (old_ctype)
{
setlocale (LC_CTYPE, old_ctype);
free (old_ctype);
}
#endif
if (ttyfi)
fclose (ttyfi);
if (ttyfo)
fclose (ttyfo);
return -2;
}
dialog_switch_pos (&diag, confirm_mode? DIALOG_POS_OK : DIALOG_POS_PIN);
#ifndef HAVE_DOSISH_SYSTEM
wtimeout (stdscr, 70);
#endif
do
{
int c;
c = wgetch (stdscr); /* Refresh, accept single keystroke of input. */
#ifndef HAVE_DOSISH_SYSTEM
if (timed_out && no_input)
{
done = -2;
pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT);
break;
}
#endif
switch (c)
{
case ERR:
#ifndef HAVE_DOSISH_SYSTEM
continue;
#else
done = -2;
break;
#endif
case 27: /* Alt was pressed. */
alt = 1;
/* Get the next key press. */
continue;
case KEY_LEFT:
case KEY_UP:
switch (diag.pos)
{
case DIALOG_POS_OK:
if (!confirm_mode)
dialog_switch_pos (&diag, DIALOG_POS_PIN);
break;
case DIALOG_POS_NOTOK:
dialog_switch_pos (&diag, DIALOG_POS_OK);
break;
case DIALOG_POS_CANCEL:
if (diag.notok)
dialog_switch_pos (&diag, DIALOG_POS_NOTOK);
else
dialog_switch_pos (&diag, DIALOG_POS_OK);
break;
default:
break;
}
break;
case KEY_RIGHT:
case KEY_DOWN:
switch (diag.pos)
{
case DIALOG_POS_PIN:
dialog_switch_pos (&diag, DIALOG_POS_OK);
break;
case DIALOG_POS_OK:
if (diag.notok)
dialog_switch_pos (&diag, DIALOG_POS_NOTOK);
else
dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
break;
case DIALOG_POS_NOTOK:
dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
break;
default:
break;
}
break;
case '\t':
switch (diag.pos)
{
case DIALOG_POS_PIN:
dialog_switch_pos (&diag, DIALOG_POS_OK);
break;
case DIALOG_POS_OK:
if (diag.notok)
dialog_switch_pos (&diag, DIALOG_POS_NOTOK);
else
dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
break;
case DIALOG_POS_NOTOK:
dialog_switch_pos (&diag, DIALOG_POS_CANCEL);
break;
case DIALOG_POS_CANCEL:
if (confirm_mode)
dialog_switch_pos (&diag, DIALOG_POS_OK);
else
dialog_switch_pos (&diag, DIALOG_POS_PIN);
break;
default:
break;
}
break;
case '\005':
done = -2;
break;
case '\r':
switch (diag.pos)
{
case DIALOG_POS_PIN:
case DIALOG_POS_OK:
done = 1;
break;
case DIALOG_POS_NOTOK:
done = -1;
break;
case DIALOG_POS_CANCEL:
done = -2;
break;
case DIALOG_POS_NONE:
break;
}
break;
default:
if (diag.pos == DIALOG_POS_PIN)
dialog_input (&diag, alt, c);
}
#ifndef HAVE_DOSISH_SYSTEM
no_input = 0;
#endif
if (c != -1)
alt = 0;
}
while (!done);
if (!confirm_mode)
{
/* NUL terminate the passphrase. dialog_run makes sure there is
enough space for the terminating NUL byte. */
diag.pinentry->pin[diag.pin_len] = 0;
}
set_cursor_state (1);
endwin ();
if (screen)
delscreen (screen);
#ifdef HAVE_NCURSESW
if (old_ctype)
{
setlocale (LC_CTYPE, old_ctype);
free (old_ctype);
}
#endif
if (ttyfi)
fclose (ttyfi);
if (ttyfo)
fclose (ttyfo);
/* XXX Factor out into dialog_release or something. */
free (diag.ok);
if (diag.cancel)
free (diag.cancel);
if (diag.notok)
free (diag.notok);
if (!confirm_mode)
{
pinentry->locale_err = 1;
pin_utf8 = pinentry_local_to_utf8 (pinentry->lc_ctype, pinentry->pin, 1);
if (pin_utf8)
{
pinentry_setbufferlen (pinentry, strlen (pin_utf8) + 1);
if (pinentry->pin)
strcpy (pinentry->pin, pin_utf8);
secmem_free (pin_utf8);
pinentry->locale_err = 0;
}
}
if (done == -2)
pinentry->canceled = 1;
/* In confirm mode return cancel instead of error. */
if (confirm_mode)
return done < 0 ? 0 : 1;
return done < 0 ? -1 : diag.pin_len;
}
/* If a touch has been registered, touch that file. */
static void
do_touch_file (pinentry_t pinentry)
{
#ifdef HAVE_UTIME_H
struct stat st;
time_t tim;
if (!pinentry->touch_file || !*pinentry->touch_file)
return;
if (stat (pinentry->touch_file, &st))
return; /* Oops. */
/* Make sure that we actually update the mtime. */
while ( (tim = time (NULL)) == st.st_mtime )
sleep (1);
/* Update but ignore errors as we can't do anything in that case.
Printing error messages may even clubber the display further. */
utime (pinentry->touch_file, NULL);
#endif /*HAVE_UTIME_H*/
}
#ifndef HAVE_DOSISH_SYSTEM
static void
catchsig (int sig)
{
if (sig == SIGALRM)
timed_out = 1;
}
#endif
int
curses_cmd_handler (pinentry_t pinentry)
{
int rc;
#ifndef HAVE_DOSISH_SYSTEM
timed_out = 0;
if (pinentry->timeout)
{
struct sigaction sa;
memset (&sa, 0, sizeof(sa));
sa.sa_handler = catchsig;
sigaction (SIGALRM, &sa, NULL);
alarm (pinentry->timeout);
}
#endif
rc = dialog_run (pinentry, pinentry->ttyname, pinentry->ttytype_l);
do_touch_file (pinentry);
return rc;
}
diff --git a/pinentry/pinentry-emacs.c b/pinentry/pinentry-emacs.c
index 16ae1c2..9685b67 100644
--- a/pinentry/pinentry-emacs.c
+++ b/pinentry/pinentry-emacs.c
@@ -1,721 +1,721 @@
/* pinentry-emacs.c - A secure emacs dialog for PIN entry, library version
* Copyright (C) 2015 Daiki Ueno
*
* 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 .
* SPDX-License-Identifier: GPL-2.0+
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#ifdef HAVE_STDINT_H
#include
#endif
#ifdef HAVE_INTTYPES_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_UTIME_H
#include
#endif /*HAVE_UTIME_H*/
#include
#include "pinentry-emacs.h"
#include "memory.h"
#include "secmem-util.h"
/* The communication mechanism is similar to emacsclient, but there
are a few differences:
- To avoid unnecessary character escaping and encoding conversion,
we use a subset of the Pinentry Assuan protocol, instead of the
emacsclient protocol.
- We only use a Unix domain socket, while emacsclient has an
ability to use a TCP socket. The socket file is located at
${TMPDIR-/tmp}/emacs$(id -u)/pinentry (i.e., under the same
directory as the socket file used by emacsclient, so the same
permission and file owner settings apply).
- The server implementation can be found in pinentry.el, which is
available in Emacs 25+ or from ELPA. */
#define LINELENGTH ASSUAN_LINELENGTH
#define SEND_BUFFER_SIZE 4096
#define INITIAL_TIMEOUT 60
static int initial_timeout = INITIAL_TIMEOUT;
#undef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#undef MAX
#define MAX(x, y) ((x) < (y) ? (y) : (x))
#ifndef SUN_LEN
# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
+ strlen ((ptr)->sun_path))
#endif
/* FIXME: We could use the I/O functions in Assuan directly, once
Pinentry links to libassuan. */
static int emacs_socket = -1;
static char send_buffer[SEND_BUFFER_SIZE + 1];
static int send_buffer_length; /* Fill pointer for the send buffer. */
static pinentry_cmd_handler_t fallback_cmd_handler;
#ifndef HAVE_DOSISH_SYSTEM
static int timed_out;
#endif
static int
set_socket (const char *socket_name)
{
struct sockaddr_un unaddr;
struct stat statbuf;
const char *tmpdir;
char *tmpdir_storage = NULL;
char *socket_name_storage = NULL;
uid_t uid;
unaddr.sun_family = AF_UNIX;
/* We assume 32-bit UIDs, which can be represented with 10 decimal
digits. */
uid = getuid ();
if (uid != (uint32_t) uid)
{
fprintf (stderr, "UID is too large\n");
return 0;
}
tmpdir = getenv ("TMPDIR");
if (!tmpdir)
{
#ifdef _CS_DARWIN_USER_TEMP_DIR
size_t n = confstr (_CS_DARWIN_USER_TEMP_DIR, NULL, (size_t) 0);
if (n > 0)
{
tmpdir = tmpdir_storage = malloc (n);
if (!tmpdir)
{
fprintf (stderr, "out of core\n");
return 0;
}
confstr (_CS_DARWIN_USER_TEMP_DIR, tmpdir_storage, n);
}
else
#endif
tmpdir = "/tmp";
}
socket_name_storage = malloc (strlen (tmpdir)
+ strlen ("/emacs") + 10 + strlen ("/")
+ strlen (socket_name)
+ 1);
if (!socket_name_storage)
{
fprintf (stderr, "out of core\n");
free (tmpdir_storage);
return 0;
}
sprintf (socket_name_storage, "%s/emacs%u/%s", tmpdir,
(uint32_t) uid, socket_name);
free (tmpdir_storage);
if (strlen (socket_name_storage) >= sizeof (unaddr.sun_path))
{
fprintf (stderr, "socket name is too long\n");
free (socket_name_storage);
return 0;
}
strcpy (unaddr.sun_path, socket_name_storage);
free (socket_name_storage);
/* See if the socket exists, and if it's owned by us. */
if (stat (unaddr.sun_path, &statbuf) == -1)
{
perror ("stat");
return 0;
}
if (statbuf.st_uid != geteuid ())
{
fprintf (stderr, "socket is not owned by the same user\n");
return 0;
}
emacs_socket = socket (AF_UNIX, SOCK_STREAM, 0);
if (emacs_socket < 0)
{
perror ("socket");
return 0;
}
if (connect (emacs_socket, (struct sockaddr *) &unaddr,
SUN_LEN (&unaddr)) < 0)
{
perror ("connect");
close (emacs_socket);
emacs_socket = -1;
return 0;
}
return 1;
}
/* Percent-escape control characters in DATA. Return a newly
allocated string. */
static char *
escape (const char *data)
{
char *buffer, *out_p;
size_t length, buffer_length;
size_t offset;
size_t count = 0;
length = strlen (data);
for (offset = 0; offset < length; offset++)
{
switch (data[offset])
{
case '%': case '\n': case '\r':
count++;
break;
default:
break;
}
}
buffer_length = length + count * 2;
buffer = malloc (buffer_length + 1);
if (!buffer)
return NULL;
out_p = buffer;
for (offset = 0; offset < length; offset++)
{
int c = data[offset];
switch (c)
{
case '%': case '\n': case '\r':
sprintf (out_p, "%%%02X", c);
out_p += 3;
break;
default:
*out_p++ = c;
break;
}
}
*out_p = '\0';
return buffer;
}
/* The inverse of escape. Unlike escape, it removes quoting in string
DATA by modifying the string in place, to avoid copying of secret
data sent from Emacs. */
static char *
unescape (char *data)
{
char *p = data, *q = data;
while (*p)
{
if (*p == '%' && p[1] && p[2])
{
p++;
*q++ = xtoi_2 (p);
p += 2;
}
else
*q++ = *p++;
}
*q = 0;
return data;
}
/* Let's send the data to Emacs when either
- the data ends in "\n", or
- the buffer is full (but this shouldn't happen)
Otherwise, we just accumulate it. */
static int
send_to_emacs (int s, const char *buffer)
{
size_t length;
length = strlen (buffer);
while (*buffer)
{
size_t part = MIN (length, SEND_BUFFER_SIZE - send_buffer_length);
memcpy (&send_buffer[send_buffer_length], buffer, part);
buffer += part;
send_buffer_length += part;
if (send_buffer_length == SEND_BUFFER_SIZE
|| (send_buffer_length > 0
&& send_buffer[send_buffer_length-1] == '\n'))
{
int sent = send (s, send_buffer, send_buffer_length, 0);
if (sent < 0)
{
fprintf (stderr, "failed to send %d bytes to socket: %s\n",
send_buffer_length, strerror (errno));
send_buffer_length = 0;
return 0;
}
if (sent != send_buffer_length)
memmove (send_buffer, &send_buffer[sent],
send_buffer_length - sent);
send_buffer_length -= sent;
}
length -= part;
}
return 1;
}
/* Read a server response. If the response contains data, it will be
stored in BUFFER with a terminating NUL byte. BUFFER must be
at least as large as CAPACITY. */
static gpg_error_t
read_from_emacs (int s, int timeout, char *buffer, size_t capacity)
{
struct timeval tv;
fd_set rfds;
int retval;
/* Offset in BUFFER. */
size_t offset = 0;
int got_response = 0;
char read_buffer[LINELENGTH + 1];
/* Offset in READ_BUFFER. */
size_t read_offset = 0;
gpg_error_t result = 0;
tv.tv_sec = timeout;
tv.tv_usec = 0;
FD_ZERO (&rfds);
FD_SET (s, &rfds);
retval = select (s + 1, &rfds, NULL, NULL, &tv);
if (retval == -1)
{
perror ("select");
return gpg_error (GPG_ERR_ASS_GENERAL);
}
else if (retval == 0)
{
timed_out = 1;
return gpg_error (GPG_ERR_TIMEOUT);
}
/* Loop until we get either OK or ERR. */
while (!got_response)
{
int rl = 0;
char *p, *end_p;
do
{
errno = 0;
rl = recv (s, read_buffer + read_offset, LINELENGTH - read_offset, 0);
}
/* If we receive a signal (e.g. SIGWINCH, which we pass
through to Emacs), on some OSes we get EINTR and must retry. */
while (rl < 0 && errno == EINTR);
if (rl < 0)
{
perror ("recv");
return gpg_error (GPG_ERR_ASS_GENERAL);;
}
if (rl == 0)
break;
read_offset += rl;
read_buffer[read_offset] = '\0';
end_p = strchr (read_buffer, '\n');
/* If the buffer is filled without NL, throw away the content
and start over the buffering.
FIXME: We could return ASSUAN_Line_Too_Long or
ASSUAN_Line_Not_Terminated here. */
if (!end_p && read_offset == sizeof (read_buffer) - 1)
{
read_offset = 0;
continue;
}
/* Loop over all NL-terminated messages. */
for (p = read_buffer; end_p; p = end_p + 1, end_p = strchr (p, '\n'))
{
*end_p = '\0';
if (!strncmp ("D ", p, 2))
{
char *data;
size_t data_length;
size_t needed_capacity;
data = p + 2;
data_length = end_p - data;
if (data_length > 0)
{
needed_capacity = offset + data_length + 1;
/* Check overflow. This is unrealistic but can
happen since OFFSET is cumulative. */
if (needed_capacity < offset)
return gpg_error (GPG_ERR_ASS_GENERAL);;
if (needed_capacity > capacity)
return gpg_error (GPG_ERR_ASS_GENERAL);;
memcpy (&buffer[offset], data, data_length);
offset += data_length;
buffer[offset] = 0;
}
}
else if (!strcmp ("OK", p) || !strncmp ("OK ", p, 3))
{
got_response = 1;
break;
}
else if (!strncmp ("ERR ", p, 4))
{
unsigned long code = strtoul (p + 4, NULL, 10);
if (code == ULONG_MAX && errno == ERANGE)
return gpg_error (GPG_ERR_ASS_GENERAL);
else
result = code;
got_response = 1;
break;
}
else if (*p == '#')
;
else
fprintf (stderr, "invalid response: %s\n", p);
}
if (!got_response)
{
size_t length = &read_buffer[read_offset] - p;
memmove (read_buffer, p, length);
read_offset = length;
}
}
return result;
}
int
set_label (pinentry_t pe, const char *name, const char *value)
{
char buffer[16], *escaped;
gpg_error_t error;
int retval;
if (!send_to_emacs (emacs_socket, name)
|| !send_to_emacs (emacs_socket, " "))
return 0;
escaped = escape (value);
if (!escaped)
return 0;
retval = send_to_emacs (emacs_socket, escaped)
&& send_to_emacs (emacs_socket, "\n");
free (escaped);
if (!retval)
return 0;
error = read_from_emacs (emacs_socket, pe->timeout, buffer, sizeof (buffer));
return error == 0;
}
static void
set_labels (pinentry_t pe)
{
char *p;
p = pinentry_get_title (pe);
if (p)
{
set_label (pe, "SETTITLE", p);
free (p);
}
if (pe->description)
set_label (pe, "SETDESC", pe->description);
if (pe->error)
set_label (pe, "SETERROR", pe->error);
if (pe->prompt)
set_label (pe, "SETPROMPT", pe->prompt);
else if (pe->default_prompt)
set_label (pe, "SETPROMPT", pe->default_prompt);
if (pe->repeat_passphrase)
set_label (pe, "SETREPEAT", pe->repeat_passphrase);
if (pe->repeat_error_string)
set_label (pe, "SETREPEATERROR", pe->repeat_error_string);
/* XXX: pe->quality_bar and pe->quality_bar_tt are not supported. */
/* Buttons. */
if (pe->ok)
set_label (pe, "SETOK", pe->ok);
else if (pe->default_ok)
set_label (pe, "SETOK", pe->default_ok);
if (pe->cancel)
set_label (pe, "SETCANCEL", pe->cancel);
- else if (pe->default_ok)
+ else if (pe->default_cancel)
set_label (pe, "SETCANCEL", pe->default_cancel);
if (pe->notok)
set_label (pe, "SETNOTOK", pe->notok);
}
static int
do_password (pinentry_t pe)
{
char *buffer, *password;
size_t length = LINELENGTH;
gpg_error_t error;
set_labels (pe);
if (!send_to_emacs (emacs_socket, "GETPIN\n"))
return -1;
buffer = secmem_malloc (length);
if (!buffer)
{
pe->specific_err = gpg_error (GPG_ERR_ENOMEM);
return -1;
}
error = read_from_emacs (emacs_socket, pe->timeout, buffer, length);
if (error != 0)
{
if (gpg_err_code (error) == GPG_ERR_CANCELED)
pe->canceled = 1;
secmem_free (buffer);
pe->specific_err = error;
return -1;
}
password = unescape (buffer);
pinentry_setbufferlen (pe, strlen (password) + 1);
if (pe->pin)
strcpy (pe->pin, password);
secmem_free (buffer);
if (pe->repeat_passphrase)
pe->repeat_okay = 1;
/* XXX: we don't support external password cache (yet). */
return 1;
}
static int
do_confirm (pinentry_t pe)
{
char buffer[16];
gpg_error_t error;
set_labels (pe);
if (!send_to_emacs (emacs_socket, "CONFIRM\n"))
return 0;
error = read_from_emacs (emacs_socket, pe->timeout, buffer, sizeof (buffer));
if (error != 0)
{
if (gpg_err_code (error) == GPG_ERR_CANCELED)
pe->canceled = 1;
pe->specific_err = error;
return 0;
}
return 1;
}
/* If a touch has been registered, touch that file. */
static void
do_touch_file (pinentry_t pinentry)
{
#ifdef HAVE_UTIME_H
struct stat st;
time_t tim;
if (!pinentry->touch_file || !*pinentry->touch_file)
return;
if (stat (pinentry->touch_file, &st))
return; /* Oops. */
/* Make sure that we actually update the mtime. */
while ( (tim = time (NULL)) == st.st_mtime )
sleep (1);
/* Update but ignore errors as we can't do anything in that case.
Printing error messages may even clubber the display further. */
utime (pinentry->touch_file, NULL);
#endif /*HAVE_UTIME_H*/
}
#ifndef HAVE_DOSISH_SYSTEM
static void
catchsig (int sig)
{
if (sig == SIGALRM)
timed_out = 1;
}
#endif
int
emacs_cmd_handler (pinentry_t pe)
{
int rc;
#ifndef HAVE_DOSISH_SYSTEM
timed_out = 0;
if (pe->timeout)
{
struct sigaction sa;
memset (&sa, 0, sizeof(sa));
sa.sa_handler = catchsig;
sigaction (SIGALRM, &sa, NULL);
alarm (pe->timeout);
}
#endif
if (pe->pin)
rc = do_password (pe);
else
rc = do_confirm (pe);
do_touch_file (pe);
return rc;
}
static int
initial_emacs_cmd_handler (pinentry_t pe)
{
/* Let the select() call in pinentry_emacs_init honor the timeout
value set through an Assuan option. */
initial_timeout = pe->timeout;
if (emacs_socket < 0)
pinentry_emacs_init ();
/* If we have successfully connected to Emacs, swap
pinentry_cmd_handler to emacs_cmd_handler, so further
interactions will be forwarded to Emacs. Otherwise, set it back
to the original command handler saved as
fallback_cmd_handler. */
if (emacs_socket < 0)
pinentry_cmd_handler = fallback_cmd_handler;
else
{
pinentry_cmd_handler = emacs_cmd_handler;
pinentry_set_flavor_flag ("emacs");
}
return (* pinentry_cmd_handler) (pe);
}
void
pinentry_enable_emacs_cmd_handler (void)
{
const char *envvar;
/* Check if pinentry_cmd_handler is already prepared for Emacs. */
if (pinentry_cmd_handler == initial_emacs_cmd_handler
|| pinentry_cmd_handler == emacs_cmd_handler)
return;
/* Check if INSIDE_EMACS envvar is set. */
envvar = getenv ("INSIDE_EMACS");
if (!envvar || !*envvar)
return;
/* Save the original command handler as fallback_cmd_handler, and
swap pinentry_cmd_handler to initial_emacs_cmd_handler. */
fallback_cmd_handler = pinentry_cmd_handler;
pinentry_cmd_handler = initial_emacs_cmd_handler;
}
/* Returns true if the Emacs pinentry is enabled. The value is 1
* before the first connection with Emacs has been done and 2 if the
* connection to Emacs has been establish. Returns false if the Emacs
* pinentry is not enabled. */
int
pinentry_emacs_status (void)
{
if (pinentry_cmd_handler == initial_emacs_cmd_handler)
return 1;
else if (pinentry_cmd_handler == emacs_cmd_handler)
return 2;
else
return 0;
}
int
pinentry_emacs_init (void)
{
char buffer[256];
gpg_error_t error;
assert (emacs_socket < 0);
/* Check if we can connect to the Emacs server socket. */
if (!set_socket ("pinentry"))
return 0;
/* Check if the server responds. */
error = read_from_emacs (emacs_socket, initial_timeout,
buffer, sizeof (buffer));
if (error != 0)
{
close (emacs_socket);
emacs_socket = -1;
return 0;
}
return 1;
}
diff --git a/pinentry/pinentry.c b/pinentry/pinentry.c
index ef81f12..26ec77a 100644
--- a/pinentry/pinentry.c
+++ b/pinentry/pinentry.c
@@ -1,2069 +1,2070 @@
/* 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 .
* SPDX-License-Identifier: GPL-2.0+
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#ifndef HAVE_W32CE_SYSTEM
# include
#endif
#include
#include
#include
#include
#include
#include
#ifndef HAVE_W32_SYSTEM
# include
#endif
#ifndef HAVE_W32CE_SYSTEM
# include
#endif
#ifdef HAVE_LANGINFO_H
#include
#endif
#include
#ifdef HAVE_W32CE_SYSTEM
# include
#endif
#undef WITH_UTF8_CONVERSION
#if defined FALLBACK_CURSES || defined PINENTRY_CURSES || defined PINENTRY_GTK
# include
# define WITH_UTF8_CONVERSION 1
#endif
#include
#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] ",
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);
+ 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 = 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
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
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);
}
diff --git a/tty/pinentry-tty.c b/tty/pinentry-tty.c
index 403dd60..4a2b67f 100644
--- a/tty/pinentry-tty.c
+++ b/tty/pinentry-tty.c
@@ -1,612 +1,612 @@
/* pinentry-tty.c - A minimalist dumb terminal mechanism for PIN entry
* Copyright (C) 2014 Serge Voilokov
* Copyright (C) 2015 Daniel Kahn Gillmor
* Copyright (C) 2015 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 .
* SPDX-License-Identifier: GPL-2.0+
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_UTIME_H
#include
#endif /*HAVE_UTIME_H*/
#include
#include
#include
#include
#include "pinentry.h"
#include "memory.h"
#ifndef HAVE_DOSISH_SYSTEM
static int timed_out;
#endif
static struct termios n_term;
static struct termios o_term;
static int
terminal_save (int fd)
{
if ((tcgetattr (fd, &o_term)) == -1)
return -1;
return 0;
}
static void
terminal_restore (int fd)
{
tcsetattr (fd, TCSANOW, &o_term);
}
static int
terminal_setup (int fd, int line_edit)
{
n_term = o_term;
if (line_edit)
n_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
else
{
n_term.c_lflag &= ~(ECHO|ICANON);
n_term.c_lflag |= ISIG;
n_term.c_cc[VMIN] = 1;
n_term.c_cc[VTIME]= 0;
}
if ((tcsetattr(fd, TCSAFLUSH, &n_term)) == -1)
return -1;
return 1;
}
#define UNDERLINE_START "\033[4m"
/* Bold, red. */
#define ALERT_START "\033[1;31m"
#define NORMAL_RESTORE "\033[0m"
static void
fputs_highlighted (char *text, char *highlight, FILE *ttyfo)
{
for (; *text; text ++)
{
/* Skip accelerator prefix. */
if (*text == '_')
{
text ++;
if (! *text)
break;
}
if (text == highlight)
fputs (UNDERLINE_START, ttyfo);
fputc (*text, ttyfo);
if (text == highlight)
fputs (NORMAL_RESTORE, ttyfo);
}
}
static char
button (char *text, char *default_text, FILE *ttyfo)
{
char *highlight;
int use_default = 0;
if (! text)
return 0;
/* Skip any leading white space. */
while (*text == ' ')
text ++;
highlight = text;
while ((highlight = strchr (highlight, '_')))
{
highlight = highlight + 1;
if (*highlight == '_')
{
/* Escaped underscore. Skip both characters. */
highlight++;
continue;
}
if (!isalnum (*highlight))
/* Unusable accelerator. */
continue;
break;
}
if (! highlight)
/* Not accelerator. Take the first alpha-numeric character. */
{
highlight = text;
while (*highlight && !isalnum (*highlight))
highlight ++;
}
if (! *highlight)
/* Hmm, no alpha-numeric characters. */
{
if (! default_text)
return 0;
highlight = default_text;
use_default = 1;
}
fputs (" ", ttyfo);
fputs_highlighted (text, highlight, ttyfo);
if (use_default)
{
fputs (" (", ttyfo);
fputs_highlighted (default_text, highlight, ttyfo);
fputc (')', ttyfo);
}
fputc ('\n', ttyfo);
return tolower (*highlight);
}
static void
dump_error_text (FILE *ttyfo, const char *text)
{
int lines = 0;
if (! text || ! *text)
return;
for (;;)
{
const char *eol = strchr (text, '\n');
if (! eol)
eol = text + strlen (text);
lines ++;
fwrite ("\n *** ", 6, 1, ttyfo);
fputs (ALERT_START, ttyfo);
fwrite (text, (size_t) (eol - text), 1, ttyfo);
fputs (NORMAL_RESTORE, ttyfo);
if (! *eol)
break;
text = eol + 1;
}
if (lines > 1)
fputc ('\n', ttyfo);
else
fwrite (" ***\n", 5, 1, ttyfo);
fputc ('\n', ttyfo);
}
static int
confirm (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
{
char *msg;
char *msgbuffer = NULL;
char ok = 0;
char notok = 0;
char cancel = 0;
int ret;
dump_error_text (ttyfo, pinentry->error);
msg = pinentry->description;
if (! msg)
{
/* If there is no description, fallback to the title. */
msg = msgbuffer = pinentry_get_title (pinentry);
}
if (! msg)
msg = "Confirm:";
if (msg)
{
fputs (msg, ttyfo);
fputc ('\n', ttyfo);
}
free (msgbuffer);
fflush (ttyfo);
if (pinentry->ok)
ok = button (pinentry->ok, "OK", ttyfo);
else if (pinentry->default_ok)
ok = button (pinentry->default_ok, "OK", ttyfo);
else
ok = button ("OK", NULL, ttyfo);
if (! pinentry->one_button)
{
if (pinentry->cancel)
cancel = button (pinentry->cancel, "Cancel", ttyfo);
else if (pinentry->default_cancel)
cancel = button (pinentry->default_cancel, "Cancel", ttyfo);
if (pinentry->notok)
notok = button (pinentry->notok, "No", ttyfo);
}
while (1)
{
int input;
if (pinentry->one_button)
fprintf (ttyfo, "Press any key to continue.");
else
{
fputc ('[', ttyfo);
if (ok)
fputc (ok, ttyfo);
if (cancel)
fputc (cancel, ttyfo);
if (notok)
fputc (notok, ttyfo);
fputs("]? ", ttyfo);
}
fflush (ttyfo);
input = fgetc (ttyfi);
if (input == EOF)
{
pinentry->close_button = 1;
pinentry->canceled = 1;
#ifndef HAVE_DOSISH_SYSTEM
if (!timed_out && errno == EINTR)
pinentry->specific_err = gpg_error (GPG_ERR_FULLY_CANCELED);
#endif
ret = 0;
break;
}
else
{
fprintf (ttyfo, "%c\n", input);
input = tolower (input);
}
if (pinentry->one_button)
{
ret = 1;
break;
}
if (cancel && input == cancel)
{
pinentry->canceled = 1;
ret = 0;
break;
}
else if (notok && input == notok)
{
ret = 0;
break;
}
else if (ok && input == ok)
{
ret = 1;
break;
}
else
{
fprintf (ttyfo, "Invalid selection.\n");
}
}
#ifndef HAVE_DOSISH_SYSTEM
if (timed_out)
pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT);
#endif
return ret;
}
static char *
read_password (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
{
int done = 0;
int len = 128;
int count = 0;
char *buffer;
(void) ttyfo;
buffer = secmem_malloc (len);
if (! buffer)
return NULL;
while (!done)
{
int c;
if (count == len - 1)
/* Double the buffer's size. Note: we check if count is len -
1 and not len so that we always have space for the NUL
character. */
{
int new_len = 2 * len;
char *tmp = secmem_realloc (buffer, new_len);
if (! tmp)
{
secmem_free (tmp);
return NULL;
}
buffer = tmp;
len = new_len;
}
c = fgetc (ttyfi);
switch (c)
{
case EOF:
done = -1;
#ifndef HAVE_DOSISH_SYSTEM
if (!timed_out && errno == EINTR)
pinentry->specific_err = gpg_error (GPG_ERR_FULLY_CANCELED);
#endif
break;
case '\n':
done = 1;
break;
default:
buffer[count ++] = c;
break;
}
}
buffer[count] = '\0';
if (done == -1)
{
secmem_free (buffer);
return NULL;
}
return buffer;
}
static int
password (pinentry_t pinentry, FILE *ttyfi, FILE *ttyfo)
{
char *msg;
char *msgbuffer = NULL;
int done = 0;
msg = pinentry->description;
if (! msg)
msg = msgbuffer = pinentry_get_title (pinentry);
if (! msg)
msg = "Enter your passphrase.";
dump_error_text (ttyfo, pinentry->error);
fprintf (ttyfo, "%s\n", msg);
free (msgbuffer);
while (! done)
{
char *passphrase;
char *prompt = pinentry->prompt;
if (! prompt || !*prompt)
prompt = "PIN";
fprintf (ttyfo, "%s%s ",
prompt,
/* Make sure the prompt ends in a : or a question mark. */
(prompt[strlen(prompt) - 1] == ':'
|| prompt[strlen(prompt) - 1] == '?') ? "" : ":");
fflush (ttyfo);
passphrase = read_password (pinentry, ttyfi, ttyfo);
fputc ('\n', ttyfo);
if (! passphrase)
{
done = -1;
break;
}
if (! pinentry->repeat_passphrase)
done = 1;
else
{
char *passphrase2;
prompt = pinentry->repeat_passphrase;
fprintf (ttyfo, "%s%s ",
prompt,
/* Make sure the prompt ends in a : or a question mark. */
(prompt[strlen(prompt) - 1] == ':'
|| prompt[strlen(prompt) - 1] == '?') ? "" : ":");
fflush (ttyfo);
passphrase2 = read_password (pinentry, ttyfi, ttyfo);
fputc ('\n', ttyfo);
if (! passphrase2)
{
done = -1;
break;
}
if (strcmp (passphrase, passphrase2) == 0)
{
pinentry->repeat_okay = 1;
done = 1;
}
else
dump_error_text (ttyfo,
pinentry->repeat_error_string
?: "Passphrases don't match.");
secmem_free (passphrase2);
}
if (done == 1)
pinentry_setbuffer_use (pinentry, passphrase, 0);
else
secmem_free (passphrase);
}
#ifndef HAVE_DOSISH_SYSTEM
if (timed_out)
pinentry->specific_err = gpg_error (GPG_ERR_TIMEOUT);
#endif
return done;
}
/* If a touch has been registered, touch that file. */
static void
do_touch_file(pinentry_t pinentry)
{
#ifdef HAVE_UTIME_H
struct stat st;
time_t tim;
if (!pinentry->touch_file || !*pinentry->touch_file)
return;
if (stat(pinentry->touch_file, &st))
return; /* Oops. */
/* Make sure that we actually update the mtime. */
while ((tim = time(NULL)) == st.st_mtime)
sleep(1);
/* Update but ignore errors as we can't do anything in that case.
Printing error messages may even clubber the display further. */
utime (pinentry->touch_file, NULL);
#endif /*HAVE_UTIME_H*/
}
#ifndef HAVE_DOSISH_SYSTEM
static void
catchsig (int sig)
{
if (sig == SIGALRM)
timed_out = 1;
}
#endif
int
tty_cmd_handler (pinentry_t pinentry)
{
int rc = 0;
FILE *ttyfi = stdin;
FILE *ttyfo = stdout;
#ifndef HAVE_DOSISH_SYSTEM
timed_out = 0;
if (pinentry->timeout)
{
struct sigaction sa;
memset (&sa, 0, sizeof(sa));
sa.sa_handler = catchsig;
sigaction (SIGALRM, &sa, NULL);
sigaction (SIGINT, &sa, NULL);
alarm (pinentry->timeout);
}
#endif
if (pinentry->ttyname)
{
ttyfi = fopen (pinentry->ttyname, "r");
if (!ttyfi)
rc = -1;
else
{
ttyfo = fopen (pinentry->ttyname, "w");
if (!ttyfo)
{
int err = errno;
fclose (ttyfi);
errno = err;
rc = -1;
}
}
}
- if (terminal_save (fileno (ttyfi)) < 0)
+ if (!rc && terminal_save (fileno (ttyfi)) < 0)
rc = -1;
if (! rc)
{
if (terminal_setup (fileno (ttyfi), !!pinentry->pin) == -1)
{
int err = errno;
fprintf (stderr, "terminal_setup failure, exiting\n");
errno = err;
}
else
{
if (pinentry->pin)
rc = password (pinentry, ttyfi, ttyfo);
else
rc = confirm (pinentry, ttyfi, ttyfo);
terminal_restore (fileno (ttyfi));
}
}
do_touch_file (pinentry);
if (pinentry->ttyname)
{
fclose (ttyfi);
fclose (ttyfo);
}
return rc;
}
pinentry_cmd_handler_t pinentry_cmd_handler = tty_cmd_handler;
int
main (int argc, char *argv[])
{
pinentry_init ("pinentry-tty");
/* Consumes all arguments. */
pinentry_parse_opts(argc, argv);
if (pinentry_loop ())
return 1;
return 0;
}