/* 
 * @(#) tty.c  RCS: $Revision: 1.10 $ $Date: 95/03/02 12:21:21 $
 *
 * Functions to handle terminal console I/O:
 *
 * askTty      - emit a prompt and read in a passphrase with echo disabled
 * mkCtrlTty   - make the specified device the controlling terminal
 * setTtyEcho  - enable/disable echoing of typed characters
 * setTtyAscii - Put terminal in ASCII input mode (echo, backspace etc)
 * setTtyBin   - Put terminal into transparent mode (no character interperation)
 * isTty       - query to see if device is a terminal
 * openCtty    - open the controlling terminal
 * restoreTty  - restore the original state of the terminal
 */
#if !defined(_POSIX_SOURCE)
#define  _POSIX_SOURCE
#endif

/*
 * <ioctl.h> is necessary for machines which require it for explicitly making 
 * a tty the controlling terminal.  This isn't very many. Unfortunately, 
 * <ioctl.h> won't export TIOCSCTTY if _POSIX_SOURCE is defined on some
 * machines.  To make this situation even worse, #include files are notoriously
 * sensitve to order; be very careful before changing it.
 */
#if defined(__ALPHA) || defined(__MIPS) || defined(__bsdi__)
#if defined(__MIPS)
#undef _POSIX_SOURCE
#endif
#include <sys/ioctl.h>		/* TIOCNOTTY TIOCSCTTY */
#if defined(__MIPS)
#define _POSIX_SOURCE
#endif
#endif

#include <unistd.h>	/* open ctermid */
#include <stdlib.h>	/* exit */
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>	/* for fcntl.h */

/*
 * BSDI's BSD/386 has no way to #define ONLCR in <termios.h> if__POSIX_SOURCE 
 * is defined.  Also, its value is not 0x04 as it is on all other machines that
 * have this problem.  POSIX really blew it by not having a standard #define to
 * enable non-posix symbol visability.  I can't count on tty drivers enabling 
 * ONLCR when OPOST in enabled either.  I also can't just do this for all 
 * machines because HP-UX will make termios.h empty if I undef _POSIX SOURCE.
 */
#if defined(__bsdi__)
#undef _POSIX_SOURCE
#endif
#include <termios.h>	/* tcsetattr ONLCR */
#if defined(__bsdi__)
#define _POSIX_SOURCE
#endif

#include <fcntl.h>
#include <string.h>	/* memcpy */
#include <errno.h>	/* ENOTTY */
#include "deslogin.h"
#include "log.h"
#include "tty.h"
#include "posignal.h"

extern int debug;

/*
 * Return 1 if the specified fd is associated with a tty device
 *        0 if not,
 *       -1 if failure
 */
int isTty(fd)
   int fd;
{
   struct termios modes;
   register int res = (tcgetattr(fd, &modes) == 0);

   if (res == 0) {
      if (errno != ENOTTY) {
	 --res;
      }
   }

   return res;
}

int openCtty()
{
   char cttyName[L_ctermid];
   int ctty;

   cttyName[0] = '\0';	/* shouldn't be necessary, but I'm paranoid */
   /*
    * Linux fails to declare "exter char *ctermid()".  Add it here if you
    * get a warning.  Linux's <unistd.h> needs to be fixed.
    */
   ctty = open(ctermid(cttyName), O_RDWR);	/* in Posix <unistd.h> */
   if (debug) {
      log("%s: (openCtty) open(ctermid = \"%s\") returned fd %d\n", 
	  progName, cttyName, ctty);
   }
   if (ctty < 0) {
      log("%s: open(\"%s\", O_RDWR) failed -- %s\n", 
	 progName, cttyName, ERRMSG);
   }
   return ctty;
}

int restoreTty(ttyfd, oldmodes)
   int ttyfd;
   struct termios *oldmodes;
{
   int res;

   res = tcsetattr(ttyfd, TCSANOW, oldmodes);
   if (res < 0) {
      log("%s: tcsetattr to fd %d failed -- %s\n", progName, ttyfd, ERRMSG);
      exit(1);
   }
   return 0;
}

/*
 * Set the specified tty device to binary (with error report)
 * Put the original modes in oldmodes.
 */
int setTtyBin(ttyfd, oldmodes)
   int ttyfd;
   struct termios *oldmodes;
{
   struct termios modes;
   int res;

   if (oldmodes != (struct termios *) 0) {
      res = tcgetattr(ttyfd, &modes);
      if (res < 0) {
	 log("%s: tcgetattr on fd %d failed--%s\n", progName, ttyfd, ERRMSG);
	 exit(1);
      }
      memcpy(oldmodes, &modes, sizeof (struct termios));
   }
    
   modes.c_cflag  = CS8 | CREAD;
   modes.c_iflag  = BRKINT;
   modes.c_oflag &= ~OPOST;		/* Don't damage other bits */
   modes.c_lflag  = 0;
   modes.c_cc[VMIN]  = 1;
   modes.c_cc[VTIME] = 0;

   res = cfsetospeed(&modes, B9600);
   if (res < 0) {
      log("%s: unsupported speed -- B9600\n", progName);
      exit(1);
   }

   res = tcsetattr(ttyfd, TCSANOW, &modes);
   if (res < 0) {
      log("%s: tcsetattr to fd %d failed -- %s\n", progName, ttyfd, ERRMSG);
      exit(1);
   }
   return 0;
}

/*
 * Set specified tty to interactive mode
 */
