Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Postfix/src/smtpstone/   (Postfix Mailserver Version 3.11©)  Datei vom 21.11.2021 mit Größe 48 kB image not shown  

Quelle  smtp-sink.c

  Sprache: C
 

/*++
/* NAME
/* smtp-sink 1
/* SUMMARY
/* parallelized SMTP/LMTP test server
/* SYNOPSIS
/* .fi
/* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR
/* \fIbacklog\fR
/*
/* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR
/* DESCRIPTION
/* \fBsmtp-sink\fR listens on the named host (or address) and port.
/* It takes SMTP messages from the network and throws them away.
/* The purpose is to measure client performance, not protocol
/* compliance.
/*
/* \fBsmtp-sink\fR may also be configured to capture each mail
/* delivery transaction to file. Since disk latencies are large
/* compared to network delays, this mode of operation can
/* reduce the maximal performance by several orders of magnitude.
/*
/* Connections can be accepted on IPv4 or IPv6 endpoints, or on
/* UNIX-domain sockets.
/* IPv4 and IPv6 are the default.
/* This program is the complement of the \fBsmtp-source\fR(1) program.
/*
/* Note: this is an unsupported test program. No attempt is made
/* to maintain compatibility between successive versions.
/*
/* Arguments:
/* .IP \fB-4\fR
/* Support IPv4 only. This option has no effect when
/* Postfix is built without IPv6 support.
/* .IP \fB-6\fR
/* Support IPv6 only. This option is not available when
/* Postfix is built without IPv6 support.
/* .IP \fB-8\fR
/* Do not announce 8BITMIME support.
/* .IP \fB-a\fR
/* Do not announce SASL authentication support.
/* .IP "\fB-A \fIdelay\fR"
/* Wait \fIdelay\fR seconds after responding to DATA, then
/* abort prematurely with a 550 reply status.  Do not read
/* further input from the client; this is an attempt to block
/* the client before it sends ".".  Specify a zero delay value
/* to abort immediately.
/* .IP "\fB-b \fIsoft-bounce-reply\fR"
/* Use \fIsoft-bounce-reply\fR for soft reject responses.  The
/* default reply is "450 4.3.0 Error: command failed".
/* .IP "\fB-B \fIhard-bounce-reply\fR"
/* Use \fIhard-bounce-reply\fR for hard reject responses.  The
/* default reply is "500 5.3.0 Error: command failed".
/* .IP \fB-c\fR
/* Display running counters that are updated whenever an SMTP
/* session ends, a QUIT command is executed, or when "." is
/* received.
/* .IP \fB-C\fR
/* Disable XCLIENT support.
/* .IP "\fB-d \fIdump-template\fR"
/* Dump each mail transaction to a single-message file whose
/* name is created by expanding the \fIdump-template\fR via
/* strftime(3) and appending a pseudo-random hexadecimal number
/* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3").
/* If the template contains "/" characters, missing directories
/* are created automatically.  The message dump format is
/* described below.
/* .sp
/* Note: this option keeps one capture file open for every
/* mail transaction in progress.
/* .IP "\fB-D \fIdump-template\fR"
/* Append mail transactions to a multi-message dump file whose
/* name is created by expanding the \fIdump-template\fR via
/* strftime(3).
/* If the template contains "/" characters, missing directories
/* are created automatically.  The message dump format is
/* described below.
/* .sp
/* Note: this option keeps one capture file open for every
/* mail transaction in progress.
/* .IP \fB-e\fR
/* Do not announce ESMTP support.
/* .IP \fB-E\fR
/* Do not announce ENHANCEDSTATUSCODES support.
/* .IP "\fB-f \fIcommand,command,...\fR"
/* Reject the specified commands with a hard (5xx) error code.
/* This option implies \fB-p\fR.
/* .sp
/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
/* white space or commas, and use quotes to protect white space
/* from the shell. Command names are case-insensitive.
/* .IP \fB-F\fR
/* Disable XFORWARD support.
/* .IP "\fB-h\fI hostname\fR"
/* Use \fIhostname\fR in the SMTP greeting, in the HELO response,
/* and in the EHLO response. The default hostname is "smtp-sink".
/* .IP "\fB-H\fI delay\fR"
/* Delay the first read operation after receiving DATA (time
/* in seconds). Combine with a large test message and a small
/* TCP window size (see the \fB-T\fR option) to test the Postfix
/* client write_wait() implementation.
/* .IP \fB-L\fR
/* Enable LMTP instead of SMTP.
/* .IP "\fB-m \fIcount\fR (default: 256)"
/* An upper bound on the maximal number of simultaneous
/* connections that \fBsmtp-sink\fR will handle. This prevents
/* the process from running out of file descriptors. Excess
/* connections will stay queued in the TCP/IP stack.
/* .IP "\fB-M \fIcount\fR"
/* Terminate after receiving \fIcount\fR messages.
/* .IP "\fB-n \fIcount\fR"
/* Terminate after \fIcount\fR sessions.
/* .IP \fB-N\fR
/* Do not announce support for DSN.
/* .IP \fB-p\fR
/* Do not announce support for ESMTP command pipelining.
/* .IP \fB-P\fR
/* Change the server greeting so that it appears to come through
/* a CISCO PIX system. Implies \fB-e\fR.
/* .IP "\fB-q \fIcommand,command,...\fR"
/* Disconnect (without replying) after receiving one of the
/* specified commands.
/* .sp
/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
/* white space or commas, and use quotes to protect white space
/* from the shell. Command names are case-insensitive.
/* .IP "\fB-Q \fIcommand,command,...\fR"
/* Send a 421 reply and disconnect after receiving one
/* of the specified commands.
/* .sp
/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
/* white space or commas, and use quotes to protect white space
/* from the shell. Command names are case-insensitive.
/* .IP "\fB-r \fIcommand,command,...\fR"
/* Reject the specified commands with a soft (4xx) error code.
/* This option implies \fB-p\fR.
/* .sp
/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
/* white space or commas, and use quotes to protect white space
/* from the shell. Command names are case-insensitive.
/* .IP "\fB-R \fIroot-directory\fR"
/* Change the process root directory to the specified location.
/* This option requires super-user privileges. See also the
/* \fB-u\fR option.
/* .IP "\fB-s \fIcommand,command,...\fR"
/* Log the named commands to syslogd.
/* .sp
/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
/* white space or commas, and use quotes to protect white space
/* from the shell. Command names are case-insensitive.
/* .IP "\fB-S start-string\fR"
/* An optional string that is prepended to each message that is
/* written to a dump file (see the dump file format description
/* below). The following C escape sequences are supported: \ea
/* (bell), \eb (backspace), \ef (formfeed), \en (newline), \er
/* (carriage return), \et (horizontal tab), \ev (vertical tab),
/* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash
/* character).
/* .IP "\fB-t \fItimeout\fR (default: 100)"
/* Limit the time for receiving a command or sending a response.
/* The time limit is specified in seconds.
/* .IP "\fB-T \fIwindowsize\fR"
/* Override the default TCP window size. To work around
/* broken TCP window scaling implementations, specify a
/* value > 0 and < 65536.
/* .IP "\fB-u \fIusername\fR"
/* Switch to the specified user privileges after opening the
/* network socket and optionally changing the process root
/* directory. This option is required when the process runs
/* with super-user privileges. See also the \fB-R\fR option.
/* .IP \fB-v\fR
/* Show the SMTP conversations.
/* .IP "\fB-w \fIdelay\fR"
/* Wait \fIdelay\fR seconds before responding to a DATA command.
/* .IP "\fB-W \fIcommand:delay[:odds]\fR"
/* Wait \fIdelay\fR seconds before responding to \fIcommand\fR.
/* If \fIodds\fR is also specified (a number between 1-99
/* inclusive), wait for a random multiple of \fIdelay\fR. The
/* random multiplier is equal to the number of times the program
/* needs to roll a dice with a range of 0..99 inclusive, before
/* the dice produces a result greater than or equal to \fIodds\fR.
/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR
/* Listen on network interface \fIhost\fR (default: any interface)
/* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be
/* specified in numeric or symbolic form.
/* .IP \fBunix:\fR\fIpathname\fR
/* Listen on the UNIX-domain socket at \fIpathname\fR.
/* .IP \fIbacklog\fR
/* The maximum length of the queue of pending connections,
/* as defined by the \fBlisten\fR(2) system call.
/* DUMP FILE FORMAT
/* .ad
/* .fi
/* Each dumped message contains a sequence of text lines,
/* terminated with the newline character. The sequence of
/* information is as follows:
/* .IP \(bu
/* The optional string specified with the \fB-S\fR option.
/* .IP \(bu
/* The \fBsmtp-sink\fR generated headers as documented below.
/* .IP \(bu
/* The message header and body as received from the SMTP client.
/* .IP \(bu
/* An empty line.
/* .PP
/* The format of the \fBsmtp-sink\fR generated headers is as
/* follows:
/* .IP "\fBX-Client-Addr: \fItext\fR"
/* The client IP address without enclosing []. An IPv6 address
/* is prefixed with "ipv6:". This record is always present.
/* .IP "\fBX-Client-Proto: \fItext\fR"
/* The client protocol: SMTP, ESMTP or LMTP. This record is
/* always present.
/* .IP "\fBX-Helo-Args: \fItext\fR"
/* The arguments of the last HELO or EHLO command before this
/* mail delivery transaction. This record is present only if
/* the client sent a recognizable HELO or EHLO command before
/* the DATA command.
/* .IP "\fBX-Mail-Args: \fItext\fR"
/* The arguments of the MAIL command that started this mail
/* delivery transaction. This record is present exactly once.
/* .IP "\fBX-Rcpt-Args: \fItext\fR"
/* The arguments of an RCPT command within this mail delivery
/* transaction. There is one record for each RCPT command, and
/* they are in the order as sent by the client.
/* .IP "\fBReceived: \fItext\fR"
/* A message header for compatibility with mail processing
/* software. This three-line header marks the end of the headers
/* provided by \fBsmtp-sink\fR, and is formatted as follows:
/* .RS
/* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])"
/* The HELO or EHLO command argument and client IP address.
/* If the client did not send HELO or EHLO, the client IP
/* address is used instead.
/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR"
/* The hostname specified with the \fB-h\fR option, the client
/* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random
/* portion of the per-message capture file name.
/* .IP \fItime-stamp\fR
/* A time stamp as defined in RFC 2822.
/* .RE
/* SEE ALSO
/* smtp-source(1), SMTP/LMTP message generator
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*--*/


