2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-08-30 13:48:06 +00:00

postfix-3.4-20180826

This commit is contained in:
Wietse Venema 2018-08-26 00:00:00 -05:00 committed by Viktor Dukhovni
parent 8c1dd8415d
commit c8e31ae510
38 changed files with 1330 additions and 341 deletions

View File

@ -23647,3 +23647,64 @@ Apologies for any names omitted.
Bugfix (introduced: 20180812): postscreen_send.c did not
build without warnings. Viktor Dukhovni.
20180824
Cleanup: with SMTPUTF8 turned off, the MySQL and PgSQL maps
accept only well-formed UTF-8 queries, and return NOT FOUND
otherwise. This was in introduced in Postfix 3.0 for LDAP
and SQLite, with no complaints coming forth. Files:
global/dict_mysql.c, global/dict_pgsql.c.
20180805-20180825 Chunking support
Cleanup: vbuf_get() now sets the EOF flag, so that reading
from a VSTRING stream works as expected. File: util/vbuf.c.
Cleanup: added an append-mode flag to functions that read
a VSTRING from a stream. The historical APIs are preserved
in the form of aliases. Files: util/vstring_vstream.[hc],
global/smtp_stream.[hc].
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.
Cleanup: the postscreen(8) daemon now hangs up after receiving
the DATA command. Justification: it should never receive DATA
from a legitimate client, because 1) postscreen(8) rejects all
recipients, and 2) postscreen(8) does not announce PIPELINING.
This makes postscreen(8) DATA and BDAT behavior more
consistent. File: postscreen/postscreen_smtpd.c.
BDAT final touches: report accurate BDAT byte counts after
timeout or lost connection; send DATA instead of BDAT in
policy delegation protocol. Files: smtpd/smtpd.[hc],
smtpd/smtpd_check.c.
BDAT final touches: if the BDAT EHLO announcement is disabled,
then smtpd(8) and postscreen(8) will not accept BDAT commands.
Files: smtpd/smtpd.c, postscreen/postscreen_smtpd.c.
20180826
Cleanup: with GSSAPI, the Postfix SMTP client's initial
SASL response may be as large as 12288 bytes. When the "AUTH
<method> <initial-response>" command would exceed the SMTP
command length of 512 bytes, send the initial response
during the SASL dialog. Viktor Dukhovni. File:
smtp/smtp_sasl_glue.c.
Cleanup: prepare the Postfix SMTP server needs to receive
SASL responses that exceed the line_length_limit value.
This introduces a new parameter smtpd_sasl_response_limit
(default: 12288). Viktor Dukhovni. Files: mantools/postlink,
proto/postconf.proto, global/mail_params.h, smtpd/smtpd.c,
smtpd/smtpd_chat.c, smtpd/smtpd_chat.h, smtpd/smtpd_sasl_glue.c.

View File

@ -166,7 +166,9 @@ The following is specific to SMTPD delegated policy requests:
* Protocol states are CONNECT, EHLO, HELO, MAIL, RCPT, 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.
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).
The policy server replies with any action that is allowed in a Postfix SMTPD
access(5) table. Example:

View File

