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

Impressum smtp_proto.c

  Interaktion und
PortierbarkeitC
 

/*++
/* NAME
/* smtp_proto 3
/* SUMMARY
/* client SMTP/LMTP protocol
/* SYNOPSIS
/* #include "smtp.h"
/*
/* int smtp_helo(state)
/* SMTP_STATE *state;
/*
/* int smtp_xfer(state)
/* SMTP_STATE *state;
/*
/* int smtp_rset(state)
/* SMTP_STATE *state;
/*
/* int smtp_quit(state)
/* SMTP_STATE *state;
/* DESCRIPTION
/* In the subsequent text, SMTP implies LMTP.
/* This module implements the client side of the SMTP protocol.
/*
/* smtp_helo() performs the initial handshake with the SMTP server.
/* When TLS is enabled, this includes STARTTLS negotiations.
/*
/* smtp_xfer() sends message envelope information followed by the
/* message data, and finishes the SMTP conversation. These operations
/* are combined in one function, in order to implement SMTP pipelining.
/* Recipients are marked as "done" in the mail queue file when
/* bounced or delivered. The message delivery status is updated
/* accordingly.
/*
/* smtp_rset() sends a single RSET command and waits for the
/* response. In case of a negative reply it sets the
/* CANT_RSET_THIS_SESSION flag.
/*
/* smtp_quit() sends a single QUIT command and waits for the
/* response if configured to do so. It always turns off connection
/* caching.
/* DIAGNOSTICS
/* smtp_helo(), smtp_xfer(), smtp_rset() and smtp_quit() return
/* 0 in case of success, -1 in case of failure. For smtp_xfer(),
/* smtp_rset() and smtp_quit(), success means the ability to
/* perform an SMTP conversation, not necessarily the ability
/* to deliver mail, or the achievement of server happiness.
/*
/* In case of a rejected or failed connection, a connection
/* is marked as "bad, do not cache". Otherwise, connection
/* caching may be turned off (without being marked "bad") at
/* the discretion of the code that implements the individual
/* protocol steps.
/*
/* Warnings: corrupt message file. A corrupt message is marked
/* as "corrupt" by changing its queue file permissions.
/* BUGS
/* Some SMTP servers will abort when the number of recipients
/* for one message exceeds their capacity. This behavior violates
/* the SMTP protocol.
/* The only way around this is to limit the number of recipients
/* per transaction to an artificially-low value.
/* SEE ALSO
/* smtp(3h) internal data structures
/* smtp_chat(3) query/reply SMTP support
/* smtp_trouble(3) error handlers
/* 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
/*
/* Wietse Venema
/* porcupine.org
/*
/* Pipelining code in cooperation with:
/* Jon Ribbens
/* Oaktree Internet Solutions Ltd.,
/* Internet House,
/* Canal Basin,
/* Coventry,
/* CV1 4LY, United Kingdom.
/*
/* Connection caching in cooperation with:
/* Victor Duchovni
/* Morgan Stanley
/*
/* TLS support originally by:
/* Lutz Jaenicke
/* BTU Cottbus
/* Allgemeine Elektrotechnik
/* Universitaetsplatz 3-4
/* D-03044 Cottbus, Germany
/*--*/


/* System library. */

#include <sys_defs.h>
#include <sys/stat.h>
#include <sys/socket.h>   /* shutdown(2) */
#include <netinet/in.h>   /* ntohs() */
#include <string.h>
#include <unistd.h>
#include <stdlib.h>   /* 44BSD stdarg.h uses abort() */
#include <stdarg.h>
#include <time.h>

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

/* Utility library. */

#include <msg.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <stringops.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <split_at.h>
#include <name_code.h>
#include <name_mask.h>

/* Global library. */

#include <mail_params.h>
#include <smtp_stream.h>
#include <mail_queue.h>
#include <recipient_list.h>
#include <deliver_request.h>
#include <defer.h>
#include <bounce.h>
#include <record.h>
#include <rec_type.h>
#include <off_cvt.h>
#include <mark_corrupt.h>
#include <quote_822_local.h>
#include <mail_proto.h>
#include <mime_state.h>
#include <ehlo_mask.h>
#include <maps.h>
#include <tok822.h>
#include <mail_addr_map.h>
#include <ext_prop.h>
#include <namadr_list.h>
#include <lex_822.h>
#include <dsn_mask.h>
#include <xtext.h>
#include <uxtext.h>
#include <smtputf8.h>
#if defined(USE_TLS) && defined(USE_TLSRPT)
#include <tlsrpt_wrapper.h>
#endif

/* Application-specific. */

#include "smtp.h"
#include "smtp_sasl.h"

 /*
  * Sender and receiver state. A session does not necessarily go through a
  * linear progression, but states are guaranteed to not jump backwards.
  * Normal sessions go from MAIL->RCPT->DATA->DOT->QUIT->LAST. The states
  * MAIL, RCPT, and DATA may also be followed by ABORT->QUIT->LAST.
  * 
  * When connection caching is enabled, the QUIT state is suppressed. Normal
  * sessions proceed as MAIL->RCPT->DATA->DOT->LAST, while aborted sessions
  * end with ABORT->LAST. The connection is left open for a limited time. An
  * RSET probe should be sent before attempting to reuse an open connection
  * for a new transaction.
  * 
  * The code to send an RSET probe is a special case with its own initial state
  * and with its own dedicated state transitions. The session proceeds as
  * RSET->LAST. This code is kept inside the main protocol engine for
  * consistent error handling and error reporting. It is not to be confused
  * with the code that sends RSET to abort a mail transaction in progress.
  * 
  * The code to send QUIT without message delivery transaction jumps into the
  * main state machine. If this introduces complications, then we should
  * introduce a second QUIT state with its own dedicated state transitions,
  * just like we did for RSET probes.
  * 
  * By default, the receiver skips the QUIT response. Some SMTP servers
  * disconnect after responding to ".", and some SMTP servers wait before
  * responding to QUIT.
  * 
  * Client states that are associated with sending mail (up to and including
  * SMTP_STATE_DOT) must have smaller numerical values than the non-sending
  * states (SMTP_STATE_ABORT .. SMTP_STATE_LAST).
  */

#define SMTP_STATE_XFORWARD_NAME_ADDR 0
#define SMTP_STATE_XFORWARD_PROTO_HELO 1
#define SMTP_STATE_MAIL  2
#define SMTP_STATE_RCPT  3
#define SMTP_STATE_DATA  4
#define SMTP_STATE_DOT  5
#define SMTP_STATE_ABORT 6
#define SMTP_STATE_RSET  7
#define SMTP_STATE_QUIT  8
#define SMTP_STATE_LAST  9

int    *xfer_timeouts[SMTP_STATE_LAST] = {
    &var_smtp_xfwd_tmout,  /* name/addr */
    &var_smtp_xfwd_tmout,  /* helo/proto */
    &var_smtp_mail_tmout,
    &var_smtp_rcpt_tmout,
    &var_smtp_data0_tmout,
    &var_smtp_data2_tmout,
    &var_smtp_rset_tmout,
    &var_smtp_rset_tmout,
    &var_smtp_quit_tmout,
};

char   *xfer_states[SMTP_STATE_LAST] = {
    "sending XFORWARD name/address",
    "sending XFORWARD protocol/helo_name",
    "sending MAIL FROM",
    "sending RCPT TO",
    "sending DATA command",
    "sending end of data -- message may be sent more than once",
    "sending final RSET",
    "sending RSET probe",
    "sending QUIT",
};

char   *xfer_request[SMTP_STATE_LAST] = {
    "XFORWARD name/address command",
    "XFORWARD helo/protocol command",
    "MAIL FROM command",
    "RCPT TO command",
    "DATA command",
    "end of DATA command",
    "final RSET command",
    "RSET probe",
    "QUIT command",
};

 /*
  * Note: MIME downgrade never happens for mail that must be delivered with
  * SMTPUTF8 (the sender requested SMTPUTF8, AND the delivery request
  * involves at least one UTF-8 envelope address or header value.
  */

#define SMTP_MIME_DOWNGRADE(session, request) \
    (var_disable_mime_oconv == 0 \
     && (session->features & SMTP_FEATURE_8BITMIME) == 0 \
     && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0)

#ifdef USE_TLS

static int smtp_start_tls(SMTP_STATE *);

#endif

 /*
  * Call-back information for header/body checks. We don't provide call-backs
  * for actions that change the message delivery time or destination.
  */

static void smtp_hbc_logger(void *, const char *, const char *, const char *, const char *);
static void smtp_text_out(void *, intconst char *, ssize_t, off_t);

HBC_CALL_BACKS smtp_hbc_callbacks[1] = {
    smtp_hbc_logger,
    smtp_text_out,
};

static int smtp_vrfy_tgt;

/* smtp_vrfy_init - initialize */

void    smtp_vrfy_init(void)
{
    static const NAME_CODE vrfy_init_table[] = {
 SMTP_VRFY_TGT_RCPT, SMTP_STATE_RCPT,
 SMTP_VRFY_TGT_DATA, SMTP_STATE_DATA,
 0,
    };

    if ((smtp_vrfy_tgt = name_code(vrfy_init_table, NAME_CODE_FLAG_NONE,
       var_smtp_vrfy_tgt)) == 0)
 msg_fatal("bad protocol stage: \"%s = %s\"",
    VAR_SMTP_VRFY_TGT, var_smtp_vrfy_tgt);
}

/* smtp_helo - perform initial handshake with SMTP server */

int     smtp_helo(SMTP_STATE *state)
{
    const char *myname = "smtp_helo";
    SMTP_SESSION *session = state->session;
    DELIVER_REQUEST *request = state->request;
    SMTP_ITERATOR *iter = state->iterator;
    SMTP_RESP *resp;
    SMTP_RESP fake;
    int     except;
    char   *lines;
    char   *words;
    char   *word;
    int     n;
    static const NAME_CODE xforward_features[] = {
 XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME,
 XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR,
 XFORWARD_PORT, SMTP_FEATURE_XFORWARD_PORT,
 XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO,
 XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO,
 XFORWARD_IDENT, SMTP_FEATURE_XFORWARD_IDENT,
 XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN,
 00,
    };
    const char *ehlo_words;
    int     discard_mask;
    static const NAME_MASK pix_bug_table[] = {
 PIX_BUG_DISABLE_ESMTP, SMTP_FEATURE_PIX_NO_ESMTP,
 PIX_BUG_DELAY_DOTCRLF, SMTP_FEATURE_PIX_DELAY_DOTCRLF,
 0,
    };
    const char *pix_bug_words;
    const char *pix_bug_source;
    int     pix_bug_mask;

#ifdef USE_TLS
    int     saved_features = session->features;
    int     tls_helo_status;

#endif
    const char *NOCLOBBER where;

    /*
     * Skip the plaintext SMTP handshake when connecting in SMTPS mode.
     */

#ifdef USE_TLS
    if (var_smtp_tls_wrappermode
 && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
 /* XXX Mix-up of per-session and per-request flags. */
 state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS;
 smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout,
     var_smtp_req_deadline, 0);
 tls_helo_status = smtp_start_tls(state);
 state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS;
 return (tls_helo_status);
    }
