]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/openpam/lib/libpam/openpam_ttyconv.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.git] / contrib / openpam / lib / libpam / openpam_ttyconv.c
1 /*-
2  * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
3  * Copyright (c) 2004-2011 Dag-Erling Smørgrav
4  * All rights reserved.
5  *
6  * This software was developed for the FreeBSD Project by ThinkSec AS and
7  * Network Associates Laboratories, the Security Research Division of
8  * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
9  * ("CBOSS"), as part of the DARPA CHATS research program.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. The name of the author may not be used to endorse or promote
20  *    products derived from this software without specific prior written
21  *    permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  * $Id: openpam_ttyconv.c 688 2013-07-11 16:40:08Z des $
36  */
37
38 #ifdef HAVE_CONFIG_H
39 # include "config.h"
40 #endif
41
42 #include <sys/types.h>
43 #include <sys/poll.h>
44 #include <sys/time.h>
45
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <signal.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <termios.h>
53 #include <unistd.h>
54
55 #include <security/pam_appl.h>
56
57 #include "openpam_impl.h"
58
59 int openpam_ttyconv_timeout = 0;
60
61 static volatile sig_atomic_t caught_signal;
62
63 /*
64  * Handle incoming signals during tty conversation
65  */
66 static void
67 catch_signal(int signo)
68 {
69
70         switch (signo) {
71         case SIGINT:
72         case SIGQUIT:
73         case SIGTERM:
74                 caught_signal = signo;
75                 break;
76         }
77 }
78
79 /*
80  * Accept a response from the user on a tty
81  */
82 static int
83 prompt_tty(int ifd, int ofd, const char *message, char *response, int echo)
84 {
85         struct sigaction action;
86         struct sigaction saction_sigint, saction_sigquit, saction_sigterm;
87         struct termios tcattr;
88         struct timeval now, target, remaining;
89         int remaining_ms;
90         tcflag_t slflag;
91         struct pollfd pfd;
92         int serrno;
93         int pos, ret;
94         char ch;
95
96         /* write prompt */
97         if (write(ofd, message, strlen(message)) < 0) {
98                 openpam_log(PAM_LOG_ERROR, "write(): %m");
99                 return (-1);
100         }
101
102         /* turn echo off if requested */
103         slflag = 0; /* prevent bogus uninitialized variable warning */
104         if (!echo) {
105                 if (tcgetattr(ifd, &tcattr) != 0) {
106                         openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m");
107                         return (-1);
108                 }
109                 slflag = tcattr.c_lflag;
110                 tcattr.c_lflag &= ~ECHO;
111                 if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) {
112                         openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m");
113                         return (-1);
114                 }
115         }
116
117         /* install signal handlers */
118         caught_signal = 0;
119         action.sa_handler = &catch_signal;
120         action.sa_flags = 0;
121         sigfillset(&action.sa_mask);
122         sigaction(SIGINT, &action, &saction_sigint);
123         sigaction(SIGQUIT, &action, &saction_sigquit);
124         sigaction(SIGTERM, &action, &saction_sigterm);
125
126         /* compute timeout */
127         if (openpam_ttyconv_timeout > 0) {
128                 (void)gettimeofday(&now, NULL);
129                 remaining.tv_sec = openpam_ttyconv_timeout;
130                 remaining.tv_usec = 0;
131                 timeradd(&now, &remaining, &target);
132         } else {
133                 /* prevent bogus uninitialized variable warning */
134                 now.tv_sec = now.tv_usec = 0;
135                 remaining.tv_sec = remaining.tv_usec = 0;
136                 target.tv_sec = target.tv_usec = 0;
137         }
138
139         /* input loop */
140         pos = 0;
141         ret = -1;
142         serrno = 0;
143         while (!caught_signal) {
144                 pfd.fd = ifd;
145                 pfd.events = POLLIN;
146                 pfd.revents = 0;
147                 if (openpam_ttyconv_timeout > 0) {
148                         gettimeofday(&now, NULL);
149                         if (timercmp(&now, &target, >))
150                                 break;
151                         timersub(&target, &now, &remaining);
152                         remaining_ms = remaining.tv_sec * 1000 +
153                             remaining.tv_usec / 1000;
154                 } else {
155                         remaining_ms = -1;
156                 }
157                 if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
158                         serrno = errno;
159                         if (errno == EINTR)
160                                 continue;
161                         openpam_log(PAM_LOG_ERROR, "poll(): %m");
162                         break;
163                 } else if (ret == 0) {
164                         /* timeout */
165                         write(ofd, " timed out", 10);
166                         openpam_log(PAM_LOG_NOTICE, "timed out");
167                         break;
168                 }
169                 if ((ret = read(ifd, &ch, 1)) < 0) {
170                         serrno = errno;
171                         openpam_log(PAM_LOG_ERROR, "read(): %m");
172                         break;
173                 } else if (ret == 0 || ch == '\n') {
174                         response[pos] = '\0';
175                         ret = pos;
176                         break;
177                 }
178                 if (pos + 1 < PAM_MAX_RESP_SIZE)
179                         response[pos++] = ch;
180                 /* overflow is discarded */
181         }
182
183         /* restore tty state */
184         if (!echo) {
185                 tcattr.c_lflag = slflag;
186                 if (tcsetattr(ifd, 0, &tcattr) != 0) {
187                         /* treat as non-fatal, since we have our answer */
188                         openpam_log(PAM_LOG_NOTICE, "tcsetattr(): %m");
189                 }
190         }
191
192         /* restore signal handlers and re-post caught signal*/
193         sigaction(SIGINT, &saction_sigint, NULL);
194         sigaction(SIGQUIT, &saction_sigquit, NULL);
195         sigaction(SIGTERM, &saction_sigterm, NULL);
196         if (caught_signal != 0) {
197                 openpam_log(PAM_LOG_ERROR, "caught signal %d",
198                     (int)caught_signal);
199                 raise((int)caught_signal);
200                 /* if raise() had no effect... */
201                 serrno = EINTR;
202                 ret = -1;
203         }
204
205         /* done */
206         write(ofd, "\n", 1);
207         errno = serrno;
208         return (ret);
209 }
210
211 /*
212  * Accept a response from the user on a non-tty stdin.
213  */
214 static int
215 prompt_notty(const char *message, char *response)
216 {
217         struct timeval now, target, remaining;
218         int remaining_ms;
219         struct pollfd pfd;
220         int ch, pos, ret;
221
222         /* show prompt */
223         fputs(message, stdout);
224         fflush(stdout);
225
226         /* compute timeout */
227         if (openpam_ttyconv_timeout > 0) {
228                 (void)gettimeofday(&now, NULL);
229                 remaining.tv_sec = openpam_ttyconv_timeout;
230                 remaining.tv_usec = 0;
231                 timeradd(&now, &remaining, &target);
232         } else {
233                 /* prevent bogus uninitialized variable warning */
234                 now.tv_sec = now.tv_usec = 0;
235                 remaining.tv_sec = remaining.tv_usec = 0;
236                 target.tv_sec = target.tv_usec = 0;
237         }
238
239         /* input loop */
240         pos = 0;
241         for (;;) {
242                 pfd.fd = STDIN_FILENO;
243                 pfd.events = POLLIN;
244                 pfd.revents = 0;
245                 if (openpam_ttyconv_timeout > 0) {
246                         gettimeofday(&now, NULL);
247                         if (timercmp(&now, &target, >))
248                                 break;
249                         timersub(&target, &now, &remaining);
250                         remaining_ms = remaining.tv_sec * 1000 +
251                             remaining.tv_usec / 1000;
252                 } else {
253                         remaining_ms = -1;
254                 }
255                 if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
256                         /* interrupt is ok, everything else -> bail */
257                         if (errno == EINTR)
258                                 continue;
259                         perror("\nopenpam_ttyconv");
260                         return (-1);
261                 } else if (ret == 0) {
262                         /* timeout */
263                         break;
264                 } else {
265                         /* input */
266                         if ((ch = getchar()) == EOF && ferror(stdin)) {
267                                 perror("\nopenpam_ttyconv");
268                                 return (-1);
269                         }
270                         if (ch == EOF || ch == '\n') {
271                                 response[pos] = '\0';
272                                 return (pos);
273                         }
274                         if (pos + 1 < PAM_MAX_RESP_SIZE)
275                                 response[pos++] = ch;
276                         /* overflow is discarded */
277                 }
278         }
279         fputs("\nopenpam_ttyconv: timeout\n", stderr);
280         return (-1);
281 }
282
283 /*
284  * Determine whether stdin is a tty; if not, try to open the tty; in
285  * either case, call the appropriate method.
286  */
287 static int
288 prompt(const char *message, char *response, int echo)
289 {
290         int ifd, ofd, ret;
291
292         if (isatty(STDIN_FILENO)) {
293                 fflush(stdout);
294 #ifdef HAVE_FPURGE
295                 fpurge(stdin);
296 #endif
297                 ifd = STDIN_FILENO;
298                 ofd = STDOUT_FILENO;
299         } else {
300                 if ((ifd = open("/dev/tty", O_RDWR)) < 0)
301                         /* no way to prevent echo */
302                         return (prompt_notty(message, response));
303                 ofd = ifd;
304         }
305         ret = prompt_tty(ifd, ofd, message, response, echo);
306         if (ifd != STDIN_FILENO)
307                 close(ifd);
308         return (ret);
309 }
310
311 /*
312  * OpenPAM extension
313  *
314  * Simple tty-based conversation function
315  */
316
317 int
318 openpam_ttyconv(int n,
319          const struct pam_message **msg,
320          struct pam_response **resp,
321          void *data)
322 {
323         char respbuf[PAM_MAX_RESP_SIZE];
324         struct pam_response *aresp;
325         int i;
326
327         ENTER();
328         (void)data;
329         if (n <= 0 || n > PAM_MAX_NUM_MSG)
330                 RETURNC(PAM_CONV_ERR);
331         if ((aresp = calloc(n, sizeof *aresp)) == NULL)
332                 RETURNC(PAM_BUF_ERR);
333         for (i = 0; i < n; ++i) {
334                 aresp[i].resp_retcode = 0;
335                 aresp[i].resp = NULL;
336                 switch (msg[i]->msg_style) {
337                 case PAM_PROMPT_ECHO_OFF:
338                         if (prompt(msg[i]->msg, respbuf, 0) < 0 ||
339                             (aresp[i].resp = strdup(respbuf)) == NULL)
340                                 goto fail;
341                         break;
342                 case PAM_PROMPT_ECHO_ON:
343                         if (prompt(msg[i]->msg, respbuf, 1) < 0 ||
344                             (aresp[i].resp = strdup(respbuf)) == NULL)
345                                 goto fail;
346                         break;
347                 case PAM_ERROR_MSG:
348                         fputs(msg[i]->msg, stderr);
349                         if (strlen(msg[i]->msg) > 0 &&
350                             msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
351                                 fputc('\n', stderr);
352                         break;
353                 case PAM_TEXT_INFO:
354                         fputs(msg[i]->msg, stdout);
355                         if (strlen(msg[i]->msg) > 0 &&
356                             msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
357                                 fputc('\n', stdout);
358                         break;
359                 default:
360                         goto fail;
361                 }
362         }
363         *resp = aresp;
364         memset(respbuf, 0, sizeof respbuf);
365         RETURNC(PAM_SUCCESS);
366 fail:
367         for (i = 0; i < n; ++i) {
368                 if (aresp[i].resp != NULL) {
369                         memset(aresp[i].resp, 0, strlen(aresp[i].resp));
370                         FREE(aresp[i].resp);
371                 }
372         }
373         memset(aresp, 0, n * sizeof *aresp);
374         FREE(aresp);
375         *resp = NULL;
376         memset(respbuf, 0, sizeof respbuf);
377         RETURNC(PAM_CONV_ERR);
378 }
379
380 /*
381  * Error codes:
382  *
383  *      PAM_SYSTEM_ERR
384  *      PAM_BUF_ERR
385  *      PAM_CONV_ERR
386  */
387
388 /**
389  * The =openpam_ttyconv function is a standard conversation function
390  * suitable for use on TTY devices.
391  * It should be adequate for the needs of most text-based interactive
392  * programs.
393  *
394  * The =openpam_ttyconv function allows the application to specify a
395  * timeout for user input by setting the global integer variable
396  * :openpam_ttyconv_timeout to the length of the timeout in seconds.
397  *
398  * >openpam_nullconv
399  * >pam_prompt
400  * >pam_vprompt
401  */