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

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

static char rcsid[] = "$Id: zendhand.c,v 1.13 1993/02/21 13:07:09 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
*/

/*
 *
 * zendhand.c -- Message handlers.
 *
 */

#include "zend.h"

#define ZEND_ADDRESS	NULL	/* Represents address of other-side zend. */


/* Support routines: */

/* Open a mail stream and write some standard stuff to it. */

static FILE *
#if _ANSI_DEFUN_
mail_open( const char *mailAddress)
#else
mail_open( mailAddress)
char *mailAddress;
#endif
{
    char mailCommand[PATHBUFSIZ];
    FILE *mailStream;
    char *q;

    if (mailAddress == ZEND_ADDRESS)	/* Special case for zend addresses. */
    {
	if (strcmp( get_global_string( global, SYSTEMNAMEPROMPT),
		    get_global_string( remoteglobal, SYSTEMNAMEPROMPT)) == 0)
	{
	    mailAddress = "zend";
	}
	else
	{
	    sprintf( mailCommand, get_global_boolean( global, UUCPMODEPROMPT) ?
		     "%s!zend" : "zend@%s",
		     get_global_string( remoteglobal, SYSTEMNAMEPROMPT));
	    mailAddress = mailCommand;
	}
    }
    set_global( global, MAILTOPROMPT, mailAddress);

    sprintf( mailCommand, "%s %s", MAILPROG, q = quote_for_sh( mailAddress));
    free( q);
#if DEBUG
    zerr( "%s\n", mailCommand);
#endif
    if ((mailStream = popen_as_user( mailCommand, "w")) == NIL)
    {
	zerr( "Unable to start mail\n");
    }
    else
    {
#if DEBUG
	zerr( "mail succesfully started.\n");
#endif
#if BINMAIL
	writeglobtostream( global, MAILTOPROMPT, mailStream);
#endif
    }
    return mailStream;
}

#define mail_close  pclose

/* Return mail address of ``local'' user. */

static const char *
#if _ANSI_DEFUN_
local_user_address( void)
#else
local_user_address()
#endif
{
    return get_global_string( global, 
			      strcmp( get_global_string( global, ROLEPROMPT),
				      ROLE_SENDER) == 0 ?
			      SENDINGUSERPROMPT : RECEIVINGUSERPROMPT);
}

/* Return mail address of ``remote'' user. */

static const char *
#if _ANSI_DEFUN_
remote_user_address( void)
#else
remote_user_address()
#endif
{
    static char	*address;
    const char *remoteUser;
    const char *remoteSystem;

    if (address)
    {
	free(address);
	address = NIL;
    }

    remoteUser = get_global_string( global,
				    strcmp( get_global_string( global,
							       ROLEPROMPT),
					    ROLE_SENDER) == 0 ?
				    RECEIVINGUSERPROMPT : SENDINGUSERPROMPT);
    remoteSystem = get_global_string( remoteglobal, SYSTEMNAMEPROMPT);

    if (strcmp( get_global_string( global, SYSTEMNAMEPROMPT), remoteSystem) == 0)
    {
	return remoteUser;
    }

    address = safecalloc( strlen(remoteUser) + strlen(remoteSystem) + 1 + 1,
			  sizeof( char));
    sprintf( address, "%s@%s", remoteUser, remoteSystem);

    return address;
}

/* Notify a user of an error (and log it). */

static void
#if _ANSI_DEFUN_
mail_report_error( const char *reason)
#else
mail_report_error( reason)
char *reason;
#endif
{
    FILE *mailStream;

    zerr( "%s\n", reason);

    if ((mailStream = mail_open( local_user_address())) == NIL)
    {
	return;
    }
    fprintf( mailStream, "Subject: ZEND FAILED %s %s %s (%s)\n\n",
	     get_global_string( global, FILEPROMPT),
	     strcmp( get_global_string( global, ROLEPROMPT), ROLE_SENDER) == 0 ?
	     "->" : "<-", remote_user_address(), reason);

    /* The subject line says it all... */

    mail_close( mailStream);
}

/* Mail an error report to the other side.
   Also report failure back to the sending user (if we are the sender). */

extern void
#if _ANSI_DEFUN_
mail_err( const char *format, ...)
#else
mail_err( format, va_alist)
char *format;
va_dcl
#endif
{
    char buf[PATHBUFSIZ];
    va_list argpt;

    va_begin( argpt, format);
    vsprintf( buf, format, argpt);
    va_end( argpt);
    set_global( global, REASONPROMPT, buf);
    set_global( global, ACTIONPROMPT, ACTION_ERR);
    mail_message();

    if (strcmp( get_global_string( global, ROLEPROMPT), ROLE_SENDER) == 0)
    {
	mail_report_error( buf);
    }
}

