[net.sources] ibm-pc xmodem utility with auto dial/login

pingel@wang7.UUCP (07/08/86)

Here is a simple xmodem utility.  I have used it for a while
with a semi-clone.  Hopefully others with not quite compatible clones
will also find this a useful solution.  Constructive criticism is
welcome.  Flames > /dev/null. :-)

NOT A SHAR FILE!

----------- cut here ------------

/* 
This is a simple pc communications utility.  Protocols supported are
xmodem and xon/xoff.  Other protocols may be added in future.
All commands are available via meta key, ie. <alt>.
Be warned that this code contains hardware dependant routines, see notes.

Terminal emulation is the normal mode.  <alt><command-letter> invokes
file transfers etc.  See main() for commands.
*/

/* Notes:
	a) Timing loops are for 8 Mhz 80186.  Decrease by factor of ~3 for
	   IBM PC.  See defines at start of code.  If you have system
	   wait calls throw out the loops.
	b) Hardware dependant code exists at end of this file.  Convert
	   com* routines to use interrupt 14 bios calls where available.
	c) You will need to change MS-DOS bdos calls to conform to your
	   libraries.
	d) This communications utility was developed for use with Hayes
	   compatible modems.  The modem must be set for V0 and E0 prior
	   to use.  These settings provide for numerical responses and no
	   echo, respectively.
	e) Terminal emulation depends on ansi.sys for handling of tabs
	   and escape sequences.
	f) Putcnb() is simply non-buffered putc.  This was used to eliminate
	   need for newline in display.  Replace with putc if possible.
*/


/* 
This program is placed in the public domain.  Distribution is allowed
provided charges levied do not exceed distribution costs.  Modified
versions may be distributed if this notice is included with a list
of modifications.
Copyright 1986 Lee D. R. Pingel
*/


#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <ctype.h>

/*	installation dependant parameters		*/
#define CONTM	10	/* seconds to dial and connect */
#define MCMDTM	3	/* seconds for modem to respond to command */
#define COMINPS	40000L	/* loops per second, approximately */
#define LPS	180000L	/* simple wait loop interations per second */



#define	ERROR	-1
#define FALSE	0
#define	TRUE	~FALSE
#define DATAMASK 0x7f
#define BLKSIZ 128
#define BPBUF 32
#define BUFFSIZ (BLKSIZ * BPBUF)
#define RETRYMAX 5
#define MAXLOGIN 5

/* special character defines */
#define TIMEOUT -1
#define QUIT 0x1b
#define SOH 1
#define EOT 4
#define ACK 6
#define NAK 0x15
#define XON 0x11
#define XOFF 0x13
#define CAN 0x18

#define ALTP 25
#define ALTR 19  
#define ALTS 31
#define ALTQ 16
#define ALTX 45
#define ALTB 48
#define ALTC 46
#define ALTD 32

#define SYSFILE "systems.txt"

/* global parameters */
int	connected = FALSE;		/* state of phone line */
char	buffer[BUFFSIZ];		/* general I/O buffer */
int	baud = 1200;			/* normal serial settings */
int	bits = 7;
int	sbits = 1;
char	parity = 'E';
char	proto = 'x';
/* I/O trace flag */
int	trace = FALSE;


/* xmodem receive and send */

