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