static int deliver_message(DELIVER_REQUEST *request) { char *myname = "deliver_message"; VSTREAM *src; int result = 0; int status; RECIPIENT *rcpt; int nrcpt; if (msg_verbose) msg_info("deliver_message: from %s", request->sender); /* * Sanity checks. */ if (request->nexthop[0] == 0) msg_fatal("empty nexthop hostname"); if (request->rcpt_list.len <= 0) msg_fatal("recipient count: %d", request->rcpt_list.len); /* * Open the queue file. Opening the file can fail for a variety of * reasons, such as the system running out of resources. Instead of * throwing away mail, we're raising a fatal error which forces the mail * system to back off, and retry later. */ src = mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0); if (src == 0) msg_fatal("%s: open %s %s: %m", myname, request->queue_name, request->queue_id); if (msg_verbose) msg_info("%s: file %s", myname, VSTREAM_PATH(src)); /* * Bounce all recipients. */ #define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags) for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (rcpt->offset >= 0) { status = bounce_append(BOUNCE_FLAGS(request), request->queue_id, rcpt->orig_addr, rcpt->address, rcpt->offset, "none", request->arrival_time, "%s", request->nexthop); if (status == 0) deliver_completed(src, rcpt->offset); result |= status; } } /* * Clean up. */ if (vstream_fclose(src)) msg_warn("close %s %s: %m", request->queue_name, request->queue_id); return (result); }
int deliver_unknown(LOCAL_STATE state) { char *myname = "deliver_unknown"; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr), "unknown user: \"%s\"", state.msg_attr.user)); }
static void cleanup_bounce_append(CLEANUP_STATE *state, RECIPIENT *rcpt, DSN *dsn) { MSG_STATS stats; /* * Don't log a spurious warning (for example, when soft_bounce is turned * on). bounce_append() already logs a record when the logfile can't be * updated. Set the write error flag, so that a maildrop queue file won't * be destroyed. */ if (bounce_append(BOUNCE_FLAG_CLEAN, state->queue_id, CLEANUP_MSG_STATS(&stats, state), rcpt, "none", dsn) != 0) { state->errs |= CLEANUP_STAT_WRITE; } }
int deliver_include(LOCAL_STATE state, USER_ATTR usr_attr, char *path) { const char *myname = "deliver_include"; struct stat st; struct mypasswd *file_pwd = 0; int status; VSTREAM *fp; int fd; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Don't process this include file more than once as this particular user. */ if (been_here(state.dup_filter, "include %ld %s", (long) usr_attr.uid, path)) return (0); state.msg_attr.exp_from = state.msg_attr.local; /* * Can of worms. Allow this include file to be symlinked, but disallow * inclusion of special files or of files with world write permission * enabled. */ if (*path != '/') { msg_warn(":include:%s uses a relative path", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (stat_as(path, &st, usr_attr.uid, usr_attr.gid) < 0) { msg_warn("unable to lookup :include: file %s: %m", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (S_ISREG(st.st_mode) == 0) { msg_warn(":include: file %s is not a regular file", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (st.st_mode & S_IWOTH) { msg_warn(":include: file %s is world writable", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } /* * DELIVERY POLICY * * Set the expansion type attribute so that we can decide if destinations * such as /file/name and |command are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_INCL; /* * DELIVERY RIGHTS * * When a non-root include file is listed in a root-owned alias, use the * rights of the include file owner. We do not want to give the include * file owner control of the default account. * * When an include file is listed in a user-owned alias or .forward file, * leave the delivery rights alone. Users should not be able to make * things happen with someone else's rights just by including some file * that is owned by their victim. */ if (usr_attr.uid == 0) { if ((errno = mypwuid_err(st.st_uid, &file_pwd)) != 0 || file_pwd == 0) { msg_warn(errno ? "cannot find username for uid %ld: %m" : "cannot find username for uid %ld", (long) st.st_uid); msg_warn("%s: cannot find :include: file owner username", path); dsb_simple(state.msg_attr.why, "4.3.5", "mail system configuration error"); return (defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (file_pwd->pw_uid != 0) SET_USER_ATTR(usr_attr, file_pwd, state.level); } /* * MESSAGE FORWARDING * * When no owner attribute is set (either via an owner- alias, or as part of * .forward file processing), set the owner attribute, to disable direct * delivery of local recipients. By now it is clear that the owner * attribute should have been called forwarder instead. */ if (state.msg_attr.owner == 0) state.msg_attr.owner = state.msg_attr.rcpt.address; /* * From here on no early returns or we have a memory leak. * * FILE OPEN RIGHTS * * Use the delivery rights to open the include file. When no delivery rights * were established sofar, the file containing the :include: is owned by * root, so it should be OK to open any file that is accessible to root. * The command and file delivery routines are responsible for setting the * proper delivery rights. These are the rights of the default user, in * case the :include: is in a root-owned alias. * * Don't propagate unmatched extensions unless permitted to do so. */ #define FOPEN_AS(p,u,g) ((fd = open_as(p,O_RDONLY,0,u,g)) >= 0 ? \ vstream_fdopen(fd,O_RDONLY) : 0) if ((fp = FOPEN_AS(path, usr_attr.uid, usr_attr.gid)) == 0) { msg_warn("cannot open include file %s: %m", path); dsb_simple(state.msg_attr.why, "5.3.5", "mail system configuration error"); status = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { if ((local_ext_prop_mask & EXT_PROP_INCLUDE) == 0) state.msg_attr.unmatched = 0; close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); status = deliver_token_stream(state, usr_attr, fp, (int *) 0); if (vstream_fclose(fp)) msg_warn("close %s: %m", path); } /* * Cleanup. */ if (file_pwd) mypwfree(file_pwd); return (status); }
int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_recipient"; int rcpt_stat; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Duplicate filter. */ if (been_here(state.dup_filter, "recipient %d %s", state.level, state.msg_attr.rcpt.address)) return (0); /* * With each level of recursion, detect and break external message * forwarding loops. * * If the looping recipient address has an owner- alias, send the error * report there instead. * * XXX A delivery agent cannot change the envelope sender address for * bouncing. As a workaround we use a one-recipient bounce procedure. * * The proper fix would be to record in the bounce logfile an error return * address for each individual recipient. This would also eliminate the * need for VERP specific bouncing code, at the cost of complicating the * normal bounce sending procedure, but would simplify the code below. */ if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) { dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s", state.msg_attr.rcpt.address); /* Account for possible owner- sender address override. */ return (bounce_workaround(state)); } /* * Set up the recipient-specific attributes. If this is forwarded mail, * leave the delivered attribute alone, so that the forwarded message * will show the correct forwarding recipient. */ if (state.msg_attr.delivered == 0) state.msg_attr.delivered = state.msg_attr.rcpt.address; state.msg_attr.local = mystrdup(state.msg_attr.rcpt.address); lowercase(state.msg_attr.local); if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0) msg_warn("no @ in recipient address: %s", state.msg_attr.local); /* * Address extension management. */ state.msg_attr.user = mystrdup(state.msg_attr.local); if (*var_rcpt_delim) { state.msg_attr.extension = split_addr(state.msg_attr.user, *var_rcpt_delim); if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) { msg_warn("%s: address with illegal extension: %s", state.msg_attr.queue_id, state.msg_attr.local); state.msg_attr.extension = 0; } } else state.msg_attr.extension = 0; state.msg_attr.unmatched = state.msg_attr.extension; /* * Do not allow null usernames. */ if (state.msg_attr.user[0] == 0) { dsb_simple(state.msg_attr.why, "5.1.3", "null username in \"%s\"", state.msg_attr.rcpt.address); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } /* * Run the recipient through the delivery switch. */ if (msg_verbose) deliver_attr_dump(&state.msg_attr); rcpt_stat = deliver_switch(state, usr_attr); /* * Clean up. */ myfree(state.msg_attr.local); myfree(state.msg_attr.user); return (rcpt_stat); }
int deliver_unknown(LOCAL_STATE state, USER_ATTR usr_attr) { const char *myname = "deliver_unknown"; int status; VSTRING *expand_luser; static MAPS *transp_maps; const char *map_transport; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE/LOOP ELIMINATION * * Don't deliver the same user twice. */ if (been_here(state.dup_filter, "%s %s", myname, state.msg_attr.local)) return (0); /* * The fall-back transport specifies a delivery machanism that handles * users not found in the aliases or UNIX passwd databases. */ if (*var_fbck_transp_maps && transp_maps == 0) transp_maps = maps_create(VAR_FBCK_TRANSP_MAPS, var_fbck_transp_maps, DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB); /* The -1 is a hint for the down-stream deliver_completed() function. */ if (transp_maps && (map_transport = maps_find(transp_maps, state.msg_attr.user, DICT_FLAG_NONE)) != 0) { state.msg_attr.rcpt.offset = -1L; return (deliver_pass(MAIL_CLASS_PRIVATE, map_transport, state.request, &state.msg_attr.rcpt)); } else if (transp_maps && transp_maps->error != 0) { /* Details in the logfile. */ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); return (defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } if (*var_fallback_transport) { state.msg_attr.rcpt.offset = -1L; return (deliver_pass(MAIL_CLASS_PRIVATE, var_fallback_transport, state.request, &state.msg_attr.rcpt)); } /* * Subject the luser_relay address to $name expansion, disable * propagation of unmatched address extension, and re-inject the address * into the delivery machinery. Do not give special treatment to "|stuff" * or /stuff. */ if (*var_luser_relay) { state.msg_attr.unmatched = 0; expand_luser = vstring_alloc(100); local_expand(expand_luser, var_luser_relay, &state, &usr_attr, (char *) 0); status = deliver_resolve_addr(state, usr_attr, STR(expand_luser)); vstring_free(expand_luser); return (status); } /* * If no alias was found for a required reserved name, toss the message * into the bit bucket, and issue a warning instead. */ #define STREQ(x,y) (strcasecmp(x,y) == 0) if (STREQ(state.msg_attr.local, MAIL_ADDR_MAIL_DAEMON) || STREQ(state.msg_attr.local, MAIL_ADDR_POSTMASTER)) { msg_warn("required alias not found: %s", state.msg_attr.local); dsb_simple(state.msg_attr.why, "2.0.0", "discarded"); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * Bounce the message when no luser relay is specified. */ dsb_simple(state.msg_attr.why, "5.1.1", "unknown user: \"%s\"", state.msg_attr.local); return (bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); }
int deliver_resolve_tree(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr) { const char *myname = "deliver_resolve_tree"; RESOLVE_REPLY reply; int status; ssize_t ext_len; char *ratsign; int rcpt_delim; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Initialize. */ resolve_clnt_init(&reply); /* * Rewrite the address to canonical form, just like the cleanup service * does. Then, resolve the address to (transport, nexhop, recipient), * just like the queue manager does. The only part missing here is the * virtual address substitution. Message forwarding fixes most of that. */ tok822_rewrite(addr, REWRITE_CANON); tok822_resolve(addr, &reply); /* * First, a healthy portion of error handling. */ if (reply.flags & RESOLVE_FLAG_FAIL) { dsb_simple(state.msg_attr.why, "4.3.0", "address resolver failure"); status = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else if (reply.flags & RESOLVE_FLAG_ERROR) { dsb_simple(state.msg_attr.why, "5.1.3", "bad recipient address syntax: %s", STR(reply.recipient)); status = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { /* * Splice in the optional unmatched address extension. */ if (state.msg_attr.unmatched) { rcpt_delim = state.msg_attr.local[strlen(state.msg_attr.user)]; if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) { VSTRING_ADDCH(reply.recipient, rcpt_delim); vstring_strcat(reply.recipient, state.msg_attr.unmatched); } else { ext_len = strlen(state.msg_attr.unmatched); VSTRING_SPACE(reply.recipient, ext_len + 2); if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) msg_panic("%s: recipient @ botch", myname); memmove(ratsign + ext_len + 1, ratsign, strlen(ratsign) + 1); *ratsign = rcpt_delim; memcpy(ratsign + 1, state.msg_attr.unmatched, ext_len); VSTRING_SKIP(reply.recipient); } } state.msg_attr.rcpt.address = STR(reply.recipient); /* * Delivery to a local or non-local address. For a while there was * some ugly code to force local recursive alias expansions on a host * with no authority over the local domain, but that code was just * too unclean. */ if (strcmp(state.msg_attr.relay, STR(reply.transport)) == 0) { status = deliver_recipient(state, usr_attr); } else { status = deliver_indirect(state); } } /* * Cleanup. */ resolve_clnt_free(&reply); return (status); }
int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, char *name, int *statusp) { char *myname = "deliver_alias"; const char *alias_result; char *expansion; char *owner; char **cpp; uid_t alias_uid; struct mypasswd *alias_pwd; VSTRING *canon_owner; DICT *dict; const char *owner_rhs; /* owner alias, RHS */ int alias_count; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE/LOOP ELIMINATION * * We cannot do duplicate elimination here. Sendmail compatibility requires * that we allow multiple deliveries to the same alias, even recursively! * For example, we must deliver to mailbox any messags that are addressed * to the alias of a user that lists that same alias in her own .forward * file. Yuck! This is just an example of some really perverse semantics * that people will expect Postfix to implement just like sendmail. * * We can recognize one special case: when an alias includes its own name, * deliver to the user instead, just like sendmail. Otherwise, we just * bail out when nesting reaches some unreasonable depth, and blame it on * a possible alias loop. */ if (state.msg_attr.exp_from != 0 && strcasecmp(state.msg_attr.exp_from, name) == 0) return (NO); if (state.level > 100) { msg_warn("possible alias database loop for %s", name); *statusp = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr), "possible alias database loop for %s", name); return (YES); } state.msg_attr.exp_from = name; /* * There are a bunch of roles that we're trying to keep track of. * * First, there's the issue of whose rights should be used when delivering * to "|command" or to /file/name. With alias databases, the rights are * those of who owns the alias, i.e. the database owner. With aliases * owned by root, a default user is used instead. When an alias with * default rights references an include file owned by an ordinary user, * we must use the rights of the include file owner, otherwise the * include file owner could take control of the default account. * * Secondly, there's the question of who to notify of delivery problems. * With aliases that have an owner- alias, the latter is used to set the * sender and owner attributes. Otherwise, the owner attribute is reset * (the alias is globally visible and could be sent to by anyone). */ for (cpp = alias_maps->argv->argv; *cpp; cpp++) { if ((dict = dict_handle(*cpp)) == 0) msg_panic("%s: dictionary not found: %s", myname, *cpp); if ((alias_result = dict_get(dict, name)) != 0) { if (msg_verbose) msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result); /* * Don't expand a verify-only request. */ if (state.request->flags & DEL_REQ_FLAG_VERIFY) { *statusp = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr), "aliased to %s", alias_result); return (YES); } /* * DELIVERY POLICY * * Update the expansion type attribute, so we can decide if * deliveries to |command and /file/name are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_ALIAS; /* * DELIVERY RIGHTS * * What rights to use for |command and /file/name deliveries? The * command and file code will use default rights when the alias * database is owned by root, otherwise it will use the rights of * the alias database owner. */ if ((alias_uid = dict_owner(*cpp)) == 0) { alias_pwd = 0; RESET_USER_ATTR(usr_attr, state.level); } else { if ((alias_pwd = mypwuid(alias_uid)) == 0) { msg_warn("cannot find alias database owner for %s", *cpp); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr), "cannot find alias database owner"); return (YES); } SET_USER_ATTR(usr_attr, alias_pwd, state.level); } /* * WHERE TO REPORT DELIVERY PROBLEMS. * * Use the owner- alias if one is specified, otherwise reset the * owner attribute and use the include file ownership if we can. * Save the dict_lookup() result before something clobbers it. * * Don't match aliases that are based on regexps. */ #define STR(x) vstring_str(x) #define OWNER_ASSIGN(own) \ (own = (var_ownreq_special == 0 ? 0 : \ concatenate("owner-", name, (char *) 0))) expansion = mystrdup(alias_result); if (OWNER_ASSIGN(owner) != 0 && (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) { canon_owner = canon_addr_internal(vstring_alloc(10), var_exp_own_alias ? owner_rhs : owner); SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level); } else { canon_owner = 0; RESET_OWNER_ATTR(state.msg_attr, state.level); } /* * EXTERNAL LOOP CONTROL * * Set the delivered message attribute to the recipient, so that * this message will list the correct forwarding address. */ state.msg_attr.delivered = state.msg_attr.recipient; /* * Deliver. */ alias_count = 0; *statusp = (dict_errno ? defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr), "alias database unavailable") : deliver_token_string(state, usr_attr, expansion, &alias_count)); #if 0 if (var_ownreq_special && strncmp("owner-", state.msg_attr.sender, 6) != 0 && alias_count > 10) msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias", name, name); #endif if (alias_count < 1) { msg_warn("no recipient in alias lookup result for %s", name); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr), "alias database unavailable"); } myfree(expansion); if (owner) myfree(owner); if (canon_owner) vstring_free(canon_owner); if (alias_pwd) mypwfree(alias_pwd); return (YES); } /* * If the alias database was inaccessible for some reason, defer * further delivery for the current top-level recipient. */ if (dict_errno != 0) { *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr), "alias database unavailable"); return (YES); } else { if (msg_verbose) msg_info("%s: %s: %s not found", myname, *cpp, name); } } /* * Try delivery to a local user instead. */ return (NO); }
int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, char *name, int *statusp) { const char *myname = "deliver_alias"; const char *alias_result; char *saved_alias_result; char *owner; char **cpp; uid_t alias_uid; struct mypasswd *alias_pwd; VSTRING *canon_owner; DICT *dict; const char *owner_rhs; /* owner alias, RHS */ int alias_count; int dsn_notify; char *dsn_envid; int dsn_ret; const char *dsn_orcpt; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE/LOOP ELIMINATION * * We cannot do duplicate elimination here. Sendmail compatibility requires * that we allow multiple deliveries to the same alias, even recursively! * For example, we must deliver to mailbox any messags that are addressed * to the alias of a user that lists that same alias in her own .forward * file. Yuck! This is just an example of some really perverse semantics * that people will expect Postfix to implement just like sendmail. * * We can recognize one special case: when an alias includes its own name, * deliver to the user instead, just like sendmail. Otherwise, we just * bail out when nesting reaches some unreasonable depth, and blame it on * a possible alias loop. */ if (state.msg_attr.exp_from != 0 && strcasecmp(state.msg_attr.exp_from, name) == 0) return (NO); if (state.level > 100) { msg_warn("alias database loop for %s", name); dsb_simple(state.msg_attr.why, "5.4.6", "alias database loop for %s", name); *statusp = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } state.msg_attr.exp_from = name; /* * There are a bunch of roles that we're trying to keep track of. * * First, there's the issue of whose rights should be used when delivering * to "|command" or to /file/name. With alias databases, the rights are * those of who owns the alias, i.e. the database owner. With aliases * owned by root, a default user is used instead. When an alias with * default rights references an include file owned by an ordinary user, * we must use the rights of the include file owner, otherwise the * include file owner could take control of the default account. * * Secondly, there's the question of who to notify of delivery problems. * With aliases that have an owner- alias, the latter is used to set the * sender and owner attributes. Otherwise, the owner attribute is reset * (the alias is globally visible and could be sent to by anyone). */ for (cpp = alias_maps->argv->argv; *cpp; cpp++) { if ((dict = dict_handle(*cpp)) == 0) msg_panic("%s: dictionary not found: %s", myname, *cpp); if ((alias_result = dict_get(dict, name)) != 0) { if (msg_verbose) msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result); /* * Don't expand a verify-only request. */ if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { dsb_simple(state.msg_attr.why, "2.0.0", "aliased to %s", alias_result); *statusp = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); return (YES); } /* * DELIVERY POLICY * * Update the expansion type attribute, so we can decide if * deliveries to |command and /file/name are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_ALIAS; /* * DELIVERY RIGHTS * * What rights to use for |command and /file/name deliveries? The * command and file code will use default rights when the alias * database is owned by root, otherwise it will use the rights of * the alias database owner. */ if ((alias_uid = dict_owner(*cpp)) == 0) { alias_pwd = 0; RESET_USER_ATTR(usr_attr, state.level); } else { if ((alias_pwd = mypwuid(alias_uid)) == 0) { msg_warn("cannot find alias database owner for %s", *cpp); dsb_simple(state.msg_attr.why, "4.3.0", "cannot find alias database owner"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } SET_USER_ATTR(usr_attr, alias_pwd, state.level); } /* * WHERE TO REPORT DELIVERY PROBLEMS. * * Use the owner- alias if one is specified, otherwise reset the * owner attribute and use the include file ownership if we can. * Save the dict_lookup() result before something clobbers it. * * Don't match aliases that are based on regexps. */ #define OWNER_ASSIGN(own) \ (own = (var_ownreq_special == 0 ? 0 : \ concatenate("owner-", name, (char *) 0))) saved_alias_result = mystrdup(alias_result); if (OWNER_ASSIGN(owner) != 0 && (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) { canon_owner = canon_addr_internal(vstring_alloc(10), var_exp_own_alias ? owner_rhs : owner); /* Set envelope sender and owner attribute. */ SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level); } else { canon_owner = 0; /* Note: this does not reset the envelope sender. */ if (var_reset_owner_attr) RESET_OWNER_ATTR(state.msg_attr, state.level); } /* * EXTERNAL LOOP CONTROL * * Set the delivered message attribute to the recipient, so that * this message will list the correct forwarding address. */ if (var_frozen_delivered == 0) state.msg_attr.delivered = state.msg_attr.rcpt.address; /* * Deliver. */ alias_count = 0; if (dict_errno != 0) { dsb_simple(state.msg_attr.why, "4.3.0", "alias database unavailable"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { /* * XXX DSN * * When delivering to a mailing list (i.e. the envelope sender * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters * which accompany the redistributed message MUST NOT be * derived from those of the original message. * * When delivering to an alias (i.e. the envelope sender is not * replaced) any ENVID, RET, or ORCPT parameters are * propagated to all forwarding addresses associated with * that alias. The NOTIFY parameter is propagated to the * forwarding addresses, except that any SUCCESS keyword is * removed. */ #define DSN_SAVE_UPDATE(saved, old, new) do { \ saved = old; \ old = new; \ } while (0) DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify, dsn_notify == DSN_NOTIFY_SUCCESS ? DSN_NOTIFY_NEVER : dsn_notify & ~DSN_NOTIFY_SUCCESS); if (canon_owner != 0) { DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, ""); DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0); DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, ""); state.msg_attr.rcpt.orig_addr = ""; } *statusp = deliver_token_string(state, usr_attr, saved_alias_result, &alias_count); #if 0 if (var_ownreq_special && strncmp("owner-", state.msg_attr.sender, 6) != 0 && alias_count > 10) msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias", name, name); #endif if (alias_count < 1) { msg_warn("no recipient in alias lookup result for %s", name); dsb_simple(state.msg_attr.why, "4.3.0", "alias database unavailable"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else { /* * XXX DSN * * When delivering to a mailing list (i.e. the envelope * sender address is replaced) and NOTIFY=SUCCESS was * specified, report a DSN of "delivered". * * When delivering to an alias (i.e. the envelope sender * address is not replaced) and NOTIFY=SUCCESS was * specified, report a DSN of "expanded". */ if (dsn_notify & DSN_NOTIFY_SUCCESS) { state.msg_attr.rcpt.dsn_notify = dsn_notify; if (canon_owner != 0) { state.msg_attr.dsn_envid = dsn_envid; state.msg_attr.dsn_ret = dsn_ret; state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt; } dsb_update(state.msg_attr.why, "2.0.0", canon_owner ? "delivered" : "expanded", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "alias expanded"); (void) trace_append(BOUNCE_FLAG_NONE, SENT_ATTR(state.msg_attr)); } } } myfree(saved_alias_result); if (owner) myfree(owner); if (canon_owner) vstring_free(canon_owner); if (alias_pwd) mypwfree(alias_pwd); return (YES); } /* * If the alias database was inaccessible for some reason, defer * further delivery for the current top-level recipient. */ if (dict_errno != 0) { dsb_simple(state.msg_attr.why, "4.3.0", "alias database unavailable"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } else { if (msg_verbose) msg_info("%s: %s: %s not found", myname, *cpp, name); } } /* * Try delivery to a local user instead. */ return (NO); }
int bounce_workaround(LOCAL_STATE state) { const char *myname = "bounce_workaround"; VSTRING *canon_owner = 0; int rcpt_stat; /* * Look up the substitute sender address. */ if (var_ownreq_special) { char *stripped_recipient; char *owner_alias; const char *owner_expansion; #define FIND_OWNER(lhs, rhs, addr) { \ lhs = concatenate("owner-", addr, (char *) 0); \ (void) split_at_right(lhs, '@'); \ rhs = maps_find(alias_maps, lhs, DICT_FLAG_NONE); \ } FIND_OWNER(owner_alias, owner_expansion, state.msg_attr.rcpt.address); if (alias_maps->error == 0 && owner_expansion == 0 && (stripped_recipient = strip_addr(state.msg_attr.rcpt.address, (char **) 0, var_rcpt_delim)) != 0) { myfree(owner_alias); FIND_OWNER(owner_alias, owner_expansion, stripped_recipient); myfree(stripped_recipient); } if (alias_maps->error == 0 && owner_expansion != 0) { canon_owner = canon_addr_internal(vstring_alloc(10), var_exp_own_alias ? owner_expansion : owner_alias); SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level); } myfree(owner_alias); if (alias_maps->error != 0) /* At this point, canon_owner == 0. */ return (defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr))); } /* * Send a delivery status notification with a single recipient to the * substitute sender address, before completion of the delivery request. */ if (canon_owner) { rcpt_stat = bounce_one(BOUNCE_FLAGS(state.request), BOUNCE_ONE_ATTR(state.msg_attr)); vstring_free(canon_owner); } /* * Send a regular delivery status notification, after completion of the * delivery request. */ else { rcpt_stat = bounce_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } return (rcpt_stat); }
static int eval_command_status(int command_status, char *service, DELIVER_REQUEST *request, PIPE_ATTR *attr, DSN_BUF *why) { RECIPIENT *rcpt; int status; int result = 0; int n; /* * Depending on the result, bounce or defer the message, and mark the * recipient as done where appropriate. */ switch (command_status) { case PIPE_STAT_OK: dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ? "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "delivered via %s service", service); (void) DSN_FROM_DSN_BUF(why); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; status = sent(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(request->fp, rcpt->offset); result |= status; } break; case PIPE_STAT_BOUNCE: case PIPE_STAT_DEFER: (void) DSN_FROM_DSN_BUF(why); if (STR(why->status)[0] != '4') { for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; status = bounce_append(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); if (status == 0) deliver_completed(request->fp, rcpt->offset); result |= status; } } else { for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; result |= defer_append(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, service, &why->dsn); } } break; case PIPE_STAT_CORRUPT: /* XXX DSN should we send something? */ result |= DEL_STAT_DEFER; break; default: msg_panic("eval_command_status: bad status %d", command_status); /* NOTREACHED */ } return (result); }