diff --git a/src/sbbs3/sexyz.c b/src/sbbs3/sexyz.c
index 6f2006058190185170042e3cdef787cfa9e29c8b..58cd098d324461ad51c00d46d3b6ffcdb28bfb4a 100644
--- a/src/sbbs3/sexyz.c
+++ b/src/sbbs3/sexyz.c
@@ -189,6 +189,9 @@ static char *chr(uchar ch)
 
 	if(mode&ZMODEM) {
 		switch(ch) {
+			case ZRQINIT:	return("ZRQINIT");
+			case ZRINIT:	return("ZRINIT");
+			case ZSINIT:	return("ZSINIT");
 			case ZACK:		return("ZACK");
 			case ZPAD:		return("ZPAD");
 			case ZDLE:		return("ZDLE");
@@ -622,6 +625,7 @@ void xmodem_progress(void* unused, unsigned block_num, ulong offset, ulong fsize
 void zmodem_progress(void* unused, ulong start_pos, ulong current_pos
 					 ,ulong fsize, time_t start)
 {
+	char		orig[128];
 	unsigned	cps;
 	long		l;
 	long		t;
@@ -633,13 +637,20 @@ void zmodem_progress(void* unused, ulong start_pos, ulong current_pos
 		t=now-start;
 		if(t<=0)
 			t=1;
+		if(start_pos>current_pos)
+			start_pos=0;
 		if((cps=(current_pos-start_pos)/t)==0)
 			cps=1;		/* cps so far */
 		l=fsize/cps;	/* total transfer est time */
 		l-=t;			/* now, it's est time left */
 		if(l<0) l=0;
-		fprintf(statfp,"\rKByte: %lu/%lu  "
+		if(start_pos)
+			sprintf(orig,"From: %lu  ", start_pos);
+		else
+			orig[0]=0;
+		fprintf(statfp,"\r%sKByte: %lu/%lu  "
 			"Time: %lu:%02lu/%lu:%02lu  CPS: %u  %lu%% "
+			,orig
 			,current_pos/1024
 			,fsize/1024
 			,t/60L
@@ -805,9 +816,10 @@ static int send_files(char** fname, uint fnames)
 	return(0);	/* success */
 }
 
-static int receive_files(char** fname, int fnames)
+static int receive_files(char** fname_list, int fnames)
 {
 	char	str[MAX_PATH+1];
+	char	fname[MAX_PATH+1];
 	int		i;
 	int		fnum=0;
 	uint	errors;
@@ -828,41 +840,91 @@ static int receive_files(char** fname, int fnames)
 
 	outbuf.highwater_mark=0;	/* don't delay ACK/NAK transmits */
 
+	/* Purge input buffer */
+	while((i=getcom(0))!=NOINP)
+		lprintf(LOG_WARNING,"Throwing out received: %s",chr((uchar)i));
+
 	while(!terminate && !cancelled) {
 		if(mode&XMODEM) {
-			SAFECOPY(str,fname[0]);
+			SAFECOPY(str,fname_list[0]);	/* we'll have at least one fname */
 			file_bytes=file_bytes_left=0x7fffffff;
 		}
 
-		else if(mode&YMODEM) {
-			lprintf(LOG_INFO,"Fetching Ymodem header block");
-			for(errors=0;errors<xm.max_errors;errors++) {
-				if(errors>(xm.max_errors/2) && mode&CRC && !(mode&GMODE))
-					mode&=~CRC;
-				xmodem_put_nak(&xm, /* expected_block: */ 0);
-				if(xmodem_get_block(&xm, block, /* expected_block: */ 0) == 0) {
-					putcom(ACK);
-					break; 
-				} 
-			}
-			if(errors==xm.max_errors) {
-				lprintf(LOG_ERR,"Error fetching Ymodem header block");
-				xmodem_cancel(&xm);
-				return(1); 
-			}
-			if(!block[0]) {
-				lprintf(LOG_INFO,"Received Ymodem termination block");
-				return(0); 
+		else {
+			if(mode&YMODEM) {
+				lprintf(LOG_INFO,"Fetching Ymodem header block");
+				for(errors=0;errors<xm.max_errors;errors++) {
+					if(errors>(xm.max_errors/2) && mode&CRC && !(mode&GMODE))
+						mode&=~CRC;
+					xmodem_put_nak(&xm, /* expected_block: */ 0);
+					if(xmodem_get_block(&xm, block, /* expected_block: */ 0) == 0) {
+						putcom(ACK);
+						break; 
+					} 
+				}
+				if(errors>=xm.max_errors) {
+					lprintf(LOG_ERR,"Error fetching Ymodem header block");
+					xmodem_cancel(&xm);
+					return(1); 
+				}
+				if(!block[0]) {
+					lprintf(LOG_INFO,"Received Ymodem termination block");
+					return(0); 
+				}
+				file_bytes=ftime=total_files=total_bytes=0;
+				i=sscanf(block+strlen(block)+1,"%ld %lo %lo %lo %d %ld"
+					,&file_bytes			/* file size (decimal) */
+					,&ftime 				/* file time (octal unix format) */
+					,&fmode 				/* file mode (not used) */
+					,&serial_num			/* program serial number */
+					,&total_files			/* remaining files to be sent */
+					,&total_bytes			/* remaining bytes to be sent */
+					);
+				lprintf(LOG_DEBUG,"Ymodem header (%u fields): %s", i, block+strlen(block)+1);
+				SAFECOPY(fname,block);
+				fprintf(statfp,"Incoming filename: %.64s ",fname);
+
+			} else {	/* Zmodem */
+				i=zmodem_recv_init(&zm);
+				if(zm.cancelled)
+					return(1);
+				if(i<0)
+					return(-1);
+				lprintf(LOG_DEBUG,"Received header: %s",chr((uchar)i));
+				switch(i) {
+					case ZFREECNT:
+						zmodem_send_pos_header(&zm, ZACK, getfreediskspace(".",1), /* Hex? */ TRUE) ;
+						continue;
+					case ZCOMMAND:
+						lprintf(LOG_WARNING,"Remote command attempted and rejected");
+						zmodem_send_nak(&zm);
+						continue;
+					case ZFILE:
+						if(!zmodem_recv_file_info(&zm
+							,fname,sizeof(fname)
+							,&file_bytes
+							,&ftime
+							,&fmode
+							,&serial_num
+							,&total_files
+							,&total_bytes))
+							continue;
+						break;
+					case ZSINIT:
+						lprintf(LOG_WARNING,"Remote attempted ZSINIT (not supported)");
+						zmodem_send_nak(&zm);
+						break;
+					case ZFIN:
+						zmodem_send_zfin(&zm);
+						/* fall-through */
+					case ZCOMPL:
+						return(0);
+					case ZRQINIT:
+					case ZCAN:
+						return(-1);
+				}
 			}
-			i=sscanf(block+strlen(block)+1,"%ld %lo %lo %lo %d %ld"
-				,&file_bytes			/* file size (decimal) */
-				,&ftime 				/* file time (octal unix format) */
-				,&fmode 				/* file mode (not used) */
-				,&serial_num			/* program serial number */
-				,&total_files			/* remaining files to be sent */
-				,&total_bytes			/* remaining bytes to be sent */
-				);
-			lprintf(LOG_DEBUG,"Ymodem header (%u fields): %s", i, block+strlen(block)+1);
+
 			if(!file_bytes)
 				file_bytes=0x7fffffff;
 			file_bytes_left=file_bytes;
@@ -872,30 +934,28 @@ static int receive_files(char** fname, int fnames)
 				total_files=1;
 			if(total_bytes<file_bytes)
 				total_bytes=file_bytes;
-			if(!serial_num)
-				serial_num=-1;
-			fprintf(statfp,"Incoming filename: %.64s ",block);
-			if(mode&DIR)
-				sprintf(str,"%s%s",fname[0],getfname(block));
+
+			if(mode&RECVDIR)
+				sprintf(str,"%s%s",fname_list[0],getfname(fname));
 			else {
-				SAFECOPY(str,getfname(block));
+				SAFECOPY(str,getfname(fname));
 				for(i=0;i<fnames;i++) {
-					if(!fname[i][0])	/* name blank or already used */
+					if(!fname_list[i][0])	/* name blank or already used */
 						continue;
-					if(!stricmp(getfname(fname[i]),str)) {
-						SAFECOPY(str,fname[i]);
-						fname[i][0]=0;
+					if(!stricmp(getfname(fname_list[i]),str)) {
+						SAFECOPY(str,fname_list[i]);
+						fname_list[i][0]=0;
 						break; 
 					} 
 				}
 				if(i==fnames) { 				/* Not found in list */
 					if(fnames)
 						fprintf(statfp," - Not in receive list!");
-					if(!fnames || fnum>=fnames || !fname[fnum][0])
-						SAFECOPY(str,getfname(block));	/* worst case */
+					if(!fnames || fnum>=fnames || !fname_list[fnum][0])
+						SAFECOPY(str,getfname(fname));	/* worst case */
 					else {
-						SAFECOPY(str,fname[fnum]);
-						fname[fnum][0]=0; 
+						SAFECOPY(str,fname_list[fnum]);
+						fname_list[fnum][0]=0; 
 					} 
 				} 
 			}
@@ -905,94 +965,10 @@ static int receive_files(char** fname, int fnames)
 //			getchar();
 		}
 
-		else {	/* Zmodem */
-#if 0
-			tryzhdrtype=ZRINIT;
-			while(1) {
-				Txhdr[ZF0]=(CANFC32|CANFDX|CANOVIO|CANRLE);
-				/* add CANBRK if we can send break signal */
-				if(zmode&CTRL_ESC)
-					Txhdr[ZF0]|=TESCCTL;
-				Txhdr[ZF1]=CANVHDR;
-				Txhdr[ZP0]=0;
-				Txhdr[ZP1]=0;
-				putzhhdr(tryzhdrtype);
-				done=0;
-				while(!done) {
-					done=1;
-					switch(getzhdr()) {
-						case ZRQINIT:
-							if(Rxhdr[ZF3]&0x80)
-								zmode|=VAR_HDRS;   /* we can var header */
-							break;
-						case ZFILE:
-							zconv=Rxhdr[ZF0];
-							zmanag=Rxhdr[ZF1];
-							ztrans=Rxhdr[ZF2];
-							if(Rxhdr[ZF3]&ZCANVHDR)
-								zmode|=VAR_HDRS;
-							tryzhdrtype=ZRINIT;
-							if(getzdata(block, 1024)==GOTCRCW) {
-								/* something */
-								done=1; 
-							}
-							putzhhdr(ZNAK);
-							done=0;
-							break;
-						case ZSINIT:
-							if(Rxhdr[ZF0]&TESCCTL)
-								zmode|=CTRL_ESC;
-							if (getzdata(attn,ZATTNLEN)==GOTCRCW) {
-								ltohdr(1L);
-								putzhhdr(ZACK); 
-							}
-							else
-								putzhhdr(ZNAK);
-							done=0;
-							break;
-						case ZFREECNT:
-							ltohdr(0);			/* should be free disk space */
-							putzhhdr(ZACK);
-							done=0;
-							break;
-						case ZCOMMAND:
-/***
-							cmdzack1flg = Rxhdr[ZF0];
-							if(getzdata(block,1024)==GOTCRCW) {
-								if (cmdzack1flg & ZCACK1)
-									ltohdr(0L);
-								else
-									ltohdr((long)sys2(block));
-								purgeline();	/* dump impatient questions */
-								do {
-									zshhdr(4,ZCOMPL, Txhdr);
-								}
-								while (++errors<20 && zgethdr(Rxhdr,1)!=ZFIN);
-								ackbibi();
-								if (cmdzack1flg & ZCACK1)
-									exec2(block);
-								return ZCOMPL;
-							}
-***/
-							putzhhdr(ZNAK);
-							done=0;
-							break;
-						case ZCOMPL:
-							done=0;
-							break;
-						case ZFIN:
-							ackbibi();
-							return ZCOMPL;
-						case ZCAN:
-							return ERROR; 
-				} 
-			}
-#endif
-		}
 
 		fnum++;
 
-		if(!(mode&DIR) && fnames && fnum>fnames) {
+		if(!(mode&RECVDIR) && fnames && fnum>fnames) {
 			lprintf(LOG_WARNING,"Attempt to send more files than specified");
 			xmodem_cancel(&xm);
 			break; 
@@ -1000,16 +976,23 @@ static int receive_files(char** fname, int fnames)
 
 		if(fexist(str) && !(mode&OVERWRITE)) {
 			lprintf(LOG_WARNING,"%s already exists",str);
+			if(mode&ZMODEM) {
+				zmodem_send_zskip(&zm);
+				continue;
+			}
 			xmodem_cancel(&xm);
 			return(1); 
 		}
 		if((fp=fopen(str,"wb"))==NULL) {
 			lprintf(LOG_ERR,"Error creating %s",str);
+			if(mode&ZMODEM) {
+				zmodem_send_zskip(&zm);
+				continue;
+			}
 			xmodem_cancel(&xm);
 			return(1); 
 		}
-		setvbuf(fp,NULL,_IOFBF,8*1024);
-		startfile=time(NULL);
+
 		if(mode&XMODEM)
 			lprintf(LOG_INFO,"Receiving %s via Xmodem %s"
 				,str
@@ -1021,55 +1004,76 @@ static int receive_files(char** fname, int fnames)
 				,mode&YMODEM ? mode&GMODE ? "Ymodem-G" : "Ymodem" :"Zmodem"
 				,mode&CRC ? "CRC-16" : "Checksum");
 
-		errors=0;
-		block_num=1;
+		startfile=time(NULL);
 		success=FALSE;
-		xmodem_put_nak(&xm, block_num);
-		while(1) {
-			xmodem_progress(NULL,block_num,ftell(fp),file_bytes,startfile);
-			i=xmodem_get_block(&xm, block, block_num); 	
+		if(mode&ZMODEM) {
 
-			if(i!=0) {
-				if(i==EOT)	{		/* end of transfer */
-					success=TRUE;
-					xmodem_put_ack(&xm);
-					break;
-				}
-				if(i==CAN) {		/* Cancel */
-					cancelled=TRUE;
+			for(errors=0; errors<zm.max_errors 
+				&& (ulong)ftell(fp) < file_bytes && !zm.cancelled; errors++) {
+				if((i = zmodem_recv_file_data(&zm,fp,0,file_bytes,startfile)) == ZEOF)
 					break;
-				}
+				lprintf(LOG_WARNING,"Error at byte %lu: %s", ftell(fp), chr((uchar)i));
+			}
 
-				if(mode&GMODE)
-					return(-1);
+			/*
+ 			 * wait for the eof header
+			 */
+
+			for(;errors<zm.max_errors && !success && !zm.cancelled; errors++) {
+				if(zmodem_rx_header_and_check(&zm,zm.recv_timeout))
+					success=TRUE;
+			} 
+
+		} else {
+			errors=0;
+			block_num=1;
+			xmodem_put_nak(&xm, block_num);
+			while(1) {
+				xmodem_progress(NULL,block_num,ftell(fp),file_bytes,startfile);
+				i=xmodem_get_block(&xm, block, block_num); 	
+
+				if(i!=0) {
+					if(i==EOT)	{		/* end of transfer */
+						success=TRUE;
+						xmodem_put_ack(&xm);
+						break;
+					}
+					if(i==CAN) {		/* Cancel */
+						cancelled=TRUE;
+						break;
+					}
 
-				if(++errors>=xm.max_errors) {
-					lprintf(LOG_ERR,"Too many errors (%u)",errors);
+					if(mode&GMODE)
+						return(-1);
+
+					if(++errors>=xm.max_errors) {
+						lprintf(LOG_ERR,"Too many errors (%u)",errors);
+						xmodem_cancel(&xm);
+						break;
+					}
+					if(block_num==1 && errors>(xm.max_errors/2) && mode&CRC && !(mode&GMODE))
+						mode&=~CRC;
+					xmodem_put_nak(&xm, block_num);
+					continue;
+				}
+				if(!(mode&GMODE))
+					putcom(ACK);
+				if(file_bytes_left<=0L)  { /* No more bytes to send */
+					lprintf(LOG_WARNING,"Attempt to send more byte specified in header");
+					break; 
+				}
+				wr=xm.block_size;
+				if(wr>file_bytes_left)
+					wr=file_bytes_left;
+				if(fwrite(block,1,wr,fp)!=wr) {
+					lprintf(LOG_ERR,"Error writing %u bytes to file at offset %lu"
+						,wr,ftell(fp));
 					xmodem_cancel(&xm);
-					break;
+					return(1); 
 				}
-				if(block_num==1 && errors>(xm.max_errors/2) && mode&CRC && !(mode&GMODE))
-					mode&=~CRC;
-				xmodem_put_nak(&xm, block_num);
-				continue;
-			}
-			if(!(mode&GMODE))
-				putcom(ACK);
-			if(file_bytes_left<=0L)  { /* No more bytes to send */
-				lprintf(LOG_WARNING,"Attempt to send more byte specified in header");
-				break; 
+				file_bytes_left-=wr; 
+				block_num++;
 			}
-			wr=xm.block_size;
-			if(wr>file_bytes_left)
-				wr=file_bytes_left;
-			if(fwrite(block,1,wr,fp)!=wr) {
-				lprintf(LOG_ERR,"Error writing %u bytes to file at offset %lu"
-					,wr,ftell(fp));
-				xmodem_cancel(&xm);
-				return(1); 
-			}
-			file_bytes_left-=wr; 
-			block_num++;
 		}
 
 		/* Use correct file size */
@@ -1106,7 +1110,7 @@ static int receive_files(char** fname, int fnames)
 				,str
 				,serial_num); 
 		}
-		if(mode&XMODEM)
+		if(mode&XMODEM)	/* maximum of one file */
 			break;
 		if((cps=file_bytes/t)==0)
 			cps=1;
@@ -1351,7 +1355,7 @@ int main(int argc, char **argv)
 		}
 
 		else if(argv[i][0]=='+') {
-			if(mode&DIR) {
+			if(mode&RECVDIR) {
 				fprintf(statfp,"!Cannot specify both directory and filename\n");
 				exit(1); 
 			}
@@ -1371,7 +1375,7 @@ int main(int argc, char **argv)
 
 		else if(mode&(SEND|RECV)){
 			if(isdir(argv[i])) { /* is a directory */
-				if(mode&DIR) {
+				if(mode&RECVDIR) {
 					fprintf(statfp,"!Only one directory can be specified\n");
 					exit(1); 
 				}
@@ -1383,7 +1387,7 @@ int main(int argc, char **argv)
 					fprintf(statfp,"!Cannot send directory '%s'\n",argv[i]);
 					exit(1);
 				}
-				mode|=DIR; 
+				mode|=RECVDIR; 
 			}
 			strListAppend(&fname_list,argv[i],fnames++);
 		} 
@@ -1439,7 +1443,7 @@ int main(int argc, char **argv)
 	}
 #endif
 
-//	if(mode&DIR)
+//	if(mode&RECVDIR)
 //		backslash(fname[0]);
 
 	if(mode&ALARM) {
diff --git a/src/sbbs3/sexyz.h b/src/sbbs3/sexyz.h
index 3e33e756375753171ecaa6cbeded5c1576adb996..70dfcc89c50fab14480a20baa7e53092497988ee 100644
--- a/src/sbbs3/sexyz.h
+++ b/src/sbbs3/sexyz.h
@@ -47,7 +47,7 @@
 #define ZMODEM			(1<<4)	/* Use Zmodem								*/
 #define CRC 			(1<<5)	/* Use CRC error correction 				*/
 #define GMODE			(1<<6)	/* For Qmodem-G and Ymodem-G				*/
-#define DIR 			(1<<7)	/* Directory specified to download to		*/
+#define RECVDIR 		(1<<7)	/* Directory specified to download to		*/
 #define DEBUG			(1<<8)	/* Debug output 							*/
 #define OVERWRITE		(1<<9)	/* Overwrite receiving files				*/
 #define ALARM			(1<<11) /* Alarm when starting and stopping xfer	*/
diff --git a/src/sbbs3/zmodem.c b/src/sbbs3/zmodem.c
index b6636e6f906564644ddf57dd7388cc188864678c..7560b7d03f6056f6511a97c4d705fc21a460996e 100644
--- a/src/sbbs3/zmodem.c
+++ b/src/sbbs3/zmodem.c
@@ -69,6 +69,9 @@ static char *chr(uchar ch)
 	static char str[25];
 
 	switch(ch) {
+		case ZRQINIT:	return("ZRQINIT");
+		case ZRINIT:	return("ZRINIT");
+		case ZSINIT:	return("ZSINIT");
 		case ZACK:		return("ZACK");
 		case ZEOF:		return("ZEOF");
 		case ZPAD:		return("ZPAD");
@@ -426,8 +429,7 @@ zmodem_tx_data(zmodem_t* zm, uchar sub_frame_type,unsigned char * p, int l)
 //	YIELD();
 }
 
-void
-zmodem_tx_pos_header(zmodem_t* zm, int type, long pos, BOOL hex) 
+void zmodem_send_pos_header(zmodem_t* zm, int type, long pos, BOOL hex) 
 {
 	uchar header[5];
 
@@ -448,13 +450,12 @@ zmodem_tx_znak(zmodem_t* zm)
 {
 //	lprintf(zm,LOG_INFO,"tx_znak");
 
-	zmodem_tx_pos_header(zm, ZNAK, zm->ack_file_pos, /* Hex? */ TRUE);
+	zmodem_send_pos_header(zm, ZNAK, zm->ack_file_pos, /* Hex? */ TRUE);
 }
 
-void
-zmodem_tx_zskip(zmodem_t* zm)
+void zmodem_send_zskip(zmodem_t* zm)
 {
-	zmodem_tx_pos_header(zm, ZSKIP, 0L, /* Hex? */ TRUE);
+	zmodem_send_pos_header(zm, ZSKIP, 0L, /* Hex? */ TRUE);
 }
 
 /*
@@ -494,7 +495,7 @@ zmodem_rx_raw(zmodem_t* zm, int to)
 		zm->n_cans++;
 		if(zm->n_cans == 5) {
 			zm->cancelled=TRUE;
-			lprintf(zm,LOG_WARNING,"Cancelled Remotely");
+			lprintf(zm,LOG_WARNING,"Cancelled remotely");
 			return(TIMEOUT);
 		}
 	}
@@ -513,7 +514,6 @@ zmodem_rx_raw(zmodem_t* zm, int to)
  * is relatively short.
  */
 
-
 int
 zmodem_rx(zmodem_t* zm, int to)
 {
@@ -634,7 +634,7 @@ zmodem_rx(zmodem_t* zm, int to)
  */
 
 int
-zmodem_rx_32_data(zmodem_t* zm, unsigned char * p,int * l)
+zmodem_rx_32_data(zmodem_t* zm, unsigned char * p, int* l)
 {
 	int c;
 	unsigned long rxd_crc;
@@ -673,6 +673,7 @@ zmodem_rx_32_data(zmodem_t* zm, unsigned char * p,int * l)
 	rxd_crc |= zmodem_rx(zm, 1) << 24;
 
 	if(rxd_crc != crc) {
+		lprintf(zm,LOG_WARNING,"CRC-32 ERROR");
 		return FALSE;
 	}
 
@@ -682,7 +683,7 @@ zmodem_rx_32_data(zmodem_t* zm, unsigned char * p,int * l)
 }
 
 int
-zmodem_rx_16_data(zmodem_t* zm, register unsigned char * p,int * l)
+zmodem_rx_16_data(zmodem_t* zm, register unsigned char * p, int* l)
 {
 	register int c;
 	int sub_frame_type;
@@ -696,7 +697,7 @@ zmodem_rx_16_data(zmodem_t* zm, register unsigned char * p,int * l)
 	crc = 0;
 
 	do {
-		c = zmodem_rx(zm, 5);
+		c = zmodem_rx(zm, zm->recv_timeout);
 
 		if(c == TIMEOUT) {
 			return TIMEOUT;
@@ -715,10 +716,12 @@ zmodem_rx_16_data(zmodem_t* zm, register unsigned char * p,int * l)
 //	crc = ucrc16(0,crc);
 //	crc = ucrc16(0,crc);
 
-	rxd_crc  = zmodem_rx(zm, 1) << 8;
-	rxd_crc |= zmodem_rx(zm, 1);
+	rxd_crc  = zmodem_rx(zm, zm->recv_timeout) << 8;
+	rxd_crc |= zmodem_rx(zm, zm->recv_timeout);
 
 	if(rxd_crc != crc) {
+		lprintf(zm,LOG_WARNING,"CRC-16 ERROR (%04hX vs %04hX)"
+			,rxd_crc, crc);
 		return FALSE;
 	}
 
@@ -772,16 +775,18 @@ zmodem_rx_data(zmodem_t* zm, unsigned char * p, int * l)
  		 * frame continues; ZACK expected
 		 */
 		case ZCRCQ:		
-			zmodem_tx_pos_header(zm, ZACK, pos, /* Hex? */ TRUE);
+			zmodem_send_pos_header(zm, ZACK, pos, /* Hex? */ TRUE);
 			return FRAMEOK;
 		/*
 		 * frame ends; ZACK expected
 		 */
 		case ZCRCW:
-			zmodem_tx_pos_header(zm, ZACK, pos, /* Hex? */ TRUE);
+			zmodem_send_pos_header(zm, ZACK, pos, /* Hex? */ TRUE);
 			return ENDOFFRAME;
 	}
 
+	lprintf(zm,LOG_WARNING,"Invalid sub-frame type: %s",chr((uchar)sub_frame_type));
+
 	return FALSE;
 }
 
@@ -1002,7 +1007,7 @@ zmodem_rx_bin32_header(zmodem_t* zm, int to)
  */
 
 int
-zmodem_rx_header_raw(zmodem_t* zm, int to,int errors)
+zmodem_rx_header_raw(zmodem_t* zm, int to, int errors)
 {
 	int c;
 
@@ -1030,7 +1035,7 @@ zmodem_rx_header_raw(zmodem_t* zm, int to,int errors)
 		 */
 
 		if(c != ZDLE) {
-			lprintf(zm,LOG_ERR,"expected ZDLE; got %c",c);
+			lprintf(zm,LOG_WARNING,"Expected ZDLE, received: %s", chr((uchar)c));
 			continue;
 		}
 
@@ -1065,7 +1070,7 @@ zmodem_rx_header_raw(zmodem_t* zm, int to,int errors)
 				/*
 				 * unrecognized header style
 				 */
-				lprintf(zm,LOG_ERR,"UNRECOGNIZED header style %c",c);
+				lprintf(zm,LOG_ERR,"UNRECOGNIZED header style: %s",chr((uchar)c));
 				if(errors) {
 					return INVHDR;
 				}
@@ -1154,7 +1159,19 @@ int zmodem_get_zrinit(zmodem_t* zm)
 	zmodem_tx_raw(zm,'\r');
 	zmodem_tx_hex_header(zm,zrqinit_header);
 	
-	return zmodem_rx_header(zm,7);
+	return zmodem_rx_header(zm,zm->recv_timeout);
+}
+
+void zmodem_send_zrinit(zmodem_t* zm)
+{
+	unsigned char zrinit_header[] = { ZRINIT, 0, 0, 0, ZF0_CANFDX | ZF0_CANOVIO | ZF0_CANFC32 };
+
+	zmodem_tx_hex_header(zm, zrinit_header);
+}
+
+void zmodem_send_nak(zmodem_t* zm)
+{
+	zmodem_send_pos_header(zm, ZNAK, 0, /* Hex? */ TRUE);
 }
 
 int zmodem_send_zfin(zmodem_t* zm)
@@ -1199,7 +1216,7 @@ zmodem_send_from(zmodem_t* zm, FILE* fp, ulong pos, ulong fsize, ulong* sent)
 
 	fseek(fp,pos,SEEK_SET);
 
-	zmodem_tx_pos_header(zm, ZDATA, pos, /* Hex? */ FALSE);
+	zmodem_send_pos_header(zm, ZDATA, pos, /* Hex? */ FALSE);
 
 	/*
 	 * send the data in the file
@@ -1218,8 +1235,9 @@ zmodem_send_from(zmodem_t* zm, FILE* fp, ulong pos, ulong fsize, ulong* sent)
 			 */
 			break;
 		}
-
-		zm->progress(zm->cbdata, pos, ftell(fp), fsize, zm->transfer_start);
+	
+		if(zm->progress!=NULL)
+			zm->progress(zm->cbdata, pos, ftell(fp), fsize, zm->transfer_start);
 
 		/*
 		 * at end of file wait for an ACK
@@ -1236,7 +1254,7 @@ zmodem_send_from(zmodem_t* zm, FILE* fp, ulong pos, ulong fsize, ulong* sent)
 		if(type == ZCRCW) {
 			int type;
 			do {
-				type = zmodem_rx_header(zm, 10);
+				type = zmodem_rx_header(zm, zm->recv_timeout);
 				if(type == ZNAK || type == ZRPOS || type == TIMEOUT) {
 					return type;
 				}
@@ -1307,7 +1325,7 @@ BOOL zmodem_send_file(zmodem_t* zm, char* fname, FILE* fp, BOOL request_init, ti
 	zm->file_skipped=FALSE;
 
 	if(request_init) {
-		for(errors=0;errors<zm->max_errors;errors++) {
+		for(errors=0; errors<zm->max_errors && !zm->cancelled; errors++) {
 			lprintf(zm,LOG_INFO,"Sending ZRQINIT (%u of %u)",errors+1,zm->max_errors);
 			i = zmodem_get_zrinit(zm);
 			if(i == ZRINIT) {
@@ -1316,7 +1334,7 @@ BOOL zmodem_send_file(zmodem_t* zm, char* fname, FILE* fp, BOOL request_init, ti
 			}
 			lprintf(zm,LOG_WARNING,"RX header type: %d 0x%02X", i, i);
 		}
-		if(errors>=zm->max_errors)
+		if(errors>=zm->max_errors || zm->cancelled)
 			return(FALSE);
 	}
 
@@ -1601,6 +1619,126 @@ zmodem_send_files(char** fname, int total_files)
 
 #endif
 
+int zmodem_recv_init(zmodem_t* zm)
+{
+	int			ch;
+	int			type=CAN;
+	unsigned	errors;
+
+	while(!zm->cancelled && (ch=zm->recv_byte(zm,0))!=NOINP)
+		lprintf(zm,LOG_WARNING,"Throwing out received: %s",chr((uchar)ch));
+
+	for(errors=0; errors<zm->max_errors && !zm->cancelled; errors++) {
+		zmodem_send_zrinit(zm);
+		type = zmodem_rx_header(zm, zm->recv_timeout);
+		if(type!=TIMEOUT && type!=ZRQINIT)
+			break;
+	}
+
+	return(type);
+}
+
+BOOL zmodem_recv_file_info(zmodem_t* zm
+						   ,char* fname, size_t maxlen
+						   ,ulong* p_size
+						   ,time_t* p_time
+						   ,long* p_mode
+						   ,long* p_serial
+						   ,ulong* p_total_files
+						   ,ulong* p_total_bytes)
+{
+	uchar		block[8192];
+	int			i;
+	ulong		size=0;
+	ulong		time=0;
+	ulong		total_files=0;
+	ulong		total_bytes=0;
+	long		mode=0;
+	long		serial=-1;
+	unsigned	l;
+
+	i=zmodem_rx_data(zm, block, &l);
+
+	if(i!=FRAMEOK && i!=ENDOFFRAME)
+		return(FALSE);
+
+	if(fname!=NULL)
+		safe_snprintf(fname,maxlen,"%s",block);
+
+	i=sscanf(block+strlen(block)+1,"%lu %lo %lo %lo %lu %lu"
+		,&size					/* file size (decimal) */
+		,&time 					/* file time (octal unix format) */
+		,&mode 					/* file mode */
+		,&serial				/* program serial number */
+		,&total_files			/* remaining files to be sent */
+		,&total_bytes			/* remaining bytes to be sent */
+		);
+
+	lprintf(zm,LOG_DEBUG,"Zmodem header (%u fields): %s"
+		,i, block+strlen(block)+1);
+
+	if(p_size)			*p_size=size;
+	if(p_time)			*p_time=time;
+	if(p_mode)			*p_mode=mode;
+	if(p_serial)		*p_serial=serial;
+	if(p_total_files)	*p_total_files=total_files;
+	if(p_total_bytes)	*p_total_bytes=total_bytes;
+
+	return(TRUE);
+}
+
+/*
+ * receive file data until the end of the file or until something goes wrong.
+ * the name is only used to show progress
+ */
+
+int zmodem_recv_file_data(zmodem_t* zm, FILE* fp, ulong offset, ulong fsize, time_t start)
+{
+	static int first = TRUE;
+	long pos;
+	int n;
+	int type;
+
+	/*
+ 	 * create a ZRPOS frame and send it to the other side
+	 */
+	zmodem_send_pos_header(zm, ZRPOS, ftell(fp), /* Hex? */ TRUE);
+
+	/*
+	 * wait for a ZDATA header with the right file offset
+	 * or a timeout or a ZFIN
+	 */
+
+	do {
+		do {
+			type = zmodem_rx_header(zm,zm->recv_timeout);
+			if (type == TIMEOUT) {
+				return TIMEOUT;
+			}
+		} while (type != ZDATA && !zm->cancelled);
+
+		pos = zm->rxd_header[ZP0] | (zm->rxd_header[ZP1] << 8) |
+			(zm->rxd_header[ZP2] << 16) | (zm->rxd_header[ZP3] << 24);
+	} while (pos != ftell(fp) && !zm->cancelled);
+		
+	do {
+		type = zmodem_rx_data(zm,zm->rx_data_subpacket,&n);
+
+/*		fprintf(stderr,"packet len %d type %d\n",n,type);
+*/
+		if (type == ENDOFFRAME || type == FRAMEOK) {
+			fwrite(zm->rx_data_subpacket,1,n,fp);
+		}
+
+		if(zm->progress!=NULL)
+			zm->progress(zm->cbdata,offset,ftell(fp),fsize,start);
+
+	} while (type == FRAMEOK && !zm->cancelled);
+
+	return type;
+}
+
+
 const char* zmodem_source(void)
 {
 	return(__FILE__);
diff --git a/src/sbbs3/zmodem.h b/src/sbbs3/zmodem.h
index d8bf62fda4f89bb454f1dcf37e31a4256c9c7c00..045bf5f9df57fc3ee890606131f934f86d2797f6 100644
--- a/src/sbbs3/zmodem.h
+++ b/src/sbbs3/zmodem.h
@@ -227,6 +227,7 @@ typedef struct {
 	int n_files_remaining;
 	int n_bytes_remaining;
 	unsigned char tx_data_subpacket[MAX_SUBPACKETSIZE];
+	unsigned char rx_data_subpacket[8192];							/* zzap = 8192 */
 
 	ulong current_file_size;
 	time_t transfer_start;
@@ -270,11 +271,25 @@ void		zmodem_init(zmodem_t*, void* cbdata, long* mode
 						,int	(*recv_byte)(void*, unsigned timeout));
 char*		zmodem_ver(char *buf);
 const char* zmodem_source(void);
+void		zmodem_send_nak(zmodem_t*);
+void		zmodem_send_zskip(zmodem_t* zm);
+void		zmodem_send_zrinit(zmodem_t*);
+void		zmodem_send_pos_header(zmodem_t* zm, int type, long pos, BOOL hex);
 int			zmodem_get_zrinit(zmodem_t*);
 void		zmodem_parse_zrinit(zmodem_t*);
 int			zmodem_send_zfin(zmodem_t*);
 BOOL		zmodem_send_file(zmodem_t*, char* name, FILE* fp, BOOL request_init, time_t* start, ulong* bytes_sent);
-
+int			zmodem_recv_init(zmodem_t* zm);
+BOOL		zmodem_recv_file_info(zmodem_t* zm
+									,char* fname, size_t maxlen
+									,ulong* size
+									,time_t* time
+									,long* mode
+									,long* serial_num
+									,ulong* total_files
+									,ulong* total_bytes);
+int			zmodem_recv_file_data(zmodem_t*, FILE*, ulong offset, ulong fsize, time_t start);
+int			zmodem_rx_header_and_check(zmodem_t* zm, int timeout);
 #endif