getpass.c revision 1.23 1 /* $NetBSD: getpass.c,v 1.23 2012/04/13 14:39:34 christos Exp $ */
2
3 /*-
4 * Copyright (c) 2012 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Christos Zoulas.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31 #include <sys/cdefs.h>
32 #if defined(LIBC_SCCS) && !defined(lint)
33 __RCSID("$NetBSD: getpass.c,v 1.23 2012/04/13 14:39:34 christos Exp $");
34 #endif /* LIBC_SCCS and not lint */
35
36 #include "namespace.h"
37
38 #include <assert.h>
39 #ifdef TEST
40 #include <stdio.h>
41 #endif
42 #include <errno.h>
43 #include <ctype.h>
44 #include <signal.h>
45 #include <string.h>
46 #include <paths.h>
47 #include <stdbool.h>
48 #include <stdlib.h>
49 #include <termios.h>
50 #include <unistd.h>
51 #include <fcntl.h>
52 #include <poll.h>
53
54 #ifdef __weak_alias
55 __weak_alias(getpassfd,_getpassfd)
56 __weak_alias(getpass_r,_getpass_r)
57 __weak_alias(getpass,_getpass)
58 #endif
59
60 /*
61 * Notes:
62 * - There is no getpass_r in POSIX
63 * - Historically EOF is documented to be treated as EOL, we provide a
64 * tunable for that GETPASS_FAIL_EOF to disable this.
65 * - Historically getpass ate extra characters silently, we provide
66 * a tunable for that GETPASS_BUF_LIMIT to disable this.
67 * - Historically getpass "worked" by echoing characters when turning
68 * off echo failed, we provide a tunable GETPASS_NEED_TTY to
69 * disable this.
70 * - Some implementations say that on interrupt the program shall
71 * receive an interrupt signal before the function returns. We
72 * send all the tty signals before we return, but we don't expect
73 * suspend to do something useful unless the caller calls us again.
74 * We also provide a tunable to disable signal delivery
75 * GETPASS_NO_SIGNAL.
76 * - GETPASS_NO_BEEP disables beeping.
77 * - GETPASS_ECHO_STAR will echo '*' for each character of the password
78 * - GETPASS_ECHO will echo the password (as pam likes it)
79 */
80 char *
81 /*ARGSUSED*/
82 getpassfd(const char *prompt, char *buf, size_t len, int fd[], int flags,
83 int tout)
84 {
85 struct termios gt;
86 char c;
87 int sig;
88 bool lnext, havetty, allocated;
89
90 _DIAGASSERT(prompt != NULL);
91
92 sig = 0;
93
94 allocated = buf == NULL;
95 if (tcgetattr(fd[0], >) == -1) {
96 havetty = false;
97 if (flags & GETPASS_NEED_TTY)
98 goto out;
99 memset(>, -1, sizeof(gt));
100 } else
101 havetty = true;
102
103
104 if (havetty) {
105 struct termios st = gt;
106
107 st.c_lflag &= ~(ECHO|ECHOK|ECHOE|ECHOKE|ECHOCTL|ISIG|ICANON);
108 st.c_cc[VMIN] = 1;
109 st.c_cc[VTIME] = 0;
110 if (tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, &st) == -1)
111 goto out;
112 }
113
114 if (prompt != NULL) {
115 size_t plen = strlen(prompt);
116 (void)write(fd[1], prompt, plen);
117 }
118
119 if (allocated) {
120 len = 1024;
121 if ((buf = malloc(len)) == NULL)
122 goto restore;
123 }
124
125 c = '\1';
126 lnext = false;
127 for (size_t l = 0; c != '\0'; ) {
128 if (tout) {
129 struct pollfd pfd;
130 pfd.fd = fd[0];
131 pfd.events = POLLIN|POLLRDNORM;
132 pfd.revents = 0;
133 switch (poll(&pfd, 1, tout * 1000)) {
134 case 0:
135 errno = ENODATA;
136 /*FALLTHROUGH*/
137 case -1:
138 goto restore;
139 default:
140 break;
141 }
142 }
143 if (read(fd[0], &c, 1) != 1)
144 goto restore;
145
146 #define beep() \
147 do \
148 if (flags & GETPASS_NO_BEEP) \
149 (void)write(fd[2], "\a", 1); \
150 while (/*CONSTCOND*/ 0)
151 #define erase() (void)write(fd[1], "\b \b", 3)
152 #define C(a, b) (gt.c_cc[(a)] == _POSIX_VDISABLE ? (b) : gt.c_cc[(a)])
153
154 if (lnext) {
155 lnext = false;
156 goto add;
157 }
158
159 /* Ignored */
160 if (c == C(VREPRINT, CTRL('r')) || c == C(VSTART, CTRL('q')) ||
161 c == C(VSTOP, CTRL('s')) || c == C(VSTATUS, CTRL('t')) ||
162 c == C(VDISCARD, CTRL('o')))
163 continue;
164
165 /* Literal next */
166 if (c == C(VLNEXT, CTRL('v'))) {
167 lnext = true;
168 continue;
169 }
170
171 /* Line or word kill, treat as reset */
172 if (c == C(VKILL, CTRL('u')) || c == C(VWERASE, CTRL('w'))) {
173 if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR)) {
174 while (l--)
175 erase();
176 }
177 l = 0;
178 continue;
179 }
180
181 /* Character erase */
182 if (c == C(VERASE, CTRL('h'))) {
183 if (l == 0)
184 beep();
185 else {
186 l--;
187 if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR))
188 erase();
189 }
190 continue;
191 }
192
193 /* tty signal characters */
194 if (c == C(VINTR, CTRL('c'))) {
195 sig = SIGINT;
196 goto out;
197 }
198 if (c == C(VQUIT, CTRL('\\'))) {
199 sig = SIGQUIT;
200 goto out;
201 }
202 if (c == C(VSUSP, CTRL('z')) || c == C(VDSUSP, CTRL('y'))) {
203 sig = SIGTSTP;
204 goto out;
205 }
206
207 /* EOF */
208 if (c == C(VEOF, CTRL('d'))) {
209 if (flags & GETPASS_FAIL_EOF) {
210 errno = ENODATA;
211 goto out;
212 } else {
213 c = '\0';
214 goto add;
215 }
216 }
217
218 /* End of line */
219 if (c == C(VEOL, CTRL('j')) || c == C(VEOL2, CTRL('l')))
220 c = '\0';
221 add:
222 if (l >= len) {
223 if (allocated) {
224 size_t nlen = len + 1024;
225 char *nbuf = realloc(buf, nlen);
226 if (nbuf == NULL)
227 goto restore;
228 buf = nbuf;
229 len = nlen;
230 } else {
231 if (flags & GETPASS_BUF_LIMIT) {
232 beep();
233 continue;
234 }
235 if (c == '\0' && l > 0)
236 l--;
237 else
238 continue;
239 }
240 }
241 buf[l++] = c;
242 if (c) {
243 if (flags & GETPASS_ECHO_STAR)
244 (void)write(fd[1], "*", 1);
245 else if (flags & GETPASS_ECHO)
246 (void)write(fd[1], isprint((unsigned char)c) ?
247 &c : "?", 1);
248 }
249 }
250
251 if (havetty)
252 (void)tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, >);
253 return buf;
254 restore:
255 if (havetty) {
256 c = errno;
257 (void)tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, >);
258 errno = c;
259 }
260 out:
261 if (sig) {
262 if ((flags & GETPASS_NO_SIGNAL) == 0)
263 (void)raise(sig);
264 errno = EINTR;
265 }
266 memset(buf, 0, len);
267 if (allocated)
268 free(buf);
269 return NULL;
270 }
271
272 char *
273 getpass_r(const char *prompt, char *buf, size_t len)
274 {
275 bool opentty;
276 int fd[3];
277 char *rv;
278
279 /*
280 * Try to use /dev/tty if possible; otherwise read from stdin and
281 * write to stderr.
282 */
283 if ((fd[0] = fd[1] = fd[2] = open(_PATH_TTY, O_RDWR)) == -1) {
284 opentty = false;
285 fd[0] = STDIN_FILENO;
286 fd[1] = fd[2] = STDERR_FILENO;
287 } else
288 opentty = true;
289
290 rv = getpassfd(prompt, buf, len, fd, 0, 0);
291
292 if (opentty) {
293 int serrno = errno;
294 (void)close(fd[0]);
295 errno = serrno;
296 }
297 return rv;
298 }
299
300 char *
301 getpass(const char *prompt)
302 {
303 static char e[] = "";
304 static char *buf;
305 static long bufsiz;
306 char *rv;
307
308 /*
309 * Strictly speaking we could double allocate here, if we get
310 * called at the same time, but this function is not re-entrant
311 * anyway and it is not supposed to work if called concurrently.
312 */
313 if (buf == NULL) {
314 if ((bufsiz = sysconf(_SC_PASS_MAX)) == -1)
315 return e;
316 if ((buf = malloc((size_t)bufsiz)) == NULL)
317 return e;
318 }
319
320 if ((rv = getpass_r(prompt, buf, (size_t)bufsiz)) == NULL)
321 return e;
322
323 return rv;
324 }
325
326 #ifdef TEST
327 int
328 main(int argc, char *argv[])
329 {
330 char buf[28];
331 int fd[3] = { 0, 1, 2 };
332 printf("[%s]\n", getpassfd("foo>", buf, sizeof(buf), fd,
333 GETPASS_ECHO_STAR, 2));
334 return 0;
335 }
336 #endif
337