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_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command) { const char *myname = "deliver_command"; DSN_BUF *why = state.msg_attr.why; int cmd_status; int deliver_status; ARGV *env; int copy_flags; char **cpp; char *cp; ARGV *export_env; VSTRING *exec_dir; int expand_status; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * DUPLICATE ELIMINATION * * Skip this command if it was already delivered to as this user. */ if (been_here(state.dup_filter, "command %s:%ld %s", state.msg_attr.user, (long) usr_attr.uid, command)) return (0); /* * Don't deliver a trace-only request. */ if (DEL_REQ_TRACE_ONLY(state.request->flags)) { dsb_simple(why, "2.0.0", "delivers to command: %s", command); return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); } /* * DELIVERY RIGHTS * * Choose a default uid and gid when none have been selected (i.e. values * are still zero). */ if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) msg_panic("privileged default user id"); if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) msg_panic("privileged default group id"); /* * Deliver. */ copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH | MAIL_COPY_ORIG_RCPT; if (local_deliver_hdr_mask & DELIVER_HDR_CMD) copy_flags |= MAIL_COPY_DELIVERED; if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("%s: seek queue file %s: %m", myname, VSTREAM_PATH(state.msg_attr.fp)); /* * Pass additional environment information. XXX This should be * configurable. However, passing untrusted information via environment * parameters opens up a whole can of worms. Lesson from web servers: * don't let any network data even near a shell. It causes trouble. */ env = argv_alloc(1); if (usr_attr.home) argv_add(env, "HOME", usr_attr.home, ARGV_END); argv_add(env, "LOGNAME", state.msg_attr.user, "USER", state.msg_attr.user, "SENDER", state.msg_attr.sender, "RECIPIENT", state.msg_attr.rcpt.address, "LOCAL", state.msg_attr.local, ARGV_END); if (usr_attr.shell) argv_add(env, "SHELL", usr_attr.shell, ARGV_END); if (state.msg_attr.domain) argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END); if (state.msg_attr.extension) argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END); if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0]) argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr, ARGV_END); #define EXPORT_REQUEST(name, value) \ if ((value)[0]) argv_add(env, (name), (value), ARGV_END); EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name); EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr); EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo); EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto); EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method); EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender); EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username); argv_terminate(env); /* * Censor out undesirable characters from exported data. */ for (cpp = env->argv; *cpp; cpp += 2) for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;) *cp++ = '_'; /* * Evaluate the command execution directory. Defer delivery if expansion * fails. */ export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); exec_dir = vstring_alloc(10); expand_status = local_expand(exec_dir, var_exec_directory, &state, &usr_attr, var_exec_exp_filter); if (expand_status & MAC_PARSE_ERROR) { cmd_status = PIPE_STAT_DEFER; dsb_simple(why, "4.3.5", "mail system configuration error"); msg_warn("bad parameter value syntax for %s: %s", VAR_EXEC_DIRECTORY, var_exec_directory); } else { cmd_status = pipe_command(state.msg_attr.fp, why, PIPE_CMD_UID, usr_attr.uid, PIPE_CMD_GID, usr_attr.gid, PIPE_CMD_COMMAND, command, PIPE_CMD_COPY_FLAGS, copy_flags, PIPE_CMD_SENDER, state.msg_attr.sender, PIPE_CMD_ORIG_RCPT, state.msg_attr.rcpt.orig_addr, PIPE_CMD_DELIVERED, state.msg_attr.delivered, PIPE_CMD_TIME_LIMIT, var_command_maxtime, PIPE_CMD_ENV, env->argv, PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_SHELL, var_local_cmd_shell, PIPE_CMD_CWD, *STR(exec_dir) ? STR(exec_dir) : (char *) 0, PIPE_CMD_END); } vstring_free(exec_dir); argv_free(export_env); argv_free(env); /* * Depending on the result, bounce or defer the message. */ switch (cmd_status) { case PIPE_STAT_OK: dsb_simple(why, "2.0.0", "delivered to command: %s", command); deliver_status = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); break; case PIPE_STAT_BOUNCE: case PIPE_STAT_DEFER: /* Account for possible owner- sender address override. */ deliver_status = bounce_workaround(state); break; case PIPE_STAT_CORRUPT: deliver_status = DEL_STAT_DEFER; break; default: msg_panic("%s: bad status %d", myname, cmd_status); /* NOTREACHED */ } return (deliver_status); }
int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) { const char *myname = "deliver_dotforward"; struct stat st; VSTRING *path; struct mypasswd *mypwd; int fd; VSTREAM *fp; int status; int forward_found = NO; int lookup_status; int addr_count; char *saved_forward_path; char *lhs; char *next; int expand_status; int saved_notify; /* * Make verbose logging easier to understand. */ state.level++; if (msg_verbose) MSG_LOG_STATE(myname, state); /* * Skip this module if per-user forwarding is disabled. */ if (*var_forward_path == 0) return (NO); /* * Skip non-existing users. The mailbox delivery routine will catch the * error. */ if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 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 (mypwd == 0) return (NO); /* * From here on no early returns or we have a memory leak. */ /* * 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; /* * DELIVERY RIGHTS * * Do not inherit rights from the .forward file owner. Instead, use the * recipient's rights, and insist that the .forward file is owned by the * recipient. This is a small but significant difference. Use the * recipient's rights for all /file and |command deliveries, and pass on * these rights to command/file destinations in included files. When * these are the rights of root, the /file and |command delivery routines * will use unprivileged default rights instead. Better safe than sorry. */ SET_USER_ATTR(usr_attr, mypwd, state.level); /* * DELIVERY POLICY * * Update the expansion type attribute so that we can decide if deliveries * to |command and /file/name are allowed at all. */ state.msg_attr.exp_type = EXPAND_TYPE_FWD; /* * WHERE TO REPORT DELIVERY PROBLEMS * * Set the owner attribute so that 1) include files won't set the sender to * be this user and 2) mail forwarded to other local users will be * resubmitted as a new queue file. */ state.msg_attr.owner = state.msg_attr.user; /* * Search the forward_path for an existing forward file. * * If unmatched extensions should never be propagated, or if a forward file * name includes the address extension, don't propagate the extension to * the recipient addresses. */ status = 0; path = vstring_alloc(100); saved_forward_path = mystrdup(var_forward_path); next = saved_forward_path; lookup_status = -1; while ((lhs = mystrtok(&next, ", \t\r\n")) != 0) { expand_status = local_expand(path, lhs, &state, &usr_attr, var_fwd_exp_filter); if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) { lookup_status = lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid); if (msg_verbose) msg_info("%s: path %s expand_status %d look_status %d", myname, STR(path), expand_status, lookup_status); if (lookup_status >= 0) { if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0 || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0) state.msg_attr.unmatched = 0; break; } } } /* * Process the forward file. * * Assume that usernames do not have file system meta characters. Open the * .forward file as the user. Ignore files that aren't regular files, * files that are owned by the wrong user, or files that have world write * permission enabled. * * DUPLICATE/LOOP ELIMINATION * * If this user includes (an alias of) herself in her own .forward file, * deliver to the user instead. */ if (lookup_status >= 0) { /* * 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", "forward via file: %s", STR(path)); *statusp = sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr)); forward_found = YES; } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { state.msg_attr.exp_from = state.msg_attr.local; if (S_ISREG(st.st_mode) == 0) { msg_warn("file %s is not a regular file", STR(path)); } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { msg_warn("file %s has bad owner uid %ld", STR(path), (long) st.st_uid); } else if (st.st_mode & 002) { msg_warn("file %s is world writable", STR(path)); } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { msg_warn("cannot open file %s: %m", STR(path)); } else { /* * XXX DSN. When delivering to an alias (i.e. the envelope * sender address 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. */ close_on_exec(fd, CLOSE_ON_EXEC); addr_count = 0; fp = vstream_fdopen(fd, O_RDONLY); saved_notify = state.msg_attr.rcpt.dsn_notify; state.msg_attr.rcpt.dsn_notify = (saved_notify == DSN_NOTIFY_SUCCESS ? DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS); status = deliver_token_stream(state, usr_attr, fp, &addr_count); if (vstream_fclose(fp)) msg_warn("close file %s: %m", STR(path)); if (addr_count > 0) { forward_found = YES; been_here(state.dup_filter, "forward-done %s", STR(path)); /* * XXX DSN. When delivering to an alias (i.e. the * envelope sender address is not replaced) and the * original NOTIFY parameter for the alias contained the * SUCCESS keyword, an "expanded" DSN is issued for the * alias. */ if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) { state.msg_attr.rcpt.dsn_notify = saved_notify; dsb_update(state.msg_attr.why, "2.0.0", "expanded", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "alias expanded"); (void) trace_append(BOUNCE_FLAG_NONE, SENT_ATTR(state.msg_attr)); } } } } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) forward_found = YES; /* else we're recursive */ } /* * Clean up. */ vstring_free(path); myfree(saved_forward_path); mypwfree(mypwd); *statusp = status; return (forward_found); }