[comp.sources.d] modem: a bi-directional uucp port controller

settle@cs.nott.ac.uk (Dave Settle SMB) (01/15/87)

The following is a program which allows a port to be used for bi-directional
uucp transfers. It also incorporates support for configuring a modem which
may be attached to the line.

It needs to be run in place of "getty" on the particular port, and has been
tested under System V - if you have BSD, I'm not sure whether you can easily
replace "getty" with another command.

Unfortunately, you will probably need to hack the program a little if:

	a) You don't have an Olivetti 3B2

	b) You're not running System V


	c) You don't have a Hayes-type modem connected to the port.

The program is supposed to be self-documenting, but that only means that I know
what it does!

You will CERTAINLY have to examine your dialer scripts, so that they

	a) Give "modem" a chnce to notice they aer there

	b) Won't fall over when "modem" eats the first character.

It should work on lines which don't have modems attached - just give a null
conversation & remove the speed configuration section.

Also, note that replies should be set to me at "smb", not "nott-cs"
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	modem.man
#	modem.c
# This archive created: Thu Jan 15 13:13:29 1987
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
A program to implement BIDIRECTIONAL uucp ports, via modems or direct links

"modem" is a program is run in place of "getty" on a terminal,  and 
allows the tty port to be used as a BI-DIRECTIONAL port for BOTH uucp and cu.

This means that incoming calls will be answered, so that callers can
connect to your machine, and also that you may place outgoing calls, 
via uucp, cu, kermit, or whatever, without having to explicitly disable the
port, or kill any process.

The program also incorporates a uucp-like "send-expect" conversation with
the modem, which allows you to put it in the correct mode for auto-answer,
useful if the uucp dial-out script leaves it in an unknown state.

To make the program work, you will need to mash it a bit to reflect your
environment. You need:

	a) To make it spawn instead of getty

	b) Change the lock files in the program to reflect YOUR system.

	c) Mess around with the modem script to make it talk to YOUR modem.

	d) (HARDER) Change the way it detects the speed of the conversation
	   My modem is a Hayes-compatible which says:
		CONNECT			(300 Baud)
	or	CONNECT 1200
	or	CONNECT 1275
	   So, since the "conversation" finishes when the program sees
"CONNECT" (see "c"), I then read the next char and then possibly the next 4.
This then allows me to spawn "getty" with the CORRECT speed argument, to save
all the messing around with BREAKs.

  I guess that this really isn't necessary, 'cos getty is supposed to be able
to figure it out, but in practise it's not always easy (or even possible). If
your modem doesn't tell you what speed it got connected at, you'll have to
chop that bit out.


SHAR_EOF
fi
if test -f 'modem.man'
then
	echo shar: "will not over-write existing file 'modem.man'"
else
cat << \SHAR_EOF > 'modem.man'

MODEM(1M)			(Utilties)			MODEM(1M)

NAME
	modem - bi-directional port control.

SYNOPSIS
	modem ttyname

DESCRIPTION
	Modem is run in place of getty on a terminal, and allows the terminal
to be used for bi-directional uucp/cu conversations.

	Modem starts a conversation with the modem, to place it in auto-answer
mode, and then waits for an incoming call to be connected. It the spawns a
version of "getty" on the terminal, to allow the incoming call to login.
It also creates a lock file, to prevent the line being used by outgoing calls,
Modem waits for the login process to terminate, removes the lock file, and 
terminates - to be respawned by the init process.

	If the shell is idle for a long time (default 30 minutes), modem will
send a hangup signal to the shell, and close the line, thus preventing 
an old shell from being available to the next caller.

	If, at any time prior to a call being connected, a lock file is created
by uucp or any other utility, modem will immediately exit. At most one character
will be lost to the outgoing call. However, the outgoing call MUST give modem
the opportunity to notice the lock file - this is best done by calling sleep(3)
after creating the lock file, but BEFORE reading the first response from the
device.

	The new version of modem which is spawned will wait indefinately for
the lock file to be removed before commencing any activities.

EXAMPLES
	modem tty44

FILES
	/usr/lib/modem.log		Log file of connects and attempts
	/usr/spool/uucp/LCK..????	Lock file (Varies with systems!)

SEE ALSO
	uucp(1), cu(1), sleep(3), init(8), getty(8)

BUGS
	One character from the device will be "eaten" when an outgoing call
is attempted. You can force a particular char to be eaten by doing a sleep(3)
when it is available (e.g. you have prodded the device), but before trying 
to read it.

