Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / local / recipient.c
blob76911aaa802a02adc45d9eb385a43c4e58365030
1 /* $NetBSD$ */
3 /*++
4 /* NAME
5 /* recipient 3
6 /* SUMMARY
7 /* deliver to one local recipient
8 /* SYNOPSIS
9 /* #include "local.h"
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.
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).
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.
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 /*--*/
62 /* System library. */
64 #include <sys_defs.h>
65 #include <sys/stat.h>
66 #include <unistd.h>
67 #include <string.h>
69 #ifdef STRCASECMP_IN_STRINGS_H
70 #include <strings.h>
71 #endif
73 /* Utility library. */
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>
83 /* Global library. */
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>
94 /* Application-specific. */
96 #include "local.h"
98 /* deliver_switch - branch on recipient type */
100 static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr)
102 const char *myname = "deliver_switch";
103 int status = 0;
104 struct stat st;
105 struct mypasswd *mypwd;
108 * Make verbose logging easier to understand.
110 state.level++;
111 if (msg_verbose)
112 MSG_LOG_STATE(myname, state);
116 * \user is special: it means don't do any alias or forward expansion.
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.
122 * XXX Should test for presence of user home directory.
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);
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.
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;
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.
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.
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.
169 if (state.msg_attr.owner != 0
170 && strcasecmp(state.msg_attr.owner, state.msg_attr.user) != 0)
171 return (deliver_indirect(state));
174 * Always forward recipients in :include: files.
176 if (state.msg_attr.exp_type == EXPAND_TYPE_INCL)
177 return (deliver_indirect(state));
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.
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)));
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);
198 /* deliver_recipient - deliver one local recipient */
200 int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr)
202 const char *myname = "deliver_recipient";
203 int rcpt_stat;
206 * Make verbose logging easier to understand.
208 state.level++;
209 if (msg_verbose)
210 MSG_LOG_STATE(myname, state);
213 * Duplicate filter.
215 if (been_here(state.dup_filter, "recipient %d %s",
216 state.level, state.msg_attr.rcpt.address))
217 return (0);
220 * With each level of recursion, detect and break external message
221 * forwarding loops.
223 * If the looping recipient address has an owner- alias, send the error
224 * report there instead.
226 * XXX A delivery agent cannot change the envelope sender address for
227 * bouncing. As a workaround we use a one-recipient bounce procedure.
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.
234 if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) {
235 VSTRING *canon_owner = 0;
237 if (var_ownreq_special) {
238 char *stripped_recipient;
239 char *owner_alias;
240 const char *owner_expansion;
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); \
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);
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);
263 myfree(owner_alias);
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));
275 return (rcpt_stat);
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.
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);
291 * Address extension management.
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;
302 } else
303 state.msg_attr.extension = 0;
304 state.msg_attr.unmatched = state.msg_attr.extension;
307 * Do not allow null usernames.
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)));
317 * Run the recipient through the delivery switch.
319 if (msg_verbose)
320 deliver_attr_dump(&state.msg_attr);
321 rcpt_stat = deliver_switch(state, usr_attr);
324 * Clean up.
326 myfree(state.msg_attr.local);
327 myfree(state.msg_attr.user);
329 return (rcpt_stat);