[net.sources] 4.2 BSD uucp and tip: dialin and dialout on same line

wls@astrovax.UUCP (William L. Sebok) (12/08/84)

: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting README'
sed 's/^X//' <<'//go.sysin dd *' >README
The following are diffs to the 4.2 distribution of uucp and tip to make them
able to use the same lines for both dialin in and dialing out.  This dialin-
dialout mechanism runs under a vanilla 4.2 BSD kernel.  Similar changes to
uucp and tip ran here when we were running 4.1 BSD.

We started to use uucp after the dialin lines had been in place for a while.
It would have been very unpopular here to remove one of lines from public use
to let uucp dial out.  The first version of these changes ran here early in
1983.

Having this ability is very useful way to make full use of your modems. For
one thing, I can smile when a neighoring site complains that their one dialer
is tied up from news backed up to one of their neighbors which has been down
for a while and has just come back up. For another thing some of our neighbors
seem to be reachable better by some modems than by others.  For example, I can
only reach rocky2 with a Racal Vadic.  I can only reach akgua, burl, and
sjuvax with a Hayes.  When we get our 2400 baud modem in a week or so I can
make special note of that --- and I don't have to buy two 2400 baud modems to
have two-way 2400 baud capability.

The key to this is a program is provided in the file acucntrl.c .  This should
be installed suid to root in the file /usr/local/lib/acucntrl . A set of diffs
for tip and uucp are also provided.

There have been other changes here to both tip and uucp so that the "after"
line numbers in the diffs will not match.  The "before" files are fresh off the
4.2 BSD distribution tape.  The most important other change here in uucp was
one to allow site names longer that 7 characters.


uucp notes:
The 3rd field of the L-devices file should contain "inout"  for all devices
intended for dialin/out use.  Devices without the "inout" specification will
be used like before: for dialout only. Changes are included to only disable
dialins on the line once per dialing session (rather than enabling it and
disabling it whenever another site is called).  Also included is the fix to
the nasty TIOCSPGP bug which caused all dialing attempts after the first
unsuccessful one to fail.

tip notes:
tip runs here setgid to uucp.  It assumes that /usr/local/lib/acucntrl is
executable by group uucp (in case you want to restrict general execute
permission on this program).  A change has been included to allow comma
separated entries in the "va" field of the remote file.  This is the field
that specifies the dialer type.  With more dialers available it is more likely
that they will be of different types.  If the number of "va" subfields is less
than the number of "dv" (i.e. device name) fields the device type that of
the last previously specified device.
Example from our own /etc/remote:
	:dv=/dev/ttyd2,/dev/ttyd1,/dev/ttyd0:at=hayes,vadic:
The first modem /dev/ttyd2 is a hayes.  The second two modems /dev/ttyd1 and
X/dev/ttyd0 are vadics.
It is assumed in tip that the dialin/dialout devices have names /dev/ttyd?.
Other names will require modifications.

Good luck.  I want to never go back to reserving modems for dialout.

Bill Sebok			Princeton University, Astrophysics
{allegra,akgua,burl,cbosgd,decvax,ihnp4,noao,princeton,vax135}!astrovax!wls
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 README
	/bin/echo -n '	'; /bin/ls -ld README
fi
/bin/echo 'Extracting acucntrl.8l'
sed 's/^X//' <<'//go.sysin dd *' >acucntrl.8l
X.TH acucntrl 8 local
X.SH NAME
X.B acucntrl \- turn around tty line between dialin and dialout
X.SH SYNOPSIS
X.B /usr/lib/local/acucntrl
keyword ttyline
X.SH DESCRIPTION
X.PP
X.I Acucntrl
turns around terminal line, enabling it to be used for both dialin and dialout.
On dialin a terminal line is assumed to have  modem control enabled and a getty
process in existence waiting for logins.  On dialout modem control is disabled
and there is no getty process.
X.PP
This program must be run setuid to root.
X.PP
X.I keyword
is chosen from the list:
X.I disable
or
X.IR dialout ,
to condition a line for dialout;
and
X.I enable
or
X.IR dialin ,
to condition a line for dialin.
X.PP
When the line is conditioned for dialing out, the login name of the real uid
of the process is placed in /etc/utmp in capitals.
This declares that the line is in use and acts as an additional locking
mechanism.
X.I Acucntrl
will refuse to act if the /etc/utmp entry for the line is not null,
is not the the user's login name (capitalized or not),
or if the process is running as the superuser.
The last condition is to allow the superuser to clear the state of the line.
X.PP
Turning modem control on or off is handled by poking into /dev/kmem.
It is currently implemented for dz, dh, and dmf lines.
X.PP
Under 4.2 BSD the program will also refuse to disable a line if carrier is
sensed on it.  The is to avoid the dead period where someone has just dialed
in and made the connection but has not yet logged in.
X.PP
X.I Ttyline
can be either of the form tty* or /dev/tty*.
Enabling/disabling a line whose name does not begin with ttyd? is prohibited
unless the real uid of the process is 0 or if the login name corresponding to
the real uid is uucp.  This is a security precaution.
X.PP 
Steps taken when disabling
X.RI ( i . e .
setup for dialing out)
X.IP 1)
check input arguments
X.IP 2)
look in /etc/utmp to check that the line is not in use by another user
X.IP 3)
disable modem control on line
X.IP 4)
check for carrier on device
X.IP 5)
change owner of device to real uid
X.IP 6)
edit /etc/ttys,  changing the first character of the appropriate line to 0
X.IP 7)
send a hangup to process 1 to poke init to disable getty
X.IP 8)
post uid name in capitals in /etc/utmp to let world know device has been grabbed
X.IP 9)
make sure that DTR is on
X.PP
Steps taken when enabling
X.RI ( i . e .
setup for dialin)
X.IP 1)
check input arguments
X.IP 2)
look in /etc/utmp to check that the line is not in use by another user
X.IP 3)
make sure modem control on line is disabled
X.IP 4)
turn off DTR to make sure line is hung up
X.IP 5)
condition line: clear exclusive use and set hangup on close modes
X.IP 6)
turn on modem control
X.IP 7)
edit /etc/ttys,  changing the first character of the appropriate line to 1
X.IP 8)
send a hangup to process 1 to poke init to enable getty
X.IP 9)
clear uid name for /etc/utmp
X.SH HISTORY
X.PP
First written by Allan Wilkes (fisher!allan)
X.PP
Modified June 8,1983 by W.Sebok (astrovax!wls) to poke the kernel rather
than use a kernel hack to turn on/off modem control, using a subroutine
stolen from a program written by Tsutomu Shimomura {astrovax,escher}!tsutomu
X.PP
Worked over many times by W.Sebok
X.RI ( i . e .
hacked to death)
X.SH FILES
X/dev/kmem, /vmunix, /etc/ttys, /etc/utmp, /dev/tty*
X.SH BUGS
X.PP
Sensing carrier requires the 4.2 BSD TIOCMGET ioctl call.  Unfortunately this
ioctl is not implemented in the vanilla 4.2 BSD dh driver even though the
dz and dmf drivers use an emulation of the DH11's modem control bits. This
has been fixed here.
X.PP
Some time (currently 2 seconds) is required between disabling modem control
and opening the device.  This is probably because of a race with getty whose
open is finally being allowed to complete.  This time interval may not be
enough on a loaded system.  Because of this problem and the above problem with
the dh driver there is deliberately no error message given when the TIOCMGET
ioctl fails.
X.PP
Previously there was similar synchronization problem with the init process.
When dialins are disabled the capitalized name of the process cannot be posted
into /etc/utmp until init has finished clearing /etc/utmp.  However one does
not know how long that will take, and, on a loaded system,  it can take quite
a while.  This was solved by the strategy of 1) posting the name, 2) poking
init, 3) going into a loop where the process repeatedly waits a second and
checks whether the entry has been cleared from /etc/utmp, and 4) posting the
name again.
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 acucntrl.8l
	/bin/echo -n '	'; /bin/ls -ld acucntrl.8l