#if _ANSI_DEFUN_
extern void handle_ayt( void)
#else
extern void handle_ayt()
#endif
{
    Ulong jobId;
    StringPointer
	receivingUser = get_global_string( remoteglobal, RECEIVINGUSERPROMPT),
	sendingSystem = get_global_string( remoteglobal, SYSTEMNAMEPROMPT),
	list;

    /*
     * See if the file is acceptable. If so, create a jobID, else send an ERR
     * message saying either file too big or user unknown.
     */
    set_global( global, ROLEPROMPT, ROLE_RECEIVER);
    set_global( global, SENDINGJOBIDPROMPT,
		get_global_ulong( remoteglobal, SENDINGJOBIDPROMPT));
#if DEBUG
    zerr( "Handling %s\n", ACTION_AYT);
#endif
    if (strcmp( get_global_string( global, ZENDIDPROMPT),
		get_global_string( remoteglobal, ZENDIDPROMPT)) != 0)
    {
	mail_err( "I do not accept protocol (%s).",
		  get_global_string( global, ZENDIDPROMPT));
	return;
    }
    else if (get_global_ulong( global, RECEIVETRUESIZEPROMPT) <
	     get_global_ulong( remoteglobal, ZENDTRUESIZEPROMPT))
    {
	mail_err( "Max real transfer size (%lu) exceeded.",
		  get_global_ulong( global, RECEIVETRUESIZEPROMPT));
	return;
    }
    else if (get_global_ulong( global, RECEIVESIZEPROMPT) <
	     get_global_ulong( remoteglobal, ZENDSIZEPROMPT))
    {
	mail_err( "Max mail traffic transfer size (%lu) exceeded.",
		  get_global_ulong( global, RECEIVESIZEPROMPT));
	return;
    }
    else if (getpwnam( receivingUser) == NIL)
    {
	mail_err( "Unknown user (%s)", receivingUser);
	return;
    }
    else if (((list = get_global_string( global, ALLOWUSERSPROMPT)) &&
	      list[0] &&
	      !str_listed( receivingUser, list)) ||
	     str_listed( receivingUser,
			 get_global_string( global, EXCLUDEUSERSPROMPT)))
    {
	mail_err( "User access denied");
	return;
    }
    else if (((list = get_global_string( global, ALLOWSYSTEMSPROMPT)) &&
	      list[0] &&
	      !str_listed( sendingSystem, list)) ||
	     str_listed( sendingSystem,
			 get_global_string( global, EXCLUDESYSTEMSPROMPT)))
    {
	mail_err( "System access denied");
	return;
    }
    else
    {
	if ((jobId = new_job()) == RESERVEDID)
	{
	    return;
	}
	set_global( global, ACTIONPROMPT, ACTION_UAR);
	set_global( global, RECEIVINGJOBIDPROMPT, jobId);
	set_global( global, DIRPROMPT,
		    get_global_string( global, SPOOLDIRPROMPT) ?
		    get_global_string( global, SPOOLDIRPROMPT) : "~/Zend");
	set_global( global, FILEPROMPT,
		    get_global_string( remoteglobal, FILEPROMPT));
	set_global( global, TYPEPROMPT,
		    get_global_string( remoteglobal, TYPEPROMPT));
	set_global( global, SENDINGUSERPROMPT,
		    get_global_string( remoteglobal, SENDINGUSERPROMPT));
	set_global( global, RECEIVINGUSERPROMPT, receivingUser);
	set_global( global, COMPRESSEDPROMPT,
		    get_global_boolean( remoteglobal, COMPRESSEDPROMPT));
	/*
	 * Store sending system's name in jobfile for timeout.
	 */
	set_global( global, SENDINGSYSTEMPROMPT, sendingSystem);
	/*
	 * Determine maximum of job- and packet timeouts.
	 */
	if (get_global_ushort( global, PACKETTIMEOUTPROMPT) <
	    get_global_ushort( remoteglobal, PACKETTIMEOUTPROMPT))
	{
	    set_global( global, PACKETTIMEOUTPROMPT,
			get_global_ushort( remoteglobal, PACKETTIMEOUTPROMPT));
	}
	if (get_global_ushort( global, JOBTIMEOUTPROMPT) <
	    get_global_ushort( remoteglobal, JOBTIMEOUTPROMPT))
	{
	    set_global( global, JOBTIMEOUTPROMPT,
			get_global_ushort( remoteglobal, JOBTIMEOUTPROMPT));
	}
	set_global( global, TIMESTAMPPROMPT, (Ulong) timestamp);
#if DEBUG
	zerr( "Writing jobfile...\n");
#endif
	write_globs_to_jobfile( global, jobId);
	zlog( "ACCEPT");
#if DEBUG
	zerr( "Sending UAR...\n");
#endif
	mail_message();
    }
}

