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