]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.11-20130422-nonprod
authorWietse Venema <wietse@porcupine.org>
Mon, 22 Apr 2013 02:45:19 +0000 (22:45 -0400)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Tue, 23 Apr 2013 02:36:47 +0000 (22:36 -0400)
27 files changed:
postfix/.indent.pro
postfix/AAAREADME
postfix/HISTORY
postfix/Makefile.in
postfix/README_FILES/TLS_README
postfix/WISHLIST
postfix/html/Makefile.in
postfix/html/TLS_README.html
postfix/html/posttls-finger.1.html [new file with mode: 0644]
postfix/man/Makefile.in
postfix/man/man1/posttls-finger.1 [new file with mode: 0644]
postfix/mantools/postlink
postfix/proto/TLS_README.html
postfix/src/global/mail_version.h
postfix/src/posttls-finger/.indent.pro [new symlink]
postfix/src/posttls-finger/Makefile.in [new file with mode: 0644]
postfix/src/posttls-finger/posttls-finger.c [new file with mode: 0644]
postfix/src/posttls-finger/tlsmgrmem.c [new file with mode: 0644]
postfix/src/posttls-finger/tlsmgrmem.h [new file with mode: 0644]
postfix/src/smtp/smtp.h
postfix/src/smtp/smtp_connect.c
postfix/src/smtp/smtp_reuse.c
postfix/src/smtp/smtp_session.c
postfix/src/smtp/smtp_state.c
postfix/src/smtp/smtp_tls_policy.c
postfix/src/tls/tls.h
postfix/src/tls/tls_misc.c

index 8722d185ebb4e41a49e773af370fc347ea219bc5..88f1aa130f165bbba22cf4b9de3ff15046bdefb1 100644 (file)
 -Tsize_t
 -Tssize_t
 -Ttime_t
+-TRESPONSE
+-TSTATE
+-TOPTIONS
index cb11a0ada40af91aa4ff25932faadb59a1ce1567..e20091c87981dce81b375d85942fde22279a6c78 100644 (file)
@@ -164,6 +164,7 @@ Postfix daemons:
 Test programs:
 
     src/fsstone/       Measure file system overhead
+    src/posttls-finger/        Postfix SMTP/LMTP TLS probe utility
     src/smtpstone/     SMTP and QMQP server torture test
 
 Miscellaneous:
index 438539c16dac70d71c39915c94dcdb2a1df20250..270abd96771cace985f080500084f4c1989da3b0 100644 (file)
@@ -18505,3 +18505,12 @@ Apologies for any names omitted.
        creation. Temporarily link session->tls to state->tls.
        Files: smtp/smtp.h, smtp/smtp_connect.c, smtp/smtp_reuse.c,
        smtp/smtp_tls_policy.c.
+
+20130422
+
+       Feature: smtptls-finger test program for SMTP over TLS.
+       Viktor Dukhovni. Files: Makefile.in, html/Makefile.in,
+       man/Makefile.in, mantools/postlink, posttls-finger/.indent.pro,
+       posttls-finger/Makefile.in, posttls-finger/posttls-finger.c,
+       posttls-finger/tlsmgrmem.c, posttls-finger/tlsmgrmem.h,
+       tls/tls.h, tls/tls_misc.c.
index a16c89fdfc6ed7746ad0e535ff16d3bc51ff22cb..7145bc4375dde36aa815d149a4fbbe551db82d89 100644 (file)
@@ -9,7 +9,8 @@ DIRS    = src/util src/global src/dns src/tls src/xsasl src/milter src/master \
        src/postkick src/postlock src/postlog src/postmap src/postqueue \
        src/postsuper src/qmqpd src/spawn src/flush src/verify \
        src/virtual src/proxymap src/anvil src/scache src/discard src/tlsmgr \
-       src/postmulti src/postscreen src/dnsblog src/tlsproxy
+       src/postmulti src/postscreen src/dnsblog src/tlsproxy \
+       src/posttls-finger
 MANDIRS        = proto man html
 LIBEXEC        = libexec/post-install libexec/postfix-files libexec/postfix-script \
        libexec/postfix-wrapper libexec/main.cf libexec/master.cf \
index 82dfd3149486b2a6bb95c10be3b41ddc6b406e27..b66803c68a810a20e2b0cb6a1d861c8f8b1151d5 100644 (file)
@@ -958,7 +958,7 @@ administrator should publish such EE records in preference to all types.
 
 The pre-requisites for DANE support in the Postfix SMTP client are:
 
-  * An compile-time OpenSSL library that supports the TLS SNI extension and the
+  * A compile-time OpenSSL library that supports the TLS SNI extension and the
     "sha256" and "sha512" message digests.
   * A compile-time DNS resolver library that supports DNSSEC. Postfix binaries
     built on an older system will not support DNSSEC even if deployed on a
@@ -976,8 +976,8 @@ plans to implement SNI in the Postfix SMTP server.
 Note: The Postfix SMTP client's internal stub DNS resolver is DNSSEC-aware, but
 it does not itself validate DNSSEC records, rather it delegates DNSSEC
 validation to the operating system's configured recursive DNS nameserver. The
-Postfix DNS resolver relies on a secure-channel to the cache for DNSSEC
-integrity, but does not support TSIG to protect the transmission channel
+Postfix DNS client relies on a secure channel to the resolver's cache for
+DNSSEC integrity, but does not support TSIG to protect the transmission channel
 between itself and the nameserver. Therefore, it is strongly recommended (DANE
 security guarantee void otherwise) that each MTA run a local DNSSEC-validating
 recursive resolver ("unbound" from nlnetlabs.nl is a reasonable choice)
index 7170996e43329f13ebeae633d9a5e9836059f7aa..be937b5c592c3c75a4154bfcec1f43b535bb4de5 100644 (file)
@@ -8,6 +8,16 @@ Wish list:
 
        Spellcheck and double-word check.
 
+       Fix a false cache-sharing problem. After the SASL handshake,
+       the connection cache client does not store SASL credentials
+       in the destination properties; it stores them in the endpoint
+       label only.  When the connection cache client reuses the
+       connection with smtp_reuse_nexthop(), it does not restore
+       the SASL credentials.  When it saves the connection afterwards,
+       it creates a new endpoint label without SASL credentials,
+       so the authenticated connection can now be used for unrelated
+       deliveries.
+
        Begin code revision, after DANE support stabilizes.  This
        should be one pass that changes only names and no code.
 
index 2778e7bac97ccfb337833029f14b23469a4f26db..39b47d70712b9022c8c6d893fc15ce0a4024dda2 100644 (file)
@@ -13,7 +13,7 @@ COMMANDS= mailq.1.html newaliases.1.html postalias.1.html postcat.1.html \
        postconf.1.html postfix.1.html postkick.1.html postlock.1.html \
        postlog.1.html postdrop.1.html postmap.1.html postmulti.1.html \
        postqueue.1.html postsuper.1.html sendmail.1.html \
-       smtp-source.1.html smtp-sink.1.html \
+       smtp-source.1.html smtp-sink.1.html posttls-finger.1.html \
        qmqp-source.1.html qmqp-sink.1.html \
        qshape.1.html
 CONFIG = access.5.html aliases.5.html canonical.5.html relocated.5.html \
@@ -225,6 +225,10 @@ smtp-sink.1.html: ../src/smtpstone/smtp-sink.c
        PATH=../mantools:$$PATH; \
        srctoman $? | $(AWK) | nroff -man | uniq | $(MAN2HTML) | postlink >$@
 
+posttls-finger.1.html: ../src/posttls-finger/posttls-finger.c
+       PATH=../mantools:$$PATH; \
+       srctoman $? | $(AWK) | nroff -man | uniq | $(MAN2HTML) | postlink >$@
+
 qmqp-source.1.html: ../src/smtpstone/qmqp-source.c
        PATH=../mantools:$$PATH; \
        srctoman $? | $(AWK) | nroff -man | uniq | $(MAN2HTML) | postlink >$@
index 5dcff92fa873b46da04a633996fb76e95392736e..2dde0c0bb82f0fd3dd3fad23cca3f75a35294bd1 100644 (file)
@@ -204,7 +204,7 @@ or via public-key infrastructure. This means that the Postfix server
 public-key certificate file must include the server certificate
 first, then the issuing CA(s) (bottom-up order). The Postfix SMTP
 server certificate must be usable as SSL server certificate and
-hence pass the "<tt>openssl verify -purpose sslserver ...<tt>" test.
+hence pass the "<tt>openssl verify -purpose sslserver ...</tt>" test.
 </p>
 
 <p> The examples that follow show how to create a server certificate
@@ -1296,7 +1296,7 @@ EE records in preference to all types. </p>
 
 <p> The pre-requisites for DANE support in the Postfix SMTP client are: </p>
 <ul>
-<li> An <i>compile-time</i> OpenSSL library that supports the TLS SNI
+<li> A <i>compile-time</i> OpenSSL library that supports the TLS SNI
 extension and the "sha256" and "sha512" message digests.
 <li> A <i>compile-time</i> DNS resolver library that supports DNSSEC.
 Postfix binaries built on an older system will not support DNSSEC even
@@ -1314,8 +1314,8 @@ records.  There are no plans to implement SNI in the Postfix SMTP server.  </p>
 <p> Note: The Postfix SMTP client's internal stub DNS resolver is
 DNSSEC-aware, but it does not itself validate DNSSEC records, rather
 it delegates DNSSEC validation to the operating system's configured
-recursive DNS nameserver.  The Postfix DNS resolver relies on a
-secure-channel to the cache for DNSSEC integrity, but does not
+recursive DNS nameserver.  The Postfix DNS client relies on a secure
+channel to the resolver's cache for DNSSEC integrity, but does not
 support TSIG to protect the transmission channel between itself and
 the nameserver.  Therefore, it is strongly recommended (DANE security
 guarantee void otherwise) that each MTA run a local DNSSEC-validating
diff --git a/postfix/html/posttls-finger.1.html b/postfix/html/posttls-finger.1.html
new file mode 100644 (file)
index 0000000..4555336
--- /dev/null
@@ -0,0 +1,346 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
+<title> Postfix manual - posttls-finger(1) </title>
+</head> <body> <pre>
+POSTTLS-FINGER(1)                                            POSTTLS-FINGER(1)
+
+<b>NAME</b>
+       posttls-finger  -  Probe the TLS properties of an ESMTP or
+       LMTP server.
+
+<b>SYNOPSIS</b>
+       <b>posttls-finger</b> [<i>options</i>] [<b>inet:</b>]<i>domain</i>[:<i>port</i>] [<i>match ...</i>]
+       <b>posttls-finger</b> -S [<i>options</i>] <b>unix:</b><i>pathname</i> [<i>match ...</i>]
+
+<b>DESCRIPTION</b>
+       <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> connects to  the  specified  destination
+       and reports TLS-related information about the server. With
+       SMTP, the destination is a domainname;  with  LMTP  it  is
+       either a domainname prefixed with <b>inet:</b> or a pathname pre-
+       fixed with <b>unix:</b>.  If Postfix is built  without  TLS  sup-
+       port,  the  resulting posttls-finger program has very lim-
+       ited functionality, and only the <b>-a</b>, <b>-c</b>, <b>-h</b>, <b>-o</b>,  <b>-S</b>,  <b>-t</b>,
+       <b>-T</b> and <b>-v</b> options are available.
+
+       Note:  this  is an unsupported test program. No attempt is
+       made to maintain  compatibility  between  successive  ver-
+       sions.
+
+       For SMTP servers that don't support ESMTP, only the greet-
+       ing banner and the negative EHLO  response  are  reported.
+       Otherwise,  the  reported  EHLO  response  details further
+       server capabilities.
+
+       If TLS support is enabled when <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a>  is  com-
+       piled,  and  the server supports <b>STARTTLS</b>, a TLS handshake
+       is attempted.
+
+       If DNSSEC support is available, the connection  TLS  secu-
+       rity  level  (<b>-l</b>  option) defaults to <b>dane</b>; see <a href="TLS_README.html">TLS_README</a>
+       for details. Otherwise, it defaults to <b>secure</b>.  This  set-
+       ting determines the certificate matching policy.
+
+       If  TLS  negotiation succeeds, the TLS protocol and cipher
+       details are reported. The server certificate is then veri-
+       fied  in  accordance  with  the  policy  at the chosen (or
+       default) security level.  With public CA-based trust, when
+       the  <b>-L</b>  option includes <b>certmatch</b>, (true by default) name
+       matching is performed even if the certificate chain is not
+       trusted.   This  logs  the  names found in the remote SMTP
+       server certificate and which if any would match, were  the
+       certificate chain trusted.
+
+       Note:   <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a>   does  not  perform  any  table
+       lookups, so the TLS policy  table  and  obsolete  per-site
+       tables  are  not  consulted.  It does not communicate with
+       the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> daemon (or any other Postfix  daemons);  its
+       TLS  session  cache  is held in private memory, and disap-
+       pears when the process exits.
+
+       With the <b>-r</b> <i>delay</i> option, if the server assigns a TLS ses-
+       sion id, the TLS session is cached. The connection is then
+       closed and re-opened after the specified delay, and  <a href="posttls-finger.1.html"><b>post-</b></a>
+       <a href="posttls-finger.1.html"><b>tls-finger</b>(1)</a>  then reports whether the cached TLS session
+       was re-used.
+
+       When the destination is a load-balancer, it  may  be  dis-
+       tributing  load between multiple server caches. Typically,
+       each server returns its unique name in its EHLO  response.
+       If,  upon  reconnecting  with  <b>-r</b>,  a  new  server name is
+       detected, another session is cached for  the  new  server,
+       and  the  reconnect  is repeated up to a maximum number of
+       times (default 5) that can be specified via the <b>-m</b> option.
+
+       The choice of SMTP or LMTP (<b>-S</b> option) determines the syn-
+       tax of the destination argument. With SMTP, one can  spec-
+       ify  a  service on a non-default port as <i>host</i>:<i>service</i>, and
+       disable MX (mail exchanger) DNS  lookups  with  [<i>host</i>]  or
+       [<i>host</i>]:<i>port</i>.   The [] form is required when you specify an
+       IP address instead of a hostname.  An IPv6  address  takes
+       the  form  [<b>ipv6:</b><i>address</i>].   The  default port for SMTP is
+       taken from the <b>smtp/tcp</b> entry in /etc/services, defaulting
+       to 25 if the entry is not found.
+
+       With  LMTP,  specify  <b>unix:</b><i>pathname</i>  to connect to a local
+       server listening on a  unix-domain  socket  bound  to  the
+       specified  pathname;  otherwise, specify an optional <b>inet:</b>
+       prefix followed by a <i>domain</i> and an optional port, with the
+       same  syntax as for SMTP. The default TCP port for LMTP is
+       24.
+
+       Arguments:
+
+       <b>-a</b>     Address family preference: <b>ipv4</b>, <b>ipv6</b> or <b>any</b>.  When
+              using  <b>any</b>, posttls-finger will randomly select one
+              of the two as the more preferred, and  exhaust  all
+              MX  preferences for the first address family before
+              trying any addresses for the  other.   The  default
+              value is <b>any</b>.
+
+       <b>-A</b>     A  list  of  PEM  trust-anchor files that overrides
+              CAfile and CApath trust chain verification.   Spec-
+              ify  the  option multiple times to specify multiple
+              files.    See   the   <a href="postconf.5.html">main.cf</a>   documentation   for
+              <a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a> for details.
+
+       <b>-c</b>     Disable  SMTP chat logging; only TLS-related infor-
+              mation is logged.
+
+       <b>-C</b>     Print the  remote  SMTP  server  certificate  trust
+              chain  in  PEM  format.  The issuer DN, subject DN,
+              certificate and public  key  fingerprints  (see  <b>-d</b>
+              <i>mdalg</i> option below) are printed above each PEM cer-
+              tificate block.  If you specify  <b>-F</b>  <i>CAfile</i>  or  <b>-P</b>
+              <i>CApath</i>,  the  OpenSSL library may augment the chain
+              with  missing  issuer  certificates.   To  see  the
+              actual  chain  sent by the remote SMTP server leave
+              <i>CAfile</i> and <i>CApath</i> unset.
+
+       <b>-d</b> <i>mdalg</i>
+              The message digest algorithm to use  for  reporting
+              remote   SMTP   server  fingerprints  and  matching
+              against  user  provided  certificate   fingerprints
+              (with  DANE TLSA records the algorithm is specified
+              in the DNS).  The default algorithm is sha1.
+
+       <b>-F</b> <i>CAfile.pem</i>
+              The PEM formatted CAfile  for  remote  SMTP  server
+              certificate  verification.  By default no CAfile is
+              used and no public CAs are trusted.
+
+       <b>-h</b> <i>host</i><b>_</b><i>lookup</i>
+              The hostname lookup methods used  for  the  connec-
+              tion.   See  the  documentation of <a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a>
+              for syntax and semantics.
+
+       <b>-l</b> <i>level</i>
+              The security level for the connection, default <b>dane</b>
+              or <b>secure</b> depending on whether DNSSEC is available.
+              For syntax and semantics, see the documentation  of
+              <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>.   When  <b>dane</b>  is supported
+              and selected, if no TLSA records are found, or  all
+              the  records found are unusable, the <i>degraded</i> level
+              will be <b>secure</b>.  The main additional level to  con-
+              sider  is  <b>fingerprint</b>,  which allows you test cer-
+              tificate or public-key fingerprint  matches  before
+              you deploy them in the policy table.
+
+              Note,   since   <b>posttls-finger</b>  does  not  actually
+              deliver any email, the <b>none</b>, <b>may</b> and <b>encrypt</b>  secu-
+              rity  levels  are  not  very useful.  Since <b>may</b> and
+              <b>encrypt</b> don't require peer certificates, they  will
+              often  negotiate anonymous TLS ciphersuites, so you
+              won't learn much about  the  remote  SMTP  server's
+              certificates  at  these  levels if it also supports
+              anonymous TLS (though you may learn that the server
+              supports anonymous TLS).
+
+       <b>-L</b> <i>logopts</i>
+              Fine-grained  TLS  logging options. To tune the TLS
+              features logged during the TLS  handshake,  specify
+              one or more of:
+
+              0, none
+                     These yield no TLS logging; you'll generally
+                     want more, but this is  handy  if  you  just
+                     want the trust chain:
+                     $ posttls-finger -cC -L none destination
+
+              1, routine, summary
+                     These  synonymous values yield a normal one-
+                     line summary of the TLS connection.
+
+              2, debug
+                     These  synonymous  values  combine  routine,
+                     ssl-debug, cache and verbose.
+
+              3, ssl-expert
+                     These  synonymous  values combine debug with
+                     ssl-handshake-packet-dump.    For    experts
+                     only.
+
+              4, ssl-developer
+                     These  synonymous  values combine ssl-expert
+                     with ssl-session-packet-dump.   For  experts
+                     only,  and  in  most  cases,  use  wireshark
+                     instead.
+
+              ssl-debug
+                     Turn on OpenSSL logging of the  progress  of
+                     the SSL handshake.
+
+              ssl-handshake-packet-dump
+                     Log  hexadecimal  packet  dumps  of  the SSL
+                     handshake; for experts only.
+
+              ssl-session-packet-dump
+                     Log hexadecimal packet dumps of  the  entire
+                     SSL  session;  only  useful to those who can
+                     debug SSL protocol problems from hex  dumps.
+
+              untrusted
+                     Logs   trust  chain  verification  problems.
+                     This is turned on automatically at  security
+                     levels  that  use  peer names signed by cer-
+                     tificate authorities  to  validate  certifi-
+                     cates.  So while this setting is recognized,
+                     you should never need to set it  explicitly.
+
+              peercert
+                     This  logs  a one line summary of the remote
+                     SMTP server certificate subject, issuer, and
+                     fingerprints.
+
+              certmatch
+                     This  logs  remote  SMTP  server certificate
+                     matching, showing the CN  and  each  subjec-
+                     tAltName and which name matched.  With DANE,
+                     logs matching of  TLSA  record  trust-anchor
+                     and end-entity certificates.
+
+              cache  This  logs session cache operations, showing
+                     whether session caching  is  effective  with
+                     the  remote SMTP server.  Automatically used
+                     when reconnecting with the <b>-r</b> option; rarely
+                     needs to be set explicitly.
+
+              verbose
+                     Enables  verbose  logging in the Postfix TLS
+                     driver; includes all of peercert..cache  and
+                     more.
+
+              The  default  is  <b>routine,certmatch</b>. After a recon-
+              nect,  the  log  level  is   unconditionally   <b>rou-</b>
+              <b>tine,cache</b>.
+
+       <b>-m</b> <i>count</i>
+              When  the  <b>-r</b>  <i>delay</i>  option  is  specified, the <b>-m</b>
+              option determines the maximum number  of  reconnect
+              attempts  to  use  with a server behind a load-bal-
+              acer, to see whether connection caching  is  likely
+              to  be  effective  for this destination.  Some MTAs
+              don't expose  the  underlying  server  identity  in
+              their  EHLO response; with these servers there will
+              never be more than 1 reconnection attempt.
+
+       <b>-o</b> <i>name=value</i>
+              Specify zero or more times to override the value of
+              the  <a href="postconf.5.html">main.cf</a>  parameter  <i>name</i> with <i>value</i>.  Possible
+              use-cases include  overriding  the  values  of  TLS
+              library parameters or "<a href="postconf.5.html#myhostname">myhostname</a>" to configure the
+              SMTP EHLO name sent to the remote server.
+
+       <b>-P</b> <i>CApath/</i>
+              The  OpenSSL   CApath/   directory   (indexed   via
+              c_rehash(1))  for  remote  SMTP  server certificate
+              verification.  By default no CApath is used and  no
+              public CAs are trusted.
+
+       <b>-r</b> <i>delay</i>
+              With  a cachable TLS session, disconnect and recon-
+              nect after <i>delay</i> seconds. Report whether  the  ses-
+              sion  is  re-used. Retry if a new server is encoun-
+              tered, up to 5 times or as specified  with  the  <b>-m</b>
+              option.
+
+       <b>-S</b>     Disable  SMTP;  that is, connect to an LMTP server.
+              The default port for LMTP over TCP is 24.  Alterna-
+              tive  ports  can  specified by appending "<i>:service-</i>
+              <i>name</i>" or ":<i>portnumber</i>" to the destination argument.
+
+       <b>-t</b> <i>timeout</i>
+              The  TCP  connection  timeout to use.  This is also
+              the timeout for reading  the  remote  server's  220
+              banner.
+
+       <b>-T</b> <i>timeout</i>
+              The SMTP/LMTP command timeout for EHLO/LHLO, START-
+              TLS and QUIT.
+
+       <b>-v</b>     Enable more verbose logging.
+
+       [<b>inet:</b>]<i>domain</i>[:<i>port</i>]
+              Connect via TCP to domain <i>domain</i>,  port  <i>port</i>.  The
+              default  port is <b>smtp</b> (or 24 with LMTP).  With SMTP
+              an MX lookup is performed to resolve the domain  to
+              a  host,  unless  the domain is enclosed in <b>[]</b>.  If
+              you want to connect to  a  specific  MX  host,  for
+              instance <i>mx1.example.com</i>, specify [<i>mx1.example.com</i>]
+              as the destination and <i>example.com</i> as a <b>match</b> argu-
+              ment.   When  using  DNS, the destination domain is
+              assumed fully qualified and no  default  domain  or
+              search  suffixes  are  applied; you must use fully-
+              qualified names or also enable <b>native</b> host  lookups
+              (these  don't  support <b>dane</b> as no DNSSEC validation
+              information is available via <b>native</b> lookups).
+
+       <b>unix:</b><i>pathname</i>
+              Connect to the UNIX-domain socket at <i>pathname</i>. LMTP
+              only.
+
+       <b>match ...</b>
+              With  no  match  arguments  specified,  certificate
+              peername  matching  uses  the  compiled-in  default
+              strategies for each security level.  If you specify
+              one or more arguments, these will be  used  as  the
+              list  of certificate or public-key digests to match
+              for the <b>fingerprint</b> level, or as the  list  of  DNS
+              names to match in the certificate at the <b>verify</b> and
+              <b>secure</b> levels.  If the security level is <b>dane</b>,  and
+              usable  TLSA records are found, the match names are
+              ignored, and <b>hostname, nexthop</b> strategies are used.
+              If  no  TLSA  records are found or none are usable,
+              the level <i>degrades</i> to <b>secure</b> and the provided  list
+              is used to match names in the certificate.
+
+<b>ENVIRONMENT</b>
+       <b>MAIL_CONFIG</b>
+              Read  configuration  parameters  from a non-default
+              location.
+
+       <b>MAIL_VERBOSE</b>
+              Same as <b>-v</b> option.
+
+<b>SEE ALSO</b>
+       <a href="smtp-source.1.html">smtp-source(1)</a>, SMTP/LMTP message source
+       <a href="smtp-sink.1.html">smtp-sink(1)</a>, SMTP/LMTP message dump
+
+<b>README FILES</b>
+       <a href="TLS_README.html">TLS_README</a>, Postfix STARTTLS howto
+
+<b>LICENSE</b>
+       The  Secure  Mailer  license must be distributed with this
+       software.
+
+<b>AUTHOR(S)</b>
+       Wietse Venema
+       IBM T.J. Watson Research
+       P.O. Box 704
+       Yorktown Heights, NY 10598, USA
+
+       Viktor Dukhovni
+
+                                                             POSTTLS-FINGER(1)
+</pre> </body> </html>
index c8a80e53bcdb12aae7746c918c6a888a8fed4f57..e096a364ed3f12c3a5a88812e29431147fbdd416 100644 (file)
@@ -22,7 +22,7 @@ CONFIG        = man5/access.5 man5/aliases.5 man5/canonical.5 man5/relocated.5 \
        man5/generic.5 man5/bounce.5 man5/postfix-wrapper.5 \
        man5/sqlite_table.5
 TOOLS  = man1/smtp-sink.1 man1/smtp-source.1 man1/qmqp-sink.1 \
-       man1/qmqp-source.1 man1/qshape.1
+       man1/qmqp-source.1 man1/qshape.1 man1/posttls-finger.1
 
 update:        $(DAEMONS) $(COMMANDS) $(CONFIG) $(TOOLS)
 
@@ -322,6 +322,11 @@ man1/smtp-source.1: ../src/smtpstone/smtp-source.c
            (cmp -s junk $? || mv junk $?) && rm -f junk
        ../mantools/srctoman $? >$@
 
+man1/posttls-finger.1: ../src/posttls-finger/posttls-finger.c
+       ../mantools/fixman ../proto/postconf.proto $? >junk && \
+           (cmp -s junk $? || mv junk $?) && rm -f junk
+       ../mantools/srctoman $? >$@
+
 man5/tcp_table.5: ../proto/tcp_table
        ../mantools/srctoman - $? >$@
 
