191094Sdes/*- 2115619Sdes * Copyright (c) 2002-2003 Networks Associates Technology, Inc. 3228690Sdes * Copyright (c) 2004-2011 Dag-Erling Sm��rgrav 491094Sdes * All rights reserved. 591094Sdes * 691094Sdes * This software was developed for the FreeBSD Project by ThinkSec AS and 799158Sdes * Network Associates Laboratories, the Security Research Division of 899158Sdes * Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 999158Sdes * ("CBOSS"), as part of the DARPA CHATS research program. 1091094Sdes * 1191094Sdes * Redistribution and use in source and binary forms, with or without 1291094Sdes * modification, are permitted provided that the following conditions 1391094Sdes * are met: 1491094Sdes * 1. Redistributions of source code must retain the above copyright 1591094Sdes * notice, this list of conditions and the following disclaimer. 1691094Sdes * 2. Redistributions in binary form must reproduce the above copyright 1791094Sdes * notice, this list of conditions and the following disclaimer in the 1891094Sdes * documentation and/or other materials provided with the distribution. 1991094Sdes * 3. The name of the author may not be used to endorse or promote 2091094Sdes * products derived from this software without specific prior written 2191094Sdes * permission. 2291094Sdes * 2391094Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 2491094Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2591094Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2691094Sdes * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2791094Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2891094Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2991094Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 3091094Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 3191094Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3291094Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3391094Sdes * SUCH DAMAGE. 3491094Sdes * 35271947Sdes * $Id: openpam_ttyconv.c 807 2014-09-09 09:41:32Z des $ 3691094Sdes */ 3791094Sdes 38228690Sdes#ifdef HAVE_CONFIG_H 39228690Sdes# include "config.h" 40228690Sdes#endif 41228690Sdes 4291094Sdes#include <sys/types.h> 43255376Sdes#include <sys/poll.h> 44255376Sdes#include <sys/time.h> 4591094Sdes 46117610Sdes#include <errno.h> 47255376Sdes#include <fcntl.h> 4891100Sdes#include <signal.h> 4991094Sdes#include <stdio.h> 5091094Sdes#include <stdlib.h> 5191094Sdes#include <string.h> 5291094Sdes#include <termios.h> 5391100Sdes#include <unistd.h> 5491094Sdes 5591094Sdes#include <security/pam_appl.h> 5691094Sdes 57107937Sdes#include "openpam_impl.h" 58271947Sdes#include "openpam_strlset.h" 59107937Sdes 6091100Sdesint openpam_ttyconv_timeout = 0; 6191100Sdes 62255376Sdesstatic volatile sig_atomic_t caught_signal; 63255376Sdes 64255376Sdes/* 65255376Sdes * Handle incoming signals during tty conversation 66255376Sdes */ 6791100Sdesstatic void 68255376Sdescatch_signal(int signo) 6991100Sdes{ 70117610Sdes 71255376Sdes switch (signo) { 72255376Sdes case SIGINT: 73255376Sdes case SIGQUIT: 74255376Sdes case SIGTERM: 75255376Sdes caught_signal = signo; 76255376Sdes break; 77255376Sdes } 7891100Sdes} 7991100Sdes 80255376Sdes/* 81255376Sdes * Accept a response from the user on a tty 82255376Sdes */ 83255376Sdesstatic int 84255376Sdesprompt_tty(int ifd, int ofd, const char *message, char *response, int echo) 8591100Sdes{ 86255376Sdes struct sigaction action; 87255376Sdes struct sigaction saction_sigint, saction_sigquit, saction_sigterm; 88255376Sdes struct termios tcattr; 89255376Sdes struct timeval now, target, remaining; 90255376Sdes int remaining_ms; 91255376Sdes tcflag_t slflag; 92255376Sdes struct pollfd pfd; 93255376Sdes int serrno; 94255376Sdes int pos, ret; 95117610Sdes char ch; 9691100Sdes 97255376Sdes /* write prompt */ 98255376Sdes if (write(ofd, message, strlen(message)) < 0) { 99255376Sdes openpam_log(PAM_LOG_ERROR, "write(): %m"); 100255376Sdes return (-1); 101255376Sdes } 102255376Sdes 103255376Sdes /* turn echo off if requested */ 104255376Sdes slflag = 0; /* prevent bogus uninitialized variable warning */ 105255376Sdes if (!echo) { 106255376Sdes if (tcgetattr(ifd, &tcattr) != 0) { 107255376Sdes openpam_log(PAM_LOG_ERROR, "tcgetattr(): %m"); 108255376Sdes return (-1); 109255376Sdes } 110255376Sdes slflag = tcattr.c_lflag; 111255376Sdes tcattr.c_lflag &= ~ECHO; 112255376Sdes if (tcsetattr(ifd, TCSAFLUSH, &tcattr) != 0) { 113255376Sdes openpam_log(PAM_LOG_ERROR, "tcsetattr(): %m"); 114255376Sdes return (-1); 115255376Sdes } 116255376Sdes } 117255376Sdes 118255376Sdes /* install signal handlers */ 119255376Sdes caught_signal = 0; 120255376Sdes action.sa_handler = &catch_signal; 12191100Sdes action.sa_flags = 0; 122255376Sdes sigfillset(&action.sa_mask); 123255376Sdes sigaction(SIGINT, &action, &saction_sigint); 124255376Sdes sigaction(SIGQUIT, &action, &saction_sigquit); 125255376Sdes sigaction(SIGTERM, &action, &saction_sigterm); 126255376Sdes 127255376Sdes /* compute timeout */ 128255376Sdes if (openpam_ttyconv_timeout > 0) { 129255376Sdes (void)gettimeofday(&now, NULL); 130255376Sdes remaining.tv_sec = openpam_ttyconv_timeout; 131255376Sdes remaining.tv_usec = 0; 132255376Sdes timeradd(&now, &remaining, &target); 133255376Sdes } else { 134255376Sdes /* prevent bogus uninitialized variable warning */ 135255376Sdes now.tv_sec = now.tv_usec = 0; 136255376Sdes remaining.tv_sec = remaining.tv_usec = 0; 137255376Sdes target.tv_sec = target.tv_usec = 0; 138255376Sdes } 139255376Sdes 140255376Sdes /* input loop */ 141255376Sdes pos = 0; 142255376Sdes ret = -1; 143255376Sdes serrno = 0; 144255376Sdes while (!caught_signal) { 145255376Sdes pfd.fd = ifd; 146255376Sdes pfd.events = POLLIN; 147255376Sdes pfd.revents = 0; 148255376Sdes if (openpam_ttyconv_timeout > 0) { 149255376Sdes gettimeofday(&now, NULL); 150255376Sdes if (timercmp(&now, &target, >)) 151255376Sdes break; 152255376Sdes timersub(&target, &now, &remaining); 153255376Sdes remaining_ms = remaining.tv_sec * 1000 + 154255376Sdes remaining.tv_usec / 1000; 155255376Sdes } else { 156255376Sdes remaining_ms = -1; 157255376Sdes } 158255376Sdes if ((ret = poll(&pfd, 1, remaining_ms)) < 0) { 159255376Sdes serrno = errno; 160255376Sdes if (errno == EINTR) 161255376Sdes continue; 162255376Sdes openpam_log(PAM_LOG_ERROR, "poll(): %m"); 163117610Sdes break; 164255376Sdes } else if (ret == 0) { 165255376Sdes /* timeout */ 166255376Sdes write(ofd, " timed out", 10); 167255376Sdes openpam_log(PAM_LOG_NOTICE, "timed out"); 168117610Sdes break; 169255376Sdes } 170255376Sdes if ((ret = read(ifd, &ch, 1)) < 0) { 171255376Sdes serrno = errno; 172255376Sdes openpam_log(PAM_LOG_ERROR, "read(): %m"); 173117610Sdes break; 174255376Sdes } else if (ret == 0 || ch == '\n') { 175255376Sdes response[pos] = '\0'; 176255376Sdes ret = pos; 177255376Sdes break; 178117610Sdes } 179255376Sdes if (pos + 1 < PAM_MAX_RESP_SIZE) 180255376Sdes response[pos++] = ch; 181255376Sdes /* overflow is discarded */ 182117610Sdes } 183255376Sdes 184255376Sdes /* restore tty state */ 185255376Sdes if (!echo) { 186255376Sdes tcattr.c_lflag = slflag; 187255376Sdes if (tcsetattr(ifd, 0, &tcattr) != 0) { 188255376Sdes /* treat as non-fatal, since we have our answer */ 189255376Sdes openpam_log(PAM_LOG_NOTICE, "tcsetattr(): %m"); 190255376Sdes } 191117610Sdes } 192255376Sdes 193255376Sdes /* restore signal handlers and re-post caught signal*/ 194255376Sdes sigaction(SIGINT, &saction_sigint, NULL); 195255376Sdes sigaction(SIGQUIT, &saction_sigquit, NULL); 196255376Sdes sigaction(SIGTERM, &saction_sigterm, NULL); 197255376Sdes if (caught_signal != 0) { 198255376Sdes openpam_log(PAM_LOG_ERROR, "caught signal %d", 199255376Sdes (int)caught_signal); 200255376Sdes raise((int)caught_signal); 201255376Sdes /* if raise() had no effect... */ 202255376Sdes serrno = EINTR; 203255376Sdes ret = -1; 204255376Sdes } 205255376Sdes 206255376Sdes /* done */ 207255376Sdes write(ofd, "\n", 1); 208255376Sdes errno = serrno; 209255376Sdes return (ret); 210255376Sdes} 211255376Sdes 212255376Sdes/* 213255376Sdes * Accept a response from the user on a non-tty stdin. 214255376Sdes */ 215255376Sdesstatic int 216255376Sdesprompt_notty(const char *message, char *response) 217255376Sdes{ 218255376Sdes struct timeval now, target, remaining; 219255376Sdes int remaining_ms; 220255376Sdes struct pollfd pfd; 221255376Sdes int ch, pos, ret; 222255376Sdes 223255376Sdes /* show prompt */ 224255376Sdes fputs(message, stdout); 225255376Sdes fflush(stdout); 226255376Sdes 227255376Sdes /* compute timeout */ 228255376Sdes if (openpam_ttyconv_timeout > 0) { 229255376Sdes (void)gettimeofday(&now, NULL); 230255376Sdes remaining.tv_sec = openpam_ttyconv_timeout; 231255376Sdes remaining.tv_usec = 0; 232255376Sdes timeradd(&now, &remaining, &target); 233255376Sdes } else { 234255376Sdes /* prevent bogus uninitialized variable warning */ 235255376Sdes now.tv_sec = now.tv_usec = 0; 236255376Sdes remaining.tv_sec = remaining.tv_usec = 0; 237255376Sdes target.tv_sec = target.tv_usec = 0; 238255376Sdes } 239255376Sdes 240255376Sdes /* input loop */ 241255376Sdes pos = 0; 242255376Sdes for (;;) { 243255376Sdes pfd.fd = STDIN_FILENO; 244255376Sdes pfd.events = POLLIN; 245255376Sdes pfd.revents = 0; 246255376Sdes if (openpam_ttyconv_timeout > 0) { 247255376Sdes gettimeofday(&now, NULL); 248255376Sdes if (timercmp(&now, &target, >)) 249255376Sdes break; 250255376Sdes timersub(&target, &now, &remaining); 251255376Sdes remaining_ms = remaining.tv_sec * 1000 + 252255376Sdes remaining.tv_usec / 1000; 253255376Sdes } else { 254255376Sdes remaining_ms = -1; 255255376Sdes } 256255376Sdes if ((ret = poll(&pfd, 1, remaining_ms)) < 0) { 257255376Sdes /* interrupt is ok, everything else -> bail */ 258255376Sdes if (errno == EINTR) 259255376Sdes continue; 260255376Sdes perror("\nopenpam_ttyconv"); 261255376Sdes return (-1); 262255376Sdes } else if (ret == 0) { 263255376Sdes /* timeout */ 26491100Sdes break; 265255376Sdes } else { 266255376Sdes /* input */ 267255376Sdes if ((ch = getchar()) == EOF && ferror(stdin)) { 268255376Sdes perror("\nopenpam_ttyconv"); 269255376Sdes return (-1); 270255376Sdes } 271255376Sdes if (ch == EOF || ch == '\n') { 272255376Sdes response[pos] = '\0'; 273255376Sdes return (pos); 274255376Sdes } 275255376Sdes if (pos + 1 < PAM_MAX_RESP_SIZE) 276255376Sdes response[pos++] = ch; 277255376Sdes /* overflow is discarded */ 278255376Sdes } 279255376Sdes } 280255376Sdes fputs("\nopenpam_ttyconv: timeout\n", stderr); 281255376Sdes return (-1); 28291100Sdes} 28391100Sdes 284255376Sdes/* 285255376Sdes * Determine whether stdin is a tty; if not, try to open the tty; in 286255376Sdes * either case, call the appropriate method. 287255376Sdes */ 288255376Sdesstatic int 289255376Sdesprompt(const char *message, char *response, int echo) 29091100Sdes{ 291255376Sdes int ifd, ofd, ret; 29291100Sdes 293255376Sdes if (isatty(STDIN_FILENO)) { 294255376Sdes fflush(stdout); 295255376Sdes#ifdef HAVE_FPURGE 296255376Sdes fpurge(stdin); 297255376Sdes#endif 298255376Sdes ifd = STDIN_FILENO; 299255376Sdes ofd = STDOUT_FILENO; 300255376Sdes } else { 301255376Sdes if ((ifd = open("/dev/tty", O_RDWR)) < 0) 302255376Sdes /* no way to prevent echo */ 303255376Sdes return (prompt_notty(message, response)); 304255376Sdes ofd = ifd; 30591100Sdes } 306255376Sdes ret = prompt_tty(ifd, ofd, message, response, echo); 307255376Sdes if (ifd != STDIN_FILENO) 308255376Sdes close(ifd); 30991100Sdes return (ret); 31091100Sdes} 31191100Sdes 31291094Sdes/* 31391100Sdes * OpenPAM extension 31491100Sdes * 31591100Sdes * Simple tty-based conversation function 31691094Sdes */ 31791094Sdes 31891094Sdesint 31991094Sdesopenpam_ttyconv(int n, 32091094Sdes const struct pam_message **msg, 32191094Sdes struct pam_response **resp, 32291094Sdes void *data) 32391094Sdes{ 324255376Sdes char respbuf[PAM_MAX_RESP_SIZE]; 325123394Sdes struct pam_response *aresp; 32691100Sdes int i; 32791094Sdes 328107937Sdes ENTER(); 329107937Sdes (void)data; 33091094Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 331107937Sdes RETURNC(PAM_CONV_ERR); 332123394Sdes if ((aresp = calloc(n, sizeof *aresp)) == NULL) 333107937Sdes RETURNC(PAM_BUF_ERR); 33491094Sdes for (i = 0; i < n; ++i) { 335123394Sdes aresp[i].resp_retcode = 0; 336123394Sdes aresp[i].resp = NULL; 33791094Sdes switch (msg[i]->msg_style) { 33891094Sdes case PAM_PROMPT_ECHO_OFF: 339255376Sdes if (prompt(msg[i]->msg, respbuf, 0) < 0 || 340255376Sdes (aresp[i].resp = strdup(respbuf)) == NULL) 34191100Sdes goto fail; 34291100Sdes break; 34391094Sdes case PAM_PROMPT_ECHO_ON: 344255376Sdes if (prompt(msg[i]->msg, respbuf, 1) < 0 || 345255376Sdes (aresp[i].resp = strdup(respbuf)) == NULL) 34691094Sdes goto fail; 34791094Sdes break; 34891094Sdes case PAM_ERROR_MSG: 34991094Sdes fputs(msg[i]->msg, stderr); 35094735Sdes if (strlen(msg[i]->msg) > 0 && 35194735Sdes msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n') 35294735Sdes fputc('\n', stderr); 35391094Sdes break; 35491094Sdes case PAM_TEXT_INFO: 35591094Sdes fputs(msg[i]->msg, stdout); 35694735Sdes if (strlen(msg[i]->msg) > 0 && 35794735Sdes msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n') 35894735Sdes fputc('\n', stdout); 35991094Sdes break; 36091094Sdes default: 36191094Sdes goto fail; 36291094Sdes } 36391094Sdes } 364123394Sdes *resp = aresp; 365255376Sdes memset(respbuf, 0, sizeof respbuf); 366107937Sdes RETURNC(PAM_SUCCESS); 367228690Sdesfail: 368122912Sdes for (i = 0; i < n; ++i) { 369123394Sdes if (aresp[i].resp != NULL) { 370271947Sdes strlset(aresp[i].resp, 0, PAM_MAX_RESP_SIZE); 371123394Sdes FREE(aresp[i].resp); 372117610Sdes } 373122912Sdes } 374123394Sdes memset(aresp, 0, n * sizeof *aresp); 375123394Sdes FREE(aresp); 376123394Sdes *resp = NULL; 377255376Sdes memset(respbuf, 0, sizeof respbuf); 378107937Sdes RETURNC(PAM_CONV_ERR); 37991094Sdes} 38091100Sdes 38191100Sdes/* 38291100Sdes * Error codes: 38391100Sdes * 38491100Sdes * PAM_SYSTEM_ERR 38591100Sdes * PAM_BUF_ERR 38691100Sdes * PAM_CONV_ERR 38791100Sdes */ 38897241Sdes 38997241Sdes/** 39097241Sdes * The =openpam_ttyconv function is a standard conversation function 391141098Sdes * suitable for use on TTY devices. 392141098Sdes * It should be adequate for the needs of most text-based interactive 393141098Sdes * programs. 39497241Sdes * 39597241Sdes * The =openpam_ttyconv function allows the application to specify a 396115619Sdes * timeout for user input by setting the global integer variable 39797241Sdes * :openpam_ttyconv_timeout to the length of the timeout in seconds. 39897241Sdes * 39997241Sdes * >openpam_nullconv 40097241Sdes * >pam_prompt 40197241Sdes * >pam_vprompt 40297241Sdes */ 403