#endif

    /*
     * Prepare for disaster.
     */

    smtp_stream_setup(state->session->stream, var_smtp_helo_tmout,
        var_smtp_req_deadline, 0);
    if ((except = vstream_setjmp(state->session->stream)) != 0)
 return (smtp_stream_except(state, except, where));

    /*
     * If not recursing after STARTTLS, examine the server greeting banner
     * and decide if we are going to send EHLO as the next command.
     */

    if (var_smtp_tls_wrappermode
 || (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {

 /*
  * Read and parse the server's SMTP greeting banner.
 */

 where = "receiving the initial server greeting";
 switch ((resp = smtp_chat_resp(session))->code / 100) {
 case 2:
     break;
 case 5:
     if (var_smtp_skip_5xx_greeting)
  STR(resp->dsn_buf)[0] = '4';
     /* FALLTHROUGH */
 default:
     return (smtp_site_fail(state, STR(iter->host), resp,
       "host %s refused to talk to me: %s",
       session->namaddr,
       translit(resp->str, "\n"" ")));
 }

 /*
  * If the policy table specifies a bogus TLS security level, fail
  * now.
 */

#ifdef USE_TLS
 if (state->tls->level == TLS_LEV_INVALID)
     /* Warning is already logged. */
     return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
       SMTP_RESP_FAKE(&fake, "4.7.0"),
       "client TLS configuration problem"));
#endif

 /*
  * XXX Some PIX firewall versions require flush before ".<CR><LF>" so
  * it does not span a packet boundary. This hurts performance so it
  * is not on by default.
 */

 if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) {
     /* Best effort only. Ignore errors. */
     if (smtp_pix_bug_maps != 0
  && (pix_bug_words =
      maps_find(smtp_pix_bug_maps,
         STR(iter->addr), 0)) != 0) {
  pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_MAPS);
     } else {
  pix_bug_words = var_smtp_pix_bug_words;
  pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_WORDS);
     }
     if (*pix_bug_words) {
  pix_bug_mask = name_mask_opt(pix_bug_source, pix_bug_table,
          pix_bug_words,
         NAME_MASK_ANY_CASE | NAME_MASK_IGNORE);
  if ((pix_bug_mask & SMTP_FEATURE_PIX_DELAY_DOTCRLF)
      && request->msg_stats.incoming_arrival.tv_sec
      > vstream_ftime(state->session->stream) - var_smtp_pix_thresh)
      pix_bug_mask &= ~SMTP_FEATURE_PIX_DELAY_DOTCRLF;
  msg_info("%s: enabling PIX workarounds: %s for %s",
    request->queue_id,
    str_name_mask("pix workaround bitmask",
           pix_bug_table, pix_bug_mask),
    session->namaddrport);
  session->features |= pix_bug_mask;
     }
 }

 /*
  * See if we are talking to ourself. This should not be possible with
  * the way we implement DNS lookups. However, people are known to
  * sometimes screw up the naming service. And, mailer loops are still
  * possible when our own mailer routing tables are mis-configured.
 */

 words = resp->str;
 (void) mystrtok(&words, "- \t\n");
 for (n = 0; (word = mystrtok(&words, " \t\n")) != 0; n++) {
     if (n == 0 && strcasecmp(word, var_myhostname) == 0) {
  if (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
      msg_warn("host %s greeted me with my own hostname %s",
        session->namaddrport, var_myhostname);
     } else if (strcasecmp(word, "ESMTP") == 0)
  session->features |= SMTP_FEATURE_ESMTP;
 }
 if (smtp_mode) {
     if (var_smtp_always_ehlo
  && (session->features & SMTP_FEATURE_PIX_NO_ESMTP) == 0)
  session->features |= SMTP_FEATURE_ESMTP;
     if (var_smtp_never_ehlo
  || (session->features & SMTP_FEATURE_PIX_NO_ESMTP) != 0)
  session->features &= ~SMTP_FEATURE_ESMTP;
 } else {
     session->features |= SMTP_FEATURE_ESMTP;
 }
    }

    /*
     * If recursing after STARTTLS, there is no server greeting banner.
     * Always send EHLO as the next command.
     */

    else {
 session->features |= SMTP_FEATURE_ESMTP;
    }

    /*
     * Return the compliment. Fall back to SMTP if our ESMTP recognition
     * heuristic failed.
     */

    if (smtp_mode) {
 where = "performing the EHLO handshake";
 if (session->features & SMTP_FEATURE_ESMTP) {
     smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name);
     if ((resp = smtp_chat_resp(session))->code / 100 != 2) {
  if (resp->code == 421)
      return (smtp_site_fail(state, STR(iter->host), resp,
     "host %s refused to talk to me: %s",
        session->namaddr,
        translit(resp->str, "\n"" ")));
  else
      session->features &= ~SMTP_FEATURE_ESMTP;
     }
#ifdef USE_TLSRPT
     if (state->tlsrpt
  && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0)
  smtp_tlsrpt_set_ehlo_resp(state, resp->str);
#endif
 }
 if ((session->features & SMTP_FEATURE_ESMTP) == 0) {
     where = "performing the HELO handshake";
     smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name);
     if ((resp = smtp_chat_resp(session))->code / 100 != 2)
  return (smtp_site_fail(state, STR(iter->host), resp,
           "host %s refused to talk to me: %s",
           session->namaddr,
           translit(resp->str, "\n"" ")));
#ifdef USE_TLSRPT
     if (state->tlsrpt)
  trw_set_ehlo_resp(state->tlsrpt, resp->str);
#endif
 }
    } else {
 where = "performing the LHLO handshake";
 smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name);
 if ((resp = smtp_chat_resp(session))->code / 100 != 2)
     return (smtp_site_fail(state, STR(iter->host), resp,
       "host %s refused to talk to me: %s",
       session->namaddr,
       translit(resp->str, "\n"" ")));
    }

    /*
     * No early returns allowed, to ensure consistent handling of TLS and
     * SASL policies.
     */

    if (session->features & SMTP_FEATURE_ESMTP) {

 /*
  * Determine what server EHLO keywords to ignore, typically to avoid
  * inter-operability problems.
 */

 if (smtp_ehlo_dis_maps == 0
     || (ehlo_words = maps_find(smtp_ehlo_dis_maps,
           STR(iter->addr), 0)) == 0)
     ehlo_words = var_smtp_ehlo_dis_words;
 if (smtp_ehlo_dis_maps && smtp_ehlo_dis_maps->error) {
     msg_warn("%s: %s map lookup error for %s",
       session->state->request->queue_id,
       smtp_ehlo_dis_maps->title, STR(iter->addr));
     vstream_longjmp(session->stream, SMTP_ERR_DATA);
 }
 discard_mask = ehlo_mask(ehlo_words);
 if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
     msg_info("discarding EHLO keywords: %s",
       str_ehlo_mask(discard_mask));

 /*
  * Pick up some useful features offered by the SMTP server. XXX Until
  * we have a portable routine to convert from string to off_t with
  * proper overflow detection, ignore the message size limit
  * advertised by the SMTP server. Otherwise, we might do the wrong
  * thing when the server advertises a really huge message size limit.
  * 
  * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...",
  * because MicroSoft implemented AUTH based on an old draft.
 */

 lines = resp->str;
 for (n = 0; (words = mystrtok(&lines, "\n")) != 0/* see below */ ) {
     if (mystrtok(&words, "- ")
  && (word = mystrtok(&words, " \t=")) != 0) {
  if (n == 0) {
      if (session->helo != 0)
   myfree(session->helo);

      /*
       * XXX: Keep the original case: we don't expect a single
       * SMTP server to randomly change the case of its helo
       * response. If different capitalization is detected, we
       * should assume disjoint TLS caches.
     */

      session->helo = mystrdup(word);
      if (strcasecmp(word, var_myhostname) == 0
   && (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) != 0) {
   msg_warn("host %s replied to HELO/EHLO"
     " with my own hostname %s",
     session->namaddrport, var_myhostname);
   if (session->features & SMTP_FEATURE_BEST_MX)
       return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
          SMTP_RESP_FAKE(&fake, "5.4.6"),
      "mail for %s loops back to myself",
         request->nexthop));
   else
       return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
          SMTP_RESP_FAKE(&fake, "4.4.6"),
      "mail for %s loops back to myself",
         request->nexthop));
      }
  } else if (strcasecmp(word, "8BITMIME") == 0) {
      if ((discard_mask & EHLO_MASK_8BITMIME) == 0)
   session->features |= SMTP_FEATURE_8BITMIME;
  } else if (strcasecmp(word, "PIPELINING") == 0) {
      if ((discard_mask & EHLO_MASK_PIPELINING) == 0)
   session->features |= SMTP_FEATURE_PIPELINING;
  } else if (strcasecmp(word, "XFORWARD") == 0) {
      if ((discard_mask & EHLO_MASK_XFORWARD) == 0)
   while ((word = mystrtok(&words, " \t")) != 0)
       session->features |=
    name_code(xforward_features,
       NAME_CODE_FLAG_NONE, word);
  } else if (strcasecmp(word, "SIZE") == 0) {
      if ((discard_mask & EHLO_MASK_SIZE) == 0) {
   session->features |= SMTP_FEATURE_SIZE;
   if ((word = mystrtok(&words, " \t")) != 0) {
       if (!alldig(word))
    msg_warn("bad EHLO SIZE limit \"%s\" from %s",
      word, session->namaddrport);
       else
    session->size_limit = off_cvt_string(word);
   }
      }
#ifdef USE_TLS
  } else if (strcasecmp(word, "STARTTLS") == 0) {
      /* Ignored later if we already sent STARTTLS. */
      if ((discard_mask & EHLO_MASK_STARTTLS) == 0)
   session->features |= SMTP_FEATURE_STARTTLS;
  } else if (strcasecmp(word, "REQUIRETLS") == 0) {
      if ((discard_mask & EHLO_MASK_REQTLS) == 0
   && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS))
   session->features |= SMTP_FEATURE_REQTLS;
#endif
#ifdef USE_SASL_AUTH
  } else if (var_smtp_sasl_enable
      && strcasecmp(word, "AUTH") == 0) {
      if ((discard_mask & EHLO_MASK_AUTH) == 0)
   smtp_sasl_helo_auth(session, words);
#endif
  } else if (strcasecmp(word, "DSN") == 0) {
      if ((discard_mask & EHLO_MASK_DSN) == 0)
   session->features |= SMTP_FEATURE_DSN;
  } else if (strcasecmp(word, "SMTPUTF8") == 0) {
      if ((discard_mask & EHLO_MASK_SMTPUTF8) == 0)
   session->features |= SMTP_FEATURE_SMTPUTF8;
  }
  n++;
     }
 }
    }
    if (msg_verbose)
 msg_info("server features: 0x%x size %.0f",
   session->features, (double) session->size_limit);

    /*
     * Require that the server supports SMTPUTF8 when delivery requires
     * SMTPUTF8.
     * 
     * Fix 20140706: moved this before negotiating TLS, AUTH, and so on.
     * 
     * Fix 20250824: try multiple servers before giving up.
     * 
     * Fix 20250911: do not cache this session because it does not satisfy the
     * requirement expressed in the cache storage key.
     */

    if ((session->features & SMTP_FEATURE_SMTPUTF8) == 0
 && DELIVERY_REQUIRES_SMTPUTF8(request))
 return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
          | SMTP_MISC_FAIL_SOFT_NON_FINAL,
          DSN_BY_LOCAL_MTA,
          SMTP_RESP_FAKE(&fake, "5.6.7"),
          "message requires SMTPUTF8, but no "
          "server was found that supports "
          "SMTPUTF8. The last attempted server "
          "was %s", session->namaddr));

    /*
     * Fix 20140706: don't do silly things when the remote server announces
     * SMTPUTF8 but not 8BITMIME support. Our primary mission is to deliver
     * mail, not to force people into compliance.
     */

    if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0
 && (session->features & SMTP_FEATURE_8BITMIME) == 0) {
 msg_info("host %s offers SMTPUTF8 support, but not 8BITMIME",
   session->namaddr);
 session->features |= SMTP_FEATURE_8BITMIME;
    }

    /*
     * Require that the server announces REQUIRETLS when the sender requested
     * REQUIRETLS. Return the message as undeliverable only when there are no
     * more alternative MX hosts. With opportunistic REQUIRETLS, only log
     * that the server does not offer REQUIRETLS.
     */

#ifdef USE_TLS
    if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE
 && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0) {
 if ((session->features & SMTP_FEATURE_REQTLS) != 0) {
     if (state->tls_stats)
  smtp_tls_stat_decide_reqtls(state->tls_stats,
     TLS_CERT_IS_MATCHED(session->tls_context) ?
         SMTP_TLS_STAT_NAME_REQTLS :
         SMTP_TLS_STAT_NAME_NOCMATCH,
         POL_STAT_COMPLIANT);
 } else if (state->reqtls_level == SMTP_REQTLS_POLICY_ACT_ENFORCE) {
     if (state->tls_stats)
  smtp_tls_stat_decide_reqtls(state->tls_stats,
         SMTP_TLS_STAT_NAME_NONE,
         POL_STAT_VIOLATION);
     return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
       | SMTP_MISC_FAIL_SOFT_NON_FINAL,
       DSN_BY_LOCAL_MTA,
       SMTP_RESP_FAKE(&fake, "5.7.30"),
       "Sender requested REQUIRETLS, "
       "but no server was found that "
       "supports REQUIRETLS. The last "
       "attempted server was %s",
       session->namaddr));
 } else {
     if (state->tls_stats)
  smtp_tls_stat_decide_reqtls(state->tls_stats,
         SMTP_TLS_STAT_NAME_NONE,
         POL_STAT_COMPLIANT);
     msg_info("%s: Sender requested REQUIRETLS, but REQUIRETLS "
       "support was not offered by host %s",
       request->queue_id, session->namaddr);
 }
    }

    /*
     * TODO(wietse) Maybe log servers that announce REQUIRETLS and whether
     * the connection is authenticated?
     */

