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

Quelle  milter8.c

  Sprache: C
 

/*++
/* NAME
/* milter8 3
/* SUMMARY
/* MTA-side Sendmail 8 Milter protocol
/* SYNOPSIS
/* #include <milter8.h>
/*
/* MILTER *milter8_create(name, conn_timeout, cmd_timeout, msg_timeout,
/* protocol, def_action, parent)
/* const char *name;
/* int conn_timeout;
/* int cmd_timeout;
/* int msg_timeout;
/* const char *protocol;
/* const char *def_action;
/* MILTERS *parent;
/*
/* MILTER *milter8_receive(stream)
/* VSTREAM *stream;
/* DESCRIPTION
/* This module implements the MTA side of the Sendmail 8 mail
/* filter protocol.
/*
/* milter8_create() creates a MILTER data structure with virtual
/* functions that implement a client for the Sendmail 8 Milter
/* protocol. These virtual functions are then invoked via the
/* milter(3) interface.  The *timeout, protocol and def_action
/* arguments come directly from milter_create(). The parent
/* argument specifies a context for content editing.
/*
/* milter8_receive() receives a mail filter definition from the
/* specified stream. The result is zero in case of success.
/*
/* Arguments:
/* .IP name
/* The Milter application endpoint, either inet:host:port or
/* unix:/pathname.
/* DIAGNOSTICS
/* Panic: interface violation.  Fatal errors: out of memory.
/* CONFIGURATION PARAMETERS
/* milter8_protocol, protocol version and extensions
/* SEE ALSO
/* milter(3) generic Milter interface
/* 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 <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stddef.h>   /* offsetof() */
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>   /* INT_MAX */

#ifndef SHUT_RDWR
#define SHUT_RDWR 2
#endif

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

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <split_at.h>
#include <connect.h>
#include <argv.h>
#include <name_mask.h>
#include <name_code.h>
#include <stringops.h>
#include <compat_va_copy.h>

/* Global library. */

#include <mail_params.h>
#include <mail_proto.h>
#include <rec_type.h>
#include <record.h>
#include <mime_state.h>
#include <is_header.h>

/* Postfix Milter library. */

#include <milter.h>

/* Application-specific. */

 /*
  * Use our own protocol definitions, so that Postfix can be built even when
  * libmilter is not installed. This means that we must specify the libmilter
  * protocol version in main.cf, and that we must send only the commands that
  * are supported for that protocol version.
  */


 /*
  * Commands from MTA to filter.
  */

#define SMFIC_ABORT  'A' /* Abort */
#define SMFIC_BODY  'B' /* Body chunk */
#define SMFIC_CONNECT  'C' /* Connection information */
#define SMFIC_MACRO  'D' /* Define macro */
#define SMFIC_BODYEOB  'E' /* final body chunk (End) */
#define SMFIC_HELO  'H' /* HELO/EHLO */
#define SMFIC_HEADER  'L' /* Header */
#define SMFIC_MAIL  'M' /* MAIL from */
#define SMFIC_EOH  'N' /* EOH */
#define SMFIC_OPTNEG  'O' /* Option negotiation */
#define SMFIC_QUIT  'Q' /* QUIT */
#define SMFIC_RCPT  'R' /* RCPT to */
 /* Introduced with Sendmail 8.13. */
#define SMFIC_DATA  'T' /* DATA */
#define SMFIC_UNKNOWN  'U' /* Any unknown command */
 /* Introduced with Sendmail 8.14. */
#define SMFIC_QUIT_NC  'K' /* Quit + new connection */

static const NAME_CODE smfic_table[] = {
    "SMFIC_ABORT", SMFIC_ABORT,
    "SMFIC_BODY", SMFIC_BODY,
    "SMFIC_CONNECT", SMFIC_CONNECT,
    "SMFIC_MACRO", SMFIC_MACRO,
    "SMFIC_BODYEOB", SMFIC_BODYEOB,
    "SMFIC_HELO", SMFIC_HELO,
    "SMFIC_HEADER", SMFIC_HEADER,
    "SMFIC_MAIL", SMFIC_MAIL,
    "SMFIC_EOH", SMFIC_EOH,
    "SMFIC_OPTNEG", SMFIC_OPTNEG,
    "SMFIC_QUIT", SMFIC_QUIT,
    "SMFIC_RCPT", SMFIC_RCPT,
    /* Introduced with Sendmail 8.13. */
    "SMFIC_DATA", SMFIC_DATA,
    "SMFIC_UNKNOWN", SMFIC_UNKNOWN,
    /* Introduced with Sendmail 8.14. */
    "SMFIC_QUIT_NC", SMFIC_QUIT_NC,
    00,
};

 /*
  * Responses from filter to MTA.
  */

#define SMFIR_ADDRCPT  '+' /* add recipient */
#define SMFIR_DELRCPT  '-' /* remove recipient */
#define SMFIR_ACCEPT  'a' /* accept */
#define SMFIR_REPLBODY  'b' /* replace body (chunk) */
#define SMFIR_CONTINUE  'c' /* continue */
#define SMFIR_DISCARD  'd' /* discard */
#define SMFIR_CONN_FAIL  'f' /* cause a connection failure */
#define SMFIR_CHGHEADER  'm' /* change header */
#define SMFIR_PROGRESS  'p' /* progress */
#define SMFIR_REJECT  'r' /* reject */
#define SMFIR_TEMPFAIL  't' /* tempfail */
#define SMFIR_SHUTDOWN  '4' /* 421: shutdown (internal to MTA) */
#define SMFIR_ADDHEADER  'h' /* add header */
#define SMFIR_INSHEADER  'i' /* insert header */
#define SMFIR_REPLYCODE  'y' /* reply code etc */
#define SMFIR_QUARANTINE 'q' /* quarantine */
 /* Introduced with Sendmail 8.14. */
#define SMFIR_SKIP  's' /* skip further events of this type */
#define SMFIR_CHGFROM  'e' /* change sender (incl. ESMTP args) */
#define SMFIR_ADDRCPT_PAR '2' /* add recipient (incl. ESMTP args) */
#define SMFIR_SETSYMLIST 'l' /* set list of symbols (macros) */

static const NAME_CODE smfir_table[] = {
    "SMFIR_ADDRCPT", SMFIR_ADDRCPT,
    "SMFIR_DELRCPT", SMFIR_DELRCPT,
    "SMFIR_ACCEPT", SMFIR_ACCEPT,
    "SMFIR_REPLBODY", SMFIR_REPLBODY,
    "SMFIR_CONTINUE", SMFIR_CONTINUE,
    "SMFIR_DISCARD", SMFIR_DISCARD,
    "SMFIR_CONN_FAIL", SMFIR_CONN_FAIL,
    "SMFIR_CHGHEADER", SMFIR_CHGHEADER,
    "SMFIR_PROGRESS", SMFIR_PROGRESS,
    "SMFIR_REJECT", SMFIR_REJECT,
    "SMFIR_TEMPFAIL", SMFIR_TEMPFAIL,
    "SMFIR_SHUTDOWN", SMFIR_SHUTDOWN,
    "SMFIR_ADDHEADER", SMFIR_ADDHEADER,
    "SMFIR_INSHEADER", SMFIR_INSHEADER,
    "SMFIR_REPLYCODE", SMFIR_REPLYCODE,
    "SMFIR_QUARANTINE", SMFIR_QUARANTINE,
    /* Introduced with Sendmail 8.14. */
    "SMFIR_SKIP", SMFIR_SKIP,
    "SMFIR_CHGFROM", SMFIR_CHGFROM,
    "SMFIR_ADDRCPT_PAR", SMFIR_ADDRCPT_PAR,
    "SMFIR_SETSYMLIST", SMFIR_SETSYMLIST,
    00,
};

 /*
  * Commands that the filter does not want to receive, and replies that the
  * filter will not send. Plus some other random stuff.
  */

#define SMFIP_NOCONNECT  (1L<<0/* filter does not want connect info */
#define SMFIP_NOHELO  (1L<<1/* filter does not want HELO info */
#define SMFIP_NOMAIL  (1L<<2/* filter does not want MAIL info */
#define SMFIP_NORCPT  (1L<<3/* filter does not want RCPT info */
#define SMFIP_NOBODY  (1L<<4/* filter does not want body */
#define SMFIP_NOHDRS  (1L<<5/* filter does not want headers */
#define SMFIP_NOEOH  (1L<<6/* filter does not want EOH */
 /* Introduced with Sendmail 8.13. */
#define SMFIP_NOHREPL  SMFIP_NR_HDR
 /* Introduced with Sendmail 8.14. */
#define SMFIP_NR_HDR  (1L<<7/* filter won't reply for header */
#define SMFIP_NOUNKNOWN  (1L<<8/* filter does not want unknown cmd */
#define SMFIP_NODATA  (1L<<9/* filter does not want DATA */
#define SMFIP_SKIP  (1L<<10)/* MTA supports SMFIR_SKIP */
#define SMFIP_RCPT_REJ  (1L<<11)/* filter wants rejected RCPTs */
#define SMFIP_NR_CONN  (1L<<12)/* filter won't reply for connect */
#define SMFIP_NR_HELO  (1L<<13)/* filter won't reply for HELO */
#define SMFIP_NR_MAIL  (1L<<14)/* filter won't reply for MAIL */
#define SMFIP_NR_RCPT  (1L<<15)/* filter won't reply for RCPT */
#define SMFIP_NR_DATA  (1L<<16)/* filter won't reply for DATA */
#define SMFIP_NR_UNKN  (1L<<17)/* filter won't reply for UNKNOWN */
#define SMFIP_NR_EOH  (1L<<18)/* filter won't reply for eoh */
#define SMFIP_NR_BODY  (1L<<19)/* filter won't reply for body chunk */
#define SMFIP_HDR_LEADSPC (1L<<20)/* header value has leading space */

#define SMFIP_NOSEND_MASK \
 (SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT \
 | SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH | SMFIP_NOUNKNOWN \
 | SMFIP_NODATA)

#define SMFIP_NOREPLY_MASK \
 (SMFIP_NR_CONN | SMFIP_NR_HELO | SMFIP_NR_MAIL | SMFIP_NR_RCPT \
 | SMFIP_NR_DATA | SMFIP_NR_UNKN | SMFIP_NR_HDR | SMFIP_NR_EOH | \
 SMFIP_NR_BODY)

static const NAME_MASK smfip_table[] = {
    "SMFIP_NOCONNECT", SMFIP_NOCONNECT,
    "SMFIP_NOHELO", SMFIP_NOHELO,
    "SMFIP_NOMAIL", SMFIP_NOMAIL,
    "SMFIP_NORCPT", SMFIP_NORCPT,
    "SMFIP_NOBODY", SMFIP_NOBODY,
    "SMFIP_NOHDRS", SMFIP_NOHDRS,
    "SMFIP_NOEOH", SMFIP_NOEOH,
    /* Introduced with Sendmail 8.14. */
    "SMFIP_NR_HDR", SMFIP_NR_HDR,
    "SMFIP_NOUNKNOWN", SMFIP_NOUNKNOWN,
    "SMFIP_NODATA", SMFIP_NODATA,
    "SMFIP_SKIP", SMFIP_SKIP,
    "SMFIP_RCPT_REJ", SMFIP_RCPT_REJ,
    "SMFIP_NR_CONN", SMFIP_NR_CONN,
    "SMFIP_NR_HELO", SMFIP_NR_HELO,
    "SMFIP_NR_MAIL", SMFIP_NR_MAIL,
    "SMFIP_NR_RCPT", SMFIP_NR_RCPT,
    "SMFIP_NR_DATA", SMFIP_NR_DATA,
    "SMFIP_NR_UNKN", SMFIP_NR_UNKN,
    "SMFIP_NR_EOH", SMFIP_NR_EOH,
    "SMFIP_NR_BODY", SMFIP_NR_BODY,
    "SMFIP_HDR_LEADSPC", SMFIP_HDR_LEADSPC,
    00,
};

 /*
  * Options that the filter may send at initial handshake time, and message
  * modifications that the filter may request at the end of the message body.
  */

#define SMFIF_ADDHDRS  (1L<<0/* filter may add headers */
#define SMFIF_CHGBODY  (1L<<1/* filter may replace body */
#define SMFIF_ADDRCPT  (1L<<2/* filter may add recipients */
#define SMFIF_DELRCPT  (1L<<3/* filter may delete recipients */
#define SMFIF_CHGHDRS  (1L<<4/* filter may change/delete headers */
 /* Introduced with Sendmail 8.13. */
#define SMFIF_QUARANTINE  (1L<<5/* filter may quarantine envelope */
 /* Introduced with Sendmail 8.14. */
#define SMFIF_CHGFROM  (1L<<6/* filter may replace sender */
#define SMFIF_ADDRCPT_PAR (1L<<7/* filter may add recipients + args */
#define SMFIF_SETSYMLIST (1L<<8/* filter may send macro names */