fi
/bin/echo 'Extracting acucntrl.c'
sed 's/^X//' <<'//go.sysin dd *' >acucntrl.c
X/*  acucntrl - turn around tty line between dialin and dialout
 * 
 * Usage:	acucntrl {enable,disable} /dev/ttydX
 *
 * History:
 *	First written by Allan Wilkes (fisher!allan)
 *
 *	Modified June 8,1983 by W.Sebok (astrovax!wls) to poke kernel rather
 * 	than use kernel hack to turn on/off modem control, using subroutine
 *	stolen from program written by Tsutomu Shimomura
 *	{astrovax,escher}!tsutomu
 *
 *	Worked over many times by W.Sebok (i.e. hacked to death)
 *
 * Operation:
 *   disable (i.e. setup for dialing out)
 *	(1) check input arguments
 *	(2) look in /etc/utmp to check that the line is not in use by other
 *	(3) disable modem control on terminal
 *	(4) check for carrier on device
 *	(5) change owner of device to real id
 *	(6) edit /etc/ttys,  changing the first character of the appropriate
 *	    line to 0
 *	(7) send a hangup to process 1 to poke init to disable getty
 *	(8) post uid name in capitals in /etc/utmp to let world know device has
 *	    been grabbed
 *	(9) make sure that DTR is on
 *
 *   enable (i.e.) restore for dialin
 *	(1) check input arguments
 *	(2) look in /etc/utmp to check that the line is not in use by other
 *	(3) make sure modem control on terminal is disabled
 *	(4) turn off DTR to make sure line is hung up
 *	(5) condition line: clear exclusive use and set hangup on close modes
 *	(6) turn on modem control
 *	(7) edit /etc/ttys,  changing the first character of the appropriate
 *	    line to 1
 *	(8) send a hangup to process 1 to poke init to enable getty
 *	(9) clear uid name for /etc/utmp
 */

#include <sys/param.h>
#include <sys/buf.h>
#include <signal.h>
#ifdef SIGIO
#	define	BSD4_2		/* how else am I to know? */
#endif
#include <sys/conf.h>
#ifdef BSD4_2
#include "/sys/vaxuba/ubavar.h"
#else
#include <sys/ubavar.h>
#endif
#include <sys/stat.h>
#include <nlist.h>
#include <sgtty.h>
#include <utmp.h>
#include <pwd.h>
#include <stdio.h>

#define NDZLINE	8	/* lines/dz */
#define NDHLINE	16	/* lines/dh */
#define NDMFLINE 8	/* lines/dmf */

#define DZ11	1
#define DH11	2
#define DMF	3

#define NLVALUE(val)	(nl[val].n_value)

struct nlist nl[] = {
#define CDEVSW	0
	{ "_cdevsw" },

#define DZOPEN	1
	{ "_dzopen" },
#define DZINFO	2
	{ "_dzinfo" },
#define NDZ11	3
	{ "_dz_cnt" },
#define DZSCAR	4
	{ "_dzsoftCAR" },

#define DHOPEN	5
	{ "_dhopen" },
#define DHINFO	6
	{ "_dhinfo" },
#define NDH11	7
	{ "_ndh11" },
#define DHSCAR	8
	{ "_dhsoftCAR" },

#define DMFOPEN	9
	{ "_dmfopen" },
#define DMFINFO	10
	{ "_dmfinfo" },
#define NDMF	11
	{ "_ndmf" },
#define DMFSCAR	12
	{ "_dmfsoftCAR" },

	{ "\0" }
};

#define ENABLE	1
#define DISABLE	0

char Etcutmp[] = "/etc/utmp";
char Etcttys[] = "/etc/ttys";
char Devhome[] = "/dev";

char usage[] = "Usage: acucntrl {dis|en}able device\n";

struct utmp utmp;
char resettty, resetmodem;
int etcutmp;
long utmpoff;

#define NAMSIZ	sizeof(utmp.ut_name)
#define	LINSIZ	sizeof(utmp.ut_line)