#endif

    /*
     * We use SMTP command pipelining if the server said it supported it.
     * Since we use blocking I/O, RFC 2197 says that we should inspect the
     * TCP window size and not send more than this amount of information.
     * Unfortunately this information is unavailable using the sockets
     * interface. However, we *can* get the TCP send buffer size on the local
     * TCP/IP stack. We should be able to fill this buffer without being
     * blocked, and then the kernel will effectively do non-blocking I/O for
     * us by automatically writing out the contents of its send buffer while
     * we are reading in the responses. In addition to TCP buffering we have
     * to be aware of application-level buffering by the vstream module,
     * which is limited to a couple kbytes.
     * 
     * XXX No need to do this before and after STARTTLS, but it's not a big deal
     * if we do.
     * 
     * XXX When TLS is turned on, the SMTP-level writes will be encapsulated as
     * TLS messages. Thus, the TCP-level payload will be larger than the
     * SMTP-level payload. This has implications for the PIPELINING engine.
     * 
     * To avoid deadlock, the PIPELINING engine needs to request a TCP send
     * buffer size that can hold the unacknowledged commands plus the TLS
     * encapsulation overhead.
     * 
     * The PIPELINING engine keeps the unacknowledged command size <= the
     * default VSTREAM buffer size (to avoid small-write performance issues
     * when the VSTREAM buffer size is at its default size). With a default
     * VSTREAM buffer size of 4096 there is no reason to increase the
     * unacknowledged command size as the TCP MSS increases. It's safer to
     * spread the remote SMTP server's recipient processing load over time,
     * than dumping a very large recipient list all at once.
     * 
     * For TLS encapsulation overhead we make a conservative guess: take the
     * current protocol overhead of ~40 bytes, double the number for future
     * proofing (~80 bytes), then round up the result to the nearest power of
     * 2 (128 bytes). Plus, be prepared for worst-case compression that
     * expands data by 1 kbyte, so that the worst-case SMTP payload per TLS
     * message becomes 15 kbytes.
     */

#define PIPELINING_BUFSIZE VSTREAM_BUFSIZE
#ifdef USE_TLS
#define TLS_WORST_PAYLOAD 16384
#define TLS_WORST_COMP_OVERHD 1024
#define TLS_WORST_PROTO_OVERHD 128
#define TLS_WORST_SMTP_PAYLOAD (TLS_WORST_PAYLOAD - TLS_WORST_COMP_OVERHD)
#define TLS_WORST_TOTAL_OVERHD (TLS_WORST_COMP_OVERHD + TLS_WORST_PROTO_OVERHD)
#endif

    if (session->features & SMTP_FEATURE_PIPELINING) {
 SOCKOPT_SIZE optlen;
 int     tcp_bufsize;
 int     enc_overhead = 0;

 optlen = sizeof(tcp_bufsize);
 if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET,
         SO_SNDBUF, (char *) &tcp_bufsize, &optlen) < 0)
     msg_fatal("%s: getsockopt: %m", myname);
#ifdef USE_TLS
 if (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS)
     enc_overhead +=
  (1 + (PIPELINING_BUFSIZE - 1)
   / TLS_WORST_SMTP_PAYLOAD) * TLS_WORST_TOTAL_OVERHD;
#endif
 if (tcp_bufsize < PIPELINING_BUFSIZE + enc_overhead) {
     tcp_bufsize = PIPELINING_BUFSIZE + enc_overhead;
     if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET,
      SO_SNDBUF, (char *) &tcp_bufsize, optlen) < 0)
  msg_fatal("%s: setsockopt: %m", myname);
 }
 if (msg_verbose)
     msg_info("Using %s PIPELINING, TCP send buffer size is %d, "
       "PIPELINING buffer size is %d",
       smtp_mode ? "ESMTP" : "LMTP",
       tcp_bufsize, PIPELINING_BUFSIZE);
    }
#ifdef USE_TLS

    /*
     * Skip this part if we already sent STARTTLS.
     */

    if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {

 /*
  * Optionally log unused STARTTLS opportunities.
 */

 if ((session->features & SMTP_FEATURE_STARTTLS) &&
     var_smtp_tls_note_starttls_offer &&
     state->tls->level <= TLS_LEV_NONE)
     msg_info("Host offered STARTTLS: [%s]", STR(iter->host));

 /*
  * Decide whether or not to send STARTTLS.
 */

 if ((session->features & SMTP_FEATURE_STARTTLS) != 0
     && smtp_tls_ctx != 0 && state->tls->level >= TLS_LEV_MAY) {

     /*
      * Prepare for disaster.
     */

     smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout,
         var_smtp_req_deadline, 0);
     if ((except = vstream_setjmp(state->session->stream)) != 0)
  return (smtp_stream_except(state, except,
     "receiving the STARTTLS response"));

     /*
      * Send STARTTLS. Recurse when the server accepts STARTTLS, after
      * resetting the SASL and EHLO features lists.
      * 
      * Reset the SASL mechanism list to avoid spurious warnings.
      * 
      * Use the smtp_sasl_tls_security_options feature to allow SASL
      * mechanisms that may not be allowed with plain-text
      * connections.
     */

     smtp_chat_cmd(session, "STARTTLS");
     if ((resp = smtp_chat_resp(session))->code / 100 == 2) {
#ifdef USE_SASL_AUTH
  if (session->features & SMTP_FEATURE_AUTH)
      smtp_sasl_cleanup(session);
#endif
  session->features = saved_features;
  /* XXX Mix-up of per-session and per-request flags. */
  state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS;
  tls_helo_status = smtp_start_tls(state);
  state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS;
  return (tls_helo_status);
     }
#ifdef USE_TLSRPT
     if (state->tlsrpt)
  trw_report_failure(state->tlsrpt,
       TLSRPT_STARTTLS_NOT_SUPPORTED,
        /* additional_info= */ (char *) 0,
        /* failure_reason= */ (char *) 0);
#endif

     /*
      * Give up if we must use TLS but the server rejects STARTTLS
      * although support for it was announced in the EHLO response.
      * 
      * When the sender requested REQUIRETLS, and the REQUIRETLS policy
      * requires TLS, return the message as undeliverable only when
      * there are no more alternative MX hosts.
     */

     session->features &= ~SMTP_FEATURE_STARTTLS;
     if (TLS_REQUIRED_BY_SECURITY_LEVEL(state->tls->level)
  || TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
  /* Before returning, decide all relevant policy status info. */
  if (TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
      if (state->tls_stats)
   smtp_tls_stat_decide_reqtls(state->tls_stats,
       SMTP_TLS_STAT_NAME_NOSTTLS,
          POL_STAT_VIOLATION);
  }
  if (TLS_REQUIRED_BY_SECURITY_LEVEL(state->tls->level)) {
      if (state->tls_stats)
   smtp_tls_stat_decide_sec_level(state->tls_stats,
             state->tls->level,
             POL_STAT_VIOLATION);
  }
  /* Then, REQUIRETLS failure must take precedence over other. */
  if (TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
      return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
        | SMTP_MISC_FAIL_SOFT_NON_FINAL,
        DSN_BY_LOCAL_MTA,
        SMTP_RESP_FAKE(&fake, "5.7.10"),
        "Sender requested REQUIRETLS, "
        "but host %s refused to "
        "start TLS: %s", session->namaddr,
        translit(resp->str, "\n"" ")));
  }
  /* TLS_REQUIRED_BY_SECURITY_LEVEL */
  return (smtp_site_fail(state, STR(iter->host), resp,
      "TLS is required, but host %s refused to start TLS: %s",
           session->namaddr,
           translit(resp->str, "\n"" ")));
     }
     /* Else try to continue in plain-text mode. */
 }

 /*
  * Give up if we must use TLS but can't for various reasons.
  * 
  * 200412 Be sure to provide the default clause at the bottom of this
  * block. When TLS is required we must never, ever, end up in
  * plain-text mode.
  * 
  * When the sender requested REQUIRETLS, and the REQUIRETLS policy
  * requires TLS, return the message as undeliverable only when there
  * are no more alternative MX hosts.
 */

 if (TLS_REQUIRED_BY_SECURITY_LEVEL(state->tls->level)
     || TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
     if (!(session->features & SMTP_FEATURE_STARTTLS)) {
#ifdef USE_TLSRPT
  if (state->tlsrpt)
      trw_report_failure(state->tlsrpt,
           TLSRPT_STARTTLS_NOT_SUPPORTED,
            /* additional_info= */ (char *) 0,
            /* failure_reason= */ (char *) 0);
#endif
  /* Before returning, decide all relevant policy status info. */
  if (TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level)) {
      if (state->tls_stats)
   smtp_tls_stat_decide_reqtls(state->tls_stats,
       SMTP_TLS_STAT_NAME_NOSTTLS,
          POL_STAT_VIOLATION);
  }
  if (TLS_REQUIRED_BY_SECURITY_LEVEL(state->tls->level))
      if (state->tls_stats)
   smtp_tls_stat_decide_sec_level(state->tls_stats,
             state->tls->level,
             POL_STAT_VIOLATION);
  /* Then, REQUIRETLS failure must take precedence over other. */
  if (TLS_REQUIRED_BY_REQTLS_POLICY(state->reqtls_level))
      return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
        | SMTP_MISC_FAIL_SOFT_NON_FINAL,
        DSN_BY_LOCAL_MTA,
        SMTP_RESP_FAKE(&fake, "5.7.30"),
        "Sender requested REQUIRETLS, "
        "but TLS service was not "
        "offered by host %s",
        session->namaddr));
  /* TLS_REQUIRED_BY_SECURITY_LEVEL */
  return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
           SMTP_RESP_FAKE(&fake, "4.7.4"),
     "TLS is required, but was not offered by host %s",
           session->namaddr));
     } else if (smtp_tls_ctx == 0) {
  return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
           SMTP_RESP_FAKE(&fake, "4.7.5"),
       "TLS is required, but our TLS engine is unavailable"));
     } else {
  msg_warn("%s: TLS is required but unavailable, don't know why",
    myname);
  return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
           SMTP_RESP_FAKE(&fake, "4.7.0"),
           "TLS is required, but unavailable"));
     }
 }
 /* Continue in plain-text mode. */
 if (state->tls_stats) {
     smtp_tls_stat_decide_sec_level(state->tls_stats, TLS_LEV_NONE,
        POL_STAT_COMPLIANT);
     if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE)
  smtp_tls_stat_decide_reqtls(state->tls_stats,
         SMTP_TLS_STAT_NAME_NONE,
         POL_STAT_COMPLIANT);
 }
    }
#endif
#ifdef USE_SASL_AUTH
    if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH))
 return (smtp_sasl_helo_login(state));
#endif

    return (0);
}

#ifdef USE_TLS

/* smtp_start_tls - turn on TLS and recurse into the HELO dialog */

static int smtp_start_tls(SMTP_STATE *state)
{
    SMTP_SESSION *session = state->session;
    SMTP_ITERATOR *iter = state->iterator;
    TLS_CLIENT_START_PROPS start_props;
    VSTRING *serverid;
    SMTP_RESP fake;
    TLS_CLIENT_INIT_PROPS init_props;
    VSTREAM *tlsproxy;
    VSTRING *port_buf;

    /*
     * When the TLS handshake succeeds, we can reuse a connection only if TLS
     * remains turned on for the lifetime of that connection. This requires
     * that the TLS library state is maintained in some proxy process, for
     * example, in tlsproxy(8). We then store the proxy file handle in the
     * connection cache, and reuse that file handle.
     * 
     * Otherwise, we must turn off connection caching. We can't turn off TLS in
     * one SMTP client process, save the open connection to a cache which is
     * shared with all SMTP clients, migrate the connection to another SMTP
     * client, and resume TLS there. When the TLS handshake fails, we can't
     * reuse the SMTP connection either, because the conversation is in an
     * unknown state.
     */

    if (state->tls->conn_reuse == 0)
 DONT_CACHE_THIS_SESSION;

    /*
     * The following assumes sites that use TLS in a perverse configuration:
     * multiple hosts per hostname, or even multiple hosts per IP address.
     * All this without a shared TLS session cache, and they still want to
     * use TLS session caching???
     * 
     * The TLS session cache records the trust chain verification status of
     * cached sessions. Different transports may have different CAfile or
     * CApath settings, perhaps to allow authenticated connections to sites
     * with private CA certs without trusting said private certs for other
     * sites. So we cannot assume that a trust chain valid for one transport
     * is valid for another. Therefore the client session id must include
     * either the transport name or the values of CAfile and CApath. We use
     * the transport name.
     * 
     * XXX: We store only one session per lookup key. Ideally the key maps
     * 1-to-1 to a server TLS session cache. We use the IP address, port and
     * ehlo response name to build a lookup key that works for split caches
     * (that announce distinct names) behind a load balancer.
     * 
     * XXX: The TLS library will salt the serverid with further details of the
     * protocol and cipher requirements including the server ehlo response.
     * Deferring the helo to the digested suffix results in more predictable
     * SSL session lookup key lengths. Add the current TLS security level to
     * account for TLS level overrides based on message content or envelope
     * metadata.
     */

    serverid = vstring_alloc(10);
    smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE
      | SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */
      | SMTP_KEY_FLAG_HOSTNAME
      | SMTP_KEY_FLAG_ADDR
      | SMTP_KEY_FLAG_TLS_LEVEL);

    if (state->tls->conn_reuse) {
 TLS_CLIENT_PARAMS tls_params;

 /*
  * Send all our wishes in one big request.
 */

 TLS_PROXY_CLIENT_INIT_PROPS(&init_props,
        log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL),
        log_level = var_smtp_tls_loglevel,
        verifydepth = var_smtp_tls_scert_vd,
        cache_type
        = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE),
        chain_files = var_smtp_tls_chain_files,
        cert_file = var_smtp_tls_cert_file,
        key_file = var_smtp_tls_key_file,
        dcert_file = var_smtp_tls_dcert_file,
        dkey_file = var_smtp_tls_dkey_file,
        eccert_file = var_smtp_tls_eccert_file,
        eckey_file = var_smtp_tls_eckey_file,
        CAfile = var_smtp_tls_CAfile,
        CApath = var_smtp_tls_CApath,
        mdalg = var_smtp_tls_fpt_dgst);
 TLS_PROXY_CLIENT_START_PROPS(&start_props,
         timeout = var_smtp_starttls_tmout,
         tls_level = state->tls->level,
         enable_rpk = state->tls->enable_rpk,
         nexthop = session->tls_nexthop,
         host = STR(iter->host),
         namaddr = session->namaddrport,
         sni = state->tls->sni,
         serverid = vstring_str(serverid),
         helo = session->helo,
         protocols = state->tls->protocols,
         cipher_grade = state->tls->grade,
         cipher_exclusions
         = vstring_str(state->tls->exclusions),
         matchargv = state->tls->matchargv,
         mdalg = var_smtp_tls_fpt_dgst,
#ifdef USE_TLSRPT
         tlsrpt = state->tlsrpt,
#else
         tlsrpt = 0,
#endif
         ffail_type = 0,
         dane = state->tls->dane);

 /*
  * The tlsproxy(8) server enforces timeouts that are larger than
  * those specified by the tlsproxy(8) client. These timeouts are a
  * safety net for the case that the tlsproxy(8) client fails to
  * enforce time limits. Normally, the tlsproxy(8) client would time
  * out and trigger a plaintext event in the tlsproxy(8) server, and
  * cause it to tear down the session.
  * 
  * However, the tlsproxy(8) server has no insight into the SMTP
  * protocol, and therefore it cannot by itself support different
  * timeouts at different SMTP protocol stages. Instead, we specify
  * the largest timeout (end-of-data) and rely on the SMTP client to
  * time out first, which normally results in a plaintext event in the
  * tlsproxy(8) server. Unfortunately, we cannot permit plaintext
  * events during the TLS handshake, so we specify a separate timeout
  * for that stage (the end-of-data timeout would be unreasonably
  * large anyway).
 */

