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