/***************************************************************************** * * $Id$ * Purpose ...............: Zmodem receive * ***************************************************************************** * Copyright (C) 1997-2006 * * 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, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *****************************************************************************/ #include "../config.h" #include "../lib/mbselib.h" #include "../lib/users.h" #include "ttyio.h" #include "transfer.h" #include "zmmisc.h" #include "zmrecv.h" #include "ymrecv.h" #include "openport.h" #include "timeout.h" #include "input.h" static FILE *fout = NULL; char *curfile = NULL; off_t rxbytes; static int Eofseen; /* indicates cpm eof (^Z) has been received */ static int errors; int sbytes; struct timeval starttime, endtime; struct timezone tz; #define DEFBYTL 2000000000L /* default rx file size */ int Bytesleft; /* number of bytes of incoming file left */ static int Modtime; /* Unix style mod time for incoming file */ static int Filemode; /* Unix style mode for incoming file */ static int Thisbinary = TRUE; /* current file is to be received in bin mode */ char *secbuf=0; /* "sector" buffer */ static int tryzhdrtype; static char zconv; /* ZMODEM file conversion request */ static char zmanag; /* ZMODEM file management request */ static char ztrans; /* ZMODEM file transport request */ static int tryz(void); static int rzfiles(void); static int rzfile(void); static void zmputs(char*); static int ackbibi(void); static int getfree(void); extern unsigned int rcvdbytes; extern int zmodem_requested; extern int Rxtimeout; /* * Receive files with Zmodem, Ymodem or Xmodem. * This receiver will figure out what to do, you should * be able to send anything. */ int zmrcvfiles(int want1k, int wantg) { int rc; Syslog('+', "%s: start receive", protname()); get_frame_buffer(); zsendline_init(); Rxtimeout = 10; if (secbuf == NULL) secbuf = malloc(MAXBLOCK+1); tryzhdrtype = ZRINIT; if ((rc = tryz()) < 0) { Syslog('+', "%s: could not initiate receive, rc=%d", protname(), rc); } else { if (rc == 0) { if (protocol == ZM_ZMODEM) { Syslog('+', "%s: switching to Ymodem", protname()); protocol = ZM_YMODEM; } rc = ymrcvfiles(want1k, wantg); goto fubar; } /* * Zmodem receiver */ switch (rc) { case ZCOMPL: rc = 0; break; case ZFILE: rc = rzfiles(); break; } } fubar: if (fout) { if (closeit(0)) { WriteError("%s: Error closing file", protname); } } if (secbuf) free(secbuf); secbuf = NULL; io_mode(0, 1); /* Normal raw mode */ /* * Some programs send some garbage after the transfer, eat these. * This also introduces a pause after the transfer, some clients * need this. */ purgeline(200); Syslog('+', "%s: end receive rc=%d", protname(), rc); return abs(rc); } /* * Initialize for Zmodem receive attempt, try to activate Zmodem sender * Handles ZSINIT frame * Return ZFILE if Zmodem filename received, -1 on error, * ZCOMPL if transaction finished, else 0: can be ymodem. */ int tryz(void) { int c, n; int cmdzack1flg; if (protocol != ZM_ZMODEM) return 0; for (n = zmodem_requested ?15:10; --n >= 0; ) { /* * Set buffer length (0) and capability flags */ Syslog('z', "tryz attempt %d", n); stohdr(0L); Txhdr[ZF0] = CANFC32|CANFDX|CANOVIO; if (Zctlesc) Txhdr[ZF0] |= TESCCTL; zshhdr(tryzhdrtype, Txhdr); if (tryzhdrtype == ZSKIP) /* Don't skip too far */ tryzhdrtype = ZRINIT; /* CAF 8-21-87 */ again: switch (zgethdr(Rxhdr)) { case ZRQINIT: continue; case ZEOF: continue; case TIMEOUT: Syslog('z', "Zmodem: tryz() timeout attempt %d", n); continue; case ZFILE: zconv = Rxhdr[ZF0]; if (!zconv) { Syslog('z', "*** !zconv %d", zconv); zconv = ZCBIN; } zmanag = Rxhdr[ZF1]; ztrans = Rxhdr[ZF2]; tryzhdrtype = ZRINIT; c = zrdata(secbuf, MAXBLOCK); io_mode(0, 3); if (c == GOTCRCW) { Syslog('z', "tryz return ZFILE"); return ZFILE; } zshhdr(ZNAK, Txhdr); goto again; case ZSINIT: /* this once was: * Zctlesc = TESCCTL & Rxhdr[ZF0]; * trouble: if rz get --escape flag: * - it sends TESCCTL to sz, * get a ZSINIT _without_ TESCCTL (yeah - sender didn't know), * overwrites Zctlesc flag ... * - sender receives TESCCTL and uses "|=..." * so: sz escapes, but rz doesn't unescape ... not good. */ Zctlesc |= TESCCTL & Rxhdr[ZF0]; if (zrdata(Attn, ZATTNLEN) == GOTCRCW) { stohdr(1L); zshhdr(ZACK, Txhdr); goto again; } zshhdr(ZNAK, Txhdr); goto again; case ZFREECNT: stohdr(getfree()); zshhdr(ZACK, Txhdr); goto again; case ZCOMMAND: cmdzack1flg = Rxhdr[ZF0]; if (zrdata(secbuf, MAXBLOCK) == GOTCRCW) { if (cmdzack1flg & ZCACK1) stohdr(0L); else Syslog('+', "Zmodem: request for command \"%s\" ignored", printable(secbuf,-32)); stohdr(0L); do { zshhdr(ZCOMPL, Txhdr); } while (++errors<20 && zgethdr(Rxhdr) != ZFIN); return ackbibi(); } zshhdr(ZNAK, Txhdr); goto again; case ZCOMPL: goto again; case ZRINIT: Syslog('z', "tryz: got ZRINIT"); return TERROR; case ZFIN: /* do not beleive in first ZFIN */ ackbibi(); return ZCOMPL; case TERROR: case HANGUP: case ZCAN: return TERROR; default: continue; } } return 0; } /* * Receive 1 or more files with ZMODEM protocol */ int rzfiles(void) { int c; for (;;) { switch (c = rzfile()) { case ZEOF: case ZSKIP: case ZFERR: switch (tryz()) { case ZCOMPL: return OK; default: return TERROR; case ZFILE: break; } continue; default: return c; case TERROR: return TERROR; } } /* NOTREACHED */ } /* * Receive a file with ZMODEM protocol * Assumes file name frame is in secbuf */ int rzfile(void) { int c, n; Eofseen=FALSE; rxbytes = 0l; if ((c = procheader(secbuf))) { return (tryzhdrtype = c); } n = 20; for (;;) { Syslog('z', "rxbytes %ld", rxbytes); stohdr(rxbytes); zshhdr(ZRPOS, Txhdr); nxthdr: switch (c = zgethdr(Rxhdr)) { default: Syslog('z', "rzfile: Wrong header %d", c); if ( --n < 0) { Syslog('+', "Zmodem: wrong header %d", c); return TERROR; } continue; case ZCAN: Syslog('+', "Zmodem: sender CANcelled"); return TERROR; case ZNAK: if ( --n < 0) { Syslog('+', "Zmodem: Got ZNAK"); return TERROR; } continue; case TIMEOUT: if ( --n < 0) { Syslog('z', "Zmodem: TIMEOUT"); return TERROR; } continue; case ZFILE: zrdata(secbuf, MAXBLOCK); continue; case ZEOF: if (rclhdr(Rxhdr) != rxbytes) { /* * Ignore eof if it's at wrong place - force * a timeout because the eof might have gone * out before we sent our zrpos. */ errors = 0; goto nxthdr; } if (closeit(1)) { tryzhdrtype = ZFERR; Syslog('+', "Zmodem: error closing file"); return TERROR; } fout = NULL; Syslog('z', "rzfile: normal EOF"); return c; case HANGUP: Syslog('+', "Zmodem: Lost Carrier"); return TERROR; case TERROR: /* Too much garbage in header search error */ if (--n < 0) { Syslog('+', "Zmodem: Too many errors"); return TERROR; } zmputs(Attn); continue; case ZSKIP: Modtime = 1; closeit(1); Syslog('+', "Zmodem: Sender SKIPPED file"); return c; case ZDATA: if (rclhdr(Rxhdr) != rxbytes) { if ( --n < 0) { Syslog('+', "Zmodem: Data has bad address"); return TERROR; } zmputs(Attn); continue; } moredata: Syslog('Z', "%7ld ZMODEM%s ", rxbytes, Crc32r?" CRC-32":""); Nopper(); alarm_on(); switch (c = zrdata(secbuf, MAXBLOCK)) { case ZCAN: Syslog('+', "Zmodem: sender CANcelled"); return TERROR; case HANGUP: Syslog('+', "Zmodem: Lost Carrier"); return TERROR; case TERROR: /* CRC error */ if (--n < 0) { Syslog('+', "Zmodem: Too many errors"); return TERROR; } zmputs(Attn); continue; case TIMEOUT: if ( --n < 0) { Syslog('+', "Zmodem: TIMEOUT"); return TERROR; } continue; case GOTCRCW: n = 20; putsec(secbuf, Rxcount); rxbytes += Rxcount; stohdr(rxbytes); PUTCHAR(XON); zshhdr(ZACK, Txhdr); goto nxthdr; case GOTCRCQ: n = 20; putsec(secbuf, Rxcount); rxbytes += Rxcount; stohdr(rxbytes); zshhdr(ZACK, Txhdr); goto moredata; case GOTCRCG: n = 20; putsec(secbuf, Rxcount); rxbytes += Rxcount; goto moredata; case GOTCRCE: n = 20; putsec(secbuf, Rxcount); rxbytes += Rxcount; goto nxthdr; } } } } /* * Send a string to the modem, processing for \336 (sleep 1 sec) * and \335 (break signal) */ void zmputs(char *s) { int c; Syslog('z', "zmputs: \"%s\"", printable(s, strlen(s))); while (*s) { switch (c = *s++) { case '\336': Syslog('z', "zmputs: sleep(1)"); sleep(1); continue; case '\335': Syslog('z', "zmputs: send break"); sendbrk(); continue; default: PUTCHAR(c); } } } int closeit(int success) { struct utimbuf ut; int rc; Syslog('z', "closeit(%d)", success); if ((fout == NULL) || (curfile == NULL)) { Syslog('+', "closeit(), nothing to close"); return 1; } rc = fclose(fout); fout = NULL; if (rc == 0) { ut.actime = Modtime; ut.modtime = Modtime; if ((rc = utime(curfile, &ut))) WriteError("$utime failed"); } free(curfile); curfile = NULL; sbytes = rxbytes - sbytes; gettimeofday(&endtime, &tz); if (success) Syslog('+', "%s: OK %s", protname(), transfertime(starttime, endtime, sbytes, FALSE)); else Syslog('+', "%s: dropped after %lu bytes", protname(), sbytes); rcvdbytes += sbytes; return rc; } /* * Ack a ZFIN packet, let byegones be byegones */ int ackbibi(void) { int n; int c; Syslog('z', "ackbibi:"); stohdr(0L); for (n=3; --n>=0; ) { zshhdr(ZFIN, Txhdr); switch ((c = GETCHAR(10))) { case 'O': GETCHAR(1); /* Discard 2nd 'O' */ Syslog('z', "Zmodem: ackbibi complete"); return ZCOMPL; case TERROR: case HANGUP: Syslog('z', "Zmodem: ackbibi got %d, ignore",c); return 0; case TIMEOUT: default: Syslog('z', "Zmodem: ackbibi got '%s', continue", printablec(c)); break; } } return ZCOMPL; } /* * Process incoming file information header */ int procheader(char *Name) { register char *openmode, *p; static int dummy; char ctt[32]; Syslog('z', "procheader \"%s\"",printable(Name,0)); /* set default parameters and overrides */ openmode = (char *)"w"; /* * Check slashes in the name */ p = strrchr(Name,'/'); if (p) { p++; if (!*p) { /* alert - file name ended in with a / */ Syslog('!', "%s: file name ends with a /, skipped: %s", protname(), Name); return ZFERR; } Name = p; Syslog('z', "filename converted to \"%s\"", MBSE_SS(Name)); } if (strlen(Name) > 80) { Syslog('!', "%s: file name received is longer then 80 characters, skipped: %s", protname(), Name); return ZFERR; } Syslog('z', "zmanag=%d", zmanag); Syslog('z', "zconv=%d", zconv); /* * Process ZMODEM remote file management requests */ if (!Thisbinary && zconv == ZCNL) /* Remote ASCII override */ Thisbinary = FALSE; if (zconv == ZCBIN) /* Remote Binary override */ Thisbinary = TRUE; if (zmanag == ZMAPND) openmode = (char *)"a"; Syslog('z', "Thisbinary %s", Thisbinary ?"TRUE":"FALSE"); Bytesleft = DEFBYTL; Filemode = 0; Modtime = 0L; Eofseen = FALSE; p = Name + 1 + strlen(Name); if (*p) { /* file coming from Unix or DOS system */ sscanf(p, "%d%o%o%o%d%d%d%d", &Bytesleft, &Modtime, &Filemode, &dummy, &dummy, &dummy, &dummy, &dummy); strcpy(ctt, rfcdate(Modtime)); } else { Syslog('z', "File coming from a CP/M system"); } Syslog('+', "%s: \"%s\" %ld bytes, %s mode %o", protname(), Name, Bytesleft, ctt, Filemode); if (curfile) free(curfile); curfile = NULL; curfile = xstrcpy(CFG.bbs_usersdir); curfile = xstrcat(curfile, (char *)"/"); curfile = xstrcat(curfile, exitinfo.Name); curfile = xstrcat(curfile, (char *)"/upl/"); curfile = xstrcat(curfile, Name); Syslog('z', "try open %s mode \"%s\"", curfile, openmode); if ((fout = fopen(curfile, openmode)) == NULL) { WriteError("$Can't open %s mode %s", curfile, openmode); } gettimeofday(&starttime, &tz); sbytes = rxbytes = 0; Syslog('z', "result %s", fout ? "Ok":"Failed"); /* if (Bytesleft == rxbytes) { FIXME: if file already received, use this. Syslog('+', "Zmodem: Skipping %s", Name); fout = NULL; return ZSKIP; } else */ if (!fout) return ZFERR; else return 0; } /* * Putsec writes the n characters of buf to receive file fout. * If not in binary mode, carriage returns, and all characters * starting with CPMEOF are discarded. */ int putsec(char *buf, int n) { register char *p; if (n == 0) return OK; if (Thisbinary) { if (fwrite(buf, n, 1, fout) != 1) return ERROR; } else { if (Eofseen) return OK; for (p = buf; --n>=0; ++p ) { if ( *p == '\r') continue; if (*p == SUB) { Eofseen=TRUE; return OK; } putc(*p ,fout); } } return OK; } int getfree(void) { struct statfs sfs; char *temp; temp = calloc(PATH_MAX, sizeof(char)); snprintf(temp, PATH_MAX, "%s/%s/upl", CFG.bbs_usersdir, exitinfo.Name); if (statfs(temp, &sfs) != 0) { WriteError("$cannot statfs \"%s\", assume enough space", temp); free(temp); return -1L; } free(temp); return (sfs.f_bsize * sfs.f_bfree); }