diff -ruN postfix-2.0.16-vanilla/Makefile.in postfix-2.0.16/Makefile.in --- postfix-2.0.16-vanilla/Makefile.in Sun Jan 12 17:23:26 2003 +++ postfix-2.0.16/Makefile.in Tue Sep 23 09:12:30 2003 @@ -7,7 +7,7 @@ src/showq src/postalias src/postcat src/postconf src/postdrop \ src/postkick src/postlock src/postlog src/postmap src/postqueue \ src/postsuper src/nqmgr src/qmqpd src/spawn src/flush src/virtual \ - src/proxymap + src/proxymap src/tlsmgr MANDIRS = proto man html default: update diff -ruN postfix-2.0.16-vanilla/conf/master.cf postfix-2.0.16/conf/master.cf --- postfix-2.0.16-vanilla/conf/master.cf Mon Apr 28 00:51:13 2003 +++ postfix-2.0.16/conf/master.cf Tue Sep 23 09:12:30 2003 @@ -72,11 +72,16 @@ # (yes) (yes) (yes) (never) (100) # ========================================================================== smtp inet n - n - - smtpd +#smtps inet n - n - - smtpd +# -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes +#submission inet n - n - - smtpd +# -o smtpd_enforce_tls=yes -o smtpd_sasl_auth_enable=yes #628 inet n - n - - qmqpd pickup fifo n - n 60 1 pickup cleanup unix n - n - 0 cleanup qmgr fifo n - n 300 1 qmgr #qmgr fifo n - n 300 1 nqmgr +#tlsmgr fifo - - n 300 1 tlsmgr rewrite unix - - n - - trivial-rewrite bounce unix - - n - 0 bounce defer unix - - n - 0 bounce diff -ruN postfix-2.0.16-vanilla/conf/postfix-files postfix-2.0.16/conf/postfix-files --- postfix-2.0.16-vanilla/conf/postfix-files Sun Jan 12 18:08:12 2003 +++ postfix-2.0.16/conf/postfix-files Tue Sep 23 09:12:30 2003 @@ -70,6 +70,7 @@ $daemon_directory/smtp:f:root:-:755 $daemon_directory/smtpd:f:root:-:755 $daemon_directory/spawn:f:root:-:755 +$daemon_directory/tlsmgr:f:root:-:755 $daemon_directory/trivial-rewrite:f:root:-:755 $daemon_directory/virtual:f:root:-:755 $command_directory/postalias:f:root:-:755 @@ -141,6 +142,7 @@ $manpage_directory/man8/smtp.8:f:root:-:644 $manpage_directory/man8/smtpd.8:f:root:-:644 $manpage_directory/man8/spawn.8:f:root:-:644 +$manpage_directory/man8/tlsmgr.8:f:root:-:644 $manpage_directory/man8/trivial-rewrite.8:f:root:-:644 $manpage_directory/man8/virtual.8:f:root:-:644 $sample_directory/sample-aliases.cf:f:root:-:644 @@ -168,6 +170,7 @@ $sample_directory/sample-rewrite.cf:f:root:-:644 $sample_directory/sample-smtp.cf:f:root:-:644 $sample_directory/sample-smtpd.cf:f:root:-:644 +$sample_directory/sample-tls.cf:f:root:-:644 $sample_directory/sample-transport.cf:f:root:-:644 $sample_directory/sample-virtual.cf:f:root:-:644 $readme_directory/ADDRESS_CLASS_README:f:root:-:644 diff -ruN postfix-2.0.16-vanilla/conf/sample-auth.cf postfix-2.0.16/conf/sample-auth.cf --- postfix-2.0.16-vanilla/conf/sample-auth.cf Fri Mar 29 22:36:53 2002 +++ postfix-2.0.16/conf/sample-auth.cf Tue Sep 23 09:12:30 2003 @@ -69,6 +69,33 @@ #smtpd_sasl_security_options = noanonymous, noplaintext smtpd_sasl_security_options = noanonymous +# The smtpd_sasl_tls_security_options parameter controls what authentication +# mechanism the Postfix SMTP server will offer to the client, in case the +# connection is protected by a TLS encrypted session. +# This parameter allows to provide for example plaintext authentication that +# otherwise would not be allowed without encryption. +# The default is to use the same settings as in the unencrypted case. +# +# Warning: this option only works against passive (eavesdropping) attackes. +# An active attacker (man in the middle) may modify the AUTH options offered +# and/or remove the STARTTLS offer from the EHLO response. Protection against +# active attackers is only possible by enforcing TLS at the client side. +# +#smtpd_sasl_tls_security_options = noanonymous +smtpd_sasl_tls_security_options = $smtpd_sasl_security_options + +# Sending AUTH data over an unencrypted channel poses a security risk. When +# smtpd_tls_enforce_tls is set, AUTH will only be announced and accepted, +# once the TLS layer has been activated via the STARTTLS protocol. If +# TLS layer encryption is optional, it may however still be useful to only +# offer AUTH, if TLS is active. To not break compatiblity with unpatched +# postfix versions, the default is to accept AUTH without encryption. In +# order to change this behaviour, set smtpd_tls_auth_only = yes. +# THIS OPTION ONLY WORKS WITH SSL/TLS SUPPORT COMPILED IN. +# +#smtpd_tls_auth_only = yes +smtpd_tls_auth_only = no + # The smtpd_sasl_local_domain parameter specifies the name of the # local authentication realm. # @@ -117,3 +144,34 @@ # #smtp_sasl_security_options = smtp_sasl_security_options = noplaintext + +# The smtp_sasl_tls_security_options parameter controls, what authentication +# mechanisms the local Postfix SMTP client is allowed to use, if the session +# is encrypted via TLS. This provides the option to permit plaintext passwords +# that otherwise could not be used. +# +# The settings allowed are the same as for the non-encrypted sessions +# (smtp_sasl_security_options). +# +# Warning, Warning, Warning: This option only works against passive +# (eavesdropping) attacks. An active attacker (man in the middle) may provide +# a TLS capabable server (proxy) and in such way obtain the password +# information. The only way to prevent a man in the middle attack is to check +# the hostname of the server presented in the certificate. This is assured +# in the (preferrably used) smtp_sasl_tls_verified_security_options case. +# +#smtp_sasl_tls_security_options = +smtp_sasl_tls_security_options = $smtp_sasl_security_options + +# The smtp_sasl_tls_verified_security_options parameter controls, what +# authentication mechanisms the local Postfix SMTP client is allowed to use, +# if the session is encrypted via TLS _and_ the server has proven its +# identity (expected hostname matches certificate, verification successfull). +# This provides the option to permit plaintext passwords that otherwise could +# not be used. +# +# The settings allowed are the same as for the non-encrypted sessions +# (smtp_sasl_security_options). +# +#smtp_sasl_tls_verified_security_options = +smtp_sasl_tls_verified_security_options = $smtp_sasl_tls_security_options diff -ruN postfix-2.0.16-vanilla/conf/sample-smtp.cf postfix-2.0.16/conf/sample-smtp.cf --- postfix-2.0.16-vanilla/conf/sample-smtp.cf Mon Dec 23 22:16:51 2002 +++ postfix-2.0.16/conf/sample-smtp.cf Tue Sep 23 09:12:30 2003 @@ -189,6 +189,14 @@ # smtp_helo_timeout = 300s +# The smtp_starttls_timeout parameter limits the time in seconds to write and +# read operations during TLS start and stop handhake procedures. +# +# In case of problems the client does NOT try the next address on +# the mail exchanger list. +# +# smtp_starttls_timeout = 300s + # The smtp_mail_timeout parameter specifies the SMTP client timeout # for sending the SMTP MAIL FROM command, and for receiving the server # response. diff -ruN postfix-2.0.16-vanilla/conf/sample-smtpd.cf postfix-2.0.16/conf/sample-smtpd.cf --- postfix-2.0.16-vanilla/conf/sample-smtpd.cf Tue Aug 12 18:28:46 2003 +++ postfix-2.0.16/conf/sample-smtpd.cf Tue Sep 23 09:12:30 2003 @@ -189,6 +189,11 @@ # strict_rfc821_envelopes = no +# The smtpd_starttls_timeout parameter limits the time in seconds to write and +# read operations during TLS start and stop handhake procedures. +# +# smtpd_starttls_timeout = 300s + # # TARPIT CONTROLS # diff -ruN postfix-2.0.16-vanilla/conf/sample-tls.cf postfix-2.0.16/conf/sample-tls.cf --- postfix-2.0.16-vanilla/conf/sample-tls.cf Thu Jan 1 01:00:00 1970 +++ postfix-2.0.16/conf/sample-tls.cf Tue Sep 23 09:12:30 2003 @@ -0,0 +1,499 @@ +# DO NOT EDIT THIS FILE. EDIT THE MAIN.CF FILE INSTEAD. THE STUFF +# HERE JUST SERVES AS AN EXAMPLE. +# +# This file contains example settings of Postfix configuration +# parameters that control the behaviour of the TLS extensions. +# +# We strictly seperate between server side TLS (smtpd_) and client side +# TLS (smtp_), as for practical reasons we might choose differently. + +# Section with SMTPD specific settings + +# To use TLS we do need a certificate and a private key. Both must be in +# "pem" format, the private key must not be encrypted, that does mean: +# it must be accessable without password. Both parts (certificate and +# private key) may be in the same file. +# +# Both RSA and DSA are certificates are supported. Typically you will only +# have RSA certificates issued by a commercial CA, also the tools supplied +# with OpenSSL will by default issue RSA certificates. +# You can have both at the same time, in this case the cipher used decides, +# which certificate is presented. For Netscape and OpenSSL clients without +# special cipher choices, the RSA certificate is preferred. +# +# In order to check the certificates, the CA-certificate (in case of a +# certificate chain, all CA-certificates) must be available. +# You should add these certificates to the server certificate, the server +# certificate first, then the issuing CA(s). +# +# Example: the certificate for "server.dom.ain" was issued by "intermediate CA" +# which itself has a certificate of "root CA". Create the server.pem file by +# 'cat server_cert.pem intemediate_CA.pem root_CA.pem > server.pem' +# +# If you want to accept certificates issued by these CAs yourself, you can +# also add the CA-certificates to the smtpd_tls_CAfile, in which case it is +# not necessary to have them in the smtpd_tls_[d]cert_file. +# +# A certificate supplied here must be useable as SSL server certificate and +# hence pass the "openssl verify -purpose sslserver ..." test. +# +smtpd_tls_cert_file = /etc/postfix/server.pem +smtpd_tls_key_file = $smtpd_tls_cert_file +# +# Its DSA counterparts: +smtpd_tls_dcert_file = /etc/postfix/server-dsa.pem +smtpd_tls_dkey_file = $smtpd_tls_dcert_file + +# The certificate was issued by a certification authority (CA), the CA-cert +# of which must be available, if not in the certificate file. +# This file may also contain the the CA certificates of other trusted CAs. +# You must use this file for the list of trusted CAs if you want to use +# chroot-mode. No default is supplied for this value as of now. +# +# smtpd_tls_CAfile = /etc/postfix/CAcert.pem + +# To verify the peer certificate, we need to know the certificates of +# certification authorities. These certificates in "pem" format are +# collected in a directory. The same CAs are offered to clients for +# client verification. Don't forget to create the necessary "hash" +# links with $OPENSSL_HOME/bin/c_rehash /etc/postfix/certs. A typical +# place for the CA-certs may also be $OPENSSL_HOME/certs, so there is +# no default and you explicitly have to set the value here! +# +# To use this option in chroot mode, this directory itself or a copy of it +# must be inside the chroot jail. Please note also, that the CAs in this +# directory are not listed to the client, so that e.g. Netscape might not +# offer certificates issued by them. +# +# I therefore discourage the use of this option. +# +smtpd_tls_CApath = /etc/postfix/certs + +# To get additional information during the TLS setup and negotiations +# you can increase the loglevel from 0..4: +# 0: No output about the TLS subsystem +# 1: Printout startup and certificate information +# 2: 1 + Printout of levels during negotiation +# 3: 2 + Hex and ASCII dump of negotiation process +# 4: 3 + Hex and ASCII dump of complete transmission after STARTTLS +# Use loglevel 3 only in case of problems. Use of loglevel 4 is strongly +# discouraged. +# +# smtpd_tls_loglevel = 0 + +# To include information about the protocol and cipher used as well as the +# client and issuer CommonName into the "Received:" header, set the +# smtpd_tls_received_header variable to true. The default is no, as the +# information is not necessarily authentic. Only the final destination +# is reliable, since the headers might have been changed in between. +# +#smtpd_tls_received_header = yes + +# By default TLS is disabled, so no difference to plain postfix is visible. +# Explicitely switch it on using "smtpd_use_tls". (Note: when invoked +# via "sendmail -bs", STARTTLS is never offered due to insufficient +# privileges to access the private key. This is intended behaviour.) +# +smtpd_use_tls = yes + +# You can ENFORCE the use of TLS, so that no commands (except QUIT of course) +# are allowed without TLS. According to RFC2487 this MUST NOT be applied +# in case of a publicly-referenced SMTP server. So this option is off +# by default and should only seldom be used. Using this option implies +# smtpd_use_tls = yes. (Note: when invoked via "sendmail -bs", STARTTLS +# is never offered due to insufficient privileges to access the private key. +# This is intended behaviour.) +# +# smtpd_enforce_tls = no + +# Besides RFC2487 some clients, namely Outlook [Express] prefer to run the +# non-standard "wrapper" mode, not the STARTTLS enhancement to SMTP. +# This is true for OE (Win32 < 5.0 and Win32 >=5.0 when run on a port!=25 +# and OE (5.01 Mac on all ports). +# It is strictly discouraged to use this mode from main.cf. If you want to +# support this service, enable a special port in master.cf. Port 465 (smtps) +# was once chosen for this feature. +# +# smtpd_tls_wrappermode = no + +# To receive a client certificate, the server must explicitly ask for one. +# Hence netscape will either complain if no certificate is available (for +# the list of CAs in /etc/postfix/certs) or will offer you client certificates +# to choose from. This might be annoying, so this option is "off" by default. +# You will however need the certificate if you want to to e.g. certificate +# based relaying. +# +# smtpd_tls_ask_ccert = no + +# You may also decide to REQUIRE a client certificate to allow TLS connections. +# I don't think it will be necessary often, it is however included here for +# completeness. This option implies smtpd_tls_ask_ccert = yes +# +# Please be aware, that this will inhibit TLS connections without a proper +# certificate and only makes sense, when normal submission is disabled and +# TLS is enforced (smtpd_enforce_tls). Otherwise clients may bypass by simply +# not using STARTTLS at all. When TLS is not enforced, the connection will be +# handled, as if only smtpd_tls_ask_ccert = yes would be set and an information +# is logged. +# +# smtpd_tls_req_ccert = no + +# The verification depth for client certificates. A depth of 1 is sufficient, +# if the certificate ist directly issued by a CA listed in the CA locations. +# The default value (5) should also suffice for longer chains (root CA issues +# special CA which then issues the actual certificate...) +# +# smtpd_tls_ccert_verifydepth = 5 + +# Sending AUTH data over an unencrypted channel poses a security risk. When +# smtpd_tls_enforce_tls is set, AUTH will only be announced and accepted, +# once the TLS layer has been activated via the STARTTLS protocol. If +# TLS layer encryption is optional, it may however still be useful to only +# offer AUTH, when TLS is active. To not break compatiblity with unpatched +# postfix versions, the default is to accept AUTH without encryption. In +# order to change this behaviour, set smtpd_tls_auth_only = yes. +# +# smtpd_tls_auth_only = no + +# The server and client negotiate a session, which takes some computer time +# and network bandwidth. The session is cached only in the smtpd process +# actually using this session and is lost when the process dies. +# To share the session information between the smtpd processes, a disc based +# session cache can be used based on the SDBM databases (routines included +# in Postfix/TLS). Since concurrent writing must be supported, only SDBM +# can be used. +# +smtpd_tls_session_cache_database = sdbm:/etc/postfix/smtpd_scache + +# The cached sessions time out after a certain amount of time. For Postfix/TLS +# I do not use the OpenSSL default of 300sec, but a longer time of 3600sec +# (=1 hour). RFC2246 recommends a maximum of 24 hours. +# +# smtpd_tls_session_cache_timeout = 3600s + +# Two additional options has been added for relay control to the UCE rules: +# permit_tls_clientcerts (a) +# and +# permit_tls_all_clientcerts. (b) +# +# If one of these options is added to +# smtpd_recipient_restrictions, +# postfix will relay if +# (a) a valid (it passed the verification) client certificate is presented +# and its fingerprint is listed in the list of client certs +# (relay_clientcerts), +# (b) any valid (it passed the verification) client certificate is presented. +# +# Option (b) must only be used, if a special CA issues the certificates and +# only this CA is listed as trusted CA. If other CAs are trusted, any owner +# of a valid (SSL client)-certificate can relay. Option (b) can be practical +# for a specically created email relay. It is however recommended to stay with +# option (a) and list all certificates, as (b) does not permit any control +# when a certificate must no longer be used (e.g. an employee leaving). +# +# smtpd_recipient_restrictions = ... permit_tls_clientcerts ... + +# The list of client certificates for which relaying will be allowed. +# Unfortunately the routines for lists in postfix use whitespaces as +# seperators and choke on special chars. So using the certificate +# X509ONELINES is quite impractical. We will use the fingerprints at +# this point, as they are difficult to fake but easy to use for lookup. +# As postmap (when using e.g. db) insists of having a pair of key and value, +# but we only need the key, the value can be chosen freely, e.g. the name +# of the user or host: +# D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home +# +# relay_clientcerts = hash:/etc/postfix/relay_clientcerts + +# To influence the cipher selection scheme, you can give cipherlist-string. +# A detailed description would go to far here, please refer to the openssl +# documentation. +# If you don't know what to do with it, simply don't touch it and leave the +# (openssl-)compiled in default! +# +# DO NOT USE " to enclose the string, just the string!!! +# +# smtpd_tls_cipherlist = DEFAULT + +# If you want to take advantage of ciphers with EDH, DH parameters are needed. +# There are built in DH parameters for both 1025bit and 512bit available. It +# is however better to have "own" parameters, since otherwise it would "pay" +# for a possible attacker to start a brute force attack against these +# parameters commonly used by everybody. For this reason, the parameters +# chosen are already different from those distributed with other TLS packages. +# +# To generate your own set of parameters, use +# openssl gendh -out /etc/postfix/dh_1024.pem -2 -rand /var/run/egd-pool 1024 +# openssl gendh -out /etc/postfix/dh_512.pem -2 -rand /var/run/egd-pool 512 +# (your source for "entropy" might vary; on Linux there is /dev/random, on +# other system, you might consider the "Entropy Gathering Daemon EGD", +# available at http://www.lothar.com/tech/crypto/. +# +smtpd_tls_dh1024_param_file = /etc/postfix/dh_1024.pem +smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem + +# The smtpd_starttls_timeout parameter limits the time in seconds to write and +# read operations during TLS start and stop handhake procedures. +# +# smtpd_starttls_timeout = 300s + +# Section with SMTP specific settings + +# During the startup negotiation we might present a certificate to the server. +# Netscape is rather clever here and lets the user select between only those +# certs that will match the CAs accepted from the server. As I simply use +# the integrated "SSL_connect()" from the OpenSSL package, this is not +# possible by now and we have to chose just one cert. +# So for now the default is to use _no_ cert and key unless explictly +# set here. It is possible to use the same key/cert pair as for the server. +# If a cert is to be presented, it must be in "pem" format, the private key +# must not be encrypted, that does mean: it must be accessable without +# password. Both parts (certificate and private key) may be in the +# same file. +# +# In order to check the certificates, the CA-certificate (in case of a +# certificate chain, all CA-certificates) must be available. +# You should add these certificates to the server certificate, the server +# certificate first, then the issuing CA(s). +# +# Example: the certificate for "client.dom.ain" was issued by "intermediate CA" +# which itself has a certificate of "root CA". Create the client.pem file by +# 'cat client_cert.pem intemediate_CA.pem root_CA.pem > client.pem' +# +# If you want to accept certificates issued by these CAs yourself, you can +# also add the CA-certificates to the smtp_tls_CAfile, in which case it is +# not necessary to have them in the smtp_tls_[d]cert_file. +# +# A certificate supplied here must be useable as SSL client certificate and +# hence pass the "openssl verify -purpose sslclient ..." test. +# +smtp_tls_cert_file = /etc/postfix/client.pem +smtp_tls_key_file = $smtp_tls_cert_file + +# The certificate was issued by a certification authority (CA), the CA-cert +# of which must be available, if not in the certificate file. +# This file may also contain the the CA certificates of other trusted CAs. +# You must use this file for the list of trusted CAs if you want to use +# chroot-mode. No default is supplied for this value as of now. +# +smtp_tls_CAfile = /etc/postfix/CAcert.pem + +# To verify the peer certificate, we need to know the certificates of +# certification authorities. These certificates in "pem" format are +# collected in a directory. Don't forget to create the necessary "hash" +# links with $OPENSSL_HOME/bin/c_rehash /etc/postfix/certs. A typical +# place for the CA-certs may also be $OPENSSL_HOME/certs, so there is +# no default and you explicitly have to set the value here! +# +# To use this option in chroot mode, this directory itself or a copy of it +# must be inside the chroot jail. +# +smtp_tls_CApath = /etc/postfix/certs + +# To get additional information during the TLS setup and negotiations +# you can increase the loglevel from 0..4: +# 0: No output about the TLS subsystem +# 1: Printout startup and certificate information +# 2: 1 + Printout of levels during negotiation +# 3: 2 + Hex and ASCII dump of negotiation process +# 4: 3 + Hex and ASCII dump of complete transmission after STARTTLS +# Use loglevel 3 only in case of problems. Use of loglevel 4 is strongly +# discouraged. +# +smtp_tls_loglevel = 0 + +# The server and client negotiate a session, which takes some computer time +# and network bandwidth. The session is cached only in the smtpd process +# actually using this session and is lost when the process dies. +# To share the session information between the smtp processes, a disc based +# session cache can be used based on the SDBM databases (routines included +# in Postfix/TLS). Since concurrent writing must be supported, only SDBM +# can be used. +# +smtp_tls_session_cache_database = sdbm:/etc/postfix/smtp_scache + +# The cached sessions time out after a certain amount of time. For Postfix/TLS +# I do not use the OpenSSL default of 300sec, but a longer time of 3600sec +# (=1 hour). RFC2246 recommends a maximum of 24 hours. +# +# smtp_tls_session_cache_timeout = 3600s + +# By default TLS is disabled, so no difference to plain postfix is visible. +# If you enable TLS it will be used when offered by the server. +# WARNING: I didn't have access to other software (except those explicitely +# listed) to test the interaction. On corresponding mailing list +# there was a discussion going on about MS exchange servers offering +# STARTTLS even if it is not configured, so it might be wise to not +# use this option on your central mail hub, as you don't know in advance +# whether you are going to hit such host. Use the recipient/site specific +# options instead. +# HINT: I have it switched on on my mailservers and did experience one +# single failure since client side TLS is implemented. (There was one +# misconfired MS Exchange server; I contacted ths admin.) Hence, I am happy +# with it running all the time, but I am interested in testing anyway. +# You have been warned, however :-) +# +# In case of failure, a "4xx" code is issued and the mail stays in the queue. +# +# Explicitely switch it on here, if you want it. +# +smtp_use_tls = yes + +# You can ENFORCE the use of TLS, so that only connections with TLS will +# be accepted. Additionally, the hostname of the receiving host is matched +# against the CommonName in the certificate. Also, the certificate must +# be verified "Ok", so that a CA trusted by the client must have issued +# the certificate. If the certificate doesn't verify or the hostname doesn't +# match, a "4xx" will be issued and the mail stays in the queue. +# The hostname used in the check is beyond question, as it must be the +# principle hostname (no CNAME allowed here). Checks are performed against +# all names provided as dNSNames in the SubjectAlternativeName. If no +# dNSNames are specified, the CommonName is checked. +# The behaviour may be changed with the smtp_tls_enforce_peername option +# +# This option is useful only if you are definitely sure that you will only +# connect to servers supporting RFC2487 _and_ with valid certificates. +# I use it for my clients which will only send email to one mailhub, which +# does offer the necessary STARTTLS support. +# +# smtp_enforce_tls = no + +# As of RFC2487 the requirements for hostname checking for MTA clients are +# not set. When in smtp_enforce_tls mode, the option smtp_tls_enforce_peername +# can be set to "no" to disable strict peername checking. In this case, the +# mail delivery will be continued, if a TLS connection was established +# _and_ the peer certificate passed verification _but_ regardless of the +# CommonName listed in the certificate. This option only applies to the +# default setting smtp_enforce_tls_mode, special settings in the +# smtp_tls_per_site table override smtp_tls_enforce_peername. +# +# This can make sense in closed environment where special CAs are created. +# If not used carefully, this option opens the danger of a "man-in-the-middle" +# attack (the CommonName of this attacker is logged). +# +# smtp_tls_enforce_peername = yes + +# As generally trying TLS can be a bad idea (some hosts offer STARTTLS but +# the negotiation will fail leading to unexplainable failures, it may be +# a good idea to decide based on the recipient or the mailhub to which you are +# connecting. +# +# Deciding per recipient may be difficult, since a singe email can have +# several recipients. We use the "nexthop" mechanism inside postfix. +# When an email is to be delivered, the "nexthop" is obtained. If it matches +# an entry in the smtp_tls_per_site list, appropriate action is taken. +# Since entries in the transport table or the use of a relay_host override +# the nexthop setting, in these cases the relay_host etc must be listed +# in the table. In any case, the hostname of the peer to be contacted is +# looked up (that is: the MX or the name of the host, if no MX is given). +# +# Special hint for enforcement mode: +# Since there is no secure mechanism for DNS lookups available, the +# recommended setup is: put the sensible domains with their mailhost +# into the transport table (since you can asure security of this table +# unlike DNS), then set MUST mode for this mailhost. +# +# Format of the table: +# The keys entries are on the left hand side, no wildcards allowed. On the +# right hand side the keywords NONE (don't use TLS at all), MAY (try to use +# STARTTLS if offered, no problem if not), MUST (enforce usage of STARTTLS, +# check server certificate CommonName against server FQDN), MUST_NOPEERMATCH +# (enforce usage of STARTTLS and verify certificate, but ignore differences +# between CommonName and server FQDN). +# dom.ain NONE +# host.dom.ain MAY +# important.host MUST +# some.host.dom.ain MUST_NOPEERMATCH +# +# If an entry is not matched, the default policy is applied; if the default +# policy is "enforce", NONE explicitely switches it off, otherwise the +# "enforce" mode is used even for MAY entries. +# +smtp_tls_per_site = hash:/etc/postfix/tls_per_site + +# The verification depth for server certificates. A depth of 1 is sufficient, +# if the certificate ist directly issued by a CA listed in the CA locations. +# The default value (5) should also suffice for longer chains (root CA issues +# special CA which then issues the actual certificate...) +# +# smtp_tls_scert_verifydepth = 5 + +# As we decide on a "per site" basis, wether to use TLS or not, it would be +# good to have a list of sites, that offered "STARTTLS'. We can collect it +# ourselves with this option. +# +# If activated and TLS is not already enabled for this host, a line is added +# to the logfile: +# postfix/smtp[pid]: Host offered STARTTLS: [name.of.host] +# +smtp_tls_note_starttls_offer = yes + +# To influence the cipher selection scheme, you can give cipherlist-string. +# A detailed description would go to far here, please refer to the openssl +# documentation. +# If you don't know what to do with it, simply don't touch it and leave the +# (openssl-)compiled in default! +# +# DO NOT USE " to enclose the string, just the string!!! +# +# smtp_tls_cipherlist = DEFAULT + +# The smtp_starttls_timeout parameter limits the time in seconds to write and +# read operations during TLS start and stop handhake procedures. +# +# In case of problems the client does NOT try the next address on +# the mail exchanger list. +# +# smtp_starttls_timeout = 300s + +# In order to seed the PRNG Pseude Random Number Generator, random data is +# needed. The PRNG pool is maintained by the "tlsmgr" daemon and is used +# (read) by the smtp[d] processes after adding some more entropy by stirring +# in time and process id. +# The file, which is from time to time rewritten by the tlsmgr, is created +# if not existant. A default value is given; the default should probably +# be on the /var partition but _not_ inside chroot jail. +# +# tls_random_exchange_name = /etc/postfix/prng_exch + +# To feed the PRNG pool, entropy is being read from an external source, +# both at startup and during run. +# Specify a good entropy source here, like EGD or /dev/urandom; make sure +# to only use non-blocking sources. +# In both cases, 32 bytes are read at each re-seeding event (which is an +# amount of 256bits and hence good enough for 128bit symmetric keys). +# You must specify the type of source: "dev:" for a device special file +# or "egd:" for a source with EGD compatible socket interface. A maximum +# 255 bytes is read from these sources in each step. +# If you specify a normal file, a larger amount of data can be read. +# +# The entropy source is queried again after a certain amount of time. The +# time is calculated using the PRNG, it is between 0 and the time specified, +# default is a maximum of 1 hour. +# +# tls_random_source = dev:/dev/urandom +tls_random_source = egd:/var/run/egd-pool +# tls_random_bytes = 32 +# tls_random_reseed_period = 3600s + +# The PRNG pool inside tlsmgr is used to re-generate the 1024 byte file +# being read by smtp[d]. The time, after which the exchange file is +# rewritten is calculated using the PRNG, it is between 0 and the time +# specified, default is a maximum of 60 seconds. +# +# tls_random_upd_period = 60s + +# If you have a entropy source available, that is not easily drained (like +# /dev/urandom), the daemons can also load additional entropy on startup from +# the source specified. By default an amount of 32 bytes is read, the +# equivalent to 256 bits. This is more than enough to generate a 128bit +# (or 168bit) session key, but we may have to generate more than one. +# Usage of this option may drain EGD (consider the case of 50 smtp starting +# up with a full queue and "postfix start", which will request 1600bytes +# of entropy). This is however not fatal, as long as "entropy" data could +# be read from the exchange file. +# +# tls_daemon_random_source = dev:/dev/urandom +tls_daemon_random_source = egd:/var/run/egd-pool +# tls_daemon_random_bytes = 32 + diff -ruN postfix-2.0.16-vanilla/man/man8/tlsmgr.8 postfix-2.0.16/man/man8/tlsmgr.8 --- postfix-2.0.16-vanilla/man/man8/tlsmgr.8 Thu Jan 1 01:00:00 1970 +++ postfix-2.0.16/man/man8/tlsmgr.8 Tue Sep 23 09:12:30 2003 @@ -0,0 +1,130 @@ +.TH TLSMGR 8 +.ad +.fi +.SH NAME +tlsmgr +\- +Postfix TLS session cache and PRNG handling manager +.SH SYNOPSIS +.na +.nf +\fBtlsmgr\fR [generic Postfix daemon options] +.SH DESCRIPTION +.ad +.fi +The tlsmgr process does housekeeping on the session cache database +files. It runs through the databases and removes expired entries +and entries written by older (incompatible) versions. + +The tlsmgr is responsible for the PRNG handling. The used internal +OpenSSL PRNG has a pool size of 8192 bits (= 1024 bytes). The pool +is initially seeded at startup from an external source (EGD or +/dev/urandom) and additional seed is obtained later during program +run at a configurable period. The exact time of seed query is +using random information and is equally distributed in the range of +[0-\fBtls_random_reseed_period\fR] with a \fBtls_random_reseed_period\fR +having a default of 1 hour. + +Tlsmgr can be run chrooted and with dropped privileges, as it will +connect to the entropy source at startup. + +The PRNG is additionally seeded internally by the data found in the +session cache and timevalues. + +Tlsmgr reads the old value of the exchange file at startup to keep +entropy already collected during previous runs. + +From the PRNG random pool a cryptographically strong 1024 byte random +sequence is written into the PRNG exchange file. The file is updated +periodically with the time changing randomly from +[0-\fBtls_random_prng_update_period\fR]. +.SH STANDARDS +.na +.nf +.SH SECURITY +.na +.nf +.ad +.fi +Tlsmgr is not security-sensitive. It only deals with external data +to be fed into the PRNG, the contents is never trusted. The session +cache housekeeping will only remove entries if expired and will never +touch the contents of the cached data. +.SH DIAGNOSTICS +.ad +.fi +Problems and transactions are logged to the syslog daemon. +.SH BUGS +.ad +.fi +There is no automatic means to limit the number of entries in the +session caches and/or the size of the session cache files. +.SH CONFIGURATION PARAMETERS +.na +.nf +.ad +.fi +The following \fBmain.cf\fR parameters are especially relevant to +this program. See the Postfix \fBmain.cf\fR file for syntax details +and for default values. Use the \fBpostfix reload\fR command after +a configuration change. +.SH Session Cache +.ad +.fi +.IP \fBsmtpd_tls_session_cache_database\fR +Name of the SDBM file (type sdbm:) containing the SMTP server session +cache. If the file does not exist, it is created. +.IP \fBsmtpd_tls_session_cache_timeout\fR +Expiry time of SMTP server session cache entries in seconds. Entries +older than this are removed from the session cache. A cleanup-run is +performed periodically every \fBsmtpd_tls_session_cache_timeout\fR +seconds. Default is 3600 (= 1 hour). +.IP \fBsmtp_tls_session_cache_database\fR +Name of the SDBM file (type sdbm:) containing the SMTP client session +cache. If the file does not exist, it is created. +.IP \fBsmtp_tls_session_cache_timeout\fR +Expiry time of SMTP client session cache entries in seconds. Entries +older than this are removed from the session cache. A cleanup-run is +performed periodically every \fBsmtp_tls_session_cache_timeout\fR +seconds. Default is 3600 (= 1 hour). +.SH Pseudo Random Number Generator +.ad +.fi +.IP \fBtls_random_source\fR +Name of the EGD socket or device or regular file to obtain entropy +from. The type of entropy source must be specified by preceding the +name with the appropriate type: egd:/path/to/egd_socket, +dev:/path/to/devicefile, or /path/to/regular/file. +tlsmgr opens \fBtls_random_source\fR and tries to read +\fBtls_random_bytes\fR from it. +.IP \fBtls_random_bytes\fR +Number of bytes to be read from \fBtls_random_source\fR. +Default value is 32 bytes. If using EGD, a maximum of 255 bytes is read. +.IP \fBtls_random_exchange_name\fR +Name of the file written by tlsmgr and read by smtp and smtpd at +startup. The length is 1024 bytes. Default value is +/etc/postfix/prng_exch. +.IP \fBtls_random_reseed_period\fR +Time in seconds until the next reseed from external sources is due. +This is the maximum value. The actual point in time is calculated +with a random factor equally distributed between 0 and this maximum +value. Default is 3600 (= 60 minutes). +.IP \fBtls_random_prng_update_period\fR +Time in seconds until the PRNG exchange file is updated with new +pseude random values. This is the maximum value. The actual point +in time is calculated with a random factor equally distributed +between 0 and this maximum value. Default is 60 (= 1 minute). +.SH SEE ALSO +.na +.nf +smtp(8) SMTP client +smtpd(8) SMTP server +.SH LICENSE +.na +.nf +.ad +.fi +The Secure Mailer license must be distributed with this software. +.SH AUTHOR(S) +.na +.nf diff -ruN postfix-2.0.16-vanilla/src/global/Makefile.in postfix-2.0.16/src/global/Makefile.in --- postfix-2.0.16-vanilla/src/global/Makefile.in Sun Sep 14 02:03:59 2003 +++ postfix-2.0.16/src/global/Makefile.in Tue Sep 23 09:12:30 2003 @@ -20,7 +20,8 @@ tok822_resolve.c tok822_rewrite.c tok822_tree.c xtext.c bounce_log.c \ flush_clnt.c mail_conf_time.c mbox_conf.c mbox_open.c abounce.c \ verp_sender.c match_parent_style.c mime_state.c header_token.c \ - strip_addr.c virtual8_maps.c hold_message.c dict_proxy.c mail_dict.c + strip_addr.c virtual8_maps.c hold_message.c dict_proxy.c mail_dict.c \ + pfixtls.c OBJS = been_here.o bounce.o canon_addr.o cleanup_strerror.o clnt_stream.o \ debug_peer.o debug_process.o defer.o deliver_completed.o \ deliver_flock.o deliver_pass.o deliver_request.o domain_list.o \ @@ -42,7 +43,8 @@ tok822_resolve.o tok822_rewrite.o tok822_tree.o xtext.o bounce_log.o \ flush_clnt.o mail_conf_time.o mbox_conf.o mbox_open.o abounce.o \ verp_sender.o match_parent_style.o mime_state.o header_token.o \ - strip_addr.o virtual8_maps.o hold_message.o dict_proxy.o mail_dict.o + strip_addr.o virtual8_maps.o hold_message.o dict_proxy.o mail_dict.o \ + pfixtls.o HDRS = been_here.h bounce.h canon_addr.h cleanup_user.h clnt_stream.h \ config.h debug_peer.h debug_process.h defer.h deliver_completed.h \ deliver_flock.h deliver_pass.h deliver_request.h domain_list.h \ @@ -61,7 +63,7 @@ mbox_conf.h mbox_open.h abounce.h qmqp_proto.h verp_sender.h \ match_parent_style.h quote_flags.h mime_state.h header_token.h \ lex_822.h strip_addr.h virtual8_maps.h hold_message.h dict_proxy.h \ - mail_dict.h + mail_dict.h pfixtls.h TESTSRC = rec2stream.c stream2rec.c recdump.c WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ @@ -1262,3 +1264,16 @@ xtext.o: ../../include/vbuf.h xtext.o: ../../include/vstring.h xtext.o: xtext.h +pfixtls.o: pfixtls.c +pfixtls.o: ../../include/sys_defs.h +pfixtls.o: ../../include/iostuff.h +pfixtls.o: ../../include/mymalloc.h +pfixtls.o: ../../include/vstring.h +pfixtls.o: ../../include/vstream.h +pfixtls.o: ../../include/dict.h +pfixtls.o: ../../include/myflock.h +pfixtls.o: ../../include/stringops.h +pfixtls.o: ../../include/msg.h +pfixtls.o: ../../include/connect.h +pfixtls.o: mail_params.h +pfixtls.o: pfixtls.h diff -ruN postfix-2.0.16-vanilla/src/global/mail_params.c postfix-2.0.16/src/global/mail_params.c --- postfix-2.0.16-vanilla/src/global/mail_params.c Fri Dec 13 23:04:31 2002 +++ postfix-2.0.16/src/global/mail_params.c Tue Sep 23 09:12:30 2003 @@ -225,6 +225,31 @@ int var_in_flow_delay; char *var_par_dom_match; char *var_config_dirs; +char *var_tls_rand_exch_name; +char *var_smtpd_tls_cert_file; +char *var_smtpd_tls_key_file; +char *var_smtpd_tls_dcert_file; +char *var_smtpd_tls_dkey_file; +char *var_smtpd_tls_CAfile; +char *var_smtpd_tls_CApath; +char *var_smtpd_tls_cipherlist; +char *var_smtpd_tls_dh512_param_file; +char *var_smtpd_tls_dh1024_param_file; +int var_smtpd_tls_loglevel; +char *var_smtpd_tls_scache_db; +int var_smtpd_tls_scache_timeout; +char *var_smtp_tls_cert_file; +char *var_smtp_tls_key_file; +char *var_smtp_tls_dcert_file; +char *var_smtp_tls_dkey_file; +char *var_smtp_tls_CAfile; +char *var_smtp_tls_CApath; +char *var_smtp_tls_cipherlist; +int var_smtp_tls_loglevel; +char *var_smtp_tls_scache_db; +int var_smtp_tls_scache_timeout; +char *var_tls_daemon_rand_source; +int var_tls_daemon_rand_bytes; char *var_import_environ; char *var_export_environ; @@ -467,6 +492,26 @@ VAR_SHOWQ_SERVICE, DEF_SHOWQ_SERVICE, &var_showq_service, 1, 0, VAR_ERROR_SERVICE, DEF_ERROR_SERVICE, &var_error_service, 1, 0, VAR_FLUSH_SERVICE, DEF_FLUSH_SERVICE, &var_flush_service, 1, 0, + VAR_TLS_RAND_EXCH_NAME, DEF_TLS_RAND_EXCH_NAME, &var_tls_rand_exch_name, 0, 0, + VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0, + VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0, + VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0, + VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0, + VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0, + VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0, + VAR_SMTPD_TLS_CLIST, DEF_SMTPD_TLS_CLIST, &var_smtpd_tls_cipherlist, 0, 0, + VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0, + VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0, + VAR_SMTPD_TLS_SCACHE_DB, DEF_SMTPD_TLS_SCACHE_DB, &var_smtpd_tls_scache_db, 0, 0, + VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_SMTP_TLS_CLIST, DEF_SMTP_TLS_CLIST, &var_smtp_tls_cipherlist, 0, 0, + VAR_SMTP_TLS_SCACHE_DB, DEF_SMTP_TLS_SCACHE_DB, &var_smtp_tls_scache_db, 0, 0, + VAR_TLS_DAEMON_RAND_SOURCE, DEF_TLS_DAEMON_RAND_SOURCE, &var_tls_daemon_rand_source, 0, 0, 0, }; static CONFIG_STR_FN_TABLE function_str_defaults_2[] = { @@ -489,6 +534,9 @@ VAR_TOKEN_LIMIT, DEF_TOKEN_LIMIT, &var_token_limit, 1, 0, VAR_MIME_MAXDEPTH, DEF_MIME_MAXDEPTH, &var_mime_maxdepth, 1, 0, VAR_MIME_BOUND_LEN, DEF_MIME_BOUND_LEN, &var_mime_bound_len, 1, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_TLS_DAEMON_RAND_BYTES, DEF_TLS_DAEMON_RAND_BYTES, &var_tls_daemon_rand_bytes, 0, 0, 0, }; static CONFIG_TIME_TABLE time_defaults[] = { @@ -499,6 +547,8 @@ VAR_FORK_DELAY, DEF_FORK_DELAY, &var_fork_delay, 1, 0, VAR_FLOCK_DELAY, DEF_FLOCK_DELAY, &var_flock_delay, 1, 0, VAR_FLOCK_STALE, DEF_FLOCK_STALE, &var_flock_stale, 1, 0, + VAR_SMTPD_TLS_SCACHTIME, DEF_SMTPD_TLS_SCACHTIME, &var_smtpd_tls_scache_timeout, 0, 0, + VAR_SMTP_TLS_SCACHTIME, DEF_SMTP_TLS_SCACHTIME, &var_smtp_tls_scache_timeout, 0, 0, VAR_DAEMON_TIMEOUT, DEF_DAEMON_TIMEOUT, &var_daemon_timeout, 1, 0, VAR_IN_FLOW_DELAY, DEF_IN_FLOW_DELAY, &var_in_flow_delay, 0, 10, 0, diff -ruN postfix-2.0.16-vanilla/src/global/mail_params.h postfix-2.0.16/src/global/mail_params.h --- postfix-2.0.16-vanilla/src/global/mail_params.h Mon Mar 3 23:07:03 2003 +++ postfix-2.0.16/src/global/mail_params.h Tue Sep 23 09:12:31 2003 @@ -487,6 +487,34 @@ #define DEF_DUP_FILTER_LIMIT 1000 extern int var_dup_filter_limit; +#define VAR_TLS_RAND_EXCH_NAME "tls_random_exchange_name" +#define DEF_TLS_RAND_EXCH_NAME "${config_directory}/prng_exch" +extern char *var_tls_rand_exch_name; + +#define VAR_TLS_RAND_SOURCE "tls_random_source" +#define DEF_TLS_RAND_SOURCE "" +extern char *var_tls_rand_source; + +#define VAR_TLS_RAND_BYTES "tls_random_bytes" +#define DEF_TLS_RAND_BYTES 32 +extern int var_tls_rand_bytes; + +#define VAR_TLS_DAEMON_RAND_SOURCE "tls_daemon_random_source" +#define DEF_TLS_DAEMON_RAND_SOURCE "" +extern char *var_tls_daemon_rand_source; + +#define VAR_TLS_DAEMON_RAND_BYTES "tls_daemon_random_bytes" +#define DEF_TLS_DAEMON_RAND_BYTES 32 +extern int var_tls_daemon_rand_bytes; + +#define VAR_TLS_RESEED_PERIOD "tls_random_reseed_period" +#define DEF_TLS_RESEED_PERIOD "3600s" +extern int var_tls_reseed_period; + +#define VAR_TLS_PRNG_UPD_PERIOD "tls_random_prng_update_period" +#define DEF_TLS_PRNG_UPD_PERIOD "60s" +extern int var_tls_prng_upd_period; + /* * Queue manager: relocated databases. */ @@ -711,6 +739,10 @@ #define DEF_SMTP_HELO_TMOUT "300s" extern int var_smtp_helo_tmout; +#define VAR_SMTP_STARTTLS_TMOUT "smtp_starttls_timeout" +#define DEF_SMTP_STARTTLS_TMOUT "300s" +extern int var_smtp_starttls_tmout; + #define VAR_SMTP_MAIL_TMOUT "smtp_mail_timeout" #define DEF_SMTP_MAIL_TMOUT "300s" extern int var_smtp_mail_tmout; @@ -800,6 +832,10 @@ #define DEF_SMTPD_TMOUT "300s" extern int var_smtpd_tmout; +#define VAR_SMTPD_STARTTLS_TMOUT "smtpd_starttls_timeout" +#define DEF_SMTPD_STARTTLS_TMOUT "300s" +extern int var_smtpd_starttls_tmout; + #define VAR_SMTPD_RCPT_LIMIT "smtpd_recipient_limit" #define DEF_SMTPD_RCPT_LIMIT 1000 extern int var_smtpd_rcpt_limit; @@ -828,6 +864,150 @@ #define DEF_SMTPD_NOOP_CMDS "" extern char *var_smtpd_noop_cmds; +#define VAR_SMTPD_TLS_WRAPPER "smtpd_tls_wrappermode" +#define DEF_SMTPD_TLS_WRAPPER 0 +extern bool var_smtpd_tls_wrappermode; + +#define VAR_SMTPD_USE_TLS "smtpd_use_tls" +#define DEF_SMTPD_USE_TLS 0 +extern bool var_smtpd_use_tls; + +#define VAR_SMTPD_ENFORCE_TLS "smtpd_enforce_tls" +#define DEF_SMTPD_ENFORCE_TLS 0 +extern bool var_smtpd_enforce_tls; + +#define VAR_SMTPD_TLS_AUTH_ONLY "smtpd_tls_auth_only" +#define DEF_SMTPD_TLS_AUTH_ONLY 0 +extern bool var_smtpd_tls_auth_only; + +#define VAR_SMTPD_TLS_ACERT "smtpd_tls_ask_ccert" +#define DEF_SMTPD_TLS_ACERT 0 +extern bool var_smtpd_tls_ask_ccert; + +#define VAR_SMTPD_TLS_RCERT "smtpd_tls_req_ccert" +#define DEF_SMTPD_TLS_RCERT 0 +extern bool var_smtpd_tls_req_ccert; + +#define VAR_SMTPD_TLS_CCERT_VD "smtpd_tls_ccert_verifydepth" +#define DEF_SMTPD_TLS_CCERT_VD 5 +extern int var_smtpd_tls_ccert_vd; + +#define VAR_SMTPD_TLS_CERT_FILE "smtpd_tls_cert_file" +#define DEF_SMTPD_TLS_CERT_FILE "" +extern char *var_smtpd_tls_cert_file; + +#define VAR_SMTPD_TLS_KEY_FILE "smtpd_tls_key_file" +#define DEF_SMTPD_TLS_KEY_FILE "$smtpd_tls_cert_file" +extern char *var_smtpd_tls_key_file; + +#define VAR_SMTPD_TLS_DCERT_FILE "smtpd_tls_dcert_file" +#define DEF_SMTPD_TLS_DCERT_FILE "" +extern char *var_smtpd_tls_dcert_file; + +#define VAR_SMTPD_TLS_DKEY_FILE "smtpd_tls_dkey_file" +#define DEF_SMTPD_TLS_DKEY_FILE "$smtpd_tls_dcert_file" +extern char *var_smtpd_tls_dkey_file; + +#define VAR_SMTPD_TLS_CA_FILE "smtpd_tls_CAfile" +#define DEF_SMTPD_TLS_CA_FILE "" +extern char *var_smtpd_tls_CAfile; + +#define VAR_SMTPD_TLS_CA_PATH "smtpd_tls_CApath" +#define DEF_SMTPD_TLS_CA_PATH "" +extern char *var_smtpd_tls_CApath; + +#define VAR_SMTPD_TLS_CLIST "smtpd_tls_cipherlist" +#define DEF_SMTPD_TLS_CLIST "" +extern char *var_smtpd_tls_cipherlist; + +#define VAR_SMTPD_TLS_512_FILE "smtpd_tls_dh512_param_file" +#define DEF_SMTPD_TLS_512_FILE "" +extern char *var_smtpd_tls_dh512_param_file; + +#define VAR_SMTPD_TLS_1024_FILE "smtpd_tls_dh1024_param_file" +#define DEF_SMTPD_TLS_1024_FILE "" +extern char *var_smtpd_tls_dh1024_param_file; + +#define VAR_SMTPD_TLS_LOGLEVEL "smtpd_tls_loglevel" +#define DEF_SMTPD_TLS_LOGLEVEL 0 +extern int var_smtpd_tls_loglevel; + +#define VAR_SMTPD_TLS_RECHEAD "smtpd_tls_received_header" +#define DEF_SMTPD_TLS_RECHEAD 0 +extern bool var_smtpd_tls_received_header; + +#define VAR_SMTPD_TLS_SCACHE_DB "smtpd_tls_session_cache_database" +#define DEF_SMTPD_TLS_SCACHE_DB "" +extern char *var_smtpd_tls_scache_db; + +#define VAR_SMTPD_TLS_SCACHTIME "smtpd_tls_session_cache_timeout" +#define DEF_SMTPD_TLS_SCACHTIME "3600s" +extern int var_smtpd_tls_scache_timeout; + +#define VAR_SMTP_TLS_PER_SITE "smtp_tls_per_site" +#define DEF_SMTP_TLS_PER_SITE "" +extern char *var_smtp_tls_per_site; + +#define VAR_SMTP_USE_TLS "smtp_use_tls" +#define DEF_SMTP_USE_TLS 0 +extern bool var_smtp_use_tls; + +#define VAR_SMTP_ENFORCE_TLS "smtp_enforce_tls" +#define DEF_SMTP_ENFORCE_TLS 0 +extern bool var_smtp_enforce_tls; + +#define VAR_SMTP_TLS_ENFORCE_PN "smtp_tls_enforce_peername" +#define DEF_SMTP_TLS_ENFORCE_PN 1 +extern bool var_smtp_tls_enforce_peername; + +#define VAR_SMTP_TLS_SCERT_VD "smtp_tls_scert_verifydepth" +#define DEF_SMTP_TLS_SCERT_VD 5 +extern int var_smtp_tls_scert_vd; + +#define VAR_SMTP_TLS_CERT_FILE "smtp_tls_cert_file" +#define DEF_SMTP_TLS_CERT_FILE "" +extern char *var_smtp_tls_cert_file; + +#define VAR_SMTP_TLS_KEY_FILE "smtp_tls_key_file" +#define DEF_SMTP_TLS_KEY_FILE "$smtp_tls_cert_file" +extern char *var_smtp_tls_key_file; + +#define VAR_SMTP_TLS_DCERT_FILE "smtp_tls_dcert_file" +#define DEF_SMTP_TLS_DCERT_FILE "" +extern char *var_smtp_tls_dcert_file; + +#define VAR_SMTP_TLS_DKEY_FILE "smtp_tls_dkey_file" +#define DEF_SMTP_TLS_DKEY_FILE "$smtp_tls_dcert_file" +extern char *var_smtp_tls_dkey_file; + +#define VAR_SMTP_TLS_CA_FILE "smtp_tls_CAfile" +#define DEF_SMTP_TLS_CA_FILE "" +extern char *var_smtp_tls_CAfile; + +#define VAR_SMTP_TLS_CA_PATH "smtp_tls_CApath" +#define DEF_SMTP_TLS_CA_PATH "" +extern char *var_smtp_tls_CApath; + +#define VAR_SMTP_TLS_CLIST "smtp_tls_cipherlist" +#define DEF_SMTP_TLS_CLIST "" +extern char *var_smtp_tls_cipherlist; + +#define VAR_SMTP_TLS_LOGLEVEL "smtp_tls_loglevel" +#define DEF_SMTP_TLS_LOGLEVEL 0 +extern int var_smtp_tls_loglevel; + +#define VAR_SMTP_TLS_NOTEOFFER "smtp_tls_note_starttls_offer" +#define DEF_SMTP_TLS_NOTEOFFER 0 +extern bool var_smtp_tls_note_starttls_offer; + +#define VAR_SMTP_TLS_SCACHE_DB "smtp_tls_session_cache_database" +#define DEF_SMTP_TLS_SCACHE_DB "" +extern char *var_smtp_tls_scache_db; + +#define VAR_SMTP_TLS_SCACHTIME "smtp_tls_session_cache_timeout" +#define DEF_SMTP_TLS_SCACHTIME "3600s" +extern int var_smtp_tls_scache_timeout; + /* * SASL authentication support, SMTP server side. */ @@ -839,6 +1019,10 @@ #define DEF_SMTPD_SASL_OPTS "noanonymous" extern char *var_smtpd_sasl_opts; +#define VAR_SMTPD_SASL_TLS_OPTS "smtpd_sasl_tls_security_options" +#define DEF_SMTPD_SASL_TLS_OPTS "$smtpd_sasl_security_options" +extern char *var_smtpd_sasl_opts; + #define VAR_SMTPD_SASL_REALM "smtpd_sasl_local_domain" #define DEF_SMTPD_SASL_REALM "" extern char *var_smtpd_sasl_realm; @@ -864,6 +1048,14 @@ #define DEF_SMTP_SASL_OPTS "noplaintext, noanonymous" extern char *var_smtp_sasl_opts; +#define VAR_SMTP_SASL_TLS_OPTS "smtp_sasl_tls_security_options" +#define DEF_SMTP_SASL_TLS_OPTS "$var_smtp_sasl_opts" +extern char *var_smtp_sasl_tls_opts; + +#define VAR_SMTP_SASL_TLSV_OPTS "smtp_sasl_tls_verified_security_options" +#define DEF_SMTP_SASL_TLSV_OPTS "$var_smtp_sasl_tls_opts" +extern char *var_smtp_sasl_tls_verified_opts; + /* * LMTP server. The soft error limit determines how many errors an LMTP * client may make before we start to slow down; the hard error limit @@ -1137,6 +1329,10 @@ #define DEF_RELAY_RCPT_CODE 550 extern int var_relay_rcpt_code; +#define VAR_RELAY_CCERTS "relay_clientcerts" +#define DEF_RELAY_CCERTS "" +extern char *var_relay_ccerts; + #define VAR_CLIENT_CHECKS "smtpd_client_restrictions" #define DEF_CLIENT_CHECKS "" extern char *var_client_checks; @@ -1229,6 +1425,8 @@ #define PERMIT_AUTH_DEST "permit_auth_destination" #define REJECT_UNAUTH_DEST "reject_unauth_destination" #define CHECK_RELAY_DOMAINS "check_relay_domains" +#define PERMIT_TLS_CLIENTCERTS "permit_tls_clientcerts" +#define PERMIT_TLS_ALL_CLIENTCERTS "permit_tls_all_clientcerts" #define VAR_RELAY_CODE "relay_domains_reject_code" #define DEF_RELAY_CODE 554 extern int var_relay_code; diff -ruN postfix-2.0.16-vanilla/src/global/mail_proto.h postfix-2.0.16/src/global/mail_proto.h --- postfix-2.0.16-vanilla/src/global/mail_proto.h Sun Jan 12 18:45:43 2003 +++ postfix-2.0.16/src/global/mail_proto.h Tue Sep 23 09:12:31 2003 @@ -41,6 +41,7 @@ #define MAIL_SERVICE_LOCAL "local" #define MAIL_SERVICE_PICKUP "pickup" #define MAIL_SERVICE_QUEUE "qmgr" +#define MAIL_SERVICE_TLSMGR "tlsmgr" #define MAIL_SERVICE_RESOLVE "resolve" #define MAIL_SERVICE_REWRITE "rewrite" #define MAIL_SERVICE_VIRTUAL "virtual" diff -ruN postfix-2.0.16-vanilla/src/global/pfixtls.c postfix-2.0.16/src/global/pfixtls.c --- postfix-2.0.16-vanilla/src/global/pfixtls.c Thu Jan 1 01:00:00 1970 +++ postfix-2.0.16/src/global/pfixtls.c Tue Sep 23 09:12:31 2003 @@ -0,0 +1,2821 @@ +/*++ +/* NAME +/* pfixtls +/* SUMMARY +/* interface to openssl routines +/* SYNOPSIS +/* #include +/* +/* const long scache_db_version; +/* const long openssl_version; +/* +/* int pfixtls_serverengine; +/* +/* int pfixtls_clientengine; +/* +/* int pfixtls_timed_read(fd, buf, len, timeout, unused_context) +/* int fd; +/* void *buf; +/* unsigned len; +/* int timeout; +/* void *context; +/* +/* int pfixtls_timed_write(fd, buf, len, timeout, unused_context); +/* int fd; +/* void *buf; +/* unsigned len; +/* int timeout; +/* void *context; +/* +/* int pfixtls_init_serverengine(verifydepth, askcert); +/* int verifydepth; +/* int askcert; +/* +/* int pfixtls_start_servertls(stream, timeout, peername, peeraddr, +/* tls_info, requirecert); +/* VSTREAM *stream; +/* int timeout; +/* const char *peername; +/* const char *peeraddr; +/* tls_info_t *tls_info; +/* int requirecert; +/* +/* int pfixtls_stop_servertls(stream, failure, tls_info); +/* VSTREAM *stream; +/* int failure; +/* tls_info_t *tls_info; +/* +/* int pfixtls_init_clientengine(verifydepth); +/* int verifydepth; +/* +/* int pfixtls_start_clienttls(stream, timeout, peername, peeraddr, +/* tls_info); +/* VSTREAM *stream; +/* int timeout; +/* const char *peername; +/* const char *peeraddr; +/* tls_info_t *tls_info; +/* +/* int pfixtls_stop_clienttls(stream, failure, tls_info); +/* VSTREAM *stream; +/* int failure; +/* tls_info_t *tls_info; +/* +/* DESCRIPTION +/* This module is the interface between Postfix and the OpenSSL library. +/* +/* pfixtls_timed_read() reads the requested number of bytes calling +/* SSL_read(). pfixtls_time_read() will only be called indirect +/* as a VSTREAM_FN function. +/* pfixtls_timed_write() is the corresponding write function. +/* +/* pfixtls_init_serverengine() is called once when smtpd is started +/* in order to initialize as much of the TLS stuff as possible. +/* The certificate handling is also decided during the setup phase, +/* so that a peer specific handling is not possible. +/* +/* pfixtls_init_clientengine() is the corresponding function called +/* in smtp. Here we take the peer's (server's) certificate in any +/* case. +/* +/* pfixtls_start_servertls() activates the TLS feature for the VSTREAM +/* passed as argument. We expect that all buffers are flushed and the +/* TLS handshake can begin immediately. Information about the peer +/* is stored into the tls_info structure passed as argument. +/* +/* pfixtls_stop_servertls() sends the "close notify" alert via +/* SSL_shutdown() to the peer and resets all connection specific +/* TLS data. As RFC2487 does not specify a seperate shutdown, it +/* is supposed that the underlying TCP connection is shut down +/* immediately afterwards, so we don't care about additional data +/* coming through the channel. +/* If the failure flag is set, the session is cleared from the cache. +/* +/* pfixtls_start_clienttls() and pfixtls_stop_clienttls() are the +/* corresponding functions for smtp. +/* +/* Once the TLS connection is initiated, information about the TLS +/* state is available via the tls_info structure: +/* protocol holds the protocol name (SSLv2, SSLv3, TLSv1), +/* tls_info->cipher_name the cipher name (e.g. RC4/MD5), +/* tls_info->cipher_usebits the number of bits actually used (e.g. 40), +/* tls_info->cipher_algbits the number of bits the algorithm is based on +/* (e.g. 128). +/* The last two values may be different when talking to a crippled +/* - ahem - export controled peer (e.g. 40/128). +/* +/* The status of the peer certificate verification is available in +/* pfixtls_peer_verified. It is set to 1, when the certificate could +/* be verified. +/* If the peer offered a certifcate, part of the certificate data are +/* available as: +/* tls_info->peer_subject X509v3-oneline with the DN of the peer +/* tls_info->peer_CN extracted CommonName of the peer +/* tls_info->peer_issuer X509v3-oneline with the DN of the issuer +/* tls_info->peer_CN extracted CommonName of the issuer +/* tls_info->PEER_FINGERPRINT fingerprint of the certificate +/* +/* DESCRIPTION (SESSION CACHING) +/* In order to achieve high performance when using a lot of connections +/* with TLS, session caching is implemented. It reduces both the CPU load +/* (less cryptograpic operations) and the network load (the amount of +/* certificate data exchanged is reduced). +/* Since postfix uses a setup of independent processes for receiving +/* and sending email, the processes must exchange the session information. +/* Several connections at the same time between the identical peers can +/* occur, so uniqueness and race conditions have to be taken into +/* account. +/* I have checked both Apache-SSL (Ben Laurie), using a seperate "gcache" +/* process and Apache mod_ssl (Ralf S. Engelshall), using shared memory +/* between several identical processes spawned from one parent. +/* +/* Postfix/TLS uses a database approach based on the internal "dict" +/* interface. Since the session cache information is approximately +/* 1300 bytes binary data, it will not fit into the dbm/ndbm model. +/* It also needs write access to the database, ruling out most other +/* interface, leaving Berkeley DB, which however cannot handle concurrent +/* access by several processes. Hence a modified SDBM (public domain DBM) +/* with enhanced buffer size is used and concurrent write capability +/* is used. SDBM is part of Postfix/TLS. +/* +/* Realization: +/* Both (client and server) session cache are realized by individual +/* cache databases. A common database would not make sense, since the +/* key criteria are different (session ID for server, peername for +/* client). +/* +/* Server side: +/* Session created by OpenSSL have a 32 byte session id, yielding a +/* 64 char file name. I consider these sessions to be unique. If they +/* are not, the last session will win, overwriting the older one in +/* the database. Remember: everything that is lost is a temporary +/* information and not more than a renegotiation will happen. +/* Originating from the same client host, several sessions can come +/* in (e.g. from several users sending mail with Netscape at the same +/* time), so the session id is the correct identifier; the hostname +/* is of no importance, here. +/* +/* Client side: +/* We cannot recall sessions based on their session id, because we would +/* have to check every session on disk for a matching server name, so +/* the lookup has to be done based on the FQDN of the peer (receiving +/* host). +/* With regard to uniqueness, we might experience several open connections +/* to the same server at the same time. This is even very likely to +/* happen, since we might have several mails for the same destination +/* in the queue, when a queue run is started. So several smtp's might +/* negotiate sessions at the same time. We can however only save one +/* session for one host. +/* Like on the server side, the "last write" wins. The reason is +/* quite simple. If we don't want to overwrite old sessions, an old +/* session file will just stay in place until it is expired. In the +/* meantime we would lose "fresh" session however. So we will keep the +/* fresh one instead to avoid unnecessary renegotiations. +/* +/* Session lifetime: +/* RFC2246 recommends a session lifetime of less than 24 hours. The +/* default is 300 seconds (5 minutes) for OpenSSL and is also used +/* this way in e.g. mod_ssl. The typical usage for emails might be +/* humans typing in emails and sending them, which might take just +/* a while, so I think 3600 seconds (1 hour) is a good compromise. +/* If the environment is save (the cached session contains secret +/* key data), one might even consider using a longer timeout. Anyway, +/* since everlasting sessions must be avoided, the session timeout +/* is done based on the creation date of the session and so each +/* session will timeout eventually. +/* +/* Connection failures: +/* RFC2246 requires us to remove sessions if something went wrong. +/* Since the in-memory session cache of other smtp[d] processes cannot +/* be controlled by simple means, we completely rely on the disc +/* based session caching and remove all sessions from memory after +/* connection closure. +/* +/* Cache cleanup: +/* Since old entries have to be removed from the session cache, a +/* cleanup process is needed that runs through the collected session +/* files on regular basis. The task is performed by tlsmgr based on +/* the timestamp created by pfixtls and included in the saved session, +/* so that tlsmgr has not to care about the SSL_SESSION internal data. +/* +/* BUGS +/* The memory allocation policy of the OpenSSL library is not well +/* documented, especially when loading sessions from disc. Hence there +/* might be memory leaks. +/* +/* LICENSE +/* AUTHOR(S) +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include +#include +#include +#include /* gettimeofday, not in POSIX */ +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "mail_params.h" +#include "pfixtls.h" + +#define STR vstring_str + +const tls_info_t tls_info_zero = { + 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0 +}; + +#ifdef USE_SSL + +/* OpenSSL library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* We must keep some of the info available */ +static const char hexcodes[] = "0123456789ABCDEF"; + +/* + * When saving sessions, we want to make sure, that the lenght of the key + * is somehow limited. When saving client sessions, the hostname is used + * as key. According to HP-UX 10.20, MAXHOSTNAMELEN=64. Maybe new standards + * will increase this value, but as this will break compatiblity with existing + * implementations, we won't see this for long. We therefore choose a limit + * of 64 bytes. + * The length of the (TLS) session id can be up to 32 bytes according to + * RFC2246, so it fits well into the 64bytes limit. + */ +#define ID_MAXLENGTH 64 /* Max ID length in bytes */ + +/* + * The session_id_context is set, such that the client knows which services + * on a host share the same session information (on the postfix host may + * as well run a TLS-enabled webserver. + */ +static char server_session_id_context[] = "Postfix/TLS"; /* anything will do */ +static int TLScontext_index = -1; +static int TLSpeername_index = -1; +static int do_dump = 0; +static DH *dh_512 = NULL, *dh_1024 = NULL; +static SSL_CTX *ctx = NULL; + +static int rand_exch_fd = -1; + +static DICT *scache_db = NULL; +const long scache_db_version = 0x00000003L; +const long openssl_version = OPENSSL_VERSION_NUMBER; + + +int pfixtls_serverengine = 0; +static int pfixtls_serveractive = 0; /* available or not */ + +int pfixtls_clientengine = 0; +static int pfixtls_clientactive = 0; /* available or not */ + +/* + * Define a maxlength for certificate onelines. The length is checked by + * all routines when copying. + */ +#define CCERT_BUFSIZ 256 + +typedef struct { + SSL *con; + BIO *internal_bio; /* postfix/TLS side of pair */ + BIO *network_bio; /* netsork side of pair */ + char peer_subject[CCERT_BUFSIZ]; + char peer_issuer[CCERT_BUFSIZ]; + char peer_CN[CCERT_BUFSIZ]; + char issuer_CN[CCERT_BUFSIZ]; + unsigned char md[EVP_MAX_MD_SIZE]; + char fingerprint[EVP_MAX_MD_SIZE * 3]; + char peername_save[129]; + int enforce_verify_errors; + int enforce_CN; + int hostname_matched; +} TLScontext_t; + +typedef struct { + int pid; + struct timeval tv; +} randseed_t; + +static randseed_t randseed; + +/* + * Finally some "backup" DH-Parameters to be loaded, if no parameters are + * explicitely loaded from file. + */ +static unsigned char dh512_p[] = { + 0x88, 0x3F, 0x00, 0xAF, 0xFC, 0x0C, 0x8A, 0xB8, 0x35, 0xCD, 0xE5, 0xC2, + 0x0F, 0x55, 0xDF, 0x06, 0x3F, 0x16, 0x07, 0xBF, 0xCE, 0x13, 0x35, 0xE4, + 0x1C, 0x1E, 0x03, 0xF3, 0xAB, 0x17, 0xF6, 0x63, 0x50, 0x63, 0x67, 0x3E, + 0x10, 0xD7, 0x3E, 0xB4, 0xEB, 0x46, 0x8C, 0x40, 0x50, 0xE6, 0x91, 0xA5, + 0x6E, 0x01, 0x45, 0xDE, 0xC9, 0xB1, 0x1F, 0x64, 0x54, 0xFA, 0xD9, 0xAB, + 0x4F, 0x70, 0xBA, 0x5B, +}; + +static unsigned char dh512_g[] = { + 0x02, +}; + +static unsigned char dh1024_p[] = { + 0xB0, 0xFE, 0xB4, 0xCF, 0xD4, 0x55, 0x07, 0xE7, 0xCC, 0x88, 0x59, 0x0D, + 0x17, 0x26, 0xC5, 0x0C, 0xA5, 0x4A, 0x92, 0x23, 0x81, 0x78, 0xDA, 0x88, + 0xAA, 0x4C, 0x13, 0x06, 0xBF, 0x5D, 0x2F, 0x9E, 0xBC, 0x96, 0xB8, 0x51, + 0x00, 0x9D, 0x0C, 0x0D, 0x75, 0xAD, 0xFD, 0x3B, 0xB1, 0x7E, 0x71, 0x4F, + 0x3F, 0x91, 0x54, 0x14, 0x44, 0xB8, 0x30, 0x25, 0x1C, 0xEB, 0xDF, 0x72, + 0x9C, 0x4C, 0xF1, 0x89, 0x0D, 0x68, 0x3F, 0x94, 0x8E, 0xA4, 0xFB, 0x76, + 0x89, 0x18, 0xB2, 0x91, 0x16, 0x90, 0x01, 0x99, 0x66, 0x8C, 0x53, 0x81, + 0x4E, 0x27, 0x3D, 0x99, 0xE7, 0x5A, 0x7A, 0xAF, 0xD5, 0xEC, 0xE2, 0x7E, + 0xFA, 0xED, 0x01, 0x18, 0xC2, 0x78, 0x25, 0x59, 0x06, 0x5C, 0x39, 0xF6, + 0xCD, 0x49, 0x54, 0xAF, 0xC1, 0xB1, 0xEA, 0x4A, 0xF9, 0x53, 0xD0, 0xDF, + 0x6D, 0xAF, 0xD4, 0x93, 0xE7, 0xBA, 0xAE, 0x9B, +}; + +static unsigned char dh1024_g[] = { + 0x02, +}; + +/* + * DESCRIPTION: Keeping control of the network interface using BIO-pairs. + * + * When the TLS layer is active, all input/output must be filtered through + * it. On the other hand to handle timeout conditions, full control over + * the network socket must be kept. This rules out the "normal way" of + * connecting the TLS layer directly to the socket. + * The TLS layer is realized with a BIO-pair: + * + * postfix | TLS-engine + * | | + * +--------> SSL_operations() + * | /\ || + * | || \/ + * | BIO-pair (internal_bio) + * +--------< BIO-pair (network_bio) + * | | + * socket | + * + * The normal postfix operations connect to the SSL operations to send + * and retrieve (cleartext) data. Inside the TLS-engine the data are converted + * to/from TLS protocol. The TLS functionality itself is only connected to + * the internal_bio and hence only has status information about this internal + * interface. + * Thus, if the SSL_operations() return successfully (SSL_ERROR_NONE) or want + * to read (SSL_ERROR_WANT_READ) there may as well be data inside the buffering + * BIO-pair. So whenever an SSL_operation() returns without a fatal error, + * the BIO-pair internal buffer must be flushed to the network. + * NOTE: This is especially true in the SSL_ERROR_WANT_READ case: the TLS-layer + * might want to read handshake data, that will never come since its own + * written data will only reach the peer after flushing the buffer! + * + * The BIO-pair buffer size has been set to 8192 bytes, this is an arbitrary + * value that can hold more data than the typical PMTU, so that it does + * not force the generation of packets smaller than necessary. + * It is also larger than the default VSTREAM_BUFSIZE (4096, see vstream.h), + * so that large write operations could be handled within one call. + * The internal buffer in the network/network_bio handling layer has been + * set to the same value, since this seems to be reasonable. The code is + * however able to handle arbitrary values smaller or larger than the + * buffer size in the BIO-pair. + */ + +const ssize_t BIO_bufsiz = 8192; + +/* + * The interface layer between network and BIO-pair. The BIO-pair buffers + * the data to/from the TLS layer. Hence, at any time, there may be data + * in the buffer that must be written to the network. This writing has + * highest priority because the handshake might fail otherwise. + * Only then a read_request can be satisfied. + */ +static int network_biopair_interop(int fd, int timeout, BIO *network_bio) +{ + int want_write; + int num_write; + int write_pos; + int from_bio; + int want_read; + int num_read; + int to_bio; +#define NETLAYER_BUFFERSIZE 8192 + char buffer[8192]; + + while ((want_write = BIO_ctrl_pending(network_bio)) > 0) { + if (want_write > NETLAYER_BUFFERSIZE) + want_write = NETLAYER_BUFFERSIZE; + from_bio = BIO_read(network_bio, buffer, want_write); + + /* + * Write the complete contents of the buffer. Since TLS performs + * underlying handshaking, we cannot afford to leave the buffer + * unflushed, as we could run into a deadlock trap (the peer + * waiting for a final byte and we already waiting for his reply + * in read position). + */ + write_pos = 0; + do { + if (timeout > 0 && write_wait(fd, timeout) < 0) + return (-1); + num_write = write(fd, buffer + write_pos, from_bio - write_pos); + if (num_write <= 0) { + if ((num_write < 0) && (timeout > 0) && (errno == EAGAIN)) { + msg_warn("write() returns EAGAIN on a writable file descriptor!"); + msg_warn("pausing to avoid going into a tight select/write loop!"); + sleep(1); + } else { + msg_warn("Write failed in network_biopair_interop with errno=%d: num_write=%d, provided=%d", errno, num_write, from_bio - write_pos); + return (-1); /* something happened to the socket */ + } + } else + write_pos += num_write; + } while (write_pos < from_bio); + } + + while ((want_read = BIO_ctrl_get_read_request(network_bio)) > 0) { + if (want_read > NETLAYER_BUFFERSIZE) + want_read = NETLAYER_BUFFERSIZE; + if (timeout > 0 && read_wait(fd, timeout) < 0) + return (-1); + num_read = read(fd, buffer, want_read); + if (num_read <= 0) { + if ((num_write < 0) && (timeout > 0) && (errno == EAGAIN)) { + msg_warn("read() returns EAGAIN on a readable file descriptor!"); + msg_warn("pausing to avoid going into a tight select/write loop!"); + sleep(1); + } else { + msg_warn("Read failed in network_biopair_interop with errno=%d: num_read=%d, want_read=%d", errno, num_read, want_read); + return (-1); /* something happened to the socket */ + } + } else { + to_bio = BIO_write(network_bio, buffer, num_read); + if (to_bio != num_read) + msg_fatal("to_bio != num_read"); + } + } + + return (0); +} + +static void pfixtls_print_errors(void); + + /* + * Function to perform the handshake for SSL_accept(), SSL_connect(), + * and SSL_shutdown() and perform the SSL_read(), SSL_write() operations. + * Call the underlying network_biopair_interop-layer to make sure the + * write buffer is flushed after every operation (that did not fail with + * a fatal error). + */ +static int do_tls_operation(int fd, int timeout, TLScontext_t *TLScontext, + int (*hsfunc)(SSL *), + int (*rfunc)(SSL *, void *, int), + int (*wfunc)(SSL *, const void *, int), + char *buf, int num) +{ + int status; + int err; + int retval = 0; + int biop_retval; + int done = 0; + + while (!done) { + if (hsfunc) + status = hsfunc(TLScontext->con); + else if (rfunc) + status = rfunc(TLScontext->con, buf, num); + else + status = wfunc(TLScontext->con, (const char *)buf, num); + err = SSL_get_error(TLScontext->con, status); + +#if (OPENSSL_VERSION_NUMBER <= 0x0090581fL) + /* + * There is a bug up to and including OpenSSL-0.9.5a: if an error + * occurs while checking the peers certificate due to some certificate + * error (e.g. as happend with a RSA-padding error), the error is put + * onto the error stack. If verification is not enforced, this error + * should be ignored, but the error-queue is not cleared, so we + * can find this error here. The bug has been fixed on May 28, 2000. + * + * This bug so far has only manifested as + * 4800:error:0407006A:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01:rsa_pk1.c:100: + * 4800:error:04067072:rsa routines:RSA_EAY_PUBLIC_DECRYPT:padding check failed:rsa_eay.c:396: + * 4800:error:0D079006:asn1 encoding routines:ASN1_verify:bad get asn1 object call:a_verify.c:109: + * so that we specifically test for this error. We print the errors + * to the logfile and automatically clear the error queue. Then we + * retry to get another error code. We cannot do better, since we + * can only retrieve the last entry of the error-queue without + * actually cleaning it on the way. + * + * This workaround is secure, as verify_result is set to "failed" + * anyway. + */ + if (err == SSL_ERROR_SSL) { + if (ERR_peek_error() == 0x0407006AL) { + pfixtls_print_errors(); /* Keep information for the logfile */ + msg_info("OpenSSL <= 0.9.5a workaround called: certificate errors ignored"); + err = SSL_get_error(TLScontext->con, status); + } + } +#endif + + switch (err) { + case SSL_ERROR_NONE: /* success */ + retval = status; + done = 1; /* no break, flush buffer before */ + /* leaving */ + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + biop_retval = network_biopair_interop(fd, timeout, + TLScontext->network_bio); + if (biop_retval < 0) + return (-1); /* fatal network error */ + break; + case SSL_ERROR_ZERO_RETURN: /* connection was closed cleanly */ + case SSL_ERROR_SYSCALL: + case SSL_ERROR_SSL: + default: + retval = status; + done = 1; + ; + } + }; + return retval; +} + +int pfixtls_timed_read(int fd, void *buf, unsigned buf_len, int timeout, + void *context) +{ + int i; + int ret; + char mybuf[40]; + char *mybuf2; + TLScontext_t *TLScontext; + + TLScontext = (TLScontext_t *)context; + if (!TLScontext) + msg_fatal("Called tls_timed_read() without TLS-context"); + + ret = do_tls_operation(fd, timeout, TLScontext, NULL, SSL_read, NULL, + (char *)buf, buf_len); + if ((pfixtls_serveractive && var_smtpd_tls_loglevel >= 4) || + (pfixtls_clientactive && var_smtp_tls_loglevel >= 4)) { + mybuf2 = (char *) buf; + if (ret > 0) { + i = 0; + while ((i < 39) && (i < ret) && (mybuf2[i] != 0)) { + mybuf[i] = mybuf2[i]; + i++; + } + mybuf[i] = '\0'; + msg_info("Read %d chars: %s", ret, mybuf); + } + } + return (ret); +} + +int pfixtls_timed_write(int fd, void *buf, unsigned len, int timeout, + void *context) +{ + int i; + char mybuf[40]; + char *mybuf2; + TLScontext_t *TLScontext; + + TLScontext = (TLScontext_t *)context; + if (!TLScontext) + msg_fatal("Called tls_timed_write() without TLS-context"); + + if ((pfixtls_serveractive && var_smtpd_tls_loglevel >= 4) || + (pfixtls_clientactive && var_smtp_tls_loglevel >= 4)) { + mybuf2 = (char *) buf; + if (len > 0) { + i = 0; + while ((i < 39) && (i < len) && (mybuf2[i] != 0)) { + mybuf[i] = mybuf2[i]; + i++; + } + mybuf[i] = '\0'; + msg_info("Write %d chars: %s", len, mybuf); + } + } + return (do_tls_operation(fd, timeout, TLScontext, NULL, NULL, SSL_write, + buf, len)); +} + +/* Add some more entropy to the pool by adding the actual time */ + +static void pfixtls_stir_seed(void) +{ + GETTIMEOFDAY(&randseed.tv); + RAND_seed(&randseed, sizeof(randseed_t)); +} + +/* + * Skeleton taken from OpenSSL crypto/err/err_prn.c. + * Query the error stack and print the error string into the logging facility. + * Clear the error stack on the way. + */ + +static void pfixtls_print_errors(void) +{ + unsigned long l; + char buf[256]; + const char *file; + const char *data; + int line; + int flags; + unsigned long es; + + es = CRYPTO_thread_id(); + while ((l = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) { + if (flags & ERR_TXT_STRING) + msg_info("%lu:%s:%s:%d:%s:", es, ERR_error_string(l, buf), + file, line, data); + else + msg_info("%lu:%s:%s:%d:", es, ERR_error_string(l, buf), + file, line); + } +} + + /* + * Set up the cert things on the server side. We do need both the + * private key (in key_file) and the cert (in cert_file). + * Both files may be identical. + * + * This function is taken from OpenSSL apps/s_cb.c + */ + +static int set_cert_stuff(SSL_CTX * ctx, char *cert_file, char *key_file) +{ + if (cert_file != NULL) { + if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) { + msg_info("unable to get certificate from '%s'", cert_file); + pfixtls_print_errors(); + return (0); + } + if (key_file == NULL) + key_file = cert_file; + if (SSL_CTX_use_PrivateKey_file(ctx, key_file, + SSL_FILETYPE_PEM) <= 0) { + msg_info("unable to get private key from '%s'", key_file); + pfixtls_print_errors(); + return (0); + } + /* Now we know that a key and cert have been set against + * the SSL context */ + if (!SSL_CTX_check_private_key(ctx)) { + msg_info("Private key does not match the certificate public key"); + return (0); + } + } + return (1); +} + +/* taken from OpenSSL apps/s_cb.c */ + +static RSA *tmp_rsa_cb(SSL * s, int export, int keylength) +{ + static RSA *rsa_tmp = NULL; + + if (rsa_tmp == NULL) { + rsa_tmp = RSA_generate_key(keylength, RSA_F4, NULL, NULL); + } + return (rsa_tmp); +} + + +static DH *get_dh512(void) +{ + DH *dh; + + if (dh_512 == NULL) { + /* No parameter file loaded, use the compiled in parameters */ + if ((dh = DH_new()) == NULL) return(NULL); + dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL); + dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL); + if ((dh->p == NULL) || (dh->g == NULL)) + return(NULL); + else + dh_512 = dh; + } + return (dh_512); +} + +static DH *get_dh1024(void) +{ + DH *dh; + + if (dh_1024 == NULL) { + /* No parameter file loaded, use the compiled in parameters */ + if ((dh = DH_new()) == NULL) return(NULL); + dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); + dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL); + if ((dh->p == NULL) || (dh->g == NULL)) + return(NULL); + else + dh_1024 = dh; + } + return (dh_1024); +} + +/* partly inspired by mod_ssl */ + +static DH *tmp_dh_cb(SSL *s, int export, int keylength) +{ + DH *dh_tmp = NULL; + + if (export) { + if (keylength == 512) + dh_tmp = get_dh512(); /* export cipher */ + else if (keylength == 1024) + dh_tmp = get_dh1024(); /* normal */ + else + dh_tmp = get_dh1024(); /* not on-the-fly (too expensive) */ + /* so use the 1024bit instead */ + } + else { + dh_tmp = get_dh1024(); /* sign-only certificate */ + } + return (dh_tmp); +} + + +/* + * match_hostname: match name provided in "buf" against the expected + * hostname. Comparison is case-insensitive, wildcard certificates are + * supported. + * "buf" may be come from some OpenSSL data structures, so we copy before + * modifying. + */ +static int match_hostname(const char *buf, TLScontext_t *TLScontext) +{ + char *hostname_lowercase; + char *peername_left; + int hostname_matched = 0; + int buf_len; + + buf_len = strlen(buf); + if (!(hostname_lowercase = (char *)mymalloc(buf_len + 1))) + return 0; + memcpy(hostname_lowercase, buf, buf_len + 1); + + hostname_lowercase = lowercase(hostname_lowercase); + if (!strcmp(TLScontext->peername_save, hostname_lowercase)) { + hostname_matched = 1; + } else { + if ((buf_len > 2) && + (hostname_lowercase[0] == '*') && (hostname_lowercase[1] == '.')) { + /* + * Allow wildcard certificate matching. The proposed rules in + * RFCs (2818: HTTP/TLS, 2830: LDAP/TLS) are different, RFC2874 + * does not specify a rule, so here the strict rule is applied. + * An asterisk '*' is allowed as the leftmost component and may + * replace the left most part of the hostname. Matching is done + * by removing '*.' from the wildcard name and the Name. from + * the peername and compare what is left. + */ + peername_left = strchr(TLScontext->peername_save, '.'); + if (peername_left) { + if (!strcmp(peername_left + 1, hostname_lowercase + 2)) + hostname_matched = 1; + } + } + } + myfree(hostname_lowercase); + return hostname_matched; +} + +/* + * Skeleton taken from OpenSSL apps/s_cb.c + * + * The verify_callback is called several times (directly or indirectly) from + * crypto/x509/x509_vfy.c. It is called as a last check for several issues, + * so this verify_callback() has the famous "last word". If it does return "0", + * the handshake is immediately shut down and the connection fails. + * + * Postfix/TLS has two modes, the "use" mode and the "enforce" mode: + * + * In the "use" mode we never want the connection to fail just because there is + * something wrong with the certificate (as we would have sent happily without + * TLS). Therefore the return value is always "1". + * + * In the "enforce" mode we can shut down the connection as soon as possible. + * In server mode TLS itself may be enforced (e.g. to protect passwords), + * but certificates are optional. In this case the handshake must not fail + * if we are unhappy with the certificate and return "1" in any case. + * Only if a certificate is required the certificate must pass the verification + * and failure to do so will result in immediate termination (return 0). + * In the client mode the decision is made with respect to the peername + * enforcement. If we strictly enforce the matching of the expected peername + * the verification must fail immediatly on verification errors. We can also + * immediatly check the expected peername, as it is the CommonName at level 0. + * In all other cases, the problem is logged, so the SSL_get_verify_result() + * will inform about the verification failure, but the handshake (and SMTP + * connection will continue). + * + * The only error condition not handled inside the OpenSSL-Library is the + * case of a too-long certificate chain, so we check inside verify_callback(). + * We only take care of this problem, if "ok = 1", because otherwise the + * verification already failed because of another problem and we don't want + * to overwrite the other error message. And if the verification failed, + * there is no such thing as "more failed", "most failed"... :-) + */ + +static int verify_callback(int ok, X509_STORE_CTX * ctx) +{ + char buf[256]; + char *peername_left; + X509 *err_cert; + int err; + int depth; + int verify_depth; + SSL *con; + TLScontext_t *TLScontext; + + err_cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + depth = X509_STORE_CTX_get_error_depth(ctx); + + con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + TLScontext = SSL_get_ex_data(con, TLScontext_index); + + X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256); + if (((pfixtls_serverengine) && (var_smtpd_tls_loglevel >= 2)) || + ((pfixtls_clientengine) && (var_smtp_tls_loglevel >= 2))) + msg_info("Peer cert verify depth=%d %s", depth, buf); + + verify_depth = SSL_get_verify_depth(con); + if (ok && (verify_depth >= 0) && (depth > verify_depth)) { + ok = 0; + err = X509_V_ERR_CERT_CHAIN_TOO_LONG; + X509_STORE_CTX_set_error(ctx, err); + } + if (!ok) { + msg_info("verify error:num=%d:%s", err, + X509_verify_cert_error_string(err)); + } + + if (ok && (depth == 0) && pfixtls_clientengine) { + int i, r; + int hostname_matched; + int dNSName_found; + STACK_OF(GENERAL_NAME) *gens; + + /* + * Check out the name certified against the hostname expected. + * In case it does not match, print an information about the result. + * If a matching is enforced, bump out with a verification error + * immediately. + * Standards are not always clear with respect to the handling of + * dNSNames. RFC3207 does not specify the handling. We therefore follow + * the strict rules in RFC2818 (HTTP over TLS), Section 3.1: + * The Subject Alternative Name/dNSName has precedence over CommonName + * (CN). If dNSName entries are provided, CN is not checked anymore. + */ + hostname_matched = dNSName_found = 0; + + gens = X509_get_ext_d2i(err_cert, NID_subject_alt_name, 0, 0); + if (gens) { + for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i); + if (gn->type == GEN_DNS) { + dNSName_found++; + if ((hostname_matched = + match_hostname((char *)gn->d.ia5->data, TLScontext))) + break; + } + } + sk_GENERAL_NAME_free(gens); + } + if (dNSName_found) { + if (!hostname_matched) + msg_info("Peer verification: %d dNSNames in certificate found, but no one does match %s", dNSName_found, TLScontext->peername_save); + } else { + buf[0] = '\0'; + if (!X509_NAME_get_text_by_NID(X509_get_subject_name(err_cert), + NID_commonName, buf, 256)) { + msg_info("Could not parse server's subject CN"); + pfixtls_print_errors(); + } + else { + hostname_matched = match_hostname(buf, TLScontext); + if (!hostname_matched) + msg_info("Peer verification: CommonName in certificate does not match: %s != %s", buf, TLScontext->peername_save); + } + } + + if (!hostname_matched) { + if (TLScontext->enforce_verify_errors && TLScontext->enforce_CN) { + err = X509_V_ERR_CERT_REJECTED; + X509_STORE_CTX_set_error(ctx, err); + msg_info("Verify failure: Hostname mismatch"); + ok = 0; + } + } + else + TLScontext->hostname_matched = 1; + } + + switch (ctx->error) { + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256); + msg_info("issuer= %s", buf); + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + msg_info("cert not yet valid"); + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + msg_info("cert has expired"); + break; + } + if (((pfixtls_serverengine) && (var_smtpd_tls_loglevel >= 2)) || + ((pfixtls_clientengine) && (var_smtp_tls_loglevel >= 2))) + msg_info("verify return:%d", ok); + + if (TLScontext->enforce_verify_errors) + return (ok); + else + return (1); +} + +/* taken from OpenSSL apps/s_cb.c */ + +static void apps_ssl_info_callback(SSL * s, int where, int ret) +{ + char *str; + int w; + + w = where & ~SSL_ST_MASK; + + if (w & SSL_ST_CONNECT) + str = "SSL_connect"; + else if (w & SSL_ST_ACCEPT) + str = "SSL_accept"; + else + str = "undefined"; + + if (where & SSL_CB_LOOP) { + msg_info("%s:%s", str, SSL_state_string_long(s)); + } else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY) + msg_info("SSL3 alert %s:%s:%s", str, + SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } else if (where & SSL_CB_EXIT) { + if (ret == 0) + msg_info("%s:failed in %s", + str, SSL_state_string_long(s)); + else if (ret < 0) { + msg_info("%s:error in %s", + str, SSL_state_string_long(s)); + } + } +} + +/* + * taken from OpenSSL crypto/bio/b_dump.c, modified to save a lot of strcpy + * and strcat by Matti Aarnio. + */ + +#define TRUNCATE +#define DUMP_WIDTH 16 + +static int pfixtls_dump(const char *s, int len) +{ + int ret = 0; + char buf[160 + 1]; + char *ss; + int i; + int j; + int rows; + int trunc; + unsigned char ch; + + trunc = 0; + +#ifdef TRUNCATE + for (; (len > 0) && ((s[len - 1] == ' ') || (s[len - 1] == '\0')); len--) + trunc++; +#endif + + rows = (len / DUMP_WIDTH); + if ((rows * DUMP_WIDTH) < len) + rows++; + + for (i = 0; i < rows; i++) { + buf[0] = '\0'; /* start with empty string */ + ss = buf; + + sprintf(ss, "%04x ", i * DUMP_WIDTH); + ss += strlen(ss); + for (j = 0; j < DUMP_WIDTH; j++) { + if (((i * DUMP_WIDTH) + j) >= len) { + strcpy(ss, " "); + } else { + ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j)) + & 0xff; + sprintf(ss, "%02x%c", ch, j == 7 ? '|' : ' '); + ss += 3; + } + } + ss += strlen(ss); + *ss++ = ' '; + for (j = 0; j < DUMP_WIDTH; j++) { + if (((i * DUMP_WIDTH) + j) >= len) + break; + ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j)) & 0xff; + *ss++ = (((ch >= ' ') && (ch <= '~')) ? ch : '.'); + if (j == 7) *ss++ = ' '; + } + *ss = 0; + /* + * if this is the last call then update the ddt_dump thing so that + * we will move the selection point in the debug window + */ + msg_info("%s", buf); + ret += strlen(buf); + } +#ifdef TRUNCATE + if (trunc > 0) { + sprintf(buf, "%04x - \n", len + trunc); + msg_info("%s", buf); + ret += strlen(buf); + } +#endif + return (ret); +} + + + +/* taken from OpenSSL apps/s_cb.c */ + +static long bio_dump_cb(BIO * bio, int cmd, const char *argp, int argi, + long argl, long ret) +{ + if (!do_dump) + return (ret); + + if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) { + msg_info("read from %08X [%08lX] (%d bytes => %ld (0x%X))", + (unsigned int)bio, (unsigned long)argp, argi, + ret, (unsigned int)ret); + pfixtls_dump(argp, (int) ret); + return (ret); + } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) { + msg_info("write to %08X [%08lX] (%d bytes => %ld (0x%X))", + (unsigned int)bio, (unsigned long)argp, argi, + ret, (unsigned int)ret); + pfixtls_dump(argp, (int) ret); + } + return (ret); +} + + + /* + * Callback to retrieve a session from the external session cache. + */ +static SSL_SESSION *get_session_cb(SSL *ssl, unsigned char *SessionID, + int length, int *copy) +{ + SSL_SESSION *session; + char idstring[2 * ID_MAXLENGTH + 1]; + int n; + int uselength; + int hex_length; + const char *session_hex; + pfixtls_scache_info_t scache_info; + unsigned char nibble, *data, *sess_data; + + if (length > ID_MAXLENGTH) + uselength = ID_MAXLENGTH; /* Limit length of ID */ + else + uselength = length; + + for(n=0 ; n < uselength ; n++) + sprintf(idstring + 2 * n, "%02x", SessionID[n]); + if (var_smtpd_tls_loglevel >= 3) + msg_info("Trying to reload Session from disc: %s", idstring); + + session = NULL; + + session_hex = dict_get(scache_db, idstring); + if (session_hex) { + hex_length = strlen(session_hex); + data = (unsigned char *)mymalloc(hex_length / 2); + if (!data) { + msg_info("could not allocate memory for session reload"); + return(NULL); + } + + memset(data, 0, hex_length / 2); + for (n = 0; n < hex_length; n++) { + if ((session_hex[n] >= '0') && (session_hex[n] <= '9')) + nibble = session_hex[n] - '0'; + else + nibble = session_hex[n] - 'A' + 10; + if (n % 2) + data[n / 2] |= nibble; + else + data[n / 2] |= (nibble << 4); + } + + /* + * First check the version numbers, since wrong session data might + * hit us hard (SEGFAULT). We also have to check for expiry. + */ + memcpy(&scache_info, data, sizeof(pfixtls_scache_info_t)); + if ((scache_info.scache_db_version != scache_db_version) || + (scache_info.openssl_version != openssl_version) || + (scache_info.timestamp + var_smtpd_tls_scache_timeout < time(NULL))) + dict_del(scache_db, idstring); + else { + sess_data = data + sizeof(pfixtls_scache_info_t); + session = d2i_SSL_SESSION(NULL, &sess_data, + hex_length / 2 - sizeof(pfixtls_scache_info_t)); + if (!session) + pfixtls_print_errors(); + } + myfree((char *)data); + } + + if (session && (var_smtpd_tls_loglevel >= 3)) + msg_info("Successfully reloaded session from disc"); + + return (session); +} + + +static SSL_SESSION *load_clnt_session(const char *hostname, + int enforce_peername) +{ + SSL_SESSION *session = NULL; + char idstring[ID_MAXLENGTH + 1]; + int n; + int uselength; + int length; + int hex_length; + const char *session_hex; + pfixtls_scache_info_t scache_info; + unsigned char nibble, *data, *sess_data; + + length = strlen(hostname); + if (length > ID_MAXLENGTH) + uselength = ID_MAXLENGTH; /* Limit length of ID */ + else + uselength = length; + + for(n=0 ; n < uselength ; n++) + idstring[n] = tolower(hostname[n]); + idstring[uselength] = '\0'; + if (var_smtp_tls_loglevel >= 3) + msg_info("Trying to reload Session from disc: %s", idstring); + + session_hex = dict_get(scache_db, idstring); + if (session_hex) { + hex_length = strlen(session_hex); + data = (unsigned char *)mymalloc(hex_length / 2); + if (!data) { + msg_info("could not allocate memory for session reload"); + return(NULL); + } + + memset(data, 0, hex_length / 2); + for (n = 0; n < hex_length; n++) { + if ((session_hex[n] >= '0') && (session_hex[n] <= '9')) + nibble = session_hex[n] - '0'; + else + nibble = session_hex[n] - 'A' + 10; + if (n % 2) + data[n / 2] |= nibble; + else + data[n / 2] |= (nibble << 4); + } + + /* + * First check the version numbers, since wrong session data might + * hit us hard (SEGFAULT). We also have to check for expiry. + * When we enforce_peername, we may find an old session, that was + * saved when enforcement was not set. In this case the session will + * be removed and a fresh session will be negotiated. + */ + memcpy(&scache_info, data, sizeof(pfixtls_scache_info_t)); + if ((scache_info.scache_db_version != scache_db_version) || + (scache_info.openssl_version != openssl_version) || + (scache_info.timestamp + var_smtpd_tls_scache_timeout < time(NULL))) + dict_del(scache_db, idstring); + else if (enforce_peername && (!scache_info.enforce_peername)) + dict_del(scache_db, idstring); + else { + sess_data = data + sizeof(pfixtls_scache_info_t); + session = d2i_SSL_SESSION(NULL, &sess_data, + hex_length / 2 - sizeof(time_t)); + strncpy(SSL_SESSION_get_ex_data(session, TLSpeername_index), + idstring, ID_MAXLENGTH + 1); + if (!session) + pfixtls_print_errors(); + } + myfree((char *)data); + } + + if (session && (var_smtp_tls_loglevel >= 3)) + msg_info("Successfully reloaded session from disc"); + + return (session); +} + + +static void create_client_lookup_id(char *idstring, char *hostname) +{ + int n, len, uselength; + + len = strlen(hostname); + if (len > ID_MAXLENGTH) + uselength = ID_MAXLENGTH; /* Limit length of ID */ + else + uselength = len; + + for (n = 0 ; n < uselength ; n++) + idstring[n] = tolower(hostname[n]); + idstring[uselength] = '\0'; +} + + +static void create_server_lookup_id(char *idstring, SSL_SESSION *session) +{ + int n, uselength; + + if (session->session_id_length > ID_MAXLENGTH) + uselength = ID_MAXLENGTH; /* Limit length of ID */ + else + uselength = session->session_id_length; + + for(n = 0; n < uselength ; n++) + sprintf(idstring + 2 * n, "%02x", session->session_id[n]); +} + + +static void remove_session_cb(SSL_CTX *ctx, SSL_SESSION *session) +{ + char idstring[2 * ID_MAXLENGTH + 1]; + char *hostname; + + if (pfixtls_clientengine) { + hostname = SSL_SESSION_get_ex_data(session, TLSpeername_index); + create_client_lookup_id(idstring, hostname); + if (var_smtp_tls_loglevel >= 3) + msg_info("Trying to remove session from disc: %s", idstring); + } + else { + create_server_lookup_id(idstring, session); + if (var_smtpd_tls_loglevel >= 3) + msg_info("Trying to remove session from disc: %s", idstring); + } + + if (scache_db) + dict_del(scache_db, idstring); +} + + +/* + * We need space to save the peername into the SSL_SESSION, as we must + * look up the external database for client sessions by peername, not + * by session id. We therefore allocate place for the peername string, + * when a new SSL_SESSION is generated. It is filled later. + */ +static int new_peername_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad, + int idx, long argl, void *argp) +{ + char *peername; + + peername = (char *)mymalloc(ID_MAXLENGTH + 1); + if (!peername) + return 0; + peername[0] = '\0'; /* initialize */ + return CRYPTO_set_ex_data(ad, idx, peername); +} + +/* + * When the SSL_SESSION is removed again, we must free the memory to avoid + * leaks. + */ +static void free_peername_func(void *parent, void *ptr, CRYPTO_EX_DATA *ad, + int idx, long argl, void *argp) +{ + myfree(CRYPTO_get_ex_data(ad, idx)); +} + +/* + * Duplicate application data, when a SSL_SESSION is duplicated + */ +static int dup_peername_func(CRYPTO_EX_DATA *to, CRYPTO_EX_DATA *from, + void *from_d, int idx, long argl, void *argp) +{ + char *peername_old, *peername_new; + + peername_old = CRYPTO_get_ex_data(from, idx); + peername_new = CRYPTO_get_ex_data(to, idx); + if (!peername_old || !peername_new) + return 0; + memcpy(peername_new, peername_old, ID_MAXLENGTH + 1); + return 1; +} + + + /* + * Save a new session to the external cache + */ +static int new_session_cb(SSL *ssl, SSL_SESSION *session) +{ + char idstring[2 * ID_MAXLENGTH + 1]; + int n; + int dsize; + int len; + unsigned char *data, *sess_data; + pfixtls_scache_info_t scache_info; + char *hexdata, *hostname; + TLScontext_t *TLScontext; + + if (pfixtls_clientengine) { + TLScontext = SSL_get_ex_data(ssl, TLScontext_index); + hostname = TLScontext->peername_save; + create_client_lookup_id(idstring, hostname); + strncpy(SSL_SESSION_get_ex_data(session, TLSpeername_index), + hostname, ID_MAXLENGTH + 1); + /* + * Remember, whether peername matching was enforced when the session + * was created. If later enforce mode is enabled, we do not want to + * reuse a session that was not sufficiently checked. + */ + scache_info.enforce_peername = + (TLScontext->enforce_verify_errors && TLScontext->enforce_CN); + + if (var_smtp_tls_loglevel >= 3) + msg_info("Trying to save session for hostID to disc: %s", idstring); + +#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L) + /* + * Ugly Hack: OpenSSL before 0.9.6a does not store the verify + * result in sessions for the client side. + * We modify the session directly which is version specific, + * but this bug is version specific, too. + * + * READ: 0-09-06-01-1 = 0-9-6-a-beta1: all versions before + * beta1 have this bug, it has been fixed during development + * of 0.9.6a. The development version of 0.9.7 can have this + * bug, too. It has been fixed on 2000/11/29. + */ + session->verify_result = SSL_get_verify_result(TLScontext->con); +#endif + + } + else { + create_server_lookup_id(idstring, session); + if (var_smtpd_tls_loglevel >= 3) + msg_info("Trying to save Session to disc: %s", idstring); + } + + + /* + * Get the session and convert it into some "database" useable form. + * First, get the length of the session to allocate the memory. + */ + dsize = i2d_SSL_SESSION(session, NULL); + if (dsize < 0) { + msg_info("Could not access session"); + return 0; + } + data = (unsigned char *)mymalloc(dsize + sizeof(pfixtls_scache_info_t)); + if (!data) { + msg_info("could not allocate memory for SSL session"); + return 0; + } + + /* + * OpenSSL is not robust against wrong session data (might SEGFAULT), + * so we secure it against version ids (session cache structure as well + * as OpenSSL version). + */ + scache_info.scache_db_version = scache_db_version; + scache_info.openssl_version = openssl_version; + + /* + * Put a timestamp, so that expiration can be checked without + * analyzing the session data itself. (We would need OpenSSL funtions, + * since the SSL_SESSION is a private structure.) + */ + scache_info.timestamp = time(NULL); + + memcpy(data, &scache_info, sizeof(pfixtls_scache_info_t)); + sess_data = data + sizeof(pfixtls_scache_info_t); + + /* + * Now, obtain the session. Unfortunately, it is binary and dict_update + * cannot handle binary data (it could contain '\0' in it) directly. + * To save memory we could use base64 encoding. To make handling easier, + * we simply use hex format. + */ + len = i2d_SSL_SESSION(session, &sess_data); + len += sizeof(pfixtls_scache_info_t); + + hexdata = (char *)mymalloc(2 * len + 1); + + if (!hexdata) { + msg_info("could not allocate memory for SSL session (HEX)"); + myfree((char *)data); + return 0; + } + for (n = 0; n < len; n++) { + hexdata[n * 2] = hexcodes[(data[n] & 0xf0) >> 4]; + hexdata[(n * 2) + 1] = hexcodes[(data[n] & 0x0f)]; + } + hexdata[len * 2] = '\0'; + + /* + * The session id is a hex string, all uppercase. We are using SDBM as + * compiled into Postfix with 8kB maximum entry size, so we set a limit + * when caching. If the session is not cached, we have to renegotiate, + * not more, not less. For a real session, this limit should never be + * met + */ + if (strlen(idstring) + strlen(hexdata) < 8000) + dict_put(scache_db, idstring, hexdata); + + myfree(hexdata); + myfree((char *)data); + return (1); +} + + + /* + * pfixtls_exchange_seed: read bytes from the seed exchange-file (expect + * 1024 bytes)and immediately write back random bytes. Do so with EXCLUSIVE + * lock, so * that each process will find a completely different (and + * reseeded) file. + */ +static void pfixtls_exchange_seed(void) +{ + unsigned char buffer[1024]; + + if (rand_exch_fd == -1) + return; + + if (myflock(rand_exch_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) != 0) + msg_info("Could not lock random exchange file: %s", + strerror(errno)); + + lseek(rand_exch_fd, 0, SEEK_SET); + if (read(rand_exch_fd, buffer, 1024) < 0) + msg_fatal("reading exchange file failed"); + RAND_seed(buffer, 1024); + + RAND_bytes(buffer, 1024); + lseek(rand_exch_fd, 0, SEEK_SET); + if (write(rand_exch_fd, buffer, 1024) != 1024) + msg_fatal("Writing exchange file failed"); + + if (myflock(rand_exch_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) != 0) + msg_fatal("Could not unlock random exchange file: %s", + strerror(errno)); +} + + /* + * This is the setup routine for the SSL server. As smtpd might be called + * more than once, we only want to do the initialization one time. + * + * The skeleton of this function is taken from OpenSSL apps/s_server.c. + */ + +int pfixtls_init_serverengine(int verifydepth, int askcert) +{ + int off = 0; + int verify_flags = SSL_VERIFY_NONE; + int rand_bytes; + int rand_source_dev_fd; + int rand_source_socket_fd; + unsigned char buffer[255]; + char *CApath; + char *CAfile; + char *s_cert_file; + char *s_key_file; + char *s_dcert_file; + char *s_dkey_file; + FILE *paramfile; + + if (pfixtls_serverengine) + return (0); /* already running */ + + if (var_smtpd_tls_loglevel >= 2) + msg_info("starting TLS engine"); + + /* + * Initialize the OpenSSL library by the book! + * To start with, we must initialize the algorithms. + * We want cleartext error messages instead of just error codes, so we + * load the error_strings. + */ + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + + /* + * Side effect, call a non-existing function to disable TLS usage with an + * outdated OpenSSL version. There is a security reason (verify_result + * is not stored with the session data). + */ +#if (OPENSSL_VERSION_NUMBER < 0x00905100L) + needs_openssl_095_or_later(); +#endif + + /* + * Initialize the PRNG Pseudo Random Number Generator with some seed. + */ + randseed.pid = getpid(); + GETTIMEOFDAY(&randseed.tv); + RAND_seed(&randseed, sizeof(randseed_t)); + + /* + * Access the external sources for random seed. We will only query them + * once, this should be sufficient and we will stir our entropy by using + * the prng-exchange file anyway. + * For reliability, we don't consider failure to access the additional + * source fatal, as we can run happily without it (considering that we + * still have the exchange-file). We also don't care how much entropy + * we get back, as we must run anyway. We simply stir in the buffer + * regardless how many bytes are actually in it. + */ + if (*var_tls_daemon_rand_source) { + if (!strncmp(var_tls_daemon_rand_source, "dev:", 4)) { + /* + * Source is a random device + */ + rand_source_dev_fd = open(var_tls_daemon_rand_source + 4, 0, 0); + if (rand_source_dev_fd == -1) + msg_info("Could not open entropy device %s", + var_tls_daemon_rand_source); + else { + if (var_tls_daemon_rand_bytes > 255) + var_tls_daemon_rand_bytes = 255; + read(rand_source_dev_fd, buffer, var_tls_daemon_rand_bytes); + RAND_seed(buffer, var_tls_daemon_rand_bytes); + close(rand_source_dev_fd); + } + } else if (!strncmp(var_tls_daemon_rand_source, "egd:", 4)) { + /* + * Source is a EGD compatible socket + */ + rand_source_socket_fd = unix_connect(var_tls_daemon_rand_source +4, + BLOCKING, 10); + if (rand_source_socket_fd == -1) + msg_info("Could not connect to %s", var_tls_daemon_rand_source); + else { + if (var_tls_daemon_rand_bytes > 255) + var_tls_daemon_rand_bytes = 255; + buffer[0] = 1; + buffer[1] = var_tls_daemon_rand_bytes; + if (write(rand_source_socket_fd, buffer, 2) != 2) + msg_info("Could not talk to %s", + var_tls_daemon_rand_source); + else if (read(rand_source_socket_fd, buffer, 1) != 1) + msg_info("Could not read info from %s", + var_tls_daemon_rand_source); + else { + rand_bytes = buffer[0]; + read(rand_source_socket_fd, buffer, rand_bytes); + RAND_seed(buffer, rand_bytes); + } + close(rand_source_socket_fd); + } + } else { + RAND_load_file(var_tls_daemon_rand_source, + var_tls_daemon_rand_bytes); + } + } + + if (*var_tls_rand_exch_name) { + rand_exch_fd = open(var_tls_rand_exch_name, O_RDWR | O_CREAT, 0600); + if (rand_exch_fd != -1) + pfixtls_exchange_seed(); + } + + randseed.pid = getpid(); + GETTIMEOFDAY(&randseed.tv); + RAND_seed(&randseed, sizeof(randseed_t)); + + /* + * The SSL/TLS speficications require the client to send a message in + * the oldest specification it understands with the highest level it + * understands in the message. + * Netscape communicator can still communicate with SSLv2 servers, so it + * sends out a SSLv2 client hello. To deal with it, our server must be + * SSLv2 aware (even if we don't like SSLv2), so we need to have the + * SSLv23 server here. If we want to limit the protocol level, we can + * add an option to not use SSLv2/v3/TLSv1 later. + */ + ctx = SSL_CTX_new(SSLv23_server_method()); + if (ctx == NULL) { + pfixtls_print_errors(); + return (-1); + }; + + /* + * Here we might set SSL_OP_NO_SSLv2, SSL_OP_NO_SSLv3, SSL_OP_NO_TLSv1. + * Of course, the last one would not make sense, since RFC2487 is only + * defined for TLS, but we also want to accept Netscape communicator + * requests, and it only supports SSLv3. + */ + off |= SSL_OP_ALL; /* Work around all known bugs */ + SSL_CTX_set_options(ctx, off); + + /* + * Set the info_callback, that will print out messages during + * communication on demand. + */ + if (var_smtpd_tls_loglevel >= 2) + SSL_CTX_set_info_callback(ctx, apps_ssl_info_callback); + + /* + * Set the list of ciphers, if explicitely given; otherwise the + * (reasonable) default list is kept. + */ + if (strlen(var_smtpd_tls_cipherlist) != 0) + if (SSL_CTX_set_cipher_list(ctx, var_smtpd_tls_cipherlist) == 0) { + pfixtls_print_errors(); + return (-1); + } + + /* + * Now we must add the necessary certificate stuff: A server key, a + * server certificate, and the CA certificates for both the server + * cert and the verification of client certificates. + * As provided by OpenSSL we support two types of CA certificate handling: + * One possibility is to add all CA certificates to one large CAfile, + * the other possibility is a directory pointed to by CApath, containing + * seperate files for each CA pointed on by softlinks named by the hash + * values of the certificate. + * The first alternative has the advantage, that the file is opened and + * read at startup time, so that you don't have the hassle to maintain + * another copy of the CApath directory for chroot-jail. On the other + * hand, the file is not really readable. + */ + if (strlen(var_smtpd_tls_CAfile) == 0) + CAfile = NULL; + else + CAfile = var_smtpd_tls_CAfile; + if (strlen(var_smtpd_tls_CApath) == 0) + CApath = NULL; + else + CApath = var_smtpd_tls_CApath; + + if (CAfile || CApath) { + if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath)) { + msg_info("TLS engine: cannot load CA data"); + pfixtls_print_errors(); + return (-1); + } + if (!SSL_CTX_set_default_verify_paths(ctx)) { + msg_info("TLS engine: cannot set verify paths"); + pfixtls_print_errors(); + return (-1); + } + } + + /* + * Now we load the certificate and key from the files and check, + * whether the cert matches the key (internally done by set_cert_stuff(). + * We cannot run without (we do not support ADH anonymous Diffie-Hellman + * ciphers as of now). + * We can use RSA certificates ("cert") and DSA certificates ("dcert"), + * both can be made available at the same time. The CA certificates for + * both are handled in the same setup already finished. + * Which one is used depends on the cipher negotiated (that is: the first + * cipher listed by the client which does match the server). A client with + * RSA only (e.g. Netscape) will use the RSA certificate only. + * A client with openssl-library will use RSA first if not especially + * changed in the cipher setup. + */ + if (strlen(var_smtpd_tls_cert_file) == 0) + s_cert_file = NULL; + else + s_cert_file = var_smtpd_tls_cert_file; + if (strlen(var_smtpd_tls_key_file) == 0) + s_key_file = NULL; + else + s_key_file = var_smtpd_tls_key_file; + + if (strlen(var_smtpd_tls_dcert_file) == 0) + s_dcert_file = NULL; + else + s_dcert_file = var_smtpd_tls_dcert_file; + if (strlen(var_smtpd_tls_dkey_file) == 0) + s_dkey_file = NULL; + else + s_dkey_file = var_smtpd_tls_dkey_file; + + if (s_cert_file) { + if (!set_cert_stuff(ctx, s_cert_file, s_key_file)) { + msg_info("TLS engine: cannot load RSA cert/key data"); + pfixtls_print_errors(); + return (-1); + } + } + if (s_dcert_file) { + if (!set_cert_stuff(ctx, s_dcert_file, s_dkey_file)) { + msg_info("TLS engine: cannot load DSA cert/key data"); + pfixtls_print_errors(); + return (-1); + } + } + if (!s_cert_file && !s_dcert_file) { + msg_info("TLS engine: do need at least RSA _or_ DSA cert/key data"); + return (-1); + } + + /* + * Sometimes a temporary RSA key might be needed by the OpenSSL + * library. The OpenSSL doc indicates, that this might happen when + * export ciphers are in use. We have to provide one, so well, we + * just do it. + */ + SSL_CTX_set_tmp_rsa_callback(ctx, tmp_rsa_cb); + + /* + * We might also need dh parameters, which can either be loaded from + * file (preferred) or we simply take the compiled in values. + * First, set the callback that will select the values when requested, + * then load the (possibly) available DH parameters from files. + * We are generous with the error handling, since we do have default + * values compiled in, so we will not abort but just log the error message. + */ + SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_cb); + if (strlen(var_smtpd_tls_dh1024_param_file) != 0) { + if ((paramfile = fopen(var_smtpd_tls_dh1024_param_file, "r")) != NULL) { + dh_1024 = PEM_read_DHparams(paramfile, NULL, NULL, NULL); + if (dh_1024 == NULL) { + msg_info("TLS engine: cannot load 1024bit DH parameters"); + pfixtls_print_errors(); + } + } + else { + msg_info("TLS engine: cannot load 1024bit DH parameters: %s: %s", + var_smtpd_tls_dh1024_param_file, strerror(errno)); + } + } + if (strlen(var_smtpd_tls_dh512_param_file) != 0) { + if ((paramfile = fopen(var_smtpd_tls_dh512_param_file, "r")) != NULL) { + dh_512 = PEM_read_DHparams(paramfile, NULL, NULL, NULL); + if (dh_512 == NULL) { + msg_info("TLS engine: cannot load 512bit DH parameters"); + pfixtls_print_errors(); + } + } + else { + msg_info("TLS engine: cannot load 512bit DH parameters: %s: %s", + var_smtpd_tls_dh512_param_file, strerror(errno)); + } + } + + /* + * If we want to check client certificates, we have to indicate it + * in advance. By now we only allow to decide on a global basis. + * If we want to allow certificate based relaying, we must ask the + * client to provide one with SSL_VERIFY_PEER. The client now can + * decide, whether it provides one or not. We can enforce a failure + * of the negotiation with SSL_VERIFY_FAIL_IF_NO_PEER_CERT, if we + * do not allow a connection without one. + * In the "server hello" following the initialization by the "client hello" + * the server must provide a list of CAs it is willing to accept. + * Some clever clients will then select one from the list of available + * certificates matching these CAs. Netscape Communicator will present + * the list of certificates for selecting the one to be sent, or it will + * issue a warning, if there is no certificate matching the available + * CAs. + * + * With regard to the purpose of the certificate for relaying, we might + * like a later negotiation, maybe relaying would already be allowed + * for other reasons, but this would involve severe changes in the + * internal postfix logic, so we have to live with it the way it is. + */ + if (askcert) + verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; + SSL_CTX_set_verify(ctx, verify_flags, verify_callback); + SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CAfile)); + + /* + * Initialize the session cache. We only want external caching to + * synchronize between server sessions, so we set it to a minimum value + * of 1. If the external cache is disabled, we won't cache at all. + * The recall of old sessions "get" and save to disk of just created + * sessions "new" is handled by the appropriate callback functions. + * + * We must not forget to set a session id context to identify to which + * kind of server process the session was related. In our case, the + * context is just the name of the patchkit: "Postfix/TLS". + */ + SSL_CTX_sess_set_cache_size(ctx, 1); + SSL_CTX_set_timeout(ctx, var_smtpd_tls_scache_timeout); + SSL_CTX_set_session_id_context(ctx, (void*)&server_session_id_context, + sizeof(server_session_id_context)); + + /* + * The session cache is realized by an external database file, that + * must be opened before going to chroot jail. Since the session cache + * data can become quite large, "[n]dbm" cannot be used as it has a + * size limit that is by far to small. + */ + if (*var_smtpd_tls_scache_db) { + /* + * Insert a test against other dbms here, otherwise while writing + * a session (content to large), we will receive a fatal error! + */ + if (strncmp(var_smtpd_tls_scache_db, "sdbm:", 5)) + msg_warn("Only sdbm: type allowed for %s", + var_smtpd_tls_scache_db); + else + scache_db = dict_open(var_smtpd_tls_scache_db, O_RDWR, + DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE); + if (scache_db) { + SSL_CTX_set_session_cache_mode(ctx, + SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_AUTO_CLEAR); + SSL_CTX_sess_set_get_cb(ctx, get_session_cb); + SSL_CTX_sess_set_new_cb(ctx, new_session_cb); + SSL_CTX_sess_set_remove_cb(ctx, remove_session_cb); + } + else + msg_warn("Could not open session cache %s", + var_smtpd_tls_scache_db); + } + + /* + * Finally create the global index to access TLScontext information + * inside verify_callback. + */ + TLScontext_index = SSL_get_ex_new_index(0, "TLScontext ex_data index", + NULL, NULL, NULL); + + pfixtls_serverengine = 1; + return (0); +} + + /* + * This is the actual startup routine for the connection. We expect + * that the buffers are flushed and the "220 Ready to start TLS" was + * send to the client, so that we can immediately can start the TLS + * handshake process. + */ +int pfixtls_start_servertls(VSTREAM *stream, int timeout, + const char *peername, const char *peeraddr, + tls_info_t *tls_info, int requirecert) +{ + int sts; + int j; + int verify_flags; + unsigned int n; + TLScontext_t *TLScontext; + SSL_SESSION *session; + SSL_CIPHER *cipher; + X509 *peer; + + if (!pfixtls_serverengine) { /* should never happen */ + msg_info("tls_engine not running"); + return (-1); + } + if (var_smtpd_tls_loglevel >= 1) + msg_info("setting up TLS connection from %s[%s]", peername, peeraddr); + + /* + * Allocate a new TLScontext for the new connection and get an SSL + * structure. Add the location of TLScontext to the SSL to later + * retrieve the information inside the verify_callback(). + */ + TLScontext = (TLScontext_t *)mymalloc(sizeof(TLScontext_t)); + if (!TLScontext) { + msg_fatal("Could not allocate 'TLScontext' with mymalloc"); + } + if ((TLScontext->con = (SSL *) SSL_new(ctx)) == NULL) { + msg_info("Could not allocate 'TLScontext->con' with SSL_new()"); + pfixtls_print_errors(); + myfree((char *)TLScontext); + return (-1); + } + if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) { + msg_info("Could not set application data for 'TLScontext->con'"); + pfixtls_print_errors(); + SSL_free(TLScontext->con); + myfree((char *)TLScontext); + return (-1); + } + + /* + * Set the verification parameters to be checked in verify_callback(). + */ + if (requirecert) { + verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; + verify_flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + TLScontext->enforce_verify_errors = 1; + SSL_set_verify(TLScontext->con, verify_flags, verify_callback); + } + else { + TLScontext->enforce_verify_errors = 0; + } + TLScontext->enforce_CN = 0; + + /* + * The TLS connection is realized by a BIO_pair, so obtain the pair. + */ + if (!BIO_new_bio_pair(&TLScontext->internal_bio, BIO_bufsiz, + &TLScontext->network_bio, BIO_bufsiz)) { + msg_info("Could not obtain BIO_pair"); + pfixtls_print_errors(); + SSL_free(TLScontext->con); + myfree((char *)TLScontext); + return (-1); + } + + /* + * Before really starting anything, try to seed the PRNG a little bit + * more. + */ + pfixtls_stir_seed(); + pfixtls_exchange_seed(); + + /* + * Initialize the SSL connection to accept state. This should not be + * necessary anymore since 0.9.3, but the call is still in the library + * and maintaining compatibility never hurts. + */ + SSL_set_accept_state(TLScontext->con); + + /* + * Connect the SSL-connection with the postfix side of the BIO-pair for + * reading and writing. + */ + SSL_set_bio(TLScontext->con, TLScontext->internal_bio, + TLScontext->internal_bio); + + /* + * If the debug level selected is high enough, all of the data is + * dumped: 3 will dump the SSL negotiation, 4 will dump everything. + * + * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called? + * Well there is a BIO below the SSL routines that is automatically + * created for us, so we can use it for debugging purposes. + */ + if (var_smtpd_tls_loglevel >= 3) + BIO_set_callback(SSL_get_rbio(TLScontext->con), bio_dump_cb); + + + /* Dump the negotiation for loglevels 3 and 4 */ + if (var_smtpd_tls_loglevel >= 3) + do_dump = 1; + + /* + * Now we expect the negotiation to begin. This whole process is like a + * black box for us. We totally have to rely on the routines build into + * the OpenSSL library. The only thing we can do we already have done + * by choosing our own callbacks for session caching and certificate + * verification. + * + * Error handling: + * If the SSL handhake fails, we print out an error message and remove + * everything that might be there. A session has to be removed anyway, + * because RFC2246 requires it. + */ + sts = do_tls_operation(vstream_fileno(stream), timeout, TLScontext, + SSL_accept, NULL, NULL, NULL, 0); + if (sts <= 0) { + msg_info("SSL_accept error from %s[%s]: %d", peername, peeraddr, sts); + pfixtls_print_errors(); + SSL_free(TLScontext->con); + myfree((char *)TLScontext); + return (-1); + } + + /* Only loglevel==4 dumps everything */ + if (var_smtpd_tls_loglevel < 4) + do_dump = 0; + + /* + * Lets see, whether a peer certificate is available and what is + * the actual information. We want to save it for later use. + */ + peer = SSL_get_peer_certificate(TLScontext->con); + if (peer != NULL) { + if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) + tls_info->peer_verified = 1; + + X509_NAME_oneline(X509_get_subject_name(peer), + TLScontext->peer_subject, CCERT_BUFSIZ); + if (var_smtpd_tls_loglevel >= 2) + msg_info("subject=%s", TLScontext->peer_subject); + tls_info->peer_subject = TLScontext->peer_subject; + X509_NAME_oneline(X509_get_issuer_name(peer), + TLScontext->peer_issuer, CCERT_BUFSIZ); + if (var_smtpd_tls_loglevel >= 2) + msg_info("issuer=%s", TLScontext->peer_issuer); + tls_info->peer_issuer = TLScontext->peer_issuer; + if (X509_digest(peer, EVP_md5(), TLScontext->md, &n)) { + for (j = 0; j < (int) n; j++) { + TLScontext->fingerprint[j * 3] = + hexcodes[(TLScontext->md[j] & 0xf0) >> 4]; + TLScontext->fingerprint[(j * 3) + 1] = + hexcodes[(TLScontext->md[j] & 0x0f)]; + if (j + 1 != (int) n) + TLScontext->fingerprint[(j * 3) + 2] = ':'; + else + TLScontext->fingerprint[(j * 3) + 2] = '\0'; + } + if (var_smtpd_tls_loglevel >= 1) + msg_info("fingerprint=%s", TLScontext->fingerprint); + tls_info->peer_fingerprint = TLScontext->fingerprint; + } + + TLScontext->peer_CN[0] = '\0'; + if (!X509_NAME_get_text_by_NID(X509_get_subject_name(peer), + NID_commonName, TLScontext->peer_CN, CCERT_BUFSIZ)) { + msg_info("Could not parse client's subject CN"); + pfixtls_print_errors(); + } + tls_info->peer_CN = TLScontext->peer_CN; + + TLScontext->issuer_CN[0] = '\0'; + if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer), + NID_commonName, TLScontext->issuer_CN, CCERT_BUFSIZ)) { + msg_info("Could not parse client's issuer CN"); + pfixtls_print_errors(); + } + if (!TLScontext->issuer_CN[0]) { + /* No issuer CN field, use Organization instead */ + if (!X509_NAME_get_text_by_NID(X509_get_issuer_name(peer), + NID_organizationName, TLScontext->issuer_CN, CCERT_BUFSIZ)) { + msg_info("Could not parse client's issuer Organization"); + pfixtls_print_errors(); + } + } + tls_info->issuer_CN = TLScontext->issuer_CN; + + if (var_smtpd_tls_loglevel >= 1) { + if (tls_info->peer_verified) + msg_info("Verified: subject_CN=%s, issuer=%s", + TLScontext->peer_CN, TLScontext->issuer_CN); + else + msg_info("Unverified: subject_CN=%s, issuer=%s", + TLScontext->peer_CN, TLScontext->issuer_CN); + } + + X509_free(peer); + } + + /* + * At this point we should have a certificate when required. + * We may however have a cached session, so the callback would never + * be called. We therefore double-check to make sure and remove the + * session, if applicable. + */ + if (requirecert) { + if (!tls_info->peer_verified || !tls_info->peer_CN) { + msg_info("Re-used session without peer certificate removed"); + session = SSL_get_session(TLScontext->con); + SSL_CTX_remove_session(ctx, session); + return (-1); + } + } + + /* + * Finally, collect information about protocol and cipher for logging + */ + tls_info->protocol = SSL_get_version(TLScontext->con); + cipher = SSL_get_current_cipher(TLScontext->con); + tls_info->cipher_name = SSL_CIPHER_get_name(cipher); + tls_info->cipher_usebits = SSL_CIPHER_get_bits(cipher, + &(tls_info->cipher_algbits)); + + pfixtls_serveractive = 1; + + /* + * The TLS engine is active, switch to the pfixtls_timed_read/write() + * functions and store the context. + */ + vstream_control(stream, + VSTREAM_CTL_READ_FN, pfixtls_timed_read, + VSTREAM_CTL_WRITE_FN, pfixtls_timed_write, + VSTREAM_CTL_CONTEXT, (void *)TLScontext, + VSTREAM_CTL_END); + + msg_info("TLS connection established from %s[%s]: %s with cipher %s (%d/%d bits)", + peername, peeraddr, + tls_info->protocol, tls_info->cipher_name, + tls_info->cipher_usebits, tls_info->cipher_algbits); + pfixtls_stir_seed(); + + return (0); +} + + /* + * Shut down the TLS connection, that does mean: remove all the information + * and reset the flags! This is needed if the actual running smtpd is to + * be restarted. We do not give back any value, as there is nothing to + * be reported. + * Since our session cache is external, we will remove the session from + * memory in any case. The SSL_CTX_flush_sessions might be redundant here, + * I however want to make sure nothing is left. + * RFC2246 requires us to remove sessions if something went wrong, as + * indicated by the "failure" value, so we remove it from the external + * cache, too. + */ +int pfixtls_stop_servertls(VSTREAM *stream, int timeout, int failure, + tls_info_t *tls_info) +{ + TLScontext_t *TLScontext; + int retval; + + if (pfixtls_serveractive) { + TLScontext = (TLScontext_t *)vstream_context(stream); + /* + * Perform SSL_shutdown() twice, as the first attempt may return + * to early: it will only send out the shutdown alert but it will + * not wait for the peer's shutdown alert. Therefore, when we are + * the first party to send the alert, we must call SSL_shutdown() + * again. + * On failure we don't want to resume the session, so we will not + * perform SSL_shutdown() and the session will be removed as being + * bad. + */ + if (!failure) { + retval = do_tls_operation(vstream_fileno(stream), timeout, + TLScontext, SSL_shutdown, NULL, NULL, NULL, 0); + if (retval == 0) + do_tls_operation(vstream_fileno(stream), timeout, TLScontext, + SSL_shutdown, NULL, NULL, NULL, 0); + } + /* + * Free the SSL structure and the BIOs. Warning: the internal_bio is + * connected to the SSL structure and is automatically freed with + * it. Do not free it again (core dump)!! + * Only free the network_bio. + */ + SSL_free(TLScontext->con); + BIO_free(TLScontext->network_bio); + myfree((char *)TLScontext); + vstream_control(stream, + VSTREAM_CTL_READ_FN, (VSTREAM_FN) NULL, + VSTREAM_CTL_WRITE_FN, (VSTREAM_FN) NULL, + VSTREAM_CTL_CONTEXT, (void *) NULL, + VSTREAM_CTL_END); + SSL_CTX_flush_sessions(ctx, time(NULL)); + + pfixtls_stir_seed(); + pfixtls_exchange_seed(); + + *tls_info = tls_info_zero; + pfixtls_serveractive = 0; + + } + + return (0); +} + + + /* + * This is the setup routine for the SSL client. As smtpd might be called + * more than once, we only want to do the initialization one time. + * + * The skeleton of this function is taken from OpenSSL apps/s_client.c. + */ + +int pfixtls_init_clientengine(int verifydepth) +{ + int off = 0; + int verify_flags = SSL_VERIFY_NONE; + int rand_bytes; + int rand_source_dev_fd; + int rand_source_socket_fd; + unsigned char buffer[255]; + char *CApath; + char *CAfile; + char *c_cert_file; + char *c_key_file; + + + if (pfixtls_clientengine) + return (0); /* already running */ + + if (var_smtp_tls_loglevel >= 2) + msg_info("starting TLS engine"); + + /* + * Initialize the OpenSSL library by the book! + * To start with, we must initialize the algorithms. + * We want cleartext error messages instead of just error codes, so we + * load the error_strings. + */ + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms()