mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

dugan
05.22.2008 139c40de1bc595ccd4b8ca952da9e2a37bc8a18e
These fixes add confidentiality/integrity to the SASL GSSAPI
and DIGEST-MD5 mechanisms. The issue links:

https://opends.dev.java.net/issues/show_bug.cgi?id=346
https://opends.dev.java.net/issues/show_bug.cgi?id=347
https://opends.dev.java.net/issues/show_bug.cgi?id=349
https://opends.dev.java.net/issues/show_bug.cgi?id=350

This page describes the changes:

https://www.opends.org/wiki/page/SASLConnectionSecurityPhase1
2 files deleted
2 files added
26 files modified
5007 ■■■■ changed files
opends/resource/schema/02-config.ldif 19 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/DigestMD5SASLMechanismHandlerConfiguration.xml 89 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/GSSAPISASLMechanismHandlerConfiguration.xml 65 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/extension.properties 353 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/AccessControlHandler.java 17 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ClientConnection.java 28 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ConnectionSecurityProvider.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/Aci.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java 51 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java 36 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java 20 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AccessControlConfigManager.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DefaultAccessControlHandler.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java 1607 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/DigestMD5StateInfo.java 104 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java 494 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/GSSAPIStateInfo.java 531 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SASLContext.java 894 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SASLSecurityProvider.java 457 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/TLSConnectionSecurityProvider.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java 23 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 33 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java 35 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPAuthenticationHandlerTestCase.java 36 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPCompareTestCase.java 19 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPModifyTestCase.java 15 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java 15 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java 22 ●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif
@@ -2354,6 +2354,21 @@
  NAME 'ds-cfg-prohibited-subtrees'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.511
  NAME 'ds-cfg-quality-of-protection'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.512
  NAME 'ds-cfg-cipher-strength'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.513
  NAME 'ds-cfg-principal-name'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler'
  SUP top
@@ -2801,6 +2816,8 @@
  STRUCTURAL
  MUST ds-cfg-identity-mapper
  MAY ( ds-cfg-realm $
        ds-cfg-cipher-strength $
        ds-cfg-quality-of-protection $
        ds-cfg-server-fqdn )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.47
@@ -2811,6 +2828,8 @@
        ds-cfg-realm $
        ds-cfg-kdc-address $
        ds-cfg-keytab $
        ds-cfg-principal-name $
        ds-cfg-quality-of-protection $
        ds-cfg-server-fqdn )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.48
opends/src/admin/defn/org/opends/server/admin/std/DigestMD5SASLMechanismHandlerConfiguration.xml
@@ -64,18 +64,18 @@
  </adm:property-override>
  <adm:property name="realm">
    <adm:synopsis>
      Specifies the realm that is to be used by the server for
      Specifies the realms that is to be used by the server for
      DIGEST-MD5 authentication.
    </adm:synopsis>
    <adm:description>
      If this value is not provided, then the server defaults to use a
      set of realm names that correspond to the defined suffixes.
      If this value is not provided, then the server defaults to use the fully
      qualified hostname of the machine.
    </adm:description>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          The server defaults to a set of realm names that
          correspond to the defined suffixes.
        If this value is not provided, then the server defaults to use the fully
        qualified hostname of the machine.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
@@ -85,8 +85,7 @@
          <adm:regex>.*</adm:regex>
          <adm:usage>STRING</adm:usage>
          <adm:synopsis>
            Any realm string. As needed, it be a DN or matched
            to a realm already in use for another service.
            Any realm string that does not contain a comma.
          </adm:synopsis>
        </adm:pattern>
      </adm:string>
@@ -96,7 +95,80 @@
        <ldap:name>ds-cfg-realm</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property> <adm:property name="identity-mapper" mandatory="true">
    </adm:property>
  <adm:property name="quality-of-protection">
    <adm:synopsis>
     The name of a property that specifies the quality of protection
     the server will support.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>none</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="none">
          <adm:synopsis>
            QOP equals authentication only.
          </adm:synopsis>
        </adm:value>
        <adm:value name="integrity">
          <adm:synopsis>
            Quality of protection equals authentication with integrity
            protection.
          </adm:synopsis>
        </adm:value>
        <adm:value name="confidentiality">
          <adm:synopsis>
            Quality of protection equals authentication with integrity and
            confidentiality protection.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-quality-of-protection</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
    <adm:property name="cipher-strength">
    <adm:synopsis>
     The name of a property that specifies the minimum cipher strength that the
     server will support.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>low</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="low">
          <adm:synopsis>
            Cipher strength suported is high, medium or low.
          </adm:synopsis>
        </adm:value>
        <adm:value name="medium">
          <adm:synopsis>
           Cipher strength suported is medium,high.
          </adm:synopsis>
          </adm:value>
          <adm:value name="high">
          <adm:synopsis>
          Cipher strength suported is high only.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-cipher-strength</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
 <adm:property name="identity-mapper" mandatory="true">
    <adm:synopsis>
      Specifies the name of the identity mapper that is to be used
      with this SASL mechanism handler to match the authentication
@@ -128,6 +200,7 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="server-fqdn">
    <adm:synopsis>
      Specifies the DNS-resolvable fully-qualified domain name for the
opends/src/admin/defn/org/opends/server/admin/std/GSSAPISASLMechanismHandlerConfiguration.xml
@@ -105,6 +105,71 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="quality-of-protection">
    <adm:synopsis>
     The name of a property that specifies the quality of protection
     the server will support.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>none</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="none">
          <adm:synopsis>
            QOP equals authentication only.
          </adm:synopsis>
        </adm:value>
        <adm:value name="integrity">
          <adm:synopsis>
            Quality of protection equals authentication with integrity
            protection.
          </adm:synopsis>
        </adm:value>
        <adm:value name="confidentiality">
          <adm:synopsis>
            Quality of protection equals authentication with integrity and
            confidentiality protection.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-quality-of-protection</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="principal-name">
    <adm:synopsis>
      Specifies the principal name.
    </adm:synopsis>
    <adm:description>
      It can either be a simple user name or a
      service name such as host/example.com.
      If this property is not provided, then the server attempts to build the
      principal name by appending the fully qualified domain name to the string
      "ldap/".
    </adm:description>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>
          The server attempts to determine the principal name from the
          underlying system configuration.
        </adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-principal-name</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="keytab">
    <adm:synopsis>
      Specifies the path to the keytab file that should be used for
opends/src/messages/messages/extension.properties
@@ -400,7 +400,7 @@
 security provider is required for clients that wish to use SASL EXTERNAL \
 authentication
MILD_ERR_SASLEXTERNAL_NO_CLIENT_CERT_126=The SASL EXTERNAL bind request could \
 not be processed because the client did not present a certificate chain \
 not be processed because the client did not present an certificate chain \
 during SSL/TLS negotiation
MILD_ERR_SASLEXTERNAL_NO_MAPPING_127=The SASL EXTERNAL bind request failed \
 because the certificate chain presented by the client during SSL/TLS \
@@ -614,271 +614,76 @@
INFO_SASLCRAMMD5_UPDATED_USER_BASE_DN_191=Attribute ds-cfg-user-base-dn in \
 configuration entry %s has been updated.  The DN %s will now be used as the \
 search base when looking up user entries based on their username
SEVERE_ERR_SASLDIGESTMD5_CANNOT_GET_MESSAGE_DIGEST_192=An unexpected error \
 occurred while attempting to obtain an MD5 digest engine for use by the \
 DIGEST-MD5 SASL handler:  %s
INFO_SASLDIGESTMD5_DESCRIPTION_USERNAME_ATTRIBUTE_193=Name of \
 the attribute that will be used to identify user entries based on the \
 username provided during SASL DIGEST-MD5 authentication.  This must specify \
 the name of a valid attribute type defined in the server schema.  Changes to \
 this configuration attribute will take effect immediately
SEVERE_ERR_SASLDIGESTMD5_CANNOT_GET_USERNAME_ATTR_194=An unexpected error \
 occurred while attempting to determine the value of the \
 ds-cfg-user-name-attribute attribute in configuration entry %s:  %s
SEVERE_ERR_SASLDIGESTMD5_UNKNOWN_USERNAME_ATTR_195=The attribute %s \
 referenced in configuration attribute ds-cfg-user-name-attribute in \
 configuration entry %s does not exist in the Directory Server schema.  The \
 attribute that is to be used for username lookups during SASL DIGEST-MD5 \
 authentication must be defined in the server schema
INFO_SASLDIGESTMD5_DESCRIPTION_USER_BASE_DN_196=Base DN that \
 should be used when searching for entries based on the username provided \
 during SASL DIGEST-MD5 authentication.  Changes to this configuration \
 attribute will take effect immediately
SEVERE_ERR_SASLDIGESTMD5_CANNOT_GET_USER_BASE_DN_197=An unexpected error \
 occurred while attempting to determine the value of the ds-cfg-user-base-dn \
 attribute in configuration entry %s:  %s
INFO_SASLDIGESTMD5_DESCRIPTION_REALM_198=Realm that should be \
 used by the server for DIGEST-MD5 authentication.  If this is not provided, \
 then the server will default to using a set of realm names that correspond to \
 the defined suffixes.  Changes to this configuration attribute will take \
 effect immediately
SEVERE_ERR_SASLDIGESTMD5_CANNOT_GET_REALM_199=An unexpected error occurred \
 while attempting to determine the value of the ds-cfg-realm attribute in \
 configuration entry %s:  %s
SEVERE_WARN_SASLDIGESTMD5_CHALLENGE_TOO_LONG_200=The initial DIGEST-MD5 must \
 be less than 2048 bytes, but the generated challenge was %d bytes
MILD_ERR_SASLDIGESTMD5_NO_CREDENTIALS_201=The client connection included \
 DIGEST-MD5 state information, indicating that the client was in the process \
 of performing a DIGEST-MD5 bind, but the bind request did not include any \
 INFO_SASL_UNSUPPORTED_CALLBACK_192=An unsupported or unexpected callback was \
 provided to the SASL server for use during %s authentication:  %s
MILD_ERR_SASL_NO_CREDENTIALS_193=The client connection included \
 %s state information, indicating that the client was in the process \
 of performing a %s bind, but the bind request did not include any \
 credentials
MILD_ERR_SASLDIGESTMD5_INVALID_STORED_STATE_202=The SASL DIGEST-MD5 bind \
 request contained SASL credentials, but the stored SASL state information for \
 this client connection is not in an appropriate form for the challenge
SEVERE_WARN_SASLDIGESTMD5_CANNOT_PARSE_ISO_CREDENTIALS_203=An error occurred \
 while attempting to parse the DIGEST-MD5 credentials as a string using the %s \
 character set:  %s.  The server will re-try using UTF-8
SEVERE_WARN_SASLDIGESTMD5_CANNOT_PARSE_UTF8_CREDENTIALS_204=An error occurred \
 while attempting to parse the DIGEST-MD5 credentials as a string using the \
 UTF-8 character set:  %s
MILD_ERR_SASLDIGESTMD5_INVALID_TOKEN_IN_CREDENTIALS_205=The DIGEST-MD5 \
 credentials provided by the client contained an invalid token of "%s" \
 starting at position %d
MILD_ERR_SASLDIGESTMD5_INVALID_CHARSET_206=The DIGEST-MD5 credentials \
 provided by the client specified an invalid character set of %s.  Only a \
 value of 'utf-8' is acceptable for this parameter
MILD_ERR_SASLDIGESTMD5_CANNOT_DECODE_REALM_AS_DN_207=An error occurred while \
 attempting to parse the provided response realm "%s" as a DN:  %s
MILD_ERR_SASLDIGESTMD5_INVALID_REALM_208=The DIGEST-MD5 credentials provided \
 by the client included an invalid realm of "%s"
SEVERE_ERR_SASLDIGESTMD5_INVALID_NONCE_209=The DIGEST-MD5 credentials \
 provided by the client included a nonce that was different from the nonce \
 supplied by the server.  This could indicate a replay attack or a chosen \
 plaintext attack, and as a result the client connection will be terminated
MILD_ERR_SASLDIGESTMD5_CANNOT_DECODE_NONCE_COUNT_210=The DIGEST-MD5 \
 credentials provided by the client included a nonce count "%s" that could not \
 be decoded as a hex-encoded integer
SEVERE_ERR_SASLDIGESTMD5_CANNOT_DECODE_STORED_NONCE_COUNT_211=An unexpected \
 error occurred while attempting to decode the nonce count stored by the \
 server for this client connection:  %s
SEVERE_ERR_SASLDIGESTMD5_INVALID_NONCE_COUNT_212=The DIGEST-MD5 credentials \
 provided by the client included a nonce count that was different from the \
 count expected by the server.  This could indicate a replay attack, and as a \
 result the client connection will be terminated
MILD_ERR_SASLDIGESTMD5_INTEGRITY_NOT_SUPPORTED_213=The client requested the \
 auth-int quality of protection but integrity protection is not currently \
 supported by the Directory Server
MILD_ERR_SASLDIGESTMD5_CONFIDENTIALITY_NOT_SUPPORTED_214=The client requested \
 the auth-conf quality of protection but confidentiality protection is not \
 currently supported by the Directory Server
MILD_ERR_SASLDIGESTMD5_INVALID_QOP_215=The DIGEST-MD5 credentials provided by \
 the client requested an invalid quality of protection mechanism of %s
MILD_ERR_SASLDIGESTMD5_CANNOT_PARSE_RESPONSE_DIGEST_216=The DIGEST-MD5 \
 credentials provided by the client included a digest that could not be \
 decoded as a hex-encoded byte sequence:  %s
MILD_ERR_SASLDIGESTMD5_INVALID_RESPONSE_TOKEN_217=The DIGEST-MD5 credentials \
 provided by the client included an invalid token named "%s"
MILD_ERR_SASLDIGESTMD5_NO_USERNAME_IN_RESPONSE_218=The DIGEST-MD5 credentials \
 provided by the client did not contain the required "username" token
MILD_ERR_SASLDIGESTMD5_NO_NONCE_IN_RESPONSE_219=The DIGEST-MD5 credentials \
 provided by the client did not contain the required "nonce" token
MILD_ERR_SASLDIGESTMD5_NO_CNONCE_IN_RESPONSE_220=The DIGEST-MD5 credentials \
 provided by the client did not contain the required "cnonce" token
MILD_ERR_SASLDIGESTMD5_NO_NONCE_COUNT_IN_RESPONSE_221=The DIGEST-MD5 \
 credentials provided by the client did not contain the required "nc" token
MILD_ERR_SASLDIGESTMD5_NO_DIGEST_IN_RESPONSE_223=The DIGEST-MD5 credentials \
 provided by the client did not contain the required "response" token
MILD_ERR_SASLDIGESTMD5_CANNOT_DECODE_USERNAME_AS_DN_224=An error occurred \
 while attempting to decode the SASL DIGEST-MD5 username "%s" because it \
 appeared to contain a DN but DN decoding failed:  %s
MILD_ERR_SASLDIGESTMD5_USERNAME_IS_NULL_DN_225=The username in the SASL \
 DIGEST-MD5 bind request appears to be an empty DN.  This is not allowed
INFO_SASLDIGESTMD5_CANNOT_LOCK_ENTRY_226=The Directory Server was unable to \
 obtain a read lock on user entry %s in order to retrieve that entry
MILD_ERR_SASLDIGESTMD5_CANNOT_GET_ENTRY_BY_DN_227=An error occurred while \
 attempting to retrieve user entry %s as specified in the DN-based username of \
 a SASL DIGEST-MD5 bind request:  %s
MILD_ERR_SASLDIGESTMD5_ZERO_LENGTH_USERNAME_228=The username contained in the \
 SASL DIGEST-MD5 bind request had a length of zero characters, which is not \
 allowed.  DIGEST-MD5 authentication does not allow an empty string for use as \
 the username
MILD_ERR_SASLDIGESTMD5_CANNOT_PERFORM_INTERNAL_SEARCH_229=An error occurred \
 while trying to perform an internal search to retrieve the user entry \
 associated with the SASL DIGEST-MD5 username %s.  The result of that search \
 was %s with a message of %s
MILD_ERR_SASLDIGESTMD5_MULTIPLE_MATCHING_ENTRIES_230=The internal search \
 attempting to resolve SASL DIGEST-MD5 username %s matched multiple entries. \
 Authentication cannot succeed unless the username is mapped to exactly one \
 user entry
MILD_ERR_SASLDIGESTMD5_NO_MATCHING_ENTRIES_231=The server was not able to \
 find any user entries for the provided username of %s
MILD_ERR_SASLDIGESTMD5_NO_PW_ATTR_232=The SASL DIGEST-MD5 authentication \
 failed because the mapped user entry did not contain any values for the %s \
 attribute
MILD_ERR_SASLDIGESTMD5_UNKNOWN_STORAGE_SCHEME_233=A password in the target \
 user entry %s could not be processed via SASL DIGEST-MD5 because that \
 password has an unknown storage scheme of %s
MILD_ERR_SASLDIGESTMD5_CANNOT_GET_CLEAR_PASSWORD_234=An error occurred while \
 attempting to obtain the clear-text password for user %s from the value with \
 storage scheme %s:  %s
MILD_ERR_SASLDIGESTMD5_INVALID_CREDENTIALS_235=The DIGEST-MD5 credentials \
 provided by the client are not appropriate for any password in the associated \
 user account
MILD_ERR_SASLDIGESTMD5_NO_REVERSIBLE_PASSWORDS_236=SASL DIGEST-MD5 \
 authentication is not possible for user %s because none of the passwords in \
 the user entry are stored in a reversible form
SEVERE_WARN_SASLDIGESTMD5_CANNOT_GENERATE_RESPONSE_DIGEST_237=An error \
 occurred while attempting to generate a server-side digest to compare with \
 the client response:  %s
SEVERE_ERR_SASLDIGESTMD5_CANNOT_GENERATE_RESPONSE_AUTH_DIGEST_238=An error \
 occurred while trying to generate the response auth digest to include in the \
 server SASL credentials:  %s
MILD_ERR_SASLDIGESTMD5_INVALID_CLOSING_QUOTE_POS_239=The DIGEST-MD5 response \
 challenge could not be parsed because it had an invalid quotation mark at \
 position %d
INFO_SASLDIGESTMD5_UPDATED_USERNAME_ATTR_240=Attribute \
 ds-cfg-user-name-attribute in configuration entry %s has been updated.  The \
 %s attribute will now be used when looking up user entries based on their \
 username
INFO_SASLDIGESTMD5_UPDATED_USER_BASE_DN_241=Attribute ds-cfg-user-base-dn in \
 configuration entry %s has been updated.  The DN %s will now be used as the \
 search base when looking up user entries based on their username
INFO_SASLDIGESTMD5_UPDATED_NEW_REALM_242=Attribute ds-cfg-realm in \
 configuration entry %s has been updated.  The realm "%s" will now be \
 advertised by the server in the challenge response
INFO_SASLDIGESTMD5_UPDATED_NO_REALM_243=Attribute ds-cfg-realm in \
 configuration entry %s has been updated.  The realm(s) advertised by the \
 server in the challenge response will be the DNs of the server suffixes
INFO_SASLGSSAPI_DESCRIPTION_USERNAME_ATTRIBUTE_244=Name of the \
 attribute that will be used to identify user entries based on the username \
 provided during SASL GSSAPI authentication.  This must specify the name of a \
 valid attribute type defined in the server schema.  Changes to this \
 configuration attribute will take effect immediately
SEVERE_ERR_SASLGSSAPI_CANNOT_GET_USERNAME_ATTR_245=An unexpected error \
 occurred while attempting to determine the value of the \
 ds-cfg-user-name-attribute attribute in configuration entry %s:  %s
SEVERE_ERR_SASLGSSAPI_UNKNOWN_USERNAME_ATTR_246=The attribute %s referenced \
 in configuration attribute ds-cfg-user-name-attribute in configuration entry \
 %s does not exist in the Directory Server schema.  The attribute that is to \
 be used for username lookups during SASL GSSAPI authentication must be \
 defined in the server schema
INFO_SASLGSSAPI_DESCRIPTION_USER_BASE_DN_247=Base DN that \
 should be used when searching for entries based on the username provided \
 during SASL GSSAPI authentication.  Changes to this configuration attribute \
 will take effect immediately
