/* 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 AIRS, P.O. Box 520, Waltham, MA 02254.

   $Log: uucico.c,v $
   Revision 1.92  1992/04/06  21:10:14  ian
   Marc Boucher: set *pqsys to NULL in faccept_call

   Revision 1.91  1992/03/30  04:07:13  ian
   Dirk Musstopf: remove temporary file if receive fails

   Revision 1.90  1992/03/28  22:06:38  ian
   Michael I Bushnell: renamed enum tstatus to avoid header file conflict

   Revision 1.89  1992/03/28  20:52:11  ian
   Petri Helenius: must dump controlling terminal when going to next alternate

   Revision 1.88  1992/03/28  20:31:55  ian
   Franc,ois Pinard: allow a name to be given to an alternate

   Revision 1.87  1992/03/28  19:40:26  ian
   Close log and statistics file at each master/slave role switch

   Revision 1.86  1992/03/28  04:45:31  ian
   Mark E. Mallett: minor cleanup

   Revision 1.85  1992/03/17  18:42:21  ian
   T. William Wells: set current time in status file when call completes

   Revision 1.84  1992/03/17  01:03:03  ian
   Miscellaneous cleanup

   Revision 1.83  1992/03/16  05:16:03  ian
   Recognize SVR4 -U flag

   Revision 1.82  1992/03/16  04:38:00  ian
   Turn off DEBUG_PORT for handshake debugging

   Revision 1.81  1992/03/15  04:51:17  ian
   Keep an array of signals we've received rather than a single variable

   Revision 1.80  1992/03/12  19:54:43  ian
   Debugging based on types rather than number

   Revision 1.79  1992/03/11  19:53:55  ian
   Improved chat script debugging

   Revision 1.78  1992/03/11  00:18:50  ian
   Save temporary file if file send fails

   Revision 1.77  1992/03/10  23:01:20  ian
   Don't run uuxqt if we got a SIGTERM

   Revision 1.76  1992/03/10  21:47:39  ian
   Added protocol command for ports

   Revision 1.75  1992/03/09  20:14:37  ian
   Ted Lindgreen: added max-remote-debug command

   Revision 1.74  1992/03/09  19:52:50  ian
   Ted Lindgreen: strip parity from initial handshake strings

   Revision 1.73  1992/03/09  19:42:43  ian
   Ted Lindgreen: don't send mail for nonexistent file

   Revision 1.72  1992/03/09  05:37:10  ian
   Only look for hangup string in debugging mode

   Revision 1.71  1992/03/09  05:29:20  ian
   Ted Lindgreen: report requested grade on an incoming call

   Revision 1.70  1992/03/09  05:08:16  ian
   Added status for wrong time to call, not used if system can't be called

   Revision 1.69  1992/03/08  17:45:41  ian
   Ted Lindgreen: start uuxqt for only one system if appropriate

   Revision 1.68  1992/03/08  17:08:20  ian
   Ted Lindgreen: ignore -u option

   Revision 1.67  1992/03/08  16:42:41  ian
   Ted Lindgreen: report port and login name in log file

   Revision 1.66  1992/03/07  02:56:30  ian
   Rewrote time routines

   Revision 1.65  1992/03/04  15:05:51  ian
   Michael Haberler: some systems send \n after Shere

   Revision 1.64  1992/03/04  00:36:44  ian
   Michael Richardson: better chat script debugging

   Revision 1.63  1992/03/03  21:01:20  ian
   Use strict timeout in fsserial_read, eliminate all race conditions

   Revision 1.62  1992/03/03  06:06:48  ian
   T. William Wells: don't complain about missing configuration files

   Revision 1.61  1992/02/29  04:07:08  ian
   Added -j option to uucp and uux

   Revision 1.60  1992/02/29  01:06:59  ian
   Chip Salzenberg: recheck file permissions before sending

   Revision 1.59  1992/02/27  05:40:54  ian
   T. William Wells: detach from controlling terminal, handle signals safely

   Revision 1.58  1992/02/24  22:38:45  ian
   Don't treat an extra argument as a port

   Revision 1.57  1992/02/24  04:58:47  ian
   Only permit files to be received into directories that are world-writeable

   Revision 1.56  1992/02/23  19:50:50  ian
   Handle READ and WRITE in Permissions correctly

   Revision 1.55  1992/02/23  03:26:51  ian
   Overhaul to use automatic configure shell script

   Revision 1.54  1992/02/20  22:57:19  ian
   Chip Salzenberg: some systems truncate the Shere= machine name

   Revision 1.53  1992/02/19  19:36:07  ian
   Rearranged time functions

   Revision 1.52  1992/02/14  21:32:50  ian
   Niels Baggesen: under HAVE_BNU_LOGGING, don't lost system name when dieing

   Revision 1.51  1992/02/09  05:21:55  ian
   Bob Denny: call fmail_transfer before fsysdep_did_work

   Revision 1.50  1992/02/08  22:33:32  ian
   Only get the current working directory if it's going to be needed

   Revision 1.49  1992/02/08  20:33:57  ian
   Handle all possible signals raised by abort

   Revision 1.48  1992/02/08  03:54:18  ian
   Include <string.h> only in <uucp.h>, added 1992 copyright

   Revision 1.47  1992/02/07  17:08:15  ian
   Bob Denny: retry time not reached is not an error

   Revision 1.46  1992/01/29  18:37:27  ian
   Patrick Smith: only wait a short time for the hangup message

   Revision 1.45  1992/01/28  04:34:10  ian
   Marty Shannon: -f uucp flag not handled correctly

   Revision 1.44  1992/01/28  03:50:42  ian
   Chip Salzenberg: set .Status correctly if wrong time to call

   Revision 1.43  1992/01/21  19:39:12  ian
   Chip Salzenberg: uucp and uux start uucico for right system, not any

   Revision 1.42  1992/01/20  16:44:54  ian
   Marty Shannon: update .Status file if it's the wrong time to call

   Revision 1.41  1992/01/19  02:27:00  ian
   Marty Shannon: update .Status file on incoming calls

   Revision 1.40  1992/01/18  22:48:53  ian
   Reworked sending of mail and general handling of failed transfers

   Revision 1.39  1992/01/15  07:06:29  ian
   Set configuration directory in Makefile rather than sysdep.h

   Revision 1.38  1992/01/14  04:38:43  ian
   Chip Salzenberg: only declare sportinfo if it will be used

   Revision 1.37  1992/01/12  19:53:05  ian
   John Antypas: pass in sportinfo structure for fdo_call to use

   Revision 1.36  1992/01/05  03:09:17  ian
   Changed abProgram and abVersion to non const to avoid compiler bug

   Revision 1.35  1992/01/04  21:53:36  ian
   Start up uuxqt even if a call fails

   Revision 1.34  1991/12/31  19:43:13  ian
   Added 'e' protocol

   Revision 1.33  1991/12/28  04:33:09  ian
   Set fmasterdone correctly in slave mode

   Revision 1.32  1991/12/23  05:15:54  ian
   David Nugent: set debugging level for a specific system

   Revision 1.31  1991/12/21  23:10:43  ian
   Terry Gardner: record failed file transfers in statistics file

   Revision 1.30  1991/12/21  22:17:20  ian
   Change protocol ordering to 't', 'g', 'f'

   Revision 1.29  1991/12/21  22:07:47  ian
   John Theus: don't warn if port file does not exist

   Revision 1.28  1991/12/20  04:30:24  ian
   Terry Gardner: record conversation time in log file

   Revision 1.27  1991/12/20  00:42:24  ian
   Clear user name from error message given by getting next command

   Revision 1.26  1991/12/18  05:12:00  ian
   Added -l option to uucico to prompt for login name once and then exit

   Revision 1.25  1991/12/18  03:54:14  ian
   Made error messages to terminal appear more normal

   Revision 1.24  1991/12/17  04:55:01  ian
   David Nugent: ignore SIGHUP in uucico and uuxqt

   Revision 1.23  1991/12/15  03:42:33  ian
   Added tprocess_chat_cmd for all chat commands, and added CMDTABTYPE_PREFIX

   Revision 1.22  1991/12/11  03:59:19  ian
   Create directories when necessary; don't just assume they exist

   Revision 1.21  1991/11/21  22:17:06  ian
   Add version string, print version when printing usage

   Revision 1.20  1991/11/16  00:33:28  ian
   Remove ?: operator between string literal and variable

   Revision 1.19  1991/11/14  03:40:10  ian
   Try to figure out whether stdin is a TCP port

   Revision 1.18  1991/11/14  03:20:13  ian
   Added seven-bit and reliable commands to help when selecting protocols

   Revision 1.17  1991/11/13  23:08:40  ian
   Expand remote pathnames in uucp and uux; fix up uux special cases

   Revision 1.16  1991/11/12  19:47:04  ian
   Add called-chat set of commands to run a chat script on an incoming call

   Revision 1.15  1991/11/12  18:25:33  ian
   Added 't' protocol

   Revision 1.14  1991/11/11  23:47:24  ian
   Added chat-program to run a program to do a chat script

   Revision 1.13  1991/11/11  19:32:03  ian
   Added breceive_char to read characters through protocol buffering

   Revision 1.12  1991/11/11  18:55:52  ian
   Get protocol parameters from port and dialer for incoming calls

   Revision 1.11  1991/11/11  16:59:05  ian
   Eliminate fread_port_info, allow NULL pflock arg to ffind_port

   Revision 1.10  1991/11/11  04:21:16  ian
   Added 'f' protocol

   Revision 1.9  1991/11/10  19:24:22  ian
   Added pffile protocol entry point for file level control

   Revision 1.8  1991/11/09  18:53:07  ian
   Reworked protocol interface

   Revision 1.7  1991/11/07  18:15:38  ian
   Chip Salzenberg: move CMAXRETRIES to conf.h for easy configuration

   Revision 1.6  1991/09/19  03:06:04  ian
   Chip Salzenberg: put BNU temporary files in system's directory

   Revision 1.5  1991/09/19  02:30:37  ian
   From Chip Salzenberg: check whether signal is ignored differently

   Revision 1.4  1991/09/19  02:22:44  ian
   Chip Salzenberg's patch to allow ";retrytime" at the end of a time string

   Revision 1.3  1991/09/12  05:04:26  ian
   Changed sense of \0 return from btime_low_grade on calltimegrade

   Revision 1.2  1991/09/11  02:33:14  ian
   Added ffork argument to fsysdep_run
  
   Revision 1.1  1991/09/10  19:40:31  ian
   Initial revision
  
   */

#include "uucp.h"

#if USE_RCS_ID
char uucico_rcsid[] = "$Id: uucico.c,v 1.92 1992/04/06 21:10:14 ian Rel $";
#endif

#include <ctype.h>

#include "getopt.h"

#include "port.h"
#include "prot.h"
#include "system.h"
#include "uutime.h"

/* The program name.  */
char abProgram[] = "uucico";

/* Define the known protocols.
   bname, ffullduplex, qcmds, pfstart, pfshutdown, pfsendcmd, pzgetspace,
   pfsenddata, pfprocess, pfwait, pffile  */

