9 /* #include <mbox_open.h>
13 /* /* public members... */
18 /* MBOX *mbox_open(path, flags, mode, st, user, group, lock_style,
27 /* const char *def_dsn;
30 /* void mbox_release(mbox)
33 /* const char *mbox_dsn(err, def_dsn)
35 /* const char *def_dsn;
37 /* This module manages access to UNIX mailbox-style files.
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().
49 /* mbox_release() releases the named mailbox. It is up to the
50 /* application to close the stream.
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).
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).
61 /* All other errors are assigned the specified default error
62 /* code. Typically, one would specify 4.2.0 or 5.2.0.
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.
70 /* The Secure Mailer license must be distributed with this software.
73 /* IBM T.J. Watson Research
75 /* Yorktown Heights, NY 10598, USA
88 /* Utility library. */
93 #include <safe_open.h>
99 #include <dot_lockfile.h>
100 #include <deliver_flock.h>
101 #include <mbox_conf.h>
102 #include <mbox_open.h>
104 /* mbox_open - open mailbox-style file for exclusive access */
106 MBOX
*mbox_open(const char *path
, int flags
, mode_t mode
, struct stat
* st
,
107 uid_t chown_uid
, gid_t chown_gid
,
108 int lock_style
, const char *def_dsn
,
111 struct stat local_statbuf
;
120 * If this is a regular file, create a dotlock file. This locking method
121 * does not work well over NFS, but it is better than some alternatives.
122 * With NFS, creating files atomically is a problem, and a successful
123 * operation can fail with EEXIST.
125 * If filename.lock can't be created for reasons other than "file exists",
126 * issue only a warning if the application says it is non-fatal. This is
127 * for bass-awkward compatibility with existing installations that
128 * deliver to files in non-writable directories.
130 * We dot-lock the file before opening, so we must avoid doing silly things
131 * like dot-locking /dev/null. Fortunately, deliveries to non-mailbox
132 * files execute with recipient privileges, so we don't have to worry
133 * about creating dotlock files in places where the recipient would not
136 * Note: we use stat() to follow symlinks, because safe_open() allows the
137 * target to be a root-owned symlink, and we don't want to create dotlock
138 * files for /dev/null or other non-file objects.
140 if ((lock_style
& MBOX_DOT_LOCK
)
141 && (stat(path
, st
) < 0 || S_ISREG(st
->st_mode
))) {
142 if (dot_lockfile(path
, why
->reason
) == 0) {
143 locked
|= MBOX_DOT_LOCK
;
144 } else if (errno
== EEXIST
) {
145 dsb_status(why
, mbox_dsn(EAGAIN
, def_dsn
));
147 } else if (lock_style
& MBOX_DOT_LOCK_MAY_FAIL
) {
148 msg_warn("%s", vstring_str(why
->reason
));
150 dsb_status(why
, mbox_dsn(errno
, def_dsn
));
156 * Open or create the target file. In case of a privileged open, the
157 * privileged user may be attacked with hard/soft link tricks in an
158 * unsafe parent directory. In case of an unprivileged open, the mail
159 * system may be attacked by a malicious user-specified path, or the
160 * unprivileged user may be attacked with hard/soft link tricks in an
161 * unsafe parent directory. Open non-blocking to fend off attacks
162 * involving non-file targets.
164 if ((fp
= safe_open(path
, flags
| O_NONBLOCK
, mode
, st
,
165 chown_uid
, chown_gid
, why
->reason
)) == 0) {
166 dsb_status(why
, mbox_dsn(errno
, def_dsn
));
167 if (locked
& MBOX_DOT_LOCK
)
168 dot_unlockfile(path
);
171 close_on_exec(vstream_fileno(fp
), CLOSE_ON_EXEC
);
174 * If this is a regular file, acquire kernel locks. flock() locks are not
175 * intended to work across a network; fcntl() locks are supposed to work
176 * over NFS, but in the real world, NFS lock daemons often have serious
179 #define HUNKY_DORY(lock_mask, myflock_style) ((lock_style & (lock_mask)) == 0 \
180 || deliver_flock(vstream_fileno(fp), (myflock_style), why->reason) == 0)
182 if (S_ISREG(st
->st_mode
)) {
183 if (HUNKY_DORY(MBOX_FLOCK_LOCK
, MYFLOCK_STYLE_FLOCK
)
184 && HUNKY_DORY(MBOX_FCNTL_LOCK
, MYFLOCK_STYLE_FCNTL
)) {
185 locked
|= lock_style
;
187 dsb_status(why
, mbox_dsn(errno
, def_dsn
));
188 if (locked
& MBOX_DOT_LOCK
)
189 dot_unlockfile(path
);
196 * Sanity check: reportedly, GNU POP3D creates a new mailbox file and
197 * deletes the old one. This does not play well with software that opens
198 * the mailbox first and then locks it, such as software that that uses
199 * FCNTL or FLOCK locks on open file descriptors (some UNIX systems don't
200 * use dotlock files).
202 * To detect that GNU POP3D deletes the mailbox file we look at the target
203 * file hard-link count. Note that safe_open() guarantees a hard-link
204 * count of 1, so any change in this count is a sign of trouble.
206 if (S_ISREG(st
->st_mode
)
207 && (fstat(vstream_fileno(fp
), st
) < 0 || st
->st_nlink
!= 1)) {
208 vstring_sprintf(why
->reason
, "target file status changed unexpectedly");
209 dsb_status(why
, mbox_dsn(EAGAIN
, def_dsn
));
210 msg_warn("%s: file status changed unexpectedly", path
);
211 if (locked
& MBOX_DOT_LOCK
)
212 dot_unlockfile(path
);
216 mp
= (MBOX
*) mymalloc(sizeof(*mp
));
217 mp
->path
= mystrdup(path
);
223 /* mbox_release - release mailbox exclusive access */
225 void mbox_release(MBOX
*mp
)
229 * Unfortunately we can't close the stream, because on some file systems
230 * (AFS), the only way to find out if a file was written successfully is
231 * to close it, and therefore the close() operation is in the mail_copy()
232 * routine. If we really insist on owning the vstream member, then we
233 * should export appropriate methods that mail_copy() can use in order to
234 * manipulate a message stream.
236 if (mp
->locked
& MBOX_DOT_LOCK
)
237 dot_unlockfile(mp
->path
);
242 /* mbox_dsn - map errno value to mailbox-related DSN detail */
244 const char *mbox_dsn(int err
, const char *def_dsn
)
246 #define TRY_AGAIN_ERROR(e) \
247 (e == EAGAIN || e == ESTALE)
248 #define SYSTEM_FULL_ERROR(e) \
250 #define MBOX_FULL_ERROR(e) \
251 (e == EDQUOT || e == EFBIG)
253 return (TRY_AGAIN_ERROR(err
) ? "4.2.0" :
254 SYSTEM_FULL_ERROR(err
) ? "4.3.0" :
255 MBOX_FULL_ERROR(err
) ? "5.2.2" :