#if _ANSI_DEFUN_
extern void handle_uar( void)
#else
extern void handle_uar()
#endif
{
    char buf[PATHBUFSIZ];
    char buf2[PATHBUFSIZ];
    FILE *tmpStream;
    FILE    *chunkStream= NIL;
    Ushort  lineno = 0,
	    chunkno = 0,
	    nrofchunks = 0,
	    chunksize = get_global_ushort( remoteglobal, LINESPROMPT);
    Ulong   jobId = get_global_ulong( remoteglobal, SENDINGJOBIDPROMPT);

    umask( ~S_IRUSR);
#if DEBUG
    zerr( "Handling %s\n", ACTION_UAR);
#endif
    if (chunksize < 100)
    {
#if 0
	mail_err( "Chunk size too small (<100) %u", (unsigned int)chunksize);
	return;
#else   /* silently set to minimum */
	chunksize = 100;
#endif
    }

    if (read_jobfile_to_globs( global, jobId) != 0)
    {
	mail_err( "UAR: Never heard of local job %lu", jobId);
	return;
    }

    /*
     * Set job timeout to maximum of send and receive.
     */
    if (get_global_ushort( global, JOBTIMEOUTPROMPT) <
	get_global_ushort( remoteglobal, JOBTIMEOUTPROMPT))
    {
	set_global( global, JOBTIMEOUTPROMPT,
		    get_global_ushort( remoteglobal, JOBTIMEOUTPROMPT));
    }

    /*
     * Make the chunks
     */
    sprintf( buf, "cd %s/D.%lu && %s < ../F.%lu ../F.%lu",
	     HOMEDIR, jobId, UUENCODEPROG, jobId,
	     get_global_ulong( remoteglobal, RECEIVINGJOBIDPROMPT));

    if ((tmpStream = popen_as_zend( buf, "r")) == NIL)
    {
	mail_err( "Unable to make chunks. Job discarded.");
	kill_job( jobId);
	return;
    }

    while (fgets( buf, PATHBUFSIZ, tmpStream) != NIL)
    {
	if (lineno == chunksize)
	{
	    if (fclose( chunkStream) != 0)
	    {
		mail_err( "Unable to make chunks. Job discarded.");
		kill_job( jobId);
		return;
	    }
	    lineno = 0;
	}
	if (lineno == 0)
	{
	    sprintf( buf2, "%s/D.%lu/%05u", HOMEDIR, jobId,
		     (unsigned int)++nrofchunks);
	    if ((chunkStream = fopen( buf2, "w")) == NIL)
	    {
		mail_err( "Unable to make chunks. Job discarded.");
		kill_job( jobId);
		return;
	    }
	}
	fputs( buf, chunkStream);
	lineno++;
    }
    if (fclose( chunkStream) != 0 || pclose( tmpStream) != 0)
    {
	mail_err( "Unable to make chunks. Job discarded.");
	kill_job( jobId);
	return;
    }


    set_global( global, ACTIONPROMPT, ACTION_CNK);
    set_global( global, NROFCHUNKSPROMPT, (unsigned int)nrofchunks);
    set_global( global, RECEIVINGJOBIDPROMPT,
		get_global_ulong( remoteglobal, RECEIVINGJOBIDPROMPT));
    set_global( global, TIMESTAMPPROMPT, timestamp);
    /*
     * We have made all chunks. Mail them off.
     */
    write_globs_to_jobfile( global, jobId);
    for (chunkno = 1; chunkno <= nrofchunks; chunkno++)
    {
	set_global( global, CHUNKNRPROMPT, (unsigned int)chunkno);
	mail_chunk();
    }
    zlog( "ZENT (%u chunk%s)", (unsigned int)nrofchunks,
	  (nrofchunks == 1) ? "" : "s");

    /*
     * remove our copy
     */
    sprintf( buf, "%s/F.%lu", HOMEDIR, jobId);

#if DEBUG
    zerr( "Unlinking %s\n", buf);
#endif
    unlink( buf);
}

