/* * $Id$ * * Z M . C * Copyright 1994 Omen Technology Inc All Rights Reserved * ZMODEM protocol primitives * * Entry point Functions: * zsbhdr(type, hdr) send binary header * zshhdr(type, hdr) send hex header * zgethdr(hdr) receive header - binary or hex * zsdata(buf, len, frameend) send data * zrdata(buf, len) receive data * stohdr(pos) store position data in Txhdr * long rclhdr(hdr) recover position offset from header * * * This version implements numerous enhancements including ZMODEM * Run Length Encoding and variable length headers. These * features were not funded by the original Telenet development * contract. * * This software may be freely used for educational (didactic * only) purposes. This software may also be freely used to * support file transfer operations to or from licensed Omen * Technology products. Use with other commercial or shareware * programs (Crosstalk, Procomm, etc.) REQUIRES REGISTRATION. * * Any programs which use part or all of this software must be * provided in source form with this notice intact except by * written permission from Omen Technology Incorporated. * * Use of this software for commercial or administrative purposes * except when exclusively limited to interfacing Omen Technology * products requires a per port license payment of $20.00 US per * port (less in quantity). Use of this code by inclusion, * decompilation, reverse engineering or any other means * constitutes agreement to these conditions and acceptance of * liability to license the materials and payment of reasonable * legal costs necessary to enforce this license agreement. * * * Omen Technology Inc * Post Office Box 4681 * Portland OR 97208 * * This code is made available in the hope it will be useful, * BUT WITHOUT ANY WARRANTY OF ANY KIND OR LIABILITY FOR ANY * DAMAGES OF ANY KIND. * */ static void zputhex(int); static void zsbh32(char*, int); static void zsda32(char*, int, int); static int zrdat32(char*,int); static int noxrd7(void); static int zrbhd32(char*); static int zrbhdr(char*); static int zrhhdr(char*); static int zgethex(void); static int zgeth1(void); static void garbitch(void); static inline void zsendline_s(const char *, int); #include "../config.h" #include "../lib/mbselib.h" #include "ttyio.h" #include "input.h" #include "zmmisc.h" /* * Original zm.c timing was in tenths of seconds, but our current ttyio driver * does timing in whole seconds. */ int Rxtimeout = 10; /* Seconds to wait for something */ char *txbuf=NULL; static int lastsent; /* Last char we sent */ static int Not8bit; /* Seven bits seen on header */ static char zsendline_tab[256]; extern unsigned Baudrate; extern int zmodem_requested; char *frametypes[] = { (char *)"EMPTY", /* -16 */ (char *)"Can't be (-15)", (char *)"Can't be (-14)", (char *)"Can't be (-13)", (char *)"Can't be (-12)", (char *)"Can't be (-11)", (char *)"Can't be (-10)", (char *)"Can't be (-9)", (char *)"HANGUP", /* -8 */ (char *)"Can't be (-7)", (char *)"Can't be (-6)", (char *)"Can't be (-5)", (char *)"EOFILE", /* -4 */ (char *)"Can't be (-3)", (char *)"TIMEOUT", /* -2 */ (char *)"ERROR", /* -1 */ (char *)"ZRQINIT", (char *)"ZRINIT", (char *)"ZSINIT", (char *)"ZACK", (char *)"ZFILE", (char *)"ZSKIP", (char *)"ZNAK", (char *)"ZABORT", (char *)"ZFIN", (char *)"ZRPOS", (char *)"ZDATA", (char *)"ZEOF", (char *)"ZFERR", (char *)"ZCRC", (char *)"ZCHALLENGE", (char *)"ZCOMPL", (char *)"ZCAN", (char *)"ZFREECNT", (char *)"ZCOMMAND", (char *)"ZSTDERR", (char *)"xxxxx" #define FRTYPES 22 /* Total number of frame types in this array */ /* not including psuedo negative entries */ }; /****************************************************************************/ /* * Send ZMODEM binary header hdr of type type */ void zsbhdr(int type, char *shdr) { register int n; register unsigned short crc; Syslog('z', "zsbhdr: %s %lx", frametypes[type+FTOFFSET], rclhdr(shdr)); if (type == ZDATA) for (n = Znulls; --n >=0; ) PUTCHAR(0); PUTCHAR(ZPAD); PUTCHAR(ZDLE); if ((Crc32t = Txfcs32)) zsbh32(shdr, type); else { PUTCHAR(ZBIN); zsendline(type); crc = updcrc16(type, 0); for (n=4; --n >= 0; ++shdr) { zsendline(*shdr); crc = updcrc16((0377& *shdr), crc); } crc = updcrc16(0,updcrc16(0,crc)); zsendline(((int)(crc>>8))); zsendline(crc); } if (type != ZDATA) fflush(stdout); } /* * Send ZMODEM binary header hdr of type type */ void zsbh32(char *shdr, int type) { register int n; register unsigned int crc; PUTCHAR(ZBIN32); zsendline(type); crc = 0xFFFFFFFFL; crc = updcrc32(type, crc); for (n=4; --n >= 0; ++shdr) { crc = updcrc32((0377 & *shdr), crc); zsendline(*shdr); } crc = ~crc; for (n=4; --n >= 0;) { zsendline((int)crc); crc >>= 8; } } /* * Send ZMODEM HEX header hdr of type type */ void zshhdr(int type, register char *shdr) { register int n; register unsigned short crc; Syslog('z', "zshhdr: %s %lx", frametypes[type+FTOFFSET], rclhdr(shdr)); PUTCHAR(ZPAD); PUTCHAR(ZPAD); PUTCHAR(ZDLE); PUTCHAR(ZHEX); zputhex(type & 0x7f); Crc32t = 0; crc = updcrc16((type & 0x7f), 0); for (n=4; --n >= 0; ++shdr) { zputhex(*shdr); crc = updcrc16((0377 & *shdr), crc); } crc = updcrc16(0,updcrc16(0,crc)); zputhex(((int)(crc>>8))); zputhex(crc); /* * Make it printable on remote machine */ PUTCHAR(015); PUTCHAR(0212); /* * Uncork the remote in case a fake XOFF has stopped data flow */ if (type != ZFIN && type != ZACK) PUTCHAR(021); fflush(stdout); } /* * Send binary array buf of length length, with ending ZDLE sequence frameend */ char *Zendnames[] = {(char *)"ZCRCE",(char *)"ZCRCG",(char *)"ZCRCQ",(char *)"ZCRCW"}; void zsdata(register char *buf, int length, int frameend) { register unsigned short crc; Syslog('z', "zsdata: %d %s", length, Zendnames[(frameend-ZCRCE)&3]); if (Crc32t) zsda32(buf, length, frameend); else { crc = 0; for (;--length >= 0; ++buf) { zsendline(*buf); crc = updcrc16((0377 & *buf), crc); } PUTCHAR(ZDLE); PUTCHAR(frameend); crc = updcrc16(frameend, crc); crc = updcrc16(0,updcrc16(0,crc)); zsendline(((int)(crc>>8))); zsendline(crc); } if (frameend == ZCRCW) { PUTCHAR(XON); fflush(stdout); } } void zsda32(register char *buf, int length, int frameend) { register int c; register unsigned int crc; crc = 0xFFFFFFFFL; zsendline_s(buf, length); for (;--length >= 0; ++buf) { c = *buf & 0377; crc = updcrc32(c, crc); } PUTCHAR(ZDLE); PUTCHAR(frameend); crc = updcrc32(frameend, crc); crc = ~crc; for (c=4; --c >= 0;) { zsendline((int)crc); crc >>= 8; } if (frameend == ZCRCW) { PUTCHAR(XON); fflush(stdout); } } /* * Receive array buf of max length with ending ZDLE sequence * and CRC. Returns the ending character or error code. * NB: On errors may store length+1 bytes! */ int zrdata(register char *buf, int length) { register int c; register unsigned short crc; register char *end; register int d; if (Crc32r) return zrdat32(buf, length); crc = Rxcount = 0; end = buf + length; while (buf <= end) { if ((c = zdlread()) & ~0377) { crcfoo: switch (c) { case GOTCRCE: case GOTCRCG: case GOTCRCQ: case GOTCRCW: crc = updcrc16((((d=c))&0377), crc); if ((c = zdlread()) & ~0377) goto crcfoo; crc = updcrc16(c, crc); if ((c = zdlread()) & ~0377) goto crcfoo; crc = updcrc16(c, crc); if (crc & 0xFFFF) { Syslog('+', "Zmodem zrdata: Bad CRC"); return TERROR; } Rxcount = length - (end - buf); Syslog('z', "zrdata: %d %s", Rxcount, Zendnames[(d-GOTCRCE)&3]); return d; case GOTCAN: Syslog('+', "Zmodem: Sender Canceled"); return ZCAN; case TIMEOUT: Syslog('+', "Zmodem: TIMEOUT receiving data"); return c; case HANGUP: Syslog('+', "Zmodem: Carrier lost while receiving"); return c; default: garbitch(); return c; } } *buf++ = c; crc = updcrc16(c, crc); } Syslog('+', "Zmodem: Data subpacket too long"); return TERROR; } int zrdat32(register char *buf, int length) { register int c; register unsigned int crc; register char *end; register int d; crc = 0xFFFFFFFFL; Rxcount = 0; end = buf + length; while (buf <= end) { if ((c = zdlread()) & ~0377) { crcfoo: switch (c) { case GOTCRCE: case GOTCRCG: case GOTCRCQ: case GOTCRCW: d = c; c &= 0377; crc = updcrc32(c, crc); if ((c = zdlread()) & ~0377) goto crcfoo; crc = updcrc32(c, crc); if ((c = zdlread()) & ~0377) goto crcfoo; crc = updcrc32(c, crc); if ((c = zdlread()) & ~0377) goto crcfoo; crc = updcrc32(c, crc); if ((c = zdlread()) & ~0377) goto crcfoo; crc = updcrc32(c, crc); if (crc != 0xDEBB20E3) { Syslog('+', "Zmodem zrdat32: Bad CRC"); return TERROR; } Rxcount = length - (end - buf); Syslog('z', "zrdat32: %d %s", Rxcount, Zendnames[(d-GOTCRCE)&3]); return d; case GOTCAN: Syslog('+', "Zmodem: Sender Canceled"); return ZCAN; case TIMEOUT: Syslog('+', "Zmodem: TIMEOUT"); return c; case HANGUP: Syslog('+', "Zmodem: Carrier lost while receiving"); return c; default: garbitch(); return c; } } *buf++ = c; crc = updcrc32(c, crc); } Syslog('+', "Zmodem: Data subpacket too long"); return TERROR; } void garbitch(void) { Syslog('+', "Zmodem: Garbled data subpacket"); } /* * Read a ZMODEM header to hdr, either binary or hex. * * Set Rxhlen to size of header (default 4) (valid if good hdr) * On success, set Zmodem to 1, set Rxpos and return type of header. * Otherwise return negative on error. * Return ERROR instantly if ZCRCW sequence, for fast error recovery. */ int zgethdr(char *shdr) { register int c, n, cancount; int Zrwindow = 1400; n = Zrwindow + Baudrate; Rxframeind = Rxtype = 0; startover: cancount = 5; again: /* * Return immediate ERROR if ZCRCW sequence seen */ if (((c = GETCHAR(Rxtimeout)) < 0) && (c != TIMEOUT)) goto fifi; else { switch(c) { case 021: case 0221: goto again; case HANGUP: case TIMEOUT: goto fifi; case CAN: gotcan: Syslog('z', "zgethdr: got CAN"); if (--cancount <= 0) { c = ZCAN; goto fifi; } switch (c = GETCHAR(Rxtimeout)) { case TIMEOUT: goto again; case ZCRCW: switch (GETCHAR(Rxtimeout)) { case TIMEOUT: c = TERROR; goto fifi; case HANGUP: goto fifi; default: goto agn2; } case HANGUP: goto fifi; default: break; case CAN: if (--cancount <= 0) { c = ZCAN; goto fifi; } goto again; } /* **** FALL THRU TO **** */ default: agn2: #define GCOUNT (-4) if ( --n == 0) { c = GCOUNT; Syslog('z', "zgethdr: garbage count exceeded"); goto fifi; } goto startover; case ZPAD|0200: /* This is what we want. */ Not8bit = c; case ZPAD: /* This is what we want. */ break; } } cancount = 5; splat: switch (c = noxrd7()) { case ZPAD: goto splat; case HANGUP: case TIMEOUT: goto fifi; default: goto agn2; case ZDLE: /* This is what we want. */ break; } Rxframeind = c = noxrd7(); switch (c) { case ZBIN32: Crc32r = 1; c = zrbhd32(shdr); break; case HANGUP: case TIMEOUT: goto fifi; case ZBIN: Crc32r = 0; c = zrbhdr(shdr); break; case ZHEX: Crc32r = 0; c = zrhhdr(shdr); break; case CAN: goto gotcan; default: goto agn2; } for (n = 4; ++n < ZMAXHLEN; ) /* Clear unused hdr bytes */ shdr[n] = 0; Rxpos = shdr[ZP3] & 0377; Rxpos = (Rxpos<<8) + (shdr[ZP2] & 0377); Rxpos = (Rxpos<<8) + (shdr[ZP1] & 0377); Rxpos = (Rxpos<<8) + (shdr[ZP0] & 0377); fifi: switch (c) { case GOTCAN: c = ZCAN; /* **** FALL THRU TO **** */ case ZNAK: case ZCAN: case TERROR: case TIMEOUT: case HANGUP: Syslog('+', "Zmodem: Got %s", frametypes[c+FTOFFSET]); /* **** FALL THRU TO **** */ default: if (c >= -FTOFFSET && c <= FRTYPES) Syslog('z', "zgethdr: %c %s %lx", Rxframeind, frametypes[c+FTOFFSET], Rxpos); else Syslog('z', "zgethdr: %d %d %lx", Rxframeind, c, Rxpos); } return c; } /* * Receive a binary style header (type and position) */ int zrbhdr(register char *shdr) { register int c, n; register unsigned short crc; if ((c = zdlread()) & ~0377) return c; Rxtype = c; crc = updcrc16(c, 0); for (n=4; --n >= 0; ++shdr) { if ((c = zdlread()) & ~0377) return c; crc = updcrc16(c, crc); *shdr = c; } if ((c = zdlread()) & ~0377) return c; crc = updcrc16(c, crc); if ((c = zdlread()) & ~0377) return c; crc = updcrc16(c, crc); if (crc & 0xFFFF) { Syslog('+', "Zmodem zrbhdr: Bad CRC"); return TERROR; } zmodem_requested = TRUE; protocol = ZM_ZMODEM; return Rxtype; } /* * Receive a binary style header (type and position) with 32 bit FCS */ int zrbhd32(register char *shdr) { register int c, n; register unsigned int crc; if ((c = zdlread()) & ~0377) return c; Rxtype = c; crc = 0xFFFFFFFFL; crc = updcrc32(c, crc); for (n=4; --n >= 0; ++shdr) { if ((c = zdlread()) & ~0377) return c; crc = updcrc32(c, crc); *shdr = c; } for (n=4; --n >= 0;) { if ((c = zdlread()) & ~0377) return c; crc = updcrc32(c, crc); } if (crc != 0xDEBB20E3) { Syslog('+', "Zmodem zrbhd32: Bad CRC"); return TERROR; } zmodem_requested = TRUE; protocol = ZM_ZMODEM; return Rxtype; } /* * Receive a hex style header (type and position) */ int zrhhdr(char *shdr) { register int c; register unsigned short crc; register int n; if ((c = zgethex()) < 0) return c; Rxtype = c; crc = updcrc16(c, 0); for (n=4; --n >= 0; ++shdr) { if ((c = zgethex()) < 0) return c; crc = updcrc16(c, crc); *shdr = c; } if ((c = zgethex()) < 0) return c; crc = updcrc16(c, crc); if ((c = zgethex()) < 0) return c; crc = updcrc16(c, crc); if (crc & 0xFFFF) { Syslog('+', "Zmodem zrhhdr: Bad CRC"); return TERROR; } switch (c = GETCHAR(Rxtimeout)) { case 0215: Not8bit = c; /* **** FALL THRU TO **** */ case 015: /* Throw away possible cr/lf */ switch (c = GETCHAR(Rxtimeout)) { case 012: Not8bit |= c; } } if (c < 0) return c; zmodem_requested = TRUE; protocol = ZM_ZMODEM; return Rxtype; } /* * Send a byte as two hex digits */ void zputhex(register int c) { static char digits[] = "0123456789abcdef"; PUTCHAR(digits[(c&0xF0)>>4]); PUTCHAR(digits[(c)&0xF]); } /* * Send character c with ZMODEM escape sequence encoding. * Escape XON, XOFF. Escape CR following @ (Telenet net escape) */ void zsendline(int c) { switch(zsendline_tab[(unsigned) (c&=0377)]) { case 0: PUTCHAR(lastsent = c); break; case 1: PUTCHAR(ZDLE); c ^= 0100; PUTCHAR(lastsent = c); break; case 2: if ((lastsent & 0177) != '@') { PUTCHAR(lastsent = c); } else { PUTCHAR(ZDLE); c ^= 0100; PUTCHAR(lastsent = c); } break; } } static inline void zsendline_s(const char *s, int count) { const char *end = s + count; while (s != end) { int last_esc = 0; const char *t = s; while (t != end) { last_esc = zsendline_tab[(unsigned) ((*t) & 0377)]; if (last_esc) break; t++; } if (t != s) { PUT((char *)s, (t-s)); lastsent = t[-1]; s = t; } if (last_esc) { int c = *s; switch (last_esc) { case 0: PUTCHAR(lastsent = c); break; case 1: PUTCHAR(ZDLE); c ^= 0100; PUTCHAR(lastsent = c); break; case 2: if ((lastsent & 0177) != '@') { PUTCHAR(lastsent = c); } else { PUTCHAR(ZDLE); c ^= 0100; PUTCHAR(lastsent = c); } break; } s++; } } } void zsendline_init(void) { int i; Syslog('z', "zsendline_init()"); for (i = 0; i < 256; i++) { if (i & 0140) zsendline_tab[i]=0; else { switch(i) { case ZDLE: case XOFF: /* ^Q */ case XON: /* ^S */ case (XOFF | 0200): case (XON | 0200): zsendline_tab[i]=1; break; case 020: /* ^P */ case 0220: zsendline_tab[i]=1; break; case 015: case 0215: if (Zctlesc) zsendline_tab[i]=1; else zsendline_tab[i]=2; break; default: if (Zctlesc) zsendline_tab[i]=1; else zsendline_tab[i]=0; } } } } /* Decode two lower case hex digits into an 8 bit byte value */ int zgethex(void) { register int c; c = zgeth1(); return c; } int zgeth1(void) { register int c, n; if ((c = noxrd7()) < 0) return c; n = c - '0'; if (n > 9) n -= ('a' - ':'); if (n & ~0xF) return TERROR; if ((c = noxrd7()) < 0) return c; c -= '0'; if (c > 9) c -= ('a' - ':'); if (c & ~0xF) return TERROR; c += (n<<4); return c; } /* * Read a byte, checking for ZMODEM escape encoding * including CAN*5 which represents a quick abort */ int zdlread(void) { register int c; again: /* Quick check for non control characters */ if ((c = GETCHAR(Rxtimeout)) & 0140) return c; switch (c) { case ZDLE: break; case XON: case XON|0200: case XOFF: case XOFF|0200: goto again; default: if (Zctlesc && !(c & 0140)) { goto again; } return c; } again2: if ((c = GETCHAR(Rxtimeout)) < 0) return c; if (c == CAN && (c = GETCHAR(Rxtimeout)) < 0) return c; if (c == CAN && (c = GETCHAR(Rxtimeout)) < 0) return c; if (c == CAN && (c = GETCHAR(Rxtimeout)) < 0) return c; switch (c) { case CAN: return GOTCAN; case ZCRCE: case ZCRCG: case ZCRCQ: case ZCRCW: return (c | GOTOR); case ZRUB0: return 0177; case ZRUB1: return 0377; case XON: case XON|0200: case XOFF: case XOFF|0200: goto again2; default: if (Zctlesc && ! (c & 0140)) { goto again2; } if ((c & 0140) == 0100) return (c ^ 0100); break; } Syslog('+', "Zmodem: Bad escape sequence 0x%x", c); return TERROR; } /* * Read a character from the modem line with timeout. * Eat parity, XON and XOFF characters. */ int noxrd7(void) { register int c; for (;;) { if ((c = GETCHAR(Rxtimeout)) < 0) return c; switch (c &= 0177) { case XON: case XOFF: continue; default: if (Zctlesc && !(c & 0140)) continue; case '\r': case '\n': case ZDLE: return c; } } } /* * Store long integer pos in Txhdr */ void stohdr(int pos) { Txhdr[ZP0] = pos; Txhdr[ZP1] = pos>>8; Txhdr[ZP2] = pos>>16; Txhdr[ZP3] = pos>>24; } /* * Recover a long integer from a header */ int rclhdr(register char *shdr) { register int l; l = (shdr[ZP3] & 0377); l = (l << 8) | (shdr[ZP2] & 0377); l = (l << 8) | (shdr[ZP1] & 0377); l = (l << 8) | (shdr[ZP0] & 0377); return l; } char *protname(void) { switch (protocol) { case ZM_XMODEM: return (char *)"Xmodem"; case ZM_YMODEM: return (char *)"Ymodem"; default: return (char *)"Zmodem"; } } /* * Purge input, maximum wait time in n * 10 mSec. */ void purgeline(int howlong) { int c, count = 0; unsigned char ch = 0; do { c = Waitchar(&ch, howlong); count++; } while (c == 1); if (count) Syslog('z', "purgeline: purged %d characters", count); } /* * send cancel string to get the other end to shut up */ void canit(int fd) { static char canistr[] = { 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0 }; Syslog('z', "%s: send canit to fd %d", protname(), fd); write(fd, canistr, strlen(canistr)); if (fd == 0) write(1, canistr, strlen(canistr)); } /* End of zmmisc.c */