#if	defined(DOSCCS) && !defined(lint)
static char sccsid[] = "@(#)acucntrl.c	5.8.2 (2.11BSD GTE) 1996/3/22";
#endif

/*  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 at the utmp file that the line is not in use
 *	(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 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 utmp to check that the line is not in use by another
 *	(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 utmp file
 */

/* #define SENSECARRIER */

#include "uucp.h"
#include <sys/buf.h>
#include <signal.h>
#include <sys/conf.h>
#ifdef pdp11
#include <pdpuba/ubavar.h>
#else
#ifdef BSD4_2
//#include <vaxuba/ubavar.h>
#else
#include <sys/ubavar.h>
#endif
#endif
#include <sys/stat.h>
#include <nlist.h>
#include <sgtty.h>
#include <utmp.h>
#include <pwd.h>
#include <stdio.h>
#include <sys/file.h>
#include <errno.h>
#include <string.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[] = _PATH_UTMP;
char Etcttys[] = "/etc/ttys";
#ifdef BSD4_3
FILE *ttysfile, *nttysfile;
char NEtcttys[] = "/etc/ttys.new";
extern long ftell();
#endif
char Devhome[] = "/dev";

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

struct utmp utmp;
char resettty, resetmodem;
int etcutmp;
off_t utmploc;
off_t ttyslnbeg;

#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;
	off_t lseek();
	struct passwd *getpwuid();
	char *rindex();

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

	/* interpret command type */
	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;

	/* only recognize devices of the form ttydX */
	if (strncmp(device, "ttyd", 4)!=0) {
		fprintf(stderr, "Bad Device Name %s", device);
		exit(1);
	}

	opnttys(device);

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

	/* Chdir to /dev */
	if(chdir(Devhome) < 0) {
		fprintf(stderr, "Cannot chdir to %s: %s\r\n",
			Devhome, strerror(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);


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

	(void)lseek(etcutmp, utmploc, 0);

	i = read(etcutmp, (char *)&utmp, sizeof(struct utmp));

	if(
		i == sizeof(struct utmp) &&
		utmp.ut_line[0] != '\0'  &&
		utmp.ut_name[0] != '\0'  &&
		(
			!upcase(utmp.ut_name, NAMSIZ) ||
			(
				uid != 0 &&
				strncmp(utmp.ut_name, Uname, NAMSIZ) != 0
			)
		)
	) {
		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, strerror(errno));
			(void)setmodem(device, resetmodem);
			exit(1);
		}
		/* Try one last time to hang up */
		if (ioctl(devfile, TIOCCDTR, (char *)0) < 0)
			fprintf(stderr, "On TIOCCDTR ioctl: %s\n",
				strerror(errno));

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

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

		i = resetmodem;

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

		if (settys(ENABLE)) {
			fprintf(stderr, "%s already enabled\n", device);
		} else {
			pokeinit(device, Uname, enable);
		}
		post(device, "");

	} else {
#if defined(TIOCMGET) && defined(SENSECARRIER)
		if (uid!=0) {
			int linestat = 0;

			/* check for presence of carrier */
			sleep(2); /* need time after modem control turnoff */

			if((devfile = open(device, 1)) < 0) {
				fprintf(stderr, "On open of %s: %s\n",
					device, strerror(errno));
				(void)setmodem(device, resetmodem);
				exit(1);
			}

			(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);
			}
			(void)close(devfile);
		}
#endif
		/* chown device */
		if(chown(device, uid, gid) < 0)
			fprintf(stderr, "Cannot chown %s: %s\n",
				device, strerror(errno));


		/* poke init */
		if(settys(DISABLE)) {
			fprintf(stderr, "%s already disabled\n", device);
		} else {
			pokeinit(device, Uname, enable);
		}
		post(device, Uname);
		if((devfile = open(device, O_RDWR|O_NDELAY)) < 0) {
			fprintf(stderr, "On %s open: %s\n",
				device, strerror(errno));
		} else {
			if(ioctl(devfile, TIOCSDTR, (char *)0) < 0)
				fprintf(stderr,
				    "Cannot set DTR on %s: %s\n",
					device, strerror(errno));
		}
	}

	exit(0);
}

