Merge branch 'experimental_ocsp'
authorPhil Pennock <pdp@exim.org>
Wed, 16 May 2012 16:35:40 +0000 (12:35 -0400)
committerPhil Pennock <pdp@exim.org>
Wed, 16 May 2012 16:35:40 +0000 (12:35 -0400)
63 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
doc/doc-txt/OptionLists.txt
src/OS/Makefile-SunOS4
src/OS/Makefile-mips
src/OS/os.h-Darwin
src/README.UPDATING
src/exim_monitor/em_globals.c
src/scripts/Configure-Makefile
src/src/EDITME
src/src/buildconfig.c
src/src/config.h.defaults
src/src/daemon.c
src/src/exim.c
src/src/exim.h
src/src/expand.c
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/host.c
src/src/log.c
src/src/malware.c
src/src/readconf.c
src/src/receive.c
src/src/smtp_in.c
src/src/spool_in.c
src/src/spool_out.c
src/src/tls-gnu.c
src/src/tls-openssl.c
src/src/tls.c
src/src/transport.c
src/src/transports/smtp.c
test/README
test/confs/0439
test/confs/0564 [new file with mode: 0644]
test/confs/0565 [new file with mode: 0644]
test/confs/2011 [deleted file]
test/log/0439
test/log/0564 [new file with mode: 0644]
test/log/0565 [new file with mode: 0644]
test/log/2011 [deleted file]
test/msglog/0564.10HmaX-0005vi-00 [new file with mode: 0644]
test/paniclog/0439
test/runtest
test/scripts/0000-Basic/0002
test/scripts/0000-Basic/0564 [new file with mode: 0644]
test/scripts/0000-Basic/0565 [new file with mode: 0644]
test/scripts/2000-GnuTLS/2011 [deleted file]
test/scripts/3450-plaintext-GnuTLS/3450
test/scripts/3450-plaintext-GnuTLS/3453
test/scripts/3450-plaintext-GnuTLS/3454
test/scripts/3650-Dovecot/3650 [deleted file]
test/scripts/3650-Dovecot/REQUIRES [deleted file]
test/scripts/9350-Dovecot/9350
test/stderr/0439
test/stdout/0002
test/stdout/0564 [new file with mode: 0644]
test/stdout/0565 [new file with mode: 0644]
test/stdout/3450
test/stdout/3453
test/stdout/3454
test/stdout/5100

index 2a01a1e5b1bf9574ab60d1819a209f0cf9774cdf..167208ac9482e4d8648cd22dfbaf03d42a4ac36b 100644 (file)
@@ -1647,6 +1647,7 @@ architecture and operating system for itself, but the defaults can be
 overridden if necessary.
 
 
+.new
 .section "PCRE library" "SECTpcre"
 .cindex "PCRE library"
 Exim no longer has an embedded PCRE library as the vast majority of
@@ -1654,10 +1655,14 @@ modern systems include PCRE as a system library, although you may need
 to install the PCRE or PCRE development package for your operating
 system. If your system has a normal PCRE installation the Exim build
 process will need no further configuration. If the library or the
-headers are in an unusual location you will need to set the PCRE_LIBS
-and INCLUDE directives appropriately. If your operating system has no
+headers are in an unusual location you will need to either set the PCRE_LIBS
+and INCLUDE directives appropriately,
+or set PCRE_CONFIG=yes to use the installed &(pcre-config)& command.
+If your operating system has no
 PCRE support then you will need to obtain and build the current PCRE
 from &url(ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/).
+More information on PCRE is available at &url(http://www.pcre.org/).
+.wen
 
 .section "DBM libraries" "SECTdb"
 .cindex "DBM libraries" "discussion of"
@@ -3386,6 +3391,23 @@ This option acts like &%-bv%&, but verifies the address as a sender rather
 than a recipient address. This affects any rewriting and qualification that
 might happen.
 
+.vitem &%-bw%&
+.oindex "&%-bw%&"
+.cindex "daemon"
+.cindex "inetd"
+.cindex "inetd" "wait mode"
+This option runs Exim as a daemon, awaiting incoming SMTP connections,
+similarly to the &%-bd%& option.  All port specifications on the command-line
+and in the configuration file are ignored.  Queue-running may not be specified.
+
+In this mode, Exim expects to be passed a socket as fd 0 (stdin) which is
+listening for connections.  This permits the system to start up and have
+inetd (or equivalent) listen on the SMTP ports, starting an Exim daemon for
+each port only when the first connection is received.
+
+If the option is given as &%-bw%&<&'time'&> then the time is a timeout, after
+which the daemon will exit, which should cause inetd to listen once more.
+
 .vitem &%-C%&&~<&'filelist'&>
 .oindex "&%-C%&"
 .cindex "configuration file" "alternate"
@@ -9522,9 +9544,10 @@ decimal, even if they start with a leading zero; hexadecimal numbers are not
 permitted. This can be useful when processing numbers extracted from dates or
 times, which often do have leading zeros.
 
-A number may be followed by &"K"& or &"M"& to multiply it by 1024 or 1024*1024,
+A number may be followed by &"K"&, &"M"& or &"G"& to multiply it by 1024, 1024*1024
+or 1024*1024*1024,
 respectively. Negative numbers are supported. The result of the computation is
-a decimal representation of the answer (without &"K"& or &"M"&). For example:
+a decimal representation of the answer (without &"K"&, &"M"& or &"G"&). For example:
 
 .display
 &`${eval:1+1}            `&  yields 2
@@ -9743,6 +9766,9 @@ This operator returns a somewhat random number which is less than the
 supplied number and is at least 0.  The quality of this randomness depends
 on how Exim was built; the values are not suitable for keying material.
 If Exim is linked against OpenSSL then RAND_pseudo_bytes() is used.
+.new
+if Exim is linked against GnuTLS then gnutls_rnd(GNUTLS_RND_NONCE) is used.
+.wen
 Otherwise, the implementation may be arc4random(), random() seeded by
 srandomdev() or srandom(), or a custom implementation even weaker than
 random().
@@ -11905,9 +11931,6 @@ used) to the client, based upon the value of the SNI extension.
 The value will be retained for the lifetime of the message.  During outbound
 SMTP deliveries, it reflects the value of the &%tls_sni%& option on
 the transport.
-
-This is currently only available when using OpenSSL, built with support for
-SNI.
 .wen
 
 .vitem &$tod_bsdinbox$&
@@ -12654,9 +12677,6 @@ listed in more than one group.
 
 .section "TLS" "SECID108"
 .table2
-.row &%gnutls_require_kx%&           "control GnuTLS key exchanges"
-.row &%gnutls_require_mac%&          "control GnuTLS MAC algorithms"
-.row &%gnutls_require_protocols%&    "control GnuTLS protocols"
 .row &%gnutls_compat_mode%&          "use GnuTLS compatibility mode"
 .row &%openssl_options%&             "adjust OpenSSL compatibility options"
 .row &%tls_advertise_hosts%&         "advertise TLS to these hosts"
@@ -13669,18 +13689,6 @@ gecos_name = $1
 See &%gecos_name%& above.
 
 
-.option gnutls_require_kx main string unset
-This option controls the key exchange mechanisms when GnuTLS is used in an Exim
-server. For details, see section &<<SECTreqciphgnu>>&.
-
-.option gnutls_require_mac main string unset
-This option controls the MAC algorithms when GnuTLS is used in an Exim
-server. For details, see section &<<SECTreqciphgnu>>&.
-
-.option gnutls_require_protocols main string unset
-This option controls the protocols when GnuTLS is used in an Exim
-server. For details, see section &<<SECTreqciphgnu>>&.
-
 .option gnutls_compat_mode main boolean unset
 This option controls whether GnuTLS is used in compatibility mode in an Exim
 server. This reduces security slightly, but improves interworking with older
@@ -22014,18 +22022,6 @@ being used, names are looked up using &[gethostbyname()]&
 instead of using the DNS. Of course, that function may in fact use the DNS, but
 it may also consult other sources of information such as &_/etc/hosts_&.
 
-.option gnutls_require_kx smtp string unset
-This option controls the key exchange mechanisms when GnuTLS is used in an Exim
-client. For details, see section &<<SECTreqciphgnu>>&.
-
-.option gnutls_require_mac smtp string unset
-This option controls the MAC algorithms when GnuTLS is used in an Exim
-client. For details, see section &<<SECTreqciphgnu>>&.
-
-.option gnutls_require_protocols smtp string unset
-This option controls the protocols when GnuTLS is used in an Exim
-client. For details, see section &<<SECTreqciphgnu>>&.
-
 .option gnutls_compat_mode smtp boolean unset
 This option controls whether GnuTLS is used in compatibility mode in an Exim
 server. This reduces security slightly, but improves interworking with older
@@ -24959,10 +24955,12 @@ implementation, then patches are welcome.
 
 
 .section "GnuTLS parameter computation" "SECID181"
+.new
 GnuTLS uses D-H parameters that may take a substantial amount of time
 to compute. It is unreasonable to re-compute them for every TLS session.
 Therefore, Exim keeps this data in a file in its spool directory, called
-&_gnutls-params_&. The file is owned by the Exim user and is readable only by
+&_gnutls-params-normal_&.
+The file is owned by the Exim user and is readable only by
 its owner. Every Exim process that start up GnuTLS reads the D-H
 parameters from this file. If the file does not exist, the first Exim process
 that needs it computes the data and writes it to a temporary file which is
@@ -24980,8 +24978,8 @@ until enough randomness (entropy) is available. This may cause Exim to hang for
 a substantial amount of time, causing timeouts on incoming connections.
 
 The solution is to generate the parameters externally to Exim. They are stored
-in &_gnutls-params_& in PEM format, which means that they can be generated
-externally using the &(certtool)& command that is part of GnuTLS.
+in &_gnutls-params-normal_& in PEM format, which means that they can be
+generated externally using the &(certtool)& command that is part of GnuTLS.
 
 To replace the parameters with new ones, instead of deleting the file
 and letting Exim re-create it, you can generate new parameters using
@@ -24991,15 +24989,19 @@ renaming. The relevant commands are something like this:
 # rm -f new-params
 # touch new-params
 # chown exim:exim new-params
+# chmod 0600 new-params
+# certtool --generate-dh-params >>new-params
 # chmod 0400 new-params
-# certtool --generate-privkey --bits 512 >new-params
-# echo "" >>new-params
-# certtool --generate-dh-params --bits 1024 >> new-params
-# mv new-params gnutls-params
+# mv new-params gnutls-params-normal
 .endd
 If Exim never has to generate the parameters itself, the possibility of
 stalling is removed.
 
+The filename changed in Exim 4.78, to gain the -normal suffix, corresponding
+to the GnuTLS constant &`GNUTLS_SEC_PARAM_NORMAL`&, defining the number of
+bits to include.  At time of writing, NORMAL corresponds to 2432 bits for D-H.
+.wen
+
 
 .section "Requiring specific ciphers in OpenSSL" "SECTreqciphssl"
 .cindex "TLS" "requiring specific ciphers (OpenSSL)"
@@ -25047,6 +25049,7 @@ not be moved to the end of the list.
 
 
 
+.new
 .section "Requiring specific ciphers or other parameters in GnuTLS" &&&
          "SECTreqciphgnu"
 .cindex "GnuTLS" "specifying parameters for"
@@ -25054,85 +25057,30 @@ not be moved to the end of the list.
 .cindex "TLS" "specifying key exchange methods (GnuTLS)"
 .cindex "TLS" "specifying MAC algorithms (GnuTLS)"
 .cindex "TLS" "specifying protocols (GnuTLS)"
+.cindex "TLS" "specifying priority string (GnuTLS)"
 .oindex "&%tls_require_ciphers%&" "GnuTLS"
-The GnuTLS library allows the caller to specify separate lists of permitted key
-exchange methods, main cipher algorithms, MAC algorithms, and protocols.
-Unfortunately, these lists are numerical, and the library does not have a
-function for turning names into numbers. Consequently, lists of recognized
-names have to be built into the application. The permitted key exchange
-methods, ciphers, and MAC algorithms may be used in any combination to form a
-cipher suite. This is unlike OpenSSL, where complete cipher suite names are
-passed to its control function.
-
-For compatibility with OpenSSL, the &%tls_require_ciphers%& option can be set
-to complete cipher suite names such as RSA_ARCFOUR_SHA, but for GnuTLS this
-option controls only the cipher algorithms. Exim searches each item in the
-list for the name of an available algorithm. For example, if the list
-contains RSA_AES_SHA, then AES is recognized, and the behaviour is exactly
-the same as if just AES were given.
-
-.oindex "&%gnutls_require_kx%&"
-.oindex "&%gnutls_require_mac%&"
-.oindex "&%gnutls_require_protocols%&"
-There are additional options called &%gnutls_require_kx%&,
-&%gnutls_require_mac%&, and &%gnutls_require_protocols%& that can be used to
-restrict the key exchange methods, MAC algorithms, and protocols, respectively.
-These options are ignored if OpenSSL is in use.
-
-All four options are available as global options, controlling how Exim
-behaves as a server, and also as options of the &(smtp)& transport, controlling
-how Exim behaves as a client. All the values are string expanded. After
-expansion, the values must be colon-separated lists, though the separator
-can be changed in the usual way.
-
-Each of the four lists starts out with a default set of algorithms. If the
-first item in a list does &'not'& start with an exclamation mark, all the
-default items are deleted. In this case, only those that are explicitly
-specified can be used. If the first item in a list &'does'& start with an
-exclamation mark, the defaults are left on the list.
-
-Then, any item that starts with an exclamation mark causes the relevant
-entry to be removed from the list, and any item that does not start with an
-exclamation mark causes a new entry to be added to the list. Unrecognized
-items in the list are ignored. Thus:
-.code
-tls_require_ciphers = !ARCFOUR
-.endd
-allows all the defaults except ARCFOUR, whereas
-.code
-tls_require_ciphers = AES : 3DES
-.endd
-allows only cipher suites that use AES or 3DES.
-
-For &%tls_require_ciphers%& the recognized names are AES_256, AES_128, AES
-(both of the preceding), 3DES, ARCFOUR_128, ARCFOUR_40, and ARCFOUR (both of
-the preceding). The default list does not contain all of these; it just has
-AES_256, AES_128, 3DES, and ARCFOUR_128.
-
-For &%gnutls_require_kx%&, the recognized names are DHE_RSA, RSA (which
-includes DHE_RSA), DHE_DSS, and DHE (which includes both DHE_RSA and
-DHE_DSS). The default list contains RSA, DHE_DSS, DHE_RSA.
-
-For &%gnutls_require_mac%&, the recognized names are SHA (synonym SHA1), and
-MD5. The default list contains SHA, MD5.
-
-.new
-For &%gnutls_require_protocols%&, the recognized names are TLS1.2, TLS1.1,
-TLS1.0, (TLS1) and SSL3.
-The default list contains TLS1.2, TLS1.1, TLS1.0, SSL3.
-TLS1 is an alias for TLS1.0, for backwards compatibility.
-For sufficiently old versions of the GnuTLS library, TLS1.2 or TLS1.1 might
-not be supported and will not be recognised by Exim.
+The GnuTLS library allows the caller to provide a "priority string", documented
+as part of the &[gnutls_priority_init]& function.  This is very similar to the
+ciphersuite specification in OpenSSL.
+
+The &%tls_require_ciphers%& option is treated as the GnuTLS priority string.
+
+The &%tls_require_ciphers%& option is available both as an global option,
+controlling how Exim behaves as a server, and also as an option of the
+&(smtp)& transport, controlling how Exim behaves as a client.  In both cases
+the value is string expanded.  The resulting string is not an Exim list and
+the string is given to the GnuTLS library, so that Exim does not need to be
+aware of future feature enhancements of GnuTLS.
+
+Documentation of the strings accepted may be found in the GnuTLS manual, under
+"Priority strings".  This is online as
+&url(http://www.gnu.org/software/gnutls/manual/html_node/Priority-Strings.html).
+
+Prior to Exim 4.78, an older API of GnuTLS was used, and Exim supported three
+additional options, "&%gnutls_require_kx%&", "&%gnutls_require_mac%&" and
+"&%gnutls_require_protocols%&".  &%tls_require_ciphers%& was an Exim list.
 .wen
 
-In a server, the order of items in these lists is unimportant. The server
-advertises the availability of all the relevant cipher suites. However, in a
-client, the order in the &%tls_require_ciphers%& list specifies a preference
-order for the cipher algorithms. The first one in the client's list that is
-also advertised by the server is tried first. The default order is as listed
-above.
-
-
 
 .section "Configuring an Exim server to use TLS" "SECID182"
 .cindex "TLS" "configuring an Exim server"
@@ -25428,8 +25376,14 @@ arbitrary unverified data provided prior to authentication.
 The Exim developers are proceeding cautiously and so far no other TLS options
 are re-expanded.
 
-Currently SNI support is only available if using OpenSSL, with TLS Extensions
-support enabled therein.
+When Exim is built againt OpenSSL, OpenSSL must have been built with support
+for TLS Extensions.  This holds true for OpenSSL 1.0.0+ and 0.9.8+ with
+enable-tlsext in EXTRACONFIGURE.  If you invoke &(openssl s_client -h)& and
+see &`-servername`& in the output, then OpenSSL has support.
+
+When Exim is built against GnuTLS, SNI support is available as of GnuTLS
+0.5.10.  (Its presence predates the current API which Exim uses, so if Exim
+built, then you have SNI support).
 .wen
 
 
index 7c6ce246f2a62b31fcc57f289aa0d7b451d57f2c..991f59f081c58b47ca91eb2d07b97dc1b297dfed 100644 (file)
@@ -91,7 +91,23 @@ PP/20 Revert part of NM/04, it broke log_path containing %D expansions.
 
 PP/21 Defaulting "accept_8bitmime" to true, not false.
 
-PP/22 Added EXPERIMENTAL_OCSP for OpenSSL.
+PP/22 Added -bw for inetd wait mode support.
+
+PP/23 Added PCRE_CONFIG=yes support to Makefile for using pcre-config to
+      locate the relevant includes and libraries.  Made this the default.
+
+PP/24 Fixed headers_only on smtp transports (was not sending trailing dot).
+      Bugzilla 1246, report and most of solution from Tomasz Kusy.
+
+JH/02 ${eval } now uses 64-bit and supports a "g" suffix (like to "k" and "m").
+      This may cause build issues on older platforms.
+
+PP/25 Revamped GnuTLS support, passing tls_require_ciphers to
+      gnutls_priority_init, ignoring Exim options gnutls_require_kx,
+      gnutls_require_mac & gnutls_require_protocols (no longer supported).
+      Added SNI support via GnuTLS too.
+
+PP/26 Added EXPERIMENTAL_OCSP for OpenSSL.
 
 
 Exim version 4.77
index 96839cde6fdf0c48fcd20753d7a35371aa6b70dc..d41d79c837fb523b7a4d22231597fc030965e094 100644 (file)
@@ -26,6 +26,9 @@ Version 4.78
     "LOOKUP_LIBS" directly.  Similarly for handling the TLS library support
     without adjusting "TLS_INCLUDE" and "TLS_LIBS".
 
+    In addition, setting PCRE_CONFIG=yes will query the pcre-config tool to
+    find the headers and libraries for PCRE.
+
  4. New expansion variable $tls_bits.
 
  5. New lookup type, "dbmjz".  Key is an Exim list, the elements of which will
@@ -54,15 +57,30 @@ Version 4.78
     A new log_selector, +tls_sni, has been added, to log received SNI values
     for Exim as a server.
 
-    Currently OpenSSL only.
-
  8. The existing "accept_8bitmime" option now defaults to true.  This means
     that Exim is deliberately not strictly RFC compliant.  We're following
     Dan Bernstein's advice in http://cr.yp.to/smtp/8bitmime.html by default.
     Those who disagree, or know that they are talking to mail servers that,
     even today, are not 8-bit clean, need to turn off this option.
 
- 9. With OpenSSL, if built with EXPERIMENTAL_OCSP, a new option tls_ocsp_file
+ 9. Exim can now be started with -bw (with an optional timeout, given as
+    -bw<timespec>).  With this, stdin at startup is a socket that is
+    already listening for connections.  This has a more modern name of
+    "socket activation", but forcing the activated socket to fd 0.  We're
+    interested in adding more support for modern variants.
+
+10. ${eval } now uses 64-bit values on supporting platforms.  A new "G" suffux
+    for numbers indicates multiplication by 1024^3.
+
+11. The GnuTLS support has been revamped; the three options gnutls_require_kx,
+    gnutls_require_mac & gnutls_require_protocols are no longer supported.
+    tls_require_ciphers is now parsed by gnutls_priority_init(3) as a priority
+    string, documentation for which is at:
+    http://www.gnu.org/software/gnutls/manual/html_node/Priority-Strings.html
+
+    SNI support has been added to Exim's GnuTLS integration too.
+
+12. With OpenSSL, if built with EXPERIMENTAL_OCSP, a new option tls_ocsp_file
     is now available.  If the contents of the file are valid, then Exim will
     send that back in response to a TLS status request; this is OCSP Stapling.
     Exim will not maintain the contents of the file in any way: administrators
index 20aeff965819a3a1d85ebdd023265f38c2ba3c92..576eaf3d24476204fa5e9607a7001337493a4728 100644 (file)
@@ -241,12 +241,12 @@ gecos_name                           string*         unset         main
 gecos_pattern                        string          unset         main
 gethostbyname                        boolean         false         smtp
 gnutls_compat_mode                   boolean         unset         main              4.70
-gnutls_require_kx                    string*         unset         main              4.67
-                                     string*         unset         smtp              4.67
-gnutls_require_mac                   string*         unset         main              4.67
-                                     string*         unset         smtp              4.67
-gnutls_require_protocols             string*         unset         main              4.67
-                                     string*         unset         smtp              4.67
+gnutls_require_kx                    string*         unset         main              4.67 deprecated, warns
+                                     string*         unset         smtp              4.67 deprecated, warns
+gnutls_require_mac                   string*         unset         main              4.67 deprecated, warns
+                                     string*         unset         smtp              4.67 deprecated, warns
+gnutls_require_protocols             string*         unset         main              4.67 deprecated, warns
+                                     string*         unset         smtp              4.67 deprecated, warns
 group                                string          +             routers           4.00
                                                      unset         transports        4.00 replaces local option in some transports
 header_line_maxsize                  integer         0 (unset)     main              4.14
@@ -641,6 +641,7 @@ provide compatibility with Sendmail.
 -bV              Verify version number
 -bv              Test recipient address verification
 -bvs             Test sender address verification
+-bw            + Inetd wait mode
 -C             + Use alternate configuration file
 -D             + Define macro for configuration file
 -d             + Turn on debugging output
@@ -909,7 +910,8 @@ MAX_INTERFACES               system       maximum network interfaces
 MSGLOG_DIRECTORY_MODE        optional*    mode for message log directory
 MV_COMMAND                   system       path to mv command
 NO_SYMLINK                   optional     install doesn't make 'exim" symlink
-PCRE_CFLAGS                  system       compile flags for PCRE library
+PCRE_CONFIG                  system*      use pcre-config for PCRE support
+PCRE_LIBS                    system*      library for using PCRE
 PERL_CC                      system*      compiler for Perl interface code
 PERL_CCOPTS                  system*      flags for same
 PERL_COMMAND                 system       path to Perl
index 63db9c5dfd21a498c09f27e494e5a07f80d6902e..c876998e5eb168b742e91aa661086cf5f6bd1584 100644 (file)
@@ -2,11 +2,6 @@
 
 CFLAGS=-O
 
-# Don't need -DSTRERROR_FROM_ERRLIST in PCRE_CFLAGS, because it is in os.h
-# for SunOS4, which gets included for pcre.
-
-PCRE_CFLAGS=-DUSE_BCOPY
-
 CHOWN_COMMAND=/usr/etc/chown
 HOSTNAME_COMMAND=/usr/bin/hostname
 EXIT_FAILURE=1
index dbc101cd6459be193fff0c46c58e7880ab806556..ff331393777470bd8def443afea6785b31dc2151 100644 (file)
@@ -8,7 +8,6 @@ LIBS=-liberty -lm
 XINCLUDE=-I/usr/X11R6/include
 
 CFLAGS=-O
-PCRE_CFLAGS=-DUSE_BCOPY -DSTRERROR_FROM_ERRLIST
 
 EXIWHAT_PS_ARG=-ax
 EXIWHAT_EGREP_ARG='/exim( |$$)'
index 888b9c759c1805ec892513ec386db9e1c4a1ca98..af06bdcbcd6a0bbb7e07ad1c2ab3024827b4f86f 100644 (file)
@@ -35,4 +35,8 @@ updating Exim to use the newer interface. */
 /* It's not .so for dynamic libraries on Darwin. */
 #define DYNLIB_FN_EXT "dylib"
 
+/* We currently need some assistance getting OFF_T_FMT correct on MacOS */
+#define OFF_T_FMT "%llu"
+#define LONGLONG_T long int
+
 /* End */
index 0d729a38482f2ab0af4ce04d32e63f187d658b30..81e767efea826ca550b1de7743b1cdc99eb5cfb7 100644 (file)
@@ -77,6 +77,30 @@ Exim version 4.78
    new option, you can safely force it off before upgrading, to decouple
    configuration changes from the binary upgrade while remaining RFC compliant.
 
+ * The GnuTLS support has been mostly rewritten, to use 2.12.x APIs.  As part
+   of this, these three options are no longer supported:
+
+     gnutls_require_kx
+     gnutls_require_mac
+     gnutls_require_protocols
+
+   Their functionality is entirely subsumed into tls_require_ciphers, which is
+   no longer parsed apart by Exim but is instead given to
+   gnutls_priority_init(3), which is no longer an Exim list.  See:
+
+     http://www.gnu.org/software/gnutls/manual/html_node/Priority-Strings.html
+
+   for fuller documentation of the strings parsed.  The three gnutls_require_*
+   options are still parsed by Exim and, for this release, silently ignored.
+   A future release will add warnings, before a later still release removes
+   parsing entirely and the presence of the options will be a configuration
+   error.
+
+   This rewrite means that Exim will continue to build against GnuTLS in the
+   future, brings Exim closer to other GnuTLS applications and lets us add
+   support for SNI and other features more readily.  We regret that it wasn't
+   feasible to retain the three dropped options.
+
 
 Exim version 4.77
 -----------------
index 7205493826022b4fd66746d45ce4b81ae232790a..e74118c452bc25ab732a7d1c474cbacacebf4dfb 100644 (file)
@@ -214,7 +214,7 @@ BOOL    timestamps_utc         = FALSE;
 BOOL    tls_certificate_verified = FALSE;
 uschar *tls_cipher             = NULL;
 uschar *tls_peerdn             = NULL;
-#ifndef USE_GNUTLS
+#ifdef SUPPORT_TLS
 uschar *tls_sni                = NULL;
 #endif
 
index 5ef0ff7f08e1e0c8e911b8e3d294828230a0e7f2..3e901e6a6a8bb6f0b9b20e904dadc39f163c2f47 100755 (executable)
@@ -117,7 +117,7 @@ done >> $mft || exit 1
 egrep "^[$st]*(AUTH|LOOKUP)_[A-Z0-9_]*[$st]*=[$st]*" $mft | \
   sed "s/[$st]*=/='/" | \
   sed "s/\$/'/" > $mftt
-egrep "^[$st]*((USE_(OPENSSL|GNUTLS)_PC)|SUPPORT_TLS|USE_GNUTLS)[$st]*=[$st]*" $mft | \
+egrep "^[$st]*((USE_(OPENSSL|GNUTLS)_PC)|SUPPORT_TLS|USE_GNUTLS|PCRE_CONFIG)[$st]*=[$st]*" $mft | \
   sed "s/[$st]*=/='/" | \
   sed "s/\$/'/" >> $mftt
 if test -s $mftt
@@ -173,6 +173,19 @@ then
         fi
         ;;
 
+      PCRE_CONFIG)
+        case $PCRE_CONFIG in
+          yes|YES|y|Y)
+            cflags=`pcre-config --cflags`
+            libs=`pcre-config --libs`
+            if [ ".$cflags" != "." ]; then
+              echo "INCLUDE += $cflags"
+            fi
+            echo "PCRE_LIBS=$libs"
+            ;;
+        esac
+        ;;
+
     esac
   done
   echo "# End of pkg-config fixups"
