/*
 * Copyright 1994 David A. Barrett.
 *
 * Remote login deaemon with authentication and data encryption of channel
 *
 * Revision History:
 *
 *    12-May-94:  Fixed two malloc bugs in gendeskd.c and deslogind.c.
 *    14-Jun-94:  
 *        deslogind.c:	Added -w option for login wrapper feature
 *                      -w option busted on HP-UX; wait for help.
 *                      Added RHOSTNAME environment variable.
 *        utmp.c:	added update of wtmp to writeut
 *        log.c:	ifdef'd return result from openlog
 *        socket.c:	errno.h inside #undef posix for ENOTSOCK (BSD/386)
 *        tty.c:	added control terminal setting for BSD/386
 *        changed "psignal" to "posignal" to avoid BSD name conflict
 *        added changes for BSDI BSD/386 
 *        changed include <ctype.h> before <stdlib.h> for BSD/386
 *        Thanks to Tom Markson <tom@twilight.com> for BSD/386 changes.
 *    24-Jan-95: Added WRITE_RETRY_TIME to txfr.c to fix linux lost connections
 *        
 * Still to do:
 *   Make the -w option work for HP-UX
 *   Add '.' notation to hostname stuff on socket.c to allow IP addr to be used
 *   figure out a way to run commands and pipelines:
 *      it requires a way to make sure the pipes are flushed before exit
 *      it may require detection of EOF across network
 *   Add ip address to utmp entry for system V. fix in socket.c.
 *   On hpux, can't reuse port numbers for a short while after exit.  Why?
 *   Check permissions on deslogin.users, and deslogin.log; issue warning.
 *   Encrypt the log files too.
 *   Catch all Coredump signals to prevent hostile user from signaling
 *      us and examining the carcass for keys (deslogind, deslogingw).
 *   How do you prevent a debugger from doing a ptrace(PT_ATTACH)? (cant!)
 *   Compression (tricky because you must not impose delays)
 *   Allow socket to be connected directly to resulting process 
 *       (won't happen because the parent is needed to encipher the data stream)
 *   Figure out a way to improve protection from local users:
 *      X11 server DISPLAY variable:
 *         :0 still doesn't help because that creates a unix-domain socket:
 *         named /usr/spool/sockets/X11/0 
 *
 *   Deslogin:
 *      Make sure that getPassphrase is not reading from unsecure device:
 *         check $DISPLAY envt for 'local:' or ':' as prefix
 *         check utmp to make sure not rlogin from somewhere else
 *         permissions on Ctermid?  Trojan Ctermid?
 *      Protect against trojan of deslogin itself.
 *
 * Enhancements:
 *     See rshd man page for internet socket login protocol.  
 *        Secondary socket for stderr ASCII port # '\0' terminated 0=none
 *        upto 16 character server user name '\0' terminated
 *        upto 16 character client user name '\0' terminated
 *        command to execute limited by system's argument list
 *        Server authenticates channel and sends a null byte to stderr sock
 *           errors are sent with a '\1' as leading byte with mesage.
 *     Notice that hpux rlogind man page describes a different protocol also.
 *     Add passphrase command
 *
 * Inetd notes:
 *    Umask and uid inherited from init and envt
 *    Socket is available as stdin and stdout
 *
 * Notes:
 *    I have set posignal so that SIGSTOP will not cause SIGCHLD.  This allows
 *    you to STOP a potential intruder in his tracks by sending SIGSTOP to
 *    the process and then attach a debugger to it and examine what is going
 *    on.
 *
 * Comments which Give a Section number and line number correspond to
 * quotes from IEEE Std 1003.1-1990 (ISO/IEC 9945-1: 1990).  
 * (E.g. Sect5:10-12 means Section 5 lines 10 through 12 inclusive)
 */
#define _POSIX_SOURCE
/*
 * This is a mess.  HP-UX ANSI C won't define pid_t in <sys/types.h> unless
 * _POSIX_SOURCE is defined.  Other machines may refuse to define it.  Pick
 * what's appropriate.  <utmp.h> uses pid_t.  On __SOLARIS__, <utmpx.h>
 * uses (struct timeval) which won't be defined unless !_POSIX_SOURCE.
 */
#if defined(__SOLARIS__) /* { */
#undef _POSIX_SOURCE	/* allows struct timeval to be defined */
#undef _POSIX_C_SOURCE
#include <sys/types.h>	/* gid_t pid_t uid_t clock_t */
#include <time.h>	/* time (also needed for "utmp.h") */
#define _POSIX_SOURCE
#define _POSIX_C_SOURCE
#else  /* } { */
#include <sys/types.h>	/* gid_t pid_t uid_t clock_t */
#include <time.h>	/* time (also needed for "utmp.h") */
#endif /* } */

#include <sys/times.h>	/* struct tms */
			/* mips fails to define prototype for times() unless */
			/* __POSIX is defined.  Strange.  */
#include <unistd.h> 	/* chdir close dup execve fork */
			/* getlogin getpid getppid getpgrp */
			/* setsid setgid setpgid setuid ctermid */
			/* _SC_NGROUPS_MAX */
#include <limits.h>	/* NGROUPS_MAX */
#include <ctype.h>	/* must be before stdlib.h for BSDI's BSD 386 */
#include <stdlib.h>	/* atexit exit free malloc */
#include <stdio.h>	/* (fopen fclose fgets scanf printf etc.) */
#include <string.h>	/* memcmp memcpy memset */
#include <errno.h>
#include <sys/stat.h>	/* umask flags */
#include <fcntl.h>
#include <termios.h>
#include <signal.h>
#include <pwd.h>
#include <sys/wait.h>	/* WNOHANG */
#include "pty.h"
#include "tty.h"
#include "auth.h"
#include "txfr.h"
#include "socket.h"
#include "log.h"
#include "deslogin.h"
#include "posignal.h"
#include "utmp.h"		/* setlogin */
#include "group.h"		/* getLoginGroups */

#if defined(sun)
#define BSD
#endif
#if defined(mips)
#define BSD
#endif

/*
 * Begin customizable area
 */
#if !defined(DEFAULT_UMASK)	/* login user mask */
#define DEFAULT_UMASK	S_IWGRP|S_IWOTH
#endif
#if !defined(TTY_MODE)	/* login permissions for controlling terminal */
#define TTY_MODE   	(S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH)
#endif

#define TTY_RWANY   	(S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)

/*
 * These environment variables may be overwridden in the makefile
 */
#if defined(BSD)		/* sun dec mips */
#if !defined(USERPATH)
#define USERPATH	":/usr/ucb:/bin:/usr/bin"
#endif
#if !defined(SYSTEMPATH)
#define SYSTEMPATH	"/etc:/usr/ucb:/bin:/usr/bin"
#endif
#endif
#if defined(hpux)		/* hpux only */
#define DEFAULT_MAIL	"/usr/mail/"
#endif

#if !defined(USER_ENV)	/* Additional envt variable to store userName */
#define USER_ENV	"USER"		/* BSD (sun dec mips) xdm (not hpux) */
#endif
#if !defined(USERPATH)
#define USERPATH	":/bin:/usr/bin"
#endif
#if !defined(SYSTEMPATH)
#define SYSTEMPATH	"/etc:/bin:/usr/bin"
#endif
#if !defined(DEFAULT_TERM)
#define DEFAULT_TERM	"network"
#endif

