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