xref: /netbsd-src/external/ibm-public/postfix/dist/src/local/alias.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*	$NetBSD: alias.c,v 1.1.1.2 2011/03/02 19:32:19 tron 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 
69 #ifdef STRCASECMP_IN_STRINGS_H
70 #include <strings.h>
71 #endif
72 
73 /* Utility library. */
74 
75 #include <msg.h>
76 #include <htable.h>
77 #include <dict.h>
78 #include <argv.h>
79 #include <stringops.h>
80 #include <mymalloc.h>
81 #include <vstring.h>
82 #include <vstream.h>
83 
84 /* Global library. */
85 
86 #include <mail_params.h>
87 #include <defer.h>
88 #include <maps.h>
89 #include <bounce.h>
90 #include <mypwd.h>
91 #include <canon_addr.h>
92 #include <sent.h>
93 #include <trace.h>
94 #include <dsn_mask.h>
95 
96 /* Application-specific. */
97 
98 #include "local.h"
99 
100 /* Application-specific. */
101 
102 #define NO	0
103 #define YES	1
104 
105 /* dict_owner - find out alias database owner */
106 
107 static uid_t dict_owner(char *table)
108 {
109     const char *myname = "dict_owner";
110     DICT   *dict;
111     struct stat st;
112 
113     /*
114      * This code sits here for now, but we may want to move it to the library
115      * some time.
116      */
117     if ((dict = dict_handle(table)) == 0)
118 	msg_panic("%s: can't find dictionary: %s", myname, table);
119     if (dict->stat_fd < 0)
120 	return (0);
121     if (fstat(dict->stat_fd, &st) < 0)
122 	msg_fatal("%s: fstat dictionary %s: %m", myname, table);
123     return (st.st_uid);
124 }
125 
126 /* deliver_alias - expand alias file entry */
127 
128 int     deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr,
129 		              char *name, int *statusp)
130 {
131     const char *myname = "deliver_alias";
132     const char *alias_result;
133     char   *saved_alias_result;
134     char   *owner;
135     char  **cpp;
136     uid_t   alias_uid;
137     struct mypasswd *alias_pwd;
138     VSTRING *canon_owner;
139     DICT   *dict;
140     const char *owner_rhs;		/* owner alias, RHS */
141     int     alias_count;
142     int     dsn_notify;
143     char   *dsn_envid;
144     int     dsn_ret;
145     const char *dsn_orcpt;
146 
147     /*
148      * Make verbose logging easier to understand.
149      */
150     state.level++;
151     if (msg_verbose)
152 	MSG_LOG_STATE(myname, state);
153 
154     /*
155      * DUPLICATE/LOOP ELIMINATION
156      *
157      * We cannot do duplicate elimination here. Sendmail compatibility requires
158      * that we allow multiple deliveries to the same alias, even recursively!
159      * For example, we must deliver to mailbox any messags that are addressed
160      * to the alias of a user that lists that same alias in her own .forward
161      * file. Yuck! This is just an example of some really perverse semantics
162      * that people will expect Postfix to implement just like sendmail.
163      *
164      * We can recognize one special case: when an alias includes its own name,
165      * deliver to the user instead, just like sendmail. Otherwise, we just
166      * bail out when nesting reaches some unreasonable depth, and blame it on
167      * a possible alias loop.
168      */
169     if (state.msg_attr.exp_from != 0
170 	&& strcasecmp(state.msg_attr.exp_from, name) == 0)
171 	return (NO);
172     if (state.level > 100) {
173 	msg_warn("alias database loop for %s", name);
174 	dsb_simple(state.msg_attr.why, "5.4.6",
175 		   "alias database loop for %s", name);
176 	*statusp = bounce_append(BOUNCE_FLAGS(state.request),
177 				 BOUNCE_ATTR(state.msg_attr));
178 	return (YES);
179     }
180     state.msg_attr.exp_from = name;
181 
182     /*
183      * There are a bunch of roles that we're trying to keep track of.
184      *
185      * First, there's the issue of whose rights should be used when delivering
186      * to "|command" or to /file/name. With alias databases, the rights are
187      * those of who owns the alias, i.e. the database owner. With aliases
188      * owned by root, a default user is used instead. When an alias with
189      * default rights references an include file owned by an ordinary user,
190      * we must use the rights of the include file owner, otherwise the
191      * include file owner could take control of the default account.
192      *
193      * Secondly, there's the question of who to notify of delivery problems.
194      * With aliases that have an owner- alias, the latter is used to set the
195      * sender and owner attributes. Otherwise, the owner attribute is reset
196      * (the alias is globally visible and could be sent to by anyone).
197      */
198     for (cpp = alias_maps->argv->argv; *cpp; cpp++) {
199 	if ((dict = dict_handle(*cpp)) == 0)
200 	    msg_panic("%s: dictionary not found: %s", myname, *cpp);
201 	if ((alias_result = dict_get(dict, name)) != 0) {
202 	    if (msg_verbose)
203 		msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result);
204 
205 	    /*
206 	     * Don't expand a verify-only request.
207 	     */
208 	    if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) {
209 		dsb_simple(state.msg_attr.why, "2.0.0",
210 			   "aliased to %s", alias_result);
211 		*statusp = sent(BOUNCE_FLAGS(state.request),
212 				SENT_ATTR(state.msg_attr));
213 		return (YES);
214 	    }
215 
216 	    /*
217 	     * DELIVERY POLICY
218 	     *
219 	     * Update the expansion type attribute, so we can decide if
220 	     * deliveries to |command and /file/name are allowed at all.
221 	     */
222 	    state.msg_attr.exp_type = EXPAND_TYPE_ALIAS;
223 
224 	    /*
225 	     * DELIVERY RIGHTS
226 	     *
227 	     * What rights to use for |command and /file/name deliveries? The
228 	     * command and file code will use default rights when the alias
229 	     * database is owned by root, otherwise it will use the rights of
230 	     * the alias database owner.
231 	     */
232 	    if ((alias_uid = dict_owner(*cpp)) == 0) {
233 		alias_pwd = 0;
234 		RESET_USER_ATTR(usr_attr, state.level);
235 	    } else {
236 		if ((alias_pwd = mypwuid(alias_uid)) == 0) {
237 		    msg_warn("cannot find alias database owner for %s", *cpp);
238 		    dsb_simple(state.msg_attr.why, "4.3.0",
239 			       "cannot find alias database owner");
240 		    *statusp = defer_append(BOUNCE_FLAGS(state.request),
241 					    BOUNCE_ATTR(state.msg_attr));
242 		    return (YES);
243 		}
244 		SET_USER_ATTR(usr_attr, alias_pwd, state.level);
245 	    }
246 
247 	    /*
248 	     * WHERE TO REPORT DELIVERY PROBLEMS.
249 	     *
250 	     * Use the owner- alias if one is specified, otherwise reset the
251 	     * owner attribute and use the include file ownership if we can.
252 	     * Save the dict_lookup() result before something clobbers it.
253 	     *
254 	     * Don't match aliases that are based on regexps.
255 	     */
256 #define OWNER_ASSIGN(own) \
257 	    (own = (var_ownreq_special == 0 ? 0 : \
258 	    concatenate("owner-", name, (char *) 0)))
259 
260 	    saved_alias_result = mystrdup(alias_result);
261 	    if (OWNER_ASSIGN(owner) != 0
262 		&& (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) {
263 		canon_owner = canon_addr_internal(vstring_alloc(10),
264 				     var_exp_own_alias ? owner_rhs : owner);
265 		/* Set envelope sender and owner attribute. */
266 		SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
267 	    } else {
268 		canon_owner = 0;
269 		/* Note: this does not reset the envelope sender. */
270 		if (var_reset_owner_attr)
271 		    RESET_OWNER_ATTR(state.msg_attr, state.level);
272 	    }
273 
274 	    /*
275 	     * EXTERNAL LOOP CONTROL
276 	     *
277 	     * Set the delivered message attribute to the recipient, so that
278 	     * this message will list the correct forwarding address.
279 	     */
280 	    if (var_frozen_delivered == 0)
281 		state.msg_attr.delivered = state.msg_attr.rcpt.address;
282 
283 	    /*
284 	     * Deliver.
285 	     */
286 	    alias_count = 0;
287 	    if (dict_errno != 0) {
288 		dsb_simple(state.msg_attr.why, "4.3.0",
289 			   "alias database unavailable");
290 		*statusp = defer_append(BOUNCE_FLAGS(state.request),
291 					BOUNCE_ATTR(state.msg_attr));
292 	    } else {
293 
294 		/*
295 		 * XXX DSN
296 		 *
297 		 * When delivering to a mailing list (i.e. the envelope sender
298 		 * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters
299 		 * which accompany the redistributed message MUST NOT be
300 		 * derived from those of the original message.
301 		 *
302 		 * When delivering to an alias (i.e. the envelope sender is not
303 		 * replaced) any ENVID, RET, or ORCPT parameters are
304 		 * propagated to all forwarding addresses associated with
305 		 * that alias.  The NOTIFY parameter is propagated to the
306 		 * forwarding addresses, except that any SUCCESS keyword is
307 		 * removed.
308 		 */
309 #define DSN_SAVE_UPDATE(saved, old, new) do { \
310 	saved = old; \
311 	old = new; \
312     } while (0)
313 
314 		DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify,
315 				dsn_notify == DSN_NOTIFY_SUCCESS ?
316 				DSN_NOTIFY_NEVER :
317 				dsn_notify & ~DSN_NOTIFY_SUCCESS);
318 		if (canon_owner != 0) {
319 		    DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, "");
320 		    DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0);
321 		    DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, "");
322 		    state.msg_attr.rcpt.orig_addr = "";
323 		}
324 		*statusp =
325 		    deliver_token_string(state, usr_attr, saved_alias_result,
326 					 &alias_count);
327 #if 0
328 		if (var_ownreq_special
329 		    && strncmp("owner-", state.msg_attr.sender, 6) != 0
330 		    && alias_count > 10)
331 		    msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias",
332 			     name, name);
333 #endif
334 		if (alias_count < 1) {
335 		    msg_warn("no recipient in alias lookup result for %s", name);
336 		    dsb_simple(state.msg_attr.why, "4.3.0",
337 			       "alias database unavailable");
338 		    *statusp = defer_append(BOUNCE_FLAGS(state.request),
339 					    BOUNCE_ATTR(state.msg_attr));
340 		} else {
341 
342 		    /*
343 		     * XXX DSN
344 		     *
345 		     * When delivering to a mailing list (i.e. the envelope
346 		     * sender address is replaced) and NOTIFY=SUCCESS was
347 		     * specified, report a DSN of "delivered".
348 		     *
349 		     * When delivering to an alias (i.e. the envelope sender
350 		     * address is not replaced) and NOTIFY=SUCCESS was
351 		     * specified, report a DSN of "expanded".
352 		     */
353 		    if (dsn_notify & DSN_NOTIFY_SUCCESS) {
354 			state.msg_attr.rcpt.dsn_notify = dsn_notify;
355 			if (canon_owner != 0) {
356 			    state.msg_attr.dsn_envid = dsn_envid;
357 			    state.msg_attr.dsn_ret = dsn_ret;
358 			    state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt;
359 			}
360 			dsb_update(state.msg_attr.why, "2.0.0", canon_owner ?
361 				   "delivered" : "expanded",
362 				   DSB_SKIP_RMTA, DSB_SKIP_REPLY,
363 				   "alias expanded");
364 			(void) trace_append(BOUNCE_FLAG_NONE,
365 					    SENT_ATTR(state.msg_attr));
366 		    }
367 		}
368 	    }
369 	    myfree(saved_alias_result);
370 	    if (owner)
371 		myfree(owner);
372 	    if (canon_owner)
373 		vstring_free(canon_owner);
374 	    if (alias_pwd)
375 		mypwfree(alias_pwd);
376 	    return (YES);
377 	}
378 
379 	/*
380 	 * If the alias database was inaccessible for some reason, defer
381 	 * further delivery for the current top-level recipient.
382 	 */
383 	if (dict_errno != 0) {
384 	    dsb_simple(state.msg_attr.why, "4.3.0",
385 		       "alias database unavailable");
386 	    *statusp = defer_append(BOUNCE_FLAGS(state.request),
387 				    BOUNCE_ATTR(state.msg_attr));
388 	    return (YES);
389 	} else {
390 	    if (msg_verbose)
391 		msg_info("%s: %s: %s not found", myname, *cpp, name);
392 	}
393     }
394 
395     /*
396      * Try delivery to a local user instead.
397      */
398     return (NO);
399 }
400