1 /* $NetBSD: alias.c,v 1.3 2020/03/18 19:05:16 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* alias 3
6 /* SUMMARY
7 /* alias data base lookups
8 /* SYNOPSIS
9 /* #include "local.h"
10 /*
11 /* int deliver_alias(state, usr_attr, name, statusp)
12 /* LOCAL_STATE state;
13 /* USER_ATTR usr_attr;
14 /* char *name;
15 /* int *statusp;
16 /* DESCRIPTION
17 /* deliver_alias() looks up the expansion of the recipient in
18 /* the global alias database and delivers the message to the
19 /* listed destinations. The result is zero when no alias was found
20 /* or when the message should be delivered to the user instead.
21 /*
22 /* deliver_alias() has wired-in knowledge about a few reserved
23 /* recipient names.
24 /* .IP \(bu
25 /* When no alias is found for the local \fIpostmaster\fR or
26 /* \fImailer-daemon\fR a warning is issued and the message
27 /* is discarded.
28 /* .IP \(bu
29 /* When an alias exists for recipient \fIname\fR, and an alias
30 /* exists for \fIowner-name\fR, the sender address is changed
31 /* to \fIowner-name\fR, and the owner delivery attribute is
32 /* set accordingly. This feature is disabled with
33 /* "owner_request_special = no".
34 /* .PP
35 /* Arguments:
36 /* .IP state
37 /* Attributes that specify the message, recipient and more.
38 /* Expansion type (alias, include, .forward).
39 /* A table with the results from expanding aliases or lists.
40 /* A table with delivered-to: addresses taken from the message.
41 /* .IP usr_attr
42 /* User attributes (rights, environment).
43 /* .IP name
44 /* The alias to be looked up.
45 /* .IP statusp
46 /* Delivery status. See below.
47 /* DIAGNOSTICS
48 /* Fatal errors: out of memory. The delivery status is non-zero
49 /* when delivery should be tried again.
50 /* LICENSE
51 /* .ad
52 /* .fi
53 /* The Secure Mailer license must be distributed with this software.
54 /* AUTHOR(S)
55 /* Wietse Venema
56 /* IBM T.J. Watson Research
57 /* P.O. Box 704
58 /* Yorktown Heights, NY 10598, USA
59 /*--*/
60
61 /* System library. */
62
63 #include <sys_defs.h>
64 #include <sys/stat.h>
65 #include <unistd.h>
66 #include <string.h>
67 #include <fcntl.h>
68 #include <errno.h>
69
70 /* Utility library. */
71
72 #include <msg.h>
73 #include <htable.h>
74 #include <dict.h>
75 #include <argv.h>
76 #include <stringops.h>
77 #include <mymalloc.h>
78 #include <vstring.h>
79 #include <vstream.h>
80
81 /* Global library. */
82
83 #include <mail_params.h>
84 #include <defer.h>
85 #include <maps.h>
86 #include <bounce.h>
87 #include <mypwd.h>
88 #include <canon_addr.h>
89 #include <sent.h>
90 #include <trace.h>
91 #include <dsn_mask.h>
92
93 /* Application-specific. */
94
95 #include "local.h"
96
97 /* Application-specific. */
98
99 #define NO 0
100 #define YES 1
101
102 /* deliver_alias - expand alias file entry */
103
deliver_alias(LOCAL_STATE state,USER_ATTR usr_attr,char * name,int * statusp)104 int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr,
105 char *name, int *statusp)
106 {
107 const char *myname = "deliver_alias";
108 const char *alias_result;
109 char *saved_alias_result;
110 char *owner;
111 char **cpp;
112 struct mypasswd *alias_pwd;
113 VSTRING *canon_owner;
114 DICT *dict;
115 const char *owner_rhs; /* owner alias, RHS */
116 int alias_count;
117 int dsn_notify;
118 char *dsn_envid;
119 int dsn_ret;
120 const char *dsn_orcpt;
121
122 /*
123 * Make verbose logging easier to understand.
124 */
125 state.level++;
126 if (msg_verbose)
127 MSG_LOG_STATE(myname, state);
128
129 /*
130 * DUPLICATE/LOOP ELIMINATION
131 *
132 * We cannot do duplicate elimination here. Sendmail compatibility requires
133 * that we allow multiple deliveries to the same alias, even recursively!
134 * For example, we must deliver to mailbox any messages that are addressed
135 * to the alias of a user that lists that same alias in her own .forward
136 * file. Yuck! This is just an example of some really perverse semantics
137 * that people will expect Postfix to implement just like sendmail.
138 *
139 * We can recognize one special case: when an alias includes its own name,
140 * deliver to the user instead, just like sendmail. Otherwise, we just
141 * bail out when nesting reaches some unreasonable depth, and blame it on
142 * a possible alias loop.
143 */
144 if (state.msg_attr.exp_from != 0
145 && strcasecmp_utf8(state.msg_attr.exp_from, name) == 0)
146 return (NO);
147 if (state.level > 100) {
148 msg_warn("alias database loop for %s", name);
149 dsb_simple(state.msg_attr.why, "5.4.6",
150 "alias database loop for %s", name);
151 *statusp = bounce_append(BOUNCE_FLAGS(state.request),
152 BOUNCE_ATTR(state.msg_attr));
153 return (YES);
154 }
155 state.msg_attr.exp_from = name;
156
157 /*
158 * There are a bunch of roles that we're trying to keep track of.
159 *
160 * First, there's the issue of whose rights should be used when delivering
161 * to "|command" or to /file/name. With alias databases, the rights are
162 * those of who owns the alias, i.e. the database owner. With aliases
163 * owned by root, a default user is used instead. When an alias with
164 * default rights references an include file owned by an ordinary user,
165 * we must use the rights of the include file owner, otherwise the
166 * include file owner could take control of the default account.
167 *
168 * Secondly, there's the question of who to notify of delivery problems.
169 * With aliases that have an owner- alias, the latter is used to set the
170 * sender and owner attributes. Otherwise, the owner attribute is reset
171 * (the alias is globally visible and could be sent to by anyone).
172 */
173 for (cpp = alias_maps->argv->argv; *cpp; cpp++) {
174 if ((dict = dict_handle(*cpp)) == 0)
175 msg_panic("%s: dictionary not found: %s", myname, *cpp);
176 if ((alias_result = dict_get(dict, name)) != 0) {
177 if (msg_verbose)
178 msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result);
179
180 /*
181 * Don't expand a verify-only request.
182 */
183 if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) {
184 dsb_simple(state.msg_attr.why, "2.0.0",
185 "aliased to %s", alias_result);
186 *statusp = sent(BOUNCE_FLAGS(state.request),
187 SENT_ATTR(state.msg_attr));
188 return (YES);
189 }
190
191 /*
192 * DELIVERY POLICY
193 *
194 * Update the expansion type attribute, so we can decide if
195 * deliveries to |command and /file/name are allowed at all.
196 */
197 state.msg_attr.exp_type = EXPAND_TYPE_ALIAS;
198
199 /*
200 * DELIVERY RIGHTS
201 *
202 * What rights to use for |command and /file/name deliveries? The
203 * command and file code will use default rights when the alias
204 * database is owned by root, otherwise it will use the rights of
205 * the alias database owner.
206 */
207 if (dict->owner.status == DICT_OWNER_TRUSTED) {
208 alias_pwd = 0;
209 RESET_USER_ATTR(usr_attr, state.level);
210 } else {
211 if (dict->owner.status == DICT_OWNER_UNKNOWN) {
212 msg_warn("%s: no owner UID for alias database %s",
213 myname, *cpp);
214 dsb_simple(state.msg_attr.why, "4.3.0",
215 "mail system configuration error");
216 *statusp = defer_append(BOUNCE_FLAGS(state.request),
217 BOUNCE_ATTR(state.msg_attr));
218 return (YES);
219 }
220 if ((errno = mypwuid_err(dict->owner.uid, &alias_pwd)) != 0
221 || alias_pwd == 0) {
222 msg_warn(errno ?
223 "cannot find alias database owner for %s: %m" :
224 "cannot find alias database owner for %s", *cpp);
225 dsb_simple(state.msg_attr.why, "4.3.0",
226 "cannot find alias database owner");
227 *statusp = defer_append(BOUNCE_FLAGS(state.request),
228 BOUNCE_ATTR(state.msg_attr));
229 return (YES);
230 }
231 SET_USER_ATTR(usr_attr, alias_pwd, state.level);
232 }
233
234 /*
235 * WHERE TO REPORT DELIVERY PROBLEMS.
236 *
237 * Use the owner- alias if one is specified, otherwise reset the
238 * owner attribute and use the include file ownership if we can.
239 * Save the dict_lookup() result before something clobbers it.
240 *
241 * Don't match aliases that are based on regexps.
242 */
243 #define OWNER_ASSIGN(own) \
244 (own = (var_ownreq_special == 0 ? 0 : \
245 concatenate("owner-", name, (char *) 0)))
246
247 saved_alias_result = mystrdup(alias_result);
248 if (OWNER_ASSIGN(owner) != 0
249 && (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) {
250 canon_owner = canon_addr_internal(vstring_alloc(10),
251 var_exp_own_alias ? owner_rhs : owner);
252 /* Set envelope sender and owner attribute. */
253 SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
254 } else {
255 canon_owner = 0;
256 /* Note: this does not reset the envelope sender. */
257 if (var_reset_owner_attr)
258 RESET_OWNER_ATTR(state.msg_attr, state.level);
259 }
260
261 /*
262 * EXTERNAL LOOP CONTROL
263 *
264 * Set the delivered message attribute to the recipient, so that
265 * this message will list the correct forwarding address.
266 */
267 if (var_frozen_delivered == 0)
268 state.msg_attr.delivered = state.msg_attr.rcpt.address;
269
270 /*
271 * Deliver.
272 */
273 alias_count = 0;
274 if (owner != 0 && alias_maps->error != 0) {
275 dsb_simple(state.msg_attr.why, "4.3.0",
276 "alias database unavailable");
277 *statusp = defer_append(BOUNCE_FLAGS(state.request),
278 BOUNCE_ATTR(state.msg_attr));
279 } else {
280
281 /*
282 * XXX DSN
283 *
284 * When delivering to a mailing list (i.e. the envelope sender
285 * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters
286 * which accompany the redistributed message MUST NOT be
287 * derived from those of the original message.
288 *
289 * When delivering to an alias (i.e. the envelope sender is not
290 * replaced) any ENVID, RET, or ORCPT parameters are
291 * propagated to all forwarding addresses associated with
292 * that alias. The NOTIFY parameter is propagated to the
293 * forwarding addresses, except that any SUCCESS keyword is
294 * removed.
295 */
296 #define DSN_SAVE_UPDATE(saved, old, new) do { \
297 saved = old; \
298 old = new; \
299 } while (0)
300
301 DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify,
302 dsn_notify == DSN_NOTIFY_SUCCESS ?
303 DSN_NOTIFY_NEVER :
304 dsn_notify & ~DSN_NOTIFY_SUCCESS);
305 if (canon_owner != 0) {
306 DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, "");
307 DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0);
308 DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, "");
309 state.msg_attr.rcpt.orig_addr = "";
310 }
311 *statusp =
312 deliver_token_string(state, usr_attr, saved_alias_result,
313 &alias_count);
314 #if 0
315 if (var_ownreq_special
316 && strncmp("owner-", state.msg_attr.sender, 6) != 0
317 && alias_count > 10)
318 msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias",
319 name, name);
320 #endif
321 if (alias_count < 1) {
322 msg_warn("no recipient in alias lookup result for %s", name);
323 dsb_simple(state.msg_attr.why, "4.3.0",
324 "alias database unavailable");
325 *statusp = defer_append(BOUNCE_FLAGS(state.request),
326 BOUNCE_ATTR(state.msg_attr));
327 } else {
328
329 /*
330 * XXX DSN
331 *
332 * When delivering to a mailing list (i.e. the envelope
333 * sender address is replaced) and NOTIFY=SUCCESS was
334 * specified, report a DSN of "delivered".
335 *
336 * When delivering to an alias (i.e. the envelope sender
337 * address is not replaced) and NOTIFY=SUCCESS was
338 * specified, report a DSN of "expanded".
339 */
340 if (dsn_notify & DSN_NOTIFY_SUCCESS) {
341 state.msg_attr.rcpt.dsn_notify = dsn_notify;
342 if (canon_owner != 0) {
343 state.msg_attr.dsn_envid = dsn_envid;
344 state.msg_attr.dsn_ret = dsn_ret;
345 state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt;
346 }
347 dsb_update(state.msg_attr.why, "2.0.0", canon_owner ?
348 "delivered" : "expanded",
349 DSB_SKIP_RMTA, DSB_SKIP_REPLY,
350 "alias expanded");
351 (void) trace_append(BOUNCE_FLAG_NONE,
352 SENT_ATTR(state.msg_attr));
353 }
354 }
355 }
356 myfree(saved_alias_result);
357 if (owner)
358 myfree(owner);
359 if (canon_owner)
360 vstring_free(canon_owner);
361 if (alias_pwd)
362 mypwfree(alias_pwd);
363 return (YES);
364 }
365
366 /*
367 * If the alias database was inaccessible for some reason, defer
368 * further delivery for the current top-level recipient.
369 */
370 if (alias_result == 0 && dict->error != 0) {
371 msg_warn("%s:%s: lookup of '%s' failed",
372 dict->type, dict->name, name);
373 dsb_simple(state.msg_attr.why, "4.3.0",
374 "alias database unavailable");
375 *statusp = defer_append(BOUNCE_FLAGS(state.request),
376 BOUNCE_ATTR(state.msg_attr));
377 return (YES);
378 } else {
379 if (msg_verbose)
380 msg_info("%s: %s: %s not found", myname, *cpp, name);
381 }
382 }
383
384 /*
385 * Try delivery to a local user instead.
386 */
387 return (NO);
388 }
389