#if !defined(USER_FILE)
#define USER_FILE	"/usr/local/etc/deslogind.users"
#endif

#if !defined(NOLOGIN_FILE)
#define NOLOGIN_FILE	"/etc/nologin"
#endif
#if !defined(LOG_FILE)
#define LOG_FILE	"/usr/adm/deslogind.log"
#endif

/*
 * End of customizable area
 */
#define PROGNAME_SIZE	255
#define USERNAME_SIZE	17	/* assumption based on rshd man page */

#define MAX_HOSTNAME	255

extern int optind;
extern char *optarg;

#if defined(__STDC__) || defined(__cplusplus)
extern int askTty(char *, char *, unsigned, int);
#else
extern int askTty();
#endif

extern unsigned long defaultKey[64];

static char ident[] = 
" @(#) deslogind.c  version 1.32 03-Mar-95 Copyright 1995 by Dave Barrett(barrett@asgard.cs.Colorado.EDU)\n";
static char RCSid[] = 
" @(#) deslogind.c  $Revision: 1.13 $ $Date: 95/03/08 01:13:14 $\n";

char mainProgName[PROGNAME_SIZE];

char *progName, *argcName;
int  debug   = 0;
int  verbose = 0;

unsigned long inBytes  = 0;	/* for txfr */
unsigned long outBytes = 0;

struct termios oldmodes;

char *defaultShell = "/bin/ksh";

char *defaultArgv[] = {
   (char *) 0,			/* substituted with arg0 variable later */
   (char *) 0,			/* if -w, then substitudet with loginArg1 */
   (char *) 0,			/* if -w, then remote host name */
   (char *) 0
};

/*
 * Static data for -w option 
 */
char loginArg1[] = "-h";
char loginProg[] = LOGIN_PROG;
char loginCwd[]  = "/";

void setProgName() {
   pid_t pid = getpid();				/* can't fail */
   sprintf(mainProgName, "%s[%lu]", argcName, (unsigned long) pid);
   progName = mainProgName;
}

void sigHandler(sig)
   int sig;
{
   switch (sig) {
   case SIGHUP:
   case SIGTERM:
   case SIGINT:
      log("%s: Terminated by signal %d\n", progName, sig);
      exit(1);			/* make sure cleanup occurs on interrupt */
      break;
   case SIGCHLD: 			/* child died */
      if (debug) {
	 log("%s: (main) SIGCHLD\n", progName);
      }
      break;
   default:
      break;
   }
}

char sname[PTY_NAMESIZE]; /* slave pty opened for shell control terminal */
char userName[USERNAME_SIZE];   	/* login name of user */
char rhostName[MAX_HOSTNAME];		/* remote login host */
int chldStat = 0;

/*
 * Registered with atexit
 */
void eraselogin()
{
   int res;

   res = setlogin(sname, userName, rhostName, &chldStat);
   if (res < 0) {
      log("%s: WARNING--eraselogin \"%s\" failed--%s\n", 
	 progName, sname, ERRMSG);
   }
   res = chmod(sname, TTY_RWANY);
   if (res < 0) {
      log("%s: WARNING--chmod \"%s\" to %o failed--%s\n",
	 progName, sname, TTY_RWANY, ERRMSG);
   }
}


/*
 * Duplicate the given file descriptor to the given mode with error check.
 */
int dupFd(fd, name, mode)
   int fd;
   char *name;
   mode_t mode;
{
   int newfd, res;

   newfd = dup(fd);
   if (newfd < 0) {
      log("%s: dup of fd %d (\"%s\") failed--%s\n",
	 progName, fd, name, ERRMSG);
      exit(1);
   }
   res = fcntl(newfd, F_SETFL, mode);
   if (res < 0) {
      log("%s: fcntl of fd %d (\"%s\") failed--%s\n", 
	 progName, newfd, name, ERRMSG);
      exit(1);
   }
   return newfd;
}

/*
 * Memory allocation with error check and abort
 */
void *allocate(size)
   register unsigned size;
{
   register void *res = (void *) malloc(size);

   if (res == (void *) 0) {
      log("%s: allocate of %u bytes failed\n", progName, size);
      exit(1);
   }
   return res;
}

/*
 * Create a copy of the input string. (0 if out of memory)
 */
char *newString(chp)
   register char *chp;
{
   register unsigned len = 0;
   register char *res;

   if (chp != (char *) 0) {
      len = strlen(chp);
   }

   res = (char *) allocate(len+1);
   if (res != (char *) 0) {
      memcpy(res, chp, len);
      res[len] = '\0';
   }
   return res;
}

void showEnv(prefix, envp)
   char *prefix;
   register char **envp;
{
   while (*envp != (char *) 0) {
      log("%s%s\n", prefix, *envp++);
   }
   log("\n");
}

/*
 * Add a new entry to the given environment.  Return the new environment.
 * Set input ep to NULL to create initial invironment
 * Frees the old environment.
 *
 * If out of memory occurs, 0 is returned
 */
char **updateEnv(ep, key, value)
   char **ep;
   char *key;
   char *value;
{
   register char **chpp = ep, **dstp = (char **) 0, **res;
   char *newEntry = (char *) 0;
   unsigned int envsize = 0, keylen = 0, vallen = 0;

   if (key != (char *) 0) {
      keylen = strlen(key);
   }
   if (ep != (char **) 0) {
      while (*chpp != (char *) 0) {
	 if (keylen != 0 && strncmp(key, *chpp, keylen) == 0) {
	    dstp = chpp;
	 }
	 chpp++;
      }
      envsize = chpp - ep;
   }

   if (keylen != 0 && value != (char *) 0) {
      vallen = strlen(value);
      newEntry = (char *) allocate(keylen+vallen+2);
      if (newEntry == (char *) 0) {
	 return (char **) 0;
      }
      memcpy(newEntry, key, keylen);
      memcpy(newEntry + keylen, "=", 1);
      memcpy(newEntry + keylen + 1, value, vallen);
      newEntry[keylen + vallen + 1] = '\0';
   }

   if (dstp != (char **) 0) {
      if (newEntry != (char *) 0) {	/* replace the entry */
	 free(dstp);
	 *dstp = newEntry;
	 return ep;
      } else {				/* delete the entry */
	 *dstp = ep[envsize-1];
	 free(ep[envsize-1]);		/* could cause problems */
	 ep[envsize-1] = (char *) 0;
	 return ep;
      }
   }

   if (newEntry != (char *) 0) {
      envsize++;
   }

   res = (char **) allocate((envsize + 1) * sizeof (char *));
   if (res == (char **) 0) {
      if (newEntry != (char *) 0) {
	 free(newEntry);
      }
      return (char **) 0;
   }

   dstp = res;
   if (ep != (char **) 0) {
      chpp = ep;
      while (*chpp != (char *) 0) {
	 *dstp++ = *chpp++;
      }
   }
   *dstp = (char *) 0;

   if (newEntry != (char *) 0) {
      *dstp++ = newEntry;
   }
   *dstp++ = (char *) 0;

   if (ep != (char **) 0) {
      free(ep);
   }

   return res;
}

