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); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; /* XXX Maybe encapsulate this with ndr_append(). */ status = (STR(why->status)[0] != '4' ? bounce_append : defer_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; } 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); }
static int deliver_message(DELIVER_REQUEST *request) { const char *myname = "deliver_message"; VSTREAM *src; int result = 0; int status; RECIPIENT *rcpt; int nrcpt; DSN_SPLIT dp; DSN dsn; 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)); /* * Discard all recipients. */ #define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags) dsn_split(&dp, "2.0.0", request->nexthop); (void) DSN_SIMPLE(&dsn, DSN_STATUS(dp.dsn), dp.text); for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; status = sent(BOUNCE_FLAGS(request), request->queue_id, &request->msg_stats, rcpt, "none", &dsn); if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) 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); }
void smtp_rcpt_fail(SMTP_STATE *state, RECIPIENT *rcpt, const char *mta_name, SMTP_RESP *resp, const char *format,...) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; DSN_BUF *why = state->why; int status; int soft_error; int soft_bounce_error; va_list ap; /* * Sanity check. */ if (SMTP_RCPT_ISMARKED(rcpt)) msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address); /* * Initialize. */ va_start(ap, format); vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap); va_end(ap); soft_error = STR(why->status)[0] == '4'; soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce); if (state->session && mta_name) smtp_check_code(state->session, resp->code); /* * Don't defer this recipient record just yet when this error qualifies * for trying other mail servers. Just log something informative to show * why we're skipping this recipient now. */ if ((soft_error || soft_bounce_error) && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) { msg_info("%s: %s", request->queue_id, STR(why->reason)); SMTP_RCPT_KEEP(state, rcpt); } /* * Defer or bounce this recipient, and delete from the delivery request. * If the bounce fails, defer instead and do not qualify the recipient * for delivery to a backup server. * * Note: we may still make an SMTP connection to deliver other recipients * that did qualify for delivery to a backup server. */ else { (void) DSN_FROM_DSN_BUF(state->why); status = (soft_error ? defer_append : bounce_append) (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, session ? session->namaddrport : "none", &why->dsn); if (status == 0) deliver_completed(state->src, rcpt->offset); SMTP_RCPT_DROP(state, rcpt); state->status |= status; } }
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); }
static int local_deliver(DELIVER_REQUEST *rqst, char *service) { const char *myname = "local_deliver"; RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len; RECIPIENT *rcpt; int rcpt_stat; int msg_stat; LOCAL_STATE state; USER_ATTR usr_attr; if (msg_verbose) msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender); /* * Initialize the delivery attributes that are not recipient specific. */ state.level = 0; deliver_attr_init(&state.msg_attr); state.msg_attr.queue_name = rqst->queue_name; state.msg_attr.queue_id = rqst->queue_id; state.msg_attr.fp = rqst->fp; state.msg_attr.offset = rqst->data_offset; state.msg_attr.sender = rqst->sender; state.msg_attr.dsn_envid = rqst->dsn_envid; state.msg_attr.dsn_ret = rqst->dsn_ret; state.msg_attr.relay = service; state.msg_attr.msg_stats = rqst->msg_stats; RESET_USER_ATTR(usr_attr, state.level); state.request = rqst; /* * Iterate over each recipient named in the delivery request. When the * mail delivery status for a given recipient is definite (i.e. bounced * or delivered), update the message queue file and cross off the * recipient. Update the per-message delivery status. */ for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) { state.msg_attr.rcpt = *rcpt; rcpt_stat = deliver_recipient(state, usr_attr); if (rcpt_stat == 0 && (rqst->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(state.msg_attr.fp, rcpt->offset); msg_stat |= rcpt_stat; } deliver_attr_free(&state.msg_attr); return (msg_stat); }
void smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; SMTP_ITERATOR *iter = state->iterator; DSN_BUF *why = state->why; const char *dsn_action = "relayed"; int status; /* * Assume this was intermediate delivery when the server announced DSN * support, and don't send a DSN "SUCCESS" notification. */ if (session->features & SMTP_FEATURE_DSN) rcpt->dsn_notify &= ~DSN_NOTIFY_SUCCESS; /* * Assume this was final delivery when the LMTP server announced no DSN * support. In backwards compatibility mode, send a "relayed" instead of * a "delivered" DSN "SUCCESS" notification. Do not attempt to "simplify" * the expression. The redundancy is for clarity. It is trivially * eliminated by the compiler. There is no need to sacrifice clarity for * the sake of "performance". */ if ((session->features & SMTP_FEATURE_DSN) == 0 && !smtp_mode && var_lmtp_assume_final != 0) dsn_action = "delivered"; /* * Report success and delete the recipient from the delivery request. * Defer if the success can't be reported. * * Note: the DSN action is ignored in case of address probes. */ dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, STR(iter->host), DSB_DTYPE_SMTP, resp->str, "%s", resp->str); status = sent(DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, session->namaddrport, DSN_FROM_DSN_BUF(why)); if (status == 0) if (request->flags & DEL_REQ_FLAG_SUCCESS) deliver_completed(state->src, rcpt->offset); SMTP_RCPT_DROP(state, rcpt); state->status |= status; }
static int local_deliver(DELIVER_REQUEST *rqst, char *service) { const char *myname = "local_deliver"; RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len; RECIPIENT *rcpt; int rcpt_stat; int msg_stat; LOCAL_STATE state; USER_ATTR usr_attr; if (msg_verbose) msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender); /* * Initialize the delivery attributes that are not recipient specific. * While messages are being delivered and while aliases or forward files * are being expanded, this attribute list is being changed constantly. * For this reason, the list is passed on by value (except when it is * being initialized :-), so that there is no need to undo attribute * changes made by lower-level routines. The alias/include/forward * expansion attribute list is part of a tree with self and parent * references (see the EXPAND_ATTR definitions). The user-specific * attributes are security sensitive, and are therefore kept separate. * All this results in a noticeable level of clumsiness, but passing * things around by value gives good protection against accidental change * by subroutines. */ state.level = 0; deliver_attr_init(&state.msg_attr); state.msg_attr.queue_name = rqst->queue_name; state.msg_attr.queue_id = rqst->queue_id; state.msg_attr.fp = rqst->fp; state.msg_attr.offset = rqst->data_offset; state.msg_attr.encoding = rqst->encoding; state.msg_attr.sender = rqst->sender; state.msg_attr.dsn_envid = rqst->dsn_envid; state.msg_attr.dsn_ret = rqst->dsn_ret; state.msg_attr.relay = service; state.msg_attr.msg_stats = rqst->msg_stats; state.msg_attr.request = rqst; RESET_OWNER_ATTR(state.msg_attr, state.level); RESET_USER_ATTR(usr_attr, state.level); state.loop_info = delivered_hdr_init(rqst->fp, rqst->data_offset, FOLD_ADDR_ALL); state.request = rqst; /* * Iterate over each recipient named in the delivery request. When the * mail delivery status for a given recipient is definite (i.e. bounced * or delivered), update the message queue file and cross off the * recipient. Update the per-message delivery status. */ for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) { state.dup_filter = been_here_init(var_dup_filter_limit, BH_FLAG_FOLD); forward_init(); state.msg_attr.rcpt = *rcpt; rcpt_stat = deliver_recipient(state, usr_attr); rcpt_stat |= forward_finish(rqst, state.msg_attr, rcpt_stat); if (rcpt_stat == 0 && (rqst->flags & DEL_REQ_FLAG_SUCCESS)) deliver_completed(state.msg_attr.fp, rcpt->offset); been_here_free(state.dup_filter); msg_stat |= rcpt_stat; } /* * Clean up. */ delivered_hdr_free(state.loop_info); deliver_attr_free(&state.msg_attr); return (msg_stat); }
static void qmgr_message_resolve(QMGR_MESSAGE *message) { static ARGV *defer_xport_argv; RECIPIENT_LIST list = message->rcpt_list; RECIPIENT *recipient; QMGR_TRANSPORT *transport = 0; QMGR_QUEUE *queue = 0; RESOLVE_REPLY reply; VSTRING *queue_name; char *at; char **cpp; char *nexthop; ssize_t len; int status; DSN dsn; MSG_STATS stats; DSN *saved_dsn; #define STREQ(x,y) (strcmp(x,y) == 0) #define STR vstring_str #define LEN VSTRING_LEN resolve_clnt_init(&reply); queue_name = vstring_alloc(1); for (recipient = list.info; recipient < list.info + list.len; recipient++) { /* * Redirect overrides all else. But only once (per entire message). * For consistency with the remainder of Postfix, rewrite the address * to canonical form before resolving it. */ if (message->redirect_addr) { if (recipient > list.info) { recipient->u.queue = 0; continue; } message->rcpt_offset = 0; message->rcpt_unread = 0; rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr, reply.recipient); RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); if (qmgr_resolve_one(message, recipient, recipient->address, &reply) < 0) continue; if (!STREQ(recipient->address, STR(reply.recipient))) RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); } /* * Content filtering overrides the address resolver. * * XXX Bypass content_filter inspection for user-generated probes * (sendmail -bv). MTA-generated probes never have the "please filter * me" bits turned on, but we handle them here anyway for the sake of * future proofing. */ #define FILTER_WITHOUT_NEXTHOP(filter, next) \ (((next) = split_at((filter), ':')) == 0 || *(next) == 0) #define RCPT_WITHOUT_DOMAIN(rcpt, next) \ ((next = strrchr(rcpt, '@')) == 0 || *++(next) == 0) else if (message->filter_xport && (message->tflags & DEL_REQ_TRACE_ONLY_MASK) == 0) { reply.flags = 0; vstring_strcpy(reply.transport, message->filter_xport); if (FILTER_WITHOUT_NEXTHOP(STR(reply.transport), nexthop) && *(nexthop = var_def_filter_nexthop) == 0 && RCPT_WITHOUT_DOMAIN(recipient->address, nexthop)) nexthop = var_myhostname; vstring_strcpy(reply.nexthop, nexthop); vstring_strcpy(reply.recipient, recipient->address); } /* * Resolve the destination to (transport, nexthop, address). The * result address may differ from the one specified by the sender. */ else { if (qmgr_resolve_one(message, recipient, recipient->address, &reply) < 0) continue; if (!STREQ(recipient->address, STR(reply.recipient))) RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); } /* * Bounce null recipients. This should never happen, but is most * likely the result of a fault in a different program, so aborting * the queue manager process does not help. */ if (recipient->address[0] == 0) { QMGR_REDIRECT(&reply, MAIL_SERVICE_ERROR, "5.1.3 null recipient address"); } /* * Discard mail to the local double bounce address here, so this * system can run without a local delivery agent. They'd still have * to configure something for mail directed to the local postmaster, * though, but that is an RFC requirement anyway. * * XXX This lookup should be done in the resolver, and the mail should * be directed to a general-purpose null delivery agent. */ if (reply.flags & RESOLVE_CLASS_LOCAL) { at = strrchr(STR(reply.recipient), '@'); len = (at ? (at - STR(reply.recipient)) : strlen(STR(reply.recipient))); if (strncasecmp(STR(reply.recipient), var_double_bounce_sender, len) == 0 && !var_double_bounce_sender[len]) { status = sent(message->tflags, message->queue_id, QMGR_MSG_STATS(&stats, message), recipient, "none", DSN_SIMPLE(&dsn, "2.0.0", "undeliverable postmaster notification discarded")); if (status == 0) { deliver_completed(message->fp, recipient->offset); #if 0 /* It's the default verification probe sender address. */ msg_warn("%s: undeliverable postmaster notification discarded", message->queue_id); #endif } else message->flags |= status; continue; } } /* * Optionally defer deliveries over specific transports, unless the * restriction is lifted temporarily. */ if (*var_defer_xports && (message->qflags & QMGR_FLUSH_DFXP) == 0) { if (defer_xport_argv == 0) defer_xport_argv = argv_split(var_defer_xports, CHARS_COMMA_SP); for (cpp = defer_xport_argv->argv; *cpp; cpp++) if (strcmp(*cpp, STR(reply.transport)) == 0) break; if (*cpp) { QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY, "4.3.2 deferred transport"); } } /* * Look up or instantiate the proper transport. */ if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) { if ((transport = qmgr_transport_find(STR(reply.transport))) == 0) transport = qmgr_transport_create(STR(reply.transport)); queue = 0; } /* * This message is being flushed. If need-be unthrottle the * transport. */ if ((message->qflags & QMGR_FLUSH_EACH) != 0 && QMGR_TRANSPORT_THROTTLED(transport)) qmgr_transport_unthrottle(transport); /* * This transport is dead. Defer delivery to this recipient. */ if (QMGR_TRANSPORT_THROTTLED(transport)) { saved_dsn = transport->dsn; if ((transport = qmgr_error_transport(MAIL_SERVICE_RETRY)) != 0) { nexthop = qmgr_error_nexthop(saved_dsn); vstring_strcpy(reply.nexthop, nexthop); myfree(nexthop); queue = 0; } else { qmgr_defer_recipient(message, recipient, saved_dsn); continue; } } /* * The nexthop destination provides the default name for the * per-destination queue. When the delivery agent accepts only one * recipient per delivery, give each recipient its own queue, so that * deliveries to different recipients of the same message can happen * in parallel, and so that we can enforce per-recipient concurrency * limits and prevent one recipient from tying up all the delivery * agent resources. We use recipient@nexthop as queue name rather * than the actual recipient domain name, so that one recipient in * multiple equivalent domains cannot evade the per-recipient * concurrency limit. Split the address on the recipient delimiter if * one is defined, so that extended addresses don't get extra * delivery slots. * * Fold the result to lower case so that we don't have multiple queues * for the same name. * * Important! All recipients in a queue must have the same nexthop * value. It is OK to have multiple queues with the same nexthop * value, but only when those queues are named after recipients. * * The single-recipient code below was written for local(8) like * delivery agents, and assumes that all domains that deliver to the * same (transport + nexthop) are aliases for $nexthop. Delivery * concurrency is changed from per-domain into per-recipient, by * changing the queue name from nexthop into localpart@nexthop. * * XXX This assumption is incorrect when different destinations share * the same (transport + nexthop). In reality, such transports are * rarely configured to use single-recipient deliveries. The fix is * to decouple the per-destination recipient limit from the * per-destination concurrency. */ vstring_strcpy(queue_name, STR(reply.nexthop)); if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0 && strcmp(transport->name, MAIL_SERVICE_RETRY) != 0 && transport->recipient_limit == 1) { /* Copy the recipient localpart. */ at = strrchr(STR(reply.recipient), '@'); len = (at ? (at - STR(reply.recipient)) : strlen(STR(reply.recipient))); vstring_strncpy(queue_name, STR(reply.recipient), len); /* Remove the address extension from the recipient localpart. */ if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim)) vstring_truncate(queue_name, strlen(STR(queue_name))); /* Assume the recipient domain is equivalent to nexthop. */ vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop)); } lowercase(STR(queue_name)); /* * This transport is alive. Find or instantiate a queue for this * recipient. */ if (queue == 0 || !STREQ(queue->name, STR(queue_name))) { if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0) queue = qmgr_queue_create(transport, STR(queue_name), STR(reply.nexthop)); } /* * This message is being flushed. If need-be unthrottle the queue. */ if ((message->qflags & QMGR_FLUSH_EACH) != 0 && QMGR_QUEUE_THROTTLED(queue)) qmgr_queue_unthrottle(queue); /* * This queue is dead. Defer delivery to this recipient. */ if (QMGR_QUEUE_THROTTLED(queue)) { saved_dsn = queue->dsn; if ((queue = qmgr_error_queue(MAIL_SERVICE_RETRY, saved_dsn)) == 0) { qmgr_defer_recipient(message, recipient, saved_dsn); continue; } } /* * This queue is alive. Bind this recipient to this queue instance. */ recipient->u.queue = queue; } resolve_clnt_free(&reply); vstring_free(queue_name); }
static int smtp_bulk_fail(SMTP_STATE *state, int throttle_queue) { DELIVER_REQUEST *request = state->request; SMTP_SESSION *session = state->session; DSN_BUF *why = state->why; RECIPIENT *rcpt; int status; int soft_error = (STR(why->status)[0] == '4'); int soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce); int nrcpt; /* * Don't defer the recipients just yet when this error qualifies them for * delivery to a backup server. Just log something informative to show * why we're skipping this host. */ if ((soft_error || soft_bounce_error) && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) { msg_info("%s: %s", request->queue_id, STR(why->reason)); for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (SMTP_RCPT_ISMARKED(rcpt)) continue; SMTP_RCPT_KEEP(state, rcpt); } } /* * Defer or bounce all the remaining recipients, and delete them from the * delivery request. If a bounce fails, defer instead and do not qualify * the recipient for delivery to a backup server. */ else { /* * If we are still in the connection set-up phase, update the set-up * completion time here, otherwise the time spent in set-up latency * will be attributed as message transfer latency. * * All remaining recipients have failed at this point, so we update the * delivery completion time stamp so that multiple recipient status * records show the same delay values. */ if (request->msg_stats.conn_setup_done.tv_sec == 0) { GETTIMEOFDAY(&request->msg_stats.conn_setup_done); request->msg_stats.deliver_done = request->msg_stats.conn_setup_done; } else GETTIMEOFDAY(&request->msg_stats.deliver_done); (void) DSN_FROM_DSN_BUF(why); for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { rcpt = request->rcpt_list.info + nrcpt; if (SMTP_RCPT_ISMARKED(rcpt)) continue; status = (soft_error ? defer_append : bounce_append) (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, &request->msg_stats, rcpt, session ? session->namaddrport : "none", &why->dsn); if (status == 0) deliver_completed(state->src, rcpt->offset); SMTP_RCPT_DROP(state, rcpt); state->status |= status; } if ((state->misc_flags & SMTP_MISC_FLAG_COMPLETE_SESSION) == 0 && throttle_queue && (soft_error || soft_bounce_error) && request->hop_status == 0) request->hop_status = DSN_COPY(&why->dsn); } /* * Don't cache this session. We can't talk to this server. */ if (throttle_queue && session) DONT_CACHE_BAD_SESSION; return (-1); }
static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv) { const char *myname = "deliver_message"; static PIPE_PARAMS conf; static PIPE_ATTR attr; RECIPIENT_LIST *rcpt_list = &request->rcpt_list; DSN_BUF *why = dsb_create(); VSTRING *buf; ARGV *expanded_argv = 0; int deliver_status; int command_status; ARGV *export_env; const char *sender; #define DELIVER_MSG_CLEANUP() { \ dsb_free(why); \ if (expanded_argv) argv_free(expanded_argv); \ } if (msg_verbose) msg_info("%s: from <%s>", myname, request->sender); /* * Sanity checks. The get_service_params() and get_service_attr() * routines also do some sanity checks. Look up service attributes and * config information only once. This is safe since the information comes * from a trusted source, not from the delivery request. */ if (request->nexthop[0] == 0) msg_fatal("empty nexthop hostname"); if (rcpt_list->len <= 0) msg_fatal("recipient count: %d", rcpt_list->len); if (attr.command == 0) { get_service_params(&conf, service); get_service_attr(&attr, argv); } /* * The D flag cannot be specified for multi-recipient deliveries. */ if ((attr.flags & MAIL_COPY_DELIVERED) && (rcpt_list->len > 1)) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); msg_warn("pipe flag `D' requires %s_destination_recipient_limit = 1", service); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * The O flag cannot be specified for multi-recipient deliveries. */ if ((attr.flags & MAIL_COPY_ORIG_RCPT) && (rcpt_list->len > 1)) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); msg_warn("pipe flag `O' requires %s_destination_recipient_limit = 1", service); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Check that this agent accepts messages this large. */ if (attr.size_limit != 0 && request->data_size > attr.size_limit) { if (msg_verbose) msg_info("%s: too big: size_limit = %ld, request->data_size = %ld", myname, (long) attr.size_limit, request->data_size); dsb_simple(why, "5.2.3", "message too large"); deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Don't deliver a trace-only request. */ if (DEL_REQ_TRACE_ONLY(request->flags)) { RECIPIENT *rcpt; int status; int n; deliver_status = 0; dsb_simple(why, "2.0.0", "delivers to command: %s", attr.command[0]); (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); deliver_status |= status; } DELIVER_MSG_CLEANUP(); return (deliver_status); } /* * Report mail delivery loops. By definition, this requires * single-recipient delivery. Don't silently lose recipients. */ if (attr.flags & MAIL_COPY_DELIVERED) { DELIVERED_HDR_INFO *info; RECIPIENT *rcpt; int loop_found; if (request->rcpt_list.len > 1) msg_panic("%s: delivered-to enabled with multi-recipient request", myname); info = delivered_hdr_init(request->fp, request->data_offset, FOLD_ADDR_ALL); rcpt = request->rcpt_list.info; loop_found = delivered_hdr_find(info, rcpt->address); delivered_hdr_free(info); if (loop_found) { dsb_simple(why, "5.4.6", "mail forwarding loop for %s", rcpt->address); deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } } /* * Deliver. Set the nexthop and sender variables, and expand the command * argument vector. Recipients will be expanded on the fly. XXX Rewrite * envelope and header addresses according to transport-specific * rewriting rules. */ if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp)); /* * A non-empty null sender replacement is subject to the 'q' flag. */ buf = vstring_alloc(10); sender = *request->sender ? request->sender : STR(attr.null_sender); if (*sender && (attr.flags & PIPE_OPT_QUOTE_LOCAL)) { quote_822_local(buf, sender); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf)); } else dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, sender); if (attr.flags & PIPE_OPT_FOLD_HOST) { vstring_strcpy(buf, request->nexthop); lowercase(STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf)); } else dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop); vstring_sprintf(buf, "%ld", (long) request->data_size); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_ADDR, request->client_addr); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_HELO, request->client_helo); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_NAME, request->client_name); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PORT, request->client_port); dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PROTO, request->client_proto); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_METHOD, request->sasl_method); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_USERNAME, request->sasl_username); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_SENDER, request->sasl_sender); dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID, request->queue_id); vstring_free(buf); if ((expanded_argv = expand_argv(service, attr.command, rcpt_list, attr.flags)) == 0) { dsb_simple(why, "4.3.5", "mail system configuration error"); deliver_status = eval_command_status(PIPE_STAT_DEFER, service, request, &attr, why); DELIVER_MSG_CLEANUP(); return (deliver_status); } export_env = argv_split(var_export_environ, ", \t\r\n"); command_status = pipe_command(request->fp, why, PIPE_CMD_UID, attr.uid, PIPE_CMD_GID, attr.gid, PIPE_CMD_SENDER, sender, PIPE_CMD_COPY_FLAGS, attr.flags, PIPE_CMD_ARGV, expanded_argv->argv, PIPE_CMD_TIME_LIMIT, conf.time_limit, PIPE_CMD_EOL, STR(attr.eol), PIPE_CMD_EXPORT, export_env->argv, PIPE_CMD_CWD, attr.exec_dir, PIPE_CMD_CHROOT, attr.chroot_dir, PIPE_CMD_ORIG_RCPT, rcpt_list->info[0].orig_addr, PIPE_CMD_DELIVERED, rcpt_list->info[0].address, PIPE_CMD_END); argv_free(export_env); deliver_status = eval_command_status(command_status, service, request, &attr, why); /* * Clean up. */ DELIVER_MSG_CLEANUP(); return (deliver_status); }
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; char *saved_text; /* * Depending on the result, bounce or defer the message, and mark the * recipient as done where appropriate. */ switch (command_status) { case PIPE_STAT_OK: /* Save the command output before dsb_update() clobbers it. */ vstring_truncate(why->reason, trimblanks(STR(why->reason), VSTRING_LEN(why->reason)) - STR(why->reason)); if (VSTRING_LEN(why->reason) > 0) { VSTRING_TERMINATE(why->reason); saved_text = vstring_export(vstring_sprintf( vstring_alloc(VSTRING_LEN(why->reason)), " (%.100s)", STR(why->reason))); } else saved_text = mystrdup(""); /* uses shared R/O storage */ dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ? "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, "delivered via %s service%s", service, saved_text); myfree(saved_text); (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); for (n = 0; n < request->rcpt_list.len; n++) { rcpt = request->rcpt_list.info + n; /* XXX Maybe encapsulate this with ndr_append(). */ status = (STR(why->status)[0] != '4' ? bounce_append : defer_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; } 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); }