index f4e788ae54e89342e13cd40810f89a1696c0cd87..1f717a988235cc65619afe69ff9a1010ca8202d0 100644 (file)
@@ -342,10 +342,13 @@ LOOKUP_DNSDB=yes
 # In either case you must specify the library link info here.  If the
 # PCRE header files are not in the standard search path you must also
 # modify the INCLUDE path (above)
-# The default setting of PCRE_LIBS should work on the vast majority of
-# systems
+#
+# Use PCRE_CONFIG to query the pcre-config command (first found in $PATH)
+# to find the include files and libraries, else use PCRE_LIBS and set INCLUDE
+# too if needed.
 
-PCRE_LIBS=-lpcre
+# PCRE_CONFIG=yes
+# PCRE_LIBS=-lpcre
 
 
 #------------------------------------------------------------------------------
index dfb4497622f1205030f80498b23f0cc56de7acae..1eaf2f505dfcd4ae3f561c9d8c7c03a85ade5af4 100644 (file)
@@ -103,6 +103,7 @@ off_t test_off_t = 0;
 time_t test_time_t = 0;
 #if ! (__STDC_VERSION__ >= 199901L)
 size_t test_size_t = 0;
+ssize_t test_ssize_t = 0;
 unsigned long test_ulong_t = 0L;
 #endif
 long test_long_t = 0;
@@ -179,15 +180,22 @@ else
 fprintf(new, "#endif\n\n");
 
 /* And for sizeof() results, size_t, which should with C99 be just %zu, deal
-with C99 not being ubiquitous yet.  Unfortunately. */
+with C99 not being ubiquitous yet.  Unfortunately.  Assume ssize_t is same
+size as size_t on C99; if someone comes up with a version where it's not, fix
+it then. */
 
 #if __STDC_VERSION__ >= 199901L
 fprintf(new, "#define SIZE_T_FMT  \"%%zu\"\n");
+fprintf(new, "#define SSIZE_T_FMT  \"%%zd\"\n");
 #else
 if (sizeof(test_size_t) > sizeof (test_ulong_t))
   fprintf(new, "#define SIZE_T_FMT  \"%%llu\"\n");
 else
   fprintf(new, "#define SIZE_T_FMT  \"%%lu\"\n");
+if (sizeof(test_ssize_t) > sizeof(test_long_t))
+  fprintf(new, "#define SSIZE_T_FMT  \"%%lld\"\n");
+else
+  fprintf(new, "#define SSIZE_T_FMT  \"%%ld\"\n");
 #endif
 
 /* Now search the makefile for certain settings */
@@ -332,6 +340,16 @@ while (fgets(buffer, sizeof(buffer), base) != NULL)
 
   while (*p == ' ' || *p == '\t') p++;
 
+  if (strncmp(p, "#ifdef ", 7) == 0
+   || strncmp(p, "#ifndef ", 8) == 0
+   || strncmp(p, "#if ", 4) == 0
+   || strncmp(p, "#endif", 6) == 0
+     )
+    {
+    fputs(buffer, new);
+    continue;
+    }
+
   if (strncmp(p, "#define ", 8) != 0) continue;
 
   p += 8;
index a5e12d2abc834b3725488f2c3ef48958537af667..14dbd0a00c2ee39d58a60cdbf018b94d7093ac1d 100644 (file)
@@ -172,4 +172,18 @@ just in case. */
 #define ROOT_UID                      0
 #define ROOT_GID                      0
 
+/* Sizes for integer arithmetic.  Go for 64bit; can be overridden in OS/os.h-FOO */
+#ifndef int_eximarith_t
+ #define int_eximarith_t int64_t
+#endif
+#ifndef PR_EXIM_ARITH
+ #define PR_EXIM_ARITH "%" PRId64              /* C99 standard, printf %lld */
+#endif
+#ifndef SC_EXIM_ARITH
+ #define SC_EXIM_ARITH "%" SCNi64              /* scanf incl. 0x prefix */
+#endif
+#ifndef SC_EXIM_DEC
+ #define SC_EXIM_DEC   "%" SCNd64              /* scanf decimal */
+#endif
+
 /* End of config.h.defaults */
index 27b4cb26567045add35870477d9f3f30e21a3714..9b19c6249fad9655e29816a9095c55a8e80e4c55 100644 (file)
@@ -913,12 +913,67 @@ struct passwd *pw;
 int *listen_sockets = NULL;
 int listen_socket_count = 0;
 ip_address_item *addresses = NULL;
+time_t last_connection_time = (time_t)0;
 
 /* If any debugging options are set, turn on the D_pid bit so that all
 debugging lines get the pid added. */
 
 DEBUG(D_any|D_v) debug_selector |= D_pid;
 
+if (inetd_wait_mode)
+  {
+  int on = 1;
+
+  listen_socket_count = 1;
+  listen_sockets = store_get(sizeof(int *));
+  (void) close(3);
+  if (dup2(0, 3) == -1)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+        "failed to dup inetd socket safely away: %s", strerror(errno));
+    }
+  listen_sockets[0] = 3;
+  (void) close(0);
+  (void) close(1);
+  (void) close(2);
+  exim_nullstd();
+
+  if (debug_file == stderr)
+    {
+    /* need a call to log_write before call to open debug_file, so that
+    log.c:file_path has been initialised.  This is unfortunate. */
+    log_write(0, LOG_MAIN, "debugging Exim in inetd wait mode starting");
+
+    fclose(debug_file);
+    debug_file = NULL;
+    exim_nullstd(); /* re-open fd2 after we just closed it again */
+    debug_logging_activate(US"-wait", NULL);
+    }
+
+  DEBUG(D_any) debug_printf("running in inetd wait mode\n");
+
+  /* As per below, when creating sockets ourselves, we handle tcp_nodelay for
+  our own buffering; we assume though that inetd set the socket REUSEADDR. */
+
+  if (tcp_nodelay) setsockopt(3, IPPROTO_TCP, TCP_NODELAY,
+    (uschar *)(&on), sizeof(on));
+  }
+
+
+if (inetd_wait_mode || daemon_listen)
+  {
+  /* If any option requiring a load average to be available during the
+  reception of a message is set, call os_getloadavg() while we are root
+  for those OS for which this is necessary the first time it is called (in
+  order to perform an "open" on the kernel memory file). */
+
+  #ifdef LOAD_AVG_NEEDS_ROOT
+  if (queue_only_load >= 0 || smtp_load_reserve >= 0 ||
+       (deliver_queue_load_max >= 0 && deliver_drop_privilege))
+    (void)os_getloadavg();
+  #endif
+  }
+
 
 /* Do the preparation for setting up a listener on one or more interfaces, and
 possible on various ports. This is controlled by the combination of
@@ -987,7 +1042,7 @@ The preparation code decodes options and sets up the relevant data. We do this
 first, so that we can return non-zero if there are any syntax errors, and also
 write to stderr. */
 
-if (daemon_listen)
+if (daemon_listen && !inetd_wait_mode)
   {
   int *default_smtp_port;
   int sep;
@@ -998,17 +1053,6 @@ if (daemon_listen)
   ip_address_item *ipa;
   ip_address_item **pipa;
 
-  /* If any option requiring a load average to be available during the
-  reception of a message is set, call os_getloadavg() while we are root
-  for those OS for which this is necessary the first time it is called (in
-  order to perform an "open" on the kernel memory file). */
-
-  #ifdef LOAD_AVG_NEEDS_ROOT
-  if (queue_only_load >= 0 || smtp_load_reserve >= 0 ||
-       (deliver_queue_load_max >= 0 && deliver_drop_privilege))
-    (void)os_getloadavg();
-  #endif
-
   /* If -oX was used, disable the writing of a pid file unless -oP was
   explicitly used to force it. Then scan the string given to -oX. Any items
   that contain neither a dot nor a colon are used to override daemon_smtp_port.
@@ -1208,6 +1252,11 @@ if (daemon_listen)
     listen_socket_count++;
   listen_sockets = store_get(sizeof(int *) * listen_socket_count);
 
+  } /* daemon_listen but not inetd_wait_mode */
+
+if (daemon_listen)
+  {
+
   /* Do a sanity check on the max connects value just to save us from getting
   a huge amount of store. */
 
@@ -1233,7 +1282,8 @@ if (daemon_listen)
 /* The variable background_daemon is always false when debugging, but
 can also be forced false in order to keep a non-debugging daemon in the
 foreground. If background_daemon is true, close all open file descriptors that
-we know about, but then re-open stdin, stdout, and stderr to /dev/null.
+we know about, but then re-open stdin, stdout, and stderr to /dev/null.  Also
+do this for inetd_wait mode.
 
 This is protection against any called functions (in libraries, or in
 Perl, or whatever) that think they can write to stderr (or stdout). Before this
@@ -1244,7 +1294,7 @@ Then disconnect from the controlling terminal, Most modern Unixes seem to have
 setsid() for getting rid of the controlling terminal. For any OS that doesn't,
 setsid() can be #defined as a no-op, or as something else. */
 
-if (background_daemon)
+if (background_daemon || inetd_wait_mode)
   {
   log_close_all();    /* Just in case anything was logged earlier */
   search_tidyup();    /* Just in case any were used in reading the config. */
@@ -1253,7 +1303,10 @@ if (background_daemon)
   (void)close(2);
   exim_nullstd();     /* Connect stdin/stdout/stderr to /dev/null */
   log_stderr = NULL;  /* So no attempt to copy paniclog output */
+  }
 
+if (background_daemon)
+  {
   /* If the parent process of this one has pid == 1, we are re-initializing the
   daemon as the result of a SIGHUP. In this case, there is no need to do
   anything, because the controlling terminal has long gone. Otherwise, fork, in
@@ -1273,7 +1326,7 @@ if (background_daemon)
 /* We are now in the disconnected, daemon process (unless debugging). Set up
 the listening sockets if required. */
 
-if (daemon_listen)
+if (daemon_listen && !inetd_wait_mode)
   {
   int sk;
   int on = 1;
@@ -1513,7 +1566,25 @@ sigalrm_seen = (queue_interval > 0);
 /* Log the start up of a daemon - at least one of listening or queue running
 must be set up. */
 
-if (daemon_listen)
+if (inetd_wait_mode)
+  {
+  uschar *p = big_buffer;
+
+  if (inetd_wait_timeout >= 0)
+    sprintf(CS p, "terminating after %d seconds", inetd_wait_timeout);
+  else
+    sprintf(CS p, "with no wait timeout");
+
+  log_write(0, LOG_MAIN,
+    "exim %s daemon started: pid=%d, launched with listening socket, %s",
+    version_string, getpid(), big_buffer);
+  set_process_info("daemon: pre-listening socket");
+
+  /* set up the timeout logic */
+  sigalrm_seen = 1;
+  }
+
+else if (daemon_listen)
   {
   int i, j;
   int smtp_ports = 0;
@@ -1631,122 +1702,166 @@ for (;;)
   pid_t pid;
 
   /* This code is placed first in the loop, so that it gets obeyed at the
-  start, before the first wait. This causes the first queue-runner to be
-  started immediately. */
+  start, before the first wait, for the queue-runner case, so that the first
+  one can be started immediately.
+
+  The other option is that we have an inetd wait timeout specified to -bw. */
 
   if (sigalrm_seen)
     {
-    DEBUG(D_any) debug_printf("SIGALRM received\n");
+    if (inetd_wait_timeout > 0)
+      {
+      time_t resignal_interval = inetd_wait_timeout;
+
+      if (last_connection_time == (time_t)0)
+        {
+        DEBUG(D_any)
+          debug_printf("inetd wait timeout expired, but still not seen first message, ignoring\n");
+        }
+      else
+        {
+        time_t now = time(NULL);
+        if (now == (time_t)-1)
+          {
+          DEBUG(D_any) debug_printf("failed to get time: %s\n", strerror(errno));
+          }
+        else
+          {
+          if ((now - last_connection_time) >= inetd_wait_timeout)
+            {
+            DEBUG(D_any)
+              debug_printf("inetd wait timeout %d expired, ending daemon\n",
+                  inetd_wait_timeout);
+            log_write(0, LOG_MAIN, "exim %s daemon terminating, inetd wait timeout reached.\n",
+                version_string);
+            exit(EXIT_SUCCESS);
+            }
+          else
+            {
+            resignal_interval -= (now - last_connection_time);
+            }
+          }
+        }
 
-    /* Do a full queue run in a child process, if required, unless we already
-    have enough queue runners on the go. If we are not running as root, a
-    re-exec is required. */
+      sigalrm_seen = FALSE;
+      alarm(resignal_interval);
+      }
 
-    if (queue_interval > 0 &&
-       (queue_run_max <= 0 || queue_run_count < queue_run_max))
+    else
       {
-      if ((pid = fork()) == 0)
-        {
-        int sk;
+      DEBUG(D_any) debug_printf("SIGALRM received\n");
 
-        DEBUG(D_any) debug_printf("Starting queue-runner: pid %d\n",
-          (int)getpid());
+      /* Do a full queue run in a child process, if required, unless we already
+      have enough queue runners on the go. If we are not running as root, a
+      re-exec is required. */
 
-        /* Disable debugging if it's required only for the daemon process. We
-        leave the above message, because it ties up with the "child ended"
-        debugging messages. */
+      if (queue_interval > 0 &&
+         (queue_run_max <= 0 || queue_run_count < queue_run_max))
+        {
+        if ((pid = fork()) == 0)
+          {
+          int sk;
 
-        if (debug_daemon) debug_selector = 0;
+          DEBUG(D_any) debug_printf("Starting queue-runner: pid %d\n",
+            (int)getpid());
 
-        /* Close any open listening sockets in the child */
+          /* Disable debugging if it's required only for the daemon process. We
+          leave the above message, because it ties up with the "child ended"
+          debugging messages. */
 
-        for (sk = 0; sk < listen_socket_count; sk++)
-          (void)close(listen_sockets[sk]);
+          if (debug_daemon) debug_selector = 0;
 
-        /* Reset SIGHUP and SIGCHLD in the child in both cases. */
+          /* Close any open listening sockets in the child */
 
-        signal(SIGHUP,  SIG_DFL);
-        signal(SIGCHLD, SIG_DFL);
+          for (sk = 0; sk < listen_socket_count; sk++)
+            (void)close(listen_sockets[sk]);
 
-        /* Re-exec if privilege has been given up, unless deliver_drop_
-        privilege is set. Reset SIGALRM before exec(). */
+          /* Reset SIGHUP and SIGCHLD in the child in both cases. */
 
-        if (geteuid() != root_uid && !deliver_drop_privilege)
-          {
-          uschar opt[8];
-          uschar *p = opt;
-          uschar *extra[5];
-          int extracount = 1;
+          signal(SIGHUP,  SIG_DFL);
+          signal(SIGCHLD, SIG_DFL);
 
-          signal(SIGALRM, SIG_DFL);
-          *p++ = '-';
-          *p++ = 'q';
-          if (queue_2stage) *p++ = 'q';
-          if (queue_run_first_delivery) *p++ = 'i';
-          if (queue_run_force) *p++ = 'f';
-          if (deliver_force_thaw) *p++ = 'f';
-          if (queue_run_local) *p++ = 'l';
-          *p = 0;
-          extra[0] = opt;
-
-          /* If -R or -S were on the original command line, ensure they get
-          passed on. */
-
-          if (deliver_selectstring != NULL)
-            {
-            extra[extracount++] = deliver_selectstring_regex? US"-Rr" : US"-R";
-            extra[extracount++] = deliver_selectstring;
-            }
+          /* Re-exec if privilege has been given up, unless deliver_drop_
+          privilege is set. Reset SIGALRM before exec(). */
 
-          if (deliver_selectstring_sender != NULL)
+          if (geteuid() != root_uid && !deliver_drop_privilege)
             {
-            extra[extracount++] = deliver_selectstring_sender_regex?
-              US"-Sr" : US"-S";
-            extra[extracount++] = deliver_selectstring_sender;
+            uschar opt[8];
+            uschar *p = opt;
+            uschar *extra[5];
+            int extracount = 1;
+
+            signal(SIGALRM, SIG_DFL);
+            *p++ = '-';
+            *p++ = 'q';
+            if (queue_2stage) *p++ = 'q';
+            if (queue_run_first_delivery) *p++ = 'i';
+            if (queue_run_force) *p++ = 'f';
+            if (deliver_force_thaw) *p++ = 'f';
+            if (queue_run_local) *p++ = 'l';
+            *p = 0;
+            extra[0] = opt;
+
+            /* If -R or -S were on the original command line, ensure they get
+            passed on. */
+
+            if (deliver_selectstring != NULL)
+              {
+              extra[extracount++] = deliver_selectstring_regex? US"-Rr" : US"-R";
+              extra[extracount++] = deliver_selectstring;
+              }
+
+            if (deliver_selectstring_sender != NULL)
+              {
+              extra[extracount++] = deliver_selectstring_sender_regex?
+                US"-Sr" : US"-S";
+              extra[extracount++] = deliver_selectstring_sender;
+              }
+
+            /* Overlay this process with a new execution. */
+
+            (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, TRUE, extracount,
+              extra[0], extra[1], extra[2], extra[3], extra[4]);
+
+            /* Control never returns here. */
             }
 
-          /* Overlay this process with a new execution. */
-
-          (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, TRUE, extracount,
-            extra[0], extra[1], extra[2], extra[3], extra[4]);
+          /* No need to re-exec; SIGALRM remains set to the default handler */
 
-          /* Control never returns here. */
+          queue_run(NULL, NULL, FALSE);
+          _exit(EXIT_SUCCESS);
           }
 
-        /* No need to re-exec; SIGALRM remains set to the default handler */
-
-        queue_run(NULL, NULL, FALSE);
-        _exit(EXIT_SUCCESS);
-        }
-
-      if (pid < 0)
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fork of queue-runner "
-          "process failed: %s", strerror(errno));
-        log_close_all();
-        }
-      else
-        {
-        int i;
-        for (i = 0; i < queue_run_max; ++i)
+        if (pid < 0)
           {
-          if (queue_pid_slots[i] <= 0)
+          log_write(0, LOG_MAIN|LOG_PANIC, "daemon: fork of queue-runner "
+            "process failed: %s", strerror(errno));
+          log_close_all();
+          }
+        else
+          {
+          int i;
+          for (i = 0; i < queue_run_max; ++i)
             {
-            queue_pid_slots[i] = pid;
-            queue_run_count++;
-            break;
+            if (queue_pid_slots[i] <= 0)
+              {
+              queue_pid_slots[i] = pid;
+              queue_run_count++;
+              break;
+              }
             }
+          DEBUG(D_any) debug_printf("%d queue-runner process%s running\n",
+            queue_run_count, (queue_run_count == 1)? "" : "es");
           }
-        DEBUG(D_any) debug_printf("%d queue-runner process%s running\n",
-          queue_run_count, (queue_run_count == 1)? "" : "es");
         }
-      }
 
-    /* Reset the alarm clock */
+      /* Reset the alarm clock */
 
-    sigalrm_seen = FALSE;
-    alarm(queue_interval);
-    }
+      sigalrm_seen = FALSE;
+      alarm(queue_interval);
+      }
+
+    } /* sigalrm_seen */
 
 
   /* Sleep till a connection happens if listening, and handle the connection if
@@ -1886,8 +2001,12 @@ for (;;)
       /* If select/accept succeeded, deal with the connection. */
 
       if (accept_socket >= 0)
+        {
+        if (inetd_wait_timeout)
+          last_connection_time = time(NULL);
         handle_smtp_call(listen_sockets, listen_socket_count, accept_socket,
           (struct sockaddr *)&accepted);
+        }
       }
     }
 
index 90ecd0629631b9dcfdb7186117a17d935b45ff48..b53d6ca9b98afb2c61f3639fd8ed9417f41ea361 100644 (file)
@@ -2060,6 +2060,24 @@ for (i = 1; i < argc; i++)
       show_whats_supported(stdout);
       }
 
+    /* -bw: inetd wait mode, accept a listening socket as stdin */
+
+    else if (*argrest == 'w')
+      {
+      inetd_wait_mode = TRUE;
+      background_daemon = FALSE;
+      daemon_listen = TRUE;
+      if (*(++argrest) != '\0')
+        {
+        inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE);
+        if (inetd_wait_timeout <= 0)
+          {
+          fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
+          exit(EXIT_FAILURE);
+          }
+        }
+      }
+
     else badarg = TRUE;
     break;
 
