diff options
Diffstat (limited to 'common/ttyio.c')
-rw-r--r-- | common/ttyio.c | 751 |
1 files changed, 751 insertions, 0 deletions
diff --git a/common/ttyio.c b/common/ttyio.c new file mode 100644 index 0000000..c310817 --- /dev/null +++ b/common/ttyio.c @@ -0,0 +1,751 @@ +/* ttyio.c - tty i/O functions + * Copyright (C) 1997-2019 Werner Koch + * Copyright (C) 1998-2020 Free Software Foundation, Inc. + * Copyright (C) 2015-2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <unistd.h> + +#ifdef HAVE_TCGETATTR +# include <termios.h> +#else +# ifdef HAVE_TERMIO_H +/* simulate termios with termio */ +# include <termio.h> +# define termios termio +# define tcsetattr ioctl +# define TCSAFLUSH TCSETAF +# define tcgetattr(A,B) ioctl(A,TCGETA,B) +# define HAVE_TCGETATTR +# endif +#endif +#ifdef HAVE_W32_SYSTEM +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> +# ifdef HAVE_TCGETATTR +# error mingw32 and termios +# endif +#endif +#include <errno.h> +#include <ctype.h> + +#include "util.h" +#include "ttyio.h" +#include "i18n.h" +#include "common-defs.h" + +#define CONTROL_D ('D' - 'A' + 1) + + +#ifdef HAVE_W32_SYSTEM +static struct { + HANDLE in, out; +} con; +#define DEF_INPMODE (ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT \ + |ENABLE_PROCESSED_INPUT ) +#define HID_INPMODE (ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT ) +#define DEF_OUTMODE (ENABLE_WRAP_AT_EOL_OUTPUT|ENABLE_PROCESSED_OUTPUT) + +#else /* Unix */ +static FILE *ttyfp = NULL; +#endif /* Unix */ + +static int initialized; +static int last_prompt_len; +static int batchmode; +static int no_terminal; + +#ifdef HAVE_TCGETATTR + static struct termios termsave; + static int restore_termios; +#endif + +/* Hooks set by gpgrlhelp.c if required. */ +static void (*my_rl_set_completer) (rl_completion_func_t *); +static void (*my_rl_inhibit_completion) (int); +static void (*my_rl_cleanup_after_signal) (void); +static void (*my_rl_init_stream) (FILE *); +static char *(*my_rl_readline) (const char*); +static void (*my_rl_add_history) (const char*); + + +/* This is a wrapper around ttyname so that we can use it even when + the standard streams are redirected. It figures the name out the + first time and returns it in a statically allocated buffer. */ +const char * +tty_get_ttyname (void) +{ + static char *name; + + /* On a GNU system ctermid() always return /dev/tty, so this does + not make much sense - however if it is ever changed we do the + Right Thing now. */ +#ifdef HAVE_CTERMID + static int got_name; + + if (!got_name) + { + const char *s; + /* Note that despite our checks for these macros the function is + not necessarily thread save. We mainly do this for + portability reasons, in case L_ctermid is not defined. */ +# if defined(_POSIX_THREAD_SAFE_FUNCTIONS) || defined(_POSIX_TRHEADS) + char buffer[L_ctermid]; + s = ctermid (buffer); +# else + s = ctermid (NULL); +# endif + if (s) + name = strdup (s); + got_name = 1; + } +#endif /*HAVE_CTERMID*/ + /* Assume the standard tty on memory error or when there is no + ctermid. */ + return name? name : "/dev/tty"; +} + + + +#ifdef HAVE_TCGETATTR +static void +cleanup(void) +{ + if (restore_termios) + { + restore_termios = 0; /* do it prior in case it is interrupted again */ + if (tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave)) + log_error ("tcsetattr() failed: %s\n", strerror (errno)); + } +} +#endif /*HAVE_TCGETATTR*/ + + +static void +init_ttyfp(void) +{ + if (initialized) + return; + +#ifdef HAVE_W32_SYSTEM + { + SECURITY_ATTRIBUTES sa; + + memset (&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + con.out = CreateFileA ("CONOUT$", GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + &sa, OPEN_EXISTING, 0, 0 ); + if (con.out == INVALID_HANDLE_VALUE) + log_fatal ("open(CONOUT$) failed: %s\n", w32_strerror (-1)); + + memset (&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + con.in = CreateFileA ("CONIN$", GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + &sa, OPEN_EXISTING, 0, 0 ); + if (con.in == INVALID_HANDLE_VALUE) + log_fatal ("open(CONIN$) failed: %s\n", w32_strerror (-1)); + } + SetConsoleMode (con.in, DEF_INPMODE); + SetConsoleMode (con.out, DEF_OUTMODE); + +#else /* Unix */ + ttyfp = batchmode? stderr : fopen (tty_get_ttyname (), "r+"); + if (!ttyfp) + { + log_error ("cannot open '%s': %s\n", tty_get_ttyname (), strerror(errno)); + exit (2); + } + if (my_rl_init_stream) + my_rl_init_stream (ttyfp); +#endif /* Unix */ + +#ifdef HAVE_TCGETATTR + atexit (cleanup); +#endif + + initialized = 1; +} + + +int +tty_batchmode( int onoff ) +{ + int old = batchmode; + if (onoff != -1) + batchmode = onoff; + return old; +} + +int +tty_no_terminal(int onoff) +{ + int old = no_terminal; + no_terminal = onoff ? 1 : 0; + return old; +} + + +#ifdef HAVE_W32_SYSTEM +/* Write the UTF-8 encoded STRING to the console. */ +static void +w32_write_console (const char *string) +{ + wchar_t *wstring; + DWORD n, nwritten; + + wstring = utf8_to_wchar (string); + if (!wstring) + log_fatal ("w32_write_console failed: %s", strerror (errno)); + n = wcslen (wstring); + + if (!WriteConsoleW (con.out, wstring, n, &nwritten, NULL)) + { + static int shown; + if (!shown) + { + shown = 1; + log_info ("WriteConsole failed: %s", w32_strerror (-1)); + log_info ("Please configure a suitable font for the console\n"); + } + n = strlen (string); + if (!WriteConsoleA (con.out, string, n , &nwritten, NULL)) + log_fatal ("WriteConsole fallback failed: %s", w32_strerror (-1)); + } + else + { + if (n != nwritten) + log_fatal ("WriteConsole failed: %lu != %lu\n", + (unsigned long)n, (unsigned long)nwritten); + } + last_prompt_len += n; + xfree (wstring); +} +#endif /*HAVE_W32_SYSTEM*/ + + +void +tty_printf (const char *fmt, ... ) +{ + va_list arg_ptr; + + if (no_terminal) + return; + + if (!initialized) + init_ttyfp (); + + va_start (arg_ptr, fmt); + +#ifdef HAVE_W32_SYSTEM + { + char *buf = NULL; + + vasprintf(&buf, fmt, arg_ptr); + if (!buf) + log_bug ("vasprintf() failed\n"); + w32_write_console (buf); + xfree (buf); + } +#else /* Unix */ + last_prompt_len += vfprintf (ttyfp, fmt, arg_ptr) ; + fflush (ttyfp); +#endif /* Unix */ + va_end(arg_ptr); +} + + +/* Same as tty_printf but if FP is not NULL, behave like a regular + fprintf. */ +void +tty_fprintf (estream_t fp, const char *fmt, ... ) +{ + va_list arg_ptr; + + if (fp) + { + va_start (arg_ptr, fmt) ; + es_vfprintf (fp, fmt, arg_ptr ); + va_end (arg_ptr); + return; + } + + if (no_terminal) + return; + + if (!initialized) + init_ttyfp (); + + va_start (arg_ptr, fmt); + +#ifdef HAVE_W32_SYSTEM + { + char *buf = NULL; + + vasprintf (&buf, fmt, arg_ptr); + if (!buf) + log_bug ("vasprintf() failed\n"); + w32_write_console (buf); + xfree (buf); + } +#else /* Unix */ + last_prompt_len += vfprintf(ttyfp,fmt,arg_ptr) ; + fflush(ttyfp); +#endif /* Unix */ + + va_end(arg_ptr); +} + + +/* Print a string, but filter all control characters out. If FP is + * not NULL print to that stream instead to the tty. */ +static void +do_print_string (estream_t fp, const byte *p, size_t n ) +{ + if (no_terminal && !fp) + return; + + if (!initialized && !fp) + init_ttyfp(); + + if (fp) + { + print_utf8_buffer (fp, p, n); + return; + } + +#ifdef HAVE_W32_SYSTEM + /* Not so effective, change it if you want */ + for (; n; n--, p++) + { + if (iscntrl (*p)) + { + if( *p == '\n' ) + tty_printf ("\\n"); + else if( !*p ) + tty_printf ("\\0"); + else + tty_printf ("\\x%02x", *p); + } + else + tty_printf ("%c", *p); + } +#else /* Unix */ + for (; n; n--, p++) + { + if (iscntrl (*p)) + { + putc ('\\', ttyfp); + if ( *p == '\n' ) + putc ('n', ttyfp); + else if ( !*p ) + putc ('0', ttyfp); + else + fprintf (ttyfp, "x%02x", *p ); + } + else + putc (*p, ttyfp); + } +#endif /* Unix */ +} + + +void +tty_print_utf8_string2 (estream_t fp, const byte *p, size_t n, size_t max_n) +{ + size_t i; + char *buf; + + if (no_terminal && !fp) + return; + + /* We can handle plain ascii simpler, so check for it first. */ + for(i=0; i < n; i++ ) + { + if (p[i] & 0x80) + break; + } + if (i < n) + { + buf = utf8_to_native ((const char *)p, n, 0); + if (max_n && (strlen (buf) > max_n)) + buf[max_n] = 0; + /* (utf8_to_native already did the control character quoting) */ + tty_fprintf (fp, "%s", buf); + xfree (buf); + } + else + { + if (max_n && (n > max_n)) + n = max_n; + do_print_string (fp, p, n ); + } +} + + +void +tty_print_utf8_string (const byte *p, size_t n) +{ + tty_print_utf8_string2 (NULL, p, n, 0); +} + + +/* Read a string from the tty using PROMPT. If HIDDEN is set the + * input is not echoed. */ +static char * +do_get (const char *prompt, int hidden) +{ + char *buf; + int n; /* Allocated size of BUF. */ + int i; /* Number of bytes in BUF. */ + int c; +#ifdef HAVE_W32_SYSTEM + char *utf8buf; + int errcount = 0; +#else + byte cbuf[1]; +#endif + + if (batchmode) + { + log_error (_("Sorry, we are in batchmode - can't get input\n")); + exit (2); + } + + if (no_terminal) + { + log_error (_("Sorry, no terminal at all requested - can't get input\n")); + exit (2); + } + + if( !initialized ) + init_ttyfp(); + + last_prompt_len = 0; + tty_printf( "%s", prompt ); + buf = xmalloc((n=50)); + i = 0; + +#ifdef HAVE_W32_SYSTEM + if (hidden) + SetConsoleMode(con.in, HID_INPMODE ); + + utf8buf = NULL; + for (;;) + { + DWORD nread; + wchar_t wbuf[2]; + const unsigned char *s; + + if (!ReadConsoleW (con.in, wbuf, 1, &nread, NULL)) + log_fatal ("ReadConsole failed: %s", w32_strerror (-1)); + if (!nread) + continue; + + wbuf[1] = 0; + xfree (utf8buf); + utf8buf = wchar_to_utf8 (wbuf); + if (!utf8buf) + { + log_info ("wchar_to_utf8 failed: %s\n", strerror (errno)); + if (++errcount > 10) + log_fatal (_("too many errors; giving up\n")); + continue; + } + if (*utf8buf == '\n') + { + if (utf8buf[1]) + { + log_info ("ReadConsole returned more than requested" + " (0x0a,0x%02x)\n", utf8buf[1]); + if (++errcount > 10) + log_fatal (_("too many errors; giving up\n")); + } + break; + } + if (!hidden) + last_prompt_len++; + + for (s=utf8buf; *s; s++) + { + c = *s; + if (c == '\t') + c = ' '; /* Map tab to a space. */ + else if ((c >= 0 && c <= 0x1f) || c == 0x7f) + continue; /* Remove control characters. */ + if (!(i < n-1)) + { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; + } + } + xfree (utf8buf); + + if (hidden) + SetConsoleMode(con.in, DEF_INPMODE ); + +#else /* Unix */ + + if (hidden) + { +#ifdef HAVE_TCGETATTR + struct termios term; + + if (tcgetattr(fileno(ttyfp), &termsave)) + log_fatal ("tcgetattr() failed: %s\n", strerror(errno)); + restore_termios = 1; + term = termsave; + term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + if (tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) ) + log_fatal("tcsetattr() failed: %s\n", strerror(errno)); +#endif /*HAVE_TCGETATTR*/ + } + + /* fixme: How can we avoid that the \n is echoed w/o disabling + * canonical mode - w/o this kill_prompt can't work */ + while (read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n') + { + if (!hidden) + last_prompt_len++; + c = *cbuf; + if (c == CONTROL_D) + log_info (_("Control-D detected\n")); + + if (c == '\t') /* Map tab to a space. */ + c = ' '; + else if ( (c >= 0 && c <= 0x1f) || c == 0x7f) + continue; /* Skip all other ASCII control characters. */ + if (!(i < n-1)) + { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; + } + + if (*cbuf != '\n') + { + buf[0] = CONTROL_D; + i = 1; + } + + if (hidden) + { +#ifdef HAVE_TCGETATTR + if (tcsetattr (fileno(ttyfp), TCSAFLUSH, &termsave)) + log_error ("tcsetattr() failed: %s\n", strerror(errno)); + restore_termios = 0; +#endif /*HAVE_TCGETATTR*/ + } +#endif /* Unix */ + + buf[i] = 0; + return buf; +} + + +char * +tty_get( const char *prompt ) +{ + if (!batchmode && !no_terminal && my_rl_readline && my_rl_add_history) + { + char *line; + char *buf; + + if (!initialized) + init_ttyfp(); + + last_prompt_len = 0; + + line = my_rl_readline (prompt?prompt:""); + + /* We need to copy it to memory controlled by our malloc + implementations; further we need to convert an EOF to our + convention. */ + buf = xmalloc(line? strlen(line)+1:2); + if (line) + { + strcpy (buf, line); + trim_spaces (buf); + if (strlen (buf) > 2 ) + my_rl_add_history (line); /* Note that we test BUF but add LINE. */ + free (line); + } + else + { + buf[0] = CONTROL_D; + buf[1] = 0; + } + return buf; + } + else + return do_get ( prompt, 0 ); +} + + +/* Variable argument version of tty_get. The prompt is actually a + * format string with arguments. */ +char * +tty_getf (const char *promptfmt, ... ) +{ + va_list arg_ptr; + char *prompt; + char *answer; + + va_start (arg_ptr, promptfmt); + if (gpgrt_vasprintf (&prompt, promptfmt, arg_ptr) < 0) + log_fatal ("estream_vasprintf failed: %s\n", strerror (errno)); + va_end (arg_ptr); + answer = tty_get (prompt); + xfree (prompt); + return answer; +} + + +char * +tty_get_hidden( const char *prompt ) +{ + return do_get (prompt, 1); +} + + +void +tty_kill_prompt (void) +{ + if (no_terminal) + return; + + if (!initialized) + init_ttyfp (); + + if (batchmode) + last_prompt_len = 0; + if (!last_prompt_len) + return; +#ifdef HAVE_W32_SYSTEM + tty_printf ("\r%*s\r", last_prompt_len, ""); +#else /* Unix */ + { + int i; + putc ('\r', ttyfp); + for (i=0; i < last_prompt_len; i ++ ) + putc (' ', ttyfp); + putc ('\r', ttyfp); + fflush (ttyfp); + } +#endif /* Unix */ + last_prompt_len = 0; +} + + +int +tty_get_answer_is_yes( const char *prompt ) +{ + int yes; + char *p; + + p = tty_get (prompt); + tty_kill_prompt (); + yes = answer_is_yes (p); + xfree (p); + + return yes; +} + + +/* Called by gnupg_rl_initialize to setup the readline support. */ +void +tty_private_set_rl_hooks (void (*init_stream) (FILE *), + void (*set_completer) (rl_completion_func_t*), + void (*inhibit_completion) (int), + void (*cleanup_after_signal) (void), + char *(*readline_fun) (const char*), + void (*add_history_fun) (const char*)) +{ + my_rl_init_stream = init_stream; + my_rl_set_completer = set_completer; + my_rl_inhibit_completion = inhibit_completion; + my_rl_cleanup_after_signal = cleanup_after_signal; + my_rl_readline = readline_fun; + my_rl_add_history = add_history_fun; +} + + +#ifdef HAVE_LIBREADLINE +void +tty_enable_completion (rl_completion_func_t *completer) +{ + if (no_terminal || !my_rl_set_completer ) + return; + + if (!initialized) + init_ttyfp(); + + my_rl_set_completer (completer); +} + +void +tty_disable_completion (void) +{ + if (no_terminal || !my_rl_inhibit_completion) + return; + + if (!initialized) + init_ttyfp(); + + my_rl_inhibit_completion (1); +} +#endif /* HAVE_LIBREADLINE */ + +void +tty_cleanup_after_signal (void) +{ +#ifdef HAVE_TCGETATTR + cleanup (); +#endif +} + +void +tty_cleanup_rl_after_signal (void) +{ + if (my_rl_cleanup_after_signal) + my_rl_cleanup_after_signal (); +} |