Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/src/tgetpass.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 1996, 1998-2005, 2007-2021
5
* Todd C. Miller <[email protected]>
6
*
7
* Permission to use, copy, modify, and distribute this software for any
8
* purpose with or without fee is hereby granted, provided that the above
9
* copyright notice and this permission notice appear in all copies.
10
*
11
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
*
19
* Sponsored in part by the Defense Advanced Research Projects
20
* Agency (DARPA) and Air Force Research Laboratory, Air Force
21
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
22
*/
23
24
#ifdef __TANDEM
25
# include <floss.h>
26
#endif
27
28
#include <config.h>
29
30
#include <sys/wait.h>
31
#include <stdio.h>
32
#include <stdlib.h>
33
#include <string.h>
34
#include <unistd.h>
35
#include <errno.h>
36
#include <signal.h>
37
#include <fcntl.h>
38
39
#include <sudo.h>
40
#include <sudo_plugin.h>
41
42
enum tgetpass_errval {
43
TGP_ERRVAL_NOERROR,
44
TGP_ERRVAL_TIMEOUT,
45
TGP_ERRVAL_NOPASSWORD,
46
TGP_ERRVAL_READERROR
47
};
48
49
static volatile sig_atomic_t signo[NSIG];
50
51
static void tgetpass_handler(int);
52
static char *getln(int, char *, size_t, bool, enum tgetpass_errval *);
53
static char *sudo_askpass(const char *, const char *);
54
55
static int
56
suspend(int sig, struct sudo_conv_callback *callback)
57
{
58
int ret = 0;
59
debug_decl(suspend, SUDO_DEBUG_CONV);
60
61
if (callback != NULL && SUDO_API_VERSION_GET_MAJOR(callback->version) != SUDO_CONV_CALLBACK_VERSION_MAJOR) {
62
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
63
"callback major version mismatch, expected %u, got %u",
64
SUDO_CONV_CALLBACK_VERSION_MAJOR,
65
SUDO_API_VERSION_GET_MAJOR(callback->version));
66
callback = NULL;
67
}
68
69
if (callback != NULL && callback->on_suspend != NULL) {
70
if (callback->on_suspend(sig, callback->closure) == -1)
71
ret = -1;
72
}
73
kill(getpid(), sig);
74
if (callback != NULL && callback->on_resume != NULL) {
75
if (callback->on_resume(sig, callback->closure) == -1)
76
ret = -1;
77
}
78
debug_return_int(ret);
79
}
80
81
static void
82
tgetpass_display_error(enum tgetpass_errval errval)
83
{
84
debug_decl(tgetpass_display_error, SUDO_DEBUG_CONV);
85
86
switch (errval) {
87
case TGP_ERRVAL_NOERROR:
88
break;
89
case TGP_ERRVAL_TIMEOUT:
90
sudo_warnx("%s", U_("timed out reading password"));
91
break;
92
case TGP_ERRVAL_NOPASSWORD:
93
sudo_warnx("%s", U_("no password was provided"));
94
break;
95
case TGP_ERRVAL_READERROR:
96
sudo_warn("%s", U_("unable to read password"));
97
break;
98
}
99
debug_return;
100
}
101
102
/*
103
* Like getpass(3) but with timeout and echo flags.
104
*/
105
char *
106
tgetpass(const char *prompt, int timeout, unsigned int flags,
107
struct sudo_conv_callback *callback)
108
{
109
struct sigaction sa, savealrm, saveint, savehup, savequit, saveterm;
110
struct sigaction savetstp, savettin, savettou;
111
char *pass;
112
static const char *askpass;
113
static char buf[SUDO_CONV_REPL_MAX + 1];
114
int i, input, output, save_errno, ttyfd;
115
bool feedback, need_restart, neednl;
116
enum tgetpass_errval errval;
117
debug_decl(tgetpass, SUDO_DEBUG_CONV);
118
119
(void) fflush(stdout);
120
121
if (askpass == NULL) {
122
askpass = getenv_unhooked("SUDO_ASKPASS");
123
if (askpass == NULL || *askpass == '\0')
124
askpass = sudo_conf_askpass_path();
125
}
126
127
restart:
128
/* Try to open /dev/tty if we are going to be using it for I/O. */
129
ttyfd = -1;
130
if (!ISSET(flags, TGP_STDIN|TGP_ASKPASS)) {
131
/* If no tty present and we need to disable echo, try askpass. */
132
ttyfd = open(_PATH_TTY, O_RDWR);
133
if (ttyfd == -1 && !ISSET(flags, TGP_ECHO|TGP_NOECHO_TRY)) {
134
if (askpass == NULL || getenv_unhooked("DISPLAY") == NULL) {
135
if (getenv_unhooked("SSH_CONNECTION") != NULL && getenv_unhooked("SSH_TTY") == NULL) {
136
sudo_warnx("%s",
137
U_("a terminal is required to read the password; either use ssh's -t option or configure an askpass helper"));
138
} else {
139
sudo_warnx("%s",
140
U_("a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper"));
141
}
142
debug_return_str(NULL);
143
}
144
SET(flags, TGP_ASKPASS);
145
}
146
}
147
148
/* If using a helper program to get the password, run it instead. */
149
if (ISSET(flags, TGP_ASKPASS)) {
150
if (askpass == NULL || *askpass == '\0')
151
sudo_fatalx("%s",
152
U_("no askpass program specified, try setting SUDO_ASKPASS"));
153
debug_return_str_masked(sudo_askpass(askpass, prompt));
154
}
155
156
/* Reset state. */
157
for (i = 0; i < NSIG; i++)
158
signo[i] = 0;
159
pass = NULL;
160
save_errno = 0;
161
neednl = false;
162
need_restart = false;
163
feedback = false;
164
165
/* Use tty for reading/writing if available else use stdin/stderr. */
166
if (ttyfd == -1) {
167
input = STDIN_FILENO;
168
output = STDERR_FILENO;
169
/* Don't try to mask password if /dev/tty is not available. */
170
CLR(flags, TGP_MASK);
171
} else {
172
input = ttyfd;
173
output = ttyfd;
174
}
175
176
/*
177
* If we are using a tty but are not the foreground pgrp this will
178
* return EINTR. We send ourself SIGTTOU bracketed by callbacks.
179
*/
180
if (!ISSET(flags, TGP_ECHO)) {
181
for (;;) {
182
if (ISSET(flags, TGP_MASK))
183
neednl = feedback = sudo_term_cbreak(input, true);
184
else
185
neednl = sudo_term_noecho(input);
186
if (neednl || errno != EINTR)
187
break;
188
/* Received SIGTTOU, suspend the process. */
189
if (suspend(SIGTTOU, callback) == -1) {
190
if (ttyfd != -1)
191
(void) close(ttyfd);
192
debug_return_ptr(NULL);
193
}
194
}
195
}
196
197
/*
198
* Catch signals that would otherwise cause the user to end
199
* up with echo turned off in the shell.
200
*/
201
memset(&sa, 0, sizeof(sa));
202
sigemptyset(&sa.sa_mask);
203
sa.sa_flags = 0; /* don't restart system calls */
204
sa.sa_handler = tgetpass_handler;
205
(void) sigaction(SIGALRM, &sa, &savealrm);
206
(void) sigaction(SIGINT, &sa, &saveint);
207
(void) sigaction(SIGHUP, &sa, &savehup);
208
(void) sigaction(SIGQUIT, &sa, &savequit);
209
(void) sigaction(SIGTERM, &sa, &saveterm);
210
(void) sigaction(SIGTSTP, &sa, &savetstp);
211
(void) sigaction(SIGTTIN, &sa, &savettin);
212
(void) sigaction(SIGTTOU, &sa, &savettou);
213
214
if (ISSET(flags, TGP_BELL) && output != STDERR_FILENO) {
215
/* Ring the bell if requested and there is a tty. */
216
if (write(output, "\a", 1) != 1)
217
goto restore;
218
}
219
if (prompt) {
220
if (write(output, prompt, strlen(prompt)) < 0)
221
goto restore;
222
}
223
224
if (timeout > 0)
225
alarm((unsigned int)timeout);
226
pass = getln(input, buf, sizeof(buf), feedback, &errval);
227
alarm(0);
228
save_errno = errno;
229
230
if (neednl || pass == NULL) {
231
if (write(output, "\n", 1) != 1)
232
goto restore;
233
}
234
tgetpass_display_error(errval);
235
236
restore:
237
/* Restore old signal handlers. */
238
(void) sigaction(SIGALRM, &savealrm, NULL);
239
(void) sigaction(SIGINT, &saveint, NULL);
240
(void) sigaction(SIGHUP, &savehup, NULL);
241
(void) sigaction(SIGQUIT, &savequit, NULL);
242
(void) sigaction(SIGTERM, &saveterm, NULL);
243
(void) sigaction(SIGTSTP, &savetstp, NULL);
244
(void) sigaction(SIGTTIN, &savettin, NULL);
245
(void) sigaction(SIGTTOU, &savettou, NULL);
246
247
/* Restore old tty settings. */
248
if (!ISSET(flags, TGP_ECHO)) {
249
/* Restore old tty settings if possible. */
250
if (!sudo_term_restore(input, true))
251
sudo_warn("%s", U_("unable to restore terminal settings"));
252
}
253
if (ttyfd != -1)
254
(void) close(ttyfd);
255
256
/*
257
* If we were interrupted by a signal, resend it to ourselves
258
* now that we have restored the signal handlers.
259
*/
260
for (i = 0; i < NSIG; i++) {
261
if (signo[i]) {
262
switch (i) {
263
case SIGALRM:
264
break;
265
case SIGTSTP:
266
case SIGTTIN:
267
case SIGTTOU:
268
if (suspend(i, callback) == 0)
269
need_restart = true;
270
break;
271
default:
272
kill(getpid(), i);
273
break;
274
}
275
}
276
}
277
if (need_restart)
278
goto restart;
279
280
if (save_errno)
281
errno = save_errno;
282
283
debug_return_str_masked(pass);
284
}
285
286
/*
287
* Fork a child and exec sudo-askpass to get the password from the user.
288
*/
289
static char *
290
sudo_askpass(const char *askpass, const char *prompt)
291
{
292
static char buf[SUDO_CONV_REPL_MAX + 1], *pass;
293
const struct sudo_cred *cred = sudo_askpass_cred(NULL);
294
sigset_t chldmask;
295
enum tgetpass_errval errval;
296
int pfd[2], status;
297
pid_t child;
298
debug_decl(sudo_askpass, SUDO_DEBUG_CONV);
299
300
/* Block SIGCHLD for the duration since we call waitpid() below. */
301
sigemptyset(&chldmask);
302
sigaddset(&chldmask, SIGCHLD);
303
(void)sigprocmask(SIG_BLOCK, &chldmask, NULL);
304
305
if (pipe2(pfd, O_CLOEXEC) == -1)
306
sudo_fatal("%s", U_("unable to create pipe"));
307
308
child = sudo_debug_fork();
309
if (child == -1)
310
sudo_fatal("%s", U_("unable to fork"));
311
312
if (child == 0) {
313
/* child, set stdout to write side of the pipe */
314
if (dup3(pfd[1], STDOUT_FILENO, 0) == -1) {
315
sudo_warn("dup3");
316
_exit(255);
317
}
318
if (setuid(ROOT_UID) == -1)
319
sudo_warn("setuid(%d)", ROOT_UID);
320
/* Close fds before uid change to prevent prlimit sabotage on Linux. */
321
closefrom(STDERR_FILENO + 1);
322
/* Run the askpass program with the user's original resource limits. */
323
restore_limits();
324
/* But avoid a setuid() failure on Linux due to RLIMIT_NPROC. */
325
unlimit_nproc();
326
if (setgid(cred->gid)) {
327
sudo_warn(U_("unable to set gid to %u"), (unsigned int)cred->gid);
328
_exit(255);
329
}
330
if (cred->ngroups != -1) {
331
if (sudo_setgroups(cred->ngroups, cred->groups) == -1) {
332
sudo_warn("%s", U_("unable to set supplementary group IDs"));
333
_exit(255);
334
}
335
}
336
if (setuid(cred->uid)) {
337
sudo_warn(U_("unable to set uid to %u"), (unsigned int)cred->uid);
338
_exit(255);
339
}
340
restore_nproc();
341
execl(askpass, askpass, prompt, (char *)NULL);
342
sudo_warn(U_("unable to run %s"), askpass);
343
_exit(255);
344
}
345
346
/* Get response from child (askpass). */
347
(void) close(pfd[1]);
348
pass = getln(pfd[0], buf, sizeof(buf), 0, &errval);
349
(void) close(pfd[0]);
350
351
tgetpass_display_error(errval);
352
353
/* Wait for child to exit. */
354
for (;;) {
355
pid_t rv = waitpid(child, &status, 0);
356
if (rv == -1 && errno != EINTR)
357
break;
358
if (rv != -1 && !WIFSTOPPED(status))
359
break;
360
}
361
362
if (pass == NULL)
363
errno = EINTR; /* make cancel button simulate ^C */
364
365
/* Unblock SIGCHLD. */
366
(void)sigprocmask(SIG_UNBLOCK, &chldmask, NULL);
367
368
debug_return_str_masked(pass);
369
}
370
371
extern int sudo_term_eof, sudo_term_erase, sudo_term_kill;
372
373
static char *
374
getln(int fd, char *buf, size_t bufsiz, bool feedback,
375
enum tgetpass_errval *errval)
376
{
377
ssize_t nr = -1;
378
const char *ep;
379
char *cp = buf;
380
char c = '\0';
381
debug_decl(getln, SUDO_DEBUG_CONV);
382
383
*errval = TGP_ERRVAL_NOERROR;
384
385
if (bufsiz == 0) {
386
*errval = TGP_ERRVAL_READERROR;
387
errno = EINVAL;
388
debug_return_str(NULL);
389
}
390
ep = buf + bufsiz - 1; /* reserve space for NUL byte */
391
392
while (cp < ep) {
393
nr = read(fd, &c, 1);
394
if (nr != 1 || c == '\n' || c == '\r')
395
break;
396
if (feedback) {
397
if (c == sudo_term_eof) {
398
nr = 0;
399
break;
400
} else if (c == sudo_term_kill) {
401
while (cp > buf) {
402
if (write(fd, "\b \b", 3) != 3)
403
break;
404
cp--;
405
}
406
cp = buf;
407
continue;
408
} else if (c == sudo_term_erase) {
409
if (cp > buf) {
410
ignore_result(write(fd, "\b \b", 3));
411
cp--;
412
}
413
continue;
414
}
415
ignore_result(write(fd, "*", 1));
416
}
417
*cp++ = c;
418
}
419
*cp = '\0';
420
if (feedback) {
421
/* erase stars */
422
while (cp > buf) {
423
if (write(fd, "\b \b", 3) != 3)
424
break;
425
cp--;
426
}
427
}
428
429
switch (nr) {
430
case -1:
431
/* Read error */
432
if (errno == EINTR) {
433
if (signo[SIGALRM] == 1)
434
*errval = TGP_ERRVAL_TIMEOUT;
435
} else {
436
*errval = TGP_ERRVAL_READERROR;
437
}
438
debug_return_str(NULL);
439
case 0:
440
/* EOF is only an error if no bytes were read. */
441
if (buf[0] == '\0') {
442
*errval = TGP_ERRVAL_NOPASSWORD;
443
debug_return_str(NULL);
444
}
445
FALLTHROUGH;
446
default:
447
debug_return_str_masked(buf);
448
}
449
}
450
451
static void
452
tgetpass_handler(int s)
453
{
454
signo[s] = 1;
455
}
456
457
const struct sudo_cred *
458
sudo_askpass_cred(const struct sudo_cred *cred)
459
{
460
static const struct sudo_cred *saved_cred;
461
462
if (cred != NULL)
463
saved_cred = cred;
464
return saved_cred;
465
}
466
467