[alt.sources] A replacement getty

bill@twwells.uucp (T. William Wells) (03/20/89)

echo x - README
sed 's/^X//' >README <<'*-*-END-of-README-*-*'
XCopyright 1989 By T. William Wells. All Rights Reserved.
X
XRead the copyright notices in the individual files to determine
Xexactly what the restrictions are. Basically, they are free
Xdistribution, no direct commercial use, and keep a modification log.
X
XThis is getty version 0.1, released 3/19/89.  Send bug reports and
Xsuggestions to twwells!bill.  I have made the code as clean as I can;
Xif you don't understand something, and the problem is not just that
Xyou don't like my coding style, let me know. I'll either comment or
Xrecode as necessary.
X
XNote the version number. That's right, this is a beta release.  I use
Xit on my system, a Microport V/386 3.0e, and it works there, but I
Xhave no practical way to test it on other systems. This program is
Xprobably not useful on BSD systems, though I'd be pleased if someone
Xwere to tell me different and what modifications I have to make to
Xmake is useful.
X
XThis is pretty much a direct replacement for the standard getty that
Xcame with my Microport V/386 3.0e system. It does not know about
Xterminal types. There are two reasons I wrote it: for logging terminal
Xactivity and so that the terminal characteristics are set up properly
Xfor better system security. Some other features: the standard getty
Xhas a few irksome bugs and is poorly documented. These are gone.  As
Xfor whatever bugs appear in this one, well I now have source.  The
Xdocumentation is, I believe, more useful than the original getty
Xdocument. I don't recommend replacing your getty with this new code
Xtill you have made sure that you still have a way into your system;
Xwouldn't it be embarassing to have to jump through the installation
Xhoops again just because getty won't talk to you?
*-*-END-of-README-*-*
echo x - getty.1
sed 's/^X//' >getty.1 <<'*-*-END-of-getty.1-*-*'
XNAME
X       getty -- set terminal types, modes, speed, line discipline
X
XSYNOPSYS
X
X       getty [-P] [-T] [-h] [-l logfile] [-t timeout]
X           line [speed [type [linedisc]]]
X       getty -c [file]
X
XCOPYRIGHT
X
X       Copyright 1989 By T. William Wells. All Rights Reserved.
X
X       You may distribute this document and its derivatives so long
X       as you do not charge for them; you may charge a fee sufficient
X       to cover distribution costs. You may not include it in a
X       product to be sold.  You may not use it as an inducement to
X       buy.
X
X       You may not modify this copyright notice; you may only add to
X       the modification log that follows this copyright notice.
X
X       You may modify this document. If you modify the document and
X       distribute it, you must update the modification log to say who
X       you are and when you made the change.
X
XMODIFICATION LOG
X
X       Version 0.1, written by T. William Wells, released 3/19/89.
X            Send bug reports to twwells!bill.
X
XDESCRIPTION
X
X       Getty is one of the programs used to connect a terminal to
X       the system. The normal procedure is for init to fork a getty,
X       getty to exec login, and login to run a shell.  Getty can
X       only be executed by the super-user, except when the -c option
X       is used.
X
X       Getty uses its arguments and the contents of /etc/gettydefs
X       to set up an initial connection to the user. It displays a
X       connect message from /etc/issue and prompts the user for a
X       set of arguments. It reads those arguments and passes them
X       along to login(1). While reading the arguments, getty sets up
X       certain terminal parameters which it establishes just before
X       calling login. If getty reads a NUL character, normally
X       created by the user pressing the break key, getty will change
X       the line speed as specified in the /etc/gettydefs file.
X
X       Line is the name of the terminal device to be opened by getty.
X       It is found by looking in /dev. The terminal modes are set to
X       0640 by getty; the owner and group ids are set from the
X       effective ids of the getty.
X
X       Speed is the label for an entry in /etc/gettydefs. Each entry
X       contains the terminal parameters to be established by the
X       getty, including the terminal speed, the login prompt, and
X       the label for the entry to be used when getty receives a
X       break. There is a default speed entry built into getty which
X       is used if this field is not specified.
X
X       Type describes the terminal type attached to the line.  The
X       default is "none". This version of getty does not recognize
X       any terminal types other than "none".
X
X       Linedisc describes the line discipline to be used when
X       communicating with the terminal. The default is "LDISC0".
X       This version of getty does not recognize any line disciplines
X       other than "LDISC0".
X
X       The program options are:
X
X       -P When you are debugging a new terminal line, you may not
X          want getty to exit immediately on errors.  If there is an
X          error in the invocation of getty, for example, init will
X          respawn gettys as fast as they die. Init will eventually
X          catch on, but why go through the hassle? If you specify
X          the -P option, getty will pause just before exiting.  It
X          will also change its arguments so that a ps will show it as
X          "badgetty".  When paused, getty will have closed the
X          terminal line.  Once you have fixed the cause of the error,
X          you can kill the getty which will cause it to exit cleanly.
X
X       -T This version of getty assumes that a succeeding open means
X          that there is a user on the other end. Accordingly, the
X          timeout, if specified, starts when the open returns.  If
X          this is undesirable, the -T option will cause getty to
X          start the timeout when the first character is read.
X
X       -h Normally, getty hangs up the line immediately after the
X          open succeeds. On certain lines, notably dial up lines
X          where the open succeeds only when there is a carrier, this
X          is a definite mistake. -h will prevent the hang up.
X
X       -l If this is specified, getty writes a record of everything
X          that happens to the log file. Getty creates the log file
X          with modes 660, you will want to make sure that it remains
X          unreadable by anyone other than the super-user, as it may
X          contain things like passwords entered at the wrong prompt.
X          Getty tries to write each log message with one system
X          call, so you should be able to use one log file for all
X          gettys. Each line in the log file contains the terminal
X          name, process id of the getty, the date, and a message.
X
X          You will almost certainly want to truncate this file
X          periodically.
X
X       -t This specifies the length of time that getty will wait for
X          a complete login argument. If it doesn't get one by this
X          time, it just exits. Getty starts its timer when the open
X          succeeds unless the -T option is specified, in which case
X          it starts it when the first character is read.
X
X       Getty -c is used for checking out a definition file. If you
X       don't specify a file name, /etc/gettydefs is used.  It is a
X       good idea to run getty -c on a just-edited definition file to
X       see if anything is amiss.
X
X       The initial sequence of operations performed by getty are:
X
X       1) Open the terminal line, set its owner, group, and modes.
X       2) Place accounting entries in /etc/utmp and /etc/wtmp.
X       3) Hang up the line unless -h was specified.
X       4) Start the timeout, unless -T was specified.
X
X       If getty can't get a speed entry, it gets the first one from
X       the /etc/gettydefs file. If it can't get to the file, it uses
X       a default that is built into the program. This default is for
X       a terminal at 2400 baud.
X
X       Then, for each speed entry that is tried, it does these steps:
X
X       1) Set terminal modes as specified in the /etc/gettydefs file.
X          Note that most of these options are ignored. In particular,
X          the fields in c_iflag, other than ICRNL are ignored; not
X          specifying a character size gets you eight bits; CREAD and
X          HUPCL in c_cflag are always set; and c_lflag is cleared.
X       2) Write the message from /etc/issue.
X       3) Get the login arguments. If a break is read, get a new
X          speed entry and try again.
X
X       When the login arguments are read, the terminal modes are set
X       up as specified in the current /etc/gettydefs entry; however,
X       they may be modified by getty. If the input terminator is a
X       return, translation of returns to newlines is enabled.  If
X       the login arguments contain at least one upper case letter
X       but no lower case letters, case translation is enabled and
X       the login arguments are made all lower case.
X
X       The arguments are then broken up into words and these become
X       the arguments to login.
X
X       Getty recognizes a number of special characters when reading
X       the login arguments. They are:
X
X       ^H causes a backspace. The character is removed from the input
X          buffer and a backspace-space-backspace is written to the
X          terminal to remove the character from the display.
X
X       # removes one character from the input buffer.  There is no
X          attempt to remove the character from the terminal display.
X
X       ^D causes getty to exit.
X
X       Any of ^C, ^U, DEL, ^|, or @ causes the current input to be
X          discarded. The user is prompted again with the login prompt
X          but not the /etc/issue message.
X
X       ^J and ^M terminate the input.
X
X       \ is an escape character. Any character other than a NUL
X         following it is not interpreted. If you want to enter a
X         backslash, use two of them.
X
X       Other control characters are not echoed as control characters,
X       but as either ^letter or \octal.
X
X       If the input buffer overflows, getty will exit. This buffer
X       can hold 255 characters.
X
XFILES
X
X       /bin/login      This is called when the arguments have been read.
X       /dev            The terminal file is found in this directory.
X       /dev/console    Error messages are written here.
X       /etc/gettydefs  The getty parameters are read from here.
X       /etc/issue      This is the initial connection message.
X       /etc/utmp       The entry for the terminal is updated.
X       /etc/wtmp       An accounting record is appended to this file.
X
XSEE ALSO
X
X       gettydefs(4) init(1) ioctl(2) login(1) termio(7) utmp(4)
X
XBUGS
X
X       None known.
*-*-END-of-getty.1-*-*
echo x - Makefile
sed 's/^X//' >Makefile <<'*-*-END-of-Makefile-*-*'
Xgetty : getty.c
X       $(CC) $(CFLAGS) getty.c
X       mv a.out $@
X
XGETTYSHAR = README getty.1 Makefile getty.c
Xgetty.shar : $(GETTYSHAR)
X       shar $(GETTYSHAR) >shar.tmp
X       mv shar.tmp $@
*-*-END-of-Makefile-*-*
echo x - getty.c
sed 's/^X//' >getty.c <<'*-*-END-of-getty.c-*-*'
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <ctype.h>
X#include <errno.h>
X#include <fcntl.h>
X#include <signal.h>
X#include <stdio.h>
X#include <string.h>
X#include <termio.h>
X#include <time.h>
X#include <utmp.h>
X
X/* Copyright 1989 By T. William Wells. All Rights Reserved.
X
X   You may distribute this code and its derivatives so long as you do not
X   charge for them; you may charge a fee sufficient to cover distribution
X   costs. You may not include it in a product to be sold.  You may not use it
X   as an inducement to buy.
X
X   You may not modify this copyright notice; you may only add to the
X   modification log that follows this copyright notice.
X
X   You may modify the code. If you modify the code and distribute it, you must
X   update the modification log to say who you are and when you made the
X   change. */
X
X/* Modification log:
X
X   Version 0.1, written by T. William Wells, released March 19, 1989.
X       Send bug reports to twwells!bill.
X*/
X
X/* getty - the program to handle initial terminal connections to the system.
X   It sets up the speed, terminal flags, and line discipline. It then waits
X   for some arguments and calls login with them.
X
X   getty [-P] [-T] [-h] [-l logfile] [-t timeout]
X       line [speed [type [linedisc]]]
X   getty -c [file]
X
X   -P When error exiting, set our name to badgetty, close the line, and pause.
X      Send a kill to finish it off.
X
X   -T Do the timeout from after the first character read. This is appropriate
X      for lines where the open does not wait for a carrier.
X
X   -h Don't hang up the line when initializing it.
X
X   -l Report getty behavior to the log file.
X
X   -t If timeout seconds elapse with no input, hang up the line. Timeout is
X      normally from when the open returns, but the -T option changes that.
X
X   Line is the device name as found in /dev.
X
X   Speed is the label of the entry in the definition file which is to be used
X      to initialize the line.
X
X   Type is the terminal type name.
X
X   Linedisc is the line discipline name.
X
X   -c Check the specified file for validity. An interpretation of the file is
X      printed so that it can be checked for correctness. */
X
Xextern unsigned alarm();
Xextern void     endutent();
Xextern void     exit();
Xextern struct utmp *getutent();
Xextern unsigned short getegid();
Xextern unsigned short geteuid();
Xextern char     *optarg;
Xextern int      opterr;
Xextern int      optind;
Xextern struct utmp *pututline();
Xextern char     *sys_errlist[];
Xextern int      sys_nerr;
Xextern time_t   time();
X
X#define BADNAME "badgetty"      /* name shown when getty hangs due to error */
X#define CONSOLE "/dev/console"  /* msgs go here when parent is init */
X#define DFLT_SPEED B2400        /* speed for default entry */
X#define GETTYDEFS "/etc/gettydefs" /* default definition file */
X#define ISSUE   "/etc/issue"    /* file containing signon message */
X#define LINELEN 256             /* length of line buffers, etc. */
X#define LOGCMD  "/bin/login"    /* command to execute to do login */
X#define LOGCMDNAME "login"      /* command name of login */
X#define MAXARGS 32              /* max args to be passed to login */
X
Xtypedef unsigned char UCHAR;
X
X/* These are the standard UNIX fd's; here defined so that they can be found
X   with no difficulty. */
X
X#define TTY_IN  0               /* fd for reading the terminal */
X#define TTY_OUT 1               /* fd for writing the terminal */
X#define TTY_ERR 2               /* fd for error writes to the terminal */
X
X#define LOG_FD  10              /* fd to assign to the log file */
X
X/* A kludge because of the missing LDISC0 in the includes. */
X
X#ifndef LDISC0
X#define LDISC0  0
X#endif
X
Xtypedef struct {                /* contains one speed entry */
X       char    td_label[16];   /* label for this entry */
X       struct termio td_initial; /* initial terminal flags */
X       struct termio td_final; /* final terminal flags */
X       int     td_msglen;      /* length of msg to prompt with */
X       char    td_logmsg[80];  /* message to prompt with */
X       char    td_nxtlbl[16];  /* next label to try */
X} TTYDEF;
X
Xtypedef struct {                /* used to contain table of word -> value */
X       char    *w_text;        /* input word */
X       unsigned w_value;       /* value for it */
X} WORD;
X
X/* These are the modes that are used when SANE is specified. */
X
X#define MODE_ISANE   (BRKINT | IGNPAR | ISTRIP | ICRNL | IXON)
X#define MODE_OSANE   (OPOST | ONLCR)
X#define MODE_CSANE   (CS8 | CREAD | HUPCL)
X#define MODE_LSANE   (ISIG | ICANON | ECHO | ECHOE | ECHOK)
X
X/* This is the default terminal entry; it is used if none is available from
X   the definition file. */
X
XTTYDEF Default_def = {
X       "default",              /* fake speed name */
X
X       /* initial modes */
X
X       ICRNL,          0,              DFLT_SPEED | MODE_CSANE,
X       0,              LDISC0,
X       { CINTR, CQUIT, CERASE, CKILL, CEOF },
X
X       /* final modes */
X
X       MODE_ISANE,     MODE_OSANE,     DFLT_SPEED | MODE_CSANE,
X       MODE_LSANE,     LDISC0,
X       { CINTR, CQUIT, CERASE, CKILL, CEOF },
X
X       7, "LOGIN: ",           /* prompt */
X       "default"               /* next speed entry */
X};
X
X/* These tables define the input words for setting modes. */
X
XWORD W_imodes[] = {
X    "IGNBRK", IGNBRK, "BRKINT", BRKINT, "IGNPAR", IGNPAR, "PARMRK", PARMRK,
X    "INPCK",  INPCK,  "ISTRIP", ISTRIP, "INLCR",  INLCR,  "IGNCR",  IGNCR,
X    "ICRNL",  ICRNL,  "IUCLC",  IUCLC,  "IXON",   IXON,   "IXANY",  IXANY,
X    "IXOFF",  IXOFF,
X0, 0};
X
XWORD W_omodes[] = {
X    "OPOST",  OPOST,  "OLCUC",  OLCUC,  "ONLCR",  ONLCR,  "OCRNL",  OCRNL,
X    "ONOCR",  ONOCR,  "ONLRET", ONLRET, "OFILL",  OFILL,  "OFDEL",  OFDEL,
X    "NL0",    NL0,    "NL1",    NL1,    "CR0",    CR0,    "CR1",    CR1,
X    "CR2",    CR2,    "CR3",    CR3,    "TAB0",   TAB0,   "TAB1",   TAB1,
X    "TAB2",   TAB2,   "TAB3",   TAB3,   "BS0",    BS0,    "BS1",    BS1,
X    "VT0",    VT0,    "VT1",    VT1,    "FF0",    FF0,    "FF1",    FF1,
X0, 0};
X
XWORD W_cmodes[] = {
X    "B0",     B0,     "B50",    B50,    "B75",    B75,    "B110",   B110,
X    "B134",   B134,   "B150",   B150,   "B200",   B200,   "B300",   B300,
X    "B600",   B600,   "B1200",  B1200,  "B1800",  B1800,  "B2400",  B2400,
X    "B4800",  B4800,  "B9600",  B9600,  "B19200", B19200, "B38400", B38400,
X    "EXTA",   EXTA,   "EXTB",   EXTB,   "CS6",    CS6,    "CS7",    CS7,
X    "CS8",    CS8,    "CSTOPB", CSTOPB, "CREAD",  CREAD,  "PARENB", PARENB,
X    "PARODD", PARODD, "HUPCL",  HUPCL,  "CLOCAL", CLOCAL,
X0, 0};
X
XWORD W_lmodes[] = {
X    "ISIG",   ISIG,   "ICANON", ICANON, "XCASE",  XCASE,  "ECHO",   ECHO,
X    "ECHOE",  ECHOE,  "ECHOK",  ECHOK,  "ECHONL", ECHONL, "NOFLSH", NOFLSH,
X0, 0};
X
X/* These are the terminal types recognized by the program. */
X
XWORD W_terminals[] = {
X    "none", TERM_NONE,
X0, 0};
X
X/* These are the line disciplines recognized by the program. */
X
XWORD W_linedisc[] = {
X    "LDISC0", LDISC0,
X0, 0};
X
X/* Global variables. */
X
Xchar    **Argv;                 /* save for argv; used to change name in ps */
Xint     Check_definitions;      /* -c option specified */
Xchar    *Definition_file = GETTYDEFS; /* definition file to read from */
Xint     Hang_up_on_init = 1;    /* do hang up after terminal opened, ! -h */
Xint     Line_discipline;        /* line discipline from the command line */
XFILE    *Log_file;              /* file to log to, null if none */
Xint     Our_pid;                /* result of getpid() */
Xint     Parent_is_init;         /* set if getppid() returns 1 */
Xint     Pause_on_exit;          /* do a pause on an error exit */
Xchar    *Program_name;          /* base name of argv[0] */
Xint     Terminal_type;          /* terminal type from command line */
Xunsigned Timeout_length;        /* from command line, zero if no timeout */
Xint     Tmout_after_first;      /* -T: start timeout after first char read */
XTTYDEF  Tty_def;                /* current definition entry */
Xchar    *Tty_line;              /* terminal device name, from the cmd line */
X
X/* Forward referenced functions. */
X
Xint     find_tty_def();
Xvoid    initialize_at_speed();
Xvoid    initialize_terminal();
Xvoid    parse_arguments();
Xvoid    read_login_args();
X
Xint
Xmain(argc, argv)
Xint     argc;
Xchar    **argv;
X{
X       char    next_label[LINELEN];
X
X       /* Random initialization. */
X
X       Our_pid = getpid();
X       Parent_is_init = getppid() == 1;
X       Argv = argv;
X
X       /* Parse our arguments; the results go in globals. This does not
X          return if there is an argument error or if the -c option was
X          specified. */
X
X       parse_arguments(argc, argv);
X
X       /* Initialize the terminal. Then cycle through the speed entries till
X          a successful read of the arguments occurs or an error occurs. */
X
X       initialize_terminal();
X       while (1) {
X               initialize_at_speed();
X               read_login_args();
X               strcpy(next_label, Tty_def.td_nxtlbl);
X               (void)find_tty_def(next_label);
X       }
X}
X
X/* Write to the log file. This routine prepends to each line in the log file
X   the terminal name, our pid, and a time stamp. */
X
Xvoid
Xlog_fputs(s)
Xchar    *s;                     /* string to write to the log file */
X{
X       char    *ptr;           /* pointer into the string */
X       time_t  clock;          /* current time */
X       static int begin = 1;   /* set to write the start of a line */
X
X       if (!Log_file) {
X               return;
X       }
X       while (*s) {
X               if (begin) {
X                       clock = time((long *)0);
X                       if (Tty_line) {
X                               (void)fprintf(Log_file, "%s", Tty_line);
X                       } else {
X                               (void)fprintf(Log_file, "<none>");
X                       }
X                       (void)fprintf(Log_file, " %d %.24s: ",
X                           Our_pid, ctime(&clock));
X                       begin = 0;
X               }
X               if (!(ptr = strchr(s, '\n'))) {
X                       (void)fputs(s, Log_file);
X                       break;
X               }
X               ++ptr;
X               (void)fwrite(s, 1, ptr - s, Log_file);
X               (void)fflush(Log_file);
X               begin = 1;
X               s = ptr;
X       }
X}
X
X/* If our parent is init, getty should not ever error exit; it should exec
X   another program or exit in order to have a new getty. However, when an
X   unrecoverable error occurs, something has to be done. What we'll do is set
X   the argument vector so that a ps will show the getty to have failed. Then
X   pause, and if an interrupt is received, exit. */
X
Xvoid
Xdo_exit(n)
Xint     n;                      /* exit code to return to caller */
X{
X       log_fputs("exiting\n");
X       if (Pause_on_exit) {
X               (void)alarm(0);
X               strcpy(Argv[0], BADNAME);
X               (void)signal(SIGHUP, SIG_IGN);
X               (void)close(TTY_IN);
X               (void)close(TTY_OUT);
X               (void)close(TTY_ERR);
X               (void)pause();
X       }
X       exit(n);
X}
X
X/* This is called when a timeout or other signal occurs.  */
X
Xvoid
Xexit_on_signal(sig)
Xint     sig;                    /* signal number that was caught */
X{
X       char    buf[LINELEN];
X
X       sprintf(buf, "exiting on signal %d\n", sig);
X       log_fputs(buf);
X       exit(1);
X}
X
X/* Start the terminal timeout alarm. */
X
Xvoid
Xstart_timeout()
X{
X       if (Timeout_length) {
X               (void)signal(SIGALRM, exit_on_signal);
X               (void)alarm(Timeout_length);
X               Timeout_length = 0;
X       }
X}
X
X/* Append text to the console output buffer. When this routine is called with
X   a null pointer, flush that buffer. The hope here is that writing the
X   message with one system call will get it out as one message. The message
X   will also be written to the log file. */
X
Xvoid
Xadd_to_msg(msg)
Xchar    *msg;                   /* string to add to error message */
X{
X       register int    tmp;
X       static int      pos;    /* index into msgbuf */
X       static char     msgbuf[LINELEN]; /* message buffer */
X
X       if (!msg) {
X               msgbuf[pos] = 0;
X               if (Parent_is_init) {
X                       if ((tmp = open(CONSOLE, O_WRONLY)) >= 0) {
X                               (void)write(tmp, msgbuf, (unsigned)pos);
X                               (void)close(tmp);
X                       }
X               } else {
X                       (void)write(TTY_ERR, msgbuf, (unsigned)pos);
X               }
X               log_fputs(msgbuf);
X               pos = 0;
X               return;
X       }
X       tmp = strlen(msg);
X       if (tmp + pos >= sizeof(msgbuf)) {
X               tmp = sizeof(msgbuf) - 1 - pos;
X       }
X       if (tmp) {
X               strncpy(msgbuf + pos, msg, tmp);
X               pos += tmp;
X       }
X}
X
X/* Print an error message. The error message has two text sections. */
X
Xvoid
Xerror2(m1, m2)
Xchar    *m1;                    /* part one of the message */
Xchar    *m2;                    /* part two of the message */
X{
X       add_to_msg(Program_name);
X       add_to_msg(" on ");
X       add_to_msg(Tty_line);
X       add_to_msg(": ");
X       add_to_msg(m1);
X       add_to_msg(m2);
X       add_to_msg(".\n");
X       add_to_msg((char *)0);
X}
X
X/* Print an error message. The error message has one text section. */
X
Xvoid
Xerror1(msg)
Xchar    *msg;                   /* text to print */
X{
X       error2(msg, "");
X}
X
X/* Print an error message for a failed system function. This requires that
X   errno be set correctly. The text string provided as input goes in text
X   like: "when (message) got error: (error message)". */
X
Xvoid
Xsys_error(msg)
Xchar    *msg;                   /* text to insert into the message */
X{
X       char    buf[LINELEN];
X
X       if (errno < sys_nerr) {
X               sprintf(buf, "when %s got error: ", msg);
X               error2(buf, sys_errlist[errno]);
X       } else {
X               sprintf(buf, "when %s got error number %d", msg, errno);
X               error1(buf);
X       }
X}
X
X/* Print a string so that it is unambiguous when read and contains no control
X   characters. All nonprinting characters are displayed as some backslash
X   sequence, similar to the C escapes. Space is also printed this way. */
X
Xvoid
Xqprint(fp, buf, len)
Xregister FILE  *fp;             /* file to write to */
Xregister UCHAR *buf;            /* buffer to write from */
Xint     len;                    /* number of characters in tbe buffer */
X{
X       int     cc;             /* character from the write buffer */
X       UCHAR   *ebuf = buf + len; /* pointer to the end of the write buffer*/
X
X       for ( ; buf < ebuf; ) {
X               switch (cc = *buf++) {
X               case '\\': (void)fputs("\\\\",fp); break;
X               case '\n': (void)fputs("\\n", fp); break;
X               case '\t': (void)fputs("\\t", fp); break;
X               case '\v': (void)fputs("\\v", fp); break;
X               case '\b': (void)fputs("\\b", fp); break;
X               case '\r': (void)fputs("\\r", fp); break;
X               case '\f': (void)fputs("\\f", fp); break;
X               case ' ':  (void)fputs("\\ ", fp); break;
X               default:
X                       if (isprint(cc)) {
X                               (void)putc(cc, fp);
X                       } else {
X                               (void)fprintf(fp, "\\%03o", cc);
X                       }
X                       break;
X               }
X       }
X}
X
X/* Print a string to the log file using qprint. This eliminates control
X   characters and makes each space explicit.  */
X
Xvoid
Xlog_qprint(msg, buf, len)
Xchar    *msg;                   /* text to print before the buffer */
XUCHAR   *buf;                   /* buffer to print */
Xint     len;                    /* number of characters in the buffer */
X{
X       if (!Log_file) {
X               return;
X       }
X       log_fputs(msg);
X       qprint(Log_file, buf, len);
X       log_fputs("\n");
X}
X
X/* Duplicates an fd. Checks for some errors, as unlikely as they may be. */
X
Xvoid
Xmy_dup2(fd, newfd)
Xint     fd;                     /* fd to duplicate */
Xint     newfd;                  /* fd it is supposed to become */
X{
X       int     ret;
X
X       if (close(newfd) < 0 && errno != EBADF) {
X               sys_error("closing an fd");
X               do_exit(1);
X       }
X       if ((ret = fcntl(fd, F_DUPFD, newfd)) != newfd) {
X               if (ret < 0) {
X                       sys_error("duping an fd");
X               } else {
X                       error1("dup didn't return expected fd");
X               }
X               do_exit(1);
X       }
X}
X
X/* An ugly: as init doesn't open any fd's for us, a regular open isn't going
X   to work because it will open one of the fd's we would use for the
X   terminal. All this BS exists so that the fd ends up not being one of the
X   first three. */
X
Xvoid
Xlog_open(name)
Xchar    *name;                  /* name of file to log to. */
X{
X       int     fd;
X
X       if ((fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0660)) < 0) {
X               sys_error("opening log file");
X               do_exit(1);
X       }
X       my_dup2(fd, LOG_FD);
X       (void)close(fd);
X       if (!(Log_file = fdopen(LOG_FD, "a"))) {
X               sys_error("fdopening log file");
X               do_exit(1);
X       }
X}
X
X/* Write text to the terminal. This logs the text, writes it to the terminal,
X   and checks for errors. */
X
Xvoid
Xtty_write(buf, len)
Xchar    *buf;                   /* text to write */
Xint     len;                    /* number of characters to write */
X{
X       int     ret;            /* return value from the write */
X       char    errbuf[LINELEN]; /* buffer for generating error message */
X
X       if (!len) {
X               return;
X       }
X       log_qprint("write: ", (UCHAR *)buf, len);
X       if ((ret = write(TTY_OUT, buf, (unsigned)len)) < 0) {
X               sys_error("writing the terminal");
X               do_exit(1);
X       }
X       if (ret != len) {
X               sprintf(errbuf, "%d bytes weren't written", len - ret);
X               error1(errbuf);
X               do_exit(1);
X       }
X}
X
X/* Do an ioctl to the terminal. Make sure that it works. */
X
Xvoid
Xtty_ioctl(cmd, termio)
Xint     cmd;                    /* command to perform */
Xstruct termio *termio;          /* termio structure to use */
X{
X       if (ioctl(TTY_IN, cmd, termio) < 0) {
X               sys_error("doing ioctl for the terminal");
X               do_exit(1);
X       }
X}
X
X/* Scan through a keyword table and return the a pointer to the keyword entry.
X   If there is no matching entry, return null. */
X
XWORD *
Xfind_word(word, table)
Xregister char *word;            /* word to look for */
Xregister WORD *table;           /* table to look into */
X{
X       for ( ; table->w_text; ++table) {
X               if (strcmp(word, table->w_text) == 0) {
X                       return (table);
X               }
X       }
X       return (0);
X}
X
X/* Print a usage message and then exit. */
X
Xvoid
Xusage(msg)
Xchar    *msg;                   /* text to insert into the usage message */
X{
X       /* This funny logging is done to prevent the entire usage message from
X          being written to the log file. */
X
X       log_fputs("usage error\n");
X       log_fputs("exiting\n");
X       Log_file = 0;
X
X       /* Print the usage message and exit. */
X
X       add_to_msg(Program_name);
X       add_to_msg(": ");
X       add_to_msg(msg);
X       add_to_msg("\nusage: ");
X       add_to_msg(Program_name);
X       add_to_msg(" [-P] [-T] [-h] [-l logfile] [-t timeout]\n");
X       add_to_msg((char *)0);
X       add_to_msg("           line [speed [type [linedisc]]]\n");
X       add_to_msg((char *)0);
X       add_to_msg("       ");
X       add_to_msg(Program_name);
X       add_to_msg(" -c [file]\n");
X       add_to_msg((char *)0);
X       do_exit(1);
X}
X
X/* Parse our arguments. */
X
Xvoid
Xparse_arguments(argc, argv)
Xint     argc;
Xregister char **argv;
X{
X       int     nocheck;        /* flag was set that isn't legal with -c */
X       register WORD *wp;      /* return value from find_word */
X       char    *ptr;           /* pointer into argument */
X
X       /* Record the base part of the path name. */
X
X       if (Program_name = strrchr(argv[0], '/')) {
X               ++Program_name;
X       } else {
X               Program_name = argv[0];
X       }
X       /* Interpret the options. */
X
X       nocheck = 0;
X       opterr = 0;
X       while (1) {
X               switch (getopt(argc, argv, "PTchl:t:")) {
X               default:  usage("illegal option");
X               case -1:  break;
X               case 'P': /* Pause on exit. */
X                       Pause_on_exit = 1;
X                       nocheck = 1;
X                       continue;
X               case 'T': /* Start timeout after first character read. */
X                       Tmout_after_first = 1;
X                       nocheck = 1;
X                       continue;
X               case 'c': /* Check definition file. */
X                       Check_definitions = 1;
X                       continue;
X               case 'h': /* Don't hang up on init. */
X                       Hang_up_on_init = 0;
X                       nocheck = 1;
X                       continue;
X               case 'l': /* Write messages to log file. */
X                       log_open(optarg);
X                       nocheck = 1;
X                       continue;
X               case 't': /* Time out after specified interval. */
X                       for (ptr = optarg; *ptr; ) {
X                               if (!isdigit(*ptr)) {
X                                       usage("illegal digit");
X                               }
X                               Timeout_length *= 10;
X                               Timeout_length += *ptr++ - '0';
X                       }
X                       nocheck = 1;
X                       continue;
X               }
X               break;
X       }
X       if (Check_definitions && nocheck) {
X               usage("improper combination of options");
X       }
X       /* If the -c option was specified, call the find_tty_def routine to
X          parse the entire file and print it out. */
X
X       if (Check_definitions) {
X               if (optind < argc) {
X                       Definition_file = argv[optind++];
X               }
X               if (optind != argc) {
X                       usage("too many arguments");
X               }
X               if (Parent_is_init) {
X                       error1("-c is not legal when called from init");
X                       do_exit(1);
X               }
X               (void)find_tty_def((char *)0);
X               do_exit(0);
X       }
X       if (optind == argc) {
X               usage("not enough arguments");
X       }
X       /* Save the terminal line name */
X
X       Tty_line = argv[optind++];
X
X       /* If the speed was provided on the command line, get the entry from
X          the definition file for that speed. */
X
X       Tty_def = Default_def;
X       if (optind < argc) {
X               if (!find_tty_def(argv[optind])) {
X                       (void)find_tty_def((char *)0);
X               }
X               ++optind;
X       }
X       /* Get the terminal type, if specified. */
X
X       Terminal_type = TERM_NONE;
X       if (optind < argc) {
X               if (!(wp = find_word(argv[optind], W_terminals))) {
X                       error2("can't find terminal type ", argv[optind]);
X               } else {
X                       Terminal_type = wp->w_value;
X               }
X               ++optind;
X       }
X       /* Get the line discipline. */
X
X       Line_discipline = LDISC0;
X       if (optind < argc) {
X               if (!(wp = find_word(argv[optind], W_linedisc))) {
X                       error2("can't find line discipline ", argv[optind]);
X               } else {
X                       Line_discipline = wp->w_value;
X               }
X               ++optind;
X       }
X       /* One final check: are all arguments used up? */
X
X       if (optind != argc) {
X               usage("too many arguments");
X       }
X}
X
X/* Interpret a quoted character. It returns a pointer to the character right
X   after the quoted character. Note that escaped nuls don't work; they are
X   treated as an end of input. Why? Because it would have been too much
X   trouble to distinguish an escaped nul from a newline at the end of the
X   input buffer. */
X
Xchar *
Xqchar(ptr, rc)
Xregister char *ptr;             /* pointer after the backslash */
Xregister UCHAR *rc;             /* return the character value through here */
X{
X       register int cc;        /* character from the input */
X
X       /* Deal with the alpha escapes and backslash null. */
X
X       switch (cc = *ptr++) {
X       case 'n': *rc = '\n';   return (ptr);
X       case 't': *rc = '\t';   return (ptr);
X       case 'v': *rc = '\v';   return (ptr);
X       case 'b': *rc = '\b';   return (ptr);
X       case 'r': *rc = '\r';   return (ptr);
X       case 'f': *rc = '\f';   return (ptr);
X       case 0:   *rc = 0;      return (ptr - 1);
X       }
X       /* If nondigit, the quote does nothing. */
X
X       if (cc < '0' || cc > '7') {
X               *rc = cc;
X               return (ptr);
X       }
X       /* Interpret 1-3 digits. */
X
X       *rc = cc - '0';
X       cc = *ptr;
X       if (cc < '0' || cc > '7') {
X               return (ptr);
X       }
X       *rc <<= 3;
X       *rc += cc - '0';
X       cc = *++ptr;
X       if (cc < '0' || cc > '7') {
X               return (ptr);
X       }
X       *rc <<= 3;
X       *rc += cc - '0';
X       return (ptr + 1);
X}
X
X/* Extract a word from some text. It returns a pointer to the next word or the
X   end of the input. A failure to extract the word is signaled by returning
X   null. */
X
Xchar *
Xextract_word(ptr, obuf, size)
Xregister char *ptr;             /* buffer to extract from */
Xchar    *obuf;                  /* output buffer */
Xint     size;                   /* size of output buffer */
X{
X       register int cc;        /* character from the input */
X       UCHAR   qcc;            /* non-register char, for qchar */
X
X       /* Find the start of the word. */
X
X       do {
X               if (!(cc = *ptr++) || cc == '#') {
X                       return (0);
X               }
X       } while (isspace(cc));
X
X       /* Move characters to the output buffer. */
X
X       --size;
X       while (cc && !isspace(cc) && cc != '#') {
X               if (cc == '\\') {
X                       ptr = qchar(ptr, &qcc);
X                       cc = qcc;
X               }
X               if (size) {
X                       *obuf++ = cc;
X                       --size;
X               }
X               cc = *ptr++;
X       }
X       /* Terminate the buffer and return a pointer to the next word in the
X          input buffer. */
X
X       *obuf = 0;
X       --ptr;
X       while (isspace(*ptr)) {
X               ++ptr;
X       }
X       return (ptr);
X}
X
X/* Extract the termio specifications from a definition line. Return the end
X   of the definition or null on error. */
X
Xchar *
Xget_tty_modes(ptr, termio)
Xchar    *ptr;                   /* definition text */
Xregister struct termio *termio; /* termio structure to initialize */
X{
X       WORD    *wp;            /* return value from find_word */
X       char    word[LINELEN];  /* buffer for extracting words */
X
X       /* Clear the termio structure. */
X
X       termio->c_iflag = 0;
X       termio->c_oflag = 0;
X       termio->c_cflag = 0;
X       termio->c_lflag = 0;
X
X       /* Extract the definition words and store the results in the termio
X          structure. */
X
X       while (isspace(*ptr)) {
X               ++ptr;
X       }
X       while (*ptr != '#') {
X               if (!(ptr = extract_word(ptr, word, sizeof(word)))) {
X                       return (0);
X               }
X               if (strcmp(word, "SANE") == 0) {
X                       termio->c_iflag |= MODE_ISANE;
X                       termio->c_oflag |= MODE_OSANE;
X                       termio->c_cflag |= MODE_CSANE;
X                       termio->c_lflag |= MODE_LSANE;
X               } else if (wp = find_word(word, W_imodes)) {
X                       termio->c_iflag |= wp->w_value;
X               } else if (wp = find_word(word, W_omodes)) {
X                       termio->c_oflag |= wp->w_value;
X               } else if (wp = find_word(word, W_cmodes)) {
X                       termio->c_cflag |= wp->w_value;
X               } else if (wp = find_word(word, W_lmodes)) {
X                       termio->c_lflag |= wp->w_value;
X               } else if (Check_definitions) {
X                       (void)printf("Undefined: %s\n", word);
X               }
X       }
X       return (ptr + 1);
X}
X
X/* Look through the definition file for a definition with the requested label.
X   If the label is null, the first entry will be used. */
X
Xint
Xfind_tty_def(label)
Xchar    *label;                 /* label to look for */
X{
X       register char *ptr;     /* general character pointer */
X       register int cc;        /* current character */
X       register FILE *fp;      /* file pointer for definition file */
X       register int field;     /* <0 means no error, else field of error */
X       register char *nptr;    /* another character pointer */
X       UCHAR   qcc;            /* argument for qchar, can't be register */
X       char    line[BUFSIZ];   /* definition input buffer */
X       static char *fnames[] = { /* names of each input field */
X               "label",   "initial flags", "final flags",
X               "message", "next label",
X       };
X       /* Open the definition file. */
X
X       if (!(fp = fopen(Definition_file, "r"))) {
X               sys_error("opening definition file");
X               Tty_def = Default_def;
X               return (0);
X       }
X       while (1) {
X               /* Read one definition from the file; the definitions are
X                  separated by blank lines. */
X
X               ptr = line;
X               *ptr++ = ' ';
X               while (1) {
X                       if ((cc = getc(fp)) == EOF) {
X                               if (ptr == line + 1) {
X                                       (void)fclose(fp);
X                                       Tty_def = Default_def;
X                                       return (0);
X                               }
X                               if (ptr[-1] == '\n') {
X                                       --ptr;
X                               }
X                               break;
X                       } else if (cc == '\n' && ptr[-1] == '\n') {
X                               --ptr;
X                               break;
X                       }
X                       if (ptr == &line[sizeof(line)]) {
X                               if (Check_definitions) {
X                                       (void)printf("Entry too long.\n");
X                               }
X                               while (cc != EOF) {
X                                       if (cc == '\n') {
X                                               cc = getc(fp);
X                                               if (cc == '\n') {
X                                                       break;
X                                               }
X                                       } else {
X                                               cc = getc(fp);
X                                       }
X                               }
X                               ptr = line + 1;
X                               break;
X                       }
X                       *ptr++ = cc;
X               }
X               *ptr = 0;
X
X               /* Ignore blank and comment lines. Lines that are too long
X                  also get rejected here. */
X
X               if (!line[1] || line[1] == '#') {
X                       continue;
X               }
X               if (Check_definitions) {
X                       (void)printf("\n**** Next Entry ****\n%s\n\n",
X                           line + 1);
X               }
X               /* Extract the label of the line. */
X
X               ptr = line + 1;
X               field = -1;
X               if (nptr = extract_word(ptr, Tty_def.td_label,
X                   sizeof(Tty_def.td_label))) {
X                       ptr = nptr;
X                       if (*ptr != '#') {
X                               field = 0;
X                       } else {
X                               ++ptr;
X                       }
X               } else {
X                       field = 0;
X               }
X               /* Extract the initial flags. */
X
X               if (field < 0) {
X                       if (nptr = get_tty_modes(ptr, &Tty_def.td_initial)) {
X                               ptr = nptr;
X
X                               /* Since not just any modes can be used by
X                                  getty, fix up the modes as necessary. */
X
X                               Tty_def.td_initial.c_iflag &= ICRNL;
X                               if (!(Tty_def.td_initial.c_cflag & CSIZE)) {
X                                       Tty_def.td_initial.c_cflag |= CS8;
X                               }
X                               Tty_def.td_initial.c_cflag |= CREAD | HUPCL;
X                               Tty_def.td_initial.c_lflag = 0;
X                       } else {
X                               field = 2;
X                       }
X               }
X               /* Extract the final flags. */
X
X               if (field < 0) {
X                       if (nptr = get_tty_modes(ptr, &Tty_def.td_final)) {
X                               ptr = nptr;
X                       } else {
X                               field = 3;
X                       }
X               }
X               /* Extract the login message. */
X
X               if (field < 0) {
X                       for (nptr = Tty_def.td_logmsg;
X                           nptr < &Tty_def.td_logmsg
X                             [sizeof(Tty_def.td_logmsg)];
X                           *nptr++ = cc) {
X                               cc = *ptr++;
X                               if (!cc || cc == '#') {
X                                       break;
X                               }
X                               if (cc == '\\') {
X                                       ptr = qchar(ptr, &qcc);
X                                       cc = qcc;
X                               }
X                       }
X                       Tty_def.td_msglen = nptr - Tty_def.td_logmsg;
X                       if (cc != '#') {
X                               field = 4;
X                       }
X               }
X               /* And finally, extract the next label. */
X
X               if (field < 0) {
X                       if (nptr = extract_word(ptr, Tty_def.td_nxtlbl,
X                           sizeof(Tty_def.td_nxtlbl))) {
X                               ptr = nptr;
X                               if (*ptr) {
X                                       field = 5;
X                               }
X                       } else {
X                               field = 5;
X                       }
X               }
X               /* Display the definition if requested. */
X
X               if (Check_definitions) {
X                       (void)printf("label: %s\n", Tty_def.td_label);
X                       (void)printf(
X"initial flags: iflag %6o oflag %6o cflag %6o lflag %6o\n",
X                           Tty_def.td_initial.c_iflag,
X                           Tty_def.td_initial.c_oflag,
X                           Tty_def.td_initial.c_cflag,
X                           Tty_def.td_initial.c_lflag);
X                       (void)printf(
X"final flags:   iflag %6o oflag %6o cflag %6o lflag %6o\n",
X                           Tty_def.td_final.c_iflag,
X                           Tty_def.td_final.c_oflag,
X                           Tty_def.td_final.c_cflag,
X                           Tty_def.td_final.c_lflag);
X                       (void)printf("message: ");
X                       qprint(stdout, (UCHAR *)Tty_def.td_logmsg,
X                           Tty_def.td_msglen);
X                       (void)printf("\nnext label: %s\n", Tty_def.td_nxtlbl);
X                       if (field >= 0) {
X                               *++ptr = 0;
X                               (void)printf(
X"Error in the \"%s\" field\n%s<--error detected here\n",
X                                   fnames[field], line + 1);
X                       }
X
X               /* Else check if this is the definition wanted. */
X
X               } else if (field < 0
X                   && (!label || strcmp(label, Tty_def.td_label) == 0)) {
X                       break;
X               }
X       }
X       (void)fclose(fp);
X       return (1);
X}
X
X/* This routine is called to set up the terminal for our use. It does all the
X   signal handling, mode changes, file opening, accounting, etc. that is
X   needed before we actually use the terminal. */
X
Xvoid
Xinitialize_terminal()
X{
X       register struct utmp *uptr; /* used to scan the utmp file */
X       int     fd;             /* fd for the terminal */
X       FILE    *fp;            /* file pointer for wtmp file */
X
X       log_fputs("starting\n");
X
X       /* Ignore keyboard interrupt signals. Sighup and sigterm cause an
X          exit, however. Make us a process group header, thus detaching us
X          from our old controlling terminal, if there was one. */
X
X       (void)signal(SIGINT,  SIG_IGN);
X       (void)signal(SIGQUIT, SIG_IGN);
X       (void)signal(SIGHUP,  exit_on_signal);
X       (void)signal(SIGTERM, exit_on_signal);
X       (void)setpgrp();
X
X       /* Change the owner and mode of the terminal line. They are to have
X          the effective user and group id of the program. Also, the terminal
X          modes are set to 640 to prevent "other" access to the terminal. */
X
X       if (chdir("/dev") < 0) {
X               sys_error("doing chdir to /dev");
X               do_exit(1);
X       }
X       if (chown(Tty_line, (int)geteuid(), (int)getegid()) < 0) {
X               sys_error("changing owner of terminal");
X               do_exit(1);
X       }
X       if (chmod(Tty_line, 0640) < 0) {
X               sys_error("changing mode of terminal");
X               do_exit(1);
X       }
X       /* Open up the terminal and make our standard fd's point to the
X          terminal. From now on, all terminal I/O is done to the opened
X          terminal. */
X
X       if ((fd = open(Tty_line, O_RDWR)) < 0) {
X               sys_error("opening the terminal");
X               do_exit(1);
X       }
X       if (fd != TTY_IN) {
X               my_dup2(fd, TTY_IN);
X       }
X       my_dup2(fd, TTY_OUT);
X       my_dup2(fd, TTY_ERR);
X       if (fd != TTY_IN) {
X               (void)close(fd);
X       }
X       /* Look for an entry in /etc/utmp for us. If one is found, modify the
X          fields in it appropriately, write it back to the file, and append
X          the modified entry to /etc/wtmp. */
X
X       while (uptr = getutent()) {
X               if (uptr->ut_type == INIT_PROCESS
X                   && uptr->ut_pid == Our_pid) {
X                       strncpy(uptr->ut_line, Tty_line,
X                           sizeof(uptr->ut_line));
X                       strncpy(uptr->ut_user, "LOGIN",
X                           sizeof(uptr->ut_user));
X                       uptr->ut_type = LOGIN_PROCESS;
X                       uptr->ut_time = time((long *)0);
X                       (void)pututline(uptr);
X                       if (fp = fopen(WTMP_FILE, "a")) {
X                               (void)fwrite(uptr, sizeof(*uptr), 1, fp);
X                               (void)fclose(fp);
X                       }
X                       break;
X               }
X       }
X       endutent();
X
X       /* Unless the -h option was specified, hang up the line. */
X
X       if (Hang_up_on_init) {
X               struct termio termio;
X
X               (void)signal(SIGHUP, SIG_IGN);
X               tty_ioctl(TCGETA, &termio);
X               termio.c_cflag &= ~CBAUD;
X               termio.c_cflag |= B0;
X               tty_ioctl(TCSETAF, &termio);
X               (void)signal(SIGHUP, exit_on_signal);
X       }
X       /* If requested, set up a timer. This will cause the getty to exit if
X          a login hasn't occured. */
X
X       if (!Tmout_after_first) {
X               start_timeout();
X       }
X}
X
X/* Change the modes of the line to correspond to the current initial flags.
X   Then write the connect message to the terminal. */
X
Xvoid
Xinitialize_at_speed()
X{
X       register char *ptr;     /* pointer into connect message buffer */
X       register int cc;        /* character from the connect message file */
X       FILE    *fp;            /* file pointer for the connect message */
X       struct termio termio;   /* used to set the terminal modes */
X       char    buffer[LINELEN]; /* buffer for the connect message */
X
X       log_fputs("new speed label ");
X       log_fputs(Tty_def.td_label);
X       log_fputs("\n");
X
X       /* Set the terminal modes from the initial flags. */
X
X       tty_ioctl(TCGETA, &termio);
X       termio.c_iflag = Tty_def.td_initial.c_iflag;
X       termio.c_oflag = Tty_def.td_initial.c_oflag;
X       termio.c_cflag = Tty_def.td_initial.c_cflag;
X       termio.c_lflag = Tty_def.td_initial.c_lflag;
X       termio.c_line = Line_discipline;
X       termio.c_cc[VMIN] = 1;
X       termio.c_cc[VTIME] = 0;
X       tty_ioctl(TCSETAF, &termio);
X
X       /* Write the connect message. */
X
X       tty_write("\r\n", 2);
X       if (!(fp = fopen(ISSUE, "r"))) {
X               return;
X       }
X       do {
X               ptr = buffer;
X               while (1) {
X                       switch (cc = getc(fp)) {
X                       case EOF: break;
X                       case '\n':
X                               *ptr++ = '\r';
X                               *ptr++ = '\n';
X                               break;
X                       default:
X                               *ptr++ = cc;
X                               if (ptr == buffer + sizeof(buffer) - 1) {
X                                       break;
X                               }
X                               continue;
X                       }
X                       break;
X               }
X               tty_write(buffer, ptr - buffer);
X       } while (cc != EOF);
X       (void)fclose(fp);
X}
X
X/* Read a line from the user. This routine only returns if the user requests a
X   speed change. */
X
Xvoid
Xread_login_args()
X{
X       register char *wptr;    /* pointer for storing into buffer */
X       register int tmp;
X       register char *rptr;    /* pointer for reading buffer */
X       UCHAR   cbuf;           /* character read from the terminal */
X       int     upper;          /* set if input has an upper case letter */
X       int     lower;          /* set if input has a lower case letter */
X       int     esc;            /* set if previous char was a \ */
X       struct termio termio;   /* used for setting the final modes */
X       char    *arglist[MAXARGS + 2]; /* argument list for login */
X       char    buffer[LINELEN]; /* input line */
X       static char ctlbuf[2] = "^"; /* buffer for printing ^@ controls */
X       static char backbuf[4] = "\\"; /* buffer for printing \xxx chars */
X
X       /* This loops till a valid login line is returned. */
X
X       wptr = buffer;
X       while (wptr == buffer) {
X               /* Print the login prompt. */
X
X               tty_write(Tty_def.td_logmsg, Tty_def.td_msglen);
X
X               /* Read the login argument line. */
X
X               upper = 0;
X               lower = 0;
X               esc = 0;
X               while (1) {
X                       /* Read a character. We shouldn't ever get an error,
X                          as all such should have been trapped somewhere. */
X
X                       if (read(TTY_IN, (char *)&cbuf, 1) < 0) {
X                               sys_error("reading the terminal");
X                               do_exit(1);
X                       }
X                       log_qprint("read: ", &cbuf, 1);
X                       tmp = cbuf;
X
X                       /* If there is to be a timeout and it hasn't already
X                          been started, start it now. This has the effect of
X                          starting the timeout after the first character
X                          read, if it wasn't started after the open. */
X
X                       start_timeout();
X
X                       /* This means a break from the user. */
X
X                       if (!tmp) {
X                               return;
X                       }
X                       /* Echo the character if it is printable. */
X
X                       if (isprint(tmp)) {
X                               tty_write((char *)&cbuf, 1);
X                       }
X                       /* If the previous character was a backslash, don't
X                          interpret this character. Note, that to make this
X                          work, we resort to a kludge. Since the following
X                          switch uses tmp, we'll set tmp to space. That
X                          forces the switch to put the character into the
X                          buffer. Of course, the default code must then
X                          reinitialize tmp from c. */
X
X                       if (esc) {
X                               tmp = ' ';
X                               esc = 0;
X                       }
X                       /* Process the character. */
X
X                       switch (tmp) {
X                       case '\b': /* backspace */
X                               if (wptr > buffer) {
X                                       --wptr;
X                                       tty_write("\b \b", 3);
X                                       if (!isprint(*wptr)) {
X                                               tty_write("\b \b", 3);
X                                       }
X                               }
X                               continue;
X                       case CERASE: /* non-control char backspace */
X                               if (wptr > buffer) {
X                                       --wptr;
X                               }
X                               continue;
X                       case CEOF: /* end of file */
X                               log_fputs("exiting because of EOF\n");
X                               exit(1);
X                       case 'C'&0x1F: /* control C, for interrupt */
X                       case 'U'&0x1F: /* control U, line kill */
X                       case CINTR: /* interrupt */
X                       case CQUIT: /* quit */
X                       case CKILL: /* line kill */
X                               wptr = buffer;
X                               break;
X                       case '\n': case '\r': /* end of line characters */
X                               *wptr = 0;
X                               break;
X                       case '\\': /* escape character */
X                               esc = 1;
X                               /* no break */
X                       default:
X                               /* See note just above the switch. */
X
X                               tmp = cbuf;
X
X                               /* If the character is not printing, it hasn't
X                                  been echoed. Now we'll print out those, but
X                                  we'll do it with printing characters. */
X
X                               if (!isprint(tmp)) {
X                                       if (tmp < 0x20) {
X                                               ctlbuf[1] = tmp | '@';
X                                               tty_write(ctlbuf, 2);
X                                       } else {
X                                               sprintf(backbuf + 1, "%03o",
X                                                   tmp);
X                                               tty_write(backbuf, 4);
X                                       }
X                               }
X                               /* If this is the last position in the buffer
X                                  the buffer is full. This could be just the
X                                  user typing a lot, but is more likely the
X                                  line going bonkers. Anyway, we'll exit so
X                                  that the line gets hung up. */
X
X                               if (wptr == buffer + sizeof(buffer) - 1) {
X                                       error1("input overflow");
X                                       do_exit(1);
X                               }
X                               if (islower(tmp)) {
X                                       lower = 1;
X                               } else if (isupper(tmp)) {
X                                       upper = 1;
X                               }
X                               *wptr++ = tmp;
X                               continue;
X                       }
X                       break;
X               }
X               tty_write("\r\n", 2);
X       }
X       log_qprint("args: ", (UCHAR *)buffer, wptr - buffer);
X
X       /* Since we now have a valid input line, turn off the timer. */
X
X       (void)alarm(0);
X
X       /* Change the terminal modes to those specified in the final flags. */
X
X       termio.c_iflag = Tty_def.td_final.c_iflag;
X       termio.c_oflag = Tty_def.td_final.c_oflag;
X       termio.c_cflag = Tty_def.td_final.c_cflag;
X       termio.c_lflag = Tty_def.td_final.c_lflag;
X       termio.c_line = Line_discipline;
X
X       /* Do one kludge: if a return was the line terminator, arrange that
X          returns are used for I/O instead of line feeds. */
X
X       if (tmp == '\r') {
X               termio.c_iflag |= ICRNL;
X               termio.c_oflag |= ONLCR;
X       }
X       /* Set up the terminal control characters. */
X
X       for (tmp = NCC; --tmp >= 0; ) {
X               termio.c_cc[tmp] = 0;
X       }
X       termio.c_cc[VINTR]  = CINTR;
X       termio.c_cc[VQUIT]  = CQUIT;
X       termio.c_cc[VERASE] = CERASE;
X       termio.c_cc[VKILL]  = CKILL;
X       termio.c_cc[VEOF]   = CEOF;
X
X       /* Do the next kludge: if the input contained an upper case letter but
X          did not contain a lower case letter, arrange for case translation
X          on I/O. While we're at it, translate all upper case letters to
X          lower case. */
X
X       if (upper && !lower) {
X               termio.c_iflag |= IUCLC;
X               termio.c_oflag |= OLCUC;
X               termio.c_lflag |= XCASE;
X               for (wptr = buffer; *wptr; ++wptr) {
X                       if (isupper(*wptr)) {
X                               *wptr = tolower(*wptr);
X                       }
X               }
X       }
X       /* Establish the new terminal modes. */
X
X       tty_ioctl(TCSETAW, &termio);
X
X       /* Split the input line into arguments. */
X
X       arglist[0] = LOGCMDNAME;
X       rptr = wptr = buffer;
X       for (tmp = 0; tmp < MAXARGS; ++tmp) {
X               while (isspace(*rptr)) {
X                       ++rptr;
X               }
X               if (!*rptr) {
X                       break;
X               }
X               arglist[tmp + 1] = wptr;
X               while (*rptr) {
X                       if (isspace(*rptr)) {
X                               ++rptr;
X                               break;
X                       }
X                       if (*rptr == '\\') {
X                               rptr = qchar(rptr + 1, &cbuf);
X                               *wptr++ = cbuf;
X                       } else {
X                               *wptr++ = *rptr++;
X                       }
X               }
X               *wptr++ = 0;
X       }
X       arglist[tmp + 1] = 0;
X       log_fputs("execing login\n");
X
X       /* Run the login program. */
X
X       (void)execv(LOGCMD, arglist);
X       sys_error("execing login program");
X       do_exit(1);
X}
*-*-END-of-getty.c-*-*
exit