/* $NetBSD: getpass.c,v 1.30 2016/01/31 23:41:38 christos Exp $ */ /*- * Copyright (c) 2012 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #if defined(LIBC_SCCS) && !defined(lint) __RCSID("$NetBSD: getpass.c,v 1.30 2016/01/31 23:41:38 christos Exp $"); #endif /* LIBC_SCCS and not lint */ #include "namespace.h" #include #ifdef TEST #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef __weak_alias __weak_alias(getpassfd,_getpassfd) __weak_alias(getpass_r,_getpass_r) __weak_alias(getpass,_getpass) #endif /* * Notes: * - There is no getpass_r in POSIX * - Historically EOF is documented to be treated as EOL, we provide a * tunable for that GETPASS_FAIL_EOF to disable this. * - Historically getpass ate extra characters silently, we provide * a tunable for that GETPASS_BUF_LIMIT to disable this. * - Historically getpass "worked" by echoing characters when turning * off echo failed, we provide a tunable GETPASS_NEED_TTY to * disable this. * - Some implementations say that on interrupt the program shall * receive an interrupt signal before the function returns. We * send all the tty signals before we return, but we don't expect * suspend to do something useful unless the caller calls us again. * We also provide a tunable to disable signal delivery * GETPASS_NO_SIGNAL. * - GETPASS_NO_BEEP disables beeping. * - GETPASS_ECHO_STAR will echo '*' for each character of the password * - GETPASS_ECHO will echo the password (as pam likes it) * - GETPASS_7BIT strips the 8th bit * - GETPASS_FORCE_UPPER forces to uppercase * - GETPASS_FORCE_LOWER forces to uppercase * - GETPASS_ECHO_NL echo's a new line on success if echo was off. */ char * /*ARGSUSED*/ getpassfd(const char *prompt, char *buf, size_t len, int *fd, int flags, int tout) { struct termios gt; char c; int sig; bool lnext, havetty, allocated, opentty, good; int fdc[3]; _DIAGASSERT(prompt != NULL); if (buf != NULL && len == 0) { errno = EINVAL; return NULL; } good = false; opentty = false; if (fd == NULL) { /* * Try to use /dev/tty if possible; otherwise read from stdin * and write to stderr. */ fd = fdc; if ((fd[0] = fd[1] = fd[2] = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) == -1) { fd[0] = STDIN_FILENO; fd[1] = fd[2] = STDERR_FILENO; } else opentty = true; } sig = 0; allocated = buf == NULL; if (tcgetattr(fd[0], >) == -1) { havetty = false; if (flags & GETPASS_NEED_TTY) goto out; memset(>, -1, sizeof(gt)); } else havetty = true; if (havetty) { struct termios st = gt; st.c_lflag &= ~(ECHO|ECHOK|ECHOE|ECHOKE|ECHOCTL|ISIG|ICANON); st.c_cc[VMIN] = 1; st.c_cc[VTIME] = 0; if (tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, &st) == -1) goto out; } if (prompt != NULL) { size_t plen = strlen(prompt); (void)write(fd[1], prompt, plen); } if (allocated) { len = 1024; if ((buf = malloc(len)) == NULL) goto restore; } c = '\1'; lnext = false; for (size_t l = 0; c != '\0'; ) { if (tout) { struct pollfd pfd; pfd.fd = fd[0]; pfd.events = POLLIN|POLLRDNORM; pfd.revents = 0; switch (poll(&pfd, 1, tout * 1000)) { case 0: errno = ETIMEDOUT; /*FALLTHROUGH*/ case -1: goto restore; default: break; } } if (read(fd[0], &c, 1) != 1) goto restore; #define beep() \ do \ if (flags & GETPASS_NO_BEEP) \ (void)write(fd[2], "\a", 1); \ while (/*CONSTCOND*/ 0) #define erase() (void)write(fd[1], "\b \b", 3) /* * We test for both _POSIX_VDISABLE and NUL here because _POSIX_VDISABLE * propagation does not seem to be very consistent on multiple daemon hops * between different OS's. Perhaps we should not even bother with * _POSIX_VDISABLE and use ~0 and 0 directly. */ #define C(a, b) ((gt.c_cc[(a)] == _POSIX_VDISABLE || gt.c_cc[(a)] == '\0') ? \ (b) : gt.c_cc[(a)]) if (lnext) { lnext = false; goto add; } /* Ignored */ if (c == C(VREPRINT, CTRL('r')) || c == C(VSTART, CTRL('q')) || c == C(VSTOP, CTRL('s')) || c == C(VSTATUS, CTRL('t')) || c == C(VDISCARD, CTRL('o'))) continue; /* Literal next */ if (c == C(VLNEXT, CTRL('v'))) { lnext = true; continue; } /* Line or word kill, treat as reset */ if (c == C(VKILL, CTRL('u')) || c == C(VWERASE, CTRL('w'))) { if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR)) { while (l--) erase(); } l = 0; continue; } /* Character erase */ if (c == C(VERASE, CTRL('h'))) { if (l == 0) beep(); else { l--; if (flags & (GETPASS_ECHO | GETPASS_ECHO_STAR)) erase(); } continue; } /* tty signal characters */ if (c == C(VINTR, CTRL('c'))) { sig = SIGINT; goto out; } if (c == C(VQUIT, CTRL('\\'))) { sig = SIGQUIT; goto out; } if (c == C(VSUSP, CTRL('z')) || c == C(VDSUSP, CTRL('y'))) { sig = SIGTSTP; goto out; } /* EOF */ if (c == C(VEOF, CTRL('d'))) { if (flags & GETPASS_FAIL_EOF) { errno = ENODATA; goto out; } else { c = '\0'; goto add; } } /* End of line */ if (c == C(VEOL, CTRL('j')) || c == C(VEOL2, CTRL('m'))) c = '\0'; add: if (l >= len) { if (allocated) { size_t nlen = len + 1024; char *nbuf = realloc(buf, nlen); if (nbuf == NULL) goto restore; buf = nbuf; len = nlen; } else { if (flags & GETPASS_BUF_LIMIT) { beep(); continue; } if (c == '\0' && l > 0) l--; else continue; } } if (flags & GETPASS_7BIT) c &= 0x7f; if ((flags & GETPASS_FORCE_LOWER) && isupper((unsigned char)c)) c = tolower((unsigned char)c); if ((flags & GETPASS_FORCE_UPPER) && islower((unsigned char)c)) c = toupper((unsigned char)c); buf[l++] = c; if (c) { if (flags & GETPASS_ECHO_STAR) (void)write(fd[1], "*", 1); else if (flags & GETPASS_ECHO) (void)write(fd[1], isprint((unsigned char)c) ? &c : "?", 1); } } good = true; restore: out: if (havetty) { c = errno; (void)tcsetattr(fd[0], TCSAFLUSH|TCSASOFT, >); errno = c; } if (good && (flags & GETPASS_ECHO_NL)) (void)write(fd[1], "\n", 1); if (opentty) { c = errno; (void)close(fd[0]); errno = c; } if (good) return buf; if (sig) { if ((flags & GETPASS_NO_SIGNAL) == 0) (void)raise(sig); errno = EINTR; } memset(buf, 0, len); if (allocated) free(buf); return NULL; } char * getpass_r(const char *prompt, char *buf, size_t len) { return getpassfd(prompt, buf, len, NULL, GETPASS_ECHO_NL, 0); } char * getpass(const char *prompt) { static char e[] = ""; static char *buf; static long bufsiz; char *rv; /* * Strictly speaking we could double allocate here, if we get * called at the same time, but this function is not re-entrant * anyway and it is not supposed to work if called concurrently. */ if (buf == NULL) { if ((bufsiz = sysconf(_SC_PASS_MAX)) == -1) return e; if ((buf = malloc((size_t)bufsiz)) == NULL) return e; } if ((rv = getpass_r(prompt, buf, (size_t)bufsiz)) == NULL) return e; return rv; } #ifdef TEST int main(int argc, char *argv[]) { char buf[28]; printf("[%s]\n", getpassfd("foo>", buf, sizeof(buf), NULL, GETPASS_ECHO_STAR|GETPASS_ECHO_NL, 2)); return 0; } #endif