diff --git a/postfix/man/man1/posttls-finger.1 b/postfix/man/man1/posttls-finger.1
new file mode 100644 (file)
index 0000000..b9855a7
--- /dev/null
@@ -0,0 +1,290 @@
+.TH POSTTLS-FINGER 1 
+.ad
+.fi
+.SH NAME
+posttls-finger
+\-
+Probe the TLS properties of an ESMTP or LMTP server.
+.SH "SYNOPSIS"
+.na
+.nf
+\fBposttls-finger\fR [\fIoptions\fR] [\fBinet:\fR]\fIdomain\fR[:\fIport\fR] [\fImatch ...\fR]
+.br
+\fBposttls-finger\fR -S [\fIoptions\fR] \fBunix:\fIpathname\fR [\fImatch ...\fR]
+.SH DESCRIPTION
+.ad
+.fi
+\fBposttls-finger\fR(1) connects to the specified destination
+and reports TLS-related information about the server. With SMTP, the
+destination is a domainname; with LMTP it is either a domainname
+prefixed with \fBinet:\fR or a pathname prefixed with \fBunix:\fR.  If
+Postfix is built without TLS support, the resulting posttls-finger
+program has very limited functionality, and only the \fB-a\fR, \fB-c\fR,
+\fB-h\fR, \fB-o\fR, \fB-S\fR, \fB-t\fR, \fB-T\fR and \fB-v\fR options
+are available.
+
+Note: this is an unsupported test program. No attempt is made
+to maintain compatibility between successive versions.
+
+For SMTP servers that don't support ESMTP, only the greeting banner
+and the negative EHLO response are reported. Otherwise, the reported
+EHLO response details further server capabilities.
+
+If TLS support is enabled when \fBposttls-finger\fR(1) is compiled, and
+the server supports \fBSTARTTLS\fR, a TLS handshake is attempted.
+
+If DNSSEC support is available, the connection TLS security level
+(\fB-l\fR option) defaults to \fBdane\fR; see TLS_README for
+details. Otherwise, it defaults to \fBsecure\fR.  This setting
+determines the certificate matching policy.
+
+If TLS negotiation succeeds, the TLS protocol and cipher details are
+reported. The server certificate is then verified in accordance with
+the policy at the chosen (or default) security level.  With public
+CA-based trust, when the \fB-L\fR option includes \fBcertmatch\fR,
+(true by default) name matching is performed even if the certificate
+chain is not trusted.  This logs the names found in the remote SMTP
+server certificate and which if any would match, were the certificate
+chain trusted.
+
+Note: \fBposttls-finger\fR(1) does not perform any table lookups, so
+the TLS policy table and obsolete per-site tables are not consulted.
+It does not communicate with the \fBtlsmgr\fR(8) daemon (or any other
+Postfix daemons); its TLS session cache is held in private memory, and
+disappears when the process exits.
+
+With the \fB-r \fIdelay\fR option, if the server assigns a TLS
+session id, the TLS session is cached. The connection is then closed
+and re-opened after the specified delay, and \fBposttls-finger\fR(1)
+then reports whether the cached TLS session was re-used.
+
+When the destination is a load-balancer, it may be distributing
+load between multiple server caches. Typically, each server returns
+its unique name in its EHLO response. If, upon reconnecting with
+\fB-r\fR, a new server name is detected, another session is cached
+for the new server, and the reconnect is repeated up to a maximum
+number of times (default 5) that can be specified via the \fB-m\fR
+option.
+
+The choice of SMTP or LMTP (\fB-S\fR option) determines the syntax of
+the destination argument. With SMTP, one can specify a service on a
+non-default port as \fIhost\fR:\fIservice\fR, and disable MX (mail
+exchanger) DNS lookups with [\fIhost\fR] or [\fIhost\fR]:\fIport\fR.
+The [] form is required when you specify an IP address instead of a
+hostname.  An IPv6 address takes the form [\fBipv6:\fIaddress\fR].
+The default port for SMTP is taken from the \fBsmtp/tcp\fR entry in
+/etc/services, defaulting to 25 if the entry is not found.
+
+With LMTP, specify \fBunix:\fIpathname\fR to connect to a local server
+listening on a unix-domain socket bound to the specified pathname;
+otherwise, specify an optional \fBinet:\fR prefix followed by a
+\fIdomain\fR and an optional port, with the same syntax as for
+SMTP. The default TCP port for LMTP is 24.
+
+Arguments:
+.IP "\fB-a\fR"
+Address family preference: \fBipv4\fR, \fBipv6\fR or \fBany\fR.  When
+using \fBany\fR, posttls-finger will randomly select one of the two as
+the more preferred, and exhaust all MX preferences for the first
+address family before trying any addresses for the other.  The
+default value is \fBany\fR.
+.IP "\fB-A\fR"
+A list of PEM trust-anchor files that overrides CAfile and CApath
+trust chain verification.  Specify the option multiple times to
+specify multiple files.  See the main.cf documentation for
+smtp_tls_trust_anchor_file for details.
+.IP "\fB-c\fR"
+Disable SMTP chat logging; only TLS-related information is logged.
+.IP "\fB-C\fR"
+Print the remote SMTP server certificate trust chain in PEM format.
+The issuer DN, subject DN, certificate and public key fingerprints
+(see \fB-d \fImdalg\fR option below) are printed above each PEM
+certificate block.  If you specify \fB-F \fICAfile\fR or
+\fB-P \fICApath\fR, the OpenSSL library may augment the chain with
+missing issuer certificates.  To see the actual chain sent by the
+remote SMTP server leave \fICAfile\fR and \fICApath\fR unset.
+.IP "\fB-d \fImdalg\fR"
+The message digest algorithm to use for reporting remote SMTP server
+fingerprints and matching against user provided certificate
+fingerprints (with DANE TLSA records the algorithm is specified
+in the DNS).  The default algorithm is sha1.
+.IP "\fB-F \fICAfile.pem\fR"
+The PEM formatted CAfile for remote SMTP server certificate
+verification.  By default no CAfile is used and no public CAs
+are trusted.
+.IP "\fB-h \fIhost_lookup\fR"
+The hostname lookup methods used for the connection.  See the
+documentation of smtp_host_lookup for syntax and semantics.
+.IP "\fB-l \fIlevel\fR"
+The security level for the connection, default \fBdane\fR or
+\fBsecure\fR depending on whether DNSSEC is available. For syntax
+and semantics, see the documentation of smtp_tls_security_level.
+When \fBdane\fR is supported and selected, if no TLSA records are
+found, or all the records found are unusable, the \fIdegraded\fR
+level will be \fBsecure\fR.  The main additional level to consider
+is \fBfingerprint\fR, which allows you test certificate or public-key
+fingerprint matches before you deploy them in the policy table.
+.IP
+Note, since \fBposttls-finger\fR does not actually deliver any email,
+the \fBnone\fR, \fBmay\fR and \fBencrypt\fR security levels are not
+very useful.  Since \fBmay\fR and \fBencrypt\fR don't require peer
+certificates, they will often negotiate anonymous TLS ciphersuites,
+so you won't learn much about the remote SMTP server's certificates
+at these levels if it also supports anonymous TLS (though you may
+learn that the server supports anonymous TLS).
+.IP "\fB-L \fIlogopts\fR"
+Fine-grained TLS logging options. To tune the TLS features logged
+during the TLS handshake, specify one or more of:
+.RS
+.IP "0, none"
+These yield no TLS logging; you'll generally want more, but this
+is handy if you just want the trust chain:
+.RS
+.ad
+.nf
+$ posttls-finger -cC -L none destination
+.fi
+.RE
+.IP "1, routine, summary"
+These synonymous values yield a normal one-line summary of the TLS
+connection.
+.IP "2, debug"
+These synonymous values combine routine, ssl-debug, cache and verbose.
+.IP "3, ssl-expert"
+These synonymous values combine debug with ssl-handshake-packet-dump.
+For experts only.
+.IP "4, ssl-developer"
+These synonymous values combine ssl-expert with ssl-session-packet-dump.
+For experts only, and in most cases, use wireshark instead.
+.IP ssl-debug
+Turn on OpenSSL logging of the progress of the SSL handshake.
+.IP ssl-handshake-packet-dump
+Log hexadecimal packet dumps of the SSL handshake; for experts only.
+.IP ssl-session-packet-dump
+Log hexadecimal packet dumps of the entire SSL session; only useful
+to those who can debug SSL protocol problems from hex dumps.
+.IP untrusted
+Logs trust chain verification problems.  This is turned on
+automatically at security levels that use peer names signed
+by certificate authorities to validate certificates.  So while
+this setting is recognized, you should never need to set it
+explicitly.
+.IP peercert
+This logs a one line summary of the remote SMTP server certificate
+subject, issuer, and fingerprints.
+.IP certmatch
+This logs remote SMTP server certificate matching, showing the CN
+and each subjectAltName and which name matched.  With DANE, logs
+matching of TLSA record trust-anchor and end-entity certificates.
+.IP cache
+This logs session cache operations, showing whether session caching
+is effective with the remote SMTP server.  Automatically used when
+reconnecting with the \fB-r\fR option; rarely needs to be set
+explicitly.
+.IP verbose
+Enables verbose logging in the Postfix TLS driver; includes all of
+peercert..cache and more.
+.RE
+.IP
+The default is \fBroutine,certmatch\fR. After a reconnect, the log
+level is unconditionally \fBroutine,cache\fR.
+.IP "\fB-m \fIcount\fR"
+When the \fB-r \fIdelay\fR option is specified, the \fB-m\fR option
+determines the maximum number of reconnect attempts to use with
+a server behind a load-balacer, to see whether connection caching
+is likely to be effective for this destination.  Some MTAs don't
+expose the underlying server identity in their EHLO response; with
+these servers there will never be more than 1 reconnection attempt.
+.IP "\fB-o \fIname=value\fR"
+Specify zero or more times to override the value of the main.cf
+parameter \fIname\fR with \fIvalue\fR.  Possible use-cases include
+overriding the values of TLS library parameters or "myhostname" to
+configure the SMTP EHLO name sent to the remote server.
+.IP "\fB-P \fICApath/\fR"
+The OpenSSL CApath/ directory (indexed via c_rehash(1)) for remote
+SMTP server certificate verification.  By default no CApath is used
+and no public CAs are trusted.
+.IP "\fB-r \fIdelay\fR"
+With a cachable TLS session, disconnect and reconnect after \fIdelay\fR
+seconds. Report whether the session is re-used. Retry if a new server
+is encountered, up to 5 times or as specified with the \fB-m\fR option.
+.IP "\fB-S\fR"
+Disable SMTP; that is, connect to an LMTP server. The default port for
+LMTP over TCP is 24.  Alternative ports can specified by appending
+"\fI:servicename\fR" or ":\fIportnumber\fR" to the destination
+argument.
+.IP "\fB-t \fItimeout\fR"
+The TCP connection timeout to use.  This is also the timeout for
+reading the remote server's 220 banner.
+.IP "\fB-T \fItimeout\fR"
+The SMTP/LMTP command timeout for EHLO/LHLO, STARTTLS and QUIT.
+.IP "\fB-v\fR"
+Enable more verbose logging.
+.IP "[\fBinet:\fR]\fIdomain\fR[:\fIport\fR]"
+Connect via TCP to domain \fIdomain\fR, port \fIport\fR. The default
+port is \fBsmtp\fR (or 24 with LMTP).  With SMTP an MX lookup is
+performed to resolve the domain to a host, unless the domain is
+enclosed in \fB[]\fR.  If you want to connect to a specific MX host,
+for instance \fImx1.example.com\fR, specify [\fImx1.example.com\fR]
+as the destination and \fIexample.com\fR as a \fBmatch\fR argument.
+When using DNS, the destination domain is assumed fully qualified
+and no default domain or search suffixes are applied; you must use
+fully-qualified names or also enable \fBnative\fR host lookups
+(these don't support \fBdane\fR as no DNSSEC validation information
+is available via \fBnative\fR lookups).
+.IP "\fBunix:\fIpathname\fR"
+Connect to the UNIX-domain socket at \fIpathname\fR. LMTP only.
+.IP "\fBmatch ...\fR"
+With no match arguments specified, certificate peername matching uses
+the compiled-in default strategies for each security level.  If you
+specify one or more arguments, these will be used as the list of
+certificate or public-key digests to match for the \fBfingerprint\fR
+level, or as the list of DNS names to match in the certificate at the
+\fBverify\fR and \fBsecure\fR levels.  If the security level is
+\fBdane\fR, and usable TLSA records are found, the match names are
+ignored, and \fBhostname, nexthop\fR strategies are used.  If no
+TLSA records are found or none are usable, the level \fIdegrades\fR
+to \fBsecure\fR and the provided list is used to match names in
+the certificate.
+.ad
+.fi
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP \fBMAIL_CONFIG\fR
+Read configuration parameters from a non-default location.
+.IP \fBMAIL_VERBOSE\fR
+Same as \fB-v\fR option.
+.SH "SEE ALSO"
+.na
+.nf
+smtp-source(1), SMTP/LMTP message source
+smtp-sink(1), SMTP/LMTP message dump
+
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+html_directory\fR" to locate this information.
+.na
+.nf
+TLS_README, Postfix STARTTLS howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.J. Watson Research
+P.O. Box 704
+Yorktown Heights, NY 10598, USA
+
+Viktor Dukhovni
index 8b85aa42c37d1734a61bca341a4c056224d9a45b..e8c6a3bbbf28808c260a6af5c4da0bb3c7f58331 100755 (executable)
@@ -790,6 +790,7 @@ while (<>) {
     s/[<bB>]*postmulti[<\/bB>]*\(1\)/<a href="postmulti.1.html">$&<\/a>/g;
     s/[<bB>]*postqueue[<\/bB>]*\(1\)/<a href="postqueue.1.html">$&<\/a>/g;
     s/[<bB>]*postsuper[<\/bB>]*\(1\)/<a href="postsuper.1.html">$&<\/a>/g;
+    s/[<bB>]*post[-<\/bB>]*\n*[ <bB>]*tls-finger[<\/bB>]*\(1\)/<a href="posttls-finger.1.html">$&<\/a>/g;
     s/[<bB>]*send[-<\/bB>]*\n*[ <bB>]*mail[<\/bB>]*\(1\)/<a href="sendmail.1.html">$&<\/a>/g;
     s/[<bB>]*smtp-[<\/bB>]*\n* *[<bB>]*source[<\/bB>]*\(1\)/<a href="smtp-source.1.html">$&<\/a>/g;
     s/[<bB>]*smtp-[<\/bB>]*\n* *[<bB>]*sink[<\/bB>]*\(1\)/<a href="smtp-sink.1.html">$&<\/a>/g;
index 74492e8c2dcac30653f44c52cd217f8a641518a6..c70ed49725f91156850e946e392414861c0afeee 100644 (file)
@@ -204,7 +204,7 @@ or via public-key infrastructure. This means that the Postfix server
 public-key certificate file must include the server certificate
 first, then the issuing CA(s) (bottom-up order). The Postfix SMTP
 server certificate must be usable as SSL server certificate and
-hence pass the "<tt>openssl verify -purpose sslserver ...<tt>" test.
+hence pass the "<tt>openssl verify -purpose sslserver ...</tt>" test.
 </p>
 
 <p> The examples that follow show how to create a server certificate
index e333c40124704f679b01c6e8a67c5438034c5972..db9bbf94f5efcb9fb962f7b56c05ded44ad7945c 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20130421"
+#define MAIL_RELEASE_DATE      "20130422"
 #define MAIL_VERSION_NUMBER    "2.11"
 
 #ifdef SNAPSHOT
diff --git a/postfix/src/posttls-finger/.indent.pro b/postfix/src/posttls-finger/.indent.pro
new file mode 120000 (symlink)
index 0000000..5c837ec
--- /dev/null
@@ -0,0 +1 @@
+../../.indent.pro
\ No newline at end of file
diff --git a/postfix/src/posttls-finger/Makefile.in b/postfix/src/posttls-finger/Makefile.in
new file mode 100644 (file)
index 0000000..9be2550
--- /dev/null
@@ -0,0 +1,99 @@
+SHELL  = /bin/sh
+SRCS   = posttls-finger.c tlsmgrmem.c
+OBJS   = posttls-finger.o tlsmgrmem.o
+HDRS   = tlsmgrmem.h
+TESTSRC        = 
+DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= 
+INC_DIR        = ../../include
+PROG   = posttls-finger
+LIBS   = ../../lib/libtls.a ../../lib/libdns.a \
+       ../../lib/libglobal.a ../../lib/libutil.a
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all:   $(PROG)
+
+$(OBJS): ../../conf/makedefs.out $(HDRS)
+
+Makefile: Makefile.in
+       cat ../../conf/makedefs.out $? >$@
+
+posttls-finger: $(OBJS) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+test:  $(TESTPROG)
+
+tests:
+
+root_tests:
+
+update: ../../bin/posttls-finger
+
+../../bin/posttls-finger: posttls-finger
+       cp $? $@
+
+printfck: $(OBJS) $(PROG)
+       rm -rf printfck
+       mkdir printfck
+       sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+       set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+       cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+       lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+       rm -f *.o *core $(PROG) $(TESTPROG) junk
+       rm -rf printfck
+
+tidy:  clean
+
+depend: $(MAKES)
+       (sed '1,/^# do not edit/!d' Makefile.in; \
+       set -e; for i in [a-z][a-z0-9]*.c; do \
+           $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+           -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+           -e 's/o: \.\//o: /' -e p -e '}' ; \
+       done | sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+       @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+posttls-finger.o: ../../include/argv.h
+posttls-finger.o: ../../include/chroot_uid.h
+posttls-finger.o: ../../include/dns.h
+posttls-finger.o: ../../include/dsn.h
+posttls-finger.o: ../../include/dsn_buf.h
+posttls-finger.o: ../../include/host_port.h
+posttls-finger.o: ../../include/inet_proto.h
+posttls-finger.o: ../../include/iostuff.h
+posttls-finger.o: ../../include/mail_conf.h
+posttls-finger.o: ../../include/mail_params.h
+posttls-finger.o: ../../include/mail_server.h
+posttls-finger.o: ../../include/msg.h
+posttls-finger.o: ../../include/msg_vstream.h
+posttls-finger.o: ../../include/myaddrinfo.h
+posttls-finger.o: ../../include/mymalloc.h
+posttls-finger.o: ../../include/name_code.h
+posttls-finger.o: ../../include/name_mask.h
+posttls-finger.o: ../../include/sane_connect.h
+posttls-finger.o: ../../include/smtp_stream.h
+posttls-finger.o: ../../include/sock_addr.h
+posttls-finger.o: ../../include/stringops.h
+posttls-finger.o: ../../include/sys_defs.h
+posttls-finger.o: ../../include/timed_connect.h
+posttls-finger.o: ../../include/tls.h
+posttls-finger.o: ../../include/vbuf.h
+posttls-finger.o: ../../include/vstream.h
+posttls-finger.o: ../../include/vstring.h
+posttls-finger.o: ../../include/vstring_vstream.h
+posttls-finger.o: posttls-finger.c
+posttls-finger.o: tlsmgrmem.h
+tlsmgrmem.o: ../../include/htable.h
+tlsmgrmem.o: ../../include/sys_defs.h
+tlsmgrmem.o: ../../include/tls_mgr.h
+tlsmgrmem.o: ../../include/vbuf.h
+tlsmgrmem.o: ../../include/vstring.h
+tlsmgrmem.o: tlsmgrmem.c
+tlsmgrmem.o: tlsmgrmem.h
diff --git a/postfix/src/posttls-finger/posttls-finger.c b/postfix/src/posttls-finger/posttls-finger.c
new file mode 100644 (file)
index 0000000..d5aed38
--- /dev/null
@@ -0,0 +1,1768 @@
+/*++
+/* NAME
+/*     posttls-finger 1
+/* SUMMARY
+/*     Probe the TLS properties of an ESMTP or LMTP server.
+/* SYNOPSIS
+/*     \fBposttls-finger\fR [\fIoptions\fR] [\fBinet:\fR]\fIdomain\fR[:\fIport\fR] [\fImatch ...\fR]
+/* .br
+/*     \fBposttls-finger\fR -S [\fIoptions\fR] \fBunix:\fIpathname\fR [\fImatch ...\fR]
+/* DESCRIPTION
+/*     \fBposttls-finger\fR(1) connects to the specified destination
+/*     and reports TLS-related information about the server. With SMTP, the
+/*     destination is a domainname; with LMTP it is either a domainname
+/*     prefixed with \fBinet:\fR or a pathname prefixed with \fBunix:\fR.  If
+/*     Postfix is built without TLS support, the resulting posttls-finger
+/*     program has very limited functionality, and only the \fB-a\fR, \fB-c\fR,
+/*     \fB-h\fR, \fB-o\fR, \fB-S\fR, \fB-t\fR, \fB-T\fR and \fB-v\fR options
+/*     are available.
+/*
+/*     Note: this is an unsupported test program. No attempt is made
+/*     to maintain compatibility between successive versions.
+/*
+/*     For SMTP servers that don't support ESMTP, only the greeting banner
+/*     and the negative EHLO response are reported. Otherwise, the reported
+/*     EHLO response details further server capabilities.
+/*
+/*     If TLS support is enabled when \fBposttls-finger\fR(1) is compiled, and
+/*     the server supports \fBSTARTTLS\fR, a TLS handshake is attempted.
+/*
+/*     If DNSSEC support is available, the connection TLS security level
+/*     (\fB-l\fR option) defaults to \fBdane\fR; see TLS_README for
+/*     details. Otherwise, it defaults to \fBsecure\fR.  This setting
+/*     determines the certificate matching policy.
+/*
+/*     If TLS negotiation succeeds, the TLS protocol and cipher details are
+/*     reported. The server certificate is then verified in accordance with
+/*     the policy at the chosen (or default) security level.  With public
+/*     CA-based trust, when the \fB-L\fR option includes \fBcertmatch\fR,
+/*     (true by default) name matching is performed even if the certificate
+/*     chain is not trusted.  This logs the names found in the remote SMTP
+/*     server certificate and which if any would match, were the certificate
+/*     chain trusted.
+/*
+/*     Note: \fBposttls-finger\fR(1) does not perform any table lookups, so
+/*     the TLS policy table and obsolete per-site tables are not consulted.
+/*     It does not communicate with the \fBtlsmgr\fR(8) daemon (or any other
+/*     Postfix daemons); its TLS session cache is held in private memory, and
+/*     disappears when the process exits.
+/*
+/*     With the \fB-r \fIdelay\fR option, if the server assigns a TLS
+/*     session id, the TLS session is cached. The connection is then closed
+/*     and re-opened after the specified delay, and \fBposttls-finger\fR(1)
+/*     then reports whether the cached TLS session was re-used.
+/*
+/*     When the destination is a load-balancer, it may be distributing
+/*     load between multiple server caches. Typically, each server returns
+/*     its unique name in its EHLO response. If, upon reconnecting with
+/*     \fB-r\fR, a new server name is detected, another session is cached
+/*     for the new server, and the reconnect is repeated up to a maximum
+/*     number of times (default 5) that can be specified via the \fB-m\fR
+/*     option.
+/*
+/*     The choice of SMTP or LMTP (\fB-S\fR option) determines the syntax of
+/*     the destination argument. With SMTP, one can specify a service on a
+/*     non-default port as \fIhost\fR:\fIservice\fR, and disable MX (mail
+/*     exchanger) DNS lookups with [\fIhost\fR] or [\fIhost\fR]:\fIport\fR.
+/*     The [] form is required when you specify an IP address instead of a
+/*     hostname.  An IPv6 address takes the form [\fBipv6:\fIaddress\fR].
+/*     The default port for SMTP is taken from the \fBsmtp/tcp\fR entry in
+/*     /etc/services, defaulting to 25 if the entry is not found.
+/*
+/*     With LMTP, specify \fBunix:\fIpathname\fR to connect to a local server
+/*     listening on a unix-domain socket bound to the specified pathname;
+/*     otherwise, specify an optional \fBinet:\fR prefix followed by a
+/*     \fIdomain\fR and an optional port, with the same syntax as for
+/*     SMTP. The default TCP port for LMTP is 24.
+/*
+/*     Arguments:
+/* .IP "\fB-a\fR"
+/*     Address family preference: \fBipv4\fR, \fBipv6\fR or \fBany\fR.  When
+/*     using \fBany\fR, posttls-finger will randomly select one of the two as
+/*     the more preferred, and exhaust all MX preferences for the first
+/*     address family before trying any addresses for the other.  The
+/*     default value is \fBany\fR.
+/* .IP "\fB-A\fR"
+/*     A list of PEM trust-anchor files that overrides CAfile and CApath
+/*     trust chain verification.  Specify the option multiple times to
+/*     specify multiple files.  See the main.cf documentation for
+/*     smtp_tls_trust_anchor_file for details.
+/* .IP "\fB-c\fR"
+/*     Disable SMTP chat logging; only TLS-related information is logged.
+/* .IP "\fB-C\fR"
+/*     Print the remote SMTP server certificate trust chain in PEM format.
+/*     The issuer DN, subject DN, certificate and public key fingerprints
+/*     (see \fB-d \fImdalg\fR option below) are printed above each PEM
+/*     certificate block.  If you specify \fB-F \fICAfile\fR or
+/*     \fB-P \fICApath\fR, the OpenSSL library may augment the chain with
+/*     missing issuer certificates.  To see the actual chain sent by the
+/*     remote SMTP server leave \fICAfile\fR and \fICApath\fR unset.
+/* .IP "\fB-d \fImdalg\fR"
+/*     The message digest algorithm to use for reporting remote SMTP server
+/*     fingerprints and matching against user provided certificate
+/*     fingerprints (with DANE TLSA records the algorithm is specified
+/*     in the DNS).  The default algorithm is sha1.
+/* .IP "\fB-F \fICAfile.pem\fR"
+/*     The PEM formatted CAfile for remote SMTP server certificate
+/*     verification.  By default no CAfile is used and no public CAs
+/*     are trusted.
+/* .IP "\fB-h \fIhost_lookup\fR"
+/*     The hostname lookup methods used for the connection.  See the
+/*     documentation of smtp_host_lookup for syntax and semantics.
+/* .IP "\fB-l \fIlevel\fR"
+/*     The security level for the connection, default \fBdane\fR or
+/*     \fBsecure\fR depending on whether DNSSEC is available. For syntax
+/*     and semantics, see the documentation of smtp_tls_security_level.
+/*     When \fBdane\fR is supported and selected, if no TLSA records are
+/*     found, or all the records found are unusable, the \fIdegraded\fR
+/*     level will be \fBsecure\fR.  The main additional level to consider
+/*     is \fBfingerprint\fR, which allows you test certificate or public-key
+/*     fingerprint matches before you deploy them in the policy table.
+/* .IP
+/*     Note, since \fBposttls-finger\fR does not actually deliver any email,
+/*     the \fBnone\fR, \fBmay\fR and \fBencrypt\fR security levels are not
+/*     very useful.  Since \fBmay\fR and \fBencrypt\fR don't require peer
+/*     certificates, they will often negotiate anonymous TLS ciphersuites,
+/*     so you won't learn much about the remote SMTP server's certificates
+/*     at these levels if it also supports anonymous TLS (though you may
+/*     learn that the server supports anonymous TLS).
+/* .IP "\fB-L \fIlogopts\fR"
+/*     Fine-grained TLS logging options. To tune the TLS features logged
+/*     during the TLS handshake, specify one or more of:
+/* .RS
+/* .IP "0, none"
+/*     These yield no TLS logging; you'll generally want more, but this
+/*     is handy if you just want the trust chain:
+/* .RS
+/* .ad
+/* .nf
+/*     $ posttls-finger -cC -L none destination
+/* .fi
+/* .RE
+/* .IP "1, routine, summary"
+/*     These synonymous values yield a normal one-line summary of the TLS
+/*     connection.
+/* .IP "2, debug"
+/*     These synonymous values combine routine, ssl-debug, cache and verbose.
+/* .IP "3, ssl-expert"
+/*     These synonymous values combine debug with ssl-handshake-packet-dump.
+/*     For experts only.
+/* .IP "4, ssl-developer"
+/*     These synonymous values combine ssl-expert with ssl-session-packet-dump.
+/*     For experts only, and in most cases, use wireshark instead.
+/* .IP ssl-debug
+/*     Turn on OpenSSL logging of the progress of the SSL handshake.
+/* .IP ssl-handshake-packet-dump
+/*     Log hexadecimal packet dumps of the SSL handshake; for experts only.
+/* .IP ssl-session-packet-dump
+/*     Log hexadecimal packet dumps of the entire SSL session; only useful
+/*     to those who can debug SSL protocol problems from hex dumps.
+/* .IP untrusted
+/*     Logs trust chain verification problems.  This is turned on
+/*     automatically at security levels that use peer names signed
+/*     by certificate authorities to validate certificates.  So while
+/*     this setting is recognized, you should never need to set it
+/*     explicitly.
+/* .IP peercert
+/*     This logs a one line summary of the remote SMTP server certificate
+/*     subject, issuer, and fingerprints.
+/* .IP certmatch
+/*     This logs remote SMTP server certificate matching, showing the CN
+/*     and each subjectAltName and which name matched.  With DANE, logs
+/*     matching of TLSA record trust-anchor and end-entity certificates.
+/* .IP cache
+/*     This logs session cache operations, showing whether session caching
+/*     is effective with the remote SMTP server.  Automatically used when
+/*     reconnecting with the \fB-r\fR option; rarely needs to be set
+/*     explicitly.
+/* .IP verbose
+/*     Enables verbose logging in the Postfix TLS driver; includes all of
+/*     peercert..cache and more.
+/* .RE
+/* .IP
+/*     The default is \fBroutine,certmatch\fR. After a reconnect, the log
+/*     level is unconditionally \fBroutine,cache\fR.
+/* .IP "\fB-m \fIcount\fR"
+/*     When the \fB-r \fIdelay\fR option is specified, the \fB-m\fR option
+/*     determines the maximum number of reconnect attempts to use with
+/*     a server behind a load-balacer, to see whether connection caching
+/*     is likely to be effective for this destination.  Some MTAs don't
+/*     expose the underlying server identity in their EHLO response; with
+/*     these servers there will never be more than 1 reconnection attempt.
+/* .IP "\fB-o \fIname=value\fR"
+/*     Specify zero or more times to override the value of the main.cf
+/*     parameter \fIname\fR with \fIvalue\fR.  Possible use-cases include
+/*     overriding the values of TLS library parameters or "myhostname" to
+/*     configure the SMTP EHLO name sent to the remote server.
+/* .IP "\fB-P \fICApath/\fR"
+/*     The OpenSSL CApath/ directory (indexed via c_rehash(1)) for remote
+/*     SMTP server certificate verification.  By default no CApath is used
+/*     and no public CAs are trusted.
+/* .IP "\fB-r \fIdelay\fR"
+/*     With a cachable TLS session, disconnect and reconnect after \fIdelay\fR
+/*     seconds. Report whether the session is re-used. Retry if a new server
+/*     is encountered, up to 5 times or as specified with the \fB-m\fR option.
+/* .IP "\fB-S\fR"
+/*     Disable SMTP; that is, connect to an LMTP server. The default port for
+/*     LMTP over TCP is 24.  Alternative ports can specified by appending
+/*     "\fI:servicename\fR" or ":\fIportnumber\fR" to the destination
+/*     argument.
+/* .IP "\fB-t \fItimeout\fR"
+/*     The TCP connection timeout to use.  This is also the timeout for
+/*     reading the remote server's 220 banner.
+/* .IP "\fB-T \fItimeout\fR"
+/*     The SMTP/LMTP command timeout for EHLO/LHLO, STARTTLS and QUIT.
+/* .IP "\fB-v\fR"
+/*     Enable more verbose logging.
+/* .IP "[\fBinet:\fR]\fIdomain\fR[:\fIport\fR]"
+/*     Connect via TCP to domain \fIdomain\fR, port \fIport\fR. The default
+/*     port is \fBsmtp\fR (or 24 with LMTP).  With SMTP an MX lookup is
+/*     performed to resolve the domain to a host, unless the domain is
+/*     enclosed in \fB[]\fR.  If you want to connect to a specific MX host,
+/*     for instance \fImx1.example.com\fR, specify [\fImx1.example.com\fR]
+/*     as the destination and \fIexample.com\fR as a \fBmatch\fR argument.
+/*     When using DNS, the destination domain is assumed fully qualified
+/*     and no default domain or search suffixes are applied; you must use
+/*     fully-qualified names or also enable \fBnative\fR host lookups
+/*     (these don't support \fBdane\fR as no DNSSEC validation information
+/*     is available via \fBnative\fR lookups).
+/* .IP "\fBunix:\fIpathname\fR"
+/*     Connect to the UNIX-domain socket at \fIpathname\fR. LMTP only.
+/* .IP "\fBmatch ...\fR"
+/*     With no match arguments specified, certificate peername matching uses
+/*     the compiled-in default strategies for each security level.  If you
+/*     specify one or more arguments, these will be used as the list of
+/*     certificate or public-key digests to match for the \fBfingerprint\fR
+/*     level, or as the list of DNS names to match in the certificate at the
+/*     \fBverify\fR and \fBsecure\fR levels.  If the security level is
+/*     \fBdane\fR, and usable TLSA records are found, the match names are
+/*     ignored, and \fBhostname, nexthop\fR strategies are used.  If no
+/*     TLSA records are found or none are usable, the level \fIdegrades\fR
+/*     to \fBsecure\fR and the provided list is used to match names in
+/*     the certificate.
+/* .ad
+/* .fi
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_CONFIG\fR
+/*     Read configuration parameters from a non-default location.
+/* .IP \fBMAIL_VERBOSE\fR
+/*     Same as \fB-v\fR option.
+/* SEE ALSO
+/*     smtp-source(1), SMTP/LMTP message source
+/*     smtp-sink(1), SMTP/LMTP message dump
+/*
+/* README FILES
+/* .ad
+/* .fi
+/*     Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+/*     html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/*     TLS_README, Postfix STARTTLS howto
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*
+/*     Viktor Dukhovni
+/*--*/
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <argv.h>
+#include <name_mask.h>
+#include <name_code.h>
+#include <chroot_uid.h>
+#include <host_port.h>
+#include <inet_proto.h>
+#include <iostuff.h>
+#include <timed_connect.h>
+#include <sane_connect.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+
+#define STR(x)         vstring_str(x)
+
+ /*
+  * Global library.
+  */
+#include <mail_params.h>
+#include <mail_conf.h>
+#include <smtp_stream.h>
+#include <dsn_buf.h>
+
+/* DNS library. */
+
+#include <dns.h>
+
+ /*
+  * master library
+  */
+#include <mail_server.h>
+
+ /*
+  * TLS Library
+  */
+#define TLS_INTERNAL
+#include <tls.h>
+
+#ifdef USE_TLS
+#include <openssl/engine.h>
+#endif
+
+ /*
+  * Application specific
+  */
+#include "tlsmgrmem.h"
+
+static int conn_tmout = 5;
+static int smtp_tmout = 30;
+
+#define HOST_FLAG_DNS          (1<<0)
+#define HOST_FLAG_NATIVE       (1<<1)
+
+#define MISC_FLAG_PREF_IPV6    (1<<0)
+#define MISC_FLAG_PREF_IPV4    (1<<1)
+
+static const NAME_MASK lookup_masks[] = {
+    "dns", HOST_FLAG_DNS,
+    "native", HOST_FLAG_NATIVE,
+    0,
+};
+
+static const NAME_CODE addr_pref_map[] = {
+    INET_PROTO_NAME_IPV6, MISC_FLAG_PREF_IPV6,
+    INET_PROTO_NAME_IPV4, MISC_FLAG_PREF_IPV4,
+    INET_PROTO_NAME_ANY, 0,
+    0, -1,
+};
+
+typedef struct OPTIONS {
+    char   *logopts;
+    char   *level;
+    ARGV   *tas;
+    char   *host_lookup;
+    char   *addr_pref;
+} OPTIONS;
+
+ /*
+  * Per-session data structure with state.
+  * 
+  * This software can maintain multiple parallel connections to the same SMTP
+  * server. However, it makes no more than one connection request at a time
+  * to avoid overwhelming the server with SYN packets and having to back off.
+  * Back-off would screw up the benchmark. Pending connection requests are
+  * kept in a linear list.
+  */
+typedef struct STATE {
+    int     smtp;                      /* SMTP or LMTP? */
+    int     host_lookup;               /* dns|native|dns,native */
+    int     addr_pref;                 /* v4, v6, both */
+    int     log_mask;                  /* via tls_log_mask() */
+    int     reconnect;                 /* -r option */
+    int     max_reconnect;             /* -m option */
+    unsigned port;                     /* TCP port */
+    char   *dest;                      /* Full destination spec */
+    char   *addrport;                  /* [addr]:port */
+    char   *namaddrport;               /* name[addr]:port */
+    char   *nexthop;                   /* Nexthop domain for verification */
+    char   *hostname;                  /* Hostname for verification */
+    DNS_RR *addr;                      /* IPv[46] Address to (re)connect to */
+    int     pass;                      /* Pass number, 2 for reconnect */
+    int     nochat;                    /* disable chat logging */
+    char   *helo;                      /* Server name from EHLO reply */
+    DSN_BUF *why;                      /* SMTP-style error message */
+    VSTRING *buffer;                   /* Response buffer */
+    VSTREAM *stream;                   /* Open connection */
+    int     level;                     /* TLS security level */
+#ifdef USE_TLS
+    char   *mdalg;                     /* fingerprint digest algorithm */
+    char   *CAfile;                    /* Trusted public CAs */
+    char   *CApath;                    /* Trusted public CAs */
+    ARGV   *match;                     /* match arguments */
+    int     print_trust;               /* -C option */
+    BIO    *tls_bio;                   /* BIO wrapper for stdout */
+    TLS_APPL_STATE *tls_ctx;           /* Application TLS context */
+    TLS_SESS_STATE *tls_context;       /* Session TLS context */
+    TLS_DANE *dane;                    /* DANE TLSA validation structure */
+    TLS_DANE *ddane;                   /* DANE TLSA from DNS */
+#endif
+    OPTIONS options;                   /* JCL */
+} STATE;
+
+static DNS_RR *host_addr(STATE *, const char *);
+
+#define HNAME(addr) (addr->validated ? addr->rname : addr->qname)
+
+ /*
+  * Structure with broken-up SMTP server response.
+  */
+typedef struct {                       /* server response */
+    int     code;                      /* status */
+    char   *str;                       /* text */
+    VSTRING *buf;                      /* origin of text */
+} RESPONSE;
+
+
+/* command - send an SMTP command */
+
+static void command(STATE *state, int verbose, char *fmt,...)
+{
+    VSTREAM *stream = state->stream;
+    VSTRING *buf;
+    va_list ap;
+    char   *line;
+
+    buf = vstring_alloc(100);
+    va_start(ap, fmt);
+    vstring_vsprintf(buf, fmt, ap);
+    va_end(ap);
+    line = vstring_str(buf);
+
+    while (line && *line) {
+       char   *nextline = strchr(line, '\n');
+
+       if (nextline)
+           *nextline++ = '\0';
+       if (verbose && !state->nochat)
+           msg_info("> %s", line);
+       smtp_printf(stream, "%s", line);
+       line = nextline;
+    }
+
+    vstring_free(buf);
+}
+
+/* response - read and process SMTP server response */
+
+static RESPONSE *response(STATE *state, int verbose)
+{
+    VSTREAM *stream = state->stream;
+    VSTRING *buf = state->buffer;
+    static RESPONSE rdata;
+    int     more;
+    char   *cp;
+
+    /*
+     * Initialize the response data buffer. Defend against a denial of
+     * service attack by limiting the amount of multi-line text that we are
+     * willing to store.
+     */
+    if (rdata.buf == 0) {
+       rdata.buf = vstring_alloc(100);
+       vstring_ctl(rdata.buf, VSTRING_CTL_MAXLEN, (ssize_t) var_line_limit, 0);
+    }
+
+    /*
+     * Censor out non-printable characters in server responses. Concatenate
+     * multi-line server responses. Separate the status code from the text.
+     * Leave further parsing up to the application.
+     */
+#define BUF ((char *) vstring_str(buf))
+    VSTRING_RESET(rdata.buf);
+    for (;;) {
+       smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP);
+       for (cp = BUF; *cp != 0; cp++)
+           if (!ISPRINT(*cp) && !ISSPACE(*cp))
+               *cp = '?';
+       cp = BUF;
+       if (verbose && !state->nochat)
+           msg_info("< %s", cp);
+       while (ISDIGIT(*cp))
+           cp++;
+       rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0);
+       if ((more = (*cp == '-')) != 0)
+           cp++;
+       while (ISSPACE(*cp))
+           cp++;
+       vstring_strcat(rdata.buf, cp);
+       if (more == 0)
+           break;
+       VSTRING_ADDCH(rdata.buf, '\n');
+    }
+    VSTRING_TERMINATE(rdata.buf);
+    rdata.str = vstring_str(rdata.buf);
+    return (&rdata);
+}
+
+/* exception_text - translate exceptions from the smtp_stream module */
+
+static char *exception_text(int except)
+{
+    switch (except) {
+       case SMTP_ERR_EOF:
+       return ("lost connection");
+    case SMTP_ERR_TIME:
+       return ("timeout");
+    default:
+       msg_panic("exception_text: unknown exception %d", except);
+    }
+}
+
+/* ehlo - send EHLO/LHLO */
+
+static RESPONSE *ehlo(STATE *state)
+{
+    int     except;
+    int     verbose;
+    volatile char *ehlo = state->smtp ? "EHLO" : "LHLO";
+    VSTREAM *stream = state->stream;
+    RESPONSE *resp;
+
+#ifdef USE_TLS
+    verbose = (state->pass == 1 && state->nochat == 0);
+#else
+    verbose = 1;
+#endif
+
+    /*
+     * Send the standard greeting with our hostname
+     */
+    if ((except = vstream_setjmp(stream)) != 0) {
+       msg_info("%s while sending %s", exception_text(except), ehlo);
+       return (0);
+    }
+    command(state, verbose, "%s %s", ehlo, var_myhostname);
+
+    resp = response(state, verbose);
+    if (resp->code / 100 != 2) {
+       msg_info("%s rejected: %d %s", ehlo, resp->code, resp->str);
+       return (0);
+    }
+    return resp;
+}
+
+#ifdef USE_TLS
+
+static void print_trust_info(STATE *state)
+{
+    STACK_OF(X509) *sk = SSL_get_peer_cert_chain(state->tls_context->con);
+
+    if (sk != NULL) {
+       int     i;
+
+       BIO_printf(state->tls_bio, "---\nCertificate chain\n");
+       for (i = 0; i < sk_X509_num(sk); i++) {
+           X509   *cert = sk_X509_value(sk, i);
+           char    buf[CCERT_BUFSIZ];
+           X509_NAME *xn;
+           char   *digest;
+
+           if ((xn = X509_get_subject_name(cert)) != 0) {
+               X509_NAME_oneline(xn, buf, sizeof buf);
+               BIO_printf(state->tls_bio, "%2d subject: %s\n", i, buf);
+           }
+           if ((xn = X509_get_issuer_name(cert)) != 0) {
+               X509_NAME_oneline(xn, buf, sizeof buf);
+               BIO_printf(state->tls_bio, "    issuer: %s\n", buf);
+           }
+           digest = tls_fingerprint(cert, state->mdalg);
+           BIO_printf(state->tls_bio, "   cert digest=%s\n", digest);
+           myfree(digest);
+
+           digest = tls_pkey_fprint(cert, state->mdalg);
+           BIO_printf(state->tls_bio, "   pkey digest=%s\n", digest);
+           myfree(digest);
+
+           PEM_write_bio_X509(state->tls_bio, cert);
+       }
+    }
+}
+
+/* starttls - SMTP STARTTLS handshake */
+
+static int starttls(STATE *state)
+{
+    VSTRING *cipher_exclusions;
+    VSTRING *serverid;
+    int     except;
+    RESPONSE *resp;
+    VSTREAM *stream = state->stream;
+    TLS_CLIENT_START_PROPS tls_props;
+
+    /* SMTP stream with deadline timeouts */
+    smtp_stream_setup(stream, smtp_tmout, 1);
+    if ((except = vstream_setjmp(stream)) != 0) {
+       msg_fatal("%s while sending STARTTLS", exception_text(except));
+       return (1);
+    }
+    command(state, state->pass == 1, "STARTTLS");
+
+    resp = response(state, state->pass == 1);
+    if (resp->code / 100 != 2) {
+       msg_info("STARTTLS rejected: %d %s", resp->code, resp->str);
+       return (1);
+    }
+
+    /*
+     * Discard any plain-text data that may be piggybacked after the server's
+     * 220 STARTTLS reply. Should we abort the session instead?
+     */
+    vstream_fpurge(stream, VSTREAM_PURGE_READ);
+
+#define ADD_EXCLUDE(vstr, str) \
+    do { \
+       if (*(str)) \
+           vstring_sprintf_append((vstr), "%s%s", \
+                                  VSTRING_LEN(vstr) ? " " : "", (str)); \
+    } while (0)
+
+    cipher_exclusions = vstring_alloc(10);
+    ADD_EXCLUDE(cipher_exclusions, DEF_SMTP_TLS_EXCL_CIPH);
+    if (TLS_REQUIRED(state->level))
+       ADD_EXCLUDE(cipher_exclusions, DEF_SMTP_TLS_MAND_EXCL);
+
+    /*
+     * If we're authenticating suppress anonymous ciphersuites, otherwise at
+     * least encrypt, not much point in doing neither.
+     */
+    if (TLS_MUST_MATCH(state->level))
+       ADD_EXCLUDE(cipher_exclusions, "aNULL");
+    else
+       ADD_EXCLUDE(cipher_exclusions, "eNULL");
+
+    serverid = vstring_alloc(10);
+    vstring_sprintf(serverid, "%s:%s", var_procname, state->addrport);
+
+    state->tls_context =
+       TLS_CLIENT_START(&tls_props,
+                        ctx = state->tls_ctx,
+                        stream = stream,
+                        timeout = smtp_tmout,
+                        tls_level = state->level,
+                        nexthop = state->nexthop,
+                        host = state->hostname,
+                        namaddr = state->namaddrport,
+                        serverid = STR(serverid),
+                        helo = state->helo ? state->helo : "",
+                        protocols = "!SSLv2",  /* XXX */
+                        cipher_grade = "medium",       /* XXX */
+                        cipher_exclusions
+                        = vstring_str(cipher_exclusions),
+                        matchargv = state->match,
+                        mdalg = state->mdalg,
+                        dane = state->ddane ? state->ddane : state->dane);
+    vstring_free(cipher_exclusions);
+    vstring_free(serverid);
+    if (state->helo) {
+       myfree(state->helo);
+       state->helo = 0;
+    }
+    if (state->tls_context == 0) {
+       /* We must avoid further I/O, the peer is in an undefined state. */
+       (void) vstream_fpurge(stream, VSTREAM_PURGE_BOTH);
+       (void) vstream_fclose(stream);
+       state->stream = 0;
+       return (1);
+    }
+    if (state->pass == 1) {
+       ehlo(state);
+       if (!TLS_CERT_IS_PRESENT(state->tls_context))
+           msg_info("Server is anonymous");
+       else if (state->print_trust)
+           print_trust_info(state);
+       state->log_mask = TLS_LOG_SUMMARY | TLS_LOG_CACHE;
+       tls_update_app_logmask(state->tls_ctx, state->log_mask);
+    }
+    return (0);
+}
+
+#endif
+
+/* doproto - do SMTP handshake */
+
+static int doproto(STATE *state)
+{
+    VSTREAM *stream = state->stream;
+    RESPONSE *resp;
+    int     except;
+    int     n;
+    char   *lines;
+    char   *words;
+    char   *word;
+
+    /*
+     * Prepare for disaster.
+     */
+    smtp_stream_setup(stream, conn_tmout, 1);
+    if ((except = vstream_setjmp(stream)) != 0)
+       msg_fatal("%s while reading server greeting", exception_text(except));
+
+    /*
+     * Read and parse the server's SMTP greeting banner.
+     */
+    if (((resp = response(state, 1))->code / 100) != 2) {
+       msg_info("SMTP service not available: %d %s", resp->code, resp->str);
+       return (1);
+    }
+
+    /*
+     * Send the standard greeting with our hostname
+     */
+    if ((resp = ehlo(state)) == 0)
+       return (1);
+
+    lines = resp->str;
+    for (n = 0; (words = mystrtok(&lines, "\n")) != 0; ++n) {
+       if ((word = mystrtok(&words, " \t=")) != 0) {
+           if (n == 0)
+               state->helo = mystrdup(word);
+           if (strcasecmp(word, "STARTTLS") == 0)
+               break;
+       }
+    }
+
+#ifdef USE_TLS
+    if (words && state->tls_ctx)
+       if (starttls(state))
+           return (1);
+#endif
+
+    /*
+     * Prepare for disaster.
+     */
+    smtp_stream_setup(stream, smtp_tmout, 1);
+    if ((except = vstream_setjmp(stream)) != 0) {
+       msg_warn("%s while sending QUIT command", exception_text(except));
+       return (0);
+    }
+    command(state, 1, "QUIT");
+    (void) response(state, 1);
+
+    return (0);
+}
+
+/* connect_sock - connect a socket over some transport */
+
+static VSTREAM *connect_sock(int sock, struct sockaddr * sa, int salen,
+                          const char *name, const char *addr, STATE *state)
+{
+    DSN_BUF *why = state->why;
+    int     conn_stat;
+    int     saved_errno;
+    VSTREAM *stream;
+
+    if (conn_tmout > 0) {
+       non_blocking(sock, NON_BLOCKING);
+       conn_stat = timed_connect(sock, sa, salen, conn_tmout);
+       saved_errno = errno;
+       non_blocking(sock, BLOCKING);
+       errno = saved_errno;
+    } else {
+       conn_stat = sane_connect(sock, sa, salen);
+    }
+    if (conn_stat < 0) {
+       if (state->port)
+           dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m",
+                      name, addr, ntohs(state->port));
+       else
+           dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr);
+       close(sock);
+       return (0);
+    }
+    stream = vstream_fdopen(sock, O_RDWR);
+    state->namaddrport =
+       vstring_export(state->port == 0 ?
+                 vstring_sprintf(vstring_alloc(10), "%s[%s]", name, addr) :
+                      vstring_sprintf(vstring_alloc(10), "%s[%s]:%u",
+                                      name, addr, ntohs(state->port)));
+    state->addrport =
+       vstring_export(state->port == 0 ?
+                      vstring_sprintf(vstring_alloc(10), "%s", addr) :
+                      vstring_sprintf(vstring_alloc(10), "[%s]:%u",
+                                      addr, ntohs(state->port)));
+
+    /*
+     * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE.
+     */
+    if (sa->sa_family == AF_INET
+#ifdef AF_INET6
+       || sa->sa_family == AF_INET6
+#endif
+       )
+       vstream_tweak_tcp(stream);
+
+    return (stream);
+}
+
+/* connect_unix - connect to a unix-domain socket */
+
+static VSTREAM *connect_unix(STATE *state, const char *path)
+{
+    static const char *myname = "connect_unix";
+    DSN_BUF *why = state->why;
+    struct sockaddr_un sock_un;
+    int     len = strlen(path);
+    int     sock;
+
+    if (!state->nexthop)
+       state->nexthop = mystrdup(var_myhostname);
+    state->hostname = mystrdup(var_myhostname);
+
+    dsb_reset(why);                            /* Paranoia */
+
+    /*
+     * Sanity checks.
+     */
+    if (len >= (int) sizeof(sock_un.sun_path)) {
+       dsb_simple(why, "4.3.5", "unix-domain name too long: %s", path);
+       return (0);
+    }
+
+    /*
+     * Initialize.
+     */
+    memset((char *) &sock_un, 0, sizeof(sock_un));
+    sock_un.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+    sock_un.sun_len = len + 1;
+#endif
+    memcpy(sock_un.sun_path, path, len + 1);
+
+    /*
+     * Create a client socket.
+     */
+    if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+       msg_fatal("%s: socket: %m", myname);
+
+    /*
+     * Connect to the server.
+     */
+    if (msg_verbose)
+       msg_info("%s: trying: %s...", myname, path);
+
+    return (connect_sock(sock, (struct sockaddr *) & sock_un, sizeof(sock_un),
+                        var_myhostname, path, state));
+}
+
+/* connect_addr - connect to explicit address */
+
+static VSTREAM *connect_addr(STATE *state, DNS_RR *addr)
+{
+    static const char *myname = "connect_addr";
+    DSN_BUF *why = state->why;
+    struct sockaddr_storage ss;                /* remote */
+    struct sockaddr *sa = (struct sockaddr *) & ss;
+    SOCKADDR_SIZE salen = sizeof(ss);
+    MAI_HOSTADDR_STR hostaddr;
+    int     sock;
+
+    dsb_reset(why);                            /* Paranoia */
+
+    /*
+     * Sanity checks.
+     */
+    if (dns_rr_to_sa(addr, state->port, sa, &salen) != 0) {
+       msg_warn("%s: skip address type %s: %m",
+                myname, dns_strtype(addr->type));
+       dsb_simple(why, "4.4.0", "network address conversion failed: %m");
+       return (0);
+    }
+
+    /*
+     * Initialize.
+     */
+    if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
+       msg_fatal("%s: socket: %m", myname);
+
+    if (inet_windowsize > 0)
+       set_inet_windowsize(sock, inet_windowsize);
+
+    /*
+     * Connect to the server.
+     */
+    SOCKADDR_TO_HOSTADDR(sa, salen, &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+    if (msg_verbose)
+       msg_info("%s: trying: %s[%s] port %d...",
+                myname, HNAME(addr), hostaddr.buf, ntohs(state->port));
+
+    return (connect_sock(sock, sa, salen, HNAME(addr), hostaddr.buf, state));
+}
+
+#define HAS_DSN(why)           (STR((why)->status)[0] != 0)
+#define HAS_SOFT_DSN(why)      (STR((why)->status)[0] == '4')
+#define HAS_HARD_DSN(why)      (STR((why)->status)[0] == '5')
+#define HAS_LOOP_DSN(why) \
+    (HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0)
+
+#define SET_SOFT_DSN(why)      (STR((why)->status)[0] = '4')
+#define SET_HARD_DSN(why)      (STR((why)->status)[0] = '5')
+
+/* addr_one - address lookup for one host name */
+
+static DNS_RR *addr_one(STATE *state, DNS_RR *addr_list, const char *host,
+                               int res_opt, unsigned pref)
+{
+    static const char *myname = "addr_one";
+    DSN_BUF *why = state->why;
+    DNS_RR *addr = 0;
+    DNS_RR *rr;
+    int     aierr;
+    struct addrinfo *res0;
+    struct addrinfo *res;
+    INET_PROTO_INFO *proto_info = inet_proto_info();
+    int     found;
+
+    if (msg_verbose)
+       msg_info("%s: host %s", myname, host);
+
+    /*
+     * Interpret a numerical name as an address.
+     */
+    if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0
+     && strchr((char *) proto_info->sa_family_list, res0->ai_family) != 0) {
+       if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0)
+           msg_fatal("host %s: conversion error for address family %d: %m",
+                   host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
+       addr_list = dns_rr_append(addr_list, addr);
+       freeaddrinfo(res0);
+       return (addr_list);
+    }
+
+    /*
+     * Use DNS lookup, but keep the option open to use native name service.
+     * 
+     * XXX A soft error dominates past and future hard errors. Therefore we
+     * should not clobber a soft error text and status code.
+     */
+    if (state->host_lookup & HOST_FLAG_DNS) {
+       switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0,
+                            why->reason, DNS_REQ_FLAG_NONE,
+                            proto_info->dns_atype_list)) {
+       case DNS_OK:
+           for (rr = addr; rr; rr = rr->next)
+               rr->pref = pref;
+           addr_list = dns_rr_append(addr_list, addr);
+           return (addr_list);
+       default:
+           dsb_status(why, "4.4.3");
+           return (addr_list);
+       case DNS_FAIL:
+           dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3");
+           return (addr_list);
+       case DNS_INVAL:
+           dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
+           return (addr_list);
+       case DNS_NOTFOUND:
+           dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
+           /* maybe native naming service will succeed */
+           break;
+       }
+    }
+
+    /*
+     * Use the native name service which also looks in /etc/hosts.
+     * 
+     * XXX A soft error dominates past and future hard errors. Therefore we
+     * should not clobber a soft error text and status code.
+     */
+#define RETRY_AI_ERROR(e) \
+        ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
+#ifdef EAI_NODATA
+#define DSN_NOHOST(e) \
+       ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME)
+#else
+#define DSN_NOHOST(e) \
+       ((e) == EAI_AGAIN || (e) == EAI_NONAME)
+#endif
+
+    if (state->host_lookup & HOST_FLAG_NATIVE) {
+       if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) {
+           dsb_simple(why, (HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ?
+                      (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") :
+                      (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"),
+                      "unable to look up host %s: %s",
+                      host, MAI_STRERROR(aierr));
+       } else {
+           for (found = 0, res = res0; res != 0; res = res->ai_next) {
+               if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+                   msg_info("skipping address family %d for host %s",
+                            res->ai_family, host);
+                   continue;
+               }
+               found++;
+               if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0)
+                   msg_fatal("host %s: conversion error for address family %d: %m",
+                   host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
+               addr_list = dns_rr_append(addr_list, addr);
+           }
+           freeaddrinfo(res0);
+           if (found == 0) {
+               dsb_simple(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
+                          "%s: host not found", host);
+           }
+           return (addr_list);
+       }
+    }
+
+    /*
+     * No further alternatives for host lookup.
+     */
+    return (addr_list);
+}
+
+/* mx_addr_list - address lookup for a list of mail exchangers */
+
+static DNS_RR *mx_addr_list(STATE *state, DNS_RR *mx_names)
+{
+    static const char *myname = "mx_addr_list";
+    DNS_RR *addr_list = 0;
+    DNS_RR *rr;
+    int     res_opt = mx_names->validated ? RES_USE_DNSSEC : 0;
+
+    for (rr = mx_names; rr; rr = rr->next) {
+       if (rr->type != T_MX)
+           msg_panic("%s: bad resource type: %d", myname, rr->type);
+       addr_list = addr_one(state, addr_list, (char *) rr->data, res_opt,
+                            rr->pref);
+    }
+    return (addr_list);
+}
+
+/* smtp_domain_addr - mail exchanger address lookup */
+
+static DNS_RR *domain_addr(STATE *state, char *domain)
+{
+    DNS_RR *mx_names;
+    DNS_RR *addr_list = 0;
+    int     r = 0;                     /* Resolver flags */
+
+    dsb_reset(state->why);
+
+#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
+    r |= RES_USE_DNSSEC;
+#endif
+
+    switch (dns_lookup(domain, T_MX, r, &mx_names, (VSTRING *) 0,
+                      state->why->reason)) {
+    default:
+       dsb_status(state->why, "4.4.3");
+       break;
+    case DNS_INVAL:
+       dsb_status(state->why, "5.4.4");
+       break;
+    case DNS_FAIL:
+       dsb_status(state->why, "5.4.3");
+       break;
+    case DNS_OK:
+       mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any);
+       addr_list = mx_addr_list(state, mx_names);
+       dns_rr_free(mx_names);
+       if (addr_list == 0) {
+           msg_warn("no MX host for %s has a valid address record", domain);
+           break;
+       }
+#define COMPARE_ADDR(flags) \
+       ((flags & MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \
+        (flags & MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \
+        dns_rr_compare_pref_any)
+       if (addr_list && addr_list->next) {
+           addr_list = dns_rr_shuffle(addr_list);
+           addr_list = dns_rr_sort(addr_list, COMPARE_ADDR(state->addr_pref));
+       }
+       break;
+    case DNS_NOTFOUND:
+       addr_list = host_addr(state, domain);
+       break;
+    }
+
+    return (addr_list);
+}
+
+/* host_addr - direct host lookup */
+
+static DNS_RR *host_addr(STATE *state, const char *host)
+{
+    DSN_BUF *why = state->why;
+    DNS_RR *addr_list;
+    int     res_opt = 0;
+
+    dsb_reset(why);                            /* Paranoia */
+
+#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
+    res_opt |= RES_USE_DNSSEC;
+#endif
+
+#define PREF0  0
+    addr_list = addr_one(state, (DNS_RR *) 0, host, res_opt, PREF0);
+    if (addr_list && addr_list->next) {
+       addr_list = dns_rr_shuffle(addr_list);
+       if (inet_proto_info()->ai_family_list[1] != 0)
+           addr_list = dns_rr_sort(addr_list, COMPARE_ADDR(state->addr_pref));
+    }
+    return (addr_list);
+}
+
+/* dane_host_level - canidate host "dane" or degraded security level */
+
+static int dane_host_level(STATE *state, DNS_RR *addr, unsigned port)
+{
+    int     level = state->level;
+
+#ifdef USE_TLS
+    if (level == TLS_LEV_DANE) {
+       if (addr->validated) {
+           if (state->log_mask & (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE))
+               tls_dane_verbose(1);
+           else
+               tls_dane_verbose(0);
+
+           /* See addr loop in connect_remote() */
+           if (state->ddane)
+               tls_dane_free(state->ddane);
+
+           /* When TLSA lookups fail, next host */
+           state->ddane = tls_dane_resolve(HNAME(addr), "tcp", port);
+           if (!state->ddane) {
+               dsb_simple(state->why, "4.7.5",
+                          "TLSA lookup error for %s:%u",
+                          HNAME(addr), ntohs(port));
+               return (TLS_LEV_INVALID);
+           }
+           /* If unusable or not found, same fallback to "secure" */
+           if (tls_dane_notfound(state->ddane)
+               || tls_dane_unusable(state->ddane)) {
+               if (msg_verbose)
+                   msg_info("no %sTLSA records found, "
+                            "resorting to \"secure\"",
+                            tls_dane_unusable(state->ddane) ?
+                            "usable " : "");
+               level = TLS_LEV_SECURE;
+           } else {
+               if (state->match)
+                   argv_free(state->match);
+               argv_add(state->match = argv_alloc(2),
+                        "hostname", "nexthop", ARGV_END);
+           }
+       } else {
+           level = TLS_LEV_SECURE;
+       }
+    }
+#endif
+
+    return (level);
+}
+
+/* parse_destination - parse host/port destination */
+
+static char *parse_destination(char *destination, char *def_service,
+                                      char **hostp, unsigned *portp)
+{
+    char   *buf = mystrdup(destination);
+    char   *service;
+    struct servent *sp;
+    char   *protocol = "tcp";
+    unsigned port;
+    const char *err;
+
+    if (msg_verbose)
+       msg_info("parse_destination: %s %s", destination, def_service);
+
+    /*
+     * Parse the host/port information. We're working with a copy of the
+     * destination argument so the parsing can be destructive.
+     */
+    if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0)
+       msg_fatal("%s in server description: %s", err, destination);
+
+    /*
+     * Convert service to port number, network byte order.
+     */
+    if (alldig(service)) {
+       if ((port = atoi(service)) >= 65536 || port == 0)
+           msg_fatal("bad network port in destination: %s", destination);
+       *portp = htons(port);
+    } else {
+       if ((sp = getservbyname(service, protocol)) != 0)
+           *portp = sp->s_port;
+       else if (strcmp(service, "smtp") == 0)
+           *portp = htons(25);
+       else
+           msg_fatal("unknown service: %s/%s", service, protocol);
+    }
+    return (buf);
+}
+
+/* connect_remote - connect to TCP destination or log an error */
+
+static void connect_remote(STATE *state, char *dest)
+{
+    DNS_RR *addr;
+    char   *buf;
+    char   *domain;
+    unsigned port;
+
+    /* When reconnecting use IP address of previous session */
+    if (state->addr == 0) {
+       buf = parse_destination(dest, state->smtp ? "smtp" : "24",
+                               &domain, &port);
+       if (!state->nexthop)
+           state->nexthop = mystrdup(domain);
+       if (state->smtp == 0 || *dest == '[')
+           state->addr = host_addr(state, domain);
+       else
+           state->addr = domain_addr(state, domain);
+       myfree(buf);
+
+       if (state->addr == 0) {
+           msg_info("Destination address lookup failed: %s",
+                    vstring_str(state->why->reason));
+           return;
+       }
+       state->port = port;
+    }
+    for (addr = state->addr; addr; addr = addr->next) {
+       int     level = dane_host_level(state, addr, port);
+
+       if (level == TLS_LEV_INVALID
+           || (state->stream = connect_addr(state, addr)) == 0) {
+           msg_info("Failed to establish session to %s via %s: %s",
+                    dest, HNAME(addr), vstring_str(state->why->reason));
+           continue;
+       }
+       /* We have a connection */
+       state->level = level;
+       state->hostname = mystrdup(HNAME(addr));
+
+       /* We use the same address when reconnecting, so flush the rest. */
+       addr = dns_rr_copy(addr);
+       dns_rr_free(state->addr);
+       state->addr = addr;
+       break;
+    }
+}
+
+/* connect_dest - connect to given inet: or unix: destination */
+
+static int connect_dest(STATE *state)
+{
+    char   *dest = state->dest;
+
+    /*
+     * With LMTP we have direct-to-host delivery only. The destination may
+     * have multiple IP addresses.
+     */
+    if (state->smtp == 0) {
+       if (strncmp(dest, "unix:", 5) == 0) {
+           connect_unix(state, dest + 5);
+           if (!state->stream)
+               msg_info("Failed to establish session to %s: %s",
+                        dest, vstring_str(state->why->reason));
+           return (1);
+       }
+       if (strncmp(dest, "inet:", 5) == 0)
+           dest += 5;
+    }
+    connect_remote(state, dest);
+
+    return (state->stream == 0);
+}
+
+static void disconnect_dest(STATE *state)
+{
+#ifdef USE_TLS
+    if (state->tls_context)
+       tls_client_stop(state->tls_ctx, state->stream,
+                       smtp_tmout, 0, state->tls_context);
+    state->tls_context = 0;
+    if (state->ddane)
+       tls_dane_free(state->ddane);
+    state->ddane = 0;
+#endif
+
+    if (state->stream)
+       vstream_fclose(state->stream);
+    state->stream = 0;
+
+    if (state->namaddrport)
+       myfree(state->namaddrport);
+    state->namaddrport = 0;
+
+    if (state->addrport)
+       myfree(state->addrport);
+    state->addrport = 0;
+
+    /* Reused on reconnect */
+    if (state->reconnect <= 0) {
+       if (state->addr)
+           dns_rr_free(state->addr);
+       state->addr = 0;
+
+       if (state->nexthop)
+           myfree(state->nexthop);
+       state->nexthop = 0;
+    }
+    if (state->hostname)
+       myfree(state->hostname);
+    state->hostname = 0;
+
+    dsb_free(state->why);
+    vstring_free(state->buffer);
+}
+
+static int finger(STATE *state)
+{
+    int     err;
+
+    /*
+     * Make sure the SMTP server cannot run us out of memory by sending
+     * never-ending lines of text.
+     */
+    state->buffer = vstring_alloc(100);
+    vstring_ctl(state->buffer, VSTRING_CTL_MAXLEN,
+               (ssize_t) var_line_limit, 0);
+    state->why = dsb_create();
+
+    if (!(err = connect_dest(state))) {
+       if (state->pass == 1)
+           msg_info("Connected to %s", state->namaddrport);
+       err = doproto(state);
+    }
+    disconnect_dest(state);
+
+    if (err != 0)
+       return (1);
+
+#ifdef USE_TLS
+    if (state->reconnect > 0) {
+       int     cache_enabled;
+       int     cache_count;
+       int     cache_hits;
+
+       tlsmgrmem_status(&cache_enabled, &cache_count, &cache_hits);
+       if (cache_enabled && cache_count == 0) {
+           msg_info("Server declined session caching. Done reconnecting.");
+           state->reconnect = 0;
+       } else if (cache_hits > 0) {
+           msg_info("Found a previously used server.  Done reconnecting.");
+           state->reconnect = 0;
+       } else if (state->max_reconnect-- <= 0) {
+           msg_info("Maximum reconnect count reached.");
+           state->reconnect = 0;
+       }
+    }
+#endif
+
+    return (0);
+}
+
+#ifdef USE_TLS
+
+/* ssl_cleanup - free memory allocated in the OpenSSL library */
+
+static void ssl_cleanup(void)
+{
+    ERR_remove_state(0);
+    ENGINE_cleanup();
+    CONF_modules_unload(1);
+    ERR_free_strings();
+    EVP_cleanup();
+    CRYPTO_cleanup_all_ex_data();
+}
+
+#endif
+
+/* run - do what we were asked to do. */
+
+static int run(STATE *state)
+{
+
+    while (1) {
+       if (finger(state) != 0)
+           break;
+       if (state->reconnect <= 0)
+           break;
+       msg_info("Reconnecting after %d seconds", state->reconnect);
+       ++state->pass;
+       sleep(state->reconnect);
+    }
+
+    return (0);
+}
+
+/* cleanup - free memory allocated in main */
+
+static void cleanup(STATE *state)
+{
+#ifdef USE_TLS
+    if (state->tls_ctx != 0)
+       tls_free_app_context(state->tls_ctx);
+    if (state->tls_bio)
+       (void) BIO_free(state->tls_bio);
+    state->tls_bio = 0;
+
+    myfree(state->mdalg);
+    myfree(state->CApath);
+    myfree(state->CAfile);
+    if (state->options.level)
+       myfree(state->options.level);
+    myfree(state->options.logopts);
+    if (state->match)
+       argv_free(state->match);
+    if (state->options.tas)
+       argv_free(state->options.tas);
+    if (state->dane)
+       tls_dane_free(state->dane);
+
+    /* Flush and free DANE TLSA cache */
+    tls_dane_flush();
+    /* Flush and free memory tlsmgr cache */
+    tlsmgrmem_flush();
+#endif
+    myfree(state->options.host_lookup);
+    myfree(state->dest);
+
+    mail_conf_flush();
+}
+
+/* usage - explain */
+
+static void usage()
+{
+#ifdef USE_TLS
+    fprintf(stderr, "usage: %s %s \\\n\t%s \\\n\t%s destination [match ...]\n",
+           var_procname, "[-acCStTv] [-d mdalg] [-F CAfile.pem]",
+           "[-h host_lookup] [-l level] [-L logopts] [-m count]",
+           "[-o name=value] [-P CApath/] [-r delay]");
+#else
+    fprintf(stderr, "usage: %s [-acStTv] [-h host_lookup] [-o name=value] destination\n"
+           var_procname);
+#endif
+    exit(1);
+}
+
+/* tls_init - initialize application TLS library context */
+
+static void tls_init(STATE *state)
+{
+#ifdef USE_TLS
+    TLS_CLIENT_INIT_PROPS props;
+
+    if (state->level <= TLS_LEV_NONE)
+       return;
+
+    state->tls_ctx =
+       TLS_CLIENT_INIT(&props,
+                       log_param = "-L option",
+                       log_level = state->options.logopts,
+                       verifydepth = DEF_SMTP_TLS_SCERT_VD,
+                       cache_type = "memory",
+                       cert_file = "",
+                       key_file = "",
+                       dcert_file = "",
+                       dkey_file = "",
+                       eccert_file = "",
+                       eckey_file = "",
+                       CAfile = state->CAfile,
+                       CApath = state->CApath,
+                       mdalg = state->mdalg);
+#endif
+}
+
+/* override - update main.cf parameter */
+
+static void override(const char *nameval)
+{
+    char   *param_name;
+    char   *param_value;
+    char   *save = mystrdup(nameval);
+
+    if (split_nameval(save, &param_name, &param_value) != 0)
+       usage();
+    mail_conf_update(param_name, param_value);
+    myfree(save);
+}
+
+/* parse_options - (argc, argv) -> state */
+
+static void parse_options(STATE *state, int argc, char *argv[])
+{
+    int     c;
+
+    state->smtp = 1;
+    state->pass = 1;
+    state->reconnect = -1;
+    state->max_reconnect = 5;
+    memset((char *) &state->options, 0, sizeof(state->options));
+    state->options.host_lookup = mystrdup("dns");
+
+#define OPTS "a:ch:o:St:T:v"
+#ifdef USE_TLS
+#define TLSOPTS "A:Cd:F:l:L:m:P:r:"
+
+    state->mdalg = mystrdup("sha1");
+    state->CApath = mystrdup("");
+    state->CAfile = mystrdup("");
+    state->options.tas = argv_alloc(1);
+    state->options.logopts = 0;
+    state->level = TLS_LEV_DANE;
+#else
+#define TLSOPTS ""
+    state->level = TLS_LEV_NONE;
+#endif
+
+    while ((c = GETOPT(argc, argv, OPTS TLSOPTS)) > 0) {
+       switch (c) {
+       default:
+           usage();
+           break;
+       case 'a':
+           state->options.addr_pref = mystrdup(optarg);
+           break;
+       case 'c':
+           state->nochat = 1;
+           break;
+       case 'h':
+           myfree(state->options.host_lookup);
+           state->options.host_lookup = mystrdup(optarg);
+           break;
+       case 'o':
+           override(optarg);
+           break;
+       case 'S':
+           state->smtp = 0;
+           break;
+       case 't':
+           conn_tmout = atoi(optarg);
+           break;
+       case 'T':
+           smtp_tmout = atoi(optarg);
+           break;
+       case 'v':
+           msg_verbose++;
+           break;
+#ifdef USE_TLS
+       case 'A':
+           argv_add(state->options.tas, optarg, ARGV_END);
+           break;
+       case 'C':
+           state->print_trust = 1;
+           break;
+       case 'd':
+           myfree(state->mdalg);
+           state->mdalg = mystrdup(optarg);
+           break;
+       case 'F':
+           myfree(state->CAfile);
+           state->CAfile = mystrdup(optarg);
+           break;
+       case 'l':
+           if (state->options.level)
+               myfree(state->options.level);
+           state->options.level = mystrdup(optarg);
+           break;
+       case 'L':
+           if (state->options.logopts)
+               myfree(state->options.logopts);
+           state->options.logopts = mystrdup(optarg);
+           break;
+       case 'm':
+           state->max_reconnect = atoi(optarg);
+           break;
+       case 'P':
+           myfree(state->CApath);
+           state->CApath = mystrdup(optarg);
+           break;
+       case 'r':
+           state->reconnect = atoi(optarg);
+           break;
+#endif
+       }
+    }
+
+    /*
+     * Address family preference.
+     */
+    state->addr_pref =
+       name_code(addr_pref_map, NAME_CODE_FLAG_NONE, state->options.addr_pref ?
+                 state->options.addr_pref : "any");
+    if (state->addr_pref < 0)
+       msg_fatal("bad '-a' option value: %s", state->options.addr_pref);
+
+    /*
+     * Select hostname lookup mechanisms.
+     */
+    state->host_lookup =
+       name_mask("-h option", lookup_masks, state->options.host_lookup ?
+                 state->options.host_lookup : "dns");
+
+#ifdef USE_TLS
+
+    if (state->reconnect < 0)
+       tlsmgrmem_disable();
+
+    if (state->options.logopts == 0)
+       state->options.logopts = mystrdup("routine,certmatch");
+    state->log_mask = tls_log_mask("-L option", state->options.logopts);
+
+    if (state->options.level) {
+       state->level = tls_level_lookup(state->options.level);
+
+       if (state->level == TLS_LEV_INVALID)
+           msg_fatal("Invalid TLS level \"%s\"", state->options.level);
+
+       if (state->level == TLS_LEV_NONE)
+           return;
+    }
+
+    /*
+     * We first call tls_init(), which ultimately calls SSL_library_init(),
+     * since otherwise we can't tell whether we have the message digests
+     * required for DANE support.
+     */
+    tls_init(state);
+    if (state->level == TLS_LEV_DANE && !tls_dane_avail()) {
+       msg_warn("The \"dane\" TLS security level is not available");
+       state->level = TLS_LEV_SECURE;
+    }
+    state->tls_bio = 0;
+    if (state->print_trust)
+       state->tls_bio = BIO_new_fp(stdout, BIO_NOCLOSE);
+
+#endif
+}
+
+/* parse_match - process match arguments */
+
+static void parse_match(STATE *state, int argc, char *argv[])
+{
+#ifdef USE_TLS
+
+    argc -= optind;
+    argv += optind;
+
+    switch (state->level) {
+    case TLS_LEV_SECURE:
+    case TLS_LEV_DANE:
+       state->match = argv_alloc(1);
+       while (*argv)
+           argv_split_append(state->match, *argv++, "");
+       if (state->match->argc == 0)
+           argv_add(state->match, "nexthop", "dot-nexthop", ARGV_END);
+       break;
+    case TLS_LEV_VERIFY:
+       state->match = argv_alloc(1);
+       while (*argv)
+           argv_split_append(state->match, *argv++, "");
+       if (state->match->argc == 0)
+           argv_add(state->match, "hostname", ARGV_END);
+       break;
+    case TLS_LEV_FPRINT:
+       state->dane = tls_dane_alloc(TLS_DANE_FLAG_MIXED);
+       while (*argv)
+           tls_dane_split((TLS_DANE *) state->dane, TLS_DANE_EE, TLS_DANE_PKEY,
+                          state->mdalg, *argv++, "");
+       tls_dane_final((TLS_DANE *) state->dane);
+       break;
+    }
+#endif
+}
+
+/* parse_tas - process '-A' trust anchor file option */
+
+static void parse_tas(STATE *state)
+{
+#ifdef USE_TLS
+    char  **file;
+
+    if (!state->options.tas->argc)
+       return;
+
+    switch (state->level) {
+    default:
+       return;
+    case TLS_LEV_SECURE:
+    case TLS_LEV_VERIFY:
+    case TLS_LEV_DANE:
+       state->dane = tls_dane_alloc(TLS_DANE_FLAG_MIXED);
+       for (file = state->options.tas->argv; *file; ++file) {
+           if (!tls_dane_load_trustfile((TLS_DANE *) state->dane, *file))
+               break;
+       }
+       if (*file)
+           msg_fatal("Failed to load trust anchor file: %s", *file);
+       tls_dane_final((TLS_DANE *) state->dane);
+       break;
+    }
+#endif
+}
+
+
+int     main(int argc, char *argv[])
+{
+    static STATE state;
+    char   *loopenv = getenv("VALGRINDLOOP");
+    int     loop = loopenv ? atoi(loopenv) : 1;
+
+    /* Don't die when a peer goes away unexpectedly. */
+    signal(SIGPIPE, SIG_IGN);
+
+    /* We're a diagnostic utility, so diagnostic messages go to stdout. */
+    var_procname = mystrdup(basename(argv[0]));
+    set_mail_conf_str(VAR_PROCNAME, var_procname);
+    msg_vstream_init(var_procname, VSTREAM_OUT);
+
+    /*
+     * Load main.cf, parse command-line options, then process main.cf
+     * settings plus any command-line "-o" overrides.
+     */
+    mail_conf_suck();
+    parse_options(&state, argc, argv);
+    mail_params_init();
+
+    parse_match(&state, argc, argv);
+    parse_tas(&state);
+
+    /* The first non-option argument is the destination. */
+    if (argc < optind)
+       usage();
+    state.dest = mystrdup(argv[optind]);
+
+    /* Don't talk to remote systems as root */
+    if (!geteuid())
+       chroot_uid(0, var_mail_owner);
+
+    while (loop-- > 0)
+       run(&state);
+
+    /* Be valgrind friendly and clean-up */
+    cleanup(&state);
+#ifdef USE_TLS
+    ssl_cleanup();
+#endif
+
+    return (0);
+}
+
diff --git a/postfix/src/posttls-finger/tlsmgrmem.c b/postfix/src/posttls-finger/tlsmgrmem.c
new file mode 100644 (file)
index 0000000..413fcfb
--- /dev/null
@@ -0,0 +1,143 @@
+/*++
+/* NAME
+/*     tlsmgrmem 3
+/* SUMMARY
+/*     Memory-based TLS manager interface for tlsfinger(1).
+/* SYNOPSIS
+/*     #ifdef  USE_TLS
+/*     #include <tlsmgrmem.h>
+/*
+/*     void    tlsmgrmem_disable()
+/*
+/*     void    tlsmgrmem_status(enable, count, hits)
+/*     int     *enable;
+/*     int     *count;
+/*     int     *hits;
+/*
+/*     void    tlsmgrmem_flush()
+/*     #endif
+/* DESCRIPTION
+/*     tlsmgrmem_disable() disables the in-memory TLS session cache.
+/*
+/*     tlsmgrmem_status() reports whether the cache is enabled, the
+/*     number of entries in the cache, and the number of cache hits.
+/*     If any of the return pointers are null, that item is not reported.
+/*
+/*     tlsmgrmem_flush() flushes any cached data and frees the cache.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*
+/*     Viktor Dukhovni
+/*--*/
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+#include <htable.h>
+#include <vstring.h>
+#include <tls_mgr.h>
+
+#include "tlsmgrmem.h"
+
+static HTABLE *tls_cache;
+static int cache_enabled = 1;
+static int cache_count;
+static int cache_hits;
+typedef void (*free_func) (char *);
+static free_func free_value = (free_func) vstring_free;
+
+void    tlsmgrmem_disable(void)
+{
+    cache_enabled = 0;
+}
+
+void    tlsmgrmem_flush(void)
+{
+    if (!tls_cache)
+       return;
+    htable_free(tls_cache, free_value);
+}
+
+void    tlsmgrmem_status(int *enabled, int *count, int *hits)
+{
+    if (enabled)
+       *enabled = cache_enabled;
+    if (count)
+       *count = cache_count;
+    if (hits)
+       *hits = cache_hits;
+}
+
+/* tls_mgr_* - Local cache and stubs that do not talk to the TLS manager */
+
+int     tls_mgr_seed(VSTRING *buf, int len)
+{
+    return (TLS_MGR_STAT_OK);
+}
+
+int     tls_mgr_policy(const char *unused_type, int *cachable)
+{
+    if (cache_enabled && tls_cache == 0)
+       tls_cache = htable_create(1);
+    *cachable = cache_enabled;
+    return (TLS_MGR_STAT_OK);
+}
+
+int     tls_mgr_lookup(const char *unused_type, const char *key, VSTRING *buf)
+{
+    VSTRING *s;
+
+    if (tls_cache == 0)
+       return TLS_MGR_STAT_ERR;
+
+    if ((s = (VSTRING *) htable_find(tls_cache, key)) == 0)
+       return TLS_MGR_STAT_ERR;
+
+    vstring_memcpy(buf, vstring_str(s), VSTRING_LEN(s));
+
+    ++cache_hits;
+    return (TLS_MGR_STAT_OK);
+}
+
+int     tls_mgr_update(const char *unused_type, const char *key,
+                              const char *buf, ssize_t len)
+{
+    HTABLE_INFO *ent;
+    VSTRING *s;
+
+    if (tls_cache == 0)
+       return TLS_MGR_STAT_ERR;
+
+    if ((ent = htable_locate(tls_cache, key)) == 0) {
+       s = vstring_alloc(len);
+       ent = htable_enter(tls_cache, key, (char *) s);
+    } else {
+       s = (VSTRING *) ent->value;
+    }
+    vstring_memcpy(s, buf, len);
+
+    ++cache_count;
+    return (TLS_MGR_STAT_OK);
+}
+
+int     tls_mgr_delete(const char *unused_type, const char *key)
+{
+    if (tls_cache == 0)
+       return TLS_MGR_STAT_ERR;
+
+    if (htable_locate(tls_cache, key)) {
+       htable_delete(tls_cache, key, free_value);
+       --cache_count;
+    }
+    return (TLS_MGR_STAT_OK);
+}
+
+#endif
+
diff --git a/postfix/src/posttls-finger/tlsmgrmem.h b/postfix/src/posttls-finger/tlsmgrmem.h
new file mode 100644 (file)
index 0000000..be2946b
--- /dev/null
@@ -0,0 +1,29 @@
+/*++
+/* NAME
+/*     tlsmgrmem 3
+/* SUMMARY
+/*     Memory-based TLS manager interface for tlsfinger(1).
+/* SYNOPSIS
+/*     #include <tlsmgrmem.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void tlsmgrmem_disable(void);
+extern void tlsmgrmem_status(int *, int *, int *);
+extern void tlsmgrmem_flush(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*
+/*     Viktor Dukhovni
+/*--*/
+
index a813228bc85c695abad035e602f26fad0aea33b4..14a9aba16aad9485ba8880fe7df2d8dddba9e773 100644 (file)
@@ -105,8 +105,27 @@ typedef struct SMTP_TLS_POLICY {
   * smtp_tls_policy.c
   */
 extern void smtp_tls_list_init(void);
-extern int smtp_tls_policy(DSN_BUF *, SMTP_TLS_POLICY *, SMTP_ITERATOR *);
-extern void smtp_tls_policy_flush(void);
+extern int smtp_tls_policy_cache_query(DSN_BUF *, SMTP_TLS_POLICY *, SMTP_ITERATOR *);
+extern void smtp_tls_policy_cache_flush(void);
+
+#define smtp_tls_policy_dummy(t) do { \
+       SMTP_TLS_POLICY *_t = (t); \
+       smtp_tls_policy_init(_t, (DSN_BUF *) 0); \
+       _t->level = TLS_LEV_NONE; \
+    } while (0)
+
+ /* This macro is not part of the module external interface. */
+#define smtp_tls_policy_init(t, w) do { \
+       SMTP_TLS_POLICY *_t = (t); \
+       _t->protocols = 0; \
+       _t->grade = 0; \
+       _t->exclusions = 0; \
+       _t->matchargv = 0; \
+       _t->why = (w); \
+       _t->dane = 0; \
+       _t->dane_no_lev = TLS_LEV_NOTFOUND; \
+       _t->dane_un_lev = TLS_LEV_NOTFOUND; \
+    } while (0)
 
 #endif
 
