xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/mbox_open.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
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 
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)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 
mbox_release(MBOX * mp)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 
mbox_dsn(int err,const char * def_dsn)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