xref: /netbsd-src/external/ibm-public/postfix/dist/src/local/recipient.c (revision 413d532bcc3f62d122e56d92e13ac64825a40baf)
1 /*	$NetBSD: recipient.c,v 1.1.1.4 2013/01/02 18:59:01 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 #include <errno.h>
69 
70 #ifdef STRCASECMP_IN_STRINGS_H
71 #include <strings.h>
72 #endif
73 
74 /* Utility library. */
75 
76 #include <msg.h>
77 #include <mymalloc.h>
78 #include <htable.h>
79 #include <split_at.h>
80 #include <stringops.h>
81 #include <dict.h>
82 #include <stat_as.h>
83 
84 /* Global library. */
85 
86 #include <bounce.h>
87 #include <defer.h>
88 #include <mail_params.h>
89 #include <split_addr.h>
90 #include <strip_addr.h>
91 #include <ext_prop.h>
92 #include <mypwd.h>
93 #include <canon_addr.h>
94 
95 /* Application-specific. */
96 
97 #include "local.h"
98 
99 /* deliver_switch - branch on recipient type */
100 
101 static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr)
102 {
103     const char *myname = "deliver_switch";
104     int     status = 0;
105     struct stat st;
106     struct mypasswd *mypwd;
107 
108     /*
109      * Make verbose logging easier to understand.
110      */
111     state.level++;
112     if (msg_verbose)
113 	MSG_LOG_STATE(myname, state);
114 
115 
116     /*
117      * \user is special: it means don't do any alias or forward expansion.
118      *
119      * XXX This code currently does not work due to revision of the RFC822
120      * address parser. \user should be permitted only in locally specified
121      * aliases, includes or forward files.
122      *
123      * XXX Should test for presence of user home directory.
124      */
125     if (state.msg_attr.rcpt.address[0] == '\\') {
126 	state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++;
127 	if (deliver_mailbox(state, usr_attr, &status) == 0)
128 	    status = deliver_unknown(state, usr_attr);
129 	return (status);
130     }
131 
132     /*
133      * Otherwise, alias expansion has highest precedence. First look up the
134      * full localpart, then the bare user. Obey the address extension
135      * propagation policy.
136      */
137     state.msg_attr.unmatched = 0;
138     if (deliver_alias(state, usr_attr, state.msg_attr.local, &status))
139 	return (status);
140     if (state.msg_attr.extension != 0) {
141 	if (local_ext_prop_mask & EXT_PROP_ALIAS)
142 	    state.msg_attr.unmatched = state.msg_attr.extension;
143 	if (deliver_alias(state, usr_attr, state.msg_attr.user, &status))
144 	    return (status);
145 	state.msg_attr.unmatched = state.msg_attr.extension;
146     }
147 
148     /*
149      * Special case for mail locally forwarded or aliased to a different
150      * local address. Resubmit the message via the cleanup service, so that
151      * each recipient gets a separate delivery queue file status record in
152      * the new queue file. The downside of this approach is that mutually
153      * recursive .forward files cause a mail forwarding loop. Fortunately,
154      * the loop can be broken by the use of the Delivered-To: message header.
155      *
156      * The code below must not trigger on mail sent to an alias that has no
157      * owner- companion, so that mail for an alias first.last->username is
158      * delivered directly, instead of going through username->first.last
159      * canonical mappings in the cleanup service. The downside of this
160      * approach is that recipients in the expansion of an alias without
161      * owner- won't have separate delivery queue file status records, because
162      * for them, the message won't be resubmitted as a new queue file.
163      *
164      * Do something sensible on systems that receive mail for multiple domains,
165      * such as primary.name and secondary.name. Don't resubmit the message
166      * when mail for `user@secondary.name' is delivered to a .forward file
167      * that lists `user' or `user@primary.name'. We already know that the
168      * recipient domain is local, so we only have to compare local parts.
169      */
170     if (state.msg_attr.owner != 0
171 	&& strcasecmp(state.msg_attr.owner, state.msg_attr.user) != 0)
172 	return (deliver_indirect(state));
173 
174     /*
175      * Always forward recipients in :include: files.
176      */
177     if (state.msg_attr.exp_type == EXPAND_TYPE_INCL)
178 	return (deliver_indirect(state));
179 
180     /*
181      * Delivery to local user. First try expansion of the recipient's
182      * $HOME/.forward file, then mailbox delivery. Back off when the user's
183      * home directory does not exist.
184      */
185     mypwd = 0;
186     if (var_stat_home_dir
187 	&& (errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) {
188 	msg_warn("error looking up passwd info for %s: %m",
189 		 state.msg_attr.user);
190 	dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error");
191 	return (defer_append(BOUNCE_FLAGS(state.request),
192 			     BOUNCE_ATTR(state.msg_attr)));
193     }
194     if (mypwd != 0) {
195 	if (stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) {
196 	    dsb_simple(state.msg_attr.why, "4.3.0",
197 		       "cannot access home directory %s: %m", mypwd->pw_dir);
198 	    mypwfree(mypwd);
199 	    return (defer_append(BOUNCE_FLAGS(state.request),
200 				 BOUNCE_ATTR(state.msg_attr)));
201 	}
202 	mypwfree(mypwd);
203     }
204     if (deliver_dotforward(state, usr_attr, &status) == 0
205 	&& deliver_mailbox(state, usr_attr, &status) == 0)
206 	status = deliver_unknown(state, usr_attr);
207     return (status);
208 }
209 
210 /* deliver_recipient - deliver one local recipient */
211 
212 int     deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr)
213 {
214     const char *myname = "deliver_recipient";
215     int     rcpt_stat;
216 
217     /*
218      * Make verbose logging easier to understand.
219      */
220     state.level++;
221     if (msg_verbose)
222 	MSG_LOG_STATE(myname, state);
223 
224     /*
225      * Duplicate filter.
226      */
227     if (been_here(state.dup_filter, "recipient %d %s",
228 		  state.level, state.msg_attr.rcpt.address))
229 	return (0);
230 
231     /*
232      * With each level of recursion, detect and break external message
233      * forwarding loops.
234      *
235      * If the looping recipient address has an owner- alias, send the error
236      * report there instead.
237      *
238      * XXX A delivery agent cannot change the envelope sender address for
239      * bouncing. As a workaround we use a one-recipient bounce procedure.
240      *
241      * The proper fix would be to record in the bounce logfile an error return
242      * address for each individual recipient. This would also eliminate the
243      * need for VERP specific bouncing code, at the cost of complicating the
244      * normal bounce sending procedure, but would simplify the code below.
245      */
246     if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) {
247 	dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s",
248 		   state.msg_attr.rcpt.address);
249 	/* Account for possible owner- sender address override. */
250 	return (bounce_workaround(state));
251     }
252 
253     /*
254      * Set up the recipient-specific attributes. If this is forwarded mail,
255      * leave the delivered attribute alone, so that the forwarded message
256      * will show the correct forwarding recipient.
257      */
258     if (state.msg_attr.delivered == 0)
259 	state.msg_attr.delivered = state.msg_attr.rcpt.address;
260     state.msg_attr.local = mystrdup(state.msg_attr.rcpt.address);
261     lowercase(state.msg_attr.local);
262     if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0)
263 	msg_warn("no @ in recipient address: %s", state.msg_attr.local);
264 
265     /*
266      * Address extension management.
267      *
268      * XXX Fix 20100422, finalized 20100529: it is too error-prone to
269      * distinguish between "no extension" and "no valid extension", so we
270      * drop an invalid extension from the recipient address local-part.
271      */
272     state.msg_attr.user = mystrdup(state.msg_attr.local);
273     if (*var_rcpt_delim) {
274 	state.msg_attr.extension =
275 	    split_addr(state.msg_attr.user, *var_rcpt_delim);
276 	if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) {
277 	    msg_warn("%s: address with illegal extension: %s",
278 		     state.msg_attr.queue_id, state.msg_attr.local);
279 	    state.msg_attr.extension = 0;
280 	    /* XXX Can't myfree + mystrdup, must truncate instead. */
281 	    state.msg_attr.local[strlen(state.msg_attr.user)] = 0;
282 	    /* Truncating is safe. The code below rejects null usernames. */
283 	}
284     } else
285 	state.msg_attr.extension = 0;
286     state.msg_attr.unmatched = state.msg_attr.extension;
287 
288     /*
289      * Do not allow null usernames.
290      */
291     if (state.msg_attr.user[0] == 0) {
292 	dsb_simple(state.msg_attr.why, "5.1.3",
293 		   "null username in \"%s\"", state.msg_attr.rcpt.address);
294 	return (bounce_append(BOUNCE_FLAGS(state.request),
295 			      BOUNCE_ATTR(state.msg_attr)));
296     }
297 
298     /*
299      * Run the recipient through the delivery switch.
300      */
301     if (msg_verbose)
302 	deliver_attr_dump(&state.msg_attr);
303     rcpt_stat = deliver_switch(state, usr_attr);
304 
305     /*
306      * Clean up.
307      */
308     myfree(state.msg_attr.local);
309     myfree(state.msg_attr.user);
310 
311     return (rcpt_stat);
312 }
313