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 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 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 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 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 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 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 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 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 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 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 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 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 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