static const NAME_MASK smfif_table[] = {
    "SMFIF_ADDHDRS", SMFIF_ADDHDRS,
    "SMFIF_CHGBODY", SMFIF_CHGBODY,
    "SMFIF_ADDRCPT", SMFIF_ADDRCPT,
    "SMFIF_DELRCPT", SMFIF_DELRCPT,
    "SMFIF_CHGHDRS", SMFIF_CHGHDRS,
    /* Introduced with Sendmail 8.13. */
    "SMFIF_QUARANTINE", SMFIF_QUARANTINE,
    /* Introduced with Sendmail 8.14. */
    "SMFIF_CHGFROM", SMFIF_CHGFROM,
    "SMFIF_ADDRCPT_PAR", SMFIF_ADDRCPT_PAR,
    "SMFIF_SETSYMLIST", SMFIF_SETSYMLIST,
    00,
};

 /*
  * Network protocol families, used when sending CONNECT information.
  */

#define SMFIA_UNKNOWN  'U' /* unknown */
#define SMFIA_UNIX  'L' /* unix/local */
#define SMFIA_INET  '4' /* inet */
#define SMFIA_INET6  '6' /* inet6 */

 /*
  * External macro class numbers, to identify the optional macro name lists
  * that may be sent after the initial negotiation header.
  */

#define SMFIM_CONNECT 0  /* macros for connect */
#define SMFIM_HELO 1  /* macros for HELO */
#define SMFIM_ENVFROM 2  /* macros for MAIL */
#define SMFIM_ENVRCPT 3  /* macros for RCPT */
#define SMFIM_DATA 4  /* macros for DATA */
#define SMFIM_EOM 5  /* macros for end-of-message */
#define SMFIM_EOH 6  /* macros for end-of-header */

static const NAME_CODE smfim_table[] = {
    "SMFIM_CONNECT", SMFIM_CONNECT,
    "SMFIM_HELO", SMFIM_HELO,
    "SMFIM_ENVFROM", SMFIM_ENVFROM,
    "SMFIM_ENVRCPT", SMFIM_ENVRCPT,
    "SMFIM_DATA", SMFIM_DATA,
    "SMFIM_EOM", SMFIM_EOM,
    "SMFIM_EOH", SMFIM_EOH,
    00,
};

 /*
  * Mapping from external macro class numbers to our internal MILTER_MACROS
  * structure members, without using a switch statement.
  */

static const size_t milter8_macro_offsets[] = {
    offsetof(MILTER_MACROS, conn_macros), /* SMFIM_CONNECT */
    offsetof(MILTER_MACROS, helo_macros), /* SMFIM_HELO */
    offsetof(MILTER_MACROS, mail_macros), /* SMFIM_ENVFROM */
    offsetof(MILTER_MACROS, rcpt_macros), /* SMFIM_ENVRCPT */
    offsetof(MILTER_MACROS, data_macros), /* SMFIM_DATA */
    offsetof(MILTER_MACROS, eod_macros),/* Note: SMFIM_EOM < SMFIM_EOH */
    offsetof(MILTER_MACROS, eoh_macros),/* Note: SMFIM_EOH > SMFIM_EOM */
};

#define MILTER8_MACRO_PTR(__macros, __class) \
 ((char **) (((char *) (__macros)) + milter8_macro_offsets[(__class)]))

 /*
  * How much buffer space is available for sending body content.
  */

#define MILTER_CHUNK_SIZE 65535 /* body chunk size */

/*#define msg_verbose 2*/

 /*
  * Sendmail 8 mail filter client.
  */

typedef struct {
    MILTER  m;    /* parent class */
    int     conn_timeout;  /* connect timeout */
    int     cmd_timeout;  /* per-command timeout */
    int     msg_timeout;  /* content inspection timeout */
    char   *protocol;   /* protocol version/extension */
    char   *def_action;   /* action if unavailable */
    int     version;   /* application protocol version */
    int     rq_mask;   /* application requests (SMFIF_*) */
    int     ev_mask;   /* application events (SMFIP_*) */
    int     np_mask;   /* events outside my protocol version */
    VSTRING *buf;   /* I/O buffer */
    VSTRING *body;   /* I/O buffer */
    VSTREAM *fp;   /* stream or null (closed) */

    /*
     * Following fields must be reset after successful CONNECT, to avoid
     * leakage from one use to another.
     */

    int     state;   /* MILTER8_STAT_mumble */
    char   *def_reply;   /* error response or null */
    int     skip_event_type;  /* skip operations of this type */
} MILTER8;

 /*
  * XXX Sendmail 8 libmilter automatically closes the MTA-to-filter socket
  * when it finds out that the SMTP client has disconnected. Because of this
  * behavior, Postfix has to open a new MTA-to-filter socket each time an
  * SMTP client connects.
  */

#define LIBMILTER_AUTO_DISCONNECT

 /*
  * Milter internal state. For the external representation we use SMTP
  * replies (4XX X.Y.Z text, 5XX X.Y.Z text) and one-letter strings
  * (H=quarantine, D=discard, S=shutdown).
  */

#define MILTER8_STAT_ERROR 1 /* error, must be non-zero */
#define MILTER8_STAT_CLOSED 2 /* no connection */
#define MILTER8_STAT_READY 3 /* wait for connect event */
#define MILTER8_STAT_ENVELOPE 4 /* in envelope */
#define MILTER8_STAT_MESSAGE 5 /* in message */
#define MILTER8_STAT_ACCEPT_CON 6 /* accept all commands */
#define MILTER8_STAT_ACCEPT_MSG 7 /* accept one message */
#define MILTER8_STAT_REJECT_CON 8 /* reject all commands */

 /*
  * Protocol formatting requests. Note: the terms "long" and "short" refer to
  * the data types manipulated by htonl(), htons() and friends. These types
  * are network specific, not host platform specific.
  */

#define MILTER8_DATA_END 0 /* no more arguments */
#define MILTER8_DATA_HLONG 1 /* host long */
#define MILTER8_DATA_BUFFER 2 /* network-formatted buffer */
#define MILTER8_DATA_STRING 3 /* null-terminated string */
#define MILTER8_DATA_NSHORT 4 /* network short */
#define MILTER8_DATA_ARGV 5 /* array of null-terminated strings */
#define MILTER8_DATA_OCTET 6 /* byte */
#define MILTER8_DATA_MORE 7 /* more arguments in next call */

 /*
  * We don't accept insane amounts of data.
  */

#define XXX_MAX_DATA (INT_MAX / 2)
#define XXX_TIMEOUT 10

 /*
  * We implement the protocol up to and including version 6, and configure in
  * main.cf what protocol version we will use. The version is the first data
  * item in the SMFIC_OPTNEG packet.
  * 
  * We must send only events that are defined for the specified protocol
  * version. Libmilter may disconnect when we send unexpected events.
  * 
  * The following events are supported in all our milter protocol versions.
  */

#define MILTER8_V2_PROTO_MASK \
 (SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT | \
 SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH)

 /*
  * Events supported by later versions.
  */

#define MILTER8_V3_PROTO_MASK (MILTER8_V2_PROTO_MASK | SMFIP_NOUNKNOWN)
#define MILTER8_V4_PROTO_MASK (MILTER8_V3_PROTO_MASK | SMFIP_NODATA)
#define MILTER8_V6_PROTO_MASK \
 (MILTER8_V4_PROTO_MASK | SMFIP_SKIP | SMFIP_RCPT_REJ \
 | SMFIP_NOREPLY_MASK | SMFIP_HDR_LEADSPC)

 /*
  * What events we can send to the milter application. The milter8_protocol
  * parameter can specify a protocol version as well as protocol extensions
  * such as "no_header_reply", a feature that speeds up the protocol by not
  * sending a filter reply for every individual message header.
  * 
  * This looks unclean because the user can specify multiple protocol versions,
  * but that is taken care of by the table that follows this one.
  * 
  * XXX Is this still needed? Sendmail 8.14 provides a proper way to negotiate
  * what replies the mail filter will send.
  * 
  * XXX Keep this table in reverse numerical order. This is needed by the code
  * that implements compatibility with older Milter protocol versions.
  */

static const NAME_CODE milter8_event_masks[] = {
    "6", MILTER8_V6_PROTO_MASK,
    "4", MILTER8_V4_PROTO_MASK,
    "3", MILTER8_V3_PROTO_MASK,
    "2", MILTER8_V2_PROTO_MASK,
    "no_header_reply", SMFIP_NOHREPL,
    0, -1,
};

 /*
  * The following table lets us use the same milter8_protocol parameter
  * setting to derive the protocol version number. In this case we ignore
  * protocol extensions such as "no_header_reply", and require that exactly
  * one version number is specified.
  */

static const NAME_CODE milter8_versions[] = {
    "2"2,
    "3"3,
    "4"4,
    "6"6,
    "no_header_reply"0,
    0, -1,
};

/* SLMs. */

#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)

/* milter8_def_reply - set persistent response */

static const char *milter8_def_reply(MILTER8 *milter, const char *reply)
{
    if (milter->def_reply)
 myfree(milter->def_reply);
    milter->def_reply = reply ? mystrdup(reply) : 0;
    return (milter->def_reply);
}

/* milter8_conf_error - local/remote configuration error */

static int milter8_conf_error(MILTER8 *milter)
{
    const char *reply;

    /*
     * XXX When the cleanup server closes its end of the Milter socket while
     * editing a queue file, the SMTP server is left out of sync with the
     * Milter. Sending an ABORT to the Milters will not restore
     * synchronization, because there may be any number of Milter replies
     * already in flight. Workaround: poison the socket and force the SMTP
     * server to abandon it.
     */

    if (milter->fp != 0) {
 (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR);
 (void) vstream_fclose(milter->fp);
 milter->fp = 0;
    }
    if (strcasecmp(milter->def_action, "accept") == 0) {
 reply = 0;
    } else if (strcasecmp(milter->def_action, "shutdown") == 0) {
 reply = "421 4.3.5 Server configuration problem - try again later";
    } else if (strcasecmp(milter->def_action, "quarantine") == 0) {
 reply = "Hdefault_action";
    } else {
 reply = "451 4.3.5 Server configuration problem - try again later";
    }
    milter8_def_reply(milter, reply);
    return (milter->state = MILTER8_STAT_ERROR);
}

/* milter8_comm_error - read/write/format communication error */

static int milter8_comm_error(MILTER8 *milter)
{
    const char *reply;

    /*
     * XXX When the cleanup server closes its end of the Milter socket while
     * editing a queue file, the SMTP server is left out of sync with the
     * Milter. Sending an ABORT to the Milters will not restore
     * synchronization, because there may be any number of Milter replies
     * already in flight. Workaround: poison the socket and force the SMTP
     * server to abandon it.
     */

    if (milter->fp != 0) {
 (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR);
 (void) vstream_fclose(milter->fp);
 milter->fp = 0;
    }
    if (strcasecmp(milter->def_action, "accept") == 0) {
 reply = 0;
    } else if (strcasecmp(milter->def_action, "reject") == 0) {
 reply = "550 5.5.0 Service unavailable";
    } else if (strcasecmp(milter->def_action, "tempfail") == 0) {
 reply = "451 4.7.1 Service unavailable - try again later";
    } else if (strcasecmp(milter->def_action, "shutdown") == 0) {
 reply = "421 4.7.1 Service unavailable - try again later";
    } else if (strcasecmp(milter->def_action, "quarantine") == 0) {
 reply = "Hdefault_action";
    } else {
 msg_warn("milter %s: unrecognized default action: %s",
   milter->m.name, milter->def_action);
 reply = "451 4.3.5 Server configuration problem - try again later";
    }
    milter8_def_reply(milter, reply);
    return (milter->state = MILTER8_STAT_ERROR);
}

/* milter8_close_stream - close stream to milter application */

static void milter8_close_stream(MILTER8 *milter)
{
    if (milter->fp != 0) {
 (void) vstream_fclose(milter->fp);
 milter->fp = 0;
    }
    milter->state = MILTER8_STAT_CLOSED;
}

/* milter8_read_resp - receive command code now, receive data later */

