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
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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> |
| | |
| | | <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 |
| | |
| | | </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> |
| | |
| | | <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> |
| | |
| | | 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"> |
| | |
| | | </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> |
| | |
| | | </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 |
| | |
| | | <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> |
| | |
| | | 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 \ |
| | |
| | | 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 \ |
| | |
| | | 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 |
| | |
| | | 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 \ |
| | |
| | | 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 \ |
| | |
| | | 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) |
| | | |
| | |
| | | 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); |
| | | |
| | | } |
| | | |
| | |
| | | * @return The connection handler that accepted this client |
| | | * connection. |
| | | */ |
| | | public abstract ConnectionHandler getConnectionHandler(); |
| | | public abstract ConnectionHandler<?> getConnectionHandler(); |
| | | |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 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 |
| | |
| | | * |
| | | * @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) |
| | |
| | | * @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) |
| | |
| | | * 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. |
| | | * |
| | |
| | | */ |
| | | 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(); |
| | | |
| | | |
| | | /** |
| | |
| | | * @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. |
| | |
| | | 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). |
| | |
| | | */ |
| | | 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. |
| | |
| | | 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. |
| | |
| | | //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. |
| | |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean isAnonymousUser() { |
| | | return !clientConnection.getAuthenticationInfo().isAuthenticated(); |
| | | return !authInfo.isAuthenticated(); |
| | | } |
| | | |
| | | /** |
| | |
| | | * {@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; |
| | | } |
| | | |
| | | /** |
| | |
| | | /* |
| | | * Some kind of authentication is required. |
| | | */ |
| | | AuthenticationInfo authInfo=clientConnection.getAuthenticationInfo(); |
| | | if(authInfo.isAuthenticated()) { |
| | | if(authMethod==EnumAuthMethod.AUTHMETHOD_SIMPLE) { |
| | | if(authInfo.hasAuthenticationType(AuthenticationType.SIMPLE)) { |
| | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean isMemberOf(Group group) { |
| | | public boolean isMemberOf(Group<?> group) { |
| | | boolean ret; |
| | | try { |
| | | if(useAuthzid) { |
| | |
| | | * {@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; |
| | |
| | | * {@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; |
| | |
| | | * @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 |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | |
| | | * {@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; |
| | |
| | | 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.*; |
| | |
| | | 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. |
| | | * |
| | |
| | | * 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, |
| | |
| | | * |
| | | * @return The active access control handler (never {@code null}). |
| | | */ |
| | | public AccessControlHandler getAccessControlHandler() |
| | | public AccessControlHandler<?> getAccessControlHandler() |
| | | { |
| | | return accessControlHandler.get(); |
| | | } |
| | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean mayProxy(Entry proxyUser, Entry proxiedUser, |
| | | Operation operation) { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | 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"; |
| | | |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@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} |
| | | */ |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@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(); |
| | | } |
| | | } |
| | | |
| | |
| | | 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; |
| | |
| | | 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.*; |
| | | |
| | |
| | | */ |
| | | 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 |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@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. |
| | |
| | | * 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 |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@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"; |
| | | } |
| | | } |
| | | |
| | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isActive() { |
| | | //This provider is always active. |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public ConnectionSecurityProvider newInstance(ClientConnection |
| | | clientConnection, |
| New file |
| | |
| | | /* |
| | | * 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); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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(); |
| | | } |
| | | } |
| | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isActive() { |
| | | //This provider is always active. |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public ConnectionSecurityProvider newInstance(ClientConnection |
| | | clientConnection, |
| | |
| | | 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; |
| | |
| | | // 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. |
| | |
| | | |
| | | |
| | | this.connectionHandler = connectionHandler; |
| | | if (connectionHandler.isAdminConnectionHandler()) { |
| | | setNetworkGroup(NetworkGroup.getAdminNetworkGroup()); |
| | | } |
| | | this.clientChannel = clientChannel; |
| | | this.securityProvider = null; |
| | | this.clearSecurityProvider = null; |
| | |
| | | */ |
| | | 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 |
| | |
| | | "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 |
| | |
| | | |
| | | |
| | | /** |
| | | * 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 = |
| | |
| | | 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; |
| | |
| | | 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. |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:test.user", |
| | | "-o", "authzid=u:test.user", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-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", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:test.user", |
| | | "-o", "authzid=u:test.user", |
| | | "-o", "realm=o=test", |
| | | "-w", "wrongpassword", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-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", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:test.user", |
| | | "-o", "authzid=u:test.user", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-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", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=dn:invaliddn", |
| | | "-o", "authzid=dn:invaliddn", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:doesntexist", |
| | | "-o", "authzid=u:doesntexist", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-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", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:", |
| | | "-o", "authzid=u:", |
| | | "-o", "realm=o=test", |
| | | "-w", "", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=dn:", |
| | | "-o", "authzid=dn:", |
| | | "-o", "realm=o=test", |
| | | "-w", "", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=", |
| | | "-o", "authzid=", |
| | | "-o", "realm=o=test", |
| | | "-w", "", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=dn:uid=test.user,o=test", |
| | | "-o", "authzid=", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | |
| | | |
| | | 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; |
| | |
| | | |
| | | 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; |
| | |
| | | public class LDAPAuthenticationHandlerTestCase |
| | | extends ToolsTestCase |
| | | { |
| | | String hostname; |
| | | /** |
| | | * Ensures that the Directory Server is running. |
| | | * |
| | |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.startServer(); |
| | | getFQDN(); |
| | | } |
| | | |
| | | |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | |
| | | s.close(); |
| | | } |
| | | |
| | | private void getFQDN() { |
| | | try { |
| | | this.hostname = InetAddress.getLocalHost().getCanonicalHostName(); |
| | | } catch(UnknownHostException ex) { |
| | | this.hostname = "localhost"; |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | |
| | | 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; |
| | |
| | | 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.*; |
| | |
| | | { |
| | | 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); |
| | |
| | | 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"); |
| | | } |
| | | |
| | | |
| | | /** |
| | |
| | | "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"); |
| | |
| | | e.getOperationalAttributes()); |
| | | assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); |
| | | |
| | | |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:test.user", |
| | | "-o", "authzid=u:test.user", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "--noPropertiesFile", |
| | | "givenName:Test", |
| | |
| | | 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; |
| | |
| | | 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); |
| | |
| | | } |
| | | |
| | | |
| | | @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 |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:test.user", |
| | | "-o", "authzid=u:test.user", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "--noPropertiesFile", |
| | | "-f", modifyFilePath |
| | |
| | | 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; |
| | |
| | | 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); |
| | |
| | | } |
| | | |
| | | |
| | | @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 |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:test.user", |
| | | "-o", "authzid=u:test.user", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "-b", "", |
| | | "-s", "base", |
| | |
| | | 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", |
| | |
| | | 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(); |
| | | |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:privileged.user", |
| | | "-o", "authzid=u:", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "-b", "o=test", |
| | | "-s", "base", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-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", |
| | |
| | | "-o", "mech=DIGEST-MD5", |
| | | "-o", "authid=u:unprivileged.user", |
| | | "-o", "authzid=u:", |
| | | "-o", "realm=o=test", |
| | | "-w", "password", |
| | | "-b", "o=test", |
| | | "-s", "base", |
| | |
| | | "-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", |
| | |
| | | "-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", |