xref: /freebsd/contrib/openpam/lib/libpam/openpam_ttyconv.c (revision d4ae33f0721c1b170fe37d97e395228ffcfb3f80)
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 
59 int openpam_ttyconv_timeout = 0;
60 
61 static volatile sig_atomic_t caught_signal;
62 
63 /*
64  * Handle incoming signals during tty conversation
65  */
66 static void
67 catch_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  */
82 static int
83 prompt_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  */
214 static int
215 prompt_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  */
287 static int
288 prompt(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 
317 int
318 openpam_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);
366 fail:
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