xref: /netbsd-src/external/ibm-public/postfix/dist/src/local/recipient.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*	$NetBSD: recipient.c,v 1.1.1.3 2011/03/02 19:32:20 tron Exp $	*/
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 
100 static 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 
200 int     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