summaryrefslogtreecommitdiffstats
path: root/src/global/smtp_stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/smtp_stream.c')
-rw-r--r--src/global/smtp_stream.c562
1 files changed, 562 insertions, 0 deletions
diff --git a/src/global/smtp_stream.c b/src/global/smtp_stream.c
new file mode 100644
index 0000000..6629508
--- /dev/null
+++ b/src/global/smtp_stream.c
@@ -0,0 +1,562 @@
+/*++
+/* NAME
+/* smtp_stream 3
+/* SUMMARY
+/* smtp stream I/O support
+/* SYNOPSIS
+/* #include <smtp_stream.h>
+/*
+/* void smtp_stream_setup(stream, timeout, enable_deadline,
+/* min_data_rate)
+/* VSTREAM *stream;
+/* int timeout;
+/* int enable_deadline;
+/* int min_data_rate;
+/*
+/* void smtp_printf(stream, format, ...)
+/* VSTREAM *stream;
+/* const char *format;
+/*
+/* void smtp_flush(stream)
+/* VSTREAM *stream;
+/*
+/* int smtp_fgetc(stream)
+/* VSTREAM *stream;
+/*
+/* int smtp_get(vp, stream, maxlen, flags)
+/* VSTRING *vp;
+/* VSTREAM *stream;
+/* ssize_t maxlen;
+/* int flags;
+/*
+/* void smtp_fputs(str, len, stream)
+/* const char *str;
+/* ssize_t len;
+/* VSTREAM *stream;
+/*
+/* void smtp_fwrite(str, len, stream)
+/* const char *str;
+/* ssize_t len;
+/* VSTREAM *stream;
+/*
+/* void smtp_fread_buf(vp, len, stream)
+/* VSTRING *vp;
+/* ssize_t len;
+/* VSTREAM *stream;
+/*
+/* void smtp_fputc(ch, stream)
+/* int ch;
+/* VSTREAM *stream;
+/*
+/* void smtp_vprintf(stream, format, ap)
+/* VSTREAM *stream;
+/* char *format;
+/* va_list ap;
+/*
+/* int smtp_detect_bare_lf;
+/* int smtp_got_bare_lf;
+/* AUXILIARY API
+/* int smtp_get_noexcept(vp, stream, maxlen, flags)
+/* VSTRING *vp;
+/* VSTREAM *stream;
+/* ssize_t maxlen;
+/* int flags;
+/* LEGACY API
+/* void smtp_timeout_setup(stream, timeout)
+/* VSTREAM *stream;
+/* int timeout;
+/* DESCRIPTION
+/* This module reads and writes text records delimited by CR LF,
+/* with error detection: timeouts or unexpected end-of-file.
+/* A trailing CR LF is added upon writing and removed upon reading.
+/*
+/* smtp_stream_setup() prepares the specified stream for SMTP read
+/* and write operations described below.
+/* This routine alters the behavior of streams as follows:
+/* .IP \(bu
+/* When enable_deadline is non-zero, then the timeout argument
+/* specifies a deadline for the total amount time that may be
+/* spent in all subsequent read/write operations.
+/* Otherwise, the stream is configured to enforce
+/* a time limit for each individual read/write system call.
+/* .IP \f(bu
+/* Additionally, when min_data_rate is > 0, the deadline is
+/* incremented by 1/min_data_rate seconds for every min_data_rate
+/* bytes transferred. However, the deadline will never exceed
+/* the value specified with the timeout argument.
+/* .IP \f(bu
+/* The stream is configured to use double buffering.
+/* .IP \f(bu
+/* The stream is configured to enable exception handling.
+/* .PP
+/* smtp_printf() formats its arguments and writes the result to
+/* the named stream, followed by a CR LF pair. The stream is NOT flushed.
+/* Long lines of text are not broken.
+/*
+/* smtp_flush() flushes the named stream.
+/*
+/* smtp_fgetc() reads one character from the named stream.
+/*
+/* smtp_get() reads the named stream up to and including
+/* the next LF character and strips the trailing CR LF. The
+/* \fImaxlen\fR argument limits the length of a line of text,
+/* and protects the program against running out of memory.
+/* Specify a zero bound to turn off bounds checking.
+/* The result is the last character read, or VSTREAM_EOF.
+/* The \fIflags\fR argument is zero or more of:
+/* .RS
+/* .IP SMTP_GET_FLAG_SKIP
+/* Skip over input in excess of \fImaxlen\fR). Either way, a result
+/* value of '\n' means that the input did not exceed \fImaxlen\fR.
+/* .IP SMTP_GET_FLAG_APPEND
+/* Append content to the buffer instead of overwriting it.
+/* .RE
+/* Specify SMTP_GET_FLAG_NONE for no special processing.
+/*
+/* smtp_fputs() writes its string argument to the named stream.
+/* Long strings are not broken. Each string is followed by a
+/* CR LF pair. The stream is not flushed.
+/*
+/* smtp_fwrite() writes its string argument to the named stream.
+/* Long strings are not broken. No CR LF is appended. The stream
+/* is not flushed.
+/*
+/* smtp_fread_buf() invokes vstream_fread_buf() to read the
+/* specified number of unformatted bytes from the stream. The
+/* result is not null-terminated. NOTE: do not skip calling
+/* smtp_fread_buf() when len == 0. This function has side
+/* effects including resetting the buffer write position, and
+/* skipping the call would invalidate the buffer state.
+/*
+/* smtp_fputc() writes one character to the named stream.
+/* The stream is not flushed.
+/*
+/* smtp_vprintf() is the machine underneath smtp_printf().
+/*
+/* smtp_get_noexcept() implements the subset of smtp_get()
+/* without timeouts and without making long jumps. Instead,
+/* query the stream status with vstream_feof() etc.
+/*
+/* This function assigns smtp_got_bare_lf = smtp_detect_bare_lf,
+/* if smtp_detect_bare_lf is non-zero and the last read line
+/* was terminated with a bare newline. Otherwise, this function
+/* sets smtp_got_bare_lf to zero.
+/*
+/* smtp_timeout_setup() is a backwards-compatibility interface
+/* for programs that don't require deadline or data-rate support.
+/* DIAGNOSTICS
+/* .fi
+/* .ad
+/* In case of error, a vstream_longjmp() call is performed to the
+/* context specified with vstream_setjmp().
+/* After write error, further writes to the socket are disabled.
+/* This eliminates the need for clumsy code to avoid unwanted
+/* I/O while shutting down a TLS engine or closing a VSTREAM.
+/* Error codes passed along with vstream_longjmp() are:
+/* .IP SMTP_ERR_EOF
+/* An I/O error happened, or the peer has disconnected unexpectedly.
+/* .IP SMTP_ERR_TIME
+/* The time limit specified to smtp_stream_setup() was exceeded.
+/* .PP
+/* Additional error codes that may be used by applications:
+/* .IP SMTP_ERR_QUIET
+/* Perform silent cleanup; the error was already reported by
+/* the application.
+/* This error is never generated by the smtp_stream(3) module, but
+/* is defined for application-specific use.
+/* .IP SMTP_ERR_DATA
+/* Application data error - the program cannot proceed with this
+/* SMTP session.
+/* .IP SMTP_ERR_NONE
+/* A non-error code that makes setjmp()/longjmp() convenient
+/* to use.
+/* BUGS
+/* The timeout deadline affects all I/O on the named stream, not
+/* just the I/O done on behalf of this module.
+/*
+/* The timeout deadline overwrites any previously set up state on
+/* the named stream.
+/* 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 <sys/socket.h>
+#include <sys/time.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h> /* FD_ZERO() needs bzero() prototype */
+#include <errno.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg.h>
+#include <iostuff.h>
+
+/* Application-specific. */
+
+#include "smtp_stream.h"
+
+ /*
+ * Important: the time limit feature must not introduce any system calls
+ * when the input is already in the buffer, or when the output still fits in
+ * the buffer. Such system calls would really hurt when receiving or sending
+ * body content one line at a time.
+ */
+int smtp_detect_bare_lf;
+int smtp_got_bare_lf;
+
+/* smtp_timeout_reset - reset per-stream error flags */
+
+static void smtp_timeout_reset(VSTREAM *stream)
+{
+
+ /*
+ * Individual smtp_stream(3) I/O functions must not recharge the deadline
+ * timer, because multiline responses involve multiple smtp_stream(3)
+ * calls, and we really want to limit the time to send or receive a
+ * response.
+ */
+ vstream_clearerr(stream);
+}
+
+/* smtp_longjmp - raise an exception */
+
+static NORETURN smtp_longjmp(VSTREAM *stream, int err, const char *context)
+{
+
+ /*
+ * If we failed to write, don't bang our head against the wall another
+ * time when closing the stream. In the case of SMTP over TLS, poisoning
+ * the socket with shutdown() is more robust than purging the VSTREAM
+ * buffer or replacing the write function pointer with dummy_write().
+ */
+ if (msg_verbose)
+ msg_info("%s: %s", context, err == SMTP_ERR_TIME ? "timeout" : "EOF");
+ if (vstream_wr_error(stream))
+ /* Don't report ECONNRESET (hangup), EINVAL (already shut down), etc. */
+ (void) shutdown(vstream_fileno(stream), SHUT_WR);
+ vstream_longjmp(stream, err);
+}
+
+/* smtp_stream_setup - configure timeout trap */
+
+void smtp_stream_setup(VSTREAM *stream, int maxtime, int enable_deadline,
+ int min_data_rate)
+{
+ const char *myname = "smtp_stream_setup";
+
+ if (msg_verbose)
+ msg_info("%s: maxtime=%d enable_deadline=%d min_data_rate=%d",
+ myname, maxtime, enable_deadline, min_data_rate);
+
+ vstream_control(stream,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_TIMEOUT(maxtime),
+ enable_deadline ? CA_VSTREAM_CTL_START_DEADLINE
+ : CA_VSTREAM_CTL_STOP_DEADLINE,
+ CA_VSTREAM_CTL_MIN_DATA_RATE(min_data_rate),
+ CA_VSTREAM_CTL_EXCEPT,
+ CA_VSTREAM_CTL_END);
+}
+
+/* smtp_flush - flush stream */
+
+void smtp_flush(VSTREAM *stream)
+{
+ int err;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ err = vstream_fflush(stream);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_flush");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_flush");
+}
+
+/* smtp_vprintf - write one line to SMTP peer */
+
+void smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap)
+{
+ int err;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ vstream_vfprintf(stream, fmt, ap);
+ vstream_fputs("\r\n", stream);
+ err = vstream_ferror(stream);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_vprintf");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_vprintf");
+}
+
+/* smtp_printf - write one line to SMTP peer */
+
+void smtp_printf(VSTREAM *stream, const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ smtp_vprintf(stream, fmt, ap);
+ va_end(ap);
+}
+
+/* smtp_fgetc - read one character from SMTP peer */
+
+int smtp_fgetc(VSTREAM *stream)
+{
+ int ch;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ ch = VSTREAM_GETC(stream);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fgetc");
+ if (vstream_feof(stream) || vstream_ferror(stream))
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fgetc");
+ return (ch);
+}
+
+/* smtp_get - read one line from SMTP peer */
+
+int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
+{
+ int last_char;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ last_char = smtp_get_noexcept(vp, stream, bound, flags);
+
+ /*
+ * EOF is bad, whether or not it happens in the middle of a record. Don't
+ * allow data that was truncated because of EOF.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_get");
+ if (vstream_feof(stream) || vstream_ferror(stream))
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_get");
+ return (last_char);
+}
+
+/* smtp_get_noexcept - read one line from SMTP peer, without exceptions */
+
+int smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
+{
+ int last_char;
+ int next_char;
+
+ smtp_got_bare_lf = 0;
+
+ /*
+ * It's painful to do I/O with records that may span multiple buffers.
+ * Allow for partial long lines (we will read the remainder later) and
+ * allow for lines ending in bare LF. The idea is to be liberal in what
+ * we accept, strict in what we send.
+ *
+ * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize
+ * bare LF as record terminator.
+ */
+ last_char = (bound == 0 ?
+ vstring_get_flags(vp, stream,
+ (flags & SMTP_GET_FLAG_APPEND) ?
+ VSTRING_GET_FLAG_APPEND : 0) :
+ vstring_get_flags_bound(vp, stream,
+ (flags & SMTP_GET_FLAG_APPEND) ?
+ VSTRING_GET_FLAG_APPEND : 0, bound));
+
+ switch (last_char) {
+
+ /*
+ * Do some repair in the rare case that we stopped reading in the
+ * middle of the CRLF record terminator.
+ */
+ case '\r':
+ if ((next_char = VSTREAM_GETC(stream)) == '\n') {
+ VSTRING_ADDCH(vp, '\n');
+ last_char = '\n';
+ /* FALLTRHOUGH */
+ } else {
+ if (next_char != VSTREAM_EOF)
+ vstream_ungetc(stream, next_char);
+ break;
+ }
+
+ /*
+ * Strip off the record terminator: either CRLF or just bare LF.
+ *
+ * XXX RFC 2821 disallows sending bare CR everywhere. We remove bare CR
+ * if received before CRLF, and leave it alone otherwise.
+ */
+ case '\n':
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+ if (smtp_detect_bare_lf) {
+ if (VSTRING_LEN(vp) == 0 || vstring_end(vp)[-1] != '\r')
+ smtp_got_bare_lf = smtp_detect_bare_lf;
+ else
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+ } else {
+ while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
+ vstring_truncate(vp, VSTRING_LEN(vp) - 1);
+ }
+ VSTRING_TERMINATE(vp);
+ /* FALLTRHOUGH */
+
+ /*
+ * Partial line: just read the remainder later. If we ran into EOF,
+ * the next test will deal with it.
+ */
+ default:
+ break;
+ }
+
+ /*
+ * Optionally, skip over excess input, protected by the same time limit.
+ */
+ if (last_char != '\n' && (flags & SMTP_GET_FLAG_SKIP)
+ && vstream_feof(stream) == 0 && vstream_ferror(stream) == 0)
+ while ((next_char = VSTREAM_GETC(stream)) != VSTREAM_EOF
+ && next_char != '\n')
+ /* void */ ;
+
+ return (last_char);
+}
+
+/* smtp_fputs - write one line to SMTP peer */
+
+void smtp_fputs(const char *cp, ssize_t todo, VSTREAM *stream)
+{
+ int err;
+
+ if (todo < 0)
+ msg_panic("smtp_fputs: negative todo %ld", (long) todo);
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ err = (vstream_fwrite(stream, cp, todo) != todo
+ || vstream_fputs("\r\n", stream) == VSTREAM_EOF);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fputs");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fputs");
+}
+
+/* smtp_fwrite - write one string to SMTP peer */
+
+void smtp_fwrite(const char *cp, ssize_t todo, VSTREAM *stream)
+{
+ int err;
+
+ if (todo < 0)
+ msg_panic("smtp_fwrite: negative todo %ld", (long) todo);
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ err = (vstream_fwrite(stream, cp, todo) != todo);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fwrite");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fwrite");
+}
+
+/* smtp_fread_buf - read one buffer from SMTP peer */
+
+void smtp_fread_buf(VSTRING *vp, ssize_t todo, VSTREAM *stream)
+{
+ int err;
+
+ /*
+ * Do not return early if todo == 0. We still need the side effects from
+ * calling vstream_fread_buf() including resetting the buffer write
+ * position. Skipping the call would invalidate the buffer state.
+ */
+ if (todo < 0)
+ msg_panic("smtp_fread_buf: negative todo %ld", (long) todo);
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ err = (vstream_fread_buf(stream, vp, todo) != todo);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fread");
+ if (err != 0)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fread");
+}
+
+/* smtp_fputc - write to SMTP peer */
+
+void smtp_fputc(int ch, VSTREAM *stream)
+{
+ int stat;
+
+ /*
+ * Do the I/O, protected against timeout.
+ */
+ smtp_timeout_reset(stream);
+ stat = VSTREAM_PUTC(ch, stream);
+
+ /*
+ * See if there was a problem.
+ */
+ if (vstream_ftimeout(stream))
+ smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fputc");
+ if (stat == VSTREAM_EOF)
+ smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fputc");
+}