static int milter8_read_resp(MILTER8 *milter, int event, unsigned char *command,
                ssize_t *data_len)
{
    UINT32_TYPE len;
    ssize_t pkt_len;
    const char *smfic_name;
    int     cmd;

    /*
     * Receive the packet length.
     */

    if ((vstream_fread(milter->fp, (void *) &len, UINT32_SIZE))
 != UINT32_SIZE) {
 smfic_name = str_name_code(smfic_table, event);
 msg_warn("milter %s: can't read %s reply packet header: %m",
   milter->m.name, smfic_name != 0 ?
   smfic_name : "(unknown MTA event)");
 return (milter8_comm_error(milter));
    } else if ((pkt_len = ntohl(len)) < 1) {
 msg_warn("milter %s: bad packet length: %ld",
   milter->m.name, (long) pkt_len);
 return (milter8_comm_error(milter));
    } else if (pkt_len > XXX_MAX_DATA) {
 msg_warn("milter %s: unreasonable packet length: %ld > %ld",
   milter->m.name, (long) pkt_len, (long) XXX_MAX_DATA);
 return (milter8_comm_error(milter));
    }

    /*
     * Receive the command code.
     */

    else if ((cmd = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) {
 msg_warn("milter %s: EOF while reading command code: %m",
   milter->m.name);
 return (milter8_comm_error(milter));
    }

    /*
     * All is well.
     */

    else {
 *command = cmd;
 *data_len = pkt_len - 1;
 return (0);
    }
}

static int milter8_read_data(MILTER8 *milter, ssize_t *data_len,...);

/* vmilter8_read_data - read command data */

static int vmilter8_read_data(MILTER8 *milter, ssize_t *data_len, va_list ap)
{
    const char *myname = "milter8_read_data";
    int     arg_type;
    UINT32_TYPE net_long;
    UINT32_TYPE *host_long_ptr;
    VSTRING *buf;
    int     ch;

    while ((arg_type = va_arg(ap, int)) > 0 && arg_type != MILTER8_DATA_MORE) {
 switch (arg_type) {

     /*
      * Host order long.
     */

 case MILTER8_DATA_HLONG:
     if (*data_len < UINT32_SIZE) {
  msg_warn("milter %s: input packet too short for network long",
    milter->m.name);
  return (milter8_comm_error(milter));
     }
     host_long_ptr = va_arg(ap, UINT32_TYPE *);
     if (vstream_fread(milter->fp, (void *) &net_long, UINT32_SIZE)
  != UINT32_SIZE) {
  msg_warn("milter %s: EOF while reading network long: %m",
    milter->m.name);
  return (milter8_comm_error(milter));
     }
     *data_len -= UINT32_SIZE;
     *host_long_ptr = ntohl(net_long);
     break;

     /*
      * Raw on-the-wire format, without explicit null terminator.
     */

 case MILTER8_DATA_BUFFER:
     if (*data_len < 0) {
  msg_warn("milter %s: no data in input packet", milter->m.name);
  return (milter8_comm_error(milter));
     }
     buf = va_arg(ap, VSTRING *);
     if (vstream_fread_buf(milter->fp, buf, *data_len)
  != *data_len) {
  msg_warn("milter %s: EOF while reading data: %m", milter->m.name);
  return (milter8_comm_error(milter));
     }
     *data_len = 0;
     break;

     /*
      * Pointer to null-terminated string.
     */

 case MILTER8_DATA_STRING:
     if (*data_len < 1) {
  msg_warn("milter %s: packet too short for string",
    milter->m.name);
  return (milter8_comm_error(milter));
     }
     buf = va_arg(ap, VSTRING *);
     VSTRING_RESET(buf);
     for (;;) {
  if ((ch = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) {
      msg_warn("%s: milter %s: EOF while reading string: %m",
        myname, milter->m.name);
      return (milter8_comm_error(milter));
  }
  *data_len -= 1;
  if (ch == 0)
      break;
  VSTRING_ADDCH(buf, ch);
  if (*data_len <= 0) {
      msg_warn("%s: milter %s: missing string null termimator",
        myname, milter->m.name);
      return (milter8_comm_error(milter));
  }
     }
     VSTRING_TERMINATE(buf);
     break;

     /*
      * Error.
     */

 default:
     msg_panic("%s: unknown argument type: %d", myname, arg_type);
 }
    }

    /*
     * Sanity checks. We may have excess data when the sender is confused. We
     * may have a negative count when we're confused ourselves.
     */

    if (arg_type != MILTER8_DATA_MORE && *data_len > 0) {
 msg_warn("%s: left-over data %ld bytes", myname, (long) *data_len);
 return (milter8_comm_error(milter));
    }
    if (*data_len < 0)
 msg_panic("%s: bad left-over data count %ld",
    myname, (long) *data_len);
    return (0);
}

/* milter8_read_data - read command data */

static int milter8_read_data(MILTER8 *milter, ssize_t *data_len,...)
{
    va_list ap;
    int     ret;

    va_start(ap, data_len);
    ret = vmilter8_read_data(milter, data_len, ap);
    va_end(ap);
    return (ret);
}

/* vmilter8_size_data - compute command data length */

static ssize_t vmilter8_size_data(va_list ap)
{
    const char *myname = "vmilter8_size_data";
    ssize_t data_len;
    int     arg_type;
    VSTRING *buf;
    const char *str;
    const char **cpp;

    /*
     * Compute data size.
     */

    for (data_len = 0; (arg_type = va_arg(ap, int)) > 0/* void */ ) {
 switch (arg_type) {

     /*
      * Host order long.
     */

 case MILTER8_DATA_HLONG:
     (void) va_arg(ap, UINT32_TYPE);
     data_len += UINT32_SIZE;
     break;

     /*
      * Raw on-the-wire format.
     */

 case MILTER8_DATA_BUFFER:
     buf = va_arg(ap, VSTRING *);
     data_len += LEN(buf);
     break;

     /*
      * Pointer to null-terminated string.
     */

 case MILTER8_DATA_STRING:
     str = va_arg(ap, char *);
     data_len += strlen(str) + 1;
     break;

     /*
      * Array of pointers to null-terminated strings.
     */

 case MILTER8_DATA_ARGV:
     for (cpp = va_arg(ap, const char **); *cpp; cpp++)
  data_len += strlen(*cpp) + 1;
     break;

     /*
      * Network order short, promoted to int.
     */

 case MILTER8_DATA_NSHORT:
     (void) va_arg(ap, unsigned);
     data_len += UINT16_SIZE;
     break;

     /*
      * Octet, promoted to int.
     */

 case MILTER8_DATA_OCTET:
     (void) va_arg(ap, unsigned);
     data_len += 1;
     break;

     /*
      * Error.
     */

 default:
     msg_panic("%s: bad argument type: %d", myname, arg_type);
 }
    }
    va_end(ap);
    return (data_len);
}

/* vmilter8_write_cmd - write command to Sendmail 8 Milter */

static int vmilter8_write_cmd(MILTER8 *milter, int command, ssize_t data_len,
                 va_list ap)
{
    const char *myname = "vmilter8_write_cmd";
    int     arg_type;
    UINT32_TYPE pkt_len;
    UINT32_TYPE host_long;
    UINT32_TYPE net_long;
    UINT16_TYPE net_short;
    VSTRING *buf;
    const char *str;
    const char **cpp;
    char    ch;

    /*
     * Deliver the packet.
     */

    if ((pkt_len = 1 + data_len) < 1)
 msg_panic("%s: bad packet length %d", myname, pkt_len);
    pkt_len = htonl(pkt_len);
    (void) vstream_fwrite(milter->fp, (void *) &pkt_len, UINT32_SIZE);
    (void) VSTREAM_PUTC(command, milter->fp);
    while ((arg_type = va_arg(ap, int)) > 0) {
 switch (arg_type) {

     /*
      * Network long.
     */

 case MILTER8_DATA_HLONG:
     host_long = va_arg(ap, UINT32_TYPE);
     net_long = htonl(host_long);
     (void) vstream_fwrite(milter->fp, (void *) &net_long, UINT32_SIZE);
     break;

     /*
      * Raw on-the-wire format.
     */

 case MILTER8_DATA_BUFFER:
     buf = va_arg(ap, VSTRING *);
     (void) vstream_fwrite(milter->fp, STR(buf), LEN(buf));
     break;

     /*
      * Pointer to null-terminated string.
     */

 case MILTER8_DATA_STRING:
     str = va_arg(ap, char *);
     (void) vstream_fwrite(milter->fp, str, strlen(str) + 1);
     break;

     /*
      * Octet, promoted to int.
     */

 case MILTER8_DATA_OCTET:
     ch = va_arg(ap, unsigned);
     (void) vstream_fwrite(milter->fp, &ch, 1);
     break;

     /*
      * Array of pointers to null-terminated strings.
     */

 case MILTER8_DATA_ARGV:
     for (cpp = va_arg(ap, const char **); *cpp; cpp++)
  (void) vstream_fwrite(milter->fp, *cpp, strlen(*cpp) + 1);
     break;

     /*
      * Network order short, promoted to int.
     */

 case MILTER8_DATA_NSHORT:
     net_short = va_arg(ap, unsigned);
     (void) vstream_fwrite(milter->fp, (void *) &net_short, UINT16_SIZE);
     break;

     /*
      * Error.
     */

 default:
     msg_panic("%s: bad argument type: %d", myname, arg_type);
 }

 /*
  * Report errors immediately.
 */

 if (vstream_ferror(milter->fp)) {
     msg_warn("milter %s: error writing command: %m", milter->m.name);
     milter8_comm_error(milter);
     break;
 }
    }
    va_end(ap);
    return (milter->state == MILTER8_STAT_ERROR);
}

/* milter8_write_cmd - write command to Sendmail 8 Milter */

static int milter8_write_cmd(MILTER8 *milter, int command,...)
{
    va_list ap;
    va_list ap2;
    ssize_t data_len;
    int     err;

    /*
     * Initialize argument lists.
     */

    va_start(ap, command);
    VA_COPY(ap2, ap);

    /*
     * Size the command data.
     */

    data_len = vmilter8_size_data(ap);
    va_end(ap);

    /*
     * Send the command and data.
     */

    err = vmilter8_write_cmd(milter, command, data_len, ap2);
    va_end(ap2);

    return (err);
}

/* milter8_event - report event and receive reply */

static const char *milter8_event(MILTER8 *milter, int event,
             int skip_event_flag,
             int skip_reply,
             ARGV *macros,...)
{
    const char *myname = "milter8_event";
    va_list ap;
    va_list ap2;
    ssize_t data_len;
    int     err;
    unsigned char cmd;
    ssize_t data_size;
    const char *smfic_name;
    const char *smfir_name;
    MILTERS *parent = milter->m.parent;
    UINT32_TYPE index;
    const char *edit_resp = 0;
    const char *retval = 0;
    VSTRING *body_line_buf = 0;
    int     done = 0;
    int     body_edit_lockout = 0;

#define DONT_SKIP_REPLY 0

    /*
     * Sanity check.
     */

    if (milter->fp == 0 || milter->def_reply != 0) {
 msg_warn("%s: attempt to send event %s to milter %s after error",
   myname,
   (smfic_name = str_name_code(smfic_table, event)) != 0 ?
   smfic_name : "(unknown MTA event)", milter->m.name);
 return (milter->def_reply);
    }

    /*
     * Skip this event if it doesn't exist in the protocol that I announced.
     */

    if ((skip_event_flag & milter->np_mask) != 0) {
 if (msg_verbose)
     msg_info("skipping non-protocol event %s for milter %s",
       (smfic_name = str_name_code(smfic_table, event)) != 0 ?
       smfic_name : "(unknown MTA event)", milter->m.name);
 return (milter->def_reply);
    }

    /*
     * Skip further events of this type if the filter told us so.
     */

    if (milter->skip_event_type != 0) {
 if (event == milter->skip_event_type) {
     if (msg_verbose)
  msg_info("skipping event %s after SMFIR_SKIP from milter %s",
       (smfic_name = str_name_code(smfic_table, event)) != 0 ?
    smfic_name : "(unknown MTA event)", milter->m.name);
     return (milter->def_reply);
 } else {
     milter->skip_event_type = 0;
 }
    }

    /*
     * Send the macros for this event, even when we're not reporting the
     * event itself. This does not introduce a performance problem because
     * we're sending macros and event parameters in one VSTREAM transaction.
     * 
     * XXX Is this still necessary?
     */

    if (msg_verbose) {
 VSTRING *buf = vstring_alloc(100);

 if (macros) {
     if (macros->argc > 0) {
  char  **cpp;

  for (cpp = macros->argv; *cpp && cpp[1]; cpp += 2)
      vstring_sprintf_append(buf, " %s=%s", *cpp, cpp[1]);
     }
 }
 msg_info("event: %s; macros:%s",
   (smfic_name = str_name_code(smfic_table, event)) != 0 ?
   smfic_name : "(unknown MTA event)", *STR(buf) ?
   STR(buf) : " (none)");
 vstring_free(buf);
    }
    if (macros) {
 if (milter8_write_cmd(milter, SMFIC_MACRO,
         MILTER8_DATA_OCTET, event,
         MILTER8_DATA_ARGV, macros->argv,
         MILTER8_DATA_END) != 0)
     return (milter->def_reply);
    }

    /*
     * Skip this event if the Milter told us not to send it.
     */

    if ((skip_event_flag & milter->ev_mask) != 0) {
 if (msg_verbose)
     msg_info("skipping event %s for milter %s",
       (smfic_name = str_name_code(smfic_table, event)) != 0 ?
       smfic_name : "(unknown MTA event)", milter->m.name);
 return (milter->def_reply);
    }

    /*
     * Initialize argument lists.
     */

    va_start(ap, macros);
    VA_COPY(ap2, ap);

    /*
     * Compute the command data size. This is necessary because the protocol
     * sends length before content.
     */

    data_len = vmilter8_size_data(ap);
    va_end(ap);

    /*
     * Send the command and data.
     */

    err = vmilter8_write_cmd(milter, event, data_len, ap2);
    va_end(ap2);

    /*
     * C99 requires that we finalize argument lists before returning.
     */

    if (err != 0)
 return (milter->def_reply);

    /*
     * Special feature: don't wait for one reply per header. This allows us
     * to send multiple headers in one VSTREAM transaction, and improves
     * over-all performance.
     */

    if (skip_reply) {
 if (msg_verbose)
     msg_info("skipping reply for event %s from milter %s",
       (smfic_name = str_name_code(smfic_table, event)) != 0 ?
       smfic_name : "(unknown MTA event)", milter->m.name);
 return (milter->def_reply);
    }

    /*
     * Receive the reply or replies.
     * 
     * Intercept all loop exits so that we can do post header/body edit
     * processing.
     * 
     * XXX Bound the loop iteration count.
     * 
     * In the end-of-body stage, the Milter may reply with one or more queue
     * file edit requests before it replies with its final decision: accept,
     * reject, etc. After a local queue file edit error (file too big, media
     * write error), do not close the Milter socket in the cleanup server.
     * Instead skip all further Milter replies until the final decision. This
     * way the Postfix SMTP server stays in sync with the Milter, and Postfix
     * doesn't have to lose the ability to handle multiple deliveries within
     * the same SMTP session. This requires that the Postfix SMTP server uses
     * something other than CLEANUP_STAT_WRITE when it loses contact with the
     * cleanup server.
     */

#define IN_CONNECT_EVENT(e) ((e) == SMFIC_CONNECT || (e) == SMFIC_HELO)

    /*
     * XXX Don't evaluate this macro's argument multiple times. Since we use
     * "continue" the macro can't be enclosed in do .. while (0).
     */

#define MILTER8_EVENT_BREAK(s) { \
 retval = (s); \
 done = 1; \
 continue; \
    }

    while (done == 0) {
 char   *cp;
 char   *rp;
 char    ch;
 char   *next;

 if (milter8_read_resp(milter, event, &cmd, &data_size) != 0)
     MILTER8_EVENT_BREAK(milter->def_reply);
 if (msg_verbose)
     msg_info("reply: %s data %ld bytes",
       (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
       smfir_name : "unknown", (long) data_size);

 /*
  * Handle unfinished message body replacement first.
  * 
  * XXX When SMFIR_REPLBODY is followed by some different request, we
  * assume that the body replacement operation is complete. The queue
  * file editing implementation currently does not support sending
  * part 1 of the body replacement text, doing some other queue file
  * updates, and then sending part 2 of the body replacement text. To
  * avoid loss of data, we log an error when SMFIR_REPLBODY requests
  * are alternated with other requests.
 */

 if (body_line_buf != 0 && cmd != SMFIR_REPLBODY) {
     /* In case the last body replacement line didn't end in CRLF. */
     if (edit_resp == 0 && LEN(body_line_buf) > 0)
  edit_resp = parent->repl_body(parent->chg_context,
           MILTER_BODY_LINE,
           REC_TYPE_NORM,
           body_line_buf);
     if (edit_resp == 0)
  edit_resp = parent->repl_body(parent->chg_context,
           MILTER_BODY_END,
            /* unused */ 0,
           (VSTRING *) 0);
     body_edit_lockout = 1;
     vstring_free(body_line_buf);
     body_line_buf = 0;
 }
 switch (cmd) {

     /*
      * Still working on it.
     */

 case SMFIR_PROGRESS:
     if (data_size != 0)
  break;
     continue;

     /*
      * Decision: continue processing.
     */

 case SMFIR_CONTINUE:
     if (data_size != 0)
  break;
     MILTER8_EVENT_BREAK(milter->def_reply);

     /*
      * Decision: accept this message, or accept all further commands
      * in this SMTP connection. This decision is final (i.e. Sendmail
      * 8 changes receiver state).
     */

 case SMFIR_ACCEPT:
     if (data_size != 0)
  break;
     if (IN_CONNECT_EVENT(event)) {
#ifdef LIBMILTER_AUTO_DISCONNECT
  milter8_close_stream(milter);
#endif
  /* No more events for this SMTP connection. */
  milter->state = MILTER8_STAT_ACCEPT_CON;
     } else {
  /* No more events for this message. */
  milter->state = MILTER8_STAT_ACCEPT_MSG;
     }
     MILTER8_EVENT_BREAK(milter->def_reply);

     /*
      * Decision: accept and silently discard this message. According
      * to the milter API documentation there will be no action when
      * this is requested by a connection-level function. This
      * decision is final (i.e. Sendmail 8 changes receiver state).
     */

 case SMFIR_DISCARD:
     if (data_size != 0)
  break;
     if (IN_CONNECT_EVENT(event)) {
  msg_warn("milter %s: DISCARD action is not allowed "
    "for connect or helo", milter->m.name);
  MILTER8_EVENT_BREAK(milter->def_reply);
     } else {
  /* No more events for this message. */
  milter->state = MILTER8_STAT_ACCEPT_MSG;
  MILTER8_EVENT_BREAK("D");
     }

     /*
      * Decision: reject connection, message or recipient. This
      * decision is final (i.e. Sendmail 8 changes receiver state).
     */

 case SMFIR_REJECT:
     if (data_size != 0)
  break;
     if (IN_CONNECT_EVENT(event)) {
#ifdef LIBMILTER_AUTO_DISCONNECT
  milter8_close_stream(milter);
#endif
  milter->state = MILTER8_STAT_REJECT_CON;
  MILTER8_EVENT_BREAK(milter8_def_reply(milter, "550 5.7.1 Command rejected"));
     } else {
  MILTER8_EVENT_BREAK("550 5.7.1 Command rejected");
     }

     /*
      * Decision: tempfail. This decision is final (i.e. Sendmail 8
      * changes receiver state).
     */

 case SMFIR_TEMPFAIL:
     if (data_size != 0)
  break;
     if (IN_CONNECT_EVENT(event)) {
#ifdef LIBMILTER_AUTO_DISCONNECT
  milter8_close_stream(milter);
#endif
  milter->state = MILTER8_STAT_REJECT_CON;
  MILTER8_EVENT_BREAK(milter8_def_reply(milter,
   "451 4.7.1 Service unavailable - try again later"));
     } else {
  MILTER8_EVENT_BREAK("451 4.7.1 Service unavailable - try again later");
     }

     /*
      * Decision: disconnect. This decision is final (i.e. Sendmail 8
      * changes receiver state).
     */

 case SMFIR_SHUTDOWN:
     if (data_size != 0)
  break;
#ifdef LIBMILTER_AUTO_DISCONNECT
     milter8_close_stream(milter);
#endif
     milter->state = MILTER8_STAT_REJECT_CON;
     MILTER8_EVENT_BREAK(milter8_def_reply(milter, "S"));

     /*
      * Decision: "ddd d.d+.d+ text". This decision is final (i.e.
      * Sendmail 8 changes receiver state). Note: the reply may be in
      * multi-line SMTP format.
      * 
      * XXX Sendmail compatibility: sendmail 8 uses the reply as a format
      * string; therefore any '%' characters in the reply are doubled.
      * Postfix doesn't use replies as format strings; we replace '%%'
      * by '%', and remove single (i.e. invalid) '%' characters.
     */

 case SMFIR_REPLYCODE:
     if (milter8_read_data(milter, &data_size,
      MILTER8_DATA_BUFFER, milter->buf,
      MILTER8_DATA_END) != 0)
  MILTER8_EVENT_BREAK(milter->def_reply);
     /* XXX Enforce this for each line of a multi-line reply. */
     if ((STR(milter->buf)[0] != '4' && STR(milter->buf)[0] != '5')
  || !ISDIGIT(STR(milter->buf)[1])
  || !ISDIGIT(STR(milter->buf)[2])
  || (STR(milter->buf)[3] != ' ' && STR(milter->buf)[3] != '-')
  || (ISDIGIT(STR(milter->buf)[4])
      && (STR(milter->buf)[4] != STR(milter->buf)[0]))) {
  msg_warn("milter %s: malformed reply: %s",
    milter->m.name, STR(milter->buf));
  milter8_conf_error(milter);
  MILTER8_EVENT_BREAK(milter->def_reply);
     }
     if ((rp = cp = strchr(STR(milter->buf), '%')) != 0) {
  for (;;) {
      if ((ch = *cp++) == '%')
   ch = *cp++;
      *rp++ = ch;
      if (ch == 0)
   break;
  }
     }
     if (var_soft_bounce) {
  for (cp = STR(milter->buf); /* void */ ; cp = next) {
      if (cp[0] == '5') {
   cp[0] = '4';
   if (cp[4] == '5')
       cp[4] = '4';
      }
      if ((next = strstr(cp, "\r\n")) == 0)
   break;
      next += 2;
  }
     }
     if (IN_CONNECT_EVENT(event)) {
#ifdef LIBMILTER_AUTO_DISCONNECT
  milter8_close_stream(milter);
#endif
  milter->state = MILTER8_STAT_REJECT_CON;
  MILTER8_EVENT_BREAK(milter8_def_reply(milter, STR(milter->buf)));
     } else {
  MILTER8_EVENT_BREAK(STR(milter->buf));
     }

     /*
      * Decision: quarantine. In Sendmail 8.13 this does not imply a
      * transition in the receiver state (reply, reject, tempfail,
      * accept, discard). We should not transition, either, otherwise
      * we get out of sync.
     */

 case SMFIR_QUARANTINE:{
  VSTRING *reply;
  ssize_t saved_size = data_size;

  if (milter8_read_data(milter, &data_size,
          MILTER8_DATA_BUFFER, milter->buf,
          MILTER8_DATA_END) != 0)
      MILTER8_EVENT_BREAK(milter->def_reply);
  /* XXX This should be reported with a call-back. */
  reply = vstring_alloc(100);
  if (saved_size > 100)
      saved_size = 100;
  vstring_sprintf(reply, "H%.*s", (int) saved_size,
    STR(milter->buf));
  milter8_def_reply(milter, STR(reply));
  vstring_free(reply);
  continue;
     }

     /*
      * Decision: skip further events of this type.
     */

 case SMFIR_SKIP:
     if (data_size != 0)
  break;
     milter->skip_event_type = event;
     MILTER8_EVENT_BREAK(milter->def_reply);

     /*
      * Modification request or error.
     */

 default:
     if (event == SMFIC_BODYEOB) {
  switch (cmd) {

#define MILTER8_HDR_SPACE(m) (((m)->ev_mask & SMFIP_HDR_LEADSPC) ? "" : " ")

      /*
       * Modification request: replace, insert or delete
       * header. Index 1 means the first instance.
     */

  case SMFIR_CHGHEADER:
      if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_HLONG, &index,
       MILTER8_DATA_STRING, milter->buf,
       MILTER8_DATA_STRING, milter->body,
       MILTER8_DATA_END) != 0)
   MILTER8_EVENT_BREAK(milter->def_reply);
      /* Skip to the next request after previous edit error. */
      if (edit_resp)
   continue;
      /* XXX Sendmail 8 compatibility. */
      if (index == 0)
   index = 1;
      if ((ssize_t) index < 1) {
   msg_warn("milter %s: bad change header index: %ld",
     milter->m.name, (long) index);
   milter8_conf_error(milter);
   MILTER8_EVENT_BREAK(milter->def_reply);
      }
      if (LEN(milter->buf) == 0) {
   msg_warn("milter %s: null change header name",
     milter->m.name);
   milter8_conf_error(milter);
   MILTER8_EVENT_BREAK(milter->def_reply);
      }
      if (STR(milter->body)[0])
   edit_resp = parent->upd_header(parent->chg_context,
             (ssize_t) index,
             STR(milter->buf),
        MILTER8_HDR_SPACE(milter),
             STR(milter->body));
      else
   edit_resp = parent->del_header(parent->chg_context,
             (ssize_t) index,
             STR(milter->buf));
      continue;

      /*
       * Modification request: append header.
     */

  case SMFIR_ADDHEADER:
      if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_STRING, milter->buf,
       MILTER8_DATA_STRING, milter->body,
       MILTER8_DATA_END) != 0)
   MILTER8_EVENT_BREAK(milter->def_reply);
      /* Skip to the next request after previous edit error. */
      if (edit_resp)
   continue;
      edit_resp = parent->add_header(parent->chg_context,
         STR(milter->buf),
         MILTER8_HDR_SPACE(milter),
         STR(milter->body));
      continue;

      /*
       * Modification request: insert header. With Sendmail 8,
       * index 0 means the top-most header. We use 1-based
       * indexing for consistency with header change
       * operations.
     */

  case SMFIR_INSHEADER:
      if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_HLONG, &index,
       MILTER8_DATA_STRING, milter->buf,
       MILTER8_DATA_STRING, milter->body,
       MILTER8_DATA_END) != 0)
   MILTER8_EVENT_BREAK(milter->def_reply);
      /* Skip to the next request after previous edit error. */
      if (edit_resp)
   continue;
      if ((ssize_t) index + 1 < 1) {
   msg_warn("milter %s: bad insert header index: %ld",
     milter->m.name, (long) index);
   milter8_conf_error(milter);
   MILTER8_EVENT_BREAK(milter->def_reply);
      }
      edit_resp = parent->ins_header(parent->chg_context,
         (ssize_t) index + 1,
         STR(milter->buf),
         MILTER8_HDR_SPACE(milter),
         STR(milter->body));
      continue;

      /*
       * Modification request: replace sender, with optional
       * ESMTP args.
     */

  case SMFIR_CHGFROM:
      if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_STRING, milter->buf,
       MILTER8_DATA_MORE) != 0)
   MILTER8_EVENT_BREAK(milter->def_reply);
      if (data_size > 0) {
   if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_STRING, milter->body,
           MILTER8_DATA_END) != 0)
       MILTER8_EVENT_BREAK(milter->def_reply);
      } else {
   VSTRING_RESET(milter->body);
   VSTRING_TERMINATE(milter->body);
      }
      /* Skip to the next request after previous edit error. */
      if (edit_resp)
   continue;
      edit_resp = parent->chg_from(parent->chg_context,
       STR(milter->buf),
       STR(milter->body));
      continue;

      /*
       * Modification request: append recipient.
     */

  case SMFIR_ADDRCPT:
      if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_STRING, milter->buf,
       MILTER8_DATA_END) != 0)
   MILTER8_EVENT_BREAK(milter->def_reply);
      /* Skip to the next request after previous edit error. */
      if (edit_resp)
   continue;
      edit_resp = parent->add_rcpt(parent->chg_context,
       STR(milter->buf));
      continue;

      /*
       * Modification request: append recipient, with optional
       * ESMTP args.
     */

  case SMFIR_ADDRCPT_PAR:
      if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_STRING, milter->buf,
       MILTER8_DATA_MORE) != 0)
   MILTER8_EVENT_BREAK(milter->def_reply);
      if (data_size > 0) {
   if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_STRING, milter->body,
           MILTER8_DATA_END) != 0)
       MILTER8_EVENT_BREAK(milter->def_reply);
      } else {
   VSTRING_RESET(milter->body);
   VSTRING_TERMINATE(milter->body);
      }
      /* Skip to the next request after previous edit error. */
      if (edit_resp)
   continue;
      edit_resp = parent->add_rcpt_par(parent->chg_context,
           STR(milter->buf),
           STR(milter->body));
      continue;

      /*
       * Modification request: delete (expansion of) recipient.
     */

  case SMFIR_DELRCPT:
      if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_STRING, milter->buf,
       MILTER8_DATA_END) != 0)
   MILTER8_EVENT_BREAK(milter->def_reply);
      /* Skip to the next request after previous edit error. */
      if (edit_resp)
   continue;
      edit_resp = parent->del_rcpt(parent->chg_context,
       STR(milter->buf));
      continue;

      /*
       * Modification request: replace the message body, and
       * update the message size.
     */

  case SMFIR_REPLBODY:
      if (body_edit_lockout) {
   msg_warn("milter %s: body replacement requests can't "
     "currently be mixed with other requests",
     milter->m.name);
   milter8_conf_error(milter);
   MILTER8_EVENT_BREAK(milter->def_reply);
      }
      if (milter8_read_data(milter, &data_size,
       MILTER8_DATA_BUFFER, milter->body,
       MILTER8_DATA_END) != 0)
   MILTER8_EVENT_BREAK(milter->def_reply);
      /* Skip to the next request after previous edit error. */
      if (edit_resp)
   continue;
      /* Start body replacement. */
      if (body_line_buf == 0) {
   body_line_buf = vstring_alloc(var_line_limit);
   edit_resp = parent->repl_body(parent->chg_context,
            MILTER_BODY_START,
             /* unused */ 0,
            (VSTRING *) 0);
      }
      /* Extract lines from the on-the-wire CRLF format. */
      for (cp = STR(milter->body); edit_resp == 0
    && cp < vstring_end(milter->body); cp++) {
   ch = *(unsigned char *) cp;
   if (ch == '\n') {
       if (LEN(body_line_buf) > 0
    && vstring_end(body_line_buf)[-1] == '\r')
    vstring_truncate(body_line_buf,
       LEN(body_line_buf) - 1);
       edit_resp = parent->repl_body(parent->chg_context,
         MILTER_BODY_LINE,
         REC_TYPE_NORM,
         body_line_buf);
       VSTRING_RESET(body_line_buf);
   } else {
       /* Preserves \r if not followed by \n. */
       if (LEN(body_line_buf) == var_line_limit) {
    edit_resp = parent->repl_body(parent->chg_context,
          MILTER_BODY_LINE,
             REC_TYPE_CONT,
             body_line_buf);
    VSTRING_RESET(body_line_buf);
       }
       VSTRING_ADDCH(body_line_buf, ch);
   }
      }
      continue;
  }
     }
     msg_warn("milter %s: unexpected filter response %s after event %s",
       milter->m.name,
       (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
       smfir_name : "(unknown filter reply)",
       (smfic_name = str_name_code(smfic_table, event)) != 0 ?
       smfic_name : "(unknown MTA event)");
     milter8_comm_error(milter);
     MILTER8_EVENT_BREAK(milter->def_reply);
 }

 /*
  * Get here when the reply was followed by data bytes that weren't
  * supposed to be there.
 */

 msg_warn("milter %s: reply %s was followed by %ld data bytes",
 milter->m.name, (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
   smfir_name : "unknown", (long) data_len);
 milter8_comm_error(milter);
 MILTER8_EVENT_BREAK(milter->def_reply);
    }

    /*
     * Clean up after aborted message body replacement.
     */

    if (body_line_buf)
 vstring_free(body_line_buf);

    /*
     * XXX Some cleanup clients ask the cleanup server to bounce mail for
     * them. In that case we must override a hard reject retval result after
     * queue file update failure. This is not a big problem; the odds are
     * small that a Milter application sends a hard reject after replacing
     * the message body.
     */

    if (edit_resp && (retval == 0 || strchr("DS4", retval[0]) == 0))
 retval = edit_resp;
    return (retval);
}

