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
60int openpam_ttyconv_timeout = 0;
61
62static volatile sig_atomic_t caught_signal;
63
64/*
65 * Handle incoming signals during tty conversation
66 */
67static void
68catch_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 */
83static int
84prompt_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 */
215static int
216prompt_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 */
288static int
289prompt(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
318int
319openpam_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);
367fail:
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