static struct sprotocol asProtocols[] =
{
  { 't', FALSE, RELIABLE_ENDTOEND | RELIABLE_RELIABLE | RELIABLE_EIGHT,
      asTproto_params, ftstart, ftshutdown, ftsendcmd, ztgetspace,
      ftsenddata, ftprocess, ftwait, ftfile },
  { 'e', FALSE, RELIABLE_ENDTOEND | RELIABLE_RELIABLE | RELIABLE_EIGHT,
      asEproto_params, festart, feshutdown, fesendcmd, zegetspace,
      fesenddata, feprocess, fewait, fefile },
  { 'g', FALSE, RELIABLE_EIGHT,
      asGproto_params, fgstart, fgshutdown, fgsendcmd, zggetspace,
      fgsenddata, fgprocess, fgwait, NULL },
  { 'a', FALSE, RELIABLE_EIGHT,
      asZproto_params, fzstart, fzshutdown, fzsendcmd, zzgetspace,
      fzsenddata, fzprocess, fzwait, fzfile },
  { 'f', FALSE, RELIABLE_RELIABLE,
      asFproto_params, ffstart, ffshutdown, ffsendcmd, zfgetspace,
      ffsenddata, ffprocess, ffwait, fffile },
};

#define CPROTOCOLS (sizeof asProtocols / sizeof asProtocols[0])

/* Locked system.  */

static boolean fLocked_system;
static struct ssysteminfo sLocked_system;

/* Local functions.  */

static void uusage P((void));
static void uabort P((void));
static boolean fcall P((const struct ssysteminfo *qsys,
			struct sport *qport,
			boolean fforce, int bgrade,
			boolean fnodetach));
static boolean fdo_call P((const struct ssysteminfo *qsys,
			   struct sport *qport,
			   struct sstatus *qstat, int cretry,
			   boolean *pfcalled, struct sport *quse));
static boolean fcall_failed P((const struct ssysteminfo *qsys,
			       enum tstatus_type twhy,
			       struct sstatus *qstat, int cretry));
static boolean flogin_prompt P((struct sport *qport));
static boolean faccept_call P((const char *zlogin, struct sport *qport,
			       const struct ssysteminfo **pqsys));
static boolean fuucp P((boolean fmaster, const struct ssysteminfo *qsys,
			int bgrade, boolean fnew, long cmax_receive));
static boolean fdo_xcmd P((const struct ssysteminfo *qsys,
			   boolean fcaller,
			   const struct scmd *qcmd));
static boolean fok_to_send P((const char *zfrom, boolean flocal,
			      boolean fcaller, boolean fspool,
			      const struct ssysteminfo *qsys,
			      const char *zuser));
static boolean fok_to_receive P((const char *zto, boolean flocal,
				 boolean fcaller,
				 const struct ssysteminfo *qsys,
				 const char *zuser));
static boolean frequest_ok P((boolean flocal, boolean fcaller,
			      const struct ssysteminfo *qsys,
			      const char *zuser));
static boolean fsend_uucp_cmd P((const char *z));
static const char *zget_uucp_cmd P((boolean frequired));
static const char *zget_typed_line P((void));

/* Long getopt options.  */

static const struct option asLongopts[] = { { NULL, 0, NULL, 0 } };

const struct option *_getopt_long_options = asLongopts;

int
main (argc, argv)
     int argc;
     char **argv;
{
  /* getopt return value  */
  int iopt;
  /* Don't detach from controlling terminal.  */
  boolean fnodetach = FALSE;
  /* Configuration file name  */
  const char *zconfig = NULL;
  /* System to call  */
  const char *zsystem = NULL;
  /* 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;
  /* Port information for the port name in zport.  */
  struct sport sportinfo;
  /* Pointer to port to use, or NULL if unknown.  */
  struct sport *qport;
  /* Whether to start uuxqt when done.  */
  boolean fuuxqt = TRUE;
  /* Whether to force a call despite status of previous call  */
  boolean fforce = FALSE;
  /* Whether we are the master  */
  boolean fmaster = FALSE;
  /* Whether to give a single login prompt.  */
  boolean flogin = FALSE;
  /* Whether to do an endless loop of accepting calls  */
  boolean floop = FALSE;
  /* Whether to wait for an inbound call after doing an outbound call  */
  boolean fwait = FALSE;
  boolean fret = TRUE;
#if DEBUG > 1
  int iholddebug;
#endif

  while ((iopt = getopt (argc, argv,
			 "DefI:lp:qr:s:S:u:x:X:w")) != EOF)
    {
      switch (iopt)
	{
	case 'D':
	  /* Don't detach from controlling terminal.  */
	  fnodetach = TRUE;
	  break;

	case 'e':
	  /* Do an endless loop of accepting calls.  */
	  floop = 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).  */
	  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 (optarg[0] == '1' && optarg[1] == '\0')
	    fmaster = TRUE;
	  else if (optarg[0] == '0' && optarg[1] == '\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)
    {
      fprintf (stderr, "%s: -w requires -e\n", abProgram);
      uusage ();
    }

  uread_config (zconfig);

#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 (TRUE, FALSE);

  ulog_to_file (TRUE);
  ulog_fatal_fn (uabort);

  /* If a port was named, get its information.  */
  if (zport == NULL)
    qport = NULL;
  else
    {
      if (! ffind_port (zport, (long) 0, (long) 0, &sportinfo,
			(boolean (*) P((struct sport *, boolean))) NULL,
			FALSE))
	{
	  ulog (LOG_ERROR, "%s: No such port", zport);
	  ulog_close ();
	  usysdep_exit (FALSE);
	}
      qport = &sportinfo;
    }

  if (fmaster)
    {
      /* If a system was named, call it up.  Otherwise check all the
	 known systems, and call all the ones which have work to do.  */
      if (zsystem != NULL)
	{
	  if (! fread_system_info (zsystem, &sLocked_system))
	    ulog (LOG_FATAL, "Unknown system %s", zsystem);

	  /* 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 (! fnodetach &&
	      (qport == NULL || qport->ttype != PORTTYPE_STDIN))
	    usysdep_detach ();

	  ulog_system (sLocked_system.zname);

#if DEBUG > 1
	  iholddebug = iDebug;
	  iDebug |= sLocked_system.idebug;
#endif

	  if (! fsysdep_lock_system (&sLocked_system))
	    {
	      ulog (LOG_ERROR, "System already locked");
	      fret = FALSE;
	    }
	  else
	    {
	      fLocked_system = TRUE;
	      fret = fcall (&sLocked_system, qport, fforce, BGRADE_HIGH,
			    fnodetach);
	      if (fLocked_system)
		{
		  (void) fsysdep_unlock_system (&sLocked_system);
		  fLocked_system = FALSE;
		}
	    }

#if DEBUG > 1
	  iDebug = iholddebug;
#endif

	  ulog_system ((const char *) NULL);
	}
      else
	{
	  int csystems;
	  struct ssysteminfo *pas;
	  int i;
	  char bgrade;
	  boolean fdidone;

	  fret = TRUE;
	  fdidone = FALSE;
	  uread_all_system_info (&csystems, &pas);
	  for (i = 0; i < csystems && ! FGOT_SIGNAL (); i++)
	    {
	      if (fsysdep_has_work (&pas[i], &bgrade))
		{
		  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 (! fnodetach
		      && (qport == NULL
			  || qport->ttype != PORTTYPE_STDIN))
		    usysdep_detach ();

		  ulog_system (pas[i].zname);

#if DEBUG > 1
		  iholddebug = iDebug;
		  iDebug |= pas[i].idebug;
#endif

		  if (! fsysdep_lock_system (&pas[i]))
		    {
		      ulog (LOG_ERROR, "System already locked");
		      fret = FALSE;
		    }
		  else
		    {
		      sLocked_system = pas[i];
		      fLocked_system = TRUE;
		      if (! fcall (&pas[i], qport, fforce, bgrade,
				   fnodetach))
			fret = FALSE;

		      /* Now ignore any SIGHUP that we got.  */
		      afSignal[INDEXSIG_SIGHUP] = FALSE;

		      if (fLocked_system)
			{
			  (void) fsysdep_unlock_system (&pas[i]);
			  fLocked_system = FALSE;
			}
		    }

#if DEBUG > 1
		  iDebug = iholddebug;
#endif

		  ulog_system ((const char *) NULL);
		}
	    }

	  if (! fdidone)
	    ulog (LOG_NORMAL, "No work");
	}

      /* If requested, wait for calls after dialing out.  */
      if (fwait)
	{
	  floop = TRUE;
	  fmaster = FALSE;
	}
    }

  if (! fmaster)
    {
      /* 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 (qport != NULL)
	{
	  /* Detach from the controlling terminal, so that the port we
	     are about to use becomes our controlling terminal.  */
	  if (! fnodetach && qport->ttype != PORTTYPE_STDIN)	      
	    usysdep_detach ();

	  floop = TRUE;
	  if (! fport_lock (qport, TRUE))
	    {
	      ulog (LOG_ERROR, "Port %s is locked", qport->zname);
	      fret = FALSE;
	    }
	}

      if (fret)
	{
	  if (! fport_open (qport, (long) 0, (long) 0, TRUE))
	    fret = FALSE;
	}

      if (fret)
	{
	  if (floop)
	    {
	      while (! FGOT_SIGNAL () && flogin_prompt (qport))
		{
		  /* Now ignore any SIGHUP that we got.  */
		  afSignal[INDEXSIG_SIGHUP] = FALSE;

		  if (fLocked_system)
		    {
		      (void) fsysdep_unlock_system (&sLocked_system);
		      fLocked_system = FALSE;
		    }
		  if (! fport_reset ())
		    break;
		}
	      fret = FALSE;
	    }
	  else
	    {
	      if (flogin)
		fret = flogin_prompt (qport);
	      else
		{
		  const struct ssysteminfo *qsys;

#if DEBUG > 1
		  iholddebug = iDebug;
#endif
		  fret = faccept_call (zsysdep_login_name (), qport,
				       &qsys);
		  if (qsys != NULL)
		    zsystem = qsys->zname;
#if DEBUG > 1
		  iDebug = iholddebug;
#endif
		}
	    }

	  (void) fport_close (fret);

	  if (fLocked_system)
	    {
	      (void) fsysdep_unlock_system (&sLocked_system);
	      fLocked_system = FALSE;
	    }
	}
    }

  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 (! fnodetach)
	usysdep_detach ();
      if (zsystem == NULL)
	fret = fsysdep_run (FALSE, "uuxqt", (const char *) NULL,
			    (const char *) NULL);
      else
	fret = fsysdep_run (FALSE, "uuxqt", "-s", zsystem);
    }

  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",
	   abVersion);
  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 (default %s%s)\n",
	   NEWCONFIGLIB, CONFIGFILE);
#endif /* HAVE_TAYLOR_CONFIG */

  exit (EXIT_FAILURE);
}

/* This function is called when a LOG_FATAL error occurs.  */

static void
uabort ()
{
  ustats_failed ();

#if ! HAVE_BNU_LOGGING
  /* When using BNU logging, it's a pain to have no system name.  */
  ulog_system ((const char *) NULL);
#endif

  ulog_user ((const char *) NULL);

  if (qPort != NULL)
    (void) fport_close (FALSE);

  if (fLocked_system)
    {
      (void) fsysdep_unlock_system (&sLocked_system);
      fLocked_system = FALSE;
    }

  ulog_close ();
  ustats_close ();

  usysdep_exit (FALSE);
}

