diff -Pur postfix-1.1.11.20020613-orig/Makefile.in postfix-1.1.11-20020613/Makefile.in --- postfix-1.1.11.20020613-orig/Makefile.in Sat Jun 8 20:49:42 2002 +++ postfix-1.1.11.20020613/Makefile.in Wed Jun 26 15:26:47 2002 @@ -6,7 +6,8 @@ src/lmtp src/trivial-rewrite src/qmgr src/smtp src/bounce src/pipe \ 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/postsuper src/nqmgr src/qmqpd src/spawn src/flush src/virtual \ + src/tlsmgr MANDIRS = proto man html default: update diff -Pur postfix-1.1.11.20020613-orig/conf/master.cf postfix-1.1.11-20020613/conf/master.cf --- postfix-1.1.11.20020613-orig/conf/master.cf Sat Jun 8 20:21:38 2002 +++ postfix-1.1.11.20020613/conf/master.cf Wed Jun 26 15:26:47 2002 @@ -70,11 +70,16 @@ # (yes) (yes) (yes) (never) (50) # ========================================================================== 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 -Pur postfix-1.1.11.20020613-orig/conf/postfix-files postfix-1.1.11-20020613/conf/postfix-files --- postfix-1.1.11.20020613-orig/conf/postfix-files Sun May 26 23:27:31 2002 +++ postfix-1.1.11.20020613/conf/postfix-files Wed Jun 26 15:26:47 2002 @@ -69,6 +69,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 @@ -139,6 +140,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 @@ -166,6 +168,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/DB_README:f:root:-:644 diff -Pur postfix-1.1.11.20020613-orig/conf/sample-auth.cf postfix-1.1.11-20020613/conf/sample-auth.cf --- postfix-1.1.11.20020613-orig/conf/sample-auth.cf Fri Mar 29 22:36:53 2002 +++ postfix-1.1.11.20020613/conf/sample-auth.cf Wed Jun 26 15:26:47 2002 @@ -117,3 +117,15 @@ # #smtp_sasl_security_options = smtp_sasl_security_options = noplaintext + +# 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. +# THIS OPTION ONLY WORKS WITH SSL/TLS SUPPORT COMPILED IN. +# +# smtpd_tls_auth_only = no + diff -Pur postfix-1.1.11.20020613-orig/conf/sample-smtp.cf postfix-1.1.11-20020613/conf/sample-smtp.cf --- postfix-1.1.11.20020613-orig/conf/sample-smtp.cf Tue Mar 26 22:46:30 2002 +++ postfix-1.1.11.20020613/conf/sample-smtp.cf Wed Jun 26 15:26:47 2002 @@ -188,6 +188,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 -Pur postfix-1.1.11.20020613-orig/conf/sample-smtpd.cf postfix-1.1.11-20020613/conf/sample-smtpd.cf --- postfix-1.1.11.20020613-orig/conf/sample-smtpd.cf Sat Jun 8 20:21:38 2002 +++ postfix-1.1.11.20020613/conf/sample-smtpd.cf Wed Jun 26 15:26:47 2002 @@ -116,6 +116,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 -Pur postfix-1.1.11.20020613-orig/conf/sample-tls.cf postfix-1.1.11-20020613/conf/sample-tls.cf --- postfix-1.1.11.20020613-orig/conf/sample-tls.cf Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11.20020613/conf/sample-tls.cf Wed Jun 26 15:26:47 2002 @@ -0,0 +1,497 @@ +# 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). +# 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 -Pur postfix-1.1.11.20020613-orig/makedefs postfix-1.1.11-20020613/makedefs --- postfix-1.1.11.20020613-orig/makedefs Sat May 4 15:36:19 2002 +++ postfix-1.1.11.20020613/makedefs Wed Jun 26 15:26:47 2002 @@ -52,6 +52,21 @@ SYSTEM=`(uname -s) 2>/dev/null` RELEASE=`(uname -r) 2>/dev/null` VERSION=`(uname -v) 2>/dev/null` +if test -f /usr/include/netinet6/in6.h; then + grep __KAME__ /usr/include/netinet6/in6.h 2>&1 >/dev/null + if [ $? = 1 ]; then + INET6= + else + if [ -f /usr/local/v6/lib/libinet6.a ]; then + INET6=kame + else + INET6=kame-merged + fi + fi +fi +if [ -z "$INET6" -a -f /usr/include/netinet/ip6.h -a -f /usr/include/linux/icmpv6.h ]; then + INET6=linux +fi case "$VERSION" in dcosx*) SYSTEM=$VERSION;; @@ -295,6 +310,26 @@ esac : ${CC='gcc $(WARN)'} ${OPT='-O'} ${DEBUG='-g'} ${AWK=awk} + +case "$INET6" in +kame) + CCARGS="$CCARGS -DINET6 -D__ss_family=ss_family -D__ss_len=ss_len" + if test -f /usr/local/v6/lib/libinet6.a; then + SYSLIBS="$SYSLIBS -L/usr/local/v6/lib -linet6" + fi + ;; +kame-merged) + CCARGS="$CCARGS -DINET6 -D__ss_family=ss_family -D__ss_len=ss_len" + ;; +linux) + CCARGS="$CCARGS -DINET6 -D__ss_family=ss_family" + if test -f /usr/include/libinet6/netinet/ip6.h -a \ + -f /usr/lib/libinet6.a; then + CCARGS="$CCARGS -I/usr/include/libinet6 -DUSAGI_LIBINET6" + SYSLIBS="$SYSLIBS -linet6" + fi + ;; +esac export SYSTYPE AR ARFL RANLIB SYSLIBS CC OPT DEBUG AWK OPTS diff -Pur postfix-1.1.11.20020613-orig/man/man8/tlsmgr.8 postfix-1.1.11-20020613/man/man8/tlsmgr.8 --- postfix-1.1.11.20020613-orig/man/man8/tlsmgr.8 Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11.20020613/man/man8/tlsmgr.8 Wed Jun 26 15:26:47 2002 @@ -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 -Pur postfix-1.1.11.20020613-orig/src/dns/dns_lookup.c postfix-1.1.11-20020613/src/dns/dns_lookup.c --- postfix-1.1.11.20020613-orig/src/dns/dns_lookup.c Sun Feb 4 19:16:20 2001 +++ postfix-1.1.11.20020613/src/dns/dns_lookup.c Wed Jun 26 15:26:47 2002 @@ -132,6 +132,9 @@ } DNS_REPLY; #define INET_ADDR_LEN 4 /* XXX */ +#ifdef INET6 +#define INET6_ADDR_LEN 16 +#endif /* dns_query - query name server and pre-parse the reply */ @@ -337,6 +340,19 @@ memcpy(temp, pos, fixed->length); data_len = fixed->length; break; +#ifdef INET6 + case T_AAAA: + if (fixed->length != INET6_ADDR_LEN) { + msg_warn("extract_answer: bad IPv6 address length: %d", fixed->length); + return (0); + } + if (fixed->length > sizeof(temp)) + msg_panic("dns_get_rr: length %d > DNS_NAME_LEN", + fixed->length); + memcpy(temp, pos, fixed->length); + data_len = fixed->length; + break; +#endif case T_TXT: data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp))); for (src = pos + 1, dst = (unsigned char *) (temp); diff -Pur postfix-1.1.11.20020613-orig/src/global/Makefile.in postfix-1.1.11-20020613/src/global/Makefile.in --- postfix-1.1.11.20020613-orig/src/global/Makefile.in Tue Jun 11 03:13:13 2002 +++ postfix-1.1.11.20020613/src/global/Makefile.in Wed Jun 26 15:26:47 2002 @@ -20,7 +20,7 @@ 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 + strip_addr.c pfixtls.c wildcard_inet_addr.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 +42,7 @@ 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 + strip_addr.o pfixtls.o wildcard_inet_addr.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 \ @@ -60,7 +60,7 @@ sys_exits.h timed_ipc.h tok822.h xtext.h bounce_log.h flush_clnt.h \ 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 + lex_822.h strip_addr.h pfixtls.h wildcard_inet_addr.h TESTSRC = rec2stream.c stream2rec.c recdump.c WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ @@ -1160,3 +1160,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 -Pur postfix-1.1.11.20020613-orig/src/global/mail_params.c postfix-1.1.11-20020613/src/global/mail_params.c --- postfix-1.1.11.20020613-orig/src/global/mail_params.c Sat Jun 8 20:21:40 2002 +++ postfix-1.1.11.20020613/src/global/mail_params.c Wed Jun 26 15:26:47 2002 @@ -223,6 +223,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 -Pur postfix-1.1.11.20020613-orig/src/global/mail_params.h postfix-1.1.11-20020613/src/global/mail_params.h --- postfix-1.1.11.20020613-orig/src/global/mail_params.h Mon Jun 10 19:49:56 2002 +++ postfix-1.1.11.20020613/src/global/mail_params.h Wed Jun 26 15:26:47 2002 @@ -458,6 +458,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. */ @@ -678,6 +706,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; @@ -734,6 +766,12 @@ #define DEF_SMTP_BIND_ADDR "" extern char *var_smtp_bind_addr; +#ifdef INET6 +#define VAR_SMTP_BIND_ADDR6 "smtp_bind_address6" +#define DEF_SMTP_BIND_ADDR6 "" +extern char *var_smtp_bind_addr6; +#endif + #define VAR_SMTP_HELO_NAME "smtp_helo_name" #define DEF_SMTP_HELO_NAME "$myhostname" extern char *var_smtp_helo_name; @@ -767,6 +805,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; @@ -795,6 +837,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. */ @@ -953,6 +1139,16 @@ #define DEF_LMTP_QUIT_TMOUT "300s" extern int var_lmtp_quit_tmout; +#define VAR_LMTP_BIND_ADDR "lmtp_bind_address" +#define DEF_LMTP_BIND_ADDR "" +extern char *var_lmtp_bind_addr; + +#ifdef INET6 +#define VAR_LMTP_BIND_ADDR6 "lmtp_bind_address6" +#define DEF_LMTP_BIND_ADDR6 "" +extern char *var_lmtp_bind_addr6; +#endif + /* * Cleanup service. Header info that exceeds $header_size_limit bytes forces * the start of the message body. @@ -1088,6 +1284,10 @@ #define DEF_RELAY_DOMAINS "$mydestination" extern char *var_relay_domains; +#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; @@ -1167,6 +1367,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 -Pur postfix-1.1.11.20020613-orig/src/global/mail_proto.h postfix-1.1.11-20020613/src/global/mail_proto.h --- postfix-1.1.11.20020613-orig/src/global/mail_proto.h Sun May 12 16:46:59 2002 +++ postfix-1.1.11.20020613/src/global/mail_proto.h Wed Jun 26 15:26:47 2002 @@ -34,6 +34,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 -Pur postfix-1.1.11.20020613-orig/src/global/mynetworks.c postfix-1.1.11-20020613/src/global/mynetworks.c --- postfix-1.1.11.20020613-orig/src/global/mynetworks.c Sun Feb 25 02:46:07 2001 +++ postfix-1.1.11.20020613/src/global/mynetworks.c Wed Jun 26 15:26:47 2002 @@ -50,6 +50,11 @@ #include #include #include +#ifdef INET6 +#include +#include +#include +#endif /* Global library. */ @@ -75,6 +80,9 @@ const char *mynetworks(void) { static VSTRING *result; +#ifdef INET6 + char hbuf[NI_MAXHOST]; +#endif if (result == 0) { char *myname = "mynetworks"; @@ -87,6 +95,9 @@ int junk; int i; int mask_style; +#ifdef INET6 + struct sockaddr *sa; +#endif mask_style = name_mask("mynetworks mask style", mask_styles, var_mynetworks_style); @@ -96,8 +107,19 @@ my_mask_list = own_inet_mask_list(); for (i = 0; i < my_addr_list->used; i++) { +#ifdef INET6 + sa = (struct sockaddr *)&my_addr_list->addrs[i]; + if (sa->sa_family != AF_INET) { + if (sa->sa_family == AF_INET6) + vstring_sprintf_append(result, "XAATODOmynetworks "); + continue; + } + addr = ntohl(((struct sockaddr_in *)sa)->sin_addr.s_addr); + mask = ntohl(((struct sockaddr_in *)&my_mask_list->addrs[i])->sin_addr.s_addr); +#else addr = ntohl(my_addr_list->addrs[i].s_addr); mask = ntohl(my_mask_list->addrs[i].s_addr); +#endif switch (mask_style) { @@ -119,8 +141,15 @@ mask = IN_CLASSD_NET; shift = IN_CLASSD_NSHIFT; } else { +#ifdef INET6 + if (getnameinfo(sa, SA_LEN(sa), hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST)) + strncpy(hbuf, "???", sizeof(hbuf)); + msg_fatal("%s: bad address class: %s", myname, hbuf); +#else msg_fatal("%s: bad address class: %s", myname, inet_ntoa(my_addr_list->addrs[i])); +#endif } break; diff -Pur postfix-1.1.11.20020613-orig/src/global/own_inet_addr.c postfix-1.1.11-20020613/src/global/own_inet_addr.c --- postfix-1.1.11.20020613-orig/src/global/own_inet_addr.c Tue Jul 31 20:38:29 2001 +++ postfix-1.1.11.20020613/src/global/own_inet_addr.c Wed Jun 26 15:26:47 2002 @@ -39,6 +39,10 @@ #include #include #include +#ifdef INET6 +#include +#include +#endif #ifdef STRCASECMP_IN_STRINGS_H #include @@ -101,10 +105,11 @@ */ else { bufp = hosts = mystrdup(var_inet_interfaces); - while ((host = mystrtok(&bufp, sep)) != 0) + while ((host = mystrtok(&bufp, sep)) != 0) { if (inet_addr_host(addr_list, host) == 0) msg_fatal("config variable %s: host not found: %s", VAR_INET_INTERFACES, host); + } myfree(hosts); /* @@ -121,15 +126,39 @@ msg_fatal("could not find any active network interfaces"); for (nvirtual = 0; nvirtual < addr_list->used; nvirtual++) { for (nlocal = 0; /* see below */ ; nlocal++) { - if (nlocal >= local_addrs.used) + if (nlocal >= local_addrs.used) { +#ifdef INET6 + char hbuf[NI_MAXHOST]; + if (getnameinfo((struct sockaddr *)&addr_list->addrs[nvirtual], + SS_LEN(addr_list->addrs[nvirtual]), hbuf, + sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) + strncpy(hbuf, "???", sizeof(hbuf)); + msg_fatal("parameter %s: no local interface found for %s", + VAR_INET_INTERFACES, hbuf); +#else msg_fatal("parameter %s: no local interface found for %s", VAR_INET_INTERFACES, inet_ntoa(addr_list->addrs[nvirtual])); +#endif + } +#ifdef INET6 + if (addr_list->addrs[nvirtual].ss_family == + local_addrs.addrs[nlocal].ss_family && + SS_LEN(addr_list->addrs[nvirtual]) == + SS_LEN(local_addrs.addrs[nlocal]) && + memcmp(&addr_list->addrs[nvirtual], + &local_addrs.addrs[nlocal], + SS_LEN(local_addrs.addrs[nlocal])) == 0) { + inet_addr_list_append(mask_list, (struct sockaddr *)&local_masks.addrs[nlocal]); + break; + } +#else if (addr_list->addrs[nvirtual].s_addr == local_addrs.addrs[nlocal].s_addr) { inet_addr_list_append(mask_list, &local_masks.addrs[nlocal]); break; } +#endif } } inet_addr_list_free(&local_addrs); @@ -139,6 +168,42 @@ /* own_inet_addr - is this my own internet address */ +#ifdef INET6 +int own_inet_addr(struct sockaddr * addr) +{ + int i; + char *p, *q; + int l; + struct sockaddr *sa; + + if (addr_list.used == 0) + own_inet_addr_init(&addr_list, &mask_list); + + for (i = 0; i < addr_list.used; i++) { + sa = (struct sockaddr *)&addr_list.addrs[i]; + if (addr->sa_family != sa->sa_family) + continue; + switch (addr->sa_family) { + case AF_INET: + p = (char *)&((struct sockaddr_in *)addr)->sin_addr; + q = (char *)&((struct sockaddr_in *)&addr_list.addrs[i])->sin_addr; + l = sizeof(struct in_addr); + break; + case AF_INET6: + /* XXX scope */ + p = (char *)&((struct sockaddr_in6 *)addr)->sin6_addr; + q = (char *)&((struct sockaddr_in6 *)&addr_list.addrs[i])->sin6_addr; + l = sizeof(struct in6_addr); + break; + default: + continue; + } + if (memcmp(p, q, l) == 0) + return (1); + } + return (0); +} +#else int own_inet_addr(struct in_addr * addr) { int i; @@ -149,8 +214,8 @@ for (i = 0; i < addr_list.used; i++) if (addr->s_addr == addr_list.addrs[i].s_addr) return (1); - return (0); } +#endif /* own_inet_addr_list - return list of addresses */ diff -Pur postfix-1.1.11.20020613-orig/src/global/own_inet_addr.h postfix-1.1.11-20020613/src/global/own_inet_addr.h --- postfix-1.1.11.20020613-orig/src/global/own_inet_addr.h Sat Feb 24 02:25:32 2001 +++ postfix-1.1.11.20020613/src/global/own_inet_addr.h Wed Jun 26 15:26:48 2002 @@ -15,11 +15,18 @@ * System library. */ #include +#ifdef INET6 +#include +#endif /* * External interface. */ +#ifdef INET6 +extern int own_inet_addr(struct sockaddr *); +#else extern int own_inet_addr(struct in_addr *); +#endif extern struct INET_ADDR_LIST *own_inet_addr_list(void); extern struct INET_ADDR_LIST *own_inet_mask_list(void); diff -Pur postfix-1.1.11.20020613-orig/src/global/peer_name.c postfix-1.1.11-20020613/src/global/peer_name.c --- postfix-1.1.11.20020613-orig/src/global/peer_name.c Sun Jan 28 16:23:02 2001 +++ postfix-1.1.11.20020613/src/global/peer_name.c Wed Jun 26 15:26:48 2002 @@ -69,12 +69,32 @@ PEER_NAME *peer_name(int sock) { static PEER_NAME peer; - struct sockaddr_in sin; - SOCKADDR_SIZE len = sizeof(sin); + union sockunion { + struct { + u_char si_len; + u_char si_family; + u_short si_port; + } su_si; + struct sockaddr peer_un; + struct sockaddr_in peer_un4; +#ifdef INET6 + struct sockaddr_in6 peer_un6; +#endif + } p_un; +#define sun p_un.peer_un +#define sin p_un.peer_un4 +#ifdef INET6 +#define sin6 p_un.peer_un6 + static char hbuf[NI_MAXHOST]; + static char abuf[NI_MAXHOST]; +#else struct hostent *hp; +#endif + SOCKADDR_SIZE len = sizeof(p_un); - if (getpeername(sock, (struct sockaddr *) & sin, &len) == 0) { - switch (sin.sin_family) { + if (getpeername(sock, (struct sockaddr *)&p_un, &len) == 0) { + switch (p_un.peer_un.sa_family) { +#ifndef INET6 case AF_INET: peer.type = PEER_TYPE_INET; hp = gethostbyaddr((char *) &(sin.sin_addr), @@ -83,6 +103,24 @@ hp->h_name : "unknown"); peer.addr = inet_ntoa(sin.sin_addr); return (&peer); +#else + case AF_INET: + peer.type = PEER_TYPE_INET; + if (getnameinfo(&sun, len, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) != 0) + peer.name = "unknown"; + else + peer.name = hbuf; + peer.addr = abuf; + return (&peer); + case AF_INET6: + peer.type = PEER_TYPE_INET6; + if (getnameinfo(&sun, len, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) != 0) + peer.name = "unknown"; + else + peer.name = hbuf; + peer.addr = abuf; + return (&peer); +#endif case AF_UNSPEC: case AF_UNIX: peer.type = PEER_TYPE_LOCAL; diff -Pur postfix-1.1.11.20020613-orig/src/global/peer_name.h postfix-1.1.11-20020613/src/global/peer_name.h --- postfix-1.1.11.20020613-orig/src/global/peer_name.h Fri Dec 11 19:55:32 1998 +++ postfix-1.1.11.20020613/src/global/peer_name.h Wed Jun 26 15:26:48 2002 @@ -22,6 +22,9 @@ #define PEER_TYPE_UNKNOWN 0 #define PEER_TYPE_INET 1 #define PEER_TYPE_LOCAL 2 +#ifdef INET6 +#define PEER_TYPE_INET6 3 +#endif extern PEER_NAME *peer_name(int); diff -Pur postfix-1.1.11.20020613-orig/src/global/pfixtls.c postfix-1.1.11-20020613/src/global/pfixtls.c --- postfix-1.1.11.20020613-orig/src/global/pfixtls.c Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11.20020613/src/global/pfixtls.c Wed Jun 26 15:26:48 2002 @@ -0,0 +1,2742 @@ +/*++ +/* 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, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0 +}; + +#ifdef HAS_SSL + +/* OpenSSL library. */ + +#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; +} 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) + return (-1); /* something happened to the socket */ + 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) + return (-1); /* something happened to the socket */ + 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); +} + + +/* + * 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 *CN_lowercase; + char *peername_left; + X509 *err_cert; + int err; + int depth; + int verify_depth; + int hostname_matched; + 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) { + /* + * 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. + */ + 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(); + } + CN_lowercase = lowercase(buf); + hostname_matched = 0; + if (!strcmp(TLScontext->peername_save, CN_lowercase)) + hostname_matched = 1; + else if ((strlen(CN_lowercase) > 2) && + (CN_lowercase[0] == '*') && (CN_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, CN_lowercase + 2)) + hostname_matched = 1; + } + } + + if (!hostname_matched) { + msg_info("Peer verification: CommonName in certificate does not match: %s != %s", CN_lowercase, TLScontext->peername_save); + 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; + } + } + } + + 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(); + + /* + * 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. + * RFC2487 is only specified for TLSv1, but we want to be as compatible + * as possible, so we will start off with a SSLv2 greeting allowing + * the best we can offer: TLSv1. + * We can restrict this with the options setting later, anyhow. + */ + ctx = SSL_CTX_new(SSLv23_client_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 don't know what is out there. So leave things + * completely open, as of today. + */ + 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_smtp_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_smtp_tls_cipherlist) != 0) + if (SSL_CTX_set_cipher_list(ctx, var_smtp_tls_cipherlist) == 0) { + pfixtls_print_errors(); + return (-1); + } + + /* + * Now we must add the necessary certificate stuff: A client key, a + * client certificate, and the CA certificates for both the client + * cert and the verification of server certificates. + * In fact, we do not need a client certificate, so the certificates + * are only loaded (and checked), if supplied. A clever client would + * handle multiple client certificates and decide based on the list + * of acceptable CAs, sent by the server, which certificate to submit. + * OpenSSL does however not do this and also has no callback hoods to + * easily realize it. + * + * 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_smtp_tls_CAfile) == 0) + CAfile = NULL; + else + CAfile = var_smtp_tls_CAfile; + if (strlen(var_smtp_tls_CApath) == 0) + CApath = NULL; + else + CApath = var_smtp_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); + } + } + + if (strlen(var_smtp_tls_cert_file) == 0) + c_cert_file = NULL; + else + c_cert_file = var_smtp_tls_cert_file; + if (strlen(var_smtp_tls_key_file) == 0) + c_key_file = NULL; + else + c_key_file = var_smtp_tls_key_file; + if (c_cert_file || c_key_file) + if (!set_cert_stuff(ctx, c_cert_file, c_key_file)) { + msg_info("TLS engine: cannot load cert/key data"); + pfixtls_print_errors(); + 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); + + /* + * Finally, the setup for the server certificate checking, done + * "by the book". + */ + SSL_CTX_set_verify(ctx, verify_flags, verify_callback); + + /* + * 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. + * + * In case of the client, there is no callback used in OpenSSL, so + * we must call the session cache functions manually during the process. + */ + SSL_CTX_sess_set_cache_size(ctx, 1); + SSL_CTX_set_timeout(ctx, var_smtp_tls_scache_timeout); + + /* + * 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_smtp_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_smtp_tls_scache_db, "sdbm:", 5)) + msg_warn("Only sdbm: type allowed for %s", + var_smtp_tls_scache_db); + else + scache_db = dict_open(var_smtp_tls_scache_db, O_RDWR, + DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE); + if (!scache_db) + msg_warn("Could not open session cache %s", + var_smtp_tls_scache_db); + /* + * It is practical to have OpenSSL automatically save newly created + * sessions for us by callback. Therefore we have to enable the + * internal session cache for the client side. Disable automatic + * clearing, as smtp has limited lifetime anyway and we can call + * the cleanup routine at will. + */ + SSL_CTX_set_session_cache_mode(ctx, + SSL_SESS_CACHE_CLIENT|SSL_SESS_CACHE_NO_AUTO_CLEAR); + SSL_CTX_sess_set_new_cb(ctx, new_session_cb); + } + + /* + * 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); + TLSpeername_index = SSL_SESSION_get_ex_new_index(0, + "TLSpeername ex_data index", + new_peername_func, + dup_peername_func, + free_peername_func); + + pfixtls_clientengine = 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 + * received by us, so that we can immediately can start the TLS + * handshake process. + */ +int pfixtls_start_clienttls(VSTREAM *stream, int timeout, + int enforce_peername, + const char *peername, + tls_info_t *tls_info) +{ + int sts; + SSL_SESSION *session, *old_session; + SSL_CIPHER *cipher; + X509 *peer; + int verify_flags; + TLScontext_t *TLScontext; + + if (!pfixtls_clientengine) { /* should never happen */ + msg_info("tls_engine not running"); + return (-1); + } + if (var_smtpd_tls_loglevel >= 1) + msg_info("setting up TLS connection to %s", peername); + + /* + * 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 (enforce_peername) { + verify_flags = SSL_VERIFY_PEER; + TLScontext->enforce_verify_errors = 1; + TLScontext->enforce_CN = 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); + } + + old_session = NULL; + + /* + * Find out the hashed HostID for the client cache and try to + * load the session from the cache. + */ + strncpy(TLScontext->peername_save, peername, ID_MAXLENGTH + 1); + TLScontext->peername_save[ID_MAXLENGTH] = '\0'; /* just in case */ + (void)lowercase(TLScontext->peername_save); + if (scache_db) { + old_session = load_clnt_session(peername, enforce_peername); + if (old_session) { + SSL_set_session(TLScontext->con, old_session); +#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. + */ + SSL_set_verify_result(TLScontext->con, old_session->verify_result); +#endif + + } + } + + /* + * Before really starting anything, try to seed the PRNG a little bit + * more. + */ + pfixtls_stir_seed(); + pfixtls_exchange_seed(); + + /* + * Initialize the SSL connection to connect 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_connect_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_smtp_tls_loglevel >= 3) + BIO_set_callback(SSL_get_rbio(TLScontext->con), bio_dump_cb); + + + /* Dump the negotiation for loglevels 3 and 4 */ + if (var_smtp_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 callback 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_connect, NULL, NULL, NULL, 0); + if (sts <= 0) { + msg_info("SSL_connect error to %s: %d", peername, sts); + pfixtls_print_errors(); + session = SSL_get_session(TLScontext->con); + if (session) { + SSL_CTX_remove_session(ctx, session); + if (var_smtp_tls_loglevel >= 2) + msg_info("SSL session removed"); + } + if ((old_session) && (!SSL_session_reused(TLScontext->con))) + SSL_SESSION_free(old_session); /* Must also be removed */ + SSL_free(TLScontext->con); + myfree((char *)TLScontext); + return (-1); + } + + if (!SSL_session_reused(TLScontext->con)) { + SSL_SESSION_free(old_session); /* Remove unused session */ + } + else if (var_smtp_tls_loglevel >= 3) + msg_info("Reusing old session"); + + /* Only loglevel==4 dumps everything */ + if (var_smtp_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; + + 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 server'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 server'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 server's issuer Organization"); + pfixtls_print_errors(); + } + } + tls_info->issuer_CN = TLScontext->issuer_CN; + + if (var_smtp_tls_loglevel >= 1) { + if (tls_info->peer_verified) + msg_info("Verified: subject_CN=%s, issuer=%s", + TLScontext->peer_CN, TLScontext->issuer_CN); + else + msg_info("Unverified: subject_CN=%s, issuer=%s", + TLScontext->peer_CN, TLScontext->issuer_CN); + } + X509_free(peer); + } + + /* + * 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_clientactive = 1; + + /* + * The TLS engine is active, switch to the pfixtls_timed_read/write() + * functions. + */ + 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 to %s: %s with cipher %s (%d/%d bits)", + peername, + tls_info->protocol, tls_info->cipher_name, + tls_info->cipher_usebits, tls_info->cipher_algbits); + + 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 smtp 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_clienttls(VSTREAM *stream, int timeout, int failure, + tls_info_t *tls_info) +{ + TLScontext_t *TLScontext; + int retval; + + if (pfixtls_clientactive) { + 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_clientactive = 0; + + } + + return (0); +} + + +#endif /* HAS_SSL */ diff -Pur postfix-1.1.11-20020613-orig/src/global/pfixtls.h postfix-1.1.11-20020613/src/global/pfixtls.h --- postfix-1.1.11-20020613-orig/src/global/pfixtls.h Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/global/pfixtls.h Wed Jun 26 15:26:48 2002 @@ -0,0 +1,76 @@ +/*++ +/* NAME +/* pfixtls 3h +/* SUMMARY +/* TLS routines +/* SYNOPSIS +/* include "pfixtls.h" +/* DESCRIPTION +/* .nf +/*--*/ + +#ifndef PFIXTLS_H_INCLUDED +#define PFIXTLS_H_INCLUDED + +typedef struct { + int peer_verified; + char *peer_subject; + char *peer_issuer; + char *peer_fingerprint; + char *peer_CN; + char *issuer_CN; + const char *protocol; + const char *cipher_name; + int cipher_usebits; + int cipher_algbits; +} tls_info_t; + +extern const tls_info_t tls_info_zero; + +#ifdef HAS_SSL + +typedef struct { + long scache_db_version; + long openssl_version; + time_t timestamp; /* We could add other info here... */ + int enforce_peername; +} pfixtls_scache_info_t; + +extern const long scache_db_version; +extern const long openssl_version; + +int pfixtls_timed_read(int fd, void *buf, unsigned len, int timout, + void *unused_timeout); +int pfixtls_timed_write(int fd, void *buf, unsigned len, int timeout, + void *unused_timeout); + +extern int pfixtls_serverengine; +int pfixtls_init_serverengine(int verifydepth, int askcert); +int pfixtls_start_servertls(VSTREAM *stream, int timeout, + const char *peername, const char *peeraddr, + tls_info_t *tls_info, int require_cert); +int pfixtls_stop_servertls(VSTREAM *stream, int timeout, int failure, + tls_info_t *tls_info); + +extern int pfixtls_clientengine; +int pfixtls_init_clientengine(int verifydepth); +int pfixtls_start_clienttls(VSTREAM *stream, int timeout, + int enforce_peername, + const char *peername, + tls_info_t *tls_info); +int pfixtls_stop_clienttls(VSTREAM *stream, int timeout, int failure, + tls_info_t *tls_info); + +#endif /* PFIXTLS_H_INCLUDED */ +#endif + +/* LICENSE +/* .ad +/* .fi +/* AUTHOR(S) +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ diff -Pur postfix-1.1.11-20020613-orig/src/global/resolve_local.c postfix-1.1.11-20020613/src/global/resolve_local.c --- postfix-1.1.11-20020613-orig/src/global/resolve_local.c Thu Jan 31 20:56:29 2002 +++ postfix-1.1.11-20020613/src/global/resolve_local.c Wed Jun 26 15:26:48 2002 @@ -42,6 +42,7 @@ #include #include #include +#include #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff @@ -79,7 +80,12 @@ { char *saved_addr = mystrdup(addr); char *dest; +#ifdef INET6 + struct addrinfo hints, *res, *res0; + int error; +#else struct in_addr ipaddr; +#endif int len; #define RETURN(x) { myfree(saved_addr); return(x); } @@ -111,9 +117,25 @@ if (*dest == '[' && dest[len - 1] == ']') { dest++; dest[len -= 2] = 0; +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo(dest, NULL, &hints, &res0); + if (!error) { + for (res = res0; res; res = res->ai_next) { + if (own_inet_addr(res->ai_addr)) { + freeaddrinfo(res0); + RETURN(1); + } + } + freeaddrinfo(res0); + } +#else if ((ipaddr.s_addr = inet_addr(dest)) != INADDR_NONE && own_inet_addr(&ipaddr)) RETURN(1); +#endif } /* diff -Pur postfix-1.1.11-20020613-orig/src/global/wildcard_inet_addr.c postfix-1.1.11-20020613/src/global/wildcard_inet_addr.c --- postfix-1.1.11-20020613-orig/src/global/wildcard_inet_addr.c Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/global/wildcard_inet_addr.c Wed Jun 26 15:26:48 2002 @@ -0,0 +1,82 @@ +/* System library. */ + +#include +#include +#include +#include +#ifdef INET6 +#include +#endif +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ +static INET_ADDR_LIST addr_list; + +/* wildcard_inet_addr_init - initialize my own address list */ + +static void wildcard_inet_addr_init(INET_ADDR_LIST *addr_list) +{ +#ifdef INET6 + struct addrinfo hints, *res, *res0; + char hbuf[NI_MAXHOST]; + int error; +#ifdef NI_WITHSCOPEID + const int niflags = NI_NUMERICHOST | NI_WITHSCOPEID; +#else + const int niflags = NI_NUMERICHOST; +#endif + + inet_addr_list_init(addr_list); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + error = getaddrinfo(NULL, "0", &hints, &res0); + if (error) + msg_fatal("could not get list of wildcard addresses"); + for (res = res0; res; res = res->ai_next) { + if (res->ai_family != AF_INET && res->ai_family != AF_INET6) + continue; + if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf), + NULL, 0, niflags) != 0) + continue; + if (inet_addr_host(addr_list, hbuf) == 0) + continue; /* msg_fatal("config variable %s: host not found: %s", + VAR_INET_INTERFACES, hbuf); */ + } + freeaddrinfo(res0); +#else + if (inet_addr_host(addr_list, "0.0.0.0") == 0) + msg_fatal("config variable %s: host not found: %s", + VAR_INET_INTERFACES, "0.0.0.0"); +#endif +} + +/* wildcard_inet_addr_list - return list of addresses */ + +INET_ADDR_LIST *wildcard_inet_addr_list(void) +{ + if (addr_list.used == 0) + wildcard_inet_addr_init(&addr_list); + + return (&addr_list); +} diff -Pur postfix-1.1.11-20020613-orig/src/global/wildcard_inet_addr.h postfix-1.1.11-20020613/src/global/wildcard_inet_addr.h --- postfix-1.1.11-20020613-orig/src/global/wildcard_inet_addr.h Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/global/wildcard_inet_addr.h Wed Jun 26 15:26:48 2002 @@ -0,0 +1,36 @@ +#ifndef _WILDCARD_INET_ADDR_H_INCLUDED_ +#define _WILDCARD_INET_ADDR_H_INCLUDED_ + +/*++ +/* NAME +/* wildcard_inet_addr_list 3h +/* SUMMARY +/* grab the list of wildcard IP addresses. +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf +/*--*/ + + /* + * System library. + */ +#include +#ifdef INET6 +#include +#endif + + /* + * External interface. + */ +extern struct INET_ADDR_LIST *wildcard_inet_addr_list(void); + +/* LICENSE +/* .ad +/* .fi +/* foo +/* AUTHOR(S) +/* Jun-ichiro itojun Hagino +/*--*/ + +#endif diff -Pur postfix-1.1.11-20020613-orig/src/lmtp/lmtp.c postfix-1.1.11-20020613/src/lmtp/lmtp.c --- postfix-1.1.11-20020613-orig/src/lmtp/lmtp.c Sat May 11 02:07:05 2002 +++ postfix-1.1.11-20020613/src/lmtp/lmtp.c Wed Jun 26 15:26:48 2002 @@ -190,6 +190,12 @@ /* .IP \fBlmtp_quit_timeout\fR /* Timeout for sending the \fBQUIT\fR command, and for /* receiving the server response. +/* .IP \fBlmtp_bind_address\fR +/* Numerical source network address (IPv4) to bind to when making +/* a connection. +/* .IP \fBlmtp_bind_address6\fR +/* Numerical source network address (IPv6) to bind to when making +/* a connection. /* SEE ALSO /* bounce(8) non-delivery status reports /* local(8) local mail delivery @@ -276,6 +282,8 @@ char *var_lmtp_sasl_opts; char *var_lmtp_sasl_passwd; bool var_lmtp_sasl_enable; +char *var_lmtp_bind_addr; +char *var_lmtp_bind_addr6; /* * Global variables. @@ -514,6 +522,10 @@ VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_lmtp_sasl_passwd, 0, 0, VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_lmtp_sasl_opts, 0, 0, + VAR_LMTP_BIND_ADDR, DEF_LMTP_BIND_ADDR, &var_lmtp_bind_addr, 0, 0, +#ifdef INET6 + VAR_LMTP_BIND_ADDR6, DEF_LMTP_BIND_ADDR6, &var_lmtp_bind_addr6, 0, 0, +#endif 0, }; static CONFIG_INT_TABLE int_table[] = { diff -Pur postfix-1.1.11-20020613-orig/src/lmtp/lmtp_connect.c postfix-1.1.11-20020613/src/lmtp/lmtp_connect.c --- postfix-1.1.11-20020613-orig/src/lmtp/lmtp_connect.c Thu Mar 22 02:01:46 2001 +++ postfix-1.1.11-20020613/src/lmtp/lmtp_connect.c Wed Jun 26 15:26:48 2002 @@ -92,11 +92,13 @@ #include #include #include +#include /* Global library. */ #include #include +#include /* DNS library. */ @@ -166,13 +168,42 @@ const char *destination, VSTRING *why) { char *myname = "lmtp_connect_addr"; - struct sockaddr_in sin; - int sock; +#ifdef INET6 + struct sockaddr_storage ss; +#else + struct sockaddr ss; +#endif + struct sockaddr *sa; + struct sockaddr_in *sin; +#ifdef INET6 + struct sockaddr_in6 *sin6; +#endif + SOCKADDR_SIZE salen; +#ifdef INET6 + char hbuf[NI_MAXHOST]; +#else + char hbuf[sizeof("255.255.255.255") + 1]; +#endif + int sock = -1; + INET_ADDR_LIST *addr_list; + char *bind_addr; + + sa = (struct sockaddr *)&ss; + sin = (struct sockaddr_in *)&ss; +#ifdef INET6 + sin6 = (struct sockaddr_in6 *)&ss; +#endif /* * Sanity checks. */ - if (addr->data_len > sizeof(sin.sin_addr)) { +#ifdef INET6 + if (((addr->type==T_A) && (addr->data_len > sizeof(sin->sin_addr))) || + ((addr->type==T_AAAA) && (addr->data_len > sizeof(sin6->sin6_addr)))) +#else + if (addr->data_len > sizeof(sin->sin_addr)) +#endif + { msg_warn("%s: skip address with length %d", myname, addr->data_len); lmtp_errno = LMTP_RETRY; return (0); @@ -181,25 +212,168 @@ /* * Initialize. */ - memset((char *) &sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; + switch (addr->type) { +#ifdef INET6 + case T_AAAA: + bind_addr = var_lmtp_bind_addr6; + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + salen = sizeof(*sin6); + break; +#endif + default: /* T_A: */ + bind_addr = var_lmtp_bind_addr; + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + salen = sizeof(*sin); + break; + }; +#ifdef HAS_SALEN + sa->sa_len = salen; +#endif - if ((sock = socket(sin.sin_family, SOCK_STREAM, 0)) < 0) + if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) msg_fatal("%s: socket: %m", myname); /* + * Allow the sysadmin to specify the source address + */ + + if (*bind_addr) { +#ifndef INET6 + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; +#ifdef HAS_SA_LEN + sin.sin_len = sizeof(sin); +#endif + sin.sin_addr.s_addr = inet_addr(bind_addr); + if (sin.sin_addr.s_addr == INADDR_NONE) + msg_fatal("%s: bad %s parameter: %s", + myname, VAR_LMTP_BIND_ADDR, var_smtp_bind_addr); + if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) + msg_warn("%s: bind %s: %m", myname, inet_ntoa(sin.sin_addr)); + if (msg_verbose) + msg_info("%s: bind %s", myname, inet_ntoa(sin.sin_addr)); +#else + char hbufl[NI_MAXHOST]; + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = sa->sa_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE|AI_NUMERICHOST; + snprintf(hbufl, sizeof(hbufl)-1, "%s", bind_addr); + if (getaddrinfo(hbufl, NULL, &hints, &res) == 0) { + (void)getnameinfo(res->ai_addr, res->ai_addrlen, hbufl, + sizeof(hbufl), NULL, 0, NI_NUMERICHOST); + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) + msg_warn("%s: bind %s: %m", myname, hbufl); + freeaddrinfo(res); + if (msg_verbose) + msg_info("%s: bind %s", myname, hbufl); + } +#endif + } + + /* + * If running on a virtual host, start connections from the + * right address. + */ + + else if ((addr_list = own_inet_addr_list())->used == 1) { +#ifndef INET6 + struct sockaddr_in sin; + unsigned long inaddr; /* XXX BAD!*/ + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; +#ifdef HAS_SA_LEN + sin.sin_len = sizeof(sin); +#endif + memcpy((char *)&sin.sin_addr, addr_list->addrs, sizeof(sin.sin_addr)); + inaddr = (unsigned long)ntohl(sin.sin_addr.s_addr); + if (!IN_CLASSA(inaddr) + /* XXX Are the two following lines correct? */ + || !(((inaddr & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT) == + IN_LOOPBACKNET)) { + if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + msg_warn("%s: bind %s: %m", myname, inet_ntoa(sin.sin_addr)); + if (msg_verbose) + msg_info("%s: bind %s", myname, inet_ntoa(sin.sin_addr)); + } +#else + char hbufl[NI_MAXHOST]; + struct addrinfo hints, *res = NULL, *loopback = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = sa->sa_family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(NULL, "0", &hints, &loopback) != 0) + loopback = NULL; + + /* + * getnameinfo -> getaddrinfo loop is here so that we can + * get rid of port + */ + (void)getnameinfo((struct sockaddr *)addr_list->addrs, + SA_LEN((struct sockaddr *)addr_list->addrs), + hbufl, sizeof(hbufl), NULL, 0, NI_NUMERICHOST); + hbufl[sizeof(hbufl) - 1] = 0; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = sa->sa_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE|AI_NUMERICHOST; + if (getaddrinfo(hbufl, NULL, &hints, &res) == 0 && + !(res->ai_addrlen == loopback->ai_addrlen && + memcmp(res->ai_addr, loopback->ai_addr, res->ai_addrlen) == 0)) { + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) + msg_warn("%s: bind %s: %m", myname, hbufl); + if (msg_verbose) + msg_info("%s: bind %s", myname, hbufl); + } + if (res) + freeaddrinfo(res); + if (loopback) + freeaddrinfo(loopback); +#endif + } + + /* * Connect to the LMTP server. */ - sin.sin_port = port; - memcpy((char *) &sin.sin_addr, addr->data, sizeof(sin.sin_addr)); + switch (addr->type) { +#ifdef INET6 + case T_AAAA: + /* XXX scope-unfriendly */ + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_port = port; + sin6->sin6_family = AF_INET6; + salen = sizeof(*sin6); + memcpy(&sin6->sin6_addr, addr->data, sizeof(sin6->sin6_addr)); + inet_ntop(AF_INET6, &sin6->sin6_addr, hbuf, sizeof(hbuf)); + break; +#endif + default: /* T_A: */ + memset(sin, 0, sizeof(*sin)); + sin->sin_port = port; + sin->sin_family = AF_INET; + salen = sizeof(*sin6); + memcpy(&sin->sin_addr, addr->data, sizeof(sin->sin_addr)); + inet_ntop(AF_INET, &sin->sin_addr, hbuf, sizeof(hbuf)); + break; + } +#ifdef HAS_SA_LEN + sa->sa_len = salen; +#endif if (msg_verbose) msg_info("%s: trying: %s[%s] port %d...", - myname, addr->name, inet_ntoa(sin.sin_addr), ntohs(port)); + myname, addr->name, hbuf, ntohs(port)); - return (lmtp_connect_sock(sock, (struct sockaddr *) & sin, sizeof(sin), - addr->name, inet_ntoa(sin.sin_addr), - destination, why)); + return (lmtp_connect_sock(sock, (struct sockaddr *)sa, salen, + addr->name, hbuf, destination, why)); } /* lmtp_connect_sock - connect a socket over some transport */ diff -Pur postfix-1.1.11-20020613-orig/src/master/master_ent.c postfix-1.1.11-20020613/src/master/master_ent.c --- postfix-1.1.11-20020613-orig/src/master/master_ent.c Sun Dec 23 20:08:58 2001 +++ postfix-1.1.11-20020613/src/master/master_ent.c Wed Jun 26 15:26:48 2002 @@ -284,8 +284,13 @@ inet_addr_host(MASTER_INET_ADDRLIST(serv), host); serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; } else if (strcasecmp(var_inet_interfaces, DEF_INET_INTERFACES) == 0) { +#ifdef INET6 + MASTER_INET_ADDRLIST(serv) = wildcard_inet_addr_list(); + serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; +#else MASTER_INET_ADDRLIST(serv) = 0; /* wild-card */ serv->listen_fd_count = 1; +#endif } else { MASTER_INET_ADDRLIST(serv) = own_inet_addr_list(); /* virtual */ serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; diff -Pur postfix-1.1.11-20020613-orig/src/master/master_listen.c postfix-1.1.11-20020613/src/master/master_listen.c --- postfix-1.1.11-20020613-orig/src/master/master_listen.c Tue May 1 00:47:57 2001 +++ postfix-1.1.11-20020613/src/master/master_listen.c Wed Jun 26 15:26:48 2002 @@ -64,13 +64,22 @@ #include "master.h" +#ifdef INET6 +#include +#include +#endif + /* master_listen_init - enable connection requests */ void master_listen_init(MASTER_SERV *serv) { char *myname = "master_listen_init"; char *end_point; - int n; + int n,m,tmpfd; +#ifdef INET6 + char hbuf[NI_MAXHOST]; + SOCKADDR_SIZE salen; +#endif /* * Find out what transport we should use, then create one or more @@ -111,18 +120,31 @@ serv->listen_fd[0] = inet_listen(MASTER_INET_PORT(serv), serv->max_proc > var_proc_limit ? - serv->max_proc : var_proc_limit, NON_BLOCKING); + serv->max_proc : var_proc_limit, NON_BLOCKING, 1); close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); } else { /* virtual or host:port */ - for (n = 0; n < serv->listen_fd_count; n++) { + for (m = n = 0; n < serv->listen_fd_count; n++) { +#ifdef INET6 + if (getnameinfo((struct sockaddr *)&MASTER_INET_ADDRLIST(serv)->addrs[n], + SA_LEN((struct sockaddr *)&MASTER_INET_ADDRLIST(serv)->addrs[n]), + hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST)) { + strncpy(hbuf, "?????", sizeof(hbuf)); + } + end_point = concatenate(hbuf, ":", MASTER_INET_PORT(serv), (char *) 0); +#else end_point = concatenate(inet_ntoa(MASTER_INET_ADDRLIST(serv)->addrs[n]), ":", MASTER_INET_PORT(serv), (char *) 0); - serv->listen_fd[n] +#endif + tmpfd = inet_listen(end_point, serv->max_proc > var_proc_limit ? - serv->max_proc : var_proc_limit, NON_BLOCKING); - close_on_exec(serv->listen_fd[n], CLOSE_ON_EXEC); + serv->max_proc : var_proc_limit, NON_BLOCKING, 0); + if (tmpfd >= 0) { + serv->listen_fd[m] = tmpfd; + close_on_exec(serv->listen_fd[m++], CLOSE_ON_EXEC); + } myfree(end_point); } + serv->listen_fd_count=m; } break; default: diff -Pur postfix-1.1.11-20020613-orig/src/qmgr/qmgr_message.c postfix-1.1.11-20020613/src/qmgr/qmgr_message.c --- postfix-1.1.11-20020613-orig/src/qmgr/qmgr_message.c Tue Jun 11 01:55:20 2002 +++ postfix-1.1.11-20020613/src/qmgr/qmgr_message.c Wed Jun 26 15:26:48 2002 @@ -507,7 +507,11 @@ * every front-ent program. */ if ((at = strrchr(recipient->address, '@')) != 0 +#ifdef INET6 + && (at + 1)[strspn(at + 1, "[]0123456789.:abcdef")] != 0 +#else && (at + 1)[strspn(at + 1, "[]0123456789.")] != 0 +#endif && valid_hostname(at + 1, DONT_GRIPE) == 0) { qmgr_bounce_recipient(message, recipient, "bad host/domain syntax: \"%s\"", at + 1); diff -Pur postfix-1.1.11-20020613-orig/src/qmqpd/qmqpd_peer.c postfix-1.1.11-20020613/src/qmqpd/qmqpd_peer.c --- postfix-1.1.11-20020613-orig/src/qmqpd/qmqpd_peer.c Thu Jul 5 22:09:35 2001 +++ postfix-1.1.11-20020613/src/qmqpd/qmqpd_peer.c Wed Jun 26 15:29:19 2002 @@ -70,6 +70,11 @@ ) #endif +#ifdef INET6 +#define GAI_STRERROR(error) \ + ((error = EAI_SYSTEM) ? gai_strerror(error) : strerror(errno)) +#endif + /* Utility library. */ #include @@ -79,7 +84,6 @@ /* Global library. */ - /* Application-specific. */ #include "qmqpd.h" @@ -88,16 +92,23 @@ void qmqpd_peer_init(QMQPD_STATE *state) { - struct sockaddr_in sin; - SOCKADDR_SIZE len = sizeof(sin); +#ifdef INET6 + struct sockaddr_storage ss; +#else + struct sockaddr ss; + struct in_addr *in; struct hostent *hp; - int i; +#endif + struct sockaddr *sa; + SOCKADDR_SIZE len; + + sa = (struct sockaddr *)&ss; + len = sizeof(ss); /* * Look up the peer address information. */ - if (getpeername(vstream_fileno(state->client), - (struct sockaddr *) & sin, &len) >= 0) { + if (getpeername(vstream_fileno(state->client), sa, &len) >= 0) { errno = 0; } @@ -112,16 +123,50 @@ /* * Look up and "verify" the client hostname. */ - else if (errno == 0 && sin.sin_family == AF_INET) { - state->addr = mystrdup(inet_ntoa(sin.sin_addr)); - hp = gethostbyaddr((char *) &(sin.sin_addr), - sizeof(sin.sin_addr), AF_INET); - if (hp == 0) { + else if (errno == 0 && (sa->sa_family == AF_INET +#ifdef INET6 + || sa->sa_family == AF_INET6 +#endif + )) { +#ifdef INET6 + char hbuf[NI_MAXHOST]; + char abuf[NI_MAXHOST]; + struct addrinfo hints, *rnull = NULL; +#else + char abuf[sizeof("255.255.255.255") + 1]; + char *hbuf; +#endif + int error = -1; + +#ifdef INET6 + (void)getnameinfo(sa, len, abuf, sizeof(abuf), NULL, 0, + NI_NUMERICHOST); +#else + in = &((struct sockaddr_in *)sa)->sin_addr; + inet_ntop(AF_INET, in, abuf, sizeof(abuf)); +#endif + + state->addr = mystrdup(abuf); +#ifdef INET6 + error = getnameinfo(sa, len, hbuf, sizeof(hbuf), NULL, 0, + NI_NAMEREQD); +#else + hbuf = NULL; + hp = gethostbyaddr((char *)in, sizeof(*in), AF_INET); + if (hp) { + error = 0; + hbuf = mystrdup(hp->h_name); + state->name = mystrdup("unknown"); + } else { + error = 1; + } +#endif + if (error) { state->name = mystrdup("unknown"); - } else if (!valid_hostname(hp->h_name, DONT_GRIPE)) { + } else if (!valid_hostname(hbuf, DONT_GRIPE)) { state->name = mystrdup("unknown"); } else { - state->name = mystrdup(hp->h_name); /* hp->name is clobbered!! */ + state->name = mystrdup(hbuf); /* hp->name is clobbered!! */ /* * Reject the hostname if it does not list the peer address. @@ -131,16 +176,31 @@ state->name = mystrdup("unknown"); \ } +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(state->name, NULL, &hints, &rnull); + if (error) { + msg_warn("%s: hostname %s verification failed: %s", + state->addr, state->name, GAI_STRERROR(error)); + REJECT_PEER_NAME(state); + } + /* memcmp() isn't needed if we use getaddrinfo */ + if (rnull) + freeaddrinfo(rnull); +#else hp = gethostbyname(state->name); /* clobbers hp->name!! */ if (hp == 0) { msg_warn("%s: hostname %s verification failed: %s", state->addr, state->name, HSTRERROR(h_errno)); REJECT_PEER_NAME(state); - } else if (hp->h_length != sizeof(sin.sin_addr)) { + } else if (hp->h_length != sizeof(*in)) { msg_warn("%s: hostname %s verification failed: bad address size %d", state->addr, state->name, hp->h_length); REJECT_PEER_NAME(state); } else { + int i; for (i = 0; /* void */ ; i++) { if (hp->h_addr_list[i] == 0) { msg_warn("%s: address not listed for hostname %s", @@ -148,12 +208,12 @@ REJECT_PEER_NAME(state); break; } - if (memcmp(hp->h_addr_list[i], - (char *) &sin.sin_addr, - sizeof(sin.sin_addr)) == 0) + if (memcmp(hp->h_addr_list[i], (char *)in, + sizeof(*in)) == 0) break; /* keep peer name */ } } +#endif } } diff -Pur postfix-1.1.11-20020613-orig/src/smtp/Makefile.in postfix-1.1.11-20020613/src/smtp/Makefile.in --- postfix-1.1.11-20020613-orig/src/smtp/Makefile.in Tue Jun 11 03:14:03 2002 +++ postfix-1.1.11-20020613/src/smtp/Makefile.in Wed Jun 26 15:26:48 2002 @@ -84,6 +84,7 @@ smtp.o: ../../include/iostuff.h smtp.o: ../../include/attr.h smtp.o: ../../include/mail_server.h +smtp.o: ../../include/pfixtls.h smtp.o: smtp.h smtp.o: smtp_sasl.h smtp_addr.o: smtp_addr.c @@ -103,6 +104,7 @@ smtp_addr.o: ../../include/argv.h smtp_addr.o: ../../include/deliver_request.h smtp_addr.o: ../../include/recipient_list.h +smtp_addr.o: ../../include/pfixtls.h smtp_addr.o: smtp_addr.h smtp_chat.o: smtp_chat.c smtp_chat.o: ../../include/sys_defs.h @@ -123,6 +125,7 @@ smtp_chat.o: ../../include/cleanup_user.h smtp_chat.o: ../../include/mail_error.h smtp_chat.o: ../../include/name_mask.h +smtp_chat.o: ../../include/pfixtls.h smtp_chat.o: smtp.h smtp_connect.o: smtp_connect.c smtp_connect.o: ../../include/sys_defs.h @@ -139,10 +142,12 @@ smtp_connect.o: ../../include/mail_params.h smtp_connect.o: ../../include/own_inet_addr.h smtp_connect.o: ../../include/dns.h +smtp_connect.o: ../../include/get_port.h smtp_connect.o: smtp.h smtp_connect.o: ../../include/argv.h smtp_connect.o: ../../include/deliver_request.h smtp_connect.o: ../../include/recipient_list.h +smtp_connetc.o: ../../include/pfixtls.h smtp_connect.o: smtp_addr.h smtp_proto.o: smtp_proto.c smtp_proto.o: ../../include/sys_defs.h @@ -174,6 +179,7 @@ smtp_proto.o: ../../include/attr.h smtp_proto.o: ../../include/mime_state.h smtp_proto.o: ../../include/header_opts.h +smtp_proto.o: ../../include/pfixtls.h smtp_proto.o: smtp.h smtp_proto.o: ../../include/argv.h smtp_proto.o: smtp_sasl.h @@ -219,9 +225,12 @@ smtp_session.o: ../../include/stringops.h smtp_session.o: ../../include/vstring.h smtp_session.o: smtp.h +smtp_session.o: ../../include/mail_params.h +smtp_session.o: ../../include/pfixtls.h smtp_session.o: ../../include/argv.h smtp_session.o: ../../include/deliver_request.h smtp_session.o: ../../include/recipient_list.h +smtp_session.o: ../../include/maps.h smtp_state.o: smtp_state.c smtp_state.o: ../../include/sys_defs.h smtp_state.o: ../../include/mymalloc.h @@ -235,6 +244,7 @@ smtp_state.o: ../../include/argv.h smtp_state.o: ../../include/deliver_request.h smtp_state.o: ../../include/recipient_list.h +smtp_state.o: ../../include/pfixtls.h smtp_state.o: smtp_sasl.h smtp_trouble.o: smtp_trouble.c smtp_trouble.o: ../../include/sys_defs.h @@ -254,6 +264,7 @@ smtp_trouble.o: ../../include/name_mask.h smtp_trouble.o: smtp.h smtp_trouble.o: ../../include/argv.h +smtp_trouble.o: ../../include/pfixtls.h smtp_unalias.o: smtp_unalias.c smtp_unalias.o: ../../include/sys_defs.h smtp_unalias.o: ../../include/htable.h @@ -266,3 +277,4 @@ smtp_unalias.o: ../../include/argv.h smtp_unalias.o: ../../include/deliver_request.h smtp_unalias.o: ../../include/recipient_list.h +smtp_unalias.o: ../../include/pfixtls.h diff -Pur postfix-1.1.11-20020613-orig/src/smtp/sasl_problem_diff postfix-1.1.11-20020613/src/smtp/sasl_problem_diff --- postfix-1.1.11-20020613-orig/src/smtp/sasl_problem_diff Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/smtp/sasl_problem_diff Wed Jun 26 15:26:48 2002 @@ -0,0 +1,17 @@ +--- smtp_proto.c.old Wed May 15 14:01:56 2002 ++++ smtp_proto.c Fri May 24 21:13:50 2002 +@@ -372,8 +372,13 @@ + else if (strcasecmp(word, "STARTTLS") == 0) + state->features |= SMTP_FEATURE_STARTTLS; + #ifdef USE_SASL_AUTH +- else if (var_smtp_sasl_enable && strcasecmp(word, "AUTH") == 0) ++ else if (var_smtp_sasl_enable && strcasecmp(word, "AUTH") == 0) { ++ if (state->sasl_mechanism_list) { ++ myfree(state->sasl_mechanism_list); ++ state->sasl_mechanism_list = 0; ++ } + smtp_sasl_helo_auth(state, words); ++ } + #endif + } + } diff -Pur postfix-1.1.11-20020613-orig/src/smtp/smtp.c postfix-1.1.11-20020613/src/smtp/smtp.c --- postfix-1.1.11-20020613-orig/src/smtp/smtp.c Mon May 27 01:07:04 2002 +++ postfix-1.1.11-20020613/src/smtp/smtp.c Wed Jun 26 15:26:48 2002 @@ -100,7 +100,11 @@ /* .IP \fBsmtp_never_send_ehlo\fR /* Never send EHLO at the start of a connection. /* .IP \fBsmtp_bind_address\fR -/* Numerical source network address to bind to when making a connection. +/* Numerical source network address (IPv4) to bind to when making +/* a connection. +/* .IP \fBsmtp_bind_address6\fR +/* Numerical source network address (IPv6) to bind to when making +/* a connection. /* .IP \fBsmtp_line_length_limit\fR /* Length limit for SMTP message content lines. Zero means no limit. /* Some SMTP servers misbehave on long lines. @@ -240,6 +244,7 @@ #include #include #include +#include /* Single server skeleton. */ @@ -256,6 +261,7 @@ */ int var_smtp_conn_tmout; int var_smtp_helo_tmout; +int var_smtp_starttls_tmout; int var_smtp_mail_tmout; int var_smtp_rcpt_tmout; int var_smtp_data0_tmout; @@ -277,11 +283,20 @@ char *var_smtp_sasl_passwd; bool var_smtp_sasl_enable; char *var_smtp_bind_addr; +#ifdef INET6 +char *var_smtp_bind_addr6; +#endif bool var_smtp_rand_addr; int var_smtp_pix_thresh; int var_smtp_pix_delay; int var_smtp_line_limit; char *var_smtp_helo_name; +int var_smtp_use_tls; +int var_smtp_enforce_tls; +int var_smtp_tls_enforce_peername; +char *var_smtp_tls_per_site; +int var_smtp_tls_scert_vd; +int var_smtp_tls_note_starttls_offer; /* * Global variables. smtp_errno is set by the address lookup routines and by @@ -391,6 +406,7 @@ static void pre_init(char *unused_name, char **unused_argv) { + debug_peer_init(); if (var_smtp_sasl_enable) @@ -400,6 +416,14 @@ msg_warn("%s is true, but SASL support is not compiled in", VAR_SMTP_SASL_ENABLE); #endif + /* + * Initialize the TLS data before entering the chroot jail + */ +#ifdef HAS_SSL + if (var_smtp_use_tls || var_smtp_enforce_tls || var_smtp_tls_per_site[0]) + pfixtls_init_clientengine(var_smtp_tls_scert_vd); + smtp_tls_list_init(); +#endif } /* pre_accept - see if tables have changed */ @@ -434,7 +458,11 @@ VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, VAR_SMTP_BIND_ADDR, DEF_SMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0, +#ifdef INET6 + VAR_SMTP_BIND_ADDR6, DEF_SMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0, +#endif VAR_SMTP_HELO_NAME, DEF_SMTP_HELO_NAME, &var_smtp_helo_name, 1, 0, + VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, 0, }; static CONFIG_TIME_TABLE time_table[] = { @@ -448,6 +476,7 @@ VAR_SMTP_QUIT_TMOUT, DEF_SMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, VAR_SMTP_PIX_THRESH, DEF_SMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, VAR_SMTP_PIX_DELAY, DEF_SMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, + VAR_SMTP_STARTTLS_TMOUT, DEF_SMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0, 0, }; static CONFIG_INT_TABLE int_table[] = { @@ -463,6 +492,10 @@ VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo, VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable, VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, + VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_SMTP_TLS_ENFORCE_PN, DEF_SMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, + VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, 0, }; diff -Pur postfix-1.1.11-20020613-orig/src/smtp/smtp.h postfix-1.1.11-20020613/src/smtp/smtp.h --- postfix-1.1.11-20020613-orig/src/smtp/smtp.h Thu May 23 21:18:02 2002 +++ postfix-1.1.11-20020613/src/smtp/smtp.h Wed Jun 26 15:26:48 2002 @@ -27,6 +27,7 @@ * Global library. */ #include +#include /* * State information associated with each SMTP delivery. We're bundling the @@ -79,9 +80,14 @@ char *addr; /* mail exchanger */ char *namaddr; /* mail exchanger */ int best; /* most preferred host */ + int tls_use_tls; /* can do TLS */ + int tls_enforce_tls; /* must do TLS */ + int tls_enforce_peername; /* cert must match */ + tls_info_t tls_info; /* TLS connection state */ } SMTP_SESSION; -extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, char *, char *); +extern void smtp_tls_list_init(void); +extern SMTP_SESSION *smtp_session_alloc(char *, VSTREAM *, char *, char *); extern void smtp_session_free(SMTP_SESSION *); /* diff -Pur postfix-1.1.11-20020613-orig/src/smtp/smtp_addr.c postfix-1.1.11-20020613/src/smtp/smtp_addr.c --- postfix-1.1.11-20020613-orig/src/smtp/smtp_addr.c Sun Jul 8 17:05:26 2001 +++ postfix-1.1.11-20020613/src/smtp/smtp_addr.c Wed Jun 26 15:26:48 2002 @@ -134,18 +134,68 @@ static void smtp_print_addr(char *what, DNS_RR *addr_list) { DNS_RR *addr; - struct in_addr in_addr; +#ifdef INET6 + struct sockaddr_storage ss; +#else + struct sockaddr ss; +#endif + struct sockaddr_in *sin; +#ifdef INET6 + struct sockaddr_in6 *sin6; + char hbuf[NI_MAXHOST]; +#else + char hbuf[sizeof("255.255.255.255") + 1]; +#endif msg_info("begin %s address list", what); for (addr = addr_list; addr; addr = addr->next) { - if (addr->data_len > sizeof(addr)) { - msg_warn("skipping address length %d", addr->data_len); - } else { - memcpy((char *) &in_addr, addr->data, sizeof(in_addr)); - msg_info("pref %4d host %s/%s", - addr->pref, addr->name, - inet_ntoa(in_addr)); + if (addr->class != C_IN) { + msg_warn("skipping unsupported address (class=%u)", addr->class); + continue; } + switch (addr->type) { + case T_A: + if (addr->data_len != sizeof(sin->sin_addr)) { + msg_warn("skipping invalid address (AAAA, len=%u)", + addr->data_len); + continue; + } + sin = (struct sockaddr_in *)&ss; + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; +#ifdef HAS_SA_LEN + sin->sin_len = sizeof(*sin); +#endif + memcpy(&sin->sin_addr, addr->data, sizeof(sin->sin_addr)); + break; +#ifdef INET6 + case T_AAAA: + if (addr->data_len != sizeof(sin6->sin6_addr)) { + msg_warn("skipping invalid address (AAAA, len=%u)", + addr->data_len); + continue; + } + sin6 = (struct sockaddr_in6 *)&ss; + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; +#ifdef HAS_SA_LEN + sin6->sin6_len = sizeof(*sin6); +#endif + memcpy(&sin6->sin6_addr, addr->data, sizeof(sin6->sin6_addr)); + break; +#endif + default: + msg_warn("skipping unsupported address (type=%u)", addr->type); + continue; + } + +#ifdef INET6 + (void)getnameinfo((struct sockaddr *)&ss, SS_LEN(ss), + hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); +#else + (void)inet_ntop(AF_INET, &sin->sin_addr, hbuf, sizeof(hbuf)); +#endif + msg_info("pref %4d host %s/%s", addr->pref, addr->name, hbuf); } msg_info("end %s address list", what); } @@ -155,15 +205,23 @@ static DNS_RR *smtp_addr_one(DNS_RR *addr_list, char *host, unsigned pref, VSTRING *why) { char *myname = "smtp_addr_one"; +#ifndef INET6 struct in_addr inaddr; - DNS_FIXED fixed; DNS_RR *addr = 0; DNS_RR *rr; struct hostent *hp; +#else + struct addrinfo hints, *res0, *res; + int error = -1; + char *addr; + size_t addrlen; +#endif + DNS_FIXED fixed; if (msg_verbose) msg_info("%s: host %s", myname, host); +#ifndef INET6 /* * Interpret a numerical name as an address. */ @@ -216,6 +274,48 @@ smtp_errno = SMTP_FAIL; break; } +#else + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, NULL, &hints, &res0); + if (error) { + switch (error) { + case EAI_AGAIN: + smtp_errno = SMTP_RETRY; + break; + default: + vstring_sprintf(why, "[%s]: %s",host,gai_strerror(error)); + smtp_errno = SMTP_FAIL; + break; + } + return (addr_list); + } + for (res = res0; res; res = res->ai_next) { + memset((char *) &fixed, 0, sizeof(fixed)); + switch(res->ai_family) { + case AF_INET6: + /* XXX not scope friendly */ + fixed.type = T_AAAA; + addr = (char *)&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr; + addrlen = sizeof(struct in6_addr); + break; + case AF_INET: + fixed.type = T_A; + addr = (char *)&((struct sockaddr_in *)res->ai_addr)->sin_addr; + addrlen = sizeof(struct in_addr); + break; + default: + msg_warn("%s: unknown address family %d for %s", + myname, res->ai_family, host); + continue; + } + addr_list = dns_rr_append(addr_list, + dns_rr_create(host, &fixed, pref, addr, addrlen)); + } + if (res0) + freeaddrinfo(res0); +#endif return (addr_list); } @@ -251,6 +351,9 @@ INET_ADDR_LIST *self; DNS_RR *addr; int i; +#ifdef INET6 + struct sockaddr *sa; +#endif /* * Find the first address that lists any address that this mail system is @@ -260,12 +363,36 @@ self = own_inet_addr_list(); for (addr = addr_list; addr; addr = addr->next) { - for (i = 0; i < self->used; i++) + for (i = 0; i < self->used; i++) { +#ifdef INET6 + sa = (struct sockaddr *)&self->addrs[i]; + switch(addr->type) { + case T_AAAA: + /* XXX scope */ + if (sa->sa_family != AF_INET6) + break; + if (memcmp(&((struct sockaddr_in6 *)sa)->sin6_addr, + addr->data, sizeof(struct in6_addr)) == 0) { + return(addr); + } + break; + case T_A: + if (sa->sa_family != AF_INET) + break; + if (memcmp(&((struct sockaddr_in *)sa)->sin_addr, + addr->data, sizeof(struct in_addr)) == 0) { + return(addr); + } + break; + } +#else if (INADDRP(addr->data)->s_addr == self->addrs[i].s_addr) { if (msg_verbose) msg_info("%s: found at pref %d", myname, addr->pref); return (addr); } +#endif + } } /* diff -Pur postfix-1.1.11-20020613-orig/src/smtp/smtp_connect.c postfix-1.1.11-20020613/src/smtp/smtp_connect.c --- postfix-1.1.11-20020613-orig/src/smtp/smtp_connect.c Sun Jul 8 21:40:03 2001 +++ postfix-1.1.11-20020613/src/smtp/smtp_connect.c Wed Jun 26 15:26:48 2002 @@ -81,6 +81,7 @@ /* System library. */ #include +#include #include #include #include @@ -110,12 +111,14 @@ #include #include #include +#include #include /* Global library. */ #include #include +#include /* DNS library. */ @@ -128,23 +131,50 @@ /* smtp_connect_addr - connect to explicit address */ -static SMTP_SESSION *smtp_connect_addr(DNS_RR *addr, unsigned port, +static SMTP_SESSION *smtp_connect_addr(char *dest, DNS_RR *addr, unsigned port, VSTRING *why) { char *myname = "smtp_connect_addr"; - struct sockaddr_in sin; - int sock; +#ifdef INET6 + struct sockaddr_storage ss; +#else + struct sockaddr ss; +#endif + struct sockaddr *sa; + struct sockaddr_in *sin; +#ifdef INET6 + struct sockaddr_in6 *sin6; +#endif + SOCKADDR_SIZE salen; +#ifdef INET6 + char hbuf[NI_MAXHOST]; +#else + char hbuf[sizeof("255.255.255.255") + 1]; +#endif + int sock = -1; INET_ADDR_LIST *addr_list; int conn_stat; int saved_errno; VSTREAM *stream; int ch; - unsigned long inaddr; + char *bind_addr; + + sa = (struct sockaddr *)&ss; + sin = (struct sockaddr_in *)&ss; +#ifdef INET6 + sin6 = (struct sockaddr_in6 *)&ss; +#endif /* * Sanity checks. */ - if (addr->data_len > sizeof(sin.sin_addr)) { +#ifdef INET6 + if (((addr->type==T_A) && (addr->data_len > sizeof(sin->sin_addr))) || + ((addr->type==T_AAAA) && (addr->data_len > sizeof(sin6->sin6_addr)))) +#else + if (addr->data_len > sizeof(sin->sin_addr)) +#endif + { msg_warn("%s: skip address with length %d", myname, addr->data_len); smtp_errno = SMTP_RETRY; return (0); @@ -153,18 +183,42 @@ /* * Initialize. */ - memset((char *) &sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - - if ((sock = socket(sin.sin_family, SOCK_STREAM, 0)) < 0) - msg_fatal("%s: socket: %m", myname); - + switch (addr->type) { +#ifdef INET6 + case T_AAAA: + bind_addr = var_smtp_bind_addr6; + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + salen = sizeof(*sin6); + break; +#endif + default: /* T_A: */ + bind_addr = var_smtp_bind_addr; + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + salen = sizeof(*sin); + break; + } +#ifdef HAS_SA_LEN + sa->sa_len = salen; +#endif + if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_warn("%s: socket: %m", myname); + /* * Allow the sysadmin to specify the source address, for example, as "-o * smtp_bind_address=x.x.x.x" in the master.cf file. */ - if (*var_smtp_bind_addr) { - sin.sin_addr.s_addr = inet_addr(var_smtp_bind_addr); + if (*bind_addr) { +#ifndef INET6 + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; +#ifdef HAS_SA_LEN + sin.sin_len = sizeof(sin); +#endif + sin.sin_addr.s_addr = inet_addr(bind_addr); if (sin.sin_addr.s_addr == INADDR_NONE) msg_fatal("%s: bad %s parameter: %s", myname, VAR_SMTP_BIND_ADDR, var_smtp_bind_addr); @@ -172,6 +226,25 @@ msg_warn("%s: bind %s: %m", myname, inet_ntoa(sin.sin_addr)); if (msg_verbose) msg_info("%s: bind %s", myname, inet_ntoa(sin.sin_addr)); +#else + char hbufl[NI_MAXHOST]; + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = sa->sa_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE|AI_NUMERICHOST; + snprintf(hbufl, sizeof(hbufl)-1, "%s", bind_addr); + if (getaddrinfo(hbufl, NULL, &hints, &res) == 0) { + (void)getnameinfo(res->ai_addr, res->ai_addrlen, hbufl, + sizeof(hbufl), NULL, 0, NI_NUMERICHOST); + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) + msg_warn("%s: bind %s: %m", myname, hbufl); + freeaddrinfo(res); + if (msg_verbose) + msg_info("%s: bind %s", myname, hbufl); + } +#endif } /* @@ -179,8 +252,17 @@ * the mail appears to come from the "right" machine address. */ else if ((addr_list = own_inet_addr_list())->used == 1) { +#ifndef INET6 + struct sockaddr_in sin; + unsigned long inaddr; /*XXX BAD!*/ + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; +#ifdef HAS_SA_LEN + sin.sin_len = sizeof(sin); +#endif memcpy((char *) &sin.sin_addr, addr_list->addrs, sizeof(sin.sin_addr)); - inaddr = ntohl(sin.sin_addr.s_addr); + inaddr = (unsigned long)ntohl(sin.sin_addr.s_addr); if (!IN_CLASSA(inaddr) || !(((inaddr & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)) { if (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) @@ -188,30 +270,85 @@ if (msg_verbose) msg_info("%s: bind %s", myname, inet_ntoa(sin.sin_addr)); } +#else + char hbufl[NI_MAXHOST]; + struct addrinfo hints, *res = NULL, *loopback = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = sa->sa_family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(NULL, "0", &hints, &loopback) != 0) + loopback = NULL; + + /* + * getnameinfo -> getaddrinfo loop is here so that we can + * get rid of port. + */ + (void)getnameinfo((struct sockaddr *)addr_list->addrs, SA_LEN((struct sockaddr *)addr_list->addrs), + hbufl, sizeof(hbufl), NULL, 0, NI_NUMERICHOST); + hbufl[sizeof(hbufl)-1] = 0; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = sa->sa_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE|AI_NUMERICHOST; + if (getaddrinfo(hbufl, NULL, &hints, &res) == 0 && + !(res->ai_addrlen == loopback->ai_addrlen && + memcmp(res->ai_addr, loopback->ai_addr, res->ai_addrlen) == 0)) { + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) + msg_warn("%s: bind %s: %m", myname, hbufl); + if (msg_verbose) + msg_info("%s: bind %s", myname, hbufl); + } + if (res) + freeaddrinfo(res); + if (loopback) + freeaddrinfo(loopback); +#endif } /* * Connect to the SMTP server. */ - sin.sin_port = port; - memcpy((char *) &sin.sin_addr, addr->data, sizeof(sin.sin_addr)); + switch (addr->type) { +#ifdef INET6 + case T_AAAA: + /* XXX scope unfriendly */ + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_port = port; + sin6->sin6_family = AF_INET6; + salen = sizeof(*sin6); + memcpy(&sin6->sin6_addr, addr->data, sizeof(sin6->sin6_addr)); + inet_ntop(AF_INET6, &sin6->sin6_addr, hbuf, sizeof(hbuf)); + break; +#endif + default: /* T_A */ + memset(sin, 0, sizeof(*sin)); + sin->sin_port = port; + sin->sin_family = AF_INET; + salen = sizeof(*sin); + memcpy(&sin->sin_addr, addr->data, sizeof(sin->sin_addr)); + inet_ntop(AF_INET, &sin->sin_addr, hbuf, sizeof(hbuf)); + break; + } +#ifdef HAS_SA_LEN + sa->sa_len = salen; +#endif if (msg_verbose) msg_info("%s: trying: %s[%s] port %d...", - myname, addr->name, inet_ntoa(sin.sin_addr), ntohs(port)); + myname, addr->name, hbuf, ntohs(port)); if (var_smtp_conn_tmout > 0) { non_blocking(sock, NON_BLOCKING); - conn_stat = timed_connect(sock, (struct sockaddr *) & sin, - sizeof(sin), var_smtp_conn_tmout); + conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout); saved_errno = errno; non_blocking(sock, BLOCKING); errno = saved_errno; } else { - conn_stat = connect(sock, (struct sockaddr *) & sin, sizeof(sin)); + conn_stat = connect(sock, sa, salen); } if (conn_stat < 0) { vstring_sprintf(why, "connect to %s[%s]: %m", - addr->name, inet_ntoa(sin.sin_addr)); + addr->name, hbuf); smtp_errno = SMTP_RETRY; close(sock); return (0); @@ -221,8 +358,8 @@ * Skip this host if it takes no action within some time limit. */ if (read_wait(sock, var_smtp_helo_tmout) < 0) { - vstring_sprintf(why, "connect to %s[%s]: read timeout", - addr->name, inet_ntoa(sin.sin_addr)); + vstring_sprintf(why, "connect to %s [%s]: read timeout", + addr->name, hbuf); smtp_errno = SMTP_RETRY; close(sock); return (0); @@ -233,8 +370,8 @@ */ stream = vstream_fdopen(sock, O_RDWR); if ((ch = VSTREAM_GETC(stream)) == VSTREAM_EOF) { - vstring_sprintf(why, "connect to %s[%s]: server dropped connection", - addr->name, inet_ntoa(sin.sin_addr)); + vstring_sprintf(why, "connect to %s [%s]: server dropped connection", + addr->name, hbuf); smtp_errno = SMTP_RETRY; vstream_fclose(stream); return (0); @@ -246,7 +383,7 @@ */ if (ch == '4' && var_smtp_skip_4xx_greeting) { vstring_sprintf(why, "connect to %s[%s]: server refused mail service", - addr->name, inet_ntoa(sin.sin_addr)); + addr->name, hbuf); smtp_errno = SMTP_RETRY; vstream_fclose(stream); return (0); @@ -257,12 +394,12 @@ */ if (ch == '5' && var_smtp_skip_5xx_greeting) { vstring_sprintf(why, "connect to %s[%s]: server refused mail service", - addr->name, inet_ntoa(sin.sin_addr)); + addr->name, hbuf); smtp_errno = SMTP_RETRY; vstream_fclose(stream); return (0); } - return (smtp_session_alloc(stream, addr->name, inet_ntoa(sin.sin_addr))); + return (smtp_session_alloc(dest, stream, addr->name, hbuf)); } /* smtp_connect_host - direct connection to host */ @@ -272,7 +409,7 @@ SMTP_SESSION *session = 0; DNS_RR *addr_list; DNS_RR *addr; - + /* * Try each address in the specified order until we find one that works. * The addresses belong to the same A record, so we have no information @@ -280,7 +417,7 @@ */ addr_list = smtp_host_addr(host, why); for (addr = addr_list; addr; addr = addr->next) { - if ((session = smtp_connect_addr(addr, port, why)) != 0) { + if ((session = smtp_connect_addr(host, addr, port, why)) != 0) { session->best = 1; break; } @@ -309,7 +446,7 @@ */ addr_list = smtp_domain_addr(name, why, found_myself); for (addr = addr_list; addr; addr = addr->next) { - if ((session = smtp_connect_addr(addr, port, why)) != 0) { + if ((session = smtp_connect_addr(name, addr, port, why)) != 0) { session->best = (addr->pref == addr_list->pref); break; } @@ -379,6 +516,7 @@ msg_fatal("unknown service: %s/%s", service, protocol); *portp = sp->s_port; } + return (buf); } diff -Pur postfix-1.1.11-20020613-orig/src/smtp/smtp_proto.c postfix-1.1.11-20020613/src/smtp/smtp_proto.c --- postfix-1.1.11-20020613-orig/src/smtp/smtp_proto.c Sat Jun 1 15:07:27 2002 +++ postfix-1.1.11-20020613/src/smtp/smtp_proto.c Wed Jun 26 15:26:48 2002 @@ -103,6 +103,7 @@ #include #include #include +#include /* Application-specific. */ @@ -170,6 +171,8 @@ char *words; char *word; int n; + int oldfeatures; + int rval; /* * Prepare for disaster. @@ -231,7 +234,8 @@ session->namaddr, translit(resp->str, "\n", " "))); } - + if (var_smtp_always_ehlo) + state->features |= SMTP_FEATURE_ESMTP; /* * Pick up some useful features offered by the SMTP server. XXX Until we * have a portable routine to convert from string to off_t with proper @@ -243,6 +247,7 @@ * MicroSoft implemented AUTH based on an old draft. */ lines = resp->str; + oldfeatures = state->features; /* remember */ while ((words = mystrtok(&lines, "\n")) != 0) { if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t=")) != 0) { if (strcasecmp(word, "8BITMIME") == 0) @@ -259,6 +264,8 @@ state->size_limit = off_cvt_string(word); } } + else if (strcasecmp(word, "STARTTLS") == 0) + state->features |= SMTP_FEATURE_STARTTLS; #ifdef USE_SASL_AUTH else if (var_smtp_sasl_enable && strcasecmp(word, "AUTH") == 0) smtp_sasl_helo_auth(state, words); @@ -276,6 +283,128 @@ msg_info("server features: 0x%x size %.0f", state->features, (double) state->size_limit); +#ifdef HAS_SSL + if ((state->features & SMTP_FEATURE_STARTTLS) && + (var_smtp_tls_note_starttls_offer) && + (!(session->tls_enforce_tls || session->tls_use_tls))) + msg_info("Host offered STARTTLS: [%s]", session->host); + if ((session->tls_enforce_tls) && + !(state->features & SMTP_FEATURE_STARTTLS)) + { + /* + * We are enforced to use TLS but it is not offered, so we will give + * up on this host. We won't even try STARTTLS, because we could + * receive a "500 command unrecognized" which would bounce the + * message. We instead want to delay until STARTTLS becomes + * available. + */ + return (smtp_site_fail(state, 450, "Could not start TLS: not offered")); + } + if ((session->tls_enforce_tls) && !pfixtls_clientengine) { + /* + * We would like to start client TLS, but our own TLS-engine is + * not running. + */ + return (smtp_site_fail(state, 450, + "Could not start TLS: our TLS-engine not running")); + } + if ((state->features & SMTP_FEATURE_STARTTLS) && + ((session->tls_use_tls && pfixtls_clientengine) || + (session->tls_enforce_tls))) { + /* + * Try to use the TLS feature + */ + smtp_chat_cmd(state, "STARTTLS"); + if ((resp = smtp_chat_resp(state))->code / 100 != 2) { + state->features &= ~SMTP_FEATURE_STARTTLS; + /* + * At this point a political decision is necessary. If we + * enforce usage of tls, we have to close the connection + * now. + */ + if (session->tls_enforce_tls) + return (smtp_site_fail(state, resp->code, + "host %s refused to start TLS: %s", + session->host, + translit(resp->str, "\n", " "))); + } else { + if (rval = pfixtls_start_clienttls(session->stream, + var_smtp_starttls_tmout, + session->tls_enforce_peername, + session->host, + &(session->tls_info))) + return (smtp_site_fail(state, 450, + "Could not start TLS: client failure")); + + + /* + * Now the connection is established and maybe we do have a + * validated cert with a CommonName in it. + * In enforce_peername state, the handshake would already have + * been terminated so the check here is for logging only! + */ + if (session->tls_info.peer_CN != NULL) { + if (!session->tls_info.peer_verified) { + msg_info("Peer certficate could not be verified"); + if (session->tls_enforce_tls) { + pfixtls_stop_clienttls(session->stream, + var_smtp_starttls_tmout, 1, + &(session->tls_info)); + return(smtp_site_fail(state, 450, "TLS-failure: Could not verify certificate")); + } + } + } else if (session->tls_enforce_tls) { + pfixtls_stop_clienttls(session->stream, + var_smtp_starttls_tmout, 1, + &(session->tls_info)); + return (smtp_site_fail(state, 450, "TLS-failure: Cannot verify hostname")); + } + + /* + * At this point we have to re-negotiate the "EHLO" to reget + * the feature-list + */ + state->features = oldfeatures; +#ifdef USE_SASL_AUTH + if (state->sasl_mechanism_list) { + myfree(state->sasl_mechanism_list); + state->sasl_mechanism_list = 0; + } +#endif + if (state->features & SMTP_FEATURE_ESMTP) { + smtp_chat_cmd(state, "EHLO %s", var_myhostname); + if ((resp = smtp_chat_resp(state))->code / 100 != 2) + state->features &= ~SMTP_FEATURE_ESMTP; + } + lines = resp->str; + (void) mystrtok(&lines, "\n"); + while ((words = mystrtok(&lines, "\n")) != 0) { + if (mystrtok(&words, "- ") && + (word = mystrtok(&words, " \t=")) != 0) { + if (strcasecmp(word, "8BITMIME") == 0) + state->features |= SMTP_FEATURE_8BITMIME; + else if (strcasecmp(word, "PIPELINING") == 0) + state->features |= SMTP_FEATURE_PIPELINING; + else if (strcasecmp(word, "SIZE") == 0) + state->features |= SMTP_FEATURE_SIZE; + else if (strcasecmp(word, "STARTTLS") == 0) + state->features |= SMTP_FEATURE_STARTTLS; +#ifdef USE_SASL_AUTH + else if (var_smtp_sasl_enable && + strcasecmp(word, "AUTH") == 0) + smtp_sasl_helo_auth(state, words); +#endif + } + } + /* + * Actually, at this point STARTTLS should not be offered + * anymore, so we could check for a protocol violation, but + * what should we do then? + */ + + } + } +#endif #ifdef USE_SASL_AUTH if (var_smtp_sasl_enable && (state->features & SMTP_FEATURE_AUTH)) return (smtp_sasl_helo_login(state)); diff -Pur postfix-1.1.11-20020613-orig/src/smtp/smtp_session.c postfix-1.1.11-20020613/src/smtp/smtp_session.c --- postfix-1.1.11-20020613-orig/src/smtp/smtp_session.c Mon Nov 20 19:06:05 2000 +++ postfix-1.1.11-20020613/src/smtp/smtp_session.c Wed Jun 26 15:26:48 2002 @@ -42,15 +42,42 @@ #include #include +#include +#include +#include + /* Application-specific. */ #include "smtp.h" +#ifdef HAS_SSL +/* static lists */ +static MAPS *tls_per_site; + +/* smtp_tls_list_init - initialize lists */ + +void smtp_tls_list_init(void) +{ + tls_per_site = maps_create(VAR_SMTP_TLS_PER_SITE, var_smtp_tls_per_site, + DICT_FLAG_LOCK); +} +#endif + /* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */ -SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, char *host, char *addr) +SMTP_SESSION *smtp_session_alloc(char *dest, VSTREAM *stream, char *host, char *addr) { SMTP_SESSION *session; + const char *lookup; + char *lookup_key; + int host_dont_use = 0; + int host_use = 0; + int host_enforce = 0; + int host_enforce_peername = 0; + int recipient_dont_use = 0; + int recipient_use = 0; + int recipient_enforce = 0; + int recipient_enforce_peername = 0; session = (SMTP_SESSION *) mymalloc(sizeof(*session)); session->stream = stream; @@ -58,6 +85,61 @@ session->addr = mystrdup(addr); session->namaddr = concatenate(host, "[", addr, "]", (char *) 0); session->best = 1; + session->tls_use_tls = session->tls_enforce_tls = 0; + session->tls_enforce_peername = 0; +#ifdef HAS_SSL + lookup_key = lowercase(mystrdup(host)); + if (lookup = maps_find(tls_per_site, lookup_key, 0)) { + if (!strcasecmp(lookup, "NONE")) + host_dont_use = 1; + else if (!strcasecmp(lookup, "MAY")) + host_use = 1; + else if (!strcasecmp(lookup, "MUST")) + host_enforce = host_enforce_peername = 1; + else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) + host_enforce = 1; + else + msg_warn("Unknown TLS state for receiving host %s: '%s', using default policy", session->host, lookup); + } + myfree(lookup_key); + lookup_key = lowercase(mystrdup(dest)); + if (lookup = maps_find(tls_per_site, dest, 0)) { + if (!strcasecmp(lookup, "NONE")) + recipient_dont_use = 1; + else if (!strcasecmp(lookup, "MAY")) + recipient_use = 1; + else if (!strcasecmp(lookup, "MUST")) + recipient_enforce = recipient_enforce_peername = 1; + else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) + recipient_enforce = 1; + else + msg_warn("Unknown TLS state for recipient domain %s: '%s', using default policy", dest, lookup); + } + myfree(lookup_key); + + if ((var_smtp_enforce_tls && !host_dont_use && !recipient_dont_use) || host_enforce || + recipient_enforce) + session->tls_enforce_tls = session->tls_use_tls = 1; + + /* + * Set up peername checking. We want to make sure that a MUST* entry in + * the tls_per_site table always has precedence. MUST always must lead to + * a peername check, MUST_NOPEERMATCH must always disable it. Only when + * no explicit setting has been found, the default will be used. + * There is the case left, that both "host" and "recipient" settings + * conflict. In this case, the "host" setting wins. + */ + if (host_enforce && host_enforce_peername) + session->tls_enforce_peername = 1; + else if (recipient_enforce && recipient_enforce_peername) + session->tls_enforce_peername = 1; + else if (var_smtp_enforce_tls && var_smtp_tls_enforce_peername) + session->tls_enforce_peername = 1; + + else if ((var_smtp_use_tls && !host_dont_use && !recipient_dont_use) || host_use || recipient_use) + session->tls_use_tls = 1; +#endif + session->tls_info = tls_info_zero; return (session); } @@ -65,6 +147,11 @@ void smtp_session_free(SMTP_SESSION *session) { +#ifdef HAS_SSL + vstream_fflush(session->stream); + pfixtls_stop_clienttls(session->stream, var_smtp_starttls_tmout, 0, + &(session->tls_info)); +#endif vstream_fclose(session->stream); myfree(session->host); myfree(session->addr); diff -Pur postfix-1.1.11-20020613-orig/src/smtp/smtp_unalias.c postfix-1.1.11-20020613/src/smtp/smtp_unalias.c --- postfix-1.1.11-20020613-orig/src/smtp/smtp_unalias.c Thu Sep 28 19:06:09 2000 +++ postfix-1.1.11-20020613/src/smtp/smtp_unalias.c Wed Jun 26 15:26:48 2002 @@ -86,7 +86,11 @@ if ((result = htable_find(cache, name)) == 0) { fqdn = vstring_alloc(10); if (dns_lookup_types(name, smtp_unalias_flags, (DNS_RR **) 0, - fqdn, (VSTRING *) 0, T_MX, T_A, 0) != DNS_OK) + fqdn, (VSTRING *) 0, T_MX, T_A, +#ifdef INET6 + T_AAAA, +#endif + 0) != DNS_OK) vstring_strcpy(fqdn, name); htable_enter(cache, name, result = vstring_export(fqdn)); } diff -Pur postfix-1.1.11-20020613-orig/src/smtpd/Makefile.in postfix-1.1.11-20020613/src/smtpd/Makefile.in --- postfix-1.1.11-20020613-orig/src/smtpd/Makefile.in Tue Jun 11 03:13:44 2002 +++ postfix-1.1.11-20020613/src/smtpd/Makefile.in Wed Jun 26 15:26:48 2002 @@ -134,6 +134,7 @@ smtpd.o: ../../include/quote_flags.h smtpd.o: ../../include/lex_822.h smtpd.o: ../../include/mail_server.h +smtpd.o: ../../include/pfixtls.h smtpd.o: smtpd_token.h smtpd.o: smtpd.h smtpd.o: smtpd_check.h @@ -162,6 +163,7 @@ smtpd_chat.o: ../../include/cleanup_user.h smtpd_chat.o: ../../include/mail_error.h smtpd_chat.o: ../../include/name_mask.h +smtpd_chat.o: ../../include/pfixtls.h smtpd_chat.o: smtpd.h smtpd_chat.o: ../../include/mail_stream.h smtpd_chat.o: smtpd_chat.h @@ -197,6 +199,7 @@ smtpd_check.o: ../../include/mail_addr_find.h smtpd_check.o: ../../include/match_parent_style.h smtpd_check.o: ../../include/strip_addr.h +smtpd_check.o: ../../include/pfixtls.h smtpd_check.o: smtpd.h smtpd_check.o: ../../include/mail_stream.h smtpd_check.o: smtpd_sasl_glue.h @@ -213,6 +216,7 @@ smtpd_peer.o: ../../include/vstream.h smtpd_peer.o: ../../include/argv.h smtpd_peer.o: ../../include/mail_stream.h +smtpd_peer.o: ../../include/pfixtls.h smtpd_sasl_glue.o: smtpd_sasl_glue.c smtpd_sasl_glue.o: ../../include/sys_defs.h smtpd_sasl_glue.o: ../../include/msg.h @@ -266,6 +270,7 @@ smtpd_state.o: ../../include/vstring.h smtpd_state.o: ../../include/argv.h smtpd_state.o: ../../include/mail_stream.h +smtpd_state.o: ../../include/pfixtls.h smtpd_state.o: smtpd_chat.h smtpd_state.o: smtpd_sasl_glue.h smtpd_token.o: smtpd_token.c @@ -275,3 +280,4 @@ smtpd_token.o: smtpd_token.h smtpd_token.o: ../../include/vstring.h smtpd_token.o: ../../include/vbuf.h +smtpd_token.o: ../../include/pfixtls.h diff -Pur postfix-1.1.11-20020613-orig/src/smtpd/smtpd.c postfix-1.1.11-20020613/src/smtpd/smtpd.c --- postfix-1.1.11-20020613-orig/src/smtpd/smtpd.c Tue May 28 19:08:56 2002 +++ postfix-1.1.11-20020613/src/smtpd/smtpd.c Wed Jun 26 15:26:48 2002 @@ -314,6 +314,7 @@ #include #include #include +#include /* Single-threaded server skeleton. */ @@ -338,6 +339,7 @@ */ int var_smtpd_rcpt_limit; int var_smtpd_tmout; +char *var_relay_ccerts; int var_smtpd_soft_erlim; int var_smtpd_hard_erlim; int var_queue_minfree; /* XXX use off_t */ @@ -385,6 +387,15 @@ char *var_smtpd_noop_cmds; char *var_smtpd_null_key; int var_smtpd_hist_thrsh; +int var_smtpd_starttls_tmout; +int var_smtpd_tls_wrappermode; +int var_smtpd_use_tls; +int var_smtpd_enforce_tls; +int var_smtpd_tls_auth_only; +int var_smtpd_tls_ask_ccert; +int var_smtpd_tls_req_ccert; +int var_smtpd_tls_ccert_vd; +int var_smtpd_tls_received_header; /* * Silly little macros. @@ -489,11 +500,21 @@ if (var_disable_vrfy_cmd == 0) smtpd_chat_reply(state, "250-VRFY"); smtpd_chat_reply(state, "250-ETRN"); +#ifdef HAS_SSL + if ((state->tls_use_tls || state->tls_enforce_tls) && (!state->tls_active)) + smtpd_chat_reply(state, "250-STARTTLS"); +#endif #ifdef USE_SASL_AUTH if (var_smtpd_sasl_enable) { +#ifdef HAS_SSL + if (!state->tls_auth_only || state->tls_active) { +#endif smtpd_chat_reply(state, "250-AUTH %s", state->sasl_mechanism_list); if (var_broken_auth_clients) smtpd_chat_reply(state, "250-AUTH=%s", state->sasl_mechanism_list); +#ifdef HAS_SSL + } +#endif } #endif smtpd_chat_reply(state, "250-%s", VERP_CMD); @@ -918,11 +939,76 @@ state->rcpt_count = 0; } +/* CN_sanitize - make sure, the CN-string is well behaved */ + +static void CN_sanitize(char *CNstring) +{ + int i; + int len; + int parencount; + + /* + * The information included in the CN (CommonName) of the peer and its + * issuer can be included into the Received: header line. The characters + * allowed as well as comment nesting are limited by RFC822. + */ + + len = strlen(CNstring); + /* + * The Received: header can only contain characters. Make sure that only + * acceptable characters are printed. Maybe we could allow more, but + * not everything makes sense inside a CommonName. + */ + for (i = 0; i < len; i++) + if (!((CNstring[i] >= 'A') && (CNstring[i] <='Z')) && + !((CNstring[i] >= 'a') && (CNstring[i] <='z')) && + !((CNstring[i] >= '0') && (CNstring[i] <='9')) && + (CNstring[i] != '(') && (CNstring[i] != ')') && + (CNstring[i] != '[') && (CNstring[i] != ']') && + (CNstring[i] != '{') && (CNstring[i] != '}') && + (CNstring[i] != '<') && (CNstring[i] != '>') && + (CNstring[i] != '?') && (CNstring[i] != '!') && + (CNstring[i] != ';') && (CNstring[i] != ':') && + (CNstring[i] != '"') && (CNstring[i] != '\'') && + (CNstring[i] != '/') && (CNstring[i] != '|') && + (CNstring[i] != '+') && (CNstring[i] != '&') && + (CNstring[i] != '~') && (CNstring[i] != '@') && + (CNstring[i] != '#') && (CNstring[i] != '$') && + (CNstring[i] != '%') && (CNstring[i] != '&') && + (CNstring[i] != '^') && (CNstring[i] != '*') && + (CNstring[i] != '_') && (CNstring[i] != '-') && + (CNstring[i] != '.') && (CNstring[i] != ' ')) + CNstring[i] = '?'; + + /* + * This information will go into the Received: header inside a comment. + * Since comments can be nested, parentheses '(' and ')' must match. + */ + parencount = 0; + for (i = 0; i < len; i++) { + if (CNstring[i] == '(') + parencount++; + else if (CNstring[i] == ')') + parencount--; + } + /* + * The necessary condition is violated. Do YOU know, where to correct? + * I don't know, so I will practically remove all parentheses. + */ + if (parencount != 0) { + for (i = 0; i < len; i++) + if ((CNstring[i] == '(') || (CNstring[i] == ')')) + CNstring[i] = '/'; + } +} + /* data_cmd - process DATA command */ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) { char *start; + char *peer_CN; + char *issuer_CN; int len; int curr_rec_type; int prev_rec_type; @@ -961,6 +1047,35 @@ "Received: from %s (%s [%s])", state->helo_name ? state->helo_name : state->name, state->name, state->addr); + if (var_smtpd_tls_received_header && state->tls_active) { + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(using %s with cipher %s (%d/%d bits))", + state->tls_info.protocol, state->tls_info.cipher_name, + state->tls_info.cipher_usebits, + state->tls_info.cipher_algbits); + if (state->tls_info.peer_CN) { + peer_CN = mystrdup(state->tls_info.peer_CN); + CN_sanitize(peer_CN); + issuer_CN = mystrdup(state->tls_info.issuer_CN); + CN_sanitize(issuer_CN); + if (state->tls_info.peer_verified) + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(Client CN \"%s\", Issuer \"%s\" (verified OK))", + peer_CN, issuer_CN); + else + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(Client CN \"%s\", Issuer \"%s\" (not verified))", + peer_CN, issuer_CN); + myfree(issuer_CN); + myfree(peer_CN); + } + else if (var_smtpd_tls_ask_ccert) + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(Client did not present a certificate)"); + else + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(No client certificate requested)"); + } if (state->rcpt_count == 1 && state->recipient) { rec_fprintf(state->cleanup, REC_TYPE_NORM, "\tby %s (%s) with %s id %s", @@ -1310,6 +1425,77 @@ } } +static int starttls_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + char *err; + +#ifdef HAS_SSL + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Syntax: STARTTLS"); + return (-1); + } + if (state->tls_active != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "554 Error: TLS already active"); + return (-1); + } + if (state->tls_use_tls == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "502 Error: command not implemented"); + return (-1); + } + if (!pfixtls_serverengine) { + smtpd_chat_reply(state, "454 TLS not available due to temporary reason"); + return (0); + } + smtpd_chat_reply(state, "220 Ready to start TLS"); + vstream_fflush(state->client); + /* + * When deciding about continuing the handshake, we will stop when a + * client certificate was _required_ and none was presented or the + * verification failed. This however does only make sense when TLS is + * enforced. Otherwise we would happily perform perform the SMTP + * transaction without any STARTTLS at all! So only have the handshake + * fail when TLS is also enforced. + */ + if (pfixtls_start_servertls(state->client, var_smtpd_starttls_tmout, + state->name, state->addr, &(state->tls_info), + (var_smtpd_tls_req_ccert && state->tls_enforce_tls))) { + /* + * Typically the connection is hanging at this point, so + * we should try to shut it down by force! Unfortunately this + * problem is not addressed in postfix! + */ + return (-1); + } + state->tls_active = 1; + helo_reset(state); + mail_reset(state); + rcpt_reset(state); + return (0); +#else + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "502 Error: command not implemented"); + return (-1); +#endif +} + +static void tls_reset(SMTPD_STATE *state) +{ + int failure = 0; + + if (state->reason && state->where && strcmp(state->where, SMTPD_AFTER_DOT)) + failure = 1; +#ifdef HAS_SSL + vstream_fflush(state->client); + if (state->tls_active) + pfixtls_stop_servertls(state->client, var_smtpd_starttls_tmout, + failure, &(state->tls_info)); +#endif + state->tls_active = 0; +} + /* * The table of all SMTP commands that we know. Set the junk limit flag on * any command that can be repeated an arbitrary number of times without @@ -1328,6 +1514,10 @@ "HELO", helo_cmd, SMTPD_CMD_FLAG_LIMIT, "EHLO", ehlo_cmd, SMTPD_CMD_FLAG_LIMIT, +#ifdef HAS_SSL + "STARTTLS", starttls_cmd, 0, +#endif + #ifdef USE_SASL_AUTH "AUTH", smtpd_sasl_auth_cmd, 0, #endif @@ -1438,9 +1628,28 @@ state->error_count++; continue; } + if (state->tls_enforce_tls && + !state->tls_active && + cmdp->action != starttls_cmd && + cmdp->action != noop_cmd && + cmdp->action != ehlo_cmd && + cmdp->action != quit_cmd) { + smtpd_chat_reply(state, + "530 Must issue a STARTTLS command first"); + state->error_count++; + continue; + } state->where = cmdp->name; - if (cmdp->action(state, argc, argv) != 0) + if (cmdp->action(state, argc, argv) != 0) { state->error_count++; + /* + * Die after TLS negotiation failure, as there is no + * stable way to recover from a possible mixture of + * TLS and SMTP protocol from the client. + */ + if (cmdp->action == starttls_cmd) + break; + } if ((cmdp->flags & SMTPD_CMD_FLAG_LIMIT) && state->junk_cmds++ > var_smtpd_junk_cmd_limit) state->error_count++; @@ -1464,6 +1673,7 @@ * Cleanup whatever information the client gave us during the SMTP * dialog. */ + tls_reset(state); helo_reset(state); #ifdef USE_SASL_AUTH if (var_smtpd_sasl_enable) @@ -1496,6 +1706,46 @@ * machines. */ smtpd_state_init(&state, stream); +#ifdef HAS_SSL + if (SMTPD_STAND_ALONE((&state))) { + state.tls_use_tls = 0; + state.tls_enforce_tls = 0; + state.tls_auth_only = 0; + } + else { + state.tls_use_tls = var_smtpd_use_tls | var_smtpd_enforce_tls; + state.tls_enforce_tls = var_smtpd_enforce_tls; + if (var_smtpd_tls_wrappermode) { + /* + * TLS has been set to wrapper mode, meaning that we run on a + * seperate port and we must switch to TLS layer before actually + * performing the SMTP protocol. This implies enforce-mode. + */ + state.tls_use_tls = state.tls_enforce_tls = 1; + if (pfixtls_start_servertls(state.client, var_smtpd_starttls_tmout, + state.name, state.addr, &state.tls_info, + var_smtpd_tls_req_ccert)) { + /* + * Typically the connection is hanging at this point, so + * we should try to shut it down by force! Unfortunately this + * problem is not addressed in postfix! + */ + return; + } + state.tls_active = 1; + } + if (var_smtpd_tls_auth_only || state.tls_enforce_tls) + state.tls_auth_only = 1; + } +#else + state.tls_use_tls = 0; + state.tls_enforce_tls = 0; + state.tls_auth_only = 0; +#endif + + /* + * Provide the SMTP service. + */ /* * See if we need to turn on verbose logging for this client. @@ -1513,10 +1763,6 @@ smtpd_chat_reply(&state, "220 %s", var_smtpd_banner); msg_info("connect from %s[%s]", state.name, state.addr); } - - /* - * Provide the SMTP service. - */ smtpd_proto(&state); /* @@ -1542,7 +1788,6 @@ static void pre_jail_init(char *unused_name, char **unused_argv) { - /* * Initialize blacklist/etc. patterns before entering the chroot jail, in * case they specify a filename pattern. @@ -1558,6 +1803,12 @@ msg_warn("%s is true, but SASL support is not compiled in", VAR_SMTPD_SASL_ENABLE); #endif + +#ifdef HAS_SSL + if (var_smtpd_use_tls || var_smtpd_enforce_tls || var_smtpd_tls_wrappermode) + pfixtls_init_serverengine(var_smtpd_tls_ccert_vd, + var_smtpd_tls_ask_ccert); +#endif } /* main - the main program */ @@ -1580,6 +1831,7 @@ VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code, 0, 0, VAR_SMTPD_JUNK_CMD, DEF_SMTPD_JUNK_CMD, &var_smtpd_junk_cmd_limit, 1, 0, VAR_SMTPD_HIST_THRSH, DEF_SMTPD_HIST_THRSH, &var_smtpd_hist_thrsh, 1, 0, + VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, 0, }; static CONFIG_TIME_TABLE time_table[] = { @@ -1595,6 +1847,13 @@ VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route, VAR_SMTPD_SASL_ENABLE, DEF_SMTPD_SASL_ENABLE, &var_smtpd_sasl_enable, VAR_BROKEN_AUTH_CLNTS, DEF_BROKEN_AUTH_CLNTS, &var_broken_auth_clients, + VAR_SMTPD_TLS_WRAPPER, DEF_SMTPD_TLS_WRAPPER, &var_smtpd_tls_wrappermode, + VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls, + VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls, + VAR_SMTPD_TLS_AUTH_ONLY, DEF_SMTPD_TLS_AUTH_ONLY, &var_smtpd_tls_auth_only, + VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert, + VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert, + VAR_SMTPD_TLS_RECHEAD, DEF_SMTPD_TLS_RECHEAD, &var_smtpd_tls_received_header, 0, }; static CONFIG_STR_TABLE str_table[] = { @@ -1623,6 +1882,7 @@ VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0, VAR_SMTPD_NOOP_CMDS, DEF_SMTPD_NOOP_CMDS, &var_smtpd_noop_cmds, 0, 0, VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key, 0, 0, + VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_relay_ccerts, 0, 0, 0, }; @@ -1638,3 +1898,4 @@ MAIL_SERVER_PRE_ACCEPT, pre_accept, 0); } + diff -Pur postfix-1.1.11-20020613-orig/src/smtpd/smtpd.h postfix-1.1.11-20020613/src/smtpd/smtpd.h --- postfix-1.1.11-20020613-orig/src/smtpd/smtpd.h Fri Mar 29 22:10:13 2002 +++ postfix-1.1.11-20020613/src/smtpd/smtpd.h Wed Jun 26 15:26:48 2002 @@ -32,6 +32,7 @@ * Global library. */ #include +#include /* * Variables that keep track of conversation state. There is only one SMTP @@ -81,6 +82,11 @@ VSTRING *sasl_decoded; #endif int warn_if_reject; + int tls_active; + int tls_use_tls; + int tls_enforce_tls; + int tls_auth_only; + tls_info_t tls_info; } SMTPD_STATE; extern void smtpd_state_init(SMTPD_STATE *, VSTREAM *); diff -Pur postfix-1.1.11-20020613-orig/src/smtpd/smtpd_check.c postfix-1.1.11-20020613/src/smtpd/smtpd_check.c --- postfix-1.1.11-20020613-orig/src/smtpd/smtpd_check.c Mon Jun 10 22:14:39 2002 +++ postfix-1.1.11-20020613/src/smtpd/smtpd_check.c Wed Jun 26 15:26:48 2002 @@ -280,6 +280,7 @@ #include #include +#include #include #include #include @@ -345,6 +346,9 @@ static DOMAIN_LIST *relay_domains; static NAMADR_LIST *mynetworks; static NAMADR_LIST *perm_mx_networks; +#ifdef HAS_SSL +static MAPS *relay_ccerts; +#endif /* * How to do parent domain wildcard matching, if any. @@ -530,6 +534,10 @@ perm_mx_networks = namadr_list_init(match_parent_style(VAR_PERM_MX_NETWORKS), var_perm_mx_networks); +#ifdef HAS_SSL + relay_ccerts = maps_create(VAR_RELAY_CCERTS, var_relay_ccerts, + DICT_FLAG_LOCK); +#endif /* * Pre-parse and pre-open the recipient maps. @@ -932,7 +940,11 @@ msg_info("%s: %s", myname, name); dns_status = dns_lookup_types(name, 0, (DNS_RR **) 0, (VSTRING *) 0, - (VSTRING *) 0, T_A, T_MX, 0); + (VSTRING *) 0, T_A, T_MX, +#ifdef INET6 + T_AAAA, +#endif + 0); if (dns_status != DNS_OK) return (smtpd_check_reject(state, MAIL_ERROR_POLICY, "%d <%s>: %s rejected: Host not found", @@ -954,7 +966,11 @@ msg_info("%s: %s", myname, name); dns_status = dns_lookup_types(name, 0, (DNS_RR **) 0, (VSTRING *) 0, - (VSTRING *) 0, T_A, T_MX, 0); + (VSTRING *) 0, T_A, T_MX, +#ifdef INET6 + T_AAAA, +#endif + 0); if (dns_status != DNS_OK) return (smtpd_check_reject(state, MAIL_ERROR_POLICY, "%d <%s>: %s rejected: Domain not found", @@ -966,6 +982,36 @@ static int permit_auth_destination(SMTPD_STATE *state, char *recipient); +/* permit_tls_clientcerts - OK/DUNNO for message relaying */ + +#ifdef HAS_SSL +static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs) +{ + char *low_name; + const char *found; + + if (state->tls_info.peer_verified && permit_all_certs) { + if (msg_verbose) + msg_info("Relaying allowed for all verified client certificates"); + return(SMTPD_CHECK_OK); + } + + if (state->tls_info.peer_verified && state->tls_info.peer_fingerprint) { + low_name = lowercase(mystrdup(state->tls_info.peer_fingerprint)); + found = maps_find(relay_ccerts, low_name, DICT_FLAG_FIXED); + myfree(low_name); + if (found) { + if (msg_verbose) + msg_info("Relaying allowed for certified client: %s", found); + return (SMTPD_CHECK_OK); + } else if (msg_verbose) + msg_info("relay_clientcerts: No match for fingerprint '%s'", + state->tls_info.peer_fingerprint); + } + return (SMTPD_CHECK_DUNNO); +} +#endif + /* check_relay_domains - OK/FAIL for message relaying */ static int check_relay_domains(SMTPD_STATE *state, char *recipient, @@ -1145,6 +1191,49 @@ static int has_my_addr(const char *host) { +#ifdef INET6 + char *myname = "has_my_addr"; + struct addrinfo hints, *res, *res0; + int error; + char hbuf[NI_MAXHOST]; + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * If we can't lookup the host, play safe and assume it is OK. + */ +#define YUP 1 +#define NOPE 0 + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo(host, NULL, &hints, &res0); + if (error) { + if (msg_verbose) + msg_info("%s: host %s: %s", myname, host, gai_strerror(error)); + return (YUP); + } + for (res = res0; res; res = res->ai_next) { + if (msg_verbose) { + if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf), + NULL, 0, NI_NUMERICHOST)) { + strncpy(hbuf, "???", sizeof(hbuf)); + } + msg_info("%s: addr %s", myname, hbuf); + } + if (own_inet_addr(res->ai_addr)) { + freeaddrinfo(res0); + return (YUP); + } + } + freeaddrinfo(res0); + if (msg_verbose) + msg_info("%s: host %s: no match", myname, host); + + return (NOPE); +#else char *myname = "has_my_addr"; struct in_addr addr; char **cpp; @@ -1180,6 +1269,7 @@ msg_info("%s: host %s: no match", myname, host); return (NOPE); +#endif } /* i_am_mx - is this machine listed as MX relay */ @@ -1834,7 +1924,7 @@ static int reject_maps_rbl(SMTPD_STATE *state) { char *myname = "reject_maps_rbl"; - ARGV *octets = argv_split(state->addr, "."); + ARGV *octets; VSTRING *query = vstring_alloc(100); char *saved_domains = mystrdup(var_maps_rbl_domains); char *bp = saved_domains; @@ -1846,17 +1936,29 @@ int dns_status = DNS_FAIL; int i; int result; + struct in_addr a; VSTRING *why; if (msg_verbose) msg_info("%s: %s", myname, state->addr); - /* - * IPv4 only for now - */ -#ifdef INET6 +#ifndef INET6 + /* IPv4 only for now */ if (inet_pton(AF_INET, state->addr, &a) != 1) return SMTPD_CHECK_DUNNO; + octets = argv_split(state->addr, "."); +#else + /* IPv4 and IPv6-mapped IPv4 only for now */ + if (inet_pton(AF_INET, state->addr, &a) == 1) + octets = argv_split(state->addr, "."); + else { + struct in6_addr a6; + if (inet_pton(AF_INET6, state->addr, &a6) != 1) + return SMTPD_CHECK_DUNNO; + if (!IN6_IS_ADDR_V4MAPPED(&a6) || (strrchr(state->addr,':') == NULL)) + return SMTPD_CHECK_DUNNO; + octets = argv_split(strrchr(state->addr,':')+1, "."); + } #endif /* @@ -2154,6 +2256,12 @@ #else msg_warn("restriction `%s' ignored: no SASL support", name); #endif +#ifdef HAS_SSL + } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 1); + } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 0); +#endif } else if (strcasecmp(name, REJECT_UNKNOWN_RCPTDOM) == 0) { if (state->recipient) status = reject_unknown_address(state, state->recipient, @@ -2588,6 +2696,7 @@ char *var_rcpt_checks = ""; char *var_etrn_checks = ""; char *var_relay_domains = ""; +char *var_relay_ccerts = ""; char *var_mynetworks = ""; char *var_notify_classes = ""; diff -Pur postfix-1.1.11-20020613-orig/src/smtpd/smtpd_peer.c postfix-1.1.11-20020613/src/smtpd/smtpd_peer.c --- postfix-1.1.11-20020613-orig/src/smtpd/smtpd_peer.c Thu Jul 5 22:09:47 2001 +++ postfix-1.1.11-20020613/src/smtpd/smtpd_peer.c Wed Jun 26 15:26:48 2002 @@ -63,6 +63,15 @@ #include #include +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + /* * Older systems don't have h_errno. Even modern systems don't have * hstrerror(). @@ -84,16 +93,11 @@ ) #endif -/* Utility library. */ - -#include -#include -#include -#include - -/* Global library. */ - - +#ifdef INET6 +#define GAI_STRERROR(error) \ + ((error = EAI_SYSTEM) ? gai_strerror(error) : strerror(errno)) +#endif + /* Application-specific. */ #include "smtpd.h" @@ -102,16 +106,23 @@ void smtpd_peer_init(SMTPD_STATE *state) { - struct sockaddr_in sin; - SOCKADDR_SIZE len = sizeof(sin); +#ifdef INET6 + struct sockaddr_storage ss; +#else + struct sockaddr ss; + struct in_addr *in; struct hostent *hp; - int i; +#endif + struct sockaddr *sa; + SOCKADDR_SIZE len; + + sa = (struct sockaddr *)&ss; + len = sizeof(ss); /* * Look up the peer address information. */ - if (getpeername(vstream_fileno(state->client), - (struct sockaddr *) & sin, &len) >= 0) { + if (getpeername(vstream_fileno(state->client), sa, &len) >= 0) { errno = 0; } @@ -127,18 +138,51 @@ /* * Look up and "verify" the client hostname. */ - else if (errno == 0 && sin.sin_family == AF_INET) { - state->addr = mystrdup(inet_ntoa(sin.sin_addr)); - hp = gethostbyaddr((char *) &(sin.sin_addr), - sizeof(sin.sin_addr), AF_INET); - if (hp == 0) { + else if (errno == 0 && (sa->sa_family == AF_INET +#ifdef INET6 + || sa->sa_family == AF_INET6 +#endif + )) { +#ifdef INET6 + char hbuf[NI_MAXHOST]; + char abuf[NI_MAXHOST]; + struct addrinfo hints, *rnull = NULL; +#else + char abuf[sizeof("255.255.255.255") + 1]; + char *hbuf; +#endif + int error = -1; + +#ifdef INET6 + (void)getnameinfo(sa, len, abuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); +#else + in = &((struct sockaddr_in *)sa)->sin_addr; + inet_ntop(AF_INET, in, abuf, sizeof(hbuf)); +#endif + state->addr = mystrdup(abuf); +#ifdef INET6 + error = getnameinfo(sa, len, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD); +#else + hbuf = NULL; + hp = gethostbyaddr((char *)in, sizeof(*in), AF_INET); + if (hp) { + error = 0; + hbuf = mystrdup(hp->h_name); + } else + error = 1; +#endif + if (error) { state->name = mystrdup("unknown"); +#ifdef INET6 + state->peer_code = (error == EAI_AGAIN ? 4 : 5); +#else state->peer_code = (h_errno == TRY_AGAIN ? 4 : 5); - } else if (!valid_hostname(hp->h_name, DONT_GRIPE)) { +#endif + } else if (!valid_hostname(hbuf, DONT_GRIPE)) { state->name = mystrdup("unknown"); state->peer_code = 5; } else { - state->name = mystrdup(hp->h_name); /* hp->name is clobbered!! */ + state->name = mystrdup(hbuf); /* hp->name is clobbered!! */ state->peer_code = 2; /* @@ -150,16 +194,31 @@ state->peer_code = code; \ } +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(state->name, NULL, &hints, &rnull); + if (error) { + msg_warn("%s: hostname %s verification failed: %s", + state->addr, state->name, GAI_STRERROR(error)); + REJECT_PEER_NAME(state, (error == EAI_AGAIN ? 4 : 5)); + } + /* memcmp() isn't needed if we use getaddrinfo */ + if (rnull) + freeaddrinfo(rnull); +#else hp = gethostbyname(state->name); /* clobbers hp->name!! */ if (hp == 0) { msg_warn("%s: hostname %s verification failed: %s", state->addr, state->name, HSTRERROR(h_errno)); REJECT_PEER_NAME(state, (h_errno == TRY_AGAIN ? 4 : 5)); - } else if (hp->h_length != sizeof(sin.sin_addr)) { + } else if (hp->h_length != sizeof(*in)) { msg_warn("%s: hostname %s verification failed: bad address size %d", state->addr, state->name, hp->h_length); REJECT_PEER_NAME(state, 5); } else { + int i; for (i = 0; /* void */ ; i++) { if (hp->h_addr_list[i] == 0) { msg_warn("%s: address not listed for hostname %s", @@ -167,12 +226,11 @@ REJECT_PEER_NAME(state, 5); break; } - if (memcmp(hp->h_addr_list[i], - (char *) &sin.sin_addr, - sizeof(sin.sin_addr)) == 0) + if (memcmp(hp->h_addr_list[i], (char *)in, sizeof(*in)) == 0) break; /* keep peer name */ } } +#endif } } diff -Pur postfix-1.1.11-20020613-orig/src/smtpd/smtpd_sasl_proto.c postfix-1.1.11-20020613/src/smtpd/smtpd_sasl_proto.c --- postfix-1.1.11-20020613-orig/src/smtpd/smtpd_sasl_proto.c Tue Sep 12 00:45:40 2000 +++ postfix-1.1.11-20020613/src/smtpd/smtpd_sasl_proto.c Wed Jun 26 15:26:48 2002 @@ -128,6 +128,13 @@ smtpd_chat_reply(state, "503 Error: authentication not enabled"); return (-1); } +#ifdef HAS_SSL + if (state->tls_auth_only && !state->tls_active) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "538 Encryption required for requested authentication mechanism"); + return (-1); + } +#endif if (state->sasl_username) { state->error_mask |= MAIL_ERROR_PROTOCOL; smtpd_chat_reply(state, "503 Error: already authenticated"); diff -Pur postfix-1.1.11-20020613-orig/src/smtpd/smtpd_state.c postfix-1.1.11-20020613/src/smtpd/smtpd_state.c --- postfix-1.1.11-20020613-orig/src/smtpd/smtpd_state.c Tue Nov 6 18:35:40 2001 +++ postfix-1.1.11-20020613/src/smtpd/smtpd_state.c Wed Jun 26 15:26:48 2002 @@ -92,6 +92,11 @@ state->msg_size = 0; state->junk_cmds = 0; state->warn_if_reject = 0; + state->tls_active = 0; + state->tls_use_tls = 0; + state->tls_enforce_tls = 0; + state->tls_info = tls_info_zero; + state->tls_auth_only = 0; #ifdef USE_SASL_AUTH if (SMTPD_STAND_ALONE(state)) diff -Pur postfix-1.1.11-20020613-orig/src/smtpstone/smtp-sink.c postfix-1.1.11-20020613/src/smtpstone/smtp-sink.c --- postfix-1.1.11-20020613-orig/src/smtpstone/smtp-sink.c Sat Jun 8 20:21:41 2002 +++ postfix-1.1.11-20020613/src/smtpstone/smtp-sink.c Wed Jun 26 15:26:48 2002 @@ -518,7 +518,7 @@ } else { if (strncmp(argv[optind], "inet:", 5) == 0) argv[optind] += 5; - sock = inet_listen(argv[optind], backlog, BLOCKING); + sock = inet_listen(argv[optind], backlog, BLOCKING, 1); } /* diff -Pur postfix-1.1.11-20020613-orig/src/tlsmgr/Makefile.in postfix-1.1.11-20020613/src/tlsmgr/Makefile.in --- postfix-1.1.11-20020613-orig/src/tlsmgr/Makefile.in Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/tlsmgr/Makefile.in Wed Jun 26 15:26:48 2002 @@ -0,0 +1,75 @@ +SHELL = /bin/sh +SRCS = tlsmgr.c +OBJS = tlsmgr.o +HDRS = +TESTSRC = +WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ + -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ + -Wunused +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = tlsmgr +INC_DIR = ../../include +LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libutil.a + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +Makefile: Makefile.in + (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs; cat $?) >$@ + +test: $(TESTPROG) + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../../include" `cd ../..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' -e 'p' -e '}'; \ + done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @make -f Makefile.in Makefile + +# do not edit below this line - it is generated by 'make depend' +tlsmgr.o: tlsmgr.c +tlsmgr.o: ../../include/sys_defs.h +tlsmgr.o: ../../include/msg.h +tlsmgr.o: ../../include/events.h +tlsmgr.o: ../../include/vstream.h +tlsmgr.o: ../../include/vbuf.h +tlsmgr.o: ../../include/dict.h +tlsmgr.o: ../../include/argv.h +tlsmgr.o: ../../include/vstring.h +tlsmgr.o: ../../include/stringops.h +tlsmgr.o: ../../include/mymalloc.h +tlsmgr.o: ../../include/connect.h +tlsmgr.o: ../../include/myflock.h +tlsmgr.o: ../../include/mail_conf.h +tlsmgr.o: ../../include/mail_params.h +tlsmgr.o: ../../include/iostuff.h +tlsmgr.o: ../../include/master_proto.h +tlsmgr.o: ../../include/mail_server.h +tlsmgr.o: ../../include/pfixtls.h diff -Pur postfix-1.1.11-20020613-orig/src/tlsmgr/tlsmgr.c postfix-1.1.11-20020613/src/tlsmgr/tlsmgr.c --- postfix-1.1.11-20020613-orig/src/tlsmgr/tlsmgr.c Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/tlsmgr/tlsmgr.c Wed Jun 26 15:26:48 2002 @@ -0,0 +1,598 @@ +/*++ +/* NAME +/* tlsmgr 8 +/* SUMMARY +/* Postfix TLS session cache and PRNG handling manager +/* SYNOPSIS +/* \fBtlsmgr\fR [generic Postfix daemon options] +/* DESCRIPTION +/* 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]. +/* STANDARDS +/* SECURITY +/* .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. +/* DIAGNOSTICS +/* Problems and transactions are logged to the syslog daemon. +/* BUGS +/* There is no automatic means to limit the number of entries in the +/* session caches and/or the size of the session cache files. +/* CONFIGURATION PARAMETERS +/* .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). +/* SEE ALSO +/* smtp(8) SMTP client +/* smtpd(8) SMTP server +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include /* gettimeofday, not POSIX */ + +/* OpenSSL library. */ +#ifdef HAS_SSL +#include /* For the PRNG */ +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Master process interface */ + +#include +#include + +/* Application-specific. */ + + /* + * Tunables. + */ +char *var_tls_rand_source; +int var_tls_rand_bytes; +int var_tls_reseed_period; +int var_tls_prng_upd_period; + +static int rand_exch_fd; +static int rand_source_dev_fd = -1; +static int rand_source_socket_fd = -1; +static int srvr_scache_db_active; +static int clnt_scache_db_active; +static DICT *srvr_scache_db = NULL; +static DICT *clnt_scache_db = NULL; + +static void tlsmgr_prng_upd_event(int unused_event, char *dummy) +{ + struct timeval tv; + unsigned char buffer[1024]; + int next_period; + +#ifdef HAS_SSL + /* + * It is time to update the PRNG exchange file. Since other processes might + * have added entropy, we do this in a read_stir-back_write cycle. + */ + GETTIMEOFDAY(&tv); + RAND_seed(&tv, sizeof(struct timeval)); + + if (myflock(rand_exch_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) != 0) + msg_fatal("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)); + + /* + * Make prediction difficult for outsiders and calculate the time for the + * next execution randomly. + */ + next_period = (var_tls_prng_upd_period * buffer[0]) / 255; + event_request_timer(tlsmgr_prng_upd_event, dummy, next_period); +#endif +} + + +static void tlsmgr_reseed_event(int unused_event, char *dummy) +{ + int egd_success; + int next_period; + int rand_bytes; + char buffer[255]; + struct timeval tv; + unsigned char randbyte; + +#ifdef HAS_SSL + /* + * It is time to reseed the PRNG. + */ + + GETTIMEOFDAY(&tv); + RAND_seed(&tv, sizeof(struct timeval)); + if (rand_source_dev_fd != -1) { + rand_bytes = read(rand_source_dev_fd, buffer, var_tls_rand_bytes); + if (rand_bytes > 0) + RAND_seed(buffer, rand_bytes); + else if (rand_bytes < 0) { + msg_fatal("Read from entropy device %s failed", + var_tls_rand_source); + } + } else if (rand_source_socket_fd != -1) { + egd_success = 0; + buffer[0] = 1; + buffer[1] = var_tls_rand_bytes; + if (write(rand_source_socket_fd, buffer, 2) != 2) + msg_info("Could not talk to %s", var_tls_rand_source); + else if (read(rand_source_socket_fd, buffer, 1) != 1) + msg_info("Could not read info from %s", var_tls_rand_source); + else { + rand_bytes = buffer[0]; + if (read(rand_source_socket_fd, buffer, rand_bytes) != rand_bytes) + msg_info("Could not read data from %s", var_tls_rand_source); + else { + egd_success = 1; + RAND_seed(buffer, rand_bytes); + } + } + if (!egd_success) { + msg_info("Lost connection to EGD-device, exiting to reconnect."); + exit(0); + } + } else if (*var_tls_rand_source) { + rand_bytes = RAND_load_file(var_tls_rand_source, var_tls_rand_bytes); + } + + /* + * Make prediction difficult for outsiders and calculate the time for the + * next execution randomly. + */ + RAND_bytes(&randbyte, 1); + next_period = (var_tls_reseed_period * randbyte) / 255; + event_request_timer(tlsmgr_reseed_event, dummy, next_period); +#endif +} + + +static int tlsmgr_do_scache_check(DICT *scache_db, int scache_timeout, + int start) +{ +#ifdef HAS_SSL + int func; + int len; + int n; + int delete = 0; + int result; + struct timeval tv; + const char *member; + const char *value; + char *member_copy; + unsigned char nibble, *data; + pfixtls_scache_info_t scache_info; + + GETTIMEOFDAY(&tv); + RAND_seed(&tv, sizeof(struct timeval)); + + /* + * Run through the given dictionary and check the stored sessions. + * If "start" is set to 1, a new run is initiated, otherwise the next + * item is accessed. The state is internally kept in the DICT. + */ + if (start) + func = DICT_SEQ_FUN_FIRST; + else + func = DICT_SEQ_FUN_NEXT; + result = dict_seq(scache_db, func, &member, &value); + + if (result > 0) + return 0; /* End of list reached */ + else if (result < 0) + msg_fatal("Database fault, should already be caught."); + else { + member_copy = mystrdup(member); + len = strlen(value); + RAND_seed(value, len); /* Use it to increase entropy */ + if (len < 2 * sizeof(pfixtls_scache_info_t)) + delete = 1; /* Messed up, delete */ + else if (len > 2 * sizeof(pfixtls_scache_info_t)) + len = 2 * sizeof(pfixtls_scache_info_t); + if (!delete) { + data = (unsigned char *)(&scache_info); + memset(data, 0, len / 2); + for (n = 0; n < len; n++) { + if ((value[n] >= '0') && (value[n] <= '9')) + nibble = value[n] - '0'; + else + nibble = value[n] - 'A' + 10; + if (n % 2) + data[n / 2] |= nibble; + else + data[n / 2] |= (nibble << 4); + } + + if ((scache_info.scache_db_version != scache_db_version) || + (scache_info.openssl_version != openssl_version) || + (scache_info.timestamp + scache_timeout < time(NULL))) + delete = 1; + } + if (delete) + result = dict_del(scache_db, member_copy); + myfree(member_copy); + } + + if (delete && result) + msg_info("Could not delete %s", member); + return 1; + +#else + return 0; +#endif +} + +static void tlsmgr_clnt_cache_run_event(int unused_event, char *dummy) +{ + + /* + * This routine runs when it is time for another tls session cache scan. + * Make sure this routine gets called again in the future. + */ + clnt_scache_db_active = tlsmgr_do_scache_check(clnt_scache_db, + var_smtp_tls_scache_timeout, 1); + event_request_timer(tlsmgr_clnt_cache_run_event, dummy, + var_smtp_tls_scache_timeout); +} + + +static void tlsmgr_srvr_cache_run_event(int unused_event, char *dummy) +{ + + /* + * This routine runs when it is time for another tls session cache scan. + * Make sure this routine gets called again in the future. + */ + srvr_scache_db_active = tlsmgr_do_scache_check(srvr_scache_db, + var_smtpd_tls_scache_timeout, 1); + event_request_timer(tlsmgr_srvr_cache_run_event, dummy, + var_smtpd_tls_scache_timeout); +} + + +static DICT *tlsmgr_cache_open(const char *dbname) +{ + DICT *retval; + char *dbpagname; + char *dbdirname; + + /* + * First, try to find out the real name of the database file, so that + * it can be removed. + */ + if (!strncmp(dbname, "sdbm:", 5)) { + dbpagname = concatenate(dbname + 5, ".pag", NULL); + REMOVE(dbpagname); + myfree(dbpagname); + dbdirname = concatenate(dbname + 5, ".dir", NULL); + REMOVE(dbdirname); + myfree(dbdirname); + } + else { + msg_warn("Only type sdbm: supported: %s", dbname); + return NULL; + } + + /* + * Now open the dictionary. Do it with O_EXCL, so that we only open a + * fresh file. If we cannot open it with a fresh file, then we won't + * touch it. + */ + retval = dict_open(dbname, O_RDWR | O_CREAT | O_EXCL, + DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE); + if (!retval) + msg_warn("Could not create dictionary %s", dbname); + return retval; +} + +/* tlsmgr_trigger_event - respond to external trigger(s) */ + +static void tlsmgr_trigger_event(char *buf, int len, + char *unused_service, char **argv) +{ + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + +} + +/* tlsmgr_loop - queue manager main loop */ + +static int tlsmgr_loop(char *unused_name, char **unused_argv) +{ + /* + * This routine runs as part of the event handling loop, after the event + * manager has delivered a timer or I/O event (including the completion + * of a connection to a delivery process), or after it has waited for a + * specified amount of time. The result value of qmgr_loop() specifies + * how long the event manager should wait for the next event. + */ +#define DONT_WAIT 0 +#define WAIT_FOR_EVENT (-1) + + if (clnt_scache_db_active) + clnt_scache_db_active = tlsmgr_do_scache_check(clnt_scache_db, + var_smtp_tls_scache_timeout, 0); + if (srvr_scache_db_active) + srvr_scache_db_active = tlsmgr_do_scache_check(srvr_scache_db, + var_smtpd_tls_scache_timeout, 0); + if (clnt_scache_db_active || srvr_scache_db_active) + return (DONT_WAIT); + return (WAIT_FOR_EVENT); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + if (dict_changed()) { + msg_info("table has changed -- exiting"); + exit(0); + } +} + +/* tlsmgr_pre_init - pre-jail initialization */ + +static void tlsmgr_pre_init(char *unused_name, char **unused_argv) +{ + int rand_bytes; + unsigned char buffer[255]; + +#ifdef HAS_SSL + /* + * Access the external sources for random seed. We may not be able to + * access them again if we are sent to chroot jail, so we must leave + * dev: and egd: type sources open. + */ + if (*var_tls_rand_source) { + if (!strncmp(var_tls_rand_source, "dev:", 4)) { + /* + * Source is a random device + */ + rand_source_dev_fd = open(var_tls_rand_source + 4, 0, 0); + if (rand_source_dev_fd == -1) + msg_fatal("Could not open entropy device %s", + var_tls_rand_source); + if (var_tls_rand_bytes > 255) + var_tls_rand_bytes = 255; + rand_bytes = read(rand_source_dev_fd, buffer, var_tls_rand_bytes); + RAND_seed(buffer, rand_bytes); + } else if (!strncmp(var_tls_rand_source, "egd:", 4)) { + /* + * Source is a EGD compatible socket + */ + rand_source_socket_fd = unix_connect(var_tls_rand_source +4, + BLOCKING, 10); + if (rand_source_socket_fd == -1) + msg_fatal("Could not connect to %s", var_tls_rand_source); + if (var_tls_rand_bytes > 255) + var_tls_rand_bytes = 255; + buffer[0] = 1; + buffer[1] = var_tls_rand_bytes; + if (write(rand_source_socket_fd, buffer, 2) != 2) + msg_fatal("Could not talk to %s", var_tls_rand_source); + if (read(rand_source_socket_fd, buffer, 1) != 1) + msg_fatal("Could not read info from %s", var_tls_rand_source); + rand_bytes = buffer[0]; + if (read(rand_source_socket_fd, buffer, rand_bytes) != rand_bytes) + msg_fatal("Could not read data from %s", var_tls_rand_source); + RAND_seed(buffer, rand_bytes); + } else { + rand_bytes = RAND_load_file(var_tls_rand_source, + var_tls_rand_bytes); + } + } +#endif + + /* + * Now open the PRNG exchange file + */ + if (*var_tls_rand_exch_name) { + rand_exch_fd = open(var_tls_rand_exch_name, O_RDWR | O_CREAT, 0600); + } + + /* + * Finally, open the session cache files. Remove old files, if still there. + * If we could not remove the old files, something is pretty wrong and we + * won't touch it!! + */ + if (*var_smtp_tls_scache_db) + clnt_scache_db = tlsmgr_cache_open(var_smtp_tls_scache_db); + if (*var_smtpd_tls_scache_db) + srvr_scache_db = tlsmgr_cache_open(var_smtpd_tls_scache_db); +} + +/* qmgr_post_init - post-jail initialization */ + +static void tlsmgr_post_init(char *unused_name, char **unused_argv) +{ + unsigned char buffer[1024]; + + /* + * This routine runs after the skeleton code has entered the chroot jail. + * Prevent automatic process suicide after a limited number of client + * requests or after a limited amount of idle time. + */ + var_use_limit = 0; + var_idle_limit = 0; + +#ifdef HAS_SSL + /* + * Complete thie initialization by reading the additional seed from the + * PRNG exchange file. Don't care how many bytes were actually read, just + * seed buffer into the PRNG, regardless of its contents. + */ + if (rand_exch_fd >= 0) { + if (myflock(rand_exch_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) == -1) + msg_fatal("Could not lock random exchange file: %s", + strerror(errno)); + read(rand_exch_fd, buffer, 1024); + if (myflock(rand_exch_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) == -1) + msg_fatal("Could not unlock random exchange file: %s", + strerror(errno)); + RAND_seed(buffer, 1024); + tlsmgr_prng_upd_event(0, (char *) 0); + tlsmgr_reseed_event(0, (char *) 0); + } +#endif + + clnt_scache_db_active = 0; + srvr_scache_db_active = 0; + if (clnt_scache_db) + tlsmgr_clnt_cache_run_event(0, (char *) 0); + if (srvr_scache_db) + tlsmgr_srvr_cache_run_event(0, (char *) 0); +} + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static CONFIG_STR_TABLE str_table[] = { + VAR_TLS_RAND_SOURCE, DEF_TLS_RAND_SOURCE, &var_tls_rand_source, 0, 0, + 0, + }; + static CONFIG_TIME_TABLE time_table[] = { + VAR_TLS_RESEED_PERIOD, DEF_TLS_RESEED_PERIOD, &var_tls_reseed_period, 0, 0, + VAR_TLS_PRNG_UPD_PERIOD, DEF_TLS_PRNG_UPD_PERIOD, &var_tls_prng_upd_period, 0, 0, + 0, + }; + static CONFIG_INT_TABLE int_table[] = { + VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 0, 0, + 0, + }; + + /* + * Use the trigger service skeleton, because no-one else should be + * monitoring our service port while this process runs, and because we do + * not talk back to the client. + */ + trigger_server_main(argc, argv, tlsmgr_trigger_event, + MAIL_SERVER_TIME_TABLE, time_table, + MAIL_SERVER_INT_TABLE, int_table, + MAIL_SERVER_STR_TABLE, str_table, + MAIL_SERVER_PRE_INIT, tlsmgr_pre_init, + MAIL_SERVER_POST_INIT, tlsmgr_post_init, + MAIL_SERVER_LOOP, tlsmgr_loop, + MAIL_SERVER_PRE_ACCEPT, pre_accept, + 0); +} diff -Pur postfix-1.1.11-20020613-orig/src/util/Makefile.in postfix-1.1.11-20020613/src/util/Makefile.in --- postfix-1.1.11-20020613-orig/src/util/Makefile.in Tue Jun 11 03:12:45 2002 +++ postfix-1.1.11-20020613/src/util/Makefile.in Wed Jun 26 15:26:48 2002 @@ -8,7 +8,7 @@ dict_tcp.c dict_unix.c dir_forest.c doze.c duplex_pipe.c \ environ.c events.c exec_command.c fifo_listen.c fifo_trigger.c \ file_limit.c find_inet.c fsspace.c fullname.c get_domainname.c \ - get_hostname.c hex_quote.c htable.c inet_addr_host.c \ + get_hostname.c get_port.c hex_quote.c htable.c inet_addr_host.c \ inet_addr_list.c inet_addr_local.c inet_connect.c inet_listen.c \ inet_trigger.c inet_util.c intv.c line_wrap.c lowercase.c \ lstat_as.c mac_expand.c mac_parse.c make_dirs.c match_list.c \ @@ -26,7 +26,7 @@ unix_connect.c unix_listen.c unix_trigger.c unsafe.c username.c \ valid_hostname.c vbuf.c vbuf_print.c vstream.c vstream_popen.c \ vstring.c vstring_vstream.c watchdog.c writable.c write_buf.c \ - write_wait.c strcasecmp.c nvtable.c + write_wait.c strcasecmp.c nvtable.c dict_sdbm.c sdbm.c OBJS = alldig.o argv.o argv_split.o attr_print0.o attr_print64.o \ attr_scan0.o attr_scan64.o base64_code.o basename.o binhash.o \ chroot_uid.o clean_env.o close_on_exec.o concatenate.o ctable.o \ @@ -36,7 +36,7 @@ dict_tcp.o dict_unix.o dir_forest.o doze.o duplex_pipe.o \ environ.o events.o exec_command.o fifo_listen.o fifo_trigger.o \ file_limit.o find_inet.o fsspace.o fullname.o get_domainname.o \ - get_hostname.o hex_quote.o htable.o inet_addr_host.o \ + get_hostname.o get_port.o hex_quote.o htable.o inet_addr_host.o \ inet_addr_list.o inet_addr_local.o inet_connect.o inet_listen.o \ inet_trigger.o inet_util.o intv.o line_wrap.o lowercase.o \ lstat_as.o mac_expand.o mac_parse.o make_dirs.o match_list.o \ @@ -54,13 +54,13 @@ unix_connect.o unix_listen.o unix_trigger.o unsafe.o username.o \ valid_hostname.o vbuf.o vbuf_print.o vstream.o vstream_popen.o \ vstring.o vstring_vstream.o watchdog.o writable.o write_buf.o \ - write_wait.o nvtable.o $(STRCASE) + write_wait.o nvtable.o $(STRCASE) dict_sdbm.o sdbm.o HDRS = argv.h attr.h base64_code.h binhash.h chroot_uid.h clean_env.h \ connect.h ctable.h dict.h dict_db.h dict_dbm.h dict_env.h \ dict_ht.h dict_ldap.h dict_mysql.h dict_ni.h dict_nis.h \ dict_nisplus.h dict_pcre.h dict_regexp.h dict_static.h dict_tcp.h \ dict_unix.h dir_forest.h events.h exec_command.h find_inet.h \ - fsspace.h fullname.h get_domainname.h get_hostname.h hex_quote.h \ + fsspace.h fullname.h get_domainname.h get_hostname.h get_port.h hex_quote.h \ htable.h inet_addr_host.h inet_addr_list.h inet_addr_local.h \ inet_util.h intv.h iostuff.h line_wrap.h listen.h lstat_as.h \ mac_expand.h mac_parse.h make_dirs.h match_list.h match_ops.h \ @@ -72,7 +72,7 @@ split_at.h stat_as.h stringops.h sys_defs.h timed_connect.h \ timed_wait.h trigger.h username.h valid_hostname.h vbuf.h \ vbuf_print.h vstream.h vstring.h vstring_vstream.h watchdog.h \ - nvtable.h + nvtable.h dict_sdbm.h sdbm.h TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ stream_test.c dup2_pass_on_exec.c WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ @@ -591,6 +591,7 @@ dict_open.o: dict_unix.h dict_open.o: dict_tcp.h dict_open.o: dict_dbm.h +dict_open.o: dict_sdbm.h dict_open.o: dict_db.h dict_open.o: dict_nis.h dict_open.o: dict_nisplus.h @@ -725,6 +726,7 @@ get_domainname.o: mymalloc.h get_domainname.o: get_hostname.h get_domainname.o: get_domainname.h +get_port.o: sys_defs.h get_hostname.o: get_hostname.c get_hostname.o: sys_defs.h get_hostname.o: mymalloc.h @@ -841,6 +843,7 @@ match_list.o: stringops.h match_list.o: argv.h match_list.o: dict.h +match_list.o: inet_util.h match_list.o: match_ops.h match_list.o: match_list.h match_ops.o: match_ops.c @@ -1225,3 +1228,9 @@ write_wait.o: sys_defs.h write_wait.o: msg.h write_wait.o: iostuff.h +sdbm.o: sdbm.c +sdbm.o: sdbm.h +dict_sdbm.o: sdbm.h +dict_sdbm.o: dict_sdbm.c +dict_sdbm.o: dict_sdbm.h +dict_sdbm.o: sys_defs.h diff -Pur postfix-1.1.11-20020613-orig/src/util/dict_open.c postfix-1.1.11-20020613/src/util/dict_open.c --- postfix-1.1.11-20020613-orig/src/util/dict_open.c Fri Dec 21 23:18:07 2001 +++ postfix-1.1.11-20020613/src/util/dict_open.c Wed Jun 26 15:26:48 2002 @@ -159,6 +159,7 @@ #include #include #include +#include #include #include #include @@ -187,6 +188,7 @@ #if 0 DICT_TYPE_TCP, dict_tcp_open, #endif + "sdbm", dict_sdbm_open, #ifdef HAS_DBM DICT_TYPE_DBM, dict_dbm_open, #endif diff -Pur postfix-1.1.11-20020613-orig/src/util/dict_sdbm.c postfix-1.1.11-20020613/src/util/dict_sdbm.c --- postfix-1.1.11-20020613-orig/src/util/dict_sdbm.c Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/util/dict_sdbm.c Wed Jun 26 15:26:48 2002 @@ -0,0 +1,408 @@ +/*++ +/* NAME +/* dict_sdbm 3 +/* SUMMARY +/* dictionary manager interface to SDBM files +/* SYNOPSIS +/* #include +/* +/* DICT *dict_sdbm_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_sdbm_open() opens the named SDBM database and makes it available +/* via the generic interface described in dict_open(3). +/* DIAGNOSTICS +/* Fatal errors: cannot open file, file write error, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* sdbm(3) data base subroutines +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include "sys_defs.h" + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "htable.h" +#include "iostuff.h" +#include "vstring.h" +#include "myflock.h" +#include "stringops.h" +#include "dict.h" +#include "dict_sdbm.h" +#include "sdbm.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + SDBM *dbm; /* open database */ + char *path; /* pathname */ +} DICT_SDBM; + +/* dict_sdbm_lookup - find database entry */ + +static const char *dict_sdbm_lookup(DICT *dict, const char *name) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + static VSTRING *buf; + const char *result = 0; + + dict_errno = 0; + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->path); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = dbm_value.dptr; + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + if (buf == 0) + buf = vstring_alloc(10); + vstring_strncpy(buf, dbm_value.dptr, dbm_value.dsize); + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = vstring_str(buf); + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->path); + + return (result); +} + +/* dict_sdbm_update - add or update database entry */ + +static void dict_sdbm_update(DICT *dict, const char *name, const char *value) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dbm_key.dptr = (void *) name; + dbm_value.dptr = (void *) value; + dbm_key.dsize = strlen(name); + dbm_value.dsize = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef DBM_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dsize++; + dbm_value.dsize++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->path); + + /* + * Do the update. + */ + if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) + msg_fatal("error writing SDBM database %s: %m", dict_sdbm->path); + if (status) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->path, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->path, name); + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->path); +} + + +/* dict_sdbm_delete - delete one entry from the dictionary */ + +static int dict_sdbm_delete(DICT *dict, const char *name) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + int status = 1; + int flags = 0; + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->path); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + sdbm_clearerr(dict_sdbm->dbm); + if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { + if (sdbm_error(dict_sdbm->dbm) != 0) /* fatal error */ + msg_fatal("error deleting from %s: %m", dict_sdbm->path); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + sdbm_clearerr(dict_sdbm->dbm); + if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { + if (sdbm_error(dict_sdbm->dbm) != 0) /* fatal error */ + msg_fatal("error deleting from %s: %m", dict_sdbm->path); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->path); + + return (status); +} + +/* traverse the dictionary */ + +static int dict_sdbm_sequence(DICT *dict, const int function, + const char **key, const char **value) +{ + char *myname = "dict_sdbm_sequence"; + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + int status = 0; + static VSTRING *key_buf; + static VSTRING *value_buf; + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->path); + + /* + * Determine and execute the seek function. It returns the key. + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + dbm_key = sdbm_firstkey(dict_sdbm->dbm); + break; + case DICT_SEQ_FUN_NEXT: + dbm_key = sdbm_nextkey(dict_sdbm->dbm); + break; + default: + msg_panic("%s: invalid function: %d", myname, function); + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->path); + + if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { + + /* + * See if this DB file was written with one null byte appended to key + * an d value or not. If necessary, copy the key. + */ + if (((char *) dbm_key.dptr)[dbm_key.dsize - 1] == 0) { + *key = dbm_key.dptr; + } else { + if (key_buf == 0) + key_buf = vstring_alloc(10); + vstring_strncpy(key_buf, dbm_key.dptr, dbm_key.dsize); + *key = vstring_str(key_buf); + } + + /* + * Fetch the corresponding value. + */ + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + + if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { + + /* + * See if this DB file was written with one null byte appended to + * key and value or not. If necessary, copy the key. + */ + if (((char *) dbm_value.dptr)[dbm_value.dsize - 1] == 0) { + *value = dbm_value.dptr; + } else { + if (value_buf == 0) + value_buf = vstring_alloc(10); + vstring_strncpy(value_buf, dbm_value.dptr, dbm_value.dsize); + *value = vstring_str(value_buf); + } + } else { + + /* + * Determine if we have hit the last record or an error + * condition. + */ + if (sdbm_error(dict_sdbm->dbm)) + msg_fatal("error seeking %s: %m", dict_sdbm->path); + return (1); /* no error: eof/not found + * (should not happen!) */ + } + } else { + + /* + * Determine if we have hit the last record or an error condition. + */ + if (sdbm_error(dict_sdbm->dbm)) + msg_fatal("error seeking %s: %m", dict_sdbm->path); + return (1); /* no error: eof/not found */ + } + return (0); +} + +/* dict_sdbm_close - disassociate from data base */ + +static void dict_sdbm_close(DICT *dict) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + + sdbm_close(dict_sdbm->dbm); + myfree(dict_sdbm->path); + myfree((char *) dict_sdbm); +} + +/* dict_sdbm_open - open SDBM data base */ + +DICT *dict_sdbm_open(const char *path, int open_flags, int dict_flags) +{ + DICT_SDBM *dict_sdbm; + struct stat st; + SDBM *dbm; + char *dbm_path; + int lock_fd; + + if (dict_flags & DICT_FLAG_LOCK) { + dbm_path = concatenate(path, ".pag", (char *) 0); + if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) + msg_fatal("open database %s: %m", dbm_path); + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("shared-lock database %s for open: %m", dbm_path); + } + + /* + * XXX SunOS 5.x has no const in dbm_open() prototype. + */ + if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0) + msg_fatal("open database %s.{dir,pag}: %m", path); + + if (dict_flags & DICT_FLAG_LOCK) { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("unlock database %s for open: %m", dbm_path); + if (close(lock_fd) < 0) + msg_fatal("close database %s: %m", dbm_path); + myfree(dbm_path); + } + dict_sdbm = (DICT_SDBM *) mymalloc(sizeof(*dict_sdbm)); + dict_sdbm->dict.lookup = dict_sdbm_lookup; + dict_sdbm->dict.update = dict_sdbm_update; + dict_sdbm->dict.delete = dict_sdbm_delete; + dict_sdbm->dict.sequence = dict_sdbm_sequence; + dict_sdbm->dict.close = dict_sdbm_close; + dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm); + dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm); + if (fstat(dict_sdbm->dict.stat_fd, &st) < 0) + msg_fatal("dict_sdbm_open: fstat: %m"); + dict_sdbm->dict.mtime = st.st_mtime; + close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC); + close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC); + dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) + dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); + dict_sdbm->dbm = dbm; + dict_sdbm->path = mystrdup(path); + + return (&dict_sdbm->dict); +} diff -Pur postfix-1.1.11-20020613-orig/src/util/dict_sdbm.h postfix-1.1.11-20020613/src/util/dict_sdbm.h --- postfix-1.1.11-20020613-orig/src/util/dict_sdbm.h Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/util/dict_sdbm.h Wed Jun 26 15:26:48 2002 @@ -0,0 +1,35 @@ +#ifndef _DICT_SDBM_H_INCLUDED_ +#define _DICT_SDBM_H_INCLUDED_ + +/*++ +/* NAME +/* dict_dbm 3h +/* SUMMARY +/* dictionary manager interface to DBM files +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern DICT *dict_sdbm_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff -Pur postfix-1.1.11-20020613-orig/src/util/get_port.c postfix-1.1.11-20020613/src/util/get_port.c --- postfix-1.1.11-20020613-orig/src/util/get_port.c Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/util/get_port.c Wed Jun 26 15:26:49 2002 @@ -0,0 +1,65 @@ +/*++ +/* NAME +/* get_port 3 +/* SUMMARY +/* trivial host and port extracter +/* SYNOPSIS +/* #include +/* +/* char *get_port(data) +/* char *data; +/* +/* DESCRIPTION +/* get_port() extract host name or ip address from +/* strings such as [3ffe:902:12::10]:25, [::1] +/* or 192.168.0.1:25, and null-terminates the +/* \fIdata\fR at the first occurrence of port separator. +/* DIAGNOSTICS +/* If port not found return null pointer. +/* LICENSE +/* .ad +/* .fi +/* BSD Style (or BSD like) license. +/* AUTHOR(S) +/* Arkadiusz Mi¶kiewicz +/* Wroclaw, POLAND +/*--*/ + +/* System libraries */ + +#include +#include + +/* Utility library. */ + +#include "get_port.h" + +/* get_port - extract port number from string */ + +char *get_port(char *data) +{ + const char *escl=strchr(data,'['); + const char *sepl=strchr(data,':'); + char *escr=strrchr(data,']'); + char *sepr=strrchr(data,':'); + + /* extract from "[address]:port" or "[address]"*/ + if (escl && escr) + { + memmove(data, data + 1, strlen(data) - strlen(escr)); + data[strlen(data) - strlen(escr) - 1] = 0; + *escr++ = 0; + if (*escr == ':') + escr++; + return (*escr ? escr : NULL); + } + /* extract from "address:port" or "address" */ + if ((sepl == sepr) && sepr && sepl) + { + *sepr++ = 0; + return sepr; + } + + /* return empty string */ + return NULL; +} diff -Pur postfix-1.1.11-20020613-orig/src/util/get_port.h postfix-1.1.11-20020613/src/util/get_port.h --- postfix-1.1.11-20020613-orig/src/util/get_port.h Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/util/get_port.h Wed Jun 26 15:26:49 2002 @@ -0,0 +1,28 @@ +#ifndef _GET_PORT_H_INCLUDED_ +#define _GET_PORT_H_INCLUDED_ + +/*++ +/* NAME +/* get_port 3h +/* SUMMARY +/* trivial host and port extracter +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern char *get_port(char *); + + +/* LICENSE +/* .ad +/* .fi +/* BSD Style (or BSD like) license. +/* AUTHOR(S) +/* Arkadiusz Mi¶kiewicz +/* Wroclaw, POLAND +/*--*/ + +#endif diff -Pur postfix-1.1.11-20020613-orig/src/util/inet_addr_host.c postfix-1.1.11-20020613/src/util/inet_addr_host.c --- postfix-1.1.11-20020613-orig/src/util/inet_addr_host.c Fri Dec 11 19:55:35 1998 +++ postfix-1.1.11-20020613/src/util/inet_addr_host.c Wed Jun 26 15:26:49 2002 @@ -38,7 +38,10 @@ #include #include #include +#include #include +#include +#include #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff @@ -48,15 +51,47 @@ #include #include +#ifdef TEST +#include +#endif /* inet_addr_host - look up address list for host */ int inet_addr_host(INET_ADDR_LIST *addr_list, const char *hostname) { +#ifdef INET6 + int s; + struct addrinfo hints, *res0, *res; +#ifdef TEST + char buforhosta[1024]; +#endif + int error; +#else struct hostent *hp; struct in_addr addr; +#endif int initial_count = addr_list->used; +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo(hostname, NULL, &hints, &res0); + if (error == 0) { + for (res = res0; res; res = res->ai_next) { + if(res->ai_family != AF_INET && res->ai_family != AF_INET6) + continue; + /* filter out address families that are not supported */ + s = socket(res->ai_family, SOCK_DGRAM, 0); + if (s < 0) + continue; + close(s); + + inet_addr_list_append(addr_list, res->ai_addr); + } + freeaddrinfo(res0); + } +#else if ((addr.s_addr = inet_addr(hostname)) != INADDR_NONE) { inet_addr_list_append(addr_list, &addr); } else { @@ -65,9 +100,12 @@ inet_addr_list_append(addr_list, (struct in_addr *) * hp->h_addr_list++); } +#endif + return (addr_list->used - initial_count); } + #ifdef TEST #include @@ -78,6 +116,8 @@ { INET_ADDR_LIST addr_list; int i; + struct sockaddr *sa; + char hbuf[NI_MAXHOST]; msg_vstream_init(argv[0], VSTREAM_ERR); @@ -89,8 +129,12 @@ if (inet_addr_host(&addr_list, *argv) == 0) msg_fatal("not found: %s", *argv); - for (i = 0; i < addr_list.used; i++) - vstream_printf("%s\n", inet_ntoa(addr_list.addrs[i])); + for (i = 0; i < addr_list.used; i++) { + sa = (struct sockaddr *)&addr_list.addrs[i]; + getnameinfo(sa, SA_LEN(sa), hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST); + vstream_printf("%s\n", hbuf); + } vstream_fflush(VSTREAM_OUT); } inet_addr_list_free(&addr_list); diff -Pur postfix-1.1.11-20020613-orig/src/util/inet_addr_list.c postfix-1.1.11-20020613/src/util/inet_addr_list.c --- postfix-1.1.11-20020613-orig/src/util/inet_addr_list.c Tue Jul 31 20:13:41 2001 +++ postfix-1.1.11-20020613/src/util/inet_addr_list.c Wed Jun 26 15:26:49 2002 @@ -51,6 +51,13 @@ #include #include +#include + +#ifdef INET6 +#include +#include +#endif + /* Utility library. */ #include @@ -63,12 +70,39 @@ { list->used = 0; list->size = 2; +#ifdef INET6 + list->addrs = (struct sockaddr_storage *) +#else list->addrs = (struct in_addr *) +#endif mymalloc(sizeof(*list->addrs) * list->size); } /* inet_addr_list_append - append address to internet address list */ +#ifdef INET6 +void inet_addr_list_append(INET_ADDR_LIST *list, + struct sockaddr * addr) +{ + char *myname = "inet_addr_list_append"; + char hbuf[NI_MAXHOST]; + + if (msg_verbose > 1) { + if (getnameinfo(addr, SA_LEN(addr), hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST)) { + strncpy(hbuf, "??????", sizeof(hbuf)); + } + msg_info("%s: %s", myname, hbuf); + } + + if (list->used >= list->size) + list->size *= 2; + list->addrs = (struct sockaddr_storage *) + myrealloc((char *) list->addrs, + sizeof(*list->addrs) * list->size); + memcpy(&list->addrs[list->used++], addr, SA_LEN(addr)); +} +#else void inet_addr_list_append(INET_ADDR_LIST *list, struct in_addr * addr) { char *myname = "inet_addr_list_append"; @@ -83,15 +117,22 @@ sizeof(*list->addrs) * list->size); list->addrs[list->used++] = *addr; } +#endif /* inet_addr_list_comp - compare addresses */ static int inet_addr_list_comp(const void *a, const void *b) { +#ifdef INET6 + if(((struct sockaddr*)a)->sa_family != ((struct sockaddr*)b)->sa_family) + return ( ((struct sockaddr*)a)->sa_family - ((struct sockaddr*)b)->sa_family ); + return memcmp(a,b,SA_LEN((struct sockaddr*)a)); +#else const struct in_addr *a_addr = (const struct in_addr *) a; const struct in_addr *b_addr = (const struct in_addr *) b; return (a_addr->s_addr - b_addr->s_addr); +#endif } /* inet_addr_list_uniq - weed out duplicates */ diff -Pur postfix-1.1.11-20020613-orig/src/util/inet_addr_list.h postfix-1.1.11-20020613/src/util/inet_addr_list.h --- postfix-1.1.11-20020613-orig/src/util/inet_addr_list.h Tue Jul 31 19:56:47 2001 +++ postfix-1.1.11-20020613/src/util/inet_addr_list.h Wed Jun 26 15:26:49 2002 @@ -16,19 +16,42 @@ */ #include +#ifndef SA_LEN +# ifndef HAS_SA_LEN +# define SA_LEN(x) (((x)->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) +# define SS_LEN(x) (((x).ss_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) +# else +# define SA_LEN(x) ((x)->sa_len) +# define SS_LEN(x) ((x).ss_len) +# endif +#else +# ifndef SS_LEN +# define SS_LEN(x) (((x).ss_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) +# endif +#endif + /* * External interface. */ typedef struct INET_ADDR_LIST { int used; /* nr of elements in use */ int size; /* actual list size */ +#ifdef INET6 + struct sockaddr_storage *addrs; /* payload */ +#else struct in_addr *addrs; /* payload */ +#endif } INET_ADDR_LIST; extern void inet_addr_list_init(INET_ADDR_LIST *); extern void inet_addr_list_free(INET_ADDR_LIST *); extern void inet_addr_list_uniq(INET_ADDR_LIST *); +#ifdef INET6 +struct sockaddr; +extern void inet_addr_list_append(INET_ADDR_LIST *, struct sockaddr *); +#else extern void inet_addr_list_append(INET_ADDR_LIST *, struct in_addr *); +#endif /* LICENSE /* .ad diff -Pur postfix-1.1.11-20020613-orig/src/util/inet_addr_local.c postfix-1.1.11-20020613/src/util/inet_addr_local.c --- postfix-1.1.11-20020613-orig/src/util/inet_addr_local.c Sun Feb 25 19:20:19 2001 +++ postfix-1.1.11-20020613/src/util/inet_addr_local.c Wed Jun 26 15:26:49 2002 @@ -47,6 +47,13 @@ #endif #include #include +#if defined(INET6) && (defined (LINUX) || defined (LINUX2)) +#include +#include +#endif +#ifdef HAVE_GETIFADDRS +#include +#endif /* Utility library. */ @@ -78,18 +85,104 @@ int inet_addr_local(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list) { +#ifdef HAVE_GETIFADDRS + char *myname = "inet_addr_local"; + struct ifaddrs *ifap, *ifa; + int initial_count = addr_list->used; + struct sockaddr *sa, *sam; +#ifdef INET6 +#ifdef __KAME__ + struct sockaddr_in6 addr6; +#endif +#else + void *addr,*addrm; +#endif + + if (getifaddrs(&ifap) < 0) + msg_fatal("%s: getifaddrs: %m", myname); + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (! (ifa->ifa_flags & IFF_RUNNING) || ifa->ifa_addr==NULL) + continue; + sa = ifa->ifa_addr; + sam = ifa->ifa_netmask; + switch (ifa->ifa_addr->sa_family) { + case AF_INET: +#ifndef INET6 + addr = (void *)&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; + addrm = (void *)&((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr; +#endif + break; +#ifdef INET6 + case AF_INET6: +#ifdef __KAME__ + memcpy(&addr6, ifa->ifa_addr, ifa->ifa_addr->sa_len); + /* decode scoped address notation */ + if ((IN6_IS_ADDR_LINKLOCAL(&addr6.sin6_addr) || + IN6_IS_ADDR_SITELOCAL(&addr6.sin6_addr)) && + addr6.sin6_scope_id == 0) { + addr6.sin6_scope_id = ntohs(addr6.sin6_addr.s6_addr[3] | + (unsigned int)addr6.sin6_addr.s6_addr[2] << 8); + addr6.sin6_addr.s6_addr[2] = addr6.sin6_addr.s6_addr[3] = 0; + sa = (struct sockaddr *)&addr6; + } +#endif + break; +#endif + default: + continue; + } + +#ifdef INET6 + inet_addr_list_append(addr_list, sa); + if (mask_list != NULL) + inet_addr_list_append(mask_list, sam); +#else + inet_addr_list_append(addr_list, (struct in_addr *)addr); + if (mask_list != NULL) + inet_addr_list_append(mask_list, (struct in_addr *)addrm); +#endif + } + + freeifaddrs(ifap); + return (addr_list->used - initial_count); +#else char *myname = "inet_addr_local"; struct ifconf ifc; struct ifreq *ifr; struct ifreq *the_end; int sock; - VSTRING *buf = vstring_alloc(1024); + VSTRING *buf; int initial_count = addr_list->used; struct in_addr addr; struct ifreq *ifr_mask; + int af = AF_INET; +#ifdef INET6 +#if defined (LINUX) || defined (LINUX2) +#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" + FILE *f; + char addr6p[8][5], addr6res[40], devname[20]; + int plen, scope, dad_status, if_idx, gaierror; + struct addrinfo hints, *res, *res0; +#endif + struct sockaddr_in6 addr6; - if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) +other_socket_type: +#endif + buf = vstring_alloc(1024); + + if ((sock = socket(af, SOCK_DGRAM, 0)) < 0) { +#ifdef INET6 + if (af == AF_INET6) + { + if (msg_verbose) + msg_warn("%s: socket: %m", myname); + goto end; + } + else +#endif msg_fatal("%s: socket: %m", myname); + } /* * Get the network interface list. XXX The socket API appears to have no @@ -126,10 +219,15 @@ */ the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len); for (ifr = ifc.ifc_req; ifr < the_end;) { - if (ifr->ifr_addr.sa_family == AF_INET) { /* IP interface */ + if ((ifr->ifr_addr.sa_family == AF_INET) && + (ifr->ifr_addr.sa_family == af)) { /* IP interface */ addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr; if (addr.s_addr != INADDR_ANY) { /* has IP address */ +#ifdef INET6 + inet_addr_list_append(addr_list, &ifr->ifr_addr); +#else inet_addr_list_append(addr_list, &addr); +#endif if (mask_list) { ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr)); memcpy((char *) ifr_mask, (char *) ifr, IFREQ_SIZE(ifr)); @@ -141,11 +239,61 @@ } } } +#ifdef INET6 + else if ((ifr->ifr_addr.sa_family == AF_INET6) && + (ifr->ifr_addr.sa_family == af)) { /* IPv6 interface */ + addr6 = *((struct sockaddr_in6 *) & ifr->ifr_addr); +#ifdef __KAME__ + /* decode scoped address notation */ + if ((IN6_IS_ADDR_LINKLOCAL(&addr6.sin6_addr) || + IN6_IS_ADDR_SITELOCAL(&addr6.sin6_addr)) && + addr6.sin6_scope_id == 0) { + addr6.sin6_scope_id = ntohs(addr6.sin6_addr.s6_addr[3] | + (unsigned int)addr6.sin6_addr.s6_addr[2] << 8); + addr6.sin6_addr.s6_addr[2] = addr6.sin6_addr.s6_addr[3] = 0; + } +#endif + if (!(IN6_IS_ADDR_UNSPECIFIED(&addr6.sin6_addr))) + inet_addr_list_append(addr_list, (struct sockaddr *)&addr6); + } +#endif ifr = NEXT_INTERFACE(ifr); } vstring_free(buf); (void) close(sock); +#ifdef INET6 +end: + if (af != AF_INET6) { + af = AF_INET6; + goto other_socket_type; + } +#if defined (LINUX) || defined (LINUX2) + if ((f = fopen(_PATH_PROCNET_IFINET6, "r")) != NULL) { + while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], addr6p[4], + addr6p[5], addr6p[6], addr6p[7], + &if_idx, &plen, &scope, &dad_status, devname) != EOF) { + sprintf(addr6res, "%s:%s:%s:%s:%s:%s:%s:%s", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], + addr6p[4], addr6p[5], addr6p[6], addr6p[7]); + addr6res[sizeof(addr6res) - 1] = 0; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + gaierror = getaddrinfo(addr6res, NULL, &hints, &res0); + if (!gaierror) { + for (res = res0; res; res = res->ai_next) { + inet_addr_list_append(addr_list, res->ai_addr); + } + freeaddrinfo(res0); + } + } + } +#endif /* linux */ +#endif return (addr_list->used - initial_count); +#endif } #ifdef TEST @@ -158,6 +306,8 @@ INET_ADDR_LIST addr_list; INET_ADDR_LIST mask_list; int i; + char abuf[NI_MAXHOST], mbuf[NI_MAXHOST]; + struct sockaddr *sa; msg_vstream_init(argv[0], VSTREAM_ERR); @@ -172,8 +322,17 @@ msg_warn("found only one active network interface"); for (i = 0; i < addr_list.used; i++) { - vstream_printf("%s/", inet_ntoa(addr_list.addrs[i])); - vstream_printf("%s\n", inet_ntoa(mask_list.addrs[i])); + sa = (struct sockaddr *)&addr_list.addrs[i]; + if (getnameinfo(sa, SA_LEN(sa), abuf, sizeof(abuf), NULL, 0, + NI_NUMERICHOST)) { + strncpy(abuf, "???", sizeof(abuf)); + } + sa = (struct sockaddr *)&mask_list.addrs[i]; + if (getnameinfo(sa, SA_LEN(sa), mbuf, sizeof(mbuf), NULL, 0, + NI_NUMERICHOST)) { + strncpy(mbuf, "???", sizeof(mbuf)); + } + vstream_printf("%s/%s\n", abuf, mbuf); } vstream_fflush(VSTREAM_OUT); inet_addr_list_free(&addr_list); diff -Pur postfix-1.1.11-20020613-orig/src/util/inet_connect.c postfix-1.1.11-20020613/src/util/inet_connect.c --- postfix-1.1.11-20020613-orig/src/util/inet_connect.c Mon Nov 20 19:06:31 2000 +++ postfix-1.1.11-20020613/src/util/inet_connect.c Wed Jun 26 15:26:49 2002 @@ -55,6 +55,9 @@ #include #include #include +#ifdef INET6 +#include +#endif /* Utility library. */ @@ -73,7 +76,12 @@ char *buf; char *host; char *port; +#ifdef INET6 + struct addrinfo hints, *res, *res0; + int error; +#else struct sockaddr_in sin; +#endif int sock; /* @@ -81,14 +89,58 @@ * the local host. */ buf = inet_parse(addr, &host, &port); +#ifdef INET6 + if (*host == 0) + host = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST; /* find_inet_addr is numeric only */ + if (getaddrinfo(host, port, &hints, &res0)) + msg_fatal("host not found: %s", host); +#else if (*host == 0) host = "localhost"; memset((char *) &sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = find_inet_addr(host); sin.sin_port = find_inet_port(port, "tcp"); +#endif myfree(buf); +#ifdef INET6 + sock = -1; + for (res = res0; res; res = res->ai_next) { + if ((res->ai_family != AF_INET) && (res->ai_family != AF_INET6)) + continue; + + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock < 0) + continue; + if (timeout > 0) { + non_blocking(sock, NON_BLOCKING); + if (timed_connect(sock, res->ai_addr, res->ai_addrlen, timeout) < 0) { + close(sock); + sock = -1; + continue; + } + if (block_mode != NON_BLOCKING) + non_blocking(sock, block_mode); + break; + } else { + non_blocking(sock, block_mode); + if (connect(sock, res->ai_addr, res->ai_addrlen) < 0 + && errno != EINPROGRESS) { + close(sock); + sock = -1; + continue; + } + break; + } + } + freeaddrinfo(res0); + return sock; +#else /* * Create a client socket. */ @@ -121,4 +173,5 @@ } return (sock); } +#endif } diff -Pur postfix-1.1.11-20020613-orig/src/util/inet_listen.c postfix-1.1.11-20020613/src/util/inet_listen.c --- postfix-1.1.11-20020613-orig/src/util/inet_listen.c Mon Nov 20 19:06:32 2000 +++ postfix-1.1.11-20020613/src/util/inet_listen.c Wed Jun 26 15:26:49 2002 @@ -6,7 +6,7 @@ /* SYNOPSIS /* #include /* -/* int inet_listen(addr, backlog, block_mode) +/* int inet_listen(addr, backlog, block_mode, addinuse_fatal) /* const char *addr; /* int backlog; /* int block_mode; @@ -51,11 +51,17 @@ #include #include #include +#ifdef INET6 +#if (! __GLIBC__ >= 2 && __GLIBC_MINOR__ >=1 ) +#include +#endif +#endif #include #include #ifndef MAXHOSTNAMELEN #include #endif +#include #include #include @@ -77,35 +83,116 @@ /* inet_listen - create inet-domain listener */ -int inet_listen(const char *addr, int backlog, int block_mode) +int inet_listen(const char *addr, int backlog, int block_mode, int addrinuse_fatal) { +#ifdef INET6 + struct addrinfo *res, *res0, hints; + int error; +#else + struct ai { + int ai_family; + int ai_socktype; + int ai_protocol; + struct sockaddr *ai_addr; + SOCKADDR_SIZE ai_addrlen; + struct ai *ai_next; + } *res, *res0, resbody; struct sockaddr_in sin; +#endif int sock; int t = 1; + int addrinuse = 0; char *buf; char *host; char *port; +#ifdef INET6 + char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; +#else + char hbuf[sizeof("255.255.255.255") + 1]; + char pbuf[sizeof("255.255.255.255") + 1]; +#endif + char *cause = "unknown"; /* * Translate address information to internal form. */ buf = inet_parse(addr, &host, &port); - memset((char *) &sin, 0, sizeof(sin)); +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(*host ? host : NULL, *port ? port : "0", &hints, &res0); + if (error) { + msg_fatal("getaddrinfo: %s", gai_strerror(error)); + } + myfree(buf); +#else + memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; +#ifdef HAS_SA_LEN + sin.sin_len = sizeof(sin); +#endif sin.sin_port = find_inet_port(port, "tcp"); sin.sin_addr.s_addr = (*host ? find_inet_addr(host) : INADDR_ANY); - myfree(buf); - /* - * Create a listener socket. - */ - if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) - msg_fatal("socket: %m"); - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &t, sizeof(t)) < 0) - msg_fatal("setsockopt: %m"); - if (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) - msg_fatal("bind %s port %d: %m", sin.sin_addr.s_addr == INADDR_ANY ? - "INADDR_ANY" : inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + memset(&resbody, 0, sizeof(resbody)); + resbody.ai_socktype = SOCK_STREAM; + resbody.ai_family = AF_INET; + resbody.ai_addr = (struct sockaddr *)&sin; + resbody.ai_addrlen = sizeof(sin); + + res0 = &resbody; +#endif + + sock = -1; + for (res = res0; res; res = res->ai_next) { + if ((res->ai_family != AF_INET) && (res->ai_family != AF_INET6)) + continue; + + /* + * Create a listener socket. + */ + if ((sock = socket(res->ai_family, res->ai_socktype, 0)) < 0) { + cause = "socket"; + continue; + } +#ifdef IPV6_V6ONLY + if (res->ai_family == AF_INET6 && + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &t, sizeof(t)) < 0) { + /* if kernel/libc don't support this simple ignore it + cause = "setsockopt(IPV6_V6ONLY)"; + close(sock); + sock = -1; + continue; + */ + ; + } +#endif + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &t, sizeof(t)) < 0) { + cause = "setsockopt(SO_REUSEADDR)"; + close(sock); + sock = -1; + continue; + } + + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { + cause = "bind"; + if (errno == EADDRINUSE) + addrinuse = 1; + close(sock); + sock = -1; + continue; + } + break; + } + if (sock < 0 && (addrinuse_fatal || !addrinuse)) + msg_fatal("%s: %m", cause); +#ifdef INET6 + freeaddrinfo(res0); +#endif + if (sock < 0) + return -1; non_blocking(sock, block_mode); if (listen(sock, backlog) < 0) msg_fatal("listen: %m"); diff -Pur postfix-1.1.11-20020613-orig/src/util/listen.h postfix-1.1.11-20020613/src/util/listen.h --- postfix-1.1.11-20020613-orig/src/util/listen.h Mon Mar 22 02:57:11 1999 +++ postfix-1.1.11-20020613/src/util/listen.h Wed Jun 26 15:26:49 2002 @@ -20,7 +20,7 @@ * Listener external interface. */ extern int unix_listen(const char *, int, int); -extern int inet_listen(const char *, int, int); +extern int inet_listen(const char *, int, int, int); extern int fifo_listen(const char *, int, int); extern int stream_listen(const char *, int, int); diff -Pur postfix-1.1.11-20020613-orig/src/util/match_list.c postfix-1.1.11-20020613/src/util/match_list.c --- postfix-1.1.11-20020613-orig/src/util/match_list.c Tue Nov 20 21:07:15 2001 +++ postfix-1.1.11-20020613/src/util/match_list.c Wed Jun 26 15:26:49 2002 @@ -118,7 +118,7 @@ list = match_list_parse(list, vstring_str(buf)); if (vstream_fclose(fp)) msg_fatal("%s: read file %s: %m", myname, pattern); - } else if (strchr(pattern, ':') != 0) { /* type:table */ + } else if ((strchr(pattern, ']') == 0) && (strchr(pattern, ':') != 0)) { /* type:table */ for (cp = pattern; *cp == '!'; cp++) /* void */ ; if (dict_handle(pattern) == 0) diff -Pur postfix-1.1.11-20020613-orig/src/util/match_ops.c postfix-1.1.11-20020613/src/util/match_ops.c --- postfix-1.1.11-20020613-orig/src/util/match_ops.c Tue Nov 20 21:16:10 2001 +++ postfix-1.1.11-20020613/src/util/match_ops.c Wed Jun 26 15:26:49 2002 @@ -81,6 +81,307 @@ #include #include +#ifdef INET6 +/* + * $Id: match_ops.c,v 1.2 2000/05/22 12:01:17 misiek Exp $ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + * + * Modifications: + * Artur Frysiak + * Arkadiusz Mi¶kiewicz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef AF_DECnet +#define AF_DECnet 12 +#endif + +#ifndef PF_PACKET +#define PF_PACKET 17 +#endif + +typedef struct +{ + unsigned char family; + unsigned char bytelen; + signed short bitlen; + unsigned int data[4]; +} inet_prefix; + +/* prototypes */ +int masked_match(char *, char *, char *); +int get_integer(int *, char *, int); +int get_addr_1(inet_prefix *, char *, int); +int get_prefix_1(inet_prefix *, char *, int); +int get_addr(inet_prefix *, char *, int); +int get_prefix(inet_prefix *, char *, int); +unsigned int get_addr32(char *); +int matches(char *, char *); +int inet_addr_match(inet_prefix *, inet_prefix *, int); +int mask_match(char *, char *, char *); + +int get_integer(int *val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > INT_MAX || res < INT_MIN) + return -1; + *val = res; + return 0; +} + +int get_addr_1(inet_prefix *addr, char *name, int family) +{ + char *cp; + unsigned char *ap = (unsigned char*)addr->data; + int i; + + memset(addr, 0, sizeof(*addr)); + + if (strcmp(name, "default") == 0 || strcmp(name, "any") == 0) { + if (family == AF_DECnet) + return -1; + addr->family = family; + addr->bytelen = (family == AF_INET6 ? 16 : 4); + addr->bitlen = -1; + return 0; + } + + if (strchr(name, ':')) { + addr->family = AF_INET6; + if (family != AF_UNSPEC && family != AF_INET6) + return -1; + if (inet_pton(AF_INET6, name, addr->data) <= 0) + return -1; + addr->bytelen = 16; + addr->bitlen = -1; + return 0; + } + addr->family = AF_INET; + if (family != AF_UNSPEC && family != AF_INET) + return -1; + addr->bytelen = 4; + addr->bitlen = -1; + for (cp = name, i = 0; *cp; cp++) { + if (*cp <= '9' && *cp >= '0') { + ap[i] = 10*ap[i] + (*cp-'0'); + continue; + } + if (*cp == '.' && ++i <= 3) + continue; + return -1; + } + return 0; +} + +int get_prefix_1(inet_prefix *dst, char *arg, int family) +{ + int err; + unsigned plen; + char *slash; + + memset(dst, 0, sizeof(*dst)); + + if (strcmp(arg, "default") == 0 || strcmp(arg, "any") == 0) { + if (family == AF_DECnet) + return -1; + dst->family = family; + dst->bytelen = 0; + dst->bitlen = 0; + return 0; + } + + slash = strchr(arg, '/'); + if (slash) + *slash = 0; + err = get_addr_1(dst, arg, family); + if (err == 0) { + switch(dst->family) { + case AF_INET6: + dst->bitlen = 128; + break; + case AF_DECnet: + dst->bitlen = 16; + break; + default: + case AF_INET: + dst->bitlen = 32; + } + if (slash) { + if (get_integer(&plen, slash+1, 0) || plen > dst->bitlen) { + err = -1; + goto done; + } + dst->bitlen = plen; + } + } +done: + if (slash) + *slash = '/'; + return err; +} + +int get_addr(inet_prefix *dst, char *arg, int family) +{ +#ifdef AF_PACKET + if (family == AF_PACKET) + return -1; +#endif + if (get_addr_1(dst, arg, family)) + return -1; + return 0; +} + +int get_prefix(inet_prefix *dst, char *arg, int family) +{ +#ifdef AF_PACKET + if (family == AF_PACKET) + return -1; +#endif + if (get_prefix_1(dst, arg, family)) + return -1; + return 0; +} + +unsigned int get_addr32(char *name) +{ + inet_prefix addr; + if (get_addr_1(&addr, name, AF_INET)) + return -1; + return addr.data[0]; +} + +int matches(char *cmd, char *pattern) +{ + int len = strlen(cmd); + if (len > strlen(pattern)) + return -1; + return memcmp(pattern, cmd, len); +} + +int inet_addr_match(inet_prefix *a, inet_prefix *b, int bits) +{ + unsigned int *a1 = a->data; + unsigned int *a2 = b->data; + int words = bits >> 0x05; + + bits &= 0x1f; + + if (words) + if (memcmp(a1, a2, words << 2)) + return -1; + + if (bits) { + unsigned int w1, w2; + unsigned int mask; + + w1 = a1[words]; + w2 = a2[words]; + + mask = htonl((0xffffffff) << (0x20 - bits)); + + if ((w1 ^ w2) & mask) + return 1; + } + + return 0; +} + +/* zero if matches */ +int mask_match(char *network, char *cprefix, char *address) +{ + inet_prefix *inetwork; + inet_prefix *iaddress; + int ret, prefix; + + if (!(network && address && cprefix)) + return -1; + prefix = strtol(cprefix, (char **)NULL, 10); + if ((prefix < 0) || (prefix > 128)) + return -1; + if ((strlen(network) == 0) || (strlen(address) == 0)) + return -1; + + inetwork = malloc(sizeof(inet_prefix)); + iaddress = malloc(sizeof(inet_prefix)); + + if ((get_addr(iaddress, address, AF_UNSPEC) >= 0) + && (get_addr(inetwork, network, AF_UNSPEC) >= 0)) + ret = inet_addr_match(inetwork, iaddress, prefix); + else + ret = -1; + free(inetwork); + free(iaddress); + + /* 1 if matches */ + /* return (!ret); */ + /* 0 if matches */ + return ret; +} + +/* + * masked_match() - universal for IPv4 and IPv6 - 1 if matches + */ +int masked_match(net_tok, mask_tok, string) +char *net_tok; +char *mask_tok; +char *string; +{ +#ifdef INET6 + struct in6_addr in6[2]; + char v4addr[2][INET_ADDRSTRLEN]; + char newmask[6]; + int plen; +#endif + + /* Check for NULL */ + if (!(net_tok && mask_tok && string)) + return 0; /* doesn't match!!! */ + + /* If IPv6 mapped convert to native-IPv4 */ +#ifdef INET6 + if (inet_pton(AF_INET6, net_tok, &in6[0]) == 1 && + inet_pton(AF_INET6, string, &in6[1]) == 1 && + IN6_IS_ADDR_V4MAPPED(&in6[0]) && IN6_IS_ADDR_V4MAPPED(&in6[1])) { + plen = atoi(mask_tok); + if (32 < plen && plen < 129) { + sprintf(newmask, "%d", plen - 96); + mask_tok = newmask; + } + + (void)inet_ntop(AF_INET, &in6[0].s6_addr[12], v4addr[0], + sizeof(v4addr[0])); + net_tok = v4addr[0]; + (void)inet_ntop(AF_INET, &in6[1].s6_addr[12], v4addr[1], + sizeof(v4addr[1])); + string = v4addr[1]; + } +#endif + return (!mask_match(net_tok, mask_tok, string)); +} +#endif + /* match_string - match a string literal */ int match_string(int unused_flags, const char *string, const char *pattern) @@ -177,6 +478,7 @@ return (0); } +#ifndef INET6 /* match_parse_mask - parse net/mask pattern */ static int match_parse_mask(const char *pattern, unsigned long *net_bits, @@ -198,27 +500,55 @@ return (mask != 0); } +#endif + /* match_hostaddr - match host by address */ int match_hostaddr(int unused_flags, const char *addr, const char *pattern) { char *myname = "match_hostaddr"; +#ifdef INET6 + char *network, *mask, *escl, *escr, *patternx; + struct in6_addr in6; + char v4addr[INET_ADDRSTRLEN]; +#else int mask_shift; unsigned long mask_bits; unsigned long net_bits; unsigned long addr_bits; +#endif if (msg_verbose) msg_info("%s: %s ~? %s", myname, addr, pattern); +#ifdef INET6 + if (addr[strspn(addr, "01234567890./:abcdef")] != 0) +#else if (addr[strspn(addr, "01234567890./:")] != 0) +#endif return (0); +#ifdef INET6 + patternx = mystrdup(pattern); + escl = strchr(patternx,'['); + escr = strrchr(patternx,']'); + if (escl && escr) { + *escr = 0; + sprintf(patternx, "%s%s", escl + 1, escr + 1); + pattern = patternx; + } +#endif + /* * Try dictionary lookup. This can be case insensitive. XXX Probably * should also try again after stripping least significant octets. */ - if (strchr(pattern, ':') != 0) { +#ifdef INET6 + if (!(escl && escr) && strchr(pattern, ':') != 0) +#else + if (strchr(pattern, ':') != 0) +#endif + { if (dict_lookup(pattern, addr) != 0) return (1); if (dict_errno != 0) @@ -229,6 +559,12 @@ /* * Try an exact match with the host address. */ +#ifdef INET6 + if (inet_pton(AF_INET6, addr, &in6) == 1 && IN6_IS_ADDR_V4MAPPED(&in6)) { + (void)inet_ntop(AF_INET, &in6.s6_addr[12], v4addr, sizeof(v4addr)); + addr = v4addr; + } +#endif if (strcasecmp(addr, pattern) == 0) { return (1); } @@ -237,6 +573,20 @@ * In a net/mask pattern, the mask is specified as the number of bits of * the network part. */ +#ifdef INET6 + network = mystrdup(patternx); + mask = split_at(network, '/'); + + if (masked_match(network, mask, (char *)addr)) { + myfree(network); + myfree(patternx); + return (1); + } else { + myfree(network); + myfree(patternx); + } +#else + if (match_parse_mask(pattern, &net_bits, &mask_shift)) { addr_bits = inet_addr(addr); if (addr_bits == INADDR_NONE) @@ -244,5 +594,6 @@ mask_bits = htonl((0xffffffff) << (BITS_PER_ADDR - mask_shift)); return ((addr_bits & mask_bits) == (net_bits & mask_bits)); } +#endif return (0); } diff -Pur postfix-1.1.11-20020613-orig/src/util/sdbm.c postfix-1.1.11-20020613/src/util/sdbm.c --- postfix-1.1.11-20020613-orig/src/util/sdbm.c Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/util/sdbm.c Wed Jun 26 15:26:49 2002 @@ -0,0 +1,971 @@ +/*++ +/* NAME +/* sdbm 3h +/* SUMMARY +/* SDBM Simple DBM: ndbm work-alike hashed database library +/* SYNOPSIS +/* include "sdbm.h" +/* DESCRIPTION +/* This file includes the public domain SDBM (ndbm work-alike hashed +/* database library), based on Per-Aake Larson's Dynamic Hashing +/* algorithms. BIT 18 (1978). +/* author: oz@nexus.yorku.ca +/* status: public domain +/* The file has been patched following the advice of Uwe Ohse +/* : +/* -------------------------------------------------------------- +/* this patch fixes a problem with sdbms .dir file, which arrises when +/* a second .dir block is needed for the first time. read() returns 0 +/* in that case, and the library forgot to initialize that new block. +/* +/* A related problem is that the calculation of db->maxbno is wrong. +/* It just appends 4096*BYTESIZ bits, which is not enough except for +/* small databases (.dir basically doubles everytime it's too small). +/* -------------------------------------------------------------- +/* According to Uwe Ohse, the patch has also been submitted to the +/* author of SDBM. (The 4096*BYTESIZ bits comment may apply with a +/* different size for Postfix/TLS, as the patch was sent against the +/* original SDBM distributiona and for Postfix/TLS I have changed the +/* default sizes. +/* .nf +/*--*/ + +/* + * sdbm - ndbm work-alike hashed database library + * based on Per-Aake Larson's Dynamic Hashing algorithms. BIT 18 (1978). + * author: oz@nexus.yorku.ca + * status: public domain. + * + * core routines + */ + +#include +#include +#ifdef WIN32 +#include +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#ifdef __STDC__ +#include +#endif + +#include + +/* + * useful macros + */ +#define bad(x) ((x).dptr == NULL || (x).dsize <= 0) +#define exhash(item) sdbm_hash((item).dptr, (item).dsize) +#define ioerr(db) ((db)->flags |= DBM_IOERR) + +#define OFF_PAG(off) (long) (off) * PBLKSIZ +#define OFF_DIR(off) (long) (off) * DBLKSIZ + +static long masks[] = +{ + 000000000000, 000000000001, 000000000003, 000000000007, + 000000000017, 000000000037, 000000000077, 000000000177, + 000000000377, 000000000777, 000000001777, 000000003777, + 000000007777, 000000017777, 000000037777, 000000077777, + 000000177777, 000000377777, 000000777777, 000001777777, + 000003777777, 000007777777, 000017777777, 000037777777, + 000077777777, 000177777777, 000377777777, 000777777777, + 001777777777, 003777777777, 007777777777, 017777777777 +}; + +datum nullitem = +{NULL, 0}; + +typedef struct +{ + int dirf; /* directory file descriptor */ + int pagf; /* page file descriptor */ + int flags; /* status/error flags, see below */ + long maxbno; /* size of dirfile in bits */ + long curbit; /* current bit number */ + long hmask; /* current hash mask */ + long blkptr; /* current block for nextkey */ + int keyptr; /* current key for nextkey */ + long blkno; /* current page to read/write */ + long pagbno; /* current page in pagbuf */ + char *pagbuf; /* page file block buffer */ + long dirbno; /* current block in dirbuf */ + char *dirbuf; /* directory file block buffer */ +} DBM; + + +/* ************************* */ + +/* + * sdbm - ndbm work-alike hashed database library + * based on Per-Aake Larson's Dynamic Hashing algorithms. BIT 18 (1978). + * author: oz@nexus.yorku.ca + * status: public domain. keep it that way. + * + * hashing routine + */ + +/* + * polynomial conversion ignoring overflows + * [this seems to work remarkably well, in fact better + * then the ndbm hash function. Replace at your own risk] + * use: 65599 nice. + * 65587 even better. + */ +static long sdbm_hash (char *str, int len) +{ + unsigned long n = 0; + +#ifdef DUFF +#define HASHC n = *str++ + 65599 * n + if (len > 0) + { + int loop = (len + 8 - 1) >> 3; + + switch (len & (8 - 1)) + { + case 0: + do + { + HASHC; + case 7: + HASHC; + case 6: + HASHC; + case 5: + HASHC; + case 4: + HASHC; + case 3: + HASHC; + case 2: + HASHC; + case 1: + HASHC; + } + while (--loop); + } + + } +#else + while (len--) + n = *str++ + 65599 * n; +#endif + return n; +} + +/* + * check page sanity: + * number of entries should be something + * reasonable, and all offsets in the index should be in order. + * this could be made more rigorous. + */ +static int chkpage (char *pag) +{ + int n; + int off; + short *ino = (short *) pag; + + if ((n = ino[0]) < 0 || n > PBLKSIZ / sizeof (short)) + return 0; + + if (n > 0) + { + off = PBLKSIZ; + for (ino++; n > 0; ino += 2) + { + if (ino[0] > off || ino[1] > off || + ino[1] > ino[0]) + return 0; + off = ino[1]; + n -= 2; + } + } + return 1; +} + +/* + * search for the key in the page. + * return offset index in the range 0 < i < n. + * return 0 if not found. + */ +static int seepair (char *pag, int n, char *key, int siz) +{ + int i; + int off = PBLKSIZ; + short *ino = (short *) pag; + + for (i = 1; i < n; i += 2) + { + if (siz == off - ino[i] && + memcmp (key, pag + ino[i], siz) == 0) + return i; + off = ino[i + 1]; + } + return 0; +} + +#ifdef SEEDUPS +static int duppair (char *pag, datum key) +{ + short *ino = (short *) pag; + + return ino[0] > 0 && seepair (pag, ino[0], key.dptr, key.dsize) > 0; +} + +#endif + +/* ************************* */ + +/* + * sdbm - ndbm work-alike hashed database library + * based on Per-Aake Larson's Dynamic Hashing algorithms. BIT 18 (1978). + * author: oz@nexus.yorku.ca + * status: public domain. + * + * page-level routines + */ + +/* + * page format: + * +------------------------------+ + * ino | n | keyoff | datoff | keyoff | + * +------------+--------+--------+ + * | datoff | - - - ----> | + * +--------+---------------------+ + * | F R E E A R E A | + * +--------------+---------------+ + * | <---- - - - | data | + * +--------+-----+----+----------+ + * | key | data | key | + * +--------+----------+----------+ + * + * calculating the offsets for free area: if the number + * of entries (ino[0]) is zero, the offset to the END of + * the free area is the block size. Otherwise, it is the + * nth (ino[ino[0]]) entry's offset. + */ + +static int fitpair (char *pag, int need) +{ + int n; + int off; + int avail; + short *ino = (short *) pag; + + off = ((n = ino[0]) > 0) ? ino[n] : PBLKSIZ; + avail = off - (n + 1) * sizeof (short); + need += 2 * sizeof (short); + + return need <= avail; +} + +static void putpair (char *pag, datum key, datum val) +{ + int n; + int off; + short *ino = (short *) pag; + + off = ((n = ino[0]) > 0) ? ino[n] : PBLKSIZ; +/* + * enter the key first + */ + off -= key.dsize; + (void) memcpy (pag + off, key.dptr, key.dsize); + ino[n + 1] = off; +/* + * now the data + */ + off -= val.dsize; + (void) memcpy (pag + off, val.dptr, val.dsize); + ino[n + 2] = off; +/* + * adjust item count + */ + ino[0] += 2; +} + +static datum getpair (char *pag, datum key) +{ + int i; + int n; + datum val; + short *ino = (short *) pag; + + if ((n = ino[0]) == 0) + return nullitem; + + if ((i = seepair (pag, n, key.dptr, key.dsize)) == 0) + return nullitem; + + val.dptr = pag + ino[i + 1]; + val.dsize = ino[i] - ino[i + 1]; + return val; +} + +static datum getnkey (char *pag, int num) +{ + datum key; + int off; + short *ino = (short *) pag; + + num = num * 2 - 1; + if (ino[0] == 0 || num > ino[0]) + return nullitem; + + off = (num > 1) ? ino[num - 1] : PBLKSIZ; + + key.dptr = pag + ino[num]; + key.dsize = off - ino[num]; + + return key; +} + +static int delpair (char *pag, datum key) +{ + int n; + int i; + short *ino = (short *) pag; + + if ((n = ino[0]) == 0) + return 0; + + if ((i = seepair (pag, n, key.dptr, key.dsize)) == 0) + return 0; +/* + * found the key. if it is the last entry + * [i.e. i == n - 1] we just adjust the entry count. + * hard case: move all data down onto the deleted pair, + * shift offsets onto deleted offsets, and adjust them. + * [note: 0 < i < n] + */ + if (i < n - 1) + { + int m; + char *dst = pag + (i == 1 ? PBLKSIZ : ino[i - 1]); + char *src = pag + ino[i + 1]; + int zoo = dst - src; + +/* + * shift data/keys down + */ + m = ino[i + 1] - ino[n]; +#ifdef DUFF +#define MOVB *--dst = *--src + if (m > 0) + { + int loop = (m + 8 - 1) >> 3; + + switch (m & (8 - 1)) + { + case 0: + do + { + MOVB; + case 7: + MOVB; + case 6: + MOVB; + case 5: + MOVB; + case 4: + MOVB; + case 3: + MOVB; + case 2: + MOVB; + case 1: + MOVB; + } + while (--loop); + } + } +#else + dst -= m; + src -= m; + memmove (dst, src, m); +#endif +/* + * adjust offset index up + */ + while (i < n - 1) + { + ino[i] = ino[i + 2] + zoo; + i++; + } + } + ino[0] -= 2; + return 1; +} + +static void splpage (char *pag, char *new, long sbit) +{ + datum key; + datum val; + + int n; + int off = PBLKSIZ; + char cur[PBLKSIZ]; + short *ino = (short *) cur; + + (void) memcpy (cur, pag, PBLKSIZ); + (void) memset (pag, 0, PBLKSIZ); + (void) memset (new, 0, PBLKSIZ); + + n = ino[0]; + for (ino++; n > 0; ino += 2) + { + key.dptr = cur + ino[0]; + key.dsize = off - ino[0]; + val.dptr = cur + ino[1]; + val.dsize = ino[0] - ino[1]; +/* + * select the page pointer (by looking at sbit) and insert + */ + (void) putpair ((exhash (key) & sbit) ? new : pag, key, val); + + off = ino[1]; + n -= 2; + } +} + +static int getdbit (DBM * db, long dbit) +{ + long c; + long dirb; + + c = dbit / BYTESIZ; + dirb = c / DBLKSIZ; + + if (dirb != db->dirbno) + { + int got; + if (lseek (db->dirf, OFF_DIR (dirb), SEEK_SET) < 0 + || (got = read(db->dirf, db->dirbuf, DBLKSIZ)) < 0) + return 0; + if (got==0) + memset(db->dirbuf,0,DBLKSIZ); + db->dirbno = dirb; + } + + return db->dirbuf[c % DBLKSIZ] & (1 << dbit % BYTESIZ); +} + +static int setdbit (DBM * db, long dbit) +{ + long c; + long dirb; + + c = dbit / BYTESIZ; + dirb = c / DBLKSIZ; + + if (dirb != db->dirbno) + { + int got; + if (lseek (db->dirf, OFF_DIR (dirb), SEEK_SET) < 0 + || (got = read(db->dirf, db->dirbuf, DBLKSIZ)) < 0) + return 0; + if (got==0) + memset(db->dirbuf,0,DBLKSIZ); + db->dirbno = dirb; + } + + db->dirbuf[c % DBLKSIZ] |= (1 << dbit % BYTESIZ); + +#if 0 + if (dbit >= db->maxbno) + db->maxbno += DBLKSIZ * BYTESIZ; +#else + if (OFF_DIR((dirb+1))*BYTESIZ > db->maxbno) + db->maxbno=OFF_DIR((dirb+1))*BYTESIZ; +#endif + + if (lseek (db->dirf, OFF_DIR (dirb), SEEK_SET) < 0 + || write (db->dirf, db->dirbuf, DBLKSIZ) < 0) + return 0; + + return 1; +} + +/* + * getnext - get the next key in the page, and if done with + * the page, try the next page in sequence + */ +static datum getnext (DBM * db) +{ + datum key; + + for (;;) + { + db->keyptr++; + key = getnkey (db->pagbuf, db->keyptr); + if (key.dptr != NULL) + return key; +/* + * we either run out, or there is nothing on this page.. + * try the next one... If we lost our position on the + * file, we will have to seek. + */ + db->keyptr = 0; + if (db->pagbno != db->blkptr++) + if (lseek (db->pagf, OFF_PAG (db->blkptr), SEEK_SET) < 0) + break; + db->pagbno = db->blkptr; + if (read (db->pagf, db->pagbuf, PBLKSIZ) <= 0) + break; + if (!chkpage (db->pagbuf)) + break; + } + + return ioerr (db), nullitem; +} + +/* + * all important binary trie traversal + */ +static int getpage (DBM * db, long hash) +{ + int hbit; + long dbit; + long pagb; + + dbit = 0; + hbit = 0; + while (dbit < db->maxbno && getdbit (db, dbit)) + dbit = 2 * dbit + ((hash & (1 << hbit++)) ? 2 : 1); + + db->curbit = dbit; + db->hmask = masks[hbit]; + + pagb = hash & db->hmask; +/* + * see if the block we need is already in memory. + * note: this lookaside cache has about 10% hit rate. + */ + if (pagb != db->pagbno) + { +/* + * note: here, we assume a "hole" is read as 0s. + * if not, must zero pagbuf first. + */ + if (lseek (db->pagf, OFF_PAG (pagb), SEEK_SET) < 0 + || read (db->pagf, db->pagbuf, PBLKSIZ) < 0) + return 0; + if (!chkpage (db->pagbuf)) + return 0; + db->pagbno = pagb; + } + return 1; +} + +/* + * makroom - make room by splitting the overfull page + * this routine will attempt to make room for SPLTMAX times before + * giving up. + */ +static int makroom (DBM * db, long hash, int need) +{ + long newp; + char twin[PBLKSIZ]; + char *pag = db->pagbuf; + char *new = twin; + int smax = SPLTMAX; + + do + { +/* + * split the current page + */ + (void) splpage (pag, new, db->hmask + 1); +/* + * address of the new page + */ + newp = (hash & db->hmask) | (db->hmask + 1); + +/* + * write delay, read avoidence/cache shuffle: + * select the page for incoming pair: if key is to go to the new page, + * write out the previous one, and copy the new one over, thus making + * it the current page. If not, simply write the new page, and we are + * still looking at the page of interest. current page is not updated + * here, as sdbm_store will do so, after it inserts the incoming pair. + */ + if (hash & (db->hmask + 1)) + { + if (lseek (db->pagf, OFF_PAG (db->pagbno), SEEK_SET) < 0 + || write (db->pagf, db->pagbuf, PBLKSIZ) < 0) + return 0; + db->pagbno = newp; + (void) memcpy (pag, new, PBLKSIZ); + } + else if (lseek (db->pagf, OFF_PAG (newp), SEEK_SET) < 0 + || write (db->pagf, new, PBLKSIZ) < 0) + return 0; + + if (!setdbit (db, db->curbit)) + return 0; +/* + * see if we have enough room now + */ + if (fitpair (pag, need)) + return 1; +/* + * try again... update curbit and hmask as getpage would have + * done. because of our update of the current page, we do not + * need to read in anything. BUT we have to write the current + * [deferred] page out, as the window of failure is too great. + */ + db->curbit = 2 * db->curbit + + ((hash & (db->hmask + 1)) ? 2 : 1); + db->hmask |= db->hmask + 1; + + if (lseek (db->pagf, OFF_PAG (db->pagbno), SEEK_SET) < 0 + || write (db->pagf, db->pagbuf, PBLKSIZ) < 0) + return 0; + + } + while (--smax); +/* + * if we are here, this is real bad news. After SPLTMAX splits, + * we still cannot fit the key. say goodnight. + */ +#ifdef BADMESS + (void) write (2, "sdbm: cannot insert after SPLTMAX attempts.\n", 44); +#endif + return 0; + +} + +static SDBM *sdbm_prep (char *dirname, char *pagname, int flags, int mode) +{ + SDBM *db; + struct stat dstat; + + if ((db = (SDBM *) mymalloc (sizeof (SDBM))) == NULL) + return errno = ENOMEM, (SDBM *) NULL; + + db->flags = 0; + db->blkptr = 0; + db->keyptr = 0; +/* + * adjust user flags so that WRONLY becomes RDWR, + * as required by this package. Also set our internal + * flag for RDONLY if needed. + */ + if (flags & O_WRONLY) + flags = (flags & ~O_WRONLY) | O_RDWR; + else if ((flags & 03) == O_RDONLY) + db->flags = DBM_RDONLY; +#if defined(OS2) || defined(MSDOS) || defined(WIN32) + flags |= O_BINARY; +#endif + +/* + * Make sure to ignore the O_EXCL option, as the file might exist due + * to the locking. + */ + flags &= ~O_EXCL; + +/* + * open the files in sequence, and stat the dirfile. + * If we fail anywhere, undo everything, return NULL. + */ + + if ((db->pagf = open (pagname, flags, mode)) > -1) + { + if ((db->dirf = open (dirname, flags, mode)) > -1) + { +/* + * need the dirfile size to establish max bit number. + */ + if (fstat (db->dirf, &dstat) == 0) + { + /* + * success + */ + return db; + } + msg_info ("closing dirf"); + (void) close (db->dirf); + } + msg_info ("closing pagf"); + (void) close (db->pagf); + } + myfree ((char *) db); + return (SDBM *) NULL; +} + +static DBM *sdbm_internal_open (SDBM * sdbm) +{ + DBM *db; + struct stat dstat; + + if ((db = (DBM *) mymalloc (sizeof (DBM))) == NULL) + return errno = ENOMEM, (DBM *) NULL; + + db->flags = sdbm->flags; + db->hmask = 0; + db->blkptr = sdbm->blkptr; + db->keyptr = sdbm->keyptr; + db->pagf = sdbm->pagf; + db->dirf = sdbm->dirf; + db->pagbuf = sdbm->pagbuf; + db->dirbuf = sdbm->dirbuf; + +/* + * need the dirfile size to establish max bit number. + */ + if (fstat (db->dirf, &dstat) == 0) + { +/* + * zero size: either a fresh database, or one with a single, + * unsplit data page: dirpage is all zeros. + */ + db->dirbno = (!dstat.st_size) ? 0 : -1; + db->pagbno = -1; + db->maxbno = dstat.st_size * BYTESIZ; + + (void) memset (db->pagbuf, 0, PBLKSIZ); + (void) memset (db->dirbuf, 0, DBLKSIZ); + return db; + } + myfree ((char *) db); + return (DBM *) NULL; +} + +static void sdbm_internal_close (DBM * db) +{ + if (db == NULL) + errno = EINVAL; + else + { + myfree ((char *) db); + } +} + +datum sdbm_fetch (SDBM * sdb, datum key) +{ + datum retval; + DBM *db; + + if (sdb == NULL || bad (key)) + return errno = EINVAL, nullitem; + + if (!(db = sdbm_internal_open (sdb))) + return errno = EINVAL, nullitem; + + if (getpage (db, exhash (key))) + { + retval = getpair (db->pagbuf, key); + sdbm_internal_close (db); + return retval; + } + + sdbm_internal_close (db); + + return ioerr (sdb), nullitem; +} + +int sdbm_delete (SDBM * sdb, datum key) +{ + int retval; + DBM *db; + + if (sdb == NULL || bad (key)) + return errno = EINVAL, -1; + if (sdbm_rdonly (sdb)) + return errno = EPERM, -1; + + if (!(db = sdbm_internal_open (sdb))) + return errno = EINVAL, -1; + + if (getpage (db, exhash (key))) + { + if (!delpair (db->pagbuf, key)) + retval = -1; +/* + * update the page file + */ + else if (lseek (db->pagf, OFF_PAG (db->pagbno), SEEK_SET) < 0 + || write (db->pagf, db->pagbuf, PBLKSIZ) < 0) + retval = ioerr (sdb), -1; + else + retval = 0; + } + else + retval = ioerr (sdb), -1; + + sdbm_internal_close (db); + + return retval; +} + +int sdbm_store (SDBM * sdb, datum key, datum val, int flags) +{ + int need; + int retval; + long hash; + DBM *db; + + if (sdb == NULL || bad (key)) + return errno = EINVAL, -1; + if (sdbm_rdonly (sdb)) + return errno = EPERM, -1; + + need = key.dsize + val.dsize; +/* + * is the pair too big (or too small) for this database ?? + */ + if (need < 0 || need > PAIRMAX) + return errno = EINVAL, -1; + + if (!(db = sdbm_internal_open (sdb))) + return errno = EINVAL, -1; + + if (getpage (db, (hash = exhash (key)))) + { +/* + * if we need to replace, delete the key/data pair + * first. If it is not there, ignore. + */ + if (flags == DBM_REPLACE) + (void) delpair (db->pagbuf, key); +#ifdef SEEDUPS + else if (duppair (db->pagbuf, key)) + { + sdbm_internal_close (db); + return 1; + } +#endif +/* + * if we do not have enough room, we have to split. + */ + if (!fitpair (db->pagbuf, need)) + if (!makroom (db, hash, need)) + { + sdbm_internal_close (db); + return ioerr (db), -1; + } +/* + * we have enough room or split is successful. insert the key, + * and update the page file. + */ + (void) putpair (db->pagbuf, key, val); + + if (lseek (db->pagf, OFF_PAG (db->pagbno), SEEK_SET) < 0 + || write (db->pagf, db->pagbuf, PBLKSIZ) < 0) + { + sdbm_internal_close (db); + return ioerr (db), -1; + } + /* + * success + */ + sdbm_internal_close (db); + return 0; + } + + sdbm_internal_close (db); + return ioerr (sdb), -1; +} + +/* + * the following two routines will break if + * deletions aren't taken into account. (ndbm bug) + */ +datum sdbm_firstkey (SDBM * sdb) +{ + datum retval; + DBM *db; + + if (sdb == NULL) + return errno = EINVAL, nullitem; + + if (!(db = sdbm_internal_open (sdb))) + return errno = EINVAL, nullitem; + +/* + * start at page 0 + */ + if (lseek (db->pagf, OFF_PAG (0), SEEK_SET) < 0 + || read (db->pagf, db->pagbuf, PBLKSIZ) < 0) + { + sdbm_internal_close (db); + return ioerr (sdb), nullitem; + } + db->pagbno = 0; + db->blkptr = 0; + db->keyptr = 0; + + retval = getnext (db); + sdb->blkptr = db->blkptr; + sdb->keyptr = db->keyptr; + sdbm_internal_close (db); + return retval; +} + +datum sdbm_nextkey (SDBM * sdb) +{ + datum retval; + DBM *db; + + if (sdb == NULL) + return errno = EINVAL, nullitem; + + if (!(db = sdbm_internal_open (sdb))) + return errno = EINVAL, nullitem; + + retval = getnext (db); + sdb->blkptr = db->blkptr; + sdb->keyptr = db->keyptr; + sdbm_internal_close (db); + return retval; +} + +void sdbm_close (SDBM * db) +{ + if (db == NULL) + errno = EINVAL; + else + { + (void) close (db->dirf); + (void) close (db->pagf); + myfree ((char *) db); + } +} + +SDBM *sdbm_open (char *file, int flags, int mode) +{ + SDBM *db; + char *dirname; + char *pagname; + int n; + + if (file == NULL || !*file) + return errno = EINVAL, (SDBM *) NULL; +/* + * need space for two seperate filenames + */ + n = strlen (file) * 2 + strlen (DIRFEXT) + strlen (PAGFEXT) + 2; + + if ((dirname = (char *) mymalloc ((unsigned) n)) == NULL) + return errno = ENOMEM, (SDBM *) NULL; +/* + * build the file names + */ + dirname = strcat (strcpy (dirname, file), DIRFEXT); + pagname = strcpy (dirname + strlen (dirname) + 1, file); + pagname = strcat (pagname, PAGFEXT); + + db = sdbm_prep (dirname, pagname, flags, mode); + myfree ((char *) dirname); + return db; +} + diff -Pur postfix-1.1.11-20020613-orig/src/util/sdbm.h postfix-1.1.11-20020613/src/util/sdbm.h --- postfix-1.1.11-20020613-orig/src/util/sdbm.h Thu Jan 1 01:00:00 1970 +++ postfix-1.1.11-20020613/src/util/sdbm.h Wed Jun 26 15:26:49 2002 @@ -0,0 +1,97 @@ +/*++ +/* NAME +/* sdbm 3h +/* SUMMARY +/* SDBM Simple DBM: ndbm work-alike hashed database library +/* SYNOPSIS +/* include "sdbm.h" +/* DESCRIPTION +/* .nf +/*--*/ + +#ifndef UTIL_SDBM_H +#define UTIL_SDBM_H + +/* + * sdbm - ndbm work-alike hashed database library + * based on Per-Ake Larson's Dynamic Hashing algorithms. BIT 18 (1978). + * author: oz@nexus.yorku.ca + * status: public domain. + */ + +#define DUFF /* go ahead and use the loop-unrolled version */ + +#include + +#define DBLKSIZ 16384 /* SSL cert chains require more */ +#define PBLKSIZ 8192 /* SSL cert chains require more */ +#define PAIRMAX 8008 /* arbitrary on PBLKSIZ-N */ +#define SPLTMAX 10 /* maximum allowed splits */ + /* for a single insertion */ +#define DIRFEXT ".dir" +#define PAGFEXT ".pag" + +typedef struct { + int dirf; /* directory file descriptor */ + int pagf; /* page file descriptor */ + int flags; /* status/error flags, see below */ + long blkptr; /* current block for nextkey */ + int keyptr; /* current key for nextkey */ + char pagbuf[PBLKSIZ]; /* page file block buffer */ + char dirbuf[DBLKSIZ]; /* directory file block buffer */ +} SDBM; + +#define DBM_RDONLY 0x1 /* data base open read-only */ +#define DBM_IOERR 0x2 /* data base I/O error */ + +/* + * utility macros + */ +#define sdbm_rdonly(db) ((db)->flags & DBM_RDONLY) +#define sdbm_error(db) ((db)->flags & DBM_IOERR) + +#define sdbm_clearerr(db) ((db)->flags &= ~DBM_IOERR) /* ouch */ + +#define sdbm_dirfno(db) ((db)->dirf) +#define sdbm_pagfno(db) ((db)->pagf) + +typedef struct { + char *dptr; + int dsize; +} datum; + +extern datum nullitem; + +/* + * flags to sdbm_store + */ +#define DBM_INSERT 0 +#define DBM_REPLACE 1 + +/* + * ndbm interface + */ +extern SDBM *sdbm_open(char *, int, int); +extern void sdbm_close(SDBM *); +extern datum sdbm_fetch(SDBM *, datum); +extern int sdbm_delete(SDBM *, datum); +extern int sdbm_store(SDBM *, datum, datum, int); +extern datum sdbm_firstkey(SDBM *); +extern datum sdbm_nextkey(SDBM *); + +/* + * sdbm - ndbm work-alike hashed database library + * tuning and portability constructs [not nearly enough] + * author: oz@nexus.yorku.ca + */ + +#define BYTESIZ 8 + +/* + * important tuning parms (hah) + */ + +#define SEEDUPS /* always detect duplicates */ +#define BADMESS /* generate a message for worst case: + cannot make room after SPLTMAX splits */ +#endif /* UTIL_SDBM_H */ diff -Pur postfix-1.1.11-20020613-orig/src/util/sys_defs.h postfix-1.1.11-20020613/src/util/sys_defs.h --- postfix-1.1.11-20020613-orig/src/util/sys_defs.h Mon Apr 15 23:56:08 2002 +++ postfix-1.1.11-20020613/src/util/sys_defs.h Wed Jun 26 15:26:49 2002 @@ -73,6 +73,10 @@ #define DEF_MAILBOX_LOCK "flock, dotlock" #endif +#if ((defined(__NetBSD_Version__) && __NetBSD_Version__ >= 105000000) || defined(USAGI_LIBINET6)) +#define HAVE_GETIFADDRS +#endif + /* * UNIX on MAC. */ diff -Pur postfix-1.1.11-20020613-orig/src/util/valid_hostname.c postfix-1.1.11-20020613/src/util/valid_hostname.c --- postfix-1.1.11-20020613-orig/src/util/valid_hostname.c Sun Jan 28 15:10:18 2001 +++ postfix-1.1.11-20020613/src/util/valid_hostname.c Wed Jun 26 15:26:49 2002 @@ -47,6 +47,13 @@ #include #include +#ifdef INET6 +#include +#include +#include +#include +#endif + /* Utility library. */ #include "msg.h" @@ -103,7 +110,23 @@ msg_warn("%s: misplaced hyphen: %.100s", myname, name); return (0); } - } else { + } +#ifdef INET6 + else if (ch == ':') { + struct addrinfo hints, *res; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(name, "0", &hints, &res) == 0) { + freeaddrinfo(res); + return 1; + } else + return 0; + } +#endif + else { if (gripe) msg_warn("%s: invalid character %d(decimal): %.100s", myname, ch, name); @@ -135,6 +158,9 @@ int byte_count = 0; int byte_val = 0; int ch; +#ifdef INET6 + struct addrinfo hints, *res; +#endif #define BYTES_NEEDED 4 @@ -146,6 +172,17 @@ msg_warn("%s: empty address", myname); return (0); } + +#ifdef INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; /*dummy*/ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(addr, "0", &hints, &res) == 0) { + freeaddrinfo(res); + return 1; + } +#endif /* * Scary code to avoid sscanf() overflow nasties.