1*38a52bd3SEd Maste /* $OpenBSD: readpass.c,v 1.70 2022/05/27 04:27:49 dtucker Exp $ */ 2511b41d2SMark Murray /* 3ae1f160dSDag-Erling Smørgrav * Copyright (c) 2001 Markus Friedl. All rights reserved. 4511b41d2SMark Murray * 5511b41d2SMark Murray * Redistribution and use in source and binary forms, with or without 6511b41d2SMark Murray * modification, are permitted provided that the following conditions 7511b41d2SMark Murray * are met: 8511b41d2SMark Murray * 1. Redistributions of source code must retain the above copyright 9511b41d2SMark Murray * notice, this list of conditions and the following disclaimer. 10511b41d2SMark Murray * 2. Redistributions in binary form must reproduce the above copyright 11511b41d2SMark Murray * notice, this list of conditions and the following disclaimer in the 12511b41d2SMark Murray * documentation and/or other materials provided with the distribution. 13511b41d2SMark Murray * 14ae1f160dSDag-Erling Smørgrav * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15ae1f160dSDag-Erling Smørgrav * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16ae1f160dSDag-Erling Smørgrav * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17ae1f160dSDag-Erling Smørgrav * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18ae1f160dSDag-Erling Smørgrav * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19ae1f160dSDag-Erling Smørgrav * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20ae1f160dSDag-Erling Smørgrav * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21ae1f160dSDag-Erling Smørgrav * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22ae1f160dSDag-Erling Smørgrav * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23ae1f160dSDag-Erling Smørgrav * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24511b41d2SMark Murray */ 25511b41d2SMark Murray 26511b41d2SMark Murray #include "includes.h" 27761efaa7SDag-Erling Smørgrav 28761efaa7SDag-Erling Smørgrav #include <sys/types.h> 29761efaa7SDag-Erling Smørgrav #include <sys/wait.h> 30761efaa7SDag-Erling Smørgrav 31761efaa7SDag-Erling Smørgrav #include <errno.h> 32761efaa7SDag-Erling Smørgrav #include <fcntl.h> 33761efaa7SDag-Erling Smørgrav #ifdef HAVE_PATHS_H 34761efaa7SDag-Erling Smørgrav # include <paths.h> 35761efaa7SDag-Erling Smørgrav #endif 364a421b63SDag-Erling Smørgrav #include <signal.h> 37761efaa7SDag-Erling Smørgrav #include <stdarg.h> 38761efaa7SDag-Erling Smørgrav #include <stdio.h> 39761efaa7SDag-Erling Smørgrav #include <stdlib.h> 40761efaa7SDag-Erling Smørgrav #include <string.h> 41761efaa7SDag-Erling Smørgrav #include <unistd.h> 42ae1f160dSDag-Erling Smørgrav 43511b41d2SMark Murray #include "xmalloc.h" 44d74d50a8SDag-Erling Smørgrav #include "misc.h" 451e8db6e2SBrian Feldman #include "pathnames.h" 461e8db6e2SBrian Feldman #include "log.h" 471e8db6e2SBrian Feldman #include "ssh.h" 48761efaa7SDag-Erling Smørgrav #include "uidswap.h" 491e8db6e2SBrian Feldman 50ae1f160dSDag-Erling Smørgrav static char * 5119261079SEd Maste ssh_askpass(char *askpass, const char *msg, const char *env_hint) 521e8db6e2SBrian Feldman { 534a421b63SDag-Erling Smørgrav pid_t pid, ret; 541e8db6e2SBrian Feldman size_t len; 55ae1f160dSDag-Erling Smørgrav char *pass; 564a421b63SDag-Erling Smørgrav int p[2], status; 571e8db6e2SBrian Feldman char buf[1024]; 584a421b63SDag-Erling Smørgrav void (*osigchld)(int); 591e8db6e2SBrian Feldman 601e8db6e2SBrian Feldman if (fflush(stdout) != 0) 6119261079SEd Maste error_f("fflush: %s", strerror(errno)); 621e8db6e2SBrian Feldman if (askpass == NULL) 631e8db6e2SBrian Feldman fatal("internal error: askpass undefined"); 6419261079SEd Maste if (pipe(p) == -1) { 6519261079SEd Maste error_f("pipe: %s", strerror(errno)); 66d0c8c0bcSDag-Erling Smørgrav return NULL; 67ae1f160dSDag-Erling Smørgrav } 6819261079SEd Maste osigchld = ssh_signal(SIGCHLD, SIG_DFL); 6919261079SEd Maste if ((pid = fork()) == -1) { 7019261079SEd Maste error_f("fork: %s", strerror(errno)); 7119261079SEd Maste ssh_signal(SIGCHLD, osigchld); 72d0c8c0bcSDag-Erling Smørgrav return NULL; 73ae1f160dSDag-Erling Smørgrav } 741e8db6e2SBrian Feldman if (pid == 0) { 751e8db6e2SBrian Feldman close(p[0]); 7619261079SEd Maste if (dup2(p[1], STDOUT_FILENO) == -1) 7719261079SEd Maste fatal_f("dup2: %s", strerror(errno)); 7819261079SEd Maste if (env_hint != NULL) 7919261079SEd Maste setenv("SSH_ASKPASS_PROMPT", env_hint, 1); 80acc1a9efSDag-Erling Smørgrav execlp(askpass, askpass, msg, (char *)NULL); 8119261079SEd Maste fatal_f("exec(%s): %s", askpass, strerror(errno)); 821e8db6e2SBrian Feldman } 831e8db6e2SBrian Feldman close(p[1]); 84ae1f160dSDag-Erling Smørgrav 854a421b63SDag-Erling Smørgrav len = 0; 86ae1f160dSDag-Erling Smørgrav do { 874a421b63SDag-Erling Smørgrav ssize_t r = read(p[0], buf + len, sizeof(buf) - 1 - len); 884a421b63SDag-Erling Smørgrav 894a421b63SDag-Erling Smørgrav if (r == -1 && errno == EINTR) 90ae1f160dSDag-Erling Smørgrav continue; 914a421b63SDag-Erling Smørgrav if (r <= 0) 92ae1f160dSDag-Erling Smørgrav break; 934a421b63SDag-Erling Smørgrav len += r; 94ae1f160dSDag-Erling Smørgrav } while (sizeof(buf) - 1 - len > 0); 95ae1f160dSDag-Erling Smørgrav buf[len] = '\0'; 96ae1f160dSDag-Erling Smørgrav 971e8db6e2SBrian Feldman close(p[0]); 9819261079SEd Maste while ((ret = waitpid(pid, &status, 0)) == -1) 991e8db6e2SBrian Feldman if (errno != EINTR) 1001e8db6e2SBrian Feldman break; 10119261079SEd Maste ssh_signal(SIGCHLD, osigchld); 1024a421b63SDag-Erling Smørgrav if (ret == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { 103b83788ffSDag-Erling Smørgrav explicit_bzero(buf, sizeof(buf)); 104d0c8c0bcSDag-Erling Smørgrav return NULL; 105d0c8c0bcSDag-Erling Smørgrav } 106d0c8c0bcSDag-Erling Smørgrav 107ae1f160dSDag-Erling Smørgrav buf[strcspn(buf, "\r\n")] = '\0'; 1081e8db6e2SBrian Feldman pass = xstrdup(buf); 109b83788ffSDag-Erling Smørgrav explicit_bzero(buf, sizeof(buf)); 1101e8db6e2SBrian Feldman return pass; 1111e8db6e2SBrian Feldman } 1121e8db6e2SBrian Feldman 11319261079SEd Maste /* private/internal read_passphrase flags */ 11419261079SEd Maste #define RP_ASK_PERMISSION 0x8000 /* pass hint to askpass for confirm UI */ 11519261079SEd Maste 116511b41d2SMark Murray /* 117ae1f160dSDag-Erling Smørgrav * Reads a passphrase from /dev/tty with echo turned off/on. Returns the 118ae1f160dSDag-Erling Smørgrav * passphrase (allocated with xmalloc). Exits if EOF is encountered. If 119ae1f160dSDag-Erling Smørgrav * RP_ALLOW_STDIN is set, the passphrase will be read from stdin if no 12019261079SEd Maste * tty is or askpass program is available 1215b9b2fafSBrian Feldman */ 122511b41d2SMark Murray char * 123ae1f160dSDag-Erling Smørgrav read_passphrase(const char *prompt, int flags) 124511b41d2SMark Murray { 12519261079SEd Maste char cr = '\r', *askpass = NULL, *ret, buf[1024]; 12619261079SEd Maste int rppflags, ttyfd, use_askpass = 0, allow_askpass = 0; 12719261079SEd Maste const char *askpass_hint = NULL; 12819261079SEd Maste const char *s; 12919261079SEd Maste 13019261079SEd Maste if ((s = getenv("DISPLAY")) != NULL) 13119261079SEd Maste allow_askpass = *s != '\0'; 13219261079SEd Maste if ((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) != NULL) { 13319261079SEd Maste if (strcasecmp(s, "force") == 0) { 13419261079SEd Maste use_askpass = 1; 13519261079SEd Maste allow_askpass = 1; 13619261079SEd Maste } else if (strcasecmp(s, "prefer") == 0) 13719261079SEd Maste use_askpass = allow_askpass; 13819261079SEd Maste else if (strcasecmp(s, "never") == 0) 13919261079SEd Maste allow_askpass = 0; 14019261079SEd Maste } 1411e8db6e2SBrian Feldman 142ae1f160dSDag-Erling Smørgrav rppflags = (flags & RP_ECHO) ? RPP_ECHO_ON : RPP_ECHO_OFF; 14319261079SEd Maste if (use_askpass) 14419261079SEd Maste debug_f("requested to askpass"); 14519261079SEd Maste else if (flags & RP_USE_ASKPASS) 146d74d50a8SDag-Erling Smørgrav use_askpass = 1; 147d74d50a8SDag-Erling Smørgrav else if (flags & RP_ALLOW_STDIN) { 148043840dfSDag-Erling Smørgrav if (!isatty(STDIN_FILENO)) { 14919261079SEd Maste debug_f("stdin is not a tty"); 1501e8db6e2SBrian Feldman use_askpass = 1; 151043840dfSDag-Erling Smørgrav } 1521e8db6e2SBrian Feldman } else { 153ae1f160dSDag-Erling Smørgrav rppflags |= RPP_REQUIRE_TTY; 154ae1f160dSDag-Erling Smørgrav ttyfd = open(_PATH_TTY, O_RDWR); 15519261079SEd Maste if (ttyfd >= 0) { 15619261079SEd Maste /* 15719261079SEd Maste * If we're on a tty, ensure that show the prompt at 15819261079SEd Maste * the beginning of the line. This will hopefully 15919261079SEd Maste * clobber any password characters the user has 16019261079SEd Maste * optimistically typed before echo is disabled. 16119261079SEd Maste */ 16219261079SEd Maste (void)write(ttyfd, &cr, 1); 1631e8db6e2SBrian Feldman close(ttyfd); 16419261079SEd Maste } else { 16519261079SEd Maste debug_f("can't open %s: %s", _PATH_TTY, 166043840dfSDag-Erling Smørgrav strerror(errno)); 1671e8db6e2SBrian Feldman use_askpass = 1; 1681e8db6e2SBrian Feldman } 169043840dfSDag-Erling Smørgrav } 1701e8db6e2SBrian Feldman 17119261079SEd Maste if ((flags & RP_USE_ASKPASS) && !allow_askpass) 172d74d50a8SDag-Erling Smørgrav return (flags & RP_ALLOW_EOF) ? NULL : xstrdup(""); 173d74d50a8SDag-Erling Smørgrav 17419261079SEd Maste if (use_askpass && allow_askpass) { 1751e8db6e2SBrian Feldman if (getenv(SSH_ASKPASS_ENV)) 1761e8db6e2SBrian Feldman askpass = getenv(SSH_ASKPASS_ENV); 1771e8db6e2SBrian Feldman else 1781e8db6e2SBrian Feldman askpass = _PATH_SSH_ASKPASS_DEFAULT; 17919261079SEd Maste if ((flags & RP_ASK_PERMISSION) != 0) 18019261079SEd Maste askpass_hint = "confirm"; 18119261079SEd Maste if ((ret = ssh_askpass(askpass, prompt, askpass_hint)) == NULL) 182d0c8c0bcSDag-Erling Smørgrav if (!(flags & RP_ALLOW_EOF)) 183d0c8c0bcSDag-Erling Smørgrav return xstrdup(""); 184d0c8c0bcSDag-Erling Smørgrav return ret; 1851e8db6e2SBrian Feldman } 1861e8db6e2SBrian Feldman 187545d5ecaSDag-Erling Smørgrav if (readpassphrase(prompt, buf, sizeof buf, rppflags) == NULL) { 188545d5ecaSDag-Erling Smørgrav if (flags & RP_ALLOW_EOF) 189545d5ecaSDag-Erling Smørgrav return NULL; 190ae1f160dSDag-Erling Smørgrav return xstrdup(""); 191545d5ecaSDag-Erling Smørgrav } 192ae1f160dSDag-Erling Smørgrav 193ae1f160dSDag-Erling Smørgrav ret = xstrdup(buf); 194b83788ffSDag-Erling Smørgrav explicit_bzero(buf, sizeof(buf)); 195ae1f160dSDag-Erling Smørgrav return ret; 196511b41d2SMark Murray } 1975e8dbd04SDag-Erling Smørgrav 1985e8dbd04SDag-Erling Smørgrav int 1995e8dbd04SDag-Erling Smørgrav ask_permission(const char *fmt, ...) 2005e8dbd04SDag-Erling Smørgrav { 2015e8dbd04SDag-Erling Smørgrav va_list args; 2025e8dbd04SDag-Erling Smørgrav char *p, prompt[1024]; 2035e8dbd04SDag-Erling Smørgrav int allowed = 0; 2045e8dbd04SDag-Erling Smørgrav 2055e8dbd04SDag-Erling Smørgrav va_start(args, fmt); 2065e8dbd04SDag-Erling Smørgrav vsnprintf(prompt, sizeof(prompt), fmt, args); 2075e8dbd04SDag-Erling Smørgrav va_end(args); 2085e8dbd04SDag-Erling Smørgrav 20919261079SEd Maste p = read_passphrase(prompt, 21019261079SEd Maste RP_USE_ASKPASS|RP_ALLOW_EOF|RP_ASK_PERMISSION); 2115e8dbd04SDag-Erling Smørgrav if (p != NULL) { 2125e8dbd04SDag-Erling Smørgrav /* 2135e8dbd04SDag-Erling Smørgrav * Accept empty responses and responses consisting 2145e8dbd04SDag-Erling Smørgrav * of the word "yes" as affirmative. 2155e8dbd04SDag-Erling Smørgrav */ 2165e8dbd04SDag-Erling Smørgrav if (*p == '\0' || *p == '\n' || 2175e8dbd04SDag-Erling Smørgrav strcasecmp(p, "yes") == 0) 2185e8dbd04SDag-Erling Smørgrav allowed = 1; 219e4a9863fSDag-Erling Smørgrav free(p); 2205e8dbd04SDag-Erling Smørgrav } 2215e8dbd04SDag-Erling Smørgrav 2225e8dbd04SDag-Erling Smørgrav return (allowed); 2235e8dbd04SDag-Erling Smørgrav } 22419261079SEd Maste 22519261079SEd Maste static void 22619261079SEd Maste writemsg(const char *msg) 22719261079SEd Maste { 22819261079SEd Maste (void)write(STDERR_FILENO, "\r", 1); 22919261079SEd Maste (void)write(STDERR_FILENO, msg, strlen(msg)); 23019261079SEd Maste (void)write(STDERR_FILENO, "\r\n", 2); 23119261079SEd Maste } 23219261079SEd Maste 23319261079SEd Maste struct notifier_ctx { 23419261079SEd Maste pid_t pid; 23519261079SEd Maste void (*osigchld)(int); 23619261079SEd Maste }; 23719261079SEd Maste 23819261079SEd Maste struct notifier_ctx * 23919261079SEd Maste notify_start(int force_askpass, const char *fmt, ...) 24019261079SEd Maste { 24119261079SEd Maste va_list args; 24219261079SEd Maste char *prompt = NULL; 24319261079SEd Maste pid_t pid = -1; 24419261079SEd Maste void (*osigchld)(int) = NULL; 24519261079SEd Maste const char *askpass, *s; 24619261079SEd Maste struct notifier_ctx *ret = NULL; 24719261079SEd Maste 24819261079SEd Maste va_start(args, fmt); 24919261079SEd Maste xvasprintf(&prompt, fmt, args); 25019261079SEd Maste va_end(args); 25119261079SEd Maste 25219261079SEd Maste if (fflush(NULL) != 0) 25319261079SEd Maste error_f("fflush: %s", strerror(errno)); 25419261079SEd Maste if (!force_askpass && isatty(STDERR_FILENO)) { 25519261079SEd Maste writemsg(prompt); 25619261079SEd Maste goto out_ctx; 25719261079SEd Maste } 25819261079SEd Maste if ((askpass = getenv("SSH_ASKPASS")) == NULL) 25919261079SEd Maste askpass = _PATH_SSH_ASKPASS_DEFAULT; 26019261079SEd Maste if (*askpass == '\0') { 26119261079SEd Maste debug3_f("cannot notify: no askpass"); 26219261079SEd Maste goto out; 26319261079SEd Maste } 26419261079SEd Maste if (getenv("DISPLAY") == NULL && 26519261079SEd Maste ((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) == NULL || 26619261079SEd Maste strcmp(s, "force") != 0)) { 26719261079SEd Maste debug3_f("cannot notify: no display"); 26819261079SEd Maste goto out; 26919261079SEd Maste } 27019261079SEd Maste osigchld = ssh_signal(SIGCHLD, SIG_DFL); 27119261079SEd Maste if ((pid = fork()) == -1) { 27219261079SEd Maste error_f("fork: %s", strerror(errno)); 27319261079SEd Maste ssh_signal(SIGCHLD, osigchld); 27419261079SEd Maste free(prompt); 27519261079SEd Maste return NULL; 27619261079SEd Maste } 27719261079SEd Maste if (pid == 0) { 27819261079SEd Maste if (stdfd_devnull(1, 1, 0) == -1) 27919261079SEd Maste fatal_f("stdfd_devnull failed"); 28019261079SEd Maste closefrom(STDERR_FILENO + 1); 28119261079SEd Maste setenv("SSH_ASKPASS_PROMPT", "none", 1); /* hint to UI */ 28219261079SEd Maste execlp(askpass, askpass, prompt, (char *)NULL); 28319261079SEd Maste error_f("exec(%s): %s", askpass, strerror(errno)); 28419261079SEd Maste _exit(1); 28519261079SEd Maste /* NOTREACHED */ 28619261079SEd Maste } 28719261079SEd Maste out_ctx: 28819261079SEd Maste if ((ret = calloc(1, sizeof(*ret))) == NULL) { 289*38a52bd3SEd Maste if (pid != -1) 29019261079SEd Maste kill(pid, SIGTERM); 29119261079SEd Maste fatal_f("calloc failed"); 29219261079SEd Maste } 29319261079SEd Maste ret->pid = pid; 29419261079SEd Maste ret->osigchld = osigchld; 29519261079SEd Maste out: 29619261079SEd Maste free(prompt); 29719261079SEd Maste return ret; 29819261079SEd Maste } 29919261079SEd Maste 30019261079SEd Maste void 30119261079SEd Maste notify_complete(struct notifier_ctx *ctx, const char *fmt, ...) 30219261079SEd Maste { 30319261079SEd Maste int ret; 30419261079SEd Maste char *msg = NULL; 30519261079SEd Maste va_list args; 30619261079SEd Maste 30719261079SEd Maste if (ctx != NULL && fmt != NULL && ctx->pid == -1) { 30819261079SEd Maste /* 30919261079SEd Maste * notify_start wrote to stderr, so send conclusion message 31019261079SEd Maste * there too 31119261079SEd Maste */ 31219261079SEd Maste va_start(args, fmt); 31319261079SEd Maste xvasprintf(&msg, fmt, args); 31419261079SEd Maste va_end(args); 31519261079SEd Maste writemsg(msg); 31619261079SEd Maste free(msg); 31719261079SEd Maste } 31819261079SEd Maste 31919261079SEd Maste if (ctx == NULL || ctx->pid <= 0) { 32019261079SEd Maste free(ctx); 32119261079SEd Maste return; 32219261079SEd Maste } 32319261079SEd Maste kill(ctx->pid, SIGTERM); 32419261079SEd Maste while ((ret = waitpid(ctx->pid, NULL, 0)) == -1) { 32519261079SEd Maste if (errno != EINTR) 32619261079SEd Maste break; 32719261079SEd Maste } 32819261079SEd Maste if (ret == -1) 32919261079SEd Maste fatal_f("waitpid: %s", strerror(errno)); 33019261079SEd Maste ssh_signal(SIGCHLD, ctx->osigchld); 33119261079SEd Maste free(ctx); 33219261079SEd Maste } 333