/* Call another system, trying all the possible sets of calling
   instructions.  The fprepare_call function should already have been
   called.  The qsys argument is the system to call.  The qport
   argument is the port to use, and may be NULL.  If the fforce
   argument is TRUE, a call is forced even if not enough time has
   passed since the last failed call.  The bgrade argument is the
   highest grade of work to be done for the system.  The qstat
   argument holds the status of the system.  */

static boolean fcall (qsys, qport, fforce, bgrade, fnodetach)
     const struct ssysteminfo *qsys;
     struct sport *qport;
     boolean fforce;
     int bgrade;
     boolean fnodetach;
{
  boolean fbadtime, fnevertime;
  const struct ssysteminfo *qorigsys;
  struct sstatus sstat;

  qorigsys = qsys;
  if (! fsysdep_get_status (qorigsys, &sstat))
    return FALSE;

  /* Make sure it's been long enough since the last failed call.  */
  if (! fforce)
    {
#ifdef CMAXRETRIES
#if CMAXRETRIES > 0
      if (sstat.cretries >= CMAXRETRIES)
	{
	  ulog (LOG_ERROR, "Too many retries");
	  return FALSE;
	}
#endif /* CMAXRETRIES > 0 */
#endif /* defined (CMAXRETRIES) */

      if (sstat.ttype != STATUS_COMPLETE
	  && sstat.ilast + sstat.cwait > isysdep_time ((long *) NULL))
	{
	  ulog (LOG_NORMAL, "Retry time not reached");
	  return FALSE;
	}
    }

  fbadtime = TRUE;
  fnevertime = TRUE;

  do
    {
      struct sspan *qtime;
      const struct ssysteminfo *qnext;

      qtime = qtimegrade_parse (qsys->ztime);
      if (qtime != NULL)
	{
	  long ival;
	  int cretry;
	  boolean fmatch;

	  fnevertime = FALSE;

	  /* The value returned in ival by fspan_match is the lowest
	     grade which may be done at this time.  */

	  fmatch = (ftimespan_match (qtime, &ival, &cretry)
		    && igradecmp (bgrade, (int) ival) <= 0);

	  utimespan_free (qtime);

	  if (fmatch)
	    {
	      boolean fret, fcalled;
	      struct sport sportinfo;
	  
	      if (FGOT_SIGNAL ())
		return FALSE;

	      fbadtime = FALSE;

	      fret = fdo_call (qsys, qport, &sstat, cretry, &fcalled,
			       &sportinfo);
	      (void) fport_close (fret);

	      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 (! fnodetach)
		{
		  (void) fsysdep_unlock_system (&sLocked_system);
		  fLocked_system = FALSE;
		  usysdep_detach ();
		  if (! fsysdep_lock_system (&sLocked_system))
		    return FALSE;
		  fLocked_system = TRUE;
		}
	    }
	}

      /* Look for the next alternate with different calling
	 instructions.  */
      qnext = qsys;
      do
	{
	  qnext = qnext->qalternate;
	}
      while (qnext != NULL
	     && qsys->ztime == qnext->ztime
	     && qsys->zport == qnext->zport
	     && qsys->qport == qnext->qport
	     && qsys->ibaud == qnext->ibaud
	     && qsys->zphone == qnext->zphone
	     && qsys->schat.zprogram == qnext->schat.zprogram
	     && qsys->schat.zchat == qnext->schat.zchat);

      qsys = qnext;
    }
  while (qsys != NULL);

  if (fbadtime)
    {
      ulog (LOG_ERROR, "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 = isysdep_time ((long *) NULL);
	  sstat.cwait = 0;
	  (void) fsysdep_set_status (qorigsys, &sstat);
	}
    }

  return FALSE;
}

/* Do the actual work of calling another system, such as dialing and
   logging in.  The qsys argument is the system to call, the qport
   argument is the port to use, and the qstat argument holds the
   current status of the ssystem.  If we log in successfully, set
   *pfcalled to TRUE; this is used to distinguish a failed dial from a
   failure during the call.  The quse argument is passed in because
   this function does not call fport_close, so if it reads in a port
   structure to open it must not keep it on the stack.  */

static boolean fdo_call (qsys, qport, qstat, cretry, pfcalled, quse)
     const struct ssysteminfo *qsys;
     struct sport *qport;
     struct sstatus *qstat;
     int cretry;
     boolean *pfcalled;
     struct sport *quse;
{
  const char *zstr;
  boolean fnew;
  int cdial_proto_params;
  struct sproto_param *qdial_proto_params;
  int idial_reliable;
  long istart_time;

  *pfcalled = 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->qport;
  if (qport != NULL)
    {
      if (! fport_lock (qport, FALSE))
	{
	  ulog (LOG_ERROR, "Port \"%s\" already locked", qport->zname);
	  return FALSE;
	}
    }
  else
    {
      if (! ffind_port (qsys->zport, qsys->ibaud, qsys->ihighbaud,
			quse, fport_lock, TRUE))
	return FALSE;
      qport = quse;
      /* The port is locked by ffind_port.  */
    }

  /* Now try to call the system.  */

  if (! fport_open (qport, qsys->ibaud, qsys->ihighbaud, FALSE))
    {
      (void) fcall_failed (qsys, STATUS_PORT_FAILED, qstat, cretry);
      return FALSE;
    }

  if (qsys->zalternate == NULL)
    ulog (LOG_NORMAL, "Calling system %s (port %s)", qsys->zname,
	  zLdevice == NULL ? "unknown" : zLdevice);
  else
    ulog (LOG_NORMAL, "Calling system %s (alternate %s, port %s)",
	  qsys->zname, qsys->zalternate,
	  zLdevice == NULL ? "unknown" : zLdevice);

  cdial_proto_params = 0;
  qdial_proto_params = NULL;
  if (! fport_dial (qsys, &cdial_proto_params, &qdial_proto_params,
		    &idial_reliable))
    {
      (void) fcall_failed (qsys, STATUS_DIAL_FAILED, qstat, cretry);
      return FALSE;
    }

  if (! fchat (&qsys->schat, qsys, (const struct sdialer *) NULL,
	       (const char *) NULL, FALSE, qPort->zname, iport_baud ()))
    {
      (void) fcall_failed (qsys, STATUS_LOGIN_FAILED, qstat, cretry);
      return FALSE;
    }

  qstat->ttype = STATUS_TALKING;
  qstat->ilast = isysdep_time ((long *) NULL);
  qstat->cretries = 0;
  qstat->cwait = 0;
  if (! fsysdep_set_status (qsys, qstat))
    return FALSE;

  ulog (LOG_NORMAL, "Login successful");

  *pfcalled = TRUE;
  istart_time = isysdep_time ((long *) NULL);

  /* We should now see "Shere" from the other system.  Apparently
     some systems send "Shere=foo" where foo is the remote name.  */

