Protocol support routines to move commands and data around.
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 AIRS, P.O. Box 520, Waltham, MA 02254.
Revision 1.21 1992/04/02 22:51:09 ian
Add gcc 2.0 format checking to ulog, and fixed discovered problems
Revision 1.20 1992/03/30 04:49:10 ian
Niels Baggesen: added debugging types abnormal and uucp-proto
Revision 1.19 1992/03/30 04:07:13 ian
Dirk Musstopf: remove temporary file if receive fails
Revision 1.18 1992/03/13 22:59:25 ian
Have breceive_char go through freceive_data
Revision 1.17 1992/03/12 19:56:10 ian
Debugging based on types rather than number
Revision 1.16 1992/03/11 01:18:15 ian
Niels Baggesen: drop the connection on a write failure
Revision 1.15 1992/03/11 00:18:50 ian
Save temporary file if file send fails
Revision 1.14 1992/02/09 05:21:55 ian
Bob Denny: call fmail_transfer before fsysdep_did_work
Revision 1.13 1992/02/08 19:41:24 ian
Simplify pffile calls for ancient stupid compilers
Revision 1.12 1992/02/08 03:54:18 ian
Include <string.h> only in <uucp.h>, added 1992 copyright
Revision 1.11 1992/01/18 22:48:53 ian
Reworked sending of mail and general handling of failed transfers
Revision 1.10 1992/01/16 18:16:58 ian
Niels Baggesen: add some debugging messages
Revision 1.9 1991/12/31 19:34:19 ian
Added number of bytes to pffile protocol entry point
Revision 1.8 1991/12/30 04:28:30 ian
John Theus: check for EOF to work around bug in fread
Revision 1.7 1991/12/21 23:10:43 ian
Terry Gardner: record failed file transfers in statistics file
Revision 1.6 1991/12/13 04:33:38 ian
Franc,ois Pinard: don't bother to warn if the final HY doesn't come in
Revision 1.5 1991/11/15 21:00:59 ian
Efficiency hacks for 'f' and 't' protocols
Revision 1.4 1991/11/11 19:32:03 ian
Added breceive_char to read characters through protocol buffering
Revision 1.3 1991/11/11 04:21:16 ian
Revision 1.2 1991/11/10 19:24:22 ian
Added pffile protocol entry point for file level control
Revision 1.1 1991/11/09 18:51:50 ian
char prot_rcsid
[] = "$Id: prot.c,v 1.21 1992/04/02 22:51:09 ian Rel $";
/* This file implements the generic UUCP protocol for making and
confirming file transfer requests. This involves sending ASCII
strings back and forth between the communicating daemons. It would
be possible to use a different scheme when designing a new
protocol, but this scheme is used by all traditional UUCP
static boolean fpsendfile_confirm
P((void));
static boolean fprecfile_confirm
P((void));
static boolean fploop
P((void));
static void upadd_cmd
P((const char *z
, int clen
, boolean flast
));
/* Variables visible to the protocol-specific routines. */
/* Protocol structure. */
const struct sprotocol
*qProto
;
/* Buffer to hold received data. */
char abPrecbuf
[CRECBUFLEN
];
/* Index of start of data in abPrecbuf. */
/* Index of end of data (first byte not included in data) in abPrecbuf. */
/* Whether an unexpected shutdown is OK now; this is used to avoid
giving a warning for systems that hang up in a hurry. */
/* Amount of data sent for current send file; -1 means there is no
static long cPsent_bytes
= -1;
/* Amount of data received for current receive file; -1 means there is
no current receive file. */
static long cPreceived_bytes
= -1;
/* Send a file. If we are the master, we must send a command to
transfer the file and wait for a confirmation that we can begin
sending the file. If we are the slave, the master has sent us a
command and is waiting for a reply; we must confirm that we will
send the file. Either way, we begin transferring data.
This function returns FALSE if there is a communication failure.
It returns TRUE otherwise, even if the file transfer failed. */
fsend_file (fmaster
, e
, qcmd
, zmail
, ztosys
, fnew
)
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. The remote
system will ignore ztemp, but it is supposed to be sent anyhow.
If fnew is TRUE, we also send the size; in this case if ztemp
is empty we must send it as "". */
clen
= (strlen (qcmd
->zfrom
) + strlen (qcmd
->zto
)
+ strlen (qcmd
->zuser
) + strlen (qcmd
->zoptions
)
+ strlen (qcmd
->ztemp
) + strlen (qcmd
->znotify
)
zsend
= (char *) alloca (clen
);
sprintf (zsend
, "S %s %s %s -%s %s 0%o %s", qcmd
->zfrom
, qcmd
->zto
,
qcmd
->zuser
, qcmd
->zoptions
, qcmd
->ztemp
, qcmd
->imode
,
if (qcmd
->znotify
[0] != '\0')
sprintf (zsend
, "S %s %s %s -%s %s 0%o %s %ld", qcmd
->zfrom
,
qcmd
->zto
, qcmd
->zuser
, qcmd
->zoptions
, qcmd
->ztemp
,
qcmd
->imode
, znotify
, qcmd
->cbytes
);
if (! (qProto
->pfsendcmd
) (zsend
))
/* Now we must await a reply. */
|| (zrec
[1] != 'Y' && zrec
[1] != 'N'))
ulog (LOG_ERROR
, "Bad response to send request");
zerr
= "permission denied";
zerr
= "remote cannot create work files";
zerr
= "too large for receiver now";
/* The file is too large to ever send. */
zerr
= "too large for receiver";
zset
= (char *) alloca (sizeof "unknown reason: "
sprintf (zset
, "unknown reason: %s", zrec
);
ulog (LOG_ERROR
, "Can't send %s: %s", qcmd
->zfrom
, zerr
);
(void) fmail_transfer (FALSE
, qcmd
->zuser
, zmail
, zerr
,
zsysdep_save_temp_file (qcmd
->pseq
));
(void) fsysdep_did_work (qcmd
->pseq
);
/* We are the slave; confirm that we will send the file. We
send the file mode in the confirmation string. */
sprintf (absend
, "RY 0%o", qcmd
->imode
);
if (! (qProto
->pfsendcmd
) (absend
))
/* Record the file we are sending, and let the protocol take over. */
if (! fstore_sendfile (e
, qcmd
->pseq
, qcmd
->zfrom
, qcmd
->zto
, ztosys
,
/* Tell the protocol that we are starting to send a file. */
if (qProto
->pffile
!= NULL
)
boolean (*pffile
) P((boolean
, boolean
, boolean
*, long));
/* Simplify expression for ancient compilers. */
if (! pffile (TRUE
, TRUE
, (boolean
*) NULL
, qcmd
->cbytes
))
/* Confirm that a file has been received correctly by the other side.
Return FALSE for a communication error. We expect the receiving
system to send back CY; if an error occurred while moving the
received file into its final location, the receiving system will
|| (zrec
[1] != 'Y' && zrec
[1] != 'N'))
zerr
= "Bad confirmation for sent file";
(void) fsent_file (FALSE
, cbytes
, zerr
, FALSE
);
zerr
= "File could not be stored in final location";
zset
= (char *) alloca (sizeof "File send failed: " + strlen (zrec
));
sprintf (zset
, "File send failed: %s", zrec
);
(void) fsent_file (FALSE
, cbytes
, zerr
, TRUE
);
(void) fsent_file (TRUE
, cbytes
, (const char *) NULL
, FALSE
);
/* Receive a file. If we are the master, we must set up a file
request and wait for the other side to confirm it. If we are the
slave, we must confirm a request made by the other side. We then
start receiving the file.
This function must return FALSE if there is a communication error
and TRUE otherwise. We return TRUE even if the file transfer
freceive_file (fmaster
, e
, qcmd
, zmail
, zfromsys
, fnew
)
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 (qcmd
->zfrom
) + strlen (qcmd
->zto
)
+ strlen (qcmd
->zuser
) + strlen (qcmd
->zoptions
) + 30);
zsend
= (char *) alloca (clen
);
sprintf (zsend
, "R %s %s %s -%s", qcmd
->zfrom
, qcmd
->zto
,
qcmd
->zuser
, qcmd
->zoptions
);
sprintf (zsend
, "R %s %s %s -%s %ld", qcmd
->zfrom
, qcmd
->zto
,
qcmd
->zuser
, qcmd
->zoptions
, qcmd
->cbytes
);
if (! (qProto
->pfsendcmd
) (zsend
))
(void) remove (qcmd
->ztemp
);
(void) remove (qcmd
->ztemp
);
|| (zrec
[1] != 'Y' && zrec
[1] != 'N'))
ulog (LOG_ERROR
, "Bad response to receive request");
(void) remove (qcmd
->ztemp
);
/* 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
ulog (LOG_ERROR
, "Can't receive %s: too large",
(void) remove (qcmd
->ztemp
);
zset
= (char *) alloca (sizeof "unknown reason: "
sprintf (zset
, "unknown reason: %s", zrec
);
ulog (LOG_ERROR
, "Can't receive %s: %s", qcmd
->zfrom
, zerr
);
(void) remove (qcmd
->ztemp
);
(void) fmail_transfer (FALSE
, qcmd
->zuser
, zmail
, zerr
,
(void) fsysdep_did_work (qcmd
->pseq
);
/* The mode should have been sent as "RY 0%o". If it wasn't,
imode
= (unsigned int) strtol (zrec
+ 2, (char **) NULL
, 8);
/* Tell the other system to go ahead and send. */
if (! (qProto
->pfsendcmd
) ("SY"))
(void) remove (qcmd
->ztemp
);
if (! fstore_recfile (e
, qcmd
->pseq
, qcmd
->zfrom
, qcmd
->zto
, zfromsys
,
qcmd
->zuser
, imode
, zmail
, qcmd
->ztemp
))
/* Tell the protocol that we are starting to receive a file. */
if (qProto
->pffile
!= NULL
)
boolean (*pffile
) P((boolean
, boolean
, boolean
*, long));
/* Simplify expression for ancient compilers. */
if (! pffile (TRUE
, FALSE
, (boolean
*) NULL
, (long) -1))
/* Confirm that a file was received correctly. */
cbytes
= cPreceived_bytes
;
if (freceived_file (TRUE
, cbytes
, (const char *) NULL
, FALSE
))
return (qProto
->pfsendcmd
) ("CY");
return (qProto
->pfsendcmd
) ("CN5");
/* Send a transfer request. This is only called by the master. It
ignored the pseq entry in the scmd structure. */
We put a dash in front of options. */
clen
= (strlen (qcmd
->zfrom
) + strlen (qcmd
->zto
)
+ strlen (qcmd
->zuser
) + strlen (qcmd
->zoptions
) + 7);
zsend
= (char *) alloca (clen
);
sprintf (zsend
, "X %s %s %s -%s", qcmd
->zfrom
, qcmd
->zto
,
qcmd
->zuser
, qcmd
->zoptions
);
if (! (qProto
->pfsendcmd
) (zsend
))
|| (zrec
[1] != 'Y' && zrec
[1] != 'N'))
ulog (LOG_ERROR
, "Bad response to wildcard request");
ulog (LOG_ERROR
, "Work request denied");
/* Confirm a transfer request. */
return (qProto
->pfsendcmd
) ("XY");
/* Signal a file transfer failure to the other side. This is only called
ftransfer_fail (bcmd
, twhy
)
ulog (LOG_ERROR
, "ftransfer_fail: Can't happen");
return (qProto
->pfsendcmd
) (z
);
/* Get and parse a command from the other system. Handle hangups
z
= (char *) xrealloc ((pointer
) z
, c
);
if (! fparse_cmd (z
, qcmd
))
/* Handle hangup commands specially. If it's just 'H', return
it. If it's 'N', the other side is denying a hangup request
which we can just ignore (since the top level code assumes
that hangup requests are denied). If it's 'Y', the other
side is confirming a hangup request. In this case we confirm
with an "HY", wait for yet another "HY" from the other side,
and then finally shut down the protocol (I don't know why it
works this way, but it does). We then return a 'Y' command
to the top level code. */
ulog (LOG_ERROR
, "Got hangup reply as master");
ulog (LOG_ERROR
, "Got hangup reply as master");
/* Don't check errors rigorously here, since the other side
might jump the gun and hang up. */
if (! (qProto
->pfsendcmd
) ("HY"))
if (strcmp (zcmd
, "HY") != 0)
DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO
| DEBUG_ABNORMAL
,
"fgetcmd: Got \"%s\" when expecting \"HY\"",
(void) (qProto
->pfshutdown
) ();
return (qProto
->pfsendcmd
) ("H");
/* Reply to a hangup request. This is only called by the slave. If
fconfirm is TRUE, we are closing down the protocol. We send an HY
message. The master responds with an HY message. We send another
HY message, and then shut down the protocol. */
return (qProto
->pfsendcmd
) ("HN");
if (! (qProto
->pfsendcmd
) ("HY"))
if (strcmp (z
, "HY") != 0)
ulog (LOG_ERROR
, "Got \"%s\" when expecting \"HY\"", z
);
if (! (qProto
->pfsendcmd
) ("HY"))
return (qProto
->pfshutdown
) ();
/* Loop sending and/or receiving data. If there is a file to send,
this will send it until the entire file has been sent or a command
has been received from the remote system or a complete file has
been received from the remote system. Otherwise this will simply
call the protocol to wait until a complete file or command has been
received from the remote system. */
DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO
, "fploop: Main protocol loop");
if (ffileisopen (eSendfile
))
/* We keep sending out packets until we have something
in the receive buffer. */
/* Get a packet and fill it with data. */
zdata
= (qProto
->pzgetspace
) (&cdata
);
if (ffileeof (eSendfile
))
cdata
= cfileread (eSendfile
, zdata
, cdata
);
if (ffilereaderror (eSendfile
, cdata
))
/* The protocol gives us no way to report a file
sending error, so we just drop the connection.
ulog (LOG_ERROR
, "read: %s", strerror (errno
));
if (! (qProto
->pfsenddata
) (zdata
, cdata
))
/* If we have reached the end of the file, tell the
protocol that the file is finished (the protocol
could also detect this by looking for zero passed as
the data length to the send data routine, but would
have no convenient way to tell us to redo the file
send). If we are not supposed to redo the file
transfer, wait for confirmation and return out to get
if (qProto
->pffile
!= NULL
)
boolean (*pffile
) P((boolean
, boolean
, boolean
*,
/* Simplify expression for ancient compilers. */
if (! pffile (FALSE
, TRUE
, &fredo
, (long) -1))
ulog (LOG_NORMAL
, "Resending file");
if (! ffilerewind (eSendfile
))
ulog (LOG_ERROR
, "rewind: %s",
return fpsendfile_confirm ();
/* Process the data in the receive buffer, and decide
whether it's time to get out. */
if (! (qProto
->pfprocess
) (&fexit
))
/* If there is no file to send, there really should be a file to
if (! ffileisopen(eRecfile
))
ulog (LOG_FATAL
, "fploop: No send or receive file");
/* We have no file to send. Wait for data to come in. */
return (qProto
->pfwait
) ();
/* This function is called by the protocol routines when data has
arrived. Some protocols may know whether the data is for a command
or a file; for others, if a receive file is open it is for the file
and is otherwise for a command. This function will set *pfexit to
TRUE if it has received a complete file (assumed to be true if
cdata is zero) or a complete command (assumed to be true if the
argument data contains a null byte). It will return FALSE on
fgot_data (zdata
, cdata
, fcmd
, ffile
, pfexit
)
if (ffileisopen (eRecfile
))
if (ffile
&& ! ffileisopen (eRecfile
))
ulog (LOG_FATAL
, "fgot_data: No file to receive into");
/* The file transfer is complete. If the protocol has a
file level routine, call it to see whether we have to
receive the file again. */
if (qProto
->pffile
!= NULL
)
boolean (*pffile
) P((boolean
, boolean
, boolean
*, long));
/* Simplify expression for ancient compilers. */
if (! pffile (FALSE
, FALSE
, &fredo
, (long) -1))
ulog (LOG_NORMAL
, "File being resent");
if (! frecfile_rewind ())
if (! fprecfile_confirm ())
/* Cast zdata to avoid warnings because of erroneous
cwrote
= cfilewrite (eRecfile
, (char *) zdata
, cdata
);
zerr
= "could not write all data";
ulog (LOG_ERROR
, "write: %s", zerr
);
/* 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
cPreceived_bytes
+= cdata
;
/* We want to add this data to the current command string. 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. */
z
= (const char *) memchr ((constpointer
) zdata
, '\0', cdata
);
upadd_cmd (zdata
, cdata
, FALSE
);
upadd_cmd (zdata
, z
- zdata
, TRUE
);
/* This function is called by fgot_data when a command string is
received. We must queue up received commands since we don't know
when we'll be able to get to them (for example, the
acknowledgements for the last few packets of a sent file may
contain the string indicating whether the file was received
struct spcmdqueue
*qnext
;
static struct spcmdqueue
*qPcmd_queue
;
static struct spcmdqueue
*qPcmd_free
;
upadd_cmd (z
, clen
, flast
)
q
= (struct spcmdqueue
*) xmalloc (sizeof (struct spcmdqueue
));
if (q
->clen
+ clen
+ 1 > q
->csize
)
q
->csize
= q
->clen
+ clen
+ 1;
q
->z
= (char *) xrealloc ((pointer
) q
->z
, q
->csize
);
memcpy (q
->z
+ q
->clen
, z
, clen
);
/* If the last string in this command, add it to the queue of
for (pq
= &qPcmd_queue
; *pq
!= NULL
; pq
= &(*pq
)->qnext
)
/* Get a command string. We just have to wait until the receive
packet function gives us something in qPcmd_queue. The return
value of this may be treated as a static buffer; it will last
at least until the next packet is received. */
/* Wait until a command comes in. */
while (qPcmd_queue
== NULL
)
DEBUG_MESSAGE0 (DEBUG_UUCP_PROTO
, "zgetcmd: Waiting for packet");
if (! (qProto
->pfwait
) ())
/* We must not replace qPcmd_free, because it may already be
receiving a new command string. */
q
->qnext
= qPcmd_free
->qnext
;
DEBUG_MESSAGE1 (DEBUG_UUCP_PROTO
, "zgetcmd: Got command \"%s\"", q
->z
);
/* We want to output and input at the same time, if supported on this
machine. If we have something to send, we send it all while
accepting a large amount of data. Once we have sent everything we
look at whatever we have received. If data comes in faster than we
can send it, we may run out of buffer space. */
fsend_data (zsend
, csend
, fdoread
)
return fport_write (zsend
, csend
);
if (iPrecend
< iPrecstart
)
zrec
= abPrecbuf
+ iPrecend
;
crec
= iPrecstart
- iPrecend
- 1;
else if (iPrecend
< CRECBUFLEN
)
zrec
= abPrecbuf
+ iPrecend
;
crec
= CRECBUFLEN
- iPrecend
;
if (! fport_io (zsend
, &csent
, zrec
, &crec
))
iPrecend
= (iPrecend
+ crec
) % CRECBUFLEN
;
/* Read data from the other system when we have nothing to send. The
argument cneed is the amount of data the caller wants, and ctimeout
is the timeout in seconds. The function sets *pcrec to the amount
of data which was actually received, which may be less than cneed
if there isn't enough room in the receive buffer. If no data is
received before the timeout expires, *pcrec will be returned as 0.
If an error occurs, the function returns FALSE. If the freport
argument is FALSE, no error should be reported. */
freceive_data (cneed
, pcrec
, ctimeout
, freport
)
/* Set *pcrec to the maximum amount of data we can read. fport_read
expects *pcrec to be the buffer size, and sets it to the amount
if (iPrecend
< iPrecstart
)
*pcrec
= iPrecstart
- iPrecend
- 1;
*pcrec
= CRECBUFLEN
- iPrecend
;
/* If we have no room in the buffer, we're in trouble. The
protocols must be written to ensure that this can't happen. */
ulog (LOG_FATAL
, "freceive_data: No room in buffer");
/* If we don't have room for all the data the caller wants, we
simply have to expect less. We'll get the rest later. */
if (! fport_read (abPrecbuf
+ iPrecend
, pcrec
, cneed
, ctimeout
, freport
))
iPrecend
= (iPrecend
+ *pcrec
) % CRECBUFLEN
;
/* Read a single character. Get it out of the receive buffer if it's
there, otherwise ask freceive_data for at least one character.
This is used because as a protocol is shutting down freceive_data
may read ahead and eat characters that should be read outside the
protocol routines. We call freceive_data rather than fport_read
with an argument of 1 so that we can get all the available data in
a single system call. The ctimeout argument is the timeout in
seconds; the freport argument is FALSE if no error should be
reported. This returns a character, or -1 on timeout or -2 on
breceive_char (ctimeout
, freport
)
if (iPrecstart
== iPrecend
)
if (! freceive_data (1, &crec
, ctimeout
, freport
))
b
= abPrecbuf
[iPrecstart
];
iPrecstart
= (iPrecstart
+ 1) % CRECBUFLEN
;
/* This routine is called when an error occurred and we are crashing
out of the connection. It is only used to report statistics on
failed transfers to the statistics file. 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) fsent_file (FALSE
, cbytes
, "connection failure", FALSE
);
if (cPreceived_bytes
!= -1)
cbytes
= cPreceived_bytes
;
(void) freceived_file (FALSE
, cbytes
, "connection failure", FALSE
);