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(×tamp);
#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.