char  **setupEnv(userName, shell, cwd, path, tz, rhostName)
   char *userName, *cwd, *shell, *path, *tz, *rhostName;
{
   register char **envp = updateEnv((char **) 0, (char *) 0, (char *) 0);
#if defined(DEFAULT_MAIL)
   char envLine[1024];
#endif

   envp = updateEnv(envp, "HOME", 	cwd);		/* sun POSIX xdm */
   envp = updateEnv(envp, "PATH", 	path);		/* sun POSIX xdm */
   envp = updateEnv(envp, "LOGNAME", 	userName);	/* sun POSIX */
#if defined(USER_ENV)					/* BSD xdm */
   envp = updateEnv(envp, USER_ENV, 	userName);	
#endif
   envp = updateEnv(envp, "SHELL", 	shell);		/* sun xdm */
   if (tz != (char *) 0) {
      envp = updateEnv(envp, "TZ", 	tz);		/* POSIX */
   }
   envp = updateEnv(envp, "TERM",	DEFAULT_TERM);	/* sun POSIX */

#if defined(DEFAULT_MAIL)				/* hpux */
   strcpy(envLine, DEFAULT_MAIL);
   strcat(envLine, userName);
   envp = updateEnv(envp, "MAIL", 	envLine);
#endif

   /*
    * Add this environment variable to help users adjust their own login
    * scripts to a given remote enviroment and to detect remote logins
    * via this program.
    */
   envp = updateEnv(envp, "RHOSTNAME", 	rhostName);	/* deslogin feature */
   /* 
    * rlogin propigates TERM, baud rate, changes to window sizes
    */
   return envp;
}

/*
 * Get information about the named user.  Returns -1 for failure, 0 for success
 */
int getUserInfo(userName, shell, cwd, uid, gid)
   char *userName;
   char **shell;
   char **cwd;
   uid_t *gid;
   uid_t *uid;
{
   register struct passwd *pwentp;
   register int res = -1;

   pwentp = getpwnam(userName);
   if (pwentp != (struct passwd *) 0) {
      *uid   = pwentp->pw_uid;
      *gid   = pwentp->pw_gid;

      *cwd   = newString(pwentp->pw_dir);
      *shell = newString(pwentp->pw_shell);
      res    = 0;
   }
   return res;
}

/*
 * Return CPU time consumed by this process and it's children in 1/10 seconds.
 *
 * Processes run by the shell "time" command will not be accumulted.
 */
long cpuTime()
{
   struct tms pt;
   clock_t curTime;
   long res;

   curTime = times(&pt);
   res = pt.tms_utime + pt.tms_cutime + pt.tms_stime + pt.tms_cstime;
   res = (res * 10) / sysconf(_SC_CLK_TCK);
   return res;
}

/*
 * Get and validate the userfile cipher key.  Returns the binary key or -1.
 *
 * If noUserKey is 1, then userKey is 0, and the userFile is not encrypted.  
 * Otherwise, it's expected to have been encrypted with the cipher program.
 */
keyType getUserFileKey(userFile, userPhrase, noUserKey) 
   char *userFile;
   char *userPhrase;
   int noUserKey;
{
   int res;
   char passPhrase[PHRASE_SIZE];
   keyType userKey = 0;

   if (noUserKey && userPhrase) {
      log("%s: can't use -n option if UserFile cipher key given\n", progName);
      return (keyType) -1;
   }
   if (!noUserKey) {
      if (userPhrase == (char *) 0) {	/* user for passphrase if none given */
	 res = askTty("UserFile cipher key: ", passPhrase, PHRASE_SIZE-1, 0);
	 if (res < 0) {
	    log("%s: couldn't get key\n", progName);
	    return (keyType) -1;
	 }
	 userPhrase = passPhrase;
      }
      if (userPhrase == (char *) -1) {
	 userKey = (keyType) &defaultKey[0];	/* use compile-time key */ 
      } else {
	 userKey = mkKey(userPhrase);
	 memset(userPhrase, '\0', strlen(userPhrase));	/* possibly optarg */
	 if (userKey == (keyType) 0) {
	    log("%s: couldn't make key\n", progName);
	    return (keyType) -1;
	 }
      }
   }
   /*
    * Make userfile exists and the correct key was specified
    */
   res = getUserPhrase(userFile, passPhrase, PHRASE_SIZE, "#", userKey);
   if (res == -1) {
      log("%s: cannot open \"%s\" for user database\n", progName, userFile);
      return (keyType) -1;
   }
   if (res == -2) {
      log("%s: incorrect decryption key for user database \"%s\"\n", 
	 progName, userFile);
      return (keyType) -1;
   }
   return userKey;
}

int handleSignals() {
   register char *chp;

   /*
    * To make sure we cleanup utmp with atexit if we're aborted
    */
   chp = (char *) posignal(SIGINT, sigHandler);
   if (chp == (char *) SIG_ERR) {
      log("%s: sigaction SIGINT failed--%s\n", progName, ERRMSG);
      return -1;
   }
   /*
    * Sent by kill command with no arguments
    */
   chp = (char *) posignal(SIGTERM, sigHandler); 
   if (chp == (char *) SIG_ERR) {
      log("%s: sigaction SIGTERM failed--%s\n", progName, ERRMSG);
      return -1;
   }
   /*
    * Controlling terminal hangup, or death of controlling process
    */
   chp = (char *) posignal(SIGHUP, sigHandler); 
   if (chp == (char *) SIG_ERR) {
      log("%s: sigaction SIGHUP failed--%s\n", progName, ERRMSG);
      return -1;
   }
   return 0;
}

/*
 * Became a deamon
 */
pid_t becomeDaemon() {
   pid_t pid = getpid();

   if (debug < 2) {	/* deamon: disassociate from controlling terminal */
      pid = fork();			/* make pgid != pid for setsid() */
      if (pid < 0) {
	 log("%s: unable to fork as daemon--%s\n", progName, ERRMSG);
	 return -1;
      }
      if (pid > 0) {			/* the parent is finished */
	 exit(1);
      }
      /* child */

      setProgName(); 		
      pid = setsid();		/* break control terminal affiliation */
      if (pid == -1) {
	 log("%s: setsid failed--%s\n", progName, ERRMSG);
	 return -1;
      }
      /*
       * We may still have a problem here.  When the child dies, we will 
       * create a zombie which will have to be waited for by the parent!
       * Since we're a session leader, presumably that parent is now init.
       */
   }
   return pid;
}

/*
 * Negotiate protocol with remote site.  
 * Return the selected protocol or 0 for failure.
 */
