ftp.nice.ch/pub/next/unix/mail/zend.1.0.s.tar.gz#/zend-1.0/zend.c

This is zend.c in view mode; [Download] [Up]

static char rcsid[] = "$Id: zend.c,v 1.14 1993/02/21 13:05:20 gerben Exp $";

/*
    Zend --- a mail transport program for large files and directories
    Copyright (C) 1992  Gerben C. Th. Wierda

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    Gerben Wierda
    Goudriaanstraat 1
    1222 SG  Hilversum
    The Netherlands

    gerben@rna.indiv.nluug.nl
*/

/*
 *
 * zend.c -- main module and data for zend
 *
 */

#include "zend.h"
#include <signal.h>

#define ZEND_ID	"version 1.0"

Variable global[] = {
    /*
     * Mail header strings:
     */
    { STRING,	MAILFROMPROMPT},
    { STRING,	MAILTOPROMPT},
    { STRING,	MAILSUBJECTPROMPT},
    { STRING,	MAILDATEPROMPT},
    { STRING,	MAILREPLYTOPROMPT},
    /*
     * Zend prompts
     */
    { STRING,	ZENDIDPROMPT},
    { STRING,	SYSTEMNAMEPROMPT},
    { BOOLEAN,	UUCPMODEPROMPT},
    { STRING,	SENDINGUSERPROMPT},
    { STRING,	RECEIVINGUSERPROMPT},
    { USHORT,	LINESPROMPT},
    { ULONG,	ZENDSIZEPROMPT},
    { ULONG,	RECEIVESIZEPROMPT},
    { ULONG,	ZENDTRUESIZEPROMPT},
    { ULONG,	RECEIVETRUESIZEPROMPT},
    { STRING,	ACTIONPROMPT},
    { ULONG,	SENDINGJOBIDPROMPT},
    { ULONG,	RECEIVINGJOBIDPROMPT},
    { STRING,	FILEPROMPT},
    { STRING,	DIRPROMPT},
    { STRING,	TYPEPROMPT},
    { BOOLEAN,	COMPRESSEDPROMPT},
    { USHORT,	CHUNKNRPROMPT},
    { USHORT,	NROFCHUNKSPROMPT},
    { STRING,	REASONPROMPT},
    { ULONG,	DUMMYULONGPROMPT},
    { STRING,	ROLEPROMPT},
    { STRING,	SPOOLDIRPROMPT},

    /* Stuff for timeouts. */
    { USHORT,	PACKETTIMEOUTPROMPT },
    { USHORT,	JOBTIMEOUTPROMPT },
    { ULONG,	TIMESTAMPPROMPT },
    { STRING,	SENDINGSYSTEMPROMPT },

    /* Access restrictions. */
    { STRING,	ALLOWUSERSPROMPT },
    { STRING,	EXCLUDEUSERSPROMPT },
    { STRING,	ALLOWSYSTEMSPROMPT },
    { STRING,	EXCLUDESYSTEMSPROMPT },

    { NONE,	NIL}	    /* sentinel */
};

Variable remoteglobal[sizeof( global) / sizeof( global[0])];

/* extern */ int		nrOfGlobalVariables =
					sizeof( global) / sizeof( global[0]) -1;
/* extern */ StringPointer	mailUser = NIL;
/* extern */ Boolean		verboseMode = FALSE;
/* extern */ uid_t		sendinguser_uid = 0;
/* extern */ unsigned int	process_id;
#if DEBUG
/* extern */ Boolean		dump = FALSE;
#endif
/* extern */ Boolean		checkTimeoutMode = FALSE;
/* extern */ Boolean		receiveMode = FALSE;
/* extern */ Boolean		cleanupMode = FALSE;
/* extern */ time_t		timestamp;
/* extern */ Boolean		logErrors = FALSE;

#if _ANSI_DEFUN_
extern void init( void)
#else
extern void init()
#endif
{
    memcpy( remoteglobal, global, sizeof( global));
}