@@ -3222,6 +3240,9 @@ if ((
     daemon_listen && queue_interval == 0
     ) ||
     (
+    inetd_wait_mode && queue_interval >= 0
+    ) ||
+    (
     list_options &&
     (checking || smtp_input || extract_recipients ||
       filter_test != FTEST_NONE || bi_option)
@@ -4406,7 +4427,7 @@ returns. We leave this till here so that the originator_ fields are available
 for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
 mode. */
 
-if (daemon_listen || queue_interval > 0)
+if (daemon_listen || inetd_wait_mode || queue_interval > 0)
   {
   if (mua_wrapper)
     {
index e6e72facc9574a3e0b40c981ad072d72e55e154d..49f52474a782efa8b996c6852a1418beb1e620bd 100644 (file)
@@ -77,6 +77,10 @@ making unique names. */
 #include <limits.h>
 #endif
 
+/* C99 integer types, figure out how to undo this if needed for older systems */
+
+#include <inttypes.h>
+
 /* Just in case some aged system doesn't define them... */
 
 #ifndef INT_MAX
index 22f7d9a66ff0c366d7cafc085caf85c3906913df..2fc953aeccf99da3b043d6ec97c8aff0fa7ad1d6 100644 (file)
@@ -615,7 +615,7 @@ static var_entry var_table[] = {
   { "tls_certificate_verified", vtype_int,    &tls_certificate_verified },
   { "tls_cipher",          vtype_stringptr,   &tls_cipher },
   { "tls_peerdn",          vtype_stringptr,   &tls_peerdn },
-#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+#ifdef SUPPORT_TLS
   { "tls_sni",             vtype_stringptr,   &tls_sni },
 #endif
   { "tod_bsdinbox",        vtype_todbsdin,    NULL },
@@ -776,6 +776,7 @@ return rc;
 
 
 
+
 /*************************************************
 *        Pseudo-random number generation         *
 *************************************************/
@@ -788,19 +789,23 @@ weirdness they'll twist this into.  The result should ideally handle fork().
 However, if we're stuck unable to provide this, then we'll fall back to
 appallingly bad randomness.
 
-If SUPPORT_TLS is defined and OpenSSL is used, then this will not be used.
-The GNUTLS randomness functions found do not seem amenable to extracting
-random numbers outside of a TLS context.  Any volunteers?
+If SUPPORT_TLS is defined then this will not be used except as an emergency
+fallback.
 
 Arguments:
   max       range maximum
 Returns     a random number in range [0, max-1]
 */
 
-#if !defined(SUPPORT_TLS) || defined(USE_GNUTLS)
+#ifdef SUPPORT_TLS
+# define vaguely_random_number vaguely_random_number_fallback
+#endif
 int
-pseudo_random_number(int max)
+vaguely_random_number(int max)
 {
+#ifdef SUPPORT_TLS
+# undef vaguely_random_number
+#endif
   static pid_t pid = 0;
   pid_t p2;
 #if defined(HAVE_SRANDOM) && !defined(HAVE_SRANDOMDEV)
@@ -843,7 +848,8 @@ pseudo_random_number(int max)
 #endif
 }
 
-#endif
+
+
 
 /*************************************************
 *             Pick out a name from a string      *
@@ -1781,7 +1787,7 @@ BOOL tempcond, combined_cond;
 BOOL *subcondptr;
 BOOL sub2_honour_dollar = TRUE;
 int i, rc, cond_type, roffset;
-int num[2];
+int_eximarith_t num[2];
 struct stat statbuf;
 uschar name[256];
 uschar *sub[4];
@@ -3069,14 +3075,14 @@ Returns:      on success: the value of the expression, with *error still NULL
               on failure: an undefined value, with *error = a message
 */
 
-static int eval_op_or(uschar **, BOOL, uschar **);
+static int_eximarith_t eval_op_or(uschar **, BOOL, uschar **);
 
 
-static int
+static int_eximarith_t
 eval_expr(uschar **sptr, BOOL decimal, uschar **error, BOOL endket)
 {
 uschar *s = *sptr;
-int x = eval_op_or(&s, decimal, error);
+int_eximarith_t x = eval_op_or(&s, decimal, error);
 if (*error == NULL)
   {
   if (endket)
@@ -3093,21 +3099,26 @@ return x;
 }
 
 
-static int
+static int_eximarith_t
 eval_number(uschar **sptr, BOOL decimal, uschar **error)
 {
 register int c;
-int n;
+int_eximarith_t n;
 uschar *s = *sptr;
 while (isspace(*s)) s++;
 c = *s;
 if (isdigit(c))
   {
   int count;
-  (void)sscanf(CS s, (decimal? "%d%n" : "%i%n"), &n, &count);
+  (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count);
   s += count;
-  if (tolower(*s) == 'k') { n *= 1024; s++; }
-    else if (tolower(*s) == 'm') { n *= 1024*1024; s++; }
+  switch (tolower(*s))
+    {
+    default: break;
+    case 'k': n *= 1024; s++; break;
+    case 'm': n *= 1024*1024; s++; break;
+    case 'g': n *= 1024*1024*1024; s++; break;
+    }
   while (isspace (*s)) s++;
   }
 else if (c == '(')
@@ -3125,10 +3136,11 @@ return n;
 }
 
 
-static int eval_op_unary(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_unary(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x;
+int_eximarith_t x;
 while (isspace(*s)) s++;
 if (*s == '+' || *s == '-' || *s == '~')
   {
@@ -3146,16 +3158,17 @@ return x;
 }
 
 
-static int eval_op_mult(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_mult(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_unary(&s, decimal, error);
+int_eximarith_t x = eval_op_unary(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '*' || *s == '/' || *s == '%')
     {
     int op = *s++;
-    int y = eval_op_unary(&s, decimal, error);
+    int_eximarith_t y = eval_op_unary(&s, decimal, error);
     if (*error != NULL) break;
     /* SIGFPE both on div/mod by zero and on INT_MIN / -1, which would give
      * a value of INT_MAX+1. Note that INT_MIN * -1 gives INT_MIN for me, which
@@ -3175,12 +3188,12 @@ if (*error == NULL)
      * can just let the other invalid results occur otherwise, as they have
      * until now.  For this one case, we can coerce.
      */
-    if (y == -1 && x == INT_MIN && op != '*')
+    if (y == -1 && x == LLONG_MIN && op != '*')
       {
       DEBUG(D_expand)
-        debug_printf("Integer exception dodging: %d%c-1 coerced to %d\n",
-            INT_MIN, op, INT_MAX);
-      x = INT_MAX;
+        debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n",
+            LLONG_MIN, op, LLONG_MAX);
+      x = LLONG_MAX;
       continue;
       }
     if (op == '*')
@@ -3205,16 +3218,17 @@ return x;
 }
 
 
-static int eval_op_sum(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_sum(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_mult(&s, decimal, error);
+int_eximarith_t x = eval_op_mult(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '+' || *s == '-')
     {
     int op = *s++;
-    int y = eval_op_mult(&s, decimal, error);
+    int_eximarith_t y = eval_op_mult(&s, decimal, error);
     if (*error != NULL) break;
     if (op == '+') x += y; else x -= y;
     }
@@ -3224,15 +3238,16 @@ return x;
 }
 
 
-static int eval_op_shift(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_shift(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_sum(&s, decimal, error);
+int_eximarith_t x = eval_op_sum(&s, decimal, error);
 if (*error == NULL)
   {
   while ((*s == '<' || *s == '>') && s[1] == s[0])
     {
-    int y;
+    int_eximarith_t y;
     int op = *s++;
     s++;
     y = eval_op_sum(&s, decimal, error);
@@ -3245,15 +3260,16 @@ return x;
 }
 
 
-static int eval_op_and(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_and(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_shift(&s, decimal, error);
+int_eximarith_t x = eval_op_shift(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '&')
     {
-    int y;
+    int_eximarith_t y;
     s++;
     y = eval_op_shift(&s, decimal, error);
     if (*error != NULL) break;
@@ -3265,15 +3281,16 @@ return x;
 }
 
 
-static int eval_op_xor(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_xor(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_and(&s, decimal, error);
+int_eximarith_t x = eval_op_and(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '^')
     {
-    int y;
+    int_eximarith_t y;
     s++;
     y = eval_op_and(&s, decimal, error);
     if (*error != NULL) break;
@@ -3285,15 +3302,16 @@ return x;
 }
 
 
-static int eval_op_or(uschar **sptr, BOOL decimal, uschar **error)
+static int_eximarith_t
+eval_op_or(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
-int x = eval_op_xor(&s, decimal, error);
+int_eximarith_t x = eval_op_xor(&s, decimal, error);
 if (*error == NULL)
   {
   while (*s == '|')
     {
-    int y;
+    int_eximarith_t y;
     s++;
     y = eval_op_xor(&s, decimal, error);
     if (*error != NULL) break;
@@ -5693,7 +5711,7 @@ while (*s != 0)
         {
         uschar *save_sub = sub;
         uschar *error = NULL;
-        int n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
+        int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
         if (error != NULL)
           {
           expand_string_message = string_sprintf("error in expression "
@@ -5701,7 +5719,7 @@ while (*s != 0)
               save_sub);
           goto EXPAND_FAILED;
           }
-        sprintf(CS var_buffer, "%d", n);
+        sprintf(CS var_buffer, PR_EXIM_ARITH, n);
         yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
         continue;
         }
@@ -5902,17 +5920,17 @@ while (*s != 0)
         continue;
         }
 
-      /* pseudo-random number less than N */
+      /* vaguely random number less than N */
 
       case EOP_RANDINT:
         {
-        int max;
+        int_eximarith_t max;
         uschar *s;
 
         max = expand_string_integer(sub, TRUE);
         if (expand_string_message != NULL)
           goto EXPAND_FAILED;
-        s = string_sprintf("%d", pseudo_random_number(max));
+        s = string_sprintf("%d", vaguely_random_number((int)max));
         yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
         continue;
         }
@@ -6103,10 +6121,10 @@ Returns:  the integer value, or
           expand_string_message is set NULL for an OK integer
 */
 
-int
+int_eximarith_t
 expand_string_integer(uschar *string, BOOL isplus)
 {
-long int value;
+int_eximarith_t value;
 uschar *s = expand_string(string);
 uschar *msg = US"invalid integer \"%s\"";
 uschar *endptr;
@@ -6138,7 +6156,7 @@ if (isspace(*s))
     }
   }
 
-value = strtol(CS s, CSS &endptr, 10);
+value = strtoll(CS s, CSS &endptr, 10);
 
 if (endptr == s)
   {
@@ -6150,24 +6168,18 @@ else if (value < 0 && isplus)
   }
 else
   {
-  /* Ensure we can cast this down to an int */
-  if (value > INT_MAX  || value < INT_MIN) errno = ERANGE;
-
-  if (errno != ERANGE)
+  if (tolower(*endptr) == 'k')
     {
-    if (tolower(*endptr) == 'k')
-      {
-      if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE;
-        else value *= 1024;
-      endptr++;
-      }
+    if (value > LLONG_MAX/1024 || value < LLONG_MIN/1024) errno = ERANGE;
+      else value *= 1024;
+    endptr++;
+    }
     else if (tolower(*endptr) == 'm')
-      {
-      if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024))
-        errno = ERANGE;
-      else value *= 1024*1024;
-      endptr++;
-      }
+    {
+    if (value > LLONG_MAX/(1024*1024) || value < LLONG_MIN/(1024*1024))
+      errno = ERANGE;
+    else value *= 1024*1024;
+    endptr++;
     }
   if (errno == ERANGE)
     msg = US"absolute value of integer \"%s\" is too large (overflow)";
index 220235235db63757b2bc4fba1e06e90df5e33a0c..557b7e72a6054cb5ae1fe87fef04465ea1e771f0 100644 (file)
@@ -23,13 +23,13 @@ extern uschar *init_perl(uschar *);
 #ifdef SUPPORT_TLS
 extern int     tls_client_start(int, host_item *, address_item *, uschar *,
                  uschar *, uschar *, uschar *, uschar *, uschar *, uschar *,
-                 uschar *, uschar *, uschar *, int);
+                 int);
 extern void    tls_close(BOOL);
 extern int     tls_feof(void);
 extern int     tls_ferror(void);
 extern int     tls_getc(void);
 extern int     tls_read(uschar *, size_t);
-extern int     tls_server_start(uschar *, uschar *, uschar *, uschar *);
+extern int     tls_server_start(const uschar *);
 extern BOOL    tls_smtp_buffered(void);
 extern int     tls_ungetc(int);
 extern int     tls_write(const uschar *, size_t);
@@ -114,7 +114,7 @@ extern void    exim_wait_tick(struct timeval *, int);
 extern BOOL    expand_check_condition(uschar *, uschar *, uschar *);
 extern uschar *expand_string(uschar *);
 extern uschar *expand_string_copy(uschar *);
-extern int     expand_string_integer(uschar *, BOOL);
+extern int_eximarith_t expand_string_integer(uschar *, BOOL);
 
 extern int     filter_interpret(uschar *, int, address_item **, uschar **);
 extern BOOL    filter_personal(string_item *, BOOL);
@@ -199,7 +199,10 @@ extern uschar *parse_fix_phrase(uschar *, int, uschar *, int);
 extern uschar *parse_message_id(uschar *, uschar **, uschar **);
 extern uschar *parse_quote_2047(uschar *, int, uschar *, uschar *, int, BOOL);
 extern uschar *parse_date_time(uschar *str, time_t *t);
-extern int     pseudo_random_number(int);
+extern int     vaguely_random_number(int);
+#ifdef SUPPORT_TLS
+extern int     vaguely_random_number_fallback(int);
+#endif
 
 extern BOOL    queue_action(uschar *, int, uschar **, int, int);
 extern void    queue_check_only(void);
@@ -379,4 +382,6 @@ extern BOOL    verify_sender(int *, uschar **);
 extern BOOL    verify_sender_preliminary(int *, uschar **);
 extern void    version_init(void);
 
+extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
+
 /* End of functions.h */
index 5ea4329125b9b0fd2d5b74de5cead93026aeeb7f..666f5e78a07875b3722d3e78005158fe1612197a 100644 (file)
@@ -119,9 +119,7 @@ BOOL    tls_offered            = FALSE;
 uschar *tls_privatekey         = NULL;
 BOOL    tls_remember_esmtp     = FALSE;
 uschar *tls_require_ciphers    = NULL;
-#ifndef USE_GNUTLS
 uschar *tls_sni                = NULL;
-#endif
 uschar *tls_try_verify_hosts   = NULL;
 uschar *tls_verify_certificates= NULL;
 uschar *tls_verify_hosts       = NULL;
@@ -662,6 +660,8 @@ uschar *hosts_connection_nolog = NULL;
 int     ignore_bounce_errors_after = 10*7*24*60*60;  /* 10 weeks */
 BOOL    ignore_fromline_local  = FALSE;
 uschar *ignore_fromline_hosts  = NULL;
+BOOL    inetd_wait_mode        = FALSE;
+int     inetd_wait_timeout     = -1;
 uschar *interface_address      = NULL;
 int     interface_port         = -1;
 BOOL    is_inetd               = FALSE;
index ec19d0a23341768ab2d0e89510c951dde9bfffe1..f0bc09f35d52e88838577bb415f4b08c99bbcd40 100644 (file)
@@ -101,9 +101,7 @@ extern BOOL    tls_offered;            /* Server offered TLS */
 extern uschar *tls_privatekey;         /* Private key file */
 extern BOOL    tls_remember_esmtp;     /* For YAEB */
 extern uschar *tls_require_ciphers;    /* So some can be avoided */
-#ifndef USE_GNUTLS
 extern uschar *tls_sni;                /* Server Name Indication */
-#endif
 extern uschar *tls_try_verify_hosts;   /* Optional client verification */
 extern uschar *tls_verify_certificates;/* Path for certificates to check */
 extern uschar *tls_verify_hosts;       /* Mandatory client verification */
@@ -427,6 +425,8 @@ extern uschar *hosts_treat_as_local;   /* For routing */
 extern int     ignore_bounce_errors_after; /* Keep them for this time. */
 extern BOOL    ignore_fromline_local;  /* Local SMTP ignore fromline */
 extern uschar *ignore_fromline_hosts;  /* Hosts permitted to send "From " */
+extern BOOL    inetd_wait_mode;        /* Whether running in inetd wait mode */
+extern int     inetd_wait_timeout;     /* Timeout for inetd wait mode */
 extern BOOL    is_inetd;               /* True for inetd calls */
 extern uschar *iterate_item;           /* Item from iterate list */
 
index 0571772cb1b19d35c214a8190e3332cf4c6c0921..c45c2d77bc1dee4812246ac7b66646eda412ef55 100644 (file)
@@ -68,7 +68,7 @@ sprintf(addr, "%d.%d.%d.%d",
 very good for the uses to which it is put. When running the regression tests,
 start with a fixed seed.
 
-If you need better, see pseudo_random_number() which is potentially stronger,
+If you need better, see vaguely_random_number() which is potentially stronger,
 if a crypto library is available, but might end up just calling this instead.
 
 Arguments:
index bfd1fef86efc15c1a447cbb18556b5e53231cf98..0ba4e638bda782c041d7aa5350c1b0b15bdc8e6e 100644 (file)
@@ -528,11 +528,11 @@ Returns:
   length actually written, persisting an errno from write()
 */
 ssize_t
-write_to_fd_buf(int fd, uschar *buf, size_t length)
+write_to_fd_buf(int fd, const uschar *buf, size_t length)
 {
 ssize_t wrote;
 size_t total_written = 0;
-uschar *p = buf;
+const uschar *p = buf;
 size_t left = length;
 
 while (1)
@@ -1306,7 +1306,7 @@ misconfiguration.
 
 The first use of this is in ACL logic, "control = debug/tag=foo/opts=+expand"
 which can be combined with conditions, etc, to activate extra logging only
-for certain sources. */
+for certain sources. The second use is inetd wait mode debug preservation. */
 
 void
 debug_logging_activate(uschar *tag_name, uschar *opts)
@@ -1316,7 +1316,7 @@ int fd = -1;
 if (debug_file)
   {
   debug_printf("DEBUGGING ACTIVATED FROM WITHIN CONFIG.\n"
-      "DEBUG: Tag=\"%s\" Opts=\"%s\"\n", tag_name, opts);
+      "DEBUG: Tag=\"%s\" Opts=\"%s\"\n", tag_name, opts ? opts : US"");
   return;
   }
 
index 864564ffc926edb4d2a563db6236897779e1e66f..79e2e382758a23e5e58beaefba78bdbcde51562c 100644 (file)
@@ -115,7 +115,7 @@ malware_in_file(uschar *eml_filename) {
   /* spool_mbox() assumes various parameters exist, when creating
   the relevant directory and the email within */
   (void) string_format(message_id_buf, sizeof(message_id_buf),
-      "dummy-%d", pseudo_random_number(INT_MAX));
+      "dummy-%d", vaguely_random_number(INT_MAX));
   message_id = message_id_buf;
   sender_address = US"malware-sender@example.net";
   return_path = US"";
index badb6a27609f1983bae803973ad8d23d1a086664..95e155ff317a86dc43d4e8981531b5aad752e2a6 100644 (file)
@@ -235,6 +235,7 @@ static optionlist optionlist_config[] = {
   { "gecos_pattern",            opt_stringptr,   &gecos_pattern },
 #ifdef SUPPORT_TLS
   { "gnutls_compat_mode",       opt_bool,        &gnutls_compat_mode },
+  /* These three gnutls_require_* options stopped working in Exim 4.78 */
   { "gnutls_require_kx",        opt_stringptr,   &gnutls_require_kx },
   { "gnutls_require_mac",       opt_stringptr,   &gnutls_require_mac },
   { "gnutls_require_protocols", opt_stringptr,   &gnutls_require_proto },
index aaaf64ce6f1e9f797462af60bddfc9c5d3fbff68..b3b06e86756fddc836468c73b79d05198659d6c4 100644 (file)
@@ -3488,12 +3488,10 @@ if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
 if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
   s = string_append(s, &size, &sptr, 3, US" DN=\"",
     string_printing(tls_peerdn), US"\"");
-#ifndef USE_GNUTLS
 if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
   s = string_append(s, &size, &sptr, 3, US" SNI=\"",
     string_printing(tls_sni), US"\"");
 #endif
-#endif
 
 if (sender_host_authenticated != NULL)
   {
index d1c10f00fb5b81f3a8ddb9f26390d25425245684..5d241d960b3d7c35e3ef989e2573e9fa55c8e343 100644 (file)
@@ -841,12 +841,10 @@ if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
 if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
   s = string_append(s, &size, &ptr, 3, US" DN=\"",
     string_printing(tls_peerdn), US"\"");
-#ifndef USE_GNUTLS
 if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
   s = string_append(s, &size, &ptr, 3, US" SNI=\"",
     string_printing(tls_sni), US"\"");
 #endif
-#endif
 
 sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
   US" C=..." : US" C=";
@@ -1676,8 +1674,7 @@ if (!sender_host_unknown)
 
   #ifdef SUPPORT_TLS
   if (tls_on_connect &&
-      tls_server_start(tls_require_ciphers,
-        gnutls_require_mac, gnutls_require_kx, gnutls_require_proto) != OK)
+      tls_server_start(tls_require_ciphers) != OK)
     return FALSE;
   #endif
 
@@ -3893,8 +3890,7 @@ while (done <= 0)
     We must allow for an extra EHLO command and an extra AUTH command after
     STARTTLS that don't add to the nonmail command count. */
 
-    if ((rc = tls_server_start(tls_require_ciphers, gnutls_require_mac,
-           gnutls_require_kx, gnutls_require_proto)) == OK)
+    if ((rc = tls_server_start(tls_require_ciphers)) == OK)
       {
       if (!tls_remember_esmtp)
         helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
index bdc3903c048a3201aad2f6812487a369233e7f5b..f6aa18c34b1f454fe1e55b54f38db6535188cc79 100644 (file)
@@ -286,10 +286,8 @@ dkim_collect_input = FALSE;
 tls_certificate_verified = FALSE;
 tls_cipher = NULL;
 tls_peerdn = NULL;
-#ifndef USE_GNUTLS
 tls_sni = NULL;
 #endif
-#endif
 
 #ifdef WITH_CONTENT_SCAN
 spam_score_int = NULL;
@@ -552,10 +550,8 @@ for (;;)
       tls_cipher = string_copy(big_buffer + 12);
     else if (Ustrncmp(p, "ls_peerdn", 9) == 0)
       tls_peerdn = string_unprinting(string_copy(big_buffer + 12));
-    #ifndef USE_GNUTLS
     else if (Ustrncmp(p, "ls_sni", 6) == 0)
       tls_sni = string_unprinting(string_copy(big_buffer + 9));
-    #endif
     break;
     #endif
 
index fa4f1b6e2f5a49ed098fd0b7e59f35ed3ffd82fb..0d32fd6cae097b68b57bcb0d1f3673da1852806f 100644 (file)
@@ -229,10 +229,8 @@ if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts);
 if (tls_certificate_verified) fprintf(f, "-tls_certificate_verified\n");
 if (tls_cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_cipher);
 if (tls_peerdn != NULL) fprintf(f, "-tls_peerdn %s\n", string_printing(tls_peerdn));
-#ifndef USE_GNUTLS
 if (tls_sni != NULL) fprintf(f, "-tls_sni %s\n", string_printing(tls_sni));
 #endif
-#endif
 
 /* To complete the envelope, write out the tree of non-recipients, followed by
 the list of recipients. These won't be disjoint the first time, when no
index 7e87dded0e0859f93dc6196ac047bb31e470fa91..f0e391f9713ef53c7edd7940138a411e3a4ea16c 100644 (file)
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
-/* This module provides TLS (aka SSL) support for Exim using the GnuTLS
-library. It is #included into tls.c when that library is used. The code herein
-is based on a patch that was contributed by Nikos Mavroyanopoulos.
+/* Copyright (c) Phil Pennock 2012 */
 
-No cryptographic code is included in Exim. All this module does is to call
-functions from the GnuTLS library. */
+/* This file provides TLS/SSL support for Exim using the GnuTLS library,
+one of the available supported implementations.  This file is #included into
+tls.c when USE_GNUTLS has been set.
 
-/* Note: This appears to be using an old API from compat.h; it is likely that
-someone familiary with GnuTLS programming could rework a lot of this to a
-modern API and perhaps remove the explicit knowledge of crypto algorithms from
-Exim.  Such a re-work would be most welcome and we'd sacrifice support for
-older GnuTLS releases without too many qualms -- maturity and experience
-in crypto libraries tends to improve their robustness against attack.
-Frankly, if you maintain it, you decide what's supported and what isn't. */
+The code herein is a revamp of GnuTLS integration using the current APIs; the
+original tls-gnu.c was based on a patch which was contributed by Nikos
+Mavroyanopoulos.  The revamp is partially a rewrite, partially cut&paste as
+appropriate.
 
-/* Heading stuff for GnuTLS */
+APIs current as of GnuTLS 2.12.18; note that the GnuTLS manual is for GnuTLS 3,
+which is not widely deployed by OS vendors.  Will note issues below, which may
+assist in updating the code in the future.  Another sources of hints is
+mod_gnutls for Apache (SNI callback registration and handling).
 
-#include <gnutls/gnutls.h>
-#include <gnutls/x509.h>
+Keeping client and server variables more split than before and is currently
+the norm, in anticipation of TLS in ACL callouts.
 
+I wanted to switch to gnutls_certificate_set_verify_function() so that
+certificate rejection could happen during handshake where it belongs, rather
+than being dropped afterwards, but that was introduced in 2.10.0 and Debian
+(6.0.5) is still on 2.8.6.  So for now we have to stick with sub-par behaviour.
 
-#define UNKNOWN_NAME "unknown"
-#define DH_BITS      1024
-#define PARAM_SIZE 2*1024
+(I wasn't looking for libraries quite that old, when updating to get rid of
+compiler warnings of deprecated APIs.  If it turns out that a lot of the rest
+require current GnuTLS, then we'll drop support for the ancient libraries).
+*/
 
+#include <gnutls/gnutls.h>
+/* needed for cert checks in verification and DN extraction: */
+#include <gnutls/x509.h>
+/* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */
+#include <gnutls/crypto.h>
 
-/* Values for verify_requirment */
+/* GnuTLS 2 vs 3
 
-enum { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
+GnuTLS 3 only:
+  gnutls_global_set_audit_log_function()
 
-/* Local static variables for GNUTLS */
+Changes:
+  gnutls_certificate_verify_peers2(): is new, drop the 2 for old version
+*/
 
-static host_item *client_host;
+/* Local static variables for GnuTLS */
 
-static gnutls_dh_params dh_params = NULL;
+/* Values for verify_requirement */
 
-static gnutls_certificate_server_credentials x509_cred = NULL;
-static gnutls_session tls_session = NULL;
+enum peer_verify_requirement { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
 
-static char ssl_errstring[256];
+/* This holds most state for server or client; with this, we can set up an
+outbound TLS-enabled connection in an ACL callout, while not stomping all
+over the TLS variables available for expansion.
 
-static int  ssl_session_timeout = 200;
-static int  verify_requirement;
+Some of these correspond to variables in globals.c; those variables will
+be set to point to content in one of these instances, as appropriate for
+the stage of the process lifetime.
 
-/* Priorities for TLS algorithms to use. In each case there's a default table,
-and space into which it can be copied and altered. */
+Not handled here: globals tls_active, tls_bits, tls_cipher, tls_peerdn,
+tls_certificate_verified, tls_channelbinding_b64, tls_sni.
+*/
 
-static const int default_proto_priority[16] = {
-  /* These are gnutls_protocol_t enum values */
-#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 7
-  GNUTLS_TLS1_2,
-#endif
-#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 2
-  GNUTLS_TLS1_1,
-#endif
-  GNUTLS_TLS1,
-  GNUTLS_SSL3,
-  0 };
+typedef struct exim_gnutls_state {
+  gnutls_session_t session;
+  gnutls_certificate_credentials_t x509_cred;
+  gnutls_priority_t priority_cache;
+  enum peer_verify_requirement verify_requirement;
+  int fd_in;
+  int fd_out;
+  BOOL peer_cert_verified;
+  BOOL trigger_sni_changes;
+  const struct host_item *host;
+  uschar *peerdn;
+  uschar *received_sni;
+
+  const uschar *tls_certificate;
+  const uschar *tls_privatekey;
+  const uschar *tls_sni; /* client send only, not received */
+  const uschar *tls_verify_certificates;
+  const uschar *tls_crl;
+  const uschar *tls_require_ciphers;
+  uschar *exp_tls_certificate;
+  uschar *exp_tls_privatekey;
+  uschar *exp_tls_sni;
+  uschar *exp_tls_verify_certificates;
+  uschar *exp_tls_crl;
+  uschar *exp_tls_require_ciphers;
+
+  uschar *xfer_buffer;
+  int xfer_buffer_lwm;
+  int xfer_buffer_hwm;
+  int xfer_eof;
+  int xfer_error;
+
+  uschar cipherbuf[256];
+} exim_gnutls_state_st;
+
+static const exim_gnutls_state_st exim_gnutls_state_init = {
+  NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE,
+  NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, 0, 0, 0, 0,
+  ""
+};
 
-static int proto_priority[16];
+/* Not only do we have our own APIs which don't pass around state, assuming
+it's held in globals, GnuTLS doesn't appear to let us register callback data
+for callbacks, or as part of the session, so we have to keep a "this is the
+context we're currently dealing with" pointer and rely upon being
+single-threaded to keep from processing data on an inbound TLS connection while
+talking to another TLS connection for an outbound check.  This does mean that
+there's no way for heart-beats to be responded to, for the duration of the
+second connection. */
 
-static const int default_kx_priority[16] = {
-  GNUTLS_KX_RSA,
-  GNUTLS_KX_DHE_DSS,
-  GNUTLS_KX_DHE_RSA,
-  0 };
+static exim_gnutls_state_st state_server, state_client;
+static exim_gnutls_state_st *current_global_tls_state;
 
-static int kx_priority[16];
+/* dh_params are initialised once within the lifetime of a process using TLS;
+if we used TLS in a long-lived daemon, we'd have to reconsider this.  But we
+don't want to repeat this. */
 
-static int default_cipher_priority[16] = {
-  GNUTLS_CIPHER_AES_256_CBC,
-  GNUTLS_CIPHER_AES_128_CBC,
-  GNUTLS_CIPHER_3DES_CBC,
-  GNUTLS_CIPHER_ARCFOUR_128,
-  0 };
+static gnutls_dh_params_t dh_server_params = NULL;
 
-static int cipher_priority[16];
+/* No idea how this value was chosen; preserving it.  Default is 3600. */
 
-static const int default_mac_priority[16] = {
-  GNUTLS_MAC_SHA,
-  GNUTLS_MAC_MD5,
-  0 };
+static const int ssl_session_timeout = 200;
 
-static int mac_priority[16];
+static const char * const exim_default_gnutls_priority = "NORMAL";
 
-/* These two are currently not changeable. */
+/* Guard library core initialisation */
 
-static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
-static const int cert_type_priority[16] = { GNUTLS_CRT_X509, 0 };
+static BOOL exim_gnutls_base_init_done = FALSE;
 
-/* Tables of priority names and equivalent numbers */
 
-typedef struct pri_item {
-  uschar *name;
-  int *values;
-} pri_item;
+/* ------------------------------------------------------------------------ */
+/* Callback declarations */
 
+static void exim_gnutls_logger_cb(int level, const char *message);
+static int exim_sni_handling_cb(gnutls_session_t session);
 
-#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 7
-static int tls1_2_codes[] = { GNUTLS_TLS1_2, 0 };
-#endif
-#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 2
-static int tls1_1_codes[] = { GNUTLS_TLS1_1, 0 };
-#endif
-/* more recent libraries define this as an equivalent value to the
-canonical GNUTLS_TLS1_0; since they're the same, we stick to the
-older name. */
-static int tls1_0_codes[] = { GNUTLS_TLS1, 0 };
-static int ssl3_codes[] = { GNUTLS_SSL3, 0 };
-
-static pri_item proto_index[] = {
-#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 7
-  { US"TLS1.2", tls1_2_codes },
-#endif
-#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 2
-  { US"TLS1.1", tls1_1_codes },
-#endif
-  { US"TLS1.0", tls1_0_codes },
-  { US"TLS1", tls1_0_codes },
-  { US"SSL3", ssl3_codes }
-};
+/* ------------------------------------------------------------------------ */
+/* macros */
 
+#define MAX_HOST_LEN 255
 
-static int kx_rsa_codes[]      = { GNUTLS_KX_RSA,
-                                   GNUTLS_KX_DHE_RSA, 0 };
-static int kx_dhe_codes[]      = { GNUTLS_KX_DHE_DSS,
-                                   GNUTLS_KX_DHE_RSA, 0 };
-static int kx_dhe_dss_codes[]  = { GNUTLS_KX_DHE_DSS, 0 };
-static int kx_dhe_rsa_codes[]  = { GNUTLS_KX_DHE_RSA, 0 };
-
-static pri_item kx_index[] = {
-  { US"DHE_DSS", kx_dhe_dss_codes },
-  { US"DHE_RSA", kx_dhe_rsa_codes },
-  { US"RSA", kx_rsa_codes },
-  { US"DHE", kx_dhe_codes }
-};
+/* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
+the library logging; a value less than 0 disables the calls to set up logging
+callbacks. */
+#define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
 
+#define EXIM_CLIENT_DH_MIN_BITS 1024
 
-static int arcfour_128_codes[] = { GNUTLS_CIPHER_ARCFOUR_128, 0 };
-static int arcfour_40_codes[]  = { GNUTLS_CIPHER_ARCFOUR_40, 0 };
-static int arcfour_codes[]     = { GNUTLS_CIPHER_ARCFOUR_128,
-                                   GNUTLS_CIPHER_ARCFOUR_40, 0 };
-static int aes_256_codes[]     = { GNUTLS_CIPHER_AES_256_CBC, 0 };
-static int aes_128_codes[]     = { GNUTLS_CIPHER_AES_128_CBC, 0 };
-static int aes_codes[]         = { GNUTLS_CIPHER_AES_256_CBC,
-                                   GNUTLS_CIPHER_AES_128_CBC, 0 };
-static int des3_codes[]        = { GNUTLS_CIPHER_3DES_CBC, 0 };
-
-static pri_item cipher_index[] = {
-  { US"ARCFOUR_128", arcfour_128_codes },
-  { US"ARCFOUR_40", arcfour_40_codes },
-  { US"ARCFOUR", arcfour_codes },
-  { US"AES_256", aes_256_codes },
-  { US"AES_128", aes_128_codes },
-  { US"AES", aes_codes },
-  { US"3DES", des3_codes }
-};
+#define exim_gnutls_err_check(Label) do { \
+  if (rc != GNUTLS_E_SUCCESS) { return tls_error((Label), gnutls_strerror(rc), host); } } while (0)
 
+#define exim_gnutls_err_debugreturn0(Label) do { \
+  if (rc != GNUTLS_E_SUCCESS) { \
+    DEBUG(D_tls) debug_printf("TLS failure: %s: %s", (Label), gnutls_strerror(rc)); \
+    return 0; } } while (0)
 
-static int mac_sha_codes[]     = { GNUTLS_MAC_SHA, 0 };
-static int mac_md5_codes[]     = { GNUTLS_MAC_MD5, 0 };
-
-static pri_item mac_index[] = {
-  { US"SHA",  mac_sha_codes },
-  { US"SHA1", mac_sha_codes },
-  { US"MD5",  mac_md5_codes }
-};
+#define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname)
 
+#if GNUTLS_VERSION_NUMBER >= 0x020c00
+#define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
+#endif
 
+/* ------------------------------------------------------------------------ */
+/* Static functions */
 
 /*************************************************
 *               Handle TLS error                 *
@@ -188,120 +188,169 @@ some shared functions.
 
 Argument:
   prefix    text to include in the logged error
-  host      NULL if setting up a server;
-            the connected host if setting up a client
   msg       additional error string (may be NULL)
             usually obtained from gnutls_strerror()
+  host      NULL if setting up a server;
+            the connected host if setting up a client
 
 Returns:    OK/DEFER/FAIL
 */
 
 static int
-tls_error(uschar *prefix, host_item *host, const char *msg)
+tls_error(const uschar *prefix, const char *msg, const host_item *host)
 {
-if (host == NULL)
+if (host)
+  {
+  log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s",
+      host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
+  return FAIL;
+  }
+else
   {
   uschar *conn_info = smtp_get_connection_info();
-  if (strncmp(conn_info, "SMTP ", 5) == 0)
+  if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
     conn_info += 5;
   log_write(0, LOG_MAIN, "TLS error on %s (%s)%s%s",
-    conn_info, prefix, msg ? ": " : "", msg ? msg : "");
+      conn_info, prefix, msg ? ": " : "", msg ? msg : "");
   return DEFER;
   }
-else
-  {
-  log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s",
-    host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
-  return FAIL;
-  }
 }
 
 
 
+
 /*************************************************
-*             Verify certificate                 *
+*    Deal with logging errors during I/O         *
 *************************************************/
 
-/* Called after a successful handshake, when certificate verification is
-required or optional, for both server and client.
+/* We have to get the identity of the peer from saved data.
 
-Arguments:
-  session    GNUTLS session
-  error      where to put text giving a reason for failure
+Argument:
+  state    the current GnuTLS exim state container
+  rc       the GnuTLS error code, or 0 if it's a local error
+  when     text identifying read or write
+  text     local error text when ec is 0
 
-Returns:     TRUE/FALSE
+Returns:   nothing
 */
 
-static BOOL
-verify_certificate(gnutls_session session, const char **error)
+static void
+record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text)
 {
-int rc = -1;
-uschar *dn_string = US"";
-const gnutls_datum *cert;
-unsigned int verify, cert_size = 0;
+const char *msg;
 
-*error = NULL;
+if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
+  msg = CS string_sprintf("%s: %s", US gnutls_strerror(rc),
+    US gnutls_alert_get_name(gnutls_alert_get(state->session)));
+else
+  msg = gnutls_strerror(rc);
 
-/* Get the peer's certificate. If it sent one, extract it's DN, and then
-attempt to verify the certificate. If no certificate is supplied, verification
-is forced to fail. */
+tls_error(when, msg, state->host);
+}
 
-cert = gnutls_certificate_get_peers(session, &cert_size);
-if (cert != NULL)
-  {
-  uschar buff[1024];
-  gnutls_x509_crt gcert;
 
-  gnutls_x509_crt_init(&gcert);
-  dn_string = US"unknown";
 
-  if (gnutls_x509_crt_import(gcert, cert, GNUTLS_X509_FMT_DER) == 0)
-    {
-    size_t bufsize = sizeof(buff);
-    if (gnutls_x509_crt_get_dn(gcert, CS buff, &bufsize) >= 0)
-      dn_string = string_copy_malloc(buff);
-    }
 
-  rc = gnutls_certificate_verify_peers2(session, &verify);
-  }
-else
-  {
-  DEBUG(D_tls) debug_printf("no peer certificate supplied\n");
-  verify = GNUTLS_CERT_INVALID;
-  *error = "not supplied";
-  }
+/*************************************************
+*        Set various Exim expansion vars         *
+*************************************************/
 
-/* Handle the result of verification. INVALID seems to be set as well
-as REVOKED, but leave the test for both. */
+/* We set various Exim global variables from the state, once a session has
+been established.  With TLS callouts, may need to change this to stack
+variables, or just re-call it with the server state after client callout
+has finished.
 
-if ((rc < 0) || (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) != 0)
-  {
-  tls_certificate_verified = FALSE;
-  if (*error == NULL) *error = ((verify & GNUTLS_CERT_REVOKED) != 0)?
-    "revoked" : "invalid";
-  if (verify_requirement == VERIFY_REQUIRED)
-    {
-    DEBUG(D_tls) debug_printf("TLS certificate verification failed (%s): "
-      "peerdn=%s\n", *error, dn_string);
-    gnutls_alert_send(session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
-    return FALSE;                       /* reject */
-    }
-  DEBUG(D_tls) debug_printf("TLS certificate verify failure (%s) overridden "
-      "(host in tls_try_verify_hosts): peerdn=%s\n", *error, dn_string);
-  }
-else
+Make sure anything set here is inset in tls_getc().
+
+Sets:
+  tls_active                fd
+  tls_bits                  strength indicator
+  tls_certificate_verified  bool indicator
+  tls_channelbinding_b64    for some SASL mechanisms
+  tls_cipher                a string
+  tls_peerdn                a string
+  tls_sni                   a (UTF-8) string
+Also:
+  current_global_tls_state  for API limitations
+
+Argument:
+  state      the relevant exim_gnutls_state_st *
+*/
+
+static void
+extract_exim_vars_from_tls_state(exim_gnutls_state_st *state)
+{
+gnutls_protocol_t protocol;
+gnutls_cipher_algorithm_t cipher;
+gnutls_kx_algorithm_t kx;
+gnutls_mac_algorithm_t mac;
+uschar *p;
+#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
+int old_pool;
+int rc;
+gnutls_datum_t channel;
+#endif
+
+current_global_tls_state = state;
+
+tls_active = state->fd_out;
+
+cipher = gnutls_cipher_get(state->session);
+/* returns size in "bytes" */
+tls_bits = gnutls_cipher_get_key_size(cipher) * 8;
+
+if (!*state->cipherbuf)
   {
-  tls_certificate_verified = TRUE;
-  DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n",
-    dn_string);
+  protocol = gnutls_protocol_get_version(state->session);
+  mac = gnutls_mac_get(state->session);
+  kx = gnutls_kx_get(state->session);
+
+  string_format(state->cipherbuf, sizeof(state->cipherbuf),
+      "%s:%s:%u",
+      gnutls_protocol_get_name(protocol),
+      gnutls_cipher_suite_get_name(kx, cipher, mac),
+      tls_bits);
+
+  /* I don't see a way that spaces could occur, in the current GnuTLS
+  code base, but it was a concern in the old code and perhaps older GnuTLS
+  releases did return "TLS 1.0"; play it safe, just in case. */
+  for (p = state->cipherbuf; *p != '\0'; ++p)
+    if (isspace(*p))
+      *p = '-';
   }
+tls_cipher = state->cipherbuf;
+
+DEBUG(D_tls) debug_printf("cipher: %s\n", tls_cipher);
+
+tls_certificate_verified = state->peer_cert_verified;
 
-tls_peerdn = dn_string;
-return TRUE;                            /* accept */
+/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
+only available for use for authenticators while this TLS session is running. */
+
+tls_channelbinding_b64 = NULL;
+#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
+channel.data = NULL;
+channel.size = 0;
+rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel);
+if (rc) {
+  DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc));
+} else {
+  old_pool = store_pool;
+  store_pool = POOL_PERM;
+  tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size);
+  store_pool = old_pool;
+  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
+}
+#endif
+
+tls_peerdn = state->peerdn;
+
+tls_sni = state->received_sni;
 }
 
 
 
+
 /*************************************************
 *            Setup up DH parameters              *
 *************************************************/
@@ -323,23 +372,30 @@ Returns:     OK/DEFER/FAIL
 */
 
 static int
-init_dh(host_item *host)
+init_server_dh(void)
 {
-int fd;
-int ret;
+int fd, rc;
+unsigned int dh_bits;
 gnutls_datum m;
-uschar filename[200];
+uschar filename[PATH_MAX];
+size_t sz;
+host_item *host = NULL; /* dummy for macros */
+const char * const dh_param_fn_ext = "normal"; /* change as dh_bits changes */
 
-/* Initialize the data structures for holding the parameters */
+DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
 
-ret = gnutls_dh_params_init(&dh_params);
-if (ret < 0) return tls_error(US"init dh_params", host, gnutls_strerror(ret));
+rc = gnutls_dh_params_init(&dh_server_params);
+exim_gnutls_err_check(US"gnutls_dh_params_init");
 
-/* Set up the name of the cache file */
+/* If you change this, also change dh_param_fn_ext so that we can use a
+different filename and ensure we have sufficient bits. */
+dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL);
+if (!dh_bits)
+  return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL);
 
-if (!string_format(filename, sizeof(filename), "%s/gnutls-params",
-      spool_directory))
-  return tls_error(US"overlong filename", host, NULL);
+if (!string_format(filename, sizeof(filename),
+      "%s/gnutls-params-%s", spool_directory, dh_param_fn_ext))
+  return tls_error(US"overlong filename", NULL, NULL);
 
 /* Open the cache file for reading and if successful, read it and set up the
 parameters. */
@@ -348,27 +404,50 @@ fd = Uopen(filename, O_RDONLY, 0);
 if (fd >= 0)
   {
   struct stat statbuf;
-  if (fstat(fd, &statbuf) < 0)
+  FILE *fp;
+  int saved_errno;
+
+  if (fstat(fd, &statbuf) < 0)  /* EIO */
+    {
+    saved_errno = errno;
+    (void)close(fd);
+    return tls_error(US"TLS cache stat failed", strerror(saved_errno), NULL);
+    }
+  if (!S_ISREG(statbuf.st_mode))
     {
     (void)close(fd);
-    return tls_error(US"TLS cache stat failed", host, strerror(errno));
+    return tls_error(US"TLS cache not a file", NULL, NULL);
+    }
+  fp = fdopen(fd, "rb");
+  if (!fp)
+    {
+    saved_errno = errno;
+    (void)close(fd);
+    return tls_error(US"fdopen(TLS cache stat fd) failed",
+        strerror(saved_errno), NULL);
     }
 
   m.size = statbuf.st_size;
   m.data = malloc(m.size);
   if (m.data == NULL)
-    return tls_error(US"memory allocation failed", host, strerror(errno));
-  errno = 0;
-  if (read(fd, m.data, m.size) != m.size)
-    return tls_error(US"TLS cache read failed", host, strerror(errno));
-  (void)close(fd);
-
-  ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM);
-  if (ret < 0)
-    return tls_error(US"DH params import", host, gnutls_strerror(ret));
-  DEBUG(D_tls) debug_printf("read D-H parameters from file\n");
+    {
+    fclose(fp);
+    return tls_error(US"malloc failed", strerror(errno), NULL);
+    }
+  sz = fread(m.data, m.size, 1, fp);
+  if (!sz)
+    {
+    saved_errno = errno;
+    fclose(fp);
+    free(m.data);
+    return tls_error(US"fread failed", strerror(saved_errno), NULL);
+    }
+  fclose(fp);
 
+  rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
   free(m.data);
+  exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+  DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
   }
 
 /* If the file does not exist, fall through to compute new data and cache it.
@@ -376,13 +455,13 @@ If there was any other opening error, it is serious. */
 
 else if (errno == ENOENT)
   {
-  ret = -1;
+  rc = -1;
   DEBUG(D_tls)
-    debug_printf("parameter cache file %s does not exist\n", filename);
+    debug_printf("D-H parameter cache file \"%s\" does not exist\n", filename);
   }
 else
-  return tls_error(string_open_failed(errno, "%s for reading", filename),
-    host, NULL);
+  return tls_error(string_open_failed(errno, "\"%s\" for reading", filename),
+      NULL, NULL);
 
 /* If ret < 0, either the cache file does not exist, or the data it contains
 is not useful. One particular case of this is when upgrading from an older
@@ -390,59 +469,72 @@ release of Exim in which the data was stored in a different format. We don't
 try to be clever and support both formats; we just regenerate new data in this
 case. */
 
-if (ret < 0)
+if (rc < 0)
   {
-  uschar tempfilename[sizeof(filename) + 10];
+  uschar *temp_fn;
 
-  DEBUG(D_tls) debug_printf("generating %d bit Diffie-Hellman key...\n",
-    DH_BITS);
-  ret = gnutls_dh_params_generate2(dh_params, DH_BITS);
-  if (ret < 0) return tls_error(US"D-H key generation", host, gnutls_strerror(ret));
+  if ((PATH_MAX - Ustrlen(filename)) < 10)
+    return tls_error(US"Filename too long to generate replacement",
+        CS filename, NULL);
 
-  /* Write the parameters to a file in the spool directory so that we
-  can use them from other Exim processes. */
-
-  sprintf(CS tempfilename, "%s-%d", filename, (int)getpid());
-  fd = Uopen(tempfilename, O_WRONLY|O_CREAT, 0400);
+  temp_fn = string_copy(US "%s.XXXXXXX");
+  fd = mkstemp(CS temp_fn); /* modifies temp_fn */
   if (fd < 0)
-    return tls_error(string_open_failed(errno, "%s for writing", filename),
-      host, NULL);
+    return tls_error(US"Unable to open temp file", strerror(errno), NULL);
   (void)fchown(fd, exim_uid, exim_gid);   /* Probably not necessary */
 
-  /* export the parameters in a format that can be generated using GNUTLS'
-   * certtool or other programs.
-   *
-   * The commands for certtool are:
-   * $ certtool --generate-dh-params --bits 1024 > params
-   */
-
-  m.size = PARAM_SIZE;
+  DEBUG(D_tls) debug_printf("generating %d bits Diffie-Hellman key ...\n", dh_bits);
+  rc = gnutls_dh_params_generate2(dh_server_params, dh_bits);
+  exim_gnutls_err_check(US"gnutls_dh_params_generate2");
+
+  /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
+  and I confirmed that a NULL call to get the size first is how the GnuTLS
+  sample apps handle this. */
+
+  sz = 0;
+  m.data = NULL;
+  rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
+      m.data, &sz);
+  if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
+    exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
+  m.size = sz;
   m.data = malloc(m.size);
   if (m.data == NULL)
-    return tls_error(US"memory allocation failed", host, strerror(errno));
-
-  m.size = PARAM_SIZE;
-  ret = gnutls_dh_params_export_pkcs3(dh_params, GNUTLS_X509_FMT_PEM, m.data,
-    &m.size);
-  if (ret < 0)
-    return tls_error(US"DH params export", host, gnutls_strerror(ret));
-
-  m.size = Ustrlen(m.data);
-  errno = 0;
-  if (write(fd, m.data, m.size) != m.size || write(fd, "\n", 1) != 1)
-    return tls_error(US"TLS cache write failed", host, strerror(errno));
+    return tls_error(US"memory allocation failed", strerror(errno), NULL);
+  rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
+      m.data, &sz);
+  if (rc != GNUTLS_E_SUCCESS)
+    {
+    free(m.data);
+    exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real");
+    }
 
+  sz = write_to_fd_buf(fd, m.data, (size_t) m.size);
+  if (sz != m.size)
+    {
+    free(m.data);
+    return tls_error(US"TLS cache write D-H params failed",
+        strerror(errno), NULL);
+    }
   free(m.data);
-  (void)close(fd);
+  sz = write_to_fd_buf(fd, US"\n", 1);
+  if (sz != 1)
+    return tls_error(US"TLS cache write D-H params final newline failed",
+        strerror(errno), NULL);
+
+  rc = close(fd);
+  if (rc)
+    return tls_error(US"TLS cache write close() failed",
+        strerror(errno), NULL);
 
-  if (rename(CS tempfilename, CS filename) < 0)
-    return tls_error(string_sprintf("failed to rename %s as %s",
-      tempfilename, filename), host, strerror(errno));
+  if (Urename(temp_fn, filename) < 0)
+    return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
+          temp_fn, filename), strerror(errno), NULL);
 
-  DEBUG(D_tls) debug_printf("wrote D-H parameters to file %s\n", filename);
+  DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename);
   }
 
-DEBUG(D_tls) debug_printf("initialized D-H parameters\n");
+DEBUG(D_tls) debug_printf("initialized server D-H parameters\n");
 return OK;
 }
 
@@ -450,90 +542,116 @@ return OK;
 
 
 /*************************************************
-*            Initialize for GnuTLS               *
+*       Variables re-expanded post-SNI           *
 *************************************************/
 
-/* Called from both server and client code. In the case of a server, errors
-before actual TLS negotiation return DEFER.
+/* Called from both server and client code, via tls_init(), and also from
+the SNI callback after receiving an SNI, if tls_certificate includes "tls_sni".
+
+We can tell the two apart by state->received_sni being non-NULL in callback.
+
+The callback should not call us unless state->trigger_sni_changes is true,
+which we are responsible for setting on the first pass through.
 
 Arguments:
-  host            connected host, if client; NULL if server
-  certificate     certificate file
-  privatekey      private key file
-  cas             CA certs file
-  crl             CRL file
+  state           exim_gnutls_state_st *
 
 Returns:          OK/DEFER/FAIL
 */
 
 static int
-tls_init(host_item *host, uschar *certificate, uschar *privatekey, uschar *cas,
-  uschar *crl)
+tls_expand_session_files(exim_gnutls_state_st *state)
 {
 int rc;
-uschar *cert_expanded, *key_expanded, *cas_expanded, *crl_expanded;
-
-client_host = host;
+const host_item *host = state->host;  /* macro should be reconsidered? */
+uschar *saved_tls_certificate = NULL;
+uschar *saved_tls_privatekey = NULL;
+uschar *saved_tls_verify_certificates = NULL;
+uschar *saved_tls_crl = NULL;
+int cert_count;
+
+/* We check for tls_sni *before* expansion. */
+if (!state->host)
+  {
+  if (!state->received_sni)
+    {
+    if (Ustrstr(state->tls_certificate, US"tls_sni"))
+      {
+      DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
+      state->trigger_sni_changes = TRUE;
+      }
+    }
+  else
+    {
+    saved_tls_certificate = state->exp_tls_certificate;
+    saved_tls_privatekey = state->exp_tls_privatekey;
+    saved_tls_verify_certificates = state->exp_tls_verify_certificates;
+    saved_tls_crl = state->exp_tls_crl;
+    }
+  }
 
-rc = gnutls_global_init();
-if (rc < 0) return tls_error(US"tls-init", host, gnutls_strerror(rc));
+/* remember: expand_check_tlsvar() is expand_check() but fiddling with
+state members, assuming consistent naming; and expand_check() returns
+false if expansion failed, unless expansion was forced to fail. */
 
-/* Create D-H parameters, or read them from the cache file. This function does
-its own SMTP error messaging. */
+/* check if we at least have a certificate, before doing expensive
+D-H generation. */
 
-rc = init_dh(host);
-if (rc != OK) return rc;
+if (!expand_check_tlsvar(tls_certificate))
+  return DEFER;
 
-/* Create the credentials structure */
+/* certificate is mandatory in server, optional in client */
 
-rc = gnutls_certificate_allocate_credentials(&x509_cred);
-if (rc < 0)
-  return tls_error(US"certificate_allocate_credentials",
-    host, gnutls_strerror(rc));
-
-/* This stuff must be done for each session, because different certificates
-may be required for different sessions. */
+if ((state->exp_tls_certificate == NULL) ||
+    (*state->exp_tls_certificate == '\0'))
+  {
+  if (state->host == NULL)
+    return tls_error(US"no TLS server certificate is specified", NULL, NULL);
+  else
+    DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
+  }
 
-if (!expand_check(certificate, US"tls_certificate", &cert_expanded))
+if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey))
   return DEFER;
 
