ftp.nice.ch/pub/next/unix/mail/smail3.1.20.s.tar.gz#/smail3.1.20/src/transports/appendfile.c

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

/* @(#)src/transports/appendfile.c	1.3 24 Oct 1990 05:25:44 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * appendfile.c:
 *	deliver mail by appending the message to a file.  Be very
 *	careful to keep things secure by checking writable of the
 *	file by an appropriate user and group.
 *
 *	Alternately, the appendfile can be used as a queueing driver
 *	which installs new files in a particular directory.  The
 *	driver can be used for very simple queueing if a `dir'
 *	attribute is given rather than a `file' attribute.
 *
 * Specifications for the appendfile transport driver:
 *
 *	private attribute data:
 *	    file (string):  a string which will be expanded to the filename
 *		to append the message to.  Some variable expansion will
 *		be done, using expand_string.
 *	    dir (string):  a string which will be expanded to a directory
 *		in which to put the message.  If this is given, then the
 *		appendfile driver will actually be considered a very simple
 *		queueing driver.
 *	    user (name):  the name of the user to setuid to in the child
 *		process.  If not specified then the addr structure is
 *		searched for a uid.  If none is found there, use the
 *		nobody_uid.
 *	    group (name):  the name of the group to setgid to in the child
 *		process.  The algorithm used for uid is used here as well.
 *	    prefix (string):  a string to be prepended before the message
 *		in the file.
 *	    suffix (string):  a string to be appended after the message
 *		in the file.
 *	    mode (integer):  defines the mode to give newly created files.
 *
 *	private attribute flags:
 *	    append_as_user: if set, default uid and gid are taken from
 *			    the addr structure.
 *	    expand_user: if set, expand username before expanding the
 *			 file name.  This is useful for the stock "file"
 *			 transport which requires ~ expansions, at a
 *			 minimum, on the username.
 *	    check_path: if set, check the complete path for accessibility
 *			by the appropriate user.  Otherwise, only the
 *			accessibility of the last file is checked.  On
 *			non-BSD systems, the flag being set translates
 *			to a stat on each of the directories in the path
 *			down to the file.
 *	    check_user: if set, verify that the $user expansion will not
 *			contain any `/' characters, which might cause a
 *			reference out of the desired directory.  if the
 *			verification fails, delivery to the address fails.
 *
 * NOTE:  the appendfile driver can only deliver to one address per call.
 *
 * NFS NOTE:
 *	If a file may be opened over NFS, care must be taken that smail is
 *	running as the desired user during the entire transaction of
 *	opening the mailbox, locking the mailbox and writing out th
 *	message.  At least this is true of NFS in SunOS3.x.  Define
 *	HAVE_SETEUID to get the necessary behavior.
 */
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "defs.h"
#ifdef UNIX_SYS5
# include <fcntl.h>
#endif
#ifdef UNIX_BSD
# include <sys/file.h>
#endif
#include "../smail.h"
#include "../smailconf.h"
#include "../parse.h"
#include "../addr.h"
#include "../log.h"
#include "../spool.h"
#include "../child.h"
#include "../transport.h"
#include "../exitcodes.h"
#include "../dys.h"
#include "appendfile.h"
#ifndef DEPEND
# include "../extern.h"
# include "../debug.h"
# include "../error.h"
#endif

/* functions local to this file */
static void get_appendfile_ugid();


/*
 * tdp_appendfile - appendfile transport driver
 */
void
tpd_appendfile(addr, succeed, defer, fail)
    struct addr *addr;			/* recipient addresses for transport */
    struct addr **succeed;		/* successful deliveries */
    struct addr **defer;		/* defer until a later queue run */
    struct addr **fail;			/* failed deliveries */
{
    register struct appendfile_private *priv;
    struct transport *tp = addr->transport;
    char *fn;				/* expanded filename */
    char *temp_fn;			/* temp queue filename */
    FILE *f;				/* open file */
    char *user;				/* expanded username */
    char *save_user = NULL;		/* save old username */
    int uid;				/* uid for creating file */
    int gid;				/* gid for creating file */
#ifdef HAVE_SETEUID
    int save_gid;			/* saved gid from before setegid() */
#endif

    DEBUG1(DBG_DRIVER_HI, "tpd_appendfile called: addr = %s\n", addr->in_addr);
    priv = (struct appendfile_private *)tp->private;

