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