SEVERE_ERR_SASLGSSAPI_CANNOT_GET_USER_BASE_DN_248=An unexpected error \
 occurred while attempting to determine the value of the ds-cfg-user-base-dn \
 attribute in configuration entry %s:  %s
INFO_SASLGSSAPI_DESCRIPTION_SERVER_FQDN_249=Fully-qualified \
 domain name that should be used for the server during SASL GSSAPI \
 authentication.  Changes to this configuration attribute will take effect \
 immediately
SEVERE_ERR_SASLGSSAPI_CANNOT_GET_SERVER_FQDN_250=An unexpected error occurred \
SEVERE_ERR_SASL_CANNOT_GET_SERVER_FQDN_194=An unexpected error occurred \
 while attempting to determine the value of the ds-cfg-server-fqdn attribute \
 in configuration entry %s:  %s
INFO_SASLGSSAPI_UPDATED_USERNAME_ATTR_251=Attribute \
 ds-cfg-user-name-attribute in configuration entry %s has been updated.  The \
 %s attribute will now be used when looking up user entries based on their \
 username
INFO_SASLGSSAPI_UPDATED_USER_BASE_DN_252=Attribute ds-cfg-user-base-dn in \
 configuration entry %s has been updated.  The DN %s will now be used as the \
 search base when looking up user entries based on their username
INFO_SASLGSSAPI_UPDATED_NEW_SERVER_FQDN_253=Attribute ds-cfg-server-fqdn in \
 configuration entry %s has been updated.  The value "%s" will now be used as \
 the fully-qualified name of the Directory Server for GSSAPI authentication
INFO_SASLGSSAPI_UPDATED_NO_SERVER_FQDN_254=Attribute ds-cfg-server-fqdn in \
 configuration entry %s has been updated.  The Directory Server will attempt \
 to determine its own FQDN for use in GSSAPI authentication
INFO_SASLGSSAPI_UNEXPECTED_CALLBACK_255=An unexpected callback was provided \
 for the SASL server for use during GSSAPI authentication:  %s
INFO_SASLGSSAPI_DESCRIPTION_KDC_ADDRESS_256=Address of the KDC \
 that should be used during SASL GSSAPI authentication.  If this is not \
 specified, then an attempt will be made to obtain it from the system-wide \
 Kerberos configuration.  Changes to this configuration attribute will take \
 effect immediately for subsequent GSSAPI bind attempts
MILD_ERR_SASLGSSAPI_CANNOT_GET_KDC_ADDRESS_257=An unexpected error occurred \
 while attempting to determine the value of the ds-cfg-kdc-address attribute \
 in configuration entry %s:  %s
INFO_SASLGSSAPI_DESCRIPTION_REALM_258=Default realm that should \
 be used during SASL GSSAPI authentication.  If this is not specified, then an \
 attempt will be made to obtain it from the system-wide Kerberos \
 configuration.  Changes to this configuration attribute will take effect \
 immediately for subsequent GSSAPI bind attempts
MILD_ERR_SASLGSSAPI_CANNOT_GET_REALM_259=An unexpected error occurred while \
 attempting to determine the value of the ds-cfg-realm attribute in \
 configuration entry %s:  %s
MILD_ERR_SASLGSSAPI_NO_CLIENT_CONNECTION_260=No client connection was \
 available for use in processing the GSSAPI bind request
MILD_ERR_SASLGSSAPI_CANNOT_CREATE_SASL_SERVER_261=An error occurred while \
 attempting to create the SASL server instance to process the GSSAPI bind \
 request:  %s
MILD_ERR_SASLGSSAPI_CANNOT_EVALUATE_RESPONSE_262=An error occurred while \
 attempting to evaluate the challenge response provided by the client in the \
 GSSAPI bind request:  %s
MILD_ERR_SASLGSSAPI_NO_AUTHZ_ID_263=The GSSAPI authentication process appears \
 to have completed but no authorization ID is available for mapping to a \
 directory user
MILD_ERR_SASLGSSAPI_CANNOT_PERFORM_INTERNAL_SEARCH_264=An error occurred \
 while attempting to perform an internal search to map the GSSAPI \
 authorization ID %s to a Directory Server user (result code %d, error message \
 "%s")
MILD_ERR_SASLGSSAPI_MULTIPLE_MATCHING_ENTRIES_265=The GSSAPI authorization ID \
 %s appears to have multiple matches in the Directory Server
MILD_ERR_SASLGSSAPI_CANNOT_MAP_AUTHZID_266=The GSSAPI authorization ID %s \
 could not be mapped to any user in the Directory Server
INFO_SASLGSSAPI_UPDATED_KDC_267=Attribute ds-cfg-kdc-address in configuration \
 entry %s has been updated.  The value "%s" will now be used as the address of \
 the KDC for GSSAPI authentication
INFO_SASLGSSAPI_UNSET_KDC_268=Attribute ds-cfg-kdc-address in configuration \
 entry %s has been un-set as a system property.  Any further GSSAPI \
 authentication attempts will rely on the Kerberos configuration in the \
 underlying operating system to determine the KDC address
INFO_SASLGSSAPI_UPDATED_REALM_269=Attribute ds-cfg-realm in configuration \
 entry %s has been updated.  The value "%s" will now be used as the default \
 realm for GSSAPI authentication
INFO_SASLGSSAPI_UNSET_REALM_270=Attribute ds-cfg-realm in configuration entry \
 %s has been un-set as a system property.  Any further GSSAPI authentication \
 attempts will rely on the Kerberos configuration in the underlying operating \
 system to determine the default realm
MILD_ERR_SASLGSSAPI_CANNOT_CREATE_LOGIN_CONTEXT_271=An error occurred while \
 attempting to create the JAAS login context for GSSAPI authentication:  %s
MILD_ERR_SASLGSSAPI_CANNOT_AUTHENTICATE_SERVER_272=An error occurred while \
 attempting to perform server-side Kerberos authentication to support a GSSAPI \
 bind operation:  %s
INFO_SASLGSSAPI_DESCRIPTION_KEYTAB_FILE_273=Path to the keytab \
 file containing the secret key for the Kerberos principal to use when \
 processing GSSAPI authentication.  If this is not specified, then the \
 system-wide default keytab file will be used.  Changes to this configuration \
 attribute will not take effect until the GSSAPI SASL mechanism handler is \
 disabled and re-enabled or the Directory Server is restarted
MILD_ERR_SASLGSSAPI_CANNOT_GET_KEYTAB_FILE_274=An unexpected error occurred \
 while attempting to determine the value of the ds-cfg-keytab attribute in \
 configuration entry %s:  %s
SEVERE_ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG_275=An error occurred while \
 SEVERE_ERR_SASL_CONTEXT_CREATE_ERROR_195=An unexpected error occurred while \
 trying to create an %s context: %s
MILD_ERR_SASL_CANNOT_DECODE_USERNAME_AS_DN_196=An error occurred \
 while attempting to decode the SASL %s username "%s" because it \
 appeared to contain a DN but DN decoding failed:  %s
MILD_ERR_SASL_USERNAME_IS_NULL_DN_197=The username in the SASL \
 %s bind request appears to be an empty DN.  This is not allowed
INFO_SASL_CANNOT_LOCK_ENTRY_198=The Directory Server was unable to \
 obtain a read lock on user entry %s in order to retrieve that entry
MILD_ERR_SASL_CANNOT_GET_ENTRY_BY_DN_199=An error occurred while \
 attempting to retrieve user entry %s as specified in the DN-based username of \
 a SASL %s bind request:  %s
MILD_ERR_SASL_ZERO_LENGTH_USERNAME_200=The username contained in the \
 SASL %s bind request had a length of zero characters, which is not \
 allowed.  %s authentication does not allow an empty string for use as \
 the username
MILD_ERR_SASL_NO_MATCHING_ENTRIES_201=The server was not able to \
 find any user entries for the provided username of %s
MILD_ERR_SASL_AUTHZID_INVALID_DN_202=The provided authorization ID \
 %s contained an invalid DN:  %s
 MILD_ERR_SASL_AUTHZID_NO_SUCH_ENTRY_203=The entry %s specified as \
 the authorization identity does not exist
MILD_ERR_SASL_AUTHZID_CANNOT_GET_ENTRY_204=The entry %s specified as \
 the authorization identity could not be retrieved:  %s
MILD_ERR_SASL_AUTHZID_NO_MAPPED_ENTRY_205=The server was unable to \
 find any entry corresponding to authorization ID %s
MILD_ERR_SASL_CANNOT_MAP_AUTHZID_206=An error occurred while \
 attempting to map authorization ID %s to a user entry:  %s
MILD_ERR_SASL_CANNOT_GET_REVERSIBLE_PASSWORDS_207=An error occurred \
 while attempting to retrieve the clear-text password(s) for user %s in order \
 to perform SASL %s authentication:  %s
MILD_ERR_SASL_NO_REVERSIBLE_PASSWORDS_208=SASL %s \
 authentication is not possible for user %s because none of the passwords in \
 the user entry are stored in a reversible form
SEVERE_ERR_SASL_PROTOCOL_ERROR_209=SASL %s protocol error: %s
MILD_ERR_SASL_AUTHZID_INSUFFICIENT_PRIVILEGES_210=The authenticating \
 user %s does not have sufficient privileges to assume a different \
 authorization identity
MILD_ERR_SASL_AUTHZID_INSUFFICIENT_ACCESS_211=The authenticating \
 user %s does not have sufficient access to assume a different \
 authorization identity
MILD_ERR_SASL_AUTHENTRY_NO_MAPPED_ENTRY_212=The server was unable to \
 find any entry corresponding to authentication ID %s
SEVERE_ERR_SASLGSSAPI_KDC_REALM_NOT_DEFINED_213=The server was unable to \
 because both the ds-cfg-kdc-address and ds-cfg-realm attributes must be \
 defined or neither defined
MILD_ERR_SASL_CANNOT_MAP_AUTHENTRY_214=An error occurred while \
 attempting to map authorization ID %s to a user entry:  %s
SEVERE_ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG_215=An error occurred while \
 attempting to write a temporary JAAS configuration file for use during GSSAPI \
 processing:  %s
SEVERE_ERR_SASLGSSAPI_DIFFERENT_AUTHID_AND_AUTHZID_276=The authentication ID \
 %s was not equal to the authorization ID %s.  This is not supported for \
 GSSAPI authentication
 SEVERE_ERR_SASLGSSAPI_CANNOT_CREATE_LOGIN_CONTEXT_216=An error occurred while \
 attempting to create the JAAS login context for GSSAPI authentication:  %s
 MILD_ERR_SASLGSSAPI_NO_CLIENT_CONNECTION_217=No client connection was \
 available for use in processing the GSSAPI bind request
 INFO_GSSAPI_PRINCIPAL_NAME_218=GSSAPI mechanism using a principal name of: %s
 INFO_GSSAPI_SERVER_FQDN_219=GSSAPI SASL mechanism using a server fully \
 qualified domain name of: %s
 INFO_DIGEST_MD5_REALM_220=DIGEST-MD5 SASL mechanism using a realm of: %s
 INFO_DIGEST_MD5_SERVER_FQDN_221=DIGEST-MD5 SASL mechanism using a server \
 fully qualified domain name of: %s
SEVERE_ERR_EXTOP_WHOAMI_PROXYAUTH_INSUFFICIENT_PRIVILEGES_277=You do not have \
 sufficient privileges to use the proxied authorization control
INFO_EXACTMAP_DESCRIPTION_MATCH_ATTR_298=Name or OID of the \
@@ -1123,9 +928,11 @@
 from configuration entry %s:  %s
NOTICE_ERRORLOG_ACCTNOTHANDLER_NOTIFICATION_375=Account-Status-Notification \
 type='%s' userdn='%s' id=%d msg='%s'
MILD_ERR_SASLDIGESTMD5_CANNOT_GET_REVERSIBLE_PASSWORDS_376=An error occurred \
 while attempting to retrieve the clear-text password(s) for user %s in order \
 to perform SASL DIGEST-MD5 authentication:  %s
MILD_ERR_SASLCRAMMD5_CANNOT_GET_REVERSIBLE_PASSWORDS_377=An error occurred \
 while attempting to retrieve the clear-text password(s) for user %s in order \
 to perform SASL CRAM-MD5 authentication:  %s
@@ -1169,13 +976,13 @@
 perform an internal modification to update the group:  %s
MILD_ERR_EXTOP_PASSMOD_INSUFFICIENT_PRIVILEGES_392=You do not have sufficient \
 privileges to perform password reset operations
MILD_ERR_SASLDIGESTMD5_EMPTY_AUTHZID_393=The provided authorization ID was \
 empty, which is not allowed for DIGEST-MD5 authentication
MILD_ERR_SASLDIGESTMD5_AUTHZID_INVALID_DN_394=The provided authorization ID \
 %s contained an invalid DN:  %s
MILD_ERR_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES_395=The authenticating \
 user %s does not have sufficient privileges to assume a different \
 authorization identity
MILD_ERR_SASLDIGESTMD5_AUTHZID_NO_SUCH_ENTRY_396=The entry %s specified as \
 the authorization identity does not exist
MILD_ERR_SASLDIGESTMD5_AUTHZID_CANNOT_GET_ENTRY_397=The entry %s specified as \
@@ -1184,6 +991,14 @@
 find any entry corresponding to authorization ID %s
MILD_ERR_SASLDIGESTMD5_CANNOT_MAP_AUTHZID_399=An error occurred while \
 attempting to map authorization ID %s to a user entry:  %s
MILD_ERR_SASLPLAIN_AUTHZID_INVALID_DN_400=The provided authorization ID %s \
 contained an invalid DN:  %s
MILD_ERR_SASLPLAIN_AUTHZID_INSUFFICIENT_PRIVILEGES_401=The authenticating \
@@ -1625,6 +1440,6 @@
SEVERE_ERR_SDTUACM_ATTR_UNINDEXED_569=The subject DN to user attribute \
 certificate mapper defined in configuration entry %s references attribute \
 type %s which is does not have an equality index defined in backend %s
INFO_LOG_EXTENSION_INFORMATION_570=Loaded extension from file '%s' (build %s, \
SEVERE_ERR_SASLDIGESTMD5_PROTOCOL_ERROR_570=SASL DIGEST MD5 protocol error: %s
INFO_LOG_EXTENSION_INFORMATION_571=Loaded extension from file '%s' (build %s, \
 revision %s)
opends/src/server/org/opends/server/api/AccessControlHandler.java
@@ -331,5 +331,22 @@
                             SearchOperation searchOperation,
                             SearchResultReference searchReference);
  /**
   * Indicates if the specified proxy user entry can proxy, or act on
   * the behalf of the specified proxied user entry. The operation
   * parameter is used in the evaluation.
   *
   * @param proxyUser The entry to use as the proxy user.
   * @param proxiedUser The entry to be proxied by the proxy user.
   * @param operation The operation to use in the evaluation.
   *
   * @return  {@code true} if the access control configuration allows
   *          the proxy user to proxy the proxied user, or
   *          {@code false} if not.
   */
  public abstract boolean mayProxy(Entry proxyUser,
                                   Entry proxiedUser,
                                   Operation operation);
}
opends/src/server/org/opends/server/api/ClientConnection.java
@@ -306,7 +306,7 @@
   * @return  The connection handler that accepted this client
   *          connection.
   */
  public abstract ConnectionHandler getConnectionHandler();
  public abstract ConnectionHandler<?> getConnectionHandler();