#if _ANSI_DEFUN_
extern void handle_cnk( void)
#else
extern void handle_cnk()
#endif
{
    char buf[PATHBUFSIZ];
    char buf2[PATHBUFSIZ];
    FILE *tmpStream, *mailStream;
    Ushort try;
    struct passwd *pwStruct = NIL;
    char *targetdir;
    char *targetfile;
    int deliver = -1, nameclash = FALSE;
    char *assembledfile = NIL;
    Boolean compressed;
    Ulong jobId = get_global_ulong( remoteglobal, RECEIVINGJOBIDPROMPT);
    struct stat statBuf;
    Boolean filenameCorrupted = FALSE,
	    tarCorrupted = FALSE;
    mode_t oldumask = umask( ~S_IRUSR);

#if DEBUG
    zerr( "Handling %s\n", ACTION_CNK);
#endif
    if (read_jobfile_to_globs( global, jobId) != 0)
    {
	mail_err( "CNK: Never heard of local job %lu", jobId);
	return;
    }

    sprintf( buf2, "%s/D.%lu/%05u.XXXXXX", HOMEDIR, jobId,
	     (unsigned int)get_global_ushort( remoteglobal, CHUNKNRPROMPT));
    if (mktemp( buf2) == NIL)
    {
	zerr( "Unable to create temp chunk name %s\n", buf2);
	return;
    }
    if ((tmpStream = fopen( buf2, "w")) == NIL)
    {
	zerr( "Unable to create chunk %s\n", buf);
	return;
    }
    /*
     * stdin is on the first line of the chunk.
     */
    while (fgets( buf, PATHBUFSIZ, stdin))
    {
	if (strncmp( buf, ZENDDELIMITER, strlen( ZENDDELIMITER)) == 0)
	{
	    break;
	}
	fputs( buf, tmpStream);
    }
    fclose( tmpStream);

    set_global( global, ACTIONPROMPT, ACTION_ACK);
    set_global( global, CHUNKNRPROMPT,
		(unsigned int)get_global_ushort( remoteglobal, CHUNKNRPROMPT));
    set_global( global, NROFCHUNKSPROMPT,
		(unsigned int)get_global_ushort( remoteglobal, NROFCHUNKSPROMPT));
    set_global( global, TIMESTAMPPROMPT, timestamp);
    write_globs_to_jobfile( global, jobId);
    mail_message();
    zlog( "CHUNK (%d of %d)",
	  (unsigned)get_global_ushort( global, CHUNKNRPROMPT),
	  (unsigned)get_global_ushort( global, NROFCHUNKSPROMPT));
    /*
     * Find out if all chunks are there:
     */
    sprintf( buf, "%s/D.%lu/%05u", HOMEDIR, jobId,
	     (unsigned int)get_global_ushort( remoteglobal, CHUNKNRPROMPT));
    if (one_at_a_time( jobId, FILE_DELIVER) != 0)
    {
	zerr( "Unable to lock rename chunk %s to %s\n", buf2, buf);
	return;
    }
    if (rename( buf2, buf) != 0)
    {
	zerr( "Unable to rename chunk %s to %s\n", buf2, buf);
	end_one_at_a_time( jobId, FILE_DELIVER);
	return;
    }
    for (try=1; try<=get_global_ushort( global, NROFCHUNKSPROMPT); try++)
    {
	sprintf( buf, "%s/D.%lu/%05u", HOMEDIR, jobId,
		 (unsigned int)try);
#if 0
	if (access( buf, F_OK) != 0)
#endif	/* NO!!! access checks access rights of REAL user!!! */
	if (stat( buf, &statBuf) != 0 && errno == ENOENT)
	{
	    /* incomplete */
	    end_one_at_a_time( jobId, FILE_DELIVER);
	    return;
	}
    }

    end_one_at_a_time( jobId, FILE_DELIVER);
    /*
     * We have all chunks and have sent all ACK's. Reassemble the file and place
     * it in the users home directory.
     */

#if DEBUG
    zerr( "I think all chunks received now\n");
#endif
    targetdir = get_global_string( global, DIRPROMPT);
    targetfile = get_global_string( global, FILEPROMPT);
    compressed = get_global_boolean( global, COMPRESSEDPROMPT);

    /* Catch attempts to corrupt filename. */
    if (basename( targetfile) != targetfile)
    {
	filenameCorrupted = TRUE;
	zlog( "BURGLAR ALARM! (corrupted filename)");
	targetfile = basename( targetfile);
    }
    /*
     * First get the users home directory and other info:
     */
    pwStruct = getpwnam( get_global_string( global, RECEIVINGUSERPROMPT));
    if (pwStruct == NIL)
    {
	zerr( "No delivery. Cannot get password info for user %s\n",
	      get_global_string( global, RECEIVINGUSERPROMPT));
	deliver = -1;
#if DEBUG
	zerr( "Unable to get password info\n");
#endif
    }
    else
    {
	/*
	 * Reassemble and deliver
	 */
	sprintf( buf, "cd %s/D.%lu && %s * | %s", HOMEDIR, jobId,
		 CATPROG, UUDECODEPROG);

	if (system_as_zend( buf) != 0)
	{
	    /*
	     * failed reassembly
	     */
	    deliver = -1;
#if DEBUG
	    zerr( "Reassembly failed\n");
#endif
	}
	else
	{
	    sprintf( buf, "%s/F.%lu", HOMEDIR, jobId);
	    assembledfile = strcpy( safecalloc( strlen( buf) + 1, sizeof( char)), buf);

	    /*
	     * Some uuencodes give default permission 0 when fed stdin, DEC
	     * Ultrix 4.2 for instance.
	     */
	    if (chmod( assembledfile, S_IRUSR) != 0)
	    {
#if DEBUG
		zerr( "Chmod assembledfile failed\n");
#endif
		deliver = -1;
	    }
	    umask( oldumask);

	    targetdir = buf2;
	    if (get_global_string( global, SPOOLDIRPROMPT) != NIL)
	    {
		/* Make sure spooldir exists.
		   {NOTE: if zend is setuid non-root this will fail in general,
		    so make sure spooldir is created at installation.} */
		mkdir( get_global_string( global, SPOOLDIRPROMPT),
		       (S_IRWXU|S_IRWXG|S_IRWXO) & ~(S_IWGRP|S_IWOTH));
		sprintf( targetdir, "%s/%s",
			 get_global_string( global, SPOOLDIRPROMPT),
			 get_global_string( global, RECEIVINGUSERPROMPT));
	    }
	    else
	    {
		/* {NOTE: this only works if zend is seetuid root!} */
		sprintf( targetdir, "%s/Zend", pwStruct->pw_dir);
	    }

	    if (mkdir( targetdir, S_IRWXU) == 0)
	    {
		if (chown( targetdir, pwStruct->pw_uid, pwStruct->pw_gid) != 0)
		{
		    /* Make sure the intended user can access the directory,
		       even if chown fails. (e.g., if zend is running
		       setuid non-root on _POSIX_CHOWN_RESTRICTED. systems.)
		       {{Is this a security hole????}}
		     */
		    chmod( targetdir, (S_IRWXU|S_IRWXG|S_IRWXO));
		}
	    }

	    if (!((stat( targetdir, &statBuf) == 0) &&
		  (S_ISDIR( statBuf.st_mode)) &&
		  (S_IWUSR & statBuf.st_mode)))
	    {
		sprintf( targetdir, "/tmp");
	    }

	    /*
	     * If name clash, make temporary subdir:
	     */
	    sprintf( buf, "%s/%s", targetdir, targetfile);
	    if (stat( buf, &statBuf) == 0)
	    {
#if DEBUG
		zerr( "Name clash\n");
#endif
		nameclash = TRUE;
		strcat( targetdir, "/ZnXXXXXX");
		if (mktemp( targetdir) != NIL &&
		    mkdir( targetdir,
			   (S_IRWXU|S_IRWXG|S_IRWXO) & ~(S_IWGRP|S_IWOTH)) == 0)
		{
		    if (chown( targetdir, pwStruct->pw_uid, pwStruct->pw_gid) != 0)
		    {
			chmod( targetdir, (S_IRWXU|S_IRWXG|S_IRWXO));
		    }
		    deliver = 0;
		    if (!((stat( targetdir, &statBuf) == 0) &&
			  (S_ISDIR( statBuf.st_mode)) &&
			  (S_IWUSR & statBuf.st_mode)))
		    {
			sprintf( targetdir, "/tmp");
		    }

		}
		else
		{
#if DEBUG
		    zerr( "Unable to make name clash directory\n");
#endif
		    deliver = -1;
		}
	    }
	    else
	    {
		deliver = 0;
	    }
	}
    }

    if (deliver == 0)
    {
	if (strcmp( get_global_string( global, TYPEPROMPT), TYPE_FILE) == 0)
	{
	    sprintf( buf, "%s > %s/%s",
		     (compressed) ? UNCOMPRESSPROG : CATPROG,
		     targetdir, quote_for_sh( targetfile));
	}
	else
	{
	    /*
	     * Check filenames in tar as a safety precaution.
	     */
	    sprintf( buf, "%s < %s | %s tf -",
		     (compressed) ? UNCOMPRESSPROG : CATPROG,
		     targetfile, TARPROG);
#if DEBUG
	    zerr( "%s", buf);
#endif
	    if ((tmpStream = popen_as_zend( buf, "r")) != NIL)
	    {
		int len = strlen( targetfile);
		char *s;

		while (!tarCorrupted && fgets( buf, sizeof( buf), tmpStream))
		{
		    /* All filenames should start with the directory name. */
		    if (strncmp( buf, targetfile, len) != 0 ||
			!(buf[len] == '/' || buf[len] == '\n'))
		    {
			tarCorrupted = TRUE;
			break;
		    }
		    /*
		     * As an extra precaution, don't allow any reference
		     * to parent directories (..).  These do not occur in
		     * well-formed tars.
		     */
		    for (s = buf;  (s = strchr( s, '.'));  s++)
		    {
			if (s[1] == '.' && ((s == buf || s[-1] == '/') &&
					    (s[2] == '/' || s[2] == '\n')))
			{
			    tarCorrupted = TRUE;
			    break;
			}
		    }
		}
		pclose( tmpStream);
	    }
	    if (tarCorrupted)
	    {
		zlog( "BURGLAR ALARM! (corrupted filenames in tar)");
		/* Deliver the tar instead of the directory. */
		sprintf( buf, "cd %s && %s > %s",
			 targetdir, (compressed) ? UNCOMPRESSPROG : CATPROG,
			 quote_for_sh( targetfile));
	    }
	    else
	    {
		sprintf( buf, "cd %s && %s | %s xf -",
			 targetdir, (compressed) ? UNCOMPRESSPROG : CATPROG,
			 TARPROG);
	    }
	}
#if DEBUG
	zerr( "%s", buf);
#endif
	/* pipe the assembled file into the unpacking command. */
	if ((tmpStream = fopen( assembledfile, "r")) == NIL)
	{
	    deliver = -1;
	}
	else
	{
	    FILE *cmdStream;

	    if ((cmdStream = popen_as( buf, "w",
				       pwStruct->pw_uid,
				       pwStruct->pw_gid)) == NIL)
	    {
		deliver = -1;
	    }
	    else
	    {
		size_t n;
		char buf[BUFSIZ];

		while (n = fread( buf, sizeof( char), sizeof( buf), tmpStream))
		{
		    if (fwrite( buf, sizeof( char), n, cmdStream) != n)
		    {
			deliver = -1;
			break;
		    }
		}
		if (deliver == 0)
		{
		    deliver = pclose( cmdStream);
		}
	    }
	    fclose( tmpStream);
	}
    }

    /*
     * Remove all
     */
#if !DEBUG
    kill_job( jobId);
#endif
    /*
     * Mail user
     */
    if ((mailStream = mail_open( get_global_string( global, RECEIVINGUSERPROMPT))) != NIL)
    {
	if (pwStruct)
	{
	    /* replace user's home directory with csh-like '~'. */
	    int homelen = strlen( pwStruct->pw_dir);

	    if (strncmp( targetdir, pwStruct->pw_dir, homelen) == 0 &&
		targetdir[homelen] == '/' && homelen)
	    {
		*(targetdir += homelen - 1) = '~';
	    }
	}
	fprintf( mailStream, "Subject: ZEND DELIVER%s %s/%s <- %s\n",
		 (deliver == 0) ? "ED" : "Y FAILED",
		 targetdir, targetfile, remote_user_address());

	fprintf( mailStream, "\nZEND report:\n");
	fprintf( mailStream, "User %s zent %s %s\n",
		 remote_user_address(),
		 strcmp( get_global_string( global, TYPEPROMPT), TYPE_FILE) == 0?
		 "file" : "directory",
		 targetfile);

	if (filenameCorrupted || tarCorrupted)
	{
	    fprintf( mailStream, 
		     "\n!!USER %s TRIED TO CORRUPT THE ZEND PROTOCOL!!\n\n",
		     remote_user_address());
	}
	if (tarCorrupted)
	{
	    fprintf( mailStream, "\n\
\tThe directory is delivered as the tar archive.\n\
\tWARNING: THIS TAR MAY CONTAIN `DANGEROUS' FILENAMES!\n\n");
	}
	if (!deliver)
	{
	    if (nameclash)
	    {
		fprintf( mailStream, "Name clash with existing file/directory.\n");
	    }
	    fprintf( mailStream, "It was succesfully delivered in %s.\n",
		     targetdir);
	}
	else
	{
	    fprintf( mailStream, "Delivery failed. Exit status %d.\n",
		     deliver);
	}
	mail_close( mailStream);
    }
    zlog( (deliver == 0) ? "COMPLETED (successfully)" :
	  "COMPLETED (exit %d)", deliver);
}

