]*rity_options\b;$&;g;
s;\bsmtpd_sender_login_maps\b;$&;g;
s;\bsmtpd_sender_restrictions\b;$&;g;
diff --git a/postfix/proto/SMTPD_POLICY_README.html b/postfix/proto/SMTPD_POLICY_README.html
index 79c876413..ebda427ba 100644
--- a/postfix/proto/SMTPD_POLICY_README.html
+++ b/postfix/proto/SMTPD_POLICY_README.html
@@ -223,6 +223,8 @@ server_port=54321
DATA, END-OF-MESSAGE, VRFY or ETRN; these are the SMTP protocol
states where
the Postfix SMTP server makes an OK/REJECT/HOLD/etc. decision.
+ The DATA protocol state also applies to email that is received
+ with BDAT commands (Postfix 3.4 and later).
diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto
index 271c701e3..7e169b6b2 100644
--- a/postfix/proto/postconf.proto
+++ b/postfix/proto/postconf.proto
@@ -10779,6 +10779,22 @@ selected with smtpd_sasl_type and smtpd_sasl_path.
This feature is available in Postfix 2.11 and later. Prior
versions behave as if "smtp" is specified.
+%PARAM smtpd_sasl_response_limit 12288
+
+ The maximum length of a SASL client's response to a server challenge.
+When the client's "initial response" is longer than the normal limit for
+SMTP commands, the client must omit its initial response, and wait for an
+empty server challenge; it can then send what would have been its "initial
+response" as a response to the empty server challenge. RFC4954 requires the
+server to accept client responses up to at least 12288 octets of
+base64-encoded text. The default value is therefore also the minimum value
+accepted for this parameter.
+
+ This feature is available in Postfix 3.4 and later. Prior versions use
+"line_length_limit", which may need to be raised to accomodate larger client
+responses, as may be needed with GSSAPI authenticaiton of Windows AD users
+who are members of many groups.
+
%PARAM cyrus_sasl_config_path
Search path for Cyrus SASL application configuration files,
diff --git a/postfix/src/global/dict_mysql.c b/postfix/src/global/dict_mysql.c
index 77adb42a4..d90bc090d 100644
--- a/postfix/src/global/dict_mysql.c
+++ b/postfix/src/global/dict_mysql.c
@@ -325,6 +325,19 @@ static const char *dict_mysql_lookup(DICT *dict, const char *name)
dict->error = 0;
+ /*
+ * Don't frustrate future attempts to make Postfix UTF-8 transparent.
+ */
+#ifdef SNAPSHOT
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
+ && !valid_utf8_string(name, strlen(name))) {
+ if (msg_verbose)
+ msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
+ myname, dict_mysql->parser->name, name);
+ return (0);
+ }
+#endif
+
/*
* Optionally fold the key.
*/
diff --git a/postfix/src/global/dict_pgsql.c b/postfix/src/global/dict_pgsql.c
index 0ffc75586..8eac25606 100644
--- a/postfix/src/global/dict_pgsql.c
+++ b/postfix/src/global/dict_pgsql.c
@@ -190,7 +190,7 @@ typedef struct {
char *hostname;
char *name;
char *port;
- unsigned type; /* TYPEUNIX | TYPEINET | TYPECONNSTRING*/
+ unsigned type; /* TYPEUNIX | TYPEINET | TYPECONNSTRING */
unsigned stat; /* STATUNTRIED | STATFAIL | STATCUR */
time_t ts; /* used for attempting reconnection */
} HOST;
@@ -345,6 +345,19 @@ static const char *dict_pgsql_lookup(DICT *dict, const char *name)
dict->error = 0;
+ /*
+ * Don't frustrate future attempts to make Postfix UTF-8 transparent.
+ */
+#ifdef SNAPSHOT
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
+ && !valid_utf8_string(name, strlen(name))) {
+ if (msg_verbose)
+ msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
+ myname, dict_pgsql->parser->name, name);
+ return (0);
+ }
+#endif
+
/*
* Optionally fold the key.
*/
@@ -643,6 +656,18 @@ static void plpgsql_connect_single(HOST *host, char *dbname, char *username, cha
msg_info("dict_pgsql: successful connection to host %s",
host->hostname);
+ /*
+ * The only legitimate encodings for Internet mail are ASCII and UTF-8.
+ */
+#ifdef SNAPSHOT
+ if (PQsetClientEncoding(host->db, "UTF8") != 0) {
+ msg_warn("dict_pgsql: cannot set the encoding to UTF8, skipping %s",
+ host->hostname);
+ plpgsql_down_host(host);
+ return;
+ }
+#else
+
/*
* XXX Postfix does not send multi-byte characters. The following piece
* of code is an explicit statement of this fact, and the database server
@@ -654,6 +679,7 @@ static void plpgsql_connect_single(HOST *host, char *dbname, char *username, cha
plpgsql_down_host(host);
return;
}
+#endif
/* Success. */
host->stat = STATACTIVE;
}
diff --git a/postfix/src/global/ehlo_mask.c b/postfix/src/global/ehlo_mask.c
index 1671beb3e..7ebcb9c2c 100644
--- a/postfix/src/global/ehlo_mask.c
+++ b/postfix/src/global/ehlo_mask.c
@@ -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,
};
diff --git a/postfix/src/global/ehlo_mask.h b/postfix/src/global/ehlo_mask.h
index 3ef2a2d87..ed0f7dc02 100644
--- a/postfix/src/global/ehlo_mask.h
+++ b/postfix/src/global/ehlo_mask.h
@@ -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 *);
diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h
index 5b92c1c68..8a4ae583e 100644
--- a/postfix/src/global/mail_params.h
+++ b/postfix/src/global/mail_params.h
@@ -1664,6 +1664,19 @@ extern char *var_smtpd_snd_auth_maps;
#define REJECT_UNAUTH_SENDER_LOGIN_MISMATCH \
"reject_unauthenticated_sender_login_mismatch"
+ /*
+ * https://tools.ietf.org/html/rfc4954#page-5
+ *
+ * (At the time of writing of this document, 12288 octets is considered to be a
+ * sufficient line length limit for handling of deployed authentication
+ * mechanisms.)
+ *
+ * The default value is also the minimum permissible value for this parameter.
+ */
+#define VAR_SMTPD_SASL_RESP_LIMIT "smtpd_sasl_response_limit"
+#define DEF_SMTPD_SASL_RESP_LIMIT 12288
+extern int var_smtpd_sasl_resp_limit;
+
/*
* SASL authentication support, SMTP client side.
*/
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index 020e9bc22..4821e002f 100644
--- a/postfix/src/global/mail_version.h
+++ b/postfix/src/global/mail_version.h
@@ -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 "20180823"
+#define MAIL_RELEASE_DATE "20180826"
#define MAIL_VERSION_NUMBER "3.4"
#ifdef SNAPSHOT
diff --git a/postfix/src/global/smtp_stream.c b/postfix/src/global/smtp_stream.c
index b9f1c506f..367dcb1d4 100644
--- a/postfix/src/global/smtp_stream.c
+++ b/postfix/src/global/smtp_stream.c
@@ -37,6 +37,11 @@
/* 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;
@@ -45,6 +50,12 @@
/* 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;
@@ -82,10 +93,15 @@
/* and protects the program against running out of memory.
/* Specify a zero bound to turn off bounds checking.
/* The result is the last character read, or VSTREAM_EOF.
-/* The \fIflags\fR argument is either SMTP_GET_FLAG_NONE (no
-/* special processing) or SMTP_GET_FLAG_SKIP (skip over input
-/* in excess of \fImaxlen\fR). Either way, a result value of
-/* '\n' means that the input did not exceed \fImaxlen\fR.
+/* The \fIflags\fR argument is zero or more of:
+/* .RS
+/* .IP SMTP_GET_FLAG_SKIP
+/* Skip over input in excess of \fImaxlen\fR). Either way, a result
+/* value of '\n' means that the input did not exceed \fImaxlen\fR.
+/* .IP SMTP_GET_FLAG_APPEND
+/* Append content to the buffer instead of overwriting it.
+/* .RE
+/* Specify SMTP_GET_FLAG_NONE for no special processing.
/*
/* smtp_fputs() writes its string argument to the named stream.
/* Long strings are not broken. Each string is followed by a
@@ -95,11 +111,18 @@
/* 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 +326,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,9 +362,13 @@ 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));
+ last_char = (bound == 0 ?
+ vstring_get_flags(vp, stream,
+ (flags & SMTP_GET_FLAG_APPEND) ?
+ VSTRING_GET_FLAG_APPEND : 0) :
+ vstring_get_flags_bound(vp, stream,
+ (flags & SMTP_GET_FLAG_APPEND) ?
+ VSTRING_GET_FLAG_APPEND : 0, bound));
switch (last_char) {
@@ -367,14 +417,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 +469,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)
diff --git a/postfix/src/global/smtp_stream.h b/postfix/src/global/smtp_stream.h
index bdb143675..cd2bfce69 100644
--- a/postfix/src/global/smtp_stream.h
+++ b/postfix/src/global/smtp_stream.h
@@ -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);
@@ -49,6 +51,7 @@ extern void smtp_vprintf(VSTREAM *, const char *, va_list);
#define SMTP_GET_FLAG_NONE 0
#define SMTP_GET_FLAG_SKIP (1<<0) /* skip over excess input */
+#define SMTP_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */
/* LICENSE
/* .ad
diff --git a/postfix/src/postscreen/postscreen.c b/postfix/src/postscreen/postscreen.c
index 4062a9b2d..af6bff303 100644
--- a/postfix/src/postscreen/postscreen.c
+++ b/postfix/src/postscreen/postscreen.c
@@ -61,6 +61,7 @@
/* RFC 2034 (SMTP Enhanced Status Codes)
/* RFC 2821 (SMTP protocol)
/* Not: RFC 2920 (SMTP Pipelining)
+/* RFC 3030 (CHUNKING without BINARYMIME)
/* RFC 3207 (STARTTLS command)
/* RFC 3461 (SMTP DSN Extension)
/* RFC 3463 (Enhanced Status Codes)
diff --git a/postfix/src/postscreen/postscreen_smtpd.c b/postfix/src/postscreen/postscreen_smtpd.c
index ee3d12b1a..50148917a 100644
--- a/postfix/src/postscreen/postscreen_smtpd.c
+++ b/postfix/src/postscreen/postscreen_smtpd.c
@@ -267,13 +267,13 @@ static DICT *psc_cmd_filter;
PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
(event), (void *) (state)); \
PSC_DROP_SESSION_STATE((state), (reply)); \
- } while (0);
+ } while (0)
#define PSC_CLEAR_EVENT_HANGUP(state, event) do { \
PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \
(event), (void *) (state)); \
psc_hangup_event(state); \
- } while (0);
+ } while (0)
/* psc_helo_cmd - record HELO and respond */
@@ -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] = ' ';
}
@@ -581,27 +583,61 @@ static int psc_rcpt_cmd(PSC_STATE *state, char *args)
static int psc_data_cmd(PSC_STATE *state, char *args)
{
+ const char myname[] = "psc_data_cmd";
/*
- * smtpd(8) incompatibility: we reject all requests.
+ * smtpd(8) incompatibility: postscreen(8) drops the connection, instead
+ * of waiting for the next command. Justification: postscreen(8) should
+ * never see DATA from a legitimate client, because 1) the server rejects
+ * every recipient, and 2) the server does not announce PIPELINING.
*/
if (PSC_SMTPD_NEXT_TOKEN(args) != 0)
- return (PSC_SEND_REPLY(state,
- "501 5.5.4 Syntax: DATA\r\n"));
- if (state->sender == 0)
- return (PSC_SEND_REPLY(state,
- "503 5.5.1 Error: need RCPT command\r\n"));
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "501 5.5.4 Syntax: DATA\r\n");
+ else if (state->sender == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "503 5.5.1 Error: need RCPT command\r\n");
+ else
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: no valid recipients\r\n");
+ /* Caution: state is now a dangling pointer. */
+ return (0);
+}
+
+/* psc_bdat_cmd - respond to BDAT and disconnect */
+
+static int psc_bdat_cmd(PSC_STATE *state, char *args)
+{
+ const char *myname = "psc_bdat_cmd";
/*
- * We really would like to hang up the connection as early as possible,
- * so that we dont't have to deal with broken zombies that fall silent at
- * the first reject response. For now we rely on stress-dependent command
- * read timeouts.
- *
- * If we proceed into the data phase, enforce over-all DATA time limit.
+ * smtpd(8) incompatibility: postscreen(8) drops the connection, instead
+ * of reading the entire BDAT chunk and staying in sync with the client.
+ * Justification: postscreen(8) should never see BDAT from a legitimate
+ * client, because 1) the server rejects every recipient, and 2) the
+ * server does not announce PIPELINING.
*/
- return (PSC_SEND_REPLY(state,
- "554 5.5.1 Error: no valid recipients\r\n"));
+ if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "502 5.5.1 Error: command not implemented\r\n");
+ else if (PSC_SMTPD_NEXT_TOKEN(args) == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "501 5.5.4 Syntax: BDAT count [LAST]\r\n");
+ else if (state->sender == 0)
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: need RCPT command\r\n");
+ else
+ PSC_CLEAR_EVENT_DROP_SESSION_STATE(state,
+ psc_smtpd_time_event,
+ "554 5.5.1 Error: no valid recipients\r\n");
+ /* Caution: state is now a dangling pointer. */
+ return (0);
}
/* psc_rset_cmd - reset, send 250 OK */
@@ -711,8 +747,9 @@ static const PSC_SMTPD_COMMAND command_table[] = {
"AUTH", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE,
"MAIL", psc_mail_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
"RCPT", psc_rcpt_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
- "DATA", psc_data_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
+ "DATA", psc_data_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY,
/* ".", psc_dot_cmd, PSC_SMTPD_CMD_FLAG_NONE, */
+ "BDAT", psc_bdat_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY,
"RSET", psc_rset_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
"NOOP", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS,
"VRFY", psc_vrfy_cmd, PSC_SMTPD_CMD_FLAG_ENABLE,
diff --git a/postfix/src/smtp/smtp_sasl_glue.c b/postfix/src/smtp/smtp_sasl_glue.c
index d2c1c3c59..f86bcd9e2 100644
--- a/postfix/src/smtp/smtp_sasl_glue.c
+++ b/postfix/src/smtp/smtp_sasl_glue.c
@@ -358,22 +358,36 @@ int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
session->namaddr, STR(session->sasl_reply));
return (-1);
}
-
- /*
+ /*-
* Send the AUTH command and the optional initial client response.
- * sasl_encode64() produces four bytes for each complete or incomplete
- * triple of input bytes. Allocate an extra byte for string termination.
+ *
+ * https://tools.ietf.org/html/rfc4954#page-4
+ * Note that the AUTH command is still subject to the line length
+ * limitations defined in [SMTP]. If use of the initial response argument
+ * would cause the AUTH command to exceed this length, the client MUST NOT
+ * use the initial response parameter...
+ *
+ * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4
+ * The maximum total length of a command line including the command word
+ * and the is 512 octets.
+ *
+ * Defer the initial response if the resulting command exceeds the limit.
*/
- if (LEN(session->sasl_reply) > 0) {
+ if (LEN(session->sasl_reply) > 0
+ && strlen(mechanism) + LEN(session->sasl_reply) + 4 <= 512) {
smtp_chat_cmd(session, "AUTH %s %s", mechanism,
STR(session->sasl_reply));
+ VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */
} else {
smtp_chat_cmd(session, "AUTH %s", mechanism);
}
/*
* Step through the authentication protocol until the server tells us
- * that we are done.
+ * that we are done. If session->sasl_reply is non-empty we have a
+ * deferred initial reply and expect an empty initial challenge from the
+ * server. If the server's initial challenge is non-empty we have a SASL
+ * protocol violation with both sides wanting to go first.
*/
while ((resp = smtp_chat_resp(session))->code / 100 == 3) {
@@ -392,21 +406,39 @@ int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
*/
line = resp->str;
(void) mystrtok(&line, "- \t\n"); /* skip over result code */
- result = xsasl_client_next(session->sasl_client, line,
- session->sasl_reply);
- if (result != XSASL_AUTH_OK) {
- dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */
+
+ if (LEN(session->sasl_reply) > 0) {
+
+ /*
+ * Deferred initial response, the server challenge must be empty.
+ * Cleared after actual transmission to the server.
+ */
+ if (*line) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION,
+ DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error",
+ "SASL authentication failed; non-empty initial "
+ "%s challenge from server %s: %s", mechanism,
+ session->namaddr, STR(session->sasl_reply));
+ return (-1);
+ }
+ } else {
+ result = xsasl_client_next(session->sasl_client, line,
+ session->sasl_reply);
+ if (result != XSASL_AUTH_OK) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */
DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply),
- "SASL authentication failed; "
- "cannot authenticate to server %s: %s",
- session->namaddr, STR(session->sasl_reply));
- return (-1); /* Fix 200512 */
+ "SASL authentication failed; "
+ "cannot authenticate to server %s: %s",
+ session->namaddr, STR(session->sasl_reply));
+ return (-1); /* Fix 200512 */
+ }
}
/*
* Send a client response.
*/
smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
+ VSTRING_RESET(session->sasl_reply); /* clear initial reply */
}
/*
diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in
index ac3d38f1a..14540d370 100644
--- a/postfix/src/smtpd/Makefile.in
+++ b/postfix/src/smtpd/Makefile.in
@@ -264,6 +264,7 @@ smtpd_chat.o: ../../include/argv.h
smtpd_chat.o: ../../include/attr.h
smtpd_chat.o: ../../include/check_arg.h
smtpd_chat.o: ../../include/cleanup_user.h
+smtpd_chat.o: ../../include/dict.h
smtpd_chat.o: ../../include/dns.h
smtpd_chat.o: ../../include/htable.h
smtpd_chat.o: ../../include/int_filt.h
@@ -280,6 +281,7 @@ smtpd_chat.o: ../../include/maps.h
smtpd_chat.o: ../../include/milter.h
smtpd_chat.o: ../../include/msg.h
smtpd_chat.o: ../../include/myaddrinfo.h
+smtpd_chat.o: ../../include/myflock.h
smtpd_chat.o: ../../include/mymalloc.h
smtpd_chat.o: ../../include/name_code.h
smtpd_chat.o: ../../include/name_mask.h
diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c
index 99284b3be..009f77f5d 100644
--- a/postfix/src/smtpd/smtpd.c
+++ b/postfix/src/smtpd/smtpd.c
@@ -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)
@@ -338,6 +339,10 @@
/* .IP "\fBsmtpd_sasl_service (smtp)\fR"
/* The service name that is passed to the SASL plug-in that is
/* selected with \fBsmtpd_sasl_type\fR and \fBsmtpd_sasl_path\fR.
+/* .PP
+/* Available in Postfix version 3.4 and later:
+/* .IP "\fBsmtpd_sasl_response_limit (12288)\fR"
+/* The maximum length of a SASL client's response to a server challenge.
/* STARTTLS SUPPORT CONTROLS
/* .ad
/* .fi
@@ -1283,6 +1288,7 @@ char *var_smtpd_sasl_path;
char *var_smtpd_sasl_service;
char *var_cyrus_conf_path;
char *var_smtpd_sasl_realm;
+int var_smtpd_sasl_resp_limit;
char *var_smtpd_sasl_exceptions_networks;
char *var_smtpd_sasl_type;
char *var_filter_xport;
@@ -1912,6 +1918,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.
@@ -2399,6 +2407,12 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
smtpd_chat_reply(state, "503 5.5.1 Error: nested MAIL command");
return (-1);
}
+ /* Don't accept MAIL after out-of-order BDAT. */
+ if (SMTPD_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: MAIL after BDAT");
+ return (-1);
+ }
if (argc < 3
|| strcasecmp(argv[1].strval, "from:") != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -2740,6 +2754,17 @@ static void mail_reset(SMTPD_STATE *state)
state->milter_argv = 0;
state->milter_argc = 0;
}
+
+ /*
+ * BDAT.
+ */
+ state->bdat_state = SMTPD_BDAT_STAT_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);
}
/* rcpt_cmd - process RCPT TO command */
@@ -2772,6 +2797,12 @@ 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);
}
+ /* Don't accept RCPT after BDAT. */
+ if (SMTPD_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: RCPT after BDAT");
+ return (-1);
+ }
if (argc < 3
|| strcasecmp(argv[1].strval, "to:") != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -3121,45 +3152,37 @@ static void comment_sanitize(VSTRING *comment_string)
VSTRING_TERMINATE(comment_string);
}
+static void common_pre_message_handling(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_PROCESSING_BDAT(state)) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "503 5.5.1 Error: DATA after BDAT");
+ return (-1);
+ }
if (state->rcpt_count == 0) {
if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
@@ -3208,6 +3231,38 @@ 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_handling(state, out_record, out_fprintf,
+ out_stream, out_error);
+ smtpd_chat_reply(state, "354 End data with .");
+ 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_handling - finish envelope and open message segment */
+
+static void common_pre_message_handling(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
@@ -3327,8 +3382,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 .");
- 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
@@ -3375,7 +3444,19 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
}
}
}
- state->where = SMTPD_AFTER_DOT;
+ state->where = SMTPD_AFTER_EOM;
+}
+
+/* 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) {
@@ -3492,6 +3573,10 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
state->junk_cmds = 0;
if (proxy)
smtpd_chat_reply(state, "%s", STR(proxy->reply));
+ else if (SMTPD_PROCESSING_BDAT(state))
+ smtpd_chat_reply(state,
+ "250 2.0.0 Ok: %ld bytes queued as %s",
+ (long) state->act_size, state->queue_id);
else
smtpd_chat_reply(state,
"250 2.0.0 Ok: queued as %s", state->queue_id);
@@ -3570,6 +3655,301 @@ 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 chunk_size,
+ bool final_chunk, 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 < chunk_size; done += len) {
+ VSTRING_RESET(state->buffer);
+ if ((len = chunk_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);
+
+ /*
+ * Reset state, or drop subsequent BDAT payloads until BDAT LAST or RSET.
+ */
+ if (final_chunk)
+ mail_reset(state);
+ else
+ state->bdat_state = SMTPD_BDAT_STAT_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;
+ bool final_chunk;
+ 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 disabled. The next input would be raw
+ * message content and that would trigger lots of command errors.
+ */
+ if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) {
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state, "521 5.5.1 Error: command not implemented");
+ return (-1);
+ }
+
+ /*
+ * 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
+ || ((final_chunk = (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, "521 5.5.4 Syntax: BDAT count [LAST]");
+ return (-1);
+ }
+
+ /*
+ * Block abuse involving empty chunks (alternatively, we could count
+ * "BDAT 0" as a "NOOP", but then we would have to refactor the code that
+ * enforces the junk command limit). Clients that send a message as a
+ * sequence of "BDAT 1" should not be a problem: the Postfix BDAT
+ * implementation should be efficient enough to handle that.
+ */
+ if (chunk_size == 0 && !final_chunk) {
+ msg_warn("%s: null BDAT request from %s",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr);
+ return skip_bdat(state, chunk_size, final_chunk,
+ "551 5.7.1 Null BDAT request");
+ }
+
+ /*
+ * BDAT commands may be pipelined within a MAIL transaction. After a BDAT
+ * request fails, keep accepting BDAT requests and skipping BDAT payloads
+ * to maintain synchronization with the remote SMTP client, until the
+ * client sends BDAT LAST or RSET.
+ */
+ if (state->bdat_state == SMTPD_BDAT_STAT_ERROR)
+ return skip_bdat(state, chunk_size, final_chunk,
+ "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 for the purpose of policy
+ * evaluation.
+ */
+ if (!SMTPD_PROCESSING_BDAT(state)) {
+
+ /*
+ * With ESMTP command pipelining a client may send BDAT before the
+ * server has replied to all RCPT commands. For this reason we cannot
+ * treat BDAT without valid recipients as a protocol error. Worse,
+ * RFC 3030 does not discuss the role of BDAT commands in RFC 2920
+ * command groups (batches of commands that may be sent without
+ * waiting for a response to each individual command). Therefore we
+ * have to allow for clients that pipeline the entire SMTP session
+ * after EHLO, including multiple MAIL transactions.
+ */
+ if (state->rcpt_count == 0) {
+ if (!SMTPD_IN_MAIL_TRANSACTION(state)) {
+ /* TODO: maybe remove this from the DATA and BDAT handlers. */
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ return skip_bdat(state, chunk_size, final_chunk,
+ "503 5.5.1 Error: need RCPT command");
+ } else {
+ return skip_bdat(state, chunk_size, final_chunk,
+ "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, final_chunk, "%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, final_chunk, "%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, final_chunk,
+ "%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, final_chunk,
+ "552 5.3.4 Chunk exceeds message size limit");
+ }
+
+ /*
+ * 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_PROCESSING_BDAT(state)) {
+ common_pre_message_handling(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;
+ }
+ state->bdat_state = SMTPD_BDAT_STAT_OK;
+ state->where = SMTPD_AFTER_BDAT;
+
+ /*
+ * 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.
+ */
+
+ /*
+ * Instead of reading the entire BDAT chunk into memory, read the chunk
+ * one fragment at a time. The loops below always make one iteration, to
+ * avoid code duplication for the "BDAT 0 LAST" case (empty chunk).
+ */
+ done = 0;
+ do {
+ if ((read_len = chunk_size - done) > VSTREAM_BUFSIZE)
+ read_len = VSTREAM_BUFSIZE;
+ /* Caution: smtp_fread() makes a long jump in case of EOF or timeout. */
+ VSTRING_RESET(state->buffer);
+ if (read_len > 0)
+ smtp_fread(state->buffer, read_len, state->client);
+ state->bdat_get_stream = vstream_memreopen(
+ state->bdat_get_stream, state->buffer, O_RDONLY);
+
+ /*
+ * Read lines from the fragment. The last line may continue in the
+ * next fragment, or in the next chunk.
+ */
+ do {
+ if (smtp_get_noexcept(state->bdat_get_buffer,
+ state->bdat_get_stream,
+ var_line_limit,
+ SMTP_GET_FLAG_APPEND) == '\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 (VSTRING_LEN(state->bdat_get_buffer) > 0
+ && final_chunk && read_len == chunk_size - done) {
+ /* Stopped at final chunk end; handle missing end-of-line. */
+ curr_rec_type = REC_TYPE_NORM;
+ } else {
+ /* Stopped at fragment end; empty buffer or not at chunk end. */
+ /* Skip the out_record() and VSTRING_RESET() calls below. */
+ 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)
+ if (out_record(out_stream, REC_TYPE_CONT, ".", 1) < 0)
+ state->err = out_error;
+ if (state->err == CLEANUP_STAT_OK
+ && out_record(out_stream, curr_rec_type,
+ vstring_str(state->bdat_get_buffer),
+ VSTRING_LEN(state->bdat_get_buffer)) < 0)
+ state->err = out_error;
+ }
+ }
+ VSTRING_RESET(state->bdat_get_buffer);
+ state->bdat_prev_rec_type = curr_rec_type;
+ } while (!vstream_feof(state->bdat_get_stream));
+ done += read_len;
+ } while (done < chunk_size);
+
+ /*
+ * Special handling for BDAT LAST (successful or unsuccessful).
+ */
+ if (final_chunk) {
+ state->where = SMTPD_AFTER_EOM;
+ 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, enter the
+ * SMTPD_BDAT_STAT_ERROR state to accept BDAT commands and skip BDAT
+ * payloads.
+ */
+ else if (state->err != CLEANUP_STAT_OK) {
+ /* NOT: state->where = SMTPD_AFTER_EOM; */
+ (void) common_post_message_handling(state);
+ state->bdat_state = SMTPD_BDAT_STAT_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)
@@ -4842,6 +5222,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 */
@@ -4864,6 +5251,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,},
@@ -5292,7 +5680,13 @@ static void smtpd_proto(SMTPD_STATE *state)
state->reason, SMTPD_CMD_DATA, /* 2.5 compat */
(long) (state->act_size + vstream_peek(state->client)),
state->namaddr);
- } else if (strcmp(state->where, SMTPD_AFTER_DOT)
+ } else if (strcmp(state->where, SMTPD_AFTER_BDAT) == 0) {
+ msg_info("%s after %s (%lu bytes) from %s",
+ state->reason, SMTPD_CMD_BDAT,
+ (long) (state->act_size + VSTRING_LEN(state->buffer)
+ + VSTRING_LEN(state->bdat_get_buffer)),
+ state->namaddr);
+ } else if (strcmp(state->where, SMTPD_AFTER_EOM)
|| strcmp(state->reason, REASON_LOST_CONNECTION)) {
msg_info("%s after %s from %s",
state->reason, state->where, state->namaddr);
@@ -5820,6 +6214,7 @@ int main(int argc, char **argv)
#ifdef USE_TLS
VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0,
#endif
+ VAR_SMTPD_SASL_RESP_LIMIT, DEF_SMTPD_SASL_RESP_LIMIT, &var_smtpd_sasl_resp_limit, DEF_SMTPD_SASL_RESP_LIMIT, 0,
VAR_SMTPD_POLICY_REQ_LIMIT, DEF_SMTPD_POLICY_REQ_LIMIT, &var_smtpd_policy_req_limit, 0, 0,
VAR_SMTPD_POLICY_TRY_LIMIT, DEF_SMTPD_POLICY_TRY_LIMIT, &var_smtpd_policy_try_limit, 1, 0,
0,
diff --git a/postfix/src/smtpd/smtpd.h b/postfix/src/smtpd/smtpd.h
index 4facfb080..a584a2c83 100644
--- a/postfix/src/smtpd/smtpd.h
+++ b/postfix/src/smtpd/smtpd.h
@@ -182,13 +182,24 @@ typedef struct {
const char **milter_argv; /* SMTP command vector */
ssize_t milter_argc; /* SMTP command vector */
const char *milter_reject_text; /* input to call-back from Milter */
- MILTERS *milters; /* Milter initialization status.*/
+ MILTERS *milters; /* Milter initialization status. */
/*
* EHLO temporary space.
*/
VSTRING *ehlo_buf;
ARGV *ehlo_argv;
+
+ /*
+ * BDAT processing state.
+ */
+#define SMTPD_BDAT_STAT_NONE 0 /* not processing BDAT */
+#define SMTPD_BDAT_STAT_OK 1 /* accepting BDAT chunks */
+#define SMTPD_BDAT_STAT_ERROR 2 /* skipping BDAT chunks */
+ int bdat_state; /* see above */
+ 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 +209,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 */
@@ -223,7 +234,8 @@ extern void smtpd_state_reset(SMTPD_STATE *);
*/
#define SMTPD_AFTER_CONNECT "CONNECT"
#define SMTPD_AFTER_DATA "DATA content"
-#define SMTPD_AFTER_DOT "END-OF-MESSAGE"
+#define SMTPD_AFTER_BDAT "BDAT content"
+#define SMTPD_AFTER_EOM "END-OF-MESSAGE"
/*
* Other stages. These are sometimes used to change the way information is
@@ -236,7 +248,8 @@ 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_EOD SMTPD_AFTER_DOT /* XXX Was: END-OF-DATA */
+#define SMTPD_CMD_BDAT "BDAT"
+#define SMTPD_CMD_EOD SMTPD_AFTER_EOM /* XXX Was: END-OF-DATA */
#define SMTPD_CMD_RSET "RSET"
#define SMTPD_CMD_NOOP "NOOP"
#define SMTPD_CMD_VRFY "VRFY"
@@ -321,6 +334,12 @@ extern void smtpd_state_reset(SMTPD_STATE *);
*/
#define SMTPD_IN_MAIL_TRANSACTION(state) ((state)->sender != 0)
+ /*
+ * Are we processing BDAT requests?
+ */
+#define SMTPD_PROCESSING_BDAT(state) \
+ ((state)->bdat_state != SMTPD_BDAT_STAT_NONE)
+
/*
* SMTPD peer information lookup.
*/
diff --git a/postfix/src/smtpd/smtpd_chat.c b/postfix/src/smtpd/smtpd_chat.c
index bd7681331..295563e47 100644
--- a/postfix/src/smtpd/smtpd_chat.c
+++ b/postfix/src/smtpd/smtpd_chat.c
@@ -9,6 +9,10 @@
/*
/* void smtpd_chat_pre_jail_init(void)
/*
+/* int smtpd_chat_query_limit(state, limit)
+/* SMTPD_STATE *state;
+/* int limit;
+/*
/* void smtpd_chat_query(state)
/* SMTPD_STATE *state;
/*
@@ -27,6 +31,11 @@
/*
/* smtpd_chat_pre_jail_init() performs one-time initialization.
/*
+/* smtpd_chat_query_limit() reads a line from the client that is
+/* at most "limit" bytes long. A copy is appended to the SMTP
+/* transaction log. The return value is non-zero for a complete
+/* line or else zero if the length limit was exceeded.
+/*
/* smtpd_chat_query() receives a client request and appends a copy
/* to the SMTP transaction log.
/*
@@ -106,7 +115,7 @@ static MAPS *smtpd_rej_ftr_maps;
/* smtpd_chat_pre_jail_init - initialize */
-void smtpd_chat_pre_jail_init(void)
+void smtpd_chat_pre_jail_init(void)
{
static int init_count = 0;
@@ -151,7 +160,7 @@ static void smtp_chat_append(SMTPD_STATE *state, char *direction,
/* smtpd_chat_query - receive and record an SMTP request */
-void smtpd_chat_query(SMTPD_STATE *state)
+int smtpd_chat_query_limit(SMTPD_STATE *state, int limit)
{
int last_char;
@@ -159,16 +168,17 @@ void smtpd_chat_query(SMTPD_STATE *state)
* We can't parse or store input that exceeds var_line_limit, so we skip
* over it to avoid loss of synchronization.
*/
- last_char = smtp_get(state->buffer, state->client, var_line_limit,
+ last_char = smtp_get(state->buffer, state->client, limit,
SMTP_GET_FLAG_SKIP);
smtp_chat_append(state, "In: ", STR(state->buffer));
if (last_char != '\n')
msg_warn("%s: request longer than %d: %.30s...",
- state->namaddr, var_line_limit,
+ state->namaddr, limit,
printable(STR(state->buffer), '?'));
if (msg_verbose)
msg_info("< %s: %s", state->namaddr, STR(state->buffer));
+ return (last_char == '\n');
}
/* smtpd_chat_reply - format, send and record an SMTP response */
@@ -176,6 +186,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;
@@ -189,9 +209,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 ((*(cp = STR(state->buffer)) == '4' || *cp == '5')
&& ((smtpd_rej_ftr_maps != 0
diff --git a/postfix/src/smtpd/smtpd_chat.h b/postfix/src/smtpd/smtpd_chat.h
index e64b1c92f..6b3a5472c 100644
--- a/postfix/src/smtpd/smtpd_chat.h
+++ b/postfix/src/smtpd/smtpd_chat.h
@@ -9,15 +9,25 @@
/* DESCRIPTION
/* .nf
+ /*
+ * Global library.
+ */
+#include
+
/*
* External interface.
*/
extern void smtpd_chat_pre_jail_init(void);
extern void smtpd_chat_reset(SMTPD_STATE *);
+extern int smtpd_chat_query_limit(SMTPD_STATE *, int);
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 *);
+#define smtpd_chat_query(state) \
+ ((void) smtpd_chat_query_limit((state), var_line_limit))
+
/* LICENSE
/* .ad
/* .fi
diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c
index 94e8c0181..f78d56cf7 100644
--- a/postfix/src/smtpd/smtpd_check.c
+++ b/postfix/src/smtpd/smtpd_check.c
@@ -2598,7 +2598,7 @@ static int check_table_result(SMTPD_STATE *state, const char *table,
if (not_in_client_helo(state, table, "PREPEND", reply_class) == 0)
return (SMTPD_CHECK_DUNNO);
#endif
- if (strcmp(state->where, SMTPD_AFTER_DOT) == 0) {
+ if (strcmp(state->where, SMTPD_AFTER_EOM) == 0) {
msg_warn("access table %s: action PREPEND must be used before %s",
table, VAR_EOD_CHECKS);
return (SMTPD_CHECK_DUNNO);
@@ -3910,7 +3910,9 @@ static int check_policy_service(SMTPD_STATE *state, const char *server,
if (attr_clnt_request(policy_clnt->client,
ATTR_FLAG_NONE, /* Query attributes. */
SEND_ATTR_STR(MAIL_ATTR_REQ, "smtpd_access_policy"),
- SEND_ATTR_STR(MAIL_ATTR_PROTO_STATE, state->where),
+ SEND_ATTR_STR(MAIL_ATTR_PROTO_STATE,
+ STREQ(state->where, SMTPD_CMD_BDAT) ?
+ SMTPD_CMD_DATA : state->where),
SEND_ATTR_STR(MAIL_ATTR_ACT_PROTO_NAME, state->protocol),
SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->addr),
SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_NAME, state->name),
@@ -3929,7 +3931,8 @@ static int check_policy_service(SMTPD_STATE *state, const char *server,
state->recipient ? state->recipient : ""),
SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT,
((strcasecmp(state->where, SMTPD_CMD_DATA) == 0) ||
- (strcasecmp(state->where, SMTPD_AFTER_DOT) == 0)) ?
+ (strcasecmp(state->where, SMTPD_CMD_BDAT) == 0) ||
+ (strcasecmp(state->where, SMTPD_AFTER_EOM) == 0)) ?
state->rcpt_count : 0),
SEND_ATTR_STR(MAIL_ATTR_QUEUEID,
state->queue_id ? state->queue_id : ""),
diff --git a/postfix/src/smtpd/smtpd_sasl_glue.c b/postfix/src/smtpd/smtpd_sasl_glue.c
index 3a0782dc7..020c830fa 100644
--- a/postfix/src/smtpd/smtpd_sasl_glue.c
+++ b/postfix/src/smtpd/smtpd_sasl_glue.c
@@ -308,12 +308,11 @@ int smtpd_sasl_authenticate(SMTPD_STATE *state,
/*
* Receive the client response. "*" means that the client gives up.
- * XXX For now we ignore the fact that an excessively long response
- * will be chopped into multiple responses. To handle such responses,
- * we need to change smtpd_chat_query() so that it returns an error
- * indication.
*/
- smtpd_chat_query(state);
+ if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) {
+ smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded");
+ return (-1);
+ }
if (strcmp(STR(state->buffer), "*") == 0) {
msg_warn("%s: SASL %s authentication aborted",
state->namaddr, sasl_method);
diff --git a/postfix/src/smtpd/smtpd_state.c b/postfix/src/smtpd/smtpd_state.c
index 87e62fa44..f2f5f899c 100644
--- a/postfix/src/smtpd/smtpd_state.c
+++ b/postfix/src/smtpd/smtpd_state.c
@@ -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,13 @@ void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream,
state->ehlo_argv = 0;
state->ehlo_buf = 0;
+
+ /*
+ * BDAT.
+ */
+ state->bdat_state = SMTPD_BDAT_STAT_NONE;
+ state->bdat_get_stream = 0;
+ state->bdat_get_buffer = 0;
}
/* smtpd_state_reset - cleanup after disconnect */
@@ -231,4 +237,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);
}
diff --git a/postfix/src/util/vbuf.c b/postfix/src/util/vbuf.c
index 6f4ef4557..ecc6f197b 100644
--- a/postfix/src/util/vbuf.c
+++ b/postfix/src/util/vbuf.c
@@ -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 */
diff --git a/postfix/src/util/vstream.c b/postfix/src/util/vstream.c
index b85d51159..674e79604 100644
--- a/postfix/src/util/vstream.c
+++ b/postfix/src/util/vstream.c
@@ -19,6 +19,11 @@
/* VSTRING *string;
/* int flags;
/*
+/* VSTREAM *vstream_memreopen(stream, string, flags)
+/* VSTREAM *stream;
+/* VSTRING *string;
+/* int flags;
+/*
/* int vstream_fclose(stream)
/* VSTREAM *stream;
/*
@@ -205,6 +210,10 @@
/* will be in an indeterminate state while the stream is open,
/* and will be null-terminated when the stream is closed.
/*
+/* vstream_memreopen() reopens a memory stream. When the
+/* \fIstream\fR argument is a null pointer, the behavior is that
+/* of vstream_memopen().
+/*
/* vstream_fclose() closes the named buffered stream. The result
/* is 0 in case of success, VSTREAM_EOF in case of problems.
/* vstream_fclose() reports the same errors as vstream_ferror().
@@ -446,6 +455,8 @@
/* 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
@@ -1692,11 +1703,12 @@ const char *vstream_peek_data(VSTREAM *vp)
/* vstream_memopen - open a VSTRING */
-VSTREAM *vstream_memopen(VSTRING *string, int flags)
+VSTREAM *vstream_memreopen(VSTREAM *stream, VSTRING *string, int flags)
{
- VSTREAM *stream;
-
- stream = vstream_subopen();
+ if (stream == 0)
+ stream = vstream_subopen();
+ else if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0)
+ msg_panic("vstream_memreopen: cannot reopen non-memory stream");
stream->fd = -1;
stream->read_fn = 0;
stream->write_fn = 0;
diff --git a/postfix/src/util/vstream.h b/postfix/src/util/vstream.h
index ac6b02ca5..60197a77a 100644
--- a/postfix/src/util/vstream.h
+++ b/postfix/src/util/vstream.h
@@ -263,7 +263,9 @@ extern int vstream_tweak_tcp(VSTREAM *);
/*
* Read/write VSTRING memory.
*/
-VSTREAM *vstream_memopen(struct VSTRING *, int);
+#define vstream_memopen(string, flags) \
+ vstream_memreopen((VSTREAM *) 0, (string), (flags))
+VSTREAM *vstream_memreopen(VSTREAM *, struct VSTRING *, int);
/* LICENSE
/* .ad
diff --git a/postfix/src/util/vstring_vstream.c b/postfix/src/util/vstring_vstream.c
index bec981699..6c39140d5 100644
--- a/postfix/src/util/vstring_vstream.c
+++ b/postfix/src/util/vstring_vstream.c
@@ -6,6 +6,39 @@
/* SYNOPSIS
/* #include
/*
+/* int vstring_get_flags(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_nonl(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_null(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/*
+/* int vstring_get_flags_nonl_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/*
+/* int vstring_get_flags_null_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/* CONVENIENCE API
/* int vstring_get(vp, fp)
/* VSTRING *vp;
/* VSTREAM *fp;
@@ -36,19 +69,25 @@
/* The routines in this module each read one newline or null-terminated
/* string from an input stream. In all cases the result is either the
/* last character read, typically the record terminator, or VSTREAM_EOF.
+/* The flags argument is VSTRING_GET_FLAG_NONE (default) or
+/* VSTRING_GET_FLAG_APPEND (append instead of overwrite).
/*
-/* vstring_get() reads one line from the named stream, including the
+/* vstring_get_flags() reads one line from the named stream, including the
/* terminating newline character if present.
/*
-/* vstring_get_nonl() reads a line from the named stream and strips
+/* vstring_get_flags_nonl() reads a line from the named stream and strips
/* the trailing newline character.
/*
-/* vstring_get_null() reads a null-terminated string from the named
+/* vstring_get_flags_null() reads a null-terminated string from the named
/* stream.
/*
-/* the vstring_get_bound() routines read no more
+/* the vstring_get_flags_bound() routines read no more
/* than \fIbound\fR characters. Otherwise they behave like the
/* unbounded versions documented above.
+/*
+/* The functions without _flags in their name accept the same
+/* arguments except flags. These functions use the default
+/* flags valie.
/* DIAGNOSTICS
/* Fatal errors: memory allocation failure.
/* Panic: improper string bound.
@@ -82,13 +121,14 @@
#define VSTRING_GET_RESULT(vp) \
(VSTRING_LEN(vp) > 0 ? vstring_end(vp)[-1] : VSTREAM_EOF)
-/* vstring_get - read line from file, keep newline */
+/* vstring_get_flags - read line from file, keep newline */
-int vstring_get(VSTRING *vp, VSTREAM *fp)
+int vstring_get_flags(VSTRING *vp, VSTREAM *fp, int flags)
{
int c;
- VSTRING_RESET(vp);
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
VSTRING_ADDCH(vp, c);
if (c == '\n')
@@ -98,42 +138,46 @@ int vstring_get(VSTRING *vp, VSTREAM *fp)
return (VSTRING_GET_RESULT(vp));
}
-/* vstring_get_nonl - read line from file, strip newline */
+/* vstring_get_flags_nonl - read line from file, strip newline */
-int vstring_get_nonl(VSTRING *vp, VSTREAM *fp)
+int vstring_get_flags_nonl(VSTRING *vp, VSTREAM *fp, int flags)
{
int c;
- VSTRING_RESET(vp);
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
VSTRING_ADDCH(vp, c);
VSTRING_TERMINATE(vp);
return (c == '\n' ? c : VSTRING_GET_RESULT(vp));
}
-/* vstring_get_null - read null-terminated string from file */
+/* vstring_get_flags_null - read null-terminated string from file */
-int vstring_get_null(VSTRING *vp, VSTREAM *fp)
+int vstring_get_flags_null(VSTRING *vp, VSTREAM *fp, int flags)
{
int c;
- VSTRING_RESET(vp);
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
VSTRING_ADDCH(vp, c);
VSTRING_TERMINATE(vp);
return (c == 0 ? c : VSTRING_GET_RESULT(vp));
}
-/* vstring_get_bound - read line from file, keep newline, up to bound */
+/* vstring_get_flags_bound - read line from file, keep newline, up to bound */
-int vstring_get_bound(VSTRING *vp, VSTREAM *fp, ssize_t bound)
+int vstring_get_flags_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
{
int c;
if (bound <= 0)
msg_panic("vstring_get_bound: invalid bound %ld", (long) bound);
- VSTRING_RESET(vp);
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
VSTRING_ADDCH(vp, c);
if (c == '\n')
@@ -143,32 +187,36 @@ int vstring_get_bound(VSTRING *vp, VSTREAM *fp, ssize_t bound)
return (VSTRING_GET_RESULT(vp));
}
-/* vstring_get_nonl_bound - read line from file, strip newline, up to bound */
+/* vstring_get_flags_nonl_bound - read line from file, strip newline, up to bound */
-int vstring_get_nonl_bound(VSTRING *vp, VSTREAM *fp, ssize_t bound)
+int vstring_get_flags_nonl_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
{
int c;
if (bound <= 0)
msg_panic("vstring_get_nonl_bound: invalid bound %ld", (long) bound);
- VSTRING_RESET(vp);
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
VSTRING_ADDCH(vp, c);
VSTRING_TERMINATE(vp);
return (c == '\n' ? c : VSTRING_GET_RESULT(vp));
}
-/* vstring_get_null_bound - read null-terminated string from file */
+/* vstring_get_flags_null_bound - read null-terminated string from file */
-int vstring_get_null_bound(VSTRING *vp, VSTREAM *fp, ssize_t bound)
+int vstring_get_flags_null_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
{
int c;
if (bound <= 0)
msg_panic("vstring_get_null_bound: invalid bound %ld", (long) bound);
- VSTRING_RESET(vp);
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
VSTRING_ADDCH(vp, c);
VSTRING_TERMINATE(vp);
diff --git a/postfix/src/util/vstring_vstream.h b/postfix/src/util/vstring_vstream.h
index 4753929d1..e0dc801b4 100644
--- a/postfix/src/util/vstring_vstream.h
+++ b/postfix/src/util/vstring_vstream.h
@@ -19,12 +19,34 @@
/*
* External interface.
*/
-extern int WARN_UNUSED_RESULT vstring_get(VSTRING *, VSTREAM *);
-extern int WARN_UNUSED_RESULT vstring_get_nonl(VSTRING *, VSTREAM *);
-extern int WARN_UNUSED_RESULT vstring_get_null(VSTRING *, VSTREAM *);
-extern int WARN_UNUSED_RESULT vstring_get_bound(VSTRING *, VSTREAM *, ssize_t);
-extern int WARN_UNUSED_RESULT vstring_get_nonl_bound(VSTRING *, VSTREAM *, ssize_t);
-extern int WARN_UNUSED_RESULT vstring_get_null_bound(VSTRING *, VSTREAM *, ssize_t);
+#define VSTRING_GET_FLAG_NONE (0)
+#define VSTRING_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */
+
+extern int WARN_UNUSED_RESULT vstring_get_flags(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_nonl(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_null(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_bound(VSTRING *, VSTREAM *, int, ssize_t);
+extern int WARN_UNUSED_RESULT vstring_get_flags_nonl_bound(VSTRING *, VSTREAM *, int, ssize_t);
+extern int WARN_UNUSED_RESULT vstring_get_flags_null_bound(VSTRING *, VSTREAM *, int, ssize_t);
+
+ /*
+ * Convenience aliases for most use cases.
+ */
+#define vstring_get(string, stream) \
+ vstring_get_flags((string), (stream), VSTRING_GET_FLAG_NONE)
+#define vstring_get_nonl(string, stream) \
+ vstring_get_flags_nonl((string), (stream), VSTRING_GET_FLAG_NONE)
+#define vstring_get_null(string, stream) \
+ vstring_get_flags_null((string), (stream), VSTRING_GET_FLAG_NONE)
+
+#define vstring_get_bound(string, stream, size) \
+ vstring_get_flags_bound((string), (stream), VSTRING_GET_FLAG_NONE, size)
+#define vstring_get_nonl_bound(string, stream, size) \
+ vstring_get_flags_nonl_bound((string), (stream), \
+ VSTRING_GET_FLAG_NONE, size)
+#define vstring_get_null_bound(string, stream, size) \
+ vstring_get_flags_null_bound((string), (stream), \
+ VSTRING_GET_FLAG_NONE, size)
/*
* Backwards compatibility for code that still uses the vstring_fgets()