  zstr = zget_uucp_cmd (TRUE);
  if (zstr == NULL)
    {
      (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
      return FALSE;
    }

  if (strncmp (zstr, "Shere", 5) != 0)
    {
      (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
      ulog (LOG_ERROR, "Bad initialization string");
      return FALSE;
    }

  if (zstr[5] == '=')
    {
      const char *zheresys;
      int icmp;

      /* Some UUCP packages only provide seven characters in the Shere
	 machine name.  */
      zheresys = zstr + 6;
      if (strlen (zheresys) == 7)
	icmp = strncmp (zheresys, qsys->zname, 7);
      else
	icmp = strcmp (zheresys, qsys->zname);
      if (icmp != 0)
	{
	  (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat,
			       cretry);
	  ulog (LOG_ERROR, "Called wrong system (%s)", zheresys);
	  return FALSE;
	}
    }
#if DEBUG > 1
  else if (zstr[5] != '\0')
    DEBUG_MESSAGE1 (DEBUG_HANDSHAKE,
		    "fdo_call: Strange Shere: %s", zstr);
#endif

  /* 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 to
     indicate that we are prepared to accept file sizes.  */
  {
    char bgrade;
    const char *zuse_local;
    char *zsend;

    /* Determine the grade we should request of the other system.  A
       '\0' means that no restrictions have been made.  */
    bgrade = btimegrade (qsys->zcalltimegrade);

    if (qsys->zlocalname != NULL)
      zuse_local = qsys->zlocalname;
    else
      zuse_local = zLocalname;

    zsend = (char *) alloca (strlen (zuse_local) + 70);
    if (! qsys->fsequence)
      {
	if (bgrade == '\0')
	  sprintf (zsend, "S%s -N", zuse_local);
	else
	  sprintf (zsend, "S%s -p%c -vgrade=%c -N", zuse_local, bgrade,
		   bgrade);
      }
    else
      {
	long iseq;

	iseq = isysdep_get_sequence (qsys);
	if (iseq < 0)
	  {
	    (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat,
				 cretry);
	    return FALSE;
	  }
	if (bgrade == '\0')
	  sprintf (zsend, "S%s -Q%ld -N", zuse_local, iseq);
	else
	  sprintf (zsend, "S%s -Q%ld -p%c -vgrade=%c -N", zuse_local, iseq,
		   bgrade, bgrade);
      }

    if (! fsend_uucp_cmd (zsend))
      {
	(void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat,
			     cretry);
	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.  */
  zstr = zget_uucp_cmd (TRUE);
  if (zstr == NULL)
    {
      (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
      return FALSE;
    }

  if (zstr[0] != 'R')
    {
      (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
      ulog (LOG_ERROR, "Bad reponse to handshake string (%s)",
	    zstr);
      return FALSE;
    }

  if (strcmp (zstr + 1, "OKN") == 0)
    fnew = TRUE;
  else if (strcmp (zstr + 1, "OK") == 0)
    fnew = FALSE;
  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);
      return TRUE;
    }
  else
    {
      (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
      ulog (LOG_ERROR, "Handshake failed (%s)", zstr + 1);
      return FALSE;
    }

  /* 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 (TRUE);
  if (zstr == NULL)
    {
      (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
      return FALSE;
    }

  if (zstr[0] != 'P')
    {
      (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
      ulog (LOG_ERROR, "Bad protocol handshake (%s)", zstr);
      return FALSE;
    }

  /* 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->zprotocols != NULL || qPort->zprotocols != NULL)
      {
	const char *zproto;

	if (qsys->zprotocols != NULL)
	  zproto = qsys->zprotocols;
	else
	  zproto = qPort->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
      {
	int ir;

	/* 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.  If we have no
	   reliability information, we default to a reliable eight bit
	   connection.  */

	ir = 0;
	if ((qPort->ireliable & RELIABLE_SPECIFIED) != 0)
	  ir = qPort->ireliable;
	if ((idial_reliable & RELIABLE_SPECIFIED) != 0)
	  {
	    if (ir != 0)
	      ir &= idial_reliable;
	    else
	      ir = idial_reliable;
	  }
	if (ir == 0)
	  ir = RELIABLE_RELIABLE | RELIABLE_EIGHT | RELIABLE_SPECIFIED;

	for (i = 0; i < CPROTOCOLS; i++)
	  {
	    int ipr;

	    ipr = asProtocols[i].ireliable;
	    if ((ipr & ir) != ipr)
	      continue;
	    if (strchr (zstr + 1, asProtocols[i].bname) != NULL)
	      break;
	  }
      }

    if (i >= CPROTOCOLS)
      {
	(void) fsend_uucp_cmd ("UN");
	(void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
	ulog (LOG_ERROR, "No mutually supported protocols");
	return FALSE;
      }

    qProto = &asProtocols[i];

    sprintf (ab, "U%c", qProto->bname);
    if (! fsend_uucp_cmd (ab))
      {
	(void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat, cretry);
	return FALSE;
      }
  }

  /* Run any protocol parameter commands.  */

  if (qProto->qcmds != NULL)
    {
      if (qsys->cproto_params != 0)
	uapply_proto_params (qProto->bname, qProto->qcmds,
			     qsys->cproto_params, qsys->qproto_params);
      if (qPort->cproto_params != 0)
	uapply_proto_params (qProto->bname, qProto->qcmds,
			     qPort->cproto_params, qPort->qproto_params);
      if (cdial_proto_params != 0)
	uapply_proto_params (qProto->bname, qProto->qcmds,
			     cdial_proto_params, qdial_proto_params);
    }

  /* Turn on the selected protocol.  */

  if (! (*qProto->pfstart) (TRUE))
    {
      (void) fcall_failed (qsys, STATUS_HANDSHAKE_FAILED, qstat,
			   cretry);
      return FALSE;
    }

  /* Now we have succesfully logged in as the master.  */

  ulog (LOG_NORMAL, "Handshake successful");

  {
    boolean fret;
    long iend_time;

    fret = fuucp (TRUE, qsys, '\0', fnew, (long) -1);
    ulog_user ((const char *) NULL);
    usysdep_get_work_free (qsys);

    /* If we jumped out due to an error, shutdown the protocol.  */
    if (! fret)
      {
	(void) (*qProto->pfshutdown) ();
	ustats_failed ();
      }

    /* 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 ("OOOOOO")
	&& fsend_uucp_cmd ("OOOOOO"))
      {
	/* We don't even look for the hangup string from the other
	   side unless we're in debugging mode.  */
#if DEBUG > 1
	if (fret && FDEBUGGING (DEBUG_HANDSHAKE))
	  {
	    zstr = zget_uucp_cmd (FALSE);
	    if (zstr != NULL)
	      {
		/* The Ultrix UUCP only sends six O's, although I
		   think it should send seven.  Because of this, we
		   only check for six.  */
		if (strstr (zstr, "OOOOOO") == NULL)
		  ulog (LOG_DEBUG, "No hangup from remote");
	      }
	  }
#endif
      }

    iend_time = isysdep_time ((long *) NULL);

    ulog (LOG_NORMAL, "Call complete (%ld seconds)",
	  iend_time - istart_time);

    if (! fret)
      {
	(void) fcall_failed (qsys, STATUS_FAILED, qstat, cretry);
	return FALSE;
      }
    else
      {
	qstat->ttype = STATUS_COMPLETE;
	qstat->ilast = iend_time;
	(void) fsysdep_set_status (qsys, qstat);
	return TRUE;
      }
  }
}

/* A small helper routine to write out the system status when something
   goes wrong.  */

static boolean
fcall_failed (qsys, twhy, qstat, cretry)
     const struct ssysteminfo *qsys;
     enum tstatus_type twhy;
     struct sstatus *qstat;
     int cretry;
{
  DEBUG_MESSAGE2 (DEBUG_HANDSHAKE,
		  "fcall_failed: Cause %d (%s)", (int) twhy,
		  azStatus[(int) twhy]);

  qstat->ttype = twhy;
  qstat->cretries++;
  qstat->ilast = isysdep_time ((long *) NULL);
  if (cretry == 0)
    qstat->cwait = CRETRY_WAIT (qstat->cretries);
  else
    qstat->cwait = cretry * 60;
  return fsysdep_set_status (qsys, qstat);
}

/* Prompt for a login name and a password, and run as the slave.  */

static boolean flogin_prompt (qport)
     struct sport *qport;
{
  const char *zuser, *zpass;

  DEBUG_MESSAGE0 (DEBUG_HANDSHAKE, "flogin_prompt: Waiting for login");

  do
    {
      if (! fport_write ("login: ", sizeof "login: " - 1))
	return FALSE;
      zuser = zget_typed_line ();
    }
  while (zuser != NULL && *zuser == '\0');

  if (zuser != NULL)
    {
      char *zhold;

      zhold = (char *) alloca (strlen (zuser) + 1);
      strcpy (zhold, zuser);

      if (! fport_write ("Password:", sizeof "Password:" - 1))
	return FALSE;

      zpass = zget_typed_line ();
      if (zpass != NULL)
	{
	  if (fcheck_login (zhold, zpass))
	    {
#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 (zhold, qport,
				   (const struct ssysteminfo **) NULL);
#if DEBUG > 1
	      iDebug = iholddebug;
#endif
	    }
	}
    }

  return TRUE;
}

/* 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 (zlogin, qport, pqsys)
     const char *zlogin;
     struct sport *qport;
     const struct ssysteminfo **pqsys;
{
  long istart_time;
  int cdial_proto_params;
  struct sproto_param *qdial_proto_params;
  int idial_reliable;
  boolean ftcp_port;
  const char *zport;
  char *zsend, *zspace;
  const char *zstr;
  struct ssysteminfo ssys;
  const struct ssysteminfo *qsys;
  boolean fnew;
  char bgrade;
  const char *zuse_local;
  struct sstatus sstat;
  long cmax_receive;
  boolean frestart;
#if HAVE_TAYLOR_CONFIG
  struct sport sportinfo;
#endif

  if (pqsys != NULL)
    *pqsys = NULL;

  ulog (LOG_NORMAL, "Incoming call (login %s port %s)",
	zlogin == NULL ? "unknown" : zlogin,
	zLdevice == NULL ? "unknown" : zLdevice);

  istart_time = isysdep_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 (qport != NULL)
    {
      zport = qport->zname;
      ftcp_port = FALSE;
    }
  else
    {
      zport = zsysdep_port_name (&ftcp_port);

      /* We want to get the protocol parameters for the port.  If we
	 aren't using HAVE_TAYLOR_CONFIG, that information isn't
	 stored anyhow, so we don't bother to look it up.  */

#if HAVE_TAYLOR_CONFIG

      if (zport != NULL && zPortfile != NULL)
	{
	  if (ffind_port (zport, (long) 0, (long) 0, &sportinfo,
			  (boolean (*) P((struct sport *, boolean))) NULL,
			  FALSE))
	    qport = &sportinfo;
	}

#endif /* HAVE_TAYLOR_CONFIG */

      if (zport == NULL)
	zport = "unknown";
    }

  /* If we've managed to figure out that this is a modem port, now try
     to get protocol parameters from the dialer.  */

  cdial_proto_params = 0;
  qdial_proto_params = NULL;
  idial_reliable = 0;
  if (qport != NULL)
    {
      if (qport->ttype == PORTTYPE_MODEM)
	{
	  if (qport->u.smodem.zdialer != NULL)
	    {
	      char *zcopy;
	      char *zdial;
	      struct sdialer sdialerinfo;

	      /* We use the first dialer in the sequence.  */
	      zcopy = (char *) alloca (strlen (qport->u.smodem.zdialer)
				       + 1);
	      strcpy (zcopy, qport->u.smodem.zdialer);

	      zdial = strtok (zcopy, " \t");
	      if (fread_dialer_info (zdial, &sdialerinfo))
		{
		  cdial_proto_params = sdialerinfo.cproto_params;
		  qdial_proto_params = sdialerinfo.qproto_params;
		  idial_reliable = sdialerinfo.ireliable;
		}
	    }
	  else if (qport->u.smodem.qdialer != NULL)
	    {
	      cdial_proto_params = qport->u.smodem.qdialer->cproto_params;
	      qdial_proto_params = qport->u.smodem.qdialer->qproto_params;
	      idial_reliable = qport->u.smodem.qdialer->ireliable;
	    }
	}	  
#if HAVE_TCP
      else if (qport->ttype == PORTTYPE_TCP)
	ftcp_port = TRUE;
#endif
    }

  /* If it's a TCP port, it's fully reliable.  Even if HAVE_TCP is not
     supported, zsysdep_port_name may be able to figure this out (not
     on Unix, though).  */
  if (ftcp_port)
    idial_reliable = (RELIABLE_SPECIFIED | RELIABLE_ENDTOEND
		      | RELIABLE_RELIABLE | RELIABLE_EIGHT);

  /* We have to check to see whether some system uses this login name
     to indicate a different local name.  Obviously, this means that
     any system which uses this login name must expect the alternate
     system name.  */
  zuse_local = NULL;
  if (fUnknown_ok)
    {
      for (qsys = &sUnknown; qsys != NULL;  qsys = qsys->qalternate)
	{
	  if (qsys->zlocalname != NULL
	      && qsys->zcalled_login != NULL
	      && strcmp (qsys->zcalled_login, zlogin) == 0)
	    {
	      zuse_local = qsys->zlocalname;
	      break;
	    }
	}
    }
  if (zuse_local == NULL)
    {
      struct ssysteminfo *pas;
      int isys, csystems;

      zuse_local = zLocalname;

      uread_all_system_info (&csystems, &pas);
      for (isys = 0; isys < csystems; isys++)
	{
	  for (qsys = &pas[isys]; qsys != NULL; qsys = qsys->qalternate)
	    {
	      if (qsys->zlocalname != NULL
		  && qsys->zcalled_login != NULL
		  && strcmp (qsys->zcalled_login, zlogin) == 0)
		{
		  zuse_local = qsys->zlocalname;
		  break;
		}
	    }
	  if (qsys != NULL)
	    break;
	}
    }

  /* Tell the remote system who we are.   */
  zsend = (char *) alloca (strlen (zuse_local) + 10);
  sprintf (zsend, "Shere=%s", zuse_local);
  if (! fsend_uucp_cmd (zsend))
    return FALSE;

  zstr = zget_uucp_cmd (TRUE);
  if (zstr == NULL)
    return FALSE;

  if (zstr[0] != 'S')
    {
      ulog (LOG_ERROR, "Bad introduction string");
      return FALSE;
    }
  ++zstr;

  zspace = strchr (zstr, ' ');
  if (zspace != NULL)
    *zspace = '\0';
  if (fread_system_info (zstr, &ssys))
    qsys = &ssys;
  else
    {
      /* We have no information on this system.  */
      if (! fUnknown_ok)
	{
	  (void) fsend_uucp_cmd ("RYou are unknown to me");
	  ulog (LOG_ERROR, "Call from unknown system %s", zstr);
	  return FALSE;
	}

      /* We have to translate the name to a canonical form for the
	 benefit of systems which only allow short system names.  */
      sUnknown.zname = ztranslate_system (zstr);
      if (sUnknown.zname == NULL)
	{
	  (void) fsend_uucp_cmd ("RYou are unknown to me");
	  return FALSE;
	}

      qsys = &sUnknown;
    }

  if (pqsys != NULL)
    *pqsys = qsys;

  if (! fcheck_validate (zlogin, qsys->zname))
    {
      (void) fsend_uucp_cmd ("RLOGIN");
      ulog (LOG_ERROR, "System %s used wrong login name %s",
	    zstr, zlogin);
      return FALSE;
    }