    if (tp->flags & APPEND_EXPAND_USER) {
	/* expand the username exactly once, if required */
	user = expand_string(addr->next_addr, addr,
			     (char *)NULL, (char *)NULL);
	if (user) {
	    save_user = addr->next_addr;
	    addr->next_addr = COPY_STRING(user);
	} else {
	    /*
	     * ERR_128 - username expansion failed
	     *
	     * DESCRIPTION
	     *      The expand_user attribute is set for the appendfile
	     *      driver but, the expand_string() encountered an error in
	     *      expansion.
	     *
	     * ACTIONS
	     *      The input addresses are failed and an error is sent to
	     *      the address owner or to the postmaster.  The parent
	     *      address is reported (if one exists), as it may be
	     *      significant.
	     *
	     * RESOLUTION
	     *      This is generally useful only for file-form addresses.
	     *      Thus, the errant address should be tracked down and
	     *      corrected.
	     */
	    register struct error *er;
	    register char *s;

	    if (addr->parent) {
		s = xprintf("transport %s: could not expand user (parent %s)",
			    tp->name,
			    addr->parent->in_addr);
	    } else {
		s = xprintf("transport %s: could not expand user",
			    tp->name);
	    }
	    er = note_error(ERR_NPOWNER|ERR_128, s);

	    insert_addr_list(addr, fail, er);
	    return;
	}
    }

    if (tp->flags & APPEND_CHECK_USER) {
	/*
	 * make sure the username can't cause an expansion to move to
	 * another directory
	 */
	if (index(user, '/')) {
	    /*
	     * ERR_129 - username contains /
	     *
	     * DESCRIPTION
	     *      If the check_user attribute is set, the `/' character is
	     *      illegal in usernames, as it can cause files to be
	     *      accessed in other than the configured directory.
	     *
	     * ACTIONS
	     *      The address is failed and an error is sent to the address
	     *      owner or to the sender.
	     *
	     * RESOLUTION
	     *      The sender or owner should ensure that any addresses
	     *      sent to this host do not contain the / character.
	     */
	    register struct error *er;

	    er = note_error(ERR_NSOWNER|ERR_129,
			    xprintf("transport %s: username contains /",
				    tp->name));
	    insert_addr_list(addr, fail, er);
	    return;
	}
    }

    /* build the expanded file name */
    if (priv->file) {
	fn = expand_string(priv->file, addr, (char *)NULL, (char *)NULL);
    } else if (priv->dir) {
	fn = expand_string(priv->dir, addr, (char *)NULL, (char *)NULL);
    } else {
	/*
	 * ERR_130 - file or dir attribute required
	 *
	 * DESCRIPTION
	 *      A file or dir attribute is required for transports that use
	 *      the appendfile attribute.
	 *
	 * ACTIONS
	 *      The message is deferred with a configuration error.
	 *
	 * RESOLUTION
	 *      The transport file should be corrected to specify a file or
	 *      directory for the transport entry.
	 */
	register struct error *er;

	er = note_error(ERR_CONFERR|ERR_130,
			xprintf("transport %s: file or dir attribute required",
				tp->name));
	insert_addr_list(addr, defer, er);
	return;
    }
    if (save_user) {
	/* restore the unexpanded username */
	xfree(addr->next_addr);
	addr->next_addr = save_user;
    }
    if (fn == NULL) {
	/*
	 * ERR_131 - failed to expand file or dir
	 *
	 * DESCRIPTION
	 *      Failed to expand the file or dir attribute into the
	 *      destination file or directory.
	 *
	 * ACTIONS
	 *      Defer the message with a configuration error.
	 *
	 * RESOLUTION
	 *      The postmaster should correct the transport entry to ensure
	 *      that the file or dir attribute is always expandable.
	 */
	register struct error *er;

	er = note_error(ERR_CONFERR|ERR_131,
			xprintf("transport %s: failed to expand %s",
				tp->name, priv->file));
	insert_addr_list(addr, defer, er);
	return;
    }
    if (fn[0] != '/') {
	/*
	 * ERR_132 - appendfile pathname not absolute
	 *
	 * DESCRIPTION
	 *      The file or directory name to which the message is to be
	 *      deliveried is a relative path.  This does not make sense in
	 *      the mailer.
	 *
	 * ACTIONS
	 *      This is most likely a configuration error, as it should not
	 *      be possible for a relative file-form address to be produced,
	 *      thus defer the message with a configuration error.
	 *
	 * RESOLUTION
	 *      Most likely, the transport file entry needs to be repaired
	 *      to ensure that the file or dir attribute yields an absolute
	 *      pathname.
	 */
	register struct error *er;

	er = note_error(ERR_CONFERR|ERR_132,
			xprintf("transport %s: pathname not absolute",
				tp->name));
	insert_addr_list(addr, defer, er);
	return;
    }

