int smtp_sasl_passwd_lookup(SMTP_SESSION *session) { const char *myname = "smtp_sasl_passwd_lookup"; SMTP_STATE *state = session->state; const char *value; char *passwd; /* * Sanity check. */ if (smtp_sasl_passwd_map == 0) msg_panic("%s: passwd map not initialized", myname); /* * Look up the per-server password information. Try the hostname first, * then try the destination. * * XXX Instead of using nexthop (the intended destination) we use dest * (either the intended destination, or a fall-back destination). * * XXX SASL authentication currently depends on the host/domain but not on * the TCP port. If the port is not :25, we should append it to the table * lookup key. Code for this was briefly introduced into 2.2 snapshots, * but didn't canonicalize the TCP port, and did not append the port to * the MX hostname. */ smtp_sasl_passwd_map->error = 0; if (((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0 && var_smtp_sender_auth && state->request->sender[0] && (value = mail_addr_find(smtp_sasl_passwd_map, state->request->sender, (char **) 0)) != 0) || (smtp_sasl_passwd_map->error == 0 && (value = maps_find(smtp_sasl_passwd_map, session->host, 0)) != 0) || (smtp_sasl_passwd_map->error == 0 && (value = maps_find(smtp_sasl_passwd_map, session->dest, 0)) != 0)) { if (session->sasl_username) myfree(session->sasl_username); session->sasl_username = mystrdup(value); passwd = split_at(session->sasl_username, ':'); if (session->sasl_passwd) myfree(session->sasl_passwd); session->sasl_passwd = mystrdup(passwd ? passwd : ""); if (msg_verbose) msg_info("%s: host `%s' user `%s' pass `%s'", myname, session->host, session->sasl_username, session->sasl_passwd); return (1); } else if (smtp_sasl_passwd_map->error) { msg_warn("%s: %s lookup error", state->request->queue_id, smtp_sasl_passwd_map->title); vstream_longjmp(session->stream, SMTP_ERR_DATA); } else { if (msg_verbose) msg_info("%s: no auth info found (sender=`%s', host=`%s')", myname, state->request->sender, session->host); return (0); } }
static const char *find_addr(MAPS *path, const char *address, int flags, int with_domain, int query_form, VSTRING *ext_addr_buf) { const char *result; #define SANS_DOMAIN 0 #define WITH_DOMAIN 1 switch (query_form) { /* * Query with external-form (quoted) address. The code looks a bit * unusual to emphasize the symmetry with the other cases. */ case MA_FORM_EXTERNAL: case MA_FORM_EXTERNAL_FIRST: quote_822_local_flags(ext_addr_buf, address, with_domain ? QUOTE_FLAG_DEFAULT : QUOTE_FLAG_DEFAULT | QUOTE_FLAG_BARE_LOCALPART); result = maps_find(path, STR(ext_addr_buf), flags); if (result != 0 || path->error != 0 || query_form != MA_FORM_EXTERNAL_FIRST || strcmp(address, STR(ext_addr_buf)) == 0) break; result = maps_find(path, address, flags); break; /* * Query with internal-form (unquoted) address. The code looks a bit * unusual to emphasize the symmetry with the other cases. */ case MA_FORM_INTERNAL: case MA_FORM_INTERNAL_FIRST: result = maps_find(path, address, flags); if (result != 0 || path->error != 0 || query_form != MA_FORM_INTERNAL_FIRST) break; quote_822_local_flags(ext_addr_buf, address, with_domain ? QUOTE_FLAG_DEFAULT : QUOTE_FLAG_DEFAULT | QUOTE_FLAG_BARE_LOCALPART); if (strcmp(address, STR(ext_addr_buf)) == 0) break; result = maps_find(path, STR(ext_addr_buf), flags); break; /* * Can't happen. */ default: msg_panic("mail_addr_find: bad query_form: %d", query_form); } return (result); }
int main(int argc, char **argv) { VSTRING *buf = vstring_alloc(100); MAPS *maps; const char *result; if (argc != 2) msg_fatal("usage: %s maps", argv[0]); msg_verbose = 2; maps = maps_create("whatever", argv[1], DICT_FLAG_LOCK); while (vstring_fgets_nonl(buf, VSTREAM_IN)) { if ((result = maps_find(maps, vstring_str(buf), 0)) != 0) { vstream_printf("%s\n", result); } else if (dict_errno != 0) { msg_fatal("lookup error: %m"); } else { vstream_printf("not found\n"); } vstream_fflush(VSTREAM_OUT); } maps_free(maps); vstring_free(buf); return (0); }
char *hbc_header_checks(void *context, HBC_CHECKS *hbc, int header_class, const HEADER_OPTS *hdr_opts, VSTRING *header, off_t offset) { const char *myname = "hbc_header_checks"; const char *action; HBC_MAP_INFO *mp; if (msg_verbose) msg_info("%s: '%.30s'", myname, STR(header)); /* * XXX This is for compatibility with the cleanup(8) server. */ if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME)) header_class = MIME_HDR_MULTIPART; mp = hbc->map_info + HBC_HEADER_INDEX(header_class); if (mp->maps != 0 && (action = maps_find(mp->maps, STR(header), 0)) != 0) { return (hbc_action(context, hbc->call_backs, mp->map_class, HBC_CTXT_HEADER, action, STR(header), LEN(header), offset)); } else if (mp->maps && mp->maps->error) { return (HBC_CHECKS_STAT_ERROR); } else { return (STR(header)); } }
static void tls_site_lookup(int *site_level, const char *site_name, const char *site_class) { const char *lookup; /* * Look up a non-default policy. In case of multiple lookup results, the * precedence order is a permutation of the TLS enforcement level order: * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more * specific policy including NONE, otherwise we choose the stronger * enforcement level. */ if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) { if (!strcasecmp(lookup, "NONE")) { /* NONE overrides MAY or NOTFOUND. */ if (*site_level <= TLS_LEV_MAY) *site_level = TLS_LEV_NONE; } else if (!strcasecmp(lookup, "MAY")) { /* MAY overrides NOTFOUND but not NONE. */ if (*site_level < TLS_LEV_NONE) *site_level = TLS_LEV_MAY; } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) { if (*site_level < TLS_LEV_ENCRYPT) *site_level = TLS_LEV_ENCRYPT; } else if (!strcasecmp(lookup, "MUST")) { if (*site_level < TLS_LEV_VERIFY) *site_level = TLS_LEV_VERIFY; } else { msg_warn("Table %s: ignoring unknown TLS policy '%s' for %s %s", var_smtp_tls_per_site, lookup, site_class, site_name); } } else if (tls_per_site->error) { msg_fatal("%s lookup error for %s", tls_per_site->title, site_name); } }
static int find_transport_entry(TRANSPORT_INFO *tp, const char *key, const char *rcpt_domain, int flags, VSTRING *channel, VSTRING *nexthop) { char *saved_value; const char *host; const char *value; #define FOUND 1 #define NOTFOUND 0 /* * Look up an entry with extreme prejudice. * * XXX Should report lookup failure status to caller instead of aborting. */ if ((value = maps_find(tp->transport_path, key, flags)) == 0) return (NOTFOUND); /* * It would be great if we could specify a recipient address in the * lookup result. Unfortunately, we cannot simply run the result through * a parser that recognizes "transport:user@domain" because the lookup * result can have arbitrary content (especially in the case of the error * mailer). */ else { saved_value = mystrdup(value); host = split_at(saved_value, ':'); update_entry(saved_value, host ? host : "", rcpt_domain, channel, nexthop); myfree(saved_value); return (FOUND); } }
static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, const char *site_class) { const char *lookup; /* * Look up a non-default policy. In case of multiple lookup results, the * precedence order is a permutation of the TLS enforcement level order: * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more * specific policy including NONE, otherwise we choose the stronger * enforcement level. */ if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) { if (!strcasecmp(lookup, "NONE")) { /* NONE overrides MAY or NOTFOUND. */ if (*site_level <= TLS_LEV_MAY) *site_level = TLS_LEV_NONE; } else if (!strcasecmp(lookup, "MAY")) { /* MAY overrides NOTFOUND but not NONE. */ if (*site_level < TLS_LEV_NONE) *site_level = TLS_LEV_MAY; } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) { if (*site_level < TLS_LEV_ENCRYPT) *site_level = TLS_LEV_ENCRYPT; } else if (!strcasecmp(lookup, "MUST")) { if (*site_level < TLS_LEV_VERIFY) *site_level = TLS_LEV_VERIFY; } else { msg_warn("%s: unknown TLS policy '%s' for %s %s", tls_per_site->title, lookup, site_class, site_name); MARK_INVALID(tls->why, site_level); return; } } else if (tls_per_site->error) { msg_warn("%s: %s \"%s\": per-site table lookup error", tls_per_site->title, site_class, site_name); dsb_simple(tls->why, "4.3.0", "Temporary lookup error"); *site_level = TLS_LEV_INVALID; return; } return; }
char *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line, ssize_t len, off_t offset) { const char *myname = "hbc_body_checks"; const char *action; HBC_MAP_INFO *mp; if (msg_verbose) msg_info("%s: '%.30s'", myname, line); mp = hbc->map_info; if ((action = maps_find(mp->maps, line, 0)) != 0) { return (hbc_action(context, hbc->call_backs, mp->map_class, HBC_CTXT_BODY, action, line, len, offset)); } else if (mp->maps->error) { return (HBC_CHECKS_STAT_ERROR); } else { return ((char *) line); } }
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_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); }
static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0) if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_fatal("%s: %s lookup error for %s", session->state->request->queue_id, tls_policy->title, site_name); /* XXX session->stream has no longjmp context yet. */ } return (0); } if (cbuf == 0) cbuf = vstring_alloc(10); #define WHERE \ vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \ site_class, site_name)) saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); *site_level = TLS_LEV_INVALID; FREE_RETURN(1); /* No further lookups */ } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); FREE_RETURN(1); /* No further lookups */ } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN(1); } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { *site_level = TLS_LEV_INVALID; msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); break; } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (session->tls_protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_protocols = mystrdup(val); continue; } /* Multiple instance(s) per policy. */ if (!strcasecmp(name, "match")) { char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":"; if (*site_level <= TLS_LEV_ENCRYPT) { msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"", WHERE, name, policy_name(*site_level)); *site_level = TLS_LEV_INVALID; break; } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_matchargv == 0) session->tls_matchargv = argv_split(val, delim); else argv_split_append(session->tls_matchargv, val, delim); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (session->tls_exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } else { msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); *site_level = TLS_LEV_INVALID; break; } } FREE_RETURN(1); }
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 deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) { const char *myname = "deliver_mailbox"; int status; struct mypasswd *mbox_pwd; char *path; static MAPS *transp_maps; const char *map_transport; static MAPS *cmd_maps; const char *map_command; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Don't come here more than once, whether or not the recipient exists. */ if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local)) return (YES); /* * Delegate mailbox delivery to another message transport. */ if (*var_mbox_transp_maps && transp_maps == 0) transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_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; *statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport, state.request, &state.msg_attr.rcpt); return (YES); } else if (transp_maps && transp_maps->error != 0) { /* Details in the logfile. */ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } if (*var_mailbox_transport) { state.msg_attr.rcpt.offset = -1L; *statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport, state.request, &state.msg_attr.rcpt); return (YES); } /* * Skip delivery when this recipient does not exist. */ if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 0) { msg_warn("error looking up passwd info for %s: %m", state.msg_attr.user); dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); *statusp = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); return (YES); } if (mbox_pwd == 0) return (NO); /* * No early returns or we have a memory leak. */ /* * DELIVERY RIGHTS * * Use the rights of the recipient user. */ SET_USER_ATTR(usr_attr, mbox_pwd, state.level); /* * Deliver to mailbox, maildir or to external command. */ #define LAST_CHAR(s) (s[strlen(s) - 1]) if (*var_mailbox_cmd_maps && cmd_maps == 0) cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps, DICT_FLAG_LOCK | DICT_FLAG_PARANOID); if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user, DICT_FLAG_NONE)) != 0) { status = deliver_command(state, usr_attr, map_command); } else if (cmd_maps && cmd_maps->error != 0) { /* Details in the logfile. */ dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); status = defer_append(BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); } else if (*var_mailbox_command) { status = deliver_command(state, usr_attr, var_mailbox_command); } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') { path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); status = deliver_maildir(state, usr_attr, path); myfree(path); } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') { path = concatenate(var_mail_spool_dir, state.msg_attr.user, "/", (char *) 0); status = deliver_maildir(state, usr_attr, path); myfree(path); } else status = deliver_mailbox_file(state, usr_attr); /* * Cleanup. */ mypwfree(mbox_pwd); *statusp = status; return (YES); }
static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN do { myfree(saved_policy); return; } while (0) #define INVALID_RETURN(why, levelp) do { \ MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) #define WHERE \ STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ tls_policy->title, site_class, site_name)) if (cbuf == 0) cbuf = vstring_alloc(10); if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_warn("%s: policy table lookup error", WHERE); MARK_INVALID(tls->why, site_level); } return; } saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); INVALID_RETURN(tls->why, site_level); } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); INVALID_RETURN(tls->why, site_level); } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN; } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); INVALID_RETURN(tls->why, site_level); } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (tls->grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (tls->protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->protocols = mystrdup(val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "match")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } switch (*site_level) { default: msg_warn("%s: attribute \"%s\" invalid at security level " "\"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); break; case TLS_LEV_FPRINT: if (!tls->dane) tls->dane = tls_dane_alloc(); tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, val, "|"); break; case TLS_LEV_VERIFY: case TLS_LEV_SECURE: if (tls->matchargv == 0) tls->matchargv = argv_split(val, ":"); else argv_split_append(tls->matchargv, val, ":"); break; } continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (tls->exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "tafile")) { /* Only makes sense if we're using CA-based trust */ if (!TLS_MUST_PKIX(*site_level)) { msg_warn("%s: attribute \"%s\" invalid at security level" " \"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (!tls->dane) tls->dane = tls_dane_alloc(); if (!tls_dane_load_trustfile(tls->dane, val)) { INVALID_RETURN(tls->why, site_level); } continue; } msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); INVALID_RETURN(tls->why, site_level); } FREE_RETURN; }
const char *mail_addr_find(MAPS *path, const char *address, char **extp) { const char *myname = "mail_addr_find"; const char *result; char *ratsign = 0; char *full_key; char *bare_key; char *saved_ext; int rc = 0; /* * Initialize. */ full_key = mystrdup(address); if (*var_rcpt_delim == 0) { bare_key = saved_ext = 0; } else { bare_key = strip_addr(full_key, &saved_ext, *var_rcpt_delim); } /* * Try user+foo@domain and user@domain. * * Specify what keys are partial or full, to avoid matching partial * addresses with regular expressions. */ #define FULL 0 #define PARTIAL DICT_FLAG_FIXED if ((result = maps_find(path, full_key, FULL)) == 0 && path->error == 0 && bare_key != 0 && (result = maps_find(path, bare_key, PARTIAL)) != 0 && extp != 0) { *extp = saved_ext; saved_ext = 0; } /* * Try user+foo@$myorigin, user+foo@$mydestination or * user+foo@[${proxy,inet}_interfaces]. Then try with +foo stripped off. */ if (result == 0 && path->error == 0 && (ratsign = strrchr(full_key, '@')) != 0 && (strcasecmp(ratsign + 1, var_myorigin) == 0 || (rc = resolve_local(ratsign + 1)) > 0)) { *ratsign = 0; result = maps_find(path, full_key, PARTIAL); if (result == 0 && path->error == 0 && bare_key != 0) { if ((ratsign = strrchr(bare_key, '@')) == 0) msg_panic("%s: bare key botch", myname); *ratsign = 0; if ((result = maps_find(path, bare_key, PARTIAL)) != 0 && extp != 0) { *extp = saved_ext; saved_ext = 0; } } *ratsign = '@'; } else if (rc < 0) path->error = rc; /* * Try @domain. */ if (result == 0 && path->error == 0 && ratsign) result = maps_find(path, ratsign, PARTIAL); /* * Clean up. */ if (msg_verbose) msg_info("%s: %s -> %s", myname, address, result ? result : path->error ? "(try again)" : "(not found)"); myfree(full_key); if (bare_key) myfree(bare_key); if (saved_ext) myfree(saved_ext); return (result); }
const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp, int in_form, int query_form, int out_form, int strategy) { const char *myname = "mail_addr_find"; VSTRING *ext_addr_buf = 0; VSTRING *int_addr_buf = 0; const char *int_addr; static VSTRING *int_result = 0; const char *result; char *ratsign = 0; char *int_full_key; char *int_bare_key; char *saved_ext; int rc = 0; /* * Optionally convert the address from external form. */ if (in_form == MA_FORM_EXTERNAL) { int_addr_buf = vstring_alloc(100); unquote_822_local(int_addr_buf, address); int_addr = STR(int_addr_buf); } else { int_addr = address; } if (query_form == MA_FORM_EXTERNAL_FIRST || query_form == MA_FORM_EXTERNAL) ext_addr_buf = vstring_alloc(100); /* * Initialize. */ int_full_key = mystrdup(int_addr); if (*var_rcpt_delim == 0 || (strategy & MA_FIND_NOEXT) == 0) { int_bare_key = saved_ext = 0; } else { /* XXX This could be done after user+foo@domain fails. */ int_bare_key = strip_addr_internal(int_full_key, &saved_ext, var_rcpt_delim); } /* * Try user+foo@domain and user@domain. */ if ((strategy & MA_FIND_FULL) != 0) { result = find_addr(path, int_full_key, FULL, WITH_DOMAIN, query_form, ext_addr_buf); } else { result = 0; path->error = 0; } if (result == 0 && path->error == 0 && int_bare_key != 0 && (result = find_addr(path, int_bare_key, PARTIAL, WITH_DOMAIN, query_form, ext_addr_buf)) != 0 && extp != 0) { *extp = saved_ext; saved_ext = 0; } /* * Try user+foo if the domain matches user+foo@$myorigin, * user+foo@$mydestination or user+foo@[${proxy,inet}_interfaces]. Then * try with +foo stripped off. */ if (result == 0 && path->error == 0 && (ratsign = strrchr(int_full_key, '@')) != 0 && (strategy & (MA_FIND_LOCALPART_IF_LOCAL | MA_FIND_LOCALPART_AT_IF_LOCAL)) != 0) { if (strcasecmp_utf8(ratsign + 1, var_myorigin) == 0 || (rc = resolve_local(ratsign + 1)) > 0) { if ((strategy & MA_FIND_LOCALPART_IF_LOCAL) != 0) result = find_local(path, ratsign, 0, int_full_key, int_bare_key, query_form, extp, &saved_ext, ext_addr_buf); if (result == 0 && path->error == 0 && (strategy & MA_FIND_LOCALPART_AT_IF_LOCAL) != 0) result = find_local(path, ratsign, 1, int_full_key, int_bare_key, query_form, extp, &saved_ext, ext_addr_buf); } else if (rc < 0) path->error = rc; } /* * Try @domain. */ if (result == 0 && path->error == 0 && ratsign != 0 && (strategy & MA_FIND_AT_DOMAIN) != 0) result = maps_find(path, ratsign, PARTIAL); /* * Try domain (optionally, subdomains). */ if (result == 0 && path->error == 0 && ratsign != 0 && (strategy & MA_FIND_DOMAIN) != 0) { const char *name; const char *next; if ((strategy & MA_FIND_PDMS) && (strategy & MA_FIND_PDDMDS)) msg_warn("mail_addr_find_opt: do not specify both " "MA_FIND_PDMS and MA_FIND_PDDMDS"); for (name = ratsign + 1; *name != 0; name = next) { if ((result = maps_find(path, name, PARTIAL)) != 0 || path->error != 0 || (strategy & (MA_FIND_PDMS | MA_FIND_PDDMDS)) == 0 || (next = strchr(name + 1, '.')) == 0) break; if ((strategy & MA_FIND_PDDMDS) == 0) next++; } } /* * Try localpart@ even if the domain is not local. */ if ((strategy & MA_FIND_LOCALPART_AT) != 0 \ &&result == 0 && path->error == 0) result = find_local(path, ratsign, 1, int_full_key, int_bare_key, query_form, extp, &saved_ext, ext_addr_buf); /* * Optionally convert the result to internal form. The lookup result is * supposed to be one external-form email address. */ if (result != 0 && out_form == MA_FORM_INTERNAL) { if (int_result == 0) int_result = vstring_alloc(100); unquote_822_local(int_result, result); result = STR(int_result); } /* * Clean up. */ if (msg_verbose) msg_info("%s: %s -> %s", myname, address, result ? result : path->error ? "(try again)" : "(not found)"); myfree(int_full_key); if (int_bare_key) myfree(int_bare_key); if (saved_ext) myfree(saved_ext); if (int_addr_buf) vstring_free(int_addr_buf); if (ext_addr_buf) vstring_free(ext_addr_buf); return (result); }