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
5141 ■■■■ 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 131 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/GSSAPISASLMechanismHandlerConfiguration.xml 81 ●●●●● 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 1631 ●●●● 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 534 ●●●● 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 25 ●●●● 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 38 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPCompareTestCase.java 21 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPModifyTestCase.java 17 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/tools/LDAPSearchTestCase.java 17 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/types/PrivilegeTestCase.java 24 ●●●● 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
@@ -33,18 +33,18 @@
  <adm:synopsis>
    The DIGEST-MD5 SASL mechanism
    is used to perform all processing related to SASL DIGEST-MD5
    authentication.
    authentication.
  </adm:synopsis>
  <adm:description>
    The DIGEST-MD5 SASL mechanism is very similar
    to the CRAM-MD5 mechanism in that it allows for password-based
    authentication without exposing the password in the clear
    (although it does require that both the client and the server
    have access to the clear-text password). Like the CRAM-MD5
    mechanism, it uses data that is randomly generated by the server
    to make it resistant to replay attacks, but it also includes
    randomly-generated data from the client, which makes it also
    resistant to problems resulting from weak server-side random
    The DIGEST-MD5 SASL mechanism is very similar
    to the CRAM-MD5 mechanism in that it allows for password-based
    authentication without exposing the password in the clear
    (although it does require that both the client and the server
    have access to the clear-text password). Like the CRAM-MD5
    mechanism, it uses data that is randomly generated by the server
    to make it resistant to replay attacks, but it also includes
    randomly-generated data from the client, which makes it also
    resistant to problems resulting from weak server-side random
    number generation.
  </adm:description>
  <adm:profile name="ldap">
@@ -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,20 +200,21 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="server-fqdn">
    <adm:synopsis>
      Specifies the DNS-resolvable fully-qualified domain name for the
      server that is used when validating the digest-uri parameter during
      the authentication process.
      server that is used when validating the digest-uri parameter during
      the authentication process.
    </adm:synopsis>
    <adm:description>
      If this configuration attribute is
      present, then the server expects that clients use a digest-uri equal
      to "ldap/" followed by the value of this attribute. For example, if
      the attribute has a value of "directory.example.com", then the
      server expects clients to use a digest-uri of
      "ldap/directory.example.com". If no value is provided, then the
      server does not attempt to validate the digest-uri provided by the
      If this configuration attribute is
      present, then the server expects that clients use a digest-uri equal
      to "ldap/" followed by the value of this attribute. For example, if
      the attribute has a value of "directory.example.com", then the
      server expects clients to use a digest-uri of
      "ldap/directory.example.com". If no value is provided, then the
      server does not attempt to validate the digest-uri provided by the
      client and accepts any value.
    </adm:description>
    <adm:default-behavior>
@@ -158,12 +231,12 @@
          <adm:regex>.*</adm:regex>
          <adm:usage>STRING</adm:usage>
          <adm:synopsis>
            The fully-qualified address that is expected for clients to use
            The fully-qualified address that is expected for clients to use
            when connecting to the server and authenticating via DIGEST-MD5.
          </adm:synopsis>
        </adm:pattern>
      </adm:string>
    </adm:syntax>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-server-fqdn</ldap:name>
opends/src/admin/defn/org/opends/server/admin/std/GSSAPISASLMechanismHandlerConfiguration.xml
@@ -31,15 +31,15 @@
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The GSSAPI SASL mechanism
    The GSSAPI SASL mechanism
    performs all processing related to SASL GSSAPI
    authentication using Kerberos V5.
  </adm:synopsis>
  <adm:description>
    The GSSAPI SASL mechanism provides the ability for clients
    to authenticate themselves to the server using existing
    authentication in a Kerberos environment. This mechanism
    provides the ability to achieve single sign-on for
    The GSSAPI SASL mechanism provides the ability for clients
    to authenticate themselves to the server using existing
    authentication in a Kerberos environment. This mechanism
    provides the ability to achieve single sign-on for
    Kerberos-based clients.
  </adm:description>
  <adm:profile name="ldap">
@@ -85,7 +85,7 @@
    </adm:synopsis>
    <adm:description>
      If provided, this property must be a fully-qualified DNS-resolvable name.
      If this property is not provided, then the server attempts to determine it
      If this property is not provided, then the server attempts to determine it
      from the system-wide Kerberos configuration.
    </adm:description>
    <adm:default-behavior>