/* milter8_connect - connect to filter */

static void milter8_connect(MILTER8 *milter)
{
    const char *myname = "milter8_connect";
    ssize_t data_len;
    unsigned char cmd;
    char   *transport;
    char   *endpoint;
    int     (*connect_fn) (const char *, intint);
    int     fd;
    const UINT32_TYPE my_actions = (SMFIF_ADDHDRS | SMFIF_ADDRCPT
        | SMFIF_DELRCPT | SMFIF_CHGHDRS
        | SMFIF_CHGBODY
        | SMFIF_QUARANTINE
        | SMFIF_CHGFROM
        | SMFIF_ADDRCPT_PAR
        | SMFIF_SETSYMLIST
    );
    UINT32_TYPE my_version = 0;
    UINT32_TYPE my_events = 0;
    char   *saved_version;
    char   *cp;
    char   *name;

    /*
     * Sanity check.
     */

    if (milter->fp != 0)
 msg_panic("%s: milter %s: socket is not closed",
    myname, milter->m.name);

    /*
     * For user friendliness reasons the milter_protocol configuration
     * parameter can specify both the protocol version and protocol
     * extensions (e.g., don't reply for each individual message header).
     * 
     * The protocol version is sent as is to the milter application.
     * 
     * The version and extensions determine what events we can send to the
     * milter application.
     * 
     * We don't announce support for events that aren't defined for my protocol
     * version. Today's libmilter implementations don't seem to care, but we
     * don't want to take the risk that a future version will be more picky.
     */

    cp = saved_version = mystrdup(milter->protocol);
    while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
 int     mask;
 int     vers;

 if ((mask = name_code(milter8_event_masks,
         NAME_CODE_FLAG_NONE, name)) == -1
     || (vers = name_code(milter8_versions,
     NAME_CODE_FLAG_NONE, name)) == -1
     || (vers != 0 && my_version != 0)) {
     msg_warn("milter %s: bad protocol information: %s",
       milter->m.name, name);
     milter8_conf_error(milter);
     return;
 }
 if (vers != 0)
     my_version = vers;
 my_events |= mask;
    }
    myfree(saved_version);
    if (my_events == 0 || my_version == 0) {
 msg_warn("milter %s: no protocol version information", milter->m.name);
 milter8_conf_error(milter);
 return;
    }

    /*
     * Don't send events that aren't defined for my protocol version.
     */

    milter->np_mask = (SMFIP_NOSEND_MASK & ~my_events);
    if (msg_verbose)
 msg_info("%s: non-protocol events for protocol version %d: %s",
   myname, my_version,
   str_name_mask_opt(milter->buf, "non-protocol event mask",
      smfip_table, milter->np_mask, NAME_MASK_NUMBER));

    /*
     * Parse the Milter application endpoint.
     */

