191094Sdes/*-
2115619Sdes * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
3228690Sdes * Copyright (c) 2004-2011 Dag-Erling Sm��rgrav
491094Sdes * All rights reserved.
591094Sdes *
691094Sdes * This software was developed for the FreeBSD Project by ThinkSec AS and
799158Sdes * Network Associates Laboratories, the Security Research Division of
899158Sdes * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
999158Sdes * ("CBOSS"), as part of the DARPA CHATS research program.
1091094Sdes *
1191094Sdes * Redistribution and use in source and binary forms, with or without
1291094Sdes * modification, are permitted provided that the following conditions
1391094Sdes * are met:
1491094Sdes * 1. Redistributions of source code must retain the above copyright
1591094Sdes *    notice, this list of conditions and the following disclaimer.
1691094Sdes * 2. Redistributions in binary form must reproduce the above copyright
1791094Sdes *    notice, this list of conditions and the following disclaimer in the
1891094Sdes *    documentation and/or other materials provided with the distribution.
1991094Sdes * 3. The name of the author may not be used to endorse or promote
2091094Sdes *    products derived from this software without specific prior written
2191094Sdes *    permission.
2291094Sdes *
2391094Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2491094Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2591094Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2691094Sdes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2791094Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2891094Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2991094Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3091094Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3191094Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3291094Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3391094Sdes * SUCH DAMAGE.
3491094Sdes *
35271947Sdes * $Id: openpam_ttyconv.c 807 2014-09-09 09:41:32Z des $
3691094Sdes */
3791094Sdes
38228690Sdes#ifdef HAVE_CONFIG_H
39228690Sdes# include "config.h"
40228690Sdes#endif
41228690Sdes
4291094Sdes#include <sys/types.h>
43255376Sdes#include <sys/poll.h>
44255376Sdes#include <sys/time.h>
4591094Sdes
46117610Sdes#include <errno.h>
47255376Sdes#include <fcntl.h>
4891100Sdes#include <signal.h>
4991094Sdes#include <stdio.h>
5091094Sdes#include <stdlib.h>
5191094Sdes#include <string.h>
5291094Sdes#include <termios.h>
5391100Sdes#include <unistd.h>
5491094Sdes
5591094Sdes#include <security/pam_appl.h>
5691094Sdes
57107937Sdes#include "openpam_impl.h"
58271947Sdes#include "openpam_strlset.h"
59107937Sdes
6091100Sdesint openpam_ttyconv_timeout = 0;
6191100Sdes
62255376Sdesstatic volatile sig_atomic_t caught_signal;
63255376Sdes
64255376Sdes/*
65255376Sdes * Handle incoming signals during tty conversation
66255376Sdes */
6791100Sdesstatic void
68255376Sdescatch_signal(int signo)
6991100Sdes{
70117610Sdes
71255376Sdes	switch (signo) {
72255376Sdes	case SIGINT:
73255376Sdes	case SIGQUIT:
74255376Sdes	case SIGTERM:
75255376Sdes		caught_signal = signo;
76255376Sdes		break;
77255376Sdes	}
7891100Sdes}
7991100Sdes
80255376Sdes/*
81255376Sdes * Accept a response from the user on a tty
82255376Sdes */
83255376Sdesstatic int
84255376Sdesprompt_tty(int ifd, int ofd, const char *message, char *response, int echo)
8591100Sdes{
86255376Sdes	struct sigaction action;
87255376Sdes	struct sigaction saction_sigint, saction_sigquit, saction_sigterm;
88255376Sdes	struct termios tcattr;
89255376Sdes	struct timeval now, target, remaining;
90255376Sdes	int remaining_ms;
91255376Sdes	tcflag_t slflag;
92255376Sdes	struct pollfd pfd;
93255376Sdes	int serrno;
94255376Sdes	int pos, ret;
95117610Sdes	char ch;
9691100Sdes
97255376Sdes	/* write prompt */
98255376Sdes	if (write(ofd, message, strlen(message)) < 0) {
99255376Sdes		openpam_log(PAM_LOG_ERROR, "write(): %m");
100255376Sdes		return (-1);
101255376Sdes	}
102255376Sdes
103255376Sdes	/* turn echo off if requested */
104255376Sdes	slflag = 0; /* prevent bogus uninitialized variable warning */
105255376Sdes	if (!echo) {
106255376Sdes		if (tcgetattr(ifd, &tcattr) != 0) {
107255376Sdes			openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m");
108255376Sdes			return (-1);
109255376Sdes		}
110255376Sdes		slflag = tcattr.c_lflag;
111255376Sdes		tcattr.c_lflag &= ~ECHO;
112255376Sdes		if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) {
113255376Sdes			openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m");
114255376Sdes			return (-1);
115255376Sdes		}
116255376Sdes	}
117255376Sdes
118255376Sdes	/* install signal handlers */
119255376Sdes	caught_signal = 0;
120255376Sdes	action.sa_handler = &catch_signal;
12191100Sdes	action.sa_flags = 0;
122255376Sdes	sigfillset(&action.sa_mask);
123255376Sdes	sigaction(SIGINT, &action, &saction_sigint);
124255376Sdes	sigaction(SIGQUIT, &action, &saction_sigquit);
125255376Sdes	sigaction(SIGTERM, &action, &saction_sigterm);
126255376Sdes
127255376Sdes	/* compute timeout */
128255376Sdes	if (openpam_ttyconv_timeout > 0) {
129255376Sdes		(void)gettimeofday(&now, NULL);
130255376Sdes		remaining.tv_sec = openpam_ttyconv_timeout;
131255376Sdes		remaining.tv_usec = 0;
132255376Sdes		timeradd(&now, &remaining, &target);
133255376Sdes	} else {
134255376Sdes		/* prevent bogus uninitialized variable warning */
135255376Sdes		now.tv_sec = now.tv_usec = 0;
136255376Sdes		remaining.tv_sec = remaining.tv_usec = 0;
137255376Sdes		target.tv_sec = target.tv_usec = 0;
138255376Sdes	}
139255376Sdes
140255376Sdes	/* input loop */
141255376Sdes	pos = 0;
142255376Sdes	ret = -1;
143255376Sdes	serrno = 0;
144255376Sdes	while (!caught_signal) {
145255376Sdes		pfd.fd = ifd;
146255376Sdes		pfd.events = POLLIN;
147255376Sdes		pfd.revents = 0;
148255376Sdes		if (openpam_ttyconv_timeout > 0) {
149255376Sdes			gettimeofday(&now, NULL);
150255376Sdes			if (timercmp(&now, &target, >))
151255376Sdes				break;
152255376Sdes			timersub(&target, &now, &remaining);
153255376Sdes			remaining_ms = remaining.tv_sec * 1000 +
154255376Sdes			    remaining.tv_usec / 1000;
155255376Sdes		} else {
156255376Sdes			remaining_ms = -1;
157255376Sdes		}
158255376Sdes		if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
159255376Sdes			serrno = errno;
160255376Sdes			if (errno == EINTR)
161255376Sdes				continue;
162255376Sdes			openpam_log(PAM_LOG_ERROR, "poll(): %m");
163117610Sdes			break;
164255376Sdes		} else if (ret == 0) {
165255376Sdes			/* timeout */
166255376Sdes			write(ofd, " timed out", 10);
167255376Sdes			openpam_log(PAM_LOG_NOTICE, "timed out");
168117610Sdes			break;
169255376Sdes		}
170255376Sdes		if ((ret = read(ifd, &ch, 1)) < 0) {
171255376Sdes			serrno = errno;
172255376Sdes			openpam_log(PAM_LOG_ERROR, "read(): %m");
173117610Sdes			break;
174255376Sdes		} else if (ret == 0 || ch == '\n') {
175255376Sdes			response[pos] = '\0';
176255376Sdes			ret = pos;
177255376Sdes			break;
178117610Sdes		}
179255376Sdes		if (pos + 1 < PAM_MAX_RESP_SIZE)
180255376Sdes			response[pos++] = ch;
181255376Sdes		/* overflow is discarded */
182117610Sdes	}
183255376Sdes
184255376Sdes	/* restore tty state */
185255376Sdes	if (!echo) {
186255376Sdes		tcattr.c_lflag = slflag;
187255376Sdes		if (tcsetattr(ifd, 0, &tcattr) != 0) {
188255376Sdes			/* treat as non-fatal, since we have our answer */
189255376Sdes			openpam_log(PAM_LOG_NOTICE, "tcsetattr(): %m");
190255376Sdes		}
191117610Sdes	}
192255376Sdes
193255376Sdes	/* restore signal handlers and re-post caught signal*/
194255376Sdes	sigaction(SIGINT, &saction_sigint, NULL);
195255376Sdes	sigaction(SIGQUIT, &saction_sigquit, NULL);
196255376Sdes	sigaction(SIGTERM, &saction_sigterm, NULL);
197255376Sdes	if (caught_signal != 0) {
198255376Sdes		openpam_log(PAM_LOG_ERROR, "caught signal %d",
199255376Sdes		    (int)caught_signal);
200255376Sdes		raise((int)caught_signal);
201255376Sdes		/* if raise() had no effect... */
202255376Sdes		serrno = EINTR;
203255376Sdes		ret = -1;
204255376Sdes	}
205255376Sdes
206255376Sdes	/* done */
207255376Sdes	write(ofd, "\n", 1);
208255376Sdes	errno = serrno;
209255376Sdes	return (ret);
210255376Sdes}
211255376Sdes
212255376Sdes/*
213255376Sdes * Accept a response from the user on a non-tty stdin.
214255376Sdes */
215255376Sdesstatic int
216255376Sdesprompt_notty(const char *message, char *response)
217255376Sdes{
218255376Sdes	struct timeval now, target, remaining;
219255376Sdes	int remaining_ms;
220255376Sdes	struct pollfd pfd;
221255376Sdes	int ch, pos, ret;
222255376Sdes
223255376Sdes	/* show prompt */
224255376Sdes	fputs(message, stdout);
225255376Sdes	fflush(stdout);
226255376Sdes
227255376Sdes	/* compute timeout */
228255376Sdes	if (openpam_ttyconv_timeout > 0) {
229255376Sdes		(void)gettimeofday(&now, NULL);
230255376Sdes		remaining.tv_sec = openpam_ttyconv_timeout;
231255376Sdes		remaining.tv_usec = 0;
232255376Sdes		timeradd(&now, &remaining, &target);
233255376Sdes	} else {
234255376Sdes		/* prevent bogus uninitialized variable warning */
235255376Sdes		now.tv_sec = now.tv_usec = 0;
236255376Sdes		remaining.tv_sec = remaining.tv_usec = 0;
237255376Sdes		target.tv_sec = target.tv_usec = 0;
238255376Sdes	}
239255376Sdes
240255376Sdes	/* input loop */
241255376Sdes	pos = 0;
242255376Sdes	for (;;) {
243255376Sdes		pfd.fd = STDIN_FILENO;
244255376Sdes		pfd.events = POLLIN;
245255376Sdes		pfd.revents = 0;
246255376Sdes		if (openpam_ttyconv_timeout > 0) {
247255376Sdes			gettimeofday(&now, NULL);
248255376Sdes			if (timercmp(&now, &target, >))
249255376Sdes				break;
250255376Sdes			timersub(&target, &now, &remaining);
251255376Sdes			remaining_ms = remaining.tv_sec * 1000 +
252255376Sdes			    remaining.tv_usec / 1000;
253255376Sdes		} else {
254255376Sdes			remaining_ms = -1;
255255376Sdes		}
256255376Sdes		if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
257255376Sdes			/* interrupt is ok, everything else -> bail */
258255376Sdes			if (errno == EINTR)
259255376Sdes				continue;
260255376Sdes			perror("\nopenpam_ttyconv");
261255376Sdes			return (-1);
262255376Sdes		} else if (ret == 0) {
263255376Sdes			/* timeout */
26491100Sdes			break;
265255376Sdes		} else {
266255376Sdes			/* input */
267255376Sdes			if ((ch = getchar()) == EOF && ferror(stdin)) {
268255376Sdes				perror("\nopenpam_ttyconv");
269255376Sdes				return (-1);
270255376Sdes			}
271255376Sdes			if (ch == EOF || ch == '\n') {
272255376Sdes				response[pos] = '\0';
273255376Sdes				return (pos);
274255376Sdes			}
275255376Sdes			if (pos + 1 < PAM_MAX_RESP_SIZE)
276255376Sdes				response[pos++] = ch;
277255376Sdes			/* overflow is discarded */
278255376Sdes		}
279255376Sdes	}
280255376Sdes	fputs("\nopenpam_ttyconv: timeout\n", stderr);
281255376Sdes	return (-1);
28291100Sdes}
28391100Sdes
284255376Sdes/*
285255376Sdes * Determine whether stdin is a tty; if not, try to open the tty; in
286255376Sdes * either case, call the appropriate method.
287255376Sdes */
288255376Sdesstatic int
289255376Sdesprompt(const char *message, char *response, int echo)
29091100Sdes{
291255376Sdes	int ifd, ofd, ret;
29291100Sdes
293255376Sdes	if (isatty(STDIN_FILENO)) {
294255376Sdes		fflush(stdout);
295255376Sdes#ifdef HAVE_FPURGE
296255376Sdes		fpurge(stdin);
297255376Sdes#endif
298255376Sdes		ifd = STDIN_FILENO;
299255376Sdes		ofd = STDOUT_FILENO;
300255376Sdes	} else {
301255376Sdes		if ((ifd = open("/dev/tty", O_RDWR)) < 0)
302255376Sdes			/* no way to prevent echo */
303255376Sdes			return (prompt_notty(message, response));
304255376Sdes		ofd = ifd;
30591100Sdes	}
306255376Sdes	ret = prompt_tty(ifd, ofd, message, response, echo);
307255376Sdes	if (ifd != STDIN_FILENO)
308255376Sdes		close(ifd);
30991100Sdes	return (ret);
31091100Sdes}
31191100Sdes
31291094Sdes/*
31391100Sdes * OpenPAM extension
31491100Sdes *
31591100Sdes * Simple tty-based conversation function
31691094Sdes */
31791094Sdes
31891094Sdesint
31991094Sdesopenpam_ttyconv(int n,
32091094Sdes	 const struct pam_message **msg,
32191094Sdes	 struct pam_response **resp,
32291094Sdes	 void *data)
32391094Sdes{
324255376Sdes	char respbuf[PAM_MAX_RESP_SIZE];
325123394Sdes	struct pam_response *aresp;
32691100Sdes	int i;
32791094Sdes
328107937Sdes	ENTER();
329107937Sdes	(void)data;
33091094Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
331107937Sdes		RETURNC(PAM_CONV_ERR);
332123394Sdes	if ((aresp = calloc(n, sizeof *aresp)) == NULL)
333107937Sdes		RETURNC(PAM_BUF_ERR);
33491094Sdes	for (i = 0; i < n; ++i) {
335123394Sdes		aresp[i].resp_retcode = 0;
336123394Sdes		aresp[i].resp = NULL;
33791094Sdes		switch (msg[i]->msg_style) {
33891094Sdes		case PAM_PROMPT_ECHO_OFF:
339255376Sdes			if (prompt(msg[i]->msg, respbuf, 0) < 0 ||
340255376Sdes			    (aresp[i].resp = strdup(respbuf)) == NULL)
34191100Sdes				goto fail;
34291100Sdes			break;
34391094Sdes		case PAM_PROMPT_ECHO_ON:
344255376Sdes			if (prompt(msg[i]->msg, respbuf, 1) < 0 ||
345255376Sdes			    (aresp[i].resp = strdup(respbuf)) == NULL)
34691094Sdes				goto fail;
34791094Sdes			break;
34891094Sdes		case PAM_ERROR_MSG:
34991094Sdes			fputs(msg[i]->msg, stderr);
35094735Sdes			if (strlen(msg[i]->msg) > 0 &&
35194735Sdes			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
35294735Sdes				fputc('\n', stderr);
35391094Sdes			break;
35491094Sdes		case PAM_TEXT_INFO:
35591094Sdes			fputs(msg[i]->msg, stdout);
35694735Sdes			if (strlen(msg[i]->msg) > 0 &&
35794735Sdes			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
35894735Sdes				fputc('\n', stdout);
35991094Sdes			break;
36091094Sdes		default:
36191094Sdes			goto fail;
36291094Sdes		}
36391094Sdes	}
364123394Sdes	*resp = aresp;
365255376Sdes	memset(respbuf, 0, sizeof respbuf);
366107937Sdes	RETURNC(PAM_SUCCESS);
367228690Sdesfail:
368122912Sdes	for (i = 0; i < n; ++i) {
369123394Sdes		if (aresp[i].resp != NULL) {
370271947Sdes			strlset(aresp[i].resp, 0, PAM_MAX_RESP_SIZE);
371123394Sdes			FREE(aresp[i].resp);
372117610Sdes		}
373122912Sdes	}
374123394Sdes	memset(aresp, 0, n * sizeof *aresp);
375123394Sdes	FREE(aresp);
376123394Sdes	*resp = NULL;
377255376Sdes	memset(respbuf, 0, sizeof respbuf);
378107937Sdes	RETURNC(PAM_CONV_ERR);
37991094Sdes}
38091100Sdes
38191100Sdes/*
38291100Sdes * Error codes:
38391100Sdes *
38491100Sdes *	PAM_SYSTEM_ERR
38591100Sdes *	PAM_BUF_ERR
38691100Sdes *	PAM_CONV_ERR
38791100Sdes */
38897241Sdes
38997241Sdes/**
39097241Sdes * The =openpam_ttyconv function is a standard conversation function
391141098Sdes * suitable for use on TTY devices.
392141098Sdes * It should be adequate for the needs of most text-based interactive
393141098Sdes * programs.
39497241Sdes *
39597241Sdes * The =openpam_ttyconv function allows the application to specify a
396115619Sdes * timeout for user input by setting the global integer variable
39797241Sdes * :openpam_ttyconv_timeout to the length of the timeout in seconds.
39897241Sdes *
39997241Sdes * >openpam_nullconv
40097241Sdes * >pam_prompt
40197241Sdes * >pam_vprompt
40297241Sdes */
403