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