  if (qsys->zcalled_login != NULL)
    {
      const struct ssysteminfo *qany;

      /* Choose an alternate system definition based on the
	 login name.  */
      qany = NULL;
      for (; qsys != NULL; qsys = qsys->qalternate)
	{
	  if (qsys->zcalled_login != NULL)
	    {
	      if (qany == NULL
		  && strcmp (qsys->zcalled_login, "ANY") == 0)
		qany = qsys;
	      else if (strcmp (qsys->zcalled_login, zlogin) == 0)
		break;
	    }
	}

      if (qsys == NULL)
	{
	  if (qany == NULL)
	    {
	      (void) fsend_uucp_cmd ("RLOGIN");
	      ulog (LOG_ERROR, "System %s used wrong login name %s",
		    zstr, zlogin);
	      return FALSE;
	    }
	  qsys = qany;
	}
    }

  ulog_system (qsys->zname);

#if DEBUG > 1
  iDebug |= qsys->idebug;
#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->fcallback)
    {
      (void) fsend_uucp_cmd ("RCB");
      ulog (LOG_NORMAL, "Will call back");
      (void) zsysdep_spool_commands (qsys, BGRADE_HIGH, 0,
				     (const struct scmd *) NULL);
      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 ("RLCK");
      ulog (LOG_ERROR, "System already locked");
      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 = isysdep_time ((long *) NULL);
  sstat.cwait = 0;
  (void) fsysdep_set_status (qsys, &sstat);

  /* Check the arguments of the remote system.  We accept -x# to set
     our debugging level and -Q# for a sequence number.  We may insist
     on a sequence number.  The -p and -vgrade= arguments are taken to
     specify the lowest job grade that we should transfer; I think
     this is the traditional meaning, but I don't know.  The -N switch
     means that we are talking to another instance of ourselves.  The
     -U switch specifies the ulimit of the remote system, which we
     treat as the maximum file size that may be sent.  The -R switch
     means that the remote system supports file restart; we don't.  */

  fnew = FALSE;
  bgrade = BGRADE_LOW;
  cmax_receive = (long) -1;
  frestart = FALSE;

  if (zspace == NULL)
    {
      if (qsys->fsequence)
	{
	  (void) fsend_uucp_cmd ("RBADSEQ");
	  ulog (LOG_ERROR, "No sequence number (call rejected)");
	  sstat.ttype = STATUS_FAILED;
	  (void) fsysdep_set_status (qsys, &sstat);
	  return FALSE;
	}
    }
  else
    {
      ++zspace;
      while (isspace (BUCHAR (*zspace)))
	++zspace;

      while (*zspace != '\0')
	{
	  boolean frecognized;
	  char *znext;
	  
	  frecognized = FALSE;
	  if (*zspace == '-')
	    {
	      switch (zspace[1])
		{
		case 'x':
		  frecognized = TRUE;
#if DEBUG > 1
		  {
		    int iwant;

		    iwant = atoi (zspace + 2);
		    if (! fnew)
		      iwant = (1 << iwant) - 1;
		    iwant &= qsys->imax_remote_debug;
		    if ((iDebug | iwant) != iDebug)
		      {
			iDebug |= iwant;
			ulog (LOG_NORMAL, "Setting debugging mode to 0%o",
			      iDebug);
		      }
		  }
#endif
		  break;
		case 'Q':
		  frecognized = TRUE;
		  {
		    long iseq;

		    if (! qsys->fsequence)
		      break;
		    iseq = atol (zspace + 2);
		    if (iseq != isysdep_get_sequence (qsys))
		      {
			(void) fsend_uucp_cmd ("RBADSEQ");
			ulog (LOG_ERROR, "Out of sequence call rejected");
			sstat.ttype = STATUS_FAILED;
			(void) fsysdep_set_status (qsys, &sstat);
			return FALSE;
		      }
		  }
		  break;
		case 'p':
		  /* We don't accept a space between the -p and the
		     grade, although we should.  */
		  frecognized = TRUE;
		  if (FGRADE_LEGAL (zspace[2]))
		    bgrade = zspace[2];
		  break;
		case 'v':
		  if (strncmp (zspace + 1, "vgrade=",
			       sizeof "vgrade=" - 1) == 0)
		    {
		      frecognized = TRUE;
		      if (FGRADE_LEGAL (zspace[sizeof "vgrade="]))
			bgrade = zspace[sizeof "vgrade="];
		    }
		  break;
		case 'N':
		  frecognized = TRUE;
		  fnew = TRUE;
		  break;
		case 'U':
		  frecognized = TRUE;
		  {
		    long c;

		    c = strtol (zspace + 2, (char **) NULL, 0);
		    if (c > 0)
		      cmax_receive = c * (long) 512;
		  }
		  break;
		case 'R':
		  frecognized = TRUE;
		  frestart = TRUE;
		  break;
		default:
		  break;
		}
	    }

	  znext = zspace;
	  while (*znext != '\0' && ! isspace (BUCHAR (*znext)))
	    ++znext;

	  if (! frecognized)
	    {
	      int clen;
	      char *zcopy;

	      /* We could just use %.*s for this, but it's probably
		 not portable.  */
	      clen = znext - zspace;
	      zcopy = (char *) alloca (clen + 1);
	      strncpy (zcopy, zspace, clen);
	      zcopy[clen] = '\0';
	      ulog (LOG_NORMAL, "Unrecognized argument %s", zcopy);
	    }

	  zspace = znext;
	  while (isspace (BUCHAR (*zspace)))
	    ++zspace;
	}
    }

  /* 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 (! fsend_uucp_cmd (fnew ? "ROKN" : "ROK"))
    {
      sstat.ttype = STATUS_FAILED;
      (void) fsysdep_set_status (qsys, &sstat);
      return FALSE;
    }

  {
    int i;
   
    if (qsys->zprotocols != NULL ||
	(qport != NULL && qport->zprotocols != NULL))
      {
	const char *zprotos;

	if (qsys->zprotocols != NULL)
	  zprotos = qsys->zprotocols;
	else
	  zprotos = qport->zprotocols;
	zsend = (char *) alloca (strlen (zprotos) + 2);
	sprintf (zsend, "P%s", zprotos);
      }
    else
      {
	char *zset;
	int ir;

	zsend = (char *) alloca (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.  If we have no information, we default
	   to a reliable eight bit connection.  */

	ir = 0;
	if (qport != NULL
	    && (qport->ireliable & RELIABLE_SPECIFIED) != 0)
	  ir = qport->ireliable;
	if ((idial_reliable & RELIABLE_SPECIFIED) != 0)
	  {
	    if (ir != 0)
	      ir &= idial_reliable;
	    else
	      ir = idial_reliable;
	  }
	if (ir == 0)
	  ir = RELIABLE_RELIABLE | RELIABLE_EIGHT | RELIABLE_SPECIFIED;

	for (i = 0; i < CPROTOCOLS; i++)
	  {
	    int ipr;

	    ipr = asProtocols[i].ireliable;
	    if ((ipr & ir) != ipr)
	      continue;
	    *zset++ = asProtocols[i].bname;
	  }
	*zset = '\0';
      }

    if (! fsend_uucp_cmd (zsend))
      {
	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 (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);
	return FALSE;
      }

    if (zstr[1] == 'N')
      {
	ulog (LOG_ERROR, "No supported protocol");
	sstat.ttype = STATUS_FAILED;
	(void) fsysdep_set_status (qsys, &sstat);
	return FALSE;
      }

    for (i = 0; i < CPROTOCOLS; i++)
      if (asProtocols[i].bname == zstr[1])
	break;

    if (i >= CPROTOCOLS)
      {
	ulog (LOG_ERROR, "No supported protocol");
	sstat.ttype = STATUS_FAILED;
	(void) fsysdep_set_status (qsys, &sstat);
	return FALSE;
      }

    qProto = &asProtocols[i];
  }

  /* Run the chat script for when a call is received.  */

  if (! fchat (&qsys->scalled_chat, qsys, (const struct sdialer *) NULL,
	       (const char *) NULL, FALSE, zport, iport_baud ()))
    {
      sstat.ttype = STATUS_FAILED;
      sstat.ilast = isysdep_time ((long *) NULL);
      (void) fsysdep_set_status (qsys, &sstat);
      return FALSE;
    }

  /* Run any protocol parameter commands.  There should be a way to
     read the dialer information if there is any to permit modem
     specific protocol parameters, but for now there isn't.  */
  
  if (qProto->qcmds != NULL)
    {
      if (qsys->cproto_params != 0)
	uapply_proto_params (qProto->bname, qProto->qcmds,
			     qsys->cproto_params, qsys->qproto_params);
      if (qport != NULL
	  && qport->cproto_params != 0)
	uapply_proto_params (qProto->bname, qProto->qcmds,
			     qport->cproto_params, qport->qproto_params);
      if (cdial_proto_params != 0)
	uapply_proto_params (qProto->bname, qProto->qcmds,
			     cdial_proto_params, qdial_proto_params);
    }

  /* Turn on the selected protocol.  */

  if (! (*qProto->pfstart)(FALSE))
    {
      sstat.ttype = STATUS_FAILED;
      sstat.ilast = isysdep_time ((long *) NULL);
      (void) fsysdep_set_status (qsys, &sstat);
      return FALSE;
    }

  /* If we using HAVE_BNU_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_BNU_LOGGING
  if (bgrade == BGRADE_LOW)
    ulog (LOG_NORMAL, "Handshake successful (login %s port %s)",
	  zlogin == NULL ? "unknown" : zlogin,
	  zLdevice == NULL ? "unknown" : zLdevice);
  else
    ulog (LOG_NORMAL, "Handshake successful (login %s port %s grade %c)",
	  zlogin == NULL ? "unknown" : zlogin,
	  zLdevice == NULL ? "unknown" : zLdevice,
	  bgrade);
#else /* ! HAVE_BNU_LOGGING */
  if (bgrade == BGRADE_LOW)
    ulog (LOG_NORMAL, "Handshake successful");
  else
    ulog (LOG_NORMAL, "Handshake successful (grade %c)", bgrade);
#endif /* ! HAVE_BNU_LOGGING */

  {
    boolean fret;
    long iend_time;

    fret = fuucp (FALSE, qsys, bgrade, fnew, cmax_receive);
    ulog_user ((const char *) NULL);
    usysdep_get_work_free (qsys);

    /* If we bombed out due to an error, shut down the protocol.  */
    if (! fret)
      {
	(void) (*qProto->pfshutdown) ();
	ustats_failed ();
      }

    /* Hangup.  As the answerer, we send seven O's and expect to see
       six.  */
    if (fsend_uucp_cmd ("OOOOOOO")
	&& fsend_uucp_cmd ("OOOOOOO"))
      {
	/* We don't even look for the hangup string from the other
	   side unless we're in debugging mode.  */
#if DEBUG > 1
	if (fret && FDEBUGGING (DEBUG_HANDSHAKE))
	  {
	    zstr = zget_uucp_cmd (FALSE);
	    if (zstr != NULL)
	      {
		if (strstr (zstr, "OOOOOO") == NULL)
		  ulog (LOG_DEBUG, "No hangup from remote");
	      }
	  }
#endif
      }

    iend_time = isysdep_time ((long *) NULL);

    ulog (LOG_NORMAL, "Call complete (%ld seconds)",
	  iend_time - istart_time);
    if (fret)
      sstat.ttype = STATUS_COMPLETE;
    else
      sstat.ttype = STATUS_FAILED;
    sstat.ilast = iend_time;
    (void) fsysdep_set_status (qsys, &sstat);
    return fret;
  }
}

