/* ttywrapper.c
 *
 * Quick fix to allow a password to be changed without an intervening
 * ptty.  Program will read (and run) any series of commands and input
 * that is in the local file called 'ttywrapper.data'.  Just remove references to
 * this file, and you will have a spawned p-tty to interact with.
 *
 * The format to change a password is:
 *
 * passwd
 * <cr>
 * <old pass>
 * <new pass>
 * <new pass>
 * <cr>
 * exit
 *
 * The format to modify an interactive eeprom setting is:
 *
 * /usr/sbin/eeprom security-mode=command
 * <cr>
 * <password><space>
 * <password><space>
 * <cr>
 * exit
 *
 * The code has been shamelessly taken from the O'reilly SVR4 programming 
 * book, with modifications to allow the file read.
 *
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <stropts.h>
#include <termios.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>

#define MAXARGS 32      /* max cmd args */

char    *shell = "/bin/sh";
char    *filename = "scriptfile";
char    *mastername = "/dev/ptmx";

int     master;                 /* master side of pty */
FILE    *script;                /* script file */
struct termios newtty, origtty; /* tty modes */

void finish(int);
int ptyopen(char *, struct termios *);


int main(int argc, char **argv)
{
    char *p;
/*    char *authc[1]; */
    char *authc[1];
    int n, nfd, in;
    FILE *in_pointer;
    FILE *master_p;
    time_t clock;
    fd_set readmask;
    char buf[BUFSIZ];

    /*
     * if an arg is given, it is a new script file 
     */
    if (argc > 1)
        filename = *++argv;

    /* use the user's shell, if known */
    if ((p = getenv("SHELL")) != NULL)
            shell = p;

    /* open the script file */
    if ((script = fopen(filename, "w")) == NULL)
    {
        perror("filename");
        exit(1);
    }

    /* get the tty modes.  use these both to set modes
       on the pseudo-tty, and to restore modes on the
       user's tty.
     */
    if (tcgetattr(0, &origtty) < 0 )
    {
        perror("tcgetattr: stdin");
        exit(1);
    }

    /* grab a p-tty and start a shell on it */
    if ((master = ptyopen(shell, &origtty)) < 0)
    {
        exit(1);
    }

    /* print a little start message */
    time(&clock);
    fprintf(script, "Script started on %s", ctime(&clock));
    printf("Script started, file is %s\n", filename);

    /* we need to catch signals now that we are going to change tty modes */
    sigset(SIGINT, finish);
    sigset(SIGQUIT, finish);

    /* change the users tty st pretty much everything gets
       passed through to the p-tty.  set raw mode so that 
       we can pass characters as they are typed.
     */
    newtty = origtty;
    newtty.c_cc[VMIN] = 1;
    newtty.c_cc[VTIME] = 0;
    newtty.c_oflag &= ~OPOST;
    newtty.c_lflag &= ~(ICANON|ISIG|ECHO); 
    newtty.c_iflag &= ~(INLCR|IGNCR|ICRNL|IUCLC|IXON);

    /* set the new tty modes */
    if (tcsetattr(0, TCSANOW, &newtty) < 0)
    {
        perror("tcsetattr: stdin");
        exit(1);
    }

    /* now sit in a loop reading from keybd and writing to
       the p-tty, and reading from the p-tty and writing to
       the screen and the scripts file */
    
       if (( in = open("ttywrapper.data", O_RDONLY)) < 0 )
    {
        perror("ttywrapper.data open");
        exit(1);
    } 

    for (;;)
    {
        FD_ZERO(&readmask);
        FD_SET(master, &readmask);
        FD_SET(0, &readmask); 
        FD_SET(in, &readmask); 
        nfd = master + 2;

        /* wait for somthing to read */
        n = select(nfd, &readmask, (fd_set *) 0, (fd_set *) 0, (struct timeval *) 0);

        if (n < 0)
        {
            perror("select");
            exit(1);
        }

        /* the user typed something read it and pass it to the p-tty */ 
       if (FD_ISSET(0, &readmask))
        {
            if ((n = read(0, buf, sizeof(buf))) < 0 ) 
            {
                perror("read:stdin");
                exit(1);
            }

            if (n == 0)
                finish(0);

            if (write(master, buf, n) != n)
            {
                perror("write: pty");
                exit(1);
            }
        } /* end of FD_ISSET for stdin */

        /* there is somthing to read from the file auth */
        if (FD_ISSET(in, &readmask))
        {
            if ((n = read(in, authc, sizeof(authc))) < 0 )
            {
              perror("read:auth");
              exit(1);
            }

         if (n == 0)
                finish(0);
	 /* do not remove this sleep - it slows things down enough for the 
	  * return for passwd
	  */
	  sleep(1); 
            if (write(master, authc, n) != n) 
            {
                perror("write: pty");
                exit(1);
            }  
        } /* end of FD_ISSET for stdin */


        /* there is output on the p-tty.  read it and pass it
           on to the screen and the script file
         */
        if (FD_ISSET(master, &readmask))
        {
            /* the process died */
            if ((n = read(master, buf,sizeof(buf))) <= 0 )
                finish(0);

            fwrite(buf, sizeof(char), n, script);
            write(1, buf, n); 
        }

    } /* end of for */
                                                                     
} /* end of main */