#if _ANSI_DEFUN_
extern void handle_ack( void)
#else
extern void handle_ack()
#endif
{
    char buf[PATHBUFSIZ];
    Ushort try, nrofchunks;
    Ushort chunkno = get_global_ushort( remoteglobal, CHUNKNRPROMPT);
    Ulong jobId = get_global_ulong( remoteglobal, SENDINGJOBIDPROMPT);
    FILE *mailStream;

#if DEBUG
    zerr( "Handling %s for Chunk %u\n", ACTION_ACK, (unsigned int)chunkno);
#endif
    if (read_jobfile_to_globs( global, jobId) != 0)
    {
	mail_err( "ACK: Never heard of local job %lu", jobId);
	return;
    }
#if DEBUG
    zerr( "Local jobId (sender) is %lu\n", jobId);
#endif
    /*
     * Look if this was the last chunk to get the ACK for
     */
    if (one_at_a_time( jobId, FILE_DELIVER) != 0)
    {
	zerr( "Unable to lock sending job for removal of chunk\n");
	return;
    }
    nrofchunks = get_global_ushort( global, NROFCHUNKSPROMPT);
    zlog( "ACK (%u of %u)", (unsigned int)chunkno, (unsigned int)nrofchunks);
    sprintf( buf, "%s/D.%lu/%05u", HOMEDIR, jobId, (unsigned int)chunkno);
#if DEBUG
    zerr( "Unlinking %s\n", buf);
#endif
    if (unlink( buf) != 0 && errno != ENOENT)
    {
	zerr( "Unable to unlink zent chunk %s (%s)\n", buf, strerror( errno));
    }

    for (try=1; try<=nrofchunks; try++)
    {
	struct stat statBuf;

	sprintf( buf, "%s/D.%lu/%05u", HOMEDIR, jobId, (unsigned int)try);
#if DEBUG
	zerr( "Testing if %s is present\n", buf);
#endif
#if 0
	if (access( buf, F_OK) == 0)
#endif
	if (stat( buf, &statBuf) == 0)
	{
	    /*
	     * still a chunk left. return.
	     */
#if DEBUG
	    zerr( "Yep, %s is present\n", buf);
#endif
	    end_one_at_a_time( jobId, FILE_DELIVER);
	    /*
	     * Update timestamp (to forestall job timeout, since the other
	     * side is obviously still alive.)
	     */
	    set_global( global, TIMESTAMPPROMPT, (Ulong) timestamp);
	    write_globs_to_jobfile( global, jobId);

	    return;
	}
	else
	{
#if DEBUG
	    zerr( "Nope, %s is not present\n", buf);
#endif
	}
    }
    end_one_at_a_time( jobId, FILE_DELIVER);
#if DEBUG
    zerr( "I think all acknowledges are received\n");
#endif
    /*
     * remove directory and job file. file copy has been removed when chunks
     * were sent.
     */
    sprintf( buf, "%s/C.%lu", HOMEDIR, jobId);
#if DEBUG
    zerr( "Unlinking %s\n", buf);
#endif
    unlink( buf);
    sprintf( buf, "%s/D.%lu", HOMEDIR, jobId);
#if DEBUG
    zerr( "Removing dir %s\n", buf);
#endif
    if (rmdir( buf) != 0)
    {
	zerr( "Unable to rmdir %s (%s)\n", buf, strerror( errno));
    }
    /*
     * Mail user
     */
    if ((mailStream = mail_open( get_global_string( global, SENDINGUSERPROMPT))) != NIL)
    {
	fprintf( mailStream, "Subject: ZENT %s -> %s\n",
		 get_global_string( global, FILEPROMPT), remote_user_address());

	fprintf( mailStream, "\nZEND report:\n");
	fprintf( mailStream, "%s %s zent succesfully to user %s\n",
		 get_global_string( global, TYPEPROMPT),
		 get_global_string( global, FILEPROMPT), remote_user_address());

	mail_close( mailStream);
    }
    zlog( "COMPLETED");
}

