xref: /netbsd-src/external/ibm-public/postfix/dist/src/local/mailbox.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*	$NetBSD: mailbox.c,v 1.1.1.2 2011/07/31 10:02:41 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	mailbox 3
6 /* SUMMARY
7 /*	mailbox delivery
8 /* SYNOPSIS
9 /*	#include "local.h"
10 /*
11 /*	int	deliver_mailbox(state, usr_attr, statusp)
12 /*	LOCAL_STATE state;
13 /*	USER_ATTR usr_attr;
14 /*	int	*statusp;
15 /* DESCRIPTION
16 /*	deliver_mailbox() delivers to mailbox, with duplicate
17 /*	suppression. The default is direct mailbox delivery to
18 /*	/var/[spool/]mail/\fIuser\fR; when a \fIhome_mailbox\fR
19 /*	has been configured, mail is delivered to ~/$\fIhome_mailbox\fR;
20 /*	and when a \fImailbox_command\fR has been configured, the message
21 /*	is piped into the command instead.
22 /*
23 /*	A zero result means that the named user was not found.
24 /*
25 /*	Arguments:
26 /* .IP state
27 /*	The attributes that specify the message, recipient and more.
28 /*	Attributes describing alias, include or forward expansion.
29 /*	A table with the results from expanding aliases or lists.
30 /* .IP usr_attr
31 /*	Attributes describing user rights and environment.
32 /* .IP statusp
33 /*	Delivery status: see below.
34 /* DIAGNOSTICS
35 /*	The message delivery status is non-zero when delivery should be tried
36 /*	again.
37 /* LICENSE
38 /* .ad
39 /* .fi
40 /*	The Secure Mailer license must be distributed with this software.
41 /* AUTHOR(S)
42 /*	Wietse Venema
43 /*	IBM T.J. Watson Research
44 /*	P.O. Box 704
45 /*	Yorktown Heights, NY 10598, USA
46 /*--*/
47 
48 /* System library. */
49 
50 #include <sys_defs.h>
51 #include <sys/stat.h>
52 #include <fcntl.h>
53 #include <string.h>
54 #include <unistd.h>
55 #include <errno.h>
56 
57 /* Utility library. */
58 
59 #include <msg.h>
60 #include <htable.h>
61 #include <vstring.h>
62 #include <vstream.h>
63 #include <mymalloc.h>
64 #include <stringops.h>
65 #include <set_eugid.h>
66 
67 /* Global library. */
68 
69 #include <mail_copy.h>
70 #include <defer.h>
71 #include <sent.h>
72 #include <mypwd.h>
73 #include <been_here.h>
74 #include <mail_params.h>
75 #include <deliver_pass.h>
76 #include <mbox_open.h>
77 #include <maps.h>
78 #include <dsn_util.h>
79 
80 /* Application-specific. */
81 
82 #include "local.h"
83 #include "biff_notify.h"
84 
85 #define YES	1
86 #define NO	0
87 
88 /* deliver_mailbox_file - deliver to recipient mailbox */
89 
90 static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr)
91 {
92     const char *myname = "deliver_mailbox_file";
93     char   *spool_dir;
94     char   *mailbox;
95     DSN_BUF *why = state.msg_attr.why;
96     MBOX   *mp;
97     int     mail_copy_status;
98     int     deliver_status;
99     int     copy_flags;
100     VSTRING *biff;
101     long    end;
102     struct stat st;
103     uid_t   spool_uid;
104     gid_t   spool_gid;
105     uid_t   chown_uid;
106     gid_t   chown_gid;
107 
108     /*
109      * Make verbose logging easier to understand.
110      */
111     state.level++;
112     if (msg_verbose)
113 	MSG_LOG_STATE(myname, state);
114 
115     /*
116      * Don't deliver trace-only requests.
117      */
118     if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
119 	dsb_simple(why, "2.0.0", "delivers to mailbox");
120 	return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)));
121     }
122 
123     /*
124      * Initialize. Assume the operation will fail. Set the delivered
125      * attribute to reflect the final recipient.
126      */
127     if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
128 	msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
129     if (var_frozen_delivered == 0)
130 	state.msg_attr.delivered = state.msg_attr.rcpt.address;
131     mail_copy_status = MAIL_COPY_STAT_WRITE;
132     if (*var_home_mailbox) {
133 	spool_dir = 0;
134 	mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
135     } else {
136 	spool_dir = var_mail_spool_dir;
137 	mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0);
138     }
139 
140     /*
141      * Mailbox delivery with least privilege. As long as we do not use root
142      * privileges this code may also work over NFS.
143      *
144      * If delivering to the recipient's home directory, perform all operations
145      * (including file locking) as that user (Mike Muuss, Army Research
146      * Laboratory, USA).
147      *
148      * If delivering to the mail spool directory, and the spool directory is
149      * world-writable, deliver as the recipient; if the spool directory is
150      * group-writable, use the recipient user id and the mail spool group id.
151      *
152      * Otherwise, use root privileges and chown the mailbox.
153      */
154     if (spool_dir == 0
155 	|| stat(spool_dir, &st) < 0
156 	|| (st.st_mode & S_IWOTH) != 0) {
157 	spool_uid = usr_attr.uid;
158 	spool_gid = usr_attr.gid;
159     } else if ((st.st_mode & S_IWGRP) != 0) {
160 	spool_uid = usr_attr.uid;
161 	spool_gid = st.st_gid;
162     } else {
163 	spool_uid = 0;
164 	spool_gid = 0;
165     }
166     if (spool_uid == usr_attr.uid) {
167 	chown_uid = -1;
168 	chown_gid = -1;
169     } else {
170 	chown_uid = usr_attr.uid;
171 	chown_gid = usr_attr.gid;
172     }
173     if (msg_verbose)
174 	msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld",
175 		 (long) spool_uid, (long) spool_gid,
176 		 (long) chown_uid, (long) chown_gid);
177 
178     /*
179      * Lock the mailbox and open/create the mailbox file. Depending on the
180      * type of locking used, we lock first or we open first.
181      *
182      * Write the file as the recipient, so that file quota work.
183      */
184     copy_flags = MAIL_COPY_MBOX;
185     if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0)
186 	copy_flags &= ~MAIL_COPY_DELIVERED;
187 
188     set_eugid(spool_uid, spool_gid);
189     mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT,
190 		   S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid,
191 		   local_mbox_lock_mask, "5.2.0", why);
192     if (mp != 0) {
193 	if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid)
194 	    set_eugid(usr_attr.uid, usr_attr.gid);
195 	if (S_ISREG(st.st_mode) == 0) {
196 	    vstream_fclose(mp->fp);
197 	    dsb_simple(why, "5.2.0",
198 		       "destination %s is not a regular file", mailbox);
199 	} else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) {
200 	    vstream_fclose(mp->fp);
201 	    dsb_simple(why, "4.2.0",
202 		       "destination %s is not owned by recipient", mailbox);
203 	    msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch",
204 		     VAR_STRICT_MBOX_OWNER);
205 	} else {
206 	    end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END);
207 	    mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
208 					 copy_flags, "\n", why);
209 	}
210 	if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid)
211 	    set_eugid(spool_uid, spool_gid);
212 	mbox_release(mp);
213     }
214     set_eugid(var_owner_uid, var_owner_gid);
215 
216     /*
217      * As the mail system, bounce, defer delivery, or report success.
218      */
219     if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
220 	deliver_status = DEL_STAT_DEFER;
221     } else if (mail_copy_status != 0) {
222 	vstring_sprintf_prepend(why->reason,
223 				"cannot update mailbox %s for user %s. ",
224 				mailbox, state.msg_attr.user);
225 	deliver_status =
226 	    (STR(why->status)[0] == '4' ?
227 	     defer_append : bounce_append)
228 	    (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr));
229     } else {
230 	dsb_simple(why, "2.0.0", "delivered to mailbox");
231 	deliver_status = sent(BOUNCE_FLAGS(state.request),
232 			      SENT_ATTR(state.msg_attr));
233 	if (var_biff) {
234 	    biff = vstring_alloc(100);
235 	    vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end);
236 	    biff_notify(STR(biff), VSTRING_LEN(biff) + 1);
237 	    vstring_free(biff);
238 	}
239     }
240 
241     /*
242      * Clean up.
243      */
244     myfree(mailbox);
245     return (deliver_status);
246 }
247 
248 /* deliver_mailbox - deliver to recipient mailbox */
249 
250 int     deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
251 {
252     const char *myname = "deliver_mailbox";
253     int     status;
254     struct mypasswd *mbox_pwd;
255     char   *path;
256     static MAPS *transp_maps;
257     const char *map_transport;
258     static MAPS *cmd_maps;
259     const char *map_command;
260 
261     /*
262      * Make verbose logging easier to understand.
263      */
264     state.level++;
265     if (msg_verbose)
266 	MSG_LOG_STATE(myname, state);
267 
268     /*
269      * DUPLICATE ELIMINATION
270      *
271      * Don't come here more than once, whether or not the recipient exists.
272      */
273     if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local))
274 	return (YES);
275 
276     /*
277      * Delegate mailbox delivery to another message transport.
278      */
279     if (*var_mbox_transp_maps && transp_maps == 0)
280 	transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps,
281 				  DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB);
282     /* The -1 is a hint for the down-stream deliver_completed() function. */
283     dict_errno = 0;
284     if (*var_mbox_transp_maps
285 	&& (map_transport = maps_find(transp_maps, state.msg_attr.user,
286 				      DICT_FLAG_NONE)) != 0) {
287 	state.msg_attr.rcpt.offset = -1L;
288 	*statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport,
289 				state.request, &state.msg_attr.rcpt);
290 	return (YES);
291     } else if (dict_errno != 0) {
292 	/* Details in the logfile. */
293 	dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
294 	*statusp = DEL_STAT_DEFER;
295 	return (YES);
296     }
297     if (*var_mailbox_transport) {
298 	state.msg_attr.rcpt.offset = -1L;
299 	*statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport,
300 				state.request, &state.msg_attr.rcpt);
301 	return (YES);
302     }
303 
304     /*
305      * Skip delivery when this recipient does not exist.
306      */
307     if ((mbox_pwd = mypwnam(state.msg_attr.user)) == 0)
308 	return (NO);
309 
310     /*
311      * No early returns or we have a memory leak.
312      */
313 
314     /*
315      * DELIVERY RIGHTS
316      *
317      * Use the rights of the recipient user.
318      */
319     SET_USER_ATTR(usr_attr, mbox_pwd, state.level);
320 
321     /*
322      * Deliver to mailbox, maildir or to external command.
323      */
324 #define LAST_CHAR(s) (s[strlen(s) - 1])
325 
326     if (*var_mailbox_cmd_maps && cmd_maps == 0)
327 	cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps,
328 			       DICT_FLAG_LOCK | DICT_FLAG_PARANOID);
329 
330     dict_errno = 0;
331     if (*var_mailbox_cmd_maps
332 	&& (map_command = maps_find(cmd_maps, state.msg_attr.user,
333 				    DICT_FLAG_NONE)) != 0) {
334 	status = deliver_command(state, usr_attr, map_command);
335     } else if (dict_errno != 0) {
336 	/* Details in the logfile. */
337 	dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
338 	status = DEL_STAT_DEFER;
339     } else if (*var_mailbox_command) {
340 	status = deliver_command(state, usr_attr, var_mailbox_command);
341     } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') {
342 	path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
343 	status = deliver_maildir(state, usr_attr, path);
344 	myfree(path);
345     } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') {
346 	path = concatenate(var_mail_spool_dir, state.msg_attr.user,
347 			   "/", (char *) 0);
348 	status = deliver_maildir(state, usr_attr, path);
349 	myfree(path);
350     } else
351 	status = deliver_mailbox_file(state, usr_attr);
352 
353     /*
354      * Cleanup.
355      */
356     mypwfree(mbox_pwd);
357     *statusp = status;
358     return (YES);
359 }
360