/* System library. */

#include <sys_defs.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
#include <time.h>
#include <ctype.h>

#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif

/* Utility library. */

#include <msg.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <get_hostname.h>
#include <listen.h>
#include <events.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <msg_vstream.h>
#include <stringops.h>
#include <sane_accept.h>
#include <inet_proto.h>
#include <myaddrinfo.h>
#include <make_dirs.h>
#include <myrand.h>
#include <chroot_uid.h>

/* Global library. */

#include <smtp_stream.h>
#include <mail_date.h>
#include <mail_version.h>

/* Application-specific. */

typedef struct SINK_STATE {
    VSTREAM *stream;
    VSTRING *buffer;
    int     data_state;
    int     (*read_fn) (struct SINK_STATE *);
    int     in_mail;
    int     rcpts;
    char   *push_back_ptr;
    /* Capture file information for fake Received: header */
    MAI_HOSTADDR_STR client_addr; /* IP address */
    char   *addr_prefix;  /* ipv6: or empty */
    char   *helo_args;   /* text after HELO or EHLO */
    const char *client_proto;  /* SMTP, ESMTP, LMTP */
    time_t  start_time;   /* MAIL command time */
    int     id;    /* pseudo-random */
    VSTREAM *dump_file;   /* dump file or null */
    void    (*delayed_response) (struct SINK_STATE *state, const char *);
    char   *delayed_args;
} SINK_STATE;

#define ST_ANY   0
#define ST_CR   1
#define ST_CR_LF  2
#define ST_CR_LF_DOT  3
#define ST_CR_LF_DOT_CR  4
#define ST_CR_LF_DOT_CR_LF 5

#define PUSH_BACK_PEEK(state)  (*(state)->push_back_ptr != 0)
#define PUSH_BACK_GET(state)  (*(state)->push_back_ptr++)
#define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text))

#ifndef DEF_MAX_CLIENT_COUNT
#define DEF_MAX_CLIENT_COUNT 256
#endif

