/* * $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(int,char*,int,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); #include "../config.h" #include "../lib/libs.h" #include "../lib/structs.h" #include "../lib/common.h" #include "../lib/nodelist.h" #include "../lib/clcomm.h" #include "ttyio.h" #include "session.h" #include "zmodem.h" /* Original zm.c timing was in tenths of seconds, but our current ttyio driver does timing in whole seconds. */ static int Rxtimeout = 10; /* Seconds to wait for something */ int Zctlesc; /* Globals used by ZMODEM functions */ int Rxframeind; /* ZBIN ZBIN32, or ZHEX type of frame */ int Rxtype; /* Type of header received */ int Rxhlen; /* Length of header received */ int Rxcount; /* Count of data bytes received */ char Rxhdr[ZMAXHLEN]; /* Received header */ char Txhdr[ZMAXHLEN]; /* Transmitted header */ long Rxpos; /* Received file position */ long Txpos; /* Transmitted file position */ int Txfcs32; /* TRUE means send binary frames with 32 bit FCS */ int Crc32t; /* Controls 32 bit CRC being sent */ /* 1 == CRC32, 2 == CRC32 + RLE */ int Crc32r; /* Indicates/controls 32 bit CRC being received */ /* 0 == CRC16, 1 == CRC32, 2 == CRC32 + RLE */ int Usevhdrs; /* Use variable length headers */ int Znulls; /* Number of nulls to send at beginning of ZDATA hdr */ char Attn[ZATTNLEN+1]; /* Attention string rx sends to tx on err */ char *Altcan; /* Alternate canit string */ char *txbuf=NULL; char *rxbuf=NULL; static int lastsent; /* Last char we sent */ static int Not8bit; /* Seven bits seen on header */ 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 */ }; /***** Hack by mj ***********************************************************/ /* * Buffer for outgoing frames. Sending them with single character write()'s * is a waste of processor time and causes severe performance degradation * on TCP and ISDN connections. */ #define FRAME_BUFFER_SIZE 16384 static char *frame_buffer=NULL; static int frame_length = 0; #define BUFFER_CLEAR() do { frame_length=0; } while(0) #define BUFFER_BYTE(c) do { frame_buffer[frame_length++]=(c); } while(0) #define BUFFER_FLUSH() do { PUT(frame_buffer, frame_length); \ frame_length=0; } while(0); /****************************************************************************/ void get_frame_buffer(void) { if (frame_buffer == NULL) frame_buffer = malloc(FRAME_BUFFER_SIZE); } void free_frame_buffer(void) { if (frame_buffer) free(frame_buffer); frame_buffer = NULL; } /* * Send ZMODEM binary header hdr of type type */ void zsbhdr(int len, int type, register char *shdr) { register int n; register unsigned short crc; Syslog('z', "zsbhdr: %c %d %s %lx", Usevhdrs?'v':'f', len, frametypes[type+FTOFFSET], rclhdr(shdr)); BUFFER_CLEAR(); if (type == ZDATA) for (n = Znulls; --n >=0; ) BUFFER_BYTE(0); BUFFER_BYTE(ZPAD); BUFFER_BYTE(ZDLE); switch (Crc32t=Txfcs32) { case 2: zsbh32(len, shdr, type, Usevhdrs?ZVBINR32:ZBINR32); BUFFER_FLUSH(); break; case 1: zsbh32(len, shdr, type, Usevhdrs?ZVBIN32:ZBIN32); break; default: if (Usevhdrs) { BUFFER_BYTE(ZVBIN); zsendline(len); } else BUFFER_BYTE(ZBIN); zsendline(type); crc = updcrc16(type, 0); for (n=len; --n >= 0; ++shdr) { zsendline(*shdr); crc = updcrc16((0377& *shdr), crc); } crc = updcrc16(0,updcrc16(0,crc)); zsendline(((int)(crc>>8))); zsendline(crc); } BUFFER_FLUSH(); } /* * Send ZMODEM binary header hdr of type type */ void zsbh32(int len, register char *shdr, int type, int flavour) { register int n; register unsigned long crc; BUFFER_BYTE(flavour); if (Usevhdrs) zsendline(len); zsendline(type); crc = 0xFFFFFFFFL; crc = updcrc32(type, crc); for (n=len; --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 len, int type, register char *shdr) { register int n; register unsigned short crc; Syslog('z', "zshhdr: %c %d %s %ld", Usevhdrs?'v':'f', len, frametypes[type+FTOFFSET], rclhdr(shdr)); BUFFER_CLEAR(); BUFFER_BYTE(ZPAD); BUFFER_BYTE(ZPAD); BUFFER_BYTE(ZDLE); if (Usevhdrs) { BUFFER_BYTE(ZVHEX); zputhex(len); } else BUFFER_BYTE(ZHEX); zputhex(type); Crc32t = 0; crc = updcrc16(type, 0); for (n=len; --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 */ BUFFER_BYTE(015); BUFFER_BYTE(0212); /* * Uncork the remote in case a fake XOFF has stopped data flow */ if (type != ZFIN && type != ZACK) BUFFER_BYTE(021); BUFFER_FLUSH(); } /* * 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]); BUFFER_CLEAR(); switch (Crc32t) { case 1: zsda32(buf, length, frameend); break; case 2: zsdar32(buf, length, frameend); break; default: crc = 0; for (;--length >= 0; ++buf) { zsendline(*buf); crc = updcrc16((0377 & *buf), crc); } BUFFER_BYTE(ZDLE); BUFFER_BYTE(frameend); crc = updcrc16(frameend, crc); crc = updcrc16(0,updcrc16(0,crc)); zsendline(((int)(crc>>8))); zsendline(crc); } if (frameend == ZCRCW) BUFFER_BYTE(XON); BUFFER_FLUSH(); } void zsda32(register char *buf, int length, int frameend) { register int c; register unsigned long crc; crc = 0xFFFFFFFFL; for (;--length >= 0; ++buf) { c = *buf & 0377; if (c & 0140) BUFFER_BYTE(lastsent = c); else zsendline(c); crc = updcrc32(c, crc); } BUFFER_BYTE(ZDLE); BUFFER_BYTE(frameend); crc = updcrc32(frameend, crc); crc = ~crc; for (c=4; --c >= 0;) { zsendline((int)crc); crc >>= 8; } } /* * 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; switch (Crc32r) { case 1: return zrdat32(buf, length); case 2: return zrdatr32(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 long 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; int Baudrate = 9600; 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: 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; 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; } Rxhlen = 4; /* Set default length */ Rxframeind = c = noxrd7(); switch (c) { case ZVBIN32: if ((Rxhlen = c = zdlread()) < 0) goto fifi; if (c > ZMAXHLEN) goto agn2; Crc32r = 1; c = zrbhd32(shdr); break; case ZBIN32: if (Usevhdrs) goto agn2; Crc32r = 1; c = zrbhd32(shdr); break; case ZVBINR32: if ((Rxhlen = c = zdlread()) < 0) goto fifi; if (c > ZMAXHLEN) goto agn2; Crc32r = 2; c = zrbhd32(shdr); break; case ZBINR32: if (Usevhdrs) goto agn2; Crc32r = 2; c = zrbhd32(shdr); break; case HANGUP: case TIMEOUT: goto fifi; case ZVBIN: if ((Rxhlen = c = zdlread()) < 0) goto fifi; if (c > ZMAXHLEN) goto agn2; Crc32r = 0; c = zrbhdr(shdr); break; case ZBIN: if (Usevhdrs) goto agn2; Crc32r = 0; c = zrbhdr(shdr); break; case ZVHEX: if ((Rxhlen = c = zgethex()) < 0) goto fifi; if (c > ZMAXHLEN) goto agn2; Crc32r = 0; c = zrhhdr(shdr); break; case ZHEX: if (Usevhdrs) goto agn2; Crc32r = 0; c = zrhhdr(shdr); break; case CAN: goto gotcan; default: goto agn2; } for (n = Rxhlen; ++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 %d %s %ld", Rxframeind, Rxhlen, frametypes[c+FTOFFSET], Rxpos); else Syslog('z', "zgethdr: %c %d %ld", Rxframeind, c, Rxpos); } /* Use variable length headers if we got one */ if (c >= 0 && c <= FRTYPES && Rxframeind & 040) Usevhdrs = 1; 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=Rxhlen; --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; } 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 long crc; if ((c = zdlread()) & ~0377) return c; Rxtype = c; crc = 0xFFFFFFFFL; crc = updcrc32(c, crc); for (n=Rxhlen; --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; } 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=Rxhlen; --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; return Rxtype; } /* * Send a byte as two hex digits */ void zputhex(register int c) { static char digits[] = "0123456789abcdef"; BUFFER_BYTE(digits[(c&0xF0)>>4]); BUFFER_BYTE(digits[(c)&0xF]); } /* * Send character c with ZMODEM escape sequence encoding. * Escape XON, XOFF. Escape CR following @ (Telenet net escape) */ void zsendline(int c) { /* Quick check for non control characters */ if (c & 0140) BUFFER_BYTE(lastsent = c); else { switch (c &= 0377) { case ZDLE: BUFFER_BYTE(ZDLE); BUFFER_BYTE (lastsent = (c ^= 0100)); break; case 015: case 0215: if (!Zctlesc && (lastsent & 0177) != '@') goto sendit; /* **** FALL THRU TO **** */ case 020: case 021: case 023: case 0220: case 0221: case 0223: BUFFER_BYTE(ZDLE); c ^= 0100; sendit: BUFFER_BYTE(lastsent = c); break; default: if (Zctlesc && ! (c & 0140)) { BUFFER_BYTE(ZDLE); c ^= 0100; } BUFFER_BYTE(lastsent = c); } } } /* 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 023: case 0223: case 021: case 0221: 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 023: case 0223: case 021: case 0221: 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(long pos) { Txhdr[ZP0] = pos; Txhdr[ZP1] = pos>>8; Txhdr[ZP2] = pos>>16; Txhdr[ZP3] = pos>>24; } /* * Recover a long integer from a header */ long rclhdr(register char *shdr) { register long 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; } /* End of zmmisc.c */