index 8ef2033c73648bf402527ce3b89173378cfdc28e..06a6ad92baede3073aae260f2aa256b32d88738b 100644 (file)
@@ -523,7 +523,7 @@ static void smtp_connect_local(SMTP_STATE *state, const char *path)
      * of SASL-unauthenticated connections.
      */
 #ifdef USE_TLS
-    if (!smtp_tls_policy(why, state->tls, iter)) {
+    if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
        msg_info("TLS policy lookup error for %s/%s: %s",
                 STR(iter->host), STR(iter->addr), STR(why->reason));
        return;
@@ -658,10 +658,10 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
     MAI_HOSTADDR_STR hostaddr;
     SMTP_SESSION *session;
     SMTP_ITERATOR *iter = state->iterator;
-    DSN_BUF *why;
+    DSN_BUF *why = state->why;
 
     /*
-     * First, search the cache by logical destination. We truncate the server
+     * First, search the cache by request nexthop. We truncate the server
      * address list when all the sessions for this destination are used up,
      * to reduce the number of variables that need to be checked later.
      * 
@@ -671,15 +671,13 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
      * and restore it here, so that subsequent connections will use the
      * proper nexthop information.
      * 
-     * Since we never reuse a connection after turning on TLS, we look up a
-     * dummy TLS policy ("surprise me") for initialization purposes only. XXX
-     * Implement a better API to initialize a TLS policy object.
+     * We request a dummy "TLS disabled" policy for connection-cache lookup by
+     * request nexthop only. If we find a saved connection, then we know that
+     * plaintext was permitted, because we never save a connection after
+     * turning on TLS.
      */
 #ifdef USE_TLS
-#define NO_DSN_BUF     ((DSN_BUF *) 0)
-#define NO_ITERATOR     ((SMTP_ITERATOR *) 0)
-
-    (void) smtp_tls_policy(NO_DSN_BUF, state->tls, NO_ITERATOR);
+    smtp_tls_policy_dummy(state->tls);
 #endif
     SMTP_ITER_SAVE_DEST(state->iterator);
     if (*addr_list && SMTP_RCPT_LEFT(state) > 0
@@ -734,7 +732,7 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
        vstring_strcpy(iter->host, SMTP_HNAME(addr));
        iter->rr = addr;
 #ifdef USE_TLS
-       if (!smtp_tls_policy(why, state->tls, iter)) {
+       if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
            msg_info("TLS policy lookup error for %s/%s: %s",
                     STR(iter->dest), STR(iter->host), STR(why->reason));
            continue;
@@ -903,10 +901,6 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
         * good sessions will be stored under their specific server IP
         * address.
         * 
-        * XXX Replace sites->argv by (lookup_mx, domain, port) triples so we
-        * don't have to make clumsy ad-hoc copies and keep track of who
-        * free()s the memory.
-        * 
         * XXX smtp_session_cache_destinations specifies domain names without
         * :port, because : is already used for maptype:mapname. Because of
         * this limitation we use the bare domain without the optional [] or
@@ -915,11 +909,6 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
         * Opportunistic (a.k.a. on-demand) session caching on request by the
         * queue manager. This is turned temporarily when a destination has a
         * high volume of mail in the active queue.
-        * 
-        * XXX Disable connection caching when sender-dependent authentication
-        * is enabled. We must not send someone elses mail over an
-        * authenticated connection, and we must not send mail that requires
-        * authentication over a connection that wasn't authenticated.
         */
        if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
            smtp_cache_policy(state, domain);
@@ -986,7 +975,7 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
            vstring_strcpy(iter->host, SMTP_HNAME(addr));
            iter->rr = addr;
 #ifdef USE_TLS
-           if (!smtp_tls_policy(why, state->tls, iter)) {
+           if (!smtp_tls_policy_cache_query(why, state->tls, iter)) {
                msg_info("TLS policy lookup for %s/%s: %s",
                         STR(iter->dest), STR(iter->host), STR(why->reason));
                continue;
@@ -994,7 +983,7 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
            }
            /* Disable TLS when retrying after a handshake failure */
            if (retry_plain) {
-               session->tls->level = TLS_LEV_NONE;
+               state->tls->level = TLS_LEV_NONE;
                retry_plain = 0;
            }
 #endif
@@ -1009,6 +998,7 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
                session->state = state;
 #ifdef USE_TLS
                session->tls = state->tls;      /* TEMPORARY */
+               session->tls_nexthop = domain;  /* for TLS_LEV_SECURE */
 #endif
                if (addr->pref == domain_best_pref)
                    session->features |= SMTP_FEATURE_BEST_MX;
@@ -1016,9 +1006,6 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
                if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
                    && next == 0)
                    state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
-#ifdef USE_TLS
-               session->tls_nexthop = domain;  /* for TLS_LEV_SECURE */
-#endif
                if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
                    && smtp_helo(state) != 0) {
 #ifdef USE_TLS
@@ -1056,7 +1043,7 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
                /* The reason already includes the IP address and TCP port. */
                msg_info("%s", STR(why->reason));
            }
-           /* Insert: test if we must skip the remaining MX hosts. */
+           /* XXX Code above assumes there is no code at this loop ending. */
        }
        dns_rr_free(addr_list);
        myfree(dest_buf);
index a657679296220bc5b99fde32192af9c197f7e168..a1ba7112f96198aaf21ba7cb9e38d377cb53defe 100644 (file)
@@ -157,6 +157,16 @@ static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd,
     const char *myname = "smtp_reuse_common";
     SMTP_SESSION *session;
 
+    /*
+     * Can't happen. Both smtp_reuse_nexthop() and smtp_reuse_addr() decline
+     * the request when the TLS policy is not TLS_LEV_NONE.
+     */
+#ifdef USE_TLS
+    if (state->tls->level > TLS_LEV_NONE)
+       msg_panic("%s: unexpected plain-text cached session to %s",
+                 myname, label);
+#endif
+
     /*
      * Re-activate the SMTP_SESSION object.
      */
@@ -173,37 +183,6 @@ static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd,
     session->tls = state->tls;                 /* TEMPORARY */
 #endif
 
-    /*
-     * XXX Temporary fix.
-     * 
-     * Cached connections are always plaintext. They must never be reused when
-     * TLS encryption is required.
-     * 
-     * As long as we support the legacy smtp_tls_per_site feature, we must
-     * search the connection cache before making TLS policy decisions. This
-     * is because the policy can depend on the server name. For example, a
-     * site could have a global policy that requires encryption, with
-     * per-server exceptions that allow plaintext.
-     * 
-     * With the newer smtp_tls_policy_maps feature, the policy depends on the
-     * next-hop destination only. We can avoid unnecessary connection cache
-     * lookups, because we can compute the TLS policy much earlier.
-     * 
-     * XXX This can now be a panic. Connection reuse by nexthop uses a dummy
-     * "surprise me" policy, and smtp_reuse_addr() now explicitly declines
-     * the request when the policy is not TLS_LEV_NONE.
-     */
-#ifdef USE_TLS
-    if (session->tls->level >= TLS_LEV_ENCRYPT) {
-       if (msg_verbose)
-           msg_info("%s: skipping plain-text cached session to %s",
-                    myname, label);
-       smtp_quit(state);                       /* Close politely */
-       smtp_session_free(session);             /* And avoid leaks */
-       return (state->session = 0);
-    }
-#endif
-
     /*
      * Send an RSET probe to verify that the session is still good.
      */
@@ -233,6 +212,15 @@ SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *state, int name_key_flags)
     SMTP_SESSION *session;
     int     fd;
 
