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 * $OpenPAM: openpam_ttyconv.c 938 2017-04-30 21:34:42Z 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 #include "openpam_strlset.h"
59
60 int openpam_ttyconv_timeout = 0;
61
62 static volatile sig_atomic_t caught_signal;
63
64 /*
65 * Handle incoming signals during tty conversation
66 */
67 static void
catch_signal(int signo)68 catch_signal(int signo)
69 {
70
71 switch (signo) {
72 case SIGINT:
73 case SIGQUIT:
74 case SIGTERM:
75 caught_signal = signo;
76 break;
77 }
78 }
79
80 /*
81 * Accept a response from the user on a tty
82 */
83 static int
prompt_tty(int ifd,int ofd,const char * message,char * response,int echo)84 prompt_tty(int ifd, int ofd, const char *message, char *response, int echo)
85 {
86 struct sigaction action;
87 struct sigaction saction_sigint, saction_sigquit, saction_sigterm;
88 struct termios tcattr;
89 struct timeval now, target, remaining;
90 int remaining_ms;
91 tcflag_t slflag;
92 struct pollfd pfd;
93 int serrno;
94 int pos, ret;
95 char ch;
96
97 /* write prompt */
98 if (write(ofd, message, strlen(message)) < 0) {
99 openpam_log(PAM_LOG_ERROR, "write(): %m");
100 return (-1);
101 }
102
103 /* turn echo off if requested */
104 slflag = 0; /* prevent bogus uninitialized variable warning */
105 if (!echo) {
106 if (tcgetattr(ifd, &tcattr) != 0) {
107 openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m");
108 return (-1);
109 }
110 slflag = tcattr.c_lflag;
111 tcattr.c_lflag &= ~ECHO;
112 if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) {
113 openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m");
114 return (-1);
115 }
116 }
117
118 /* install signal handlers */
119 caught_signal = 0;
120 action.sa_handler = &catch_signal;
121 action.sa_flags = 0;
122 sigfillset(&action.sa_mask);
123 sigaction(SIGINT, &action, &saction_sigint);
124 sigaction(SIGQUIT, &action, &saction_sigquit);
125 sigaction(SIGTERM, &action, &saction_sigterm);
126
127 /* compute timeout */
128 if (openpam_ttyconv_timeout > 0) {
129 (void)gettimeofday(&now, NULL);
130 remaining.tv_sec = openpam_ttyconv_timeout;
131 remaining.tv_usec = 0;
132 timeradd(&now, &remaining, &target);
133 } else {
134 /* prevent bogus uninitialized variable warning */
135 now.tv_sec = now.tv_usec = 0;
136 remaining.tv_sec = remaining.tv_usec = 0;
137 target.tv_sec = target.tv_usec = 0;
138 }
139
140 /* input loop */
141 pos = 0;
142 ret = -1;
143 serrno = 0;
144 while (!caught_signal) {
145 pfd.fd = ifd;
146 pfd.events = POLLIN;
147 pfd.revents = 0;
148 if (openpam_ttyconv_timeout > 0) {
149 gettimeofday(&now, NULL);
150 if (timercmp(&now, &target, >))
151 break;
152 timersub(&target, &now, &remaining);
153 remaining_ms = remaining.tv_sec * 1000 +
154 remaining.tv_usec / 1000;
155 } else {
156 remaining_ms = -1;
157 }
158 if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
159 serrno = errno;
160 if (errno == EINTR)
161 continue;
162 openpam_log(PAM_LOG_ERROR, "poll(): %m");
163 break;
164 } else if (ret == 0) {
165 /* timeout */
166 write(ofd, " timed out", 10);
167 openpam_log(PAM_LOG_NOTICE, "timed out");
168 break;
169 }
170 if ((ret = read(ifd, &ch, 1)) < 0) {
171 serrno = errno;
172 openpam_log(PAM_LOG_ERROR, "read(): %m");
173 break;
174 } else if (ret == 0 || ch == '\n') {
175 response[pos] = '\0';
176 ret = pos;
177 break;
178 }
179 if (pos + 1 < PAM_MAX_RESP_SIZE)
180 response[pos++] = ch;
181 /* overflow is discarded */
182 }
183
184 /* restore tty state */
185 if (!echo) {
186 tcattr.c_lflag = slflag;
187 if (tcsetattr(ifd, 0, &tcattr) != 0) {
188 /* treat as non-fatal, since we have our answer */
189 openpam_log(PAM_LOG_NOTICE, "tcsetattr(): %m");
190 }
191 }
192
193 /* restore signal handlers and re-post caught signal*/
194 sigaction(SIGINT, &saction_sigint, NULL);
195 sigaction(SIGQUIT, &saction_sigquit, NULL);
196 sigaction(SIGTERM, &saction_sigterm, NULL);
197 if (caught_signal != 0) {
198 openpam_log(PAM_LOG_ERROR, "caught signal %d",
199 (int)caught_signal);
200 raise((int)caught_signal);
201 /* if raise() had no effect... */
202 serrno = EINTR;
203 ret = -1;
204 }
205
206 /* done */
207 write(ofd, "\n", 1);
208 errno = serrno;
209 return (ret);
210 }
211
212 /*
213 * Accept a response from the user on a non-tty stdin.
214 */
215 static int
prompt_notty(const char * message,char * response)216 prompt_notty(const char *message, char *response)
217 {
218 struct timeval now, target, remaining;
219 int remaining_ms;
220 struct pollfd pfd;
221 int ch, pos, ret;
222
223 /* show prompt */
224 fputs(message, stdout);
225 fflush(stdout);
226
227 /* compute timeout */
228 if (openpam_ttyconv_timeout > 0) {
229 (void)gettimeofday(&now, NULL);
230 remaining.tv_sec = openpam_ttyconv_timeout;
231 remaining.tv_usec = 0;
232 timeradd(&now, &remaining, &target);
233 } else {
234 /* prevent bogus uninitialized variable warning */
235 now.tv_sec = now.tv_usec = 0;
236 remaining.tv_sec = remaining.tv_usec = 0;
237 target.tv_sec = target.tv_usec = 0;
238 }
239
240 /* input loop */
241 pos = 0;
242 for (;;) {
243 pfd.fd = STDIN_FILENO;
244 pfd.events = POLLIN;
245 pfd.revents = 0;
246 if (openpam_ttyconv_timeout > 0) {
247 gettimeofday(&now, NULL);
248 if (timercmp(&now, &target, >))
249 break;
250 timersub(&target, &now, &remaining);
251 remaining_ms = remaining.tv_sec * 1000 +
252 remaining.tv_usec / 1000;
253 } else {
254 remaining_ms = -1;
255 }
256 if ((ret = poll(&pfd, 1, remaining_ms)) < 0) {
257 /* interrupt is ok, everything else -> bail */
258 if (errno == EINTR)
259 continue;
260 perror("\nopenpam_ttyconv");
261 return (-1);
262 } else if (ret == 0) {
263 /* timeout */
264 break;
265 } else {
266 /* input */
267 if ((ch = getchar()) == EOF && ferror(stdin)) {
268 perror("\nopenpam_ttyconv");
269 return (-1);
270 }
271 if (ch == EOF || ch == '\n') {
272 response[pos] = '\0';
273 return (pos);
274 }
275 if (pos + 1 < PAM_MAX_RESP_SIZE)
276 response[pos++] = ch;
277 /* overflow is discarded */
278 }
279 }
280 fputs("\nopenpam_ttyconv: timeout\n", stderr);
281 return (-1);
282 }
283
284 /*
285 * Determine whether stdin is a tty; if not, try to open the tty; in
286 * either case, call the appropriate method.
287 */
288 static int
prompt(const char * message,char * response,int echo)289 prompt(const char *message, char *response, int echo)
290 {
291 int ifd, ofd, ret;
292
293 if (isatty(STDIN_FILENO)) {
294 fflush(stdout);
295 #ifdef HAVE_FPURGE
296 fpurge(stdin);
297 #endif
298 ifd = STDIN_FILENO;
299 ofd = STDOUT_FILENO;
300 } else {
301 if ((ifd = open("/dev/tty", O_RDWR)) < 0)
302 /* no way to prevent echo */
303 return (prompt_notty(message, response));
304 ofd = ifd;
305 }
306 ret = prompt_tty(ifd, ofd, message, response, echo);
307 if (ifd != STDIN_FILENO)
308 close(ifd);
309 return (ret);
310 }
311
312 /*
313 * OpenPAM extension
314 *
315 * Simple tty-based conversation function
316 */
317
318 int
openpam_ttyconv(int n,const struct pam_message ** msg,struct pam_response ** resp,void * data)319 openpam_ttyconv(int n,
320 const struct pam_message **msg,
321 struct pam_response **resp,
322 void *data)
323 {
324 char respbuf[PAM_MAX_RESP_SIZE];
325 struct pam_response *aresp;
326 int i;
327
328 ENTER();
329 (void)data;
330 if (n <= 0 || n > PAM_MAX_NUM_MSG)
331 RETURNC(PAM_CONV_ERR);
332 if ((aresp = calloc(n, sizeof *aresp)) == NULL)
333 RETURNC(PAM_BUF_ERR);
334 for (i = 0; i < n; ++i) {
335 aresp[i].resp_retcode = 0;
336 aresp[i].resp = NULL;
337 switch (msg[i]->msg_style) {
338 case PAM_PROMPT_ECHO_OFF:
339 if (prompt(msg[i]->msg, respbuf, 0) < 0 ||
340 (aresp[i].resp = strdup(respbuf)) == NULL)
341 goto fail;
342 break;
343 case PAM_PROMPT_ECHO_ON:
344 if (prompt(msg[i]->msg, respbuf, 1) < 0 ||
345 (aresp[i].resp = strdup(respbuf)) == NULL)
346 goto fail;
347 break;
348 case PAM_ERROR_MSG:
349 fputs(msg[i]->msg, stderr);
350 if (strlen(msg[i]->msg) > 0 &&
351 msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
352 fputc('\n', stderr);
353 break;
354 case PAM_TEXT_INFO:
355 fputs(msg[i]->msg, stdout);
356 if (strlen(msg[i]->msg) > 0 &&
357 msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
358 fputc('\n', stdout);
359 break;
360 default:
361 goto fail;
362 }
363 }
364 *resp = aresp;
365 memset(respbuf, 0, sizeof respbuf);
366 RETURNC(PAM_SUCCESS);
367 fail:
368 for (i = 0; i < n; ++i) {
369 if (aresp[i].resp != NULL) {
370 strlset(aresp[i].resp, 0, PAM_MAX_RESP_SIZE);
371 FREE(aresp[i].resp);
372 }
373 }
374 memset(aresp, 0, n * sizeof *aresp);
375 FREE(aresp);
376 *resp = NULL;
377 memset(respbuf, 0, sizeof respbuf);
378 RETURNC(PAM_CONV_ERR);
379 }
380
381 /*
382 * Error codes:
383 *
384 * PAM_SYSTEM_ERR
385 * PAM_BUF_ERR
386 * PAM_CONV_ERR
387 */
388
389 /**
390 * The =openpam_ttyconv function is a standard conversation function
391 * suitable for use on TTY devices.
392 * It should be adequate for the needs of most text-based interactive
393 * programs.
394 *
395 * The =openpam_ttyconv function allows the application to specify a
396 * timeout for user input by setting the global integer variable
397 * :openpam_ttyconv_timeout to the length of the timeout in seconds.
398 *
399 * >openpam_nullconv
400 * >pam_prompt
401 * >pam_vprompt
402 */
403