SHAR_EOF
fi
if test -f 'modem.c'
then
	echo shar: "will not over-write existing file 'modem.c'"
else
cat << \SHAR_EOF > 'modem.c'
Received: from oscar by Robin.nott-cs.uucp id a013091; 13 Jan 87 18:19 GMT
To: nott-cs!dave@smb.co.uk
Subject: Update to modem.c
From: dave@smb.co.uk
Date: Tue, 13 Jan 87 15:18:00 GMT


/*
 * modem.c: a bi-directional "getty" to allow incoming calls AND outgoing 
 * uucico's
 *
 * A script is defined to place the modem in a state where it will accept
 * incoming calls (i.e. auto-answer). If a call arrives, a getty is spawned
 * and a device lock created, thus preventing outgoing calls.
 * If uucp tries to use the line, the program notices the lock file, 
 * and abandons the session. "init" then starts another version off, which
 * suspends itself until the device is not locked.
 *
 * For your local site, you have to do the following:
 *
 * Put this line in /etc/inittab
 * XX:2:respawn:/usr/lib/modem ttyXX		where ttyXX is your modem line
 *
 * Make sure that your dialer script is prepared to:
 *	a) sleep for > 1 sec BEFORE reading the first reply from the modem
 *	b) accept that the first character sent by the modem will NOT get there
 *
 * e.g. Hayes dialer script is NORMALLY
 *
 *	... AT OK ATDP\T CONNECT
 *
 * but MUST be modified to
 *
 *	... AT\r\D OK ATDP\T CONNECT  		#( \D is a 2-sec pause )
 * 
 *
 * Modify the included dialer script to talk to YOUR modem
 * Modify the speed-detection routine, if your modem is not a hayes-compatible
 *
 * Modify "locked()" to read YOUR lock directory
 *
 */
#include <sys/types.h>
#include <termio.h>
#include <stdio.h>
#include <signal.h>
#include <sys/stat.h>
#include <setjmp.h>

#define PNUMBER	'T'				/* \T is "insert tel no" */
#define WTIME 5					/* default wait time */

#define LOGFILE "/usr/lib/modem.log"		/* log file */
#define max(a,b) (a) > (b) ? (a) : (b)

#define DEBUG 0					/* if "1", you get told what's
						 * going on
						 */
#define OL3B2 1					/* my machine is a 3B2 */
#define IDLETIME 30*60				/* autologout time */

long time();
char *localtime();

struct conv {
	char *c_send;
	char *c_expect;
	int c_wait;		/* time to wait for c_expect */
} ring[] = {
	{"AT\rAT", "OK", WTIME },	/* anyone there? */
	{"ATX1V1", "OK", WTIME },	/* turn on responses */ 
	{"ATS0=1", "OK", WTIME },	/* enable auto answer */
	{"ATS9=10", "OK", WTIME},	/* 1 sec carrier before detect */
	{"ATS10=14", "OK", WTIME},	/* 400 msec carrier loss is OK */
	{"ATS24=3", "OK", WTIME},	/* step speed 3 seconds */
	{"\\c", "RING", 0},		/* wait (indefinately) for RING in */
	{"\\c", "CONNECT", 20},		/* wait for call to be connected */
	{NULL, NULL, 0}			/* finished */
};
char lockf[50];				/* argument lock file */
/*
 * We have 2 /dev files linked together, so it is also necessary to look
 * for a lock on the modem device
 */
#ifdef OL3B2
char *altlock = "/usr/spool/locks/LCK..modem";
#else
char *altlock = "/usr/spool/uucp/LCK..modem";
#endif

wakeup(){}			/* gets called when the alarm goes off */