+    /*
+     * Don't look up an existing plaintext connection when a new connection
+     * would (try to) use TLS.
+     */
+#ifdef USE_TLS
+    if (state->tls->level > TLS_LEV_NONE)
+       return (0);
+#endif
+
     /*
      * Look up the session by its logical name.
      */
index e02e1a1874c54b310ca1de7274534243c3993396..a8eaae1888eebd9f71093bc5eddf9b58e6fcc6d6 100644 (file)
@@ -234,6 +234,10 @@ int     smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
      * XXX It would be nice to have a VSTRING to VSTREAM adapter so that we can
      * serialize the properties with attr_print() instead of using ad-hoc,
      * non-reusable, code and hard-coded format strings.
+     * 
+     * TODO: save SASL username and password information so that we can
+     * correctly save a reused authenticated connection.
+     * 
      */
     vstring_sprintf(dest_prop, "%s\n%s\n%s\n%u",
                    session->dest, session->host, session->addr,
@@ -335,6 +339,9 @@ SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter,
      * SMTP_ITER_SAVE_DEST() and SMTP_ITER_RESTORE_DEST().
      * 
      * TODO: Eliminate the duplication between SMTP_ITERATOR and SMTP_SESSION.
+     * 
+     * TODO: restore SASL username and password information so that we can
+     * correctly save a reused authenticated connection.
      */
     if (dest_prop && VSTRING_LEN(dest_prop)) {
        dest_props = STR(dest_prop);
@@ -364,7 +371,7 @@ SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter,
 #define NO_FLAGS       0
 
     session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), iter,
-                                               (time_t) 0, NO_FLAGS);
+                                (time_t) 0, NO_FLAGS);
     session->features = (features | SMTP_FEATURE_FROM_CACHE);
     CACHE_THIS_SESSION_UNTIL(expire_time);
     session->reuse_count = ++reuse_count;