static void
#if _ANSI_DEFUN_
usage( void)
#else
usage()
#endif
{
    fprintf( stderr, "\
Usage: zend user[@remotehost] file|dir [ user[@remotehost] file|dir ... ]\n");
}


/* signal handling */

#define SIGNALS_TO_HANDLE   SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGABRT

/*
 * Set a bunch of signals to `handler'.  The bunch of signals is given by
 * the variable argument list, which should be terminated with zero.
 */
static void
#if _ANSI_DEFUN_
set_signals( void (*handler)(int), ...)
#else
set_signals( handler, va_alist)
void (*handler)();
va_dcl
#endif
{
    va_list	argpt;
    int		sig;

    va_begin( argpt, sig);

    while (sig = va_arg( argpt, int))
    {
	/* protect background processes */
	if (signal( sig, SIG_IGN) != SIG_IGN && handler != SIG_IGN)
	{
	    signal( sig, handler);
	}
    }
}

static void
#if _ANSI_DEFUN_
interrupt( int sig)
#else
interrupt( sig)
int sig;
#endif
{
    signal( sig, SIG_IGN);
    zerr( "Aborted.\n");
    file_cleanup();
    signal( sig, SIG_DFL);
    exit( -3);
}

#if _ANSI_DEFUN_
void main( int argc, char *argv[])
#else
void main( argc, argv)
int argc;
char *argv[];
#endif
{
    int c;
    int status = 0;
    char *progName;

    time(&timestamp);

#if _ANSI_LIBRARY_ && _IOLBF
    setvbuf( stdout, NULL, _IOLBF, BUFSIZ);
    setvbuf( stderr, NULL, _IOLBF, BUFSIZ);
#endif
    process_id = getpid();
    sendinguser_uid = getuid();

#if 0	/* Obsolete. (since 1.12) */
    if (setuid( 0) != 0)
    {
	/* {NOTE: this assumes that privileged user (root) has uid 0,
	    which is not necessarily true on non-UNIX Posix systems} */
	zerr( "ZEND should be installed setuid root.\n");
	exit(-2);
    }
    /* At this point we're running with root as both real and effective uid. */
#endif
    /*
     * Security: check that effective user owns the spool directory, and
     * can access it.
     */
    {
	struct stat statBuf;

	if (stat( HOMEDIR, &statBuf) != 0)
	{
	    zerr( "cannot stat spool directory %s (%s).\n", HOMEDIR, 
		  strerror( errno));
	    exit(-2);
	}
	if (!S_ISDIR( statBuf.st_mode))
	{
	    zerr( "%s is not a directory.\n", HOMEDIR);
	    exit(-2);
	}
	if (statBuf.st_uid != geteuid())
	{
	    struct passwd   *pwStruct = getpwuid( statBuf.st_uid);

	    if (pwStruct)
	    {
		zerr( "ZEND should be setuid %s!\n", pwStruct->pw_name);
	    }
	    else
	    {
		zerr( "ZEND should be setuid %d!\n", statBuf.st_uid);
	    }
	    exit(-2);
	}
	if ((statBuf.st_mode & S_IRWXU) != S_IRWXU)
	/* {Don't use access() since that checks rights of REAL user.} */
	{
	    zerr("cannot access spool directory %s\n", HOMEDIR);
	    exit( -2);
	}
    }
    /*
     * At this point we're still running with the original real and
     * effective uids, that is, `sendinguser_uid' as real uid and zend's
     * setuid (not necessarily root) as effective uid.  This is changed
     * only when we do work on behalf of the user, in subprocesses
     * created by system_as() or popen_as().  (Previous versions of
     * zend used to set both real and effective uid to root, which
     * required to obtain `sendinguser_uid' beforehand; now we could have
     * used getuid() directly instead.)
     */

    /* Security: normalize umask. */
    umask( S_IWGRP|S_IWOTH);

    /* Security: blank out environment, to avoid lies to subprograms. */
    {
#ifndef environ
	extern char **environ;
#endif
	if (environ)
	{
	    environ[0] = NULL;
	}
	else
	{
	    static char *dummy_env[1] = { NULL };

	    environ = dummy_env;
	}
    }

    init();

    /*
     * Let program's invokation name determine the action.
     */
    assert(argc > 0 && argv != NIL);
    progName = basename( argv[0]);
    if (strcmp( "zend-R" , progName) == 0)
    {
	receiveMode = TRUE;
    }
    else if (strcmp( "zend-T", progName) == 0)
    {
	checkTimeoutMode = TRUE;
    }

    while ((c = getopt( argc, argv, "vRTC:")) != -1)
    {
	switch (c)
	{
	case 'v':
	    verboseMode = TRUE;
	    break;
	/*
	 * Alternatively, determine the action from command-line options
	 * if you object to the above method.
	 */
	case 'R':
	    receiveMode = TRUE;
	    break;
	case 'T':
	    checkTimeoutMode = TRUE;
	    break;
	case 'C':
	    if (strcmp( optarg, "LEANUP") == 0)
	    {
		cleanupMode = TRUE;
		break;
	    }
	    /* FALL THROUGH */
	case '?':
	    usage();
	    exit( -1);
	}
    }

    if (read_config() != 0)
    {
	exit( -2);
    }
    /*
     * Check proper installation: non-root setuid zend cannot deliver in
     * user's home directory.
     */
    if (geteuid() != ROOT && get_global_string( global, SPOOLDIRPROMPT) == NIL)
    {
	zerr("config error: %s should be non-empty for non-root setuid zend.\n",
	     SPOOLDIRPROMPT);
	exit( -2);
    }

    set_global( global, ZENDIDPROMPT, ZEND_ID);

    if (receiveMode)
    {
	logErrors = TRUE;
#if 0	/* Obsolete. */
	freopen( "/dev/console", "w", stderr);
	setbuf( stderr, NIL);
#endif
#if DEBUG
	zerr( "%s started as receiver.\n", ZEND_ID);
#endif
	receive();
    }
    else if (checkTimeoutMode)
    {
	logErrors = TRUE;
#if 0	/* Obsolete. */
	freopen( "/dev/console", "w", stderr);
	setbuf( stderr, NIL);
#endif
#if DEBUG
	zerr( "%s started to check timeout.\n", ZEND_ID);
#endif
	check_timeout();
    }
    else if (cleanupMode)
    {
	/* This should be called only from /etc/rc. (which implies ruid root) */
	if (sendinguser_uid != 0)
	{
	    usage();
	    exit(-1);
	}
	cleanup_stale_locks();
    }
    else if (optind == argc || ((argc - optind) % 2) != 0)
    {
	usage();
	exit(-1);
    }
    else
    {
#if DEBUG
	zerr( "%s started as sender.\n", ZEND_ID);
#endif
	set_signals( interrupt, SIGNALS_TO_HANDLE, 0);

	while (optind<argc)
	{
	    char *remoteUser, *localFile;

	    remoteUser = argv[optind++];
	    localFile = argv[optind++];
	    /* (already checked for correct number of parameters above.) */

	    if (!starttransfer( remoteUser, localFile))
	    {
		status++;
	    }
	}
    }
    exit( status);
}

