]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.4-20180805-nonprod
authorWietse Venema <wietse@porcupine.org>
Sun, 5 Aug 2018 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Thu, 9 Aug 2018 04:33:43 +0000 (00:33 -0400)
18 files changed:
postfix/HISTORY
postfix/RELEASE_NOTES
postfix/html/smtpd.8.html
postfix/makedefs
postfix/man/man8/smtpd.8
postfix/src/global/ehlo_mask.c
postfix/src/global/ehlo_mask.h
postfix/src/global/mail_version.h
postfix/src/global/smtp_stream.c
postfix/src/global/smtp_stream.h
postfix/src/postscreen/postscreen_smtpd.c
postfix/src/smtpd/smtpd.c
postfix/src/smtpd/smtpd.h
postfix/src/smtpd/smtpd_chat.c
postfix/src/smtpd/smtpd_chat.h
postfix/src/smtpd/smtpd_state.c
postfix/src/util/vbuf.c
postfix/src/util/vstream.c

index 25a231d51b5f82c87e71b9428091134304b44e3e..07f0442af0acefea6478e30524ff695877ffa3b6 100644 (file)
@@ -23630,3 +23630,19 @@ Apologies for any names omitted.
        VSTRING during vstream_fflush(); added a simple 'allow'
        filter for vstream_control() requests; added a unit test.
        File: util/vstream.c.
+
+20180805
+
+       Cleanup: vbuf_get() now sets the EOF flag, so that reading
+       from a VSTRING stream works as expected. File: util/vbuf.c.
+
+       SMTP server support for CHUNKING (BDAT) per RFC 3030. The
+       SMTP server is the only program that knows the difference
+       between mail received with BDAT or DATA. Both use the same
+       smtpd_data_restrictions and smtpd_end_of_data_restrictions,
+       both send one Milter DATA event per mail transaction, and
+       both send one DATA command ending in <CR><LF>.<CR><LF> to
+       an smtpd_proxy_filter. Files: global/ehlo_mask.h,
+       global/smtp_stream.c, global/smtp_stream.c, global/smtp_stream.h,
+       postscreen/postscreen_smtpd.c, smtpd/smtpd.c, smtpd/smtpd.h,
+       smtpd/smtpd_chat.c, smtpd/smtpd_chat.h, smtpd/smtpd_state.c.
index 3c429a651e0a20d14c299f13787df6dbd89e87c7..91f8f30dd50e437c5b2503fe96e83e960b5c965c 100644 (file)
@@ -25,6 +25,19 @@ more recent Eclipse Public License 2.0. Recipients can choose to take
 the software under the license of their choice. Those who are more
 comfortable with the IPL can continue with that license.
 
+Major changes with snapshot 20180805-nonprod
+=============================================
+
+Preliminary support for RFC 3030 CHUNKING (BDAT) without BINARYMIME.
+The Postfix SMTP server is the only program that knows the difference
+between mail that was received with BDAT or DATA. 
+
+In both cases, the Postfix SMTP server will use smtpd_data_restrictions
+and smtpd_end_of_data_restrictions, it will send one Milter DATA event
+per mail transaction, and it will send one DATA command ending in
+<CR><LF>.<CR><LF> to an smtpd_proxy_filter. There is no difference
+in the Postfix queue file content.
+
 Incompatble change with snapshot 20180701
 =========================================
 
index 141e8336f1be12cd06780354f9779a4e940f753c..e2800798f69b8d2f23cc0f658adbbb8435c09e1a 100644 (file)
@@ -50,6 +50,7 @@ SMTPD(8)                                                              SMTPD(8)
        <a href="http://tools.ietf.org/html/rfc2554">RFC 2554</a> (AUTH command)
        <a href="http://tools.ietf.org/html/rfc2821">RFC 2821</a> (SMTP protocol)
        <a href="http://tools.ietf.org/html/rfc2920">RFC 2920</a> (SMTP pipelining)
+       <a href="http://tools.ietf.org/html/rfc3030">RFC 3030</a> (CHUNKING without BINARYMIME)
        <a href="http://tools.ietf.org/html/rfc3207">RFC 3207</a> (STARTTLS command)
        <a href="http://tools.ietf.org/html/rfc3461">RFC 3461</a> (SMTP DSN extension)
        <a href="http://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced status codes)
index 945a6360b18abfa06ee99009ae6d7aea9ad16c73..704bb60e601ec7b8ff2cdbda660326fea297e959 100644 (file)
@@ -878,7 +878,7 @@ CCARGS="$CCARGS -DSNAPSHOT"
 
 # Non-production: needs thorough testing, or major changes are still
 # needed before the code stabilizes.
-#CCARGS="$CCARGS -DNONPROD"
+CCARGS="$CCARGS -DNONPROD"
 
 # Workaround: prepend Postfix include files before other include files.
 CCARGS="-I. -I../../include $CCARGS"
index 10db32d006f1e656aea2febf3d3a716d6ae42fb5..60caccb956fcf6415c5b91eee9e3276374bfec4a 100644 (file)
@@ -56,6 +56,7 @@ RFC 2034 (SMTP enhanced status codes)
 RFC 2554 (AUTH command)
 RFC 2821 (SMTP protocol)
 RFC 2920 (SMTP pipelining)