char *serverHandshake(nfd, rhostName, rport, timeout)
   int nfd;
   char *rhostName;
   int rport;
   unsigned timeout;
{
   int len, res, count;
   static char protover[VERS_SIZE];

   len = strlen(PROTOCOL_VERS);
   res = write(nfd, PROTOCOL_VERS, len+1);
   if (debug) {
      log("%s: server protocol \"%-.*s\"\n", progName, len, PROTOCOL_VERS);
   }
   if (res < 0) {
      log("%s: write (%d, \"%-.*s\", %d) failed--%s\n", 
	 progName, nfd, len, PROTOCOL_VERS, len+1, ERRMSG);
      return 0;
   }
   count = getString(nfd, protover, VERS_SIZE-1, timeout);
   if (count == 0) {
      log("%s: NOPROTO %s:%d\n", progName, rhostName, rport);
      return 0;
   }
   if (debug) {
      log("%s: client protocol \"%-.*s\"\n", progName, VERS_SIZE-1, protover);
   }
   if (protover[0] != PROTOCOL_VERS[0]) {
      log("%s: BADPROTO(%-.*s) %s:%d\n",
       progName, count, protover, rhostName, rport);
      return 0;
   }
   return &protover[0];
}

/*
 * Set the supplementary group id's for the specified user with error checking
 *
 * Returns: 0 success, -1 if failure (warning issued)
 */
int setupGroups(userName) 
   char *userName;
{
   int res = -1, ngroups, groups;	/* number of supplementary group ids */
   gid_t *gidset;

   /*
    * Supplementary group ids are inherited from the parent.  They
    * should be changed if they exist.  Supplementary group id's are
    * checked against the target gid of chmod(), chown().
    */
   ngroups = (int) sysconf(_SC_NGROUPS_MAX);
   if (ngroups >= 0) {
      gidset = (gid_t *) malloc((ngroups+1) * sizeof (gid_t));
      if (gidset == (gid_t *) 0) {
	 log(
      "%s: WARNING--couldn't allocate %d entry supplementary group list\n",
	    progName, ngroups);
      } else {
	 groups = getLoginGroups(ngroups, gidset, userName);
	 if (groups < 0) {
	    log("%s: WARNING--getLoginGroups(%d, %s) failed--%s\n", 
	       progName, ngroups, userName, ERRMSG);
	 } else {
	    if (groups > ngroups) {
	       log(
	       "%s: WARNING--only first %d of %d supplementary groups set\n",
		  progName, ngroups, res, userName);
	       groups = ngroups;
	    }
	    res = setGroups(groups, gidset);
	    if (res < 0) {
	       log("%s: WARNING--setgroups(%d) failed--%s\n", 
		  progName, groups, ERRMSG);
	    }
	 }
	 free(gidset);
      }
   }
   return res;
}


/*
 * Set the user's uid, gid, and supplementary gid's  which determine
 * the capabilities for this process.
 */
void setupUser(uid, gid, userName)
   uid_t uid;
   gid_t gid;
   char *userName;
{
   int res;

   res = setgid(gid);		/* must setgid before setuid */
   if (res != 0) {
      log("%s: setgid to %d failed--%s\n", progName, gid, ERRMSG);
      exit(1);
   }
   /*
    * Supplementary group ids are inherited from the parent.  They
    * should be changed if they exist.  Supplementary group id's are
    * checked against the target gid of chmod(), chown().
    */
   res = setupGroups(userName);
   /* 
    * If res < 0, setupgroups failed, but that's OK, warning issued.
    */
   /* 
    * We may lose the ability to update the log file with log() after this
    */
   res = setuid(uid);
   if (debug > 1) {
      log("%s: setuid:  returned %ld\n", progName, (long) res);
   }
   if (res != 0) {
      log("%s: setuid to %d failed--%s\n", progName, uid, ERRMSG);
      exit(1);
   }
}