#define FREE_TRANSPORT_AND_BAIL_OUT(milter, milter_error) do { \
 myfree(transport); \
 milter_error(milter); \
 return; \
    } while (0);

    transport = mystrdup(milter->m.name);
    if ((endpoint = split_at(transport, ':')) == 0
 || *endpoint == 0 || *transport == 0) {
 msg_warn("Milter service needs transport:endpoint instead of \"%s\"",
   milter->m.name);
 FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error);
    }
    if (msg_verbose)
 msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint);
    if (strcmp(transport, "inet") == 0) {
 connect_fn = inet_connect;
    } else if (strcmp(transport, "unix") == 0) {
 connect_fn = unix_connect;
    } else if (strcmp(transport, "local") == 0) {
 connect_fn = LOCAL_CONNECT;
    } else {
 msg_warn("invalid transport name: %s in Milter service: %s",
   transport, milter->m.name);
 FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error);
    }

    /*
     * Connect to the Milter application.
     */

    if ((fd = connect_fn(endpoint, BLOCKING, milter->conn_timeout)) < 0) {
 msg_warn("connect to Milter service %s: %m", milter->m.name);
 FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_comm_error);
    }
    myfree(transport);
    milter->fp = vstream_fdopen(fd, O_RDWR);
    vstream_control(milter->fp,
      CA_VSTREAM_CTL_DOUBLE,
      CA_VSTREAM_CTL_TIMEOUT(milter->cmd_timeout),
      CA_VSTREAM_CTL_END);
    /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
    if (connect_fn == inet_connect)
 vstream_tweak_tcp(milter->fp);

    /*
     * Open the negotiations by sending what actions the Milter may request
     * and what events the Milter can receive.
     */

    if (msg_verbose) {
 msg_info("%s: my_version=0x%lx", myname, (long) my_version);
 msg_info("%s: my_actions=0x%lx %s", myname, (long) my_actions,
   str_name_mask_opt(milter->buf, "request mask",
    smfif_table, my_actions, NAME_MASK_NUMBER));
 msg_info("%s: my_events=0x%lx %s", myname, (long) my_events,
   str_name_mask_opt(milter->buf, "event mask",
     smfip_table, my_events, NAME_MASK_NUMBER));
    }
    errno = 0;
    if (milter8_write_cmd(milter, SMFIC_OPTNEG,
     MILTER8_DATA_HLONG, my_version,
     MILTER8_DATA_HLONG, my_actions,
     MILTER8_DATA_HLONG, my_events,
     MILTER8_DATA_END) != 0) {
 msg_warn("milter %s: write error in initial handshake",
   milter->m.name);
 /* milter8_write_cmd() called milter8_comm_error() */
 return;
    }

    /*
     * Receive the filter's response and verify that we are compatible.
     */

    if (milter8_read_resp(milter, SMFIC_OPTNEG, &cmd, &data_len) != 0) {
 msg_warn("milter %s: read error in initial handshake", milter->m.name);
 /* milter8_read_resp() called milter8_comm_error() */
 return;
    }
    if (cmd != SMFIC_OPTNEG) {
 msg_warn("milter %s: unexpected reply \"%c\" in initial handshake",
   milter->m.name, cmd);
 (void) milter8_comm_error(milter);
 return;
    }
    if (milter8_read_data(milter, &data_len,
     MILTER8_DATA_HLONG, &milter->version,
     MILTER8_DATA_HLONG, &milter->rq_mask,
     MILTER8_DATA_HLONG, &milter->ev_mask,
     MILTER8_DATA_MORE) != 0) {
 msg_warn("milter %s: read error in initial handshake", milter->m.name);
 /* milter8_read_data() called milter8_comm_error() */
 return;
    }
    if (milter->version > my_version) {
 msg_warn("milter %s: protocol version %d conflict"
   " with MTA protocol version %d",
   milter->m.name, milter->version, my_version);
 (void) milter8_comm_error(milter);
 return;
    }
    if ((milter->rq_mask & my_actions) != milter->rq_mask) {
 msg_warn("milter %s: request mask 0x%x conflict"
   " with MTA request mask 0x%lx",
   milter->m.name, milter->rq_mask, (long) my_actions);
 (void) milter8_comm_error(milter);
 return;
    }
    if (milter->ev_mask & SMFIP_RCPT_REJ)
 milter->m.flags |= MILTER_FLAG_WANT_RCPT_REJ;

    /*
     * Allow the remote application to run an older protocol version, but
     * don't them send events that their protocol version doesn't support.
     * Based on a suggestion by Kouhei Sutou.
     * 
     * XXX When the Milter sends a protocol version that we don't have
     * information for, use the information for the next-lower protocol
     * version instead. This code assumes that the milter8_event_masks table
     * is organized in reverse numerical order.
     */

    if (milter->version < my_version) {
 const NAME_CODE *np;
 int     version;

 for (np = milter8_event_masks; /* see below */ ; np++) {
     if (np->name == 0) {
  msg_warn("milter %s: unexpected protocol version %d",
    milter->m.name, milter->version);
  break;
     }
     if ((version = atoi(np->name)) > 0 && version <= milter->version) {
  milter->np_mask |= (SMFIP_NOSEND_MASK & ~np->code);
  if (msg_verbose)
      msg_info("%s: non-protocol events for milter %s"
        " protocol version %d: %s",
        myname, milter->m.name, milter->version,
        str_name_mask_opt(milter->buf,
            "non-protocol event mask",
            smfip_table, milter->np_mask,
            NAME_MASK_NUMBER));
  break;
     }
 }
    }

    /*
     * Initial negotiations completed.
     */

    if (msg_verbose) {
 if ((milter->ev_mask & my_events) != milter->ev_mask)
     msg_info("milter %s: event mask 0x%x includes features not"
       " offered in MTA event mask 0x%lx",
       milter->m.name, milter->ev_mask, (long) my_events);
 msg_info("%s: milter %s version %d",
   myname, milter->m.name, milter->version);
 msg_info("%s: events %s", myname,
   str_name_mask_opt(milter->buf, "event mask",
      smfip_table, milter->ev_mask, NAME_MASK_NUMBER));
 msg_info("%s: requests %s", myname,
   str_name_mask_opt(milter->buf, "request mask",
      smfif_table, milter->rq_mask, NAME_MASK_NUMBER));
    }
    milter->state = MILTER8_STAT_READY;
    milter8_def_reply(milter, 0);
    milter->skip_event_type = 0;

    /*
     * Secondary negotiations: override lists of macro names.
     */

    if (data_len > 0) {
 VSTRING *buf = vstring_alloc(100);
 UINT32_TYPE mac_type;
 const char *smfim_name;
 char  **mac_value_ptr;

 milter->m.macros = milter_macros_alloc(MILTER_MACROS_ALLOC_EMPTY);

 while (data_len > 0
        && milter8_read_data(milter, &data_len,
        MILTER8_DATA_HLONG, &mac_type,
        MILTER8_DATA_STRING, buf,
        MILTER8_DATA_MORE) == 0) {
     smfim_name = str_name_code(smfim_table, mac_type);
     if (smfim_name == 0) {
  msg_warn("milter %s: ignoring unknown macro type %u",
    milter->m.name, (unsigned) mac_type);
     } else {
  if (msg_verbose)
      msg_info("override %s macro list with \"%s\"",
        smfim_name, STR(buf));
  mac_value_ptr = MILTER8_MACRO_PTR(milter->m.macros, mac_type);
  myfree(*mac_value_ptr);
  *mac_value_ptr = mystrdup(STR(buf));
     }
 }
 /* milter8_read_data() calls milter8_comm_error() after error. */
 vstring_free(buf);
 /* At this point the filter state is either READY or ERROR. */
    }
}

