/***************************************************************************** * * $Id$ * Purpose ...............: Fidonet mailer * ***************************************************************************** * Copyright (C) 1997-2001 * * Michiel Broek FIDO: 2:280/2802 * Beekmansbos 10 * 1971 BV IJmuiden * the Netherlands * * This file is part of MBSE BBS. * * This BBS is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * MBSE BBS is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MBSE BBS; see the file COPYING. If not, write to the Free * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. *****************************************************************************/ #include "../lib/libs.h" #include "../lib/structs.h" #include "../lib/common.h" #include "../lib/clcomm.h" #include "session.h" #include "ttyio.h" #include "statetbl.h" #include "config.h" #include "lutil.h" #include "openfile.h" #include "m7recv.h" #include "xmrecv.h" #include "filetime.h" #define XMBLKSIZ 128 static int xm_recv(void); static int resync(off_t); static int closeit(int); static char *recvname=NULL; static char *fpath=NULL; static FILE *fp=NULL; static int last; static time_t stm,etm; static off_t startofs; static long recv_blk; extern unsigned long rcvdbytes; int xmrecv(char *Name) { int rc; Syslog('+', "Xmodem start receive \"%s\"",MBSE_SS(Name)); recvname = Name; last = 0; rc = xm_recv(); if (fp) closeit(0); if (rc) return -1; else if (last) return 1; else return 0; } int closeit(int success) { off_t endofs; endofs = recv_blk*XMBLKSIZ; etm = time(NULL); if (etm == stm) etm++; Syslog('+', "Xmodem %s %lu bytes in %s (%lu cps)", success?"received":"dropped after", (unsigned long)(endofs-startofs),str_time(etm-stm), (unsigned long)(endofs-startofs)/(etm-stm)); rcvdbytes += (unsigned long)(endofs-startofs); fp = NULL; return closefile(success); } SM_DECL(xm_recv,(char *)"xmrecv") SM_STATES sendnak0, waitblk0, sendnak, waitblk, recvblk, sendack, checktelink, recvm7, goteof SM_NAMES (char *)"sendnak0", (char *)"waitblk0", (char *)"sendnak", (char *)"waitblk", (char *)"recvblk", (char *)"sendack", (char *)"checktelink", (char *)"recvm7", (char *)"goteof" SM_EDECL int tmp, i; int SEAlink = FALSE, Slo = FALSE; int crcmode = session_flags & FTSC_XMODEM_CRC; int count=0,junk=0,cancount=0; int header = 0; struct _xmblk { unsigned char n1,n2; unsigned char data[XMBLKSIZ]; unsigned char c1,c2; } xmblk; unsigned short localcrc,remotecrc; unsigned char localcs,remotecs; long ackd_blk=-1L; long next_blk=1L; long last_blk=0L; off_t resofs; char tmpfname[16]; off_t wsize; time_t remtime=0L; off_t remsize=0; int goteot = FALSE; Syslog('x', "xmrecv INIT"); stm = time(NULL); recv_blk=-1L; memset(&tmpfname, 0, sizeof(tmpfname)); if (recvname) strncpy(tmpfname,recvname,sizeof(tmpfname)-1); SM_START(sendnak0) SM_STATE(sendnak0) Syslog('x', "xmrecv SENDNAK0 count=%d mode=%s", count, crcmode?"crc":"cksum"); if (count++ > 9) { Syslog('+', "too many errors while xmodem receive init"); SM_ERROR; } if ((ackd_blk < 0) && crcmode && (count > 5)) { Syslog('x', "no responce to 'C', try checksum mode"); session_flags &= ~FTSC_XMODEM_CRC; crcmode = FALSE; } if (crcmode) PUTCHAR('C'); else PUTCHAR(NAK); junk = 0; SM_PROCEED(waitblk0); SM_STATE(waitblk0) Syslog('x', "xmrecv WAITBLK0"); header = GETCHAR(5); if (header == TIMEOUT) { Syslog('x', "timeout waiting for xmodem block 0 header, count=%d", count); if ((count > 2) && (session_flags & SESSION_IFNA)) { Syslog('+', "Timeout waiting for file in WaZOO session, report success"); last=1; SM_SUCCESS; } SM_PROCEED(sendnak0); } else if (header < 0) { Syslog('x', "Error"); SM_ERROR; } else { switch (header) { case EOT: Syslog('x', "got EOT"); Slo = FALSE; if (ackd_blk == -1L) last=1; else { ackd_blk++; PUTCHAR(ACK); if (SEAlink) { PUTCHAR(ackd_blk); PUTCHAR(~ackd_blk); } } if (STATUS) { SM_ERROR; } SM_SUCCESS; break; case CAN: Syslog('+', "Got CAN while xmodem receive init"); SM_ERROR; break; case SOH: SEAlink = TRUE; Syslog('x', "Got SOH, SEAlink mode"); SM_PROCEED(recvblk); break; case SYN: SEAlink = FALSE; Syslog('x', "Got SYN, Telink mode"); SM_PROCEED(recvblk); break; case ACK: SEAlink = FALSE; Syslog('x', "Got ACK, Modem7 mode"); SM_PROCEED(recvm7); break; case TSYNC: Syslog('x', "Got TSYSNC char"); SM_PROCEED(sendnak0); break; case NAK: case 'C': Syslog('x', "Got %s waiting for block 0, sending EOT", printablec(header)); PUTCHAR(EOT); /* other end still waiting us to send? */ SM_PROCEED(waitblk0); break; default: Syslog('x', "Got '%s' waiting for block 0", printablec(header)); if (junk++ > 300) { SM_PROCEED(sendnak0); } else { SM_PROCEED(waitblk0); } break; } } SM_STATE(sendnak) Syslog('x', "xmrecv SENDNAK"); if (ackd_blk < 0) { SM_PROCEED(sendnak0); } if (count++ > 9) { Syslog('+', "too many errors while xmodem receive"); SM_ERROR; } junk = 0; if (remote_flags & FTSC_XMODEM_RES) { if (resync(ackd_blk*XMBLKSIZ)) { SM_ERROR; } else { SM_PROCEED(waitblk); } } else { /* simple NAK */ Syslog('x', "negative acknowlege block %ld",ackd_blk+1); if (crcmode) PUTCHAR('C'); else PUTCHAR(NAK); if (SEAlink) { PUTCHAR(ackd_blk+1); PUTCHAR(~(ackd_blk+1)); } if (STATUS) { SM_ERROR; } else { SM_PROCEED(waitblk); } } SM_STATE(sendack) Syslog('x', "xmrecv SENDACK block=%d", recv_blk); ackd_blk = recv_blk; count = 0; cancount = 0; PUTCHAR(ACK); if (SEAlink) { PUTCHAR(ackd_blk); PUTCHAR(~ackd_blk); } if (STATUS) { SM_ERROR; } if (goteot) { SM_SUCCESS; } SM_PROCEED(waitblk); SM_STATE(waitblk) Syslog('x', "xmrecv WAITBLK"); header = GETCHAR(15); if (header == TIMEOUT) { Syslog('x', "timeout waiting for xmodem block header, count=%d", count); SM_PROCEED(sendnak); } else if (header < 0) { SM_ERROR; } else { switch (header) { case EOT: if (last_blk && (ackd_blk != last_blk)) { Syslog('x', "false EOT after %ld block, need after %ld", ackd_blk,last_blk); SM_PROCEED(waitblk); } else { SM_PROCEED(goteof); } break; case CAN: if (cancount++ > 4) { closeit(0); Syslog('+', "Got CAN while xmodem receive"); SM_ERROR; } else { SM_PROCEED(waitblk); } break; case SOH: SM_PROCEED(recvblk); break; default: Syslog('x', "got '%s' waiting SOH", printablec(header)); if (junk++ > 200) { SM_PROCEED(sendnak); } else { SM_PROCEED(waitblk); } break; } } SM_STATE(recvblk) Syslog('x', "xmrecv RECVBLK"); Nopper(); GET((char*)&xmblk,(crcmode && (header != SYN))? sizeof(xmblk): sizeof(xmblk)-1,15); if (STATUS == STAT_TIMEOUT) { Syslog('x', "xmrecv timeout waiting for block body"); SM_PROCEED(sendnak); } if (STATUS) { SM_ERROR; } if ((xmblk.n1 & 0xff) != ((~xmblk.n2) & 0xff)) { Syslog('x', "bad block number: 0x%02x/0x%02x (0x%02x)", xmblk.n1,xmblk.n2,(~xmblk.n2)&0xff); SM_PROCEED(waitblk); } recv_blk = xmblk.n1 + (ackd_blk & ~0xff); if (abs(recv_blk - ackd_blk) > 128) recv_blk += 256; if (crcmode && (header != SYN)) { remotecrc = (short)xmblk.c1 << 8 | xmblk.c2; localcrc = crc16xmodem(xmblk.data, sizeof(xmblk.data)); if (remotecrc != localcrc) { Syslog('x', "bad crc: 0x%04x/0x%04x",remotecrc,localcrc); if (recv_blk == (ackd_blk+1)) { SM_PROCEED(sendnak); } else { SM_PROCEED(waitblk); } } } else { remotecs = xmblk.c1; localcs = checksum(xmblk.data, sizeof(xmblk.data)); if (remotecs != localcs) { Syslog('x', "bad checksum: 0x%02x/0x%02x",remotecs,localcs); if (recv_blk == (ackd_blk+1)) { SM_PROCEED(sendnak); } else { SM_PROCEED(waitblk); } } } if ((ackd_blk == -1L) && (recv_blk == 0L)) { SM_PROCEED(checktelink); } if ((ackd_blk == -1L) && (recv_blk == 1L)) { if (count < 3) { SM_PROCEED(sendnak0); } else ackd_blk=0L; } if (recv_blk < (ackd_blk+1L)) { Syslog('x', "old block number %ld after %ld, go on", recv_blk,ackd_blk); SM_PROCEED(waitblk); } else if (recv_blk > (ackd_blk+1L)) { Syslog('x', "bad block order: %ld after %ld, go on", recv_blk,ackd_blk); SM_PROCEED(waitblk); } Syslog('X', "received block %ld \"%s\"", recv_blk,printable(xmblk.data,128)); if (fp == NULL) { if ((fp = openfile(tmpfname,remtime,remsize,&resofs,resync)) == NULL) { SM_ERROR; } else { if (resofs) ackd_blk=(resofs-1)/XMBLKSIZ+1L; else ackd_blk=-1L; } startofs=resofs; Syslog('+', "Xmodem receive: \"%s\"",tmpfname); } if (recv_blk > next_blk) { WriteError("xmrecv internal error: recv_blk %ld > next_blk %ld", recv_blk,next_blk); SM_ERROR; } if (recv_blk == next_blk) { if (recv_blk == last_blk) wsize=remsize%XMBLKSIZ; else wsize=XMBLKSIZ; if (wsize == 0) wsize=XMBLKSIZ; if ((tmp = fwrite(xmblk.data,wsize,1,fp)) != 1) { WriteError("$error writing block %l (%d bytes) to file \"%s\" (fwrite return %d)", recv_blk,wsize,fpath,tmp); SM_ERROR; } else Syslog('x', "Block %ld size %d written (ret %d)", recv_blk,wsize,tmp); next_blk++; } else { Syslog('x', "recv_blk %ld < next_blk %ld, ack without writing", recv_blk,next_blk); } SM_PROCEED(sendack); SM_STATE(checktelink) Syslog('x', "xmrecv CHECKTELINK"); Syslog('X', "checktelink got \"%s\"",printable(xmblk.data,45)); if (tmpfname[0] == '\0') { strncpy(tmpfname,xmblk.data+8,16); /* * Some systems fill the rest of the filename with spaces, sigh. */ for (i = 16; i; i--) { if ((tmpfname[i] == ' ') || (tmpfname[i] == '\0')) tmpfname[i] = '\0'; else break; } } else { Syslog('+', "Remote uses %s",printable(xmblk.data+25,-14)); Syslog('X', "Remote file name \"%s\" discarded", printable(xmblk.data+8,-16)); } remsize = ((off_t)xmblk.data[0]) + ((off_t)xmblk.data[1]<<8) + ((off_t)xmblk.data[2]<<16) + ((off_t)xmblk.data[3]<<24); last_blk = (remsize-1)/XMBLKSIZ+1; if (header == SOH) { /* * SEAlink block */ remtime=sl2mtime(((time_t)xmblk.data[4])+ ((time_t)xmblk.data[5]<<8)+ ((time_t)xmblk.data[6]<<16)+ ((time_t)xmblk.data[7]<<24)); if (xmblk.data[40]) { Slo = TRUE; remote_flags |= FTSC_XMODEM_SLO; } else remote_flags &= ~FTSC_XMODEM_SLO; if (xmblk.data[41]) remote_flags |= FTSC_XMODEM_RES; else remote_flags &= ~FTSC_XMODEM_RES; if (xmblk.data[42]) remote_flags |= FTSC_XMODEM_XOF; else remote_flags &= ~FTSC_XMODEM_XOF; } else if (header == SYN) { /* * Telink block */ remtime=tl2mtime(((time_t)xmblk.data[4])+ ((time_t)xmblk.data[5]<<8)+ ((time_t)xmblk.data[6]<<16)+ ((time_t)xmblk.data[7]<<24)); if (xmblk.data[41]) session_flags |= FTSC_XMODEM_CRC; else session_flags &= ~FTSC_XMODEM_CRC; } else { WriteError("Got data block with header 0x%02x", header); SM_PROCEED(sendnak0); } Syslog('x', "%s block, session_flags=0x%04x, remote_flags=0x%04x", (header == SYN)?"Telink":"Sealink",session_flags,remote_flags); if ((fp = openfile(tmpfname,remtime,remsize,&resofs,resync)) == NULL) { SM_ERROR; } if (resofs) ackd_blk=(resofs-1)/XMBLKSIZ+1L; else ackd_blk=-1L; startofs=resofs; Syslog('+', "Xmodem %s receive: \"%s\" %ld bytes dated %s", (header == SYN)?"Telink":"Sealink", tmpfname, remsize, rfcdate(remtime)); if (ackd_blk == -1) { SM_PROCEED(sendack); } else { SM_PROCEED(waitblk); } SM_STATE(recvm7) Syslog('x', "xmrecv RECVM7"); switch (m7recv(tmpfname)) { case 0: ackd_blk=0; SM_PROCEED(sendnak); break; case 1: last=1; SM_SUCCESS; break; default: SM_PROCEED(sendnak); } SM_STATE(goteof) Syslog('x', "xmrecv GOTEOF"); Slo = FALSE; closeit(1); if (ackd_blk == -1L) last=1; else { ackd_blk++; PUTCHAR(ACK); if (SEAlink) { PUTCHAR(ackd_blk); PUTCHAR(~ackd_blk); } } if (STATUS) { SM_ERROR; } SM_SUCCESS; SM_END SM_RETURN int resync(off_t resofs) { char resynbuf[16]; short lcrc; int count=0; int gotack,gotnak; int c; long sblk; Syslog('x', "trying to resync at offset %ld",resofs); sblk=resofs/XMBLKSIZ+1; sprintf(resynbuf,"%ld",sblk); lcrc=crc16xmodem(resynbuf,strlen(resynbuf)); gotack=0; gotnak=0; do { count++; PUTCHAR(SYN); PUTSTR(resynbuf); PUTCHAR(ETX); PUTCHAR(lcrc&0xff); PUTCHAR(lcrc>>8); do { if ((c=GETCHAR(5)) == ACK) { if ((c=GETCHAR(1)) == SOH) gotack=1; UNGETCHAR(c); } else if (c == NAK) { if ((c=GETCHAR(1)) == TIMEOUT) gotnak=1; UNGETCHAR(c); } } while (!gotack && !gotnak && (c >= 0)); if ((c < 0) && (c != TIMEOUT)) return 1; } while (!gotack && !gotnak && (count < 6)); if (gotack) { Syslog('x', "resyncing at offset %ld",resofs); return 0; } else { Syslog('+', "sealink resync at offset %ld failed",resofs); return 1; } }