+RFC 3030 (CHUNKING without BINARYMIME)
 RFC 3207 (STARTTLS command)
 RFC 3461 (SMTP DSN extension)
 RFC 3463 (Enhanced status codes)
index 1671beb3ec0121bacbc02a93906e396852385576..7ebcb9c2ce85f0beffa8ac67600c186a198fb33a 100644 (file)
@@ -19,6 +19,7 @@
 /*     #define EHLO_MASK_ENHANCEDSTATUSCODES   (1<<10)
 /*     #define EHLO_MASK_DSN           (1<<11)
 /*     #define EHLO_MASK_SMTPUTF8      (1<<12)
+/*     #define EHLO_MASK_CHUNKING      (1<<13)
 /*     #define EHLO_MASK_SILENT        (1<<15)
 /*
 /*     int     ehlo_mask(keyword_list)
@@ -77,6 +78,7 @@ static const NAME_MASK ehlo_mask_table[] = {
     "ENHANCEDSTATUSCODES", EHLO_MASK_ENHANCEDSTATUSCODES,
     "DSN", EHLO_MASK_DSN,
     "EHLO_MASK_SMTPUTF8", EHLO_MASK_SMTPUTF8,
+    "CHUNKING", EHLO_MASK_CHUNKING,
     "SILENT-DISCARD", EHLO_MASK_SILENT,        /* XXX In-band signaling */
     0,
 };
index 3ef2a2d870d401ccef92bef66812bc13a0e840a2..ed0f7dc02f56b50817997c3bcc9d86aec138b8d3 100644 (file)
@@ -28,6 +28,7 @@
 #define EHLO_MASK_ENHANCEDSTATUSCODES  (1<<10)
 #define EHLO_MASK_DSN          (1<<11)
 #define EHLO_MASK_SMTPUTF8     (1<<12)
+#define EHLO_MASK_CHUNKING     (1<<13)
 #define EHLO_MASK_SILENT       (1<<15)
 
 extern int ehlo_mask(const char *);
index 89664e23191db77fb2c578a40803a59fefb05523..a654302230e843203013d4045f363fc6f362bede 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20180708"
+#define MAIL_RELEASE_DATE      "20180805"
 #define MAIL_VERSION_NUMBER    "3.4"
 
 #ifdef SNAPSHOT
index b9f1c506f80236dbd4598f63b17de3d50ee37f78..217be61695530c1c40093a41bfcf7ef23145a946 100644 (file)
 /*     ssize_t len;
 /*     VSTREAM *stream;
 /*
+/*     void    smtp_fread(vp, len, stream)
+/*     VSTRING *vp;
+/*     ssize_t len;
+/*     VSTREAM *stream;
+/*
 /*     void    smtp_fputc(ch, stream)
 /*     int     ch;
 /*     VSTREAM *stream;
 /*     VSTREAM *stream;
 /*     char    *format;
 /*     va_list ap;
+/* AUXILIARY API
+/*     int     smtp_get_noexcept(vp, stream, maxlen, flags)
+/*     VSTRING *vp;
+/*     VSTREAM *stream;
+/*     ssize_t maxlen;
+/*     int     flags;
 /* LEGACY API
 /*     void    smtp_timeout_setup(stream, timeout)
 /*     VSTREAM *stream;
 /*     Long strings are not broken. No CR LF is appended. The stream
 /*     is not flushed.
 /*
+/*     smtp_fread() appends the specified number of bytes from the
+/*     stream to the buffer. The result is not null-terminated.
+/*
 /*     smtp_fputc() writes one character to the named stream.
 /*     The stream is not flushed.
 /*
 /*     smtp_vprintf() is the machine underneath smtp_printf().
 /*
+/*     smtp_get_noexcept() implements the subset of smtp_get()
+/*     without timeouts and without making long jumps. Instead,
+/*     query the stream status with vstream_feof() etc.
+/*
 /*     smtp_timeout_setup() is a backwards-compatibility interface
 /*     for programs that don't require per-record deadline support.
 /* DIAGNOSTICS
@@ -303,6 +321,29 @@ int     smtp_fgetc(VSTREAM *stream)
 /* smtp_get - read one line from SMTP peer */
 
 int     smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
+{
+    int     last_char;
+
+    /*
+     * Do the I/O, protected against timeout.
+     */
+    smtp_timeout_reset(stream);
+    last_char = smtp_get_noexcept(vp, stream, bound, flags);
+
+    /*
+     * EOF is bad, whether or not it happens in the middle of a record. Don't
+     * allow data that was truncated because of EOF.
+     */
+    if (vstream_ftimeout(stream))
+       smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_get");
+    if (vstream_feof(stream) || vstream_ferror(stream))
+       smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_get");
+    return (last_char);
+}
+
+/* smtp_get_noexcept - read one line from SMTP peer, without exceptions */
+
+int     smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
 {
     int     last_char;
     int     next_char;
@@ -316,7 +357,6 @@ int     smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
      * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize
      * bare LF as record terminator.
      */
-    smtp_timeout_reset(stream);
     last_char = (bound == 0 ? vstring_get(vp, stream) :
                 vstring_get_bound(vp, stream, bound));
 
@@ -367,14 +407,6 @@ int     smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
               && next_char != '\n')
             /* void */ ;
 