#define PROXY_OPEN_FLAGS \
        (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT)

 port_buf = vstring_alloc(100);  /* minimize fragmentation */
 vstring_sprintf(port_buf, "%d", ntohs(iter->port));
 tlsproxy =
     tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS,
      session->stream, STR(iter->addr),
      STR(port_buf), var_smtp_starttls_tmout,
      var_smtp_data2_tmout, state->service,
      tls_proxy_client_param_from_config(&tls_params),
      &init_props, &start_props);
 vstring_free(port_buf);

 /*
  * To insert tlsproxy(8) between this process and the remote SMTP
  * server, we swap the file descriptors between the tlsproxy and
  * session->stream VSTREAMS, so that we don't lose all the
  * user-configurable session->stream attributes (such as longjump
  * buffers or timeouts).
  * 
  * TODO: the tlsproxy RPCs should return more error detail than a "NO"
  * result. OTOH, the in-process TLS engine does not return such info
  * either.
  * 
  * If the tlsproxy request fails we do not fall back to the in-process
  * TLS stack. Reason: the admin enabled connection reuse to respect
  * receiver policy; silently violating such policy would not be
  * useful.
  * 
  * We also don't fall back to the in-process TLS stack under low-traffic
  * conditions, to avoid frustrating attempts to debug a problem with
  * using the tlsproxy(8) service.
 */

 if (tlsproxy == 0) {
     session->tls_context = 0;
 } else {
     vstream_control(tlsproxy,
       CA_VSTREAM_CTL_DOUBLE,
       CA_VSTREAM_CTL_END);
     vstream_control(session->stream,
       CA_VSTREAM_CTL_SWAP_FD(tlsproxy),
       CA_VSTREAM_CTL_END);
     (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */

     /*
      * There must not be any pending data in the stream buffers
      * before we read the TLS context attributes.
     */

     vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);

     /*
      * After plumbing the plaintext stream, receive the TLS context
      * object. For this we use the same VSTREAM buffer that we also
      * use to receive subsequent SMTP commands, therefore we must be
      * prepared for the possibility that the remote SMTP server
      * starts talking immediately. The tlsproxy implementation sends
      * the TLS context before remote content. The attribute protocol
      * is robust enough that an adversary cannot insert their own TLS
      * context attributes.
     */

     session->tls_context = tls_proxy_context_receive(session->stream);
     if (session->tls_context) {
  session->features |= SMTP_FEATURE_FROM_PROXY;
  tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW,
    session->tls_context);
     }
 }
    } else {     /* state->tls->conn_reuse */

 /*
  * As of Postfix 2.5, tls_client_start() tries hard to always
  * complete the TLS handshake. It records the verification and match
  * status in the resulting TLScontext. It is now up to the
  * application to abort the TLS connection if it chooses.
  * 
  * XXX When tls_client_start() fails then we don't know what state the
  * SMTP connection is in, so we give up on this connection even if we
  * are not required to use TLS.
  * 
  * Large parameter lists are error-prone, so we emulate a language
  * feature that C does not have natively: named parameter lists.
 */

 session->tls_context =
     TLS_CLIENT_START(&start_props,
        ctx = smtp_tls_ctx,
        stream = session->stream,
        fd = -1,
        timeout = var_smtp_starttls_tmout,
        tls_level = state->tls->level,
        enable_rpk = state->tls->enable_rpk,
        nexthop = session->tls_nexthop,
        host = STR(iter->host),
        namaddr = session->namaddrport,
        sni = state->tls->sni,
        serverid = vstring_str(serverid),
        helo = session->helo,
        protocols = state->tls->protocols,
        cipher_grade = state->tls->grade,
        cipher_exclusions
        = vstring_str(state->tls->exclusions),
        matchargv = state->tls->matchargv,
        mdalg = var_smtp_tls_fpt_dgst,
#ifdef USE_TLSRPT
        tlsrpt = state->tlsrpt,
#else
        tlsrpt = 0,
#endif
        ffail_type = state->tls->ext_policy_failure,
        dane = state->tls->dane);

 /*
  * At this point there must not be any pending data in the stream
  * buffers.
 */

 vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
    }      /* state->tls->conn_reuse */

    vstring_free(serverid);

    if (session->tls_context == 0) {

 /*
  * We must avoid further I/O, the peer is in an undefined state.
 */

 DONT_USE_FORBIDDEN_SESSION;

 /*
  * If TLS is optional, try delivery to the same server over a
  * plaintext connection. Otherwise we would defer mail forever with
  * destinations that have no alternate MX host.
  * 
  * Don't fall back to plaintext if we were willing to use SASL-over-TLS
  * authentication. If the server doesn't announce SASL support over
  * plaintext connections, then we don't want delivery to fail with
  * "relay access denied".
  * 
  * If TLS is opportunistic, don't throttle the destination, otherwise if
  * the mail is volume is high enough we may have difficulty ever
  * draining even the deferred mail, as new mail provides a constant
  * stream of negative feedback.
 */

 if (PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE)
     RETRY_AS_PLAINTEXT;
 /* Leave all TLS feature policy status info as 'undecided'. */
 return (smtp_misc_fail(state, state->tls->level == TLS_LEV_MAY ?
          SMTP_MISC_FAIL_NONE : SMTP_MISC_FAIL_THROTTLE,
          DSN_BY_LOCAL_MTA,
          SMTP_RESP_FAKE(&fake, "4.7.5"),
          "Cannot start TLS: handshake failure"));
    }

    /*
     * If we are verifying the server certificate and are not happy with the
     * result, abort the delivery here. We have a usable TLS session with the
     * server, so no need to disable I/O, ... we can even be polite and send
     * "QUIT".
     * 
     * See src/tls/tls_level.c and src/tls/tls.h. Levels above "encrypt" require
     * matching.
     * 
     * NOTE: We use "IS_MATCHED" to satisfy policy, but "IS_SECURED" to log
     * effective security.  Thus "half-dane" is never "Verified" only
     * "Trusted", but matching is enforced here.
     * 
     * NOTE: When none of the TLSA records were usable, "dane" and "half-dane"
     * fall back to "encrypt", updating the tls_context level accordingly, so
     * we must check that here, and not state->tls->level.
     */

    if (TLS_MUST_MATCH(session->tls_context->level)) {
 if (!TLS_CERT_IS_MATCHED(session->tls_context)) {
     int     trusted = TLS_CERT_IS_TRUSTED(session->tls_context);

#ifdef USE_TLSRPT

     /*
      * Don't create a TLSRPT 'failure' event here, if the TLS engine
      * already reported a more specific reason.
     */

     if (state->tlsrpt && session->tls_context->rpt_reported == 0) {
  (void) trw_report_failure(state->tlsrpt, trusted ?
       TLSRPT_CERTIFICATE_HOST_MISMATCH :
       TLSRPT_CERTIFICATE_NOT_TRUSTED,
        /* additional_info= */ (char *) 0,
        /* failure_reason= */ (char *) 0);
     }
#endif
     /* Finalize TLS feature policy status info before giving up. */
     if (state->tls_stats)
  smtp_tls_stat_decide_sec_level(state->tls_stats,
            session->tls_context->level,
            POL_STAT_VIOLATION);

     /*
      * When the sender requested REQUIRETLS, and REQUIRETLS is
      * enforced, return the message as undeliverable only when there
      * are no more alternative MX hosts.
     */

     if (state->reqtls_level == SMTP_REQTLS_POLICY_ACT_ENFORCE) {
  if (state->tls_stats)
      smtp_tls_stat_decide_reqtls(state->tls_stats,
      SMTP_TLS_STAT_NAME_NOCMATCH,
      POL_STAT_VIOLATION);
  return (smtp_misc_fail(state, SMTP_MISC_FAIL_DONT_CACHE
           | SMTP_MISC_FAIL_SOFT_NON_FINAL,
           DSN_BY_LOCAL_MTA,
           SMTP_RESP_FAKE(&fake, "5.7.10"),
           "Sender requested REQUIRETLS, "
           "but no %s server certificate "
           "was found. The last attempted "
           "server was %s", trusted ?
           "matching" : "trusted",
           session->namaddr));
     } else if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE) {
  if (state->tls_stats)
      smtp_tls_stat_decide_reqtls(state->tls_stats,
      SMTP_TLS_STAT_NAME_NOCMATCH,
      POL_STAT_COMPLIANT);
     }
     return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
       SMTP_RESP_FAKE(&fake, "4.7.5"),
       "Server certificate not verified"));
 }
    }

    /*
     * Create a TLSRPT 'success' event only if the TLS engine has not created
     * a TLSRPT event. For example, The TLS engine will create a TLSRPT
     * 'failure' event when the TLS handshake was be successful, but the
     * security level was downgraded from opportunistic "dane" to
     * unauthenticated "encrypt".
     */

#ifdef USE_TLSRPT
    if (state->tlsrpt && session->tls_context->rpt_reported == 0)
 (void) trw_report_success(state->tlsrpt);
#endif

    /*
     * Report relaxed enforcement if the initial TLS level was degraded.
     */

    if (state->tls_stats)
 smtp_tls_stat_decide_sec_level(state->tls_stats,
           session->tls_context->level,
           POL_STAT_COMPLIANT);

    /*
     * At this point we have to re-negotiate the "EHLO" to reget the
     * feature-list.
     */

    return (smtp_helo(state));
}

#endif

/* smtp_hbc_logger - logging call-back for header/body checks */

static void smtp_hbc_logger(void *context, const char *action,
               const char *where, const char *content,
               const char *text)
{
    const SMTP_STATE *state = (SMTP_STATE *) context;

    if (*text) {
 msg_info("%s: %s: %s %.200s: %s",
   state->request->queue_id, action, where, content, text);
    } else {
 msg_info("%s: %s: %s %.200s",
   state->request->queue_id, action, where, content);
    }
}

/* smtp_text_out - output one header/body record */

