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