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 
58 int openpam_ttyconv_timeout = 0;
59 
60 static volatile sig_atomic_t caught_signal;
61 
62 /*
63  * Handle incoming signals during tty conversation
64  */
65 static void
66 catch_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  */
81 static int
82 prompt_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  */
213 static int
214 prompt_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  */
286 static int
287 prompt(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 
316 int
317 openpam_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);
365 fail:
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