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