/* uucico.c
   This is the main UUCP communication program.

   Copyright (C) 1991, 1992, 1993, 1994, 1995 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

   The author of the program may be contacted at ian@airs.com or
   c/o Cygnus Support, 48 Grove Street, Somerville, MA 02144.
   */

#include "uucp.h"

#if USE_RCS_ID
const char uucico_rcsid[] = "$Id: uucico.c,v 1.192 1995/08/17 02:03:35 ian Rel $";
#endif

#include <ctype.h>

#if HAVE_LIMITS_H
#include <limits.h>
#else
#define LONG_MAX 2147483647L
#endif

#include "getopt.h"

#include "uudefs.h"
#include "uuconf.h"
#include "conn.h"
#include "prot.h"
#include "trans.h"
#include "system.h"

#if HAVE_ENCRYPTED_PASSWORDS
#ifndef crypt
extern char *crypt ();
#endif
#endif

/* Coherent already had a different meaning for the -c option.  What a
   pain.  */
#ifdef __COHERENT__
#define COHERENT_C_OPTION 1
#else
#define COHERENT_C_OPTION 0
#endif

/* Define the known protocols.  */

#define TCP_PROTO \
  (UUCONF_RELIABLE_ENDTOEND \
   | UUCONF_RELIABLE_RELIABLE \
   | UUCONF_RELIABLE_EIGHT)

static const struct sprotocol asProtocols[] =
{
  { 't', TCP_PROTO, 1, TRUE,
      asTproto_params, ftstart, ftshutdown, ftsendcmd, ztgetspace,
      ftsenddata, ftwait, ftfile },
  { 'e', TCP_PROTO, 1, TRUE,
      asEproto_params, festart, feshutdown, fesendcmd, zegetspace,
      fesenddata, fewait, fefile },
  { 'i', UUCONF_RELIABLE_EIGHT, 7, TRUE,
      asIproto_params, fistart, fishutdown, fisendcmd, zigetspace,
      fisenddata, fiwait, NULL },
  { 'a', UUCONF_RELIABLE_EIGHT, 1, TRUE,
      asZproto_params, fzstart, fzshutdown, fzsendcmd, zzgetspace,
      fzsenddata, fzwait, fzfile },
  { 'g', UUCONF_RELIABLE_EIGHT, 1, TRUE,
      asGproto_params, fgstart, fgshutdown, fgsendcmd, zggetspace,
      fgsenddata, fgwait, NULL },
  { 'G', UUCONF_RELIABLE_EIGHT, 1, TRUE,
      asGproto_params, fbiggstart, fgshutdown, fgsendcmd, zggetspace,
      fgsenddata, fgwait, NULL },
  { 'j', UUCONF_RELIABLE_EIGHT, 7, TRUE,
      asIproto_params, fjstart, fjshutdown, fisendcmd, zigetspace,
      fisenddata, fiwait, NULL },
  { 'f', UUCONF_RELIABLE_RELIABLE, 1, FALSE,
      asFproto_params, ffstart, ffshutdown, ffsendcmd, zfgetspace,
      ffsenddata, ffwait, fffile },
  { 'v', UUCONF_RELIABLE_EIGHT, 1, TRUE,
      asGproto_params, fvstart, fgshutdown, fgsendcmd, zggetspace,
      fgsenddata, fgwait, NULL },
  { 'y', UUCONF_RELIABLE_RELIABLE | UUCONF_RELIABLE_EIGHT, 1, TRUE,
      asYproto_params, fystart, fyshutdown, fysendcmd, zygetspace,
      fysenddata, fywait, fyfile }
};

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

/* Locked system.  */
static boolean fLocked_system;
static struct uuconf_system sLocked_system;

/* Daemon structure holding information about the remote system (must
   be global so the error handler can see it.  */
static struct sdaemon sDaemon;

/* Open connection.  */
static struct sconnection *qConn;

/* uuconf global pointer; need to close the connection after a fatal
   error.  */
static pointer pUuconf;

/* This structure is passed to iuport_lock via uuconf_find_port.  */
struct spass
{
  boolean fmatched;
  boolean flocked;
  struct sconnection *qconn;
};

/* Local functions.  */

static void uusage P((void));
static void uhelp P((void));
static void uabort P((void));
static boolean fcall P((pointer puuconf, const char *zconfig, boolean fuuxqt,
			const struct uuconf_system *qsys,
			struct uuconf_port *qport, boolean fifwork,
			boolean fforce, boolean fdetach,
			boolean fquiet, boolean ftrynext));
static boolean fconn_call P((struct sdaemon *qdaemon,
			     struct uuconf_port *qport,
			     struct sstatus *qstat, int cretry,
			     boolean *pfcalled));
static boolean fdo_call P((struct sdaemon *qdaemon,
			   struct sstatus *qstat,
			   const struct uuconf_dialer *qdialer,
			   boolean *pfcalled, enum tstatus_type *pterr));
static int iuport_lock P((struct uuconf_port *qport, pointer pinfo));
static boolean flogin_prompt P((pointer puuconf, const char *zconfig,
				boolean fuuxqt, struct sconnection *qconn,
				const char *zlogin, const char **pzsystem));
static int icallin_cmp P((int iwhich, pointer pinfo, const char *zfile));
static boolean faccept_call P((pointer puuconf, const char *zconfig,
			       boolean fuuxqt, const char *zlogin,
			       struct sconnection *qconn,
			       const char **pzsystem));
static void uaccept_call_cleanup P((pointer puuconf,
				    struct uuconf_system *qfreesys,
				    struct uuconf_port *qport,
				    struct uuconf_port *qfreeport,
				    char *zloc));
static void uapply_proto_params P((pointer puuconf, int bproto,
				   struct uuconf_cmdtab *qcmds,
				   struct uuconf_proto_param *pas));
static boolean fsend_uucp_cmd P((struct sconnection *qconn,
				 const char *z));
static char *zget_uucp_cmd P((struct sconnection *qconn,
			      boolean frequired, boolean fstrip));
static char *zget_typed_line P((struct sconnection *qconn,
				boolean fstrip));

/* Long getopt options.  */
static const struct option asLongopts[] =
{
  { "quiet", no_argument, NULL, 2 },
  { "ifwork", no_argument, NULL, 'C' },
  { "nodetach", no_argument, NULL, 'D' },
  { "loop", no_argument, NULL, 'e' },
  { "force", no_argument, NULL, 'f'},
  { "stdin", required_argument, NULL, 'i' },
  { "prompt", no_argument, NULL, 'l' },
  { "port", required_argument, NULL, 'p' },
  { "nouuxqt", no_argument, NULL, 'q' },
  { "master", no_argument, NULL, 3 },
  { "slave", no_argument, NULL, 4 },
  { "system", required_argument, NULL, 's' },
  { "login", required_argument, NULL, 'u' },
  { "wait", no_argument, NULL, 'w' },
  { "try-next", no_argument, NULL, 'z' },
  { "config", required_argument, NULL, 'I' },
  { "debug", required_argument, NULL, 'x' },
  { "version", no_argument, NULL, 'v' },
  { "help", no_argument, NULL, 1 },
  { NULL, 0, NULL, 0 }
};

