/*
 * For license terms, see the file COPYING in this directory.
 */

/***********************************************************************
  module:       getpass.c
  project:      fetchmail
  programmer:   Carl Harris, ceharris@mal.com
  description: 	getpass() replacement which allows for long passwords.
                This version hacked by Wilfred Teiken, allowing the
                password to be piped to fetchmail.

 ***********************************************************************/

#include "config.h"
#include "fetchmail.h"

#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include "i18n.h"

#define INPUT_BUF_SIZE	PASSWORDLEN

#include <termios.h>

static int ttyfd;

static struct termios termb;
static tcflag_t flags;

static void save_tty_state(void);
static void disable_tty_echo(void);
static void restore_tty_state(void);
static void sigint_handler(int);

static char pbuf[INPUT_BUF_SIZE];

void fm_clearpassword(void)
{
    fm_safe_clearmem(pbuf, sizeof(pbuf));
}

char *fm_getpassword(const char *prompt)
{
    char *p;
    FILE *fi = NULL;
    SIGHANDLERTYPE sig = 0;	/* initialization pacifies -Wall */

    int istty = isatty(0);
    ttyfd = STDIN_FILENO;

    /* get the file descriptor for the actual input device if it's a tty */
    if (istty)
    {
        fi = fopen("/dev/tty", "r+");
	if (fi) {
	    setbuf(fi, (char *)NULL);
	    /* store descriptor for the controlling tty */
	    ttyfd = fileno(fi);
	} else {
	    report(stderr, "fm_getpassword: fopen(\"/dev/tty\", \"r+\"): %s\n", strerror(errno));
	}

	/* preserve tty state before turning off echo */
	save_tty_state();

	/* now that we have the current tty state, we can catch SIGINT and
	   exit gracefully */
	sig = set_signal_handler(SIGINT, sigint_handler);

	/* turn off echo on the tty */
	disable_tty_echo();

	/* display the prompt and get the input string */
	fputs(prompt, stderr);
    }

    /* regardless of if the input is a TTY or not, read characters
     * until we have a LF or an EOF, truncating if we reach full
     * buffer size */
    FILE *readstream = fi ? fi : stdin;
    clearerr(readstream);
    p = fgets(pbuf, sizeof(pbuf), readstream);
    int save_errno = errno;
    if (!p && !ferror(readstream)) {
	// fgets() returns NULL if EOF happens early,
	// so don't report error in that case
	p = pbuf;
    }
    // Zap LF:
    (void)strtok(pbuf, "\n");

    if (istty)
    {
	/* write a newline so cursor won't appear to hang */
	fprintf(stderr, "\n");

	/* restore previous state of the tty */
	restore_tty_state();

	/* restore previous state of SIGINT */
	set_signal_handler(SIGINT, sig);

	if (fi) {
	    (void)fclose(fi);	/* not checking should be safe, file mode was "r" */
	}
    }

    errno = save_errno;
    return p;
}

static void save_tty_state (void)
{
    tcgetattr(ttyfd, &termb);
    flags = termb.c_lflag;
}

static void disable_tty_echo(void)
{
    /* turn off echo on the tty */
    termb.c_lflag &= ~ECHO;
    tcsetattr(ttyfd, TCSAFLUSH, &termb);
}

static void restore_tty_state(void)
{
    /* restore previous tty echo state */
    termb.c_lflag = flags;
    tcsetattr(ttyfd, TCSAFLUSH, &termb);
}

static void sigint_handler(int signum)
{
    (void)signum;
    fm_clearpassword();
    restore_tty_state();
    report(stderr, GT_("\nCaught SIGINT... bailing out.\n"));
    exit(1);
}

/* getpass.c ends here */
