xref: /netbsd-src/external/ibm-public/postfix/dist/src/local/mailbox.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: mailbox.c,v 1.2 2017/02/14 01:16:45 christos 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 #include <warn_stat.h>
67 
68 /* Global library. */
69 
70 #include <mail_copy.h>
71 #include <defer.h>
72 #include <sent.h>
73 #include <mypwd.h>
74 #include <been_here.h>
75 #include <mail_params.h>
76 #include <deliver_pass.h>
77 #include <mbox_open.h>
78 #include <maps.h>
79 #include <dsn_util.h>
80 
81 /* Application-specific. */
82 
83 #include "local.h"
84 #include "biff_notify.h"
85 
86 #define YES	1
87 #define NO	0
88 
89 /* deliver_mailbox_file - deliver to recipient mailbox */
90 
91 static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr)
92 {
93     const char *myname = "deliver_mailbox_file";
94     char   *spool_dir;
95     char   *mailbox;
96     DSN_BUF *why = state.msg_attr.why;
97     MBOX   *mp;
98     int     mail_copy_status;
99     int     deliver_status;
100     int     copy_flags;
101     VSTRING *biff;
102     long    end;
103     struct stat st;
104     uid_t   spool_uid;
105     gid_t   spool_gid;
106     uid_t   chown_uid;
107     gid_t   chown_gid;
108 
109     /*
110      * Make verbose logging easier to understand.
111      */
112     state.level++;
113     if (msg_verbose)
114 	MSG_LOG_STATE(myname, state);
115 
116     /*
117      * Don't deliver trace-only requests.
118      */
119     if (DEL_REQ_TRACE_ONLY(state.request->flags)) {
120 	dsb_simple(why, "2.0.0", "delivers to mailbox");
121 	return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)));
122     }
123 
124     /*
125      * Initialize. Assume the operation will fail. Set the delivered
126      * attribute to reflect the final recipient.
127      */
128     if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0)
129 	msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp));
130     if (var_frozen_delivered == 0)
131 	state.msg_attr.delivered = state.msg_attr.rcpt.address;
132     mail_copy_status = MAIL_COPY_STAT_WRITE;
133     if (*var_home_mailbox) {
134 	spool_dir = 0;
135 	mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
136     } else {
137 	spool_dir = var_mail_spool_dir;
138 	mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0);
139     }
140 
141     /*
142      * Mailbox delivery with least privilege. As long as we do not use root
143      * privileges this code may also work over NFS.
144      *
145      * If delivering to the recipient's home directory, perform all operations
146      * (including file locking) as that user (Mike Muuss, Army Research
147      * Laboratory, USA).
148      *
149      * If delivering to the mail spool directory, and the spool directory is
150      * world-writable, deliver as the recipient; if the spool directory is
151      * group-writable, use the recipient user id and the mail spool group id.
152      *
153      * Otherwise, use root privileges and chown the mailbox.
154      */
155     if (spool_dir == 0
156 	|| stat(spool_dir, &st) < 0
157 	|| (st.st_mode & S_IWOTH) != 0) {
158 	spool_uid = usr_attr.uid;
159 	spool_gid = usr_attr.gid;
160     } else if ((st.st_mode & S_IWGRP) != 0) {
161 	spool_uid = usr_attr.uid;
162 	spool_gid = st.st_gid;
163     } else {
164 	spool_uid = 0;
165 	spool_gid = 0;
166     }
167     if (spool_uid == usr_attr.uid) {
168 	chown_uid = -1;
169 	chown_gid = -1;
170     } else {
171 	chown_uid = usr_attr.uid;
172 	chown_gid = usr_attr.gid;
173     }
174     if (msg_verbose)
175 	msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld",
176 		 (long) spool_uid, (long) spool_gid,
177 		 (long) chown_uid, (long) chown_gid);
178 
179     /*
180      * Lock the mailbox and open/create the mailbox file. Depending on the
181      * type of locking used, we lock first or we open first.
182      *
183      * Write the file as the recipient, so that file quota work.
184      */
185     copy_flags = MAIL_COPY_MBOX;
186     if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0)
187 	copy_flags &= ~MAIL_COPY_DELIVERED;
188 
189     set_eugid(spool_uid, spool_gid);
190     mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT,
191 		   S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid,
192 		   local_mbox_lock_mask, "5.2.0", why);
193     if (mp != 0) {
194 	if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid)
195 	    set_eugid(usr_attr.uid, usr_attr.gid);
196 	if (S_ISREG(st.st_mode) == 0) {
197 	    vstream_fclose(mp->fp);
198 	    dsb_simple(why, "5.2.0",
199 		       "destination %s is not a regular file", mailbox);
200 	} else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) {
201 	    vstream_fclose(mp->fp);
202 	    dsb_simple(why, "4.2.0",
203 		       "destination %s is not owned by recipient", mailbox);
204 	    msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch",
205 		     VAR_STRICT_MBOX_OWNER);
206 	} else {
207 	    end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END);
208 	    mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp,
209 					 copy_flags, "\n", why);
210 	}
211 	if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid)
212 	    set_eugid(spool_uid, spool_gid);
213 	mbox_release(mp);
214     }
215     set_eugid(var_owner_uid, var_owner_gid);
216 
217     /*
218      * As the mail system, bounce, defer delivery, or report success.
219      */
220     if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
221 	deliver_status = DEL_STAT_DEFER;
222     } else if (mail_copy_status != 0) {
223 	vstring_sprintf_prepend(why->reason,
224 				"cannot update mailbox %s for user %s. ",
225 				mailbox, state.msg_attr.user);
226 	deliver_status =
227 	    (STR(why->status)[0] == '4' ?
228 	     defer_append : bounce_append)
229 	    (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr));
230     } else {
231 	dsb_simple(why, "2.0.0", "delivered to mailbox");
232 	deliver_status = sent(BOUNCE_FLAGS(state.request),
233 			      SENT_ATTR(state.msg_attr));
234 	if (var_biff) {
235 	    biff = vstring_alloc(100);
236 	    vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end);
237 	    biff_notify(STR(biff), VSTRING_LEN(biff) + 1);
238 	    vstring_free(biff);
239 	}
240     }
241 
242     /*
243      * Clean up.
244      */
245     myfree(mailbox);
246     return (deliver_status);
247 }
248 
249 /* deliver_mailbox - deliver to recipient mailbox */
250 
251 int     deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
252 {
253     const char *myname = "deliver_mailbox";
254     int     status;
255     struct mypasswd *mbox_pwd;
256     char   *path;
257     static MAPS *transp_maps;
258     const char *map_transport;
259     static MAPS *cmd_maps;
260     const char *map_command;
261 
262     /*
263      * Make verbose logging easier to understand.
264      */
265     state.level++;
266     if (msg_verbose)
267 	MSG_LOG_STATE(myname, state);
268 
269     /*
270      * DUPLICATE ELIMINATION
271      *
272      * Don't come here more than once, whether or not the recipient exists.
273      */
274     if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local))
275 	return (YES);
276 
277     /*
278      * Delegate mailbox delivery to another message transport.
279      */
280     if (*var_mbox_transp_maps && transp_maps == 0)
281 	transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps,
282 				  DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB
283 				  | DICT_FLAG_UTF8_REQUEST);
284     /* The -1 is a hint for the down-stream deliver_completed() function. */
285     if (transp_maps
286 	&& (map_transport = maps_find(transp_maps, state.msg_attr.user,
287 				      DICT_FLAG_NONE)) != 0) {
288 	state.msg_attr.rcpt.offset = -1L;
289 	*statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport,
290 				state.request, &state.msg_attr.rcpt);
291 	return (YES);
292     } else if (transp_maps && transp_maps->error != 0) {
293 	/* Details in the logfile. */
294 	dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
295 	*statusp = defer_append(BOUNCE_FLAGS(state.request),
296 				BOUNCE_ATTR(state.msg_attr));
297 	return (YES);
298     }
299     if (*var_mailbox_transport) {
300 	state.msg_attr.rcpt.offset = -1L;
301 	*statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport,
302 				state.request, &state.msg_attr.rcpt);
303 	return (YES);
304     }
305 
306     /*
307      * Skip delivery when this recipient does not exist.
308      */
309     if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 0) {
310 	msg_warn("error looking up passwd info for %s: %m",
311 		 state.msg_attr.user);
312 	dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error");
313 	*statusp = defer_append(BOUNCE_FLAGS(state.request),
314 				BOUNCE_ATTR(state.msg_attr));
315 	return (YES);
316     }
317     if (mbox_pwd == 0)
318 	return (NO);
319 
320     /*
321      * No early returns or we have a memory leak.
322      */
323 
324     /*
325      * DELIVERY RIGHTS
326      *
327      * Use the rights of the recipient user.
328      */
329     SET_USER_ATTR(usr_attr, mbox_pwd, state.level);
330 
331     /*
332      * Deliver to mailbox, maildir or to external command.
333      */
334 #define LAST_CHAR(s) (s[strlen(s) - 1])
335 
336     if (*var_mailbox_cmd_maps && cmd_maps == 0)
337 	cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps,
338 			       DICT_FLAG_LOCK | DICT_FLAG_PARANOID
339 			       | DICT_FLAG_UTF8_REQUEST);
340 
341     if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user,
342 					     DICT_FLAG_NONE)) != 0) {
343 	status = deliver_command(state, usr_attr, map_command);
344     } else if (cmd_maps && cmd_maps->error != 0) {
345 	/* Details in the logfile. */
346 	dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure");
347 	status = defer_append(BOUNCE_FLAGS(state.request),
348 			      BOUNCE_ATTR(state.msg_attr));
349     } else if (*var_mailbox_command) {
350 	status = deliver_command(state, usr_attr, var_mailbox_command);
351     } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') {
352 	path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0);
353 	status = deliver_maildir(state, usr_attr, path);
354 	myfree(path);
355     } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') {
356 	path = concatenate(var_mail_spool_dir, state.msg_attr.user,
357 			   "/", (char *) 0);
358 	status = deliver_maildir(state, usr_attr, path);
359 	myfree(path);
360     } else
361 	status = deliver_mailbox_file(state, usr_attr);
362 
363     /*
364      * Cleanup.
365      */
366     mypwfree(mbox_pwd);
367     *statusp = status;
368     return (YES);
369 }
370