/* ixocico -- IXO *manual* protocol call-in call-out.
**
** Completely slammed by Tom Jennings 20 Mar 94. Made modem
** stuff BSD compatible, used manual mode instead of
** automatic mode (which I could not get to work with Pac*Tel paging
** and saw no disadvantage to the profoundly simpler manual mode).

The Pac*Tel manual sequence is, in toto:

 ID=M 					(enter "M\r")
 Pager ID? 2320853 			(enter "pager ID\r")
 Message: testing 1 2 3 4 5 6 7 8 9 0 testing (message\r)
 Page accepted.  
 Pager ID?  				(enter "\r")
 Thank you. 
(disconnect)


**
** Original work by:
**   by Tom Limoncelli, tal@warren.mentorg.com
**   Copyright (c) 1992, Tom Limoncelli
**   The sources can be freely copied for non-commercial use only
**   and only if they are unmodified.
**
** Version 2.0 -- See file HISTORY for details.
**  $Id: ixocico.c,v 1.4 1992/09/22 17:31:01 root Exp $

$Log: ixocico.c,v $
 * Revision 1.4  1992/09/22  17:31:01  root
 * set serial parameters to 300bps, 7e1
 *
 * Revision 1.3  1992/09/21  19:41:31  root
 * made a small change to a comment
 *
 * Revision 1.2  1992/09/21  19:40:22  root
 * changed parity to ODD
 *
 * Revision 1.1  1992/09/21  19:38:02  root
 * Initial revision
 *

*/

/****************************************************************/
/* USER CONFIGURABLE OPTIONS: */

/* this should be "#define" if you use SunOS, or "#undef" if you
** use HPUX.  This controls the name of LOCKDIR and if getpriority()
** is used.  I'm sure more needs to be done, but that's a small start.
*/
#define REAL_OS

#ifdef REAL_OS
#define LOCKDIR	"/var/spool/uucp"
#else
#define LOCKDIR "/usr/spool/locks"
/* That may not be correct */
#endif

/* not talking to the modem correctly?  Try mucking with
the grabmodem() routine.  */

/* END OF USER CONFIGURABLE OPTIONS */
/****************************************************************/

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
/* #include <strings.h> */
#include <ctype.h>
#include <errno.h>
#include <sys/termios.h>
#include <sys/param.h>
#include <termios.h>

#ifdef REAL_OS
#include <sys/time.h>		/* required for <sys/resource.h> */
#include <sys/resource.h>	/* required for getpriority() */
#endif

/* ASCII constants */
#define STX (2)
#define EOT (4)
#define ACK (6)
#define LF (10)
#define CR (13)
#define NAK (21)
#define ESC (27)
#define RS (30)

#define MAX_PACKET	(10000)	/* we'll never get a packet this big */
#define MAXLINE	(1000)

/* only two little global variables, how's that? */

int modem = 0;
char *lockname = NULL;

/* print a string without worrying about unprintable charactors */
void 
safeprint(str)
char           *str;
{
	while (*str) {
		if (isgraph(*str))
			(void) fprintf(stdout, "%c", *str);
		else {
			switch (*str) {
			case LF:
				(void) fprintf(stdout, "\\n");
				break;
			case CR:
				(void) fprintf(stdout, "\\r");
				break;
			case 32:
				(void) fprintf(stdout, "\\s");
				break;
			default:
				(void) fprintf(stdout, "\\%d", *str);
				break;
			}
		}
		str++;
	}
	fflush(stdout);
}

/* open the modem.  You should have done a lockmodem() first */

int grabmodem(dev)
char           *dev;
{
int fd;
struct termios tp;

	if ((fd = open(dev, O_RDWR | O_NDELAY)) < 0) {
		perror(dev);
		exit(1);
	}

	if (tcgetattr (fd, &tp) < 0) {
		perror("#ixocico:grabtty:tcgetattr");
		return(0);
	};
	cfmakeraw(&tp);

/* set tty params to 300bps, even parity, 7-1-e */

	tp.c_cflag |= CS8 | CLOCAL | CREAD | HUPCL | CRTSCTS;
	cfsetspeed(&tp, 38400);

	if (tcsetattr (fd,TCSADRAIN, &tp) < 0) {
		perror("#ixocico:grabtty:tcsetattr");
		return(0);
	};
	return(fd);
}