static void smtp_text_out(void *context, int rec_type,
             const char *text, ssize_t len,
             off_t unused_offset)
{
    SMTP_STATE *state = (SMTP_STATE *) context;
    SMTP_SESSION *session = state->session;
    ssize_t data_left;
    const char *data_start;

    /*
     * Deal with an impedance mismatch between Postfix queue files (record
     * length <= $message_line_length_limit) and SMTP (DATA record length <=
     * $smtp_line_length_limit). The code below does a little too much work
     * when the SMTP line length limit is disabled, but it avoids code
     * duplication, and thus, it avoids testing and maintenance problems.
     */

    data_left = len;
    data_start = text;
    do {
 if (state->space_left == var_smtp_line_limit
     && data_left > 0 && *data_start == '.')
     smtp_fputc('.', session->stream);
 if (ENFORCING_SIZE_LIMIT(var_smtp_line_limit)
     && data_left >= state->space_left) {
     smtp_fputs(data_start, state->space_left, session->stream);
     data_start += state->space_left;
     data_left -= state->space_left;
     state->space_left = var_smtp_line_limit;
     if (data_left > 0 || rec_type == REC_TYPE_CONT) {
  smtp_fputc(' ', session->stream);
  state->space_left -= 1;

  /*
   * XXX This can insert a line break into the middle of a
   * multi-byte character (not necessarily UTF-8). Note that
   * multibyte characters can span queue file records, for
   * example if line_length_limit == smtp_line_length_limit.
 */

  if (state->logged_line_length_limit == 0) {
      msg_info("%s: breaking line > %d bytes with <CR><LF>SPACE",
        state->request->queue_id, var_smtp_line_limit);
      state->logged_line_length_limit = 1;
  }
     }
 } else {
     if (rec_type == REC_TYPE_CONT) {
  smtp_fwrite(data_start, data_left, session->stream);
  state->space_left -= data_left;
     } else {
  smtp_fputs(data_start, data_left, session->stream);
  state->space_left = var_smtp_line_limit;
     }
     break;
 }
    } while (data_left > 0);
}

/* smtp_format_out - output one header/body record */

static void PRINTFLIKE(34) smtp_format_out(void *, intconst char *,...);

static void smtp_format_out(void *context, int rec_type, const char *fmt,...)
{
    static VSTRING *vp;
    va_list ap;

    if (vp == 0)
 vp = vstring_alloc(100);
    va_start(ap, fmt);
    vstring_vsprintf(vp, fmt, ap);
    va_end(ap);
    smtp_text_out(context, rec_type, vstring_str(vp), VSTRING_LEN(vp), 0);
}

/* smtp_header_out - output one message header */

static void smtp_header_out(void *context, int unused_header_class,
               const HEADER_OPTS *unused_info,
               VSTRING *buf, off_t offset)
{
    char   *start = vstring_str(buf);
    char   *line;
    char   *next_line;

    /*
     * This code destroys the header. We could try to avoid clobbering it,
     * but we're not going to use the data any further.
     */

    for (line = start; line; line = next_line) {
 next_line = split_at(line, '\n');
 smtp_text_out(context, REC_TYPE_NORM, line, next_line ?
        next_line - line - 1 : strlen(line), offset);
    }
}

/* smtp_header_rewrite - rewrite message header before output */

static void smtp_header_rewrite(void *context, int header_class,
            const HEADER_OPTS *header_info,
            VSTRING *buf, off_t offset)
{
    SMTP_STATE *state = (SMTP_STATE *) context;
    int     did_rewrite = 0;
    char   *line;
    char   *start;
    char   *next_line;
    char   *end_line;
    char   *result;

    /*
     * Apply optional header filtering.
     */

    if (smtp_header_checks) {
 result = hbc_header_checks(context, smtp_header_checks, header_class,
       header_info, buf, offset);
 if (result == 0)
     return;
 if (result == HBC_CHECKS_STAT_ERROR) {
     msg_warn("%s: smtp header checks lookup error",
       state->request->queue_id);
     vstream_longjmp(state->session->stream, SMTP_ERR_DATA);
 }
 if (result != STR(buf)) {
     vstring_strcpy(buf, result);
     myfree(result);
 }
    }

    /*
     * Rewrite primary header addresses that match the smtp_generic_maps. The
     * cleanup server already enforces that all headers have proper lengths
     * and that all addresses are in proper form, so we don't have to repeat
     * that.
     */

    if (smtp_generic_maps && header_info && header_class == MIME_HDR_PRIMARY
 && (header_info->flags & (HDR_OPT_SENDER | HDR_OPT_RECIP)) != 0) {
 TOK822 *tree;
 TOK822 **addr_list;
 TOK822 **tpp;

 tree = tok822_parse(vstring_str(buf)
       + strlen(header_info->name) + 1);
 addr_list = tok822_grep(tree, TOK822_ADDR);
 for (tpp = addr_list; *tpp; tpp++)
     did_rewrite |= smtp_map11_tree(tpp[0], smtp_generic_maps,
         smtp_ext_prop_mask & EXT_PROP_GENERIC);
 if (did_rewrite) {
     vstring_truncate(buf, strlen(header_info->name));
     vstring_strcat(buf, ": ");
     tok822_externalize(buf, tree, TOK822_STR_HEAD);
 }
 myfree((void *) addr_list);
 tok822_free_tree(tree);
    }

    /*
     * Pass through unmodified headers without reconstruction.
     */

    if (did_rewrite == 0) {
 smtp_header_out(context, header_class, header_info, buf, offset);
 return;
    }

    /*
     * A rewritten address list contains one address per line. The code below
     * replaces newlines by spaces, to fit as many addresses on a line as
     * possible (without rearranging the order of addresses). Prepending
     * white space to the beginning of lines is delegated to the output
     * routine.
     * 
     * Code derived from cleanup_fold_header().
     */

    for (line = start = vstring_str(buf); line != 0; line = next_line) {
 end_line = line + strcspn(line, "\n");
 if (line > start) {
     if (end_line - start < 70) { /* TAB counts as one */
  line[-1] = ' ';
     } else {
  start = line;
     }
 }
 next_line = *end_line ? end_line + 1 : 0;
    }

    /*
     * Prepend a tab to continued header lines that went through the address
     * rewriting machinery. Just like smtp_header_out(), this code destroys
     * the header. We could try to avoid clobbering it, but we're not going
     * to use the data any further.
     * 
     * Code derived from cleanup_out_header().
     */

    for (line = start = vstring_str(buf); line != 0; line = next_line) {
 next_line = split_at(line, '\n');
 if (line == start || IS_SPACE_TAB(*line)) {
     smtp_text_out(state, REC_TYPE_NORM, line, next_line ?
     next_line - line - 1 : strlen(line), offset);
 } else {
     smtp_format_out(state, REC_TYPE_NORM, "\t%s", line);
 }
    }
}

/* smtp_body_rewrite - rewrite message body before output */

static void smtp_body_rewrite(void *context, int type,
                 const char *buf, ssize_t len,
                 off_t offset)
{
    SMTP_STATE *state = (SMTP_STATE *) context;
    char   *result;

    /*
     * Apply optional body filtering.
     */

    if (smtp_body_checks) {
 result = hbc_body_checks(context, smtp_body_checks, buf, len, offset);
 if (result == buf) {
     smtp_text_out(state, type, buf, len, offset);
 } else if (result == HBC_CHECKS_STAT_ERROR) {
     msg_warn("%s: smtp body checks lookup error",
       state->request->queue_id);
     vstream_longjmp(state->session->stream, SMTP_ERR_DATA);
 } else if (result != 0) {
     smtp_text_out(state, type, result, strlen(result), offset);
     myfree(result);
 }
    }
}

/* smtp_mime_fail - MIME problem */

static void smtp_mime_fail(SMTP_STATE *state, int mime_errs)
{
    const MIME_STATE_DETAIL *detail;
    SMTP_RESP fake;

    detail = mime_state_detail(mime_errs);
    smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
     SMTP_RESP_FAKE(&fake, detail->dsn),
     "%s", detail->text);
}

/* smtp_out_raw_or_mime - output buffer, raw output or MIME-aware */

static int smtp_out_raw_or_mime(SMTP_STATE *state, int rec_type, VSTRING *buf)
{
    SMTP_SESSION *session = state->session;
    int     mime_errs;

    if (session->mime_state == 0) {
 smtp_text_out((void *) state, rec_type, vstring_str(buf),
        VSTRING_LEN(buf), (off_t) 0);
    } else {
 mime_errs =
     mime_state_update(session->mime_state, rec_type,
         vstring_str(buf), VSTRING_LEN(buf));
 if (mime_errs) {
     smtp_mime_fail(state, mime_errs);
     return (-1);
 }
    }
    return (0);
}

/* smtp_out_add_header - format address header, uses session->scratch* */

static int smtp_out_add_header(SMTP_STATE *state, const char *label,
                  const char *lt, const char *addr,
                  const char *gt)
{
    SMTP_SESSION *session = state->session;

    smtp_rewrite_generic_internal(session->scratch2, addr);
    vstring_sprintf(session->scratch, "%s: %s", label, lt);
    smtp_quote_822_address_flags(session->scratch,
     vstring_str(session->scratch2),
     QUOTE_FLAG_DEFAULT | QUOTE_FLAG_APPEND);
    vstring_strcat(session->scratch, gt);
    return (smtp_out_raw_or_mime(state, REC_TYPE_NORM, session->scratch));
}

/* smtp_out_add_headers - output additional headers, uses session->scratch* */

static int smtp_out_add_headers(SMTP_STATE *state)
{
    /* Prepend headers in the same order as mail_copy.c. */
    if (smtp_cli_attr.flags & SMTP_CLI_FLAG_RETURN_PATH)
 if (smtp_out_add_header(state, "Return-Path""<",
    state->request->sender, ">") < 0)
     return (-1);
    if (smtp_cli_attr.flags & SMTP_CLI_FLAG_ORIG_RCPT)
 if (smtp_out_add_header(state, "X-Original-To""",
    state->request->rcpt_list.info->orig_addr, "") < 0)
     return (-1);
    if (smtp_cli_attr.flags & SMTP_CLI_FLAG_DELIVERED_TO)
 if (smtp_out_add_header(state, "Delivered-To""",
      state->request->rcpt_list.info->address, "") < 0)
     return (-1);
    return (0);
}

/* smtp_loop - exercise the SMTP protocol engine */

static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
               NOCLOBBER int recv_state)
{
    const char *myname = "smtp_loop";
    DELIVER_REQUEST *request = state->request;
    SMTP_SESSION *session = state->session;
    SMTP_ITERATOR *iter = state->iterator;
    SMTP_RESP *resp;
    RECIPIENT *rcpt;
    VSTRING *next_command = vstring_alloc(100);
    int    *NOCLOBBER survivors = 0;
    NOCLOBBER int next_state;
    NOCLOBBER int next_rcpt;
    NOCLOBBER int send_rcpt;
    NOCLOBBER int recv_rcpt;
    NOCLOBBER int nrcpt;
    NOCLOBBER int recv_done;
    int     except;
    int     rec_type;
    NOCLOBBER int prev_type = 0;
    NOCLOBBER int mail_from_rejected;
    NOCLOBBER int downgrading;
    int     mime_errs;
    SMTP_RESP fake;
    int     fail_status;

    /* Caution: changes to RETURN() also affect code outside the main loop. */

#define RETURN(x) do { \
 if (recv_state != SMTP_STATE_LAST) \
     DONT_CACHE_THIS_SESSION; \
 vstring_free(next_command); \
 if (survivors) \
     myfree((void *) survivors); \
 if (session->mime_state) \
     session->mime_state = mime_state_free(session->mime_state); \
 return (x); \
    } while (0)

#define SENDER_IS_AHEAD \
 (recv_state < send_state || recv_rcpt != send_rcpt)

#define SENDER_IN_WAIT_STATE \
 (send_state == SMTP_STATE_DOT || send_state == SMTP_STATE_LAST)

#define SENDING_MAIL \
 (recv_state <= SMTP_STATE_DOT)