/* Remove partially created job. */

static void
#if _ANSI_DEFUN_
abort_job( Ulong jobId)
#else
abort_job( jobId)
#endif
{
    char buf   [PATHBUFSIZ];

    sprintf( buf, "%s/F.%lu", HOMEDIR, jobId);
    unlink( buf);
    sprintf( buf, "%s/D.%lu", HOMEDIR, jobId);
    rmdir( buf);
}

/* Spool output of `command' (run as sending user) to `spoolfile'. */

static Boolean
#if _ANSI_DEFUN_
spool( const char *command, const char *spoolfile)
#else
spool( command, spoolfile)
const char *command;
const char *spoolfile;
#endif
{
    FILE *cmdOutput;
    int	spoolFd, st;
    char buf[BUFSIZ];
    size_t n;
    Boolean status = TRUE;

    unlink( spoolfile);	    /* so creat read-only will not fail if it exists. */

    if ((spoolFd = creat( spoolfile, S_IRUSR)) < 0)
    {
#if DEBUG
	zerr( "spool: cannot create %s. (%s)\n", spoolfile, strerror( errno));
#endif
	return FALSE;
    }
    if ((cmdOutput = popen_as_user( command, "r")) == NULL)
    {
	close( spoolFd);
	return FALSE;
    }
    while ((n = fread( buf, sizeof( char), sizeof( buf), cmdOutput)) != 0)
    {
	ssize_t written;

	if ((written = write( spoolFd, buf, n)) != n)
	{
#if DEBUG
	    zerr( "spool: write failed. (%s)\n",
		  (written < 0) ? strerror( errno) : "Full disk?");
#endif
	    status = FALSE;
	    break;
	}
    }
    if ((st = pclose( cmdOutput)) != 0)
    {
#if DEBUG
	zerr( "spool: pclose( %s) failed. (%s)\n", command, strerror( errno));
#endif
	status = FALSE;
    }
    if (close( spoolFd) != 0)
    {
#if DEBUG
	zerr( "spool: close( %s) failed. (%s)\n", spoolfile, strerror( errno));
#endif
	status = FALSE;
    }
    return status;
}

