aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/smtp_session.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/smtp_session.c')
-rw-r--r--smtpd/smtp_session.c3223
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, &copy,
+ 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, &copy,
+ 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, &copy,
+ 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, &copy,
+ 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(&copy, " "))) {
+ 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, &copy,
+ 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(&copy, " "))) {
+ 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);
+}