main(argc, argv)
int argc; char *argv[];
{
	register char *p;
	register int i;
	char uname[NAMSIZ], Uname[NAMSIZ];
	int enable ;
	char *device;
	int devfile;
	int uid, gid;
	long lseek();
	struct passwd *getpwuid();
	char *rindex();
	extern int errno;
	extern char *sys_errlist[];

	/* check input arguments */
	if (argc!=3) {
		fprintf(stderr, usage);
		exit(1);
	}

	if (argc != 3) {
		fprintf(stderr, usage);
		exit(1);
	}
	if (prefix(argv[1], "disable")  || strcmp(argv[1],"dialout")==0)
		enable = 0;
	else if (prefix(argv[1], "enable")  || strcmp(argv[1],"dialin")==0)
		enable = 1;
	else {
		fprintf(stderr, usage);
		exit(1);
	}

	device = rindex(argv[2],'/');
	device = (device == NULL) ? argv[2]: device+1;

	/* Get nlist info */
	nlist("/vmunix",nl);

	/* Chdir to /dev */
	if(chdir(Devhome) < 0) {
		fprintf(stderr, "Cannot chdir to %s: %s\r\n",
			Devhome, sys_errlist[errno]);
		exit(1);
	}

	/* Get uid information */
	uid = getuid();
	gid = getgid();

	p = getpwuid(uid)->pw_name;
	if (p==NULL) {
		fprintf(stderr,"cannot get uid name\n");
		exit(1);
	}

	/*  to upper case */
	i = 0;
	do {
		uname[i] = *p;
		Uname[i] = (*p>='a' && *p<='z') ? (*p - ('a'-'A')) : *p;
		i++; p++;
	} while (*p && i<NAMSIZ);

	/* don't let just anyone do this */
	if (strncmp(device,"ttyd",4)!=0 && uid!=0 && strcmp(uname,"uucp")!=0) {
		fprintf(stderr,"Permission Denied.\n");
		exit(1);
	}

	/* check to see if line is being used */
	if( (etcutmp = open(Etcutmp, 2)) < 0) {
		fprintf(stderr,"On open %s open: %s\n",
			Etcutmp,sys_errlist[errno]);
		exit(1);
	}

	while(read(etcutmp, &utmp,sizeof(struct utmp)) == sizeof(struct utmp)) {
		if(strcmp(device, utmp.ut_line) == 0) {
			if (utmp.ut_name[0] == '\0'
			  || (upcase(utmp.ut_name,NAMSIZ)
	 		  && (uid==0
			    || strncmp(utmp.ut_name,Uname,NAMSIZ)==0))) {
				utmpoff
				  = lseek(etcutmp, (long)(-sizeof(utmp)), 1);
				break;
			} else {
				fprintf(stderr, "%s in use by %s\n", device,
					utmp.ut_name);
				exit(2);
			}
		}
	}

	/* Disable modem control */
	if (setmodem(device,DISABLE)<0) {
		fprintf(stderr,"Unable to disable modem control\n");
		exit(1);
	}


	if (enable) {
		if((devfile = open(device, 1)) < 0) {
			fprintf(stderr,"On open of %s: %s\n",
				device, sys_errlist[errno]);
			(void)setmodem(device,resetmodem);
			exit(1);
		}
		/* Try one last time to hang up */
		if (ioctl(devfile,TIOCCDTR,0) < 0)
			fprintf(stderr,"On TIOCCDTR ioctl: %s\n",
				sys_errlist[errno]);

		if (ioctl(devfile, TIOCNXCL,0) < 0)
			fprintf(stderr,
			    "Cannot clear Exclusive Use on %s: %s\n",
				device, sys_errlist[errno]);

		if (ioctl(devfile, TIOCHPCL,0) < 0)
			fprintf(stderr,
			    "Cannot set hangup on close on %s: %s\n",
				device, sys_errlist[errno]);

		i = resetmodem;

		if (setmodem(device,ENABLE) < 0) {
			fprintf(stderr,"Cannot Enable modem control\n");
			(void)setmodem(device,i);
			exit(1);
		}
		resetmodem=i;

		settys(device,ENABLE);

		pokeinit(device,Uname,enable);
		post(device,"");

	} else {
#ifdef TIOCMGET
		int linestat=0;
		sleep(2);	/* need time after modem control turnoff */
#endif TIOCMGET
		if((devfile = open(device, 1)) < 0) {
			fprintf(stderr,"On open of %s: %s\n",
				device, sys_errlist[errno]);
			(void)setmodem(device,resetmodem);
			exit(1);
		}
#ifdef TIOCMGET
		/* check for presence of carrier */
		(void)ioctl(devfile,TIOCMGET,&linestat);

		if (linestat&TIOCM_CAR) {
			fprintf(stderr,"%s is in use (Carrier On)\n",device);
			(void)setmodem(device,resetmodem);
			exit(2);
		}
#endif TIOCMGET
		/* chown device */
		if(chown(device, uid, gid) < 0)
			fprintf(stderr, "Cannot chown %s: %s\n",
				device, sys_errlist[errno]);


		/* poke init */
		settys(device,DISABLE);
		pokeinit(device,Uname,enable);
		post(device,Uname);
		(void)close(devfile); /* close because init did vhangup */
		if((devfile = open(device, 1)) < 0) {
			fprintf(stderr, "On %s open: %s\n",
				device, sys_errlist[errno]);
		} else {
			if(ioctl(devfile, TIOCSDTR, 0) < 0)
				fprintf(stderr,
				    "Cannot set DTR on %s: %s\n",
					device, sys_errlist[errno]);
		}
	}

	exit(0);
}

X/* return true if no lower case */
upcase(str,len)
register char *str;
register int len;
{
	for (; *str, --len >= 0 ; str++) {
		if (*str>='a' && *str<='z') return(0);
	}
	return(1);
}

X/* Post name to public */
post(device,name)
char *device, *name;
{
	(void)time(&utmp.ut_time);
	strcpyn(utmp.ut_line, device, LINSIZ);
	strcpyn(utmp.ut_name, name,  NAMSIZ);
	if (lseek(etcutmp, utmpoff, 0)<0)
		fprintf(stderr,"on lseek in /etc/utmp: %s",
			sys_errlist[errno]);
	if (write(etcutmp, (char *)&utmp, sizeof(utmp))<0)
		fprintf(stderr,"on write in /etc/utmp: %s",
			sys_errlist[errno]);
}
	
X/* poke process 1 and wait for it to do its thing */
pokeinit(device,uname,enable)
char *uname, *device; int enable;
{
	struct utmp utmp;

	post(device, uname);

	/* poke init */
	if (kill(1, SIGHUP)) {
		fprintf(stderr,
		    "Cannot send hangup to init process: %s\n",
			sys_errlist[errno]);
		settys(device,resettty);
		(void)setmodem(device,resetmodem);
		exit(1);
	}

	if (enable)
		return;

	/* wait till init has responded, clearing the utmp entry */
	do {
		sleep(1);
		if (lseek(etcutmp,utmpoff,0)<0)
			fprintf(stderr,"On lseek in /etc/utmp: %s",
				sys_errlist[errno]);
		if (read(etcutmp,&utmp,sizeof utmp)<0)
			fprintf(stderr,"On read from /etc/utmp: %s",
				sys_errlist[errno]);
	} while (utmp.ut_name[0] !='\0');
}

