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