#if _ANSI_DEFUN_
extern void handle_rsn( void)
#else
extern void handle_rsn()
#endif
{
    char buf[PATHBUFSIZ];
    unsigned int chunkno;
    Ulong jobId = get_global_ulong( remoteglobal, SENDINGJOBIDPROMPT);

#if DEBUG
    zerr( "Handling %s\n", ACTION_RSN);
#endif
    if (read_jobfile_to_globs( global, jobId) != 0)
    {
	/* {[TRH] Should we do this?  I mean, it is possible--if unlikely--that
	     a resend is received AFTER all chunks have been acknowledged.} */
	mail_err( "RSN: Never heard of local job %lu", jobId);
	return;
    }
    /*
     * Update timestamp (to forestall job timeout, since the other
     * side is obviously still alive.)
     */
    set_global( global, TIMESTAMPPROMPT, (Ulong) timestamp);
    write_globs_to_jobfile( global, jobId);

    zlog( "RESEND");
    /*
     * Read requested chunks from stdin.
     */
    while (scanf( "%u", &chunkno) > 0)
    {
	struct stat statBuf;
	/* rezend the requested chunk (if still outstanding.) */

	sprintf( buf, "%s/D.%lu/%05u", HOMEDIR, jobId, chunkno);
#if 0
	if (access( buf, F_OK) == 0)
#endif
	if (stat( buf, &statBuf) == 0)
	{
	    set_global( global, CHUNKNRPROMPT, chunkno);
	    mail_chunk();
	}
    }
    if (scanf( "%s", buf) <= 0 || strcmp( ZENDDELIMITER, buf) != 0)
    {
	zerr( "garbled %s packet\n", ACTION_RSN);
    }
}

