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