X/* modify appropriate line in /etc/ttys to turn on/off the device */
settys(device,enable)
char *device; int enable;
{
	FILE *ttysfile;  int ittysfil;
	int  lnbeg, foundit, ndevice; 
	char linebuf[100];

	ttysfile = fopen(Etcttys, "r");
	if(ttysfile == NULL) {
		fprintf(stderr, "Cannot open %s: %s\n", Etcttys,
			sys_errlist[errno]);
		exit(1);
	}

	ndevice = strlen(device);
	lnbeg = 0;
	foundit = 0; 

	while(fgets(linebuf, sizeof(linebuf) - 1, ttysfile) != NULL) {
		if(strncmp(device, &linebuf[2], ndevice) == 0) {
			resettty = linebuf[0];
			if(enable && resettty == '1' ||
			  !enable && resettty == '0') {
				fprintf(stderr, "%s already %sbled\n",
					device, enable ? "ena" : "disa");
				exit(0);
			}
			foundit = 1;
			break;
		}
		lnbeg += strlen(linebuf);
	}
	if(foundit == 0) {
		fprintf(stderr, "%s not found in %s\n", device, Etcttys);
		exit(1);
	}
	(void)fclose(ttysfile);

	ittysfil = open(Etcttys, 1);
	if(ittysfil == NULL) {
		fprintf(stderr, "Cannot open %s for output: %s\n",
			Etcttys, sys_errlist[errno]);
		exit(1);
	}
	linebuf[0] = enable ? '1' : '0';
	(void)lseek(ittysfil,lnbeg,0);
	if(write(ittysfil,&linebuf[0],1)<0) {
		fprintf(stderr,"On %s write: %s\n",
			Etcttys, sys_errlist[errno]);
		exit(1);
	}
	(void)close(ittysfil);
}

X/*
 * Excerpted from (June 8,1983 W.Sebok)
 * > ttymodem.c - enable/disable modem control for tty lines.
 * >
 * > Knows about DZ11s and DH11/DM11s.
 * > 23.3.83 - TS
 * > modified to know about DMF's  (hasn't been tested) Nov 8,1984 - WLS
 */


setmodem(ttyline,enable)
char *ttyline; int enable;
{
	dev_t dev;
	int kmem;
	int unit,line,nlines,addr,tflags;
	struct uba_device *ubinfo;
	struct stat statb;
	short flags,devtype=0;
	struct cdevsw cdevsw;

	if(nl[CDEVSW].n_type == 0) {
		fprintf(stderr,"No namelist.\n");
		return(-1);
	}

	if((kmem = open("/dev/kmem", 2)) < 0) {
		fprintf(stderr,"/dev/kmem open: %s\n", sys_errlist[errno]);
		return(-1);
	}

	if(stat(ttyline,&statb) < 0) {
		fprintf(stderr,"%s stat: %s\n", ttyline, sys_errlist[errno]);
		return(-1);
	}

	if(statb.st_mode&S_IFMT != S_IFCHR) {
		fprintf(stderr,"%s is not a character device.\n",ttyline);
		return(-1);
	}

	dev = statb.st_rdev;
	(void)lseek(kmem,
		(int) &(((struct cdevsw *)NLVALUE(CDEVSW))[major(dev)]),0);
	(void)read(kmem,(char *) &cdevsw,sizeof cdevsw);

	if((int)(cdevsw.d_open) == NLVALUE(DZOPEN)) {
		devtype = DZ11;
		unit = minor(dev) / NDZLINE;
		line = minor(dev) % NDZLINE;
		addr = (int) &(((int *)NLVALUE(DZINFO))[unit]);
		(void)lseek(kmem,(int) NLVALUE(NDZ11),0);
	} else if((int)(cdevsw.d_open) == NLVALUE(DHOPEN)) {
		devtype = DH11;
		unit = minor(dev) / NDHLINE;
		line = minor(dev) % NDHLINE;
		addr = (int) &(((int *)NLVALUE(DHINFO))[unit]);
		(void)lseek(kmem,(int) NLVALUE(NDH11),0);
	} else if((int)(cdevsw.d_open) == NLVALUE(DMFOPEN)) {
		devtype = DMF;
		unit = minor(dev) / NDMFLINE;
		line = minor(dev) % NDMFLINE;
		addr = (int) &(((int *)NLVALUE(DMFINFO))[unit]);
		(void)lseek(kmem,(int) NLVALUE(NDMF),0);
	} else {
		fprintf(stderr,"Device %s (%d/%d) unknown.\n",ttyline,
		    major(dev),minor(dev));
		return(-1);
	}

	(void)read(kmem,(char *) &nlines,sizeof nlines);
	if(minor(dev) >= nlines) {
		fprintf(stderr,"Sub-device %d does not exist (only %d).\n",
		    minor(dev),nlines);
		return(-1);
	}

	(void)lseek(kmem,addr,0);
	(void)read(kmem,(char *) &ubinfo,sizeof ubinfo);
	(void)lseek(kmem,(int) &(ubinfo->ui_flags),0);
	(void)read(kmem,(char *) &flags,sizeof flags);

	tflags = 1<<line;
	resetmodem = ((flags&tflags) == 0);
	flags = enable ? (flags & ~tflags) : (flags | tflags);
	(void)lseek(kmem,(int) &(ubinfo->ui_flags),0);
	(void)write(kmem,(char *) &flags, sizeof flags);
	switch(devtype) {
		case DZ11:
			if((addr = NLVALUE(DZSCAR)) == 0) {
				fprintf(stderr,"No dzsoftCAR.\n");
				return(-1);
			}
			(void)lseek(kmem,(int) &(((char *)addr)[unit]),0);
			(void)write(kmem,(char *) &flags, sizeof flags);
			break;
		case DH11:
			if((addr = NLVALUE(DHSCAR)) == 0) {
				fprintf(stderr,"No dhsoftCAR.\n");
				return(-1);
			}
			(void)lseek(kmem,(int) &(((short *)addr)[unit]),0);
			(void)write(kmem,(char *) &flags, sizeof flags);
			break;
		case DMF:
			if((addr = NLVALUE(DMFSCAR)) == 0) {
				fprintf(stderr,"No dmfsoftCAR.\n");
				return(-1);
			}
			(void)lseek(kmem,(int) &(((short *)addr)[unit]),0);
			(void)write(kmem,(char *) &flags,2);
			break;
		default:
			fprintf(stderr,"Unknown device type\n");
			return(-1);
	}
	return(0);
}