    /* get the user id and group id */
    get_appendfile_ugid(tp, addr, &uid, &gid);

    if (dont_deliver) {
	insert_addr_list(addr, succeed, (struct error *)NULL);
	return;
    }

#ifdef HAVE_SETEUID
    /*
     * If we are using NFS, then assume that the seteuid call is available
     * and become the desired user here.  It is not enough to merely be
     * the desired user when opening the file (what a pain!).
     */
    /* order is important here */
    save_gid = getegid();
    (void) setegid(gid);
    (void) seteuid(uid);

    f = NULL;
    if (priv->file) {
	int fd;

	/* open and lock the mailbox file */
	temp_fn = COPY_STRING(fn);	/* copy expanded string */
	fd = open(temp_fn, O_WRONLY|O_APPEND|O_CREAT, priv->mode);
	if (fd >= 0) {
	    f = fdopen(fd, "a");
	}
    } else {
	int fd;

	temp_fn = xprintf("%s/temp.%d", fn, getpid());
	fd = open(temp_fn, O_WRONLY|O_CREAT, priv->mode);
	if (fd >= 0) {
	    f = fdopen(fd, "a");
	}
    }
#else	/* not HAVE_SETEUID */
    if (priv->file) {
	/* open and lock the mailbox file */
	temp_fn = COPY_STRING(fn);	/* copy expanded string */
	f = fopen_as_user(fn, "a", (tp->flags & APPEND_CHECK_PATH) != 0,
			  uid, gid, priv->mode);
    } else {
	fn = COPY_STRING(fn);
	temp_fn = xprintf("%s/temp.%d", fn, getpid());
	f = fopen_as_user(temp_fn, "w", (tp->flags & APPEND_CHECK_PATH) != 0,
			  uid, gid, priv->mode);
    }
#endif	/* not HAVE_SETEUID */
    if (f == NULL) {
	/*
	 * ERR_133 - appendfile failed to open file
	 *
	 * DESCRIPTION
	 *      The appendfile driver failed to open the output file.  The
	 *      reason for failure should be in errno.
	 *
	 * ACTIONS
	 *      Send a message to the address owner or the postmaster.  This
	 *      is not the kind of error the sender should have to deal
	 *      with.
	 *
	 * RESOLUTION
	 *      The pathnames produced by the transport from the input
	 *      address should be checked against the filesystem and
	 *      permissions (including directory search path permissions).
	 *      Keep in mind that opening a file is done within the context
	 *      of a particular user, possibly the nobody user, and that all
	 *      directories up to that point must be searchable by that user
	 *      or group, and that the last directory may need to be
	 *      writable.
	 */
	register struct error *er;

	er = note_error(ERR_NPOWNER|ERR_133,
			xprintf("transport %s: failed to open output file: %s",
				tp->name, strerrno()));
	insert_addr_list(addr, fail, er);
	xfree(temp_fn);			/* no longer need filename */
	if (priv->file == NULL) {
	    xfree(fn);
	}

#ifdef HAVE_SETEUID
	/* order is important here */
	(void) seteuid(0);
	(void) setegid(save_gid);
#endif

	return;
    }