index a00c08a83428e7cf2d4da5074142a0071ad02152..7e95462eb1d558741d39b9fa99d947a57ad69938 100644 (file)
@@ -90,7 +90,7 @@ void    smtp_state_free(SMTP_STATE *state)
 {
 #ifdef USE_TLS
     /* The TLS policy cache lifetime is one delivery. */
-    smtp_tls_policy_flush();
+    smtp_tls_policy_cache_flush();
 #endif
     vstring_free(state->iterator->request_nexthop);
     vstring_free(state->iterator->dest);
index cac6ceba7fa90972f39de65f83e1510559346c72..f0ec164151804b6672ee74ce862b977961357f2c 100644 (file)
@@ -8,38 +8,42 @@
 /*
 /*     void    smtp_tls_list_init()
 /*
-/*     int     smtp_tls_policy(why, tls, iter)
+/*     int     smtp_tls_policy_cache_query(why, tls, iter)
 /*     DSN_BUF *why;
 /*     SMTP_TLS_POLICY *tls;
 /*     SMTP_ITERATOR *iter;
 /*
-/*     void    smtp_tls_policy_flush()
+/*     void    smtp_tls_policy_dummy(tls)
+/*     SMTP_TLS_POLICY *tls;
+/*
+/*     void    smtp_tls_policy_cache_flush()
 /* DESCRIPTION
 /*     smtp_tls_list_init() initializes lookup tables used by the TLS
 /*     policy engine.
 /*
-/*     smtp_tls_policy() returns the SMTP_TLS_POLICY structure for
-/*     the iterator's destination, host, port and DNSSEC validation
-/*     status. Any of the required table and DNS lookups can fail.
-/*     When this happens, the "why" argument is updated with the
-/*     error reason and the result value is zero (false).
-/*     If the iterator argument is null, no policy checks are made,
-/*     a trivial policy with TLS disabled is returned, and no error
-/*     is reported.
+/*     smtp_tls_policy_cache_query() returns a shallow copy of the
+/*     cached SMTP_TLS_POLICY structure for the iterator's
+/*     destination, host, port and DNSSEC validation status.
+/*     This copy is guaranteed to be valid until the next
+/*     smtp_tls_policy_cache_query() or smtp_tls_policy_cache_flush()
+/*     call.  The caller can override the TLS security level without
+/*     corrupting the policy cache.
+/*     When any required table or DNS lookups fail, the TLS level
+/*     is set to TLS_LEV_INVALID, the "why" argument is updated
+/*     with the error reason and the result value is zero (false).
+/*
+/*     smtp_tls_policy_dummy() initializes a trivial, non-cached,
+/*     policy with TLS disabled.
 /*
-/*     smtp_tls_policy_flush() destroys the TLS policy cache and
-/*     contents.
+/*     smtp_tls_policy_cache_flush() destroys the TLS policy cache
+/*     and contents.
 /*
 /*     Arguments:
 /* .IP why
 /*     A pointer to a DSN_BUF which holds error status information when
 /*     the TLS policy lookup fails.
 /* .IP tls
-/*     Pointer to storage for a shallow copy of the cached TLS
-/*     policy. This a copy is guaranteed to be until the next
-/*     smtp_tls_policy() or smtp_tls_policy_flush() call.
-/*     The caller can override the TLS security level without
-/*     corrupting the policy cache.
+/*     Pointer to TLS policy storage.
 /* .IP iter
 /*     The literal next-hop or fall-back destination including
 /*     the optional [] and including the :port or :service;
@@ -507,21 +511,21 @@ static void set_cipher_grade(SMTP_TLS_POLICY *tls)
     ADD_EXCLUDE(tls->exclusions, also_exclude);
 }
 
-/* tls_policy_init - initialize policy in an embryonic cache entry */
+/* policy_create - create SMTP TLS policy cache object (ctable call-back) */
 
-static void tls_policy_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
+static void *policy_create(const char *unused_key, void *context)
 {
+    SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context;
     int     site_level;
-    const char *dest;
-    const char *host;
+    const char *dest = STR(iter->dest);
+    const char *host = STR(iter->host);
 
-    /* Caller requested trivial policy */
-    if (!iter) {
-       tls->level = TLS_LEV_NONE;
-       return;
-    }
-    dest = STR(iter->dest);
-    host = STR(iter->host);
+    /*
+     * Prepare a pristine policy object.
+     */
+    SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls));
+
+    smtp_tls_policy_init(tls, dsb_create());
 
     /*
      * Compute the per-site TLS enforcement level. For compatibility with the
@@ -564,7 +568,7 @@ static void tls_policy_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
     case TLS_LEV_NOTFOUND:
        break;
     case TLS_LEV_INVALID:
-       return;
+       return ((void *) tls);
     }
 
     /*
@@ -574,7 +578,7 @@ static void tls_policy_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
     if (tls->level == TLS_LEV_DANE)
        dane_init(tls, iter);
     if (tls->level == TLS_LEV_INVALID)
-       return;
+       return ((void *) tls);
 
     /*
      * Use main.cf protocols setting if not set in per-destination table.
@@ -613,7 +617,7 @@ static void tls_policy_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
                       "security level, but with no fingerprints to match.",
                         dest);
                MARK_INVALID(tls->why, &tls->level);
-               return;
+               return ((void *) tls);
            }
        }
        tls_dane_final(tls->dane);
@@ -633,7 +637,7 @@ static void tls_policy_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
                    tls_dane_final(tls->dane);
                else {
                    MARK_INVALID(tls->why, &tls->level);
-                   return;
+                   return ((void *) tls);
                }
            }
        }
@@ -644,12 +648,16 @@ static void tls_policy_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
 
     if (msg_verbose && tls->level != global_tls_level())
        msg_info("%s TLS level: %s", "effective", policy_name(tls->level));
+
+    return ((void *) tls);
 }
 
-/* tls_policy_free - free no longer cached policy */
+/* policy_delete - free no longer cached policy (ctable call-back) */
 
