xref: /netbsd-src/external/bsd/openpam/dist/lib/libpam/openpam_ttyconv.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: openpam_ttyconv.c,v 1.3 2017/05/06 19:50:09 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
5  * Copyright (c) 2004-2014 Dag-Erling Smørgrav
6  * All rights reserved.
7  *
8  * This software was developed for the FreeBSD Project by ThinkSec AS and
9  * Network Associates Laboratories, the Security Research Division of
10  * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
11  * ("CBOSS"), as part of the DARPA CHATS research program.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. The name of the author may not be used to endorse or promote
22  *    products derived from this software without specific prior written
23  *    permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  *
37  * $OpenPAM: openpam_ttyconv.c 938 2017-04-30 21:34:42Z des $
38  */
39 
40 #ifdef HAVE_CONFIG_H
41 # include "config.h"
42 #endif
43 
44 #include <sys/cdefs.h>
45 __RCSID("$NetBSD: openpam_ttyconv.c,v 1.3 2017/05/06 19:50:09 christos Exp $");
46 
47 #include <sys/types.h>
48 #include <sys/poll.h>
49 #include <sys/time.h>
50 
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <signal.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <termios.h>
58 #include <unistd.h>
59 #include <paths.h>
60 
61 #include <security/pam_appl.h>
62 
63 #include "openpam_impl.h"
64 #include "openpam_strlset.h"
65 
66 int openpam_ttyconv_timeout = 0;
67 
68 #ifdef GETPASS_ECHO
69 static int
70 prompt(const char *message, char *response, int echo)
71 {
72 	char *rv;
73 
74 	rv = getpassfd(message, response, PAM_MAX_RESP_SIZE, NULL,
75 	    GETPASS_NEED_TTY | GETPASS_FAIL_EOF | GETPASS_NO_SIGNAL |
76 	    (echo ? GETPASS_ECHO : 0), openpam_ttyconv_timeout);
77 	if (rv == NULL) {
78 		fprintf(stderr, " %s\n", strerror(errno));
79 		return -1;
80 	} else if (echo == 0)
81 		fputs("\n", stderr);
82 	return 0;
83 }
84 
85 #else
86 
87 static volatile sig_atomic_t caught_signal;
88 /*
89  * Handle incoming signals during tty conversation
90  */
91 static void
92 catch_signal(int signo)
93 {
94 
95 	switch (signo) {
96 	case SIGINT:
97 	case SIGQUIT:
98 	case SIGTERM:
99 		caught_signal = signo;
100 		break;
101 	}
102 }
103 
104 /*
105  * Accept a response from the user on a tty
106  */
107 static int
108 prompt_tty(int ifd, int ofd, const char *message, char *response, int echo)
109 {
110 	struct sigaction action;
111 	struct sigaction saction_sigint, saction_sigquit, saction_sigterm;
112 	struct termios tcattr;
113 	struct timeval now, target, remaining;
114 	int remaining_ms;
115 	tcflag_t slflag;
116 	struct pollfd pfd;
117 	int serrno;
118 	int pos, ret;
119 	char ch;
120 
121 	/* write prompt */
122 	if (write(ofd, message, strlen(message)) < 0) {
123 		openpam_log(PAM_LOG_ERROR, "write(): %m");
124 		return (-1);
125 	}
126 
127 	/* turn echo off if requested */
128 	slflag = 0; /* prevent bogus uninitialized variable warning */
129 	if (!echo) {
130 		if (tcgetattr(ifd, &tcattr) != 0) {
131 			openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m");
132 			return (-1);
133 		}
134 		slflag = tcattr.c_lflag;
135 		tcattr.c_lflag &= ~ECHO;
136 		if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) {
137 			openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m");
138 			return (-1);
139 		}
140 	}
141 
142 	/* install signal handlers */
143 	caught_signal = 0;
144 	action.sa_handler = &catch_signal;
145 	action.sa_flags = 0;
146 	sigfillset(&action.sa_mask);
147 	sigaction(SIGINT, &action, &saction_sigint);
148 	sigaction(SIGQUIT, &action, &saction_sigquit);
149 	sigaction(SIGTERM, &action, &saction_sigterm);
150 
151 	/* compute timeout */
152 	if (openpam_ttyconv_timeout > 0) {
153 		(void)gettimeofday(&now, NULL);
154 		remaining.tv_sec = openpam_ttyconv_timeout;
155 		remaining.tv_usec = 0;
156 		timeradd(&now, &remaining, &target);
157 	} else {
158 		/* prevent bogus uninitialized variable warning */
159 		now.tv_sec = now.tv_usec = 0;
160 		remaining.tv_sec = remaining.tv_usec = 0;
161 		target.tv_sec = target.tv_usec = 0;
162 	}
163 
164 	/* input loop */
165 	pos = 0;
166 	ret = -1;
167 	serrno = 0;
168 	while (!caught_signal) {
169 		pfd.fd = ifd;
170 		pfd.events = POLLIN;
171 		pfd.revents = 0;
172 		if (openpam_ttyconv_timeout > 0) {
173 			gettimeofday(&now, NULL);
174 			if (timercmp(&now, &target, >))
175 				break;
176 			timersub(&target, &now, &remaining);
177 			remaining_ms = remaining.tv_sec * 1000 +
178 			    remaining.tv_usec / 1000;
179 		} else {
180 			remaining_ms = -1;
181 		}
182 		if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
183 			serrno = errno;
184 			if (errno == EINTR)
185 				continue;
186 			openpam_log(PAM_LOG_ERROR, "poll(): %m");
187 			break;
188 		} else if (ret == 0) {
189 			/* timeout */
190 			write(ofd, " timed out", 10);
191 			openpam_log(PAM_LOG_NOTICE, "timed out");
192 			break;
193 		}
194 		if ((ret = read(ifd, &ch, 1)) < 0) {
195 			serrno = errno;
196 			openpam_log(PAM_LOG_ERROR, "read(): %m");
197 			break;
198 		} else if (ret == 0 || ch == '\n') {
199 			response[pos] = '\0';
200 			ret = pos;
201 			break;
202 		}
203 		if (pos + 1 < PAM_MAX_RESP_SIZE)
204 			response[pos++] = ch;
205 		/* overflow is discarded */
206 	}
207 
208 	/* restore tty state */
209 	if (!echo) {
210 		tcattr.c_lflag = slflag;
211 		if (tcsetattr(ifd, 0, &tcattr) != 0) {
212 			/* treat as non-fatal, since we have our answer */
213 			openpam_log(PAM_LOG_NOTICE, "tcsetattr(): %m");
214 		}
215 	}
216 
217 	/* restore signal handlers and re-post caught signal*/
218 	sigaction(SIGINT, &saction_sigint, NULL);
219 	sigaction(SIGQUIT, &saction_sigquit, NULL);
220 	sigaction(SIGTERM, &saction_sigterm, NULL);
221 	if (caught_signal != 0) {
222 		openpam_log(PAM_LOG_ERROR, "caught signal %d",
223 		    (int)caught_signal);
224 		raise((int)caught_signal);
225 		/* if raise() had no effect... */
226 		serrno = EINTR;
227 		ret = -1;
228 	}
229 
230 	/* done */
231 	write(ofd, "\n", 1);
232 	errno = serrno;
233 	return (ret);
234 }
235 
236 /*
237  * Accept a response from the user on a non-tty stdin.
238  */
239 static int
240 prompt_notty(const char *message, char *response)
241 {
242 	struct timeval now, target, remaining;
243 	int remaining_ms;
244 	struct pollfd pfd;
245 	int ch, pos, ret;
246 
247 	/* show prompt */
248 	fputs(message, stdout);
249 	fflush(stdout);
250 
251 	/* compute timeout */
252 	if (openpam_ttyconv_timeout > 0) {
253 		(void)gettimeofday(&now, NULL);
254 		remaining.tv_sec = openpam_ttyconv_timeout;
255 		remaining.tv_usec = 0;
256 		timeradd(&now, &remaining, &target);
257 	} else {
258 		/* prevent bogus uninitialized variable warning */
259 		now.tv_sec = now.tv_usec = 0;
260 		remaining.tv_sec = remaining.tv_usec = 0;
261 		target.tv_sec = target.tv_usec = 0;
262 	}
263 
264 	/* input loop */
265 	pos = 0;
266 	for (;;) {
267 		pfd.fd = STDIN_FILENO;
268 		pfd.events = POLLIN;
269 		pfd.revents = 0;
270 		if (openpam_ttyconv_timeout > 0) {
271 			gettimeofday(&now, NULL);
272 			if (timercmp(&now, &target, >))
273 				break;
274 			timersub(&target, &now, &remaining);
275 			remaining_ms = remaining.tv_sec * 1000 +
276 			    remaining.tv_usec / 1000;
277 		} else {
278 			remaining_ms = -1;
279 		}
280 		if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
281 			/* interrupt is ok, everything else -> bail */
282 			if (errno == EINTR)
283 				continue;
284 			perror("\nopenpam_ttyconv");
285 			return (-1);
286 		} else if (ret == 0) {
287 			/* timeout */
288 			break;
289 		} else {
290 			/* input */
291 			if ((ch = getchar()) == EOF && ferror(stdin)) {
292 				perror("\nopenpam_ttyconv");
293 				return (-1);
294 			}
295 			if (ch == EOF || ch == '\n') {
296 				response[pos] = '\0';
297 				return (pos);
298 			}
299 			if (pos + 1 < PAM_MAX_RESP_SIZE)
300 				response[pos++] = ch;
301 			/* overflow is discarded */
302 		}
303 	}
304 	fputs("\nopenpam_ttyconv: timeout\n", stderr);
305 	return (-1);
306 }
307 
308 /*
309  * Determine whether stdin is a tty; if not, try to open the tty; in
310  * either case, call the appropriate method.
311  */
312 static int
313 prompt(const char *message, char *response, int echo)
314 {
315 	int ifd, ofd, ret;
316 
317 	if (isatty(STDIN_FILENO)) {
318 		fflush(stdout);
319 #ifdef HAVE_FPURGE
320 		fpurge(stdin);
321 #endif
322 		ifd = STDIN_FILENO;
323 		ofd = STDOUT_FILENO;
324 	} else {
325 		if ((ifd = open("/dev/tty", O_RDWR)) < 0)
326 			/* no way to prevent echo */
327 			return (prompt_notty(message, response));
328 		ofd = ifd;
329 	}
330 	ret = prompt_tty(ifd, ofd, message, response, echo);
331 	if (ifd != STDIN_FILENO)
332 		close(ifd);
333 	return (ret);
334 }
335 #endif
336 
337 /*
338  * OpenPAM extension
339  *
340  * Simple tty-based conversation function
341  */
342 
343 int
344 openpam_ttyconv(int n,
345 	 const struct pam_message **msg,
346 	 struct pam_response **resp,
347 	 void *data)
348 {
349 	char respbuf[PAM_MAX_RESP_SIZE];
350 	struct pam_response *aresp;
351 	int i;
352 	FILE *infp, *outfp, *errfp;
353 
354 	ENTER();
355 	/*LINTED unused*/
356 	(void)data;
357 	if (n <= 0 || n > PAM_MAX_NUM_MSG)
358 		RETURNC(PAM_CONV_ERR);
359 	if ((aresp = calloc((size_t)n, sizeof *aresp)) == NULL)
360 		RETURNC(PAM_BUF_ERR);
361 
362 	/*
363 	 * read and write to /dev/tty if possible; else read from
364 	 * stdin and write to stderr.
365 	 */
366 	if ((outfp = infp = errfp = fopen(_PATH_TTY, "w+")) == NULL) {
367 		errfp = stderr;
368 		outfp = stderr;
369 		infp = stdin;
370 	}
371 
372 	for (i = 0; i < n; ++i) {
373 		aresp[i].resp_retcode = 0;
374 		aresp[i].resp = NULL;
375 		switch (msg[i]->msg_style) {
376 		case PAM_PROMPT_ECHO_OFF:
377 			if (prompt(msg[i]->msg, respbuf, 0) < 0 ||
378 			    (aresp[i].resp = strdup(respbuf)) == NULL)
379 				goto fail;
380 			break;
381 		case PAM_PROMPT_ECHO_ON:
382 			if (prompt(msg[i]->msg, respbuf, 1) < 0 ||
383 			    (aresp[i].resp = strdup(respbuf)) == NULL)
384 				goto fail;
385 			break;
386 		case PAM_ERROR_MSG:
387 			fputs(msg[i]->msg, errfp);
388 			if (strlen(msg[i]->msg) > 0 &&
389 			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
390 				fputc('\n', errfp);
391 			break;
392 		case PAM_TEXT_INFO:
393 			fputs(msg[i]->msg, outfp);
394 			if (strlen(msg[i]->msg) > 0 &&
395 			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
396 				fputc('\n', outfp);
397 			break;
398 		default:
399 			goto fail;
400 		}
401 	}
402 	if (infp != stdin)
403 		(void)fclose(infp);
404 	*resp = aresp;
405 	memset(respbuf, 0, sizeof respbuf);
406 	RETURNC(PAM_SUCCESS);
407 fail:
408 	for (i = 0; i < n; ++i) {
409 		if (aresp[i].resp != NULL) {
410 			strlset(aresp[i].resp, 0, PAM_MAX_RESP_SIZE);
411 			FREE(aresp[i].resp);
412 		}
413 	}
414 	if (infp != stdin)
415 		(void)fclose(infp);
416 	memset(aresp, 0, (size_t)n * sizeof *aresp);
417 	FREE(aresp);
418 	*resp = NULL;
419 	memset(respbuf, 0, sizeof respbuf);
420 	RETURNC(PAM_CONV_ERR);
421 }
422 
423 /*
424  * Error codes:
425  *
426  *	PAM_SYSTEM_ERR
427  *	PAM_BUF_ERR
428  *	PAM_CONV_ERR
429  */
430 
431 /**
432  * The =openpam_ttyconv function is a standard conversation function
433  * suitable for use on TTY devices.
434  * It should be adequate for the needs of most text-based interactive
435  * programs.
436  *
437  * The =openpam_ttyconv function displays a prompt to, and reads in a
438  * password from /dev/tty. If this file is not accessible, =openpam_ttyconv
439  * displays the prompt on the standard error output and reads from the
440  * standard input.
441  *
442  * The =openpam_ttyconv function allows the application to specify a
443  * timeout for user input by setting the global integer variable
444  * :openpam_ttyconv_timeout to the length of the timeout in seconds.
445  *
446  * >openpam_nullconv
447  * >pam_prompt
448  * >pam_vprompt
449  * >getpass
450  */
451