prefix(s1, s2)
	register char *s1, *s2;
{
	register char c;

	while ((c = *s1++) == *s2++)
		if (c == '\0')
			return (1);
	return (c == '\0');
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 acucntrl.c
	/bin/echo -n '	'; /bin/ls -ld acucntrl.c
fi
/bin/echo 'Extracting uucp.DIFF'
sed 's/^X//' <<'//go.sysin dd *' >uucp.DIFF
diff -rc ORIG/usr.bin/uucp/cico.c /usr/src/usr.bin/uucp/cico.c
*** ORIG/usr.bin/uucp/cico.c	Wed Nov  2 18:51:44 1983
***************
*** 409,414
  		}
  		alarm(0);
  		clsacu();	/* rti!trt: is this needed? */
  	}
  next:
  	if (!onesys) {

--- 426,432 -----
  		}
  		alarm(0);
  		clsacu();	/* rti!trt: is this needed? */
+ 				/* astrovax!wls: for dialin/out YES!!!! */
  	}
  next:
  	if (!onesys) {
***************
*** 444,449
  			ret = ioctl(0, TCSETA, &Savettyb);
  #endif
  #ifndef	SYSIII
  			/* rti!trt:  use more robust hang up sequence */
  			ret = ioctl(0, TIOCHPCL, STBNULL);
  			ret = ioctl(0, TIOCGETP, &Hupvec);

--- 462,473 -----
  			ret = ioctl(0, TCSETA, &Savettyb);
  #endif
  #ifndef	SYSIII
+ #ifdef TIOCSDTR		/* WLS 3/2/84 */
+ 			(void)ioctl(0, TIOCCDTR, STBNULL);
+ 			sleep(2);
+ 			(void)ioctl(0, TIOCSDTR, STBNULL);
+ 			(void)ioctl(0, TIOCHPCL, STBNULL);
+ #else  TIOCSDTR
  			/* rti!trt:  use more robust hang up sequence */
  			ret = ioctl(0, TIOCHPCL, STBNULL);
  			ret = ioctl(0, TIOCGETP, &Hupvec);
***************
*** 465,470
  		close(Ifn);
  		close(Ofn);
  	}
  	DEBUG(1, "exit code %d\n", code);
  	if (code == 0)
  		xuuxqt();

--- 493,502 -----
  		close(Ifn);
  		close(Ofn);
  	}
+ #ifdef DIALINOUT
+ 	/* reenable logins on dialout (WLS Mar 3, 1984) */
+ 	reenable();
+ #endif
  	DEBUG(1, "exit code %d\n", code);
  	if (code == 0)
  		xuuxqt();
diff -rc ORIG/usr.bin/uucp/condevs.c /usr/src/usr.bin/uucp/condevs.c
*** ORIG/usr.bin/uucp/condevs.c	Sat Aug 13 13:23:31 1983
--- /usr/src/usr.bin/uucp/condevs.c	Wed Nov 28 14:31:07 1984
***************
*** 193,198
  		delock(dev.D_line);
  		return(FAIL);
  	}
  	fflush(stdout);
  	fixline(dcr, dev.D_speed);
  	strcpy(devSel, dev.D_line);	/* for latter unlock */

--- 193,204 -----
  		delock(dev.D_line);
  		return(FAIL);
  	}
+ #ifdef TIOCSPGRP
+ 	{	/* fix for mysterious "EIO" on read error */
+ 		int pgrp = getpgrp(0);
+ 		ioctl(dcr, TIOCSPGRP, &pgrp);
+ 	}
+ #endif
  	fflush(stdout);
  	fixline(dcr, dev.D_speed);
  	strcpy(devSel, dev.D_line);	/* for latter unlock */
***************
*** 406,411
  				}
  			}
  		}
  	fclose(dfp);
  	if (dh < 0)
  		return(CF_NODEV);

--- 412,423 -----
  				}
  			}
  		}
+ #ifdef TIOCSPGRP
+ 	{	/* fix for mysterious "EIO" on read error */
+ 		int pgrp = getpgrp(0);
+ 		ioctl(dh, TIOCSPGRP, &pgrp);
+ 	}
+ #endif
  	fclose(dfp);
  	if (dh < 0)
  		return(CF_NODEV);
***************
*** 478,488
      register FILE *dfp;
      struct Devices dev;
  
