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