@@ -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
@@ -155,8 +220,8 @@
  <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 Kerberos principal
      with this SASL mechanism handler
      to match the Kerberos principal
      included in the SASL bind request to the corresponding
      user in the directory.
    </adm:synopsis>
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,9 +321,29 @@
      //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.
   */
      extends SASLMechanismHandler<DigestMD5SASLMechanismHandlerCfg>
      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
  {
    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);
          DigestMD5SASLMechanismHandlerCfg configuration)
  throws ConfigException, InitializationException {
      configuration.addDigestMD5ChangeListener(this);
      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);
      }
      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.
    DN identityMapperDN = configuration.getIdentityMapperDN();
    identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
    DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5,
                                                 this);
  }
  /**
   * {@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);
        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);
      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);
  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;
        }
      }
      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);
      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 (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;
            }
          }
        }
          saslContext.evaluateInitialStage(bindOp);
      } else {
          saslContext.evaluateFinalStage(bindOp);
      }
    }
    // 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));
    }
    return buffer.toString();
  }
  /**
   * {@inheritDoc}
   */
@@ -1554,7 +210,6 @@
  }
  /**
   * {@inheritDoc}
   */
@@ -1569,7 +224,6 @@
  }
  /**
   * {@inheritDoc}
   */
@@ -1581,23 +235,116 @@
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
              DigestMD5SASLMechanismHandlerCfg configuration)
          DigestMD5SASLMechanismHandlerCfg configuration)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
      ResultCode        resultCode          = ResultCode.SUCCESS;
      boolean           adminActionRequired = false;
    // Get the identity mapper that should be used to find users.
    DN identityMapperDN = configuration.getIdentityMapperDN();
    identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
    currentConfig  = configuration;
      ArrayList<Message> messages            = new ArrayList<Message>();
      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;
      } 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);
  }
    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,221 +113,237 @@
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeSASLMechanismHandler(
                   GSSAPISASLMechanismHandlerCfg configuration)
         throws ConfigException, InitializationException
  {
    configuration.addGSSAPIChangeListener(this);
    currentConfig = configuration;
    configEntryDN = configuration.dn();
    // Get the identity mapper that should be used to find users.
    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();
  public void
  initializeSASLMechanismHandler(GSSAPISASLMechanismHandlerCfg configuration)
  throws ConfigException, InitializationException {
      configuration.addGSSAPIChangeListener(this);
      this.configuration = configuration;
      configEntryDN = configuration.dn();
      try {
          DN identityMapperDN = configuration.getIdentityMapperDN();
          identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
          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);
          }
          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);
      }
      catch (Exception 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);
  /**
   * 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);
      }
    }
  }
    // 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.
    String configFileName;
    try
    {
  /**
   * 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);
          }
      }
  }
  /**
   * 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;
      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 ");
      "storeKey=true useKeyTab=true ");
      String keyTabFile = configuration.getKeytab();
      if (keyTabFile != null)
      {
        w.write("keyTab=\"" + keyTabFile + "\" ");
      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();
    }
    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);
      return configFileName;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizeSASLMechanismHandler()
  {
    currentConfig.removeGSSAPIChangeListener(this);
    DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_GSSAPI);
  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)
    {
      Message message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get();
      bindOperation.setAuthFailureReason(message);
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      return;
    }
    GSSAPIStateInfo stateInfo = null;
    Object saslBindState = clientConnection.getSASLAuthStateInfo();
    if ((saslBindState != null) && (saslBindState instanceof GSSAPIStateInfo))
    {
      stateInfo = (GSSAPIStateInfo) saslBindState;
    }
    else
    {
      try
      {
        stateInfo = new GSSAPIStateInfo(this, bindOperation, serverFQDN);
  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;
      }
      catch (InitializationException ie)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, ie);
        }
        bindOperation.setAuthFailureReason(ie.getMessageObject());
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        clientConnection.setSASLAuthStateInfo(null);
        return;
      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);
               }
              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();
      }
      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);
    }
      saslContext.performAuthentication(loginContext, bindOp);
  }
  /**
   * Retrieves the user account for the user associated with the provided
   * authorization ID.
@@ -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)
  {
    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);
          GSSAPISASLMechanismHandlerCfg configuration) {
      ResultCode        resultCode          = ResultCode.SUCCESS;
      boolean           adminActionRequired = false;
      ArrayList<Message> messages            = new ArrayList<Message>();
      DN identityMapperDN = configuration.getIdentityMapperDN();
      IdentityMapper<?> newIdentityMapper =
          DirectoryServer.getIdentityMapper(identityMapperDN);
      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);
  }
   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()
  {
    return securityProvider;
      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");
@@ -894,7 +910,7 @@
                         e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    String[] args =
    {
      "-h", "127.0.0.1",
@@ -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;
@@ -78,7 +79,12 @@
         throws Exception
  {
    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;
@@ -74,7 +75,12 @@
         throws Exception
  {
    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
@@ -114,7 +114,12 @@
         throws Exception
  {
    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",