recvxmodem(file)
char *file;
{
	int blkcur, blk, blkcomp;
	int checksum, moddata, firstchar, attempts;
	int fd;
	unsigned int j, bufptr;
	
	fd = creat(file);
	if(fd == ERROR){
		return(FALSE);
	}
	blkcur = 1;
	bufptr = 0;
 	attempts = 0;
	serialset(baud, 8, 2, 'N');
	comout(NAK);
	do{
		/* initial synchronization */
		do {
			firstchar = cominw(5);
		}while(firstchar != SOH
			&& firstchar != EOT
			&& firstchar != CAN
			&& firstchar != TIMEOUT);
		if (firstchar == TIMEOUT){
			comout(NAK);
			printmsg("Timeout error.");
			attempts++;
			continue;
		}
		if (firstchar == CAN){
			comout(ACK);
			printmsg("Transfer canceled by host.");
			break;
		}
		if (firstchar == EOT){
			printmsg("Transfer complete.");
			comout(ACK);
			break;
		}
		blk = cominw(1);
		blkcomp = cominw(1);
		if(blk == (~blkcomp & 0xff) && blk == ((blkcur - 1) & 0xff)){
			comout(ACK);
			printmsg("Duplicate block %x.", blkcur);
			while(cominw(1) != TIMEOUT);
			attempts++;
			continue;
		}
		if(blk != (~blkcomp & 0xff) /*  || blk != (blkcur & 0xff) */ ){
			/* commented out as some xmodem go 255,1,2 */
			comout(NAK);
			printmsg("Block number error, wanted %x and got %x.",
				blkcur, blk);
			while(cominw(1)!= TIMEOUT);
			attempts++;
			continue;
		}
		checksum = 0;
		for(j = bufptr; j <(bufptr + BLKSIZ); j++){
			moddata = cominw(1);
			if(moddata == TIMEOUT) break;
			buffer[j] = moddata;
			checksum = ((checksum + moddata) & 0xff);
		}
		moddata = cominw(1);
		if(checksum != (moddata & 0xff)){
			comout(NAK);
			printmsg("Checksum error, got %x expected %x.",
				checksum, moddata & 0xff);
			attempts++;
			continue;
		}
		printmsg("Block %d.",blkcur);
		bufptr += BLKSIZ;
		blkcur++;
		if(bufptr >= BUFSIZ){
			bufptr = 0;
			if(write(fd, buffer, BUFSIZ)== ERROR){
				comout(CAN);
				printmsg("Error writing file.");
				break;
			}
		}
		comout(ACK);
	}while (attempts < RETRYMAX);
	if(attempts >= RETRYMAX)
		comout(CAN);
	serialset(baud, bits, sbits, parity);
	comout(ACK);
	if(write(fd, buffer, bufptr)== ERROR){
		printmsg("Error writing file.");
	}
	close(fd);
}



sendxmodem(file)
char *file;
{
	int	fd, blks, attempts, blkcur, checksum;
	unsigned j, bufptr;
	char	sync;

	fd = open(file, 2);
	if(fd == ERROR){
		printmsg("Cannot open %s.", file);
		return;
	}
	blkcur = 1;
	serialset(baud, 8, 2, 'N');
	for(attempts = 0; attempts < 10;){
		sync = cominw(1);
		if(sync == NAK)
			break;
		else if(sync == TIMEOUT)
			attempts++;
	}
	attempts = 0;	
	while((blks = read(fd,buffer,BUFFSIZ)) > 0 &&
		attempts < RETRYMAX){
		if(blks == ERROR){
			printmsg("Error reading file.");
			close(fd);
			return;
		}
		blks = (blks % BLKSIZ) == 0 ? blks/BLKSIZ :(blks/BLKSIZ) + 1;
		bufptr = 0;
		do{
			attempts = 0;
			do{
				comout(SOH);
				comout(blkcur & 0xff);
				comout(~blkcur & 0xff);
				checksum = 0;
				for(j=bufptr;j<(bufptr+BLKSIZ);j++){
					comout(buffer[j]);
					checksum = (checksum + buffer[j]) & 0xff;
				}
				comout(checksum);
			}while(cominw(1)!= ACK && attempts++ < RETRYMAX);
			printmsg("Block %d.",blkcur);
			bufptr += BLKSIZ;
			blkcur++;
			blks--;
		}while(blks && attempts < RETRYMAX);
	}
	if(attempts >= RETRYMAX){
		printmsg("No acknowledgment of block, aborting.");
		comout(CAN);
	}
	else{
		attempts = 0;
		do{
			comout(EOT);
		}while(cominw(1)!= ACK && attempts++ < RETRYMAX);
		if(attempts >= RETRYMAX){
			printmsg("No acknowledgement of EOF.");
		}
	}
	serialset(baud, bits, sbits, parity);
	close(fd);
	return;
}

recvascii(file)
char	*file;
{
	char	kbdata, moddata;
	int	fd;
	char	*bp,*limit, tmp[16];

	fd = creat(file);
	if(fd == ERROR){
		printmsg("Cannot create %s.",file);
		return;
	}
	bp = buffer;
	limit = bp + BUFFSIZ;
	kbdata = '\0';
	purgeline();
	while( comcts()&&(kbdata != QUIT)){
		if(kbdrdy()){
			kbdata = kbdin();
			if (kbdata != QUIT)
				comout(kbdata);
		}
		if(cominrdy()){
			moddata = comin();
			putcnb(moddata);
			*bp++ = moddata;
			if(bp >= limit){
				comout(XOFF);
				bp = tmp;
				while((*bp = cominw(1)) != TIMEOUT){
					putcnb(*bp);
					bp++;
				}
				*bp = '\0';
				write(fd, buffer, BUFFSIZ);
				strcpy(buffer, tmp);
				bp = buffer + (bp - tmp);
				comout(XON);
			}
		}
	}
	*bp = CTRLZ;
	write(fd,buffer,bp - buffer);
	close(fd);
}