#define CANT_RSET_THIS_SESSION \
 (session->features |= SMTP_FEATURE_RSET_REJECTED)

    /*
     * Pipelining support requires two loops: one loop for sending and one
     * for receiving. Each loop has its own independent state. Most of the
     * time the sender can run ahead of the receiver by as much as the TCP
     * send buffer permits. There are only two places where the sender must
     * wait for status information from the receiver: once after sending DATA
     * and once after sending QUIT.
     * 
     * The sender state advances until the TCP send buffer would overflow, or
     * until the sender needs status information from the receiver. At that
     * point the receiver starts processing responses. Once the receiver has
     * caught up with the sender, the sender resumes sending commands. If the
     * receiver detects a serious problem (MAIL FROM rejected, all RCPT TO
     * commands rejected, DATA rejected) it forces the sender to abort the
     * SMTP dialog with RSET and QUIT.
     */

    nrcpt = 0;
    next_rcpt = send_rcpt = recv_rcpt = recv_done = 0;
    mail_from_rejected = 0;

    /*
     * Prepare for disaster. This should not be needed because the design
     * guarantees that no output is flushed before smtp_chat_resp() is
     * called.
     * 
     * 1) Every SMTP command fits entirely in a VSTREAM output buffer.
     * 
     * 2) smtp_loop() never invokes smtp_chat_cmd() without making sure that
     * there is sufficient space for the command in the output buffer.
     * 
     * 3) smtp_loop() flushes the output buffer to avoid server timeouts.
     * 
     * Changing any of these would violate the design, and would likely break
     * SMTP pipelining.
     * 
     * We set up the error handler anyway (only upon entry to avoid wasting
     * resources) because 1) there is code below that expects that VSTREAM
     * timeouts are enabled, and 2) this allows us to detect if someone broke
     * Postfix by introducing spurious flush before read operations.
     */

    if (send_state < SMTP_STATE_XFORWARD_NAME_ADDR
 || send_state > SMTP_STATE_QUIT)
 msg_panic("%s: bad sender state %d (receiver state %d)",
    myname, send_state, recv_state);
    smtp_stream_setup(session->stream, *xfer_timeouts[send_state],
        var_smtp_req_deadline, 0);
    if ((except = vstream_setjmp(session->stream)) != 0) {
 msg_warn("smtp_proto: spurious flush before read in send state %d",
   send_state);
 RETURN(SENDING_MAIL ? smtp_stream_except(state, except,
          xfer_states[send_state]) : -1);
    }

    /*
     * The main protocol loop.
     */

    do {

 /*
  * Build the next command.
 */

 switch (send_state) {

     /*
      * Sanity check.
     */

 default:
     msg_panic("%s: bad sender state %d", myname, send_state);

     /*
      * Build the XFORWARD command. With properly sanitized
      * information, the command length stays within the 512 byte
      * command line length limit.
      * 
      * XXX smtpd_xforward_preset() initializes some fields as "unknown"
      * and some as null; historically, pickup(8) does not send any of
      * these, and the queue manager presets absent fields to "not
      * available" except for the rewrite context which is preset to
      * local by way of migration aid. These definitions need to be
      * centralized for maintainability.
     */

#ifndef CAN_FORWARD_CLIENT_NAME
#define _ATTR_AVAIL_AND_KNOWN_(val) \
 (DEL_REQ_ATTR_AVAIL(val) && strcasecmp((val), "unknown"))
#define CAN_FORWARD_CLIENT_NAME _ATTR_AVAIL_AND_KNOWN_
#define CAN_FORWARD_CLIENT_ADDR _ATTR_AVAIL_AND_KNOWN_
#define CAN_FORWARD_CLIENT_PORT _ATTR_AVAIL_AND_KNOWN_
#define CAN_FORWARD_PROTO_NAME _ATTR_AVAIL_AND_KNOWN_
#define CAN_FORWARD_HELO_NAME DEL_REQ_ATTR_AVAIL
#define CAN_FORWARD_IDENT_NAME DEL_REQ_ATTR_AVAIL
#define CAN_FORWARD_RWR_CONTEXT DEL_REQ_ATTR_AVAIL
#endif

 case SMTP_STATE_XFORWARD_NAME_ADDR:
     vstring_strcpy(next_command, XFORWARD_CMD);
     if ((session->features & SMTP_FEATURE_XFORWARD_NAME)
  && CAN_FORWARD_CLIENT_NAME(request->client_name)) {
  vstring_strcat(next_command, " " XFORWARD_NAME "=");
  xtext_quote_append(next_command, request->client_name, "");
     }
     if ((session->features & SMTP_FEATURE_XFORWARD_ADDR)
  && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) {
  vstring_strcat(next_command, " " XFORWARD_ADDR "=");
  xtext_quote_append(next_command, request->client_addr, "");
     }
     if ((session->features & SMTP_FEATURE_XFORWARD_PORT)
  && CAN_FORWARD_CLIENT_PORT(request->client_port)) {
  vstring_strcat(next_command, " " XFORWARD_PORT "=");
  xtext_quote_append(next_command, request->client_port, "");
     }
     if (session->send_proto_helo)
  next_state = SMTP_STATE_XFORWARD_PROTO_HELO;
     else
  next_state = SMTP_STATE_MAIL;
     break;

 case SMTP_STATE_XFORWARD_PROTO_HELO:
     vstring_strcpy(next_command, XFORWARD_CMD);
     if ((session->features & SMTP_FEATURE_XFORWARD_PROTO)
  && CAN_FORWARD_PROTO_NAME(request->client_proto)) {
  vstring_strcat(next_command, " " XFORWARD_PROTO "=");
  xtext_quote_append(next_command, request->client_proto, "");
     }
     if ((session->features & SMTP_FEATURE_XFORWARD_HELO)
  && CAN_FORWARD_HELO_NAME(request->client_helo)) {
  vstring_strcat(next_command, " " XFORWARD_HELO "=");
  xtext_quote_append(next_command, request->client_helo, "");
     }
     if ((session->features & SMTP_FEATURE_XFORWARD_IDENT)
  && CAN_FORWARD_IDENT_NAME(request->log_ident)) {
  vstring_strcat(next_command, " " XFORWARD_IDENT "=");
  xtext_quote_append(next_command, request->log_ident, "");
     }
     if ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN)
  && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)) {
  vstring_strcat(next_command, " " XFORWARD_DOMAIN "=");
  xtext_quote_append(next_command,
       strcmp(request->rewrite_context, MAIL_ATTR_RWR_LOCAL) ?
         XFORWARD_DOM_REMOTE : XFORWARD_DOM_LOCAL, "");
     }
     next_state = SMTP_STATE_MAIL;
     break;

     /*
      * Build the MAIL FROM command.
     */

 case SMTP_STATE_MAIL:
     request->msg_stats.reuse_count = session->reuse_count;
     GETTIMEOFDAY(&request->msg_stats.conn_setup_done);
     smtp_rewrite_generic_internal(session->scratch2, request->sender);
     smtp_quote_821_address(session->scratch,
       vstring_str(session->scratch2));
     vstring_sprintf(next_command, "MAIL FROM:<%s>",
       vstring_str(session->scratch));
     /* XXX Don't announce SIZE if we're going to MIME downgrade. */
     if (session->features & SMTP_FEATURE_SIZE /* RFC 1870 */
  && !SMTP_MIME_DOWNGRADE(session, request))
  vstring_sprintf_append(next_command, " SIZE=%lu",
           request->data_size);
     if (session->features & SMTP_FEATURE_8BITMIME) { /* RFC 1652 */
  if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0)
      vstring_strcat(next_command, " BODY=8BITMIME");
  else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0)
      vstring_strcat(next_command, " BODY=7BIT");
  else if (strcmp(request->encoding, MAIL_ATTR_ENC_NONE) != 0)
      msg_warn("%s: unknown content encoding: %s",
        request->queue_id, request->encoding);
     }
     if (session->features & SMTP_FEATURE_DSN) {
  if (request->dsn_envid[0]) {
      vstring_sprintf_append(next_command, " ENVID=");
      xtext_quote_append(next_command, request->dsn_envid, "+=");
  }
  /* Fix 20250825: limit content exposure in bounce. */
#ifdef USE_TLS
  if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE
      && (session->features & SMTP_FEATURE_REQTLS) == 0)
      vstring_sprintf_append(next_command, " RET=%s",
        dsn_ret_str(DSN_RET_HDRS));
  else
#endif
  if (request->dsn_ret)
      vstring_sprintf_append(next_command, " RET=%s",
        dsn_ret_str(request->dsn_ret));
     }

     /*
      * Request SMTPUTF8 when the remote SMTP server supports SMTPUTF8
      * and the sender requested SMTPUTF8 support.
      * 
      * If the sender requested SMTPUTF8 but the remote SMTP server does
      * not support SMTPUTF8, then we have already determined earlier
      * that delivering this message without SMTPUTF8 will not break
      * the SMTPUTF8 promise that was made to the sender.
     */

     if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0
  && (request->sendopts & SMTPUTF8_FLAG_REQUESTED) != 0)
  vstring_strcat(next_command, " SMTPUTF8");

     /*
      * Request REQUIRETLS when the remote SMTP server supports
      * REQUIRETLS and the sender requested REQUIRETLS.
     */

#ifdef USE_TLS
     if (state->reqtls_level > SMTP_REQTLS_POLICY_ACT_DISABLE) {
  if ((session->features & SMTP_FEATURE_REQTLS) != 0) {
      vstring_strcat(next_command, " REQUIRETLS");
  } else if (state->reqtls_level
      == SMTP_REQTLS_POLICY_ACT_ENFORCE) {
      msg_panic("Can't happen: must enforce REQUIRETLS, but "
         "host %s did not announce REQUIRETLS support",
         session->namaddr);
  }
     }
#endif

     /*
      * We authenticate the local MTA only, but not the sender.
     */

#ifdef USE_SASL_AUTH
     if (var_smtp_sasl_enable
  && var_smtp_dummy_mail_auth
  && (session->features & SMTP_FEATURE_AUTH))
  vstring_strcat(next_command, " AUTH=<>");
#endif

     /*
      * CVE-2009-3555 (TLS renegotiation). Try to detect a mail
      * hijacking attack that prepends malicious EHLO/MAIL/RCPT/DATA
      * commands to our TLS session.
      * 
      * For the attack to succeed, the remote SMTP server must reply to
      * the malicious EHLO/MAIL/RCPT/DATA commands after completing
      * TLS (re)negotiation, so that the replies arrive in our TLS
      * session (otherwise the Postfix SMTP client would time out
      * waiting for an answer). With some luck we can detect this
      * specific attack as a server MAIL reply that arrives before we
      * send our own MAIL command.
      * 
      * We don't apply this test to the HELO command because the result
      * would be very timing sensitive, and we don't apply this test
      * to RCPT and DATA replies because these may be pipelined for
      * legitimate reasons.
     */

#ifdef USE_TLS
     if (var_smtp_tls_blk_early_mail_reply
  && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0
  && (vstream_peek(session->stream) > 0
      || peekfd(vstream_fileno(session->stream)) > 0))
  session->features |= SMTP_FEATURE_EARLY_TLS_MAIL_REPLY;