@ -25,8 +25,120 @@ 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.
Incompatble change with snapshot 20180701
=========================================
Major changes with snapshot 20180826
====================================
Postfix SMTP server support for RFC 3030 CHUNKING (the BDAT command)
without BINARYMIME, in both smtpd(8) and postscreen(8).
To disable the SMTP server's CHUNKING support:
/etc/postfix/main.cf:
# The logging alternative:
smtpd_discard_ehlo_keywords = chunking
# The non-logging alternative:
smtpd_discard_ehlo_keywords = chunking, silent_discard
Be sure to specify '-o smtpd_discard_ehlo_keywords=' in master.cf
for the submission and smtps services, in case you have clients
that benefit from CHUNKING support.
Impact on existing configurations:
----------------------------------
- There are no changes for smtpd_mumble_restrictions, smtpd_proxy_filter,
smtpd_milters, or for postscreen settings, except for the additional
option to suppress the SMTP server's CHUNKING service announcement.
- There are no changes in the Postfix queue file content, no changes
for down-stream SMTP servers or after-queue content filters, and
no changes in the envelope or message content that Milters will
receive.
Example SMTP session
--------------------
The main differences are that the Postfix SMTP server announces
"CHUNKING" support in the EHLO response, and that instead of sending
one DATA request, the remote SMTP client may send one or more BDAT
requests. In the example below, "S:" indicates server responses,
and "C:" indicates client requests.
S: 220 server.example.com
C: EHLO client.example.com
S: 250-server.example.com
S: 250-PIPELINING
S: 250-SIZE 153600000
S: 250-VRFY
S: 250-ETRN
S: 250-STARTTLS
S: 250-AUTH PLAIN LOGIN
S: 250-ENHANCEDSTATUSCODES
S: 250-8BITMIME
S: 250-DSN
S: 250-SMTPUTF8
S: 250 CHUNKING
C: MAIL FROM:<sender@example.com>
S: 250 2.1.0 Ok
C: RCPT TO:<recipient@example.com>
S: 250 2.1.5 Ok
C: BDAT 10000
C: ..followed by 10000 bytes...
S: 250 2.0.0 Ok: 10000 bytes
C: BDAT 123
C: ..followed by 123 bytes...
S: 250 2.0.0 Ok: 123 bytes
C: BDAT 0 LAST
S: 250 2.0.0 Ok: 10123 bytes queued as 41yYhh41qmznjbD
C: QUIT
S: 221 2.0.0 Bye
Internally in Postfix, there is no difference between mail that was
received with BDAT or with DATA. Postfix smtpd_mumble_restrictions,
policy delegation queries, smtpd_proxy_filter and Milters all behave
as if Postfix received (MAIL + RCPT + DATA + end-of-data). However,
Postfix will log BDAT-related failures as "xxx after BDAT" to avoid
complicating troubleshooting (xxx = 'lost connection' or 'timeout'),
and will log a warning when a client sends a malformed BDAT command.
Benefits of CHUNKING (BDAT) support without BINARYMIME:
-------------------------------------------------------
Support for CHUNKING (BDAT) was added to improve interoperability
with some clients, a benefit that would reportedly exist even without
Postfix support for BINARYMIME.
Postfix does not support BINARYMIME at this time because:
- BINARYMIME support would require moderately invasive changes to
support email content that is not line-oriented. With BINARYMIME,
the Content-Length: header specifies the length of arbitrary
content that has no line boundaries. Without BINARYMIME, binary
content is base64-encoded, and formatted as lines of text.
- There is no conversion of BINARYMIME to a line-oriented 8BITMIME
form that is compatible with legacy systems including UNIX mbox.
The available options are to convert binary content into one of
the 7bit forms (base64 or quoted-printable), or to return email
as undeliverable. Any conversion would break digital signatures,
so it would have to happen before signing.
Downsides of CHUNKING (BDAT) support:
-------------------------------------
The RFC 3030 authors did not specify any limitations on how clients
may pipeline commands (i.e. send commands without waiting for a
server response). If a server announces PIPELINING support, like
Postfix does, then a remote SMTP client can pipeline all commands
following EHLO, for example, MAIL/RCPT/BDAT/BDAT/MAIL/RCPT/BDAT,
without ever having to wait for a server response. This means that
with BDAT, the Postfix SMTP server cannot distinguish between a
well-behaved client and a spambot, based on their command pipelining
behavior. If you require "reject_unauth_pipelining" to block spambots,
turn off the CHUNKING support announcement as described above.
Incompatible change with snapshot 20180701
==========================================
To avoid performance loss under load, the tlsproxy(8) daemon now
requires a zero process limit in master.cf (this setting is provided

View File

@ -1,5 +1,8 @@
Wish list:
In smtpd(8) and postscreen(8), set the ehlo_discard_mask
to ~0 so that STARTTLS, BDAT, DSN, etc. won't work.
In postscreen, don't fork after 'postfix reload' when
psc_check_queue_length (and psc_post_queue_length?) is zero.

View File

@ -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).
</p>
</ul>

View File

@ -15484,7 +15484,7 @@ SMTP session. </p>
<p> Note: specify $$name in footer text that is looked up from
<a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>:-based <a href="postconf.5.html#smtpd_reject_footer_maps">smtpd_reject_footer_maps</a>, otherwise the
Postfix server not use the footer text and will log a warning
Postfix server will not use the footer text and will log a warning
instead. </p>
<dl>
@ -15876,6 +15876,26 @@ configuration file or rendezvous point. </p>
releases it was called <b><a href="postconf.5.html#smtpd_sasl_application_name">smtpd_sasl_application_name</a></b>. </p>
</DD>
<DT><b><a name="smtpd_sasl_response_limit">smtpd_sasl_response_limit</a>
(default: 12288)</b></DT><DD>
<p> 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. <a href="http://tools.ietf.org/html/rfc4954">RFC4954</a> 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.</p>
<p> This feature is available in Postfix 3.4 and later. Prior versions use
"<a href="postconf.5.html#line_length_limit">line_length_limit</a>", 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. </p>
</DD>
<DT><b><a name="smtpd_sasl_security_options">smtpd_sasl_security_options</a>

