You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1411 lines
32 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/* 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 <http://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-2.0+
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <curses.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <locale.h>
#include <iconv.h>
#if defined(HAVE_LANGINFO_H)
# include <langinfo.h>
#elif defined(HAVE_W32_SYSTEM)
# include <stdio.h>
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
/* A simple replacement for nl_langinfo that only understands
CODESET. */
# define CODESET 1
char *
nl_langinfo (int ignore)
{
static char codepage[20];
UINT cp = GetACP ();
(void)ignore;
sprintf (codepage, "CP%u", cp);
return codepage;
}
#endif
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UTIME_H
#include <utime.h>
#endif /*HAVE_UTIME_H*/
#include <memory.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /*HAVE_WCHAR_H*/
#include <assuan.h>
#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 "<OK>"
#define STRING_NOTOK "<No>"
#define STRING_CANCEL "<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;
/* Flag to remember whether a warning has been printed. */
static int lc_ctype_unknown_warning;
static 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;
const char *pgmname = pinentry_get_pgmname ();
/* 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",
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",
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",
pgmname, target_encoding, strerror (errno));
free (output_buf);
return NULL;
}
return output_buf;
}
/* 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. */
static 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;
const char *pgmname = pinentry_get_pgmname ();
/* 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",
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",
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",
pgmname, source_encoding? source_encoding : "?",
strerror (errno));
if (secure)
secmem_free (output_buf);
else
free (output_buf);
return NULL;
}
return output_buf;
}
#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);
if (!screen)
{
pinentry->specific_err = gpg_error (GPG_ERR_WINDOW_TOO_SMALL);
pinentry->specific_err_loc = "curses_init";
fclose (ttyfo);
fclose (ttyfi);
#ifdef HAVE_NCURSESW
free (old_ctype);
#endif
return confirm_mode? 0 : -1;
}
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 ();
/* Ncurses has use_default_colors, an extentions to the curses
library, which allows use of -1 to select default color. */
#ifdef NCURSES_VERSION
use_default_colors ();
#else
/* With no extention, we need to specify color explicitly. */
if (pinentry->color_fg == PINENTRY_COLOR_DEFAULT)
pinentry->color_fg = PINENTRY_COLOR_WHITE;
if (pinentry->color_bg == PINENTRY_COLOR_DEFAULT)
pinentry->color_bg = PINENTRY_COLOR_BLACK;
#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;
}