| opends/resource/schema/02-config.ldif | ●●●●● patch | view | raw | blame | history | |
| opends/resource/schema/05-samba.ldif | ●●●●● patch | view | raw | blame | history | |
| opends/src/admin/defn/org/opends/server/admin/std/SambaPasswordPluginConfiguration.xml | ●●●●● patch | view | raw | blame | history | |
| opends/src/admin/messages/SambaPasswordPluginCfgDefn.properties | ●●●●● patch | view | raw | blame | history | |
| opends/src/messages/messages/plugin.properties | ●●●●● patch | view | raw | blame | history | |
| opends/src/server/org/opends/server/plugins/SambaPasswordPlugin.java | ●●●●● patch | view | raw | blame | history | |
| opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SambaPasswordPluginTestCase.java | ●●●●● patch | view | raw | blame | history |
opends/resource/schema/02-config.ldif
@@ -2563,6 +2563,14 @@ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.8 NAME 'ds-cfg-pwd-sync-policy' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.9 NAME 'ds-cfg-samba-administrator-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top @@ -4289,4 +4297,10 @@ ds-task-purge-conflicts-historical-purge-completed-in-time $ ds-task-purge-conflicts-historical-purged-values-count ) X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.2 NAME 'ds-cfg-samba-password-plugin' SUP ds-cfg-plugin STRUCTURAL MUST ( ds-cfg-pwd-sync-policy ) MAY ( ds-cfg-samba-administrator-dn ) X-ORIGIN 'OpenDJ Directory Server' ) opends/resource/schema/05-samba.ldif
New file @@ -0,0 +1,88 @@ # 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 2011 profiq s.r.o. # Portions copyright 2011 ForgeRock AS. # # dn: cn=schema objectClass: top objectClass: ldapSubentry objectClass: subschema attributeTypes: ( 1.3.6.1.4.1.7165.2.1.24 NAME 'sambaLMPassword' DESC 'LanManager Password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.25 NAME 'sambaNTPassword' DESC 'MD4 hash of the unicode password' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.26 NAME 'sambaAcctFlags' DESC 'Account Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{16} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.27 NAME 'sambaPwdLastSet' DESC 'Timestamp of the last password update' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.28 NAME 'sambaPwdCanChange' DESC 'Timestamp of when the user is allowed to update the password' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.29 NAME 'sambaPwdMustChange' DESC 'Timestamp of when the password will expire' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.30 NAME 'sambaLogonTime' DESC 'Timestamp of last logon' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.31 NAME 'sambaLogoffTime' DESC 'Timestamp of last logoff' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.32 NAME 'sambaKickoffTime' DESC 'Timestamp of when the user will be logged off automatically' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.48 NAME 'sambaBadPasswordCount' DESC 'Bad password attempt count' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.49 NAME 'sambaBadPasswordTime' DESC 'Time of the last bad password attempt' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.55 NAME 'sambaLogonHours' DESC 'Logon Hours' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.33 NAME 'sambaHomeDrive' DESC 'Driver letter of home directory mapping' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{4} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.34 NAME 'sambaLogonScript' DESC 'Logon script path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.35 NAME 'sambaProfilePath' DESC 'Roaming profile path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.36 NAME 'sambaUserWorkstations' DESC 'List of user workstations the user is allowed to logon to' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{255} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.37 NAME 'sambaHomePath' DESC 'Home directory UNC path' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.38 NAME 'sambaDomainName' DESC 'Windows NT domain to which the user belongs' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.47 NAME 'sambaMungedDial' DESC 'Base64 encoded user parameter string' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1050} ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.54 NAME 'sambaPasswordHistory' DESC 'Concatenated MD4 hashes of the unicode passwords used on this account' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{1024} ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.20 NAME 'sambaSID' DESC 'Security ID' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.23 NAME 'sambaPrimaryGroupSID' DESC 'Primary Group Security ID' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.51 NAME 'sambaSIDList' DESC 'Security ID List' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.19 NAME 'sambaGroupType' DESC 'NT Group Type' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.21 NAME 'sambaNextUserRid' DESC 'Next NT rid to give our for users' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.22 NAME 'sambaNextGroupRid' DESC 'Next NT rid to give out for groups' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.39 NAME 'sambaNextRid' DESC 'Next NT rid to give out for anything' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.40 NAME 'sambaAlgorithmicRidBase' DESC 'Base at which the samba RID generation algorithm should operate' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.41 NAME 'sambaShareName' DESC 'Share Name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.42 NAME 'sambaOptionName' DESC 'Option Name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.43 NAME 'sambaBoolOption' DESC 'A boolean option' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.44 NAME 'sambaIntegerOption' DESC 'An integer option' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.45 NAME 'sambaStringOption' DESC 'A string option' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.46 NAME 'sambaStringListOption' DESC 'A string list option' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.52 NAME 'sambaPrivilegeList' DESC 'Privileges List' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64} ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.53 NAME 'sambaTrustFlags' DESC 'Trust Password Flags' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.58 NAME 'sambaMinPwdLength' DESC 'Minimal password length (default: 5)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.59 NAME 'sambaPwdHistoryLength' DESC 'Length of Password History Entries (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.60 NAME 'sambaLogonToChgPwd' DESC 'Force Users to logon for password change (default: 0 => off, 2 => on)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.61 NAME 'sambaMaxPwdAge' DESC 'Maximum password age, in seconds (default: -1 => never expire passwords)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.62 NAME 'sambaMinPwdAge' DESC 'Minimum password age, in seconds (default: 0 => allow immediate password change)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.63 NAME 'sambaLockoutDuration' DESC 'Lockout duration in minutes (default: 30, -1 => forever)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.64 NAME 'sambaLockoutObservationWindow' DESC 'Reset time after lockout in minutes (default: 30)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.65 NAME 'sambaLockoutThreshold' DESC 'Lockout users after bad logon attempts (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.66 NAME 'sambaForceLogoff' DESC 'Disconnect Users outside logon hours (default: -1 => off, 0 => on)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.4.1.7165.2.1.67 NAME 'sambaRefuseMachinePwdChange' DESC 'Allow Machine Password changes (default: 0 => off)' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) objectClasses: ( 1.3.6.1.4.1.7165.2.2.6 NAME 'sambaSamAccount' DESC 'Samba 3.0 Auxilary SAM Account' SUP top AUXILIARY MUST ( uid $ sambaSID ) MAY ( cn $ sambaLMPassword $ sambaNTPassword $ sambaPwdLastSet $ sambaLogonTime $ sambaLogoffTime $ sambaKickoffTime $ sambaPwdCanChange $ sambaPwdMustChange $ sambaAcctFlags $ displayName $ sambaHomePath $ sambaHomeDrive $ sambaLogonScript $ sambaProfilePath $ description $ sambaUserWorkstations $ sambaPrimaryGroupSID $ sambaDomainName $ sambaMungedDial $ sambaBadPasswordCount $ sambaBadPasswordTime $ sambaPasswordHistory $ sambaLogonHours)) objectClasses: ( 1.3.6.1.4.1.7165.2.2.4 NAME 'sambaGroupMapping' DESC 'Samba Group Mapping' SUP top AUXILIARY MUST ( gidNumber $ sambaSID $ sambaGroupType ) MAY ( displayName $ description $ sambaSIDList)) objectClasses: ( 1.3.6.1.4.1.7165.2.2.14 NAME 'sambaTrustPassword' DESC 'Samba Trust Password' SUP top STRUCTURAL MUST ( sambaDomainName $ sambaNTPassword $ sambaTrustFlags ) MAY ( sambaSID $ sambaPwdLastSet )) objectClasses: ( 1.3.6.1.4.1.7165.2.2.5 NAME 'sambaDomain' DESC 'Samba Domain Information' SUP top STRUCTURAL MUST ( sambaDomainName $ sambaSID ) MAY ( sambaNextRid $ sambaNextGroupRid $ sambaNextUserRid $ sambaAlgorithmicRidBase $ sambaMinPwdLength $ sambaPwdHistoryLength $ sambaLogonToChgPwd $ sambaMaxPwdAge $ sambaMinPwdAge $ sambaLockoutDuration $ sambaLockoutObservationWindow $ sambaLockoutThreshold $ sambaForceLogoff $ sambaRefuseMachinePwdChange )) objectClasses: ( 1.3.6.1.4.1.7165.1.2.2.7 NAME 'sambaUnixIdPool' DESC 'Pool for allocating UNIX uids/gids' SUP top AUXILIARY MUST ( uidNumber $ gidNumber )) objectClasses: ( 1.3.6.1.4.1.7165.1.2.2.8 NAME 'sambaIdmapEntry' DESC 'Mapping from a SID to an ID' SUP top AUXILIARY MUST ( sambaSID ) MAY ( uidNumber $ gidNumber )) objectClasses: ( 1.3.6.1.4.1.7165.1.2.2.9 NAME 'sambaSidEntry' DESC 'Structural Class for a SID' SUP top STRUCTURAL MUST ( sambaSID )) objectClasses: ( 1.3.6.1.4.1.7165.1.2.2.10 NAME 'sambaConfig' DESC 'Samba Configuration Section' SUP top AUXILIARY MAY ( description )) objectClasses: ( 1.3.6.1.4.1.7165.2.2.11 NAME 'sambaShare' DESC 'Samba Share Section' SUP top STRUCTURAL MUST ( sambaShareName ) MAY ( description )) objectClasses: ( 1.3.6.1.4.1.7165.2.2.12 NAME 'sambaConfigOption' DESC 'Samba Configuration Option' SUP top STRUCTURAL MUST ( sambaOptionName ) MAY ( sambaBoolOption $ sambaIntegerOption $ sambaStringOption $ sambaStringListoption $ description )) objectClasses: ( 1.3.6.1.4.1.7165.2.2.13 NAME 'sambaPrivilege' DESC 'Samba Privilege' SUP top AUXILIARY MUST ( sambaSID ) MAY ( sambaPrivilegeList )) opends/src/admin/defn/org/opends/server/admin/std/SambaPasswordPluginConfiguration.xml
New file @@ -0,0 +1,120 @@ <?xml version="1.0" encoding="utf-8"?> <!-- ! 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 2011 profiq s.r.o. ! Portions copyright 2011 ForgeRock AS ! --> <adm:managed-object name="samba-password-plugin" plural-name="samba-password-plugins" package="org.opends.server.admin.std" extends="plugin" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap"> <adm:synopsis>Samba Password Synchronization Plugin.</adm:synopsis> <adm:description> This plugin captures clear-text password changes for a user and generates LanMan or NTLM hashes for the respective Samba attributes (sambaLMPassword and sambaNTPassword). </adm:description> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-samba-password-plugin</ldap:name> <ldap:superior>ds-cfg-plugin</ldap:superior> </ldap:object-class> </adm:profile> <adm:property-override name="java-class"> <adm:default-behavior> <adm:defined> <adm:value>org.opends.server.plugins.SambaPasswordPlugin</adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property-override name="plugin-type" advanced="true"> <adm:default-behavior> <adm:defined> <adm:value>preoperationmodify</adm:value> <adm:value>postoperationextended</adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property name="pwd-sync-policy" mandatory="true" multi-valued="true"> <adm:synopsis> Specifies which Samba passwords should be kept synchronized. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>sync-nt-password</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:enumeration> <adm:value name="sync-nt-password"> <adm:synopsis> Synchronize the NT password attribute "sambaNTPassword" </adm:synopsis> </adm:value> <adm:value name="sync-lm-password"> <adm:synopsis> Synchronize the LanMan password attribute "sambaLMPassword" </adm:synopsis> </adm:value> </adm:enumeration> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name> ds-cfg-pwd-sync-policy </ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="samba-administrator-dn" mandatory="false"> <adm:synopsis> Specifies the distinguished name of the user which Samba uses to perform Password Modify extended operations against this directory server in order to synchronize the userPassword attribute after the LanMan or NT passwords have been updated. </adm:synopsis> <adm:description> The user must have the 'password-reset' privilege and should not be a root user. This user name can be used in order to identify Samba connections and avoid double re-synchronization of the same password. If this property is left undefined, then no password updates will be skipped. </adm:description> <adm:default-behavior> <adm:alias> <adm:synopsis>Synchronize all updates to user passwords</adm:synopsis> </adm:alias> </adm:default-behavior> <adm:syntax> <adm:dn /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-samba-administrator-dn</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opends/src/admin/messages/SambaPasswordPluginCfgDefn.properties
New file @@ -0,0 +1,68 @@ user-friendly-name=Samba Password Plugin user-friendly-plural-name=Samba Password Plugins synopsis=Samba Password Synchronization Plugin. description=This plugin captures clear-text password changes for a user and generates LanMan or NTLM hashes for the respective Samba attributes (sambaLMPassword and sambaNTPassword). property.enabled.synopsis=Indicates whether the plug-in is enabled for use. property.invoke-for-internal-operations.synopsis=Indicates whether the plug-in should be invoked for internal operations. property.invoke-for-internal-operations.description=Any plug-in that can be invoked for internal operations must ensure that it does not create any new internal operatons that can cause the same plug-in to be re-invoked. property.java-class.synopsis=Specifies the fully-qualified name of the Java class that provides the plug-in implementation. property.plugin-type.synopsis=Specifies the set of plug-in types for the plug-in, which specifies the times at which the plug-in is invoked. property.plugin-type.syntax.enumeration.value.intermediateresponse.synopsis=Invoked before sending an intermediate repsonse message to the client. property.plugin-type.syntax.enumeration.value.ldifexport.synopsis=Invoked for each operation to be written during an LDIF export. property.plugin-type.syntax.enumeration.value.ldifimport.synopsis=Invoked for each entry read during an LDIF import. property.plugin-type.syntax.enumeration.value.ldifimportbegin.synopsis=Invoked at the beginning of an LDIF import session. property.plugin-type.syntax.enumeration.value.ldifimportend.synopsis=Invoked at the end of an LDIF import session. property.plugin-type.syntax.enumeration.value.postconnect.synopsis=Invoked whenever a new connection is established to the server. property.plugin-type.syntax.enumeration.value.postdisconnect.synopsis=Invoked whenever an existing connection is terminated (by either the client or the server). property.plugin-type.syntax.enumeration.value.postoperationabandon.synopsis=Invoked after completing the abandon processing. property.plugin-type.syntax.enumeration.value.postoperationadd.synopsis=Invoked after completing the core add processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationbind.synopsis=Invoked after completing the core bind processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationcompare.synopsis=Invoked after completing the core compare processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationdelete.synopsis=Invoked after completing the core delete processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationextended.synopsis=Invoked after completing the core extended processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationmodify.synopsis=Invoked after completing the core modify processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationmodifydn.synopsis=Invoked after completing the core modify DN processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationsearch.synopsis=Invoked after completing the core search processing but before sending the response to the client. property.plugin-type.syntax.enumeration.value.postoperationunbind.synopsis=Invoked after completing the unbind processing. property.plugin-type.syntax.enumeration.value.postresponseadd.synopsis=Invoked after sending the add response to the client. property.plugin-type.syntax.enumeration.value.postresponsebind.synopsis=Invoked after sending the bind response to the client. property.plugin-type.syntax.enumeration.value.postresponsecompare.synopsis=Invoked after sending the compare response to the client. property.plugin-type.syntax.enumeration.value.postresponsedelete.synopsis=Invoked after sending the delete response to the client. property.plugin-type.syntax.enumeration.value.postresponseextended.synopsis=Invoked after sending the extended response to the client. property.plugin-type.syntax.enumeration.value.postresponsemodify.synopsis=Invoked after sending the modify response to the client. property.plugin-type.syntax.enumeration.value.postresponsemodifydn.synopsis=Invoked after sending the modify DN response to the client. property.plugin-type.syntax.enumeration.value.postresponsesearch.synopsis=Invoked after sending the search result done message to the client. property.plugin-type.syntax.enumeration.value.postsynchronizationadd.synopsis=Invoked after completing post-synchronization processing for an add operation. property.plugin-type.syntax.enumeration.value.postsynchronizationdelete.synopsis=Invoked after completing post-synchronization processing for a delete operation. property.plugin-type.syntax.enumeration.value.postsynchronizationmodify.synopsis=Invoked after completing post-synchronization processing for a modify operation. property.plugin-type.syntax.enumeration.value.postsynchronizationmodifydn.synopsis=Invoked after completing post-synchronization processing for a modify DN operation. property.plugin-type.syntax.enumeration.value.preoperationadd.synopsis=Invoked prior to performing the core add processing. property.plugin-type.syntax.enumeration.value.preoperationbind.synopsis=Invoked prior to performing the core bind processing. property.plugin-type.syntax.enumeration.value.preoperationcompare.synopsis=Invoked prior to performing the core compare processing. property.plugin-type.syntax.enumeration.value.preoperationdelete.synopsis=Invoked prior to performing the core delete processing. property.plugin-type.syntax.enumeration.value.preoperationextended.synopsis=Invoked prior to performing the core extended processing. property.plugin-type.syntax.enumeration.value.preoperationmodify.synopsis=Invoked prior to performing the core modify processing. property.plugin-type.syntax.enumeration.value.preoperationmodifydn.synopsis=Invoked prior to performing the core modify DN processing. property.plugin-type.syntax.enumeration.value.preoperationsearch.synopsis=Invoked prior to performing the core search processing. property.plugin-type.syntax.enumeration.value.preparseabandon.synopsis=Invoked prior to parsing an abandon request. property.plugin-type.syntax.enumeration.value.preparseadd.synopsis=Invoked prior to parsing an add request. property.plugin-type.syntax.enumeration.value.preparsebind.synopsis=Invoked prior to parsing a bind request. property.plugin-type.syntax.enumeration.value.preparsecompare.synopsis=Invoked prior to parsing a compare request. property.plugin-type.syntax.enumeration.value.preparsedelete.synopsis=Invoked prior to parsing a delete request. property.plugin-type.syntax.enumeration.value.preparseextended.synopsis=Invoked prior to parsing an extended request. property.plugin-type.syntax.enumeration.value.preparsemodify.synopsis=Invoked prior to parsing a modify request. property.plugin-type.syntax.enumeration.value.preparsemodifydn.synopsis=Invoked prior to parsing a modify DN request. property.plugin-type.syntax.enumeration.value.preparsesearch.synopsis=Invoked prior to parsing a search request. property.plugin-type.syntax.enumeration.value.preparseunbind.synopsis=Invoked prior to parsing an unbind request. property.plugin-type.syntax.enumeration.value.searchresultentry.synopsis=Invoked before sending a search result entry to the client. property.plugin-type.syntax.enumeration.value.searchresultreference.synopsis=Invoked before sending a search result reference to the client. property.plugin-type.syntax.enumeration.value.shutdown.synopsis=Invoked during a graceful directory server shutdown. property.plugin-type.syntax.enumeration.value.startup.synopsis=Invoked during the directory server startup process. property.plugin-type.syntax.enumeration.value.subordinatedelete.synopsis=Invoked in the course of deleting a subordinate entry of a delete operation. property.plugin-type.syntax.enumeration.value.subordinatemodifydn.synopsis=Invoked in the course of moving or renaming an entry subordinate to the target of a modify DN operation. property.pwd-sync-policy.synopsis=Specifies which Samba passwords should be kept synchronized. property.pwd-sync-policy.syntax.enumeration.value.sync-lm-password.synopsis=Synchronize the LanMan password attribute "sambaLMPassword" property.pwd-sync-policy.syntax.enumeration.value.sync-nt-password.synopsis=Synchronize the NT password attribute "sambaNTPassword" property.samba-administrator-dn.synopsis=Specifies the distinguished name of the user which Samba uses to perform Password Modify extended operations against this directory server in order to synchronize the userPassword attribute after the LanMan or NT passwords have been updated. property.samba-administrator-dn.description=The user must have the 'password-reset' privilege and should not be a root user. This user name can be used in order to identify Samba connections and avoid double re-synchronization of the same password. If this property is left undefined, then no password updates will be skipped. property.samba-administrator-dn.default-behavior.alias.synopsis=Synchronize all updates to user passwords opends/src/messages/messages/plugin.properties
@@ -421,3 +421,10 @@ subordinate delete plugin defined in configuration entry %s returned null \ when invoked for connection %d operation %s. This is an illegal response, \ and processing on this operation will be terminated SEVERE_ERR_PLUGIN_SAMBA_SYNC_INVALID_PLUGIN_TYPE_117=An attempt was made to \ register the Samba password synchronization plugin to be invoked as a %s plugin. \ This plugin type is not allowed for this plugin SEVERE_ERR_PLUGIN_SAMBA_SYNC_ENCODING_118=The Samba password \ synchronization plugin could not encode a password for the following reasons: %s SEVERE_ERR_PLUGIN_SAMBA_SYNC_MODIFICATION_PROCESSING_119=The Samba password \ synchronization plugin could not process a modification for the following reason: %s opends/src/server/org/opends/server/plugins/SambaPasswordPlugin.java
New file @@ -0,0 +1,1150 @@ /* * 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 2011 profiq s.r.o. * Portions copyright 2011 ForgeRock AS. */ package org.opends.server.plugins; import static org.opends.messages.PluginMessages.*; import static org.opends.server.extensions.ExtensionsConstants.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.util.StaticUtils.bytesToHexNoSpace; import static org.opends.server.util.StaticUtils.toLowerCase; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.*; import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.meta.PluginCfgDefn; import org.opends.server.admin.std.meta.SambaPasswordPluginCfgDefn.*; import org.opends.server.admin.std.server.SambaPasswordPluginCfg; import org.opends.server.api.plugin.DirectoryServerPlugin; import org.opends.server.api.plugin.PluginResult; import org.opends.server.api.plugin.PluginType; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ModifyOperation; import org.opends.server.loggers.debug.DebugLogger; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.asn1.ASN1; import org.opends.server.protocols.asn1.ASN1Exception; import org.opends.server.protocols.asn1.ASN1Reader; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.types.*; import org.opends.server.types.operation.PostOperationExtendedOperation; import org.opends.server.types.operation.PreOperationModifyOperation; /** * The Samba password synchronization plugin implementation class. * <p> * This plugin synchronizes the userPassword attribute with the Samba password * attribute(s) for all entries containing the specified Samba object class. * <p> * It handles clear-text userPassword modify operations and password modify * extended operations. It does not cover the case of using pre-encoded * password. */ public final class SambaPasswordPlugin extends DirectoryServerPlugin<SambaPasswordPluginCfg> implements ConfigurationChangeListener<SambaPasswordPluginCfg> { /** * The implementation of this algorithm has been derived from BouncyCastle.org * whose license can be found at http://www.bouncycastle.org/licence.html: * <p> * Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle * (http://www.bouncycastle.org) * <p> * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * <p> * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * <p> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ static final class MD4MessageDigest extends MessageDigest { // Class is package private for testing. private final byte[] xBuf = new byte[4]; private int xBufOff; private long byteCount; private static final int DIGEST_LENGTH = 16; private int H1, H2, H3, H4; // IV's private final int[] X = new int[16]; private int xOff; // // round 1 left rotates // private static final int S11 = 3; private static final int S12 = 7; private static final int S13 = 11; private static final int S14 = 19; // // round 2 left rotates // private static final int S21 = 3; private static final int S22 = 5; private static final int S23 = 9; private static final int S24 = 13; // // round 3 left rotates // private static final int S31 = 3; private static final int S32 = 9; private static final int S33 = 11; private static final int S34 = 15; /** * Creates a new MD4 message digest algorithm. */ MD4MessageDigest() { super("MD4"); engineReset(); } /** * {@inheritDoc} */ @Override public byte[] engineDigest() { final byte[] digestBytes = new byte[DIGEST_LENGTH]; finish(); unpackWord(H1, digestBytes, 0); unpackWord(H2, digestBytes, 4); unpackWord(H3, digestBytes, 8); unpackWord(H4, digestBytes, 12); engineReset(); return digestBytes; } /** * {@inheritDoc} */ @Override public void engineReset() { byteCount = 0; xBufOff = 0; for (int i = 0; i < xBuf.length; i++) { xBuf[i] = 0; } H1 = 0x67452301; H2 = 0xefcdab89; H3 = 0x98badcfe; H4 = 0x10325476; xOff = 0; for (int i = 0; i != X.length; i++) { X[i] = 0; } } /** * {@inheritDoc} */ @Override public void engineUpdate(final byte input) { xBuf[xBufOff++] = input; if (xBufOff == xBuf.length) { processWord(xBuf, 0); xBufOff = 0; } byteCount++; } /** * {@inheritDoc} */ @Override public void engineUpdate(final byte[] in, int inOff, int len) { // // fill the current word // while ((xBufOff != 0) && (len > 0)) { update(in[inOff]); inOff++; len--; } // // process whole words. // while (len > xBuf.length) { processWord(in, inOff); inOff += xBuf.length; len -= xBuf.length; byteCount += xBuf.length; } // // load in the remainder. // while (len > 0) { update(in[inOff]); inOff++; len--; } } /* * F, G, H and I are the basic MD4 functions. */ private int F(final int u, final int v, final int w) { return (u & v) | (~u & w); } private void finish() { final long bitLength = (byteCount << 3); // // add the pad bytes. // engineUpdate((byte) 128); while (xBufOff != 0) { engineUpdate((byte) 0); } processLength(bitLength); processBlock(); } private int G(final int u, final int v, final int w) { return (u & v) | (u & w) | (v & w); } private int H(final int u, final int v, final int w) { return u ^ v ^ w; } private void processBlock() { int a = H1; int b = H2; int c = H3; int d = H4; // // Round 1 - F cycle, 16 times. // a = rotateLeft(a + F(b, c, d) + X[0], S11); d = rotateLeft(d + F(a, b, c) + X[1], S12); c = rotateLeft(c + F(d, a, b) + X[2], S13); b = rotateLeft(b + F(c, d, a) + X[3], S14); a = rotateLeft(a + F(b, c, d) + X[4], S11); d = rotateLeft(d + F(a, b, c) + X[5], S12); c = rotateLeft(c + F(d, a, b) + X[6], S13); b = rotateLeft(b + F(c, d, a) + X[7], S14); a = rotateLeft(a + F(b, c, d) + X[8], S11); d = rotateLeft(d + F(a, b, c) + X[9], S12); c = rotateLeft(c + F(d, a, b) + X[10], S13); b = rotateLeft(b + F(c, d, a) + X[11], S14); a = rotateLeft(a + F(b, c, d) + X[12], S11); d = rotateLeft(d + F(a, b, c) + X[13], S12); c = rotateLeft(c + F(d, a, b) + X[14], S13); b = rotateLeft(b + F(c, d, a) + X[15], S14); // // Round 2 - G cycle, 16 times. // a = rotateLeft(a + G(b, c, d) + X[0] + 0x5a827999, S21); d = rotateLeft(d + G(a, b, c) + X[4] + 0x5a827999, S22); c = rotateLeft(c + G(d, a, b) + X[8] + 0x5a827999, S23); b = rotateLeft(b + G(c, d, a) + X[12] + 0x5a827999, S24); a = rotateLeft(a + G(b, c, d) + X[1] + 0x5a827999, S21); d = rotateLeft(d + G(a, b, c) + X[5] + 0x5a827999, S22); c = rotateLeft(c + G(d, a, b) + X[9] + 0x5a827999, S23); b = rotateLeft(b + G(c, d, a) + X[13] + 0x5a827999, S24); a = rotateLeft(a + G(b, c, d) + X[2] + 0x5a827999, S21); d = rotateLeft(d + G(a, b, c) + X[6] + 0x5a827999, S22); c = rotateLeft(c + G(d, a, b) + X[10] + 0x5a827999, S23); b = rotateLeft(b + G(c, d, a) + X[14] + 0x5a827999, S24); a = rotateLeft(a + G(b, c, d) + X[3] + 0x5a827999, S21); d = rotateLeft(d + G(a, b, c) + X[7] + 0x5a827999, S22); c = rotateLeft(c + G(d, a, b) + X[11] + 0x5a827999, S23); b = rotateLeft(b + G(c, d, a) + X[15] + 0x5a827999, S24); // // Round 3 - H cycle, 16 times. // a = rotateLeft(a + H(b, c, d) + X[0] + 0x6ed9eba1, S31); d = rotateLeft(d + H(a, b, c) + X[8] + 0x6ed9eba1, S32); c = rotateLeft(c + H(d, a, b) + X[4] + 0x6ed9eba1, S33); b = rotateLeft(b + H(c, d, a) + X[12] + 0x6ed9eba1, S34); a = rotateLeft(a + H(b, c, d) + X[2] + 0x6ed9eba1, S31); d = rotateLeft(d + H(a, b, c) + X[10] + 0x6ed9eba1, S32); c = rotateLeft(c + H(d, a, b) + X[6] + 0x6ed9eba1, S33); b = rotateLeft(b + H(c, d, a) + X[14] + 0x6ed9eba1, S34); a = rotateLeft(a + H(b, c, d) + X[1] + 0x6ed9eba1, S31); d = rotateLeft(d + H(a, b, c) + X[9] + 0x6ed9eba1, S32); c = rotateLeft(c + H(d, a, b) + X[5] + 0x6ed9eba1, S33); b = rotateLeft(b + H(c, d, a) + X[13] + 0x6ed9eba1, S34); a = rotateLeft(a + H(b, c, d) + X[3] + 0x6ed9eba1, S31); d = rotateLeft(d + H(a, b, c) + X[11] + 0x6ed9eba1, S32); c = rotateLeft(c + H(d, a, b) + X[7] + 0x6ed9eba1, S33); b = rotateLeft(b + H(c, d, a) + X[15] + 0x6ed9eba1, S34); H1 += a; H2 += b; H3 += c; H4 += d; // // reset the offset and clean out the word buffer. // xOff = 0; for (int i = 0; i != X.length; i++) { X[i] = 0; } } private void processLength(final long bitLength) { if (xOff > 14) { processBlock(); } X[14] = (int) (bitLength & 0xffffffff); X[15] = (int) (bitLength >>> 32); } private void processWord(final byte[] in, final int inOff) { X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); if (xOff == 16) { processBlock(); } } /* * rotate int x left n bits. */ private int rotateLeft(final int x, final int n) { return (x << n) | (x >>> (32 - n)); } private void unpackWord(final int word, final byte[] out, final int outOff) { out[outOff] = (byte) word; out[outOff + 1] = (byte) (word >>> 8); out[outOff + 2] = (byte) (word >>> 16); out[outOff + 3] = (byte) (word >>> 24); } } /** * Plugin configuration object. */ private SambaPasswordPluginCfg config; // The name of the Samba LanMan password attribute. private static final String SAMBA_LM_PASSWORD_ATTRIBUTE_NAME = "sambaLMPassword"; // The name of the Samba NT password attribute. private static final String SAMBA_NT_PASSWORD_ATTRIBUTE_NAME = "sambaNTPassword"; // The name of the Samba account object class. private static final String SAMBA_SAM_ACCOUNT_OC_NAME = "sambaSAMAccount"; /** * Debug tracer object to log debugging information. */ private static final DebugTracer TRACER = DebugLogger.getTracer(); /** * Password Modify Extended Operation OID. */ private static final String PWMOD_EXTOP_OID = "1.3.6.1.4.1.4203.1.11.1"; /** * Magic string to be used as salt. */ private static final String MAGIC_STR = "KGS!@#$%"; /** * Add the parity to the 56-bit key converting it to 64-bit key. * * @param key56 * 56-bit key. * @return 64-bit key. */ private static byte[] addParity(final byte[] key56) { final byte[] key64 = new byte[8]; final int[] key7 = new int[7]; final int[] key8 = new int[8]; for (int i = 0; i < 7; i++) { key7[i] = key56[i] & 0xFF; } key8[0] = key7[0]; key8[1] = ((key7[0] << 7) & 0xFF | (key7[1] >> 1)); key8[2] = ((key7[1] << 6) & 0xFF | (key7[2] >> 2)); key8[3] = ((key7[2] << 5) & 0xFF | (key7[3] >> 3)); key8[4] = ((key7[3] << 4) & 0xFF | (key7[4] >> 4)); key8[5] = ((key7[4] << 3) & 0xFF | (key7[5] >> 5)); key8[6] = ((key7[5] << 2) & 0xFF | (key7[6] >> 6)); key8[7] = (key7[6] << 1); for (int i = 0; i < 8; i++) { key64[i] = (byte) (setOddParity(key8[i])); } return key64; } /** * Create a LanMan hash from a clear-text password. * * @param password * Clear-text password. * @return Hex string version of the hash based on the clear-text password. * @throws UnsupportedEncodingException * if the <code>US-ASCII</code> coding is not available. * @throws NoSuchAlgorithmException * if the algorithm does not exist for the used provider. * @throws InvalidKeyException * if the key is inappropriate to initialize the cipher. * @throws NoSuchPaddingException * if the padding scheme is not available. * @throws IllegalBlockSizeException * if this encryption algorithm is unable to process the input data * provided * @throws BadPaddingException * if this cipher is in decryption mode, and (un)padding has been * requested, but the decrypted data is not bounded by the * appropriate padding bytes */ private static String lmHash(final String password) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { // Password has to be OEM encoded and in upper case final byte[] oemPass = password.toUpperCase().getBytes("US-ASCII"); // It shouldn't be longer then 14 bytes int length = 14; if (oemPass.length < length) { length = oemPass.length; } // The password should be divided into two 7-byte keys final byte[] key1 = new byte[7]; final byte[] key2 = new byte[7]; if (length <= 7) { System.arraycopy(oemPass, 0, key1, 0, length); } else { System.arraycopy(oemPass, 0, key1, 0, 7); System.arraycopy(oemPass, 7, key2, 0, length - 7); } // We create two DES keys using key1 and key2 to on the magic string final SecretKey lowKey = new SecretKeySpec(addParity(key1), "DES"); final SecretKey highKey = new SecretKeySpec(addParity(key2), "DES"); final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); des.init(Cipher.ENCRYPT_MODE, lowKey); final byte[] lowHash = des.doFinal(MAGIC_STR.getBytes()); des.init(Cipher.ENCRYPT_MODE, highKey); final byte[] highHash = des.doFinal(MAGIC_STR.getBytes()); // We finally merge hashes and return them to the client final byte[] lmHash = new byte[16]; System.arraycopy(lowHash, 0, lmHash, 0, 8); System.arraycopy(highHash, 0, lmHash, 8, 8); return toLowerCase(bytesToHexNoSpace(lmHash)); } /** * Creates a NTLM hash from a clear-text password. * * @param password * Clear text password. * @return Returns a NTLM hash. * @throws NoSuchProviderException * if the BouncyCastle provider does not load * @throws NoSuchAlgorithmException * if the MD4 algorithm is not found * @throws UnsupportedEncodingException * if the encoding <code>UnicodeLittleUnmarked</code> is not * supported. */ private static String ntHash(final String password) throws NoSuchProviderException, UnsupportedEncodingException, NoSuchAlgorithmException { final byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked"); final MessageDigest md4 = new MD4MessageDigest(); return toLowerCase(bytesToHexNoSpace(md4.digest(unicodePassword))); } /** * Set the parity bit for an integer. * * @param integer * to add the parity bit for. * @return integer with the parity bit set. */ private static int setOddParity(final int parity) { final boolean hasEvenBits = ((parity >>> 7) ^ (parity >>> 6) ^ (parity >>> 5) ^ (parity >>> 4) ^ (parity >>> 3) ^ (parity >>> 2) ^ (parity >>> 1) & 0x01) == 0; if (hasEvenBits) { return parity | 0x01; } else { return parity & 0xFE; } } /** * Default constructor. */ public SambaPasswordPlugin() { super(); } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( final SambaPasswordPluginCfg newConfig) { // No validation required and no restart required. config = newConfig; return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ @Override public PluginResult.PostOperation doPostOperation( final PostOperationExtendedOperation extendedOperation) { /* * If the operation is not Password Modify Extended Operation then skip this * operation. */ if (!extendedOperation.getRequestOID().equals(PWMOD_EXTOP_OID)) { return PluginResult.PostOperation.continueOperationProcessing(); } /* * If the operation has not been successful then ignore the operation. */ if (extendedOperation.getResultCode() != ResultCode.SUCCESS) { return PluginResult.PostOperation.continueOperationProcessing(); } /* * Verify if the operation has been initiated by what was defined as Samba * administrative user. If so, we will skip this operation to avoid double * synchronization of Samba attributes. */ final DN authDN = extendedOperation.getAuthorizationDN(); final DN sambaAdminDN = config.getSambaAdministratorDN(); if (sambaAdminDN != null && !sambaAdminDN.isNullDN()) { if (authDN.equals(sambaAdminDN)) { if (DebugLogger.debugEnabled()) { TRACER.debugInfo("This operation will be skipped because" + " it was performed by Samba admin user: " + sambaAdminDN); } return PluginResult.PostOperation.continueOperationProcessing(); } } // Get the request and response value from the operation. final ByteString requestValue = extendedOperation.getRequestValue(); final ByteString responseValue = extendedOperation.getResponseValue(); ASN1Reader asn1Reader = ASN1.getReader(requestValue.asReader()); DN dn = null; String password = null; // @formatter:off /* * Request and response are defined like this: * * passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1 * * PasswdModifyRequestValue ::= SEQUENCE * { * userIdentity [0] OCTET STRING OPTIONAL * oldPasswd [1] OCTET STRING OPTIONAL * newPasswd [2] OCTET STRING OPTIONAL * } * * PasswdModifyResponseValue ::= SEQUENCE * { * genPasswd [0] OCTET STRING OPTIONAL * } */ // @formatter:on try { // FIXME: need to handle dn: and a: notation. // Read the request value first. asn1Reader.readStartSequence(); // First get the userIdentity field. if (asn1Reader.hasNextElement() && asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_USER_ID) { dn = DN.decode(asn1Reader.readOctetString()); if (DebugLogger.debugEnabled()) { TRACER.debugInfo("Processing DN:" + dn.toNormalizedString()); } } if (DebugLogger.debugEnabled()) { TRACER.debugInfo("Authorization DN: " + authDN); } if (dn == null) { dn = authDN; if (DebugLogger.debugEnabled()) { TRACER.debugInfo("The DN was not found in the request, but " + "we can use the authorization DN."); } } // Before proceeding make sure this entry has samba object class. final Entry entry = DirectoryServer.getEntry(dn); if (!isSynchronizable(entry)) { if (DebugLogger.debugEnabled()) { TRACER.debugInfo("The entry is not Samba object."); } return PluginResult.PostOperation.continueOperationProcessing(); } // Read the old password field if it exists. Skip it either way. if (asn1Reader.hasNextElement() && asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_OLD_PASSWORD) { asn1Reader.skipElement(); } // Read the new password field. if (asn1Reader.hasNextElement() && asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_NEW_PASSWORD) { password = asn1Reader.readOctetStringAsString(); if (DebugLogger.debugEnabled()) { TRACER.debugInfo("Got new password from the request."); } } // Finish reading the request value. asn1Reader.readEndSequence(); /* * If the new password was not found in the request, look into the * response - this usually the case when the password is changed by the * directory manager or when the password is generated. */ if (password == null) { if (DebugLogger.debugEnabled()) { TRACER.debugInfo("Password couldn't be found in the" + " request, getting it from the response."); } // Start reading the response value. asn1Reader = ASN1.getReader(responseValue.asReader()); asn1Reader.readStartSequence(); // Read the generated password field. if (asn1Reader.hasNextElement() && asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD) { password = asn1Reader.readOctetStringAsString(); if (DebugLogger.debugEnabled()) { TRACER.debugInfo("Got the new password from the " + "response."); } } // Finish reading. asn1Reader.readEndSequence(); } /* * Make an internal connection to process the password modification. It * will not trigger this plugin again with the pre-operation modify since * the password passed would be encoded hence the pre operation part would * skip it. */ // FIXME: This should be performed with a write lock taken when processing // the extended operation. However, this is not yet supported by OpenDJ // See OPENDJ-235 - https://bugster.forgerock.org/jira/browse/OPENDJ-235 final InternalClientConnection connection = InternalClientConnection .getRootConnection(); final ModifyOperation modifyOperation = connection.processModify(dn, getModifications(password)); if (DebugLogger.debugEnabled()) { TRACER.debugInfo(modifyOperation.getResultCode().toString()); } } catch (final ASN1Exception e) { /* * The ASN1 reader was not able to process the request because: - it * started reading an element which is not a sequence; or - it was not * able to decode an element; or - it was not able to determine the BER * type of an element; or - it was not able to decode the value as octet * string. Either way, this should not happen, so we just log it. */ if (DebugLogger.debugEnabled()) { TRACER.debugCaught(DebugLogLevel.WARNING, e); } } catch (final DirectoryException e) { /* * This exception occurs if there is a problem while retrieving the entry. * This should never happen as we are processing the post-operation which * succeeded so the entry has to exist if we have reached this point. */ if (DebugLogger.debugEnabled()) { TRACER.debugCaught(DebugLogLevel.WARNING, e); } } return PluginResult.PostOperation.continueOperationProcessing(); } /** * {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation( final PreOperationModifyOperation modifyOperation) { /* * If the passwords are changed in clear text they will be available with * the getNewPasswords() method. If they are encoded the method would return * null. The list of passwords should not be modified. */ final List<AttributeValue> passwords = modifyOperation.getNewPasswords(); /* * If the password list is not empty, we can be sure the current operation * is the one that applies to our case: - it's a modify operation on * userPassword attribute - it's replaces or adds new userPassword attribute * value. If it doesn't then we skip this modify operation. */ if (passwords == null) { return PluginResult.PreOperation.continueOperationProcessing(); } // Skip synchronization operations. if (modifyOperation.isSynchronizationOperation()) { if (DebugLogger.debugEnabled()) { TRACER.debugInfo("Synchronization operation. Skipping."); } return PluginResult.PreOperation.continueOperationProcessing(); } /* * Verify if the operation has been initiated by the Samba administrative * user. If so, we will skip this operation to avoid double synchronization * of Samba attributes. */ final DN authDN = modifyOperation.getAuthorizationDN(); final DN sambaAdminDN = config.getSambaAdministratorDN(); if (sambaAdminDN != null && !sambaAdminDN.isNullDN()) { if (authDN.equals(sambaAdminDN)) { if (DebugLogger.debugEnabled()) { TRACER.debugInfo("This operation will be skipped because" + " it was performed by Samba admin user: " + sambaAdminDN); } return PluginResult.PreOperation.continueOperationProcessing(); } } /* * Before proceeding with the modification, we have to make sure this entry * is indeed a Samba object. */ if (!isSynchronizable(modifyOperation.getCurrentEntry())) { if (DebugLogger.debugEnabled()) { TRACER.debugInfo("Skipping '" + modifyOperation.getEntryDN().toString() + "' because it does not have Samba object class."); } return PluginResult.PreOperation.continueOperationProcessing(); } /* * Proceed with processing: add a new modification to the current modify * operation, so they could be executed at the same time. */ processModification(modifyOperation, passwords); // Continue plugin processing. return PluginResult.PreOperation.continueOperationProcessing(); } /** * {@inheritDoc} */ @Override public void initializePlugin(final Set<PluginType> pluginTypes, final SambaPasswordPluginCfg configuration) throws ConfigException, InitializationException { // Verify config parameters. final LinkedList<Message> messages = new LinkedList<Message>(); if (!isConfigurationAcceptable(configuration, messages)) { for (final Message m : messages) { logError(m); } throw new ConfigException(messages.poll()); } // Register the configuration change listener. configuration.addSambaPasswordChangeListener(this); // Save the configuration. this.config = configuration; } /** * Verifies if the plugin configuration is acceptable. * * @param configuration * The plugin configuration. * @param unacceptableReasons * Reasons why the configuration is not acceptable. * @return Returns <code>true</code> for the correct configuration and * <code>false</code> for the incorrect one. */ public boolean isConfigurationAcceptable( final SambaPasswordPluginCfg configuration, final List<Message> unacceptableReasons) { return isConfigurationChangeAcceptable(configuration, unacceptableReasons); } /** * {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( final SambaPasswordPluginCfg newConfig, final List<Message> messages) { /* * The plugin implements only postoperationmodify and postoperationextended * plugin types. The rest should be rejected. */ final SortedSet<PluginCfgDefn.PluginType> pluginTypes = newConfig .getPluginType(); for (final PluginCfgDefn.PluginType t : pluginTypes) { switch (t) { case PREOPERATIONMODIFY: case POSTOPERATIONEXTENDED: break; default: messages.add(ERR_PLUGIN_SAMBA_SYNC_INVALID_PLUGIN_TYPE.get(String .valueOf(t))); return false; } } return true; } /** * Creates the modifications to modify Samba password attributes. It uses * clear-text password and encodes it with the appropriate algorithm for it's * respective type (NTLM or LanMan); then it wraps it in the modifications to * be added to the modify operation. * * @param password * New password which is to be encoded for Samba. * @return Returns a list of modifications, or null if a problem occurs. */ private List<Modification> getModifications(final String password) { ArrayList<Modification> modifications = new ArrayList<Modification>(); try { if (config.getPwdSyncPolicy().contains(PwdSyncPolicy.SYNC_NT_PASSWORD)) { final Attribute attribute = Attributes.create( SAMBA_NT_PASSWORD_ATTRIBUTE_NAME, ntHash(password)); modifications .add(new Modification(ModificationType.REPLACE, attribute)); } if (config.getPwdSyncPolicy().contains(PwdSyncPolicy.SYNC_LM_PASSWORD)) { final Attribute attribute = Attributes.create( SAMBA_LM_PASSWORD_ATTRIBUTE_NAME, lmHash(password)); modifications .add(new Modification(ModificationType.REPLACE, attribute)); } } catch (final Exception e) { ERR_PLUGIN_SAMBA_SYNC_ENCODING.get(e.getMessage()); if (DebugLogger.debugEnabled()) { TRACER.debugError(e.getMessage(), e); } modifications = null; } return modifications; } /** * Verify if the target entry contains pre-defined Samba object class. * * @param entry * The entry being modified. * @return Returns true if the entry has Samba object class, otherwise returns * false. */ private boolean isSynchronizable(final Entry entry) { final Schema schema = DirectoryServer.getSchema(); final ObjectClass sambaOc = schema .getObjectClass(toLowerCase(SAMBA_SAM_ACCOUNT_OC_NAME)); if (sambaOc == null) { // If the object class is not defined then presumably we're not syncing. return false; } else { return entry.hasObjectClass(sambaOc); } } /** * Adds modifications for the configured Samba password attributes to the * current modify operation. * * @param modifyOperation * Current modify operation which will be modified to add samba * password attribute changes. * @param passwords * List of userPassword clear-text attribute values to be hashed for * Samba */ private void processModification( final PreOperationModifyOperation modifyOperation, final List<AttributeValue> passwords) { // Get the last password (in case there is more then one). final String password = passwords.get(passwords.size() - 1).toString(); try { // Generate the necessary modifications. for (final Modification modification : getModifications(password)) { modifyOperation.addModification(modification); } } catch (final DirectoryException e) { ERR_PLUGIN_SAMBA_SYNC_MODIFICATION_PROCESSING.get(e.getMessage()); if (DebugLogger.debugEnabled()) { TRACER.debugError(e.getMessage()); } } } } opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SambaPasswordPluginTestCase.java
New file @@ -0,0 +1,651 @@ /* * 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 2011 profiq s.r.o. * Portions copyright 2011 ForgeRock AS. */ package org.opends.server.plugins; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static org.opends.server.plugins.SambaPasswordPlugin.MD4MessageDigest; import static org.opends.server.util.StaticUtils.bytesToHexNoSpace; import static org.opends.server.util.StaticUtils.toLowerCase; import java.util.LinkedList; import java.util.List; import org.opends.server.TestCaseUtils; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ExtendedOperation; import org.opends.server.core.ModifyOperation; import org.opends.server.extensions.ExtensionsConstants; import org.opends.server.protocols.asn1.ASN1; import org.opends.server.protocols.asn1.ASN1Writer; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.types.*; import org.opends.server.util.ServerConstants; import org.testng.annotations.*; /** * Unit tests for the Samba password synchronization plugin. */ public class SambaPasswordPluginTestCase extends PluginTestCase { /** * Initializes the synchronization plugin before running this test suite. * * @throws Exception * If an unexpected exception occurred. */ @BeforeClass public void beforeClass() throws Exception { // Start the server TestCaseUtils.startServer(); /* * Configuration for the plugin which would be used to execute tests. */ Entry configEntry = TestCaseUtils .makeEntry( "dn: cn=samba password,cn=Plugins,cn=config", "objectClass: ds-cfg-samba-password-plugin", "objectClass: ds-cfg-plugin", "objectClass: top", "ds-cfg-plugin-type: postoperationextended", "ds-cfg-plugin-type: preoperationmodify", "ds-cfg-pwd-sync-policy: sync-nt-password", "ds-cfg-pwd-sync-policy: sync-lm-password", "ds-cfg-samba-administrator-dn: cn=samba admin,o=test", "cn: samba password", "ds-cfg-enabled: true", "ds-cfg-java-class: org.opends.server.plugins.SambaPasswordPlugin"); // Add the configuration to the running directory instance. TestCaseUtils.addEntry(configEntry); } /** * Cleans up after running this test suite. * * @throws Exception * If an unexpected exception occurred. */ @AfterClass public void afterClass() throws Exception { TestCaseUtils.deleteEntry(DN .decode("cn=samba password,cn=Plugins,cn=config")); } /** * Initializes the test backend before each test. * * @throws Exception * If an unexpected exception occurred. */ @BeforeMethod public void beforeMethod() throws Exception { // Create an empty test backend 'o=test' TestCaseUtils.initializeTestBackend(true); /* * For the Samba administrative user parameter to be set correctly the * backend needs to have an existing user with the 'password-reset' * privilege. */ TestCaseUtils.addEntries("dn: cn=Samba Admin,o=test", "objectClass: top", "objectClass: person", "userPassword: password", "sn: Admin", "cn: Samba Admin", "ds-privilege-name: password-reset"); /* * Create the administrative user without privileges which will be used for * negative configuration tests. */ TestCaseUtils.addEntries("dn: cn=Samba Admin NP,o=test", "objectClass: top", "objectClass: person", "userPassword: password", "sn: Admin NP", "cn: Samba Admin NP"); /* * Samba administrative user needs a permission to manipulate user accounts. * Hence, we add a very permissive ACI. */ InternalClientConnection conn = InternalClientConnection .getRootConnection(); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.ADD, Attributes.create("aci", "(target=\"ldap:///uid=*,o=test\")(targetattr=\"*\")" + "(version 3.0; acl \"Samba admin\"; allow (all) " + "userdn=\"ldap:///cn=samba admin,o=test\";)"))); ModifyOperation modOp = conn.processModify(DN.decode("o=test"), mods); assertEquals(modOp.getResultCode(), ResultCode.SUCCESS); } /** * Returns test data for testMD4Digest taken from RFC 1320. * * @return The test data. */ @DataProvider public Object[][] md4DigestData() { return new Object[][] { { "", "31d6cfe0d16ae931b73c59d7e0c089c0" }, { "a", "bde52cb31de33e46245e05fbdbd6fb24" }, { "abc", "a448017aaf21d8525fc10ae87aa6729d" }, { "message digest", "d9130a8164549fe818874806e1c7014b" }, { "abcdefghijklmnopqrstuvwxyz", "d79e1c308aa5bbcdeea8ed63df412da9" }, { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "043f8582f241db351ce627e153e7f0e4" }, { "12345678901234567890123456789012345678901234567890123456789012345678901234567890", "e33b4ddc9c38f2199c3e7b164fcc0536" }, }; } /** * Tests the MD4 implementation ported from Bouncy Castle using the test data * defined in RFC 1320. * * @param inputString * Input data. * @param expectedHash * Expected hash in hex. * @throws Exception * If an unexpected error occurred. */ @Test(dataProvider = "md4DigestData") public void testMD4Digest(String inputString, String expectedHash) throws Exception { MD4MessageDigest digest = new MD4MessageDigest(); byte[] result = digest.digest(inputString.getBytes("UTF-8")); assertEquals(toLowerCase(bytesToHexNoSpace(result)), expectedHash); } /** * Change the user password using REPLACE modify operation on 'userPassword' * attribute as ROOT user. * * @throws Exception * if the password hash does not match. */ @Test public void testModifyOperationAsRoot() throws Exception { // Test entry Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user1,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User", "givenName: Test", "sn: User", "sambaSID: 123", "userPassword: password"); TestCaseUtils.addEntry(testEntry); // Perform the modify operation InternalClientConnection conn = InternalClientConnection .getRootConnection(); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.REPLACE, Attributes.create( "userPassword", "password"))); ModifyOperation modOp = conn.processModify(testEntry.getDN(), mods); assertEquals(modOp.getResultCode(), ResultCode.SUCCESS); // Verification of the change assertThatPasswordsAreEqualTo(testEntry, "8846f7eaee8fb117ad06bdd830b7586c", "e52cac67419a9a224a3b108f3fa6cb6d"); TestCaseUtils.deleteEntry(testEntry); } private void assertThatPasswordsAreEqualTo(Entry testEntry, String ntPassword, String lmPassword) throws DirectoryException { Entry entry = DirectoryServer.getEntry(testEntry.getDN()); assertNotNull(entry); List<Attribute> sambaAttribute = entry.getAttribute("sambantpassword"); assertNotNull(sambaAttribute); boolean foundNTPassword = false; for (Attribute a : sambaAttribute) { for (AttributeValue val : a) { foundNTPassword = true; assertEquals(val.toString(), ntPassword); } } assertTrue(foundNTPassword, "NT password not found in test entry"); sambaAttribute = entry.getAttribute("sambalmpassword"); assertNotNull(sambaAttribute); boolean foundLMPassword = false; for (Attribute a : sambaAttribute) { for (AttributeValue val : a) { foundLMPassword = true; assertEquals(val.toString(), lmPassword); } } assertTrue(foundLMPassword, "LanMan password not found in test entry"); } /** * Use modify operation to replace the userPassword attribute as a normal * user. * * @throws Exception * if the test fails. */ @Test public void testModifyOperationAsUser() throws Exception { // Test entry Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user3,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User", "givenName: Test", "sn: User", "sambaSID: 123", "userPassword: password"); TestCaseUtils.addEntry(testEntry); // Perform the modify operation AuthenticationInfo authInfo = new AuthenticationInfo(testEntry, false); InternalClientConnection conn = new InternalClientConnection(authInfo); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.REPLACE, Attributes.create( "userPassword", "password"))); ModifyOperation modOp = conn.processModify(testEntry.getDN(), mods); assertEquals(modOp.getResultCode(), ResultCode.SUCCESS); // Verification of the result assertThatPasswordsAreEqualTo(testEntry, "8846f7eaee8fb117ad06bdd830b7586c", "e52cac67419a9a224a3b108f3fa6cb6d"); TestCaseUtils.deleteEntry(testEntry); } /** * Use modify operation to replace the userPassword attribute as Samba * administrative user. This operation should be skipped, that is, the hash * should be unchanged if it existed. * * @throws Exception * if the test fails. */ @Test public void testModifyOperationAsSambaAdmin() throws Exception { // Test entry Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user2,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User", "givenName: Test", "sn: User", "sambaSID: 123", "userPassword: password"); TestCaseUtils.addEntry(testEntry); // Perform the modify operation AuthenticationInfo authInfo = new AuthenticationInfo( TestCaseUtils.makeEntry("dn: cn=Samba Admin,o=test", "objectClass: top", "objectClass: person", "userPassword: password", "sn: Admin", "cn: Samba Admin", "ds-privilege-name: password-reset"), false); InternalClientConnection conn = new InternalClientConnection(authInfo); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.REPLACE, Attributes.create( "userPassword", "password1"))); ModifyOperation modOp = conn.processModify(testEntry.getDN(), mods); assertEquals(modOp.getResultCode(), ResultCode.SUCCESS); // Verification of the result Entry entry = DirectoryServer.getEntry(testEntry.getDN()); assertNotNull(entry); List<Attribute> sambaAttribute = entry.getAttribute("sambantpassword"); assertNull(sambaAttribute); sambaAttribute = entry.getAttribute("sambalmpassword"); assertNull(sambaAttribute); TestCaseUtils.deleteEntry(entry); } /** * If multiple passwords are set, make sure only last one is synchronized. * * @throws Exception * if the test fails. */ @Test public void testModifyOperationMultiplePasswords() throws Exception { // Test entry Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user4,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User", "givenName: Test", "sn: User", "sambaSID: 123", "userPassword: password"); TestCaseUtils.addEntry(testEntry); // Perform the modify operation InternalClientConnection conn = InternalClientConnection .getRootConnection(); LinkedList<Modification> mods = new LinkedList<Modification>(); mods.add(new Modification(ModificationType.ADD, Attributes.create( "userPassword", "password1"))); mods.add(new Modification(ModificationType.ADD, Attributes.create( "userPassword", "password2"))); mods.add(new Modification(ModificationType.ADD, Attributes.create( "userPassword", "password3"))); ModifyOperation modOp = conn.processModify(testEntry.getDN(), mods); assertEquals(modOp.getResultCode(), ResultCode.SUCCESS); // Verification of the result assertThatPasswordsAreEqualTo(testEntry, "bd7dfbf29a93f93c63cb84790da00e63", "e52cac67419a9a221b087c18752bdbee"); TestCaseUtils.deleteEntry(testEntry); } /** * Test the Password Modify Extended Operation as ROOT. * * @throws Exception * if the test fails. */ @Test public void testPWEOAsRoot() throws Exception { // Test entry Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user5,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User", "givenName: Test", "sn: User", "sambaSID: 123", "userPassword: password"); TestCaseUtils.addEntry(testEntry); // Perform the extended operation InternalClientConnection conn = InternalClientConnection .getRootConnection(); /* * Create the Password Modify Extended Operation request. It has the * following format: passwdModifyOID OBJECT IDENTIFIER ::= * 1.3.6.1.4.1.4203.1.11.1 PasswdModifyRequestValue ::= SEQUENCE { * userIdentity [0] OCTET STRING OPTIONAL oldPasswd [1] OCTET STRING * OPTIONAL newPasswd [2] OCTET STRING OPTIONAL } */ ByteStringBuilder bsBuilder = new ByteStringBuilder(); ASN1Writer writer = ASN1.getWriter(bsBuilder); // Start the sequence writer.writeStartSequence(); // Write the DN of the entry we are changing. writer.writeOctetString(ExtensionsConstants.TYPE_PASSWORD_MODIFY_USER_ID, testEntry.getDN().toString()); /* * Since we perform the operation as ROOT, we don't have to put the old * password writer.writeOctetString( * ExtensionsConstants.TYPE_PASSWORD_MODIFY_OLD_PASSWORD, ""); */ // Write the new password writer.writeOctetString( ExtensionsConstants.TYPE_PASSWORD_MODIFY_NEW_PASSWORD, "password"); // End the sequence writer.writeEndSequence(); ExtendedOperation extOp = conn.processExtendedOperation( ServerConstants.OID_PASSWORD_MODIFY_REQUEST, bsBuilder.toByteString()); assert (extOp.getResultCode() == ResultCode.SUCCESS); // Verification of the result assertThatPasswordsAreEqualTo(testEntry, "8846f7eaee8fb117ad06bdd830b7586c", "e52cac67419a9a224a3b108f3fa6cb6d"); TestCaseUtils.deleteEntry(testEntry); } /** * Test the Password Modify Extended Operation as Samba administrative user. * This operation should be skipped. * * @throws Exception * if the test fails. */ @Test public void testPWEOAsSambaAdmin() throws Exception { // Test entry Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user6,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User", "givenName: Test", "sn: User", "sambaSID: 123", "userPassword: password"); TestCaseUtils.addEntry(testEntry); // Perform the extended operation AuthenticationInfo authInfo = new AuthenticationInfo( TestCaseUtils.makeEntry("dn: cn=Samba Admin,o=test", "objectClass: top", "objectClass: person", "userPassword: password", "sn: Admin", "cn: Samba Admin", "ds-privilege-name: password-reset"), false); InternalClientConnection conn = new InternalClientConnection(authInfo); /* * Create the Password Modify Extended Operation request. It has the * following format: passwdModifyOID OBJECT IDENTIFIER ::= * 1.3.6.1.4.1.4203.1.11.1 PasswdModifyRequestValue ::= SEQUENCE { * userIdentity [0] OCTET STRING OPTIONAL oldPasswd [1] OCTET STRING * OPTIONAL newPasswd [2] OCTET STRING OPTIONAL } */ ByteStringBuilder bsBuilder = new ByteStringBuilder(); ASN1Writer writer = ASN1.getWriter(bsBuilder); // Start the sequence writer.writeStartSequence(); // Write the DN of the entry we are changing. writer.writeOctetString(ExtensionsConstants.TYPE_PASSWORD_MODIFY_USER_ID, testEntry.getDN().toString()); /* * Since we perform the operation as Samba admininistrative user, we don't * have to put the old password. writer.writeOctetString( * ExtensionsConstants.TYPE_PASSWORD_MODIFY_OLD_PASSWORD, ""); */ // Write the new password writer.writeOctetString( ExtensionsConstants.TYPE_PASSWORD_MODIFY_NEW_PASSWORD, "password"); // End the sequence writer.writeEndSequence(); ExtendedOperation extOp = conn.processExtendedOperation( ServerConstants.OID_PASSWORD_MODIFY_REQUEST, bsBuilder.toByteString()); assert (extOp.getResultCode() == ResultCode.SUCCESS); // Verification of the result Entry entry = DirectoryServer.getEntry(testEntry.getDN()); assertNotNull(entry); List<Attribute> sambaAttribute = entry.getAttribute("sambantpassword"); assertNull(sambaAttribute); sambaAttribute = entry.getAttribute("sambalmpassword"); assertNull(sambaAttribute); TestCaseUtils.deleteEntry(entry); } /** * Test the Password Modify Extended Operation as normal user. * * @throws Exception * if the test fails. */ @Test public void testPWEOAsUser() throws Exception { // Test entry Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user7,o=test", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User", "givenName: Test", "sn: User", "sambaSID: 123", "userPassword: password"); TestCaseUtils.addEntry(testEntry); // Perform the extended operation AuthenticationInfo authInfo = new AuthenticationInfo(testEntry, false); InternalClientConnection conn = new InternalClientConnection(authInfo); /* * Create the Password Modify Extended Operation request. It has the * following format: passwdModifyOID OBJECT IDENTIFIER ::= * 1.3.6.1.4.1.4203.1.11.1 PasswdModifyRequestValue ::= SEQUENCE { * userIdentity [0] OCTET STRING OPTIONAL oldPasswd [1] OCTET STRING * OPTIONAL newPasswd [2] OCTET STRING OPTIONAL } */ ByteStringBuilder bsBuilder = new ByteStringBuilder(); ASN1Writer writer = ASN1.getWriter(bsBuilder); // Start the sequence writer.writeStartSequence(); // Write the DN of the entry we are changing. writer.writeOctetString(ExtensionsConstants.TYPE_PASSWORD_MODIFY_USER_ID, testEntry.getDN().toString()); // Write the old password writer.writeOctetString( ExtensionsConstants.TYPE_PASSWORD_MODIFY_OLD_PASSWORD, "password"); // Write the new password writer.writeOctetString( ExtensionsConstants.TYPE_PASSWORD_MODIFY_NEW_PASSWORD, "password"); // End the sequence writer.writeEndSequence(); ExtendedOperation extOp = conn.processExtendedOperation( ServerConstants.OID_PASSWORD_MODIFY_REQUEST, bsBuilder.toByteString()); assert (extOp.getResultCode() == ResultCode.SUCCESS); // Verification of the result assertThatPasswordsAreEqualTo(testEntry, "8846f7eaee8fb117ad06bdd830b7586c", "e52cac67419a9a224a3b108f3fa6cb6d"); TestCaseUtils.deleteEntry(testEntry); } }