/* This function runs the main UUCP protocol.  It is called when the
   two systems have succesfully connected.  It transfers files back
   and forth until neither system has any more work to do.  The
   traditional UUCP protocol has a master which sends files to the
   slave or requests files from the slave (a single file is requested
   with the R command; a wildcarded file name is requested with the X
   command).  The slave simply obeys the commands of the master.  When
   the master has done all its work, it requests a hangup.  If the
   slave has work to do it refuses the hangup and becomes the new
   master.

   This is essentially a half-duplex connection, in that files are
   only transferred in one direction at a time.  This is not
   unreasonable, since generally one site is receiving a lot of news
   from the other site, and I believe that Telebit modems are
   basically half-duplex in that it takes a comparatively long time to
   turn the line around.  However, it is possible to design a
   full-duplex protocol which would be useful in some situtations when
   using V.32 (or a network) and this function attempts to support
   this possibility.

   Traditionally the work to be done is kept in a set of files whose
   names begin with C.[system][grade][pid], where system is the remote
   system name, grade is the grade of transfer, and pid makes the file
   name unique.  Each line in these files is a command, and each line
   can be treated independently.  We let the system dependent layer
   handle all of this.  This will let us use some other scheme on
   systems in which the fourteen character filename length limit
   restricts the name of the remote system to seven characters (the
   usual restriction cited is six characters; I do not yet know where
   this comes from).

   Here are the types of commands, along with the definitions of the
   variables they use in the fuucp function.

   'S' -- Send a file from master to slave.
     zfrom -- master file name
     zto -- slave file name
     zuser -- user who requested the transfer
     zoptions -- list of options
     ztemp -- temporary file name on master (used unless option c)
     imode -- mode to give file
     znotify -- user to notify (if option n)

     The options are:
     C -- file copied to spool (use ztemp rather than zfrom)
     c -- file not copied to spool (use zfrom rather than ztemp)
     d -- create directories if necessary
     f -- do not create directories
     m -- notify originator (in zuser) when complete
     n -- notify recipient (in znotify) when complete

     I assume that the n option is implemented by the remote system.

   'R' -- Retrieve a file from slave to master.
     zfrom -- slave file name
     zto -- master file name
     zuser -- user who requested the transfer
     zoptions -- list of options

     The options are the same as in case 'S', except that option n is
     not supported.  If zto is a directory, we must create a file in
     that directory using the last component of zfrom.

   'X' -- Execute wildcard transfer from slave to master.
     zfrom -- wildcard file name
     zto -- local file (hopefully a directory)
     zuser -- user who requested the transfer
     zoptions -- list of options

     The options are presumably the same as in case 'R'.  It may be
     permissible to have no zuser or zoptions.  The zto name will have
     local! prepended to it already (where local is the local system
     name).

     This command is merely sent over to the remote system, where it
     is executed.  When the remote system becomes the master, it sends
     the files back.

   'H' -- Hangup
     This is used by the master to indicate a transfer of control.  If
     slave has nothing to do, it responds with HY and the conversation
     is finished.  Otherwise, the slave becomes the master, and
     vice-versa.  */