#if _ANSI_DEFUN_
extern void handle_err( void)
#else
extern void handle_err()
#endif
{
#if DEBUG
    FILE *mailStream;
    Ushort try;
#endif
    Ulong jobId = get_global_ulong( remoteglobal, SENDINGJOBIDPROMPT);

#if DEBUG
    zerr( "Handling %s\n", ACTION_RSN);
#endif
    if (read_jobfile_to_globs( global, jobId) == 0)
    {
	kill_job( jobId);
    }
#if !DEBUG
    mail_report_error( get_global_string( remoteglobal, REASONPROMPT));
#else
    if ((mailStream = mail_open( "postmaster")) == NIL)
    {
	return;
    }
    /*
     * The next fprintf is necessary, since some mail programs get confused
     * by the header like contents of Zend messages. This forces us to go
     * into body-mode.
     */
    fprintf( mailStream, "\nZEND received error message:\n\n");

    for (try=0; try<nrOfGlobalVariables; try++)
    {
	writeglobtostream( remoteglobal, remoteglobal[try].prompt, mailStream);
    }
    mail_close( mailStream);
#endif /* DEBUG */
}

#if _ANSI_DEFUN_
extern void mail_chunk( void)
#else
extern void mail_chunk()
#endif
{
    char buf[PATHBUFSIZ];
    FILE *mailStream;
    FILE *chunkStream;
    int number;

    sprintf( buf, "%s/D.%lu/%05u", HOMEDIR,
	     get_global_ulong( global, SENDINGJOBIDPROMPT),
	     (unsigned int)get_global_ushort( global, CHUNKNRPROMPT));
#if DEBUG
    zerr( "Mail chunk %s\n", buf);
#endif
    if ((chunkStream = fopen( buf, "r")) == NIL)
    {
	zerr( "Unable to read chunk %s\n", buf);
	return;
    }
    if ((mailStream = mail_open( ZEND_ADDRESS)) == NIL)
    {
	return;
    }
    /*
     * The next fprintf is necessary, since some mail programs get confused
     * by the header like contents of Zend messages. This forces us to go
     * into body-mode.
     */
    fprintf( mailStream, "\n\n++++++\n\n");

    writeglobtostream( global, SYSTEMNAMEPROMPT, mailStream);
    writeglobtostream( global, ACTIONPROMPT, mailStream);
    writeglobtostream( global, RECEIVINGJOBIDPROMPT, mailStream);
    writeglobtostream( global, CHUNKNRPROMPT, mailStream);
    writeglobtostream( global, NROFCHUNKSPROMPT, mailStream);
    fprintf( mailStream, "%s\n", ZENDDELIMITER);
    while ((number = fread( buf, sizeof(char), PATHBUFSIZ, chunkStream)) != 0)
    {
	if (fwrite( buf, sizeof( char), number, mailStream) != number)
	{
	    zerr( "ERROR: failure copying chunk to mail\n");
	    break;
	}
    }
    fprintf( mailStream, "%s\n", ZENDDELIMITER);
    mail_close( mailStream);
}

#if _ANSI_DEFUN_
extern void mail_resend( void)
#else
extern void mail_resend()
#endif
{
    char buf[PATHBUFSIZ];
    FILE *mailStream;
    Ulong jobId = get_global_ulong( global, SENDINGJOBIDPROMPT);
    Ushort nrofchunks = get_global_ushort( global, NROFCHUNKSPROMPT);
    Ushort try;

    set_global( global, ACTIONPROMPT, ACTION_RSN);
#if DEBUG
    zerr( "Mail message %s to zend at system %s\n",
	  ACTION_RSN, get_global_string( remoteglobal, SYSTEMNAMEPROMPT));
#endif
    if ((mailStream = mail_open( ZEND_ADDRESS)) == NIL)
    {
	return;
    }
    /*
     * The next fprintf is necessary, since some mail programs get confused
     * by the header like contents of Zend messages. This forces us to go
     * into body-mode.
     */
    fprintf( mailStream, "\n\n++++++\n\n");

    writeglobtostream( global, SYSTEMNAMEPROMPT, mailStream);
    writeglobtostream( global, ACTIONPROMPT, mailStream);
    writeglobtostream( global, SENDINGJOBIDPROMPT, mailStream);
    fprintf( mailStream, "%s\n", ZENDDELIMITER);

    for (try = 1;  try <= nrofchunks;  try++)
    {
	struct stat statBuf;

	sprintf( buf, "%s/D.%lu/%05u", HOMEDIR, jobId, (unsigned int)try);

	/*
	 * Build a list of chunk numbers that we want to have re-zent.
	 */
#if 0
	if (access( buf, F_OK) != 0)
#endif
	if (stat( buf, &statBuf) != 0)	
	{
	    fprintf( mailStream, "%u\n", (unsigned int)try);
	}
    }
    fprintf( mailStream, "%s\n", ZENDDELIMITER);
    mail_close( mailStream);
}

