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