#if _ANSI_DEFUN_
extern Boolean starttransfer( const char *remoteUser, const char *localFile)
#else
extern Boolean starttransfer( remoteUser, localFile)
char *remoteUser;
char *localFile;
#endif
    /* return TRUE when successfully spooled. */
{
    char buf[PATHBUFSIZ], spoolfile[PATHBUFSIZ];
    char *sep;
    char *qDir, *qFile;
    Ulong jobId, trueSize;
    Boolean isFile;
    struct passwd *pwStruct;
    struct stat statBuf;
    Ulong maxZendTrueSize = get_global_ulong( global, ZENDTRUESIZEPROMPT);
    Ulong maxZendSize = get_global_ulong( global, ZENDSIZEPROMPT);
    FILE *tmpStream;
	

    if (verboseMode)
    {
	fprintf( stderr, "%s -> %s\n", localFile, remoteUser);
    }

    set_global( global, ROLEPROMPT, ROLE_SENDER);

    /*
     * Global var "Action:" is "Are You There?"
     */

    set_global( global, ACTIONPROMPT, ACTION_AYT);

    /*
     * find out about local user.  Refuse to proceed if we cannot find out.
     */
    if ((pwStruct = getpwuid( sendinguser_uid)) == NULL)
    {
	zerr( "who ARE you???\n");
	exit( -2);
    }
    set_global( global, SENDINGUSERPROMPT, pwStruct->pw_name);

    /*
     * Get some statistics from the file, like size and sum.
     */
    if (stat( localFile, &statBuf) != 0)
    {
	zerr( "Unable to stat %s\n", localFile);
	return FALSE;
    }
    if (S_ISREG( statBuf.st_mode))
    {
	trueSize = (Ulong)(statBuf.st_size);
	isFile = TRUE;
	set_global( global, TYPEPROMPT, TYPE_FILE);
    }
    else if (S_ISDIR( statBuf.st_mode))
    {
	sprintf( buf, "%s -s %s", DUPROG, qFile = quote_for_sh( localFile));
	free( qFile);
	if ((tmpStream = popen_as_user( buf, "r")) == NIL)
	{
	    zerr( "Unable to du %s\n", localFile);
	    return FALSE;
	}
	if (fscanf( tmpStream, "%lu", &trueSize) != 1)
	{
	    pclose( tmpStream);
	    zerr( "Unable to determine size from du %s\n", localFile);
	    return FALSE;
	}
	pclose( tmpStream);
	trueSize *= DUBLOCKSIZE;    /* Note that this is just an estimate. */
	isFile = FALSE;
	set_global( global, TYPEPROMPT, TYPE_DIR);
    }
    else
    {
	zerr( "Unable to zend %s. File must be regular file or directory.\n", localFile);
	return FALSE;
    }
    if (maxZendTrueSize < trueSize)
    {
	zerr( "Transfer true size of %s %s too big (%lu, max %lu).\n",
	      (isFile ? "file" : "directory"), localFile,
	      trueSize, maxZendTrueSize);
	return FALSE;
    }
    set_global( global, ZENDTRUESIZEPROMPT, trueSize);

    /*
     * Get filename part from localFile and put it into global[]
     * (mind trailing '/'s)
     */
    strcpy( buf, localFile);
    while ((sep = strrchr( buf, '/')) != NIL)
    {
	*sep = '\0';		/* separate directory- and filename. */
	if (sep[1] != '\0')
	{
	    if (sep == buf)	/* refuse to zend root. */
	    {
		zerr( "Cannot zend \"/\"!\n");
		return FALSE;
	    }
	    break;
	}
    }
    if (sep != NIL)
    {
	set_global( global, FILEPROMPT, sep + 1);
	set_global( global, DIRPROMPT, buf);
    }
    else
    {
	set_global( global, FILEPROMPT, localFile);
	set_global( global, DIRPROMPT, ".");
    }

    /*
     * Kludge to catch "." and ".." as filenames: get absolute pathname.
     */
    sep = get_global_string( global, FILEPROMPT);
    if (strcmp( sep, ".") == 0 || strcmp( sep, "..") == 0)
    {
	sprintf( buf, "cd %s && %s", qFile = quote_for_sh( localFile), PWDPROG);
	free( qFile);
	if ((tmpStream = popen_as_user( buf, "r")) != NIL)
	{
	    sep = fgets( buf, sizeof( buf), tmpStream);
	    if (pclose( tmpStream) != 0)
	    {
		sep = NULL;
	    }
	}
	if (tmpStream == NIL || sep == NIL || *sep != '/')
	{
	    zerr( "Unable to determine absolute pathname %s\n", localFile);
	    return FALSE;
	}
	if (*(sep += strlen(sep) - 1) == '\n')
	{
	    *sep = '\0';
	}
	if (sep == buf + 1)	/* refuse to zend root. */
	{
	    zerr( "Cannot zend \"/\"!\n");
	    return FALSE;
	}
	*(sep = strrchr( buf, '/')) = '\0';
#if DEBUG
	zerr( "normalize filename %s -> %s/%s\n", localFile, buf, sep + 1);
#endif
	set_global( global, FILEPROMPT, sep + 1);
	set_global( global, DIRPROMPT, buf);
    }
    
    /*
     * separate remote user from remote system
     */
    if ((sep = strchr( strcpy( buf, remoteUser), '@')) != NIL)
    {
	*sep = '\0';		/* separate user- and system name */
	set_global( global, RECEIVINGUSERPROMPT, buf);
	set_global( remoteglobal, SYSTEMNAMEPROMPT, sep + 1);
    }
    else
    {
	set_global( global, RECEIVINGUSERPROMPT, remoteUser);
	set_global( remoteglobal, SYSTEMNAMEPROMPT,
		    get_global_string( global, SYSTEMNAMEPROMPT));
    }

    if ((jobId = new_job()) == RESERVEDID)
    {
	return FALSE;
    }
    set_global( global, SENDINGJOBIDPROMPT, jobId);

    sprintf( spoolfile, "%s/F.%lu", HOMEDIR, jobId);
    add_file_to_cleanup( spoolfile, FALSE);

    /*
     * Try compressed first.  If this fails, or the result would be bigger
     * than the original, retry uncompressed.
     */
    qDir = quote_for_sh( get_global_string( global, DIRPROMPT));
    qFile = quote_for_sh( get_global_string( global, FILEPROMPT));

    if (isFile)
    {
	sprintf( buf, "%s <%s/%s",
		 COMPRESSPROG, qDir, qFile);
    }
    else
    {
	sprintf( buf, "cd %s && %s cf - %s | %s",
		 qDir, TARPROG, qFile, COMPRESSPROG);
    }

    if (!spool( buf, spoolfile) ||
	stat( spoolfile, &statBuf) != 0 || trueSize < (Ulong)statBuf.st_size)
    {
	if (isFile)
	{
	    sprintf( buf, "%s %s/%s",
		     CATPROG, qDir, qFile);
	}
	else
	{
	    sprintf( buf, "cd %s && %s cf - %s",
		     qDir, TARPROG, qFile);
	}

	if (!spool( buf, spoolfile))
	{
	    free( qDir), free( qFile);
	    zerr( "Unable to copy/tar/compress file/dir %s\n", localFile);
	    abort_job( jobId);
	    return FALSE;
	}
	set_global( global, COMPRESSEDPROMPT, FALSE);
    }
    else
    {
	set_global( global, COMPRESSEDPROMPT, TRUE);
    }

    free( qDir), free( qFile);

    if (stat( spoolfile, &statBuf) != 0)
    {
	zerr( "Unable to stat %s\n", spoolfile);
	abort_job( jobId);
	return FALSE;
    }

    if (maxZendSize < (Ulong)(statBuf.st_size))
    {
	zerr( "Transfer mail size too big.\n");
	abort_job( jobId);
	return FALSE;
    }

    set_global( global, ZENDSIZEPROMPT, (Ulong)(statBuf.st_size));

    set_global( global, TIMESTAMPPROMPT, (Ulong) timestamp);

    /*
     * Write all globals to a job specific config file:
     */
    if (!write_globs_to_jobfile( global, jobId))
    {
	abort_job( jobId);
	return FALSE;
    }

    /*
     * Send the ayt message:
     */
    mail_message();

    zlog( "START");

    /* Restore global parameters that we may need the next time (kludge). */
    set_global( global, ZENDTRUESIZEPROMPT, maxZendTrueSize);
    set_global( global, ZENDSIZEPROMPT, maxZendSize);

    return TRUE;
}