-    /*
-     * EOF is bad, whether or not it happens in the middle of a record. Don't
-     * allow data that was truncated because of EOF.
-     */
-    if (vstream_ftimeout(stream))
-       smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_get");
-    if (vstream_feof(stream) || vstream_ferror(stream))
-       smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_get");
     return (last_char);
 }
 
@@ -427,6 +459,33 @@ void    smtp_fwrite(const char *cp, ssize_t todo, VSTREAM *stream)
        smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fwrite");
 }
 
+/* smtp_fread - read one buffer from SMTP peer */
+
+void    smtp_fread(VSTRING *vp, ssize_t todo, VSTREAM *stream)
+{
+    int     err;
+
+    if (todo <= 0)
+       msg_panic("smtp_fread: zero or negative todo %ld", (long) todo);
+
+    /*
+     * Do the I/O, protected against timeout.
+     */
+    smtp_timeout_reset(stream);
+    VSTRING_SPACE(vp, todo);
+    err = (vstream_fread(stream, vstring_end(vp), todo) != todo);
+    if (err == 0)
+       VSTRING_AT_OFFSET(vp, VSTRING_LEN(vp) + todo);
+
+    /*
+     * See if there was a problem.
+     */
+    if (vstream_ftimeout(stream))
+       smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fread");
+    if (err != 0)
+       smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fread");
+}
+
 /* smtp_fputc - write to SMTP peer */
 
 void    smtp_fputc(int ch, VSTREAM *stream)
index bdb143675ee49f00ed75933534586415be7de408..066eb5452b1fb29ed4e0ca9fa1a9b99d0af8d44e 100644 (file)
@@ -38,8 +38,10 @@ extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...);
 extern void smtp_flush(VSTREAM *);
 extern int smtp_fgetc(VSTREAM *);
 extern int smtp_get(VSTRING *, VSTREAM *, ssize_t, int);
+extern int smtp_get_noexcept(VSTRING *, VSTREAM *, ssize_t, int);
 extern void smtp_fputs(const char *, ssize_t len, VSTREAM *);
 extern void smtp_fwrite(const char *, ssize_t len, VSTREAM *);
+extern void smtp_fread(VSTRING *, ssize_t len, VSTREAM *);
 extern void smtp_fputc(int, VSTREAM *);
 
 extern void smtp_vprintf(VSTREAM *, const char *, va_list);
index 814eedc1c9a07fb1b179de3ddcd53128c5f4b214..152748f388be6e50dd0e1d76682c33a5ac4dcf88 100644 (file)
@@ -345,6 +345,8 @@ static void psc_smtpd_format_ehlo_reply(VSTRING *buf, int discard_mask
     /* Fix 20140708: announce SMTPUTF8. */
     if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0)
        PSC_EHLO_APPEND(saved_len, psc_temp, "250-SMTPUTF8\r\n");
+    if ((discard_mask & EHLO_MASK_CHUNKING) == 0)
+       PSC_EHLO_APPEND(saved_len, psc_temp, "250-CHUNKING\r\n");
     STR(psc_temp)[saved_len + 3] = ' ';
 }
 
index 5006e7449197770427ff0e98c80e282f32edec70..5d7fac0fffee1e740ce61084b40a5f6204061a9c 100644 (file)
@@ -46,6 +46,7 @@
 /*     RFC 2554 (AUTH command)
 /*     RFC 2821 (SMTP protocol)
 /*     RFC 2920 (SMTP pipelining)
+/*     RFC 3030 (CHUNKING without BINARYMIME)
 /*     RFC 3207 (STARTTLS command)
 /*     RFC 3461 (SMTP DSN extension)
 /*     RFC 3463 (Enhanced status codes)
@@ -1906,6 +1907,8 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
        EHLO_APPEND(state, "DSN");
     if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0)
        EHLO_APPEND(state, "SMTPUTF8");
+    if ((discard_mask & EHLO_MASK_CHUNKING) == 0)
+       EHLO_APPEND(state, "CHUNKING");
 
     /*
      * Send the reply.
@@ -2734,6 +2737,18 @@ static void mail_reset(SMTPD_STATE *state)
        state->milter_argv = 0;
        state->milter_argc = 0;
     }
+
+    /*
+     * BDAT.
+     */
+    state->bdat_state = SMTPD_BDAT_NONE;
+    if (state->bdat_get_stream) {
+       (void) vstream_fclose(state->bdat_get_stream);
+       state->bdat_get_stream = 0;
+    }
+    if (state->bdat_get_buffer)
+       VSTRING_RESET(state->bdat_get_buffer);
+    state->bdat_last_chunk_size = 0;
 }
 
 /* rcpt_cmd - process RCPT TO command */
