diff options
Diffstat (limited to 'src/local/mailbox.c')
-rw-r--r-- | src/local/mailbox.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/src/local/mailbox.c b/src/local/mailbox.c new file mode 100644 index 0000000..ed55291 --- /dev/null +++ b/src/local/mailbox.c @@ -0,0 +1,373 @@ +/*++ +/* NAME +/* mailbox 3 +/* SUMMARY +/* mailbox delivery +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_mailbox(state, usr_attr, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* int *statusp; +/* DESCRIPTION +/* deliver_mailbox() delivers to mailbox, with duplicate +/* suppression. The default is direct mailbox delivery to +/* /var/[spool/]mail/\fIuser\fR; when a \fIhome_mailbox\fR +/* has been configured, mail is delivered to ~/$\fIhome_mailbox\fR; +/* and when a \fImailbox_command\fR has been configured, the message +/* is piped into the command instead. +/* +/* A zero result means that the named user was not found. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP statusp +/* Delivery status: see below. +/* DIAGNOSTICS +/* The message delivery status is non-zero when delivery should be tried +/* again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <htable.h> +#include <vstring.h> +#include <vstream.h> +#include <mymalloc.h> +#include <stringops.h> +#include <set_eugid.h> +#include <warn_stat.h> + +/* Global library. */ + +#include <mail_copy.h> +#include <defer.h> +#include <sent.h> +#include <mypwd.h> +#include <been_here.h> +#include <mail_params.h> +#include <deliver_pass.h> +#include <mbox_open.h> +#include <maps.h> +#include <dsn_util.h> + +/* Application-specific. */ + +#include "local.h" +#include "biff_notify.h" + +#define YES 1 +#define NO 0 + +/* deliver_mailbox_file - deliver to recipient mailbox */ + +static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_mailbox_file"; + char *spool_dir; + char *mailbox; + DSN_BUF *why = state.msg_attr.why; + MBOX *mp; + int mail_copy_status; + int deliver_status; + int copy_flags; + VSTRING *biff; + off_t end; + struct stat st; + uid_t spool_uid; + gid_t spool_gid; + uid_t chown_uid; + gid_t chown_gid; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to mailbox"); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + mail_copy_status = MAIL_COPY_STAT_WRITE; + if (*var_home_mailbox) { + spool_dir = 0; + mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); + } else { + spool_dir = var_mail_spool_dir; + mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0); + } + + /* + * Mailbox delivery with least privilege. As long as we do not use root + * privileges this code may also work over NFS. + * + * If delivering to the recipient's home directory, perform all operations + * (including file locking) as that user (Mike Muuss, Army Research + * Laboratory, USA). + * + * If delivering to the mail spool directory, and the spool directory is + * world-writable, deliver as the recipient; if the spool directory is + * group-writable, use the recipient user id and the mail spool group id. + * + * Otherwise, use root privileges and chown the mailbox if we create it. + */ + if (spool_dir == 0 + || stat(spool_dir, &st) < 0 + || (st.st_mode & S_IWOTH) != 0) { + spool_uid = usr_attr.uid; + spool_gid = usr_attr.gid; + } else if ((st.st_mode & S_IWGRP) != 0) { + spool_uid = usr_attr.uid; + spool_gid = st.st_gid; + } else { + spool_uid = 0; + spool_gid = 0; + } + if (spool_uid == usr_attr.uid) { + chown_uid = -1; + chown_gid = -1; + } else { + chown_uid = usr_attr.uid; + chown_gid = usr_attr.gid; + } + if (msg_verbose) + msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld", + (long) spool_uid, (long) spool_gid, + (long) chown_uid, (long) chown_gid); + + /* + * Lock the mailbox and open/create the mailbox file. Depending on the + * type of locking used, we lock first or we open first. + * + * Write the file as the recipient, so that file quota work. + */ + copy_flags = MAIL_COPY_MBOX; + if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) + copy_flags &= ~MAIL_COPY_DELIVERED; + + set_eugid(spool_uid, spool_gid); + mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT, + S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid, + local_mbox_lock_mask, "5.2.0", why); + if (mp != 0) { + if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) + set_eugid(usr_attr.uid, usr_attr.gid); + if (S_ISREG(st.st_mode) == 0) { + vstream_fclose(mp->fp); + dsb_simple(why, "5.2.0", + "destination %s is not a regular file", mailbox); + } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { + vstream_fclose(mp->fp); + dsb_simple(why, "4.2.0", + "destination %s is not owned by recipient", mailbox); + msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", + VAR_STRICT_MBOX_OWNER); + } else { + if ((end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END)) < 0) + msg_fatal("seek mailbox file %s: %m", mailbox); + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + copy_flags, "\n", why); + } + if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) + set_eugid(spool_uid, spool_gid); + mbox_release(mp); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce, defer delivery, or report success. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + vstring_sprintf_prepend(why->reason, + "cannot update mailbox %s for user %s. ", + mailbox, state.msg_attr.user); + deliver_status = + (STR(why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); + } else { + dsb_simple(why, "2.0.0", "delivered to mailbox"); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + if (var_biff) { + biff = vstring_alloc(100); + vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end); + biff_notify(STR(biff), VSTRING_LEN(biff) + 1); + vstring_free(biff); + } + } + + /* + * Clean up. + */ + myfree(mailbox); + return (deliver_status); +} + +/* deliver_mailbox - deliver to recipient mailbox */ + +int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) +{ + const char *myname = "deliver_mailbox"; + int status; + struct mypasswd *mbox_pwd; + char *path; + static MAPS *transp_maps; + const char *map_transport; + static MAPS *cmd_maps; + const char *map_command; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Don't come here more than once, whether or not the recipient exists. + */ + if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local)) + return (YES); + + /* + * Delegate mailbox delivery to another message transport. + */ + if (*var_mbox_transp_maps && transp_maps == 0) + transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps, + DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); + /* The -1 is a hint for the down-stream deliver_completed() function. */ + if (transp_maps + && (map_transport = maps_find(transp_maps, state.msg_attr.user, + DICT_FLAG_NONE)) != 0) { + state.msg_attr.rcpt.offset = -1L; + *statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport, + state.request, &state.msg_attr.rcpt); + return (YES); + } else if (transp_maps && transp_maps->error != 0) { + /* Details in the logfile. */ + dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (*var_mailbox_transport) { + state.msg_attr.rcpt.offset = -1L; + *statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport, + state.request, &state.msg_attr.rcpt); + return (YES); + } + + /* + * Skip delivery when this recipient does not exist. + */ + if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 0) { + msg_warn("error looking up passwd info for %s: %m", + state.msg_attr.user); + dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (mbox_pwd == 0) + return (NO); + + /* + * No early returns or we have a memory leak. + */ + + /* + * DELIVERY RIGHTS + * + * Use the rights of the recipient user. + */ + SET_USER_ATTR(usr_attr, mbox_pwd, state.level); + + /* + * Deliver to mailbox, maildir or to external command. + */ +#define LAST_CHAR(s) (s[strlen(s) - 1]) + + if (*var_mailbox_cmd_maps && cmd_maps == 0) + cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps, + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); + + if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user, + DICT_FLAG_NONE)) != 0) { + status = deliver_command(state, usr_attr, map_command); + } else if (cmd_maps && cmd_maps->error != 0) { + /* Details in the logfile. */ + dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); + status = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (*var_mailbox_command) { + status = deliver_command(state, usr_attr, var_mailbox_command); + } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') { + path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); + status = deliver_maildir(state, usr_attr, path); + myfree(path); + } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') { + path = concatenate(var_mail_spool_dir, state.msg_attr.user, + "/", (char *) 0); + status = deliver_maildir(state, usr_attr, path); + myfree(path); + } else + status = deliver_mailbox_file(state, usr_attr); + + /* + * Cleanup. + */ + mypwfree(mbox_pwd); + *statusp = status; + return (YES); +} |