1 /*-------------------------------------------------------------------------
2  *
3  * sprompt.c
4  *	  simple_prompt() routine
5  *
lipol_ps()6  * Portions Copyright (c) 1996-2020, 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  */
36 void
37 simple_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 	/* strip trailing newline, including \r in case we're on Windows */
148 	while (length > 0 &&
149 		   (destination[length - 1] == '\n' ||
150 			destination[length - 1] == '\r'))
151 		destination[--length] = '\0';
152 
153 	if (!echo)
154 	{
155 		/* restore previous echo behavior, then echo \n */
156 #if defined(HAVE_TERMIOS_H)
157 		tcsetattr(fileno(termin), TCSAFLUSH, &t_orig);
158 		fputs("\n", termout);
159 		fflush(termout);
160 #elif defined(WIN32)
161 		SetConsoleMode(t, t_orig);
162 		fputs("\n", termout);
163 		fflush(termout);
164 #endif
165 	}
166 
167 	if (termin != stdin)
168 	{
169 		fclose(termin);
170 		fclose(termout);
171 	}
172 }
173