!     exphone(flds[F_PHONE], phone);
!     devSel[0] = '\0';
!     DEBUG(4, "Dialing %s\n", phone);
!     dfp = fopen(DEVFILE, "r");
!     ASSERT(dfp != NULL, "Can't open", DEVFILE, 0);
  
      for(cd = condevs; cd->CU_meth != NULL; cd++) {
  	if (snccmp(flds[F_LINE], cd->CU_meth) == SAME) {

--- 491,501 -----
  	struct Devices dev;
+  	int success = 0;
  
! 	exphone(flds[F_PHONE], phone);
! 	devSel[0] = '\0';
! 	DEBUG(4, "Dialing %s\n", phone);
! 	dfp = fopen(DEVFILE, "r");
! 	ASSERT(dfp != NULL, "Can't open", DEVFILE, 0);
  
  	fseek(dfp, (off_t)0, 0);
  	while(rddev(dfp, &dev) != FAIL) {
***************
*** 484,493
      dfp = fopen(DEVFILE, "r");
      ASSERT(dfp != NULL, "Can't open", DEVFILE, 0);
  
!     for(cd = condevs; cd->CU_meth != NULL; cd++) {
! 	if (snccmp(flds[F_LINE], cd->CU_meth) == SAME) {
! 	    fseek(dfp, (off_t)0, 0);
! 	    while(rddev(dfp, &dev) != FAIL) {
  		if (strcmp(flds[F_CLASS], dev.D_class) != SAME)
  		    continue;
  		if (snccmp(flds[F_LINE], dev.D_type) != SAME)

--- 497,504 -----
  	dfp = fopen(DEVFILE, "r");
  	ASSERT(dfp != NULL, "Can't open", DEVFILE, 0);
  
! 	fseek(dfp, (off_t)0, 0);
! 	while(rddev(dfp, &dev) != FAIL) {
  		if (strcmp(flds[F_CLASS], dev.D_class) != SAME)
  			continue;
  		if (snccmp(flds[F_LINE], dev.D_type) != SAME)
***************
*** 496,501
 		if (dev.D_brand[0] == '\0')
 		    logent("Acuopn","No 'brand' name on ACU");
! 		else if (snccmp(dev.D_brand, cd->CU_brand) != SAME)
! 		    continue;
  		if (mlock(dev.D_line) == FAIL)
  		    continue;
  

--- 508,522 -----
 		if (dev.D_brand[0] == '\0') {
 			logent("Acuopn","No 'brand' name on ACU");
! 			continue;
! 		}
! 		for(cd = condevs; cd->CU_meth != NULL; cd++) {
! 			if (snccmp(flds[F_LINE], cd->CU_meth) == SAME
! 			    && snccmp(dev.D_brand, cd->CU_brand) == SAME)
! 				break;
! 		}
! 		if (cd->CU_meth == NULL) {
! 			logent(dev.D_brand,"unsupported ACU type");
! 			continue;
! 		}
! 
  		if (mlock(dev.D_line) == FAIL)
  			continue;
  
***************
*** 499,504
  		if (mlock(dev.D_line) == FAIL)
  		    continue;
  
  		DEBUG(4, "Using %s\n", cd->CU_brand);
  		fd = (*(cd->CU_open))(phone, flds, &dev);
  		if (fd > 0) {

--- 520,539 -----
  		if (mlock(dev.D_line) == FAIL)
  			continue;
  
+ #ifdef DIALINOUT
+ 		if (snccmp("inout",dev.D_calldev) == 0
+ 		    && disable(dev.D_line) == FAIL) {
+ 			delock(dev.D_line);
+ 			continue;
+ 		}
+ #endif
+ 		success++;
+ 		break;
+ 	}
+ 
+ 	fclose(dfp);
+ 
+ 	if (success) {
  		DEBUG(4, "Using %s\n", cd->CU_brand);
  		fd = (*(cd->CU_open))(phone, flds, &dev);
  		if (fd > 0) {
***************
*** 502,513
  		DEBUG(4, "Using %s\n", cd->CU_brand);
  		fd = (*(cd->CU_open))(phone, flds, &dev);
  		if (fd > 0) {
! 		    CU_end = cd->CU_clos;   /* point CU_end at close func */
! 		    fclose(dfp);
! 		    strcpy(devSel, dev.D_line);   /* save for later unlock() */
! 		    return(fd);
! 		    }
! 		delock(dev.D_line);
  		}
  	    }
  	}

--- 537,547 -----
  		DEBUG(4, "Using %s\n", cd->CU_brand);
  		fd = (*(cd->CU_open))(phone, flds, &dev);
  		if (fd > 0) {
! 			CU_end = cd->CU_clos;   /* point CU_end at close func */
! 			strcpy(devSel, dev.D_line); /* save for later unlock()*/
! 			return(fd);
! 		} else {
! 			delock(dev.D_line);
  		}
  	}
  	return(FAIL);
***************
*** 509,515
  		    }
  		delock(dev.D_line);
  		}
- 	    }
  	}
      fclose(dfp);
      return(FAIL);

--- 543,548 -----
  		} else {
  			delock(dev.D_line);
  		}
  	}
  	return(FAIL);
  }
***************
*** 511,519
  		}
  	    }
  	}
!     fclose(dfp);
!     return(FAIL);
!     }
  
  #ifdef DN11
  

--- 544,551 -----
  			delock(dev.D_line);
  		}
  	}
! 	return(FAIL);
! }
  
  #ifdef DN11
  
***************
*** 850,855
  	/* modem is open */
  	next_fd = -1;
  	if (dh >= 0) {
  		fixline(dh, dev->D_speed);
  #ifdef HAYSTONE
  		write(dh, "\rATDT", 5);

--- 885,895 -----
  	/* modem is open */
  	next_fd = -1;
  	if (dh >= 0) {
+ #ifdef TIOCSPGRP
+ 		/* fix for mysterious "EIO" on read error */
+ 		int pgrp = getpgrp(0);
+ 		ioctl(dh, TIOCSPGRP, &pgrp);
+ #endif
  		fixline(dh, dev->D_speed);
  		if (hayes_sync(dh) == FAIL) {
  			logent(dev->D_line,"Cannot sync Hayes");
***************
*** 978,983
  
  	alarm(0);
  	next_fd = -1;
  	fixline(dnf, dev->D_speed);
  	DEBUG(4, "Hayes port - %s, ", dcname);
  

--- 1116,1128 -----
  
  	alarm(0);
  	next_fd = -1;
+ #ifdef TIOCSPGRP
+ 	{
+ 		/* fix for mysterious "EIO" on read error */
+ 		int pgrp = getpgrp(0);
+ 		ioctl(dnf, TIOCSPGRP, &pgrp);
+ 	}
+ #endif
  	fixline(dnf, dev->D_speed);
  	DEBUG(4, "Hayes port - %s, ", dcname);
  
***************
*** 1394,1399
  		i = CF_NODEV;
  		goto ret;
  	}
  	fixline(va, dev->D_speed);
  
  	p_chwrite(va, STX);	/* access adaptor */

--- 1525,1536 -----
  		i = CF_NODEV;
  		goto ret;
  	}
+ #if TIOCSPGRP
+ 	{	/* fix for mysterious "EIO" on read error */
+ 		int pgrp = getpgrp(0);
+ 		ioctl(va, TIOCSPGRP, &pgrp);
+ 	}
+ #endif
  	fixline(va, dev->D_speed);
  
  	p_chwrite(va, STX);	/* access adaptor */
***************
*** 1459,1464
  			logent("dialup open", "FAILED");
  		goto failret;
  	}
  	fixline(i, dev->D_speed);
  	goto ret;
  failret:

--- 1596,1607 -----
  			logent("dialup open", "FAILED");
  		goto failret;
  	}
