7 /* deliver to one local recipient
11 /* int deliver_recipient(state, usr_attr)
13 /* USER_ATTR *usr_attr;
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.
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.
39 /* Attributes describing user rights and environment.
41 /* deliver_recipient() returns non-zero when delivery should be
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.
48 /* alias(3) delivery to aliases
49 /* mailbox(3) delivery to mailbox
50 /* dotforward(3) delivery to destinations in .forward file
54 /* The Secure Mailer license must be distributed with this software.
57 /* IBM T.J. Watson Research
59 /* Yorktown Heights, NY 10598, USA
69 #ifdef STRCASECMP_IN_STRINGS_H
73 /* Utility library. */
79 #include <stringops.h>
87 #include <mail_params.h>
88 #include <split_addr.h>
89 #include <strip_addr.h>
92 #include <canon_addr.h>
94 /* Application-specific. */
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";
105 struct mypasswd
*mypwd
;
108 * Make verbose logging easier to understand.
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
);
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
))
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
))
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
);
198 /* deliver_recipient - deliver one local recipient */
200 int deliver_recipient(LOCAL_STATE state
, USER_ATTR usr_attr
)
202 const char *myname
= "deliver_recipient";
206 * Make verbose logging easier to understand.
210 MSG_LOG_STATE(myname
, state
);
215 if (been_here(state
.dup_filter
, "recipient %d %s",
216 state
.level
, state
.msg_attr
.rcpt
.address
))
220 * With each level of recursion, detect and break external message
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
;
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
,
252 *var_rcpt_delim
)) != 0) {
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),
260 owner_expansion
: owner_alias
);
261 SET_OWNER_ATTR(state
.msg_attr
, STR(canon_owner
), state
.level
);
265 dsb_simple(state
.msg_attr
.why
, "5.4.6", "mail forwarding loop for %s",
266 state
.msg_attr
.rcpt
.address
);
268 rcpt_stat
= bounce_one(BOUNCE_FLAGS(state
.request
),
269 BOUNCE_ONE_ATTR(state
.msg_attr
));
270 vstring_free(canon_owner
);
272 rcpt_stat
= bounce_append(BOUNCE_FLAGS(state
.request
),
273 BOUNCE_ATTR(state
.msg_attr
));
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;
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.
320 deliver_attr_dump(&state
.msg_attr
);
321 rcpt_stat
= deliver_switch(state
, usr_attr
);
326 myfree(state
.msg_attr
.local
);
327 myfree(state
.msg_attr
.user
);