sendascii(file)
char	*file;
{
	int	fd,size;
	char	moddata;
	char	*cp,*limit;

	fd = open(file, 0);
	if(fd == ERROR){
		printmsg("Cannot open %s.", file);
		return;
	}
	
	while((size = read(fd,buffer,BUFFSIZ)) > 0){
		for(cp = buffer, limit = buffer + size; cp < limit; cp++){
			comout(*cp);
			if(comin() == XOFF){
				do{
					moddata = cominw(10);
				}while (moddata != XON && moddata != TIMEOUT);
			}
		}
	}
	close(fd);
}

/*
Connect uses a file (systems.txt) with one line per remote system:
<name> <phone-number> [<delimiter><host-prompt><delimter><response>etc.]

asys 555-1212 'login:'mylogin'password:'mypassword'

Note that prompt/response string is optional.  Normal use is to login
to the remote system.  The as many prompt/response pairs may be used as
needed.
*/

connect(namenum)
char	*namenum;
{
	char	name[32], number[32], logseq[80];
	char	buffer[80];
	char	*cp, *prompt;
	char	delimiter;
	int	moddata;
	int	attempts;
	FILE	*fd;

	disconnect();
	if(isdigit(namenum[0])){
		dial(namenum);
		return;
	}
	if((fd = fopen(SYSFILE,"r")) == NULL){
		printmsg("Unable ot open %s.",SYSFILE);
		return;
	}
	do{
		name[0] = '\0';
		logseq[0] = '\0';
		logseq[1] = '\0';	/* prompt starts here */
		number[0] = '\0';
		if(fgets(buffer,80,fd) == NULL){
			fclose(fd);
			printmsg("Unable to find %s.",namenum);
			return;
		}
		sscanf(buffer,"%s %s %s\n",name,number,logseq);
	}while(strcmp(namenum,name) != 0);
	dial(number);
	if(!connected){
		fclose(fd);
		return;
	}
	attempts = 0;
	delimiter = logseq[0];
	prompt = logseq + 1;
	cp = prompt;
	comout('\n');
	while(*cp != '\0' && attempts < MAXLOGIN){
		cp = prompt;
		moddata = 0x20;
		while(*cp != '\0' && *cp != delimiter && moddata != TIMEOUT){
			moddata = cominw(3);
			if (*cp == (moddata & 0xff))
				cp++;
			else
				cp = prompt;
		}
		if(moddata == TIMEOUT){
			comout('\n');
			attempts++;
			prompt = logseq + 1;
		}else if(*cp != '\0'){
			cp++;
			for(;*cp != delimiter && *cp != '\0';cp++){
				comout(*cp);
			}
			comout('\n');
			prompt = cp;
			if(*cp == delimiter)
				prompt++;
		}
	}
	if(*cp != '\0'){
		disconnect();
	}
	fclose(fd);
	
}

dial(num)
char	*num;
{
	long	time;
	char	*cp;
	connected = TRUE;
	for(cp="AT DT "; *cp != '\0'; cp++){
		comout(*cp);
	}
	for(cp=num; *cp != '\0'; cp++){
		comout(*cp);
	}
	comout('\015');
	for(time = COMINPS * CONTM; comin() != '1' && time > 0; time--);
	if(!time){
		disconnect();
		printmsg("Failed dial.");
	}
}



disconnect()
{
	long	time;
	char	*cp;

	if(!connected)
		return;
	for(time =  LPS; time > 0; time--);	/* wait 2 seconds */
	for(cp = "+++"; *cp != '\0'; cp++){
		comout(*cp);
	}
	for(time =  LPS; time > 0; time--);
	purgeline();
	for(cp = "ATH\015"; *cp != '\0'; cp++){
		comout(*cp);
	}
	for(time = COMINPS * CONTM; comin() != '0' && time > 0; time--);
	for(time =  LPS; time > 0; time--);	/* let the line settle */
	connected = FALSE;
}



