1/*-------------------------------------------------------------------------
2 *
3 * sprompt.c
4 * simple_prompt() routine
5 *
6 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 *
10 * IDENTIFICATION
11 * src/port/sprompt.c
12 *
13 *-------------------------------------------------------------------------
14 */
15#include "c.h"
16
17#ifdef HAVE_TERMIOS_H
18#include <termios.h>
19#endif
20
21
22/*
23 * simple_prompt
24 *
25 * Generalized function especially intended for reading in usernames and
26 * passwords interactively. Reads from /dev/tty or stdin/stderr.
27 *
28 * prompt: The prompt to print, or NULL if none (automatically localized)
29 * destination: buffer in which to store result
30 * destlen: allocated length of destination
31 * echo: Set to false if you want to hide what is entered (for passwords)
32 *
33 * The input (without trailing newline) is returned in the destination buffer,
34 * with a '\0' appended.
35 */
36void
37simple_prompt(const char *prompt, char *destination, size_t destlen, bool echo)
38{
39 int length;
40 FILE *termin,
41 *termout;
42
43#if defined(HAVE_TERMIOS_H)
44 struct termios t_orig,
45 t;
46#elif defined(WIN32)
47 HANDLE t = NULL;
48 DWORD t_orig = 0;
49#endif
50
51#ifdef WIN32
52
53 /*
54 * A Windows console has an "input code page" and an "output code page";
55 * these usually match each other, but they rarely match the "Windows ANSI
56 * code page" defined at system boot and expected of "char *" arguments to
57 * Windows API functions. The Microsoft CRT write() implementation
58 * automatically converts text between these code pages when writing to a
59 * console. To identify such file descriptors, it calls GetConsoleMode()
60 * on the underlying HANDLE, which in turn requires GENERIC_READ access on
61 * the HANDLE. Opening termout in mode "w+" allows that detection to
62 * succeed. Otherwise, write() would not recognize the descriptor as a
63 * console, and non-ASCII characters would display incorrectly.
64 *
65 * XXX fgets() still receives text in the console's input code page. This
66 * makes non-ASCII credentials unportable.
67 *
68 * Unintuitively, we also open termin in mode "w+", even though we only
69 * read it; that's needed for SetConsoleMode() to succeed.
70 */
71 termin = fopen("CONIN$", "w+");
72 termout = fopen("CONOUT$", "w+");
73#else
74
75 /*
76 * Do not try to collapse these into one "w+" mode file. Doesn't work on
77 * some platforms (eg, HPUX 10.20).
78 */
79 termin = fopen("/dev/tty", "r");
80 termout = fopen("/dev/tty", "w");
81#endif
82 if (!termin || !termout
83#ifdef WIN32
84
85 /*
86 * Direct console I/O does not work from the MSYS 1.0.10 console. Writes
87 * reach nowhere user-visible; reads block indefinitely. XXX This affects
88 * most Windows terminal environments, including rxvt, mintty, Cygwin
89 * xterm, Cygwin sshd, and PowerShell ISE. Switch to a more-generic test.
90 */
91 || (getenv("OSTYPE") && strcmp(getenv("OSTYPE"), "msys") == 0)
92#endif
93 )
94 {
95 if (termin)
96 fclose(termin);
97 if (termout)
98 fclose(termout);
99 termin = stdin;
100 termout = stderr;
101 }
102
103 if (!echo)
104 {
105#if defined(HAVE_TERMIOS_H)
106 /* disable echo via tcgetattr/tcsetattr */
107 tcgetattr(fileno(termin), &t);
108 t_orig = t;
109 t.c_lflag &= ~ECHO;
110 tcsetattr(fileno(termin), TCSAFLUSH, &t);
111#elif defined(WIN32)
112 /* need the file's HANDLE to turn echo off */
113 t = (HANDLE) _get_osfhandle(_fileno(termin));
114
115 /* save the old configuration first */
116 GetConsoleMode(t, &t_orig);
117
118 /* set to the new mode */
119 SetConsoleMode(t, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
120#endif
121 }
122
123 if (prompt)
124 {
125 fputs(_(prompt), termout);
126 fflush(termout);
127 }
128
129 if (fgets(destination, destlen, termin) == NULL)
130 destination[0] = '\0';
131
132 length = strlen(destination);
133 if (length > 0 && destination[length - 1] != '\n')
134 {
135 /* eat rest of the line */
136 char buf[128];
137 int buflen;
138
139 do
140 {
141 if (fgets(buf, sizeof(buf), termin) == NULL)
142 break;
143 buflen = strlen(buf);
144 } while (buflen > 0 && buf[buflen - 1] != '\n');
145 }
146
147 if (length > 0 && destination[length - 1] == '\n')
148 /* remove trailing newline */
149 destination[length - 1] = '\0';
150
151 if (!echo)
152 {
153 /* restore previous echo behavior, then echo \n */
154#if defined(HAVE_TERMIOS_H)
155 tcsetattr(fileno(termin), TCSAFLUSH, &t_orig);
156 fputs("\n", termout);
157 fflush(termout);
158#elif defined(WIN32)
159 SetConsoleMode(t, t_orig);
160 fputs("\n", termout);
161 fflush(termout);
162#endif
163 }
164
165 if (termin != stdin)
166 {
167 fclose(termin);
168 fclose(termout);
169 }
170}
171