static boolean
fuucp (fmaster, qsys, bgrade, fnew, cmax_receive)
     boolean fmaster;
     const struct ssysteminfo *qsys;
     int bgrade;
     boolean fnew;
     long cmax_receive;
{
  boolean fcaller, fmasterdone, fnowork;
  const char *zlocal_size, *zremote_size;
  long clocal_size, cremote_size, cmax_ever;

  fcaller = fmaster;

  fmasterdone = FALSE;
  if (! qProto->ffullduplex && ! fmaster)
    fmasterdone = TRUE;

  /* Make sure we have a spool directory for this system.  */
  if (! fsysdep_make_spool_dir (qsys))
    return FALSE;

  /* If we are not the caller, the grade will be passed in as an
     argument.  If we are the caller, we compute the grade in this
     function so that we can recompute if time has passed.  */

  if (fcaller)
    bgrade = btimegrade (qsys->ztime);

  if (bgrade == '\0')
    fnowork = TRUE;
  else
    {
      if (! fsysdep_get_work_init (qsys, bgrade, FALSE))
	return FALSE;
      fnowork = FALSE;
    }

  /* Determine the maximum sizes we can send and receive.  */

  if (fcaller)
    {
      zlocal_size = qsys->zcall_local_size;
      zremote_size = qsys->zcall_remote_size;
    }
  else
    {
      zlocal_size = qsys->zcalled_local_size;
      zremote_size = qsys->zcalled_remote_size;
    }

  clocal_size = cmax_size_now (zlocal_size);
  cremote_size = cmax_size_now (zremote_size);
  cmax_ever = (long) -2;

  /* Loop while we have local commands to execute and while we receive
     remote commands.  */

  while (TRUE)
    {
#if ! HAVE_ALLOCA
      /* This only works if we know that no caller of this function is
	 holding an alloca'ed pointer.  */
      (void) alloca (0);
#endif

#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

      /* We send a command to the remote system if
	 we are the master or
	 this is full duplex protocol which is ready for a command and
	 we haven't finished executing commands.  */
      if (fmaster ||
	  (qProto->ffullduplex && ! fmasterdone))
	{
	  struct scmd s;
	  const char *zmail, *zuse;
	  boolean fspool, fnever;
	  openfile_t e = EFILECLOSED;
	  boolean fgone;

	  /* Get the next work line for this system.  All the arguments
	     are left pointing into a static buffer, so they must be
	     copied out before the next call.  */
	  ulog_user ((const char *) NULL);
	  if (fnowork)
	    s.bcmd = 'H';
	  else
	    {
	      s.zuser = NULL;
	      if (! fsysdep_get_work (qsys, bgrade, FALSE, &s))
		return FALSE;
	      ulog_user (s.zuser);
	    }

	  switch (s.bcmd)
	    {
	    case 'S':
	      /* Send a file.  */

	      fspool = fspool_file (s.zfrom);

	      if (! fspool)
		{
		  zuse = zsysdep_real_file_name (qsys, s.zfrom,
						 (const char *) NULL);
		  if (zuse == NULL)
		    {
		      (void) fmail_transfer (FALSE, s.zuser,
					     (const char *) NULL,
					     "cannot form file name",
					     s.zfrom, zLocalname,
					     s.zto, qsys->zname,
					     (const char *) NULL);
		      (void) fsysdep_did_work (s.pseq);
		      break;
		    }

		  /* The 'C' option means that the file has been
		     copied to the spool directory.  */
		  if (strchr (s.zoptions, 'C') != NULL)
		    fspool = TRUE;

		  if (! fok_to_send (zuse, TRUE, fcaller, fspool, qsys,
				     s.zuser))
		    {
		      ulog (LOG_ERROR, "Not permitted to send %s", zuse);
		      (void) fmail_transfer (FALSE, s.zuser,
					     (const char *) NULL,
					     "not permitted to send",
					     s.zfrom, zLocalname,
					     s.zto, qsys->zname,
					     (const char *) NULL);
		      (void) fsysdep_did_work (s.pseq);
		      break;
		    }

		  /* If we're copying the real file, use its mode
		     directly rather than the mode copied into the
		     command file.  */
		  if (! fspool)
		    e = esysdep_open_send (qsys, zuse, TRUE, s.zuser,
					   &s.imode, &s.cbytes, &fgone);
		}

	      if (fspool)
		{
		  unsigned int idummy;

		  zuse = zsysdep_spool_file_name (qsys, s.ztemp);
		  if (zuse == NULL)
		    {
		      (void) fmail_transfer (FALSE, s.zuser,
					     (const char *) NULL,
					     "cannot form file name",
					     s.zfrom, zLocalname,
					     s.zto, qsys->zname,
					     (const char *) NULL);
		      (void) fsysdep_did_work (s.pseq);
		      break;
		    }
		  e = esysdep_open_send (qsys, zuse, FALSE,
					 (const char *) NULL, &idummy,
					 &s.cbytes, &fgone);
		}

	      if (! ffileisopen (e))
		{
		  /* If the file does not exist, fgone will be set to
		     TRUE.  In this case we might have sent the file
		     the last time we talked to the remote system,
		     because we might have been interrupted in the
		     middle of a command file.  To avoid confusion, we
		     don't send a mail message.  */
		  if (! fgone)
		    (void) fmail_transfer (FALSE, s.zuser,
					   (const char *) NULL,
					   "cannot open file",
					   s.zfrom, zLocalname,
					   s.zto, qsys->zname,
					   (const char *) NULL);
		  (void) fsysdep_did_work (s.pseq);
		  break;
		}

	      if (s.cbytes != -1)
		{
		  boolean fsmall;
		  const char *zerr;

		  fsmall = FALSE;
		  fnever = FALSE;
		  zerr = NULL;

		  if (cmax_receive != -1 && cmax_receive < s.cbytes)
		    {
		      fsmall = TRUE;
		      fnever = TRUE;
		      zerr = "too large for receiver";
		    }
		  else if (clocal_size != -1 && clocal_size < s.cbytes)
		    {
		      fsmall = TRUE;

		      if (cmax_ever == -2)
			{
			  long c1, c2;

			  c1 = cmax_size_ever (qsys->zcall_local_size);
			  c2 = cmax_size_ever (qsys->zcalled_local_size);
			  if (c1 > c2)
			    cmax_ever = c1;
			  else
			    cmax_ever = c2;
			}
		      
		      if (cmax_ever == -1 || cmax_ever >= s.cbytes)
			zerr = "too large to send now";
		      else
			{
			  fnever = TRUE;
			  zerr = "too large to send";
			}
		    }

		  if (fsmall)
		    {
		      ulog (LOG_ERROR, "File %s is %s", s.zfrom, zerr);

		      if (fnever)
			{
			  const char *zsaved;

			  zsaved = zsysdep_save_temp_file (s.pseq);
			  (void) fmail_transfer (FALSE, s.zuser,
						 (const char *) NULL,
						 zerr,
						 s.zfrom, zLocalname,
						 s.zto, qsys->zname,
						 zsaved);
			  (void) fsysdep_did_work (s.pseq);
			}

		      (void) ffileclose (e);
		      break;
		    }
		}

	      ulog (LOG_NORMAL, "Sending %s", s.zfrom);

	      /* The send file function is responsible for notifying
		 the user upon success (if option m) or failure, and
		 for closing the file.  This allows it to not complete
		 immediately.  */
	      if (strchr (s.zoptions, 'm') == NULL)
		zmail = NULL;
	      else
		zmail = s.zuser;
				      
	      if (! fsend_file (TRUE, e, &s, zmail, qsys->zname, fnew))
		return FALSE;

	      break;

	    case 'R':
	      /* Receive a file.  */

	      if (fspool_file (s.zto))
		{
		  /* Normal users are not allowed to receive files in
		     the spool directory, and to make it particularly
		     difficult we require a special option '9'.  This
		     is used only by uux when a file must be requested
		     from one system and then sent to another.  */
		  if (s.zto[0] != 'D'
		      || strchr (s.zoptions, '9') == NULL)
		    {
		      ulog (LOG_ERROR, "Not permitted to receive %s",
			    s.zto);
		      (void) fmail_transfer (FALSE, s.zuser,
					     (const char *) NULL,
					     "not permitted to receive",
					     s.zfrom, qsys->zname,
					     s.zto, zLocalname,
					     (const char *) NULL);
		      (void) fsysdep_did_work (s.pseq);
		      break;
		    }

		  zuse = zsysdep_spool_file_name (qsys, s.zto);
		  if (zuse == NULL)
		    {
		      (void) fmail_transfer (FALSE, s.zuser,
					     (const char *) NULL,
					     "cannot form file name",
					     s.zfrom, qsys->zname,
					     s.zto, zLocalname,
					     (const char *) NULL);
		      (void) fsysdep_did_work (s.pseq);
		      break;
		    }
		}
	      else
		{
		  zuse = zsysdep_real_file_name (qsys, s.zto, s.zfrom);
		  if (zuse == NULL)
		    {
		      (void) fmail_transfer (FALSE, s.zuser,
					     (const char *) NULL,
					     "cannot form file name",
					     s.zfrom, qsys->zname,
					     s.zto, zLocalname,
					     (const char *) NULL);
		      (void) fsysdep_did_work (s.pseq);
		      break;
		    }

		  /* Check permissions.  */
		  if (! fok_to_receive (zuse, TRUE, fcaller, qsys, s.zuser))
		    {
		      ulog (LOG_ERROR, "Not permitted to receive %s",
			    s.zto);
		      (void) fmail_transfer (FALSE, s.zuser,
					     (const char *) NULL,
					     "not permitted to receive",
					     s.zfrom, qsys->zname,
					     s.zto, zLocalname,
					     (const char *) NULL);
		      (void) fsysdep_did_work (s.pseq);
		      break;
		    }

		  /* The 'f' option means that directories should not
		     be created if they do not already exist.  */
		  if (strchr (s.zoptions, 'f') == NULL)
		    {
		      if (! fsysdep_make_dirs (zuse, TRUE))
			{
			  (void) fmail_transfer (FALSE, s.zuser,
						 (const char *) NULL,
						 "cannot create directories",
						 s.zfrom, qsys->zname,
						 s.zto, zLocalname,
						 (const char *) NULL);
			  (void) fsysdep_did_work (s.pseq);
			  break;
			}
		    }
		}

	      e = esysdep_open_receive (qsys, zuse, &s.ztemp, &s.cbytes);
	      if (! ffileisopen (e))
		{
		  (void) fmail_transfer (FALSE, s.zuser,
					 (const char *) NULL,
					 "cannot open file",
					 s.zfrom, qsys->zname,
					 s.zto, zLocalname,
					 (const char *) NULL);
		  (void) fsysdep_did_work (s.pseq);
		  break;
		}

	      /* Here s.cbytes represents the amount of free space we
		 have.  We want to adjust it by the amount of free
		 space permitted for this system.  If there is a
		 maximum transfer size, we may want to use that as an
		 amount of free space.  */
	      if (s.cbytes != -1)
		{
		  s.cbytes -= qsys->cfree_space;
		  if (s.cbytes < 0)
		    s.cbytes = 0;
		}

	      if (clocal_size != -1
		  && (s.cbytes == -1 || clocal_size < s.cbytes))
		s.cbytes = clocal_size;

	      ulog (LOG_NORMAL, "Receiving %s", zuse);
	      s.zto = zuse;

	      /* As with the send routine, this function is
		 responsible for mailing a message to the user on
		 failure or on success if the m option is set, and is
		 also responsible for closing the file.  */
	      if (strchr (s.zoptions, 'm') == NULL)
		zmail = NULL;
	      else
		zmail = s.zuser;

	      /* The imode argument (passed as 0666) will be corrected
		 with information from the remote system.  */
	      s.imode = 0666;

	      if (! freceive_file (TRUE, e, &s, zmail, qsys->zname, fnew))
		return FALSE;

	      break;

	    case 'X':
	      /* Request a file copy.  This is used to request a file
		 to be sent to another machine, as well as to get a
		 wildcarded filespec.  */
	      ulog (LOG_NORMAL, "Requesting work: %s to %s", s.zfrom,
		    s.zto);

	      if (! fxcmd (&s, &fnever))
		return FALSE;

	      if (fnever)
		(void) fmail_transfer (FALSE, s.zuser,
				       (const char *) NULL,
				       "wildcard request denied",
				       s.zfrom, qsys->zname,
				       s.zto, zLocalname,
				       (const char *) NULL);
	      (void) fsysdep_did_work (s.pseq);
	      break;

	    case 'H':
	      /* There is nothing left to do; hang up.  If we are not the
		 master, take no action (this allows for two-way
		 protocols.  */

	      fmasterdone = TRUE;
	      if (fmaster)
		{
		  if (! fhangup_request ())
		    {
		      ulog (LOG_ERROR, "Hangup failed");
		      return FALSE;
		    }

		  fmaster = FALSE;

		  /* Close the log file at every master/slave switch.
		     This will cut down on the amount of time we have
		     an old log file open.  */
		  ulog_close ();
		  ustats_close ();
		}
	      break;

	    default:
	      ulog (LOG_ERROR, "Unknown command '%c'", s.bcmd);
	      break;
	    }
	}

      /* We look for a command from the other system if we are the
	 slave or this is a full-duplex protocol and the slave still
	 has work to do.  */
      if (! fmaster || qProto->ffullduplex)
	{
	  struct scmd s;
	  const char *zuse, *zmail;
	  openfile_t e;
	  char bhave_grade;
	  long cbytes;

	  /* We are the slave.  Get the next command from the other
	     system.  */
	  ulog_user ((const char *) NULL);
	  if (! fgetcmd (fmaster, &s))
	    return FALSE;

	  if (s.bcmd != 'H' && s.bcmd != 'Y')
	    ulog_user (s.zuser);

	  switch (s.bcmd)
	    {
	    case 'S':
	      /* The master wants to send a file to us.  */

	      if (fspool_file (s.zto))
		{
		  zuse = zsysdep_spool_file_name (qsys, s.zto);
		  /* We don't accept remote command files.  */
		  if (zuse == NULL || s.zto[0] == 'C')
		    {
		      if (! ftransfer_fail ('S', FAILURE_PERM))
			return FALSE;
		      break;
		    }
		}
	      else
		{
		  zuse = zsysdep_real_file_name (qsys, s.zto, s.zfrom);
		  if (zuse == NULL)
		    {
		      if (! ftransfer_fail ('S', FAILURE_PERM))
			return FALSE;
		      break;
		    }

		  /* Check permissions.  */
		  if (! fok_to_receive (zuse, FALSE, fcaller, qsys,
					s.zuser))
		    {
		      ulog (LOG_ERROR, "Not permitted to receive %s", zuse);
		      if (! ftransfer_fail ('S', FAILURE_PERM))
			return FALSE;
		      break;
		    }

		  if (strchr (s.zoptions, 'f') == NULL)
		    {
		      if (! fsysdep_make_dirs (zuse, TRUE))
			{
			  if (! ftransfer_fail ('S', FAILURE_PERM))
			    return FALSE;
			  break;
			}
		    }
		}

	      e = esysdep_open_receive (qsys, zuse, &s.ztemp, &cbytes);
	      if (! ffileisopen (e))
		{
		  if (! ftransfer_fail ('S', FAILURE_OPEN))
		    return FALSE;
		  break;
		}

	      /* 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.  */
	      if (cbytes != -1)
		{
		  cbytes -= qsys->cfree_space;
		  if (cbytes < 0)
		    cbytes = 0;
		}
	      
	      if (cremote_size != -1
		  && (cbytes == -1 || cremote_size < cbytes))
		cbytes = cremote_size;

	      /* If the number of bytes we are prepared to receive
		 is less than the file size, we must fail.  */

	      if (s.cbytes != -1
		  && cbytes != -1
		  && cbytes < s.cbytes)
		{
		  ulog (LOG_ERROR, "%s is too big to receive", zuse);
		  (void) ffileclose (e);
		  (void) remove (s.ztemp);
		  if (! ftransfer_fail ('S', FAILURE_SIZE))
		    return FALSE;
		  break;
		}

	      ulog (LOG_NORMAL, "Receiving %s", zuse);
	      s.zto = zuse;

	      if (strchr (s.zoptions, 'n') == NULL)
		zmail = NULL;
	      else
		zmail = s.znotify;
	      s.pseq = NULL;
	      if (! freceive_file (FALSE, e, &s, zmail, qsys->zname, fnew))
		return FALSE;

	      break;

	    case 'R':
	      /* The master wants to get a file from us.  */

	      if (fspool_file (s.zfrom))
		{
		  ulog (LOG_ERROR, "No permission to send %s", s.zfrom);
		  if (! ftransfer_fail ('R', FAILURE_PERM))
		    return FALSE;
		  break;
		}

	      zuse = zsysdep_real_file_name (qsys, s.zfrom,
					     (const char *) NULL);
	      if (zuse == NULL)
		{
		  if (! ftransfer_fail ('R', FAILURE_PERM))
		    return FALSE;
		  break;
		}

	      if (! fok_to_send (zuse, FALSE, fcaller, FALSE, qsys,
				 s.zuser))
		{
		  ulog (LOG_ERROR, "No permission to send %s", zuse);
		  if (! ftransfer_fail ('R', FAILURE_PERM))
		    return FALSE;
		  break;
		}

	      e = esysdep_open_send (qsys, zuse, TRUE, (const char *) NULL,
				     &s.imode, &cbytes, (boolean *) NULL);
	      if (! ffileisopen (e))
		{
		  if (! ftransfer_fail ('R', FAILURE_OPEN))
		    return FALSE;
		  break;
		}

	      /* If the file is larger than the amount of space
		 the other side reported, we can't send it.  */
	      if (cbytes != -1
		  && ((s.cbytes != -1 && s.cbytes < cbytes)
		      || (cremote_size != -1 && cremote_size < cbytes)
		      || (cmax_receive != -1 && cmax_receive < cbytes)))
		{
		  ulog (LOG_ERROR, "%s is too large to send", zuse);
		  if (! ftransfer_fail ('R', FAILURE_SIZE))
		    return FALSE;
		  (void) ffileclose (e);
		  break;
		}

	      ulog (LOG_NORMAL, "Sending %s", zuse);

	      /* Pass in the real size of the file.  */
	      s.cbytes = cbytes;

	      if (! fsend_file (FALSE, e, &s, (const char *) NULL,
				qsys->zname, fnew))
		return FALSE;

	      break;

	    case 'X':
	      /* This is an execution request.  We are being asked to
		 send one or more files to a destination on either the
		 local or a remote system.  We do this by spooling up
		 commands for the destination system.  */
	      ulog (LOG_NORMAL, "Work requested: %s to %s", s.zfrom,
		    s.zto);

	      if (fdo_xcmd (qsys, fcaller, &s))
		{
		  if (! fxcmd_confirm ())
		    return FALSE;
		}
	      else
		{
		  if (! ftransfer_fail ('X', FAILURE_PERM))
		    return FALSE;
		}

	      break;

	    case 'H':
	      /* The master wants to hang up.  If we have something to
		 do, become the master.  Otherwise, agree to hang up.
		 We recheck the grades allowed at this time, since a
		 lot of time may have passed.  */
	      if (fcaller)
		bgrade = btimegrade (qsys->ztime);
	      if (bgrade != '\0'
		  && fsysdep_has_work (qsys, &bhave_grade)
		  && igradecmp (bgrade, bhave_grade) >= 0)
		{
		  if (fmasterdone)
		    {
		      if (! fsysdep_get_work_init (qsys, bgrade, FALSE))
			return FALSE;
		      fnowork = FALSE;
		    }

		  fmasterdone = FALSE;
		  
		  if (! fhangup_reply (FALSE))
		      return FALSE;
		  fmaster = TRUE;

		  /* Recalculate the maximum sizes we can send, since
		     the time might have changed significantly.  */
		  clocal_size = cmax_size_now (zlocal_size);
		  cremote_size = cmax_size_now (zremote_size);

		  /* Close the log file at every switch of master and
		     slave.  */
		  ulog_close ();
		  ustats_close ();
		}
	      else
		{
		  /* The hangup_reply function will shut down the
		     protocol.  */
		  return fhangup_reply (TRUE);
		}
	      break;

	    case 'Y':
	      /* This is returned when a hangup has been confirmed and
		 the protocol has been shut down.  */
	      return TRUE;

	    default:
	      ulog (LOG_ERROR, "Unknown command %c", s.bcmd);
	      break;
	    }
	}
    }
}