#define SOFT_ERROR_RESP  "450 4.3.0 Error: command failed"
#define HARD_ERROR_RESP  "500 5.3.0 Error: command failed"

 /*
  * We can't rely on vstream auto-flushing, so we have to prepare for the
  * next read request.
  */

#define SMTP_FLUSH(fp) do { \
    if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \
        smtp_flush(fp); \
    } while (0)

static int var_tmout = 100;
static int var_max_line_length = 2048;
static char *var_myhostname;
static char *soft_error_resp = SOFT_ERROR_RESP;
static char *hard_error_resp = HARD_ERROR_RESP;
static int command_read(SINK_STATE *);
static int data_read(SINK_STATE *);
static void disconnect(SINK_STATE *);
static void read_timeout(intvoid *);
static void read_event(intvoid *);
static int show_count;
static int sess_count;
static int quit_count;
static int mesg_count;
static int max_quit_count;
static int max_msg_quit_count;
static int disable_pipelining;
static int disable_8bitmime;
static int disable_esmtp;
static int enable_lmtp;
static int pretend_pix;
static int disable_saslauth;
static int disable_xclient;
static int disable_xforward;
static int disable_enh_status;
static int disable_dsn;
static int max_client_count = DEF_MAX_CLIENT_COUNT;
static int client_count;
static int sock;
static int abort_delay = -1;
static int data_read_delay = 0;

static char *single_template;  /* individual template */
static char *shared_template;  /* shared template */
static VSTRING *start_string;  /* dump content prefix */

static const INET_PROTO_INFO *proto_info;

#define STR(x) vstring_str(x)

/* do_stats - show counters */

static void do_stats(void)
{
    vstream_printf("sess=%d quit=%d mesg=%d\r",
     sess_count, quit_count, mesg_count);
    vstream_fflush(VSTREAM_OUT);
}

/* hard_err_resp - generic hard error response */

static void hard_err_resp(SINK_STATE *state)
{
    smtp_printf(state->stream, "%s", hard_error_resp);
    SMTP_FLUSH(state->stream);
}

/* soft_err_resp - generic soft error response */

static void soft_err_resp(SINK_STATE *state)
{
    smtp_printf(state->stream, "%s", soft_error_resp);
    SMTP_FLUSH(state->stream);
}

/* exp_path_template - expand template pathname, static result */

static VSTRING *exp_path_template(const char *template, time_t start_time)
{
    static VSTRING *path_buf = 0;
    struct tm *lt;

    if (path_buf == 0)
 path_buf = vstring_alloc(100);
    else
 VSTRING_RESET(path_buf);
    lt = localtime(&start_time);
    while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0)
 VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100);
    VSTRING_SKIP(path_buf);
    return (path_buf);
}

/* make_parent_dir - create parent directory or bust */

static void make_parent_dir(const char *path, mode_t mode)
{
    const char *parent;

    parent = sane_dirname((VSTRING *) 0, path);
    if (make_dirs(parent, mode) < 0)
 msg_fatal("mkdir %s: %m", parent);
}

/* mail_file_open - open mail capture file */

static void mail_file_open(SINK_STATE *state)
{
    const char *myname = "mail_file_open";
    VSTRING *path_buf;
    ssize_t len;
    int     tries = 0;

    /*
     * Save the start time for later.
     */

    time(&(state->start_time));

    /*
     * Expand the per-message dumpfile pathname template.
     */

    path_buf = exp_path_template(single_template, state->start_time);

    /*
     * Append a random hexadecimal string to the pathname and create a new
     * file. Retry with a different path if the file already exists. Create
     * intermediate directories on the fly when the template specifies
     * multiple pathname segments.
     */

#define ID_FORMAT "%08x"

    for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) {
 if (++tries > 100)
     msg_fatal("%s: something is looping", myname);
 state->id = myrand();
 vstring_sprintf_append(path_buf, ID_FORMAT, state->id);
 if ((state->dump_file = vstream_fopen(STR(path_buf),
           O_RDWR | O_CREAT | O_EXCL,
           0644)) != 0) {
     break;
 } else if (errno == EEXIST) {
     continue;
 } else if (errno == ENOENT) {
     make_parent_dir(STR(path_buf), 0755);
     continue;
 } else {
     msg_fatal("open %s: %m", STR(path_buf));
 }
    }

    /*
     * Don't leave temporary files behind.
     */

    if (shared_template != 0 && unlink(STR(path_buf)) < 0)
 msg_fatal("unlink %s: %m", STR(path_buf));

    /*
     * Do initial header records.
     */

    if (start_string)
 vstream_fprintf(state->dump_file, "%s", STR(start_string));
    vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n",
      state->addr_prefix, state->client_addr.buf);
    vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto);
    if (state->helo_args)
 vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args);
    /* Note: there may be more than one recipient. */
}

/* mail_file_finish_header - do final smtp-sink generated header records */

static void mail_file_finish_header(SINK_STATE *state)
{
    if (state->helo_args)
 vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n",
   state->helo_args, state->addr_prefix,
   state->client_addr.buf);
    else
 vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n",
   state->addr_prefix, state->client_addr.buf,
   state->addr_prefix, state->client_addr.buf);
    vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)"
      " with %s id " ID_FORMAT ";\n",
      var_myhostname, state->client_proto, state->id);
    vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time));
}

/* mail_file_cleanup - common cleanup for capture file */

static void mail_file_cleanup(SINK_STATE *state)
{
    (void) vstream_fclose(state->dump_file);
    state->dump_file = 0;
}

/* mail_file_finish - handle message completion for capture file */

static void mail_file_finish(SINK_STATE *state)
{

    /*
     * Optionally append the captured message to a shared dumpfile.
     */

    if (shared_template) {
 const char *out_path;
 VSTREAM *out_fp;
 ssize_t count;

 /*
  * Expand the shared dumpfile pathname template.
 */

 out_path = STR(exp_path_template(shared_template, state->start_time));

 /*
  * Open the shared dump file.
 */

#define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
#define OUT_OPEN_MODE 0644

 if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE))
     == 0 && errno == ENOENT) {
     make_parent_dir(out_path, 0755);
     out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE);
 }
 if (out_fp == 0)
     msg_fatal("open %s: %m", out_path);

 /*
  * Append message content from single-message dump file.
 */

 if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0)
     msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file));
 VSTRING_RESET(state->buffer);
 for (;;) {
     count = vstream_fread(state->dump_file, STR(state->buffer),
      vstring_avail(state->buffer));
     if (count <= 0)
  break;
     if (vstream_fwrite(out_fp, STR(state->buffer), count) != count)
  msg_fatal("append file %s: %m", out_path);
 }
 if (vstream_ferror(state->dump_file))
     msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file));
 if (vstream_fclose(out_fp))
     msg_fatal("append file %s: %m", out_path);
    }
    mail_file_cleanup(state);
}

