getpass.c revision 1.18 1 /* $NetBSD: getpass.c,v 1.18 2012/04/12 20:08:01 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.18 2012/04/12 20:08:01 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 <signal.h>
44 #include <string.h>
45 #include <paths.h>
46 #include <stdbool.h>
47 #include <stdlib.h>
48 #include <termios.h>
49 #include <unistd.h>
50 #include <fcntl.h>
51
52 #ifdef __weak_alias
53 __weak_alias(getpass_r,_getpass_r)
54 __weak_alias(getpass,_getpass)
55 #endif
56
57 /*
58 * Notes:
59 * - There is no getpass_r in POSIX
60 * - Historically EOF is documented to be treated as EOL, we provide a
61 * tunable for that DONT_TREAT_EOF_AS_EOL to disable this.
62 * - Historically getpass ate extra characters silently, we provide
63 * a tunable for that DONT_DISCARD_SILENTLY to disable this.
64 * - Historically getpass "worked" by echoing characters when turning
65 * off echo failed, we provide a tunable DONT_WORK_AND_ECHO to
66 * disable this.
67 * - Some implementations say that on interrupt the program shall
68 * receive an interrupt signal before the function returns. We
69 * send all the tty signals before we return, but we don't expect
70 * suspend to do something useful unless the caller calls us again.
71 */
72 char *
73 getpass_r(const char *prompt, char *ret, size_t len)
74 {
75 struct termios gt;
76 char c;
77 int infd, outfd, sig;
78 bool lnext, havetty;
79
80 _DIAGASSERT(prompt != NULL);
81
82 sig = 0;
83 /*
84 * Try to use /dev/tty if possible; otherwise read from stdin and
85 * write to stderr.
86 */
87 if ((outfd = infd = open(_PATH_TTY, O_RDWR)) == -1) {
88 infd = STDIN_FILENO;
89 outfd = STDERR_FILENO;
90 havetty = false;
91 } else
92 havetty = true;
93
94 if (tcgetattr(infd, >) == -1) {
95 havetty = false;
96 #ifdef DONT_WORK_AND_ECHO
97 goto out;
98 #else
99 memset(>, -1, sizeof(gt));
100 #endif
101 } else
102 havetty = true;
103
104
105 if (havetty) {
106 struct termios st = gt;
107
108 st.c_lflag &= ~(ECHO|ECHOK|ECHOE|ECHOKE|ECHOCTL|ISIG|ICANON);
109 st.c_cc[VMIN] = 1;
110 st.c_cc[VTIME] = 0;
111 if (tcsetattr(infd, TCSAFLUSH|TCSASOFT, &st) == -1)
112 goto out;
113 }
114
115 if (prompt != NULL) {
116 size_t plen = strlen(prompt);
117 (void)write(outfd, prompt, plen);
118 }
119
120 c = '\1';
121 lnext = false;
122 for (size_t l = 0; c != '\0'; ) {
123 if (read(infd, &c, 1) != 1)
124 goto restore;
125
126 #define beep() write(outfd, "\a", 1)
127 #define C(a, b) (gt.c_cc[(a)] == _POSIX_VDISABLE ? (b) : gt.c_cc[(a)])
128
129 if (lnext) {
130 lnext = false;
131 goto add;
132 }
133
134 /* Ignored */
135 if (c == C(VREPRINT, CTRL('r')) || c == C(VSTART, CTRL('q')) ||
136 c == C(VSTOP, CTRL('s')) || c == C(VSTATUS, CTRL('t')) ||
137 c == C(VDISCARD, CTRL('o')))
138 continue;
139
140 /* Literal next */
141 if (c == C(VLNEXT, CTRL('v'))) {
142 lnext = true;
143 continue;
144 }
145
146 /* Line or word kill, treat as reset */
147 if (c == C(VKILL, CTRL('u')) || c == C(VWERASE, CTRL('w'))) {
148 l = 0;
149 continue;
150 }
151
152 /* Character erase */
153 if (c == C(VERASE, CTRL('h'))) {
154 if (l == 0)
155 beep();
156 else
157 l--;
158 continue;
159 }
160
161 /* tty signal characters */
162 if (c == C(VINTR, CTRL('c'))) {
163 sig = SIGINT;
164 goto out;
165 }
166 if (c == C(VQUIT, CTRL('\\'))) {
167 sig = SIGQUIT;
168 goto out;
169 }
170 if (c == C(VSUSP, CTRL('z')) || c == C(VDSUSP, CTRL('y'))) {
171 sig = SIGTSTP;
172 goto out;
173 }
174
175 /* EOF */
176 if (c == C(VEOF, CTRL('d'))) {
177 #ifdef DONT_TREAT_EOF_AS_EOL
178 errno = ENODATA;
179 goto out;
180 #else
181 c = '\0';
182 goto add;
183 #endif
184 }
185
186 /* End of line */
187 if (c == C(VEOL, CTRL('j')) || c == C(VEOL2, CTRL('l')))
188 c = '\0';
189 add:
190 if (l >= len) {
191 #ifdef DONT_DISCARD_SILENTLY
192 beep();
193 continue;
194 #else
195 if (c == '\0' && l > 0)
196 l--;
197 else
198 continue;
199 #endif
200 }
201 ret[l++] = c;
202 }
203
204 if (havetty)
205 (void)tcsetattr(infd, TCSAFLUSH|TCSASOFT, >);
206 return ret;
207 restore:
208 c = errno;
209 if (havetty)
210 (void)tcsetattr(infd, TCSAFLUSH|TCSASOFT, >);
211 errno = c;
212 out:
213 if (sig) {
214 (void)raise(sig);
215 errno = EINTR;
216 }
217 return NULL;
218 }
219
220 char *
221 getpass(const char *prompt)
222 {
223 static char e[] = "";
224 static char *buf;
225 static long bufsiz;
226 char *rv;
227
228 if (buf == NULL) {
229 if ((bufsiz = sysconf(_SC_PASS_MAX)) == -1)
230 return e;
231 if ((buf = malloc((size_t)bufsiz)) == NULL)
232 return e;
233 }
234
235 if ((rv = getpass_r(prompt, buf, (size_t)bufsiz)) == NULL)
236 return e;
237
238 return rv;
239 }
240
241 #ifdef TEST
242 int
243 main(int argc, char *argv[])
244 {
245 char buf[28];
246 printf("[%s]\n", getpass_r("foo>", buf, sizeof(buf)));
247 return 0;
248 }
249 #endif
250