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
59int openpam_ttyconv_timeout = 0;
60
61static volatile 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 = -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 */
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 = -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 */
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 */
402