/* mail_file_reset - abort mail to capture file */

static void mail_file_reset(SINK_STATE *state)
{
    if (shared_template == 0
 && unlink(VSTREAM_PATH(state->dump_file)) < 0
 && errno != ENOENT)
 msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file));
    mail_file_cleanup(state);
}

/* mail_cmd_reset - reset mail transaction information */

static void mail_cmd_reset(SINK_STATE *state)
{
    state->in_mail = 0;
    /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */
    if (state->dump_file)
 mail_file_reset(state);
}

/* ehlo_response - respond to EHLO command */

static void ehlo_response(SINK_STATE *state, const char *args)
{
#define SKIP(cp, cond) do { \
 for (/* void */; *cp && (cond); cp++) \
     /* void */; \
    } while (0)

    /* EHLO aborts a mail transaction in progress. */
    mail_cmd_reset(state);
    if (enable_lmtp == 0)
 state->client_proto = "ESMTP";
    smtp_printf(state->stream, "250-%s", var_myhostname);
    if (!disable_pipelining)
 smtp_printf(state->stream, "250-PIPELINING");
    if (!disable_8bitmime)
 smtp_printf(state->stream, "250-8BITMIME");
    if (!disable_saslauth)
 smtp_printf(state->stream, "250-AUTH PLAIN LOGIN");
    if (!disable_xclient)
 smtp_printf(state->stream, "250-XCLIENT NAME HELO");
    if (!disable_xforward)
 smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO");
    if (!disable_enh_status)
 smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES");
    if (!disable_dsn)
 smtp_printf(state->stream, "250-DSN");
    /* RFC 821/2821/5321: Format is replycode<SPACE>optional-text<CRLF> */
    smtp_printf(state->stream, "250 ");
    SMTP_FLUSH(state->stream);
    if (single_template) {
 if (state->helo_args)
     myfree(state->helo_args);
 SKIP(args, ISSPACE(*args));
 state->helo_args = mystrdup(args);
    }
}

/* helo_response - respond to HELO command */

static void helo_response(SINK_STATE *state, const char *args)
{
    /* HELO aborts a mail transaction in progress. */
    mail_cmd_reset(state);
    state->client_proto = "SMTP";
    smtp_printf(state->stream, "250 %s", var_myhostname);
    SMTP_FLUSH(state->stream);
    if (single_template) {
 if (state->helo_args)
     myfree(state->helo_args);
 SKIP(args, ISSPACE(*args));
 state->helo_args = mystrdup(args);
    }
}

/* ok_response - send 250 OK */

static void ok_response(SINK_STATE *state, const char *unused_args)
{
    smtp_printf(state->stream, "250 2.0.0 Ok");
    SMTP_FLUSH(state->stream);
}

/* rset_response - reset, send 250 OK */

static void rset_response(SINK_STATE *state, const char *unused_args)
{
    mail_cmd_reset(state);
    smtp_printf(state->stream, "250 2.1.0 Ok");
    SMTP_FLUSH(state->stream);
}

/* mail_response - reset recipient count, send 250 OK */

static void mail_response(SINK_STATE *state, const char *args)
{
    if (state->in_mail) {
 smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command");
 SMTP_FLUSH(state->stream);
 return;
    }
    state->in_mail++;
    state->rcpts = 0;
    smtp_printf(state->stream, "250 2.1.0 Ok");
    SMTP_FLUSH(state->stream);
    if (single_template) {
 mail_file_open(state);
 SKIP(args, *args != ':');
 SKIP(args, *args == ':');
 SKIP(args, ISSPACE(*args));
 vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args);
    }
}

/* rcpt_response - bump recipient count, send 250 OK */

static void rcpt_response(SINK_STATE *state, const char *args)
{
    if (state->in_mail == 0) {
 smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command");
 SMTP_FLUSH(state->stream);
 return;
    }
    state->rcpts++;
    smtp_printf(state->stream, "250 2.1.5 Ok");
    SMTP_FLUSH(state->stream);
    /* Note: there may be more than one recipient per mail transaction. */
    if (state->dump_file) {
 SKIP(args, *args != ':');
 SKIP(args, *args == ':');
 SKIP(args, ISSPACE(*args));
 vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args);
    }
}

/* abort_event - delayed abort after DATA command */

static void abort_event(int unused_event, void *context)
{
    SINK_STATE *state = (SINK_STATE *) context;

    smtp_printf(state->stream, "550 This violates SMTP");
    SMTP_FLUSH(state->stream);
    disconnect(state);
}

/* delay_read_event - resume input event handling */

static void delay_read_event(int event, void *context)
{
    SINK_STATE *state = (SINK_STATE *) context;

    if (event != EVENT_TIME)
 msg_panic("delay_read_event: non-timer event %d", event);

    event_enable_read(vstream_fileno(state->stream), read_event, (void *) state);
    event_request_timer(read_timeout, (void *) state, var_tmout);
}

/* delay_read - temporarily suspend input event handling */

static void delay_read(SINK_STATE *state, int delay)
{
    event_disable_readwrite(vstream_fileno(state->stream));
    event_cancel_timer(read_timeout, (void *) state);
    event_request_timer(delay_read_event, (void *) state, delay);
}

/* data_response - respond to DATA command */

static void data_response(SINK_STATE *state, const char *unused_args)
{
    if (state->in_mail == 0 || state->rcpts == 0) {
 smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command");
 SMTP_FLUSH(state->stream);
 return;
    }
    /* Not: ST_ANY. */
    state->data_state = ST_CR_LF;
    smtp_printf(state->stream, "354 End data with <CR><LF>.<CR><LF>");
    SMTP_FLUSH(state->stream);
    if (abort_delay < 0) {
 state->read_fn = data_read;
 /* Todo: move into code that invokes the command response function. */
 if (data_read_delay > 0)
     delay_read(state, data_read_delay);
    } else {
 /* Stop reading, send premature 550, and disconnect. */
 event_disable_readwrite(vstream_fileno(state->stream));
 event_cancel_timer(read_event, (void *) state);
 event_request_timer(abort_event, (void *) state, abort_delay);
    }
    if (state->dump_file)
 mail_file_finish_header(state);
}

