From: Phil Pennock Date: Sat, 18 Feb 2012 16:20:18 +0000 (-0500) Subject: Merge branch 'sasl_fixes' X-Git-Tag: exim-4_80_RC1~80 X-Git-Url: https://git.exim.org/users/jgh/exim.git/commitdiff_plain/f1e05cc79778c693a1a2bad478ced44791922cce?hp=20aa9dbde39896d6ab006180c153996f649b41a2 Merge branch 'sasl_fixes' --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 5ae4f7649..9c39b4aa2 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -1868,6 +1868,14 @@ SUPPORT_TLS=yes TLS_LIBS=-L/usr/local/openssl/lib -lssl -lcrypto TLS_INCLUDE=-I/usr/local/openssl/include/ .endd +.new +.cindex "pkg-config" "OpenSSL" +If you have &'pkg-config'& available, then instead you can just use: +.code +SUPPORT_TLS=yes +USE_OPENSSL_PC=openssl +.endd +.wen .cindex "USE_GNUTLS" If GnuTLS is installed, you should set .code @@ -1883,6 +1891,16 @@ USE_GNUTLS=yes TLS_LIBS=-L/usr/gnu/lib -lgnutls -ltasn1 -lgcrypt TLS_INCLUDE=-I/usr/gnu/include .endd +.new +.cindex "pkg-config" "GnuTLS" +If you have &'pkg-config'& available, then instead you can just use: +.code +SUPPORT_TLS=yes +USE_GNUTLS=yes +USE_GNUTLS_PC=gnutls +.endd +.wen + You do not need to set TLS_INCLUDE if the relevant directory is already specified in INCLUDE. Details of how to configure Exim to make use of TLS are given in chapter &<>&. @@ -2110,6 +2128,28 @@ files or libraries are required. When a lookup type is not included in the binary, attempts to configure Exim to use it cause run time configuration errors. +.new +.cindex "pkg-config" "lookups" +.cindex "pkg-config" "authenticators" +Many systems now use a tool called &'pkg-config'& to encapsulate information +about how to compile against a library; Exim has some initial support for +being able to use pkg-config for lookups and authenticators. For any given +makefile variable which starts &`LOOKUP_`& or &`AUTH_`&, you can add a new +variable with the &`_PC`& suffix in the name and assign as the value the +name of the package to be queried. The results of querying via the +&'pkg-config'& command will be added to the appropriate Makefile variables +with &`+=`& directives, so your version of &'make'& will need to support that +syntax. For instance: +.code +LOOKUP_SQLITE=yes +LOOKUP_SQLITE_PC=sqlite3 +AUTH_GSASL=yes +AUTH_GSASL_PC=libgsasl +AUTH_HEIMDAL_GSSAPI=yes +AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi +.endd +.wen + .cindex "Perl" "including support for" Exim can be linked with an embedded Perl interpreter, allowing Perl subroutines to be called during string expansion. To enable this facility, @@ -23406,15 +23446,29 @@ included by setting .code AUTH_CRAM_MD5=yes AUTH_CYRUS_SASL=yes +.new +AUTH_DOVECOT=yes +AUTH_GSASL=yes +AUTH_HEIMDAL_GSSAPI=yes +.wen AUTH_PLAINTEXT=yes AUTH_SPA=yes .endd in &_Local/Makefile_&, respectively. The first of these supports the CRAM-MD5 authentication mechanism (RFC 2195), and the second provides an interface to -the Cyrus SASL authentication library. The third can be configured to support +the Cyrus SASL authentication library. +.new +The third is an interface to Dovecot's authentication system, delegating the +work via a socket interface. +The fourth provides an interface to the GNU SASL authentication library, which +provides mechanisms but typically not data sources. +The fifth provides direct access to Heimdal GSSAPI, geared for Kerberos, but +supporting setting a server keytab. +The sixth can be configured to support the PLAIN authentication mechanism (RFC 2595) or the LOGIN mechanism, which is -not formally documented, but used by several MUAs. The fourth authenticator +not formally documented, but used by several MUAs. The seventh authenticator supports Microsoft's &'Secure Password Authentication'& mechanism. +.wen The authenticators are configured using the same syntax as other drivers (see section &<>&). If no authenticators are required, no @@ -23446,6 +23500,30 @@ The remainder of this chapter covers the generic options for the authenticators, followed by general discussion of the way authentication works in Exim. +.new +&*Beware:*& the meaning of &$auth1$&, &$auth2$&, ... varies on a per-driver and +per-mechanism basis. Please read carefully to determine which variables hold +account labels such as usercodes and which hold passwords or other +authenticating data. + +Note that some mechanisms support two different identifiers for accounts: the +&'authentication id'& and the &'authorization id'&. The contractions &'authn'& +and &'authz'& are commonly encountered. The American spelling is standard here. +Conceptually, authentication data such as passwords are tied to the identifier +used to authenticate; servers may have rules to permit one user to act as a +second user, so that after login the session is treated as though that second +user had logged in. That second user is the &'authorization id'&. A robust +configuration might confirm that the &'authz'& field is empty or matches the +&'authn'& field. Often this is just ignored. The &'authn'& can be considered +as verified data, the &'authz'& as an unverified request which the server might +choose to honour. + +A &'realm'& is a text string, typically a domain name, presented by a server +to a client to help it select an account and credentials to use. In some +mechanisms, the client and server provably agree on the realm, but clients +typically can not treat the realm as secure data to be blindly trusted. +.wen + .section "Generic options for authenticators" "SECID168" @@ -23492,6 +23570,11 @@ This option must be set for a &%plaintext%& server authenticator, where it is used directly to control authentication. See section &<>& for details. +.new +For the &(gsasl)& authenticator, this option is required for various +mechanisms; see chapter &<>& for details. +.wen + For the other authenticators, &%server_condition%& can be used as an additional authentication or authorization mechanism that is applied after the other authenticator conditions succeed. If it is set, it is expanded when the @@ -24169,10 +24252,17 @@ be set in &_exim.conf_& in your SASL directory. If you are using GSSAPI for Kerberos, note that because of limitations in the GSSAPI interface, changing the server keytab might need to be communicated down to the Kerberos layer independently. The mechanism for doing so is dependent upon the Kerberos -implementation. For example, for Heimdal, the environment variable KRB5_KTNAME +implementation. +.new +For example, for older releases of Heimdal, the environment variable KRB5_KTNAME may be set to point to an alternative keytab file. Exim will pass this variable through from its own inherited environment when started as root or the Exim user. The keytab file needs to be readable by the Exim user. +With newer releases of Heimdal, a setuid Exim may cause Heimdal to discard the +environment variable. In practice, for those releases, the Cyrus authenticator +is not a suitable interface for GSSAPI (Kerberos) support. Instead, consider +the &(heimdal_gssapi)& authenticator, described in chapter &<>& +.wen .section "Using cyrus_sasl as a server" "SECID178" @@ -24275,6 +24365,202 @@ who authenticated is placed in &$auth1$&. .ecindex IIDdcotauth2 +. //////////////////////////////////////////////////////////////////////////// +. //////////////////////////////////////////////////////////////////////////// +.new +.chapter "The gsasl authenticator" "CHAPgsasl" +.scindex IIDgsaslauth1 "&(gsasl)& authenticator" +.scindex IIDgsaslauth2 "authenticators" "&(gsasl)&" +.cindex "authentication" "GNU SASL" +.cindex "authentication" "SASL" +.cindex "authentication" "EXTERNAL" +.cindex "authentication" "ANONYMOUS" +.cindex "authentication" "PLAIN" +.cindex "authentication" "LOGIN" +.cindex "authentication" "DIGEST-MD5" +.cindex "authentication" "CRAM-MD5" +.cindex "authentication" "SCRAM-SHA-1" +The &(gsasl)& authenticator provides server integration for the GNU SASL +library and the mechanisms it provides. This is new as of the 4.78 release +and there are a few areas where the library does not let Exim smoothly +scale to handle future authentication mechanisms, so no guarantee can be +made that any particular new authentication mechanism will be supported +without code changes in Exim. + + +.option server_channelbinding gsasl bool false +Some authentication mechanisms are able to use external context at both ends +of the session to bind the authentication to that context, and fail the +authentication process if that context differs. Specifically, some TLS +ciphersuites can provide identifying information about the cryptographic +context. + +This means that certificate identity and verification becomes a non-issue, +as a man-in-the-middle attack will cause the correct client and server to +see different identifiers and authentication will fail. + +This is currently only supported when using the GnuTLS library. This is +only usable by mechanisms which support "channel binding"; at time of +writing, that's the SCRAM family. + +This defaults off to ensure smooth upgrade across Exim releases, in case +this option causes some clients to start failing. Some future release +of Exim may switch the default to be true. + + +.option server_hostname gsasl string&!! "see below" +This option selects the hostname that is used when communicating with the +library. The default value is &`$primary_hostname`&. +Some mechanisms will use this data. + + +.option server_mech gsasl string "see below" +This option selects the authentication mechanism this driver should use. The +default is the value of the generic &%public_name%& option. This option allows +you to use a different underlying mechanism from the advertised name. For +example: +.code +sasl: + driver = gsasl + public_name = X-ANYTHING + server_mech = CRAM-MD5 + server_set_id = $auth1 +.endd + + +.option server_password gsasl string&!! unset +Various mechanisms need access to the cleartext password on the server, so +that proof-of-possession can be demonstrated on the wire, without sending +the password itself. + +The data available for lookup varies per mechanism. +In all cases, &$auth1$& is set to the &'authentication id'&. +The &$auth2$& variable will always be the &'authorization id'& (&'authz'&) +if available, else the empty string. +The &$auth3$& variable will always be the &'realm'& if available, +else the empty string. + +A forced failure will cause authentication to defer. + +If using this option, it may make sense to set the &%server_condition%& +option to be simply "true". + + +.option server_realm gsasl string&!! unset +This specifies the SASL realm that the server claims to be in. +Some mechanisms will use this data. + + +.option server_scram_iter gsasl string&!! unset +This option provides data for the SCRAM family of mechanisms. +&$auth1$& is not available at evaluation time. +(This may change, as we receive feedback on use) + + +.option server_scram_salt gsasl string&!! unset +This option provides data for the SCRAM family of mechanisms. +&$auth1$& is not available at evaluation time. +(This may change, as we receive feedback on use) + + +.option server_service gsasl string &`smtp`& +This is the SASL service that the server claims to implement. +Some mechanisms will use this data. + + +.section "&(gsasl)& auth variables" "SECTgsaslauthvar" +.vindex "&$auth1$&, &$auth2$&, etc" +These may be set when evaluating specific options, as detailed above. +They will also be set when evaluating &%server_condition%&. + +Unless otherwise stated below, the &(gsasl)& integration will use the following +meanings for these variables: + +.ilist +.vindex "&$auth1$&" +&$auth1$&: the &'authentication id'& +.next +.vindex "&$auth2$&" +&$auth2$&: the &'authorization id'& +.next +.vindex "&$auth3$&" +&$auth3$&: the &'realm'& +.endlist + +On a per-mechanism basis: + +.ilist +.cindex "authentication" "EXTERNAL" +EXTERNAL: only &$auth1$& is set, to the possibly empty &'authorization id'&; +the &%server_condition%& option must be present. +.next +.cindex "authentication" "ANONYMOUS" +ANONYMOUS: only &$auth1$& is set, to the possibly empty &'anonymous token'&; +the &%server_condition%& option must be present. +.next +.cindex "authentication" "GSSAPI" +GSSAPI: &$auth1$& will be set to the &'GSSAPI Display Name'&; +&$auth2$& will be set to the &'authorization id'&, +the &%server_condition%& option must be present. +.endlist + +An &'anonymous token'& is something passed along as an unauthenticated +identifier; this is analogous to FTP anonymous authentication passing an +email address, or software-identifier@, as the "password". + +.wen + +. //////////////////////////////////////////////////////////////////////////// +. //////////////////////////////////////////////////////////////////////////// + +.new +.chapter "The heimdal_gssapi authenticator" "CHAPheimdalgss" +.scindex IIDheimdalgssauth1 "&(heimdal_gssapi)& authenticator" +.scindex IIDheimdalgssauth2 "authenticators" "&(heimdal_gssapi)&" +.cindex "authentication" "GSSAPI" +.cindex "authentication" "Kerberos" +The &(heimdal_gssapi)& authenticator provides server integration for the +Heimdal GSSAPI/Kerberos library, permitting Exim to set a keytab pathname +reliably. + +.option server_hostname heimdal_gssapi string&!! "see below" +This option selects the hostname that is used, with &%server_service%&, +for constructing the GSS server name, as a &'GSS_C_NT_HOSTBASED_SERVICE'& +identifier. The default value is &`$primary_hostname`&. + +.option server_keytab heimdal_gssapi string&!! unset +If set, then Heimdal will not use the system default keytab (typically +&_/etc/krb5.keytab_&) but instead the pathname given in this option. +The value should be a pathname, with no &"file:"& prefix. + +.option server_service heimdal_gssapi string&!! "smtp" +This option specifies the service identifier used, in conjunction with +&%server_hostname%&, for building the identifer for finding credentials +from the keytab. + + +.section "&(heimdal_gssapi)& auth variables" "SECTheimdalgssauthvar" +Beware that these variables will typically include a realm, thus will appear +to be roughly like an email address already. The &'authzid'& in &$auth2$& is +not verified, so a malicious client can set it to anything. + +The &$auth1$& field should be safely trustable as a value from the Key +Distribution Center. Note that these are not quite email addresses. +Each identifier is for a role, and so the left-hand-side may include a +role suffix. For instance, &"joe/admin@EXAMPLE.ORG"&. + +.vindex "&$auth1$&, &$auth2$&, etc" +.ilist +.vindex "&$auth1$&" +&$auth1$&: the &'authentication id'&, set to the GSS Display Name. +.next +.vindex "&$auth2$&" +&$auth2$&: the &'authorization id'&, sent within SASL encapsulation after +authentication. +.endlist + +.wen + . //////////////////////////////////////////////////////////////////////////// . //////////////////////////////////////////////////////////////////////////// diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 0212a51ae..3f43ef83d 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -15,7 +15,20 @@ NM/02 Bugzilla 1093 - eximstats DATA reject detection regexps NM/03 Bugzilla 1169 - primary_hostname spelling was incorrect in docs. -PP/02 New expansion variable $tls_bits; Cyrus SASL server connection +PP/02 Implemented gsasl authenticator. + +PP/03 Implemented heimdal_gssapi authenticator with "server_keytab" option. + +PP/04 Local/Makefile support for (AUTH|LOOKUP)_*_PC=foo to use + `pkg-config foo` for cflags/libs. + +PP/05 Swapped $auth1/$auth2 for gsasl GSSAPI mechanism, to be more consistent + with rest of GSASL and with heimdal_gssapi. + +PP/06 Local/Makefile support for USE_(GNUTLS|OPENSSL)_PC=foo to use + `pkg-config foo` for cflags/libs for the TLS implementation. + +PP/07 New expansion variable $tls_bits; Cyrus SASL server connection properties get this fed in as external SSF. A number of robustness and debugging improvements to the cyrus_sasl authenticator. diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index fdbb07488..057656c24 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -7,8 +7,29 @@ test from the snapshots or the CVS before the documentation is updated. Once the documentation is updated, this file is reduced to a short list. Version 4.78 +------------ - 1. New expansion variable $tls_bits. + 1. New authenticator driver, "gsasl". Server-only (at present). + This is a SASL interface, licensed under GPL, which can be found at + http://www.gnu.org/software/gsasl/. + This system does not provide sources of data for authentication, so + careful use needs to be made of the conditions in Exim. Note that + this can not yet be used as a drop-in replacement for Cyrus SASL, as + Exim is currently unable to construct strings with embedded NULs for + use as keys in lookups against sasldb2. + + 2. New authenticator driver, "heimdal_gssapi". Server-only. + A replacement for using cyrus_sasl with Heimdal, now that $KRB5_KTNAME + is no longer honoured for setuid programs by Heimdal. Use the + "server_keytab" option to point to the keytab. + + 3. The "pkg-config" system can now be used when building Exim to reference + cflags and library information for lookups and authenticators, rather + than having to update "CFLAGS", "AUTH_LIBS", "LOOKUP_INCLUDE" and + "LOOKUP_LIBS" directly. Similarly for handling the TLS library support + without adjusting "TLS_INCLUDE" and "TLS_LIBS". + + 4. New expansion variable $tls_bits. Version 4.77 diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 8a4362a74..5ef0ff7f0 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -111,6 +111,76 @@ do if test -r ../$f fi done >> $mft || exit 1 +# handle pkg-config +# beware portability of extended regexps with sed. + +egrep "^[$st]*(AUTH|LOOKUP)_[A-Z0-9_]*[$st]*=[$st]*" $mft | \ + sed "s/[$st]*=/='/" | \ + sed "s/\$/'/" > $mftt +egrep "^[$st]*((USE_(OPENSSL|GNUTLS)_PC)|SUPPORT_TLS|USE_GNUTLS)[$st]*=[$st]*" $mft | \ + sed "s/[$st]*=/='/" | \ + sed "s/\$/'/" >> $mftt +if test -s $mftt +then + ( + echo "# pkg-config fixups" + . ./$mftt + for var in `cut -d = -f 1 < $mftt`; do + case $var in + + USE_*_PC) + eval "pc_value=\"\$$var\"" + need_this='' + if [ ".$SUPPORT_TLS" = "." ]; then + # no TLS, not referencing + true + elif [ ".$var" = ".USE_GNUTLS_PC" ] && [ ".$USE_GNUTLS" != "." ]; then + need_this=t + elif [ ".$var" = ".USE_OPENSSL_PC" ] && [ ".$USE_GNUTLS" = "." ]; then + need_this=t + fi + if [ ".$need_this" != "." ]; then + tls_include=`pkg-config --cflags $pc_value` + tls_libs=`pkg-config --libs $pc_value` + echo "TLS_INCLUDE=$tls_include" + echo "TLS_LIBS=$tls_libs" + fi + ;; + + *_PC) + eval "pc_value=\"\$$var\"" + base=`echo $var | sed 's/_PC$//'` + eval "basevalue=\"\$$base\"" + if [ ".$basevalue" = "." ]; then + # not pulling in this module, _PC defined as default? Ignore + true + elif [ $basevalue = 2 ]; then + # module; handled in scripts/lookups-Makefile + true + else + # main binary + cflags=`pkg-config --cflags $pc_value` + libs=`pkg-config --libs $pc_value` + if [ "$var" != "${var#LOOKUP_}" ]; then + echo "LOOKUP_INCLUDE += $cflags" + echo "LOOKUP_LIBS += $libs" + elif [ "$var" != "${var#AUTH_}" ]; then + echo "CFLAGS += $cflags" + echo "AUTH_LIBS += $libs" + else + echo >&2 "Don't know how to handle pkg-config for $var" + fi + fi + ;; + + esac + done + echo "# End of pkg-config fixups" + echo + ) >> $mft +fi +rm -f $mftt + # make the lookups Makefile with the definitions ## prepend stuff here; eg: grep LOOKUP_ $mft > $look_mft @@ -184,4 +254,5 @@ else echo " " exit 1; fi +# vim: set ft=sh : # End of Configure-Makefile diff --git a/src/scripts/Configure-config.h b/src/scripts/Configure-config.h index c23523995..75d366fca 100755 --- a/src/scripts/Configure-config.h +++ b/src/scripts/Configure-config.h @@ -34,7 +34,7 @@ $MAKE buildconfig || exit 1 st=' ' (sed -n \ - "/\\\$/d;s/#.*\$//;s/^[$st]*\\([A-Z][^:$st]*\\)[$st]*=[$st]*\\([^$st]*\\)[$st]*\$/\\1=\\2 export \\1/p" \ + "/\\\$/d;s/#.*\$//;s/^[$st]*\\([A-Z][^:!+$st]*\\)[$st]*=[$st]*\\([^$st]*\\)[$st]*\$/\\1=\\2 export \\1/p" \ < Makefile ; echo "./buildconfig") | /bin/sh # If buildconfig ends with an error code, it will have output an error @@ -56,4 +56,5 @@ fi echo ">>> config.h built" echo "" +# vim: set ft=sh : # End of Configure-config.h diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 082659c99..166a25f88 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -123,8 +123,12 @@ ln -s ../../src/auths/call_radius.c call_radius.c ln -s ../../src/auths/check_serv_cond.c check_serv_cond.c ln -s ../../src/auths/cyrus_sasl.c cyrus_sasl.c ln -s ../../src/auths/cyrus_sasl.h cyrus_sasl.h +ln -s ../../src/auths/gsasl_exim.c gsasl_exim.c +ln -s ../../src/auths/gsasl_exim.h gsasl_exim.h ln -s ../../src/auths/get_data.c get_data.c ln -s ../../src/auths/get_no64_data.c get_no64_data.c +ln -s ../../src/auths/heimdal_gssapi.c heimdal_gssapi.c +ln -s ../../src/auths/heimdal_gssapi.h heimdal_gssapi.h ln -s ../../src/auths/md5.c md5.c ln -s ../../src/auths/xtextencode.c xtextencode.c ln -s ../../src/auths/xtextdecode.c xtextdecode.c diff --git a/src/scripts/lookups-Makefile b/src/scripts/lookups-Makefile index cd0a51b34..14c15259e 100755 --- a/src/scripts/lookups-Makefile +++ b/src/scripts/lookups-Makefile @@ -84,7 +84,7 @@ OBJ="" emit_module_rule() { local lookup_name="$1" - local mod_name + local mod_name pkgconf if [ "${lookup_name%:*}" = "$lookup_name" ] then mod_name=$(echo $lookup_name | tr A-Z a-z) @@ -100,9 +100,16 @@ emit_module_rule() { exit 1 fi MODS="${MODS} ${mod_name}.so" - grep "^LOOKUP_${lookup_name}_" "$defs_source" - echo "LOOKUP_${mod_name}_INCLUDE = \$(LOOKUP_${lookup_name}_INCLUDE)" - echo "LOOKUP_${mod_name}_LIBS = \$(LOOKUP_${lookup_name}_LIBS)" + pkgconf=$(grep "^LOOKUP_${lookup_name}_PC" "$defs_source") + if [ $? -eq 0 ]; then + pkgconf=$(echo $pkgconf | sed 's/^.*= *//') + echo "LOOKUP_${mod_name}_INCLUDE = $(pkg-config --cflags $pkgconf)" + echo "LOOKUP_${mod_name}_LIBS = $(pkg-config --libs $pkgconf)" + else + grep "^LOOKUP_${lookup_name}_" "$defs_source" + echo "LOOKUP_${mod_name}_INCLUDE = \$(LOOKUP_${lookup_name}_INCLUDE)" + echo "LOOKUP_${mod_name}_LIBS = \$(LOOKUP_${lookup_name}_LIBS)" + fi elif want_at_all "$lookup_name" then OBJ="${OBJ} ${mod_name}.o" diff --git a/src/src/EDITME b/src/src/EDITME index a180cd5cd..fc57054bf 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -279,6 +279,10 @@ TRANSPORT_SMTP=yes # the dynamic library and not the exim binary will be linked against the # library. # NOTE: LDAP cannot be built as a module! +# +# If your system has pkg-config then the _INCLUDE/_LIBS setting can be +# handled for you automatically by also defining the _PC variable to reference +# the name of the pkg-config package, if such is available. LOOKUP_DBM=yes LOOKUP_LSEARCH=yes @@ -295,6 +299,7 @@ LOOKUP_DNSDB=yes # LOOKUP_PASSWD=yes # LOOKUP_PGSQL=yes # LOOKUP_SQLITE=yes +# LOOKUP_SQLITE_PC=sqlite3 # LOOKUP_WHOSON=yes # These two settings are obsolete; all three lookups are compiled when @@ -342,6 +347,8 @@ PCRE_LIBS=-lpcre # don't need to set LOOKUP_INCLUDE if the relevant directories are already # specified in INCLUDE. The settings below are just examples; -lpq is for # PostgreSQL, -lgds is for Interbase, -lsqlite3 is for SQLite. +# +# You do not need to use this for any lookup information added via pkg-config. # LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include -I /usr/local/pgsql/include # LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3 @@ -553,6 +560,10 @@ FIXED_NEVER_USERS=root # AUTH_CRAM_MD5=yes # AUTH_CYRUS_SASL=yes # AUTH_DOVECOT=yes +# AUTH_GSASL=yes +# AUTH_GSASL_PC=libgsasl +# AUTH_HEIMDAL_GSSAPI=yes +# AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi # AUTH_PLAINTEXT=yes # AUTH_SPA=yes @@ -560,9 +571,13 @@ FIXED_NEVER_USERS=root #------------------------------------------------------------------------------ # If you specified AUTH_CYRUS_SASL above, you should ensure that you have the # Cyrus SASL library installed before trying to build Exim, and you probably -# want to uncomment the following line: +# want to uncomment the first line below. +# Similarly for GNU SASL, unless pkg-config is used via AUTH_GSASL_PC. +# Ditto for AUTH_HEIMDAL_GSSAPI(_PC). # AUTH_LIBS=-lsasl2 +# AUTH_LIBS=-lgsasl +# AUTH_LIBS=-lgssapi -lheimntlm -lkrb5 -lhx509 -lcom_err -lhcrypto -lasn1 -lwind -lroken -lcrypt #------------------------------------------------------------------------------ @@ -655,11 +670,14 @@ HEADERS_CHARSET="ISO-8859-1" # This setting is required for any TLS support (either OpenSSL or GnuTLS) # SUPPORT_TLS=yes -# Uncomment this setting if you are using OpenSSL +# Uncomment one of these settings if you are using OpenSSL; pkg-config vs not +# USE_OPENSSL_PC=openssl # TLS_LIBS=-lssl -lcrypto -# Uncomment these settings if you are using GnuTLS +# Uncomment the first and either the second or the third of these if you +# are using GnuTLS. If you have pkg-config, then the second, else the third. # USE_GNUTLS=yes +# USE_GNUTLS_PC=gnutls # TLS_LIBS=-lgnutls -ltasn1 -lgcrypt # If you are running Exim as a server, note that just building it with TLS @@ -670,6 +688,11 @@ HEADERS_CHARSET="ISO-8859-1" # if you are running Exim only as a client, building it with TLS support # is all you need to do. +# If you are using pkg-config then you should not need to worry where the +# libraries and headers are installed, as the pkg-config .pc specification +# should include all -L/-I information necessary. If not using pkg-config +# then you might need to specify the locations too. + # Additional libraries and include files are required for both OpenSSL and # GnuTLS. The TLS_LIBS settings above assume that the libraries are installed # with all your other libraries. If they are in a special directory, you may diff --git a/src/src/auths/Makefile b/src/src/auths/Makefile index 3e0e1a2cd..c6ef218b2 100644 --- a/src/src/auths/Makefile +++ b/src/src/auths/Makefile @@ -7,7 +7,8 @@ OBJ = auth-spa.o b64decode.o b64encode.o call_pam.o call_pwcheck.o \ call_radius.o check_serv_cond.o cram_md5.o cyrus_sasl.o dovecot.o \ - get_data.o get_no64_data.o md5.o plaintext.o pwcheck.o sha1.o \ + get_data.o get_no64_data.o gsasl_exim.o heimdal_gssapi.o \ + md5.o plaintext.o pwcheck.o sha1.o \ spa.o xtextdecode.o xtextencode.o auths.a: $(OBJ) @@ -38,6 +39,8 @@ xtextencode.o: $(HDRS) xtextencode.c cram_md5.o: $(HDRS) cram_md5.c cram_md5.h cyrus_sasl.o: $(HDRS) cyrus_sasl.c cyrus_sasl.h dovecot.o: $(HDRS) dovecot.c dovecot.h +gsasl_exim.o: $(HDRS) gsasl_exim.c gsasl_exim.h +heimdal_gssapi.o: $(HDRS) heimdal_gssapi.c heimdal_gssapi.h plaintext.o: $(HDRS) plaintext.c plaintext.h spa.o: $(HDRS) spa.c spa.h diff --git a/src/src/auths/check_serv_cond.c b/src/src/auths/check_serv_cond.c index 476d112ae..c10ff1be4 100644 --- a/src/src/auths/check_serv_cond.c +++ b/src/src/auths/check_serv_cond.c @@ -16,8 +16,8 @@ by all authenticators. */ *************************************************/ /* This function is called from the server code of all authenticators. For -plaintext, it is always called: the argument cannot be empty, because for -plaintext, setting server_condition is what enables it as a server +plaintext and gsasl, it is always called: the argument cannot be empty, because +for those, setting server_condition is what enables it as a server authenticator. For all the other authenticators, this function is called after they have authenticated, to enable additional authorization to be done. @@ -31,13 +31,41 @@ Returns: int auth_check_serv_cond(auth_instance *ablock) +{ + return auth_check_some_cond(ablock, + US"server_condition", ablock->server_condition, OK); +} + + +/************************************************* +* Check some server condition * +*************************************************/ + +/* This underlies server_condition, but is also used for some more generic + checks. + +Arguments: + ablock the authenticator's instance block + label debugging label naming the string checked + condition the condition string to be expanded and checked + unset value to return on NULL condition + +Returns: + OK success (or unset=OK) + DEFER couldn't complete the check + FAIL authentication failed +*/ + +int +auth_check_some_cond(auth_instance *ablock, + uschar *label, uschar *condition, int unset) { uschar *cond; HDEBUG(D_auth) { int i; - debug_printf("%s authenticator:\n", ablock->name); + debug_printf("%s authenticator %s:\n", ablock->name, label); for (i = 0; i < AUTH_VARS; i++) { if (auth_vars[i] != NULL) @@ -51,8 +79,13 @@ HDEBUG(D_auth) /* For the plaintext authenticator, server_condition is never NULL. For the rest, an unset condition lets everything through. */ -if (ablock->server_condition == NULL) return OK; -cond = expand_string(ablock->server_condition); +/* For server_condition, an unset condition lets everything through. +For plaintext/gsasl authenticators, it will have been pre-checked to prevent +this. We return the unset scenario value given to us, which for +server_condition will be OK and otherwise will typically be FAIL. */ + +if (condition == NULL) return unset; +cond = expand_string(condition); HDEBUG(D_auth) { diff --git a/src/src/auths/cyrus_sasl.h b/src/src/auths/cyrus_sasl.h index 7e62e63aa..031e783ed 100644 --- a/src/src/auths/cyrus_sasl.h +++ b/src/src/auths/cyrus_sasl.h @@ -31,5 +31,6 @@ extern void auth_cyrus_sasl_init(auth_instance *); extern int auth_cyrus_sasl_server(auth_instance *, uschar *); extern int auth_cyrus_sasl_client(auth_instance *, smtp_inblock *, smtp_outblock *, int, uschar *, int); +extern void auth_cyrus_sasl_version_report(FILE *f); /* End of cyrus_sasl.h */ diff --git a/src/src/auths/get_no64_data.c b/src/src/auths/get_no64_data.c index 4055c0411..ea5fd6f6d 100644 --- a/src/src/auths/get_no64_data.c +++ b/src/src/auths/get_no64_data.c @@ -14,8 +14,8 @@ /* This function is used by authentication drivers to output a challenge to the SMTP client and read the response line. This version does not use base -64 encoding for the text on the 334 line. It is used by the SPA and dovecot -authenticators. +64 encoding for the text on the 334 line. It is used by the SPA, dovecot +and gsasl authenticators. Arguments: aptr set to point to the response (which is in big_buffer) diff --git a/src/src/auths/gsasl_exim.c b/src/src/auths/gsasl_exim.c new file mode 100644 index 000000000..aef337c44 --- /dev/null +++ b/src/src/auths/gsasl_exim.c @@ -0,0 +1,617 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to GNU SASL library for generic authentication. */ + +/* Trade-offs: + +GNU SASL does not provide authentication data itself, so we have to expose +that decision to configuration. For some mechanisms, we need to act much +like plaintext. For others, we only need to be able to provide some +evaluated data on demand. There's no abstracted way (ie, without hardcoding +knowledge of authenticators here) to know which need what properties; we +can't query a session or the library for "we will need these for mechanism X". + +So: we always require server_condition, even if sometimes it will just be +set as "yes". We do provide a number of other hooks, which might not make +sense in all contexts. For some, we can do checks at init time. +*/ + +#include "../exim.h" + +#ifndef AUTH_GSASL +/* dummy function to satisfy compilers when we link in an "empty" file. */ +static void dummy(int x) { dummy(x-1); } +#else + +#include +#include "gsasl_exim.h" + +/* Authenticator-specific options. */ +/* I did have server_*_condition options for various mechanisms, but since +we only ever handle one mechanism at a time, I didn't see the point in keeping +that. In case someone sees a point, I've left the condition_check() API +alone. */ +optionlist auth_gsasl_options[] = { + { "server_channelbinding", opt_bool, + (void *)(offsetof(auth_gsasl_options_block, server_channelbinding)) }, + { "server_hostname", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_hostname)) }, + { "server_mech", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_mech)) }, + { "server_password", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_password)) }, + { "server_realm", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_realm)) }, + { "server_scram_iter", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_scram_iter)) }, + { "server_scram_salt", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_scram_salt)) }, + { "server_service", opt_stringptr, + (void *)(offsetof(auth_gsasl_options_block, server_service)) } +}; +/* GSASL_SCRAM_SALTED_PASSWORD documented only for client, so not implementing +hooks to avoid cleartext passwords in the Exim server. */ + +int auth_gsasl_options_count = + sizeof(auth_gsasl_options)/sizeof(optionlist); + +/* Defaults for the authenticator-specific options. */ +auth_gsasl_options_block auth_gsasl_option_defaults = { + US"smtp", /* server_service */ + US"$primary_hostname", /* server_hostname */ + NULL, /* server_realm */ + NULL, /* server_mech */ + NULL, /* server_password */ + NULL, /* server_scram_iter */ + NULL, /* server_scram_salt */ + FALSE /* server_channelbinding */ +}; + +/* "Globals" for managing the gsasl interface. */ + +static Gsasl *gsasl_ctx = NULL; +static int + main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop); +static int + server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock); +static int + client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock); + +static BOOL sasl_error_should_defer = FALSE; +static Gsasl_property callback_loop = 0; +static BOOL checked_server_condition = FALSE; + +enum { CURRENTLY_SERVER = 1, CURRENTLY_CLIENT = 2 }; + +struct callback_exim_state { + auth_instance *ablock; + int currently; +}; + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +void +auth_gsasl_init(auth_instance *ablock) +{ + char *p; + int rc, supported; + auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); + + /* As per existing Cyrus glue, use the authenticator's public name as + the default for the mechanism name; we don't handle multiple mechanisms + in one authenticator, but the same driver can be used multiple times. */ + + if (ob->server_mech == NULL) + ob->server_mech = string_copy(ablock->public_name); + + /* Can get multiple session contexts from one library context, so just + initialise the once. */ + if (gsasl_ctx == NULL) { + rc = gsasl_init(&gsasl_ctx); + if (rc != GSASL_OK) { + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "couldn't initialise GNU SASL library: %s (%s)", + ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc)); + } + gsasl_callback_set(gsasl_ctx, main_callback); + } + + /* We don't need this except to log it for debugging. */ + rc = gsasl_server_mechlist(gsasl_ctx, &p); + if (rc != GSASL_OK) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "failed to retrieve list of mechanisms: %s (%s)", + ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p); + + supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech); + if (!supported) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "GNU SASL does not support mechanism \"%s\"", + ablock->name, ob->server_mech); + + if ((ablock->server_condition == NULL) && + (streqic(ob->server_mech, US"EXTERNAL") || + streqic(ob->server_mech, US"ANONYMOUS") || + streqic(ob->server_mech, US"PLAIN") || + streqic(ob->server_mech, US"LOGIN"))) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Need server_condition for %s mechanism", + ablock->name, ob->server_mech); + + /* This does *not* scale to new SASL mechanisms. Need a better way to ask + which properties will be needed. */ + if ((ob->server_realm == NULL) && + streqic(ob->server_mech, US"DIGEST-MD5")) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Need server_realm for %s mechanism", + ablock->name, ob->server_mech); + + /* At present, for mechanisms we don't panic on absence of server_condition; + need to figure out the most generically correct approach to deciding when + it's critical and when it isn't. Eg, for simple validation (PLAIN mechanism, + etc) it clearly is critical. + + So don't activate without server_condition, this might be relaxed in the future. + */ + if (ablock->server_condition != NULL) ablock->server = TRUE; + ablock->client = FALSE; +} + + +/* GNU SASL uses one top-level callback, registered at library level. +We dispatch to client and server functions instead. */ + +static int +main_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) +{ + int rc = 0; + struct callback_exim_state *cb_state = + (struct callback_exim_state *)gsasl_session_hook_get(sctx); + + HDEBUG(D_auth) + debug_printf("GNU SASL Callback entered, prop=%d (loop prop=%d)\n", + prop, callback_loop); + + if (cb_state == NULL) { + HDEBUG(D_auth) debug_printf(" not from our server/client processing.\n"); + return GSASL_NO_CALLBACK; + } + + if (callback_loop > 0) { + /* Most likely is that we were asked for property foo, and to + expand the string we asked for property bar to put into an auth + variable, but property bar is not supplied for this mechanism. */ + HDEBUG(D_auth) + debug_printf("Loop, asked for property %d while handling property %d\n", + prop, callback_loop); + return GSASL_NO_CALLBACK; + } + callback_loop = prop; + + if (cb_state->currently == CURRENTLY_CLIENT) + rc = client_callback(ctx, sctx, prop, cb_state->ablock); + else if (cb_state->currently == CURRENTLY_SERVER) + rc = server_callback(ctx, sctx, prop, cb_state->ablock); + else { + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "unhandled callback state, bug in Exim", cb_state->ablock->name); + /* NOTREACHED */ + } + + callback_loop = 0; + return rc; +} + + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_server(auth_instance *ablock, uschar *initial_data) +{ + char *tmps; + char *to_send, *received; + Gsasl_session *sctx = NULL; + auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); + struct callback_exim_state cb_state; + int rc, auth_result, exim_error, exim_error_override; + + HDEBUG(D_auth) + debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n", + ablock->name, ob->server_mech); + + rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx); + if (rc != GSASL_OK) { + auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg); + return DEFER; + } + /* Hereafter: gsasl_finish(sctx) please */ + + gsasl_session_hook_set(sctx, (void *)ablock); + cb_state.ablock = ablock; + cb_state.currently = CURRENTLY_SERVER; + gsasl_session_hook_set(sctx, (void *)&cb_state); + + tmps = CS expand_string(ob->server_service); + gsasl_property_set(sctx, GSASL_SERVICE, tmps); + tmps = CS expand_string(ob->server_hostname); + gsasl_property_set(sctx, GSASL_HOSTNAME, tmps); + if (ob->server_realm) { + tmps = CS expand_string(ob->server_realm); + if (tmps && *tmps) { + gsasl_property_set(sctx, GSASL_REALM, tmps); + } + } + /* We don't support protection layers. */ + gsasl_property_set(sctx, GSASL_QOPS, "qop-auth"); +#ifdef SUPPORT_TLS + if (tls_channelbinding_b64) { + /* Some auth mechanisms can ensure that both sides are talking withing the + same security context; for TLS, this means that even if a bad certificate + has been accepted, they remain MitM-proof because both sides must be within + the same negotiated session; if someone is terminating one sesson and + proxying data on within a second, authentication will fail. + + We might not have this available, depending upon TLS implementation, + ciphersuite, phase of moon ... + + If we do, it results in extra SASL mechanisms being available; here, + Exim's one-mechanism-per-authenticator potentially causes problems. + It depends upon how GNU SASL will implement the PLUS variants of GS2 + and whether it automatically mandates a switch to the bound PLUS + if the data is available. Since default-on, despite being more secure, + would then result in mechanism name changes on a library update, we + have little choice but to default it off and let the admin choose to + enable it. *sigh* + */ + if (ob->server_channelbinding) { + HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n", + ablock->name); + gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, + (const char *) tls_channelbinding_b64); + } else { + HDEBUG(D_auth) + debug_printf("Auth %s: Not enabling channel-binding (data available)\n", + ablock->name); + } + } else { + HDEBUG(D_auth) + debug_printf("Auth %s: no channel-binding data available\n", + ablock->name); + } +#endif + + checked_server_condition = FALSE; + + received = CS initial_data; + to_send = NULL; + exim_error = exim_error_override = OK; + + do { + rc = gsasl_step64(sctx, received, &to_send); + + switch (rc) { + case GSASL_OK: + if (!to_send) + goto STOP_INTERACTION; + break; + + case GSASL_NEEDS_MORE: + break; + + case GSASL_AUTHENTICATION_ERROR: + case GSASL_INTEGRITY_ERROR: + case GSASL_NO_AUTHID: + case GSASL_NO_ANONYMOUS_TOKEN: + case GSASL_NO_AUTHZID: + case GSASL_NO_PASSWORD: + case GSASL_NO_PASSCODE: + case GSASL_NO_PIN: + case GSASL_BASE64_ERROR: + HDEBUG(D_auth) debug_printf("GNU SASL permanent error: %s (%s)\n", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + log_write(0, LOG_REJECT, "%s authenticator (%s):\n " + "GNU SASL permanent failure: %s (%s)", + ablock->name, ob->server_mech, + gsasl_strerror_name(rc), gsasl_strerror(rc)); + if (rc == GSASL_BASE64_ERROR) + exim_error_override = BAD64; + goto STOP_INTERACTION; + + default: + auth_defer_msg = string_sprintf("GNU SASL temporary error: %s (%s)", + gsasl_strerror_name(rc), gsasl_strerror(rc)); + HDEBUG(D_auth) debug_printf("%s\n", auth_defer_msg); + exim_error_override = DEFER; + goto STOP_INTERACTION; + } + + if ((rc == GSASL_NEEDS_MORE) || + (to_send && *to_send)) + exim_error = + auth_get_no64_data((uschar **)&received, (uschar *)to_send); + + if (to_send) { + free(to_send); + to_send = NULL; + } + + if (exim_error) + break; /* handles * cancelled check */ + + } while (rc == GSASL_NEEDS_MORE); + +STOP_INTERACTION: + auth_result = rc; + + gsasl_finish(sctx); + + /* Can return: OK DEFER FAIL CANCELLED BAD64 UNEXPECTED */ + + if (exim_error != OK) + return exim_error; + + if (auth_result != GSASL_OK) { + HDEBUG(D_auth) debug_printf("authentication returned %s (%s)\n", + gsasl_strerror_name(auth_result), gsasl_strerror(auth_result)); + if (exim_error_override != OK) + return exim_error_override; /* might be DEFER */ + if (sasl_error_should_defer) /* overriding auth failure SASL error */ + return DEFER; + return FAIL; + } + + /* Auth succeeded, check server_condition unless already done in callback */ + return checked_server_condition ? OK : auth_check_serv_cond(ablock); +} + +/* returns the GSASL status of expanding the Exim string given */ +static int +condition_check(auth_instance *ablock, uschar *label, uschar *condition_string) +{ + int exim_rc; + + exim_rc = auth_check_some_cond(ablock, label, condition_string, FAIL); + + if (exim_rc == OK) { + return GSASL_OK; + } else if (exim_rc == DEFER) { + sasl_error_should_defer = TRUE; + return GSASL_AUTHENTICATION_ERROR; + } else if (exim_rc == FAIL) { + return GSASL_AUTHENTICATION_ERROR; + } + + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: " + "Unhandled return from checking %s: %d", + ablock->name, label, exim_rc); + /* NOTREACHED */ + return GSASL_AUTHENTICATION_ERROR; +} + +static int +server_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) +{ + char *tmps; + uschar *propval; + int cbrc = GSASL_NO_CALLBACK; + int i; + auth_gsasl_options_block *ob = + (auth_gsasl_options_block *)(ablock->options_block); + + HDEBUG(D_auth) + debug_printf("GNU SASL callback %d for %s/%s as server\n", + prop, ablock->name, ablock->public_name); + + for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; + expand_nmax = 0; + + switch (prop) { + case GSASL_VALIDATE_SIMPLE: + /* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */ + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_PASSWORD); + auth_vars[2] = expand_nstring[3] = propval ? propval : US""; + expand_nmax = 3; + for (i = 1; i <= 3; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + cbrc = condition_check(ablock, US"server_condition", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_EXTERNAL: + if (ablock->server_condition == NULL) { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate EXTERNAL.\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID); + /* We always set $auth1, even if only to empty string. */ + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + expand_nlength[1] = Ustrlen(expand_nstring[1]); + expand_nmax = 1; + + cbrc = condition_check(ablock, + US"server_condition (EXTERNAL)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_ANONYMOUS: + if (ablock->server_condition == NULL) { + HDEBUG(D_auth) debug_printf("No server_condition supplied, to validate ANONYMOUS.\n"); + cbrc = GSASL_AUTHENTICATION_ERROR; + break; + } + propval = (uschar *) gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN); + /* We always set $auth1, even if only to empty string. */ + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + expand_nlength[1] = Ustrlen(expand_nstring[1]); + expand_nmax = 1; + + cbrc = condition_check(ablock, + US"server_condition (ANONYMOUS)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_VALIDATE_GSSAPI: + /* GSASL_AUTHZID and GSASL_GSSAPI_DISPLAY_NAME + The display-name is authenticated as part of GSS, the authzid is claimed + by the SASL integration after authentication; protected against tampering + (if the SASL mechanism supports that, which Kerberos does) but is + unverified, same as normal for other mechanisms. + + First coding, we had these values swapped, but for consistency and prior + to the first release of Exim with this authenticator, they've been + switched to match the ordering of GSASL_VALIDATE_SIMPLE. */ + propval = (uschar *) gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + expand_nmax = 2; + for (i = 1; i <= 2; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + /* In this one case, it perhaps makes sense to default back open? + But for consistency, let's just mandate server_condition here too. */ + cbrc = condition_check(ablock, + US"server_condition (GSSAPI family)", ablock->server_condition); + checked_server_condition = TRUE; + break; + + case GSASL_PASSWORD: + /* DIGEST-MD5: GSASL_AUTHID, GSASL_AUTHZID and GSASL_REALM + CRAM-MD5: GSASL_AUTHID + PLAIN: GSASL_AUTHID and GSASL_AUTHZID + LOGIN: GSASL_AUTHID + */ + if (ob->server_scram_iter) { + tmps = CS expand_string(ob->server_scram_iter); + gsasl_property_set(sctx, GSASL_SCRAM_ITER, tmps); + } + if (ob->server_scram_salt) { + tmps = CS expand_string(ob->server_scram_salt); + gsasl_property_set(sctx, GSASL_SCRAM_SALT, tmps); + } + /* Asking for GSASL_AUTHZID calls back into us if we use + gsasl_property_get(), thus the use of gsasl_property_fast(). + Do we really want to hardcode limits per mechanism? What happens when + a new mechanism is added to the library. It *shouldn't* result in us + needing to add more glue, since avoiding that is a large part of the + point of SASL. */ + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID); + auth_vars[0] = expand_nstring[1] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID); + auth_vars[1] = expand_nstring[2] = propval ? propval : US""; + propval = (uschar *) gsasl_property_fast(sctx, GSASL_REALM); + auth_vars[2] = expand_nstring[3] = propval ? propval : US""; + expand_nmax = 3; + for (i = 1; i <= 3; ++i) + expand_nlength[i] = Ustrlen(expand_nstring[i]); + + tmps = CS expand_string(ob->server_password); + if (tmps == NULL) { + sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE; + HDEBUG(D_auth) debug_printf("server_password expansion failed, so " + "can't tell GNU SASL library the password for %s\n", auth_vars[0]); + return GSASL_AUTHENTICATION_ERROR; + } + gsasl_property_set(sctx, GSASL_PASSWORD, tmps); + /* This is inadequate; don't think Exim's store stacks are geared + for memory wiping, so expanding strings will leave stuff laying around. + But no need to compound the problem, so get rid of the one we can. */ + memset(tmps, '\0', strlen(tmps)); + cbrc = GSASL_OK; + break; + + default: + HDEBUG(D_auth) debug_printf("Unrecognised callback: %d\n", prop); + cbrc = GSASL_NO_CALLBACK; + } + + HDEBUG(D_auth) debug_printf("Returning %s (%s)\n", + gsasl_strerror_name(cbrc), gsasl_strerror(cbrc)); + + return cbrc; +} + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_gsasl_client( + auth_instance *ablock, /* authenticator block */ + smtp_inblock *inblock, /* connection inblock */ + smtp_outblock *outblock, /* connection outblock */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ + HDEBUG(D_auth) + debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n"); + /* NOT IMPLEMENTED */ + return FAIL; +} + +static int +client_callback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop, auth_instance *ablock) +{ + int cbrc = GSASL_NO_CALLBACK; + HDEBUG(D_auth) + debug_printf("GNU SASL callback %d for %s/%s as client\n", + prop, ablock->name, ablock->public_name); + + HDEBUG(D_auth) + debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n"); + + return cbrc; +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +void +auth_gsasl_version_report(FILE *f) +{ + const char *runtime; + runtime = gsasl_check_version(NULL); + fprintf(f, "Library version: GNU SASL: Compile: %s\n" + " Runtime: %s\n", + GSASL_VERSION, runtime); +} + +#endif /* AUTH_GSASL */ + +/* End of gsasl_exim.c */ diff --git a/src/src/auths/gsasl_exim.h b/src/src/auths/gsasl_exim.h new file mode 100644 index 000000000..785b8538b --- /dev/null +++ b/src/src/auths/gsasl_exim.h @@ -0,0 +1,42 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 */ + +/* Interface to GNU SASL library for generic authentication. */ + +/* Authenticator-specific options. */ + +typedef struct { + uschar *server_service; + uschar *server_hostname; + uschar *server_realm; + uschar *server_mech; + uschar *server_password; + uschar *server_scram_iter; + uschar *server_scram_salt; + BOOL server_channelbinding; +} auth_gsasl_options_block; + +/* Data for reading the authenticator-specific options. */ + +extern optionlist auth_gsasl_options[]; +extern int auth_gsasl_options_count; + +/* Defaults for the authenticator-specific options. */ + +extern auth_gsasl_options_block auth_gsasl_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_gsasl_init(auth_instance *); +extern int auth_gsasl_server(auth_instance *, uschar *); +extern int auth_gsasl_client(auth_instance *, smtp_inblock *, + smtp_outblock *, int, uschar *, int); +extern void auth_gsasl_version_report(FILE *f); + +/* End of gsasl_exim.h */ diff --git a/src/src/auths/heimdal_gssapi.c b/src/src/auths/heimdal_gssapi.c new file mode 100644 index 000000000..9021509dd --- /dev/null +++ b/src/src/auths/heimdal_gssapi.c @@ -0,0 +1,582 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to Heimdal library for GSSAPI authentication. */ + +/* Naming and rationale + +Sensibly, this integration would be deferred to a SASL library, but none +of them appear to offer keytab file selection interfaces in their APIs. It +might be that this driver only requires minor modification to work with MIT +Kerberos. + +Heimdal provides a number of interfaces for various forms of authentication. +As GS2 does not appear to provide keytab control interfaces either, we may +end up supporting that too. It's possible that we could trivially expand to +support NTLM support via Heimdal, etc. Rather than try to be too generic +immediately, this driver is directly only supporting GSSAPI. + +Without rename, we could add an option for GS2 support in the future. +*/ + +/* Sources + +* mailcheck-imap (Perl, client-side, written by me years ago) +* gsasl driver (GPL, server-side) +* heimdal sources and man-pages, plus http://www.h5l.org/manual/ +* FreeBSD man-pages (very informative!) +* http://www.ggf.org/documents/GFD.24.pdf confirming GSS_KRB5_REGISTER_ACCEPTOR_IDENTITY_X + semantics, that found by browsing Heimdal source to find how to set the keytab; however, + after multiple attempts I failed to get that to work and instead switched to + gsskrb5_register_acceptor_identity(). +*/ + +#include "../exim.h" + +#ifndef AUTH_HEIMDAL_GSSAPI +/* dummy function to satisfy compilers when we link in an "empty" file. */ +static void dummy(int x) { dummy(x-1); } +#else + +#include +#include + +/* for the _init debugging */ +#include + +#include "heimdal_gssapi.h" + +/* Authenticator-specific options. */ +optionlist auth_heimdal_gssapi_options[] = { + { "server_hostname", opt_stringptr, + (void *)(offsetof(auth_heimdal_gssapi_options_block, server_hostname)) }, + { "server_keytab", opt_stringptr, + (void *)(offsetof(auth_heimdal_gssapi_options_block, server_keytab)) }, + { "server_service", opt_stringptr, + (void *)(offsetof(auth_heimdal_gssapi_options_block, server_service)) } +}; + +int auth_heimdal_gssapi_options_count = + sizeof(auth_heimdal_gssapi_options)/sizeof(optionlist); + +/* Defaults for the authenticator-specific options. */ +auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults = { + US"$primary_hostname", /* server_hostname */ + NULL, /* server_keytab */ + US"smtp", /* server_service */ +}; + +/* "Globals" for managing the heimdal_gssapi interface. */ + +/* Utility functions */ +static void + exim_heimdal_error_debug(const char *, krb5_context, krb5_error_code); +static int + exim_gssapi_error_defer(uschar *, OM_uint32, OM_uint32, const char *, ...) + PRINTF_FUNCTION(4, 5); + +#define EmptyBuf(buf) do { buf.value = NULL; buf.length = 0; } while (0) + + +/************************************************* +* Initialization entry point * +*************************************************/ + +/* Called for each instance, after its options have been read, to +enable consistency checks to be done, or anything else that needs +to be set up. */ + +/* Heimdal provides a GSSAPI extension method for setting the keytab; +in the init, we mostly just use raw krb5 methods so that we can report +the keytab contents, for -D+auth debugging. */ + +void +auth_heimdal_gssapi_init(auth_instance *ablock) +{ + krb5_context context; + krb5_keytab keytab; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_error_code krc; + char *principal, *enctype_s; + const char *k_keytab_typed_name = NULL; + auth_heimdal_gssapi_options_block *ob = + (auth_heimdal_gssapi_options_block *)(ablock->options_block); + + ablock->server = FALSE; + ablock->client = FALSE; + + if (!ob->server_service || !*ob->server_service) { + HDEBUG(D_auth) debug_printf("heimdal: missing server_service\n"); + return; + } + + krc = krb5_init_context(&context); + if (krc != 0) { + int kerr = errno; + HDEBUG(D_auth) debug_printf("heimdal: failed to initialise krb5 context: %s\n", + strerror(kerr)); + return; + } + + if (ob->server_keytab) { + k_keytab_typed_name = CCS string_sprintf("file:%s", expand_string(ob->server_keytab)); + HDEBUG(D_auth) debug_printf("heimdal: using keytab %s\n", k_keytab_typed_name); + krc = krb5_kt_resolve(context, k_keytab_typed_name, &keytab); + if (krc) { + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_resolve", context, krc); + return; + } + } else { + HDEBUG(D_auth) debug_printf("heimdal: using system default keytab\n"); + krc = krb5_kt_default(context, &keytab); + if (krc) { + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_default", context, krc); + return; + } + } + + HDEBUG(D_auth) { + /* http://www.h5l.org/manual/HEAD/krb5/krb5_keytab_intro.html */ + krc = krb5_kt_start_seq_get(context, keytab, &cursor); + if (krc) + exim_heimdal_error_debug("krb5_kt_start_seq_get", context, krc); + else { + while ((krc = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0) { + principal = enctype_s = NULL; + krb5_unparse_name(context, entry.principal, &principal); + krb5_enctype_to_string(context, entry.keyblock.keytype, &enctype_s); + debug_printf("heimdal: keytab principal: %s vno=%d type=%s\n", + principal ? principal : "??", + entry.vno, + enctype_s ? enctype_s : "??"); + free(principal); + free(enctype_s); + krb5_kt_free_entry(context, &entry); + } + krc = krb5_kt_end_seq_get(context, keytab, &cursor); + if (krc) + exim_heimdal_error_debug("krb5_kt_end_seq_get", context, krc); + } + } + + krc = krb5_kt_close(context, keytab); + if (krc) + HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_close", context, krc); + + krb5_free_context(context); + + /* RFC 4121 section 5.2, SHOULD support 64K input buffers */ + if (big_buffer_size < (64 * 1024)) { + uschar *newbuf; + big_buffer_size = 64 * 1024; + newbuf = store_malloc(big_buffer_size); + store_free(big_buffer); + big_buffer = newbuf; + } + + ablock->server = TRUE; +} + + +static void +exim_heimdal_error_debug(const char *label, + krb5_context context, krb5_error_code err) +{ + const char *kerrsc; + kerrsc = krb5_get_error_message(context, err); + debug_printf("heimdal %s: %s\n", label, kerrsc ? kerrsc : "unknown error"); + krb5_free_error_message(context, kerrsc); +} + +/************************************************* +* Server entry point * +*************************************************/ + +/* For interface, see auths/README */ + +/* GSSAPI notes: +OM_uint32: portable type for unsigned int32 +gss_buffer_desc / *gss_buffer_t: hold/point-to size_t .length & void *value + -- all strings/etc passed in should go through one of these + -- when allocated by gssapi, release with gss_release_buffer() +*/ + +int +auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data) +{ + gss_name_t gclient = GSS_C_NO_NAME; + gss_name_t gserver = GSS_C_NO_NAME; + gss_cred_id_t gcred = GSS_C_NO_CREDENTIAL; + gss_ctx_id_t gcontext = GSS_C_NO_CONTEXT; + uschar *ex_server_str; + gss_buffer_desc gbufdesc = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gbufdesc_in = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gbufdesc_out = GSS_C_EMPTY_BUFFER; + gss_OID mech_type; + OM_uint32 maj_stat, min_stat; + int step, error_out, i; + uschar *tmp1, *tmp2, *from_client; + auth_heimdal_gssapi_options_block *ob = + (auth_heimdal_gssapi_options_block *)(ablock->options_block); + BOOL handled_empty_ir; + uschar *store_reset_point; + uschar *keytab; + uschar sasl_config[4]; + uschar requested_qop; + + store_reset_point = store_get(0); + + HDEBUG(D_auth) + debug_printf("heimdal: initialising auth context for %s\n", ablock->name); + + /* Construct our gss_name_t gserver describing ourselves */ + tmp1 = expand_string(ob->server_service); + tmp2 = expand_string(ob->server_hostname); + ex_server_str = string_sprintf("%s@%s", tmp1, tmp2); + gbufdesc.value = (void *) ex_server_str; + gbufdesc.length = Ustrlen(ex_server_str); + maj_stat = gss_import_name(&min_stat, + &gbufdesc, GSS_C_NT_HOSTBASED_SERVICE, &gserver); + if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "gss_import_name(%s)", CS gbufdesc.value); + + /* Use a specific keytab, if specified */ + if (ob->server_keytab) { + keytab = expand_string(ob->server_keytab); + maj_stat = gsskrb5_register_acceptor_identity(CCS keytab); + if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "registering keytab \"%s\"", keytab); + HDEBUG(D_auth) + debug_printf("heimdal: using keytab \"%s\"\n", keytab); + } + + /* Acquire our credentials */ + maj_stat = gss_acquire_cred(&min_stat, + gserver, /* desired name */ + 0, /* time */ + GSS_C_NULL_OID_SET, /* desired mechs */ + GSS_C_ACCEPT, /* cred usage */ + &gcred, /* handle */ + NULL /* actual mechs */, + NULL /* time rec */); + if (GSS_ERROR(maj_stat)) + return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat, + "gss_acquire_cred(%s)", ex_server_str); + + maj_stat = gss_release_name(&min_stat, &gserver); + + HDEBUG(D_auth) debug_printf("heimdal: have server credentials.\n"); + + /* Loop talking to client */ + step = 0; + from_client = initial_data; + handled_empty_ir = FALSE; + error_out = OK; + + /* buffer sizes: auth_get_data() uses big_buffer, which we grow per + GSSAPI RFC in _init, if needed, to meet the SHOULD size of 64KB. + (big_buffer starts life at the MUST size of 16KB). */ + + /* step values + 0: getting initial data from client to feed into GSSAPI + 1: iterating for as long as GSS_S_CONTINUE_NEEDED + 2: GSS_S_COMPLETE, SASL wrapping for authz and qop to send to client + 3: unpick final auth message from client + 4: break/finish (non-step) + */ + while (step < 4) { + switch (step) { + case 0: + if (!from_client || *from_client == '\0') { + if (handled_empty_ir) { + HDEBUG(D_auth) debug_printf("gssapi: repeated empty input, grr.\n"); + error_out = BAD64; + goto ERROR_OUT; + } else { + HDEBUG(D_auth) debug_printf("gssapi: missing initial response, nudging.\n"); + error_out = auth_get_data(&from_client, US"", 0); + if (error_out != OK) + goto ERROR_OUT; + handled_empty_ir = TRUE; + continue; + } + } + /* We should now have the opening data from the client, base64-encoded. */ + step += 1; + HDEBUG(D_auth) debug_printf("heimdal: have initial client data\n"); + break; + + case 1: + gbufdesc_in.length = auth_b64decode(from_client, USS &gbufdesc_in.value); + if (gclient) { + maj_stat = gss_release_name(&min_stat, &gclient); + gclient = GSS_C_NO_NAME; + } + maj_stat = gss_accept_sec_context(&min_stat, + &gcontext, /* context handle */ + gcred, /* acceptor cred handle */ + &gbufdesc_in, /* input from client */ + GSS_C_NO_CHANNEL_BINDINGS, /* XXX fixme: use the channel bindings from GnuTLS */ + &gclient, /* client identifier */ + &mech_type, /* mechanism in use */ + &gbufdesc_out, /* output to send to client */ + NULL, /* return flags */ + NULL, /* time rec */ + NULL /* delegated cred_handle */ + ); + if (GSS_ERROR(maj_stat)) { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_accept_sec_context()"); + error_out = FAIL; + goto ERROR_OUT; + } + if (&gbufdesc_out.length != 0) { + error_out = auth_get_data(&from_client, + gbufdesc_out.value, gbufdesc_out.length); + if (error_out != OK) + goto ERROR_OUT; + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + } + if (maj_stat == GSS_S_COMPLETE) { + step += 1; + HDEBUG(D_auth) debug_printf("heimdal: GSS complete\n"); + } else { + HDEBUG(D_auth) debug_printf("heimdal: need more data\n"); + } + break; + + case 2: + memset(sasl_config, 0xFF, 4); + /* draft-ietf-sasl-gssapi-06.txt defines bitmasks for first octet + 0x01 No security layer + 0x02 Integrity protection + 0x04 Confidentiality protection + + The remaining three octets are the maximum buffer size for wrapped + content. */ + sasl_config[0] = 0x01; /* Exim does not wrap/unwrap SASL layers after auth */ + gbufdesc.value = (void *) sasl_config; + gbufdesc.length = 4; + maj_stat = gss_wrap(&min_stat, + gcontext, + 0, /* conf_req_flag: integrity only */ + GSS_C_QOP_DEFAULT, /* qop requested */ + &gbufdesc, /* message to protect */ + NULL, /* conf_state: no confidentiality applied */ + &gbufdesc_out /* output buffer */ + ); + if (GSS_ERROR(maj_stat)) { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_wrap(SASL state after auth)"); + error_out = FAIL; + goto ERROR_OUT; + } + + HDEBUG(D_auth) debug_printf("heimdal SASL: requesting QOP with no security layers\n"); + + error_out = auth_get_data(&from_client, + gbufdesc_out.value, gbufdesc_out.length); + if (error_out != OK) + goto ERROR_OUT; + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + step += 1; + break; + + case 3: + gbufdesc_in.length = auth_b64decode(from_client, USS &gbufdesc_in.value); + maj_stat = gss_unwrap(&min_stat, + gcontext, + &gbufdesc_in, /* data from client */ + &gbufdesc_out, /* results */ + NULL, /* conf state */ + NULL /* qop state */ + ); + if (GSS_ERROR(maj_stat)) { + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_unwrap(final SASL message from client)"); + error_out = FAIL; + goto ERROR_OUT; + } + if (gbufdesc_out.length < 5) { + HDEBUG(D_auth) + debug_printf("gssapi: final message too short; " + "need flags, buf sizes and authzid\n"); + error_out = FAIL; + goto ERROR_OUT; + } + + requested_qop = (CS gbufdesc_out.value)[0]; + if ((requested_qop & 0x01) == 0) { + HDEBUG(D_auth) + debug_printf("gssapi: client requested security layers (%x)\n", + (unsigned int) requested_qop); + error_out = FAIL; + goto ERROR_OUT; + } + + for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; + expand_nmax = 0; + + /* Identifiers: + The SASL provided identifier is an unverified authzid. + GSSAPI provides us with a verified identifier. + */ + + /* $auth2 is authzid requested at SASL layer */ + expand_nlength[2] = gbufdesc_out.length - 4; + auth_vars[1] = expand_nstring[2] = + string_copyn((US gbufdesc_out.value) + 4, expand_nlength[2]); + expand_nmax = 2; + + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + + /* $auth1 is GSSAPI display name */ + maj_stat = gss_display_name(&min_stat, + gclient, + &gbufdesc_out, + &mech_type); + if (GSS_ERROR(maj_stat)) { + auth_vars[1] = expand_nstring[2] = NULL; + expand_nmax = 0; + exim_gssapi_error_defer(NULL, maj_stat, min_stat, + "gss_display_name(client identifier)"); + error_out = FAIL; + goto ERROR_OUT; + } + + expand_nlength[1] = gbufdesc_out.length; + auth_vars[0] = expand_nstring[1] = + string_copyn(gbufdesc_out.value, gbufdesc_out.length); + + HDEBUG(D_auth) + debug_printf("heimdal SASL: happy with client request\n" + " auth1 (verified GSSAPI display-name): \"%s\"\n" + " auth2 (unverified SASL requested authzid): \"%s\"\n", + auth_vars[0], auth_vars[1]); + + step += 1; + break; + + } /* switch */ + } /* while step */ + + +ERROR_OUT: + maj_stat = gss_release_cred(&min_stat, &gcred); + if (gclient) { + gss_release_name(&min_stat, &gclient); + gclient = GSS_C_NO_NAME; + } + if (gbufdesc_out.length) { + gss_release_buffer(&min_stat, &gbufdesc_out); + EmptyBuf(gbufdesc_out); + } + if (gcontext != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&min_stat, &gcontext, GSS_C_NO_BUFFER); + } + + store_reset(store_reset_point); + + if (error_out != OK) + return error_out; + + /* Auth succeeded, check server_condition */ + return auth_check_serv_cond(ablock); +} + + +static int +exim_gssapi_error_defer(uschar *store_reset_point, + OM_uint32 major, OM_uint32 minor, + const char *format, ...) +{ + va_list ap; + uschar buffer[STRING_SPRINTF_BUFFER_SIZE]; + OM_uint32 maj_stat, min_stat; + OM_uint32 msgcontext = 0; + gss_buffer_desc status_string; + + va_start(ap, format); + if (!string_vformat(buffer, sizeof(buffer), format, ap)) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "exim_gssapi_error_defer expansion larger than %d", + sizeof(buffer)); + va_end(ap); + + auth_defer_msg = NULL; + + do { + maj_stat = gss_display_status(&min_stat, + major, GSS_C_GSS_CODE, GSS_C_NO_OID, + &msgcontext, &status_string); + + if (auth_defer_msg == NULL) { + auth_defer_msg = string_copy(US status_string.value); + } + + HDEBUG(D_auth) debug_printf("heimdal %s: %.*s\n", + buffer, (int)status_string.length, CS status_string.value); + gss_release_buffer(&min_stat, &status_string); + + } while (msgcontext != 0); + + if (store_reset_point) + store_reset(store_reset_point); + return DEFER; +} + + +/************************************************* +* Client entry point * +*************************************************/ + +/* For interface, see auths/README */ + +int +auth_heimdal_gssapi_client( + auth_instance *ablock, /* authenticator block */ + smtp_inblock *inblock, /* connection inblock */ + smtp_outblock *outblock, /* connection outblock */ + int timeout, /* command timeout */ + uschar *buffer, /* buffer for reading response */ + int buffsize) /* size of buffer */ +{ + HDEBUG(D_auth) + debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n"); + /* NOT IMPLEMENTED */ + return FAIL; +} + +/************************************************* +* Diagnostic API * +*************************************************/ + +void +auth_heimdal_gssapi_version_report(FILE *f) +{ + /* No build-time constants available unless we link against libraries at + build-time and export the result as a string into a header ourselves. */ + fprintf(f, "Library version: Heimdal: Runtime: %s\n" + " Build Info: %s\n", + heimdal_version, heimdal_long_version); +} + +#endif /* AUTH_HEIMDAL_GSSAPI */ + +/* End of heimdal_gssapi.c */ diff --git a/src/src/auths/heimdal_gssapi.h b/src/src/auths/heimdal_gssapi.h new file mode 100644 index 000000000..a606a5c26 --- /dev/null +++ b/src/src/auths/heimdal_gssapi.h @@ -0,0 +1,39 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Copyright (c) Twitter Inc 2012 + Author: Phil Pennock */ +/* Copyright (c) Phil Pennock 2012 */ + +/* Interface to Heimdal library for GSSAPI authentication. */ + +/* Authenticator-specific options. */ + +typedef struct { + uschar *server_hostname; + uschar *server_keytab; + uschar *server_service; +} auth_heimdal_gssapi_options_block; + +/* Data for reading the authenticator-specific options. */ + +extern optionlist auth_heimdal_gssapi_options[]; +extern int auth_heimdal_gssapi_options_count; + +/* Defaults for the authenticator-specific options. */ + +extern auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults; + +/* The entry points for the mechanism */ + +extern void auth_heimdal_gssapi_init(auth_instance *); +extern int auth_heimdal_gssapi_server(auth_instance *, uschar *); +extern int auth_heimdal_gssapi_client(auth_instance *, smtp_inblock *, + smtp_outblock *, int, uschar *, int); +extern void auth_heimdal_gssapi_version_report(FILE *f); + +/* End of heimdal_gssapi.h */ diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index bc983c444..f3e3d880a 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -20,6 +20,8 @@ it's a default value. */ #define AUTH_CRAM_MD5 #define AUTH_CYRUS_SASL #define AUTH_DOVECOT +#define AUTH_GSASL +#define AUTH_HEIMDAL_GSSAPI #define AUTH_PLAINTEXT #define AUTH_SPA diff --git a/src/src/drtables.c b/src/src/drtables.c index 37ecf4f4b..c87e9c23b 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -37,6 +37,14 @@ set to NULL for those that are not compiled into the binary. */ #include "auths/dovecot.h" #endif +#ifdef AUTH_GSASL +#include "auths/gsasl_exim.h" +#endif + +#ifdef AUTH_HEIMDAL_GSSAPI +#include "auths/heimdal_gssapi.h" +#endif + #ifdef AUTH_PLAINTEXT #include "auths/plaintext.h" #endif @@ -58,7 +66,8 @@ auth_info auths_available[] = { sizeof(auth_cram_md5_options_block), auth_cram_md5_init, /* init function */ auth_cram_md5_server, /* server function */ - auth_cram_md5_client /* client function */ + auth_cram_md5_client, /* client function */ + NULL /* diagnostic function */ }, #endif @@ -71,7 +80,8 @@ auth_info auths_available[] = { sizeof(auth_cyrus_sasl_options_block), auth_cyrus_sasl_init, /* init function */ auth_cyrus_sasl_server, /* server function */ - NULL /* client function */ + NULL, /* client function */ + auth_cyrus_sasl_version_report /* diagnostic function */ }, #endif @@ -84,7 +94,36 @@ auth_info auths_available[] = { sizeof(auth_dovecot_options_block), auth_dovecot_init, /* init function */ auth_dovecot_server, /* server function */ - NULL /* client function */ + NULL, /* client function */ + NULL /* diagnostic function */ + }, +#endif + +#ifdef AUTH_GSASL + { + US"gsasl", /* lookup name */ + auth_gsasl_options, + &auth_gsasl_options_count, + &auth_gsasl_option_defaults, + sizeof(auth_gsasl_options_block), + auth_gsasl_init, /* init function */ + auth_gsasl_server, /* server function */ + NULL, /* client function */ + auth_gsasl_version_report /* diagnostic function */ + }, +#endif + +#ifdef AUTH_HEIMDAL_GSSAPI + { + US"heimdal_gssapi", /* lookup name */ + auth_heimdal_gssapi_options, + &auth_heimdal_gssapi_options_count, + &auth_heimdal_gssapi_option_defaults, + sizeof(auth_heimdal_gssapi_options_block), + auth_heimdal_gssapi_init, /* init function */ + auth_heimdal_gssapi_server, /* server function */ + NULL, /* client function */ + auth_heimdal_gssapi_version_report /* diagnostic function */ }, #endif @@ -97,7 +136,8 @@ auth_info auths_available[] = { sizeof(auth_plaintext_options_block), auth_plaintext_init, /* init function */ auth_plaintext_server, /* server function */ - auth_plaintext_client /* client function */ + auth_plaintext_client, /* client function */ + NULL /* diagnostic function */ }, #endif @@ -110,11 +150,12 @@ auth_info auths_available[] = { sizeof(auth_spa_options_block), auth_spa_init, /* init function */ auth_spa_server, /* server function */ - auth_spa_client /* client function */ + auth_spa_client, /* client function */ + NULL /* diagnostic function */ }, #endif -{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL } +{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL } }; diff --git a/src/src/exim.c b/src/src/exim.c index a6c0d7832..4a690a783 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -725,6 +725,8 @@ Returns: nothing static void show_whats_supported(FILE *f) { + auth_info *authi; + #ifdef DB_VERSION_STRING fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING); #elif defined(BTREEVERSION) && defined(HASHVERSION) @@ -867,6 +869,12 @@ fprintf(f, "Authenticators:"); #ifdef AUTH_DOVECOT fprintf(f, " dovecot"); #endif +#ifdef AUTH_GSASL + fprintf(f, " gsasl"); +#endif +#ifdef AUTH_HEIMDAL_GSSAPI + fprintf(f, " heimdal_gssapi"); +#endif #ifdef AUTH_PLAINTEXT fprintf(f, " plaintext"); #endif @@ -962,9 +970,11 @@ DEBUG(D_any) do { tls_version_report(f); #endif -#ifdef AUTH_CYRUS_SASL - auth_cyrus_sasl_version_report(f); -#endif + for (authi = auths_available; *authi->driver_name != '\0'; ++authi) { + if (authi->version_report) { + (*authi->version_report)(f); + } + } fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n" " Runtime: %s\n", diff --git a/src/src/exim.h b/src/src/exim.h index a45ea0b30..626d33dae 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -538,12 +538,4 @@ default to EDQUOT if it exists, otherwise ENOSPC. */ #endif #endif -/* These are for reporting version information from various componenents, to -figure out what's actually happening. They need to be available to the main -function, so we declare them here. Unfortunate. */ - -#ifdef AUTH_CYRUS_SASL -extern void auth_cyrus_sasl_version_report(FILE *); -#endif - /* End of exim.h */ diff --git a/src/src/functions.h b/src/src/functions.h index 5efcbbb04..d2575946b 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -54,6 +54,8 @@ extern int auth_call_radius(uschar *, uschar **); extern int auth_call_saslauthd(uschar *, uschar *, uschar *, uschar *, uschar **); extern int auth_check_serv_cond(auth_instance *); +extern int auth_check_some_cond(auth_instance *, uschar *, uschar *, int); + extern int auth_get_data(uschar **, uschar *, int); extern int auth_get_no64_data(uschar **, uschar *); extern uschar *auth_xtextencode(uschar *, int); diff --git a/src/src/globals.h b/src/src/globals.h index 869a23e63..a51e3bc50 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -91,6 +91,7 @@ extern uschar *openssl_options; /* OpenSSL compatibility options */ extern const pcre *regex_STARTTLS; /* For recognizing STARTTLS settings */ extern uschar *tls_advertise_hosts; /* host for which TLS is advertised */ extern uschar *tls_certificate; /* Certificate file */ +extern uschar *tls_channelbinding_b64; /* string of base64 channel binding */ extern uschar *tls_crl; /* CRL File */ extern uschar *tls_dhparam; /* DH param file */ extern BOOL tls_offered; /* Server offered TLS */ diff --git a/src/src/macros.h b/src/src/macros.h index 451bc56fd..c1c4cc33f 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -72,6 +72,11 @@ as unsigned. */ ((uschar)(c) > 127 && print_topbitchars)) +/* Convenience for testing strings */ + +#define streqic(Foo, Bar) (strcmpic(Foo, Bar) == 0) + + /* When built with TLS support, the act of flushing SMTP output becomes a no-op once an SSL session is in progress. */ diff --git a/src/src/structs.h b/src/src/structs.h index 3790c7fe8..9b51d0b7c 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -367,6 +367,8 @@ typedef struct auth_info { int, /* command timeout */ uschar *, /* buffer for reading response */ int); /* sizeof buffer */ + void (*version_report)( /* diagnostic version reporting */ + FILE *); /* I/O stream to print to */ } auth_info; diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index dc09d4720..2f952e47b 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -855,6 +855,10 @@ construct_cipher_name(gnutls_session session) static uschar cipherbuf[256]; uschar *ver; int c, kx, mac; +#ifdef GNUTLS_CB_TLS_UNIQUE +int rc; +gnutls_datum_t channel; +#endif ver = string_copy( US gnutls_protocol_get_name(gnutls_protocol_get_version(session))); @@ -872,6 +876,21 @@ string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver, tls_cipher = cipherbuf; DEBUG(D_tls) debug_printf("cipher: %s\n", cipherbuf); + +if (tls_channelbinding_b64) + free(tls_channelbinding_b64); +tls_channelbinding_b64 = NULL; + +#ifdef GNUTLS_CB_TLS_UNIQUE +channel = { NULL, 0 }; +rc = gnutls_session_channel_binding(session, GNUTLS_CB_TLS_UNIQUE, &channel); +if (rc) { + DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); +} else { + tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size); + DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n"); +} +#endif } diff --git a/src/src/tls.c b/src/src/tls.c index 7cb1550f3..d975a2c89 100644 --- a/src/src/tls.c +++ b/src/src/tls.c @@ -40,6 +40,7 @@ static int ssl_xfer_buffer_hwm = 0; static int ssl_xfer_eof = 0; static int ssl_xfer_error = 0; +uschar *tls_channelbinding_b64 = NULL; /*************************************************