xref: /dragonfly/contrib/openpam/lib/libpam/openpam_ttyconv.c (revision 7031abe4d1ef8c309d4113438494530b74f3f3fe)
1 /*-
2  * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
3  * Copyright (c) 2004-2014 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  * $OpenPAM: openpam_ttyconv.c 938 2017-04-30 21:34:42Z 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 #include "openpam_strlset.h"
59 
60 int openpam_ttyconv_timeout = 0;
61 
62 static volatile sig_atomic_t caught_signal;
63 
64 /*
65  * Handle incoming signals during tty conversation
66  */
67 static void
catch_signal(int signo)68 catch_signal(int signo)
69 {
70 
71           switch (signo) {
72           case SIGINT:
73           case SIGQUIT:
74           case SIGTERM:
75                     caught_signal = signo;
76                     break;
77           }
78 }
79 
80 /*
81  * Accept a response from the user on a tty
82  */
83 static int
prompt_tty(int ifd,int ofd,const char * message,char * response,int echo)84 prompt_tty(int ifd, int ofd, const char *message, char *response, int echo)
85 {
86           struct sigaction action;
87           struct sigaction saction_sigint, saction_sigquit, saction_sigterm;
88           struct termios tcattr;
89           struct timeval now, target, remaining;
90           int remaining_ms;
91           tcflag_t slflag;
92           struct pollfd pfd;
93           int serrno;
94           int pos, ret;
95           char ch;
96 
97           /* write prompt */
98           if (write(ofd, message, strlen(message)) < 0) {
99                     openpam_log(PAM_LOG_ERROR, "write(): %m");
100                     return (-1);
101           }
102 
103           /* turn echo off if requested */
104           slflag = 0; /* prevent bogus uninitialized variable warning */
105           if (!echo) {
106                     if (tcgetattr(ifd, &tcattr) != 0) {
107                               openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m");
108                               return (-1);
109                     }
110                     slflag = tcattr.c_lflag;
111                     tcattr.c_lflag &= ~ECHO;
112                     if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) {
113                               openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m");
114                               return (-1);
115                     }
116           }
117 
118           /* install signal handlers */
119           caught_signal = 0;
120           action.sa_handler = &catch_signal;
121           action.sa_flags = 0;
122           sigfillset(&action.sa_mask);
123           sigaction(SIGINT, &action, &saction_sigint);
124           sigaction(SIGQUIT, &action, &saction_sigquit);
125           sigaction(SIGTERM, &action, &saction_sigterm);
126 
127           /* compute timeout */
128           if (openpam_ttyconv_timeout > 0) {
129                     (void)gettimeofday(&now, NULL);
130                     remaining.tv_sec = openpam_ttyconv_timeout;
131                     remaining.tv_usec = 0;
132                     timeradd(&now, &remaining, &target);
133           } else {
134                     /* prevent bogus uninitialized variable warning */
135                     now.tv_sec = now.tv_usec = 0;
136                     remaining.tv_sec = remaining.tv_usec = 0;
137                     target.tv_sec = target.tv_usec = 0;
138           }
139 
140           /* input loop */
141           pos = 0;
142           ret = -1;
143           serrno = 0;
144           while (!caught_signal) {
145                     pfd.fd = ifd;
146                     pfd.events = POLLIN;
147                     pfd.revents = 0;
148                     if (openpam_ttyconv_timeout > 0) {
149                               gettimeofday(&now, NULL);
150                               if (timercmp(&now, &target, >))
151                                         break;
152                               timersub(&target, &now, &remaining);
153                               remaining_ms = remaining.tv_sec * 1000 +
154                                   remaining.tv_usec / 1000;
155                     } else {
156                               remaining_ms = -1;
157                     }
158                     if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
159                               serrno = errno;
160                               if (errno == EINTR)
161                                         continue;
162                               openpam_log(PAM_LOG_ERROR, "poll(): %m");
163                               break;
164                     } else if (ret == 0) {
165                               /* timeout */
166                               write(ofd, " timed out", 10);
167                               openpam_log(PAM_LOG_NOTICE, "timed out");
168                               break;
169                     }
170                     if ((ret = read(ifd, &ch, 1)) < 0) {
171                               serrno = errno;
172                               openpam_log(PAM_LOG_ERROR, "read(): %m");
173                               break;
174                     } else if (ret == 0 || ch == '\n') {
175                               response[pos] = '\0';
176                               ret = pos;
177                               break;
178                     }
179                     if (pos + 1 < PAM_MAX_RESP_SIZE)
180                               response[pos++] = ch;
181                     /* overflow is discarded */
182           }
183 
184           /* restore tty state */
185           if (!echo) {
186                     tcattr.c_lflag = slflag;
187                     if (tcsetattr(ifd, 0, &tcattr) != 0) {
188                               /* treat as non-fatal, since we have our answer */
189                               openpam_log(PAM_LOG_NOTICE, "tcsetattr(): %m");
190                     }
191           }
192 
193           /* restore signal handlers and re-post caught signal*/
194           sigaction(SIGINT, &saction_sigint, NULL);
195           sigaction(SIGQUIT, &saction_sigquit, NULL);
196           sigaction(SIGTERM, &saction_sigterm, NULL);
197           if (caught_signal != 0) {
198                     openpam_log(PAM_LOG_ERROR, "caught signal %d",
199                         (int)caught_signal);
200                     raise((int)caught_signal);
201                     /* if raise() had no effect... */
202                     serrno = EINTR;
203                     ret = -1;
204           }
205 
206           /* done */
207           write(ofd, "\n", 1);
208           errno = serrno;
209           return (ret);
210 }
211 
212 /*
213  * Accept a response from the user on a non-tty stdin.
214  */
215 static int
prompt_notty(const char * message,char * response)216 prompt_notty(const char *message, char *response)
217 {
218           struct timeval now, target, remaining;
219           int remaining_ms;
220           struct pollfd pfd;
221           int ch, pos, ret;
222 
223           /* show prompt */
224           fputs(message, stdout);
225           fflush(stdout);
226 
227           /* compute timeout */
228           if (openpam_ttyconv_timeout > 0) {
229                     (void)gettimeofday(&now, NULL);
230                     remaining.tv_sec = openpam_ttyconv_timeout;
231                     remaining.tv_usec = 0;
232                     timeradd(&now, &remaining, &target);
233           } else {
234                     /* prevent bogus uninitialized variable warning */
235                     now.tv_sec = now.tv_usec = 0;
236                     remaining.tv_sec = remaining.tv_usec = 0;
237                     target.tv_sec = target.tv_usec = 0;
238           }
239 
240           /* input loop */
241           pos = 0;
242           for (;;) {
243                     pfd.fd = STDIN_FILENO;
244                     pfd.events = POLLIN;
245                     pfd.revents = 0;
246                     if (openpam_ttyconv_timeout > 0) {
247                               gettimeofday(&now, NULL);
248                               if (timercmp(&now, &target, >))
249                                         break;
250                               timersub(&target, &now, &remaining);
251                               remaining_ms = remaining.tv_sec * 1000 +
252                                   remaining.tv_usec / 1000;
253                     } else {
254                               remaining_ms = -1;
255                     }
256                     if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
257                               /* interrupt is ok, everything else -> bail */
258                               if (errno == EINTR)
259                                         continue;
260                               perror("\nopenpam_ttyconv");
261                               return (-1);
262                     } else if (ret == 0) {
263                               /* timeout */
264                               break;
265                     } else {
266                               /* input */
267                               if ((ch = getchar()) == EOF && ferror(stdin)) {
268                                         perror("\nopenpam_ttyconv");
269                                         return (-1);
270                               }
271                               if (ch == EOF || ch == '\n') {
272                                         response[pos] = '\0';
273                                         return (pos);
274                               }
275                               if (pos + 1 < PAM_MAX_RESP_SIZE)
276                                         response[pos++] = ch;
277                               /* overflow is discarded */
278                     }
279           }
280           fputs("\nopenpam_ttyconv: timeout\n", stderr);
281           return (-1);
282 }
283 
284 /*
285  * Determine whether stdin is a tty; if not, try to open the tty; in
286  * either case, call the appropriate method.
287  */
288 static int
prompt(const char * message,char * response,int echo)289 prompt(const char *message, char *response, int echo)
290 {
291           int ifd, ofd, ret;
292 
293           if (isatty(STDIN_FILENO)) {
294                     fflush(stdout);
295 #ifdef HAVE_FPURGE
296                     fpurge(stdin);
297 #endif
298                     ifd = STDIN_FILENO;
299                     ofd = STDOUT_FILENO;
300           } else {
301                     if ((ifd = open("/dev/tty", O_RDWR)) < 0)
302                               /* no way to prevent echo */
303                               return (prompt_notty(message, response));
304                     ofd = ifd;
305           }
306           ret = prompt_tty(ifd, ofd, message, response, echo);
307           if (ifd != STDIN_FILENO)
308                     close(ifd);
309           return (ret);
310 }
311 
312 /*
313  * OpenPAM extension
314  *
315  * Simple tty-based conversation function
316  */
317 
318 int
openpam_ttyconv(int n,const struct pam_message ** msg,struct pam_response ** resp,void * data)319 openpam_ttyconv(int n,
320            const struct pam_message **msg,
321            struct pam_response **resp,
322            void *data)
323 {
324           char respbuf[PAM_MAX_RESP_SIZE];
325           struct pam_response *aresp;
326           int i;
327 
328           ENTER();
329           (void)data;
330           if (n <= 0 || n > PAM_MAX_NUM_MSG)
331                     RETURNC(PAM_CONV_ERR);
332           if ((aresp = calloc(n, sizeof *aresp)) == NULL)
333                     RETURNC(PAM_BUF_ERR);
334           for (i = 0; i < n; ++i) {
335                     aresp[i].resp_retcode = 0;
336                     aresp[i].resp = NULL;
337                     switch (msg[i]->msg_style) {
338                     case PAM_PROMPT_ECHO_OFF:
339                               if (prompt(msg[i]->msg, respbuf, 0) < 0 ||
340                                   (aresp[i].resp = strdup(respbuf)) == NULL)
341                                         goto fail;
342                               break;
343                     case PAM_PROMPT_ECHO_ON:
344                               if (prompt(msg[i]->msg, respbuf, 1) < 0 ||
345                                   (aresp[i].resp = strdup(respbuf)) == NULL)
346                                         goto fail;
347                               break;
348                     case PAM_ERROR_MSG:
349                               fputs(msg[i]->msg, stderr);
350                               if (strlen(msg[i]->msg) > 0 &&
351                                   msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
352                                         fputc('\n', stderr);
353                               break;
354                     case PAM_TEXT_INFO:
355                               fputs(msg[i]->msg, stdout);
356                               if (strlen(msg[i]->msg) > 0 &&
357                                   msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
358                                         fputc('\n', stdout);
359                               break;
360                     default:
361                               goto fail;
362                     }
363           }
364           *resp = aresp;
365           memset(respbuf, 0, sizeof respbuf);
366           RETURNC(PAM_SUCCESS);
367 fail:
368           for (i = 0; i < n; ++i) {
369                     if (aresp[i].resp != NULL) {
370                               strlset(aresp[i].resp, 0, PAM_MAX_RESP_SIZE);
371                               FREE(aresp[i].resp);
372                     }
373           }
374           memset(aresp, 0, n * sizeof *aresp);
375           FREE(aresp);
376           *resp = NULL;
377           memset(respbuf, 0, sizeof respbuf);
378           RETURNC(PAM_CONV_ERR);
379 }
380 
381 /*
382  * Error codes:
383  *
384  *        PAM_SYSTEM_ERR
385  *        PAM_BUF_ERR
386  *        PAM_CONV_ERR
387  */
388 
389 /**
390  * The =openpam_ttyconv function is a standard conversation function
391  * suitable for use on TTY devices.
392  * It should be adequate for the needs of most text-based interactive
393  * programs.
394  *
395  * The =openpam_ttyconv function allows the application to specify a
396  * timeout for user input by setting the global integer variable
397  * :openpam_ttyconv_timeout to the length of the timeout in seconds.
398  *
399  * >openpam_nullconv
400  * >pam_prompt
401  * >pam_vprompt
402  */
403