1/*	$NetBSD: openpam_ttyconv.c,v 1.2 2011/12/25 22:27:55 christos Exp $	*/
2
3/*-
4 * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
5 * Copyright (c) 2004-2011 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 * Id: openpam_ttyconv.c 437 2011-09-13 12:00:13Z des
38 */
39
40#ifdef HAVE_CONFIG_H
41# include "config.h"
42#endif
43
44#include <sys/types.h>
45
46#include <ctype.h>
47#include <errno.h>
48#include <setjmp.h>
49#include <signal.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <termios.h>
54#include <unistd.h>
55#include <paths.h>
56
57#include <security/pam_appl.h>
58
59#include "openpam_impl.h"
60
61int openpam_ttyconv_timeout = 0;
62
63static void
64timeout(int sig)
65{
66	/*LINTED unused*/
67	(void)sig;
68}
69
70static char *
71prompt(const char *msg, FILE *infp, FILE *outfp, FILE *errfp)
72{
73	char buf[PAM_MAX_RESP_SIZE];
74	struct sigaction action, saved_action;
75	sigset_t saved_sigset, sigs;
76	unsigned int saved_alarm;
77	int eof, error, fd;
78	size_t len;
79	char *retval;
80	char ch;
81
82	saved_alarm = 0;
83	sigemptyset(&sigs);
84	sigaddset(&sigs, SIGINT);
85	sigaddset(&sigs, SIGTSTP);
86	sigprocmask(SIG_SETMASK, &sigs, &saved_sigset);
87	action.sa_handler = &timeout;
88	action.sa_flags = 0;
89	sigemptyset(&action.sa_mask);
90	sigaction(SIGALRM, &action, &saved_action);
91
92
93	fputs(msg, outfp);
94	fflush(outfp);
95#ifdef HAVE_FPURGE
96	fpurge(infp);
97#endif
98	fd = fileno(infp);
99	buf[0] = '\0';
100	eof = error = 0;
101	saved_alarm = 0;
102	if (openpam_ttyconv_timeout >= 0)
103		saved_alarm = alarm((unsigned int)openpam_ttyconv_timeout);
104	ch = '\0';
105	for (len = 0; ch != '\n' && !eof && !error; ++len) {
106		switch (read(fd, &ch, (size_t)1)) {
107		case 1:
108			if (len < PAM_MAX_RESP_SIZE - 1) {
109				buf[len + 1] = '\0';
110				buf[len] = ch;
111			}
112			break;
113		case 0:
114			eof = 1;
115			break;
116		default:
117			error = errno;
118			break;
119		}
120	}
121	if (openpam_ttyconv_timeout >= 0)
122		alarm(0);
123	sigaction(SIGALRM, &saved_action, NULL);
124	sigprocmask(SIG_SETMASK, &saved_sigset, NULL);
125	if (saved_alarm > 0)
126		alarm(saved_alarm);
127	if (error == EINTR)
128		fputs(" timeout!", errfp);
129	if (error || eof) {
130		fputs("\n", errfp);
131		memset(buf, 0, sizeof(buf));
132		return (NULL);
133	}
134	/* trim trailing whitespace */
135	for (len = strlen(buf); len > 0; --len)
136		if (buf[len - 1] != '\r' && buf[len - 1] != '\n')
137			break;
138	buf[len] = '\0';
139	retval = strdup(buf);
140	memset(buf, 0, sizeof(buf));
141	return (retval);
142}
143
144static char *
145prompt_echo_off(const char *msg, FILE *infp, FILE *outfp, FILE *errfp)
146{
147	struct termios tattr;
148	tcflag_t lflag;
149	char *ret;
150	int fd;
151
152	fd = fileno(infp);
153	if (tcgetattr(fd, &tattr) != 0) {
154		openpam_log(PAM_LOG_ERROR, "tcgetattr(): %s", strerror(errno));
155		return (NULL);
156	}
157	lflag = tattr.c_lflag;
158	tattr.c_lflag &= ~ECHO;
159	if (tcsetattr(fd, TCSAFLUSH, &tattr) != 0) {
160		openpam_log(PAM_LOG_ERROR, "tcsetattr(): %s", strerror(errno));
161		return (NULL);
162	}
163	ret = prompt(msg, infp, outfp, errfp);
164	tattr.c_lflag = lflag;
165	(void)tcsetattr(fd, TCSANOW, &tattr);
166	if (ret != NULL)
167		fputs("\n", outfp);
168	return (ret);
169}
170
171/*
172 * OpenPAM extension
173 *
174 * Simple tty-based conversation function
175 */
176
177int
178openpam_ttyconv(int n,
179	 const struct pam_message **msg,
180	 struct pam_response **resp,
181	 void *data)
182{
183	struct pam_response *aresp;
184	int i;
185	FILE *infp, *outfp, *errfp;
186
187	ENTER();
188	/*LINTED unused*/
189	(void)data;
190	if (n <= 0 || n > PAM_MAX_NUM_MSG)
191		RETURNC(PAM_CONV_ERR);
192	if ((aresp = calloc((size_t)n, sizeof *aresp)) == NULL)
193		RETURNC(PAM_BUF_ERR);
194
195	/*
196	 * read and write to /dev/tty if possible; else read from
197	 * stdin and write to stderr.
198	 */
199	if ((outfp = infp = errfp = fopen(_PATH_TTY, "w+")) == NULL) {
200		errfp = stderr;
201		outfp = stderr;
202		infp = stdin;
203	}
204
205	for (i = 0; i < n; ++i) {
206		aresp[i].resp_retcode = 0;
207		aresp[i].resp = NULL;
208		switch (msg[i]->msg_style) {
209		case PAM_PROMPT_ECHO_OFF:
210			aresp[i].resp = prompt_echo_off(msg[i]->msg, infp,
211			    outfp, errfp);
212			if (aresp[i].resp == NULL)
213				goto fail;
214			break;
215		case PAM_PROMPT_ECHO_ON:
216			aresp[i].resp = prompt(msg[i]->msg, infp, outfp, errfp);
217			if (aresp[i].resp == NULL)
218				goto fail;
219			break;
220		case PAM_ERROR_MSG:
221			fputs(msg[i]->msg, errfp);
222			if (strlen(msg[i]->msg) > 0 &&
223			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
224				fputc('\n', errfp);
225			break;
226		case PAM_TEXT_INFO:
227			fputs(msg[i]->msg, outfp);
228			if (strlen(msg[i]->msg) > 0 &&
229			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
230				fputc('\n', outfp);
231			break;
232		default:
233			goto fail;
234		}
235	}
236	if (infp != stdin)
237		(void)fclose(infp);
238	*resp = aresp;
239	RETURNC(PAM_SUCCESS);
240fail:
241	for (i = 0; i < n; ++i) {
242		if (aresp[i].resp != NULL) {
243			memset(aresp[i].resp, 0, strlen(aresp[i].resp));
244			FREE(aresp[i].resp);
245		}
246	}
247	if (infp != stdin)
248		(void)fclose(infp);
249	memset(aresp, 0, n * sizeof *aresp);
250	FREE(aresp);
251	*resp = NULL;
252	RETURNC(PAM_CONV_ERR);
253	/*NOTREACHED*/
254}
255
256/*
257 * Error codes:
258 *
259 *	PAM_SYSTEM_ERR
260 *	PAM_BUF_ERR
261 *	PAM_CONV_ERR
262 */
263
264/**
265 * The =openpam_ttyconv function is a standard conversation function
266 * suitable for use on TTY devices.
267 * It should be adequate for the needs of most text-based interactive
268 * programs.
269 *
270 * The =openpam_ttyconv function displays a prompt to, and reads in a
271 * password from /dev/tty. If this file is not accessible, =openpam_ttyconv
272 * displays the prompt on the standard error output and reads from the
273 * standard input.
274 *
275 * The =openpam_ttyconv function allows the application to specify a
276 * timeout for user input by setting the global integer variable
277 * :openpam_ttyconv_timeout to the length of the timeout in seconds.
278 *
279 * >openpam_nullconv
280 * >pam_prompt
281 * >pam_vprompt
282 * >getpass
283 */
284