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