1*19261079SEd Maste /* $OpenBSD: readpass.c,v 1.69 2021/07/23 05:56:47 djm 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 * 51*19261079SEd 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) 61*19261079SEd Maste error_f("fflush: %s", strerror(errno)); 621e8db6e2SBrian Feldman if (askpass == NULL) 631e8db6e2SBrian Feldman fatal("internal error: askpass undefined"); 64*19261079SEd Maste if (pipe(p) == -1) { 65*19261079SEd Maste error_f("pipe: %s", strerror(errno)); 66d0c8c0bcSDag-Erling Smørgrav return NULL; 67ae1f160dSDag-Erling Smørgrav } 68*19261079SEd Maste osigchld = ssh_signal(SIGCHLD, SIG_DFL); 69*19261079SEd Maste if ((pid = fork()) == -1) { 70*19261079SEd Maste error_f("fork: %s", strerror(errno)); 71*19261079SEd 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]); 76*19261079SEd Maste if (dup2(p[1], STDOUT_FILENO) == -1) 77*19261079SEd Maste fatal_f("dup2: %s", strerror(errno)); 78*19261079SEd Maste if (env_hint != NULL) 79*19261079SEd Maste setenv("SSH_ASKPASS_PROMPT", env_hint, 1); 80acc1a9efSDag-Erling Smørgrav execlp(askpass, askpass, msg, (char *)NULL); 81*19261079SEd 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]); 98*19261079SEd Maste while ((ret = waitpid(pid, &status, 0)) == -1) 991e8db6e2SBrian Feldman if (errno != EINTR) 1001e8db6e2SBrian Feldman break; 101*19261079SEd 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 113*19261079SEd Maste /* private/internal read_passphrase flags */ 114*19261079SEd Maste #define RP_ASK_PERMISSION 0x8000 /* pass hint to askpass for confirm UI */ 115*19261079SEd 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 120*19261079SEd 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 { 125*19261079SEd Maste char cr = '\r', *askpass = NULL, *ret, buf[1024]; 126*19261079SEd Maste int rppflags, ttyfd, use_askpass = 0, allow_askpass = 0; 127*19261079SEd Maste const char *askpass_hint = NULL; 128*19261079SEd Maste const char *s; 129*19261079SEd Maste 130*19261079SEd Maste if ((s = getenv("DISPLAY")) != NULL) 131*19261079SEd Maste allow_askpass = *s != '\0'; 132*19261079SEd Maste if ((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) != NULL) { 133*19261079SEd Maste if (strcasecmp(s, "force") == 0) { 134*19261079SEd Maste use_askpass = 1; 135*19261079SEd Maste allow_askpass = 1; 136*19261079SEd Maste } else if (strcasecmp(s, "prefer") == 0) 137*19261079SEd Maste use_askpass = allow_askpass; 138*19261079SEd Maste else if (strcasecmp(s, "never") == 0) 139*19261079SEd Maste allow_askpass = 0; 140*19261079SEd Maste } 1411e8db6e2SBrian Feldman 142ae1f160dSDag-Erling Smørgrav rppflags = (flags & RP_ECHO) ? RPP_ECHO_ON : RPP_ECHO_OFF; 143*19261079SEd Maste if (use_askpass) 144*19261079SEd Maste debug_f("requested to askpass"); 145*19261079SEd 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)) { 149*19261079SEd 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); 155*19261079SEd Maste if (ttyfd >= 0) { 156*19261079SEd Maste /* 157*19261079SEd Maste * If we're on a tty, ensure that show the prompt at 158*19261079SEd Maste * the beginning of the line. This will hopefully 159*19261079SEd Maste * clobber any password characters the user has 160*19261079SEd Maste * optimistically typed before echo is disabled. 161*19261079SEd Maste */ 162*19261079SEd Maste (void)write(ttyfd, &cr, 1); 1631e8db6e2SBrian Feldman close(ttyfd); 164*19261079SEd Maste } else { 165*19261079SEd 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 171*19261079SEd Maste if ((flags & RP_USE_ASKPASS) && !allow_askpass) 172d74d50a8SDag-Erling Smørgrav return (flags & RP_ALLOW_EOF) ? NULL : xstrdup(""); 173d74d50a8SDag-Erling Smørgrav 174*19261079SEd 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; 179*19261079SEd Maste if ((flags & RP_ASK_PERMISSION) != 0) 180*19261079SEd Maste askpass_hint = "confirm"; 181*19261079SEd 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 209*19261079SEd Maste p = read_passphrase(prompt, 210*19261079SEd 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 } 224*19261079SEd Maste 225*19261079SEd Maste static void 226*19261079SEd Maste writemsg(const char *msg) 227*19261079SEd Maste { 228*19261079SEd Maste (void)write(STDERR_FILENO, "\r", 1); 229*19261079SEd Maste (void)write(STDERR_FILENO, msg, strlen(msg)); 230*19261079SEd Maste (void)write(STDERR_FILENO, "\r\n", 2); 231*19261079SEd Maste } 232*19261079SEd Maste 233*19261079SEd Maste struct notifier_ctx { 234*19261079SEd Maste pid_t pid; 235*19261079SEd Maste void (*osigchld)(int); 236*19261079SEd Maste }; 237*19261079SEd Maste 238*19261079SEd Maste struct notifier_ctx * 239*19261079SEd Maste notify_start(int force_askpass, const char *fmt, ...) 240*19261079SEd Maste { 241*19261079SEd Maste va_list args; 242*19261079SEd Maste char *prompt = NULL; 243*19261079SEd Maste pid_t pid = -1; 244*19261079SEd Maste void (*osigchld)(int) = NULL; 245*19261079SEd Maste const char *askpass, *s; 246*19261079SEd Maste struct notifier_ctx *ret = NULL; 247*19261079SEd Maste 248*19261079SEd Maste va_start(args, fmt); 249*19261079SEd Maste xvasprintf(&prompt, fmt, args); 250*19261079SEd Maste va_end(args); 251*19261079SEd Maste 252*19261079SEd Maste if (fflush(NULL) != 0) 253*19261079SEd Maste error_f("fflush: %s", strerror(errno)); 254*19261079SEd Maste if (!force_askpass && isatty(STDERR_FILENO)) { 255*19261079SEd Maste writemsg(prompt); 256*19261079SEd Maste goto out_ctx; 257*19261079SEd Maste } 258*19261079SEd Maste if ((askpass = getenv("SSH_ASKPASS")) == NULL) 259*19261079SEd Maste askpass = _PATH_SSH_ASKPASS_DEFAULT; 260*19261079SEd Maste if (*askpass == '\0') { 261*19261079SEd Maste debug3_f("cannot notify: no askpass"); 262*19261079SEd Maste goto out; 263*19261079SEd Maste } 264*19261079SEd Maste if (getenv("DISPLAY") == NULL && 265*19261079SEd Maste ((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) == NULL || 266*19261079SEd Maste strcmp(s, "force") != 0)) { 267*19261079SEd Maste debug3_f("cannot notify: no display"); 268*19261079SEd Maste goto out; 269*19261079SEd Maste } 270*19261079SEd Maste osigchld = ssh_signal(SIGCHLD, SIG_DFL); 271*19261079SEd Maste if ((pid = fork()) == -1) { 272*19261079SEd Maste error_f("fork: %s", strerror(errno)); 273*19261079SEd Maste ssh_signal(SIGCHLD, osigchld); 274*19261079SEd Maste free(prompt); 275*19261079SEd Maste return NULL; 276*19261079SEd Maste } 277*19261079SEd Maste if (pid == 0) { 278*19261079SEd Maste if (stdfd_devnull(1, 1, 0) == -1) 279*19261079SEd Maste fatal_f("stdfd_devnull failed"); 280*19261079SEd Maste closefrom(STDERR_FILENO + 1); 281*19261079SEd Maste setenv("SSH_ASKPASS_PROMPT", "none", 1); /* hint to UI */ 282*19261079SEd Maste execlp(askpass, askpass, prompt, (char *)NULL); 283*19261079SEd Maste error_f("exec(%s): %s", askpass, strerror(errno)); 284*19261079SEd Maste _exit(1); 285*19261079SEd Maste /* NOTREACHED */ 286*19261079SEd Maste } 287*19261079SEd Maste out_ctx: 288*19261079SEd Maste if ((ret = calloc(1, sizeof(*ret))) == NULL) { 289*19261079SEd Maste kill(pid, SIGTERM); 290*19261079SEd Maste fatal_f("calloc failed"); 291*19261079SEd Maste } 292*19261079SEd Maste ret->pid = pid; 293*19261079SEd Maste ret->osigchld = osigchld; 294*19261079SEd Maste out: 295*19261079SEd Maste free(prompt); 296*19261079SEd Maste return ret; 297*19261079SEd Maste } 298*19261079SEd Maste 299*19261079SEd Maste void 300*19261079SEd Maste notify_complete(struct notifier_ctx *ctx, const char *fmt, ...) 301*19261079SEd Maste { 302*19261079SEd Maste int ret; 303*19261079SEd Maste char *msg = NULL; 304*19261079SEd Maste va_list args; 305*19261079SEd Maste 306*19261079SEd Maste if (ctx != NULL && fmt != NULL && ctx->pid == -1) { 307*19261079SEd Maste /* 308*19261079SEd Maste * notify_start wrote to stderr, so send conclusion message 309*19261079SEd Maste * there too 310*19261079SEd Maste */ 311*19261079SEd Maste va_start(args, fmt); 312*19261079SEd Maste xvasprintf(&msg, fmt, args); 313*19261079SEd Maste va_end(args); 314*19261079SEd Maste writemsg(msg); 315*19261079SEd Maste free(msg); 316*19261079SEd Maste } 317*19261079SEd Maste 318*19261079SEd Maste if (ctx == NULL || ctx->pid <= 0) { 319*19261079SEd Maste free(ctx); 320*19261079SEd Maste return; 321*19261079SEd Maste } 322*19261079SEd Maste kill(ctx->pid, SIGTERM); 323*19261079SEd Maste while ((ret = waitpid(ctx->pid, NULL, 0)) == -1) { 324*19261079SEd Maste if (errno != EINTR) 325*19261079SEd Maste break; 326*19261079SEd Maste } 327*19261079SEd Maste if (ret == -1) 328*19261079SEd Maste fatal_f("waitpid: %s", strerror(errno)); 329*19261079SEd Maste ssh_signal(SIGCHLD, ctx->osigchld); 330*19261079SEd Maste free(ctx); 331*19261079SEd Maste } 332