[comp.sources.d] modem.c - bug fixes

settle@cs.nott.ac.uk (Dave Settle SMB) (02/03/87)

A thousand and one appologies ... 

The original program "modem.c" did not work. This was due to about three
separate bugs, two of them trivial and one of them MAJOR.

The diagnostic about "no utmp entry" is the major one. This is a message from
"login", complaining that it can't find its own process id in the file
/etc/utmp.

The reason it can't find it, is that the pid is actually the pid of "modem",
which is hanging around waiting for it to finish.

The fix is to change the pid value to the pid of the spawned shell (and change
it back again afterwards). This fix is in "uchange.c".

I have also had problems getting "login" to prompt for a password. This turns
out to be a result of the non-default stty settings that you have to have
to talk to the modem. This has been fixed to work on my machine, but may cause
problems on others - check the termio ioctl before the "exec".

I've also moved speed configuration to a place before the device is locked -
otherwise it gives up thinking the device is locked by someone else!

Since the changes have been reasonably extensive (and I've never been a big
fan of "diff"), I'm re-posting the whole source (it's not very big).

If you want the manpage again, mail me.

		Sorry about the bugs.

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

#! /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:
#	modem.c
#	uchange.c
#	Makefile
# This archive created: Mon Feb  2 14:10:07 1987
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'modem.c'" '(8689 characters)'
if test -f 'modem.c'
then
	echo shar: "will not over-write existing file 'modem.c'"
else
cat << \SHAR_EOF > 'modem.c'
/* 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 */

int shell, status;			/* pid of shell, and exit status */

closedown(){
	time_t t;
	if(shell) uchange(shell, getpid());	/* back to normal */
	unlink(lockf);
	unlink(altlock);
	sleep(1);				/* leave dev closed for a bit */
	t = time((long *)0);
	if(status) printf("Logout status 0x%x\n", status);
	printf("Call disconnected on %s", asctime(localtime(&t)));
	fclose(stdout);
	exit(0);
}
		
main(argc, argv, envp)
char **argv, **envp;
{
	int dev;
	long t;
	char dname[20], *arg[4], c, buff[10], *speed;
	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);
 	signal(SIGTERM, closedown);
/*
 * 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 | 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->c_send; 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);
		}
	}
/*
 * having reached here, we have to determine the speed of the connection
 */
 	dread(dev, &c, 1);
 	speed = "1200";				/* default speed */
 	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)));
/*
 * Someone has rung in - lock the device.
 */
 	lock = fopen(lockf, "w");
 	close(creat(altlock, 0666));		/* and the other one */
 /*
  * hand over to "getty"
  */
 	arg[0] = "getty";
 	arg[1] = argv[1];
 	arg[2] = speed;
	arg[3] = NULL;
	
	signal(SIGALRM, wakeup);		/* reset the alarm call */
	
	if(shell = fork()) {
/*
 * change the utmp pid to the "getty" process, so that it can login
 */
		uchange(getpid(), shell);	/* change utmp entry */
#ifdef OL3B2
	 	fprintf(lock,"       %d\n", shell);
#else
		fwrite(&shell, sizeof shell, 1, lock);
#endif
	 	fclose(lock);
	 	sleep(3); close(dev);
/*
 * wait for logout from shell. 
 * if terminal is idle for IDLETIME, force a logout anyway
 */
 		mtime = time((long *)0);	 	
		while((alarm(IDLETIME) != -1) && (wait(&status) == -1)) {
			stat(dname, &sbuf);
			otime = mtime;
			mtime = max(sbuf.st_atime, sbuf.st_mtime);
			if((mtime - otime) >= IDLETIME) {
				kill(shell, SIGHUP);
				wait(&status);
				break;
			}
		}
		closedown();		/* remove locks etc */
		/*NOTREACHED*/
	}
	else {
		sleep(1);			/* allow changes to utmp */
/*
 * We have to redo the termio settings, 'cos we can't close "dev" without
 * hanging up the phone.
 * Strange things have been known to happen during "login" with incorrect 
 * settings - if it forgets to ask you for a password, check the settings below
 */
		term.c_cc[VEOF] = 04;		/* cntrl-D */
		term.c_iflag = BRKINT | IGNPAR | ISTRIP | ICRNL;
		term.c_oflag = OPOST | ONLCR | TAB3;
		term.c_cflag &= ~CSIZE;			/* keep the speed */
		term.c_cflag |= CS7 | HUPCL | CREAD;
		term.c_lflag = ISIG | ICANON;
		ioctl(dev, TCSETAW, &term);

#if	DEBUG	 
	 	printf("%d: /etc/getty %s %s\n", getpid(), arg[1], arg[2]);
#endif
		execve("/etc/getty", arg, envp);
		printf("can't exec getty\n");
	}
	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
if test 8689 -ne "`wc -c < 'modem.c'`"
then
	echo shar: "error transmitting 'modem.c'" '(should have been 8689 characters)'
fi
fi
echo shar: "extracting 'uchange.c'" '(561 characters)'
if test -f 'uchange.c'
then
	echo shar: "will not over-write existing file 'uchange.c'"
else
cat << \SHAR_EOF > 'uchange.c'
/*
 * change the ut_pid value from "old" to "new".
 * Attempts to do all the things that "getty" appears to do.
 */
 
#include <sys/types.h>
#include <utmp.h>

struct utmp *utmp, *getutent(), *getutid();

uchange(old, new)
{
	setutent();
	while(utmp = getutent()) {
		if(utmp->ut_pid == old) {
			utmp->ut_pid = new;
			if(strcmp(utmp->ut_user, "getty")) 
				strcpy(utmp->ut_user, "modem");
			else
				strcpy(utmp->ut_user, "getty");
			pututline(utmp);
			endutent();
			return(1);
		}
	}
	printf("modem: can't find utmp entry");
	endutent();
	return(1);
}

SHAR_EOF
if test 561 -ne "`wc -c < 'uchange.c'`"
then
	echo shar: "error transmitting 'uchange.c'" '(should have been 561 characters)'
fi
fi
echo shar: "extracting 'Makefile'" '(94 characters)'
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#
# new makefile for modem.
#
PARTS = modem.o uchange.o
modem: ${PARTS}
	cc ${PARTS} -o modem
SHAR_EOF
if test 94 -ne "`wc -c < 'Makefile'`"
then
	echo shar: "error transmitting 'Makefile'" '(should have been 94 characters)'
fi
fi
exit 0
#	End of shell archive

wendt@arizona.UUCP (02/27/87)

In article <4552@robin.cs.nott.ac.uk>, settle@cs.nott.ac.uk (Dave Settle SMB) writes:
> 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);
> 	}		
> }

The character pointer, s, needs to be incremented in this loop.

Now in order to comply with Federal law and comp.sources.d practice
and traditions,  here's my contribution to the discussion about port
turnaround:


	"That's the sort of blinkard philistine pig-ignorance
	I've come to expect from you non-creative garbage.

	"Sitting all day long on your loathsome spotty behinds
	picking blackheads, not giving a tinkers cusp for the
	struggling artist.  With your Tony Jacklin golf clubs
	and your secret Masonic handshakes...  Well I wouldn't
	become a freemason now if you got on your lousy
	stinkin knees and begged me!"

Alan Wendt
U of AZ Computer Science