/* Do an 'X' request for another system.  The other system has
   basically requested us to execute a uucp command for them.  */

static boolean
fdo_xcmd (qsys, fcaller, q)
     const struct ssysteminfo *qsys;
     boolean fcaller;
     const struct scmd *q;
{
  const char *zexclam;
  const char *zdestfile;
  char *zcopy;
  struct ssysteminfo sdestsys;
  const struct ssysteminfo *qdestsys;
  char *zuser = NULL;
  char aboptions[5];
  char *zoptions = NULL;
  boolean fmkdirs;
  const char *zfile;

  zexclam = strchr (q->zto, '!');
  if (zexclam == NULL
      || zexclam == q->zto
      || strncmp (zLocalname, q->zto, zexclam - q->zto) == 0)
    {
      /* The files are supposed to be copied to the
	 local system.  */
      qdestsys = NULL;
      if (zexclam == NULL)
	zdestfile = q->zto;
      else
	zdestfile = zexclam + 1;
    }
  else
    {
      int clen;

      clen = zexclam - q->zto;
      zcopy = (char *) alloca (clen + 1);
      strncpy (zcopy, q->zto, clen);
      zcopy[clen] = '\0';

      if (! fread_system_info (zcopy, &sdestsys))
	{
	  if (! fUnknown_ok)
	    {
	      ulog (LOG_ERROR, "Destination system %s unknown",
		    zcopy);
	      return FALSE;
	    }
	  sdestsys = sUnknown;
	  sdestsys.zname = zcopy;
	}
      qdestsys = &sdestsys;
      zdestfile = zexclam + 1;
    }

  if (qdestsys != NULL)
    {
      zuser = (char *) alloca (strlen (qdestsys->zname)
			       + strlen (q->zuser) + sizeof "!");
      sprintf (zuser, "%s!%s", qdestsys->zname,
	       q->zuser);
      zoptions = aboptions;
      *zoptions++ = 'C';
      if (strchr (q->zoptions, 'd') != NULL)
	*zoptions++ = 'd';
      if (strchr (q->zoptions, 'm') != NULL)
	*zoptions++ = 'm';
      *zoptions = '\0';
      fmkdirs = TRUE;
    }
  else
    fmkdirs = strchr (q->zoptions, 'f') != NULL;

  /* Now we have to process each source file.  The
     source specification may or may use wildcards.  */
  if (! fsysdep_wildcard_start (qsys, q->zfrom))
    return FALSE;

  while ((zfile = zsysdep_wildcard (qsys, q->zfrom)) != NULL)
    {
      const char *zsend;
      const char *zto;
      char abtname[CFILE_NAME_LEN];

      zcopy = (char *) alloca (strlen (zfile) + 1);
      strcpy (zcopy, zfile);
      zfile = zcopy;

      /* Make sure the remote system is permitted to read the
	 specified file.  */
      zsend = qsys->zremote_send;
      if (! fcaller && qsys->zcalled_remote_send != NULL)
	zsend = qsys->zcalled_remote_send;

      if (! fin_directory_list (qsys, zfile, zsend, TRUE, TRUE,
				(const char *) NULL))
	{
	  ulog (LOG_ERROR, "Not permitted to send %s", zfile);
	  (void) fsysdep_wildcard_end ();
	  return FALSE;
	}

      if (qdestsys != NULL)
	{
	  /* We really should get the original grade here.  */
	  zto = zsysdep_data_file_name (qdestsys, BDEFAULT_UUCP_GRADE,
					abtname, (char *) NULL,
					(char *) NULL);
	}
      else
	{
	  const char *zrec;

	  zto = zsysdep_real_file_name (qsys, zexclam + 1, zfile);
	  if (zto == NULL)
	    {
	      (void) fsysdep_wildcard_end ();
	      return FALSE;
	    }
	  /* We only accept a local destination if the remote system
	     has the right to create files there.  */
	  zrec = qsys->zremote_receive;
	  if (! fcaller && qsys->zcalled_remote_receive != NULL)
	    zrec = qsys->zcalled_remote_receive;

	  if (! fin_directory_list (qsys, zto, zrec, TRUE, FALSE,
				    (const char *) NULL))
	    {
	      ulog (LOG_ERROR, "Not permitted to receive %s", zto);
	      (void) fsysdep_wildcard_end ();
	      return FALSE;
	    }
	}

      /* Copy the file either to the final destination or to the
	 spool directory.  */
      if (! fcopy_file (zfile, zto, qdestsys == NULL, fmkdirs))
	{
	  (void) fsysdep_wildcard_end ();
	  return FALSE;
	}

      /* If there is a destination system, queue it up.  */
      if (qdestsys != NULL)
	{
	  struct scmd ssend;

	  ssend.bcmd = 'S';
	  ssend.pseq = NULL;
	  ssend.zfrom = zfile;
	  ssend.zto = zdestfile;
	  ssend.zuser = zuser;
	  ssend.zoptions = aboptions;
	  ssend.ztemp = abtname;
	  ssend.imode = isysdep_file_mode (zfile);
	  if (ssend.imode == 0)
	    {
	      (void) fsysdep_wildcard_end ();
	      return FALSE;
	    }
	  ssend.znotify = "";
	  ssend.cbytes = -1;

	  if (zsysdep_spool_commands (qdestsys, BDEFAULT_UUCP_GRADE,
				      1, &ssend) == NULL)
	    {
	      (void) fsysdep_wildcard_end ();
	      return FALSE;
	    }
	}
    }

  if (! fsysdep_wildcard_end ())
    return FALSE;

  return TRUE;
}

/* See whether it's OK to send a file to another system, according to
   the permissions recorded for that system.  If the file is not in
   the spool directory, this also makes sure that the user has
   permission to access the file and all its containing directories.

   zfile -- file to send
   flocal -- TRUE if the send was requested locally
   fcaller -- TRUE if the local system called the other system
   fspool -- TRUE if file was copied to spool directory
   qsys -- remote system information
   zuser -- user who requested the action  */

static boolean
fok_to_send (zfile, flocal, fcaller, fspool, qsys, zuser)
     const char *zfile;
     boolean flocal;
     boolean fcaller;
     boolean fspool;
     const struct ssysteminfo *qsys;
     const char *zuser;
{
  const char *z;

  if (! frequest_ok (flocal, fcaller, qsys, zuser))
    return FALSE;

  if (flocal)
    {
      z = qsys->zlocal_send;
      if (! fcaller && qsys->zcalled_local_send != NULL)
	z = qsys->zcalled_local_send;
    }
  else
    {
      z = qsys->zremote_send;
      if (! fcaller && qsys->zcalled_remote_send != NULL)
	z = qsys->zcalled_remote_send;
    }

  /* If fspool is TRUE, we don't want to check file accessibility.  If
     this was not a local request, we pass a NULL down as the user
     name, since zuser has no meaning on this system.  */
  return fin_directory_list (qsys, zfile, z, ! fspool, TRUE,
			     flocal ? zuser : (const char *) NULL);
}

/* See whether it's OK to receive a file from another system.  */

/*ARGSUSED*/
static boolean
fok_to_receive (zto, flocal, fcaller, qsys, zuser)
     const char *zto;
     boolean flocal;
     boolean fcaller;
     const struct ssysteminfo *qsys;
     const char *zuser;
{
  const char *z;

  if (! frequest_ok (flocal, fcaller, qsys, zuser))
    return FALSE;

  if (flocal)
    {
      z = qsys->zlocal_receive;
      if (! fcaller && qsys->zcalled_local_receive != NULL)
	z = qsys->zcalled_local_receive;
    }
  else
    {
      z = qsys->zremote_receive;
      if (! fcaller && qsys->zcalled_remote_receive != NULL)
	z = qsys->zcalled_remote_receive;
    }

  return fin_directory_list (qsys, zto, z, TRUE, FALSE,
			     flocal ? zuser : (const char *) NULL);
}

/* See whether a request is OK.  This depends on which system placed
   the call and which system made the request.  */

/*ARGSUSED*/
static boolean
frequest_ok (flocal, fcaller, qsys, zuser)
     boolean flocal;
     boolean fcaller;
     const struct ssysteminfo *qsys;
     const char *zuser;
{
  if (flocal)
    {
      if (fcaller)
	return qsys->fcall_transfer;
      else
	return qsys->fcalled_transfer;
    }
  else
    {
      if (fcaller)
	return qsys->fcall_request;
      else
	return qsys->fcalled_request;
    }
}

/* 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 (z)
     const char *z;
{
  char *zalc;
  int cwrite;

  cwrite = strlen (z) + 2;

  zalc = (char *) alloca (cwrite);
  sprintf (zalc, "\020%s", z);

  return fport_write (zalc, cwrite);
}

/* 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 (10)

static const char *
zget_uucp_cmd (frequired)
     boolean frequired;
{
  static char *zalc;
  static int calc;
  int cgot;
  long iendtime;
  int ctimeout;
#if DEBUG > 1
  int cchars;
  int iolddebug;
#endif

  iendtime = isysdep_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

  cgot = -1;
  while ((ctimeout = (int) (iendtime - isysdep_time ((long *) NULL))) > 0)
    {
      int b;
      
      b = breceive_char (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");
	  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 (cgot < 0)
	{
	  if (b != '\020')
	    continue;
	  cgot = 0;
	  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)
	{
	  calc += CINCREMENT;
	  zalc = (char *) xrealloc ((pointer) zalc, calc);
	}

      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

  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 const char *
zget_typed_line ()
{
  static char *zalc;
  static int calc;
  int 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

  cgot = 0;
  while (TRUE)
    {
      int b;
      
      b = breceive_char (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
	  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)
	{
	  calc += CINCREMENT;
	  zalc = (char *) xrealloc ((pointer) zalc, calc);
	}

      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;
	}
    }
}