+ #ifdef TIOCSPGRP
+ 	{	/* fix for mysterious "EIO" on read error */
+ 		int pgrp = getpgrp(0);
+ 		ioctl(i, TIOCSPGRP, &pgrp);
+ 	}
+ #endif
  	fixline(i, dev->D_speed);
  	goto ret;
  failret:
***************
*** 1478,1482
  	p_chwrite(fd, SOH);
  /*	ioctl(fd, TIOCCDTR, NULL);*/
  	close(fd);
  }
  #endif

--- 1621,1773 -----
  	p_chwrite(fd, SOH);
  /*	ioctl(fd, TIOCCDTR, NULL);*/
  	close(fd);
+ }
+ #endif
+ 
+ #ifdef DIALINOUT
+ /* DIALIN/OUT CODE (WLS) */
+ /***************************************************************************
+  * disable and reenable:  allow a single line to be use for dialin/dialout *
+  ***************************************************************************/
+ 
+ char enbdev[16];
+ 
+ disable(dev)
+ register char *dev;
+ {
+ 	register char *rdev;
+ 
+ 	/* strip of directory prefixes */
+ 	rdev = dev;
+ 	while (*rdev) rdev++;
+ 	while (--rdev >= dev && *rdev != '/');
+ 	rdev++;
+ 
+ 
+ 	if (enbdev[0]) {
+ 		if (strcmp(enbdev,rdev) == 0)
+ 			return(SUCCESS);	/* already disabled */
+ 		delock(enbdev);
+ 		reenable();			/* else, reenable the old one */
+ 	}
+ 	DEBUG(4, "Disable %s\n", rdev);
+ 	if (enbcall("disable", rdev) == FAIL)
+ 		return(FAIL);
+ 	logent(rdev, "DISABLED LOGIN");
+ 	strcpy(enbdev,rdev);
+ 	return(SUCCESS);
+ }
+ 
+ reenable()
+ {
+ 	if (enbdev[0] == NULL) return;
+ 	DEBUG(4, "Reenable %s\n", enbdev);
+ 	(void)enbcall("enable", enbdev);
+ 	logent(enbdev, "REENABLED LOGIN");
+ 	enbdev[0] = '\0';
+ }
+ 
+ enbcall(type, dev)
+ char *type, *dev;
+ {
+ 	register int pid;
+ 	int status;
+ 
+ 	if ((pid = fork()) == 0) {
+ 		DEBUG(4, ACUPROG, 0);
+ 		DEBUG(4, " %s", type);
+ 		DEBUG(4, " %s\n", dev);
+ 		setuid(geteuid());	/* for chown(uid()) in acu program */
+ 		execl(ACUPROG, "acu", type, dev, 0);
+ 		exit(999);
+ 	}
+ 	while(wait(&status) != pid);
+ 	return(status ? FAIL : SUCCESS);
+ }
diff -rc ORIG/usr.bin/uucp/uucp.h /usr/src/usr.bin/uucp/uucp.h
*** ORIG/usr.bin/uucp/uucp.h	Tue Jul 26 23:58:08 1983
--- /usr/src/usr.bin/uucp/uucp.h	Sun Dec  2 23:30:30 1984
***************
*** 113,119
  
  /* All users with getuid() <= PRIV_UIDS are 'privileged'. */
  /* Was 10, reduced to 3 as suggested by duke!dbl (David Leonard) */
 #define	PRIV_UIDS	3
  
  #define XQTDIR		"/usr/spool/uucp/XTMP"
  #define SQFILE		"/usr/lib/uucp/SQFILE"

--- 116,132 -----
  
  /* All users with getuid() <= PRIV_UIDS are 'privileged'. */
  /* Was 10, reduced to 3 as suggested by duke!dbl (David Leonard) */
 #define	PRIV_UIDS	3
 
! /* program to disable/reenable logins on dialout line  (Mar 3,1984 WLS) */
! #define ACUPROG		"/usr/local/lib/acucntrl"
! #ifdef  ACUPROG
! #	define	DIALINOUT
! #endif
  
  #define XQTDIR		"/usr/spool/uucp/XTMP"
  #define SQFILE		"/usr/lib/uucp/SQFILE"
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 uucp.DIFF
	/bin/echo -n '	'; /bin/ls -ld uucp.DIFF
fi
/bin/echo 'Extracting tip.DIFF'
sed 's/^X//' <<'//go.sysin dd *' >tip.DIFF
diff -rc ORIG/usr.bin/tip/cu.c /usr/src/usr.bin/tip/cu.c
*** ORIG/usr.bin/tip/cu.c	Tue Jun 28 03:33:09 1983
--- /usr/src/usr.bin/tip/cu.c	Mon May 28 20:23:25 1984
***************
*** 82,88
  	setbuf(stdout, NULL);
  	loginit();
  	setuid(getuid());
! 	setgid(getgid());
  	vinit();
  	setparity("none");
  	boolean(value(VERBOSE)) = 0;

--- 82,88 -----
  	setbuf(stdout, NULL);
  	loginit();
  	setuid(getuid());
! /*	setgid(getgid());	*/
  	vinit();
  	setparity("none");
  	boolean(value(VERBOSE)) = 0;