#if _ANSI_DEFUN_
extern void receive( void)
#else
extern void receive()
#endif
{
    StringPointer   action;

    while (TRUE)
    {
	int result;

	/*
	 * Skip unknown entries and walk until EOF or first delimiter.
	 */
	result = parsestreamtoglob( remoteglobal, stdin);

	if (result == PARSEEOF)
	{
	    /*
	     * No delimiter and eof.
	     */
	    zerr( "No delimiter found in mail from %s. Job discarded.\n",
		  get_global_string( remoteglobal, MAILFROMPROMPT));
	    return;
	}
	if (result == PARSEDELIMITER)
	{
	    break;
	}
    }

    action = get_global_string( remoteglobal, ACTIONPROMPT);
    /*
     * For every kind of message there is a handler:
     */
    if      (strcmp( ACTION_AYT, action) == 0)
    {
	handle_ayt();
    }
    else if (strcmp( ACTION_UAR, action) == 0)
    {
	handle_uar();
    }
    else if (strcmp( ACTION_CNK, action) == 0)
    {
	handle_cnk();
    }
    else if (strcmp( ACTION_ACK, action) == 0)
    {
	handle_ack();
    }
    else if (strcmp( ACTION_RSN, action) == 0)
    {
	handle_rsn();
    }
    else if (strcmp( ACTION_ERR, action) == 0)
    {
	handle_err();
    }
}