main(argc, argv, envp)
char **argv, **envp;
{
	int dev, shell;
	long t;
	char dname[20], *arg[4], *speed, c, buff[10];
	struct conv *p;
	struct termio term;
	struct stat sbuf;
	time_t otime, mtime;
	FILE *lock;
/*
 * make ourselves immune from hangups
 */
 	signal(SIGHUP, SIG_IGN);
 	signal(SIGINT, SIG_IGN);
 	signal(SIGQUIT, SIG_IGN);
 	signal(SIGALRM, wakeup);
/*
 * running from getty, we don't have a terminal, so output to LOGFILE
 */
 	freopen(LOGFILE, "a", stdout);
 	setvbuf(stdout, NULL, _IOLBF, 0);
 	 	
	sprintf(dname, "/dev/%s", argv[1]);
#ifdef OL3B2
	sprintf(lockf, "/usr/spool/locks/LCK..%s", argv[1]);
#else
	sprintf(lockf, "/usr/spool/uucp/LCK..%s", argv[1]);
#endif
	if((dev = open(dname, 2)) == -1) {
			perror(dname);
			sleep(5);	/* don't go crazy with respawns */
			exit(1);
	}
/*
 * do not start while device is lockeed
 */
 	while(locked()) sleep(30);
 /*
  * setup correct time zone
  */
 	putenv("TZ=GMT0BST");
 /*
  * set terminal parameters
  */
	if(ioctl(dev, TCGETA, &term) == -1) perror("TCGETA");
	term.c_lflag = ISIG | ECHOK;
	term.c_iflag = BRKINT;
	term.c_oflag &= ~OPOST;
	term.c_cflag = B1200 | CREAD | CLOCAL | HUPCL | CS8;
	term.c_cc[VMIN] = 1;
	term.c_cc[VTIME] = 0;
 	if(ioctl(dev, TCSETAW, &term) == -1) perror("TCSETA");
/*
 * send-expect strings - at the end, someone has connected to the modem
 */
	for(p = ring; *p; p++) {
		send(p->c_send, dev);
		if(expect(p->c_expect, p->c_wait, dev)) {
			t = time((long *) 0);
			printf("Incoming call failed to connect on %s", asctime(localtime(&t)));
			fclose(stdout);
			exit(1);
		}
	}
/*
 * Someone has rung in - lock the device.
 */
 	lock = fopen(lockf, "w");
 	close(creat(altlock, 0666));		/* and the other one */
/*
 * having reached here, we have to determine the speed of the connection
 */
 	dread(dev, &c, 1);
 	if(c == '\r') speed = "300";
 	else {
 		dread(dev, buff, 4);
 		if(!strcmp(buff, "1200")) speed = "1200";
 		if(!strcmp(buff, "2400")) speed = "2400";
 		if(!strcmp(buff, "1275")) speed = "1200";
 		if(!strcmp(buff, "7512")) speed = "1200";
 	}
 	t = time((long *)0);
 	printf("Call connected at %s on %s", speed, asctime(localtime(&t)));
 /*
  * hand over to "getty"
  */
 	arg[0] = "/etc/getty";
 	arg[1] = argv[argc - 1];
 	arg[2] = speed;
 	arg[3] = NULL;
#if	DEBUG
 	printf("/etc/getty %s %s\n", arg[1], arg[2]);
#endif
	if(shell = fork()) {
#ifdef OL3B2
	 	fprintf(lock,"       %d\n", shell);
#else
		fwrite(&shell, sizeof shell, 1, lock);
#endif
	 	fclose(lock);
/*
 * wait for logout from shell. 
 * if terminal is idle for IDLETIME, force a logout anyway
 */
 		mtime = time((long *)0);	 	
		while(alarm(IDLETIME) && wait((int *) 0) == -1) {
			stat(dname, &sbuf);
			otime = mtime;
			mtime = max(sbuf.st_atime, sbuf.st_mtime);
			if((mtime - otime) >= IDLETIME) {
				kill(shell, SIGHUP);
				break;
			}
		}
		unlink(lockf);
		unlink(altlock);
		close(dev);		/* shut down the line */
		sleep(5);		/* pause to allow things to happen */
		t = time((long *)0);
		printf("Call disconnected on %s", asctime(localtime(&t)));
		fclose(stdout);
		exit(0);
	}
	else execve("/etc/getty", arg, envp);
	exit(0);
}

/*
 * send: send the string to the modem
 */
send(s, dev)
char *s;
{
	int nocr = 0;
#if	DEBUG
	sshow("send(");
#endif
	for(;*s;s++) {
		if(*s == '\\') switch(*++s) {
		case 'c':
			nocr = 1;
			continue;
		default:
			printf("%c: unknown escape char\n");
		}
		dwrite(dev, s, 1);
		show(*s);
	}
	if(nocr == 0) {
		dwrite(dev, "\r", 1);
		sshow("\\r)\n");
	}
	else sshow("<no cr>)\n");
	sleep(1);
}
/*
 * expect: expect a string from the modem
 * chars are threaded on the needle as they match the expect string
 * if one fails to match, all chars are unthreaded. 
 * expect succeeds if all chars on expect string are threaded.
 */
jmp_buf env;

timeout(){
	longjmp(env, 1);
}

