1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	recipient 3
6/* SUMMARY
7/*	deliver to one local recipient
8/* SYNOPSIS
9/*	#include "local.h"
10/*
11/*	int	deliver_recipient(state, usr_attr)
12/*	LOCAL_STATE state;
13/*	USER_ATTR *usr_attr;
14/* DESCRIPTION
15/*	deliver_recipient() delivers a message to a local recipient.
16/*	It is called initially when the queue manager requests
17/*	delivery to a local recipient, and is called recursively
18/*	when an alias or forward file expands to a local recipient.
19/*
20/*	When called recursively with, for example, a result from alias
21/*	or forward file expansion, aliases are expanded immediately,
22/*	but mail for non-alias destinations is submitted as a new
23/*	message, so that each recipient has a dedicated queue file
24/*	message delivery status record (in a shared queue file).
25/*
26/*	When the \fIrecipient_delimiter\fR configuration parameter
27/*	is set, it is used to separate cookies off recipient names.
28/*	A common setting is to have "recipient_delimiter = +"
29/*	so that mail for \fIuser+foo\fR is delivered to \fIuser\fR,
30/*	with a "Delivered-To: user+foo@domain" header line.
31/*
32/*	Arguments:
33/* .IP state
34/*	The attributes that specify the message, sender, and more.
35/*	Attributes describing alias, include or forward expansion.
36/*	A table with the results from expanding aliases or lists.
37/*	A table with delivered-to: addresses taken from the message.
38/* .IP usr_attr
39/*	Attributes describing user rights and environment.
40/* DIAGNOSTICS
41/*	deliver_recipient() returns non-zero when delivery should be
42/*	tried again.
43/* BUGS
44/*	Mutually-recursive aliases or $HOME/.forward files aren't
45/*	detected when they could be. The resulting mail forwarding loop
46/*	is broken by the use of the Delivered-To: message header.
47/* SEE ALSO
48/*	alias(3) delivery to aliases
49/*	mailbox(3) delivery to mailbox
50/*	dotforward(3) delivery to destinations in .forward file
51/* LICENSE
52/* .ad
53/* .fi
54/*	The Secure Mailer license must be distributed with this software.
55/* AUTHOR(S)
56/*	Wietse Venema
57/*	IBM T.J. Watson Research
58/*	P.O. Box 704
59/*	Yorktown Heights, NY 10598, USA
60/*--*/
61
62/* System library. */
63
64#include <sys_defs.h>
65#include <sys/stat.h>
66#include <unistd.h>
67#include <string.h>
68
69#ifdef STRCASECMP_IN_STRINGS_H
70#include <strings.h>
71#endif
72
73/* Utility library. */
74
75#include <msg.h>
76#include <mymalloc.h>
77#include <htable.h>
78#include <split_at.h>
79#include <stringops.h>
80#include <dict.h>
81#include <stat_as.h>
82
83/* Global library. */
84
85#include <bounce.h>
86#include <defer.h>
87#include <mail_params.h>
88#include <split_addr.h>
89#include <strip_addr.h>
90#include <ext_prop.h>
91#include <mypwd.h>
92#include <canon_addr.h>
93
94/* Application-specific. */
95
96#include "local.h"
97
98/* deliver_switch - branch on recipient type */
99
100static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr)
101{
102    const char *myname = "deliver_switch";
103    int     status = 0;
104    struct stat st;
105    struct mypasswd *mypwd;
106
107    /*
108     * Make verbose logging easier to understand.
109     */
110    state.level++;
111    if (msg_verbose)
112	MSG_LOG_STATE(myname, state);
113
114
115    /*
116     * \user is special: it means don't do any alias or forward expansion.
117     *
118     * XXX This code currently does not work due to revision of the RFC822
119     * address parser. \user should be permitted only in locally specified
120     * aliases, includes or forward files.
121     *
122     * XXX Should test for presence of user home directory.
123     */
124    if (state.msg_attr.rcpt.address[0] == '\\') {
125	state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++;
126	if (deliver_mailbox(state, usr_attr, &status) == 0)
127	    status = deliver_unknown(state, usr_attr);
128	return (status);
129    }
130
131    /*
132     * Otherwise, alias expansion has highest precedence. First look up the
133     * full localpart, then the bare user. Obey the address extension
134     * propagation policy.
135     */
136    state.msg_attr.unmatched = 0;
137    if (deliver_alias(state, usr_attr, state.msg_attr.local, &status))
138	return (status);
139    if (state.msg_attr.extension != 0) {
140	if (local_ext_prop_mask & EXT_PROP_ALIAS)
141	    state.msg_attr.unmatched = state.msg_attr.extension;
142	if (deliver_alias(state, usr_attr, state.msg_attr.user, &status))
143	    return (status);
144	state.msg_attr.unmatched = state.msg_attr.extension;
145    }
146
147    /*
148     * Special case for mail locally forwarded or aliased to a different
149     * local address. Resubmit the message via the cleanup service, so that
150     * each recipient gets a separate delivery queue file status record in
151     * the new queue file. The downside of this approach is that mutually
152     * recursive .forward files cause a mail forwarding loop. Fortunately,
153     * the loop can be broken by the use of the Delivered-To: message header.
154     *
155     * The code below must not trigger on mail sent to an alias that has no
156     * owner- companion, so that mail for an alias first.last->username is
157     * delivered directly, instead of going through username->first.last
158     * canonical mappings in the cleanup service. The downside of this
159     * approach is that recipients in the expansion of an alias without
160     * owner- won't have separate delivery queue file status records, because
161     * for them, the message won't be resubmitted as a new queue file.
162     *
163     * Do something sensible on systems that receive mail for multiple domains,
164     * such as primary.name and secondary.name. Don't resubmit the message
165     * when mail for `user@secondary.name' is delivered to a .forward file
166     * that lists `user' or `user@primary.name'. We already know that the
167     * recipient domain is local, so we only have to compare local parts.
168     */
169    if (state.msg_attr.owner != 0
170	&& strcasecmp(state.msg_attr.owner, state.msg_attr.user) != 0)
171	return (deliver_indirect(state));
172
173    /*
174     * Always forward recipients in :include: files.
175     */
176    if (state.msg_attr.exp_type == EXPAND_TYPE_INCL)
177	return (deliver_indirect(state));
178
179    /*
180     * Delivery to local user. First try expansion of the recipient's
181     * $HOME/.forward file, then mailbox delivery. Back off when the user's
182     * home directory does not exist.
183     */
184    if (var_stat_home_dir
185	&& (mypwd = mypwnam(state.msg_attr.user)) != 0
186	&& stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) {
187	dsb_simple(state.msg_attr.why, "4.3.0",
188		   "cannot access home directory %s: %m", mypwd->pw_dir);
189	return (defer_append(BOUNCE_FLAGS(state.request),
190			     BOUNCE_ATTR(state.msg_attr)));
191    }
192    if (deliver_dotforward(state, usr_attr, &status) == 0
193	&& deliver_mailbox(state, usr_attr, &status) == 0)
194	status = deliver_unknown(state, usr_attr);
195    return (status);
196}
197
198/* deliver_recipient - deliver one local recipient */
199
200int     deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr)
201{
202    const char *myname = "deliver_recipient";
203    int     rcpt_stat;
204
205    /*
206     * Make verbose logging easier to understand.
207     */
208    state.level++;
209    if (msg_verbose)
210	MSG_LOG_STATE(myname, state);
211
212    /*
213     * Duplicate filter.
214     */
215    if (been_here(state.dup_filter, "recipient %d %s",
216		  state.level, state.msg_attr.rcpt.address))
217	return (0);
218
219    /*
220     * With each level of recursion, detect and break external message
221     * forwarding loops.
222     *
223     * If the looping recipient address has an owner- alias, send the error
224     * report there instead.
225     *
226     * XXX A delivery agent cannot change the envelope sender address for
227     * bouncing. As a workaround we use a one-recipient bounce procedure.
228     *
229     * The proper fix would be to record in the bounce logfile an error return
230     * address for each individual recipient. This would also eliminate the
231     * need for VERP specific bouncing code, at the cost of complicating the
232     * normal bounce sending procedure, but would simplify the code below.
233     */
234    if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) {
235	dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s",
236		   state.msg_attr.rcpt.address);
237	/* Account for possible owner- sender address override. */
238	return (bounce_workaround(state));
239    }
240
241    /*
242     * Set up the recipient-specific attributes. If this is forwarded mail,
243     * leave the delivered attribute alone, so that the forwarded message
244     * will show the correct forwarding recipient.
245     */
246    if (state.msg_attr.delivered == 0)
247	state.msg_attr.delivered = state.msg_attr.rcpt.address;
248    state.msg_attr.local = mystrdup(state.msg_attr.rcpt.address);
249    lowercase(state.msg_attr.local);
250    if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0)
251	msg_warn("no @ in recipient address: %s", state.msg_attr.local);
252
253    /*
254     * Address extension management.
255     *
256     * XXX Fix 20100422, finalized 20100529: it is too error-prone to
257     * distinguish between "no extension" and "no valid extension", so we
258     * drop an invalid extension from the recipient address local-part.
259     */
260    state.msg_attr.user = mystrdup(state.msg_attr.local);
261    if (*var_rcpt_delim) {
262	state.msg_attr.extension =
263	    split_addr(state.msg_attr.user, *var_rcpt_delim);
264	if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) {
265	    msg_warn("%s: address with illegal extension: %s",
266		     state.msg_attr.queue_id, state.msg_attr.local);
267	    state.msg_attr.extension = 0;
268	    /* XXX Can't myfree + mystrdup, must truncate instead. */
269	    state.msg_attr.local[strlen(state.msg_attr.user)] = 0;
270	    /* Truncating is safe. The code below rejects null usernames. */
271	}
272    } else
273	state.msg_attr.extension = 0;
274    state.msg_attr.unmatched = state.msg_attr.extension;
275
276    /*
277     * Do not allow null usernames.
278     */
279    if (state.msg_attr.user[0] == 0) {
280	dsb_simple(state.msg_attr.why, "5.1.3",
281		   "null username in \"%s\"", state.msg_attr.rcpt.address);
282	return (bounce_append(BOUNCE_FLAGS(state.request),
283			      BOUNCE_ATTR(state.msg_attr)));
284    }
285
286    /*
287     * Run the recipient through the delivery switch.
288     */
289    if (msg_verbose)
290	deliver_attr_dump(&state.msg_attr);
291    rcpt_stat = deliver_switch(state, usr_attr);
292
293    /*
294     * Clean up.
295     */
296    myfree(state.msg_attr.local);
297    myfree(state.msg_attr.user);
298
299    return (rcpt_stat);
300}
301