int main(argc, argv)
   int argc;
   char *argv[];
{
   int ch, count, sin, sout, serr, res, ttyfd, noUserKey = 0; 
   long cpuUsed;
   char *chp, *shell, *cwd, *arg0, *tz, *protocol;
   char *path = USERPATH, *userFile = USER_FILE, *logName = LOG_FILE, buf[2];
   char **cargv = defaultArgv, **cenv = (char **) 0;
   char ruser[USERNAME_SIZE], passPhrase[PHRASE_SIZE];
   char *userPhrase = (char *) 0, *generic = (char *) 0;
   char mname[PTY_NAMESIZE];
   unsigned port = 0, rport = 0;
   unsigned connTimeout  = 1000 * INACTIVITY_TIMEOUT;
   unsigned loginTimeout = 1000 * LOGIN_TIMEOUT;	/* login timer */
   unsigned bufSize = 128;			/* connection bufsize */
   int      masterpty = -1, slavepty = -1, nfd = -1, pty = 1, stype;
   int 	    pipe0[2], pipe1[2], pipe2[2];
   mode_t   newmask = DEFAULT_UMASK, oldmask;
   pid_t    pid, pid_res;
   uid_t    uid;
   gid_t    gid;
   time_t   loginTime;
   keyType  key = (keyType) 0, userKey;

   argcName = *argv;
   if ((chp = strrchr(argcName, '/')) != (char *) 0) argcName = chp + 1;
   progName = argcName;

   stype = issocket(0);
   if (stype > 0) {		/* if we're invoked from inetd */
      res = openLog(logName);
      if (res < 0) {
	 _exit(1);
      }
   }

   while ((ch = getopt(argc, argv, "w:k:Pl:t:i:f:p:dvnc")) != EOF) switch (ch) {
   case 'c': 				   /* user compile'd userFile key */
      userPhrase = (char *) -1;
      break;
   case 'k': 				   /* user file cipher key */
      userPhrase = optarg;
      break;
   case 'n': 				   /* no user file cipher key */
      noUserKey++;
      break;
   case 'P':
      pty = 0;				   /* pipeline instead of pty */
      break;
   case 'l':
      logName = optarg;
      break;
   case 'i':					/* idle timeout */
      count = sscanf(optarg, "%u", &connTimeout);	/* in seconds */
      if (count != 1) goto usage;
      connTimeout *= 1000;
      break;
   case 't':					/* passphrase timeout */
      count = sscanf(optarg, "%u", &loginTimeout);	/* in seconds */
      if (count != 1) goto usage;
      loginTimeout *= 1000;
      break;
   case 'f':					/* set userfile */
      userFile = optarg;
      break;
   case 'p':					/* bind to port # */
      count = sscanf(optarg, "%u", &port);
      if (count != 1) goto usage;
      break;
   case 'w':				/* function as wrapper for login */
      generic = optarg;	 /* name in userfile with system-wide passphrase */
      break;
   case 'v':
      verbose++;
      break;
   case 'd':
      debug++;
      break;
   default:
usage:	log(
"usage: %s  [-dn] [-w name ] [-i inactiveSecs ] [-t loginSecs ] [-l logfile ] [-f userfile ] [-k userfileCipherKey ] [-p port ]\n", 
progName);
      return 1;
   }
   argc -= optind;
   argv += optind;

   if (debug > 1) {
      log("%s: issocket(0) returned %d\n", progName, stype);
   }
   userKey = getUserFileKey(userFile, userPhrase, noUserKey);
   if (userKey == (keyType) -1) {
      return 1;
   }

   /*
    * Open log *after* options parsed and some simple error checking,
    * and *before* closing fd's 0,1, and 2.
    */
   res = openLog(logName);		/* leaves existing log if failed */
   if (res < 0) { 
      log("%s: couldn't open logfile \"%s\"--%s\n", progName, logName, ERRMSG);
      return 1;
   }
   setProgName();
   res = handleSignals();		/* handle quit attempts */
   if (res < 0) {
      return 1;
   }
   /*
    * The remote host name is not trustworthy for several reasons.
    * We don't use it (or the remote IP address) for authentication.
    * Relying on the Domain Name System, or the remote IP address
    * is not something I'm willing to trust.  Here, we use it strictly
    * for logging, and informational purposes (who, RHOSTNAME, etc).
    */
   if (getRemoteAddr(0, rhostName, sizeof rhostName, &rport) == 0) {
      nfd = 0; 			/* From inetd.  0 and 1 is our socket. */
      close(2);		/* inetd leaves it open */
   } else {			/* Interactive invocation */
      if (port == 0) {
	 port = getServicePort(SERVICE_NAME);
	 if ((int) port < 0) {
	    port = DESLOGIN_PORT;
	    log(
	 "%s: WARNING--no \"%s\" service; using port %u\n", 
	       progName, SERVICE_NAME, port);
	 }
      }
      log("%s: Starting daemon pid %ld port %d\n", 
	 progName, (unsigned long) getpid(), port);

      /* 
       * Must close fds 0,1, and 2 *before* openServer and becomeDaemon
       */
      close(0);
      close(1);
      close(2);

      pid = becomeDaemon();
      if (pid < 0) {
	 return 1;		/* messages already logged */
      }

      nfd = openServer(port, rhostName, sizeof rhostName, &rport, (debug < 2));
      if (nfd < 0) {
	 log("%s: open network port %d failed--%s\n", progName, port, ERRMSG);
	 return 1;
      }
      setProgName(); /* Openserver did a fork when it returned successfully */
      /*
       * Openserver handed us nfd as fd 1, 0 is now available
       */
   }
   log("%s: connect %s:%d\n", progName, rhostName, rport);

   protocol = serverHandshake(nfd, rhostName, rport, SETUP_TIMEOUT);
   if (!protocol) {
      return 1;		/* log already done */
   }
   count = getString(nfd, ruser, USERNAME_SIZE-1, SETUP_TIMEOUT);
   if (count == 0) {
      log("%s: NORUSER  %s:%d\n", progName, rhostName, rport);
      return 1;
   }
   count = getString(nfd, userName, USERNAME_SIZE-1, SETUP_TIMEOUT);
   if (count == 0) {
      log("%s: NOLUSER  %s@%s:%d\n", 
	 progName, ruser, rhostName, rport);
      return 1;
   }
   /*
    * Minimize the the time the phrase must me visible in memory.
    */
   if (generic) {
      chp = generic;
   } else {
      chp = userName;
   }
   res = getUserPhrase(userFile, passPhrase, PHRASE_SIZE, chp, userKey);
   if (res == 0) {
      log("%s: NOPHRASE %s@%s:%d->\"%s\"\n", 
	 progName, ruser, rhostName, rport, chp);
      return 1;
   }
   if (res == -1) {
      log("%s: cannot open \"%s\" for user database\n", progName, userFile);
      return 1;
   }
   if (res == -2) {
      log("%s: incorrect decryption key for user database \"%s\"\n", 
	 progName, userFile);
      return 1;
   }
   key = challenge(nfd, passPhrase, loginTimeout);
   if (key == (keyType) 0) {
      log("%s: BADLOGIN %s@%s:%d->\"%s\"\n", 
	 progName, ruser, rhostName, rport, userName);
      return 1;
   }
   memset(passPhrase, '\0', PHRASE_SIZE);

   if (!generic) {
      res = getUserInfo(userName, &shell, &cwd, &uid, &gid);
      if (res < 0) {
	 log("%s: NOUSER   %s@%s:%d->\"%s\"\n",
	    progName, ruser, rhostName, rport, userName);
	 return 1;
      }
   } else {
      shell = loginProg;
      cwd   = loginCwd;		/* not used except for log message */
      uid   = getuid();		/* cannot fail */
      gid   = getgid();		/* cannot fail */
   }

   /*
    * If /etc/nologin exists, just copy it to the port and terminate.
    */
#if defined(NOLOGIN_FILE)
   res = open(NOLOGIN_FILE, O_RDONLY, 0);
   if (res >= 0) {
      log("%s: NOLOGIN %s@%s:%d->%s\n",
	 progName, ruser, rhostName, rport, userName);
      cipherCopy(nfd, res, 2, key);	  /* two seconds max to blast it out */
      close(res);
      exit(1);
   }
#endif

   /*
    * We really should log the tty assigned, and pid of the shell as well
    */
   log("%s: Login %s@%s:%d as %s(%u:%u) %s %s\n",
      progName, ruser, rhostName, rport, userName, uid, gid, cwd, shell);

   time(&loginTime);

   if (pty) {
      /*
       * We only need to do all this pty stuff and forking, if we want
       * the local process to use a the slave tty interface.  This will 
       * not always be the case.  We should add a feature to eliminate it
       * for remote commands that do not need a tty as stdin, stdout & stderr.
       *
       * The openPty below make the slave the controlling terminal for
       * this process, unless we use O_NOCTTY.
       */
      masterpty = 
	 openPty(O_RDWR | O_NOCTTY, sname, mname, PTY_NAMESIZE, &slavepty);
      if (masterpty < 0) {
	 log("%s: no pty's available\n", progName);
	 exit(1);
      }
      /*
       * Better make sure this close relinquishes the slavepty as the 
       * controlling terminal if it was.  Otherwise, the child will be unable 
       * to reacquire it as the controlling terminal and exit.
       *
       * DEC Ultrix 4.3 definitely ignores O_NOCTTY, but this code makes
       * sure that is harmless by closing the slave before the fork below.
       *
       * In fact, I can't figure out a way to force Ultrix to give me
       * a controlling terminal.  It claims I got one, but then when I 
       * try to relinquish it, it fails ENOTTY.  tcgetpgrp is really busted
       * on this machine.
       */
      pid_res = tcgetpgrp(slavepty);
      if (debug > 1) {
	 log("%s: tcgetpgrp(%d) returned pid %d\n", 
	    progName, slavepty, pid_res);
      }
      if (debug && (pid_res >= (pid_t) 0)) {
	 log("%s: WARNING--%s fd %d is controlling terminal of daemon!\n",
	    progName, sname, slavepty);
	 res = mkCtrlTty(slavepty, 0);
	 if (debug > 1) {
	    log("%s: mkCtrlTty(%d) returned %d\n", progName, slavepty, res);
	 }
	 if (res < 0) {
	    log("%s: WARNING--couldn't relinquish control terminal--%s\n", 
	        progName, ERRMSG);
	 }
      }
      close(slavepty);
      /*
       * The next three calls can fail when running as a normal user
       */
      res = chmod(sname, TTY_MODE);
      if (res < 0) {
	 log("%s: WARNING--chmod(\"%s\",0%o) failed--%s\n",
	    progName, sname, TTY_MODE, ERRMSG);
      }
      res = chown(sname, uid, gid);
      if  (res < 0) {
	 log("%s: WARNING--chown(\"%s\",%d,%d) failed--%s\n", 
	    progName, sname, uid, gid, ERRMSG);
      }
      if (debug) {
	 log("%s: \"%s\" %d->%d \"%s\"\n", 
	    progName, mname, masterpty, slavepty, sname);
      }
   } else {			/* pipe */
      /*
       * We must make sure these are not fd's 0, 1, or 2, so this first
       * call is just to use them up if necessary.
       */
      res = pipe(pipe2);
      if (res < 0) {
	 log("%s: pipe failed--%s\n", progName, ERRMSG);
	 exit(1);
      }
      res = pipe(pipe1);
      if (res < 0) {
	 log("%s: pipe failed--%s\n", progName, ERRMSG);
	 exit(1);
      }
      res = pipe(pipe0);
      if (res < 0) {
	 log("%s: pipe failed--%s\n", progName, ERRMSG);
	 exit(1);
      }
      close(pipe2[0]);
      close(pipe2[1]);
      if (debug) {
	 log("%s: pipe (%d>%d) (%d<%d)\n", progName,
	    pipe0[1], pipe0[1], pipe1[0], pipe1[1]);
      }
   }
   /*
    * POSIX states default is not to receive signal when child dies (or stops)
    * We want to know when it happens.  We must do this before the fork
    * to prevent a race condition.  We must catch this signal to force
    * the select call in txfr to interrupt when the child dies.  Note thhat
    * this may do nothing if on systems where sysconf(_SC_JOB_CONTROL) 
    * returns 0 (although System V may implement SIGCHLD as SIGCLD.).  We'll
    * just have to assume that this will allow us to detect when the child
    * dies.  If not, the symptom is that exiting the remote shell will just
    * hang until the INACTIVITY_TIMEOUT (connTimeout) expires.
    */
   chp = (char *) posignal(SIGCHLD, sigHandler);
   if (chp == (char *) SIG_ERR) {
      log("%s: sigaction SIGCHLD failed--%s\n", progName, ERRMSG);
      return 1;
   }
   /*
    * This fork is necessary to reset much of the process state and
    * to ensure that setsid won't fail because we're a process group leader
    * It is also needed to transport data from the pty to the socket 
    * (or control terminal) from the remote shell.
    *
    * Sect3:15: fork() ensures that child pid != any active pgid 
    */
   pid = fork();
   if (pid != 0) {			/* parent */
      if (pid < 0) {			/* fork failure */
	 log("%s: fork failed--%s\n", progName, ERRMSG);
	 exit(1);
      }
      setProgName();
      if (debug) {
	 log("%s: parent pid %d; child pid %ld\n", 
	     progName, getpid(), (long) pid);
      }
      if (pty) {
	 /*
	  * This must be done after we have a pty, and before we do a setuid.
	  * It updates the utmp file so getlogin and who will work corretly.
	  * It should be done only by the parent.
	  * It's required on hp-ux even for -w (wrapper) option.
	  */
	 res = setlogin(sname, userName, rhostName, 0);
	 if (res < 0) {
	    log("%s: WARNING--setlogin %s to %s@%s failed--%s\n", 
	       progName, sname, userName, rhostName, ERRMSG);
	 }
	 res = atexit(eraselogin);
	 if (res < 0) {
	    log("%s: WARNING--atexit of eraselogin failed--%s\n", 
	    progName, ERRMSG);
	 }

	 /*
	  * There is a race condition between the parent and the child.
	  * On DEC, if the parent executes txfr before the child opens the
	  * slave, we'll abort with an I/O error.  Wait until pty is ready.
	  */
	 res = waitReady(masterpty, buf, 1, SETUP_TIMEOUT);
	 if (debug > 1) {
	    log("%s: waitReady(%d,%u)=%d\n", 
		progName, masterpty, SETUP_TIMEOUT, res);
	 }
	 if (res <= 0) {
	    if (res < 0 && errno != EINTR) {	/* fail, and not SIGCHLD */
	       log("%s: master pty handshake failed--%s\n", 
		  progName, res, ERRMSG);
	    } else if (res == 0) {
	       log("%s: master pty handshake failed\n", progName, res);
	    }
	 } else {
	    if (debug) {
	       log("%s: txfr %d-decrypt->%d\n", progName, nfd, masterpty);
	    }
	    res = txfr(nfd, masterpty, masterpty, bufSize, connTimeout, key);
	 }
      } else {		/* pipe */
	 close(pipe0[1]);
	 close(pipe1[0]);
	 if (debug) {
	    log("%s: txfr pipe %d->%d<-%d\n", 
	       progName, pipe0[0], nfd, pipe1[1]);
	 }
	 res = txfr(nfd, pipe0[0], pipe1[1], bufSize, connTimeout, key);
      }
      /*
       * Really should be registered with atexit, but since it's just
       * a session key anyway, it's not insecure to let's its carcass
       * rot in inaccessible bits of free space.
       */
      destroyKey(&key);
      /*
       * Reap all terminated children so we don't leave zombie processes
       * and make sure our child CPU times are updated.  Waitpid returns
       * -1 when all children have been reaped, and 0 if still have 
       * unterminated children.
       */
      do {
	 pid = waitpid(-1, &chldStat, WNOHANG);
	 if (debug) {
	    log("%s: waitpid returned %ld\n", progName, (long) pid);
	 }
      } while (pid > 0);

      cpuUsed   = cpuTime();
      loginTime = time((time_t *) 0) - loginTime;

      if (res != 0) {
	 log(
	    "%s: Logout %s on %u(%ld%c%ld) sec(CPU), %lu/%lu bytes in/out\n",
	    progName, userName, loginTime, cpuUsed / 10, '.', cpuUsed % 10, 
	    inBytes, outBytes);
      } else {
	 log(
"%s: Logout %s on %u(%ld%c%ld) sec(CPU), %lu/%lu bytes in/out (idle %u sec)\n",
	    progName, userName, loginTime, cpuUsed / 10, '.', cpuUsed % 10, 
	    inBytes, outBytes, connTimeout / 1000);
      }
      exit(0);
   }

   /* child */
   setProgName();

   if (generic) {
      cargv[1] = loginArg1;
      cargv[2] = rhostName;
   } else {
      tz = getenv("TZ");	/* may fail by returning NULL; OK though */
      if (uid == 0) {		/* root user gets special path */
	 path = SYSTEMPATH;
      }
      cenv = setupEnv(userName, shell, cwd, path, tz, rhostName);

      res = chdir(cwd);
      if (res != 0) {
	 log("%s: chdir to %s failed \"%s\"--%s\n", progName, cwd, ERRMSG);
	 return 1;
      }
      oldmask = umask(newmask);	/* documentation says umask cannot fail! */

      /*
       * This code must be placed only in the child because of the atexit call
       * in the parent for updating utmp.  This changes the uid, and gid now.
       */
      setupUser(uid, gid, userName);	/* exits if failure */
   }

   if ((chp = strrchr(shell, '/')) == (char *) 0) chp = shell-1;
   chp++;
   arg0	= (char *) malloc(strlen(chp)+2);
   if (arg0 == (char *) 0) {
      log("%s: couldn't allocate %d bytes for arg0\n", 
	 progName, strlen(chp)+2);
      exit(1);
   }
   arg0[0] = '-';				/* make this a login shell */
   memcpy(&arg0[1], chp, strlen(chp)+1);
   cargv[0] = arg0;

   /*
    * Sect4.3.2:157-177:
    * If the calling process is not a process group leader, the setsid()
    * function shall create a new session. The calling process shall be
    * the session leader of this new session, shall be be the process
    * group leader of a new process group, and shall have no controlling
    * terminal. The process group ID of the calling process shall be
    * set equal to  the process ID of the calling process.  The calling
    * process shall be the only process in the new proccess group and the
    * only process in the new session.
    *    Upon successful completion, the setsid() function returns the value
    * of the process group ID of the calling process.  Otherwise, a value of
    * -1 is returned and errno is set to indicate the error.
    * 
    * EPERM - The calling process is already a process group leader, or
    *         the process group ID of a process other than the calling process
    *         matches the process id of the calling process
    *
    * D.A.B.: the fork() made sure that pgid != pid, and
    *         we're not a pg leader because we didn't call setpgid.
    *
    * NOTE: getpgrp() expects an argument on BSD, but not POSIX.  If
    *       if you have BSD, (DEC MIPS, SunOS4.1), you should pass it 0.
    */
   if (debug > 1) {
      log("%s: getpid:  %ld\n", progName, (long) getpid());
      log("%s: getpgrp: %ld\n", progName, (long) getpgrp());
   }
   pid = setsid();	      /* break control terminal affiliation; get sid */
   /* 
    * Any open of a terminal device after setsid without O_NOCCTY will likely
    * become the controlling terminal.  
    */
   if (pid == -1) {
      log("%s: setsid failed--%s\n", progName, ERRMSG);
      return 1;
   }
   if (debug > 1) {
      log("%s: setsid:  %ld\n", progName, (long) pid);
      log("%s: getpid:  %ld\n", progName, (long) getpid());
      log("%s: getpgrp: %ld\n", progName, (long) getpgrp());
   }

   close(0);		/* actually, we should close ALL fd's here */
   close(1);		/* this should be all of them */
   close(2);		/* we made sure none of these are needed below */
   close(nfd);		/* they must be free for dup's to work */
   if (pty) {
      close(masterpty);
   /* 
    * This was designed to prevent someone from hooking upto the slave before
    * the child could.  It doesn't.  I've moved the close before the fork.
    */
#if 0
      /* must be as close as possible to the open 
       * to reduce the race condition for someone hijacking the slave pty
       * before this process can get it.  There needs to be a better way.
       *
       * There was a bug here if the parent still opened the slave as
       * a controlling terminal, and it hasn't closed slavepty before we do
       * here.  That's why O_NOCTTY is specified to openPty above.
       */
      close(slavepty);	
#endif
      /*
       * Sect7.2.3:753-765:
       * Upon successful completion, tcgetpgrp() returns the process group ID 
       * of the foreground process group associated with the terminal.  If 
       * there is no foreground process group, tcgetpgrp() shall return a value
       * greater than 1 that does not match the process group ID of any 
       * existing process group.  Otherwise, a value of -1 is returned and 
       * errno is set to indicate the error.
       *
       * EBADF  - The filedes argument is not a valid file descriptor
       * ENOSYS - The tcgetprgp() function is not supported in this 
       *          implementation
       * ENOTTY - The calling process does not have a controlling terminal,
       *          or the file is not the controlling terminal.
       */
      /*
       * Establish a new control terminal by being the first process without
       * a controlling terminal to open a tty device.  Non-POSIX, but
       * very common.
       *
       * The close must happen as close as possible with the open to prevent
       * hijacking of the slave side of the pty.  Note that on some systems
       * this may cause the master to get notified and abort prematurely.  I
       * don't think this exists now because of SIGCHLD being used instead.
       *
       * AnnexB:3884-3887:
       * POSIX.1 does not specify a mechanism by which to allocate a 
       * controlling terminal...Historical implementations allocate controlling
       * terminals on certain open() calls...Some historical implementations 
       * deallocate a controlling terminal on its last systemwide close.
       * This behavior is neither required nor prohibited.  Even on 
       * implementations that do provide this behavior, applications cannot
       * depend on it due to its systemwide nature.  
       *
       * Sect5:204-206: O_NOCTTY if set, and path identifies a terminal device,
       * the open() function shall not cause the terminal device to become the
       * controlling terminal for the process (see 7.1.1.3)
       *
       * Sect7.1.1.3:42-68:
       * ...The controlling terminal for a session is allocated by the session
       * leader in an implementation-defined manner.  If a session leader has
       * no controlling terminal and opens a terminal device file that is not
       * already associated with a session without using the O_NOCTTY option
       * (see 5.3.1), it is implementation defined whethere the terminal
       * becomes the controlling terminal of the session leader.  If a process
       * that is not a session leader opens a terminal file, or the O_NOCTTY
       * option is used on open(), that terminal shall not become the the 
       * controlling terminal of the calling process.  When a controlling
       * terminal becomes associated with a session, its foreground process
       * group shall be set to the process group of the session leader.
       *    The controlling terminal is inherited by the child process during a
       * fork() function call.  A process relinquishes its controlling terminal
       * when it creates a new session with the setsid() function; other 
       * processes remaining in the old session that had this terminal as
       * their controlling terminal continue to have it.  Upon the close of
       * the last file descriptor in the system (whether or not it is in the
       * current session) associated with the cntrolling terminal, it is 
       * unspecified whether all process that had the terminal cease to have
       * any controlling terminal.  Whether and how a session leader can
       * reacquire a controlling terminal after the controlling terminal has
       * been relinquished in this fashion is unspecified. A process does not
       * relinquish its controlling terminal simply by closing all of its
       * file descriptiors associated with the controlling terminal if other
       * processes continue to have it open.
       *    When a controlling process terminates, the controlling terminal
       * is disassociated from the current session, allowing it to be acquired
       * by a new session leader.  Subsequent access to the terminal by other
       * processes in the earlier session may be denied, with attempts to
       * access the terminal treated as if a modem disconnect had been sensed.
       * 
       * Sect7.2.4:775-800
       * If the process has a controlling terminal, the tcsetpgrp() function
       * shall set the foreground process group ID associated with the 
       * terminal to pgrp_id.  The file associated with filedes must be the 
       * controlling terminal of the calling process, and the controlling 
       * terminal must be currently associated with the session of the 
       * calling process.  The value of pgrp_id must match a process 
       * group ID of a process in the same session as the calling process.
       *     Upon successful completion, tcsetpgrp() returns a value of zero.
       * Othewise, a value of -1 is returned, and errno is set to indicate the
       * error.
       * EBADF  - The filedes argument is not a valid file descriptor.
       * EINVAL - The value of pgrp_id argument is not supported by the
       *	  implementation.
       * ENOSYS - The tcsetpgrp() function is not supported in this
       *	  implementation.
       * ENOTTY - The calling process does not have a controlling terminal, or
       *	  the file is not the controlling terminal, or the controlling
       *	  terminal is no longer associated with the session of the
       *	  calling process.
       * EPERM  - The value of pgrp_id is a value supported by the
       *	  implementation, but does not mmatch the process group ID of a
       *	  process in the same session as the calling process.
       *
       * HP-UX_9.0:termio(7) man page states:
       * A terminal can belong to a process as its controlling terminal.  Each
       * process of a session that has a controlling terminal has the same
       * controlling terminal.  A terminal can be the controlling terminal for
       * at most one session.  The controlling terminal for a session is
       * allocated by the session leader.	If a session leader has no
       * controlling terminal and opens a terminal device file that is not
       * already associated with a session without using the O_NOCTTY option
       * (see open(2), the terminal becomes the controlling terminal of the
       * session and the controlling terminal's foreground process group is set
       * to the process group of the session leader.  While a controlling
       * terminal is associated with a session, the session leader is said to
       * be the controlling process of the controlling terminal.
       *    The controlling terminal is inherited by a child process during a
       * fork() (see fork(2)).  A process relinquishes its controlling terminal
       * if it creates a new session with setsid() or setpgrp() (see setsid(2)
       * and setpgrp(2)), or when all file descriptors associated with the
       * controlling terminal have been closed.
       *    When the controlling process terminates, the controlling terminal is
       * disassociated from the current session, allowing it to be acquired by
       * a new session leader.  A SIGHUP signal is sent to all processes in the
       * foreground process group of the controlling terminal.  Subsequent
       * access to the terminal by other processes in the earlier session can
       * be denied (see Terminal Access Control) with attempts to access the
       * terminal treated as if a modem disconnect had been sensed.
       *
       * Ultrix4.3pty(4): The slave device can be opened multiple times, while
       * the master half can be opened only once.  [EIO] An attempt to open
       * the slave side of the pty was made before opening the master side of 
       * the pty.
       */
      sin = openSlavePty(sname, O_RDWR);
      if (sin < 0) {
	 log("%s: open slave pty \"%s\" for read-write failed--%s\n", 
	    progName, sname, ERRMSG);
	 exit(1);		/* sends SIGCHLD to parent */
      }
      res = mkCtrlTty(sin, 1);
      if (debug > 1) {
	 log("%s: mkCtrlTty(%d, %d) returned %d\n", 
	    progName, sin, 1, res);
      }
      if (res < 0) {
         log(
	    "%s: mkCtrlTty(%d) didn't acquire %s as contrlling terminal--%s\n",
	    progName, sin, sname, ERRMSG);
	 exit(1);
      }
      res  = tcsetpgrp(sin, pid);
      if (debug > 1) {
	 log("%s: tcsetpgrp(%d, %d) returned %d\n", 
	    progName, sin, pid, res);
      }
      if (res < 0) {
	 log("%s: could not acquire fd %d (%s) as controlling terminal--%s\n",
	    progName, sin, sname, ERRMSG);
	 exit(1);
      }
      res = setTtyAscii(sin);		/* initialize modes for ASCII I/O */
      res = mkReady(sin, buf, 1, SETUP_TIMEOUT);
      if (debug > 1) {
	 log("%s: mkReady(%d,%d)=%d\n", 
	    progName, sin, SETUP_TIMEOUT, res);
      }
      if (res < 0) {
	 log("%s: slave pty handshake failed--%s\n", progName, ERRMSG);
	 exit(1);
      }
      if (res == 0) {
	 log("%s: slave pty handshake failed.\n", progName);
	 exit(1);
      }

      sout = dupFd(sin, sname, O_WRONLY);
      serr = dupFd(sin, sname, O_RDWR);
      res  = fcntl(sin, F_SETFL, O_RDONLY);
      if (res < 0) {
	 log("%s: fcntl of \"%s\" for read-only failed--%s\n", 
	    progName, sname, ERRMSG);
	 exit(1);
      }
      if (debug > 1) {
         log("%s: STDIN = %d, STDOUT = %d, STDERR = %d\n", 
	    progName, sin, sout, serr);
      }
      /*
       * Make sure open(ctermid(NULL)) works.
       *
       * If you get a compile time error here on linux, it's because linux 
       * forgot to add an extern for ctermid in unistd.h.  Fix your include 
       * file or add "extern char *ctermid();" above.  Hopefully, linux will 
       * fix this error in the next release.  
       */
      chp = ctermid((char *) 0);	/* In POSIX <unistd.h> */
      if (chp == (char *) 0 || *chp == (char) 0) {
	 log("%s: WARNING--ctermid(0) failed --%s\n", progName, ERRMSG);
      } else {
	 ttyfd = open(chp, O_RDWR);
	 if (ttyfd == -1) {
	    log("%s: WARNING--open(\"%s\", O_RDWR) failed --%s\n", 
	       progName, chp, ERRMSG);
	 }
	 close(ttyfd);
      }
      /* 
       * Make sure getlogin works.  If not, setlogin didn't work.  You will
       * need to repair setlogin.c.  This call can still fail if the parent
       * still hasn't called setlogin by now.
       */
      chp = (char *) getlogin();
      if (chp == (char *) 0) {
	 log("%s: WARNING--getlogin returned NULL\n", progName);
      } else {
	 if (debug > 1) {
	    log("%s: getlogin returned \"%s\"\n", progName, chp);
	 }
      }
   } else {		/* pipe */
      close(pipe0[0]);
      close(pipe1[1]);

      sin  = dupFd(pipe1[0], "pipe1[0]", O_RDONLY);
      close(pipe1[0]);

      sout = dupFd(pipe0[1], "pipe0[1]", O_WRONLY);
      close(pipe0[1]);

      serr = dupFd(sin, "serr", O_RDWR);
   }

   /*
    * Effect of exec:
    *
    * Unchanged:
    *    POSIX State:
    *       open files and state (including locks)
    *       pid, pgid, ppid, sid, ruid, rgid,
    *
    *       chroot(), chdir(), umask(), getlogin(), ctermid()
    *       getgroups()
    *       times()
    *       alarm(), sigpending(), sigprocmask(), 
    *
    * Note that fork() updates:
    *       pid, ppid, times(), alarm(), sigpending(), file locks,
    *       own copy of fd's, directory streams; alarm cleared.
    *
    *    Non-posix state:
    *       ptrace(), semop().
    *       ulimit() nice() rtprio()
    *       semop() 
    *       ptrace() 
    *       setitimer()
    *
    * Changed:
    *    euid, egid, suid, sgid - changed if exec file has setuid/gid
    *    caught signals are set to SIG_DFL
    *
    *    shmop(2)  - none attached
    *    profil(2) - profiling disabled
    */
   if (debug) {
      if (generic) {
	 log("%s: exec \"%s\" as \"%s %s %s\"\n", progName, shell, 
	    cargv[0], cargv[1], cargv[2]);
      } else {
	 log("%s: exec \"%s\" as \"%s\"\n", progName, shell, cargv[0]);
      }
      if (debug > 1) {
	 log("%s: env:\n", progName);
	 showEnv("   ", cenv);
      }
   }
   closeLog();
   
   res  = execve(shell, cargv, cenv);
   if (res < 0) {
      return 1;
   }

   return 0;
}