#endif

     /*
      * We now return to our regular broadcast.
     */

     next_state = SMTP_STATE_RCPT;
     break;

     /*
      * Build one RCPT TO command before we have seen the MAIL FROM
      * response.
     */

 case SMTP_STATE_RCPT:
     rcpt = request->rcpt_list.info + send_rcpt;
     smtp_rewrite_generic_internal(session->scratch2, rcpt->address);
     smtp_quote_821_address(session->scratch,
       vstring_str(session->scratch2));
     vstring_sprintf(next_command, "RCPT TO:<%s>",
       vstring_str(session->scratch));
     if (session->features & SMTP_FEATURE_DSN) {
  /* XXX DSN xtext encode address value not type. */
  const char *orcpt_type_addr = rcpt->dsn_orcpt;

  /* Fix 20140706: don't use empty rcpt->orig_addr. */
  if (orcpt_type_addr[0] == 0 && rcpt->orig_addr[0] != 0) {
      quote_822_local(session->scratch, rcpt->orig_addr);
      vstring_sprintf(session->scratch2, "%s;%s",
      /* Fix 20140707: sender must request SMTPUTF8. */
        ((request->sendopts & SMTPUTF8_FLAG_ALL)
     && !allascii(vstring_str(session->scratch))
       && valid_utf8_stringz(vstring_str(session->scratch))) ?
        "utf-8" : "rfc822",
        vstring_str(session->scratch));
      orcpt_type_addr = vstring_str(session->scratch2);
  }
  if (orcpt_type_addr[0] != 0) {
      /* Fix 20140706: don't send unquoted ORCPT. */
      /* Fix 20140707: quoting method must match orcpt type. */
      /* Fix 20140707: handle uxtext encoder errors. */
      if (strncasecmp(orcpt_type_addr, "utf-8;"6) == 0) {
   if (uxtext_quote(session->scratch,
      orcpt_type_addr, "+=") != 0)
       vstring_sprintf_append(next_command, " ORCPT=%s",
          vstring_str(session->scratch));
      } else {
   xtext_quote(session->scratch, orcpt_type_addr, "=");
   vstring_sprintf_append(next_command, " ORCPT=%s",
          vstring_str(session->scratch));
      }
  }
  if (rcpt->dsn_notify)
      vstring_sprintf_append(next_command, " NOTIFY=%s",
        dsn_notify_str(rcpt->dsn_notify));
     }
     if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state))
  next_state = (DEL_REQ_TRACE_ONLY(request->flags)
         && smtp_vrfy_tgt == SMTP_STATE_RCPT) ?
      SMTP_STATE_ABORT : SMTP_STATE_DATA;
     break;

     /*
      * Build the DATA command before we have seen all the RCPT TO
      * responses.
     */

 case SMTP_STATE_DATA:
     vstring_strcpy(next_command, "DATA");
     next_state = SMTP_STATE_DOT;
     break;

     /*
      * Build the "." command after we have seen the DATA response
      * (DATA is a protocol synchronization point).
      * 
      * Changing the connection caching state here is safe because it
      * affects none of the not-yet processed replies to
      * already-generated commands.
     */

 case SMTP_STATE_DOT:
     vstring_strcpy(next_command, ".");
     if (THIS_SESSION_IS_EXPIRED)
  DONT_CACHE_THIS_SESSION;
     next_state = THIS_SESSION_IS_CACHED ?
  SMTP_STATE_LAST : SMTP_STATE_QUIT;
     break;

     /*
      * The SMTP_STATE_ABORT sender state is entered by the sender
      * when it has verified all recipients; or it is entered by the
      * receiver when all recipients are verified or rejected, and is
      * then left before the bottom of the main loop.
      * 
      * Changing the connection caching state here is safe because there
      * are no not-yet processed replies to already-generated
      * commands.
     */

 case SMTP_STATE_ABORT:
     vstring_strcpy(next_command, "RSET");
     if (THIS_SESSION_IS_EXPIRED)
  DONT_CACHE_THIS_SESSION;
     next_state = THIS_SESSION_IS_CACHED ?
  SMTP_STATE_LAST : SMTP_STATE_QUIT;
     break;

     /*
      * Build the RSET command. This is entered as initial state from
      * smtp_rset() and has its own dedicated state transitions. It is
      * used to find out the status of a cached session before
      * attempting mail delivery.
     */

 case SMTP_STATE_RSET:
     vstring_strcpy(next_command, "RSET");
     next_state = SMTP_STATE_LAST;
     break;

     /*
      * Build the QUIT command before we have seen the "." or RSET
      * response. This is entered as initial state from smtp_quit(),
      * or is reached near the end of any non-cached session.
      * 
      * Changing the connection caching state here is safe. If this
      * command is pipelined together with a preceding command, then
      * connection caching was already turned off. Do not clobber the
      * "bad connection" flag.
     */

 case SMTP_STATE_QUIT:
     vstring_strcpy(next_command, "QUIT");
     next_state = SMTP_STATE_LAST;
     if (THIS_SESSION_IS_CACHED)
  DONT_CACHE_THIS_SESSION;
     break;

     /*
      * The final sender state has no action associated with it.
     */

 case SMTP_STATE_LAST:
     VSTRING_RESET(next_command);
     break;
 }
 VSTRING_TERMINATE(next_command);

 /*
  * Process responses until the receiver has caught up. Vstreams
  * automatically flush buffered output when reading new data.
  * 
  * Flush unsent output if command pipelining is off or if no I/O
  * happened for a while. This limits the accumulation of client-side
  * delays in pipelined sessions.
  * 
  * The PIPELINING engine will flush the VSTREAM buffer if the sender
  * could otherwise produce more output than fits the PIPELINING
  * buffer. This generally works because we know exactly how much
  * output we produced since the last time that the sender and
  * receiver synchronized the SMTP state. However this logic is not
  * applicable after the sender enters the DATA phase, where it does
  * not synchronize with the receiver until the <CR><LF>.<CR><LF>.
  * Thus, the PIPELINING engine no longer knows how much data is
  * pending in the TCP send buffer. For this reason, if PIPELINING is
  * enabled, we always pipeline QUIT after <CR><LF>.<CR><LF>. This is
  * safe because once the receiver reads <CR><LF>.<CR><LF>, its TCP
  * stack either has already received the QUIT<CR><LF>, or else it
  * acknowledges all bytes up to and including <CR><LF>.<CR><LF>,
  * making room in the sender's TCP stack for QUIT<CR><LF>.
 */

#define CHECK_PIPELINING_BUFSIZE \
 (recv_state != SMTP_STATE_DOT || send_state != SMTP_STATE_QUIT)

 if (SENDER_IN_WAIT_STATE
     || (SENDER_IS_AHEAD
  && ((session->features & SMTP_FEATURE_PIPELINING) == 0
      || (CHECK_PIPELINING_BUFSIZE
   && (VSTRING_LEN(next_command) + 2
      + vstream_bufstat(session->stream, VSTREAM_BST_OUT_PEND)
       > PIPELINING_BUFSIZE))
      || time((time_t *) 0)
      - vstream_ftime(session->stream) > 10))) {
     while (SENDER_IS_AHEAD) {

  /*
   * Sanity check.
 */

  if (recv_state < SMTP_STATE_XFORWARD_NAME_ADDR
      || recv_state > SMTP_STATE_QUIT)
      msg_panic("%s: bad receiver state %d (sender state %d)",
         myname, recv_state, send_state);

  /*
   * Receive the next server response. Use the proper timeout,
   * and log the proper client state in case of trouble.
   * 
   * XXX If we lose the connection before sending end-of-data,
   * find out if the server sent a premature end-of-data reply.
   * If this read attempt fails, report "lost connection while
   * sending message body", not "lost connection while sending
   * end-of-data".
   * 
   * "except" becomes zero just above the protocol loop, and stays
   * zero or triggers an early return from the loop. In just
   * one case: loss of the connection when sending the message
   * body, we record the exception, and keep processing in the
   * hope of detecting a premature 5XX. We must be careful to
   * not clobber this non-zero value once it is set. The
   * variable need not survive longjmp() calls, since the only
   * setjmp() which does not return early is the one sets this
   * condition, subsequent failures always return early.
 */

#define LOST_CONNECTION_INSIDE_DATA (except == SMTP_ERR_EOF)

  smtp_stream_setup(session->stream, *xfer_timeouts[recv_state],
      var_smtp_req_deadline, 0);
  if (LOST_CONNECTION_INSIDE_DATA) {
      if (vstream_setjmp(session->stream) != 0)
   RETURN(smtp_stream_except(state, SMTP_ERR_EOF,
        "sending message body"));
  } else {
      if ((except = vstream_setjmp(session->stream)) != 0)
   RETURN(SENDING_MAIL ? smtp_stream_except(state, except,
          xfer_states[recv_state]) : -1);
  }
  resp = smtp_chat_resp(session);

  /*
   * Process the response.
 */

  switch (recv_state) {

      /*
       * Process the XFORWARD response.
     */

  case SMTP_STATE_XFORWARD_NAME_ADDR:
      if (resp->code / 100 != 2)
   msg_warn("host %s said: %s (in reply to %s)",
     session->namaddrport,
     translit(resp->str, "\n"" "),
          xfer_request[SMTP_STATE_XFORWARD_NAME_ADDR]);
      if (session->send_proto_helo)
   recv_state = SMTP_STATE_XFORWARD_PROTO_HELO;
      else
   recv_state = SMTP_STATE_MAIL;
      break;

  case SMTP_STATE_XFORWARD_PROTO_HELO:
      if (resp->code / 100 != 2)
   msg_warn("host %s said: %s (in reply to %s)",
     session->namaddrport,
     translit(resp->str, "\n"" "),
         xfer_request[SMTP_STATE_XFORWARD_PROTO_HELO]);
      recv_state = SMTP_STATE_MAIL;
      break;

      /*
       * Process the MAIL FROM response. When the server
       * rejects the sender, set the mail_from_rejected flag so
       * that the receiver may apply a course correction.
     */

  case SMTP_STATE_MAIL:
      if (resp->code / 100 != 2) {
   smtp_mesg_fail(state, STR(iter->host), resp,
           "host %s said: %s (in reply to %s)",
           session->namaddr,
           translit(resp->str, "\n"" "),
           xfer_request[SMTP_STATE_MAIL]);
   mail_from_rejected = 1;
      }

      /*
       * CVE-2009-3555 (TLS renegotiation). Whatever it was
       * that arrived before we sent our MAIL FROM command, it
       * was not a fatal-level TLS alert message. It could be a
       * warning-level TLS alert message, or a ChangeCipherSpec
       * message, but such messages are not normally sent in
       * the middle of a TLS session. We disconnect and try
       * again later.
     */

#ifdef USE_TLS
      if (var_smtp_tls_blk_early_mail_reply
   && (session->features & SMTP_FEATURE_EARLY_TLS_MAIL_REPLY)) {
   smtp_site_fail(state, DSN_BY_LOCAL_MTA,
           SMTP_RESP_FAKE(&fake, "4.7.0"),
           "unexpected server message");
   msg_warn("server %s violates %s policy",
     session->namaddr,
     VAR_LMTP_SMTP(TLS_BLK_EARLY_MAIL_REPLY));
   mail_from_rejected = 1;
      }
#endif

      /*
       * We now return to our regular broadcast.
     */

      recv_state = SMTP_STATE_RCPT;
      break;

      /*
       * Process one RCPT TO response. If MAIL FROM was
       * rejected, ignore RCPT TO responses: all recipients are
       * dead already. When all recipients are rejected the
       * receiver may apply a course correction.
       * 
       * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply
       * must be treated as if the server replied with 452.
       * However, this causes "too much mail data" to be
       * treated as a recoverable error, which is wrong. I'll
       * stick with RFC 821.
     */

  case SMTP_STATE_RCPT:
      if (!mail_from_rejected) {
#ifdef notdef
   if (resp->code == 552) {
       resp->code = 452;
       resp->dsn[0] = '4';
   }
#endif
   rcpt = request->rcpt_list.info + recv_rcpt;
   if (resp->code / 100 == 2) {
       if (!smtp_mode) {
    if (survivors == 0)
        survivors = (int *)
     mymalloc(request->rcpt_list.len
       * sizeof(int));
    survivors[nrcpt] = recv_rcpt;
       }
       ++nrcpt;
       /* If trace-only, mark the recipient done. */
       if (DEL_REQ_TRACE_ONLY(request->flags)
    && smtp_vrfy_tgt == SMTP_STATE_RCPT) {
    translit(resp->str, "\n"" ");
    smtp_rcpt_done(state, resp, rcpt);
       }
   } else {
       smtp_rcpt_fail(state, rcpt, STR(iter->host), resp,
     "host %s said: %s (in reply to %s)",
        session->namaddr,
        translit(resp->str, "\n"" "),
        xfer_request[SMTP_STATE_RCPT]);
   }
      }
      /* If trace-only, send RSET instead of DATA. */
      if (++recv_rcpt == SMTP_RCPT_LEFT(state))
   recv_state = (DEL_REQ_TRACE_ONLY(request->flags)
          && smtp_vrfy_tgt == SMTP_STATE_RCPT) ?
       SMTP_STATE_ABORT : SMTP_STATE_DATA;
      /* XXX Also: record if non-delivering session. */
      break;

      /*
       * Process the DATA response. When the server rejects
       * DATA, set nrcpt to a negative value so that the
       * receiver can apply a course correction.
     */

  case SMTP_STATE_DATA:
      recv_state = SMTP_STATE_DOT;
      if (resp->code / 100 != 3) {
   if (nrcpt > 0)
       smtp_mesg_fail(state, STR(iter->host), resp,
     "host %s said: %s (in reply to %s)",
        session->namaddr,
        translit(resp->str, "\n"" "),
        xfer_request[SMTP_STATE_DATA]);
   nrcpt = -1;
      }

      /*
       * In the case of a successful address probe with target
       * equal to DATA, the remote server is now in the DATA
       * state, and therefore we must not make any further
       * attempt to send or receive on this connection. This
       * means that we cannot not reuse the general-purpose
       * course-correction logic below which sends RSET (and
       * perhaps QUIT). Instead we "jump" straight to the exit
       * and force an unceremonious disconnect.
     */

      else if (DEL_REQ_TRACE_ONLY(request->flags)
        && smtp_vrfy_tgt == SMTP_STATE_DATA) {
   for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
       rcpt = request->rcpt_list.info + nrcpt;
       if (!SMTP_RCPT_ISMARKED(rcpt)) {
    translit(resp->str, "\n"" ");
    SMTP_RESP_SET_DSN(resp, "2.0.0");
    smtp_rcpt_done(state, resp, rcpt);
       }
   }
   DONT_CACHE_THIS_SESSION;
   send_state = recv_state = SMTP_STATE_LAST;
      }
      break;

      /*
       * Process the end of message response. Ignore the
       * response when no recipient was accepted: all
       * recipients are dead already, and the next receiver
       * state is SMTP_STATE_LAST/QUIT regardless. Otherwise,
       * if the message transfer fails, bounce all remaining
       * recipients, else cross off the recipients that were
       * delivered.
     */

  case SMTP_STATE_DOT:
      GETTIMEOFDAY(&request->msg_stats.deliver_done);
      if (smtp_mode) {
   if (nrcpt > 0) {
       if (resp->code / 100 != 2) {
    smtp_mesg_fail(state, STR(iter->host), resp,
     "host %s said: %s (in reply to %s)",
            session->namaddr,
          translit(resp->str, "\n"" "),
            xfer_request[SMTP_STATE_DOT]);
       } else {
    for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
        rcpt = request->rcpt_list.info + nrcpt;
        if (!SMTP_RCPT_ISMARKED(rcpt)) {
     translit(resp->str, "\n"" ");
     smtp_rcpt_done(state, resp, rcpt);
        }
    }
       }
   }
      }

      /*
       * With LMTP we have one response per accepted RCPT TO
       * command. Stay in the SMTP_STATE_DOT state until we
       * have collected all responses.
     */

      else {
   if (nrcpt > 0) {
       rcpt = request->rcpt_list.info
    + survivors[recv_done++];
       if (resp->code / 100 != 2) {
    smtp_rcpt_fail(state, rcpt, STR(iter->host), resp,
     "host %s said: %s (in reply to %s)",
            session->namaddr,
          translit(resp->str, "\n"" "),
            xfer_request[SMTP_STATE_DOT]);
       } else {
    translit(resp->str, "\n"" ");
    smtp_rcpt_done(state, resp, rcpt);
       }
   }
   if (msg_verbose)
       msg_info("%s: got %d of %d end-of-data replies",
         myname, recv_done, nrcpt);
   if (recv_done < nrcpt)
       break;
      }

      /*
       * XXX Do not change the connection caching state here,
       * even if the connection caching timer expired between
       * generating the command and processing the reply,
       * otherwise the sender and receiver loops get out of
       * sync. The caller will call smtp_quit() if appropriate.
     */

      if (var_skip_quit_resp || THIS_SESSION_IS_CACHED
   || LOST_CONNECTION_INSIDE_DATA)
   recv_state = SMTP_STATE_LAST;
      else
   recv_state = SMTP_STATE_QUIT;
      break;

      /*
       * Receive the RSET response.
       * 
       * The SMTP_STATE_ABORT sender state is entered by the
       * sender when it has verified all recipients; or it is
       * entered by the receiver when all recipients are
       * verified or rejected, and is then left before the
       * bottom of the main loop.
       * 
       * XXX Do not change the connection caching state here, even
       * if the server rejected RSET or if the connection
       * caching timer expired between generating the command
       * and processing the reply, otherwise the sender and
       * receiver loops get out of sync. The caller will call
       * smtp_quit() if appropriate.
     */

  case SMTP_STATE_ABORT:
      recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ?
      SMTP_STATE_LAST : SMTP_STATE_QUIT);
      break;

      /*
       * This is the initial receiver state from smtp_rset().
       * It is used to find out the status of a cached session
       * before attempting mail delivery.
     */

  case SMTP_STATE_RSET:
      if (resp->code / 100 != 2)
   CANT_RSET_THIS_SESSION;
      recv_state = SMTP_STATE_LAST;
      break;

      /*
       * Receive, but otherwise ignore, the QUIT response.
     */

  case SMTP_STATE_QUIT:
      recv_state = SMTP_STATE_LAST;
      break;
  }
     }

     /*
      * At this point, the sender and receiver are fully synchronized.
     */


     /*
      * We know the server response to every command that was sent.
      * Apply a course correction if necessary: the sender wants to
      * send RCPT TO but MAIL FROM was rejected; the sender wants to
      * send DATA but all recipients were rejected; the sender wants
      * to deliver the message but DATA was rejected.
     */

     if ((send_state == SMTP_STATE_RCPT && mail_from_rejected)
  || (send_state == SMTP_STATE_DATA && nrcpt == 0)
  || (send_state == SMTP_STATE_DOT && nrcpt < 0)) {
  send_state = recv_state = SMTP_STATE_ABORT;
  send_rcpt = recv_rcpt = 0;
  vstring_strcpy(next_command, "RSET");
  if (THIS_SESSION_IS_EXPIRED)
      DONT_CACHE_THIS_SESSION;
  next_state = THIS_SESSION_IS_CACHED ?
      SMTP_STATE_LAST : SMTP_STATE_QUIT;
  /* XXX Also: record if non-delivering session. */
  next_rcpt = 0;
     }
 }

 /*
  * Make the next sender state the current sender state.
 */

 if (send_state == SMTP_STATE_LAST)
     continue;

 /*
  * Special case if the server accepted the DATA command. If the
  * server accepted at least one recipient send the entire message.
  * Otherwise, just send "." as per RFC 2197.
  * 
  * XXX If there is a hard MIME error while downgrading to 7-bit mail,
  * disconnect ungracefully, because there is no other way to cancel a
  * transaction in progress.
 */

 if (send_state == SMTP_STATE_DOT && nrcpt > 0) {

     smtp_stream_setup(session->stream, var_smtp_data1_tmout,
         var_smtp_req_deadline, var_smtp_min_data_rate);

     if ((except = vstream_setjmp(session->stream)) == 0) {

  if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0)
      msg_fatal("seek queue file: %m");

  downgrading = SMTP_MIME_DOWNGRADE(session, request);

  /*
   * XXX Don't downgrade just because generic_maps is turned
   * on.
 */

#define SMTP_ANY_CHECKS (smtp_header_checks || smtp_body_checks)

  if (downgrading || smtp_generic_maps || SMTP_ANY_CHECKS)
      session->mime_state = mime_state_alloc(downgrading ?
          MIME_OPT_DOWNGRADE
       | MIME_OPT_REPORT_NESTING :
            SMTP_ANY_CHECKS == 0 ?
           MIME_OPT_DISABLE_MIME :
          0,
          smtp_generic_maps
           || smtp_header_checks ?
             smtp_header_rewrite :
          smtp_header_out,
           (MIME_STATE_ANY_END) 0,
          smtp_body_checks ?
        smtp_body_rewrite :
          smtp_text_out,
           (MIME_STATE_ANY_END) 0,
         (MIME_STATE_ERR_PRINT) 0,
          (void *) state);
  state->space_left = var_smtp_line_limit;

  if ((smtp_cli_attr.flags & SMTP_CLI_MASK_ADD_HEADERS) != 0
      && smtp_out_add_headers(state) < 0)
      RETURN(0);

  while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) {
      if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
   break;
      if (smtp_out_raw_or_mime(state, rec_type,
          session->scratch) < 0)
   RETURN(0);
      prev_type = rec_type;
  }

  if (session->mime_state) {

      /*
       * The cleanup server normally ends MIME content with a
       * normal text record. The following code is needed to
       * flush an internal buffer when someone submits 8-bit
       * mail not ending in newline via /usr/sbin/sendmail
       * while MIME input processing is turned off, and MIME
       * 8bit->7bit conversion is requested upon delivery.
       * 
       * Or some error while doing generic address mapping.
     */

      mime_errs =
   mime_state_update(session->mime_state, rec_type, ""0);
      if (mime_errs) {
   smtp_mime_fail(state, mime_errs);
   RETURN(0);
      }
  } else if (prev_type == REC_TYPE_CONT) /* missing newline */
      smtp_fputs(""0, session->stream);
  if (session->features & SMTP_FEATURE_PIX_DELAY_DOTCRLF) {
      smtp_flush(session->stream);/* hurts performance */
      sleep(var_smtp_pix_delay); /* not to mention this */
  }
  if (vstream_ferror(state->src))
      msg_fatal("queue file read error");
  if (rec_type != REC_TYPE_XTRA) {
      msg_warn("%s: bad record type: %d in message content",
        request->queue_id, rec_type);
      fail_status = smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
          SMTP_RESP_FAKE(&fake, "5.3.0"),
          "unreadable mail queue entry");
      /* Bailing out, abort stream with prejudice */
      (void) vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
      DONT_USE_FORBIDDEN_SESSION;
      /* If bounce_append() succeeded, status is still 0 */
      if (state->status == 0)
   (void) mark_corrupt(state->src);
      /* Don't override smtp_mesg_fail() here. */
      RETURN(fail_status);
  }
     } else {
  if (!LOST_CONNECTION_INSIDE_DATA)
      RETURN(smtp_stream_except(state, except,
           "sending message body"));

  /*
   * We will clear the stream error flag to try and read a
   * premature 5XX response, so it is important to flush any
   * unwritten data. Otherwise, we will try to flush it again
   * before reading, which may incur an unnecessary delay and
   * will prevent the reading of any response that is not
   * already buffered (bundled with the DATA 354 response).
   * 
   * Not much point in sending QUIT at this point, skip right to
   * SMTP_STATE_LAST. The read engine above will likewise avoid
   * looking for a QUIT response.
 */

  (void) vstream_fpurge(session->stream, VSTREAM_PURGE_WRITE);
  next_state = SMTP_STATE_LAST;
     }
 }

 /*
  * Copy the next command to the buffer and update the sender state.
 */

 if (except == 0) {
     smtp_chat_cmd(session, "%s", vstring_str(next_command));
 } else {
     DONT_CACHE_THIS_SESSION;
 }
 send_state = next_state;
 send_rcpt = next_rcpt;
    } while (recv_state != SMTP_STATE_LAST);
    RETURN(0);
}