diff -rc ORIG/usr.bin/tip/remote.c /usr/src/usr.bin/tip/remote.c
*** ORIG/usr.bin/tip/remote.c	Sat Jun 25 04:19:16 1983
--- /usr/src/usr.bin/tip/remote.c	Sat Jun  2 00:40:49 1984
***************
*** 121,126
  {
  	register char *cp;
  	static char *next;
  	static int lookedup = 0;
  
  	if (!lookedup) {

--- 123,131 -----
  {
  	register char *cp;
  	static char *next;
+ 	static char *nextat;	/* next AT */
  	static int lookedup = 0;
  
  	if (!lookedup) {
***************
*** 130,135
  		}
  		getremcap(host);
  		next = DV;
  		lookedup++;
  	}
  	/*

--- 135,143 -----
  		}
  		getremcap(host);
  		next = DV;
+ 		nextat = AT;
  		lookedup++;
  	}
  	/*
***************
*** 146,150
  		DV = next;
  		next = cp;
  	}
  	return (DV);
  }

--- 154,165 -----
  		DV = next;
  		next = cp;
  	}
+ 	if (nextat != NOSTR) {
+ 		AT = nextat;
+ 		if ((nextat = index(nextat,',')) != NOSTR)
+ 			*nextat++ = '\0';
+ 	}
  	return (DV);
  }
===================================
diff -rc ORIG/usr.bin/tip/tip.c /usr/src/usr.bin/tip/tip.c
*** ORIG/usr.bin/tip/tip.c	Tue Jun 28 03:33:11 1983
--- /usr/src/usr.bin/tip/tip.c	Mon May 28 21:14:01 1984
***************
*** 82,87
  	signal(SIGHUP, cleanup);
  	signal(SIGTERM, cleanup);
  
  	if ((i = hunt(system)) == 0) {
  		printf("all ports busy\n");
  		exit(3);

--- 82,90 -----
  	signal(SIGHUP, cleanup);
  	signal(SIGTERM, cleanup);
  
+ 	setbuf(stdout, NULL);
+ 	loginit();
+ 
  	if ((i = hunt(system)) == 0) {
  		printf("all ports busy\n");
  		exit(3);
***************
*** 91,98
  		delock(uucplock);
  		exit(3);
  	}
- 	setbuf(stdout, NULL);
- 	loginit();
  	/*
  	 * Now that we have the logfile and the ACU open
  	 *  return to the real uid and gid.  These things will

--- 94,99 -----
  		delock(uucplock);
  		exit(3);
  	}
  	/*
  	 * Now that we have the logfile and the ACU open
  	 *  return to the real uid and gid.  These things will
***************
*** 100,106
  	 *  because locking mechanism on the tty and the accounting
  	 *  will be bypassed.
  	 */
! 	setgid(getgid());
  	setuid(getuid());
  
  	/*

--- 101,107 -----
  	 *  because locking mechanism on the tty and the accounting
  	 *  will be bypassed.
  	 */
! /*	setgid(getgid()); */
  	setuid(getuid());
  
  	/*
======================
diff -rc ORIG/usr.bin/tip/uucplock.c /usr/src/usr.bin/tip/uucplock.c
*** ORIG/usr.bin/tip/uucplock.c	Sat Jun 25 04:19:18 1983
--- /usr/src/usr.bin/tip/uucplock.c	Sun Dec  2 23:37:34 1984
***************
*** 4,10
  /*
   * defs that come from uucp.h
   */
! #define NAMESIZE 15
  #define FAIL -1
  #define SAME 0
  #define SLCKTIME 28800	/* system/device timeout (LCK.. files) in seconds (8 hours) */

--- 4,12 -----
  /*
   * defs that come from uucp.h
   */
! /* program to enable/disable devices */
! #define ACUPROG "/usr/local/lib/acucntrl"
! #define NAMESIZE 40
  #define FAIL -1
  #define SAME 0
  #define SLCKTIME 28800	/* system/device timeout (LCK.. files) in seconds (8 hours) */
***************
*** 180,186
  }
  
  /***
!  *	delock(s)	remove a lock file
   *	char *s;
   *
   *	return codes:  0  |  FAIL

--- 182,188 -----
  }
  
  /***
!  *	delock(s)	remove a lock file and reenable logins
   *	char *s;
   *
   *	no return codes
***************
*** 190,196
  	char *s;
  {
  	char ln[30];
! 
  	sprintf(ln, "%s.%s", LOCKPRE, s);
  	rmlock(ln);
  }

--- 192,199 -----
  	char *s;
  {
  	char ln[30];
! 	if (strncmp(s, "ttyd", 4) == 0)
! 		reenable(s);
  	sprintf(ln, "%s.%s", LOCKPRE, s);
  	rmlock(ln);
  }
***************
*** 196,202
  }
  
  /***
!  *	mlock(sys)	create system lock
   *	char *sys;
   *
   *	return codes:  0  |  FAIL

--- 199,205 -----
  }
  
  /***
!  *	mlock(sys)	create system lock and disable logins on port
   *	char *sys;
   *
   *	return codes:  0  |  FAIL
***************
*** 207,211
  {
  	char lname[30];
  	sprintf(lname, "%s.%s", LOCKPRE, sys);
! 	return (ulockf(lname, (time_t) SLCKTIME ) < 0 ? FAIL : 0);
  }

--- 210,268 -----
  {
  	char lname[30];
  	sprintf(lname, "%s.%s", LOCKPRE, sys);
! 	if (ulockf(lname, (time_t) SLCKTIME ) < 0)
! 		return(FAIL);
! 	if (strncmp(sys, "ttyd", 4)!=0)
! 		return(0);
! 	if (disable(sys) == 0)
! 		return(0);
! 	return(FAIL);
! }
! 
! /***************************************************************************
!  * disable and reenable:  allow a single line to be use for dialin/dialout *
!  *  routines added by David Sebok 4/15/84 based on those created by        *
!  *  William Sebok							   *
!  ***************************************************************************/
! 
! static int disableflg=0;
! disable(dev)
! register char *dev;
! {
! 	printf("disabling %s...",dev);
! 	fflush(stdout);
! 	if (enbcall("disable", dev) == FAIL) {
! 		printf("could not disable %s\n",dev);
! 		return(FAIL);
! 	}
! 	disableflg=1;
! 	printf(" done\n");
! 	return(0);
! }
! 
! reenable(dev)
! register char *dev;
! {
! 	if (disableflg==0)
! 		return(FAIL);
! 	printf("reenabling %s...",dev);
! 	if (enbcall("enable", dev) == FAIL)
! 		return(FAIL);
! 	printf(" done\n\r");
! 	return(0);
! }
! 
! enbcall(type, dev)
! char *type, *dev;
! {
! 	register int pid;
! 	int status;
! 
! 	if ((pid = fork()) == 0) {
! 		setuid(getuid());	/* for chown(uid()) in acu program */
! 		execl(ACUPROG, "acu", type, dev, 0);
! 		exit(9);
! 	}
! 	while(wait(&status) != pid);
! 	return(status ? FAIL : 0);	/*FAIL if ACU exits other than 0 */
  }
==========
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 tip.DIFF
	/bin/echo -n '	'; /bin/ls -ld tip.DIFF
fi
-- 
Bill Sebok			Princeton University, Astrophysics
{allegra,akgua,burl,cbosgd,decvax,ihnp4,noao,princeton,vax135}!astrovax!wls