#include "super.h"
#include "version.h"

/* glibc says to use TCSASOFT if it's available. */
#ifndef TCSASOFT
#define TCSASOFT 0
#endif

#ifdef HAVE_TERMIOS_H
    static struct termios old, new;

    #ifdef HAVE_TCGETATTR
    #define Gtty(fd, arg)   tcgetattr(fd, arg)
    #else
    #ifdef TIOCGETA
    #define Gtty(fd, arg)   ioctl(fd, TIOCGETA, arg)
    #else
    #define Gtty(fd, arg)   ioctl(fd, TCGETA, arg)
    #endif
    #endif

    #ifdef HAVE_TCSETATTR
    #define Stty(fd, arg)   tcsetattr(fd, TCSAFLUSH|TCSASOFT, arg)
    #else
    #define Stty(fd, arg)   ioctl(fd, TCSAFLUSH, arg)
    #endif

    /* From Gordon Lack: do not turn off ICANON (erase and kill processing):
     * on some OS's, chars will come through in bunches of 4; and
     * it is incompatible with getpass(), which does erase and kill
     * processing anyway.
     */
    #define ECHO_OFF(tios)  ((tios).c_lflag &= ~ECHO)

#else
#ifdef HAVE_TERMIO_H
    static struct termio old, new;

    #define Gtty(fd, arg)  ioctl(fd, TCGETA, arg)
    #define Stty(fd, arg)  ioctl(fd, TCSETAF, arg)
    #define ECHO_OFF(tio)  ((tio).c_lflag &= ~ECHO)

#else
#ifdef HAVE_SGTTY_H
    static struct sgttyb old, new;

    #define ECHO_OFF(tty)  ((tty).sg_flags &= ~(ECHO))
    #define Gtty(fd, arg)  gtty(fd, arg)
    #define Stty(fd, arg)  stty(fd, arg)

#else
#error "You do not have any of termios.h, termio.h, sgtty.h!"
#endif
#endif
#endif

/* We want to catch signals, so that we can restore terminal
 * settings before responding to signals.
 */
static void (*oldHUP)(), (*oldINT)(), (*oldQUIT)();
static void (*oldALRM)(), (*oldTERM)(), (*oldPIPE)();

static int fdin=0, fdout=1;

static void restoresig();
static void setsighdl();
static void sighdl();

/*
 * In order to stay compatible with (very) old Unixes, we stick
 * with the old signal() interface.  So, it won't do the right thing
 * if multiple signals are received quickly, but that's just tough
 * luck for the (ab)user -- all we really want to do is restore
 * terminal settings when a signal is received, and if a user is
 * trying to be too clever by half with signals, all they will get
 * is an aborted super() call and a non-echoing terminal.
 */

static void 
sighdl(sig)
int sig;
{
    /* Restore everything, then resignal */
    Stty(fdin, &old);
    restoresig();
    kill(getpid(), sig);
}

static void
setsighdl()
{
    oldHUP  = (void (*)()) signal(SIGHUP, sighdl);
    oldINT  = (void (*)()) signal(SIGINT, sighdl);
    oldQUIT = (void (*)()) signal(SIGQUIT, sighdl);
    oldALRM = (void (*)()) signal(SIGALRM, sighdl);
    oldTERM = (void (*)()) signal(SIGTERM, sighdl);
    oldPIPE = (void (*)()) signal(SIGPIPE, sighdl);
}

static void
restoresig()
{
    (void) signal(SIGHUP,  oldHUP);
    (void) signal(SIGINT,  oldINT);
    (void) signal(SIGQUIT, oldQUIT);
    (void) signal(SIGALRM, oldALRM);
    (void) signal(SIGTERM, oldTERM);
    (void) signal(SIGPIPE, oldPIPE);
}

int
readl(fd, buf, buflen)
int fd;
char *buf;
int buflen;	/* must be > 0 */
{
    int n;
    char *s, *end;
    for (s=buf, end=buf+buflen; s < end; s++) {
	n = read(fd, s, 1);
	if (n != 1 || *s == '\n' || *s == '\r') {
	    *s = '\0';
	    return s-buf;
	}
    }
    return s-buf;
}

/*
 * Returns: -1 (and print message) if there was a problem adjusting
 *	tty settings, else number of characters in password.
 */
int
s_getpass(prompt, use_stdin, buf, n_buf)
char *prompt;		/* If !null, printed as prompt.
			 * Uses /dev/tty unless use_stdin is set, or 
			 * if /dev/tty can't be opened, in which case
			 * it uses stderr.
			 */
int use_stdin;		/* Directs input to come from stdin instead of
			 * /dev/tty.
			 */
char *buf;		/* Where the password is placed */
int n_buf;		/* size of buf.  Up to n_buf characters are written
			 * into *buf, and then nul-terminated.
			 */
{
    int nread, gtty_status;

    if (use_stdin || (fdin = open("/dev/tty", O_RDWR)) == -1) {
	fdin = 0;
	fdout = 2;	/* use stderr for prompting when input is stdin */
    } else {
	fdout = fdin;
    }
  
    /*
     * Turn echoing off
     */
    gtty_status = Gtty(fdin, &old);
    new = old;
    ECHO_OFF(new);
    setsighdl();
    if (gtty_status == 0) {
	Stty(fdin, &new);
    }

    if (prompt) {
	write(fdout, prompt, strlen(prompt));
    }

    /*
     * Read password
     */
    nread = readl(fdin, buf, n_buf);
    if (nread >= 0) {
	buf[(nread < n_buf) ? nread : (n_buf-1)] = '\0';
    }
    if (prompt) {
	write(fdout, "\n", 1);
    }

    /*
     * Restore terminal and signal handling.
     */
    if (gtty_status == 0) {
	Stty(fdin, &old);
    }
    if (fdin != 0) {
	close(fdin);
    }
    restoresig();
  
    return nread;
}
