const char *cleanup_strflags(unsigned flags) { static VSTRING *result; unsigned i; if (flags == 0) return ("none"); if (result == 0) result = vstring_alloc(20); else VSTRING_RESET(result); for (i = 0; i < sizeof(cleanup_flag_map) / sizeof(cleanup_flag_map[0]); i++) { if (cleanup_flag_map[i].flag & flags) { vstring_sprintf_append(result, "%s ", cleanup_flag_map[i].text); flags &= ~cleanup_flag_map[i].flag; } } if (flags != 0 || VSTRING_LEN(result) == 0) msg_panic("cleanup_strflags: unrecognized flag value(s) 0x%x", flags); vstring_truncate(result, VSTRING_LEN(result) - 1); VSTRING_TERMINATE(result); return (vstring_str(result)); }
int memcache_get(VSTREAM *stream, VSTRING *vp, ssize_t bound) { int last_char; int next_char; last_char = (bound == 0 ? vstring_get(vp, stream) : vstring_get_bound(vp, stream, bound)); switch (last_char) { /* * Do some repair in the rare case that we stopped reading in the * middle of the CRLF record terminator. */ case '\r': if ((next_char = VSTREAM_GETC(stream)) == '\n') { VSTRING_ADDCH(vp, '\n'); /* FALLTRHOUGH */ } else { if (next_char != VSTREAM_EOF) vstream_ungetc(stream, next_char); /* * Input too long, or EOF */ default: if (msg_verbose) msg_info("%s got %s", VSTREAM_PATH(stream), LEN(vp) < bound ? "EOF" : "input too long"); return (-1); } /* * Strip off the record terminator: either CRLF or just bare LF. */ case '\n': vstring_truncate(vp, VSTRING_LEN(vp) - 1); if (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r') vstring_truncate(vp, VSTRING_LEN(vp) - 1); VSTRING_TERMINATE(vp); if (msg_verbose) msg_info("%s got: %s", VSTREAM_PATH(stream), STR(vp)); return (0); } }
VSTRING *readlline(VSTRING *buf, VSTREAM *fp, int *lineno) { int ch; int next; int start; char *cp; VSTRING_RESET(buf); /* * Ignore comment lines, all whitespace lines, and empty lines. Terminate * at EOF or at the beginning of the next logical line. */ for (;;) { /* Read one line, possibly not newline terminated. */ start = LEN(buf); while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n') VSTRING_ADDCH(buf, ch); if (ch == '\n' && lineno != 0) *lineno += 1; /* Ignore comment line, all whitespace line, or empty line. */ for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++) /* void */ ; if (cp == END(buf) || *cp == '#') vstring_truncate(buf, start); /* Terminate at EOF or at the beginning of the next logical line. */ if (ch == VSTREAM_EOF) break; if (LEN(buf) > 0) { if ((next = VSTREAM_GETC(fp)) != VSTREAM_EOF) vstream_ungetc(fp, next); if (next != '#' && !ISSPACE(next)) break; } } VSTRING_TERMINATE(buf); /* * Invalid input: continuing text without preceding text. Allowing this * would complicate "postconf -e", which implements its own multi-line * parsing routine. Do not abort, just warn, so that critical programs * like postmap do not leave behind a truncated table. */ if (LEN(buf) > 0 && ISSPACE(*STR(buf))) { msg_warn("%s: logical line must not start with whitespace: \"%.30s%s\"", VSTREAM_PATH(fp), STR(buf), LEN(buf) > 30 ? "..." : ""); return (readlline(buf, fp, lineno)); } /* * Done. */ return (LEN(buf) > 0 ? buf : 0); }
static void mime_state_downgrade(MIME_STATE *state, int rec_type, const char *text, int len) { static char hexchars[] = "0123456789ABCDEF"; const unsigned char *cp; int ch; #define QP_ENCODE(buffer, ch) { \ VSTRING_ADDCH(buffer, '='); \ VSTRING_ADDCH(buffer, hexchars[(ch >> 4) & 0xff]); \ VSTRING_ADDCH(buffer, hexchars[ch & 0xf]); \ } /* * Insert a soft line break when the output reaches a critical length * before we reach a hard line break. */ for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) { /* Critical length before hard line break. */ if (LEN(state->output_buffer) > 72) { VSTRING_ADDCH(state->output_buffer, '='); VSTRING_TERMINATE(state->output_buffer); BODY_OUT(state, REC_TYPE_NORM, STR(state->output_buffer), LEN(state->output_buffer)); VSTRING_RESET(state->output_buffer); } /* Append the next character. */ ch = *cp; if ((ch < 32 && ch != '\t') || ch == '=' || ch > 126) { QP_ENCODE(state->output_buffer, ch); } else { VSTRING_ADDCH(state->output_buffer, ch); } } /* * Flush output after a hard line break (i.e. the end of a REC_TYPE_NORM * record). Fix trailing whitespace as per the RFC: in the worst case, * the output length will grow from 73 characters to 75 characters. */ if (rec_type == REC_TYPE_NORM) { if (LEN(state->output_buffer) > 0 && ((ch = END(state->output_buffer)[-1]) == ' ' || ch == '\t')) { vstring_truncate(state->output_buffer, LEN(state->output_buffer) - 1); QP_ENCODE(state->output_buffer, ch); } VSTRING_TERMINATE(state->output_buffer); BODY_OUT(state, REC_TYPE_NORM, STR(state->output_buffer), LEN(state->output_buffer)); VSTRING_RESET(state->output_buffer); } }
static void cleanup_rewrite_recip(CLEANUP_STATE *state, const HEADER_OPTS *hdr_opts, VSTRING *header_buf) { TOK822 *tree; TOK822 **addr_list; TOK822 **tpp; int did_rewrite = 0; if (msg_verbose) msg_info("rewrite_recip: %s", hdr_opts->name); /* * Parse the header line, rewrite each address found, and regenerate the * header line. Finally, pipe the result through the header line folding * routine. */ tree = tok822_parse_limit(vstring_str(header_buf) + strlen(hdr_opts->name) + 1, var_token_limit); addr_list = tok822_grep(tree, TOK822_ADDR); for (tpp = addr_list; *tpp; tpp++) { did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp); if (state->flags & CLEANUP_FLAG_MAP_OK) { if (cleanup_rcpt_canon_maps && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT)) did_rewrite |= cleanup_map11_tree(state, *tpp, cleanup_rcpt_canon_maps, cleanup_ext_prop_mask & EXT_PROP_CANONICAL); if (cleanup_comm_canon_maps && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT)) did_rewrite |= cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps, cleanup_ext_prop_mask & EXT_PROP_CANONICAL); if (cleanup_masq_domains && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT)) did_rewrite |= cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains); } } if (did_rewrite) { vstring_truncate(header_buf, strlen(hdr_opts->name)); vstring_strcat(header_buf, ": "); tok822_externalize(header_buf, tree, TOK822_STR_HEAD); } myfree((char *) addr_list); tok822_free_tree(tree); if ((hdr_opts->flags & HDR_OPT_DROP) == 0) { if (did_rewrite) cleanup_fold_header(state, header_buf); else cleanup_out_header(state, header_buf); } }
int psc_send_reply(PSC_STATE *state, const char *text) { ssize_t start; int ret; const char *footer; ssize_t text_len = strlen(text) - 2; if (msg_verbose) msg_info("> [%s]:%s: %.*s", state->smtp_client_addr, state->smtp_client_port, (int) text_len, text); /* * Append the new text to earlier text that could not be sent because the * output was throttled. */ start = VSTRING_LEN(state->send_buf); vstring_strcat(state->send_buf, text); /* * For soft_bounce support, we also fix the REJECT logging before the * dummy SMTP engine calls the psc_send_reply() output routine. We do * some double work, but it is for debugging only. */ if (var_soft_bounce) { if (text[0] == '5') STR(state->send_buf)[start + 0] = '4'; if (text[4] == '5') STR(state->send_buf)[start + 4] = '4'; } /* * Append the optional reply footer. */ if ((*text == '4' || *text == '5') && ((psc_rej_ftr_maps != 0 && (footer = psc_get_footer(text, text_len)) != 0) || *(footer = var_psc_rej_footer) != 0)) smtp_reply_footer(state->send_buf, start, footer, STR(psc_expand_filter), psc_expand_lookup, (void *) state); /* * Do a best effort sending text, but don't block when the output is * throttled by a hostile peer. */ ret = write(vstream_fileno(state->smtp_client_stream), STR(state->send_buf), LEN(state->send_buf)); if (ret > 0) vstring_truncate(state->send_buf, ret - LEN(state->send_buf)); if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET) msg_warn("write [%s]:%s: %m", state->smtp_client_addr, state->smtp_client_port); return (ret < 0 && errno != EAGAIN); }
const char *str_long_name_mask_opt(VSTRING *buf, const char *context, const LONG_NAME_MASK * table, long mask, int flags) { const char *myname = "name_mask"; int len; static VSTRING *my_buf = 0; int delim = (flags & NAME_MASK_COMMA ? ',' : (flags & NAME_MASK_PIPE ? '|' : ' ')); const LONG_NAME_MASK *np; if ((flags & STR_NAME_MASK_REQUIRED) == 0) msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag", myname); if (buf == 0) { if (my_buf == 0) my_buf = vstring_alloc(1); buf = my_buf; } VSTRING_RESET(buf); for (np = table; mask != 0; np++) { if (np->name == 0) { if (flags & NAME_MASK_NUMBER) { vstring_sprintf_append(buf, "0x%lx%c", mask, delim); } else if (flags & NAME_MASK_FATAL) { msg_fatal("%s: unknown %s bit in mask: 0x%lx", myname, context, mask); } else if (flags & NAME_MASK_RETURN) { msg_warn("%s: unknown %s bit in mask: 0x%lx", myname, context, mask); return (0); } else if (flags & NAME_MASK_WARN) { msg_warn("%s: unknown %s bit in mask: 0x%lx", myname, context, mask); } break; } if (mask & np->mask) { mask &= ~np->mask; vstring_sprintf_append(buf, "%s%c", np->name, delim); } } if ((len = VSTRING_LEN(buf)) > 0) vstring_truncate(buf, len - 1); VSTRING_TERMINATE(buf); return (STR(buf)); }
static void qmqpd_copy_sender(QMQPD_STATE *state) { char *end_prefix; char *end_origin; int verp_requested; static char verp_delims[] = "-="; /* * If the sender address looks like prefix@origin-@[], then request * variable envelope return path delivery, with an envelope sender * address of prefi@origin, and with VERP delimiters of x and =. This * way, the recipients will see envelope sender addresses that look like: * prefixuser=domain@origin. */ state->where = "receiving sender address"; netstring_get(state->client, state->buf, var_line_limit); VSTRING_TERMINATE(state->buf); verp_requested = ((end_origin = vstring_end(state->buf) - 4) > STR(state->buf) && strcmp(end_origin, "-@[]") == 0 && (end_prefix = strchr(STR(state->buf), '@')) != 0 /* XXX */ && --end_prefix < end_origin - 2 /* non-null origin */ && end_prefix > STR(state->buf)); /* non-null prefix */ if (verp_requested) { verp_delims[0] = end_prefix[0]; if (verp_delims_verify(verp_delims) != 0) { state->err |= CLEANUP_STAT_CONT; /* XXX */ vstring_sprintf(state->why_rejected, "Invalid VERP delimiters: \"%s\". Need two characters from \"%s\"", verp_delims, var_verp_filter); } memmove(end_prefix, end_prefix + 1, end_origin - end_prefix - 1); vstring_truncate(state->buf, end_origin - STR(state->buf) - 1); } if (state->err == CLEANUP_STAT_OK && REC_PUT_BUF(state->cleanup, REC_TYPE_FROM, state->buf) < 0) state->err = CLEANUP_STAT_WRITE; if (verp_requested) if (state->err == CLEANUP_STAT_OK && rec_put(state->cleanup, REC_TYPE_VERP, verp_delims, 2) < 0) state->err = CLEANUP_STAT_WRITE; state->sender = mystrndup(STR(state->buf), LEN(state->buf)); }
static void strip_address(VSTRING *vp, int start, TOK822 *addr) { VSTRING *tmp; /* * Emit plain <address>. Discard any comments or phrases. */ msg_warn("stripping too many comments from address: %.100s...", printable(vstring_str(vp) + start, '?')); vstring_truncate(vp, start); VSTRING_ADDCH(vp, '<'); if (addr) { tmp = vstring_alloc(100); tok822_internalize(tmp, addr, TOK822_STR_TERM); quote_822_local_flags(vp, vstring_str(tmp), QUOTE_FLAG_8BITCLEAN | QUOTE_FLAG_APPEND); vstring_free(tmp); } VSTRING_ADDCH(vp, '>'); }
const HEADER_OPTS *header_opts_find(const char *string) { const char *cp; if (header_hash == 0) header_opts_init(); /* * Look up the lower-cased version of the header name. */ VSTRING_RESET(header_key); for (cp = string; *cp != ':'; cp++) { if (*cp == 0) msg_panic("header_opts_find: no colon in header: %.30s", string); VSTRING_ADDCH(header_key, TOLOWER(*cp)); } vstring_truncate(header_key, trimblanks(vstring_str(header_key), cp - string) - vstring_str(header_key)); VSTRING_TERMINATE(header_key); return ((const HEADER_OPTS *) htable_find(header_hash, vstring_str(header_key))); }
int main(int unused_argc, char **unused_argv) { VSTRING *vp = vstring_alloc(100); TOK822 *list; VSTRING *buf = vstring_alloc(100); #define TEST_TOKEN_LIMIT 20 while (readlline(buf, VSTREAM_IN, (int *) 0)) { while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\n') { vstring_end(buf)[-1] = 0; vstring_truncate(buf, VSTRING_LEN(buf) - 1); } if (!isatty(vstream_fileno(VSTREAM_IN))) vstream_printf(">>>%s<<<\n\n", vstring_str(buf)); list = tok822_parse_limit(vstring_str(buf), TEST_TOKEN_LIMIT); vstream_printf("Parse tree:\n"); tok822_print(list, 0); vstream_printf("\n"); vstream_printf("Internalized:\n%s\n\n", vstring_str(tok822_internalize(vp, list, TOK822_STR_DEFL))); vstream_fflush(VSTREAM_OUT); vstream_printf("Externalized, no newlines inserted:\n%s\n\n", vstring_str(tok822_externalize(vp, list, TOK822_STR_DEFL | TOK822_STR_TRNC))); vstream_fflush(VSTREAM_OUT); vstream_printf("Externalized, newlines inserted:\n%s\n\n", vstring_str(tok822_externalize(vp, list, TOK822_STR_DEFL | TOK822_STR_LINE | TOK822_STR_TRNC))); vstream_fflush(VSTREAM_OUT); tok822_free_tree(list); } vstring_free(vp); vstring_free(buf); return (0); }
int smtp_get(VSTRING *vp, VSTREAM *stream, int bound) { int last_char; int next_char; /* * It's painful to do I/O with records that may span multiple buffers. * Allow for partial long lines (we will read the remainder later) and * allow for lines ending in bare LF. The idea is to be liberal in what * we accept, strict in what we send. * * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize * bare LF as record terminator. */ smtp_timeout_reset(stream); last_char = (bound == 0 ? vstring_get(vp, stream) : vstring_get_bound(vp, stream, bound)); switch (last_char) { /* * Do some repair in the rare case that we stopped reading in the * middle of the CRLF record terminator. */ case '\r': if ((next_char = VSTREAM_GETC(stream)) == '\n') { VSTRING_ADDCH(vp, '\n'); last_char = '\n'; /* FALLTRHOUGH */ } else { if (next_char != VSTREAM_EOF) vstream_ungetc(stream, next_char); break; } /* * Strip off the record terminator: either CRLF or just bare LF. * * XXX RFC 2821 disallows sending bare CR everywhere. We remove bare CR * if received before CRLF, and leave it alone otherwise. */ case '\n': vstring_truncate(vp, VSTRING_LEN(vp) - 1); while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r') vstring_truncate(vp, VSTRING_LEN(vp) - 1); VSTRING_TERMINATE(vp); /* * Partial line: just read the remainder later. If we ran into EOF, * the next test will deal with it. */ default: break; } smtp_timeout_detect(stream); /* * EOF is bad, whether or not it happens in the middle of a record. Don't * allow data that was truncated because of EOF. */ if (vstream_feof(stream) || vstream_ferror(stream)) { if (msg_verbose) msg_info("smtp_get: EOF"); vstream_longjmp(stream, SMTP_ERR_EOF); } return (last_char); }
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 void enqueue(const int flags, const char *encoding, const char *dsn_envid, int dsn_ret, int dsn_notify, const char *rewrite_context, const char *sender, const char *full_name, char **recipients) { VSTRING *buf; VSTREAM *dst; char *saved_sender; char **cpp; int type; char *start; int skip_from_; TOK822 *tree; TOK822 *tp; int rcpt_count = 0; enum { STRIP_CR_DUNNO, STRIP_CR_DO, STRIP_CR_DONT, STRIP_CR_ERROR } strip_cr; MAIL_STREAM *handle; VSTRING *postdrop_command; uid_t uid = getuid(); int status; int naddr; int prev_type; MIME_STATE *mime_state = 0; SM_STATE state; int mime_errs; const char *errstr; int addr_count; int level; static NAME_CODE sm_fix_eol_table[] = { SM_FIX_EOL_ALWAYS, STRIP_CR_DO, SM_FIX_EOL_STRICT, STRIP_CR_DUNNO, SM_FIX_EOL_NEVER, STRIP_CR_DONT, 0, STRIP_CR_ERROR, }; /* * Access control is enforced in the postdrop command. The code here * merely produces a more user-friendly interface. */ if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, uid)) != 0) msg_fatal_status(EX_NOPERM, "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); /* * Initialize. */ buf = vstring_alloc(100); /* * Stop run-away process accidents by limiting the queue file size. This * is not a defense against DOS attack. */ if (var_message_limit > 0 && get_file_limit() > var_message_limit) set_file_limit((off_t) var_message_limit); /* * The sender name is provided by the user. In principle, the mail pickup * service could deduce the sender name from queue file ownership, but: * pickup would not be able to run chrooted, and it may not be desirable * to use login names at all. */ if (sender != 0) { VSTRING_RESET(buf); VSTRING_TERMINATE(buf); tree = tok822_parse(sender); for (naddr = 0, tp = tree; tp != 0; tp = tp->next) if (tp->type == TOK822_ADDR && naddr++ == 0) tok822_internalize(buf, tp->head, TOK822_STR_DEFL); tok822_free_tree(tree); saved_sender = mystrdup(STR(buf)); if (naddr > 1) msg_warn("-f option specified malformed sender: %s", sender); } else { if ((sender = username()) == 0) msg_fatal_status(EX_OSERR, "no login name found for user ID %lu", (unsigned long) uid); saved_sender = mystrdup(sender); } /* * Let the postdrop command open the queue file for us, and sanity check * the content. XXX Make postdrop a manifest constant. */ errno = 0; postdrop_command = vstring_alloc(1000); vstring_sprintf(postdrop_command, "%s/postdrop -r", var_command_dir); for (level = 0; level < msg_verbose; level++) vstring_strcat(postdrop_command, " -v"); if ((handle = mail_stream_command(STR(postdrop_command))) == 0) msg_fatal_status(EX_UNAVAILABLE, "%s(%ld): unable to execute %s: %m", saved_sender, (long) uid, STR(postdrop_command)); vstring_free(postdrop_command); dst = handle->stream; /* * First, write envelope information to the output stream. * * For sendmail compatibility, parse each command-line recipient as if it * were an RFC 822 message header; some MUAs specify comma-separated * recipient lists; and some MUAs even specify "word word <address>". * * Sort-uniq-ing the recipient list is done after address canonicalization, * before recipients are written to queue file. That's cleaner than * having the queue manager nuke duplicate recipient status records. * * XXX Should limit the size of envelope records. * * With "sendmail -N", instead of a per-message NOTIFY record we store one * per recipient so that we can simplify the implementation somewhat. */ if (dsn_envid) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_DSN_ENVID, dsn_envid); if (dsn_ret) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_RET, dsn_ret); rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_RWR_CONTEXT, rewrite_context); if (full_name || (full_name = fullname()) != 0) rec_fputs(dst, REC_TYPE_FULL, full_name); rec_fputs(dst, REC_TYPE_FROM, saved_sender); if (verp_delims && *saved_sender == 0) msg_fatal_status(EX_USAGE, "%s(%ld): -V option requires non-null sender address", saved_sender, (long) uid); if (encoding) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_ENCODING, encoding); if (DEL_REQ_TRACE_FLAGS(flags)) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS, DEL_REQ_TRACE_FLAGS(flags)); if (verp_delims) rec_fputs(dst, REC_TYPE_VERP, verp_delims); if (recipients) { for (cpp = recipients; *cpp != 0; cpp++) { tree = tok822_parse(*cpp); for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { if (tp->type == TOK822_ADDR) { tok822_internalize(buf, tp->head, TOK822_STR_DEFL); if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; ++addr_count; } } tok822_free_tree(tree); if (addr_count == 0) { if (rec_put(dst, REC_TYPE_RCPT, "", 0) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } } } /* * Append the message contents to the queue file. Write chunks of at most * 1kbyte. Internally, we use different record types for data ending in * LF and for data that doesn't, so we can actually be binary transparent * for local mail. Unfortunately, SMTP has no record continuation * convention, so there is no guarantee that arbitrary data will be * delivered intact via SMTP. Strip leading From_ lines. For the benefit * of UUCP environments, also get rid of leading >>>From_ lines. */ rec_fputs(dst, REC_TYPE_MESG, ""); if (DEL_REQ_TRACE_ONLY(flags) != 0) { if (flags & SM_FLAG_XRCPT) msg_fatal_status(EX_USAGE, "%s(%ld): -t option cannot be used with -bv", saved_sender, (long) uid); if (*saved_sender) rec_fprintf(dst, REC_TYPE_NORM, "From: %s", saved_sender); rec_fprintf(dst, REC_TYPE_NORM, "Subject: probe"); if (recipients) { rec_fprintf(dst, REC_TYPE_CONT, "To:"); for (cpp = recipients; *cpp != 0; cpp++) { rec_fprintf(dst, REC_TYPE_NORM, " %s%s", *cpp, cpp[1] ? "," : ""); } } } else { /* * Initialize the MIME processor and set up the callback context. */ if (flags & SM_FLAG_XRCPT) { state.dst = dst; state.recipients = argv_alloc(2); state.resent_recip = argv_alloc(2); state.resent = 0; state.saved_sender = saved_sender; state.uid = uid; state.temp = vstring_alloc(10); mime_state = mime_state_alloc(MIME_OPT_DISABLE_MIME | MIME_OPT_REPORT_TRUNC_HEADER, output_header, (MIME_STATE_ANY_END) 0, output_text, (MIME_STATE_ANY_END) 0, (MIME_STATE_ERR_PRINT) 0, (void *) &state); } /* * Process header/body lines. */ skip_from_ = 1; strip_cr = name_code(sm_fix_eol_table, NAME_CODE_FLAG_STRICT_CASE, var_sm_fix_eol); if (strip_cr == STRIP_CR_ERROR) msg_fatal_status(EX_USAGE, "invalid %s value: %s", VAR_SM_FIX_EOL, var_sm_fix_eol); for (prev_type = 0; (type = rec_streamlf_get(VSTREAM_IN, buf, var_line_limit)) != REC_TYPE_EOF; prev_type = type) { if (strip_cr == STRIP_CR_DUNNO && type == REC_TYPE_NORM) { if (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') strip_cr = STRIP_CR_DO; else strip_cr = STRIP_CR_DONT; } if (skip_from_) { if (type == REC_TYPE_NORM) { start = STR(buf); if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) continue; } skip_from_ = 0; } if (strip_cr == STRIP_CR_DO && type == REC_TYPE_NORM) while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') vstring_truncate(buf, VSTRING_LEN(buf) - 1); if ((flags & SM_FLAG_AEOF) && prev_type != REC_TYPE_CONT && VSTRING_LEN(buf) == 1 && *STR(buf) == '.') break; if (mime_state) { mime_errs = mime_state_update(mime_state, type, STR(buf), VSTRING_LEN(buf)); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); } else { if (REC_PUT_BUF(dst, type, buf) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); } } } /* * Finish MIME processing. We need a final mime_state_update() call in * order to flush text that is still buffered. That can happen when the * last line did not end in newline. */ if (mime_state) { mime_errs = mime_state_update(mime_state, REC_TYPE_EOF, "", 0); if (mime_errs) msg_fatal_status(EX_DATAERR, "%s(%ld): unable to extract recipients: %s", saved_sender, (long) uid, mime_state_error(mime_errs)); mime_state = mime_state_free(mime_state); } /* * Append recipient addresses that were extracted from message headers. */ rec_fputs(dst, REC_TYPE_XTRA, ""); if (flags & SM_FLAG_XRCPT) { for (cpp = state.resent ? state.resent_recip->argv : state.recipients->argv; *cpp; cpp++) { if (dsn_notify) rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_DSN_NOTIFY, dsn_notify); if (rec_put(dst, REC_TYPE_RCPT, *cpp, strlen(*cpp)) < 0) msg_fatal_status(EX_TEMPFAIL, "%s(%ld): error writing queue file: %m", saved_sender, (long) uid); ++rcpt_count; } argv_free(state.recipients); argv_free(state.resent_recip); vstring_free(state.temp); } if (rcpt_count == 0) msg_fatal_status(EX_USAGE, (flags & SM_FLAG_XRCPT) ? "%s(%ld): No recipient addresses found in message header" : "%s(%ld): Recipient addresses must be specified on" " the command line or via the -t option", saved_sender, (long) uid); /* * Identify the end of the queue file. */ rec_fputs(dst, REC_TYPE_END, ""); /* * Make sure that the message makes it to the file system. Once we have * terminated with successful exit status we cannot lose the message due * to "frivolous reasons". If all goes well, prevent the run-time error * handler from removing the file. */ if (vstream_ferror(VSTREAM_IN)) msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m", saved_sender, (long) uid); if ((status = mail_stream_finish(handle, (VSTRING *) 0)) != 0) msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE : (status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL : EX_UNAVAILABLE, "%s(%ld): %s", saved_sender, (long) uid, cleanup_strerror(status)); /* * Don't leave them in the dark. */ if (DEL_REQ_TRACE_FLAGS(flags)) { vstream_printf("Mail Delivery Status Report will be mailed to <%s>.\n", saved_sender); vstream_fflush(VSTREAM_OUT); } /* * Cleanup. Not really necessary as we're about to exit, but good for * debugging purposes. */ vstring_free(buf); myfree(saved_sender); }
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); }
const char *make_verify_sender_addr(void) { static VSTRING *verify_sender_buf; /* the complete sender address */ static VSTRING *my_epoch_buf; /* scratch space */ char *my_at_domain; /* * The null sender is always time-independent. */ if (*var_verify_sender == 0 || strcmp(var_verify_sender, "<>") == 0) return (""); /* * Sanity check. */ if (*var_verify_sender == '@') msg_fatal("parameter %s: value \"%s\" must not start with '@'", VAR_VERIFY_SENDER, var_verify_sender); if ((my_at_domain = strchr(var_verify_sender, '@')) != 0 && my_at_domain[1] == 0) msg_fatal("parameter %s: value \"%s\" must not end with '@'", VAR_VERIFY_SENDER, var_verify_sender); /* * One-time initialization. */ if (verify_sender_buf == 0) { verify_sender_buf = vstring_alloc(10); my_epoch_buf = vstring_alloc(10); } /* * Start with the bare sender address. */ vstring_strcpy(verify_sender_buf, var_verify_sender); /* * Append the time stamp to the address localpart, encoded in some * non-decimal form for obscurity. * * XXX It would be nice to have safe_ultostr() append-only support. */ if (var_verify_sender_ttl > 0) { /* Strip the @domain portion, if applicable. */ if (my_at_domain != 0) vstring_truncate(verify_sender_buf, (ssize_t) (my_at_domain - var_verify_sender)); /* Append the time stamp to the address localpart. */ vstring_sprintf_append(verify_sender_buf, "%s", safe_ultostr(my_epoch_buf, VERIFY_SENDER_ADDR_EPOCH(), VERIFY_BASE, 0, 0)); /* Add back the @domain, if applicable. */ if (my_at_domain != 0) vstring_sprintf_append(verify_sender_buf, "%s", my_at_domain); } /* * Rewrite the address to canonical form. */ rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, STR(verify_sender_buf), verify_sender_buf); return (STR(verify_sender_buf)); }
ARGV *mail_addr_map(MAPS *path, const char *address, int propagate) { VSTRING *buffer = 0; const char *myname = "mail_addr_map"; const char *string; char *ratsign; char *extension = 0; ARGV *argv = 0; int i; /* * Look up the full address; if no match is found, look up the address * with the extension stripped off, and remember the unmatched extension. */ if ((string = mail_addr_find(path, address, &extension)) != 0) { /* * Prepend the original user to @otherdomain, but do not propagate * the unmatched address extension. */ if (*string == '@') { buffer = vstring_alloc(100); if ((ratsign = strrchr(address, '@')) != 0) vstring_strncpy(buffer, address, ratsign - address); else vstring_strcpy(buffer, address); if (extension) vstring_truncate(buffer, LEN(buffer) - strlen(extension)); vstring_strcat(buffer, string); string = STR(buffer); } /* * Canonicalize and externalize the result, and propagate the * unmatched extension to each address found. */ argv = mail_addr_crunch(string, propagate ? extension : 0); if (buffer) vstring_free(buffer); if (msg_verbose) for (i = 0; i < argv->argc; i++) msg_info("%s: %s -> %d: %s", myname, address, i, argv->argv[i]); if (argv->argc == 0) { msg_warn("%s lookup of %s returns non-address result \"%s\"", path->title, address, string); argv = argv_free(argv); path->error = DICT_ERR_RETRY; } } /* * No match found. */ else { if (msg_verbose) msg_info("%s: %s -> %s", myname, address, path->error ? "(try again)" : "(not found)"); } /* * Cleanup. */ if (extension) myfree(extension); return (argv); }