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