/*	@(#)dial.c	2.1 	*/
/*LINTLIBRARY*/
/***************************************************************
 *	dial() returns an fd for an open tty-line connected to the
 *	specified remote.  The caller should trap all ways to
 *	terminate, and call undial(). This will release the `lock'
 *	file and return the outgoing line to the system.  This routine
 *	would prefer that the calling routine not use the `alarm()'
 *	system call, nor issue a `signal(SIGALRM, xxx)' call.
 *	If you must, then please save and restore the alarm times.
 *	The sleep() library routine is ok, though.
 *
 *	#include "dial.h"
 *
 *	int dial(call);
 *	CALL call;
 *
 *	void undial(rlfd);
 *	int rlfd;
 *
 *	rlfd is the "remote-lne file descriptor" returned from dial.
 *
 *	The CALL structure as (defined in dial.h):
 *
 *	typedef struct {
 *		struct termio *attr;	ptr to term attribute structure
 *		int	baud;		transmission baud-rate
 *		int	speed;		212A modem: low=300, high=1200
 *		char	*line;		device name for out-going line
 *		char	*telno;		ptr to tel-no digit string
 *	} CALL;
 *
 *	The error returns from dial are negative, in the range -1
 *	to -11, and their meanings are:
 *
 *		INTRPT	 -1: interrupt occured
 *		D_HUNG	 -2: dialer hung (no return from write)
 *		NO_ANS	 -3: no answer within 10 seconds
 *		ILL_BD	 -4: illegal baud-rate
 *		A_PROB	 -5: acu problem (open() failure)
 *		L_PROB	 -6: line problem (open() failure)
 *		NO_Ldv	 -7: can't open L-devs file
 *		DV_NT_A	 -8: specified device not available
 *		DV_NT_K	 -9: specified device not known
 *		NO_BD_A	-10: no device available at requested baud-rate
 *		NO_BD_K	-11: no device known at requested baud-rate
 *
 *	Setting attributes in the termio structure indicated in
 *	the `attr' field of the CALL structure before passing the
 *	structure to dial(), will cause those attributes to be set
 *	before the connection is made.  This can be important for
 *	some attributes such as parity and baud.
 *
 *	As a device-lockout semaphore mechanism, we create an entry,
 *	in the directory #defined as LOCK, whose name is LCK..dev
 *	where dev is the device name taken from the "line" column
 *	in the file #defined as LDEVS.  Be sure to trap every possible
 *	way out of execution in order to "release" the device.
 *	This entry is `touched' every hour in order to keep uucp
 *	from removing it on its 90 minute rounds.
 *	Also, have the system start-up procedure clean all such
 *	entries from the LOCK directory.
 *
 *	With an error return (negative value), there will not be
 *	any `lock-file' entry, so no need to call undial().
 ***************************************************************/

#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <termio.h>
#include <errno.h>
#include "dial.h"

#define	UPDTE	3600		/* how often to touch the lock entry */
#define	ACULAST	"<"		/* character which terminates dialing*/
#define	YES	1		/* mnemonic */
#define	NO	0		/* mnemonic */
#define	DIFFER	strcmp		/* mnemonic */
#define say	(void)fprintf	/* mnemonic */

extern unsigned
	sleep(),
	alarm();

extern int errno;

extern char *malloc();

static char
	cul[15+sizeof(DEVDIR)],	/* line's device-name */
	cua[15+sizeof(DEVDIR)],	/* acu's device-name */
	*find_dev(),		/* local function */
	lock[16+sizeof(LOCK)];	/* directory in which to make lockfile*/

static int
	found=0,		/* set when device is seen legal */
	saverr, 		/* hide errno during other calls */
	rlfd,			/* fd for remote comm line */
	lfd= -1,		/* fd for the device-lock file */
	intflag=NO,		/* interrupt indicator */
	connect(),		/* local function */
	intcatch(),		/* interrupt routine */
	alrmcatch(),		/* interrupt routine */
	hupcatch();		/* interrupt routine */

#ifdef ddt
static void dump();
#endif

int	_debug=0;
void	undial();

