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
catch_signal(int signo)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
prompt_tty(int ifd,int ofd,const char * message,char * response,int echo)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
prompt_notty(const char * message,char * response)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
prompt(const char * message,char * response,int echo)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
openpam_ttyconv(int n,const struct pam_message ** msg,struct pam_response ** resp,void * data)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