-key_expanded = NULL;
-if (privatekey != NULL)
+/* tls_privatekey is optional, defaulting to same file as certificate */
+
+if (state->tls_privatekey == NULL || *state->tls_privatekey == '\0')
   {
-  if (!expand_check(privatekey, US"tls_privatekey", &key_expanded))
-    return DEFER;
+  state->tls_privatekey = state->tls_certificate;
+  state->exp_tls_privatekey = state->exp_tls_certificate;
   }
 
-/* If expansion was forced to fail, key_expanded will be NULL. If the result of
-the expansion is an empty string, ignore it also, and assume that the private
-key is in the same file as the certificate. */
-
-if (key_expanded == NULL || *key_expanded == 0)
-  key_expanded = cert_expanded;
 
-/* Set the certificate and private keys */
-
-if (cert_expanded != NULL)
+if (state->exp_tls_certificate && *state->exp_tls_certificate)
   {
+  BOOL setit = TRUE;
   DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n",
-    cert_expanded, key_expanded);
-  rc = gnutls_certificate_set_x509_key_file(x509_cred, CS cert_expanded,
-    CS key_expanded, GNUTLS_X509_FMT_PEM);
-  if (rc < 0)
+      state->exp_tls_certificate, state->exp_tls_privatekey);
+
+  if (state->received_sni)
     {
-    uschar *msg = string_sprintf("cert/key setup: cert=%s key=%s",
-      cert_expanded, key_expanded);
-    return tls_error(msg, host, gnutls_strerror(rc));
+    if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) &&
+        (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
+      {
+      DEBUG(D_tls) debug_printf("cert and key unchanged with SNI.\n");
+      setit = FALSE;
+      }
+    else
+      {
+      DEBUG(D_tls) debug_printf("SNI changed cert/key pair.\n");
+      }
     }