extern void
#if _ANSI_DEFUN_
check_timeout( void)
#else
check_timeout()
#endif
{
    char buf[PATHBUFSIZ];
    FILE *dirStream;
    Ulong jobId, jobTimestamp;
    Ushort timeout;
    struct stat jobStat;

    /*
     * Loop over all jobfiles in Zend's home directory.
     */
    sprintf( buf, "cd %s && %s C.*", HOMEDIR, ECHOPROG);
    if ((dirStream = popen_as_zend( buf, "r")) == NIL)
    {
	zerr( "Unable to execute \"%s\"\n", buf);
	return;
    }
    /* {{Alternatively, use opendir/readdir/closedir instead of pipe.}} */
#if DEBUG
    zerr( "%s\n", buf);
#endif
    while (fscanf(dirStream, " C.%lu", &jobId) > 0)
    {
	sprintf( buf, "%s/C.%lu", HOMEDIR, jobId);
	if (stat( buf, &jobStat) != 0)
	{
	    zerr( "Unable to stat jobfile (%lu) (%s)\n",
		  jobId, strerror(errno));
	    continue;
	}
	/*
	 * Start with a clean slate, and read variables.
	 */
	set_global( global, TIMESTAMPPROMPT, (Ulong) 0);
	set_global( global, JOBTIMEOUTPROMPT, 0);
	set_global( global, PACKETTIMEOUTPROMPT, 0);

	if (read_jobfile_to_globs( global, jobId) != 0)
	{
	    zerr( "Unable to read jobfile (%lu)\n", jobId);
	    continue;
	}

	/*
	 * First, check for job timeout.
	 */
	if ((timeout = get_global_ushort(global, JOBTIMEOUTPROMPT)) == 0)
	{
	    timeout = JOB_TIMEOUT;
	}
	if ((jobTimestamp = get_global_ulong(global, TIMESTAMPPROMPT)) == 0)
	{
	    /* Time stamp shouldn't be missing, but in case it does... */
	    jobTimestamp = (Ulong) jobStat.st_mtime;
	}
	if (jobTimestamp + timeout * 24 * 60 * 60L < timestamp)
	{
	    mail_err( "Job %lu expired. Will kill job.", jobId);
	    /* {{Should we notify the receiving user?}} */

	    /*
	     * Job Timeout expired.  Kill the job.
	     */
	    zlog( "EXPIRED (killing job)");
	    kill_job(jobId);
	    continue;
	}

	/*
	 * Packet timeout.  Only the receiver checks for it.
	 */

	/*
	 * This also occurs when the UAR is not answered, e.g. when zend on the
	 * other side is not installed as mail alias. There is no packet to ask
	 * for yet.
	 */
	if (strcmp(get_global_string(global, ROLEPROMPT), ROLE_RECEIVER) != 0)
	{
	    continue;
	}
	if ((timeout = get_global_ushort(global, PACKETTIMEOUTPROMPT)) &&
	    jobStat.st_mtime + timeout * 24 * 60 * 60L < timestamp)
	{
	    /*
	     * Packet timeout expired.  Fire resend packet.
	     */
	    zlog( "TIMEOUT (request resend)");
	    set_global( global, ACTIONPROMPT, ACTION_RSN);
	    set_global( remoteglobal, SYSTEMNAMEPROMPT,
			get_global_string( global, SENDINGSYSTEMPROMPT));
#if DEBUG
          zerr( "Writing jobfile...\n");
#endif
          write_globs_to_jobfile( global, jobId);
	    mail_resend();
	}

    }
    pclose( dirStream);
}

