/* lrz - receive files with x/y/zmodem Copyright (C) until 1988 Chuck Forsberg (Omen Technology INC) Copyright (C) 1994 Matt Porter, Michael D. Black Copyright (C) 1996, 1997 Uwe Ohse This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. originally written by Chuck Forsberg */ #include "zglobal.h" #define SS_NORMAL 0 #include #include #include #include #include #include #ifdef HAVE_UTIME_H #include #endif #include "timing.h" #include "long-options.h" #include "xstrtoul.h" #include "error.h" #ifndef STRICT_PROTOTYPES extern time_t time(); extern char *strerror(); extern char *strstr(); #endif #ifndef HAVE_ERRNO_DECLARATION extern int errno; #endif #define MAX_BLOCK 8192 /* * Max value for HOWMANY is 255 if NFGVMIN is not defined. * A larger value reduces system overhead but may evoke kernel bugs. * 133 corresponds to an XMODEM/CRC sector */ #ifndef HOWMANY #ifdef NFGVMIN #define HOWMANY MAX_BLOCK #else #define HOWMANY 255 #endif #endif unsigned Baudrate = 2400; FILE *fout; int Lastrx; int Crcflg; int Firstsec; int errors; int Restricted=1; /* restricted; no /.. or ../ in filenames */ int Readnum = HOWMANY; /* Number of bytes to ask for in read() from modem */ int skip_if_not_found; char *Pathname; const char *program_name; /* the name by which we were called */ int Topipe=0; int MakeLCPathname=TRUE; /* make received pathname lower case */ int Verbose=0; int Quiet=0; /* overrides logic that would otherwise set verbose */ int Nflag = 0; /* Don't really transfer files */ int Rxclob=FALSE; /* Clobber existing file */ int Rxbinary=FALSE; /* receive all files in bin mode */ int Rxascii=FALSE; /* receive files in ascii (translate) mode */ int Thisbinary; /* current file is to be received in bin mode */ int try_resume=FALSE; int allow_remote_commands=FALSE; int junk_path=FALSE; int no_timeout=FALSE; enum zm_type_enum protocol; int under_rsh=FALSE; int zmodem_requested=FALSE; #ifdef SEGMENTS int chinseg = 0; /* Number of characters received in this data seg */ char secbuf[1+(SEGMENTS+1)*MAX_BLOCK]; #else char secbuf[MAX_BLOCK + 1]; #endif #ifdef ENABLE_TIMESYNC int timesync_flag=0; int in_timesync=0; #endif int in_tcpsync=0; int tcpsync_flag=1; int tcp_socket=-1; int tcp_flag=0; char *tcp_server_address=NULL; char tcp_buf[256]=""; #if defined(F_GETFD) && defined(F_SETFD) && defined(O_SYNC) static int o_sync = 0; #endif static int rzfiles __P ((struct zm_fileinfo *)); static int tryz __P ((void)); static void checkpath __P ((const char *name)); static void chkinvok __P ((const char *s)); static void report __P ((int sct)); static void uncaps __P ((char *s)); static int IsAnyLower __P ((const char *s)); static int putsec __P ((struct zm_fileinfo *zi, char *buf, size_t n)); static int make_dirs __P ((char *pathname)); static int procheader __P ((char *name, struct zm_fileinfo *)); static int wcgetsec __P ((size_t *Blklen, char *rxbuf, unsigned int maxtime)); static int wcrx __P ((struct zm_fileinfo *)); static int wcrxpn __P ((struct zm_fileinfo *, char *rpn)); static int wcreceive __P ((int argc, char **argp)); static int rzfile __P ((struct zm_fileinfo *)); static void usage __P ((int exitcode, const char *what)); static void usage1 __P ((int exitcode)); static void exec2 __P ((const char *s)); static int closeit __P ((struct zm_fileinfo *)); static void ackbibi __P ((void)); static int sys2 __P ((const char *s)); static void zmputs __P ((const char *s)); static size_t getfree __P ((void)); static long buffersize=32768; static unsigned long min_bps=0; static long min_bps_time=120; char Lzmanag; /* Local file management request */ char zconv; /* ZMODEM file conversion request */ char zmanag; /* ZMODEM file management request */ char ztrans; /* ZMODEM file transport request */ int Zctlesc; /* Encode control characters */ int Zrwindow = 1400; /* RX window size (controls garbage count) */ int tryzhdrtype=ZRINIT; /* Header type to send corresponding to Last rx close */ time_t stop_time; #ifdef ENABLE_SYSLOG # if defined(ENABLE_SYSLOG_FORCE) || defined(ENABLE_SYSLOG_DEFAULT) int enable_syslog=TRUE; # else int enable_syslog=FALSE; # endif #define DO_SYSLOG_FNAME(message) do { \ if (enable_syslog) { \ const char *shortname; \ if (!zi->fname) \ shortname="no.name"; \ else { \ shortname=strrchr(zi->fname,'/'); \ if (!shortname) \ shortname=zi->fname; \ else \ shortname++; \ } \ lsyslog message ; \ } \ } while(0) #define DO_SYSLOG(message) do { \ if (enable_syslog) { \ lsyslog message ; \ } \ } while(0) #else #define DO_SYSLOG_FNAME(message) do { } while(0) #define DO_SYSLOG(message) do { } while(0) #endif /* called by signal interrupt or terminate to clean things up */ RETSIGTYPE bibi(int n) { if (zmodem_requested) zmputs(Attn); canit(STDOUT_FILENO); io_mode(0,0); error(128+n,0,_("caught signal %d; exiting"), n); } static struct option const long_options[] = { {"append", no_argument, NULL, '+'}, {"ascii", no_argument, NULL, 'a'}, {"binary", no_argument, NULL, 'b'}, {"bufsize", required_argument, NULL, 'B'}, {"allow-commands", no_argument, NULL, 'C'}, {"allow-remote-commands", no_argument, NULL, 'C'}, {"escape", no_argument, NULL, 'e'}, {"rename", no_argument, NULL, 'E'}, {"help", no_argument, NULL, 'h'}, {"crc-check", no_argument, NULL, 'H'}, {"junk-path", no_argument, NULL, 'j'}, {"errors", required_argument, NULL, 3}, {"disable-timeouts", no_argument, NULL, 'O'}, {"disable-timeout", no_argument, NULL, 'O'}, /* i can't get it right */ {"min-bps", required_argument, NULL, 'm'}, {"min-bps-time", required_argument, NULL, 'M'}, {"newer", no_argument, NULL, 'n'}, {"newer-or-longer", no_argument, NULL, 'N'}, {"protect", no_argument, NULL, 'p'}, {"resume", no_argument, NULL, 'r'}, {"restricted", no_argument, NULL, 'R'}, {"quiet", no_argument, NULL, 'q'}, {"stop-at", required_argument, NULL, 's'}, {"timesync", no_argument, NULL, 'S'}, {"timeout", required_argument, NULL, 't'}, {"keep-uppercase", no_argument, NULL, 'u'}, {"unrestrict", no_argument, NULL, 'U'}, {"verbose", no_argument, NULL, 'v'}, {"windowsize", required_argument, NULL, 'w'}, {"with-crc", no_argument, NULL, 'c'}, {"xmodem", no_argument, NULL, 'X'}, {"ymodem", no_argument, NULL, 1}, {"zmodem", no_argument, NULL, 'Z'}, {"overwrite", no_argument, NULL, 'y'}, {"null", no_argument, NULL, 'D'}, {"syslog", optional_argument, NULL , 2}, {"delay-startup", required_argument, NULL, 4}, {"o-sync", no_argument, NULL, 5}, {"o_sync", no_argument, NULL, 5}, {"tcp-server", no_argument, NULL, 6}, {"tcp-client", required_argument, NULL, 7}, {NULL,0,NULL,0} }; static void show_version(void) { printf ("%s (%s) %s\n", program_name, PACKAGE, VERSION); } int main(int argc, char *argv[]) { register char *cp; register int npats; char **patts=NULL; /* keep compiler quiet */ int exitcode=0; int c; unsigned int startup_delay=0; Rxtimeout = 100; setbuf(stderr, NULL); if ((cp=getenv("SHELL")) && (strstr(cp, "rsh") || strstr(cp, "rksh") || strstr(cp,"rbash") || strstr(cp, "rshell"))) under_rsh=TRUE; if ((cp=getenv("ZMODEM_RESTRICTED"))!=NULL) Restricted=2; /* make temporary and unfinished files */ umask(0077); from_cu(); chkinvok(argv[0]); /* if called as [-]rzCOMMAND set flag */ #ifdef ENABLE_SYSLOG openlog(program_name,LOG_PID,ENABLE_SYSLOG); #endif setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); parse_long_options (argc, argv, show_version, usage1); while ((c = getopt_long (argc, argv, "a+bB:cCDeEhm:M:OprRqs:St:uUvw:XZy", long_options, (int *) 0)) != EOF) { unsigned long int tmp; char *tmpptr; enum strtol_error s_err; switch (c) { case 0: break; case '+': Lzmanag = ZF1_ZMAPND; break; case 'a': Rxascii=TRUE; break; case 'b': Rxbinary=TRUE; break; case 'B': if (strcmp(optarg,"auto")==0) buffersize=-1; else buffersize=strtol(optarg,NULL,10); break; case 'c': Crcflg=TRUE; break; case 'C': allow_remote_commands=TRUE; break; case 'D': Nflag = TRUE; break; case 'E': Lzmanag = ZF1_ZMCHNG; break; case 'e': Zctlesc = 1; break; case 'h': usage(0,NULL); break; case 'H': Lzmanag= ZF1_ZMCRC; break; case 'j': junk_path=TRUE; break; case 'm': s_err = xstrtoul (optarg, &tmpptr, 0, &tmp, "km"); min_bps = tmp; if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("min_bps"), s_err); break; case 'M': s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL); min_bps_time = tmp; if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("min_bps_time"), s_err); if (min_bps_time<=1) usage(2,_("min_bps_time must be > 1")); break; case 'N': Lzmanag = ZF1_ZMNEWL; break; case 'n': Lzmanag = ZF1_ZMNEW; break; case 'O': no_timeout=TRUE; break; case 'p': Lzmanag = ZF1_ZMPROT; break; case 'q': Quiet=TRUE; Verbose=0; break; case 's': if (isdigit((unsigned char) (*optarg))) { struct tm *tm; time_t t; int hh,mm; char *nex; hh = strtoul (optarg, &nex, 10); if (hh>23) usage(2,_("hour to large (0..23)")); if (*nex!=':') usage(2, _("unparsable stop time\n")); nex++; mm = strtoul (optarg, &nex, 10); if (mm>59) usage(2,_("minute to large (0..59)")); t=time(NULL); tm=localtime(&t); tm->tm_hour=hh; tm->tm_min=hh; stop_time=mktime(tm); if (stop_time1000) usage(2,_("timeout out of range 10..1000")); break; case 'w': s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL); Zrwindow = tmp; if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("window size"), s_err); break; case 'u': MakeLCPathname=FALSE; break; case 'U': if (!under_rsh) Restricted=0; else { DO_SYSLOG((LOG_INFO,"--unrestrict option used under restricted shell")); error(1,0, _("security violation: can't do that under restricted shell\n")); } break; case 'v': ++Verbose; break; case 'X': protocol=ZM_XMODEM; break; case 1: protocol=ZM_YMODEM; break; case 'Z': protocol=ZM_ZMODEM; break; case 'y': Rxclob=TRUE; break; case 2: #ifdef ENABLE_SYSLOG # ifndef ENABLE_SYSLOG_FORCE if (optarg && (!strcmp(optarg,"off") || !strcmp(optarg,"no"))) { if (under_rsh) error(0,0, _("cannot turnoff syslog")); else enable_syslog=FALSE; } else enable_syslog=TRUE; # else error(0,0, _("cannot turnoff syslog")); # endif #endif case 3: s_err = xstrtoul (optarg, NULL, 0, &tmp, "km"); bytes_per_error = tmp; if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("bytes_per_error"), s_err); if (bytes_per_error<100) usage(2,_("bytes-per-error should be >100")); break; case 4: s_err = xstrtoul (optarg, NULL, 0, &tmp, NULL); startup_delay = tmp; if (s_err != LONGINT_OK) STRTOL_FATAL_ERROR (optarg, _("startup delay"), s_err); break; case 5: #if defined(F_GETFD) && defined(F_SETFD) && defined(O_SYNC) o_sync=1; #else error(0,0, _("O_SYNC not supported by the kernel")); #endif break; case 6: tcp_flag=2; break; case 7: tcp_flag=3; tcp_server_address=(char *)strdup(optarg); if (!tcp_server_address) error(1,0,_("out of memory")); break; default: usage(2,NULL); } } if (getuid()!=geteuid()) { error(1,0, _("this program was never intended to be used setuid\n")); } /* initialize zsendline tab */ zsendline_init(); #ifdef HAVE_SIGINTERRUPT siginterrupt(SIGALRM,1); #endif if (startup_delay) sleep(startup_delay); npats = argc - optind; patts=&argv[optind]; if (npats > 1) usage(2,_("garbage on commandline")); if (protocol!=ZM_XMODEM && npats) usage(2, _("garbage on commandline")); if (protocol==ZM_XMODEM && !npats) usage(2, _("need a file name to receive")); if (Restricted && allow_remote_commands) { allow_remote_commands=FALSE; } if (Fromcu && !Quiet) { if (Verbose == 0) Verbose = 2; } vfile("%s %s\n", program_name, VERSION); if (tcp_flag==2) { char buf[256]; #ifdef MAXHOSTNAMELEN char hn[MAXHOSTNAMELEN]; #else char hn[256]; #endif char *p,*q; int d; /* tell receiver to receive via tcp */ d=tcp_server(buf); p=strchr(buf+1,'<'); p++; q=strchr(p,'>'); *q=0; if (gethostname(hn,sizeof(hn))==-1) { error(1,0, _("hostname too long\n")); } fprintf(stdout,"connect with lrz --tcp-client \"%s:%s\"\n",hn,p); fflush(stdout); /* ok, now that this file is sent we can switch to tcp */ tcp_socket=tcp_accept(d); dup2(tcp_socket,0); dup2(tcp_socket,1); } if (tcp_flag==3) { char buf[256]; char *p; p=strchr(tcp_server_address,':'); if (!p) error(1,0, _("illegal server address\n")); *p++=0; sprintf(buf,"[%s] <%s>\n",tcp_server_address,p); fprintf(stdout,"connecting to %s\n",buf); fflush(stdout); /* we need to switch to tcp mode */ tcp_socket=tcp_connect(buf); dup2(tcp_socket,0); dup2(tcp_socket,1); } io_mode(0,1); readline_setup(0, HOWMANY, MAX_BLOCK*2); if (signal(SIGINT, bibi) == SIG_IGN) signal(SIGINT, SIG_IGN); else signal(SIGINT, bibi); signal(SIGTERM, bibi); signal(SIGPIPE, bibi); if (wcreceive(npats, patts)==ERROR) { exitcode=0200; canit(STDOUT_FILENO); } io_mode(0,0); if (exitcode && !zmodem_requested) /* bellow again with all thy might. */ canit(STDOUT_FILENO); if (Verbose) { fputs("\r\n",stderr); if (exitcode) fputs(_("Transfer incomplete\n"),stderr); else fputs(_("Transfer complete\n"),stderr); } exit(exitcode); } static void usage1(int exitcode) { usage(exitcode,NULL); } static void usage(int exitcode, const char *what) { FILE *f=stdout; if (exitcode) { if (what) fprintf(stderr, "%s: %s\n",program_name,what); fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); exit(exitcode); } fprintf(f, _("%s version %s\n"), program_name, VERSION); fprintf(f,_("Usage: %s [options] [filename.if.xmodem]\n"), program_name); fputs(_("Receive files with ZMODEM/YMODEM/XMODEM protocol\n"),f); fputs(_( " (X) = option applies to XMODEM only\n" " (Y) = option applies to YMODEM only\n" " (Z) = option applies to ZMODEM only\n" ),f); fputs(_( " -+, --append append to existing files\n" " -a, --ascii ASCII transfer (change CR/LF to LF)\n" " -b, --binary binary transfer\n" " -B, --bufsize N buffer N bytes (N==auto: buffer whole file)\n" " -c, --with-crc Use 16 bit CRC (X)\n" " -C, --allow-remote-commands allow execution of remote commands (Z)\n" " -D, --null write all received data to /dev/null\n" " --delay-startup N sleep N seconds before doing anything\n" " -e, --escape Escape control characters (Z)\n" " -E, --rename rename any files already existing\n" " --errors N generate CRC error every N bytes (debugging)\n" " -h, --help Help, print this usage message\n" " -m, --min-bps N stop transmission if BPS below N\n" " -M, --min-bps-time N for at least N seconds (default: 120)\n" " -O, --disable-timeouts disable timeout code, wait forever for data\n" " --o-sync open output file(s) in synchronous write mode\n" " -p, --protect protect existing files\n" " -q, --quiet quiet, no progress reports\n" " -r, --resume try to resume interrupted file transfer (Z)\n" " -R, --restricted restricted, more secure mode\n" " -s, --stop-at {HH:MM|+N} stop transmission at HH:MM or in N seconds\n" " -S, --timesync request remote time (twice: set local time)\n" " --syslog[=off] turn syslog on or off, if possible\n" " -t, --timeout N set timeout to N tenths of a second\n" " -u, --keep-uppercase keep upper case filenames\n" " -U, --unrestrict disable restricted mode (if allowed to)\n" " -v, --verbose be verbose, provide debugging information\n" " -w, --windowsize N Window is N bytes (Z)\n" " -X --xmodem use XMODEM protocol\n" " -y, --overwrite Yes, clobber existing file if any\n" " --ymodem use YMODEM protocol\n" " -Z, --zmodem use ZMODEM protocol\n" "\n" "short options use the same arguments as the long ones\n" ),f); exit(exitcode); } /* * Let's receive something already. */ static int wcreceive(int argc, char **argp) { int c; struct zm_fileinfo zi; #ifdef ENABLE_SYSLOG const char *shortname=NULL;; #endif zi.fname=NULL; zi.modtime=0; zi.mode=0; zi.bytes_total=0; zi.bytes_sent=0; zi.bytes_received=0; zi.bytes_skipped=0; zi.eof_seen=0; if (protocol!=ZM_XMODEM || argc==0) { Crcflg=1; if ( !Quiet) vstringf(_("%s waiting to receive."), program_name); if ((c=tryz())!=0) { if (c == ZCOMPL) return OK; if (c == ERROR) goto fubar; c = rzfiles(&zi); #ifdef ENABLE_SYSLOG shortname=NULL; #endif if (c) goto fubar; } else { for (;;) { if (Verbose > 1 #ifdef ENABLE_SYSLOG || enable_syslog #endif ) timing(1,NULL); #ifdef ENABLE_SYSLOG shortname=NULL; #endif if (wcrxpn(&zi,secbuf)== ERROR) goto fubar; if (secbuf[0]==0) return OK; if (procheader(secbuf, &zi) == ERROR) goto fubar; #ifdef ENABLE_SYSLOG shortname=strrchr(zi.fname,'/'); if (shortname) shortname++; else shortname=zi.fname; #endif if (wcrx(&zi)==ERROR) goto fubar; if (Verbose > 1 #ifdef ENABLE_SYSLOG || enable_syslog #endif ) { double d; long bps; d=timing(0,NULL); if (d==0) d=0.5; /* can happen if timing uses time() */ bps=(zi.bytes_received-zi.bytes_skipped)/d; if (Verbose>1) { vstringf( _("\rBytes received: %7ld/%7ld BPS:%-6ld \r\n"), (long) zi.bytes_received, (long) zi.bytes_total, bps); } #ifdef ENABLE_SYSLOG if (enable_syslog) lsyslog(LOG_INFO,"%s/%s: %ld Bytes, %ld BPS", shortname,protname(),zi.bytes_received, bps); #endif } } } } else { char dummy[128]; dummy[0]='\0'; /* pre-ANSI HPUX cc demands this */ dummy[1]='\0'; /* procheader uses name + 1 + strlen(name) */ zi.bytes_total = DEFBYTL; if (Verbose > 1 #ifdef ENABLE_SYSLOG || enable_syslog #endif ) timing(1,NULL); procheader(dummy, &zi); if (Pathname) free(Pathname); errno=0; Pathname=malloc(PATH_MAX+1); if (!Pathname) error(1,0,_("out of memory")); strcpy(Pathname, *argp); checkpath(Pathname); #ifdef ENABLE_SYSLOG shortname=strrchr(*argp,'/'); if (shortname) shortname++; else shortname=*argp; #endif vchar('\n'); vstringf(_("%s: ready to receive %s"), program_name, Pathname); vstring("\r\n"); if ((fout=fopen(Pathname, "w")) == NULL) { #ifdef ENABLE_SYSLOG if (enable_syslog) lsyslog(LOG_ERR,"%s/%s: cannot open: %m", shortname,protname()); #endif return ERROR; } if (wcrx(&zi)==ERROR) { goto fubar; } if (Verbose > 1 #ifdef ENABLE_SYSLOG || enable_syslog #endif ) { double d; long bps; d=timing(0,NULL); if (d==0) d=0.5; /* can happen if timing uses time() */ bps=(zi.bytes_received-zi.bytes_skipped)/d; if (Verbose) { vstringf( _("\rBytes received: %7ld BPS:%-6ld \r\n"), (long) zi.bytes_received, bps); } #ifdef ENABLE_SYSLOG if (enable_syslog) lsyslog(LOG_INFO,"%s/%s: %ld Bytes, %ld BPS", shortname,protname(),zi.bytes_received, bps); #endif } } return OK; fubar: #ifdef ENABLE_SYSLOG if (enable_syslog) lsyslog(LOG_ERR,"%s/%s: got error", shortname ? shortname : "no.name", protname()); #endif canit(STDOUT_FILENO); if (Topipe && fout) { pclose(fout); return ERROR; } if (fout) fclose(fout); if (Restricted && Pathname) { unlink(Pathname); vstringf(_("\r\n%s: %s removed.\r\n"), program_name, Pathname); } return ERROR; } /* * Fetch a pathname from the other end as a C ctyle ASCIZ string. * Length is indeterminate as long as less than Blklen * A null string represents no more files (YMODEM) */ static int wcrxpn(struct zm_fileinfo *zi, char *rpn) { register int c; size_t Blklen=0; /* record length of received packets */ #ifdef NFGVMIN READLINE_PF(1); #else purgeline(0); #endif et_tu: Firstsec=TRUE; zi->eof_seen=FALSE; sendline(Crcflg?WANTCRC:NAK); flushmo(); purgeline(0); /* Do read next time ... */ while ((c = wcgetsec(&Blklen, rpn, 100)) != 0) { if (c == WCEOT) { zperr( _("Pathname fetch returned EOT")); sendline(ACK); flushmo(); purgeline(0); /* Do read next time ... */ READLINE_PF(1); goto et_tu; } return ERROR; } sendline(ACK); flushmo(); return OK; } /* * Adapted from CMODEM13.C, written by * Jack M. Wierda and Roderick W. Hart */ static int wcrx(struct zm_fileinfo *zi) { register int sectnum, sectcurr; register char sendchar; size_t Blklen; Firstsec=TRUE;sectnum=0; zi->eof_seen=FALSE; sendchar=Crcflg?WANTCRC:NAK; for (;;) { sendline(sendchar); /* send it now, we're ready! */ flushmo(); purgeline(0); /* Do read next time ... */ sectcurr=wcgetsec(&Blklen, secbuf, (unsigned int) ((sectnum&0177) ? 50 : 130)); report(sectcurr); if (sectcurr==((sectnum+1) &0377)) { sectnum++; /* if using xmodem we don't know how long a file is */ if (zi->bytes_total && R_BYTESLEFT(zi) < Blklen) Blklen=R_BYTESLEFT(zi); zi->bytes_received+=Blklen; if (putsec(zi, secbuf, Blklen)==ERROR) return ERROR; sendchar=ACK; } else if (sectcurr==(sectnum&0377)) { zperr( _("Received dup Sector")); sendchar=ACK; } else if (sectcurr==WCEOT) { if (closeit(zi)) return ERROR; sendline(ACK); flushmo(); purgeline(0); /* Do read next time ... */ return OK; } else if (sectcurr==ERROR) return ERROR; else { zperr( _("Sync Error")); return ERROR; } } } /* * Wcgetsec fetches a Ward Christensen type sector. * Returns sector number encountered or ERROR if valid sector not received, * or CAN CAN received * or WCEOT if eot sector * time is timeout for first char, set to 4 seconds thereafter ***************** NO ACK IS SENT IF SECTOR IS RECEIVED OK ************** * (Caller must do that when he is good and ready to get next sector) */ static int wcgetsec(size_t *Blklen, char *rxbuf, unsigned int maxtime) { register int checksum, wcj, firstch; register unsigned short oldcrc; register char *p; int sectcurr; for (Lastrx=errors=0; errors=0; ) { if ((firstch=READLINE_PF(1)) < 0) goto bilge; oldcrc=updcrc(firstch, oldcrc); checksum += (*p++ = firstch); } if ((firstch=READLINE_PF(1)) < 0) goto bilge; if (Crcflg) { oldcrc=updcrc(firstch, oldcrc); if ((firstch=READLINE_PF(1)) < 0) goto bilge; oldcrc=updcrc(firstch, oldcrc); if (oldcrc & 0xFFFF) zperr( _("CRC")); else { Firstsec=FALSE; return sectcurr; } } else if (((checksum-firstch)&0377)==0) { Firstsec=FALSE; return sectcurr; } else zperr( _("Checksum")); } else zperr(_("Sector number garbled")); } /* make sure eot really is eot and not just mixmash */ #ifdef NFGVMIN else if (firstch==EOT && READLINE_PF(1)==TIMEOUT) return WCEOT; #else else if (firstch==EOT && READLINE_PF>0) return WCEOT; #endif else if (firstch==CAN) { if (Lastrx==CAN) { zperr( _("Sender Cancelled")); return ERROR; } else { Lastrx=CAN; continue; } } else if (firstch==TIMEOUT) { if (Firstsec) goto humbug; bilge: zperr( _("TIMEOUT")); } else zperr( _("Got 0%o sector header"), firstch); humbug: Lastrx=0; { int cnt=1000; while(cnt-- && READLINE_PF(1)!=TIMEOUT) ; } if (Firstsec) { sendline(Crcflg?WANTCRC:NAK); flushmo(); purgeline(0); /* Do read next time ... */ } else { maxtime=40; sendline(NAK); flushmo(); purgeline(0); /* Do read next time ... */ } } /* try to stop the bubble machine. */ canit(STDOUT_FILENO); return ERROR; } #define ZCRC_DIFFERS (ERROR+1) #define ZCRC_EQUAL (ERROR+2) /* * do ZCRC-Check for open file f. * check at most check_bytes bytes (crash recovery). if 0 -> whole file. * remote file size is remote_bytes. */ static int do_crc_check(FILE *f, size_t remote_bytes, size_t check_bytes) { struct stat st; unsigned long crc; unsigned long rcrc; size_t n; int c; int t1=0,t2=0; if (-1==fstat(fileno(f),&st)) { DO_SYSLOG((LOG_ERR,"cannot fstat open file: %s",strerror(errno))); return ERROR; } if (check_bytes==0 && ((size_t) st.st_size)!=remote_bytes) return ZCRC_DIFFERS; /* shortcut */ crc=0xFFFFFFFFL; n=check_bytes; if (n==0) n=st.st_size; while (n-- && ((c = getc(f)) != EOF)) crc = UPDC32(c, crc); crc = ~crc; clearerr(f); /* Clear EOF */ fseek(f, 0L, 0); while (t1<3) { stohdr(check_bytes); zshhdr(ZCRC, Txhdr); while(t2<3) { size_t tmp; c = zgethdr(Rxhdr, 0, &tmp); rcrc=(unsigned long) tmp; switch (c) { default: /* ignore */ break; case ZFIN: return ERROR; case ZRINIT: return ERROR; case ZCAN: if (Verbose) vstringf(_("got ZCAN")); return ERROR; break; case ZCRC: if (crc!=rcrc) return ZCRC_DIFFERS; return ZCRC_EQUAL; break; } } } return ERROR; } /* * Process incoming file information header */ static int procheader(char *name, struct zm_fileinfo *zi) { const char *openmode; char *p; static char *name_static=NULL; char *nameend; if (name_static) free(name_static); if (junk_path) { p=strrchr(name,'/'); if (p) { p++; if (!*p) { /* alert - file name ended in with a / */ if (Verbose) vstringf(_("file name ends with a /, skipped: %s\n"),name); DO_SYSLOG((LOG_ERR,"file name ends with a /, skipped: %s", name)); return ERROR; } name=p; } } name_static=malloc(strlen(name)+1); if (!name_static) error(1,0,_("out of memory")); strcpy(name_static,name); zi->fname=name_static; if (Verbose>2) { vstringf(_("zmanag=%d, Lzmanag=%d\n"),zmanag,Lzmanag); vstringf(_("zconv=%d\n"),zconv); } /* set default parameters and overrides */ openmode = "w"; Thisbinary = (!Rxascii) || Rxbinary; if (Lzmanag) zmanag = Lzmanag; /* * Process ZMODEM remote file management requests */ if (!Rxbinary && zconv == ZCNL) /* Remote ASCII override */ Thisbinary = 0; if (zconv == ZCBIN) /* Remote Binary override */ Thisbinary = TRUE; if (Thisbinary && zconv == ZCBIN && try_resume) zconv=ZCRESUM; if (zmanag == ZF1_ZMAPND && zconv!=ZCRESUM) openmode = "a"; if (skip_if_not_found) openmode="r+"; #ifdef ENABLE_TIMESYNC in_timesync=0; if (timesync_flag && 0==strcmp(name,"$time$.t")) in_timesync=1; #endif in_tcpsync=0; if (tcpsync_flag && 0==strcmp(name,"$tcp$.t")) in_tcpsync=1; zi->bytes_total = DEFBYTL; zi->mode = 0; zi->eof_seen = 0; zi->modtime = 0; nameend = name + 1 + strlen(name); if (*nameend) { /* file coming from Unix or DOS system */ long modtime=0; long bytes_total=DEFBYTL; int mode=0; sscanf(nameend, "%ld%lo%o", &bytes_total, &modtime, &mode); zi->modtime=modtime; zi->bytes_total=bytes_total; zi->mode=mode; if (zi->mode & UNIXFILE) ++Thisbinary; } /* Check for existing file */ if (zconv != ZCRESUM && !Rxclob && (zmanag&ZF1_ZMMASK) != ZF1_ZMCLOB && (zmanag&ZF1_ZMMASK) != ZF1_ZMAPND #ifdef ENABLE_TIMESYNC && !in_timesync && !in_tcpsync #endif && (fout=fopen(name, "r"))) { struct stat sta; char *tmpname; char *ptr; int i; if (zmanag == ZF1_ZMNEW || zmanag==ZF1_ZMNEWL) { if (-1==fstat(fileno(fout),&sta)) { #ifdef ENABLE_SYSLOG int e=errno; #endif if (Verbose) vstringf(_("file exists, skipped: %s\n"),name); DO_SYSLOG((LOG_ERR,"cannot fstat open file %s: %s", name,strerror(e))); return ERROR; } if (zmanag == ZF1_ZMNEW) { if (sta.st_mtime > zi->modtime) { DO_SYSLOG((LOG_INFO,"skipping %s: newer file exists", name)); return ERROR; /* skips file */ } } else { /* newer-or-longer */ if (((size_t) sta.st_size) >= zi->bytes_total && sta.st_mtime > zi->modtime) { DO_SYSLOG((LOG_INFO,"skipping %s: longer+newer file exists", name)); return ERROR; /* skips file */ } } fclose(fout); } else if (zmanag==ZF1_ZMCRC) { int r=do_crc_check(fout,zi->bytes_total,0); if (r==ERROR) { fclose(fout); return ERROR; } if (r!=ZCRC_DIFFERS) { return ERROR; /* skips */ } fclose(fout); } else { size_t namelen; fclose(fout); if ((zmanag & ZF1_ZMMASK)!=ZF1_ZMCHNG) { if (Verbose) vstringf(_("file exists, skipped: %s\n"),name); return ERROR; } /* try to rename */ namelen=strlen(name); tmpname=alloca(namelen+5); memcpy(tmpname,name,namelen); ptr=tmpname+namelen; *ptr++='.'; i=0; do { sprintf(ptr,"%d",i++); } while (i<1000 && stat(tmpname,&sta)==0); if (i==1000) return ERROR; free(name_static); name_static=malloc(strlen(tmpname)+1); if (!name_static) error(1,0,_("out of memory")); strcpy(name_static,tmpname); zi->fname=name_static; } } if (!*nameend) { /* File coming from CP/M system */ for (p=name_static; *p; ++p) /* change / to _ */ if ( *p == '/') *p = '_'; if ( *--p == '.') /* zap trailing period */ *p = 0; } #ifdef ENABLE_TIMESYNC if (in_timesync) { long t=time(0); long d=t-zi->modtime; if (d<0) d=0; if ((Verbose && d>60) || Verbose > 1) vstringf(_("TIMESYNC: here %ld, remote %ld, diff %ld seconds\n"), (long) t, (long) zi->modtime, d); #ifdef HAVE_SETTIMEOFDAY if (timesync_flag > 1 && d > 10) { struct timeval tv; tv.tv_sec=zi->modtime; tv.tv_usec=0; if (settimeofday(&tv,NULL)) vstringf(_("TIMESYNC: cannot set time: %s\n"), strerror(errno)); } #endif return ERROR; /* skips file */ } #endif /* ENABLE_TIMESYNC */ if (in_tcpsync) { fout=tmpfile(); if (!fout) { error(1,errno,_("cannot tmpfile() for tcp protocol synchronization")); } zi->bytes_received=0; return OK; } if (!zmodem_requested && MakeLCPathname && !IsAnyLower(name_static) && !(zi->mode&UNIXFILE)) uncaps(name_static); if (Topipe > 0) { if (Pathname) free(Pathname); Pathname=malloc((PATH_MAX)*2); if (!Pathname) error(1,0,_("out of memory")); sprintf(Pathname, "%s %s", program_name+2, name_static); if (Verbose) { vstringf("%s: %s %s\n", _("Topipe"), Pathname, Thisbinary?"BIN":"ASCII"); } if ((fout=popen(Pathname, "w")) == NULL) return ERROR; } else { if (protocol==ZM_XMODEM) /* we don't have the filename yet */ return OK; /* dummy */ if (Pathname) free(Pathname); Pathname=malloc((PATH_MAX)*2); if (!Pathname) error(1,0,_("out of memory")); strcpy(Pathname, name_static); if (Verbose) { /* overwrite the "waiting to receive" line */ vstring("\r \r"); vstringf(_("Receiving: %s\n"), name_static); } checkpath(name_static); if (Nflag) { /* cast because we might not have a prototyp for strdup :-/ */ free(name_static); name_static=(char *) strdup("/dev/null"); if (!name_static) { fprintf(stderr,"%s: %s\n", program_name, _("out of memory")); exit(1); } } #ifdef OMEN /* looks like a security hole -- uwe */ if (name_static[0] == '!' || name_static[0] == '|') { if ( !(fout = popen(name_static+1, "w"))) { return ERROR; } Topipe = -1; return(OK); } #endif if (Thisbinary && zconv==ZCRESUM) { struct stat st; fout = fopen(name_static, "r+"); if (fout && 0==fstat(fileno(fout),&st)) { int can_resume=TRUE; if (zmanag==ZF1_ZMCRC) { int r=do_crc_check(fout,zi->bytes_total,st.st_size); if (r==ERROR) { fclose(fout); return ZFERR; } if (r==ZCRC_DIFFERS) { can_resume=FALSE; } } if ((unsigned long)st.st_size > zi->bytes_total) { can_resume=FALSE; } /* retransfer whole blocks */ zi->bytes_skipped = st.st_size & ~(1023); if (can_resume) { if (fseek(fout, (long) zi->bytes_skipped, SEEK_SET)) { fclose(fout); return ZFERR; } } else zi->bytes_skipped=0; /* resume impossible, file has changed */ goto buffer_it; } zi->bytes_skipped=0; if (fout) fclose(fout); } fout = fopen(name_static, openmode); #ifdef ENABLE_MKDIR if ( !fout && Restricted < 2) { if (make_dirs(name_static)) fout = fopen(name_static, openmode); } #endif if ( !fout) { #ifdef ENABLE_SYSLOG int e=errno; #endif zpfatal(_("cannot open %s"), name_static); DO_SYSLOG((LOG_ERR,"%s: cannot open: %s", protname(),strerror(e))); return ERROR; } } buffer_it: if (Topipe == 0) { static char *s=NULL; static size_t last_length=0; #if defined(F_GETFD) && defined(F_SETFD) && defined(O_SYNC) if (o_sync) { int oldflags; oldflags = fcntl (fileno(fout), F_GETFD, 0); if (oldflags>=0 && !(oldflags & O_SYNC)) { oldflags|=O_SYNC; fcntl (fileno(fout), F_SETFD, oldflags); /* errors don't matter */ } } #endif if (buffersize==-1 && s) { if (zi->bytes_total>last_length) { free(s); s=NULL; last_length=0; } } if (!s && buffersize) { last_length=32768; if (buffersize==-1) { if (zi->bytes_total>0) last_length=zi->bytes_total; } else last_length=buffersize; /* buffer `4096' bytes pages */ last_length=(last_length+4095)&0xfffff000; s=malloc(last_length); if (!s) { zpfatal(_("out of memory")); exit(1); } } if (s) { #ifdef SETVBUF_REVERSED setvbuf(fout,_IOFBF,s,last_length); #else setvbuf(fout,s,_IOFBF,last_length); #endif } } zi->bytes_received=zi->bytes_skipped; return OK; } #ifdef ENABLE_MKDIR /* * Directory-creating routines from Public Domain TAR by John Gilmore */ /* * After a file/link/symlink/dir creation has failed, see if * it's because some required directory was not present, and if * so, create all required dirs. */ static int make_dirs(char *pathname) { register char *p; /* Points into path */ int madeone = 0; /* Did we do anything yet? */ int save_errno = errno; /* Remember caller's errno */ if (errno != ENOENT) return 0; /* Not our problem */ for (p = strchr(pathname, '/'); p != NULL; p = strchr(p+1, '/')) { /* Avoid mkdir of empty string, if leading or double '/' */ if (p == pathname || p[-1] == '/') continue; /* Avoid mkdir where last part of path is '.' */ if (p[-1] == '.' && (p == pathname+1 || p[-2] == '/')) continue; *p = 0; /* Truncate the path there */ if ( !mkdir(pathname, 0777)) { /* Try to create it as a dir */ vfile("Made directory %s\n", pathname); madeone++; /* Remember if we made one */ *p = '/'; continue; } *p = '/'; if (errno == EEXIST) /* Directory already exists */ continue; /* * Some other error in the mkdir. We return to the caller. */ break; } errno = save_errno; /* Restore caller's errno */ return madeone; /* Tell them to retry if we made one */ } #endif /* ENABLE_MKDIR */ /* * 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. */ static int putsec(struct zm_fileinfo *zi, char *buf, size_t n) { register char *p; if (n == 0) return OK; if (Thisbinary) { if (fwrite(buf,n,1,fout)!=1) return ERROR; } else { if (zi->eof_seen) return OK; for (p=buf; n>0; ++p,n-- ) { if ( *p == '\r') continue; if (*p == CPMEOF) { zi->eof_seen=TRUE; return OK; } putc(*p ,fout); } } return OK; } /* make string s lower case */ static void uncaps(char *s) { for ( ; *s; ++s) if (isupper((unsigned char)(*s))) *s = tolower(*s); } /* * IsAnyLower returns TRUE if string s has lower case letters. */ static int IsAnyLower(const char *s) { for ( ; *s; ++s) if (islower((unsigned char)(*s))) return TRUE; return FALSE; } static void report(int sct) { if (Verbose>1) { vstringf(_("Blocks received: %d"),sct); vchar('\r'); } } /* * If called as [-][dir/../]vrzCOMMAND set Verbose to 1 * If called as [-][dir/../]rzCOMMAND set the pipe flag * If called as rb use YMODEM protocol */ static void chkinvok(const char *s) { const char *p; p = s; while (*p == '-') s = ++p; while (*p) if (*p++ == '/') s = p; if (*s == 'v') { Verbose=1; ++s; } program_name = s; if (*s == 'l') s++; /* lrz -> rz */ protocol=ZM_ZMODEM; if (s[0]=='r' && s[1]=='x') protocol=ZM_XMODEM; if (s[0]=='r' && (s[1]=='b' || s[1]=='y')) protocol=ZM_YMODEM; if (s[2] && protocol!=ZM_XMODEM) Topipe = 1; } /* * Totalitarian Communist pathname processing */ static void checkpath(const char *name) { if (Restricted) { const char *p; p=strrchr(name,'/'); if (p) p++; else p=name; /* don't overwrite any file in very restricted mode. * don't overwrite hidden files in restricted mode */ if ((Restricted==2 || *name=='.') && fopen(name, "r") != NULL) { canit(STDOUT_FILENO); vstring("\r\n"); vstringf(_("%s: %s exists\n"), program_name, name); bibi(-1); } /* restrict pathnames to current tree or uucppublic */ if ( strstr(name, "../") #ifdef PUBDIR || (name[0]== '/' && strncmp(name, PUBDIR, strlen(PUBDIR))) #endif ) { canit(STDOUT_FILENO); vstring("\r\n"); vstringf(_("%s:\tSecurity Violation"),program_name); vstring("\r\n"); bibi(-1); } if (Restricted > 1) { if (name[0]=='.' || strstr(name,"/.")) { canit(STDOUT_FILENO); vstring("\r\n"); vstringf(_("%s:\tSecurity Violation"),program_name); vstring("\r\n"); bibi(-1); } } } } /* * 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 */ static int tryz(void) { register int c, n; register int cmdzack1flg; int zrqinits_received=0; size_t bytes_in_block=0; if (protocol!=ZM_ZMODEM) /* Check for "rb" program name */ return 0; for (n=zmodem_requested?15:5; (--n + zrqinits_received) >=0 && zrqinits_received<10; ) { /* Set buffer length (0) and capability flags */ #ifdef SEGMENTS stohdr(SEGMENTS*MAX_BLOCK); #else stohdr(0L); #endif #ifdef CANBREAK Txhdr[ZF0] = CANFC32|CANFDX|CANOVIO|CANBRK; #else Txhdr[ZF0] = CANFC32|CANFDX|CANOVIO; #endif #ifdef ENABLE_TIMESYNC if (timesync_flag) Txhdr[ZF1] |= ZF1_TIMESYNC; #endif if (Zctlesc) Txhdr[ZF0] |= TESCCTL; /* TESCCTL == ESCCTL */ zshhdr(tryzhdrtype, Txhdr); if (tcp_socket==-1 && *tcp_buf) { /* we need to switch to tcp mode */ tcp_socket=tcp_connect(tcp_buf); tcp_buf[0]=0; dup2(tcp_socket,0); dup2(tcp_socket,1); } if (tryzhdrtype == ZSKIP) /* Don't skip too far */ tryzhdrtype = ZRINIT; /* CAF 8-21-87 */ again: switch (zgethdr(Rxhdr, 0, NULL)) { case ZRQINIT: /* getting one ZRQINIT is totally ok. Normally a ZFILE follows * (and might be in our buffer, so don't purge it). But if we * get more ZRQINITs than the sender has started up before us * and sent ZRQINITs while waiting. */ zrqinits_received++; continue; case ZEOF: continue; case TIMEOUT: continue; case ZFILE: zconv = Rxhdr[ZF0]; if (!zconv) /* resume with sz -r is impossible (at least with unix sz) * if this is not set */ zconv=ZCBIN; if (Rxhdr[ZF1] & ZF1_ZMSKNOLOC) { Rxhdr[ZF1] &= ~(ZF1_ZMSKNOLOC); skip_if_not_found=TRUE; } zmanag = Rxhdr[ZF1]; ztrans = Rxhdr[ZF2]; tryzhdrtype = ZRINIT; c = zrdata(secbuf, MAX_BLOCK,&bytes_in_block); io_mode(0,3); if (c == GOTCRCW) 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,&bytes_in_block) == 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, MAX_BLOCK,&bytes_in_block) == GOTCRCW) { if (Verbose) { vstringf("%s: %s\n", program_name, _("remote command execution requested")); vstringf("%s: %s\n", program_name, secbuf); } if (!allow_remote_commands) { if (Verbose) vstringf("%s: %s\n", program_name, _("not executed")); zshhdr(ZCOMPL, Txhdr); DO_SYSLOG((LOG_INFO,"rexec denied: %s",secbuf)); return ZCOMPL; } DO_SYSLOG((LOG_INFO,"rexec allowed: %s",secbuf)); if (cmdzack1flg & ZCACK1) stohdr(0L); else stohdr((size_t)sys2(secbuf)); purgeline(0); /* dump impatient questions */ do { zshhdr(ZCOMPL, Txhdr); } while (++errors<20 && zgethdr(Rxhdr,1, NULL) != ZFIN); ackbibi(); if (cmdzack1flg & ZCACK1) exec2(secbuf); return ZCOMPL; } zshhdr(ZNAK, Txhdr); goto again; case ZCOMPL: goto again; default: continue; case ZFIN: ackbibi(); return ZCOMPL; case ZRINIT: if (Verbose) vstringf(_("got ZRINIT")); return ERROR; case ZCAN: if (Verbose) vstringf(_("got ZCAN")); return ERROR; } } return 0; } /* * Receive 1 or more files with ZMODEM protocol */ static int rzfiles(struct zm_fileinfo *zi) { register int c; for (;;) { timing(1,NULL); c = rzfile(zi); switch (c) { case ZEOF: if (Verbose > 1 #ifdef ENABLE_SYSLOG || enable_syslog #endif ) { double d; long bps; d=timing(0,NULL); if (d==0) d=0.5; /* can happen if timing uses time() */ bps=(zi->bytes_received-zi->bytes_skipped)/d; if (Verbose > 1) { vstringf( _("\rBytes received: %7ld/%7ld BPS:%-6ld \r\n"), (long) zi->bytes_received, (long) zi->bytes_total, bps); } DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: %ld Bytes, %ld BPS",shortname, protname(), (long) zi->bytes_total,bps)); } /* FALL THROUGH */ case ZSKIP: if (c==ZSKIP) { if (Verbose) vstringf(_("Skipped")); DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: skipped",shortname,protname())); } switch (tryz()) { case ZCOMPL: return OK; default: return ERROR; case ZFILE: break; } continue; default: return c; case ERROR: DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: error",shortname,protname())); return ERROR; } } } /* "OOSB" means Out Of Sync Block. I once thought that if sz sents * blocks a,b,c,d, of which a is ok, b fails, we might want to save * c and d. But, alas, i never saw c and d. */ #define SAVE_OOSB #ifdef SAVE_OOSB typedef struct oosb_t { size_t pos; size_t len; char *data; struct oosb_t *next; } oosb_t; struct oosb_t *anker=NULL; #endif /* * Receive a file with ZMODEM protocol * Assumes file name frame is in secbuf */ static int rzfile(struct zm_fileinfo *zi) { register int c, n; long last_rxbytes=0; unsigned long last_bps=0; long not_printed=0; time_t low_bps=0; size_t bytes_in_block=0; zi->eof_seen=FALSE; n = 20; if (procheader(secbuf,zi) == ERROR) { DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: procheader error", shortname,protname())); return (tryzhdrtype = ZSKIP); } for (;;) { #ifdef SEGMENTS chinseg = 0; #endif stohdr(zi->bytes_received); zshhdr(ZRPOS, Txhdr); goto skip_oosb; nxthdr: #ifdef SAVE_OOSB if (anker) { oosb_t *akt,*last,*next; for (akt=anker,last=NULL;akt;last= akt ? akt : last ,akt=next) { if (akt->pos==zi->bytes_received) { putsec(zi, akt->data, akt->len); zi->bytes_received += akt->len; vfile("using saved out-of-sync-paket %lx, len %ld", akt->pos,akt->len); goto nxthdr; } next=akt->next; if (akt->posbytes_received) { vfile("removing unneeded saved out-of-sync-paket %lx, len %ld", akt->pos,akt->len); if (last) last->next=akt->next; else anker=akt->next; free(akt->data); free(akt); akt=NULL; } } } #endif skip_oosb: c = zgethdr(Rxhdr, 0, NULL); switch (c) { default: DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: error: zgethdr returned %d",shortname, protname(),c)); vfile("rzfile: zgethdr returned %d", c); return ERROR; case ZNAK: case TIMEOUT: #ifdef SEGMENTS putsec(secbuf, chinseg); chinseg = 0; #endif if ( --n < 0) { DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: error: zgethdr returned %s",shortname, protname(),c == ZNAK ? "ZNAK" : "TIMEOUT")); vfile("rzfile: zgethdr returned %d", c); return ERROR; } case ZFILE: zrdata(secbuf, MAX_BLOCK,&bytes_in_block); continue; case ZEOF: #ifdef SEGMENTS putsec(secbuf, chinseg); chinseg = 0; #endif if (rclhdr(Rxhdr) != (long) zi->bytes_received) { /* * 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(zi)) { tryzhdrtype = ZFERR; DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: error: closeit return <>0", shortname, protname())); vfile("rzfile: closeit returned <> 0"); return ERROR; } vfile("rzfile: normal EOF"); return c; case ERROR: /* Too much garbage in header search error */ #ifdef SEGMENTS putsec(secbuf, chinseg); chinseg = 0; #endif if ( --n < 0) { DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: error: zgethdr returned %d", shortname, protname(),c)); vfile("rzfile: zgethdr returned %d", c); return ERROR; } zmputs(Attn); continue; case ZSKIP: #ifdef SEGMENTS putsec(secbuf, chinseg); chinseg = 0; #endif closeit(zi); DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: error: sender skipped", shortname, protname())); vfile("rzfile: Sender SKIPPED file"); return c; case ZDATA: if (rclhdr(Rxhdr) != (long) zi->bytes_received) { #if defined(SAVE_OOSB) oosb_t *neu; size_t pos=rclhdr(Rxhdr); #endif if ( --n < 0) { vfile("rzfile: out of sync"); DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: error: out of sync", shortname, protname())); return ERROR; } #if defined(SAVE_OOSB) switch (c = zrdata(secbuf, MAX_BLOCK,&bytes_in_block)) { case GOTCRCW: case GOTCRCG: case GOTCRCE: case GOTCRCQ: if (pos>zi->bytes_received) { neu=malloc(sizeof(oosb_t)); if (neu) neu->data=malloc(bytes_in_block); if (neu && neu->data) { #ifdef ENABLE_SYSLOG /* call syslog to tell me if this happens */ lsyslog(LOG_ERR, "saving out-of-sync-block %lx, len %lu", pos, (unsigned long) bytes_in_block); #endif vfile("saving out-of-sync-block %lx, len %lu",pos, (unsigned long) bytes_in_block); memcpy(neu->data,secbuf,bytes_in_block); neu->pos=pos; neu->len=bytes_in_block; neu->next=anker; anker=neu; } else if (neu) free(neu); } } #endif #ifdef SEGMENTS putsec(secbuf, chinseg); chinseg = 0; #endif zmputs(Attn); continue; } moredata: if ((Verbose>1 || min_bps || stop_time) && (not_printed > (min_bps ? 3 : 7) || zi->bytes_received > last_bps / 2 + last_rxbytes)) { int minleft = 0; int secleft = 0; time_t now; double d; d=timing(0,&now); if (d==0) d=0.5; /* timing() might use time() */ last_bps=zi->bytes_received/d; if (last_bps > 0) { minleft = (R_BYTESLEFT(zi))/last_bps/60; secleft = ((R_BYTESLEFT(zi))/last_bps)%60; } if (min_bps) { if (low_bps) { if (last_bps=min_bps_time) { /* too bad */ vfile(_("rzfile: bps rate %ld below min %ld"), last_bps, min_bps); DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: bps rate low: %ld < %ld", shortname, protname(), last_bps, min_bps)); return ERROR; } } else low_bps=0; } else if (last_bps=stop_time) { /* too bad */ vfile(_("rzfile: reached stop time")); DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: reached stop time", shortname, protname())); return ERROR; } if (Verbose > 1) { vstringf(_("\rBytes received: %7ld/%7ld BPS:%-6ld ETA %02d:%02d "), (long) zi->bytes_received, (long) zi->bytes_total, last_bps, minleft, secleft); last_rxbytes=zi->bytes_received; not_printed=0; } } else if (Verbose) not_printed++; #ifdef SEGMENTS if (chinseg >= (MAX_BLOCK * SEGMENTS)) { putsec(secbuf, chinseg); chinseg = 0; } switch (c = zrdata(secbuf+chinseg, MAX_BLOCK,&bytes_in_block)) #else switch (c = zrdata(secbuf, MAX_BLOCK,&bytes_in_block)) #endif { case ZCAN: #ifdef SEGMENTS putsec(secbuf, chinseg); chinseg = 0; #endif vfile("rzfile: zrdata returned %d", c); DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: zrdata returned ZCAN", shortname, protname())); return ERROR; case ERROR: /* CRC error */ #ifdef SEGMENTS putsec(secbuf, chinseg); chinseg = 0; #endif if ( --n < 0) { vfile("rzfile: zgethdr returned %d", c); DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: zrdata returned ERROR", shortname, protname())); return ERROR; } zmputs(Attn); continue; case TIMEOUT: #ifdef SEGMENTS putsec(secbuf, chinseg); chinseg = 0; #endif if ( --n < 0) { DO_SYSLOG_FNAME((LOG_INFO, "%s/%s: zrdata returned TIMEOUT", shortname, protname())); vfile("rzfile: zgethdr returned %d", c); return ERROR; } continue; case GOTCRCW: n = 20; #ifdef SEGMENTS chinseg += bytes_in_block; putsec(zi, secbuf, chinseg); chinseg = 0; #else putsec(zi, secbuf, bytes_in_block); #endif zi->bytes_received += bytes_in_block; stohdr(zi->bytes_received); zshhdr(ZACK | 0x80, Txhdr); goto nxthdr; case GOTCRCQ: n = 20; #ifdef SEGMENTS chinseg += bytes_in_block; #else putsec(zi, secbuf, bytes_in_block); #endif zi->bytes_received += bytes_in_block; stohdr(zi->bytes_received); zshhdr(ZACK, Txhdr); goto moredata; case GOTCRCG: n = 20; #ifdef SEGMENTS chinseg += bytes_in_block; #else putsec(zi, secbuf, bytes_in_block); #endif zi->bytes_received += bytes_in_block; goto moredata; case GOTCRCE: n = 20; #ifdef SEGMENTS chinseg += bytes_in_block; #else putsec(zi, secbuf, bytes_in_block); #endif zi->bytes_received += bytes_in_block; goto nxthdr; } } } } /* * Send a string to the modem, processing for \336 (sleep 1 sec) * and \335 (break signal) */ static void zmputs(const char *s) { const char *p; while (s && *s) { p=strpbrk(s,"\335\336"); if (!p) { write(1,s,strlen(s)); return; } if (p!=s) { write(1,s,(size_t) (p-s)); s=p; } if (*p=='\336') sleep(1); else sendbrk(0); p++; } } /* * Close the receive dataset, return OK or ERROR */ static int closeit(struct zm_fileinfo *zi) { int ret; if (Topipe) { if (pclose(fout)) { return ERROR; } return OK; } if (in_tcpsync) { rewind(fout); if (!fgets(tcp_buf,sizeof(tcp_buf),fout)) { error(1,errno,_("fgets for tcp protocol synchronization failed: ")); } fclose(fout); return OK; } ret=fclose(fout); if (ret) { zpfatal(_("file close error")); /* this may be any sort of error, including random data corruption */ unlink(Pathname); return ERROR; } if (zi->modtime) { #ifdef HAVE_STRUCT_UTIMBUF struct utimbuf timep; timep.actime = time(NULL); timep.modtime = zi->modtime; utime(Pathname, &timep); #else time_t timep[2]; timep[0] = time(NULL); timep[1] = zi->modtime; utime(Pathname, timep); #endif } #ifdef S_ISREG if (S_ISREG(zi->mode)) { #else if ((zi->mode&S_IFMT) == S_IFREG) { #endif /* we must not make this program executable if running * under rsh, because the user might have uploaded an * unrestricted shell. */ if (under_rsh) chmod(Pathname, (00666 & zi->mode)); else chmod(Pathname, (07777 & zi->mode)); } return OK; } /* * Ack a ZFIN packet, let byegones be byegones */ static void ackbibi(void) { int n; vfile("ackbibi:"); Readnum = 1; stohdr(0L); for (n=3; --n>=0; ) { purgeline(0); zshhdr(ZFIN, Txhdr); switch (READLINE_PF(100)) { case 'O': READLINE_PF(1); /* Discard 2nd 'O' */ vfile("ackbibi complete"); return; case RCDO: return; case TIMEOUT: default: break; } } } /* * Strip leading ! if present, do shell escape. */ static int sys2(const char *s) { if (*s == '!') ++s; return system(s); } /* * Strip leading ! if present, do exec. */ static void exec2(const char *s) { if (*s == '!') ++s; io_mode(0,0); execl("/bin/sh", "sh", "-c", s); zpfatal("execl"); exit(1); } /* * Routine to calculate the free bytes on the current file system * ~0 means many free bytes (unknown) */ static size_t getfree(void) { return((size_t) (~0L)); /* many free bytes ... */ } /* End of lrz.c */