/* 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);
}

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

	post(device, uname);

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

	if (enable)
		return;

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

#ifdef BSD4_3
/* identify terminal line in ttys */
opnttys(device)
char *device;
{
	register int  ndevice; 
	register char *p;
	char *index();
	char linebuf[BUFSIZ];

	ttysfile = NULL;
	do {
		if (ttysfile != NULL) {
			fclose(ttysfile);
			sleep(5);
		}
		ttysfile = fopen(Etcttys, "r");
		if(ttysfile == NULL) {
			fprintf(stderr, "Cannot open %s: %s\n", Etcttys,
				strerror(errno));
			exit(1);
		}
	} while (flock(fileno(ttysfile), LOCK_NB|LOCK_EX) < 0);
	nttysfile = fopen(NEtcttys, "w");
	if(nttysfile == NULL) {
		fprintf(stderr, "Cannot open %s: %s\n", Etcttys,
			strerror(errno));
		exit(1);
	}

	ndevice = strlen(device);
#ifndef BRL4_2
	utmploc = sizeof(utmp);
#else
	utmploc = 0;
#endif

	while(fgets(linebuf, sizeof(linebuf) - 1, ttysfile) != NULL) {
		if(strncmp(device, linebuf, ndevice) == 0)
			return;
		ttyslnbeg += strlen(linebuf);
		if (linebuf[0] != '#' && linebuf[0] != '\n')
			utmploc += sizeof(utmp);
		if (fputs(linebuf, nttysfile) == NULL) {
			fprintf(stderr, "On %s write: %s\n",
				Etcttys, strerror(errno));
			exit(1);
		}
		
	}
	fprintf(stderr, "%s not found in %s\n", device, Etcttys);
	exit(1);
}

/* modify appropriate line in /etc/ttys to turn on/off the device */
settys(enable)
int enable;
{
	register char *cp, *cp2;
	char lbuf[BUFSIZ];
	int i;
	char c1, c2;

	(void) fseek(ttysfile, ttyslnbeg, 0);
	if(fgets(lbuf, BUFSIZ, ttysfile) == NULL) {
		fprintf(stderr, "On %s read: %s\n",
			Etcttys, strerror(errno));
		exit(1);
	}
	/* format is now */
	/* ttyd0 std.100 dialup on secure # comment */
	/* except, 2nd item may have embedded spaces inside quotes, Hubert */
	cp = lbuf;
	for (i=0;*cp && i<3;i++) {
		if (*cp == '"') {
			cp++;
			while (*cp && *cp != '"')
				cp++;
			if (*cp != '\0')
				cp++;
		}else {
			while (*cp && *cp != ' ' && *cp != '\t')
				cp++;
		}
		while (*cp && (*cp == ' ' || *cp == '\t'))
			cp++;
	}
	if (*cp == '\0') {
		fprintf(stderr,"Badly formatted line in /etc/ttys:\n%s", lbuf);
		exit(1);
	}
	c1 = *--cp;
	*cp++ = '\0';
	cp2 = cp;
	while (*cp && *cp != ' ' && *cp != '\t' && *cp != '\n')
		cp++;
	if (*cp == '\0') {
		fprintf(stderr,"Badly formatted line in /etc/ttys:\n%s", lbuf);
		exit(1);
	}
	c2 = *cp;
	*cp++ = '\0';
	while (*cp && (*cp == ' ' || *cp == '\t'))
		cp++;
	resettty = strcmp("on", cp2) != 0;
	fprintf(nttysfile,"%s%c%s%c%s", lbuf, c1, enable ? "on" : "off", c2, cp);
	if (ferror(nttysfile)) {
		fprintf(stderr, "On %s fprintf: %s\n",
			NEtcttys, strerror(errno));
		exit(1);
	}
	while(fgets(lbuf, sizeof(lbuf) - 1, ttysfile) != NULL) {
		if (fputs(lbuf, nttysfile) == NULL) {
			fprintf(stderr, "On %s write: %s\n",
				NEtcttys, strerror(errno));
			exit(1);
		}
	}
		
	if (enable^resettty)
		(void) unlink(NEtcttys);
	else {
		struct stat statb;
		if (stat(Etcttys, &statb) == 0) {
			fchmod(fileno(nttysfile) ,statb.st_mode);
			fchown(fileno(nttysfile), statb.st_uid, statb.st_gid);
		}
		(void) rename(NEtcttys, Etcttys);
	}
	(void) fclose(nttysfile);
	(void) fclose(ttysfile);
	return enable^resettty;
}

#else

/* identify terminal line in ttys */
opnttys(device)
char *device;
{
	register FILE *ttysfile;
	register int  ndevice, lnsiz; 
	register char *p;
	char *index();
	char linebuf[BUFSIZ];

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

	ndevice = strlen(device);
	ttyslnbeg = 0;
	utmploc = 0;

	while(fgets(linebuf, sizeof(linebuf) - 1, ttysfile) != NULL) {
		lnsiz = strlen(linebuf);
		if ((p = index(linebuf, '\n')) != NULL)
			*p = '\0';
		if(strncmp(device, &linebuf[2], ndevice) == 0) {
			(void)fclose(ttysfile);
			return;
		}
		ttyslnbeg += lnsiz;
		utmploc += sizeof(utmp);
	}
	fprintf(stderr, "%s not found in %s\n", device, Etcttys);
	exit(1);
}