-static void smtp_tls_policy_free(SMTP_TLS_POLICY *tls)
+static void policy_delete(void *item, void *unused_context)
 {
+    SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) item;
+
     if (tls->protocols)
        myfree(tls->protocols);
     if (tls->grade)
@@ -665,88 +673,59 @@ static void smtp_tls_policy_free(SMTP_TLS_POLICY *tls)
     myfree((char *) tls);
 }
 
-/* policy_create - create embryonic SMTP TLS policy, ctable glue. */
-
-static void *policy_create(const char *key, void *context)
-{
-    SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls));
-    SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context;
-
-    tls->protocols = 0;
-    tls->grade = 0;
-    tls->exclusions = 0;
-    tls->matchargv = 0;
-    tls->why = dsb_create();
-    tls->dane = 0;
-    tls->dane_no_lev = TLS_LEV_NOTFOUND;
-    tls->dane_un_lev = TLS_LEV_NOTFOUND;
-
-    if (iter)
-       tls_policy_init(tls, iter);
-    else
-       tls->level = TLS_LEV_NONE;
-
-    return ((void *) tls);
-}
-
-/* policy_delete - delete SMTP TLS policy, ctable glue. */
+/* smtp_tls_policy_cache_query - cached lookup of TLS policy */
 
-static void policy_delete(void *item, void *unused_context)
-{
-    smtp_tls_policy_free((SMTP_TLS_POLICY *) item);
-}
-
-/* smtp_tls_policy - cached lookup of TLS policy */
-
-int     smtp_tls_policy(DSN_BUF *why, SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
+int     smtp_tls_policy_cache_query(DSN_BUF *why, SMTP_TLS_POLICY *tls,
+                                           SMTP_ITERATOR *iter)
 {
     VSTRING *key;
-    SMTP_TLS_POLICY *lookup;
-    int     valid = iter && iter->rr && iter->rr->validated;
+    int     valid = iter->rr && iter->rr->validated;
 
+    /*
+     * Create an empty TLS Policy cache on the fly.
+     */
     if (policy_cache == 0)
        policy_cache =
            ctable_create(CACHE_SIZE, policy_create, policy_delete, (void *) 0);
 
+    /*
+     * Query the TLS Policy cache, with a search key that reflects our shared
+     * values that also appear in other cache and table search keys.
+     */
+    key = vstring_alloc(100);
+    smtp_key_prefix(key, ":", iter, SMTP_KEY_FLAG_NEXTHOP
+                   | SMTP_KEY_FLAG_HOSTNAME
+                   | SMTP_KEY_FLAG_PORT);
+    vstring_sprintf_append(key, "%d", !!valid);
     ctable_newcontext(policy_cache, (void *) iter);
-    if (iter != 0) {
-       if (why == 0)
-           msg_panic("smtp_tls_policy: no storage for error information");
-       key = vstring_alloc(100);
-       smtp_key_prefix(key, ":", iter, SMTP_KEY_FLAG_NEXTHOP
-                       | SMTP_KEY_FLAG_HOSTNAME
-                       | SMTP_KEY_FLAG_PORT);
-       vstring_sprintf_append(key, "%d", !!valid);
-       lookup = (SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key));
-       vstring_free(key);
-    } else {
-       lookup = (SMTP_TLS_POLICY *) ctable_locate(policy_cache, "");
-    }
-
-    /* Shallow copy, so that caller can disable TLS without cache corruption. */
-    *tls = *lookup;
+    *tls = *(SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key));
+    vstring_free(key);
 