#if _ANSI_DEFUN_
extern void mail_message( void)
#else
extern void mail_message()
#endif
{
    char buf[PATHBUFSIZ];
    FILE *mailStream;
    char *action = get_global_string( global, ACTIONPROMPT);

#if DEBUG
    zerr( "Mail message %s to zend at system %s\n",
	  action, get_global_string( remoteglobal, SYSTEMNAMEPROMPT));
#endif
    if ((mailStream = mail_open( ZEND_ADDRESS)) == NIL)
    {
	if (strcmp( ACTION_AYT, action) == 0)
	{
	    sprintf( buf, "%s/F.%lu", HOMEDIR,
		     get_global_ulong( global, SENDINGJOBIDPROMPT));
#if DEBUG
	    zerr( "Unlinking %s\n", buf);
#endif
	    if (unlink( buf) != 0)
	    {
		zerr( "Unable to unlink copy of file %s\n", buf);
	    }
	}
	if (strcmp( ACTION_AYT, action) == 0 ||
	    strcmp( ACTION_UAR, action) == 0)
	{
	    sprintf( buf, "%s/D.%lu", HOMEDIR,
		     get_global_ulong( global, SENDINGJOBIDPROMPT));
	    if (rmdir( buf) != 0)
	    {
		zerr( "Unable to remove jobDir %s\n", buf);
	    }
	}
	return;
    }
    if (strcmp( ACTION_AYT, action) == 0)
    {
	FILE *tmpStream;

	sprintf( buf, "%s/postmast.msg", HOMEDIR);
	if ((tmpStream = fopen( buf, "r")) != NIL)
	{
	    while (fgets( buf, PATHBUFSIZ, tmpStream))
	    {
		fputs( buf, mailStream);
	    }
	    fclose( tmpStream);
	}
#if DEBUG
	zerr( "Postmaster message added.\n");
#endif
    }
    /*
     * The next fprintf is necessary, since some mail programs get confused
     * by the header like contents of Zend messages. This forces us to go
     * into body-mode.
     */
    fprintf( mailStream, "\n\n++++++\n\n");

    writeglobtostream( global, ZENDIDPROMPT, mailStream);
    writeglobtostream( global, SYSTEMNAMEPROMPT, mailStream);
    writeglobtostream( global, ACTIONPROMPT, mailStream);
    writeglobtostream( global, SENDINGJOBIDPROMPT, mailStream);
    writeglobtostream( global, RECEIVINGJOBIDPROMPT, mailStream);
    writeglobtostream( global, ROLEPROMPT, mailStream);
    if (strcmp( ACTION_AYT, action) == 0)
    {
	writeglobtostream( global, FILEPROMPT, mailStream);
	writeglobtostream( global, TYPEPROMPT, mailStream);
	writeglobtostream( global, SENDINGUSERPROMPT, mailStream);
	writeglobtostream( global, RECEIVINGUSERPROMPT, mailStream);
	writeglobtostream( global, ZENDTRUESIZEPROMPT, mailStream);
	writeglobtostream( global, ZENDSIZEPROMPT, mailStream);
	writeglobtostream( global, COMPRESSEDPROMPT, mailStream);
	writeglobtostream( global, PACKETTIMEOUTPROMPT, mailStream);
	writeglobtostream( global, JOBTIMEOUTPROMPT, mailStream);
    }
    else if (strcmp( ACTION_UAR, action) == 0)
    {
	writeglobtostream( global, LINESPROMPT, mailStream);
	writeglobtostream( global, JOBTIMEOUTPROMPT, mailStream);
    }
    else if (strcmp( ACTION_ACK, action) == 0)
    {
	writeglobtostream( global, CHUNKNRPROMPT, mailStream);
    }
    else if (strcmp( ACTION_ERR, action) == 0)
    {
	writeglobtostream( global, REASONPROMPT, mailStream);
    }
    fprintf( mailStream, "%s\n", ZENDDELIMITER);
    mail_close( mailStream);
}

/*======================================================================
 * $Log: zendhand.c,v $
 * Revision 1.13  1993/02/21  13:07:09  gerben
 * mail_err is global because we use it in zend.c
 *
 * Revision 1.12  1993/02/02  21:00:00  gerben
 * *** empty log message ***
 *
 * Revision 1.12  1993/01/29  18:04:40  tom
 * fix bug in sending empty mail body in mail_err().
 *
 * Revision 1.11  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; read spoolfile through a pipe so user process does not
 *  have to access the spool directory); fix MAILTOPROMPT.
 *
 * Revision 1.10  1993/01/10  20:20:25  tom
 * shell-quote user-supplied names; check filenames if tar'ed;
 * Posixate access modes.
 *
 * Revision 1.9  1992/12/18  23:11:12  tom
 * fix some non-ANSI C discrepancies; make receiving user own nameclash
 * directory.
 *
 * Revision 1.8  1992/12/10  05:27:34  tom
 * major cleanup to remove code duplication; plug security holes (see zend.c)
 * add subject lines to user notification messages for easy identification.
 *
 *======================================================================*/

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