int
dial(call)
CALL call;
{
	FILE *Ldevices;		/* file pointer for Device name file */
	char dvc[30];
	int (*savint)(), (*savhup)();

#ifdef ddt
	if(_debug == YES) {
		say(stderr, "call dial(%d)\r\n", call);
		dump(&call, 0);
	}
#endif

	saverr = 0;
	savint = signal(SIGINT, intcatch);
	savhup = signal(SIGHUP, hupcatch);
	(void)signal(SIGALRM, alrmcatch);

	if(call.telno == NULL && call.line == NULL) {
		rlfd = DV_NT_K;
		goto OUT;
	}

	if((Ldevices = fopen(LDEVS, "r")) == NULL) {
		saverr = errno;
		rlfd = NO_Ldv;
		goto OUT;
	}

	while(1) {
		int xx;

		(void)strcpy(dvc, find_dev(Ldevices, &call));
		if(strlen(dvc) == 0)
			goto F1;	/* failure to find device */
		(void)strcpy(lock, LOCK);
		(void)strcat(lock, dvc);
		/* creat will always succeed if usr-id is 0,
			so check that case separately */
		if(geteuid() == 0 && access(lock, 0) == 0)
			goto F0;	/* device found but busy */
		if((lfd = creat(lock, 0444)) < 0)
			goto F0;	/* device found but busy */
		xx = getpid();
		(void)write(lfd, (char*)&xx, sizeof(xx));
		break;	/* we have a device, get out of here */
	F0:
		if(!call.line)		/* dial device is busy */
			continue;	/* try to find another */
	F1:
		if(call.line)
			if(found)	/* specific device request */
				rlfd = DV_NT_A;
			else
				rlfd = DV_NT_K;
		else
			if(found)	/* we are dialing */
				rlfd = NO_BD_A;
			else
				rlfd = NO_BD_K;
		goto CLOUT;
	}
	if(intflag == YES)
		rlfd = INTRPT;
	else
		if((rlfd = connect(&call)) < 0)
			undial(rlfd);
		else
			(void)alarm(UPDTE);
CLOUT:
	(void)fclose(Ldevices);
OUT:
	(void)signal(SIGINT, savint);
	(void)signal(SIGHUP, savhup);
	errno = saverr;
	return(rlfd);
}

/***************************************************************
 *	connect: establish dial-out or direct connection.
 *	Negative values returned (-1...-7) are error message indices.
 ***************************************************************/
static int
connect(call)
CALL *call;
{
	struct termio *lvp, lv;
	unsigned u;
	int er=0, dum, fdac, fd=0, t, w, x;
	char *p, sp_code, b[30];

#ifdef ddt
	if(_debug == YES) {
		say(stderr, "call connect(%o)\n", call);
		dump(call, 0);
	}
#endif

	switch(call->baud) {
		case 110:
			sp_code = (B110 | CSTOPB);
			break;
		case 134:
			sp_code = B134;
			break;
		case 150:
			sp_code = B150;
			break;
		case 300:
			sp_code = B300;
			break;
		case 600:
			sp_code = B600;
			break;
		case 1200:
			sp_code = B1200;
			break;
		case 2400:
			sp_code = B2400;
			break;
		case 4800:
			sp_code = B4800;
			break;
		case 9600:
			sp_code = B9600;
			break;
		default:
			er = ILL_BD;
			goto RTN;
	}
	if((fd = open(cul, O_EXCL | O_RDWR | O_NDELAY)) < 0) {
		perror(cul);
		er = L_PROB;
		goto RTN;
	}
	if(!call->attr)
		lvp = &lv;
	else
		lvp = call->attr;
	lvp->c_cflag |= (CREAD | HUPCL);
	if(!(lvp->c_cflag & CSIZE))
		lvp->c_cflag |= CS8;
	if( (call->telno == NULL ) && (!call->modem) )
		lvp->c_cflag |= CLOCAL;
	else
		lvp->c_cflag &= ~CLOCAL;
#ifdef	ddt
	if(_debug == YES) say(stderr,"value of cflag = %o\n\r", lvp->c_cflag);
#endif
	lvp->c_cflag &= ~CBAUD;
	lvp->c_cflag |= sp_code;
	if((t = ioctl(fd, TCSETA, lvp)) < 0) {
		perror("stty for remote");
		er = L_PROB;
		goto RTN;
	}
	if(call->telno) {
		if((fdac = open(cua, O_WRONLY)) < 0) {
			perror(cua);
			er = A_PROB;
			goto RTN;
		}
		t = strlen(strcat(strcpy(b, call->telno), ACULAST));
		/* length of number (+10) is first guess for dial time,
		    add 10 sec. for each intermediate dial tone wait */
		for(u=t+10, p=b; p=strchr(p, '='); ++p ,u+=10);
#ifdef ddt
		if(_debug == YES)
			say(stderr, "dialing %s\n", b);
#endif
		(void)alarm((u>50)? 50: u);
		w = write(fdac, b, (unsigned)t); /* dial the number */
		x = errno;
		(void)alarm(0);
		if(w != t) {
			errno = x;
			if(w == -1)
				perror("write to acu");
			else
				say(stderr, "%s: Semaphore failure\n", cul);
			er = (errno == EINTR)? D_HUNG: A_PROB;
			goto RTN;
		}
		(void)close(fdac);	/* dialing is complete */
#ifdef ddt
		if(_debug == YES)
			say(stderr, "dialing complete\n");
#endif
	}
	(void)alarm(10);	/* should answer within 10 seconds */
	dum = open(cul, O_RDWR); /* wait here for carrier */
	x = errno;
	(void)alarm(0);
	if(dum < 0) {
		errno = x;
#ifdef ddt
		if(_debug == YES)
			perror(cul);
#endif
		er = (errno == EINTR)? NO_ANS: L_PROB;
		goto RTN;
	}
	(void)close(dum);	/* the dummy open used for waiting*/
	(void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NDELAY);
RTN:
	if(intflag == YES)
		er = INTRPT;
#ifdef ddt
	if(_debug == YES)
		say(stderr, "connect ends eith er=%d, fd=%d\n", er, fd);
#endif
	return(er? er: fd);
}

