uuxqt.c   [plain text]


/* uuxqt.c
   Run uux commands.

   Copyright (C) 1991, 1992, 1993, 1994, 1995, 2002 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.
   */

#include "uucp.h"

#if USE_RCS_ID
const char uuxqt_rcsid[] = "$Id: uuxqt.c,v 1.92 2002/03/05 19:10:42 ian Rel $";
#endif

#include <errno.h>
#include <ctype.h>

#include "getopt.h"

#include "uudefs.h"
#include "uuconf.h"
#include "system.h"

/* Static variables used to unlock things if we get a fatal error.  */
static int iQlock_seq = -1;
static const char *zQunlock_cmd;
static const char *zQunlock_file;
static boolean fQunlock_directory;
int cQmaxuuxqts;

/* Static variables to free in uqcleanup.  */
static char *zQoutput;
static char *zQmail;

/* Local functions.  */
static void uqusage P((void));
static void uqhelp P((void));
static void uqabort P((void));
static void uqdo_xqt_file P((pointer puuconf, const char *zfile,
			     const char *zbase,
			     const struct uuconf_system *qsys,
			     const char *zlocalname,
			     const char *zcmd, boolean *pfprocessed));
static void uqcleanup P((const char *zfile, int iflags));
static int isave_files P((const struct uuconf_system *, const char *zmail,
			  const char *zfile, int iclean));
static boolean fqforward P((const char *zfile, char **pzallowed,
			    const char *zlog, const char *zmail));

