Merge commit 'upstream/1.7.4'
[debian/sudo] / tgetpass.c
1 /*
2  * Copyright (c) 1996, 1998-2005, 2007-2010
3  *      Todd C. Miller <Todd.Miller@courtesan.com>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  *
17  * Sponsored in part by the Defense Advanced Research Projects
18  * Agency (DARPA) and Air Force Research Laboratory, Air Force
19  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
20  */
21
22 #ifdef __TANDEM
23 # include <floss.h>
24 #endif
25
26 #include <config.h>
27
28 #include <sys/types.h>
29 #include <sys/param.h>
30 #include <stdio.h>
31 #ifdef STDC_HEADERS
32 # include <stdlib.h>
33 # include <stddef.h>
34 #else
35 # ifdef HAVE_STDLIB_H
36 #  include <stdlib.h>
37 # endif
38 #endif /* STDC_HEADERS */
39 #ifdef HAVE_STRING_H
40 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
41 #  include <memory.h>
42 # endif
43 # include <string.h>
44 #endif /* HAVE_STRING_H */
45 #ifdef HAVE_STRINGS_H
46 # include <strings.h>
47 #endif /* HAVE_STRINGS_H */
48 #ifdef HAVE_UNISTD_H
49 # include <unistd.h>
50 #endif /* HAVE_UNISTD_H */
51 #include <pwd.h>
52 #include <errno.h>
53 #include <signal.h>
54 #include <fcntl.h>
55
56 #include "sudo.h"
57
58 static volatile sig_atomic_t signo[NSIG];
59
60 static void handler __P((int));
61 static char *getln __P((int, char *, size_t, int));
62 static char *sudo_askpass __P((const char *));
63
64 /*
65  * Like getpass(3) but with timeout and echo flags.
66  */
67 char *
68 tgetpass(prompt, timeout, flags)
69     const char *prompt;
70     int timeout;
71     int flags;
72 {
73     sigaction_t sa, savealrm, saveint, savehup, savequit, saveterm;
74     sigaction_t savetstp, savettin, savettou, savepipe;
75     char *pass;
76     static char buf[SUDO_PASS_MAX + 1];
77     int i, input, output, save_errno, neednl = 0, need_restart;
78
79     (void) fflush(stdout);
80
81     /* If using a helper program to get the password, run it instead. */
82     if (ISSET(flags, TGP_ASKPASS) && user_askpass)
83         return(sudo_askpass(prompt));
84
85 restart:
86     for (i = 0; i < NSIG; i++)
87         signo[i] = 0;
88     pass = NULL;
89     save_errno = 0;
90     need_restart = 0;
91     /* Open /dev/tty for reading/writing if possible else use stdin/stderr. */
92     if (ISSET(flags, TGP_STDIN) ||
93         (input = output = open(_PATH_TTY, O_RDWR|O_NOCTTY)) == -1) {
94         input = STDIN_FILENO;
95         output = STDERR_FILENO;
96     }
97
98     /*
99      * If we are using a tty but are not the foreground pgrp this will
100      * generate SIGTTOU, so do it *before* installing the signal handlers.
101      */
102     if (!ISSET(flags, TGP_ECHO)) {
103         if (def_pwfeedback)
104             neednl = term_cbreak(input);
105         else
106             neednl = term_noecho(input);
107     }
108
109     /*
110      * Catch signals that would otherwise cause the user to end
111      * up with echo turned off in the shell.
112      */
113     zero_bytes(&sa, sizeof(sa));
114     sigemptyset(&sa.sa_mask);
115     sa.sa_flags = SA_INTERRUPT; /* don't restart system calls */
116     sa.sa_handler = handler;
117     (void) sigaction(SIGALRM, &sa, &savealrm);
118     (void) sigaction(SIGINT, &sa, &saveint);
119     (void) sigaction(SIGHUP, &sa, &savehup);
120     (void) sigaction(SIGQUIT, &sa, &savequit);
121     (void) sigaction(SIGTERM, &sa, &saveterm);
122     (void) sigaction(SIGTSTP, &sa, &savetstp);
123     (void) sigaction(SIGTTIN, &sa, &savettin);
124     (void) sigaction(SIGTTOU, &sa, &savettou);
125
126     /* Ignore SIGPIPE in case stdin is a pipe and TGP_STDIN is set */
127     sa.sa_handler = SIG_IGN;
128     (void) sigaction(SIGPIPE, &sa, &savepipe);
129
130     if (prompt)
131         (void) write(output, prompt, strlen(prompt));
132
133     if (timeout > 0)
134         alarm(timeout);
135     pass = getln(input, buf, sizeof(buf), def_pwfeedback);
136     alarm(0);
137     save_errno = errno;
138
139     if (neednl || pass == NULL)
140         (void) write(output, "\n", 1);
141
142     /* Restore old tty settings and signals. */
143     if (!ISSET(flags, TGP_ECHO))
144         term_restore(input, 1);
145     (void) sigaction(SIGALRM, &savealrm, NULL);
146     (void) sigaction(SIGINT, &saveint, NULL);
147     (void) sigaction(SIGHUP, &savehup, NULL);
148     (void) sigaction(SIGQUIT, &savequit, NULL);
149     (void) sigaction(SIGTERM, &saveterm, NULL);
150     (void) sigaction(SIGTSTP, &savetstp, NULL);
151     (void) sigaction(SIGTTIN, &savettin, NULL);
152     (void) sigaction(SIGTTOU, &savettou, NULL);
153     (void) sigaction(SIGTTOU, &savepipe, NULL);
154     if (input != STDIN_FILENO)
155         (void) close(input);
156
157     /*
158      * If we were interrupted by a signal, resend it to ourselves
159      * now that we have restored the signal handlers.
160      */
161     for (i = 0; i < NSIG; i++) {
162         if (signo[i]) {
163             kill(getpid(), i);
164             switch (i) {
165                 case SIGTSTP:
166                 case SIGTTIN:
167                 case SIGTTOU:
168                     need_restart = 1;
169                     break;
170             }
171         }
172     }
173     if (need_restart)
174         goto restart;
175
176     if (save_errno)
177         errno = save_errno;
178     return(pass);
179 }
180
181 /*
182  * Fork a child and exec sudo-askpass to get the password from the user.
183  */
184 static char *
185 sudo_askpass(prompt)
186     const char *prompt;
187 {
188     static char buf[SUDO_PASS_MAX + 1], *pass;
189     sigaction_t sa, saved_sa_pipe;
190     int pfd[2];
191     pid_t pid;
192
193     if (pipe(pfd) == -1)
194         error(1, "unable to create pipe");
195
196     if ((pid = fork()) == -1)
197         error(1, "unable to fork");
198
199     if (pid == 0) {
200         /* child, point stdout to output side of the pipe and exec askpass */
201         if (dup2(pfd[1], STDOUT_FILENO) == -1) {
202             warning("dup2");
203             _exit(255);
204         }
205         (void) dup2(pfd[1], STDOUT_FILENO);
206         set_perms(PERM_FULL_USER);
207         closefrom(STDERR_FILENO + 1);
208         execl(user_askpass, user_askpass, prompt, (char *)NULL);
209         warning("unable to run %s", user_askpass);
210         _exit(255);
211     }
212
213     /* Ignore SIGPIPE in case child exits prematurely */
214     zero_bytes(&sa, sizeof(sa));
215     sigemptyset(&sa.sa_mask);
216     sa.sa_flags = SA_INTERRUPT;
217     sa.sa_handler = SIG_IGN;
218     (void) sigaction(SIGPIPE, &sa, &saved_sa_pipe);
219
220     /* Get response from child (askpass) and restore SIGPIPE handler */
221     (void) close(pfd[1]);
222     pass = getln(pfd[0], buf, sizeof(buf), 0);
223     (void) close(pfd[0]);
224     (void) sigaction(SIGPIPE, &saved_sa_pipe, NULL);
225
226     return(pass);
227 }
228
229 extern int term_erase, term_kill;
230
231 static char *
232 getln(fd, buf, bufsiz, feedback)
233     int fd;
234     char *buf;
235     size_t bufsiz;
236     int feedback;
237 {
238     size_t left = bufsiz;
239     ssize_t nr = -1;
240     char *cp = buf;
241     char c = '\0';
242
243     if (left == 0) {
244         errno = EINVAL;
245         return(NULL);                   /* sanity */
246     }
247
248     while (--left) {
249         nr = read(fd, &c, 1);
250         if (nr != 1 || c == '\n' || c == '\r')
251             break;
252         if (feedback) {
253             if (c == term_kill) {
254                 while (cp > buf) {
255                     (void) write(fd, "\b \b", 3);
256                     --cp;
257                 }
258                 left = bufsiz;
259                 continue;
260             } else if (c == term_erase) {
261                 if (cp > buf) {
262                     (void) write(fd, "\b \b", 3);
263                     --cp;
264                     left++;
265                 }
266                 continue;
267             }
268             (void) write(fd, "*", 1);
269         }
270         *cp++ = c;
271     }
272     *cp = '\0';
273     if (feedback) {
274         /* erase stars */
275         while (cp > buf) {
276             (void) write(fd, "\b \b", 3);
277             --cp;
278         }
279     }
280
281     return(nr == 1 ? buf : NULL);
282 }
283
284 static void
285 handler(s)
286     int s;
287 {
288     if (s != SIGALRM)
289         signo[s] = 1;
290 }
291
292 int
293 tty_present()
294 {
295     int fd;
296
297     if ((fd = open(_PATH_TTY, O_RDWR|O_NOCTTY)) != -1)
298         close(fd);
299     return(fd != -1);
300 }