1/* $NetBSD: smtp-sink.c,v 1.3 2022/10/08 16:12:49 christos Exp $ */ 2 3/*++ 4/* NAME 5/* smtp-sink 1 6/* SUMMARY 7/* parallelized SMTP/LMTP test server 8/* SYNOPSIS 9/* .fi 10/* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR 11/* \fIbacklog\fR 12/* 13/* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR 14/* DESCRIPTION 15/* \fBsmtp-sink\fR listens on the named host (or address) and port. 16/* It takes SMTP messages from the network and throws them away. 17/* The purpose is to measure client performance, not protocol 18/* compliance. 19/* 20/* \fBsmtp-sink\fR may also be configured to capture each mail 21/* delivery transaction to file. Since disk latencies are large 22/* compared to network delays, this mode of operation can 23/* reduce the maximal performance by several orders of magnitude. 24/* 25/* Connections can be accepted on IPv4 or IPv6 endpoints, or on 26/* UNIX-domain sockets. 27/* IPv4 and IPv6 are the default. 28/* This program is the complement of the \fBsmtp-source\fR(1) program. 29/* 30/* Note: this is an unsupported test program. No attempt is made 31/* to maintain compatibility between successive versions. 32/* 33/* Arguments: 34/* .IP \fB-4\fR 35/* Support IPv4 only. This option has no effect when 36/* Postfix is built without IPv6 support. 37/* .IP \fB-6\fR 38/* Support IPv6 only. This option is not available when 39/* Postfix is built without IPv6 support. 40/* .IP \fB-8\fR 41/* Do not announce 8BITMIME support. 42/* .IP \fB-a\fR 43/* Do not announce SASL authentication support. 44/* .IP "\fB-A \fIdelay\fR" 45/* Wait \fIdelay\fR seconds after responding to DATA, then 46/* abort prematurely with a 550 reply status. Do not read 47/* further input from the client; this is an attempt to block 48/* the client before it sends ".". Specify a zero delay value 49/* to abort immediately. 50/* .IP "\fB-b \fIsoft-bounce-reply\fR" 51/* Use \fIsoft-bounce-reply\fR for soft reject responses. The 52/* default reply is "450 4.3.0 Error: command failed". 53/* .IP "\fB-B \fIhard-bounce-reply\fR" 54/* Use \fIhard-bounce-reply\fR for hard reject responses. The 55/* default reply is "500 5.3.0 Error: command failed". 56/* .IP \fB-c\fR 57/* Display running counters that are updated whenever an SMTP 58/* session ends, a QUIT command is executed, or when "." is 59/* received. 60/* .IP \fB-C\fR 61/* Disable XCLIENT support. 62/* .IP "\fB-d \fIdump-template\fR" 63/* Dump each mail transaction to a single-message file whose 64/* name is created by expanding the \fIdump-template\fR via 65/* strftime(3) and appending a pseudo-random hexadecimal number 66/* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3"). 67/* If the template contains "/" characters, missing directories 68/* are created automatically. The message dump format is 69/* described below. 70/* .sp 71/* Note: this option keeps one capture file open for every 72/* mail transaction in progress. 73/* .IP "\fB-D \fIdump-template\fR" 74/* Append mail transactions to a multi-message dump file whose 75/* name is created by expanding the \fIdump-template\fR via 76/* strftime(3). 77/* If the template contains "/" characters, missing directories 78/* are created automatically. The message dump format is 79/* described below. 80/* .sp 81/* Note: this option keeps one capture file open for every 82/* mail transaction in progress. 83/* .IP \fB-e\fR 84/* Do not announce ESMTP support. 85/* .IP \fB-E\fR 86/* Do not announce ENHANCEDSTATUSCODES support. 87/* .IP "\fB-f \fIcommand,command,...\fR" 88/* Reject the specified commands with a hard (5xx) error code. 89/* This option implies \fB-p\fR. 90/* .sp 91/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, 92/* DATA, ., RSET, NOOP, and QUIT. Separate command names by 93/* white space or commas, and use quotes to protect white space 94/* from the shell. Command names are case-insensitive. 95/* .IP \fB-F\fR 96/* Disable XFORWARD support. 97/* .IP "\fB-h\fI hostname\fR" 98/* Use \fIhostname\fR in the SMTP greeting, in the HELO response, 99/* and in the EHLO response. The default hostname is "smtp-sink". 100/* .IP "\fB-H\fI delay\fR" 101/* Delay the first read operation after receiving DATA (time 102/* in seconds). Combine with a large test message and a small 103/* TCP window size (see the \fB-T\fR option) to test the Postfix 104/* client write_wait() implementation. 105/* .IP \fB-L\fR 106/* Enable LMTP instead of SMTP. 107/* .IP "\fB-m \fIcount\fR (default: 256)" 108/* An upper bound on the maximal number of simultaneous 109/* connections that \fBsmtp-sink\fR will handle. This prevents 110/* the process from running out of file descriptors. Excess 111/* connections will stay queued in the TCP/IP stack. 112/* .IP "\fB-M \fIcount\fR" 113/* Terminate after receiving \fIcount\fR messages. 114/* .IP "\fB-n \fIcount\fR" 115/* Terminate after \fIcount\fR sessions. 116/* .IP \fB-N\fR 117/* Do not announce support for DSN. 118/* .IP \fB-p\fR 119/* Do not announce support for ESMTP command pipelining. 120/* .IP \fB-P\fR 121/* Change the server greeting so that it appears to come through 122/* a CISCO PIX system. Implies \fB-e\fR. 123/* .IP "\fB-q \fIcommand,command,...\fR" 124/* Disconnect (without replying) after receiving one of the 125/* specified commands. 126/* .sp 127/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, 128/* DATA, ., RSET, NOOP, and QUIT. Separate command names by 129/* white space or commas, and use quotes to protect white space 130/* from the shell. Command names are case-insensitive. 131/* .IP "\fB-Q \fIcommand,command,...\fR" 132/* Send a 421 reply and disconnect after receiving one 133/* of the specified commands. 134/* .sp 135/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, 136/* DATA, ., RSET, NOOP, and QUIT. Separate command names by 137/* white space or commas, and use quotes to protect white space 138/* from the shell. Command names are case-insensitive. 139/* .IP "\fB-r \fIcommand,command,...\fR" 140/* Reject the specified commands with a soft (4xx) error code. 141/* This option implies \fB-p\fR. 142/* .sp 143/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, 144/* DATA, ., RSET, NOOP, and QUIT. Separate command names by 145/* white space or commas, and use quotes to protect white space 146/* from the shell. Command names are case-insensitive. 147/* .IP "\fB-R \fIroot-directory\fR" 148/* Change the process root directory to the specified location. 149/* This option requires super-user privileges. See also the 150/* \fB-u\fR option. 151/* .IP "\fB-s \fIcommand,command,...\fR" 152/* Log the named commands to syslogd. 153/* .sp 154/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, 155/* DATA, ., RSET, NOOP, and QUIT. Separate command names by 156/* white space or commas, and use quotes to protect white space 157/* from the shell. Command names are case-insensitive. 158/* .IP "\fB-S start-string\fR" 159/* An optional string that is prepended to each message that is 160/* written to a dump file (see the dump file format description 161/* below). The following C escape sequences are supported: \ea 162/* (bell), \eb (backspace), \ef (formfeed), \en (newline), \er 163/* (carriage return), \et (horizontal tab), \ev (vertical tab), 164/* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash 165/* character). 166/* .IP "\fB-t \fItimeout\fR (default: 100)" 167/* Limit the time for receiving a command or sending a response. 168/* The time limit is specified in seconds. 169/* .IP "\fB-T \fIwindowsize\fR" 170/* Override the default TCP window size. To work around 171/* broken TCP window scaling implementations, specify a 172/* value > 0 and < 65536. 173/* .IP "\fB-u \fIusername\fR" 174/* Switch to the specified user privileges after opening the 175/* network socket and optionally changing the process root 176/* directory. This option is required when the process runs 177/* with super-user privileges. See also the \fB-R\fR option. 178/* .IP \fB-v\fR 179/* Show the SMTP conversations. 180/* .IP "\fB-w \fIdelay\fR" 181/* Wait \fIdelay\fR seconds before responding to a DATA command. 182/* .IP "\fB-W \fIcommand:delay[:odds]\fR" 183/* Wait \fIdelay\fR seconds before responding to \fIcommand\fR. 184/* If \fIodds\fR is also specified (a number between 1-99 185/* inclusive), wait for a random multiple of \fIdelay\fR. The 186/* random multiplier is equal to the number of times the program 187/* needs to roll a dice with a range of 0..99 inclusive, before 188/* the dice produces a result greater than or equal to \fIodds\fR. 189/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR 190/* Listen on network interface \fIhost\fR (default: any interface) 191/* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be 192/* specified in numeric or symbolic form. 193/* .IP \fBunix:\fR\fIpathname\fR 194/* Listen on the UNIX-domain socket at \fIpathname\fR. 195/* .IP \fIbacklog\fR 196/* The maximum length of the queue of pending connections, 197/* as defined by the \fBlisten\fR(2) system call. 198/* DUMP FILE FORMAT 199/* .ad 200/* .fi 201/* Each dumped message contains a sequence of text lines, 202/* terminated with the newline character. The sequence of 203/* information is as follows: 204/* .IP \(bu 205/* The optional string specified with the \fB-S\fR option. 206/* .IP \(bu 207/* The \fBsmtp-sink\fR generated headers as documented below. 208/* .IP \(bu 209/* The message header and body as received from the SMTP client. 210/* .IP \(bu 211/* An empty line. 212/* .PP 213/* The format of the \fBsmtp-sink\fR generated headers is as 214/* follows: 215/* .IP "\fBX-Client-Addr: \fItext\fR" 216/* The client IP address without enclosing []. An IPv6 address 217/* is prefixed with "ipv6:". This record is always present. 218/* .IP "\fBX-Client-Proto: \fItext\fR" 219/* The client protocol: SMTP, ESMTP or LMTP. This record is 220/* always present. 221/* .IP "\fBX-Helo-Args: \fItext\fR" 222/* The arguments of the last HELO or EHLO command before this 223/* mail delivery transaction. This record is present only if 224/* the client sent a recognizable HELO or EHLO command before 225/* the DATA command. 226/* .IP "\fBX-Mail-Args: \fItext\fR" 227/* The arguments of the MAIL command that started this mail 228/* delivery transaction. This record is present exactly once. 229/* .IP "\fBX-Rcpt-Args: \fItext\fR" 230/* The arguments of an RCPT command within this mail delivery 231/* transaction. There is one record for each RCPT command, and 232/* they are in the order as sent by the client. 233/* .IP "\fBReceived: \fItext\fR" 234/* A message header for compatibility with mail processing 235/* software. This three-line header marks the end of the headers 236/* provided by \fBsmtp-sink\fR, and is formatted as follows: 237/* .RS 238/* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])" 239/* The HELO or EHLO command argument and client IP address. 240/* If the client did not send HELO or EHLO, the client IP 241/* address is used instead. 242/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR" 243/* The hostname specified with the \fB-h\fR option, the client 244/* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random 245/* portion of the per-message capture file name. 246/* .IP \fItime-stamp\fR 247/* A time stamp as defined in RFC 2822. 248/* .RE 249/* SEE ALSO 250/* smtp-source(1), SMTP/LMTP message generator 251/* LICENSE 252/* .ad 253/* .fi 254/* The Secure Mailer license must be distributed with this software. 255/* AUTHOR(S) 256/* Wietse Venema 257/* IBM T.J. Watson Research 258/* P.O. Box 704 259/* Yorktown Heights, NY 10598, USA 260/* 261/* Wietse Venema 262/* Google, Inc. 263/* 111 8th Avenue 264/* New York, NY 10011, USA 265/*--*/ 266 267/* System library. */ 268 269#include <sys_defs.h> 270#include <sys/socket.h> 271#include <sys/wait.h> 272#include <sys/stat.h> 273#include <unistd.h> 274#include <string.h> 275#include <stdlib.h> 276#include <fcntl.h> 277#include <syslog.h> 278#include <signal.h> 279#include <time.h> 280#include <ctype.h> 281 282#ifdef STRCASECMP_IN_STRINGS_H 283#include <strings.h> 284#endif 285 286/* Utility library. */ 287 288#include <msg.h> 289#include <vstring.h> 290#include <vstream.h> 291#include <vstring_vstream.h> 292#include <get_hostname.h> 293#include <listen.h> 294#include <events.h> 295#include <mymalloc.h> 296#include <iostuff.h> 297#include <msg_vstream.h> 298#include <stringops.h> 299#include <sane_accept.h> 300#include <inet_proto.h> 301#include <myaddrinfo.h> 302#include <make_dirs.h> 303#include <myrand.h> 304#include <chroot_uid.h> 305 306/* Global library. */ 307 308#include <smtp_stream.h> 309#include <mail_date.h> 310#include <mail_version.h> 311 312/* Application-specific. */ 313 314typedef struct SINK_STATE { 315 VSTREAM *stream; 316 VSTRING *buffer; 317 int data_state; 318 int (*read_fn) (struct SINK_STATE *); 319 int in_mail; 320 int rcpts; 321 char *push_back_ptr; 322 /* Capture file information for fake Received: header */ 323 MAI_HOSTADDR_STR client_addr; /* IP address */ 324 char *addr_prefix; /* ipv6: or empty */ 325 char *helo_args; /* text after HELO or EHLO */ 326 const char *client_proto; /* SMTP, ESMTP, LMTP */ 327 time_t start_time; /* MAIL command time */ 328 int id; /* pseudo-random */ 329 VSTREAM *dump_file; /* dump file or null */ 330 void (*delayed_response) (struct SINK_STATE *state, const char *); 331 char *delayed_args; 332} SINK_STATE; 333 334#define ST_ANY 0 335#define ST_CR 1 336#define ST_CR_LF 2 337#define ST_CR_LF_DOT 3 338#define ST_CR_LF_DOT_CR 4 339#define ST_CR_LF_DOT_CR_LF 5 340 341#define PUSH_BACK_PEEK(state) (*(state)->push_back_ptr != 0) 342#define PUSH_BACK_GET(state) (*(state)->push_back_ptr++) 343#define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text)) 344 345#ifndef DEF_MAX_CLIENT_COUNT 346#define DEF_MAX_CLIENT_COUNT 256 347#endif 348 349#define SOFT_ERROR_RESP "450 4.3.0 Error: command failed" 350#define HARD_ERROR_RESP "500 5.3.0 Error: command failed" 351 352 /* 353 * We can't rely on vstream auto-flushing, so we have to prepare for the 354 * next read request. 355 */ 356#define SMTP_FLUSH(fp) do { \ 357 if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \ 358 smtp_flush(fp); \ 359 } while (0) 360 361static int var_tmout = 100; 362static int var_max_line_length = 2048; 363static char *var_myhostname; 364static char *soft_error_resp = SOFT_ERROR_RESP; 365static char *hard_error_resp = HARD_ERROR_RESP; 366static int command_read(SINK_STATE *); 367static int data_read(SINK_STATE *); 368static void disconnect(SINK_STATE *); 369static void read_timeout(int, void *); 370static void read_event(int, void *); 371static int show_count; 372static int sess_count; 373static int quit_count; 374static int mesg_count; 375static int max_quit_count; 376static int max_msg_quit_count; 377static int disable_pipelining; 378static int disable_8bitmime; 379static int disable_esmtp; 380static int enable_lmtp; 381static int pretend_pix; 382static int disable_saslauth; 383static int disable_xclient; 384static int disable_xforward; 385static int disable_enh_status; 386static int disable_dsn; 387static int max_client_count = DEF_MAX_CLIENT_COUNT; 388static int client_count; 389static int sock; 390static int abort_delay = -1; 391static int data_read_delay = 0; 392 393static char *single_template; /* individual template */ 394static char *shared_template; /* shared template */ 395static VSTRING *start_string; /* dump content prefix */ 396 397static const INET_PROTO_INFO *proto_info; 398 399#define STR(x) vstring_str(x) 400 401/* do_stats - show counters */ 402 403static void do_stats(void) 404{ 405 vstream_printf("sess=%d quit=%d mesg=%d\r", 406 sess_count, quit_count, mesg_count); 407 vstream_fflush(VSTREAM_OUT); 408} 409 410/* hard_err_resp - generic hard error response */ 411 412static void hard_err_resp(SINK_STATE *state) 413{ 414 smtp_printf(state->stream, "%s", hard_error_resp); 415 SMTP_FLUSH(state->stream); 416} 417 418/* soft_err_resp - generic soft error response */ 419 420static void soft_err_resp(SINK_STATE *state) 421{ 422 smtp_printf(state->stream, "%s", soft_error_resp); 423 SMTP_FLUSH(state->stream); 424} 425 426/* exp_path_template - expand template pathname, static result */ 427 428static VSTRING *exp_path_template(const char *template, time_t start_time) 429{ 430 static VSTRING *path_buf = 0; 431 struct tm *lt; 432 433 if (path_buf == 0) 434 path_buf = vstring_alloc(100); 435 else 436 VSTRING_RESET(path_buf); 437 lt = localtime(&start_time); 438 while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0) 439 VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100); 440 VSTRING_SKIP(path_buf); 441 return (path_buf); 442} 443 444/* make_parent_dir - create parent directory or bust */ 445 446static void make_parent_dir(const char *path, mode_t mode) 447{ 448 const char *parent; 449 450 parent = sane_dirname((VSTRING *) 0, path); 451 if (make_dirs(parent, mode) < 0) 452 msg_fatal("mkdir %s: %m", parent); 453} 454 455/* mail_file_open - open mail capture file */ 456 457static void mail_file_open(SINK_STATE *state) 458{ 459 const char *myname = "mail_file_open"; 460 VSTRING *path_buf; 461 ssize_t len; 462 int tries = 0; 463 464 /* 465 * Save the start time for later. 466 */ 467 time(&(state->start_time)); 468 469 /* 470 * Expand the per-message dumpfile pathname template. 471 */ 472 path_buf = exp_path_template(single_template, state->start_time); 473 474 /* 475 * Append a random hexadecimal string to the pathname and create a new 476 * file. Retry with a different path if the file already exists. Create 477 * intermediate directories on the fly when the template specifies 478 * multiple pathname segments. 479 */ 480#define ID_FORMAT "%08x" 481 482 for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) { 483 if (++tries > 100) 484 msg_fatal("%s: something is looping", myname); 485 state->id = myrand(); 486 vstring_sprintf_append(path_buf, ID_FORMAT, state->id); 487 if ((state->dump_file = vstream_fopen(STR(path_buf), 488 O_RDWR | O_CREAT | O_EXCL, 489 0644)) != 0) { 490 break; 491 } else if (errno == EEXIST) { 492 continue; 493 } else if (errno == ENOENT) { 494 make_parent_dir(STR(path_buf), 0755); 495 continue; 496 } else { 497 msg_fatal("open %s: %m", STR(path_buf)); 498 } 499 } 500 501 /* 502 * Don't leave temporary files behind. 503 */ 504 if (shared_template != 0 && unlink(STR(path_buf)) < 0) 505 msg_fatal("unlink %s: %m", STR(path_buf)); 506 507 /* 508 * Do initial header records. 509 */ 510 if (start_string) 511 vstream_fprintf(state->dump_file, "%s", STR(start_string)); 512 vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n", 513 state->addr_prefix, state->client_addr.buf); 514 vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto); 515 if (state->helo_args) 516 vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args); 517 /* Note: there may be more than one recipient. */ 518} 519 520/* mail_file_finish_header - do final smtp-sink generated header records */ 521 522static void mail_file_finish_header(SINK_STATE *state) 523{ 524 if (state->helo_args) 525 vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n", 526 state->helo_args, state->addr_prefix, 527 state->client_addr.buf); 528 else 529 vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n", 530 state->addr_prefix, state->client_addr.buf, 531 state->addr_prefix, state->client_addr.buf); 532 vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)" 533 " with %s id " ID_FORMAT ";\n", 534 var_myhostname, state->client_proto, state->id); 535 vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time)); 536} 537 538/* mail_file_cleanup - common cleanup for capture file */ 539 540static void mail_file_cleanup(SINK_STATE *state) 541{ 542 (void) vstream_fclose(state->dump_file); 543 state->dump_file = 0; 544} 545 546/* mail_file_finish - handle message completion for capture file */ 547 548static void mail_file_finish(SINK_STATE *state) 549{ 550 551 /* 552 * Optionally append the captured message to a shared dumpfile. 553 */ 554 if (shared_template) { 555 const char *out_path; 556 VSTREAM *out_fp; 557 ssize_t count; 558 559 /* 560 * Expand the shared dumpfile pathname template. 561 */ 562 out_path = STR(exp_path_template(shared_template, state->start_time)); 563 564 /* 565 * Open the shared dump file. 566 */ 567#define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND) 568#define OUT_OPEN_MODE 0644 569 570 if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE)) 571 == 0 && errno == ENOENT) { 572 make_parent_dir(out_path, 0755); 573 out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE); 574 } 575 if (out_fp == 0) 576 msg_fatal("open %s: %m", out_path); 577 578 /* 579 * Append message content from single-message dump file. 580 */ 581 if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0) 582 msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file)); 583 VSTRING_RESET(state->buffer); 584 for (;;) { 585 count = vstream_fread(state->dump_file, STR(state->buffer), 586 vstring_avail(state->buffer)); 587 if (count <= 0) 588 break; 589 if (vstream_fwrite(out_fp, STR(state->buffer), count) != count) 590 msg_fatal("append file %s: %m", out_path); 591 } 592 if (vstream_ferror(state->dump_file)) 593 msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file)); 594 if (vstream_fclose(out_fp)) 595 msg_fatal("append file %s: %m", out_path); 596 } 597 mail_file_cleanup(state); 598} 599 600/* mail_file_reset - abort mail to capture file */ 601 602static void mail_file_reset(SINK_STATE *state) 603{ 604 if (shared_template == 0 605 && unlink(VSTREAM_PATH(state->dump_file)) < 0 606 && errno != ENOENT) 607 msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file)); 608 mail_file_cleanup(state); 609} 610 611/* mail_cmd_reset - reset mail transaction information */ 612 613static void mail_cmd_reset(SINK_STATE *state) 614{ 615 state->in_mail = 0; 616 /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */ 617 if (state->dump_file) 618 mail_file_reset(state); 619} 620 621/* ehlo_response - respond to EHLO command */ 622 623static void ehlo_response(SINK_STATE *state, const char *args) 624{ 625#define SKIP(cp, cond) do { \ 626 for (/* void */; *cp && (cond); cp++) \ 627 /* void */; \ 628 } while (0) 629 630 /* EHLO aborts a mail transaction in progress. */ 631 mail_cmd_reset(state); 632 if (enable_lmtp == 0) 633 state->client_proto = "ESMTP"; 634 smtp_printf(state->stream, "250-%s", var_myhostname); 635 if (!disable_pipelining) 636 smtp_printf(state->stream, "250-PIPELINING"); 637 if (!disable_8bitmime) 638 smtp_printf(state->stream, "250-8BITMIME"); 639 if (!disable_saslauth) 640 smtp_printf(state->stream, "250-AUTH PLAIN LOGIN"); 641 if (!disable_xclient) 642 smtp_printf(state->stream, "250-XCLIENT NAME HELO"); 643 if (!disable_xforward) 644 smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO"); 645 if (!disable_enh_status) 646 smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES"); 647 if (!disable_dsn) 648 smtp_printf(state->stream, "250-DSN"); 649 /* RFC 821/2821/5321: Format is replycode<SPACE>optional-text<CRLF> */ 650 smtp_printf(state->stream, "250 "); 651 SMTP_FLUSH(state->stream); 652 if (single_template) { 653 if (state->helo_args) 654 myfree(state->helo_args); 655 SKIP(args, ISSPACE(*args)); 656 state->helo_args = mystrdup(args); 657 } 658} 659 660/* helo_response - respond to HELO command */ 661 662static void helo_response(SINK_STATE *state, const char *args) 663{ 664 /* HELO aborts a mail transaction in progress. */ 665 mail_cmd_reset(state); 666 state->client_proto = "SMTP"; 667 smtp_printf(state->stream, "250 %s", var_myhostname); 668 SMTP_FLUSH(state->stream); 669 if (single_template) { 670 if (state->helo_args) 671 myfree(state->helo_args); 672 SKIP(args, ISSPACE(*args)); 673 state->helo_args = mystrdup(args); 674 } 675} 676 677/* ok_response - send 250 OK */ 678 679static void ok_response(SINK_STATE *state, const char *unused_args) 680{ 681 smtp_printf(state->stream, "250 2.0.0 Ok"); 682 SMTP_FLUSH(state->stream); 683} 684 685/* rset_response - reset, send 250 OK */ 686 687static void rset_response(SINK_STATE *state, const char *unused_args) 688{ 689 mail_cmd_reset(state); 690 smtp_printf(state->stream, "250 2.1.0 Ok"); 691 SMTP_FLUSH(state->stream); 692} 693 694/* mail_response - reset recipient count, send 250 OK */ 695 696static void mail_response(SINK_STATE *state, const char *args) 697{ 698 if (state->in_mail) { 699 smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command"); 700 SMTP_FLUSH(state->stream); 701 return; 702 } 703 state->in_mail++; 704 state->rcpts = 0; 705 smtp_printf(state->stream, "250 2.1.0 Ok"); 706 SMTP_FLUSH(state->stream); 707 if (single_template) { 708 mail_file_open(state); 709 SKIP(args, *args != ':'); 710 SKIP(args, *args == ':'); 711 SKIP(args, ISSPACE(*args)); 712 vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args); 713 } 714} 715 716/* rcpt_response - bump recipient count, send 250 OK */ 717 718static void rcpt_response(SINK_STATE *state, const char *args) 719{ 720 if (state->in_mail == 0) { 721 smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command"); 722 SMTP_FLUSH(state->stream); 723 return; 724 } 725 state->rcpts++; 726 smtp_printf(state->stream, "250 2.1.5 Ok"); 727 SMTP_FLUSH(state->stream); 728 /* Note: there may be more than one recipient per mail transaction. */ 729 if (state->dump_file) { 730 SKIP(args, *args != ':'); 731 SKIP(args, *args == ':'); 732 SKIP(args, ISSPACE(*args)); 733 vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args); 734 } 735} 736 737/* abort_event - delayed abort after DATA command */ 738 739static void abort_event(int unused_event, void *context) 740{ 741 SINK_STATE *state = (SINK_STATE *) context; 742 743 smtp_printf(state->stream, "550 This violates SMTP"); 744 SMTP_FLUSH(state->stream); 745 disconnect(state); 746} 747 748/* delay_read_event - resume input event handling */ 749 750static void delay_read_event(int event, void *context) 751{ 752 SINK_STATE *state = (SINK_STATE *) context; 753 754 if (event != EVENT_TIME) 755 msg_panic("delay_read_event: non-timer event %d", event); 756 757 event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); 758 event_request_timer(read_timeout, (void *) state, var_tmout); 759} 760 761/* delay_read - temporarily suspend input event handling */ 762 763static void delay_read(SINK_STATE *state, int delay) 764{ 765 event_disable_readwrite(vstream_fileno(state->stream)); 766 event_cancel_timer(read_timeout, (void *) state); 767 event_request_timer(delay_read_event, (void *) state, delay); 768} 769 770/* data_response - respond to DATA command */ 771 772static void data_response(SINK_STATE *state, const char *unused_args) 773{ 774 if (state->in_mail == 0 || state->rcpts == 0) { 775 smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command"); 776 SMTP_FLUSH(state->stream); 777 return; 778 } 779 /* Not: ST_ANY. */ 780 state->data_state = ST_CR_LF; 781 smtp_printf(state->stream, "354 End data with <CR><LF>.<CR><LF>"); 782 SMTP_FLUSH(state->stream); 783 if (abort_delay < 0) { 784 state->read_fn = data_read; 785 /* Todo: move into code that invokes the command response function. */ 786 if (data_read_delay > 0) 787 delay_read(state, data_read_delay); 788 } else { 789 /* Stop reading, send premature 550, and disconnect. */ 790 event_disable_readwrite(vstream_fileno(state->stream)); 791 event_cancel_timer(read_event, (void *) state); 792 event_request_timer(abort_event, (void *) state, abort_delay); 793 } 794 if (state->dump_file) 795 mail_file_finish_header(state); 796} 797 798/* dot_resp_hard - hard error response to . command */ 799 800static void dot_resp_hard(SINK_STATE *state) 801{ 802 if (enable_lmtp) { 803 while (state->rcpts-- > 0) /* XXX this could block */ 804 smtp_printf(state->stream, "%s", hard_error_resp); 805 } else { 806 smtp_printf(state->stream, "%s", hard_error_resp); 807 } 808 SMTP_FLUSH(state->stream); 809} 810 811/* dot_resp_soft - soft error response to . command */ 812 813static void dot_resp_soft(SINK_STATE *state) 814{ 815 if (enable_lmtp) { 816 while (state->rcpts-- > 0) /* XXX this could block */ 817 smtp_printf(state->stream, "%s", soft_error_resp); 818 } else { 819 smtp_printf(state->stream, "%s", soft_error_resp); 820 } 821 SMTP_FLUSH(state->stream); 822} 823 824/* dot_response - response to . command */ 825 826static void dot_response(SINK_STATE *state, const char *unused_args) 827{ 828 if (enable_lmtp) { 829 while (state->rcpts-- > 0) /* XXX this could block */ 830 smtp_printf(state->stream, "250 2.2.0 Ok"); 831 } else { 832 smtp_printf(state->stream, "250 2.0.0 Ok"); 833 } 834 SMTP_FLUSH(state->stream); 835} 836 837/* quit_response - respond to QUIT command */ 838 839static void quit_response(SINK_STATE *state, const char *unused_args) 840{ 841 smtp_printf(state->stream, "221 Bye"); 842 smtp_flush(state->stream); /* not: SMTP_FLUSH */ 843 if (show_count) 844 quit_count++; 845} 846 847/* conn_response - respond to connect command */ 848 849static void conn_response(SINK_STATE *state, const char *unused_args) 850{ 851 if (pretend_pix) 852 smtp_printf(state->stream, "220 ********"); 853 else if (disable_esmtp) 854 smtp_printf(state->stream, "220 %s", var_myhostname); 855 else 856 smtp_printf(state->stream, "220 %s ESMTP", var_myhostname); 857 SMTP_FLUSH(state->stream); 858} 859 860/* delay_event - delayed command response */ 861 862static void delay_event(int unused_event, void *context) 863{ 864 SINK_STATE *state = (SINK_STATE *) context; 865 866 switch (vstream_setjmp(state->stream)) { 867 868 default: 869 msg_panic("unknown read/write error"); 870 /* NOTREACHED */ 871 872 case SMTP_ERR_TIME: 873 msg_warn("write timeout"); 874 disconnect(state); 875 return; 876 877 case SMTP_ERR_EOF: 878 msg_warn("lost connection"); 879 disconnect(state); 880 return; 881 882 case 0: 883 state->delayed_response(state, state->delayed_args); 884 myfree(state->delayed_args); 885 state->delayed_args = 0; 886 break; 887 } 888 889 if (state->delayed_response == quit_response) { 890 disconnect(state); 891 return; 892 } 893 state->delayed_response = 0; 894 895 /* Resume input event handling after the delayed response. */ 896 event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); 897 event_request_timer(read_timeout, (void *) state, var_tmout); 898} 899 900/* data_read - read data from socket */ 901 902static int data_read(SINK_STATE *state) 903{ 904 int ch; 905 struct data_trans { 906 int state; 907 int want; 908 int next_state; 909 }; 910 static struct data_trans data_trans[] = { 911 ST_ANY, '\r', ST_CR, 912 ST_CR, '\n', ST_CR_LF, 913 ST_CR_LF, '.', ST_CR_LF_DOT, 914 ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR, 915 ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF, 916 }; 917 struct data_trans *dp; 918 919 /* 920 * A read may result in EOF, but is never supposed to time out - a time 921 * out means that we were trying to read when no data was available. 922 */ 923 for (;;) { 924 if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) 925 return (-1); 926 for (dp = data_trans; dp->state != state->data_state; dp++) 927 /* void */ ; 928 929 /* 930 * Try to match the current character desired by the state machine. 931 * If that fails, try to restart the machine with a match for its 932 * first state. This covers the case of a CR/LF/CR/LF sequence 933 * (empty line) right before the end of the message data. 934 */ 935 if (ch == dp->want) 936 state->data_state = dp->next_state; 937 else if (ch == data_trans[0].want) 938 state->data_state = data_trans[0].next_state; 939 else 940 state->data_state = ST_ANY; 941 if (state->dump_file) { 942 if (ch != '\r' && state->data_state != ST_CR_LF_DOT) 943 VSTREAM_PUTC(ch, state->dump_file); 944 if (vstream_ferror(state->dump_file)) 945 msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file)); 946 } 947 if (state->data_state == ST_CR_LF_DOT_CR_LF) { 948 PUSH_BACK_SET(state, ".\r\n"); 949 state->read_fn = command_read; 950 state->data_state = ST_ANY; 951 if (state->dump_file) 952 mail_file_finish(state); 953 mail_cmd_reset(state); 954 if (show_count || max_msg_quit_count > 0) { 955 mesg_count++; 956 if (show_count) 957 do_stats(); 958 if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count) 959 exit(0); 960 } 961 break; 962 } 963 964 /* 965 * We must avoid blocking I/O, so get out of here as soon as both the 966 * VSTREAM and kernel read buffers dry up. 967 */ 968 if (vstream_peek(state->stream) <= 0 969 && readable(vstream_fileno(state->stream)) <= 0) 970 return (0); 971 } 972 return (0); 973} 974 975 /* 976 * The table of all SMTP commands that we can handle. 977 */ 978typedef struct SINK_COMMAND { 979 const char *name; 980 void (*response) (SINK_STATE *, const char *); 981 void (*hard_response) (SINK_STATE *); 982 void (*soft_response) (SINK_STATE *); 983 int flags; 984 int delay; 985 int delay_odds; 986} SINK_COMMAND; 987 988#define FLAG_ENABLE (1<<0) /* command is enabled */ 989#define FLAG_SYSLOG (1<<1) /* log the command */ 990#define FLAG_HARD_ERR (1<<2) /* report hard error */ 991#define FLAG_SOFT_ERR (1<<3) /* report soft error */ 992#define FLAG_DISCONNECT (1<<4) /* disconnect */ 993#define FLAG_CLOSE (1<<5) /* say goodbye and disconnect */ 994 995static SINK_COMMAND command_table[] = { 996 "connect", conn_response, hard_err_resp, soft_err_resp, 0, 0, 0, 997 "helo", helo_response, hard_err_resp, soft_err_resp, 0, 0, 0, 998 "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, 999 "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, 1000 "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1001 "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1002 "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1003 "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1004 "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1005 "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1006 ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 0, 0, 1007 "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1008 "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1009 "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1010 "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 1011 0, 1012}; 1013 1014/* reset_cmd_flags - reset per-command command flags */ 1015 1016static void reset_cmd_flags(const char *cmd, int flags) 1017{ 1018 SINK_COMMAND *cmdp; 1019 1020 for (cmdp = command_table; cmdp->name != 0; cmdp++) 1021 if (strcasecmp(cmd, cmdp->name) == 0) 1022 break; 1023 if (cmdp->name == 0) 1024 msg_fatal("unknown command: %s", cmd); 1025 cmdp->flags &= ~flags; 1026} 1027 1028/* set_cmd_flags - set per-command command flags */ 1029 1030static void set_cmd_flags(const char *cmd, int flags) 1031{ 1032 SINK_COMMAND *cmdp; 1033 1034 for (cmdp = command_table; cmdp->name != 0; cmdp++) 1035 if (strcasecmp(cmd, cmdp->name) == 0) 1036 break; 1037 if (cmdp->name == 0) 1038 msg_fatal("unknown command: %s", cmd); 1039 cmdp->flags |= flags; 1040} 1041 1042/* set_cmds_flags - set per-command flags for multiple commands */ 1043 1044static void set_cmds_flags(const char *cmds, int flags) 1045{ 1046 char *saved_cmds; 1047 char *cp; 1048 char *cmd; 1049 1050 saved_cmds = cp = mystrdup(cmds); 1051 while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0) 1052 set_cmd_flags(cmd, flags); 1053 myfree(saved_cmds); 1054} 1055 1056/* set_cmd_delay - set per-command delay */ 1057 1058static void set_cmd_delay(const char *cmd, int delay, int odds) 1059{ 1060 SINK_COMMAND *cmdp; 1061 1062 for (cmdp = command_table; cmdp->name != 0; cmdp++) 1063 if (strcasecmp(cmd, cmdp->name) == 0) 1064 break; 1065 if (cmdp->name == 0) 1066 msg_fatal("unknown command: %s", cmd); 1067 1068 if (delay <= 0) 1069 msg_fatal("non-positive '%s' delay", cmd); 1070 if (odds < 0 || odds > 99) 1071 msg_fatal("delay odds for '%s' out of range", cmd); 1072 1073 cmdp->delay = delay; 1074 cmdp->delay_odds = odds; 1075} 1076 1077/* set_cmd_delay_arg - set per-command delay from option argument */ 1078 1079static void set_cmd_delay_arg(char *arg) 1080{ 1081 char *cp; 1082 char *saved_arg; 1083 char *cmd; 1084 char *delay; 1085 char *odds; 1086 1087 saved_arg = cp = mystrdup(arg); 1088 cmd = mystrtok(&cp, ":"); 1089 delay = mystrtok(&cp, ":"); 1090 if (cmd == 0 || delay == 0) 1091 msg_fatal("invalid command delay argument: %s", arg); 1092 odds = mystrtok(&cp, ""); 1093 set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0); 1094 myfree(saved_arg); 1095} 1096 1097/* command_resp - respond to command */ 1098 1099static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp, 1100 const char *command, const char *args) 1101{ 1102 /* We use raw syslog. Sanitize data content and length. */ 1103 if (cmdp->flags & FLAG_SYSLOG) 1104 syslog(LOG_INFO, "%s %.100s", command, args); 1105 if (cmdp->flags & FLAG_DISCONNECT) 1106 return (-1); 1107 if (cmdp->flags & FLAG_CLOSE) { 1108 smtp_printf(state->stream, "421 4.0.0 Server closing connection"); 1109 return (-1); 1110 } 1111 if (cmdp->flags & FLAG_HARD_ERR) { 1112 cmdp->hard_response(state); 1113 return (0); 1114 } 1115 if (cmdp->flags & FLAG_SOFT_ERR) { 1116 cmdp->soft_response(state); 1117 return (0); 1118 } 1119 if (cmdp->delay > 0) { 1120 int delay = cmdp->delay; 1121 1122 if (cmdp->delay_odds > 0) 1123 for (delay = 0; 1124 ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds; 1125 delay += cmdp->delay) 1126 /* NOP */ ; 1127 /* Suspend input event handling while delaying the command response. */ 1128 event_disable_readwrite(vstream_fileno(state->stream)); 1129 event_cancel_timer(read_timeout, (void *) state); 1130 event_request_timer(delay_event, (void *) state, delay); 1131 state->delayed_response = cmdp->response; 1132 state->delayed_args = mystrdup(args); 1133 } else { 1134 cmdp->response(state, args); 1135 if (cmdp->response == quit_response) 1136 return (-1); 1137 } 1138 return (0); 1139} 1140 1141/* command_read - talk the SMTP protocol, server side */ 1142 1143static int command_read(SINK_STATE *state) 1144{ 1145 char *command; 1146 SINK_COMMAND *cmdp; 1147 int ch; 1148 struct cmd_trans { 1149 int state; 1150 int want; 1151 int next_state; 1152 }; 1153 static struct cmd_trans cmd_trans[] = { 1154 ST_ANY, '\r', ST_CR, 1155 ST_CR, '\n', ST_CR_LF, 1156 0, 0, 0, 1157 }; 1158 struct cmd_trans *cp; 1159 char *ptr; 1160 1161 /* 1162 * A read may result in EOF, but is never supposed to time out - a time 1163 * out means that we were trying to read when no data was available. 1164 */ 1165#define NEXT_CHAR(state) \ 1166 (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream)) 1167 1168 if (state->data_state == ST_CR_LF) 1169 state->data_state = ST_ANY; /* XXX */ 1170 for (;;) { 1171 if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF) 1172 return (-1); 1173 1174 /* 1175 * Sanity check. We don't want to store infinitely long commands. 1176 */ 1177 if (VSTRING_LEN(state->buffer) >= var_max_line_length) { 1178 msg_warn("command line too long"); 1179 return (-1); 1180 } 1181 VSTRING_ADDCH(state->buffer, ch); 1182 1183 /* 1184 * Try to match the current character desired by the state machine. 1185 * If that fails, try to restart the machine with a match for its 1186 * first state. 1187 */ 1188 for (cp = cmd_trans; cp->state != state->data_state; cp++) 1189 if (cp->want == 0) 1190 msg_panic("command_read: unknown state: %d", state->data_state); 1191 if (ch == cp->want) 1192 state->data_state = cp->next_state; 1193 else if (ch == cmd_trans[0].want) 1194 state->data_state = cmd_trans[0].next_state; 1195 else 1196 state->data_state = ST_ANY; 1197 if (state->data_state == ST_CR_LF) 1198 break; 1199 1200 /* 1201 * We must avoid blocking I/O, so get out of here as soon as both the 1202 * VSTREAM and kernel read buffers dry up. 1203 * 1204 * XXX Solaris non-blocking read() may fail on a socket when ioctl 1205 * FIONREAD reports there is unread data. Diagnosis by Max Pashkov. 1206 * As a workaround we use readable() (which uses poll or select()) 1207 * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added 1208 * 20020604. 1209 */ 1210 if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0 1211 && readable(vstream_fileno(state->stream)) <= 0) 1212 return (0); 1213 } 1214 1215 /* 1216 * Properly terminate the result, and reset the buffer write pointer for 1217 * reading the next command. This is ugly, but not as ugly as trying to 1218 * deal with all the early returns below. 1219 */ 1220 vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2); 1221 VSTRING_TERMINATE(state->buffer); 1222 state->data_state = ST_CR_LF; 1223 VSTRING_RESET(state->buffer); 1224 1225 /* 1226 * Got a complete command line. Parse it. 1227 */ 1228 ptr = vstring_str(state->buffer); 1229 if (msg_verbose) 1230 msg_info("%s", ptr); 1231 if ((command = mystrtok(&ptr, " \t")) == 0) { 1232 smtp_printf(state->stream, "500 5.5.2 Error: unknown command"); 1233 SMTP_FLUSH(state->stream); 1234 return (0); 1235 } 1236 for (cmdp = command_table; cmdp->name != 0; cmdp++) 1237 if (strcasecmp(command, cmdp->name) == 0) 1238 break; 1239 if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) { 1240 smtp_printf(state->stream, "500 5.5.1 Error: unknown command"); 1241 SMTP_FLUSH(state->stream); 1242 return (0); 1243 } 1244 return (command_resp(state, cmdp, command, printable(ptr, '?'))); 1245} 1246 1247/* read_timeout - handle timer event */ 1248 1249static void read_timeout(int unused_event, void *context) 1250{ 1251 SINK_STATE *state = (SINK_STATE *) context; 1252 1253 /* 1254 * We don't send anything to the client, because we would have to set up 1255 * an smtp_stream exception handler first. And that is just too much 1256 * trouble. 1257 */ 1258 msg_warn("read timeout"); 1259 disconnect(state); 1260} 1261 1262/* read_event - handle command or data read events */ 1263 1264static void read_event(int unused_event, void *context) 1265{ 1266 SINK_STATE *state = (SINK_STATE *) context; 1267 1268 /* 1269 * The input reading routine not only reads input (with vstream calls) 1270 * but also produces output (with smtp_stream calls). Because the output 1271 * routines can raise timeout or EOF exceptions with vstream_longjmp(), 1272 * the input reading routine needs to set up corresponding exception 1273 * handlers with vstream_setjmp(). Guarding the input operations in the 1274 * same manner is not useful: we must read input in non-blocking mode, so 1275 * we never get called when the socket stays unreadable too long. And EOF 1276 * is already trivial to detect with the vstream calls. 1277 */ 1278 do { 1279 switch (vstream_setjmp(state->stream)) { 1280 1281 default: 1282 msg_panic("unknown read/write error"); 1283 /* NOTREACHED */ 1284 1285 case SMTP_ERR_TIME: 1286 msg_warn("write timeout"); 1287 disconnect(state); 1288 return; 1289 1290 case SMTP_ERR_EOF: 1291 msg_warn("lost connection"); 1292 disconnect(state); 1293 return; 1294 1295 case 0: 1296 if (state->read_fn(state) < 0) { 1297 if (msg_verbose) 1298 msg_info("disconnect"); 1299 disconnect(state); 1300 return; 1301 } 1302 } 1303 } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0); 1304 1305 /* 1306 * Reset the idle timer. Wait until the next input event, or until the 1307 * idle timer goes off. 1308 */ 1309 event_request_timer(read_timeout, (void *) state, var_tmout); 1310} 1311 1312static void connect_event(int, void *); 1313 1314/* disconnect - handle disconnection events */ 1315 1316static void disconnect(SINK_STATE *state) 1317{ 1318 event_disable_readwrite(vstream_fileno(state->stream)); 1319 event_cancel_timer(read_timeout, (void *) state); 1320 if (show_count) { 1321 sess_count++; 1322 do_stats(); 1323 } 1324 vstream_fclose(state->stream); 1325 vstring_free(state->buffer); 1326 /* Clean up file capture attributes. */ 1327 if (state->helo_args) 1328 myfree(state->helo_args); 1329 /* Delete incomplete mail transaction. */ 1330 mail_cmd_reset(state); 1331 if (state->delayed_args) 1332 myfree(state->delayed_args); 1333 myfree((void *) state); 1334 if (max_quit_count > 0 && quit_count >= max_quit_count) 1335 exit(0); 1336 if (client_count-- == max_client_count) 1337 event_enable_read(sock, connect_event, (void *) 0); 1338} 1339 1340/* connect_event - handle connection events */ 1341 1342static void connect_event(int unused_event, void *unused_context) 1343{ 1344 struct sockaddr_storage ss; 1345 SOCKADDR_SIZE len = sizeof(ss); 1346 struct sockaddr *sa = (struct sockaddr *) &ss; 1347 SINK_STATE *state; 1348 int fd; 1349 1350 if ((fd = sane_accept(sock, sa, &len)) >= 0) { 1351 /* Safety: limit the number of open sockets and capture files. */ 1352 if (++client_count == max_client_count) 1353 event_disable_readwrite(sock); 1354 state = (SINK_STATE *) mymalloc(sizeof(*state)); 1355 if (strchr((char *) proto_info->sa_family_list, sa->sa_family)) 1356 SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr, 1357 (MAI_SERVPORT_STR *) 0, sa->sa_family); 1358 else 1359 strncpy(state->client_addr.buf, "local", sizeof("local") + 0); 1360 if (msg_verbose) 1361 msg_info("connect (%s %s)", 1362#ifdef AF_LOCAL 1363 sa->sa_family == AF_LOCAL ? "AF_LOCAL" : 1364#else 1365 sa->sa_family == AF_UNIX ? "AF_UNIX" : 1366#endif 1367 sa->sa_family == AF_INET ? "AF_INET" : 1368#ifdef AF_INET6 1369 sa->sa_family == AF_INET6 ? "AF_INET6" : 1370#endif 1371 "unknown protocol family", 1372 state->client_addr.buf); 1373 non_blocking(fd, NON_BLOCKING); 1374 state->stream = vstream_fdopen(fd, O_RDWR); 1375 vstream_tweak_sock(state->stream); 1376 state->buffer = vstring_alloc(1024); 1377 state->read_fn = command_read; 1378 state->data_state = ST_ANY; 1379 PUSH_BACK_SET(state, ""); 1380 smtp_timeout_setup(state->stream, var_tmout); 1381 state->in_mail = 0; 1382 state->rcpts = 0; 1383 state->delayed_response = 0; 1384 state->delayed_args = 0; 1385 /* Initialize file capture attributes. */ 1386#ifdef AF_INET6 1387 if (sa->sa_family == AF_INET6) 1388 state->addr_prefix = "ipv6:"; 1389 else 1390#endif 1391 state->addr_prefix = ""; 1392 1393 state->helo_args = 0; 1394 state->client_proto = enable_lmtp ? "LMTP" : "SMTP"; 1395 state->start_time = 0; 1396 state->id = 0; 1397 state->dump_file = 0; 1398 1399 /* 1400 * We use the smtp_stream module to produce output. That module 1401 * throws an exception via vstream_longjmp() in case of a timeout or 1402 * lost connection error. Therefore we must prepare to handle these 1403 * exceptions with vstream_setjmp(). 1404 */ 1405 switch (vstream_setjmp(state->stream)) { 1406 1407 default: 1408 msg_panic("unknown read/write error"); 1409 /* NOTREACHED */ 1410 1411 case SMTP_ERR_TIME: 1412 msg_warn("write timeout"); 1413 disconnect(state); 1414 return; 1415 1416 case SMTP_ERR_EOF: 1417 msg_warn("lost connection"); 1418 disconnect(state); 1419 return; 1420 1421 case 0: 1422 if (command_resp(state, command_table, "connect", "") < 0) 1423 disconnect(state); 1424 else if (command_table->delay == 0) { 1425 event_enable_read(fd, read_event, (void *) state); 1426 event_request_timer(read_timeout, (void *) state, var_tmout); 1427 } 1428 } 1429 } 1430} 1431 1432/* usage - explain */ 1433 1434static void usage(char *myname) 1435{ 1436 msg_fatal("usage: %s [-468acCeEFLpPv] [-A abort_delay] [-b soft_bounce_reply] [-B hard_bounce_reply] [-d dump-template] [-D dump-template] [-f commands] [-h hostname] [-m max_concurrency] [-M message_quit_count] [-n quit_count] [-q commands] [-r commands] [-R root-dir] [-s commands] [-S start-string] [-u user_privs] [-w delay] [host]:port backlog", myname); 1437} 1438 1439MAIL_VERSION_STAMP_DECLARE; 1440 1441int main(int argc, char **argv) 1442{ 1443 int backlog; 1444 int ch; 1445 int delay; 1446 const char *protocols = INET_PROTO_NAME_ALL; 1447 const char *root_dir = 0; 1448 const char *user_privs = 0; 1449 1450 /* 1451 * Fingerprint executables and core dumps. 1452 */ 1453 MAIL_VERSION_STAMP_ALLOCATE; 1454 1455 /* 1456 * Fix 20051207. 1457 */ 1458 signal(SIGPIPE, SIG_IGN); 1459 1460 /* 1461 * Initialize diagnostics. 1462 */ 1463 msg_vstream_init(argv[0], VSTREAM_ERR); 1464 1465 /* 1466 * Parse JCL. 1467 */ 1468 while ((ch = GETOPT(argc, argv, "468aA:b:B:cCd:D:eEf:Fh:H:Ln:m:M:NpPq:Q:r:R:s:S:t:T:u:vw:W:")) > 0) { 1469 switch (ch) { 1470 case '4': 1471 protocols = INET_PROTO_NAME_IPV4; 1472 break; 1473 case '6': 1474 protocols = INET_PROTO_NAME_IPV6; 1475 break; 1476 case '8': 1477 disable_8bitmime = 1; 1478 break; 1479 case 'a': 1480 disable_saslauth = 1; 1481 break; 1482 case 'A': 1483 if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0) 1484 usage(argv[0]); 1485 break; 1486 case 'b': 1487 if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) { 1488 msg_error("bad soft error reply: %s", optarg); 1489 usage(argv[0]); 1490 } else 1491 soft_error_resp = optarg; 1492 break; 1493 case 'B': 1494 if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) { 1495 msg_error("bad hard error reply: %s", optarg); 1496 usage(argv[0]); 1497 } else 1498 hard_error_resp = optarg; 1499 break; 1500 case 'c': 1501 show_count++; 1502 break; 1503 case 'C': 1504 disable_xclient = 1; 1505 reset_cmd_flags("xclient", FLAG_ENABLE); 1506 break; 1507 case 'd': 1508 single_template = optarg; 1509 break; 1510 case 'D': 1511 shared_template = optarg; 1512 break; 1513 case 'e': 1514 disable_esmtp = 1; 1515 break; 1516 case 'E': 1517 disable_enh_status = 1; 1518 break; 1519 case 'f': 1520 set_cmds_flags(optarg, FLAG_HARD_ERR); 1521 disable_pipelining = 1; 1522 break; 1523 case 'F': 1524 disable_xforward = 1; 1525 reset_cmd_flags("xforward", FLAG_ENABLE); 1526 break; 1527 case 'h': 1528 var_myhostname = optarg; 1529 break; 1530 case 'H': 1531 if ((data_read_delay = atoi(optarg)) <= 0) 1532 msg_fatal("bad data read delay: %s", optarg); 1533 break; 1534 case 'L': 1535 enable_lmtp = 1; 1536 break; 1537 case 'm': 1538 if ((max_client_count = atoi(optarg)) <= 0) 1539 msg_fatal("bad concurrency limit: %s", optarg); 1540 break; 1541 case 'M': 1542 if ((max_msg_quit_count = atoi(optarg)) <= 0) 1543 msg_fatal("bad message quit count: %s", optarg); 1544 break; 1545 case 'n': 1546 if ((max_quit_count = atoi(optarg)) <= 0) 1547 msg_fatal("bad quit count: %s", optarg); 1548 break; 1549 case 'N': 1550 disable_dsn = 1; 1551 break; 1552 case 'p': 1553 disable_pipelining = 1; 1554 break; 1555 case 'P': 1556 pretend_pix = 1; 1557 disable_esmtp = 1; 1558 break; 1559 case 'q': 1560 set_cmds_flags(optarg, FLAG_DISCONNECT); 1561 break; 1562 case 'Q': 1563 set_cmds_flags(optarg, FLAG_CLOSE); 1564 break; 1565 case 'r': 1566 set_cmds_flags(optarg, FLAG_SOFT_ERR); 1567 disable_pipelining = 1; 1568 break; 1569 case 'R': 1570 root_dir = optarg; 1571 break; 1572 case 's': 1573 openlog(basename(argv[0]), LOG_PID, LOG_MAIL); 1574 set_cmds_flags(optarg, FLAG_SYSLOG); 1575 break; 1576 case 'S': 1577 start_string = vstring_alloc(10); 1578 unescape(start_string, optarg); 1579 break; 1580 case 't': 1581 if ((var_tmout = atoi(optarg)) <= 0) 1582 msg_fatal("bad timeout: %s", optarg); 1583 break; 1584 case 'T': 1585 if ((inet_windowsize = atoi(optarg)) <= 0) 1586 msg_fatal("bad TCP window size: %s", optarg); 1587 break; 1588 case 'u': 1589 user_privs = optarg; 1590 break; 1591 case 'v': 1592 msg_verbose++; 1593 break; 1594 case 'w': 1595 if ((delay = atoi(optarg)) <= 0) 1596 usage(argv[0]); 1597 set_cmd_delay("data", delay, 0); 1598 break; 1599 case 'W': 1600 set_cmd_delay_arg(optarg); 1601 break; 1602 default: 1603 usage(argv[0]); 1604 } 1605 } 1606 if (argc - optind != 2) 1607 usage(argv[0]); 1608 if ((backlog = atoi(argv[optind + 1])) <= 0) 1609 usage(argv[0]); 1610 if (single_template && shared_template) 1611 msg_fatal("use only one of -d or -D, but not both"); 1612 if (geteuid() == 0 && user_privs == 0) 1613 msg_fatal("-u option is required if running as root"); 1614 1615 /* 1616 * Initialize. 1617 */ 1618 if (var_myhostname == 0) 1619 var_myhostname = "smtp-sink"; 1620 set_cmds_flags(enable_lmtp ? "lhlo" : 1621 disable_esmtp ? "helo" : 1622 "helo, ehlo", FLAG_ENABLE); 1623 proto_info = inet_proto_init("protocols", protocols); 1624 if (strncmp(argv[optind], "unix:", 5) == 0) { 1625 sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); 1626 } else { 1627 if (strncmp(argv[optind], "inet:", 5) == 0) 1628 argv[optind] += 5; 1629 sock = inet_listen(argv[optind], backlog, BLOCKING); 1630 } 1631 if (user_privs) 1632 chroot_uid(root_dir, user_privs); 1633 1634 if (single_template) 1635 mysrand((int) time((time_t *) 0)); 1636 else if (shared_template) 1637 single_template = shared_template; 1638 1639 /* 1640 * Start the event handler. 1641 */ 1642 event_enable_read(sock, connect_event, (void *) 0); 1643 for (;;) 1644 event_loop(-1); 1645} 1646