-TDNS_REPLY
-TDNS_RR
-TDOMAIN_LIST
+-TDSN_BUF
-TDSN_SPLIT
-TDSN_VSTRING
-TEXPAND_ATTR
need to unlock the output file, so it won't clobber the errno value.
- Avoid passing around strings that combine enhanced status code
-and diagnostic text, because the compiler can't help to enforce
-that everything has a status code. Currently there are two exceptions
-to this rule: the cleanup server status reply, and the delivery
-agent status reply. Once these protocols are updated we can remove
-the dns_prepend() routine. The third exception, enhanced status
-codes in external command output, is a feature.
+and diagnostic text. Instead, use separate variables for status
+code and text, so that the compiler can enforce that everything has
+a status code. Currently there are two exceptions to this rule:
+the cleanup server status reply, and the delivery agent status
+reply. Once these protocols are updated we can remove the dns_prepend()
+routine. The third exception, enhanced status codes in external
+command output, is a feature.
- The bounce/defer/sent library modules will catch the cases where
an enhanced status code does not match the reject/defer/success
20050321-27
Support for RFC 3463 enhanced status codes. See also the
- ENHANCED_STATUS_README file for background.
+ ENHANCED_STATUS_README (a hacker's guide) for background.
New module to pass around (status code + text) instead of
just text. File: Files: global/dsn_util.c.
server IP address, TCP Port, and server HELO hostname
if available. File: smtp/smtp_proto.c.
+20050328
+
+ Cleanup: the REPLACE action is no longer implemented as
+ PREPEND+IGNORE. The result remains in the input stream,
+ and is subject to address rewriting and other processing
+ where applicable. File: cleanup/cleanup_message.c.
+
+ Feature: the TLS server name verification status is moved
+ out of the TLS session cache. This not only simplifies the
+ client-side TLS cache implementation, but also provides
+ better cache support for clients that connect to multiple
+ independent MTAs under the same DNS hostname or IP address,
+ provided that each MTA replies with a unique name in the
+ EHLO response. Patch by Victor Duchovni. Files: tlsmgr/tlsmgr.c,
+ tls/tls_verify.c, tls/tls_session.c, tls/tls_server.c,
+ tls/tls_scache.h, tls/tls_scache.c, tls/tls_misc.c,
+ tls/tls_mgr.h, tls/tls_mgr.c, tls/tls_client.c, tls/tls.h,
+ smtp/smtp_proto.c.
+
Open problems:
Med: disable header address rewriting after XCLIENT?
You can specify any database type that can store objects of several kbytes and
that supports the sequence operator. DBM databases are not suitable because
they can only store small objects. The cache is maintained by the tlsmgr(8)
-process, so there is no problem with concurrent access.
+process, so there is no problem with concurrent access. Session caching is
+highly recommended, because the cost of repeatedly negotiating TLS session keys
+is high.
Example:
can specify any database type that can store objects of several kbytes and that
supports the sequence operator. DBM databases are not suitable because they can
only store small objects. The cache is maintained by the tlsmgr(8) process, so
-there is no problem with concurrent access.
+there is no problem with concurrent access. Session caching is highly
+recommended, because the cost of repeatedly negotiating TLS session keys is
+high. Future Postfix SMTP servers may limit the number of sessions that a
+client is allowed to negotiate per unit time.
Example:
If you upgrade from Postfix 2.1 or earlier, read RELEASE_NOTES-2.2
before proceeding.
+Incompatibility with snapshot 20050329
+======================================
+
+If you use TLS, you need to execute "postfix reload" because the
+TLS manager protocol has changed.
+
Incompatibility with snapshot 20050328
======================================
+The logging format has changed. Postfix delivery agents now log the
+RFC 3463 enhanced status code as "dsn=x.y.z" where y and z can be
+up to three digits each.
+
After you upgrade from Postfix 2.2 or 2.3 you need to execute
"postfix reload", otherwise you will keep running the old Postfix
queue manager, which gives no special treatment to the enhanced
# line, immediately before the input that
# triggered the PREPEND action.
#
+# o The prepended text is not considered part of
+# the input stream. Unlike the result from the
+# REPLACE action, prepended text is not sub-
+# ject to header/body checks or address
+# rewriting, and does not affect the way that
+# Postfix adds missing message headers.
+#
# o When prepending text before a message header
-# line, the prepended text must begin with a
+# line, the prepended text must begin with a
# valid message header label.
#
# o This action cannot be used to prepend multi-
# This feature is available in Postfix 2.1 and later.
#
# REDIRECT user@domain
-# Write a message redirection request to the queue
-# file and inspect the next input line. After the
+# Write a message redirection request to the queue
+# file and inspect the next input line. After the
# message is queued, it will be sent to the specified
# address instead of the intended recipient(s).
#
-# Note: this action overrides the FILTER action, and
-# affects all recipients of the message. If multiple
-# REDIRECT actions fire, only the last one is exe-
+# Note: this action overrides the FILTER action, and
+# affects all recipients of the message. If multiple
+# REDIRECT actions fire, only the last one is exe-
# cuted.
#
# This feature is available in Postfix 2.1 and later.
#
# REPLACE text...
-# Replace the current line with the specified text
+# Replace the current line with the specified text
# and inspect the next input line.
#
-# Note: when replacing a message header line, the
-# replacement text must begin with a valid header
-# label.
-#
# This feature is available in Postfix 2.2 and later.
+# The description below applies to Postfix 2.2.2 and
+# later.
+#
+# Notes:
+#
+# o When replacing a message header line, the
+# replacement text must begin with a valid
+# header label.
+#
+# o The replaced text remains part of the input
+# stream. Unlike the result from the PREPEND
+# action, a replaced message header may be
+# subject to address rewriting and may affect
+# the way that Postfix adds missing message
+# headers.
#
# REJECT optional text...
-# Reject the entire message. Reply with optional
+# Reject the entire message. Reply with optional
# text... when the optional text is specified, other-
# wise reply with a generic error message.
#
-# Note: this action disables further header or
-# body_checks inspection of the current message and
+# Note: this action disables further header or
+# body_checks inspection of the current message and
# affects all recipients.
#
# Postfix version 2.3 and later support enhanced sta-
# enhanced status code of "5.7.1".
#
# WARN optional text...
-# Log a warning with the optional text... (or log a
-# generic message) and inspect the next input line.
+# Log a warning with the optional text... (or log a
+# generic message) and inspect the next input line.
# This action is useful for debugging and for testing
# a pattern before applying more drastic actions.
#
# BUGS
-# Many people overlook the main limitations of header and
-# body_checks rules. These rules operate on one logical
-# message header or one body line at a time, and a decision
-# made for one line is not carried over to the next line.
+# Many people overlook the main limitations of header and
+# body_checks rules. These rules operate on one logical
+# message header or one body line at a time, and a decision
+# made for one line is not carried over to the next line.
# If text in the message body is encoded (RFC 2045) then the
-# rules have to specified for the encoded form. Likewise,
+# rules have to specified for the encoded form. Likewise,
# when message headers are encoded (RFC 2047) then the rules
# need to be specified for the encoded form.
#
-# Message headers added by the cleanup(8) daemon itself are
+# Message headers added by the cleanup(8) daemon itself are
# excluded from inspection. Examples of such message headers
# are From:, To:, Message-ID:, Date:.
#
-# Message headers deleted by the cleanup(8) daemon will be
+# Message headers deleted by the cleanup(8) daemon will be
# examined before they are deleted. Examples are: Bcc:, Con-
# tent-Length:, Return-Path:.
#
# body_checks
# Lookup tables with content filter rules for message
# body lines. These filters see one physical line at
-# a time, in chunks of at most $line_length_limit
+# a time, in chunks of at most $line_length_limit
# bytes.
#
# body_checks_size_limit
-# The amount of content per message body segment
+# The amount of content per message body segment
# (attachment) that is subjected to $body_checks fil-
# tering.
#
#
# nested_header_checks (default: $header_checks)
# Lookup tables with content filter rules for message
-# header lines: respectively, these are applied to
-# the initial message headers (not including MIME
-# headers), to the MIME headers anywhere in the mes-
-# sage, and to the initial headers of attached mes-
+# header lines: respectively, these are applied to
+# the initial message headers (not including MIME
+# headers), to the MIME headers anywhere in the mes-
+# sage, and to the initial headers of attached mes-
# sages.
#
-# Note: these filters see one logical message header
-# at a time, even when a message header spans multi-
-# ple lines. Message headers that are longer than
+# Note: these filters see one logical message header
+# at a time, even when a message header spans multi-
+# ple lines. Message headers that are longer than
# $header_size_limit characters are truncated.
#
# disable_mime_input_processing
-# While receiving mail, give no special treatment to
-# MIME related message headers; all text after the
+# While receiving mail, give no special treatment to
+# MIME related message headers; all text after the
# initial message headers is considered to be part of
-# the message body. This means that header_checks is
-# applied to all the initial message headers, and
+# the message body. This means that header_checks is
+# applied to all the initial message headers, and
# that body_checks is applied to the remainder of the
# message.
#
-# Note: when used in this manner, body_checks will
-# process a multi-line message header one line at a
+# Note: when used in this manner, body_checks will
+# process a multi-line message header one line at a
# time.
#
# EXAMPLES
-# Header pattern to block attachments with bad file name
+# Header pattern to block attachments with bad file name
# extensions.
#
# /etc/postfix/main.cf:
# RFC 2047, message header encoding for non-ASCII text
#
# README FILES
-# Use "postconf readme_directory" or "postconf html_direc-
+# Use "postconf readme_directory" or "postconf html_direc-
# tory" to locate this information.
# DATABASE_README, Postfix lookup table overview
# CONTENT_INSPECTION_README, Postfix content inspection overview
# BACKSCATTER_README, blocking returned forged mail
#
# LICENSE
-# The Secure Mailer license must be distributed with this
+# The Secure Mailer license must be distributed with this
# software.
#
# AUTHOR(S)
kbytes and that supports the sequence operator. DBM databases are
not suitable because they can only store small objects. The cache
is maintained by the <a href="tlsmgr.8.html">tlsmgr(8)</a> process, so there is no problem with
-concurrent access. </p>
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high.</p>
<p> Example: </p>
kbytes and that supports the sequence operator. DBM databases are
not suitable because they can only store small objects. The cache
is maintained by the <a href="tlsmgr.8.html">tlsmgr(8)</a> process, so there is no problem with
-concurrent access. </p>
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high. Future
+Postfix SMTP servers may limit the number of sessions that a client
+is allowed to negotiate per unit time.</p>
<p> Example: </p>
line, immediately before the input that
triggered the <b>PREPEND</b> action.
+ <b>o</b> The prepended text is not considered part of
+ the input stream. Unlike the result from the
+ <b>REPLACE</b> action, prepended text is not sub-
+ ject to header/body checks or address
+ rewriting, and does not affect the way that
+ Postfix adds missing message headers.
+
<b>o</b> When prepending text before a message header
- line, the prepended text must begin with a
+ line, the prepended text must begin with a
valid message header label.
<b>o</b> This action cannot be used to prepend multi-
This feature is available in Postfix 2.1 and later.
<b>REDIRECT</b> <i>user@domain</i>
- Write a message redirection request to the queue
- file and inspect the next input line. After the
+ Write a message redirection request to the queue
+ file and inspect the next input line. After the
message is queued, it will be sent to the specified
address instead of the intended recipient(s).
- Note: this action overrides the <b>FILTER</b> action, and
- affects all recipients of the message. If multiple
- <b>REDIRECT</b> actions fire, only the last one is exe-
+ Note: this action overrides the <b>FILTER</b> action, and
+ affects all recipients of the message. If multiple
+ <b>REDIRECT</b> actions fire, only the last one is exe-
cuted.
This feature is available in Postfix 2.1 and later.
<b>REPLACE</b> <i>text...</i>
- Replace the current line with the specified text
+ Replace the current line with the specified text
and inspect the next input line.
- Note: when replacing a message header line, the
- replacement text must begin with a valid header
- label.
-
This feature is available in Postfix 2.2 and later.
+ The description below applies to Postfix 2.2.2 and
+ later.
+
+ Notes:
+
+ <b>o</b> When replacing a message header line, the
+ replacement text must begin with a valid
+ header label.
+
+ <b>o</b> The replaced text remains part of the input
+ stream. Unlike the result from the <b>PREPEND</b>
+ action, a replaced message header may be
+ subject to address rewriting and may affect
+ the way that Postfix adds missing message
+ headers.
<b>REJECT</b> <i>optional text...</i>
- Reject the entire message. Reply with <i>optional</i>
+ Reject the entire message. Reply with <i>optional</i>
<i>text...</i> when the optional text is specified, other-
wise reply with a generic error message.
- Note: this action disables further header or
- <a href="postconf.5.html#body_checks">body_checks</a> inspection of the current message and
+ Note: this action disables further header or
+ <a href="postconf.5.html#body_checks">body_checks</a> inspection of the current message and
affects all recipients.
Postfix version 2.3 and later support enhanced sta-
enhanced status code of "5.7.1".
<b>WARN</b> <i>optional text...</i>
- Log a warning with the <i>optional text...</i> (or log a
- generic message) and inspect the next input line.
+ Log a warning with the <i>optional text...</i> (or log a
+ generic message) and inspect the next input line.
This action is useful for debugging and for testing
a pattern before applying more drastic actions.
<b>BUGS</b>
- Many people overlook the main limitations of header and
- <a href="postconf.5.html#body_checks">body_checks</a> rules. These rules operate on one logical
- message header or one body line at a time, and a decision
- made for one line is not carried over to the next line.
+ Many people overlook the main limitations of header and
+ <a href="postconf.5.html#body_checks">body_checks</a> rules. These rules operate on one logical
+ message header or one body line at a time, and a decision
+ made for one line is not carried over to the next line.
If text in the message body is encoded (<a href="http://www.faqs.org/rfcs/rfc2045.html">RFC 2045</a>) then the
- rules have to specified for the encoded form. Likewise,
+ rules have to specified for the encoded form. Likewise,
when message headers are encoded (<a href="http://www.faqs.org/rfcs/rfc2047.html">RFC 2047</a>) then the rules
need to be specified for the encoded form.
- Message headers added by the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon itself are
+ Message headers added by the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon itself are
excluded from inspection. Examples of such message headers
are <b>From:</b>, <b>To:</b>, <b>Message-ID:</b>, <b>Date:</b>.
- Message headers deleted by the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon will be
+ Message headers deleted by the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon will be
examined before they are deleted. Examples are: <b>Bcc:, Con-</b>
<b>tent-Length:</b>, <b>Return-Path:</b>.
<b><a href="postconf.5.html#body_checks">body_checks</a></b>
Lookup tables with content filter rules for message
body lines. These filters see one physical line at
- a time, in chunks of at most <b>$<a href="postconf.5.html#line_length_limit">line_length_limit</a></b>
+ a time, in chunks of at most <b>$<a href="postconf.5.html#line_length_limit">line_length_limit</a></b>
bytes.
<b><a href="postconf.5.html#body_checks_size_limit">body_checks_size_limit</a></b>
- The amount of content per message body segment
+ The amount of content per message body segment
(attachment) that is subjected to <b>$<a href="postconf.5.html#body_checks">body_checks</a></b> fil-
tering.
<b><a href="postconf.5.html#nested_header_checks">nested_header_checks</a></b> (default: <b>$<a href="postconf.5.html#header_checks">header_checks</a></b>)
Lookup tables with content filter rules for message
- header lines: respectively, these are applied to
- the initial message headers (not including MIME
- headers), to the MIME headers anywhere in the mes-
- sage, and to the initial headers of attached mes-
+ header lines: respectively, these are applied to
+ the initial message headers (not including MIME
+ headers), to the MIME headers anywhere in the mes-
+ sage, and to the initial headers of attached mes-
sages.
- Note: these filters see one logical message header
- at a time, even when a message header spans multi-
- ple lines. Message headers that are longer than
+ Note: these filters see one logical message header
+ at a time, even when a message header spans multi-
+ ple lines. Message headers that are longer than
<b>$<a href="postconf.5.html#header_size_limit">header_size_limit</a></b> characters are truncated.
<b><a href="postconf.5.html#disable_mime_input_processing">disable_mime_input_processing</a></b>
- While receiving mail, give no special treatment to
- MIME related message headers; all text after the
+ While receiving mail, give no special treatment to
+ MIME related message headers; all text after the
initial message headers is considered to be part of
- the message body. This means that <b><a href="postconf.5.html#header_checks">header_checks</a></b> is
- applied to all the initial message headers, and
+ the message body. This means that <b><a href="postconf.5.html#header_checks">header_checks</a></b> is
+ applied to all the initial message headers, and
that <b><a href="postconf.5.html#body_checks">body_checks</a></b> is applied to the remainder of the
message.
- Note: when used in this manner, <b><a href="postconf.5.html#body_checks">body_checks</a></b> will
- process a multi-line message header one line at a
+ Note: when used in this manner, <b><a href="postconf.5.html#body_checks">body_checks</a></b> will
+ process a multi-line message header one line at a
time.
<b>EXAMPLES</b>
- Header pattern to block attachments with bad file name
+ Header pattern to block attachments with bad file name
extensions.
/etc/postfix/main.cf:
<a href="BACKSCATTER_README.html">BACKSCATTER_README</a>, blocking returned forged mail
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>AUTHOR(S)</b>
The prepended text is output on a separate line, immediately
before the input that triggered the \fBPREPEND\fR action.
.IP \(bu
+The prepended text is not considered part of the input
+stream. Unlike the result from the \fBREPLACE\fR action,
+prepended text is not subject to header/body checks or
+address rewriting, and does not affect the way that Postfix
+adds missing message headers.
+.IP \(bu
When prepending text before a message header line, the prepended
text must begin with a valid message header label.
.IP \(bu
Replace the current line with the specified text and inspect the next
input line.
.sp
-Note: when replacing a message header line, the replacement text
-must begin with a valid header label.
+This feature is available in Postfix 2.2 and later. The
+description below applies to Postfix 2.2.2 and later.
.sp
-This feature is available in Postfix 2.2 and later.
+Notes:
+.RS
+.IP \(bu
+When replacing a message header line, the replacement text
+must begin with a valid header label.
+.IP \(bu
+The replaced text remains part of the input stream. Unlike
+the result from the \fBPREPEND\fR action, a replaced message
+header may be subject to address rewriting and may affect
+the way that Postfix adds missing message headers.
+.RE
.IP "\fBREJECT \fIoptional text...\fR
Reject the entire message. Reply with \fIoptional text...\fR when
the optional text is specified, otherwise reply with a generic error
kbytes and that supports the sequence operator. DBM databases are
not suitable because they can only store small objects. The cache
is maintained by the tlsmgr(8) process, so there is no problem with
-concurrent access. </p>
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high.</p>
<p> Example: </p>
kbytes and that supports the sequence operator. DBM databases are
not suitable because they can only store small objects. The cache
is maintained by the tlsmgr(8) process, so there is no problem with
-concurrent access. </p>
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high. Future
+Postfix SMTP servers may limit the number of sessions that a client
+is allowed to negotiate per unit time.</p>
<p> Example: </p>
# The prepended text is output on a separate line, immediately
# before the input that triggered the \fBPREPEND\fR action.
# .IP \(bu
+# The prepended text is not considered part of the input
+# stream. Unlike the result from the \fBREPLACE\fR action,
+# prepended text is not subject to header/body checks or
+# address rewriting, and does not affect the way that Postfix
+# adds missing message headers.
+# .IP \(bu
# When prepending text before a message header line, the prepended
# text must begin with a valid message header label.
# .IP \(bu
# Replace the current line with the specified text and inspect the next
# input line.
# .sp
-# Note: when replacing a message header line, the replacement text
-# must begin with a valid header label.
+# This feature is available in Postfix 2.2 and later. The
+# description below applies to Postfix 2.2.2 and later.
# .sp
-# This feature is available in Postfix 2.2 and later.
+# Notes:
+# .RS
+# .IP \(bu
+# When replacing a message header line, the replacement text
+# must begin with a valid header label.
+# .IP \(bu
+# The replaced text remains part of the input stream. Unlike
+# the result from the \fBPREPEND\fR action, a replaced message
+# header may be subject to address rewriting and may affect
+# the way that Postfix adds missing message headers.
+# .RE
# .IP "\fBREJECT \fIoptional text...\fR
# Reject the entire message. Reply with \fIoptional text...\fR when
# the optional text is specified, otherwise reply with a generic error
if (state->errs != 0) {
if (CAN_BOUNCE()) {
- if (state->reason)
+ if (state->reason)
dsn_split(&dp, "5.0.0", state->reason);
else
detail = cleanup_stat_detail(state->errs);
state->recip ? state->recip : "unknown",
state->recip ? state->recip : "unknown",
(long) 0, "none",
- detail ? detail->dsn : dp.dsn,
- state->time, "%s",
+ detail ? detail->dsn : DSN_CODE(dp.dsn),
+ state->time, "%s",
detail ? detail->text : dp.text) == 0
&& bounce_flush(BOUNCE_FLAG_CLEAN, state->queue_name,
state->queue_id,
/* cleanup_act - act upon a header/body match */
-static int cleanup_act(CLEANUP_STATE *state, char *context, const char *buf,
- const char *value, const char *map_class)
+static const char *cleanup_act(CLEANUP_STATE *state, char *context,
+ const char *buf, const char *value,
+ const char *map_class)
{
const char *optional_text = value + strcspn(value, " \t");
int command_len = optional_text - value;
- VSTRING *bp;
- CLEANUP_STAT_DETAIL *detail;
while (*optional_text && ISSPACE(*optional_text))
optional_text++;
#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
-#define CLEANUP_ACT_KEEP 1
#define CLEANUP_ACT_DROP 0
if (STREQUAL(value, "REJECT", command_len)) {
+ CLEANUP_STAT_DETAIL *detail;
+
if (state->reason == 0) {
if (*optional_text) {
state->reason = dsn_prepend("5.7.1", optional_text);
state->errs |= CLEANUP_STAT_CONT;
state->flags &= ~CLEANUP_FLAG_FILTER;
cleanup_act_log(state, "reject", context, buf, state->reason);
- return (CLEANUP_ACT_KEEP);
+ return (buf);
}
if (STREQUAL(value, "WARN", command_len)) {
cleanup_act_log(state, "warning", context, buf, optional_text);
- return (CLEANUP_ACT_KEEP);
+ return (buf);
}
if (STREQUAL(value, "FILTER", command_len)) {
if (*optional_text == 0) {
msg_warn("missing FILTER command argument in %s map", map_class);
} else if (strchr(optional_text, ':') == 0) {
- msg_warn("bad FILTER command %s in %s, need transport:destination",
+ msg_warn("bad FILTER command %s in %s -- "
+ "need transport:destination",
optional_text, map_class);
} else {
if (state->filter)
state->filter = mystrdup(optional_text);
cleanup_act_log(state, "filter", context, buf, optional_text);
}
- return (CLEANUP_ACT_KEEP);
+ return (buf);
}
if (STREQUAL(value, "DISCARD", command_len)) {
cleanup_act_log(state, "discard", context, buf, optional_text);
state->flags |= CLEANUP_FLAG_DISCARD;
state->flags &= ~CLEANUP_FLAG_FILTER;
- return (CLEANUP_ACT_KEEP);
+ return (buf);
}
if (STREQUAL(value, "HOLD", command_len)) {
cleanup_act_log(state, "hold", context, buf, optional_text);
state->flags |= CLEANUP_FLAG_HOLD;
- return (CLEANUP_ACT_KEEP);
+ return (buf);
}
if (STREQUAL(value, "PREPEND", command_len)) {
if (*optional_text == 0) {
msg_warn("PREPEND action without text in %s map", map_class);
} else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0
&& !is_header(optional_text)) {
- msg_warn("bad PREPEND header text \"%s\" in %s map, "
+ msg_warn("bad PREPEND header text \"%s\" in %s map -- "
"need \"headername: headervalue\"",
optional_text, map_class);
} else {
cleanup_act_log(state, "prepend", context, buf, optional_text);
cleanup_out_string(state, REC_TYPE_NORM, optional_text);
}
- return (CLEANUP_ACT_KEEP);
+ return (buf);
}
if (STREQUAL(value, "REPLACE", command_len)) {
if (*optional_text == 0) {
msg_warn("REPLACE action without text in %s map", map_class);
- return (CLEANUP_ACT_KEEP);
- } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0) {
- if (!is_header(optional_text)) {
- msg_warn("bad REPLACE header text \"%s\" in %s map, "
- "need \"headername: headervalue\"",
- optional_text, map_class);
- return (CLEANUP_ACT_KEEP);
- }
- /* XXX Impedance mismatch. */
- bp = vstring_strcpy(vstring_alloc(100), optional_text);
- cleanup_out_header(state, bp);
- vstring_free(bp);
+ return (buf);
+ } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0
+ && !is_header(optional_text)) {
+ msg_warn("bad REPLACE header text \"%s\" in %s map -- "
+ "need \"headername: headervalue\"",
+ optional_text, map_class);
+ return (buf);
} else {
- cleanup_out_string(state, REC_TYPE_NORM, optional_text);
+ cleanup_act_log(state, "replace", context, buf, optional_text);
+ return (mystrdup(optional_text));
}
- cleanup_act_log(state, "replace", context, buf, optional_text);
- return (CLEANUP_ACT_DROP);
}
if (STREQUAL(value, "REDIRECT", command_len)) {
if (strchr(optional_text, '@') == 0) {
- msg_warn("bad REDIRECT target \"%s\" in %s map, need user@domain",
+ msg_warn("bad REDIRECT target \"%s\" in %s map -- "
+ "need user@domain",
optional_text, map_class);
} else {
if (state->redirect)
cleanup_act_log(state, "redirect", context, buf, optional_text);
state->flags &= ~CLEANUP_FLAG_FILTER;
}
- return (CLEANUP_ACT_KEEP);
+ return (buf);
}
/* Allow and ignore optional text after the action. */
return (CLEANUP_ACT_DROP);
if (STREQUAL(value, "DUNNO", command_len)) /* preferred */
- return (CLEANUP_ACT_KEEP);
+ return (buf);
if (STREQUAL(value, "OK", command_len)) /* compat */
- return (CLEANUP_ACT_KEEP);
+ return (buf);
msg_warn("unknown command in %s map: %s", map_class, value);
- return (CLEANUP_ACT_KEEP);
+ return (buf);
}
/* cleanup_header_callback - process one complete header line */
const char *value;
if ((value = maps_find(checks, header, 0)) != 0) {
- if (cleanup_act(state, CLEANUP_ACT_CTXT_HEADER,
- header, value, map_class)
- == CLEANUP_ACT_DROP)
+ const char *result;
+
+ if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_HEADER,
+ header, value, map_class))
+ == CLEANUP_ACT_DROP) {
return;
+ } else if (result != header) {
+ vstring_strcpy(header_buf, result);
+ hdr_opts = header_opts_find(result);
+ myfree((char *) result);
+ }
}
}
const char *value;
if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) {
- if (cleanup_act(state, CLEANUP_ACT_CTXT_BODY,
- buf, value, VAR_BODY_CHECKS)
- == CLEANUP_ACT_DROP)
+ const char *result;
+
+ if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_BODY,
+ buf, value, VAR_BODY_CHECKS))
+ == CLEANUP_ACT_DROP) {
+ return;
+ } else if (result != buf) {
+ cleanup_out(state, type, result, strlen(result));
+ myfree((char *) result);
return;
+ }
}
}
cleanup_out(state, type, buf, len);
if (rcpt->offset >= 0) {
status = sent(BOUNCE_FLAGS(request), request->queue_id,
rcpt->orig_addr, rcpt->address, rcpt->offset,
- "none", dp.dsn, request->arrival_time,
- "%s", dp.text);
+ "none", DSN_CODE(dp.dsn),
+ request->arrival_time, "%s", dp.text);
if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS))
deliver_completed(src, rcpt->offset);
result |= status;
if (rcpt->offset >= 0) {
status = bounce_append(BOUNCE_FLAGS(request), request->queue_id,
rcpt->orig_addr, rcpt->address,
- rcpt->offset, "none", dp.dsn,
+ rcpt->offset, "none", DSN_CODE(dp.dsn),
request->arrival_time,
"%s", dp.text);
if (status == 0)
/* SYNOPSIS
/* #include <dsn_util.h>
/*
+/* #define DSN_SIZE ...
+/*
+/* typedef struct { ... } DSN_BUF;
+/*
+/* void DSN_UPDATE(dsn_buf, dsn, len)
+/* DSN_BUF dsn_buf;
+/* const char *dsn;
+/* size_t len;
+/*
+/* const char *DSN_CODE(dsn_buf)
+/* DSN_BUF dsn_buf;
+/*
+/* char *DSN_CLASS(dsn_buf)
+/* DSN_BUF dsn_buf;
+/*
/* typedef struct {
/* .in +4
-/* char dsn[...]; /* RFC 3463 */
+/* DSN_BUF dsn; /* RFC 3463 detail */
/* const char *text; /* Free text */
/* .in -4
/* } DSN_SPLIT;
/*
/* typedef struct {
/* .in +4
-/* char dsn[...]; /* RFC 3463 */
+/* DSN_BUF dsn; /* RFC 3463 detail */
/* VSTRING *text; /* Free text */
/* .in -4
/* } DSN_VSTRING;
/* dsn_vstring_free() recycles the storage that was allocated
/* by dsn_vstring_alloc() and dsn_vstring_update().
/*
+/* DSN_UPDATE() is a helper macro to safely update an
+/* RFC 3463 detail code.
+/*
+/* DSN_CODE() is a helper macro to safely read an
+/* RFC 3463 detail code.
+/*
+/* DSN_CLASS() is a helper macro to safely read or update an
+/* RFC 3463 detail code class (i.e. the first digit).
+/*
+/* DSN_SIZE is the maximal length of an enhanced status
+/* code including the null string terminator.
+/*
/* dsn_valid() returns the length of the RFC 3463 detail code
/* at the beginning of text, or zero. It does not skip initial
/* whitespace.
while (ISSPACE(*cp))
cp++;
if ((len = dsn_valid(cp)) > 0) {
- if (len >= sizeof(dp->dsn))
- msg_panic("dsn_split: bad DSN code length %d", len);
- DSN_BUF_UPDATE(dp->dsn, cp, len);
+ DSN_UPDATE(dp->dsn, cp, len);
cp += len + 1;
} else {
len = strlen(def_dsn);
- if (len >= sizeof(dp->dsn))
- msg_panic("dsn_split: bad default DSN code length %d", len);
- DSN_BUF_UPDATE(dp->dsn, def_dsn, len);
+ DSN_UPDATE(dp->dsn, def_dsn, len);
}
/*
DSN_SPLIT dp;
dsn_split(&dp, def_dsn, text);
- return (concatenate(dp.dsn, " ", dp.text, (char *) 0));
+ return (concatenate(DSN_CODE(dp.dsn), " ", dp.text, (char *) 0));
}
/* dsn_vstring_alloc - create DSN+string storage */
DSN_VSTRING *dv;
dv = (DSN_VSTRING *) mymalloc(sizeof(*dv));
- dv->dsn[0] = 0;
+ DSN_CLASS(dv->dsn) = 0;
dv->vstring = vstring_alloc(len);
return(dv);
}
size_t len;
if (dsn && *dsn) {
- if ((len = dsn_valid(dsn)) == 0 || len >= sizeof(dv->dsn))
+ if ((len = dsn_valid(dsn)) == 0)
msg_panic("dsn_vstring_update: bad dsn: \"%s\"", dsn);
- DSN_BUF_UPDATE(dv->dsn, dsn, len);
+ DSN_UPDATE(dv->dsn, dsn, len);
}
if (format && *format) {
va_start(ap, format);
#define DSN_DIGS2 3 /* middle digits */
#define DSN_DIGS3 3 /* trailing digits */
#define DSN_LEN (DSN_DIGS1 + 1 + DSN_DIGS2 + 1 + DSN_DIGS3)
-#define DSN_BUFSIZE (DSN_LEN + 1)
+#define DSN_SIZE (DSN_LEN + 1)
+
+ /*
+ * Storage for an enhanced status code. Avoid using malloc for itty-bitty
+ * strings with a known size limit.
+ */
+typedef struct {
+ char data[DSN_SIZE]; /* NOT a public interface */
+} DSN_BUF;
+
+#define DSN_UPDATE(dsn_buf, dsn, len) do { \
+ if (len >= sizeof((dsn_buf).data)) \
+ msg_panic("DSN_UPDATE: bad DSN code \"%.*s...\" length %d", \
+ sizeof((dsn_buf).data) - 1, dsn, len); \
+ strncpy((dsn_buf).data, (dsn), (len)); \
+ (dsn_buf).data[len] = 0; \
+ } while (0)
+
+#define DSN_CODE(dsn_buf) ((const char *) (dsn_buf).data)
+
+#define DSN_CLASS(dsn_buf) ((dsn_buf).data[0])
/*
* Split flat text into detail code and free text.
*/
typedef struct {
- char dsn[DSN_BUFSIZE]; /* RFC 3463 X.XXX.XXX detail */
+ DSN_BUF dsn; /* RFC 3463 X.XXX.XXX detail */
const char *text; /* free text */
} DSN_SPLIT;
-#define DSN_BUF_UPDATE(buf, text, len) do { \
- strncpy((buf), (text), (len)); \
- (buf)[len] = 0; \
- } while (0)
-
extern DSN_SPLIT *dsn_split(DSN_SPLIT *, const char *, const char *);
extern size_t dsn_valid(const char *);
* Easy to update pair of detail code and free text.
*/
typedef struct {
- char dsn[DSN_LEN + 1]; /* RFC 3463 X.XXX.XXX detail */
+ DSN_BUF dsn; /* RFC 3463 X.XXX.XXX detail */
VSTRING *vstring; /* free text */
} DSN_VSTRING;
* Patches change the patchlevel and the release date. Snapshots change the
* release date only.
*/
-#define MAIL_RELEASE_DATE "20050328"
+#define MAIL_RELEASE_DATE "20050329"
#define MAIL_VERSION_NUMBER "2.3"
#define VAR_MAIL_VERSION "mail_version"
else if (dsn_valid(log_buf) > 0) {
/* XXX Assumes dsn_split() does not require 5.x.x in log_buf */
dsn_split(&dp, "5.3.0", log_buf);
- dsn_vstring_update(why, dp.dsn, "%s", dp.text);
- return (dp.dsn[0] == '4' ?
+ dsn_vstring_update(why, DSN_CODE(dp.dsn), "%s", dp.text);
+ return (DSN_CLASS(dp.dsn) == '4' ?
PIPE_STAT_DEFER : PIPE_STAT_BOUNCE);
}
/* Use <sysexits.h> compatible exit status. */
*/
if ((state->session = lmtp_connect(request->nexthop, why)) == 0) {
if (lmtp_errno == LMTP_RETRY) {
- why->dsn[0] = '4';
- lmtp_site_fail(state, why->dsn, 450,
+ DSN_CLASS(why->dsn) = '4';
+ lmtp_site_fail(state, DSN_CODE(why->dsn), 450,
"%s", vstring_str(why->vstring));
} else {
- why->dsn[0] = '5';
- lmtp_site_fail(state, why->dsn, 550,
+ DSN_CLASS(why->dsn) = '5';
+ lmtp_site_fail(state, DSN_CODE(why->dsn), 550,
"%s", vstring_str(why->vstring));
}
}
*/
typedef struct LMTP_RESP { /* server response */
int code; /* status */
- char dsn[DSN_BUFSIZE]; /* DSN detail */
+ DSN_BUF dsn; /* DSN detail */
char *str; /* text */
VSTRING *buf; /* origin of text */
} LMTP_RESP;
* Extract RFC 821 reply code and RFC 2034 detail code. Use a default
* detail code if none was given.
*/
- rdata.dsn[0] = 0;
+ DSN_CLASS(rdata.dsn) = 0;
if (three_digs != 0) {
rdata.code = atoi(STR(state->buffer));
for (cp = STR(state->buffer) + 4; *cp == ' '; cp++)
/* void */ ;
- if ((len = dsn_valid(cp)) > 0 && len < sizeof(rdata.dsn)) {
- DSN_BUF_UPDATE(rdata.dsn, cp, len);
+ if ((len = dsn_valid(cp)) > 0 && len < sizeof(DSN_SIZE)) {
+ DSN_UPDATE(rdata.dsn, cp, len);
} else if (strchr("245", STR(state->buffer)[0]) != 0) {
- DSN_BUF_UPDATE(rdata.dsn, "0.0.0", sizeof("0.0.0") - 1);
- rdata.dsn[0] = STR(state->buffer)[0];
+ DSN_UPDATE(rdata.dsn, "0.0.0", sizeof("0.0.0") - 1);
+ DSN_CLASS(rdata.dsn) = STR(state->buffer)[0];
}
} else {
rdata.code = 0;
* Read and parse the server's LMTP greeting banner.
*/
if (((resp = lmtp_chat_resp(state))->code / 100) != 2)
- return (lmtp_site_fail(state, resp->dsn, resp->code,
+ return (lmtp_site_fail(state, DSN_CODE(resp->dsn), resp->code,
"host %s refused to talk to me: %s",
session->namaddr, translit(resp->str, "\n", " ")));
*/
lmtp_chat_cmd(state, "LHLO %s", var_myhostname);
if ((resp = lmtp_chat_resp(state))->code / 100 != 2)
- return (lmtp_site_fail(state, resp->dsn, resp->code,
+ return (lmtp_site_fail(state, DSN_CODE(resp->dsn), resp->code,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
*/
case LMTP_STATE_MAIL:
if (resp->code / 100 != 2) {
- lmtp_mesg_fail(state, resp->dsn, resp->code,
+ lmtp_mesg_fail(state, DSN_CODE(resp->dsn),
+ resp->code,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
&& sent(DEL_REQ_TRACE_FLAGS(request->flags),
request->queue_id, rcpt->orig_addr,
rcpt->address, rcpt->offset,
- session->namaddr, resp->dsn,
+ session->namaddr,
+ DSN_CODE(resp->dsn),
request->arrival_time, "%s",
translit(resp->str, "\n", " ")) == 0) {
if (request->flags & DEL_REQ_FLAG_SUCCESS)
rcpt->offset = 0; /* in case deferred */
}
} else {
- lmtp_rcpt_fail(state, resp->dsn,
+ lmtp_rcpt_fail(state, DSN_CODE(resp->dsn),
resp->code, rcpt,
"host %s said: %s (in reply to %s)",
session->namaddr,
case LMTP_STATE_DATA:
if (resp->code / 100 != 3) {
if (nrcpt > 0)
- lmtp_mesg_fail(state, resp->dsn, resp->code,
+ lmtp_mesg_fail(state, DSN_CODE(resp->dsn),
+ resp->code,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
if (sent(DEL_REQ_TRACE_FLAGS(request->flags),
request->queue_id, rcpt->orig_addr,
rcpt->address, rcpt->offset,
- session->namaddr, resp->dsn,
+ session->namaddr,
+ DSN_CODE(resp->dsn),
request->arrival_time,
"%s", resp->str) == 0) {
if (request->flags & DEL_REQ_FLAG_SUCCESS)
}
}
} else {
- lmtp_rcpt_fail(state, resp->dsn,
+ lmtp_rcpt_fail(state, DSN_CODE(resp->dsn),
resp->code, rcpt,
"host %s said: %s (in reply to %s)",
session->namaddr,
break;
case PIPE_STAT_BOUNCE:
case PIPE_STAT_DEFER:
- deliver_status = (why->dsn[0] == '4' ? defer_append : bounce_append)
+ deliver_status = (DSN_CLASS(why->dsn) == '4' ?
+ defer_append : bounce_append)
(BOUNCE_FLAGS(state.request),
- BOUNCE_ATTR(state.msg_attr, why->dsn),
+ BOUNCE_ATTR(state.msg_attr, DSN_CODE(why->dsn)),
"%s", vstring_str(why->vstring));
break;
case PIPE_STAT_CORRUPT:
if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
deliver_status = DEL_STAT_DEFER;
} else if (mail_copy_status != 0) {
- deliver_status = (why->dsn[0] == '4' ? defer_append : bounce_append)
+ deliver_status = (DSN_CLASS(why->dsn) == '4' ?
+ defer_append : bounce_append)
(BOUNCE_FLAGS(state.request),
- BOUNCE_ATTR(state.msg_attr, why->dsn),
+ BOUNCE_ATTR(state.msg_attr, DSN_CODE(why->dsn)),
"cannot append message to destination file %s: %s",
path, vstring_str(why->vstring));
} else {
if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
deliver_status = DEL_STAT_DEFER;
} else if (mail_copy_status != 0) {
- deliver_status = (why->dsn[0] == '4' ? defer_append : bounce_append)
+ deliver_status = (DSN_CLASS(why->dsn) == '4' ?
+ defer_append : bounce_append)
(BOUNCE_FLAGS(state.request),
- BOUNCE_ATTR(state.msg_attr, why->dsn),
+ BOUNCE_ATTR(state.msg_attr, DSN_CODE(why->dsn)),
"cannot update mailbox %s for user %s. %s",
mailbox, state.msg_attr.user, vstring_str(why->vstring));
} else {
if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
deliver_status = DEL_STAT_DEFER;
} else if (mail_copy_status != 0) {
- deliver_status = (why->dsn[0] == '4' ? defer_append : bounce_append)
+ deliver_status = (DSN_CLASS(why->dsn) == '4' ?
+ defer_append : bounce_append)
(BOUNCE_FLAGS(state.request),
- BOUNCE_ATTR(state.msg_attr, why->dsn),
+ BOUNCE_ATTR(state.msg_attr, DSN_CODE(why->dsn)),
"maildir delivery failed: %s", vstring_str(why->vstring));
if (errno == EACCES) {
msg_warn("maildir access problem for UID/GID=%lu/%lu: %s",
if (VSTRING_LEN(reason)) {
/* Sanitize the DSN status from delivery agent. */
dsn_split(&dp, "4.0.0", printable(vstring_str(reason), '?'));
- qmgr_queue_throttle(queue, dp.dsn, *dp.text ?
+ qmgr_queue_throttle(queue, DSN_CODE(dp.dsn), *dp.text ?
dp.text : "unknown problem");
if (queue->window == 0)
qmgr_defer_todo(queue, queue->dsn, queue->reason);
argv_free(export_env);
deliver_status = eval_command_status(command_status, service, request,
- request->fp, why->dsn,
+ request->fp, DSN_CODE(why->dsn),
vstring_str(why->vstring));
/*
if (VSTRING_LEN(reason)) {
/* Sanitize the DSN status from delivery agent. */
dsn_split(&dp, "4.0.0", printable(vstring_str(reason), '?'));
- qmgr_queue_throttle(queue, dp.dsn, *dp.text ?
+ qmgr_queue_throttle(queue, DSN_CODE(dp.dsn), *dp.text ?
dp.text : "unknown problem");
if (queue->window == 0)
qmgr_defer_todo(queue, queue->dsn, queue->reason);
*/
typedef struct SMTP_RESP { /* server response */
int code; /* status */
- char dsn[DSN_BUFSIZE]; /* DSN detail */
+ DSN_BUF dsn; /* DSN detail */
char *str; /* text */
VSTRING *buf; /* origin of text */
} SMTP_RESP;
* Extract RFC 821 reply code and RFC 2034 detail. Use a default detail
* code if none was given.
*/
- rdata.dsn[0] = 0;
+ DSN_CLASS(rdata.dsn) = 0;
if (three_digs != 0) {
rdata.code = atoi(STR(session->buffer));
for (cp = STR(session->buffer) + 4; *cp == ' '; cp++)
/* void */ ;
- if ((len = dsn_valid(cp)) > 0 && len < sizeof(rdata.dsn)) {
- DSN_BUF_UPDATE(rdata.dsn, cp, len);
+ if ((len = dsn_valid(cp)) > 0 && len < sizeof(DSN_SIZE)) {
+ DSN_UPDATE(rdata.dsn, cp, len);
} else if (strchr("245", STR(session->buffer)[0]) != 0) {
- DSN_BUF_UPDATE(rdata.dsn, "0.0.0", sizeof("0.0.0") - 1);
- rdata.dsn[0] = STR(session->buffer)[0];
+ DSN_UPDATE(rdata.dsn, "0.0.0", sizeof("0.0.0") - 1);
+ DSN_CLASS(rdata.dsn) = STR(session->buffer)[0];
}
} else {
rdata.code = 0;
*/
state->final_server = 1; /* XXX */
if (smtp_errno == SMTP_ERR_RETRY) {
- why->dsn[0] = '4';
- smtp_site_fail(state, why->dsn, 450, "%s", STR(why->vstring));
+ DSN_CLASS(why->dsn) = '4';
+ smtp_site_fail(state, DSN_CODE(why->dsn), 450,
+ "%s", STR(why->vstring));
} else {
- why->dsn[0] = '5';
- smtp_site_fail(state, why->dsn, 550, "%s", STR(why->vstring));
+ DSN_CLASS(why->dsn) = '5';
+ smtp_site_fail(state, DSN_CODE(why->dsn), 550,
+ "%s", STR(why->vstring));
}
/*
case 5:
if (var_smtp_skip_5xx_greeting) {
resp->code = 400;
- resp->dsn[0] = '4';
+ DSN_CLASS(resp->dsn) = '4';
}
default:
- return (smtp_site_fail(state, resp->dsn, resp->code,
+ return (smtp_site_fail(state, DSN_CODE(resp->dsn), resp->code,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
if ((session->features & SMTP_FEATURE_ESMTP) == 0) {
smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name);
if ((resp = smtp_chat_resp(session))->code / 100 != 2)
- return (smtp_site_fail(state, resp->dsn, resp->code,
+ return (smtp_site_fail(state, DSN_CODE(resp->dsn), resp->code,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
*/
session->features &= ~SMTP_FEATURE_STARTTLS;
if (session->tls_enforce_tls)
- return (smtp_site_fail(state, resp->dsn, resp->code,
+ return (smtp_site_fail(state, DSN_CODE(resp->dsn),
+ resp->code,
"TLS is required, but host %s refused to start TLS: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
* use TLS session caching???
*/
serverid = vstring_alloc(10);
- vstring_sprintf(serverid, "%s:%u", session->addr, ntohs(session->port));
- if (session->helo != 0)
- vstring_sprintf_append(serverid, ":%s", session->helo);
+ vstring_sprintf(serverid, "%s:%u:%s", session->addr,
+ ntohs(session->port), session->helo ? session->helo : "");
session->tls_context =
tls_client_start(smtp_tls_ctx, session->stream,
var_smtp_starttls_tmout,
return (smtp_site_fail(state, "4.7.5", 450,
"Cannot start TLS: handshake failure"));
- /*
- * Give up when TLS is required, we can parse the server certificate's
- * CommonName field, but server certificate verification failed.
- *
- * In enforce_peername state, the handshake would already have been
- * terminated by the certificate verification call-back routine, so the
- * check here is for logging only.
- *
- * XXX It appears that the CommonName field is used as an indicator that a
- * server certificate is available. If the latter is what we want, then
- * we should test for that instead.
- */
- if (session->tls_info.peer_CN != NULL) {
- if (!session->tls_info.peer_verified) {
- msg_info("Server certificate could not be verified");
- if (session->tls_enforce_tls) {
- tls_client_stop(smtp_tls_ctx, session->stream,
- var_smtp_starttls_tmout, 1,
- &(session->tls_info));
- return (smtp_site_fail(state, "4.7.5", 450,
- "TLS failure: Cannot verify server certificate"));
- }
- }
- }
-
- /*
- * Give up when TLS is required but no server certificate is available
- * (or we could not parse the certificate's CommonName) field.
- *
- * XXX The test below is not accurate: the server hostname verification may
- * use the dNSNames instead of the CommonName. We really should be
- * testing if a certificate is available.
- */
- else {
- if (session->tls_enforce_tls) {
- tls_client_stop(smtp_tls_ctx, session->stream,
- var_smtp_starttls_tmout, 1,
- &(session->tls_info));
- return (smtp_site_fail(state, "4.7.5", 450,
- "TLS failure: Cannot verify server hostname"));
- }
- }
-
/*
* At this point we have to re-negotiate the "EHLO" to reget the
* feature-list.
*/
case SMTP_STATE_MAIL:
if (resp->code / 100 != 2) {
- smtp_mesg_fail(state, resp->dsn, resp->code,
+ smtp_mesg_fail(state, DSN_CODE(resp->dsn), resp->code,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
#ifdef notdef
if (resp->code == 552) {
resp->code = 452;
- resp->dsn[0] = '4';
+ DSN_CLASS(resp->dsn) = '4';
}
#endif
rcpt = request->rcpt_list.info + recv_rcpt;
++nrcpt;
/* If trace-only, mark the recipient done. */
if (DEL_REQ_TRACE_ONLY(request->flags))
- smtp_rcpt_done(state, resp->dsn,
+ smtp_rcpt_done(state, DSN_CODE(resp->dsn),
resp->str, rcpt);
} else {
- smtp_rcpt_fail(state, resp->dsn,
+ smtp_rcpt_fail(state, DSN_CODE(resp->dsn),
resp->code, rcpt,
"host %s said: %s (in reply to %s)",
session->namaddr,
case SMTP_STATE_DATA:
if (resp->code / 100 != 3) {
if (nrcpt > 0)
- smtp_mesg_fail(state, resp->dsn, resp->code,
+ smtp_mesg_fail(state, DSN_CODE(resp->dsn),
+ resp->code,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
case SMTP_STATE_DOT:
if (nrcpt > 0) {
if (resp->code / 100 != 2) {
- smtp_mesg_fail(state, resp->dsn, resp->code,
+ smtp_mesg_fail(state, DSN_CODE(resp->dsn),
+ resp->code,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
rcpt = request->rcpt_list.info + nrcpt;
if (!SMTP_RCPT_ISMARKED(rcpt))
- smtp_rcpt_done(state, resp->dsn,
+ smtp_rcpt_done(state, DSN_CODE(resp->dsn),
resp->str, rcpt);
}
}
if (STREQUAL(value, "REJECT", cmd_len)) {
dsn_split(&dp, "5.7.1", cmd_text);
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
- var_access_map_code, dp.dsn,
+ var_access_map_code, DSN_CODE(dp.dsn),
"<%s>: %s rejected: %s",
reply_name, reply_class,
*dp.text ? dp.text : "Access denied"));
if (STREQUAL(value, DEFER_IF_PERMIT, cmd_len)) {
dsn_split(&dp, "4.7.1", cmd_text);
DEFER_IF_PERMIT3(state, MAIL_ERROR_POLICY,
- 450, dp.dsn,
+ 450, DSN_CODE(dp.dsn),
"<%s>: %s rejected: %s",
reply_name, reply_class,
*dp.text ? dp.text : "Service unavailable");
if (STREQUAL(value, DEFER_IF_REJECT, cmd_len)) {
dsn_split(&dp, "4.7.1", cmd_text);
DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
- 450, dp.dsn,
+ 450, DSN_CODE(dp.dsn),
"<%s>: %s rejected: %s",
reply_name, reply_class,
*dp.text ? dp.text : "Service unavailable");
def_dsn[0] = value[0];
dsn_split(&dp, def_dsn, cmd_text);
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
- code, dp.dsn,
+ code, DSN_CODE(dp.dsn),
"<%s>: %s rejected: %s",
- reply_name, reply_class, dp.text));
+ reply_name, reply_class,
+ *dp.text ? dp.text : "Access denied"));
}
/*
code = atoi(STR(why));
dsn_split(&dp, "4.7.1", STR(why) + 4);
result = smtpd_check_reject(state, MAIL_ERROR_POLICY,
- code, dp.dsn, "%s", *dp.text ?
+ code, DSN_CODE(dp.dsn),
+ "%s", *dp.text ?
dp.text : "Service unavailable");
}
char issuer_CN[CCERT_BUFSIZ];
unsigned char md[EVP_MAX_MD_SIZE];
char fingerprint[EVP_MAX_MD_SIZE * 3];
- char peername_save[HOST_BUFSIZ + 1];
+ char *peername;
int enforce_verify_errors;
int enforce_CN;
int hostname_matched;
#define TLS_BIO_BUFSIZE 8192
-#define NEW_TLS_CONTEXT(p) do { \
- p = (TLScontext_t *) mymalloc(sizeof(*p)); \
- memset((char *) p, 0, sizeof(*p)); \
- p->serverid = 0; \
- } while (0)
-
-#define FREE_TLS_CONTEXT(p) do { \
- if ((p)->serverid) \
- myfree((p)->serverid); \
- myfree((char *) (p)); \
- } while (0)
-
typedef struct {
int peer_verified;
int hostname_matched;
/*
* tls_verify.c
*/
-extern int tls_verify_certificate_callback(int, X509_STORE_CTX *, int);
-
-#define TLS_VERIFY_DEFAULT (0)
-#define TLS_VERIFY_PEERNAME (1<<0)
+extern int tls_verify_certificate_callback(int, X509_STORE_CTX *);
/*
* tls_certkey.c
*/
extern int TLScontext_index;
+extern TLScontext_t *tls_alloc_context(int, const char *);
+extern void tls_free_context(TLScontext_t *);
extern void tls_print_errors(void);
extern void tls_info_callback(const SSL *, int, int);
extern long tls_bio_dump_cb(BIO *, int, const char *, int, long, long);
/* SSL_CTX *tls_client_init(verifydepth)
/* int verifydepth; /* unused */
/*
-/* TLScontext_t *tls_client_start(client_ctx, stream, timeout,
-/* enforce_peername, peername,
+/* TLScontext_t *tls_client_start(client_ctx, stream, timeout,
+/* enforce_peername, peername,
/* serverid, tls_info)
/* SSL_CTX *client_ctx;
/* VSTREAM *stream;
*/
static int tls_client_cache = 0;
-/* client_verify_callback - certificate verification wrapper */
-
-static int client_verify_callback(int ok, X509_STORE_CTX *ctx)
-{
- return (tls_verify_certificate_callback(ok, ctx, TLS_VERIFY_PEERNAME));
-}
-
/* load_clnt_session - load session from client cache (non-callback) */
-static SSL_SESSION *load_clnt_session(const char *cache_id,
- int enforce_peername)
+static SSL_SESSION *load_clnt_session(const char *cache_id)
{
SSL_SESSION *session = 0;
VSTRING *session_data = vstring_alloc(2048);
- int flags = 0;
-
-#define TLS_FLAG_ENFORCE_PEERNAME (1<<0)
/*
* Prepare the query.
*/
if (var_smtp_tls_loglevel >= 3)
msg_info("looking for session %s in client cache", cache_id);
- if (enforce_peername)
- flags |= TLS_FLAG_ENFORCE_PEERNAME;
/*
* Look up and activate the SSL_SESSION object. Errors are non-fatal,
* since caching is only an optimization.
*/
- if (tls_mgr_lookup(tls_client_cache, cache_id, OPENSSL_VERSION_NUMBER,
- flags, session_data) == TLS_MGR_STAT_OK) {
+ if (tls_mgr_lookup(tls_client_cache, cache_id,
+ session_data) == TLS_MGR_STAT_OK) {
session = tls_session_activate(STR(session_data), LEN(session_data));
if (session) {
if (var_smtp_tls_loglevel >= 3)
TLScontext_t *TLScontext;
VSTRING *session_data;
const char *cache_id;
- int flags = 0;
/*
* Look up the cache ID string for this session object.
if (var_smtp_tls_loglevel >= 3)
msg_info("save session %s to client cache", cache_id);
- /*
- * Remember whether peername matching was enforced when the session was
- * created. If later enforce mode is enabled, we do not want to reuse a
- * session that was not sufficiently checked.
- */
- if (TLScontext->enforce_verify_errors && TLScontext->enforce_CN)
- flags |= TLS_FLAG_ENFORCE_PEERNAME;
-
#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L)
/*
session_data = tls_session_passivate(session);
if (session_data)
tls_mgr_update(tls_client_cache, cache_id,
- OPENSSL_VERSION_NUMBER, flags,
STR(session_data), LEN(session_data));
/*
return (1);
}
+/* uncache_session - remove session from the external cache */
+
+static void uncache_session(TLScontext_t *TLScontext)
+{
+ if (TLScontext->serverid == 0)
+ return;
+
+ if (var_smtp_tls_loglevel >= 3)
+ msg_info("remove session %s from client cache", TLScontext->serverid);
+
+ tls_mgr_delete(tls_client_cache, TLScontext->serverid);
+}
+
/* tls_client_init - initialize client-side TLS engine */
SSL_CTX *tls_client_init(int unused_verifydepth)
{
int off = 0;
- int verify_flags = SSL_VERIFY_NONE;
SSL_CTX *client_ctx;
int cache_types;
* Finally, the setup for the server certificate checking, done "by the
* book".
*/
- SSL_CTX_set_verify(client_ctx, verify_flags, client_verify_callback);
+ SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
+ tls_verify_certificate_callback);
/*
- * Initialize the session cache. In order to share cached sessions among
- * multiple SMTP client processes, we use an external cache and set the
- * internal cache size to a minimum value of 1.
+ * Initialize the session cache.
+ *
+ * Since the client does not search an internal cache, we simply disable it.
+ * It is only useful for expiring old sessions, but we do that in the
+ * tlsmgr(8).
+ *
+ * This makes SSL_CTX_remove_session() not useful for flushing broken
+ * sessions from the external cache, so we must delete them directly (not
+ * via a callback).
*/
- SSL_CTX_sess_set_cache_size(client_ctx, 1);
SSL_CTX_set_timeout(client_ctx, var_smtp_tls_scache_timeout);
/*
* OpenSSL can, however, automatically save newly created sessions for
* us by callback (we create the session name in the call-back
* function).
- *
- * Disable automatic clearing of cache entries, as the client process
- * has limited lifetime anyway and we can call the cleanup routine
- * directly.
*/
SSL_CTX_set_session_cache_mode(client_ctx,
- SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ SSL_SESS_CACHE_CLIENT |
+#ifdef SSL_SESS_CACHE_NO_INTERNAL_STORE
+ SSL_SESS_CACHE_NO_INTERNAL_STORE |
+#endif
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
SSL_CTX_sess_set_new_cb(client_ctx, new_client_session_cb);
}
return (client_ctx);
}
+/* match_hostname - match hostname against pattern */
+
+static int match_hostname(const char *pattern, const char *hostname)
+{
+ char *peername_left;
+
+ return (strcasecmp(hostname, pattern) == 0
+ || (pattern[0] == '*' && pattern[1] == '.' && pattern[2] != 0
+ && (peername_left = strchr(hostname, '.')) != 0
+ && strcasecmp(peername_left + 1, pattern + 2) == 0));
+}
+
+/* verify_extract_peer - verify peer name and extract peer information */
+
+static void verify_extract_peer(const char *peername, X509 * peercert,
+ TLScontext_t *TLScontext, tls_info_t *tls_info)
+{
+ char buf[1024];
+ int i;
+ int r;
+ int hostname_matched;
+ int dNSName_found;
+
+ STACK_OF(GENERAL_NAME) * gens;
+
+ tls_info->peer_verified =
+ (SSL_get_verify_result(TLScontext->con) == X509_V_OK);
+
+ if (TLScontext->enforce_CN != 0 && tls_info->peer_verified != 0) {
+
+ /*
+ * Verify the name(s) in the peer certificate against the peer
+ * hostname. Log peer hostname/certificate mis-matches. If a match is
+ * required but fails, bail out with a verification error.
+ */
+ hostname_matched = dNSName_found = 0;
+
+ gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0);
+ if (gens) {
+ for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i) {
+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
+
+ if (gn->type == GEN_DNS) {
+ dNSName_found++;
+ if ((hostname_matched =
+ match_hostname((char *) gn->d.ia5->data, peername)))
+ break;
+ }
+ }
+ sk_GENERAL_NAME_free(gens);
+ }
+ if (dNSName_found) {
+ if (!hostname_matched)
+ msg_info("certificate peer name verification failed for "
+ "%s: %d dNSNames in certificate found, "
+ "but none matches", peername, dNSName_found);
+ } else {
+ buf[0] = '\0';
+ if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peercert),
+ NID_commonName, buf,
+ sizeof(buf))) {
+ msg_info("certificate peer name verification failed for"
+ " %s: cannot parse subject CommonName", peername);
+ tls_print_errors();
+ } else {
+ hostname_matched = match_hostname(buf, peername);
+ if (!hostname_matched)
+ msg_info("certificate peer name verification failed "
+ "for %s: CommonName mis-match: %s",
+ peername, buf);
+ }
+ }
+
+ TLScontext->hostname_matched = hostname_matched;
+ }
+ tls_info->hostname_matched = TLScontext->hostname_matched;
+
+ TLScontext->peer_CN[0] = '\0';
+ if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peercert),
+ NID_commonName, TLScontext->peer_CN,
+ sizeof(TLScontext->peer_CN))) {
+ msg_info("Could not parse server's subject CN");
+ tls_print_errors();
+ }
+ tls_info->peer_CN = TLScontext->peer_CN;
+
+ TLScontext->issuer_CN[0] = '\0';
+ if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peercert),
+ NID_commonName, TLScontext->issuer_CN,
+ sizeof(TLScontext->issuer_CN))) {
+ msg_info("Could not parse server's issuer CN");
+ tls_print_errors();
+ }
+ if (!TLScontext->issuer_CN[0]) {
+ /* No issuer CN field, use Organization instead */
+ if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peercert),
+ NID_organizationName, TLScontext->issuer_CN,
+ sizeof(TLScontext->issuer_CN))) {
+ msg_info("Could not parse server's issuer Organization");
+ tls_print_errors();
+ }
+ }
+ tls_info->issuer_CN = TLScontext->issuer_CN;
+
+ if (var_smtp_tls_loglevel >= 1) {
+ if (tls_info->peer_verified
+ && (!TLScontext->enforce_CN || TLScontext->hostname_matched))
+ msg_info("Verified: subject_CN=%s, issuer=%s",
+ TLScontext->peer_CN, TLScontext->issuer_CN);
+ else
+ msg_info("Unverified: subject_CN=%s, issuer=%s",
+ TLScontext->peer_CN, TLScontext->issuer_CN);
+ }
+}
+
/*
* This is the actual startup routine for the connection. We expect that the
* buffers are flushed and the "220 Ready to start TLS" was received by us,
tls_info_t *tls_info)
{
int sts;
- SSL_SESSION *session, *old_session;
+ SSL_SESSION *session;
SSL_CIPHER *cipher;
- X509 *peer;
- int verify_flags;
+ X509 *peercert;
TLScontext_t *TLScontext;
if (var_smtp_tls_loglevel >= 1)
* Allocate a new TLScontext for the new connection and get an SSL
* structure. Add the location of TLScontext to the SSL to later retrieve
* the information inside the tls_verify_certificate_callback().
- *
- * XXX Need a dedicated procedure for consistent initialization of all the
- * fields in this structure.
*/
-#define PEERNAME_SIZE sizeof(TLScontext->peername_save)
-
- NEW_TLS_CONTEXT(TLScontext);
- TLScontext->log_level = var_smtp_tls_loglevel;
- strncpy(TLScontext->peername_save, peername, PEERNAME_SIZE - 1);
- TLScontext->peername_save[PEERNAME_SIZE - 1] = 0;
- (void) lowercase(TLScontext->peername_save);
+ TLScontext = tls_alloc_context(var_smtp_tls_loglevel, peername);
TLScontext->serverid = mystrdup(serverid);
if ((TLScontext->con = (SSL *) SSL_new(client_ctx)) == NULL) {
msg_info("Could not allocate 'TLScontext->con' with SSL_new()");
tls_print_errors();
- FREE_TLS_CONTEXT(TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
msg_info("Could not set application data for 'TLScontext->con'");
tls_print_errors();
- SSL_free(TLScontext->con);
- FREE_TLS_CONTEXT(TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
* tls_verify_certificate_callback().
*/
if (enforce_peername) {
- verify_flags = SSL_VERIFY_PEER;
TLScontext->enforce_verify_errors = 1;
TLScontext->enforce_CN = 1;
- SSL_set_verify(TLScontext->con, verify_flags, client_verify_callback);
+ SSL_set_verify(TLScontext->con, SSL_VERIFY_PEER,
+ tls_verify_certificate_callback);
} else {
TLScontext->enforce_verify_errors = 0;
TLScontext->enforce_CN = 0;
&TLScontext->network_bio, TLS_BIO_BUFSIZE)) {
msg_info("Could not obtain BIO_pair");
tls_print_errors();
- SSL_free(TLScontext->con);
- FREE_TLS_CONTEXT(TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
- old_session = NULL;
/*
* Try to load an existing session from the TLS session cache.
* will be reused.
*/
if (tls_client_cache) {
- old_session = load_clnt_session(serverid, enforce_peername);
- if (old_session) {
- SSL_set_session(TLScontext->con, old_session);
- SSL_SESSION_free(old_session); /* 200411 */
+ session = load_clnt_session(serverid);
+ if (session) {
+ SSL_set_session(TLScontext->con, session);
+ SSL_SESSION_free(session); /* 200411 */
#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L)
/*
* The development version of 0.9.7 can have this bug, too. It
* has been fixed on 2000/11/29.
*/
- SSL_set_verify_result(TLScontext->con, old_session->verify_result);
+ SSL_set_verify_result(TLScontext->con, session->verify_result);
#endif
}
if (sts <= 0) {
msg_info("SSL_connect error to %s: %d", peername, sts);
tls_print_errors();
- session = SSL_get_session(TLScontext->con);
- if (session) {
- SSL_CTX_remove_session(client_ctx, session);
- if (var_smtp_tls_loglevel >= 2)
- msg_info("SSL session removed");
- }
- SSL_free(TLScontext->con);
- BIO_free(TLScontext->network_bio); /* 200411 */
- FREE_TLS_CONTEXT(TLScontext);
+ uncache_session(TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
+
+ /*
+ * The TLS engine is active. Switch to the tls_timed_read/write()
+ * functions and make the TLScontext available to those functions.
+ */
+ tls_stream_start(stream, TLScontext);
+
if (var_smtp_tls_loglevel >= 3 && SSL_session_reused(TLScontext->con))
msg_info("Reusing old session");
BIO_set_callback(SSL_get_rbio(TLScontext->con), 0);
/*
- * Let's see whether a peer certificate is available and what is the
- * actual information. We want to save it for later use.
+ * Do peername verification if requested and extract useful information
+ * from the certificate for later use.
*/
- peer = SSL_get_peer_certificate(TLScontext->con);
- if (peer != NULL) {
- if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
- tls_info->peer_verified = 1;
-
- tls_info->hostname_matched = TLScontext->hostname_matched;
-
- TLScontext->peer_CN[0] = '\0';
- if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
- NID_commonName, TLScontext->peer_CN,
- sizeof(TLScontext->peer_CN))) {
- msg_info("Could not parse server's subject CN");
- tls_print_errors();
- }
- tls_info->peer_CN = TLScontext->peer_CN;
-
- TLScontext->issuer_CN[0] = '\0';
- if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
- NID_commonName, TLScontext->issuer_CN,
- sizeof(TLScontext->issuer_CN))) {
- msg_info("Could not parse server's issuer CN");
- tls_print_errors();
- }
- if (!TLScontext->issuer_CN[0]) {
- /* No issuer CN field, use Organization instead */
- if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
- NID_organizationName, TLScontext->issuer_CN,
- sizeof(TLScontext->issuer_CN))) {
- msg_info("Could not parse server's issuer Organization");
- tls_print_errors();
- }
- }
- tls_info->issuer_CN = TLScontext->issuer_CN;
-
- if (var_smtp_tls_loglevel >= 1) {
- if (tls_info->peer_verified)
- msg_info("Verified: subject_CN=%s, issuer=%s",
- TLScontext->peer_CN, TLScontext->issuer_CN);
- else
- msg_info("Unverified: subject_CN=%s, issuer=%s",
- TLScontext->peer_CN, TLScontext->issuer_CN);
- }
- X509_free(peer);
+ if ((peercert = SSL_get_peer_certificate(TLScontext->con)) != 0) {
+ verify_extract_peer(peername, peercert, TLScontext, tls_info);
+ X509_free(peercert);
+ }
+ if (enforce_peername && !TLScontext->hostname_matched) {
+ msg_info("Server certificate could not be verified for %s:"
+ " hostname mismatch", peername);
+ tls_client_stop(client_ctx, stream, timeout, 0, tls_info);
+ return (0);
}
/*
tls_info->cipher_usebits = SSL_CIPHER_get_bits(cipher,
&(tls_info->cipher_algbits));
- /*
- * The TLS engine is active. Switch to the tls_timed_read/write()
- * functions and make the TLScontext available to those functions.
- */
- tls_stream_start(stream, TLScontext);
-
if (var_smtp_tls_loglevel >= 1)
- msg_info("TLS connection established to %s: %s with cipher %s (%d/%d bits)",
- peername, tls_info->protocol, tls_info->cipher_name,
+ msg_info("TLS connection established to %s: %s with cipher %s"
+ " (%d/%d bits)", peername,
+ tls_info->protocol, tls_info->cipher_name,
tls_info->cipher_usebits, tls_info->cipher_algbits);
tls_int_seed();
/* int tls_mgr_policy(cache_types)
/* int *cache_types;
/*
-/* int tls_mgr_update(cache_type, cache_id,
-/* openssl_version, flags, buf, len)
+/* int tls_mgr_update(cache_type, cache_id, buf, len)
/* int cache_type;
/* const char *cache_id;
-/* long openssl_version;
-/* int flags;
/* const char *buf;
/* int len;
/*
-/* int tls_mgr_lookup(cache_type, cache_id,
-/* openssl_version, flags, buf)
+/* int tls_mgr_lookup(cache_type, cache_id, buf)
/* int cache_type;
/* const char *cache_id;
-/* long openssl_version;
-/* int flags;
/* VSTRING *buf;
/*
/* int tls_mgr_delete(cache_type, cache_id)
/* One of TLS_MGR_SCACHE_CLIENT or TLS_MGR_SCACHE_SERVER (see above).
/* .IP cache_id
/* The session cache lookup key.
-/* .IP openssl_version
-/* The OpenSSL version. Sessions saved by the wrong OpenSSL version are
-/* deleted, to avoid compatibility problems.
-/* .IP flags
-/* Flags that must be set in the retrieved cache entry; it not,
-/* the cache entry is deleted.
/* .IP buf
/* The result or input buffer.
/* .IP len
/* tls_mgr_lookup - request cached session */
-int tls_mgr_lookup(int cache_type, const char *cache_id,
- long openssl_vsn, int flags, VSTRING *buf)
+int tls_mgr_lookup(int cache_type, const char *cache_id, VSTRING *buf)
{
int status;
ATTR_TYPE_STR, TLS_MGR_ATTR_REQ, TLS_MGR_REQ_LOOKUP,
ATTR_TYPE_NUM, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
- ATTR_TYPE_LONG, TLS_MGR_ATTR_VERSION, openssl_vsn,
- ATTR_TYPE_NUM, TLS_MGR_ATTR_FLAGS, flags,
ATTR_TYPE_END,
ATTR_FLAG_MISSING, /* Reply */
ATTR_TYPE_NUM, TLS_MGR_ATTR_STATUS, &status,
/* tls_mgr_update - save session to cache */
int tls_mgr_update(int cache_type, const char *cache_id,
- long openssl_vsn, int flags,
const char *buf, int len)
{
int status;
ATTR_TYPE_STR, TLS_MGR_ATTR_REQ, TLS_MGR_REQ_UPDATE,
ATTR_TYPE_NUM, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
- ATTR_TYPE_LONG, TLS_MGR_ATTR_VERSION, openssl_vsn,
- ATTR_TYPE_NUM, TLS_MGR_ATTR_FLAGS, flags,
ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION, len, buf,
ATTR_TYPE_END,
ATTR_FLAG_MISSING, /* Reply */
vstream_printf("status=%d seed=%s\n", status, STR(hex));
vstring_free(hex);
vstring_free(buf);
- } else if (COMMAND(argv, "lookup", 5)) {
+ } else if (COMMAND(argv, "lookup", 3)) {
VSTRING *buf = vstring_alloc(10);
int cache_type = atoi(argv->argv[1]);
- long openssl_vsn = atol(argv->argv[3]);
- int flags = atoi(argv->argv[4]);
- status = tls_mgr_lookup(cache_type, argv->argv[2],
- openssl_vsn, flags, buf);
+ status = tls_mgr_lookup(cache_type, argv->argv[2], buf);
vstream_printf("status=%d session=%.*s\n",
status, LEN(buf), STR(buf));
- } else if (COMMAND(argv, "update", 6)) {
+ } else if (COMMAND(argv, "update", 4)) {
int cache_type = atoi(argv->argv[1]);
- long openssl_vsn = atol(argv->argv[3]);
- int flags = atoi(argv->argv[4]);
status = tls_mgr_update(cache_type, argv->argv[2],
- openssl_vsn, flags,
- argv->argv[5], strlen(argv->argv[5]));
+ argv->argv[3], strlen(argv->argv[3]));
vstream_printf("status=%d\n", status);
} else if (COMMAND(argv, "delete", 3)) {
int cache_type = atoi(argv->argv[1]);
vstream_printf("usage:\n"
"seed byte_count\n"
"policy\n"
- "lookup cache_type cache_id openssl_version flags\n"
- "update cache_type cache_id openssl_version flags session\n"
+ "lookup cache_type cache_id\n"
+ "update cache_type cache_id session\n"
"delete cache_type cache_id\n");
}
vstream_fflush(VSTREAM_OUT);
#define TLS_MGR_ATTR_CACHE_TYPE "cache_type"
#define TLS_MGR_ATTR_SEED "seed"
#define TLS_MGR_ATTR_CACHE_ID "cache_id"
-#define TLS_MGR_ATTR_VERSION "version"
-#define TLS_MGR_ATTR_FLAGS "flags"
#define TLS_MGR_ATTR_SESSION "session"
#define TLS_MGR_ATTR_SIZE "size"
#define TLS_MGR_ATTR_STATUS "status"
-#define TLS_MGR_ATTR_FLAGS "flags"
/*
* TLS manager request status codes.
*/
extern int tls_mgr_seed(VSTRING *, int);
extern int tls_mgr_policy(int *);
-extern int tls_mgr_lookup(int, const char *, long, int, VSTRING *);
-extern int tls_mgr_update(int, const char *, long, int, const char *, int);
+extern int tls_mgr_lookup(int, const char *, VSTRING *);
+extern int tls_mgr_update(int, const char *, const char *, int);
extern int tls_mgr_delete(int, const char *);
-#define TLS_MGR_NO_FLAGS 0
-
/* LICENSE
/* .ad
/* .fi
/* #define TLS_INTERNAL
/* #include <tls.h>
/*
+/* TLScontext_t *tls_alloc_context(log_level, peername)
+/* int log_level;
+/* const char *peername;
+/*
+/* void tls_free_context(TLScontext)
+/* TLScontext_t *TLScontext;
+/*
/* void tls_print_errors()
/*
/* void tls_info_callback(ssl, where, ret)
/* This module implements routines that support the TLS client
/* and server internals.
/*
+/* tls_alloc_context() creates an initialized TLScontext
+/* structure with the specified peer name and logging level.
+/*
+/* tls_free_context() destroys a TLScontext structure
+/* together with OpenSSL structures that are attached to it.
+/*
/* tls_print_errors() queries the OpenSSL error stack,
/* logs the error messages, and clears the error stack.
/*
#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>
+#include <stringops.h>
/* TLS library. */
*/
int TLScontext_index = -1;
+/* tls_alloc_context - allocate TLScontext */
+
+TLScontext_t *tls_alloc_context(int log_level, const char *peername)
+{
+ TLScontext_t *TLScontext;
+
+ /*
+ * PORTABILITY: Do not assume that null pointers are all-zero bits.
+ * Use explicit assignments to initialize pointers.
+ *
+ * See the C language FAQ item 5.17, or if you have time to burn,
+ * http://www.google.com/search?q=zero+bit+null+pointer
+ */
+ TLScontext = (TLScontext_t *) mymalloc(sizeof(TLScontext_t));
+ memset((char *) TLScontext, 0, sizeof(*TLScontext));
+ TLScontext->con = 0;
+ TLScontext->internal_bio = 0;
+ TLScontext->network_bio = 0;
+ TLScontext->serverid = 0;
+ TLScontext->log_level = log_level;
+ TLScontext->peername = lowercase(mystrdup(peername));
+
+ return (TLScontext);
+}
+
+/* tls_free_context - deallocate TLScontext and members */
+
+void tls_free_context(TLScontext_t *TLScontext)
+{
+
+ /*
+ * Free the SSL structure and the BIOs. Warning: the internal_bio is
+ * connected to the SSL structure and is automatically freed with it. Do
+ * not free it again (core dump)!! Only free the network_bio.
+ */
+ if (TLScontext->con != 0)
+ SSL_free(TLScontext->con);
+ if (TLScontext->network_bio)
+ BIO_free(TLScontext->network_bio);
+ if (TLScontext->peername)
+ myfree(TLScontext->peername);
+ if (TLScontext->serverid)
+ myfree(TLScontext->serverid);
+ myfree((char *) TLScontext);
+}
+
/* tls_print_errors - print and clear the error stack */
void tls_print_errors(void)
/* void tls_scache_close(cache)
/* TLS_SCACHE *cache;
/*
-/* int tls_scache_lookup(cache, cache_id, openssl_version,
-/* flags, out_openssl_version, out_flags,
-/* out_session)
+/* int tls_scache_lookup(cache, cache_id, out_session)
/* TLS_SCACHE *cache;
/* const char *cache_id;
-/* long openssl_version;
-/* int flags;
-/* long *out_openssl_version;
-/* int *out_flags;
/* VSTRING *out_session;
/*
-/* int tls_scache_update(cache, cache_id, openssl_version,
-/* flags, session, session_len)
+/* int tls_scache_update(cache, cache_id, session, session_len)
/* TLS_SCACHE *cache;
/* const char *cache_id;
-/* long openssl_version;
-/* int flags;
/* const char *session;
/* int session_len;
/*
-/* int tls_scache_sequence(cache, first_next, openssl_version, flags,
-/* out_cache_id, out_openssl_version, out_flags,
+/* int tls_scache_sequence(cache, first_next, out_cache_id,
/* VSTRING *out_session)
/* TLS_SCACHE *cache;
/* int first_next;
-/* long openssl_version;
-/* int flags;
/* char **out_cache_id;
-/* long *out_openssl_version;
-/* int *out_flags;
/* VSTRING *out_session;
/*
/* int tls_scache_delete(cache, cache_id)
/* DESCRIPTION
/* This module maintains Postfix TLS session cache files.
/* each session is stored under a lookup key (hostname or
-/* session ID) together with the OpenSSL version that
-/* created the session and application-specific flags.
-/* Upon lookup, the OpenSSL version and flags can be
-/* specified as optional filters. Entries that don't
-/* satisfy the filter requirements are silently deleted.
+/* session ID).
/*
/* tls_scache_open() opens the specified TLS session cache
/* and returns a handle that must be used for subsequent
/* and releases memory that was allocated by tls_scache_open().
/*
/* tls_scache_lookup() looks up the specified session in the
-/* specified cache, and applies the session timeout, openssl
-/* version and flags restrictions. Entries that don't satisfy
-/* the requirements are silently deleted.
+/* specified cache, and applies session timeout restrictions.
+/* Entries that are too old are silently deleted.
/*
/* tls_scache_update() updates the specified TLS session cache
/* with the specified session information.
/*
/* tls_scache_sequence() iterates over the specified TLS session
-/* cache and looks up the first or next entry. If that entry
-/* matches the session timeout, OpenSSL version and flags
-/* restrictions, tls_scache_sequence() saves the entry by
-/* updating the result parameters; otherwise it deletes the
-/* entry and does not update the result parameters. Specify
-/* TLS_SCACHE_SEQUENCE_NOTHING
-/* as the third and last argument to disable OpenSSL version
-/* and flags restrictions, and to disable saving of cache
-/* entry content or cache entry ID information. This is useful
-/* when purging expired entries. A result value of zero means
-/* that the end of the cache was reached.
+/* cache and either returns the first or next entry that has not
+/* timed out, or returns no data. Entries that are too old are
+/* silently deleted. Specify TLS_SCACHE_SEQUENCE_NOTHING as the
+/* third and last argument to disable saving of cache entry
+/* content or cache entry ID information. This is useful when
+/* purging expired entries. A result value of zero means that
+/* the end of the cache was reached.
/*
/* tls_scache_delete() removes the specified cache entry from
/* the specified TLS session cache.
/* (next cache element).
/* .IP cache_id
/* Session cache lookup key.
-/* .IP openssl_version
-/* When storing information, the OpenSSL version that generated a
-/* session. When retrieving information, delete cache entries that
-/* don't match the specified OpenSSL version.
-/*
-/* Specify TLS_SCACHE_ANY_OPENSSL_VSN to match any OpenSSL version.
-/* .IP flags
-/* When storing information, application flags that specify properties
-/* of a session. When retrieving information, delete cache entries that
-/* have the specified flags set.
-/*
-/* Specify TLS_SCACHE_ANY_FLAGS to match any flags value.
/* .IP session
/* Storage for session information.
/* .IP session_len
/* The size of the session information in bytes.
/* .IP out_cache_id
-/* .IP out_openssl_version
-/* .IP out_flags
/* .IP out_session
-/* Storage for saving the cache_id, openssl_version, flags
-/* or session information of the current cache entry.
+/* Storage for saving the cache_id or session information of the
+/* current cache entry.
/*
/* Specify TLS_SCACHE_DONT_NEED_CACHE_ID to avoid saving
/* the session cache ID of the cache entry.
/*
-/* Specify TLS_SCACHE_DONT_NEED_OPENSSL_VSN to avoid
-/* saving the OpenSSL version in the cache entry.
-/*
-/* Specify TLS_SCACHE_DONT_NEED_FLAGS to avoid
-/* saving the flags information in the cache entry.
-/*
/* Specify TLS_SCACHE_DONT_NEED_SESSION to avoid
/* saving the session information in the cache entry.
/* DIAGNOSTICS
* database when it is opened.
*/
typedef struct {
- long scache_db_version; /* obsolete */
- long openssl_version; /* clients may differ... */
time_t timestamp; /* time when saved */
- int flags; /* enforcement etc. */
char session[1]; /* actually a bunch of bytes */
} TLS_SCACHE_ENTRY;
-#define TLS_SCACHE_DB_VERSION 0x00000003L
-
/*
* SLMs.
*/
/* tls_scache_encode - encode TLS session cache entry */
static VSTRING *tls_scache_encode(TLS_SCACHE *cp, const char *cache_id,
- long openssl_version, int flags,
const char *session,
int session_len)
{
*/
binary_data_len = session_len + offsetof(TLS_SCACHE_ENTRY, session);
entry = (TLS_SCACHE_ENTRY *) mymalloc(binary_data_len);
- entry->scache_db_version = TLS_SCACHE_DB_VERSION;
- entry->openssl_version = openssl_version;
entry->timestamp = time((time_t *) 0);
- entry->flags = flags;
memcpy(entry->session, session, session_len);
/*
* Logging.
*/
if (cp->log_level >= 3)
- msg_info("write %s TLS cache entry %s: cache_version=%ld"
- " openssl_version=0x%lx flags=0x%x time=%ld [data %d bytes]",
- cp->cache_label, cache_id,
- (long) entry->scache_db_version,
- (long) entry->openssl_version,
- entry->flags,
- (long) entry->timestamp,
+ msg_info("write %s TLS cache entry %s: time=%ld [data %d bytes]",
+ cp->cache_label, cache_id, (long) entry->timestamp,
session_len);
/*
static int tls_scache_decode(TLS_SCACHE *cp, const char *cache_id,
const char *hex_data, int hex_data_len,
- long openssl_version, int flags,
- long *out_openssl_version,
- int *out_flags,
VSTRING *out_session)
{
TLS_SCACHE_ENTRY *entry;
}
/*
- * Disassemble the TLS session cache entry and enforce the restrictions
- * specified as version numbers or flags.
+ * Disassemble the TLS session cache entry and enforce version number
+ * restrictions.
*
* No early returns or we have a memory leak.
*/
cp->cache_label, cache_id, hex_data);
FREE_AND_RETURN(bin_data, 0);
}
-
- /*
- * Before doing anything else, verify that the database format version
- * matches this program.
- */
entry = (TLS_SCACHE_ENTRY *) STR(bin_data);
- if (entry->scache_db_version != TLS_SCACHE_DB_VERSION) {
- msg_warn("%s TLS cache: cache version mis-match for %s: 0x%lx != 0x%lx",
- cp->cache_label, cache_id, entry->scache_db_version,
- TLS_SCACHE_DB_VERSION);
- FREE_AND_RETURN(bin_data, 0);
- }
/*
* Logging.
*/
if (cp->log_level >= 3)
- msg_info("read %s TLS cache entry %s: cache_version=%ld"
- " openssl_version=0x%lx time=%ld flags=0x%x [data %d bytes]",
- cp->cache_label, cache_id, (long) entry->scache_db_version,
- (long) entry->openssl_version, (long) entry->timestamp,
- entry->flags,
+ msg_info("read %s TLS cache entry %s: time=%ld [data %d bytes]",
+ cp->cache_label, cache_id, (long) entry->timestamp,
LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session));
/*
if (entry->timestamp + cp->timeout < time((time_t *) 0))
FREE_AND_RETURN(bin_data, 0);
- /*
- * Optional restrictions.
- */
- if (openssl_version != 0 && entry->openssl_version != openssl_version) {
- msg_warn("%s TLS cache: openssl version mis-match for %s: 0x%lx != 0x%lx",
- cp->cache_label, cache_id, entry->openssl_version,
- openssl_version);
- FREE_AND_RETURN(bin_data, 0);
- }
- if (flags != 0 && (entry->flags & flags) != flags) {
- msg_warn("%s TLS cache: flags mis-match for %s: 0x%x is not subset of 0x%x",
- cp->cache_label, cache_id, entry->flags, flags);
- FREE_AND_RETURN(bin_data, 0);
- }
-
/*
* Optional output.
*/
- if (out_openssl_version != 0)
- *out_openssl_version = entry->openssl_version;
- if (out_flags != 0)
- *out_flags = entry->flags;
if (out_session != 0)
vstring_memcpy(out_session, entry->session,
LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session));
/* tls_scache_lookup - load session from cache */
int tls_scache_lookup(TLS_SCACHE *cp, const char *cache_id,
- long openssl_version, int flags,
- long *out_openssl_version, int *out_flags,
VSTRING *session)
{
const char *hex_data;
* Logging.
*/
if (cp->log_level >= 3)
- msg_info("lookup %s session id=%s ssl=0x%lx flags=0x%x",
- cp->cache_label, cache_id, openssl_version, flags);
+ msg_info("lookup %s session id=%s", cp->cache_label, cache_id);
/*
* Initialize. Don't leak data.
return (0);
/*
- * Decode entry and verify version and flags information.
- *
- * XXX We throw away sessions when flags don't match. If we want to allow
- * for co-existing cache entries with different flags, the flags would
- * have to be encoded in the cache lookup key.
+ * Decode entry and verify version information.
*/
if (tls_scache_decode(cp, cache_id, hex_data, strlen(hex_data),
- openssl_version, flags, out_openssl_version,
- out_flags, session) == 0) {
+ session) == 0) {
tls_scache_delete(cp, cache_id);
return (0);
} else {
/* tls_scache_update - save session to cache */
int tls_scache_update(TLS_SCACHE *cp, const char *cache_id,
- long openssl_version, int flags,
const char *buf, int len)
{
VSTRING *hex_data;
* Logging.
*/
if (cp->log_level >= 3)
- msg_info("put %s session id=%s ssl=0x%lx flags=0x%x [data %d bytes]",
- cp->cache_label, cache_id, openssl_version, flags, len);
+ msg_info("put %s session id=%s [data %d bytes]",
+ cp->cache_label, cache_id, len);
/*
* Encode the cache entry.
*/
- hex_data =
- tls_scache_encode(cp, cache_id, openssl_version, flags, buf, len);
+ hex_data = tls_scache_encode(cp, cache_id, buf, len);
/*
* Store the cache entry.
/* tls_scache_sequence - get first/next TLS session cache entry */
int tls_scache_sequence(TLS_SCACHE *cp, int first_next,
- long openssl_version,
- int flags,
char **out_cache_id,
- long *out_openssl_version,
- int *out_flags,
VSTRING *out_session)
{
const char *member;
/*
* Find the first or next database entry. Activate the passivated entry
- * and check the version, time stamp and flags information. Schedule the
- * entry for deletion if it is bad or too old.
+ * and check the time stamp. Schedule the entry for deletion if it is too
+ * old.
*
* Save the member (cache id) so that it will not be clobbered by the
* tls_scache_lookup() call below.
found_entry = (dict_seq(cp->db, first_next, &member, &value) == 0);
if (found_entry) {
keep_entry = tls_scache_decode(cp, member, value, strlen(value),
- openssl_version, flags,
- out_openssl_version,
- out_flags, out_session);
+ out_session);
if (keep_entry && out_cache_id)
*out_cache_id = mystrdup(member);
saved_member = mystrdup(member);
cp->flags &= ~TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
saved_cursor = cp->saved_cursor;
cp->saved_cursor = 0;
- tls_scache_lookup(cp, saved_cursor, cp->saved_openssl_version,
- cp->saved_flags, (long *) 0, (int *) 0,
- (VSTRING *) 0);
+ tls_scache_lookup(cp, saved_cursor, (VSTRING *) 0);
myfree(saved_cursor);
}
*/
if (found_entry) {
cp->saved_cursor = saved_member;
- if (keep_entry == 0) {
+ if (keep_entry == 0)
cp->flags |= TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
- cp->saved_openssl_version = openssl_version;
- cp->saved_flags = flags;
- }
}
return (found_entry);
}
int log_level; /* smtp(d)_tls_log_level */
int timeout; /* smtp(d)_tls_session_cache_timeout */
char *saved_cursor; /* cursor cache ID */
- long saved_openssl_version; /* cursor OpenSSL version */
- int saved_flags; /* cursor lookup flags */
} TLS_SCACHE;
#define TLS_SCACHE_FLAG_DEL_SAVED_CURSOR (1<<0)
extern TLS_SCACHE *tls_scache_open(const char *, const char *, int, int);
extern void tls_scache_close(TLS_SCACHE *);
-extern int tls_scache_lookup(TLS_SCACHE *, const char *, long, int, long *, int *, VSTRING *);
-extern int tls_scache_update(TLS_SCACHE *, const char *, long, int, const char *, int);
+extern int tls_scache_lookup(TLS_SCACHE *, const char *, VSTRING *);
+extern int tls_scache_update(TLS_SCACHE *, const char *, const char *, int);
extern int tls_scache_delete(TLS_SCACHE *, const char *);
-extern int tls_scache_sequence(TLS_SCACHE *, int, long, int, char **, long *, int *, VSTRING *);
-
-#define TLS_SCACHE_ANY_OPENSSL_VSN ((long) 0)
-#define TLS_SCACHE_ANY_FLAGS (0)
+extern int tls_scache_sequence(TLS_SCACHE *, int, char **, VSTRING *);
#define TLS_SCACHE_DONT_NEED_CACHE_ID ((char **) 0)
-#define TLS_SCACHE_DONT_NEED_OPENSSL_VSN ((long *) 0)
-#define TLS_SCACHE_DONT_NEED_FLAGS ((int *) 0)
#define TLS_SCACHE_DONT_NEED_SESSION ((VSTRING *) 0)
#define TLS_SCACHE_SEQUENCE_NOTHING \
- TLS_SCACHE_ANY_FLAGS, TLS_SCACHE_ANY_OPENSSL_VSN, \
- TLS_SCACHE_DONT_NEED_CACHE_ID, TLS_SCACHE_DONT_NEED_OPENSSL_VSN, \
- TLS_SCACHE_DONT_NEED_FLAGS, TLS_SCACHE_DONT_NEED_SESSION
+ TLS_SCACHE_DONT_NEED_CACHE_ID, TLS_SCACHE_DONT_NEED_SESSION
/* LICENSE
/* .ad
static int tls_server_cache = 0;
-/* server_verify_callback - server verification wrapper */
-
-static int server_verify_callback(int ok, X509_STORE_CTX *ctx)
-{
- return (tls_verify_certificate_callback(ok, ctx, TLS_VERIFY_DEFAULT));
-}
-
/* get_server_session_cb - callback to retrieve session from server cache */
static SSL_SESSION *get_server_session_cb(SSL *unused_ssl,
VSTRING *session_data = vstring_alloc(2048);
SSL_SESSION *session = 0;
-#define MAKE_SERVER_CACHE_ID(id, len) \
+#define HEX_CACHE_ID(id, len) \
hex_encode(vstring_alloc(2 * (len) + 1), (char *) (id), (len))
- /*
- * Encode the session ID.
- */
- cache_id = MAKE_SERVER_CACHE_ID(session_id, session_id_length);
+ cache_id = HEX_CACHE_ID(session_id, session_id_length);
if (var_smtpd_tls_loglevel >= 3)
msg_info("looking up session %s in server cache", STR(cache_id));
* Load the session from cache and decode it.
*/
if (tls_mgr_lookup(tls_server_cache, STR(cache_id),
- OPENSSL_VERSION_NUMBER, TLS_MGR_NO_FLAGS,
session_data) == TLS_MGR_STAT_OK) {
session = tls_session_activate(STR(session_data), LEN(session_data));
if (session && (var_smtpd_tls_loglevel >= 3))
return (session);
}
+/* uncache_session - remove session from internal & external cache */
+
+static void uncache_session(SSL_CTX *ctx, TLScontext_t *TLScontext)
+{
+ VSTRING *cache_id;
+ SSL_SESSION *session = SSL_get_session(TLScontext->con);
+
+ SSL_CTX_remove_session(ctx, session);
+
+ cache_id = HEX_CACHE_ID(session->session_id, session->session_id_length);
+ if (var_smtpd_tls_loglevel >= 3)
+ msg_info("remove session %s from server cache", STR(cache_id));
+
+ tls_mgr_delete(tls_server_cache, STR(cache_id));
+ vstring_free(cache_id);
+}
+
/* new_server_session_cb - callback to save session to server cache */
static int new_server_session_cb(SSL *unused_ssl, SSL_SESSION *session)
VSTRING *cache_id;
VSTRING *session_data;
- /*
- * Encode the session ID.
- */
- cache_id =
- MAKE_SERVER_CACHE_ID(session->session_id, session->session_id_length);
+ cache_id = HEX_CACHE_ID(session->session_id, session->session_id_length);
if (var_smtpd_tls_loglevel >= 3)
msg_info("save session %s to server cache", STR(cache_id));
session_data = tls_session_passivate(session);
if (session_data)
tls_mgr_update(tls_server_cache, STR(cache_id),
- OPENSSL_VERSION_NUMBER, TLS_MGR_NO_FLAGS,
STR(session_data), LEN(session_data));
/*
*/
if (askcert)
verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
- SSL_CTX_set_verify(server_ctx, verify_flags, server_verify_callback);
+ SSL_CTX_set_verify(server_ctx, verify_flags,
+ tls_verify_certificate_callback);
if (*var_smtpd_tls_CAfile)
SSL_CTX_set_client_CA_list(server_ctx,
SSL_load_client_CA_file(var_smtpd_tls_CAfile));
/*
- * Initialize the session cache. In order to share cached sessions among
- * multiple SMTP server processes, we use an external cache and set the
- * internal cache size to a minimum value of 1. Access to the external
- * cache is handled by the appropriate callback functions.
+ * Initialize the session cache.
+ *
+ * With a large number of concurrent smtpd(8) processes, it is not a good
+ * idea to cache multiple large session objects in each process. We set
+ * the internal cache size to 1, and don't register a "remove_cb" so as
+ * to avoid deleting good sessions from the external cache prematurely
+ * (when the internal cache is full, OpenSSL removes sessions from the
+ * external cache also)!
+ *
+ * This makes SSL_CTX_remove_session() not useful for flushing broken
+ * sessions from the external cache, so we must delete them directly (not
+ * via a callback).
*
* Set a session id context to identify to what type of server process
* created a session. In our case, the context is simply the name of the
if (tls_mgr_policy(&cache_types) == TLS_MGR_STAT_OK
&& (tls_server_cache = (cache_types & TLS_MGR_SCACHE_SERVER)) != 0) {
SSL_CTX_set_session_cache_mode(server_ctx,
- SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ SSL_SESS_CACHE_SERVER |
+ SSL_SESS_CACHE_NO_AUTO_CLEAR);
SSL_CTX_sess_set_get_cb(server_ctx, get_server_session_cb);
SSL_CTX_sess_set_new_cb(server_ctx, new_server_session_cb);
}
int verify_flags;
unsigned int n;
TLScontext_t *TLScontext;
- SSL_SESSION *session;
SSL_CIPHER *cipher;
X509 *peer;
* Allocate a new TLScontext for the new connection and get an SSL
* structure. Add the location of TLScontext to the SSL to later retrieve
* the information inside the tls_verify_certificate_callback().
- *
- * XXX Need a dedicated procedure for consistent initialization of all the
- * fields in this structure.
*/
-#define PEERNAME_SIZE sizeof(TLScontext->peername_save)
-
- NEW_TLS_CONTEXT(TLScontext);
- TLScontext->log_level = var_smtpd_tls_loglevel;
- strncpy(TLScontext->peername_save, peername, PEERNAME_SIZE - 1);
- TLScontext->peername_save[PEERNAME_SIZE - 1] = 0;
- (void) lowercase(TLScontext->peername_save);
+ TLScontext = tls_alloc_context(var_smtpd_tls_loglevel, peername);
if ((TLScontext->con = (SSL *) SSL_new(server_ctx)) == NULL) {
msg_info("Could not allocate 'TLScontext->con' with SSL_new()");
tls_print_errors();
- FREE_TLS_CONTEXT(TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
msg_info("Could not set application data for 'TLScontext->con'");
tls_print_errors();
- SSL_free(TLScontext->con);
- FREE_TLS_CONTEXT(TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
verify_flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
TLScontext->enforce_verify_errors = 1;
- SSL_set_verify(TLScontext->con, verify_flags, server_verify_callback);
+ SSL_set_verify(TLScontext->con, verify_flags,
+ tls_verify_certificate_callback);
} else {
TLScontext->enforce_verify_errors = 0;
}
&TLScontext->network_bio, TLS_BIO_BUFSIZE)) {
msg_info("Could not obtain BIO_pair");
tls_print_errors();
- SSL_free(TLScontext->con);
- FREE_TLS_CONTEXT(TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
if (sts <= 0) {
msg_info("SSL_accept error from %s[%s]: %d", peername, peeraddr, sts);
tls_print_errors();
- SSL_free(TLScontext->con);
- BIO_free(TLScontext->network_bio); /* 200411 */
- FREE_TLS_CONTEXT(TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
/* Only loglevel==4 dumps everything */
if (requirecert) {
if (!tls_info->peer_verified || !tls_info->peer_CN) {
msg_info("Re-used session without peer certificate removed");
- session = SSL_get_session(TLScontext->con);
- SSL_CTX_remove_session(server_ctx, session);
- SSL_free(TLScontext->con);
- BIO_free(TLScontext->network_bio); /* 200411 */
- FREE_TLS_CONTEXT(TLScontext);
+ uncache_session(server_ctx, TLScontext);
+ tls_free_context(TLScontext);
return (0);
}
}
if (retval == 0)
tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext);
}
-
- /*
- * Free the SSL structure and the BIOs. Warning: the internal_bio is
- * connected to the SSL structure and is automatically freed with it. Do
- * not free it again (core dump)!! Only free the network_bio.
- *
- * XXX SSL_CTX_flush_sessions() searches memory for expired sessions and
- * removes them from memory and external cache.
- */
- SSL_free(TLScontext->con);
-
- BIO_free(TLScontext->network_bio);
- FREE_TLS_CONTEXT(TLScontext);
+ tls_free_context(TLScontext);
tls_stream_stop(stream);
- SSL_CTX_flush_sessions(ctx, time(NULL));
-
*tls_info = tls_info_zero;
}
/* #define TLS_INTERNAL
/* #include <tls.h>
/*
-/* int tls_verify_certificate_callback(ok, ctx, int flags)
+/* int tls_verify_certificate_callback(ok, ctx)
/* int ok;
/* X509_STORE_CTX *ctx;
-/* int flags;
/* DESCRIPTION
/* tls_verify_callback() is called several times (directly or
/* indirectly) from crypto/x509/x509_vfy.c. It is called as
/* .IP ctx
/* TLS client or server context. This also specifies the
/* TLScontext with enforcement options.
-/* .IP flags
-/* .RS
-/* .IP TLS_VERIFY_PEERNAME
-/* Verify the peer hostname against the names listed
-/* in the peer certificate. The peer hostname is specified
-/* via the ctx argument.
-/* .RE
/* LICENSE
/* .ad
/* .fi
#define TLS_INTERNAL
#include <tls.h>
-/* match_hostname - match hostname against pattern */
-
-static int match_hostname(const char *pattern, const char *hostname)
-{
- char *peername_left;
-
- return (strcasecmp(hostname, pattern) == 0
- || (pattern[0] == '*' && pattern[1] == '.' && pattern[2] != 0
- && (peername_left = strchr(hostname, '.')) != 0
- && strcasecmp(peername_left + 1, pattern + 2) == 0));
-}
-
/* tls_verify_certificate_callback - verify peer certificate info */
-int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx, int flags)
+int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
{
char buf[1024];
X509 *err_cert;
}
if (!ok) {
msg_info("certificate verification failed for %s: num=%d:%s",
- TLScontext->peername_save, err,
+ TLScontext->peername, err,
X509_verify_cert_error_string(err));
}
/*
- * Match the peer hostname against the names listed in the peer
- * certificate.
+ * We delay peername verification until the SSL handshake completes. The
+ * peername verification previously done here is now called directly from
+ * tls_client_start(). This substantially simplifies the cache interface.
*/
- if (ok && (depth == 0) && (flags & TLS_VERIFY_PEERNAME)) {
- int i,
- r;
- int hostname_matched;
- int dNSName_found;
-
- STACK_OF(GENERAL_NAME) * gens;
-
- /*
- * Verify the name(s) in the peer certificate against the peer
- * hostname. Log peer hostname/certificate mis-matches. If a match is
- * required but fails, bail out with a verification error.
- */
- hostname_matched = dNSName_found = 0;
-
- gens = X509_get_ext_d2i(err_cert, NID_subject_alt_name, 0, 0);
- if (gens) {
- for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i) {
- const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
-
- if (gn->type == GEN_DNS) {
- dNSName_found++;
- if ((hostname_matched =
- match_hostname((char *) gn->d.ia5->data,
- TLScontext->peername_save)))
- break;
- }
- }
- sk_GENERAL_NAME_free(gens);
- }
- if (dNSName_found) {
- if (!hostname_matched)
- msg_info("certificate peer name verification failed for %s: "
- "%d dNSNames in certificate found, but none matches",
- TLScontext->peername_save, dNSName_found);
- } else {
- buf[0] = '\0';
- if (!X509_NAME_get_text_by_NID(X509_get_subject_name(err_cert),
- NID_commonName, buf, sizeof(buf))) {
- msg_info("certificate peer name verification failed for %s:"
- "cannot parse subject CommonName",
- TLScontext->peername_save);
- tls_print_errors();
- } else {
- hostname_matched = match_hostname(buf,
- TLScontext->peername_save);
- if (!hostname_matched)
- msg_info("certificate peer name verification failed for %s:"
- " CommonName mis-match: %s",
- TLScontext->peername_save, buf);
- }
- }
-
- if (!hostname_matched) {
- if (TLScontext->enforce_verify_errors && TLScontext->enforce_CN) {
- err = X509_V_ERR_CERT_REJECTED;
- X509_STORE_CTX_set_error(ctx, err);
- msg_info("certificate peer name verification failed for %s:"
- " hostname mismatch", TLScontext->peername_save);
- ok = 0;
- }
- } else
- TLScontext->hostname_matched = 1;
- }
/*
* Other causes for verification failure.
buf, sizeof(buf));
msg_info("certificate verification failed for %s:"
"issuer %s certificate unavailable",
- TLScontext->peername_save, buf);
+ TLScontext->peername, buf);
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
msg_info("certificate verification failed for %s:"
"certificate not yet valid",
- TLScontext->peername_save);
+ TLScontext->peername);
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
msg_info("certificate verification failed for %s:"
"certificate has expired",
- TLScontext->peername_save);
+ TLScontext->peername);
break;
}
if (TLScontext->log_level >= 2)
static VSTRING *buffer = 0;
int cache_type;
int len;
- long openssl_vsn;
- int flags;
static char wakeup[] = { /* master wakeup request */
TRIGGER_REQ_WAKEUP,
0,
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_NUM, TLS_MGR_ATTR_CACHE_TYPE, &cache_type,
ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
- ATTR_TYPE_LONG, TLS_MGR_ATTR_VERSION, &openssl_vsn,
- ATTR_TYPE_NUM, TLS_MGR_ATTR_FLAGS, &flags,
- ATTR_TYPE_END) == 4) {
+ ATTR_TYPE_END) == 2) {
if ((cache = WHICH_CACHE_INFO(cache_type)) == 0) {
msg_warn("bogus cache type \"%d\" in \"%s\" request",
cache_type, TLS_MGR_REQ_LOOKUP);
VSTRING_RESET(buffer);
} else {
- status =
- tls_scache_lookup(cache, STR(cache_id), openssl_vsn,
- flags,
- TLS_SCACHE_DONT_NEED_OPENSSL_VSN,
- TLS_SCACHE_DONT_NEED_FLAGS,
- buffer) ?
+ status = tls_scache_lookup(cache, STR(cache_id), buffer) ?
TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
}
}
if (attr_scan(client_stream, ATTR_FLAG_STRICT,
ATTR_TYPE_NUM, TLS_MGR_ATTR_CACHE_TYPE, &cache_type,
ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
- ATTR_TYPE_LONG, TLS_MGR_ATTR_VERSION, &openssl_vsn,
- ATTR_TYPE_NUM, TLS_MGR_ATTR_FLAGS, &flags,
ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION, buffer,
- ATTR_TYPE_END) == 5) {
+ ATTR_TYPE_END) == 3) {
if ((cache = WHICH_CACHE_INFO(cache_type)) == 0) {
msg_warn("bogus cache type \"%d\" in \"%s\" request",
cache_type, TLS_MGR_REQ_UPDATE);
} else {
status =
- tls_scache_update(cache, STR(cache_id), openssl_vsn,
- flags, STR(buffer), LEN(buffer)) ?
+ tls_scache_update(cache, STR(cache_id),
+ STR(buffer), LEN(buffer)) ?
TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
}
}
#include <sent.h>
#include <mail_params.h>
#include <mail_addr_find.h>
+#include <dsn_util.h>
/* Application-specific. */
if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
deliver_status = DEL_STAT_DEFER;
} else if (mail_copy_status != 0) {
- deliver_status = (why->dsn[0] == '4' ? defer_append : bounce_append)
+ deliver_status = (DSN_CLASS(why->dsn) == '4' ?
+ defer_append : bounce_append)
(BOUNCE_FLAGS(state.request),
- BOUNCE_ATTR(state.msg_attr, why->dsn),
+ BOUNCE_ATTR(state.msg_attr, DSN_CODE(why->dsn)),
"mailbox %s: %s", usr_attr.mailbox, vstring_str(why->vstring));
} else {
deliver_status = sent(BOUNCE_FLAGS(state.request),
#include <sent.h>
#include <mail_params.h>
#include <mbox_open.h>
+#include <dsn_util.h>
/* Application-specific. */
if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) {
deliver_status = DEL_STAT_DEFER;
} else if (mail_copy_status != 0) {
- deliver_status = (why->dsn[0] == '4' ? defer_append : bounce_append)
+ deliver_status = (DSN_CLASS(why->dsn) == '4' ?
+ defer_append : bounce_append)
(BOUNCE_FLAGS(state.request),
- BOUNCE_ATTR(state.msg_attr, why->dsn),
+ BOUNCE_ATTR(state.msg_attr, DSN_CODE(why->dsn)),
"maildir delivery failed: %s", vstring_str(why->vstring));
if (errno == EACCES) {
msg_warn("maildir access problem for UID/GID=%lu/%lu: %s",