-  }
 
-/* A certificate is mandatory in a server, but not in a client */
-
-else
-  {
-  if (host == NULL)
-    return tls_error(US"no TLS server certificate is specified", NULL, NULL);
-  DEBUG(D_tls) debug_printf("no TLS client certificate is specified\n");
+  if (setit)
+    {
+    rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
+        CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
+        GNUTLS_X509_FMT_PEM);
+    exim_gnutls_err_check(
+        string_sprintf("cert/key setup: cert=%s key=%s",
+          state->exp_tls_certificate, state->exp_tls_privatekey));
+    }
   }
 
 /* Set the trusted CAs file if one is provided, and then add the CRL if one is
@@ -542,359 +660,516 @@ error message is provided. However, if we just refrain from setting anything up
 in that case, certificate verification fails, which seems to be the correct
 behaviour. */
 
-if (cas != NULL)
+if (state->tls_verify_certificates && *state->tls_verify_certificates)
   {
   struct stat statbuf;
+  BOOL setit_vc = TRUE, setit_crl = TRUE;
 
-  if (!expand_check(cas, US"tls_verify_certificates", &cas_expanded))
+  if (!expand_check_tlsvar(tls_verify_certificates))
     return DEFER;
+  if (state->tls_crl && *state->tls_crl)
+    if (!expand_check_tlsvar(tls_crl))
+      return DEFER;
 
-  if (stat(CS cas_expanded, &statbuf) < 0)
+  if (state->received_sni)
+    {
+    if (Ustrcmp(state->exp_tls_verify_certificates, saved_tls_verify_certificates) == 0)
+      setit_vc = FALSE;
+    if (Ustrcmp(state->exp_tls_crl, saved_tls_crl) == 0)
+      setit_crl = FALSE;
+    }
+
+  /* nb: early exit; change if add more expansions to this function */
+  if (!(setit_vc || setit_crl))
+    return OK;
+
+  if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
     {
     log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
-      "(tls_verify_certificates): %s", cas_expanded, strerror(errno));
+        "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
+        strerror(errno));
+    return DEFER;
+    }
+
+  if (!S_ISREG(statbuf.st_mode))
+    {
+    DEBUG(D_tls)
+      debug_printf("verify certificates path is not a file: \"%s\"\n%s\n",
+          state->exp_tls_verify_certificates,
+          S_ISDIR(statbuf.st_mode)
+            ? " it's a directory, that's OpenSSL, this is GnuTLS"
+            : " (not a directory either)");
+    log_write(0, LOG_MAIN|LOG_PANIC,
+        "tls_verify_certificates \"%s\" is not a file",
+        state->exp_tls_verify_certificates);
     return DEFER;
     }
 
   DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
-    cas_expanded, statbuf.st_size);
+          state->exp_tls_verify_certificates, statbuf.st_size);
 
-  /* If the cert file is empty, there's no point in loading the CRL file. */
+  /* If the CA cert file is empty, there's no point in loading the CRL file,
+  as we aren't verifying, so checking for revocation is pointless. */
 
   if (statbuf.st_size > 0)
     {
-    rc = gnutls_certificate_set_x509_trust_file(x509_cred, CS cas_expanded,
-      GNUTLS_X509_FMT_PEM);
-    if (rc < 0) return tls_error(US"setup_certs", host, gnutls_strerror(rc));
-
-    if (crl != NULL && *crl != 0)
+    if (setit_vc)
       {
-      if (!expand_check(crl, US"tls_crl", &crl_expanded))
-        return DEFER;
-      DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl_expanded);
-      rc = gnutls_certificate_set_x509_crl_file(x509_cred, CS crl_expanded,
-        GNUTLS_X509_FMT_PEM);
-      if (rc < 0) return tls_error(US"CRL setup", host, gnutls_strerror(rc));
+      cert_count = gnutls_certificate_set_x509_trust_file(state->x509_cred,
+          CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
+      if (cert_count < 0)
+        {
+        rc = cert_count;
+        exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file");
+        }
+      DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
       }
-    }
-  }
 
-/* Associate the parameters with the x509 credentials structure. */
-
-gnutls_certificate_set_dh_params(x509_cred, dh_params);
+    if (setit_crl && state->tls_crl && *state->tls_crl)
+      {
+      if (state->exp_tls_crl && *state->exp_tls_crl)
+        {
+        DEBUG(D_tls) debug_printf("loading CRL file = %s\n", state->exp_tls_crl);
+        rc = gnutls_certificate_set_x509_crl_file(state->x509_cred,
+            CS state->exp_tls_crl, GNUTLS_X509_FMT_PEM);
+        exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
+        }
+      }
+    } /* statbuf.st_size */
+  } /* tls_verify_certificates */
 
-DEBUG(D_tls) debug_printf("initialized certificate stuff\n");
 return OK;
+/* also above, during verify_certificates/crl, during SNI, if unchanged */
 }
 
 
 
 
 /*************************************************
-*           Remove from a priority list          *
+*            Initialize for GnuTLS               *
 *************************************************/
 
-/* Cautiously written so that it will remove duplicates if present.
+/* Called from both server and client code. In the case of a server, errors
+before actual TLS negotiation return DEFER.
 
 Arguments:
-  list         a zero-terminated list
-  remove_list  a zero-terminated list to be removed
+  host            connected host, if client; NULL if server
+  certificate     certificate file
+  privatekey      private key file
+  sni             TLS SNI to send, sometimes when client; else NULL
+  cas             CA certs file
+  crl             CRL file
+  require_ciphers tls_require_ciphers setting
 
-Returns:       nothing
+Returns:          OK/DEFER/FAIL
 */
 
-static void
-remove_priority(int *list, int *remove_list)
+static int
+tls_init(
+    const host_item *host,
+    const uschar *certificate,
+    const uschar *privatekey,
+    const uschar *sni,
+    const uschar *cas,
+    const uschar *crl,
+    const uschar *require_ciphers,
+    exim_gnutls_state_st **caller_state)
 {
-for (; *remove_list != 0; remove_list++)
+exim_gnutls_state_st *state;
+int rc;
+size_t sz;
+const char *errpos;
+uschar *p;
+BOOL want_default_priorities;
+
+if (!exim_gnutls_base_init_done)
   {
-  int *p = list;
-  while (*p != 0)
+  DEBUG(D_tls) debug_printf("GnuTLS global init required.\n");
+
+  rc = gnutls_global_init();
+  exim_gnutls_err_check(US"gnutls_global_init");
+
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+  DEBUG(D_tls)
     {
-    if (*p == *remove_list)
-      {
-      int *pp = p;
-      do { pp[0] = pp[1]; pp++; } while (*pp != 0);
-      }
-    else p++;
+    gnutls_global_set_log_function(exim_gnutls_logger_cb);
+    /* arbitrarily chosen level; bump upto 9 for more */
+    gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
     }
+#endif
+
+  exim_gnutls_base_init_done = TRUE;
   }
-}
 
+if (host)
+  {
+  state = &state_client;
+  memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+  DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
+  rc = gnutls_init(&state->session, GNUTLS_CLIENT);
+  }
+else
+  {
+  state = &state_server;
+  memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+  DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
+  rc = gnutls_init(&state->session, GNUTLS_SERVER);
+  }
+exim_gnutls_err_check(US"gnutls_init");
 
+state->host = host;
 
-/*************************************************
-*            Add to a priority list              *
-*************************************************/
+state->tls_certificate = certificate;
+state->tls_privatekey = privatekey;
+state->tls_sni = sni;
+state->tls_verify_certificates = cas;
+state->tls_crl = crl;
 
-/* Cautiously written to check the list size
+rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
+exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
 
-Arguments:
-  list         a zero-terminated list
-  list_max     maximum offset in the list
-  add_list     a zero-terminated list to be added
+/* This handles the variables that might get re-expanded after TLS SNI;
+that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
 
-Returns:       TRUE if OK; FALSE if list overflows
-*/
+DEBUG(D_tls)
+  debug_printf("Expanding various TLS configuration options for session credentials.\n");
+rc = tls_expand_session_files(state);
+if (rc != OK) return rc;
 
-static BOOL
-add_priority(int *list, int list_max, int *add_list)
-{
-int next = 0;
-while (list[next] != 0) next++;
-while (*add_list != 0)
+/* Create D-H parameters, or read them from the cache file. This function does
+its own SMTP error messaging. This only happens for the server, TLS D-H ignores
+client-side params. */
+
+if (!host)
   {
-  if (next >= list_max) return FALSE;
-  list[next++] = *add_list++;
+  rc = init_server_dh();
+  if (rc != OK) return rc;
+  gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params);
   }
-list[next] = 0;
-return TRUE;
-}
-
-
-
-/*************************************************
-*          Adjust a priority list                *
-*************************************************/
-
-/* This function is called to adjust the lists of cipher algorithms, MAC
-algorithms, key-exchange methods, and protocols.
 
-Arguments:
-  plist       the appropriate priority list
-  psize       the length of the list
-  s           the configuation string
-  index       the index of recognized strings
-  isize       the length of the index
+/* Link the credentials to the session. */
 
+rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
+exim_gnutls_err_check(US"gnutls_credentials_set");
 
-  which       text for an error message
+/* set SNI in client, only */
+if (host)
+  {
+  if (!expand_check_tlsvar(tls_sni))
+    return DEFER;
+  if (state->exp_tls_sni && *state->exp_tls_sni)
+    {
+    DEBUG(D_tls)
+      debug_printf("Setting TLS client SNI to \"%s\"\n", state->exp_tls_sni);
+    sz = Ustrlen(state->exp_tls_sni);
+    rc = gnutls_server_name_set(state->session,
+        GNUTLS_NAME_DNS, state->exp_tls_sni, sz);
+    exim_gnutls_err_check(US"gnutls_server_name_set");
+    }
+  }
+else if (state->tls_sni)
+  DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
+      "have an SNI set for a client [%s]\n", state->tls_sni);
 
-Returns:      FALSE if the table overflows, else TRUE
-*/
+/* This is the priority string support,
+http://www.gnu.org/software/gnutls/manual/html_node/Priority-Strings.html
+and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols.
+This was backwards incompatible, but means Exim no longer needs to track
+all algorithms and provide string forms for them. */
 
-static BOOL
-set_priority(int *plist, int psize, uschar *s, pri_item *index, int isize,
-   uschar *which)
-{
-int sep = 0;
-BOOL first = TRUE;
-uschar *t;
+want_default_priorities = TRUE;
 
-while ((t = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)) != NULL)
+if (state->tls_require_ciphers && *state->tls_require_ciphers)
   {
-  int i;
-  BOOL exclude = t[0] == '!';
-  if (first && !exclude) plist[0] = 0;
-  first = FALSE;
-  for (i = 0; i < isize; i++)
+  if (!expand_check_tlsvar(tls_require_ciphers))
+    return DEFER;
+  if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
     {
-    uschar *ss = strstric(t, index[i].name, FALSE);
-    if (ss != NULL)
-      {
-      uschar *endss = ss + Ustrlen(index[i].name);
-      if ((ss == t || !isalnum(ss[-1])) && !isalnum(*endss))
-        {
-        if (exclude)
-          remove_priority(plist, index[i].values);
-        else
-          {
-          if (!add_priority(plist, psize, index[i].values))
-            {
-            log_write(0, LOG_MAIN|LOG_PANIC, "GnuTLS init failed: %s "
-              "priority table overflow", which);
-            return FALSE;
-            }
-          }
-        }
-      }
+    DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n",
+        state->exp_tls_require_ciphers);
+
+    rc = gnutls_priority_init(&state->priority_cache,
+        CS state->exp_tls_require_ciphers, &errpos);
+    want_default_priorities = FALSE;
+    p = state->exp_tls_require_ciphers;
     }
   }
+if (want_default_priorities)
+  {
+  rc = gnutls_priority_init(&state->priority_cache,
+      exim_default_gnutls_priority, &errpos);
+  p = US exim_default_gnutls_priority;
+  }
 
-DEBUG(D_tls)
+exim_gnutls_err_check(string_sprintf(
+      "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
+      p, errpos - CS p, errpos));
+
+rc = gnutls_priority_set(state->session, state->priority_cache);
+exim_gnutls_err_check(US"gnutls_priority_set");
+
+gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);
+
+/* Reduce security in favour of increased compatibility, if the admin
+decides to make that trade-off. */
+if (gnutls_compat_mode)
   {
-  int *ptr = plist;
-  debug_printf("adjusted %s priorities:", which);
-  while (*ptr != 0) debug_printf(" %d", *ptr++);
-  debug_printf("\n");
+#if LIBGNUTLS_VERSION_NUMBER >= 0x020104
+  DEBUG(D_tls) debug_printf("lowering GnuTLS security, compatibility mode\n");
+  gnutls_session_enable_compatibility_mode(state->session);
+#else
+  DEBUG(D_tls) debug_printf("Unable to set gnutls_compat_mode - GnuTLS version too old\n");
+#endif
   }
 
-return TRUE;
+*caller_state = state;
+/* needs to happen before callbacks during handshake */
+current_global_tls_state = state;
+return OK;
 }
 
 
 
 
 /*************************************************
-*        Initialize a single GNUTLS session      *
+*            Extract peer information            *
 *************************************************/
 
-/* Set the algorithm, the db backend, whether to request certificates etc.
-
-TLS in Exim was first implemented using OpenSSL. This has a function to which
-you pass a list of cipher suites that are permitted/not permitted. GnuTLS works
-differently. It operates using priority lists for the different components of
-cipher suites.
-
-For compatibility of configuration, we scan a list of cipher suites and set
-priorities therefrom. However, at the moment, we pay attention only to the bulk
-cipher.
+/* Called from both server and client code.
+Only this is allowed to set state->peerdn and we use that to detect double-calls.
 
 Arguments:
-  side         one of GNUTLS_SERVER, GNUTLS_CLIENT
-  expciphers   expanded ciphers list or NULL
-  expmac       expanded MAC list or NULL
-  expkx        expanded key-exchange list or NULL
-  expproto     expanded protocol list or NULL
+  state           exim_gnutls_state_st *
 
-Returns:  a gnutls_session, or NULL if there is a problem
+Returns:          OK/DEFER/FAIL
 */
 
-static gnutls_session
-tls_session_init(int side, uschar *expciphers, uschar *expmac, uschar *expkx,
-  uschar *expproto)
+static int
+peer_status(exim_gnutls_state_st *state)
 {
-gnutls_session session;
+const gnutls_datum *cert_list;
+int rc;
+unsigned int cert_list_size = 0;
+gnutls_certificate_type_t ct;
+gnutls_x509_crt_t crt;
+uschar *dn_buf;
+size_t sz;
 
-gnutls_init(&session, side);
+if (state->peerdn)
+  return OK;
 
-/* Initialize the lists of permitted protocols, key-exchange methods, ciphers,
-and MACs. */
+state->peerdn = US"unknown";
 
-memcpy(cipher_priority, default_cipher_priority, sizeof(cipher_priority));
-memcpy(mac_priority, default_mac_priority, sizeof(mac_priority));
-memcpy(kx_priority, default_kx_priority, sizeof(kx_priority));
-memcpy(proto_priority, default_proto_priority, sizeof(proto_priority));
+cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
 
-/* The names OpenSSL uses in tls_require_ciphers are of the form DES-CBC3-SHA,
-using hyphen separators. GnuTLS uses underscore separators. So that I can use
-either form for tls_require_ciphers in my tests, and also for general
-convenience, we turn hyphens into underscores before scanning the list. */
+if (cert_list == NULL || cert_list_size == 0)
+  {
+  state->peerdn = US"unknown (no certificate)";
+  DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
+      cert_list, cert_list_size);
+  if (state->verify_requirement == VERIFY_REQUIRED)
+    return tls_error(US"certificate verification failed",
+        "no certificate received from peer", state->host);
+  return OK;
+  }
 
-if (expciphers != NULL)
+ct = gnutls_certificate_type_get(state->session);
+if (ct != GNUTLS_CRT_X509)
   {
-  uschar *s = expciphers;
-  while (*s != 0) { if (*s == '-') *s = '_'; s++; }
+  const char *ctn = gnutls_certificate_type_get_name(ct);
+  state->peerdn = string_sprintf("unknown (type %s)", ctn);
+  DEBUG(D_tls)
+    debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
+  if (state->verify_requirement == VERIFY_REQUIRED)
+    return tls_error(US"certificate verification not possible, unhandled type",
+        ctn, state->host);
+  return OK;
   }
 
-if ((expciphers != NULL &&
-      !set_priority(cipher_priority, sizeof(cipher_priority)/sizeof(int),
-        expciphers, cipher_index, sizeof(cipher_index)/sizeof(pri_item),
-        US"cipher")) ||
-    (expmac != NULL &&
-      !set_priority(mac_priority, sizeof(mac_priority)/sizeof(int),
-        expmac, mac_index, sizeof(mac_index)/sizeof(pri_item),
-        US"MAC")) ||
-    (expkx != NULL &&
-      !set_priority(kx_priority, sizeof(kx_priority)/sizeof(int),
-        expkx, kx_index, sizeof(kx_index)/sizeof(pri_item),
-        US"key-exchange")) ||
-    (expproto != NULL &&
-      !set_priority(proto_priority, sizeof(proto_priority)/sizeof(int),
-        expproto, proto_index, sizeof(proto_index)/sizeof(pri_item),
-        US"protocol")))
+#define exim_gnutls_peer_err(Label) do { \
+  if (rc != GNUTLS_E_SUCCESS) { \
+    DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", (Label), gnutls_strerror(rc)); \
+    if (state->verify_requirement == VERIFY_REQUIRED) { return tls_error((Label), gnutls_strerror(rc), state->host); } \
+    return OK; } } while (0)
+
+rc = gnutls_x509_crt_init(&crt);
+exim_gnutls_peer_err(US"gnutls_x509_crt_init (crt)");
+
+rc = gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER);
+exim_gnutls_peer_err(US"failed to import certificate [gnutls_x509_crt_import(cert 0)]");
+sz = 0;
+rc = gnutls_x509_crt_get_dn(crt, NULL, &sz);
+if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
   {
-  gnutls_deinit(session);
-  return NULL;
+  exim_gnutls_peer_err(US"getting size for cert DN failed");
+  return FAIL; /* should not happen */
   }
+dn_buf = store_get_perm(sz);
+rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
+exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
+state->peerdn = dn_buf;
+
+return OK;
+#undef exim_gnutls_peer_err
+}
 
-/* Define the various priorities */
 
-gnutls_cipher_set_priority(session, cipher_priority);
-gnutls_compression_set_priority(session, comp_priority);
-gnutls_kx_set_priority(session, kx_priority);
-gnutls_protocol_set_priority(session, proto_priority);
-gnutls_mac_set_priority(session, mac_priority);
 
-gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
 
-gnutls_dh_set_prime_bits(session, DH_BITS);
+/*************************************************
+*            Verify peer certificate             *
+*************************************************/
 
-/* Request or demand a certificate of the peer, as configured. This will
-happen only in a server. */
+/* Called from both server and client code.
+*Should* be using a callback registered with
+gnutls_certificate_set_verify_function() to fail the handshake if we dislike
+the peer information, but that's too new for some OSes.
 
-if (verify_requirement != VERIFY_NONE)
-  gnutls_certificate_server_set_request(session,
-    (verify_requirement == VERIFY_OPTIONAL)?
-      GNUTLS_CERT_REQUEST : GNUTLS_CERT_REQUIRE);
+Arguments:
+  state           exim_gnutls_state_st *
+  error           where to put an error message
 
-gnutls_db_set_cache_expiration(session, ssl_session_timeout);
+Returns:
+  FALSE     if the session should be rejected
+  TRUE      if the cert is okay or we just don't care
+*/
 