/*======================================================================
 * $Log: zend.c,v $
 * Revision 1.14  1993/02/21  13:05:20  gerben
 * We zend an error to the receiver at job timeout
 *
 * Revision 1.13  1993/02/02  21:00:00  gerben
 * *** empty log message ***
 *
 * Revision 1.14  1993/02/02  17:55:03  tom
 * touch jobfile after rezend.
 *
 * Revision 1.13  1993/02/02  11:45:45  tom
 * add some comments.
 *
 * Revision 1.12  1993/01/28  18:54:26  tom
 * allow zend to be installed setuid non-root.  Plug some more security holes
 * (environment; umask; subprogram execution as real user; be more careful with
 *  file access modes; create spoolfile through a pipe so user process does not
 *  have to access the spool directory.)
 *
 * Revision 1.11  1993/01/10  23:47:53  tom
 * shell-quote user-supplied names; Posixate access modes; handle "." and "..".
 *
 * Revision 1.10  1992/12/10  05:27:33  tom
 * add signal handling; -CLEANUP flag (for use in /etc/rc to cleanup stale
 * locks); plug security holes by switching to original uid when executing
 * programs wherever possible, and using absolute program pathnames elsewhere;
 * allow trailing '/' after filename; send uncompressed if compression would
 * increase the file size (yes this can happen!).
 *
 *======================================================================*/

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.