/* smtp_xfer - send a batch of envelope information and the message data */

int     smtp_xfer(SMTP_STATE *state)
{
    DELIVER_REQUEST *request = state->request;
    SMTP_SESSION *session = state->session;
    SMTP_RESP fake;
    int     send_state;
    int     recv_state;
    int     send_name_addr;
    int     result;

    /*
     * Sanity check. Recipients should be unmarked at this point.
     */

    if (SMTP_RCPT_LEFT(state) <= 0)
 msg_panic("smtp_xfer: bad recipient count: %d",
    SMTP_RCPT_LEFT(state));
    if (SMTP_RCPT_ISMARKED(request->rcpt_list.info))
 msg_panic("smtp_xfer: bad recipient status: %d",
    request->rcpt_list.info->u.status);

    /*
     * See if we should even try to send this message at all. This code sits
     * here rather than in the EHLO processing code, because of SMTP
     * connection caching.
     */

    if (session->size_limit > 0 && session->size_limit < request->data_size) {
 smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
         SMTP_RESP_FAKE(&fake, "5.3.4"),
      "message size %lu exceeds size limit %.0f of server %s",
         request->data_size, (double) session->size_limit,
         session->namaddr);
 /* Redundant. We abort this delivery attempt. */
 state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION;
 return (0);
    }

    /*
     * Use XFORWARD to forward the origin of this email message across an
     * SMTP-based content filter. Send client attribute information only if
     * it exists (i.e. remote submission). Local submissions have no client
     * attributes; the mail will appear to originate from the content filter
     * which is acceptable.
     */

    send_name_addr =
 var_smtp_send_xforward
 && (((session->features & SMTP_FEATURE_XFORWARD_NAME)
      && CAN_FORWARD_CLIENT_NAME(request->client_name))
     || ((session->features & SMTP_FEATURE_XFORWARD_ADDR)
  && CAN_FORWARD_CLIENT_ADDR(request->client_addr))
     || ((session->features & SMTP_FEATURE_XFORWARD_PORT)
  && CAN_FORWARD_CLIENT_PORT(request->client_port)));
    session->send_proto_helo =
 var_smtp_send_xforward
 && (((session->features & SMTP_FEATURE_XFORWARD_PROTO)
      && CAN_FORWARD_PROTO_NAME(request->client_proto))
     || ((session->features & SMTP_FEATURE_XFORWARD_HELO)
  && CAN_FORWARD_HELO_NAME(request->client_helo))
     || ((session->features & SMTP_FEATURE_XFORWARD_IDENT)
  && CAN_FORWARD_IDENT_NAME(request->log_ident))
     || ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN)
  && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)));
    if (send_name_addr)
 recv_state = send_state = SMTP_STATE_XFORWARD_NAME_ADDR;
    else if (session->send_proto_helo)
 recv_state = send_state = SMTP_STATE_XFORWARD_PROTO_HELO;
    else
 recv_state = send_state = SMTP_STATE_MAIL;

    /*
     * Remember this session's "normal completion", even if the server 4xx-ed
     * some or all recipients. Connection or handshake errors with a later MX
     * host should not cause this destination be marked as unreachable.
     */

    result = smtp_loop(state, send_state, recv_state);

    if (result == 0
    /* Just in case */
 && vstream_ferror(session->stream) == 0
 && vstream_feof(session->stream) == 0)
 state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION;

    return (result);
}

/* smtp_rset - send a lone RSET command */

int     smtp_rset(SMTP_STATE *state)
{

    /*
     * This works because SMTP_STATE_RSET is a dedicated sender/recipient
     * entry state, with SMTP_STATE_LAST as next sender/recipient state.
     */

    return (smtp_loop(state, SMTP_STATE_RSET, SMTP_STATE_RSET));
}

/* smtp_quit - send a lone QUIT command */

int     smtp_quit(SMTP_STATE *state)
{

    /*
     * This works because SMTP_STATE_QUIT is the last state with a sender
     * action, with SMTP_STATE_LAST as the next sender/recipient state.
     */

    return (smtp_loop(state, SMTP_STATE_QUIT, var_skip_quit_resp ?
        SMTP_STATE_LAST : SMTP_STATE_QUIT));
}

Messung V0.5 in Prozent
C=84 H=90 G=86

¤ 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.0.86Bemerkung:  (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.