diff options
Diffstat (limited to 'src/global/mbox_open.c')
-rw-r--r-- | src/global/mbox_open.c | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/src/global/mbox_open.c b/src/global/mbox_open.c new file mode 100644 index 0000000..b38424a --- /dev/null +++ b/src/global/mbox_open.c @@ -0,0 +1,256 @@ +/*++ +/* NAME +/* mbox_open 3 +/* SUMMARY +/* mailbox access +/* SYNOPSIS +/* #include <mbox_open.h> +/* +/* typedef struct { +/* .in +4 +/* /* public members... */ +/* VSTREAM *fp; +/* .in -4 +/* } MBOX; +/* +/* MBOX *mbox_open(path, flags, mode, st, user, group, lock_style, +/* def_dsn, why) +/* const char *path; +/* int flags; +/* mode_t mode; +/* struct stat *st; +/* uid_t user; +/* gid_t group; +/* int lock_style; +/* const char *def_dsn; +/* DSN_BUF *why; +/* +/* void mbox_release(mbox) +/* MBOX *mbox; +/* +/* const char *mbox_dsn(err, def_dsn) +/* int err; +/* const char *def_dsn; +/* DESCRIPTION +/* This module manages access to UNIX mailbox-style files. +/* +/* mbox_open() acquires exclusive access to the named file. +/* The \fBpath, flags, mode, st, user, group, why\fR arguments +/* are passed to the \fBsafe_open\fR() routine. Attempts to change +/* file ownership will succeed only if the process runs with +/* adequate effective privileges. +/* The \fBlock_style\fR argument specifies a lock style from +/* mbox_lock_mask(). Locks are applied to regular files only. +/* The result is a handle that must be destroyed by mbox_release(). +/* The \fBdef_dsn\fR argument is given to mbox_dsn(). +/* +/* mbox_release() releases the named mailbox. It is up to the +/* application to close the stream. +/* +/* mbox_dsn() translates an errno value to a mailbox related +/* enhanced status code. +/* .IP "EAGAIN, ESTALE" +/* These result in a 4.2.0 soft error (mailbox problem). +/* .IP ENOSPC +/* This results in a 4.3.0 soft error (mail system full). +/* .IP "EDQUOT, EFBIG" +/* These result in a 5.2.2 hard error (mailbox full). +/* .PP +/* All other errors are assigned the specified default error +/* code. Typically, one would specify 4.2.0 or 5.2.0. +/* DIAGNOSTICS +/* mbox_open() returns a null pointer in case of problems, and +/* sets errno to EAGAIN if someone else has exclusive access. +/* Other errors are likely to have a more permanent nature. +/* 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 +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <errno.h> + +#ifndef EDQUOT +#define EDQUOT EFBIG +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <safe_open.h> +#include <iostuff.h> +#include <mymalloc.h> +#include <warn_stat.h> + +/* Global library. */ + +#include <dot_lockfile.h> +#include <deliver_flock.h> +#include <mbox_conf.h> +#include <mbox_open.h> + +/* mbox_open - open mailbox-style file for exclusive access */ + +MBOX *mbox_open(const char *path, int flags, mode_t mode, struct stat * st, + uid_t chown_uid, gid_t chown_gid, + int lock_style, const char *def_dsn, + DSN_BUF *why) +{ + struct stat local_statbuf; + MBOX *mp; + int locked = 0; + VSTREAM *fp; + + if (st == 0) + st = &local_statbuf; + + /* + * If this is a regular file, create a dotlock file. This locking method + * does not work well over NFS, but it is better than some alternatives. + * With NFS, creating files atomically is a problem, and a successful + * operation can fail with EEXIST. + * + * If filename.lock can't be created for reasons other than "file exists", + * issue only a warning if the application says it is non-fatal. This is + * for bass-awkward compatibility with existing installations that + * deliver to files in non-writable directories. + * + * We dot-lock the file before opening, so we must avoid doing silly things + * like dot-locking /dev/null. Fortunately, deliveries to non-mailbox + * files execute with recipient privileges, so we don't have to worry + * about creating dotlock files in places where the recipient would not + * be able to write. + * + * Note: we use stat() to follow symlinks, because safe_open() allows the + * target to be a root-owned symlink, and we don't want to create dotlock + * files for /dev/null or other non-file objects. + */ + if ((lock_style & MBOX_DOT_LOCK) + && (stat(path, st) < 0 || S_ISREG(st->st_mode))) { + if (dot_lockfile(path, why->reason) == 0) { + locked |= MBOX_DOT_LOCK; + } else if (errno == EEXIST) { + dsb_status(why, mbox_dsn(EAGAIN, def_dsn)); + return (0); + } else if (lock_style & MBOX_DOT_LOCK_MAY_FAIL) { + msg_warn("%s", vstring_str(why->reason)); + } else { + dsb_status(why, mbox_dsn(errno, def_dsn)); + return (0); + } + } + + /* + * Open or create the target file. In case of a privileged open, the + * privileged user may be attacked with hard/soft link tricks in an + * unsafe parent directory. In case of an unprivileged open, the mail + * system may be attacked by a malicious user-specified path, or the + * unprivileged user may be attacked with hard/soft link tricks in an + * unsafe parent directory. Open non-blocking to fend off attacks + * involving non-file targets. + */ + if ((fp = safe_open(path, flags | O_NONBLOCK, mode, st, + chown_uid, chown_gid, why->reason)) == 0) { + dsb_status(why, mbox_dsn(errno, def_dsn)); + if (locked & MBOX_DOT_LOCK) + dot_unlockfile(path); + return (0); + } + close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); + + /* + * If this is a regular file, acquire kernel locks. flock() locks are not + * intended to work across a network; fcntl() locks are supposed to work + * over NFS, but in the real world, NFS lock daemons often have serious + * problems. + */ +#define HUNKY_DORY(lock_mask, myflock_style) ((lock_style & (lock_mask)) == 0 \ + || deliver_flock(vstream_fileno(fp), (myflock_style), why->reason) == 0) + + if (S_ISREG(st->st_mode)) { + if (HUNKY_DORY(MBOX_FLOCK_LOCK, MYFLOCK_STYLE_FLOCK) + && HUNKY_DORY(MBOX_FCNTL_LOCK, MYFLOCK_STYLE_FCNTL)) { + locked |= lock_style; + } else { + dsb_status(why, mbox_dsn(errno, def_dsn)); + if (locked & MBOX_DOT_LOCK) + dot_unlockfile(path); + vstream_fclose(fp); + return (0); + } + } + + /* + * Sanity check: reportedly, GNU POP3D creates a new mailbox file and + * deletes the old one. This does not play well with software that opens + * the mailbox first and then locks it, such as software that uses FCNTL + * or FLOCK locks on open file descriptors (some UNIX systems don't use + * dotlock files). + * + * To detect that GNU POP3D deletes the mailbox file we look at the target + * file hard-link count. Note that safe_open() guarantees a hard-link + * count of 1, so any change in this count is a sign of trouble. + */ + if (S_ISREG(st->st_mode) + && (fstat(vstream_fileno(fp), st) < 0 || st->st_nlink != 1)) { + vstring_sprintf(why->reason, "target file status changed unexpectedly"); + dsb_status(why, mbox_dsn(EAGAIN, def_dsn)); + msg_warn("%s: file status changed unexpectedly", path); + if (locked & MBOX_DOT_LOCK) + dot_unlockfile(path); + vstream_fclose(fp); + return (0); + } + mp = (MBOX *) mymalloc(sizeof(*mp)); + mp->path = mystrdup(path); + mp->fp = fp; + mp->locked = locked; + return (mp); +} + +/* mbox_release - release mailbox exclusive access */ + +void mbox_release(MBOX *mp) +{ + + /* + * Unfortunately we can't close the stream, because on some file systems + * (AFS), the only way to find out if a file was written successfully is + * to close it, and therefore the close() operation is in the mail_copy() + * routine. If we really insist on owning the vstream member, then we + * should export appropriate methods that mail_copy() can use in order to + * manipulate a message stream. + */ + if (mp->locked & MBOX_DOT_LOCK) + dot_unlockfile(mp->path); + myfree(mp->path); + myfree((void *) mp); +} + +/* mbox_dsn - map errno value to mailbox-related DSN detail */ + +const char *mbox_dsn(int err, const char *def_dsn) +{ +#define TRY_AGAIN_ERROR(e) \ + (e == EAGAIN || e == ESTALE) +#define SYSTEM_FULL_ERROR(e) \ + (e == ENOSPC) +#define MBOX_FULL_ERROR(e) \ + (e == EDQUOT || e == EFBIG) + + return (TRY_AGAIN_ERROR(err) ? "4.2.0" : + SYSTEM_FULL_ERROR(err) ? "4.3.0" : + MBOX_FULL_ERROR(err) ? "5.2.2" : + def_dsn); +} |