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 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 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 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 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 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