/* Long getopt options.  */
static const struct option asQlongopts[] =
{
  { "command", required_argument, 0, 'c' },
  { "system", required_argument, 0, 's' },
  { "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;
{
  /* The type of command to execute (NULL for any type).  */
  const char *zcmd = NULL;
  /* The configuration file name.  */
  const char *zconfig = NULL;
  /* The system to execute commands for.  */
  const char *zdosys = NULL;
  int iopt;
  pointer puuconf;
  int iuuconf;
  const char *zlocalname;
  boolean fany;
  char *z, *zgetsys;
  boolean ferr;
  boolean fsys;
  struct uuconf_system ssys;

  if (argc < 1)
  {
      zProgram = "uuxqt";
      uqusage ();
  }
  
  zProgram = argv[0];

  while ((iopt = getopt_long (argc, argv, "c:I:s:vx:", asQlongopts,
			      (int *) NULL)) != EOF)
    {
      switch (iopt)
	{
	case 'c':
	  /* Set the type of command to execute.  */
	  zcmd = optarg;
	  break;

	case 'I':
	  /* Set the configuration file name.  */
	  if (fsysdep_other_config (optarg))
	    zconfig = optarg;
	  break;

	case 's':
	  zdosys = optarg;
	  break;

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

	case 'v':
	  /* Print version and exit.  */
	  printf ("uuxqt (Taylor UUCP) %s\n", VERSION);
	  printf ("Copyright (C) 1991, 92, 93, 94, 1995, 2002 Ian Lance Taylor\n");
	  printf ("This program is free software; you may redistribute it under the terms of\n");
	  printf ("the GNU General Public LIcense.  This program has ABSOLUTELY NO WARRANTY.\n");
	  exit (EXIT_SUCCESS);
	  /*NOTREACHED*/

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

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

	default:
	  uqusage ();
	  break;
	}
    }

  if (optind != argc)
    uqusage ();

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

#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

  iuuconf = uuconf_maxuuxqts (puuconf, &cQmaxuuxqts);
  if (iuuconf != UUCONF_SUCCESS)
    ulog_uuconf (LOG_FATAL, puuconf, iuuconf);

#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 (uqabort);

  iuuconf = uuconf_localname (puuconf, &zlocalname);
  if (iuuconf == UUCONF_NOT_FOUND)
    {
      zlocalname = zsysdep_localname ();
      if (zlocalname == NULL)
	exit (EXIT_FAILURE);
    }
  else if (iuuconf != UUCONF_SUCCESS)
    ulog_uuconf (LOG_FATAL, puuconf, iuuconf);

  fsys = FALSE;

  /* If we were given a system name, canonicalize it.  */
  if (zdosys != NULL)
    {
      iuuconf = uuconf_system_info (puuconf, zdosys, &ssys);
      if (iuuconf != UUCONF_SUCCESS)
	{
	  if (iuuconf != UUCONF_NOT_FOUND)
	    ulog_uuconf (LOG_FATAL, puuconf, iuuconf);

	  if (strcmp (zdosys, zlocalname) == 0)
	    {
	      iuuconf = uuconf_system_local (puuconf, &ssys);
	      if (iuuconf != UUCONF_SUCCESS)
		ulog_uuconf (LOG_FATAL, puuconf, iuuconf);
	      ssys.uuconf_zname = (char *) zlocalname;
	    }
	  else
	    {
	      if (! funknown_system (puuconf, zdosys, &ssys))
		ulog (LOG_FATAL, "%s: system not found", zdosys);
	    }
	}

      zdosys = zbufcpy (ssys.uuconf_zname);
      fsys = TRUE;
    }

  /* Limit the number of uuxqt processes, and make sure we're the only
     uuxqt daemon running for this command.  */
  iQlock_seq = ixsysdep_lock_uuxqt (zcmd, cQmaxuuxqts);
  if (iQlock_seq < 0)
    {
      ulog_close ();
      usysdep_exit (TRUE);
    }
  zQunlock_cmd = zcmd;

  /* Keep scanning the execute files until we don't process any of
     them.  */
  do
    {
      fany = FALSE;

      /* Look for each execute file, and run it.  */

      if (! fsysdep_get_xqt_init (zdosys))
	{
	  ulog_close ();
	  usysdep_exit (FALSE);
	}

      while ((z = zsysdep_get_xqt (zdosys, &zgetsys, &ferr)) != NULL)
	{
	  const char *zloc;
	  boolean fprocessed;
	  char *zbase;

	  /* Get the system information for the system returned by
	     zsysdep_get_xqt.  */
	  if (! fsys || strcmp (ssys.uuconf_zname, zgetsys) != 0)
	    {
	      if (fsys)
		(void) uuconf_system_free (puuconf, &ssys);

	      iuuconf = uuconf_system_info (puuconf, zgetsys,
					    &ssys);
	      if (iuuconf != UUCONF_SUCCESS)
		{
		  if (iuuconf != UUCONF_NOT_FOUND)
		    {
		      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
		      ubuffree (z);
		      ubuffree (zgetsys);
		      continue;
		    }
		  else if (strcmp (zgetsys, zlocalname) == 0)
		    {
		      iuuconf = uuconf_system_local (puuconf, &ssys);
		      if (iuuconf != UUCONF_SUCCESS)
			{
			  ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
			  ubuffree (z);
			  ubuffree (zgetsys);
			  continue;
			}
		      ssys.uuconf_zname = (char *) zlocalname;
		    }
		  else
		    {
		      if (! funknown_system (puuconf, zgetsys, &ssys))
			{
			  ulog (LOG_ERROR,
				"%s: Execute file for unknown system %s",
				z, zgetsys);
			  (void) remove (z);
			  ubuffree (z);
			  ubuffree (zgetsys);
			  continue;
			}
		    }
		}

	      fsys = TRUE;
	    }

	  /* If we've received a signal, get out of the loop.  */
	  if (FGOT_SIGNAL ())
	    {
	      ubuffree (z);
	      ubuffree (zgetsys);
	      break;
	    }

	  /* Make sure we are supposed to be executing jobs for this
	     system.  */
	  if (zdosys != NULL && strcmp (zdosys, ssys.uuconf_zname) != 0)
	    {
	      ubuffree (z);
	      ubuffree (zgetsys);
	      continue;
	    }

	  zloc = ssys.uuconf_zlocalname;
	  if (zloc == NULL)
	    zloc = zlocalname;

	  ulog_system (ssys.uuconf_zname);
	  zbase = zsysdep_base_name (z);
	  uqdo_xqt_file (puuconf, z, zbase, &ssys, zloc, zcmd, &fprocessed);
	  ubuffree (zbase);
	  ulog_system ((const char *) NULL);
	  ulog_user ((const char *) NULL);

	  if (fprocessed)
	    fany = TRUE;
	  ubuffree (z);
	  ubuffree (zgetsys);
	}

      usysdep_get_xqt_free (zdosys);
    }
  while (fany && ! FGOT_SIGNAL ());

  (void) fsysdep_unlock_uuxqt (iQlock_seq, zcmd, cQmaxuuxqts);
  iQlock_seq = -1;

  ulog_close ();

  if (FGOT_SIGNAL ())
    ferr = TRUE;

  usysdep_exit (! ferr);

  /* Avoid errors about not returning a value.  */
  return 0;
}

static void
uqhelp ()
{
  printf ("Taylor UUCP %s, copyright (C) 1991, 92, 93, 94, 1995, 2002 Ian Lance Taylor\n",
	   VERSION);
  printf ("Usage: %s [-c,--command cmd] [-s,--system system]\n", zProgram);
  printf (" -c,--command cmd: Set type of command to execute\n");
  printf (" -s,--system system: Execute commands only for named system\n");
  printf (" -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");
  printf ("Report bugs to taylor-uucp@gnu.org\n");
}

static void
uqusage ()
{
  fprintf (stderr,
	   "Usage: %s [-c,--command cmd] [-s,--system system]\n", zProgram);
  fprintf (stderr, "Use %s --help for help\n", zProgram);
  exit (EXIT_FAILURE);
}

/* This is the abort function called when we get a fatal error.  */

static void
uqabort ()
{
#if ! HAVE_HDB_LOGGING
  /* When using HDB logging, it's a pain to have no system name.  */
  ulog_system ((const char *) NULL);
#endif

  ulog_user ((const char *) NULL);

  if (fQunlock_directory)
    (void) fsysdep_unlock_uuxqt_dir (iQlock_seq);

  if (zQunlock_file != NULL)
    (void) fsysdep_unlock_uuxqt_file (zQunlock_file);

  if (iQlock_seq >= 0)
    (void) fsysdep_unlock_uuxqt (iQlock_seq, zQunlock_cmd, cQmaxuuxqts);

  ulog_close ();

  usysdep_exit (FALSE);
}

/* An execute file is a series of lines.  The first character of each
   line is a command.  The following commands are defined:

   C command-line
   I standard-input
   O standard-output [ system ]
   F required-file filename-to-use
   R requestor-address
   U user system
   Z (acknowledge if command failed; default)
   N (no acknowledgement on failure)
   n (acknowledge if command succeeded)
   B (return command input on error)
   e (process with sh)
   E (process with exec)
   M status-file
   Q (C, I, O, F, R, U, M arguments are backslash quoted)
   W change working directory
   # comment

   Unrecognized commands are ignored.  We actually do not recognize
   the Z command, since it requests default behaviour.  We always send
   mail on failure, unless the N command appears.  We never send mail
   on success, unless the n command appears.

   This code does not currently support the B or M commands.  */

/* Command arguments.  */
static char **azQargs;
/* Command as a complete string.  */
static char *zQcmd;
/* Working directory */
static char *zQwd;
/* Standard input file name.  */
static char *zQinput;
/* Standard output file name.  */
static char *zQoutfile;
/* Standard output system.  */
static char *zQoutsys;
/* Number of required files.  */
static int cQfiles;
/* Names of required files.  */
static char **azQfiles;
/* Names required files should be renamed to (NULL if original is OK).  */
static char **azQfiles_to;
/* Requestor address (this is where mail should be sent).  */
static char *zQrequestor;
/* User name.  */
static const char *zQuser;
/* System name.  */
static const char *zQsystem;
/* This is set by the N flag, meaning that no acknowledgement should
   be mailed on failure.  */
static boolean fQno_ack;
/* This is set by the n flag, meaning that acknowledgement should be
   mailed if the command succeeded.  */
static boolean fQsuccess_ack;
/* This is set by the B flag, meaning that command input should be
   mailed to the requestor if an error occurred.  */
static boolean fQsend_input;
/* This is set by the E flag, meaning that exec should be used to
   execute the command.  */
static boolean fQuse_exec;
/* The status should be copied to this file on the requesting host.  */
static const char *zQstatus_file;
/* Whether the entries in the file are backslash quoted.  */
static boolean fQquoted;
#if ALLOW_SH_EXECUTION
/* This is set by the e flag, meaning that sh should be used to
   execute the command.  */
static boolean fQuse_sh;
#endif /* ALLOW_SH_EXECUTION */

static int iqcmd P((pointer puuconf, int argc, char **argv, pointer pvar,
		    pointer pinfo));
static int iqout P((pointer puuconf, int argc, char **argv, pointer pvar,
		    pointer pinfo));
static int iqfile P((pointer puuconf, int argc, char **argv, pointer pvar,
		     pointer pinfo));
static int iqrequestor P((pointer puuconf, int argc, char **argv,
			  pointer pvar, pointer pinfo));
static int iquser P((pointer puuconf, int argc, char **argv, pointer pvar,
		     pointer pinfo));
static int iqset P((pointer puuconf, int argc, char **argv, pointer pvar,
		    pointer pinfo));

/* We are lax about the number of arguments the functions accept,
   because there is a lot of variation in what other (buggy) UUCP
   packages generate.  Unused arguments are ignored.  */

static const struct uuconf_cmdtab asQcmds[] =
{
  { "C", UUCONF_CMDTABTYPE_FN | 0, NULL, iqcmd },
  { "I", UUCONF_CMDTABTYPE_STRING, (pointer) &zQinput, NULL },
  { "O", UUCONF_CMDTABTYPE_FN | 0, NULL, iqout },
  { "F", UUCONF_CMDTABTYPE_FN | 0, NULL, iqfile },
  { "R", UUCONF_CMDTABTYPE_FN | 0, NULL, iqrequestor },
  { "U", UUCONF_CMDTABTYPE_FN | 0, NULL, iquser },
  { "N", UUCONF_CMDTABTYPE_FN | 0, (pointer) &fQno_ack, iqset },
  { "n", UUCONF_CMDTABTYPE_FN | 0, (pointer) &fQsuccess_ack, iqset },
  { "B", UUCONF_CMDTABTYPE_FN | 0, (pointer) &fQsend_input, iqset },
#if ALLOW_SH_EXECUTION
  { "e", UUCONF_CMDTABTYPE_FN | 0, (pointer) &fQuse_sh, iqset },
#endif
  { "E", UUCONF_CMDTABTYPE_FN | 0, (pointer) &fQuse_exec, iqset },
  { "M", UUCONF_CMDTABTYPE_STRING, (pointer) &zQstatus_file, NULL },
  { "Q", UUCONF_CMDTABTYPE_FN | 0, (pointer) &fQquoted, iqset },
  { "W", UUCONF_CMDTABTYPE_STRING | 0, (pointer) &zQwd, NULL },
  { NULL, 0, NULL, NULL }
};

/* Handle the C command: store off the arguments.  */

/*ARGSUSED*/
static int
iqcmd (puuconf, argc, argv, pvar, pinfo)
     pointer puuconf ATTRIBUTE_UNUSED;
     int argc;
     char **argv;
     pointer pvar ATTRIBUTE_UNUSED;
     pointer pinfo ATTRIBUTE_UNUSED;
{
  int i;
  size_t clen;

  if (argc <= 1)
    return UUCONF_CMDTABRET_CONTINUE;

  azQargs = (char **) xmalloc (argc * sizeof (char *));
  clen = 0;
  for (i = 1; i < argc; i++)
    {
      azQargs[i - 1] = zbufcpy (argv[i]);
      clen += strlen (argv[i]) + 1;
    }
  azQargs[i - 1] = NULL;

  zQcmd = (char *) xmalloc (clen);
  zQcmd[0] = '\0';
  for (i = 1; i < argc - 1; i++)
    {
      strcat (zQcmd, argv[i]);
      strcat (zQcmd, " ");
    }
  strcat (zQcmd, argv[i]);

  return UUCONF_CMDTABRET_CONTINUE;
}

/* Handle the O command, which may have one or two arguments.  */

/*ARGSUSED*/
static int
iqout (puuconf, argc, argv, pvar, pinfo)
     pointer puuconf ATTRIBUTE_UNUSED;
     int argc;
     char **argv;
     pointer pvar ATTRIBUTE_UNUSED;
     pointer pinfo ATTRIBUTE_UNUSED;
{
  if (argc > 1)
    zQoutfile = zbufcpy (argv[1]);
  if (argc > 2)
    zQoutsys = zbufcpy (argv[2]);

  return UUCONF_CMDTABRET_CONTINUE;
}

/* Handle the F command, which may have one or two arguments.  */

/*ARGSUSED*/
static int
iqfile (puuconf, argc, argv, pvar, pinfo)
     pointer puuconf ATTRIBUTE_UNUSED;
     int argc;
     char **argv;
     pointer pvar ATTRIBUTE_UNUSED;
     pointer pinfo ATTRIBUTE_UNUSED;
{
  if (argc < 2)
    return UUCONF_CMDTABRET_CONTINUE;

  /* If this file is not in the spool directory, just ignore it.  */
  if (! fspool_file (argv[1]))
    return UUCONF_CMDTABRET_CONTINUE;

  ++cQfiles;
  azQfiles = (char **) xrealloc ((pointer) azQfiles,
				 cQfiles * sizeof (char *));
  azQfiles_to = (char **) xrealloc ((pointer) azQfiles_to,
				    cQfiles * sizeof (char *));

  azQfiles[cQfiles - 1] = zbufcpy (argv[1]);
  if (argc > 2)
    azQfiles_to[cQfiles - 1] = zbufcpy (argv[2]);
  else
    azQfiles_to[cQfiles - 1] = NULL;

  return UUCONF_CMDTABRET_CONTINUE;
}

/* Handle the R command, which may have one or two arguments.  */

/*ARGSUSED*/
static int
iqrequestor (puuconf, argc, argv, pvar, pinfo)
     pointer puuconf ATTRIBUTE_UNUSED;
     int argc;
     char **argv;
     pointer pvar ATTRIBUTE_UNUSED;
     pointer pinfo ATTRIBUTE_UNUSED;
{
  /* We normally have a single argument, which is the ``requestor''
     address, to which we should send any success or error messages.
     Apparently the DOS program UUPC sends two arguments, which are
     the username and the host.  */
  if (argc == 2)
    zQrequestor = zbufcpy (argv[1]);
  else if (argc > 2)
    {
      zQrequestor = zbufalc (strlen (argv[1]) + strlen (argv[2])
			     + sizeof "!");
      sprintf (zQrequestor, "%s!%s", argv[2], argv[1]);
    }

  return UUCONF_CMDTABRET_CONTINUE;
}

/* Handle the U command, which takes two arguments.  */

/*ARGSUSED*/
static int
iquser (puuconf, argc, argv, pvar, pinfo)
     pointer puuconf ATTRIBUTE_UNUSED;
     int argc;
     char **argv;
     pointer pvar ATTRIBUTE_UNUSED;
     pointer pinfo ATTRIBUTE_UNUSED;
{
  if (argc > 1)
    zQuser = argv[1];
  if (argc > 2)
    zQsystem = argv[2];
  return UUCONF_CMDTABRET_KEEP;
}

/* Handle various commands which just set boolean variables.  */

/*ARGSUSED*/
static int
iqset (puuconf, argc, argv, pvar, pinfo)
     pointer puuconf ATTRIBUTE_UNUSED;
     int argc ATTRIBUTE_UNUSED;
     char **argv ATTRIBUTE_UNUSED;
     pointer pvar;
     pointer pinfo ATTRIBUTE_UNUSED; 
{
  boolean *pf = (boolean *) pvar;

  *pf = TRUE;
  return UUCONF_CMDTABRET_CONTINUE;
}

/* The execution processing does a lot of things that have to be
   cleaned up.  Rather than try to add the appropriate statements
   to each return point, we keep a set of flags indicating what
   has to be cleaned up.  The actual clean up is done by the
   function uqcleanup.  */
#define REMOVE_FILE (01)
#define REMOVE_NEEDED (02)
#define FREE_QINPUT (04)
#define REMOVE_QINPUT (010)
#define FREE_OUTPUT (020)
#define FREE_MAIL (040)

static void ulog_azQargs(char *msg) {
    char *buf = NULL;
    int i;
    for(i = 0; azQargs[i]; i++) {
	char *tmp = NULL;
	asprintf(&tmp, "%s [%d] '%s'", buf ? buf : "", i, azQargs[i]);
	free(buf);
	buf = tmp;
    }
    ulog(LOG_ERROR, "%s %s", msg, buf);
    free(buf);
}

char *strbufdup(char *src) {
    char *ret = zbufalc(strlen(src));
    strcpy(ret, src);
    return ret;
}

/* Process an execute file.  The zfile argument is the name of the
   execute file.  The zbase argument is the base name of zfile.  The
   qsys argument describes the system it came from.  The zcmd argument
   is the name of the command we are executing (from the -c option) or
   NULL if any command is OK.  This sets *pfprocessed to TRUE if the
   file is ready to be executed.  */

static void
uqdo_xqt_file (puuconf, zfile, zbase, qsys, zlocalname, zcmd, pfprocessed)
     pointer puuconf;
     const char *zfile;
     const char *zbase;
     const struct uuconf_system *qsys;
     const char *zlocalname;
     const char *zcmd;
     boolean *pfprocessed;
{
  char *zabsolute;
  boolean ferr;
  FILE *e;
  int iuuconf;
  int i;
  int iclean;
  const char *zmail;
  char *zoutput;
  char *zinput;
  boolean fbadname;
  char abtemp[CFILE_NAME_LEN];
  char abdata[CFILE_NAME_LEN];
  char *zerror;
  struct uuconf_system soutsys;
  const struct uuconf_system *qoutsys;
  boolean fshell;
  size_t clen;
  char *zfullcmd;
  boolean ftemp;

  *pfprocessed = FALSE;

  e = fopen (zfile, "r");
  if (e == NULL)
    return;

  azQargs = NULL;
  zQcmd = NULL;
  zQwd = NULL;
  zQinput = NULL;
  zQoutfile = NULL;
  zQoutsys = NULL;
  cQfiles = 0;
  azQfiles = NULL;
  azQfiles_to = NULL;
  zQrequestor = NULL;
  zQuser = NULL;
  zQsystem = NULL;
  fQno_ack = FALSE;
  fQsuccess_ack = FALSE;
  fQsend_input = FALSE;
  fQuse_exec = FALSE;
  zQstatus_file = NULL;
  fQquoted = FALSE;
#if ALLOW_SH_EXECUTION
  fQuse_sh = FALSE;
#endif

  iuuconf = uuconf_cmd_file (puuconf, e, asQcmds, (pointer) zbase,
			     (uuconf_cmdtabfn) NULL,
			     (UUCONF_CMDTABFLAG_CASE
			      | UUCONF_CMDTABFLAG_NOCOMMENTS),
			     (pointer) NULL);
  (void) fclose (e);

  if (iuuconf != UUCONF_SUCCESS)
    {
      ulog_uuconf (LOG_ERROR, puuconf, iuuconf);

      /* If we got a non-transient error, we notify the administrator.
	 We can't bounce it back to the original requestor, because we
	 don't know how to read the file to figure out who it is (it
	 would probably be possible to read the file and work it out,
	 but it doesn't seem worth it for such an unlikely error).  */
      if (UUCONF_ERROR_VALUE (iuuconf) == UUCONF_SYNTAX_ERROR
	  || UUCONF_ERROR_VALUE (iuuconf) == UUCONF_UNKNOWN_COMMAND)
	{
	  const char *az[20];
	  char *znew;

	  i = 0;
	  az[i++] = "The execution file\n\t";
	  az[i++] = zfile;
	  az[i++] = "\nfor system\n\t";
	  az[i++] = qsys->uuconf_zname;
	  az[i++] = "\nwas corrupt.  ";
	  znew = zsysdep_save_corrupt_file (zfile);
	  if (znew == NULL)
	    {
	      az[i++] = "The file could not be preserved.\n";
	      (void) remove (zfile);
	    }
	  else
	    {
	      az[i++] = "It has been moved to\n\t";
	      az[i++] = znew;
	      az[i++] = "\n";
	    }
	  (void) fsysdep_mail (OWNER, "Corrupt execution file", i, az);
	  ubuffree (znew);
	}

      return;
    }

  if (fQquoted)
    {
      if (azQargs != NULL)
	{
	  for (i = 0; azQargs[i] != NULL; ++i)
	    (void) cescape (azQargs[i]);
	}
      if (zQcmd != NULL)
	(void) cescape (zQcmd);
      if (zQinput != NULL)
	(void) cescape (zQinput);
      if (zQoutfile != NULL)
	(void) cescape (zQoutfile);
      if (zQoutsys != NULL)
	(void) cescape (zQoutsys);
      for (i = 0; i < cQfiles; ++i)
	{
	  (void) cescape (azQfiles[i]);
	  if (azQfiles_to[i] != NULL)
	    (void) cescape (azQfiles_to[i]);
	}
      if (zQrequestor != NULL)
	(void) cescape (zQrequestor);
      if (zQuser != NULL)
	(void) cescape ((char *) zQuser);
      if (zQsystem != NULL)
	(void) cescape ((char *) zQsystem);
      if (zQstatus_file != NULL)
	(void) cescape ((char *) zQstatus_file);
    }

  iclean = 0;

  if (azQargs == NULL)
    {
      ulog (LOG_ERROR, "%s: No command given", zbase);
      uqcleanup (zfile, iclean | REMOVE_FILE);
      return;
    }

  if (zcmd != NULL)
    {
      if (strcmp (zcmd, azQargs[0]) != 0)
	{
	  uqcleanup (zfile, iclean);
	  return;
	}
    }
  else
    {
      /* If there is a lock file for this particular command already,
	 it means that some other uuxqt is supposed to handle it.  */
      if (fsysdep_uuxqt_locked (azQargs[0]))
	{
	  uqcleanup (zfile, iclean);
	  return;
	}
    }

  /* Lock this particular file.  */
  if (! fsysdep_lock_uuxqt_file (zfile))
    {
      uqcleanup (zfile, iclean);
      return;
    }

  zQunlock_file = zfile;

  /* Now that we have the file locked, make sure it still exists.
     Otherwise another uuxqt could have just finished processing it
     and removed the lock file.  */
  if (! fsysdep_file_exists (zfile))
    {
      uqcleanup (zfile, iclean);
      return;
    }

  if (zQuser != NULL)
    ulog_user (zQuser);
  else if (zQrequestor != NULL)
    ulog_user (zQrequestor);
  else
    ulog_user ("unknown");

  /* zQsystem, if it is set, comes from the execution file, which
     means that we do not trust it.  We only retain it if
     qsys->uuconf_zname is a prefix of it, since that can happen with
     a job from an anonymous system on certain spool directory types,
     and is unlikely to cause any trouble anyhow.  */
  if (zQsystem == NULL
      || strncmp (zQsystem, qsys->uuconf_zname,
		  strlen (qsys->uuconf_zname)) != 0)
    zQsystem = qsys->uuconf_zname;

  /* Make sure that all the required files exist, and get their
     full names in the spool directory.  */
  for (i = 0; i < cQfiles; i++)
    {
      char *zreal;

      zreal = zsysdep_spool_file_name (qsys, azQfiles[i], (pointer) NULL);
      if (zreal == NULL)
	{
	  uqcleanup (zfile, iclean);
	  return;
	}
      if (! fsysdep_file_exists (zreal))
	{
	  uqcleanup (zfile, iclean);
	  return;
	}
      ubuffree (azQfiles[i]);
      azQfiles[i] = zbufcpy (zreal);
      ubuffree (zreal);
    }

  /* Lock the execution directory.  */
  if (! fsysdep_lock_uuxqt_dir (iQlock_seq))
    {
      ulog (LOG_ERROR, "Could not lock execute directory");
      uqcleanup (zfile, iclean);
      return;
    }
  fQunlock_directory = TRUE;

  iclean |= REMOVE_FILE | REMOVE_NEEDED;
  *pfprocessed = TRUE;

  /* Get the address to mail results to.  Prepend the system from
     which the execute file originated, since mail addresses are
     relative to it.  */
  zmail = NULL;
  if (zQrequestor != NULL)
    zmail = zQrequestor;
  else if (zQuser != NULL)
    zmail = zQuser;
  if (zmail != NULL
#if HAVE_INTERNET_MAIL
      && strchr (zmail, '@') == NULL
#endif
      && strcmp (zQsystem, zlocalname) != 0)
    {
      char *zset;

      zset = zbufalc (strlen (zQsystem) + strlen (zmail) + 2);
      sprintf (zset, "%s!%s", zQsystem, zmail);
      zmail = zset;
      zQmail = zset;
      iclean |= FREE_MAIL;
    }

  /* The command "uucp" is handled specially.  We make sure that the
     appropriate forwarding is permitted, and we add a -u argument to
     specify the user.  */
  if (strcmp (azQargs[0], "uucp") == 0)
    {
      char *zfrom, *zto;
      boolean fmany;
      boolean finoptions;
      char **azargs;
      const char *zuser;

      zfrom = NULL;
      zto = NULL;
      fmany = FALSE;
      finoptions = TRUE;

      /* Skip all the options, and get the from and to specs.  We
	 don't permit multiple arguments.  We have to do mini-getopt
	 processing here.  */
      for (i = 1; azQargs[i] != NULL; i++)
	{
	  if (azQargs[i][0] == '-' && finoptions)
	    {
	      if (azQargs[i][1] == '-')
		{
		  if (azQargs[i][2] == '\0')
		    finoptions = FALSE;
		  /* The --grade, --notify, and --status options take
                     an argument.  */
		  else if (strncmp (azQargs[i] + 2, "g", 1) == 0
		      || strncmp (azQargs[i] + 2, "not", 3) == 0
		      || strncmp (azQargs[i] + 2, "s", 1) == 0)
		    {
		      if (strchr (azQargs[i] + 2, '=') == NULL)
			++i;
		    }
		  /* The --config, --user, and --debug options are not
                     permitted.  */
		  else if (strncmp (azQargs[i] + 2, "con", 3) == 0
			   || strncmp (azQargs[i] + 2, "us", 2) == 0
			   || strncmp (azQargs[i] + 2, "de", 2) == 0)
		    {
		      azQargs[i][1] = 'r';
		      azQargs[i][2] = '\0';
		      if (strchr (azQargs[i] + 3, '=') == NULL)
			{
			  ++i;
			  azQargs[i] = zbufcpy ("-r");
			}
		    }
		}
	      else
		{
		  char *zopts;

		  for (zopts = azQargs[i] + 1; *zopts != '\0'; zopts++)
		    {
		      /* The -g, -n, and -s options take an argument.  */
		      if (*zopts == 'g' || *zopts == 'n' || *zopts == 's' || *zopts == 'x')
			{
			  if (zopts[1] == '\0')
			    ++i;
			  break;
			}
		      /* The -I, -u and -x options are not permitted.  */
		      if (*zopts == 'I' || *zopts == 'u' || *zopts == 'x')
			{
			  *zopts = 'r';
			  if (zopts[1] != '\0')
			    zopts[1] = '\0';
			  else
			    {
			      ++i;
			      azQargs[i] = zbufcpy ("-r");
			    }
			  break;
			}
		    }
		}
	    }
	  else if (zfrom == NULL)
	    zfrom = azQargs[i];
	  else if (zto == NULL)
	    zto = azQargs[i];
	  else
	    {
	      fmany = TRUE;
	      break;
	    }
	}

#define USE_KTRACE 0
      /* Add the -u argument.  This is required to let uucp do the
	 correct permissions checking on the file transfer.  */
      for (i = 0; azQargs[i] != NULL; i++)
	;
      azargs = (char **) xmalloc ((i + 2 + USE_KTRACE) * sizeof (char *));
      ulog(LOG_ERROR, "USE_KTRACE %d azargs %p azQargs %p", USE_KTRACE, azargs, azQargs);
      azargs[0] = azQargs[0];
      zuser = zQuser;
      if (zuser == NULL)
	zuser = "uucp";
      azargs[1 + USE_KTRACE] = zbufalc (strlen (zQsystem) + strlen (zuser)
			   + sizeof "-u!");
      sprintf (azargs[1 + USE_KTRACE], "-u%s!%s", zQsystem, zuser);
      memcpy (azargs + 2 + USE_KTRACE, azQargs + 1 + USE_KTRACE, i * sizeof (char *));
      xfree ((pointer) azQargs);
      azQargs = azargs;

#if USE_KTRACE
      ulog(LOG_ERROR, "KTRACE");
	azQargs[0] = strbufdup("ktrace");
	azQargs[1] = strbufdup("-f");
	azQargs[2] = strbufdup("/tmp/uu.kt");
	azQargs[3] = zsysdep_find_command ("uucp", qsys->uuconf_pzcmds, qsys->uuconf_pzpath, &ferr);

      ulog_azQargs("USE_KTRACE ");
#endif

      /* Find the uucp binary.  */
      zabsolute = zsysdep_find_command (USE_KTRACE ? "ktrace" : "uucp", 
        qsys->uuconf_pzcmds, qsys->uuconf_pzpath, &ferr);
      ulog(LOG_ERROR, "zabsolute %s", zabsolute);
      if (zabsolute == NULL && ! ferr)
	{
	  const char *azcmds[2];

	  /* If "uucp" is not a permitted command, then the forwarding
	     entries must be set.  */
	  if (! fqforward (zfrom, qsys->uuconf_pzforward_from, "from", zmail)
	      || ! fqforward (zto, qsys->uuconf_pzforward_to, "to", zmail))
	    {
	      uqcleanup (zfile, iclean);
	      return;
	    }

	  /* If "uucp" is not a permitted command, then only uucp
	     requests with a single source are permitted, since that
	     is all that will be generated by uucp or uux.  */
	  if (fmany || zfrom == NULL || zto == NULL)
	    {
	      ulog (LOG_ERROR, "Bad uucp request %s", zQcmd);

	      if (zmail != NULL && ! fQno_ack)
		{
		  const char *az[20];

		  i = 0;
		  az[i++] = "Your execution request failed because it was an";
		  az[i++] = " unsupported uucp request.\n";
		  az[i++] = "Execution requested was:\n\t";
		  az[i++] = zQcmd;
		  az[i++] = "\n";

		  (void) fsysdep_mail (zmail, "Execution failed", i, az);
		}

	      uqcleanup (zfile, iclean);
	      return;
	    }

	  azcmds[0] = "uucp";
	  azcmds[1] = NULL;
	  zabsolute = zsysdep_find_command ("uucp", (char **) azcmds,
					    qsys->uuconf_pzpath, &ferr);
	}
      if (zabsolute == NULL)
	{
	  if (! ferr)
	    ulog (LOG_ERROR, "Can't find uucp executable");

	  uqcleanup (zfile, iclean &~ (REMOVE_FILE | REMOVE_NEEDED));
	  *pfprocessed = FALSE;
	  return;
	}
    }
  else
    {
      /* Get the pathname to execute.  */
      zabsolute = zsysdep_find_command (azQargs[0], qsys->uuconf_pzcmds,
					qsys->uuconf_pzpath,
					&ferr);
      if (zabsolute == NULL)
	{
	  if (ferr)
	    {
	      /* If we get an error, try again later.  */
	      uqcleanup (zfile, iclean &~ (REMOVE_FILE | REMOVE_NEEDED));
	      *pfprocessed = FALSE;
	      return;
	    }

	  /* Not permitted.  Send mail to requestor.  */
	  ulog (LOG_ERROR, "Not permitted to execute %s",
		azQargs[0]);

	  if (zmail != NULL && ! fQno_ack)
	    {
	      const char *az[20];

	      i = 0;
	      az[i++] = "Your execution request failed because you are not";
	      az[i++] = " permitted to execute\n\t";
	      az[i++] = azQargs[0];
	      az[i++] = "\non this system.\n";
	      az[i++] = "Execution requested was:\n\t";
	      az[i++] = zQcmd;
	      az[i++] = "\n";

	      (void) fsysdep_mail (zmail, "Execution failed", i, az);
	    }

	  iclean = isave_files (qsys, zmail, zfile, iclean);

	  uqcleanup (zfile, iclean);
	  return;
	}
    }

  ulog(LOG_ERROR, "Eh?");
  ubuffree (azQargs[0]);
  azQargs[0] = zabsolute;
  ulog_azQargs("zabsolute");
  ulog(LOG_ERROR, "azQargs[1] %p", azQargs[i]);

  for (i = 1; azQargs[i] != NULL; i++)
    {
      char *zlocal;

      zlocal = zsysdep_xqt_local_file (qsys, azQargs[i]);
      if (zlocal != NULL)
	{
	  ulog(LOG_ERROR, "[%d] zlocal %s, a %s", i, zlocal, azQargs[i]);
	  ubuffree (azQargs[i]);
	  azQargs[i] = zlocal;
	}
    }
  ulog_azQargs("zsysdep_xqt_local_file");

#if ! ALLOW_FILENAME_ARGUMENTS

  /* Check all the arguments to make sure they don't try to specify
     files they are not permitted to access.  */
  for (i = 1; azQargs[i] != NULL; i++)
    {
      if (! fsysdep_xqt_check_file (qsys, azQargs[i]))
	{
	  if (zmail != NULL && ! fQno_ack)
	    {
	      const char *az[20];
	      const char *zfailed;

	      zfailed = azQargs[i];
	      i = 0;
	      az[i++] = "Your execution request failed because you are not";
	      az[i++] = " permitted to refer to file\n\t";
	      az[i++] = zfailed;
	      az[i++] = "\non this system.\n";
	      az[i++] = "Execution requested was:\n\t";
	      az[i++] = zQcmd;
	      az[i++] = "\n";

	      (void) fsysdep_mail (zmail, "Execution failed", i, az);
	    }

	  iclean = isave_files (qsys, zmail, zfile, iclean);

	  uqcleanup (zfile, iclean);
	  return;
	}
    }

#endif /* ! ALLOW_FILENAME_ARGUMENTS */

  ulog(LOG_ERROR, "Executing %s (%s)", zbase, zQcmd);
  ulog (LOG_NORMAL, "Executing %s (%s)", zbase, zQcmd);
  ulog(LOG_ERROR, "zQoutsys %p zQinput %p", zQoutsys, zQinput);

  if (zQinput != NULL)
    {
      boolean fspool;
      char *zreal;

      fspool = fspool_file (zQinput);
      if (! fspool)
	zreal = zsysdep_local_file (zQinput, qsys->uuconf_zpubdir, &fbadname);
      else
	{
	  zreal = zsysdep_spool_file_name (qsys, zQinput, (pointer) NULL);
	  fbadname = FALSE;
	}
      if (zreal == NULL && ! fbadname)
	{
	  /* If we get an error, try again later.  */
	  uqcleanup (zfile, iclean &~ (REMOVE_FILE | REMOVE_NEEDED));
	  *pfprocessed = FALSE;
	  return;
	}

      if (zreal != NULL)
	{
	  zQinput = zreal;
	  iclean |= FREE_QINPUT;
	  if (fspool)
	    iclean |= REMOVE_QINPUT;
	}

      if (zreal == NULL
	  || (! fspool
	      && ! fin_directory_list (zQinput, qsys->uuconf_pzremote_send,
				       qsys->uuconf_zpubdir, TRUE, TRUE,
				       (const char *) NULL)))
	{
	  ulog (LOG_ERROR, "Not permitted to read %s", zQinput);
	      
	  if (zmail != NULL && ! fQno_ack)
	    {
	      const char *az[20];

	      i = 0;
	      az[i++] = "Your execution request failed because you are";
	      az[i++] = " not permitted to read\n\t";
	      az[i++] = zQinput;
	      az[i++] = "\non this system.\n";
	      az[i++] = "Execution requested was:\n\t";
	      az[i++] = zQcmd;
	      az[i++] = "\n";

	      (void) fsysdep_mail (zmail, "Execution failed", i, az);
	    }

	  uqcleanup (zfile, iclean);
	  return;
	}
    }

  zoutput = NULL;
  if (zQoutfile == NULL)
    qoutsys = NULL;
  else if (zQoutsys != NULL
	   && strcmp (zQoutsys, zlocalname) != 0)
    {
      char *zdata;
	 
      /* The output file is destined for some other system, so we must
	 use a temporary file to catch standard output.  */
      if (strcmp (zQoutsys, qsys->uuconf_zname) == 0)
	qoutsys = qsys;
      else
	{
	  iuuconf = uuconf_system_info (puuconf, zQoutsys, &soutsys);
	  if (iuuconf != UUCONF_SUCCESS)
	    {
	      if (iuuconf != UUCONF_NOT_FOUND)
		{
		  ulog_uuconf (LOG_ERROR, puuconf, iuuconf);
		  uqcleanup (zfile, iclean &~ (REMOVE_FILE | REMOVE_NEEDED));
		  *pfprocessed = FALSE;
		  return;
		}

	      if (! funknown_system (puuconf, zQoutsys, &soutsys))
		{
		  ulog (LOG_ERROR,
			"Can't send standard output to unknown system %s",
			zQoutsys);
		  /* We don't send mail to unknown systems, either.
		     Maybe we should.  */
		  uqcleanup (zfile, iclean);
		  return;
		}
	    }

	  qoutsys = &soutsys;
	}

      zdata = zsysdep_data_file_name (qoutsys, zlocalname,
				      BDEFAULT_UUX_GRADE, FALSE, abtemp,
				      abdata, (char *) NULL);
      if (zdata == NULL)
	{
	  /* If we get an error, try again later.  */
	  uqcleanup (zfile, iclean &~ (REMOVE_FILE | REMOVE_NEEDED));
	  *pfprocessed = FALSE;
	  return;
	}
      zoutput = zdata;
      zQoutput = zoutput;
      iclean |= FREE_OUTPUT;
    }
  else
    {
      boolean fok;
	 
      qoutsys = NULL;

      /* If we permitted the standard output to be redirected into
	 the spool directory, people could set up phony commands.  */
      if (fspool_file (zQoutfile))
	fok = FALSE;
      else
	{
	  zoutput = zsysdep_local_file (zQoutfile, qsys->uuconf_zpubdir,
					&fbadname);
	  if (zoutput == NULL)
	    {
	      if (! fbadname)
		{
		  /* If we get an error, try again later.  */
		  uqcleanup (zfile, iclean &~ (REMOVE_FILE | REMOVE_NEEDED));
		  *pfprocessed = FALSE;
		  return;
		}
	      fok = FALSE;
	    }
	  else
	    {
	      ubuffree (zQoutfile);
	      zQoutfile = zoutput;

	      /* Make sure it's OK to receive this file.  */
	      fok = fin_directory_list (zQoutfile,
					qsys->uuconf_pzremote_receive,
					qsys->uuconf_zpubdir, TRUE, FALSE,
					(const char *) NULL);
	    }
	}

      if (! fok)
	{
	  ulog (LOG_ERROR, "Not permitted to write to %s", zQoutfile);
	      
	  if (zmail != NULL && ! fQno_ack)
	    {
	      const char *az[20];

	      i = 0;
	      az[i++] = "Your execution request failed because you are";
	      az[i++] = " not permitted to write to\n\t";
	      az[i++] = zQoutfile;
	      az[i++] = "\non this system.\n";
	      az[i++] = "Execution requested was:\n\t";
	      az[i++] = zQcmd;
	      az[i++] = "\n";

	      (void) fsysdep_mail (zmail, "Execution failed", i, az);
	    }

	  uqcleanup (zfile, iclean);
	  return;
	}
    }

  ulog(LOG_ERROR, "fsysdep_copy_uuxqt_files...");
  /* Move the required files to the execution directory if necessary.  */
  zinput = zQinput;
  if (! fsysdep_copy_uuxqt_files (cQfiles, (const char **) azQfiles,
				  (const char **) azQfiles_to,
				  iQlock_seq, &zinput))
    {
      /* If we get an error, try again later.  */
      uqcleanup (zfile, iclean &~ (REMOVE_FILE | REMOVE_NEEDED));
      *pfprocessed = FALSE;
      return;
    }
  if (zQinput != NULL && strcmp (zQinput, zinput) != 0)
    {
      if ((iclean & FREE_QINPUT) != 0)
	ubuffree (zQinput);
      zQinput = zinput;
      iclean |= FREE_QINPUT;
    }

#if ALLOW_SH_EXECUTION
  fshell = fQuse_sh;
#else
  fshell = FALSE;
#endif

  /* Get a shell command which uses the full path of the command to
     execute.  */
  clen = 0;
  for (i = 0; azQargs[i] != NULL; i++) {
    clen += strlen (azQargs[i]) + 1;
  }
  zfullcmd = zbufalc (clen);
  strcpy (zfullcmd, azQargs[0]);
  for (i = 1; azQargs[i] != NULL; i++)
    {
      strcat (zfullcmd, " ");
      strcat (zfullcmd, azQargs[i]);
    }

  ulog(LOG_ERROR, "fsysdep_execute fshell %d zfullcmd: %s", fshell, zfullcmd);
  if (! fsysdep_execute (qsys,
			 zQuser == NULL ? (const char *) "uucp" : zQuser,
			 (const char **) azQargs, zfullcmd, zQinput,
			 zoutput, zQwd, fshell, iQlock_seq, &zerror, &ftemp))
    {
      ubuffree (zfullcmd);

      if (ftemp)
	{
	  ulog (LOG_NORMAL, "Will retry later (%s)", zbase);
	  if (zoutput != NULL)
	    (void) remove (zoutput);
	  if (zerror != NULL)
	    {
	      (void) remove (zerror);
	      ubuffree (zerror);
	    }
	  uqcleanup (zfile, iclean &~ (REMOVE_FILE | REMOVE_NEEDED));
	  *pfprocessed = FALSE;
	  return;
	}

      ulog (LOG_NORMAL, "Execution failed (%s)", zbase);

      if (zmail != NULL && ! fQno_ack)
	{
	  const char **pz;
	  int cgot;
	  FILE *eerr;
	  int istart;

	  cgot = 20;
	  pz = (const char **) xmalloc (cgot * sizeof (const char *));
	  i = 0;
	  pz[i++] = "Execution request failed:\n\t";
	  pz[i++] = zQcmd;
	  pz[i++] = "\n";

	  if (zerror == NULL)
	    eerr = NULL;
	  else
	    eerr = fopen (zerror, "r");
	  if (eerr == NULL)
	    {
	      pz[i++] = "There was no output on standard error\n";
	      istart = i;
	    }
	  else
	    {
	      char *zline;
	      size_t cline;

	      pz[i++] = "Standard error output was:\n";
	      istart = i;

	      zline = NULL;
	      cline = 0;
	      while (getline (&zline, &cline, eerr) > 0)
		{
		  if (i >= cgot)
		    {
		      cgot += 20;
		      pz = ((const char **)
			    xrealloc ((pointer) pz,
				      cgot * sizeof (const char *)));
		    }
		  pz[i++] = zbufcpy (zline);
		}

	      (void) fclose (eerr);
	      xfree ((pointer) zline);
	    }

	  (void) fsysdep_mail (zmail, "Execution failed", i, pz);

	  for (; istart < i; istart++)
	    ubuffree ((char *) pz[istart]);
	  xfree ((pointer) pz);
	}

      if (qoutsys != NULL)
	(void) remove (zoutput);

      iclean = isave_files (qsys, zmail, zfile, iclean);
    }
  else
    {
      ubuffree (zfullcmd);

      if (zmail != NULL && fQsuccess_ack)
	{
	  const char *az[20];

	  i = 0;
	  az[i++] = "\nExecution request succeeded:\n\t";
	  az[i++] = zQcmd;
	  az[i++] = "\n";

	  (void) fsysdep_mail (zmail, "Execution succeded", i, az);
	}

      /* Now we may have to uucp the output to some other machine.  */

      if (qoutsys != NULL)
	{
	  struct scmd s;

	  /* Fill in the command structure.  */

	  s.bcmd = 'S';
	  s.bgrade = BDEFAULT_UUX_GRADE;
	  s.pseq = NULL;
	  s.zfrom = abtemp;
	  s.zto = zQoutfile;
	  if (zQuser != NULL)
	    s.zuser = zQuser;
	  else
	    s.zuser = "uucp";
	  if (zmail != NULL && fQsuccess_ack)
	    s.zoptions = "Cn";
	  else
	    s.zoptions = "C";
	  s.ztemp = abtemp;
	  s.imode = 0666;
	  if (zmail != NULL && fQsuccess_ack)
	    s.znotify = zmail;
	  else
	    s.znotify = "";
	  s.cbytes = -1;
	  s.zcmd = NULL;
	  s.ipos = 0;

	  ubuffree (zsysdep_spool_commands (qoutsys, BDEFAULT_UUX_GRADE,
					    1, &s, (boolean *) NULL));
	}
    }

    /* XXX the umask doesn't seem to work, maybe a launchd problem,
      revisit if UUCP is ever really added to the system */
    chmod(zoutput, 0666);

  if (zerror != NULL)
    {
      (void) remove (zerror);
      ubuffree (zerror);
    }

  uqcleanup (zfile, iclean);
}

/* If we have enough disk space, save the data files so that the UUCP
   administrator can examine them.  Send a mail message listing the
   saved files.  */

static int
isave_files (qsys, zmail, zfile, iclean)
     const struct uuconf_system *qsys;
     const char *zmail;
     const char *zfile;
     int iclean;
{
  long cspace;
  char *zsavecmd;
  char **pzsave;
  int c;
  int ifile;
  char *zsaveinput;
  const char **pz;
  int i;

  /* Save the files if there is 1.5 times the amount of required free
     space.  */
  cspace = csysdep_bytes_free (zfile);
  if (cspace == -1)
    cspace = FREE_SPACE_DELTA;
  cspace -= qsys->uuconf_cfree_space + qsys->uuconf_cfree_space / 2;
  if (cspace < 0)
    return iclean;

  zsavecmd = zsysdep_save_failed_file (zfile);
  if (zsavecmd == NULL)
    return iclean;

  c = 1;

  pzsave = (char **) xmalloc (cQfiles * sizeof (char *));
  for (ifile = 0; ifile < cQfiles; ifile++)
    {
      if (azQfiles[ifile] != NULL)
	{
	  ++c;
	  pzsave[ifile] = zsysdep_save_failed_file (azQfiles[ifile]);
	  if (pzsave[ifile] == NULL)
	    {
	      ubuffree (zsavecmd);
	      for (i = 0; i < ifile; i++)
		if (azQfiles[i] != NULL)
		  ubuffree (pzsave[i]);
	      xfree ((pointer) pzsave);
	      return iclean;
	    }
	}
    }

  zsaveinput = NULL;
  if ((iclean & REMOVE_QINPUT) != 0
      && fsysdep_file_exists (zQinput))
    {
      zsaveinput = zsysdep_save_failed_file (zQinput);
      if (zsaveinput == NULL)
	{
	  ubuffree (zsavecmd);
	  for (i = 0; i < cQfiles; i++)
	    if (azQfiles[i] != NULL)
	      ubuffree  (pzsave[i]);
	  xfree ((pointer) pzsave);
	  return iclean;
	}
    }

  pz = (const char **) xmalloc ((20 + 2 * cQfiles) * sizeof (char *));
  i = 0;

  pz[i++] = "A UUCP execution request failed:\n\t";
  pz[i++] = zQcmd;
  if (zmail != NULL)
    {
      pz[i++] = "\nThe request was made by\n\t";
      pz[i++] = zmail;
    }
  else
    {
      pz[i++] = "\nThe request came from system\n\t";
      pz[i++] = qsys->uuconf_zname;
    }
  if (c == 1 && zsaveinput == NULL)
    pz[i++] = "\nThe following file has been saved:\n\t";
  else
    pz[i++] = "\nThe following files have been saved:\n\t";
  pz[i++] = zsavecmd;
  for (ifile = 0; ifile < cQfiles; ifile++)
    {
      if (azQfiles[ifile] != NULL)
	{
	  pz[i++] = "\n\t";
	  pz[i++] = pzsave[ifile];
	}
    }
  if (zsaveinput != NULL)
    {
      pz[i++] = "\n\t";
      pz[i++] = zsaveinput;
    }
  pz[i++] = "\n";

  (void) fsysdep_mail (OWNER,
		       "UUCP execution files saved after failure",
		       i, pz);

  xfree ((pointer) pz);

  ubuffree (zsavecmd);
  for (ifile = 0; ifile < cQfiles; ifile++)
    if (azQfiles[ifile] != NULL)
      ubuffree (pzsave[ifile]);
  xfree ((pointer) pzsave);
  ubuffree (zsaveinput);

  return iclean &~ (REMOVE_FILE | REMOVE_NEEDED);
}

/* Clean up the results of uqdo_xqt_file.  */

static void
uqcleanup (zfile, iflags)
     const char *zfile;
     int iflags;
{
  int i;

  DEBUG_MESSAGE2 (DEBUG_SPOOLDIR,
		  "uqcleanup: %s, %d", zfile, iflags);

  if ((iflags & REMOVE_FILE) != 0)
    (void) remove (zfile);

  if ((iflags & REMOVE_NEEDED) != 0)
    {
      for (i = 0; i < cQfiles; i++)
	{
	  if (azQfiles[i] != NULL)
	    (void) remove (azQfiles[i]);
	}
      if ((iflags & REMOVE_QINPUT) != 0)
	(void) remove (zQinput);
    }

  if (zQunlock_file != NULL)
    {
      (void) fsysdep_unlock_uuxqt_file (zQunlock_file);
      zQunlock_file = NULL;
    }

  if ((iflags & FREE_QINPUT) != 0)
    ubuffree (zQinput);

  if ((iflags & FREE_OUTPUT) != 0)
    ubuffree (zQoutput);
  if ((iflags & FREE_MAIL) != 0)
    ubuffree (zQmail);

  if (fQunlock_directory)
    {
      (void) fsysdep_unlock_uuxqt_dir (iQlock_seq);
      fQunlock_directory = FALSE;
    }

  for (i = 0; i < cQfiles; i++)
    {
      ubuffree (azQfiles[i]);
      ubuffree (azQfiles_to[i]);
    }

  ubuffree (zQoutfile);
  ubuffree (zQoutsys);
  ubuffree (zQrequestor);

  if (azQargs != NULL)
    {
      for (i = 0; azQargs[i] != NULL; i++)
	ubuffree (azQargs[i]);
      xfree ((pointer) azQargs);
      azQargs = NULL;
    }

  xfree ((pointer) zQcmd);
  zQcmd = NULL;

  xfree ((pointer) azQfiles);
  azQfiles = NULL;

  xfree ((pointer) azQfiles_to);
  azQfiles_to = NULL;
}

/* Check whether forwarding is permitted.  */

static boolean
fqforward (zfile, pzallowed, zlog, zmail)
     const char *zfile;
     char **pzallowed;
     const char *zlog;
     const char *zmail;
{
  const char *zexclam;

  if (zfile == NULL)
    return TRUE;

  zexclam = strchr (zfile, '!');
  if (zexclam != NULL)
    {
      size_t clen;
      char *zsys;
      boolean fret;

      clen = zexclam - zfile;
      zsys = zbufalc (clen + 1);
      memcpy (zsys, zfile, clen);
      zsys[clen] = '\0';

      fret = FALSE;
      if (pzallowed != NULL)
	{
	  char **pz;

	  for (pz = pzallowed; *pz != NULL; pz++)
	    {
	      if (strcmp (*pz, "ANY") == 0
		  || strcmp (*pz, zsys) == 0)
		{
		  fret = TRUE;
		  break;
		}
	    }
	}

      if (! fret)
	{
	  ulog (LOG_ERROR, "Not permitted to forward %s %s (%s)",
		zlog, zsys, zQcmd);

	  if (zmail != NULL && ! fQno_ack)
	    {
	      int i;
	      const char *az[20];

	      i = 0;
	      az[i++] = "Your execution request failed because you are";
	      az[i++] = " not permitted to forward files\n";
	      az[i++] = zlog;
	      az[i++] = " the system\n\t";
	      az[i++] = zsys;
	      az[i++] = "\n";
	      az[i++] = "Execution requested was:\n\t";
	      az[i++] = zQcmd;
	      az[i++] = "\n";

	      (void) fsysdep_mail (zmail, "Execution failed", i, az);
	    }
	}

      ubuffree (zsys);

      return fret;
    }

  return TRUE;
}