NetBSD/gnu/libexec/uucp/uuxqt/uuxqt.c

1805 lines
43 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* uuxqt.c
Run uux commands.
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 uuxqt_rcsid[] = "$Id: uuxqt.c,v 1.4 2001/09/12 07:51:03 itojun Exp $";
#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;
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 ("%s: Taylor UUCP %s, copyright (C) 1991, 92, 93, 94, 1995 Ian Lance Taylor\n",
zProgram, VERSION);
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 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");
}
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
# 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;
/* 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;
#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 },
{ NULL, 0, NULL, NULL }
};
/* Handle the C command: store off the arguments. */
/*ARGSUSED*/
static int
iqcmd (puuconf, argc, argv, pvar, pinfo)
pointer puuconf;
int argc;
char **argv;
pointer pvar;
pointer pinfo;
{
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;
int argc;
char **argv;
pointer pvar;
pointer pinfo;
{
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;
int argc;
char **argv;
pointer pvar;
pointer pinfo;
{
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;
int argc;
char **argv;
pointer pvar;
pointer pinfo;
{
/* 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;
int argc;
char **argv;
pointer pvar;
pointer pinfo;
{
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;
int argc;
char **argv;
pointer pvar;
pointer pinfo;
{
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)
/* 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;
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;
#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;
}
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;
char **azargs;
const char *zuser;
zfrom = NULL;
zto = NULL;
fmany = FALSE;
/* Skip all the options, and get the from and to specs. We
don't permit multiple arguments. */
for (i = 1; azQargs[i] != NULL; i++)
{
if (azQargs[i][0] == '-' && azQargs[i][1] == '-')
{
char *zopts = azQargs[i] + 2;
/* The -g, -n, and -s options take an argument. */
if (!strncmp(zopts, "grade", 5) && zopts[5] != '=')
{
if (azQargs[i+1] != NULL)
++i;
}
if (!(strncmp(zopts, "notify", 6)
&& strncmp(zopts, "status", 6)) && zopts[6] != '=')
{
if (azQargs[i+1] != NULL)
++i;
}
/* The -I, -u and -x options are not permitted. */
if (!strncmp(zopts, "config", 6))
{
if (zopts[6] != '=' && azQargs[i+1] != NULL)
++i;
azQargs[i] = zbufcpy ("--nouucico");
}
if (!strncmp(zopts, "user", 4))
{
if (zopts[4] != '=' && azQargs[i+1] != NULL)
++i;
azQargs[i] = zbufcpy ("--nouucico");
}
if (!strncmp(zopts, "debug", 5))
{
if (zopts[5] != '=' && azQargs[i+1] != NULL)
++i;
azQargs[i] = zbufcpy ("--nouucico");
}
}
else
if (azQargs[i][0] == '-')
{
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')
{
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;
}
}
/* 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) * sizeof (char *));
azargs[0] = azQargs[0];
zuser = zQuser;
if (zuser == NULL)
zuser = "uucp";
azargs[1] = zbufalc (strlen (zQsystem) + strlen (zuser)
+ sizeof "-u!");
sprintf (azargs[1], "-u%s!%s", zQsystem, zuser);
memcpy (azargs + 2, azQargs + 1, i * sizeof (char *));
xfree ((pointer) azQargs);
azQargs = azargs;
/* Find the uucp binary. */
zabsolute = zsysdep_find_command ("uucp", qsys->uuconf_pzcmds,
qsys->uuconf_pzpath, &ferr);
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)
{
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;
}
}
ubuffree (azQargs[0]);
azQargs[0] = zabsolute;
for (i = 1; azQargs[i] != NULL; i++)
{
char *zlocal;
zlocal = zsysdep_xqt_local_file (qsys, azQargs[i]);
if (zlocal != NULL)
{
ubuffree (azQargs[i]);
azQargs[i] = zlocal;
}
}
#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_NORMAL, "Executing %s (%s)", zbase, zQcmd);
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;
}
}
/* Move the required files to the execution directory if necessary. */
zinput = zQinput;
if (! fsysdep_move_uuxqt_files (cQfiles, (const char **) azQfiles,
(const char **) azQfiles_to,
TRUE, 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]);
}
if (! fsysdep_execute (qsys,
zQuser == NULL ? (const char *) "uucp" : zQuser,
(const char **) azQargs, zfullcmd, zQinput,
zoutput, fshell, iQlock_seq, &zerror, &ftemp))
{
ubuffree (zfullcmd);
(void) fsysdep_move_uuxqt_files (cQfiles, (const char **) azQfiles,
(const char **) azQfiles_to,
FALSE, iQlock_seq,
(char **) NULL);
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));
}
}
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 (zQunlock_file != NULL)
{
(void) fsysdep_unlock_uuxqt_file (zQunlock_file);
zQunlock_file = NULL;
}
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 ((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;
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;
}