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
96static 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
257int     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