jcs's openbsd hax
openbsd
1/* $OpenBSD: readpass.c,v 1.73 2026/02/14 00:18:34 jsg Exp $ */
2/*
3 * Copyright (c) 2001 Markus Friedl. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include <sys/types.h>
27#include <sys/wait.h>
28
29#include <errno.h>
30#include <fcntl.h>
31#include <paths.h>
32#include <readpassphrase.h>
33#include <signal.h>
34#include <stdarg.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39
40#include "xmalloc.h"
41#include "misc.h"
42#include "pathnames.h"
43#include "log.h"
44#include "ssh.h"
45
46static char *
47ssh_askpass(char *askpass, const char *msg, const char *env_hint)
48{
49 pid_t pid, ret;
50 size_t len;
51 char *pass;
52 int p[2], status;
53 char buf[1024];
54 void (*osigchld)(int);
55
56 if (fflush(stdout) != 0)
57 error_f("fflush: %s", strerror(errno));
58 if (askpass == NULL)
59 fatal("internal error: askpass undefined");
60 if (pipe(p) == -1) {
61 error_f("pipe: %s", strerror(errno));
62 return NULL;
63 }
64 osigchld = ssh_signal(SIGCHLD, SIG_DFL);
65 if ((pid = fork()) == -1) {
66 error_f("fork: %s", strerror(errno));
67 ssh_signal(SIGCHLD, osigchld);
68 return NULL;
69 }
70 if (pid == 0) {
71 close(p[0]);
72 if (dup2(p[1], STDOUT_FILENO) == -1)
73 fatal_f("dup2: %s", strerror(errno));
74 if (env_hint != NULL)
75 setenv("SSH_ASKPASS_PROMPT", env_hint, 1);
76 execlp(askpass, askpass, msg, (char *)NULL);
77 fatal_f("exec(%s): %s", askpass, strerror(errno));
78 }
79 close(p[1]);
80
81 len = 0;
82 do {
83 ssize_t r = read(p[0], buf + len, sizeof(buf) - 1 - len);
84
85 if (r == -1 && errno == EINTR)
86 continue;
87 if (r <= 0)
88 break;
89 len += r;
90 } while (len < sizeof(buf) - 1);
91 buf[len] = '\0';
92
93 close(p[0]);
94 while ((ret = waitpid(pid, &status, 0)) == -1)
95 if (errno != EINTR)
96 break;
97 ssh_signal(SIGCHLD, osigchld);
98 if (ret == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
99 explicit_bzero(buf, sizeof(buf));
100 return NULL;
101 }
102
103 buf[strcspn(buf, "\r\n")] = '\0';
104 pass = xstrdup(buf);
105 explicit_bzero(buf, sizeof(buf));
106 return pass;
107}
108
109/* private/internal read_passphrase flags */
110#define RP_ASK_PERMISSION 0x8000 /* pass hint to askpass for confirm UI */
111
112/*
113 * Reads a passphrase from /dev/tty with echo turned off/on. Returns the
114 * passphrase (allocated with xmalloc). Exits if EOF is encountered. If
115 * RP_ALLOW_STDIN is set, the passphrase will be read from stdin if no
116 * tty is or askpass program is available
117 */
118char *
119read_passphrase(const char *prompt, int flags)
120{
121 char cr = '\r', *askpass = NULL, *ret, buf[1024];
122 int rppflags, ttyfd, use_askpass = 0, allow_askpass = 0;
123 const char *askpass_hint = NULL;
124 const char *s;
125
126 if (((s = getenv("DISPLAY")) != NULL && *s != '\0') ||
127 ((s = getenv("WAYLAND_DISPLAY")) != NULL && *s != '\0'))
128 allow_askpass = 1;
129 if ((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) != NULL) {
130 if (strcasecmp(s, "force") == 0) {
131 use_askpass = 1;
132 allow_askpass = 1;
133 } else if (strcasecmp(s, "prefer") == 0)
134 use_askpass = allow_askpass;
135 else if (strcasecmp(s, "never") == 0)
136 allow_askpass = 0;
137 }
138
139 rppflags = (flags & RP_ECHO) ? RPP_ECHO_ON : RPP_ECHO_OFF;
140 if (use_askpass)
141 debug_f("requested to askpass");
142 else if (flags & RP_USE_ASKPASS)
143 use_askpass = 1;
144 else if (flags & RP_ALLOW_STDIN) {
145 if (!isatty(STDIN_FILENO)) {
146 debug_f("stdin is not a tty");
147 use_askpass = 1;
148 }
149 } else {
150 rppflags |= RPP_REQUIRE_TTY;
151 ttyfd = open(_PATH_TTY, O_RDWR);
152 if (ttyfd >= 0) {
153 /*
154 * If we're on a tty, ensure that show the prompt at
155 * the beginning of the line. This will hopefully
156 * clobber any password characters the user has
157 * optimistically typed before echo is disabled.
158 */
159 (void)write(ttyfd, &cr, 1);
160 close(ttyfd);
161 } else {
162 debug_f("can't open %s: %s", _PATH_TTY,
163 strerror(errno));
164 use_askpass = 1;
165 }
166 }
167
168 if ((flags & RP_USE_ASKPASS) && !allow_askpass)
169 return (flags & RP_ALLOW_EOF) ? NULL : xstrdup("");
170
171 if (use_askpass && allow_askpass) {
172 if (getenv(SSH_ASKPASS_ENV))
173 askpass = getenv(SSH_ASKPASS_ENV);
174 else
175 askpass = _PATH_SSH_ASKPASS_DEFAULT;
176 if ((flags & RP_ASK_PERMISSION) != 0)
177 askpass_hint = "confirm";
178 if ((ret = ssh_askpass(askpass, prompt, askpass_hint)) == NULL)
179 if (!(flags & RP_ALLOW_EOF))
180 return xstrdup("");
181 return ret;
182 }
183
184 if (readpassphrase(prompt, buf, sizeof buf, rppflags) == NULL) {
185 if (flags & RP_ALLOW_EOF)
186 return NULL;
187 return xstrdup("");
188 }
189
190 ret = xstrdup(buf);
191 explicit_bzero(buf, sizeof(buf));
192 return ret;
193}
194
195int
196ask_permission(const char *fmt, ...)
197{
198 va_list args;
199 char *p, prompt[1024];
200 int allowed = 0;
201
202 va_start(args, fmt);
203 vsnprintf(prompt, sizeof(prompt), fmt, args);
204 va_end(args);
205
206 p = read_passphrase(prompt,
207 RP_USE_ASKPASS|RP_ALLOW_EOF|RP_ASK_PERMISSION);
208 if (p != NULL) {
209 /*
210 * Accept empty responses and responses consisting
211 * of the word "yes" as affirmative.
212 */
213 if (*p == '\0' || *p == '\n' ||
214 strcasecmp(p, "yes") == 0)
215 allowed = 1;
216 free(p);
217 }
218
219 return (allowed);
220}
221
222static void
223writemsg(const char *msg)
224{
225 (void)write(STDERR_FILENO, "\r", 1);
226 (void)write(STDERR_FILENO, msg, strlen(msg));
227 (void)write(STDERR_FILENO, "\r\n", 2);
228}
229
230struct notifier_ctx {
231 pid_t pid;
232 void (*osigchld)(int);
233};
234
235struct notifier_ctx *
236notify_start(int force_askpass, const char *fmt, ...)
237{
238 va_list args;
239 char *prompt = NULL;
240 pid_t pid = -1;
241 void (*osigchld)(int) = NULL;
242 const char *askpass, *s;
243 struct notifier_ctx *ret = NULL;
244
245 va_start(args, fmt);
246 xvasprintf(&prompt, fmt, args);
247 va_end(args);
248
249 if (fflush(NULL) != 0)
250 error_f("fflush: %s", strerror(errno));
251 if (!force_askpass && isatty(STDERR_FILENO)) {
252 writemsg(prompt);
253 goto out_ctx;
254 }
255 if ((askpass = getenv("SSH_ASKPASS")) == NULL)
256 askpass = _PATH_SSH_ASKPASS_DEFAULT;
257 if (*askpass == '\0') {
258 debug3_f("cannot notify: no askpass");
259 goto out;
260 }
261 if (getenv("DISPLAY") == NULL && getenv("WAYLAND_DISPLAY") == NULL &&
262 ((s = getenv(SSH_ASKPASS_REQUIRE_ENV)) == NULL ||
263 strcmp(s, "force") != 0)) {
264 debug3_f("cannot notify: no display");
265 goto out;
266 }
267 osigchld = ssh_signal(SIGCHLD, SIG_DFL);
268 if ((pid = fork()) == -1) {
269 error_f("fork: %s", strerror(errno));
270 ssh_signal(SIGCHLD, osigchld);
271 free(prompt);
272 return NULL;
273 }
274 if (pid == 0) {
275 if (stdfd_devnull(1, 1, 0) == -1)
276 fatal_f("stdfd_devnull failed");
277 closefrom(STDERR_FILENO + 1);
278 setenv("SSH_ASKPASS_PROMPT", "none", 1); /* hint to UI */
279 execlp(askpass, askpass, prompt, (char *)NULL);
280 error_f("exec(%s): %s", askpass, strerror(errno));
281 _exit(1);
282 /* NOTREACHED */
283 }
284 out_ctx:
285 if ((ret = calloc(1, sizeof(*ret))) == NULL) {
286 if (pid != -1)
287 kill(pid, SIGTERM);
288 fatal_f("calloc failed");
289 }
290 ret->pid = pid;
291 ret->osigchld = osigchld;
292 out:
293 free(prompt);
294 return ret;
295}
296
297void
298notify_complete(struct notifier_ctx *ctx, const char *fmt, ...)
299{
300 int ret;
301 char *msg = NULL;
302 va_list args;
303
304 if (ctx != NULL && fmt != NULL && ctx->pid == -1) {
305 /*
306 * notify_start wrote to stderr, so send conclusion message
307 * there too
308 */
309 va_start(args, fmt);
310 xvasprintf(&msg, fmt, args);
311 va_end(args);
312 writemsg(msg);
313 free(msg);
314 }
315
316 if (ctx == NULL || ctx->pid <= 0) {
317 free(ctx);
318 return;
319 }
320 kill(ctx->pid, SIGTERM);
321 while ((ret = waitpid(ctx->pid, NULL, 0)) == -1) {
322 if (errno != EINTR)
323 break;
324 }
325 if (ret == -1)
326 fatal_f("waitpid: %s", strerror(errno));
327 ssh_signal(SIGCHLD, ctx->osigchld);
328 free(ctx);
329}