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:
parent
8c1dd8415d
commit
c8e31ae510
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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 *);
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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 */
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 : ""),
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user