diff options
| author | svn2git <svn2git@FreeBSD.org> | 1994-05-01 08:00:00 +0000 |
|---|---|---|
| committer | svn2git <svn2git@FreeBSD.org> | 1994-05-01 08:00:00 +0000 |
| commit | a16f65c7d117419bd266c28a1901ef129a337569 (patch) | |
| tree | 2626602f66dc3551e7a7c7bc9ad763c3bc7ab40a /gnu/libexec/uucp/uucico | |
| parent | 8503f4f13f77abf7adc8f7e329c6f9c1d52b6a20 (diff) | |
Diffstat (limited to 'gnu/libexec/uucp/uucico')
| -rw-r--r-- | gnu/libexec/uucp/uucico/Makefile | 20 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/prote.c | 387 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/protf.c | 842 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/protg.c | 1933 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/proti.c | 1563 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/protj.c | 671 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/prott.c | 330 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/protz.c | 2626 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/rec.c | 1162 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/send.c | 1273 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/time.c | 130 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/trans.c | 1439 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/uucico.8 | 225 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/uucico.c | 2618 | ||||
| -rw-r--r-- | gnu/libexec/uucp/uucico/xcmd.c | 396 |
15 files changed, 15615 insertions, 0 deletions
diff --git a/gnu/libexec/uucp/uucico/Makefile b/gnu/libexec/uucp/uucico/Makefile new file mode 100644 index 0000000000000..6cd270934bbf3 --- /dev/null +++ b/gnu/libexec/uucp/uucico/Makefile @@ -0,0 +1,20 @@ +# Makefile for uucico +# $Id: Makefile,v 1.2 1993/08/06 23:38:23 rgrimes Exp $ + +BINDIR= $(sbindir) +BINOWN= $(owner) +BINMODE= 4555 + +PROG= uucico +SRCS= uucico.c trans.c send.c rec.c xcmd.c prot.c protg.c protf.c \ + prott.c prote.c proti.c protj.c protz.c time.c chat.c \ + conn.c copy.c log.c tcp.c tli.c util.c +LDADD+= $(LIBUNIX) $(LIBUUCONF) $(LIBUUCP) +DPADD+= $(LIBUNIX) $(LIBUUCONF) $(LIBUUCP) +CFLAGS+= -I$(.CURDIR)/../common_sources\ + -DVERSION=\"$(VERSION)\" + +MAN8= uucico.8 + +.include <bsd.prog.mk> +.PATH: $(.CURDIR)/../common_sources diff --git a/gnu/libexec/uucp/uucico/prote.c b/gnu/libexec/uucp/uucico/prote.c new file mode 100644 index 0000000000000..9b824651c2f77 --- /dev/null +++ b/gnu/libexec/uucp/uucico/prote.c @@ -0,0 +1,387 @@ +/* prote.c + The 'e' protocol. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char prote_rcsid[] = "$Id: prote.c,v 1.1 1993/08/05 18:27:09 conklin Exp $"; +#endif + +#include "uudefs.h" +#include "uuconf.h" +#include "conn.h" +#include "trans.h" +#include "system.h" +#include "prot.h" + +/* This implementation is based on my implementation of the 't' + protocol, which is fairly similar. The main difference between the + protocols seems to be that 't' breaks the file into packets and + transmits the size of the packet with each packet, whereas 'e' + sends the size of the entire file and then sends all the data in a + single enormous packet. + + The 'e' protocol does no error checking whatsoever and thus + requires an end-to-end verified eight bit communication line, such + as is provided by TCP. Using it with a modem is inadvisable, since + errors can occur between the modem and the computer. */ + +/* The buffer size we use. */ +#define CEBUFSIZE (CRECBUFLEN / 2) + +/* The size of the initial file size message. */ +#define CEFRAMELEN (20) + +/* A pointer to the buffer we will use. */ +static char *zEbuf; + +/* True if we are receiving a file. */ +static boolean fEfile; + +/* The number of bytes we have left to send or receive. */ +static long cEbytes; + +/* The timeout we use. */ +static int cEtimeout = 120; + +struct uuconf_cmdtab asEproto_params[] = +{ + { "timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cEtimeout, NULL }, + { NULL, 0, NULL, NULL } +}; + +/* Local function. */ + +static boolean feprocess_data P((struct sdaemon *qdaemon, boolean *pfexit, + size_t *pcneed)); + +/* Start the protocol. */ + +boolean +festart (qdaemon, pzlog) + struct sdaemon *qdaemon; + char **pzlog; +{ + *pzlog = NULL; + if (! fconn_set (qdaemon->qconn, PARITYSETTING_NONE, + STRIPSETTING_EIGHTBITS, XONXOFF_OFF)) + return FALSE; + zEbuf = (char *) xmalloc (CEBUFSIZE); + fEfile = FALSE; + usysdep_sleep (2); + return TRUE; +} + +/* Stop the protocol. */ + +/*ARGSUSED*/ +boolean +feshutdown (qdaemon) + struct sdaemon *qdaemon; +{ + xfree ((pointer) zEbuf); + zEbuf = NULL; + cEtimeout = 120; + return TRUE; +} + +/* Send a command string. We send everything up to and including the + null byte. */ + +/*ARGSUSED*/ +boolean +fesendcmd (qdaemon, z, ilocal, iremote) + struct sdaemon *qdaemon; + const char *z; + int ilocal; + int iremote; +{ + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, "fesendcmd: Sending command \"%s\"", z); + + return fsend_data (qdaemon->qconn, z, strlen (z) + 1, TRUE); +} + +/* Get space to be filled with data. We provide a buffer which has + 20 bytes at the start available to hold the length. */ + +/*ARGSUSED*/ +char * +zegetspace (qdaemon, pclen) + struct sdaemon *qdaemon; + size_t *pclen; +{ + *pclen = CEBUFSIZE; + return zEbuf; +} + +/* Send out some data. We are allowed to modify the 20 bytes + preceding the buffer. This allows us to send the entire block with + header bytes in a single call. */ + +/*ARGSIGNORED*/ +boolean +fesenddata (qdaemon, zdata, cdata, ilocal, iremote, ipos) + struct sdaemon *qdaemon; + char *zdata; + size_t cdata; + int ilocal; + int iremote; + long ipos; +{ +#if DEBUG > 0 + /* Keep track of the number of bytes we send out to make sure it all + adds up. */ + cEbytes -= cdata; + if (cEbytes < 0) + { + ulog (LOG_ERROR, "Protocol 'e' internal error"); + return FALSE; + } +#endif + + /* We pass FALSE to fsend_data since we don't expect the other side + to be sending us anything just now. */ + return fsend_data (qdaemon->qconn, zdata, cdata, FALSE); +} + +/* Process data and return the amount we need in *pfneed. */ + +static boolean +feprocess_data (qdaemon, pfexit, pcneed) + struct sdaemon *qdaemon; + boolean *pfexit; + size_t *pcneed; +{ + int cinbuf, cfirst, clen; + + *pfexit = FALSE; + + cinbuf = iPrecend - iPrecstart; + if (cinbuf < 0) + cinbuf += CRECBUFLEN; + + if (! fEfile) + { + /* We are not receiving a file. Commands continue up to a null + byte. */ + while (cinbuf > 0) + { + char *pnull; + + cfirst = CRECBUFLEN - iPrecstart; + if (cfirst > cinbuf) + cfirst = cinbuf; + + pnull = memchr (abPrecbuf + iPrecstart, '\0', (size_t) cfirst); + if (pnull != NULL) + cfirst = pnull - (abPrecbuf + iPrecstart) + 1; + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "feprocess_data: Got %d command bytes", + cfirst); + + if (! fgot_data (qdaemon, abPrecbuf + iPrecstart, + (size_t) cfirst, (const char *) NULL, (size_t) 0, + -1, -1, (long) -1, TRUE, pfexit)) + return FALSE; + + iPrecstart = (iPrecstart + cfirst) % CRECBUFLEN; + + if (*pfexit) + return TRUE; + + cinbuf = iPrecend - iPrecstart; + if (cinbuf < 0) + cinbuf += CRECBUFLEN; + } + + if (pcneed != NULL) + *pcneed = 1; + + return TRUE; + } + + /* Here we are receiving a file. We want cEbytes in total. If we + don't have cEbytes yet, we have to get it first. */ + + if (cEbytes == -1) + { + char ab[CEFRAMELEN + 1]; + + if (cinbuf < CEFRAMELEN) + { + if (pcneed != NULL) + *pcneed = CEFRAMELEN - cinbuf; + return TRUE; + } + + cfirst = CRECBUFLEN - iPrecstart; + if (cfirst >= CEFRAMELEN) + memcpy (ab, abPrecbuf + iPrecstart, (size_t) CEFRAMELEN); + else + { + memcpy (ab, abPrecbuf + iPrecstart, (size_t) cfirst); + memcpy (ab + cfirst, abPrecbuf, (size_t) CEFRAMELEN - cfirst); + } + + ab[CEFRAMELEN] = '\0'; + cEbytes = strtol (ab, (char **) NULL, 10); + + iPrecstart = (iPrecstart + CEFRAMELEN) % CRECBUFLEN; + + cinbuf = iPrecend - iPrecstart; + if (cinbuf < 0) + cinbuf += CRECBUFLEN; + } + + /* Here we can read real data for the file. */ + + while (cinbuf > 0) + { + clen = cinbuf; + if ((long) clen > cEbytes) + clen = (int) cEbytes; + + cfirst = CRECBUFLEN - iPrecstart; + if (cfirst > clen) + cfirst = clen; + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "feprocess_data: Got %d data bytes", + clen); + + if (! fgot_data (qdaemon, abPrecbuf + iPrecstart, + (size_t) cfirst, abPrecbuf, (size_t) (clen - cfirst), + -1, -1, (long) -1, TRUE, pfexit)) + return FALSE; + + iPrecstart = (iPrecstart + clen) % CRECBUFLEN; + cEbytes -= clen; + + if (cEbytes == 0) + { + if (! fgot_data (qdaemon, abPrecbuf, (size_t) 0, + (const char *) NULL, (size_t) 0, + -1, -1, (long) -1, TRUE, pfexit)) + return FALSE; + if (*pfexit) + return TRUE; + } + + cinbuf -= clen; + } + + if (pcneed != NULL) + { + if (cEbytes > CRECBUFLEN / 2) + *pcneed = CRECBUFLEN / 2; + else + *pcneed = (int) cEbytes; + } + + return TRUE; +} + +/* Wait for data to come in and process it until we've reached the end + of a command or a file. */ + +boolean +fewait (qdaemon) + struct sdaemon *qdaemon; +{ + while (TRUE) + { + boolean fexit; + size_t cneed, crec; + + if (! feprocess_data (qdaemon, &fexit, &cneed)) + return FALSE; + if (fexit) + return TRUE; + + if (! freceive_data (qdaemon->qconn, cneed, &crec, cEtimeout, TRUE)) + return FALSE; + + if (crec == 0) + { + ulog (LOG_ERROR, "Timed out waiting for data"); + return FALSE; + } + } +} + +/* File level routine, to handle transferring the amount of data and + to set fEfile correctly. */ + +boolean +fefile (qdaemon, qtrans, fstart, fsend, cbytes, pfhandled) + struct sdaemon *qdaemon; + struct stransfer *qtrans; + boolean fstart; + boolean fsend; + long cbytes; + boolean *pfhandled; +{ + *pfhandled = FALSE; + + if (fstart) + { + if (fsend) + { + char ab[CEFRAMELEN]; + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "Protocol 'e' starting to send %ld bytes", + cbytes); + + bzero (ab, (size_t) CEFRAMELEN); + sprintf (ab, "%ld", cbytes); + if (! fsend_data (qdaemon->qconn, ab, (size_t) CEFRAMELEN, TRUE)) + return FALSE; + cEbytes = cbytes; + } + else + { + cEbytes = -1; + fEfile = TRUE; + } + } + else + { + if (! fsend) + fEfile = FALSE; +#if DEBUG > 0 + if (cEbytes != 0) + { + ulog (LOG_ERROR, + "Protocol 'e' internal error: %ld bytes left over", + cEbytes); + return FALSE; + } +#endif + } + + return TRUE; +} diff --git a/gnu/libexec/uucp/uucico/protf.c b/gnu/libexec/uucp/uucico/protf.c new file mode 100644 index 0000000000000..96b213157cee0 --- /dev/null +++ b/gnu/libexec/uucp/uucico/protf.c @@ -0,0 +1,842 @@ +/* protf.c + The 'f' protocol. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char protf_rcsid[] = "$Id: protf.c,v 1.1 1993/08/05 18:27:10 conklin Exp $"; +#endif + +#include <ctype.h> +#include <errno.h> + +#include "uudefs.h" +#include "uuconf.h" +#include "conn.h" +#include "trans.h" +#include "system.h" +#include "prot.h" + +/* This implementation is based on code by Piet Beertema, CWI, + Amsterdam, Sep 1984. + + This code implements the 'f' protocol, which requires a + flow-controlled error-free seven-bit data path. It does check for + errors, but only at the end of each file transmission, so a noisy + line without error correcting modems will be unusable. + + The conversion to seven bit data is done as follows, where b + represents the character to convert: + + 0 <= b <= 037: 0172, b + 0100 (0100 to 0137) + 040 <= b <= 0171: b ( 040 to 0171) + 0172 <= b <= 0177: 0173, b - 0100 ( 072 to 077) + 0200 <= b <= 0237: 0174, b - 0100 (0100 to 0137) + 0240 <= b <= 0371: 0175, b - 0200 ( 040 to 0171) + 0372 <= b <= 0377: 0176, b - 0300 ( 072 to 077) + + This causes all output bytes to be in the range 040 to 0176; these + are the printable ASCII characters. */ + +/* This structure is used to hold information when dealing with the + end of file acknowledgement. */ + +struct sfinfo +{ + /* The functions from the generic code. */ + boolean (*psendfn) P((struct stransfer *qtrans, struct sdaemon *qdaemon)); + boolean (*precfn) P((struct stransfer *qtrans, struct sdaemon *qdaemon, + const char *zdata, size_t cdata)); + /* The info pointer from the generic code. */ + pointer pinfo; + /* The character to send after receiving the checksum. */ + char bsend; +}; + +/* Internal functions. */ +static boolean ffprocess_data P((struct sdaemon *qdaemon, + boolean *pfexit, size_t *pcneed)); +static boolean ffawait_ack P((struct stransfer *qtrans, + struct sdaemon *qdaemon, + const char *zdata, size_t cdata)); +static boolean ffawait_cksum P((struct stransfer *qtrans, + struct sdaemon *qdaemon, + const char *zdata, size_t cdata)); +static boolean ffsend_ack P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); + +/* The size of the buffer we allocate to store outgoing data in. */ +#define CFBUFSIZE (256) + +/* The timeout to wait for data to arrive before giving up. */ +static int cFtimeout = 120; + +/* The maximum number of retries. */ +static int cFmaxretries = 2; + +/* The buffer we allocate for outgoing data. */ +static char *zFbuf; + +/* TRUE if we are receiving a file rather than a command. */ +static boolean fFfile; + +/* The checksum so far. */ +static unsigned int iFcheck; + +/* The last special byte (0172 to 0176) or 0 if none. */ +static char bFspecial; + +/* The number of times we have retried this file. */ +static int cFretries; + +/* Whether this file has been acknowledged. */ +static boolean fFacked; + +struct uuconf_cmdtab asFproto_params[] = +{ + { "timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cFtimeout, NULL }, + { "retries", UUCONF_CMDTABTYPE_INT, (pointer) &cFmaxretries, NULL }, + { NULL, 0, NULL, NULL } +}; + +/* Statistics. */ + +/* The number of data bytes sent in files. */ +static long cFsent_data; + +/* The number of actual bytes sent in files. */ +static long cFsent_bytes; + +/* The number of data bytes received in files. */ +static long cFrec_data; + +/* The number of actual bytes received in files. */ +static long cFrec_bytes; + +/* The number of file retries when sending. */ +static long cFsend_retries; + +/* The number of file retries when receiving. */ +static long cFrec_retries; + +/* Start the protocol. */ + +boolean +ffstart (qdaemon, pzlog) + struct sdaemon *qdaemon; + char **pzlog; +{ + *pzlog = NULL; + + cFsent_data = 0; + cFsent_bytes = 0; + cFrec_data = 0; + cFrec_bytes = 0; + cFsend_retries = 0; + cFrec_retries = 0; + + /* Use XON/XOFF handshaking. */ + if (! fconn_set (qdaemon->qconn, PARITYSETTING_DEFAULT, + STRIPSETTING_SEVENBITS, XONXOFF_ON)) + return FALSE; + + /* We sleep to allow the other side to reset the terminal; this is + what Mr. Beertema's code does. */ + usysdep_sleep (2); + + return TRUE; +} + +/* Shutdown the protocol. */ + +/*ARGSIGNORED*/ +boolean +ffshutdown (qdaemon) + struct sdaemon *qdaemon; +{ + xfree ((pointer) zFbuf); + zFbuf = NULL; + ulog (LOG_NORMAL, + "Protocol 'f': sent %ld bytes for %ld, received %ld bytes for %ld", + cFsent_bytes, cFsent_data, cFrec_bytes, cFrec_data); + if (cFsend_retries != 0 || cFrec_retries != 0) + ulog (LOG_NORMAL, "Protocol 'f' file retries: %ld sending, %ld receiving", + cFsend_retries, cFrec_retries); + cFtimeout = 120; + cFmaxretries = 2; + return TRUE; +} + +/* Send a command string. We just send the string followed by a carriage + return. */ + +/*ARGSUSED*/ +boolean +ffsendcmd (qdaemon, z, ilocal, iremote) + struct sdaemon *qdaemon; + const char *z; + int ilocal; + int iremote; +{ + size_t clen; + char *zalc; + boolean fret; + + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, "ffsendcmd: Sending command \"%s\"", z); + + clen = strlen (z); + zalc = zbufalc (clen + 2); + memcpy (zalc, z, clen); + zalc[clen] = '\r'; + zalc[clen + 1] = '\0'; + fret = fsend_data (qdaemon->qconn, zalc, clen + 1, TRUE); + ubuffree (zalc); + return fret; +} + +/* Get space to be filled with data. We allocate the space from the + heap. */ + +/*ARGSIGNORED*/ +char * +zfgetspace (qdaemon, pclen) + struct sdaemon *qdaemon; + size_t *pclen; +{ + *pclen = CFBUFSIZE; + if (zFbuf == NULL) + zFbuf = (char *) xmalloc (CFBUFSIZE); + return zFbuf; +} + +/* Send out a data packet. We have to encode the data into seven bits + and accumulate a checksum. */ + +/*ARGSIGNORED*/ +boolean +ffsenddata (qdaemon, zdata, cdata, ilocal, iremote, ipos) + struct sdaemon *qdaemon; + char *zdata; + size_t cdata; + int ilocal; + int iremote; + long ipos; +{ + char ab[CFBUFSIZE * 2]; + char *ze; + register unsigned int itmpchk; + + cFsent_data += cdata; + + ze = ab; + itmpchk = iFcheck; + while (cdata-- > 0) + { + register int b; + + /* Rotate the checksum left. */ + if ((itmpchk & 0x8000) == 0) + itmpchk <<= 1; + else + { + itmpchk <<= 1; + ++itmpchk; + } + + /* Add the next byte into the checksum. */ + b = *zdata++ & 0xff; + itmpchk += b; + + /* Encode the byte. */ + if (b <= 0177) + { + if (b <= 037) + { + *ze++ = '\172'; + *ze++ = (char) (b + 0100); + } + else if (b <= 0171) + *ze++ = (char) b; + else + { + *ze++ = '\173'; + *ze++ = (char) (b - 0100); + } + } + else + { + if (b <= 0237) + { + *ze++ = '\174'; + *ze++ = (char) (b - 0100); + } + else if (b <= 0371) + { + *ze++ = '\175'; + *ze++ = (char) (b - 0200); + } + else + { + *ze++ = '\176'; + *ze++ = (char) (b - 0300); + } + } + } + + iFcheck = itmpchk; + + cFsent_bytes += ze - ab; + + /* Passing FALSE tells fsend_data not to bother looking for incoming + information, since we really don't expect any. */ + return fsend_data (qdaemon->qconn, ab, (size_t) (ze - ab), FALSE); +} + +/* Process data and return the amount of data we are looking for in + *pcneed. The 'f' protocol doesn't really reveal this, but when + transferring file we know that we need at least seven characters + for the checksum. */ + +static boolean +ffprocess_data (qdaemon, pfexit, pcneed) + struct sdaemon *qdaemon; + boolean *pfexit; + size_t *pcneed; +{ + int i; + register unsigned int itmpchk; + + *pfexit = FALSE; + if (pcneed != NULL) + *pcneed = 1; + + if (! fFfile) + { + /* A command continues until a '\r' character, which we turn + into '\0' before calling fgot_data. */ + while (iPrecstart != iPrecend) + { + for (i = iPrecstart; i < CRECBUFLEN && i != iPrecend; i++) + { + if (abPrecbuf[i] == '\r') + { + int istart; + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "ffprocess_data: Got %d command bytes", + i - iPrecstart + 1); + + abPrecbuf[i] = '\0'; + istart = iPrecstart; + iPrecstart = (i + 1) % CRECBUFLEN; + if (pcneed != NULL) + *pcneed = 0; + return fgot_data (qdaemon, abPrecbuf + istart, + (size_t) (i - istart + 1), + (const char *) NULL, (size_t) 0, + -1, -1, (long) -1, TRUE, pfexit); + } + } + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "ffprocess_data: Got %d command bytes", + i - iPrecstart); + + if (! fgot_data (qdaemon, abPrecbuf + iPrecstart, + (size_t) (i - iPrecstart), + (const char *) NULL, (size_t) 0, + -1, -1, (long) -1, TRUE, pfexit)) + return FALSE; + + iPrecstart = i % CRECBUFLEN; + } + + return TRUE; + } + + /* Here the data is destined for a file, and we must decode it. */ + + itmpchk = iFcheck; + + while (iPrecstart != iPrecend) + { + char *zstart, *zto, *zfrom; + int c; + + zto = zfrom = zstart = abPrecbuf + iPrecstart; + + c = iPrecend - iPrecstart; + if (c < 0) + c = CRECBUFLEN - iPrecstart; + + while (c-- != 0) + { + int b; + + b = *zfrom++ & 0xff; + if (b < 040 || b > 0176) + { + ulog (LOG_ERROR, "Illegal byte %d", b); + continue; + } + + /* Characters >= 0172 are always special characters. The + only legal pair of consecutive special characters + are 0176 0176 which immediately precede the four + digit checksum. */ + if (b >= 0172) + { + if (bFspecial != 0) + { + if (bFspecial != 0176 || b != 0176) + { + ulog (LOG_ERROR, "Illegal bytes %d %d", + bFspecial, b); + bFspecial = 0; + continue; + } + + /* Pass any initial data. */ + if (zto != zstart) + { + /* Don't count the checksum in the received bytes. */ + cFrec_bytes += zfrom - zstart - 2; + cFrec_data += zto - zstart; + if (! fgot_data (qdaemon, zstart, + (size_t) (zto - zstart), + (const char *) NULL, (size_t) 0, + -1, -1, (long) -1, TRUE, pfexit)) + return FALSE; + } + + /* The next characters we want to read are the + checksum, so skip the second 0176. */ + iPrecstart = (iPrecstart + zfrom - zstart) % CRECBUFLEN; + + iFcheck = itmpchk; + + /* Tell fgot_data that we've read the entire file by + passing 0 length data. This will wind up calling + fffile to verify the checksum. We set *pcneed to + 0 because we don't want to read any more data + from the port, since we may have already read the + checksum. */ + if (pcneed != NULL) + *pcneed = 0; + return fgot_data (qdaemon, (const char *) NULL, + (size_t) 0, (const char *) NULL, + (size_t) 0, -1, -1, (long) -1, + TRUE, pfexit); + } + + /* Here we have encountered a special character that + does not follow another special character. */ + bFspecial = (char) b; + } + else + { + int bnext; + + /* Here we have encountered a nonspecial character. */ + + switch (bFspecial) + { + default: + bnext = b; + break; + case 0172: + bnext = b - 0100; + break; + case 0173: + case 0174: + bnext = b + 0100; + break; + case 0175: + bnext = b + 0200; + break; + case 0176: + bnext = b + 0300; + break; + } + + *zto++ = (char) bnext; + bFspecial = 0; + + /* Rotate the checksum left. */ + if ((itmpchk & 0x8000) == 0) + itmpchk <<= 1; + else + { + itmpchk <<= 1; + ++itmpchk; + } + + /* Add the next byte into the checksum. */ + itmpchk += bnext; + } + } + + if (zto != zstart) + { + DEBUG_MESSAGE1 (DEBUG_PROTO, + "ffprocess_data: Got %d bytes", + zto - zstart); + + cFrec_data += zto - zstart; + if (! fgot_data (qdaemon, zstart, (size_t) (zto - zstart), + (const char *) NULL, (size_t) 0, + -1, -1, (long) -1, TRUE, pfexit)) + return FALSE; + } + + cFrec_bytes += zfrom - zstart; + + iPrecstart = (iPrecstart + zfrom - zstart) % CRECBUFLEN; + } + + iFcheck = itmpchk; + + if (pcneed != NULL) + { + /* At this point we may have seen the first 0176 in the checksum + but not the second. The checksum is at least seven + characters long (0176 0176 a b c d \r). This won't help + much, but reading seven characters is a lot better than + reading two, which is what I saw in a 2400 baud log file. */ + if (bFspecial == 0176) + *pcneed = 6; + else + *pcneed = 7; + } + + return TRUE; +} + +/* Wait for data to come in and process it until we've finished a + command or a file. */ + +boolean +ffwait (qdaemon) + struct sdaemon *qdaemon; +{ + while (TRUE) + { + boolean fexit; + size_t cneed, crec; + + if (! ffprocess_data (qdaemon, &fexit, &cneed)) + return FALSE; + if (fexit) + return TRUE; + + if (cneed > 0) + { + /* We really want to do something like get all available + characters, then sleep for half a second and get all + available characters again, and keep this up until we + don't get anything after sleeping. */ + if (! freceive_data (qdaemon->qconn, cneed, &crec, cFtimeout, TRUE)) + return FALSE; + if (crec == 0) + { + ulog (LOG_ERROR, "Timed out waiting for data"); + return FALSE; + } + } + } +} + +/* File level operations. Reset the checksums when starting to send + or receive a file, and output the checksum when we've finished + sending a file. */ + +/*ARGSUSED*/ +boolean +fffile (qdaemon, qtrans, fstart, fsend, cbytes, pfhandled) + struct sdaemon *qdaemon; + struct stransfer *qtrans; + boolean fstart; + boolean fsend; + long cbytes; + boolean *pfhandled; +{ + DEBUG_MESSAGE3 (DEBUG_PROTO, "fffile: fstart %s; fsend %s; fFacked %s", + fstart ? "true" : "false", fsend ? "true" : "false", + fFacked ? "true" : "false"); + + *pfhandled = FALSE; + + if (fstart) + { + iFcheck = 0xffff; + cFretries = 0; + fFacked = FALSE; + if (! fsend) + { + bFspecial = 0; + fFfile = TRUE; + } + return TRUE; + } + else + { + struct sfinfo *qinfo; + + /* We need to handle the checksum and the acknowledgement. If + we get a successful ACK, we set fFacked to TRUE and call the + send or receive function by hand. This will wind up calling + here again, so if fFacked is TRUE we just return out and let + the send or receive function do whatever it does. This is a + bit of a hack. */ + if (fFacked) + { + fFacked = FALSE; + return TRUE; + } + + if (fsend) + { + char ab[sizeof "\176\176ABCD\r"]; + + /* Send the final checksum. */ + sprintf (ab, "\176\176%04x\r", iFcheck & 0xffff); + if (! fsend_data (qdaemon->qconn, ab, (size_t) 7, TRUE)) + return FALSE; + + /* Now wait for the acknowledgement. */ + fFfile = FALSE; + qinfo = (struct sfinfo *) xmalloc (sizeof (struct sfinfo)); + qinfo->psendfn = qtrans->psendfn; + qinfo->precfn = qtrans->precfn; + qinfo->pinfo = qtrans->pinfo; + qtrans->psendfn = NULL; + qtrans->precfn = ffawait_ack; + qtrans->pinfo = (pointer) qinfo; + qtrans->fcmd = TRUE; + + *pfhandled = TRUE; + + return fqueue_receive (qdaemon, qtrans); + } + else + { + /* Wait for the checksum. */ + fFfile = FALSE; + qinfo = (struct sfinfo *) xmalloc (sizeof (struct sfinfo)); + qinfo->psendfn = qtrans->psendfn; + qinfo->precfn = qtrans->precfn; + qinfo->pinfo = qtrans->pinfo; + qtrans->psendfn = NULL; + qtrans->precfn = ffawait_cksum; + qtrans->pinfo = (pointer) qinfo; + qtrans->fcmd = TRUE; + + *pfhandled = TRUE; + + return fqueue_receive (qdaemon, qtrans); + } + } +} + +/* Wait for the ack after sending a file and the checksum. */ + +static boolean +ffawait_ack (qtrans, qdaemon, zdata, cdata) + struct stransfer *qtrans; + struct sdaemon *qdaemon; + const char *zdata; + size_t cdata; +{ + struct sfinfo *qinfo = (struct sfinfo *) qtrans->pinfo; + + qtrans->precfn = NULL; + + /* An R means to retry sending the file. */ + if (*zdata == 'R') + { + if (! ffileisopen (qtrans->e)) + { + ulog (LOG_ERROR, "Request to resent non-file"); + return FALSE; + } + + ++cFretries; + if (cFretries > cFmaxretries) + { + ulog (LOG_ERROR, "Too many retries"); + return FALSE; + } + + ulog (LOG_NORMAL, "Resending file"); + if (! ffilerewind (qtrans->e)) + { + ulog (LOG_ERROR, "rewind: %s", strerror (errno)); + return FALSE; + } + qtrans->ipos = (long) 0; + + iFcheck = 0xffff; + ++cFsend_retries; + + qtrans->psendfn = qinfo->psendfn; + qtrans->precfn = qinfo->precfn; + qtrans->pinfo = qinfo->pinfo; + xfree ((pointer) qinfo); + qtrans->fsendfile = TRUE; + + return fqueue_send (qdaemon, qtrans); + } + + if (*zdata != 'G') + { + DEBUG_MESSAGE1 (DEBUG_PROTO, "fffile: Got \"%s\"", zdata); + ulog (LOG_ERROR, "File send failed"); + return FALSE; + } + + qtrans->psendfn = qinfo->psendfn; + qtrans->precfn = qinfo->precfn; + qtrans->pinfo = qinfo->pinfo; + xfree ((pointer) qinfo); + + /* Now call the send function by hand after setting fFacked to TRUE. + Since fFacked is true fffile will simply return out, and the send + function can do whatever it what was going to do. */ + fFacked = TRUE; + return (*qtrans->psendfn) (qtrans, qdaemon); +} + +/* This function is called when the checksum arrives. */ + +/*ARGSUSED*/ +static boolean +ffawait_cksum (qtrans, qdaemon, zdata, cdata) + struct stransfer *qtrans; + struct sdaemon *qdaemon; + const char *zdata; + size_t cdata; +{ + struct sfinfo *qinfo = (struct sfinfo *) qtrans->pinfo; + unsigned int icheck; + + qtrans->precfn = NULL; + + if (! isxdigit (zdata[0]) + || ! isxdigit (zdata[1]) + || ! isxdigit (zdata[2]) + || ! isxdigit (zdata[3]) + || zdata[4] != '\0') + { + ulog (LOG_ERROR, "Bad checksum format"); + xfree (qtrans->pinfo); + return FALSE; + } + + icheck = (unsigned int) strtol ((char *) zdata, (char **) NULL, 16); + + if (icheck != (iFcheck & 0xffff)) + { + DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL, + "Checksum failed; calculated 0x%x, got 0x%x", + iFcheck & 0xffff, icheck); + + if (! ffileisopen (qtrans->e)) + { + ulog (LOG_ERROR, "Failed to get non-file"); + return FALSE; + } + + ++cFretries; + if (cFretries > cFmaxretries) + { + ulog (LOG_ERROR, "Too many retries"); + qinfo->bsend = 'Q'; + } + else + { + ulog (LOG_NORMAL, "File being resent"); + + /* This bit of code relies on the receive code setting + qtrans->s.ztemp to the full name of the temporary file + being used. */ + qtrans->e = esysdep_truncate (qtrans->e, qtrans->s.ztemp); + if (! ffileisopen (qtrans->e)) + return FALSE; + qtrans->ipos = (long) 0; + + iFcheck = 0xffff; + bFspecial = 0; + fFfile = TRUE; + ++cFrec_retries; + + /* Send an R to tell the other side to resend the file. */ + qinfo->bsend = 'R'; + } + } + else + { + /* Send a G to tell the other side the file was received + correctly. */ + qinfo->bsend = 'G'; + } + + qtrans->psendfn = ffsend_ack; + + return fqueue_send (qdaemon, qtrans); +} + +/* Send the acknowledgement, and then possible wait for the resent + file. */ + +static boolean +ffsend_ack (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct sfinfo *qinfo = (struct sfinfo *) qtrans->pinfo; + char ab[2]; + + ab[0] = qinfo->bsend; + ab[1] = '\0'; + if (! ffsendcmd (qdaemon, ab, 0, 0)) + return FALSE; + + qtrans->psendfn = qinfo->psendfn; + qtrans->precfn = qinfo->precfn; + qtrans->pinfo = qinfo->pinfo; + xfree ((pointer) qinfo); + + if (ab[0] == 'Q') + return FALSE; + if (ab[0] == 'R') + { + qtrans->frecfile = TRUE; + return fqueue_receive (qdaemon, qtrans); + } + + fFacked = TRUE; + return (*qtrans->precfn) (qtrans, qdaemon, (const char *) NULL, + (size_t) 0); +} diff --git a/gnu/libexec/uucp/uucico/protg.c b/gnu/libexec/uucp/uucico/protg.c new file mode 100644 index 0000000000000..39a79212cf771 --- /dev/null +++ b/gnu/libexec/uucp/uucico/protg.c @@ -0,0 +1,1933 @@ +/* protg.c + The 'g' protocol. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char protg_rcsid[] = "$Id: protg.c,v 1.1 1993/08/05 18:27:11 conklin Exp $"; +#endif + +#include <ctype.h> +#include <errno.h> + +#include "uudefs.h" +#include "uuconf.h" +#include "conn.h" +#include "trans.h" +#include "system.h" +#include "prot.h" + +/* Each 'g' protocol packet begins with six bytes. They are: + + <DLE><k><c0><c1><C><x> + + <DLE> is the ASCII DLE character (^P or '\020'). + if 1 <= <k> <= 8, the packet is followed by 2 ** (k + 4) bytes of data; + if <k> == 9, these six bytes are a complete control packet; + other value of <k> are illegal. + <c0> is the low byte of a checksum. + <c1> is the high byte of a checksum. + <C> is a control byte (see below). + <x> is <k> ^ <c0> ^ <c1> ^ <C>. + + The control byte <C> is divided into three bitfields: + + t t x x x y y y + + The two bit field tt is the packet type. + The three bit field xxx is the control type for a control packet, or + the sequence number for a data packet. + The three bit field yyy is a value for a control packet, or the + sequence number of the last packet received for a data packet. + + For all successfully recieved packets, the control byte is stored + into iGpacket_control. */ + +/* Names for the bytes in the frame header. */ +#define IFRAME_DLE (0) +#define IFRAME_K (1) +#define IFRAME_CHECKLOW (2) +#define IFRAME_CHECKHIGH (3) +#define IFRAME_CONTROL (4) +#define IFRAME_XOR (5) + +/* Length of the frame header. */ +#define CFRAMELEN (6) + +/* Macros to break apart the control bytes. */ +#define CONTROL_TT(b) ((int)(((b) >> 6) & 03)) +#define CONTROL_XXX(b) ((int)(((b) >> 3) & 07)) +#define CONTROL_YYY(b) ((int)((b) & 07)) + +/* DLE value. */ +#define DLE ('\020') + +/* Get the length of a packet given a pointer to the header. */ +#define CPACKLEN(z) ((size_t) (1 << ((z)[IFRAME_K] + 4))) + +/* <k> field value for a control message. */ +#define KCONTROL (9) + +/* Get the next sequence number given a sequence number. */ +#define INEXTSEQ(i) ((i + 1) & 07) + +/* Compute i1 - i2 modulo 8. */ +#define CSEQDIFF(i1, i2) (((i1) + 8 - (i2)) & 07) + +/* Packet types. These are from the tt field. + CONTROL -- control packet + ALTCHAN -- alternate channel; not used by UUCP + DATA -- full data segment + SHORTDATA -- less than full data segment (all the bytes specified by + the packet length <k> are always transferred). Let <u> be the number + of bytes in the data segment not to be used. If <u> <= 0x7f, the first + byte of the data segment is <u> and the data follows. If <u> > 0x7f, + the first byte of the data segment is 0x80 | (<u> & 0x7f), the second + byte of the data segment is <u> >> 7, and the data follows. The + maximum possible data segment size is 2**12, so this handles all + possible cases. */ +#define CONTROL (0) +#define ALTCHAN (1) +#define DATA (2) +#define SHORTDATA (3) + +/* Control types. These are from the xxx field if the type (tt field) + is CONTROL. + + CLOSE -- close the connection + RJ -- reject; packet yyy last to be received correctly + SRJ -- selective reject; reject only packet yyy (not used by UUCP) + RR -- receiver ready; packet yyy received correctly + INITC -- third step of initialization; yyy holds window size + INITB -- second step of initialization; yyy holds maximum <k> value - 1 + INITA -- first step of initialization; yyy holds window size. + + The yyy value for RR is the same as the yyy value for an ordinary + data packet. */ +#define CLOSE (1) +#define RJ (2) +#define SRJ (3) +#define RR (4) +#define INITC (5) +#define INITB (6) +#define INITA (7) + +/* Maximum amount of data in a single packet. This is set by the <k> + field in the header; the amount of data in a packet is + 2 ** (<k> + 4). <k> ranges from 1 to 8. */ + +#define CMAXDATAINDEX (8) + +#define CMAXDATA (1 << (CMAXDATAINDEX + 4)) + +/* Maximum window size. */ +#define CMAXWINDOW (7) + +/* Defaults for the protocol parameters. These may all be changed by + using the ``protocol-parameter g'' command, so there is no + particular reason to change the values given here. */ + +/* The desired window size. This is what we tell the other system to + use. It must be between 1 and 7, and there's no reason to use less + than 7. Protocol parameter ``window''. */ +#define IWINDOW (7) + +/* The desired packet size. Many implementations only support 64 byte + packets. Protocol parameter ``packet-size''. */ +#define IPACKSIZE (64) + +/* The number of times to retry the exchange of INIT packets when + starting the protocol. Protocol parameter ``startup-retries''. */ +#define CSTARTUP_RETRIES (8) + +/* The timeout to use when waiting for an INIT packet when starting up + the protocol. Protocol parameter ``init-timeout''. */ +#define CEXCHANGE_INIT_TIMEOUT (10) + +/* The number of times to retry sending and waiting for a single INIT + packet when starting the protocol. This controls a single INIT + packet, while CSTARTUP_RETRIES controls how many times to try the + entire INIT sequence. Protocol parameter ``init-retries''. */ +#define CEXCHANGE_INIT_RETRIES (4) + +/* The timeout to use when waiting for a packet. Protocol parameter + ``timeout''. */ +#define CTIMEOUT (10) + +/* The number of times to retry waiting for a packet. Each time the + timeout fails we send a copy of our last data packet or a reject + message for the packet we expect from the other side, depending on + whether we are waiting for an acknowledgement or a data packet. + This is the number of times we try doing that and then waiting + again. Protocol parameter ``retries''. */ +#define CRETRIES (6) + +/* If we see more than this much unrecognized data, we drop the + connection. This must be larger than a single packet size, which + means it must be larger than 4096 (the largest possible packet + size). Protocol parameter ``garbage''. */ +#define CGARBAGE (10000) + +/* If we see more than this many protocol errors, we drop the + connection. Protocol parameter ``errors''. */ +#define CERRORS (100) + +/* Default decay rate. Each time we send or receive this many packets + succesfully, we decrement the error level by one (protocol + parameter ``error-decay''). */ +#define CERROR_DECAY (10) + +/* If this value is non-zero, it will be used as the remote window + size regardless of what the other side requested. This can be + useful for dealing with some particularly flawed packages. This + default value should always be 0, and protocol parameter + ``remote-window'' should be used for the affected systems. */ +#define IREMOTE_WINDOW (0) + +/* If this value is non-zero, it will be used as the packet size to + send to the remote system regardless of what it requested. It's + difficult to imagine any circumstances where you would want to set + this. Protocol parameter ``remote-packet-size''. */ +#define IREMOTE_PACKSIZE (0) + +/* Local variables. */ + +/* Next sequence number to send. */ +static int iGsendseq; + +/* Last sequence number that has been acked. */ +static int iGremote_ack; + +/* Last sequence number to be retransmitted. */ +static int iGretransmit_seq; + +/* Last sequence number we have received. */ +static int iGrecseq; + +/* Last sequence number we have acked. */ +static int iGlocal_ack; + +/* Window size to request (protocol parameter ``window''). */ +static int iGrequest_winsize = IWINDOW; + +/* Packet size to request (protocol parameter ``packet-size''). */ +static int iGrequest_packsize = IPACKSIZE; + +/* Remote window size (set during handshake). */ +static int iGremote_winsize; + +/* Forced remote window size (protocol parameter ``remote-window''). */ +static int iGforced_remote_winsize = IREMOTE_WINDOW; + +/* Remote segment size (set during handshake). This is one less than + the value in a packet header. */ +static int iGremote_segsize; + +/* Remote packet size (set based on iGremote_segsize). */ +static size_t iGremote_packsize; + +/* Forced remote packet size (protocol parameter + ``remote-packet-size''). */ +static int iGforced_remote_packsize = IREMOTE_PACKSIZE; + +/* Recieved control byte. */ +static int iGpacket_control; + +/* Number of times to retry the initial handshake. Protocol parameter + ``startup-retries''. */ +static int cGstartup_retries = CSTARTUP_RETRIES; + +/* Number of times to retry sending an initial control packet. + Protocol parameter ``init-retries''. */ +static int cGexchange_init_retries = CEXCHANGE_INIT_RETRIES; + +/* Timeout (seconds) for receiving an initial control packet. + Protocol parameter ``init-timeout''. */ +static int cGexchange_init_timeout = CEXCHANGE_INIT_TIMEOUT; + +/* Timeout (seconds) for receiving a data packet. Protocol parameter + ``timeout''. */ +static int cGtimeout = CTIMEOUT; + +/* Maximum number of timeouts when receiving a data packet or + acknowledgement. Protocol parameter ``retries''. */ +static int cGretries = CRETRIES; + +/* Amount of garbage data we are prepared to see before giving up. + Protocol parameter ``garbage''. */ +static int cGgarbage_data = CGARBAGE; + +/* Maximum number of errors we are prepared to see before giving up. + Protocol parameter ``errors''. */ +static int cGmax_errors = CERRORS; + +/* Each time we receive this many packets succesfully, we decrement + the error level by one (protocol parameter ``error-decay''). */ +static int cGerror_decay = CERROR_DECAY; + +/* Whether to use shorter packets when possible. Protocol parameter + ``short-packets''. */ +static boolean fGshort_packets = TRUE; + +/* Protocol parameter commands. */ +struct uuconf_cmdtab asGproto_params[] = +{ + { "window", UUCONF_CMDTABTYPE_INT, (pointer) &iGrequest_winsize, NULL }, + { "packet-size", UUCONF_CMDTABTYPE_INT, (pointer) &iGrequest_packsize, + NULL }, + { "startup-retries", UUCONF_CMDTABTYPE_INT, (pointer) &cGstartup_retries, + NULL }, + { "init-timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cGexchange_init_timeout, + NULL }, + { "init-retries", UUCONF_CMDTABTYPE_INT, (pointer) &cGexchange_init_retries, + NULL }, + { "timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cGtimeout, NULL }, + { "retries", UUCONF_CMDTABTYPE_INT, (pointer) &cGretries, NULL }, + { "garbage", UUCONF_CMDTABTYPE_INT, (pointer) &cGgarbage_data, NULL }, + { "errors", UUCONF_CMDTABTYPE_INT, (pointer) &cGmax_errors, NULL }, + { "error-decay", UUCONF_CMDTABTYPE_INT, (pointer) &cGerror_decay, NULL }, + { "remote-window", UUCONF_CMDTABTYPE_INT, + (pointer) &iGforced_remote_winsize, NULL }, + { "remote-packet-size", UUCONF_CMDTABTYPE_INT, + (pointer) &iGforced_remote_packsize, NULL }, + { "short-packets", UUCONF_CMDTABTYPE_BOOLEAN, (pointer) &fGshort_packets, + NULL }, + { NULL, 0, NULL, NULL } +}; + +/* Statistics. */ + +/* Number of packets we have sent. */ +static long cGsent_packets; + +/* Number of packets we have resent (these are not included in + cGsent_packets). */ +static long cGresent_packets; + +/* Number of packets we have delayed sending (these should not be + counted in cGresent_packets). */ +static long cGdelayed_packets; + +/* Number of packets we have received. */ +static long cGrec_packets; + +/* Number of packets rejected because the header was bad. */ +static long cGbad_hdr; + +/* Number of packets rejected because the checksum was bad. */ +static long cGbad_checksum; + +/* Number of packets received out of order. */ +static long cGbad_order; + +/* Number of packets rejected by receiver (number of RJ packets + received). */ +static long cGremote_rejects; + +/* The error level. This is the total number of errors as adjusted by + cGerror_decay. */ +static long cGerror_level; + +/* Each time we send an RJ, we can expect several out of order of + packets, because the other side will probably have sent a full + window by the time it sees the RJ. This variable keeps track of + the number of out of order packets we expect to see. We don't + count expected out of order packets against the error level. This + is reset to 0 when an in order packet is received. */ +static int cGexpect_bad_order; + +#if DEBUG > 1 +/* Control packet names used for debugging. */ +static const char * const azGcontrol[] = +{"?0?", "CLOSE", "RJ", "SRJ", "RR", "INITC", "INITB", "INITA"}; +#endif + +/* Local functions. */ +static boolean fgexchange_init P((struct sdaemon *qdaemon, int ictl, + int ival, int *piset)); +static boolean fgsend_control P((struct sdaemon *qdaemon, int ictl, + int ival)); +static char *zgadjust_ack P((int iseq)); +static boolean fgwait_for_packet P((struct sdaemon *qdaemon, + boolean freturncontrol, int ctimeout, + int cretries)); +static boolean fgsend_acks P((struct sdaemon *qdaemon)); +static boolean fggot_ack P((struct sdaemon *qdaemon, int iack)); +static boolean fgprocess_data P((struct sdaemon *qdaemon, boolean fdoacks, + boolean freturncontrol, + boolean *pfexit, size_t *pcneed, + boolean *pffound)); +static boolean fginit_sendbuffers P((boolean fallocate)); +static boolean fgcheck_errors P((struct sdaemon *qdaemon)); +static int igchecksum P((const char *zdata, size_t clen)); +static int igchecksum2 P((const char *zfirst, size_t cfirst, + const char *zsecond, size_t csecond)); + +/* Start the protocol. This requires a three way handshake. Both sides + must send and receive an INITA packet, an INITB packet, and an INITC + packet. The INITA and INITC packets contain the window size, and the + INITB packet contains the packet size. */ + +boolean +fgstart (qdaemon, pzlog) + struct sdaemon *qdaemon; + char **pzlog; +{ + int iseg; + int i; + boolean fgota, fgotb; + + *pzlog = NULL; + + /* The 'g' protocol requires a full eight bit interface. */ + if (! fconn_set (qdaemon->qconn, PARITYSETTING_NONE, + STRIPSETTING_EIGHTBITS, XONXOFF_OFF)) + return FALSE; + + iGsendseq = 1; + iGremote_ack = 0; + iGretransmit_seq = -1; + iGrecseq = 0; + iGlocal_ack = 0; + cGsent_packets = 0; + cGresent_packets = 0; + cGdelayed_packets = 0; + cGrec_packets = 0; + cGbad_hdr = 0; + cGbad_checksum = 0; + cGbad_order = 0; + cGremote_rejects = 0; + cGerror_level = 0; + cGexpect_bad_order = 0; + + /* We must determine the segment size based on the packet size + which may have been modified by a protocol parameter command. + A segment size of 2^n is passed as n - 5. */ + i = iGrequest_packsize; + iseg = -1; + while (i > 0) + { + ++iseg; + i >>= 1; + } + iseg -= 5; + if (iseg < 0 || iseg > 7) + { + ulog (LOG_ERROR, "Illegal packet size %d for '%c' protocol", + iGrequest_packsize, qdaemon->qproto->bname); + iseg = 1; + } + + fgota = FALSE; + fgotb = FALSE; + for (i = 0; i < cGstartup_retries; i++) + { + if (fgota) + { + if (! fgsend_control (qdaemon, INITA, iGrequest_winsize)) + return FALSE; + } + else + { + if (! fgexchange_init (qdaemon, INITA, iGrequest_winsize, + &iGremote_winsize)) + continue; + } + fgota = TRUE; + + if (fgotb) + { + if (! fgsend_control (qdaemon, INITB, iseg)) + return FALSE; + } + else + { + if (! fgexchange_init (qdaemon, INITB, iseg, &iGremote_segsize)) + continue; + } + fgotb = TRUE; + + if (! fgexchange_init (qdaemon, INITC, iGrequest_winsize, + &iGremote_winsize)) + continue; + + /* We have succesfully connected. Determine the remote packet + size. */ + iGremote_packsize = 1 << (iGremote_segsize + 5); + + /* If the user requested us to force specific remote window and + packet sizes, do so now. */ + if (iGforced_remote_winsize > 0 + && iGforced_remote_winsize <= CMAXWINDOW) + iGremote_winsize = iGforced_remote_winsize; + + if (iGforced_remote_packsize >= 32 + && iGforced_remote_packsize <= 4096) + { + /* Force the value to a power of two. */ + i = iGforced_remote_packsize; + iseg = -1; + while (i > 0) + { + ++iseg; + i >>= 1; + } + iGremote_packsize = 1 << iseg; + iGremote_segsize = iseg - 5; + } + + /* Set up packet buffers to use. We don't do this until we know + the maximum packet size we are going to send. */ + if (! fginit_sendbuffers (TRUE)) + return FALSE; + + *pzlog = zbufalc (sizeof "protocol '' packet size window " + 50); + sprintf (*pzlog, "protocol '%c' packet size %d window %d", + qdaemon->qproto->bname, (int) iGremote_packsize, + (int) iGremote_winsize); + + return TRUE; + } + + DEBUG_MESSAGE0 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgstart: Protocol startup failed"); + + return FALSE; +} + +/* The 'G' protocol is identical to the 'g' protocol, except that + short packets are never supported. */ + +boolean +fbiggstart (qdaemon, pzlog) + struct sdaemon *qdaemon; + char **pzlog; +{ + fGshort_packets = FALSE; + return fgstart (qdaemon, pzlog); +} + +/* Exchange initialization messages with the other system. + + A problem: + + We send INITA; it gets received + We receive INITA + We send INITB; it gets garbled + We receive INITB + + We have seen and sent INITB, so we start to send INITC. The other + side as sent INITB but not seen it, so it times out and resends + INITB. We will continue sending INITC and the other side will + continue sending INITB until both sides give up and start again + with INITA. + + It might seem as though if we are sending INITC and receive INITB, + we should resend our INITB, but this could cause infinite echoing + of INITB on a long-latency line. Rather than risk that, I have + implemented a fast drop-back procedure. If we are sending INITB and + receive INITC, the other side has gotten ahead of us. We immediately + fail and begin again with INITA. For the other side, if we are + sending INITC and see INITA, we also immediately fail back to INITA. + + Unfortunately, this doesn't work for the other case, in which we + are sending INITB but the other side has not yet seen INITA. As + far as I can see, if this happens we just have to wait until we + time out and resend INITA. */ + +static boolean +fgexchange_init (qdaemon, ictl, ival, piset) + struct sdaemon *qdaemon; + int ictl; + int ival; + int *piset; +{ + int i; + + /* The three-way handshake should be independent of who initializes + it, but it seems that some versions of uucico assume that the + caller sends first and the callee responds. This only matters if + we are the callee and the first packet is garbled. If we send a + packet, the other side will assume that we must have seen the + packet they sent and will never time out and send it again. + Therefore, if we are the callee we don't send a packet the first + time through the loop. This can still fail, but should usually + work, and, after all, if the initialization packets are received + correctly there will be no problem no matter what we do. */ + for (i = 0; i < cGexchange_init_retries; i++) + { + long itime; + int ctimeout; + + if (qdaemon->fcaller || i > 0) + { + if (! fgsend_control (qdaemon, ictl, ival)) + return FALSE; + } + + itime = ixsysdep_time ((long *) NULL); + ctimeout = cGexchange_init_timeout; + + do + { + long inewtime; + + /* We pass 0 as the retry count to fgwait_for_packet because + we want to handle retries here and because if it retried + it would send a packet, which would be bad. */ + if (! fgwait_for_packet (qdaemon, TRUE, ctimeout, 0)) + break; + + if (CONTROL_TT (iGpacket_control) == CONTROL) + { + if (CONTROL_XXX (iGpacket_control) == ictl) + { + *piset = CONTROL_YYY (iGpacket_control); + + /* If we didn't already send our initialization + packet, send it now. */ + if (! qdaemon->fcaller && i == 0) + { + if (! fgsend_control (qdaemon, ictl, ival)) + return FALSE; + } + + return TRUE; + } + + /* If the other side is farther along than we are, + we have lost a packet. Fail immediately back to + INITA (but don't fail if we are already doing INITA, + since that would count against cStart_retries more + than it should). */ + if (CONTROL_XXX (iGpacket_control) < ictl && ictl != INITA) + return FALSE; + + /* If we are sending INITC and we receive an INITA, the other + side has failed back (we know this because we have + seen an INITB from them). Fail back ourselves to + start the whole handshake over again. */ + if (CONTROL_XXX (iGpacket_control) == INITA && ictl == INITC) + return FALSE; + + /* As a special hack, if we are sending INITC and we + receive INITB, we update the segment size from the + packet. This permits a second INITB to override the + first one. It would be nice to do this in a cleaner + way. */ + if (CONTROL_XXX (iGpacket_control) == INITB && ictl == INITC) + iGremote_segsize = CONTROL_YYY (iGpacket_control); + } + + inewtime = ixsysdep_time ((long *) NULL); + ctimeout -= inewtime - itime; + } + while (ctimeout > 0); + } + + return FALSE; +} + +/* Shut down the protocol. */ + +boolean +fgshutdown (qdaemon) + struct sdaemon *qdaemon; +{ + (void) fgsend_control (qdaemon, CLOSE, 0); + (void) fgsend_control (qdaemon, CLOSE, 0); + (void) fginit_sendbuffers (FALSE); + + /* The count of sent packets may not be accurate, because some of + them may have not been sent yet if the connection failed in the + middle (the ones that counted for cGdelayed_packets). I don't + think it's worth being precise. */ + ulog (LOG_NORMAL, + "Protocol '%c' packets: sent %ld, resent %ld, received %ld", + qdaemon->qproto->bname, cGsent_packets, + cGresent_packets - cGdelayed_packets, cGrec_packets); + if (cGbad_hdr != 0 + || cGbad_checksum != 0 + || cGbad_order != 0 + || cGremote_rejects != 0) + ulog (LOG_NORMAL, + "Errors: header %ld, checksum %ld, order %ld, remote rejects %ld", + cGbad_hdr, cGbad_checksum, cGbad_order, cGremote_rejects); + + /* Reset all the parameters to their default values, so that the + protocol parameters used for this connection do not affect the + next one. */ + iGrequest_winsize = IWINDOW; + iGrequest_packsize = IPACKSIZE; + cGstartup_retries = CSTARTUP_RETRIES; + cGexchange_init_timeout = CEXCHANGE_INIT_TIMEOUT; + cGexchange_init_retries = CEXCHANGE_INIT_RETRIES; + cGtimeout = CTIMEOUT; + cGretries = CRETRIES; + cGgarbage_data = CGARBAGE; + cGmax_errors = CERRORS; + cGerror_decay = CERROR_DECAY; + iGforced_remote_winsize = IREMOTE_WINDOW; + iGforced_remote_packsize = IREMOTE_PACKSIZE; + fGshort_packets = TRUE; + + return TRUE; +} + +/* Send a command string. We send packets containing the string until + the entire string has been sent. Each packet is full. */ + +/*ARGSUSED*/ +boolean +fgsendcmd (qdaemon, z, ilocal, iremote) + struct sdaemon *qdaemon; + const char *z; + int ilocal; + int iremote; +{ + size_t clen; + boolean fagain; + + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, "fgsendcmd: Sending command \"%s\"", z); + + clen = strlen (z); + + do + { + char *zpacket; + size_t cdummy; + + zpacket = zggetspace (qdaemon, &cdummy); + + if (clen < iGremote_packsize) + { + size_t csize; + + /* If the remote packet size is larger than 64 (the default, + which may indicate an older UUCP package), try to fit + this command into a smaller packet. We still always send + a complete packet, though. */ + if (iGremote_packsize <= 64 || ! fGshort_packets) + csize = iGremote_packsize; + else + { + csize = 32; + while (csize <= clen) + csize <<= 1; + } + + memcpy (zpacket, z, clen); + bzero (zpacket + clen, csize - clen); + fagain = FALSE; + + if (! fgsenddata (qdaemon, zpacket, csize, 0, 0, (long) 0)) + return FALSE; + } + else + { + memcpy (zpacket, z, iGremote_packsize); + z += iGremote_packsize; + clen -= iGremote_packsize; + fagain = TRUE; + + if (! fgsenddata (qdaemon, zpacket, iGremote_packsize, + 0, 0, (long) 0)) + return FALSE; + } + } + while (fagain); + + return TRUE; +} + +/* We keep an array of buffers to retransmit as necessary. Rather + than waste static space on large buffer sizes, we allocate the + buffers once we know how large the other system expects them to be. + The sequence numbers used in the 'g' protocol are only three bits + long, so we allocate eight buffers and maintain a correspondence + between buffer index and sequence number. This always wastes some + buffer space, but it's easy to implement. + + We leave room at the front of the buffer for the frame header and + two additional bytes. The two extra bytes are used for short + packets, which essentially use a longer header and shorter data. + We do this to avoid moving the data. We zero out any unused bytes + before the frame, so we can locate the real header given a buffer + by finding the first non-zero byte (which will be one of the first + three bytes in the buffer). */ + +#define CSENDBUFFERS (CMAXWINDOW + 1) + +static char *azGsendbuffers[CSENDBUFFERS]; + +static boolean +fginit_sendbuffers (fallocate) + boolean fallocate; +{ + int i; + + /* Free up any remaining old buffers. */ + for (i = 0; i < CSENDBUFFERS; i++) + { + xfree ((pointer) azGsendbuffers[i]); + if (fallocate) + { + azGsendbuffers[i] = (char *) malloc (CFRAMELEN + 2 + + iGremote_packsize); + if (azGsendbuffers[i] == NULL) + return FALSE; + + /* This bzero might not seem necessary, since before we send + out each packet we zero out any non-data bytes. However, + if we receive an SRJ at the start of the conversation, we + will send out the packet before it has been set to + anything, thus sending the contents of our heap. We + avoid this by using bzero. */ + bzero (azGsendbuffers[i], CFRAMELEN + 2 + iGremote_packsize); + } + else + azGsendbuffers[i] = NULL; + } + return TRUE; +} + +/* Allocate a packet to send out. The return value of this function + must be filled in and passed to fgsenddata, or discarded. This + will ensure that the buffers and iGsendseq stay in synch. Set + *pclen to the amount of data to place in the buffer. */ + +/*ARGSUSED*/ +char * +zggetspace (qdaemon, pclen) + struct sdaemon *qdaemon; + size_t *pclen; +{ + *pclen = iGremote_packsize; + return azGsendbuffers[iGsendseq] + CFRAMELEN + 2; +} + +/* Send out a data packet. This computes the checksum, sets up the + header, and sends the packet out. The argument zdata should point + to the return value of zggetspace. */ + +/*ARGSIGNORED*/ +boolean +fgsenddata (qdaemon, zdata, cdata, ilocal, iremote, ipos) + struct sdaemon *qdaemon; + char *zdata; + size_t cdata; + int ilocal; + int iremote; + long ipos; +{ + char *z; + int itt, iseg; + size_t csize; + int iclr1, iclr2; + unsigned short icheck; + + /* Set the initial length bytes. See the description at the definition + of SHORTDATA, above. */ + itt = DATA; + csize = iGremote_packsize; + iseg = iGremote_segsize + 1; + +#if DEBUG > 0 + if (cdata > csize) + ulog (LOG_FATAL, "fgsend_packet: Packet size too large"); +#endif + + iclr1 = -1; + iclr2 = -2; + if (cdata < csize) + { + /* If the remote packet size is larger than 64, the default, we + can assume they can handle a smaller packet as well, which + will be more efficient to send. */ + if (iGremote_packsize > 64 && fGshort_packets) + { + /* The packet size is 1 << (iseg + 4). */ + iseg = 1; + csize = 32; + while (csize < cdata) + { + csize <<= 1; + ++iseg; + } + } + + if (csize != cdata) + { + size_t cshort; + + /* We have to add bytes which indicate how short the packet + is. We do this by pushing the header backward, which we + can do because we allocated two extra bytes for this + purpose. */ + iclr2 = 0; + itt = SHORTDATA; + cshort = csize - cdata; + if (cshort <= 127) + { + --zdata; + zdata[0] = (char) cshort; + zdata[-1] = '\0'; + bzero (zdata + cdata + 1, cshort - 1); + } + else + { + zdata -= 2; + zdata[0] = (char) (0x80 | (cshort & 0x7f)); + zdata[1] = (char) (cshort >> 7); + bzero (zdata + cdata + 2, cshort - 2); + iclr1 = 0; + } + } + } + + z = zdata - CFRAMELEN; + + /* Zero out the preceding bytes, in case the last time this buffer + was used those bytes were used. We need to zero out the initial + bytes so that we can find the true start of the packet in + zgadjust_ack. */ + z[iclr1] = '\0'; + z[iclr2] = '\0'; + + z[IFRAME_DLE] = DLE; + z[IFRAME_K] = (char) iseg; + + icheck = (unsigned short) igchecksum (zdata, csize); + + /* We're just about ready to go. Wait until there is room in the + receiver's window for us to send the packet. We do this now so + that we send the correct value for the last packet received. + Note that if iGsendseq == iGremote_ack, this means that the + sequence numbers are actually 8 apart, since the packet could not + have been acknowledged before it was sent; this can happen when + the window size is 7. */ + while (iGsendseq == iGremote_ack + || CSEQDIFF (iGsendseq, iGremote_ack) > iGremote_winsize) + { + if (! fgwait_for_packet (qdaemon, TRUE, cGtimeout, cGretries)) + return FALSE; + } + + /* Ack all packets up to the next one, since the UUCP protocol + requires that all packets be acked in order. */ + while (CSEQDIFF (iGrecseq, iGlocal_ack) > 1) + { + iGlocal_ack = INEXTSEQ (iGlocal_ack); + if (! fgsend_control (qdaemon, RR, iGlocal_ack)) + return FALSE; + } + iGlocal_ack = iGrecseq; + + z[IFRAME_CONTROL] = (char) ((itt << 6) | (iGsendseq << 3) | iGrecseq); + + iGsendseq = INEXTSEQ (iGsendseq); + + icheck = ((unsigned short) + ((0xaaaa - (icheck ^ (z[IFRAME_CONTROL] & 0xff))) & 0xffff)); + z[IFRAME_CHECKLOW] = (char) (icheck & 0xff); + z[IFRAME_CHECKHIGH] = (char) (icheck >> 8); + + z[IFRAME_XOR] = (char) (z[IFRAME_K] ^ z[IFRAME_CHECKLOW] + ^ z[IFRAME_CHECKHIGH] ^ z[IFRAME_CONTROL]); + + /* If we're waiting for acks of retransmitted packets, then don't + send this packet yet. The other side may not be ready for it + yet. Instead, code in fggot_ack will send the outstanding + packets when an ack is received. */ + ++cGsent_packets; + + if (iGretransmit_seq != -1) + { + ++cGdelayed_packets; + return TRUE; + } + + DEBUG_MESSAGE2 (DEBUG_PROTO, + "fgsenddata: Sending packet %d (%d bytes)", + CONTROL_XXX (z[IFRAME_CONTROL]), cdata); + + return fsend_data (qdaemon->qconn, z, CFRAMELEN + csize, TRUE); +} + +/* Recompute the control byte and checksum of a packet so that it + includes the correct packet acknowledgement. This is called when a + packet is retransmitted to make sure the retransmission does not + confuse the other side. It returns a pointer to the start of the + packet, skipping the bytes that may be unused at the start of + azGsendbuffers[iseq]. */ + +static char * +zgadjust_ack (iseq) + int iseq; +{ + register char *z; + unsigned short icheck; + + z = azGsendbuffers[iseq]; + if (*z == '\0') + ++z; + if (*z == '\0') + ++z; + + /* If the received packet number is the same, there is nothing + to do. */ + if (CONTROL_YYY (z[IFRAME_CONTROL]) == iGrecseq) + return z; + + /* Get the old checksum. */ + icheck = (unsigned short) (((z[IFRAME_CHECKHIGH] & 0xff) << 8) + | (z[IFRAME_CHECKLOW] & 0xff)); + icheck = ((unsigned short) + (((0xaaaa - icheck) ^ (z[IFRAME_CONTROL] & 0xff)) & 0xffff)); + + /* Update the control byte. */ + z[IFRAME_CONTROL] = (char) ((z[IFRAME_CONTROL] &~ 07) | iGrecseq); + + /* Create the new checksum. */ + icheck = ((unsigned short) + ((0xaaaa - (icheck ^ (z[IFRAME_CONTROL] & 0xff))) & 0xffff)); + z[IFRAME_CHECKLOW] = (char) (icheck & 0xff); + z[IFRAME_CHECKHIGH] = (char) (icheck >> 8); + + /* Update the XOR byte. */ + z[IFRAME_XOR] = (char) (z[IFRAME_K] ^ z[IFRAME_CHECKLOW] + ^ z[IFRAME_CHECKHIGH] ^ z[IFRAME_CONTROL]); + + return z; +} + +/* Send a control packet. These are fairly simple to construct. It + seems reasonable to me that we should be able to send a control + packet at any time, even if the receive window is closed. In + particular, we don't want to delay when sending a CLOSE control + message. If I'm wrong, it can be changed easily enough. */ + +static boolean +fgsend_control (qdaemon, ixxx, iyyy) + struct sdaemon *qdaemon; + int ixxx; + int iyyy; +{ + char ab[CFRAMELEN]; + int ictl; + unsigned short icheck; + +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_PROTO) || + (FDEBUGGING (DEBUG_ABNORMAL) && ixxx != RR)) + ulog (LOG_DEBUG, "fgsend_control: Sending control %s %d", + azGcontrol[ixxx], iyyy); +#endif + + ab[IFRAME_DLE] = DLE; + ab[IFRAME_K] = KCONTROL; + + ictl = (CONTROL << 6) | (ixxx << 3) | iyyy; + icheck = (unsigned short) (0xaaaa - ictl); + ab[IFRAME_CHECKLOW] = (char) (icheck & 0xff); + ab[IFRAME_CHECKHIGH] = (char) (icheck >> 8); + + ab[IFRAME_CONTROL] = (char) ictl; + + ab[IFRAME_XOR] = (char) (ab[IFRAME_K] ^ ab[IFRAME_CHECKLOW] + ^ ab[IFRAME_CHECKHIGH] ^ ab[IFRAME_CONTROL]); + + return fsend_data (qdaemon->qconn, ab, (size_t) CFRAMELEN, TRUE); +} + +/* Wait for data to come in. This continues processing until a + complete file or command has been received. */ + +boolean +fgwait (qdaemon) + struct sdaemon *qdaemon; +{ + return fgwait_for_packet (qdaemon, FALSE, cGtimeout, cGretries); +} + +/* Get a packet. This is called when we have nothing to send, but + want to wait for a packet to come in. If freturncontrol is TRUE, + this will return after getting any control packet. Otherwise, it + will continue to receive packets until a complete file or a + complete command has been received. The timeout and the number of + retries are specified as arguments. The function returns FALSE if + an error occurs or if cretries timeouts of ctimeout seconds were + exceeded. */ + +static boolean +fgwait_for_packet (qdaemon, freturncontrol, ctimeout, cretries) + struct sdaemon *qdaemon; + boolean freturncontrol; + int ctimeout; + int cretries; +{ + int ctimeouts; + int cgarbage; + int cshort; + + ctimeouts = 0; + cgarbage = 0; + cshort = 0; + + while (TRUE) + { + boolean fexit; + size_t cneed; + boolean ffound; + size_t crec; + + if (! fgprocess_data (qdaemon, TRUE, freturncontrol, &fexit, + &cneed, &ffound)) + return FALSE; + + if (fexit) + return TRUE; + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fgwait_for_packet: Need %lu bytes", + (unsigned long) cneed); + + if (ffound) + { + ctimeouts = 0; + cgarbage = 0; + } + else + { + if (cgarbage > cGgarbage_data) + { + ulog (LOG_ERROR, "Too much unrecognized data"); + return FALSE; + } + } + + if (! freceive_data (qdaemon->qconn, cneed, &crec, ctimeout, TRUE)) + return FALSE; + + cgarbage += crec; + + if (crec != 0) + { + /* If we don't get enough data twice in a row, we may have + dropped some data and still be looking for the end of a + large packet. Incrementing iPrecstart will force + fgprocess_data to skip that packet and look through the + rest of the data. In some situations, this will be a + mistake. */ + if (crec >= cneed) + cshort = 0; + else + { + ++cshort; + if (cshort > 1) + { + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + cshort = 0; + } + } + } + else + { + /* The read timed out. If we have an unacknowledged packet, + send it again. Otherwise, send an RJ with the last + packet we received correctly. */ + ++ctimeouts; + if (ctimeouts > cretries) + { + if (cretries > 0) + ulog (LOG_ERROR, "Timed out waiting for packet"); + return FALSE; + } + + if (INEXTSEQ (iGremote_ack) != iGsendseq) + { + int inext; + char *zsend; + + inext = INEXTSEQ (iGremote_ack); + + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgwait_for_packet: Resending packet %d", + inext); + + ++cGresent_packets; + zsend = zgadjust_ack (inext); + if (! fsend_data (qdaemon->qconn, zsend, + CFRAMELEN + CPACKLEN (zsend), TRUE)) + return FALSE; + iGretransmit_seq = inext; + } + else + { + /* Send all pending acks first, to avoid confusing + the other side. */ + if (iGlocal_ack != iGrecseq) + { + if (! fgsend_acks (qdaemon)) + return FALSE; + } + if (! fgsend_control (qdaemon, RJ, iGrecseq)) + return FALSE; + } + } + } +} + +/* Send acks for all packets we haven't acked yet. */ + +static boolean +fgsend_acks (qdaemon) + struct sdaemon *qdaemon; +{ + while (iGlocal_ack != iGrecseq) + { + iGlocal_ack = INEXTSEQ (iGlocal_ack); + if (! fgsend_control (qdaemon, RR, iGlocal_ack)) + return FALSE; + } + return TRUE; +} + +/* Handle an ack of a packet. According to Hanrahan's paper, this + acknowledges all previous packets. If this is an ack for a + retransmitted packet, continue by resending up to two more packets + following the retransmitted one. This should recover quickly from + a line glitch, while avoiding the problem of continual + retransmission. */ + +static boolean +fggot_ack (qdaemon, iack) + struct sdaemon *qdaemon; + int iack; +{ + int inext; + char *zsend; + + /* We only decrement the error level if we are not retransmitting + packets. We want to catch a sudden downgrade in line quality as + fast as possible. */ + if (cGerror_level > 0 + && iGretransmit_seq == -1 + && cGsent_packets % cGerror_decay == 0) + --cGerror_level; + cGexpect_bad_order = 0; + + /* Each time packet 0 is acknowledged, we call uwindow_acked since a + new window has been acked. */ + if (iack < iGremote_ack) + uwindow_acked (qdaemon, FALSE); + + iGremote_ack = iack; + + if (iGretransmit_seq == -1) + return TRUE; + + inext = INEXTSEQ (iGretransmit_seq); + if (inext == iGsendseq) + iGretransmit_seq = -1; + else + { + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fggot_ack: Sending packet %d", inext); + + ++cGresent_packets; + zsend = zgadjust_ack (inext); + if (! fsend_data (qdaemon->qconn, zsend, CFRAMELEN + CPACKLEN (zsend), + TRUE)) + return FALSE; + inext = INEXTSEQ (inext); + if (inext == iGsendseq) + iGretransmit_seq = -1; + else + { + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fggot_ack: Sending packet %d", inext); + + ++cGresent_packets; + zsend = zgadjust_ack (inext); + if (! fsend_data (qdaemon->qconn, zsend, + CFRAMELEN + CPACKLEN (zsend), TRUE)) + return FALSE; + iGretransmit_seq = inext; + } + } + + return TRUE; +} + +/* See if we've received more than the permitted number of errors. If + we receive a bad packet, we can expect a window full (less one) of + out of order packets to follow, so we discount cGbad_order + accordingly. */ + +static boolean +fgcheck_errors (qdaemon) + struct sdaemon *qdaemon; +{ + if (cGerror_level > cGmax_errors && cGmax_errors >= 0) + { + ulog (LOG_ERROR, "Too many '%c' protocol errors", + qdaemon->qproto->bname); + return FALSE; + } + + return TRUE; +} + +/* Process the receive buffer into a data packet, if possible. All + control packets are handled here. When a data packet is received, + fgprocess_data calls fgot_data with the data; if that sets its + pfexit argument to TRUE fgprocess_data will set *pfexit to TRUE and + return TRUE. Also, if the freturncontrol argument is TRUE + fgprocess_data will set *pfexit to TRUE and return TRUE. Otherwise + fgprocess_data will continue trying to process data. If some error + occurs, fgprocess_data will return FALSE. If there is not enough + data to form a complete packet, then *pfexit will be set to FALSE, + *pcneed will be set to the number of bytes needed to form a + complete packet (unless pcneed is NULL) and fgprocess_data will + return TRUE. If this function found a data packet, and pffound is + not NULL, it will set *pffound to TRUE; this can be used to tell + valid data from an endless stream of garbage and control packets. + If fdoacks is TRUE, received packets will be acknowledged; + otherwise they must be acknowledged later. */ + +static boolean +fgprocess_data (qdaemon, fdoacks, freturncontrol, pfexit, pcneed, pffound) + struct sdaemon *qdaemon; + boolean fdoacks; + boolean freturncontrol; + boolean *pfexit; + size_t *pcneed; + boolean *pffound; +{ + *pfexit = FALSE; + if (pffound != NULL) + *pffound = FALSE; + + while (iPrecstart != iPrecend) + { + char ab[CFRAMELEN]; + int i, iget, cwant; + unsigned short ihdrcheck, idatcheck; + const char *zfirst, *zsecond; + int cfirst, csecond; + boolean fduprr; + + /* Look for the DLE which must start a packet. */ + if (abPrecbuf[iPrecstart] != DLE) + { + char *zdle; + + cfirst = iPrecend - iPrecstart; + if (cfirst < 0) + cfirst = CRECBUFLEN - iPrecstart; + + zdle = memchr (abPrecbuf + iPrecstart, DLE, (size_t) cfirst); + + if (zdle == NULL) + { + iPrecstart = (iPrecstart + cfirst) % CRECBUFLEN; + continue; + } + + /* We don't need % CRECBUFLEN here because zdle - (abPrecbuf + + iPrecstart) < cfirst <= CRECBUFLEN - iPrecstart. */ + iPrecstart += zdle - (abPrecbuf + iPrecstart); + } + + /* Get the first six bytes into ab. */ + for (i = 0, iget = iPrecstart; + i < CFRAMELEN && iget != iPrecend; + i++, iget = (iget + 1) % CRECBUFLEN) + ab[i] = abPrecbuf[iget]; + + /* If there aren't six bytes, there is no packet. */ + if (i < CFRAMELEN) + { + if (pcneed != NULL) + *pcneed = CFRAMELEN - i; + return TRUE; + } + + /* Make sure these six bytes start a packet. The check on + IFRAME_DLE is basically a debugging check, since the above + code should have ensured that it will never fail. If this is + not the start of a packet, bump iPrecstart and loop around to + look for another DLE. */ + if (ab[IFRAME_DLE] != DLE + || ab[IFRAME_K] < 1 + || ab[IFRAME_K] > 9 + || ab[IFRAME_XOR] != (ab[IFRAME_K] ^ ab[IFRAME_CHECKLOW] + ^ ab[IFRAME_CHECKHIGH] ^ ab[IFRAME_CONTROL]) + || CONTROL_TT (ab[IFRAME_CONTROL]) == ALTCHAN) + { + ++cGbad_hdr; + ++cGerror_level; + + DEBUG_MESSAGE4 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgprocess_data: Bad header: K %d TT %d XOR byte %d calc %d", + ab[IFRAME_K] & 0xff, + CONTROL_TT (ab[IFRAME_CONTROL]), + ab[IFRAME_XOR] & 0xff, + (ab[IFRAME_K] + ^ ab[IFRAME_CHECKLOW] + ^ ab[IFRAME_CHECKHIGH] + ^ ab[IFRAME_CONTROL]) & 0xff); + + if (! fgcheck_errors (qdaemon)) + return FALSE; + + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + continue; + } + + /* The zfirst and cfirst pair point to the first set of data for + this packet; the zsecond and csecond point to the second set, + in case the packet wraps around the end of the buffer. */ + zfirst = abPrecbuf + iPrecstart + CFRAMELEN; + cfirst = 0; + zsecond = NULL; + csecond = 0; + + if (ab[IFRAME_K] == KCONTROL) + { + /* This is a control packet. It should not have any data. */ + if (CONTROL_TT (ab[IFRAME_CONTROL]) != CONTROL) + { + ++cGbad_hdr; + ++cGerror_level; + + DEBUG_MESSAGE0 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgprocess_data: Bad header: control packet with data"); + + if (! fgcheck_errors (qdaemon)) + return FALSE; + + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + continue; + } + + idatcheck = (unsigned short) (0xaaaa - ab[IFRAME_CONTROL]); + cwant = 0; + } + else + { + int cinbuf; + unsigned short icheck; + + /* This is a data packet. It should not be type CONTROL. */ + if (CONTROL_TT (ab[IFRAME_CONTROL]) == CONTROL) + { + ++cGbad_hdr; + ++cGerror_level; + + DEBUG_MESSAGE0 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgprocess_data: Bad header: data packet is type CONTROL"); + + if (! fgcheck_errors (qdaemon)) + return FALSE; + + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + continue; + } + + cinbuf = iPrecend - iPrecstart; + if (cinbuf < 0) + cinbuf += CRECBUFLEN; + cinbuf -= CFRAMELEN; + + /* Make sure we have enough data. If we don't, wait for + more. */ + + cwant = (int) CPACKLEN (ab); + if (cinbuf < cwant) + { + if (pcneed != NULL) + *pcneed = cwant - cinbuf; + return TRUE; + } + + /* Set up the data pointers and compute the checksum. */ + if (iPrecend >= iPrecstart) + cfirst = cwant; + else + { + cfirst = CRECBUFLEN - (iPrecstart + CFRAMELEN); + if (cfirst >= cwant) + cfirst = cwant; + else if (cfirst > 0) + { + zsecond = abPrecbuf; + csecond = cwant - cfirst; + } + else + { + /* Here cfirst is non-positive, so subtracting from + abPrecbuf will actually skip the appropriate number + of bytes at the start of abPrecbuf. */ + zfirst = abPrecbuf - cfirst; + cfirst = cwant; + } + } + + if (csecond == 0) + icheck = (unsigned short) igchecksum (zfirst, (size_t) cfirst); + else + icheck = (unsigned short) igchecksum2 (zfirst, (size_t) cfirst, + zsecond, + (size_t) csecond); + + idatcheck = ((unsigned short) + (((0xaaaa - (icheck ^ (ab[IFRAME_CONTROL] & 0xff))) + & 0xffff))); + } + + ihdrcheck = (unsigned short) (((ab[IFRAME_CHECKHIGH] & 0xff) << 8) + | (ab[IFRAME_CHECKLOW] & 0xff)); + + if (ihdrcheck != idatcheck) + { + DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgprocess_data: Bad checksum: header 0x%x, data 0x%x", + ihdrcheck, idatcheck); + + ++cGbad_checksum; + ++cGerror_level; + + if (! fgcheck_errors (qdaemon)) + return FALSE; + + /* If the checksum failed for a data packet, then if it was + the one we were expecting send an RJ, otherwise ignore + it. Previously if this code got the wrong packet number + it would send an RR, but that may confuse some Telebit + modems and it doesn't help in any case since the receiver + will probably just ignore the RR as a duplicate (that's + basically what this code does). If we totally missed the + packet we will time out and send an RJ in the function + fgwait_for_packet above. */ + if (CONTROL_TT (ab[IFRAME_CONTROL]) != CONTROL) + { + /* Make sure we've acked everything up to this point. */ + if (iGrecseq != iGlocal_ack) + { + if (! fgsend_acks (qdaemon)) + return FALSE; + } + + /* If this is the packet we wanted, tell the sender that + it failed. */ + if (CONTROL_XXX (ab[IFRAME_CONTROL]) == INEXTSEQ (iGrecseq)) + { + if (! fgsend_control (qdaemon, RJ, iGrecseq)) + return FALSE; + cGexpect_bad_order += iGrequest_winsize - 1; + } + } + + /* We can't skip the packet data after this, because if we + have lost incoming bytes the next DLE will be somewhere + in what we thought was the packet data. */ + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + continue; + } + + /* We have a packet; remove the processed bytes from the receive + buffer. */ + iPrecstart = (iPrecstart + cwant + CFRAMELEN) % CRECBUFLEN; + + /* Store the control byte for the handshake routines. */ + iGpacket_control = ab[IFRAME_CONTROL] & 0xff; + + /* Annoyingly, some UUCP packages appear to send an RR packet + rather than an RJ packet when they want a packet to be + resent. If we get a duplicate RR, we treat it as an RJ. */ + fduprr = FALSE; + if (CONTROL_TT (ab[IFRAME_CONTROL]) == CONTROL + && CONTROL_XXX (ab[IFRAME_CONTROL]) == RR + && iGremote_ack == CONTROL_YYY (ab[IFRAME_CONTROL]) + && INEXTSEQ (iGremote_ack) != iGsendseq) + { + DEBUG_MESSAGE0 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgprocess_data: Treating duplicate RR as RJ"); + fduprr = TRUE; + } + + /* Update the received sequence number from the yyy field of a + data packet or an RR control packet. If we've been delaying + sending packets until we received an ack, this may send out + some packets. */ + if (CONTROL_TT (ab[IFRAME_CONTROL]) != CONTROL + || CONTROL_XXX (ab[IFRAME_CONTROL]) == RR) + { + if (! fggot_ack (qdaemon, CONTROL_YYY (ab[IFRAME_CONTROL]))) + return FALSE; + } + + /* If this isn't a control message, make sure we have received + the expected packet sequence number, acknowledge the packet + if it's the right one, and process the data. */ + if (CONTROL_TT (ab[IFRAME_CONTROL]) != CONTROL) + { + if (CONTROL_XXX (ab[IFRAME_CONTROL]) != INEXTSEQ (iGrecseq)) + { + /* We got the wrong packet number. */ + DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgprocess_data: Got packet %d; expected %d", + CONTROL_XXX (ab[IFRAME_CONTROL]), + INEXTSEQ (iGrecseq)); + + if (cGexpect_bad_order > 0) + --cGexpect_bad_order; + else + { + ++cGbad_order; + ++cGerror_level; + if (! fgcheck_errors (qdaemon)) + return FALSE; + } + + /* This code used to send an RR to encourage the other + side to get back in synch, but that may confuse some + Telebit modems and does little good in any case, + since the other side will probably just ignore it + anyhow (that's what this code does). */ + continue; + } + + /* We got the packet we expected. */ + ++cGrec_packets; + if (cGerror_level > 0 + && cGrec_packets % cGerror_decay == 0) + --cGerror_level; + cGexpect_bad_order = 0; + + iGrecseq = INEXTSEQ (iGrecseq); + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fgprocess_data: Got packet %d", iGrecseq); + + /* Tell the caller that we found something. */ + if (pffound != NULL) + *pffound = TRUE; + + /* If we are supposed to do acknowledgements here, send back + an RR packet. */ + if (fdoacks) + { + if (! fgsend_acks (qdaemon)) + return FALSE; + } + + /* If this is a short data packet, adjust the data pointers + and lengths. */ + if (CONTROL_TT (ab[IFRAME_CONTROL]) == SHORTDATA) + { + int cshort, cmove; + + if ((zfirst[0] & 0x80) == 0) + { + cshort = zfirst[0] & 0xff; + cmove = 1; + } + else + { + int cbyte2; + + if (cfirst > 1) + cbyte2 = zfirst[1] & 0xff; + else + cbyte2 = zsecond[0] & 0xff; + cshort = (zfirst[0] & 0x7f) + (cbyte2 << 7); + cmove = 2; + } + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fgprocess_data: Packet short by %d", + cshort); + + /* Adjust the start of the buffer for the bytes used + by the count. */ + if (cfirst > cmove) + { + zfirst += cmove; + cfirst -= cmove; + } + else + { + zfirst = zsecond + (cmove - cfirst); + cfirst = csecond - (cmove - cfirst); + csecond = 0; + } + + /* Adjust the length of the buffer for the bytes we are + not supposed to consider. */ + cshort -= cmove; + if (csecond >= cshort) + csecond -= cshort; + else + { + cfirst -= cshort - csecond; + csecond = 0; + } + +#if DEBUG > 0 + /* This should not happen, but just in case. */ + if (cfirst < 0) + cfirst = 0; +#endif + } + + if (! fgot_data (qdaemon, zfirst, (size_t) cfirst, + zsecond, (size_t) csecond, + -1, -1, (long) -1, + INEXTSEQ (iGremote_ack) == iGsendseq, + pfexit)) + return FALSE; + + /* If fgot_data told us that we were finished, get out. */ + if (*pfexit) + return TRUE; + + /* If we've been asked to return control packets, get out + now. */ + if (freturncontrol) + { + *pfexit = TRUE; + return TRUE; + } + + continue; + } + + /* Handle control messages here. */ +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_PROTO) + || (FDEBUGGING (DEBUG_ABNORMAL) + && CONTROL_XXX (ab[IFRAME_CONTROL]) != RR)) + ulog (LOG_DEBUG, "fgprocess_data: Got control %s %d", + azGcontrol[CONTROL_XXX (ab[IFRAME_CONTROL])], + CONTROL_YYY (ab[IFRAME_CONTROL])); +#endif + + switch (CONTROL_XXX (ab[IFRAME_CONTROL])) + { + case CLOSE: + /* The other side has closed the connection. */ + if (fLog_sighup) + { + ulog (LOG_ERROR, "Received unexpected CLOSE packet"); + (void) fgsend_control (qdaemon, CLOSE, 0); + } + return FALSE; + case RR: + /* Acknowledge receipt of a packet. This was already handled + above, unless we are treating it as RJ. */ + if (! fduprr) + break; + /* Fall through. */ + case RJ: + /* The other side dropped a packet. Begin retransmission with + the packet following the one acknowledged. We don't + retransmit the packets immediately, but instead wait + for the first one to be acked. This prevents us from + sending an entire window several times if we get several + RJ packets. */ + iGremote_ack = CONTROL_YYY (ab[IFRAME_CONTROL]); + iGretransmit_seq = INEXTSEQ (iGremote_ack); + if (iGretransmit_seq == iGsendseq) + iGretransmit_seq = -1; + else + { + char *zpack; + + DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgprocess_data: Remote reject: next %d resending %d", + iGsendseq, iGretransmit_seq); + + ++cGresent_packets; + ++cGremote_rejects; + ++cGerror_level; + if (! fgcheck_errors (qdaemon)) + return FALSE; + zpack = zgadjust_ack (iGretransmit_seq); + if (! fsend_data (qdaemon->qconn, zpack, + CFRAMELEN + CPACKLEN (zpack), + TRUE)) + return FALSE; + } + break; + case SRJ: + /* Selectively reject a particular packet. This is not used + by UUCP, but it's easy to support. */ + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fgprocess_data: Selective reject of %d", + CONTROL_YYY (ab[IFRAME_CONTROL])); + { + char *zpack; + + ++cGresent_packets; + ++cGremote_rejects; + ++cGerror_level; + zpack = zgadjust_ack (CONTROL_YYY (ab[IFRAME_CONTROL])); + if (! fsend_data (qdaemon->qconn, zpack, + CFRAMELEN + CPACKLEN (zpack), + TRUE)) + return FALSE; + } + break; + case INITC: + case INITB: + case INITA: + /* Ignore attempts to reinitialize. */ + break; + } + + /* If we've been asked to return control packets, get out. */ + if (freturncontrol) + { + *pfexit = TRUE; + return TRUE; + } + + /* Loop around to look for the next packet, if any. */ + } + + /* There is no data left in the receive buffer. */ + if (pcneed != NULL) + *pcneed = CFRAMELEN; + return TRUE; +} + +/* Compute the 'g' protocol checksum. This is unfortunately rather + awkward. This is the most time consuming code in the entire + program. It's also not a great checksum, since it can be fooled + by some single bit errors. */ + +/* Sorry about this knavery, but it speeds up the VAX code + significantly. It would be better to rewrite the whole routine in + assembler. */ +#ifdef __GNUC__ +#ifdef __vax__ +#define VAX_ASM 1 +#endif +#endif + +#if VAX_ASM +#define ROTATE(i) \ + asm ("cvtwl %1,%0\n\trotl $1,%0,%0" : "=g" (i) : "g" (i)) +#else +#define ROTATE(i) i += i + ((i & 0x8000) >> 15) +#endif + +#define ITERATION \ + /* Rotate ichk1 left. */ \ + ROTATE (ichk1); \ + \ + /* The guts of the checksum. */ \ + b = BUCHAR (*z++); \ + if (b != 0) \ + { \ + ichk1 &= 0xffff; \ + ichk1 += b; \ + ichk2 += ichk1 ^ c; \ + if ((ichk1 >> 16) != 0) \ + ichk1 ^= ichk2; \ + } \ + else \ + { \ + ichk2 += ichk1 ^ c; \ + ichk1 ^= ichk2; \ + } \ + \ + --c + +static int +igchecksum (z, c) + register const char *z; + register size_t c; +{ + register unsigned long ichk1, ichk2; + + ichk1 = 0xffff; + ichk2 = 0; + + do + { + register unsigned int b; + + ITERATION; + ITERATION; + ITERATION; + ITERATION; + } + while (c > 0); + + return ichk1 & 0xffff; +} + +/* We use a separate function compute the checksum if the block is + split around the end of the receive buffer since it occurs much + less frequently and the checksum is already high up in the + profiles. These functions are almost identical, and this one + actually only has a few more instructions in the inner loop. */ + +static int +igchecksum2 (zfirst, cfirst, zsecond, csecond) + const char *zfirst; + size_t cfirst; + const char *zsecond; + size_t csecond; +{ + register unsigned long ichk1, ichk2; + register const char *z; + register size_t c; + + z = zfirst; + c = cfirst + csecond; + + ichk1 = 0xffff; + ichk2 = 0; + + do + { + register unsigned int b; + + ITERATION; + + /* If the first buffer has been finished, switch to the second. */ + --cfirst; + if (cfirst == 0) + z = zsecond; + } + while (c > 0); + + return ichk1 & 0xffff; +} diff --git a/gnu/libexec/uucp/uucico/proti.c b/gnu/libexec/uucp/uucico/proti.c new file mode 100644 index 0000000000000..d14d42b42db07 --- /dev/null +++ b/gnu/libexec/uucp/uucico/proti.c @@ -0,0 +1,1563 @@ +/* proti.c + The 'i' protocol. + + Copyright (C) 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char proti_rcsid[] = "$Id: proti.c,v 1.1 1993/08/05 18:27:12 conklin Exp $"; +#endif + +#include <ctype.h> +#include <errno.h> + +#include "uudefs.h" +#include "uuconf.h" +#include "conn.h" +#include "trans.h" +#include "system.h" +#include "prot.h" + +/* The 'i' protocol is a simple sliding window protocol, created by + me. It is in many ways similar to the 'g' protocol. Several ideas + are also taken from the paper ``A High-Throughput Message Transport + System'' by P. Lauder. I don't know where the paper was published, + but the author's e-mail address is piers@cs.su.oz.au. However, I + haven't adopted his main idea, which is to dispense with windows + entirely. This is because some links still do require flow control + and, more importantly, because I want to have a limit to the amount + of data I must be able to resend upon request. To reduce the costs + of window acknowledgements, I use a large window and only require + an ack at the halfway point. + + Each packet starts with a header containing the following + information: + + Intro byte 8 bits byte 1 + Packet number 5 bits byte 2 + Local channel 3 bits + Packet ack 5 bits byte 3 + Remote channel 3 bits + Packet type 3 bits bytes 4-5 + Direction 1 bit + Data length 12 bits + Header check 8 bits byte 6 + + If the data length is not 0, this is followed by the data and a 32 + bit CRC checksum. + + The following packet types are defined: + + SYNC Initialize the connection + DATA Data packet + ACK Simple acknowledgement with no data + NAK Negative acknowledgement; requests resend of single packet + SPOS Set file position + CLOSE Close the connection + */ + +/* The offsets of the bytes in the packet header. */ + +#define IHDR_INTRO (0) +#define IHDR_LOCAL (1) +#define IHDR_REMOTE (2) +#define IHDR_CONTENTS1 (3) +#define IHDR_CONTENTS2 (4) +#define IHDR_CHECK (5) + +/* Macros to set and extract values of IHDR_LOCAL and IHDR_REMOTE. */ +#define IHDRWIN_SET(iseq, ichan) (((iseq) << 3) | (ichan)) +#define IHDRWIN_GETSEQ(ival) (((ival) >> 3) & 0x1f) +#define IHDRWIN_GETCHAN(ival) ((ival) & 0x07) + +/* Macros to set and extract values of IHDR_CONTENTS fields. */ +#define IHDRCON_SET1(ttype, fcaller, cbytes) \ + (((ttype) << 5) | ((fcaller) ? (1 << 4) : 0) | (((cbytes) >> 8) & 0x0f)) +#define IHDRCON_SET2(ttype, fcaller, cbytes) ((cbytes) & 0xff) +#define THDRCON_GETTYPE(i1, i2) (((i1) >> 5) & 0x07) +#define FHDRCON_GETCALLER(i1, i2) (((i1) & (1 << 4)) != 0) +#define CHDRCON_GETBYTES(i1, i2) ((((i1) & 0x0f) << 8) | ((i2) & 0xff)) + +/* Macros for the IHDR_CHECK field. */ +#define IHDRCHECK_VAL(zhdr) \ + ((zhdr[IHDR_LOCAL] \ + ^ zhdr[IHDR_REMOTE] \ + ^ zhdr[IHDR_CONTENTS1] \ + ^ zhdr[IHDR_CONTENTS2]) \ + & 0xff) + +/* Length of the packet header. */ +#define CHDRLEN (6) + +/* Amount of space to skip between start of packet and actual data. + This is used to make the actual data longword aligned, to encourage + good performance when copying data into the buffer. */ +#define CHDRSKIPLEN (CHDRLEN + (sizeof (long) - CHDRLEN % sizeof (long))) + +/* Amount of space to skip between memory buffer and header. */ +#define CHDROFFSET (CHDRSKIPLEN - CHDRLEN) + +/* Length of the trailing checksum. */ +#define CCKSUMLEN (4) + +/* Macros to set and get the checksum. These multiply evaluate their + arguments. */ +#define ICKSUM_GET(z) \ + ((((((((unsigned long) ((z)[0] & 0xff)) << 8) \ + | (unsigned long) ((z)[1] & 0xff)) << 8) \ + | (unsigned long) ((z)[2] & 0xff)) << 8) \ + | (unsigned long) ((z)[3] & 0xff)) +#define UCKSUM_SET(z, i) \ + (void) ((z)[0] = (((i) >> 24) & 0xff), \ + (z)[1] = (((i) >> 16) & 0xff), \ + (z)[2] = (((i) >> 8) & 0xff), \ + (z)[3] = ((i) & 0xff)) + +/* The header introduction character. */ +#define IINTRO ('\007') + +/* The packet types. */ + +#define DATA (0) +#define SYNC (1) +#define ACK (2) +#define NAK (3) +#define SPOS (4) +#define CLOSE (5) + +/* Largest possible packet size (plus 1). */ +#define IMAXPACKSIZE (1 << 12) + +/* Largest possible sequence number (plus 1). */ +#define IMAXSEQ 32 + +/* Get the next sequence number given a sequence number. */ +#define INEXTSEQ(i) ((i + 1) & (IMAXSEQ - 1)) + +/* Compute i1 - i2 in sequence space (i.e., the number of packets from + i2 to i1). */ +#define CSEQDIFF(i1, i2) (((i1) + IMAXSEQ - (i2)) & (IMAXSEQ - 1)) + +/* Largest possible channel number (plus 1). */ +#define IMAXICHAN (8) + +/* Default packet size to request (protocol parameter + ``packet-size''). */ +#define IREQUEST_PACKSIZE (1024) + +/* Default window size to request (protocol parameter ``window''). */ +#define IREQUEST_WINSIZE (16) + +/* Default timeout to use when sending the SYNC packet (protocol + parameter ``sync-timeout''). */ +#define CSYNC_TIMEOUT (10) + +/* Default number of times to retry sending the SYNC packet (protocol + parameter ``sync-retries''). */ +#define CSYNC_RETRIES (6) + +/* Default timeout to use when waiting for a packet (protocol + parameter ``timeout''). */ +#define CTIMEOUT (10) + +/* Default number of times to retry sending a packet before giving up + (protocol parameter ``retries''). */ +#define CRETRIES (6) + +/* Default maximum level of errors to accept before giving up + (protocol parameter ``errors''). */ +#define CERRORS (100) + +/* Default decay rate. Each time we receive this many packets + succesfully, we decrement the error level by one (protocol + parameter ``error-decay''). */ +#define CERROR_DECAY (10) + +/* The default list of characters to avoid: XON and XOFF. This string + is processed as an escape sequence. This is 'j' protocol parameter + ``avoid''; it is defined in this file because the 'i' and 'j' + protocols share protocol parameters. */ +#define ZAVOID "\\021\\023" + +/* Local variables. */ + +/* Packet size to request (protocol parameter ``packet-size''). */ +static int iIrequest_packsize = IREQUEST_PACKSIZE; + +/* Window size to request (protocol parameter ``window''). */ +static int iIrequest_winsize = IREQUEST_WINSIZE; + +/* Remote packet size (set from SYNC packet or from + iIforced_remote_packsize). */ +static int iIremote_packsize; + +/* Size which buffers were allocated for. */ +static int iIalc_packsize; + +/* Forced remote packet size, used if non-zero (protocol parameter + ``remote-packet-size''). */ +static int iIforced_remote_packsize = 0; + +/* Remote window size (set from SYNC packet or from + iIforced_remote_winsize). */ +static int iIremote_winsize; + +/* Forced remote window size, used if non-zero (protocol parameter + ``remote-window''). */ +static int iIforced_remote_winsize = 0; + +/* Timeout to use when sending the SYNC packet (protocol + parameter ``sync-timeout''). */ +int cIsync_timeout = CSYNC_TIMEOUT; + +/* Number of times to retry sending the SYNC packet (protocol + parameter ``sync-retries''). */ +static int cIsync_retries = CSYNC_RETRIES; + +/* Timeout to use when waiting for a packet (protocol parameter + ``timeout''). */ +static int cItimeout = CTIMEOUT; + +/* Number of times to retry sending a packet before giving up + (protocol parameter ``retries''). */ +static int cIretries = CRETRIES; + +/* Maximum level of errors to accept before giving up (protocol + parameter ``errors''). */ +static int cIerrors = CERRORS; + +/* Each time we receive this many packets succesfully, we decrement + the error level by one (protocol parameter ``error-decay''). */ +static int cIerror_decay = CERROR_DECAY; + +/* The set of characters to avoid (protocol parameter ``avoid''). + This is actually part of the 'j' protocol; it is defined in this + file because the 'i' and 'j' protocols use the same protocol + parameters. */ +const char *zJavoid_parameter = ZAVOID; + +/* Routine to use when sending data. This is a hook for the 'j' + protocol. */ +static boolean (*pfIsend) P((struct sconnection *qconn, const char *zsend, + size_t csend, boolean fdoread)); + +/* Routine to use to use when reading data. This is a hook for the + 'j' protocol. */ +static boolean (*pfIreceive) P((struct sconnection *qconn, size_t cneed, + size_t *pcrec, int ctimeout, + boolean freport)); + +/* Next sequence number to send. */ +static int iIsendseq; + +/* Last sequence number received. */ +static int iIrecseq; + +/* Last sequence number we have acknowledged. */ +static int iIlocal_ack; + +/* Last sequence number remote system has acknowledged. */ +static int iIremote_ack; + +/* File position we are sending from. */ +static long iIsendpos; + +/* File position we are receiving to. */ +static long iIrecpos; + +/* TRUE if closing the connection. */ +static boolean fIclosing; + +/* Array of sent packets indexed by packet number. */ +static char *azIsendbuffers[IMAXSEQ]; + +/* Array of received packets that we aren't ready to process yet, + indexed by packet number. */ +static char *azIrecbuffers[IMAXSEQ]; + +/* For each packet sequence number, record whether we sent a NAK for + the packet. */ +static boolean afInaked[IMAXSEQ]; + +/* Number of SYNC packets received (used only to detect whether one + was received). */ +static int cIsyncs; + +/* Number of packets sent. */ +static long cIsent_packets; + +/* Number of packets received. */ +static long cIreceived_packets; + +/* Number of packets resent. */ +static long cIresent_packets; + +/* Number of bad packet headers received. */ +static long cIbad_hdr; + +/* Number of out of order packets received. */ +static long cIbad_order; + +/* Number of bad checksums received. */ +static long cIbad_cksum; + +/* Number of packets rejected by remote system. */ +static long cIremote_rejects; + +/* Protocol parameter commands. */ + +struct uuconf_cmdtab asIproto_params[] = +{ + { "packet-size", UUCONF_CMDTABTYPE_INT, (pointer) &iIrequest_packsize, + NULL }, + { "window", UUCONF_CMDTABTYPE_INT, (pointer) &iIrequest_winsize, NULL }, + { "remote-packet-size", UUCONF_CMDTABTYPE_INT, + (pointer) &iIforced_remote_packsize, NULL }, + { "remote-window", UUCONF_CMDTABTYPE_INT, + (pointer) &iIforced_remote_winsize, NULL }, + { "sync-timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cIsync_timeout, + NULL }, + { "sync-retries", UUCONF_CMDTABTYPE_INT, (pointer) &cIsync_retries, + NULL }, + { "timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cItimeout, NULL }, + { "retries", UUCONF_CMDTABTYPE_INT, (pointer) &cIretries, NULL }, + { "errors", UUCONF_CMDTABTYPE_INT, (pointer) &cIerrors, NULL }, + { "error-decay", UUCONF_CMDTABTYPE_INT, (pointer) &cIerror_decay, NULL }, + /* The ``avoid'' protocol parameter is part of the 'j' protocol, but + it is convenient for the 'i' and 'j' protocols to share the same + protocol parameter table. */ + { "avoid", UUCONF_CMDTABTYPE_STRING, (pointer) &zJavoid_parameter, NULL }, + { NULL, 0, NULL, NULL } +}; + +/* Local functions. */ + +static boolean finak P((struct sdaemon *qdaemon, int iseq)); +static boolean firesend P((struct sdaemon *qdaemon)); +static boolean fiwindow_wait P((struct sdaemon *qdaemon)); +static boolean fiwait_for_packet P((struct sdaemon *qdaemon, + int ctimeout, int cretries, + boolean fone, boolean *ftimedout)); +static boolean ficheck_errors P((struct sdaemon *qdaemon)); +static boolean fiprocess_data P((struct sdaemon *qdaemon, + boolean *pfexit, boolean *pffound, + size_t *pcneed)); +static boolean fiprocess_packet P((struct sdaemon *qdaemon, + const char *zhdr, + const char *zfirst, int cfirst, + const char *zsecond, int csecond, + boolean *pfexit)); + +/* The 'i' protocol start routine. The work is done in a routine + which is also called by the 'j' protocol start routine. */ + +boolean +fistart (qdaemon, pzlog) + struct sdaemon *qdaemon; + char **pzlog; +{ + return fijstart (qdaemon, pzlog, IMAXPACKSIZE, fsend_data, freceive_data); +} + +/* Start the protocol. This routine is called by both the 'i' and 'j' + protocol start routines. We keep transmitting a SYNC packet + containing the window and packet size we would like to receive + until we receive a SYNC packet from the remote system. The first + two bytes of the data contents of a SYNC packet are the maximum + packet size we want to receive (high byte, low byte), and the next + byte is the maximum window size we want to use. */ + +boolean +fijstart (qdaemon, pzlog, imaxpacksize, pfsend, pfreceive) + struct sdaemon *qdaemon; + char **pzlog; + int imaxpacksize; + boolean (*pfsend) P((struct sconnection *qconn, const char *zsend, + size_t csend, boolean fdoread)); + boolean (*pfreceive) P((struct sconnection *qconn, size_t cneed, + size_t *pcrec, int ctimeout, boolean freport)); +{ + char ab[CHDRLEN + 3 + CCKSUMLEN]; + unsigned long icksum; + int ctries; + int csyncs; + + *pzlog = NULL; + + pfIsend = pfsend; + pfIreceive = pfreceive; + + if (iIforced_remote_packsize <= 0 + || iIforced_remote_packsize >= imaxpacksize) + iIforced_remote_packsize = 0; + else + iIremote_packsize = iIforced_remote_packsize; + iIalc_packsize = 0; + if (iIforced_remote_winsize <= 0 || iIforced_remote_winsize >= IMAXSEQ) + iIforced_remote_winsize = 0; + else + iIremote_winsize = iIforced_remote_winsize; + + iIsendseq = 1; + iIrecseq = 0; + iIlocal_ack = 0; + iIremote_ack = 0; + iIsendpos = 0; + iIrecpos = 0; + fIclosing = FALSE; + + cIsent_packets = 0; + cIreceived_packets = 0; + cIresent_packets = 0; + cIbad_hdr = 0; + cIbad_order = 0; + cIbad_cksum = 0; + cIremote_rejects = 0; + + ab[IHDR_INTRO] = IINTRO; + ab[IHDR_LOCAL] = ab[IHDR_REMOTE] = IHDRWIN_SET (0, 0); + ab[IHDR_CONTENTS1] = IHDRCON_SET1 (SYNC, qdaemon->fcaller, 3); + ab[IHDR_CONTENTS2] = IHDRCON_SET2 (SYNC, qdaemon->fcaller, 3); + ab[IHDR_CHECK] = IHDRCHECK_VAL (ab); + ab[CHDRLEN + 0] = (iIrequest_packsize >> 8) & 0xff; + ab[CHDRLEN + 1] = iIrequest_packsize & 0xff; + ab[CHDRLEN + 2] = iIrequest_winsize; + icksum = icrc (ab + CHDRLEN, 3, ICRCINIT); + UCKSUM_SET (ab + CHDRLEN + 3, icksum); + + /* The static cIsyncs is incremented each time a SYNC packet is + received. */ + csyncs = cIsyncs; + ctries = 0; + + while (TRUE) + { + boolean ftimedout; + + DEBUG_MESSAGE2 (DEBUG_PROTO, + "fistart: Sending SYNC packsize %d winsize %d", + iIrequest_packsize, iIrequest_winsize); + + if (! (*pfIsend) (qdaemon->qconn, ab, CHDRLEN + 3 + CCKSUMLEN, + TRUE)) + return FALSE; + + if (fiwait_for_packet (qdaemon, cIsync_timeout, 0, FALSE, + &ftimedout)) + { + if (csyncs != cIsyncs) + break; + } + else + { + if (! ftimedout) + return FALSE; + + ++ctries; + if (ctries > cIsync_retries) + { + ulog (LOG_ERROR, "Protocol startup failed"); + return FALSE; + } + } + } + + /* We got a SYNC packet; set up packet buffers to use. */ + if (iIremote_packsize > imaxpacksize) + iIremote_packsize = imaxpacksize; + do + { + int iseq; + + for (iseq = 0; iseq < IMAXSEQ; iseq++) + { + azIrecbuffers[iseq] = NULL; + afInaked[iseq] = FALSE; + azIsendbuffers[iseq] = (char *) malloc (iIremote_packsize + + CHDRSKIPLEN + + CCKSUMLEN); + if (azIsendbuffers[iseq] == NULL) + { + int ifree; + + for (ifree = 0; ifree < iseq; ifree++) + free ((pointer) azIsendbuffers[ifree]); + break; + } + } + + if (iseq >= IMAXSEQ) + { + *pzlog = zbufalc (sizeof "protocol 'i' packet size %d window %d" + + 50); + sprintf (*pzlog, "protocol '%c' packet size %d window %d", + qdaemon->qproto->bname, iIremote_packsize, + iIremote_winsize); + iIalc_packsize = iIremote_packsize; + + return TRUE; + } + + iIremote_packsize >>= 1; + } + while (iIremote_packsize > 200); + + ulog (LOG_ERROR, + "'%c' protocol startup failed; insufficient memory for packets", + qdaemon->qproto->bname); + + return FALSE; +} + +/* Shut down the protocol. We can be fairly informal about this, + since we know that the upper level protocol has already exchanged + hangup messages. If we didn't know that, we would have to make + sure that all packets before the CLOSE had been received. */ + +boolean +fishutdown (qdaemon) + struct sdaemon *qdaemon; +{ + char *z; + size_t clen; + + fIclosing = TRUE; + + z = zigetspace (qdaemon, &clen) - CHDRLEN; + + z[IHDR_INTRO] = IINTRO; + z[IHDR_LOCAL] = IHDRWIN_SET (iIsendseq, 0); + z[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0); + iIlocal_ack = iIrecseq; + z[IHDR_CONTENTS1] = IHDRCON_SET1 (CLOSE, qdaemon->fcaller, 0); + z[IHDR_CONTENTS2] = IHDRCON_SET2 (CLOSE, qdaemon->fcaller, 0); + z[IHDR_CHECK] = IHDRCHECK_VAL (z); + + DEBUG_MESSAGE0 (DEBUG_PROTO, "fishutdown: Sending CLOSE"); + + if (! (*pfIsend) (qdaemon->qconn, z, CHDRLEN, FALSE)) + return FALSE; + + ulog (LOG_NORMAL, + "Protocol '%c' packets: sent %ld, resent %ld, received %ld", + qdaemon->qproto->bname, cIsent_packets, cIresent_packets, + cIreceived_packets); + if (cIbad_hdr != 0 + || cIbad_cksum != 0 + || cIbad_order != 0 + || cIremote_rejects != 0) + ulog (LOG_NORMAL, + "Errors: header %ld, checksum %ld, order %ld, remote rejects %ld", + cIbad_hdr, cIbad_cksum, cIbad_order, cIremote_rejects); + + /* Reset the protocol parameters to their default values. */ + iIrequest_packsize = IREQUEST_PACKSIZE; + iIrequest_winsize = IREQUEST_WINSIZE; + iIforced_remote_packsize = 0; + iIforced_remote_winsize = 0; + cIsync_timeout = CSYNC_TIMEOUT; + cIsync_retries = CSYNC_RETRIES; + cItimeout = CTIMEOUT; + cIretries = CRETRIES; + cIerrors = CERRORS; + cIerror_decay = CERROR_DECAY; + zJavoid_parameter = ZAVOID; + + return TRUE; +} + +/* Send a command string. These are just sent as normal packets, + ending in a packet containing a null byte. */ + +boolean +fisendcmd (qdaemon, z, ilocal, iremote) + struct sdaemon *qdaemon; + const char *z; + int ilocal; + int iremote; +{ + size_t clen; + + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, "fisendcmd: Sending command \"%s\"", z); + + clen = strlen (z); + + while (TRUE) + { + char *zpacket; + size_t csize; + + zpacket = zigetspace (qdaemon, &csize); + + if (clen < csize) + { + memcpy (zpacket, z, clen + 1); + return fisenddata (qdaemon, zpacket, clen + 1, ilocal, iremote, + (long) -1); + } + + memcpy (zpacket, z, csize); + z += csize; + clen -= csize; + + if (! fisenddata (qdaemon, zpacket, csize, ilocal, iremote, (long) -1)) + return FALSE; + } + /*NOTREACHED*/ +} + +/* Send a NAK. */ + +static boolean +finak (qdaemon, iseq) + struct sdaemon *qdaemon; + int iseq; +{ + char abnak[CHDRLEN]; + + abnak[IHDR_INTRO] = IINTRO; + abnak[IHDR_LOCAL] = IHDRWIN_SET (iseq, 0); + abnak[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0); + iIlocal_ack = iIrecseq; + abnak[IHDR_CONTENTS1] = IHDRCON_SET1 (NAK, qdaemon->fcaller, 0); + abnak[IHDR_CONTENTS2] = IHDRCON_SET2 (NAK, qdaemon->fcaller, 0); + abnak[IHDR_CHECK] = IHDRCHECK_VAL (abnak); + + afInaked[iseq] = TRUE; + + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "finak: Sending NAK %d", iseq); + + return (*pfIsend) (qdaemon->qconn, abnak, CHDRLEN, TRUE); +} + +/* Resend the latest packet the remote has not acknowledged. */ + +static boolean +firesend (qdaemon) + struct sdaemon *qdaemon; +{ + int iseq; + char *zhdr; + size_t clen; + + iseq = INEXTSEQ (iIremote_ack); + if (iseq == iIsendseq) + { + /* Everything has been ACKed and there is nothing to resend. */ + return TRUE; + } + + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "firesend: Resending packet %d", iseq); + + /* Update the received sequence number. */ + zhdr = azIsendbuffers[iseq] + CHDROFFSET; + if (IHDRWIN_GETSEQ (zhdr[IHDR_REMOTE]) != iIrecseq) + { + int iremote; + + iremote = IHDRWIN_GETCHAN (zhdr[IHDR_REMOTE]); + zhdr[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, iremote); + zhdr[IHDR_CHECK] = IHDRCHECK_VAL (zhdr); + iIlocal_ack = iIrecseq; + } + + ++cIresent_packets; + + clen = CHDRCON_GETBYTES (zhdr[IHDR_CONTENTS1], + zhdr[IHDR_CONTENTS2]); + + return (*pfIsend) (qdaemon->qconn, zhdr, + CHDRLEN + clen + (clen > 0 ? CCKSUMLEN : 0), + TRUE); +} + +/* Wait until there is an opening in the receive window of the remote + system. */ + +static boolean +fiwindow_wait (qdaemon) + struct sdaemon *qdaemon; +{ + /* iIsendseq is the sequence number we are sending, and iIremote_ack + is the last sequence number acknowledged by the remote. */ + while (CSEQDIFF (iIsendseq, iIremote_ack) > iIremote_winsize) + { + /* If a NAK is lost, it is possible for the other side to be + sending a stream of packets while we are waiting for an ACK. + This should be caught in fiprocess_data; if it is about to + send an ACK, but it has an unacknowledged packet to send, it + sends the entire packet. Hopefully that will trigger an ACK + or a NAK and get us going again. */ + DEBUG_MESSAGE0 (DEBUG_PROTO, "fiwindow_wait: Waiting for ACK"); + if (! fiwait_for_packet (qdaemon, cItimeout, cIretries, + TRUE, (boolean *) NULL)) + return FALSE; + } + + return TRUE; +} + +/* Get buffer space to use for packet data. We return a pointer to + the space to be used for the actual data, leaving room for the + header. */ + +/*ARGSUSED*/ +char * +zigetspace (qdaemon, pclen) + struct sdaemon *qdaemon; + size_t *pclen; +{ + *pclen = iIremote_packsize; + return azIsendbuffers[iIsendseq] + CHDRSKIPLEN; +} + +/* Send a data packet. The zdata argument will always point to value + returned by zigetspace, so we know that we have room before it for + the header information. */ + +boolean +fisenddata (qdaemon, zdata, cdata, ilocal, iremote, ipos) + struct sdaemon *qdaemon; + char *zdata; + size_t cdata; + int ilocal; + int iremote; + long ipos; +{ + char *zhdr; + unsigned long icksum; + boolean fret; + +#if DEBUG > 0 + if (ilocal < 0 || ilocal >= IMAXICHAN + || iremote < 0 || iremote >= IMAXICHAN) + ulog (LOG_FATAL, "fisenddata: ilocal %d iremote %d", ilocal, iremote); +#endif + + /* If we are changing the file position, we must send an SPOS + packet. */ + if (ipos != iIsendpos && ipos != (long) -1) + { + int inext; + char *zspos; + + /* We need to get a buffer to hold the SPOS packet, and it needs + to be next sequence number. However, the data we have been + given is currently in the next sequence number buffer. So we + shuffle the buffers around. */ + inext = INEXTSEQ (iIsendseq); + zspos = azIsendbuffers[inext]; + azIsendbuffers[inext] = zdata - CHDRSKIPLEN; + azIsendbuffers[iIsendseq] = zspos; + zspos += CHDROFFSET; + + zspos[IHDR_INTRO] = IINTRO; + zspos[IHDR_LOCAL] = IHDRWIN_SET (iIsendseq, 0); + zspos[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0); + iIlocal_ack = iIrecseq; + zspos[IHDR_CONTENTS1] = IHDRCON_SET1 (SPOS, qdaemon->fcaller, + CCKSUMLEN); + zspos[IHDR_CONTENTS2] = IHDRCON_SET2 (SPOS, qdaemon->fcaller, + CCKSUMLEN); + zspos[IHDR_CHECK] = IHDRCHECK_VAL (zspos); + UCKSUM_SET (zspos + CHDRLEN, (unsigned long) ipos); + icksum = icrc (zspos + CHDRLEN, CCKSUMLEN, ICRCINIT); + UCKSUM_SET (zspos + CHDRLEN + CCKSUMLEN, icksum); + + /* Wait for an opening in the window. */ + if (iIremote_winsize > 0 + && CSEQDIFF (iIsendseq, iIremote_ack) > iIremote_winsize) + { + if (! fiwindow_wait (qdaemon)) + return FALSE; + } + + DEBUG_MESSAGE1 (DEBUG_PROTO, "fisenddata: Sending SPOS %ld", + ipos); + + if (! (*pfIsend) (qdaemon->qconn, zspos, + CHDRLEN + CCKSUMLEN + CCKSUMLEN, TRUE)) + return FALSE; + + iIsendseq = INEXTSEQ (iIsendseq); + iIsendpos = ipos; + } + + zhdr = zdata - CHDRLEN; + zhdr[IHDR_INTRO] = IINTRO; + zhdr[IHDR_LOCAL] = IHDRWIN_SET (iIsendseq, ilocal); + zhdr[IHDR_CONTENTS1] = IHDRCON_SET1 (DATA, qdaemon->fcaller, cdata); + zhdr[IHDR_CONTENTS2] = IHDRCON_SET2 (DATA, qdaemon->fcaller, cdata); + + /* Compute and set the checksum. */ + if (cdata > 0) + { + icksum = icrc (zdata, cdata, ICRCINIT); + UCKSUM_SET (zdata + cdata, icksum); + } + + /* Wait until there is an opening in the window (we hope to not have + to wait here at all, actually; ideally the window should be large + enough to avoid a wait). */ + if (iIremote_winsize > 0 + && CSEQDIFF (iIsendseq, iIremote_ack) > iIremote_winsize) + { + if (! fiwindow_wait (qdaemon)) + return FALSE; + } + + /* We only fill in IHDR_REMOTE now, since only now do know the + correct value of iIrecseq. */ + zhdr[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, iremote); + iIlocal_ack = iIrecseq; + zhdr[IHDR_CHECK] = IHDRCHECK_VAL (zhdr); + + DEBUG_MESSAGE2 (DEBUG_PROTO, "fisenddata: Sending packet %d (%d bytes)", + iIsendseq, (int) cdata); + + iIsendseq = INEXTSEQ (iIsendseq); + ++cIsent_packets; + + fret = (*pfIsend) (qdaemon->qconn, zhdr, + cdata + CHDRLEN + (cdata > 0 ? CCKSUMLEN : 0), + TRUE); + + iIsendpos += cdata; + + if (fret && iPrecstart != iPrecend) + { + boolean fexit; + + fret = fiprocess_data (qdaemon, &fexit, (boolean *) NULL, + (size_t *) NULL); + } + + return fret; +} + +/* Wait for data to come in. */ + +boolean +fiwait (qdaemon) + struct sdaemon *qdaemon; +{ + return fiwait_for_packet (qdaemon, cItimeout, cIretries, + FALSE, (boolean *) NULL); +} + +/* Wait for a packet. Either there is no data to send, or the remote + window is full. */ + +static boolean +fiwait_for_packet (qdaemon, ctimeout, cretries, fone, pftimedout) + struct sdaemon *qdaemon; + int ctimeout; + int cretries; + boolean fone; + boolean *pftimedout; +{ + int cshort; + int ctimeouts; + + if (pftimedout != NULL) + *pftimedout = FALSE; + + cshort = 0; + ctimeouts = 0; + + while (TRUE) + { + boolean fexit, ffound; + size_t cneed; + size_t crec; + + if (! fiprocess_data (qdaemon, &fexit, &ffound, &cneed)) + return FALSE; + + if (fexit || (fone && ffound)) + return TRUE; + + if (cneed == 0) + continue; + + DEBUG_MESSAGE1 (DEBUG_PROTO, "fiwait_for_packet: Need %d bytes", + (int) cneed); + + if (! (*pfIreceive) (qdaemon->qconn, cneed, &crec, ctimeout, TRUE)) + return FALSE; + + if (crec != 0) + { + /* If we didn't get enough data twice in a row, we may have + dropped some data and be waiting for the end of a large + packet. Incrementing iPrecstart will force + fiprocess_data to skip the current packet and try to find + the next one. */ + if (crec >= cneed) + cshort = 0; + else + { + ++cshort; + if (cshort > 1) + { + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + cshort = 0; + } + } + } + else + { + int i; + + /* We timed out on the read. */ + ++ctimeouts; + if (ctimeouts > cretries) + { + if (cretries > 0) + ulog (LOG_ERROR, "Timed out waiting for packet"); + if (pftimedout != NULL) + *pftimedout = TRUE; + return FALSE; + } + + /* Clear out the list of packets we have sent NAKs for. We + should have seen some sort of response by now. */ + for (i = 0; i < IMAXSEQ; i++) + afInaked[i] = FALSE; + + /* Send a NAK for the packet we want, and, if we have an + unacknowledged packet, send it again. */ + if (! finak (qdaemon, INEXTSEQ (iIrecseq)) + || ! firesend (qdaemon)) + return FALSE; + } + } + /*NOTREACHED*/ +} + +/* Make sure we haven't overflowed the permissible error level. */ + +static boolean +ficheck_errors (qdaemon) + struct sdaemon *qdaemon; +{ + if (cIerrors < 0) + return TRUE; + + if (((cIbad_order + cIbad_hdr + cIbad_cksum + cIremote_rejects) + - (cIreceived_packets / cIerror_decay)) + > cIerrors) + { + /* Try shrinking the packet size. */ + if (iIrequest_packsize > 400) + { + char absync[CHDRLEN + 3 + CCKSUMLEN]; + unsigned long icksum; + + iIrequest_packsize /= 2; + absync[IHDR_INTRO] = IINTRO; + absync[IHDR_LOCAL] = IHDRWIN_SET (0, 0); + absync[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0); + iIlocal_ack = iIrecseq; + absync[IHDR_CONTENTS1] = IHDRCON_SET1 (SYNC, qdaemon->fcaller, 3); + absync[IHDR_CONTENTS2] = IHDRCON_SET2 (SYNC, qdaemon->fcaller, 3); + absync[IHDR_CHECK] = IHDRCHECK_VAL (absync); + absync[CHDRLEN + 0] = (iIrequest_packsize >> 8) & 0xff; + absync[CHDRLEN + 1] = iIrequest_packsize & 0xff; + absync[CHDRLEN + 2] = iIrequest_winsize; + icksum = icrc (absync + CHDRLEN, 3, ICRCINIT); + UCKSUM_SET (absync + CHDRLEN + 3, icksum); + + cIerrors *= 2; + + DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL, + "ficheck_errors: Sending SYNC packsize %d winsize %d", + iIrequest_packsize, iIrequest_winsize); + + return (*pfIsend) (qdaemon->qconn, absync, + CHDRLEN + 3 + CCKSUMLEN, TRUE); + } + + ulog (LOG_ERROR, "Too many '%c' protocol errors", + qdaemon->qproto->bname); + return FALSE; + } + + return TRUE; +} + +/* Process data waiting in the receive buffer, passing to the + fgot_data function. */ + +static boolean +fiprocess_data (qdaemon, pfexit, pffound, pcneed) + struct sdaemon *qdaemon; + boolean *pfexit; + boolean *pffound; + size_t *pcneed; +{ + boolean fbadhdr; + + if (pfexit != NULL) + *pfexit = FALSE; + if (pffound != NULL) + *pffound = FALSE; + + fbadhdr = FALSE; + + while (iPrecstart != iPrecend) + { + char ab[CHDRLEN]; + int cfirst, csecond; + char *zfirst, *zsecond; + int i; + int iget; + int ttype; + int iseq; + int csize; + int iack; + + /* If we're closing the connection, ignore any data remaining in + the input buffer. */ + if (fIclosing) + { + if (pfexit != NULL) + *pfexit = TRUE; + if (pcneed != NULL) + *pcneed = 0; + return TRUE; + } + + /* Look for the IINTRO character. */ + if (abPrecbuf[iPrecstart] != IINTRO) + { + char *zintro; + int cintro; + + cintro = iPrecend - iPrecstart; + if (cintro < 0) + cintro = CRECBUFLEN - iPrecstart; + + zintro = memchr (abPrecbuf + iPrecstart, IINTRO, (size_t) cintro); + + if (zintro == NULL) + { + iPrecstart = (iPrecstart + cintro) % CRECBUFLEN; + continue; + } + + /* We don't need % CRECBUFLEN here because zintro - (abPrecbuf + + iPrecstart) < cintro <= CRECBUFLEN - iPrecstart. */ + iPrecstart += zintro - (abPrecbuf + iPrecstart); + } + + /* Get the header into ab. */ + for (i = 0, iget = iPrecstart; + i < CHDRLEN && iget != iPrecend; + i++, iget = (iget + 1) % CRECBUFLEN) + ab[i] = abPrecbuf[iget]; + + if (i < CHDRLEN) + { + if (pcneed != NULL) + *pcneed = CHDRLEN - i; + return TRUE; + } + + if ((ab[IHDR_CHECK] & 0xff) != IHDRCHECK_VAL (ab) + || (FHDRCON_GETCALLER (ab[IHDR_CONTENTS1], ab[IHDR_CONTENTS2]) + ? qdaemon->fcaller : ! qdaemon->fcaller)) + { + /* We only report a single bad header message per call, to + avoid generating many errors if we get many INTRO bytes + in a row. */ + if (! fbadhdr) + { + DEBUG_MESSAGE0 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fiprocess_data: Bad header"); + + ++cIbad_hdr; + if (! ficheck_errors (qdaemon)) + return FALSE; + + fbadhdr = TRUE; + } + + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + continue; + } + + zfirst = zsecond = NULL; + cfirst = csecond = 0; + + ttype = THDRCON_GETTYPE (ab[IHDR_CONTENTS1], ab[IHDR_CONTENTS2]); + if (ttype == DATA || ttype == SPOS || ttype == CLOSE) + iseq = IHDRWIN_GETSEQ (ab[IHDR_LOCAL]); + else + iseq = -1; + csize = CHDRCON_GETBYTES (ab[IHDR_CONTENTS1], ab[IHDR_CONTENTS2]); + + if (iseq != -1) + { + /* Make sure this packet is in our receive window. The last + packet we have acked is iIlocal_ack. */ + if (iIrequest_winsize > 0 + && CSEQDIFF (iseq, iIlocal_ack) > iIrequest_winsize) + { + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fiprocess_data: Out of order packet %d", + iseq); + + ++cIbad_order; + if (! ficheck_errors (qdaemon)) + return FALSE; + + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + + continue; + } + } + + if (csize > 0) + { + int cinbuf; + char abcksum[CCKSUMLEN]; + unsigned long ickdata; + + cinbuf = iPrecend - iPrecstart; + if (cinbuf < 0) + cinbuf += CRECBUFLEN; + if (cinbuf < CHDRLEN + csize + CCKSUMLEN) + { + if (pcneed != NULL) + *pcneed = CHDRLEN + csize + CCKSUMLEN - cinbuf; + return TRUE; + } + + if (iPrecend > iPrecstart) + { + cfirst = csize; + zfirst = abPrecbuf + iPrecstart + CHDRLEN; + } + else + { + cfirst = CRECBUFLEN - (iPrecstart + CHDRLEN); + if (cfirst <= 0) + { + /* Here cfirst is non-positive, so subtracting from + abPrecbuf will actually skip the appropriate number + of bytes at the start of abPrecbuf. */ + zfirst = abPrecbuf - cfirst; + cfirst = csize; + } + else + { + if (cfirst >= csize) + cfirst = csize; + else + { + zsecond = abPrecbuf; + csecond = csize - cfirst; + } + zfirst = abPrecbuf + iPrecstart + CHDRLEN; + } + } + + /* Get the checksum into abcksum. */ + for (i = 0, iget = (iPrecstart + CHDRLEN + csize) % CRECBUFLEN; + i < CCKSUMLEN; + i++, iget = (iget + 1) % CRECBUFLEN) + abcksum[i] = abPrecbuf[iget]; + + ickdata = icrc (zfirst, (size_t) cfirst, ICRCINIT); + if (csecond > 0) + ickdata = icrc (zsecond, (size_t) csecond, ickdata); + + if (ICKSUM_GET (abcksum) != ickdata) + { + DEBUG_MESSAGE2 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fiprocess_data: Bad checksum; data %lu, frame %lu", + ickdata, ICKSUM_GET (abcksum)); + + ++cIbad_cksum; + if (! ficheck_errors (qdaemon)) + return FALSE; + + /* If this sequence number is in our receive window, + send a NAK. iIrecseq is the last sequence number we + have succesfully received. */ + if (iseq != -1 + && iseq != iIrecseq + && (iIrequest_winsize <= 0 + || CSEQDIFF (iseq, iIrecseq) <= iIrequest_winsize) + && azIrecbuffers[iseq] == NULL) + { + if (! finak (qdaemon, iseq)) + return FALSE; + } + + iPrecstart = (iPrecstart + 1) % CRECBUFLEN; + continue; + } + } + + /* Here we know that this is a valid packet, so we can adjust + iPrecstart accordingly. */ + if (csize == 0) + iPrecstart = (iPrecstart + CHDRLEN) % CRECBUFLEN; + else + { + iPrecstart = ((iPrecstart + CHDRLEN + csize + CCKSUMLEN) + % CRECBUFLEN); + ++cIreceived_packets; + } + + /* Get the ack from the packet, if appropriate. iIsendseq is + the next sequence number we are going to send, and + iIremote_ack is the last sequence number acknowledged by the + remote system. */ + iack = IHDRWIN_GETSEQ (ab[IHDR_REMOTE]); + if (iIremote_winsize > 0 + && iack != iIsendseq + && CSEQDIFF (iack, iIremote_ack) <= iIremote_winsize + && CSEQDIFF (iIsendseq, iack) <= iIremote_winsize) + { + /* Call uwindow_acked each time packet 0 is acked. */ + if (iack < iIremote_ack) + uwindow_acked (qdaemon, FALSE); + iIremote_ack = iack; + } + + if (iseq != -1) + { + afInaked[iseq] = FALSE; + + /* If we haven't handled all previous packets, we must save + off this packet and deal with it later. */ + if (iseq != INEXTSEQ (iIrecseq)) + { + if (iseq == iIrecseq + || (iIrequest_winsize > 0 + && CSEQDIFF (iseq, iIrecseq) > iIrequest_winsize)) + { + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fiprocess_data: Ignoring out of order packet %d", + iseq); + continue; + } + else + { + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fiprocess_data: Saving unexpected packet %d", + iseq); + + if (azIrecbuffers[iseq] == NULL) + { + azIrecbuffers[iseq] = zbufalc ((size_t) (CHDRLEN + + csize)); + memcpy (azIrecbuffers[iseq], ab, CHDRLEN); + if (csize > 0) + { + memcpy (azIrecbuffers[iseq] + CHDRLEN, zfirst, + (size_t) cfirst); + if (csecond > 0) + memcpy (azIrecbuffers[iseq] + CHDRLEN + cfirst, + zsecond, (size_t) csecond); + } + } + } + + /* Send NAK's for each packet between the last one we + received and this one, avoiding any packets for which + we've already sent NAK's or which we've already + received. */ + for (i = INEXTSEQ (iIrecseq); + i != iseq; + i = INEXTSEQ (i)) + { + if (! afInaked[i] + && azIrecbuffers[i] == NULL) + { + if (! finak (qdaemon, i)) + return FALSE; + } + } + + continue; + } + + iIrecseq = iseq; + } + + if (pffound != NULL) + *pffound = TRUE; + + if (! fiprocess_packet (qdaemon, ab, zfirst, cfirst, zsecond, csecond, + pfexit)) + return FALSE; + + if (iseq != -1) + { + int inext; + + /* If we've already received the next packet(s), process + them. */ + inext = INEXTSEQ (iIrecseq); + while (azIrecbuffers[inext] != NULL) + { + char *z; + int c; + + z = azIrecbuffers[inext]; + c = CHDRCON_GETBYTES (z[IHDR_CONTENTS1], z[IHDR_CONTENTS2]); + iIrecseq = inext; + if (! fiprocess_packet (qdaemon, z, z + CHDRLEN, c, + (char *) NULL, 0, pfexit)) + return FALSE; + ubuffree (azIrecbuffers[inext]); + azIrecbuffers[inext] = NULL; + inext = INEXTSEQ (inext); + } + } + + /* If we have received half of our window size or more since the + last ACK, send one now. Sending an ACK for half the window + at a time should significantly cut the acknowledgement + traffic when only one side is sending. We should normally + not have to send an ACK if we have data to send, since each + packet sent will ACK the most recently received packet. + However, it can happen if we receive a burst of short + packets, such as a set of command acknowledgements. */ + if (iIrequest_winsize > 0 + && CSEQDIFF (iIrecseq, iIlocal_ack) >= iIrequest_winsize / 2) + { + char aback[CHDRLEN]; + + aback[IHDR_INTRO] = IINTRO; + aback[IHDR_LOCAL] = IHDRWIN_SET (0, 0); + aback[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, 0); + iIlocal_ack = iIrecseq; + aback[IHDR_CONTENTS1] = IHDRCON_SET1 (ACK, qdaemon->fcaller, 0); + aback[IHDR_CONTENTS2] = IHDRCON_SET2 (ACK, qdaemon->fcaller, 0); + aback[IHDR_CHECK] = IHDRCHECK_VAL (aback); + + DEBUG_MESSAGE1 (DEBUG_PROTO, "fiprocess_data: Sending ACK %d", + iIrecseq); + + if (! (*pfIsend) (qdaemon->qconn, aback, CHDRLEN, TRUE)) + return FALSE; + } + } + + if (pcneed != NULL) + *pcneed = CHDRLEN; + + return TRUE; +} + +/* Process a single packet. */ + +static boolean +fiprocess_packet (qdaemon, zhdr, zfirst, cfirst, zsecond, csecond, pfexit) + struct sdaemon *qdaemon; + const char *zhdr; + const char *zfirst; + int cfirst; + const char *zsecond; + int csecond; + boolean *pfexit; +{ + int ttype; + + ttype = THDRCON_GETTYPE (zhdr[IHDR_CONTENTS1], zhdr[IHDR_CONTENTS2]); + switch (ttype) + { + case DATA: + { + int iseq; + boolean fret; + + iseq = IHDRWIN_GETSEQ (zhdr[IHDR_LOCAL]); + DEBUG_MESSAGE2 (DEBUG_PROTO, + "fiprocess_packet: Got DATA packet %d size %d", + iseq, cfirst + csecond); + fret = fgot_data (qdaemon, zfirst, (size_t) cfirst, + zsecond, (size_t) csecond, + IHDRWIN_GETCHAN (zhdr[IHDR_REMOTE]), + IHDRWIN_GETCHAN (zhdr[IHDR_LOCAL]), + iIrecpos, + INEXTSEQ (iIremote_ack) == iIsendseq, + pfexit); + iIrecpos += cfirst + csecond; + return fret; + } + + case SYNC: + { + int ipack, iwin; + + /* We accept a SYNC packet to adjust the packet and window + sizes at any time. */ + if (cfirst + csecond < 3) + { + ulog (LOG_ERROR, "Bad SYNC packet"); + return FALSE; + } + ipack = (zfirst[0] & 0xff) << 8; + if (cfirst > 1) + ipack |= zfirst[1] & 0xff; + else + ipack |= zsecond[0]; + if (cfirst > 2) + iwin = zfirst[2]; + else + iwin = zsecond[2 - cfirst]; + + DEBUG_MESSAGE2 (DEBUG_PROTO, + "fiprocess_packet: Got SYNC packsize %d winsize %d", + ipack, iwin); + + if (iIforced_remote_packsize == 0 + && (iIalc_packsize == 0 + || ipack <= iIalc_packsize)) + iIremote_packsize = ipack; + if (iIforced_remote_winsize == 0) + iIremote_winsize = iwin; + + /* We increment a static variable to tell the initialization + code that a SYNC was received, and we set *pfexit to TRUE + to get out to the initialization code (this will do no harm + if we are called from elsewhere). */ + ++cIsyncs; + *pfexit = TRUE; + return TRUE; + } + + case ACK: + /* There is nothing to do here, since the ack was already + handled in fiprocess_data. */ + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fiprocess_packet: Got ACK %d", + IHDRWIN_GETSEQ (zhdr[IHDR_REMOTE])); + return TRUE; + + case NAK: + /* We must resend the requested packet. */ + { + int iseq; + char *zsend; + size_t clen; + + ++cIremote_rejects; + if (! ficheck_errors (qdaemon)) + return FALSE; + + iseq = IHDRWIN_GETSEQ (zhdr[IHDR_LOCAL]); + + /* The timeout code will send a NAK for the packet the remote + side wants. So we may see a NAK here for the packet we are + about to send. */ + if (iseq == iIsendseq + || (iIremote_winsize > 0 + && (CSEQDIFF (iseq, iIremote_ack) > iIremote_winsize + || CSEQDIFF (iIsendseq, iseq) > iIremote_winsize))) + { + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fiprocess_packet: Ignoring out of order NAK %d", + iseq); + return TRUE; + } + + DEBUG_MESSAGE1 (DEBUG_PROTO | DEBUG_ABNORMAL, + "fiprocess_packet: Got NAK %d; resending packet", + iseq); + + /* Update the received sequence number. */ + zsend = azIsendbuffers[iseq] + CHDROFFSET; + if (IHDRWIN_GETSEQ (zsend[IHDR_REMOTE]) != iIrecseq) + { + int iremote; + + iremote = IHDRWIN_GETCHAN (zsend[IHDR_REMOTE]); + zsend[IHDR_REMOTE] = IHDRWIN_SET (iIrecseq, iremote); + zsend[IHDR_CHECK] = IHDRCHECK_VAL (zsend); + iIlocal_ack = iIrecseq; + } + + ++cIresent_packets; + + clen = CHDRCON_GETBYTES (zsend[IHDR_CONTENTS1], + zsend[IHDR_CONTENTS2]); + + return (*pfIsend) (qdaemon->qconn, zsend, + CHDRLEN + clen + (clen > 0 ? CCKSUMLEN : 0), + TRUE); + } + + case SPOS: + /* Set the file position. */ + { + char abpos[CCKSUMLEN]; + const char *zpos; + + if (cfirst >= CCKSUMLEN) + zpos = zfirst; + else + { + memcpy (abpos, zfirst, (size_t) cfirst); + memcpy (abpos + cfirst, zsecond, (size_t) (CCKSUMLEN - cfirst)); + zpos = abpos; + } + iIrecpos = (long) ICKSUM_GET (zpos); + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fiprocess_packet: Got SPOS %ld", iIrecpos); + return TRUE; + } + + case CLOSE: + { + boolean fexpected; + + fexpected = ! fLog_sighup || fIclosing; + if (! fexpected) + ulog (LOG_ERROR, "Received unexpected CLOSE packet"); + else + DEBUG_MESSAGE0 (DEBUG_PROTO, "fiprocess_packet: Got CLOSE packet"); + + fIclosing = TRUE; + *pfexit = TRUE; + return fexpected; + } + + default: + /* Just ignore unrecognized packet types, for future protocol + enhancements. */ + DEBUG_MESSAGE1 (DEBUG_PROTO, "fiprocess_packet: Got packet type %d", + ttype); + return TRUE; + } + /*NOTREACHED*/ +} diff --git a/gnu/libexec/uucp/uucico/protj.c b/gnu/libexec/uucp/uucico/protj.c new file mode 100644 index 0000000000000..5717bb916dfb9 --- /dev/null +++ b/gnu/libexec/uucp/uucico/protj.c @@ -0,0 +1,671 @@ +/* protj.c + The 'j' protocol. + + Copyright (C) 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char protj_rcsid[] = "$Id: protj.c,v 1.1 1993/08/05 18:27:14 conklin Exp $"; +#endif + +#include <ctype.h> +#include <errno.h> + +#include "uudefs.h" +#include "conn.h" +#include "trans.h" +#include "system.h" +#include "prot.h" + +/* The 'j' protocol. + + The 'j' protocol is a wrapper around the 'i' protocol, which avoids + the use of certain characters, such as XON and XOFF. + + Each 'j' protocol packet begins with a '^' character, followed by a + two byte encoded size giving the total number of bytes in the + packet. The first byte is HIGH, the second byte is LOW, and the + number of bytes is (HIGH - 32) * 64 + (LOW - 32), where 32 <= HIGH + < 127 and 32 <= LOW < 96 (i.e., HIGH and LOW are printable ASCII + characters). This is followed by a '=' character. The next two + bytes are the number of data bytes in the packet, using the same + encoding. This is followed by a '@' character, and then that + number of data bytes. The remaining bytes in the packet are + indices of bytes which must be transformed, followed by a trailing + '~' character. The indices are encoded in the following overly + complex format. + + Each byte index is two bytes long. The first byte in the index is + INDEX-HIGH and the second is INDEX-LOW. If 32 <= INDEX-HIGH < 126, + the byte index refers to the byte at position (INDEX-HIGH - 32) * + 32 + INDEX-LOW % 32 in the actual data, where 32 <= INDEX-LOW < + 127. If 32 <= INDEX-LOW < 64, then 128 must be added to the + indexed byte. If 64 <= INDEX-LOW < 96, then the indexed byte must + be exclusive or'red with 32. If 96 <= INDEX-LOW < 127, both + operations must be performed. If INDEX-HIGH == 126, then the byte + index refers to the byte at position (INDEX-LOW - 32) * 32 + 31, + where 32 <= INDEX-LOW < 126. 128 must be added to the byte, and it + must be exclusive or'red with 32. This unfortunately requires a + special test (when encoding INDEX-LOW must be checked for 127; when + decoding INDEX-HIGH must be checked for 126). It does, however, + permit the byte indices field to consist exclusively of printable + ASCII characters. + + The maximum value for a byte index is (125 - 32) * 32 + 31 == 3007, + so the is the maximum number of data bytes permitted. Since it is + convenient to have each 'j' protocol packet correspond to each 'i' + protocol packet, we restrict the 'i' protocol accordingly. + + Note that this encoding method assumes that we can send all + printable ASCII characters. */ + +/* The first byte of each packet. I just picked these values + randomly, trying to get characters that were perhaps slightly less + likely to appear in normal text. */ +#define FIRST '\136' + +/* The fourth byte of each packet. */ +#define FOURTH '\075' + +/* The seventh byte of each packet. */ +#define SEVENTH '\100' + +/* The trailing byte of each packet. */ +#define TRAILER '\176' + +/* The length of the header. */ +#define CHDRLEN (7) + +/* Get a number of bytes encoded in a two byte length at the start of + a packet. */ +#define CGETLENGTH(b1, b2) (((b1) - 32) * 64 + ((b2) - 32)) + +/* Set the high and low bytes of a two byte length at the start of a + packet. */ +#define ISETLENGTH_FIRST(i) ((i) / 64 + 32) +#define ISETLENGTH_SECOND(i) ((i) % 64 + 32) + +/* The maximum packet size we support, as determined by the byte + indices. */ +#define IMAXPACKSIZE ((125 - 32) * 32 + 31) + +/* Amount to offset the bytes in the byte index by. */ +#define INDEX_OFFSET (32) + +/* Maximum value of INDEX-LOW, before offsetting. */ +#define INDEX_MAX_LOW (32) + +/* Maximum value of INDEX-HIGH, before offsetting. */ +#define INDEX_MAX_HIGH (94) + +/* The set of characters to avoid. */ +static char *zJavoid; + +/* The number of characters to avoid. */ +static size_t cJavoid; + +/* A buffer used when sending data. */ +static char *zJbuf; + +/* The end of the undecoded data in abPrecbuf. */ +static int iJrecend; + +/* Local functions. */ +static boolean fjsend_data P((struct sconnection *qconn, const char *zsend, + size_t csend, boolean fdoread)); +static boolean fjreceive_data P((struct sconnection *qconn, size_t cneed, + size_t *pcrec, int ctimeout, + boolean freport)); +static boolean fjprocess_data P((size_t *pcneed)); + +/* Start the protocol. We first send over the list of characters to + avoid as an escape sequence, starting with FIRST and ending with + TRAILER. There is no error checking done on this string. */ + +boolean +fjstart (qdaemon, pzlog) + struct sdaemon *qdaemon; + char **pzlog; +{ + size_t clen; + char *zsend; + int b; + size_t cbuf, cgot; + char *zbuf; + int i; + + /* Send the characters we want to avoid to the other side. */ + clen = strlen (zJavoid_parameter); + zsend = zbufalc (clen + 3); + zsend[0] = FIRST; + memcpy (zsend + 1, zJavoid_parameter, clen); + zsend[clen + 1] = TRAILER; + zsend[clen + 2] = '\0'; + if (! fsend_data (qdaemon->qconn, zsend, clen + 2, TRUE)) + { + ubuffree (zsend); + return FALSE; + } + ubuffree (zsend); + + /* Read the characters the other side wants to avoid. */ + while ((b = breceive_char (qdaemon->qconn, cIsync_timeout, TRUE)) + != FIRST) + { + if (b < 0) + { + if (b == -1) + ulog (LOG_ERROR, "Timed out in 'j' protocol startup"); + return FALSE; + } + } + + cbuf = 20; + zbuf = zbufalc (cbuf); + cgot = 0; + while ((b = breceive_char (qdaemon->qconn, cIsync_timeout, TRUE)) + != TRAILER) + { + if (b < 0) + { + ubuffree (zbuf); + if (b == -1) + ulog (LOG_ERROR, "Timed out in 'j' protocol startup"); + return FALSE; + } + if (cgot + 1 >= cbuf) + { + char *znew; + + cbuf += 20; + znew = zbufalc (cbuf); + memcpy (znew, zbuf, cgot); + ubuffree (zbuf); + zbuf = znew; + } + zbuf[cgot] = b; + ++cgot; + } + zbuf[cgot] = '\0'; + + /* Merge the local and remote avoid bytes into one list, translated + into bytes. */ + cgot = cescape (zbuf); + + clen = strlen (zJavoid_parameter); + zJavoid = zbufalc (clen + cgot + 1); + memcpy (zJavoid, zJavoid_parameter, clen + 1); + cJavoid = cescape (zJavoid); + + for (i = 0; i < cgot; i++) + { + if (memchr (zJavoid, zbuf[i], cJavoid) == NULL) + { + zJavoid[cJavoid] = zbuf[i]; + ++cJavoid; + } + } + + ubuffree (zbuf); + + /* We can't avoid ASCII printable characters, since the encoding + method assumes that they can always be sent. If it ever turns + out to be important, a different encoding method could be used, + perhaps keyed by a different FIRST character. */ + if (cJavoid == 0) + { + ulog (LOG_ERROR, "No characters to avoid in 'j' protocol"); + return FALSE; + } + for (i = 0; i < cJavoid; i++) + { + if (zJavoid[i] >= 32 && zJavoid[i] <= 126) + { + ulog (LOG_ERROR, "'j' protocol can't avoid character '\\%03o'", + zJavoid[i]); + return FALSE; + } + } + + /* If we are avoiding XON and XOFF, use XON/XOFF handshaking. */ + if (memchr (zJavoid, '\021', cJavoid) != NULL + && memchr (zJavoid, '\023', cJavoid) != NULL) + { + if (! fconn_set (qdaemon->qconn, PARITYSETTING_NONE, + STRIPSETTING_EIGHTBITS, XONXOFF_ON)) + return FALSE; + } + + /* Let the port settle. */ + usysdep_sleep (2); + + /* Allocate a buffer we use when sending data. We will probably + never actually need one this big; if this code is ported to a + computer with small amounts of memory, this should be changed to + increase the buffer size as needed. */ + zJbuf = zbufalc (CHDRLEN + IMAXPACKSIZE * 3 + 1); + zJbuf[0] = FIRST; + zJbuf[3] = FOURTH; + zJbuf[6] = SEVENTH; + + /* iJrecend is the end of the undecoded data, and iPrecend is the + end of the decoded data. At this point there is no decoded data, + and we must initialize the variables accordingly. */ + iJrecend = iPrecend; + iPrecend = iPrecstart; + + /* Now do the 'i' protocol startup. */ + return fijstart (qdaemon, pzlog, IMAXPACKSIZE, fjsend_data, + fjreceive_data); +} + +/* Shut down the protocol. */ + +boolean +fjshutdown (qdaemon) + struct sdaemon *qdaemon; +{ + boolean fret; + + fret = fishutdown (qdaemon); + ubuffree (zJavoid); + ubuffree (zJbuf); + return fret; +} + +/* Encode a packet of data and send it. This copies the data, which + is a waste of time, but calling fsend_data three times (for the + header, the body, and the trailer) would waste even more time. */ + +static boolean +fjsend_data (qconn, zsend, csend, fdoread) + struct sconnection *qconn; + const char *zsend; + size_t csend; + boolean fdoread; +{ + char *zput, *zindex; + const char *zfrom, *zend; + char bfirst, bsecond; + int iprecendhold; + boolean fret; + + zput = zJbuf + CHDRLEN; + zindex = zput + csend; + zfrom = zsend; + zend = zsend + csend; + + /* Optimize for the common case of avoiding two characters. */ + bfirst = zJavoid[0]; + if (cJavoid <= 1) + bsecond = bfirst; + else + bsecond = zJavoid[1]; + while (zfrom < zend) + { + char b; + boolean f128, f32; + int i, ihigh, ilow; + + b = *zfrom++; + if (b != bfirst && b != bsecond) + { + int ca; + char *za; + + if (cJavoid <= 2) + { + *zput++ = b; + continue; + } + + ca = cJavoid - 2; + za = zJavoid + 2; + while (ca-- != 0) + if (*za++ == b) + break; + + if (ca < 0) + { + *zput++ = b; + continue; + } + } + + if ((b & 0x80) == 0) + f128 = FALSE; + else + { + b &=~ 0x80; + f128 = TRUE; + } + if (b >= 32 && b != 127) + f32 = FALSE; + else + { + b ^= 0x20; + f32 = TRUE; + } + + /* We must now put the byte index into the buffer. The byte + index is encoded similarly to the length of the actual data, + but the byte index also encodes the operations that must be + performed on the byte. The first byte in the index is the + most significant bits. If we only had to subtract 128 from + the byte, we use the second byte directly. If we had to xor + the byte with 32, we add 32 to the second byte index. If we + had to perform both operations, we add 64 to the second byte + index. However, if we had to perform both operations, and + the second byte index was 31, then after adding 64 and + offsetting by 32 we would come up with 127, which we are not + permitted to use. Therefore, in this special case we set the + first byte of the index to 126 and put the original first + byte into the second byte position instead. This is why we + could not permit the high byte of the length of the actual + data to be 126. We can get away with the switch because both + the value of the second byte index (31) and the operations to + perform (both) are known. */ + i = zput - (zJbuf + CHDRLEN); + ihigh = i / INDEX_MAX_LOW; + ilow = i % INDEX_MAX_LOW; + + if (f128 && ! f32) + ; + else if (f32 && ! f128) + ilow += INDEX_MAX_LOW; + else + { + /* Both operations had to be performed. */ + if (ilow != INDEX_MAX_LOW - 1) + ilow += 2 * INDEX_MAX_LOW; + else + { + ilow = ihigh; + ihigh = INDEX_MAX_HIGH; + } + } + + *zindex++ = ihigh + INDEX_OFFSET; + *zindex++ = ilow + INDEX_OFFSET; + *zput++ = b; + } + + *zindex++ = TRAILER; + + /* Set the lengths into the buffer. zJbuf[0,3,6] were set when + zJbuf was allocated, and are never changed thereafter. */ + zJbuf[1] = ISETLENGTH_FIRST (zindex - zJbuf); + zJbuf[2] = ISETLENGTH_SECOND (zindex - zJbuf); + zJbuf[4] = ISETLENGTH_FIRST (csend); + zJbuf[5] = ISETLENGTH_SECOND (csend); + + /* Send the data over the line. We must preserve iPrecend as + discussed in fjreceive_data. */ + iprecendhold = iPrecend; + iPrecend = iJrecend; + fret = fsend_data (qconn, zJbuf, (size_t) (zindex - zJbuf), fdoread); + iJrecend = iPrecend; + iPrecend = iprecendhold; + + /* Process any bytes that may have been placed in abPrecbuf. */ + if (fret && iPrecend != iJrecend) + { + if (! fjprocess_data ((size_t *) NULL)) + return FALSE; + } + + return fret; +} + +/* Receive and decode data. This is called by fiwait_for_packet. We + need to be able to return decoded data between iPrecstart and + iPrecend, while not losing any undecoded partial packets we may + have read. We use iJrecend as a pointer to the end of the + undecoded data, and set iPrecend for the decoded data. iPrecend + points to the start of the undecoded data. */ + +static boolean +fjreceive_data (qconn, cineed, pcrec, ctimeout, freport) + struct sconnection *qconn; + size_t cineed; + size_t *pcrec; + int ctimeout; + boolean freport; +{ + int iprecendstart; + size_t cjneed; + size_t crec; + int cnew; + + iprecendstart = iPrecend; + + /* Figure out how many bytes we need to decode the next packet. */ + if (! fjprocess_data (&cjneed)) + return FALSE; + + /* As we long as we read some data but don't have enough to decode a + packet, we try to read some more. We decrease the timeout each + time so that we will not wait forever if the connection starts + dribbling data. */ + do + { + int iprecendhold; + size_t cneed; + + if (cjneed > cineed) + cneed = cjneed; + else + cneed = cineed; + + /* We are setting iPrecend to the end of the decoded data for + the 'i' protocol. When we do the actual read, we have to set + it to the end of the undecoded data so that any undecoded + data we have received is not overwritten. */ + iprecendhold = iPrecend; + iPrecend = iJrecend; + if (! freceive_data (qconn, cneed, &crec, ctimeout, freport)) + return FALSE; + iJrecend = iPrecend; + iPrecend = iprecendhold; + + /* Process any data we have received. This will set iPrecend to + the end of the new decoded data. */ + if (! fjprocess_data (&cjneed)) + return FALSE; + + cnew = iPrecend - iprecendstart; + if (cnew < 0) + cnew += CRECBUFLEN; + + if (cnew > cineed) + cineed = 0; + else + cineed -= cnew; + + --ctimeout; + } + while (cnew == 0 && crec > 0 && ctimeout > 0); + + DEBUG_MESSAGE1 (DEBUG_PROTO, "fjreceive_data: Got %d decoded bytes", + cnew); + + *pcrec = cnew; + return TRUE; +} + +/* Decode the data in the buffer, optionally returning the number of + bytes needed to complete the next packet. */ + +static boolean +fjprocess_data (pcneed) + size_t *pcneed; +{ + int istart; + + istart = iPrecend; + while (istart != iJrecend) + { + int i, iget; + char ab[CHDRLEN]; + int cpacket, cdata, chave; + int iindex, iendindex; + + /* Find the next occurrence of FIRST. If we have to skip some + garbage bytes to get to it, zero them out (so they don't + confuse the 'i' protocol) and advance iPrecend. This will + save us from looking at them again. */ + if (abPrecbuf[istart] != FIRST) + { + int cintro; + char *zintro; + size_t cskipped; + + cintro = iJrecend - istart; + if (cintro < 0) + cintro = CRECBUFLEN - istart; + + zintro = memchr (abPrecbuf + istart, FIRST, (size_t) cintro); + if (zintro == NULL) + { + bzero (abPrecbuf + istart, (size_t) cintro); + istart = (istart + cintro) % CRECBUFLEN; + iPrecend = istart; + continue; + } + + cskipped = zintro - (abPrecbuf + istart); + bzero (abPrecbuf + istart, cskipped); + istart += cskipped; + iPrecend = istart; + } + + for (i = 0, iget = istart; + i < CHDRLEN && iget != iJrecend; + ++i, iget = (iget + 1) % CRECBUFLEN) + ab[i] = abPrecbuf[iget]; + + if (i < CHDRLEN) + { + if (pcneed != NULL) + *pcneed = CHDRLEN - i; + return TRUE; + } + + cpacket = CGETLENGTH (ab[1], ab[2]); + cdata = CGETLENGTH (ab[4], ab[5]); + + /* Make sure the header has the right magic characters, that the + data is not larger than the packet, and that we have an even + number of byte index characters. */ + if (ab[3] != FOURTH + || ab[6] != SEVENTH + || cdata > cpacket - CHDRLEN - 1 + || (cpacket - cdata - CHDRLEN - 1) % 2 == 1) + { + istart = (istart + 1) % CRECBUFLEN; + continue; + } + + chave = iJrecend - istart; + if (chave < 0) + chave += CRECBUFLEN; + + if (chave < cpacket) + { + if (pcneed != NULL) + *pcneed = cpacket - chave; + return TRUE; + } + + /* Figure out where the byte indices start and end. */ + iindex = (istart + CHDRLEN + cdata) % CRECBUFLEN; + iendindex = (istart + cpacket - 1) % CRECBUFLEN; + + /* Make sure the magic trailer character is there. */ + if (abPrecbuf[iendindex] != TRAILER) + { + istart = (istart + 1) % CRECBUFLEN; + continue; + } + + /* We have a packet to decode. The decoding process is simpler + than the encoding process, since all we have to do is examine + the byte indices. We zero out the byte indices as we go, so + that they will not confuse the 'i' protocol. */ + while (iindex != iendindex) + { + int ihigh, ilow; + boolean f32, f128; + int iset; + + ihigh = abPrecbuf[iindex] - INDEX_OFFSET; + abPrecbuf[iindex] = 0; + iindex = (iindex + 1) % CRECBUFLEN; + ilow = abPrecbuf[iindex] - INDEX_OFFSET; + abPrecbuf[iindex] = 0; + iindex = (iindex + 1) % CRECBUFLEN; + + /* Now we must undo the encoding, by adding 128 and xoring + with 32 as appropriate. Which to do is encoded in the + low byte, except that if the high byte is the special + value 126, then the low byte is actually the high byte + and both operations are performed. */ + f128 = TRUE; + f32 = TRUE; + if (ihigh == INDEX_MAX_HIGH) + iset = ilow * INDEX_MAX_LOW + INDEX_MAX_LOW - 1; + else + { + iset = ihigh * INDEX_MAX_LOW + ilow % INDEX_MAX_LOW; + if (ilow < INDEX_MAX_LOW) + f32 = FALSE; + else if (ilow < 2 * INDEX_MAX_LOW) + f128 = FALSE; + } + + /* Now iset is the index from the start of the data to the + byte to modify; adjust it to an index in abPrecbuf. */ + iset = (istart + CHDRLEN + iset) % CRECBUFLEN; + + if (f128) + abPrecbuf[iset] |= 0x80; + if (f32) + abPrecbuf[iset] ^= 0x20; + } + + /* Zero out the header and trailer to avoid confusing the 'i' + protocol, and update iPrecend to the end of decoded data. */ + for (i = 0, iget = istart; + i < CHDRLEN && iget != iJrecend; + ++i, iget = (iget + 1) % CRECBUFLEN) + abPrecbuf[iget] = 0; + abPrecbuf[iendindex] = 0; + iPrecend = (iendindex + 1) % CRECBUFLEN; + istart = iPrecend; + } + + if (pcneed != NULL) + *pcneed = CHDRLEN + 1; + return TRUE; +} diff --git a/gnu/libexec/uucp/uucico/prott.c b/gnu/libexec/uucp/uucico/prott.c new file mode 100644 index 0000000000000..44763958349ae --- /dev/null +++ b/gnu/libexec/uucp/uucico/prott.c @@ -0,0 +1,330 @@ +/* prott.c + The 't' protocol. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char prott_rcsid[] = "$Id: prott.c,v 1.1 1993/08/05 18:27:15 conklin Exp $"; +#endif + +#include "uudefs.h" +#include "uuconf.h" +#include "conn.h" +#include "trans.h" +#include "system.h" +#include "prot.h" + +/* This implementation is based on code written by Rick Adams. + + This code implements the 't' protocol, which does no error checking + whatsoever and thus requires an end-to-end verified eight bit + communication line, such as is provided by TCP. Using it with a + modem is unadvisable, since errors can occur between the modem and + the computer. */ + +/* The buffer size we use. */ +#define CTBUFSIZE (1024) + +/* The offset in the buffer to the data. */ +#define CTFRAMELEN (4) + +/* Commands are sent in multiples of this size. */ +#define CTPACKSIZE (512) + +/* A pointer to the buffer we will use. */ +static char *zTbuf; + +/* True if we are receiving a file. */ +static boolean fTfile; + +/* The timeout we use. */ +static int cTtimeout = 120; + +struct uuconf_cmdtab asTproto_params[] = +{ + { "timeout", UUCONF_CMDTABTYPE_INT, (pointer) &cTtimeout, NULL }, + { NULL, 0, NULL, NULL } +}; + +/* Local function. */ + +static boolean ftprocess_data P((struct sdaemon *qdaemon, boolean *pfexit, + size_t *pcneed)); + +/* Start the protocol. */ + +boolean +ftstart (qdaemon, pzlog) + struct sdaemon *qdaemon; + char **pzlog; +{ + *pzlog = NULL; + if (! fconn_set (qdaemon->qconn, PARITYSETTING_NONE, + STRIPSETTING_EIGHTBITS, XONXOFF_OFF)) + return FALSE; + zTbuf = (char *) xmalloc (CTBUFSIZE + CTFRAMELEN); + /* The first two bytes of the buffer are always zero. */ + zTbuf[0] = 0; + zTbuf[1] = 0; + fTfile = FALSE; + usysdep_sleep (2); + return TRUE; +} + +/* Stop the protocol. */ + +/*ARGSUSED*/ +boolean +ftshutdown (qdaemon) + struct sdaemon *qdaemon; +{ + xfree ((pointer) zTbuf); + zTbuf = NULL; + cTtimeout = 120; + return TRUE; +} + +/* Send a command string. We send everything up to and including the + null byte. The number of bytes we send must be a multiple of + TPACKSIZE. */ + +/*ARGSUSED*/ +boolean +ftsendcmd (qdaemon, z, ilocal, iremote) + struct sdaemon *qdaemon; + const char *z; + int ilocal; + int iremote; +{ + size_t clen, csend; + char *zalc; + boolean fret; + + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, "ftsendcmd: Sending command \"%s\"", z); + + clen = strlen (z); + + /* We need to send the smallest multiple of CTPACKSIZE which is + greater than clen (not equal to clen, since we need room for the + null byte). */ + csend = ((clen / CTPACKSIZE) + 1) * CTPACKSIZE; + + zalc = zbufalc (csend); + memcpy (zalc, z, clen); + bzero (zalc + clen, csend - clen); + + fret = fsend_data (qdaemon->qconn, zalc, csend, TRUE); + ubuffree (zalc); + return fret; +} + +/* Get space to be filled with data. We provide a buffer which has + four bytes at the start available to hold the length. */ + +/*ARGSIGNORED*/ +char * +ztgetspace (qdaemon, pclen) + struct sdaemon *qdaemon; + size_t *pclen; +{ + *pclen = CTBUFSIZE; + return zTbuf + CTFRAMELEN; +} + +/* Send out some data. We are allowed to modify the four bytes + preceding the buffer. This allows us to send the entire block with + header bytes in a single call. */ + +/*ARGSIGNORED*/ +boolean +ftsenddata (qdaemon, zdata, cdata, ilocal, iremote, ipos) + struct sdaemon *qdaemon; + char *zdata; + size_t cdata; + int ilocal; + int iremote; + long ipos; +{ + /* Here we do htonl by hand, since it doesn't exist everywhere. We + know that the amount of data cannot be greater than CTBUFSIZE, so + the first two bytes of this value will always be 0. They were + set to 0 in ftstart so we don't touch them here. This is useful + because we cannot portably right shift by 24 or 16, since we + might be dealing with sixteen bit integers. */ + zdata[-2] = (char) ((cdata >> 8) & 0xff); + zdata[-1] = (char) (cdata & 0xff); + + /* We pass FALSE to fsend_data since we don't expect the other side + to be sending us anything just now. */ + return fsend_data (qdaemon->qconn, zdata - CTFRAMELEN, cdata + CTFRAMELEN, + FALSE); +} + +/* Process data and return the amount we need in *pfneed. */ + +static boolean +ftprocess_data (qdaemon, pfexit, pcneed) + struct sdaemon *qdaemon; + boolean *pfexit; + size_t *pcneed; +{ + int cinbuf, cfirst, clen; + + *pfexit = FALSE; + + cinbuf = iPrecend - iPrecstart; + if (cinbuf < 0) + cinbuf += CRECBUFLEN; + + if (! fTfile) + { + /* We are not receiving a file. Commands are read in chunks of + CTPACKSIZE. */ + while (cinbuf >= CTPACKSIZE) + { + cfirst = CRECBUFLEN - iPrecstart; + if (cfirst > CTPACKSIZE) + cfirst = CTPACKSIZE; + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "ftprocess_data: Got %d command bytes", + cfirst); + + if (! fgot_data (qdaemon, abPrecbuf + iPrecstart, + (size_t) cfirst, abPrecbuf, + (size_t) CTPACKSIZE - cfirst, + -1, -1, (long) -1, TRUE, pfexit)) + return FALSE; + + iPrecstart = (iPrecstart + CTPACKSIZE) % CRECBUFLEN; + + if (*pfexit) + return TRUE; + + cinbuf -= CTPACKSIZE; + } + + if (pcneed != NULL) + *pcneed = CTPACKSIZE - cinbuf; + + return TRUE; + } + + /* Here we are receiving a file. The data comes in blocks. The + first four bytes contain the length, followed by that amount of + data. */ + + while (cinbuf >= CTFRAMELEN) + { + /* The length is stored in network byte order, MSB first. */ + + clen = (((((((abPrecbuf[iPrecstart] & 0xff) << 8) + + (abPrecbuf[(iPrecstart + 1) % CRECBUFLEN] & 0xff)) << 8) + + (abPrecbuf[(iPrecstart + 2) % CRECBUFLEN] & 0xff)) << 8) + + (abPrecbuf[(iPrecstart + 3) % CRECBUFLEN] & 0xff)); + + if (cinbuf < clen + CTFRAMELEN) + { + if (pcneed != NULL) + *pcneed = clen + CTFRAMELEN - cinbuf; + return TRUE; + } + + iPrecstart = (iPrecstart + CTFRAMELEN) % CRECBUFLEN; + + cfirst = CRECBUFLEN - iPrecstart; + if (cfirst > clen) + cfirst = clen; + + DEBUG_MESSAGE1 (DEBUG_PROTO, + "ftprocess_data: Got %d data bytes", + clen); + + if (! fgot_data (qdaemon, abPrecbuf + iPrecstart, + (size_t) cfirst, abPrecbuf, (size_t) (clen - cfirst), + -1, -1, (long) -1, TRUE, pfexit)) + return FALSE; + + iPrecstart = (iPrecstart + clen) % CRECBUFLEN; + + if (*pfexit) + return TRUE; + + cinbuf -= clen + CTFRAMELEN; + } + + if (pcneed != NULL) + *pcneed = CTFRAMELEN - cinbuf; + + return TRUE; +} + +/* Wait for data to come in and process it until we've reached the end + of a command or a file. */ + +boolean +ftwait (qdaemon) + struct sdaemon *qdaemon; +{ + while (TRUE) + { + boolean fexit; + size_t cneed, crec; + + if (! ftprocess_data (qdaemon, &fexit, &cneed)) + return FALSE; + if (fexit) + return TRUE; + + if (! freceive_data (qdaemon->qconn, cneed, &crec, cTtimeout, TRUE)) + return FALSE; + + if (crec == 0) + { + ulog (LOG_ERROR, "Timed out waiting for data"); + return FALSE; + } + } +} + +/* File level routine, to set fTfile correctly. */ + +/*ARGSUSED*/ +boolean +ftfile (qdaemon, qtrans, fstart, fsend, cbytes, pfhandled) + struct sdaemon *qdaemon; + struct stransfer *qtrans; + boolean fstart; + boolean fsend; + long cbytes; + boolean *pfhandled; +{ + *pfhandled = FALSE; + + if (! fsend) + fTfile = fstart; + + return TRUE; +} diff --git a/gnu/libexec/uucp/uucico/protz.c b/gnu/libexec/uucp/uucico/protz.c new file mode 100644 index 0000000000000..6510df7d798e7 --- /dev/null +++ b/gnu/libexec/uucp/uucico/protz.c @@ -0,0 +1,2626 @@ +/* protz.c Version 1.5, 92Apr24 */ +/* Modified by Ian Lance Taylor for Taylor UUCP 1.04 92Aug4. */ + +/* + * Doug Evans, dje@sspiff.UUCP or dje@ersys.edmonton.ab.ca + * + * This file provides the Zmodem protocol (by Chuck Forsberg) for + * Ian Taylor's UUCP package. + * + * It was originally developed to establish a uucp link between myself and my + * employer: Ivation Datasystems, Inc. of Ottawa. + * + * My thanks to Ivation for letting me release this to the public. Given that + * Zmodem is in the public domain, no additional copyrights have been added. + * + ***************************************************************************** + * + * It's been difficult fitting Zmodem into the UUCP world. I have been guided + * mostly by trying to plug it into Taylor UUCP. Where "the Zmodem way of doing + * things" conflicted with "the UUCP way of doing things", I have err'd on the + * side of UUCP. At the end of it all, I have achieved something that will plug + * into Taylor UUCP very easily, but some might argue that I have corrupted Z + * too much. At any rate, compatibility with sz/rz was sacrificed to achieve a + * clean UUCP protocol. Given that, I took the opportunity to start from + * scratch when defining protocol constants (EG: ZBIN). + * + * 1) I wasn't quite sure how to enhance Zmodem to handle send+receive in one + * session, so I added a 'g' protocol like initialization sequence. This + * also gets this stuff out of the way, in case we ever try to support + * full-duplex. + * + * Caller Callee + * ------ ------ + * ZINIT --> <-- ZINIT + * ZDATA (ZCRCF) --> <-- ZDATA (ZCRCF) + * ZACK --> <-- ZACK + * ZINITEND --> <-- ZINITEND + * + * ZINIT is a combination of ZRINIT and ZSINIT and is intended to exchange + * simple protocol information (flags) and the protocol version number. + * ZDATA is intended to include window size information as well as the + * "Myattn" string (although at the moment it doesn't contain anything). + * ZDATA may contain at most 1k bytes of data and is sent out as one ZCRCF + * packet. Two ack's (ZACK + ZINITEND) are needed to ensure both sides have + * received ZDATA. + * + * 2) I've hardcoded several protocol parameters, like 32 bit CRC's for data. + * Others are not supported (we don't need them). + * + * 3) ZHEX headers use 32 bit CRC's. + * + * 4) Zmodem sends the ZFILE message "in one chunk". If there are errors, the + * entire string is resent. I have continued this practice. All UUCP + * commands are sent "in one chunk". This can be changed down the road if + * necessary. + * + * 5) The ZEOF message has been replaced with a new ZCRCx value: ZCRCF. ZCRCF + * is identical to ZCRCW except that it indicates the end of the message. + * The protocol here is *not* a file transfer protocol. It is an end to end + * transport protocol (that preserves message boundaries). + * + * 6) Zmodem handles restarting a file transfer, but as best as I can tell UUCP + * does not. At least Taylor UUCP doesn't. And if UUCP does start handling + * file restart, can it be plugged into the existing Zmodem way with zero + * changes? Beats me. Therefore I have removed this part of the code. One + * can always put it back in if and when UUCP handles it. Ditto for other + * pieces of removed code: there's no point in overly complicating this code + * when supporting all the bells and whistles requires enhancements to UUCP + * itself. + * + * *** It is easier to put code back in in an upward compatible manner *** + * *** than it is to correct for misunderstood code or poorly merged *** + * *** (Zmodem vs UUCP) code. *** + * + * 7) For the character in the initial "protocol selection" sequence, I have + * chosen 'a'. I'm told 'z' is already in use for something that isn't + * Zmodem. It's entirely reasonable to believe that if Zmodem ever becomes a + * standard UUCP protocol, this won't be it (so I'll leave z/Z for them). + * Publicly, this is the 'a' protocol. Internally, it is refered to as 'z'. + * A little confusing, I know. Maybe in time I'll refer to it internally as + * 'a', or maybe in time this will be *the* 'z' protocol. + * + * 8) Since we are writing a transport protocol, which isn't supposed to know + * anything about what is being transfered or where it is coming from, the + * header data value has changed meaning. It no longer means "file position" + * but instead means "window position". It is a running counter of the bytes + * transfered. Each "message" begins on a 1k boundary so the count isn't a + * precise byte count. The counter wraps every 4 gigabytes, although this + * wrapping isn't supported yet. + * + * FIXME: At present the max data transfered per session is 4 gigabytes. + * + **************************************************************************** + * + * A typical message sequence is (master sending file to slave): + * + * Master Slave + * ------ ----- + * ZDATA (S, ZCRCF) --> + * <-- ZACK + * <-- ZDATA (SY, ZCRCF) + * ZACK --> + * ZDATA --> + * ... <-- ZACK/ZRPOS + * ZDATA (ZCRCF) --> + * <-- ZACK + * <-- ZDATA (CY, ZCRCF) + * ZACK --> + * + * A typical message sequence is (master receiving file from slave): + * + * Master Slave + * ------ ----- + * ZDATA (R, ZCRCF) --> + * <-- ZACK + * <-- ZDATA (RY, ZCRCF) + * ZACK --> + * <-- ZDATA + * ZACK/ZRPOS ... --> + * <-- ZDATA (ZCRCF) + * ZACK --> + * ZDATA (CY, ZCRCF) --> + * <-- ZACK + * + ***************************************************************************** + * + * Notes: + * 1) For future bidirectional concerns, keep packet types "unidirectional". + * Sender always uses: ZDATA, ZNAK + * Receiver always uses: ZRPOS, ZACK + * There is no intersection. + * + * I'm not sure if this is necessary or even useful, but it seems to be. + * + * 2) I use to store the byte count / 32 in the data header. This left 5 bits + * unused for future concerns. I removed this because of the following + * situation when sending a file: + * + * ZDATA (ZCRCG, xx bytes) - received ok + * ZDATA (ZCRCF, 0 bytes) - corrupted + * + * At this point the receiver would like to send back a ZRPOS with a value + * of the size of the file. However, it can't because the value is divided + * by 32, and it would have to round up to the next multiple of 32. This + * seemed a little ugly, so I went with using the entire header to store + * the byte count. + * + ***************************************************************************** + * + * Source version: + * + * 1.1,2,3 + * Protocol version 0 + * Early attempts, completely rewritten later. + * + * 1.4 Protocol version 1 + * Beta test sent to Ian for analysis 92Apr18. + * + * 1.5 Protocol version 1 + * Released 92Apr24. + * + ***************************************************************************** + * + * Protocol version: + * + * A version number is exchanged in the ZINIT message, so it is possible to + * correct or enhance the protocol, without breaking existing versions. + * The purpose of this section is to document these versions as they come out. + * Remember, this is the protocol version, not the source version. + * + * 0 Initial version. + * Zmodem controlled file transfer. This was more of a "plug Z + * into UUCP as is" port. + * + * 1 Complete rewrite. + * Made Z more of a transport protocol. UUCP now controls transfer and Z + * is on the same footing as the other UUCP protocols. + * Theoretically, there will be little pain when UUCP goes bidirectional. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char protz_rcsid[] = "$Id: protz.c,v 1.1 1993/08/05 18:27:16 conklin Exp $"; +#endif + +#include <errno.h> + +#include "uudefs.h" +#include "uuconf.h" +#include "conn.h" +#include "trans.h" +#include "system.h" +#include "prot.h" + +#define ZPROTOCOL_VERSION 1 + +/* + * Control message characters ... + */ + +#define ZPAD '*' /* Padding character begins frames */ +#define ZDLE 030 /* Ctrl-X Zmodem escape - `ala BISYNC DLE */ +#define ZBIN 'A' /* Binary frame indicator */ +#define ZHEX 'B' /* HEX frame indicator */ + +/* + * Frame types (see array "frametypes" in zm.c) ... + * + * Note that the numbers here have been reorganized, as we don't support + * all of them (nor do we need to). + * + * WARNING: The init sequence assumes ZINIT < ZDATA < ZACK < ZINITEND. + */ + +#define ZINIT 0 /* Init (contains protocol version, flags) */ +#define ZDATA 1 /* Data packet(s) follow */ +#define ZRPOS 2 /* Resume data trans at this position */ +#define ZACK 3 /* ACK to above */ +#define ZNAK 4 /* Last packet was garbled */ +#define Zreserved 5 /* reserved (for future concerns) */ +#define ZINITEND 6 /* end of init sequence */ +#define ZFIN 7 /* Finish session */ + +/* + * ZDLE sequences ... + * + * Note addition of ZCRCF: "end of message". + */ + +#define ZCRCE 'h' /* CRC next, frame ends, header packet follows */ +#define ZCRCG 'i' /* CRC next, frame continues nonstop */ +#define ZCRCQ 'j' /* CRC next, frame continues, ZACK expected */ +#define ZCRCW 'k' /* CRC next, ZACK expected, end of frame */ +#define ZCRCF 'l' /* CRC next, ZACK expected, end of message */ + +#define ZRUB0 'm' /* Translate to rubout 0177 */ +#define ZRUB1 'n' /* Translate to rubout 0377 */ + + +/* + * zdlread return values (internal) ... + * Other values are ZM_ERROR, ZM_TIMEOUT, ZM_RCDO. + */ + +#define GOTOR 0400 +#define GOTCRCE (ZCRCE | GOTOR) /* ZDLE-ZCRCE received */ +#define GOTCRCG (ZCRCG | GOTOR) /* ZDLE-ZCRCG received */ +#define GOTCRCQ (ZCRCQ | GOTOR) /* ZDLE-ZCRCQ received */ +#define GOTCRCW (ZCRCW | GOTOR) /* ZDLE-ZCRCW received */ +#define GOTCRCF (ZCRCF | GOTOR) /* ZDLE-ZCRCF received */ + +/* + * Byte positions within header array ... + */ + +#define ZF0 3 /* First flags byte */ +#define ZF1 2 +#define ZF2 1 +#define ZF3 0 + +#define ZP0 0 /* Low order 8 bits of position */ +#define ZP1 1 +#define ZP2 2 +#define ZP3 3 /* High order 8 bits of position */ + +/* + * Bit Masks for ZRQINIT flags byte ZF0 ... + */ + +#define TX_ESCCTL 1 /* Tx will escape control chars */ + +/* + * Possible errors when running ZMODEM ... + */ + +#define ZM_ERROR (-1) /* crc error, etc. */ +#define ZM_TIMEOUT (-2) +#define ZM_RCDO (-3) /* Carrier Lost */ + +/* + * ASCII characters ... + */ + +#define LF 012 +#define CR 015 +#define XON 021 +#define XOFF 023 + +#define XON_WAIT 10 /* seconds */ + +/* + * Packet sizes ... + * + * FIXME: CPACKETSIZE is hardcoded in a lot of places. + * It's not clear to me whether changing it's value would be a + * "good thing" or not. But of course that doesn't excuse the hardcoding. + */ + +#define CPACKETSIZE 1024 /* max packet size (data only) */ +#define CFRAMELEN 12 /* header size */ +#define CSUFFIXLEN 10 /* suffix at end of data packets */ +#define CEXCHANGE_INIT_RETRIES 4 + +/* The header CRC value. */ + +#if ANSI_C +#define IHDRCRC 0xDEBB20E3UL +#else +#define IHDRCRC ((unsigned long) 0xDEBB20E3L) +#endif + +/* packet buffer size */ +#define CPACKBUFSIZE (CFRAMELEN + 2 * CPACKETSIZE + CSUFFIXLEN + 42 /*slop*/) + +/* + * Data types ... + */ + +typedef unsigned char achdrval_t[4]; +typedef unsigned long hdrval_t; +typedef unsigned long winpos_t; + +/* + * Configurable parms ... + * + * FIXME: <cZrx_buf_len> isn't used yet. It may not be needed. + */ + +#define CTIMEOUT 10 +#define CRETRIES 10 +#define CSTARTUP_RETRIES 4 +#define CGARBAGE 2400 +#define CSEND_WINDOW 16384 +#define FESCAPE_CONTROL FALSE + +static int cZtimeout = CTIMEOUT; /* (seconds) */ +static int cZretries = CRETRIES; +static int cZstartup_retries = CSTARTUP_RETRIES; +static int cZmax_garbage = CGARBAGE; /* max garbage before header */ +static int cZtx_window = CSEND_WINDOW; /* our transmission window */ +static int cZrx_buf_len = 0; /* our reception buffer size */ +static boolean fZesc_ctl = FESCAPE_CONTROL; /* escape control chars */ + +struct uuconf_cmdtab asZproto_params[] = +{ + {"timeout", UUCONF_CMDTABTYPE_INT, (pointer) & cZtimeout, NULL}, + {"retries", UUCONF_CMDTABTYPE_INT, (pointer) & cZretries, NULL}, + {"startup-retries", UUCONF_CMDTABTYPE_INT, + (pointer) & cZstartup_retries, NULL}, + {"garbage", UUCONF_CMDTABTYPE_INT, (pointer) & cZmax_garbage, NULL}, + {"send-window", UUCONF_CMDTABTYPE_INT, (pointer) & cZtx_window, NULL}, + {"escape-control", UUCONF_CMDTABTYPE_BOOLEAN, (pointer) & fZesc_ctl, + NULL}, + {NULL, 0, NULL, NULL} +}; + +/* + * Variables for statistic gathering ... + * + * We use <wpZtxpos, wpZrxbytes> to record the number of "packets" + * sent/received. Packets is in double quotes because some of them aren't full. + */ + +static unsigned long cZheaders_sent; +static unsigned long cZheaders_received; +static unsigned long cZbytes_resent; +static unsigned long cZtimeouts; +static unsigned long cZerrors; + +/* + * Data buffers ... + */ + +static char *zZtx_buf; /* transmit buffer */ + +static char *zZtx_packet_buf; /* raw outgoing packet data */ +static char *zZrx_packet_buf; /* raw incoming packet data */ + +/* + * Transmitter state variables ... + */ + +static unsigned cZblklen; /* data length in sent/received packets */ +static unsigned cZtxwspac; /* spacing between ZCRCQ requests */ +/*static unsigned cZblklen_override;*//* override value for <cZblklen> */ +static unsigned cZtxwcnt; /* counter used to space ack requests */ +static unsigned cZrxwcnt; /* counter used to watch receiver's buf size */ +static winpos_t wpZtxstart; /* <wpZtxpos> when message started */ +static winpos_t wpZtxpos; /* transmitter position */ +static winpos_t wpZlastsync; /* last offset to which we got a ZRPOS */ +static winpos_t wpZlrxpos; /* receiver's last reported offset */ +static winpos_t wpZrxpos; /* receiver file position */ + +static int iZlast_tx_data_packet; /* type of last ZDATA packet (ZCRCx) */ +static int iZjunk_count; /* amount of garbage characters received */ +static int iZtleft; /* for dynamic packet resizing */ + +static int iZbeenhereb4; /* times we've been ZRPOS'd to same place */ + +/* + * Receiver state variables ... + */ + +static winpos_t wpZrxbytes; /* receiver byte count */ +static int iZlast_rx_data_packet; /* last successfully received ZCRCx packet */ + +/* + * Misc. globals ... + */ + +static char xon = XON; + +#ifdef DJE_TESTING +int uucptest = -1; +int uucptest2; +int uucptestseed; +#endif + +/* + * Kludge!!! + * See fzfinish_tx(). Basically the next two globals are used to record the + * fact that we got a ZDATA, but aren't quite ready to process it. + */ + +static int iZpkt_rcvd_kludge; /* -1 if not valid */ +static hdrval_t hvZpkt_hdrval_kludge; + +/* + * Packet types ... + */ + +static const char *azZframe_types[] = { + "Carrier Lost", /* -3 */ + "Timeout", /* -2 */ + "Error", /* -1 */ +#define FTOFFSET 3 + "ZINIT", + "ZDATA", + "ZRPOS", + "ZACK", + "ZNAK", + "Zreserved", + "ZINITEND", + "ZFIN", + "UNKNOWN!!!" +}; +#define FTNUMBER (sizeof(azZframe_types) / sizeof(char *)) + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif +#define ZZHEADER_NAME(itype) \ + azZframe_types[min((itype) + FTOFFSET, FTNUMBER - 1)] + +/* + * Local functions ... + */ + +static boolean fzsend_data P((struct sdaemon *qdaemon, char *zdata, + size_t cdata, boolean fendofmessage)); +static boolean fzprocess P((struct sdaemon *qdaemon)); +static boolean fzstart_proto P((struct sdaemon *qdaemon)); +static int izexchange_init P((struct sdaemon *qdaemon, int send_type, + achdrval_t send_val, achdrval_t recv_val)); +static boolean fzshutdown_proto P((struct sdaemon *qdaemon)); +static boolean fzstart_tx P((void)); +static boolean fzfinish_tx P((struct sdaemon *qdaemon, long *plredo)); +static boolean fzstart_rx P((void)); +static boolean fzfinish_rx P((struct sdaemon *qdaemon)); +static boolean fzsend_hdr P((struct sdaemon *qdaemon, int ipkttype, + int ihdrtype, hdrval_t hdrval, + boolean fcheckreceive)); +static boolean fzsend_data_packet P((struct sdaemon *qdaemon, char *zdata, + size_t cdata, int frameend, + boolean fcheckreceive)); +static int czbuild_header P((char *zresult, int ipkttype, int ihdrtype, + hdrval_t hdrval)); +static int czbuild_data_packet P((char *zresult, const char *zdata, + size_t cdata, int frameend)); +/* + * The rest of the functions do not follow Ian's naming style. I have left + * the names the same as the original zm source. Over time, they may change. + */ +static int izrecv_hdr P((struct sdaemon *qdaemon, achdrval_t hdr)); +static int zrbhdr32 P((struct sdaemon *qdaemon, achdrval_t hdr)); +static int zrhhdr P((struct sdaemon *qdaemon, achdrval_t hdr)); +static int zrdat32 P((struct sdaemon *qdaemon, char *buf, int length, + int *iprxcount)); +static int getinsync P((struct sdaemon *qdaemon, boolean flag)); +static char *zputhex P((char *p, int ch)); +static char *zputchar P((char *p, int ch)); +static int zgethex P((struct sdaemon *qdaemon)); +static int zdlread P((struct sdaemon *qdaemon)); +static int noxrd7 P((struct sdaemon *qdaemon)); +static int realreadchar P((struct sdaemon *qdaemon, int timeout)); +static boolean fzreceive_ready P((void)); +static void stohdr P((hdrval_t pos, achdrval_t hdr)); +static hdrval_t rclhdr P((achdrval_t hdr)); +static hdrval_t hvzencode_data_hdr P((winpos_t cbytes)); +static void zdecode_data_hdr P((hdrval_t hdrval, winpos_t *pcbytes)); +static winpos_t lzupdate_rxpos P((achdrval_t rx_hdr, winpos_t rxpos, + winpos_t lrxpos, winpos_t txpos)); + +/* + * This macro replaces readchar() because it achieves a noticable speed up. The + * readchar() function has been renamed realreadchar(). Thanks to Ian for + * running this stuff through a profiler to find this out. Ian suggests further + * speed ups may be obtained by doing a similar thing in zrdat32(). + */ + +/* Assign the next character to b. */ +#define READCHAR(qdaemon, b, i) \ + (iPrecstart != iPrecend \ + ? ((b) = BUCHAR (abPrecbuf[iPrecstart]), \ + iPrecstart = (iPrecstart + 1) % CRECBUFLEN) \ + : ((b) = realreadchar ((qdaemon), (i)))) + +/************************************************************************/ + + +/* + * Start the protocol ... + */ + +boolean +fzstart(qdaemon, pzlog) +struct sdaemon *qdaemon; +char **pzlog; +{ + *pzlog = zbufalc (sizeof "protocol 'a' starting: , , , , , " + 100); + sprintf (*pzlog, "protocol 'a' starting: %d, %d, %d, %d, %d, %d", + cZtimeout, cZretries, cZstartup_retries, + cZmax_garbage, cZtx_window, fZesc_ctl); + + if (! fconn_set (qdaemon->qconn, PARITYSETTING_NONE, + STRIPSETTING_EIGHTBITS, XONXOFF_OFF)) + return FALSE; + + /* + * For now, we place tight restrictions on the size of the transmit + * window. This might be relaxed in the future. If it is relaxed, + * some of these tests will stay, some will go. That is why it is + * coded like it is. + */ + + if (cZtx_window % 1024 != 0 || + cZtx_window < 4096 || cZtx_window > 65536 || + 65536 % cZtx_window != 0 + ) { + ulog (LOG_ERROR, + "fzstart: cZtx_window not one of 4096, 8192, 16384, 32768, 65536"); + return FALSE; + } + + zZtx_buf = (char *) xmalloc (CPACKETSIZE); + zZtx_packet_buf = (char *) xmalloc (CPACKBUFSIZE); + zZrx_packet_buf = (char *) xmalloc (CPACKBUFSIZE); + + iZlast_tx_data_packet = -1; + iZlast_rx_data_packet = -1; + + wpZtxpos = wpZlrxpos = wpZrxpos = wpZrxbytes = 0; + cZtxwspac = cZtx_window / 4; + + cZheaders_sent = cZheaders_received = cZbytes_resent = 0; + cZtimeouts = cZerrors = 0; + + iZpkt_rcvd_kludge = -1; + +#if 0 + /* + * We ensure <cZtx_window> is at least 4k, so the following is + * unnecessary. It can be put back in later if needed. + */ + if (cZblklen_override > cZtxwspac + || (!cZblklen_override && cZtxwspac < 1024)) + cZblklen_override = cZtxwspac; +#endif + +#ifdef DJE_TESTING + { + extern int uucptest,uucptest2,uucptestseed; + FILE *f; + + if (uucptest == -1) { + f = fopen ("/usr/local/src/bin/uucp/uucptest", "r"); + if (f != NULL) { + fscanf (f, "%d %d %d", + &uucptestseed, &uucptest, &uucptest2); + fclose (f); + } + srand (uucptestseed); + } + } +#endif + + /* + * Fire up the protocol (exchange init messages) ... + */ + + if (!fzstart_proto (qdaemon)) + return FALSE; + + return TRUE; +} + +/* + * Stop the protocol ... + */ + +boolean +fzshutdown(qdaemon) +struct sdaemon *qdaemon; +{ + (void) fzshutdown_proto (qdaemon); + + xfree ((pointer) zZtx_buf); + xfree ((pointer) zZtx_packet_buf); + xfree ((pointer) zZrx_packet_buf); + zZtx_buf = NULL; + zZtx_packet_buf = NULL; + zZrx_packet_buf = NULL; + + /* + * Print some informative statistics ... + * + * I use the word "messages" here instead of "headers" because the + * latter is jargonese. + */ + + ulog (LOG_NORMAL, + "Protocol 'a' messages: sent %lu, received %lu", + cZheaders_sent, cZheaders_received); + ulog (LOG_NORMAL, + "Protocol 'a' packets: sent %lu, received %lu", + wpZtxpos / 1024, wpZrxbytes / 1024); + if (cZbytes_resent != 0 || cZtimeouts != 0 || cZerrors != 0) + ulog (LOG_NORMAL, + "Protocol 'a' errors: bytes resent %lu, timeouts %lu, errors %lu", + cZbytes_resent, cZtimeouts, cZerrors); + + /* + * Reset all the parameters to their default values, so that the + * protocol parameters used for this connection do not affect the + * next one. + */ + + cZtimeout = CTIMEOUT; + cZretries = CRETRIES; + cZstartup_retries = CSTARTUP_RETRIES; + cZmax_garbage = CGARBAGE; + cZtx_window = CSEND_WINDOW; + fZesc_ctl = FESCAPE_CONTROL; + + cZheaders_sent = cZheaders_received = cZbytes_resent = 0; + cZtimeouts = cZerrors = 0; + + return TRUE; +} + +/* + * Send a command string ... + * We send everything up to and including the null byte. + * + * We assume the command will fit in the outgoing data buffer. + * FIXME: A valid assumption? + */ + +/*ARGSUSED*/ +boolean +fzsendcmd(qdaemon, z, ilocal, iremote) +struct sdaemon *qdaemon; +const char *z; +int ilocal; +int iremote; +{ + size_t n,clen; + long lredo; + char *zbuf; + + clen = strlen (z) + 1; + + DEBUG_MESSAGE1 (DEBUG_PROTO, "fzsendcmd: sending command %s", z); + + if (!fzstart_tx ()) /* must be called before zzgetspace() */ + return FALSE; + + if ((zbuf = zzgetspace (qdaemon, &n)) == NULL) + return FALSE; + +#if DEBUG > 0 + if (clen > n) + ulog (LOG_FATAL, "fzsendcmd: clen > n"); +#endif + + strcpy (zbuf, z); + + /* + * Send it out ... + */ + + do { + if (!fzsend_data (qdaemon, zbuf, clen, TRUE)) + return FALSE; + if (!fzfinish_tx (qdaemon, &lredo)) + return FALSE; + } while (lredo >= 0); + + return fzprocess (qdaemon); +} + +/* + * Allocate a packet to send out ... + * + * Note that 'z' has dynamic packet resizing and that <cZblklen> will range + * from 32 to 1024, in multiples of 2. + */ + +/*ARGSUSED*/ +char * +zzgetspace(qdaemon, pclen) +struct sdaemon *qdaemon; +size_t *pclen; +{ + *pclen = cZblklen; + return zZtx_buf; +} + +/* + * Send a block of data ... + * + * If (cdata == 0) then the end of the file has been reached. + */ + +/*ARGSUSED*/ +boolean +fzsenddata(qdaemon, zdata, cdata, ilocal, iremote, ipos) +struct sdaemon *qdaemon; +char *zdata; +size_t cdata; +int ilocal; +int iremote; +long ipos; +{ + DEBUG_MESSAGE1 (DEBUG_PROTO, "fzsenddata: %d bytes", cdata); + + if (! fzsend_data (qdaemon, zdata, cdata, cdata == 0)) + return FALSE; + return fzprocess (qdaemon); +} + +/* + * Send a block of data (command or file) ... + */ + +/* This should buffer the data internally. Until it does, it needs to + be able to reset the file position when it is called. This is + really ugly. */ +extern struct stransfer *qTsend; + +static boolean +fzsend_data(qdaemon, zdata, cdata, fendofmessage) +struct sdaemon *qdaemon; +char *zdata; +size_t cdata; +boolean fendofmessage; +{ + size_t n; + + if (iZlast_tx_data_packet == -1 || iZlast_tx_data_packet == ZCRCW) { + cZtxwcnt = cZrxwcnt = 0; + iZjunk_count = 0; + if (!fzsend_hdr (qdaemon, ZBIN, ZDATA, + hvzencode_data_hdr (wpZtxpos), TRUE)) + return FALSE; + } + + n = cdata; + + if (fendofmessage) + iZlast_tx_data_packet = ZCRCF; + else if (iZjunk_count > 3) + iZlast_tx_data_packet = ZCRCW; + else if (wpZtxpos == wpZlastsync) + iZlast_tx_data_packet = ZCRCW; + else if (cZrx_buf_len && (cZrxwcnt += n) >= cZrx_buf_len) + iZlast_tx_data_packet = ZCRCW; + else if ((cZtxwcnt += n) >= cZtxwspac) { + iZlast_tx_data_packet = ZCRCQ; + cZtxwcnt = 0; + } else + iZlast_tx_data_packet = ZCRCG; + + if (++iZtleft > 3) { + iZtleft = 0; + if (cZblklen < 1024) + cZblklen *= 2; +#if 0 /* <cZblklen_override> is currently unnecessary */ + if (cZblklen_override && cZblklen > cZblklen_override) + cZblklen = cZblklen_override; +#endif + if (cZblklen > 1024) + cZblklen = 1024; + if (cZrx_buf_len && cZblklen > cZrx_buf_len) + cZblklen = cZrx_buf_len; + } + +#if DEBUG > 1 + if (FDEBUGGING(DEBUG_PROTO)) { + const char *type; + + switch (iZlast_tx_data_packet) { + case ZCRCW: type = "ZCRCW"; break; + case ZCRCG: type = "ZCRCG"; break; + case ZCRCQ: type = "ZCRCQ"; break; + case ZCRCE: type = "ZCRCE"; break; + case ZCRCF: type = "ZCRCF"; break; + default : type = "UNKNOWN!!!"; break; + } + DEBUG_MESSAGE3 (DEBUG_PROTO, + "fzsend_data: %s, pos 0x%lx, %d bytes", + type, wpZtxpos, n); + } +#endif + + if (!fzsend_data_packet (qdaemon, zdata, n, iZlast_tx_data_packet, + TRUE)) + return FALSE; + + wpZtxpos += n; + + if (iZlast_tx_data_packet == ZCRCW) { + /* + * FIXME: Ideally this would be done in fzprocess. However, it + * is only called if there is data pending which there + * may not be yet. I could have patched fploop() a bit but + * for now, I've done it like this. + */ + switch (getinsync (qdaemon, FALSE)) { + case ZACK: + break; + case ZRPOS: + if (qTsend == NULL + || ! ffileisopen (qTsend->e)) { + ulog (LOG_ERROR, "Can't reset non-file"); + return FALSE; + } + iZlast_tx_data_packet = -1; /* trigger ZDATA */ + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fzsend_data: Seeking to %ld", + (long) (wpZrxpos - wpZtxstart)); + if (!ffileseek (qTsend->e, wpZrxpos - wpZtxstart)) { + ulog (LOG_ERROR, "seek: %s", strerror (errno)); + return FALSE; + } + break; + default: + return FALSE; + } + return TRUE; + } + + /* + * If we've reached the maximum transmit window size, let the + * receiver catch up ... + * + * I use (cZtx_window - 2048) to play it safe. + */ + + while (wpZtxpos - wpZlrxpos >= cZtx_window - 2048) { + if (iZlast_tx_data_packet != ZCRCQ) { + if (!fzsend_data_packet (qdaemon, zdata, (size_t) 0, + iZlast_tx_data_packet = ZCRCQ, + TRUE)) + return FALSE; + } + /* + * FIXME: I'd rather not call ffileseek() in this file. When we + * start buffering the outgoing data, the following + * ffileseek() will disappear. + */ + switch (getinsync (qdaemon, TRUE)) { + case ZACK: + break; + case ZRPOS: + if (qTsend == NULL + || ! ffileisopen (qTsend->e)) { + ulog (LOG_ERROR, "Can't reset non-file"); + return FALSE; + } + iZlast_tx_data_packet = -1; /* trigger ZDATA */ + DEBUG_MESSAGE1 (DEBUG_PROTO, + "fzsend_data: Seeking to %ld", + (long) (wpZrxpos - wpZtxstart)); + if (!ffileseek (qTsend->e, wpZrxpos - wpZtxstart)) { + ulog (LOG_ERROR, "seek: %s", strerror (errno)); + return FALSE; + } + break; + default: + return FALSE; + } + } + + return TRUE; +} + +/* + * Process existing data ... + */ + +static boolean +fzprocess(qdaemon) +struct sdaemon *qdaemon; +{ + int c,ch; + + while (fzreceive_ready ()) { + READCHAR (qdaemon, ch, 1); + switch (ch) { + case ZPAD: + /* see if we're detecting ZRPOS packets quickly */ + DEBUG_MESSAGE0 (DEBUG_PROTO, + "fzprocess: possible ZRPOS packet"); + /* We just ate the ZPAD char that getinsync + expects, so put it back. */ + iPrecstart = ((iPrecstart + CRECBUFLEN - 1) + % CRECBUFLEN); + c = getinsync (qdaemon, TRUE); + if (c == ZACK) + break; + /* FIXME: sz does a TCFLSH here */ +#if 0 /* FIXME: Not sure if this is needed, or where to put it. */ + /* ZCRCE - dinna wanna starta ping-pong game */ + if (!fzsend_data_packet (qdaemon, zZtx_packet_buf, + 0, ZCRCE, TRUE)) + return FALSE; +#endif + if (c == ZRPOS) { + if (qTsend == NULL + || ! ffileisopen (qTsend->e)) { + ulog (LOG_ERROR, + "Attempt to back up non-file"); + return FALSE; + } + if (! ffileseek (qTsend->e, + wpZrxpos - wpZtxstart)) { + ulog (LOG_ERROR, + "seek: %s", strerror (errno)); + return FALSE; + } + iZlast_tx_data_packet = -1; /* trigger ZDATA */ + break; /* not returning is intentional */ + } + return FALSE; + case XOFF: + case XOFF | 0200: + READCHAR (qdaemon, ch, XON_WAIT); + break; + case CR: + break; + default: + iZjunk_count++; + break; + } + } + + return TRUE; +} + +/* + * Wait for data to come in. + * + * This continues processing until a complete file or command has been + * received. + */ + +boolean +fzwait(qdaemon) +struct sdaemon *qdaemon; +{ + int c,cerr,rxcount; + boolean fexit; + achdrval_t rx_hdr; + + if (!fzstart_rx ()) + return FALSE; + + cerr = cZretries; + + goto nxthdr; + + for (;;) { + if (!fzsend_hdr (qdaemon, ZHEX, ZRPOS, + hvzencode_data_hdr (wpZrxbytes), FALSE)) + return FALSE; +nxthdr: + c = izrecv_hdr (qdaemon, rx_hdr); + + switch (c) { + case ZM_TIMEOUT: + case ZNAK: + if (--cerr < 0) { + ulog (LOG_ERROR, "fzwait: retries exhausted"); + return FALSE; + } + continue; + case ZM_ERROR: + if (--cerr < 0) { + ulog (LOG_ERROR, "fzwait: retries exhausted"); + return FALSE; + } + /*fport_break ();*/ + continue; + case ZM_RCDO: + case ZFIN: + return FALSE; + case ZRPOS: + case ZACK: + goto nxthdr; /* ignore, partner is out of sync */ + case ZDATA: { + winpos_t rx_bytes; + + zdecode_data_hdr (rclhdr (rx_hdr), &rx_bytes); + DEBUG_MESSAGE2 (DEBUG_PROTO, + "fzwait: bytes(us,them) 0x%lx,0x%lx", + wpZrxbytes, rx_bytes); + if (rx_bytes != wpZrxbytes) { + if (--cerr < 0) { + ulog (LOG_ERROR, + "fzwait: retries exhausted"); + return FALSE; + } + (void) zrdat32 (qdaemon, zZrx_packet_buf, + 1024, &rxcount); + /*fport_break ();*/ + /* + * FIXME: Seems to me we should ignore this one + * and go for a timeout, the theory being + * that the appropriate ZRPOS has already + * been sent. We're obviously out of sync. + * /dje 92Mar10 + */ + continue; /* goto nxthdr? */ + } +moredata: + /* + * Do not call fgot_data() with (rxcount == 0) if it's + * not ZCRCF. fgot_data() will erroneously think this + * is the end of the message. + */ + c = zrdat32 (qdaemon, zZrx_packet_buf, 1024, + &rxcount); +#if DEBUG > 1 + if (FDEBUGGING(DEBUG_PROTO)) { + const char *msg; + + if (c < 0) { + msg = ZZHEADER_NAME(c); + } else { + switch (c) { + case GOTCRCW: msg = "ZCRCW"; break; + case GOTCRCG: msg = "ZCRCG"; break; + case GOTCRCQ: msg = "ZCRCQ"; break; + case GOTCRCE: msg = "ZCRCE"; break; + case GOTCRCF: msg = "ZCRCF"; break; + default : msg = NULL; break; + } + } + if (msg != NULL) + DEBUG_MESSAGE2 (DEBUG_PROTO, + "fzwait: zrdat32: %s, %d bytes", + msg, rxcount); + else + DEBUG_MESSAGE2 (DEBUG_PROTO, + "fzwait: zrdat32: %d, %d bytes", + c, rxcount); + } +#endif + switch (c) { + case ZM_ERROR: /* CRC error */ + cZerrors++; + if (--cerr < 0) { + ulog (LOG_ERROR, + "fzwait: retries exhausted"); + return FALSE; + } + /*fport_break ();*/ + continue; + case ZM_TIMEOUT: + cZtimeouts++; + if (--cerr < 0) { + ulog (LOG_ERROR, + "fzwait: retries exhausted"); + return FALSE; + } + continue; + case ZM_RCDO: + return FALSE; + case GOTCRCW: + iZlast_rx_data_packet = ZCRCW; + cerr = cZretries; + if (rxcount != 0 + && !fgot_data (qdaemon, zZrx_packet_buf, + (size_t) rxcount, + (const char *) NULL, + (size_t) 0, + -1, -1, (long) -1, + TRUE, &fexit)) + return FALSE; + wpZrxbytes += rxcount; + if (!fzsend_hdr (qdaemon, ZHEX, ZACK, + hvzencode_data_hdr (wpZrxbytes), + FALSE)) + return FALSE; + if (! fsend_data (qdaemon->qconn, &xon, + (size_t) 1, FALSE)) + return FALSE; + goto nxthdr; + case GOTCRCQ: + iZlast_rx_data_packet = ZCRCQ; + cerr = cZretries; + if (rxcount != 0 + && !fgot_data (qdaemon, zZrx_packet_buf, + (size_t) rxcount, + (const char *) NULL, + (size_t) 0, + -1, -1, (long) -1, + TRUE, &fexit)) + return FALSE; + wpZrxbytes += rxcount; + if (!fzsend_hdr (qdaemon, ZHEX, ZACK, + hvzencode_data_hdr (wpZrxbytes), + FALSE)) + return FALSE; + goto moredata; + case GOTCRCG: + iZlast_rx_data_packet = ZCRCG; + cerr = cZretries; + if (rxcount != 0 + && !fgot_data (qdaemon, zZrx_packet_buf, + (size_t) rxcount, + (const char *) NULL, + (size_t) 0, + -1, -1, (long) -1, + TRUE, &fexit)) + return FALSE; + wpZrxbytes += rxcount; + goto moredata; + case GOTCRCE: + iZlast_rx_data_packet = ZCRCE; + cerr = cZretries; + if (rxcount != 0 + && !fgot_data (qdaemon, zZrx_packet_buf, + (size_t) rxcount, + (const char *) NULL, + (size_t) 0, + -1, -1, (long) -1, + TRUE, &fexit)) + return FALSE; + wpZrxbytes += rxcount; + goto nxthdr; + case GOTCRCF: + iZlast_rx_data_packet = ZCRCF; + /* + * fzfinish_rx() must be called before + * fgot_data() because fgot_data() will send + * out a UUCP-command but the sender won't be + * ready for it until it receives our final + * ZACK. + */ + cerr = cZretries; + wpZrxbytes += rxcount; + if (!fzfinish_rx (qdaemon)) + return FALSE; + if (!fgot_data (qdaemon, zZrx_packet_buf, + (size_t) rxcount, + (const char *) NULL, + (size_t) 0, -1, -1, + (long) -1, TRUE, &fexit)) + return FALSE; + /* + * FIXME: Examine <fexit>? + * Or maybe ensure it's TRUE? + */ + return TRUE; + } + return FALSE; + } + default: + ulog (LOG_FATAL, "fzwait: received header %s", + ZZHEADER_NAME(c)); + return FALSE; + } + } + + return TRUE; +} + +/* + * File level routine. Called when initiating/terminating file transfers. + * + * When starting to send a file: (TRUE, TRUE, cbytes) + * When starting to receive a file: (TRUE, FALSE, -1) + * When send EOF, check resend: (FALSE, TRUE, -1) + * When receive EOF, check re-receive: (FALSE, FALSE, -1) + */ + +boolean +fzfile(qdaemon, qtrans, fstart, fsend, cbytes, pfhandled) +struct sdaemon *qdaemon; +struct stransfer *qtrans; +boolean fstart; +boolean fsend; +long cbytes; +boolean *pfhandled; +{ + long iredo; + + *pfhandled = FALSE; + + DEBUG_MESSAGE2 (DEBUG_PROTO, "fzfile: fstart=%d, fsend=%d", fstart, + fsend); + + if (fsend) { + if (fstart) + return fzstart_tx (); + if (! fzfinish_tx (qdaemon, &iredo)) + return FALSE; + if (iredo >= 0) { + if (! ffileisopen (qtrans->e)) { + ulog (LOG_ERROR, + "Attempt to back up non-file"); + return FALSE; + } + if (! ffileseek (qtrans->e, iredo)) { + ulog (LOG_ERROR, + "seek: %s", strerror (errno)); + return FALSE; + } + *pfhandled = TRUE; + qtrans->fsendfile = TRUE; + return fqueue_send (qdaemon, qtrans); + } + } + + return TRUE; +} + +/****************************************************************************/ + + +#if 0 /* not used, we only use 32 bit crc's */ +/* + * crctab calculated by Mark G. Mendel, Network Systems Corporation + */ + +static unsigned short crctab[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; +#endif /* crctab */ + +/* + * Copyright (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +/* First, the polynomial itself and its table of feedback terms. The */ +/* polynomial is */ +/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */ +/* Note that we take it "backwards" and put the highest-order term in */ +/* the lowest-order bit. The X^32 term is "implied"; the LSB is the */ +/* X^31 term, etc. The X^0 term (usually shown as "+1") results in */ +/* the MSB being 1. */ + +/* Note that the usual hardware shift register implementation, which */ +/* is what we're using (we're merely optimizing it by doing eight-bit */ +/* chunks at a time) shifts bits into the lowest-order term. In our */ +/* implementation, that means shifting towards the right. Why do we */ +/* do it this way? Because the calculated CRC must be transmitted in */ +/* order from highest-order term to lowest-order term. UARTs transmit */ +/* characters in order from LSB to MSB. By storing the CRC this way, */ +/* we hand it to the UART in the order low-byte to high-byte; the UART */ +/* sends each low-bit to hight-bit; and the result is transmission bit */ +/* by bit from highest- to lowest-order term without requiring any bit */ +/* shuffling on our part. Reception works similarly. */ + +/* The feedback terms table consists of 256, 32-bit entries. Notes: */ +/* */ +/* The table can be generated at runtime if desired; code to do so */ +/* is shown later. It might not be obvious, but the feedback */ +/* terms simply represent the results of eight shift/xor opera- */ +/* tions for all combinations of data and CRC register values. */ +/* */ +/* The values must be right-shifted by eight bits by the "updcrc" */ +/* logic; the shift must be unsigned (bring in zeroes). On some */ +/* hardware you could probably optimize the shift in assembler by */ +/* using byte-swap instructions. */ + +static unsigned long crc_32_tab[] = { /* CRC polynomial 0xedb88320 */ + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, + 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, + 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, + 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, + 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, + 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, + 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, + 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, + 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, + 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, + 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, + 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, + 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, + 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, + 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, + 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, + 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, + 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, + 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, + 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, + 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, + 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, + 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, + 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, + 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, + 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, + 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL +}; + +/* + * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. + * NOTE: First argument must be in range 0 to 255. + * Second argument is referenced twice. + * + * Programmers may incorporate any or all code into their programs, + * giving proper credit within the source. Publication of the + * source routines is permitted so long as proper credit is given + * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, + * Omen Technology. + */ + +#define updcrc(cp, crc) (crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp) + +#define UPDC32(b, crc) \ + (crc_32_tab[((unsigned)(crc) ^ (unsigned)(b)) & 0xff] \ + ^ (((crc) >> 8) & 0x00ffffffL)) + +/****************************************************************************/ + +/* + * This section contains the guts of the Zmodem protocol. The intention + * is to leave as much of it alone as possible at the start. Overtime it + * will be cleaned up (EG: I'd like to clean up the naming of the globals). + * Also, Zmodem has a different coding style. Over time this will be converted + * to the Taylor UUCP coding style. + */ + +/* + * Start the protocol (exchange init packets) ... + * + * UUCP can transfer files in both directions in one session. Therefore the + * init sequence is a little different. + * + * 1) ZINIT packets are exchanged + * - contains protocol version and protocol flags + * 2) ZDATA packets are exchanged + * - is intended to contain various numeric and string information + * 3) ZACK packets are exchanged + * 4) ZINITEND packets are exchanged + */ + +static boolean +fzstart_proto(qdaemon) +struct sdaemon *qdaemon; +{ + int i; + achdrval_t tx_hdr,rx_hdr; + + for (i = 0; i < cZstartup_retries; i++) { + stohdr (0L, tx_hdr); + tx_hdr[ZF0] = ZPROTOCOL_VERSION; + if (fZesc_ctl) + tx_hdr[ZF1] |= TX_ESCCTL; + switch (izexchange_init (qdaemon, ZINIT, tx_hdr, rx_hdr)) { + case -1: return FALSE; + case 0: continue; + case 1: break; + } +#if 0 /* can't work, but kept for documentation */ + if (rx_hdr[ZF0] == 0) { + ulog (LOG_ERROR, "Old protocol version, init failed"); + return FALSE; + } +#endif + fZesc_ctl = fZesc_ctl || (rx_hdr[ZF1] & TX_ESCCTL) != 0; + + stohdr (0L, tx_hdr); + switch (izexchange_init (qdaemon, ZDATA, tx_hdr, rx_hdr)) { + case -1: return FALSE; + case 0: continue; + case 1: break; + } + + stohdr (0L, tx_hdr); + switch (izexchange_init (qdaemon, ZACK, tx_hdr, rx_hdr)) { + case -1: return FALSE; + case 0: continue; + case 1: break; + } + + stohdr (0L, tx_hdr); + switch (izexchange_init (qdaemon, ZINITEND, tx_hdr, rx_hdr)) { + case -1: return FALSE; + case 0: continue; + case 1: break; + } + + DEBUG_MESSAGE0 (DEBUG_PROTO, + "fzstart_proto: Protocol started"); + return TRUE; + + /* FIXME: see protg.c regarding sequencing here. */ + } + + ulog (LOG_ERROR, "Protocol init failed"); + return FALSE; +} + +/* + * Exchange init messages. This is based on 'g'. + * See the comments concerning fgexchange_init() in protg.c. + * + * We return 1 for success, 0 for restart, -1 for comm failure (terminate). + */ + +static int +izexchange_init(qdaemon, send_type, send_val, recv_val) +struct sdaemon *qdaemon; +int send_type; +achdrval_t send_val; +achdrval_t recv_val; +{ + int i,recv_type,count; + + for (i = 0; i < CEXCHANGE_INIT_RETRIES; i++) { + if (!fzsend_hdr (qdaemon, send_type == ZDATA ? ZBIN : ZHEX, + send_type, rclhdr (send_val), FALSE)) + return -1; + + /* + * The ZDATA packet is intended to contain the <Attn> string + * (eventually, if it's ever usable) and allow for anything + * else that will need to be thrown in. + */ + + if (send_type == ZDATA) { + count = czbuild_data_packet (zZtx_packet_buf, "", + (size_t) 1, ZCRCF); + if (!fsend_data (qdaemon->qconn, zZtx_packet_buf, + (size_t) count, FALSE)) + return -1; + } + + recv_type = izrecv_hdr (qdaemon, recv_val); + + switch (recv_type) { + case ZM_TIMEOUT: + case ZM_ERROR: + continue; + case ZM_RCDO: + case ZFIN: + return -1; + case ZINIT: + case ZACK: + case ZINITEND: + break; + case ZDATA: + if (zrdat32 (qdaemon, zZrx_packet_buf, 1024, &count) + == GOTCRCF) + break; + continue; + default: + continue; + } + + if (recv_type == send_type) + return 1; + + /* + * If the other side is farther along than we are, we have lost + * a packet. Fall immediately back to ZINIT (but don't fail + * if we are already doing ZINIT, since that would count + * against cStart_retries more than it should). + * + * FIXME: The ">" test is "<" in protg.c. Check who's right. + */ + + if (recv_type > send_type && send_type != ZINIT) + return 0; + + /* + * If we are sending ZINITEND and we receive an ZINIT, the + * other side has falled back (we know this because we have + * seen a ZINIT from them). Fall back ourselves to start + * the whole handshake over again. + */ + + if (recv_type == ZINIT && send_type == ZINITEND) + return 0; + } + + return 0; +} + +/* + * Shut down the protocol ... + */ + +static boolean +fzshutdown_proto(qdaemon) +struct sdaemon *qdaemon; +{ + (void) fzsend_hdr (qdaemon, ZHEX, ZFIN, 0L, FALSE); + return TRUE; +} + +/* + * Reset the transmitter side for sending a new message ... + */ + +static boolean +fzstart_tx() +{ + iZlast_tx_data_packet = -1; + + /* + * <wpZlastsync> is set to -1L to suppress ZCRCW request otherwise + * triggered by (wpZlastsync == wpZtxpos). + */ + + cZblklen = 1024; + wpZlastsync = -1L; + iZbeenhereb4 = 0; + iZtleft = 0; + iZjunk_count = 0; + + wpZtxpos = (wpZtxpos + 1024L) & ~1023L; /* next packet boundary */ + wpZlrxpos = wpZrxpos = wpZtxpos; + + wpZtxstart = wpZtxpos; /* so we can compute the "file offset" */ + + return TRUE; +} + +/* + * Finish the sending of a message ... + * + * Basically, we wait for some indication that the receiver received our last + * message. If the receiver tells us to restart from some point, we set + * *plredo to that point. + * + * FIXME: This function is a major kludge at the moment. It is taken from + * getinsync(). It is necessary because I don't yet buffer outgoing data. + * It will go away when we do (buffer outgoing data). + */ + +static boolean +fzfinish_tx(qdaemon, plredo) +struct sdaemon *qdaemon; +long *plredo; +{ + int c,cerr,ctimeouts; + achdrval_t rx_hdr; + winpos_t rx_bytes; + + *plredo = -1; + cerr = cZretries; + ctimeouts = 0; + + DEBUG_MESSAGE4 (DEBUG_PROTO, + "fzfinish_tx: txpos=0x%lx, rxpos=0x%lx, lrxpos=0x%lx, rxbytes=0x%lx", + wpZtxpos, wpZrxpos, wpZlrxpos, wpZrxbytes); + + for (;;) { + c = izrecv_hdr (qdaemon, rx_hdr); + + switch (c) { + case ZRPOS: + wpZrxpos = lzupdate_rxpos (rx_hdr, wpZrxpos, + wpZlrxpos, wpZtxpos); + /* + * If the receiver sends a ZRPOS for the 1k block after + * the one we're currently at, we lost the final ZACK. + * We cheat and ignore this ZRPOS. Remember: the theory + * is that this entire function will go away when we + * begin buffering the outgoing data. Of course, one + * can reword the protocol definition and say this + * isn't cheating at all. + */ + if (((wpZtxpos + 1024) & ~1023) == wpZrxpos) + return TRUE; + cZbytes_resent += wpZtxpos - wpZrxpos; + wpZlrxpos = wpZtxpos = wpZrxpos; + if (wpZlastsync == wpZrxpos) { + if (++iZbeenhereb4 > 4) + if (cZblklen > 32) + cZblklen /= 2; + /* FIXME: shouldn't we reset iZbeenhereb4? */ + } + wpZlastsync = wpZrxpos; + iZlast_tx_data_packet = ZCRCW; /* force a timeout */ + *plredo = wpZrxpos - wpZtxstart; + return TRUE; + case ZACK: + wpZrxpos = lzupdate_rxpos (rx_hdr, wpZrxpos, + wpZlrxpos, wpZtxpos); + wpZlrxpos = wpZrxpos; + if (wpZtxpos == wpZrxpos) /* the ACK we want? */ + return TRUE; + break; + case ZDATA: + /* + * We cheat here and take advantage of UUCP's current + * half duplex nature. If we get a ZDATA starting on + * the next 1k boundary, we lost the ZACK. We cheat and + * tuck it away so that izrecv_hdr() can later detect + * it. Remember: see above. + */ + zdecode_data_hdr (rclhdr (rx_hdr), &rx_bytes); + if (((wpZrxbytes + 1024L) & ~1023L) == rx_bytes) { + iZpkt_rcvd_kludge = ZDATA; + hvZpkt_hdrval_kludge = rclhdr (rx_hdr); + return TRUE; + } + break; /* ignore, out of sync (old) */ + case ZNAK: + /* + * We cheat here and take advantage of UUCP's current + * half duplex nature. If we get a ZNAK starting on + * the next 1k boundary, we lost the ZACK. We cheat and + * throw the ZNAK away. Remember: see above. + * + * On the other hand, if (rx_bytes == wpZrxbytes) then + * the other side is also in fzfinish_tx(). He must + * have lost our ZACK, so we send him another. + */ + zdecode_data_hdr (rclhdr (rx_hdr), &rx_bytes); + if (((wpZrxbytes + 1024L) & ~1023L) == rx_bytes) + return TRUE; + if (rx_bytes == wpZrxbytes) { + if (!fzsend_hdr (qdaemon, ZHEX, ZACK, + hvzencode_data_hdr (wpZrxbytes), + TRUE)) + return FALSE; + } + break; /* ignore, out of sync (old) */ + case ZFIN: + case ZM_RCDO: + return FALSE; + case ZM_TIMEOUT: + if (--cerr < 0) { + ulog (LOG_ERROR, + "fzfinish_tx: retries exhausted"); + return FALSE; + } + /* + * Normally the sender doesn't send NAK's for timeouts. + * We have to here because of the following scenario: + * + * - We send ZDATA/ZCRCF + * - They send ZACK (corrupted) + * - They send ZDATA/ZCRCF (corrupted) + * + * At this point, both sides are in fzfinish_tx(). + * We only send ZNAK every second timeout to increase + * our timeout delay vs. our partner. This tries to + * avoid ZRPOS and ZNAK "passing in transit". + */ + if (++ctimeouts % 2 == 0) + if (!fzsend_hdr (qdaemon, ZHEX, ZNAK, + hvzencode_data_hdr (wpZtxpos), + TRUE)) + return FALSE; + break; + case ZM_ERROR: + default: + if (--cerr < 0) { + ulog (LOG_ERROR, + "fzfinish_tx: retries exhausted"); + return FALSE; + } + if (!fzsend_hdr (qdaemon, ZHEX, ZNAK, + hvzencode_data_hdr (wpZtxpos), + TRUE)) + return FALSE; + break; + } + } +} + +/* + * Initialize the receiver ... + */ + +static boolean +fzstart_rx() +{ + wpZrxbytes = (wpZrxbytes + 1024L) & ~1023L; /* next packet boundary */ + + return TRUE; +} + +/* + * Terminate the receiver ... + * + * Acknowledge the last packet received. + */ + +static boolean +fzfinish_rx(qdaemon) +struct sdaemon *qdaemon; +{ + DEBUG_MESSAGE0 (DEBUG_PROTO, "fzfinish_rx: message/file received"); + + return fzsend_hdr (qdaemon, ZHEX, ZACK, + hvzencode_data_hdr (wpZrxbytes), FALSE); +} + +/* + * Send a Zmodem header to our partner ... + */ + +static boolean +fzsend_hdr(qdaemon, ipkttype, ihdrtype, hdrval, fcheckreceive) +struct sdaemon *qdaemon; +int ipkttype; +int ihdrtype; +hdrval_t hdrval; +boolean fcheckreceive; +{ + int cpacketlen; + + DEBUG_MESSAGE2 (DEBUG_PROTO, "fzsend_hdr: %s, data = 0x%lx", + ZZHEADER_NAME(ihdrtype), hdrval); + + cpacketlen = czbuild_header (zZtx_packet_buf, ipkttype, + ihdrtype, hdrval); + +#ifdef DJE_TESTING +#if 0 + if (ihdrtype == ZACK && rand () % 100 < uucptest2) { + cZheaders_sent++; + return TRUE; + } +#else + if (ihdrtype == ZACK || ihdrtype == ZDATA) { + boolean fresult; + int old; + extern int uucptest,uucptest2; + + old = uucptest; + uucptest = uucptest2; + cZheaders_sent++; + fresult = fsend_data (qdaemon->qconn, zZtx_packet_buf, + (size_t) cpacketlen, fcheckreceive); + uucptest = old; + return fresult; + } +#endif +#endif + cZheaders_sent++; + return fsend_data (qdaemon->qconn, zZtx_packet_buf, + (size_t) cpacketlen, fcheckreceive); +} + +/* + * Send a data packet to our partner ... + * <frameend> is one of ZCRCx. + */ + +static boolean +fzsend_data_packet(qdaemon, zdata, cdata, frameend, fcheckreceive) +struct sdaemon *qdaemon; +char *zdata; +size_t cdata; +int frameend; +boolean fcheckreceive; +{ + int cpacketlen; + + cpacketlen = czbuild_data_packet (zZtx_packet_buf, zdata, cdata, + frameend); + + return fsend_data (qdaemon->qconn, zZtx_packet_buf, + (size_t) cpacketlen, fcheckreceive); +} + +/* + * Build Zmodem headers ... + * + * Note that we use 32 bit CRC's for ZHEX headers. + * + * This function is a combination of zm fns: zsbhdr(), zsbh32(), and zshhdr(). + */ + +static int +czbuild_header(zresult, ipkttype, ihdrtype, hdrval) +char *zresult; +int ipkttype; +int ihdrtype; +hdrval_t hdrval; +{ + char *p; + int i; + unsigned long crc; + achdrval_t achdrval; + + p = zresult; + + switch (ipkttype) { + case ZBIN: + *p++ = ZPAD; + *p++ = ZDLE; + *p++ = ZBIN; + p = zputchar (p, ihdrtype); + crc = ICRCINIT; + crc = UPDC32 (ihdrtype, crc); + stohdr (hdrval, achdrval); + for (i = 0; i < 4; i++) { + p = zputchar (p, achdrval[i]); + crc = UPDC32 (achdrval[i], crc); + } + crc = ~crc; + for (i = 0; i < 4; i++) { + p = zputchar (p, (char) crc); + crc >>= 8; + } + break; + case ZHEX: /* build hex header */ + *p++ = ZPAD; + *p++ = ZPAD; + *p++ = ZDLE; + *p++ = ZHEX; + p = zputhex (p, ihdrtype); + crc = ICRCINIT; + crc = UPDC32 (ihdrtype, crc); + stohdr (hdrval, achdrval); + for (i = 0; i < 4; i++) { + p = zputhex (p, achdrval[i]); + crc = UPDC32 (achdrval[i], crc); + } + crc = ~crc; + for (i = 0; i < 4; i++) { + p = zputhex (p, (char) crc); + crc >>= 8; + } + *p++ = CR; + /* + * Uncork the remote in case a fake XOFF has stopped data flow. + */ + if (ihdrtype != ZFIN && ihdrtype != ZACK) /* FIXME: why? */ + *p++ = XON; + break; + default: + ulog (LOG_FATAL, "czbuild_header: ipkttype == %d", ipkttype); + break; + } + + return p - zresult; +} + +/* + * Build Zmodem data packets ... + * + * This function is zsdata() and zsda32() from the zm source. + */ + +static int +czbuild_data_packet(zresult, zdata, cdata, frameend) +char *zresult; +const char *zdata; +size_t cdata; +int frameend; +{ + char *p; + unsigned long crc; + + p = zresult; + + crc = ICRCINIT; + for ( ; cdata-- != 0; zdata++) { + char c; + + c = *zdata; + if (c & 0140) + *p++ = c; + else + p = zputchar (p, c); + crc = UPDC32 ((unsigned char) c, crc); + } + *p++ = ZDLE; + *p++ = frameend; + crc = UPDC32 (frameend, crc); + crc = ~crc; + for (cdata = 0; cdata < 4; cdata++) { + p = zputchar (p, (char) crc); + crc >>= 8; + } + if (frameend == ZCRCW || frameend == ZCRCE || frameend == ZCRCF) { + *p++ = CR; + *p++ = XON; + } + + return p - zresult; +} + +/* + * Read in a header ... + * + * This is function zgethdr() from the Zmodem source. + */ + +static int +izrecv_hdr(qdaemon, hdr) +struct sdaemon *qdaemon; +achdrval_t hdr; +{ + int c,cerr; + + /* + * Kludge alert! If another part of the program received a packet but + * wasn't ready to handle it, it is tucked away for us to handle now. + */ + + if (iZpkt_rcvd_kludge != -1) { + c = iZpkt_rcvd_kludge; + iZpkt_rcvd_kludge = -1; + stohdr (hvZpkt_hdrval_kludge, hdr); + DEBUG_MESSAGE2 (DEBUG_PROTO, + "izrecv_hdr: queued %s, data = 0x%lx", + ZZHEADER_NAME(c), rclhdr (hdr)); + cZheaders_received++; + return c; + } + + cerr = cZmax_garbage; /* Max bytes before start of frame */ + +again: + switch (c = noxrd7 (qdaemon)) { + case ZM_TIMEOUT: + case ZM_ERROR: + case ZM_RCDO: + goto fifi; + case ZPAD: /* This is what we want */ + break; + case CR: /* padding at end of previous header */ + default: + if (--cerr < 0) { + c = ZM_ERROR; + goto fifi; + } + goto again; + } + +splat: + switch (c = noxrd7 (qdaemon)) { + case ZPAD: + if (--cerr < 0) { + c = ZM_ERROR; + goto fifi; + } + goto splat; + case ZM_TIMEOUT: + case ZM_RCDO: + goto fifi; + case ZDLE: /* This is what we want */ + break; + default: + if (--cerr < 0) { + c = ZM_ERROR; + goto fifi; + } + goto again; + } + + switch (c = noxrd7 (qdaemon)) { + case ZM_TIMEOUT: + case ZM_RCDO: + goto fifi; + case ZBIN: + c = zrbhdr32 (qdaemon, hdr); + break; + case ZHEX: + c = zrhhdr (qdaemon, hdr); + break; + default: + if (--cerr < 0) { + c = ZM_ERROR; + goto fifi; + } + goto again; + } + +fifi: + switch (c) { + case ZM_TIMEOUT: + cZtimeouts++; + break; + case ZM_ERROR: + cZerrors++; + break; + case ZM_RCDO: + break; + default: + cZheaders_received++; + break; + } + DEBUG_MESSAGE2 (DEBUG_PROTO, "izrecv_hdr: %s, data = 0x%x", + ZZHEADER_NAME(c), rclhdr (hdr)); + + return c; +} + +/* + * Receive a binary style header (type and position) with 32 bit FCS ... + */ + +static int +zrbhdr32(qdaemon, hdr) +struct sdaemon *qdaemon; +achdrval_t hdr; +{ + int c,i,type; + unsigned long crc; + + if ((c = zdlread (qdaemon)) & ~0377) + return c; + type = c; + crc = ICRCINIT; + crc = UPDC32 (c, crc); + + for (i = 0; i < 4; i++) { + if ((c = zdlread (qdaemon)) & ~0377) + return c; + crc = UPDC32 (c, crc); + hdr[i] = (char) c; + } + for (i = 0; i < 4; i++) { + if ((c = zdlread (qdaemon)) & ~0377) + return c; + crc = UPDC32 (c, crc); + } + if (crc != IHDRCRC) + return ZM_ERROR; + + return type; +} + +/* + * Receive a hex style header (type and position) ... + */ + +static int +zrhhdr(qdaemon, hdr) +struct sdaemon *qdaemon; +achdrval_t hdr; +{ + int c,i,type; + unsigned long crc; + + if ((c = zgethex (qdaemon)) < 0) + return c; + type = c; + crc = ICRCINIT; + crc = UPDC32 (c, crc); + + for (i = 0; i < 4; i++) { + if ((c = zgethex (qdaemon)) < 0) + return c; + crc = UPDC32 (c, crc); + hdr[i] = (char) c; + } + for (i = 0; i < 4; i++) { + if ((c = zgethex (qdaemon)) < 0) + return c; + crc = UPDC32 (c, crc); + } + if (crc != IHDRCRC) + return ZM_ERROR; + + return type; +} + +/* + * Receive a data packet ... + */ + +static int +zrdat32(qdaemon, buf, length, iprxcount) +struct sdaemon *qdaemon; +char *buf; +int length; +int *iprxcount; +{ + int c,d; + unsigned long crc; + char *end; + + crc = ICRCINIT; + *iprxcount = 0; + end = buf + length; + while (buf <= end) { + if ((c = zdlread (qdaemon)) & ~0377) { +crcfoo: + switch (c) { + case GOTCRCE: + case GOTCRCG: + case GOTCRCQ: + case GOTCRCW: + case GOTCRCF: + d = c; + c &= 0377; + crc = UPDC32 (c, crc); + if ((c = zdlread (qdaemon)) & ~0377) + goto crcfoo; + crc = UPDC32 (c, crc); + if ((c = zdlread (qdaemon)) & ~0377) + goto crcfoo; + crc = UPDC32 (c, crc); + if ((c = zdlread (qdaemon)) & ~0377) + goto crcfoo; + crc = UPDC32 (c, crc); + if ((c = zdlread (qdaemon)) & ~0377) + goto crcfoo; + crc = UPDC32 (c, crc); + if (crc != IHDRCRC) + return ZM_ERROR; + *iprxcount = length - (end - buf); + return d; + case ZM_TIMEOUT: + case ZM_RCDO: + return c; + default: + return ZM_ERROR; + } + } + *buf++ = (char) c; + crc = UPDC32 (c, crc); + } + + return ZM_ERROR; /* bad packet, too long */ +} + +/* + * Respond to receiver's complaint, get back in sync with receiver ... + */ + +static int +getinsync(qdaemon, flag) +struct sdaemon *qdaemon; +boolean flag; +{ + int c,cerr; + achdrval_t rx_hdr; + + cerr = cZretries; + + for (;;) { + c = izrecv_hdr (qdaemon, rx_hdr); + + switch (c) { + case ZRPOS: + wpZrxpos = lzupdate_rxpos (rx_hdr, wpZrxpos, + wpZlrxpos, wpZtxpos); + cZbytes_resent += wpZtxpos - wpZrxpos; + wpZlrxpos = wpZtxpos = wpZrxpos; + if (wpZlastsync == wpZrxpos) { + if (++iZbeenhereb4 > 4) + if (cZblklen > 32) + cZblklen /= 2; + /* FIXME: shouldn't we reset iZbeenhereb4? */ + } + wpZlastsync = wpZrxpos; + return ZRPOS; + case ZACK: + wpZrxpos = lzupdate_rxpos (rx_hdr, wpZrxpos, + wpZlrxpos, wpZtxpos); + wpZlrxpos = wpZrxpos; + if (flag || wpZtxpos == wpZrxpos) + return ZACK; + break; + case ZNAK: { + winpos_t rx_bytes; + /* + * Our partner is in fzfinish_tx() and is waiting + * for ZACK ... + */ + zdecode_data_hdr (rclhdr (rx_hdr), &rx_bytes); + if (rx_bytes == wpZrxbytes) { + if (!fzsend_hdr (qdaemon, ZHEX, ZACK, + hvzencode_data_hdr (wpZrxbytes), + TRUE)) + return FALSE; + } + break; + } + case ZFIN: + case ZM_RCDO: + return c; + case ZM_TIMEOUT: + if (--cerr < 0) { + ulog (LOG_ERROR, + "getinsync: retries exhausted"); + return ZM_ERROR; + } + break; /* sender doesn't send ZNAK for timeout */ + case ZM_ERROR: + default: + if (--cerr < 0) { + ulog (LOG_ERROR, + "getinsync: retries exhausted"); + return ZM_ERROR; + } + if (!fzsend_hdr (qdaemon, ZHEX, ZNAK, + hvzencode_data_hdr (wpZtxpos), + TRUE)) + return ZM_ERROR; + break; + } + } +} + +/* + * Send a byte as two hex digits ... + */ + +static char * +zputhex(p, ch) +char *p; +int ch; +{ + static char digits[] = "0123456789abcdef"; + + *p++ = digits[(ch & 0xF0) >> 4]; + *p++ = digits[ch & 0xF]; + return p; +} + +/* + * Send character c with ZMODEM escape sequence encoding ... + * + * Escape XON, XOFF. + * FIXME: Escape CR following @ (Telenet net escape) ... disabled for now + * Will need to put back references to <lastsent>. + */ + +static char * +zputchar(p, ch) +char *p; +int ch; +{ + char c = ch; + + /* Quick check for non control characters */ + + if (c & 0140) { + *p++ = c; + } else { + switch (c & 0377) { + case ZDLE: + *p++ = ZDLE; + *p++ = c ^ 0100; + break; + case CR: +#if 0 + if (!fZesc_ctl && (lastsent & 0177) != '@') + goto sendit; +#endif + /* fall through */ + case 020: /* ^P */ + case XON: + case XOFF: + *p++ = ZDLE; + c ^= 0100; +/*sendit:*/ + *p++ = c; + break; + default: + if (fZesc_ctl && !(c & 0140)) { + *p++ = ZDLE; + c ^= 0100; + } + *p++ = c; + break; + } + } + + return p; +} + +/* + * Decode two lower case hex digits into an 8 bit byte value ... + */ + +static int +zgethex(qdaemon) +struct sdaemon *qdaemon; +{ + int c,n; + + if ((c = noxrd7 (qdaemon)) < 0) + return c; + n = c - '0'; + if (n > 9) + n -= ('a' - ':'); + if (n & ~0xF) + return ZM_ERROR; + if ((c = noxrd7 (qdaemon)) < 0) + return c; + c -= '0'; + if (c > 9) + c -= ('a' - ':'); + if (c & ~0xF) + return ZM_ERROR; + c += (n << 4); + + return c; +} + +/* + * Read a byte, checking for ZMODEM escape encoding ... + */ + +static int +zdlread(qdaemon) +struct sdaemon *qdaemon; +{ + int c; + +again: + READCHAR (qdaemon, c, cZtimeout); + if (c < 0) + return c; + if (c & 0140) /* quick check for non control characters */ + return c; + switch (c) { + case ZDLE: + break; + case XON: + goto again; + case XOFF: + READCHAR (qdaemon, c, XON_WAIT); + goto again; + default: + if (fZesc_ctl && !(c & 0140)) + goto again; + return c; + } + +again2: + READCHAR (qdaemon, c, cZtimeout); + if (c < 0) + return c; + switch (c) { + case ZCRCE: + case ZCRCG: + case ZCRCQ: + case ZCRCW: + case ZCRCF: + return c | GOTOR; + case ZRUB0: /* FIXME: This is never generated. */ + return 0177; + case ZRUB1: /* FIXME: This is never generated. */ + return 0377; + case XON: + goto again2; + case XOFF: + READCHAR (qdaemon, c, XON_WAIT); + goto again2; + default: + if (fZesc_ctl && !(c & 0140)) + goto again2; /* FIXME: why again2? */ + if ((c & 0140) == 0100) + return c ^ 0100; + break; + } + + return ZM_ERROR; +} + +/* + * Read a character from the modem line with timeout ... + * Eat parity bit, XON and XOFF characters. + */ + +static int +noxrd7(qdaemon) +struct sdaemon *qdaemon; +{ + int c; + + for (;;) { + READCHAR (qdaemon, c, cZtimeout); + if (c < 0) + return c; + switch (c &= 0177) { + case XON: + continue; + case XOFF: + READCHAR (qdaemon, c, XON_WAIT); + continue; + case CR: + case ZDLE: + return c; + default: + if (fZesc_ctl && !(c & 0140)) + continue; + return c; + } + } +} + +/* + * Read a character from the receive buffer, or from the line if empty ... + * + * <timeout> is in seconds (maybe make it tenths of seconds like in Zmodem?) + */ + +static int +realreadchar(qdaemon, timeout) +struct sdaemon *qdaemon; +int timeout; +{ + int c; + + if ((c = breceive_char (qdaemon->qconn, timeout, TRUE)) >= 0) + return c; + + switch (c) { + case -1: + return ZM_TIMEOUT; + case -2: + return ZM_RCDO; + } + + ulog (LOG_FATAL, "realreadchar: breceive_char() returned %d", c); + return ZM_ERROR; +} + + +/* + * Check if the receive channel has any characters in it. + * + * At present we can only test the receive buffer. No mechanism is available + * to go to the hardware. This should not be a problem though, as long as all + * appropriate calls to fsend_data() set <fdoread> to TRUE. + */ + +static boolean +fzreceive_ready() +{ + return iPrecstart != iPrecend; +} + +/* + * Store integer value in an achdrval_t ... + */ + +static void +stohdr(val, hdr) +hdrval_t val; +achdrval_t hdr; +{ + hdr[ZP0] = (char) val; + hdr[ZP1] = (char) (val >> 8); + hdr[ZP2] = (char) (val >> 16); + hdr[ZP3] = (char) (val >> 24); +} + +/* + * Recover an integer from a header ... + */ + +static hdrval_t +rclhdr(hdr) +achdrval_t hdr; +{ + hdrval_t v; + + v = hdr[ZP3] & 0377; + v = (v << 8) | (hdr[ZP2] & 0377); + v = (v << 8) | (hdr[ZP1] & 0377); + v = (v << 8) | (hdr[ZP0] & 0377); + + return v; +} + +/* + * Encode a <hdrval_t> from the byte count ... + * + * We use to store the byte count / 32 and a message sequence number which + * made this function very useful. Don't remove it. + * FIXME: Well, maybe remove it later. + */ + +static hdrval_t +hvzencode_data_hdr(cbytes) +winpos_t cbytes; +{ + return (hdrval_t) cbytes; +} + +/* + * Decode a <hdrval_t> into a byte count ... + * + * We use to store the byte count / 32 and a message sequence number which + * made this function very useful. Don't remove it. + * FIXME: Well, maybe remove it later. + */ + +static void +zdecode_data_hdr(hdrval, pcbytes) +hdrval_t hdrval; +winpos_t *pcbytes; +{ + *pcbytes = hdrval; +} + +/* + * Update <wpZrxpos> from the received data header value ... + * + * FIXME: Here is where we'd handle wrapping around at 4 gigabytes. + */ + +static winpos_t +lzupdate_rxpos(rx_hdr, rxpos, lrxpos, txpos) +achdrval_t rx_hdr; +winpos_t rxpos,lrxpos,txpos; +{ + winpos_t rx_pktpos; + + zdecode_data_hdr (rclhdr (rx_hdr), &rx_pktpos); + + DEBUG_MESSAGE4 (DEBUG_PROTO, + "lzupdate_rxpos: rx_pktpos=0x%lx, rxpos=0x%lx, lrxpos=0x%lx, txpos=0x%lx", + rx_pktpos, rxpos, lrxpos, txpos); + + /* + * Check if <rx_pktpos> valid. It could be old. + */ + + if (rx_pktpos < wpZlrxpos + || rx_pktpos > ((wpZtxpos + 1024L) & ~1023L)) + return rxpos; + + return rx_pktpos; +} diff --git a/gnu/libexec/uucp/uucico/rec.c b/gnu/libexec/uucp/uucico/rec.c new file mode 100644 index 0000000000000..b55d7f2c401f2 --- /dev/null +++ b/gnu/libexec/uucp/uucico/rec.c @@ -0,0 +1,1162 @@ +/* rec.c + Routines to receive a file. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char rec_rcsid[] = "$Id: rec.c,v 1.1 1993/08/05 18:27:18 conklin Exp $"; +#endif + +#include <errno.h> + +#include "uudefs.h" +#include "uuconf.h" +#include "system.h" +#include "prot.h" +#include "trans.h" + +/* If the other side does not tell us the size of a file it wants to + send us, we assume it is this long. This is only used for free + space checking. */ +#define CASSUMED_FILE_SIZE (10240) + +/* We keep this information in the pinfo field of the stransfer + structure. */ +struct srecinfo +{ + /* Local user to send mail to (may be NULL). */ + char *zmail; + /* Full file name. */ + char *zfile; + /* Temporary file name. */ + char *ztemp; + /* TRUE if this is a spool directory file. */ + boolean fspool; + /* TRUE if this was a local request. */ + boolean flocal; + /* TRUE if the file has been completely received. */ + boolean freceived; + /* TRUE if remote request has been replied to. */ + boolean freplied; + /* TRUE if we moved the file to the final destination. */ + boolean fmoved; +}; + +/* This structure is kept in the pinfo field if we are refusing a + remote request. */ +struct srecfailinfo +{ + /* Reason for refusal. */ + enum tfailure twhy; + /* TRUE if we have sent the reason for refusal. */ + boolean fsent; + /* TRUE if we have seen the end of the file. */ + boolean freceived; +}; + +/* Local functions. */ + +static void urrec_free P((struct stransfer *qtrans)); +static boolean flocal_rec_fail P((struct stransfer *qtrans, + struct scmd *qcmd, + const struct uuconf_system *qsys, + const char *zwhy)); +static boolean flocal_rec_send_request P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean flocal_rec_await_reply P((struct stransfer *qtrans, + struct sdaemon *qdaemon, + const char *zdata, + size_t cdata)); +static boolean fremote_send_reply P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean fremote_send_fail P((struct sdaemon *qdaemon, + struct scmd *qcmd, + enum tfailure twhy, + int iremote)); +static boolean fremote_send_fail_send P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean fremote_discard P((struct stransfer *qtrans, + struct sdaemon *qdaemon, + const char *zdata, size_t cdata)); +static boolean frec_file_end P((struct stransfer *qtrans, + struct sdaemon *qdaemon, + const char *zdata, size_t cdata)); +static boolean frec_file_send_confirm P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); + +/* Free up a receive stransfer structure. */ + +static void +urrec_free (qtrans) + struct stransfer *qtrans; +{ + struct srecinfo *qinfo = (struct srecinfo *) qtrans->pinfo; + + if (qinfo != NULL) + { + ubuffree (qinfo->zmail); + ubuffree (qinfo->zfile); + ubuffree (qinfo->ztemp); + xfree (qtrans->pinfo); + } + + utransfree (qtrans); +} + +/* Set up a request for a file from the remote system. This may be + called before the remote system has been called. + + This is the order of function calls: + + flocal_rec_file_init --> fqueue_local + flocal_rec_send_request (send R ...) --> fqueue_receive + flocal_rec_await_reply (open file, call pffile) --> fqueue_receive + receive file + frec_file_end (close and move file, call pffile) --> fqueue_send + frec_file_send_confirm (send CY) + */ + +boolean +flocal_rec_file_init (qdaemon, qcmd) + struct sdaemon *qdaemon; + struct scmd *qcmd; +{ + const struct uuconf_system *qsys; + boolean fspool; + char *zfile; + struct srecinfo *qinfo; + struct stransfer *qtrans; + + qsys = qdaemon->qsys; + + /* Make sure we are permitted to transfer files. */ + if (qdaemon->fcaller + ? ! qsys->uuconf_fcall_transfer + : ! qsys->uuconf_fcalled_transfer) + { + /* This case will have been checked by uucp or uux, but it could + have changed. */ + if (! qsys->uuconf_fcall_transfer + && ! qsys->uuconf_fcalled_transfer) + return flocal_rec_fail ((struct stransfer *) NULL, qcmd, qsys, + "not permitted to request files"); + return TRUE; + } + + fspool = fspool_file (qcmd->zto); + + if (fspool) + { + pointer puuconf; + int iuuconf; + const char *zlocalname; + struct uuconf_system slocalsys; + + /* Normal users are not allowed to request files to be received + into the spool directory. To support uux forwarding, we use + the special option '9'. This permits a file to be received + into the spool directory for the local system only without + the usual checking. This is only done for local requests, of + course. */ + if (qcmd->zto[0] != 'D' + || strchr (qcmd->zoptions, '9') == NULL) + return flocal_rec_fail ((struct stransfer *) NULL, qcmd, qsys, + "not permitted to receive"); + + puuconf = qdaemon->puuconf; + iuuconf = uuconf_localname (puuconf, &zlocalname); + if (iuuconf == UUCONF_NOT_FOUND) + { + zlocalname = zsysdep_localname (); + if (zlocalname == NULL) + return FALSE; + } + else if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + return FALSE; + } + + iuuconf = uuconf_system_info (puuconf, zlocalname, &slocalsys); + if (iuuconf == UUCONF_NOT_FOUND) + { + iuuconf = uuconf_system_local (puuconf, &slocalsys); + if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + return FALSE; + } + } + else if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + return FALSE; + } + + zfile = zsysdep_spool_file_name (&slocalsys, qcmd->zto, qcmd->pseq); + + (void) uuconf_system_free (puuconf, &slocalsys); + + if (zfile == NULL) + return FALSE; + } + else + { + zfile = zsysdep_add_base (qcmd->zto, qcmd->zfrom); + if (zfile == NULL) + return FALSE; + + /* Check permissions. */ + if (! fin_directory_list (zfile, qsys->uuconf_pzlocal_receive, + qsys->uuconf_zpubdir, TRUE, + FALSE, qcmd->zuser)) + { + ubuffree (zfile); + return flocal_rec_fail ((struct stransfer *) NULL, qcmd, qsys, + "not permitted to receive"); + } + + /* The 'f' option means that directories should not + be created if they do not already exist. */ + if (strchr (qcmd->zoptions, 'f') == NULL) + { + if (! fsysdep_make_dirs (zfile, TRUE)) + { + ubuffree (zfile); + return flocal_rec_fail ((struct stransfer *) NULL, qcmd, + qsys, "cannot create directories"); + } + } + } + + qinfo = (struct srecinfo *) xmalloc (sizeof (struct srecinfo)); + if (strchr (qcmd->zoptions, 'm') == NULL) + qinfo->zmail = NULL; + else + qinfo->zmail = zbufcpy (qcmd->zuser); + qinfo->zfile = zfile; + qinfo->ztemp = NULL; + qinfo->fspool = fspool; + qinfo->flocal = TRUE; + qinfo->freceived = FALSE; + qinfo->freplied = TRUE; + + qtrans = qtransalc (qcmd); + qtrans->psendfn = flocal_rec_send_request; + qtrans->pinfo = (pointer) qinfo; + + return fqueue_local (qdaemon, qtrans); +} + +/* Report an error for a local receive request. */ + +static boolean +flocal_rec_fail (qtrans, qcmd, qsys, zwhy) + struct stransfer *qtrans; + struct scmd *qcmd; + const struct uuconf_system *qsys; + const char *zwhy; +{ + if (zwhy != NULL) + { + ulog (LOG_ERROR, "%s: %s", qcmd->zfrom, zwhy); + (void) fmail_transfer (FALSE, qcmd->zuser, (const char *) NULL, zwhy, + qcmd->zfrom, qsys->uuconf_zname, + qcmd->zto, (const char *) NULL, + (const char *) NULL); + (void) fsysdep_did_work (qcmd->pseq); + } + if (qtrans != NULL) + urrec_free (qtrans); + return TRUE; +} + +/* This is called when we are ready to send the actual request to the + other system. */ + +static boolean +flocal_rec_send_request (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct srecinfo *qinfo = (struct srecinfo *) qtrans->pinfo; + long cbytes, cbytes2; + size_t clen; + char *zsend; + boolean fret; + + qinfo->ztemp = zsysdep_receive_temp (qdaemon->qsys, qinfo->zfile, + (const char *) NULL); + if (qinfo->ztemp == NULL) + { + urrec_free (qtrans); + return FALSE; + } + + /* Check the amount of free space available for both the temporary + file and the real file. */ + cbytes = csysdep_bytes_free (qinfo->ztemp); + cbytes2 = csysdep_bytes_free (qinfo->zfile); + if (cbytes < cbytes2) + cbytes = cbytes2; + if (cbytes != -1) + { + cbytes -= qdaemon->qsys->uuconf_cfree_space; + if (cbytes < 0) + cbytes = 0; + } + + if (qdaemon->clocal_size != -1 + && (cbytes == -1 || qdaemon->clocal_size < cbytes)) + cbytes = qdaemon->clocal_size; + + /* We send the string + R from to user options + + We put a dash in front of options. If we are talking to a + counterpart, we also send the maximum size file we are prepared + to accept, as returned by esysdep_open_receive. */ + clen = (strlen (qtrans->s.zfrom) + strlen (qtrans->s.zto) + + strlen (qtrans->s.zuser) + strlen (qtrans->s.zoptions) + 30); + zsend = zbufalc (clen); + if ((qdaemon->ifeatures & FEATURE_SIZES) == 0) + sprintf (zsend, "R %s %s %s -%s", qtrans->s.zfrom, qtrans->s.zto, + qtrans->s.zuser, qtrans->s.zoptions); + else if ((qdaemon->ifeatures & FEATURE_V103) == 0) + sprintf (zsend, "R %s %s %s -%s 0x%lx", qtrans->s.zfrom, qtrans->s.zto, + qtrans->s.zuser, qtrans->s.zoptions, (unsigned long) cbytes); + else + sprintf (zsend, "R %s %s %s -%s %ld", qtrans->s.zfrom, qtrans->s.zto, + qtrans->s.zuser, qtrans->s.zoptions, cbytes); + + fret = (*qdaemon->qproto->pfsendcmd) (qdaemon, zsend, qtrans->ilocal, + qtrans->iremote); + ubuffree (zsend); + if (! fret) + { + urrec_free (qtrans); + return FALSE; + } + + qtrans->fcmd = TRUE; + qtrans->precfn = flocal_rec_await_reply; + + return fqueue_receive (qdaemon, qtrans); +} + +/* This is called when a reply is received for the request. */ + +/*ARGSUSED*/ +static boolean +flocal_rec_await_reply (qtrans, qdaemon, zdata, cdata) + struct stransfer *qtrans; + struct sdaemon *qdaemon; + const char *zdata; + size_t cdata; +{ + struct srecinfo *qinfo = (struct srecinfo *) qtrans->pinfo; + long crestart; + const char *zlog; + + if (zdata[0] != 'R' + || (zdata[1] != 'Y' && zdata[1] != 'N')) + { + ulog (LOG_ERROR, "%s: bad response to receive request: \"%s\"", + qtrans->s.zfrom, zdata); + urrec_free (qtrans); + return FALSE; + } + + if (zdata[1] == 'N') + { + boolean fnever; + const char *zerr; + + fnever = TRUE; + if (zdata[2] == '2') + zerr = "no such file"; + else if (zdata[2] == '6') + { + /* We sent over the maximum file size we were prepared to + receive, and the remote system is telling us that the + file is larger than that. Try again later. It would be + better if we could know whether there will ever be enough + room. */ + zerr = "too large to receive now"; + fnever = FALSE; + } + else + zerr = "unknown reason"; + + if (fnever) + return flocal_rec_fail (qtrans, &qtrans->s, qdaemon->qsys, zerr); + + ulog (LOG_ERROR, "%s: %s", qtrans->s.zfrom, zerr); + + urrec_free (qtrans); + + return TRUE; + } + + /* The mode should have been sent as "RY 0%o". If it wasn't, we use + 0666. */ + qtrans->s.imode = (unsigned int) strtol ((char *) (zdata + 2), + (char **) NULL, 8); + if (qtrans->s.imode == 0) + qtrans->s.imode = 0666; + + /* Open the file to receive into. We just ignore any restart count, + since we have no way to tell it to the other side. SVR4 may have + some way to do this, but I don't know what it is. */ + qtrans->e = esysdep_open_receive (qdaemon->qsys, qinfo->zfile, + (const char *) NULL, qinfo->ztemp, + &crestart); + if (! ffileisopen (qtrans->e)) + return flocal_rec_fail (qtrans, &qtrans->s, qdaemon->qsys, + "cannot open file"); + + if (qinfo->fspool) + zlog = qtrans->s.zto; + else + zlog = qinfo->zfile; + qtrans->zlog = zbufalc (sizeof "Receiving " + strlen (zlog)); + sprintf (qtrans->zlog, "Receiving %s", zlog); + + if (qdaemon->qproto->pffile != NULL) + { + boolean fhandled; + + if (! (*qdaemon->qproto->pffile) (qdaemon, qtrans, TRUE, FALSE, + (long) -1, &fhandled)) + { + (void) ffileclose (qtrans->e); + return flocal_rec_fail (qtrans, &qtrans->s, qdaemon->qsys, + (const char *) NULL); + } + if (fhandled) + return TRUE; + } + + qtrans->frecfile = TRUE; + qtrans->psendfn = frec_file_send_confirm; + qtrans->precfn = frec_file_end; + + return fqueue_receive (qdaemon, qtrans); +} + +/* Make sure there is still enough disk space available to receive a + file. */ + +boolean +frec_check_free (qtrans, cfree_space) + struct stransfer *qtrans; + long cfree_space; +{ + struct srecinfo *qinfo = (struct srecinfo *) qtrans->pinfo; + long cfree1, cfree2; + + cfree1 = csysdep_bytes_free (qinfo->ztemp); + cfree2 = csysdep_bytes_free (qinfo->zfile); + if (cfree1 < cfree2) + cfree1 = cfree2; + if (cfree1 != -1 && cfree1 < cfree_space) + { + ulog (LOG_ERROR, "%s: too big to receive now", qinfo->zfile); + return FALSE; + } + + return TRUE; +} + +/* A remote request to send a file to the local system, meaning that + we are going to receive a file. + + If we are using a protocol which does not support multiple + channels, the remote system will not start sending us the file + until it has received our confirmation. In that case, the order of + functions is as follows: + + fremote_send_file_init (open file) --> fqueue_remote + fremote_send_reply (send SY, call pffile) --> fqueue_receive + receive file + frec_file_end (close and move file, call pffile) --> fqueue_send + frec_file_send_confirm (send CY) + + If the protocol supports multiple channels, then the remote system + will start sending the file immediately after the send request. + That means that the data may come in before remote_send_reply is + called, so frec_file_end may be called before fremote_send_reply. + Note that this means the pffile entry points may be called in + reverse order for such a protocol. + + If the send request is rejected, via fremote_send_fail, and the + protocol supports multiple channels, we must accept and discard + data until a zero byte buffer is received from the other side, + indicating that it has received our rejection. + + This code also handles execution requests, which are very similar + to send requests. */ + +boolean +fremote_send_file_init (qdaemon, qcmd, iremote) + struct sdaemon *qdaemon; + struct scmd *qcmd; + int iremote; +{ + const struct uuconf_system *qsys; + boolean fspool; + char *zfile; + openfile_t e; + char *ztemp; + long cbytes, cbytes2; + long crestart; + struct srecinfo *qinfo; + struct stransfer *qtrans; + const char *zlog; + + qsys = qdaemon->qsys; + + if (! qsys->uuconf_frec_request) + { + ulog (LOG_ERROR, "%s: not permitted to receive files from remote", + qcmd->zfrom); + return fremote_send_fail (qdaemon, qcmd, FAILURE_PERM, iremote); + } + + fspool = fspool_file (qcmd->zto); + + /* We don't accept remote command files. An execution request may + only send a simple data file. */ + if ((fspool && qcmd->zto[0] == 'C') + || (qcmd->bcmd == 'E' + && (! fspool || qcmd->zto[0] != 'D'))) + { + ulog (LOG_ERROR, "%s: not permitted to receive", qcmd->zfrom); + return fremote_send_fail (qdaemon, qcmd, FAILURE_PERM, iremote); + } + + /* See if we have already received this file in a previous + conversation. */ + if (fsysdep_already_received (qsys, qcmd->zto, qcmd->ztemp)) + return fremote_send_fail (qdaemon, qcmd, FAILURE_RECEIVED, iremote); + + if (fspool) + { + zfile = zsysdep_spool_file_name (qsys, qcmd->zto, (pointer) NULL); + if (zfile == NULL) + return FALSE; + } + else + { + zfile = zsysdep_local_file (qcmd->zto, qsys->uuconf_zpubdir); + if (zfile != NULL) + { + char *zadd; + + zadd = zsysdep_add_base (zfile, qcmd->zfrom); + ubuffree (zfile); + zfile = zadd; + } + if (zfile == NULL) + return FALSE; + + /* Check permissions. */ + if (! fin_directory_list (zfile, qsys->uuconf_pzremote_receive, + qsys->uuconf_zpubdir, TRUE, + FALSE, (const char *) NULL)) + { + ulog (LOG_ERROR, "%s: not permitted to receive", zfile); + ubuffree (zfile); + return fremote_send_fail (qdaemon, qcmd, FAILURE_PERM, iremote); + } + + if (strchr (qcmd->zoptions, 'f') == NULL) + { + if (! fsysdep_make_dirs (zfile, TRUE)) + { + ubuffree (zfile); + return fremote_send_fail (qdaemon, qcmd, FAILURE_OPEN, + iremote); + } + } + } + + ztemp = zsysdep_receive_temp (qsys, zfile, qcmd->ztemp); + + /* Adjust the number of bytes we are prepared to receive according + to the amount of free space we are supposed to leave available + and the maximum file size we are permitted to transfer. */ + cbytes = csysdep_bytes_free (ztemp); + cbytes2 = csysdep_bytes_free (zfile); + if (cbytes < cbytes2) + cbytes = cbytes2; + + if (cbytes != -1) + { + cbytes -= qsys->uuconf_cfree_space; + if (cbytes < 0) + cbytes = 0; + } + + if (qdaemon->cremote_size != -1 + && (cbytes == -1 || qdaemon->cremote_size < cbytes)) + cbytes = qdaemon->cremote_size; + + /* If the number of bytes we are prepared to receive is less than + the file size, we must fail. If the remote did not tell us the + file size, arbitrarily assumed that it is 10240 bytes. */ + if (cbytes != -1) + { + long csize; + + csize = qcmd->cbytes; + if (csize == -1) + csize = CASSUMED_FILE_SIZE; + if (cbytes < csize) + { + ulog (LOG_ERROR, "%s: too big to receive", zfile); + ubuffree (ztemp); + ubuffree (zfile); + return fremote_send_fail (qdaemon, qcmd, FAILURE_SIZE, iremote); + } + } + + /* Open the file to receive into. This may find an old copy of the + file, which will be used for file restart if the other side + supports it. */ + e = esysdep_open_receive (qsys, zfile, qcmd->ztemp, ztemp, &crestart); + if (! ffileisopen (e)) + { + ubuffree (ztemp); + ubuffree (zfile); + return fremote_send_fail (qdaemon, qcmd, FAILURE_OPEN, iremote); + } + + if (crestart > 0) + { + if ((qdaemon->ifeatures & FEATURE_RESTART) == 0) + crestart = -1; + else + { + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, + "fremote_send_file_init: Restarting receive from %ld", + crestart); + if (! ffileseek (e, crestart)) + { + ulog (LOG_ERROR, "seek: %s", strerror (errno)); + (void) ffileclose (e); + ubuffree (ztemp); + ubuffree (zfile); + return FALSE; + } + } + } + + qinfo = (struct srecinfo *) xmalloc (sizeof (struct srecinfo)); + if (strchr (qcmd->zoptions, 'n') == NULL) + qinfo->zmail = NULL; + else + qinfo->zmail = zbufcpy (qcmd->znotify); + qinfo->zfile = zfile; + qinfo->ztemp = ztemp; + qinfo->fspool = fspool; + qinfo->flocal = FALSE; + qinfo->freceived = FALSE; + qinfo->freplied = FALSE; + + qtrans = qtransalc (qcmd); + qtrans->psendfn = fremote_send_reply; + qtrans->precfn = frec_file_end; + qtrans->iremote = iremote; + qtrans->pinfo = (pointer) qinfo; + qtrans->frecfile = TRUE; + qtrans->e = e; + if (crestart > 0) + qtrans->ipos = crestart; + + if (qcmd->bcmd == 'E') + zlog = qcmd->zcmd; + else + { + if (qinfo->fspool) + zlog = qcmd->zto; + else + zlog = qinfo->zfile; + } + qtrans->zlog = zbufalc (sizeof "Receiving " + strlen (zlog)); + sprintf (qtrans->zlog, "Receiving %s", zlog); + + return fqueue_remote (qdaemon, qtrans); +} + +/* Reply to a send request, and prepare to receive the file. */ + +static boolean +fremote_send_reply (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct srecinfo *qinfo = (struct srecinfo *) qtrans->pinfo; + char ab[50]; + + ab[0] = qtrans->s.bcmd; + ab[1] = 'Y'; + if (qtrans->ipos <= 0) + ab[2] = '\0'; + else + sprintf (ab + 2, " 0x%lx", (unsigned long) qtrans->ipos); + + if (! (*qdaemon->qproto->pfsendcmd) (qdaemon, ab, qtrans->ilocal, + qtrans->iremote)) + { + (void) ffileclose (qtrans->e); + (void) remove (qinfo->ztemp); + urrec_free (qtrans); + return FALSE; + } + + qinfo->freplied = TRUE; + + if (qdaemon->qproto->pffile != NULL) + { + boolean fhandled; + + if (! (*qdaemon->qproto->pffile) (qdaemon, qtrans, TRUE, FALSE, + (long) -1, &fhandled)) + { + (void) ffileclose (qtrans->e); + (void) remove (qinfo->ztemp); + urrec_free (qtrans); + return FALSE; + } + if (fhandled) + return TRUE; + } + + /* If the file has been completely received, we just want to send + the final confirmation. Otherwise, we must wait for the file + first. */ + qtrans->psendfn = frec_file_send_confirm; + if (qinfo->freceived) + return fqueue_send (qdaemon, qtrans); + else + return fqueue_receive (qdaemon, qtrans); +} + +/* If we can't receive a file, queue up a response to the remote + system. */ + +static boolean +fremote_send_fail (qdaemon, qcmd, twhy, iremote) + struct sdaemon *qdaemon; + struct scmd *qcmd; + enum tfailure twhy; + int iremote; +{ + struct srecfailinfo *qinfo; + struct stransfer *qtrans; + + qinfo = (struct srecfailinfo *) xmalloc (sizeof (struct srecfailinfo)); + qinfo->twhy = twhy; + qinfo->fsent = FALSE; + + /* If the protocol does not support multiple channels (cchans <= 1), + then we have essentially already received the entire file. */ + qinfo->freceived = qdaemon->qproto->cchans <= 1; + + qtrans = qtransalc (qcmd); + qtrans->psendfn = fremote_send_fail_send; + qtrans->precfn = fremote_discard; + qtrans->iremote = iremote; + qtrans->pinfo = (pointer) qinfo; + + return fqueue_remote (qdaemon, qtrans); +} + +/* Send a failure string for a send command to the remote system; + this is called when we are ready to reply to the command. */ + +static boolean +fremote_send_fail_send (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct srecfailinfo *qinfo = (struct srecfailinfo *) qtrans->pinfo; + char ab[4]; + boolean fret; + + ab[0] = qtrans->s.bcmd; + ab[1] = 'N'; + + switch (qinfo->twhy) + { + case FAILURE_PERM: + ab[2] = '2'; + break; + case FAILURE_OPEN: + ab[2] = '4'; + break; + case FAILURE_SIZE: + ab[2] = '6'; + break; + case FAILURE_RECEIVED: + /* Remember this file as though we successfully received it; + when the other side acknowledges our rejection, we know that + we no longer have to remember that we received this file. */ + usent_receive_ack (qdaemon, qtrans); + ab[2] = '8'; + break; + default: + ab[2] = '\0'; + break; + } + + ab[3] = '\0'; + + fret = (*qdaemon->qproto->pfsendcmd) (qdaemon, ab, qtrans->ilocal, + qtrans->iremote); + + qinfo->fsent = TRUE; + + /* Wait for the end of file marker if we haven't gotten it yet. */ + if (! qinfo->freceived) + { + if (! fqueue_receive (qdaemon, qtrans)) + fret = FALSE; + } + else + { + xfree (qtrans->pinfo); + utransfree (qtrans); + } + + return fret; +} + +/* Discard data until we reach the end of the file. This is used for + a protocol with multiple channels, since the remote system may + start sending the file before the confirmation is sent. If we + refuse the file, the remote system will get us back in synch by + sending an empty buffer, which is what we look for here. */ + +/*ARGSUSED*/ +static boolean +fremote_discard (qtrans, qdaemon, zdata, cdata) + struct stransfer *qtrans; + struct sdaemon *qdaemon; + const char *zdata; + size_t cdata; +{ + struct srecfailinfo *qinfo = (struct srecfailinfo *) qtrans->pinfo; + + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, + "fremote_discard: Discarding %lu bytes", + (unsigned long) cdata); + + if (cdata != 0) + return TRUE; + + qinfo->freceived = TRUE; + + /* If we have already sent the denial, we are done. */ + if (qinfo->fsent) + { + xfree (qtrans->pinfo); + utransfree (qtrans); + } + + return TRUE; +} + +/* This is called when a file has been completely received. It sends + a response to the remote system. */ + +/*ARGSUSED*/ +static boolean +frec_file_end (qtrans, qdaemon, zdata, cdata) + struct stransfer *qtrans; + struct sdaemon *qdaemon; + const char *zdata; + size_t cdata; +{ + struct srecinfo *qinfo = (struct srecinfo *) qtrans->pinfo; + const char *zerr; + boolean fnever; + + DEBUG_MESSAGE3 (DEBUG_UUCP_PROTO, "frec_file_end: %s to %s (freplied %s)", + qtrans->s.zfrom, qtrans->s.zto, + qinfo->freplied ? "TRUE" : "FALSE"); + + if (qdaemon->qproto->pffile != NULL) + { + boolean fhandled; + + if (! (*qdaemon->qproto->pffile) (qdaemon, qtrans, FALSE, FALSE, + (long) -1, &fhandled)) + { + (void) ffileclose (qtrans->e); + (void) remove (qinfo->ztemp); + urrec_free (qtrans); + return FALSE; + } + if (fhandled) + return TRUE; + } + + qinfo->freceived = TRUE; + + fnever = FALSE; + + if (! ffileclose (qtrans->e)) + { + zerr = strerror (errno); + ulog (LOG_ERROR, "%s: close: %s", qtrans->s.zto, zerr); + } + else if (! fsysdep_move_file (qinfo->ztemp, qinfo->zfile, qinfo->fspool, + FALSE, ! qinfo->fspool, + (qinfo->flocal + ? qtrans->s.zuser + : (const char *) NULL))) + { + zerr = "could not move to final location"; + ulog (LOG_ERROR, "%s: %s", qinfo->zfile, zerr); + fnever = TRUE; + } + else + { + if (! qinfo->fspool) + { + unsigned int imode; + + /* Unless we can change the ownership of the file, the only + choice to make about these bits is whether to set the + execute bit or not. */ + if ((qtrans->s.imode & 0111) != 0) + imode = 0777; + else + imode = 0666; + (void) fsysdep_change_mode (qinfo->zfile, imode); + } + + zerr = NULL; + } + + if (zerr != NULL) + (void) remove (qinfo->ztemp); + + ustats (zerr == NULL, qtrans->s.zuser, qdaemon->qsys->uuconf_zname, + FALSE, qtrans->cbytes, qtrans->isecs, qtrans->imicros, + qdaemon->fmaster); + + if (zerr == NULL) + { + if (qinfo->zmail != NULL && *qinfo->zmail != '\0') + (void) fmail_transfer (TRUE, qtrans->s.zuser, qinfo->zmail, + (const char *) NULL, + qtrans->s.zfrom, qdaemon->qsys->uuconf_zname, + qtrans->s.zto, (const char *) NULL, + (const char *) NULL); + + if (qtrans->s.pseq != NULL) + (void) fsysdep_did_work (qtrans->s.pseq); + + if (! qinfo->flocal) + { + /* Remember that we have received this file, so that if the + connection drops at this point we won't receive it again. + We could check the return value here, but if we return + FALSE we couldn't do anything but drop the connection, + which would hardly be reasonable. Instead we trust that + the administrator will notice and handle any error + messages, which are very unlikely to occur if everything + is set up correctly. */ + (void) fsysdep_remember_reception (qdaemon->qsys, qtrans->s.zto, + qtrans->s.ztemp); + } + } + else + { + /* If the transfer failed, we send mail if it was requested + locally and if it can never succeed. */ + if (qinfo->flocal && fnever) + { + (void) fmail_transfer (FALSE, qtrans->s.zuser, qinfo->zmail, + zerr, qtrans->s.zfrom, + qdaemon->qsys->uuconf_zname, + qtrans->s.zto, (const char *) NULL, + (const char *) NULL); + (void) fsysdep_did_work (qtrans->s.pseq); + } + } + + /* If this is an execution request, we must create the execution + file itself. */ + if (qtrans->s.bcmd == 'E' && zerr == NULL) + { + char *zxqt, *zxqtfile, *ztemp; + FILE *e; + boolean fbad; + + /* We get an execution file name by simply replacing the leading + D in the received file name with an X. This pretty much + always has to work since we can always receive a file name + starting with X, so the system dependent code must be + prepared to see one. */ + zxqt = zbufcpy (qtrans->s.zto); + zxqt[0] = 'X'; + zxqtfile = zsysdep_spool_file_name (qdaemon->qsys, zxqt, + (pointer) NULL); + ubuffree (zxqt); + + if (zxqtfile == NULL) + { + urrec_free (qtrans); + return FALSE; + } + + /* We have to write via a temporary file, because otherwise + uuxqt might pick up the file before we have finished writing + it. */ + e = NULL; + ztemp = zsysdep_receive_temp (qdaemon->qsys, zxqtfile, "D.0"); + if (ztemp != NULL) + e = esysdep_fopen (ztemp, FALSE, FALSE, TRUE); + + if (e == NULL) + { + ubuffree (zxqtfile); + ubuffree (ztemp); + urrec_free (qtrans); + return FALSE; + } + + fprintf (e, "U %s %s\n", qtrans->s.zuser, qdaemon->qsys->uuconf_zname); + fprintf (e, "F %s\n", qtrans->s.zto); + fprintf (e, "I %s\n", qtrans->s.zto); + if (strchr (qtrans->s.zoptions, 'N') != NULL) + fprintf (e, "N\n"); + if (strchr (qtrans->s.zoptions, 'Z') != NULL) + fprintf (e, "Z\n"); + if (strchr (qtrans->s.zoptions, 'R') != NULL) + fprintf (e, "R %s\n", qtrans->s.znotify); + if (strchr (qtrans->s.zoptions, 'e') != NULL) + fprintf (e, "e\n"); + fprintf (e, "C %s\n", qtrans->s.zcmd); + + fbad = FALSE; + if (fclose (e) == EOF) + { + ulog (LOG_ERROR, "fclose: %s", strerror (errno)); + (void) remove (ztemp); + fbad = TRUE; + } + + if (! fbad) + { + if (! fsysdep_move_file (ztemp, zxqtfile, TRUE, FALSE, FALSE, + (const char *) NULL)) + fbad = TRUE; + } + + ubuffree (zxqtfile); + ubuffree (ztemp); + + if (fbad) + { + urrec_free (qtrans); + return FALSE; + } + } + + /* Prepare to send the completion string to the remote system. If + we have not yet replied to the remote send request, we leave the + transfer structure on the remote queue. Otherwise we add it to + the send queue. The psendfn field will already be set. */ + qinfo->fmoved = zerr == NULL; + if (qinfo->freplied) + return fqueue_send (qdaemon, qtrans); + + return TRUE; +} + +/* Send the final confirmation string to the remote system. */ + +static boolean +frec_file_send_confirm (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct srecinfo *qinfo = (struct srecinfo *) qtrans->pinfo; + const char *zsend; + boolean fret; + + if (! qinfo->fmoved) + zsend = "CN5"; + else if (! qdaemon->frequest_hangup) + zsend = "CY"; + else + { +#if DEBUG > 0 + if (qdaemon->fmaster) + ulog (LOG_FATAL, "frec_file_send_confirm: Can't happen"); +#endif + + DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO, + "frec_send_file_confirm: Requesting remote to transfer control"); + zsend = "CYM"; + } + + fret = (*qdaemon->qproto->pfsendcmd) (qdaemon, zsend, + qtrans->ilocal, qtrans->iremote); + + /* Now, if that was a remote command, then when the confirmation + message is acked we no longer have to remember that we received + that file. */ + if (! qinfo->flocal && qinfo->fmoved) + usent_receive_ack (qdaemon, qtrans); + + urrec_free (qtrans); + return fret; +} + +/* Discard a temporary file if it is not useful. A temporary file is + useful if it could be used to restart a receive. This is called if + the connection is lost. It is only called if qtrans->frecfile is + TRUE. */ + +boolean +frec_discard_temp (qdaemon, qtrans) + struct sdaemon *qdaemon; + struct stransfer *qtrans; +{ + struct srecinfo *qinfo = (struct srecinfo *) qtrans->pinfo; + + if ((qdaemon->ifeatures & FEATURE_RESTART) == 0 + || qtrans->s.ztemp == NULL + || qtrans->s.ztemp[0] != 'D' + || strcmp (qtrans->s.ztemp, "D.0") == 0) + (void) remove (qinfo->ztemp); + return TRUE; +} diff --git a/gnu/libexec/uucp/uucico/send.c b/gnu/libexec/uucp/uucico/send.c new file mode 100644 index 0000000000000..8e2c55ef37244 --- /dev/null +++ b/gnu/libexec/uucp/uucico/send.c @@ -0,0 +1,1273 @@ +/* send.c + Routines to send a file. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char send_rcsid[] = "$Id: send.c,v 1.1 1993/08/05 18:27:19 conklin Exp $"; +#endif + +#include <errno.h> + +#include "uudefs.h" +#include "uuconf.h" +#include "system.h" +#include "prot.h" +#include "trans.h" + +/* We keep this information in the pinfo field of the stransfer + structure. */ +struct ssendinfo +{ + /* Local user to send mail to (may be NULL). */ + char *zmail; + /* Full file name. */ + char *zfile; + /* Number of bytes in file. */ + long cbytes; + /* TRUE if this was a local request. */ + boolean flocal; + /* TRUE if this is a spool directory file. */ + boolean fspool; + /* TRUE if the file has been completely sent. */ + boolean fsent; + /* Execution file for sending an unsupported E request. */ + char *zexec; +}; + +/* Local functions. */ + +static void usfree_send P((struct stransfer *qtrans)); +static boolean flocal_send_fail P((struct stransfer *qtrans, + struct scmd *qcmd, + const struct uuconf_system *qsys, + const char *zwhy)); +static boolean flocal_send_request P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean flocal_send_await_reply P((struct stransfer *qtrans, + struct sdaemon *qdaemon, + const char *zdata, size_t cdata)); +static boolean flocal_send_cancelled P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean flocal_send_open_file P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean fremote_rec_fail P((struct sdaemon *qdaemon, + enum tfailure twhy, int iremote)); +static boolean fremote_rec_fail_send P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean fremote_rec_reply P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean fsend_file_end P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean fsend_await_confirm P((struct stransfer *qtrans, + struct sdaemon *qdaemon, + const char *zdata, size_t cdata)); +static boolean fsend_exec_file_init P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static void usadd_exec_line P((char **pz, size_t *pcalc, size_t *pclen, + int bcmd, const char *z1, const char *z2)); +static boolean fsend_exec_file P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); + +/* Free up a send stransfer structure. */ + +static void +usfree_send (qtrans) + struct stransfer *qtrans; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + + if (qinfo != NULL) + { + ubuffree (qinfo->zmail); + ubuffree (qinfo->zfile); + ubuffree (qinfo->zexec); + xfree (qtrans->pinfo); + } + + utransfree (qtrans); +} + +/* Set up a local request to send a file. This may be called before + we have even tried to call the remote system. + + If we are using a traditional protocol, which doesn't support + channel numbers and doesn't permit the file to be sent until an + acknowledgement has been received, the sequence of function calls + looks like this: + + flocal_send_file_init --> fqueue_local + flocal_send_request (sends S request) --> fqueue_receive + flocal_send_await_reply (waits for SY) --> fqueue_send + flocal_send_open_file (opens file, calls pffile) --> fqueue_send + send file + fsend_file_end (calls pffile) --> fqueue_receive + fsend_await_confirm (waits for CY) + + If flocal_send_await_reply gets an SN, it deletes the request. If + the SY reply contains a file position at which to start sending, + flocal_send_await_reply sets qinfo->ipos. + + This gets more complex if the protocol supports channels. In that + case, we want to start sending the file data immediately, to avoid + the round trip delay between flocal_send_request and + flocal_send_await_reply. To do this, flocal_send_request calls + fqueue_send rather than fqueue_receive. The main execution + sequence looks like this: + + flocal_send_file_init --> fqueue_local + flocal_send_request (sends S request) --> fqueue_send + flocal_send_open_file (opens file, calls pffile) --> fqueue_send + send file + fsend_file_end (calls pffile) --> fqueue_receive + sometime: flocal_send_await_reply (waits for SY) + fsend_await_confirm (waits for CY) + + In this case flocal_send_await_reply must be run before + fsend_await_confirm; it may be run anytime after + flocal_send_request. + + If flocal_send_await_reply is called before the entire file has + been sent: if it gets an SN, it calls flocal_send_cancelled to send + an empty data block to inform the remote system that the file + transfer has stopped. If it gets a file position request, it must + adjust the file position accordingly. + + If flocal_send_await_reply is called after the entire file has been + sent: if it gets an SN, it can simply delete the request. It can + ignore any file position request. + + If the request is not deleted, flocal_send_await_reply must arrange + for the next string to be passed to fsend_await_confirm. + Presumably fsend_await_confirm will only be called after the entire + file has been sent. + + Just to make things even more complex, these same routines support + sending execution requests, since that is much like sending a file. + For an execution request, the bcmd character will be E rather than + S. If an execution request is being sent to a system which does + not support them, it must be sent as two S requests instead. The + second one will be the execution file, but no actual file is + created; instead the zexec and znext fields in the ssendinfo + structure are used. So if the bcmd character is E, then if the + zexec field is NULL, the data file is being sent, otherwise the + fake execution file is being sent. */ + +boolean +flocal_send_file_init (qdaemon, qcmd) + struct sdaemon *qdaemon; + struct scmd *qcmd; +{ + const struct uuconf_system *qsys; + boolean fspool; + char *zfile; + long cbytes; + struct ssendinfo *qinfo; + struct stransfer *qtrans; + + qsys = qdaemon->qsys; + + if (qdaemon->fcaller + ? ! qsys->uuconf_fcall_transfer + : ! qsys->uuconf_fcalled_transfer) + { + /* uux or uucp should have already made sure that the transfer + is possible, but it might have changed since then. */ + if (! qsys->uuconf_fcall_transfer + && ! qsys->uuconf_fcalled_transfer) + return flocal_send_fail ((struct stransfer *) NULL, qcmd, qsys, + "not permitted to transfer files"); + + /* We can't do the request now, but it may get done later. */ + return TRUE; + } + + /* The 'C' option means that the file has been copied to the spool + directory. */ + if (strchr (qcmd->zoptions, 'C') == NULL + && ! fspool_file (qcmd->zfrom)) + { + fspool = FALSE; + if (! fin_directory_list (qcmd->zfrom, + qsys->uuconf_pzlocal_send, + qsys->uuconf_zpubdir, TRUE, + TRUE, qcmd->zuser)) + return flocal_send_fail ((struct stransfer *) NULL, qcmd, qsys, + "not permitted to send"); + zfile = zbufcpy (qcmd->zfrom); + } + else + { + fspool = TRUE; + zfile = zsysdep_spool_file_name (qsys, qcmd->ztemp, qcmd->pseq); + if (zfile == NULL) + return FALSE; + } + + /* Make sure we meet any local size restrictions. The connection + may not have been opened at this point, so we can't check remote + size restrictions. */ + cbytes = csysdep_size (zfile); + if (cbytes < 0) + { + ubuffree (zfile); + if (cbytes != -1) + return FALSE; + /* A cbytes value of -1 means that the file does not exist. + This can happen legitimately if it has already been sent from + the spool directory. */ + if (! fspool) + return flocal_send_fail ((struct stransfer *) NULL, qcmd, qsys, + "does not exist"); + (void) fsysdep_did_work (qcmd->pseq); + return TRUE; + } + + if (qdaemon->clocal_size != -1 + && qdaemon->clocal_size < cbytes) + { + ubuffree (zfile); + + if (qdaemon->cmax_ever == -2) + { + long c1, c2; + + c1 = cmax_size_ever (qsys->uuconf_qcall_local_size); + c2 = cmax_size_ever (qsys->uuconf_qcalled_local_size); + if (c1 > c2) + qdaemon->cmax_ever = c1; + else + qdaemon->cmax_ever = c2; + } + + if (qdaemon->cmax_ever != -1 + && qdaemon->cmax_ever < qcmd->cbytes) + return flocal_send_fail ((struct stransfer *) NULL, qcmd, qsys, + "too large to send"); + + return TRUE; + } + + /* We are now prepared to send the command to the remote system. We + queue up a transfer request to send the command when we are + ready. */ + qinfo = (struct ssendinfo *) xmalloc (sizeof (struct ssendinfo)); + if (strchr (qcmd->zoptions, 'm') == NULL) + qinfo->zmail = NULL; + else + qinfo->zmail = zbufcpy (qcmd->zuser); + qinfo->zfile = zfile; + qinfo->cbytes = cbytes; + qinfo->flocal = TRUE; + qinfo->fspool = fspool; + qinfo->fsent = FALSE; + qinfo->zexec = NULL; + + qtrans = qtransalc (qcmd); + qtrans->psendfn = flocal_send_request; + qtrans->pinfo = (pointer) qinfo; + + return fqueue_local (qdaemon, qtrans); +} + +/* Clean up after a failing local send request. If zwhy is not NULL, + this reports an error to the log file and to the user. */ + +static boolean +flocal_send_fail (qtrans, qcmd, qsys, zwhy) + struct stransfer *qtrans; + struct scmd *qcmd; + const struct uuconf_system *qsys; + const char *zwhy; +{ + if (zwhy != NULL) + { + char *zfree; + + if (qcmd->bcmd != 'E') + zfree = NULL; + else + { + zfree = zbufalc (sizeof "Execution of \"\": " + + strlen (qcmd->zcmd) + + strlen (zwhy)); + sprintf (zfree, "Execution of \"%s\": %s", qcmd->zcmd, zwhy); + zwhy = zfree; + } + + ulog (LOG_ERROR, "%s: %s", qcmd->zfrom, zwhy); + (void) fmail_transfer (FALSE, qcmd->zuser, (const char *) NULL, + zwhy, qcmd->zfrom, (const char *) NULL, + qcmd->zto, qsys->uuconf_zname, + zsysdep_save_temp_file (qcmd->pseq)); + + ubuffree (zfree); + } + + (void) fsysdep_did_work (qcmd->pseq); + + if (qtrans != NULL) + usfree_send (qtrans); + + return TRUE; +} + +/* This is called when we are ready to send the request to the remote + system. We form the request and send it over. If the protocol + does not support multiple channels, we start waiting for the + response; otherwise we can start sending the file immediately. */ + +static boolean +flocal_send_request (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + char *zsend; + const char *znotify; + char absize[20]; + boolean fret; + + /* Make sure the file meets any remote size restrictions. */ + if (qdaemon->cmax_receive != -1 + && qdaemon->cmax_receive < qinfo->cbytes) + return flocal_send_fail (qtrans, &qtrans->s, qdaemon->qsys, + "too large for receiver"); + + /* Construct the notify string to send. If we are going to send a + size or an execution command, it must be non-empty. */ + znotify = qtrans->s.znotify; + if (znotify == NULL) + znotify = ""; + if ((qdaemon->ifeatures & FEATURE_SIZES) != 0 + || (qtrans->s.bcmd == 'E' + && (qdaemon->ifeatures & FEATURE_EXEC) != 0)) + { + if (*znotify == '\0') + znotify = "\"\""; + } + else + { + /* We don't need a notify string. Some crufty UUCP code can't + handle a pair of double quotes. */ + if (strcmp (znotify, "\"\"") == 0) + znotify = ""; + } + + /* Construct the size string to send. */ + if ((qdaemon->ifeatures & FEATURE_SIZES) == 0 + && (qtrans->s.bcmd != 'E' + || (qdaemon->ifeatures & FEATURE_EXEC) == 0)) + absize[0] = '\0'; + else if ((qdaemon->ifeatures & FEATURE_V103) == 0) + sprintf (absize, "0x%lx", (unsigned long) qinfo->cbytes); + else + sprintf (absize, "%ld", qinfo->cbytes); + + zsend = zbufalc (strlen (qtrans->s.zfrom) + strlen (qtrans->s.zto) + + strlen (qtrans->s.zuser) + strlen (qtrans->s.zoptions) + + strlen (qtrans->s.ztemp) + strlen (znotify) + + strlen (absize) + + (qtrans->s.zcmd != NULL ? strlen (qtrans->s.zcmd) : 0) + + 50); + + /* If this an execution request and the other side supports + execution requests, we send an E command. Otherwise we send an S + command. The case of an execution request when we are sending + the fake execution file is handled just like an S request at this + point. */ + if (qtrans->s.bcmd == 'E' + && (qdaemon->ifeatures & FEATURE_EXEC) != 0) + { + /* Send the string + E zfrom zto zuser zoptions ztemp imode znotify size zcmd + to the remote system. We put a '-' in front of the (possibly + empty) options and a '0' in front of the mode. */ + sprintf (zsend, "E %s %s %s -%s %s 0%o %s %s %s", qtrans->s.zfrom, + qtrans->s.zto, qtrans->s.zuser, qtrans->s.zoptions, + qtrans->s.ztemp, qtrans->s.imode, znotify, absize, + qtrans->s.zcmd); + } + else + { + const char *zoptions, *zdummy; + + /* Send the string + S zfrom zto zuser zoptions ztemp imode znotify + to the remote system. We put a '-' in front of the (possibly + empty) options and a '0' in front of the mode. If size + negotiation is supported, we also send the size; in this case + if znotify is empty we must send it as "". If this is really + an execution request, we have to simplify the options string + to remove the various execution options which may confuse the + remote system. SVR4 expects a string "dummy" between the + notify string and the size; I don't know why. */ + if (qtrans->s.bcmd != 'E') + zoptions = qtrans->s.zoptions; + else if (strchr (qtrans->s.zoptions, 'C') != NULL) + { + /* This should set zoptions to "C", but at least one UUCP + program gets confused by it. That means that it will + fail in certain cases, but I suppose we might as well + kowtow to compatibility. This shouldn't matter to any + other program, I hope. */ + zoptions = ""; + } + else + zoptions = "c"; + + if ((qdaemon->ifeatures & FEATURE_SVR4) != 0) + zdummy = " dummy "; + else + zdummy = " "; + + sprintf (zsend, "S %s %s %s -%s %s 0%o %s%s%s", qtrans->s.zfrom, + qtrans->s.zto, qtrans->s.zuser, zoptions, + qtrans->s.ztemp, qtrans->s.imode, znotify, zdummy, + absize); + } + + fret = (*qdaemon->qproto->pfsendcmd) (qdaemon, zsend, qtrans->ilocal, + qtrans->iremote); + ubuffree (zsend); + if (! fret) + { + usfree_send (qtrans); + return FALSE; + } + + /* If we are using a protocol which can make multiple channels, then + we can open and send the file whenever we are ready. This is + because we will be able to distinguish the response by the + channel it is directed to. This assumes that every protocol + which supports multiple channels also supports sending the file + position in mid-stream, since otherwise we would not be able to + restart files. */ + qtrans->fcmd = TRUE; + qtrans->psendfn = flocal_send_open_file; + qtrans->precfn = flocal_send_await_reply; + + if (qdaemon->qproto->cchans > 1) + return fqueue_send (qdaemon, qtrans); + else + return fqueue_receive (qdaemon, qtrans); +} + +/* This is called when a reply is received for the send request. As + described at length above, if the protocol supports multiple + channels we may be in the middle of sending the file, or we may + even finished sending the file. */ + +static boolean +flocal_send_await_reply (qtrans, qdaemon, zdata, cdata) + struct stransfer *qtrans; + struct sdaemon *qdaemon; + const char *zdata; + size_t cdata; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + char bcmd; + + if (qtrans->s.bcmd == 'E' + && (qdaemon->ifeatures & FEATURE_EXEC) != 0) + bcmd = 'E'; + else + bcmd = 'S'; + if (zdata[0] != bcmd + || (zdata[1] != 'Y' && zdata[1] != 'N')) + { + ulog (LOG_ERROR, "%s: Bad response to %c request: \"%s\"", + qtrans->s.zfrom, bcmd, zdata); + usfree_send (qtrans); + return FALSE; + } + + if (zdata[1] == 'N') + { + const char *zerr; + boolean fnever; + + fnever = TRUE; + if (zdata[2] == '2') + zerr = "permission denied by remote"; + else if (zdata[2] == '4') + { + zerr = "remote cannot create work files"; + fnever = FALSE; + } + else if (zdata[2] == '6') + { + zerr = "too large for remote now"; + fnever = FALSE; + } + else if (zdata[2] == '7') + { + /* The file is too large to ever send. */ + zerr = "too large for remote"; + } + else if (zdata[2] == '8') + { + /* The file was already received by the remote system. This + is not an error, it just means that the ack from the + remote was lost in the previous conversation, and there + is no need to resend the file. */ + zerr = NULL; + } + else + zerr = "unknown reason"; + + if (! fnever) + { + if (qtrans->s.bcmd == 'E') + ulog (LOG_ERROR, "Execution of \"%s\": %s", qtrans->s.zcmd, + zerr); + else + ulog (LOG_ERROR, "%s: %s", qtrans->s.zfrom, zerr); + } + else + { + if (! flocal_send_fail ((struct stransfer *) NULL, &qtrans->s, + qdaemon->qsys, zerr)) + return FALSE; + } + + /* If the protocol does not support multiple channels, we can + simply remove the transaction. Otherwise we must make sure + the remote side knows that we have finished sending the file + data. If we have already sent the entire file, there will be + no confusion. */ + if (qdaemon->qproto->cchans == 1 || qinfo->fsent) + { + usfree_send (qtrans); + return TRUE; + } + else + { + qtrans->psendfn = flocal_send_cancelled; + qtrans->precfn = NULL; + qtrans->fsendfile = FALSE; + return fqueue_send (qdaemon, qtrans); + } + } + + /* A number following the SY or EY is the file position to start + sending from. If we are already sending the file, we must set + the position accordingly. */ + if (zdata[2] != '\0') + { + long cskip; + + cskip = strtol ((char *) (zdata + 2), (char **) NULL, 0); + if (cskip > 0 && qtrans->ipos < cskip) + { + if (qtrans->fsendfile && ! qinfo->fsent) + { + if (! ffileseek (qtrans->e, cskip)) + { + ulog (LOG_ERROR, "seek: %s", strerror (errno)); + usfree_send (qtrans); + return FALSE; + } + } + qtrans->ipos = cskip; + } + } + + /* Now queue up to send the file or to wait for the confirmation. + We already set psendfn at the end of flocal_send_request. If the + protocol supports multiple channels, we have already called + fqueue_send; calling it again would move the request in the + queue, which would make the log file a bit confusing. */ + qtrans->precfn = fsend_await_confirm; + if (qinfo->fsent) + return fqueue_receive (qdaemon, qtrans); + else if (qdaemon->qproto->cchans <= 1) + return fqueue_send (qdaemon, qtrans); + else + return TRUE; +} + +/* Open the file, if any, and prepare to send it. */ + +static boolean +flocal_send_open_file (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + const char *zuser; + + /* If this is not a fake execution file, open it. */ + if (qinfo->zexec == NULL) + { + /* If there is an ! in the user name, this is a remote request + queued up by fremote_xcmd_init. */ + zuser = qtrans->s.zuser; + if (strchr (zuser, '!') != NULL) + zuser = NULL; + + qtrans->e = esysdep_open_send (qdaemon->qsys, qinfo->zfile, + ! qinfo->fspool, zuser); + if (! ffileisopen (qtrans->e)) + { + (void) fmail_transfer (FALSE, qtrans->s.zuser, + (const char *) NULL, + "cannot open file", + qtrans->s.zfrom, (const char *) NULL, + qtrans->s.zto, + qdaemon->qsys->uuconf_zname, + zsysdep_save_temp_file (qtrans->s.pseq)); + (void) fsysdep_did_work (qtrans->s.pseq); + usfree_send (qtrans); + + /* Unfortunately, there is no way to cancel a file send + after we've already put it in progress. So we have to + return FALSE to drop the connection. */ + return FALSE; + } + } + + /* If flocal_send_await_reply has received a reply with a file + position, it will have set qtrans->ipos to the position at which + to start. */ + if (qtrans->ipos > 0) + { + if (qinfo->zexec != NULL) + { + if (qtrans->ipos > qtrans->cbytes) + qtrans->ipos = qtrans->cbytes; + } + else + { + if (! ffileseek (qtrans->e, qtrans->ipos)) + { + ulog (LOG_ERROR, "seek: %s", strerror (errno)); + usfree_send (qtrans); + return FALSE; + } + } + } + + /* We don't bother to log sending the execution file. */ + if (qinfo->zexec == NULL) + { + const char *zsend; + char *zalc; + + if (qtrans->s.bcmd != 'E') + { + zsend = qtrans->s.zfrom; + zalc = NULL; + } + else + { + zalc = zbufalc (strlen (qtrans->s.zcmd) + sizeof " ()" + + strlen (qtrans->s.zfrom)); + sprintf (zalc, "%s (%s)", qtrans->s.zcmd, qtrans->s.zfrom); + zsend = zalc; + } + qtrans->zlog = zbufalc (sizeof "Sending " + strlen (zsend)); + sprintf (qtrans->zlog, "Sending %s", zsend); + ubuffree (zalc); + } + + if (qdaemon->qproto->pffile != NULL) + { + boolean fhandled; + + if (! (*qdaemon->qproto->pffile) (qdaemon, qtrans, TRUE, TRUE, + qinfo->cbytes, &fhandled)) + { + usfree_send (qtrans); + return FALSE; + } + + if (fhandled) + return TRUE; + } + + if (qinfo->zexec != NULL) + qtrans->psendfn = fsend_exec_file; + else + { + qtrans->fsendfile = TRUE; + qtrans->psendfn = fsend_file_end; + } + + return fqueue_send (qdaemon, qtrans); +} + +/* Cancel a file send by sending an empty buffer. This is only called + for a protocol which supports multiple channels. It is needed + so that both systems agree as to when a channel is no longer + needed. */ + +static boolean +flocal_send_cancelled (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + char *zdata; + size_t cdata; + boolean fret; + + zdata = (*qdaemon->qproto->pzgetspace) (qdaemon, &cdata); + if (zdata == NULL) + { + usfree_send (qtrans); + return FALSE; + } + + fret = (*qdaemon->qproto->pfsenddata) (qdaemon, zdata, (size_t) 0, + qtrans->ilocal, qtrans->iremote, + qtrans->ipos); + usfree_send (qtrans); + return fret; +} + +/* A remote request to receive a file (meaning that we have to send a + file). The sequence of functions calls is as follows: + + fremote_rec_file_init (open file) --> fqueue_remote + fremote_rec_reply (send RY, call pffile) --> fqueue_send + send file + fsend_file_end (calls pffile) --> fqueue_receive + fsend_await_confirm (waits for CY) + */ + +boolean +fremote_rec_file_init (qdaemon, qcmd, iremote) + struct sdaemon *qdaemon; + struct scmd *qcmd; + int iremote; +{ + const struct uuconf_system *qsys; + char *zfile; + long cbytes; + unsigned int imode; + openfile_t e; + struct ssendinfo *qinfo; + struct stransfer *qtrans; + + qsys = qdaemon->qsys; + + if (! qsys->uuconf_fsend_request) + { + ulog (LOG_ERROR, "%s: not permitted to send files to remote", + qcmd->zfrom); + return fremote_rec_fail (qdaemon, FAILURE_PERM, iremote); + } + + if (fspool_file (qcmd->zfrom)) + { + ulog (LOG_ERROR, "%s: not permitted to send", qcmd->zfrom); + return fremote_rec_fail (qdaemon, FAILURE_PERM, iremote); + } + + zfile = zsysdep_local_file (qcmd->zfrom, qsys->uuconf_zpubdir); + if (zfile != NULL) + { + char *zbased; + + zbased = zsysdep_add_base (zfile, qcmd->zto); + ubuffree (zfile); + zfile = zbased; + } + if (zfile == NULL) + return fremote_rec_fail (qdaemon, FAILURE_PERM, iremote); + + if (! fin_directory_list (zfile, qsys->uuconf_pzremote_send, + qsys->uuconf_zpubdir, TRUE, TRUE, + (const char *) NULL)) + { + ulog (LOG_ERROR, "%s: not permitted to send", zfile); + ubuffree (zfile); + return fremote_rec_fail (qdaemon, FAILURE_PERM, iremote); + } + + /* If the file is larger than the amount of space the other side + reported, we can't send it. Should we adjust this check based on + the restart position? */ + cbytes = csysdep_size (zfile); + if (cbytes != -1 + && ((qcmd->cbytes != -1 && qcmd->cbytes < cbytes) + || (qdaemon->cremote_size != -1 + && qdaemon->cremote_size < cbytes) + || (qdaemon->cmax_receive != -1 + && qdaemon->cmax_receive < cbytes))) + { + ulog (LOG_ERROR, "%s: too large to send", zfile); + ubuffree (zfile); + return fremote_rec_fail (qdaemon, FAILURE_SIZE, iremote); + } + + imode = ixsysdep_file_mode (zfile); + + e = esysdep_open_send (qsys, zfile, TRUE, (const char *) NULL); + if (! ffileisopen (e)) + { + ubuffree (zfile); + return fremote_rec_fail (qdaemon, FAILURE_OPEN, iremote); + } + + /* If the remote requested that the file send start from a + particular position, arrange to do so. */ + if (qcmd->ipos > 0) + { + if (! ffileseek (e, qcmd->ipos)) + { + ulog (LOG_ERROR, "seek: %s", strerror (errno)); + ubuffree (zfile); + return FALSE; + } + } + + qinfo = (struct ssendinfo *) xmalloc (sizeof (struct ssendinfo)); + qinfo->zmail = NULL; + qinfo->zfile = zfile; + qinfo->cbytes = cbytes; + qinfo->flocal = FALSE; + qinfo->fspool = FALSE; + qinfo->fsent = FALSE; + qinfo->zexec = NULL; + + qtrans = qtransalc (qcmd); + qtrans->psendfn = fremote_rec_reply; + qtrans->iremote = iremote; + qtrans->pinfo = (pointer) qinfo; + qtrans->e = e; + qtrans->ipos = qcmd->ipos; + qtrans->s.imode = imode; + + return fqueue_remote (qdaemon, qtrans); +} + +/* Reply to a receive request from the remote system, and prepare to + start sending the file. */ + +static boolean +fremote_rec_reply (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + char absend[50]; + + sprintf (absend, "RY 0%o 0x%lx", qtrans->s.imode, + (unsigned long) qinfo->cbytes); + if (! (*qdaemon->qproto->pfsendcmd) (qdaemon, absend, qtrans->ilocal, + qtrans->iremote)) + { + (void) ffileclose (qtrans->e); + usfree_send (qtrans); + return FALSE; + } + + qtrans->zlog = zbufalc (sizeof "Sending " + strlen (qtrans->s.zfrom)); + sprintf (qtrans->zlog, "Sending %s", qtrans->s.zfrom); + + if (qdaemon->qproto->pffile != NULL) + { + boolean fhandled; + + if (! (*qdaemon->qproto->pffile) (qdaemon, qtrans, TRUE, TRUE, + qinfo->cbytes, &fhandled)) + { + usfree_send (qtrans); + return FALSE; + } + + if (fhandled) + return TRUE; + } + + qtrans->fsendfile = TRUE; + qtrans->psendfn = fsend_file_end; + qtrans->precfn = fsend_await_confirm; + + return fqueue_send (qdaemon, qtrans); +} + +/* If we can't send a file as requested by the remote system, queue up + a failure reply which will be sent when possible. */ + +static boolean +fremote_rec_fail (qdaemon, twhy, iremote) + struct sdaemon *qdaemon; + enum tfailure twhy; + int iremote; +{ + enum tfailure *ptinfo; + struct stransfer *qtrans; + + ptinfo = (enum tfailure *) xmalloc (sizeof (enum tfailure)); + *ptinfo = twhy; + + qtrans = qtransalc ((struct scmd *) NULL); + qtrans->psendfn = fremote_rec_fail_send; + qtrans->iremote = iremote; + qtrans->pinfo = (pointer) ptinfo; + + return fqueue_remote (qdaemon, qtrans); +} + +/* Send a failure string for a receive command to the remote system; + this is called when we are ready to reply to the command. */ + +static boolean +fremote_rec_fail_send (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + enum tfailure *ptinfo = (enum tfailure *) qtrans->pinfo; + const char *z; + boolean fret; + + switch (*ptinfo) + { + case FAILURE_PERM: + case FAILURE_OPEN: + z = "RN2"; + break; + case FAILURE_SIZE: + z = "RN6"; + break; + default: + z = "RN"; + break; + } + + fret = (*qdaemon->qproto->pfsendcmd) (qdaemon, z, qtrans->ilocal, + qtrans->iremote); + xfree (qtrans->pinfo); + utransfree (qtrans); + return fret; +} + +/* This is called when the main loop has finished sending a file. It + prepares to wait for a response from the remote system. Note that + if this is a local request and the protocol supports multiple + channels, we may not even have received a confirmation of the send + request. */ + +static boolean +fsend_file_end (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + + if (qdaemon->qproto->pffile != NULL) + { + boolean fhandled; + + if (! (*qdaemon->qproto->pffile) (qdaemon, qtrans, FALSE, TRUE, + (long) -1, &fhandled)) + { + usfree_send (qtrans); + return FALSE; + } + + if (fhandled) + return TRUE; + } + + qinfo->fsent = TRUE; + + /* qtrans->precfn should have been set by a previous function. */ + qtrans->fcmd = TRUE; + return fqueue_receive (qdaemon, qtrans); +} + +/* Handle the confirmation string received after sending a file. */ + +/*ARGSUSED*/ +static boolean +fsend_await_confirm (qtrans, qdaemon, zdata, cdata) + struct stransfer *qtrans; + struct sdaemon *qdaemon; + const char *zdata; + size_t cdata; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + boolean fnever; + const char *zerr; + + if (qinfo->zexec == NULL) + (void) ffileclose (qtrans->e); + + fnever = FALSE; + if (zdata[0] != 'C' + || (zdata[1] != 'Y' && zdata[1] != 'N')) + { + zerr = "bad confirmation from remote"; + ulog (LOG_ERROR, "%s: %s \"%s\"", qtrans->s.zfrom, zerr, zdata); + } + else if (zdata[1] == 'N') + { + fnever = TRUE; + if (zdata[2] == '5') + { + zerr = "file could not be stored in final location"; + ulog (LOG_ERROR, "%s: %s", qtrans->s.zfrom, zerr); + } + else + { + zerr = "file send failed for unknown reason"; + ulog (LOG_ERROR, "%s: %s \"%s\"", qtrans->s.zfrom, zerr, zdata); + } + } + else + { + zerr = NULL; + + /* If we receive CYM, it means that the other side wants us to + hang up so that they can send us something. The + fhangup_requested field is checked in the main loop. */ + if (zdata[2] == 'M' && qdaemon->fmaster) + { + DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO, + "fsend_await_confirm: Remote has requested transfer of control"); + qdaemon->fhangup_requested = TRUE; + } + } + + ustats (zerr == NULL, qtrans->s.zuser, qdaemon->qsys->uuconf_zname, + TRUE, qtrans->cbytes, qtrans->isecs, qtrans->imicros, + qdaemon->fmaster); + + if (zerr == NULL) + { + /* If this is an execution request, and the remote system + doesn't support execution requests, we have to set up the + fake execution file and loop around again. */ + if (qtrans->s.bcmd == 'E' + && (qdaemon->ifeatures & FEATURE_EXEC) == 0 + && qinfo->zexec == NULL) + return fsend_exec_file_init (qtrans, qdaemon); + + /* Send mail about the transfer if requested. */ + if (qinfo->zmail != NULL && *qinfo->zmail != '\0') + (void) fmail_transfer (TRUE, qtrans->s.zuser, qinfo->zmail, + (const char *) NULL, + qtrans->s.zfrom, (const char *) NULL, + qtrans->s.zto, qdaemon->qsys->uuconf_zname, + (const char *) NULL); + + if (qtrans->s.pseq != NULL) + (void) fsysdep_did_work (qtrans->s.pseq); + } + else + { + /* If the file send failed, we only try to save the file and + send mail if it was requested locally and it will never + succeed. We send mail to qinfo->zmail if set, otherwise to + qtrans->s.zuser. I hope this is reasonable. */ + if (fnever && qinfo->flocal) + { + (void) fmail_transfer (FALSE, qtrans->s.zuser, qinfo->zmail, + zerr, qtrans->s.zfrom, (const char *) NULL, + qtrans->s.zto, qdaemon->qsys->uuconf_zname, + zsysdep_save_temp_file (qtrans->s.pseq)); + (void) fsysdep_did_work (qtrans->s.pseq); + } + } + + usfree_send (qtrans); + + return TRUE; +} + +/* Prepare to send an execution file to a system which does not + support execution requests. We build the execution file in memory, + and then call flocal_send_request as though we were sending a real + file. Instead of sending a file, the code in flocal_send_open_file + will arrange to call fsend_exec_file which will send data out of + the buffer we have created. */ + +static boolean +fsend_exec_file_init (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + char *zxqtfile; + char abtname[CFILE_NAME_LEN]; + char abxname[CFILE_NAME_LEN]; + char *z; + size_t calc, clen; + + z = NULL; + calc = 0; + clen = 0; + + usadd_exec_line (&z, &calc, &clen, 'U', qtrans->s.zuser, + qdaemon->zlocalname); + usadd_exec_line (&z, &calc, &clen, 'F', qtrans->s.zto, ""); + usadd_exec_line (&z, &calc, &clen, 'I', qtrans->s.zto, ""); + if (strchr (qtrans->s.zoptions, 'N') != NULL) + usadd_exec_line (&z, &calc, &clen, 'N', "", ""); + if (strchr (qtrans->s.zoptions, 'Z') != NULL) + usadd_exec_line (&z, &calc, &clen, 'Z', "", ""); + if (strchr (qtrans->s.zoptions, 'R') != NULL) + usadd_exec_line (&z, &calc, &clen, 'R', qtrans->s.znotify, ""); + if (strchr (qtrans->s.zoptions, 'e') != NULL) + usadd_exec_line (&z, &calc, &clen, 'e', "", ""); + usadd_exec_line (&z, &calc, &clen, 'C', qtrans->s.zcmd, ""); + + qinfo->zexec = z; + qinfo->cbytes = clen; + + zxqtfile = zsysdep_data_file_name (qdaemon->qsys, qdaemon->zlocalname, + BDEFAULT_UUX_GRADE, TRUE, abtname, + (char *) NULL, abxname); + if (zxqtfile == NULL) + { + usfree_send (qtrans); + return FALSE; + } + ubuffree (zxqtfile); + + ubuffree ((char *) qtrans->s.zfrom); + qtrans->s.zfrom = zbufcpy (abtname); + ubuffree ((char *) qtrans->s.zto); + qtrans->s.zto = zbufcpy (abxname); + ubuffree ((char *) qtrans->s.zoptions); + qtrans->s.zoptions = zbufcpy ("C"); + ubuffree ((char *) qtrans->s.ztemp); + qtrans->s.ztemp = zbufcpy (abtname); + + qtrans->psendfn = flocal_send_request; + qtrans->precfn = NULL; + qtrans->ipos = 0; + qtrans->cbytes = 0; + qtrans->isecs = 0; + qtrans->imicros = 0; + qinfo->fsent = FALSE; + + return fqueue_send (qdaemon, qtrans); +} + +/* Add a line to the fake execution file. */ + +static void +usadd_exec_line (pz, pcalc, pclen, bcmd, z1, z2) + char **pz; + size_t *pcalc; + size_t *pclen; + int bcmd; + const char *z1; + const char *z2; +{ + size_t c1, c2; + char *znew; + + c1 = strlen (z1); + c2 = strlen (z2); + + if (*pclen + c1 + c2 + 4 >= *pcalc) + { + *pcalc += c1 + c2 + 100; + znew = zbufalc (*pcalc); + if (*pclen > 0) + { + memcpy (znew, *pz, *pclen); + ubuffree (*pz); + } + *pz = znew; + } + + znew = *pz + *pclen; + *znew++ = bcmd; + if (*z1 != '\0') + { + *znew++ = ' '; + memcpy (znew, z1, c1); + znew += c1; + if (*z2 != '\0') + { + *znew++ = ' '; + memcpy (znew, z2, c2); + znew += c2; + } + } + + /* In some bizarre non-Unix case we might have to worry about the + newline here. We don't know how a newline is normally written + out to a file, but whatever is written to a file is what we will + normally transfer. If that is not simply \n then this fake + execution file will not look like other execution files. */ + *znew++ = '\n'; + + *pclen = znew - *pz; +} + +/* This routine is called to send the contents of the fake execution + file. Normally file data is sent by the floop routine in trans.c, + but since we don't have an actual file we must do it here. This + routine sends the complete buffer, followed by a zero length + packet, and then calls fsend_file_end. */ + +static boolean +fsend_exec_file (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + struct ssendinfo *qinfo = (struct ssendinfo *) qtrans->pinfo; + char *zdata; + size_t cdata; + size_t csend; + + zdata = (*qdaemon->qproto->pzgetspace) (qdaemon, &cdata); + if (zdata == NULL) + { + usfree_send (qtrans); + return FALSE; + } + + csend = qinfo->cbytes - qtrans->ipos; + if (csend > cdata) + csend = cdata; + + memcpy (zdata, qinfo->zexec + qtrans->ipos, csend); + + if (! (*qdaemon->qproto->pfsenddata) (qdaemon, zdata, csend, + qtrans->ilocal, qtrans->iremote, + qtrans->ipos)) + { + usfree_send (qtrans); + return FALSE; + } + + qtrans->cbytes += csend; + qtrans->ipos += csend; + + if (csend == 0) + return fsend_file_end (qtrans, qdaemon); + + /* Leave the job on the send queue. */ + + return TRUE; +} diff --git a/gnu/libexec/uucp/uucico/time.c b/gnu/libexec/uucp/uucico/time.c new file mode 100644 index 0000000000000..4305e990e2453 --- /dev/null +++ b/gnu/libexec/uucp/uucico/time.c @@ -0,0 +1,130 @@ +/* time.c + Routines to deal with UUCP time spans. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char time_rcsid[] = "$Id: time.c,v 1.1 1993/08/05 18:27:20 conklin Exp $"; +#endif + +#include <ctype.h> + +#if HAVE_TIME_H +#include <time.h> +#endif + +#include "uudefs.h" +#include "uuconf.h" + +/* External functions. */ +#ifndef time +extern time_t time (); +#endif +#ifndef localtime +extern struct tm *localtime (); +#endif + +/* See if the current time matches a time span. If it does, return + TRUE, set *pival to the value for the matching span, and set + *pcretry to the retry for the matching span. Otherwise return + FALSE. */ + +boolean +ftimespan_match (qspan, pival, pcretry) + const struct uuconf_timespan *qspan; + long *pival; + int *pcretry; +{ + time_t inow; + struct tm *qtm; + int itm; + const struct uuconf_timespan *q; + + if (qspan == NULL) + return FALSE; + + time (&inow); + qtm = localtime (&inow); + + /* Get the number of minutes since Sunday for the time. */ + itm = qtm->tm_wday * 24 * 60 + qtm->tm_hour * 60 + qtm->tm_min; + + for (q = qspan; q != NULL; q = q->uuconf_qnext) + { + if (q->uuconf_istart <= itm && itm <= q->uuconf_iend) + { + if (pival != NULL) + *pival = q->uuconf_ival; + if (pcretry != NULL) + *pcretry = q->uuconf_cretry; + return TRUE; + } + } + + return FALSE; +} + +/* Determine the maximum size that may ever be transferred, according + to a timesize span. This returns -1 if there is no limit. */ + +long +cmax_size_ever (qtimesize) + const struct uuconf_timespan *qtimesize; +{ + long imax; + const struct uuconf_timespan *q; + + if (qtimesize == NULL) + return -1; + + /* Look through the list of spans. If there is any gap larger than + 1 hour, we assume there are no restrictions. Otherwise we keep + track of the largest value we see. I picked 1 hour arbitrarily, + on the theory that a 1 hour span to transfer large files might + actually occur, and is probably not an accident. */ + if (qtimesize->uuconf_istart >= 60) + return -1; + + imax = -1; + + for (q = qtimesize; q != NULL; q = q->uuconf_qnext) + { + if (q->uuconf_qnext == NULL) + { + if (q->uuconf_iend <= 6 * 24 * 60 + 23 * 60) + return -1; + } + else + { + if (q->uuconf_iend + 60 <= q->uuconf_qnext->uuconf_istart) + return -1; + } + + if (imax < q->uuconf_ival) + imax = q->uuconf_ival; + } + + return imax; +} diff --git a/gnu/libexec/uucp/uucico/trans.c b/gnu/libexec/uucp/uucico/trans.c new file mode 100644 index 0000000000000..2bb4d228195ca --- /dev/null +++ b/gnu/libexec/uucp/uucico/trans.c @@ -0,0 +1,1439 @@ +/* trans.c + Routines to handle file transfers. + + Copyright (C) 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char trans_rcsid[] = "$Id: trans.c,v 1.1 1993/08/05 18:27:21 conklin Exp $"; +#endif + +#include <errno.h> + +#include "uudefs.h" +#include "uuconf.h" +#include "prot.h" +#include "system.h" +#include "trans.h" + +/* Local functions. */ + +static void utqueue P((struct stransfer **, struct stransfer *, + boolean fhead)); +static void utdequeue P((struct stransfer *)); +static void utchanalc P((struct sdaemon *qdaemon, struct stransfer *qtrans)); +__inline__ static struct stransfer *qtchan P((int ichan)); +__inline__ static void utchanfree P((struct stransfer *qtrans)); +static boolean ftcharge P((struct sdaemon *qdaemon, + struct stransfer *qtrans, + boolean fsend, boolean fforce)); +static boolean fcheck_queue P((struct sdaemon *qdaemon)); +static boolean ftadd_cmd P((struct sdaemon *qdaemon, const char *z, + size_t cdata, int iremote, boolean flast)); +static boolean fremote_hangup_reply P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean flocal_poll_file P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); + +/* Queue of transfer structures that are ready to start which have + been requested by the local system. These are only permitted to + start when the local system is the master. */ +static struct stransfer *qTlocal; + +/* Queue of transfer structures that are ready to start which have + been requested by the remote system. These are responses to + commands received from the remote system, and should be started as + soon as possible. */ +static struct stransfer *qTremote; + +/* Queue of transfer structures that have been started and want to + send information. This should be static, but the 'a' protocol + looks at it, at least for now. */ +struct stransfer *qTsend; + +/* Queue of transfer structures that have been started and are waiting + to receive information. */ +static struct stransfer *qTreceive; + +/* Queue of free transfer structures. */ +static struct stransfer *qTavail; + +/* Array of transfer structures indexed by local channel number. This + is maintained for local jobs. */ +static struct stransfer *aqTchan[IMAX_CHAN + 1]; + +/* Number of local channel numbers currently allocated. */ +static int cTchans; + +/* Next channel number to allocate. */ +static int iTchan; + +/* Array of transfer structures indexed by remote channel number. + This is maintained for remote jobs. */ +static struct stransfer *aqTremote[IMAX_CHAN + 1]; + +/* A structure used to charge time to file transfers. */ +struct scharge +{ + /* The transfer we are currently charging. */ + struct stransfer *qtrans; + /* The time at the last update. */ + long isecs; + long imicros; +}; + +/* We are always charging one send and one receive. */ +static struct scharge sTsend; +static struct scharge sTreceive; + +/* The minimum amount of time, in seconds, to wait between times we + check the spool directory, if we are busy transferring data. If we + have nothing to do, we will check the spool directory regardless of + how long ago the last check was. This should probably be + configurable. */ +#define CCHECKWAIT (600) + +/* The time we last checked the spool directory for work. This is set + from the return value of ixsysdep_process_time, not ixsysdep_time, + for convenience in the routines which use it. */ +static long iTchecktime; + +/* The size of the command we have read so far in ftadd_cmd. */ +static size_t cTcmdlen; + +/* The structure we use when waiting for an acknowledgement of a + confirmed received file in fsent_receive_ack, and a list of those + structures. */ + +struct sreceive_ack +{ + struct sreceive_ack *qnext; + char *zto; + char *ztemp; + boolean fmarked; +}; + +static struct sreceive_ack *qTreceive_ack; + +/* Queue up a transfer structure before *pq. This puts it at the head + or the tail of the list headed by *pq. */ + +static void +utqueue (pq, q, fhead) + struct stransfer **pq; + struct stransfer *q; + boolean fhead; +{ + if (*pq == NULL) + { + *pq = q; + q->qprev = q->qnext = q; + } + else + { + q->qnext = *pq; + q->qprev = (*pq)->qprev; + q->qprev->qnext = q; + q->qnext->qprev = q; + if (fhead) + *pq = q; + } + q->pqqueue = pq; +} + +/* Dequeue a transfer structure. */ + +static void +utdequeue (q) + struct stransfer *q; +{ + if (q->pqqueue != NULL) + { + if (*(q->pqqueue) == q) + { + if (q->qnext == q) + *(q->pqqueue) = NULL; + else + *(q->pqqueue) = q->qnext; + } + q->pqqueue = NULL; + } + if (q->qprev != NULL) + q->qprev->qnext = q->qnext; + if (q->qnext != NULL) + q->qnext->qprev = q->qprev; + q->qprev = NULL; + q->qnext = NULL; +} + +/* Queue up a transfer structure requested by the local system. */ + +/*ARGSIGNORED*/ +boolean +fqueue_local (qdaemon, qtrans) + struct sdaemon *qdaemon; + struct stransfer *qtrans; +{ + utdequeue (qtrans); + utqueue (&qTlocal, qtrans, FALSE); + return TRUE; +} + +/* Queue up a transfer structure requested by the remote system. The + stransfer structure should have the iremote field set. We need to + record it, so that any subsequent data associated with this + channel can be routed to the right place. */ + +boolean +fqueue_remote (qdaemon, qtrans) + struct sdaemon *qdaemon; + struct stransfer *qtrans; +{ + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, "fqueue_remote: Channel %d", + qtrans->iremote); + if (qtrans->iremote > 0) + aqTremote[qtrans->iremote] = qtrans; + utdequeue (qtrans); + utqueue (&qTremote, qtrans, FALSE); + + /* We just received data for this transfer, so start charging. */ + return ftcharge (qdaemon, qtrans, FALSE, FALSE); +} + +/* Queue up a transfer with something to send. */ + +boolean +fqueue_send (qdaemon, qtrans) + struct sdaemon *qdaemon; + struct stransfer *qtrans; +{ +#if DEBUG > 0 + if (qtrans->psendfn == NULL) + ulog (LOG_FATAL, "fqueue_send: Bad call"); +#endif + utdequeue (qtrans); + utqueue (&qTsend, qtrans, FALSE); + + /* Since we're now going to wait to send data, don't charge this + transfer for receive time. */ + if (qtrans == sTreceive.qtrans) + return ftcharge (qdaemon, (struct stransfer *) NULL, FALSE, FALSE); + return TRUE; +} + +/* Queue up a transfer with something to receive. */ + +boolean +fqueue_receive (qdaemon, qtrans) + struct sdaemon *qdaemon; + struct stransfer *qtrans; +{ +#if DEBUG > 0 + if (qtrans->precfn == NULL) + ulog (LOG_FATAL, "fqueue_receive: Bad call"); +#endif + utdequeue (qtrans); + utqueue (&qTreceive, qtrans, FALSE); + + /* Since we are now going to wait to receive data, don't charge this + transfer for send time. */ + if (qtrans == sTsend.qtrans) + return ftcharge (qdaemon, (struct stransfer *) NULL, TRUE, FALSE); + return TRUE; +} + +/* Get a new local channel number. */ + +static void +utchanalc (qdaemon, qtrans) + struct sdaemon *qdaemon; + struct stransfer *qtrans; +{ + do + { + ++iTchan; + if (iTchan > qdaemon->qproto->cchans) + iTchan = 1; + } + while (aqTchan[iTchan] != NULL); + + qtrans->ilocal = iTchan; + aqTchan[iTchan] = qtrans; + ++cTchans; +} + +/* Return the transfer for a channel number. */ + +__inline__ +static struct stransfer * +qtchan (ic) + int ic; +{ + return aqTchan[ic]; +} + +/* Clear the channel number for a transfer. */ + +__inline__ +static void +utchanfree (qt) + struct stransfer *qt; +{ + if (qt->ilocal != 0) + { + aqTchan[qt->ilocal] = NULL; + qt->ilocal = 0; + --cTchans; + } +} + +/* Allocate a new transfer structure. */ + +struct stransfer * +qtransalc (qcmd) + struct scmd *qcmd; +{ + register struct stransfer *q; + + q = qTavail; + if (q != NULL) + utdequeue (q); + else + q = (struct stransfer *) xmalloc (sizeof (struct stransfer)); + q->qnext = NULL; + q->qprev = NULL; + q->pqqueue = NULL; + q->psendfn = NULL; + q->precfn = NULL; + q->pinfo = NULL; + q->fsendfile = FALSE; + q->frecfile = FALSE; + q->e = EFILECLOSED; + q->ipos = 0; + q->fcmd = FALSE; + q->zcmd = NULL; + q->ccmd = 0; + q->ilocal = 0; + q->iremote = 0; + if (qcmd != NULL) + { + q->s = *qcmd; + q->s.zfrom = zbufcpy (qcmd->zfrom); + q->s.zto = zbufcpy (qcmd->zto); + q->s.zuser = zbufcpy (qcmd->zuser); + q->s.zoptions = zbufcpy (qcmd->zoptions); + q->s.ztemp = zbufcpy (qcmd->ztemp); + q->s.znotify = zbufcpy (qcmd->znotify); + q->s.zcmd = zbufcpy (qcmd->zcmd); + } + else + { + q->s.zfrom = NULL; + q->s.zto = NULL; + q->s.zuser = NULL; + q->s.zoptions = NULL; + q->s.ztemp = NULL; + q->s.znotify = NULL; + q->s.zcmd = NULL; + } + q->isecs = 0; + q->imicros = 0; + q->cbytes = 0; + + return q; +} + +/* Free a transfer structure. This does not free any pinfo + information that may have been allocated. */ + +void +utransfree (q) + struct stransfer *q; +{ + ubuffree (q->zcmd); + ubuffree ((char *) q->s.zfrom); + ubuffree ((char *) q->s.zto); + ubuffree ((char *) q->s.zuser); + ubuffree ((char *) q->s.zoptions); + ubuffree ((char *) q->s.ztemp); + ubuffree ((char *) q->s.znotify); + ubuffree ((char *) q->s.zcmd); + + utchanfree (q); + if (q->iremote > 0) + { + aqTremote[q->iremote] = NULL; + q->iremote = 0; + } + +#if DEBUG > 0 + q->zcmd = NULL; + q->s.zfrom = NULL; + q->s.zto = NULL; + q->s.zuser = NULL; + q->s.zoptions = NULL; + q->s.ztemp = NULL; + q->s.znotify = NULL; + q->s.zcmd = NULL; + q->psendfn = NULL; + q->precfn = NULL; +#endif + + /* Don't try to charge time to this structure any longer. */ + if (sTsend.qtrans == q) + sTsend.qtrans = NULL; + if (sTreceive.qtrans == q) + sTreceive.qtrans = NULL; + + utdequeue (q); + utqueue (&qTavail, q, FALSE); +} + +/* Handle timing of file tranfers. This is called when processing + starts for a transfer structure. All time up to the next call to + this function is charged to that transfer structure. Sending time + and receiving time are charged separately. Normally if we are + about to start charging the same structure we are already charging, + we do nothing; but if the fforce argument is TRUE, we charge the + time anyhow. */ + +static boolean +ftcharge (qdaemon, qtrans, fsend, fforce) + struct sdaemon *qdaemon; + struct stransfer *qtrans; + boolean fsend; + boolean fforce; +{ + struct scharge *qcharge, *qother; + long inextsecs, inextmicros; + + if (fsend) + { + qcharge = &sTsend; + qother = &sTreceive; + } + else + { + qcharge = &sTreceive; + qother = &sTsend; + } + + if (! fforce && qtrans == qcharge->qtrans) + return TRUE; + + inextsecs = ixsysdep_process_time (&inextmicros); + if (qcharge->qtrans != NULL) + { + qcharge->qtrans->isecs += inextsecs - qcharge->isecs; + qcharge->qtrans->imicros += inextmicros - qcharge->imicros; + + /* If we are charging the same structure for both send and + receive, update the time we are not currently charging so + that we don't charge twice for the same time. */ + if (qcharge->qtrans == qother->qtrans) + { + qother->isecs = inextsecs; + qother->imicros = inextmicros; + } + } + + qcharge->qtrans = qtrans; + qcharge->isecs = inextsecs; + qcharge->imicros = inextmicros; + + /* If enough time has elapsed since the last time we checked the + queue, check it again. We do this here because we have already + gone to the trouble of getting the time. */ + if (inextsecs - iTchecktime >= CCHECKWAIT) + { + if (! fcheck_queue (qdaemon)) + return FALSE; + } + + return TRUE; +} + + +/* Gather local commands and queue them up for later processing. Also + recompute time based control values. */ + +boolean +fqueue (qdaemon, pfany) + struct sdaemon *qdaemon; + boolean *pfany; +{ + const struct uuconf_system *qsys; + int bgrade; + struct uuconf_timespan *qlocal_size, *qremote_size; + + if (pfany != NULL) + *pfany = FALSE; + + qsys = qdaemon->qsys; + + /* If we are not the caller, the grade will be set during the + initial handshake. */ + if (! qdaemon->fcaller) + bgrade = qdaemon->bgrade; + else + { + long ival; + + if (! ftimespan_match (qsys->uuconf_qtimegrade, &ival, + (int *) NULL)) + bgrade = '\0'; + else + bgrade = (char) ival; + } + + /* Determine the maximum sizes we can send and receive. */ + if (qdaemon->fcaller) + { + qlocal_size = qsys->uuconf_qcall_local_size; + qremote_size = qsys->uuconf_qcall_remote_size; + } + else + { + qlocal_size = qsys->uuconf_qcalled_local_size; + qremote_size = qsys->uuconf_qcalled_remote_size; + } + + if (! ftimespan_match (qlocal_size, &qdaemon->clocal_size, (int *) NULL)) + qdaemon->clocal_size = (long) -1; + if (! ftimespan_match (qremote_size, &qdaemon->cremote_size, (int *) NULL)) + qdaemon->cremote_size = (long) -1; + + if (bgrade == '\0') + return TRUE; + + if (! fsysdep_get_work_init (qsys, bgrade)) + return FALSE; + + while (TRUE) + { + struct scmd s; + + if (! fsysdep_get_work (qsys, bgrade, &s)) + return FALSE; + + if (s.bcmd == 'H') + { + ulog_user ((const char *) NULL); + break; + } + + if (s.bcmd == 'P') + { + struct stransfer *qtrans; + + /* A poll file. */ + ulog_user ((const char *) NULL); + qtrans = qtransalc (&s); + qtrans->psendfn = flocal_poll_file; + if (! fqueue_local (qdaemon, qtrans)) + return FALSE; + continue; + } + + ulog_user (s.zuser); + + switch (s.bcmd) + { + case 'S': + case 'E': + if (! flocal_send_file_init (qdaemon, &s)) + return FALSE; + break; + case 'R': + if (! flocal_rec_file_init (qdaemon, &s)) + return FALSE; + break; + case 'X': + if (! flocal_xcmd_init (qdaemon, &s)) + return FALSE; + break; +#if DEBUG > 0 + default: + ulog (LOG_FATAL, "fqueue: Can't happen"); + break; +#endif + } + } + + if (pfany != NULL) + *pfany = qTlocal != NULL; + + iTchecktime = ixsysdep_process_time ((long *) NULL); + + return TRUE; +} + +/* Clear everything off the work queue. This is used when the call is + complete, or if the call is never made. */ + +void +uclear_queue (qdaemon) + struct sdaemon *qdaemon; +{ + int i; + + usysdep_get_work_free (qdaemon->qsys); + + qTlocal = NULL; + qTremote = NULL; + qTsend = NULL; + qTreceive = NULL; + cTchans = 0; + iTchan = 0; + sTsend.qtrans = NULL; + sTreceive.qtrans = NULL; + cTcmdlen = 0; + qTreceive_ack = NULL; + for (i = 0; i < IMAX_CHAN + 1; i++) + { + aqTchan[i] = NULL; + aqTremote[i] = NULL; + } +} + +/* Recheck the work queue during a conversation. This is only called + if it's been more than CCHECKWAIT seconds since the last time the + queue was checked. */ + +static boolean +fcheck_queue (qdaemon) + struct sdaemon *qdaemon; +{ + int cchans; + + /* Only check if we are the master, or if there are multiple + channels, or if we aren't already trying to get the other side to + hang up. Otherwise, there's nothing we can do with any new jobs + we might find. */ + if ((qdaemon->ireliable & UUCONF_RELIABLE_FULLDUPLEX) == 0) + cchans = 1; + else + cchans = qdaemon->qproto->cchans; + if (qdaemon->fmaster + || cchans > 1 + || ! qdaemon->frequest_hangup) + { + boolean fany; + + DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO, + "fcheck_queue: Rechecking work queue"); + if (! fqueue (qdaemon, &fany)) + return FALSE; + + /* If we found something to do, and we're not the master, and we + don't have multiple channels to send new jobs over, try to + get the other side to hang up. */ + if (fany && ! qdaemon->fmaster && cchans <= 1) + qdaemon->frequest_hangup = TRUE; + } + + return TRUE; +} + +/* The main transfer loop. The uucico daemon spends essentially all + its time in this function. */ + +boolean +floop (qdaemon) + struct sdaemon *qdaemon; +{ + int cchans; + boolean fret; + + /* If we are using a half-duplex line, act as though we have only a + single channel; otherwise we might start a send and a receive at + the same time. */ + if ((qdaemon->ireliable & UUCONF_RELIABLE_FULLDUPLEX) == 0) + cchans = 1; + else + cchans = qdaemon->qproto->cchans; + + fret = TRUE; + + while (! qdaemon->fhangup) + { + register struct stransfer *q; + +#if DEBUG > 1 + /* If we're doing any debugging, close the log and debugging + files regularly. This will let people copy them off and + remove them while the conversation is in progresss. */ + if (iDebug != 0) + { + ulog_close (); + ustats_close (); + } +#endif + + if (qdaemon->fmaster) + { + boolean fhangup; + + /* We've managed to become the master, so we no longer want + to request a hangup. */ + qdaemon->frequest_hangup = FALSE; + + fhangup = FALSE; + + if (qdaemon->fhangup_requested + && qTsend == NULL) + { + /* The remote system has requested that we transfer + control by sending CYM after receiving a file. */ + DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO, + "floop: Transferring control at remote request"); + fhangup = TRUE; + } + else if (qTremote == NULL + && qTlocal == NULL + && qTsend == NULL + && qTreceive == NULL) + { + /* We don't have anything to do. Try to find some new + jobs. If we can't, transfer control. */ + if (! fqueue (qdaemon, (boolean *) NULL)) + { + fret = FALSE; + break; + } + if (qTlocal == NULL) + { + DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO, + "floop: No work for master"); + fhangup = TRUE; + } + } + + if (fhangup) + { + if (! (*qdaemon->qproto->pfsendcmd) (qdaemon, "H", 0, 0)) + { + fret = FALSE; + break; + } + qdaemon->fmaster = FALSE; + } + } + + /* If we are no long the master, clear any requested hangup. We + may have already hung up before checking this variable in the + block above. */ + if (! qdaemon->fmaster) + qdaemon->fhangup_requested = FALSE; + + /* Immediately queue up any remote jobs. We don't need local + channel numbers for them, since we can disambiguate based on + the remote channel number. */ + while (qTremote != NULL) + { + q = qTremote; + utdequeue (q); + utqueue (&qTsend, q, TRUE); + } + + /* If we are the master, or if we have multiple channels, try to + queue up additional local jobs. */ + if (qdaemon->fmaster || cchans > 1) + { + while (qTlocal != NULL && cTchans < cchans) + { + /* We have room for an additional channel. */ + q = qTlocal; + if (! fqueue_send (qdaemon, q)) + { + fret = FALSE; + break; + } + utchanalc (qdaemon, q); + } + if (! fret) + break; + } + + q = qTsend; + + if (q == NULL) + { + ulog_user ((const char *) NULL); + DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO, "floop: Waiting for data"); + if (! (*qdaemon->qproto->pfwait) (qdaemon)) + { + fret = FALSE; + break; + } + } + else + { + ulog_user (q->s.zuser); + + if (! q->fsendfile) + { + if (! ftcharge (qdaemon, q, TRUE, TRUE)) + { + fret = FALSE; + break; + } + + if (! (*q->psendfn) (q, qdaemon)) + { + fret = FALSE; + break; + } + } + else + { + if (! ftcharge (qdaemon, q, TRUE, FALSE)) + { + fret = FALSE; + break; + } + + if (q->zlog != NULL) + { + ulog (LOG_NORMAL, "%s", q->zlog); + ubuffree (q->zlog); + q->zlog = NULL; + } + + /* We can read the file in a tight loop until qTremote + changes or until we have transferred the entire file. + We can disregard any changes to qTlocal since we + already have something to send anyhow. */ + while (qTremote == NULL) + { + char *zdata; + size_t cdata; + long ipos; + + zdata = (*qdaemon->qproto->pzgetspace) (qdaemon, &cdata); + if (zdata == NULL) + { + fret = FALSE; + break; + } + + if (ffileeof (q->e)) + cdata = 0; + else + { + cdata = cfileread (q->e, zdata, cdata); + if (ffilereaderror (q->e, cdata)) + { + /* There is no way to report a file reading + error, so we just drop the connection. */ + ulog (LOG_ERROR, "read: %s", strerror (errno)); + fret = FALSE; + break; + } + } + + ipos = q->ipos; + q->ipos += cdata; + q->cbytes += cdata; + + if (! (*qdaemon->qproto->pfsenddata) (qdaemon, zdata, + cdata, q->ilocal, + q->iremote, ipos)) + { + fret = FALSE; + break; + } + + /* It is possible that this transfer has just been + cancelled. */ + if (q != qTsend || ! q->fsendfile) + break; + + if (cdata == 0) + { + /* We must update the time now, because this + call may make an entry in the statistics + file. */ + if (! ftcharge (qdaemon, q, TRUE, TRUE)) + fret = FALSE; + q->fsendfile = FALSE; + if (! (*q->psendfn) (q, qdaemon)) + fret = FALSE; + break; + } + } + + if (! fret) + break; + } + } + } + + ulog_user ((const char *) NULL); + + (void) (*qdaemon->qproto->pfshutdown) (qdaemon); + + if (fret) + uwindow_acked (qdaemon, TRUE); + else + ufailed (qdaemon); + + return fret; +} + +/* This is called by the protocol routines when they have received + some data. If pfexit is not NULL, *pfexit should be set to TRUE if + the protocol receive loop should exit back to the main floop + routine, above. It is only important to set *pfexit to TRUE if the + main loop called the pfwait entry point, so we need never set it to + TRUE if we just receive data for a file. This routine never sets + *pfexit to FALSE. */ + +boolean +fgot_data (qdaemon, zfirst, cfirst, zsecond, csecond, ilocal, iremote, ipos, + fallacked, pfexit) + struct sdaemon *qdaemon; + const char *zfirst; + size_t cfirst; + const char *zsecond; + size_t csecond; + int ilocal; + int iremote; + long ipos; + boolean fallacked; + boolean *pfexit; +{ + struct stransfer *q; + int cwrote; + boolean fret; + + if (fallacked && qTreceive_ack != NULL) + uwindow_acked (qdaemon, TRUE); + + /* Now we have to decide which transfer structure gets the data. If + ilocal is -1, it means that the protocol does not know where to + route the data. In that case we route it to the first transfer + that is waiting for data, or, if none, as a new command. If + ilocal is 0, we either select based on the remote channel number + or we have a new command. */ + if (ilocal == -1 && qTreceive != NULL) + q = qTreceive; + else if (ilocal == 0 && iremote > 0 && aqTremote[iremote] != NULL) + q = aqTremote[iremote]; + else if (ilocal <= 0) + { + const char *znull; + + ulog_user ((const char *) NULL); + + /* This data is part of a command. If there is no null + character in the data, this string will be continued by the + next packet. Otherwise this must be the last string in the + command, and we don't care about what comes after the null + byte. */ + znull = (const char *) memchr (zfirst, '\0', cfirst); + if (znull != NULL) + fret = ftadd_cmd (qdaemon, zfirst, (size_t) (znull - zfirst), + iremote, TRUE); + else + { + fret = ftadd_cmd (qdaemon, zfirst, cfirst, iremote, FALSE); + if (fret && csecond > 0) + { + znull = (const char *) memchr (zsecond, '\0', csecond); + if (znull != NULL) + fret = ftadd_cmd (qdaemon, zsecond, + (size_t) (znull - zsecond), iremote, TRUE); + else + fret = ftadd_cmd (qdaemon, zsecond, csecond, iremote, FALSE); + } + } + + if (pfexit != NULL && (qdaemon->fhangup || qTremote != NULL)) + *pfexit = TRUE; + + return fret; + } + else + { + /* Get the transfer structure this data is intended for. */ + + q = qtchan (ilocal); + } + +#if DEBUG > 0 + if (q == NULL || q->precfn == NULL) + { + ulog (LOG_ERROR, "Protocol error: %lu bytes remote %d local %d", + (unsigned long) (cfirst + csecond), + iremote, ilocal); + return FALSE; + } +#endif + + ulog_user (q->s.zuser); + + fret = TRUE; + + /* If we're receiving a command, then accumulate it up to the null + byte. */ + if (q->fcmd) + { + const char *znull; + + znull = NULL; + while (cfirst > 0) + { + size_t cnew; + char *znew; + + znull = (const char *) memchr (zfirst, '\0', cfirst); + if (znull != NULL) + cnew = znull - zfirst; + else + cnew = cfirst; + znew = zbufalc (q->ccmd + cnew + 1); + memcpy (znew, q->zcmd, q->ccmd); + memcpy (znew + q->ccmd, zfirst, cnew); + znew[q->ccmd + cnew] = '\0'; + ubuffree (q->zcmd); + q->zcmd = znew; + q->ccmd += cnew; + + if (znull != NULL) + break; + + zfirst = zsecond; + cfirst = csecond; + csecond = 0; + } + + if (znull != NULL) + { + char *zcmd; + size_t ccmd; + + if (! ftcharge (qdaemon, q, FALSE, TRUE)) + fret = FALSE; + zcmd = q->zcmd; + ccmd = q->ccmd; + q->fcmd = FALSE; + q->zcmd = NULL; + q->ccmd = 0; + if (! (*q->precfn) (q, qdaemon, zcmd, ccmd + 1)) + fret = FALSE; + ubuffree (zcmd); + } + else + { + if (! ftcharge (qdaemon, q, FALSE, FALSE)) + fret = FALSE; + } + + if (pfexit != NULL + && (qdaemon->fhangup + || qdaemon->fmaster + || qTsend != NULL)) + *pfexit = TRUE; + } + else if (! q->frecfile || cfirst == 0) + { + /* We're either not receiving a file or the file transfer is + complete. */ + if (! ftcharge (qdaemon, q, FALSE, TRUE)) + fret = FALSE; + q->frecfile = FALSE; + if (! (*q->precfn) (q, qdaemon, zfirst, cfirst)) + fret = FALSE; + if (fret && csecond > 0) + return fgot_data (qdaemon, zsecond, csecond, + (const char *) NULL, (size_t) 0, + ilocal, iremote, ipos + (long) cfirst, + FALSE, pfexit); + if (pfexit != NULL + && (qdaemon->fhangup + || qdaemon->fmaster + || qTsend != NULL)) + *pfexit = TRUE; + } + else + { + if (! ftcharge (qdaemon, q, FALSE, FALSE)) + fret = FALSE; + + if (q->zlog != NULL) + { + ulog (LOG_NORMAL, "%s", q->zlog); + ubuffree (q->zlog); + q->zlog = NULL; + } + + if (ipos != -1 && ipos != q->ipos) + { + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, + "fgot_data: Seeking to %ld", ipos); + if (! ffileseek (q->e, ipos)) + { + ulog (LOG_ERROR, "seek: %s", strerror (errno)); + fret = FALSE; + } + q->ipos = ipos; + } + + if (fret) + { + while (cfirst > 0) + { + cwrote = cfilewrite (q->e, (char *) zfirst, cfirst); + if (cwrote == cfirst) + { +#if FREE_SPACE_DELTA > 0 + long cfree_space; + + /* Check that there is still enough space on the + disk. If there isn't, we drop the connection, + because we have no way to abort a file transfer + in progress. */ + cfree_space = qdaemon->qsys->uuconf_cfree_space; + if (cfree_space > 0 + && ((q->cbytes / FREE_SPACE_DELTA) + != (q->cbytes + cfirst) / FREE_SPACE_DELTA) + && ! frec_check_free (q, cfree_space)) + { + fret = FALSE; + break; + } +#endif + q->cbytes += cfirst; + q->ipos += cfirst; + } + else + { + if (cwrote < 0) + ulog (LOG_ERROR, "write: %s", strerror (errno)); + else + ulog (LOG_ERROR, + "Wrote %d to file when trying to write %lu", + cwrote, (unsigned long) cfirst); + + /* Any write error is almost certainly a temporary + condition, or else UUCP would not be functioning + at all. If we continue to accept the file, we + will wind up rejecting it at the end (what else + could we do?) and the remote system will throw + away the request. We're better off just dropping + the connection, which is what happens when we + return FALSE, and trying again later. */ + fret = FALSE; + break; + } + + zfirst = zsecond; + cfirst = csecond; + csecond = 0; + } + } + + if (pfexit != NULL && qdaemon->fhangup) + *pfexit = TRUE; + } + + return fret; +} + +/* Accumulate a string into a command. If the command is complete, + start up a new transfer. */ + +static boolean +ftadd_cmd (qdaemon, z, clen, iremote, flast) + struct sdaemon *qdaemon; + const char *z; + size_t clen; + int iremote; + boolean flast; +{ + static char *zbuf; + static size_t cbuf; + size_t cneed; + struct scmd s; + + cneed = cTcmdlen + clen + 1; + if (cneed > cbuf) + { + zbuf = (char *) xrealloc ((pointer) zbuf, cneed); + cbuf = cneed; + } + + memcpy (zbuf + cTcmdlen, z, clen); + zbuf[cTcmdlen + clen] = '\0'; + + if (! flast) + { + cTcmdlen += clen; + return TRUE; + } + + /* Don't save this string for next time. */ + cTcmdlen = 0; + + DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO, + "ftadd_cmd: Got command \"%s\"", zbuf); + + if (! fparse_cmd (zbuf, &s)) + { + ulog (LOG_ERROR, "Received garbled command \"%s\"", zbuf); + return TRUE; + } + + if (s.bcmd != 'H' && s.bcmd != 'Y' && s.bcmd != 'N') + ulog_user (s.zuser); + else + ulog_user ((const char *) NULL); + + switch (s.bcmd) + { + case 'S': + case 'E': + return fremote_send_file_init (qdaemon, &s, iremote); + case 'R': + return fremote_rec_file_init (qdaemon, &s, iremote); + case 'X': + return fremote_xcmd_init (qdaemon, &s, iremote); + case 'H': + /* This is a remote request for a hangup. We close the log + files so that they may be moved at this point. */ + ulog_close (); + ustats_close (); + { + struct stransfer *q; + + q = qtransalc ((struct scmd *) NULL); + q->psendfn = fremote_hangup_reply; + q->iremote = iremote; + return fqueue_remote (qdaemon, q); + } + case 'N': + /* This means a hangup request is being denied; we just ignore + this and wait for further commands. */ + return TRUE; + case 'Y': + /* This is a remote confirmation of a hangup. We reconfirm. */ + if (qdaemon->fhangup) + return TRUE; +#if DEBUG > 0 + if (qdaemon->fmaster) + ulog (LOG_ERROR, "Got hangup reply as master"); +#endif + /* Don't check errors rigorously here, since the other side + might jump the gun and hang up. The fLog_sighup variable + will get set TRUE again when the port is closed. */ + fLog_sighup = FALSE; + (void) (*qdaemon->qproto->pfsendcmd) (qdaemon, "HY", 0, iremote); + qdaemon->fhangup = TRUE; + return TRUE; +#if DEBUG > 0 + default: + ulog (LOG_FATAL, "ftadd_cmd: Can't happen"); + return FALSE; +#endif + } +} + +/* The remote system is requesting a hang up. If we have something to + do, send an HN. Otherwise send two HY commands (the other side is + presumed to send an HY command between the first and second, but we + don't bother to wait for it) and hang up. */ + +static boolean +fremote_hangup_reply (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + boolean fret; + + utransfree (qtrans); + + if (qTremote == NULL + && qTlocal == NULL + && qTsend == NULL + && qTreceive == NULL) + { + if (! fqueue (qdaemon, (boolean *) NULL)) + return FALSE; + + if (qTlocal == NULL) + { + DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO, "fremote_hangup_reply: No work"); + fret = ((*qdaemon->qproto->pfsendcmd) (qdaemon, "HY", 0, 0) + && (*qdaemon->qproto->pfsendcmd) (qdaemon, "HY", 0, 0)); + qdaemon->fhangup = TRUE; + return fret; + } + } + + DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO, "fremote_hangup_reply: Found work"); + fret = (*qdaemon->qproto->pfsendcmd) (qdaemon, "HN", 0, 0); + qdaemon->fmaster = TRUE; + return fret; +} + +/* As described in system.h, we need to keep track of which files have + been successfully received for which we do not know that the other + system has received our acknowledgement. This routine is called to + keep a list of such files. */ + +static struct sreceive_ack *qTfree_receive_ack; + +void +usent_receive_ack (qdaemon, qtrans) + struct sdaemon *qdaemon; + struct stransfer *qtrans; +{ + struct sreceive_ack *q; + + if (qTfree_receive_ack == NULL) + q = (struct sreceive_ack *) xmalloc (sizeof (struct sreceive_ack)); + else + { + q = qTfree_receive_ack; + qTfree_receive_ack = q->qnext; + } + + q->qnext = qTreceive_ack; + q->zto = zbufcpy (qtrans->s.zto); + q->ztemp = zbufcpy (qtrans->s.ztemp); + q->fmarked = FALSE; + + qTreceive_ack = q; +} + +/* This routine is called by the protocol code when either all + outstanding data has been acknowledged or one complete window has + passed. It may be called directly by the protocol, or it may be + called via fgot_data. If one complete window has passed, then all + unmarked receives are marked, and we know that all marked ones have + been acked. */ + +void +uwindow_acked (qdaemon, fallacked) + struct sdaemon *qdaemon; + boolean fallacked; +{ + register struct sreceive_ack **pq; + + pq = &qTreceive_ack; + while (*pq != NULL) + { + if (fallacked || (*pq)->fmarked) + { + struct sreceive_ack *q; + + q = *pq; + (void) fsysdep_forget_reception (qdaemon->qsys, q->zto, + q->ztemp); + ubuffree (q->zto); + ubuffree (q->ztemp); + *pq = q->qnext; + q->qnext = qTfree_receive_ack; + qTfree_receive_ack = q; + } + else + { + (*pq)->fmarked = TRUE; + pq = &(*pq)->qnext; + } + } +} + +/* This routine is called when an error occurred and we are crashing + out of the connection. It is used to report statistics on failed + transfers to the statistics file, and it also discards useless + temporary files for file receptions. Note that the number of bytes + we report as having been sent has little or nothing to do with the + number of bytes the remote site actually received. */ + +void +ufailed (qdaemon) + struct sdaemon *qdaemon; +{ + register struct stransfer *q; + + /* Update the transfer times, but avoid looking in the queue. */ + iTchecktime = ixsysdep_process_time ((long *) NULL); + (void) ftcharge (qdaemon, (struct stransfer *) NULL, TRUE, TRUE); + (void) ftcharge (qdaemon, (struct stransfer *) NULL, FALSE, TRUE); + + if (qTsend != NULL) + { + q = qTsend; + do + { + if ((q->fsendfile || q->frecfile) + && q->cbytes > 0) + ustats (FALSE, q->s.zuser, qdaemon->qsys->uuconf_zname, + q->fsendfile, q->cbytes, q->isecs, q->imicros, + FALSE); + if (q->frecfile) + (void) frec_discard_temp (qdaemon, q); + q = q->qnext; + } + while (q != qTsend); + } + + if (qTreceive != NULL) + { + q = qTreceive; + do + { + if ((q->fsendfile || q->frecfile) + && q->cbytes > 0) + ustats (FALSE, q->s.zuser, qdaemon->qsys->uuconf_zname, + q->fsendfile, q->cbytes, q->isecs, q->imicros, + FALSE); + if (q->frecfile) + (void) frec_discard_temp (qdaemon, q); + q = q->qnext; + } + while (q != qTreceive); + } +} + +/* When a local poll file is found, it is entered on the queue like + any other job. When it is pulled off the queue, this function is + called. It just calls fsysdep_did_work, which will remove the poll + file. This ensures that poll files are only removed if the system + is actually called. */ + +/*ARGSUSED*/ +static boolean +flocal_poll_file (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + boolean fret; + + fret = fsysdep_did_work (qtrans->s.pseq); + utransfree (qtrans); + return fret; +} diff --git a/gnu/libexec/uucp/uucico/uucico.8 b/gnu/libexec/uucp/uucico/uucico.8 new file mode 100644 index 0000000000000..ccc93afab5633 --- /dev/null +++ b/gnu/libexec/uucp/uucico/uucico.8 @@ -0,0 +1,225 @@ +''' $Id: uucico.8,v 1.1.2.1 1994/05/01 16:02:47 jkh Exp $ +.TH uucico 8 "Taylor UUCP 1.04" +.SH NAME +uucico \- UUCP file transfer daemon +.SH SYNOPSIS +.B uucico +[ options ] +.SH DESCRIPTION +The +.I uucico +daemon processes file transfer requests queued by +.I uucp +(1) and +.I uux +(1). It is started when +.I uucp +or +.I uux +is run (unless they are given the +.B \-r +option). It is also typically started periodically using +entries in the +.I crontab +table(s). + +When invoked with the +.B \-r1 +option or the +.B \-s +or +.B \-S +option, the daemon will place a call to a remote system, running in +master mode. +Otherwise the daemon will start in slave mode, accepting a +call from a remote system. Typically a special login name will be set +up for UUCP which automatically invokes +.I uucico +when a call is made. + +When +.I uucico +terminates, it invokes the +.I uuxqt +(8) daemon, unless the +.B \-q +option is given; +.I uuxqt +(8) executes any work orders created by +.I uux +(1) on a remote system, and any work orders created locally which have +received remote files for which they were waiting. + +If a call fails, +.I uucico +will normally refuse to retry the +call until a certain (configurable) amount of time +has passed. This may be overridden by the +.B -f +or the +.B -S +option. + +The +.B \-l +or +.B \-e +option may be used to force +.I uucico +to produce its own prompts of "login: " and "Password:". When another +daemon calls in, it will see these prompts and log in as usual; the +login name and password will be checked against a separate list kept +specially for +.I uucico +rather than the +.I /etc/passwd +file. The +.B \-l +option will prompt once and then exit. The +.B \-e +option will prompt again after the first session is over; in this mode +.I uucico +will permanently control a port. + +If +.I uucico +receives a SIGQUIT, SIGTERM or SIGPIPE signal, it will cleanly abort +any current conversation with a remote system and exit. If it +receives a SIGHUP signal it will abort any current conversation, but +will continue to place calls to (if invoked with +.B \-r1) +and accept calls from (if invoked with +.B \-e) +other systems. If it receives a +SIGINT signal it will finish the current conversation, but will not +place or accept any more calls. +.SH OPTIONS +The following options may be given to +.I uucico. +.TP 5 +.B \-r1 +Start in master mode (call out to a system); implied by +.B \-s +or +.B \-S. +If no system is specified, call any system for which work is waiting +to be done. +.TP 5 +.B \-r0 +Start in slave mode. This is the default. +.TP 5 +.B \-s system +Call the named system. +.TP 5 +.B \-S system +Call the named system, ignoring any required wait. +.TP 5 +.B \-f +Ignore any required wait for any systems to be called. +.TP 5 +.B \-l +Prompt for login name and password using "login: " and "Password:". +This allows +.I uucico +to be easily run from +.I inetd +(8). The login name and password are checked against the UUCP +password file, which has no connection to the file +.I /etc/passwd. +.TP 5 +.B \-p port +Specify a port to call out on or to listen to. In slave mode, this +implies +.B \-e. +.TP 5 +.B \-e +Enter endless loop of login/password prompts and slave mode daemon +execution. The program will not stop by itself; you must use +.I kill +(1) to shut it down. +.TP 5 +.B \-w +After calling out (to a particular system when +.B \-s +or +.B \-S +is specified, or to all systems which have work when +.B \-r1 +is specified), begin an endless loop as with +.B \-e. +.TP 5 +.B \-q +Do not start the +.I uuxqt +(8) daemon when finished. +.TP 5 +.B \-c +If no calls are permitted at this time, then don't make the call, but +also do not put an error message in the log file and do not update the +system status (as reported by +.I uustat +(1)). This can be convenient for automated polling scripts, which may +want to simply attempt to call every system rather than worry about +which particular systems may be called at the moment. +.TP 5 +.B \-D +Do not detach from the controlling terminal. Normally +.I uucico +detaches from the terminal before each call out to another system and +before invoking +.I uuxqt. +This option prevents this. +.TP 5 +.B \-x type, \-X type +Turn on particular debugging types. The following types are +recognized: abnormal, chat, handshake, uucp-proto, proto, port, +config, spooldir, execute, incoming, outgoing. + +Multiple types may be given, separated by commas, and the +.B \-x +option may appear multiple times. A number may also be given, which +will turn on that many types from the foregoing list; for example, +.B \-x 2 +is equivalent to +.B \-x abnormal,chat. + +The debugging output is sent to the debugging file, usually one of +/usr/spool/uucp/Debug, /usr/spool/uucp/DEBUG, or +/usr/spool/uucp/.Admin/audit.local. +.TP 5 +.B \-I file +Set configuration file to use. This option may not be available, +depending upon how +.I uucico +was compiled. +.TP 5 +.B \-u login +This option is ignored. It is only included because some versions of +uucpd invoke +.I uucico +with it. +.SH FILES +The file names may be changed at compilation time or by the +configuration file, so these are only approximations. + +.br +/usr/lib/uucp/config - Configuration file. +.br +/usr/lib/uucp/passwd - Default UUCP password file. +.br +/usr/spool/uucp - +UUCP spool directory. +.br +/usr/spool/uucp/Log - +UUCP log file. +.br +/usr/spool/uucppublic - +Default UUCP public directory. +.br +/usr/spool/uucp/Debug - +Debugging file. +.SH SEE ALSO +kill(1), uucp(1), uux(1), uustat(1), uuxqt(8) +.SH AUTHOR +Ian Lance Taylor +(ian@airs.com or uunet!airs!ian) diff --git a/gnu/libexec/uucp/uucico/uucico.c b/gnu/libexec/uucp/uucico/uucico.c new file mode 100644 index 0000000000000..01717c4bcf4fa --- /dev/null +++ b/gnu/libexec/uucp/uucico/uucico.c @@ -0,0 +1,2618 @@ +/* uucico.c + This is the main UUCP communication program. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char uucico_rcsid[] = "$Id: uucico.c,v 1.1 1993/08/05 18:27:24 conklin Exp $"; +#endif + +#include <ctype.h> + +#if HAVE_LIMITS_H +#include <limits.h> +#else +#define LONG_MAX 2147483647L +#endif + +#include "getopt.h" + +#include "uudefs.h" +#include "uuconf.h" +#include "conn.h" +#include "prot.h" +#include "trans.h" +#include "system.h" + +/* The program name. */ +char abProgram[] = "uucico"; + +/* Define the known protocols. */ + +#define TCP_PROTO \ + (UUCONF_RELIABLE_ENDTOEND \ + | UUCONF_RELIABLE_RELIABLE \ + | UUCONF_RELIABLE_EIGHT) + +static const struct sprotocol asProtocols[] = +{ + { 't', TCP_PROTO, 1, + asTproto_params, ftstart, ftshutdown, ftsendcmd, ztgetspace, + ftsenddata, ftwait, ftfile }, + { 'e', TCP_PROTO, 1, + asEproto_params, festart, feshutdown, fesendcmd, zegetspace, + fesenddata, fewait, fefile }, + { 'i', UUCONF_RELIABLE_EIGHT, 7, + asIproto_params, fistart, fishutdown, fisendcmd, zigetspace, + fisenddata, fiwait, NULL }, + { 'a', UUCONF_RELIABLE_EIGHT, 1, + asZproto_params, fzstart, fzshutdown, fzsendcmd, zzgetspace, + fzsenddata, fzwait, fzfile }, + { 'g', UUCONF_RELIABLE_EIGHT, 1, + asGproto_params, fgstart, fgshutdown, fgsendcmd, zggetspace, + fgsenddata, fgwait, NULL }, + { 'G', UUCONF_RELIABLE_EIGHT, 1, + asGproto_params, fbiggstart, fgshutdown, fgsendcmd, zggetspace, + fgsenddata, fgwait, NULL }, + { 'j', UUCONF_RELIABLE_EIGHT, 7, + asIproto_params, fjstart, fjshutdown, fisendcmd, zigetspace, + fisenddata, fiwait, NULL }, + { 'f', UUCONF_RELIABLE_RELIABLE, 1, + asFproto_params, ffstart, ffshutdown, ffsendcmd, zfgetspace, + ffsenddata, ffwait, fffile }, +}; + +#define CPROTOCOLS (sizeof asProtocols / sizeof asProtocols[0]) + +/* Locked system. */ +static boolean fLocked_system; +static struct uuconf_system sLocked_system; + +/* Daemon structure holding information about the remote system (must + be global so the error handler can see it. */ +static struct sdaemon sDaemon; + +/* Open connection. */ +static struct sconnection *qConn; + +/* uuconf global pointer; need to close the connection after a fatal + error. */ +static pointer pUuconf; + +/* This structure is passed to iuport_lock via uuconf_find_port. */ +struct spass +{ + boolean fmatched; + boolean flocked; + struct sconnection *qconn; +}; + +/* Local functions. */ + +static void uusage P((void)); +static void uabort P((void)); +static boolean fcall P((pointer puuconf, + const struct uuconf_system *qsys, + struct uuconf_port *qport, boolean fifwork, + boolean fforce, boolean fdetach, + boolean ftimewarn)); +static boolean fconn_call P((struct sdaemon *qdaemon, + struct uuconf_port *qport, + struct sstatus *qstat, int cretry, + boolean *pfcalled)); +static boolean fdo_call P((struct sdaemon *qdaemon, + struct sstatus *qstat, + const struct uuconf_dialer *qdialer, + boolean *pfcalled, enum tstatus_type *pterr)); +static int iuport_lock P((struct uuconf_port *qport, pointer pinfo)); +static boolean flogin_prompt P((pointer puuconf, + struct sconnection *qconn)); +static boolean faccept_call P((pointer puuconf, const char *zlogin, + struct sconnection *qconn, + const char **pzsystem)); +static void uapply_proto_params P((pointer puuconf, int bproto, + struct uuconf_cmdtab *qcmds, + struct uuconf_proto_param *pas)); +static boolean fsend_uucp_cmd P((struct sconnection *qconn, + const char *z)); +static char *zget_uucp_cmd P((struct sconnection *qconn, + boolean frequired)); +static char *zget_typed_line P((struct sconnection *qconn)); + +/* Long getopt options. */ +static const struct option asLongopts[] = { { NULL, 0, NULL, 0 } }; + +int +main (argc, argv) + int argc; + char **argv; +{ + /* -c: Whether to warn if a call is attempted at a bad time. */ + boolean ftimewarn = TRUE; + /* -D: don't detach from controlling terminal. */ + boolean fdetach = TRUE; + /* -e: Whether to do an endless loop of accepting calls. */ + boolean fendless = FALSE; + /* -f: Whether to force a call despite status of previous call. */ + boolean fforce = FALSE; + /* -I file: configuration file name. */ + const char *zconfig = NULL; + /* -l: Whether to give a single login prompt. */ + boolean flogin = FALSE; + /* -P port: port to use; in master mode, call out on this port. In + slave mode, accept logins on this port. If port not specified, + then in master mode figure it out for each system, and in slave + mode use stdin and stdout. */ + const char *zport = NULL; + /* -q: Whether to start uuxqt when done. */ + boolean fuuxqt = TRUE; + /* -r1: Whether we are the master. */ + boolean fmaster = FALSE; + /* -s,-S system: system to call. */ + const char *zsystem = NULL; + /* -w: Whether to wait for a call after doing one. */ + boolean fwait = FALSE; + int iopt; + struct uuconf_port *qport; + struct uuconf_port sport; + boolean fret = TRUE; + pointer puuconf; + int iuuconf; +#if DEBUG > 1 + int iholddebug; +#endif + + while ((iopt = getopt_long (argc, argv, + "cDefI:lp:qr:s:S:u:x:X:w", + asLongopts, (int *) NULL)) != EOF) + { + switch (iopt) + { + case 'c': + /* Don't warn if a call is attempted at a bad time. */ + ftimewarn = FALSE; + break; + + case 'D': + /* Don't detach from controlling terminal. */ + fdetach = FALSE; + break; + + case 'e': + /* Do an endless loop of accepting calls. */ + fendless = TRUE; + break; + + case 'f': + /* Force a call even if it hasn't been long enough since the last + failed call. */ + fforce = TRUE; + break; + + case 'I': + /* Set configuration file name (default is in sysdep.h). */ + if (fsysdep_other_config (optarg)) + zconfig = optarg; + break; + + case 'l': + /* Prompt for login name and password. */ + flogin = TRUE; + break; + + case 'p': + /* Port to use */ + zport = optarg; + break; + + case 'q': + /* Don't start uuxqt. */ + fuuxqt = FALSE; + break; + + case 'r': + /* Set mode: -r1 for master, -r0 for slave (default) */ + if (strcmp (optarg, "1") == 0) + fmaster = TRUE; + else if (strcmp (optarg, "0") == 0) + fmaster = FALSE; + else + uusage (); + break; + + case 's': + /* Set system name */ + zsystem = optarg; + fmaster = TRUE; + break; + + case 'S': + /* Set system name and force call like -f */ + zsystem = optarg; + fforce = TRUE; + fmaster = TRUE; + break; + + case 'u': + /* Some versions of uucpd invoke uucico with a -u argument + specifying the login name. I'm told it is safe to ignore + this value, although perhaps we should use it rather than + zsysdep_login_name (). */ + break; + + case 'x': + case 'X': +#if DEBUG > 1 + /* Set debugging level */ + iDebug |= idebug_parse (optarg); +#endif + break; + + case 'w': + /* Call out and then wait for a call in */ + fwait = TRUE; + break; + + case 0: + /* Long option found, and flag value set. */ + break; + + default: + uusage (); + break; + } + } + + if (optind != argc) + uusage (); + + if (fwait && zport == NULL) + { + ulog (LOG_ERROR, "-w requires -e"); + uusage (); + } + + iuuconf = uuconf_init (&puuconf, (const char *) NULL, zconfig); + if (iuuconf != UUCONF_SUCCESS) + ulog_uuconf (LOG_FATAL, puuconf, iuuconf); + pUuconf = puuconf; + +#if DEBUG > 1 + { + const char *zdebug; + + iuuconf = uuconf_debuglevel (puuconf, &zdebug); + if (iuuconf != UUCONF_SUCCESS) + ulog_uuconf (LOG_FATAL, puuconf, iuuconf); + if (zdebug != NULL) + iDebug |= idebug_parse (zdebug); + } +#endif + + /* If a port was named, get its information. */ + if (zport == NULL) + qport = NULL; + else + { + iuuconf = uuconf_find_port (puuconf, zport, (long) 0, (long) 0, + (int (*) P((struct uuconf_port *, + pointer))) NULL, + (pointer) NULL, &sport); + if (iuuconf == UUCONF_NOT_FOUND) + ulog (LOG_FATAL, "%s: Port not found", zport); + else if (iuuconf != UUCONF_SUCCESS) + ulog_uuconf (LOG_FATAL, puuconf, iuuconf); + qport = &sport; + } + +#ifdef SIGINT + usysdep_signal (SIGINT); +#endif +#ifdef SIGHUP + usysdep_signal (SIGHUP); +#endif +#ifdef SIGQUIT + usysdep_signal (SIGQUIT); +#endif +#ifdef SIGTERM + usysdep_signal (SIGTERM); +#endif +#ifdef SIGPIPE + usysdep_signal (SIGPIPE); +#endif + + usysdep_initialize (puuconf, INIT_SUID); + + ulog_to_file (puuconf, TRUE); + ulog_fatal_fn (uabort); + + if (fmaster) + { + if (zsystem != NULL) + { + /* A system was named. Call it. */ + iuuconf = uuconf_system_info (puuconf, zsystem, + &sLocked_system); + if (iuuconf == UUCONF_NOT_FOUND) + ulog (LOG_FATAL, "%s: System not found", zsystem); + else if (iuuconf != UUCONF_SUCCESS) + ulog_uuconf (LOG_FATAL, puuconf, iuuconf); + + /* Detach from the controlling terminal for the call. This + probably makes sense only on Unix. We want the modem + line to become the controlling terminal. */ + if (fdetach && + (qport == NULL + || qport->uuconf_ttype != UUCONF_PORTTYPE_STDIN)) + usysdep_detach (); + + ulog_system (sLocked_system.uuconf_zname); + +#if DEBUG > 1 + iholddebug = iDebug; + if (sLocked_system.uuconf_zdebug != NULL) + iDebug |= idebug_parse (sLocked_system.uuconf_zdebug); +#endif + + if (! fsysdep_lock_system (&sLocked_system)) + { + ulog (LOG_ERROR, "System already locked"); + fret = FALSE; + } + else + { + fLocked_system = TRUE; + fret = fcall (puuconf, &sLocked_system, qport, FALSE, + fforce, fdetach, ftimewarn); + if (fLocked_system) + { + (void) fsysdep_unlock_system (&sLocked_system); + fLocked_system = FALSE; + } + } +#if DEBUG > 1 + iDebug = iholddebug; +#endif + ulog_system ((const char *) NULL); + (void) uuconf_system_free (puuconf, &sLocked_system); + } + else + { + char **pznames, **pz; + int c, i; + boolean fdidone; + + /* Call all systems which have work to do. */ + fret = TRUE; + fdidone = FALSE; + + iuuconf = uuconf_system_names (puuconf, &pznames, 0); + if (iuuconf != UUCONF_SUCCESS) + ulog_uuconf (LOG_FATAL, puuconf, iuuconf); + + /* Randomize the order in which we call the systems. */ + c = 0; + for (pz = pznames; *pz != NULL; pz++) + c++; + + srand ((unsigned int) ixsysdep_time ((long *) NULL)); + for (i = c - 1; i > 0; i--) + { + int iuse; + char *zhold; + + iuse = rand () % (i + 1); + zhold = pznames[i]; + pznames[i] = pznames[iuse]; + pznames[iuse] = zhold; + } + + for (pz = pznames; *pz != NULL && ! FGOT_SIGNAL (); pz++) + { + iuuconf = uuconf_system_info (puuconf, *pz, + &sLocked_system); + if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + xfree ((pointer) *pz); + continue; + } + + if (fsysdep_has_work (&sLocked_system)) + { + fdidone = TRUE; + + /* Detach from the controlling terminal. On Unix + this means that we will wind up forking a new + process for each system we call. */ + if (fdetach + && (qport == NULL + || qport->uuconf_ttype != UUCONF_PORTTYPE_STDIN)) + usysdep_detach (); + + ulog_system (sLocked_system.uuconf_zname); + +#if DEBUG > 1 + iholddebug = iDebug; + if (sLocked_system.uuconf_zdebug != NULL) + iDebug |= idebug_parse (sLocked_system.uuconf_zdebug); +#endif + + if (! fsysdep_lock_system (&sLocked_system)) + { + ulog (LOG_ERROR, "System already locked"); + fret = FALSE; + } + else + { + fLocked_system = TRUE; + if (! fcall (puuconf, &sLocked_system, qport, TRUE, + fforce, fdetach, ftimewarn)) + fret = FALSE; + + /* Now ignore any SIGHUP that we got. */ + afSignal[INDEXSIG_SIGHUP] = FALSE; + + if (fLocked_system) + { + (void) fsysdep_unlock_system (&sLocked_system); + fLocked_system = FALSE; + } + } +#if DEBUG > 1 + iDebug = iholddebug; +#endif + ulog_system ((const char *) NULL); + } + + (void) uuconf_system_free (puuconf, &sLocked_system); + xfree ((pointer) *pz); + } + + xfree ((pointer) pznames); + + if (! fdidone) + ulog (LOG_NORMAL, "No work"); + } + + /* If requested, wait for calls after dialing out. */ + if (fwait) + { + fendless = TRUE; + fmaster = FALSE; + } + } + + if (! fmaster) + { + struct sconnection sconn; + boolean flocked; + + /* If a port was specified by name, we go into endless loop + mode. In this mode, we wait for calls and prompt them with + "login:" and "Password:", so that they think we are a regular + UNIX system. If we aren't in endless loop mode, we have been + called by some other system. If flogin is TRUE, we prompt + with "login:" and "Password:" a single time. */ + + fret = TRUE; + zsystem = NULL; + + if (! fconn_init (qport, &sconn)) + fret = FALSE; + + if (qport != NULL) + { + /* We are not using standard input. Detach from the + controlling terminal, so that the port we are about to + use becomes our controlling terminal. */ + if (fdetach + && qport->uuconf_ttype != UUCONF_PORTTYPE_STDIN) + usysdep_detach (); + + /* If a port was given, we loop forever. */ + fendless = TRUE; + } + + if (fconn_lock (&sconn, TRUE)) + flocked = TRUE; + else + { + flocked = FALSE; + ulog (LOG_ERROR, "%s: Port already locked", + qport->uuconf_zname); + fret = FALSE; + } + + if (fret) + { + if (! fconn_open (&sconn, (long) 0, (long) 0, TRUE)) + fret = FALSE; + qConn = &sconn; + } + + if (fret) + { + if (fendless) + { + while (! FGOT_SIGNAL () + && flogin_prompt (puuconf, &sconn)) + { + /* Now ignore any SIGHUP that we got. */ + afSignal[INDEXSIG_SIGHUP] = FALSE; + + if (fLocked_system) + { + (void) fsysdep_unlock_system (&sLocked_system); + fLocked_system = FALSE; + } + if (! fconn_reset (&sconn)) + break; + } + fret = FALSE; + } + else + { + if (flogin) + fret = flogin_prompt (puuconf, &sconn); + else + { +#if DEBUG > 1 + iholddebug = iDebug; +#endif + fret = faccept_call (puuconf, zsysdep_login_name (), + &sconn, &zsystem); +#if DEBUG > 1 + iDebug = iholddebug; +#endif + } + } + } + + if (qConn != NULL) + { + if (! fconn_close (&sconn, puuconf, (struct uuconf_dialer *) NULL, + fret)) + fret = FALSE; + qConn = NULL; + } + + if (flocked) + (void) fconn_unlock (&sconn); + + if (fLocked_system) + { + (void) fsysdep_unlock_system (&sLocked_system); + fLocked_system = FALSE; + } + + uconn_free (&sconn); + } + + ulog_close (); + ustats_close (); + + /* If we got a SIGTERM, perhaps because the system is going down, + don't run uuxqt. We go ahead and run it for any other signal, + since I think they indicate more temporary conditions. */ + if (afSignal[INDEXSIG_SIGTERM]) + fuuxqt = FALSE; + + if (fuuxqt) + { + /* Detach from the controlling terminal before starting up uuxqt, + so that it runs as a true daemon. */ + if (fdetach) + usysdep_detach (); + if (zsystem == NULL) + { + if (! fsysdep_run ("uuxqt", (const char *) NULL, + (const char *) NULL)) + fret = FALSE; + } + else + { + if (! fsysdep_run ("uuxqt", "-s", zsystem)) + fret = FALSE; + } + } + + usysdep_exit (fret); + + /* Avoid complaints about not returning. */ + return 0; +} + +/* Print out a usage message. */ + +static void +uusage () +{ + fprintf (stderr, + "Taylor UUCP version %s, copyright (C) 1991, 1992 Ian Lance Taylor\n", + VERSION); + fprintf (stderr, + "Usage: uucico [options]\n"); + fprintf (stderr, + " -s,-S system: Call system (-S implies -f)\n"); + fprintf (stderr, + " -f: Force call despite system status\n"); + fprintf (stderr, + " -r state: 1 for master, 0 for slave (default)\n"); + fprintf (stderr, + " -p port: Specify port (implies -e)\n"); + fprintf (stderr, + " -l: prompt for login name and password\n"); + fprintf (stderr, + " -e: Endless loop of login prompts and daemon execution\n"); + fprintf (stderr, + " -w: After calling out, wait for incoming calls\n"); + fprintf (stderr, + " -q: Don't start uuxqt when done\n"); + fprintf (stderr, + " -x,-X debug: Set debugging level\n"); +#if HAVE_TAYLOR_CONFIG + fprintf (stderr, + " -I file: Set configuration file to use\n"); +#endif /* HAVE_TAYLOR_CONFIG */ + + exit (EXIT_FAILURE); +} + +/* This function is called when a LOG_FATAL error occurs. */ + +static void +uabort () +{ + if (fLocked_system) + ufailed (&sDaemon); + + ulog_user ((const char *) NULL); + + if (qConn != NULL) + { + (void) fconn_close (qConn, pUuconf, (struct uuconf_dialer *) NULL, + FALSE); + (void) fconn_unlock (qConn); + uconn_free (qConn); + } + + if (fLocked_system) + { + (void) fsysdep_unlock_system (&sLocked_system); + fLocked_system = FALSE; + } + + ulog_system ((const char *) NULL); + + ulog_close (); + ustats_close (); + + usysdep_exit (FALSE); +} + +/* Call another system, trying all the possible sets of calling + instructions. The qsys argument is the system to call. The qport + argument is the port to use, and may be NULL. If the fifwork + argument is TRUE, the call is only placed if there is work to be + done. If the fforce argument is TRUE, a call is forced even if not + enough time has passed since the last failed call. If the + ftimewarn argument is TRUE (the normal case), then a warning is + given if calls are not permitted at this time. */ + +static boolean +fcall (puuconf, qorigsys, qport, fifwork, fforce, fdetach, ftimewarn) + pointer puuconf; + const struct uuconf_system *qorigsys; + struct uuconf_port *qport; + boolean fifwork; + boolean fforce; + boolean fdetach; + boolean ftimewarn; +{ + struct sstatus sstat; + long inow; + boolean fbadtime, fnevertime; + const struct uuconf_system *qsys; + + if (! fsysdep_get_status (qorigsys, &sstat, (boolean *) NULL)) + return FALSE; + + /* Make sure it's been long enough since the last failed call, and + that we haven't exceeded the maximum number of retries. Even if + we are over the limit on retries, we permit a call to be made if + 24 hours have passed. This 24 hour limit is still controlled by + the retry time. */ + inow = ixsysdep_time ((long *) NULL); + if (! fforce) + { + if (qorigsys->uuconf_cmax_retries > 0 + && sstat.cretries >= qorigsys->uuconf_cmax_retries + && sstat.ilast + 24 * 60 * 60 < inow) + { + ulog (LOG_ERROR, "Too many retries"); + return FALSE; + } + + if (sstat.ttype == STATUS_COMPLETE + ? sstat.ilast + qorigsys->uuconf_csuccess_wait > inow + : sstat.ilast + sstat.cwait > inow) + { + ulog (LOG_NORMAL, "Retry time not reached"); + return FALSE; + } + } + + sDaemon.puuconf = puuconf; + sDaemon.qsys = NULL; + sDaemon.zlocalname = NULL; + sDaemon.qconn = NULL; + sDaemon.qproto = NULL; + sDaemon.clocal_size = -1; + sDaemon.cremote_size = -1; + sDaemon.cmax_ever = -2; + sDaemon.cmax_receive = -1; + sDaemon.ifeatures = 0; + sDaemon.frequest_hangup = FALSE; + sDaemon.fhangup_requested = FALSE; + sDaemon.fhangup = FALSE; + sDaemon.fmaster = TRUE; + sDaemon.fcaller = TRUE; + sDaemon.ireliable = 0; + sDaemon.bgrade = '\0'; + + fbadtime = TRUE; + fnevertime = TRUE; + + for (qsys = qorigsys; qsys != NULL; qsys = qsys->uuconf_qalternate) + { + int cretry; + boolean fany, fret, fcalled; + + if (FGOT_SIGNAL ()) + return FALSE; + + if (! qsys->uuconf_fcall || qsys->uuconf_qtimegrade == NULL) + continue; + + fnevertime = FALSE; + + /* Make sure this is a legal time to call. */ + if (! ftimespan_match (qsys->uuconf_qtimegrade, (long *) NULL, + &cretry)) + continue; + + sDaemon.qsys = qsys; + + /* Queue up any work there is to do. */ + if (! fqueue (&sDaemon, &fany)) + return FALSE; + + /* If we are only supposed to call if there is work, and there + isn't any work, check the next alternates. We can't give up + at this point because there might be some other alternates + with fewer restrictions on grade or file transfer size. */ + if (fifwork && ! fany) + { + uclear_queue (&sDaemon); + continue; + } + + fbadtime = FALSE; + + fret = fconn_call (&sDaemon, qport, &sstat, cretry, &fcalled); + + uclear_queue (&sDaemon); + + if (fret) + return TRUE; + if (fcalled) + return FALSE; + + /* Now we have to dump that port so that we can aquire a new + one. On Unix this means that we will fork and get a new + process ID, so we must unlock and relock the system. */ + if (fdetach) + { + (void) fsysdep_unlock_system (&sLocked_system); + fLocked_system = FALSE; + usysdep_detach (); + if (! fsysdep_lock_system (&sLocked_system)) + return FALSE; + fLocked_system = TRUE; + } + } + + if (fbadtime && ftimewarn) + { + ulog (LOG_NORMAL, "Wrong time to call"); + + /* Update the status, unless the system can never be called. If + the system can never be called, there is little point to + putting in a ``wrong time to call'' message. We don't change + the number of retries, although we do set the wait until the + next retry to 0. */ + if (! fnevertime) + { + sstat.ttype = STATUS_WRONG_TIME; + sstat.ilast = inow; + sstat.cwait = 0; + (void) fsysdep_set_status (qorigsys, &sstat); + } + } + + return FALSE; +} + +/* Find a port to use when calling a system, open a connection, and + dial the system. The actual call is done in fdo_call. This + routine is responsible for opening and closing the connection. */ + +static boolean +fconn_call (qdaemon, qport, qstat, cretry, pfcalled) + struct sdaemon *qdaemon; + struct uuconf_port *qport; + struct sstatus *qstat; + int cretry; + boolean *pfcalled; +{ + pointer puuconf; + const struct uuconf_system *qsys; + struct uuconf_port sport; + struct sconnection sconn; + enum tstatus_type terr; + boolean fret; + + puuconf = qdaemon->puuconf; + qsys = qdaemon->qsys; + + *pfcalled = FALSE; + + /* Ignore any SIGHUP signal we may have received up to this point. + This is needed on Unix because we may have gotten one from the + shell before we detached from the controlling terminal. */ + afSignal[INDEXSIG_SIGHUP] = FALSE; + + /* If no port was specified on the command line, use any port + defined for the system. To select the system port: 1) see if + port information was specified directly; 2) see if a port was + named; 3) get an available port given the baud rate. We don't + change the system status if a port is unavailable; i.e. we don't + force the system to wait for the retry time. */ + if (qport == NULL) + qport = qsys->uuconf_qport; + if (qport != NULL) + { + if (! fconn_init (qport, &sconn)) + return FALSE; + if (! fconn_lock (&sconn, FALSE)) + { + ulog (LOG_ERROR, "%s: Port already locked", + qport->uuconf_zname); + return FALSE; + } + } + else + { + struct spass s; + int iuuconf; + + s.fmatched = FALSE; + s.flocked = FALSE; + s.qconn = &sconn; + iuuconf = uuconf_find_port (puuconf, qsys->uuconf_zport, + qsys->uuconf_ibaud, + qsys->uuconf_ihighbaud, + iuport_lock, (pointer) &s, + &sport); + if (iuuconf == UUCONF_NOT_FOUND) + { + if (s.fmatched) + ulog (LOG_ERROR, "All matching ports in use"); + else + ulog (LOG_ERROR, "No matching ports"); + return FALSE; + } + else if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + if (s.flocked) + { + (void) fconn_unlock (&sconn); + uconn_free (&sconn); + } + return FALSE; + } + } + + if (! fconn_open (&sconn, qsys->uuconf_ibaud, qsys->uuconf_ihighbaud, + FALSE)) + { + terr = STATUS_PORT_FAILED; + fret = FALSE; + } + else + { + struct uuconf_dialer *qdialer; + struct uuconf_dialer sdialer; + enum tdialerfound tdialer; + + if (qsys->uuconf_zalternate == NULL) + ulog (LOG_NORMAL, "Calling system %s (port %s)", qsys->uuconf_zname, + zLdevice == NULL ? (char *) "unknown" : zLdevice); + else + ulog (LOG_NORMAL, "Calling system %s (alternate %s, port %s)", + qsys->uuconf_zname, qsys->uuconf_zalternate, + zLdevice == NULL ? (char *) "unknown" : zLdevice); + + qdialer = NULL; + + if (! fconn_dial (&sconn, puuconf, qsys, qsys->uuconf_zphone, + &sdialer, &tdialer)) + { + terr = STATUS_DIAL_FAILED; + fret = FALSE; + } + else + { + qdaemon->qconn = &sconn; + if (tdialer == DIALERFOUND_FALSE) + qdialer = NULL; + else + qdialer = &sdialer; + fret = fdo_call (qdaemon, qstat, qdialer, pfcalled, &terr); + } + + (void) fconn_close (&sconn, puuconf, qdialer, fret); + + if (tdialer == DIALERFOUND_FREE) + (void) uuconf_dialer_free (puuconf, &sdialer); + } + + if (! fret) + { + DEBUG_MESSAGE2 (DEBUG_HANDSHAKE, "Call failed: %d (%s)", + (int) terr, azStatus[(int) terr]); + qstat->ttype = terr; + qstat->cretries++; + qstat->ilast = ixsysdep_time ((long *) NULL); + if (cretry == 0) + qstat->cwait = CRETRY_WAIT (qstat->cretries); + else + qstat->cwait = cretry * 60; + (void) fsysdep_set_status (qsys, qstat); + } + + (void) fconn_unlock (&sconn); + uconn_free (&sconn); + + if (qport == NULL) + (void) uuconf_port_free (puuconf, &sport); + + return fret; +} + +/* Do the actual work of calling another system. The qsys argument is + the system to call, the qconn argument is the connection to use, + the qstat argument holds the current status of the ssystem, and the + qdialer argument holds the dialer being used (it may be NULL). If + we log in successfully, set *pfcalled to TRUE; this is used to + distinguish a failed dial from a failure during the call. If an + error occurs *pterr is set to the status type to record. */ + +static boolean +fdo_call (qdaemon, qstat, qdialer, pfcalled, pterr) + struct sdaemon *qdaemon; + struct sstatus *qstat; + const struct uuconf_dialer *qdialer; + boolean *pfcalled; + enum tstatus_type *pterr; +{ + pointer puuconf; + const struct uuconf_system *qsys; + struct sconnection *qconn; + const char *zport; + int iuuconf; + char *zstr; + long istart_time; + char *zlog; + + puuconf = qdaemon->puuconf; + qsys = qdaemon->qsys; + qconn = qdaemon->qconn; + + *pterr = STATUS_LOGIN_FAILED; + + if (qconn->qport == NULL) + zport = "unknown"; + else + zport = qconn->qport->uuconf_zname; + if (! fchat (qconn, puuconf, &qsys->uuconf_schat, qsys, + (const struct uuconf_dialer *) NULL, + (const char *) NULL, FALSE, zport, + iconn_baud (qconn))) + return FALSE; + + *pfcalled = TRUE; + istart_time = ixsysdep_time ((long *) NULL); + + *pterr = STATUS_HANDSHAKE_FAILED; + + /* We should now see "Shere" from the other system. Newer systems + send "Shere=foo" where foo is the remote name. */ + zstr = zget_uucp_cmd (qconn, TRUE); + if (zstr == NULL) + return FALSE; + + if (strncmp (zstr, "Shere", 5) != 0) + { + ulog (LOG_ERROR, "Bad initialization string"); + ubuffree (zstr); + return FALSE; + } + + ulog (LOG_NORMAL, "Login successful"); + + qstat->ttype = STATUS_TALKING; + qstat->ilast = ixsysdep_time ((long *) NULL); + qstat->cretries = 0; + qstat->cwait = 0; + if (! fsysdep_set_status (qsys, qstat)) + return FALSE; + + if (zstr[5] == '=') + { + const char *zheresys; + size_t clen; + int icmp; + + /* Some UUCP packages only provide seven characters in the Shere + machine name. Others only provide fourteen. */ + zheresys = zstr + 6; + clen = strlen (zheresys); + if (clen == 7 || clen == 14) + icmp = strncmp (zheresys, qsys->uuconf_zname, clen); + else + icmp = strcmp (zheresys, qsys->uuconf_zname); + if (icmp != 0) + { + if (qsys->uuconf_pzalias != NULL) + { + char **pz; + + for (pz = qsys->uuconf_pzalias; *pz != NULL; pz++) + { + if (clen == 7 || clen == 14) + icmp = strncmp (zheresys, *pz, clen); + else + icmp = strcmp (zheresys, *pz); + if (icmp == 0) + break; + } + } + if (icmp != 0) + { + ulog (LOG_ERROR, "Called wrong system (%s)", zheresys); + ubuffree (zstr); + return FALSE; + } + } + } +#if DEBUG > 1 + else if (zstr[5] != '\0') + DEBUG_MESSAGE1 (DEBUG_HANDSHAKE, + "fdo_call: Strange Shere: %s", zstr); +#endif + + ubuffree (zstr); + + /* We now send "S" name switches, where name is our UUCP name. If + we are using sequence numbers with this system, we send a -Q + argument with the sequence number. If the call-timegrade command + was used, we send a -p argument and a -vgrade= argument with the + grade to send us (we send both argument to make it more likely + that one is recognized). We always send a -N (for new) switch + indicating what new features we support. */ + { + long ival; + char bgrade; + char *zsend; + boolean fret; + + /* Determine the grade we should request of the other system. A + '\0' means that no restrictions have been made. */ + if (! ftimespan_match (qsys->uuconf_qcalltimegrade, &ival, + (int *) NULL)) + bgrade = '\0'; + else + bgrade = (char) ival; + + /* Determine the name we will call ourselves. */ + if (qsys->uuconf_zlocalname != NULL) + qdaemon->zlocalname = qsys->uuconf_zlocalname; + else + { + iuuconf = uuconf_localname (puuconf, &qdaemon->zlocalname); + if (iuuconf == UUCONF_NOT_FOUND) + { + qdaemon->zlocalname = zsysdep_localname (); + if (qdaemon->zlocalname == NULL) + return FALSE; + } + else if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + return FALSE; + } + } + + zsend = zbufalc (strlen (qdaemon->zlocalname) + 70); + if (! qsys->uuconf_fsequence) + { + if (bgrade == '\0') + sprintf (zsend, "S%s -R -N0%o", qdaemon->zlocalname, + (unsigned int) (FEATURE_SIZES + | FEATURE_EXEC + | FEATURE_RESTART)); + else + sprintf (zsend, "S%s -p%c -vgrade=%c -R -N0%o", + qdaemon->zlocalname, bgrade, bgrade, + (unsigned int) (FEATURE_SIZES + | FEATURE_EXEC + | FEATURE_RESTART)); + } + else + { + long iseq; + + iseq = ixsysdep_get_sequence (qsys); + if (iseq < 0) + return FALSE; + if (bgrade == '\0') + sprintf (zsend, "S%s -Q%ld -R -N0%o", qdaemon->zlocalname, iseq, + (unsigned int) (FEATURE_SIZES + | FEATURE_EXEC + | FEATURE_RESTART)); + else + sprintf (zsend, "S%s -Q%ld -p%c -vgrade=%c -R -N0%o", + qdaemon->zlocalname, iseq, bgrade, bgrade, + (unsigned int) (FEATURE_SIZES + | FEATURE_EXEC + | FEATURE_RESTART)); + } + + fret = fsend_uucp_cmd (qconn, zsend); + ubuffree (zsend); + if (! fret) + return FALSE; + } + + /* Now we should see ROK or Rreason where reason gives a cryptic + reason for failure. If we are talking to a counterpart, we will + get back ROKN, possibly with a feature bitfield attached. */ + zstr = zget_uucp_cmd (qconn, TRUE); + if (zstr == NULL) + return FALSE; + + if (zstr[0] != 'R') + { + ulog (LOG_ERROR, "Bad reponse to handshake string (%s)", + zstr); + ubuffree (zstr); + return FALSE; + } + + if (strncmp (zstr + 1, "OKN", sizeof "OKN" - 1) == 0) + { + if (zstr[sizeof "ROKN" - 1] == '\0') + qdaemon->ifeatures |= FEATURE_SIZES | FEATURE_V103; + else + qdaemon->ifeatures |= (int) strtol (zstr + sizeof "ROKN" - 1, + (char **) NULL, 0); + } + else if (strncmp (zstr + 1, "OK", sizeof "OK" - 1) == 0) + { + if (zstr[sizeof "ROK" - 1] != '\0') + { + char *zopt; + + /* SVR4 UUCP returns options following the ROK string. */ + zopt = zstr + sizeof "ROK" - 1; + while (*zopt != '\0') + { + char b; + long c; + char *zend; + + b = *zopt++; + if (isspace (b) || b != '-') + continue; + switch (*zopt) + { + case 'R': + qdaemon->ifeatures |= (FEATURE_RESTART + | FEATURE_SVR4 + | FEATURE_SIZES); + break; + case 'U': + c = strtol (zopt, &zend, 0); + if (c > 0 && c <= LONG_MAX / (long) 512) + qdaemon->cmax_receive = c * (long) 512; + zopt = zend; + break; + } + while (*zopt != '\0' && ! isspace (*zopt)) + ++zopt; + } + } + } + else if (strcmp (zstr + 1, "CB") == 0) + { + ulog (LOG_NORMAL, "Remote system will call back"); + qstat->ttype = STATUS_COMPLETE; + (void) fsysdep_set_status (qsys, qstat); + ubuffree (zstr); + return TRUE; + } + else + { + ulog (LOG_ERROR, "Handshake failed (%s)", zstr + 1); + ubuffree (zstr); + return FALSE; + } + + ubuffree (zstr); + + /* The slave should now send \020Pprotos\0 where protos is a list of + supported protocols. Each protocol is a single character. */ + zstr = zget_uucp_cmd (qconn, TRUE); + if (zstr == NULL) + return FALSE; + + if (zstr[0] != 'P') + { + ulog (LOG_ERROR, "Bad protocol handshake (%s)", zstr); + ubuffree (zstr); + return FALSE; + } + + /* Determine the reliability characteristics of the connection by + combining information for the port and the dialer. If we have no + information, default to a reliable eight-bit full-duplex + connection. */ + if (qconn->qport != NULL + && (qconn->qport->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0) + qdaemon->ireliable = qconn->qport->uuconf_ireliable; + if (qdialer != NULL + && (qdialer->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0) + { + if (qdaemon->ireliable != 0) + qdaemon->ireliable &= qdialer->uuconf_ireliable; + else + qdaemon->ireliable = qdialer->uuconf_ireliable; + } + if (qdaemon->ireliable == 0) + qdaemon->ireliable = (UUCONF_RELIABLE_RELIABLE + | UUCONF_RELIABLE_EIGHT + | UUCONF_RELIABLE_FULLDUPLEX + | UUCONF_RELIABLE_SPECIFIED); + + /* Now decide which protocol to use. The system and the port may + have their own list of protocols. */ + { + int i; + char ab[5]; + + i = CPROTOCOLS; + if (qsys->uuconf_zprotocols != NULL + || (qconn->qport != NULL + && qconn->qport->uuconf_zprotocols != NULL)) + { + const char *zproto; + + if (qsys->uuconf_zprotocols != NULL) + zproto = qsys->uuconf_zprotocols; + else + zproto = qconn->qport->uuconf_zprotocols; + for (; *zproto != '\0'; zproto++) + { + if (strchr (zstr + 1, *zproto) != NULL) + { + for (i = 0; i < CPROTOCOLS; i++) + if (asProtocols[i].bname == *zproto) + break; + if (i < CPROTOCOLS) + break; + } + } + } + else + { + /* If neither the system nor the port specified a list of + protocols, we want only protocols that match the known + reliability of the dialer and the port. */ + for (i = 0; i < CPROTOCOLS; i++) + { + int ipr; + + ipr = asProtocols[i].ireliable; + if ((ipr & qdaemon->ireliable) != ipr) + continue; + if (strchr (zstr + 1, asProtocols[i].bname) != NULL) + break; + } + } + + ubuffree (zstr); + + if (i >= CPROTOCOLS) + { + (void) fsend_uucp_cmd (qconn, "UN"); + ulog (LOG_ERROR, "No mutually supported protocols"); + return FALSE; + } + + qdaemon->qproto = &asProtocols[i]; + + sprintf (ab, "U%c", qdaemon->qproto->bname); + if (! fsend_uucp_cmd (qconn, ab)) + return FALSE; + } + + /* Run any protocol parameter commands. */ + if (qdaemon->qproto->qcmds != NULL) + { + if (qsys->uuconf_qproto_params != NULL) + uapply_proto_params (puuconf, qdaemon->qproto->bname, + qdaemon->qproto->qcmds, + qsys->uuconf_qproto_params); + if (qconn->qport != NULL + && qconn->qport->uuconf_qproto_params != NULL) + uapply_proto_params (puuconf, qdaemon->qproto->bname, + qdaemon->qproto->qcmds, + qconn->qport->uuconf_qproto_params); + if (qdialer != NULL + && qdialer->uuconf_qproto_params != NULL) + uapply_proto_params (puuconf, qdaemon->qproto->bname, + qdaemon->qproto->qcmds, + qdialer->uuconf_qproto_params); + } + + /* Turn on the selected protocol. */ + if (! (*qdaemon->qproto->pfstart) (qdaemon, &zlog)) + return FALSE; + if (zlog == NULL) + { + zlog = zbufalc (sizeof "protocol ''" + 1); + sprintf (zlog, "protocol '%c'", qdaemon->qproto->bname); + } + ulog (LOG_NORMAL, "Handshake successful (%s)", zlog); + ubuffree (zlog); + + *pterr = STATUS_FAILED; + + { + boolean fret; + long iend_time; + + fret = floop (qdaemon); + + /* Now send the hangup message. As the caller, we send six O's + and expect to receive seven O's. We send the six O's twice to + help the other side. We don't worry about errors here. */ + if (fsend_uucp_cmd (qconn, "OOOOOO") + && fsend_uucp_cmd (qconn, "OOOOOO")) + { + int i, fdone; + + /* We look for the remote hangup string to ensure that the + modem has sent out our hangup string. This is only + necessary because some versions of UUCP complain if they + don't get the hangup string. The remote site should send 7 + O's, but some versions of UUCP only send 6. We look for + the string several times because supposedly some + implementations send some garbage after the last packet but + before the hangup string. */ + for (i = 0; i < 25; i++) + { + zstr = zget_uucp_cmd (qconn, FALSE); + if (zstr == NULL) + break; + fdone = strstr (zstr, "OOOOOO") != NULL; + ubuffree (zstr); + if (fdone) + break; + } + } + + iend_time = ixsysdep_time ((long *) NULL); + + ulog (LOG_NORMAL, "Call complete (%ld seconds)", + iend_time - istart_time); + + if (fret) + { + qstat->ttype = STATUS_COMPLETE; + qstat->ilast = iend_time; + (void) fsysdep_set_status (qsys, qstat); + } + + return fret; + } +} + +/* This routine is called via uuconf_find_port when a matching port is + found. It tries to lock the port. If it fails, it returns + UUCONF_NOT_FOUND to force uuconf_find_port to continue searching + for the next matching port. */ + +static int +iuport_lock (qport, pinfo) + struct uuconf_port *qport; + pointer pinfo; +{ + struct spass *q = (struct spass *) pinfo; + + q->fmatched = TRUE; + + if (! fconn_init (qport, q->qconn)) + return UUCONF_NOT_FOUND; + else if (! fconn_lock (q->qconn, FALSE)) + { + uconn_free (q->qconn); + return UUCONF_NOT_FOUND; + } + else + { + q->flocked = TRUE; + return UUCONF_SUCCESS; + } +} + +/* Prompt for a login name and a password, and run as the slave. */ + +static boolean +flogin_prompt (puuconf, qconn) + pointer puuconf; + struct sconnection *qconn; +{ + char *zuser, *zpass; + boolean fret; + int iuuconf; + + DEBUG_MESSAGE0 (DEBUG_HANDSHAKE, "flogin_prompt: Waiting for login"); + + zuser = NULL; + do + { + ubuffree (zuser); + if (! fconn_write (qconn, "login: ", sizeof "login: " - 1)) + return FALSE; + zuser = zget_typed_line (qconn); + } + while (zuser != NULL && *zuser == '\0'); + + if (zuser == NULL) + return TRUE; + + if (! fconn_write (qconn, "Password:", sizeof "Password:" - 1)) + { + ubuffree (zuser); + return FALSE; + } + + zpass = zget_typed_line (qconn); + if (zpass == NULL) + { + ubuffree (zuser); + return TRUE; + } + + fret = TRUE; + + iuuconf = uuconf_callin (puuconf, zuser, zpass); + ubuffree (zpass); + if (iuuconf == UUCONF_NOT_FOUND) + ulog (LOG_ERROR, "Bad login"); + else if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + fret = FALSE; + } + else + { +#if DEBUG > 1 + int iholddebug; +#endif + + /* We ignore the return value of faccept_call because we really + don't care whether the call succeeded or not. We are going + to reset the port anyhow. */ +#if DEBUG > 1 + iholddebug = iDebug; +#endif + (void) faccept_call (puuconf, zuser, qconn, (const char **) NULL); +#if DEBUG > 1 + iDebug = iholddebug; +#endif + } + + ubuffree (zuser); + + return fret; +} + +/* Accept a call from a remote system. If pqsys is not NULL, *pqsys + will be set to the system that called in if known. */ + +static boolean +faccept_call (puuconf, zlogin, qconn, pzsystem) + pointer puuconf; + const char *zlogin; + struct sconnection *qconn; + const char **pzsystem; +{ + long istart_time; + const char *zport; + struct uuconf_port *qport; + struct uuconf_port sport; + int iuuconf; + struct uuconf_dialer *qdialer; + struct uuconf_dialer sdialer; + boolean ftcp_port; + char *zsend, *zspace; + boolean fret; + char *zstr; + struct uuconf_system ssys; + const struct uuconf_system *qsys; + const struct uuconf_system *qany; + char *zloc; + struct sstatus sstat; + boolean fgotseq, fgotn; + int i; + char *zlog; + char *zgrade; + + if (pzsystem != NULL) + *pzsystem = NULL; + + ulog (LOG_NORMAL, "Incoming call (login %s port %s)", zlogin, + zLdevice == NULL ? (char *) "unknown" : zLdevice); + + istart_time = ixsysdep_time ((long *) NULL); + + /* Figure out protocol parameters determined by the port. If no + port was specified we're reading standard input, so try to get + the port name and read information from the port file. We only + use the port information to get protocol parameters; we don't + want to start treating the port as though it were a modem, for + example. */ + if (qconn->qport != NULL) + { + qport = qconn->qport; + zport = qport->uuconf_zname; + ftcp_port = FALSE; + } + else + { + zport = zsysdep_port_name (&ftcp_port); + if (zport == NULL) + { + qport = NULL; + zport = "unknown"; + } + else + { + iuuconf = uuconf_find_port (puuconf, zport, (long) 0, (long) 0, + (int (*) P((struct uuconf_port *, + pointer pinfo))) NULL, + (pointer) NULL, + &sport); + if (iuuconf == UUCONF_NOT_FOUND) + qport = NULL; + else if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + return FALSE; + } + else + qport = &sport; + } + } + + /* If we've managed to figure out that this is a modem port, now try + to get protocol parameters from the dialer. */ + qdialer = NULL; + if (qport != NULL) + { + if (qport->uuconf_ttype == UUCONF_PORTTYPE_MODEM) + { + if (qport->uuconf_u.uuconf_smodem.uuconf_pzdialer != NULL) + { + const char *zdialer; + + zdialer = qport->uuconf_u.uuconf_smodem.uuconf_pzdialer[0]; + iuuconf = uuconf_dialer_info (puuconf, zdialer, &sdialer); + if (iuuconf == UUCONF_SUCCESS) + qdialer = &sdialer; + } + else + qdialer = qport->uuconf_u.uuconf_smodem.uuconf_qdialer; + } + else if (qport->uuconf_ttype == UUCONF_PORTTYPE_TCP + || (qport->uuconf_ttype == UUCONF_PORTTYPE_TLI + && (qport->uuconf_ireliable + & UUCONF_RELIABLE_SPECIFIED) == 0)) + ftcp_port = TRUE; + } + + sDaemon.puuconf = puuconf; + sDaemon.qsys = NULL; + sDaemon.zlocalname = NULL; + sDaemon.qconn = qconn; + sDaemon.qproto = NULL; + sDaemon.clocal_size = -1; + sDaemon.cremote_size = -1; + sDaemon.cmax_ever = -2; + sDaemon.cmax_receive = -1; + sDaemon.ifeatures = 0; + sDaemon.frequest_hangup = FALSE; + sDaemon.fhangup_requested = FALSE; + sDaemon.fhangup = FALSE; + sDaemon.fmaster = FALSE; + sDaemon.fcaller = FALSE; + sDaemon.ireliable = 0; + sDaemon.bgrade = UUCONF_GRADE_LOW; + + /* Get the local name to use. If uuconf_login_localname returns a + value, it is not always freed up, although it should be. */ + iuuconf = uuconf_login_localname (puuconf, zlogin, &zloc); + if (iuuconf == UUCONF_SUCCESS) + sDaemon.zlocalname = zloc; + else if (iuuconf == UUCONF_NOT_FOUND) + { + sDaemon.zlocalname = zsysdep_localname (); + if (sDaemon.zlocalname == NULL) + return FALSE; + } + else + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + return FALSE; + } + + /* Tell the remote system who we are. */ + zsend = zbufalc (strlen (sDaemon.zlocalname) + sizeof "Shere="); + sprintf (zsend, "Shere=%s", sDaemon.zlocalname); + fret = fsend_uucp_cmd (qconn, zsend); + ubuffree (zsend); + if (! fret) + return FALSE; + + zstr = zget_uucp_cmd (qconn, TRUE); + if (zstr == NULL) + return FALSE; + + if (zstr[0] != 'S') + { + ulog (LOG_ERROR, "Bad introduction string"); + ubuffree (zstr); + return FALSE; + } + + zspace = strchr (zstr, ' '); + if (zspace != NULL) + *zspace = '\0'; + + iuuconf = uuconf_system_info (puuconf, zstr + 1, &ssys); + if (iuuconf == UUCONF_NOT_FOUND) + { + char *zscript; + + /* Run the remote.unknown script, if appropriate. */ + iuuconf = uuconf_remote_unknown (puuconf, &zscript); + if (iuuconf == UUCONF_SUCCESS) + { + if (! fsysdep_unknown_caller (zscript, zstr + 1)) + { + xfree ((pointer) zscript); + (void) fsend_uucp_cmd (qconn, "RYou are unknown to me"); + ubuffree (zstr); + return FALSE; + } + xfree ((pointer) zscript); + } + else if (iuuconf != UUCONF_NOT_FOUND) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + ubuffree (zstr); + return FALSE; + } + + if (! funknown_system (puuconf, zstr + 1, &ssys)) + { + (void) fsend_uucp_cmd (qconn, "RYou are unknown to me"); + ulog (LOG_ERROR, "Call from unknown system %s", zstr + 1); + ubuffree (zstr); + return FALSE; + } + } + else if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + ubuffree (zstr); + return FALSE; + } + + qany = NULL; + for (qsys = &ssys; qsys != NULL; qsys = qsys->uuconf_qalternate) + { + if (! qsys->uuconf_fcalled) + continue; + + if (qsys->uuconf_zcalled_login == NULL + || strcmp (qsys->uuconf_zcalled_login, "ANY") == 0) + { + if (qany == NULL) + qany = qsys; + } + else if (strcmp (qsys->uuconf_zcalled_login, zlogin) == 0) + break; + } + + if (qsys == NULL && qany != NULL) + { + iuuconf = uuconf_validate (puuconf, qany, zlogin); + if (iuuconf == UUCONF_SUCCESS) + qsys = qany; + else if (iuuconf != UUCONF_NOT_FOUND) + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + ubuffree (zstr); + return FALSE; + } + } + + if (qsys == NULL) + { + (void) fsend_uucp_cmd (qconn, "RLOGIN"); + ulog (LOG_ERROR, "System %s used wrong login name %s", + zstr + 1, zlogin); + ubuffree (zstr); + return FALSE; + } + + sDaemon.qsys = qsys; + + if (pzsystem != NULL) + *pzsystem = zbufcpy (qsys->uuconf_zname); + + ulog_system (qsys->uuconf_zname); + +#if DEBUG > 1 + if (qsys->uuconf_zdebug != NULL) + iDebug |= idebug_parse (qsys->uuconf_zdebug); +#endif + + /* See if we are supposed to call the system back. This will queue + up an empty command. It would be better to actually call back + directly at this point as well. */ + if (qsys->uuconf_fcallback) + { + (void) fsend_uucp_cmd (qconn, "RCB"); + ulog (LOG_NORMAL, "Will call back"); + + /* Clear any existing status. */ + sstat.ttype = STATUS_COMPLETE; + sstat.cretries = 0; + sstat.ilast = ixsysdep_time ((long *) NULL); + sstat.cwait = 0; + (void) fsysdep_set_status (qsys, &sstat); + + ubuffree (zsysdep_spool_commands (qsys, UUCONF_GRADE_HIGH, 0, + (const struct scmd *) NULL)); + ubuffree (zstr); + return TRUE; + } + + /* We only permit one call at a time from a remote system. Lock it. */ + if (! fsysdep_lock_system (qsys)) + { + (void) fsend_uucp_cmd (qconn, "RLCK"); + ulog (LOG_ERROR, "System already locked"); + ubuffree (zstr); + return FALSE; + } + sLocked_system = *qsys; + fLocked_system = TRUE; + + /* Set the system status. We don't care what the status was before. + We also don't want to kill the conversation just because we can't + output the .Status file, so we ignore any errors. */ + sstat.ttype = STATUS_TALKING; + sstat.cretries = 0; + sstat.ilast = ixsysdep_time ((long *) NULL); + sstat.cwait = 0; + (void) fsysdep_set_status (qsys, &sstat); + + /* Check the arguments of the remote system, if any. */ + fgotseq = FALSE; + fgotn = FALSE; + if (zspace != NULL) + { + char **paz; + char **pzset; + + ++zspace; + + /* Break the introduction line up into arguments. */ + paz = (char **) xmalloc ((strlen (zspace) / 2 + 2) * sizeof (char *)); + pzset = paz; + *pzset++ = NULL; + while (TRUE) + { + while (*zspace != '\0' && isspace (BUCHAR (*zspace))) + ++zspace; + if (*zspace == '\0') + break; + *pzset++ = zspace; + ++zspace; + while (*zspace != '\0' && ! isspace (BUCHAR (*zspace))) + ++zspace; + if (*zspace == '\0') + break; + *zspace++ = '\0'; + } + + if (pzset != paz + 1) + { + int iopt; + + *pzset = NULL; + + /* We are going to use getopt to parse the arguments. We + must clear optind to force getopt to reinitialize, and + clear opterr to prevent getopt from printing an error + message. This approach assumes we are using the GNU + getopt, which is distributed with the program anyhow. */ + optind = 0; + opterr = 0; + + while ((iopt = getopt (pzset - paz, paz, + "N::p:Q:RU:v:x:")) != EOF) + { + long iseq; + long c; + char b; + int iwant; + + switch (iopt) + { + case 'N': + /* This is used to indicate support for Taylor UUCP + extensions. An plain -N mean support for size + negotiation. If -N is followed by a number (with + no intervening space), the number is a bit field + of feature flags as defined in trans.h. Note + that the argument may start with 0x for hex or 0 + for octal. */ + fgotn = TRUE; + if (optarg == NULL) + sDaemon.ifeatures |= FEATURE_SIZES | FEATURE_V103; + else + sDaemon.ifeatures |= (int) strtol (optarg, + (char **) NULL, + 0); + break; + + case 'p': + /* The argument is the lowest grade of work the + local system should send. */ + if (UUCONF_GRADE_LEGAL (optarg[0])) + sDaemon.bgrade = optarg[0]; + break; + + case 'Q': + /* The conversation sequence number. */ + iseq = strtol (optarg, (char **) NULL, 10); + if (qsys->uuconf_fsequence + && iseq != ixsysdep_get_sequence (qsys)) + { + (void) fsend_uucp_cmd (qconn, "RBADSEQ"); + ulog (LOG_ERROR, "Out of sequence call rejected"); + sstat.ttype = STATUS_FAILED; + (void) fsysdep_set_status (qsys, &sstat); + xfree ((pointer) paz); + ubuffree (zstr); + return FALSE; + } + fgotseq = TRUE; + break; + + case 'R': + /* The remote system supports file restart. */ + sDaemon.ifeatures |= FEATURE_RESTART; + break; + + case 'U': + /* The maximum file size the remote system is + prepared to received, in blocks where each block + is 512 bytes. */ + c = strtol (optarg, (char **) NULL, 0); + if (c > 0 && c < LONG_MAX / (long) 512) + sDaemon.cmax_receive = c * (long) 512; + break; + + case 'v': + /* -vgrade=X can be used to set the lowest grade of + work the local system should send. */ + if (strncmp (optarg, "grade=", sizeof "grade=" - 1) == 0) + { + b = optarg[sizeof "grade=" - 1]; + if (UUCONF_GRADE_LEGAL (b)) + sDaemon.bgrade = b; + } + break; + + case 'x': + iwant = (int) strtol (optarg, (char **) NULL, 10); +#if DEBUG > 1 + if (iwant <= 9) + iwant = (1 << iwant) - 1; + if (qsys->uuconf_zmax_remote_debug != NULL) + iwant &= idebug_parse (qsys->uuconf_zmax_remote_debug); + if ((iDebug | iwant) != iDebug) + { + iDebug |= iwant; + ulog (LOG_NORMAL, "Setting debugging mode to 0%o", + iDebug); + } +#endif + break; + + default: + break; + } + } + } + + xfree ((pointer) paz); + } + + ubuffree (zstr); + + if (qsys->uuconf_fsequence && ! fgotseq) + { + (void) fsend_uucp_cmd (qconn, "RBADSEQ"); + ulog (LOG_ERROR, "No sequence number (call rejected)"); + sstat.ttype = STATUS_FAILED; + (void) fsysdep_set_status (qsys, &sstat); + return FALSE; + } + + /* We recognized the system, and the sequence number (if any) was + OK. Send an ROK, and send a list of protocols. If we got the -N + switch, send ROKN to confirm it; if the -N switch was followed by + a feature bitfield, return our own feature bitfield. */ + { + char ab[20]; + const char *zreply; + + if (! fgotn) + { + if ((sDaemon.ifeatures & FEATURE_RESTART) == 0) + zreply = "ROK"; + else + { + /* We got -R without -N, so assume that this is SVR4 UUCP. + SVR4 UUCP expects ROK -R to signal support for file + restart. */ + sDaemon.ifeatures |= FEATURE_SVR4 | FEATURE_SIZES; + zreply = "ROK -R"; + } + } + else if ((sDaemon.ifeatures & FEATURE_V103) != 0) + zreply = "ROKN"; + else + { + sprintf (ab, "ROKN0%o", + (unsigned int) (FEATURE_SIZES + | FEATURE_EXEC + | FEATURE_RESTART)); + zreply = ab; + } + if (! fsend_uucp_cmd (qconn, zreply)) + { + sstat.ttype = STATUS_FAILED; + (void) fsysdep_set_status (qsys, &sstat); + return FALSE; + } + } + + /* Determine the reliability of the connection based on the + reliability of the port and the dialer. If we have no + information, default to a reliable eight-bit full-duplex + connection. */ + if (ftcp_port) + sDaemon.ireliable = (UUCONF_RELIABLE_SPECIFIED + | UUCONF_RELIABLE_ENDTOEND + | UUCONF_RELIABLE_RELIABLE + | UUCONF_RELIABLE_EIGHT + | UUCONF_RELIABLE_FULLDUPLEX); + else + { + if (qport != NULL + && (qport->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0) + sDaemon.ireliable = qport->uuconf_ireliable; + if (qdialer != NULL + && (qdialer->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0) + { + if (sDaemon.ireliable != 0) + sDaemon.ireliable &= qdialer->uuconf_ireliable; + else + sDaemon.ireliable = qdialer->uuconf_ireliable; + } + if (sDaemon.ireliable == 0) + sDaemon.ireliable = (UUCONF_RELIABLE_RELIABLE + | UUCONF_RELIABLE_EIGHT + | UUCONF_RELIABLE_FULLDUPLEX + | UUCONF_RELIABLE_SPECIFIED); + } + + if (qsys->uuconf_zprotocols != NULL || + (qport != NULL && qport->uuconf_zprotocols != NULL)) + { + const char *zprotos; + + if (qsys->uuconf_zprotocols != NULL) + zprotos = qsys->uuconf_zprotocols; + else + zprotos = qport->uuconf_zprotocols; + zsend = zbufalc (strlen (zprotos) + 2); + sprintf (zsend, "P%s", zprotos); + } + else + { + char *zset; + + zsend = zbufalc (CPROTOCOLS + 2); + zset = zsend; + *zset++ = 'P'; + + /* If the system did not specify a list of protocols, we want + only protocols that match the known reliability of the dialer + and the port. */ + for (i = 0; i < CPROTOCOLS; i++) + { + int ipr; + + ipr = asProtocols[i].ireliable; + if ((ipr & sDaemon.ireliable) != ipr) + continue; + *zset++ = asProtocols[i].bname; + } + *zset = '\0'; + } + + fret = fsend_uucp_cmd (qconn, zsend); + ubuffree (zsend); + if (! fret) + { + sstat.ttype = STATUS_FAILED; + (void) fsysdep_set_status (qsys, &sstat); + return FALSE; + } + + /* The master will now send back the selected protocol. */ + zstr = zget_uucp_cmd (qconn, TRUE); + if (zstr == NULL) + { + sstat.ttype = STATUS_FAILED; + (void) fsysdep_set_status (qsys, &sstat); + return FALSE; + } + + if (zstr[0] != 'U' || zstr[2] != '\0') + { + ulog (LOG_ERROR, "Bad protocol response string"); + sstat.ttype = STATUS_FAILED; + (void) fsysdep_set_status (qsys, &sstat); + ubuffree (zstr); + return FALSE; + } + + if (zstr[1] == 'N') + { + ulog (LOG_ERROR, "No supported protocol"); + sstat.ttype = STATUS_FAILED; + (void) fsysdep_set_status (qsys, &sstat); + ubuffree (zstr); + return FALSE; + } + + for (i = 0; i < CPROTOCOLS; i++) + if (asProtocols[i].bname == zstr[1]) + break; + + ubuffree (zstr); + + if (i >= CPROTOCOLS) + { + ulog (LOG_ERROR, "No supported protocol"); + sstat.ttype = STATUS_FAILED; + (void) fsysdep_set_status (qsys, &sstat); + return FALSE; + } + + sDaemon.qproto = &asProtocols[i]; + + /* Run the chat script for when a call is received. */ + if (! fchat (qconn, puuconf, &qsys->uuconf_scalled_chat, qsys, + (const struct uuconf_dialer *) NULL, (const char *) NULL, + FALSE, zport, iconn_baud (qconn))) + { + sstat.ttype = STATUS_FAILED; + sstat.ilast = ixsysdep_time ((long *) NULL); + (void) fsysdep_set_status (qsys, &sstat); + return FALSE; + } + + /* Run any protocol parameter commands. */ + if (sDaemon.qproto->qcmds != NULL) + { + if (qsys->uuconf_qproto_params != NULL) + uapply_proto_params (puuconf, sDaemon.qproto->bname, + sDaemon.qproto->qcmds, + qsys->uuconf_qproto_params); + if (qport != NULL + && qport->uuconf_qproto_params != NULL) + uapply_proto_params (puuconf, sDaemon.qproto->bname, + sDaemon.qproto->qcmds, + qport->uuconf_qproto_params); + if (qdialer != NULL + && qdialer->uuconf_qproto_params != NULL) + uapply_proto_params (puuconf, sDaemon.qproto->bname, + sDaemon.qproto->qcmds, + qdialer->uuconf_qproto_params); + } + + /* We don't need the dialer information any more. */ + if (qdialer == &sdialer) + (void) uuconf_dialer_free (puuconf, &sdialer); + + /* Get any jobs queued for the system, and turn on the selected + protocol. */ + if (! fqueue (&sDaemon, (boolean *) NULL) + || ! (*sDaemon.qproto->pfstart) (&sDaemon, &zlog)) + { + uclear_queue (&sDaemon); + sstat.ttype = STATUS_FAILED; + sstat.ilast = ixsysdep_time ((long *) NULL); + (void) fsysdep_set_status (qsys, &sstat); + return FALSE; + } + + if (zlog == NULL) + { + zlog = zbufalc (sizeof "protocol ''" + 1); + sprintf (zlog, "protocol '%c'", sDaemon.qproto->bname); + } + + zgrade = zbufalc (sizeof "grade " + 1); + if (sDaemon.bgrade == UUCONF_GRADE_LOW) + *zgrade = '\0'; + else + sprintf (zgrade, "grade %c ", sDaemon.bgrade); + + /* If we are using HAVE_HDB_LOGGING, then the previous ``incoming + call'' message went to the general log, since we didn't know the + system name at that point. In that case, we repeat the port and + login names. */ +#if HAVE_HDB_LOGGING + ulog (LOG_NORMAL, "Handshake successful (login %s port %s %s%s)", + zlogin, + zLdevice == NULL ? "unknown" : zLdevice, + zgrade, zlog); +#else /* ! HAVE_HDB_LOGGING */ + ulog (LOG_NORMAL, "Handshake successful (%s%s)", zgrade, zlog); +#endif /* ! HAVE_HDB_LOGGING */ + + ubuffree (zlog); + ubuffree (zgrade); + + { + long iend_time; + + fret = floop (&sDaemon); + + /* Hangup. As the answerer, we send seven O's and expect to + receive six O's. We send the seven O's twice to help the other + side. We don't worry about errors here. */ + if (fsend_uucp_cmd (qconn, "OOOOOOO") + && fsend_uucp_cmd (qconn, "OOOOOOO")) + { + int fdone; + + /* We look for the remote hangup string to ensure that the + modem has sent out our hangup string. This is only + necessary because some versions of UUCP complain if they + don't get the hangup string. We look for the string + several times because supposedly some implementations send + some garbage after the last packet but before the hangup + string. */ + for (i = 0; i < 25; i++) + { + zstr = zget_uucp_cmd (qconn, FALSE); + if (zstr == NULL) + break; + fdone = strstr (zstr, "OOOOOO") != NULL; + ubuffree (zstr); + if (fdone) + break; + } + } + + iend_time = ixsysdep_time ((long *) NULL); + + ulog (LOG_NORMAL, "Call complete (%ld seconds)", + iend_time - istart_time); + + uclear_queue (&sDaemon); + + if (fret) + sstat.ttype = STATUS_COMPLETE; + else + sstat.ttype = STATUS_FAILED; + sstat.ilast = iend_time; + (void) fsysdep_set_status (qsys, &sstat); + + (void) uuconf_system_free (puuconf, &ssys); + if (qport == &sport) + (void) uuconf_port_free (puuconf, &sport); + xfree ((pointer) zloc); + + return fret; + } +} + +/* Apply protocol parameters, once we know the protocol. */ + +static void +uapply_proto_params (puuconf, bproto, qcmds, pas) + pointer puuconf; + int bproto; + struct uuconf_cmdtab *qcmds; + struct uuconf_proto_param *pas; +{ + struct uuconf_proto_param *qp; + + for (qp = pas; qp->uuconf_bproto != '\0'; qp++) + { + if (qp->uuconf_bproto == bproto) + { + struct uuconf_proto_param_entry *qe; + + for (qe = qp->uuconf_qentries; qe->uuconf_cargs > 0; qe++) + { + int iuuconf; + + iuuconf = uuconf_cmd_args (puuconf, qe->uuconf_cargs, + qe->uuconf_pzargs, qcmds, + (pointer) NULL, + (uuconf_cmdtabfn) NULL, 0, + (pointer) NULL); + if (UUCONF_ERROR_VALUE (iuuconf) != UUCONF_SUCCESS) + { + ulog (LOG_ERROR, "Error in %c protocol parameters", + bproto); + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + } + } + + break; + } + } +} + +/* Send a string to the other system beginning with a DLE + character and terminated with a null byte. This is only + used when no protocol is in force. */ + +static boolean +fsend_uucp_cmd (qconn, z) + struct sconnection *qconn; + const char *z; +{ + size_t cwrite; + char *zalc; + boolean fret; + + DEBUG_MESSAGE1 (DEBUG_HANDSHAKE, "fsend_uucp_cmd: Sending \"%s\"", z); + + cwrite = strlen (z) + 2; + + zalc = zbufalc (cwrite); + zalc[0] = '\020'; + memcpy (zalc + 1, z, cwrite - 1); + + fret = fconn_write (qconn, zalc, cwrite); + ubuffree (zalc); + return fret; +} + +/* Get a UUCP command beginning with a DLE character and ending with a + null byte. This is only used when no protocol is in force. This + implementation has the potential of being seriously slow. It also + doesn't have any real error recovery. The frequired argument is + passed as TRUE if we need the string; we don't care that much if + we're closing down the connection anyhow. */ + +#define CTIMEOUT (120) +#define CSHORTTIMEOUT (10) +#define CINCREMENT (100) + +static char * +zget_uucp_cmd (qconn, frequired) + struct sconnection *qconn; + boolean frequired; +{ + char *zalc; + size_t calc; + size_t cgot; + boolean fintro; + long iendtime; + int ctimeout; +#if DEBUG > 1 + int cchars; + int iolddebug; +#endif + + iendtime = ixsysdep_time ((long *) NULL); + if (frequired) + iendtime += CTIMEOUT; + else + iendtime += CSHORTTIMEOUT; + +#if DEBUG > 1 + cchars = 0; + iolddebug = iDebug; + if (FDEBUGGING (DEBUG_HANDSHAKE)) + { + ulog (LOG_DEBUG_START, "zget_uucp_cmd: Got \""); + iDebug &=~ (DEBUG_INCOMING | DEBUG_PORT); + } +#endif + + zalc = NULL; + calc = 0; + cgot = 0; + fintro = FALSE; + while ((ctimeout = (int) (iendtime - ixsysdep_time ((long *) NULL))) > 0) + { + int b; + + b = breceive_char (qconn, ctimeout, frequired); + /* Now b == -1 on timeout, -2 on error. */ + if (b < 0) + { +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_HANDSHAKE)) + { + ulog (LOG_DEBUG_END, "\" (%s)", + b == -1 ? "timeout" : "error"); + iDebug = iolddebug; + } +#endif + if (b == -1 && frequired) + ulog (LOG_ERROR, "Timeout"); + ubuffree (zalc); + return NULL; + } + + /* Apparently some systems use parity on these strings, so we + strip the parity bit. This may need to be configurable at + some point, although only if system names can have eight bit + characters. */ + if (! isprint (BUCHAR (b))) + b &= 0x7f; + +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_HANDSHAKE)) + { + char ab[5]; + + ++cchars; + if (cchars > 60) + { + ulog (LOG_DEBUG_END, "\""); + ulog (LOG_DEBUG_START, "zget_uucp_cmd: Got \""); + cchars = 0; + } + (void) cdebug_char (ab, b); + ulog (LOG_DEBUG_CONTINUE, "%s", ab); + } +#endif + + if (! fintro) + { + if (b == '\020') + fintro = TRUE; + continue; + } + + /* If we see another DLE, something has gone wrong; continue + as though this were the first one we saw. */ + if (b == '\020') + { + cgot = 0; + continue; + } + + /* Some systems send a trailing \n on the Shere line. As far as + I can tell this line can never contain a \n, so this + modification should be safe enough. */ + if (b == '\r' || b == '\n') + b = '\0'; + + if (cgot >= calc) + { + char *znew; + + calc += CINCREMENT; + znew = zbufalc (calc); + memcpy (znew, zalc, cgot); + ubuffree (zalc); + zalc = znew; + } + + zalc[cgot] = (char) b; + ++cgot; + + if (b == '\0') + { +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_HANDSHAKE)) + { + ulog (LOG_DEBUG_END, "\""); + iDebug = iolddebug; + } +#endif + return zalc; + } + } + +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_HANDSHAKE)) + { + ulog (LOG_DEBUG_END, "\" (timeout)"); + iDebug = iolddebug; + } +#endif + + ubuffree (zalc); + + if (frequired) + ulog (LOG_ERROR, "Timeout"); + return NULL; +} + +/* Read a sequence of characters up to a newline or carriage return, and + return the line without the line terminating character. */ + +static char * +zget_typed_line (qconn) + struct sconnection *qconn; +{ + char *zalc; + size_t calc; + size_t cgot; + +#if DEBUG > 1 + int cchars; + int iolddebug; + + cchars = 0; + iolddebug = iDebug; + if (FDEBUGGING (DEBUG_CHAT)) + { + ulog (LOG_DEBUG_START, "zget_typed_line: Got \""); + iDebug &=~ (DEBUG_INCOMING | DEBUG_PORT); + } +#endif + + zalc = NULL; + calc = 0; + cgot = 0; + while (TRUE) + { + int b; + + b = breceive_char (qconn, CTIMEOUT, FALSE); + + /* Now b == -1 on timeout, -2 on error. */ + + if (b == -2 || FGOT_SIGNAL ()) + { +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_CHAT)) + { + ulog (LOG_DEBUG_END, "\" (error)"); + iDebug = iolddebug; + } +#endif + ubuffree (zalc); + return NULL; + } + + if (b == -1) + continue; + +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_CHAT)) + { + char ab[5]; + + ++cchars; + if (cchars > 60) + { + ulog (LOG_DEBUG_END, "\""); + ulog (LOG_DEBUG_START, "zget_typed_line: Got \""); + cchars = 0; + } + (void) cdebug_char (ab, b); + ulog (LOG_DEBUG_CONTINUE, "%s", ab); + } +#endif + + if (cgot >= calc) + { + char *znew; + + calc += CINCREMENT; + znew = zbufalc (calc); + memcpy (znew, zalc, cgot); + ubuffree (zalc); + zalc = znew; + } + + if (b == '\r' || b == '\n') + b = '\0'; + + zalc[cgot] = (char) b; + ++cgot; + + if (b == '\0') + { +#if DEBUG > 1 + if (FDEBUGGING (DEBUG_CHAT)) + { + ulog (LOG_DEBUG_END, "\""); + iDebug = iolddebug; + } +#endif + return zalc; + } + } +} diff --git a/gnu/libexec/uucp/uucico/xcmd.c b/gnu/libexec/uucp/uucico/xcmd.c new file mode 100644 index 0000000000000..cadf6383dff91 --- /dev/null +++ b/gnu/libexec/uucp/uucico/xcmd.c @@ -0,0 +1,396 @@ +/* xcmd.c + Routines to handle work requests. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + 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 of the + License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char xcmd_rcsid[] = "$Id: xcmd.c,v 1.1 1993/08/05 18:27:25 conklin Exp $"; +#endif + +#include <errno.h> + +#include "uudefs.h" +#include "uuconf.h" +#include "system.h" +#include "prot.h" +#include "trans.h" + +/* Local functions. */ + +static boolean flocal_xcmd_request P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); +static boolean flocal_xcmd_await_reply P((struct stransfer *qtrans, + struct sdaemon *qdaemon, + const char *zdata, size_t cdata)); +static boolean fremote_xcmd_reply P((struct stransfer *qtrans, + struct sdaemon *qdaemon)); + +/* Handle a local work request. We just set up the request for + transmission. */ + +boolean +flocal_xcmd_init (qdaemon, qcmd) + struct sdaemon *qdaemon; + struct scmd *qcmd; +{ + struct stransfer *qtrans; + + qtrans = qtransalc (qcmd); + qtrans->psendfn = flocal_xcmd_request; + + return fqueue_local (qdaemon, qtrans); +} + +/* Send the execution request to the remote system. */ + +static boolean +flocal_xcmd_request (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + size_t clen; + char *zsend; + boolean fret; + + ulog (LOG_NORMAL, "Requesting work: %s to %s", qtrans->s.zfrom, + qtrans->s.zto); + + /* We send the string + X from to user options + We put a dash in front of options. */ + clen = (strlen (qtrans->s.zfrom) + strlen (qtrans->s.zto) + + strlen (qtrans->s.zuser) + strlen (qtrans->s.zoptions) + 7); + zsend = zbufalc (clen); + sprintf (zsend, "X %s %s %s -%s", qtrans->s.zfrom, qtrans->s.zto, + qtrans->s.zuser, qtrans->s.zoptions); + + fret = (*qdaemon->qproto->pfsendcmd) (qdaemon, zsend, qtrans->ilocal, + qtrans->iremote); + ubuffree (zsend); + if (! fret) + { + utransfree (qtrans); + return FALSE; + } + + qtrans->fcmd = TRUE; + qtrans->precfn = flocal_xcmd_await_reply; + + return fqueue_receive (qdaemon, qtrans); +} + +/* Get a reply to an execution request from the remote system. */ + +/*ARGSUSED*/ +static boolean +flocal_xcmd_await_reply (qtrans, qdaemon, zdata, cdata) + struct stransfer *qtrans; + struct sdaemon *qdaemon; + const char *zdata; + size_t cdata; +{ + qtrans->precfn = NULL; + + if (zdata[0] != 'X' + || (zdata[1] != 'Y' && zdata[1] != 'N')) + { + ulog (LOG_ERROR, "Bad response to work request"); + utransfree (qtrans); + return FALSE; + } + + if (zdata[1] == 'N') + { + ulog (LOG_ERROR, "%s: work request denied", qtrans->s.zfrom); + (void) fmail_transfer (FALSE, qtrans->s.zuser, (const char *) NULL, + "work request denied", + qtrans->s.zfrom, qdaemon->qsys->uuconf_zname, + qtrans->s.zto, (const char *) NULL, + (const char *) NULL); + } + + (void) fsysdep_did_work (qtrans->s.pseq); + utransfree (qtrans); + + return TRUE; +} + +/* Handle a remote work request. This just queues up the requests for + later processing. */ + +boolean +fremote_xcmd_init (qdaemon, qcmd, iremote) + struct sdaemon *qdaemon; + struct scmd *qcmd; + int iremote; +{ + const struct uuconf_system *qsys; + const char *zexclam; + const struct uuconf_system *qdestsys; + struct uuconf_system sdestsys; + char *zdestfile; + boolean fmkdirs; + struct stransfer *qtrans; + char *zuser; + char aboptions[5]; + char *zfrom; + boolean fret; + char *zfile; + + ulog (LOG_NORMAL, "Work requested: %s to %s", qcmd->zfrom, + qcmd->zto); + + qsys = qdaemon->qsys; + + zexclam = strchr (qcmd->zto, '!'); + if (zexclam == NULL + || zexclam == qcmd->zto + || strncmp (qdaemon->zlocalname, qcmd->zto, + (size_t) (zexclam - qcmd->zto)) == 0) + { + const char *zconst; + + /* The files are supposed to be copied to the local system. */ + qdestsys = NULL; + if (zexclam == NULL) + zconst = qcmd->zto; + else + zconst = zexclam + 1; + + zdestfile = zsysdep_local_file (zconst, qsys->uuconf_zpubdir); + if (zdestfile == NULL) + return FALSE; + + zuser = NULL; + fmkdirs = strchr (qcmd->zoptions, 'f') != NULL; + } + else + { + size_t clen; + char *zcopy; + int iuuconf; + char *zoptions; + + clen = zexclam - qcmd->zto; + zcopy = zbufalc (clen + 1); + memcpy (zcopy, qcmd->zto, clen); + zcopy[clen] = '\0'; + + iuuconf = uuconf_system_info (qdaemon->puuconf, zcopy, &sdestsys); + if (iuuconf == UUCONF_NOT_FOUND) + { + if (! funknown_system (qdaemon->puuconf, zcopy, &sdestsys)) + { + ulog (LOG_ERROR, "%s: System not found", zcopy); + ubuffree (zcopy); + qtrans = qtransalc (qcmd); + qtrans->psendfn = fremote_xcmd_reply; + qtrans->pinfo = (pointer) "XN"; + qtrans->iremote = iremote; + return fqueue_remote (qdaemon, qtrans); + } + } + else if (iuuconf != UUCONF_SUCCESS) + { + ulog_uuconf (LOG_ERROR, qdaemon->puuconf, iuuconf); + ubuffree (zcopy); + return FALSE; + } + + ubuffree (zcopy); + + qdestsys = &sdestsys; + zdestfile = zbufcpy (zexclam + 1); + + zuser = zbufalc (strlen (qdestsys->uuconf_zname) + + strlen (qcmd->zuser) + sizeof "!"); + sprintf (zuser, "%s!%s", qdestsys->uuconf_zname, qcmd->zuser); + zoptions = aboptions; + *zoptions++ = 'C'; + if (strchr (qcmd->zoptions, 'd') != NULL) + *zoptions++ = 'd'; + if (strchr (qcmd->zoptions, 'm') != NULL) + *zoptions++ = 'm'; + *zoptions = '\0'; + fmkdirs = TRUE; + } + + /* At this point we prepare to confirm the remote request. We could + actually fork here and let the child spool up the requests. */ + qtrans = qtransalc (qcmd); + qtrans->psendfn = fremote_xcmd_reply; + qtrans->pinfo = (pointer) "XY"; + qtrans->iremote = iremote; + if (! fqueue_remote (qdaemon, qtrans)) + { + ubuffree (zdestfile); + ubuffree (zuser); + return FALSE; + } + + /* Now we have to process each source file. The source + specification may or may use wildcards. */ + zfrom = zsysdep_local_file (qcmd->zfrom, qsys->uuconf_zpubdir); + if (zfrom == NULL) + { + ubuffree (zdestfile); + ubuffree (zuser); + return FALSE; + } + + if (! fsysdep_wildcard_start (zfrom)) + { + ubuffree (zfrom); + ubuffree (zdestfile); + ubuffree (zuser); + return FALSE; + } + + fret = TRUE; + + while ((zfile = zsysdep_wildcard (zfrom)) != NULL) + { + char *zto; + char abtname[CFILE_NAME_LEN]; + + if (! fsysdep_file_exists (zfile)) + { + ulog (LOG_ERROR, "%s: no such file", zfile); + continue; + } + + /* Make sure the remote system is permitted to read the + specified file. */ + if (! fin_directory_list (zfile, qsys->uuconf_pzremote_send, + qsys->uuconf_zpubdir, TRUE, TRUE, + (const char *) NULL)) + { + ulog (LOG_ERROR, "%s: not permitted to send", zfile); + break; + } + + if (qdestsys != NULL) + { + /* We really should get the original grade here. */ + zto = zsysdep_data_file_name (qdestsys, qdaemon->zlocalname, + BDEFAULT_UUCP_GRADE, FALSE, + abtname, (char *) NULL, + (char *) NULL); + if (zto == NULL) + { + fret = FALSE; + break; + } + } + else + { + zto = zsysdep_add_base (zdestfile, zfile); + if (zto == NULL) + { + fret = FALSE; + break; + } + /* We only accept a local destination if the remote system + has the right to create files there. */ + if (! fin_directory_list (zto, qsys->uuconf_pzremote_receive, + qsys->uuconf_zpubdir, TRUE, FALSE, + (const char *) NULL)) + { + ulog (LOG_ERROR, "%s: not permitted to receive", zto); + ubuffree (zto); + break; + } + } + + /* Copy the file either to the final destination or to the + spool directory. */ + if (! fcopy_file (zfile, zto, qdestsys == NULL, fmkdirs)) + { + ubuffree (zto); + break; + } + + ubuffree (zto); + + /* If there is a destination system, queue it up. */ + if (qdestsys != NULL) + { + struct scmd ssend; + char *zjobid; + + ssend.bcmd = 'S'; + ssend.pseq = NULL; + ssend.zfrom = zfile; + ssend.zto = zdestfile; + ssend.zuser = zuser; + ssend.zoptions = aboptions; + ssend.ztemp = abtname; + ssend.imode = ixsysdep_file_mode (zfile); + ssend.znotify = ""; + ssend.cbytes = -1; + ssend.zcmd = NULL; + ssend.ipos = 0; + + zjobid = zsysdep_spool_commands (qdestsys, BDEFAULT_UUCP_GRADE, + 1, &ssend); + if (zjobid == NULL) + break; + ubuffree (zjobid); + } + + ubuffree (zfile); + } + + if (zfile != NULL) + ubuffree (zfile); + + (void) fsysdep_wildcard_end (); + + ubuffree (zdestfile); + if (qdestsys != NULL) + (void) uuconf_system_free (qdaemon->puuconf, &sdestsys); + + ubuffree (zfrom); + ubuffree (zuser); + + return fret; +} + +/* Reply to a remote work request. */ + +static boolean +fremote_xcmd_reply (qtrans, qdaemon) + struct stransfer *qtrans; + struct sdaemon *qdaemon; +{ + boolean fret; + + fret = (*qdaemon->qproto->pfsendcmd) (qdaemon, + (const char *) qtrans->pinfo, + qtrans->ilocal, + qtrans->iremote); + utransfree (qtrans); + return fret; +} |
