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.