View File

@ -64,6 +64,7 @@ POSTSCREEN(8) POSTSCREEN(8)
<a href="http://tools.ietf.org/html/rfc2034">RFC 2034</a> (SMTP Enhanced Status Codes)
<a href="http://tools.ietf.org/html/rfc2821">RFC 2821</a> (SMTP protocol)
Not: <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)

File diff suppressed because it is too large Load Diff

View File

@ -10451,7 +10451,7 @@ SMTP session.
.PP
Note: specify $$name in footer text that is looked up from
regexp: or pcre:\-based smtpd_reject_footer_maps, otherwise the
Postfix server not use the footer text and will log a warning
Postfix server will not use the footer text and will log a warning
instead.
.IP "\fBclient_address\fR"
The Client IP address that
@ -10746,6 +10746,20 @@ configuration file or rendezvous point.
.PP
This feature is available in Postfix 2.3 and later. In earlier
releases it was called \fBsmtpd_sasl_application_name\fR.
.SH smtpd_sasl_response_limit (default: 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.
.PP
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.
.SH smtpd_sasl_security_options (default: noanonymous)
Postfix SMTP server SASL security options; as of Postfix 2.3
the list of available

View File

@ -71,6 +71,7 @@ RFC 1985 (ETRN command)
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)

View 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)
@ -370,6 +371,10 @@ Available in Postfix version 2.11 and later:
.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.
.SH "STARTTLS SUPPORT CONTROLS"
.na
.nf

View File

@ -582,6 +582,7 @@ while (<>) {
s;\bsmtpd_sasl_authenticated_header\b;<a href="postconf.5.html#smtpd_sasl_authenticated_header">$&</a>;g;
s;\bsmtpd_sasl_exceptions_networks\b;<a href="postconf.5.html#smtpd_sasl_exceptions_networks">$&</a>;g;
s;\bsmtpd_sasl_local_domain\b;<a href="postconf.5.html#smtpd_sasl_local_domain">$&</a>;g;
s;\bsmtpd_sasl_response_limit\b;<a href="postconf.5.html#smtpd_sasl_response_limit">$&</a>;g;
s;\bsmtpd_sasl_secu[-</Bb>]*\n* *[<Bb>]*rity_options\b;<a href="postconf.5.html#smtpd_sasl_security_options">$&</a>;g;
s;\bsmtpd_sender_login_maps\b;<a href="postconf.5.html#smtpd_sender_login_maps">$&</a>;g;
s;\bsmtpd_sender_restrictions\b;<a href="postconf.5.html#smtpd_sender_restrictions">$&</a>;g;

View File

@ -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).
</p>
</ul>

View File

@ -10779,6 +10779,22 @@ selected with <b>smtpd_sasl_type</b> and <b>smtpd_sasl_path</b>.
<p> This feature is available in Postfix 2.11 and later. Prior
versions behave as if "<b>smtp</b>" is specified. </p>
%PARAM smtpd_sasl_response_limit 12288
<p> 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.</p>
<p> 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. </p>
%PARAM cyrus_sasl_config_path
<p> Search path for Cyrus SASL application configuration files,

View File

@ -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.
*/

View File

@ -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;
}

View 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,
};

View 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 *);

View File

@ -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.
*/

View 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 "20180823"
#define MAIL_RELEASE_DATE "20180826"
#define MAIL_VERSION_NUMBER "3.4"
#ifdef SNAPSHOT

View File

@ -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)

View 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);
@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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 <CRLF> 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 */
}
/*

View File

@ -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

View 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)
@ -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 <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_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 <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
@ -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,

View File

@ -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.
*/

View File

@ -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

View File

@ -9,15 +9,25 @@
/* DESCRIPTION
/* .nf
/*
* Global library.
*/
#include <mail_params.h>
/*
* 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

View File

@ -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 : ""),

View File

@ -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);

View 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,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);
}

View 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 */

View File

@ -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;

View File

@ -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

View File

@ -6,6 +6,39 @@
/* SYNOPSIS
/* #include <vstring_vstream.h>
/*
/* 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<whatever>_bound() routines read no more
/* the vstring_get_flags<whatever>_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);

View File

@ -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()