summaryrefslogtreecommitdiffstats
path: root/src/smtpd/smtpd_chat.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/smtpd/smtpd_chat.c')
-rw-r--r--src/smtpd/smtpd_chat.c352
1 files changed, 352 insertions, 0 deletions
diff --git a/src/smtpd/smtpd_chat.c b/src/smtpd/smtpd_chat.c
new file mode 100644
index 0000000..278e536
--- /dev/null
+++ b/src/smtpd/smtpd_chat.c
@@ -0,0 +1,352 @@
+/*++
+/* NAME
+/* smtpd_chat 3
+/* SUMMARY
+/* SMTP server request/response support
+/* SYNOPSIS
+/* #include <smtpd.h>
+/* #include <smtpd_chat.h>
+/*
+/* void smtpd_chat_pre_jail_init(void)
+/*
+/* int smtpd_chat_query_limit(state, limit)
+/* SMTPD_STATE *state;
+/* int limit;
+/*
+/* void smtpd_chat_query(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_chat_reply(state, format, ...)
+/* SMTPD_STATE *state;
+/* char *format;
+/*
+/* void smtpd_chat_notify(state)
+/* SMTPD_STATE *state;
+/*
+/* void smtpd_chat_reset(state)
+/* SMTPD_STATE *state;
+/* DESCRIPTION
+/* This module implements SMTP server support for request/reply
+/* conversations, and maintains a limited SMTP transaction log.
+/*
+/* smtpd_chat_pre_jail_init() performs one-time initialization.
+/*
+/* smtpd_chat_query_limit() reads a line from the client that is
+/* at most "limit" bytes long. A copy is appended to the SMTP
+/* transaction log. The return value is non-zero for a complete
+/* line or else zero if the length limit was exceeded.
+/*
+/* smtpd_chat_query() receives a client request and appends a copy
+/* to the SMTP transaction log.
+/*
+/* smtpd_chat_reply() formats a server reply, sends it to the
+/* client, and appends a copy to the SMTP transaction log.
+/* When soft_bounce is enabled, all 5xx (reject) responses are
+/* replaced by 4xx (try again). In case of a 421 reply the
+/* SMTPD_FLAG_HANGUP flag is set for orderly disconnect.
+/*
+/* smtpd_chat_notify() sends a copy of the SMTP transaction log
+/* to the postmaster for review. The postmaster notice is sent only
+/* when delivery is possible immediately. It is an error to call
+/* smtpd_chat_notify() when no SMTP transaction log exists.
+/*
+/* smtpd_chat_reset() resets the transaction log. This is
+/* typically done at the beginning of an SMTP session, or
+/* within a session to discard non-error information.
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal errors: out of memory.
+/* internal protocol errors.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <stringops.h>
+#include <line_wrap.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <mail_addr.h>
+#include <maps.h>
+#include <post_mail.h>
+#include <mail_error.h>
+#include <smtp_reply_footer.h>
+#include <hfrom_format.h>
+
+/* Application-specific. */
+
+#include "smtpd.h"
+#include "smtpd_expand.h"
+#include "smtpd_chat.h"
+
+ /*
+ * Reject footer.
+ */
+static MAPS *smtpd_rej_ftr_maps;
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* smtpd_chat_pre_jail_init - initialize */
+
+void smtpd_chat_pre_jail_init(void)
+{
+ static int init_count = 0;
+
+ if (init_count++ != 0)
+ msg_panic("smtpd_chat_pre_jail_init: multiple calls");
+
+ /*
+ * SMTP server reject footer.
+ */
+ if (*var_smtpd_rej_ftr_maps)
+ smtpd_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS,
+ var_smtpd_rej_ftr_maps,
+ DICT_FLAG_LOCK);
+}
+
+/* smtp_chat_reset - reset SMTP transaction log */
+
+void smtpd_chat_reset(SMTPD_STATE *state)
+{
+ if (state->history) {
+ argv_free(state->history);
+ state->history = 0;
+ }
+}
+
+/* smtp_chat_append - append record to SMTP transaction log */
+
+static void smtp_chat_append(SMTPD_STATE *state, char *direction,
+ const char *text)
+{
+ char *line;
+
+ if (state->notify_mask == 0)
+ return;
+
+ if (state->history == 0)
+ state->history = argv_alloc(10);
+ line = concatenate(direction, text, (char *) 0);
+ argv_add(state->history, line, (char *) 0);
+ myfree(line);
+}
+
+/* smtpd_chat_query - receive and record an SMTP request */
+
+int smtpd_chat_query_limit(SMTPD_STATE *state, int limit)
+{
+ int last_char;
+
+ /*
+ * We can't parse or store input that exceeds var_line_limit, so we skip
+ * over it to avoid loss of synchronization.
+ */
+ last_char = smtp_get(state->buffer, state->client, limit,
+ SMTP_GET_FLAG_SKIP);
+ smtp_chat_append(state, "In: ", STR(state->buffer));
+ if (last_char != '\n')
+ msg_warn("%s: request longer than %d: %.30s...",
+ state->namaddr, limit,
+ printable(STR(state->buffer), '?'));
+
+ if (msg_verbose)
+ msg_info("< %s: %s", state->namaddr, STR(state->buffer));
+ return (last_char == '\n');
+}
+
+/* smtpd_chat_reply - format, send and record an SMTP response */
+
+void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vsmtpd_chat_reply(state, format, ap);
+ va_end(ap);
+}
+
+/* vsmtpd_chat_reply - format, send and record an SMTP response */
+
+void vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap)
+{
+ int delay = 0;
+ char *cp;
+ char *next;
+ char *end;
+ const char *footer;
+
+ /*
+ * Slow down clients that make errors. Sleep-on-anything slows down
+ * clients that make an excessive number of errors within a session.
+ */
+ if (state->error_count >= var_smtpd_soft_erlim)
+ sleep(delay = var_smtpd_err_sleep);
+
+ vstring_vsprintf(state->buffer, format, ap);
+
+ if ((*(cp = STR(state->buffer)) == '4' || *cp == '5')
+ && ((smtpd_rej_ftr_maps != 0
+ && (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0)
+ || *(footer = var_smtpd_rej_footer) != 0))
+ smtp_reply_footer(state->buffer, 0, footer, STR(smtpd_expand_filter),
+ smtpd_expand_lookup, (void *) state);
+
+ /* All 5xx replies must have a 5.xx.xx detail code. */
+ for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) {
+ if (var_soft_bounce) {
+ if (cp[0] == '5') {
+ cp[0] = '4';
+ if (cp[4] == '5')
+ cp[4] = '4';
+ }
+ }
+ /* This is why we use strlen() above instead of VSTRING_LEN(). */
+ if ((next = strstr(cp, "\r\n")) != 0) {
+ *next = 0;
+ if (next[2] != 0)
+ cp[3] = '-'; /* contact footer kludge */
+ else
+ next = end; /* strip trailing \r\n */
+ } else {
+ next = end;
+ }
+ smtp_chat_append(state, "Out: ", cp);
+
+ if (msg_verbose)
+ msg_info("> %s: %s", state->namaddr, cp);
+
+ smtp_fputs(cp, next - cp, state->client);
+ if (next < end)
+ cp = next + 2;
+ else
+ break;
+ }
+
+ /*
+ * Flush unsent output if no I/O happened for a while. This avoids
+ * timeouts with pipelined SMTP sessions that have lots of server-side
+ * delays (tarpit delays or DNS lookups for UCE restrictions).
+ */
+ if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10)
+ vstream_fflush(state->client);
+
+ /*
+ * Abort immediately if the connection is broken.
+ */
+ if (vstream_ftimeout(state->client))
+ vstream_longjmp(state->client, SMTP_ERR_TIME);
+ if (vstream_ferror(state->client))
+ vstream_longjmp(state->client, SMTP_ERR_EOF);
+
+ /*
+ * Orderly disconnect in case of 421 or 521 reply.
+ */
+ if (strncmp(STR(state->buffer), "421", 3) == 0
+ || strncmp(STR(state->buffer), "521", 3) == 0)
+ state->flags |= SMTPD_FLAG_HANGUP;
+}
+
+/* print_line - line_wrap callback */
+
+static void print_line(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *notice = (VSTREAM *) context;
+
+ post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str);
+}
+
+/* smtpd_chat_notify - notify postmaster */
+
+void smtpd_chat_notify(SMTPD_STATE *state)
+{
+ const char *myname = "smtpd_chat_notify";
+ VSTREAM *notice;
+ char **cpp;
+
+ /*
+ * Sanity checks.
+ */
+ if (state->history == 0)
+ msg_panic("%s: no conversation history", myname);
+ if (msg_verbose)
+ msg_info("%s: notify postmaster", myname);
+
+ /*
+ * Construct a message for the postmaster, explaining what this is all
+ * about. This is junk mail: don't send it when the mail posting service
+ * is unavailable, and use the double bounce sender address to prevent
+ * mail bounce wars. Always prepend one space to message content that we
+ * generate from untrusted data.
+ */
+#define NULL_TRACE_FLAGS 0
+#define NO_QUEUE_ID ((VSTRING *) 0)
+#define LENGTH 78
+#define INDENT 4
+
+ notice = post_mail_fopen_nowait(mail_addr_double_bounce(),
+ (state->error_mask & MAIL_ERROR_BOUNCE) ?
+ var_bounce_rcpt : var_error_rcpt,
+ MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS,
+ SMTPUTF8_FLAG_NONE, NO_QUEUE_ID);
+ if (notice == 0) {
+ msg_warn("postmaster notify: %m");
+ return;
+ }
+ if (smtpd_hfrom_format == HFROM_FORMAT_CODE_STD) {
+ post_mail_fprintf(notice, "From: Mail Delivery System <%s>",
+ mail_addr_mail_daemon());
+ post_mail_fprintf(notice, "To: Postmaster <%s>", var_error_rcpt);
+ } else {
+ post_mail_fprintf(notice, "From: %s (Mail Delivery System)",
+ mail_addr_mail_daemon());
+ post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt);
+ }
+ post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s",
+ var_mail_name, state->namaddr);
+ post_mail_fputs(notice, "");
+ post_mail_fputs(notice, "Transcript of session follows.");
+ post_mail_fputs(notice, "");
+ argv_terminate(state->history);
+ for (cpp = state->history->argv; *cpp; cpp++)
+ line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line,
+ (void *) notice);
+ post_mail_fputs(notice, "");
+ if (state->reason)
+ post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason);
+ post_mail_fputs(notice, "");
+ post_mail_fprintf(notice, "For other details, see the local mail logfile");
+ (void) post_mail_fclose(notice);
+}