@@ -2766,6 +2781,11 @@ static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
        smtpd_chat_reply(state, "503 5.5.1 Error: need MAIL command");
        return (-1);
     }
+    if (SMTPD_IN_BDAT_TRANSACTION(state)) {
+       state->error_mask |= MAIL_ERROR_PROTOCOL;
+       smtpd_chat_reply(state, "503 5.5.1 Error: RCPT in BDAT transaction");
+       return (-1);
+    }
     if (argc < 3
        || strcasecmp(argv[1].strval, "to:") != 0) {
        state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -3115,45 +3135,37 @@ static void comment_sanitize(VSTRING *comment_string)
     VSTRING_TERMINATE(comment_string);
 }
 
+static void common_pre_message(SMTPD_STATE *state,
+                 int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+                     int (*out_fprintf) (VSTREAM *, int, const char *,...),
+                                      VSTREAM *out_stream, int out_error);
+static void receive_data_message(SMTPD_STATE *state,
+                 int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+                     int (*out_fprintf) (VSTREAM *, int, const char *,...),
+                                        VSTREAM *out_stream, int out_error);
+static int common_post_message_handling(SMTPD_STATE *state);
+
 /* data_cmd - process DATA command */
 
 static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
 {
     SMTPD_PROXY *proxy;
     const char *err;
-    char   *start;
-    int     len;
-    int     curr_rec_type;
-    int     prev_rec_type;
-    int     first = 1;
-    VSTRING *why = 0;
-    int     saved_err;
     int     (*out_record) (VSTREAM *, int, const char *, ssize_t);
     int     (*out_fprintf) (VSTREAM *, int, const char *,...);
     VSTREAM *out_stream;
     int     out_error;
-    char  **cpp;
-    const CLEANUP_STAT_DETAIL *detail;
-    const char *rfc3848_sess;
-    const char *rfc3848_auth;
-    const char *with_protocol = (state->flags & SMTPD_FLAG_SMTPUTF8) ?
-    "UTF8SMTP" : state->protocol;
-
-#ifdef USE_TLS
-    VSTRING *peer_CN;
-    VSTRING *issuer_CN;
-
-#endif
-#ifdef USE_SASL_AUTH
-    VSTRING *username;
-
-#endif
 
     /*
      * Sanity checks. With ESMTP command pipelining the client can send DATA
      * before all recipients are rejected, so don't report that as a protocol
      * error.
      */
+    if (SMTPD_IN_BDAT_TRANSACTION(state)) {
+       state->error_mask |= MAIL_ERROR_PROTOCOL;
+       smtpd_chat_reply(state, "503 5.5.1 Error: DATA in BDAT transaction");
+       return (-1);
+    }
     if (state->rcpt_count == 0) {
        if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
            state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -3202,6 +3214,37 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
        out_fprintf = rec_fprintf;
        out_error = CLEANUP_STAT_WRITE;
     }
+    common_pre_message(state, out_record, out_fprintf, out_stream, out_error);
+    smtpd_chat_reply(state, "354 End data with <CR><LF>.<CR><LF>");
+    state->where = SMTPD_AFTER_DATA;
+    receive_data_message(state, out_record, out_fprintf, out_stream, out_error);
+    return common_post_message_handling(state);
+}
+
+/* common_pre_message - finish envelope and open message segment */
+
+static void common_pre_message(SMTPD_STATE *state,
+                 int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+                     int (*out_fprintf) (VSTREAM *, int, const char *,...),
+                                      VSTREAM *out_stream,
+                                      int out_error)
+{
+    SMTPD_PROXY *proxy = state->proxy;
+    char  **cpp;
+    const char *rfc3848_sess;
+    const char *rfc3848_auth;
+    const char *with_protocol = (state->flags & SMTPD_FLAG_SMTPUTF8) ?
+    "UTF8SMTP" : state->protocol;
+
+#ifdef USE_TLS
+    VSTRING *peer_CN;
+    VSTRING *issuer_CN;
+
+#endif
+#ifdef USE_SASL_AUTH
+    VSTRING *username;
+
+#endif
 
     /*
      * Flush out a first batch of access table actions that are delegated to
@@ -3321,8 +3364,22 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
                    "\t(envelope-from %s)", STR(state->buffer));
 #endif
     }
-    smtpd_chat_reply(state, "354 End data with <CR><LF>.<CR><LF>");
-    state->where = SMTPD_AFTER_DATA;
+}
+
+/* receive_data_message - finish envelope and open message segment */
+
+static void receive_data_message(SMTPD_STATE *state,
+                 int (*out_record) (VSTREAM *, int, const char *, ssize_t),
+                     int (*out_fprintf) (VSTREAM *, int, const char *,...),
+                                        VSTREAM *out_stream,
+                                        int out_error)
+{
+    SMTPD_PROXY *proxy = state->proxy;
+    char   *start;
+    int     len;
+    int     curr_rec_type;
+    int     prev_rec_type;
+    int     first = 1;
 
     /*
      * Copy the message content. If the cleanup process has a problem, keep
@@ -3370,6 +3427,18 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
        }
     }
     state->where = SMTPD_AFTER_DOT;
+}
+
+/* common_post_message_handling - commit message or report error */
+
+static int common_post_message_handling(SMTPD_STATE *state)
+{
+    SMTPD_PROXY *proxy = state->proxy;
+    const char *err;
+    VSTRING *why = 0;
+    int     saved_err;
+    const CLEANUP_STAT_DETAIL *detail;
+
     if (state->err == CLEANUP_STAT_OK
        && SMTPD_STAND_ALONE(state) == 0
        && (err = smtpd_check_eod(state)) != 0) {
@@ -3564,6 +3633,263 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
     return (saved_err);
 }
 
+/* skip_bdat - skip content and respond to BDAT error */
+
+static int skip_bdat(SMTPD_STATE *state, off_t skip_size,
+                            const char *format,...)
+{
+    va_list ap;
+    off_t   done;
+    off_t   len;
+
+    /*
+     * Read and discard content from the remote SMTP client. TODO: drop the
+     * connection in case of overload.
+     */
+    for (done = 0; done < skip_size; done += len) {
+       VSTRING_RESET(state->buffer);
+       if ((len = skip_size - done) > VSTREAM_BUFSIZE)
+           len = VSTREAM_BUFSIZE;
+       smtp_fread(state->buffer, len, state->client);
+    }
+
+    /*
+     * Send the response to the remote SMTP client.
+     */
+    va_start(ap, format);
+    vsmtpd_chat_reply(state, format, ap);
+    va_end(ap);
+
+    /*
+     * Drop subsequent BDAT payloads until BDAT LAST or RSET.
+     */
+    state->bdat_state = SMTPD_BDAT_ERROR;
+    return (-1);
+}
+
+/* bdat_cmd - process BDAT command */
+
+static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
+{
+    SMTPD_PROXY *proxy;
+    const char *err;
+    off_t   chunk_size;
+    off_t   done;
+    off_t   read_len;
+    char   *start;
+    int     len;
+    int     curr_rec_type;
+    int     (*out_record) (VSTREAM *, int, const char *, ssize_t);
+    int     (*out_fprintf) (VSTREAM *, int, const char *,...);
+    VSTREAM *out_stream;
+    int     out_error;
+
+    /*
+     * Hang up if the BDAT command is malformed. The next input would be raw
+     * message content and that would trigger lots of command errors.
+     */
+    if (argc < 2 || argc > 3 || !alldig(argv[1].strval)
+       || (chunk_size = off_cvt_string(argv[1].strval)) < 0
+       || (argc == 3 && strcasecmp(argv[2].strval, "LAST") != 0)) {
+       state->error_mask |= MAIL_ERROR_PROTOCOL;
+       msg_warn("%s: malformed BDAT command syntax from %s: %.100s",
+                state->queue_id ? state->queue_id : "NOQUEUE",
+                state->namaddr, printable(vstring_str(state->buffer), '?'));
+       smtpd_chat_reply(state, "421 5.5.4 Syntax: BDAT count [LAST]");
+       return (-1);
+    }
+
+    /*
+     * BDAT commands may be pipelined within a MAIL transaction. After a
+     * previous BDAT command failed, accept BDAT requests and skip BDAT
+     * payloads to maintain synchronization with the remote SMTP client.
+     */
+    if (state->bdat_state == SMTPD_BDAT_ERROR)
+       return skip_bdat(state, chunk_size,
+                        "551 5.0.0 Discarded %ld bytes after earlier error",
+                        (long) chunk_size);
+
+    /*
+     * Special handling for the first BDAT command in a MAIL transaction,
+     * treating it as a kind of "DATA" command in policy evaluation.
+     */
+    if (!SMTPD_IN_BDAT_TRANSACTION(state)) {
+
+       /*
+        * With ESMTP command pipelining a client may send BDAT before all
+        * recipients are rejected. That is not necessarily a good idea. but
+        * we should not treat such a BDAT command as a protocol error.
+        */
+       if (state->rcpt_count == 0) {
+           if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
+               state->error_mask |= MAIL_ERROR_PROTOCOL;
+               return skip_bdat(state, chunk_size,
+                                "503 5.5.1 Error: need RCPT command");
+           } else {
+               return skip_bdat(state, chunk_size,
+                                "554 5.5.1 Error: no valid recipients");
+           }
+       }
+       if (SMTPD_STAND_ALONE(state) == 0
+           && (err = smtpd_check_data(state)) != 0) {
+           return skip_bdat(state, chunk_size, "%s", err);
+       }
+       if (state->milters != 0
+           && (state->saved_flags & MILTER_SKIP_FLAGS) == 0
+           && (err = milter_data_event(state->milters)) != 0
+           && (err = check_milter_reply(state, err)) != 0) {
+           return skip_bdat(state, chunk_size, "%s", err);
+       }
+       proxy = state->proxy;
+       if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE,
+                                    SMTPD_CMD_DATA) != 0) {
+           return skip_bdat(state, chunk_size, "%s", STR(proxy->reply));
+       }
+    }
+    /* Block too large chunks. */
+    if (state->act_size > var_message_limit - chunk_size) {
+       state->error_mask |= MAIL_ERROR_POLICY;
+       msg_warn("%s: BDAT request from %s exceeds message size limit",
+                state->queue_id ? state->queue_id : "NOQUEUE",
+                state->namaddr);
+       return skip_bdat(state, chunk_size,
+                        "552 5.3.4 Chunk exceeds message size limit");
+    }
+    /* Block trickle attacks with too small successive chunks. */
+    if (SMTPD_IN_BDAT_TRANSACTION(state) && argc != 3
+       && state->bdat_last_chunk_size + chunk_size < 20) {
+       msg_warn("%s: multiple small BDAT requests from %s",
+                state->queue_id ? state->queue_id : "NOQUEUE",
+                state->namaddr);
+       return skip_bdat(state, chunk_size,
+                        "551 5.7.1 Too many small BDAT requests");
+    }
+    state->bdat_last_chunk_size = chunk_size;
+
+    /*
+     * One level of indirection to choose between normal or proxied
+     * operation. We want to avoid massive code duplication within tons of
+     * if-else clauses. TODO: store this in its own data structure, or in
+     * SMTPD_STATE.
+     */
+    proxy = state->proxy;
+    if (proxy) {
+       out_stream = proxy->stream;
+       out_record = proxy->rec_put;
+       out_fprintf = proxy->rec_fprintf;
+       out_error = CLEANUP_STAT_PROXY;
+    } else {
+       out_stream = state->cleanup;
+       out_record = rec_put;
+       out_fprintf = rec_fprintf;
+       out_error = CLEANUP_STAT_WRITE;
+    }
+    if (!SMTPD_IN_BDAT_TRANSACTION(state)) {
+       common_pre_message(state, out_record, out_fprintf,
+                          out_stream, out_error);
+       if (state->bdat_get_buffer == 0)
+           state->bdat_get_buffer = vstring_alloc(VSTREAM_BUFSIZE);
+       else
+           VSTRING_RESET(state->bdat_get_buffer);
+       state->bdat_prev_rec_type = 0;
+    }
+    if (state->bdat_get_stream != 0)
+       msg_panic("bdat_cmd: bdat_get_stream not null");
+
+    state->bdat_state = (argc == 3 ? SMTPD_BDAT_LAST : SMTPD_BDAT_OK);
+
+    /*
+     * Copy the message content. If the cleanup process has a problem, keep
+     * reading until the remote stops sending, then complain. Produce typed
+     * records from the SMTP stream so we can handle data that spans buffers.
+     */
+#define SWAP(type, a, b) do { \
+       type _c_; _c_ = (a); (a) = (b); (b) = _c_; \
+    } while (0)
+
+    /* Read the chunk one fragment at a time. */
+    for (done = 0; done < chunk_size; done += read_len) {
+       if ((read_len = chunk_size - done) > VSTREAM_BUFSIZE)
+           read_len = VSTREAM_BUFSIZE;
+       /* Kludge: append the chunk fragment to the last partial record. */
+       if (VSTRING_LEN(state->bdat_get_buffer) > 0)
+           SWAP(VSTRING *, state->bdat_get_buffer, state->buffer);
+       else
+           VSTRING_RESET(state->buffer);
+       /* Caution: this makes a long jump in case of EOF or timeout. */
+       smtp_fread(state->buffer, read_len, state->client);
+       state->bdat_get_stream = vstream_memopen(state->buffer, O_RDONLY);
+       for ( /* */ ; /* */ ; state->bdat_prev_rec_type = curr_rec_type) {
+           if (smtp_get_noexcept(state->bdat_get_buffer,
+                                 state->bdat_get_stream,
+                                 var_line_limit,
+                                 SMTP_GET_FLAG_NONE) == '\n') {
+               /* Stopped at end-of-line. */
+               curr_rec_type = REC_TYPE_NORM;
+           } else if (!vstream_feof(state->bdat_get_stream)) {
+               /* Stopped at var_line_limit. */
+               curr_rec_type = REC_TYPE_CONT;
+           } else if (state->bdat_state == SMTPD_BDAT_LAST
+                      && read_len == chunk_size - done) {
+               /* Final chunk does not end in end-of-line. Fake it. */
+               curr_rec_type = REC_TYPE_NORM;
+           } else {
+               /* Stopped at end of buffer, maybe in a partial record. */
+               break;
+           }
+           start = vstring_str(state->bdat_get_buffer);
+           len = VSTRING_LEN(state->bdat_get_buffer);
+           if (state->err == CLEANUP_STAT_OK) {
+               if (var_message_limit > 0
+                   && var_message_limit - state->act_size < len + 2) {
+                   state->err = CLEANUP_STAT_SIZE;
+                   msg_warn("%s: queue file size limit exceeded",
+                            state->queue_id ? state->queue_id : "NOQUEUE");
+               } else {
+                   state->act_size += len + 2;
+                   if (*start == '.' && proxy != 0
+                       && state->bdat_prev_rec_type != REC_TYPE_CONT)
+                       /* out_fprintf would break text containing nulls. */
+                       vstring_prepend(state->bdat_get_buffer, ".", 1);
+                   if (out_record(out_stream, curr_rec_type,
+                                  vstring_str(state->bdat_get_buffer),
+                                  VSTRING_LEN(state->bdat_get_buffer)) < 0)
+                       state->err = out_error;
+               }
+           }
+       }
+       vstream_fclose(state->bdat_get_stream);
+       state->bdat_get_stream = 0;
+    }
+
+    /*
+     * Special handling for BDAT LAST (successful or unsuccessful).
+     */
+    if (state->bdat_state == SMTPD_BDAT_LAST) {
+       return common_post_message_handling(state);
+    }
+
+    /*
+     * Unsuccessful non-final BDAT command. common_post_message_handling()
+     * resets all MAIL transaction state including BDAT state. To avoid
+     * useless error messages due to pipelined BDAT commands, set the
+     * SMTPD_BDAT_ERROR state to accept BDAT commands and skip BDAT payloads.
+     */
+    else if (state->err != CLEANUP_STAT_OK) {
+       (void) common_post_message_handling(state);
+       state->bdat_state = SMTPD_BDAT_ERROR;
+       return (-1);
+    }
+
+    /*
+     * Successful non-final BDAT command.
+     */
+    else {
+       smtpd_chat_reply(state, "250 2.0.0 Ok: %ld bytes", (long) chunk_size);
+       return (0);
+    }
+}
+
 /* rset_cmd - process RSET */
 
 static int rset_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