int
ptyopen(char *command, struct termios *ttymodes)
{
    char *p;
    pid_t pid;
    char *slavename;
    char *args[MAXARGS];
    int nargs, master, slave;

    /* break the command into args */
    nargs = 0;
    p = strtok(command, " \t\n");

    do  {
        if (nargs == MAXARGS)
        {
            fprintf(stderr, "too many arguments.\n");
            return(-1);
        }

        args[nargs++] = p;
        p = strtok(NULL, " \t\n");
    } while (p != NULL);

    args[nargs] = NULL;

    /* get master p-tty */
    if ((master = open(mastername, O_RDWR)) < 0 )
    {
        perror(mastername);
        return(-1);
    }

    /* select the permissions on the slave */
    if (grantpt(master) < 0 )
    {
        perror("grantpt");
        close(master);
        return(-1);
    }

    /* unlock the slave */
    if (unlockpt(master) < 0 )
    {
        perror("unlockpt");
        close(master);
        return(-1);
    }

    /* start a child process */
    if ((pid = fork()) < 0 )
    {
        perror("fork");
        close(master);
        return(-1);
    }

    /* the child process will open the slave, which
       will become it's controlling process
     */
    if (pid == 0)
    {
        /* get rid of our current controlling term */
        setsid();

        /* get the name of the slave p-tty */
        if ((slavename = ptsname(master)) == NULL )
        {
            perror("ptsname");
            close(master);
            exit(1);
        }

        /* open the slave p-tty */
        if ((slave = open(slavename, O_RDWR)) < 0 )
        {
            perror("slavename");
            close(master);
            exit(1);
        }

        /* push the hardware emulation module */
        if (ioctl(slave, I_PUSH, "ptem") < 0)
        {
            perror("ioctl: ptem");
            close(master);
            close(slave);
            exit(1);
        }

        /* push the line dicipline module */
        if (ioctl(slave, I_PUSH, "ldterm") < 0 )
        {
            perror("ioctl: ldterm");
            close(master);
            close(slave);
            exit(1);
        }

        /* copy the users term modes to the slave p-tty */
        if (tcsetattr(slave, TCSANOW, ttymodes) < 0 )
        {
            perror("tcsetattr: pty");
            close(master);
            close(slave);
            exit(1);
        }

        /* close the script file and the master, these are not
           needed by the slave
         */
        fclose(script);
        close(master);

        /* set the slave to be our stdin, out and err,  get rid of
           the original file descript
         */
        dup2(slave, 0);
        dup2(slave, 1);
        dup2(slave, 2);
        close(slave);

        /* execute the command, this runs once upon execution*/
        execv(args[0], args);

        perror(args[0]);
        exit(1);
    }

    /* return the file desc for communicating with the process
       to our caller
     */
    return(master);

} /* end of ptyopen */

/*
 * finish - called when we are done
 */
void
finish(int sig)
{
    time_t clock;

    /* restore the orig tty modes */
    if (tcsetattr(0, TCSANOW, &origtty) < 0 )
    {
        perror("tcsetattr: stdin");
    }

    /* print a finishing message */
    time(&clock);
    fprintf(script, "\nScript finished at %s", ctime(&clock));
    printf("\nScript done, file is %s\n", filename);

    /* all done */
    fclose(script);
    close(master);
    exit(0);
}  /* end of finish */