/* milter8_conn_event - report connect event to Sendmail 8 milter */

static const char *milter8_conn_event(MILTER *m,
                  const char *client_name,
                  const char *client_addr,
                  const char *client_port,
                  unsigned addr_family,
                  ARGV *macros)
{
    const char *myname = "milter8_conn_event";
    MILTER8 *milter = (MILTER8 *) m;
    int     port;
    int     skip_reply;
    const char *sm_name;
    char   *ptr = 0;
    const char *resp;

    /*
     * Need a global definition for "unknown" host name or address that is
     * shared by smtpd, cleanup and libmilter.
     */

#define XXX_UNKNOWN "unknown"
#define STR_EQ(x,y) (strcmp((x), (y)) == 0)
#define STR_NE(x,y) (strcmp((x), (y)) != 0)

    /*
     * Report the event.
     */

    switch (milter->state) {
    case MILTER8_STAT_ERROR:
 if (msg_verbose)
     msg_info("%s: skip milter %s", myname, milter->m.name);
 return (milter->def_reply);
    case MILTER8_STAT_READY:
 if (msg_verbose)
     msg_info("%s: milter %s: connect %s/%s",
       myname, milter->m.name, client_name, client_addr);
 if (client_port == 0) {
     port = 0;
 } else if (!alldig(client_port) || (port = atoi(client_port)) < 0
     || port > 65535) {
     msg_warn("milter %s: bad client port number %s",
       milter->m.name, client_port);
     port = 0;
 }
 milter->state = MILTER8_STAT_ENVELOPE;
 skip_reply = ((milter->ev_mask & SMFIP_NR_CONN) != 0);
 /* Transform unknown hostname from Postfix to Sendmail form. */
 sm_name = (STR_NE(client_name, XXX_UNKNOWN) ? client_name :
     STR_EQ(client_addr, XXX_UNKNOWN) ? client_name :
     (ptr = concatenate("[", client_addr, "]", (char *) 0)));
 switch (addr_family) {
 case AF_INET:
     resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
     skip_reply, macros,
     MILTER8_DATA_STRING, sm_name,
     MILTER8_DATA_OCTET, SMFIA_INET,
     MILTER8_DATA_NSHORT, htons(port),
     MILTER8_DATA_STRING, client_addr,
     MILTER8_DATA_END);
     break;
#ifdef HAS_IPV6
 case AF_INET6:
     resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
     skip_reply, macros,
     MILTER8_DATA_STRING, sm_name,
     MILTER8_DATA_OCTET, SMFIA_INET6,
     MILTER8_DATA_NSHORT, htons(port),
     MILTER8_DATA_STRING, client_addr,
     MILTER8_DATA_END);
     break;
#endif
 case AF_UNIX:
     resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
     skip_reply, macros,
     MILTER8_DATA_STRING, sm_name,
     MILTER8_DATA_OCTET, SMFIA_UNIX,
     MILTER8_DATA_NSHORT, htons(0),
     MILTER8_DATA_STRING, client_addr,
     MILTER8_DATA_END);
     break;
 default:
     resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT,
     skip_reply, macros,
     MILTER8_DATA_STRING, sm_name,
     MILTER8_DATA_OCTET, SMFIA_UNKNOWN,
     MILTER8_DATA_END);
     break;
 }
 if (ptr != 0)
     myfree(ptr);
 return (resp);
    default:
 msg_panic("%s: milter %s: bad state %d",
    myname, milter->m.name, milter->state);
    }
}

/* milter8_helo_event - report HELO/EHLO command to Sendmail 8 milter */

static const char *milter8_helo_event(MILTER *m, const char *helo_name,
                  int unused_esmtp,
                  ARGV *macros)
{
    const char *myname = "milter8_helo_event";
    MILTER8 *milter = (MILTER8 *) m;
    int     skip_reply;

    /*
     * Report the event.
     */

    switch (milter->state) {
    case MILTER8_STAT_ERROR:
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
 if (msg_verbose)
     msg_info("%s: skip milter %s", myname, milter->m.name);
 return (milter->def_reply);
    case MILTER8_STAT_ENVELOPE:
    case MILTER8_STAT_ACCEPT_MSG:
 /* With HELO after MAIL, smtpd(8) calls milter8_abort() next. */
 if (msg_verbose)
     msg_info("%s: milter %s: helo %s",
       myname, milter->m.name, helo_name);
 skip_reply = ((milter->ev_mask & SMFIP_NR_HELO) != 0);
 return (milter8_event(milter, SMFIC_HELO, SMFIP_NOHELO,
         skip_reply, macros,
         MILTER8_DATA_STRING, helo_name,
         MILTER8_DATA_END));
    default:
 msg_panic("%s: milter %s: bad state %d",
    myname, milter->m.name, milter->state);
    }
}

/* milter8_mail_event - report MAIL command to Sendmail 8 milter */

static const char *milter8_mail_event(MILTER *m, const char **argv,
                  ARGV *macros)
{
    const char *myname = "milter8_mail_event";
    MILTER8 *milter = (MILTER8 *) m;
    const char **cpp;
    int     skip_reply;

    /*
     * Report the event.
     */

    switch (milter->state) {
    case MILTER8_STAT_ERROR:
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
 if (msg_verbose)
     msg_info("%s: skip milter %s", myname, milter->m.name);
 return (milter->def_reply);
    case MILTER8_STAT_ENVELOPE:
 if (msg_verbose) {
     VSTRING *buf = vstring_alloc(100);

     for (cpp = argv; *cpp; cpp++)
  vstring_sprintf_append(buf, " %s", *cpp);
     msg_info("%s: milter %s: mail%s",
       myname, milter->m.name, STR(buf));
     vstring_free(buf);
 }
 skip_reply = ((milter->ev_mask & SMFIP_NR_MAIL) != 0);
 return (milter8_event(milter, SMFIC_MAIL, SMFIP_NOMAIL,
         skip_reply, macros,
         MILTER8_DATA_ARGV, argv,
         MILTER8_DATA_END));
    default:
 msg_panic("%s: milter %s: bad state %d",
    myname, milter->m.name, milter->state);
    }
}

/* milter8_rcpt_event - report RCPT command to Sendmail 8 milter */

static const char *milter8_rcpt_event(MILTER *m, const char **argv,
                  ARGV *macros)
{
    const char *myname = "milter8_rcpt_event";
    MILTER8 *milter = (MILTER8 *) m;
    const char **cpp;
    int     skip_reply;

    /*
     * Report the event.
     */

    switch (milter->state) {
    case MILTER8_STAT_ERROR:
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
    case MILTER8_STAT_ACCEPT_MSG:
 if (msg_verbose)
     msg_info("%s: skip milter %s", myname, milter->m.name);
 return (milter->def_reply);
    case MILTER8_STAT_ENVELOPE:
 if (msg_verbose) {
     VSTRING *buf = vstring_alloc(100);

     for (cpp = argv; *cpp; cpp++)
  vstring_sprintf_append(buf, " %s", *cpp);
     msg_info("%s: milter %s: rcpt%s",
       myname, milter->m.name, STR(buf));
     vstring_free(buf);
 }
 skip_reply = ((milter->ev_mask & SMFIP_NR_RCPT) != 0);
 return (milter8_event(milter, SMFIC_RCPT, SMFIP_NORCPT,
         skip_reply, macros,
         MILTER8_DATA_ARGV, argv,
         MILTER8_DATA_END));
    default:
 msg_panic("%s: milter %s: bad state %d",
    myname, milter->m.name, milter->state);
    }
}

/* milter8_data_event - report DATA command to Sendmail 8 milter */

static const char *milter8_data_event(MILTER *m, ARGV *macros)
{
    const char *myname = "milter8_data_event";
    MILTER8 *milter = (MILTER8 *) m;
    int     skip_reply;

    /*
     * Report the event.
     */

    switch (milter->state) {
    case MILTER8_STAT_ERROR:
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
    case MILTER8_STAT_ACCEPT_MSG:
 if (msg_verbose)
     msg_info("%s: skip milter %s", myname, milter->m.name);
 return (milter->def_reply);
    case MILTER8_STAT_ENVELOPE:
 if (msg_verbose)
     msg_info("%s: milter %s: data command", myname, milter->m.name);
 skip_reply = ((milter->ev_mask & SMFIP_NR_DATA) != 0);
 return (milter8_event(milter, SMFIC_DATA, SMFIP_NODATA,
         skip_reply, macros,
         MILTER8_DATA_END));
    default:
 msg_panic("%s: milter %s: bad state %d",
    myname, milter->m.name, milter->state);
    }
}

/* milter8_unknown_event - report unknown SMTP command to Sendmail 8 milter */

static const char *milter8_unknown_event(MILTER *m, const char *command,
              ARGV *macros)
{
    const char *myname = "milter8_unknown_event";
    MILTER8 *milter = (MILTER8 *) m;
    int     skip_reply;

    /*
     * Report the event.
     */

    switch (milter->state) {
    case MILTER8_STAT_ERROR:
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
    case MILTER8_STAT_ACCEPT_MSG:
 if (msg_verbose)
     msg_info("%s: skip milter %s", myname, milter->m.name);
 return (milter->def_reply);
    case MILTER8_STAT_ENVELOPE:
 if (msg_verbose)
     msg_info("%s: milter %s: unknown command: %s",
       myname, milter->m.name, command);
 /* XXX Sendmail doesn't send macros (checked with 8.6.13). */
 skip_reply = ((milter->ev_mask & SMFIP_NR_UNKN) != 0);
 return (milter8_event(milter, SMFIC_UNKNOWN, SMFIP_NOUNKNOWN,
         skip_reply, macros,
         MILTER8_DATA_STRING, command,
         MILTER8_DATA_END));
    default:
 msg_panic("%s: milter %s: bad state %d",
    myname, milter->m.name, milter->state);
    }
}

/* milter8_other_event - reply for other event */

static const char *milter8_other_event(MILTER *m)
{
    const char *myname = "milter8_other_event";
    MILTER8 *milter = (MILTER8 *) m;

    /*
     * Return the default reply.
     */

    if (msg_verbose)
 msg_info("%s: milter %s", myname, milter->m.name);
    return (milter->def_reply);
}

/* milter8_abort - cancel one milter's message receiving state */

static void milter8_abort(MILTER *m)
{
    const char *myname = "milter8_abort";
    MILTER8 *milter = (MILTER8 *) m;

    /*
     * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
     * out that the SMTP client has disconnected. Because of this, Postfix
     * has to open a new MTA-to-filter socket for each SMTP client.
     */

    switch (milter->state) {
    case MILTER8_STAT_CLOSED:
    case MILTER8_STAT_READY:
 return;
    case MILTER8_STAT_ERROR:
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
 if (msg_verbose)
     msg_info("%s: skip milter %s", myname, milter->m.name);
 break;
    case MILTER8_STAT_ENVELOPE:
    case MILTER8_STAT_MESSAGE:
    case MILTER8_STAT_ACCEPT_MSG:
 if (msg_verbose)
     msg_info("%s: abort milter %s", myname, milter->m.name);
 (void) milter8_write_cmd(milter, SMFIC_ABORT, MILTER8_DATA_END);
 if (milter->state != MILTER8_STAT_ERROR)
     milter->state = MILTER8_STAT_ENVELOPE;
 break;
    default:
 msg_panic("%s: milter %s: bad state %d",
    myname, milter->m.name, milter->state);
    }
}

