source: openpam/trunk/lib/libpam/openpam_ttyconv.c @ 687

Last change on this file since 687 was 687, checked in by Dag-Erling Smørgrav, 7 years ago

Reimplement, hopefully with marginally fewer bugs. There is an
unfortunate amount of code duplication between the tty and non-tty
paths, but the alternative is greatly increased complexity.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 9.9 KB
Line 
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 687 2013-07-11 16:37:25Z 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
59int openpam_ttyconv_timeout = 0;
60
61volatile sig_atomic_t caught_signal;
62
63/*
64 * Handle incoming signals during tty conversation
65 */
66static void
67catch_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 */
82static int
83prompt_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 = INFTIM;
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 */
214static int
215prompt_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 = INFTIM;
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 */
287static int
288prompt(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
317int
318openpam_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);
366fail:
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 */
Note: See TracBrowser for help on using the repository browser.