cmd(c)
char	c;
{
	char	cbuf[16];

	switch(c){
		case ALTX:
			if(trace){
				printmsg("trace off");
				trace = FALSE;
			}else{
				printmsg("trace on");
				trace = TRUE;
			}
			break;
		case ALTB:
			printmsg("baud rate (300,1200,2400,4800,9600):%d ",baud);
			scanf("%d",&baud);
			printmsg("bits per character:%d ",bits);
			scanf("%d",&bits);
			printmsg("stop bits:%d ",sbits);
			scanf("%d",&sbits);
			printmsg("parity (Even|Odd|None):%c ",parity);
			scanf("%s",cbuf);
			switch (cbuf[0] & 0x5f){
			case 'E':
				parity = 'E';
				break;
			case 'O':
				parity = 'O';
				break;
			case 'N':
				parity = 'N';
				break;
			}
			serialset(baud,bits,sbits,parity);
			break;
		case ALTD:
			printmsg("disconnect in progress");
			disconnect(cbuf);
			break;
		case ALTC:
			printmsg("connect (name|number):");
			scanf("%s",cbuf);
			connect(cbuf);
			break;
		case ALTR:
			printmsg("File:");
			scanf("%s",cbuf);
			switch(proto){
			case 'a':
				recvascii(cbuf);
				break;
			case 'x':
				recvxmodem(cbuf);
				break;
			}
			putcnb("\007");
			break;
		case ALTS:
			printmsg("File:");
			scanf("%s",cbuf);
			switch(proto){
			case 'a':
				sendascii(cbuf);
				break;
			case 'x':
				sendxmodem(cbuf);
				break;
			}
			putcnb("\007");
			break;
		case ALTP:
			printmsg("protocol (Ascii|Xmodem):[%c]",proto);
			while (!kbdrdy());
			switch(kbdin() & 0x5f){
			case 'A':
				proto = 'a';
				break;
			case 'X':
				proto = 'x';
				break;
			default:
				break;
			}
			putcnb(proto);			
			break;
	}
}


char	cmdmsg[] = 
"commands: <Alt><Connect|Disconnect|Send|Recv|Protocol|Baudrate|Quit>";

main()
{
	char	kbdata, moddata;
	long	time;
	char	*cp;
		
	serialset(baud,bits,sbits,parity);
	for(time =  LPS; time > 0; time--);	/* wait 2 seconds */
	for(cp = "+++"; *cp != '\0'; cp++){
		comout(*cp);
	}
	for(time =  LPS; time > 0; time--);	/* wait 2 seconds */
	printmsg("%s",cmdmsg);
	purgeline();		/* remove echo */
	kbdata = 0;
	while(1){
		if(kbdrdy()){
			kbdata = kbdin();
			if(kbdata == '\0'){
				kbdata = kbdin();
				if(kbdata == ALTQ)
					break;
				else
					cmd(kbdata);
			}
			else{
				comout(kbdata);
			}
		}
		if(cominrdy()){
			moddata = comin();
			putcnb(moddata);
		}
	}
	disconnect();
}

/* MESSY! use ansi.sys where available */
printmsg(fmt,arg1,arg2,arg3)
char	*fmt;
int	arg1,arg2,arg3;
{
	char	buf[160];
	char	*cp;
	int	n;

	if (trace) printf("\n");
	putcnb('\015');
	for(n = 79; n > 0; n--){
		putcnb(' ');
	}
	putcnb('\015');
	sprintf(buf,fmt,arg1,arg2,arg3);
	for(cp = buf;*cp!= '\0'; cp++){
		putcnb(*cp);
	}
}

/* I/O trace display */
/* format of report: >15(nak) <01(soh) <e3 <f0 <61(a) <0a(nl) >06(ack) */

char ascii[] = "\
nul soh stx etx eot enq ack bel \
bs  ht  nl  vt  np  cr  so  si  \
dle dc1 dc2 dc3 dc4 nak syn etb \
can em  sub esc fs  gs  rs  us  \
sp  !   \"   #   $   %   &   '   \
(   )   *   +   ,   -   .   /   \
0   1   2   3   4   5   6   7   \
8   9   :   ;   <   =   >   ?   \
@   A   B   C   D   E   F   G   \
H   I   J   K   L   M   N   O   \
P   Q   R   S   T   U   V   W   \
X   Y   Z   [   \\   ]   ^   _   \
`   a   b   c   d   e   f   g   \
h   i   j   k   l   m   n   o   \
p   q   r   s   t   u   v   w   \
x   y   z   {   |   }   ~   del ";