@@ -1113,6 +1113,26 @@
  }
  /**
   * Indicate whether the specified authorization entry parameter
   * has the specified privilege. The method can be used to perform
   * a "what-if" scenario.
   *
 * @param authorizationEntry The authentication entry to use.
 * @param privilege The privilege to check for.
   *
   * @return  {@code true} if the authentication entry has the
   *          specified privilege, or {@code false} if not.
   */
  public static boolean hasPrivilege(Entry authorizationEntry,
                                   Privilege privilege) {
      boolean isRoot =
          DirectoryServer.isRootDN(authorizationEntry.getDN());
      return getPrivileges(authorizationEntry,
              isRoot).contains(privilege) ||
              DirectoryServer.isDisabled(privilege);
  }
  /**
   * Indicates whether the authenticated client has the specified
@@ -1298,7 +1318,7 @@
   *
   * @return  A set of the privileges that should be assigned.
   */
  private HashSet<Privilege> getPrivileges(Entry entry,
  private static HashSet<Privilege> getPrivileges(Entry entry,
                                           boolean isRoot)
  {
    if (entry == null)
@@ -1579,7 +1599,7 @@
   * @throws  DirectoryException  If a problem occurs while attempting
   *                             to make the determination.
   */
  public boolean isMemberOf(Group group, Operation operation)
  public boolean isMemberOf(Group<?> group, Operation operation)
         throws DirectoryException
  {
    if (operation == null)
@@ -1668,7 +1688,7 @@
   * Retrieves the DN of the key manager provider that should be used
   * for operations requiring access to a key manager.  The default
   * implementation returns {@code null} to indicate that no key
   * manager provider is avaialble, but subclasses should override
   * manager provider is available, but subclasses should override
   * this method to return a valid DN if they perform operations which
   * may need access to a key manager.
   *
opends/src/server/org/opends/server/api/ConnectionSecurityProvider.java
@@ -98,6 +98,16 @@
   */
  public abstract boolean isSecure();
 /**
  * Indicates whether the security provider is active or not. Some
  * security providers (DIGEST-MD5, GSSAPI) perform
  * confidentiality/integrity processing of messages and require
  * several handshakes to setup.
  *
  * @return  {@code true} if the security provider is active, or,
  *          {@code false} if not.
  */
  public abstract boolean isActive();
  /**
opends/src/server/org/opends/server/authorization/dseecompat/Aci.java
@@ -538,7 +538,7 @@
   * @param expr A string representing the OID expression.
   * @param msg  A message to be used if there is an exception.
   *
   * @return  Return a hash set of verfied OID strings parsed from the OID
   * @return  Return a hash set of verified OID strings parsed from the OID
   *          expression.
   *
   * @throws AciException If the specified expression string is invalid.
opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
@@ -93,9 +93,9 @@
    private boolean isAddOp=false;
    /*
     * The rights to use in the evaluation of the LDAP operation.
     * The right mask to use in the evaluation of the LDAP operation.
     */
    private int rights;
    private int rightsMask;
    /*
     * The entry being evaluated (resource entry).
@@ -246,8 +246,15 @@
    */
    private String extOpOID;
    /*
     * AuthenticationInfo class to use.
     */
    private AuthenticationInfo authInfo;
  /**
     * This constructor is used by all currently supported LDAP operations.
     * This constructor is used by all currently supported LDAP operations
     * except the generic access control check that can be used by
     * plugins.
     *
     * @param operation The Operation object being evaluated and target
     * matching.
@@ -262,6 +269,7 @@
      this.clientConnection=operation.getClientConnection();
      if(operation instanceof AddOperationBasis)
          this.isAddOp=true;
      this.authInfo = clientConnection.getAuthenticationInfo();
      //If the proxied authorization control was processed, then the operation
      //will contain an attachment containing the original authorization entry.
@@ -313,10 +321,30 @@
      //if an access proxy check was performed.
      this.saveAuthorizationEntry=this.authorizationEntry;
      this.saveResourceEntry=this.resourceEntry;
      this.rights = rights;
      this.rightsMask = rights;
    }
  /**
     * This constructor is used by the generic access control check.
     *
     * @param operation The operation to use in the access evaluation.
     * @param e The entry to check access for.
     * @param authInfo The authentication information to use in the evaluation.
     * @param rights The rights to check access of.
     */
    protected AciContainer(Operation operation, Entry e,
                            AuthenticationInfo authInfo,
                            int rights) {
        this.resourceEntry=e;
        this.operation=operation;
        this.clientConnection=operation.getClientConnection();
        this.authInfo = authInfo;
        this.authorizationEntry = authInfo.getAuthorizationEntry();
        this.saveAuthorizationEntry=this.authorizationEntry;
        this.saveResourceEntry=this.resourceEntry;
        this.rightsMask = rights;
    }
  /**
   * Returns true if an entry has already been processed by an access proxy
   * check.
   *
@@ -655,7 +683,7 @@
    * {@inheritDoc}
    */
    public boolean isAnonymousUser() {
        return !clientConnection.getAuthenticationInfo().isAuthenticated();
        return !authInfo.isAuthenticated();
    }
   /**
@@ -689,21 +717,21 @@
    * {@inheritDoc}
    */
    public boolean hasRights(int rights) {
       return (this.rights & rights) != 0;
       return (this.rightsMask & rights) != 0;
    }
   /**
    * {@inheritDoc}
    */
    public int getRights() {
        return this.rights;
        return this.rightsMask;
    }
   /**
    * {@inheritDoc}
    */
    public void setRights(int rights) {
         this.rights=rights;
         this.rightsMask=rights;
    }
   /**
@@ -791,7 +819,6 @@
        /*
         * Some kind of authentication is required.
         */
        AuthenticationInfo authInfo=clientConnection.getAuthenticationInfo();
        if(authInfo.isAuthenticated()) {
          if(authMethod==EnumAuthMethod.AUTHMETHOD_SIMPLE) {
            if(authInfo.hasAuthenticationType(AuthenticationType.SIMPLE)) {
@@ -833,7 +860,7 @@
    /**
     * {@inheritDoc}
     */
    public boolean isMemberOf(Group group) {
    public boolean isMemberOf(Group<?> group) {
        boolean ret;
        try {
            if(useAuthzid) {
@@ -884,7 +911,7 @@
   * {@inheritDoc}
   */
  public  void setEvalUserAttributes(int v) {
    if(operation instanceof SearchOperation && (rights == ACI_READ)) {
    if(operation instanceof SearchOperation && (rightsMask == ACI_READ)) {
      if(v == ACI_FOUND_USER_ATTR_RULE) {
        evalAllAttributes |= ACI_FOUND_USER_ATTR_RULE;
        evalAllAttributes &= ~ACI_USER_ATTR_STAR_MATCHED;
@@ -897,7 +924,7 @@
   * {@inheritDoc}
   */
  public  void setEvalOpAttributes(int v) {
    if(operation instanceof SearchOperation && (rights == ACI_READ)) {
    if(operation instanceof SearchOperation && (rightsMask == ACI_READ)) {
      if(v == ACI_FOUND_OP_ATTR_RULE) {
        evalAllAttributes |= ACI_FOUND_OP_ATTR_RULE;
        evalAllAttributes &= ~ACI_OP_ATTR_PLUS_MATCHED;
opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java
@@ -146,7 +146,7 @@
     * @return True if the authorization DN of the operation is a
     * member of the specified group.
     */
    public boolean isMemberOf(Group group);
    public boolean isMemberOf(Group<?> group);
  /**
   * Returns true if the hashtable of ACIs that matched the targattrfilters
opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
@@ -28,8 +28,6 @@
package org.opends.server.authorization.dseecompat;
import org.opends.messages.Message;
import static org.opends.server.authorization.dseecompat.Aci.*;
import static org.opends.server.config.ConfigConstants.ATTR_AUTHZ_GLOBAL_ACI;
import static org.opends.server.loggers.ErrorLogger.logError;
@@ -39,12 +37,11 @@
import static org.opends.server.schema.SchemaConstants.SYNTAX_DN_OID;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.util.*;
import java.util.concurrent.locks.Lock;
import org.opends.server.admin.std.server.DseeCompatAccessControlHandlerCfg;
import org.opends.server.api.AccessControlHandler;
import org.opends.server.api.ClientConnection;
import org.opends.server.config.ConfigException;
import org.opends.server.core.*;
import org.opends.server.loggers.debug.DebugTracer;
@@ -535,6 +532,17 @@
    }
    /**
     * Check to see if the specified entry has the specified privilege.
     *
     * @param e The entry to check privileges on.
     * @return  {@code true} if the entry has the
     *          specified privilege, or {@code false} if not.
     */
    private boolean skipAccessCheck(Entry e) {
        return ClientConnection.hasPrivilege(e, Privilege.BYPASS_ACL);
    }
    /**
     * Check access using the specified container. This container will have all
     * of the information to gather applicable ACIs and perform evaluation on
     * them.
@@ -1242,6 +1250,26 @@
   * {@inheritDoc}
   */
  @Override
  public boolean
  mayProxy(Entry proxyUser, Entry proxiedUser, Operation op) {
      boolean ret;
      if(!(ret=skipAccessCheck(proxyUser))) {
          AuthenticationInfo authInfo =
              new AuthenticationInfo(proxyUser,
                     DirectoryServer.isRootDN(proxyUser.getDN()));
          AciLDAPOperationContainer operationContainer =
              new AciLDAPOperationContainer(op, proxiedUser,
                                            authInfo, ACI_PROXY);
          ret=accessAllowedEntry(operationContainer);
      }
      return ret;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAllowed(LocalBackendBindOperation bindOperation) {
      //Not planned to be implemented.
      return true;
opends/src/server/org/opends/server/authorization/dseecompat/AciLDAPOperationContainer.java
@@ -28,7 +28,6 @@
package org.opends.server.authorization.dseecompat;
import java.util.List;
import org.opends.server.core.*;
import org.opends.server.types.*;
import org.opends.server.workflowelement.localbackend.*;
@@ -62,6 +61,23 @@
        super(operation, rights, operation.getEntryToCompare());
    }
    /**
     * Constructor interface for evaluation general purpose Operation, entry and
     * rights..
     *
     * @param operation The operation to use in the evaluation.
     * @param e The entry for evaluation.
     * @param authInfo The authentication information to use in the evaluation.
     * @param rights The rights of the operation.
     */
    public AciLDAPOperationContainer(Operation operation, Entry e,
                                     AuthenticationInfo authInfo,
                                     int rights) {
      super(operation, e, authInfo, rights);
    }
    /**
     * Constructor interface for evaluation of a control.
     *
@@ -126,7 +142,7 @@
     * Constructor interface for the modify DN operation.
     * @param operation  The modify DN operation.
     * @param rights  The rights of the modify DN operation.
     * @param entry  The entry to evalauted for this modify DN.
     * @param entry  The entry to evaluated for this modify DN.
     */
    public AciLDAPOperationContainer(LocalBackendModifyDNOperation operation,
                                     int rights,
opends/src/server/org/opends/server/core/AccessControlConfigManager.java
@@ -143,7 +143,7 @@
   *
   * @return   The active access control handler (never {@code null}).
   */
  public AccessControlHandler getAccessControlHandler()
  public AccessControlHandler<?> getAccessControlHandler()
  {
    return accessControlHandler.get();
  }
opends/src/server/org/opends/server/core/DefaultAccessControlHandler.java
@@ -222,5 +222,15 @@
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public  boolean mayProxy(Entry proxyUser, Entry proxiedUser,
                           Operation operation) {
      return true;
  }
}
opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
@@ -26,95 +26,65 @@
 */
package org.opends.server.extensions;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.text.ParseException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import javax.security.sasl.*;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.DigestMD5SASLMechanismHandlerCfgDefn.*;
import org.opends.server.admin.std.server.DigestMD5SASLMechanismHandlerCfg;
import org.opends.server.admin.std.server.SASLMechanismHandlerCfg;
import org.opends.server.api.Backend;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.IdentityMapper;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.config.ConfigException;
import org.opends.server.core.BindOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
import org.opends.server.loggers.debug.*;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LockManager;
import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.util.Base64;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class provides an implementation of a SASL mechanism that uses digest
 * authentication via DIGEST-MD5.  This is a password-based mechanism that does
 * not expose the password itself over the wire but rather uses an MD5 hash that
 * proves the client knows the password.  This is similar to the CRAM-MD5
 * mechanism, and the primary differences are that CRAM-MD5 only obtains random
 * data from the server whereas DIGEST-MD5 uses random data from both the
 * server and the client, CRAM-MD5 does not allow for an authorization ID in
 * addition to the authentication ID where DIGEST-MD5 does, and CRAM-MD5 does
 * not define any integrity and confidentiality mechanisms where DIGEST-MD5
 * does.  This implementation is based on the specification in RFC 2831 and
 * updates from draft-ietf-sasl-rfc2831bis-06.
 * This class provides an implementation of a SASL mechanism that authenticates
 * clients through DIGEST-MD5.
 */
public class DigestMD5SASLMechanismHandler
       extends SASLMechanismHandler<DigestMD5SASLMechanismHandlerCfg>
       implements ConfigurationChangeListener<
                       DigestMD5SASLMechanismHandlerCfg>
{
  /**
   * The tracer object for the debug logger.
   */
      implements ConfigurationChangeListener<DigestMD5SASLMechanismHandlerCfg> {
  //The tracer object for the debug logger.
  private static final DebugTracer TRACER = getTracer();
  // The current configuration for this SASL mechanism handler.
  private DigestMD5SASLMechanismHandlerCfg currentConfig;
  private DigestMD5SASLMechanismHandlerCfg configuration;
  // The identity mapper that will be used to map ID strings to user entries.
  private IdentityMapper<?> identityMapper;
  // The message digest engine that will be used to create the MD5 digests.
  private MessageDigest md5Digest;
  //Properties to use when creating a SASL server to process the authentication.
  private HashMap<String,String> saslProps;
  // The lock that will be used to provide threadsafe access to the message
  // digest.
  private Object digestLock;
//The fully qualified domain name used when creating the SASL server.
  private String serverFQDN;
  // The random number generator that we will use to create the nonce.
  private SecureRandom randomGenerator;
  // The DN of the configuration entry for this SASL mechanism handler.
  private DN configEntryDN;
  //Property used to set the realm in the environment.
  private static final String REALM_PROPERTY =
                                          "com.sun.security.sasl.digest.realm";
  /**
@@ -128,1409 +98,95 @@
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeSASLMechanismHandler(
                   DigestMD5SASLMechanismHandlerCfg configuration)
         throws ConfigException, InitializationException
  {
  throws ConfigException, InitializationException {
    configuration.addDigestMD5ChangeListener(this);
    currentConfig = configuration;
    // Initialize the variables needed for the MD5 digest creation.
    digestLock      = new Object();
    randomGenerator = new SecureRandom();
    try
    {
      md5Digest = MessageDigest.getInstance("MD5");
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message = ERR_SASLDIGESTMD5_CANNOT_GET_MESSAGE_DIGEST.get(
          getExceptionMessage(e));
      throw new InitializationException(message, e);
    }
    // Get the identity mapper that should be used to find users.
      configEntryDN = configuration.dn();
      try {
    DN identityMapperDN = configuration.getIdentityMapperDN();
    identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
         serverFQDN = getFQDN(configuration);
         Message msg= INFO_DIGEST_MD5_SERVER_FQDN.get(serverFQDN);
         logError(msg);
         String QOP = getQOP(configuration);
         saslProps = new HashMap<String,String>();
         saslProps.put(Sasl.QOP, QOP);
         if(QOP.equalsIgnoreCase(SASL_MECHANISM_CONFIDENTIALITY)) {
             saslProps.put(Sasl.STRENGTH, getStrength(configuration));
         }
         String realm=getRealm(configuration);
         if(realm != null) {
           msg = INFO_DIGEST_MD5_REALM.get(realm);
           logError(msg);
           saslProps.put(REALM_PROPERTY, getRealm(configuration));
         }
         this.configuration = configuration;
    DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5,
                                                 this);
      } catch (UnknownHostException unhe) {
          if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, unhe);
  }
          Message message = ERR_SASL_CANNOT_GET_SERVER_FQDN.get(
                  String.valueOf(configEntryDN), getExceptionMessage(unhe));
          throw new InitializationException(message, unhe);
      }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizeSASLMechanismHandler()
  {
    currentConfig.removeDigestMD5ChangeListener(this);
  public void finalizeSASLMechanismHandler() {
    configuration.removeDigestMD5ChangeListener(this);
    DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSASLBind(BindOperation bindOperation)
  {
    DigestMD5SASLMechanismHandlerCfg config = currentConfig;
    IdentityMapper<?> identityMapper = this.identityMapper;
    String realm = config.getRealm();
    // The DIGEST-MD5 bind process uses two stages.  See if we have any state
    // information from the first stage to determine whether this is a
    // continuation of an existing bind or an initial authentication.  Note that
    // this implementation does not support subsequent authentication, so even
    // if the client provided credentials for the bind, it will be treated as an
    // initial authentication if there is no existing state.
    boolean initialAuth = true;
    ClientConnection clientConnection  = bindOperation.getClientConnection();
    Object saslStateInfo = clientConnection.getSASLAuthStateInfo();
    if ((saslStateInfo != null) &&
        (saslStateInfo instanceof DigestMD5StateInfo))
    {
      initialAuth = false;
    }
    if (initialAuth)
    {
      // Create a buffer to hold the challenge.
      StringBuilder challengeBuffer = new StringBuilder();
      // Add the realm to the challenge.  If we have a configured realm, then
      // use it.  Otherwise, add a realm for each suffix defined in the server.
      if (realm == null)
      {
        Map<DN,Backend> suffixes = DirectoryServer.getPublicNamingContexts();
        if (! suffixes.isEmpty())
        {
          Iterator<DN> iterator = suffixes.keySet().iterator();
          challengeBuffer.append("realm=\"");
          challengeBuffer.append(iterator.next().toNormalizedString());
          challengeBuffer.append("\"");
          while (iterator.hasNext())
          {
            challengeBuffer.append(",realm=\"");
            challengeBuffer.append(iterator.next().toNormalizedString());
            challengeBuffer.append("\"");
          }
        }
      }
      else
      {
        challengeBuffer.append("realm=\"");
        challengeBuffer.append(realm);
        challengeBuffer.append("\"");
      }
      // Generate the nonce.  Add it to the challenge and remember it for future
      // use.
      String nonce = generateNonce();
      if (challengeBuffer.length() > 0)
      {
        challengeBuffer.append(",");
      }
      challengeBuffer.append("nonce=\"");
      challengeBuffer.append(nonce);
      challengeBuffer.append("\"");
      // Generate the qop-list and add it to the challenge.
      // FIXME -- Add support for integrity and confidentiality.  Once we do,
      //          we'll also want to add the maxbuf and cipher options.
      challengeBuffer.append(",qop=\"auth\"");
      // Add the charset option to indicate that we support UTF-8 values.
      challengeBuffer.append(",charset=utf-8");
      // Add the algorithm, which will always be "md5-sess".
      challengeBuffer.append(",algorithm=md5-sess");
      // Encode the challenge as an ASN.1 element.  The total length of the
      // encoded value must be less than 2048 bytes, which should not be a
      // problem, but we'll add a safety check just in case....  In the event
      // that it does happen, we'll also log an error so it is more noticeable.
      ASN1OctetString challenge =
           new ASN1OctetString(challengeBuffer.toString());
      if (challenge.value().length >= 2048)
      {
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = WARN_SASLDIGESTMD5_CHALLENGE_TOO_LONG.get(
                challenge.value().length);
        bindOperation.setAuthFailureReason(message);
        logError(message);
  public void processSASLBind(BindOperation bindOp) {
      ClientConnection clientConnection = bindOp.getClientConnection();
      if (clientConnection == null) {
          Message message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get();
          bindOp.setAuthFailureReason(message);
          bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
        return;
      }
      // Store the state information with the client connection so we can use it
      // for later validation.
      DigestMD5StateInfo stateInfo = new DigestMD5StateInfo(nonce, "00000000");
      clientConnection.setSASLAuthStateInfo(stateInfo);
      // Prepare the response and return so it will be sent to the client.
      bindOperation.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
      bindOperation.setServerSASLCredentials(challenge);
      ClientConnection clientConn  = bindOp.getClientConnection();
      SASLContext saslContext =
         (SASLContext) clientConn.getSASLAuthStateInfo();
      if(saslContext == null) {
          try {
              saslContext = SASLContext.createSASLContext(saslProps, serverFQDN,
                            SASL_MECHANISM_DIGEST_MD5, identityMapper);
          } catch (SaslException ex) {
              if (debugEnabled()) {
                  TRACER.debugCaught(DebugLogLevel.ERROR, ex);
              }
              Message msg =
                  ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_DIGEST_MD5,
                                                    getExceptionMessage(ex));
              clientConn.setSASLAuthStateInfo(null);
              bindOp.setAuthFailureReason(msg);
              bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
      return;
    }
    // If we've gotten here, then we have existing SASL state information for
    // this client.  Make sure that the client also provided credentials.
    ASN1OctetString clientCredentials = bindOperation.getSASLCredentials();
    if ((clientCredentials == null) || (clientCredentials.value().length == 0))
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message = ERR_SASLDIGESTMD5_NO_CREDENTIALS.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }
    // Parse the SASL state information.  Also, since there are only ever two
    // stages of a DIGEST-MD5 bind, clear the SASL state information stored in
    // the client connection because it shouldn't be used anymore regardless of
    // whether the bind succeeds or fails.  Note that if we do add support for
    // subsequent authentication in the future, then we will probably need to
    // keep state information in the client connection, but even then it will
    // be different from what's already there.
    DigestMD5StateInfo stateInfo = (DigestMD5StateInfo) saslStateInfo;
    clientConnection.setSASLAuthStateInfo(null);
    // Create variables to hold values stored in the client's response.  We'll
    // also store the base DN because we might need to override it later.
    String responseUserName      = null;
    String responseRealm         = null;
    String responseNonce         = null;
    String responseCNonce        = null;
    int    responseNonceCount    = -1;
    String responseNonceCountStr = null;
    String responseQoP           = "auth";
    String responseDigestURI     = null;
    byte[] responseDigest        = null;
    String responseCharset       = "ISO-8859-1";
    String responseAuthzID       = null;
    // Get a temporary string representation of the SASL credentials using the
    // ISO-8859-1 encoding and see if it contains "charset=utf-8".  If so, then
    // re-parse the credentials using that character set.
    byte[] credBytes  = clientCredentials.value();
    String credString = null;
    String lowerCreds = null;
    try
    {
      credString = new String(credBytes, responseCharset);
      lowerCreds = toLowerCase(credString);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      // This isn't necessarily fatal because we're going to retry using UTF-8,
      // but we want to log it anyway.
      logError(WARN_SASLDIGESTMD5_CANNOT_PARSE_ISO_CREDENTIALS.get(
          responseCharset, getExceptionMessage(e)));
    }
    if ((credString == null) ||
        (lowerCreds.indexOf("charset=utf-8") >= 0))
    {
      try
      {
        credString = new String(credBytes, "UTF-8");
        lowerCreds = toLowerCase(credString);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        // This is fatal because either we can't parse the credentials as a
        // string at all, or we know we need to do so using UTF-8 and can't.
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = WARN_SASLDIGESTMD5_CANNOT_PARSE_UTF8_CREDENTIALS.get(
                getExceptionMessage(e));
        bindOperation.setAuthFailureReason(message);
        return;
      }
    }
    // Iterate through the credentials string, parsing the property names and
    // their corresponding values.
    int pos    = 0;
    int length = credString.length();
    while (pos < length)
    {
      int equalPos = credString.indexOf('=', pos+1);
      if (equalPos < 0)
      {
        // This is bad because we're not at the end of the string but we don't
        // have a name/value delimiter.
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_SASLDIGESTMD5_INVALID_TOKEN_IN_CREDENTIALS.get(
                credString, pos);
        bindOperation.setAuthFailureReason(message);
        return;
      }
      String tokenName  = lowerCreds.substring(pos, equalPos);
      String tokenValue;
      try
      {
        StringBuilder valueBuffer = new StringBuilder();
        pos = readToken(credString, equalPos+1, length, valueBuffer);
        tokenValue = valueBuffer.toString();
      }
      catch (DirectoryException de)
      {
        // We couldn't parse the token value, so it must be malformed.
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        bindOperation.setAuthFailureReason(
                de.getMessageObject());
        return;
      }
      if (tokenName.equals("charset"))
      {
        // The value must be the string "utf-8".  If not, that's an error.
        if (! tokenValue.equalsIgnoreCase("utf-8"))
        {
          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          Message message = ERR_SASLDIGESTMD5_INVALID_CHARSET.get(tokenValue);
          bindOperation.setAuthFailureReason(message);
          return;
        }
      }
      else if (tokenName.equals("username"))
      {
        responseUserName = tokenValue;
      }
      else if (tokenName.equals("realm"))
      {
        responseRealm = tokenValue;
        if (realm != null)
        {
          if (! responseRealm.equals(realm))
          {
            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
            Message message =
                    ERR_SASLDIGESTMD5_INVALID_REALM.get(responseRealm);
            bindOperation.setAuthFailureReason(message);
            return;
          }
        }
      }
      else if (tokenName.equals("nonce"))
      {
        responseNonce = tokenValue;
        String requestNonce = stateInfo.getNonce();
        if (! responseNonce.equals(requestNonce))
        {
          // The nonce provided by the client is incorrect.  This could be an
          // attempt at a replay or chosen plaintext attack, so we'll close the
          // connection.  We will put a message in the log but will not send it
          // to the client.
          Message message = ERR_SASLDIGESTMD5_INVALID_NONCE.get();
          clientConnection.disconnect(DisconnectReason.SECURITY_PROBLEM, false,
                  message);
          return;
        }
      }
      else if (tokenName.equals("cnonce"))
      {
        responseCNonce = tokenValue;
      }
      else if (tokenName.equals("nc"))
      {
        try
        {
          responseNonceCountStr = tokenValue;
          responseNonceCount    = Integer.parseInt(responseNonceCountStr, 16);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          Message message = ERR_SASLDIGESTMD5_CANNOT_DECODE_NONCE_COUNT.get(
                  tokenValue);
          bindOperation.setAuthFailureReason(message);
          return;
        }
        int storedNonce;
        try
        {
          storedNonce = Integer.parseInt(stateInfo.getNonceCount(), 16);
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          Message message =
                  ERR_SASLDIGESTMD5_CANNOT_DECODE_STORED_NONCE_COUNT.get(
                          getExceptionMessage(e));
          bindOperation.setAuthFailureReason(message);
          return;
        }
        if (responseNonceCount != (storedNonce + 1))
        {
          // The nonce count provided by the client is incorrect.  This
          // indicates a replay attack, so we'll close the connection.  We will
          // put a message in the log but we will not send it to the client.
          Message message = ERR_SASLDIGESTMD5_INVALID_NONCE_COUNT.get();
          clientConnection.disconnect(DisconnectReason.SECURITY_PROBLEM, false,
                  message);
          return;
        }
      }
      else if (tokenName.equals("qop"))
      {
        responseQoP = tokenValue;
        if (responseQoP.equals("auth"))
        {
          // No action necessary.
        }
        else if (responseQoP.equals("auth-int"))
        {
          // FIXME -- Add support for integrity protection.
          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          Message message = ERR_SASLDIGESTMD5_INTEGRITY_NOT_SUPPORTED.get();
          bindOperation.setAuthFailureReason(message);
          return;
        }
        else if (responseQoP.equals("auth-conf"))
        {
          // FIXME -- Add support for confidentiality protection.
          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          Message message =
                  ERR_SASLDIGESTMD5_CONFIDENTIALITY_NOT_SUPPORTED.get();
          bindOperation.setAuthFailureReason(message);
          return;
        }
        else
        {
          // This is an invalid QoP value.
          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          Message message = ERR_SASLDIGESTMD5_INVALID_QOP.get(responseQoP);
          bindOperation.setAuthFailureReason(message);
          return;
        }
      }
      else if (tokenName.equals("digest-uri"))
      {
        responseDigestURI = tokenValue;
        String serverFQDN = config.getServerFqdn();
        if ((serverFQDN != null) && (serverFQDN.length() > 0))
        {
          // If a server FQDN is populated, then we'll use it to validate the
          // digest-uri, which should be in the form "ldap/serverfqdn".
          String expectedDigestURI = "ldap/" + serverFQDN;
          if (! expectedDigestURI.equalsIgnoreCase(responseDigestURI))
          {
            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
            Message message = ERR_SASLDIGESTMD5_INVALID_DIGEST_URI.get(
                    responseDigestURI, expectedDigestURI);
            bindOperation.setAuthFailureReason(message);
            return;
          }
        }
      }
      else if (tokenName.equals("response"))
      {
        try
        {
          responseDigest = hexStringToByteArray(tokenValue);
        }
        catch (ParseException pe)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, pe);
          }
          Message message =
                  ERR_SASLDIGESTMD5_CANNOT_PARSE_RESPONSE_DIGEST.get(
                          getExceptionMessage(pe));
          bindOperation.setAuthFailureReason(message);
          return;
        }
      }
      else if (tokenName.equals("authzid"))
      {
        responseAuthzID = tokenValue;
        // FIXME -- This must always be parsed in UTF-8 even if the charset for
        // other elements is ISO 8859-1.
      }
      else if (tokenName.equals("maxbuf") || tokenName.equals("cipher"))
      {
        // FIXME -- Add support for confidentiality and integrity protection.
      }
      else
      {
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_SASLDIGESTMD5_INVALID_RESPONSE_TOKEN.get(
                tokenName);
        bindOperation.setAuthFailureReason(message);
        return;
      }
    }
    // Make sure that all required properties have been specified.
    if ((responseUserName == null) || (responseUserName.length() == 0))
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message = ERR_SASLDIGESTMD5_NO_USERNAME_IN_RESPONSE.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }
    else if (responseNonce == null)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message = ERR_SASLDIGESTMD5_NO_NONCE_IN_RESPONSE.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }
    else if (responseCNonce == null)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message = ERR_SASLDIGESTMD5_NO_CNONCE_IN_RESPONSE.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }
    else if (responseNonceCount < 0)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message = ERR_SASLDIGESTMD5_NO_NONCE_COUNT_IN_RESPONSE.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }
    else if (responseDigest == null)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message = ERR_SASLDIGESTMD5_NO_DIGEST_IN_RESPONSE.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }
    // Slight departure from draft-ietf-sasl-rfc2831bis-06 in order to
    // support legacy/broken client implementations, such as Solaris
    // Native LDAP Client, which omit digest-uri directive. the presence
    // of digest-uri directive erroneously read "may" in the RFC and has
    // been fixed later in the DRAFT to read "must". if the client does
    // not include digest-uri directive use the empty string instead.
    if (responseDigestURI == null)
    {
      responseDigestURI = "";
    }
    // If a realm has not been specified, then use the empty string.
    // FIXME -- Should we reject this if a specific realm is defined?
    if (responseRealm == null)
    {
      responseRealm = "";
    }
    // Get the user entry for the authentication ID.  Allow for an
    // authentication ID that is just a username (as per the DIGEST-MD5 spec),
    // but also allow a value in the authzid form specified in RFC 2829.
    Entry  userEntry    = null;
    String lowerUserName = toLowerCase(responseUserName);
    if (lowerUserName.startsWith("dn:"))
    {
      // Try to decode the user DN and retrieve the corresponding entry.
      DN userDN;
      try
      {
        userDN = DN.decode(responseUserName.substring(3));
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_SASLDIGESTMD5_CANNOT_DECODE_USERNAME_AS_DN.get(
                responseUserName, de.getMessageObject());
        bindOperation.setAuthFailureReason(message);
        return;
      }
      if (userDN.isNullDN())
      {
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_SASLDIGESTMD5_USERNAME_IS_NULL_DN.get();
        bindOperation.setAuthFailureReason(message);
        return;
      }
      DN rootDN = DirectoryServer.getActualRootBindDN(userDN);
      if (rootDN != null)
      {
        userDN = rootDN;
      }
      // Acquire a read lock on the user entry.  If this fails, then so will the
      // authentication.
      Lock readLock = null;
      for (int i=0; i < 3; i++)
      {
        readLock = LockManager.lockRead(userDN);
        if (readLock != null)
        {
          break;
        }
      }
      if (readLock == null)
      {
        bindOperation.setResultCode(DirectoryServer.getServerErrorResultCode());
        Message message = INFO_SASLDIGESTMD5_CANNOT_LOCK_ENTRY.get(
                String.valueOf(userDN));
        bindOperation.setAuthFailureReason(message);
        return;
      }
      try
      {
        userEntry = DirectoryServer.getEntry(userDN);
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_SASLDIGESTMD5_CANNOT_GET_ENTRY_BY_DN.get(
                String.valueOf(userDN), de.getMessageObject());
        bindOperation.setAuthFailureReason(message);
        return;
      }
      finally
      {
        LockManager.unlock(userDN, readLock);
      }
    }
    else
    {
      // Use the identity mapper to resolve the username to an entry.
      String userName = responseUserName;
      if (lowerUserName.startsWith("u:"))
      {
        if (lowerUserName.equals("u:"))
        {
          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          Message message = ERR_SASLDIGESTMD5_ZERO_LENGTH_USERNAME.get();
          bindOperation.setAuthFailureReason(message);
          return;
        }
        userName = responseUserName.substring(2);
      }
      try
      {
        userEntry = identityMapper.getEntryForID(userName);
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_SASLDIGESTMD5_CANNOT_MAP_USERNAME.get(
                String.valueOf(responseUserName), de.getMessageObject());
        bindOperation.setAuthFailureReason(message);
        return;
      }
    }
    // At this point, we should have a user entry.  If we don't then fail.
    if (userEntry == null)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message =
              ERR_SASLDIGESTMD5_NO_MATCHING_ENTRIES.get(responseUserName);
      bindOperation.setAuthFailureReason(message);
      return;
    }
    else
    {
      bindOperation.setSASLAuthUserEntry(userEntry);
    }
    Entry authZEntry = userEntry;
    if (responseAuthzID != null)
    {
      if (responseAuthzID.length() == 0)
      {
        // The authorization ID must not be an empty string.
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_SASLDIGESTMD5_EMPTY_AUTHZID.get();
        bindOperation.setAuthFailureReason(message);
        return;
      }
      else if (! responseAuthzID.equals(responseUserName))
      {
        String lowerAuthzID = toLowerCase(responseAuthzID);
        if (lowerAuthzID.startsWith("dn:"))
        {
          DN authzDN;
          try
          {
            authzDN = DN.decode(responseAuthzID.substring(3));
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
            Message message = ERR_SASLDIGESTMD5_AUTHZID_INVALID_DN.get(
                    responseAuthzID, de.getMessageObject());
            bindOperation.setAuthFailureReason(message);
            return;
          }
          DN actualAuthzDN = DirectoryServer.getActualRootBindDN(authzDN);
          if (actualAuthzDN != null)
          {
            authzDN = actualAuthzDN;
          }
          if (! authzDN.equals(userEntry.getDN()))
          {
            AuthenticationInfo tempAuthInfo =
              new AuthenticationInfo(userEntry,
                       DirectoryServer.isRootDN(userEntry.getDN()));
            InternalClientConnection tempConn =
                 new InternalClientConnection(tempAuthInfo);
            if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOperation))
            {
              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
              Message message =
                      ERR_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES.get(
                              String.valueOf(userEntry.getDN()));
              bindOperation.setAuthFailureReason(message);
              return;
            }
            if (authzDN.isNullDN())
            {
              authZEntry = null;
            }
            else
            {
              try
              {
                authZEntry = DirectoryServer.getEntry(authzDN);
                if (authZEntry == null)
                {
                  bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
                  Message message = ERR_SASLDIGESTMD5_AUTHZID_NO_SUCH_ENTRY.get(
                          String.valueOf(authzDN));
                  bindOperation.setAuthFailureReason(message);
                  return;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
                Message message = ERR_SASLDIGESTMD5_AUTHZID_CANNOT_GET_ENTRY
                        .get(String.valueOf(authzDN), de.getMessageObject());
                bindOperation.setAuthFailureReason(message);
                return;
              }
            }
          }
        }
        else
        {
          String idStr;
          if (lowerAuthzID.startsWith("u:"))
          {
            idStr = responseAuthzID.substring(2);
          }
          else
          {
            idStr = responseAuthzID;
          }
          if (idStr.length() == 0)
          {
            authZEntry = null;
          }
          else
          {
            try
            {
              authZEntry = identityMapper.getEntryForID(idStr);
              if (authZEntry == null)
              {
                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
                Message message = ERR_SASLDIGESTMD5_AUTHZID_NO_MAPPED_ENTRY.get(
                        responseAuthzID);
                bindOperation.setAuthFailureReason(message);
                return;
              }
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
              Message message = ERR_SASLDIGESTMD5_CANNOT_MAP_AUTHZID.get(
                      responseAuthzID, de.getMessageObject());
              bindOperation.setAuthFailureReason(message);
              return;
            }
          }
          if ((authZEntry == null) ||
              (! authZEntry.getDN().equals(userEntry.getDN())))
          {
            AuthenticationInfo tempAuthInfo =
              new AuthenticationInfo(userEntry,
                       DirectoryServer.isRootDN(userEntry.getDN()));
            InternalClientConnection tempConn =
                 new InternalClientConnection(tempAuthInfo);
            if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOperation))
            {
              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
              Message message =
                      ERR_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES.get(
                              String.valueOf(userEntry.getDN()));
              bindOperation.setAuthFailureReason(message);
              return;
            }
          }
        }
      }
    }
    // Get the clear-text passwords from the user entry, if there are any.
    List<ByteString> clearPasswords;
    try
    {
      PasswordPolicyState pwPolicyState =
           new PasswordPolicyState(userEntry, false);
      clearPasswords = pwPolicyState.getClearPasswords();
      if ((clearPasswords == null) || clearPasswords.isEmpty())
      {
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_SASLDIGESTMD5_NO_REVERSIBLE_PASSWORDS.get(
                String.valueOf(userEntry.getDN()));
        bindOperation.setAuthFailureReason(message);
        return;
      }
    }
    catch (Exception e)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message = ERR_SASLDIGESTMD5_CANNOT_GET_REVERSIBLE_PASSWORDS.get(
              String.valueOf(userEntry.getDN()),
              String.valueOf(e));
      bindOperation.setAuthFailureReason(message);
      return;
    }
    // Iterate through the clear-text values and see if any of them can be used
    // in conjunction with the challenge to construct the provided digest.
    boolean matchFound    = false;
    byte[]  passwordBytes = null;
    for (ByteString clearPassword : clearPasswords)
    {
      byte[] generatedDigest;
      try
      {
        generatedDigest =
             generateResponseDigest(responseUserName, responseAuthzID,
                                    clearPassword.value(), responseRealm,
                                    responseNonce, responseCNonce,
                                    responseNonceCountStr, responseDigestURI,
                                    responseQoP, responseCharset);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        logError(WARN_SASLDIGESTMD5_CANNOT_GENERATE_RESPONSE_DIGEST.get(
            getExceptionMessage(e)));
        continue;
      }
      if (Arrays.equals(responseDigest, generatedDigest))
      {
        matchFound    = true;
        passwordBytes = clearPassword.value();
        break;
      }
    }
    if (! matchFound)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message = ERR_SASLDIGESTMD5_INVALID_CREDENTIALS.get();
      bindOperation.setAuthFailureReason(message);
      return;
    }
    // Generate the response auth element to include in the response to the
    // client.
    byte[] responseAuth;
    try
    {
      responseAuth =
           generateResponseAuthDigest(responseUserName, responseAuthzID,
                                      passwordBytes, responseRealm,
                                      responseNonce, responseCNonce,
                                      responseNonceCountStr, responseDigestURI,
                                      responseQoP, responseCharset);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      Message message =
              ERR_SASLDIGESTMD5_CANNOT_GENERATE_RESPONSE_AUTH_DIGEST.get(
                      getExceptionMessage(e));
      bindOperation.setAuthFailureReason(message);
      return;
    }
    ASN1OctetString responseAuthStr =
         new ASN1OctetString("rspauth=" + getHexString(responseAuth));
    // Make sure to store the updated nonce count with the client connection to
    // allow for correct subsequent authentication.
    stateInfo.setNonceCount(responseNonceCountStr);
    // If we've gotten here, then the authentication was successful.  We'll also
    // need to include the response auth string in the server SASL credentials.
    bindOperation.setResultCode(ResultCode.SUCCESS);
    bindOperation.setServerSASLCredentials(responseAuthStr);
    AuthenticationInfo authInfo =
         new AuthenticationInfo(userEntry, authZEntry,
                                SASL_MECHANISM_DIGEST_MD5,
                                DirectoryServer.isRootDN(userEntry.getDN()));
    bindOperation.setAuthenticationInfo(authInfo);
    return;
  }
  /**
   * Generates a new nonce value to use during the DIGEST-MD5 authentication
   * process.
   *
   * @return  The nonce that should be used for DIGEST-MD5 authentication.
   */
  private String generateNonce()
  {
    byte[] nonceBytes = new byte[16];
    randomGenerator.nextBytes(nonceBytes);
    return Base64.encode(nonceBytes);
  }
  /**
   * Reads the next token from the provided credentials string using the
   * provided information.  If the token is surrounded by quotation marks, then
   * the token returned will not include those quotation marks.
   *
   * @param  credentials  The credentials string from which to read the token.
   * @param  startPos     The position of the first character of the token to
   *                      read.
   * @param  length       The total number of characters in the credentials
   *                      string.
   * @param  token        The buffer into which the token is to be placed.
   *
   * @return  The position at which the next token should start, or a value
   *          greater than or equal to the length of the string if there are no
   *          more tokens.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to read
   *                              the token.
   */
  private int readToken(String credentials, int startPos, int length,
                        StringBuilder token)
          throws DirectoryException
  {
    // If the position is greater than or equal to the length, then we shouldn't
    // do anything.
    if (startPos >= length)
    {
      return startPos;
    }
    // Look at the first character to see if it's an empty string or the string
    // is quoted.
    boolean isEscaped = false;
    boolean isQuoted  = false;
    int     pos       = startPos;
    char    c         = credentials.charAt(pos++);
    if (c == ',')
    {
      // This must be a zero-length token, so we'll just return the next
      // position.
      return pos;
    }
    else if (c == '"')
    {
      // The string is quoted, so we'll ignore this character, and we'll keep
      // reading until we find the unescaped closing quote followed by a comma
      // or the end of the string.
      isQuoted = true;
    }
    else if (c == '\\')
    {
      // The next character is escaped, so we'll take it no matter what.
      isEscaped = true;
    }
    else
    {
      // The string is not quoted, and this is the first character.  Store this
      // character and keep reading until we find a comma or the end of the
      // string.
      token.append(c);
    }
    // Enter a loop, reading until we find the appropriate criteria for the end
    // of the token.
    while (pos < length)
    {
      c = credentials.charAt(pos++);
      if (isEscaped)
      {
        // The previous character was an escape, so we'll take this no matter
        // what.
        token.append(c);
        isEscaped = false;
      }
      else if (c == ',')
      {
        // If this is a quoted string, then this comma is part of the token.
        // Otherwise, it's the end of the token.
        if (isQuoted)
        {
          token.append(c);
        }
        else
        {
          break;
        }
      }
      else if (c == '"')
      {
        if (isQuoted)
        {
          // This should be the end of the token, but in order for it to be
          // valid it must be followed by a comma or the end of the string.
          if (pos >= length)
          {
            // We have hit the end of the string, so this is fine.
            break;
          }
          else
          {
            char c2 = credentials.charAt(pos++);
            if (c2 == ',')
            {
              // We have hit the end of the token, so this is fine.
              break;
            }
            else
            {
              // We found the closing quote before the end of the token.  This
              // is not fine.
              Message message =
                  ERR_SASLDIGESTMD5_INVALID_CLOSING_QUOTE_POS.get((pos-2));
              throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
                                           message);
            }
          }
        }
        else
        {
          // This must be part of the value, so we'll take it.
          token.append(c);
        }
      }
      else if (c == '\\')
      {
        // The next character is escaped.  We'll set a flag so we know to
        // accept it, but will not include the backspace itself.
        isEscaped = true;
      }
      else
      {
        token.append(c);
      }
    }
    return pos;
  }
  /**
   * Generates the appropriate DIGEST-MD5 response for the provided set of
   * information.
   *
   * @param  userName    The username from the authentication request.
   * @param  authzID     The authorization ID from the request, or
   *                     <CODE>null</CODE> if there is none.
   * @param  password    The clear-text password for the user.
   * @param  realm       The realm for which the authentication is to be
   *                     performed.
   * @param  nonce       The random data generated by the server for use in the
   *                     digest.
   * @param  cnonce      The random data generated by the client for use in the
   *                     digest.
   * @param  nonceCount  The 8-digit hex string indicating the number of times
   *                     the provided nonce has been used by the client.
   * @param  digestURI   The digest URI that specifies the service and host for
   *                     which the authentication is being performed.
   * @param  qop         The quality of protection string for the
   *                     authentication.
   * @param  charset     The character set used to encode the information.
   *
   * @return  The DIGEST-MD5 response for the provided set of information.
   *
   * @throws  UnsupportedEncodingException  If the specified character set is
   *                                        invalid for some reason.
   */
  public byte[] generateResponseDigest(String userName, String authzID,
                                       byte[] password, String realm,
                                       String nonce, String cnonce,
                                       String nonceCount, String digestURI,
                                       String qop, String charset)
         throws UnsupportedEncodingException
  {
    synchronized (digestLock)
    {
      // First, get a hash of "username:realm:password".
      StringBuilder a1String1 = new StringBuilder();
      a1String1.append(userName);
      a1String1.append(':');
      a1String1.append(realm);
      a1String1.append(':');
      byte[] a1Bytes1a = a1String1.toString().getBytes(charset);
      byte[] a1Bytes1  = new byte[a1Bytes1a.length + password.length];
      System.arraycopy(a1Bytes1a, 0, a1Bytes1, 0, a1Bytes1a.length);
      System.arraycopy(password, 0, a1Bytes1, a1Bytes1a.length,
                       password.length);
      byte[] urpHash = md5Digest.digest(a1Bytes1);
      // Next, get a hash of "urpHash:nonce:cnonce[:authzid]".
      StringBuilder a1String2 = new StringBuilder();
      a1String2.append(':');
      a1String2.append(nonce);
      a1String2.append(':');
      a1String2.append(cnonce);
      if (authzID != null)
      {
        a1String2.append(':');
        a1String2.append(authzID);
      }
      byte[] a1Bytes2a = a1String2.toString().getBytes(charset);
      byte[] a1Bytes2  = new byte[urpHash.length + a1Bytes2a.length];
      System.arraycopy(urpHash, 0, a1Bytes2, 0, urpHash.length);
      System.arraycopy(a1Bytes2a, 0, a1Bytes2, urpHash.length,
                       a1Bytes2a.length);
      byte[] a1Hash = md5Digest.digest(a1Bytes2);
      // Next, get a hash of "AUTHENTICATE:digesturi".
      byte[] a2Bytes = ("AUTHENTICATE:" + digestURI).getBytes(charset);
      byte[] a2Hash  = md5Digest.digest(a2Bytes);
      // Get hex string representations of the last two hashes.
      String a1HashHex = getHexString(a1Hash);
      String a2HashHex = getHexString(a2Hash);
      // Put together the final string to hash, consisting of
      // "a1HashHex:nonce:nonceCount:cnonce:qop:a2HashHex" and get its digest.
      StringBuilder kdString = new StringBuilder();
      kdString.append(a1HashHex);
      kdString.append(':');
      kdString.append(nonce);
      kdString.append(':');
      kdString.append(nonceCount);
      kdString.append(':');
      kdString.append(cnonce);
      kdString.append(':');
      kdString.append(qop);
      kdString.append(':');
      kdString.append(a2HashHex);
      return md5Digest.digest(kdString.toString().getBytes(charset));
    }
  }
  /**
   * Generates the appropriate DIGEST-MD5 rspauth digest using the provided
   * information.
   *
   * @param  userName    The username from the authentication request.
   * @param  authzID     The authorization ID from the request, or
   *                     <CODE>null</CODE> if there is none.
   * @param  password    The clear-text password for the user.
   * @param  realm       The realm for which the authentication is to be
   *                     performed.
   * @param  nonce       The random data generated by the server for use in the
   *                     digest.
   * @param  cnonce      The random data generated by the client for use in the
   *                     digest.
   * @param  nonceCount  The 8-digit hex string indicating the number of times
   *                     the provided nonce has been used by the client.
   * @param  digestURI   The digest URI that specifies the service and host for
   *                     which the authentication is being performed.
   * @param  qop         The quality of protection string for the
   *                     authentication.
   * @param  charset     The character set used to encode the information.
   *
   * @return  The DIGEST-MD5 response for the provided set of information.
   *
   * @throws  UnsupportedEncodingException  If the specified character set is
   *                                        invalid for some reason.
   */
  public byte[] generateResponseAuthDigest(String userName, String authzID,
                                           byte[] password, String realm,
                                           String nonce, String cnonce,
                                           String nonceCount, String digestURI,
                                           String qop, String charset)
         throws UnsupportedEncodingException
  {
    synchronized (digestLock)
    {
      // First, get a hash of "username:realm:password".
      StringBuilder a1String1 = new StringBuilder();
      a1String1.append(userName);
      a1String1.append(':');
      a1String1.append(realm);
      a1String1.append(':');
      byte[] a1Bytes1a = a1String1.toString().getBytes(charset);
      byte[] a1Bytes1  = new byte[a1Bytes1a.length + password.length];
      System.arraycopy(a1Bytes1a, 0, a1Bytes1, 0, a1Bytes1a.length);
      System.arraycopy(password, 0, a1Bytes1, a1Bytes1a.length,
                       password.length);
      byte[] urpHash = md5Digest.digest(a1Bytes1);
      // Next, get a hash of "urpHash:nonce:cnonce[:authzid]".
      StringBuilder a1String2 = new StringBuilder();
      a1String2.append(':');
      a1String2.append(nonce);
      a1String2.append(':');
      a1String2.append(cnonce);
      if (authzID != null)
      {
        a1String2.append(':');
        a1String2.append(authzID);
      }
      byte[] a1Bytes2a = a1String2.toString().getBytes(charset);
      byte[] a1Bytes2  = new byte[urpHash.length + a1Bytes2a.length];
      System.arraycopy(urpHash, 0, a1Bytes2, 0, urpHash.length);
      System.arraycopy(a1Bytes2a, 0, a1Bytes2, urpHash.length,
                       a1Bytes2a.length);
      byte[] a1Hash = md5Digest.digest(a1Bytes2);
      // Next, get a hash of "AUTHENTICATE:digesturi".
      String a2String = ":" + digestURI;
      if (qop.equals("auth-int") || qop.equals("auth-conf"))
      {
        a2String += ":00000000000000000000000000000000";
      }
      byte[] a2Bytes = a2String.getBytes(charset);
      byte[] a2Hash  = md5Digest.digest(a2Bytes);
      // Get hex string representations of the last two hashes.
      String a1HashHex = getHexString(a1Hash);
      String a2HashHex = getHexString(a2Hash);
      // Put together the final string to hash, consisting of
      // "a1HashHex:nonce:nonceCount:cnonce:qop:a2HashHex" and get its digest.
      StringBuilder kdString = new StringBuilder();
      kdString.append(a1HashHex);
      kdString.append(':');
      kdString.append(nonce);
      kdString.append(':');
      kdString.append(nonceCount);
      kdString.append(':');
      kdString.append(cnonce);
      kdString.append(':');
      kdString.append(qop);
      kdString.append(':');
      kdString.append(a2HashHex);
      return md5Digest.digest(kdString.toString().getBytes(charset));
    }
  }
  /**
   * Retrieves a hexadecimal string representation of the contents of the
   * provided byte array.
   *
   * @param  byteArray  The byte array for which to obtain the hexadecimal
   *                    string representation.
   *
   * @return  The hexadecimal string representation of the contents of the
   *          provided byte array.
   */
  private String getHexString(byte[] byteArray)
  {
    StringBuilder buffer = new StringBuilder(2*byteArray.length);
    for (byte b : byteArray)
    {
      buffer.append(byteToLowerHex(b));
          saslContext.evaluateInitialStage(bindOp);
      } else {
          saslContext.evaluateFinalStage(bindOp);
    }
    return buffer.toString();
  }
  /**
   * {@inheritDoc}
   */
@@ -1554,7 +210,6 @@
  }
  /**
   * {@inheritDoc}
   */
@@ -1569,7 +224,6 @@
  }
  /**
   * {@inheritDoc}
   */
@@ -1581,7 +235,6 @@
  }
  /**
   * {@inheritDoc}
   */
@@ -1590,14 +243,108 @@
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    // Get the identity mapper that should be used to find users.
      ArrayList<Message> messages            = new ArrayList<Message>();
      try {
    DN identityMapperDN = configuration.getIdentityMapperDN();
    identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
    currentConfig  = configuration;
          serverFQDN = getFQDN(configuration);
          Message msg = INFO_DIGEST_MD5_SERVER_FQDN.get(serverFQDN);
          logError(msg);
          String QOP = getQOP(configuration);
          saslProps = new HashMap<String,String>();
          saslProps.put(Sasl.QOP, QOP);
          if(QOP.equalsIgnoreCase(SASL_MECHANISM_CONFIDENTIALITY)) {
              saslProps.put(Sasl.STRENGTH, getStrength(configuration));
          }
          String realm=getRealm(configuration);
          if(realm != null) {
               msg = INFO_DIGEST_MD5_REALM.get(realm);
              logError(msg);
             saslProps.put(REALM_PROPERTY, getRealm(configuration));
          }
          this.configuration  = configuration;
      } catch (UnknownHostException unhe) {
          if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, unhe);
          }
          resultCode = ResultCode.OPERATIONS_ERROR;
          messages.add(ERR_SASL_CANNOT_GET_SERVER_FQDN.get(
                  String.valueOf(configEntryDN), getExceptionMessage(unhe)));
          return new ConfigChangeResult(resultCode,adminActionRequired,
                  messages);
      }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * Retrieves the cipher strength string to use if confidentiality is enforce.
   * This determination is the lowest value that the server can use.
   *
   * @param configuration The configuration to examine.
   * @return The cipher strength string.
   */
  private String
  getStrength(DigestMD5SASLMechanismHandlerCfg configuration) {
      CipherStrength strength = configuration.getCipherStrength();
      if(strength.equals(CipherStrength.HIGH)) {
          return "high";
      } else if(strength.equals(CipherStrength.MEDIUM)) {
          return "high,medium";
      } else {
          return "high,medium,low";
      }
}
  /**
   * Retrieves the QOP (quality-of-protection) from the specified
   * configuration.
   *
   * @param configuration The new configuration to use.
   * @return A string representing the quality-of-protection.
   */
  private String
  getQOP(DigestMD5SASLMechanismHandlerCfg configuration) {
      QualityOfProtection QOP = configuration.getQualityOfProtection();
      if(QOP.equals(QualityOfProtection.CONFIDENTIALITY))
          return "auth-conf";
      else if(QOP.equals(QualityOfProtection.INTEGRITY))
          return "auth-int";
      else
          return "auth";
  }
  /**
   * Returns the fully qualified name either defined in the configuration, or,
   * determined by examining the system configuration.
   *
   * @param configuration The configuration to check.
   * @return The fully qualified hostname of the server.
   *
   * @throws UnknownHostException If the name cannot be determined from the
   *                              system configuration.
   */
  private String getFQDN(DigestMD5SASLMechanismHandlerCfg configuration)
  throws UnknownHostException {
      String serverName = configuration.getServerFqdn();
      if (serverName == null) {
              serverName = InetAddress.getLocalHost().getCanonicalHostName();
      }
      return serverName;
  }
  /**
   * Retrieve the realm either defined in the specified configuration. If this
   * isn't defined, the SaslServer internal code uses the server name.
   *
   * @param configuration The configuration to check.
   * @return A string representing the realm.
   */
  private String getRealm(DigestMD5SASLMechanismHandlerCfg configuration) {
    return configuration.getRealm();
  }
}
opends/src/server/org/opends/server/extensions/DigestMD5StateInfo.java
File was deleted
opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java
@@ -32,11 +32,21 @@
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.GSSAPISASLMechanismHandlerCfgDefn.*;
import org.opends.server.admin.std.server.GSSAPISASLMechanismHandlerCfg;
import org.opends.server.admin.std.server.SASLMechanismHandlerCfg;
import org.opends.server.api.ClientConnection;
@@ -45,19 +55,17 @@
import org.opends.server.config.ConfigException;
import org.opends.server.core.BindOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -69,28 +77,30 @@
 */
public class GSSAPISASLMechanismHandler
       extends SASLMechanismHandler<GSSAPISASLMechanismHandlerCfg>
       implements ConfigurationChangeListener<
                       GSSAPISASLMechanismHandlerCfg>
{
  /**
   * The tracer object for the debug logger.
   */
       implements ConfigurationChangeListener< GSSAPISASLMechanismHandlerCfg>,
       CallbackHandler {
  //The tracer object for the debug logger.
  private static final DebugTracer TRACER = getTracer();
  // The DN of the configuration entry for this SASL mechanism handler.
  private DN configEntryDN;
  // The current configuration for this SASL mechanism handler.
  private GSSAPISASLMechanismHandlerCfg currentConfig;
  private GSSAPISASLMechanismHandlerCfg configuration;
  // The identity mapper that will be used to map the Kerberos principal to a
  // directory user.
  // The identity mapper that will be used to map identities.
  private IdentityMapper<?> identityMapper;
  // The fully-qualified domain name for the server system.
  //The properties to use when creating a SASL server to process the GSSAPI
  //authentication.
  private HashMap<String,String> saslProps;
  //The fully qualified domain name used when creating the SASL server.
  private String serverFQDN;
  //The login context used to perform server-side authentication.
  private LoginContext loginContext;
  /**
   * Creates a new instance of this SASL mechanism handler.  No initialization
@@ -103,219 +113,235 @@
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeSASLMechanismHandler(
                   GSSAPISASLMechanismHandlerCfg configuration)
         throws ConfigException, InitializationException
  {
  public void
  initializeSASLMechanismHandler(GSSAPISASLMechanismHandlerCfg configuration)
  throws ConfigException, InitializationException {
    configuration.addGSSAPIChangeListener(this);
    currentConfig = configuration;
      this.configuration = configuration;
    configEntryDN = configuration.dn();
    // Get the identity mapper that should be used to find users.
      try {
    DN identityMapperDN = configuration.getIdentityMapperDN();
    identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
    // Determine the fully-qualified hostname for this system.  It may be
    // provided, but if not, then try to determine it programmatically.
    serverFQDN = configuration.getServerFqdn();
    if (serverFQDN == null)
    {
      try
      {
        serverFQDN = InetAddress.getLocalHost().getCanonicalHostName();
          serverFQDN = getFQDN(configuration);
          Message msg= INFO_GSSAPI_SERVER_FQDN.get(serverFQDN);
          logError(msg);
          saslProps = new HashMap<String,String>();
          saslProps.put(Sasl.QOP, getQOP(configuration));
          saslProps.put(Sasl.REUSE, "false");
          String configFileName=configureLoginConfFile(configuration);
          System.setProperty(JAAS_PROPERTY_CONFIG_FILE, configFileName);
          System.setProperty(JAAS_PROPERTY_SUBJECT_CREDS_ONLY, "false");
          getKdcRealm(configuration);
          DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_GSSAPI,
                  this);
          login();
      } catch (UnknownHostException unhe) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, unhe);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          Message message = ERR_SASL_CANNOT_GET_SERVER_FQDN.get(
                  String.valueOf(configEntryDN), getExceptionMessage(unhe));
          throw new InitializationException(message, unhe);
      } catch(IOException ioe) {
          if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
            }
          Message message = ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(
                                                     getExceptionMessage(ioe));
          throw new InitializationException(message, ioe);
      } catch (LoginException le) {
          if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, le);
           }
          Message message = ERR_SASLGSSAPI_CANNOT_CREATE_LOGIN_CONTEXT.get(
                  getExceptionMessage(le));
          throw new InitializationException(message, le);
      }
  }
  /**
   * Checks to make sure that the ds-cfg-kdc-address and dc-cfg-realm are
   * both defined in the configuration. If only one is set, then that is an
   * error. If both are defined, or, both are null that is fine.
   *
   * @param configuration The configuration to use.
   * @throws InitializationException If the properties violate the requirements.
   */
  private void getKdcRealm(GSSAPISASLMechanismHandlerCfg configuration)
  throws InitializationException {
      String kdcAddress = configuration.getKdcAddress();
      String realm = configuration.getRealm();
      if((kdcAddress != null && realm == null) ||
         (kdcAddress == null && realm != null)) {
          Message message = ERR_SASLGSSAPI_KDC_REALM_NOT_DEFINED.get();
          throw new InitializationException(message);
      } else if(kdcAddress != null && realm != null) {
          System.setProperty(KRBV_PROPERTY_KDC, kdcAddress);
          System.setProperty(KRBV_PROPERTY_REALM, realm);
      }
  }
  /**
   * During login, callbacks are usually used to prompt for passwords. All of
   * the GSSAPI login information is provided in the properties and login.conf
   * file, so callbacks are ignored.
   *
   * @param callbacks An array of callbacks to process.
   * @throws UnsupportedCallbackException if an error occurs.
   */
  public void handle(Callback[] callbacks)
  throws UnsupportedCallbackException {
  }
  /**
   * Returns the fully qualified name either defined in the configuration, or,
   * determined by examining the system configuration.
   *
   * @param configuration The configuration to check.
   * @return The fully qualified hostname of the server.
   *
   * @throws UnknownHostException If the name cannot be determined from the
   *                              system configuration.
   */
  private String getFQDN(GSSAPISASLMechanismHandlerCfg configuration)
  throws UnknownHostException {
      String serverName = configuration.getServerFqdn();
      if (serverName == null) {
              serverName = InetAddress.getLocalHost().getCanonicalHostName();
      }
      return serverName;
  }
  /**
   * Create a login context or login using the principal and keytab information
   * specified in the configuration.
   *
   * @throws LoginException If a login context cannot be created.
   */
  private void login() throws LoginException {
      loginContext =
          new LoginContext(GSSAPISASLMechanismHandler.class.getName(), this);
      loginContext.login();
  }
  /**
   * Logout of the current login context.
   *
   */
  private void logout() {
      try {
          loginContext.logout();
      } catch (LoginException e) {
          if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        Message message = ERR_SASLGSSAPI_CANNOT_GET_SERVER_FQDN.get(
            String.valueOf(configEntryDN), getExceptionMessage(e));
        throw new InitializationException(message, e);
      }
    }
    // Since we're going to be using JAAS behind the scenes, we need to have a
    // JAAS configuration.  Rather than always requiring the user to provide it,
    // we'll write one to a temporary file that will be deleted when the JVM
    // exits.
  /**
   * Creates an login.conf file from information in the specified configuration.
   * This file is used during the login phase.
   *
   * @param configuration The new configuration to use.
   * @return The filename of the new configuration file.
   *
   * @throws IOException If the configuration file cannot be created.
   */
  private String
  configureLoginConfFile(GSSAPISASLMechanismHandlerCfg configuration)
  throws IOException {
    String configFileName;
    try
    {
      File tempFile = File.createTempFile("login", "conf");
      configFileName = tempFile.getAbsolutePath();
      tempFile.deleteOnExit();
      BufferedWriter w = new BufferedWriter(new FileWriter(tempFile, false));
      w.write(getClass().getName() + " {");
      w.newLine();
      w.write("  com.sun.security.auth.module.Krb5LoginModule required " +
              "storeKey=true useKeyTab=true ");
      String keyTabFile = configuration.getKeytab();
      if (keyTabFile != null)
      {
      if (keyTabFile != null) {
        w.write("keyTab=\"" + keyTabFile + "\" ");
      }
      // FIXME -- Should we add the ability to include "debug=true"?
      // FIXME -- Can we get away from hard-coding a protocol here?
      w.write("principal=\"ldap/" + serverFQDN);
      StringBuilder principal= new StringBuilder();
      String principalName = configuration.getPrincipalName();
      String realm = configuration.getRealm();
      if (realm != null)
      {
        w.write("@" + realm);
      if(principalName != null) {
          principal.append("principal=\"" + principalName);
      } else {
          principal.append("principal=\"ldap/" + serverFQDN);
      }
      if (realm != null) {
          principal.append("@" + realm);
      }
      w.write(principal.toString());
      Message msg =  INFO_GSSAPI_PRINCIPAL_NAME.get(principal.toString());
      logError(msg);
      w.write("\";");
      w.newLine();
      w.write("};");
      w.newLine();
      w.flush();
      w.close();
      return configFileName;
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message =
          ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(getExceptionMessage(e));
      throw new InitializationException(message, e);
    }
    System.setProperty(JAAS_PROPERTY_CONFIG_FILE, configFileName);
    System.setProperty(JAAS_PROPERTY_SUBJECT_CREDS_ONLY, "false");
    DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_GSSAPI, this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizeSASLMechanismHandler()
  {
    currentConfig.removeGSSAPIChangeListener(this);
  public void finalizeSASLMechanismHandler() {
      logout();
      configuration.removeGSSAPIChangeListener(this);
    DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_GSSAPI);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSASLBind(BindOperation bindOperation)
  {
    // GSSAPI binds use multiple stages, so we need to determine whether this is
    // the first stage or a subsequent one.  To do that, see if we have SASL
    // state information in the client connection.
    ClientConnection clientConnection = bindOperation.getClientConnection();
    if (clientConnection == null)
    {
  public void processSASLBind(BindOperation bindOp) {
      ClientConnection clientConnection = bindOp.getClientConnection();
      if (clientConnection == null) {
      Message message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get();
      bindOperation.setAuthFailureReason(message);
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          bindOp.setAuthFailureReason(message);
          bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
      return;
    }
    GSSAPIStateInfo stateInfo = null;
    Object saslBindState = clientConnection.getSASLAuthStateInfo();
    if ((saslBindState != null) && (saslBindState instanceof GSSAPIStateInfo))
    {
      stateInfo = (GSSAPIStateInfo) saslBindState;
      ClientConnection clientConn  = bindOp.getClientConnection();
      SASLContext saslContext = (SASLContext) clientConn.getSASLAuthStateInfo();
      if(saslContext == null) {
          try {
              saslContext = SASLContext.createSASLContext(saslProps, serverFQDN,
                                        SASL_MECHANISM_GSSAPI, identityMapper);
          } catch (SaslException ex) {
              if (debugEnabled()) {
                  TRACER.debugCaught(DebugLogLevel.ERROR, ex);
    }
    else
    {
      try
      {
        stateInfo = new GSSAPIStateInfo(this, bindOperation, serverFQDN);
      }
      catch (InitializationException ie)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, ie);
        }
        bindOperation.setAuthFailureReason(ie.getMessageObject());
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        clientConnection.setSASLAuthStateInfo(null);
              Message msg =
                  ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
                                                    getExceptionMessage(ex));
              clientConn.setSASLAuthStateInfo(null);
              bindOp.setAuthFailureReason(msg);
              bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
        return;
      }
    }
    stateInfo.setBindOperation(bindOperation);
    stateInfo.processAuthenticationStage();
    if (bindOperation.getResultCode() == ResultCode.SUCCESS)
    {
      // The authentication was successful, so set the proper state information
      // in the client connection and return success.
      Entry userEntry = stateInfo.getUserEntry();
      AuthenticationInfo authInfo =
           new AuthenticationInfo(userEntry, SASL_MECHANISM_GSSAPI,
                                  DirectoryServer.isRootDN(userEntry.getDN()));
      bindOperation.setAuthenticationInfo(authInfo);
      bindOperation.setResultCode(ResultCode.SUCCESS);
      // FIXME -- If we're using integrity or confidentiality, then we can't do
      // this.
      clientConnection.setSASLAuthStateInfo(null);
      try
      {
        stateInfo.dispose();
      saslContext.performAuthentication(loginContext, bindOp);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
    else if (bindOperation.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS)
    {
      // We need to store the SASL auth state with the client connection so we
      // can resume authentication the next time around.
      clientConnection.setSASLAuthStateInfo(stateInfo);
    }
    else
    {
      // The authentication failed.  We don't want to keep the SASL state
      // around.
      // FIXME -- Are there other result codes that we need to check for and
      //          preserve the auth state?
      clientConnection.setSASLAuthStateInfo(null);
    }
  }
  /**
@@ -328,7 +354,7 @@
   *                        associated user.
   *
   * @return  The user entry for the user with the specified authorization ID,
   *          or <CODE>null</CODE> if none is identified.
   *          or {@code null} if none is identified.
   *
   * @throws  DirectoryException  If a problem occurs while searching the
   *                              directory for the associated user, or if
@@ -392,119 +418,41 @@
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
              GSSAPISASLMechanismHandlerCfg configuration)
  {
          GSSAPISASLMechanismHandlerCfg configuration) {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    // Get the identity mapper that should be used to find users.
    DN identityMapperDN = configuration.getIdentityMapperDN();
    IdentityMapper<?> newIdentityMapper =
         DirectoryServer.getIdentityMapper(identityMapperDN);
    // Determine the fully-qualified hostname for this system.  It may be
    // provided, but if not, then try to determine it programmatically.
    String newFQDN = configuration.getServerFqdn();
    if (newFQDN == null)
    {
      try
      {
        newFQDN = InetAddress.getLocalHost().getCanonicalHostName();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = DirectoryServer.getServerErrorResultCode();
        }
        messages.add(ERR_SASLGSSAPI_CANNOT_GET_SERVER_FQDN.get(
                String.valueOf(configEntryDN),
                getExceptionMessage(e)));
      }
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      String configFileName;
      try
      {
        File tempFile = File.createTempFile("login", "conf");
        configFileName = tempFile.getAbsolutePath();
        tempFile.deleteOnExit();
        BufferedWriter w = new BufferedWriter(new FileWriter(tempFile, false));
        w.write(getClass().getName() + " {");
        w.newLine();
        w.write("  com.sun.security.auth.module.Krb5LoginModule required " +
                "storeKey=true useKeyTab=true ");
        String keyTabFile = configuration.getKeytab();
        if (keyTabFile != null)
        {
          w.write("keyTab=\"" + keyTabFile + "\" ");
        }
        // FIXME -- Should we add the ability to include "debug=true"?
        // FIXME -- Can we get away from hard-coding a protocol here?
        w.write("principal=\"ldap/" + serverFQDN);
        String realm = configuration.getRealm();
        if (realm != null)
        {
          w.write("@" + realm);
        }
        w.write("\";");
        w.newLine();
        w.write("};");
        w.newLine();
        w.flush();
        w.close();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        resultCode = DirectoryServer.getServerErrorResultCode();
        messages.add(ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(
                getExceptionMessage(e)));
       return new ConfigChangeResult(resultCode, adminActionRequired, messages);
      }
      System.setProperty(JAAS_PROPERTY_CONFIG_FILE, configFileName);
      identityMapper = newIdentityMapper;
      serverFQDN     = newFQDN;
      currentConfig  = configuration;
    }
      saslProps = new HashMap<String,String>();
      saslProps.put(Sasl.QOP, getQOP(configuration));
      saslProps.put(Sasl.REUSE, "false");
      this.configuration  = configuration;
   return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
  /**
   * Retrieves the QOP (quality-of-protection) from the specified
   * configuration.
   *
   * @param configuration The new configuration to use.
   * @return A string representing the quality-of-protection.
   */
  private String
  getQOP(GSSAPISASLMechanismHandlerCfg configuration) {
      QualityOfProtection QOP = configuration.getQualityOfProtection();
      if(QOP.equals(QualityOfProtection.CONFIDENTIALITY))
          return "auth-conf";
      else if(QOP.equals(QualityOfProtection.INTEGRITY))
          return "auth-int";
      else
          return "auth";
  }
}
opends/src/server/org/opends/server/extensions/GSSAPIStateInfo.java
File was deleted
opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java
@@ -173,6 +173,15 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isActive() {
      //This provider is always active.
      return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConnectionSecurityProvider newInstance(ClientConnection
                                                      clientConnection,
opends/src/server/org/opends/server/extensions/SASLContext.java
New file
@@ -0,0 +1,894 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.locks.Lock;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.sasl.AuthorizeCallback;
import javax.security.sasl.RealmCallback;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.messages.Message;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.IdentityMapper;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.BindOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.ldap.LDAPClientConnection;
import org.opends.server.types.*;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines the SASL context needed to process GSSAPI and DIGEST-MD5
 * bind requests from clients.
 *
 */
public class
SASLContext implements CallbackHandler, PrivilegedExceptionAction<Boolean> {
    // The tracer object for the debug logger.
    private static final DebugTracer TRACER = getTracer();
    // The SASL server to use in the authentication.
    private SaslServer saslServer=null;
    // The identity mapper to use when mapping identities.
    private final IdentityMapper<?> identityMapper;
    //The  property set to use when creating the SASL server.
    private HashMap<String, String>saslProps;
    //The fully qualified domain name to use when creating the SASL server.
    private String serverFQDN;
    //The SASL mechanism name.
    private final String mech;
    //The authorization entry used in the authentication.
    private Entry authEntry=null;
    //The authorization entry used in the authentication.
    private Entry authzEntry=null;
    //The user name used in the authentication taken from the name callback.
    private String userName;
    //Error message used by callbacks.
    private Message cbMsg;
    //The current bind operation used by the callbacks.
    private BindOperation bindOp;
    //Used to check if negotiated QOP is confidentiality or integrity.
    private final String confidentiality = "auth-conf";
    private final String integrity = "auth-int";
    /**
     * Create a SASL context using the specified parameters. A SASL server will
     * be instantiated only for the DIGEST-MD5 mechanism. The GSSAPI mechanism
     * must instantiate the SASL server as the login context in a separate step.
     *
     * @param saslProps The properties to use in creating the SASL server.
     * @param serverFQDN The fully qualified domain name to use in creating the
     *                   SASL server.
     * @param mech The SASL mechanism name.
     * @param identityMapper The identity mapper to use in mapping identities.
     *
     * @throws SaslException If the SASL server can not be instantiated.
     */
    private SASLContext(HashMap<String, String>saslProps, String serverFQDN,
                          String mech, IdentityMapper<?> identityMapper)
                          throws SaslException {
        this.identityMapper = identityMapper;
        this.mech = mech;
        this.saslProps = saslProps;
        this.serverFQDN = serverFQDN;
        if(mech.equals(SASL_MECHANISM_DIGEST_MD5)) {
            initSASLServer();
        }
    }
    /**
     * Instantiate a GSSAPI/DIGEST-MD5 SASL context using the specified
     * parameters.
     *
     * @param saslProps The properties to use in creating the SASL server.
     * @param serverFQDN The fully qualified domain name to use in creating the
     *                   SASL server.
     * @param mech The SASL mechanism name.
     * @param identityMapper The identity mapper to use in mapping identities.
     * @return A fully instantiated SASL context to use in processing a SASL
     *         bind for the GSSAPI or DIGEST-MD5 mechanisms.
     *
     * @throws SaslException If the SASL server can not be instantiated.
     */
    public static
    SASLContext createSASLContext(HashMap<String,String>saslProps,
                        String serverFQDN, String mech,
                        IdentityMapper<?> identityMapper) throws SaslException {
        return (new SASLContext(saslProps,serverFQDN, mech, identityMapper));
    }
    /**
     * Initialize the SASL server using parameters specified in the
     * constructor.
     *
     * @throws SaslException If the SASL server can not be instantiated.
     */
    private void initSASLServer() throws SaslException {
        this.saslServer = Sasl.createSaslServer(mech, SASL_DEFAULT_PROTOCOL,
                                                serverFQDN, saslProps, this);
    }
    /**
     * Wrap the specified clear byte array using the provided offset and length
     * values. Used only when the SASL server has negotiated
     * confidentiality/integrity  processing.
     *
     * @param clearBytes The clear byte array to wrap.
     * @param offset The offset into the clear byte array..
     * @param len The length from the offset of the number of bytes to wrap.
     * @return A byte array containing the wrapped bytes.
     *
     * @throws SaslException If the clear bytes cannot be wrapped.
     */
    byte[] wrap(byte[] clearBytes, int offset, int len)
    throws SaslException {
        return saslServer.wrap(clearBytes, offset, len);
    }
    /**
     * Unwrap the specified byte array using the provided offset and length
     * values. Used only when the SASL server has negotiated
     * confidentiality/integrity  processing.
     *
     * @param bytes The byte array to unwrap.
     * @param offset The offset in the array.
     * @param len The length from the offset of the number of bytes to unwrap.
     * @return A byte array containing the clear or unwrapped bytes.
     *
     * @throws SaslException If the bytes cannot be unwrapped.
     */
    byte[] unwrap(byte[] bytes, int offset, int len)
    throws SaslException {
        return saslServer.unwrap(bytes, offset, len);
    }
    /**
     * Dispose of the SASL server instance.
     */
    void dispose() {
        try {
            saslServer.dispose();
        } catch (SaslException e) {
            if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
        }
    }
    /**
     * Return the negotiated buffer size.
     *
     * @param prop The buffer size property to return.
     * @return The value of the negotiated buffer size.
     */
    int getBufSize(String prop) {
          String sizeStr =
              (String) saslServer.getNegotiatedProperty(prop);
          return Integer.parseInt(sizeStr);
    }
    /**
     * Return true if the bind has been completed. If the context is supporting
     * confidentiality/integrity, the security provider will need to check
     * if the context has completed its handshakes with the client and is
     * ready to process confidentiality/integrity messages.
     *
     * @return {@code true} if the handshaking is complete,
     *         {@code false} if further handshaking is needed.
     */
    boolean isBindComplete() {
          return saslServer.isComplete();
    }
    /**
     * Return true if the SASL server has negotiated with the client to support
     * confidentiality/integrity.
     *
     * @return {@code true} if the context support
     *         confidentiality/integrity, or, {@code false} otherwise.
     */
    private boolean isConfidentialIntegrity() {
      boolean ret = false;
      String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP);
      if(qop.equalsIgnoreCase(confidentiality) ||
         qop.equalsIgnoreCase(integrity))
           ret = true;
      return ret;
    }
    /**
     * Helper routine to call the SASL server evaluateResponse method with the
     * specified byte array.
     *
     * @param bytes The byte array to pass to the SASL server.
     * @return A byte array containing the result of the evaluation.
     *
     * @throws SaslException If the SASL server cannot evaluate the byte array.
     */
    private byte[] evaluateResponse(byte[] bytes) throws SaslException {
          return saslServer.evaluateResponse(bytes);
      }
    /**
     * This method is used to process an exception that is thrown during bind
     * processing. It will try to determine if the exception is a result of
     * callback processing, and if it is, will try to use a more informative
     * failure message set by the callback.
     *
     * If the exception is a result of a error during the the SASL server
     * processing, the callback message will be null, and
     * the method will use the specified message parameter as the
     * failure reason. This is a more cryptic exception message hard-coded
     * in the SASL server internals.
     *
     * The method also disposes of the SASL server, clears the authentication
     * state  and sets the result code to INVALID_CREDENTIALs
     *
     * @param msg The message to use if the callback message is not null.
     */
    private void handleError(Message msg) {
        dispose();
        ClientConnection clientConn = bindOp.getClientConnection();
        clientConn.setSASLAuthStateInfo(null);
        //Check if the callback message is null and use that message if not.
        if(cbMsg != null)
            bindOp.setAuthFailureReason(cbMsg);
        else
            bindOp.setAuthFailureReason(msg);
        bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
    }
    /**
     * Checks the specified authentication information parameter against the
     * privilege subsystem to see if it has PROXIED_AUTH privileges.
     *
     * @param authInfo The authentication information to use in the check.
     * @return {@code true} if the authentication information has
     *         PROXIED_AUTH privileges, {@code false} otherwise.
     */
    private boolean
    hasPrivilege(AuthenticationInfo authInfo) {
        boolean ret = true;
          InternalClientConnection tempConn =
               new InternalClientConnection(authInfo);
          if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOp)) {
              setCallbackMsg(ERR_SASL_AUTHZID_INSUFFICIENT_PRIVILEGES.get(
                             String.valueOf(authEntry.getDN())));
              ret = false;
          }
          return ret;
    }
    /**
     * Checks the specified authentication information parameter against the
     * access control subsystem to see if it has the "proxy" right.
     *
     * @param authInfo The authentication information to check access on.
     * @return {@code true} if the authentication information has
     *         proxy access, {@code false} otherwise.
     */
    private boolean
    hasPermission(AuthenticationInfo authInfo) {
        boolean ret = true;
        Entry e = authzEntry;
        //If the authz entry is null, use the entry associated with the NULL DN.
        if(e == null) {
            try {
                e = DirectoryServer.getEntry(DN.nullDN());
            } catch (DirectoryException ex) {
                return false;
            }
        }
        if (AccessControlConfigManager.getInstance().getAccessControlHandler().
               mayProxy(authInfo.getAuthenticationEntry(), e,
                        bindOp) == false) {
            setCallbackMsg(ERR_SASL_AUTHZID_INSUFFICIENT_ACCESS.get(
                    String.valueOf(authEntry.getDN())));
            ret = false;
        }
        return ret;
    }
    /**
     * Sets the callback message to the specified message.
     *
     * @param cbMsg The message to set the callback message to.
     */
    private void setCallbackMsg(Message cbMsg) {
        this.cbMsg = cbMsg;
    }
   /**
     * Process the specified callback array.
     *
     *@param callbacks An array of callbacks that need processing.
     *@throws UnsupportedCallbackException If a callback is not supported.
     */
    public void handle(Callback[] callbacks)
    throws UnsupportedCallbackException {
        for (Callback callback : callbacks) {
            if (callback instanceof NameCallback) {
                nameCallback((NameCallback) callback);
            } else if (callback instanceof PasswordCallback) {
                passwordCallback((PasswordCallback) callback);
            } else if (callback instanceof RealmCallback) {
                realmCallback((RealmCallback) callback);
            } else if (callback instanceof AuthorizeCallback)  {
                authorizeCallback((AuthorizeCallback) callback);
            } else {
                Message message =
                    INFO_SASL_UNSUPPORTED_CALLBACK.get(mech,
                                                      String.valueOf(callback));
                throw new UnsupportedCallbackException(callback,
                        message.toString());
            }
        }
    }
    /**
     * This callback is used to process realm information. It is not used.
     *
     * @param callback The realm callback instance to process.
     */
    private void realmCallback(RealmCallback callback) {
    }
    /**
     * This callback is used to process the authorize callback. It is used
     * during both GSSAPI and DIGEST-MD5 processing. When processing the GSSAPI
     * mechanism, this is the only callback invoked. When processing the
     * DIGEST-MD5 mechanism, it is the last callback invoked after the name
     * and password callbacks respectively.
     *
     * @param callback The authorize callback instance to process.
     */
    private void authorizeCallback(AuthorizeCallback callback) {
        String  responseAuthzID = callback.getAuthorizationID();
        //If the authEntry is null, then we are processing a GSSAPI SASL bind,
        //and first need to try to map the authentication ID to an user entry.
        //The authEntry is never null, when processing a DIGEST-MD5 SASL bind.
        if(authEntry == null) {
            String  authid = callback.getAuthenticationID();
            try {
                authEntry = identityMapper.getEntryForID(authid);
                if (authEntry == null) {
                 setCallbackMsg(ERR_SASL_AUTHENTRY_NO_MAPPED_ENTRY.get(authid));
                 callback.setAuthorized(false);
                 return;
                }
            } catch (DirectoryException de) {
                if (debugEnabled()) {
                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                setCallbackMsg(ERR_SASL_CANNOT_MAP_AUTHENTRY.get(authid,
                        de.getMessage()));
                callback.setAuthorized(false);
                return;
            }
            userName=authid;
        }
        if (responseAuthzID.length() == 0) {
            setCallbackMsg(ERR_SASLDIGESTMD5_EMPTY_AUTHZID.get());
            callback.setAuthorized(false);
            return;
        } else if (!responseAuthzID.equals(userName))  {
            String lowerAuthzID = toLowerCase(responseAuthzID);
            //Process the callback differently depending on if the authzid
            //string begins with the string "dn:" or not.
            if (lowerAuthzID.startsWith("dn:")) {
                authzDNCheck(callback);
            } else {
                authzIDCheck(callback);
            }
        } else {
            authzEntry = authEntry;
            callback.setAuthorized(true);
        }
    }
    /**
     * Process the specified authorize callback. This method is called if the
     * callback's authorization ID does not begin with the string "dn:".
     *
     * @param callback The authorize callback to process.
     */
    private void authzIDCheck(AuthorizeCallback callback) {
        String  authzid = callback.getAuthorizationID();
        String lowerAuthzID = toLowerCase(authzid);
        String idStr;
        callback.setAuthorized(true);
        if (lowerAuthzID.startsWith("u:")) {
            idStr = authzid.substring(2);
        } else {
            idStr = authzid;
        }
        if (idStr.length() == 0) {
            authzEntry = null;
        } else {
            try {
                authzEntry = identityMapper.getEntryForID(idStr);
                if (authzEntry == null) {
                  setCallbackMsg(ERR_SASL_AUTHZID_NO_MAPPED_ENTRY.get(authzid));
                  callback.setAuthorized(false);
                  return;
                }
            } catch (DirectoryException e) {
                if (debugEnabled()) {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                }
                setCallbackMsg(ERR_SASL_AUTHZID_NO_MAPPED_ENTRY.get(authzid));
                callback.setAuthorized(false);
                return;
            }
        }
        if ((authzEntry == null) ||
                (!authzEntry.getDN().equals(authEntry.getDN()))) {
            //Create temporary authorization information and run it both
            //through the privilege and then the access control subsystems.
            AuthenticationInfo authInfo = new AuthenticationInfo(authEntry,
                    DirectoryServer.isRootDN(authEntry.getDN()));
            if(!hasPrivilege(authInfo)) {
                callback.setAuthorized(false);
            } else {
                callback.setAuthorized(hasPermission(authInfo));
            }
        }
    }
    /**
     * Process the specified authorize callback. This method is called if the
     * callback's authorization ID begins with the string "dn:".
     *
     * @param callback The authorize callback to process.
     */
    private void authzDNCheck(AuthorizeCallback callback) {
        String  responseAuthzID = callback.getAuthorizationID();
        DN authzDN;
        callback.setAuthorized(true);
        try {
            authzDN = DN.decode(responseAuthzID.substring(3));
        } catch (DirectoryException e) {
            if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            setCallbackMsg(ERR_SASL_AUTHZID_INVALID_DN.get(responseAuthzID,
                                                        e.getMessageObject()));
            callback.setAuthorized(false);
            return;
        }
        DN actualAuthzDN =
            DirectoryServer.getActualRootBindDN(authzDN);
        if (actualAuthzDN != null) {
            authzDN = actualAuthzDN;
        }
        if (!authzDN.equals(authEntry.getDN())) {
            if (authzDN.isNullDN()) {
                authzEntry = null;
            } else {
                try {
                  if((authzEntry = DirectoryServer.getEntry(authzDN)) == null) {
                        setCallbackMsg(ERR_SASL_AUTHZID_NO_SUCH_ENTRY.get(
                                                      String.valueOf(authzDN)));
                        callback.setAuthorized(false);
                        return;
                    }
                } catch (DirectoryException e) {
                    if (debugEnabled()) {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                    }
                    setCallbackMsg(ERR_SASL_AUTHZID_CANNOT_GET_ENTRY
                          .get(String.valueOf(authzDN), e.getMessageObject()));
                    callback.setAuthorized(false);
                    return;
                }
            }
            AuthenticationInfo authInfo = new AuthenticationInfo(authEntry,
                                  DirectoryServer.isRootDN(authEntry.getDN()));
            if(!hasPrivilege(authInfo)) {
                callback.setAuthorized(false);
            } else
                callback.setAuthorized(hasPermission(authInfo));
        }
    }
    /**
     * Process the specified password callback. Used only for the DIGEST-MD5
     * SASL mechanism. The password callback is processed after the name
     * callback.
     *
     * @param passwordCallback The password callback to process.
     */
    private void passwordCallback(PasswordCallback passwordCallback) {
        //If there is no authEntry this is an error.
        if(authEntry == null) {
            setCallbackMsg(ERR_SASL_NO_MATCHING_ENTRIES.get(userName));
            return;
        }
        //Try to get a clear password to use.
        List<ByteString> clearPasswords;
        try {
          PasswordPolicyState pwPolicyState =
                                    new PasswordPolicyState(authEntry, false);
          clearPasswords = pwPolicyState.getClearPasswords();
          if ((clearPasswords == null) || clearPasswords.isEmpty()) {
              setCallbackMsg(
                 ERR_SASL_NO_REVERSIBLE_PASSWORDS.get(mech,
                                            String.valueOf(authEntry.getDN())));
            return;
          }
        }
        catch (Exception e) {
            if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            setCallbackMsg(ERR_SASL_CANNOT_GET_REVERSIBLE_PASSWORDS.get(
                    String.valueOf(authEntry.getDN()),mech,
                    String.valueOf(e)));
          return;
        }
        //Use the first password.
        char[] password = clearPasswords.get(0).toString().toCharArray();
        passwordCallback.setPassword(password);
        return;
    }
    /**
     * Process the specified name callback. Used only for DIGEST-MD5 SASL
     * mechanism.
     *
     * @param nameCallback The name callback to process.
     */
    private void nameCallback(NameCallback nameCallback) {
        userName= nameCallback.getDefaultName();
        String lowerUserName = toLowerCase(userName);
        //Process the user name differently if it starts with the string "dn:".
        if (lowerUserName.startsWith("dn:")) {
            DN userDN;
            try {
                userDN = DN.decode(userName.substring(3));
            } catch (DirectoryException e) {
                if (debugEnabled()) {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                }
                 setCallbackMsg(ERR_SASL_CANNOT_DECODE_USERNAME_AS_DN.get(
                                      mech,
                                      userName, e.getMessageObject()));
                return;
            }
            if (userDN.isNullDN()) {
              setCallbackMsg(ERR_SASL_USERNAME_IS_NULL_DN.get(
                                                      mech));
              return;
            }
            DN rootDN = DirectoryServer.getActualRootBindDN(userDN);
            if (rootDN != null) {
                userDN = rootDN;
            }
            getAuthEntry(userDN);
        } else {
            //The entry name is not a DN, try to map it using the identity
            //mapper.
            String entryID = userName;
            if (lowerUserName.startsWith("u:")) {
                if (lowerUserName.equals("u:")) {
                    setCallbackMsg(ERR_SASL_ZERO_LENGTH_USERNAME.get(
                            mech,mech));
                    return;
                }
                entryID = userName.substring(2);
            }
            try {
                authEntry = identityMapper.getEntryForID(entryID);
            } catch (DirectoryException e) {
                if (debugEnabled()) {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                }
               setCallbackMsg(ERR_SASLDIGESTMD5_CANNOT_MAP_USERNAME.get(
                      String.valueOf(userName), e.getMessageObject()));
                return;
            }
        }
        if (authEntry == null) {
            //The authEntry is null, this is an error. The password callback
            //will catch this error. There is no way to stop the processing
            //from the name callback.
            return;
        }
    }
    /**
     * Try to get a entry from the directory using the specified DN. Used only
     * for DIGEST-MD5 SASL mechanism.
     *
     * @param userDN The DN of the entry to retrieve from the server.
     */
   private void getAuthEntry(DN userDN) {
       Lock readLock = null;
       for (int i=0; i < 3; i++) {
           readLock = LockManager.lockRead(userDN);
           if (readLock != null) {
               break;
           }
       }
       if (readLock == null) {
           setCallbackMsg(INFO_SASL_CANNOT_LOCK_ENTRY.get(
                                                       String.valueOf(userDN)));
           return;
       } try {
           authEntry = DirectoryServer.getEntry(userDN);
       } catch (DirectoryException e) {
           if (debugEnabled()) {
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           setCallbackMsg(ERR_SASL_CANNOT_GET_ENTRY_BY_DN.get(
                   String.valueOf(userDN), SASL_MECHANISM_DIGEST_MD5,
                   e.getMessageObject()));
           return;
       } finally {
           LockManager.unlock(userDN, readLock);
       }
   }
   /**
    * The method performs all GSSAPI processing. It is run as the context of
    * the login context performed by the GSSAPI mechanism handler. See comments
    * for processing overview.
    * @return {@code true} if the authentication processing was successful,
    *         or, {@code false} otherwise.
    */
   public Boolean run() {
       ClientConnection clientConn = bindOp.getClientConnection();
       //If the SASL server is null then this is the first handshake and the
       //server needs to be initialized before any processing can be performed.
       //If the SASL server cannot be created then all processing is abandoned
       //and INVALID_CREDENTIALS is returned to the client.
       if(saslServer == null) {
           try {
               initSASLServer();
           } catch (SaslException e) {
               if (debugEnabled()) {
                   TRACER.debugCaught(DebugLogLevel.ERROR, e);
               }
               Message msg =
                   ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_DIGEST_MD5,
                                                     getExceptionMessage(e));
               clientConn.setSASLAuthStateInfo(null);
               bindOp.setAuthFailureReason(msg);
               bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
               return false;
           }
       }
       byte[] clientCredBytes = new byte[0];
       ASN1OctetString clientCredentials = bindOp.getSASLCredentials();
       if(clientCredentials != null) {
           clientCredBytes = clientCredentials.value();
       }
       clientConn.setSASLAuthStateInfo(null);
       try {
           byte[] responseBytes =
               evaluateResponse(clientCredBytes);
           ASN1OctetString responseAuthStr =
               new ASN1OctetString(responseBytes);
           //If the bind has not been completed,then
           //more handshake is needed and SASL_BIND_IN_PROGRESS is returned back
           //to the client.
           if (isBindComplete()) {
               bindOp.setResultCode(ResultCode.SUCCESS);
               bindOp.setSASLAuthUserEntry(authEntry);
               AuthenticationInfo authInfo =
                    new AuthenticationInfo(authEntry, authzEntry,
                                    mech,
                                   DirectoryServer.isRootDN(authEntry.getDN()));
               bindOp.setAuthenticationInfo(authInfo);
               //If confidentiality/integrity has been negotiated then
               //create a SASL security provider and save it in the client
               //connection. If confidentiality/integrity has not been
               //negotiated, dispose of the SASL server.
               if(isConfidentialIntegrity()) {
                   SASLSecurityProvider secProvider =
                       new SASLSecurityProvider(clientConn, mech, this);
                   LDAPClientConnection ldapConn =
                       (LDAPClientConnection) clientConn;
                       ldapConn.setSASLConnectionSecurityProvider(secProvider);
               } else {
                   dispose();
                   clientConn.setSASLAuthStateInfo(null);
               }
           } else {
               bindOp.setServerSASLCredentials(responseAuthStr);
               clientConn.setSASLAuthStateInfo(this);
               bindOp.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
           }
       } catch (SaslException e) {
           if (debugEnabled()) {
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           Message msg =
               ERR_SASL_PROTOCOL_ERROR.get(mech, getExceptionMessage(e));
           handleError(msg);
           return false;
       }
       return true;
   }
   /**
    * Perform the authentication as the specified login context. The specified
    * bind operation needs to be saved so the callbacks have access to it.
    * Only used by the GSSAPI mechanism.
    *
    * @param loginContext The login context to perform the authentication
    *                     as.
    * @param bindOp The bind operation needed by the callbacks to process the
    *               authentication.
    */
   void
   performAuthentication(LoginContext loginContext, BindOperation bindOp) {
       this.bindOp = bindOp;
       try {
           Subject.doAs(loginContext.getSubject(), this);
       } catch (PrivilegedActionException e) {
           if (debugEnabled()) {
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           Message msg =
               ERR_SASL_PROTOCOL_ERROR.get(mech, getExceptionMessage(e));
           handleError(msg);
       }
   }
   /**
    * Process the initial stage of a DIGEST-MD5 SASL bind using the specified
    * bind operation.
    *
    * @param bindOp The bind operation to use in processing.
    */
   void
   evaluateInitialStage(BindOperation bindOp) {
       this.bindOp = bindOp;
       ClientConnection clientConn = bindOp.getClientConnection();
       try {
           byte[] challengeBuffer = evaluateResponse(new byte[0]);
           ASN1OctetString challenge = new ASN1OctetString(challengeBuffer);
           bindOp.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
           bindOp.setServerSASLCredentials(challenge);
           clientConn.setSASLAuthStateInfo(this);
       } catch (SaslException e) {
           if (debugEnabled()) {
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           Message msg =
               ERR_SASL_PROTOCOL_ERROR.get(mech,getExceptionMessage(e));
           handleError(msg);
       }
   }
   /**
    * Evaluate the final stage of a DIGEST-MD5 SASL bind using the specified
    * bind operation.
    *
    * @param bindOp The bind operation to use in processing.
    */
   void
   evaluateFinalStage(BindOperation bindOp) {
      this.bindOp = bindOp;
       ASN1OctetString clientCredentials = bindOp.getSASLCredentials();
       if ((clientCredentials == null) ||
               (clientCredentials.value().length == 0)) {
           Message msg =
               ERR_SASL_NO_CREDENTIALS.get(mech, mech);
           handleError(msg);
           return;
       }
       ClientConnection clientConn = bindOp.getClientConnection();
       clientConn.setSASLAuthStateInfo(null);
       try {
           byte[] responseBytes =
                        evaluateResponse(clientCredentials.value());
           ASN1OctetString responseAuthStr =
               new ASN1OctetString(responseBytes);
           bindOp.setResultCode(ResultCode.SUCCESS);
           bindOp.setServerSASLCredentials(responseAuthStr);
           bindOp.setSASLAuthUserEntry(authEntry);
           AuthenticationInfo authInfo =
                new AuthenticationInfo(authEntry, authzEntry,
                                       mech,
                                  DirectoryServer.isRootDN(authEntry.getDN()));
           bindOp.setAuthenticationInfo(authInfo);
           //If confidentiality/integrity has been negotiated, then create a
           //SASL security provider and save it in the client connection for
           //use in later processing.
           if(isConfidentialIntegrity()) {
               SASLSecurityProvider secProvider =
                   new SASLSecurityProvider(clientConn, mech, this);
               LDAPClientConnection ldapConn =
                   (LDAPClientConnection) clientConn;
               ldapConn.setSASLConnectionSecurityProvider(secProvider);
           } else {
               dispose();
               clientConn.setSASLAuthStateInfo(null);
           }
       } catch (SaslException e) {
           if (debugEnabled()) {
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           Message msg =
               ERR_SASL_PROTOCOL_ERROR.get(mech, getExceptionMessage(e));
           handleError(msg);
       }
   }
}
opends/src/server/org/opends/server/extensions/SASLSecurityProvider.java
New file
@@ -0,0 +1,457 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConnectionSecurityProvider;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.ldap.LDAPClientConnection;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.InitializationException;
import static org.opends.server.loggers.debug.DebugLogger.*;
/**
 * This class provides an implementation of a connection security provider that
 * provides SASL confidentiality/integrity between the server and client.
 *
 */
public class SASLSecurityProvider extends ConnectionSecurityProvider {
    // The tracer object for the debug logger.
    private static final DebugTracer TRACER = getTracer();
    //The client connection associated with this provider.
    private ClientConnection connection;
    //The socket channel associated with this provider.
    private SocketChannel sockChannel;
    //The SASL context associated with the provider
    private SASLContext saslContext;
    //The number of bytes in the length buffer.
    private final int lengthSize = 4;
    //A byte buffer used to hold the length of the clear buffer.
    private ByteBuffer lengthBuf =  ByteBuffer.allocate(lengthSize);
    //The SASL mechanism name.
    private String name;
    /**
     * Create a SASL security provider with the specified parameters that is
     * capable of processing a confidentiality/integrity SASL connection.
     *
     * @param connection The client connection to read/write the bytes.
     * @param name The SASL mechanism name.
     * @param saslContext The SASL context to process the data through.
     */
    public SASLSecurityProvider(ClientConnection connection, String name,
                               SASLContext saslContext) {
      super();
      this.connection = connection;
      this.name = name;
      this.saslContext = saslContext;
      this.sockChannel = ((LDAPClientConnection) connection).getSocketChannel();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void disconnect(boolean connectionValid) {
        this.saslContext.dispose();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void finalizeConnectionSecurityProvider() {
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public int getClearBufferSize() {
        return 0;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public int getEncodedBufferSize() {
        return 0;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getSecurityMechanismName() {
        return name;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void initializeConnectionSecurityProvider(ConfigEntry configEntry)
            throws ConfigException, InitializationException {
        this.connection = null;
        this.sockChannel = null;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isSecure() {
        return true;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public ConnectionSecurityProvider newInstance(ClientConnection clientConn,
                                                  SocketChannel socketChannel) {
            return new SASLSecurityProvider(clientConn, null,null);
    }
    /**
     * Return the clear buffer length as determined by processing the first
     * 4 bytes of the specified buffer.
     *
     * @param byteBuf The buffer to examine the first 4 bytes of.
     * @return The size of the clear buffer.
     */
    private int getBufLength(ByteBuffer byteBuf) {
        int answer = 0;
        byte[] buf = byteBuf.array();
        for (int i = 0; i < lengthSize; i++) {
            answer <<= 8;
            answer |= ((int)buf[i] & 0xff);
        }
        return answer;
    }
    /**
     * Read from the socket channel into the specified byte buffer the
     * number of bytes specified in the total parameter.
     *
     * @param byteBuf The byte buffer to put the bytes in.
     * @param total The total number of bytes to read from the socket channel.
     * @return The number of bytes read, 0 or -1.
     * @throws IOException If an error occurred reading the socket channel.
     */
    private int readAll(ByteBuffer byteBuf, int total) throws IOException {
        int count = 0;
        while(sockChannel.isOpen() && total > 0) {
            count = sockChannel.read(byteBuf);
            if(count == -1)
                return -1;
           if(count == 0)
                return 0;
            total -= count;
        }
        if(total > 0)
            return -1;
        else
            return byteBuf.position();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean readData() throws DirectoryException {
        int recvBufSize = saslContext.getBufSize(Sasl.MAX_BUFFER);
        try {
            while(true) {
                lengthBuf.clear();
                int readResult = readAll(lengthBuf, lengthSize);
                if (readResult == -1) {
                    //Client connection has been closed. Disconnect and return.
                    connection.disconnect(DisconnectReason.CLIENT_DISCONNECT,
                            false, null);
                    return false;
                } else if(readResult == 0)
                    return true;
                int bufLength = getBufLength(lengthBuf);
                if(bufLength > recvBufSize) {
                    connection.disconnect(DisconnectReason.CLIENT_DISCONNECT,
                            false, null);
                    return false;
                }
                ByteBuffer readBuf = ByteBuffer.allocate(bufLength);
                readResult = readAll(readBuf, bufLength);
                if (readResult == -1) {
                    //Client connection has been closed. Disconnect and return.
                    connection.disconnect(DisconnectReason.CLIENT_DISCONNECT,
                            false, null);
                    return false;
                } else if (readResult == 0)
                    return true;
                byte[] inBytes = readBuf.array();
                byte[] clearBytes =
                                saslContext.unwrap(inBytes, 0, inBytes.length);
                ByteBuffer clearBuffer = ByteBuffer.wrap(clearBytes);
                if (!connection.processDataRead(clearBuffer))
                    return false;
            }
        } catch (SaslException saslEx) {
            if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, saslEx);
              }
            //Error trying to unwrap the data.
            connection.disconnect(DisconnectReason.IO_ERROR, false, null);
            return false;
        } catch (IOException ioe) {
            // An error occurred while trying to communicate with the client.
            // Disconnect and return.
            if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
            }
            connection.disconnect(DisconnectReason.IO_ERROR, false, null);
            return false;
        }
    }
    /**
     * Creates a buffer suitable to send to the client using the specified
     * clear byte array, offset and length of the bytes to wrap.
     *
     * @param clearBytes The clear byte array to send to the client.
     * @param offSet An offset into the byte array to start the wrap at.
     * @param len The length of the bytes to wrap in the byte array.
     * @throws SaslException If the wrap of the bytes fails.
     */
    private ByteBuffer
    createSendBuffer(byte[] clearBytes, int offSet, int len)
    throws SaslException {
        byte[] wrapBytes = saslContext.wrap(clearBytes, offSet, len);
        byte[] outBuf = new  byte[wrapBytes.length + lengthSize];
        writeBufLen(outBuf, wrapBytes.length);
        System.arraycopy(wrapBytes, 0, outBuf, lengthSize, wrapBytes.length);
        return ByteBuffer.wrap(outBuf);
    }
    /**
     *  Writes the specified len parameter into the buffer in a form that can
     *  be sent over a network to the client.
     *
     * @param buf The buffer to hold the length bytes.
     * @param len The length to encode.
     */
    private void writeBufLen(byte[] buf, int len) {
        for (int i = 3; i >= 0; i--) {
            buf[i] = (byte)(len & 0xff);
            len >>>= 8;
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override()
    public boolean writeData(ByteBuffer clearData) {
        int maxSendBufSize = saslContext.getBufSize(Sasl.RAW_SEND_SIZE);
        int clearLength = clearData.limit();
        try {
            if(clearLength < maxSendBufSize) {
                ByteBuffer sendBuffer =
                    createSendBuffer(clearData.array(),0,clearLength);
                return writeChannel(sendBuffer);
            } else {
                byte[] clearBytes = clearData.array();
                for(int i=0; i < clearLength; i += maxSendBufSize) {
                    int totLength = (clearLength - i) < maxSendBufSize ?
                                    (clearLength - i) : maxSendBufSize;
                    ByteBuffer sendBuffer =
                                     createSendBuffer(clearBytes, i, totLength);
                    if(!writeChannel(sendBuffer))
                        return false;
                }
            }
        } catch (SaslException e) {
            if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            connection.disconnect(DisconnectReason.IO_ERROR, false, null);
            return false;
        }
        return true;
    }
   /**
    * Write the specified byte buffer to the socket channel.
    *
    * @param buffer The byte buffer to write to the socket channel.
    * @return {@code true} if the byte buffer was successfully written to the
    *         socket channel, or, {@code false} if not.
    */
    private boolean writeChannel(ByteBuffer buffer) {
        try {
            while (buffer.hasRemaining())  {
                int bytesWritten = sockChannel.write(buffer);
                if (bytesWritten < 0)  {
                    connection.disconnect(DisconnectReason.CLIENT_DISCONNECT,
                                          false, null);
                    return false;
                } else if (bytesWritten == 0)  {
                    return writeWithTimeout(buffer);
                }
            }
        }  catch (IOException ioe)  {
            if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
            }
            connection.disconnect(DisconnectReason.IO_ERROR, false, null);
            return false;
        }
        return true;
    }
    /**
     * Writes the specified byte buffer parameter to the socket channel waiting
     * for a period of time if the buffer cannot be written immediately.
     *
     * @param buffer The byte buffer to write to the channel.
     * @return {@code true} if the bytes were sent, or, {@code false} otherwise.
     * @throws IOException If an IO error occurs while writing the bytes.
     */
    private boolean writeWithTimeout(ByteBuffer buffer) throws IOException {
        long startTime = System.currentTimeMillis();
        long waitTime  = connection.getMaxBlockedWriteTimeLimit();
        if (waitTime <= 0) {
            // We won't support an infinite time limit, so fall back to using
            // five minutes, which is a very long timeout given that we're
            // blocking a worker thread.
            waitTime = 300000L;
        }
        long stopTime = startTime + waitTime;
        Selector selector = connection.getWriteSelector();
        if (selector == null) {
            // The client connection does not provide a selector, so we'll
            // fall back to a more inefficient way that will work without a
            // selector.
            while (buffer.hasRemaining() &&
                   (System.currentTimeMillis() < stopTime)) {
                if (sockChannel.write(buffer) < 0) {
                    // The client connection has been closed
                    connection.disconnect(DisconnectReason.CLIENT_DISCONNECT,
                                          false,  null);
                    return false;
                }
            }
            if (buffer.hasRemaining()) {
                // If we've gotten here, then the write timed out.
                // Terminate the client connection.
                connection.disconnect(DisconnectReason.IO_TIMEOUT, false, null);
                return false;
            }
            return true;
        }
        // Register with the selector for handling write operations.
        SelectionKey key =
                         sockChannel.register(selector, SelectionKey.OP_WRITE);
        try
        {
            selector.select(waitTime);
            while (buffer.hasRemaining())
            {
                long currentTime = System.currentTimeMillis();
                if (currentTime >= stopTime) {
                    // We've been blocked for too long
                    connection.disconnect(DisconnectReason.IO_TIMEOUT,
                                          false, null);
                    return false;
                }
                else {
                    waitTime = stopTime - currentTime;
                }
                Iterator<SelectionKey> iterator =
                                             selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey k = iterator.next();
                    if (k.isWritable()) {
                        int bytesWritten = sockChannel.write(buffer);
                        if (bytesWritten < 0) {
                            // The client connection has been closed.
                            connection.disconnect(
                                      DisconnectReason.CLIENT_DISCONNECT,
                                      false, null);
                            return false;
                        }
                        iterator.remove();
                    }
                }
                if (buffer.hasRemaining()) {
                    selector.select(waitTime);
                }
            }
            return true;
        } finally {
            if (key.isValid()) {
                key.cancel();
                selector.selectNow();
            }
        }
    }
    /**
     * Return if the underlying SASL context is active or not. The SASL context
     * may still be negotiating a multi-stage SASL bind and is not ready to
     * process confidentiality or integrity data yet.
     *
     * @return {@code true} if the underlying SASL context is active or ready
     *         to process confidentiality/integrity messages, or, {@code false}
     *         if not.
    */
    public boolean isActive() {
        return saslContext.isBindComplete();
    }
}
opends/src/server/org/opends/server/extensions/TLSConnectionSecurityProvider.java
@@ -339,6 +339,15 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isActive() {
      //This provider is always active.
      return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConnectionSecurityProvider newInstance(ClientConnection
                                                      clientConnection,
opends/src/server/org/opends/server/protocols/ldap/LDAPClientConnection.java
@@ -59,7 +59,6 @@
import org.opends.server.core.SearchOperation;
import org.opends.server.core.SearchOperationBasis;
import org.opends.server.core.UnbindOperationBasis;
import org.opends.server.core.networkgroups.NetworkGroup;
import org.opends.server.extensions.NullConnectionSecurityProvider;
import org.opends.server.extensions.TLSCapableConnection;
import org.opends.server.extensions.TLSConnectionSecurityProvider;
@@ -220,7 +219,9 @@
  // if StartTLS is requested.
  private TLSConnectionSecurityProvider tlsSecurityProvider;
  //The SASL connection provider used if confidentiality/integrity is negotiated
  //during a SASL bind (GSSAPI and DIGEST-MD5 only).
  private ConnectionSecurityProvider saslSecurityProvider;
  /**
   * Creates a new LDAP client connection with the provided information.
@@ -237,9 +238,6 @@
    this.connectionHandler     = connectionHandler;
    if (connectionHandler.isAdminConnectionHandler()) {
      setNetworkGroup(NetworkGroup.getAdminNetworkGroup());
    }
    this.clientChannel         = clientChannel;
    this.securityProvider      = null;
    this.clearSecurityProvider = null;
@@ -479,12 +477,27 @@
   */
  public ConnectionSecurityProvider getConnectionSecurityProvider()
  {
      if(saslSecurityProvider != null && saslSecurityProvider.isActive())
          securityProvider =  saslSecurityProvider;
    return securityProvider;
  }
  /**
   * Set the security provider to be used to process SASL (DIGEST-MD5, GSSAPI)
   * confidentiality/integrity messages.
   *
   * @param secProvider The security provider to use.
   */
    public void
    setSASLConnectionSecurityProvider(ConnectionSecurityProvider secProvider) {
        saslSecurityProvider = secProvider;
    }
  /**
   * Specifies the connection security provider for this client connection.
   *
   * @param  securityProvider  The connection security provider to use for
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -1873,6 +1873,12 @@
       "Kerberos Confidentiality";
  /**
   * The name of the default protocol used.
   */
  public static final String SASL_DEFAULT_PROTOCOL = "ldap";
  /**
   * The name of the security mechanism that will be used for connections
@@ -1934,6 +1940,33 @@
  /**
   * The name of the security mechanism that will be used for connections whose
   * communication only SASL authenticated.
   */
  public static final String SASL_MECHANISM_AUTHENTICATION_ONLY =
       "none";
  /**
   * The name of the security mechanism that will be used for connections whose
   * communication is protected using the confidentiality features SASL.
   */
  public static final String SASL_MECHANISM_CONFIDENTIALITY =
       "confidentiality";
  /**
   * The name of the security mechanism that will be used for connections whose
   * communication is verified using SASL integrity.
   */
  public static final String SASL_MECHANISM_INTEGRITY =
       "integrity";
  /**
   * The OID for the account usable request and response controls.
   */
  public static final String OID_ACCOUNT_USABLE_CONTROL =
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandlerTestCase.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.List;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -79,9 +80,25 @@
         throws Exception
  {
    TestCaseUtils.startServer();
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--set", "server-fqdn:" + "127.0.0.1");
  }
  @AfterClass
  public void tearDown() throws Exception {
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--remove", "server-fqdn:" + "127.0.0.1");
  }
  /**
   * Retrieves a set of invvalid configuration entries.
@@ -234,7 +251,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:test.user",
      "-o", "authzid=u:test.user",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -286,7 +302,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:uid=test.user,o=test",
      "-o", "authzid=dn:uid=test.user,o=test",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -338,7 +353,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:test.user",
      "-o", "authzid=u:test.user",
      "-o", "realm=o=test",
      "-w", "wrongpassword",
      "-b", "",
      "-s", "base",
@@ -390,7 +404,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:uid=test.user,o=test",
      "-o", "authzid=dn:uid=test.user,o=test",
      "-o", "realm=o=test",
      "-w", "wrongpassword",
      "-b", "",
      "-s", "base",
@@ -440,7 +453,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:test.user",
      "-o", "authzid=u:test.user",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -490,7 +502,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:uid=test.user,o=test",
      "-o", "authzid=dn:uid=test.user,o=test",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -540,7 +551,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:invaliddn",
      "-o", "authzid=dn:invaliddn",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -590,7 +600,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:doesntexist",
      "-o", "authzid=u:doesntexist",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -640,7 +649,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:uid=doesntexist,o=test",
      "-o", "authzid=dn:uid=doesntexist,o=test",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -671,7 +679,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:",
      "-o", "authzid=u:",
      "-o", "realm=o=test",
      "-w", "",
      "-b", "",
      "-s", "base",
@@ -702,7 +709,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:",
      "-o", "authzid=dn:",
      "-o", "realm=o=test",
      "-w", "",
      "-b", "",
      "-s", "base",
@@ -733,7 +739,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=",
      "-o", "authzid=",
      "-o", "realm=o=test",
      "-w", "",
      "-b", "",
      "-s", "base",
@@ -778,7 +783,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:uid=test.user,o=test",
      "-o", "authzid=",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -808,7 +812,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:cn=Directory Manager",
      "-o", "authzid=dn:cn=Directory Manager",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -860,7 +863,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:cn=Second Root DN",
      "-o", "authzid=dn:cn=Second Root DN",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -909,7 +911,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:uid=test.user,o=test",
      "-o", "authzid=dn:uid=nonexistent,o=test",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -954,7 +955,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:uid=test.user,o=test",
      "-o", "authzid=u:nonexistent",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
@@ -999,7 +999,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:uid=test.user,o=test",
      "-o", "authzid=dn:malformed",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPAuthenticationHandlerTestCase.java
@@ -29,7 +29,9 @@
import java.io.File;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -41,6 +43,7 @@
import org.opends.server.TestCaseUtils;
import org.opends.messages.Message;
import org.opends.server.admin.std.server.DigestMD5SASLMechanismHandlerCfg;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.controls.PasswordPolicyRequestControl;
import org.opends.server.core.AddOperation;
@@ -63,6 +66,7 @@
public class LDAPAuthenticationHandlerTestCase
       extends ToolsTestCase
{
   String hostname;
  /**
   * Ensures that the Directory Server is running.
   *
@@ -73,6 +77,7 @@
         throws Exception
  {
    TestCaseUtils.startServer();
    getFQDN();
  }
@@ -1412,11 +1417,9 @@
    saslProperties.put("authid", propList);
    propList = new ArrayList<String>();
    propList.add("o=test");
    saslProperties.put("realm", propList);
    LDAPAuthenticationHandler authHandler =
         new LDAPAuthenticationHandler(r, w, "localhost", messageID);
         new LDAPAuthenticationHandler(r, w, this.hostname, messageID);
    authHandler.doSASLBind(new ASN1OctetString(),
                           new ASN1OctetString("password"),
                           "DIGEST-MD5", saslProperties, requestControls,
@@ -1476,11 +1479,9 @@
    saslProperties.put("authid", propList);
    propList = new ArrayList<String>();
    propList.add("o=test");
    saslProperties.put("realm", propList);
    LDAPAuthenticationHandler authHandler =
         new LDAPAuthenticationHandler(r, w, "localhost", messageID);
         new LDAPAuthenticationHandler(r, w, this.hostname, messageID);
    authHandler.doSASLBind(new ASN1OctetString(),
                           new ASN1OctetString("password"),
                           "DIGEST-MD5", saslProperties, requestControls,
@@ -1793,15 +1794,11 @@
    saslProperties.put("authid", propList);
    propList = new ArrayList<String>();
    propList.add("o=test");
    saslProperties.put("realm", propList);
    propList = new ArrayList<String>();
    propList.add("auth");
    saslProperties.put("qop", propList);
    LDAPAuthenticationHandler authHandler =
         new LDAPAuthenticationHandler(r, w, "localhost", messageID);
         new LDAPAuthenticationHandler(r, w, this.hostname, messageID);
    authHandler.doSASLBind(new ASN1OctetString(),
                           new ASN1OctetString("password"),
                           "DIGEST-MD5", saslProperties, requestControls,
@@ -2412,11 +2409,9 @@
    saslProperties.put("authid", propList);
    propList = new ArrayList<String>();
    propList.add("o=test");
    saslProperties.put("realm", propList);
    LDAPAuthenticationHandler authHandler =
         new LDAPAuthenticationHandler(r, w, "localhost", messageID);
         new LDAPAuthenticationHandler(r, w, this.hostname, messageID);
    authHandler.doSASLBind(new ASN1OctetString(),
                           new ASN1OctetString("password"),
                           "DIGEST-MD5", saslProperties, requestControls,
@@ -4154,11 +4149,9 @@
    saslProperties.put("authid", propList);
    propList = new ArrayList<String>();
    propList.add("o=test");
    saslProperties.put("realm", propList);
    LDAPAuthenticationHandler authHandler =
         new LDAPAuthenticationHandler(r, w, "localhost", messageID);
         new LDAPAuthenticationHandler(r, w, this.hostname, messageID);
    authHandler.doSASLBind(new ASN1OctetString(),
                           new ASN1OctetString("password"),
                           "DIGEST-MD5", saslProperties, requestControls,
@@ -4290,5 +4283,14 @@
    s.close();
  }
  private void getFQDN() {
      try {
         this.hostname = InetAddress.getLocalHost().getCanonicalHostName();
      } catch(UnknownHostException ex) {
         this.hostname = "localhost";
      }
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPCompareTestCase.java
@@ -32,6 +32,7 @@
import java.io.FileWriter;
import java.util.ArrayList;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -44,7 +45,7 @@
import org.opends.server.types.OperatingSystem;
import org.opends.server.types.ResultCode;
import org.opends.server.util.Base64;
import org.opends.server.tools.dsconfig.DSConfig;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
@@ -77,6 +78,12 @@
  {
    TestCaseUtils.startServer();
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--set", "server-fqdn:" + "127.0.0.1");
    File pwFile = File.createTempFile("valid-bind-password-", ".txt");
    pwFile.deleteOnExit();
    FileWriter fileWriter = new FileWriter(pwFile);
@@ -92,6 +99,14 @@
    invalidPasswordFile = pwFile.getAbsolutePath();
  }
  @AfterClass
  public void tearDown() throws Exception {
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--remove", "server-fqdn:" + "127.0.0.1");
  }
  /**
@@ -883,6 +898,7 @@
         "sn: User",
         "cn: Test User",
         "ds-privilege-name: bypass-acl",
         "ds-privilege-name: proxied-auth",
         "userPassword: password",
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
@@ -902,7 +918,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:test.user",
      "-o", "authzid=u:test.user",
      "-o", "realm=o=test",
      "-w", "password",
      "--noPropertiesFile",
      "givenName:Test",
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPModifyTestCase.java
@@ -32,6 +32,7 @@
import java.io.FileWriter;
import java.util.ArrayList;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -79,6 +80,11 @@
  {
    TestCaseUtils.startServer();
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--set", "server-fqdn:" + "127.0.0.1");
    File pwFile = File.createTempFile("valid-bind-password-", ".txt");
    pwFile.deleteOnExit();
    FileWriter fileWriter = new FileWriter(pwFile);
@@ -100,6 +106,14 @@
  }
  @AfterClass
  public void tearDown() throws Exception {
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--remove", "server-fqdn:" + "127.0.0.1");
  }
  /**
   * Retrieves sets of invalid arguments that may not be used to initialize
@@ -824,7 +838,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:test.user",
      "-o", "authzid=u:test.user",
      "-o", "realm=o=test",
      "-w", "password",
      "--noPropertiesFile",
      "-f", modifyFilePath
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java
@@ -32,6 +32,7 @@
import java.io.FileWriter;
import java.util.ArrayList;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -75,6 +76,11 @@
  {
    TestCaseUtils.startServer();
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--set", "server-fqdn:" + "127.0.0.1");
    File pwFile = File.createTempFile("valid-bind-password-", ".txt");
    pwFile.deleteOnExit();
    FileWriter fileWriter = new FileWriter(pwFile);
@@ -91,6 +97,14 @@
  }
  @AfterClass
  public void tearDown() throws Exception {
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--remove", "server-fqdn:" + "127.0.0.1");
  }
  /**
   * Retrieves sets of invalid arguments that may not be used to initialize
@@ -1054,7 +1068,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:test.user",
      "-o", "authzid=u:test.user",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "",
      "-s", "base",
opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java
@@ -115,6 +115,11 @@
  {
    TestCaseUtils.startServer();
    TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--set", "server-fqdn:" + "127.0.0.1");
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.addEntries(
      "dn: cn=Unprivileged Root,cn=Root DNs,cn=config",
@@ -303,6 +308,11 @@
  public void cleanUp()
         throws Exception
  {
   TestCaseUtils.dsconfig(
            "set-sasl-mechanism-handler-prop",
            "--handler-name", "DIGEST-MD5",
            "--remove", "server-fqdn:" + "127.0.0.1");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
@@ -1595,7 +1605,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:cn=Privileged User,o=test",
      "-o", "authzid=dn:",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1627,7 +1636,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:cn=Privileged User,o=test",
      "-o", "authzid=dn:cn=Privileged User,o=test",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1659,7 +1667,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:cn=Privileged User,o=test",
      "-o", "authzid=dn:cn=Unprivileged User,o=test",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1691,7 +1698,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:privileged.user",
      "-o", "authzid=u:",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1723,7 +1729,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:privileged.user",
      "-o", "authzid=u:privileged.user",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1755,7 +1760,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:privileged.user",
      "-o", "authzid=u:unprivileged.user",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1787,7 +1791,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:cn=Unprivileged User,o=test",
      "-o", "authzid=dn:",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1819,7 +1822,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:cn=Unprivileged User,o=test",
      "-o", "authzid=dn:cn=Unprivileged User,o=test",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1851,7 +1853,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=dn:cn=Unprivileged User,o=test",
      "-o", "authzid=dn:cn=Privileged User,o=test",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1883,7 +1884,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:unprivileged.user",
      "-o", "authzid=u:",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1915,7 +1915,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:unprivileged.user",
      "-o", "authzid=u:unprivileged.user",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",
@@ -1947,7 +1946,6 @@
      "-o", "mech=DIGEST-MD5",
      "-o", "authid=u:unprivileged.user",
      "-o", "authzid=u:privileged.user",
      "-o", "realm=o=test",
      "-w", "password",
      "-b", "o=test",
      "-s", "base",