    /* lock necessary only when appending */
    if (priv->file && lock_file(temp_fn, f) == FAIL) {
	/*
	 * ERR_134 - failed to lock mailbox
	 *
	 * DESCRIPTION
	 *      lock_file() was unable to lock the output file, possibly
	 *      within a timeout interval.  Note that any lock file created
	 *      by lock_file() is created within the context of the user
	 *      smail is running as, not as the user that is used in opening
	 *      the mailbox file itself.
	 *
	 * ACTIONS
	 *      The error is recoverable by just attempting delivery at a
	 *      later time when, perhaps, the lock_file() attemp succeeds.
	 *	Thus, delivery to the input addresses is deferred.
	 *
	 * RESOLUTION
	 *      Hopefully, a later attempt at delivery will succeed.
	 */
	register struct error *er;

	er = note_error(ERR_134,
			xprintf("transport %s: failed to lock mailbox",
				tp->name));
	insert_addr_list(addr, defer, er);
	(void) fclose(f);
	xfree(temp_fn);
	if (priv->file == NULL) {
	    xfree(fn);
	}

#ifdef HAVE_SETEUID
	/* order is important here */
	(void) seteuid(0);
	(void) setegid(save_gid);
#endif

	return;
    }

    (void) lseek(fileno(f), 0L, 2);

    /* write out the message */
    if (priv->prefix && fputs(priv->prefix, f) == EOF ||
	write_message(f, tp, addr) != SUCCEED ||
	priv->suffix && fputs(priv->suffix, f) == EOF ||
	fflush(f) == EOF)
    {
	/*
	 * ERR_135 - write to mailbox failed
	 *
	 * DESCRIPTION
	 *      A write to the mailbox file failed.  This is unusual, unless
	 *      the filesystem is full.
	 *
	 * ACTIONS
	 *      As this probably means the filesystem is temporarily full,
	 *      defer delivery to the input addresses and attempt delivery
	 *      later when, hopefully, the filesystem has gained some space.
	 *      Unfortunately, the mailbox file may contain only part of a
	 *      message, which is undesirable though difficult to prevent.
	 *
	 * RESOLUTION
	 *      Hopefully, a later attempt to write the file will succeed.
	 */
	register struct error *er;

	er = note_error(ERR_135,
			xprintf("transport %s: write to mailbox failed: %s",
				tp->name, strerrno()));
	insert_addr_list(addr, defer, er);
	xfree(temp_fn);
	(void) fclose(f);
	if (priv->file == NULL) {
	    xfree(fn);
	}

#ifdef HAVE_SETEUID
	/* order is important here */
	(void) seteuid(0);
	(void) setegid(save_gid);
#endif

	return;
    }

    if (priv->file) {
	/* unlock and close the file */
	unlock_file(temp_fn, f);
	(void) fclose(f);
    } else {
	/*
	 * move the queue file to a permanent name the name
	 * is formed from the clock time plus the inode number,
	 * all in ASCII base 62
	 *
	 *    "q"tttttt-iiiiii
	 */
	char *a_inode;
	char *perm_fn;
	struct stat statbuf;

	(void) fstat(fileno(f), &statbuf);
	a_inode = COPY_STRING(base62((long)statbuf.st_ino));
	perm_fn = xprintf("%s/q%s-%s", fn,
			  base62(statbuf.st_atime),
			  a_inode);
	(void) fclose(f);
	if (rename(temp_fn, perm_fn) < 0) {
	    /*
	     * ERR_136 - rename failed for queue file
	     *
	     * DESCRIPTION
	     *      rename() failed to move the output file to its final
	     *      name.  This is an unusual problem, since a write already
	     *      succeeded to the directory and a rename() requires only
	     *      write access to the directory.
	     *
	     * ACTIONS
	     *      Fail the address and send an error to the postmaster.
	     *      This is not likely to be an error that an address owner
	     *      can deal with more effectively than the site
	     *      administrator, so don't bother sending to any owners.
	     *
	     * RESOLUTION
	     *      Suggestions are a full filesystem that could not handle
	     *      a small expansion in the size of a directory, or
	     *      possibly, a chmod() or directory rename() occured at
	     *      just the wrong time.
	     */
	    register struct error *er;

	    er = note_error(ERR_NPOSTMAST|ERR_136,
		      xprintf("transport %s: rename failed for queue file: %s",
			      tp->name, strerrno()));
	    insert_addr_list(addr, fail, er);
	    xfree(temp_fn);
	    xfree(perm_fn);

#ifdef HAVE_SETEUID
	/* order is important here */
	(void) seteuid(0);
	(void) setegid(save_gid);
#endif

	    return;
	}
	xfree(perm_fn);
	xfree(fn);
    }