-/* Reduce security in favour of increased compatibility, if the admin
-decides to make that trade-off. */
-if (gnutls_compat_mode)
+static BOOL
+verify_certificate(exim_gnutls_state_st *state, const char **error)
+{
+int rc;
+unsigned int verify;
+
+*error = NULL;
+
+rc = peer_status(state);
+if (rc != OK)
   {
-#if LIBGNUTLS_VERSION_NUMBER >= 0x020104
-  DEBUG(D_tls) debug_printf("lowering GnuTLS security, compatibility mode\n");
-  gnutls_session_enable_compatibility_mode(session);
-#else
-  DEBUG(D_tls) debug_printf("Unable to set gnutls_compat_mode - GnuTLS version too old\n");
-#endif
+  verify = GNUTLS_CERT_INVALID;
+  *error = "not supplied";
+  }
+else
+  {
+  rc = gnutls_certificate_verify_peers2(state->session, &verify);
   }
 
-DEBUG(D_tls) debug_printf("initialized GnuTLS session\n");
-return session;
-}
+/* Handle the result of verification. INVALID seems to be set as well
+as REVOKED, but leave the test for both. */
 
+if ((rc < 0) || (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) != 0)
+  {
+  state->peer_cert_verified = FALSE;
+  if (*error == NULL)
+    *error = ((verify & GNUTLS_CERT_REVOKED) != 0) ? "revoked" : "invalid";
 
+  DEBUG(D_tls)
+    debug_printf("TLS certificate verification failed (%s): peerdn=%s\n",
+        *error, state->peerdn);
 
-/*************************************************
-*           Get name of cipher in use            *
-*************************************************/
+  if (state->verify_requirement == VERIFY_REQUIRED)
+    {
+    gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+    return FALSE;
+    }
+  DEBUG(D_tls)
+    debug_printf("TLS verify failure overriden (host in tls_try_verify_hosts)\n");
+  }
+else
+  {
+  state->peer_cert_verified = TRUE;
+  DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n", state->peerdn);
+  }
 
-/* The answer is left in a static buffer, and tls_cipher is set to point
-to it.
+tls_peerdn = state->peerdn;
 
-Argument:   pointer to a GnuTLS session
-Returns:    nothing
-*/
+return TRUE;
+}
 
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Callbacks */
+
+/* Logging function which can be registered with
+ *   gnutls_global_set_log_function()
+ *   gnutls_global_set_log_level() 0..9
+ */
 static void
-construct_cipher_name(gnutls_session session)
+exim_gnutls_logger_cb(int level, const char *message)
 {
-static uschar cipherbuf[256];
-uschar *ver;
-int c, kx, mac;
-#ifdef GNUTLS_CB_TLS_UNIQUE
-int rc;
-gnutls_datum_t channel;
-#endif
+  DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s\n", level, message);
+}
 
-ver = string_copy(
-  US gnutls_protocol_get_name(gnutls_protocol_get_version(session)));
-if (Ustrncmp(ver, "TLS ", 4) == 0) ver[3] = '-';   /* Don't want space */
 
-c = gnutls_cipher_get(session);
-/* returns size in "bytes" */
-tls_bits = gnutls_cipher_get_key_size(c) * 8;
+/* Called after client hello, should handle SNI work.
+This will always set tls_sni (state->received_sni) if available,
+and may trigger presenting different certificates,
+if state->trigger_sni_changes is TRUE.
 
-mac = gnutls_mac_get(session);
-kx = gnutls_kx_get(session);
+Should be registered with
+  gnutls_handshake_set_post_client_hello_function()
 
-string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
-  gnutls_cipher_suite_get_name(kx, c, mac), tls_bits);
-tls_cipher = cipherbuf;
+"This callback must return 0 on success or a gnutls error code to terminate the
+handshake.".
 
-DEBUG(D_tls) debug_printf("cipher: %s\n", cipherbuf);
+For inability to get SNI information, we return 0.
+We only return non-zero if re-setup failed.
+*/
 
-if (tls_channelbinding_b64)
-  free(tls_channelbinding_b64);
-tls_channelbinding_b64 = NULL;
+static int
+exim_sni_handling_cb(gnutls_session_t session)
+{
+char sni_name[MAX_HOST_LEN];
+size_t data_len = MAX_HOST_LEN;
+exim_gnutls_state_st *state = current_global_tls_state;
+unsigned int sni_type;
+int rc, old_pool;
+
+rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
+exim_gnutls_err_debugreturn0("gnutls_server_name_get()");
+if (sni_type != GNUTLS_NAME_DNS)
+  {
+  DEBUG(D_tls) debug_printf("TLS: ignoring SNI of unhandled type %u\n", sni_type);
+  return 0;
+  }
 
-#ifdef GNUTLS_CB_TLS_UNIQUE
-channel = { NULL, 0 };
-rc = gnutls_session_channel_binding(session, GNUTLS_CB_TLS_UNIQUE, &channel);
-if (rc) {
-  DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc));
-} else {
-  tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size);
-  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
-}
-#endif
+/* We now have a UTF-8 string in sni_name */
+old_pool = store_pool;
+store_pool = POOL_PERM;
+state->received_sni = string_copyn(US sni_name, data_len);
+store_pool = old_pool;
+
+/* We set this one now so that variable expansions below will work */
+tls_sni = state->received_sni;
+
+DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", sni_name,
+    state->trigger_sni_changes ? "" : " (unused for certificate selection)");
+
+if (!state->trigger_sni_changes)
+  return 0;
+
+rc = tls_expand_session_files(state);
+if (rc != OK)
+  {
+  /* If the setup of certs/etc failed before handshake, TLS would not have
+  been offered.  The best we can do now is abort. */
+  return GNUTLS_E_APPLICATION_ERROR_MIN;
+  }
+
+rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
+return (rc == GNUTLS_E_SUCCESS) ? 0 : rc;
 }
 
 
 
+
+/* ------------------------------------------------------------------------ */
+/* Exported functions */
+
+
+
+
 /*************************************************
 *       Start a TLS session in a server          *
 *************************************************/
@@ -905,9 +1180,6 @@ a TLS session.
 
 Arguments:
   require_ciphers  list of allowed ciphers or NULL
-  require_mac      list of allowed MACs or NULL
-  require_kx       list of allowed key_exchange methods or NULL
-  require_proto    list of allowed protocols or NULL
 
 Returns:           OK on success
                    DEFER for errors before the start of the negotiation
@@ -916,21 +1188,18 @@ Returns:           OK on success
 */
 
 int
-tls_server_start(uschar *require_ciphers, uschar *require_mac,
-  uschar *require_kx, uschar *require_proto)
+tls_server_start(const uschar *require_ciphers)
 {
 int rc;
 const char *error;
-uschar *expciphers = NULL;
-uschar *expmac = NULL;
-uschar *expkx = NULL;
-uschar *expproto = NULL;
+exim_gnutls_state_st *state = NULL;
 
 /* Check for previous activation */
+/* nb: this will not be TLS callout safe, needs reworking as part of that. */
 
 if (tls_active >= 0)
   {
-  tls_error("STARTTLS received after TLS started", NULL, "");
+  tls_error(US"STARTTLS received after TLS started", "", NULL);
   smtp_printf("554 Already in TLS\r\n");
   return FAIL;
   }
@@ -938,36 +1207,40 @@ if (tls_active >= 0)
 /* Initialize the library. If it fails, it will already have logged the error
 and sent an SMTP response. */
 
-DEBUG(D_tls) debug_printf("initializing GnuTLS as a server\n");
+DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
 
-rc = tls_init(NULL, tls_certificate, tls_privatekey, tls_verify_certificates,
-  tls_crl);
+rc = tls_init(NULL, tls_certificate, tls_privatekey,
+    NULL, tls_verify_certificates, tls_crl,
+    require_ciphers, &state);
 if (rc != OK) return rc;
 
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers) ||
-    !expand_check(require_mac, US"gnutls_require_mac", &expmac) ||
-    !expand_check(require_kx, US"gnutls_require_kx", &expkx) ||
-    !expand_check(require_proto, US"gnutls_require_proto", &expproto))
-  return FAIL;
-
 /* If this is a host for which certificate verification is mandatory or
 optional, set up appropriately. */
 
-tls_certificate_verified = FALSE;
-verify_requirement = VERIFY_NONE;
-
 if (verify_check_host(&tls_verify_hosts) == OK)
-  verify_requirement = VERIFY_REQUIRED;
+  {
+  DEBUG(D_tls) debug_printf("TLS: a client certificate will be required.\n");
+  state->verify_requirement = VERIFY_REQUIRED;
+  gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+  }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
-  verify_requirement = VERIFY_OPTIONAL;
+  {
+  DEBUG(D_tls) debug_printf("TLS: a client certificate will be requested but not required.\n");
+  state->verify_requirement = VERIFY_OPTIONAL;
+  gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
+  }
+else
+  {
+  DEBUG(D_tls) debug_printf("TLS: a client certificate will not be requested.\n");
+  state->verify_requirement = VERIFY_NONE;
+  gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
+  }
 
-/* Prepare for new connection */
+/* Register SNI handling; always, even if not in tls_certificate, so that the
+expansion variable $tls_sni is always available. */
 
-tls_session = tls_session_init(GNUTLS_SERVER, expciphers, expmac, expkx,
-  expproto);
-if (tls_session == NULL)
-  return tls_error(US"tls_session_init", NULL,
-    gnutls_strerror(GNUTLS_E_MEMORY_ERROR));
+gnutls_handshake_set_post_client_hello_function(state->session,
+    exim_sni_handling_cb);
 
 /* Set context and tell client to go ahead, except in the case of TLS startup
 on connection, where outputting anything now upsets the clients and tends to
@@ -984,19 +1257,24 @@ if (!tls_on_connect)
 /* Now negotiate the TLS session. We put our own timer on it, since it seems
 that the GnuTLS library doesn't. */
 
-gnutls_transport_set_ptr2(tls_session, (gnutls_transport_ptr)fileno(smtp_in),
-                                       (gnutls_transport_ptr)fileno(smtp_out));
+gnutls_transport_set_ptr2(state->session,
+    (gnutls_transport_ptr)fileno(smtp_in),
+    (gnutls_transport_ptr)fileno(smtp_out));
+state->fd_in = fileno(smtp_in);
+state->fd_out = fileno(smtp_out);
 
 sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-rc = gnutls_handshake(tls_session);
+do
+  {
+  rc = gnutls_handshake(state->session);
+  } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
 alarm(0);
 
-if (rc < 0)
+if (rc != GNUTLS_E_SUCCESS)
   {
-  tls_error(US"gnutls_handshake", NULL,
-    sigalrm_seen ? "timed out" : gnutls_strerror(rc));
-
+  tls_error(US"gnutls_handshake",
+      sigalrm_seen ? "timed out" : gnutls_strerror(rc), NULL);
   /* It seems that, except in the case of a timeout, we have to close the
   connection right here; otherwise if the other end is running OpenSSL it hangs
   until the server times out. */
@@ -1012,21 +1290,39 @@ if (rc < 0)
 
 DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
-if (verify_requirement != VERIFY_NONE &&
-     !verify_certificate(tls_session, &error))
+/* Verify after the fact */
+
+if (state->verify_requirement != VERIFY_NONE)
   {
-  tls_error(US"certificate verification failed", NULL, error);
-  return FAIL;
+  if (!verify_certificate(state, &error))
+    {
+    if (state->verify_requirement == VERIFY_OPTIONAL)
+      {
+      DEBUG(D_tls)
+        debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
+            error);
+      }
+    else
+      {
+      tls_error(US"certificate verification failed", error, NULL);
+      return FAIL;
+      }
+    }
   }
 
-construct_cipher_name(tls_session);
+/* Figure out peer DN, and if authenticated, etc. */
+
+rc = peer_status(state);
+if (rc != OK) return rc;
+
+/* Sets various Exim expansion variables; always safe within server */
+
+extract_exim_vars_from_tls_state(state);
 
 /* TLS has been set up. Adjust the input functions to read via TLS,
 and initialize appropriately. */
 
-ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
-ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
-ssl_xfer_eof = ssl_xfer_error = 0;
+state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 
 receive_getc = tls_getc;
 receive_ungetc = tls_ungetc;
@@ -1034,8 +1330,6 @@ receive_feof = tls_feof;
 receive_ferror = tls_ferror;
 receive_smtp_buffered = tls_smtp_buffered;
 
-tls_active = fileno(smtp_out);
-
 return OK;
 }
 
@@ -1052,16 +1346,13 @@ Arguments:
   fd                the fd of the connection
   host              connected host (for messages)
   addr              the first address (not used)
-  dhparam           DH parameter file
+  dhparam           DH parameter file (ignored, we're a client)
   certificate       certificate file
   privatekey        private key file
   sni               TLS SNI to send to remote host
   verify_certs      file for certificate verify
   verify_crl        CRL for verify
   require_ciphers   list of allowed ciphers or NULL
-  require_mac       list of allowed MACs or NULL
-  require_kx        list of allowed key_exchange methods or NULL
-  require_proto     list of allowed protocols or NULL
   timeout           startup timeout
 
 Returns:            OK/DEFER/FAIL (because using common functions),
@@ -1069,114 +1360,116 @@ Returns:            OK/DEFER/FAIL (because using common functions),
 */
 
 int
-tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
-  uschar *certificate, uschar *privatekey, uschar *sni ARG_UNUSED,
-  uschar *verify_certs, uschar *verify_crl,
-  uschar *require_ciphers, uschar *require_mac,
-  uschar *require_kx, uschar *require_proto, int timeout)
+tls_client_start(int fd, host_item *host,
+    address_item *addr ARG_UNUSED, uschar *dhparam ARG_UNUSED,
+    uschar *certificate, uschar *privatekey, uschar *sni,
+    uschar *verify_certs, uschar *verify_crl,
+    uschar *require_ciphers, int timeout)
 {
-const gnutls_datum *server_certs;
-uschar *expciphers = NULL;
-uschar *expmac = NULL;
-uschar *expkx = NULL;
-uschar *expproto = NULL;
-const char *error;
-unsigned int server_certs_size;
 int rc;
+const char *error;
+exim_gnutls_state_st *state = NULL;
 
-DEBUG(D_tls) debug_printf("initializing GnuTLS as a client\n");
+DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
 
-verify_requirement = (verify_certs == NULL)? VERIFY_NONE : VERIFY_REQUIRED;
-rc = tls_init(host, certificate, privatekey, verify_certs, verify_crl);
+rc = tls_init(host, certificate, privatekey,
+    sni, verify_certs, verify_crl, require_ciphers, &state);
 if (rc != OK) return rc;
 
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers) ||
-    !expand_check(require_mac, US"gnutls_require_mac", &expmac) ||
-    !expand_check(require_kx, US"gnutls_require_kx", &expkx) ||
-    !expand_check(require_proto, US"gnutls_require_proto", &expproto))
-  return FAIL;
-
-tls_session = tls_session_init(GNUTLS_CLIENT, expciphers, expmac, expkx,
-  expproto);
+gnutls_dh_set_prime_bits(state->session, EXIM_CLIENT_DH_MIN_BITS);
 
-if (tls_session == NULL)
-  return tls_error(US "tls_session_init", host,
-    gnutls_strerror(GNUTLS_E_MEMORY_ERROR));
+if (verify_certs == NULL)
+  {
+  DEBUG(D_tls) debug_printf("TLS: server certificate verification not required\n");
+  state->verify_requirement = VERIFY_NONE;
+  /* we still ask for it, to log it, etc */
+  gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
+  }
+else
+  {
+  DEBUG(D_tls) debug_printf("TLS: server certificate verification required\n");
+  state->verify_requirement = VERIFY_REQUIRED;
+  gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+  }
 
-gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)fd);
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd);
+state->fd_in = fd;
+state->fd_out = fd;
 
 /* There doesn't seem to be a built-in timeout on connection. */
 
 sigalrm_seen = FALSE;
 alarm(timeout);
-rc = gnutls_handshake(tls_session);
+do
+  {
+  rc = gnutls_handshake(state->session);
+  } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
 alarm(0);
 
-if (rc < 0)
-  return tls_error(US "gnutls_handshake", host,
-    sigalrm_seen ? "timed out" : gnutls_strerror(rc));
+DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
-server_certs = gnutls_certificate_get_peers(tls_session, &server_certs_size);
+/* Verify late */
 
-if (server_certs != NULL)
-  {
-  uschar buff[1024];
-  gnutls_x509_crt gcert;
+if (state->verify_requirement != VERIFY_NONE &&
+    !verify_certificate(state, &error))
+  return tls_error(US"certificate verification failed", error, state->host);
 
-  gnutls_x509_crt_init(&gcert);
-  tls_peerdn = US"unknown";
+/* Figure out peer DN, and if authenticated, etc. */
 
-  if (gnutls_x509_crt_import(gcert, server_certs, GNUTLS_X509_FMT_DER) == 0)
-    {
-    size_t bufsize = sizeof(buff);
-    if (gnutls_x509_crt_get_dn(gcert, CS buff, &bufsize) >= 0)
-      tls_peerdn = string_copy_malloc(buff);
-    }
-  }
+rc = peer_status(state);
+if (rc != OK) return rc;
 
-/* Should we also verify the hostname here? */
+/* Sets various Exim expansion variables; always safe within server */
 
-if (verify_requirement != VERIFY_NONE &&
-      !verify_certificate(tls_session, &error))
-  return tls_error(US"certificate verification failed", host, error);
+extract_exim_vars_from_tls_state(state);
 
-construct_cipher_name(tls_session);    /* Sets tls_cipher */
-tls_active = fd;
 return OK;
 }
 
 
 
+
 /*************************************************
-*    Deal with logging errors during I/O         *
+*         Close down a TLS session               *
 *************************************************/
 
-/* We have to get the identity of the peer from saved data.
-
-Argument:
-  ec       the GnuTLS error code, or 0 if it's a local error
-  when     text identifying read or write
-  text     local error text when ec is 0
+/* This is also called from within a delivery subprocess forked from the
+daemon, to shut down the TLS library, without actually doing a shutdown (which
+would tamper with the TLS session in the parent process).
 
-Returns:   nothing
+Arguments:   TRUE if gnutls_bye is to be called
+Returns:     nothing
 */
 
-static void
-record_io_error(int ec, uschar *when, uschar *text)
+void
+tls_close(BOOL shutdown)
 {
-const char *msg;
+exim_gnutls_state_st *state = current_global_tls_state;
 
-if (ec == GNUTLS_E_FATAL_ALERT_RECEIVED)
-  msg = string_sprintf("%s: %s", gnutls_strerror(ec),
-    gnutls_alert_get_name(gnutls_alert_get(tls_session)));
-else
-  msg = gnutls_strerror(ec);
+if (tls_active < 0) return;  /* TLS was not active */
+
+if (shutdown)
+  {
+  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
+  gnutls_bye(state->session, GNUTLS_SHUT_WR);
+  }
+
+gnutls_deinit(state->session);
+
+memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+
+if ((state_server.session == NULL) && (state_client.session == NULL))
+  {
+  gnutls_global_deinit();
+  exim_gnutls_base_init_done = FALSE;
+  }
 
-tls_error(when, client_host, msg);
+tls_active = -1;
 }
 
 
 
+
 /*************************************************
 *            TLS version of getc                 *
 *************************************************/
@@ -1184,6 +1477,8 @@ tls_error(when, client_host, msg);
 /* This gets the next byte from the TLS input buffer. If the buffer is empty,
 it refills the buffer via the GnuTLS reading function.
 
+This feeds DKIM and should be used for all message-body reads.
+
 Arguments:  none
 Returns:    the next character or EOF
 */
@@ -1191,15 +1486,16 @@ Returns:    the next character or EOF
 int
 tls_getc(void)
 {
-if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
+exim_gnutls_state_st *state = current_global_tls_state;
+if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
   {
-  int inbytes;
+  ssize_t inbytes;
 
-  DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%lx, %lx, %u)\n",
-    (long) tls_session, (long) ssl_xfer_buffer, ssl_xfer_buffer_size);
+  DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
+    state->session, state->xfer_buffer, ssl_xfer_buffer_size);
 
   if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-  inbytes = gnutls_record_recv(tls_session, CS ssl_xfer_buffer,
+  inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
     ssl_xfer_buffer_size);
   alarm(0);
 
@@ -1217,9 +1513,12 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     receive_ferror = smtp_ferror;
     receive_smtp_buffered = smtp_buffered;
 
-    gnutls_deinit(tls_session);
-    tls_session = NULL;
+    gnutls_deinit(state->session);
+    state->session = NULL;
     tls_active = -1;
+    tls_bits = 0;
+    tls_certificate_verified = FALSE;
+    tls_channelbinding_b64 = NULL;
     tls_cipher = NULL;
     tls_peerdn = NULL;
 
@@ -1230,30 +1529,31 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
 
   else if (inbytes < 0)
     {
-    record_io_error(inbytes, US"recv", NULL);
-    ssl_xfer_error = 1;
+    record_io_error(state, (int) inbytes, US"recv", NULL);
+    state->xfer_error = 1;
     return EOF;
     }
 #ifndef DISABLE_DKIM
-  dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+  dkim_exim_verify_feed(state->xfer_buffer, inbytes);
 #endif
-  ssl_xfer_buffer_hwm = inbytes;
-  ssl_xfer_buffer_lwm = 0;
+  state->xfer_buffer_hwm = (int) inbytes;
+  state->xfer_buffer_lwm = 0;
   }
 
-
 /* Something in the buffer; return next uschar */
 
-return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
+return state->xfer_buffer[state->xfer_buffer_lwm++];
 }
 
 
 
+
 /*************************************************
 *          Read bytes from TLS channel           *
 *************************************************/
 
-/*
+/* This does not feed DKIM, so if the caller uses this for reading message body,
+then the caller must feed DKIM.
 Arguments:
   buff      buffer of data
   len       size of buffer
@@ -1265,24 +1565,36 @@ Returns:    the number of bytes read
 int
 tls_read(uschar *buff, size_t len)
 {
-int inbytes;
+exim_gnutls_state_st *state = current_global_tls_state;
+ssize_t inbytes;
 
-DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%lx, %lx, %u)\n",
-  (long) tls_session, (long) buff, len);
+if (len > INT_MAX)
+  len = INT_MAX;
 
-inbytes = gnutls_record_recv(tls_session, CS buff, len);
+if (state->xfer_buffer_lwm < state->xfer_buffer_hwm)
+  DEBUG(D_tls)
+    debug_printf("*** PROBABLY A BUG *** " \
+        "tls_read() called with data in the tls_getc() buffer, %d ignored\n",
+        state->xfer_buffer_hwm - state->xfer_buffer_lwm);
+
+DEBUG(D_tls)
+  debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n",
+      state->session, buff, len);
+
+inbytes = gnutls_record_recv(state->session, buff, len);
 if (inbytes > 0) return inbytes;
 if (inbytes == 0)
   {
   DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
   }
-else record_io_error(inbytes, US"recv", NULL);
+else record_io_error(state, (int)inbytes, US"recv", NULL);
 
 return -1;
 }
 
 
 
+
 /*************************************************
 *         Write bytes down TLS channel           *
 *************************************************/
@@ -1299,25 +1611,26 @@ Returns:    the number of bytes after a successful write,
 int
 tls_write(const uschar *buff, size_t len)
 {
-int outbytes;
-int left = len;
+ssize_t outbytes;
+size_t left = len;
+exim_gnutls_state_st *state = current_global_tls_state;
 
-DEBUG(D_tls) debug_printf("tls_do_write(%lx, %d)\n", (long) buff, left);
+DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left);
 while (left > 0)
   {
-  DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %lx, %d)\n", (long)buff,
-    left);
-  outbytes = gnutls_record_send(tls_session, CS buff, left);
+  DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
+      buff, left);
+  outbytes = gnutls_record_send(state->session, buff, left);
 
-  DEBUG(D_tls) debug_printf("outbytes=%d\n", outbytes);
+  DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
   if (outbytes < 0)
     {
-    record_io_error(outbytes, US"send", NULL);
+    record_io_error(state, outbytes, US"send", NULL);
     return -1;
     }
   if (outbytes == 0)
     {
-    record_io_error(0, US"send", US"TLS channel closed on write");
+    record_io_error(state, 0, US"send", US"TLS channel closed on write");
     return -1;
     }
 
@@ -1325,39 +1638,71 @@ while (left > 0)
   buff += outbytes;
   }
 
-return len;
+if (len > INT_MAX)
+  {
+  DEBUG(D_tls)
+    debug_printf("Whoops!  Wrote more bytes (" SIZE_T_FMT ") than INT_MAX\n",
+        len);
+  len = INT_MAX;
+  }
+
+return (int) len;
 }
 
 
 
+
 /*************************************************
-*         Close down a TLS session               *
+*            Random number generation            *
 *************************************************/
 
