xref: /netbsd-src/external/ibm-public/postfix/dist/src/local/recipient.c (revision b62fc9e20372b08e1785ff6d769312d209fa2005)
1 /*	$NetBSD: recipient.c,v 1.1.1.1 2009/06/23 10:08:49 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 	VSTRING *canon_owner = 0;
236 
237 	if (var_ownreq_special) {
238 	    char   *stripped_recipient;
239 	    char   *owner_alias;
240 	    const char *owner_expansion;
241 
242 #define FIND_OWNER(lhs, rhs, addr) { \
243 	lhs = concatenate("owner-", addr, (char *) 0); \
244 	(void) split_at_right(lhs, '@'); \
245 	rhs = maps_find(alias_maps, lhs, DICT_FLAG_NONE); \
246     }
247 
248 	    FIND_OWNER(owner_alias, owner_expansion, state.msg_attr.rcpt.address);
249 	    if (owner_expansion == 0
250 	    && (stripped_recipient = strip_addr(state.msg_attr.rcpt.address,
251 						(char **) 0,
252 						*var_rcpt_delim)) != 0) {
253 		myfree(owner_alias);
254 		FIND_OWNER(owner_alias, owner_expansion, stripped_recipient);
255 		myfree(stripped_recipient);
256 	    }
257 	    if (owner_expansion != 0) {
258 		canon_owner = canon_addr_internal(vstring_alloc(10),
259 						  var_exp_own_alias ?
260 					     owner_expansion : owner_alias);
261 		SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
262 	    }
263 	    myfree(owner_alias);
264 	}
265 	dsb_simple(state.msg_attr.why, "5.4.6",	"mail forwarding loop for %s",
266 		   state.msg_attr.rcpt.address);
267 	if (canon_owner) {
268 	    rcpt_stat = bounce_one(BOUNCE_FLAGS(state.request),
269 				   BOUNCE_ONE_ATTR(state.msg_attr));
270 	    vstring_free(canon_owner);
271 	} else {
272 	    rcpt_stat = bounce_append(BOUNCE_FLAGS(state.request),
273 				      BOUNCE_ATTR(state.msg_attr));
274 	}
275 	return (rcpt_stat);
276     }
277 
278     /*
279      * Set up the recipient-specific attributes. If this is forwarded mail,
280      * leave the delivered attribute alone, so that the forwarded message
281      * will show the correct forwarding recipient.
282      */
283     if (state.msg_attr.delivered == 0)
284 	state.msg_attr.delivered = state.msg_attr.rcpt.address;
285     state.msg_attr.local = mystrdup(state.msg_attr.rcpt.address);
286     lowercase(state.msg_attr.local);
287     if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0)
288 	msg_warn("no @ in recipient address: %s", state.msg_attr.local);
289 
290     /*
291      * Address extension management.
292      */
293     state.msg_attr.user = mystrdup(state.msg_attr.local);
294     if (*var_rcpt_delim) {
295 	state.msg_attr.extension =
296 	    split_addr(state.msg_attr.user, *var_rcpt_delim);
297 	if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) {
298 	    msg_warn("%s: address with illegal extension: %s",
299 		     state.msg_attr.queue_id, state.msg_attr.local);
300 	    state.msg_attr.extension = 0;
301 	}
302     } else
303 	state.msg_attr.extension = 0;
304     state.msg_attr.unmatched = state.msg_attr.extension;
305 
306     /*
307      * Do not allow null usernames.
308      */
309     if (state.msg_attr.user[0] == 0) {
310 	dsb_simple(state.msg_attr.why, "5.1.3",
311 		   "null username in \"%s\"", state.msg_attr.rcpt.address);
312 	return (bounce_append(BOUNCE_FLAGS(state.request),
313 			      BOUNCE_ATTR(state.msg_attr)));
314     }
315 
316     /*
317      * Run the recipient through the delivery switch.
318      */
319     if (msg_verbose)
320 	deliver_attr_dump(&state.msg_attr);
321     rcpt_stat = deliver_switch(state, usr_attr);
322 
323     /*
324      * Clean up.
325      */
326     myfree(state.msg_attr.local);
327     myfree(state.msg_attr.user);
328 
329     return (rcpt_stat);
330 }
331