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 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 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 /* turn echo off if requested */ 98 slflag = 0; /* prevent bogus uninitialized variable warning */ 99 if (!echo) { 100 if (tcgetattr(ifd, &tcattr) != 0) { 101 openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m"); 102 return (-1); 103 } 104 slflag = tcattr.c_lflag; 105 tcattr.c_lflag &= ~ECHO; 106 if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) { 107 openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m"); 108 return (-1); 109 } 110 } 111 112 /* write prompt */ 113 if (write(ofd, message, strlen(message)) < 0) { 114 openpam_log(PAM_LOG_ERROR, "write(): %m"); 115 return (-1); 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 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 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 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