/* send data to the modem */
void send(fd, str)
int             fd;
char           *str;
{
	printf("Sending: :"); safeprint(str); printf(":\n", str);
	write(fd, str, strlen(str));
}

/* wait for a particular string from the modem (err = # of retries
permitted ) */

int match(mod, str, err) 
	int mod;
	char *str;
	int err;
{
	int len;
	char *s, c;

printf("MATCHING on :"); safeprint(str); printf(":\n", str);

	s= str;
	while (err) {
		c = 0; len = read(mod, &c, 1);
		c &= 0x7f;		/* parity strip! */
		if (len && c) {
printf("want: %c got: %c\n", *s, c);
			if (c == *s) s++; else s= str;
			if (!*s) {
				printf("MATCHED\n");
				return(0);
			}
		} else {
			--err;
			sleep(1);
			continue;
		}
	}
	printf("NOT MATCHED\n");
	return 1;
}

/* hang up the modem */
void 
hangup_modem(fd)
int             fd;
{
	sleep(3);
	send(fd, "+++");
	sleep(3);
	send(fd, "ATH\r");
	sleep(1);
}

/* unlock the modem */
void unlockmodem(name)
char           *name;
{
	printf("Unlocking modem.\n");
    (void)unlink(name);
    return;
}

/* clean up and leave this program */
void bail_out()
{
	if (modem) {
		hangup_modem(modem);
		close(modem);
	}
	if (lockname) unlockmodem(lockname);
	exit(0);
}

/* lock the modem OR DIE*/
char *lockmodem(dev)
char           *dev;
{
	int lock, pid;
	int failcnt = 3;
	char waitcnt = 0;
	char *lkn, lname[200];

	strcpy(lname, LOCKDIR);
	strcat(lname, "/LCK..");
	strcat(lname, 1 + rindex(dev, '/'));

printf("Lockfile = %s\n", lname);
	lkn = strdup(lname);
	while (failcnt--) {
		errno = 0;
		lock = open(lname, O_CREAT | O_WRONLY | O_EXCL, 0777);
		if (lock == -1) {
#ifdef REAL_OS
			printf("Modem is locked, attempting to steal.\n");
			/* locked, let's read the cookie in the lock */
			pid = 0;
			if ((lock = open(lname, O_RDONLY)) != -1) {
				(void)read(lock, &pid, sizeof(int) );
				printf("Device is locked by process %d\n", pid);
				close(lock);
			}
			printf("Lock = %d\n", lock);
			if (pid < 3) {
				printf("#MODOPEN device is locked by pid < 3\n");
				bail_out();
			}
			/* see if the process still is alive */
			errno = 0;
			(void) getpriority(PRIO_PROCESS, pid);
			if (errno == ESRCH) {   /* lock process dead, let's go! */
				if (unlink(lname)) {
					printf("#MODOPEN Can't steal lock.\n");
					bail_out();
				} else {
					printf("Lock is stale, stealing!\n");
				}
			} else {
				printf("#MODOPEN Valid lock in the way.\n");
				bail_out();
			}
#else
			printf("#MODOPEN it's locked, I'm out of here!\n");
			bail_out();
#endif
		} else {
			/* lock opened, stuff and go */
			pid = getpid();
			write(lock, &pid, sizeof(int));
			close(lock);
			break;
		}
	}
	if (failcnt==-1) {
		printf("#MODOPEN Couldn't lock modem after many tries.\n");
		bail_out();
	}
	return lkn;
}

/* get a line from stdin OR DIE */
char *getline(line)
char *line;
{
	int len;
	char *r;

	/* get a line, if EOF return 0 */
	if (!(r = fgets(line, MAXLINE, stdin))) return 0;

	printf("Data in queue=:"); safeprint(line); printf(":\n", line);

	if (!(len = strlen(line))) {
		printf("#BADQUEUE Blank line in queued data\n");
		bail_out();
	}

	if (line[len-1] == '\n') {
		line[len-1] = 0;
	} else {
		/* if fgets didn't return a string ending in \n */
		printf("#BADQUEUE Data in queue has line too long\n");
		bail_out();
	}
	return r;
}

