Autobaud getty replacement for System V Machines
Karl Denninger
karl at ddsw1.MCS.COM
Mon Jan 21 06:41:05 AEST 1991
A program to replace getty on System V machines, this autobauder shares
ports with UUCP and CU easily. See distribution restrictions below.
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of shell archive."
# Contents: autouu.c
# Wrapped by karl at ddsw1 on Sun Jan 20 13:40:17 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'autouu.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'autouu.c'\"
else
echo shar: Extracting \"'autouu.c'\" \(19854 characters\)
sed "s/^X//" >'autouu.c' <<'END_OF_FILE'
X/*
X * Copyright 1990 MCS & Karl Denninger. All rights reserved.
X *
X * Public use is permitted under the following conditions:
X *
X * 1) You do not remove my name from the package, or claim you wrote it.
X * 2) You distribute ORIGINAL source code with all distributions made,
X * modified or not, binary or source.
X * 3) You do not attempt to sell the package, or use it to enhance the
X * commercial value of any product or service.
X * 4) This package is distributed with ABSOLUTELY NO WARRANTY OF ANY
X * KIND. If it melts your system to slag YOU are responsible, not
X * MCS or myself. The burden rests with you to perform adaquate
X * testing before turning this loose on unsuspecting users.
X *
X * Commercial distribution rights reserved; contact MCS at (708) 808-7200
X * for details on commercial distribution licensing.
X *
X * Compile with: cc -s -o autouu autouu.c -lc_s -lx
X *
X */
X/* Autobaud program
X
X Run in place of 'getty', this will prompt for a name
X and call login just like the old one used to do... Only
X difference is that it is rather interesting in it's interpretation
X of what a 'gettydefs' file is; that is, there isn't one.
X
X We use modem return messages to determine the baud rate. Locks are
X respected as well, allowing the uucp system to share the ports.
X
X You invoke this with:
X /etc/autonew ttyA2 [code] [file]
X
X from /etc/inittab. "[code]" is the numeric code for the baud rate
X to send the initialization string at -- most of the time you want
X this to be the highest baud rate your modem will support.
X
X Notes:
X 1) The device name does not have a prefix. It is prepended
X automatically (/dev/ is added).
X 2) For ISC, use the MODEM CONTROL PORTS. This program can
X interlock with UUCP; see their DEVICES file for the
X proper flags to set in the DEVICES and DIALERS files.
X Use the "new" definitions which have ",M" added (see your
X documentation for details).
X 3) While a port is being used for dialout, it will show
X up in a "who" command as "_Dialout" once data
X transmission begins.
X 4) Modes and owners will be changed on ports to prevent
X random users from using the ports for "cu"s and other
X communications uses. This can be easily changed if
X desired (look for the "chmod" call in the source).
X 5) The file /etc/autobaud.parm must be present if the "file"
X argument is missing. If the "file" argument is present,
X it points to the control file to be used. The format
X is as follows:
X First line -- initialization string for ports
X Second line -- response to initialization string
X Third line -- Generic "connected" message
X Up to first "#" alone -- baud codes, rates (text), and
X response strings expected.
X Next line -- Login prompt
X Remainder of file -- Issue file
X
X Baud codes are the speed codes from termio.h;
X 11, for example, is 2400 baud.
X
X An example /etc/autobaud.parm file:
X
X AAATE0Q0V1
X OK
X CONNECTED
X 7 300 CONNECT
X 9 1200 CONNECT 1200
X 11 2400 CONNECT 2400
X 13 9600 CONNECT 9600
X 14 19200 CONNECT FAST
X #
X Login:
X
X Welcome to the system
X <EOF>
X
X This is a typical file for a system containing both
X Telebit and low-speed modems (300-2400 baud). Note
X that the "AAA" is doubled to allow the Telebit to
X autosync. If you have hardware flow control then
X enable it -- otherwise, set the modem up for
X Xon/Xoff flow control, BREAK is sent and flushes,
X Telebit S66=0 and S58=254 (Autobaud and prefer
X 19200). This permits full functionality with the
X exception of low-speed UUCP inbound calls through
X Telebits; if you have hardware flow control then no
X restrictions apply.
X
X 6) Your I/O board and/or drivers MUST correctly support the
X notion of O_NDELAY. In addition, you have to be able to
X turn on and off the NDELAY flag with fcntl. LOTS of
X intelligent boards broke this; if it's broken this
X program will NOT work. ONE HACK: If your NDELAY
X interpretation returns non-blocking if CD is down (with
X CLOCAL set and NDELAY cleared) this program will function
X correctly, although it will eat a small portion of CPU
X time to do so.
X
X 7) Autobaud will wait for a carriage return and use it
X to determine the parity of the caller's terminal (either
X 8/N/1 or 7/E/1 only). If the user doesn't press anything
X within a reasonable time frame, 8/N/1 is assumed. The
X message "CONNECTED" is output to the user terminal
X immediately after autobaud senses the user's baud rate.
X
X 8) All modems served by a configuration must use the same
X response sequences, although subsets are permitted (ie: the
X example file above would work for a USR Courier 2400 and
X a Telebit Trailblazer Plus equally well).
X
X CHECK THE FUNCTIONS "checklock()" and "makelock()" -- they may need
X to be modified for your system! In particular, some systems use
X binary PIDs and/or store the lock file in a different place. We
X currently are set up for HDB UUCP on ISC 2.0.2/2.2.
X
X Note that this program can share a port with a modem dialing out on
X the same line! It will perform with uucp on the same port without
X trouble, so long as the locking is done correctly by uucp and other
X programs which expect lock files.
X
X Autobaud removes any stale lock files it finds automatically.
X
X*/
X
X#define MAXISSUE 100 /* Lines in /etc/issue file */
X#define MAXSECONDS 60 /* Timeout at start */
X#define LOGSECONDS 90 /* Timeout at login */
X#define UUCICO "/usr/lib/uucp/uucico" /* Where's uucico? */
X#define BELL 7 /* Makes a "beep" */
X
extern char *malloc();
X
X#include <stdio.h>
X#include <fcntl.h>
X#include <sys/types.h>
X#include <sys/tty.h>
X#include <utmp.h>
X#include <signal.h>
X#include <errno.h>
X#include <termio.h>
X#include <sys/stat.h>
X
X/* Globals */
X
int maschan = -1; /* Nothing initially; master channel */
int timeout = 0; /* Timer value for keeping track of time */
XFILE *ferr; /* Error channel */
X
slowwrite(chan, str, len) /* Write a string slowly to the port */
int chan, len;
char *str;
X{
X int x;
X char *ptr;
X char ch[2];
X
X ptr = str;
X for (x = 0; x < len; x++) {
X ch[0] = *ptr;
X write(chan, ch, 1);
X nap(10);
X ptr++;
X }
X return;
X}
X
checkmatch(matches, mcount, bfr, speed) /* Any matches in array? */
struct {
X char string[40];
X int baud;
X char speed[20];
X} matches[];
int mcount;
char bfr[];
char speed[];
X{
X int x = 0;
X
X for (x = 0; x < mcount; x++) {
X if (!strcmp(bfr, matches[x].string)) {
X strcpy(speed, matches[x].speed);
X return(matches[x].baud);
X }
X }
X return(0);
X}
X
X/* External declarations */
extern struct utmp *getutent(), *pututline();
X
X
X/* Makelock / Checklock - makes / checks for lock files
X line - the line to check for a lock
X lockflag - if non-zero, checklock will sleep until it sees the
X lock is gone, otherwise it returns status
X (checklock only)
X
Returns not zero (-1) if line is locked
X
X*/
X
int makelock(line)
char *line;
X{
X
char tmp[80];
char tmp2[80];
char tbfr[20];
int id;
XFILE *id2;
int pid;
struct stat st;
X
X sprintf(tmp, "/usr/spool/locks/LTMP..%d", getpid());
X sprintf(tmp2, "/usr/spool/locks/LCK..%s", line);
X if ((id = open(tmp, O_WRONLY|O_CREAT, 0660)) < 0) {
X exit(1);
X }
X sprintf(tbfr, "%10d\n", getpid());
X tbfr[11] = 0;
X write(id, tbfr, 11);
X close(id);
X if (!stat(UUCICO, &st)) { /* Find ownership */
X chown(tmp, st.st_uid, st.st_gid); /* Set owner/group */
X }
X while (link(tmp, tmp2)) {
X if ((id2 = fopen(tmp2, "r")) == (FILE *) NULL) {
X sleep(1); /* Slow down.. */
X continue;
X }
X fscanf(id2, "%d", &pid); /* Read PID from file */
X fclose(id2); /* Be nice.. */
X if (!kill(pid, 0)) { /* Oh oh, a process! */
X return(-1);
X }
X unlink(tmp2);
X }
X unlink(tmp);
X return(0);
X}
X
int checklock(line, lockflag)
char *line;
int lockflag; /* If non-zero, wait for open line */
X{
X
char ltmp[10];
char tmp[80];
int pid;
XFILE *id;
X
X strcpy(ltmp, line);
X sprintf(tmp, "/usr/spool/locks/LCK..%s", ltmp); /* Where are locks? */
X while (!access(tmp, 0)) { /* If file is there */
X if ((id = fopen(tmp, "r")) != (FILE *) NULL) { /* opened? */
X fscanf(id, " %d", &pid); /* Get pid from bfr */
X fclose(id); /* Clean up */
X if (kill(pid, 0)) { /* See if process is alive */
X if (errno == ESRCH) { /* Nope; it died */
X unlink(tmp); /* Clear lock */
X continue; /* Look again */
X }
X }
X if (lockflag) { /* IF waiting */
X sleep(1); /* Wait/keep going */
X } else {
X return(-1); /* Else return locked */
X }
X }
X }
X return(0); /* Line is clear */
X}
X
settogetty(line) /* Mark process in Getty */
char *line;
X{
X int pid;
X struct utmp *u;
X FILE *fid;
X
X pid = getpid(); /* Get our pid */
X while ((u = getutent()) != NULL) { /* While there are more lines */
X if (((u->ut_type == INIT_PROCESS) || (u->ut_type == USER_PROCESS)) && (u->ut_pid == pid)) {
X strcpy(u->ut_line, line); /* Set line name */
X strcpy(u->ut_user, "_Idle"); /* And name */
X u->ut_pid = getpid(); /* And pid */
X u->ut_type = LOGIN_PROCESS; /* And type */
X pututline(u); /* Do it */
X if ((fid = fopen(WTMP_FILE, "a+")) != NULL) {
X fseek(fid, 0L, 2); /* Seek end */
X fwrite((char *) u, sizeof(*u), 1, fid);
X fclose(fid); /* Wrote wtmp */
X }
X break;
X }
X }
X endutent();
X return;
X}
X
settologin(line) /* Tell the system we're at login state */
char *line;
X{
X int pid;
X struct utmp *u;
X
X pid = getpid(); /* Get our pid */
X while ((u = getutent()) != NULL) { /* While there are more lines */
X if ((u->ut_type == LOGIN_PROCESS) && (u->ut_pid == pid)) {
X strcpy(u->ut_line, line);
X strcpy(u->ut_user, "LOGIN"); /* Change name.. */
X pututline(u);
X }
X }
X endutent();
X return;
X}
X
setlocked(line) /* Fake a utmp entry for dialout lines (flagging) */
char *line;
X{
X int pid;
X struct utmp *u;
X
X pid = getpid(); /* Get our pid */
X while ((u = getutent()) != NULL) { /* While there are more lines */
X if ((u->ut_type == INIT_PROCESS) && (u->ut_pid == pid)) {
X strcpy(u->ut_line, line); /* Set line name */
X strncpy(u->ut_user, "_Dialout", 8); /* "User" */
X/* u->ut_type = LOGIN_PROCESS; *//* If invisible */
X u->ut_type = USER_PROCESS; /* It's visible */
X pututline(u);
X }
X }
X endutent();
X return;
X}
X
X/* Catch alarm signals
X Does two things -- catches the alarms, and also adds one to the
X seconds counter. If the user takes too long call 'hangup()' before
X returning */
X
catch()
X{
X signal(SIGALRM, catch); /* Re-enable signal catcher */
X if (timeout++ >= MAXSECONDS)
X hangup(); /* Kill the user if he just sat */
X return; /* Do nothing else, just exit */
X}
X
X/* Make upper case into lower case */
X
tlc(ptr)
char *ptr;
X{
X char *pt;
X int qm = 0;
X
X pt = ptr;
X while (*pt != 0) {
X if ((*pt >= 'A') && (*pt <= 'Z')) {
X *pt = *pt + 32;
X qm++;
X }
X pt++;
X }
X if (qm)
X printf("%c\nWarning: Please use *lower* case on this system%c\n", BELL, BELL);
X return;
X}
X
X
hangup() /* Make sure the phone gets hung up when we exit */
X{
X struct termio tt_array;
X
X if (maschan < 0)
X exit(1); /* Just exit */
X if (ioctl(maschan, TCGETA, &tt_array)) { /* No delay */
X perror("Get parameter error");
X exit(1);
X }
X tt_array.c_cflag &= ~(CBAUD|CLOCAL); /* Set baud to 0 */
X tt_array.c_cflag |= HUPCL|CREAD|CS8; /* Set hangup */
X
X if (ioctl(maschan, TCSETA, &tt_array)) { /* No delay */
X perror("Hang up error");
X exit(1);
X }
X exit(0);
X}
X
X/* Here is where all the fun begins; the main program */
X
main(argc, argv)
int argc;
char *argv[];
X{
X int debug = 0;
X int x, ch, sw, status;
X struct termio tt_array;
X FILE *fid, *fid2;
X static char tmp[133], loginp[132];
X extern struct utmp *getutent(), *pututline();
X char baud[132];
X char connected[512];
X int fbaud = 0;
X int sbaud = 0;
X char line[80];
X struct stat st;
X char init[80];
X char iresp[80];
X int initbaud;
X char speed[20];
X struct {
X char string[40];
X int baud;
X char speed[20];
X } matches[20];
X char *istring[MAXISSUE];
X int mcount = 0;
X int bcount = 0;
X int icount = 0;
X int lcount = 0;
X char bc[2];
X char bfr[20];
X int satisfied = 0;
X int hoseline;
X int quit = 0;
X int stop = 0;
X
X signal(SIGALRM, catch); /* Catch alarms */
X signal(SIGINT, SIG_IGN); /* Ignore interrupts */
X if (argc > 4)
X debug++;
X initbaud = atoi(argv[2]); /* Initial baud rate */
X strcpy(init, "ATZ\r"); /* Send this to init */
X if (argc > 3)
X fid = fopen(argv[3], "r"); /* Try to open it */
X else
X fid = fopen("/etc/autobaud.parm", "r"); /* Try to open it */
X if (fid != (FILE *) NULL) {
X fgets(init, 80, fid); /* Init string */
X init[strlen(init) - 1] = '\r'; /* Make last a return */
X fgets(tmp, 80, fid);
X sscanf(tmp, " %s", iresp);
X fgets(connected, 511, fid); /* Connected string */
X stop = 0;
X while ((!stop) && (fgets(tmp, 80, fid) != (char *) NULL)) {
X if ((tmp[0] == '#') && (strlen(tmp) <= 2)) {
X stop++;
X continue;
X }
X sscanf(tmp, "%d %[!-z] %[!-z ]", &matches[mcount].baud, matches[mcount].speed, matches[mcount].string);
X mcount++;
X }
X lcount = MAXISSUE;
X icount = 0;
X fgets(loginp, 80, fid); /* Get login prompt */
X if (strlen(loginp))
X loginp[strlen(loginp) - 1] = 0; /* Chop off L/F */
X while ((lcount) && (fgets(tmp, 132, fid) != (char *) NULL)) { /* Get line */
X istring[icount] = malloc(strlen(tmp) + 2);
X strcpy(istring[icount++], tmp);
X lcount--;
X }
X fclose(fid);
X }
X strcpy(line, argv[1]); /* Look on this line */
X setlocked(argv[1]); /* Set port id to locked */
X sprintf(tmp, "/dev/%s", line); /* Check the line */
X if (!stat(UUCICO, &st)) { /* Who owns uucp? */
X chown(tmp, st.st_uid, st.st_gid);/* Set line owner & group */
X chmod(tmp, 0660);
X }
X checklock(line, 1); /* Wait for no locks */
X settogetty(argv[1]); /* Mark us as in 'getty' */
X#ifndef DEBUG
X close(0); /* Close stdin,stdout,stderr */
X close(1);
X close(2);
X (void) setpgrp(); /* Make sure we have our own
X control terminal */
X#endif
X sprintf(tmp, "/dev/%s", argv[1]);
X if ((x = open(tmp, O_RDWR|O_NDELAY)) < 0) {/* BECOMES CONTROL TERM */
X exit(1); /* Exit; error! */
X } /* End of line.. */
X maschan = x; /* Master I/O channel */
X tt_array.c_cflag = (HUPCL|CLOCAL|CS8|CREAD);
X tt_array.c_cc[VMIN] = 1;
X tt_array.c_cc[VTIME] = 1; /* Set parameters */
X ioctl(maschan, TCFLSH, 2); /* Flush channel */
X if (ioctl(maschan, TCSETAW, &tt_array) == -1) {/* Set DTR down */
X exit(1);
X }
X sleep(1);
X tt_array.c_cflag |= initbaud; /* Set initial baud rate */
X checklock(line, 0); /* Exit if locked */
X if (ioctl(maschan, TCSETAW, &tt_array) == -1) {/* Set parameters */
X exit(1);
X }
X ioctl(maschan, TCFLSH, 2); /* Flush channel */
X sleep(1);
X if ((status = fcntl(x, F_GETFL, 0)) != -1) {
X status &= (~O_NDELAY);
X if (fcntl(x, F_SETFL, status)) { /* Clear O_NDELAY */
X exit(0);
X }
X }
X status = fcntl(maschan, F_GETFL, 0); /* Read it again to be sure */
X sleep(1); /* Allow modem to settle */
X checklock(line, 0); /* Exit if locked */
X timeout = 0; /* No timeout yet */
X slowwrite(maschan, init, strlen(init));/* Write initialization */
X if (debug)
X fprintf(ferr, "%x\n", status);
X signal(SIGALRM, hangup); /* Quit on timeout */
X bcount = 0;
X alarm(5); /* Wait 5 seconds tops */
X while (!quit) { /* Check return from init */
X if (!read(maschan, bc, 1)) { /* Look for a character */
X sleep(1);
X continue;
X }
X if (debug)
X fprintf(ferr, " %d\n", bc[0]);
X if ((bc[0] == 10) || (bc[0] == 13)) {
X if (strcmp(bfr, iresp)) {/* If not correct response */
X bcount = 0; /* Then drop it */
X continue; /* Keep looking */
X } else {
X quit++; /* Else done */
X }
X } else {
X bfr[bcount++] = bc[0]; /* Save character */
X bfr[bcount] = 0; /* Null terminate */
X }
X }
X dup(maschan);
X dup(maschan);
X ioctl(maschan, TCFLSH, 2);
X ioctl((maschan + 1), TCFLSH, 2);
X ioctl((maschan + 2), TCFLSH, 2);
X ferr = fopen("/dev/console", "w"); /* Write to console for errs */
X alarm(0); /* Turn off timeout */
X strcpy(line, argv[1]); /* Look on this line */
X if (checklock(line, 0)) /* Check lock status again */
X exit(0); /* Exit if line is locked */
X satisfied = 0;
X bcount = 0; /* Nothing in buffer */
X signal(SIGALRM, hangup);
X alarm(1800); /* 30 minutes idle time */
X while (!satisfied) { /* Read result codes */
X if (!read(maschan, bc, 1)) { /* Look for a character */
X sleep(2);
X continue;
X }
X if (checklock(line, 0)) /* Locked by someone else? */
X exit(0); /* Quit if so */
X if ((bc[0] == 10) || (bc[0] == 13)) { /* New line? */
X satisfied = checkmatch(matches, mcount, bfr, speed);
X if (!satisfied) {
X bcount = 0;
X }
X } else {
X bfr[bcount++] = bc[0]; /* Store character */
X bfr[bcount] = 0; /* Null terminate */
X }
X }
X alarm(0);
X if (makelock(line)) { /* This is VERY bad! */
X exit(0); /* Quit right now! */
X }
X ioctl(maschan, TCGETA, &tt_array);
X tt_array.c_cflag &= (~CBAUD); /* Clear baud rate bits */
X tt_array.c_cflag |= satisfied; /* Set new baud rate */
X ioctl(maschan, TCSETAW, &tt_array);
X sprintf(baud, "%s @ %s bps", line, speed);
X fid2 = fdopen(maschan, "a+"); /* Give us a stream channel please */
X ioctl(maschan, TCFLSH, 2); /* Flush input & output */
X sleep(1); /* Wait a second for settling... */
X fprintf(fid2, "%s\r\n", connected);
X fflush(fid2);
X signal(SIGALRM, catch);
X timeout = 0;
X alarm(10);
X if (read(maschan, bc, 1) > 0) {
X switch(bc[0]) {
X case 13:
X case 10:
X strcat(baud, ", 8/N/1");
X goto aexit; /* Do nothing */
X break;
X case -115: /* If signed... */
X case 141: /* 7/E/1, but otherwise ok */
X tt_array.c_cflag &= (~CS8);
X tt_array.c_cflag |= (CS7|PARENB);
X ioctl(maschan, TCSETAW, &tt_array);
X ioctl((maschan + 1), TCSETAW, &tt_array);
X ioctl((maschan + 2), TCSETAW, &tt_array);
X strcat(baud, ", 7/E/1");
X goto aexit;
X break;
X default:
X break;
X }
X }
aexit:;
X tt_array.c_cflag &= (~(CBAUD|CLOCAL));
X tt_array.c_cflag |= (satisfied|HUPCL); /* New baud rate */
X tt_array.c_oflag |= OPOST|ONLCR;
X
X/* If available, enable CTS flow control. This is necessary for
X Telebit Trailblazers and others of the general type. Unfortunately,
X only SCO has these things... (grr)
X*/
X
X#ifdef HARDWARE_FLOW
X tt_array.c_iflag |= BRKINT|IGNPAR|INPCK|ICRNL;
X tt_array.c_cflag |= HUPCL|CTSFLOW|RTSFLOW;
X#else
X tt_array.c_iflag |= BRKINT|IGNPAR|INPCK|ICRNL|IXON|IXANY;
X tt_array.c_cflag |= HUPCL;
X#endif
X tt_array.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHOK;
X tt_array.c_cc[VINTR] = 177;
X tt_array.c_cc[VQUIT] = 0;
X tt_array.c_cc[VERASE] = 8;
X tt_array.c_cc[VKILL] = 21;
X tt_array.c_cc[VEOF] = 4;
X tt_array.c_cc[VEOL] = 0;
X ioctl(maschan, TCSETAW, &tt_array); /* Set parameters */
X ioctl((maschan + 1), TCSETAW, &tt_array);
X ioctl((maschan + 2), TCSETAW, &tt_array);
X signal(SIGALRM, hangup); /* If we time out, hang up the line */
X alarm(LOGSECONDS); /* LOGSECONDS to read/reply, then out */
X fprintf(fid2, "\n[%s]\n", baud);/* Display parameters we found */
X for (x = 0; x < icount; x++) {
X fputs(istring[x], fid2);
X }
X fputs("\r\n", fid2); /* End with another <return> */
bg1:;
X fputs(loginp, fid2); /* Prompt for login name */
X fgets(tmp, 80, fid2); /* Read it, but don't allow overrun */
X if (*tmp == 10) /* If nothing, ask again */
X goto bg1;
X tmp[strlen(tmp)-1] = 0; /* Make sure null terminated */
X tlc(tmp); /* Lower case the name */
X signal(SIGINT, SIG_DFL); /* Reset signal handling */
X signal(SIGALRM, SIG_DFL);
X alarm(0); /* Clear alarm */
X fclose(ferr); /* Close error channel */
X/*
X * Take two shots at where login is. If we can't find it in either of these
X * places you're screwed; dump the caller. Try to tell the user if this
X * happens, but no guarantees....
X */
X
X execlp("/etc/login", "login", tmp, (char *) NULL);
X execlp("/bin/login", "login", tmp, (char *) NULL);
X fputs("Login not executable; contact administrator\n", fid2);
X fflush(fid2); /* Make sure it's printed */
X sleep(3); /* Wait for output to drain */
X exit(1); /* And quit with error (ignored) */
X}
X
END_OF_FILE
if test 19854 -ne `wc -c <'autouu.c'`; then
echo shar: \"'autouu.c'\" unpacked with wrong size!
fi
# end of 'autouu.c'
fi
echo shar: End of shell archive.
exit 0
--
Karl Denninger (karl at ddsw1.MCS.COM, <well-connected>!ddsw1!karl)
Public Access Data Line: [+1 708 808-7300], Voice: [+1 708 808-7200]
Macro Computer Solutions, Inc. "Quality Solutions at a Fair Price"
More information about the Alt.sources
mailing list