int setTtyAscii(ctty)
   int ctty;
{
   int res;
   struct termios modes;

   res = tcgetattr(ctty, &modes);
   if (res < 0) {
      log("%s: tcgetattr failed --%s\n", progName, ERRMSG);
      exit(1);
   }
    
   modes.c_cflag  = CS8 | CREAD | HUPCL;
   modes.c_iflag  = ICRNL | BRKINT | IXON;
   modes.c_lflag  = ICANON | ECHO | ECHOE | ISIG;
   modes.c_oflag |= OPOST;			
   modes.c_oflag |= ONLCR;	/* ONLCR is not POSIX, but we must set it! */
   modes.c_cc[VERASE] = '\b';
   modes.c_cc[VINTR]   = '\003';

   res = cfsetospeed(&modes, B9600);
   if (res < 0) {
      log("%s: unsupported speed -- B9600\n", progName);
      exit(1);
   }

   res = tcsetattr(ctty, TCSANOW, &modes);
   if (debug > 1) {
      log("%s: tcsetattr returned %d\n", progName, res);
   }
   if (res < 0) {
      log("%s: tcsetattr failed -- %s\n", progName, ERRMSG);
      exit(1);
   }
   return 0;
}

/*
 * Set tty echo on(1) or off(0); returns previous setting or -1
 */
int setTtyEcho(fd, echo)
   int fd;
   int echo;
{
   register int res, oldecho;
   struct termios modes;

   res = tcgetattr(fd, &modes);
   if (res < 0) return -1;
    
   oldecho = ((modes.c_lflag & ECHO) != 0);
   if (echo) {
      modes.c_lflag |= ECHO;
   } else {
      modes.c_lflag &= ~ECHO;
   }

   res = tcsetattr(fd, TCSANOW, &modes);
   if (res < 0) return -2;

   return oldecho;
}

/*
 * Make the specified  fd the controlling terminal.
 *
 * Some machines require an explicit call.  Only the session leader can
 * do this successfully.  Returns: >=0 success, < 0 failure w/errno set.
 * 
 * If flag is non-zero, make fd a controlling terminal, otherwise make it
 * not the controlling terminal.
 */
int mkCtrlTty(fd, flag)
   int fd, flag;
{
   int res = 0;
   pid_t pid;
#if defined(__ALPHA) || defined(__bsdi__)
   /* OSF1(DECalpha)tty(7): 
    * In earlier versions of UNIX systems, a controlling terminal was 
    * implicitly assigned to a process if, at the time an open was done on 
    * the terminal, the terminal was not the controlling terminal for any 
    * process, and if the pro-cess doing the open did not have a controlling 
    * terminal.  In this version of UNIX, in accordance with POSIX 1003.1, 
    * a process must be a session leader to allocate a controlling terminal.
    * In addition, the allocation is now done explicitly with a call to 
    * ioctl().  (This implies that the O_NOCTTY flag to the open() 
    * function is ignored.) 
    */
   if (flag) {
      res = ioctl(fd, TIOCSCTTY,0);
   } else {
      res = ioctl(fd, TIOCNOTTY,0);
   }
#endif
#if defined(__MIPS) /* { */
  /* ULTRIX(MIPS)tty(4):
   * If a process that has no control terminal opens a terminal file, then
   * that terminal file becomes the control terminal for that process.  The
   * control terminal is thereafter inherited by a child process during a
   * fork(2), even if the control terminal is closed.
   */
   if (flag) {
      res = 0;			/* Hack for DEC MIPS Ultrix 4.1.2 and 4.1.3 */
   } else {
      res = ioctl(fd, TIOCNOTTY,0);
   }
#endif /* } */
#if defined(__SOLARIS__)	/* { */
   pid = getpgrp();
   if (pid < 0) {
      log("%s: (mkCtrlTty) getpgrp failed -- %s\n", progName, ERRMSG);
   } else {
      res = ioctl(fd, TIOCSPGRP, &pid);
      if (res < 0) {
	 log("%s: (mkCtrlTty) ioctl(%d, TIOCSPGRP, %d) failed -- %s\n", 
	     progName, fd, pid, ERRMSG);
      }
   }
#endif /* } */
   if (debug > 1) {
      log("%s: (mkCtrlTty)(%d)=%d\n", progName, fd, res);
   }
   return res;
}

int oldEcho = 1;
int ttyfd = -1;

void restoreEcho(sig)
   int sig;
{
   if (ttyfd >= 0) {
      setTtyEcho(ttyfd, oldEcho);
      close(ttyfd);
   }
   exit(0);
}

/*
 * Read in a passphrase terminated by '\n' of upto size characters from the
 * controlling terminal with echo disabled.  The phrase strips the '\n', 
 * and appends a '\0'.  Intercepts interrupt signal and restores echo.
 */
int askTty(prompt, str, size, echo)
   char *prompt;
   char *str;
   unsigned size;
   int echo;
{
   unsigned len = strlen(prompt);
   register int count;
   register char *chp = str;
   register int res = -1;
   char ch; 
   pfv oldsigint;

   ttyfd     = openCtty();
   if (ttyfd == -1) return -1;

   oldsigint = posignal(SIGINT, restoreEcho);
   oldEcho   = setTtyEcho(ttyfd, echo);

   count = write(ttyfd, prompt, len);
   if (count != len) return -1;
   if (size != 0) do {
      count = read(ttyfd, &ch, 1);
      if (count == 0) break;
      if (count != 1) break;
      if (ch == '\n') break;
      *chp++ = ch;
   } while (--size != 0);
   if (count >= 0) {
      res = chp - str;
   }
 
   *chp = '\0';
   if (!echo) {
      count = write(ttyfd, "\n", 1);			/* goto next line */
   }

   oldEcho = setTtyEcho(ttyfd, oldEcho);
   posignal(SIGINT, oldsigint);
   close(ttyfd);
   ttyfd = -1;
   return res;
}
