diff options
Diffstat (limited to 'smtpd/smtp_session.c')
-rw-r--r-- | smtpd/smtp_session.c | 3223 |
1 files changed, 3223 insertions, 0 deletions
diff --git a/smtpd/smtp_session.c b/smtpd/smtp_session.c new file mode 100644 index 00000000..aefce155 --- /dev/null +++ b/smtpd/smtp_session.c @@ -0,0 +1,3223 @@ +/* $OpenBSD: smtp_session.c,v 1.426 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <limits.h> +#include <inttypes.h> +#include <openssl/ssl.h> +#include <resolv.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include <vis.h> +#else +#include "bsd-vis.h" +#endif + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" +#include "rfc5322.h" + +#define SMTP_LINE_MAX 65535 +#define DATA_HIWAT 65535 +#define APPEND_DOMAIN_BUFFER_SIZE SMTP_LINE_MAX + +enum smtp_state { + STATE_NEW = 0, + STATE_CONNECTED, + STATE_TLS, + STATE_HELO, + STATE_AUTH_INIT, + STATE_AUTH_USERNAME, + STATE_AUTH_PASSWORD, + STATE_AUTH_FINALIZE, + STATE_BODY, + STATE_QUIT, +}; + +enum session_flags { + SF_EHLO = 0x0001, + SF_8BITMIME = 0x0002, + SF_SECURE = 0x0004, + SF_AUTHENTICATED = 0x0008, + SF_BOUNCE = 0x0010, + SF_VERIFIED = 0x0020, + SF_BADINPUT = 0x0080, +}; + +enum { + TX_OK = 0, + TX_ERROR_ENVELOPE, + TX_ERROR_SIZE, + TX_ERROR_IO, + TX_ERROR_LOOP, + TX_ERROR_MALFORMED, + TX_ERROR_RESOURCES, + TX_ERROR_INTERNAL, +}; + +enum smtp_command { + CMD_HELO = 0, + CMD_EHLO, + CMD_STARTTLS, + CMD_AUTH, + CMD_MAIL_FROM, + CMD_RCPT_TO, + CMD_DATA, + CMD_RSET, + CMD_QUIT, + CMD_HELP, + CMD_WIZ, + CMD_NOOP, + CMD_COMMIT, +}; + +struct smtp_rcpt { + TAILQ_ENTRY(smtp_rcpt) entry; + uint64_t evpid; + struct mailaddr maddr; + size_t destcount; +}; + +struct smtp_tx { + struct smtp_session *session; + uint32_t msgid; + + struct envelope evp; + size_t rcptcount; + size_t destcount; + TAILQ_HEAD(, smtp_rcpt) rcpts; + + time_t time; + int error; + size_t datain; + size_t odatalen; + FILE *ofile; + struct io *filter; + struct rfc5322_parser *parser; + int rcvcount; + int has_date; + int has_message_id; + + uint8_t junk; +}; + +struct smtp_session { + uint64_t id; + struct io *io; + struct listener *listener; + void *ssl_ctx; + struct sockaddr_storage ss; + char rdns[HOST_NAME_MAX+1]; + char smtpname[HOST_NAME_MAX+1]; + int fcrdns; + + int flags; + enum smtp_state state; + + uint8_t banner_sent; + char helo[LINE_MAX]; + char cmd[LINE_MAX]; + char username[SMTPD_MAXMAILADDRSIZE]; + + size_t mailcount; + struct event pause; + + struct smtp_tx *tx; + + enum smtp_command last_cmd; + enum filter_phase filter_phase; + const char *filter_param; + + uint8_t junk; +}; + +#define ADVERTISE_TLS(s) \ + ((s)->listener->flags & F_STARTTLS && !((s)->flags & SF_SECURE)) + +#define ADVERTISE_AUTH(s) \ + ((s)->listener->flags & F_AUTH && (s)->flags & SF_SECURE && \ + !((s)->flags & SF_AUTHENTICATED)) + +#define ADVERTISE_EXT_DSN(s) \ + ((s)->listener->flags & F_EXT_DSN) + +#define SESSION_FILTERED(s) \ + ((s)->listener->flags & F_FILTERED) + +#define SESSION_DATA_FILTERED(s) \ + ((s)->listener->flags & F_FILTERED) + + +static int smtp_mailaddr(struct mailaddr *, char *, int, char **, const char *); +static void smtp_session_init(void); +static void smtp_lookup_servername(struct smtp_session *); +static void smtp_getnameinfo_cb(void *, int, const char *, const char *); +static void smtp_getaddrinfo_cb(void *, int, struct addrinfo *); +static void smtp_connected(struct smtp_session *); +static void smtp_send_banner(struct smtp_session *); +static void smtp_tls_verified(struct smtp_session *); +static void smtp_io(struct io *, int, void *); +static void smtp_enter_state(struct smtp_session *, int); +static void smtp_reply(struct smtp_session *, char *, ...); +static void smtp_command(struct smtp_session *, char *); +static void smtp_rfc4954_auth_plain(struct smtp_session *, char *); +static void smtp_rfc4954_auth_login(struct smtp_session *, char *); +static void smtp_free(struct smtp_session *, const char *); +static const char *smtp_strstate(int); +static void smtp_cert_init(struct smtp_session *); +static void smtp_cert_init_cb(void *, int, const char *, const void *, size_t); +static void smtp_cert_verify(struct smtp_session *); +static void smtp_cert_verify_cb(void *, int); +static void smtp_auth_failure_pause(struct smtp_session *); +static void smtp_auth_failure_resume(int, short, void *); + +static int smtp_tx(struct smtp_session *); +static void smtp_tx_free(struct smtp_tx *); +static void smtp_tx_create_message(struct smtp_tx *); +static void smtp_tx_mail_from(struct smtp_tx *, const char *); +static void smtp_tx_rcpt_to(struct smtp_tx *, const char *); +static void smtp_tx_open_message(struct smtp_tx *); +static void smtp_tx_commit(struct smtp_tx *); +static void smtp_tx_rollback(struct smtp_tx *); +static int smtp_tx_dataline(struct smtp_tx *, const char *); +static int smtp_tx_filtered_dataline(struct smtp_tx *, const char *); +static void smtp_tx_eom(struct smtp_tx *); +static void smtp_filter_fd(struct smtp_tx *, int); +static int smtp_message_fd(struct smtp_tx *, int); +static void smtp_message_begin(struct smtp_tx *); +static void smtp_message_end(struct smtp_tx *); +static int smtp_filter_printf(struct smtp_tx *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +static int smtp_message_printf(struct smtp_tx *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); + +static int smtp_check_rset(struct smtp_session *, const char *); +static int smtp_check_helo(struct smtp_session *, const char *); +static int smtp_check_ehlo(struct smtp_session *, const char *); +static int smtp_check_auth(struct smtp_session *s, const char *); +static int smtp_check_starttls(struct smtp_session *, const char *); +static int smtp_check_mail_from(struct smtp_session *, const char *); +static int smtp_check_rcpt_to(struct smtp_session *, const char *); +static int smtp_check_data(struct smtp_session *, const char *); +static int smtp_check_noparam(struct smtp_session *, const char *); + +static void smtp_filter_phase(enum filter_phase, struct smtp_session *, const char *); + +static void smtp_proceed_connected(struct smtp_session *); +static void smtp_proceed_rset(struct smtp_session *, const char *); +static void smtp_proceed_helo(struct smtp_session *, const char *); +static void smtp_proceed_ehlo(struct smtp_session *, const char *); +static void smtp_proceed_auth(struct smtp_session *, const char *); +static void smtp_proceed_starttls(struct smtp_session *, const char *); +static void smtp_proceed_mail_from(struct smtp_session *, const char *); +static void smtp_proceed_rcpt_to(struct smtp_session *, const char *); +static void smtp_proceed_data(struct smtp_session *, const char *); +static void smtp_proceed_noop(struct smtp_session *, const char *); +static void smtp_proceed_help(struct smtp_session *, const char *); +static void smtp_proceed_wiz(struct smtp_session *, const char *); +static void smtp_proceed_quit(struct smtp_session *, const char *); +static void smtp_proceed_commit(struct smtp_session *, const char *); +static void smtp_proceed_rollback(struct smtp_session *, const char *); + +static void smtp_filter_begin(struct smtp_session *); +static void smtp_filter_end(struct smtp_session *); +static void smtp_filter_data_begin(struct smtp_session *); +static void smtp_filter_data_end(struct smtp_session *); + +static void smtp_report_link_connect(struct smtp_session *, const char *, int, + const struct sockaddr_storage *, + const struct sockaddr_storage *); +static void smtp_report_link_greeting(struct smtp_session *, const char *); +static void smtp_report_link_identify(struct smtp_session *, const char *, const char *); +static void smtp_report_link_tls(struct smtp_session *, const char *); +static void smtp_report_link_disconnect(struct smtp_session *); +static void smtp_report_link_auth(struct smtp_session *, const char *, const char *); +static void smtp_report_tx_reset(struct smtp_session *, uint32_t); +static void smtp_report_tx_begin(struct smtp_session *, uint32_t); +static void smtp_report_tx_mail(struct smtp_session *, uint32_t, const char *, int); +static void smtp_report_tx_rcpt(struct smtp_session *, uint32_t, const char *, int); +static void smtp_report_tx_envelope(struct smtp_session *, uint32_t, uint64_t); +static void smtp_report_tx_data(struct smtp_session *, uint32_t, int); +static void smtp_report_tx_commit(struct smtp_session *, uint32_t, size_t); +static void smtp_report_tx_rollback(struct smtp_session *, uint32_t); +static void smtp_report_protocol_client(struct smtp_session *, const char *); +static void smtp_report_protocol_server(struct smtp_session *, const char *); +static void smtp_report_filter_response(struct smtp_session *, int, int, const char *); +static void smtp_report_timeout(struct smtp_session *); + + +/* musl work-around */ +void portable_freeaddrinfo(struct addrinfo *); + + +static struct { + int code; + enum filter_phase filter_phase; + const char *cmd; + + int (*check)(struct smtp_session *, const char *); + void (*proceed)(struct smtp_session *, const char *); +} commands[] = { + { CMD_HELO, FILTER_HELO, "HELO", smtp_check_helo, smtp_proceed_helo }, + { CMD_EHLO, FILTER_EHLO, "EHLO", smtp_check_ehlo, smtp_proceed_ehlo }, + { CMD_STARTTLS, FILTER_STARTTLS, "STARTTLS", smtp_check_starttls, smtp_proceed_starttls }, + { CMD_AUTH, FILTER_AUTH, "AUTH", smtp_check_auth, smtp_proceed_auth }, + { CMD_MAIL_FROM, FILTER_MAIL_FROM, "MAIL FROM", smtp_check_mail_from, smtp_proceed_mail_from }, + { CMD_RCPT_TO, FILTER_RCPT_TO, "RCPT TO", smtp_check_rcpt_to, smtp_proceed_rcpt_to }, + { CMD_DATA, FILTER_DATA, "DATA", smtp_check_data, smtp_proceed_data }, + { CMD_RSET, FILTER_RSET, "RSET", smtp_check_rset, smtp_proceed_rset }, + { CMD_QUIT, FILTER_QUIT, "QUIT", smtp_check_noparam, smtp_proceed_quit }, + { CMD_NOOP, FILTER_NOOP, "NOOP", smtp_check_noparam, smtp_proceed_noop }, + { CMD_HELP, FILTER_HELP, "HELP", smtp_check_noparam, smtp_proceed_help }, + { CMD_WIZ, FILTER_WIZ, "WIZ", smtp_check_noparam, smtp_proceed_wiz }, + { CMD_COMMIT, FILTER_COMMIT, ".", smtp_check_noparam, smtp_proceed_commit }, + { -1, 0, NULL, NULL }, +}; + +static struct tree wait_lka_helo; +static struct tree wait_lka_mail; +static struct tree wait_lka_rcpt; +static struct tree wait_parent_auth; +static struct tree wait_queue_msg; +static struct tree wait_queue_fd; +static struct tree wait_queue_commit; +static struct tree wait_ssl_init; +static struct tree wait_ssl_verify; +static struct tree wait_filters; +static struct tree wait_filter_fd; + +static void +header_append_domain_buffer(char *buffer, char *domain, size_t len) +{ + size_t i; + int escape, quote, comment, bracket; + int has_domain, has_bracket, has_group; + int pos_bracket, pos_component, pos_insert; + char copy[APPEND_DOMAIN_BUFFER_SIZE]; + + escape = quote = comment = bracket = 0; + has_domain = has_bracket = has_group = 0; + pos_bracket = pos_insert = pos_component = 0; + for (i = 0; buffer[i]; ++i) { + if (buffer[i] == '(' && !escape && !quote) + comment++; + if (buffer[i] == '"' && !escape && !comment) + quote = !quote; + if (buffer[i] == ')' && !escape && !quote && comment) + comment--; + if (buffer[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + if (buffer[i] == '<' && !escape && !comment && !quote && !bracket) { + bracket++; + has_bracket = 1; + } + if (buffer[i] == '>' && !escape && !comment && !quote && bracket) { + bracket--; + pos_bracket = i; + } + if (buffer[i] == '@' && !escape && !comment && !quote) + has_domain = 1; + if (buffer[i] == ':' && !escape && !comment && !quote) + has_group = 1; + + /* update insert point if not in comment and not on a whitespace */ + if (!comment && buffer[i] != ')' && !isspace((unsigned char)buffer[i])) + pos_component = i; + } + + /* parse error, do not attempt to modify */ + if (escape || quote || comment || bracket) + return; + + /* domain already present, no need to modify */ + if (has_domain) + return; + + /* address is group, skip */ + if (has_group) + return; + + /* there's an address between brackets, just append domain */ + if (has_bracket) { + pos_bracket--; + while (isspace((unsigned char)buffer[pos_bracket])) + pos_bracket--; + if (buffer[pos_bracket] == '<') + return; + pos_insert = pos_bracket + 1; + } + else { + /* otherwise append address to last component */ + pos_insert = pos_component + 1; + + /* empty address */ + if (buffer[pos_component] == '\0' || + isspace((unsigned char)buffer[pos_component])) + return; + } + + if (snprintf(copy, sizeof copy, "%.*s@%s%s", + (int)pos_insert, buffer, + domain, + buffer+pos_insert) >= (int)sizeof copy) + return; + + memcpy(buffer, copy, len); +} + +static void +header_address_rewrite_buffer(char *buffer, const char *address, size_t len) +{ + size_t i; + int address_len; + int escape, quote, comment, bracket; + int has_bracket, has_group; + int pos_bracket_beg, pos_bracket_end, pos_component_beg, pos_component_end; + int insert_beg, insert_end; + char copy[APPEND_DOMAIN_BUFFER_SIZE]; + + escape = quote = comment = bracket = 0; + has_bracket = has_group = 0; + pos_bracket_beg = pos_bracket_end = pos_component_beg = pos_component_end = 0; + for (i = 0; buffer[i]; ++i) { + if (buffer[i] == '(' && !escape && !quote) + comment++; + if (buffer[i] == '"' && !escape && !comment) + quote = !quote; + if (buffer[i] == ')' && !escape && !quote && comment) + comment--; + if (buffer[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + if (buffer[i] == '<' && !escape && !comment && !quote && !bracket) { + bracket++; + has_bracket = 1; + pos_bracket_beg = i+1; + } + if (buffer[i] == '>' && !escape && !comment && !quote && bracket) { + bracket--; + pos_bracket_end = i; + } + if (buffer[i] == ':' && !escape && !comment && !quote) + has_group = 1; + + /* update insert point if not in comment and not on a whitespace */ + if (!comment && buffer[i] != ')' && !isspace((unsigned char)buffer[i])) + pos_component_end = i; + } + + /* parse error, do not attempt to modify */ + if (escape || quote || comment || bracket) + return; + + /* address is group, skip */ + if (has_group) + return; + + /* there's an address between brackets, just replace everything brackets */ + if (has_bracket) { + insert_beg = pos_bracket_beg; + insert_end = pos_bracket_end; + } + else { + if (pos_component_end == 0) + pos_component_beg = 0; + else { + for (pos_component_beg = pos_component_end; pos_component_beg >= 0; --pos_component_beg) + if (buffer[pos_component_beg] == ')' || isspace((unsigned char)buffer[pos_component_beg])) + break; + pos_component_beg += 1; + pos_component_end += 1; + } + insert_beg = pos_component_beg; + insert_end = pos_component_end; + } + + /* check that masquerade won' t overflow */ + address_len = strlen(address); + if (strlen(buffer) - (insert_end - insert_beg) + address_len >= len) + return; + + (void)strlcpy(copy, buffer, sizeof copy); + (void)strlcpy(copy+insert_beg, address, sizeof (copy) - insert_beg); + (void)strlcat(copy, buffer+insert_end, sizeof (copy)); + memcpy(buffer, copy, len); +} + +static void +header_domain_append_callback(struct smtp_tx *tx, const char *hdr, + const char *val) +{ + size_t i, j, linelen; + int escape, quote, comment, skip; + char buffer[APPEND_DOMAIN_BUFFER_SIZE]; + const char *line, *end; + + if (smtp_message_printf(tx, "%s:", hdr) == -1) + return; + + j = 0; + escape = quote = comment = skip = 0; + memset(buffer, 0, sizeof buffer); + + for (line = val; line; line = end) { + end = strchr(line, '\n'); + if (end) { + linelen = end - line; + end++; + } + else + linelen = strlen(line); + + for (i = 0; i < linelen; ++i) { + if (line[i] == '(' && !escape && !quote) + comment++; + if (line[i] == '"' && !escape && !comment) + quote = !quote; + if (line[i] == ')' && !escape && !quote && comment) + comment--; + if (line[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + + /* found a separator, buffer contains a full address */ + if (line[i] == ',' && !escape && !quote && !comment) { + if (!skip && j + strlen(tx->session->listener->hostname) + 1 < sizeof buffer) { + header_append_domain_buffer(buffer, tx->session->listener->hostname, sizeof buffer); + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0] && + tx->session->listener->flags & F_MASQUERADE && + !(strcasecmp(hdr, "From"))) + header_address_rewrite_buffer(buffer, mailaddr_to_text(&tx->evp.sender), + sizeof buffer); + } + if (smtp_message_printf(tx, "%s,", buffer) == -1) + return; + j = 0; + skip = 0; + memset(buffer, 0, sizeof buffer); + } + else { + if (skip) { + if (smtp_message_printf(tx, "%c", line[i]) == -1) + return; + } + else { + buffer[j++] = line[i]; + if (j == sizeof (buffer) - 1) { + if (smtp_message_printf(tx, "%s", buffer) == -1) + return; + skip = 1; + j = 0; + memset(buffer, 0, sizeof buffer); + } + } + } + } + if (skip) { + if (smtp_message_printf(tx, "\n") == -1) + return; + } + else { + buffer[j++] = '\n'; + if (j == sizeof (buffer) - 1) { + if (smtp_message_printf(tx, "%s", buffer) == -1) + return; + skip = 1; + j = 0; + memset(buffer, 0, sizeof buffer); + } + } + } + + /* end of header, if buffer is not empty we'll process it */ + if (buffer[0]) { + if (j + strlen(tx->session->listener->hostname) + 1 < sizeof buffer) { + header_append_domain_buffer(buffer, tx->session->listener->hostname, sizeof buffer); + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0] && + tx->session->listener->flags & F_MASQUERADE && + !(strcasecmp(hdr, "From"))) + header_address_rewrite_buffer(buffer, mailaddr_to_text(&tx->evp.sender), + sizeof buffer); + } + smtp_message_printf(tx, "%s", buffer); + } +} + +static void +smtp_session_init(void) +{ + static int init = 0; + + if (!init) { + tree_init(&wait_lka_helo); + tree_init(&wait_lka_mail); + tree_init(&wait_lka_rcpt); + tree_init(&wait_parent_auth); + tree_init(&wait_queue_msg); + tree_init(&wait_queue_fd); + tree_init(&wait_queue_commit); + tree_init(&wait_ssl_init); + tree_init(&wait_ssl_verify); + tree_init(&wait_filters); + tree_init(&wait_filter_fd); + init = 1; + } +} + +int +smtp_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, const char *hostname, struct io *io) +{ + struct smtp_session *s; + + smtp_session_init(); + + if ((s = calloc(1, sizeof(*s))) == NULL) + return (-1); + + s->id = generate_uid(); + s->listener = listener; + memmove(&s->ss, ss, sizeof(*ss)); + + if (io != NULL) + s->io = io; + else + s->io = io_new(); + + io_set_callback(s->io, smtp_io, s); + io_set_fd(s->io, sock); + io_set_timeout(s->io, SMTPD_SESSION_TIMEOUT * 1000); + io_set_write(s->io); + s->state = STATE_NEW; + + (void)strlcpy(s->smtpname, listener->hostname, sizeof(s->smtpname)); + + log_trace(TRACE_SMTP, "smtp: %p: connected to listener %p " + "[hostname=%s, port=%d, tag=%s]", s, listener, + listener->hostname, ntohs(listener->port), listener->tag); + + /* For local enqueueing, the hostname is already set */ + if (hostname) { + s->flags |= SF_AUTHENTICATED; + /* A bit of a hack */ + if (!strcmp(hostname, "localhost")) + s->flags |= SF_BOUNCE; + (void)strlcpy(s->rdns, hostname, sizeof(s->rdns)); + s->fcrdns = 1; + smtp_lookup_servername(s); + } else { + resolver_getnameinfo((struct sockaddr *)&s->ss, NI_NAMEREQD, + smtp_getnameinfo_cb, s); + } + + /* session may have been freed by now */ + + return (0); +} + +static void +smtp_getnameinfo_cb(void *arg, int gaierrno, const char *host, const char *serv) +{ + struct smtp_session *s = arg; + struct addrinfo hints; + + if (gaierrno) { + (void)strlcpy(s->rdns, "<unknown>", sizeof(s->rdns)); + + if (gaierrno == EAI_NODATA || gaierrno == EAI_NONAME) + s->fcrdns = 0; + else { + log_warnx("getnameinfo: %s: %s", ss_to_text(&s->ss), + gai_strerror(gaierrno)); + s->fcrdns = -1; + } + + smtp_lookup_servername(s); + return; + } + + (void)strlcpy(s->rdns, host, sizeof(s->rdns)); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = s->ss.ss_family; + hints.ai_socktype = SOCK_STREAM; + resolver_getaddrinfo(s->rdns, NULL, &hints, smtp_getaddrinfo_cb, s); +} + +static void +smtp_getaddrinfo_cb(void *arg, int gaierrno, struct addrinfo *ai0) +{ + struct smtp_session *s = arg; + struct addrinfo *ai; + char fwd[64], rev[64]; + + if (gaierrno) { + if (gaierrno == EAI_NODATA || gaierrno == EAI_NONAME) + s->fcrdns = 0; + else { + log_warnx("getaddrinfo: %s: %s", s->rdns, + gai_strerror(gaierrno)); + s->fcrdns = -1; + } + } + else { + strlcpy(rev, ss_to_text(&s->ss), sizeof(rev)); + for (ai = ai0; ai; ai = ai->ai_next) { + strlcpy(fwd, sa_to_text(ai->ai_addr), sizeof(fwd)); + if (!strcmp(fwd, rev)) { + s->fcrdns = 1; + break; + } + } + portable_freeaddrinfo(ai0); + } + + smtp_lookup_servername(s); +} + +void +smtp_session_imsg(struct mproc *p, struct imsg *imsg) +{ + struct smtp_session *s; + struct smtp_rcpt *rcpt; + char user[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTP_LINE_MAX]; + struct msg m; + const char *line, *helo; + uint64_t reqid, evpid; + uint32_t msgid; + int status, success; + int filter_response; + const char *filter_param; + uint8_t i; + + switch (imsg->hdr.type) { + + case IMSG_SMTP_CHECK_SENDER: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + m_end(&m); + s = tree_xpop(&wait_lka_mail, reqid); + switch (status) { + case LKA_OK: + smtp_tx_create_message(s->tx); + break; + + case LKA_PERMFAIL: + smtp_tx_free(s->tx); + smtp_reply(s, "%d %s", 530, "Sender rejected"); + break; + case LKA_TEMPFAIL: + smtp_tx_free(s->tx); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + break; + } + return; + + case IMSG_SMTP_EXPAND_RCPT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + m_get_string(&m, &line); + m_end(&m); + s = tree_xpop(&wait_lka_rcpt, reqid); + + tmp[0] = '\0'; + if (s->tx->evp.rcpt.user[0]) { + (void)strlcpy(tmp, s->tx->evp.rcpt.user, sizeof tmp); + if (s->tx->evp.rcpt.domain[0]) { + (void)strlcat(tmp, "@", sizeof tmp); + (void)strlcat(tmp, s->tx->evp.rcpt.domain, + sizeof tmp); + } + } + + switch (status) { + case LKA_OK: + fatalx("unexpected ok"); + case LKA_PERMFAIL: + smtp_reply(s, "%s: <%s>", line, tmp); + break; + case LKA_TEMPFAIL: + smtp_reply(s, "%s: <%s>", line, tmp); + break; + } + return; + + case IMSG_SMTP_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + s = tree_xpop(&wait_lka_helo, reqid); + m_get_int(&m, &status); + if (status == LKA_OK) { + m_get_string(&m, &helo); + (void)strlcpy(s->smtpname, helo, sizeof(s->smtpname)); + } + m_end(&m); + smtp_connected(s); + return; + + case IMSG_SMTP_MESSAGE_CREATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + s = tree_xpop(&wait_queue_msg, reqid); + if (success) { + m_get_msgid(&m, &msgid); + s->tx->msgid = msgid; + s->tx->evp.id = msgid_to_evpid(msgid); + s->tx->rcptcount = 0; + smtp_reply(s, "250 %s Ok", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + } else { + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_tx_free(s->tx); + smtp_enter_state(s, STATE_QUIT); + } + m_end(&m); + return; + + case IMSG_SMTP_MESSAGE_OPEN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_queue_fd, reqid); + if (!success || imsg->fd == -1) { + if (imsg->fd != -1) + close(imsg->fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + log_debug("smtp: %p: fd %d from queue", s, imsg->fd); + + if (smtp_message_fd(s->tx, imsg->fd)) { + if (!SESSION_DATA_FILTERED(s)) + smtp_message_begin(s->tx); + else + smtp_filter_data_begin(s); + } + return; + + case IMSG_FILTER_SMTP_DATA_BEGIN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_filter_fd, reqid); + if (!success || imsg->fd == -1) { + if (imsg->fd != -1) + close(imsg->fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + log_debug("smtp: %p: fd %d from lka", s, imsg->fd); + + smtp_filter_fd(s->tx, imsg->fd); + smtp_message_begin(s->tx); + return; + + case IMSG_QUEUE_ENVELOPE_SUBMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + s = tree_xget(&wait_lka_rcpt, reqid); + if (success) { + m_get_evpid(&m, &evpid); + s->tx->evp.id = evpid; + s->tx->destcount++; + smtp_report_tx_envelope(s, s->tx->msgid, evpid); + } + else + s->tx->error = TX_ERROR_ENVELOPE; + m_end(&m); + return; + + case IMSG_QUEUE_ENVELOPE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + if (!success) + fatalx("commit evp failed: not supposed to happen"); + s = tree_xpop(&wait_lka_rcpt, reqid); + if (s->tx->error) { + /* + * If an envelope failed, we can't cancel the last + * RCPT only so we must cancel the whole transaction + * and close the connection. + */ + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + } + else { + rcpt = xcalloc(1, sizeof(*rcpt)); + rcpt->evpid = s->tx->evp.id; + rcpt->destcount = s->tx->destcount; + rcpt->maddr = s->tx->evp.rcpt; + TAILQ_INSERT_TAIL(&s->tx->rcpts, rcpt, entry); + + s->tx->destcount = 0; + s->tx->rcptcount++; + smtp_reply(s, "250 %s %s: Recipient ok", + esc_code(ESC_STATUS_OK, ESC_DESTINATION_ADDRESS_VALID), + esc_description(ESC_DESTINATION_ADDRESS_VALID)); + } + return; + + case IMSG_SMTP_MESSAGE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + s = tree_xpop(&wait_queue_commit, reqid); + if (!success) { + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_tx_free(s->tx); + smtp_enter_state(s, STATE_QUIT); + return; + } + + smtp_reply(s, "250 %s %08x Message accepted for delivery", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS), + s->tx->msgid); + smtp_report_tx_commit(s, s->tx->msgid, s->tx->odatalen); + smtp_report_tx_reset(s, s->tx->msgid); + + log_info("%016"PRIx64" smtp message " + "msgid=%08x size=%zu nrcpt=%zu proto=%s", + s->id, + s->tx->msgid, + s->tx->odatalen, + s->tx->rcptcount, + s->flags & SF_EHLO ? "ESMTP" : "SMTP"); + TAILQ_FOREACH(rcpt, &s->tx->rcpts, entry) { + log_info("%016"PRIx64" smtp envelope " + "evpid=%016"PRIx64" from=<%s%s%s> to=<%s%s%s>", + s->id, + rcpt->evpid, + s->tx->evp.sender.user, + s->tx->evp.sender.user[0] == '\0' ? "" : "@", + s->tx->evp.sender.domain, + rcpt->maddr.user, + rcpt->maddr.user[0] == '\0' ? "" : "@", + rcpt->maddr.domain); + } + smtp_tx_free(s->tx); + s->mailcount++; + smtp_enter_state(s, STATE_HELO); + return; + + case IMSG_SMTP_AUTHENTICATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_parent_auth, reqid); + strnvis(user, s->username, sizeof user, VIS_WHITE | VIS_SAFE); + if (success == LKA_OK) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=ok", + s->id, user); + s->flags |= SF_AUTHENTICATED; + smtp_report_link_auth(s, user, "pass"); + smtp_reply(s, "235 %s Authentication succeeded", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + } + else if (success == LKA_PERMFAIL) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=permfail", + s->id, user); + smtp_report_link_auth(s, user, "fail"); + smtp_auth_failure_pause(s); + return; + } + else if (success == LKA_TEMPFAIL) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=tempfail", + s->id, user); + smtp_report_link_auth(s, user, "error"); + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + } + else + fatalx("bad lka response"); + + smtp_enter_state(s, STATE_HELO); + return; + + case IMSG_FILTER_SMTP_PROTOCOL: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &filter_response); + if (filter_response != FILTER_PROCEED && + filter_response != FILTER_JUNK) + m_get_string(&m, &filter_param); + else + filter_param = NULL; + m_end(&m); + + s = tree_xpop(&wait_filters, reqid); + + switch (filter_response) { + case FILTER_REJECT: + case FILTER_DISCONNECT: + if (!valid_smtp_response(filter_param) || + (filter_param[0] != '4' && filter_param[0] != '5')) + filter_param = "421 Internal server error"; + if (!strncmp(filter_param, "421", 3)) + filter_response = FILTER_DISCONNECT; + + smtp_report_filter_response(s, s->filter_phase, + filter_response, filter_param); + + smtp_reply(s, "%s", filter_param); + + if (filter_response == FILTER_DISCONNECT) + smtp_enter_state(s, STATE_QUIT); + else if (s->filter_phase == FILTER_COMMIT) + smtp_proceed_rollback(s, NULL); + break; + + + case FILTER_JUNK: + if (s->tx) + s->tx->junk = 1; + else + s->junk = 1; + /* fallthrough */ + + case FILTER_PROCEED: + filter_param = s->filter_param; + /* fallthrough */ + + case FILTER_REWRITE: + smtp_report_filter_response(s, s->filter_phase, + filter_response, + filter_param == s->filter_param ? NULL : filter_param); + if (s->filter_phase == FILTER_CONNECT) { + smtp_proceed_connected(s); + return; + } + for (i = 0; i < nitems(commands); ++i) + if (commands[i].filter_phase == s->filter_phase) { + if (filter_response == FILTER_REWRITE) + if (!commands[i].check(s, filter_param)) + break; + commands[i].proceed(s, filter_param); + break; + } + break; + } + return; + } + + log_warnx("smtp_session_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); + fatalx(NULL); +} + +static void +smtp_tls_verified(struct smtp_session *s) +{ + X509 *x; + + x = SSL_get_peer_certificate(io_tls(s->io)); + if (x) { + log_info("%016"PRIx64" smtp " + "client-cert-check result=\"%s\"", + s->id, + (s->flags & SF_VERIFIED) ? "success" : "failure"); + X509_free(x); + } + + if (s->listener->flags & F_SMTPS) { + stat_increment("smtp.smtps", 1); + io_set_write(s->io); + smtp_send_banner(s); + } + else { + stat_increment("smtp.tls", 1); + smtp_enter_state(s, STATE_HELO); + } +} + +static void +smtp_io(struct io *io, int evt, void *arg) +{ + struct smtp_session *s = arg; + char *line; + size_t len; + int eom; + + log_trace(TRACE_IO, "smtp: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + + case IO_TLSREADY: + log_info("%016"PRIx64" smtp tls ciphers=%s", + s->id, ssl_to_text(io_tls(s->io))); + + smtp_report_link_tls(s, ssl_to_text(io_tls(s->io))); + + s->flags |= SF_SECURE; + s->helo[0] = '\0'; + + smtp_cert_verify(s); + break; + + case IO_DATAIN: + nextline: + line = io_getline(s->io, &len); + if ((line == NULL && io_datalen(s->io) >= SMTP_LINE_MAX) || + (line && len >= SMTP_LINE_MAX)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s Line too long", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + + /* No complete line received */ + if (line == NULL) + return; + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + /* Message body */ + eom = 0; + if (s->state == STATE_BODY) { + if (strcmp(line, ".")) { + s->tx->datain += strlen(line) + 1; + if (s->tx->datain > env->sc_maxsize) + s->tx->error = TX_ERROR_SIZE; + } + eom = (s->tx->filter == NULL) ? + smtp_tx_dataline(s->tx, line) : + smtp_tx_filtered_dataline(s->tx, line); + if (eom == 0) + goto nextline; + } + + /* Pipelining not supported */ + if (io_datalen(s->io)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s %s: Pipelining not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + + if (eom) { + io_set_write(io); + if (s->tx->filter == NULL) + smtp_tx_eom(s->tx); + return; + } + + /* Must be a command */ + if (strlcpy(s->cmd, line, sizeof(s->cmd)) >= sizeof(s->cmd)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s Command line too long", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + io_set_write(io); + smtp_command(s, line); + break; + + case IO_LOWAT: + if (s->state == STATE_QUIT) { + log_info("%016"PRIx64" smtp disconnected " + "reason=quit", + s->id); + smtp_free(s, "done"); + break; + } + + /* Wait for the client to start tls */ + if (s->state == STATE_TLS) { + smtp_cert_init(s); + break; + } + + io_set_read(io); + break; + + case IO_TIMEOUT: + log_info("%016"PRIx64" smtp disconnected " + "reason=timeout", + s->id); + smtp_report_timeout(s); + smtp_free(s, "timeout"); + break; + + case IO_DISCONNECTED: + log_info("%016"PRIx64" smtp disconnected " + "reason=disconnect", + s->id); + smtp_free(s, "disconnected"); + break; + + case IO_ERROR: + log_info("%016"PRIx64" smtp disconnected " + "reason=\"io-error: %s\"", + s->id, io_error(io)); + smtp_free(s, "IO error"); + break; + + default: + fatalx("smtp_io()"); + } +} + +static void +smtp_command(struct smtp_session *s, char *line) +{ + char *args; + int cmd, i; + + log_trace(TRACE_SMTP, "smtp: %p: <<< %s", s, line); + + /* + * These states are special. + */ + if (s->state == STATE_AUTH_INIT) { + smtp_report_protocol_client(s, "********"); + smtp_rfc4954_auth_plain(s, line); + return; + } + if (s->state == STATE_AUTH_USERNAME || s->state == STATE_AUTH_PASSWORD) { + smtp_report_protocol_client(s, "********"); + smtp_rfc4954_auth_login(s, line); + return; + } + + if (s->state == STATE_HELO && strncasecmp(line, "AUTH PLAIN ", 11) == 0) + smtp_report_protocol_client(s, "AUTH PLAIN ********"); + else + smtp_report_protocol_client(s, line); + + + /* + * Unlike other commands, "mail from" and "rcpt to" contain a + * space in the command name. + */ + if (strncasecmp("mail from:", line, 10) == 0 || + strncasecmp("rcpt to:", line, 8) == 0) + args = strchr(line, ':'); + else + args = strchr(line, ' '); + + if (args) { + *args++ = '\0'; + while (isspace((unsigned char)*args)) + args++; + } + + cmd = -1; + for (i = 0; commands[i].code != -1; i++) + if (!strcasecmp(line, commands[i].cmd)) { + cmd = commands[i].code; + break; + } + + s->last_cmd = cmd; + switch (cmd) { + /* + * INIT + */ + case CMD_HELO: + if (!smtp_check_helo(s, args)) + break; + smtp_filter_phase(FILTER_HELO, s, args); + break; + + case CMD_EHLO: + if (!smtp_check_ehlo(s, args)) + break; + smtp_filter_phase(FILTER_EHLO, s, args); + break; + + /* + * SETUP + */ + case CMD_STARTTLS: + if (!smtp_check_starttls(s, args)) + break; + + smtp_filter_phase(FILTER_STARTTLS, s, NULL); + break; + + case CMD_AUTH: + if (!smtp_check_auth(s, args)) + break; + smtp_filter_phase(FILTER_AUTH, s, args); + break; + + case CMD_MAIL_FROM: + if (!smtp_check_mail_from(s, args)) + break; + smtp_filter_phase(FILTER_MAIL_FROM, s, args); + break; + + /* + * TRANSACTION + */ + case CMD_RCPT_TO: + if (!smtp_check_rcpt_to(s, args)) + break; + smtp_filter_phase(FILTER_RCPT_TO, s, args); + break; + + case CMD_RSET: + if (!smtp_check_rset(s, args)) + break; + smtp_filter_phase(FILTER_RSET, s, NULL); + break; + + case CMD_DATA: + if (!smtp_check_data(s, args)) + break; + smtp_filter_phase(FILTER_DATA, s, NULL); + break; + + /* + * ANY + */ + case CMD_QUIT: + if (!smtp_check_noparam(s, args)) + break; + smtp_filter_phase(FILTER_QUIT, s, NULL); + break; + + case CMD_NOOP: + if (!smtp_check_noparam(s, args)) + break; + smtp_filter_phase(FILTER_NOOP, s, NULL); + break; + + case CMD_HELP: + if (!smtp_check_noparam(s, args)) + break; + smtp_proceed_help(s, NULL); + break; + + case CMD_WIZ: + if (!smtp_check_noparam(s, args)) + break; + smtp_proceed_wiz(s, NULL); + break; + + default: + smtp_reply(s, "500 %s %s: Command unrecognized", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + break; + } +} + +static int +smtp_check_rset(struct smtp_session *s, const char *args) +{ + if (!smtp_check_noparam(s, args)) + return 0; + + if (s->helo[0] == '\0') { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + return 1; +} + +static int +smtp_check_helo(struct smtp_session *s, const char *args) +{ + if (!s->banner_sent) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->helo[0]) { + smtp_reply(s, "503 %s %s: Already identified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: HELO requires domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!valid_domainpart(args)) { + smtp_reply(s, "501 %s %s: Invalid domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_ehlo(struct smtp_session *s, const char *args) +{ + if (!s->banner_sent) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->helo[0]) { + smtp_reply(s, "503 %s %s: Already identified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: EHLO requires domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!valid_domainpart(args)) { + smtp_reply(s, "501 %s %s: Invalid domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_auth(struct smtp_session *s, const char *args) +{ + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->flags & SF_AUTHENTICATED) { + smtp_reply(s, "503 %s %s: Already authenticated", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!ADVERTISE_AUTH(s)) { + smtp_reply(s, "503 %s %s: Command not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: No parameters given", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_starttls(struct smtp_session *s, const char *args) +{ + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!(s->listener->flags & F_STARTTLS)) { + smtp_reply(s, "503 %s %s: Command not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->flags & SF_SECURE) { + smtp_reply(s, "503 %s %s: Channel already secured", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args != NULL) { + smtp_reply(s, "501 %s %s: No parameters allowed", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_mail_from(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + struct mailaddr sender; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->listener->flags & F_STARTTLS_REQUIRE && + !(s->flags & SF_SECURE)) { + smtp_reply(s, + "530 %s %s: Must issue a STARTTLS command first", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->listener->flags & F_AUTH_REQUIRE && + !(s->flags & SF_AUTHENTICATED)) { + smtp_reply(s, + "530 %s %s: Must issue an AUTH command first", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->mailcount >= env->sc_session_max_mails) { + /* we can pretend we had too many recipients */ + smtp_reply(s, "452 %s %s: Too many messages sent", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return 0; + } + + if (smtp_mailaddr(&sender, copy, 1, ©, + s->smtpname) == 0) { + smtp_reply(s, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + return 0; + } + + return 1; +} + +static int +smtp_check_rcpt_to(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (s->tx == NULL) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->tx->rcptcount >= env->sc_session_max_rcpt) { + smtp_reply(s->tx->session, "451 %s %s: Too many recipients", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return 0; + } + + if (smtp_mailaddr(&s->tx->evp.rcpt, copy, 0, ©, + s->tx->session->smtpname) == 0) { + smtp_reply(s->tx->session, + "501 %s Recipient address syntax error", + esc_code(ESC_STATUS_PERMFAIL, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX)); + return 0; + } + + return 1; +} + +static int +smtp_check_data(struct smtp_session *s, const char *args) +{ + if (!smtp_check_noparam(s, args)) + return 0; + + if (s->tx == NULL) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->tx->rcptcount == 0) { + smtp_reply(s, "503 %s %s: No recipient specified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_noparam(struct smtp_session *s, const char *args) +{ + if (args != NULL) { + smtp_reply(s, "500 %s %s: command does not accept arguments.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + return 1; +} + +static void +smtp_query_filters(enum filter_phase phase, struct smtp_session *s, const char *args) +{ + m_create(p_lka, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_int(p_lka, phase); + m_add_string(p_lka, args); + m_close(p_lka); + tree_xset(&wait_filters, s->id, s); +} + +static void +smtp_filter_begin(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->filter_name); + m_close(p_lka); +} + +static void +smtp_filter_end(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_END, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); +} + +static void +smtp_filter_data_begin(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); + tree_xset(&wait_filter_fd, s->id, s); +} + +static void +smtp_filter_data_end(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + if (s->tx->filter == NULL) + return; + + io_free(s->tx->filter); + s->tx->filter = NULL; + + m_create(p_lka, IMSG_FILTER_SMTP_DATA_END, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); +} + +static void +smtp_filter_phase(enum filter_phase phase, struct smtp_session *s, const char *param) +{ + uint8_t i; + + s->filter_phase = phase; + s->filter_param = param; + + if (SESSION_FILTERED(s)) { + smtp_query_filters(phase, s, param ? param : ""); + return; + } + + if (s->filter_phase == FILTER_CONNECT) { + smtp_proceed_connected(s); + return; + } + + for (i = 0; i < nitems(commands); ++i) + if (commands[i].filter_phase == s->filter_phase) { + commands[i].proceed(s, param); + break; + } +} + +static void +smtp_proceed_rset(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "250 %s Reset state", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + + if (s->tx) { + if (s->tx->msgid) + smtp_tx_rollback(s->tx); + smtp_tx_free(s->tx); + } +} + +static void +smtp_proceed_helo(struct smtp_session *s, const char *args) +{ + (void)strlcpy(s->helo, args, sizeof(s->helo)); + s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; + + smtp_report_link_identify(s, "HELO", s->helo); + + smtp_enter_state(s, STATE_HELO); + + smtp_reply(s, "250 %s Hello %s %s%s%s, pleased to meet you", + s->smtpname, + s->helo, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); +} + +static void +smtp_proceed_ehlo(struct smtp_session *s, const char *args) +{ + (void)strlcpy(s->helo, args, sizeof(s->helo)); + s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; + s->flags |= SF_EHLO; + s->flags |= SF_8BITMIME; + + smtp_report_link_identify(s, "EHLO", s->helo); + + smtp_enter_state(s, STATE_HELO); + smtp_reply(s, "250-%s Hello %s %s%s%s, pleased to meet you", + s->smtpname, + s->helo, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); + + smtp_reply(s, "250-8BITMIME"); + smtp_reply(s, "250-ENHANCEDSTATUSCODES"); + smtp_reply(s, "250-SIZE %zu", env->sc_maxsize); + if (ADVERTISE_EXT_DSN(s)) + smtp_reply(s, "250-DSN"); + if (ADVERTISE_TLS(s)) + smtp_reply(s, "250-STARTTLS"); + if (ADVERTISE_AUTH(s)) + smtp_reply(s, "250-AUTH PLAIN LOGIN"); + smtp_reply(s, "250 HELP"); +} + +static void +smtp_proceed_auth(struct smtp_session *s, const char *args) +{ + char tmp[SMTP_LINE_MAX]; + char *eom, *method; + + (void)strlcpy(tmp, args, sizeof tmp); + + method = tmp; + eom = strchr(tmp, ' '); + if (eom == NULL) + eom = strchr(tmp, '\t'); + if (eom != NULL) + *eom++ = '\0'; + if (strcasecmp(method, "PLAIN") == 0) + smtp_rfc4954_auth_plain(s, eom); + else if (strcasecmp(method, "LOGIN") == 0) + smtp_rfc4954_auth_login(s, eom); + else + smtp_reply(s, "504 %s %s: AUTH method \"%s\" not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_SECURITY_FEATURES_NOT_SUPPORTED), + esc_description(ESC_SECURITY_FEATURES_NOT_SUPPORTED), + method); +} + +static void +smtp_proceed_starttls(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "220 %s Ready to start TLS", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_TLS); +} + +static void +smtp_proceed_mail_from(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (!smtp_tx(s)) { + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + if (smtp_mailaddr(&s->tx->evp.sender, copy, 1, ©, + s->smtpname) == 0) { + smtp_reply(s, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + smtp_tx_free(s->tx); + return; + } + + smtp_tx_mail_from(s->tx, args); +} + +static void +smtp_proceed_rcpt_to(struct smtp_session *s, const char *args) +{ + smtp_tx_rcpt_to(s->tx, args); +} + +static void +smtp_proceed_data(struct smtp_session *s, const char *args) +{ + smtp_tx_open_message(s->tx); +} + +static void +smtp_proceed_quit(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "221 %s Bye", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); +} + +static void +smtp_proceed_noop(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "250 %s Ok", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); +} + +static void +smtp_proceed_help(struct smtp_session *s, const char *args) +{ + const char *code = esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS); + + smtp_reply(s, "214-%s This is " SMTPD_NAME, code); + smtp_reply(s, "214-%s To report bugs in the implementation, " + "please contact bugs@openbsd.org", code); + smtp_reply(s, "214-%s with full details", code); + smtp_reply(s, "214 %s End of HELP info", code); +} + +static void +smtp_proceed_wiz(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "500 %s %s: this feature is not supported yet ;-)", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); +} + +static void +smtp_proceed_commit(struct smtp_session *s, const char *args) +{ + smtp_message_end(s->tx); +} + +static void +smtp_proceed_rollback(struct smtp_session *s, const char *args) +{ + struct smtp_tx *tx; + + tx = s->tx; + + fclose(tx->ofile); + tx->ofile = NULL; + + smtp_tx_rollback(tx); + smtp_tx_free(tx); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_rfc4954_auth_plain(struct smtp_session *s, char *arg) +{ + char buf[1024], *user, *pass; + int len; + + switch (s->state) { + case STATE_HELO: + if (arg == NULL) { + smtp_enter_state(s, STATE_AUTH_INIT); + smtp_reply(s, "334 "); + return; + } + smtp_enter_state(s, STATE_AUTH_INIT); + /* FALLTHROUGH */ + + case STATE_AUTH_INIT: + /* String is not NUL terminated, leave room. */ + if ((len = base64_decode(arg, (unsigned char *)buf, + sizeof(buf) - 1)) == -1) + goto abort; + /* buf is a byte string, NUL terminate. */ + buf[len] = '\0'; + + /* + * Skip "foo" in "foo\0user\0pass", if present. + */ + user = memchr(buf, '\0', len); + if (user == NULL || user >= buf + len - 2) + goto abort; + user++; /* skip NUL */ + if (strlcpy(s->username, user, sizeof(s->username)) + >= sizeof(s->username)) + goto abort; + + pass = memchr(user, '\0', len - (user - buf)); + if (pass == NULL || pass >= buf + len - 2) + goto abort; + pass++; /* skip NUL */ + + m_create(p_lka, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->authtable); + m_add_string(p_lka, user); + m_add_string(p_lka, pass); + m_close(p_lka); + tree_xset(&wait_parent_auth, s->id, s); + return; + + default: + fatal("smtp_rfc4954_auth_plain: unknown state"); + } + +abort: + smtp_reply(s, "501 %s %s: Syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_SYNTAX_ERROR), + esc_description(ESC_SYNTAX_ERROR)); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_rfc4954_auth_login(struct smtp_session *s, char *arg) +{ + char buf[LINE_MAX]; + + switch (s->state) { + case STATE_HELO: + smtp_enter_state(s, STATE_AUTH_USERNAME); + if (arg != NULL && *arg != '\0') { + smtp_rfc4954_auth_login(s, arg); + return; + } + smtp_reply(s, "334 VXNlcm5hbWU6"); + return; + + case STATE_AUTH_USERNAME: + memset(s->username, 0, sizeof(s->username)); + if (base64_decode(arg, (unsigned char *)s->username, + sizeof(s->username) - 1) == -1) + goto abort; + + smtp_enter_state(s, STATE_AUTH_PASSWORD); + smtp_reply(s, "334 UGFzc3dvcmQ6"); + return; + + case STATE_AUTH_PASSWORD: + memset(buf, 0, sizeof(buf)); + if (base64_decode(arg, (unsigned char *)buf, + sizeof(buf)-1) == -1) + goto abort; + + m_create(p_lka, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->authtable); + m_add_string(p_lka, s->username); + m_add_string(p_lka, buf); + m_close(p_lka); + tree_xset(&wait_parent_auth, s->id, s); + return; + + default: + fatal("smtp_rfc4954_auth_login: unknown state"); + } + +abort: + smtp_reply(s, "501 %s %s: Syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_SYNTAX_ERROR), + esc_description(ESC_SYNTAX_ERROR)); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_lookup_servername(struct smtp_session *s) +{ + if (s->listener->hostnametable[0]) { + m_create(p_lka, IMSG_SMTP_LOOKUP_HELO, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->hostnametable); + m_add_sockaddr(p_lka, (struct sockaddr*)&s->listener->ss); + m_close(p_lka); + tree_xset(&wait_lka_helo, s->id, s); + return; + } + + smtp_connected(s); +} + +static void +smtp_connected(struct smtp_session *s) +{ + smtp_enter_state(s, STATE_CONNECTED); + + log_info("%016"PRIx64" smtp connected address=%s host=%s", + s->id, ss_to_text(&s->ss), s->rdns); + + smtp_filter_begin(s); + + smtp_report_link_connect(s, s->rdns, s->fcrdns, &s->ss, + &s->listener->ss); + + smtp_filter_phase(FILTER_CONNECT, s, ss_to_text(&s->ss)); +} + +static void +smtp_proceed_connected(struct smtp_session *s) +{ + if (s->listener->flags & F_SMTPS) + smtp_cert_init(s); + else + smtp_send_banner(s); +} + +static void +smtp_send_banner(struct smtp_session *s) +{ + smtp_reply(s, "220 %s ESMTP %s", s->smtpname, SMTPD_NAME); + s->banner_sent = 1; + smtp_report_link_greeting(s, s->smtpname); +} + +void +smtp_enter_state(struct smtp_session *s, int newstate) +{ + log_trace(TRACE_SMTP, "smtp: %p: %s -> %s", s, + smtp_strstate(s->state), + smtp_strstate(newstate)); + + s->state = newstate; +} + +static void +smtp_reply(struct smtp_session *s, char *fmt, ...) +{ + va_list ap; + int n; + char buf[LINE_MAX*2], tmp[LINE_MAX*2]; + + va_start(ap, fmt); + n = vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + if (n < 0) + fatalx("smtp_reply: response format error"); + if (n < 4) + fatalx("smtp_reply: response too short"); + if (n >= (int)sizeof buf) { + /* only first three bytes are used by SMTP logic, + * so if _our_ reply does not fit entirely in the + * buffer, it's ok to truncate. + */ + } + + log_trace(TRACE_SMTP, "smtp: %p: >>> %s", s, buf); + smtp_report_protocol_server(s, buf); + + switch (buf[0]) { + case '2': + if (s->tx) { + if (s->last_cmd == CMD_MAIL_FROM) { + smtp_report_tx_begin(s, s->tx->msgid); + smtp_report_tx_mail(s, s->tx->msgid, s->cmd + 10, 1); + } + else if (s->last_cmd == CMD_RCPT_TO) + smtp_report_tx_rcpt(s, s->tx->msgid, s->cmd + 8, 1); + } + break; + case '3': + if (s->tx) { + if (s->last_cmd == CMD_DATA) + smtp_report_tx_data(s, s->tx->msgid, 1); + } + break; + case '5': + case '4': + /* do not report smtp_tx_mail/smtp_tx_rcpt errors + * if they happened outside of a transaction. + */ + if (s->tx) { + if (s->last_cmd == CMD_MAIL_FROM) + smtp_report_tx_mail(s, s->tx->msgid, + s->cmd + 10, buf[0] == '4' ? -1 : 0); + else if (s->last_cmd == CMD_RCPT_TO) + smtp_report_tx_rcpt(s, + s->tx->msgid, s->cmd + 8, buf[0] == '4' ? -1 : 0); + else if (s->last_cmd == CMD_DATA && s->tx->rcptcount) + smtp_report_tx_data(s, s->tx->msgid, + buf[0] == '4' ? -1 : 0); + } + + if (s->flags & SF_BADINPUT) { + log_info("%016"PRIx64" smtp " + "bad-input result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_INIT) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH PLAIN (...)\" result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_USERNAME) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH LOGIN (username)\" result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_PASSWORD) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH LOGIN (password)\" result=\"%.*s\"", + s->id, n, buf); + } + else { + strnvis(tmp, s->cmd, sizeof tmp, VIS_SAFE | VIS_CSTYLE); + log_info("%016"PRIx64" smtp " + "failed-command command=\"%s\" " + "result=\"%.*s\"", + s->id, tmp, n, buf); + } + break; + } + + io_xprintf(s->io, "%s\r\n", buf); +} + +static void +smtp_free(struct smtp_session *s, const char * reason) +{ + if (s->tx) { + if (s->tx->msgid) + smtp_tx_rollback(s->tx); + smtp_tx_free(s->tx); + } + + smtp_report_link_disconnect(s); + smtp_filter_end(s); + + if (s->flags & SF_SECURE && s->listener->flags & F_SMTPS) + stat_decrement("smtp.smtps", 1); + if (s->flags & SF_SECURE && s->listener->flags & F_STARTTLS) + stat_decrement("smtp.tls", 1); + + io_free(s->io); + free(s); + + smtp_collect(); +} + +static int +smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, + const char *domain) +{ + char *p, *e; + + if (line == NULL) + return (0); + + if (*line != '<') + return (0); + + e = strchr(line, '>'); + if (e == NULL) + return (0); + *e++ = '\0'; + while (*e == ' ') + e++; + *args = e; + + if (!text_to_mailaddr(maddr, line + 1)) + return (0); + + p = strchr(maddr->user, ':'); + if (p != NULL) { + p++; + memmove(maddr->user, p, strlen(p) + 1); + } + + /* accept empty return-path in MAIL FROM, required for bounces */ + if (mailfrom && maddr->user[0] == '\0' && maddr->domain[0] == '\0') + return (1); + + /* no or invalid user-part, reject */ + if (maddr->user[0] == '\0' || !valid_localpart(maddr->user)) + return (0); + + /* no domain part, local user */ + if (maddr->domain[0] == '\0') { + (void)strlcpy(maddr->domain, domain, + sizeof(maddr->domain)); + } + + if (!valid_domainpart(maddr->domain)) + return (0); + + return (1); +} + +static void +smtp_cert_init(struct smtp_session *s) +{ + const char *name; + int fallback; + + if (s->listener->pki_name[0]) { + name = s->listener->pki_name; + fallback = 0; + } + else { + name = s->smtpname; + fallback = 1; + } + + if (cert_init(name, fallback, smtp_cert_init_cb, s)) + tree_xset(&wait_ssl_init, s->id, s); +} + +static void +smtp_cert_init_cb(void *arg, int status, const char *name, const void *cert, + size_t cert_len) +{ + struct smtp_session *s = arg; + void *ssl, *ssl_ctx; + + tree_pop(&wait_ssl_init, s->id); + + if (status == CA_FAIL) { + log_info("%016"PRIx64" smtp disconnected " + "reason=ca-failure", + s->id); + smtp_free(s, "CA failure"); + return; + } + + ssl_ctx = dict_get(env->sc_ssl_dict, name); + ssl = ssl_smtp_init(ssl_ctx, s->listener->flags & F_TLS_VERIFY); + io_set_read(s->io); + io_start_tls(s->io, ssl); +} + +static void +smtp_cert_verify(struct smtp_session *s) +{ + const char *name; + int fallback; + + if (s->listener->ca_name[0]) { + name = s->listener->ca_name; + fallback = 0; + } + else { + name = s->smtpname; + fallback = 1; + } + + if (cert_verify(io_tls(s->io), name, fallback, smtp_cert_verify_cb, s)) { + tree_xset(&wait_ssl_verify, s->id, s); + io_pause(s->io, IO_IN); + } +} + +static void +smtp_cert_verify_cb(void *arg, int status) +{ + struct smtp_session *s = arg; + const char *reason = NULL; + int resume; + + resume = tree_pop(&wait_ssl_verify, s->id) != NULL; + + switch (status) { + case CERT_OK: + reason = "cert-ok"; + s->flags |= SF_VERIFIED; + break; + case CERT_NOCA: + reason = "no-ca"; + break; + case CERT_NOCERT: + reason = "no-client-cert"; + break; + case CERT_INVALID: + reason = "cert-invalid"; + break; + default: + reason = "cert-check-failed"; + break; + } + + log_debug("smtp: %p: smtp_cert_verify_cb: %s", s, reason); + + if (!(s->flags & SF_VERIFIED) && (s->listener->flags & F_TLS_VERIFY)) { + log_info("%016"PRIx64" smtp disconnected " + " reason=%s", s->id, + reason); + smtp_free(s, "SSL certificate check failed"); + return; + } + + smtp_tls_verified(s); + if (resume) + io_resume(s->io, IO_IN); +} + +static void +smtp_auth_failure_resume(int fd, short event, void *p) +{ + struct smtp_session *s = p; + + smtp_reply(s, "535 Authentication failed"); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_auth_failure_pause(struct smtp_session *s) +{ + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = arc4random_uniform(1000000); + log_trace(TRACE_SMTP, "smtp: timing-attack protection triggered, " + "will defer answer for %lu microseconds", tv.tv_usec); + evtimer_set(&s->pause, smtp_auth_failure_resume, s); + evtimer_add(&s->pause, &tv); +} + +static int +smtp_tx(struct smtp_session *s) +{ + struct smtp_tx *tx; + + tx = calloc(1, sizeof(*tx)); + if (tx == NULL) + return 0; + + TAILQ_INIT(&tx->rcpts); + + s->tx = tx; + tx->session = s; + + /* setup the envelope */ + tx->evp.ss = s->ss; + (void)strlcpy(tx->evp.tag, s->listener->tag, sizeof(tx->evp.tag)); + (void)strlcpy(tx->evp.smtpname, s->smtpname, sizeof(tx->evp.smtpname)); + (void)strlcpy(tx->evp.hostname, s->rdns, sizeof tx->evp.hostname); + (void)strlcpy(tx->evp.helo, s->helo, sizeof(tx->evp.helo)); + (void)strlcpy(tx->evp.username, s->username, sizeof(tx->evp.username)); + + if (s->flags & SF_BOUNCE) + tx->evp.flags |= EF_BOUNCE; + if (s->flags & SF_AUTHENTICATED) + tx->evp.flags |= EF_AUTHENTICATED; + + if ((tx->parser = rfc5322_parser_new()) == NULL) { + free(tx); + return 0; + } + + return 1; +} + +static void +smtp_tx_free(struct smtp_tx *tx) +{ + struct smtp_rcpt *rcpt; + + rfc5322_free(tx->parser); + + while ((rcpt = TAILQ_FIRST(&tx->rcpts))) { + TAILQ_REMOVE(&tx->rcpts, rcpt, entry); + free(rcpt); + } + + if (tx->ofile) + fclose(tx->ofile); + + tx->session->tx = NULL; + + free(tx); +} + +static void +smtp_tx_mail_from(struct smtp_tx *tx, const char *line) +{ + char *opt; + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, line, sizeof tmp); + copy = tmp; + + if (smtp_mailaddr(&tx->evp.sender, copy, 1, ©, + tx->session->smtpname) == 0) { + smtp_reply(tx->session, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + smtp_tx_free(tx); + return; + } + + while ((opt = strsep(©, " "))) { + if (*opt == '\0') + continue; + + if (strncasecmp(opt, "AUTH=", 5) == 0) + log_debug("debug: smtp: AUTH in MAIL FROM command"); + else if (strncasecmp(opt, "SIZE=", 5) == 0) + log_debug("debug: smtp: SIZE in MAIL FROM command"); + else if (strcasecmp(opt, "BODY=7BIT") == 0) + /* XXX only for this transaction */ + tx->session->flags &= ~SF_8BITMIME; + else if (strcasecmp(opt, "BODY=8BITMIME") == 0) + ; + else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "RET=", 4) == 0) { + opt += 4; + if (strcasecmp(opt, "HDRS") == 0) + tx->evp.dsn_ret = DSN_RETHDRS; + else if (strcasecmp(opt, "FULL") == 0) + tx->evp.dsn_ret = DSN_RETFULL; + } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ENVID=", 6) == 0) { + opt += 6; + if (strlcpy(tx->evp.dsn_envid, opt, sizeof(tx->evp.dsn_envid)) + >= sizeof(tx->evp.dsn_envid)) { + smtp_reply(tx->session, + "503 %s %s: option too large, truncated: %s", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS), opt); + smtp_tx_free(tx); + return; + } + } else { + smtp_reply(tx->session, "503 %s %s: Unsupported option %s", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS), opt); + smtp_tx_free(tx); + return; + } + } + + /* only check sendertable if defined and user has authenticated */ + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0]) { + m_create(p_lka, IMSG_SMTP_CHECK_SENDER, 0, 0, -1); + m_add_id(p_lka, tx->session->id); + m_add_string(p_lka, tx->session->listener->sendertable); + m_add_string(p_lka, tx->session->username); + m_add_mailaddr(p_lka, &tx->evp.sender); + m_close(p_lka); + tree_xset(&wait_lka_mail, tx->session->id, tx->session); + } + else + smtp_tx_create_message(tx); +} + +static void +smtp_tx_create_message(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_CREATE, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_close(p_queue); + tree_xset(&wait_queue_msg, tx->session->id, tx->session); +} + +static void +smtp_tx_rcpt_to(struct smtp_tx *tx, const char *line) +{ + char *opt, *p; + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, line, sizeof tmp); + copy = tmp; + + if (tx->rcptcount >= env->sc_session_max_rcpt) { + smtp_reply(tx->session, "451 %s %s: Too many recipients", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return; + } + + if (smtp_mailaddr(&tx->evp.rcpt, copy, 0, ©, + tx->session->smtpname) == 0) { + smtp_reply(tx->session, + "501 %s Recipient address syntax error", + esc_code(ESC_STATUS_PERMFAIL, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX)); + return; + } + + while ((opt = strsep(©, " "))) { + if (*opt == '\0') + continue; + + if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "NOTIFY=", 7) == 0) { + opt += 7; + while ((p = strsep(&opt, ","))) { + if (strcasecmp(p, "SUCCESS") == 0) + tx->evp.dsn_notify |= DSN_SUCCESS; + else if (strcasecmp(p, "FAILURE") == 0) + tx->evp.dsn_notify |= DSN_FAILURE; + else if (strcasecmp(p, "DELAY") == 0) + tx->evp.dsn_notify |= DSN_DELAY; + else if (strcasecmp(p, "NEVER") == 0) + tx->evp.dsn_notify |= DSN_NEVER; + } + + if (tx->evp.dsn_notify & DSN_NEVER && + tx->evp.dsn_notify & (DSN_SUCCESS | DSN_FAILURE | + DSN_DELAY)) { + smtp_reply(tx->session, + "553 NOTIFY option NEVER cannot be" + " combined with other options"); + return; + } + } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ORCPT=", 6) == 0) { + opt += 6; + + if (strncasecmp(opt, "rfc822;", 7) == 0) + opt += 7; + + if (!text_to_mailaddr(&tx->evp.dsn_orcpt, opt) || + !valid_localpart(tx->evp.dsn_orcpt.user) || + !valid_domainpart(tx->evp.dsn_orcpt.domain)) { + smtp_reply(tx->session, + "553 ORCPT address syntax error"); + return; + } + } else { + smtp_reply(tx->session, "503 Unsupported option %s", opt); + return; + } + } + + m_create(p_lka, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); + m_add_id(p_lka, tx->session->id); + m_add_envelope(p_lka, &tx->evp); + m_close(p_lka); + tree_xset(&wait_lka_rcpt, tx->session->id, tx->session); +} + +static void +smtp_tx_open_message(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_OPEN, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + tree_xset(&wait_queue_fd, tx->session->id, tx->session); +} + +static void +smtp_tx_commit(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_COMMIT, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + tree_xset(&wait_queue_commit, tx->session->id, tx->session); + smtp_filter_data_end(tx->session); +} + +static void +smtp_tx_rollback(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_ROLLBACK, 0, 0, -1); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + smtp_report_tx_rollback(tx->session, tx->msgid); + smtp_report_tx_reset(tx->session, tx->msgid); + smtp_filter_data_end(tx->session); +} + +static int +smtp_tx_dataline(struct smtp_tx *tx, const char *line) +{ + struct rfc5322_result res; + int r; + + log_trace(TRACE_SMTP, "<<< [MSG] %s", line); + + if (!strcmp(line, ".")) { + smtp_report_protocol_client(tx->session, "."); + log_trace(TRACE_SMTP, "<<< [EOM]"); + if (tx->error) + return 1; + line = NULL; + } + else { + /* ignore data line if an error is set */ + if (tx->error) + return 0; + + /* escape lines starting with a '.' */ + if (line[0] == '.') + line += 1; + } + + if (rfc5322_push(tx->parser, line) == -1) { + log_warnx("failed to push dataline"); + tx->error = TX_ERROR_INTERNAL; + return 0; + } + + for(;;) { + r = rfc5322_next(tx->parser, &res); + switch (r) { + case -1: + if (errno == ENOMEM) + tx->error = TX_ERROR_INTERNAL; + else + tx->error = TX_ERROR_MALFORMED; + return 0; + + case RFC5322_NONE: + /* Need more data */ + return 0; + + case RFC5322_HEADER_START: + /* ignore bcc */ + if (!strcasecmp("Bcc", res.hdr)) + continue; + + if (!strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) { + rfc5322_unfold_header(tx->parser); + continue; + } + + if (!strcasecmp("Received", res.hdr)) { + if (++tx->rcvcount >= MAX_HOPS_COUNT) { + log_warnx("warn: loop detected"); + tx->error = TX_ERROR_LOOP; + return 0; + } + } + else if (!tx->has_date && !strcasecmp("Date", res.hdr)) + tx->has_date = 1; + else if (!tx->has_message_id && + !strcasecmp("Message-Id", res.hdr)) + tx->has_message_id = 1; + + smtp_message_printf(tx, "%s:%s\n", res.hdr, res.value); + break; + + case RFC5322_HEADER_CONT: + + if (!strcasecmp("Bcc", res.hdr) || + !strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) + continue; + + smtp_message_printf(tx, "%s\n", res.value); + break; + + case RFC5322_HEADER_END: + if (!strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) + header_domain_append_callback(tx, res.hdr, + res.value); + break; + + case RFC5322_END_OF_HEADERS: + if (tx->session->listener->local || + tx->session->listener->port == 587) { + + if (!tx->has_date) { + log_debug("debug: %p: adding Date", tx); + smtp_message_printf(tx, "Date: %s\n", + time_to_text(tx->time)); + } + + if (!tx->has_message_id) { + log_debug("debug: %p: adding Message-ID", tx); + smtp_message_printf(tx, + "Message-ID: <%016"PRIx64"@%s>\n", + generate_uid(), + tx->session->listener->hostname); + } + } + break; + + case RFC5322_BODY_START: + case RFC5322_BODY: + smtp_message_printf(tx, "%s\n", res.value); + break; + + case RFC5322_END_OF_MESSAGE: + return 1; + + default: + fatalx("%s", __func__); + } + } +} + +static int +smtp_tx_filtered_dataline(struct smtp_tx *tx, const char *line) +{ + if (!strcmp(line, ".")) + line = NULL; + else { + /* ignore data line if an error is set */ + if (tx->error) + return 0; + } + io_printf(tx->filter, "%s\n", line ? line : "."); + return line ? 0 : 1; +} + +static void +smtp_tx_eom(struct smtp_tx *tx) +{ + smtp_filter_phase(FILTER_COMMIT, tx->session, NULL); +} + +static int +smtp_message_fd(struct smtp_tx *tx, int fd) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("smtp: %p: message fd %d", s, fd); + + if ((tx->ofile = fdopen(fd, "w")) == NULL) { + close(fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return 0; + } + return 1; +} + +static void +filter_session_io(struct io *io, int evt, void *arg) +{ + struct smtp_tx*tx = arg; + char*line = NULL; + ssize_t len; + + log_trace(TRACE_IO, "filter session io (smtp): %p: %s %s", tx, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_DATAIN: + nextline: + line = io_getline(tx->filter, &len); + /* No complete line received */ + if (line == NULL) + return; + + if (smtp_tx_dataline(tx, line)) { + smtp_tx_eom(tx); + return; + } + + goto nextline; + } +} + +static void +smtp_filter_fd(struct smtp_tx *tx, int fd) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("smtp: %p: filter fd %d", s, fd); + + tx->filter = io_new(); + io_set_fd(tx->filter, fd); + io_set_callback(tx->filter, filter_session_io, tx); +} + +static void +smtp_message_begin(struct smtp_tx *tx) +{ + struct smtp_session *s; + X509 *x; + int (*m_printf)(struct smtp_tx *, const char *, ...); + + m_printf = smtp_message_printf; + if (tx->filter) + m_printf = smtp_filter_printf; + + s = tx->session; + + log_debug("smtp: %p: message begin", s); + + smtp_reply(s, "354 Enter mail, end with \".\"" + " on a line by itself"); + + if (s->junk || (s->tx && s->tx->junk)) + m_printf(tx, "X-Spam: Yes\n"); + + m_printf(tx, "Received: "); + if (!(s->listener->flags & F_MASK_SOURCE)) { + m_printf(tx, "from %s (%s %s%s%s)", + s->helo, + s->rdns, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); + } + m_printf(tx, "\n\tby %s (%s) with %sSMTP%s%s id %08x", + s->smtpname, + SMTPD_NAME, + s->flags & SF_EHLO ? "E" : "", + s->flags & SF_SECURE ? "S" : "", + s->flags & SF_AUTHENTICATED ? "A" : "", + tx->msgid); + + if (s->flags & SF_SECURE) { + x = SSL_get_peer_certificate(io_tls(s->io)); + m_printf(tx, " (%s:%s:%d:%s)", + SSL_get_version(io_tls(s->io)), + SSL_get_cipher_name(io_tls(s->io)), + SSL_get_cipher_bits(io_tls(s->io), NULL), + (s->flags & SF_VERIFIED) ? "YES" : (x ? "FAIL" : "NO")); + X509_free(x); + + if (s->listener->flags & F_RECEIVEDAUTH) { + m_printf(tx, " auth=%s", + s->username[0] ? "yes" : "no"); + if (s->username[0]) + m_printf(tx, " user=%s", s->username); + } + } + + if (tx->rcptcount == 1) { + m_printf(tx, "\n\tfor <%s@%s>", + tx->evp.rcpt.user, + tx->evp.rcpt.domain); + } + + m_printf(tx, ";\n\t%s\n", time_to_text(time(&tx->time))); + + smtp_enter_state(s, STATE_BODY); +} + +static void +smtp_message_end(struct smtp_tx *tx) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("debug: %p: end of message, error=%d", s, tx->error); + + fclose(tx->ofile); + tx->ofile = NULL; + + switch(tx->error) { + case TX_OK: + smtp_tx_commit(tx); + return; + + case TX_ERROR_SIZE: + smtp_reply(s, "554 %s %s: Transaction failed, message too big", + esc_code(ESC_STATUS_PERMFAIL, ESC_MESSAGE_TOO_BIG_FOR_SYSTEM), + esc_description(ESC_MESSAGE_TOO_BIG_FOR_SYSTEM)); + break; + + case TX_ERROR_LOOP: + smtp_reply(s, "500 %s %s: Loop detected", + esc_code(ESC_STATUS_PERMFAIL, ESC_ROUTING_LOOP_DETECTED), + esc_description(ESC_ROUTING_LOOP_DETECTED)); + break; + + case TX_ERROR_MALFORMED: + smtp_reply(s, "550 %s %s: Message is not RFC 2822 compliant", + esc_code(ESC_STATUS_PERMFAIL, ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED), + esc_description(ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED)); + break; + + case TX_ERROR_IO: + case TX_ERROR_RESOURCES: + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + break; + + default: + /* fatal? */ + smtp_reply(s, "421 Internal server error"); + } + + smtp_tx_rollback(tx); + smtp_tx_free(tx); + smtp_enter_state(s, STATE_HELO); +} + +static int +smtp_filter_printf(struct smtp_tx *tx, const char *fmt, ...) +{ + va_list ap; + int len; + + if (tx->error) + return -1; + + va_start(ap, fmt); + len = io_vprintf(tx->filter, fmt, ap); + va_end(ap); + + if (len < 0) { + log_warn("smtp-in: session %016"PRIx64": vfprintf", tx->session->id); + tx->error = TX_ERROR_IO; + } + else + tx->odatalen += len; + + return len; +} + +static int +smtp_message_printf(struct smtp_tx *tx, const char *fmt, ...) +{ + va_list ap; + int len; + + if (tx->error) + return -1; + + va_start(ap, fmt); + len = vfprintf(tx->ofile, fmt, ap); + va_end(ap); + + if (len == -1) { + log_warn("smtp-in: session %016"PRIx64": vfprintf", tx->session->id); + tx->error = TX_ERROR_IO; + } + else + tx->odatalen += len; + + return len; +} + +#define CASE(x) case x : return #x + +const char * +smtp_strstate(int state) +{ + static char buf[32]; + + switch (state) { + CASE(STATE_NEW); + CASE(STATE_CONNECTED); + CASE(STATE_TLS); + CASE(STATE_HELO); + CASE(STATE_AUTH_INIT); + CASE(STATE_AUTH_USERNAME); + CASE(STATE_AUTH_PASSWORD); + CASE(STATE_AUTH_FINALIZE); + CASE(STATE_BODY); + CASE(STATE_QUIT); + default: + (void)snprintf(buf, sizeof(buf), "STATE_??? (%d)", state); + return (buf); + } +} + + +static void +smtp_report_link_connect(struct smtp_session *s, const char *rdns, int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_connect("smtp-in", s->id, rdns, fcrdns, ss_src, ss_dest); +} + +static void +smtp_report_link_greeting(struct smtp_session *s, + const char *domain) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_greeting("smtp-in", s->id, domain); +} + +static void +smtp_report_link_identify(struct smtp_session *s, const char *method, const char *identity) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_identify("smtp-in", s->id, method, identity); +} + +static void +smtp_report_link_tls(struct smtp_session *s, const char *ssl) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_tls("smtp-in", s->id, ssl); +} + +static void +smtp_report_link_disconnect(struct smtp_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_disconnect("smtp-in", s->id); +} + +static void +smtp_report_link_auth(struct smtp_session *s, const char *user, const char *result) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_auth("smtp-in", s->id, user, result); +} + +static void +smtp_report_tx_reset(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_reset("smtp-in", s->id, msgid); +} + +static void +smtp_report_tx_begin(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_begin("smtp-in", s->id, msgid); +} + +static void +smtp_report_tx_mail(struct smtp_session *s, uint32_t msgid, const char *address, int ok) +{ + char mailaddr[SMTPD_MAXMAILADDRSIZE]; + char *p; + + if (! SESSION_FILTERED(s)) + return; + + if ((p = strchr(address, '<')) == NULL) + return; + (void)strlcpy(mailaddr, p + 1, sizeof mailaddr); + if ((p = strchr(mailaddr, '>')) == NULL) + return; + *p = '\0'; + + report_smtp_tx_mail("smtp-in", s->id, msgid, mailaddr, ok); +} + +static void +smtp_report_tx_rcpt(struct smtp_session *s, uint32_t msgid, const char *address, int ok) +{ + char mailaddr[SMTPD_MAXMAILADDRSIZE]; + char *p; + + if (! SESSION_FILTERED(s)) + return; + + if ((p = strchr(address, '<')) == NULL) + return; + (void)strlcpy(mailaddr, p + 1, sizeof mailaddr); + if ((p = strchr(mailaddr, '>')) == NULL) + return; + *p = '\0'; + + report_smtp_tx_rcpt("smtp-in", s->id, msgid, mailaddr, ok); +} + +static void +smtp_report_tx_envelope(struct smtp_session *s, uint32_t msgid, uint64_t evpid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_envelope("smtp-in", s->id, msgid, evpid); +} + +static void +smtp_report_tx_data(struct smtp_session *s, uint32_t msgid, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_data("smtp-in", s->id, msgid, ok); +} + +static void +smtp_report_tx_commit(struct smtp_session *s, uint32_t msgid, size_t msgsz) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_commit("smtp-in", s->id, msgid, msgsz); +} + +static void +smtp_report_tx_rollback(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_rollback("smtp-in", s->id, msgid); +} + +static void +smtp_report_protocol_client(struct smtp_session *s, const char *command) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_client("smtp-in", s->id, command); +} + +static void +smtp_report_protocol_server(struct smtp_session *s, const char *response) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_server("smtp-in", s->id, response); +} + +static void +smtp_report_filter_response(struct smtp_session *s, int phase, int response, const char *param) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_filter_response("smtp-in", s->id, phase, response, param); +} + +static void +smtp_report_timeout(struct smtp_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_timeout("smtp-in", s->id); +} |