@@ -4836,6 +5162,13 @@ typedef struct SMTPD_CMD {
     int     total_count;
 } SMTPD_CMD;
 
+ /*
+  * Per RFC 2920: "In particular, the commands RSET, MAIL FROM, SEND FROM,
+  * SOML FROM, SAML FROM, and RCPT TO can all appear anywhere in a pipelined
+  * command group. The EHLO, DATA, VRFY, EXPN, TURN, QUIT, and NOOP commands
+  * can only appear as the last command in a group". RFC 3030 allows BDAT
+  * commands to be pipelined as well.
+  */
 #define SMTPD_CMD_FLAG_LIMIT   (1<<0)  /* limit usage */
 #define SMTPD_CMD_FLAG_PRE_TLS (1<<1)  /* allow before STARTTLS */
 #define SMTPD_CMD_FLAG_LAST    (1<<2)  /* last in PIPELINING command group */
@@ -4858,6 +5191,7 @@ static SMTPD_CMD smtpd_cmd_table[] = {
     {SMTPD_CMD_MAIL, mail_cmd,},
     {SMTPD_CMD_RCPT, rcpt_cmd,},
     {SMTPD_CMD_DATA, data_cmd, SMTPD_CMD_FLAG_LAST,},
+    {SMTPD_CMD_BDAT, bdat_cmd,},
     {SMTPD_CMD_RSET, rset_cmd, SMTPD_CMD_FLAG_LIMIT,},
     {SMTPD_CMD_NOOP, noop_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
     {SMTPD_CMD_VRFY, vrfy_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_LAST,},
index 4facfb0803d9c0ebf233ffb2eb14d9c5908e2fb0..e12a5123efdbe07ca498194686fd58a4c7027f00 100644 (file)
@@ -189,6 +189,19 @@ typedef struct {
      */
     VSTRING *ehlo_buf;
     ARGV   *ehlo_argv;
+
+    /*
+     * BDAT transaction state.
+     */
+#define SMTPD_BDAT_NONE                0       /* not in BDAT transaction */
+#define SMTPD_BDAT_OK          1       /* in BDAT, accepting chunks */
+#define SMTPD_BDAT_LAST                2       /* in BDAT, last chunk */
+#define SMTPD_BDAT_ERROR       3       /* in BDAT, dropping chunks */
+    int     bdat_state;                        /* see above */
+    off_t bdat_last_chunk_size;                /* trickle defense */
+    VSTREAM *bdat_get_stream;          /* memory stream from BDAT chunk */
+    VSTRING *bdat_get_buffer;          /* read from memory stream */
+    int bdat_prev_rec_type;
 } SMTPD_STATE;
 
 #define SMTPD_FLAG_HANGUP         (1<<0)       /* 421/521 disconnect */
@@ -198,7 +211,7 @@ typedef struct {
 
  /* Security: don't reset SMTPD_FLAG_AUTH_USED. */
 #define SMTPD_MASK_MAIL_KEEP \
-           ~(SMTPD_FLAG_SMTPUTF8)              /* Fix 20140706 */
+           ~(SMTPD_FLAG_SMTPUTF8)      /* Fix 20140706 */
 
 #define SMTPD_STATE_XFORWARD_INIT  (1<<0)      /* xforward preset done */
 #define SMTPD_STATE_XFORWARD_NAME  (1<<1)      /* client name received */
@@ -236,6 +249,7 @@ extern void smtpd_state_reset(SMTPD_STATE *);
 #define SMTPD_CMD_MAIL         "MAIL"
 #define SMTPD_CMD_RCPT         "RCPT"
 #define SMTPD_CMD_DATA         "DATA"
+#define SMTPD_CMD_BDAT         "BDAT"
 #define SMTPD_CMD_EOD          SMTPD_AFTER_DOT /* XXX Was: END-OF-DATA */
 #define SMTPD_CMD_RSET         "RSET"
 #define SMTPD_CMD_NOOP         "NOOP"
@@ -321,6 +335,12 @@ extern void smtpd_state_reset(SMTPD_STATE *);
   */
 #define SMTPD_IN_MAIL_TRANSACTION(state) ((state)->sender != 0)
 
+ /*
+  * Are we in a BDAT transaction?
+  */
+#define SMTPD_IN_BDAT_TRANSACTION(state) \
+       ((state)->bdat_state != SMTPD_BDAT_NONE)
+
  /*
   * SMTPD peer information lookup.
   */
index 8ba46b01cf874102d34fe3639c1c1d48f7593121..14bd94f575875dadae14c6ee6c4194842119d2d0 100644 (file)
@@ -148,6 +148,16 @@ void    smtpd_chat_query(SMTPD_STATE *state)
 void    smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)
 {
     va_list ap;
+
+    va_start(ap, format);
+    vsmtpd_chat_reply(state, format, ap);
+    va_end(ap);
+}
+
+/* vsmtpd_chat_reply - format, send and record an SMTP response */
+
+void    vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap)
+{
     int     delay = 0;
     char   *cp;
     char   *next;
@@ -160,9 +170,7 @@ void    smtpd_chat_reply(SMTPD_STATE *state, const char *format,...)
     if (state->error_count >= var_smtpd_soft_erlim)
        sleep(delay = var_smtpd_err_sleep);
 
-    va_start(ap, format);
     vstring_vsprintf(state->buffer, format, ap);
-    va_end(ap);
 
     if (*var_smtpd_rej_footer
        && (*(cp = STR(state->buffer)) == '4' || *cp == '5'))
index 6ac49ac0aa19e9d615c75ad53822e64a6f96de64..5e9982ad41d496d4d4359632ad195953cae4a0e4 100644 (file)
@@ -15,6 +15,7 @@
 extern void smtpd_chat_reset(SMTPD_STATE *);
 extern void smtpd_chat_query(SMTPD_STATE *);
 extern void PRINTFLIKE(2, 3) smtpd_chat_reply(SMTPD_STATE *, const char *,...);
+extern void vsmtpd_chat_reply(SMTPD_STATE *, const char *, va_list);
 extern void smtpd_chat_notify(SMTPD_STATE *);
 
 /* LICENSE
index 87e62fa4410fec1d455a4d820426628a0551c788..7e04cf23d6c2ebb191d7bfe63b491925be7e2d9c 100644 (file)
@@ -150,7 +150,6 @@ void    smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream,
     state->tls_context = 0;
 #endif
 
-
     /*
      * Minimal initialization to support external authentication (e.g.,
      * XCLIENT) without having to enable SASL in main.cf.
@@ -183,6 +182,14 @@ void    smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream,
 
     state->ehlo_argv = 0;
     state->ehlo_buf = 0;
+
+    /*
+     * BDAT.
+     */
+    state->bdat_state = SMTPD_BDAT_NONE;
+    state->bdat_get_stream = 0;
+    state->bdat_get_buffer = 0;
+    state->bdat_last_chunk_size = 0;
 }
 
 /* smtpd_state_reset - cleanup after disconnect */
@@ -231,4 +238,12 @@ void    smtpd_state_reset(SMTPD_STATE *state)
     if (state->tlsproxy)                       /* still open after longjmp */
        vstream_fclose(state->tlsproxy);
 #endif
+
+    /*
+     * BDAT.
+     */
+    if (state->bdat_get_stream)
+       (void) vstream_fclose(state->bdat_get_stream);
+    if (state->bdat_get_buffer)
+       vstring_free(state->bdat_get_buffer);
 }
index 6f4ef4557954848a63db653954b638ce6ef6f036..ecc6f197b223830db5273ba86ecf8283a76494e7 100644 (file)
@@ -169,7 +169,8 @@ int     vbuf_unget(VBUF *bp, int ch)
 
 int     vbuf_get(VBUF *bp)
 {
-    return (bp->get_ready(bp) ? VBUF_EOF : VBUF_GET(bp));
+    return (bp->get_ready(bp) ?
+       ((bp->flags |= VBUF_FLAG_EOF), VBUF_EOF) : VBUF_GET(bp));
 }
 
 /* vbuf_put - handle write buffer full condition */
index b85d51159e93e0aede73623faa21029b2ec26c87..d4191ab4330671400a4b1c14da1f7ab9d3b2c175 100644 (file)
 /*     The deadline feature is activated.
 /* .IP VSTREAM_FLAG_DOUBLE
 /*     The double-buffering feature is activated.
+/* .IP VSTREAM_FLAG_MEMORY
+/*     The stream is connected to a VSTRING buffer.
 /* DIAGNOSTICS
 /*     Panics: interface violations. Fatal errors: out of memory.
 /* SEE ALSO