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 
smtp_timeout_reset(VSTREAM * stream)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 
smtp_longjmp(VSTREAM * stream,int err,const char * context)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 
smtp_stream_setup(VSTREAM * stream,int maxtime,int enable_deadline,int min_data_rate)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 
smtp_flush(VSTREAM * stream)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 
smtp_vprintf(VSTREAM * stream,const char * fmt,va_list ap)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 
smtp_printf(VSTREAM * stream,const char * fmt,...)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 
smtp_fgetc(VSTREAM * stream)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 
smtp_get(VSTRING * vp,VSTREAM * stream,ssize_t bound,int flags)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 
smtp_get_noexcept(VSTRING * vp,VSTREAM * stream,ssize_t bound,int flags)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 
smtp_fputs(const char * cp,ssize_t todo,VSTREAM * stream)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 
smtp_fwrite(const char * cp,ssize_t todo,VSTREAM * stream)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 
smtp_fread_buf(VSTRING * vp,ssize_t todo,VSTREAM * stream)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 
smtp_fputc(int ch,VSTREAM * stream)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