1 /* Copyright (C) 1992-2001, 2003-2007, 2009-2023 Free Software Foundation, Inc.
2
3 This file is part of the GNU C Library.
4
5 This file is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of the
8 License, or (at your option) any later version.
9
10 This file is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 #ifndef _LIBC
19 /* Don't use __attribute__ __nonnull__ in this compilation unit. Otherwise gcc
20 warns for the null checks on 'prompt' below. */
21 # define _GL_ARG_NONNULL(params)
22 # include <config.h>
23 #endif
24
25 #include "getpass.h"
26
27 #include <stdio.h>
28
29 #if !(defined _WIN32 && !defined __CYGWIN__)
30
31 # if HAVE_DECL___FSETLOCKING && HAVE___FSETLOCKING
32 # if HAVE_STDIO_EXT_H
33 # include <stdio_ext.h>
34 # endif
35 # else
36 # define __fsetlocking(stream, type) /* empty */
37 # endif
38
39 # if HAVE_TERMIOS_H
40 # include <termios.h>
41 # endif
42
43 # if USE_UNLOCKED_IO
44 # include "unlocked-io.h"
45 # else
46 # if !HAVE_DECL_FFLUSH_UNLOCKED
47 # undef fflush_unlocked
48 # define fflush_unlocked(x) fflush (x)
49 # endif
50 # if !HAVE_DECL_FLOCKFILE
51 # undef flockfile
52 # define flockfile(x) ((void) 0)
53 # endif
54 # if !HAVE_DECL_FUNLOCKFILE
55 # undef funlockfile
56 # define funlockfile(x) ((void) 0)
57 # endif
58 # if !HAVE_DECL_FPUTS_UNLOCKED
59 # undef fputs_unlocked
60 # define fputs_unlocked(str,stream) fputs (str, stream)
61 # endif
62 # if !HAVE_DECL_PUTC_UNLOCKED
63 # undef putc_unlocked
64 # define putc_unlocked(c,stream) putc (c, stream)
65 # endif
66 # endif
67
68 /* It is desirable to use this bit on systems that have it.
69 The only bit of terminal state we want to twiddle is echoing, which is
70 done in software; there is no need to change the state of the terminal
71 hardware. */
72
73 # ifndef TCSASOFT
74 # define TCSASOFT 0
75 # endif
76
77 static void
78 call_fclose (void *arg)
79 {
80 if (arg != NULL)
81 fclose (arg);
82 }
83
84 char *
85 getpass (const char *prompt)
86 {
87 FILE *tty;
88 FILE *in, *out;
89 # if HAVE_TCGETATTR
90 struct termios s, t;
91 # endif
92 bool tty_changed = false;
93 static char *buf;
94 static size_t bufsize;
95 ssize_t nread;
96
97 /* Try to write to and read from the terminal if we can.
98 If we can't open the terminal, use stderr and stdin. */
99
100 tty = fopen ("/dev/tty", "w+e");
101 if (tty == NULL)
102 {
103 in = stdin;
104 out = stderr;
105 }
106 else
107 {
108 /* We do the locking ourselves. */
109 __fsetlocking (tty, FSETLOCKING_BYCALLER);
110
111 out = in = tty;
112 }
113
114 flockfile (out);
115
116 /* Turn echoing off if it is on now. */
117 # if HAVE_TCGETATTR
118 if (tcgetattr (fileno (in), &t) == 0)
119 {
120 /* Save the old one. */
121 s = t;
122 /* Tricky, tricky. */
123 t.c_lflag &= ~(ECHO | ISIG);
124 tty_changed = (tcsetattr (fileno (in), TCSAFLUSH | TCSASOFT, &t) == 0);
125 }
126 # endif
127
128 if (prompt)
129 {
130 /* Write the prompt. */
131 fputs_unlocked (prompt, out);
132 fflush_unlocked (out);
133 }
134
135 /* Read the password. */
136 nread = getline (&buf, &bufsize, in);
137
138 /* According to the C standard, input may not be followed by output
139 on the same stream without an intervening call to a file
140 positioning function. Suppose in == out; then without this fseek
141 call, on Solaris, HP-UX, AIX, OSF/1, the previous input gets
142 echoed, whereas on IRIX, the following newline is not output as
143 it should be. POSIX imposes similar restrictions if fileno (in)
144 == fileno (out). The POSIX restrictions are tricky and change
145 from POSIX version to POSIX version, so play it safe and invoke
146 fseek even if in != out. */
147 fseeko (out, 0, SEEK_CUR);
148
149 if (buf != NULL)
150 {
151 if (nread < 0)
152 buf[0] = '\0';
153 else if (buf[nread - 1] == '\n')
154 {
155 /* Remove the newline. */
156 buf[nread - 1] = '\0';
157 if (tty_changed)
158 {
159 /* Write the newline that was not echoed. */
160 putc_unlocked ('\n', out);
161 }
162 }
163 }
164
165 /* Restore the original setting. */
166 # if HAVE_TCSETATTR
167 if (tty_changed)
168 tcsetattr (fileno (in), TCSAFLUSH | TCSASOFT, &s);
169 # endif
170
171 funlockfile (out);
172
173 call_fclose (tty);
174
175 return buf;
176 }
177
178 #else /* W32 native */
179
180 /* Windows implementation by Martin Lambers <marlam@marlam.de>,
181 improved by Simon Josefsson. */
182
183 /* For PASS_MAX. */
184 # include <limits.h>
185 /* For _getch(). */
186 # include <conio.h>
187 /* For strdup(). */
188 # include <string.h>
189
190 # ifndef PASS_MAX
191 # define PASS_MAX 512
192 # endif
193
194 char *
195 getpass (const char *prompt)
196 {
197 char getpassbuf[PASS_MAX + 1];
198 size_t i = 0;
199 int c;
200
201 if (prompt)
202 {
203 fputs (prompt, stderr);
204 fflush (stderr);
205 }
206
207 for (;;)
208 {
209 c = _getch ();
210 if (c == '\r')
211 {
212 getpassbuf[i] = '\0';
213 break;
214 }
215 else if (i < PASS_MAX)
216 {
217 getpassbuf[i++] = c;
218 }
219
220 if (i >= PASS_MAX)
221 {
222 getpassbuf[i] = '\0';
223 break;
224 }
225 }
226
227 if (prompt)
228 {
229 fputs ("\r\n", stderr);
230 fflush (stderr);
231 }
232
233 return strdup (getpassbuf);
234 }
235 #endif