iotrace(c,flag)
char	c;
{
	char	*cp, *xcp;
	char	buffer[16];
		
	sprintf(buffer,"%c%x",flag,(c & 0xff));
	cp = buffer + strlen(buffer);
	if(c < 0x80 && c > 0){
		*cp++ = '(';
		for(xcp = &ascii[c << 2]; *xcp != ' '; xcp++){
			*cp++ = *xcp;
		}
		*cp++ = ')';
	}
	*cp++ = ' ';
	*cp++ = '\0';
	for(cp = buffer; *cp != '\0'; cp++){
		putcnb(*cp);
	}
}



kbdrdy()
{
	struct	reg	sreg,dreg;
	sreg.r_ax = 0x0b00;
	intcall(&sreg,&dreg,0x21);
	return(dreg.r_ax & 0xff);
}

char	kbdin()
{
	struct	reg	sreg,dreg;
	sreg.r_ax = 0x0700;
	intcall(&sreg,&dreg, 0x21);
	return(dreg.r_ax & 0xff);
}



/*********************************************************/
/* device specific code 				*/
/********************************************************/

#define MR1	0
#define MR2	0
#define SR	2
#define CSR	2
#define CR	4
#define HR	6

#define STRTCTR	0x1c
#define	SPB	0x1c
#define	STPCTR	0x1e
#define	IP	0x1a
#define	OPCR	0x1a
#define	IPCR	0x8
#define	ACR	0x8
#define	ISR	0xa
#define	IMR	0xa
#define	CTU	0xc
#define	CTL	0xe


static	int	duart = 0x80;	/* base of duart channel */


/* the following code is written for the 1681 DUART */

serialset(baud,bits,sbits,parity)
int	baud,bits,sbits;
char	parity;
{

	int	mr1,mr2,csr;
	mr1 = 0x80;
	mr2 = 0x30;

	switch(baud){
	case 300:
		csr = 0x44;
		break;
	case 1200:
		csr = 0x66;
		break;
	case 2400:
		csr = 0x88;
		break;
	case 4800:
		csr = 0x99;
		break;
	case 9600:
		csr = 0xbb;
		break;
	}
	switch(bits){
	case 7:
		mr1 |= 2;
		break;
	case 8:
		mr1 |= 3;
		break;
	}
	switch(sbits){
	case 1:
		mr2 |= 7;
		break;
	case 2:
		mr2 |= 0xf;
		break;
	}
	switch(parity){
	case 'E':
		break;
	case 'O':
		mr1 |= 4;
		break;
	case 'N':
		mr1 |= 0x10;
		break;
	}
	outb(duart + IMR, 0x0);
	outb(duart + CR, 0x15);
	outb(duart + CSR, csr);
	outb(duart + MR1, mr1);
	outb(duart + MR2, mr2);
}

purgeline()
{
	while(cominw(1) != TIMEOUT);
}


cominw(seconds)			/* int to differentiate TIMEOUT */
unsigned seconds;
{
	int	c;
	long	idle;

	idle = seconds * COMINPS;
	while(!cominrdy()&& idle)
		--idle;
	if(!idle)
		return(TIMEOUT);
	c = inb(duart + HR);
	c &= 0xff;	/* ensure 0xff doesn't appear as 0xffff */
	if (trace) iotrace(c,'>');
	return(c);
}


comin()			/* int so that TIMEOUT is different from data */
{
	int	c;

	if(inb(duart+SR) & 1){
		c = inb(duart + HR);
		c &= 0xff;	/* ensure 0xff doesn't appear as 0xffff */
		if (trace) iotrace(c,'>');
	}
	else{
		c = '\0';
	}
	return(c);
}

comout(c)
char c;
{
	int	time;

	if (trace) iotrace(c,'<');
	time = COMINPS/10;
	while(!comoutrdy() && time--);
	outb(duart + HR,c);
}

comoutrdy()
{
	return(inb(duart+SR) & 0x4);
}


cominrdy()
{
	return(inb(duart+SR) & 1);
}


comcts()
{
	return TRUE;
}


---------------------------------------------------------------------------
Lee Pingel		decvax!wang!wang7!wangvs!pingel
MS 1479    		Strictly my own biases and opinions.
Wang Laboratories Inc.
1 Industrial Ave	What if we also had to import food???
Lowell, Ma 01850