/* milter8_disc_event - report client disconnect event */

static void milter8_disc_event(MILTER *m)
{
    const char *myname = "milter8_disc_event";
    MILTER8 *milter = (MILTER8 *) m;

    /*
     * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
     * out that the SMTP client has disconnected. Because of this, Postfix
     * has to open a new MTA-to-filter socket for each SMTP client.
     */

    switch (milter->state) {
    case MILTER8_STAT_CLOSED:
    case MILTER8_STAT_READY:
 return;
    case MILTER8_STAT_ERROR:
#ifdef LIBMILTER_AUTO_DISCONNECT
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
#endif
 if (msg_verbose)
     msg_info("%s: skip quit milter %s", myname, milter->m.name);
 break;
    case MILTER8_STAT_ENVELOPE:
    case MILTER8_STAT_MESSAGE:
#ifndef LIBMILTER_AUTO_DISCONNECT
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
#endif
    case MILTER8_STAT_ACCEPT_MSG:
 if (msg_verbose)
     msg_info("%s: quit milter %s", myname, milter->m.name);
 (void) milter8_write_cmd(milter, SMFIC_QUIT, MILTER8_DATA_END);
 break;
    }
#ifdef LIBMILTER_AUTO_DISCONNECT
    milter8_close_stream(milter);
#else
    if (milter->state != MILTER8_STAT_ERROR)
 milter->state = MILTER8_STAT_READY;
#endif
    milter8_def_reply(milter, 0);
}

 /*
  * Structure to ship context across the MIME_STATE engine.
  */

typedef struct {
    MILTER8 *milter;   /* milter client */
    ARGV   *eoh_macros;   /* end-of-header macros */
    ARGV   *eod_macros;   /* end-of-body macros */
    ARGV   *auto_hdrs;   /* auto-generated headers */
    int     auto_done;   /* good enough for now */
    int     first_header;  /* first header */
    int     first_body;   /* first body line */
    const char *resp;   /* milter application response */
} MILTER_MSG_CONTEXT;

/* milter8_header - milter8_message call-back for message header */

static void milter8_header(void *ptr, int unused_header_class,
              const HEADER_OPTS *header_info,
              VSTRING *buf, off_t unused_offset)
{
    const char *myname = "milter8_header";
    MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
    MILTER8 *milter = msg_ctx->milter;
    char   *cp;
    int     skip_reply;
    char  **cpp;
    unsigned done;

    /*
     * XXX Workaround: mime_state_update() may invoke multiple call-backs
     * before returning to the caller.
     */

#define MILTER8_MESSAGE_DONE(milter, msg_ctx) \
 ((milter)->state != MILTER8_STAT_MESSAGE || (msg_ctx)->resp != 0)

    if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
 return;

    /*
     * XXX Sendmail compatibility. Don't expose our first (received) header
     * to mail filter applications. See also cleanup_milter.c for code to
     * ensure that header replace requests are relative to the message
     * content as received, that is, without our own first (received) header,
     * while header insert requests are relative to the message as delivered,
     * that is, including our own first (received) header.
     * 
     * XXX But this breaks when they delete our own Received: header with
     * header_checks before it reaches the queue file. Even then we must not
     * expose the first header to mail filter applications, otherwise the
     * dk-filter signature will be inserted at the wrong position. It should
     * precede the headers that it signs.
     * 
     * XXX Sendmail compatibility. It eats the first space (not tab) after the
     * header label and ":".
     */

    for (cpp = msg_ctx->auto_hdrs->argv, done = 1; *cpp; cpp++, done <<= 1)
 if ((msg_ctx->auto_done & done) == 0 && strcmp(*cpp, STR(buf)) == 0) {
     msg_ctx->auto_done |= done;
     return;
 }

    /*
     * Sendmail 8 sends multi-line headers as text separated by newline.
     * 
     * We destroy the header buffer to split it into label and value. Changing
     * the buffer is explicitly allowed by the mime_state(3) interface.
     */

    if (msg_verbose > 1)
 msg_info("%s: header milter %s: %.100s",
   myname, milter->m.name, STR(buf));
    cp = STR(buf) + (header_info ? strlen(header_info->name) :
       is_header(STR(buf)));
    /* XXX Following matches is_header.c */
    while (*cp == ' ' || *cp == '\t')
 *cp++ = 0;
    if (*cp != ':')
 msg_panic("%s: header label not followed by ':'", myname);
    *cp++ = 0;
    /* XXX Sendmail by default eats one space (not tab) after the colon. */
    if ((milter->ev_mask & SMFIP_HDR_LEADSPC) == 0 && *cp == ' ')
 cp++;
    skip_reply = ((milter->ev_mask & SMFIP_NOHREPL) != 0);
    msg_ctx->resp =
 milter8_event(milter, SMFIC_HEADER, SMFIP_NOHDRS,
        skip_reply, msg_ctx->eoh_macros,
        MILTER8_DATA_STRING, STR(buf),
        MILTER8_DATA_STRING, cp,
        MILTER8_DATA_END);
}

/* milter8_eoh - milter8_message call-back for end-of-header */

static void milter8_eoh(void *ptr)
{
    const char *myname = "milter8_eoh";
    MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
    MILTER8 *milter = msg_ctx->milter;
    int     skip_reply;

    if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
 return;
    if (msg_verbose)
 msg_info("%s: eoh milter %s", myname, milter->m.name);
    skip_reply = ((milter->ev_mask & SMFIP_NR_EOH) != 0);
    msg_ctx->resp =
 milter8_event(milter, SMFIC_EOH, SMFIP_NOEOH,
        skip_reply, msg_ctx->eoh_macros,
        MILTER8_DATA_END);
}

/* milter8_body - milter8_message call-back for body content */

static void milter8_body(void *ptr, int rec_type,
            const char *buf, ssize_t len,
            off_t offset)
{
    const char *myname = "milter8_body";
    MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
    MILTER8 *milter = msg_ctx->milter;
    ssize_t todo = len;
    const char *bp = buf;
    ssize_t space;
    ssize_t count;
    int     skip_reply;

    if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
 return;

    /*
     * XXX Sendmail compatibility: don't expose our first body line.
     */

    if (msg_ctx->first_body) {
 msg_ctx->first_body = 0;
 return;
    }

    /*
     * XXX I thought I was going to delegate all the on-the-wire formatting
     * to a common lower layer, but unfortunately it's not practical. If we
     * were to do MILTER_CHUNK_SIZE buffering in a common lower layer, then
     * we would have to pass along call-backs and state, so that the
     * call-back can invoke milter8_event() with the right arguments when the
     * MILTER_CHUNK_SIZE buffer reaches capacity. That's just too ugly.
     * 
     * To recover the cost of making an extra copy of body content from Milter
     * buffer to VSTREAM buffer, we could make vstream_fwrite() a little
     * smarter so that it does large transfers directly from the user buffer
     * instead of copying the data one block at a time into a VSTREAM buffer.
     */

    if (msg_verbose > 1)
 msg_info("%s: body milter %s: %.100s", myname, milter->m.name, buf);
    skip_reply = ((milter->ev_mask & SMFIP_NR_BODY) != 0);
    /* To append \r\n, simply redirect input to another buffer. */
    if (rec_type == REC_TYPE_NORM && todo == 0) {
 bp = "\r\n";
 todo = 2;
 rec_type = REC_TYPE_EOF;
    }
    while (todo > 0) {
 /* Append one REC_TYPE_NORM or REC_TYPE_CONT to body chunk buffer. */
 space = MILTER_CHUNK_SIZE - LEN(milter->body);
 if (space <= 0)
     msg_panic("%s: bad buffer size: %ld",
        myname, (long) LEN(milter->body));
 count = (todo > space ? space : todo);
 vstring_memcat(milter->body, bp, count);
 bp += count;
 todo -= count;
 /* Flush body chunk buffer when full. See also milter8_eob(). */
 if (LEN(milter->body) == MILTER_CHUNK_SIZE) {
     msg_ctx->resp =
  milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY,
         skip_reply, msg_ctx->eod_macros,
         MILTER8_DATA_BUFFER, milter->body,
         MILTER8_DATA_END);
     if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
  break;
     VSTRING_RESET(milter->body);
 }
 /* To append \r\n, simply redirect input to another buffer. */
 if (rec_type == REC_TYPE_NORM && todo == 0) {
     bp = "\r\n";
     todo = 2;
     rec_type = REC_TYPE_EOF;
 }
    }
}

/* milter8_eob - milter8_message call-back for end-of-body */

static void milter8_eob(void *ptr)
{
    const char *myname = "milter8_eob";
    MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr;
    MILTER8 *milter = msg_ctx->milter;
    int     skip_reply;

    if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
 return;
    if (msg_verbose)
 msg_info("%s: eob milter %s", myname, milter->m.name);

    /*
     * Flush partial body chunk buffer. See also milter8_body().
     * 
     * XXX Sendmail 8 libmilter accepts SMFIC_EOB+data, and delivers it to the
     * application as two events: SMFIC_BODY+data followed by SMFIC_EOB. This
     * breaks with the PMilter 0.95 protocol re-implementation, which
     * delivers the SMFIC_EOB event and ignores the data. To avoid such
     * compatibility problems we separate the events in the client. With
     * this, we also prepare for a future where different event types can
     * have different macro lists.
     */

    if (LEN(milter->body) > 0) {
 skip_reply = ((milter->ev_mask & SMFIP_NR_BODY) != 0);
 msg_ctx->resp =
     milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY,
     skip_reply, msg_ctx->eod_macros,
     MILTER8_DATA_BUFFER, milter->body,
     MILTER8_DATA_END);
 if (MILTER8_MESSAGE_DONE(milter, msg_ctx))
     return;
    }
    msg_ctx->resp =
 milter8_event(msg_ctx->milter, SMFIC_BODYEOB, 0,
        DONT_SKIP_REPLY, msg_ctx->eod_macros,
        MILTER8_DATA_END);
}

/* milter8_message - send message content and receive reply */

static const char *milter8_message(MILTER *m, VSTREAM *qfile,
               off_t data_offset,
               ARGV *eoh_macros,
               ARGV *eod_macros,
               ARGV *auto_hdrs)
{
    const char *myname = "milter8_message";
    MILTER8 *milter = (MILTER8 *) m;
    MIME_STATE *mime_state;
    int     rec_type;
    const MIME_STATE_DETAIL *detail;
    int     mime_errs = 0;
    MILTER_MSG_CONTEXT msg_ctx;
    VSTRING *buf;
    int     saved_errno;

    switch (milter->state) {
    case MILTER8_STAT_ERROR:
    case MILTER8_STAT_ACCEPT_CON:
    case MILTER8_STAT_REJECT_CON:
    case MILTER8_STAT_ACCEPT_MSG:
 if (msg_verbose)
     msg_info("%s: skip message to milter %s", myname, milter->m.name);
 return (milter->def_reply);
    case MILTER8_STAT_ENVELOPE:
 if (msg_verbose)
     msg_info("%s: message to milter %s", myname, milter->m.name);
 if (vstream_fseek(qfile, data_offset, SEEK_SET) < 0) {
     saved_errno = errno;
     msg_warn("%s: vstream_fseek %s: %m", myname, VSTREAM_PATH(qfile));
     /* XXX This should be available from cleanup_strerror.c. */
     return (saved_errno == EFBIG ?
      "552 5.3.4 Message file too big" :
      "451 4.3.0 Queue file write error");
 }
 msg_ctx.milter = milter;
 msg_ctx.eoh_macros = eoh_macros;
 msg_ctx.eod_macros = eod_macros;
 msg_ctx.auto_hdrs = auto_hdrs;
 msg_ctx.auto_done = 0;
 msg_ctx.first_header = 1;
 msg_ctx.first_body = 1;
 msg_ctx.resp = 0;
 mime_state =
     mime_state_alloc(MIME_OPT_DISABLE_MIME,
        (milter->ev_mask & SMFIP_NOHDRS) ?
        (MIME_STATE_HEAD_OUT) 0 : milter8_header,
        (milter->ev_mask & SMFIP_NOEOH) ?
        (MIME_STATE_ANY_END) 0 : milter8_eoh,
        (milter->ev_mask & SMFIP_NOBODY) ?
        (MIME_STATE_BODY_OUT) 0 : milter8_body,
        milter8_eob,
        (MIME_STATE_ERR_PRINT) 0,
        (void *) &msg_ctx);
 buf = vstring_alloc(100);
 milter->state = MILTER8_STAT_MESSAGE;
 VSTRING_RESET(milter->body);
 vstream_control(milter->fp,
   CA_VSTREAM_CTL_DOUBLE,
   CA_VSTREAM_CTL_TIMEOUT(milter->msg_timeout),
   CA_VSTREAM_CTL_END);

 /*
  * XXX When the message (not MIME body part) does not end in CRLF
  * (i.e. the last record was REC_TYPE_CONT), do we send a CRLF
  * terminator before triggering the end-of-body condition?
 */

 for (;;) {
     if ((rec_type = rec_get(qfile, buf, 0)) < 0) {
  msg_warn("%s: error reading %s: %m",
    myname, VSTREAM_PATH(qfile));
  msg_ctx.resp = "450 4.3.0 Queue file write error";
  break;
     }
     /* Invoke the appropriate call-back routine. */
     mime_errs = mime_state_update(mime_state, rec_type,
       STR(buf), LEN(buf));
     if (mime_errs) {
  detail = mime_state_detail(mime_errs);
  msg_warn("%s: MIME problem %s in %s",
    myname, detail->text, VSTREAM_PATH(qfile));
  msg_ctx.resp = "450 4.3.0 Queue file write error";
  break;
     }
     if (MILTER8_MESSAGE_DONE(milter, &msg_ctx))
  break;
     if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
  break;
 }
 mime_state_free(mime_state);
 vstring_free(buf);
 if (milter->fp)
     vstream_control(milter->fp,
       CA_VSTREAM_CTL_DOUBLE,
       CA_VSTREAM_CTL_TIMEOUT(milter->cmd_timeout),
       CA_VSTREAM_CTL_END);
 if (milter->state == MILTER8_STAT_MESSAGE
     || milter->state == MILTER8_STAT_ACCEPT_MSG)
     milter->state = MILTER8_STAT_ENVELOPE;
 return (msg_ctx.resp);
    default:
 msg_panic("%s: milter %s: bad state %d",
    myname, milter->m.name, milter->state);
    }
}

 /*
  * Preliminary protocol to send/receive milter instances. This needs to be
  * extended with type information once we support multiple milter protocols.
  */