/* dot_resp_hard - hard error response to . command */

static void dot_resp_hard(SINK_STATE *state)
{
    if (enable_lmtp) {
 while (state->rcpts-- > 0/* XXX this could block */
     smtp_printf(state->stream, "%s", hard_error_resp);
    } else {
 smtp_printf(state->stream, "%s", hard_error_resp);
    }
    SMTP_FLUSH(state->stream);
}

/* dot_resp_soft - soft error response to . command */

static void dot_resp_soft(SINK_STATE *state)
{
    if (enable_lmtp) {
 while (state->rcpts-- > 0/* XXX this could block */
     smtp_printf(state->stream, "%s", soft_error_resp);
    } else {
 smtp_printf(state->stream, "%s", soft_error_resp);
    }
    SMTP_FLUSH(state->stream);
}

/* dot_response - response to . command */

static void dot_response(SINK_STATE *state, const char *unused_args)
{
    if (enable_lmtp) {
 while (state->rcpts-- > 0/* XXX this could block */
     smtp_printf(state->stream, "250 2.2.0 Ok");
    } else {
 smtp_printf(state->stream, "250 2.0.0 Ok");
    }
    SMTP_FLUSH(state->stream);
}

/* quit_response - respond to QUIT command */

static void quit_response(SINK_STATE *state, const char *unused_args)
{
    smtp_printf(state->stream, "221 Bye");
    smtp_flush(state->stream);   /* not: SMTP_FLUSH */
    if (show_count)
 quit_count++;
}

/* conn_response - respond to connect command */

static void conn_response(SINK_STATE *state, const char *unused_args)
{
    if (pretend_pix)
 smtp_printf(state->stream, "220 ********");
    else if (disable_esmtp)
 smtp_printf(state->stream, "220 %s", var_myhostname);
    else
 smtp_printf(state->stream, "220 %s ESMTP", var_myhostname);
    SMTP_FLUSH(state->stream);
}

/* delay_event - delayed command response */

static void delay_event(int unused_event, void *context)
{
    SINK_STATE *state = (SINK_STATE *) context;

    switch (vstream_setjmp(state->stream)) {

    default:
 msg_panic("unknown read/write error");
 /* NOTREACHED */

    case SMTP_ERR_TIME:
 msg_warn("write timeout");
 disconnect(state);
 return;

    case SMTP_ERR_EOF:
 msg_warn("lost connection");
 disconnect(state);
 return;

    case 0:
 state->delayed_response(state, state->delayed_args);
 myfree(state->delayed_args);
 state->delayed_args = 0;
 break;
    }

    if (state->delayed_response == quit_response) {
 disconnect(state);
 return;
    }
    state->delayed_response = 0;

    /* Resume input event handling after the delayed response. */
    event_enable_read(vstream_fileno(state->stream), read_event, (void *) state);
    event_request_timer(read_timeout, (void *) state, var_tmout);
}

/* data_read - read data from socket */

static int data_read(SINK_STATE *state)
{
    int     ch;
    struct data_trans {
 int     state;
 int     want;
 int     next_state;
    };
    static struct data_trans data_trans[] = {
 ST_ANY, '\r', ST_CR,
 ST_CR, '\n', ST_CR_LF,
 ST_CR_LF, '.', ST_CR_LF_DOT,
 ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR,
 ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF,
    };
    struct data_trans *dp;

    /*
     * A read may result in EOF, but is never supposed to time out - a time
     * out means that we were trying to read when no data was available.
     */

    for (;;) {
 if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF)
     return (-1);
 for (dp = data_trans; dp->state != state->data_state; dp++)
      /* void */ ;

 /*
  * Try to match the current character desired by the state machine.
  * If that fails, try to restart the machine with a match for its
  * first state.  This covers the case of a CR/LF/CR/LF sequence
  * (empty line) right before the end of the message data.
 */

 if (ch == dp->want)
     state->data_state = dp->next_state;
 else if (ch == data_trans[0].want)
     state->data_state = data_trans[0].next_state;
 else
     state->data_state = ST_ANY;
 if (state->dump_file) {
     if (ch != '\r' && state->data_state != ST_CR_LF_DOT)
  VSTREAM_PUTC(ch, state->dump_file);
     if (vstream_ferror(state->dump_file))
  msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file));
 }
 if (state->data_state == ST_CR_LF_DOT_CR_LF) {
     PUSH_BACK_SET(state, ".\r\n");
     state->read_fn = command_read;
     state->data_state = ST_ANY;
     if (state->dump_file)
  mail_file_finish(state);
     mail_cmd_reset(state);
     if (show_count || max_msg_quit_count > 0) {
  mesg_count++;
  if (show_count)
      do_stats();
  if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count)
      exit(0);
     }
     break;
 }

 /*
  * We must avoid blocking I/O, so get out of here as soon as both the
  * VSTREAM and kernel read buffers dry up.
 */

 if (vstream_peek(state->stream) <= 0
     && readable(vstream_fileno(state->stream)) <= 0)
     return (0);
    }
    return (0);
}

 /*
  * The table of all SMTP commands that we can handle.
  */

typedef struct SINK_COMMAND {
    const char *name;
    void    (*response) (SINK_STATE *, const char *);
    void    (*hard_response) (SINK_STATE *);
    void    (*soft_response) (SINK_STATE *);
    int     flags;
    int     delay;
    int     delay_odds;
} SINK_COMMAND;

#define FLAG_ENABLE (1<<0)  /* command is enabled */
#define FLAG_SYSLOG (1<<1)  /* log the command */
#define FLAG_HARD_ERR (1<<2)  /* report hard error */
#define FLAG_SOFT_ERR (1<<3)  /* report soft error */
#define FLAG_DISCONNECT (1<<4)  /* disconnect */
#define FLAG_CLOSE (1<<5)  /* say goodbye and disconnect */

