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