-/* This is also called from within a delivery subprocess forked from the
-daemon, to shut down the TLS library, without actually doing a shutdown (which
-would tamper with the TLS session in the parent process).
+/* Pseudo-random number generation.  The result is not expected to be
+cryptographically strong but not so weak that someone will shoot themselves
+in the foot using it as a nonce in input in some email header scheme or
+whatever weirdness they'll twist this into.  The result should handle fork()
+and avoid repeating sequences.  OpenSSL handles that for us.
 
-Arguments:   TRUE if gnutls_bye is to be called
-Returns:     nothing
+Arguments:
+  max       range maximum
+Returns     a random number in range [0, max-1]
 */
 
-void
-tls_close(BOOL shutdown)
+int
+vaguely_random_number(int max)
 {
-if (tls_active < 0) return;  /* TLS was not active */
-
-if (shutdown)
+unsigned int r;
+int i, needed_len;
+uschar *p;
+uschar smallbuf[sizeof(r)];
+
+if (max <= 1)
+  return 0;
+
+needed_len = sizeof(r);
+/* Don't take 8 times more entropy than needed if int is 8 octets and we were
+ * asked for a number less than 10. */
+for (r = max, i = 0; r; ++i)
+  r >>= 1;
+i = (i + 7) / 8;
+if (i < needed_len)
+  needed_len = i;
+
+i = gnutls_rnd(GNUTLS_RND_NONCE, smallbuf, needed_len);
+if (i < 0)
   {
-  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
-  gnutls_bye(tls_session, GNUTLS_SHUT_WR);
+  DEBUG(D_all) debug_printf("gnutls_rnd() failed, using fallback.\n");
+  return vaguely_random_number_fallback(max);
+  }
+r = 0;
+for (p = smallbuf; needed_len; --needed_len, ++p)
+  {
+  r *= 256;
+  r += *p;
   }
 
-gnutls_deinit(tls_session);
-tls_session = NULL;
-gnutls_global_deinit();
-
-tls_active = -1;
+/* We don't particularly care about weighted results; if someone wants
+ * smooth distribution and cares enough then they should submit a patch then. */
+return r % max;
 }
 
 
index 9ead7945d6d55e7898a09016a2debc802f5361eb..e485aa67d74e4fad0834ac405bd1498181863f95 100644 (file)
@@ -1006,11 +1006,6 @@ a TLS session.
 
 Arguments:
   require_ciphers   allowed ciphers
-  ------------------------------------------------------
-  require_mac      list of allowed MACs                 ) Not used
-  require_kx       list of allowed key_exchange methods )   for
-  require_proto    list of allowed protocols            ) OpenSSL
-  ------------------------------------------------------
 
 Returns:            OK on success
                     DEFER for errors before the start of the negotiation
@@ -1019,8 +1014,7 @@ Returns:            OK on success
 */
 
 int
-tls_server_start(uschar *require_ciphers, uschar *require_mac,
-  uschar *require_kx, uschar *require_proto)
+tls_server_start(const uschar *require_ciphers)
 {
 int rc;
 uschar *expciphers;
@@ -1050,8 +1044,9 @@ if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
   return FAIL;
 
 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
-are separated by underscores. So that I can use either form in my tests, and
-also for general convenience, we turn underscores into hyphens here. */
+were historically separated by underscores. So that I can use either form in my
+tests, and also for general convenience, we turn underscores into hyphens here.
+*/
 
 if (expciphers != NULL)
   {
@@ -1185,11 +1180,6 @@ Argument:
   verify_certs     file for certificate verify
   crl              file containing CRL
   require_ciphers  list of allowed ciphers
-  ------------------------------------------------------
-  require_mac      list of allowed MACs                 ) Not used
-  require_kx       list of allowed key_exchange methods )   for
-  require_proto    list of allowed protocols            ) OpenSSL
-  ------------------------------------------------------
   timeout          startup timeout
 
 Returns:           OK on success
@@ -1201,8 +1191,7 @@ int
 tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
   uschar *certificate, uschar *privatekey, uschar *sni,
   uschar *verify_certs, uschar *crl,