int
main (argc, argv)
     int argc;
     char **argv;
{
  /* -c: Whether to be quiet.  */
  boolean fquiet = FALSE;
  /* -C: Only call the system if there is work.  */
  boolean fifwork = FALSE;
  /* -D: don't detach from controlling terminal.  */
  boolean fdetach = TRUE;
  /* -e: Whether to do an endless loop of accepting calls.  */
  boolean fendless = FALSE;
  /* -f: Whether to force a call despite status of previous call.  */
  boolean fforce = FALSE;
  /* -i type: type of port to use for stdin.  */
  enum uuconf_porttype tstdintype = UUCONF_PORTTYPE_STDIN;
  /* -I file: configuration file name.  */
  const char *zconfig = NULL;
  /* -l: Whether to give a single login prompt.  */
  boolean flogin = FALSE;
  /* -P port: port to use; in master mode, call out on this port.  In
     slave mode, accept logins on this port.  If port not specified,
     then in master mode figure it out for each system, and in slave
     mode use stdin and stdout.  */
  const char *zport = NULL;
  /* -q: Whether to start uuxqt when done.  */
  boolean fuuxqt = TRUE;
  /* -r1: Whether we are the master.  */
  boolean fmaster = FALSE;
  /* -s,-S system: system to call.  */
  const char *zsystem = NULL;
  /* -u: Login name to use.  */
  const char *zlogin = NULL;
  /* -w: Whether to wait for a call after doing one.  */
  boolean fwait = FALSE;
  /* -z: Try next alternate if call fails.  */
  boolean ftrynext = FALSE;
  const char *zopts;
  int iopt;
  struct uuconf_port *qport;
  struct uuconf_port sport;
  boolean fret = TRUE;
  pointer puuconf;
  int iuuconf;
#if DEBUG > 1
  int iholddebug;
#endif

  zProgram = argv[0];

  /* When uucico is invoked by login, the first character of the
     program will be a dash.  We don't want that.  */
  if (*zProgram == '-')
    ++zProgram;

#if COHERENT_C_OPTION
  zopts = "c:CDefi:I:lp:qr:s:S:u:x:X:vwz";
#else
  zopts = "cCDefi:I:lp:qr:s:S:u:x:X:vwz";
#endif

  while ((iopt = getopt_long (argc, argv, zopts,
			      asLongopts, (int *) NULL)) != EOF)
    {
#if COHERENT_C_OPTION
      if (iopt == 'c')
	{
	  iopt = 's';
	  fifwork = TRUE;
	}
#endif
      switch (iopt)
	{
	case 2:
	case 'c':
	  /* Don't warn if a call is attempted at a bad time, and
	     don't print the "No work" message.  */
	  fquiet = TRUE;
	  break;

	case 'C':
	  fifwork = TRUE;
	  break;

	case 'D':
	  /* Don't detach from controlling terminal.  */
	  fdetach = FALSE;
	  break;

	case 'e':
	  /* Do an endless loop of accepting calls.  */
	  fendless = TRUE;
	  break;

	case 'f':
	  /* Force a call even if it hasn't been long enough since the last
	     failed call.  */
	  fforce = TRUE;
	  break;

	case 'i':
	  /* Type of port to use for standard input.  Only TLI is
	     supported here, and only if HAVE_TLI is true.  This
	     permits the Network Listener to tell uucico to use TLI
	     I/O calls.  */
	  if (strcasecmp (optarg, "tli") != 0)
	    fprintf (stderr, "%s: unsupported port type \"%s\"\n",
		     zProgram, optarg);
	  else
	    {
#if HAVE_TLI
	      tstdintype = UUCONF_PORTTYPE_TLI;
#else
	      fprintf (stderr, "%s: not compiled with TLI support\n",
		       zProgram);
#endif
	    }
	  break;

	case 'l':
	  /* Prompt for login name and password.  */
	  flogin = TRUE;
	  break;

	case 'p':
	  /* Port to use  */
	  zport = optarg;
	  break;

	case 'q':
	  /* Don't start uuxqt.  */
	  fuuxqt = FALSE;
	  break;

	case 'r':
	  /* Set mode: -r1 for master, -r0 for slave (default)  */
	  if (strcmp (optarg, "1") == 0)
	    fmaster = TRUE;
	  else if (strcmp (optarg, "0") == 0)
	    fmaster = FALSE;
	  else
	    uusage ();
	  break;
    
	case 's':
	  /* Set system name  */
	  zsystem = optarg;
	  fmaster = TRUE;
	  break;

	case 'S':
	  /* Set system name and force call like -f  */
	  zsystem = optarg;
	  fforce = TRUE;
	  fmaster = TRUE;
	  break;

	case 'u':
	  /* Some versions of uucpd invoke uucico with a -u argument
	     specifying the login name.  If invoked by a privileged
	     user, we use it instead of the result of
	     zsysdep_login_name.  */
	  if (fsysdep_privileged ())
	    zlogin = optarg;
	  else
	    fprintf (stderr,
		     "%s: ignoring command line login name: not a privileged user\n",
		     zProgram);
	  break;

	case 'w':
	  /* Call out and then wait for a call in  */
	  fwait = TRUE;
	  break;

	case 'z':
	  /* Try next alternate if call fails.  */
	  ftrynext = TRUE;
	  break;

	case 'I':
	  /* Set configuration file name (default is in sysdep.h).  */
	  if (fsysdep_other_config (optarg))
	    zconfig = optarg;
	  break;

	case 'x':
	case 'X':
#if DEBUG > 1
	  /* Set debugging level.  */
	  iDebug |= idebug_parse (optarg);
#endif
	  break;

	case 'v':
	  /* Print version and exit.  */
	  printf ("%s: Taylor UUCP %s, copyright (C) 1991, 92, 93, 94, 1995 Ian Lance Taylor\n",
		  zProgram, VERSION);
	  exit (EXIT_SUCCESS);
	  /*NOTREACHED*/

	case 4:
	  /* --slave.  */
	  fmaster = FALSE;
	  break;

	case 3:
	  /* --master.  */
	  fmaster = TRUE;
	  break;

	case 1:
	  /* --help.  */
	  uhelp ();
	  exit (EXIT_SUCCESS);
	  /*NOTREACHED*/

	case 0:
	  /* Long option found, and flag value set.  */
	  break;

	default:
	  uusage ();
	  /*NOTREACHED*/
	}
    }

  if (optind != argc)
    uusage ();

  if (fwait && zport == NULL)
    {
      fprintf (stderr, "%s: -w requires -p", zProgram);
      uusage ();
    }

  iuuconf = uuconf_init (&puuconf, (const char *) NULL, zconfig);
  if (iuuconf != UUCONF_SUCCESS)
    ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
  pUuconf = puuconf;

#if DEBUG > 1
  {
    const char *zdebug;

    iuuconf = uuconf_debuglevel (puuconf, &zdebug);
    if (iuuconf != UUCONF_SUCCESS)
      ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
    if (zdebug != NULL)
      iDebug |= idebug_parse (zdebug);
  }
#endif

  /* If a port was named, get its information.  */
  if (zport == NULL)
    qport = NULL;
  else
    {
      iuuconf = uuconf_find_port (puuconf, zport, (long) 0, (long) 0,
				  (int (*) P((struct uuconf_port *,
					      pointer))) NULL,
				  (pointer) NULL, &sport);
      if (iuuconf == UUCONF_NOT_FOUND)
	ulog (LOG_FATAL, "%s: port not found", zport);
      else if (iuuconf != UUCONF_SUCCESS)
	ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
      qport = &sport;
    }

#ifdef SIGINT
  usysdep_signal (SIGINT);
#endif
#ifdef SIGHUP
  usysdep_signal (SIGHUP);
#endif
#ifdef SIGQUIT
  usysdep_signal (SIGQUIT);
#endif
#ifdef SIGTERM
  usysdep_signal (SIGTERM);
#endif
#ifdef SIGPIPE
  usysdep_signal (SIGPIPE);
#endif

  usysdep_initialize (puuconf, INIT_SUID);

  ulog_to_file (puuconf, TRUE);
  ulog_fatal_fn (uabort);

  if (fmaster)
    {
      if (zsystem != NULL)
	{
	  /* A system was named.  Call it.  */
	  iuuconf = uuconf_system_info (puuconf, zsystem,
					&sLocked_system);
	  if (iuuconf == UUCONF_NOT_FOUND)
	    ulog (LOG_FATAL, "%s: System not found", zsystem);
	  else if (iuuconf != UUCONF_SUCCESS)
	    ulog_uuconf (LOG_FATAL, puuconf, iuuconf);

	  /* Detach from the controlling terminal for the call.  This
	     probably makes sense only on Unix.  We want the modem
	     line to become the controlling terminal.  */
	  if (fdetach &&
	      (qport == NULL
	       || qport->uuconf_ttype != UUCONF_PORTTYPE_STDIN))
	    usysdep_detach ();

	  ulog_system (sLocked_system.uuconf_zname);

#if DEBUG > 1
	  iholddebug = iDebug;
	  if (sLocked_system.uuconf_zdebug != NULL)
	    iDebug |= idebug_parse (sLocked_system.uuconf_zdebug);
#endif

	  if (! fsysdep_lock_system (&sLocked_system))
	    {
	      ulog (LOG_ERROR, "System already locked");
	      fret = FALSE;
	    }
	  else
	    {
	      fLocked_system = TRUE;
	      fret = fcall (puuconf, zconfig, fuuxqt, &sLocked_system, qport,
			    fifwork, fforce, fdetach, fquiet, ftrynext);
	      if (fLocked_system)
		{
		  (void) fsysdep_unlock_system (&sLocked_system);
		  fLocked_system = FALSE;
		}
	    }
#if DEBUG > 1
	  iDebug = iholddebug;
#endif
	  ulog_system ((const char *) NULL);
	  (void) uuconf_system_free (puuconf, &sLocked_system);
	}
      else
	{
	  char **pznames, **pz;
	  int c, i;
	  boolean fdidone;

	  /* Call all systems which have work to do.  */
	  fret = TRUE;
	  fdidone = FALSE;

	  iuuconf = uuconf_system_names (puuconf, &pznames, 0);
	  if (iuuconf != UUCONF_SUCCESS)
	    ulog_uuconf (LOG_FATAL, puuconf, iuuconf);

	  /* Randomize the order in which we call the systems.  */
	  c = 0;
	  for (pz = pznames; *pz != NULL; pz++)
	    c++;

	  srand ((unsigned int) ixsysdep_time ((long *) NULL));
	  for (i = c - 1; i > 0; i--)
	    {
	      int iuse;
	      char *zhold;

	      iuse = rand () % (i + 1);
	      zhold = pznames[i];
	      pznames[i] = pznames[iuse];
	      pznames[iuse] = zhold;
	    }

	  for (pz = pznames; *pz != NULL && ! FGOT_SIGNAL (); pz++)
	    {
	      iuuconf = uuconf_system_info (puuconf, *pz,
					    &sLocked_system);
	      if (iuuconf != UUCONF_SUCCESS)
		{
		  ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
		  xfree ((pointer) *pz);
		  continue;
		}

	      if (fsysdep_has_work (&sLocked_system))
		{
		  fdidone = TRUE;

		  /* Detach from the controlling terminal.  On Unix
		     this means that we will wind up forking a new
		     process for each system we call.  */
		  if (fdetach
		      && (qport == NULL
			  || qport->uuconf_ttype != UUCONF_PORTTYPE_STDIN))
		    usysdep_detach ();

		  ulog_system (sLocked_system.uuconf_zname);

#if DEBUG > 1
		  iholddebug = iDebug;
		  if (sLocked_system.uuconf_zdebug != NULL)
		    iDebug |= idebug_parse (sLocked_system.uuconf_zdebug);
#endif

		  if (! fsysdep_lock_system (&sLocked_system))
		    {
		      ulog (LOG_ERROR, "System already locked");
		      fret = FALSE;
		    }
		  else
		    {
		      fLocked_system = TRUE;
		      if (! fcall (puuconf, zconfig, fuuxqt, &sLocked_system,
				   qport, TRUE, fforce, fdetach, fquiet,
				   ftrynext))
			fret = FALSE;

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

		      if (fLocked_system)
			{
			  (void) fsysdep_unlock_system (&sLocked_system);
			  fLocked_system = FALSE;
			}
		    }
#if DEBUG > 1
		  iDebug = iholddebug;
#endif
		  ulog_system ((const char *) NULL);
		}

	      (void) uuconf_system_free (puuconf, &sLocked_system);
	      xfree ((pointer) *pz);
	    }

	  xfree ((pointer) pznames);

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

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

  if (! fmaster)
    {
      struct sconnection sconn;
      boolean flocked;

      /* If a port was specified by name, we go into endless loop
	 mode.  In this mode, we wait for calls and prompt them with
	 "login:" and "Password:", so that they think we are a regular
	 UNIX system.  If we aren't in endless loop mode, we have been
	 called by some other system.  If flogin is TRUE, we prompt
	 with "login:" and "Password:" a single time.  */

      fret = TRUE;
      zsystem = NULL;

      if (! fconn_init (qport, &sconn, tstdintype))
	fret = FALSE;

      if (qport != NULL)
	{
	  /* We are not using standard input.  Detach from the
	     controlling terminal, so that the port we are about to
	     use becomes our controlling terminal.  */
	  if (fdetach
	      && qport->uuconf_ttype != UUCONF_PORTTYPE_STDIN)
	    usysdep_detach ();
	}

      if (fconn_lock (&sconn, TRUE))
	flocked = TRUE;
      else
	{
	  flocked = FALSE;
	  ulog (LOG_ERROR, "%s: Port already locked",
		qport->uuconf_zname);
	  fret = FALSE;
	}

      if (fret)
	{
	  if (! fconn_open (&sconn, (long) 0, (long) 0, TRUE))
	    fret = FALSE;
	  qConn = &sconn;
	}

      if (fret)
	{
	  if (fendless)
	    {
	      while (! FGOT_SIGNAL ()
		     && flogin_prompt (puuconf, zconfig, fuuxqt, &sconn,
				       (const char *) NULL,
				       (const char **) NULL))
		{
		  /* Close and reopen the port in between calls.  */
		  if (! fconn_close (&sconn, puuconf,
				     (struct uuconf_dialer *) NULL,
				     TRUE)
		      || ! fconn_open (&sconn, (long) 0, (long) 0, TRUE))
		    break;
		}
	      fret = FALSE;
	    }
	  else
	    {
	      if (flogin)
		fret = flogin_prompt (puuconf, zconfig, fuuxqt, &sconn,
				      zlogin, &zsystem);
	      else
		{
#if DEBUG > 1
		  iholddebug = iDebug;
#endif
		  if (zlogin == NULL)
		    zlogin = zsysdep_login_name ();
		  fret = faccept_call (puuconf, zconfig, fuuxqt, zlogin,
				       &sconn, &zsystem);
#if DEBUG > 1
		  iDebug = iholddebug;
#endif
		}
	    }
	}

      if (qConn != NULL)
	{
	  if (! fconn_close (&sconn, puuconf, (struct uuconf_dialer *) NULL,
			     fret))
	    fret = FALSE;
	  qConn = NULL;
	}

      if (flocked)
	(void) fconn_unlock (&sconn);

      uconn_free (&sconn);
    }

  ulog_close ();
  ustats_close ();

  /* If we got a SIGTERM, perhaps because the system is going down,
     don't run uuxqt.  We go ahead and run it for any other signal,
     since I think they indicate more temporary conditions.  */
  if (afSignal[INDEXSIG_SIGTERM])
    fuuxqt = FALSE;

  if (fuuxqt)
    {
      int irunuuxqt;

      iuuconf = uuconf_runuuxqt (puuconf, &irunuuxqt);
      if (iuuconf != UUCONF_SUCCESS)
	ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
      else if (irunuuxqt == UUCONF_RUNUUXQT_ONCE)
	{
	  /* Detach from the controlling terminal before starting up uuxqt,
	     so that it runs as a true daemon.  */
	  if (fdetach)
	    usysdep_detach ();

	  if (! fspawn_uuxqt (FALSE, zsystem, zconfig))
	    fret = FALSE;
	}
    }

  usysdep_exit (fret);

  /* Avoid complaints about not returning.  */
  return 0;
}

/* Print out a usage message and die.  */

static void
uusage ()
{
  fprintf (stderr, "Usage: %s [options]\n", zProgram);
  fprintf (stderr, "Use %s --help for help\n", zProgram);
  exit (EXIT_FAILURE);
}

/* Print a help message.  */

static void
uhelp ()
{
  printf ("Taylor UUCP %s, copyright (C) 1991, 92, 93, 94, 1995 Ian Lance Taylor\n",
	   VERSION);
  printf ("Usage: %s [options]\n", zProgram);
  printf (" -s,-S,--system system: Call system (-S implies -f)\n");
  printf (" -f,--force: Force call despite system status\n");
  printf (" -r state: 1 for master, 0 for slave (default)\n");
  printf (" --master: Act as master\n");
  printf (" --slave: Act as slave (default)\n");
  printf (" -p,--port port: Specify port\n");
  printf (" -l,--prompt: prompt for login name and password\n");
  printf (" -e,--loop: Endless loop of login prompts and daemon execution\n");
  printf (" -w,--wait: After calling out, wait for incoming calls\n");
  printf (" -q,--nouuxqt: Don't start uuxqt when done\n");
  printf (" -c,--quiet: Don't log bad time or no work warnings\n");
  printf (" -C,--ifwork: Only call named system if there is work\n");
  printf (" -D,--nodetach: Don't detach from controlling terminal\n");
  printf (" -u,--login: Set login name (privileged users only)\n");
  printf (" -i,--stdin type: Type of standard input (only TLI supported)\n");
  printf (" -z,--try-next: If a call fails, try the next alternate\n");
  printf (" -x,-X,--debug debug: Set debugging level\n");
#if HAVE_TAYLOR_CONFIG
  printf (" -I,--config file: Set configuration file to use\n");
#endif /* HAVE_TAYLOR_CONFIG */
  printf (" -v,--version: Print version and exit\n");
  printf (" --help: Print help and exit\n");
}

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

static void
uabort ()
{
  if (fLocked_system)
    ufailed (&sDaemon);

  ulog_user ((const char *) NULL);

  if (qConn != NULL)
    {
      (void) fconn_close (qConn, pUuconf, (struct uuconf_dialer *) NULL,
			  FALSE);
      (void) fconn_unlock (qConn);
      uconn_free (qConn);
    }

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

  ulog_system ((const char *) NULL);

  ulog_close ();
  ustats_close ();

  usysdep_exit (FALSE);
}

/* The number of seconds in one day.  We must cast to long for this
   to be calculated correctly on a machine with 16 bit ints.  */
#define SECS_PER_DAY ((long) 24 * (long) 60 * (long) 60)

/* Call another system, trying all the possible sets of calling
   instructions.  The qsys argument is the system to call.  The qport
   argument is the port to use, and may be NULL.  If the fifwork
   argument is TRUE, the call is only placed if there is work to be
   done.  If the fforce argument is TRUE, a call is forced even if not
   enough time has passed since the last failed call.  If the fquiet
   argument is FALSE (the normal case), then a warning is given if
   calls are not permitted at this time.  */

static boolean
fcall (puuconf, zconfig, fuuxqt, qorigsys, qport, fifwork, fforce, fdetach,
       fquiet, ftrynext)
     pointer puuconf;
     const char *zconfig;
     boolean fuuxqt;
     const struct uuconf_system *qorigsys;
     struct uuconf_port *qport;
     boolean fifwork;
     boolean fforce;
     boolean fdetach;
     boolean fquiet;
     boolean ftrynext;
{
  struct sstatus sstat;
  long inow;
  boolean fbadtime, fnevertime, ffoundwork;
  const struct uuconf_system *qsys;

  if (! fsysdep_get_status (qorigsys, &sstat, (boolean *) NULL))
    return FALSE;
  ubuffree (sstat.zstring);

  /* Make sure it's been long enough since the last failed call, and
     that we haven't exceeded the maximum number of retries.  Even if
     we are over the limit on retries, we permit a call to be made if
     24 hours have passed.  This 24 hour limit is still controlled by
     the retry time.  We ignore times in the future, presumably the
     result of some sort of error.  */
  inow = ixsysdep_time ((long *) NULL);
  if (! fforce)
    {
      if (qorigsys->uuconf_cmax_retries > 0
	  && sstat.cretries >= qorigsys->uuconf_cmax_retries
	  && sstat.ilast <= inow
	  && sstat.ilast + SECS_PER_DAY > inow)
	{
	  ulog (LOG_ERROR, "Too many retries");
	  return FALSE;
	}

      if ((sstat.ttype == STATUS_COMPLETE
	   ? sstat.ilast + qorigsys->uuconf_csuccess_wait > inow
	   : sstat.ilast + sstat.cwait > inow)
	  && sstat.ilast <= inow)
	{
	  ulog (LOG_NORMAL, "Retry time not reached");
	  return FALSE;
	}
    }

  sDaemon.puuconf = puuconf;
  sDaemon.zconfig = zconfig;
  if (! fuuxqt)
    sDaemon.irunuuxqt = UUCONF_RUNUUXQT_NEVER;
  else
    {
      int iuuconf;

      iuuconf = uuconf_runuuxqt (puuconf, &sDaemon.irunuuxqt);
      if (iuuconf != UUCONF_SUCCESS)
	ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
    }

  fbadtime = TRUE;
  fnevertime = TRUE;
  ffoundwork = FALSE;

  for (qsys = qorigsys; qsys != NULL; qsys = qsys->uuconf_qalternate)
    {
      int cretry;
      boolean fany, fret, fcalled;

      if (FGOT_SIGNAL ())
	return FALSE;

      if (! qsys->uuconf_fcall || qsys->uuconf_qtimegrade == NULL)
	continue;

      fnevertime = FALSE;

      /* Make sure this is a legal time to call.  */
      if (! ftimespan_match (qsys->uuconf_qtimegrade, (long *) NULL,
			     &cretry))
	continue;

      fbadtime = FALSE;

      sDaemon.qsys = qsys;
      sDaemon.zlocalname = NULL;
      sDaemon.qconn = NULL;
      sDaemon.qproto = NULL;
      sDaemon.cchans = 1;
      sDaemon.clocal_size = -1;
      sDaemon.cremote_size = -1;
      sDaemon.cmax_ever = -2;
      sDaemon.cmax_receive = -1;
      sDaemon.csent = 0;
      sDaemon.creceived = 0;
      sDaemon.cxfiles_received = 0;
      sDaemon.ifeatures = 0;
      sDaemon.frequest_hangup = FALSE;
      sDaemon.fhangup_requested = FALSE;
      sDaemon.fhangup = FALSE;
      sDaemon.fmaster = TRUE;
      sDaemon.fcaller = TRUE;
      sDaemon.ireliable = 0;
      sDaemon.bgrade = '\0';

      /* Queue up any work there is to do.  */
      if (! fqueue (&sDaemon, &fany))
	return FALSE;

      /* If we are only supposed to call if there is work, and there
	 isn't any work, check the next alternates.  We can't give up
	 at this point because there might be some other alternates
	 with fewer restrictions on grade or file transfer size.  */
      if (fifwork && ! fany)
	{
	  uclear_queue (&sDaemon);
	  continue;
	}

      ffoundwork = TRUE;

      fret = fconn_call (&sDaemon, qport, &sstat, cretry, &fcalled);

      uclear_queue (&sDaemon);

      if (fret)
	return TRUE;
      if (fcalled && ! ftrynext)
	return FALSE;

      /* Now we have to dump that port so that we can aquire a new
	 one.  On Unix this means that we will fork and get a new
	 process ID, so we must unlock and relock the system.  */
      if (fdetach)
	{
	  (void) fsysdep_unlock_system (&sLocked_system);
	  fLocked_system = FALSE;
	  usysdep_detach ();
	  if (! fsysdep_lock_system (&sLocked_system))
	    return FALSE;
	  fLocked_system = TRUE;
	}
    }

  /* We only get here if no call succeeded.  If fbadtime is TRUE it
     was the wrong time for all the alternates.  Otherwise, if
     ffoundwork is FALSE there was no work for any of the alternates.
     Otherwise, we attempted a call and fconn_call logged an error
     message.  */

  if (fbadtime)
    {
      if (! fquiet)
	ulog (LOG_NORMAL, "Wrong time to call");

      /* Update the status, unless the system can never be called.  If
	 the system can never be called, there is little point to
	 putting in a ``wrong time to call'' message.  We don't change
	 the number of retries, although we do set the wait until the
	 next retry to 0.  */
      if (! fnevertime)
	{
	  sstat.ttype = STATUS_WRONG_TIME;
	  sstat.ilast = inow;
	  sstat.cwait = 0;
	  (void) fsysdep_set_status (qorigsys, &sstat);
	}
    }
  else if (! ffoundwork)
    {
      if (! fquiet)
	ulog (LOG_NORMAL, "No work");
      return TRUE;
    }

  return FALSE;
}

/* Find a port to use when calling a system, open a connection, and
   dial the system.  The actual call is done in fdo_call.  This
   routine is responsible for opening and closing the connection.  */

static boolean
fconn_call (qdaemon, qport, qstat, cretry, pfcalled)
     struct sdaemon *qdaemon;
     struct uuconf_port *qport;
     struct sstatus *qstat;
     int cretry;
     boolean *pfcalled;
{
  pointer puuconf;
  const struct uuconf_system *qsys;
  struct uuconf_port sport;
  struct sconnection sconn;
  enum tstatus_type terr;
  boolean fret;

  puuconf = qdaemon->puuconf;
  qsys = qdaemon->qsys;

  *pfcalled = FALSE;

  /* Ignore any SIGHUP signal we may have received up to this point.
     This is needed on Unix because we may have gotten one from the
     shell before we detached from the controlling terminal.  */
  afSignal[INDEXSIG_SIGHUP] = FALSE;

  /* If no port was specified on the command line, use any port
     defined for the system.  To select the system port: 1) see if
     port information was specified directly; 2) see if a port was
     named; 3) get an available port given the baud rate.  We don't
     change the system status if a port is unavailable; i.e. we don't
     force the system to wait for the retry time.  */
  if (qport == NULL)
    qport = qsys->uuconf_qport;
  if (qport != NULL)
    {
      if (! fconn_init (qport, &sconn, UUCONF_PORTTYPE_UNKNOWN))
	return FALSE;
      if (! fconn_lock (&sconn, FALSE))
	{
	  ulog (LOG_ERROR, "%s: Port already locked",
		qport->uuconf_zname);
	  return FALSE;
	}
    }
  else
    {
      struct spass s;
      int iuuconf;

      s.fmatched = FALSE;
      s.flocked = FALSE;
      s.qconn = &sconn;
      iuuconf = uuconf_find_port (puuconf, qsys->uuconf_zport,
				  qsys->uuconf_ibaud,
				  qsys->uuconf_ihighbaud,
				  iuport_lock, (pointer) &s,
				  &sport);
      if (iuuconf == UUCONF_NOT_FOUND)
	{
	  if (s.fmatched)
	    ulog (LOG_ERROR, "All matching ports in use");
	  else
	    ulog (LOG_ERROR, "No matching ports");
	  return FALSE;
	}
      else if (iuuconf != UUCONF_SUCCESS)
	{
	  ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
	  if (s.flocked)
	    {
	      (void) fconn_unlock (&sconn);
	      uconn_free (&sconn);
	    }
	  return FALSE;
	}
    }

  if (! fconn_open (&sconn, qsys->uuconf_ibaud, qsys->uuconf_ihighbaud,
		    FALSE))
    {
      terr = STATUS_PORT_FAILED;
      fret = FALSE;
    }
  else
    {
      struct uuconf_dialer *qdialer;
      struct uuconf_dialer sdialer;
      enum tdialerfound tdialer;

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

      qdialer = NULL;

      if (! fconn_dial (&sconn, puuconf, qsys, qsys->uuconf_zphone,
			&sdialer, &tdialer))
	{
	  tdialer = DIALERFOUND_FALSE;
	  terr = STATUS_DIAL_FAILED;
	  fret = FALSE;
	}
      else
	{
	  qdaemon->qconn = &sconn;
	  if (tdialer == DIALERFOUND_FALSE)
	    qdialer = NULL;
	  else
	    qdialer = &sdialer;
	  fret = fdo_call (qdaemon, qstat, qdialer, pfcalled, &terr);
	}

      (void) fconn_close (&sconn, puuconf, qdialer, fret);

      if (tdialer == DIALERFOUND_FREE)
	(void) uuconf_dialer_free (puuconf, &sdialer);
    }

  if (! fret)
    {
      DEBUG_MESSAGE2 (DEBUG_HANDSHAKE, "Call failed: %d (%s)",
		      (int) terr, azStatus[(int) terr]);
      qstat->ttype = terr;
      qstat->cretries++;
      qstat->ilast = ixsysdep_time ((long *) NULL);
      if (cretry == 0)
	qstat->cwait = CRETRY_WAIT (qstat->cretries);
      else
	qstat->cwait = cretry * 60;
      (void) fsysdep_set_status (qsys, qstat);
    }

  (void) fconn_unlock (&sconn);
  uconn_free (&sconn);

  if (qport == NULL)
    (void) uuconf_port_free (puuconf, &sport);

  return fret;
}

/* Do the actual work of calling another system.  The qsys argument is
   the system to call, the qconn argument is the connection to use,
   the qstat argument holds the current status of the ssystem, and the
   qdialer argument holds the dialer being used (it may be NULL).  If
   we log in successfully, set *pfcalled to TRUE; this is used to
   distinguish a failed dial from a failure during the call.  If an
   error occurs *pterr is set to the status type to record.  */

static boolean
fdo_call (qdaemon, qstat, qdialer, pfcalled, pterr)
     struct sdaemon *qdaemon;
     struct sstatus *qstat;
     const struct uuconf_dialer *qdialer;
     boolean *pfcalled;
     enum tstatus_type *pterr;
{
  pointer puuconf;
  const struct uuconf_system *qsys;
  struct sconnection *qconn;
  int iuuconf;
  int istrip;
  boolean fstrip;
  const char *zport;
  char *zstr;
  long istart_time;
  char *zlog;

  puuconf = qdaemon->puuconf;
  qsys = qdaemon->qsys;
  qconn = qdaemon->qconn;

  iuuconf = uuconf_strip (puuconf, &istrip);
  if (iuuconf != UUCONF_SUCCESS)
    {
      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
      return FALSE;
    }
  fstrip = (istrip & UUCONF_STRIP_PROTO) != 0;

  *pterr = STATUS_LOGIN_FAILED;

  if (qconn->qport == NULL)
    zport = "unknown";
  else
    zport = qconn->qport->uuconf_zname;
  if (! fchat (qconn, puuconf, &qsys->uuconf_schat, qsys,
	       (const struct uuconf_dialer *) NULL,
	       (const char *) NULL, FALSE, zport,
	       iconn_baud (qconn)))
    return FALSE;

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

  *pterr = STATUS_HANDSHAKE_FAILED;

  /* We should now see "Shere" from the other system.  Newer systems
     send "Shere=foo" where foo is the remote name.  */
  zstr = zget_uucp_cmd (qconn, TRUE, fstrip);
  if (zstr == NULL)
    return FALSE;

  if (strncmp (zstr, "Shere", 5) != 0)
    {
      ulog (LOG_ERROR, "Bad startup string (expected \"Shere\" got \"%s\")",
	    zstr);
      ubuffree (zstr);
      return FALSE;
    }

  ulog (LOG_NORMAL, "Login successful");

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

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

      /* Some UUCP packages only provide seven characters in the Shere
	 machine name.  Others only provide fourteen.  */
      zheresys = zstr + 6;
      clen = strlen (zheresys);
      if (clen == 7 || clen == 14)
	icmp = strncmp (zheresys, qsys->uuconf_zname, clen);
      else
	icmp = strcmp (zheresys, qsys->uuconf_zname);
      if (icmp != 0)
	{
	  if (qsys->uuconf_pzalias != NULL)
	    {
	      char **pz;

	      for (pz = qsys->uuconf_pzalias; *pz != NULL; pz++)
		{
		  if (clen == 7 || clen == 14)
		    icmp = strncmp (zheresys, *pz, clen);
		  else
		    icmp = strcmp (zheresys, *pz);
		  if (icmp == 0)
		    break;
		}
	    }
	  if (icmp != 0)
	    {
	      ulog (LOG_ERROR, "Called wrong system (%s)", zheresys);
	      ubuffree (zstr);
	      return FALSE;
	    }
	}
    }
#if DEBUG > 1
  else if (zstr[5] != '\0')
    DEBUG_MESSAGE1 (DEBUG_HANDSHAKE,
		    "fdo_call: Strange Shere: %s", zstr);
#endif

  ubuffree (zstr);

  /* We now send "S" name switches, where name is our UUCP name.  If
     we are using sequence numbers with this system, we send a -Q
     argument with the sequence number.  If the call-timegrade command
     was used, we send a -p argument and a -vgrade= argument with the
     grade to send us (we send both argument to make it more likely
     that one is recognized).  We always send a -N (for new) switch
     indicating what new features we support.  */
  {
    long ival;
    char bgrade;
    char *zsend;
    boolean fret;

    /* Determine the grade we should request of the other system.  A
       '\0' means that no restrictions have been made.  */
    if (! ftimespan_match (qsys->uuconf_qcalltimegrade, &ival,
			   (int *) NULL))
      bgrade = '\0';
    else
      bgrade = (char) ival;

    /* Determine the name we will call ourselves.  */
    if (qsys->uuconf_zlocalname != NULL)
      qdaemon->zlocalname = qsys->uuconf_zlocalname;
    else
      {
	iuuconf = uuconf_localname (puuconf, &qdaemon->zlocalname);
	if (iuuconf == UUCONF_NOT_FOUND)
	  {
	    qdaemon->zlocalname = zsysdep_localname ();
	    if (qdaemon->zlocalname == NULL)
	      return FALSE;
	  }
	else if (iuuconf != UUCONF_SUCCESS)
	  {
	    ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
	    return FALSE;
	  }
      }	    

    zsend = zbufalc (strlen (qdaemon->zlocalname) + 70);
    if (! qsys->uuconf_fsequence)
      {
	if (bgrade == '\0')
	  sprintf (zsend, "S%s -R -N0%o", qdaemon->zlocalname,
		   (unsigned int) (FEATURE_SIZES
				   | FEATURE_EXEC
				   | FEATURE_RESTART));
	else
	  sprintf (zsend, "S%s -p%c -vgrade=%c -R -N0%o",
		   qdaemon->zlocalname, bgrade, bgrade,
		   (unsigned int) (FEATURE_SIZES
				   | FEATURE_EXEC
				   | FEATURE_RESTART));
      }
    else
      {
	long iseq;

	iseq = ixsysdep_get_sequence (qsys);
	if (iseq < 0)
	  return FALSE;
	if (bgrade == '\0')
	  sprintf (zsend, "S%s -Q%ld -R -N0%o", qdaemon->zlocalname, iseq,
		   (unsigned int) (FEATURE_SIZES
				   | FEATURE_EXEC
				   | FEATURE_RESTART));
	else
	  sprintf (zsend, "S%s -Q%ld -p%c -vgrade=%c -R -N0%o",
		   qdaemon->zlocalname, iseq, bgrade, bgrade,
		   (unsigned int) (FEATURE_SIZES
				   | FEATURE_EXEC
				   | FEATURE_RESTART));
      }

    fret = fsend_uucp_cmd (qconn, zsend);
    ubuffree (zsend);
    if (! fret)
	return FALSE;
  }

  /* Now we should see ROK or Rreason where reason gives a cryptic
     reason for failure.  If we are talking to a counterpart, we will
     get back ROKN, possibly with a feature bitfield attached.  */
  zstr = zget_uucp_cmd (qconn, TRUE, fstrip);
  if (zstr == NULL)
    return FALSE;

  if (zstr[0] != 'R')
    {
      ulog (LOG_ERROR, "Bad response to handshake string (%s)",
	    zstr);
      ubuffree (zstr);
      return FALSE;
    }

  if (strncmp (zstr + 1, "OKN", sizeof "OKN" - 1) == 0)
    {
      if (zstr[sizeof "ROKN" - 1] == '\0')
	qdaemon->ifeatures |= FEATURE_SIZES | FEATURE_V103;
      else
	qdaemon->ifeatures |= (int) strtol (zstr + sizeof "ROKN" - 1,
					   (char **) NULL, 0);
    }
  else if (strncmp (zstr + 1, "OK", sizeof "OK" - 1) == 0)
    {
      if (zstr[sizeof "ROK" - 1] != '\0')
	{
	  char *zopt;

	  /* SVR4 UUCP returns options following the ROK string.  */
	  zopt = zstr + sizeof "ROK" - 1;
	  while (*zopt != '\0')
	    {
	      char b;
	      long c;
	      char *zend;

	      b = *zopt++;
	      if (isspace (b) || b != '-')
		continue;
	      switch (*zopt)
		{
		case 'R':
		  qdaemon->ifeatures |= (FEATURE_RESTART
					 | FEATURE_SVR4
					 | FEATURE_SIZES);
		  break;
		case 'U':
		  c = strtol (zopt, &zend, 0);
		  if (c > 0 && c <= LONG_MAX / (long) 512)
		    qdaemon->cmax_receive = c * (long) 512;
		  zopt = zend;
		  break;
		}
	      while (*zopt != '\0' && ! isspace (*zopt))
		++zopt;
	    }
	}
    }
  else if (strcmp (zstr + 1, "CB") == 0)
    {
      ulog (LOG_NORMAL, "Remote system will call back");
      qstat->ttype = STATUS_COMPLETE;
      (void) fsysdep_set_status (qsys, qstat);
      ubuffree (zstr);
      return TRUE;
    }
  else
    {
      ulog (LOG_ERROR, "Handshake failed (%s)", zstr + 1);
      ubuffree (zstr);
      return FALSE;
    }

  ubuffree (zstr);

  /* The slave should now send \020Pprotos\0 where protos is a list of
     supported protocols.  Each protocol is a single character.  */
  zstr = zget_uucp_cmd (qconn, TRUE, fstrip);
  if (zstr == NULL)
    return FALSE;

  if (zstr[0] != 'P')
    {
      ulog (LOG_ERROR, "Bad protocol handshake (%s)", zstr);
      ubuffree (zstr);
      return FALSE;
    }

  /* Determine the reliability characteristics of the connection by
     combining information for the port and the dialer.  If we have no
     information, default to a reliable eight-bit full-duplex
     connection.  */
  if (qconn->qport != NULL
      && (qconn->qport->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0)
    qdaemon->ireliable = qconn->qport->uuconf_ireliable;
  if (qdialer != NULL
      && (qdialer->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0)
    {
      if (qdaemon->ireliable != 0)
	qdaemon->ireliable &= qdialer->uuconf_ireliable;
      else
	qdaemon->ireliable = qdialer->uuconf_ireliable;
    }
  if (qdaemon->ireliable == 0)
    qdaemon->ireliable = (UUCONF_RELIABLE_RELIABLE
			  | UUCONF_RELIABLE_EIGHT
			  | UUCONF_RELIABLE_FULLDUPLEX
			  | UUCONF_RELIABLE_SPECIFIED);

  /* Now decide which protocol to use.  The system and the port may
     have their own list of protocols.  */
  {
    int i;
    char ab[5];

    i = CPROTOCOLS;
    if (qsys->uuconf_zprotocols != NULL
	|| (qconn->qport != NULL
	    && qconn->qport->uuconf_zprotocols != NULL))
      {
	const char *zproto;

	if (qsys->uuconf_zprotocols != NULL)
	  zproto = qsys->uuconf_zprotocols;
	else
	  zproto = qconn->qport->uuconf_zprotocols;
	for (; *zproto != '\0'; zproto++)
	  {
	    if (strchr (zstr + 1, *zproto) != NULL)
	      {
		for (i = 0; i < CPROTOCOLS; i++)
		  if (asProtocols[i].bname == *zproto)
		    break;
		if (i < CPROTOCOLS)
		  break;
	      }
	  }
      }
    else
      {
	/* If neither the system nor the port specified a list of
	   protocols, we want only protocols that match the known
	   reliability of the dialer and the port.  */
	for (i = 0; i < CPROTOCOLS; i++)
	  {
	    int ipr;

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

    ubuffree (zstr);

    if (i >= CPROTOCOLS)
      {
	(void) fsend_uucp_cmd (qconn, "UN");
	ulog (LOG_ERROR, "No mutually supported protocols");
	return FALSE;
      }

    qdaemon->qproto = &asProtocols[i];

    /* If we are using a half-duplex line, act as though we have only
       a single channel; otherwise we might start a send and a receive
       at the same time.  */
    if ((qdaemon->ireliable & UUCONF_RELIABLE_FULLDUPLEX) == 0)
      qdaemon->cchans = 1;
    else
      qdaemon->cchans = asProtocols[i].cchans;

    sprintf (ab, "U%c", qdaemon->qproto->bname);
    if (! fsend_uucp_cmd (qconn, ab))
      return FALSE;
  }

  /* Run any protocol parameter commands.  */
  if (qdaemon->qproto->qcmds != NULL)
    {
      if (qsys->uuconf_qproto_params != NULL)
	uapply_proto_params (puuconf, qdaemon->qproto->bname,
			     qdaemon->qproto->qcmds,
			     qsys->uuconf_qproto_params);
      if (qconn->qport != NULL
	  && qconn->qport->uuconf_qproto_params != NULL)
	uapply_proto_params (puuconf, qdaemon->qproto->bname,
			     qdaemon->qproto->qcmds,
			     qconn->qport->uuconf_qproto_params);
      if (qdialer != NULL
	  && qdialer->uuconf_qproto_params != NULL)
	uapply_proto_params (puuconf, qdaemon->qproto->bname,
			     qdaemon->qproto->qcmds,
			     qdialer->uuconf_qproto_params);
    }

  /* Turn on the selected protocol.  */
  if (! (*qdaemon->qproto->pfstart) (qdaemon, &zlog))
    return FALSE;
  if (zlog == NULL)
    {
      zlog = zbufalc (sizeof "protocol ''" + 1);
      sprintf (zlog, "protocol '%c'", qdaemon->qproto->bname);
    }
  ulog (LOG_NORMAL, "Handshake successful (%s)", zlog);
  ubuffree (zlog);

  *pterr = STATUS_FAILED;

  {
    boolean fret;
    long iend_time;

    fret = floop (qdaemon);

    /* Now send the hangup message.  As the caller, we send six O's
       and expect to receive seven O's.  We send the six O's twice to
       help the other side.  We don't worry about errors here.  */
    if (fsend_uucp_cmd (qconn, "OOOOOO")
	&& fsend_uucp_cmd (qconn, "OOOOOO"))
      {
	int i, fdone;

	/* We look for the remote hangup string to ensure that the
	   modem has sent out our hangup string.  This is only
	   necessary because some versions of UUCP complain if they
	   don't get the hangup string.  The remote site should send 7
	   O's, but some versions of UUCP only send 6.  We look for
	   the string several times because supposedly some
	   implementations send some garbage after the last packet but
	   before the hangup string.  */
	for (i = 0; i < 25; i++)
	  {
	    zstr = zget_uucp_cmd (qconn, FALSE, fstrip);
	    if (zstr == NULL)
	      break;
	    fdone = strstr (zstr, "OOOOOO") != NULL;
	    ubuffree (zstr);
	    if (fdone)
	      break;
	  }
      }

    iend_time = ixsysdep_time ((long *) NULL);

    ulog (LOG_NORMAL, "Call complete (%ld seconds %ld bytes %ld bps)",
	  iend_time - istart_time,
	  qdaemon->csent + qdaemon->creceived,
	  (iend_time != istart_time
	   ? (qdaemon->csent + qdaemon->creceived) / (iend_time - istart_time)
	   : 0));

    if (fret)
      {
	qstat->ttype = STATUS_COMPLETE;
	qstat->ilast = iend_time;
	(void) fsysdep_set_status (qsys, qstat);
      }

    if (qdaemon->irunuuxqt == UUCONF_RUNUUXQT_PERCALL
	|| (qdaemon->irunuuxqt > 0 && qdaemon->cxfiles_received > 0))
      (void) fspawn_uuxqt (TRUE, qdaemon->qsys->uuconf_zname,
			   qdaemon->zconfig);

    return fret;
  }
}

/* This routine is called via uuconf_find_port when a matching port is
   found.  It tries to lock the port.  If it fails, it returns
   UUCONF_NOT_FOUND to force uuconf_find_port to continue searching
   for the next matching port.  */

static int
iuport_lock (qport, pinfo)
     struct uuconf_port *qport;
     pointer pinfo;
{
  struct spass *q = (struct spass *) pinfo;

  q->fmatched = TRUE;

  if (! fconn_init (qport, q->qconn, UUCONF_PORTTYPE_UNKNOWN))
    return UUCONF_NOT_FOUND;
  else if (! fconn_lock (q->qconn, FALSE))
    {
      uconn_free (q->qconn);
      return UUCONF_NOT_FOUND;
    }
  else
    {
      q->flocked = TRUE;
      return UUCONF_SUCCESS;
    }
}

/* The information structure used for the uuconf_callin comparison
   function.  */

struct scallin_info
{
  const char *zuser;
  const char *zpass;
};

/* Prompt for a login name and a password, and run as the slave.  */

static boolean
flogin_prompt (puuconf, zconfig, fuuxqt, qconn, zlogin, pzsystem)
     pointer puuconf;
     const char *zconfig;
     boolean fuuxqt;
     struct sconnection *qconn;
     const char *zlogin;
     const char **pzsystem;
{
  int iuuconf;
  int istrip;
  boolean fstrip;
  char *zuser, *zpass;
  boolean fret;
  struct scallin_info s;

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

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

  iuuconf = uuconf_strip (puuconf, &istrip);
  if (iuuconf != UUCONF_SUCCESS)
    {
      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
      return FALSE;
    }
  fstrip = (istrip & UUCONF_STRIP_LOGIN) != 0;

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

      if (zuser == NULL)
	return TRUE;

      zlogin = zuser;
    }

  if (! fconn_write (qconn, "Password:", sizeof "Password:" - 1))
    {
      ubuffree (zuser);
      return FALSE;
    }

  zpass = zget_typed_line (qconn, fstrip);
  if (zpass == NULL)
    {
      ubuffree (zuser);
      return TRUE;
    }

  fret = TRUE;

  s.zuser = zlogin;
  s.zpass = zpass;
  iuuconf = uuconf_callin (puuconf, icallin_cmp, &s);

  ubuffree (zpass);

  if (iuuconf == UUCONF_NOT_FOUND)
    ulog (LOG_ERROR, "Bad login");
  else if (iuuconf != UUCONF_SUCCESS)
    {
      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
      fret = FALSE;
    }
  else
    {
#if DEBUG > 1
      int iholddebug;
#endif

      /* We ignore the return value of faccept_call because we really
	 don't care whether the call succeeded or not.  We are going
	 to reset the port anyhow.  */
#if DEBUG > 1
      iholddebug = iDebug;
#endif
      (void) faccept_call (puuconf, zconfig, fuuxqt, zlogin, qconn, pzsystem);
#if DEBUG > 1
      iDebug = iholddebug;
#endif
    }

  ubuffree (zuser);

  return fret;
}

/* The comparison function which we pass to uuconf_callin.  This
   expands escape sequences in the login name, and either encrypts or
   expands escape sequences in the password.  */

static int
icallin_cmp (iwhich, pinfo, zfile)
     int iwhich;
     pointer pinfo;
     const char *zfile;
{
  struct scallin_info *qinfo = (struct scallin_info *) pinfo;
  char *zcopy;
  int icmp;

#if HAVE_ENCRYPTED_PASSWORDS
  if (iwhich != 0)
    return strcmp (crypt (qinfo->zpass, zfile), zfile) == 0;
#endif

  zcopy = zbufcpy (zfile);
  (void) cescape (zcopy);
  if (iwhich == 0)
    icmp = strcmp (qinfo->zuser, zcopy);
  else
    icmp = strcmp (qinfo->zpass, zcopy);
  ubuffree (zcopy);
  return icmp == 0;
}

/* Accept a call from a remote system.  If pqsys is not NULL, *pqsys
   will be set to the system that called in if known.  */

static boolean
faccept_call (puuconf, zconfig, fuuxqt, zlogin, qconn, pzsystem)
     pointer puuconf;
     const char *zconfig;
     boolean fuuxqt;
     const char *zlogin;
     struct sconnection *qconn;
     const char **pzsystem;
{
  long istart_time;
  int iuuconf;
  int istrip;
  boolean fstrip;
  const char *zport;
  struct uuconf_port *qport;
  struct uuconf_port sport;
  struct uuconf_dialer *qdialer;
  struct uuconf_dialer sdialer;
  boolean ftcp_port;
  char *zsend, *zspace;
  boolean fret;
  char *zstr;
  struct uuconf_system ssys;
  const struct uuconf_system *qsys;
  const struct uuconf_system *qany;
  char *zloc;
  struct sstatus sstat;
  boolean fgotseq, fgotn;
  int i;
  char *zlog;
  char *zgrade;

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

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

  istart_time = ixsysdep_time ((long *) NULL);

  iuuconf = uuconf_strip (puuconf, &istrip);
  if (iuuconf != UUCONF_SUCCESS)
    {
      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
      uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
			    (struct uuconf_port *) NULL,
			    &sport, (char *) NULL);
      return FALSE;
    }
  fstrip = (istrip & UUCONF_STRIP_PROTO) != 0;

  /* Figure out protocol parameters determined by the port.  If no
     port was specified we're reading standard input, so try to get
     the port name and read information from the port file.  We only
     use the port information to get protocol parameters; we don't
     want to start treating the port as though it were a modem, for
     example.  */
  if (qconn->qport != NULL)
    {
      qport = qconn->qport;
      zport = qport->uuconf_zname;
      ftcp_port = FALSE;
    }
  else
    {
      zport = zsysdep_port_name (&ftcp_port);
      if (zport == NULL)
	{
	  qport = NULL;
	  zport = "unknown";
	}
      else
	{
	  iuuconf = uuconf_find_port (puuconf, zport, (long) 0, (long) 0,
				      (int (*) P((struct uuconf_port *,
						  pointer pinfo))) NULL,
				      (pointer) NULL,
				      &sport);
	  if (iuuconf == UUCONF_NOT_FOUND)
	    qport = NULL;
	  else if (iuuconf != UUCONF_SUCCESS)
	    {
	      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
	      uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
				    (struct uuconf_port *) NULL,
				    &sport, (char *) NULL);
	      return FALSE;
	    }
	  else
	    qport = &sport;
	}
    }

  /* If we've managed to figure out that this is a modem port, now try
     to get protocol parameters from the dialer.  */
  qdialer = NULL;
  if (qport != NULL)
    {
      if (qport->uuconf_ttype == UUCONF_PORTTYPE_MODEM)
	{
	  if (qport->uuconf_u.uuconf_smodem.uuconf_pzdialer != NULL)
	    {
	      const char *zdialer;

	      zdialer = qport->uuconf_u.uuconf_smodem.uuconf_pzdialer[0];
	      iuuconf = uuconf_dialer_info (puuconf, zdialer, &sdialer);
	      if (iuuconf == UUCONF_SUCCESS)
		qdialer = &sdialer;
	    }
	  else
	    qdialer = qport->uuconf_u.uuconf_smodem.uuconf_qdialer;
	}	  
      else if (qport->uuconf_ttype == UUCONF_PORTTYPE_TCP
	       || (qport->uuconf_ttype == UUCONF_PORTTYPE_TLI
		   && (qport->uuconf_ireliable
		       & UUCONF_RELIABLE_SPECIFIED) == 0))
	ftcp_port = TRUE;
    }

  sDaemon.puuconf = puuconf;
  sDaemon.zconfig = zconfig;
  if (! fuuxqt)
    sDaemon.irunuuxqt = UUCONF_RUNUUXQT_NEVER;
  else
    {
      iuuconf = uuconf_runuuxqt (puuconf, &sDaemon.irunuuxqt);
      if (iuuconf != UUCONF_SUCCESS)
	ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
    }
  sDaemon.qsys = NULL;
  sDaemon.zlocalname = NULL;
  sDaemon.qconn = qconn;
  sDaemon.qproto = NULL;
  sDaemon.cchans = 1;
  sDaemon.clocal_size = -1;
  sDaemon.cremote_size = -1;
  sDaemon.cmax_ever = -2;
  sDaemon.cmax_receive = -1;
  sDaemon.csent = 0;
  sDaemon.creceived = 0;
  sDaemon.cxfiles_received = 0;
  sDaemon.ifeatures = 0;
  sDaemon.frequest_hangup = FALSE;
  sDaemon.fhangup_requested = FALSE;
  sDaemon.fhangup = FALSE;
  sDaemon.fmaster = FALSE;
  sDaemon.fcaller = FALSE;
  sDaemon.ireliable = 0;
  sDaemon.bgrade = UUCONF_GRADE_LOW;

  /* Get the local name to use.  If uuconf_login_localname returns a
     value, it is not always freed up, although it should be.  */
  iuuconf = uuconf_login_localname (puuconf, zlogin, &zloc);
  if (iuuconf == UUCONF_SUCCESS)
    sDaemon.zlocalname = zloc;
  else if (iuuconf == UUCONF_NOT_FOUND)
    {
      sDaemon.zlocalname = zsysdep_localname ();
      if (sDaemon.zlocalname == NULL)
	{
	  uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
				qport, &sport, (char *) NULL);
	  return FALSE;
	}
    }
  else
    {
      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
      uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
			    qport, &sport, (char *) NULL);
      return FALSE;
    }

  /* Tell the remote system who we are.   */
  zsend = zbufalc (strlen (sDaemon.zlocalname) + sizeof "Shere=");
  sprintf (zsend, "Shere=%s", sDaemon.zlocalname);
  fret = fsend_uucp_cmd (qconn, zsend);
  ubuffree (zsend);
  if (! fret)
    {
      uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
			    qport, &sport, zloc);
      return FALSE;
    }

  zstr = zget_uucp_cmd (qconn, TRUE, fstrip);
  if (zstr == NULL)
    {
      uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
			    qport, &sport, zloc);
      return FALSE;
    }

  if (zstr[0] != 'S')
    {
      ulog (LOG_ERROR, "Bad introduction string");
      ubuffree (zstr);
      uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
			    qport, &sport, zloc);
      return FALSE;
    }

  zspace = strchr (zstr, ' ');
  if (zspace != NULL)
    *zspace = '\0';

  iuuconf = uuconf_system_info (puuconf, zstr + 1, &ssys);
  if (iuuconf == UUCONF_NOT_FOUND)
    {
      char *zscript;

      /* Run the remote.unknown script, if appropriate.  */
      iuuconf = uuconf_remote_unknown (puuconf, &zscript);
      if (iuuconf == UUCONF_SUCCESS)
	{
	  if (! fsysdep_unknown_caller (zscript, zstr + 1))
	    {
	      xfree ((pointer) zscript);
	      (void) fsend_uucp_cmd (qconn, "RYou are unknown to me");
	      ubuffree (zstr);
	      uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
				    qport, &sport, zloc);
	      return FALSE;
	    }
	  xfree ((pointer) zscript);
	}
      else if (iuuconf != UUCONF_NOT_FOUND)
	{
	  ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
	  ubuffree (zstr);
	  uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
				qport, &sport, zloc);
	  return FALSE;
	}

      if (! funknown_system (puuconf, zstr + 1, &ssys))
	{
	  (void) fsend_uucp_cmd (qconn, "RYou are unknown to me");
	  ulog (LOG_ERROR, "Call from unknown system %s", zstr + 1);
	  ubuffree (zstr);
	  uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
				qport, &sport, zloc);
	  return FALSE;
	}
    }
  else if (iuuconf != UUCONF_SUCCESS)
    {
      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
      ubuffree (zstr);
      uaccept_call_cleanup (puuconf, (struct uuconf_system *) NULL,
			    qport, &sport, zloc);
      return FALSE;
    }

  qany = NULL;
  for (qsys = &ssys; qsys != NULL; qsys = qsys->uuconf_qalternate)
    {
      if (! qsys->uuconf_fcalled)
	continue;

      if (qsys->uuconf_zcalled_login == NULL
	  || strcmp (qsys->uuconf_zcalled_login, "ANY") == 0)
	{
	  if (qany == NULL)
	    qany = qsys;
	}
      else if (strcmp (qsys->uuconf_zcalled_login, zlogin) == 0)
	break;
    }

  if (qsys == NULL && qany != NULL)
    {
      iuuconf = uuconf_validate (puuconf, qany, zlogin);
      if (iuuconf == UUCONF_SUCCESS)
	qsys = qany;
      else if (iuuconf != UUCONF_NOT_FOUND)
	{
	  ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
	  ubuffree (zstr);
	  uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
	  return FALSE;
	}
    }

  if (qsys == NULL)
    {
      (void) fsend_uucp_cmd (qconn, "RLOGIN");
      ulog (LOG_ERROR, "System %s used wrong login name %s",
	    zstr + 1, zlogin);
      ubuffree (zstr);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return FALSE;
    }

  sDaemon.qsys = qsys;

  if (pzsystem != NULL)
    *pzsystem = zbufcpy (qsys->uuconf_zname);

  ulog_system (qsys->uuconf_zname);