static SINK_COMMAND command_table[] = {
    "connect", conn_response, hard_err_resp, soft_err_resp, 000,
    "helo", helo_response, hard_err_resp, soft_err_resp, 000,
    "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 000,
    "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 000,
    "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 00,
    "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 00,
    0,
};

/* reset_cmd_flags - reset per-command command flags */

static void reset_cmd_flags(const char *cmd, int flags)
{
    SINK_COMMAND *cmdp;

    for (cmdp = command_table; cmdp->name != 0; cmdp++)
 if (strcasecmp(cmd, cmdp->name) == 0)
     break;
    if (cmdp->name == 0)
 msg_fatal("unknown command: %s", cmd);
    cmdp->flags &= ~flags;
}

/* set_cmd_flags - set per-command command flags */

static void set_cmd_flags(const char *cmd, int flags)
{
    SINK_COMMAND *cmdp;

    for (cmdp = command_table; cmdp->name != 0; cmdp++)
 if (strcasecmp(cmd, cmdp->name) == 0)
     break;
    if (cmdp->name == 0)
 msg_fatal("unknown command: %s", cmd);
    cmdp->flags |= flags;
}

/* set_cmds_flags - set per-command flags for multiple commands */

static void set_cmds_flags(const char *cmds, int flags)
{
    char   *saved_cmds;
    char   *cp;
    char   *cmd;

    saved_cmds = cp = mystrdup(cmds);
    while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0)
 set_cmd_flags(cmd, flags);
    myfree(saved_cmds);
}

/* set_cmd_delay - set per-command delay */

static void set_cmd_delay(const char *cmd, int delay, int odds)
{
    SINK_COMMAND *cmdp;

    for (cmdp = command_table; cmdp->name != 0; cmdp++)
 if (strcasecmp(cmd, cmdp->name) == 0)
     break;
    if (cmdp->name == 0)
 msg_fatal("unknown command: %s", cmd);

    if (delay <= 0)
 msg_fatal("non-positive '%s' delay", cmd);
    if (odds < 0 || odds > 99)
 msg_fatal("delay odds for '%s' out of range", cmd);

    cmdp->delay = delay;
    cmdp->delay_odds = odds;
}

/* set_cmd_delay_arg - set per-command delay from option argument */

static void set_cmd_delay_arg(char *arg)
{
    char   *cp;
    char   *saved_arg;
    char   *cmd;
    char   *delay;
    char   *odds;

    saved_arg = cp = mystrdup(arg);
    cmd = mystrtok(&cp, ":");
    delay = mystrtok(&cp, ":");
    if (cmd == 0 || delay == 0)
 msg_fatal("invalid command delay argument: %s", arg);
    odds = mystrtok(&cp, "");
    set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0);
    myfree(saved_arg);
}

/* command_resp - respond to command */

static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp,
           const char *command, const char *args)
{
    /* We use raw syslog. Sanitize data content and length. */
    if (cmdp->flags & FLAG_SYSLOG)
 syslog(LOG_INFO, "%s %.100s", command, args);
    if (cmdp->flags & FLAG_DISCONNECT)
 return (-1);
    if (cmdp->flags & FLAG_CLOSE) {
 smtp_printf(state->stream, "421 4.0.0 Server closing connection");
 return (-1);
    }
    if (cmdp->flags & FLAG_HARD_ERR) {
 cmdp->hard_response(state);
 return (0);
    }
    if (cmdp->flags & FLAG_SOFT_ERR) {
 cmdp->soft_response(state);
 return (0);
    }
    if (cmdp->delay > 0) {
 int     delay = cmdp->delay;

 if (cmdp->delay_odds > 0)
     for (delay = 0;
      ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds;
   delay += cmdp->delay)
   /* NOP */ ;
 /* Suspend input event handling while delaying the command response. */
 event_disable_readwrite(vstream_fileno(state->stream));
 event_cancel_timer(read_timeout, (void *) state);
 event_request_timer(delay_event, (void *) state, delay);
 state->delayed_response = cmdp->response;
 state->delayed_args = mystrdup(args);
    } else {
 cmdp->response(state, args);
 if (cmdp->response == quit_response)
     return (-1);
    }
    return (0);
}

/* command_read - talk the SMTP protocol, server side */

static int command_read(SINK_STATE *state)
{
    char   *command;
    SINK_COMMAND *cmdp;
    int     ch;
    struct cmd_trans {
 int     state;
 int     want;
 int     next_state;
    };
    static struct cmd_trans cmd_trans[] = {
 ST_ANY, '\r', ST_CR,
 ST_CR, '\n', ST_CR_LF,
 000,
    };
    struct cmd_trans *cp;
    char   *ptr;

    /*
     * A read may result in EOF, but is never supposed to time out - a time
     * out means that we were trying to read when no data was available.
     */

#define NEXT_CHAR(state) \
    (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream))

    if (state->data_state == ST_CR_LF)
 state->data_state = ST_ANY;  /* XXX */
    for (;;) {
 if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF)
     return (-1);

 /*
  * Sanity check. We don't want to store infinitely long commands.
 */

 if (VSTRING_LEN(state->buffer) >= var_max_line_length) {
     msg_warn("command line too long");
     return (-1);
 }
 VSTRING_ADDCH(state->buffer, ch);

 /*
  * Try to match the current character desired by the state machine.
  * If that fails, try to restart the machine with a match for its
  * first state.
 */

 for (cp = cmd_trans; cp->state != state->data_state; cp++)
     if (cp->want == 0)
  msg_panic("command_read: unknown state: %d", state->data_state);
 if (ch == cp->want)
     state->data_state = cp->next_state;
 else if (ch == cmd_trans[0].want)
     state->data_state = cmd_trans[0].next_state;
 else
     state->data_state = ST_ANY;
 if (state->data_state == ST_CR_LF)
     break;

 /*
  * We must avoid blocking I/O, so get out of here as soon as both the
  * VSTREAM and kernel read buffers dry up.
  * 
  * XXX Solaris non-blocking read() may fail on a socket when ioctl
  * FIONREAD reports there is unread data. Diagnosis by Max Pashkov.
  * As a workaround we use readable() (which uses poll or select())
  * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added
  * 20020604.
 */

 if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0
     && readable(vstream_fileno(state->stream)) <= 0)
     return (0);
    }

    /*
     * Properly terminate the result, and reset the buffer write pointer for
     * reading the next command. This is ugly, but not as ugly as trying to
     * deal with all the early returns below.
     */

    vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2);
    VSTRING_TERMINATE(state->buffer);
    state->data_state = ST_CR_LF;
    VSTRING_RESET(state->buffer);

    /*
     * Got a complete command line. Parse it.
     */

    ptr = vstring_str(state->buffer);
    if (msg_verbose)
 msg_info("%s", ptr);
    if ((command = mystrtok(&ptr, " \t")) == 0) {
 smtp_printf(state->stream, "500 5.5.2 Error: unknown command");
 SMTP_FLUSH(state->stream);
 return (0);
    }
    for (cmdp = command_table; cmdp->name != 0; cmdp++)
 if (strcasecmp(command, cmdp->name) == 0)
     break;
    if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) {
 smtp_printf(state->stream, "500 5.5.1 Error: unknown command");
 SMTP_FLUSH(state->stream);
 return (0);
    }
    return (command_resp(state, cmdp, command, printable(ptr, '?')));
}

