Merge branch 'sasl_fixes'
authorPhil Pennock <pdp@exim.org>
Sat, 18 Feb 2012 16:20:18 +0000 (11:20 -0500)
committerPhil Pennock <pdp@exim.org>
Sat, 18 Feb 2012 16:20:18 +0000 (11:20 -0500)
26 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/scripts/Configure-Makefile
src/scripts/Configure-config.h
src/scripts/MakeLinks
src/scripts/lookups-Makefile
src/src/EDITME
src/src/auths/Makefile
src/src/auths/check_serv_cond.c
src/src/auths/cyrus_sasl.h
src/src/auths/get_no64_data.c
src/src/auths/gsasl_exim.c [new file with mode: 0644]
src/src/auths/gsasl_exim.h [new file with mode: 0644]
src/src/auths/heimdal_gssapi.c [new file with mode: 0644]
src/src/auths/heimdal_gssapi.h [new file with mode: 0644]
src/src/config.h.defaults
src/src/drtables.c
src/src/exim.c
src/src/exim.h
src/src/functions.h
src/src/globals.h
src/src/macros.h
src/src/structs.h
src/src/tls-gnu.c
src/src/tls.c

index 5ae4f76498b4c8cd0d37812cff7cc054c2fa990c..9c39b4aa204f1202116d9207c60d22eeb319b71d 100644 (file)
@@ -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 &<<CHAPTLS>>&.
@@ -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 &<<SECTfordricon>>&). 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 &<<SECTplainserver>>&
 for details.
 
+.new
+For the &(gsasl)& authenticator, this option is required for various
+mechanisms; see chapter &<<CHAPgsasl>>& 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 &<<CHAPheimdalgss>>&
+.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
+
 . ////////////////////////////////////////////////////////////////////////////
 . ////////////////////////////////////////////////////////////////////////////
 
index 0212a51ae99e39c79be11b2fc30801e2ba1f6677..3f43ef83da0b369150c9bfd0a3c492c56dfdeabc 100644 (file)
@@ -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.
 
index fdbb07488ea7e3eb97d97c7baa8a3a1b6ce848cc..057656c24186fece32d366cf4ced54482a091955 100644 (file)
@@ -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
index 8a4362a74f43d847bfcede4b81a98a70e4f0134a..5ef0ff7f08e1e0c8e911b8e3d294828230a0e7f2 100755 (executable)
@@ -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
index c235239951f81be82fca332a31ec0f56885671b5..75d366fca5d7e072d995a3de66522a10715ec458 100755 (executable)
@@ -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
index 082659c9923b8af8a046f05e167b867251d60630..166a25f88ac053d15c6093542bea02c296ea4cce 100755 (executable)
@@ -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
index cd0a51b340769efdd766456810090aa4cd2d0625..14c15259e8ac1edbfc56cb93a8d558250bfa5b73 100755 (executable)
@@ -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"
index a180cd5cd02342c05b3703dfb184b8c3daa917ee..fc57054bfc0ac5cf5cf09911c81212fa20221311 100644 (file)
@@ -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
index 3e0e1a2cde59c0820443d239650ac456b5fbca43..c6ef218b27b4695e27ccb7ef71159c9c43a4caa3 100644 (file)
@@ -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
 
index 476d112ae4925887969cdc7329cfb29bcc379968..c10ff1be42d8fc5060cc09433004a9cb76a053d5 100644 (file)
@@ -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)
   {
index 7e62e63aa0d835557fbc2b04dffbd5f353c92079..031e783ed90072983202a29f6be71f49728ad107 100644 (file)
@@ -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 */
index 4055c04113e353d6832c50dfba6553cd0cfbf535..ea5fd6f6dc3db168814a6198fb50c412d9e58141 100644 (file)
@@ -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 (file)
index 0000000..aef337c
--- /dev/null
@@ -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 <pdp@exim.org> */
+/* 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 <gsasl.h>
+#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 (file)
index 0000000..785b853
--- /dev/null
@@ -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 (file)
index 0000000..9021509
--- /dev/null
@@ -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 <pdp@exim.org> */
+/* 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 <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+
+/* for the _init debugging */
+#include <krb5.h>
+
+#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 (file)
index 0000000..a606a5c
--- /dev/null
@@ -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 <pdp@exim.org> */
+/* 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 */
index bc983c444e5007608a88e4319c81fcc6c12cc666..f3e3d880a393774c34b4f9efe52ee218dcc3dbcd 100644 (file)
@@ -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
 
index 37ecf4f4b8cf92790a6fd3dfe292351538b82c79..c87e9c23bf313165d646f117f3befc296b330cd1 100644 (file)
@@ -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  }
 };
 
 
index a6c0d7832fbc6aa7a0a8c15785e03dfec3d16dcc..4a690a78396187720f78ca1e6c31c57d99f20ff7 100644 (file)
@@ -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",
index a45ea0b30433a613353488cf5e7db27e661c34cb..626d33dae08a3da4efcaa3e1a9248085fd84be75 100644 (file)
@@ -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 */
index 5efcbbb0440457eb4171274ebb9b4c486bf33e09..d2575946b0e1d08e414279a3a513d51b9cfdb3b5 100644 (file)
@@ -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);
index 869a23e63dfa71f4b1454ea5326a50c566179e9c..a51e3bc50657475312548f3efbd7bf035541798a 100644 (file)
@@ -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 */
index 451bc56fddd97cc0bad2fd5dae0bb9c719b05b8d..c1c4cc33f703faeedaf500d5268f86daf336443c 100644 (file)
@@ -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. */
 
index 3790c7fe878783dbab5b0f8db509213e9cb9a2ae..9b51d0b7c113b8e257e34260c3421236a8542625 100644 (file)
@@ -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;
 
 
index dc09d4720d9df0962feee5357e4723a315010f5d..2f952e47b3aeef121f294ae8e6d521f46fa57841 100644 (file)
@@ -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
 }
 
 
index 7cb1550f35921dbc856ffe63834e7fef7ba43c2d..d975a2c89a64ec4340df2a65792138ec3442a9cc 100644 (file)
@@ -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;
 
 
 /*************************************************