1/*++ 2/* NAME 3/* mbox_open 3 4/* SUMMARY 5/* mailbox access 6/* SYNOPSIS 7/* #include <mbox_open.h> 8/* 9/* typedef struct { 10/* .in +4 11/* /* public members... */ 12/* VSTREAM *fp; 13/* .in -4 14/* } MBOX; 15/* 16/* MBOX *mbox_open(path, flags, mode, st, user, group, lock_style, 17/* def_dsn, why) 18/* const char *path; 19/* int flags; 20/* mode_t mode; 21/* struct stat *st; 22/* uid_t user; 23/* gid_t group; 24/* int lock_style; 25/* const char *def_dsn; 26/* DSN_BUF *why; 27/* 28/* void mbox_release(mbox) 29/* MBOX *mbox; 30/* 31/* const char *mbox_dsn(err, def_dsn) 32/* int err; 33/* const char *def_dsn; 34/* DESCRIPTION 35/* This module manages access to UNIX mailbox-style files. 36/* 37/* mbox_open() acquires exclusive access to the named file. 38/* The \fBpath, flags, mode, st, user, group, why\fR arguments 39/* are passed to the \fBsafe_open\fR() routine. Attempts to change 40/* file ownership will succeed only if the process runs with 41/* adequate effective privileges. 42/* The \fBlock_style\fR argument specifies a lock style from 43/* mbox_lock_mask(). Locks are applied to regular files only. 44/* The result is a handle that must be destroyed by mbox_release(). 45/* The \fBdef_dsn\fR argument is given to mbox_dsn(). 46/* 47/* mbox_release() releases the named mailbox. It is up to the 48/* application to close the stream. 49/* 50/* mbox_dsn() translates an errno value to a mailbox related 51/* enhanced status code. 52/* .IP "EAGAIN, ESTALE" 53/* These result in a 4.2.0 soft error (mailbox problem). 54/* .IP ENOSPC 55/* This results in a 4.3.0 soft error (mail system full). 56/* .IP "EDQUOT, EFBIG" 57/* These result in a 5.2.2 hard error (mailbox full). 58/* .PP 59/* All other errors are assigned the specified default error 60/* code. Typically, one would specify 4.2.0 or 5.2.0. 61/* DIAGNOSTICS 62/* mbox_open() returns a null pointer in case of problems, and 63/* sets errno to EAGAIN if someone else has exclusive access. 64/* Other errors are likely to have a more permanent nature. 65/* LICENSE 66/* .ad 67/* .fi 68/* The Secure Mailer license must be distributed with this software. 69/* AUTHOR(S) 70/* Wietse Venema 71/* IBM T.J. Watson Research 72/* P.O. Box 704 73/* Yorktown Heights, NY 10598, USA 74/*--*/ 75 76/* System library. */ 77 78#include <sys_defs.h> 79#include <sys/stat.h> 80#include <errno.h> 81 82#ifndef EDQUOT 83#define EDQUOT EFBIG 84#endif 85 86/* Utility library. */ 87 88#include <msg.h> 89#include <vstream.h> 90#include <vstring.h> 91#include <safe_open.h> 92#include <iostuff.h> 93#include <mymalloc.h> 94#include <warn_stat.h> 95 96/* Global library. */ 97 98#include <dot_lockfile.h> 99#include <deliver_flock.h> 100#include <mbox_conf.h> 101#include <mbox_open.h> 102 103/* mbox_open - open mailbox-style file for exclusive access */ 104 105MBOX *mbox_open(const char *path, int flags, mode_t mode, struct stat * st, 106 uid_t chown_uid, gid_t chown_gid, 107 int lock_style, const char *def_dsn, 108 DSN_BUF *why) 109{ 110 struct stat local_statbuf; 111 MBOX *mp; 112 int locked = 0; 113 VSTREAM *fp; 114 115 if (st == 0) 116 st = &local_statbuf; 117 118 /* 119 * If this is a regular file, create a dotlock file. This locking method 120 * does not work well over NFS, but it is better than some alternatives. 121 * With NFS, creating files atomically is a problem, and a successful 122 * operation can fail with EEXIST. 123 * 124 * If filename.lock can't be created for reasons other than "file exists", 125 * issue only a warning if the application says it is non-fatal. This is 126 * for bass-awkward compatibility with existing installations that 127 * deliver to files in non-writable directories. 128 * 129 * We dot-lock the file before opening, so we must avoid doing silly things 130 * like dot-locking /dev/null. Fortunately, deliveries to non-mailbox 131 * files execute with recipient privileges, so we don't have to worry 132 * about creating dotlock files in places where the recipient would not 133 * be able to write. 134 * 135 * Note: we use stat() to follow symlinks, because safe_open() allows the 136 * target to be a root-owned symlink, and we don't want to create dotlock 137 * files for /dev/null or other non-file objects. 138 */ 139 if ((lock_style & MBOX_DOT_LOCK) 140 && (stat(path, st) < 0 || S_ISREG(st->st_mode))) { 141 if (dot_lockfile(path, why->reason) == 0) { 142 locked |= MBOX_DOT_LOCK; 143 } else if (errno == EEXIST) { 144 dsb_status(why, mbox_dsn(EAGAIN, def_dsn)); 145 return (0); 146 } else if (lock_style & MBOX_DOT_LOCK_MAY_FAIL) { 147 msg_warn("%s", vstring_str(why->reason)); 148 } else { 149 dsb_status(why, mbox_dsn(errno, def_dsn)); 150 return (0); 151 } 152 } 153 154 /* 155 * Open or create the target file. In case of a privileged open, the 156 * privileged user may be attacked with hard/soft link tricks in an 157 * unsafe parent directory. In case of an unprivileged open, the mail 158 * system may be attacked by a malicious user-specified path, or the 159 * unprivileged user may be attacked with hard/soft link tricks in an 160 * unsafe parent directory. Open non-blocking to fend off attacks 161 * involving non-file targets. 162 */ 163 if ((fp = safe_open(path, flags | O_NONBLOCK, mode, st, 164 chown_uid, chown_gid, why->reason)) == 0) { 165 dsb_status(why, mbox_dsn(errno, def_dsn)); 166 if (locked & MBOX_DOT_LOCK) 167 dot_unlockfile(path); 168 return (0); 169 } 170 close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); 171 172 /* 173 * If this is a regular file, acquire kernel locks. flock() locks are not 174 * intended to work across a network; fcntl() locks are supposed to work 175 * over NFS, but in the real world, NFS lock daemons often have serious 176 * problems. 177 */ 178#define HUNKY_DORY(lock_mask, myflock_style) ((lock_style & (lock_mask)) == 0 \ 179 || deliver_flock(vstream_fileno(fp), (myflock_style), why->reason) == 0) 180 181 if (S_ISREG(st->st_mode)) { 182 if (HUNKY_DORY(MBOX_FLOCK_LOCK, MYFLOCK_STYLE_FLOCK) 183 && HUNKY_DORY(MBOX_FCNTL_LOCK, MYFLOCK_STYLE_FCNTL)) { 184 locked |= lock_style; 185 } else { 186 dsb_status(why, mbox_dsn(errno, def_dsn)); 187 if (locked & MBOX_DOT_LOCK) 188 dot_unlockfile(path); 189 vstream_fclose(fp); 190 return (0); 191 } 192 } 193 194 /* 195 * Sanity check: reportedly, GNU POP3D creates a new mailbox file and 196 * deletes the old one. This does not play well with software that opens 197 * the mailbox first and then locks it, such as software that uses FCNTL 198 * or FLOCK locks on open file descriptors (some UNIX systems don't use 199 * dotlock files). 200 * 201 * To detect that GNU POP3D deletes the mailbox file we look at the target 202 * file hard-link count. Note that safe_open() guarantees a hard-link 203 * count of 1, so any change in this count is a sign of trouble. 204 */ 205 if (S_ISREG(st->st_mode) 206 && (fstat(vstream_fileno(fp), st) < 0 || st->st_nlink != 1)) { 207 vstring_sprintf(why->reason, "target file status changed unexpectedly"); 208 dsb_status(why, mbox_dsn(EAGAIN, def_dsn)); 209 msg_warn("%s: file status changed unexpectedly", path); 210 if (locked & MBOX_DOT_LOCK) 211 dot_unlockfile(path); 212 vstream_fclose(fp); 213 return (0); 214 } 215 mp = (MBOX *) mymalloc(sizeof(*mp)); 216 mp->path = mystrdup(path); 217 mp->fp = fp; 218 mp->locked = locked; 219 return (mp); 220} 221 222/* mbox_release - release mailbox exclusive access */ 223 224void mbox_release(MBOX *mp) 225{ 226 227 /* 228 * Unfortunately we can't close the stream, because on some file systems 229 * (AFS), the only way to find out if a file was written successfully is 230 * to close it, and therefore the close() operation is in the mail_copy() 231 * routine. If we really insist on owning the vstream member, then we 232 * should export appropriate methods that mail_copy() can use in order to 233 * manipulate a message stream. 234 */ 235 if (mp->locked & MBOX_DOT_LOCK) 236 dot_unlockfile(mp->path); 237 myfree(mp->path); 238 myfree((char *) mp); 239} 240 241/* mbox_dsn - map errno value to mailbox-related DSN detail */ 242 243const char *mbox_dsn(int err, const char *def_dsn) 244{ 245#define TRY_AGAIN_ERROR(e) \ 246 (e == EAGAIN || e == ESTALE) 247#define SYSTEM_FULL_ERROR(e) \ 248 (e == ENOSPC) 249#define MBOX_FULL_ERROR(e) \ 250 (e == EDQUOT || e == EFBIG) 251 252 return (TRY_AGAIN_ERROR(err) ? "4.2.0" : 253 SYSTEM_FULL_ERROR(err) ? "4.3.0" : 254 MBOX_FULL_ERROR(err) ? "5.2.2" : 255 def_dsn); 256} 257