xref: /netbsd-src/lib/libc/gen/getpass.c (revision 8397619c32719a22ddc889264a119a40a135f9db)
1*8397619cSchristos /*	$NetBSD: getpass.c,v 1.30 2016/01/31 23:41:38 christos Exp $	*/
2b585e843Scgd 
348c7acbcSchristos /*-
448c7acbcSchristos  * Copyright (c) 2012 The NetBSD Foundation, Inc.
548c7acbcSchristos  * All rights reserved.
648c7acbcSchristos  *
748c7acbcSchristos  * This code is derived from software contributed to The NetBSD Foundation
848c7acbcSchristos  * by Christos Zoulas.
961f28255Scgd  *
1061f28255Scgd  * Redistribution and use in source and binary forms, with or without
1161f28255Scgd  * modification, are permitted provided that the following conditions
1261f28255Scgd  * are met:
1361f28255Scgd  * 1. Redistributions of source code must retain the above copyright
1461f28255Scgd  *    notice, this list of conditions and the following disclaimer.
1561f28255Scgd  * 2. Redistributions in binary form must reproduce the above copyright
1661f28255Scgd  *    notice, this list of conditions and the following disclaimer in the
1761f28255Scgd  *    documentation and/or other materials provided with the distribution.
1861f28255Scgd  *
1948c7acbcSchristos  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2048c7acbcSchristos  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2148c7acbcSchristos  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2248c7acbcSchristos  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2348c7acbcSchristos  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2448c7acbcSchristos  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2548c7acbcSchristos  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2648c7acbcSchristos  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2748c7acbcSchristos  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2848c7acbcSchristos  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2948c7acbcSchristos  * POSSIBILITY OF SUCH DAMAGE.
3061f28255Scgd  */
313e458a62Schristos #include <sys/cdefs.h>
3261f28255Scgd #if defined(LIBC_SCCS) && !defined(lint)
33*8397619cSchristos __RCSID("$NetBSD: getpass.c,v 1.30 2016/01/31 23:41:38 christos Exp $");
3461f28255Scgd #endif /* LIBC_SCCS and not lint */
3561f28255Scgd 
3643fa6fe3Sjtc #include "namespace.h"
37b585e843Scgd 
38b48252f3Slukem #include <assert.h>
3948c7acbcSchristos #ifdef TEST
4061f28255Scgd #include <stdio.h>
4148c7acbcSchristos #endif
4248c7acbcSchristos #include <errno.h>
4398ba7d20Schristos #include <ctype.h>
4410fa0b64Schristos #include <signal.h>
4548c7acbcSchristos #include <string.h>
4648c7acbcSchristos #include <paths.h>
4748c7acbcSchristos #include <stdbool.h>
4848c7acbcSchristos #include <stdlib.h>
49b48252f3Slukem #include <termios.h>
5061f28255Scgd #include <unistd.h>
5148c7acbcSchristos #include <fcntl.h>
5277cac556Schristos #include <poll.h>
5361f28255Scgd 
5443fa6fe3Sjtc #ifdef __weak_alias
__weak_alias(getpassfd,_getpassfd)55969c948dSchristos __weak_alias(getpassfd,_getpassfd)
5648c7acbcSchristos __weak_alias(getpass_r,_getpass_r)
5760549036Smycroft __weak_alias(getpass,_getpass)
5843fa6fe3Sjtc #endif
5943fa6fe3Sjtc 
6048c7acbcSchristos /*
6148c7acbcSchristos  * Notes:
6248c7acbcSchristos  *	- There is no getpass_r in POSIX
6348c7acbcSchristos  *	- Historically EOF is documented to be treated as EOL, we provide a
64969c948dSchristos  *	  tunable for that GETPASS_FAIL_EOF to disable this.
6548c7acbcSchristos  *	- Historically getpass ate extra characters silently, we provide
66969c948dSchristos  *	  a tunable for that GETPASS_BUF_LIMIT to disable this.
6748c7acbcSchristos  *	- Historically getpass "worked" by echoing characters when turning
68969c948dSchristos  *	  off echo failed, we provide a tunable GETPASS_NEED_TTY to
6948c7acbcSchristos  *	  disable this.
7048c7acbcSchristos  *	- Some implementations say that on interrupt the program shall
7110fa0b64Schristos  *	  receive an interrupt signal before the function returns. We
7210fa0b64Schristos  *	  send all the tty signals before we return, but we don't expect
7310fa0b64Schristos  *	  suspend to do something useful unless the caller calls us again.
74969c948dSchristos  *	  We also provide a tunable to disable signal delivery
75969c948dSchristos  *	  GETPASS_NO_SIGNAL.
76969c948dSchristos  *	- GETPASS_NO_BEEP disables beeping.
77311b90b9Schristos  *	- GETPASS_ECHO_STAR will echo '*' for each character of the password
78969c948dSchristos  *	- GETPASS_ECHO will echo the password (as pam likes it)
793765cebfSchristos  *	- GETPASS_7BIT strips the 8th bit
803765cebfSchristos  *	- GETPASS_FORCE_UPPER forces to uppercase
813765cebfSchristos  *	- GETPASS_FORCE_LOWER forces to uppercase
823765cebfSchristos  *	- GETPASS_ECHO_NL echo's a new line on success if echo was off.
8348c7acbcSchristos  */
8461f28255Scgd char *
85969c948dSchristos /*ARGSUSED*/
863765cebfSchristos getpassfd(const char *prompt, char *buf, size_t len, int *fd, int flags,
8777cac556Schristos     int tout)
8861f28255Scgd {
8948c7acbcSchristos 	struct termios gt;
9048c7acbcSchristos 	char c;
91969c948dSchristos 	int sig;
923765cebfSchristos 	bool lnext, havetty, allocated, opentty, good;
933765cebfSchristos 	int fdc[3];
9461f28255Scgd 
95b48252f3Slukem 	_DIAGASSERT(prompt != NULL);
96b48252f3Slukem 
973765cebfSchristos 	if (buf != NULL && len == 0) {
983765cebfSchristos 		errno = EINVAL;
993765cebfSchristos 		return NULL;
1003765cebfSchristos 	}
101b2459dacSchristos 
1023765cebfSchristos 	good = false;
1033765cebfSchristos 	opentty = false;
1043765cebfSchristos 	if (fd == NULL) {
1053765cebfSchristos 		/*
1063765cebfSchristos 		 * Try to use /dev/tty if possible; otherwise read from stdin
1073765cebfSchristos 		 * and write to stderr.
1083765cebfSchristos 		 */
1093765cebfSchristos 		fd = fdc;
1109a513d96Schristos 		if ((fd[0] = fd[1] = fd[2] = open(_PATH_TTY,
1119a513d96Schristos 		    O_RDWR | O_CLOEXEC)) == -1) {
1123765cebfSchristos 			fd[0] = STDIN_FILENO;
1133765cebfSchristos 			fd[1] = fd[2] = STDERR_FILENO;
1143765cebfSchristos 		} else
1153765cebfSchristos 			opentty = true;
1163765cebfSchristos 	}
1173765cebfSchristos 
1183765cebfSchristos 	sig = 0;
119969c948dSchristos 	allocated = buf == NULL;
120969c948dSchristos 	if (tcgetattr(fd[0], &gt) == -1) {
12148c7acbcSchristos 		havetty = false;
122969c948dSchristos 		if (flags & GETPASS_NEED_TTY)
12348c7acbcSchristos 			goto out;
12448c7acbcSchristos 		memset(&gt, -1, sizeof(gt));
12548c7acbcSchristos 	} else
12648c7acbcSchristos 		havetty = true;
12748c7acbcSchristos 
12848c7acbcSchristos 	if (havetty) {
12948c7acbcSchristos 		struct termios st = gt;
13048c7acbcSchristos 
13148c7acbcSchristos 		st.c_lflag &= ~(ECHO|ECHOK|ECHOE|ECHOKE|ECHOCTL|ISIG|ICANON);
13248c7acbcSchristos 		st.c_cc[VMIN] = 1;
13348c7acbcSchristos 		st.c_cc[VTIME] = 0;
134969c948dSchristos 		if (tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, &st) == -1)
13548c7acbcSchristos 			goto out;
1368bb17685Schristos 	}
13748c7acbcSchristos 
13848c7acbcSchristos 	if (prompt != NULL) {
13948c7acbcSchristos 		size_t plen = strlen(prompt);
140969c948dSchristos 		(void)write(fd[1], prompt, plen);
141969c948dSchristos 	}
142969c948dSchristos 
143969c948dSchristos 	if (allocated) {
144969c948dSchristos 		len = 1024;
145969c948dSchristos 		if ((buf = malloc(len)) == NULL)
146969c948dSchristos 			goto restore;
14761f28255Scgd 	}
14848c7acbcSchristos 
14948c7acbcSchristos 	c = '\1';
15048c7acbcSchristos 	lnext = false;
15148c7acbcSchristos 	for (size_t l = 0; c != '\0'; ) {
15277cac556Schristos 		if (tout) {
15377cac556Schristos 			struct pollfd pfd;
15477cac556Schristos 			pfd.fd = fd[0];
15577cac556Schristos 			pfd.events = POLLIN|POLLRDNORM;
15677cac556Schristos 			pfd.revents = 0;
15777cac556Schristos 			switch (poll(&pfd, 1, tout * 1000)) {
15877cac556Schristos 			case 0:
159d6d7452bSchristos 				errno = ETIMEDOUT;
16077cac556Schristos 				/*FALLTHROUGH*/
16177cac556Schristos 			case -1:
16277cac556Schristos 				goto restore;
16377cac556Schristos 			default:
16477cac556Schristos 				break;
16577cac556Schristos 			}
16677cac556Schristos 		}
167969c948dSchristos 		if (read(fd[0], &c, 1) != 1)
16848c7acbcSchristos 			goto restore;
16948c7acbcSchristos 
17077cac556Schristos #define beep() \
17177cac556Schristos 	do \
172969c948dSchristos 		if (flags & GETPASS_NO_BEEP) \
173969c948dSchristos 			(void)write(fd[2], "\a", 1); \
174969c948dSchristos 	while (/*CONSTCOND*/ 0)
175969c948dSchristos #define erase() (void)write(fd[1], "\b \b", 3)
176b7d29129Schristos /*
177b7d29129Schristos  * We test for both _POSIX_VDISABLE and NUL here because _POSIX_VDISABLE
178b7d29129Schristos  * propagation does not seem to be very consistent on multiple daemon hops
179b7d29129Schristos  * between different OS's. Perhaps we should not even bother with
180b7d29129Schristos  * _POSIX_VDISABLE and use ~0 and 0 directly.
181b7d29129Schristos  */
182b7d29129Schristos #define C(a, b) ((gt.c_cc[(a)] == _POSIX_VDISABLE || gt.c_cc[(a)] == '\0') ? \
183b7d29129Schristos     (b) : gt.c_cc[(a)])
18448c7acbcSchristos 		if (lnext) {
18548c7acbcSchristos 			lnext = false;
18648c7acbcSchristos 			goto add;
18761f28255Scgd 		}
18848c7acbcSchristos 
18948c7acbcSchristos 		/* Ignored */
19048c7acbcSchristos 		if (c == C(VREPRINT, CTRL('r')) || c == C(VSTART, CTRL('q')) ||
19148c7acbcSchristos 		    c == C(VSTOP, CTRL('s')) || c == C(VSTATUS, CTRL('t')) ||
19248c7acbcSchristos 		    c == C(VDISCARD, CTRL('o')))
19348c7acbcSchristos 			continue;
19448c7acbcSchristos 
19548c7acbcSchristos 		/* Literal next */
19648c7acbcSchristos 		if (c == C(VLNEXT, CTRL('v'))) {
19748c7acbcSchristos 			lnext = true;
19848c7acbcSchristos 			continue;
19961f28255Scgd 		}
20048c7acbcSchristos 
20148c7acbcSchristos 		/* Line or word kill, treat as reset */
20248c7acbcSchristos 		if (c == C(VKILL, CTRL('u')) || c == C(VWERASE, CTRL('w'))) {
203311b90b9Schristos 			if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR)) {
204969c948dSchristos 				while (l--)
205969c948dSchristos 					erase();
206969c948dSchristos 			}
20748c7acbcSchristos 			l = 0;
20848c7acbcSchristos 			continue;
20948c7acbcSchristos 		}
21048c7acbcSchristos 
21148c7acbcSchristos 		/* Character erase */
21248c7acbcSchristos 		if (c == C(VERASE, CTRL('h'))) {
21348c7acbcSchristos 			if (l == 0)
21448c7acbcSchristos 				beep();
215969c948dSchristos 			else {
21648c7acbcSchristos 				l--;
217311b90b9Schristos 				if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR))
218969c948dSchristos 					erase();
219969c948dSchristos 			}
22048c7acbcSchristos 			continue;
22148c7acbcSchristos 		}
22248c7acbcSchristos 
22310fa0b64Schristos 		/* tty signal characters */
22410fa0b64Schristos 		if (c == C(VINTR, CTRL('c'))) {
22510fa0b64Schristos 			sig = SIGINT;
22610fa0b64Schristos 			goto out;
22710fa0b64Schristos 		}
22810fa0b64Schristos 		if (c == C(VQUIT, CTRL('\\'))) {
22910fa0b64Schristos 			sig = SIGQUIT;
23010fa0b64Schristos 			goto out;
23110fa0b64Schristos 		}
23210fa0b64Schristos 		if (c == C(VSUSP, CTRL('z')) || c == C(VDSUSP, CTRL('y'))) {
23310fa0b64Schristos 			sig = SIGTSTP;
23448c7acbcSchristos 			goto out;
23548c7acbcSchristos 		}
23648c7acbcSchristos 
23710fa0b64Schristos 		/* EOF */
23810fa0b64Schristos 		if (c == C(VEOF, CTRL('d')))  {
239969c948dSchristos 			if (flags & GETPASS_FAIL_EOF) {
24010fa0b64Schristos 				errno = ENODATA;
24110fa0b64Schristos 				goto out;
242969c948dSchristos 			} else {
24310fa0b64Schristos 				c = '\0';
24410fa0b64Schristos 				goto add;
245969c948dSchristos 			}
24610fa0b64Schristos 		}
24710fa0b64Schristos 
24810fa0b64Schristos 		/* End of line */
2491913c941Schristos 		if (c == C(VEOL, CTRL('j')) || c == C(VEOL2, CTRL('m')))
25048c7acbcSchristos 			c = '\0';
25148c7acbcSchristos add:
25248c7acbcSchristos 		if (l >= len) {
253969c948dSchristos 			if (allocated) {
25498ba7d20Schristos 				size_t nlen = len + 1024;
25598ba7d20Schristos 				char *nbuf = realloc(buf, nlen);
25698ba7d20Schristos 				if (nbuf == NULL)
257969c948dSchristos 					goto restore;
25898ba7d20Schristos 				buf = nbuf;
25998ba7d20Schristos 				len = nlen;
260969c948dSchristos 			} else {
261969c948dSchristos 				if (flags & GETPASS_BUF_LIMIT) {
26248c7acbcSchristos 					beep();
26348c7acbcSchristos 					continue;
264969c948dSchristos 				}
26548c7acbcSchristos 				if (c == '\0' && l > 0)
26648c7acbcSchristos 					l--;
26748c7acbcSchristos 				else
26848c7acbcSchristos 					continue;
26948c7acbcSchristos 			}
270969c948dSchristos 		}
2713765cebfSchristos 
2723765cebfSchristos 		if (flags & GETPASS_7BIT)
2733765cebfSchristos 			c &= 0x7f;
2743765cebfSchristos 		if ((flags & GETPASS_FORCE_LOWER) && isupper((unsigned char)c))
2753765cebfSchristos 			c = tolower((unsigned char)c);
2763765cebfSchristos 		if ((flags & GETPASS_FORCE_UPPER) && islower((unsigned char)c))
2773765cebfSchristos 			c = toupper((unsigned char)c);
2783765cebfSchristos 
279969c948dSchristos 		buf[l++] = c;
280311b90b9Schristos 		if (c) {
281311b90b9Schristos 			if (flags & GETPASS_ECHO_STAR)
282311b90b9Schristos 				(void)write(fd[1], "*", 1);
283311b90b9Schristos 			else if (flags & GETPASS_ECHO)
28498ba7d20Schristos 				(void)write(fd[1], isprint((unsigned char)c) ?
28598ba7d20Schristos 				    &c : "?", 1);
28648c7acbcSchristos 		}
287311b90b9Schristos 	}
2883765cebfSchristos 	good = true;
28948c7acbcSchristos 
29048c7acbcSchristos restore:
291*8397619cSchristos out:
292969c948dSchristos 	if (havetty) {
29348c7acbcSchristos 		c = errno;
294969c948dSchristos 		(void)tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, &gt);
29548c7acbcSchristos 		errno = c;
296969c948dSchristos 	}
2973765cebfSchristos 	if (good && (flags & GETPASS_ECHO_NL))
2983765cebfSchristos 		(void)write(fd[1], "\n", 1);
2993765cebfSchristos 
3003765cebfSchristos 	if (opentty) {
3013765cebfSchristos 		c = errno;
3023765cebfSchristos 		(void)close(fd[0]);
3033765cebfSchristos 		errno = c;
3043765cebfSchristos 	}
3053765cebfSchristos 
3063765cebfSchristos 	if (good)
3073765cebfSchristos 		return buf;
3083765cebfSchristos 
30910fa0b64Schristos 	if (sig) {
310969c948dSchristos 		if ((flags & GETPASS_NO_SIGNAL) == 0)
31110fa0b64Schristos 			(void)raise(sig);
31210fa0b64Schristos 		errno = EINTR;
31310fa0b64Schristos 	}
314969c948dSchristos 	memset(buf, 0, len);
315969c948dSchristos 	if (allocated)
316969c948dSchristos 		free(buf);
31748c7acbcSchristos 	return NULL;
31848c7acbcSchristos }
31948c7acbcSchristos 
32048c7acbcSchristos char *
getpass_r(const char * prompt,char * buf,size_t len)321969c948dSchristos getpass_r(const char *prompt, char *buf, size_t len)
322969c948dSchristos {
3238f44324eSchristos 	return getpassfd(prompt, buf, len, NULL, GETPASS_ECHO_NL, 0);
324969c948dSchristos }
325969c948dSchristos 
326969c948dSchristos char *
getpass(const char * prompt)32748c7acbcSchristos getpass(const char *prompt)
32848c7acbcSchristos {
32948c7acbcSchristos 	static char e[] = "";
33048c7acbcSchristos 	static char *buf;
33148c7acbcSchristos 	static long bufsiz;
33248c7acbcSchristos 	char *rv;
33348c7acbcSchristos 
334969c948dSchristos 	/*
335969c948dSchristos 	 * Strictly speaking we could double allocate here, if we get
336969c948dSchristos 	 * called at the same time, but this function is not re-entrant
337969c948dSchristos 	 * anyway and it is not supposed to work if called concurrently.
338969c948dSchristos 	 */
33948c7acbcSchristos 	if (buf == NULL) {
34048c7acbcSchristos 		if ((bufsiz = sysconf(_SC_PASS_MAX)) == -1)
34148c7acbcSchristos 			return e;
34248c7acbcSchristos 		if ((buf = malloc((size_t)bufsiz)) == NULL)
34348c7acbcSchristos 			return e;
34448c7acbcSchristos 	}
34548c7acbcSchristos 
34648c7acbcSchristos 	if ((rv = getpass_r(prompt, buf, (size_t)bufsiz)) == NULL)
34748c7acbcSchristos 		return e;
34848c7acbcSchristos 
34948c7acbcSchristos 	return rv;
35048c7acbcSchristos }
35148c7acbcSchristos 
35248c7acbcSchristos #ifdef TEST
35348c7acbcSchristos int
main(int argc,char * argv[])35448c7acbcSchristos main(int argc, char *argv[])
35548c7acbcSchristos {
35648c7acbcSchristos 	char buf[28];
3573765cebfSchristos 	printf("[%s]\n", getpassfd("foo>", buf, sizeof(buf), NULL,
3583765cebfSchristos 	    GETPASS_ECHO_STAR|GETPASS_ECHO_NL, 2));
35948c7acbcSchristos 	return 0;
36048c7acbcSchristos }
36148c7acbcSchristos #endif
362