/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
#include "k5-int.h"
#if !defined(_WIN32) || (defined(_WIN32) && defined(__CYGWIN32__))
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <limits.h>
/* Is vxworks broken w.r.t. termios? --tlyu */
#ifdef __vxworks
#define ECHO_PASSWORD
#endif
#include <termios.h>
#ifdef POSIX_SIGNALS
typedef struct sigaction osiginfo;
#else
typedef struct krb5_sigtype (*osiginfo)();
#endif
static void catch_signals(osiginfo *);
static void restore_signals(osiginfo *);
static krb5_sigtype intrfunc(int sig);
static krb5_error_code setup_tty(FILE*, int, struct termios *, osiginfo *);
static krb5_error_code restore_tty(FILE*, struct termios *, osiginfo *);
static volatile int got_int; /* should be sig_atomic_t */
krb5_error_code KRB5_CALLCONV
krb5_prompter_posix(
krb5_context context,
void *data,
const char *name,
const char *banner,
int num_prompts,
krb5_prompt prompts[])
{
int fd, i, scratchchar;
FILE *fp;
char *retp;
krb5_error_code errcode;
struct termios saveparm;
osiginfo osigint;
errcode = KRB5_LIBOS_CANTREADPWD;
if (name) {
fputs(name, stdout);
fputs("\n", stdout);
}
if (banner) {
fputs(banner, stdout);
fputs("\n", stdout);
}
/*
* Get a non-buffered stream on stdin.
*/
fp = NULL;
fd = dup(STDIN_FILENO);
if (fd < 0)
return KRB5_LIBOS_CANTREADPWD;
set_cloexec_fd(fd);
fp = fdopen(fd, "r");
if (fp == NULL)
goto cleanup;
if (setvbuf(fp, NULL, _IONBF, 0))
goto cleanup;
for (i = 0; i < num_prompts; i++) {
errcode = KRB5_LIBOS_CANTREADPWD;
/* fgets() takes int, but krb5_data.length is unsigned. */
if (prompts[i].reply->length > INT_MAX)
goto cleanup;
errcode = setup_tty(fp, prompts[i].hidden, &saveparm, &osigint);
if (errcode)
break;
/* put out the prompt */
(void)fputs(prompts[i].prompt, stdout);
(void)fputs(": ", stdout);
(void)fflush(stdout);
(void)memset(prompts[i].reply->data, 0, prompts[i].reply->length);
got_int = 0;
retp = fgets(prompts[i].reply->data, (int)prompts[i].reply->length,
fp);
if (prompts[i].hidden) {
putchar('\n');
/* Solaris Kerberos */
fflush(stdout);
}
if (retp == NULL) {
if (got_int)
errcode = KRB5_LIBOS_PWDINTR;
else
errcode = KRB5_LIBOS_CANTREADPWD;
restore_tty(fp, &saveparm, &osigint);
break;
}
/* replace newline with null */
retp = strchr(prompts[i].reply->data, '\n');
if (retp != NULL)
*retp = '\0';
else {
/* flush rest of input line */
do {
scratchchar = getc(fp);
} while (scratchchar != EOF && scratchchar != '\n');
}
errcode = restore_tty(fp, &saveparm, &osigint);
if (errcode)
break;
prompts[i].reply->length = strlen(prompts[i].reply->data);
}
cleanup:
if (fp != NULL)
fclose(fp);
else if (fd >= 0)
close(fd);
return errcode;
}
static krb5_sigtype
intrfunc(int sig)
{
got_int = 1;
}
static void
catch_signals(osiginfo *osigint)
{
#ifdef POSIX_SIGNALS
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = intrfunc;
sigaction(SIGINT, &sa, osigint);
#else
*osigint = signal(SIGINT, intrfunc);
#endif
}
static void
restore_signals(osiginfo *osigint)
{
#ifdef POSIX_SIGNALS
sigaction(SIGINT, osigint, NULL);
#else
signal(SIGINT, *osigint);
#endif
}
static krb5_error_code
setup_tty(FILE *fp, int hidden, struct termios *saveparm, osiginfo *osigint)
{
krb5_error_code ret;
int fd;
struct termios tparm;
ret = KRB5_LIBOS_CANTREADPWD;
catch_signals(osigint);
fd = fileno(fp);
do {
if (!isatty(fd)) {
ret = 0;
break;
}
if (tcgetattr(fd, &tparm) < 0)
break;
*saveparm = tparm;
#ifndef ECHO_PASSWORD
if (hidden)
tparm.c_lflag &= ~(ECHO|ECHONL);
#endif
tparm.c_lflag |= ISIG|ICANON;
if (tcsetattr(STDIN_FILENO, TCSANOW, &tparm) < 0)
break;
ret = 0;
} while (0);
/* If we're losing, restore signal handlers. */
if (ret)
restore_signals(osigint);
return ret;
}
static krb5_error_code
restore_tty(FILE* fp, struct termios *saveparm, osiginfo *osigint)
{
int ret, fd;
ret = 0;
fd = fileno(fp);
if (isatty(fd)) {
ret = tcsetattr(fd, TCSANOW, saveparm);
if (ret < 0)
ret = KRB5_LIBOS_CANTREADPWD;
else
ret = 0;
}
restore_signals(osigint);
return ret;
}
#else /* non-Cygwin Windows, or Mac */
#if defined(_WIN32)
#include <io.h>
krb5_error_code KRB5_CALLCONV
krb5_prompter_posix(krb5_context context,
void *data,
const char *name,
const char *banner,
int num_prompts,
krb5_prompt prompts[])
{
HANDLE handle;
DWORD old_mode, new_mode;
char *ptr;
int scratchchar;
krb5_error_code errcode = 0;
int i;
handle = GetStdHandle(STD_INPUT_HANDLE);
if (handle == INVALID_HANDLE_VALUE)
return ENOTTY;
if (!GetConsoleMode(handle, &old_mode))
return ENOTTY;
new_mode = old_mode;
new_mode |= ( ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT );
new_mode &= ~( ENABLE_ECHO_INPUT );
if (!SetConsoleMode(handle, new_mode))
return ENOTTY;
if (!SetConsoleMode(handle, old_mode))
return ENOTTY;
if (name) {
fputs(name, stdout);
fputs("\n", stdout);
}
if (banner) {
fputs(banner, stdout);
fputs("\n", stdout);
}
for (i = 0; i < num_prompts; i++) {
if (prompts[i].hidden) {
if (!SetConsoleMode(handle, new_mode)) {
errcode = ENOTTY;
goto cleanup;
}
}
fputs(prompts[i].prompt,stdout);
fputs(": ", stdout);
fflush(stdout);
memset(prompts[i].reply->data, 0, prompts[i].reply->length);
if (fgets(prompts[i].reply->data, prompts[i].reply->length, stdin)
== NULL) {
if (prompts[i].hidden)
putchar('\n');
errcode = KRB5_LIBOS_CANTREADPWD;
goto cleanup;
}
if (prompts[i].hidden)
putchar('\n');
/* fgets always null-terminates the returned string */
/* replace newline with null */
if ((ptr = strchr(prompts[i].reply->data, '\n')))
*ptr = '\0';
else /* flush rest of input line */
do {
scratchchar = getchar();
} while (scratchchar != EOF && scratchchar != '\n');
prompts[i].reply->length = strlen(prompts[i].reply->data);
if (!SetConsoleMode(handle, old_mode)) {
errcode = ENOTTY;
goto cleanup;
}
}
cleanup:
if (errcode) {
for (i = 0; i < num_prompts; i++) {
memset(prompts[i].reply->data, 0, prompts[i].reply->length);
}
}
return errcode;
}
#else /* !_WIN32 */
krb5_error_code KRB5_CALLCONV
krb5_prompter_posix(krb5_context context,
void *data,
const char *name,
const char *banner,
int num_prompts,
krb5_prompt prompts[])
{
return(EINVAL);
}
#endif /* !_WIN32 */
#endif /* Windows or Mac */
void
krb5int_set_prompt_types(krb5_context context, krb5_prompt_type *types)
{
context->prompt_types = types;
}
krb5_prompt_type*
KRB5_CALLCONV
krb5_get_prompt_types(krb5_context context)
{
return context->prompt_types;
}