/* read_timeout - handle timer event */

static void read_timeout(int unused_event, void *context)
{
    SINK_STATE *state = (SINK_STATE *) context;

    /*
     * We don't send anything to the client, because we would have to set up
     * an smtp_stream exception handler first. And that is just too much
     * trouble.
     */

    msg_warn("read timeout");
    disconnect(state);
}

/* read_event - handle command or data read events */

static void read_event(int unused_event, void *context)
{
    SINK_STATE *state = (SINK_STATE *) context;

    /*
     * The input reading routine not only reads input (with vstream calls)
     * but also produces output (with smtp_stream calls). Because the output
     * routines can raise timeout or EOF exceptions with vstream_longjmp(),
     * the input reading routine needs to set up corresponding exception
     * handlers with vstream_setjmp(). Guarding the input operations in the
     * same manner is not useful: we must read input in non-blocking mode, so
     * we never get called when the socket stays unreadable too long. And EOF
     * is already trivial to detect with the vstream calls.
     */

    do {
 switch (vstream_setjmp(state->stream)) {

 default:
     msg_panic("unknown read/write error");
     /* NOTREACHED */

 case SMTP_ERR_TIME:
     msg_warn("write timeout");
     disconnect(state);
     return;

 case SMTP_ERR_EOF:
     msg_warn("lost connection");
     disconnect(state);
     return;

 case 0:
     if (state->read_fn(state) < 0) {
  if (msg_verbose)
      msg_info("disconnect");
  disconnect(state);
  return;
     }
 }
    } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0);

    /*
     * Reset the idle timer. Wait until the next input event, or until the
     * idle timer goes off.
     */

    event_request_timer(read_timeout, (void *) state, var_tmout);
}

static void connect_event(intvoid *);

/* disconnect - handle disconnection events */

static void disconnect(SINK_STATE *state)
{
    event_disable_readwrite(vstream_fileno(state->stream));
    event_cancel_timer(read_timeout, (void *) state);
    if (show_count) {
 sess_count++;
 do_stats();
    }
    vstream_fclose(state->stream);
    vstring_free(state->buffer);
    /* Clean up file capture attributes. */
    if (state->helo_args)
 myfree(state->helo_args);
    /* Delete incomplete mail transaction. */
    mail_cmd_reset(state);
    if (state->delayed_args)
 myfree(state->delayed_args);
    myfree((void *) state);
    if (max_quit_count > 0 && quit_count >= max_quit_count)
 exit(0);
    if (client_count-- == max_client_count)
 event_enable_read(sock, connect_event, (void *) 0);
}

/* connect_event - handle connection events */

static void connect_event(int unused_event, void *unused_context)
{
    struct sockaddr_storage ss;
    SOCKADDR_SIZE len = sizeof(ss);
    struct sockaddr *sa = (struct sockaddr *) &ss;
    SINK_STATE *state;
    int     fd;

    if ((fd = sane_accept(sock, sa, &len)) >= 0) {
 /* Safety: limit the number of open sockets and capture files. */
 if (++client_count == max_client_count)
     event_disable_readwrite(sock);
 state = (SINK_STATE *) mymalloc(sizeof(*state));
 if (strchr((char *) proto_info->sa_family_list, sa->sa_family))
     SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr,
     (MAI_SERVPORT_STR *) 0, sa->sa_family);
 else
     strncpy(state->client_addr.buf, "local"sizeof("local") + 0);
 if (msg_verbose)
     msg_info("connect (%s %s)",
#ifdef AF_LOCAL
       sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
#else
       sa->sa_family == AF_UNIX ? "AF_UNIX" :
#endif
       sa->sa_family == AF_INET ? "AF_INET" :
#ifdef AF_INET6
       sa->sa_family == AF_INET6 ? "AF_INET6" :
#endif
       "unknown protocol family",
       state->client_addr.buf);
 non_blocking(fd, NON_BLOCKING);
 state->stream = vstream_fdopen(fd, O_RDWR);
 vstream_tweak_sock(state->stream);
 state->buffer = vstring_alloc(1024);
 state->read_fn = command_read;
 state->data_state = ST_ANY;
 PUSH_BACK_SET(state, "");
 smtp_timeout_setup(state->stream, var_tmout);
 state->in_mail = 0;
 state->rcpts = 0;
 state->delayed_response = 0;
 state->delayed_args = 0;
 /* Initialize file capture attributes. */
#ifdef AF_INET6
 if (sa->sa_family == AF_INET6)
     state->addr_prefix = "ipv6:";
 else
#endif
     state->addr_prefix = "";

 state->helo_args = 0;
 state->client_proto = enable_lmtp ? "LMTP" : "SMTP";
 state->start_time = 0;
 state->id = 0;
 state->dump_file = 0;

 /*
  * We use the smtp_stream module to produce output. That module
  * throws an exception via vstream_longjmp() in case of a timeout or
  * lost connection error. Therefore we must prepare to handle these
  * exceptions with vstream_setjmp().
 */

 switch (vstream_setjmp(state->stream)) {

 default:
     msg_panic("unknown read/write error");
     /* NOTREACHED */

 case SMTP_ERR_TIME:
     msg_warn("write timeout");
     disconnect(state);
     return;

 case SMTP_ERR_EOF:
     msg_warn("lost connection");
     disconnect(state);
     return;

 case 0:
     if (command_resp(state, command_table, "connect""") < 0)
  disconnect(state);
     else if (command_table->delay == 0) {
  event_enable_read(fd, read_event, (void *) state);
  event_request_timer(read_timeout, (void *) state, var_tmout);
     }
 }
    }
}