expect(s, t, dev)
char *s;
{
	char *needle = s, c;
	signal(SIGALRM, timeout);
	if(setjmp(env)) {
		printf("modem: timeout expecting %s\n", s);
		return(1);		/* fail - timeout */
	}
	alarm(t);				/* if zero, no timeout */
#if	DEBUG
	printf("expect(%s)\n", s);
#endif
	while(*needle) {
		dread(dev, &c, 1);
		show(c);
		if(*needle == c) needle++;
		else needle = s;
	}
#if	DEBUG
	printf("got it\n");
#endif
	return(0);
}
/*
 * sshow: show string
 */
sshow(s)
char *s;
{
	while(*s) show(*s++);
}
show(c)
char c;
{
#if	DEBUG
	if((c < 31) && (c != '\n')) printf("^%c", c + '@');
	else putchar(c);
	fflush(stdout);
#endif
}
/*
 * check for presence of lock file
 * return 1 if locked.
 */
locked(){
	struct stat sb;
	if((stat(lockf, &sb) == -1) && (stat(altlock, &sb) == -1)) return(0);
#if	DEBUG
	printf("%s locked\n", lockf);
#endif
	return(1);
}
dwrite(f, s, n)
int f, n;
char *s;
{
	if(locked()) exit(0);
	return(write(f, s, n));
}
dread(f, s, n)
int f, n;
char *s;
{
	int i;
	for(i=0;i<n;i++) {
		if(locked()) exit(0);
		while(read(f, s, 1) < 1) if(locked()) exit(0);
	}		
}

SHAR_EOF
fi
exit 0
#	End of shell archive

Dave Settle, SMB Business Software, Thorn EMI Datasolve

UUCP:
	...!mcvax!ukc!nott-cs!smb!dave	
	dave@smb.UUCP

SMAIL:					Voice:
	SMB Business Software		     +44 623 651651
	High Street
	Mansfield
	Nottingham NG18 1ES		Telex:
	England				     37392 TECSMG

coleman@sask.UUCP (01/23/87)

The version of UUCP I got with our 3b2/400 comes with a replavement for getty
called ugetty. Don't all Sys V distributions have this.

-- 
Geoff Coleman                         | BITNET: Coleman@sask
College of Engineering                | UUCP: {utcsri,ihnp4}!sask!skul!geoff
University of Saskatchewan            | Compserve: 76515,1513  just a number 
Saskatoon, Saskatchewan               | voice: (306) 966-5415

tank@apc3b2.UUCP (01/30/87)

In article <586@sask.UUCP>, coleman@sask.UUCP (Geoff Coleman) writes:
> 
> The version of UUCP I got with our 3b2/400 comes with a replavement for getty
> called ugetty. Don't all Sys V distributions have this.

All 3Bx will have uugetty.  Warning though about shutdown.  You have to
modify the order of files in shutdown.d to kill the uugettys LAST (I think,
I don't use it anymore).  It also has had some problems with spurious garbage
tying up the system (except for the console).  Not a nice thing to do.

-- 
-tank-
{ ... }!okstate!apc3b2!tank
Jon A. Tankersley, Amoco Corporation
PO Box 591, MS N1068; Tulsa, Oklahoma, 74013

rbl@nitrex.UUCP (01/31/87)

In:
Message-ID: <357@apc3b2.UUCP>


Jon notes:
>In article <586@sask.UUCP>, coleman@sask.UUCP (Geoff Coleman) writes:
>> 
>> The version of UUCP I got with our 3b2/400 comes with a replavement for getty
>> called ugetty. Don't all Sys V distributions have this.
>
>All 3Bx will have uugetty.  Warning though about shutdown.  You have to
>modify the order of files in shutdown.d to kill the uugettys LAST (I think,
>I don't use it anymore).  It also has had some problems with spurious garbage
>tying up the system (except for the console).  Not a nice thing to do.
>
>-- 

Also, if uugetty is running on a 3B serial port, someone coming into that
port will have to try twice.  The first time, uugetty "turns itself around"
for incoming service.  The second time, it connects.

If you have a long access sequence to get thru data switches, access control
passwords, networks, etc., this CAN be a pain.  Particularly if the uugetty
decides to "turn itself back" into an outgoing port before you can access
it again.  Of course, if you have an access method that may connect you to
a different port on the next call in, it's become a neat game ....

Rob Lake
decvax!cwruecmp!nitrex!rbl
ihnp4!{cbatt,cbosgd}!nitrex!rbl