-    if (tls->level != TLS_LEV_INVALID) {
-       return (1);
-    } else {
-       /* XXX Simplify this by implementing a dsn_copy() primitive. */
+    /*
+     * Report errors. Both error and non-error results are cached. We must
+     * therefore copy the cached DSN buffer content to the caller's buffer.
+     */
+    if (tls->level == TLS_LEV_INVALID) {
+       /* XXX Simplify this by implementing a "copy" primitive. */
        dsb_update(why,
                   STR(tls->why->status), STR(tls->why->action),
                   STR(tls->why->mtype), STR(tls->why->mname),
                   STR(tls->why->dtype), STR(tls->why->dtext),
                   "%s", STR(tls->why->reason));
        return (0);
+    } else {
+       return (1);
     }
 }
 
-/* smtp_tls_policy_flush - flush TLS policy cache */
+/* smtp_tls_policy_cache_flush - flush TLS policy cache */
 
-void    smtp_tls_policy_flush(void)
+void    smtp_tls_policy_cache_flush(void)
 {
-    if (policy_cache == 0)
-       return;
-    ctable_free(policy_cache);
-    policy_cache = 0;
+    if (policy_cache != 0) {
+       ctable_free(policy_cache);
+       policy_cache = 0;
+    }
 }
 
 /* dane_no_level - parse and cache var_smtp_tls_dane_no_lev */
@@ -824,7 +803,7 @@ static int global_tls_level(void)
 static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
 {
     TLS_DANE *dane;
-    int     valid = iter && iter->rr && iter->rr->validated;
+    int     valid = iter->rr && iter->rr->validated;
 
     if (!iter->port) {
        msg_warn("%s: the \"dane\" security level is invalid for delivery via"
@@ -832,26 +811,30 @@ static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
        MARK_INVALID(tls->why, &tls->level);
        return;
     }
-
-    /*
+    /*-
      * Some senders will want (destination-dependent) mandatory TLS with key
      * management via DANE.  When TLSA lookups are not possible, use the
      * locally specified "degraded" levels.
-     * 
+     *
      * This is supported via the policy table:
-     * 
-     * tls_policy: # # If TLSA records vanish, force TLS without verification #
-     * example.com dane notfound_tlsa_level=encrypt # # If TLSA records
-     * vanish, resort to public CA trust # example.net dane
-     * notfound_tlsa_level=secure unusable_tlsa_level=secure
-     * match=example.net
-     * 
+     *
+     * tls_policy:
+     *
+     *     # If TLSA records vanish, force TLS without verification
+     *     example.com dane notfound_tlsa_level=encrypt
+     *
+     *     # If TLSA records vanish, resort to public CA trust
+     *     example.net dane notfound_tlsa_level=secure
+     *         unusable_tlsa_level=secure match=example.net
+     *
      * or in isolated cases via global settings:
-     * 
-     * main.cf: relayhost = [secure.example.com] smtp_tls_security_level = dane
-     * smtp_tls_dane_tlsa_notfound_level = secure
-     * smtp_tls_dane_tlsa_unusable_level = secure smtp_tls_CAfile =
-     * /etc/ssl/certs/someCA.pem
+     *
+     * main.cf:
+     *     relayhost = [secure.example.com]
+     *     smtp_tls_security_level = dane
+     *     smtp_tls_dane_tlsa_notfound_level = secure
+     *     smtp_tls_dane_tlsa_unusable_level = secure
+     *     smtp_tls_CAfile = /etc/ssl/certs/someCA.pem
      */
     if (tls->dane_no_lev == TLS_LEV_NOTFOUND)
        tls->dane_no_lev = dane_no_level();
@@ -935,11 +918,6 @@ static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
 
     /*
      * With DANE trust anchors, peername matching is not configurable.
-     * 
-     * XXX: No SSLv3, DANE is for TLSv1 and later. We may also want to enable
-     * the client side of SNI to indicate the MX hostname.
-     * 
-     * Note: already sorted by tls_dane_lookup().
      */
     if (tls->matchargv)
        argv_free(tls->matchargv);
index fde67f80d2e094b014164d8dfe2361e9dae29fbe..138b5b4fa8345d5a1908ec12a5041aeea2afa5a6 100644 (file)
@@ -261,8 +261,9 @@ struct TLS_APPL_STATE {
 };
 
  /*
-  * tls_misc.c One time finalization of application context.
+  * tls_misc.c Application-context update and disposal.
   */
+extern void tls_update_app_logmask(TLS_APPL_STATE *, int);
 extern void tls_free_app_context(TLS_APPL_STATE *);
 
  /*
index 4dba3bf70be43abb2a00cc52945e64f994071c01..5fbb417cd34fad3625aad4870d5f86cfce3a9d88 100644 (file)
 /*     const char *log_param;
 /*     const char *log_level;
 /*
+/*     void     tls_update_app_logmask(app_ctx, log_mask)
+/*     TLS_APPL_STATE *app_ctx;
+/*     int      log_mask;
+/*
 /*     int     tls_validate_digest(dgst)
 /*     const char *dgst;
 /* DESCRIPTION
 /*     to mask.  The main.cf parameter name is passed along for
 /*     diagnostics.
 /*
+/*     tls_update_app_logmask() changes the log mask of the
+/*     application TLS context to the new setting.
+/*
 /*     tls_validate_digest() returns non-zero if the named digest
 /*     is usable and zero otherwise.
 /* LICENSE
@@ -393,6 +400,13 @@ int     tls_log_mask(const char *log_param, const char *log_level)
     return (mask);
 }
 
+/* tls_update_app_logmask - update log level after init */
+
+void    tls_update_app_logmask(TLS_APPL_STATE *app_ctx, int log_mask)
+{
+    app_ctx->log_mask = log_mask;
+}
+
 /* tls_exclude_missing - Append exclusions for missing ciphers */
 
 static const char *tls_exclude_missing(SSL_CTX *ctx, VSTRING *buf)