int main(argc, argv)
int argc;
char *argv[];
{
	char dialstring[MAX_PACKET];
	char *pack = dialstring;	/* use same workspace */
	char line[MAXLINE+1];
	char pin[MAXLINE+1];
	char mesg[MAXLINE+1];
	int mesnum, failcnt, len;
	char c;

	/* check arguments */
	if (argc != 3) {
		printf("#WRONGARGS wrong number of arguments\n");
		bail_out();
	}

	/* lock modem or die */
	lockname = lockmodem( argv[1] );
	/* open modem or die */
	printf("opening modem\n");
	modem = grabmodem( argv[1] );
	if (!modem) bail_out();

/* Wake up the modem. COmmand it to what we want. */

	failcnt = 3;
	while (failcnt--) {
		send(modem, "ATM0X1E1Q0V1\r");
		sleep(1);
		send(modem, "ATM0X1E1Q0V1\r");

		if (match(modem, "OK", 10)) {
			printf("No response.  Hang up and try again.\n");
			hangup_modem(modem);
		} else break;
	}
	if (failcnt==-1) bail_out();

	sleep(1);		/* modem settle time */

printf("dialing\n");

	/* send the "A" of "ATDT" */
	do {
		send(modem, "A");
	} while (match(modem, "A", 2));

	/* send the rest of the dial string */

	sprintf( dialstring, "TDT%s\r", argv[2] );
	send(modem, dialstring);
	(void) match(modem, argv[2], 1000);

	/* wait for the modem to connect */
	printf("waiting for CONNECT\n");
	if (match(modem, "CONNECT", 60)) {
		printf("#NOCONN no connect\n");
		bail_out();
	}
	match(modem, "\r", 50);			/* end of "CONNECT" message */

/* Wait for the opening response of "ID=". It waits for an initial
CR to start (autobaud?). To take care of line noise (eg. "ID~i=")
we issue "?\r". If indeed in autobaud, the ? is extraneous and
ignored; if we simply missed the ID= prompt, the incorrect response
causes the prompt to be reissued. */

printf("Waiting for \"ID=\"\n");

	failcnt = 10;
	while (failcnt--) {
		send(modem, "\r");		/* send initial CR */
		if (!match(modem, "ID=", 5)) break;
	}
	if (failcnt==-1) bail_out();

	printf("Sending M\n");
	send(modem, "M\r");
	match(modem, "\r", 10);			/* wait for the echo */
	sleep(1);


/* Now send all messages to all pagers. Under ideal circumstances
the loop terminates when getline(pin) reaches EOF. */

printf("Processing messages\n");

	for (mesnum=0; getline(pin); ) {
		getline(mesg);

/* Now wait for the prompt, "Pager ID?". If line noise trashes the
response (eg. "Pager I~iD?") issue a known-bad ID to make it reprompt
if we time out waiting. */

		failcnt= 10;
		while (failcnt--) {

printf("Waiting for \"Pager ID?\"\n"); 
			if (match(modem, "ID?", 30)) {
				send(modem, "?????\r");
				continue;
			}
			send(modem, pin);	/* send pager ID */
			send(modem, "\r");	/* and CR */
			match(modem, pin, 10);	/* flush the echo */

printf("Waiting for \"Message:\"\n");
			if (!match(modem, "age:", 60)) {
				send(modem, mesg);
				send(modem, "\r");
printf("#MESOK %d message xmitted fine\n", mesnum++);
				match(modem, mesg, 20);
				match(modem, "Page accepted.", 20);
				break;
			}
		}

		if (failcnt==-1) {
printf("#MESREJECT %d message rejected\n", mesnum);
			bail_out();
		}
	}

/* No more messages; send a null pager ID and terminate. */

	printf("#DONE we're done.  Logging out.\n");

	send(modem, "\r\r");		/* answer "Pager ID?" with null line */
printf("Waiting for \"Thank you.\"\n");
	match(modem, "Thank you.", 50);	/* try to be clean about this */
	sleep(2);

	printf("#BYE we're leaving.\n");
	bail_out();
}
