getpass.c revision 1.22 1 /* $NetBSD: getpass.c,v 1.22 2012/04/13 14:16:27 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.22 2012/04/13 14:16:27 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
53 #ifdef __weak_alias
54 __weak_alias(getpassfd,_getpassfd)
55 __weak_alias(getpass_r,_getpass_r)
56 __weak_alias(getpass,_getpass)
57 #endif
58
59 /*
60 * Notes:
61 * - There is no getpass_r in POSIX
62 * - Historically EOF is documented to be treated as EOL, we provide a
63 * tunable for that GETPASS_FAIL_EOF to disable this.
64 * - Historically getpass ate extra characters silently, we provide
65 * a tunable for that GETPASS_BUF_LIMIT to disable this.
66 * - Historically getpass "worked" by echoing characters when turning
67 * off echo failed, we provide a tunable GETPASS_NEED_TTY to
68 * disable this.
69 * - Some implementations say that on interrupt the program shall
70 * receive an interrupt signal before the function returns. We
71 * send all the tty signals before we return, but we don't expect
72 * suspend to do something useful unless the caller calls us again.
73 * We also provide a tunable to disable signal delivery
74 * GETPASS_NO_SIGNAL.
75 * - GETPASS_NO_BEEP disables beeping.
76 * - GETPASS_ECHO_STAR will echo '*' for each character of the password
77 * - GETPASS_ECHO will echo the password (as pam likes it)
78 */
79 char *
80 /*ARGSUSED*/
81 getpassfd(const char *prompt, char *buf, size_t len, int fd[], int flags)
82 {
83 struct termios gt;
84 char c;
85 int sig;
86 bool lnext, havetty, allocated;
87
88 _DIAGASSERT(prompt != NULL);
89
90 sig = 0;
91
92 allocated = buf == NULL;
93 if (tcgetattr(fd[0], >) == -1) {
94 havetty = false;
95 if (flags & GETPASS_NEED_TTY)
96 goto out;
97 memset(>, -1, sizeof(gt));
98 } else
99 havetty = true;
100
101
102 if (havetty) {
103 struct termios st = gt;
104
105 st.c_lflag &= ~(ECHO|ECHOK|ECHOE|ECHOKE|ECHOCTL|ISIG|ICANON);
106 st.c_cc[VMIN] = 1;
107 st.c_cc[VTIME] = 0;
108 if (tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, &st) == -1)
109 goto out;
110 }
111
112 if (prompt != NULL) {
113 size_t plen = strlen(prompt);
114 (void)write(fd[1], prompt, plen);
115 }
116
117 if (allocated) {
118 len = 1024;
119 if ((buf = malloc(len)) == NULL)
120 goto restore;
121 }
122
123 c = '\1';
124 lnext = false;
125 for (size_t l = 0; c != '\0'; ) {
126 if (read(fd[0], &c, 1) != 1)
127 goto restore;
128
129 #define beep() do \
130 if (flags & GETPASS_NO_BEEP) \
131 (void)write(fd[2], "\a", 1); \
132 while (/*CONSTCOND*/ 0)
133 #define erase() (void)write(fd[1], "\b \b", 3)
134
135 #define C(a, b) (gt.c_cc[(a)] == _POSIX_VDISABLE ? (b) : gt.c_cc[(a)])
136
137 if (lnext) {
138 lnext = false;
139 goto add;
140 }
141
142 /* Ignored */
143 if (c == C(VREPRINT, CTRL('r')) || c == C(VSTART, CTRL('q')) ||
144 c == C(VSTOP, CTRL('s')) || c == C(VSTATUS, CTRL('t')) ||
145 c == C(VDISCARD, CTRL('o')))
146 continue;
147
148 /* Literal next */
149 if (c == C(VLNEXT, CTRL('v'))) {
150 lnext = true;
151 continue;
152 }
153
154 /* Line or word kill, treat as reset */
155 if (c == C(VKILL, CTRL('u')) || c == C(VWERASE, CTRL('w'))) {
156 if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR)) {
157 while (l--)
158 erase();
159 }
160 l = 0;
161 continue;
162 }
163
164 /* Character erase */
165 if (c == C(VERASE, CTRL('h'))) {
166 if (l == 0)
167 beep();
168 else {
169 l--;
170 if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR))
171 erase();
172 }
173 continue;
174 }
175
176 /* tty signal characters */
177 if (c == C(VINTR, CTRL('c'))) {
178 sig = SIGINT;
179 goto out;
180 }
181 if (c == C(VQUIT, CTRL('\\'))) {
182 sig = SIGQUIT;
183 goto out;
184 }
185 if (c == C(VSUSP, CTRL('z')) || c == C(VDSUSP, CTRL('y'))) {
186 sig = SIGTSTP;
187 goto out;
188 }
189
190 /* EOF */
191 if (c == C(VEOF, CTRL('d'))) {
192 if (flags & GETPASS_FAIL_EOF) {
193 errno = ENODATA;
194 goto out;
195 } else {
196 c = '\0';
197 goto add;
198 }
199 }
200
201 /* End of line */
202 if (c == C(VEOL, CTRL('j')) || c == C(VEOL2, CTRL('l')))
203 c = '\0';
204 add:
205 if (l >= len) {
206 if (allocated) {
207 size_t nlen = len + 1024;
208 char *nbuf = realloc(buf, nlen);
209 if (nbuf == NULL)
210 goto restore;
211 buf = nbuf;
212 len = nlen;
213 } else {
214 if (flags & GETPASS_BUF_LIMIT) {
215 beep();
216 continue;
217 }
218 if (c == '\0' && l > 0)
219 l--;
220 else
221 continue;
222 }
223 }
224 buf[l++] = c;
225 if (c) {
226 if (flags & GETPASS_ECHO_STAR)
227 (void)write(fd[1], "*", 1);
228 else if (flags & GETPASS_ECHO)
229 (void)write(fd[1], isprint((unsigned char)c) ?
230 &c : "?", 1);
231 }
232 }
233
234 if (havetty)
235 (void)tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, >);
236 return buf;
237 restore:
238 if (havetty) {
239 c = errno;
240 (void)tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, >);
241 errno = c;
242 }
243 out:
244 if (sig) {
245 if ((flags & GETPASS_NO_SIGNAL) == 0)
246 (void)raise(sig);
247 errno = EINTR;
248 }
249 memset(buf, 0, len);
250 if (allocated)
251 free(buf);
252 return NULL;
253 }
254
255 char *
256 getpass_r(const char *prompt, char *buf, size_t len)
257 {
258 bool opentty;
259 int fd[3];
260 char *rv;
261
262 /*
263 * Try to use /dev/tty if possible; otherwise read from stdin and
264 * write to stderr.
265 */
266 if ((fd[0] = fd[1] = fd[2] = open(_PATH_TTY, O_RDWR)) == -1) {
267 opentty = false;
268 fd[0] = STDIN_FILENO;
269 fd[1] = fd[2] = STDERR_FILENO;
270 } else
271 opentty = true;
272
273 rv = getpassfd(prompt, buf, len, fd, 0);
274
275 if (opentty) {
276 int serrno = errno;
277 (void)close(fd[0]);
278 errno = serrno;
279 }
280 return rv;
281 }
282
283 char *
284 getpass(const char *prompt)
285 {
286 static char e[] = "";
287 static char *buf;
288 static long bufsiz;
289 char *rv;
290
291 /*
292 * Strictly speaking we could double allocate here, if we get
293 * called at the same time, but this function is not re-entrant
294 * anyway and it is not supposed to work if called concurrently.
295 */
296 if (buf == NULL) {
297 if ((bufsiz = sysconf(_SC_PASS_MAX)) == -1)
298 return e;
299 if ((buf = malloc((size_t)bufsiz)) == NULL)
300 return e;
301 }
302
303 if ((rv = getpass_r(prompt, buf, (size_t)bufsiz)) == NULL)
304 return e;
305
306 return rv;
307 }
308
309 #ifdef TEST
310 int
311 main(int argc, char *argv[])
312 {
313 char buf[28];
314 int fd[3] = { 0, 1, 2 };
315 printf("[%s]\n", getpassfd("foo>", buf, sizeof(buf), fd,
316 GETPASS_ECHO_STAR));
317 return 0;
318 }
319 #endif
320