/* usage - explain */

static void usage(char *myname)
{
    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);
}

MAIL_VERSION_STAMP_DECLARE;

int     main(int argc, char **argv)
{
    int     backlog;
    int     ch;
    int     delay;
    const char *protocols = INET_PROTO_NAME_ALL;
    const char *root_dir = 0;
    const char *user_privs = 0;

    /*
     * Fingerprint executables and core dumps.
     */

    MAIL_VERSION_STAMP_ALLOCATE;

    /*
     * Fix 20051207.
     */

    signal(SIGPIPE, SIG_IGN);

    /*
     * Initialize diagnostics.
     */

    msg_vstream_init(argv[0], VSTREAM_ERR);

    /*
     * Parse JCL.
     */

    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) {
 switch (ch) {
 case '4':
     protocols = INET_PROTO_NAME_IPV4;
     break;
 case '6':
     protocols = INET_PROTO_NAME_IPV6;
     break;
 case '8':
     disable_8bitmime = 1;
     break;
 case 'a':
     disable_saslauth = 1;
     break;
 case 'A':
     if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0)
  usage(argv[0]);
     break;
 case 'b':
     if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) {
  msg_error("bad soft error reply: %s", optarg);
  usage(argv[0]);
     } else
  soft_error_resp = optarg;
     break;
 case 'B':
     if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) {
  msg_error("bad hard error reply: %s", optarg);
  usage(argv[0]);
     } else
  hard_error_resp = optarg;
     break;
 case 'c':
     show_count++;
     break;
 case 'C':
     disable_xclient = 1;
     reset_cmd_flags("xclient", FLAG_ENABLE);
     break;
 case 'd':
     single_template = optarg;
     break;
 case 'D':
     shared_template = optarg;
     break;
 case 'e':
     disable_esmtp = 1;
     break;
 case 'E':
     disable_enh_status = 1;
     break;
 case 'f':
     set_cmds_flags(optarg, FLAG_HARD_ERR);
     disable_pipelining = 1;
     break;
 case 'F':
     disable_xforward = 1;
     reset_cmd_flags("xforward", FLAG_ENABLE);
     break;
 case 'h':
     var_myhostname = optarg;
     break;
 case 'H':
     if ((data_read_delay = atoi(optarg)) <= 0)
  msg_fatal("bad data read delay: %s", optarg);
     break;
 case 'L':
     enable_lmtp = 1;
     break;
 case 'm':
     if ((max_client_count = atoi(optarg)) <= 0)
  msg_fatal("bad concurrency limit: %s", optarg);
     break;
 case 'M':
     if ((max_msg_quit_count = atoi(optarg)) <= 0)
  msg_fatal("bad message quit count: %s", optarg);
     break;
 case 'n':
     if ((max_quit_count = atoi(optarg)) <= 0)
  msg_fatal("bad quit count: %s", optarg);
     break;
 case 'N':
     disable_dsn = 1;
     break;
 case 'p':
     disable_pipelining = 1;
     break;
 case 'P':
     pretend_pix = 1;
     disable_esmtp = 1;
     break;
 case 'q':
     set_cmds_flags(optarg, FLAG_DISCONNECT);
     break;
 case 'Q':
     set_cmds_flags(optarg, FLAG_CLOSE);
     break;
 case 'r':
     set_cmds_flags(optarg, FLAG_SOFT_ERR);
     disable_pipelining = 1;
     break;
 case 'R':
     root_dir = optarg;
     break;
 case 's':
     openlog(basename(argv[0]), LOG_PID, LOG_MAIL);
     set_cmds_flags(optarg, FLAG_SYSLOG);
     break;
 case 'S':
     start_string = vstring_alloc(10);
     unescape(start_string, optarg);
     break;
 case 't':
     if ((var_tmout = atoi(optarg)) <= 0)
  msg_fatal("bad timeout: %s", optarg);
     break;
 case 'T':
     if ((inet_windowsize = atoi(optarg)) <= 0)
  msg_fatal("bad TCP window size: %s", optarg);
     break;
 case 'u':
     user_privs = optarg;
     break;
 case 'v':
     msg_verbose++;
     break;
 case 'w':
     if ((delay = atoi(optarg)) <= 0)
  usage(argv[0]);
     set_cmd_delay("data", delay, 0);
     break;
 case 'W':
     set_cmd_delay_arg(optarg);
     break;
 default:
     usage(argv[0]);
 }
    }
    if (argc - optind != 2)
 usage(argv[0]);
    if ((backlog = atoi(argv[optind + 1])) <= 0)
 usage(argv[0]);
    if (single_template && shared_template)
 msg_fatal("use only one of -d or -D, but not both");
    if (geteuid() == 0 && user_privs == 0)
 msg_fatal("-u option is required if running as root");

    /*
     * Initialize.
     */

    if (var_myhostname == 0)
 var_myhostname = "smtp-sink";
    set_cmds_flags(enable_lmtp ? "lhlo" :
     disable_esmtp ? "helo" :
     "helo, ehlo", FLAG_ENABLE);
    proto_info = inet_proto_init("protocols", protocols);
    if (strncmp(argv[optind], "unix:"5) == 0) {
 sock = unix_listen(argv[optind] + 5, backlog, BLOCKING);
    } else {
 if (strncmp(argv[optind], "inet:"5) == 0)
     argv[optind] += 5;
 sock = inet_listen(argv[optind], backlog, BLOCKING);
    }
    if (user_privs)
 chroot_uid(root_dir, user_privs);

    if (single_template)
 mysrand((int) time((time_t *) 0));
    else if (shared_template)
 single_template = shared_template;

    /*
     * Start the event handler.
     */

    event_enable_read(sock, connect_event, (void *) 0);
    for (;;)
 event_loop(-1);
}

Messung V0.5 in Prozent
C=85 H=97 G=91

¤ Dauer der Verarbeitung: 0.9 Sekunden  (vorverarbeitet am  2026-08-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.