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