/* modify appropriate line in /etc/ttys to turn on/off the device */
settys(enable)
int enable;
{
	int ittysfil;
	char out, in;

	ittysfil = open(Etcttys, 2);
	if(ittysfil < 0) {
		fprintf(stderr, "Cannot open %s for output: %s\n",
			Etcttys, strerror(errno));
		exit(1);
	}
	(void)lseek(ittysfil, ttyslnbeg, 0);
	if(read(ittysfil, &in, 1)<0) {
		fprintf(stderr, "On %s write: %s\n",
			Etcttys, strerror(errno));
		exit(1);
	}
	resettty = (in == '1');
	out = enable ? '1' : '0';
	(void)lseek(ittysfil, ttyslnbeg, 0);
	if(write(ittysfil, &out, 1)<0) {
		fprintf(stderr, "On %s write: %s\n",
			Etcttys, strerror(errno));
		exit(1);
	}
	(void)close(ittysfil);
	return(in==out);
}
#endif

/*
 * 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
 */


/*
 * 2.11BSD NOTE:  2.11BSD doesn't use ui_flags.  We've included the code
 * for correctness in case someone decides to change the way the 2.11BSD
 * tty drivers work.  Mostly what needs to be done is have the tty drivers
 * do something like:
 *
 *	if ((minor(dev)&0200) || (XXinfo[unit].ui_flags&(1L<<line))) {
 *		XXsoftCAR[unit] |= 1L << line;
 *		tp->t_state |= TS_CARR_ON;
 *	}
 *	else
 *		XXsoftCAR[unit] &= ~(1L << line);
 *
 * (2.11BSD uses a bit 0200 in the minor device number of a tty /dev node
 * to indicate soft carrier as opposed to compiling it into the kernel as
 * 4.3BSD does.)  This code minus the check of ui_flags is already present
 * in all 2.11BSD drivers.
 */
setmodem(ttyline, enable)
char *ttyline; int enable;
{
	dev_t dev;
	int kmem;
	int unit, line, nlines;
	int addr;
	int devtype=0;
	char cflags;
	unsigned short sflags;
#ifdef BSD4_2
	long flags, tflags;
#else
	short flags, tflags;
#endif
	struct uba_device *ubinfo;
	struct stat statb;
	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", strerror(errno));
		return(-1);
	}

	if(stat(ttyline, &statb) < 0) {
		fprintf(stderr, "%s stat: %s\n", ttyline, strerror(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,
		(off_t) &(((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;
#ifdef pdp11
		ubinfo = &(((struct uba_device *)NLVALUE(DZINFO))[unit]);
#else
		addr = (int) &(((int *)NLVALUE(DZINFO))[unit]);
#endif
		(void)lseek(kmem, (off_t) NLVALUE(NDZ11), 0);
	} else if((int)(cdevsw.d_open) == NLVALUE(DHOPEN)) {
		devtype = DH11;
		unit = minor(dev) / NDHLINE;
		line = minor(dev) % NDHLINE;
#ifdef pdp11
		ubinfo = &(((struct uba_device *)NLVALUE(DHINFO))[unit]);
#else
		addr = (int) &(((int *)NLVALUE(DHINFO))[unit]);
#endif
		(void)lseek(kmem, (off_t) NLVALUE(NDH11), 0);
	} else if((int)(cdevsw.d_open) == NLVALUE(DMFOPEN)) {
		devtype = DMF;
		unit = minor(dev) / NDMFLINE;
		line = minor(dev) % NDMFLINE;
#ifdef pdp11
		ubinfo = &(((struct uba_device *)NLVALUE(DMFINFO))[unit]);
#else
		addr = (int) &(((int *)NLVALUE(DMFINFO))[unit]);
#endif
		(void)lseek(kmem, (off_t) 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);
	}

#ifndef pdp11
	(void)lseek(kmem, (off_t)addr, 0);
	(void)read(kmem, (char *) &ubinfo, sizeof ubinfo);
#endif
	(void)lseek(kmem, (off_t) &(ubinfo->ui_flags), 0);
	(void)read(kmem, (char *) &flags, sizeof flags);

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

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

	while ((c = *s1++) == *s2++)
		if (c == '\0')
			return (1);
	return (c == '\0');
}