/***************************************************************
 *	find_dev: find a device pair with the wanted characteristics
 *	      specified in line and baud arguments.
 *	Return pointer to device name for use in lock-out semaphore.
 *	The variables 'cua' and 'cul' will be set to contain the
 *		complete path names of the corresponding devices.
 *	If the L-devices list contains a '0' entry because the
 *		line is direct, the variable 'cua' is set to '\0'.
 ***************************************************************/

static char*
find_dev(iop, call)
FILE *iop;
CALL *call;
{
	char buf[50], typ[4], temp[15], *b;
	int tspeed;

#ifdef ddt
	if(_debug == YES) {
		say(stderr, "call find_dev(%o)\n", call);
		dump(call, 0);
	}
#endif

	if(call->telno == NULL)
		(void)strcpy(typ, "DIR");
	else
		(void)strcpy(typ, "ACU");

	while(fgets(buf, 50, iop) != NULL) {

		if (strchr("# \t\n", buf[0]) != NULL)
			continue;
		if(DIFFER(typ, strtok(buf, " \t")))
			continue;

		(void)strcat(strcpy(cul,DEVDIR),strtok((char*)0," \t"));

		if(*(b = strtok((char*)0, " \t")) == '0')
			cua[0] = '\0';
		else
			(void)strcat(strcpy(cua, DEVDIR), b);

		tspeed = atoi(strtok((char*)0," \t\n"));

		if(call->line) {
			if(strchr((b=call->line), '/') == 0) {
				(void)strcpy(temp, DEVDIR);
				b = strcat(temp, call->line);
			}
			if(DIFFER(b, cul))
				continue;
			call->baud = call->speed = tspeed;
		}
		if(call->telno) {
			if(call->speed != tspeed)
				continue;
		}
		if(call->baud > call->speed)
			continue;
		++found;
		return(1+strrchr(cul, '/'));
	}
	return("");
}

void
undial(rfd)
int rfd;
{
#ifdef ddt
	if(_debug == YES)
		say(stderr, "call undial(%d)\n", rfd);
#endif
	if(rfd > 0)
		(void)close(rfd);
	if(lfd > 0) {
		(void)close(lfd);
		lfd = -1;
		if(unlink(lock) < 0)
			say(stderr, "Can't unlink lock-file\r\n");
#ifdef ddt
		else if(_debug == YES)
			say(stderr, "Lock-file unlinked\r\n");
#endif
	}
	return;
}

static int
alrmcatch()
{
	(void)alarm(UPDTE);
	(void)utime(lock, (struct {long a,b;} *)0);
	(void)signal(SIGALRM, alrmcatch);
}

static int
hupcatch()
{
	undial(rlfd);
}

static int
intcatch()
{
	intflag = YES;
	(void)signal(SIGINT, intcatch);
}

#ifdef ddt
static void
dump(arg, fd)
CALL *arg;
int fd;
{
	struct termio xv;
	int i;

	if(fd > 0) {
		say(stderr, "\r\ndevice status for fd=%d\r\n", fd);
		say(stderr, "F_GETFL=%o\r\n", fcntl(fd, F_GETFL,1));
		if(ioctl(fd, TCGETA, &xv) < 0) {
			char buf[100];
			int x=errno;

			(void)sprintf(buf, "\rtdmp for fd=%d:", fd);
			errno = x;
			perror(buf);
			return;
		}
		say(stderr, "iflag=`%o',", xv.c_iflag);
		say(stderr, "oflag=`%o',", xv.c_oflag);
		say(stderr, "cflag=`%o',", xv.c_cflag);
		say(stderr, "lflag=`%o',", xv.c_lflag);
		say(stderr, "line=`%o'\r\n", xv.c_line);
		say(stderr, "cc[0]=`%o',", xv.c_cc[0]);
		for(i=1; i<8; ++i)
			say(stderr, "[%d]=`%o',", i, xv.c_cc[i]);
		say(stderr, "\r\n");
	}
	say(stderr,"baud=%d, ",arg->baud);
	say(stderr,"speed=%d, ",arg->speed);
	say(stderr,"line=%s, ",arg->line? arg->line: "(null)");
	say(stderr,"telno=%s\r\n",arg->telno? arg->telno: "(null)");
}
#endif
