|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T a
Length: 19890 (0x4db2)
Types: TextFile
Names: »appendfile.c«
└─⟦9ae75bfbd⟧ Bits:30007242 EUUGD3: Starter Kit
└─⟦2fafebccf⟧ »EurOpenD3/mail/smail3.1.19.tar.Z«
└─⟦bcd2bc73f⟧
└─⟦this⟧ »src/transports/appendfile.c«
/* @(#)appendfile.c 3.25 10/20/89 16:05:47 */
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*
* namei master id: @(#)appendfile.c 3.25 10/20/89 16:05:47
*/
/*
* 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();
\f
/*
* 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;
}
}
}
}