#define MAIL_ATTR_MILT_NAME "milter_name"
#define MAIL_ATTR_MILT_VERS "milter_version"
#define MAIL_ATTR_MILT_ACTS "milter_actions"
#define MAIL_ATTR_MILT_EVTS "milter_events"
#define MAIL_ATTR_MILT_NPTS "milter_non_events"
#define MAIL_ATTR_MILT_STAT "milter_state"
#define MAIL_ATTR_MILT_CONN "milter_conn_timeout"
#define MAIL_ATTR_MILT_CMD "milter_cmd_timeout"
#define MAIL_ATTR_MILT_MSG "milter_msg_timeout"
#define MAIL_ATTR_MILT_ACT "milter_action"
#define MAIL_ATTR_MILT_MAC "milter_macro_list"

/* milter8_active - report if this milter still wants events */

static int milter8_active(MILTER *m)
{
    MILTER8 *milter = (MILTER8 *) m;

    return (milter->fp != 0
     && (milter->state == MILTER8_STAT_ENVELOPE
  || milter->state == MILTER8_STAT_READY));
}

/* milter8_send - send milter instance */

static int milter8_send(MILTER *m, VSTREAM *stream)
{
    const char *myname = "milter8_send";
    MILTER8 *milter = (MILTER8 *) m;

    if (msg_verbose)
 msg_info("%s: milter %s", myname, milter->m.name);

    /*
     * The next read on this Milter socket happens in a different process. It
     * will not automatically flush the output buffer in this process.
     */

    if (milter->fp)
 vstream_fflush(milter->fp);

    if (attr_print(stream, ATTR_FLAG_MORE,
     SEND_ATTR_STR(MAIL_ATTR_MILT_NAME, milter->m.name),
     SEND_ATTR_INT(MAIL_ATTR_MILT_VERS, milter->version),
     SEND_ATTR_INT(MAIL_ATTR_MILT_ACTS, milter->rq_mask),
     SEND_ATTR_INT(MAIL_ATTR_MILT_EVTS, milter->ev_mask),
     SEND_ATTR_INT(MAIL_ATTR_MILT_NPTS, milter->np_mask),
     SEND_ATTR_INT(MAIL_ATTR_MILT_STAT, milter->state),
     SEND_ATTR_INT(MAIL_ATTR_MILT_CONN, milter->conn_timeout),
     SEND_ATTR_INT(MAIL_ATTR_MILT_CMD, milter->cmd_timeout),
     SEND_ATTR_INT(MAIL_ATTR_MILT_MSG, milter->msg_timeout),
     SEND_ATTR_STR(MAIL_ATTR_MILT_ACT, milter->def_action),
     SEND_ATTR_INT(MAIL_ATTR_MILT_MAC, milter->m.macros != 0),
     ATTR_TYPE_END) != 0
 || (milter->m.macros != 0
     && attr_print(stream, ATTR_FLAG_NONE,
     SEND_ATTR_FUNC(milter_macros_print,
      (const void *) milter->m.macros),
     ATTR_TYPE_END) != 0)
 || (milter->m.macros == 0
     && attr_print(stream, ATTR_FLAG_NONE,
     ATTR_TYPE_END) != 0)
 || vstream_fflush(stream) != 0) {
 return (-1);
#ifdef CANT_WRITE_BEFORE_SENDING_FD
    } else if (attr_scan(stream, ATTR_FLAG_STRICT,
    RECV_ATTR_STR(MAIL_ATTR_DUMMY, milter->buf),
    ATTR_TYPE_END) != 1) {
 return (-1);
#endif
    } else if (LOCAL_SEND_FD(vstream_fileno(stream),
        vstream_fileno(milter->fp)) < 0) {
 return (-1);
#ifdef MUST_READ_AFTER_SENDING_FD
    } else if (attr_scan(stream, ATTR_FLAG_STRICT,
    RECV_ATTR_STR(MAIL_ATTR_DUMMY, milter->buf),
    ATTR_TYPE_END) != 1) {
 return (-1);
#endif
    } else {
 return (0);
    }
}

static MILTER8 *milter8_alloc(const char *, intintintconst char *,
                 const char *, MILTERS *);

/* milter8_receive - receive milter instance */

MILTER *milter8_receive(VSTREAM *stream, MILTERS *parent)
{
    const char *myname = "milter8_receive";
    static VSTRING *name_buf;
    static VSTRING *act_buf;
    MILTER8 *milter;
    int     version;
    int     rq_mask;
    int     ev_mask;
    int     np_mask;
    int     state;
    int     conn_timeout;
    int     cmd_timeout;
    int     msg_timeout;
    int     fd;
    int     has_macros;
    MILTER_MACROS *macros = 0;

#define FREE_MACROS_AND_RETURN(x) do { \
 if (macros) \
     milter_macros_free(macros); \
 return (x); \
    } while (0)

    if (name_buf == 0) {
 name_buf = vstring_alloc(10);
 act_buf = vstring_alloc(10);
    }
    if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
    RECV_ATTR_STR(MAIL_ATTR_MILT_NAME, name_buf),
    RECV_ATTR_INT(MAIL_ATTR_MILT_VERS, &version),
    RECV_ATTR_INT(MAIL_ATTR_MILT_ACTS, &rq_mask),
    RECV_ATTR_INT(MAIL_ATTR_MILT_EVTS, &ev_mask),
    RECV_ATTR_INT(MAIL_ATTR_MILT_NPTS, &np_mask),
    RECV_ATTR_INT(MAIL_ATTR_MILT_STAT, &state),
    RECV_ATTR_INT(MAIL_ATTR_MILT_CONN, &conn_timeout),
    RECV_ATTR_INT(MAIL_ATTR_MILT_CMD, &cmd_timeout),
    RECV_ATTR_INT(MAIL_ATTR_MILT_MSG, &msg_timeout),
    RECV_ATTR_STR(MAIL_ATTR_MILT_ACT, act_buf),
    RECV_ATTR_INT(MAIL_ATTR_MILT_MAC, &has_macros),
    ATTR_TYPE_END) < 10
 || (has_macros != 0
     && attr_scan(stream, ATTR_FLAG_STRICT,
    RECV_ATTR_FUNC(milter_macros_scan,
     (void *) (macros =
       milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO))),
    ATTR_TYPE_END) < 1)
 || (has_macros == 0
     && attr_scan(stream, ATTR_FLAG_STRICT,
    ATTR_TYPE_END) < 0)) {
 FREE_MACROS_AND_RETURN(0);
#ifdef CANT_WRITE_BEFORE_SENDING_FD
    } else if (attr_print(stream, ATTR_FLAG_NONE,
     SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
     ATTR_TYPE_END) != 0
        || vstream_fflush(stream) != 0) {
 FREE_MACROS_AND_RETURN(0);
#endif
    } else if ((fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) {
 FREE_MACROS_AND_RETURN(0);
    } else {
#ifdef MUST_READ_AFTER_SENDING_FD
 (void) attr_print(stream, ATTR_FLAG_NONE,
     SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
     ATTR_TYPE_END);
#endif
#define NO_PROTOCOL ((char *) 0)

 if (msg_verbose)
     msg_info("%s: milter %s", myname, STR(name_buf));

 milter = milter8_alloc(STR(name_buf), conn_timeout, cmd_timeout,
       msg_timeout, NO_PROTOCOL, STR(act_buf), parent);
 milter->fp = vstream_fdopen(fd, O_RDWR);
 milter->m.macros = macros;
 vstream_control(milter->fp, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END);
 /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
 vstream_tweak_sock(milter->fp);
 milter->version = version;
 milter->rq_mask = rq_mask;
 milter->ev_mask = ev_mask;
 milter->np_mask = np_mask;
 milter->state = state;
 return (&milter->m);
    }
}

/* milter8_free - destroy Milter instance */

static void milter8_free(MILTER *m)
{
    MILTER8 *milter = (MILTER8 *) m;

    if (msg_verbose)
 msg_info("free milter %s", milter->m.name);
    if (milter->fp)
 (void) vstream_fclose(milter->fp);
    myfree(milter->m.name);
    vstring_free(milter->buf);
    vstring_free(milter->body);
    if (milter->protocol)
 myfree(milter->protocol);
    myfree(milter->def_action);
    if (milter->def_reply)
 myfree(milter->def_reply);
    if (milter->m.macros)
 milter_macros_free(milter->m.macros);
    myfree((void *) milter);
}

/* milter8_alloc - create MTA-side Sendmail 8 Milter instance */

static MILTER8 *milter8_alloc(const char *name, int conn_timeout,
                 int cmd_timeout, int msg_timeout,
                 const char *protocol,
                 const char *def_action,
                 MILTERS *parent)
{
    MILTER8 *milter;

    /*
     * Fill in the structure. Note: all strings must be copied.
     * 
     * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
     * out that the SMTP client has disconnected. Because of this, Postfix
     * has to open a new MTA-to-filter socket for each SMTP client.
     */

    milter = (MILTER8 *) mymalloc(sizeof(*milter));
    milter->m.name = mystrdup(name);
    milter->m.flags = 0;
    milter->m.next = 0;
    milter->m.parent = parent;
    milter->m.macros = 0;
#ifdef LIBMILTER_AUTO_DISCONNECT
    milter->m.connect_on_demand = (void (*) (struct MILTER *)) milter8_connect;
#else
    milter->m.connect_on_demand = 0;
#endif
    milter->m.conn_event = milter8_conn_event;
    milter->m.helo_event = milter8_helo_event;
    milter->m.mail_event = milter8_mail_event;
    milter->m.rcpt_event = milter8_rcpt_event;
    milter->m.data_event = milter8_data_event; /* may be null */
    milter->m.message = milter8_message;
    milter->m.unknown_event = milter8_unknown_event; /* may be null */
    milter->m.other_event = milter8_other_event;
    milter->m.abort = milter8_abort;
    milter->m.disc_event = milter8_disc_event;
    milter->m.active = milter8_active;
    milter->m.send = milter8_send;
    milter->m.free = milter8_free;
    milter->fp = 0;
    milter->buf = vstring_alloc(100);
    milter->body = vstring_alloc(100);
    milter->version = 0;
    milter->rq_mask = 0;
    milter->ev_mask = 0;
    milter->state = MILTER8_STAT_CLOSED;
    milter->conn_timeout = conn_timeout;
    milter->cmd_timeout = cmd_timeout;
    milter->msg_timeout = msg_timeout;
    milter->protocol = (protocol ? mystrdup(protocol) : 0);
    milter->def_action = mystrdup(def_action);
    milter->def_reply = 0;
    milter->skip_event_type = 0;

    return (milter);
}

/* milter8_create - create MTA-side Sendmail 8 Milter instance */

MILTER *milter8_create(const char *name, int conn_timeout, int cmd_timeout,
                 int msg_timeout, const char *protocol,
                 const char *def_action, MILTERS *parent)
{
    MILTER8 *milter;

    /*
     * Fill in the structure.
     */

    milter = milter8_alloc(name, conn_timeout, cmd_timeout, msg_timeout,
      protocol, def_action, parent);

    /*
     * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds
     * out that the SMTP client has disconnected. Because of this, Postfix
     * has to open a new MTA-to-filter socket for each SMTP client.
     */

#ifndef LIBMILTER_AUTO_DISCONNECT
    milter8_connect(milter);
#endif
    return (&milter->m);
}

Messung V0.5 in Prozent
C=87 H=95 G=90

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.71Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-09) ¤

*Eine klare Vorstellung vom Zielzustand






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.