-  uschar *require_ciphers, uschar *require_mac, uschar *require_kx,
-  uschar *require_proto, int timeout)
+  uschar *require_ciphers, int timeout)
 {
 static uschar txt[256];
 uschar *expciphers;
@@ -1527,7 +1516,7 @@ fprintf(f, "Library version: OpenSSL: Compile: %s\n"
 
 
 /*************************************************
-*        Pseudo-random number generation         *
+*            Random number generation            *
 *************************************************/
 
 /* Pseudo-random number generation.  The result is not expected to be
@@ -1542,7 +1531,7 @@ Returns     a random number in range [0, max-1]
 */
 
 int
-pseudo_random_number(int max)
+vaguely_random_number(int max)
 {
 unsigned int r;
 int i, needed_len;
@@ -1578,7 +1567,14 @@ if (i < needed_len)
   needed_len = i;
 
 /* We do not care if crypto-strong */
-(void) RAND_pseudo_bytes(smallbuf, needed_len);
+i = RAND_pseudo_bytes(smallbuf, needed_len);
+if (i < 0)
+  {
+  DEBUG(D_all)
+    debug_printf("OpenSSL RAND_pseudo_bytes() not supported by RAND method, using fallback.\n");
+  return vaguely_random_number_fallback(max);
+  }
+
 r = 0;
 for (p = smallbuf; needed_len; --needed_len, ++p)
   {
index d975a2c89a64ec4340df2a65792138ec3442a9cc..f0eba37f82b5b456f62d4562729c2a11b912b7de 100644 (file)
@@ -34,7 +34,7 @@ functions and the common functions below. */
 
 
 static uschar *ssl_xfer_buffer = NULL;
-static int ssl_xfer_buffer_size = 4096;
+static const int ssl_xfer_buffer_size = 4096;
 static int ssl_xfer_buffer_lwm = 0;
 static int ssl_xfer_buffer_hwm = 0;
 static int ssl_xfer_eof = 0;
@@ -59,11 +59,11 @@ Returns:    TRUE if OK; result may still be NULL after forced failure
 */
 
 static BOOL
-expand_check(uschar *s, uschar *name, uschar **result)
+expand_check(const uschar *s, const uschar *name, uschar **result)
 {
 if (s == NULL) *result = NULL; else
   {
-  *result = expand_string(s);
+  *result = expand_string(US s); /* need to clean up const some more */
   if (*result == NULL && !expand_string_forcedfail)
     {
     log_write(0, LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
index 0557318d0dea5ddc98b649046d4d20a771d3e00a..1a70b6f32b19c47fb70f8143450c4377312e828f 100644 (file)
@@ -919,19 +919,19 @@ if ((options & topt_no_body) == 0)
       }
     }
 
-  /* Finished with the check string */
-
-  nl_check_length = nl_escape_length = 0;
-
   /* A read error on the body will have left len == -1 and errno set. */
 
   if (len != 0) return FALSE;
+  }
 
-  /* If requested, add a terminating "." line (SMTP output). */
+/* Finished with the check string */
 
-  if ((options & topt_end_dot) != 0 && !write_chunk(fd, US".\n", 2, use_crlf))
-    return FALSE;
-  }
+nl_check_length = nl_escape_length = 0;
+
+/* If requested, add a terminating "." line (SMTP output). */
+
+if ((options & topt_end_dot) != 0 && !write_chunk(fd, US".\n", 2, use_crlf))
+  return FALSE;
 
 /* Write out any remaining data in the buffer before returning. */
 
index 4db67f871b282f011f814ead27437efc9ef98b80..b01322662ca923e6bcf5269535fa33b9d5c828aa 100644 (file)
@@ -62,6 +62,9 @@ optionlist smtp_transport_options[] = {
   { "gethostbyname",        opt_bool,
       (void *)offsetof(smtp_transport_options_block, gethostbyname) },
 #ifdef SUPPORT_TLS
+  /* These are no longer honoured, as of Exim 4.78; for now, we silently
+  ignore; a later release will warn, and a later-still release will remove
+  these options, so that using them becomes an error. */
   { "gnutls_require_kx",    opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, gnutls_require_kx) },
   { "gnutls_require_mac",   opt_stringptr,
@@ -895,7 +898,7 @@ outblock.authenticating = FALSE;
 tls_bits = 0;
 tls_cipher = NULL;
 tls_peerdn = NULL;
-#ifndef USE_GNUTLS
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
 tls_sni = NULL;
 #endif
 
@@ -1133,9 +1136,6 @@ if (tls_offered && !suppress_tls &&
       ob->tls_verify_certificates,
       ob->tls_crl,
       ob->tls_require_ciphers,
-      ob->gnutls_require_mac,
-      ob->gnutls_require_kx,
-      ob->gnutls_require_proto,
       ob->command_timeout);
 
     /* TLS negotiation failed; give an error. From outside, this function may
index b6d06e540f207aab9c0d89aaa7a97a008c64aa24..4fd16eb9db7e267759197af5b6861ef710aac2c4 100644 (file)
@@ -142,8 +142,8 @@ RUNNING THE TEST SUITE
 
 (2) cd into the exim-testsuite-x.xx directory.
 
-(3) Run "./configure" and then "make". This builds a few auxiliary programs
-    that are written in C.
+(3) Run "autoconf" then "./configure" and then "make". This builds a few
+    auxiliary programs that are written in C.
 
 (4) echo $PWD/test-config >> your_TRUSTED_CONFIG_LIST_filename
 
index 67c51f4e061470668c15529ee93727573353734a..1d79e650bec966a60a8c562f3aab9c7fa6911c25 100644 (file)
@@ -12,6 +12,6 @@ gecos_name = CALLER_NAME
 
 # ----- Main settings -----
 
-message_size_limit = 2048M
+message_size_limit = 8796093022208M
 
 # End
diff --git a/test/confs/0564 b/test/confs/0564
new file mode 100644 (file)
index 0000000..e10445b
--- /dev/null
@@ -0,0 +1,18 @@
+# Exim test configuration 0564
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+rfc1413_query_timeout = 0s
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+acl_smtp_data = accept
+
+queue_only
+
+# End
diff --git a/test/confs/0565 b/test/confs/0565
new file mode 100644 (file)
index 0000000..c51fd63
--- /dev/null
@@ -0,0 +1,50 @@
+# Exim test configuration 0565
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+rfc1413_query_timeout = 0s
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+
+
+# ----- Routers -----
+
+begin routers
+
+hdronly_dnslookup:
+  domains = test.ex
+  driver = manualroute
+  route_data = 127.0.0.1
+  self = send
+  transport = remote_smtp_hdrs
+
+dnslookup:
+  driver = manualroute
+  route_data = 127.0.0.1
+  self = send
+  transport = remote_smtp
+
+
+# ----- Transports -----
+
+begin transports
+
+remote_smtp:
+  driver = smtp
+  port = PORT_S
+  allow_localhost
+
+remote_smtp_hdrs:
+  driver = smtp
+  port = PORT_S
+  allow_localhost
+  headers_only
+
+# End
diff --git a/test/confs/2011 b/test/confs/2011
deleted file mode 100644 (file)
index 334ca89..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-# Exim test configuration 2011
-
-SERVER =
-CREQCIP =
-CREQMAC =
-SREQCIP =
-SREQMAC =
-
-exim_path = EXIM_PATH
-host_lookup_order = bydns
-primary_hostname = myhost.test.ex
-rfc1413_query_timeout = 0s
-spool_directory = DIR/spool
-log_file_path = DIR/spool/log/SERVER%slog
-gecos_pattern = ""
-gecos_name = CALLER_NAME
-
-# ----- Main settings -----
-
-acl_smtp_rcpt = accept
-log_selector = +tls_peerdn
-queue_only
-queue_run_in_order
-
-tls_advertise_hosts = *
-
-# Set certificate only if server
-
-tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
-tls_privatekey = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail}
-
-tls_verify_hosts = *
-tls_verify_certificates = ${if eq {SERVER}{server}{DIR/aux-fixed/cert2}fail}
-
-SREQCIP
-SREQMAC
-
-
-# ----- Routers -----
-
-begin routers
-
-client:
-  driver = accept
-  condition = ${if eq {SERVER}{server}{no}{yes}}
-  retry_use_local_part
-  transport = send_to_server
-
-
-# ----- Transports -----
-
-begin transports
-
-send_to_server:
-  driver = smtp
-  allow_localhost
-  hosts = HOSTIPV4 : 127.0.0.1
-  hosts_require_tls = HOSTIPV4
-  port = PORT_D
-  tls_certificate = DIR/aux-fixed/cert2
-  tls_privatekey = DIR/aux-fixed/cert2
-  CREQCIP
-  CREQMAC
-
-# End
index 050f9f897f860667b4842220c9151e5c0e9a372f..05245e7f0a2e97eb14bf4c70f226419c179f8b80 100644 (file)
@@ -1,4 +1,4 @@
-1999-03-02 09:44:33 invalid value for message_size_limit: absolute value of integer "2048M" is too large (overflow)
-1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "2048M" is too large (overflow)
+1999-03-02 09:44:33 invalid value for message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
+1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
-1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "2048M" is too large (overflow)
+1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
diff --git a/test/log/0564 b/test/log/0564
new file mode 100644 (file)
index 0000000..dbaac4b
--- /dev/null
@@ -0,0 +1,2 @@
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, launched with listening socket, with no wait timeout
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= userx@test.ex H=(abcd) [127.0.0.1] P=esmtp S=sss
diff --git a/test/log/0565 b/test/log/0565
new file mode 100644 (file)
index 0000000..1fef38b
--- /dev/null
@@ -0,0 +1,6 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-smtp S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => x@y R=dnslookup T=remote_smtp H=127.0.0.1 [127.0.0.1]
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local-smtp S=sss
+1999-03-02 09:44:33 10HmaY-0005vi-00 => x@test.ex R=hdronly_dnslookup T=remote_smtp_hdrs H=127.0.0.1 [127.0.0.1]
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
diff --git a/test/log/2011 b/test/log/2011
deleted file mode 100644 (file)
index 41abc9b..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
-1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmaX-0005vi-00 TLS error on connection to ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] (gnutls_handshake): No supported cipher suites have been found.
-1999-03-02 09:44:33 10HmaX-0005vi-00 TLS error on connection to 127.0.0.1 [127.0.0.1] (gnutls_handshake): No supported cipher suites have been found.
-1999-03-02 09:44:33 10HmaX-0005vi-00 TLS session failure: delivering unencrypted to 127.0.0.1 [127.0.0.1] (not in hosts_require_tls)
-1999-03-02 09:44:33 10HmaX-0005vi-00 => userx@test.ex R=client T=send_to_server H=127.0.0.1 [127.0.0.1]
-1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
-1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmaY-0005vi-00 => userx@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.2:RSA_ARCFOUR_SHA1:128 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel"
-1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
-1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmaZ-0005vi-00 => userx@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.2:RSA_ARCFOUR_MD5:128 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel"
-1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
-1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmbA-0005vi-00 => userx@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.2:RSA_ARCFOUR_MD5:128 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel"
-1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
-1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmbB-0005vi-00 => userx@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.2:RSA_AES_256_CBC_SHA1:256 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel"
-1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
-1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmbC-0005vi-00 => userx@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.2:RSA_AES_256_CBC_SHA1:256 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel"
-1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
-1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmbD-0005vi-00 => userx@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=SSL3.0:RSA_AES_256_CBC_SHA1:256 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel"
-1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
-1999-03-02 09:44:33 End queue run: pid=pppp -qf
-1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmbE-0005vi-00 => userx@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.2:RSA_ARCFOUR_MD5:128 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel"
-1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
-1999-03-02 09:44:33 End queue run: pid=pppp -qf
-
-******** SERVER ********
-1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
-1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (gnutls_handshake): A TLS packet with unexpected length was received.
-1999-03-02 09:44:33 TLS error on connection from localhost (myhost.test.ex) [127.0.0.1] (gnutls_handshake): A TLS packet with unexpected length was received.
-1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtp S=sss id=E10HmaX-0005vi-00@myhost.test.ex
-1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.2:RSA_ARCFOUR_SHA1:128 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel" S=sss id=E10HmaX-0005vi-00@myhost.test.ex
-1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.2:RSA_ARCFOUR_MD5:128 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel" S=sss id=E10HmaX-0005vi-00@myhost.test.ex
-1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.2:RSA_ARCFOUR_MD5:128 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel" S=sss id=E10HmaX-0005vi-00@myhost.test.ex
-1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.2:RSA_AES_256_CBC_SHA1:256 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel" S=sss id=E10HmaX-0005vi-00@myhost.test.ex
-1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.2:RSA_AES_256_CBC_SHA1:256 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel" S=sss id=E10HmaX-0005vi-00@myhost.test.ex
-1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=SSL3.0:RSA_AES_256_CBC_SHA1:256 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel" S=sss id=E10HmaX-0005vi-00@myhost.test.ex
-1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
-1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.2:RSA_ARCFOUR_MD5:128 DN="C=UK,L=Cambridge,O=University of Cambridge,OU=Computing Service,CN=Philip Hazel" S=sss id=E10HmaX-0005vi-00@myhost.test.ex
diff --git a/test/msglog/0564.10HmaX-0005vi-00 b/test/msglog/0564.10HmaX-0005vi-00
new file mode 100644 (file)
index 0000000..3b44e88
--- /dev/null
@@ -0,0 +1 @@
+1999-03-02 09:44:33 Received from userx@test.ex H=(abcd) [127.0.0.1] P=esmtp S=sss
index 0cbb80591bb5489d4f2b0d6ee458e5313fe7458d..9b990fca50760bf42b5a282158414161ea1458f8 100644 (file)
@@ -1,3 +1,3 @@
-1999-03-02 09:44:33 invalid value for message_size_limit: absolute value of integer "2048M" is too large (overflow)
-1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "2048M" is too large (overflow)
-1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "2048M" is too large (overflow)
+1999-03-02 09:44:33 invalid value for message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
+1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
+1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
index d42d400a961ad7bcba8ffdfcb354d328cfb2e8fc..5f77e625652785b7beea3f5717c25a44ca14af39 100755 (executable)
 # Placed in the Exim CVS: 06 February 2006                                    #
 ###############################################################################
 
+#use strict;
 require Cwd;
 use Errno;
 use FileHandle;
 use Socket;
+use Time::Local;
 
 
 # Start by initializing some global variables
 
-$testversion = "4.72 (02-Jun-10)";
+$testversion = "4.78 (08-May-12)";
 
 $cf = "bin/cf -exact";
 $cr = "\r";
@@ -118,6 +120,12 @@ s?\bV6NET:?$parm_ipv6_test_net:?g;
 }
 
 
+##################################################
+#     Any state to be preserved across tests     #
+##################################################
+
+my $TEST_STATE = {};
+
 
 ##################################################
 #        Subroutine to tidy up and exit          #
@@ -140,6 +148,13 @@ my($spool);
 # than SIGTERM to stop it outputting "Terminated" to the terminal when not in
 # the background.
 
+if (exists $TEST_STATE->{exim_pid})
+  {
+  $pid = $TEST_STATE->{exim_pid};
+  print "Tidyup: killing wait-mode daemon pid=$pid\n";
+  system("sudo kill -SIGINT $pid");
+  }
+
 if (opendir(DIR, "spool"))
   {
   my(@spools) = sort readdir(DIR);
@@ -196,14 +211,26 @@ return $newid;
 }
 
 
-# This is used while munging the output from exim_dumpdb. We cheat by assuming
-# that the  date always the same, and just return the number of seconds since
-# midnight.
+# This is used while munging the output from exim_dumpdb.
+# May go wrong across DST changes.
 
 sub date_seconds {
 my($day,$month,$year,$hour,$min,$sec) =
   $_[0] =~ /^(\d\d)-(\w\w\w)-(\d{4})\s(\d\d):(\d\d):(\d\d)/;
-return $hour * 60 * 60 + $min * 60 + $sec;
+my($mon);
+if   ($month =~ /Jan/) {$mon = 0;}
+elsif($month =~ /Feb/) {$mon = 1;}
+elsif($month =~ /Mar/) {$mon = 2;}
+elsif($month =~ /Apr/) {$mon = 3;}
+elsif($month =~ /May/) {$mon = 4;}
+elsif($month =~ /Jun/) {$mon = 5;}
+elsif($month =~ /Jul/) {$mon = 6;}
+elsif($month =~ /Aug/) {$mon = 7;}
+elsif($month =~ /Sep/) {$mon = 8;}
+elsif($month =~ /Oct/) {$mon = 9;}
+elsif($month =~ /Nov/) {$mon = 10;}
+elsif($month =~ /Dec/) {$mon = 11;}
+return timelocal($sec,$min,$hour,$day,$mon,$year);
 }
 
 
@@ -1360,17 +1387,21 @@ system("$cmd");
 #            reference to the subtest number, holding previous value
 #            reference to the expected return code value
 #            reference to where to put the command name (for messages)
+#            auxilliary information returned from a previous run
 #
 # Returns:   0 the commmand was executed inline, no subprocess was run
 #            1 a non-exim command was run and waited for
 #            2 an exim command was run and waited for
 #            3 a command was run and not waited for (daemon, server, exim_lock)
 #            4 EOF was encountered after an initial return code line
+# Optionally alse a second parameter, a hash-ref, with auxilliary information:
+#            exim_pid: pid of a run process
 
 sub run_command{
 my($testno) = $_[0];
 my($subtestref) = $_[1];
 my($commandnameref) = $_[3];
+my($aux_info) = $_[4];
 my($yield) = 1;
 
 if (/^(\d+)\s*$/)                # Handle unusual return code
@@ -1541,11 +1572,27 @@ if (/^gnutls/)
 
 if (/^killdaemon/)
   {
-  $pid = `cat $parm_cwd/spool/exim-daemon.*`;
-  run_system("sudo /bin/kill -SIGINT $pid");
-  close DAEMONCMD;                                   # Waits for process
-  run_system("sudo /bin/rm -f spool/exim-daemon.*");
-  return 1;
+  my $return_extra = {};
+  if (exists $aux_info->{exim_pid})
+    {
+    $pid = $aux_info->{exim_pid};
+    $return_extra->{exim_pid} = undef;
+    print ">> killdaemon: recovered pid $pid\n" if $debug;
+    if ($pid)
+      {
+      run_system("sudo /bin/kill -SIGINT $pid");
+      wait;
+      }
+    } else {
+    $pid = `cat $parm_cwd/spool/exim-daemon.*`;
+    if ($pid)
+      {
+      run_system("sudo /bin/kill -SIGINT $pid");
+      close DAEMONCMD;                                   # Waits for process
+      }
+    }
+    run_system("sudo /bin/rm -f spool/exim-daemon.*");
+  return (1, $return_extra);
   }
 
 
@@ -1773,6 +1820,10 @@ elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo\s+)?exim(_\S+)?\s+(.*)$/)
 
     my($i);
     for ($i = @msglist; $i > 0; $i--) { $args =~ s/\$msg$i/$msglist[$i-1]/g; }
+    if ( $args =~ /\$msg\d/ )
+      {
+      tests_exit(-1, "Not enough messages in spool, for test $testno line $lineno\n");
+      }
     }
 
   # If -d is specified in $optargs, remove it from $args; i.e. let
@@ -1816,6 +1867,38 @@ elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo\s+)?exim(_\S+)?\s+(.*)$/)
     select(undef, undef, undef, 0.3);             # Let the daemon get going
     return 3;                                     # Don't wait
     }
+  elsif ($cmd =~ /\s-DSERVER=wait:(\d+)\s/)
+    {
+    my $listen_port = $1;
+    my $waitmode_sock = new FileHandle;
+    if ($debug) { printf ">> wait-mode daemon: $cmd\n"; }
+    run_system("sudo mkdir spool/log 2>/dev/null");
+    run_system("sudo chown $parm_eximuser:$parm_eximgroup spool/log");
+
+    my ($s_ip,$s_port) = ('127.0.0.1', $listen_port);
+    my $sin = sockaddr_in($s_port, inet_aton($s_ip))
+        or die "** Failed packing $s_ip:$s_port\n";
+    socket($waitmode_sock, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
+        or die "** Unable to open socket $s_ip:$s_port: $!\n";
+    setsockopt($waitmode_sock, SOL_SOCKET, SO_REUSEADDR, 1)
+        or die "** Unable to setsockopt(SO_REUSEADDR): $!\n";
+    bind($waitmode_sock, $sin)
+        or die "** Unable to bind socket ($s_port): $!\n";
+    listen($waitmode_sock, 5);
+    my $pid = fork();
+    if (not defined $pid) { die "** fork failed: $!\n" }
+    if (not $pid) {
+      close(STDIN);
+      open(STDIN, "<&", $waitmode_sock) or die "** dup sock to stdin failed: $!\n";
+      close($waitmode_sock);
+      print "[$$]>> ${cmd}-server\n" if ($debug);
+      exec "exec ${cmd}-server";
+      exit(1);
+    }
+    while (<SCRIPT>) { $lineno++; last if /^\*{4}\s*$/; }   # Ignore any input
+    select(undef, undef, undef, 0.3);             # Let the daemon get going
+    return (3, { exim_pid => $pid });             # Don't wait
+    }
   }
 
 
@@ -1916,7 +1999,7 @@ else
 # If the first character of the first argument is '/', the argument is taken
 # as the path to the binary.
 
-$parm_exim = (@ARGV > 0 && $ARGV[0] =~ ?^/?)? shift @ARGV : "";
+$parm_exim = (@ARGV > 0 && $ARGV[0] =~ m?^/?)? shift @ARGV : "";
 print "Exim binary is $parm_exim\n" if $parm_exim ne "";
 
 
@@ -2179,6 +2262,8 @@ unlink("$parm_cwd/test-config");
 
 if (defined $parm_support{'Content_Scanning'})
   {
+  my $sock = new FileHandle;
+
   if (system("spamc -h 2>/dev/null >/dev/null") == 0)
     {
     print "The spamc command works:\n";
@@ -2195,23 +2280,23 @@ if (defined $parm_support{'Content_Scanning'})
       {
       my $sin = sockaddr_in($sport, inet_aton($sint))
           or die "** Failed packing $sint:$sport\n";
-      socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
+      socket($sock, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
           or die "** Unable to open socket $sint:$sport\n";
 
       local $SIG{ALRM} =
           sub { die "** Timeout while connecting to socket $sint:$sport\n"; };
       alarm(5);
-      connect(SOCK, $sin)
+      connect($sock, $sin)
           or die "** Unable to connect to socket $sint:$sport\n";
       alarm(0);
 
-      select((select(SOCK), $| = 1)[0]);
-      print SOCK "bad command\r\n";
+      select((select($sock), $| = 1)[0]);
+      print $sock "bad command\r\n";
 
       $SIG{ALRM} =
           sub { die "** Timeout while reading from socket $sint:$sport\n"; };
       alarm(10);
-      my $res = <SOCK>;
+      my $res = <$sock>;
       alarm(0);
 
       $res =~ m|^SPAMD/|
@@ -2322,18 +2407,18 @@ if (defined $parm_support{'Content_Scanning'})
             {
             die "** Unknown socket domain '$socket_domain' (should not happen)\n";
             }
-          socket(SOCK, $socket_domain, SOCK_STREAM, 0) or die "** Unable to open socket '$parm_clamsocket'\n";
+          socket($sock, $socket_domain, SOCK_STREAM, 0) or die "** Unable to open socket '$parm_clamsocket'\n";
           local $SIG{ALRM} = sub { die "** Timeout while connecting to socket '$parm_clamsocket'\n"; };
           alarm(5);
-          connect(SOCK, $socket) or die "** Unable to connect to socket '$parm_clamsocket'\n";
+          connect($sock, $socket) or die "** Unable to connect to socket '$parm_clamsocket'\n";
           alarm(0);
 
-          my $ofh = select SOCK; $| = 1; select $ofh;
-          print SOCK "PING\n";
+          my $ofh = select $sock; $| = 1; select $ofh;
+          print $sock "PING\n";
 
           $SIG{ALRM} = sub { die "** Timeout while reading from socket '$parm_clamsocket'\n"; };
           alarm(10);
-          my $res = <SOCK>;
+          my $res = <$sock>;
           alarm(0);
 
           $res =~ /PONG/ or die "** Did not get PONG from socket '$parm_clamsocket'. It said: $res\n";
@@ -2649,7 +2734,7 @@ system("sudo cp eximdir/exim eximdir/exim_exim;" .
 # Certain of the tests make use of some of Exim's utilities. We do not need
 # to be root to copy these.
 
-($parm_exim_dir) = $parm_exim =~ ?^(.*)/exim?;
+($parm_exim_dir) = $parm_exim =~ m?^(.*)/exim?;
 
 $dbm_build_deleted = 0;
 if (defined $parm_lookups{'dbm'} &&
@@ -3168,10 +3253,30 @@ foreach $test (@test_list)
 
     my($commandname) = "";
     my($expectrc) = 0;
-    my($rc) = run_command($testno, \$subtestno, \$expectrc, \$commandname);
+    my($rc, $run_extra) = run_command($testno, \$subtestno, \$expectrc, \$commandname, $TEST_STATE);
     my($cmdrc) = $?;
 
-    print ">> rc=$rc cmdrc=$cmdrc\n" if $debug;
+    if ($debug) {
+      print ">> rc=$rc cmdrc=$cmdrc\n";
+      if (defined $run_extra) {
+        foreach my $k (keys %$run_extra) {
+          my $v = defined $run_extra->{$k} ? qq!"$run_extra->{$k}"! : '<undef>';
+          print ">>   $k -> $v\n";
+        }
+      }
+    }
+    $run_extra = {} unless defined $run_extra;
+    foreach my $k (keys %$run_extra) {
+      if (exists $TEST_STATE->{$k}) {
+        my $nv = defined $run_extra->{$k} ? qq!"$run_extra->{$k}"! : 'removed';
+        print ">> override of $k; was $TEST_STATE->{$k}, now $nv\n" if $debug;
+      }
+      if (defined $run_extra->{$k}) {
+        $TEST_STATE->{$k} = $run_extra->{$k};
+      } elsif (exists $TEST_STATE->{$k}) {
+        delete $TEST_STATE->{$k};
+      }
+    }
 
     # Hit EOF after an initial return code number
 
@@ -3200,10 +3305,10 @@ foreach $test (@test_list)
 
       for (;;)
         {
-        print "\nshow stdErr, show stdOut, Continue (without file comparison), or Quit? [Q] ";
+        print "\nshow stdErr, show stdOut, Retry, Continue (without file comparison), or Quit? [Q] ";
         $_ = <T>;
         tests_exit(1) if /^q?$/i;
-        last if /^c$/i;
+        last if /^[rc]$/i;
         if (/^e$/i)
           {
           system("$more test-stderr");
@@ -3214,6 +3319,7 @@ foreach $test (@test_list)
           }
         }
 
+      $retry = 1 if /^r$/i;
       $docheck = 0;
       }
 
@@ -3236,10 +3342,10 @@ foreach $test (@test_list)
 
         for (;;)
           {
-          print "\nShow server stdout, Continue, or Quit? [Q] ";
+          print "\nShow server stdout, Retry, Continue, or Quit? [Q] ";
           $_ = <T>;
           tests_exit(1) if /^q?$/i;
-          last if /^c$/i;
+          last if /^[rc]$/i;
 
           if (/^s$/i)
             {
@@ -3249,6 +3355,7 @@ foreach $test (@test_list)
             close(S);
             }
           }
+        $retry = 1 if /^r$/i;
         }
       }
     }
@@ -3259,6 +3366,13 @@ foreach $test (@test_list)
   # function returns 0 if all is well, 1 if we should rerun the test (the files
   # have been updated). It does not return if the user responds Q to a prompt.
 
+  if ($retry)
+    {
+    $retry = '0';
+    print (("#" x 79) . "\n");
+    redo;
+    }
+
   if ($docheck)
     {
     if (check_output() != 0)
@@ -3282,4 +3396,4 @@ tests_exit(-1, "No runnable tests selected") if @test_list == 0;
 tests_exit(0);
 
 # End of runtest script
-# vim: set sw=2 :
+# vim: set sw=2 et :
index 01ba6d911a61e1222409d3f2deced172acd2c3eb..f87251e1ea83d838bff6c53ed3b9935ec5f52087 100644 (file)
@@ -239,9 +239,12 @@ md5:    ${if eq {1}{2}{${md5:invalid}}{NO}}
 mask:   ${if eq {1}{2}{${mask:invalid}}{NO}}
 
 # Numeric overflow
+# >32b should work, >64b not
 
 4096M      ${if >{1}{4096M}{y}{n}}
 4096000000 ${if >{1}{4096000000}{y}{n}}
+4611686018427387904  ${if >{1}{4611686018427387904} {y}{n}}
+46116860184273879040 ${if >{1}{46116860184273879040}{y}{n}}
 
 # Conditions
 
diff --git a/test/scripts/0000-Basic/0564 b/test/scripts/0000-Basic/0564
new file mode 100644 (file)
index 0000000..68fb607
--- /dev/null
@@ -0,0 +1,25 @@
+# testing -bw inetd wait mode
+need_ipv4
+#
+exim -DSERVER=wait:PORT_D -bw
+****
+client 127.0.0.1 PORT_D
+??? 220
+ehlo abcd
+??? 250-
+??? 250-
+??? 250-
+??? 250-
+??? 250
+mail from:<userx@test.ex>\r\nrcpt to:<userx@test.ex>\r\ndata
+??? 250
+??? 250
+??? 354
+This is a test message.
+.
+??? 250
+quit
+??? 221
+****
+sleep 1
+killdaemon
diff --git a/test/scripts/0000-Basic/0565 b/test/scripts/0000-Basic/0565
new file mode 100644 (file)
index 0000000..8596233
--- /dev/null
@@ -0,0 +1,58 @@
+# headers_only in SMTP
+need_ipv4
+server -noipv6 PORT_S
+220 ESMTP
+EHLO
+250-OK
+250 HELP
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 Receiver OK
+DATA
+354 Send it
+.
+250 OK (wizzle)
+QUIT
+221 bye
+****
+exim -odf -bs
+mail from:<postmaster@y>
+rcpt to:<x@y>
+data
+From: postmaster@y
+To: x@y
+Subject: first test message
+
+This is the FIRST message body.
+.
+quit
+****
+server -noipv6 PORT_S
+220 ESMTP
+EHLO
+250-OK
+250 HELP
+MAIL FROM:
+250 Sender OK
+RCPT TO:
+250 Receiver OK
+DATA
+354 Send it
+.
+250 OK (wizzle)
+QUIT
+221 bye
+****
+exim -odf -bs
+mail from:<postmaster@y>
+rcpt to:<x@test.ex>
+data
+From: postmaster@y
+To: x@y
+Subject: second test message
+
+This is the SECOND message body.
+.
+quit
+****
diff --git a/test/scripts/2000-GnuTLS/2011 b/test/scripts/2000-GnuTLS/2011
deleted file mode 100644 (file)
index 6f72fba..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-# TLS client & server: (gnu)tls_require_xxx
-gnutls
-# Start up the server
-exim -DSERVER=server -bd -oX PORT_D
-****
-# This puts a message on the queue (queue_only is set).
-exim userx@test.ex
-Testing
-****
-# This will fail to deliver encrypted because there are no acceptable 
-# ciphers, so it will deliver in clear.
-exim -qf -DCREQCIP=tls_require_ciphers=IDEA-CBC-MD5
-****
-# This delivers the message to the server, where it will remain
-# on the queue because queue_only is set.
-exim -qf -DCREQCIP=tls_require_ciphers=IDEA-CBC-MD5:DES-CBC3-SHA:RSA_ARCFOUR_SHA
-****
-# So we can deliver it again and again, with different parameters.
-exim -qf -DCREQMAC=gnutls_require_mac=MD5
-****
-exim -qf -DCREQMAC=gnutls_require_mac=!SHA1
-****
-exim -qf -DCREQMAC=gnutls_require_mac=MD5:SHA
-****
-exim -qf -DCREQMAC=gnutls_require_kx=!DHE
-****
-exim -qf -DCREQMAC=gnutls_require_protocols=SSL3
-****
-# Restart the server with a cipher restriction
-killdaemon
-exim -DSERVER=server \
-     -DSREQCIP=tls_require_ciphers=ARCFOUR \
-     -DSREQMAC=gnutls_require_mac=MD5 \
-     -bd -oX PORT_D
-****
-exim -qf
-****
-killdaemon
-no_msglog_check
index bb50a3e9c2867c6e07b22475055e90895c50c92d..0494118ca94d4afff4d1c6654b397e56c8da6d72 100644 (file)
@@ -10,6 +10,7 @@ ehlo foobar
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 auth plain AHVzZXJ4AHNlY3JldA==
 ??? 235
@@ -24,6 +25,7 @@ ehlo foobar
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 auth plain AHVzZXJ4AHNlY3JldA==
 ??? 503
@@ -34,6 +36,7 @@ ehlo foobar
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 auth plain AHVzZXJ4AHNlY3JldA==
 ??? 235
index cf99cd8969ea3d605d299cdad99d70dcd60bed55..a88f49f3df6f834f531fa9c0ff57a30e38421cb6 100644 (file)
@@ -10,6 +10,7 @@ ehlo foobar
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 starttls
 ??? 220
@@ -31,6 +32,7 @@ ehlo foobar
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 starttls
 ??? 220
index 0c45cbcc5c340e14f43564370a9911b22c344bbf..7ef0077076d44cc54e1d83c639776047a7af5cee 100644 (file)
@@ -5,34 +5,37 @@ exim -DSERVER=server -bd -oX PORT_D
 client-ssl 127.0.0.1 PORT_D
 ??? 220
 ehlo foobar
-??? 250-
-??? 250-
-??? 250-
-??? 250-
-??? 250-
-??? 250
+??? 250-myhost
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-AUTH
+??? 250-STARTTLS
+??? 250 HELP
 starttls
-??? 220
+??? 220 TLS
 auth plain AHVzZXJ4AHNlY3JldA==
 ??? 503
 ****
 client-ssl 127.0.0.1 PORT_D
 ??? 220
 ehlo foobar
-??? 250-
-??? 250-
-??? 250-
-??? 250-
-??? 250-
-??? 250
+??? 250-myhost
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-AUTH
+??? 250-STARTTLS
+??? 250 HELP
 starttls
-??? 220
+??? 220 TLS
 ehlo foobar
-??? 250-
-??? 250-
-??? 250-
-??? 250-
-??? 250
+??? 250-myhost
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-AUTH
+??? 250 HELP
 auth plain AHVzZXJ4AHNlY3JldA==
 ??? 235
 quit
diff --git a/test/scripts/3650-Dovecot/3650 b/test/scripts/3650-Dovecot/3650
deleted file mode 100644 (file)
index 55b6c1e..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# Dovecot authentication (server only)
-exim -DSERVER=server -bd -oX PORT_D
-****
-client -t3 127.0.0.1 PORT_D
-??? 220
-EHLO xxxx
-??? 250-
-??? 250-
-??? 250-
-??? 250-
-??? 250
-AUTH PLAIN AHVzZXJ4AHNlY3JldA==
-??? 535
-quit
-??? 221
-****
-killdaemon
-no_msglog_check
diff --git a/test/scripts/3650-Dovecot/REQUIRES b/test/scripts/3650-Dovecot/REQUIRES
deleted file mode 100644 (file)
index 76218eb..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-authenticator dovecot
-running IPv4
index 3325f246c15b5482025bde3c588ff105ffd7fd18..ccfaf32b64155f3b1b7056432bf0017ea39df298 100644 (file)
@@ -3,14 +3,15 @@ exim -DSERVER=server -bd -oX PORT_D
 ****
 # Try without TLS
 client -t3 127.0.0.1 PORT_D
-??? 220
+??? 220 myhost
 EHLO xxxx
-??? 250-
-??? 250-
-??? 250-
-??? 250-
-??? 250-
-??? 250
+??? 250-myhost
+??? 250-SIZE
+??? 250-8BITMIME
+??? 250-PIPELINING
+??? 250-AUTH
+??? 250-STARTTLS
+??? 250 HELP
 AUTH PLAIN AHVzZXJ4AHNlY3JldA==
 ??? 535
 quit
@@ -25,6 +26,7 @@ EHLO xxxx
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 STARTTLS
 ??? 220
@@ -33,6 +35,7 @@ EHLO xxxx
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 AUTH PLAIN AHVzZXJ4AHNlY3JldA==
 ??? 535
@@ -48,6 +51,7 @@ EHLO xxxx
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 STARTTLS
 ??? 220
@@ -56,6 +60,7 @@ EHLO xxxx
 ??? 250-
 ??? 250-
 ??? 250-
+??? 250-
 ??? 250
 AUTH PLAIN AHVzZXJ4AHNlY3JldA==
 ??? 535
index ba7af5848a60b5258f9a1a86eb36c682a491b331..18974ef51daf839916cf837033fc500dc81583ee 100644 (file)
@@ -1,5 +1,5 @@
-1999-03-02 09:44:33 invalid value for message_size_limit: absolute value of integer "2048M" is too large (overflow)
-1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "2048M" is too large (overflow)
+1999-03-02 09:44:33 invalid value for message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
+1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
 
 ******** SERVER ********
-1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "2048M" is too large (overflow)
+1999-03-02 09:44:33 invalid message_size_limit: absolute value of integer "8796093022208M" is too large (overflow)
index e209ada6ecc34c28c131dc64dcd5a7be89f1fe81..c009fbd1214f9d92d903876faf292d09d60147cf 100644 (file)
 > mask:   NO
 > 
 > # Numeric overflow
+> # >32b should work, >64b not
 > 
-> Failed: absolute value of integer "4096M" is too large (overflow)
-> Failed: absolute value of integer "4096000000" is too large (overflow)
+> 4096M      y
+> 4096000000 y
+> 4611686018427387904  y
+> Failed: absolute value of integer "46116860184273879040" is too large (overflow)
 > 
 > # Conditions
 > 
diff --git a/test/stdout/0564 b/test/stdout/0564
new file mode 100644 (file)
index 0000000..6f4f06b
--- /dev/null
@@ -0,0 +1,29 @@
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo abcd
+??? 250-
+<<< 250-the.local.host.name Hello abcd [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-8BITMIME
+??? 250-
+<<< 250-PIPELINING
+??? 250
+<<< 250 HELP
+>>> mail from:<userx@test.ex>\r\nrcpt to:<userx@test.ex>\r\ndata
+??? 250
+<<< 250 OK
+??? 250
+<<< 250 Accepted
+??? 354
+<<< 354 Enter message, ending with "." on a line by itself
+>>> This is a test message.
+>>> .
+??? 250
+<<< 250 OK id=10HmaX-0005vi-00
+>>> quit
+??? 221
+<<< 221 the.local.host.name closing connection
+End of script
diff --git a/test/stdout/0565 b/test/stdout/0565
new file mode 100644 (file)
index 0000000..be4e455
--- /dev/null
@@ -0,0 +1,71 @@
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250 OK\r
+250 Accepted\r
+354 Enter message, ending with "." on a line by itself\r
+250 OK id=10HmaX-0005vi-00\r
+221 myhost.test.ex closing connection\r
+220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
+250 OK\r
+250 Accepted\r
+354 Enter message, ending with "." on a line by itself\r
+250 OK id=10HmaY-0005vi-00\r
+221 myhost.test.ex closing connection\r
+
+******** SERVER ********
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+220 ESMTP
+EHLO myhost.test.ex
+250-OK
+250 HELP
+MAIL FROM:<CALLER@myhost.test.ex>
+250 Sender OK
+RCPT TO:<x@y>
+250 Receiver OK
+DATA
+354 Send it
+Received: from CALLER by myhost.test.ex with local-smtp (Exim x.yz)
+       (envelope-from <CALLER@myhost.test.ex>)
+       id 10HmaX-0005vi-00
+       for x@y; Tue, 2 Mar 1999 09:44:33 +0000
+From: postmaster@y
+To: x@y
+Subject: first test message
+Message-Id: <E10HmaX-0005vi-00@myhost.test.ex>
+Sender: CALLER_NAME <CALLER@myhost.test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+This is the FIRST message body.
+.
+250 OK (wizzle)
+QUIT
+221 bye
+End of script
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+220 ESMTP
+EHLO myhost.test.ex
+250-OK
+250 HELP
+MAIL FROM:<CALLER@myhost.test.ex>
+250 Sender OK
+RCPT TO:<x@test.ex>
+250 Receiver OK
+DATA
+354 Send it
+Received: from CALLER by myhost.test.ex with local-smtp (Exim x.yz)
+       (envelope-from <CALLER@myhost.test.ex>)
+       id 10HmaY-0005vi-00
+       for x@test.ex; Tue, 2 Mar 1999 09:44:33 +0000
+From: postmaster@y
+To: x@y
+Subject: second test message
+Message-Id: <E10HmaY-0005vi-00@myhost.test.ex>
+Sender: CALLER_NAME <CALLER@myhost.test.ex>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+.
+250 OK (wizzle)
+QUIT
+221 bye
+End of script
index 04dd751a8316678d6f166f2c4c6196bfc0ca9719..3c2ee8a31bd6108a98b552f71fd2736aaa95f4a9 100644 (file)
@@ -7,6 +7,8 @@ Connecting to ip4.ip4.ip4.ip4 port 1225 ... connected
 ??? 250-
 <<< 250-SIZE 52428800
 ??? 250-
+<<< 250-8BITMIME
+??? 250-
 <<< 250-PIPELINING
 ??? 250-
 <<< 250-AUTH PLAIN
@@ -30,6 +32,8 @@ Connecting to 127.0.0.1 port 1225 ... connected
 ??? 250-
 <<< 250-SIZE 52428800
 ??? 250-
+<<< 250-8BITMIME
+??? 250-
 <<< 250-PIPELINING
 ??? 250-
 <<< 250-AUTH PLAIN
@@ -66,6 +70,8 @@ Succeeded in starting TLS
 ??? 250-
 <<< 250-SIZE 52428800
 ??? 250-
+<<< 250-8BITMIME
+??? 250-
 <<< 250-PIPELINING
 ??? 250-
 <<< 250-AUTH PLAIN
index e2186de39991434345e751b5229d88a06eb35fba..098973d425fe172d791d755b20f4faa7d3b423a8 100644 (file)
@@ -7,6 +7,8 @@ Connecting to 127.0.0.1 port 1225 ... connected
 ??? 250-
 <<< 250-SIZE 52428800
 ??? 250-
+<<< 250-8BITMIME
+??? 250-
 <<< 250-PIPELINING
 ??? 250-
 <<< 250-AUTH PLAIN
@@ -38,6 +40,8 @@ Connecting to 127.0.0.1 port 1225 ... connected
 ??? 250-
 <<< 250-SIZE 52428800
 ??? 250-
+<<< 250-8BITMIME
+??? 250-
 <<< 250-PIPELINING
 ??? 250-
 <<< 250-AUTH PLAIN
index 7ccebbc847cfbdec46f27d3381a9dea47d4b483f..1deec37c5e7dd0895283d928eda01d6b5bc59682 100644 (file)
@@ -2,20 +2,22 @@ Connecting to 127.0.0.1 port 1225 ... connected
 ??? 220
 <<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
 >>> ehlo foobar
-??? 250-
+??? 250-myhost
 <<< 250-myhost.test.ex Hello foobar [127.0.0.1]
-??? 250-
+??? 250-SIZE
 <<< 250-SIZE 52428800
-??? 250-
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
 <<< 250-PIPELINING
-??? 250-
+??? 250-AUTH
 <<< 250-AUTH PLAIN
-??? 250-
+??? 250-STARTTLS
 <<< 250-STARTTLS
-??? 250
+??? 250 HELP
 <<< 250 HELP
 >>> starttls
-??? 220
+??? 220 TLS
 <<< 220 TLS go ahead
 Attempting to start TLS
 SSL info: before/connect initialization
@@ -42,20 +44,22 @@ Connecting to 127.0.0.1 port 1225 ... connected
 ??? 220
 <<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
 >>> ehlo foobar
-??? 250-
+??? 250-myhost
 <<< 250-myhost.test.ex Hello foobar [127.0.0.1]
-??? 250-
+??? 250-SIZE
 <<< 250-SIZE 52428800
-??? 250-
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
 <<< 250-PIPELINING
-??? 250-
+??? 250-AUTH
 <<< 250-AUTH PLAIN
-??? 250-
+??? 250-STARTTLS
 <<< 250-STARTTLS
-??? 250
+??? 250 HELP
 <<< 250 HELP
 >>> starttls
-??? 220
+??? 220 TLS
 <<< 220 TLS go ahead
 Attempting to start TLS
 SSL info: before/connect initialization
@@ -75,15 +79,17 @@ SSL info: SSL negotiation finished successfully
 SSL connection using DHE-RSA-AES256-SHA
 Succeeded in starting TLS
 >>> ehlo foobar
-??? 250-
+??? 250-myhost
 <<< 250-myhost.test.ex Hello foobar [127.0.0.1]
-??? 250-
+??? 250-SIZE
 <<< 250-SIZE 52428800
-??? 250-
+??? 250-8BITMIME
+<<< 250-8BITMIME
+??? 250-PIPELINING
 <<< 250-PIPELINING
-??? 250-
+??? 250-AUTH
 <<< 250-AUTH PLAIN
-??? 250
+??? 250 HELP
 <<< 250 HELP
 >>> auth plain AHVzZXJ4AHNlY3JldA==
 ??? 235
index cf3412153ba01e6ff953b1b2f4e6e6e95b07a751..eff877af5f89d330b6f768353c5d56ea7fa20b86 100644 (file)
@@ -1,5 +1,5 @@
 +++++++++++++++++++++++++++
   T:jack@myhost.test.ex -46 12800 LMTP error after end of data: 450 Number 2 is now delayed
-first failed = time last try = time2 next try = time2 + 0
+first failed = time last try = time2 next try = time2 + 86400
   T:tom@myhost.test.ex -44 12800 LMTP error after RCPT TO:<tom@myhost.test.ex>: 450 This one is delayed on RCPT
 first failed = time last try = time2 next try = time2 + 60