Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: smtp_stream.c,v 1.6 2025/02/25 19:15:46 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	smtp_stream 3
      6 /* SUMMARY
      7 /*	smtp stream I/O support
      8 /* SYNOPSIS
      9 /*	#include <smtp_stream.h>
     10 /*
     11 /*	void	smtp_stream_setup(stream, timeout, enable_deadline,
     12 /*					min_data_rate)
     13 /*	VSTREAM *stream;
     14 /*	int	timeout;
     15 /*	int	enable_deadline;
     16 /*	int	min_data_rate;
     17 /*
     18 /*	void	smtp_printf(stream, format, ...)
     19 /*	VSTREAM *stream;
     20 /*	const char *format;
     21 /*
     22 /*	void	smtp_flush(stream)
     23 /*	VSTREAM *stream;
     24 /*
     25 /*	int	smtp_fgetc(stream)
     26 /*	VSTREAM *stream;
     27 /*
     28 /*	int	smtp_get(vp, stream, maxlen, flags)
     29 /*	VSTRING	*vp;
     30 /*	VSTREAM *stream;
     31 /*	ssize_t	maxlen;
     32 /*	int	flags;
     33 /*
     34 /*	void	smtp_fputs(str, len, stream)
     35 /*	const char *str;
     36 /*	ssize_t	len;
     37 /*	VSTREAM *stream;
     38 /*
     39 /*	void	smtp_fwrite(str, len, stream)
     40 /*	const char *str;
     41 /*	ssize_t	len;
     42 /*	VSTREAM *stream;
     43 /*
     44 /*	void	smtp_fread_buf(vp, len, stream)
     45 /*	VSTRING	*vp;
     46 /*	ssize_t	len;
     47 /*	VSTREAM *stream;
     48 /*
     49 /*	void	smtp_fputc(ch, stream)
     50 /*	int	ch;
     51 /*	VSTREAM *stream;
     52 /*
     53 /*	void	smtp_vprintf(stream, format, ap)
     54 /*	VSTREAM *stream;
     55 /*	char	*format;
     56 /*	va_list	ap;
     57 /*
     58 /*	int	smtp_detect_bare_lf;
     59 /*	int	smtp_got_bare_lf;
     60 /* AUXILIARY API
     61 /*	int	smtp_get_noexcept(vp, stream, maxlen, flags)
     62 /*	VSTRING	*vp;
     63 /*	VSTREAM *stream;
     64 /*	ssize_t	maxlen;
     65 /*	int	flags;
     66 /* LEGACY API
     67 /*	void	smtp_timeout_setup(stream, timeout)
     68 /*	VSTREAM *stream;
     69 /*	int	timeout;
     70 /* DESCRIPTION
     71 /*	This module reads and writes text records delimited by CR LF,
     72 /*	with error detection: timeouts or unexpected end-of-file.
     73 /*	A trailing CR LF is added upon writing and removed upon reading.
     74 /*
     75 /*	smtp_stream_setup() prepares the specified stream for SMTP read
     76 /*	and write operations described below.
     77 /*	This routine alters the behavior of streams as follows:
     78 /* .IP \(bu
     79 /*	When enable_deadline is non-zero, then the timeout argument
     80 /*	specifies a deadline for the total amount time that may be
     81 /*	spent in all subsequent read/write operations.
     82 /*	Otherwise, the stream is configured to enforce
     83 /*	a time limit for each individual read/write system call.
     84 /* .IP \f(bu
     85 /*	Additionally, when min_data_rate is > 0, the deadline is
     86 /*	incremented by 1/min_data_rate seconds for every min_data_rate
     87 /*	bytes transferred. However, the deadline will never exceed
     88 /*	the value specified with the timeout argument.
     89 /* .IP \f(bu
     90 /*	The stream is configured to use double buffering.
     91 /* .IP \f(bu
     92 /*	The stream is configured to enable exception handling.
     93 /* .PP
     94 /*	smtp_printf() formats its arguments and writes the result to
     95 /*	the named stream, followed by a CR LF pair. The stream is NOT flushed.
     96 /*	Long lines of text are not broken.
     97 /*
     98 /*	smtp_flush() flushes the named stream.
     99 /*
    100 /*	smtp_fgetc() reads one character from the named stream.
    101 /*
    102 /*	smtp_get() reads the named stream up to and including
    103 /*	the next LF character and strips the trailing CR LF. The
    104 /*	\fImaxlen\fR argument limits the length of a line of text,
    105 /*	and protects the program against running out of memory.
    106 /*	Specify a zero bound to turn off bounds checking.
    107 /*	The result is the last character read, or VSTREAM_EOF.
    108 /*	The \fIflags\fR argument is zero or more of:
    109 /* .RS
    110 /* .IP SMTP_GET_FLAG_SKIP
    111 /*	Skip over input in excess of \fImaxlen\fR). Either way, a result
    112 /*	value of '\n' means that the input did not exceed \fImaxlen\fR.
    113 /* .IP SMTP_GET_FLAG_APPEND
    114 /*	Append content to the buffer instead of overwriting it.
    115 /* .RE
    116 /*	Specify SMTP_GET_FLAG_NONE for no special processing.
    117 /*
    118 /*	smtp_fputs() writes its string argument to the named stream.
    119 /*	Long strings are not broken. Each string is followed by a
    120 /*	CR LF pair. The stream is not flushed.
    121 /*
    122 /*	smtp_fwrite() writes its string argument to the named stream.
    123 /*	Long strings are not broken. No CR LF is appended. The stream
    124 /*	is not flushed.
    125 /*
    126 /*	smtp_fread_buf() invokes vstream_fread_buf() to read the
    127 /*	specified number of unformatted bytes from the stream. The
    128 /*	result is not null-terminated. NOTE: do not skip calling
    129 /*	smtp_fread_buf() when len == 0. This function has side
    130 /*	effects including resetting the buffer write position, and
    131 /*	skipping the call would invalidate the buffer state.
    132 /*
    133 /*	smtp_fputc() writes one character to the named stream.
    134 /*	The stream is not flushed.
    135 /*
    136 /*	smtp_vprintf() is the machine underneath smtp_printf().
    137 /*
    138 /*	smtp_get_noexcept() implements the subset of smtp_get()
    139 /*	without timeouts and without making long jumps. Instead,
    140 /*	query the stream status with vstream_feof() etc.
    141 /*
    142 /*	This function assigns smtp_got_bare_lf = smtp_detect_bare_lf,
    143 /*	if smtp_detect_bare_lf is non-zero and the last read line
    144 /*	was terminated with a bare newline. Otherwise, this function
    145 /*	sets smtp_got_bare_lf to zero.
    146 /*
    147 /*	smtp_timeout_setup() is a backwards-compatibility interface
    148 /*	for programs that don't require deadline or data-rate support.
    149 /* DIAGNOSTICS
    150 /* .fi
    151 /* .ad
    152 /*	In case of error, a vstream_longjmp() call is performed to the
    153 /*	context specified with vstream_setjmp().
    154 /*	After write error, further writes to the socket are disabled.
    155 /*	This eliminates the need for clumsy code to avoid unwanted
    156 /*	I/O while shutting down a TLS engine or closing a VSTREAM.
    157 /*	Error codes passed along with vstream_longjmp() are:
    158 /* .IP SMTP_ERR_EOF
    159 /*	An I/O error happened, or the peer has disconnected unexpectedly.
    160 /* .IP SMTP_ERR_TIME
    161 /*	The time limit specified to smtp_stream_setup() was exceeded.
    162 /* .PP
    163 /*	Additional error codes that may be used by applications:
    164 /* .IP SMTP_ERR_QUIET
    165 /*	Perform silent cleanup; the error was already reported by
    166 /*	the application.
    167 /*	This error is never generated by the smtp_stream(3) module, but
    168 /*	is defined for application-specific use.
    169 /* .IP SMTP_ERR_DATA
    170 /*	Application data error - the program cannot proceed with this
    171 /*	SMTP session.
    172 /* .IP SMTP_ERR_NONE
    173 /*	A non-error code that makes setjmp()/longjmp() convenient
    174 /*	to use.
    175 /* BUGS
    176 /*	The timeout deadline affects all I/O on the named stream, not
    177 /*	just the I/O done on behalf of this module.
    178 /*
    179 /*	The timeout deadline overwrites any previously set up state on
    180 /*	the named stream.
    181 /* LICENSE
    182 /* .ad
    183 /* .fi
    184 /*	The Secure Mailer license must be distributed with this software.
    185 /* AUTHOR(S)
    186 /*	Wietse Venema
    187 /*	IBM T.J. Watson Research
    188 /*	P.O. Box 704
    189 /*	Yorktown Heights, NY 10598, USA
    190 /*
    191 /*	Wietse Venema
    192 /*	Google, Inc.
    193 /*	111 8th Avenue
    194 /*	New York, NY 10011, USA
    195 /*--*/
    196 
    197 /* System library. */
    198 
    199 #include <sys_defs.h>
    200 #include <sys/socket.h>
    201 #include <sys/time.h>
    202 #include <setjmp.h>
    203 #include <stdlib.h>
    204 #include <stdarg.h>
    205 #include <unistd.h>
    206 #include <string.h>			/* FD_ZERO() needs bzero() prototype */
    207 #include <errno.h>
    208 
    209 /* Utility library. */
    210 
    211 #include <vstring.h>
    212 #include <vstream.h>
    213 #include <vstring_vstream.h>
    214 #include <msg.h>
    215 #include <iostuff.h>
    216 
    217 /* Application-specific. */
    218 
    219 #include "smtp_stream.h"
    220 
    221  /*
    222   * Important: the time limit feature must not introduce any system calls
    223   * when the input is already in the buffer, or when the output still fits in
    224   * the buffer. Such system calls would really hurt when receiving or sending
    225   * body content one line at a time.
    226   */
    227 int     smtp_detect_bare_lf;
    228 int     smtp_got_bare_lf;
    229 
    230 /* smtp_timeout_reset - reset per-stream error flags */
    231 
    232 static void smtp_timeout_reset(VSTREAM *stream)
    233 {
    234 
    235     /*
    236      * Individual smtp_stream(3) I/O functions must not recharge the deadline
    237      * timer, because multiline responses involve multiple smtp_stream(3)
    238      * calls, and we really want to limit the time to send or receive a
    239      * response.
    240      */
    241     vstream_clearerr(stream);
    242 }
    243 
    244 /* smtp_longjmp - raise an exception */
    245 
    246 static NORETURN smtp_longjmp(VSTREAM *stream, int err, const char *context)
    247 {
    248 
    249     /*
    250      * If we failed to write, don't bang our head against the wall another
    251      * time when closing the stream. In the case of SMTP over TLS, poisoning
    252      * the socket with shutdown() is more robust than purging the VSTREAM
    253      * buffer or replacing the write function pointer with dummy_write().
    254      */
    255     if (msg_verbose)
    256 	msg_info("%s: %s", context, err == SMTP_ERR_TIME ? "timeout" : "EOF");
    257     if (vstream_wr_error(stream))
    258 	/* Don't report ECONNRESET (hangup), EINVAL (already shut down), etc. */
    259 	(void) shutdown(vstream_fileno(stream), SHUT_WR);
    260     vstream_longjmp(stream, err);
    261 }
    262 
    263 /* smtp_stream_setup - configure timeout trap */
    264 
    265 void    smtp_stream_setup(VSTREAM *stream, int maxtime, int enable_deadline,
    266 			          int min_data_rate)
    267 {
    268     const char *myname = "smtp_stream_setup";
    269 
    270     if (msg_verbose)
    271 	msg_info("%s: maxtime=%d enable_deadline=%d min_data_rate=%d",
    272 		 myname, maxtime, enable_deadline, min_data_rate);
    273 
    274     vstream_control(stream,
    275 		    CA_VSTREAM_CTL_DOUBLE,
    276 		    CA_VSTREAM_CTL_TIMEOUT(maxtime),
    277 		    enable_deadline ? CA_VSTREAM_CTL_START_DEADLINE
    278 		    : CA_VSTREAM_CTL_STOP_DEADLINE,
    279 		    CA_VSTREAM_CTL_MIN_DATA_RATE(min_data_rate),
    280 		    CA_VSTREAM_CTL_EXCEPT,
    281 		    CA_VSTREAM_CTL_END);
    282 }
    283 
    284 /* smtp_flush - flush stream */
    285 
    286 void    smtp_flush(VSTREAM *stream)
    287 {
    288     int     err;
    289 
    290     /*
    291      * Do the I/O, protected against timeout.
    292      */
    293     smtp_timeout_reset(stream);
    294     err = vstream_fflush(stream);
    295 
    296     /*
    297      * See if there was a problem.
    298      */
    299     if (vstream_ftimeout(stream))
    300 	smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_flush");
    301     if (err != 0)
    302 	smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_flush");
    303 }
    304 
    305 /* smtp_vprintf - write one line to SMTP peer */
    306 
    307 void    smtp_vprintf(VSTREAM *stream, const char *fmt, va_list ap)
    308 {
    309     int     err;
    310 
    311     /*
    312      * Do the I/O, protected against timeout.
    313      */
    314     smtp_timeout_reset(stream);
    315     vstream_vfprintf(stream, fmt, ap);
    316     vstream_fputs("\r\n", stream);
    317     err = vstream_ferror(stream);
    318 
    319     /*
    320      * See if there was a problem.
    321      */
    322     if (vstream_ftimeout(stream))
    323 	smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_vprintf");
    324     if (err != 0)
    325 	smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_vprintf");
    326 }
    327 
    328 /* smtp_printf - write one line to SMTP peer */
    329 
    330 void    smtp_printf(VSTREAM *stream, const char *fmt,...)
    331 {
    332     va_list ap;
    333 
    334     va_start(ap, fmt);
    335     smtp_vprintf(stream, fmt, ap);
    336     va_end(ap);
    337 }
    338 
    339 /* smtp_fgetc - read one character from SMTP peer */
    340 
    341 int     smtp_fgetc(VSTREAM *stream)
    342 {
    343     int     ch;
    344 
    345     /*
    346      * Do the I/O, protected against timeout.
    347      */
    348     smtp_timeout_reset(stream);
    349     ch = VSTREAM_GETC(stream);
    350 
    351     /*
    352      * See if there was a problem.
    353      */
    354     if (vstream_ftimeout(stream))
    355 	smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fgetc");
    356     if (vstream_feof(stream) || vstream_ferror(stream))
    357 	smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fgetc");
    358     return (ch);
    359 }
    360 
    361 /* smtp_get - read one line from SMTP peer */
    362 
    363 int     smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
    364 {
    365     int     last_char;
    366 
    367     /*
    368      * Do the I/O, protected against timeout.
    369      */
    370     smtp_timeout_reset(stream);
    371     last_char = smtp_get_noexcept(vp, stream, bound, flags);
    372 
    373     /*
    374      * EOF is bad, whether or not it happens in the middle of a record. Don't
    375      * allow data that was truncated because of EOF.
    376      */
    377     if (vstream_ftimeout(stream))
    378 	smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_get");
    379     if (vstream_feof(stream) || vstream_ferror(stream))
    380 	smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_get");
    381     return (last_char);
    382 }
    383 
    384 /* smtp_get_noexcept - read one line from SMTP peer, without exceptions */
    385 
    386 int     smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
    387 {
    388     int     last_char;
    389     int     next_char;
    390 
    391     smtp_got_bare_lf = 0;
    392 
    393     /*
    394      * It's painful to do I/O with records that may span multiple buffers.
    395      * Allow for partial long lines (we will read the remainder later) and
    396      * allow for lines ending in bare LF. The idea is to be liberal in what
    397      * we accept, strict in what we send.
    398      *
    399      * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize
    400      * bare LF as record terminator.
    401      */
    402     last_char = (bound == 0 ?
    403 		 vstring_get_flags(vp, stream,
    404 				   (flags & SMTP_GET_FLAG_APPEND) ?
    405 				   VSTRING_GET_FLAG_APPEND : 0) :
    406 		 vstring_get_flags_bound(vp, stream,
    407 					 (flags & SMTP_GET_FLAG_APPEND) ?
    408 				       VSTRING_GET_FLAG_APPEND : 0, bound));
    409 
    410     switch (last_char) {
    411 
    412 	/*
    413 	 * Do some repair in the rare case that we stopped reading in the
    414 	 * middle of the CRLF record terminator.
    415 	 */
    416     case '\r':
    417 	if ((next_char = VSTREAM_GETC(stream)) == '\n') {
    418 	    VSTRING_ADDCH(vp, '\n');
    419 	    last_char = '\n';
    420 	    /* FALLTRHOUGH */
    421 	} else {
    422 	    if (next_char != VSTREAM_EOF)
    423 		vstream_ungetc(stream, next_char);
    424 	    break;
    425 	}
    426 
    427 	/*
    428 	 * Strip off the record terminator: either CRLF or just bare LF.
    429 	 *
    430 	 * XXX RFC 2821 disallows sending bare CR everywhere. We remove bare CR
    431 	 * if received before CRLF, and leave it alone otherwise.
    432 	 */
    433     case '\n':
    434 	vstring_truncate(vp, VSTRING_LEN(vp) - 1);
    435 	if (smtp_detect_bare_lf) {
    436 	    if (VSTRING_LEN(vp) == 0 || vstring_end(vp)[-1] != '\r')
    437 		smtp_got_bare_lf = smtp_detect_bare_lf;
    438 	    else
    439 		vstring_truncate(vp, VSTRING_LEN(vp) - 1);
    440 	} else {
    441 	    while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
    442 		vstring_truncate(vp, VSTRING_LEN(vp) - 1);
    443 	}
    444 	VSTRING_TERMINATE(vp);
    445 	/* FALLTRHOUGH */
    446 
    447 	/*
    448 	 * Partial line: just read the remainder later. If we ran into EOF,
    449 	 * the next test will deal with it.
    450 	 */
    451     default:
    452 	break;
    453     }
    454 
    455     /*
    456      * Optionally, skip over excess input, protected by the same time limit.
    457      */
    458     if (last_char != '\n' && (flags & SMTP_GET_FLAG_SKIP)
    459 	&& vstream_feof(stream) == 0 && vstream_ferror(stream) == 0)
    460 	while ((next_char = VSTREAM_GETC(stream)) != VSTREAM_EOF
    461 	       && next_char != '\n')
    462 	     /* void */ ;
    463 
    464     return (last_char);
    465 }
    466 
    467 /* smtp_fputs - write one line to SMTP peer */
    468 
    469 void    smtp_fputs(const char *cp, ssize_t todo, VSTREAM *stream)
    470 {
    471     int     err;
    472 
    473     if (todo < 0)
    474 	msg_panic("smtp_fputs: negative todo %ld", (long) todo);
    475 
    476     /*
    477      * Do the I/O, protected against timeout.
    478      */
    479     smtp_timeout_reset(stream);
    480     err = (vstream_fwrite(stream, cp, todo) != todo
    481 	   || vstream_fputs("\r\n", stream) == VSTREAM_EOF);
    482 
    483     /*
    484      * See if there was a problem.
    485      */
    486     if (vstream_ftimeout(stream))
    487 	smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fputs");
    488     if (err != 0)
    489 	smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fputs");
    490 }
    491 
    492 /* smtp_fwrite - write one string to SMTP peer */
    493 
    494 void    smtp_fwrite(const char *cp, ssize_t todo, VSTREAM *stream)
    495 {
    496     int     err;
    497 
    498     if (todo < 0)
    499 	msg_panic("smtp_fwrite: negative todo %ld", (long) todo);
    500 
    501     /*
    502      * Do the I/O, protected against timeout.
    503      */
    504     smtp_timeout_reset(stream);
    505     err = (vstream_fwrite(stream, cp, todo) != todo);
    506 
    507     /*
    508      * See if there was a problem.
    509      */
    510     if (vstream_ftimeout(stream))
    511 	smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fwrite");
    512     if (err != 0)
    513 	smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fwrite");
    514 }
    515 
    516 /* smtp_fread_buf - read one buffer from SMTP peer */
    517 
    518 void    smtp_fread_buf(VSTRING *vp, ssize_t todo, VSTREAM *stream)
    519 {
    520     int     err;
    521 
    522     /*
    523      * Do not return early if todo == 0. We still need the side effects from
    524      * calling vstream_fread_buf() including resetting the buffer write
    525      * position. Skipping the call would invalidate the buffer state.
    526      */
    527     if (todo < 0)
    528 	msg_panic("smtp_fread_buf: negative todo %ld", (long) todo);
    529 
    530     /*
    531      * Do the I/O, protected against timeout.
    532      */
    533     smtp_timeout_reset(stream);
    534     err = (vstream_fread_buf(stream, vp, todo) != todo);
    535 
    536     /*
    537      * See if there was a problem.
    538      */
    539     if (vstream_ftimeout(stream))
    540 	smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fread");
    541     if (err != 0)
    542 	smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fread");
    543 }
    544 
    545 /* smtp_fputc - write to SMTP peer */
    546 
    547 void    smtp_fputc(int ch, VSTREAM *stream)
    548 {
    549     int     stat;
    550 
    551     /*
    552      * Do the I/O, protected against timeout.
    553      */
    554     smtp_timeout_reset(stream);
    555     stat = VSTREAM_PUTC(ch, stream);
    556 
    557     /*
    558      * See if there was a problem.
    559      */
    560     if (vstream_ftimeout(stream))
    561 	smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fputc");
    562     if (stat == VSTREAM_EOF)
    563 	smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fputc");
    564 }
    565