    /* everything went okay, link into the succeed list */
    insert_addr_list(addr, succeed, (struct error *)NULL);
    xfree(temp_fn);			/* free temporary storage */

#ifdef HAVE_SETEUID
	/* order is important here */
	(void) seteuid(0);
	(void) setegid(save_gid);
#endif

    return;
}

/*
 * tpb_appendfile - read the configuration file attributes
 */
char *
tpb_appendfile(tp, attrs)
    struct transport *tp;		/* transport entry being defined */
    struct attribute *attrs;		/* list of per-driver attributes */
{
    char *error;
    static struct attr_table appendfile_attributes[] = {
	{ "file", t_string, NULL, NULL, OFFSET(appendfile_private, file) },
	{ "dir", t_string, NULL, NULL, OFFSET(appendfile_private, dir) },
	{ "user", t_string, NULL, NULL, OFFSET(appendfile_private, user) },
	{ "group", t_string, NULL, NULL, OFFSET(appendfile_private, group) },
	{ "prefix", t_string, NULL, NULL, OFFSET(appendfile_private, prefix) },
	{ "suffix", t_string, NULL, NULL, OFFSET(appendfile_private, suffix) },
	{ "mode", t_int, NULL, NULL, OFFSET(appendfile_private, mode) },
	{ "append_as_user", t_boolean, NULL, NULL, APPEND_AS_USER },
	{ "expand_user", t_boolean, NULL, NULL, APPEND_EXPAND_USER },
	{ "check_path", t_boolean, NULL, NULL, APPEND_CHECK_PATH },
	{ "check_user", t_boolean, NULL, NULL, APPEND_CHECK_USER },
    };
    static struct attr_table *end_appendfile_attributes =
	ENDTABLE(appendfile_attributes);
    static struct appendfile_private appendfile_template = {
	NULL,				/* file */
	NULL,				/* dir */
	NULL,				/* user */
	NULL,				/* group */
	NULL,				/* prefix */
	NULL,				/* suffix */
	0666,				/* mode */
    };
    struct appendfile_private *priv;	/* new appendfile_private structure */

    /* copy the template private data */
    priv = (struct appendfile_private *)xmalloc(sizeof(*priv));
    (void) memcpy((char *)priv, (char *)&appendfile_template, sizeof(*priv));

    tp->private = (char *)priv;

    /* set default flags */
    tp->flags |= APPEND_AS_USER;

    /* fill in the attributes of the private data */
    error = fill_attributes((char *)priv,
			    attrs,
			    &tp->flags,
			    appendfile_attributes,
			    end_appendfile_attributes);

    if (error) {
	return error;
    }
    return NULL;
}

/*
 * get_appendfile_ugid - return the uid and gid to use in creating file
 */
static void
get_appendfile_ugid(tp, addr, uid, gid)
    struct transport *tp;		/* associated transport structure */
    struct addr *addr;			/* associated addr structures */
    int *uid;				/* store uid here */
    int *gid;				/* store gid here */
{
    struct appendfile_private *priv = (struct appendfile_private *)tp->private;
    /*
     * determine the uid to use for delivery
     */
    if (priv->user == NULL) {
	if ((tp->flags & APPEND_AS_USER) && addr->uid != BOGUS_USER) {
	    *uid = addr->uid;
	} else {
	    *uid = nobody_uid;
	}
    } else {
	struct passwd *pw = getpwbyname(priv->user);

	if (pw == NULL) {
	    write_log(LOG_PANIC,
		      "transport %s: warning: user %s unknown, using nobody",
		      tp->name, priv->user);
	    priv->user = NULL;
	    *uid = nobody_uid;
	} else {
	    *uid = pw->pw_uid;
	    *gid = pw->pw_gid;
	}
    }

    /* determine the gid for use for delivery */
    if (priv->group) {
	struct group *gr = getgrbyname(priv->group);

	if (gr == NULL) {
	    write_log(LOG_PANIC,
		      "transport %s: warning: group %s unknown, ignored",
		      tp->name, priv->group);
	    priv->group = NULL;
	} else {
	    *gid = gr->gr_gid;
	}
    }
    if (priv->group == NULL) {
	if (priv->user == NULL) {
	    if ((tp->flags & APPEND_AS_USER) && addr->gid != BOGUS_GROUP) {
		*gid = addr->gid;
	    } else {
		*gid = nobody_gid;
	    }
	}
    }
}

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