#if DEBUG > 1
  if (qsys->uuconf_zdebug != NULL)
    iDebug |= idebug_parse (qsys->uuconf_zdebug);
#endif

  /* See if we are supposed to call the system back.  This will queue
     up an empty command.  It would be better to actually call back
     directly at this point as well.  */
  if (qsys->uuconf_fcallback)
    {
      (void) fsend_uucp_cmd (qconn, "RCB");
      ulog (LOG_NORMAL, "Will call back");

      /* Clear any existing status.  */
      sstat.ttype = STATUS_COMPLETE;
      sstat.cretries = 0;
      sstat.ilast = ixsysdep_time ((long *) NULL);
      sstat.cwait = 0;
      (void) fsysdep_set_status (qsys, &sstat);

      ubuffree (zsysdep_spool_commands (qsys, UUCONF_GRADE_HIGH, 0,
					(const struct scmd *) NULL));
      ubuffree (zstr);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return TRUE;
    }

  /* We only permit one call at a time from a remote system.  Lock it.  */
  if (! fsysdep_lock_system (qsys))
    {
      if (qsys->uuconf_fsequence)
	{
	  /* At this point the calling system has already incremented
	     its sequence number, so we increment ours.  This will
	     only cause a mismatch if the other system is not what it
	     says it is.  */
	  (void) ixsysdep_get_sequence (qsys);
	}
      (void) fsend_uucp_cmd (qconn, "RLCK");
      ulog (LOG_ERROR, "System already locked");
      ubuffree (zstr);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return FALSE;
    }
  sLocked_system = *qsys;
  fLocked_system = TRUE;

  /* Set the system status.  We don't care what the status was before.
     We also don't want to kill the conversation just because we can't
     output the .Status file, so we ignore any errors.  */
  sstat.ttype = STATUS_TALKING;
  sstat.cretries = 0;
  sstat.ilast = ixsysdep_time ((long *) NULL);
  sstat.cwait = 0;
  (void) fsysdep_set_status (qsys, &sstat);

  /* Check the arguments of the remote system, if any.  */
  fgotseq = FALSE;
  fgotn = FALSE;
  if (zspace != NULL)
    {
      char **paz;
      char **pzset;

      ++zspace;

      /* Break the introduction line up into arguments.  */
      paz = (char **) xmalloc ((strlen (zspace) / 2 + 2) * sizeof (char *));
      pzset = paz;
      *pzset++ = NULL;
      while (TRUE)
	{
	  while (*zspace != '\0' && isspace (BUCHAR (*zspace)))
	    ++zspace;
	  if (*zspace == '\0')
	    break;
	  *pzset++ = zspace;
	  ++zspace;
	  while (*zspace != '\0' && ! isspace (BUCHAR (*zspace)))
	    ++zspace;
	  if (*zspace == '\0')
	    break;
	  *zspace++ = '\0';
	}

      if (pzset != paz + 1)
	{
	  int iopt;

	  *pzset = NULL;

	  /* We are going to use getopt to parse the arguments.  We
	     must clear optind to force getopt to reinitialize, and
	     clear opterr to prevent getopt from printing an error
	     message.  This approach assumes we are using the GNU
	     getopt, which is distributed with the program anyhow.  */
	  optind = 0;
	  opterr = 0;
	  
	  while ((iopt = getopt (pzset - paz, paz,
				 "N::p:Q:RU:v:x:")) != EOF)
	    {
	      long iseq;
	      long c;
	      char b;
	      int iwant;

	      switch (iopt)
		{
		case 'N':
		  /* This is used to indicate support for Taylor UUCP
		     extensions.  An plain -N mean support for size
		     negotiation.  If -N is followed by a number (with
		     no intervening space), the number is a bit field
		     of feature flags as defined in trans.h.  Note
		     that the argument may start with 0x for hex or 0
		     for octal.  */
		  fgotn = TRUE;
		  if (optarg == NULL)
		    sDaemon.ifeatures |= FEATURE_SIZES | FEATURE_V103;
		  else
		    sDaemon.ifeatures |= (int) strtol (optarg,
						       (char **) NULL,
						       0);
		  break;

		case 'p':
		  /* The argument is the lowest grade of work the
		     local system should send.  */
		  if (UUCONF_GRADE_LEGAL (optarg[0]))
		    sDaemon.bgrade = optarg[0];
		  break;

		case 'Q':
		  /* The conversation sequence number.  */
		  iseq = strtol (optarg, (char **) NULL, 10);
		  if (qsys->uuconf_fsequence
		      && iseq != ixsysdep_get_sequence (qsys))
		    {
		      (void) fsend_uucp_cmd (qconn, "RBADSEQ");
		      ulog (LOG_ERROR, "Out of sequence call rejected");
		      sstat.ttype = STATUS_FAILED;
		      (void) fsysdep_set_status (qsys, &sstat);
		      xfree ((pointer) paz);
		      ubuffree (zstr);
		      uaccept_call_cleanup (puuconf, &ssys, qport, &sport,
					    zloc);
		      return FALSE;
		    }
		  fgotseq = TRUE;
		  break;

		case 'R':
		  /* The remote system supports file restart.  */
		  sDaemon.ifeatures |= FEATURE_RESTART;
		  break;

		case 'U':
		  /* The maximum file size the remote system is
		     prepared to received, in blocks where each block
		     is 512 bytes.  */
		  c = strtol (optarg, (char **) NULL, 0);
		  if (c > 0 && c < LONG_MAX / (long) 512)
		    sDaemon.cmax_receive = c * (long) 512;
		  break;

		case 'v':
		  /* -vgrade=X can be used to set the lowest grade of
		     work the local system should send.  */
		  if (strncmp (optarg, "grade=", sizeof "grade=" - 1) == 0)
		    {
		      b = optarg[sizeof "grade=" - 1];
		      if (UUCONF_GRADE_LEGAL (b))
			sDaemon.bgrade = b;
		    }
		  break;

		case 'x':
		  iwant = (int) strtol (optarg, (char **) NULL, 10);
#if DEBUG > 1
		  if (iwant <= 9)
		    iwant = (1 << iwant) - 1;
		  if (qsys->uuconf_zmax_remote_debug != NULL)
		    iwant &= idebug_parse (qsys->uuconf_zmax_remote_debug);
		  else
		    iwant &= DEBUG_ABNORMAL | DEBUG_CHAT | DEBUG_HANDSHAKE;
		  if ((iDebug | iwant) != iDebug)
		    {
		      iDebug |= iwant;
		      ulog (LOG_NORMAL, "Setting debugging mode to 0%o",
			    iDebug);
		    }
#endif
		  break;

		default:
		  break;
		}
	    }
	}

      xfree ((pointer) paz);
    }

  ubuffree (zstr);

  if (qsys->uuconf_fsequence && ! fgotseq)
    {
      (void) fsend_uucp_cmd (qconn, "RBADSEQ");
      ulog (LOG_ERROR, "No sequence number (call rejected)");
      sstat.ttype = STATUS_FAILED;
      (void) fsysdep_set_status (qsys, &sstat);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return FALSE;
    }

  /* We recognized the system, and the sequence number (if any) was
     OK.  Send an ROK, and send a list of protocols.  If we got the -N
     switch, send ROKN to confirm it; if the -N switch was followed by
     a feature bitfield, return our own feature bitfield.  */
  {
    char ab[20];
    const char *zreply;

    if (! fgotn)
      {
	if ((sDaemon.ifeatures & FEATURE_RESTART) == 0)
	  zreply = "ROK";
	else
	  {
	    /* We got -R without -N, so assume that this is SVR4 UUCP.
	       SVR4 UUCP expects ROK -R to signal support for file
	       restart.  */
	    sDaemon.ifeatures |= FEATURE_SVR4 | FEATURE_SIZES;
	    zreply = "ROK -R";
	  }
      }
    else if ((sDaemon.ifeatures & FEATURE_V103) != 0)
      zreply = "ROKN";
    else
      {
	sprintf (ab, "ROKN0%o",
		 (unsigned int) (FEATURE_SIZES
				 | FEATURE_EXEC
				 | FEATURE_RESTART));
	zreply = ab;
      }
    if (! fsend_uucp_cmd (qconn, zreply))
      {
	sstat.ttype = STATUS_FAILED;
	(void) fsysdep_set_status (qsys, &sstat);
	uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
	return FALSE;
      }
  }

  /* Determine the reliability of the connection based on the
     reliability of the port and the dialer.  If we have no
     information, default to a reliable eight-bit full-duplex
     connection.  */
  if (ftcp_port)
    sDaemon.ireliable = (UUCONF_RELIABLE_SPECIFIED
			 | UUCONF_RELIABLE_ENDTOEND
			 | UUCONF_RELIABLE_RELIABLE
			 | UUCONF_RELIABLE_EIGHT
			 | UUCONF_RELIABLE_FULLDUPLEX);
  else
    {
      if (qport != NULL
	  && (qport->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0)
	sDaemon.ireliable = qport->uuconf_ireliable;
      if (qdialer != NULL
	  && (qdialer->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0)
	{
	  if (sDaemon.ireliable != 0)
	    sDaemon.ireliable &= qdialer->uuconf_ireliable;
	  else
	    sDaemon.ireliable = qdialer->uuconf_ireliable;
	}
      if (sDaemon.ireliable == 0)
	sDaemon.ireliable = (UUCONF_RELIABLE_RELIABLE
			     | UUCONF_RELIABLE_EIGHT
			     | UUCONF_RELIABLE_FULLDUPLEX
			     | UUCONF_RELIABLE_SPECIFIED);
    }

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

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

      zsend = zbufalc (CPROTOCOLS + 2);
      zset = zsend;
      *zset++ = 'P';

      /* If the system did not specify a list of protocols, we want
	 only protocols that match the known reliability of the dialer
	 and the port.  */
      for (i = 0; i < CPROTOCOLS; i++)
	{
	  int ipr;

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

  fret = fsend_uucp_cmd (qconn, zsend);
  ubuffree (zsend);
  if (! fret)
    {
      sstat.ttype = STATUS_FAILED;
      (void) fsysdep_set_status (qsys, &sstat);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return FALSE;
    }
    
  /* The master will now send back the selected protocol.  */
  zstr = zget_uucp_cmd (qconn, TRUE, fstrip);
  if (zstr == NULL)
    {
      sstat.ttype = STATUS_FAILED;
      (void) fsysdep_set_status (qsys, &sstat);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return FALSE;
    }

  if (zstr[0] != 'U')
    {
      ulog (LOG_ERROR, "Bad protocol response string");
      sstat.ttype = STATUS_FAILED;
      (void) fsysdep_set_status (qsys, &sstat);
      ubuffree (zstr);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return FALSE;
    }

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

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

  ubuffree (zstr);

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

  sDaemon.qproto = &asProtocols[i];

  /* If we are using a half-duplex line, act as though we have only a
     single channel; otherwise we might start a send and a receive at
     the same time.  */
  if ((sDaemon.ireliable & UUCONF_RELIABLE_FULLDUPLEX) == 0)
    sDaemon.cchans = 1;
  else
    sDaemon.cchans = asProtocols[i].cchans;

  /* Run the chat script for when a call is received.  */
  if (! fchat (qconn, puuconf, &qsys->uuconf_scalled_chat, qsys,
	       (const struct uuconf_dialer *) NULL, (const char *) NULL,
	       FALSE, zport, iconn_baud (qconn)))
    {
      sstat.ttype = STATUS_FAILED;
      sstat.ilast = ixsysdep_time ((long *) NULL);
      (void) fsysdep_set_status (qsys, &sstat);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return FALSE;
    }

  /* Run any protocol parameter commands.  */
  if (sDaemon.qproto->qcmds != NULL)
    {
      if (qsys->uuconf_qproto_params != NULL)
	uapply_proto_params (puuconf, sDaemon.qproto->bname,
			     sDaemon.qproto->qcmds,
			     qsys->uuconf_qproto_params);
      if (qport != NULL
	  && qport->uuconf_qproto_params != NULL)
	uapply_proto_params (puuconf, sDaemon.qproto->bname,
			     sDaemon.qproto->qcmds,
			     qport->uuconf_qproto_params);
      if (qdialer != NULL
	  && qdialer->uuconf_qproto_params != NULL)
	uapply_proto_params (puuconf, sDaemon.qproto->bname,
			     sDaemon.qproto->qcmds,
			     qdialer->uuconf_qproto_params);
    }

  /* We don't need the dialer information any more.  */
  if (qdialer == &sdialer)
    (void) uuconf_dialer_free (puuconf, &sdialer);

  /* Turn on the selected protocol and get any jobs queued for the
     system.  */
  if (! (*sDaemon.qproto->pfstart) (&sDaemon, &zlog)
      || ! fqueue (&sDaemon, (boolean *) NULL))
    {
      uclear_queue (&sDaemon);
      sstat.ttype = STATUS_FAILED;
      sstat.ilast = ixsysdep_time ((long *) NULL);
      (void) fsysdep_set_status (qsys, &sstat);
      uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);
      return FALSE;
    }

  if (zlog == NULL)
    {
      zlog = zbufalc (sizeof "protocol ''" + 1);
      sprintf (zlog, "protocol '%c'", sDaemon.qproto->bname);
    }

  zgrade = zbufalc (sizeof "grade  " + 1);
  if (sDaemon.bgrade == UUCONF_GRADE_LOW)
    *zgrade = '\0';
  else
    sprintf (zgrade, "grade %c ", sDaemon.bgrade);

  /* If we are using HAVE_HDB_LOGGING, then the previous ``incoming
     call'' message went to the general log, since we didn't know the
     system name at that point.  In that case, we repeat the port and
     login names.  */
#if HAVE_HDB_LOGGING
  ulog (LOG_NORMAL, "Handshake successful (login %s port %s %s%s)",
	zlogin,
	zLdevice == NULL ? "unknown" : zLdevice,
	zgrade, zlog);
#else /* ! HAVE_HDB_LOGGING */
  ulog (LOG_NORMAL, "Handshake successful (%s%s)", zgrade, zlog);
#endif /* ! HAVE_HDB_LOGGING */

  ubuffree (zlog);
  ubuffree (zgrade);

  {
    long iend_time;

    fret = floop (&sDaemon);

    /* Hangup.  As the answerer, we send seven O's and expect to
       receive six O's.  We send the seven O's twice to help the other
       side.  We don't worry about errors here.  */
    if (fsend_uucp_cmd (qconn, "OOOOOOO")
	&& fsend_uucp_cmd (qconn, "OOOOOOO"))
      {
	int fdone;

	/* We look for the remote hangup string to ensure that the
	   modem has sent out our hangup string.  This is only
	   necessary because some versions of UUCP complain if they
	   don't get the hangup string.  We look for the string
	   several times because supposedly some implementations send
	   some garbage after the last packet but before the hangup
	   string.  */
	for (i = 0; i < 25; i++)
	  {
	    zstr = zget_uucp_cmd (qconn, FALSE, fstrip);
	    if (zstr == NULL)
	      break;
	    fdone = strstr (zstr, "OOOOOO") != NULL;
	    ubuffree (zstr);
	    if (fdone)
	      break;
	  }
      }

    iend_time = ixsysdep_time ((long *) NULL);

    ulog (LOG_NORMAL, "Call complete (%ld seconds %ld bytes %ld bps)",
	  iend_time - istart_time,
	  sDaemon.csent + sDaemon.creceived,
	  (iend_time != istart_time
	   ? (sDaemon.csent + sDaemon.creceived) / (iend_time - istart_time)
	   : 0));

    uclear_queue (&sDaemon);

    if (fret)
      sstat.ttype = STATUS_COMPLETE;
    else
      sstat.ttype = STATUS_FAILED;
    sstat.ilast = iend_time;
    (void) fsysdep_set_status (qsys, &sstat);

    if (sDaemon.irunuuxqt == UUCONF_RUNUUXQT_PERCALL
	|| (sDaemon.irunuuxqt > 0 && sDaemon.cxfiles_received > 0))
      (void) fspawn_uuxqt (TRUE, qsys->uuconf_zname, zconfig);

    uaccept_call_cleanup (puuconf, &ssys, qport, &sport, zloc);

    return fret;
  }
}

/* Clean up after faccept_call.  */

static void
uaccept_call_cleanup (puuconf, qfreesys, qport, qfreeport, zloc)
     pointer puuconf;
     struct uuconf_system *qfreesys;
     struct uuconf_port *qport;
     struct uuconf_port *qfreeport;
     char *zloc;
{
  if (fLocked_system)
    {
      (void) fsysdep_unlock_system (&sLocked_system);
      fLocked_system = FALSE;
    }
  if (qfreesys != NULL)
    (void) uuconf_system_free (puuconf, qfreesys);
  if (qport == qfreeport)
    (void) uuconf_port_free (puuconf, qfreeport);
  xfree ((pointer) zloc);
  ulog_system ((const char *) NULL);
}

/* Apply protocol parameters, once we know the protocol.  */

static void
uapply_proto_params (puuconf, bproto, qcmds, pas)
     pointer puuconf;
     int bproto;
     struct uuconf_cmdtab *qcmds;
     struct uuconf_proto_param *pas;
{
  struct uuconf_proto_param *qp;

  for (qp = pas; qp->uuconf_bproto != '\0'; qp++)
    {
      if (qp->uuconf_bproto == bproto)
	{
	  struct uuconf_proto_param_entry *qe;

	  for (qe = qp->uuconf_qentries; qe->uuconf_cargs > 0; qe++)
	    {
	      int iuuconf;

	      iuuconf = uuconf_cmd_args (puuconf, qe->uuconf_cargs,
					 qe->uuconf_pzargs, qcmds,
					 (pointer) NULL,
					 (uuconf_cmdtabfn) NULL, 0,
					 (pointer) NULL);
	      if (UUCONF_ERROR_VALUE (iuuconf) != UUCONF_SUCCESS)
		{
		  ulog (LOG_ERROR, "Error in %c protocol parameters",
			bproto);
		  ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
		}
	    }

	  break;
	}
    }
}

/* Send a string to the other system beginning with a DLE
   character and terminated with a null byte.  This is only
   used when no protocol is in force.  */

static boolean
fsend_uucp_cmd (qconn, z)
     struct sconnection *qconn;
     const char *z;
{
  size_t cwrite;
  char *zalc;
  boolean fret;

  DEBUG_MESSAGE1 (DEBUG_HANDSHAKE, "fsend_uucp_cmd: Sending \"%s\"", z);

  cwrite = strlen (z) + 2;

  zalc = zbufalc (cwrite);
  zalc[0] = '\020';
  memcpy (zalc + 1, z, cwrite - 1);

  fret = fconn_write (qconn, zalc, cwrite);
  ubuffree (zalc);
  return fret;
}

/* Get a UUCP command beginning with a DLE character and ending with a
   null byte.  This is only used when no protocol is in force.  This
   implementation has the potential of being seriously slow.  It also
   doesn't have any real error recovery.  The frequired argument is
   passed as TRUE if we need the string; we don't care that much if
   we're closing down the connection anyhow.  */

#define CTIMEOUT (120)
#define CSHORTTIMEOUT (10)
#define CINCREMENT (100)

static char *
zget_uucp_cmd (qconn, frequired, fstrip)
     struct sconnection *qconn;
     boolean frequired;
     boolean fstrip;
{
  char *zalc;
  size_t calc;
  size_t cgot;
  boolean fintro;
  long iendtime;
  int ctimeout;
#if DEBUG > 1
  int cchars;
  int iolddebug;
#endif

  iendtime = ixsysdep_time ((long *) NULL);
  if (frequired)
    iendtime += CTIMEOUT;
  else
    iendtime += CSHORTTIMEOUT;

#if DEBUG > 1
  cchars = 0;
  iolddebug = iDebug;
  if (FDEBUGGING (DEBUG_HANDSHAKE))
    {
      ulog (LOG_DEBUG_START, "zget_uucp_cmd: Got \"");
      iDebug &=~ (DEBUG_INCOMING | DEBUG_PORT);
    }
#endif

  zalc = NULL;
  calc = 0;
  cgot = 0;
  fintro = FALSE;
  while ((ctimeout = (int) (iendtime - ixsysdep_time ((long *) NULL))) > 0)
    {
      int b;
      
      b = breceive_char (qconn, ctimeout, frequired);
      /* Now b == -1 on timeout, -2 on error.  */
      if (b < 0)
	{
#if DEBUG > 1
	  if (FDEBUGGING (DEBUG_HANDSHAKE))
	    {
	      ulog (LOG_DEBUG_END, "\" (%s)",
		    b == -1 ? "timeout" : "error");
	      iDebug = iolddebug;
	    }
#endif
	  if (b == -1 && frequired)
	    ulog (LOG_ERROR, "Timeout");
	  ubuffree (zalc);
	  return NULL;
	}

      /* Apparently some systems use parity on these strings, so we
	 optionally strip the parity bit.  */
      if (fstrip)
	b &= 0x7f;

#if DEBUG > 1
      if (FDEBUGGING (DEBUG_HANDSHAKE))
	{
	  char ab[5];

	  ++cchars;
	  if (cchars > 60)
	    {
	      ulog (LOG_DEBUG_END, "\"");
	      ulog (LOG_DEBUG_START, "zget_uucp_cmd: Got \"");
	      cchars = 0;
	    }
	  (void) cdebug_char (ab, b);
	  ulog (LOG_DEBUG_CONTINUE, "%s", ab);
	}
#endif

      if (! fintro)
	{
	  if (b == '\020')
	    fintro = TRUE;
	  continue;
	}

      /* If we see another DLE, something has gone wrong; continue
	 as though this were the first one we saw.  */
      if (b == '\020')
	{
	  cgot = 0;
	  continue;
	}

      /* Some systems send a trailing \n on the Shere line.  As far as
	 I can tell this line can never contain a \n, so this
	 modification should be safe enough.  */
      if (b == '\r' || b == '\n')
	b = '\0';

      if (cgot >= calc)
	{
	  char *znew;

	  calc += CINCREMENT;
	  znew = zbufalc (calc);
	  if (cgot > 0)
	    memcpy (znew, zalc, cgot);
	  ubuffree (zalc);
	  zalc = znew;
	}

      zalc[cgot] = (char) b;
      ++cgot;

      if (b == '\0')
	{
#if DEBUG > 1
	  if (FDEBUGGING (DEBUG_HANDSHAKE))
	    {
	      ulog (LOG_DEBUG_END, "\"");
	      iDebug = iolddebug;
	    }
#endif
	  return zalc;
	}
    }

#if DEBUG > 1
  if (FDEBUGGING (DEBUG_HANDSHAKE))
    {
      ulog (LOG_DEBUG_END, "\" (timeout)");
      iDebug = iolddebug;
    }
#endif

  ubuffree (zalc);

  if (frequired)
    ulog (LOG_ERROR, "Timeout");
  return NULL;
}

/* Read a sequence of characters up to a newline or carriage return,
   and return the line without the line terminating character.
   Remember whether the last string we returned ended in \r; if it
   did, ignore a leading \n to account for \r\n pairs.  */

static char *
zget_typed_line (qconn, fstrip)
     struct sconnection *qconn;
     boolean fstrip;
{
  static boolean flastcr; 
  char *zalc;
  size_t calc;
  size_t cgot;

#if DEBUG > 1
  int cchars;
  int iolddebug;

  cchars = 0;
  iolddebug = iDebug;
  if (FDEBUGGING (DEBUG_CHAT))
    {
      ulog (LOG_DEBUG_START, "zget_typed_line: Got \"");
      iDebug &=~ (DEBUG_INCOMING | DEBUG_PORT);
    }
#endif

  zalc = NULL;
  calc = 0;
  cgot = 0;
  while (TRUE)
    {
      int b;
      
      b = breceive_char (qconn, CTIMEOUT, FALSE);

      /* Now b == -1 on timeout, -2 on error.  */

      if (b == -2 || FGOT_SIGNAL ())
	{
#if DEBUG > 1
	  if (FDEBUGGING (DEBUG_CHAT))
	    {
	      ulog (LOG_DEBUG_END, "\" (error)");
	      iDebug = iolddebug;
	    }
#endif
	  ubuffree (zalc);
	  flastcr = FALSE;
	  return NULL;
	}

      if (b == -1)
	{
	  flastcr = FALSE;
	  continue;
	}

      /* Optionally strip the parity bit.  */
      if (fstrip)
	b &= 0x7f;

#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 (b == '\n' && cgot == 0 && flastcr)
	{
	  /* Ignore \n in \r\n pair.  */
	  flastcr = FALSE;
	  continue;
	}

      flastcr = FALSE;

      if (cgot >= calc)
	{
	  char *znew;

	  calc += CINCREMENT;
	  znew = zbufalc (calc);
	  if (cgot > 0)
	    memcpy (znew, zalc, cgot);
	  ubuffree (zalc);
	  zalc = znew;
	}

      if (b == '\n')
	b = '\0';
      else if (b == '\r')
	{
	  flastcr = TRUE;
	  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;
	}
    }
}

/* Spawn a uuxqt job.  This probably belongs in some other file, but I
   don't have a good place for it.  */

boolean
fspawn_uuxqt (ffork, zsys, zconfig)
     boolean ffork;
     const char *zsys;
     const char *zconfig;
{
  char *zsysarg;
  char *zconfigarg;
  boolean fret;

  if (zsys == NULL)
    zsysarg = NULL;
  else
    {
      zsysarg = zbufalc (sizeof "-s" + strlen (zsys));
      sprintf (zsysarg, "-s%s", zsys);
    }

  if (zconfig == NULL)
    zconfigarg = NULL;
  else
    {
      zconfigarg = zbufalc (sizeof "-I" + strlen (zconfig));
      sprintf (zconfigarg, "-I%s", zconfig);
      if (zsysarg == NULL)
	{
	  zsysarg = zconfigarg;
	  zconfigarg = NULL;
	}
    }

  fret = fsysdep_run (ffork, "uuxqt", zsysarg, zconfigarg);

  ubuffree (zsysarg);
  ubuffree (zconfigarg);

  return fret;
}
