[freeipa/f19] Add OTP patches and patch to fix 389-ds ccache

rcritten rcritten at fedoraproject.org
Tue May 14 18:19:56 UTC 2013


commit ccd45fe5f8c69751159e09198e65ace9727df87c
Author: Rob Crittenden <rcritten at redhat.com>
Date:   Tue May 14 14:18:15 2013 -0400

    Add OTP patches and patch to fix 389-ds ccache
    
    The OTP patches add basic support for TOTP and Radius.
    
    The 389-ds patch sets KRB5CCNAME in /etc/sysconfig/dirsrv so it can
    get a usable ccache.

 ...-ipaUserAuthType-and-ipaUserAuthTypeClass.patch |   49 +
 0002-Add-IPA-OTP-schema-and-ACLs.patch             |  288 +
 0003-ipa-kdb-Add-OTP-support.patch                 |  187 +
 ...-the-krb5-FreeIPA-RADIUS-companion-daemon.patch | 1998 +++++++
 ...cessary-prefixes-from-ipa-pwd-extop-files.patch | 5603 ++++++++++++++++++++
 0006-Add-OTP-support-to-ipa-pwd-extop.patch        | 1711 ++++++
 ...ame-so-that-dirsrv-can-work-with-newer-kr.patch |   76 +
 freeipa.spec                                       |   19 +-
 8 files changed, 9930 insertions(+), 1 deletions(-)
---
diff --git a/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch b/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch
new file mode 100644
index 0000000..7c6a1ca
--- /dev/null
+++ b/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch
@@ -0,0 +1,49 @@
+From f807909bc12041af67b0051d4085b0ee1bee56d7 Mon Sep 17 00:00:00 2001
+From: Nathaniel McCallum <npmccallum at redhat.com>
+Date: Thu, 11 Apr 2013 12:30:23 -0400
+Subject: [PATCH 1/6] Add ipaUserAuthType and ipaUserAuthTypeClass
+
+This schema addition will be useful for future commits. It allows us to
+define permitted external authentication methods on both the user and
+global config. The implementation is generic, but the immediate usage
+is for otp support.
+
+https://fedorahosted.org/freeipa/ticket/3365
+http://freeipa.org/page/V3/OTP
+---
+ install/share/60basev3.ldif        | 2 ++
+ install/updates/10-60basev3.update | 4 ++++
+ 2 files changed, 6 insertions(+)
+
+diff --git a/install/share/60basev3.ldif b/install/share/60basev3.ldif
+index 43da2e7..435948f 100644
+--- a/install/share/60basev3.ldif
++++ b/install/share/60basev3.ldif
+@@ -36,6 +36,7 @@ attributeTypes: (2.16.840.1.113730.3.8.11.36 NAME 'ipaSecondaryBaseRID' DESC 'Fi
+ # 2.16.840.1.113730.3.8.11.37 ipaKrbAuthzData
+ attributeTypes: (2.16.840.1.113730.3.8.11.38 NAME 'ipaNTSIDBlacklistIncoming' DESC 'Extra SIDs filtered out from incoming MS-PAC' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA v3')
+ attributeTypes: (2.16.840.1.113730.3.8.11.39 NAME 'ipaNTSIDBlacklistOutgoing' DESC 'Extra SIDs filtered out from outgoing MS-PAC' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA v3')
++attributeTypes: (2.16.840.1.113730.3.8.11.40 NAME 'ipaUserAuthType' DESC 'Allowed authentication methods' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3')
+ objectClasses: (2.16.840.1.113730.3.8.12.1 NAME 'ipaExternalGroup' SUP top STRUCTURAL MUST ( cn ) MAY ( ipaExternalMember $ memberOf $ description $ owner) X-ORIGIN 'IPA v3' )
+ objectClasses: (2.16.840.1.113730.3.8.12.2 NAME 'ipaNTUserAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) MAY ( ipaNTHash $ ipaNTLogonScript $ ipaNTProfilePath $ ipaNTHomeDirectory $ ipaNTHomeDirectoryDrive ) X-ORIGIN 'IPA v3' )
+ objectClasses: (2.16.840.1.113730.3.8.12.3 NAME 'ipaNTGroupAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) X-ORIGIN 'IPA v3' )
+@@ -51,3 +52,4 @@ objectClasses: (2.16.840.1.113730.3.8.12.14 NAME 'ipaIDobject' SUP top AUXILIARY
+ objectClasses: (2.16.840.1.113730.3.8.12.15 NAME 'ipaIDrange' ABSTRACT MUST ( cn $ ipaBaseID $ ipaIDRangeSize ) X-ORIGIN 'IPA v3' )
+ objectClasses: (2.16.840.1.113730.3.8.12.16 NAME 'ipaDomainIDRange' SUP ipaIDrange STRUCTURAL MAY ( ipaBaseRID $ ipaSecondaryBaseRID ) X-ORIGIN 'IPA v3' )
+ objectClasses: (2.16.840.1.113730.3.8.12.17 NAME 'ipaTrustedADDomainRange' SUP ipaIDrange STRUCTURAL MUST ( ipaBaseRID $ ipaNTTrustedDomainSID ) X-ORIGIN 'IPA v3' )
++objectclasses: (2.16.840.1.113730.3.8.12.19 NAME 'ipaUserAuthTypeClass' SUP top AUXILIARY DESC 'Class for authentication methods definition' MAY ipaUserAuthType X-ORIGIN 'IPA v3')
+diff --git a/install/updates/10-60basev3.update b/install/updates/10-60basev3.update
+index 62dd472..476fa3b 100644
+--- a/install/updates/10-60basev3.update
++++ b/install/updates/10-60basev3.update
+@@ -16,3 +16,7 @@ replace:objectClasses: (2.16.840.1.113730.3.8.4.1 NAME 'ipaHost' AUXILIARY MUST
+ 
+ # Fix dc syntax (RFC 2247)
+ replace:attributeTypes:"( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domaincomponent' ) DESC 'Standard LDAP attribute type' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'RFC 2247' )::( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domaincomponent' ) DESC 'Standard LDAP attribute type' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN 'RFC 2247' )"
++
++# Add ipaUserAuthType and ipaUserAuthTypeClass
++add:attributeTypes: (2.16.840.1.113730.3.8.11.40 NAME 'ipaUserAuthType' DESC 'Allowed authentication methods' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3')
++add:objectclasses: (2.16.840.1.113730.3.8.12.19 NAME 'ipaUserAuthTypeClass' SUP top AUXILIARY DESC 'Class for authentication methods definition' MAY ipaUserAuthType X-ORIGIN 'IPA v3')
+-- 
+1.8.2.1
+
diff --git a/0002-Add-IPA-OTP-schema-and-ACLs.patch b/0002-Add-IPA-OTP-schema-and-ACLs.patch
new file mode 100644
index 0000000..01a2771
--- /dev/null
+++ b/0002-Add-IPA-OTP-schema-and-ACLs.patch
@@ -0,0 +1,288 @@
+From edca6946f81e01ddc5f3d5a8389560a704f11d7b Mon Sep 17 00:00:00 2001
+From: Nathaniel McCallum <npmccallum at redhat.com>
+Date: Thu, 11 Apr 2013 13:24:46 -0400
+Subject: [PATCH 2/6] Add IPA OTP schema and ACLs
+
+This commit adds schema support for two factor authentication via
+OTP devices, including RADIUS or TOTP. This schema will be used
+by future patches which will enable two factor authentication
+directly.
+
+https://fedorahosted.org/freeipa/ticket/3365
+http://freeipa.org/page/V3/OTP
+---
+ install/share/70ipaotp.ldif                       | 28 +++++++++++++++++++++++
+ install/share/Makefile.am                         |  1 +
+ install/share/copy-schema-to-ca.py                |  1 +
+ install/share/default-aci.ldif                    | 10 +++++++-
+ install/updates/10-70ipaotp.update                | 25 ++++++++++++++++++++
+ install/updates/40-otp.update                     |  9 ++++++++
+ install/updates/Makefile.am                       |  4 +++-
+ ipalib/constants.py                               |  1 +
+ ipaserver/install/dsinstance.py                   |  3 ++-
+ ipaserver/install/plugins/update_anonymous_aci.py | 25 ++++++++++++++------
+ 10 files changed, 97 insertions(+), 10 deletions(-)
+ create mode 100644 install/share/70ipaotp.ldif
+ create mode 100644 install/updates/10-70ipaotp.update
+ create mode 100644 install/updates/40-otp.update
+
+diff --git a/install/share/70ipaotp.ldif b/install/share/70ipaotp.ldif
+new file mode 100644
+index 0000000..3cfe872
+--- /dev/null
++++ b/install/share/70ipaotp.ldif
+@@ -0,0 +1,28 @@
++# IPA OTP schema
++# BaseOID: 2.16.840.1.113730.3.8.16
++# See RFC 4517 for Syntax OID definitions
++dn: cn=schema
++attributeTypes: (2.16.840.1.113730.3.8.16.1.1  NAME 'ipatokenUniqueID' DESC 'Token Unique Identifier' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.2  NAME 'ipatokenDisabled' DESC 'Optionally marks token as Disabled' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.3  NAME 'ipatokenNotBefore' DESC 'Token validity date' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.4  NAME 'ipatokenNotAfter' DESC 'Token expiration date' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.5  NAME 'ipatokenVendor' DESC 'Optional Vendor identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.6  NAME 'ipatokenModel' DESC 'Optional Model identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.7  NAME 'ipatokenSerial' DESC 'OTP Token Serial number' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.8  NAME 'ipatokenOTPkey' DESC 'OTP Token Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.9  NAME 'ipatokenOTPalgorithm' DESC 'OTP Token Algorithm' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.10 NAME 'ipatokenOTPdigits' DESC 'OTP Token Number of digits' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.11 NAME 'ipatokenTOTPclockOffset' DESC 'TOTP clock offset' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.12 NAME 'ipatokenTOTPtimeStep' DESC 'TOTP time-step' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.13 NAME 'ipatokenOwner' DESC 'User entry that owns this token' SUP distinguishedName SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.14 NAME 'ipatokenRadiusUserName' DESC 'Corresponding Radius username' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.15 NAME 'ipatokenRadiusConfigLink' DESC 'Corresponding Radius Configuration link' SUP distinguishedName SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.16 NAME 'ipatokenRadiusServer' DESC 'Server String Configuration' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.17 NAME 'ipatokenRadiusSecret' DESC 'Server's Secret' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.18 NAME 'ipatokenRadiusTimeout' DESC 'Server Timeout' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries' DESC 'Number of allowed Retries' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++attributeTypes: (2.16.840.1.113730.3.8.16.1.20 NAME 'ipatokenUserMapAttribute' DESC 'Attribute to map from the user entry for RADIUS server authentication' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++objectClasses:  (2.16.840.1.113730.3.8.16.2.1  NAME 'ipaToken' SUP top ABSTRACT DESC 'Abstract token class for tokens' MUST (ipatokenUniqueID) MAY (description $ ipatokenOwner $ ipatokenDisabled $ ipatokenNotBefore $ ipatokenNotAfter $ ipatokenVendor $ ipatokenModel $ ipatokenSerial) X-ORIGIN 'IPA OTP')
++objectClasses:  (2.16.840.1.113730.3.8.16.2.2  NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MAY (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenTOTPclockOffset $ ipatokenTOTPtimeStep) X-ORIGIN 'IPA OTP')
++objectClasses:  (2.16.840.1.113730.3.8.16.2.3  NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MUST (ipatokenRadiusConfigLink) MAY (ipatokenRadiusUserName) X-ORIGIN 'IPA OTP')
++objectClasses:  (2.16.840.1.113730.3.8.16.2.4  NAME 'ipatokenRadiusConfiguration' SUP top STRUCTURAL DESC 'Proxy Radius Configuration' MUST (cn $ ipatokenRadiusServer $ ipatokenRadiusSecret) MAY (description $ ipatokenRadiusTimeout $ ipatokenRadiusRetries $ ipatokenUserMapAttribute) X-ORIGIN 'IPA OTP')
+diff --git a/install/share/Makefile.am b/install/share/Makefile.am
+index f8f9b74..8823723 100644
+--- a/install/share/Makefile.am
++++ b/install/share/Makefile.am
+@@ -11,6 +11,7 @@ app_DATA =				\
+ 	60ipadns.ldif			\
+ 	61kerberos-ipav3.ldif		\
+ 	65ipasudo.ldif			\
++	70ipaotp.ldif			\
+ 	anonymous-vlv.ldif		\
+ 	bootstrap-template.ldif		\
+ 	caJarSigningCert.cfg.template	\
+diff --git a/install/share/copy-schema-to-ca.py b/install/share/copy-schema-to-ca.py
+index 4e2054e..1888f12 100755
+--- a/install/share/copy-schema-to-ca.py
++++ b/install/share/copy-schema-to-ca.py
+@@ -31,6 +31,7 @@ SCHEMA_FILENAMES = (
+     "60ipadns.ldif",
+     "61kerberos-ipav3.ldif",
+     "65ipasudo.ldif",
++    "70ipaotp.ldif",
+     "05rfc2247.ldif",
+ )
+ 
+diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif
+index f173f79..18881ec 100644
+--- a/install/share/default-aci.ldif
++++ b/install/share/default-aci.ldif
+@@ -3,7 +3,7 @@
+ dn: $SUFFIX
+ changetype: modify
+ add: aci
+-aci: (target != "ldap:///idnsname=*,cn=dns,$SUFFIX")(targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey || userPKCS12 || ipaNTHash || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming")(version 3.0; acl "Enable Anonymous access"; allow (read, search, compare) userdn = "ldap:///anyone";)
++aci: (targetfilter = "(&(!(objectClass=ipaToken))(!(objectClass=ipatokenTOTP))(!(objectClass=ipatokenRadiusProxyUser))(!(objectClass=ipatokenRadiusConfiguration)))")(target != "ldap:///idnsname=*,cn=dns,$SUFFIX")(targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey || userPKCS12 || ipaNTHash || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming")(version 3.0; acl "Enable Anonymous access"; allow (read, search, compare) userdn = "ldap:///anyone";)
+ aci: (targetattr = "memberOf || memberHost || memberUser")(version 3.0; acl "No anonymous access to member information"; deny (read,search,compare) userdn != "ldap:///all";)
+ aci: (targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey || krbPrincipalName || krbCanonicalName || krbUPEnabled || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData || krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount || ipaUniqueId || memberOf || serverHostName || enrolledBy || ipaNTHash")(version 3.0; acl "Admin can manage any entry"; allow (all) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)
+ aci: (targetattr = "userpassword || krbprincipalkey || sambalmpassword || sambantpassword")(version 3.0; acl "selfservice:Self can write own password"; allow (write) userdn="ldap:///self";)
+@@ -96,3 +96,11 @@ dn: cn=ipa,cn=etc,$SUFFIX
+ changetype: modify
+ add: aci
+ aci: (target="ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX")(targetattr="userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";)
++
++# Let users manage their own tokens
++dn: $SUFFIX
++changetype: modify
++add: aci
++aci: (targetfilter = "(objectClass=ipaToken)")(targetattrs = "objectclass || ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can read basic token info"; allow (read, search, compare) userattr = "ipatokenOwner#USERDN";)
++aci: (targetfilter = "(objectClass=ipaToken)")(targetattrs = "ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can write basic token info"; allow (write) userattr = "ipatokenOwner#USERDN";)
++aci: (targetfilter = "(objectClass=ipatokenTOTP)")(targetattrs = "ipatokenOTPkey || ipatokenOTPalgorithm || ipatokenOTPdigits || ipatokenTOTPclockOffset || ipatokenTOTPtimeStep")(version 3.0; acl "Users can add TOTP token secrets"; allow (write, search) userattr = "ipatokenOwner#USERDN";)
+diff --git a/install/updates/10-70ipaotp.update b/install/updates/10-70ipaotp.update
+new file mode 100644
+index 0000000..600ef9c
+--- /dev/null
++++ b/install/updates/10-70ipaotp.update
+@@ -0,0 +1,25 @@
++dn: cn=schema
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.1  NAME 'ipatokenUniqueID' DESC 'Token Unique Identifier' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.2  NAME 'ipatokenDisabled' DESC 'Optionally marks token as Disabled' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.3  NAME 'ipatokenNotBefore' DESC 'Token validity date' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.4  NAME 'ipatokenNotAfter' DESC 'Token expiration date' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.5  NAME 'ipatokenVendor' DESC 'Optional Vendor identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.6  NAME 'ipatokenModel' DESC 'Optional Model identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.7  NAME 'ipatokenSerial' DESC 'OTP Token Serial number' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.8  NAME 'ipatokenOTPkey' DESC 'OTP Token Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.9  NAME 'ipatokenOTPalgorithm' DESC 'OTP Token Algorithm' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.10 NAME 'ipatokenOTPdigits' DESC 'OTP Token Number of digits' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.11 NAME 'ipatokenTOTPclockOffset' DESC 'TOTP clock offset' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.12 NAME 'ipatokenTOTPtimeStep' DESC 'TOTP time-step' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.13 NAME 'ipatokenOwner' DESC 'User entry that owns this token' SUP distinguishedName SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.14 NAME 'ipatokenRadiusUserName' DESC 'Corresponding Radius username' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.15 NAME 'ipatokenRadiusConfigLink' DESC 'Corresponding Radius Configuration link' SUP distinguishedName SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.16 NAME 'ipatokenRadiusServer' DESC 'Server String Configuration' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.17 NAME 'ipatokenRadiusSecret' DESC 'Server's Secret' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.18 NAME 'ipatokenRadiusTimeout' DESC 'Server Timeout' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries' DESC 'Number of allowed Retries' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.20 NAME 'ipatokenUserMapAttribute' DESC 'Attribute to map from the user entry for RADIUS server authentication' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
++add:objectClasses:  (2.16.840.1.113730.3.8.16.2.1  NAME 'ipaToken' SUP top ABSTRACT DESC 'Abstract token class for tokens' MUST (ipatokenUniqueID) MAY (description $$ ipatokenOwner $$ ipatokenDisabled $$ ipatokenNotBefore $$ ipatokenNotAfter $$ ipatokenVendor $$ ipatokenModel $$ ipatokenSerial) X-ORIGIN 'IPA OTP')
++add:objectClasses:  (2.16.840.1.113730.3.8.16.2.2  NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MAY (ipatokenOTPkey $$ ipatokenOTPalgorithm $$ ipatokenOTPdigits $$ ipatokenTOTPclockOffset $$ ipatokenTOTPtimeStep) X-ORIGIN 'IPA OTP')
++add:objectClasses:  (2.16.840.1.113730.3.8.16.2.3  NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MUST (ipatokenRadiusConfigLink) MAY (ipatokenRadiusUserName) X-ORIGIN 'IPA OTP')
++add:objectClasses:  (2.16.840.1.113730.3.8.16.2.4  NAME 'ipatokenRadiusConfiguration' SUP top STRUCTURAL DESC 'Proxy Radius Configuration' MUST (cn $$ ipatokenRadiusServer $$ ipatokenRadiusSecret) MAY (description $$ ipatokenRadiusTimeout $$ ipatokenRadiusRetries $$ ipatokenUserMapAttribute) X-ORIGIN 'IPA OTP')
+diff --git a/install/updates/40-otp.update b/install/updates/40-otp.update
+new file mode 100644
+index 0000000..ff36c87
+--- /dev/null
++++ b/install/updates/40-otp.update
+@@ -0,0 +1,9 @@
++dn: cn=otp,$SUFFIX
++default: objectClass: nsContainer
++default: objectClass: top
++default: cn: otp
++
++dn: $SUFFIX
++add: aci:'(targetfilter = "(objectClass=ipaToken)")(targetattrs = "objectclass || ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can read basic token info"; allow (read, search, compare) userattr = "ipatokenOwner#USERDN";)'
++add: aci:'(targetfilter = "(objectClass=ipaToken)")(targetattrs = "ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can write basic token info"; allow (write) userattr = "ipatokenOwner#USERDN";)'
++add: aci:'(targetfilter = "(objectClass=ipatokenTOTP)")(targetattrs = "ipatokenOTPkey || ipatokenOTPalgorithm || ipatokenOTPdigits || ipatokenTOTPclockOffset || ipatokenTOTPtimeStep")(version 3.0; acl "Users can add TOTP token secrets"; allow (write, search) userattr = "ipatokenOwner#USERDN";)'
+diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
+index ab3f411..787a51c 100644
+--- a/install/updates/Makefile.am
++++ b/install/updates/Makefile.am
+@@ -4,6 +4,7 @@ appdir = $(IPA_DATA_DIR)/updates
+ app_DATA =				\
+ 	10-60basev2.update		\
+ 	10-60basev3.update		\
++	10-70ipaotp.update		\
+ 	10-RFC2307bis.update		\
+ 	10-RFC4876.update		\
+ 	10-config.update		\
+@@ -13,6 +14,7 @@ app_DATA =				\
+ 	10-ssh.update			\
+ 	10-bind-schema.update		\
+ 	10-uniqueness.update		\
++	10-schema_compat.update		\
+ 	19-managed-entries.update	\
+ 	20-aci.update			\
+ 	20-dna.update			\
+@@ -20,7 +22,6 @@ app_DATA =				\
+ 	20-indices.update		\
+ 	20-nss_ldap.update		\
+ 	20-replication.update		\
+-	10-schema_compat.update		\
+ 	20-user_private_groups.update	\
+ 	20-winsync_index.update		\
+ 	21-replicas_container.update	\
+@@ -32,6 +33,7 @@ app_DATA =				\
+ 	40-replication.update		\
+ 	40-dns.update			\
+ 	40-automember.update		\
++	40-otp.update			\
+ 	45-roles.update			\
+ 	50-lockout-policy.update	\
+ 	50-groupuuid.update		\
+diff --git a/ipalib/constants.py b/ipalib/constants.py
+index ecb9255..de08457 100644
+--- a/ipalib/constants.py
++++ b/ipalib/constants.py
+@@ -109,6 +109,7 @@ DEFAULT_CONFIG = (
+     ('container_dna', DN(('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))),
+     ('container_dna_posix_ids', DN(('cn', 'posix-ids'), ('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))),
+     ('container_realm_domains', DN(('cn', 'Realm Domains'), ('cn', 'ipa'), ('cn', 'etc'))),
++    ('container_otp', DN(('cn', 'otp'))),
+ 
+     # Ports, hosts, and URIs:
+     # FIXME: let's renamed xmlrpc_uri to rpc_xml_uri
+diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
+index e6bb054..7c809ec 100644
+--- a/ipaserver/install/dsinstance.py
++++ b/ipaserver/install/dsinstance.py
+@@ -409,7 +409,8 @@ class DsInstance(service.Service):
+                              "60basev3.ldif",
+                              "60ipadns.ldif",
+                              "61kerberos-ipav3.ldif",
+-                             "65ipasudo.ldif"):
++                             "65ipasudo.ldif",
++                             "70ipaotp.ldif"):
+             target_fname = schema_dirname(self.serverid) + schema_fname
+             shutil.copyfile(ipautil.SHARE_DIR + schema_fname, target_fname)
+             os.chmod(target_fname, 0440)    # read access for dirsrv user/group
+diff --git a/ipaserver/install/plugins/update_anonymous_aci.py b/ipaserver/install/plugins/update_anonymous_aci.py
+index 2b7446a..1e75113 100644
+--- a/ipaserver/install/plugins/update_anonymous_aci.py
++++ b/ipaserver/install/plugins/update_anonymous_aci.py
+@@ -20,8 +20,6 @@
+ from copy import deepcopy
+ from ipaserver.install.plugins import FIRST, LAST
+ from ipaserver.install.plugins.baseupdate import PostUpdate
+-#from ipalib.frontend import Updater
+-#from ipaserver.install.plugins import baseupdate
+ from ipalib import api
+ from ipalib.aci import ACI
+ from ipalib.plugins import aci
+@@ -37,6 +35,8 @@ class update_anonymous_aci(PostUpdate):
+         aciname = u'Enable Anonymous access'
+         aciprefix = u'none'
+         ldap = self.obj.backend
++        targetfilter = '(&(!(objectClass=ipaToken))(!(objectClass=ipatokenTOTP))(!(objectClass=ipatokenRadiusProxyUser))(!(objectClass=ipatokenRadiusConfiguration)))'
++        filter = None
+ 
+         (dn, entry_attrs) = ldap.get_entry(api.env.basedn, ['aci'])
+ 
+@@ -45,6 +45,9 @@ class update_anonymous_aci(PostUpdate):
+         rawaci = aci._find_aci_by_name(acilist, aciprefix, aciname)
+ 
+         attrs = rawaci.target['targetattr']['expression']
++        rawfilter = rawaci.target.get('targetfilter', None)
++        if rawfilter is not None:
++            filter = rawfilter['expression']
+ 
+         update_attrs = deepcopy(attrs)
+ 
+@@ -54,12 +57,10 @@ class update_anonymous_aci(PostUpdate):
+                 needed_attrs.append(attr)
+ 
+         update_attrs.extend(needed_attrs)
+-        if len(attrs) == len(update_attrs):
++        if (len(attrs) == len(update_attrs) and
++            filter == targetfilter):
+             root_logger.debug("Anonymous ACI already update-to-date")
+             return (False, False, [])
+-        else:
+-            root_logger.debug("New Anonymous ACI attributes needed: %s",
+-                needed_attrs)
+ 
+         for tmpaci in acistrs:
+             candidate = ACI(tmpaci)
+@@ -67,7 +68,17 @@ class update_anonymous_aci(PostUpdate):
+                 acistrs.remove(tmpaci)
+                 break
+ 
+-        rawaci.target['targetattr']['expression'] = update_attrs
++        if len(attrs) != len(update_attrs):
++            root_logger.debug("New Anonymous ACI attributes needed: %s",
++                needed_attrs)
++
++            rawaci.target['targetattr']['expression'] = update_attrs
++
++        if filter != targetfilter:
++            root_logger.debug("New Anonymous ACI targetfilter needed.")
++
++            rawaci.set_target_filter(targetfilter)
++
+         acistrs.append(unicode(rawaci))
+         entry_attrs['aci'] = acistrs
+ 
+-- 
+1.8.2.1
+
diff --git a/0003-ipa-kdb-Add-OTP-support.patch b/0003-ipa-kdb-Add-OTP-support.patch
new file mode 100644
index 0000000..7db9ac1
--- /dev/null
+++ b/0003-ipa-kdb-Add-OTP-support.patch
@@ -0,0 +1,187 @@
+From 5b15278283d3be6d615c98963807facf34da31eb Mon Sep 17 00:00:00 2001
+From: Nathaniel McCallum <npmccallum at redhat.com>
+Date: Thu, 11 Apr 2013 13:50:42 -0400
+Subject: [PATCH 3/6] ipa-kdb: Add OTP support
+
+If OTP is enabled for a user, then:
+  1. Long-term keys are not provided to KDB
+  2. The user string 'otp' is defined to KDB
+
+Since it is not secure to send radius configuration information
+over krb5 user strings, we simply set the string to a known default
+('[]') which enables the default configuration in the KDC.
+
+https://fedorahosted.org/freeipa/ticket/3561
+http://freeipa.org/page/V3/OTP
+---
+ daemons/ipa-kdb/ipa_kdb.c            | 38 +++++++++++++++++++++++++++++++++++-
+ daemons/ipa-kdb/ipa_kdb.h            | 13 ++++++++++++
+ daemons/ipa-kdb/ipa_kdb_principals.c | 28 ++++++++++++++++++++++++++
+ 3 files changed, 78 insertions(+), 1 deletion(-)
+
+diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c
+index e5c718e..8464264 100644
+--- a/daemons/ipa-kdb/ipa_kdb.c
++++ b/daemons/ipa-kdb/ipa_kdb.c
+@@ -173,9 +173,42 @@ done:
+     return base;
+ }
+ 
++static const struct {
++    const char *name;
++    enum ipadb_user_auth flag;
++} userauth_table[] = {
++    { "disabled", IPADB_USER_AUTH_DISABLED },
++    { "password", IPADB_USER_AUTH_PASSWORD },
++    { "radius", IPADB_USER_AUTH_RADIUS },
++    { "otp", IPADB_USER_AUTH_OTP },
++    { }
++};
++
++void ipadb_get_user_auth(LDAP *lcontext, LDAPMessage *le,
++                         enum ipadb_user_auth *userauth)
++{
++    struct berval **vals;
++    int i, j;
++
++    *userauth = IPADB_USER_AUTH_EMPTY;
++    vals = ldap_get_values_len(lcontext, le, IPA_USER_AUTH_TYPE);
++    if (!vals)
++        return;
++
++    for (i = 0; vals[i]; i++) {
++        for (j = 0; userauth_table[j].name; j++) {
++            if (strcasecmp(vals[i]->bv_val, userauth_table[j].name) == 0) {
++                *userauth |= userauth_table[j].flag;
++                break;
++            }
++        }
++    }
++}
++
+ int ipadb_get_global_configs(struct ipadb_context *ipactx)
+ {
+-    char *attrs[] = { "ipaConfigString", IPA_KRB_AUTHZ_DATA_ATTR, NULL };
++    char *attrs[] = { "ipaConfigString", IPA_KRB_AUTHZ_DATA_ATTR,
++                      IPA_USER_AUTH_TYPE, NULL };
+     struct berval **vals = NULL;
+     LDAPMessage *res = NULL;
+     LDAPMessage *first;
+@@ -203,6 +236,9 @@ int ipadb_get_global_configs(struct ipadb_context *ipactx)
+         goto done;
+     }
+ 
++    /* Check for permitted authentication types. */
++    ipadb_get_user_auth(ipactx->lcontext, res, &ipactx->user_auth);
++
+     vals = ldap_get_values_len(ipactx->lcontext, first,
+                                "ipaConfigString");
+     if (!vals || !vals[0]) {
+diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
+index 9daaab8..54869d8 100644
+--- a/daemons/ipa-kdb/ipa_kdb.h
++++ b/daemons/ipa-kdb/ipa_kdb.h
+@@ -75,9 +75,18 @@
+ #define IPA_SETUP "ipa-setup-override-restrictions"
+ 
+ #define IPA_KRB_AUTHZ_DATA_ATTR "ipaKrbAuthzData"
++#define IPA_USER_AUTH_TYPE "ipaUserAuthType"
+ 
+ struct ipadb_mspac;
+ 
++enum ipadb_user_auth {
++  IPADB_USER_AUTH_EMPTY    = 0,
++  IPADB_USER_AUTH_DISABLED = 1 << 0,
++  IPADB_USER_AUTH_PASSWORD = 1 << 1,
++  IPADB_USER_AUTH_RADIUS   = 1 << 2,
++  IPADB_USER_AUTH_OTP      = 1 << 3,
++};
++
+ struct ipadb_context {
+     char *uri;
+     char *base;
+@@ -92,6 +101,7 @@ struct ipadb_context {
+     bool disable_last_success;
+     bool disable_lockout;
+     char **authz_data;
++    enum ipadb_user_auth user_auth;
+ };
+ 
+ #define IPA_E_DATA_MAGIC 0x0eda7a
+@@ -259,3 +269,6 @@ void ipadb_audit_as_req(krb5_context kcontext,
+                         krb5_timestamp authtime,
+                         krb5_error_code error_code);
+ 
++/* AUTH METHODS */
++void ipadb_get_user_auth(LDAP *lcontext, LDAPMessage *le,
++                         enum ipadb_user_auth *user_auth);
+diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
+index 11c155e..3566e1e 100644
+--- a/daemons/ipa-kdb/ipa_kdb_principals.c
++++ b/daemons/ipa-kdb/ipa_kdb_principals.c
+@@ -64,6 +64,7 @@ static char *std_principal_attrs[] = {
+     "nsaccountlock",
+     "passwordHistory",
+     IPA_KRB_AUTHZ_DATA_ATTR,
++    IPA_USER_AUTH_TYPE,
+ 
+     "objectClass",
+     NULL
+@@ -228,6 +229,9 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
+                                               krb5_db_entry **kentry,
+                                               uint32_t *polmask)
+ {
++    krb5_octet otp_string[] = {'o', 't', 'p', 0, '[', ']', 0 };
++    enum ipadb_user_auth user_ua = IPADB_USER_AUTH_EMPTY;
++    enum ipadb_user_auth *active_ua = &user_ua;
+     struct ipadb_context *ipactx;
+     LDAP *lcontext;
+     krb5_db_entry *entry;
+@@ -262,6 +266,17 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
+     entry->magic = KRB5_KDB_MAGIC_NUMBER;
+     entry->len = KRB5_KDB_V1_BASE_LENGTH;
+ 
++    /* Get the user's user_auth settings. */
++    ipadb_get_user_auth(ipactx->lcontext, lentry, &user_ua);
++
++    /* TODO: Should we confirm the existence of ipatokenRadiusConfigLink in
++     *       the case of RADIUS? Existence of a token for OTP? */
++
++    /* Determine which user_auth policy is active: user or global. */
++    if ((ipactx->user_auth & IPADB_USER_AUTH_DISABLED)
++        || user_ua == IPADB_USER_AUTH_EMPTY)
++        active_ua = &ipactx->user_auth;
++
+     /* ignore mask for now */
+ 
+     ret = ipadb_ldap_attr_to_int(lcontext, lentry,
+@@ -393,6 +408,13 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
+                                       &res_key_data, &result, &mkvno);
+     switch (ret) {
+     case 0:
++        /* Only set a principal's key if password auth should be used. */
++        if ((*active_ua & ~IPADB_USER_AUTH_DISABLED) != IPADB_USER_AUTH_EMPTY
++            && !(*active_ua & IPADB_USER_AUTH_PASSWORD)) {
++            /* This is the same behavior as ENOENT below. */
++            break;
++        }
++
+         entry->key_data = res_key_data;
+         entry->n_key_data = result;
+         if (mkvno) {
+@@ -515,6 +537,12 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
+         ied->authz_data = authz_data_list;
+     }
+ 
++    /* If enabled, set the otp user string, enabling otp. */
++    if ((*active_ua & (IPADB_USER_AUTH_RADIUS | IPADB_USER_AUTH_OTP)) &&
++        !(*active_ua & IPADB_USER_AUTH_DISABLED)) {
++        ret = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS,
++                                sizeof(otp_string), otp_string);
++    }
+ 
+     kerr = 0;
+ 
+-- 
+1.8.2.1
+
diff --git a/0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch b/0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch
new file mode 100644
index 0000000..70df0fc
--- /dev/null
+++ b/0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch
@@ -0,0 +1,1998 @@
+From 71fcffc0ef79c3dea590b6e49ff74f90f40b8c65 Mon Sep 17 00:00:00 2001
+From: Nathaniel McCallum <npmccallum at redhat.com>
+Date: Thu, 11 Apr 2013 14:03:25 -0400
+Subject: [PATCH 4/6] Add the krb5/FreeIPA RADIUS companion daemon
+
+This daemon listens for RADIUS packets on a well known
+UNIX domain socket. When a packet is received, it queries
+LDAP to see if the user is configured for RADIUS authentication.
+If so, then the packet is forwarded to the 3rd party RADIUS server.
+Otherwise, a bind is attempted against the LDAP server.
+
+https://fedorahosted.org/freeipa/ticket/3366
+http://freeipa.org/page/V3/OTP
+---
+ daemons/Makefile.am                   |   1 +
+ daemons/configure.ac                  |  97 ++++------
+ daemons/ipa-otpd/Makefile.am          |  21 +++
+ daemons/ipa-otpd/bind.c               | 144 ++++++++++++++
+ daemons/ipa-otpd/forward.c            | 124 +++++++++++++
+ daemons/ipa-otpd/internal.h           | 153 +++++++++++++++
+ daemons/ipa-otpd/ipa-otpd.socket.in   |  11 ++
+ daemons/ipa-otpd/ipa-otpd at .service.in |   9 +
+ daemons/ipa-otpd/main.c               | 340 ++++++++++++++++++++++++++++++++++
+ daemons/ipa-otpd/parse.c              | 176 ++++++++++++++++++
+ daemons/ipa-otpd/query.c              | 253 +++++++++++++++++++++++++
+ daemons/ipa-otpd/queue.c              | 183 ++++++++++++++++++
+ daemons/ipa-otpd/stdio.c              | 205 ++++++++++++++++++++
+ daemons/ipa-otpd/test.py              |  61 ++++++
+ freeipa.spec.in                       |   9 +-
+ 15 files changed, 1723 insertions(+), 64 deletions(-)
+ create mode 100644 daemons/ipa-otpd/Makefile.am
+ create mode 100644 daemons/ipa-otpd/bind.c
+ create mode 100644 daemons/ipa-otpd/forward.c
+ create mode 100644 daemons/ipa-otpd/internal.h
+ create mode 100644 daemons/ipa-otpd/ipa-otpd.socket.in
+ create mode 100644 daemons/ipa-otpd/ipa-otpd at .service.in
+ create mode 100644 daemons/ipa-otpd/main.c
+ create mode 100644 daemons/ipa-otpd/parse.c
+ create mode 100644 daemons/ipa-otpd/query.c
+ create mode 100644 daemons/ipa-otpd/queue.c
+ create mode 100644 daemons/ipa-otpd/stdio.c
+ create mode 100644 daemons/ipa-otpd/test.py
+
+diff --git a/daemons/Makefile.am b/daemons/Makefile.am
+index 05cd1a7..956f399 100644
+--- a/daemons/Makefile.am
++++ b/daemons/Makefile.am
+@@ -16,6 +16,7 @@ SUBDIRS =			\
+ 	ipa-kdb			\
+ 	ipa-slapi-plugins	\
+ 	ipa-sam			\
++	ipa-otpd		\
+ 	$(NULL)
+ 
+ DISTCLEANFILES =		\
+diff --git a/daemons/configure.ac b/daemons/configure.ac
+index 3e8e81f..371c28d 100644
+--- a/daemons/configure.ac
++++ b/daemons/configure.ac
+@@ -79,63 +79,17 @@ dnl ---------------------------------------------------------------------------
+ dnl - Check for KRB5
+ dnl ---------------------------------------------------------------------------
+ 
+-KRB5_LIBS=
+ AC_CHECK_HEADER(krb5.h, [], [AC_MSG_ERROR([krb5.h not found])])
+-
+-krb5_impl=mit
+-
+-if test "x$ac_cv_header_krb5_h" = "xyes" ; then
+-  dnl lazy check for Heimdal Kerberos
+-  AC_CHECK_HEADERS(heim_err.h)
+-  if test $ac_cv_header_heim_err_h = yes ; then
+-    krb5_impl=heimdal
+-  else
+-    krb5_impl=mit
+-  fi
+-
+-  if test "x$krb5_impl" = "xmit"; then
+-    AC_CHECK_LIB(k5crypto, main,
+-      [krb5crypto=k5crypto],
+-      [krb5crypto=crypto])
+-
+-    AC_CHECK_LIB(krb5, main,
+-      [have_krb5=yes
+-	KRB5_LIBS="-lkrb5 -l$krb5crypto -lcom_err"],
+-      [have_krb5=no],
+-      [-l$krb5crypto -lcom_err])
+-
+-  elif test "x$krb5_impl" = "xheimdal"; then
+-    AC_CHECK_LIB(des, main,
+-      [krb5crypto=des],
+-      [krb5crypto=crypto])
+-
+-    AC_CHECK_LIB(krb5, main,
+-      [have_krb5=yes
+-	KRB5_LIBS="-lkrb5 -l$krb5crypto -lasn1 -lroken -lcom_err"],
+-      [have_krb5=no],
+-      [-l$krb5crypto -lasn1 -lroken -lcom_err])
+-
+-    AC_DEFINE(HAVE_HEIMDAL_KERBEROS, 1,
+-      [define if you have HEIMDAL Kerberos])
+-
+-  else
+-    have_krb5=no
+-    AC_MSG_WARN([Unrecognized Kerberos5 Implementation])
+-  fi
+-
+-  if test "x$have_krb5" = "xyes" ; then
+-    ol_link_krb5=yes
+-
+-    AC_DEFINE(HAVE_KRB5, 1,
+-      [define if you have Kerberos V])
+-
+-  else
+-    AC_MSG_ERROR([Required Kerberos 5 support not available])
+-  fi
+-
+-fi
+-
++AC_CHECK_HEADER(krad.h, [], [AC_MSG_ERROR([krad.h not found])])
++AC_CHECK_LIB(krb5, main, [], [AC_MSG_ERROR([libkrb5 not found])])
++AC_CHECK_LIB(k5crypto, main, [krb5crypto=k5crypto], [krb5crypto=crypto])
++AC_CHECK_LIB(krad, main, [], [AC_MSG_ERROR([libkrad not found])])
++KRB5_LIBS="-lkrb5 -l$krb5crypto -lcom_err"
++KRAD_LIBS="-lkrad"
++krb5kdcdir="${localstatedir}/kerberos/krb5kdc"
+ AC_SUBST(KRB5_LIBS)
++AC_SUBST(KRAD_LIBS)
++AC_SUBST(krb5kdcdir)
+ 
+ dnl ---------------------------------------------------------------------------
+ dnl - Check for Mozilla LDAP and OpenLDAP SDK
+@@ -263,6 +217,11 @@ AC_CHECK_LIB([pdb],[pdb_enum_upn_suffixes],
+              [$SAMBA40EXTRA_LIBPATH])
+ 
+ dnl ---------------------------------------------------------------------------
++dnl Check for libverto
++dnl ---------------------------------------------------------------------------
++PKG_CHECK_MODULES([LIBVERTO], [libverto])
++
++dnl ---------------------------------------------------------------------------
+ dnl - Check for check unit test framework http://check.sourceforge.net/
+ dnl ---------------------------------------------------------------------------
+ PKG_CHECK_MODULES([CHECK], [check >= 0.9.5], [have_check=1], [have_check=])
+@@ -310,6 +269,20 @@ dnl -- sss_idmap is needed by the extdom exop --
+ PKG_CHECK_MODULES([SSSIDMAP], [sss_idmap])
+ 
+ dnl ---------------------------------------------------------------------------
++dnl - Check for systemd unit directory
++dnl ---------------------------------------------------------------------------
++PKG_CHECK_EXISTS([systemd], [], [AC_MSG_ERROR([systemd not found])])
++AC_ARG_WITH([systemdsystemunitdir],
++            AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
++            [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
++AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
++
++dnl ---------------------------------------------------------------------------
++dnl - Check for program paths
++dnl ---------------------------------------------------------------------------
++AC_PATH_PROG(UNLINK, unlink, [AC_MSG_ERROR([unlink not found])])
++
++dnl ---------------------------------------------------------------------------
+ dnl - Set the data install directory since we don't use pkgdatadir
+ dnl ---------------------------------------------------------------------------
+ 
+@@ -373,6 +346,7 @@ AC_CONFIG_FILES([
+     Makefile
+     ipa-kdb/Makefile
+     ipa-sam/Makefile
++    ipa-otpd/Makefile
+     ipa-slapi-plugins/Makefile
+     ipa-slapi-plugins/ipa-cldap/Makefile
+     ipa-slapi-plugins/ipa-dns/Makefile
+@@ -394,19 +368,22 @@ echo "
+                     IPA Server $VERSION
+                     ========================
+ 
+-	prefix:                   ${prefix}
+-	exec_prefix:              ${exec_prefix}
++        prefix:                   ${prefix}
++        exec_prefix:              ${exec_prefix}
+         libdir:                   ${libdir}
+         bindir:                   ${bindir}
+         sbindir:                  ${sbindir}
+         sysconfdir:               ${sysconfdir}
+         localstatedir:            ${localstatedir}
+         datadir:                  ${datadir}
+-	source code location:	  ${srcdir}
+-	compiler:		  ${CC}
+-	cflags:		          ${CFLAGS}
++        krb5kdcdir:               ${krb5kdcdir}
++        systemdsystemunitdir:     ${systemdsystemunitdir}
++        source code location:     ${srcdir}
++        compiler:                 ${CC}
++        cflags:                   ${CFLAGS}
+         LDAP libs:                ${LDAP_LIBS}
+         KRB5 libs:                ${KRB5_LIBS}
++        KRAD libs:                ${KRAD_LIBS}
+         OpenSSL libs:             ${SSL_LIBS}
+         Maintainer mode:          ${USE_MAINTAINER_MODE}
+ "
+diff --git a/daemons/ipa-otpd/Makefile.am b/daemons/ipa-otpd/Makefile.am
+new file mode 100644
+index 0000000..ed99c3e
+--- /dev/null
++++ b/daemons/ipa-otpd/Makefile.am
+@@ -0,0 +1,21 @@
++AM_CFLAGS := $(CFLAGS) @LDAP_CFLAGS@ @LIBVERTO_CFLAGS@
++AM_LDFLAGS := $(LDFLAGS) @LDAP_LIBS@ @LIBVERTO_LIBS@ @KRAD_LIBS@
++
++noinst_HEADERS = internal.h
++libexec_PROGRAMS = ipa-otpd
++dist_noinst_DATA = ipa-otpd.socket.in ipa-otpd at .service.in test.py
++systemdsystemunit_DATA = ipa-otpd.socket ipa-otpd at .service
++
++ipa_otpd_SOURCES = bind.c forward.c main.c parse.c query.c queue.c stdio.c
++
++%.socket: %.socket.in
++	@sed -e 's|@krb5kdcdir[@]|$(krb5kdcdir)|g' \
++	     -e 's|@UNLINK[@]|@UNLINK@|g' \
++	     $< > $@
++
++%.service: %.service.in
++	@sed -e 's|@libexecdir[@]|$(libexecdir)|g' \
++	     -e 's|@sysconfdir[@]|$(sysconfdir)|g' \
++	     $< > $@
++
++CLEANFILES = $(systemdsystemunit_DATA)
+diff --git a/daemons/ipa-otpd/bind.c b/daemons/ipa-otpd/bind.c
+new file mode 100644
+index 0000000..c985ccd
+--- /dev/null
++++ b/daemons/ipa-otpd/bind.c
+@@ -0,0 +1,144 @@
++/*
++ * FreeIPA 2FA companion daemon
++ *
++ * Authors: Nathaniel McCallum <npmccallum at redhat.com>
++ *
++ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
++ * see file 'COPYING' for use and warranty information
++ *
++ * This program is free software you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ */
++
++/*
++ * This file takes requests from query.c and performs an LDAP bind on behalf
++ * of the user. The results are placed in the stdout queue (stdio.c).
++ */
++
++#include "internal.h"
++
++static void on_bind_writable(verto_ctx *vctx, verto_ev *ev)
++{
++    struct otpd_queue *push = &ctx.stdio.responses;
++    const krb5_data *data;
++    struct berval cred;
++    struct otpd_queue_item *item;
++    int i;
++    (void)vctx;
++
++    item = otpd_queue_pop(&ctx.bind.requests);
++    if (item == NULL) {
++        verto_set_flags(ctx.bind.io, VERTO_EV_FLAG_PERSIST |
++                                     VERTO_EV_FLAG_IO_ERROR |
++                                     VERTO_EV_FLAG_IO_READ);
++        return;
++    }
++
++    if (item->user.dn == NULL)
++        goto error;
++
++    data = krad_packet_get_attr(item->req,
++                                krad_attr_name2num("User-Password"), 0);
++    if (data == NULL)
++        goto error;
++
++    cred.bv_val = data->data;
++    cred.bv_len = data->length;
++    i = ldap_sasl_bind(verto_get_private(ev), item->user.dn, LDAP_SASL_SIMPLE,
++                       &cred, NULL, NULL, &item->msgid);
++    if (i != LDAP_SUCCESS) {
++        otpd_log_err(errno, "Unable to initiate bind: %s", ldap_err2string(i));
++        verto_break(ctx.vctx);
++        ctx.exitstatus = 1;
++    }
++
++    otpd_log_req(item->req, "bind start: %s", item->user.dn);
++    push = &ctx.bind.responses;
++
++error:
++    otpd_queue_push(push, item);
++}
++
++static void on_bind_readable(verto_ctx *vctx, verto_ev *ev)
++{
++    const char *errstr = "error";
++    LDAPMessage *results;
++    struct otpd_queue_item *item = NULL;
++    int i, rslt;
++    (void)vctx;
++
++    rslt = ldap_result(verto_get_private(ev), LDAP_RES_ANY, 0, NULL, &results);
++    if (rslt != LDAP_RES_BIND) {
++        if (rslt <= 0)
++            results = NULL;
++        ldap_msgfree(results);
++        return;
++    }
++
++    item = otpd_queue_pop_msgid(&ctx.bind.responses, ldap_msgid(results));
++    if (item == NULL) {
++        ldap_msgfree(results);
++        return;
++    }
++    item->msgid = -1;
++
++    rslt = ldap_parse_result(verto_get_private(ev), results, &i,
++                             NULL, NULL, NULL, NULL, 0);
++    if (rslt != LDAP_SUCCESS) {
++        errstr = ldap_err2string(rslt);
++        goto error;
++    }
++
++    rslt = i;
++    if (rslt != LDAP_SUCCESS) {
++        errstr = ldap_err2string(rslt);
++        goto error;
++    }
++
++    item->sent = 0;
++    i = krad_packet_new_response(ctx.kctx, SECRET,
++                                 krad_code_name2num("Access-Accept"),
++                                 NULL, item->req, &item->rsp);
++    if (i != 0) {
++        errstr = krb5_get_error_message(ctx.kctx, i);
++        goto error;
++    }
++
++error:
++    if (item != NULL)
++        otpd_log_req(item->req, "bind end: %s",
++                item->rsp != NULL ? "success" : errstr);
++
++    ldap_msgfree(results);
++    otpd_queue_push(&ctx.stdio.responses, item);
++    verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
++                                      VERTO_EV_FLAG_IO_ERROR |
++                                      VERTO_EV_FLAG_IO_READ |
++                                      VERTO_EV_FLAG_IO_WRITE);
++}
++
++void otpd_on_bind_io(verto_ctx *vctx, verto_ev *ev)
++{
++    verto_ev_flag flags;
++
++    flags = verto_get_fd_state(ev);
++    if (flags & VERTO_EV_FLAG_IO_WRITE)
++        on_bind_writable(vctx, ev);
++    if (flags & VERTO_EV_FLAG_IO_READ)
++        on_bind_readable(vctx, ev);
++    if (flags & VERTO_EV_FLAG_IO_ERROR) {
++        otpd_log_err(EIO, "IO error received on bind socket");
++        verto_break(ctx.vctx);
++        ctx.exitstatus = 1;
++    }
++}
+diff --git a/daemons/ipa-otpd/forward.c b/daemons/ipa-otpd/forward.c
+new file mode 100644
+index 0000000..e6ae1e9
+--- /dev/null
++++ b/daemons/ipa-otpd/forward.c
+@@ -0,0 +1,124 @@
++/*
++ * FreeIPA 2FA companion daemon
++ *
++ * Authors: Nathaniel McCallum <npmccallum at redhat.com>
++ *
++ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
++ * see file 'COPYING' for use and warranty information
++ *
++ * This program is free software you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ */
++
++/*
++ * This file proxies the incoming RADIUS request (stdio.c/query.c) to a
++ * third-party RADIUS server if the user is configured for forwarding. The
++ * result is placed in the stdout queue (stdio.c).
++ */
++
++#include "internal.h"
++
++static void forward_cb(krb5_error_code retval, const krad_packet *request,
++                       const krad_packet *response, void *data)
++{
++    krad_code code, acpt;
++    struct otpd_queue_item *item = data;
++    (void)request;
++
++    acpt = krad_code_name2num("Access-Accept");
++    code = krad_packet_get_code(response);
++    if (retval == 0 && code == acpt) {
++        item->sent = 0;
++        retval = krad_packet_new_response(ctx.kctx, SECRET, acpt,
++                                          NULL, item->req, &item->rsp);
++    }
++
++    otpd_log_req(item->req, "forward end: %s",
++            retval == 0
++                ? krad_code_num2name(code)
++                : krb5_get_error_message(ctx.kctx, retval));
++
++    otpd_queue_push(&ctx.stdio.responses, item);
++    verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
++                                      VERTO_EV_FLAG_IO_ERROR |
++                                      VERTO_EV_FLAG_IO_READ |
++                                      VERTO_EV_FLAG_IO_WRITE);
++}
++
++krb5_error_code otpd_forward(struct otpd_queue_item **item)
++{
++    krad_attr usernameid, passwordid;
++    const krb5_data *password;
++    krb5_error_code retval;
++    char *username;
++    krb5_data data;
++
++    /* Find the username. */
++    username = (*item)->user.ipatokenRadiusUserName;
++    if (username == NULL) {
++        username = (*item)->user.other;
++        if (username == NULL)
++            username = (*item)->user.uid;
++    }
++
++    /* Check to see if we are supposed to forward. */
++    if ((*item)->radius.ipatokenRadiusServer == NULL ||
++        (*item)->radius.ipatokenRadiusSecret == NULL ||
++        username == NULL)
++        return 0;
++
++    otpd_log_req((*item)->req, "forward start: %s / %s", username,
++            (*item)->radius.ipatokenRadiusServer);
++
++    usernameid = krad_attr_name2num("User-Name");
++    passwordid = krad_attr_name2num("User-Password");
++
++    /* Set User-Name. */
++    data.data = username;
++    data.length = strlen(data.data);
++    retval = krad_attrset_add(ctx.attrs, usernameid, &data);
++    if (retval != 0)
++        goto error;
++
++    /* Set User-Password. */
++    password = krad_packet_get_attr((*item)->req, passwordid, 0);
++    if (password == NULL) {
++        krad_attrset_del(ctx.attrs, usernameid, 0);
++        goto error;
++    }
++    retval = krad_attrset_add(ctx.attrs, passwordid, password);
++    if (retval != 0) {
++        krad_attrset_del(ctx.attrs, usernameid, 0);
++        goto error;
++    }
++
++    /* Forward the request to the RADIUS server. */
++    retval = krad_client_send(ctx.client,
++                              krad_code_name2num("Access-Request"),
++                              ctx.attrs,
++                              (*item)->radius.ipatokenRadiusServer,
++                              (*item)->radius.ipatokenRadiusSecret,
++                              (*item)->radius.ipatokenRadiusTimeout,
++                              (*item)->radius.ipatokenRadiusRetries,
++                              forward_cb, *item);
++    krad_attrset_del(ctx.attrs, usernameid, 0);
++    krad_attrset_del(ctx.attrs, passwordid, 0);
++    if (retval == 0)
++        *item = NULL;
++
++error:
++    if (retval != 0)
++        otpd_log_req((*item)->req, "forward end: %s",
++                krb5_get_error_message(ctx.kctx, retval));
++    return retval;
++}
+diff --git a/daemons/ipa-otpd/internal.h b/daemons/ipa-otpd/internal.h
+new file mode 100644
+index 0000000..5ab4a77
+--- /dev/null
++++ b/daemons/ipa-otpd/internal.h
+@@ -0,0 +1,153 @@
++/*
++ * FreeIPA 2FA companion daemon
++ *
++ * Authors: Nathaniel McCallum <npmccallum at redhat.com>
++ *
++ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
++ * see file 'COPYING' for use and warranty information
++ *
++ * This program is free software you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef INTERNAL_H_
++#define INTERNAL_H_
++
++#include "krad.h"
++
++#include <ldap.h>
++
++#include <errno.h>
++
++#define SECRET ""
++#define otpd_log_req(req, ...) \
++    otpd_log_req_(__FILE__, __LINE__, (req), __VA_ARGS__)
++#define otpd_log_err(errnum, ...) \
++    otpd_log_err_(__FILE__, __LINE__, (errnum), __VA_ARGS__)
++
++struct otpd_queue_iter;
++
++struct otpd_queue_item {
++    struct otpd_queue_item *next;
++    krad_packet *req;
++    krad_packet *rsp;
++    size_t sent;
++    char *error;
++
++    struct {
++        char *dn;
++        char *uid;
++        char *ipatokenRadiusUserName;
++        char *ipatokenRadiusConfigLink;
++        char *other;
++    } user;
++
++    struct {
++        char *ipatokenUserMapAttribute;
++        char *ipatokenRadiusSecret;
++        char *ipatokenRadiusServer;
++        time_t ipatokenRadiusTimeout;
++        size_t ipatokenRadiusRetries;
++    } radius;
++    int msgid;
++};
++
++struct otpd_queue {
++    struct otpd_queue_item *head;
++    struct otpd_queue_item *tail;
++};
++
++/* This structure contains our global state. The most important part is the
++ * queues. When a request comes in (stdio.c), it is placed into an item object.
++ * This item exists in only one queue at a time as it flows through this
++ * daemon.
++ *
++ * The flow is: stdin => query => (forward (no queue) or bind) => stdout.
++ */
++struct otpd_context {
++    verto_ctx *vctx;
++    krb5_context kctx;
++    krad_client *client;
++    krad_attrset *attrs;
++    int exitstatus;
++
++    struct {
++        verto_ev *reader;
++        verto_ev *writer;
++        struct otpd_queue responses;
++    } stdio;
++
++    struct {
++        char *base;
++        verto_ev *io;
++        struct otpd_queue requests;
++        struct otpd_queue responses;
++    } query;
++
++    struct {
++        verto_ev *io;
++        struct otpd_queue requests;
++        struct otpd_queue responses;
++    } bind;
++};
++
++extern struct otpd_context ctx;
++
++void otpd_log_req_(const char * const file, int line, krad_packet *req,
++                   const char * const tmpl, ...);
++
++void otpd_log_err_(const char * const file, int line, krb5_error_code code,
++                   const char * const tmpl, ...);
++
++krb5_error_code otpd_queue_item_new(krad_packet *req,
++                                    struct otpd_queue_item **item);
++
++void otpd_queue_item_free(struct otpd_queue_item *item);
++
++krb5_error_code otpd_queue_iter_new(const struct otpd_queue * const *queues,
++                                    struct otpd_queue_iter **iter);
++
++const krad_packet *otpd_queue_iter_func(void *data, krb5_boolean cancel);
++
++void otpd_queue_push(struct otpd_queue *q, struct otpd_queue_item *item);
++
++void otpd_queue_push_head(struct otpd_queue *q, struct otpd_queue_item *item);
++
++struct otpd_queue_item *otpd_queue_peek(struct otpd_queue *q);
++
++struct otpd_queue_item *otpd_queue_pop(struct otpd_queue *q);
++
++struct otpd_queue_item *otpd_queue_pop_msgid(struct otpd_queue *q, int msgid);
++
++void otpd_queue_free_items(struct otpd_queue *q);
++
++void otpd_on_stdin_readable(verto_ctx *vctx, verto_ev *ev);
++
++void otpd_on_stdout_writable(verto_ctx *vctx, verto_ev *ev);
++
++void otpd_on_query_io(verto_ctx *vctx, verto_ev *ev);
++
++void otpd_on_bind_io(verto_ctx *vctx, verto_ev *ev);
++
++krb5_error_code otpd_forward(struct otpd_queue_item **i);
++
++const char *otpd_parse_user(LDAP *ldp, LDAPMessage *entry,
++                            struct otpd_queue_item *item);
++
++const char *otpd_parse_radius(LDAP *ldp, LDAPMessage *entry,
++                              struct otpd_queue_item *item);
++
++const char *otpd_parse_radius_username(LDAP *ldp, LDAPMessage *entry,
++                                       struct otpd_queue_item *item);
++
++#endif /* INTERNAL_H_ */
+diff --git a/daemons/ipa-otpd/ipa-otpd.socket.in b/daemons/ipa-otpd/ipa-otpd.socket.in
+new file mode 100644
+index 0000000..b968bea
+--- /dev/null
++++ b/daemons/ipa-otpd/ipa-otpd.socket.in
+@@ -0,0 +1,11 @@
++[Unit]
++Description=ipa-otpd socket
++
++[Socket]
++ListenStream=@krb5kdcdir@/DEFAULT.socket
++ExecStopPre=@UNLINK@ @krb5kdcdir@/DEFAULT.socket
++SocketMode=0600
++Accept=true
++
++[Install]
++WantedBy=krb5kdc.service
+diff --git a/daemons/ipa-otpd/ipa-otpd at .service.in b/daemons/ipa-otpd/ipa-otpd at .service.in
+new file mode 100644
+index 0000000..b85d5a1
+--- /dev/null
++++ b/daemons/ipa-otpd/ipa-otpd at .service.in
+@@ -0,0 +1,9 @@
++[Unit]
++Description=ipa-otpd service
++
++[Service]
++EnvironmentFile=@sysconfdir@/ipa/default.conf
++ExecStart=@libexecdir@/ipa-otpd $ldap_uri
++StandardInput=socket
++StandardOutput=socket
++StandardError=syslog
+diff --git a/daemons/ipa-otpd/main.c b/daemons/ipa-otpd/main.c
+new file mode 100644
+index 0000000..a5d1f93
+--- /dev/null
++++ b/daemons/ipa-otpd/main.c
+@@ -0,0 +1,340 @@
++/*
++ * FreeIPA 2FA companion daemon
++ *
++ * Authors: Nathaniel McCallum <npmccallum at redhat.com>
++ *
++ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
++ * see file 'COPYING' for use and warranty information
++ *
++ * This program is free software you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ */
++
++/*
++ * This file initializes a systemd socket-activated daemon which receives
++ * RADIUS packets on STDIN and either proxies them to a third party RADIUS
++ * server or performs authentication directly by binding to the LDAP server.
++ * The choice between bind or proxy is made by evaluating LDAP configuration
++ * for the given user.
++ */
++
++#include "internal.h"
++
++#include <signal.h>
++#include <stdbool.h>
++
++/* Our global state. */
++struct otpd_context ctx;
++
++/* Implementation function for logging a request's state. See internal.h. */
++void otpd_log_req_(const char * const file, int line, krad_packet *req,
++                   const char * const tmpl, ...)
++{
++    const krb5_data *data;
++    va_list ap;
++
++#ifdef DEBUG
++    if (file != NULL)
++        fprintf(stderr, "%8s:%03d: ", file, line);
++#else
++    (void)file;
++    (void)line;
++#endif
++
++    data = krad_packet_get_attr(req, krad_attr_name2num("User-Name"), 0);
++    if (data == NULL)
++        fprintf(stderr, "<unknown>: ");
++    else
++        fprintf(stderr, "%*s: ", data->length, data->data);
++
++    va_start(ap, tmpl);
++    vfprintf(stderr, tmpl, ap);
++    va_end(ap);
++
++    fprintf(stderr, "\n");
++}
++
++/* Implementation function for logging a generic error. See internal.h. */
++void otpd_log_err_(const char * const file, int line, krb5_error_code code,
++                   const char * const tmpl, ...)
++{
++    const char *msg;
++    va_list ap;
++
++    if (file != NULL)
++        fprintf(stderr, "%10s:%03d: ", file, line);
++
++    if (code != 0) {
++        msg = krb5_get_error_message(ctx.kctx, code);
++        fprintf(stderr, "%s: ", msg);
++        krb5_free_error_message(ctx.kctx, msg);
++    }
++
++    va_start(ap, tmpl);
++    vfprintf(stderr, tmpl, ap);
++    va_end(ap);
++
++    fprintf(stderr, "\n");
++}
++
++static void on_ldap_free(verto_ctx *vctx, verto_ev *ev)
++{
++    (void)vctx; /* Unused */
++    ldap_unbind_ext_s(verto_get_private(ev), NULL, NULL);
++}
++
++static void on_signal(verto_ctx *vctx, verto_ev *ev)
++{
++    (void)ev; /* Unused */
++    fprintf(stderr, "Signaled, exiting...\n");
++    verto_break(vctx);
++}
++
++static char *find_base(LDAP *ldp)
++{
++    LDAPMessage *results = NULL, *entry;
++    struct berval **vals = NULL;
++    struct timeval timeout;
++    int i, len;
++    char *base = NULL, *attrs[] = {
++        "namingContexts",
++        "defaultNamingContext",
++        NULL
++    };
++
++    timeout.tv_sec = -1;
++    i = ldap_search_ext_s(ldp, "", LDAP_SCOPE_BASE, NULL, attrs,
++                          0, NULL, NULL, &timeout, 1, &results);
++    if (i != LDAP_SUCCESS) {
++        otpd_log_err(0, "Unable to search for query base: %s",
++                     ldap_err2string(i));
++        goto egress;
++    }
++
++    entry = ldap_first_entry(ldp, results);
++    if (entry == NULL) {
++        otpd_log_err(0, "No entries found");
++        goto egress;
++    }
++
++    vals = ldap_get_values_len(ldp, entry, "defaultNamingContext");
++    if (vals == NULL) {
++        vals = ldap_get_values_len(ldp, entry, "namingContexts");
++        if (vals == NULL) {
++            otpd_log_err(0, "No namingContexts found");
++            goto egress;
++        }
++    }
++
++    len = ldap_count_values_len(vals);
++    if (len == 1)
++        base = strndup(vals[0]->bv_val, vals[0]->bv_len);
++    else
++        otpd_log_err(0, "Too many namingContexts found");
++
++    /* TODO: search multiple namingContexts to find the base? */
++
++egress:
++    ldap_value_free_len(vals);
++    ldap_msgfree(results);
++    return base;
++}
++
++/* Set up an LDAP connection as a verto event. */
++static krb5_error_code setup_ldap(const char *uri, krb5_boolean bind,
++                                  verto_callback *io, verto_ev **ev,
++                                  char **base)
++{
++    struct timeval timeout;
++    int err, ver, fd;
++    char *basetmp;
++    LDAP *ldp;
++
++    err = ldap_initialize(&ldp, uri);
++    if (err != LDAP_SUCCESS)
++        return errno;
++
++    ver = LDAP_VERSION3;
++    ldap_set_option(ldp, LDAP_OPT_PROTOCOL_VERSION, &ver);
++
++    if (bind) {
++        err = ldap_sasl_bind_s(ldp, NULL, "EXTERNAL", NULL, NULL, NULL, NULL);
++        if (err != LDAP_SUCCESS)
++            return errno;
++    }
++
++    /* Always find the base since this forces open the socket. */
++    basetmp = find_base(ldp);
++    if (basetmp == NULL)
++        return ENOTCONN;
++    if (base != NULL)
++        *base = basetmp;
++    else
++        free(basetmp);
++
++    /* Set default timeout to just return immediately for async requests. */
++    memset(&timeout, 0, sizeof(timeout));
++    err = ldap_set_option(ldp, LDAP_OPT_TIMEOUT, &timeout);
++    if (err != LDAP_OPT_SUCCESS) {
++        ldap_unbind_ext_s(ldp, NULL, NULL);
++        return ENOMEM; /* What error code do I use? */
++    }
++
++    /* Get the file descriptor. */
++    if (ldap_get_option(ldp, LDAP_OPT_DESC, &fd) != LDAP_OPT_SUCCESS) {
++        ldap_unbind_ext_s(ldp, NULL, NULL);
++        return EINVAL;
++    }
++
++    *ev = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
++                                 VERTO_EV_FLAG_IO_ERROR |
++                                 VERTO_EV_FLAG_IO_READ,
++                       io, fd);
++    if (*ev == NULL) {
++        ldap_unbind_ext_s(ldp, NULL, NULL);
++        return ENOMEM; /* What error code do I use? */
++    }
++
++    verto_set_private(*ev, ldp, on_ldap_free);
++    return 0;
++}
++
++int main(int argc, char **argv)
++{
++    char hostname[HOST_NAME_MAX + 1];
++    krb5_error_code retval;
++    krb5_data hndata;
++    verto_ev *sig;
++
++    if (argc != 2) {
++        fprintf(stderr, "Usage: %s <ldap_uri>\n", argv[0]);
++        return 1;
++    } else {
++        fprintf(stderr, "LDAP: %s\n", argv[1]);
++    }
++
++    memset(&ctx, 0, sizeof(ctx));
++    ctx.exitstatus = 1;
++
++    if (gethostname(hostname, sizeof(hostname)) < 0) {
++        otpd_log_err(errno, "Unable to get hostname");
++        goto error;
++    }
++
++    retval = krb5_init_context(&ctx.kctx);
++    if (retval != 0) {
++        otpd_log_err(retval, "Unable to initialize context");
++        goto error;
++    }
++
++    ctx.vctx = verto_new(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_SIGNAL);
++    if (ctx.vctx == NULL) {
++        otpd_log_err(ENOMEM, "Unable to initialize event loop");
++        goto error;
++    }
++
++    /* Build attrset. */
++    retval = krad_attrset_new(ctx.kctx, &ctx.attrs);
++    if (retval != 0) {
++        otpd_log_err(retval, "Unable to initialize attrset");
++        goto error;
++    }
++
++    /* Set NAS-Identifier. */
++    hndata.data = hostname;
++    hndata.length = strlen(hndata.data);
++    retval = krad_attrset_add(ctx.attrs, krad_attr_name2num("NAS-Identifier"),
++                              &hndata);
++    if (retval != 0) {
++        otpd_log_err(retval, "Unable to set NAS-Identifier");
++        goto error;
++    }
++
++    /* Set Service-Type. */
++    retval = krad_attrset_add_number(ctx.attrs,
++                                     krad_attr_name2num("Service-Type"),
++                                     KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY);
++    if (retval != 0) {
++        otpd_log_err(retval, "Unable to set Service-Type");
++        goto error;
++    }
++
++    /* Radius Client */
++    retval = krad_client_new(ctx.kctx, ctx.vctx, &ctx.client);
++    if (retval != 0) {
++        otpd_log_err(retval, "Unable to initialize radius client");
++        goto error;
++    }
++
++    /* Signals */
++    sig = verto_add_signal(ctx.vctx, VERTO_EV_FLAG_NONE, on_signal, SIGTERM);
++    if (sig == NULL) {
++        otpd_log_err(ENOMEM, "Unable to initialize signal event");
++        goto error;
++    }
++    sig = verto_add_signal(ctx.vctx, VERTO_EV_FLAG_NONE, on_signal, SIGINT);
++    if (sig == NULL) {
++        otpd_log_err(ENOMEM, "Unable to initialize signal event");
++        goto error;
++    }
++
++    /* Standard IO */
++    ctx.stdio.reader = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
++                                              VERTO_EV_FLAG_IO_ERROR |
++                                              VERTO_EV_FLAG_IO_READ,
++                                    otpd_on_stdin_readable, STDIN_FILENO);
++    if (ctx.stdio.reader == NULL) {
++        otpd_log_err(ENOMEM, "Unable to initialize reader event");
++        goto error;
++    }
++    ctx.stdio.writer = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
++                                              VERTO_EV_FLAG_IO_ERROR |
++                                              VERTO_EV_FLAG_IO_READ,
++                                    otpd_on_stdout_writable, STDOUT_FILENO);
++    if (ctx.stdio.writer == NULL) {
++        otpd_log_err(ENOMEM, "Unable to initialize writer event");
++        goto error;
++    }
++
++    /* LDAP (Query) */
++    retval = setup_ldap(argv[1], TRUE, otpd_on_query_io,
++                        &ctx.query.io, &ctx.query.base);
++    if (retval != 0) {
++        otpd_log_err(retval, "Unable to initialize LDAP (Query)");
++        goto error;
++    }
++
++    /* LDAP (Bind) */
++    retval = setup_ldap(argv[1], FALSE, otpd_on_bind_io,
++                        &ctx.bind.io, NULL);
++    if (retval != 0) {
++        otpd_log_err(retval, "Unable to initialize LDAP (Bind)");
++        goto error;
++    }
++
++    ctx.exitstatus = 0;
++    verto_run(ctx.vctx);
++
++error:
++    krad_client_free(ctx.client);
++    otpd_queue_free_items(&ctx.stdio.responses);
++    otpd_queue_free_items(&ctx.query.requests);
++    otpd_queue_free_items(&ctx.query.responses);
++    otpd_queue_free_items(&ctx.bind.requests);
++    otpd_queue_free_items(&ctx.bind.responses);
++    free(ctx.query.base);
++    verto_free(ctx.vctx);
++    krb5_free_context(ctx.kctx);
++    return ctx.exitstatus;
++}
++
+diff --git a/daemons/ipa-otpd/parse.c b/daemons/ipa-otpd/parse.c
+new file mode 100644
+index 0000000..062b640
+--- /dev/null
++++ b/daemons/ipa-otpd/parse.c
+@@ -0,0 +1,176 @@
++/*
++ * FreeIPA 2FA companion daemon
++ *
++ * Authors: Nathaniel McCallum <npmccallum at redhat.com>
++ *
++ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
++ * see file 'COPYING' for use and warranty information
++ *
++ * This program is free software you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ */
++
++/*
++ * This file parses the user's configuration received from LDAP (see query.c).
++ */
++
++#include "internal.h"
++#include <ctype.h>
++
++#define DEFAULT_TIMEOUT 15
++#define DEFAULT_RETRIES 3
++
++/* Convert an LDAP entry into an allocated string. */
++static int get_string(LDAP *ldp, LDAPMessage *entry, const char *name,
++                      char **out)
++{
++    struct berval **vals;
++    ber_len_t i;
++    char *buf;
++
++    vals = ldap_get_values_len(ldp, entry, name);
++    if (vals == NULL)
++        return ENOENT;
++
++    buf = calloc(vals[0]->bv_len + 1, sizeof(char));
++    if (buf == NULL) {
++        ldap_value_free_len(vals);
++        return ENOMEM;
++    }
++
++    for (i = 0; i < vals[0]->bv_len; i++) {
++        if (!isprint(vals[0]->bv_val[i])) {
++            free(buf);
++            ldap_value_free_len(vals);
++            return EINVAL;
++        }
++
++        buf[i] = vals[0]->bv_val[i];
++    }
++
++    if (*out != NULL)
++        free(*out);
++    *out = buf;
++    ldap_value_free_len(vals);
++    return 0;
++}
++
++/* Convert an LDAP entry into an unsigned long. */
++static int get_ulong(LDAP *ldp, LDAPMessage *entry, const char *name,
++                     unsigned long *out)
++{
++    struct berval **vals;
++    char buffer[32];
++
++    vals = ldap_get_values_len(ldp, entry, name);
++    if (vals == NULL)
++        return ENOENT;
++
++    if (vals[0]->bv_len > sizeof(buffer) - 1) {
++        ldap_value_free_len(vals);
++        return ERANGE;
++    }
++
++    memcpy(buffer, vals[0]->bv_val, vals[0]->bv_len);
++    buffer[vals[0]->bv_len] = '\0';
++    ldap_value_free_len(vals);
++
++    *out = strtoul(buffer, NULL, 10);
++    if (*out == ULONG_MAX)
++        return errno;
++
++    return 0;
++}
++
++/* Parse basic user configuration. */
++const char *otpd_parse_user(LDAP *ldp, LDAPMessage *entry,
++                            struct otpd_queue_item *item)
++{
++  int i, j;
++
++  i = get_string(ldp, entry, "uid", &item->user.uid);
++  if (i != 0)
++      return strerror(i);
++
++  i = get_string(ldp, entry, "ipatokenRadiusUserName",
++                 &item->user.ipatokenRadiusUserName);
++  if (i != 0 && i != ENOENT)
++      return strerror(i);
++
++  i = get_string(ldp, entry, "ipatokenRadiusConfigLink",
++                 &item->user.ipatokenRadiusConfigLink);
++  if (i != 0 && i != ENOENT)
++      return strerror(i);
++
++  /* Get the DN. */
++  item->user.dn = ldap_get_dn(ldp, entry);
++  if (item->user.dn == NULL) {
++      i = ldap_get_option(ldp, LDAP_OPT_RESULT_CODE, &j);
++      return ldap_err2string(i == LDAP_OPT_SUCCESS ? j : i);
++  }
++
++  return NULL;
++}
++
++/* Parse the user's RADIUS configuration. */
++const char *otpd_parse_radius(LDAP *ldp, LDAPMessage *entry,
++                              struct otpd_queue_item *item)
++{
++  unsigned long l;
++  int i;
++
++  i = get_string(ldp, entry, "ipatokenRadiusServer",
++                 &item->radius.ipatokenRadiusServer);
++  if (i != 0)
++      return strerror(i);
++
++  i = get_string(ldp, entry, "ipatokenRadiusSecret",
++                 &item->radius.ipatokenRadiusSecret);
++  if (i != 0)
++      return strerror(i);
++
++  i = get_string(ldp, entry, "ipatokenUserMapAttribute",
++                 &item->radius.ipatokenUserMapAttribute);
++  if (i != 0 && i != ENOENT)
++      return strerror(i);
++
++  i = get_ulong(ldp, entry, "ipatokenRadiusTimeout", &l);
++  if (i == ENOENT)
++      l = DEFAULT_TIMEOUT;
++  else if (i != 0)
++      return strerror(i);
++  item->radius.ipatokenRadiusTimeout = l * 1000;
++
++  i = get_ulong(ldp, entry, "ipatokenRadiusRetries", &l);
++  if (i == ENOENT)
++      l = DEFAULT_RETRIES;
++  else if (i != 0)
++      return strerror(i);
++  item->radius.ipatokenRadiusRetries = l;
++
++  return NULL;
++}
++
++/* Parse the user's RADIUS username. */
++const char *otpd_parse_radius_username(LDAP *ldp, LDAPMessage *entry,
++                                       struct otpd_queue_item *item)
++{
++  int i;
++
++  i = get_string(ldp, entry, item->radius.ipatokenUserMapAttribute,
++                 &item->user.other);
++  if (i != 0)
++      return strerror(i);
++
++  return NULL;
++}
+diff --git a/daemons/ipa-otpd/query.c b/daemons/ipa-otpd/query.c
+new file mode 100644
+index 0000000..67e2d75
+--- /dev/null
++++ b/daemons/ipa-otpd/query.c
+@@ -0,0 +1,253 @@
++/*
++ * FreeIPA 2FA companion daemon
++ *
++ * Authors: Nathaniel McCallum <npmccallum at redhat.com>
++ *
++ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
++ * see file 'COPYING' for use and warranty information
++ *
++ * This program is free software you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ */
++
++/*
++ * This file receives requests (from stdio.c) and queries the LDAP server for
++ * the user's configuration. When the user's configuration is received, it is
++ * parsed (parse.c). Once the configuration is parsed, the request packet is
++ * either forwarded to a third-party RADIUS server (forward.c) or authenticated
++ * directly via an LDAP bind (bind.c) based on the configuration received.
++ */
++
++#define _GNU_SOURCE 1 /* for asprintf() */
++#include "internal.h"
++#include <ctype.h>
++
++#define DEFAULT_TIMEOUT 15
++#define DEFAULT_RETRIES 3
++
++static char *user[] = {
++    "uid",
++    "ipatokenRadiusUserName",
++    "ipatokenRadiusConfigLink",
++    NULL
++};
++
++static char *radius[] = {
++    "ipatokenRadiusServer",
++    "ipatokenRadiusSecret",
++    "ipatokenRadiusTimeout",
++    "ipatokenRadiusRetries",
++    "ipatokenUserMapAttribute",
++    NULL
++};
++
++/* Send queued LDAP requests to the server. */
++static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
++{
++    struct otpd_queue *push = &ctx.stdio.responses;
++    const krb5_data *princ = NULL;
++    char *filter = NULL, *attrs[2];
++    int i = LDAP_SUCCESS;
++    struct otpd_queue_item *item;
++    (void)vctx;
++
++    item = otpd_queue_pop(&ctx.query.requests);
++    if (item == NULL) {
++        verto_set_flags(ctx.query.io, VERTO_EV_FLAG_PERSIST |
++                                      VERTO_EV_FLAG_IO_ERROR |
++                                      VERTO_EV_FLAG_IO_READ);
++        return;
++    }
++
++    if (item->user.dn == NULL) {
++        princ = krad_packet_get_attr(item->req,
++                                     krad_attr_name2num("User-Name"), 0);
++        if (princ == NULL)
++            goto error;
++
++        otpd_log_req(item->req, "user query start");
++
++        if (asprintf(&filter, "(&(objectClass=Person)(krbPrincipalName=%*s))",
++                     princ->length, princ->data) < 0)
++            goto error;
++
++        i = ldap_search_ext(verto_get_private(ev), ctx.query.base,
++                            LDAP_SCOPE_SUBTREE, filter, user, 0, NULL,
++                            NULL, NULL, 1, &item->msgid);
++        free(filter);
++
++    } else if (item->radius.ipatokenRadiusSecret == NULL) {
++        otpd_log_req(item->req, "radius query start: %s",
++                item->user.ipatokenRadiusConfigLink);
++
++        i = ldap_search_ext(verto_get_private(ev),
++                            item->user.ipatokenRadiusConfigLink,
++                            LDAP_SCOPE_BASE, NULL, radius, 0, NULL,
++                            NULL, NULL, 1, &item->msgid);
++
++    } else if (item->radius.ipatokenUserMapAttribute != NULL) {
++        otpd_log_req(item->req, "username query start: %s",
++                item->radius.ipatokenUserMapAttribute);
++
++        attrs[0] = item->radius.ipatokenUserMapAttribute;
++        attrs[1] = NULL;
++        i = ldap_search_ext(verto_get_private(ev), item->user.dn,
++                            LDAP_SCOPE_BASE, NULL, attrs, 0, NULL,
++                            NULL, NULL, 1, &item->msgid);
++    }
++
++    if (i == LDAP_SUCCESS) {
++        item->sent++;
++        push = &ctx.query.responses;
++    }
++
++error:
++    otpd_queue_push(push, item);
++}
++
++/* Read LDAP responses from the server. */
++static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
++{
++    struct otpd_queue *push = &ctx.stdio.responses;
++    verto_ev *event = ctx.stdio.writer;
++    LDAPMessage *results, *entry;
++    struct otpd_queue_item *item = NULL;
++    const char *err;
++    LDAP *ldp;
++    int i;
++    (void)vctx;
++
++    ldp = verto_get_private(ev);
++
++    i = ldap_result(ldp, LDAP_RES_ANY, 0, NULL, &results);
++    if (i != LDAP_RES_SEARCH_ENTRY && i != LDAP_RES_SEARCH_RESULT) {
++        if (i <= 0)
++            results = NULL;
++        goto egress;
++    }
++
++    item = otpd_queue_pop_msgid(&ctx.query.responses, ldap_msgid(results));
++    if (item == NULL)
++        goto egress;
++
++    if (i == LDAP_RES_SEARCH_ENTRY) {
++        entry = ldap_first_entry(ldp, results);
++        if (entry == NULL)
++            goto egress;
++
++        err = NULL;
++        switch (item->sent) {
++        case 1:
++            err = otpd_parse_user(ldp, entry, item);
++            break;
++        case 2:
++            err = otpd_parse_radius(ldp, entry, item);
++            break;
++        case 3:
++            err = otpd_parse_radius_username(ldp, entry, item);
++            break;
++        default:
++            ldap_msgfree(entry);
++            goto egress;
++        }
++
++        ldap_msgfree(entry);
++
++        if (err != NULL) {
++            if (item->error != NULL)
++                free(item->error);
++            item->error = strdup(err);
++            if (item->error == NULL)
++                goto egress;
++        }
++
++        otpd_queue_push_head(&ctx.query.responses, item);
++        return;
++    }
++
++    item->msgid = -1;
++
++    switch (item->sent) {
++    case 1:
++        otpd_log_req(item->req, "user query end: %s",
++                item->error == NULL ? item->user.dn : item->error);
++        if (item->user.dn == NULL || item->user.uid == NULL)
++            goto egress;
++        break;
++    case 2:
++        otpd_log_req(item->req, "radius query end: %s",
++                item->error == NULL
++                    ? item->radius.ipatokenRadiusServer
++                    : item->error);
++        if (item->radius.ipatokenRadiusServer == NULL ||
++            item->radius.ipatokenRadiusSecret == NULL)
++            goto egress;
++        break;
++    case 3:
++        otpd_log_req(item->req, "username query end: %s",
++                item->error == NULL ? item->user.other : item->error);
++        break;
++    default:
++        goto egress;
++    }
++
++    if (item->error != NULL)
++        goto egress;
++
++    if (item->sent == 1 && item->user.ipatokenRadiusConfigLink != NULL) {
++        push = &ctx.query.requests;
++        event = ctx.query.io;
++        goto egress;
++    } else if (item->sent == 2 &&
++               item->radius.ipatokenUserMapAttribute != NULL &&
++               item->user.ipatokenRadiusUserName == NULL) {
++        push = &ctx.query.requests;
++        event = ctx.query.io;
++        goto egress;
++    }
++
++    /* Forward to RADIUS if necessary. */
++    i = otpd_forward(&item);
++    if (i != 0)
++        goto egress;
++
++    push = &ctx.bind.requests;
++    event = ctx.bind.io;
++
++egress:
++    ldap_msgfree(results);
++    otpd_queue_push(push, item);
++
++    if (item != NULL)
++        verto_set_flags(event, VERTO_EV_FLAG_PERSIST |
++                               VERTO_EV_FLAG_IO_ERROR |
++                               VERTO_EV_FLAG_IO_READ |
++                               VERTO_EV_FLAG_IO_WRITE);
++}
++
++/* Handle the reading/writing of LDAP query requests asynchronously. */
++void otpd_on_query_io(verto_ctx *vctx, verto_ev *ev)
++{
++    verto_ev_flag flags;
++
++    flags = verto_get_fd_state(ev);
++    if (flags & VERTO_EV_FLAG_IO_WRITE)
++        on_query_writable(vctx, ev);
++    if (flags & VERTO_EV_FLAG_IO_READ)
++        on_query_readable(vctx, ev);
++    if (flags & VERTO_EV_FLAG_IO_ERROR) {
++        otpd_log_err(EIO, "IO error received on query socket");
++        verto_break(ctx.vctx);
++        ctx.exitstatus = 1;
++    }
++}
+diff --git a/daemons/ipa-otpd/queue.c b/daemons/ipa-otpd/queue.c
+new file mode 100644
+index 0000000..730bbc4
+--- /dev/null
++++ b/daemons/ipa-otpd/queue.c
+@@ -0,0 +1,183 @@
++/*
++ * FreeIPA 2FA companion daemon
++ *
++ * Authors: Nathaniel McCallum <npmccallum at redhat.com>
++ *
++ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
++ * see file 'COPYING' for use and warranty information
++ *
++ * This program is free software you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ */
++
++/*
++ * This file contains an implementation of a queue of request/response items.
++ */
++
++#include "internal.h"
++
++struct otpd_queue_iter {
++    struct otpd_queue_item *next;
++    unsigned int qindx;
++    const struct otpd_queue * const *queues;
++};
++
++krb5_error_code otpd_queue_item_new(krad_packet *req,
++                                    struct otpd_queue_item **item)
++{
++    *item = calloc(1, sizeof(struct otpd_queue_item));
++    if (*item == NULL)
++        return ENOMEM;
++
++    (*item)->req = req;
++    (*item)->msgid = -1;
++    return 0;
++}
++
++void otpd_queue_item_free(struct otpd_queue_item *item)
++{
++    if (item == NULL)
++        return;
++
++    ldap_memfree(item->user.dn);
++    free(item->user.uid);
++    free(item->user.ipatokenRadiusUserName);
++    free(item->user.ipatokenRadiusConfigLink);
++    free(item->user.other);
++    free(item->radius.ipatokenRadiusServer);
++    free(item->radius.ipatokenRadiusSecret);
++    free(item->radius.ipatokenUserMapAttribute);
++    free(item->error);
++    krad_packet_free(item->req);
++    krad_packet_free(item->rsp);
++    free(item);
++}
++
++krb5_error_code otpd_queue_iter_new(const struct otpd_queue * const *queues,
++                                    struct otpd_queue_iter **iter)
++{
++    *iter = calloc(1, sizeof(struct otpd_queue_iter));
++    if (*iter == NULL)
++        return ENOMEM;
++
++    (*iter)->queues = queues;
++    return 0;
++}
++
++/* This iterator function is used by krad to loop over all outstanding requests
++ * to check for duplicates. Hence, we have to iterate over all the queues to
++ * return all the outstanding requests as a flat list. */
++const krad_packet *otpd_queue_iter_func(void *data, krb5_boolean cancel)
++{
++    struct otpd_queue_iter *iter = data;
++    const struct otpd_queue *q;
++
++    if (cancel) {
++        free(iter);
++        return NULL;
++    }
++
++    if (iter->next != NULL) {
++        struct otpd_queue_item *tmp;
++        tmp = iter->next;
++        iter->next = tmp->next;
++        return tmp->req;
++    }
++
++    q = iter->queues[iter->qindx++];
++    if (q == NULL)
++        return otpd_queue_iter_func(data, TRUE);
++
++    iter->next = q->head;
++    return otpd_queue_iter_func(data, FALSE);
++}
++
++void otpd_queue_push(struct otpd_queue *q, struct otpd_queue_item *item)
++{
++    if (item == NULL)
++        return;
++
++    if (q->tail == NULL)
++        q->head = q->tail = item;
++    else
++        q->tail = q->tail->next = item;
++}
++
++void otpd_queue_push_head(struct otpd_queue *q, struct otpd_queue_item *item)
++{
++    if (item == NULL)
++        return;
++
++    if (q->head == NULL)
++        q->tail = q->head = item;
++    else {
++        item->next = q->head;
++        q->head = item;
++    }
++}
++
++struct otpd_queue_item *otpd_queue_peek(struct otpd_queue *q)
++{
++    return q->head;
++}
++
++struct otpd_queue_item *otpd_queue_pop(struct otpd_queue *q)
++{
++    struct otpd_queue_item *item;
++
++    if (q == NULL)
++        return NULL;
++
++    item = q->head;
++    if (item != NULL)
++        q->head = item->next;
++
++    if (q->head == NULL)
++        q->tail = NULL;
++
++    return item;
++}
++
++/* Remove and return an item from the queue with the given msgid. */
++struct otpd_queue_item *otpd_queue_pop_msgid(struct otpd_queue *q, int msgid)
++{
++    struct otpd_queue_item *item, **prev;
++
++    for (item = q->head, prev = &q->head;
++         item != NULL;
++         item = item->next, prev = &item->next) {
++        if (item->msgid == msgid) {
++            *prev = item->next;
++            if (q->head == NULL)
++                q->tail = NULL;
++            return item;
++        }
++    }
++
++    return NULL;
++}
++
++void otpd_queue_free_items(struct otpd_queue *q)
++{
++    struct otpd_queue_item *item, *next;
++
++    next = q->head;
++    while (next != NULL) {
++        item = next;
++        next = next->next;
++        otpd_queue_item_free(item);
++    }
++
++    q->head = NULL;
++    q->tail = NULL;
++}
+diff --git a/daemons/ipa-otpd/stdio.c b/daemons/ipa-otpd/stdio.c
+new file mode 100644
+index 0000000..ac51c78
+--- /dev/null
++++ b/daemons/ipa-otpd/stdio.c
+@@ -0,0 +1,205 @@
++/*
++ * FreeIPA 2FA companion daemon
++ *
++ * Authors: Nathaniel McCallum <npmccallum at redhat.com>
++ *
++ * Copyright (C) 2013  Nathaniel McCallum, Red Hat
++ * see file 'COPYING' for use and warranty information
++ *
++ * This program is free software you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ */
++
++/*
++ * This file reads and writes RADIUS packets on STDIN/STDOUT.
++ *
++ * Incoming requests are placed into a "query" queue to look up the user's
++ * configuration from LDAP (query.c).
++ */
++
++#include "internal.h"
++
++static const struct otpd_queue *const queues[] = {
++    &ctx.stdio.responses,
++    &ctx.query.requests,
++    &ctx.query.responses,
++    &ctx.bind.requests,
++    &ctx.bind.responses,
++    NULL
++};
++
++/* Read a RADIUS request from stdin. */
++void otpd_on_stdin_readable(verto_ctx *vctx, verto_ev *ev)
++{
++    static char _buffer[KRAD_PACKET_SIZE_MAX];
++    static krb5_data buffer = { .data = _buffer, .length = 0 };
++    (void)vctx;
++
++    const krad_packet *dup;
++    const krb5_data *data;
++    struct otpd_queue_iter *iter;
++    struct otpd_queue_item *item;
++    krad_packet *req;
++    ssize_t pktlen;
++    int i;
++
++    pktlen = krad_packet_bytes_needed(&buffer);
++    if (pktlen < 0) {
++        otpd_log_err(EBADMSG, "Received a malformed packet");
++        goto shutdown;
++    }
++
++    /* Read the item. */
++    i = read(verto_get_fd(ev), buffer.data + buffer.length, pktlen);
++    if (i < 1) {
++        /* On EOF, shutdown gracefully. */
++        if (i == 0) {
++            fprintf(stderr, "Socket closed, shutting down...\n");
++            verto_break(ctx.vctx);
++            return;
++        }
++
++        if (errno != EAGAIN && errno != EINTR) {
++            otpd_log_err(errno, "Error receiving packet");
++            goto shutdown;
++        }
++
++        return;
++    }
++
++    /* If we have a partial read or just the header, try again. */
++    buffer.length += i;
++    pktlen = krad_packet_bytes_needed(&buffer);
++    if (pktlen > 0)
++        return;
++
++    /* Create the iterator. */
++    i = otpd_queue_iter_new(queues, &iter);
++    if (i != 0) {
++        otpd_log_err(i, "Unable to create iterator");
++        goto shutdown;
++    }
++
++    /* Decode the item. */
++    i = krad_packet_decode_request(ctx.kctx, SECRET, &buffer,
++                                   otpd_queue_iter_func, iter, &dup, &req);
++    buffer.length = 0;
++    if (i == EAGAIN)
++        return;
++    else if (i != 0) {
++        otpd_log_err(i, "Unable to decode item");
++        goto shutdown;
++    }
++
++    /* Drop duplicate requests. */
++    if (dup != NULL) {
++        krad_packet_free(req);
++        return;
++    }
++
++    /* Ensure the packet has the User-Name attribute. */
++    data = krad_packet_get_attr(req, krad_attr_name2num("User-Name"), 0);
++    if (data == NULL) {
++        krad_packet_free(req);
++        return;
++    }
++
++    /* Create the new queue item. */
++    i = otpd_queue_item_new(req, &item);
++    if (i != 0) {
++        krad_packet_free(req);
++        return;
++    }
++
++    /* Push it to the query queue. */
++    otpd_queue_push(&ctx.query.requests, item);
++    verto_set_flags(ctx.query.io, VERTO_EV_FLAG_PERSIST |
++                                  VERTO_EV_FLAG_IO_ERROR |
++                                  VERTO_EV_FLAG_IO_READ |
++                                  VERTO_EV_FLAG_IO_WRITE);
++
++    otpd_log_req(req, "request received");
++    return;
++
++shutdown:
++    verto_break(ctx.vctx);
++    ctx.exitstatus = 1;
++}
++
++/* Send a RADIUS response to stdout. */
++void otpd_on_stdout_writable(verto_ctx *vctx, verto_ev *ev)
++{
++    const krb5_data *data;
++    struct otpd_queue_item *item;
++    int i;
++    (void)vctx;
++
++    item = otpd_queue_peek(&ctx.stdio.responses);
++    if (item == NULL) {
++        verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
++                                          VERTO_EV_FLAG_IO_ERROR |
++                                          VERTO_EV_FLAG_IO_READ);
++        return;
++    }
++
++    /* If no response has been generated thus far, send Access-Reject. */
++    if (item->rsp == NULL) {
++        item->sent = 0;
++        i = krad_packet_new_response(ctx.kctx, SECRET,
++                                     krad_code_name2num("Access-Reject"),
++                                     NULL, item->req, &item->rsp);
++        if (i != 0) {
++            otpd_log_err(errno, "Unable to craft response");
++            goto shutdown;
++        }
++    }
++
++    /* Send the packet. */
++    data = krad_packet_encode(item->rsp);
++    i = write(verto_get_fd(ev), data->data + item->sent,
++              data->length - item->sent);
++    if (i < 0) {
++        switch (errno) {
++#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN - EWOULDBLOCK != 0)
++        case EWOULDBLOCK:
++#endif
++#if defined(EAGAIN)
++        case EAGAIN:
++#endif
++        case ENOBUFS:
++        case EINTR:
++            /* In this case, we just need to try again. */
++            return;
++        default:
++            /* Unrecoverable. */
++            break;
++        }
++
++        otpd_log_err(errno, "Error writing to stdout!");
++        goto shutdown;
++    }
++
++    /* If the packet was completely sent, free the response. */
++    item->sent += i;
++    if (item->sent == data->length) {
++        otpd_log_req(item->req, "response sent: %s",
++                krad_code_num2name(krad_packet_get_code(item->rsp)));
++        otpd_queue_item_free(otpd_queue_pop(&ctx.stdio.responses));
++    }
++
++    return;
++
++shutdown:
++    verto_break(ctx.vctx);
++    ctx.exitstatus = 1;
++}
+diff --git a/daemons/ipa-otpd/test.py b/daemons/ipa-otpd/test.py
+new file mode 100644
+index 0000000..d748c82
+--- /dev/null
++++ b/daemons/ipa-otpd/test.py
+@@ -0,0 +1,61 @@
++#!/usr/bin/python
++#
++# FreeIPA 2FA companion daemon
++#
++# Authors: Nathaniel McCallum <npmccallum at redhat.com>
++#
++# Copyright (C) 2013  Nathaniel McCallum, Red Hat
++# see file 'COPYING' for use and warranty information
++#
++# This program is free software you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.
++
++import StringIO
++import struct
++import subprocess
++import sys
++
++try:
++    from pyrad import packet
++    from pyrad.dictionary import Dictionary
++except ImportError:
++    sys.stdout.write("pyrad not found!\n")
++    sys.exit(0)
++
++# We could use a dictionary file, but since we need
++# such few attributes, we'll just include them here
++DICTIONARY = """
++ATTRIBUTE	User-Name	1	string
++ATTRIBUTE	User-Password	2	string
++ATTRIBUTE	NAS-Identifier	32	string
++"""
++
++dct = Dictionary(StringIO.StringIO(DICTIONARY))
++
++proc = subprocess.Popen(["./ipa-otpd", sys.argv[1]],
++                        stdin=subprocess.PIPE, stdout=subprocess.PIPE)
++
++pkt = packet.AuthPacket(secret="", dict=dct)
++pkt["User-Name"] = sys.argv[2]
++pkt["User-Password"] = pkt.PwCrypt(sys.argv[3])
++pkt["NAS-Identifier"] = "localhost"
++proc.stdin.write(pkt.RequestPacket())
++
++rsp = packet.Packet(secret="", dict=dict)
++buf = proc.stdout.read(4)
++buf += proc.stdout.read(struct.unpack("!BBH", buf)[2] - 4)
++rsp.DecodePacket(buf)
++pkt.VerifyReply(rsp)
++
++proc.terminate() #pylint: disable=E1101
++proc.wait()
+diff --git a/freeipa.spec.in b/freeipa.spec.in
+index c67c09c..4a38e87 100644
+--- a/freeipa.spec.in
++++ b/freeipa.spec.in
+@@ -37,11 +37,7 @@ BuildRequires:  nspr-devel
+ BuildRequires:  nss-devel
+ BuildRequires:  openssl-devel
+ BuildRequires:  openldap-devel
+-%if 0%{?fedora} >= 19
+ BuildRequires:  krb5-devel >= 1.11
+-%else
+-BuildRequires:  krb5-devel >= 1.10
+-%endif
+ BuildRequires:  krb5-workstation
+ BuildRequires:  libuuid-devel
+ BuildRequires:  libcurl-devel >= 7.21.7-2
+@@ -73,6 +69,8 @@ BuildRequires:  m2crypto
+ BuildRequires:  check
+ BuildRequires:  libsss_idmap-devel
+ BuildRequires:  java-1.7.0-openjdk
++BuildRequires:  libverto-devel
++BuildRequires:  systemd
+ 
+ # Find out Kerberos middle version to infer ABI changes in DAL driver
+ # We cannot load DAL driver into KDC with wrong ABI.
+@@ -639,6 +637,7 @@ fi
+ %{_sbindir}/ipa-upgradeconfig
+ %{_sbindir}/ipa-compliance
+ %{_libexecdir}/certmonger/dogtag-ipa-retrieve-agent-submit
++%{_libexecdir}/ipa-otpd
+ %{_sysconfdir}/cron.d/ipa-compliance
+ %config(noreplace) %{_sysconfdir}/sysconfig/ipa_memcached
+ %dir %attr(0700,apache,apache) %{_localstatedir}/run/ipa_memcached/
+@@ -647,6 +646,8 @@ fi
+ %config %{_sysconfdir}/tmpfiles.d/ipa.conf
+ %attr(644,root,root) %{_unitdir}/ipa.service
+ %attr(644,root,root) %{_unitdir}/ipa_memcached.service
++%attr(644,root,root) %{_unitdir}/ipa-otpd.socket
++%attr(644,root,root) %{_unitdir}/ipa-otpd at .service
+ # END
+ %dir %{python_sitelib}/ipaserver
+ %dir %{python_sitelib}/ipaserver/install
+-- 
+1.8.2.1
+
diff --git a/0005-Remove-unnecessary-prefixes-from-ipa-pwd-extop-files.patch b/0005-Remove-unnecessary-prefixes-from-ipa-pwd-extop-files.patch
new file mode 100644
index 0000000..dd06461
--- /dev/null
+++ b/0005-Remove-unnecessary-prefixes-from-ipa-pwd-extop-files.patch
@@ -0,0 +1,5603 @@
+From fe0b5a2cf772c3f85ca2c030b5be2dd0cd9c041b Mon Sep 17 00:00:00 2001
+From: Nathaniel McCallum <npmccallum at redhat.com>
+Date: Thu, 9 May 2013 14:43:17 -0400
+Subject: [PATCH 5/6] Remove unnecessary prefixes from ipa-pwd-extop files
+
+---
+ .../ipa-slapi-plugins/ipa-pwd-extop/Makefile.am    |    6 +-
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c   | 1107 ++++++++++++++++
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c |  291 +++++
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h   |    6 +-
+ .../ipa-pwd-extop/ipapwd_common.c                  | 1107 ----------------
+ .../ipa-pwd-extop/ipapwd_encoding.c                |  291 -----
+ .../ipa-pwd-extop/ipapwd_prepost.c                 | 1349 --------------------
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c  | 1349 ++++++++++++++++++++
+ 8 files changed, 2753 insertions(+), 2753 deletions(-)
+ create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
+ create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c
+ delete mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
+ delete mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c
+ delete mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c
+ create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
+index ec98f95..90f940f 100644
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
+@@ -30,9 +30,9 @@ plugin_LTLIBRARIES = 			\
+ 	$(NULL)
+ 
+ libipa_pwd_extop_la_SOURCES = 		\
+-	ipapwd_common.c			\
+-	ipapwd_encoding.c		\
+-	ipapwd_prepost.c		\
++	common.c			\
++	encoding.c			\
++	prepost.c			\
+ 	ipa_pwd_extop.c			\
+ 	$(KRB5_UTIL_SRCS)		\
+ 	$(NULL)
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
+new file mode 100644
+index 0000000..bb1d96a
+--- /dev/null
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
+@@ -0,0 +1,1107 @@
++/** BEGIN COPYRIGHT BLOCK
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ *
++ * Additional permission under GPLv3 section 7:
++ *
++ * In the following paragraph, "GPL" means the GNU General Public
++ * License, version 3 or any later version, and "Non-GPL Code" means
++ * code that is governed neither by the GPL nor a license
++ * compatible with the GPL.
++ *
++ * You may link the code of this Program with Non-GPL Code and convey
++ * linked combinations including the two, provided that such Non-GPL
++ * Code only links to the code of this Program through those well
++ * defined interfaces identified in the file named EXCEPTION found in
++ * the source code files (the "Approved Interfaces"). The files of
++ * Non-GPL Code may instantiate templates or use macros or inline
++ * functions from the Approved Interfaces without causing the resulting
++ * work to be covered by the GPL. Only the copyright holders of this
++ * Program may make changes or additions to the list of Approved
++ * Interfaces.
++ *
++ * Authors:
++ * Simo Sorce <ssorce at redhat.com>
++ *
++ * Copyright (C) 2007-2010 Red Hat, Inc.
++ * All rights reserved.
++ * END COPYRIGHT BLOCK **/
++
++#include "ipapwd.h"
++#include "util.h"
++
++/* Type of connection for this operation;*/
++#define LDAP_EXTOP_PASSMOD_CONN_SECURE
++
++/* Uncomment the following #undef FOR TESTING:
++ * allows non-SSL connections to use the password change extended op */
++/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */
++
++extern void *ipapwd_plugin_id;
++extern const char *ipa_realm_dn;
++extern const char *ipa_etc_config_dn;
++extern const char *ipa_pwd_config_dn;
++
++/* These are the default enc:salt types if nothing is defined.
++ * TODO: retrieve the configure set of ecntypes either from the
++ * kfc.conf file or by synchronizing the file content into
++ * the directory */
++static const char *ipapwd_def_encsalts[] = {
++    "des3-hmac-sha1:normal",
++/*    "arcfour-hmac:normal",
++    "des-hmac-sha1:normal",
++    "des-cbc-md5:normal", */
++    "des-cbc-crc:normal",
++/*    "des-cbc-crc:v4",
++    "des-cbc-crc:afs3", */
++    NULL
++};
++
++static struct ipapwd_krbcfg *ipapwd_getConfig(void)
++{
++    krb5_error_code krberr;
++    struct ipapwd_krbcfg *config = NULL;
++    krb5_keyblock *kmkey = NULL;
++    Slapi_Entry *realm_entry = NULL;
++    Slapi_Entry *config_entry = NULL;
++    Slapi_Attr *a;
++    Slapi_Value *v;
++    BerElement *be = NULL;
++    ber_tag_t tag, tvno;
++    ber_int_t ttype;
++    const struct berval *bval;
++    struct berval *mkey = NULL;
++    char **encsalts;
++    char **tmparray;
++    char *tmpstr;
++    int i, ret;
++
++    config = calloc(1, sizeof(struct ipapwd_krbcfg));
++    if (!config) {
++        LOG_OOM();
++        goto free_and_error;
++    }
++    kmkey = calloc(1, sizeof(krb5_keyblock));
++    if (!kmkey) {
++        LOG_OOM();
++        goto free_and_error;
++    }
++    config->kmkey = kmkey;
++
++    krberr = krb5_init_context(&config->krbctx);
++    if (krberr) {
++        LOG_FATAL("krb5_init_context failed\n");
++        goto free_and_error;
++    }
++
++    ret = krb5_get_default_realm(config->krbctx, &config->realm);
++    if (ret) {
++        LOG_FATAL("Failed to get default realm?!\n");
++        goto free_and_error;
++    }
++
++    /* get the Realm Container entry */
++    ret = ipapwd_getEntry(ipa_realm_dn, &realm_entry, NULL);
++    if (ret != LDAP_SUCCESS) {
++        LOG_FATAL("No realm Entry?\n");
++        goto free_and_error;
++    }
++
++    /*** get the Kerberos Master Key ***/
++
++    ret = slapi_entry_attr_find(realm_entry, "krbMKey", &a);
++    if (ret == -1) {
++        LOG_FATAL("No master key??\n");
++        goto free_and_error;
++    }
++
++    /* there should be only one value here */
++    ret = slapi_attr_first_value(a, &v);
++    if (ret == -1) {
++        LOG_FATAL("No master key??\n");
++        goto free_and_error;
++    }
++
++    bval = slapi_value_get_berval(v);
++    if (!bval) {
++        LOG_FATAL("Error retrieving master key berval\n");
++        goto free_and_error;
++    }
++
++    be = ber_init(discard_const(bval));
++    if (!be) {
++        LOG_FATAL("ber_init() failed!\n");
++        goto free_and_error;
++    }
++
++    tag = ber_scanf(be, "{i{iO}}", &tvno, &ttype, &mkey);
++    if (tag == LBER_ERROR) {
++        LOG_FATAL("Bad Master key encoding ?!\n");
++        goto free_and_error;
++    }
++
++    config->mkvno = tvno;
++    kmkey->magic = KV5M_KEYBLOCK;
++    kmkey->enctype = ttype;
++    kmkey->length = mkey->bv_len;
++    kmkey->contents = malloc(mkey->bv_len);
++    if (!kmkey->contents) {
++        LOG_OOM();
++        goto free_and_error;
++    }
++    memcpy(kmkey->contents, mkey->bv_val, mkey->bv_len);
++    ber_bvfree(mkey);
++    ber_free(be, 1);
++    mkey = NULL;
++    be = NULL;
++
++    /*** get the Supported Enc/Salt types ***/
++
++    encsalts = slapi_entry_attr_get_charray(realm_entry,
++                                            "krbSupportedEncSaltTypes");
++    if (encsalts) {
++        for (i = 0; encsalts[i]; i++) /* count */ ;
++        ret = parse_bval_key_salt_tuples(config->krbctx,
++                                         (const char * const *)encsalts, i,
++                                         &config->supp_encsalts,
++                                         &config->num_supp_encsalts);
++        slapi_ch_array_free(encsalts);
++    } else {
++        LOG("No configured salt types use defaults\n");
++        for (i = 0; ipapwd_def_encsalts[i]; i++) /* count */ ;
++        ret = parse_bval_key_salt_tuples(config->krbctx,
++                                         ipapwd_def_encsalts, i,
++                                         &config->supp_encsalts,
++                                         &config->num_supp_encsalts);
++    }
++    if (ret) {
++        LOG_FATAL("Can't get Supported EncSalt Types\n");
++        goto free_and_error;
++    }
++
++    /*** get the Preferred Enc/Salt types ***/
++
++    encsalts = slapi_entry_attr_get_charray(realm_entry,
++                                            "krbDefaultEncSaltTypes");
++    if (encsalts) {
++        for (i = 0; encsalts[i]; i++) /* count */ ;
++        ret = parse_bval_key_salt_tuples(config->krbctx,
++                                         (const char * const *)encsalts, i,
++                                         &config->pref_encsalts,
++                                         &config->num_pref_encsalts);
++        slapi_ch_array_free(encsalts);
++    } else {
++        LOG("No configured salt types use defaults\n");
++        for (i = 0; ipapwd_def_encsalts[i]; i++) /* count */ ;
++        ret = parse_bval_key_salt_tuples(config->krbctx,
++                                         ipapwd_def_encsalts, i,
++                                         &config->pref_encsalts,
++                                         &config->num_pref_encsalts);
++    }
++    if (ret) {
++        LOG_FATAL("Can't get Preferred EncSalt Types\n");
++        goto free_and_error;
++    }
++
++    slapi_entry_free(realm_entry);
++
++    /* get the Realm Container entry */
++    ret = ipapwd_getEntry(ipa_pwd_config_dn, &config_entry, NULL);
++    if (ret != LDAP_SUCCESS) {
++        LOG_FATAL("No config Entry? Impossible!\n");
++        goto free_and_error;
++    }
++    config->passsync_mgrs =
++            slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs");
++    /* now add Directory Manager, it is always added by default */
++    tmpstr = slapi_ch_strdup("cn=Directory Manager");
++    slapi_ch_array_add(&config->passsync_mgrs, tmpstr);
++    if (config->passsync_mgrs == NULL) {
++        LOG_OOM();
++        goto free_and_error;
++    }
++    for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ;
++    config->num_passsync_mgrs = i;
++
++    slapi_entry_free(config_entry);
++
++    /* get the ipa etc/ipaConfig entry */
++    config->allow_lm_hash = false;
++    config->allow_nt_hash = false;
++    ret = ipapwd_getEntry(ipa_etc_config_dn, &config_entry, NULL);
++    if (ret != LDAP_SUCCESS) {
++        LOG_FATAL("No config Entry?\n");
++        goto free_and_error;
++    } else {
++        tmparray = slapi_entry_attr_get_charray(config_entry,
++                                                "ipaConfigString");
++        for (i = 0; tmparray && tmparray[i]; i++) {
++            if (strcasecmp(tmparray[i], "AllowLMhash") == 0) {
++                config->allow_lm_hash = true;
++                continue;
++            }
++            if (strcasecmp(tmparray[i], "AllowNThash") == 0) {
++                config->allow_nt_hash = true;
++                continue;
++            }
++        }
++        if (tmparray) slapi_ch_array_free(tmparray);
++    }
++
++    slapi_entry_free(config_entry);
++
++    return config;
++
++free_and_error:
++    if (mkey) ber_bvfree(mkey);
++    if (be) ber_free(be, 1);
++    if (kmkey) {
++        free(kmkey->contents);
++        free(kmkey);
++    }
++    if (config) {
++        if (config->krbctx) {
++            if (config->realm)
++                krb5_free_default_realm(config->krbctx, config->realm);
++            krb5_free_context(config->krbctx);
++        }
++        free(config->pref_encsalts);
++        free(config->supp_encsalts);
++        slapi_ch_array_free(config->passsync_mgrs);
++        free(config);
++    }
++    slapi_entry_free(config_entry);
++    slapi_entry_free(realm_entry);
++    return NULL;
++}
++
++/* Easier handling for virtual attributes. You must call pwd_values_free()
++ * to free memory allocated here. It must be called before
++ * slapi_free_search_results_internal(entries) or
++ * slapi_pblock_destroy(pb)
++ */
++static int pwd_get_values(const Slapi_Entry *ent, const char *attrname,
++			  Slapi_ValueSet** results, char** actual_type_name,
++			  int *buffer_flags)
++{
++    int flags=0;
++    int type_name_disposition = 0;
++    int ret;
++
++    ret = slapi_vattr_values_get((Slapi_Entry *)ent, (char *)attrname,
++                                 results, &type_name_disposition,
++                                 actual_type_name, flags, buffer_flags);
++
++    return ret;
++}
++
++static void pwd_values_free(Slapi_ValueSet** results,
++                            char** actual_type_name, int buffer_flags)
++{
++    slapi_vattr_values_free(results, actual_type_name, buffer_flags);
++}
++
++static int ipapwd_rdn_count(const char *dn)
++{
++    int rdnc = 0;
++    LDAPDN ldn;
++    int ret;
++
++    ret = ldap_str2dn(dn, &ldn, LDAP_DN_FORMAT_LDAPV3);
++    if (ret != LDAP_SUCCESS) {
++        LOG_TRACE("ldap_str2dn(dn) failed ?!");
++        return -1;
++    }
++
++    for (rdnc = 0; ldn != NULL && ldn[rdnc]; rdnc++) /* count */ ;
++    ldap_dnfree(ldn);
++
++    return rdnc;
++}
++
++int ipapwd_getPolicy(const char *dn,
++                     Slapi_Entry *target,
++                     struct ipapwd_policy *policy)
++{
++    const char *krbPwdPolicyReference;
++    const char *pdn;
++    const Slapi_DN *psdn;
++    Slapi_Backend *be;
++    Slapi_PBlock *pb = NULL;
++    char *attrs[] = { "krbMaxPwdLife", "krbMinPwdLife",
++                      "krbPwdMinDiffChars", "krbPwdMinLength",
++                      "krbPwdHistoryLength", NULL};
++    Slapi_Entry **es = NULL;
++    Slapi_Entry *pe = NULL;
++    int ret, res, dist, rdnc, scope, i;
++    Slapi_DN *sdn = NULL;
++    int buffer_flags=0;
++    Slapi_ValueSet* results = NULL;
++    char* actual_type_name = NULL;
++    int tmpint;
++
++    LOG_TRACE("Searching policy for [%s]\n", dn);
++
++    sdn = slapi_sdn_new_dn_byref(dn);
++    if (sdn == NULL) {
++        LOG_OOM();
++        ret = -1;
++        goto done;
++    }
++
++    pwd_get_values(target, "krbPwdPolicyReference",
++                   &results, &actual_type_name, &buffer_flags);
++    if (results) {
++        Slapi_Value *sv;
++        slapi_valueset_first_value(results, &sv);
++        krbPwdPolicyReference = slapi_value_get_string(sv);
++        pdn = krbPwdPolicyReference;
++        scope = LDAP_SCOPE_BASE;
++        LOG_TRACE("using policy reference: %s\n", pdn);
++    } else {
++        /* Find ancestor base DN */
++        be = slapi_be_select(sdn);
++        psdn = slapi_be_getsuffix(be, 0);
++        if (psdn == NULL) {
++            LOG_FATAL("Invalid DN [%s]\n", dn);
++            ret = -1;
++            goto done;
++        }
++        pdn = slapi_sdn_get_dn(psdn);
++        scope = LDAP_SCOPE_SUBTREE;
++    }
++
++    pb = slapi_pblock_new();
++    slapi_search_internal_set_pb(pb,
++                                 pdn, scope,
++                                 "(objectClass=krbPwdPolicy)",
++                                 attrs, 0,
++                                 NULL, /* Controls */
++                                 NULL, /* UniqueID */
++                                 ipapwd_plugin_id,
++                                 0); /* Flags */
++
++    /* do search the tree */
++    ret = slapi_search_internal_pb(pb);
++    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
++    if (ret == -1 || res != LDAP_SUCCESS) {
++        LOG_FATAL("Couldn't find policy, err (%d)\n", res ? res : ret);
++        ret = -1;
++        goto done;
++    }
++
++    /* get entries */
++    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es);
++    if (!es) {
++        LOG_TRACE("No entries ?!");
++        ret = -1;
++        goto done;
++    }
++
++    /* count entries */
++    for (i = 0; es[i]; i++) /* count */ ;
++
++    /* if there is only one, return that */
++    if (i == 1) {
++        pe = es[0];
++        goto fill;
++    }
++
++    /* count number of RDNs in DN */
++    rdnc = ipapwd_rdn_count(dn);
++    if (rdnc == -1) {
++        LOG_TRACE("ipapwd_rdn_count(dn) failed");
++        ret = -1;
++        goto done;
++    }
++
++    pe = NULL;
++    dist = -1;
++
++    /* find closest entry */
++    for (i = 0; es[i]; i++) {
++        const Slapi_DN *esdn;
++
++        esdn = slapi_entry_get_sdn_const(es[i]);
++        if (esdn == NULL) continue;
++        if (0 == slapi_sdn_compare(esdn, sdn)) {
++            pe = es[i];
++            dist = 0;
++            break;
++        }
++        if (slapi_sdn_issuffix(sdn, esdn)) {
++            const char *dn1;
++            int c1;
++
++            dn1 = slapi_sdn_get_dn(esdn);
++            if (!dn1) continue;
++            c1 = ipapwd_rdn_count(dn1);
++            if (c1 == -1) continue;
++            if ((dist == -1) ||
++                ((rdnc - c1) < dist)) {
++                dist = rdnc - c1;
++                pe = es[i];
++            }
++        }
++        if (dist == 0) break; /* found closest */
++    }
++
++    if (pe == NULL) {
++        ret = -1;
++        goto done;
++    }
++
++fill:
++    policy->min_pwd_life = slapi_entry_attr_get_int(pe, "krbMinPwdLife");
++
++    tmpint = slapi_entry_attr_get_int(pe, "krbMaxPwdLife");
++    if (tmpint != 0) {
++        policy->max_pwd_life = tmpint;
++    }
++
++    tmpint = slapi_entry_attr_get_int(pe, "krbPwdMinLength");
++    if (tmpint != 0) {
++        policy->min_pwd_length = tmpint;
++    }
++
++    policy->history_length = slapi_entry_attr_get_int(pe,
++                                                      "krbPwdHistoryLength");
++
++    policy->min_complexity = slapi_entry_attr_get_int(pe,
++                                                      "krbPwdMinDiffChars");
++
++    ret = 0;
++
++done:
++    if (results) {
++        pwd_values_free(&results, &actual_type_name, buffer_flags);
++    }
++    if (pb) {
++        slapi_free_search_results_internal(pb);
++        slapi_pblock_destroy(pb);
++    }
++    if (sdn) slapi_sdn_free(&sdn);
++    return ret;
++}
++
++
++/*==Common-public-functions=============================================*/
++
++int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
++                        int *is_root, int *is_krb, int *is_smb, int *is_ipant,
++                        char *attr, int acc)
++{
++    Slapi_Value *sval;
++    int rc;
++
++    /* Check ACIs */
++    slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, is_root);
++
++    if (!*is_root) {
++        /* verify this user is allowed to write a user password */
++        rc = slapi_access_allowed(pb, e, attr, NULL, acc);
++        if (rc != LDAP_SUCCESS) {
++            /* we have no business here, the operation will be denied anyway */
++            rc = LDAP_SUCCESS;
++            goto done;
++        }
++    }
++
++    /* Check if this is a krbPrincial and therefore needs us to generate other
++     * hashes */
++    sval = slapi_value_new_string("krbPrincipalAux");
++    if (!sval) {
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++    *is_krb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval);
++    slapi_value_free(&sval);
++
++    sval = slapi_value_new_string("sambaSamAccount");
++    if (!sval) {
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++    *is_smb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval);
++    slapi_value_free(&sval);
++
++    sval = slapi_value_new_string("ipaNTUserAttrs");
++    if (!sval) {
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++    *is_ipant = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS,
++                                                  sval);
++    slapi_value_free(&sval);
++
++    rc = LDAP_SUCCESS;
++
++done:
++    return rc;
++}
++
++int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg,
++                      struct ipapwd_krbcfg **config, int check_flags)
++{
++    int ret, ssf;
++    int rc = LDAP_SUCCESS;
++    Slapi_Backend *be;
++    const Slapi_DN *psdn;
++    Slapi_DN *sdn;
++    char *dn = NULL;
++
++    LOG_TRACE("=>\n");
++
++#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE
++    if (check_flags & IPAPWD_CHECK_CONN_SECURE) {
++       /* Allow password modify on all connections with a Security Strength
++        * Factor (SSF) higher than 1 */
++        if (slapi_pblock_get(pb, SLAPI_OPERATION_SSF, &ssf) != 0) {
++            LOG("Could not get SSF from connection\n");
++            *errMesg = "Operation requires a secure connection.\n";
++            rc = LDAP_OPERATIONS_ERROR;
++            goto done;
++        }
++
++        if (ssf <= 1) {
++            *errMesg = "Operation requires a secure connection.\n";
++            rc = LDAP_CONFIDENTIALITY_REQUIRED;
++            goto done;
++        }
++    }
++#endif
++
++    if (check_flags & IPAPWD_CHECK_DN) {
++        /* check we have a valid DN in the pblock or just abort */
++        ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
++        if (ret) {
++            LOG("Tried to change password for an invalid DN [%s]\n",
++                dn ? dn : "<NULL>");
++            *errMesg = "Invalid DN";
++            rc = LDAP_OPERATIONS_ERROR;
++            goto done;
++        }
++        sdn = slapi_sdn_new_dn_byref(dn);
++        if (!sdn) {
++            LOG_FATAL("Unable to convert dn to sdn %s", dn ? dn : "<NULL>");
++            *errMesg = "Internal Error";
++            rc = LDAP_OPERATIONS_ERROR;
++            goto done;
++        }
++        be = slapi_be_select(sdn);
++        slapi_sdn_free(&sdn);
++
++        psdn = slapi_be_getsuffix(be, 0);
++        if (!psdn) {
++            *errMesg = "Invalid DN";
++            rc = LDAP_OPERATIONS_ERROR;
++            goto done;
++        }
++    }
++
++    /* get the kerberos context and master key */
++    *config = ipapwd_getConfig();
++    if (NULL == *config) {
++        LOG_FATAL("Error Retrieving Master Key");
++        *errMesg = "Fatal Internal Error";
++        rc = LDAP_OPERATIONS_ERROR;
++    }
++
++done:
++    return rc;
++}
++
++/* check password strenght and history */
++int ipapwd_CheckPolicy(struct ipapwd_data *data)
++{
++    struct ipapwd_policy pol = {0};
++    time_t acct_expiration;
++    time_t pwd_expiration;
++    time_t last_pwd_change;
++    char **pwd_history;
++    char *tmpstr;
++    int ret;
++
++    pol.max_pwd_life = IPAPWD_DEFAULT_PWDLIFE;
++    pol.min_pwd_length = IPAPWD_DEFAULT_MINLEN;
++
++    if (data->changetype != IPA_CHANGETYPE_NORMAL) {
++        /* We must skip policy checks (Admin change) but
++         * force a password change on the next login.
++         * But not if Directory Manager */
++        if (data->changetype == IPA_CHANGETYPE_ADMIN) {
++            /* The expiration date needs to be older than the current time
++             * otherwise the KDC may not immediately register the password
++             * as expired. The last password change needs to match the
++             * password expiration otherwise minlife issues will arise.
++             */
++            data->timeNow -= 1;
++            data->expireTime = data->timeNow;
++        }
++
++        /* do not load policies */
++    } else {
++
++        /* find the entry with the password policy */
++        ret = ipapwd_getPolicy(data->dn, data->target, &pol);
++        if (ret) {
++            LOG_TRACE("No password policy, use defaults");
++        }
++    }
++
++    tmpstr = slapi_entry_attr_get_charptr(data->target,
++                                          "krbPrincipalExpiration");
++    acct_expiration = ipapwd_gentime_to_time_t(tmpstr);
++    slapi_ch_free_string(&tmpstr);
++
++    tmpstr = slapi_entry_attr_get_charptr(data->target,
++                                          "krbPasswordExpiration");
++    pwd_expiration = ipapwd_gentime_to_time_t(tmpstr);
++    slapi_ch_free_string(&tmpstr);
++
++    tmpstr = slapi_entry_attr_get_charptr(data->target,
++                                          "krbLastPwdChange");
++    last_pwd_change = ipapwd_gentime_to_time_t(tmpstr);
++    slapi_ch_free_string(&tmpstr);
++
++    pwd_history = slapi_entry_attr_get_charray(data->target,
++                                               "passwordHistory");
++
++    /* check policy */
++    ret = ipapwd_check_policy(&pol, data->password,
++                                    data->timeNow,
++                                    acct_expiration,
++                                    pwd_expiration,
++                                    last_pwd_change,
++                                    pwd_history);
++
++    slapi_ch_array_free(pwd_history);
++
++    if (data->expireTime == 0) {
++        data->expireTime = data->timeNow + pol.max_pwd_life;
++    }
++
++    data->policy = pol;
++
++    return ret;
++}
++
++/* Searches the dn in directory,
++ *  If found	 : fills in slapi_entry structure and returns 0
++ *  If NOT found : returns the search result as LDAP_NO_SUCH_OBJECT
++ */
++int ipapwd_getEntry(const char *dn, Slapi_Entry **e2, char **attrlist)
++{
++    Slapi_DN *sdn;
++    int search_result = 0;
++
++    LOG_TRACE("=>\n");
++
++    sdn = slapi_sdn_new_dn_byref(dn);
++    search_result = slapi_search_internal_get_entry(sdn, attrlist, e2,
++                                                    ipapwd_plugin_id);
++    if (search_result != LDAP_SUCCESS) {
++        LOG_TRACE("No such entry-(%s), err (%d)\n", dn, search_result);
++    }
++
++    slapi_sdn_free(&sdn);
++    LOG_TRACE("<= result: %d\n", search_result);
++    return search_result;
++}
++
++int ipapwd_get_cur_kvno(Slapi_Entry *target)
++{
++    Slapi_Attr *krbPrincipalKey = NULL;
++    Slapi_ValueSet *svs;
++    Slapi_Value *sv;
++    BerElement *be = NULL;
++    const struct berval *cbval;
++    ber_tag_t tag, tmp;
++    ber_int_t tkvno;
++    int hint;
++    int kvno;
++    int ret;
++
++    /* retrieve current kvno and and keys */
++    ret = slapi_entry_attr_find(target, "krbPrincipalKey", &krbPrincipalKey);
++    if (ret != 0) {
++        return 0;
++    }
++
++    kvno = 0;
++
++    slapi_attr_get_valueset(krbPrincipalKey, &svs);
++    hint = slapi_valueset_first_value(svs, &sv);
++    while (hint != -1) {
++        cbval = slapi_value_get_berval(sv);
++        if (!cbval) {
++            LOG_TRACE("Error retrieving berval from Slapi_Value\n");
++            goto next;
++        }
++        be = ber_init(discard_const(cbval));
++        if (!be) {
++            LOG_TRACE("ber_init() failed!\n");
++            goto next;
++        }
++
++        tag = ber_scanf(be, "{xxt[i]", &tmp, &tkvno);
++        if (tag == LBER_ERROR) {
++            LOG_TRACE("Bad OLD key encoding ?!\n");
++            ber_free(be, 1);
++            goto next;
++        }
++
++        if (tkvno > kvno) {
++            kvno = tkvno;
++        }
++
++        ber_free(be, 1);
++next:
++        hint = slapi_valueset_next_value(svs, hint, &sv);
++    }
++
++    return kvno;
++}
++
++/* Modify the Password attributes of the entry */
++int ipapwd_SetPassword(struct ipapwd_krbcfg *krbcfg,
++                       struct ipapwd_data *data, int is_krb)
++{
++    int ret = 0;
++    Slapi_Mods *smods = NULL;
++    Slapi_Value **svals = NULL;
++    Slapi_Value **ntvals = NULL;
++    Slapi_Value **pwvals = NULL;
++    struct tm utctime;
++    char timestr[GENERALIZED_TIME_LENGTH+1];
++    char *lm = NULL;
++    char *nt = NULL;
++    int is_smb = 0;
++    int is_ipant = 0;
++    int is_host = 0;
++    Slapi_Value *sambaSamAccount;
++    Slapi_Value *ipaNTUserAttrs;
++    Slapi_Value *ipaHost;
++    char *errMesg = NULL;
++    char *modtime = NULL;
++
++    LOG_TRACE("=>\n");
++
++    sambaSamAccount = slapi_value_new_string("sambaSamAccount");
++    if (slapi_entry_attr_has_syntax_value(data->target,
++                                          "objectClass", sambaSamAccount)) {
++        is_smb = 1;
++    }
++    slapi_value_free(&sambaSamAccount);
++
++    ipaNTUserAttrs = slapi_value_new_string("ipaNTUserAttrs");
++    if (slapi_entry_attr_has_syntax_value(data->target,
++                                          "objectClass", ipaNTUserAttrs)) {
++        is_ipant = 1;
++    }
++    slapi_value_free(&ipaNTUserAttrs);
++
++    ipaHost = slapi_value_new_string("ipaHost");
++    if (slapi_entry_attr_has_syntax_value(data->target,
++                                          "objectClass", ipaHost)) {
++        is_host = 1;
++    }
++    slapi_value_free(&ipaHost);
++
++    ret = ipapwd_gen_hashes(krbcfg, data,
++                            data->password,
++                            is_krb, is_smb, is_ipant,
++                            &svals, &nt, &lm, &ntvals, &errMesg);
++    if (ret) {
++        goto free_and_return;
++    }
++
++    smods = slapi_mods_new();
++
++    if (svals) {
++        slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
++                                  "krbPrincipalKey", svals);
++
++		/* krbLastPwdChange is used to tell whether a host entry has a
++		 * keytab so don't set it on hosts.
++		 */
++        if (!is_host) {
++	        /* change Last Password Change field with the current date */
++			if (!gmtime_r(&(data->timeNow), &utctime)) {
++				LOG_FATAL("failed to retrieve current date (buggy gmtime_r ?)\n");
++				ret = LDAP_OPERATIONS_ERROR;
++				goto free_and_return;
++			}
++			strftime(timestr, GENERALIZED_TIME_LENGTH + 1,
++                 "%Y%m%d%H%M%SZ", &utctime);
++			slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                              "krbLastPwdChange", timestr);
++
++			/* set Password Expiration date */
++			if (!gmtime_r(&(data->expireTime), &utctime)) {
++				LOG_FATAL("failed to convert expiration date\n");
++				ret = LDAP_OPERATIONS_ERROR;
++				goto free_and_return;
++			}
++			strftime(timestr, GENERALIZED_TIME_LENGTH + 1,
++                 "%Y%m%d%H%M%SZ", &utctime);
++			slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                              "krbPasswordExpiration", timestr);
++		}
++	}
++
++    if (lm && is_smb) {
++        slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                              "sambaLMPassword", lm);
++    }
++
++    if (nt && is_smb) {
++        slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                              "sambaNTPassword", nt);
++    }
++
++    if (ntvals && is_ipant) {
++        slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
++                                  "ipaNTHash", ntvals);
++    }
++
++    if (is_smb) {
++        /* with samba integration we need to also set sambaPwdLastSet or
++         * samba will decide the user has to change the password again */
++        if (data->changetype == IPA_CHANGETYPE_ADMIN) {
++            /* if it is an admin change instead we need to let know to
++             * samba as well that the use rmust change its password */
++            modtime = slapi_ch_smprintf("0");
++        } else {
++            modtime = slapi_ch_smprintf("%ld", (long)data->timeNow);
++        }
++        if (!modtime) {
++            LOG_FATAL("failed to smprintf string!\n");
++            ret = LDAP_OPERATIONS_ERROR;
++            goto free_and_return;
++        }
++        slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                              "sambaPwdLastset", modtime);
++    }
++    if (is_krb) {
++        if (data->changetype == IPA_CHANGETYPE_ADMIN) {
++            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                                 "krbLoginFailedCount", "0");
++        }
++    }
++    /* let DS encode the password itself, this allows also other plugins to
++     * intercept it to perform operations like synchronization with Active
++     * Directory domains through the replication plugin */
++    slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                          "userPassword", data->password);
++
++    /* set password history */
++    if (data->policy.history_length > 0) {
++        pwvals = ipapwd_setPasswordHistory(smods, data);
++        if (pwvals) {
++            slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
++                                      "passwordHistory", pwvals);
++        }
++    }
++
++    /* FIXME:
++     * instead of replace we should use a delete/add so that we are
++     * completely sure nobody else modified the entry meanwhile and
++     * fail if that's the case */
++
++    /* commit changes */
++    ret = ipapwd_apply_mods(data->dn, smods);
++
++    LOG_TRACE("<= result: %d\n", ret);
++
++free_and_return:
++    if (lm) slapi_ch_free((void **)&lm);
++    if (nt) slapi_ch_free((void **)&nt);
++    if (modtime) slapi_ch_free((void **)&modtime);
++    slapi_mods_free(&smods);
++    ipapwd_free_slapi_value_array(&svals);
++    ipapwd_free_slapi_value_array(&ntvals);
++    ipapwd_free_slapi_value_array(&pwvals);
++
++    return ret;
++}
++
++
++Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods,
++                                        struct ipapwd_data *data)
++{
++    Slapi_Value **pH = NULL;
++    char **pwd_history = NULL;
++    char **new_pwd_history = NULL;
++    int n = 0;
++    int ret;
++    int i;
++
++    pwd_history = slapi_entry_attr_get_charray(data->target,
++                                               "passwordHistory");
++
++    ret = ipapwd_generate_new_history(data->password, data->timeNow,
++                                      data->policy.history_length,
++                                      pwd_history, &new_pwd_history, &n);
++
++    if (ret && data->policy.history_length) {
++        LOG_FATAL("failed to generate new password history!\n");
++        goto done;
++    }
++
++    pH = (Slapi_Value **)slapi_ch_calloc(n + 1, sizeof(Slapi_Value *));
++    if (!pH) {
++        LOG_OOM();
++        goto done;
++    }
++
++    for (i = 0; i < n; i++) {
++        pH[i] = slapi_value_new_string(new_pwd_history[i]);
++        if (!pH[i]) {
++            ipapwd_free_slapi_value_array(&pH);
++            LOG_OOM();
++            goto done;
++        }
++    }
++
++done:
++    slapi_ch_array_free(pwd_history);
++    for (i = 0; i < n; i++) {
++        free(new_pwd_history[i]);
++    }
++    free(new_pwd_history);
++    return pH;
++}
++
++/* Construct Mods pblock and perform the modify operation
++ * Sets result of operation in SLAPI_PLUGIN_INTOP_RESULT
++ */
++int ipapwd_apply_mods(const char *dn, Slapi_Mods *mods)
++{
++    Slapi_PBlock *pb;
++    int ret;
++
++    LOG_TRACE("=>\n");
++
++    if (!mods || (slapi_mods_get_num_mods(mods) == 0)) {
++        return -1;
++    }
++
++    pb = slapi_pblock_new();
++    slapi_modify_internal_set_pb(pb, dn,
++                                 slapi_mods_get_ldapmods_byref(mods),
++                                 NULL, /* Controls */
++                                 NULL, /* UniqueID */
++                                 ipapwd_plugin_id, /* PluginID */
++                                 0); /* Flags */
++
++    ret = slapi_modify_internal_pb(pb);
++    if (ret) {
++        LOG_TRACE("WARNING: modify error %d on entry '%s'\n", ret, dn);
++    } else {
++
++        slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
++
++        if (ret != LDAP_SUCCESS){
++            LOG_TRACE("WARNING: modify error %d on entry '%s'\n", ret, dn);
++        } else {
++            LOG_TRACE("<= Successful\n");
++        }
++    }
++
++    slapi_pblock_destroy(pb);
++
++    return ret;
++}
++
++int ipapwd_set_extradata(const char *dn,
++                         const char *principal,
++                         time_t unixtime)
++{
++    Slapi_Mods *smods;
++    Slapi_Value *va[2] = { NULL };
++    struct berval bv;
++    char *xdata;
++    int xd_len;
++    int p_len;
++    int ret;
++
++    p_len = strlen(principal);
++    xd_len = 2 + 4 + p_len + 1;
++    xdata = malloc(xd_len);
++    if (!xdata) {
++        return LDAP_OPERATIONS_ERROR;
++    }
++
++    smods = slapi_mods_new();
++
++    /* data type id */
++    xdata[0] = 0x00;
++    xdata[1] = 0x02;
++
++    /* unix timestamp in Little Endian */
++    xdata[2] = unixtime & 0xff;
++    xdata[3] = (unixtime & 0xff00) >> 8;
++    xdata[4] = (unixtime & 0xff0000) >> 16;
++    xdata[5] = (unixtime & 0xff000000) >> 24;
++
++    /* append the principal name */
++    strncpy(&xdata[6], principal, p_len);
++
++    xdata[xd_len -1] = 0;
++
++    bv.bv_val = xdata;
++    bv.bv_len = xd_len;
++    va[0] = slapi_value_new_berval(&bv);
++
++    slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbExtraData", va);
++
++    ret = ipapwd_apply_mods(dn, smods);
++
++    slapi_value_free(&va[0]);
++    slapi_mods_free(&smods);
++
++    return ret;
++}
++
++void ipapwd_free_slapi_value_array(Slapi_Value ***svals)
++{
++    Slapi_Value **sv = *svals;
++    int i;
++
++    if (sv) {
++        for (i = 0; sv[i]; i++) {
++            slapi_value_free(&sv[i]);
++        }
++    }
++
++    slapi_ch_free((void **)sv);
++}
++
++void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg)
++{
++    struct ipapwd_krbcfg *c = *cfg;
++
++    if (!c) return;
++
++    krb5_free_default_realm(c->krbctx, c->realm);
++    krb5_free_context(c->krbctx);
++    free(c->kmkey->contents);
++    free(c->kmkey);
++    free(c->supp_encsalts);
++    free(c->pref_encsalts);
++    slapi_ch_array_free(c->passsync_mgrs);
++    free(c);
++    *cfg = NULL;
++};
++
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c
+new file mode 100644
+index 0000000..a92eaf0
+--- /dev/null
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c
+@@ -0,0 +1,291 @@
++/** BEGIN COPYRIGHT BLOCK
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ *
++ * Additional permission under GPLv3 section 7:
++ *
++ * In the following paragraph, "GPL" means the GNU General Public
++ * License, version 3 or any later version, and "Non-GPL Code" means
++ * code that is governed neither by the GPL nor a license
++ * compatible with the GPL.
++ *
++ * You may link the code of this Program with Non-GPL Code and convey
++ * linked combinations including the two, provided that such Non-GPL
++ * Code only links to the code of this Program through those well
++ * defined interfaces identified in the file named EXCEPTION found in
++ * the source code files (the "Approved Interfaces"). The files of
++ * Non-GPL Code may instantiate templates or use macros or inline
++ * functions from the Approved Interfaces without causing the resulting
++ * work to be covered by the GPL. Only the copyright holders of this
++ * Program may make changes or additions to the list of Approved
++ * Interfaces.
++ *
++ * Authors:
++ * Simo Sorce <ssorce at redhat.com>
++ *
++ * Copyright (C) 2007-2010 Red Hat, Inc.
++ * All rights reserved.
++ * END COPYRIGHT BLOCK **/
++
++#ifdef HAVE_CONFIG_H
++#  include <config.h>
++#endif
++
++#include <stdio.h>
++#include <string.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <fcntl.h>
++#include <unistd.h>
++
++#include <dirsrv/slapi-plugin.h>
++#include <lber.h>
++#include <time.h>
++
++#include <endian.h>
++
++#include "ipapwd.h"
++#include "util.h"
++#include "ipa_krb5.h"
++
++/* krbTicketFlags */
++#define KTF_DISALLOW_POSTDATED        0x00000001
++#define KTF_DISALLOW_FORWARDABLE      0x00000002
++#define KTF_DISALLOW_TGT_BASED        0x00000004
++#define KTF_DISALLOW_RENEWABLE        0x00000008
++#define KTF_DISALLOW_PROXIABLE        0x00000010
++#define KTF_DISALLOW_DUP_SKEY         0x00000020
++#define KTF_DISALLOW_ALL_TIX          0x00000040
++#define KTF_REQUIRES_PRE_AUTH         0x00000080
++#define KTF_REQUIRES_HW_AUTH          0x00000100
++#define KTF_REQUIRES_PWCHANGE         0x00000200
++#define KTF_DISALLOW_SVR              0x00001000
++#define KTF_PWCHANGE_SERVICE          0x00002000
++
++/* ascii hex output of bytes in "in"
++ * out len is 32 (preallocated)
++ * in len is 16 */
++static const char hexchars[] = "0123456789ABCDEF";
++static void hexbuf(char *out, const uint8_t *in)
++{
++    int i;
++
++    for (i = 0; i < 16; i++) {
++        out[i*2] = hexchars[in[i] >> 4];
++        out[i*2+1] = hexchars[in[i] & 0x0f];
++    }
++}
++
++void ipapwd_keyset_free(struct ipapwd_keyset **pkset)
++{
++    struct ipapwd_keyset *kset = *pkset;
++    int i;
++
++    if (!kset) return;
++
++    for (i = 0; i < kset->num_keys; i++) {
++        free(kset->keys[i].key_data_contents[0]);
++        free(kset->keys[i].key_data_contents[1]);
++    }
++    free(kset->keys);
++    free(kset);
++    *pkset = NULL;
++}
++
++static Slapi_Value **encrypt_encode_key(struct ipapwd_krbcfg *krbcfg,
++                                        struct ipapwd_data *data,
++                                        char **errMesg)
++{
++    krb5_context krbctx;
++    char *krbPrincipalName = NULL;
++    int kvno;
++    struct berval *bval = NULL;
++    Slapi_Value **svals = NULL;
++    krb5_principal princ = NULL;
++    krb5_error_code krberr;
++    krb5_data pwd;
++    struct ipapwd_keyset *kset = NULL;
++
++    krbctx = krbcfg->krbctx;
++
++    svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *));
++    if (!svals) {
++        LOG_OOM();
++        return NULL;
++    }
++
++    kvno = ipapwd_get_cur_kvno(data->target);
++
++    krbPrincipalName = slapi_entry_attr_get_charptr(data->target,
++                                                    "krbPrincipalName");
++    if (!krbPrincipalName) {
++        *errMesg = "no krbPrincipalName present in this entry\n";
++        LOG_FATAL("%s", *errMesg);
++        goto enc_error;
++    }
++
++    krberr = krb5_parse_name(krbctx, krbPrincipalName, &princ);
++    if (krberr) {
++        LOG_FATAL("krb5_parse_name failed [%s]\n",
++                  krb5_get_error_message(krbctx, krberr));
++        goto enc_error;
++    }
++
++    pwd.data = (char *)data->password;
++    pwd.length = strlen(data->password);
++
++    kset = malloc(sizeof(struct ipapwd_keyset));
++    if (!kset) {
++        LOG_OOM();
++        goto enc_error;
++    }
++
++    /* this encoding assumes all keys have the same kvno */
++    /* major-vno = 1 and minor-vno = 1 */
++    kset->major_vno = 1;
++    kset->minor_vno = 1;
++    /* increment kvno (will be 1 if this is a new entry) */
++    kvno += 1;
++    kset->mkvno = krbcfg->mkvno;
++
++    krberr = ipa_krb5_generate_key_data(krbctx, princ,
++                                        pwd, kvno, krbcfg->kmkey,
++                                        krbcfg->num_pref_encsalts,
++                                        krbcfg->pref_encsalts,
++                                        &kset->num_keys, &kset->keys);
++    if (krberr != 0) {
++        LOG_FATAL("generating kerberos keys failed [%s]\n",
++                  krb5_get_error_message(krbctx, krberr));
++        goto enc_error;
++    }
++
++    krberr = ber_encode_krb5_key_data(kset->keys, kset->num_keys,
++                                      kset->mkvno, &bval);
++    if (krberr != 0) {
++        LOG_FATAL("encoding krb5_key_data failed\n");
++        goto enc_error;
++    }
++
++    svals[0] = slapi_value_new_berval(bval);
++    if (!svals[0]) {
++        LOG_FATAL("Converting berval to Slapi_Value\n");
++        goto enc_error;
++    }
++
++    ipapwd_keyset_free(&kset);
++    krb5_free_principal(krbctx, princ);
++    slapi_ch_free_string(&krbPrincipalName);
++    ber_bvfree(bval);
++    return svals;
++
++enc_error:
++    *errMesg = "key encryption/encoding failed\n";
++    if (kset) ipapwd_keyset_free(&kset);
++    krb5_free_principal(krbctx, princ);
++    slapi_ch_free_string(&krbPrincipalName);
++    if (bval) ber_bvfree(bval);
++    free(svals);
++    return NULL;
++}
++
++int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg,
++                      struct ipapwd_data *data, char *userpw,
++                      int is_krb, int is_smb, int is_ipant, Slapi_Value ***svals,
++                      char **nthash, char **lmhash, Slapi_Value ***ntvals,
++                      char **errMesg)
++{
++    int rc;
++    char *userpw_uc = NULL;
++
++    *svals = NULL;
++    *nthash = NULL;
++    *lmhash = NULL;
++    *errMesg = NULL;
++
++    if (is_krb) {
++
++        *svals = encrypt_encode_key(krbcfg, data, errMesg);
++
++        if (!*svals) {
++            /* errMesg should have been set in encrypt_encode_key() */
++            LOG_FATAL("key encryption/encoding failed\n");
++            rc = LDAP_OPERATIONS_ERROR;
++            goto done;
++        }
++    }
++
++    if (is_smb || is_ipant) {
++        char lm[33], nt[33];
++        struct ntlm_keys ntlm;
++        int ret;
++
++        userpw_uc = (char *) slapi_utf8StrToUpper((unsigned char *) userpw);
++        if (!userpw_uc) {
++            *errMesg = "Failed to generate upper case password\n";
++            LOG_FATAL("%s", *errMesg);
++            rc = LDAP_OPERATIONS_ERROR;
++            goto done;
++        }
++
++        ret = encode_ntlm_keys(userpw,
++                               userpw_uc,
++                               krbcfg->allow_lm_hash,
++                               krbcfg->allow_nt_hash,
++                               &ntlm);
++        memset(userpw_uc, 0, strlen(userpw_uc));
++        slapi_ch_free_string(&userpw_uc);
++        if (ret) {
++            *errMesg = "Failed to generate NT/LM hashes\n";
++            LOG_FATAL("%s", *errMesg);
++            rc = LDAP_OPERATIONS_ERROR;
++            goto done;
++        }
++        if (krbcfg->allow_lm_hash) {
++            hexbuf(lm, ntlm.lm);
++            lm[32] = '\0';
++            *lmhash = slapi_ch_strdup(lm);
++        }
++        if (krbcfg->allow_nt_hash) {
++            hexbuf(nt, ntlm.nt);
++            nt[32] = '\0';
++            *nthash = slapi_ch_strdup(nt);
++        }
++
++        if (is_ipant) {
++            *ntvals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *));
++            if (!*ntvals) {
++                LOG_OOM();
++                rc = LDAP_OPERATIONS_ERROR;
++                goto done;
++            }
++            (*ntvals)[0] = slapi_value_new();
++            if (slapi_value_set((*ntvals)[0], ntlm.nt, 16) == NULL) {
++                rc = LDAP_OPERATIONS_ERROR;
++                goto done;
++            }
++        }
++    }
++
++    rc = LDAP_SUCCESS;
++
++done:
++
++    /* when error, free possibly allocated output parameters */
++    if (rc) {
++        ipapwd_free_slapi_value_array(svals);
++        ipapwd_free_slapi_value_array(ntvals);
++    }
++
++    return rc;
++}
++
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
+index 3689783..372441d 100644
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
+@@ -96,7 +96,7 @@ struct ipapwd_operation {
+ 
+ #define GENERALIZED_TIME_LENGTH 15
+ 
+-/* from ipapwd_common.c */
++/* from common.c */
+ struct ipapwd_krbcfg {
+     krb5_context krbctx;
+     char *realm;
+@@ -131,7 +131,7 @@ int ipapwd_set_extradata(const char *dn,
+ void ipapwd_free_slapi_value_array(Slapi_Value ***svals);
+ void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg);
+ 
+-/* from ipapwd_encoding.c */
++/* from encoding.c */
+ struct ipapwd_keyset {
+     uint16_t major_vno;
+     uint16_t minor_vno;
+@@ -148,7 +148,7 @@ int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg,
+                       Slapi_Value ***svals, char **nthash, char **lmhash,
+                       Slapi_Value ***ntvals, char **errMesg);
+ 
+-/* from ipapwd_prepost.c */
++/* from prepost.c */
+ int ipapwd_ext_init(void);
+ int ipapwd_pre_init(Slapi_PBlock *pb);
+ int ipapwd_post_init(Slapi_PBlock *pb);
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
+deleted file mode 100644
+index bb1d96a..0000000
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
++++ /dev/null
+@@ -1,1107 +0,0 @@
+-/** BEGIN COPYRIGHT BLOCK
+- * This program is free software; you can redistribute it and/or modify
+- * it under the terms of the GNU General Public License as published by
+- * the Free Software Foundation, either version 3 of the License, or
+- * (at your option) any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+- * GNU General Public License for more details.
+- *
+- * You should have received a copy of the GNU General Public License
+- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+- *
+- * Additional permission under GPLv3 section 7:
+- *
+- * In the following paragraph, "GPL" means the GNU General Public
+- * License, version 3 or any later version, and "Non-GPL Code" means
+- * code that is governed neither by the GPL nor a license
+- * compatible with the GPL.
+- *
+- * You may link the code of this Program with Non-GPL Code and convey
+- * linked combinations including the two, provided that such Non-GPL
+- * Code only links to the code of this Program through those well
+- * defined interfaces identified in the file named EXCEPTION found in
+- * the source code files (the "Approved Interfaces"). The files of
+- * Non-GPL Code may instantiate templates or use macros or inline
+- * functions from the Approved Interfaces without causing the resulting
+- * work to be covered by the GPL. Only the copyright holders of this
+- * Program may make changes or additions to the list of Approved
+- * Interfaces.
+- *
+- * Authors:
+- * Simo Sorce <ssorce at redhat.com>
+- *
+- * Copyright (C) 2007-2010 Red Hat, Inc.
+- * All rights reserved.
+- * END COPYRIGHT BLOCK **/
+-
+-#include "ipapwd.h"
+-#include "util.h"
+-
+-/* Type of connection for this operation;*/
+-#define LDAP_EXTOP_PASSMOD_CONN_SECURE
+-
+-/* Uncomment the following #undef FOR TESTING:
+- * allows non-SSL connections to use the password change extended op */
+-/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */
+-
+-extern void *ipapwd_plugin_id;
+-extern const char *ipa_realm_dn;
+-extern const char *ipa_etc_config_dn;
+-extern const char *ipa_pwd_config_dn;
+-
+-/* These are the default enc:salt types if nothing is defined.
+- * TODO: retrieve the configure set of ecntypes either from the
+- * kfc.conf file or by synchronizing the file content into
+- * the directory */
+-static const char *ipapwd_def_encsalts[] = {
+-    "des3-hmac-sha1:normal",
+-/*    "arcfour-hmac:normal",
+-    "des-hmac-sha1:normal",
+-    "des-cbc-md5:normal", */
+-    "des-cbc-crc:normal",
+-/*    "des-cbc-crc:v4",
+-    "des-cbc-crc:afs3", */
+-    NULL
+-};
+-
+-static struct ipapwd_krbcfg *ipapwd_getConfig(void)
+-{
+-    krb5_error_code krberr;
+-    struct ipapwd_krbcfg *config = NULL;
+-    krb5_keyblock *kmkey = NULL;
+-    Slapi_Entry *realm_entry = NULL;
+-    Slapi_Entry *config_entry = NULL;
+-    Slapi_Attr *a;
+-    Slapi_Value *v;
+-    BerElement *be = NULL;
+-    ber_tag_t tag, tvno;
+-    ber_int_t ttype;
+-    const struct berval *bval;
+-    struct berval *mkey = NULL;
+-    char **encsalts;
+-    char **tmparray;
+-    char *tmpstr;
+-    int i, ret;
+-
+-    config = calloc(1, sizeof(struct ipapwd_krbcfg));
+-    if (!config) {
+-        LOG_OOM();
+-        goto free_and_error;
+-    }
+-    kmkey = calloc(1, sizeof(krb5_keyblock));
+-    if (!kmkey) {
+-        LOG_OOM();
+-        goto free_and_error;
+-    }
+-    config->kmkey = kmkey;
+-
+-    krberr = krb5_init_context(&config->krbctx);
+-    if (krberr) {
+-        LOG_FATAL("krb5_init_context failed\n");
+-        goto free_and_error;
+-    }
+-
+-    ret = krb5_get_default_realm(config->krbctx, &config->realm);
+-    if (ret) {
+-        LOG_FATAL("Failed to get default realm?!\n");
+-        goto free_and_error;
+-    }
+-
+-    /* get the Realm Container entry */
+-    ret = ipapwd_getEntry(ipa_realm_dn, &realm_entry, NULL);
+-    if (ret != LDAP_SUCCESS) {
+-        LOG_FATAL("No realm Entry?\n");
+-        goto free_and_error;
+-    }
+-
+-    /*** get the Kerberos Master Key ***/
+-
+-    ret = slapi_entry_attr_find(realm_entry, "krbMKey", &a);
+-    if (ret == -1) {
+-        LOG_FATAL("No master key??\n");
+-        goto free_and_error;
+-    }
+-
+-    /* there should be only one value here */
+-    ret = slapi_attr_first_value(a, &v);
+-    if (ret == -1) {
+-        LOG_FATAL("No master key??\n");
+-        goto free_and_error;
+-    }
+-
+-    bval = slapi_value_get_berval(v);
+-    if (!bval) {
+-        LOG_FATAL("Error retrieving master key berval\n");
+-        goto free_and_error;
+-    }
+-
+-    be = ber_init(discard_const(bval));
+-    if (!be) {
+-        LOG_FATAL("ber_init() failed!\n");
+-        goto free_and_error;
+-    }
+-
+-    tag = ber_scanf(be, "{i{iO}}", &tvno, &ttype, &mkey);
+-    if (tag == LBER_ERROR) {
+-        LOG_FATAL("Bad Master key encoding ?!\n");
+-        goto free_and_error;
+-    }
+-
+-    config->mkvno = tvno;
+-    kmkey->magic = KV5M_KEYBLOCK;
+-    kmkey->enctype = ttype;
+-    kmkey->length = mkey->bv_len;
+-    kmkey->contents = malloc(mkey->bv_len);
+-    if (!kmkey->contents) {
+-        LOG_OOM();
+-        goto free_and_error;
+-    }
+-    memcpy(kmkey->contents, mkey->bv_val, mkey->bv_len);
+-    ber_bvfree(mkey);
+-    ber_free(be, 1);
+-    mkey = NULL;
+-    be = NULL;
+-
+-    /*** get the Supported Enc/Salt types ***/
+-
+-    encsalts = slapi_entry_attr_get_charray(realm_entry,
+-                                            "krbSupportedEncSaltTypes");
+-    if (encsalts) {
+-        for (i = 0; encsalts[i]; i++) /* count */ ;
+-        ret = parse_bval_key_salt_tuples(config->krbctx,
+-                                         (const char * const *)encsalts, i,
+-                                         &config->supp_encsalts,
+-                                         &config->num_supp_encsalts);
+-        slapi_ch_array_free(encsalts);
+-    } else {
+-        LOG("No configured salt types use defaults\n");
+-        for (i = 0; ipapwd_def_encsalts[i]; i++) /* count */ ;
+-        ret = parse_bval_key_salt_tuples(config->krbctx,
+-                                         ipapwd_def_encsalts, i,
+-                                         &config->supp_encsalts,
+-                                         &config->num_supp_encsalts);
+-    }
+-    if (ret) {
+-        LOG_FATAL("Can't get Supported EncSalt Types\n");
+-        goto free_and_error;
+-    }
+-
+-    /*** get the Preferred Enc/Salt types ***/
+-
+-    encsalts = slapi_entry_attr_get_charray(realm_entry,
+-                                            "krbDefaultEncSaltTypes");
+-    if (encsalts) {
+-        for (i = 0; encsalts[i]; i++) /* count */ ;
+-        ret = parse_bval_key_salt_tuples(config->krbctx,
+-                                         (const char * const *)encsalts, i,
+-                                         &config->pref_encsalts,
+-                                         &config->num_pref_encsalts);
+-        slapi_ch_array_free(encsalts);
+-    } else {
+-        LOG("No configured salt types use defaults\n");
+-        for (i = 0; ipapwd_def_encsalts[i]; i++) /* count */ ;
+-        ret = parse_bval_key_salt_tuples(config->krbctx,
+-                                         ipapwd_def_encsalts, i,
+-                                         &config->pref_encsalts,
+-                                         &config->num_pref_encsalts);
+-    }
+-    if (ret) {
+-        LOG_FATAL("Can't get Preferred EncSalt Types\n");
+-        goto free_and_error;
+-    }
+-
+-    slapi_entry_free(realm_entry);
+-
+-    /* get the Realm Container entry */
+-    ret = ipapwd_getEntry(ipa_pwd_config_dn, &config_entry, NULL);
+-    if (ret != LDAP_SUCCESS) {
+-        LOG_FATAL("No config Entry? Impossible!\n");
+-        goto free_and_error;
+-    }
+-    config->passsync_mgrs =
+-            slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs");
+-    /* now add Directory Manager, it is always added by default */
+-    tmpstr = slapi_ch_strdup("cn=Directory Manager");
+-    slapi_ch_array_add(&config->passsync_mgrs, tmpstr);
+-    if (config->passsync_mgrs == NULL) {
+-        LOG_OOM();
+-        goto free_and_error;
+-    }
+-    for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ;
+-    config->num_passsync_mgrs = i;
+-
+-    slapi_entry_free(config_entry);
+-
+-    /* get the ipa etc/ipaConfig entry */
+-    config->allow_lm_hash = false;
+-    config->allow_nt_hash = false;
+-    ret = ipapwd_getEntry(ipa_etc_config_dn, &config_entry, NULL);
+-    if (ret != LDAP_SUCCESS) {
+-        LOG_FATAL("No config Entry?\n");
+-        goto free_and_error;
+-    } else {
+-        tmparray = slapi_entry_attr_get_charray(config_entry,
+-                                                "ipaConfigString");
+-        for (i = 0; tmparray && tmparray[i]; i++) {
+-            if (strcasecmp(tmparray[i], "AllowLMhash") == 0) {
+-                config->allow_lm_hash = true;
+-                continue;
+-            }
+-            if (strcasecmp(tmparray[i], "AllowNThash") == 0) {
+-                config->allow_nt_hash = true;
+-                continue;
+-            }
+-        }
+-        if (tmparray) slapi_ch_array_free(tmparray);
+-    }
+-
+-    slapi_entry_free(config_entry);
+-
+-    return config;
+-
+-free_and_error:
+-    if (mkey) ber_bvfree(mkey);
+-    if (be) ber_free(be, 1);
+-    if (kmkey) {
+-        free(kmkey->contents);
+-        free(kmkey);
+-    }
+-    if (config) {
+-        if (config->krbctx) {
+-            if (config->realm)
+-                krb5_free_default_realm(config->krbctx, config->realm);
+-            krb5_free_context(config->krbctx);
+-        }
+-        free(config->pref_encsalts);
+-        free(config->supp_encsalts);
+-        slapi_ch_array_free(config->passsync_mgrs);
+-        free(config);
+-    }
+-    slapi_entry_free(config_entry);
+-    slapi_entry_free(realm_entry);
+-    return NULL;
+-}
+-
+-/* Easier handling for virtual attributes. You must call pwd_values_free()
+- * to free memory allocated here. It must be called before
+- * slapi_free_search_results_internal(entries) or
+- * slapi_pblock_destroy(pb)
+- */
+-static int pwd_get_values(const Slapi_Entry *ent, const char *attrname,
+-			  Slapi_ValueSet** results, char** actual_type_name,
+-			  int *buffer_flags)
+-{
+-    int flags=0;
+-    int type_name_disposition = 0;
+-    int ret;
+-
+-    ret = slapi_vattr_values_get((Slapi_Entry *)ent, (char *)attrname,
+-                                 results, &type_name_disposition,
+-                                 actual_type_name, flags, buffer_flags);
+-
+-    return ret;
+-}
+-
+-static void pwd_values_free(Slapi_ValueSet** results,
+-                            char** actual_type_name, int buffer_flags)
+-{
+-    slapi_vattr_values_free(results, actual_type_name, buffer_flags);
+-}
+-
+-static int ipapwd_rdn_count(const char *dn)
+-{
+-    int rdnc = 0;
+-    LDAPDN ldn;
+-    int ret;
+-
+-    ret = ldap_str2dn(dn, &ldn, LDAP_DN_FORMAT_LDAPV3);
+-    if (ret != LDAP_SUCCESS) {
+-        LOG_TRACE("ldap_str2dn(dn) failed ?!");
+-        return -1;
+-    }
+-
+-    for (rdnc = 0; ldn != NULL && ldn[rdnc]; rdnc++) /* count */ ;
+-    ldap_dnfree(ldn);
+-
+-    return rdnc;
+-}
+-
+-int ipapwd_getPolicy(const char *dn,
+-                     Slapi_Entry *target,
+-                     struct ipapwd_policy *policy)
+-{
+-    const char *krbPwdPolicyReference;
+-    const char *pdn;
+-    const Slapi_DN *psdn;
+-    Slapi_Backend *be;
+-    Slapi_PBlock *pb = NULL;
+-    char *attrs[] = { "krbMaxPwdLife", "krbMinPwdLife",
+-                      "krbPwdMinDiffChars", "krbPwdMinLength",
+-                      "krbPwdHistoryLength", NULL};
+-    Slapi_Entry **es = NULL;
+-    Slapi_Entry *pe = NULL;
+-    int ret, res, dist, rdnc, scope, i;
+-    Slapi_DN *sdn = NULL;
+-    int buffer_flags=0;
+-    Slapi_ValueSet* results = NULL;
+-    char* actual_type_name = NULL;
+-    int tmpint;
+-
+-    LOG_TRACE("Searching policy for [%s]\n", dn);
+-
+-    sdn = slapi_sdn_new_dn_byref(dn);
+-    if (sdn == NULL) {
+-        LOG_OOM();
+-        ret = -1;
+-        goto done;
+-    }
+-
+-    pwd_get_values(target, "krbPwdPolicyReference",
+-                   &results, &actual_type_name, &buffer_flags);
+-    if (results) {
+-        Slapi_Value *sv;
+-        slapi_valueset_first_value(results, &sv);
+-        krbPwdPolicyReference = slapi_value_get_string(sv);
+-        pdn = krbPwdPolicyReference;
+-        scope = LDAP_SCOPE_BASE;
+-        LOG_TRACE("using policy reference: %s\n", pdn);
+-    } else {
+-        /* Find ancestor base DN */
+-        be = slapi_be_select(sdn);
+-        psdn = slapi_be_getsuffix(be, 0);
+-        if (psdn == NULL) {
+-            LOG_FATAL("Invalid DN [%s]\n", dn);
+-            ret = -1;
+-            goto done;
+-        }
+-        pdn = slapi_sdn_get_dn(psdn);
+-        scope = LDAP_SCOPE_SUBTREE;
+-    }
+-
+-    pb = slapi_pblock_new();
+-    slapi_search_internal_set_pb(pb,
+-                                 pdn, scope,
+-                                 "(objectClass=krbPwdPolicy)",
+-                                 attrs, 0,
+-                                 NULL, /* Controls */
+-                                 NULL, /* UniqueID */
+-                                 ipapwd_plugin_id,
+-                                 0); /* Flags */
+-
+-    /* do search the tree */
+-    ret = slapi_search_internal_pb(pb);
+-    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+-    if (ret == -1 || res != LDAP_SUCCESS) {
+-        LOG_FATAL("Couldn't find policy, err (%d)\n", res ? res : ret);
+-        ret = -1;
+-        goto done;
+-    }
+-
+-    /* get entries */
+-    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es);
+-    if (!es) {
+-        LOG_TRACE("No entries ?!");
+-        ret = -1;
+-        goto done;
+-    }
+-
+-    /* count entries */
+-    for (i = 0; es[i]; i++) /* count */ ;
+-
+-    /* if there is only one, return that */
+-    if (i == 1) {
+-        pe = es[0];
+-        goto fill;
+-    }
+-
+-    /* count number of RDNs in DN */
+-    rdnc = ipapwd_rdn_count(dn);
+-    if (rdnc == -1) {
+-        LOG_TRACE("ipapwd_rdn_count(dn) failed");
+-        ret = -1;
+-        goto done;
+-    }
+-
+-    pe = NULL;
+-    dist = -1;
+-
+-    /* find closest entry */
+-    for (i = 0; es[i]; i++) {
+-        const Slapi_DN *esdn;
+-
+-        esdn = slapi_entry_get_sdn_const(es[i]);
+-        if (esdn == NULL) continue;
+-        if (0 == slapi_sdn_compare(esdn, sdn)) {
+-            pe = es[i];
+-            dist = 0;
+-            break;
+-        }
+-        if (slapi_sdn_issuffix(sdn, esdn)) {
+-            const char *dn1;
+-            int c1;
+-
+-            dn1 = slapi_sdn_get_dn(esdn);
+-            if (!dn1) continue;
+-            c1 = ipapwd_rdn_count(dn1);
+-            if (c1 == -1) continue;
+-            if ((dist == -1) ||
+-                ((rdnc - c1) < dist)) {
+-                dist = rdnc - c1;
+-                pe = es[i];
+-            }
+-        }
+-        if (dist == 0) break; /* found closest */
+-    }
+-
+-    if (pe == NULL) {
+-        ret = -1;
+-        goto done;
+-    }
+-
+-fill:
+-    policy->min_pwd_life = slapi_entry_attr_get_int(pe, "krbMinPwdLife");
+-
+-    tmpint = slapi_entry_attr_get_int(pe, "krbMaxPwdLife");
+-    if (tmpint != 0) {
+-        policy->max_pwd_life = tmpint;
+-    }
+-
+-    tmpint = slapi_entry_attr_get_int(pe, "krbPwdMinLength");
+-    if (tmpint != 0) {
+-        policy->min_pwd_length = tmpint;
+-    }
+-
+-    policy->history_length = slapi_entry_attr_get_int(pe,
+-                                                      "krbPwdHistoryLength");
+-
+-    policy->min_complexity = slapi_entry_attr_get_int(pe,
+-                                                      "krbPwdMinDiffChars");
+-
+-    ret = 0;
+-
+-done:
+-    if (results) {
+-        pwd_values_free(&results, &actual_type_name, buffer_flags);
+-    }
+-    if (pb) {
+-        slapi_free_search_results_internal(pb);
+-        slapi_pblock_destroy(pb);
+-    }
+-    if (sdn) slapi_sdn_free(&sdn);
+-    return ret;
+-}
+-
+-
+-/*==Common-public-functions=============================================*/
+-
+-int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
+-                        int *is_root, int *is_krb, int *is_smb, int *is_ipant,
+-                        char *attr, int acc)
+-{
+-    Slapi_Value *sval;
+-    int rc;
+-
+-    /* Check ACIs */
+-    slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, is_root);
+-
+-    if (!*is_root) {
+-        /* verify this user is allowed to write a user password */
+-        rc = slapi_access_allowed(pb, e, attr, NULL, acc);
+-        if (rc != LDAP_SUCCESS) {
+-            /* we have no business here, the operation will be denied anyway */
+-            rc = LDAP_SUCCESS;
+-            goto done;
+-        }
+-    }
+-
+-    /* Check if this is a krbPrincial and therefore needs us to generate other
+-     * hashes */
+-    sval = slapi_value_new_string("krbPrincipalAux");
+-    if (!sval) {
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-    *is_krb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval);
+-    slapi_value_free(&sval);
+-
+-    sval = slapi_value_new_string("sambaSamAccount");
+-    if (!sval) {
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-    *is_smb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval);
+-    slapi_value_free(&sval);
+-
+-    sval = slapi_value_new_string("ipaNTUserAttrs");
+-    if (!sval) {
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-    *is_ipant = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS,
+-                                                  sval);
+-    slapi_value_free(&sval);
+-
+-    rc = LDAP_SUCCESS;
+-
+-done:
+-    return rc;
+-}
+-
+-int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg,
+-                      struct ipapwd_krbcfg **config, int check_flags)
+-{
+-    int ret, ssf;
+-    int rc = LDAP_SUCCESS;
+-    Slapi_Backend *be;
+-    const Slapi_DN *psdn;
+-    Slapi_DN *sdn;
+-    char *dn = NULL;
+-
+-    LOG_TRACE("=>\n");
+-
+-#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE
+-    if (check_flags & IPAPWD_CHECK_CONN_SECURE) {
+-       /* Allow password modify on all connections with a Security Strength
+-        * Factor (SSF) higher than 1 */
+-        if (slapi_pblock_get(pb, SLAPI_OPERATION_SSF, &ssf) != 0) {
+-            LOG("Could not get SSF from connection\n");
+-            *errMesg = "Operation requires a secure connection.\n";
+-            rc = LDAP_OPERATIONS_ERROR;
+-            goto done;
+-        }
+-
+-        if (ssf <= 1) {
+-            *errMesg = "Operation requires a secure connection.\n";
+-            rc = LDAP_CONFIDENTIALITY_REQUIRED;
+-            goto done;
+-        }
+-    }
+-#endif
+-
+-    if (check_flags & IPAPWD_CHECK_DN) {
+-        /* check we have a valid DN in the pblock or just abort */
+-        ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+-        if (ret) {
+-            LOG("Tried to change password for an invalid DN [%s]\n",
+-                dn ? dn : "<NULL>");
+-            *errMesg = "Invalid DN";
+-            rc = LDAP_OPERATIONS_ERROR;
+-            goto done;
+-        }
+-        sdn = slapi_sdn_new_dn_byref(dn);
+-        if (!sdn) {
+-            LOG_FATAL("Unable to convert dn to sdn %s", dn ? dn : "<NULL>");
+-            *errMesg = "Internal Error";
+-            rc = LDAP_OPERATIONS_ERROR;
+-            goto done;
+-        }
+-        be = slapi_be_select(sdn);
+-        slapi_sdn_free(&sdn);
+-
+-        psdn = slapi_be_getsuffix(be, 0);
+-        if (!psdn) {
+-            *errMesg = "Invalid DN";
+-            rc = LDAP_OPERATIONS_ERROR;
+-            goto done;
+-        }
+-    }
+-
+-    /* get the kerberos context and master key */
+-    *config = ipapwd_getConfig();
+-    if (NULL == *config) {
+-        LOG_FATAL("Error Retrieving Master Key");
+-        *errMesg = "Fatal Internal Error";
+-        rc = LDAP_OPERATIONS_ERROR;
+-    }
+-
+-done:
+-    return rc;
+-}
+-
+-/* check password strenght and history */
+-int ipapwd_CheckPolicy(struct ipapwd_data *data)
+-{
+-    struct ipapwd_policy pol = {0};
+-    time_t acct_expiration;
+-    time_t pwd_expiration;
+-    time_t last_pwd_change;
+-    char **pwd_history;
+-    char *tmpstr;
+-    int ret;
+-
+-    pol.max_pwd_life = IPAPWD_DEFAULT_PWDLIFE;
+-    pol.min_pwd_length = IPAPWD_DEFAULT_MINLEN;
+-
+-    if (data->changetype != IPA_CHANGETYPE_NORMAL) {
+-        /* We must skip policy checks (Admin change) but
+-         * force a password change on the next login.
+-         * But not if Directory Manager */
+-        if (data->changetype == IPA_CHANGETYPE_ADMIN) {
+-            /* The expiration date needs to be older than the current time
+-             * otherwise the KDC may not immediately register the password
+-             * as expired. The last password change needs to match the
+-             * password expiration otherwise minlife issues will arise.
+-             */
+-            data->timeNow -= 1;
+-            data->expireTime = data->timeNow;
+-        }
+-
+-        /* do not load policies */
+-    } else {
+-
+-        /* find the entry with the password policy */
+-        ret = ipapwd_getPolicy(data->dn, data->target, &pol);
+-        if (ret) {
+-            LOG_TRACE("No password policy, use defaults");
+-        }
+-    }
+-
+-    tmpstr = slapi_entry_attr_get_charptr(data->target,
+-                                          "krbPrincipalExpiration");
+-    acct_expiration = ipapwd_gentime_to_time_t(tmpstr);
+-    slapi_ch_free_string(&tmpstr);
+-
+-    tmpstr = slapi_entry_attr_get_charptr(data->target,
+-                                          "krbPasswordExpiration");
+-    pwd_expiration = ipapwd_gentime_to_time_t(tmpstr);
+-    slapi_ch_free_string(&tmpstr);
+-
+-    tmpstr = slapi_entry_attr_get_charptr(data->target,
+-                                          "krbLastPwdChange");
+-    last_pwd_change = ipapwd_gentime_to_time_t(tmpstr);
+-    slapi_ch_free_string(&tmpstr);
+-
+-    pwd_history = slapi_entry_attr_get_charray(data->target,
+-                                               "passwordHistory");
+-
+-    /* check policy */
+-    ret = ipapwd_check_policy(&pol, data->password,
+-                                    data->timeNow,
+-                                    acct_expiration,
+-                                    pwd_expiration,
+-                                    last_pwd_change,
+-                                    pwd_history);
+-
+-    slapi_ch_array_free(pwd_history);
+-
+-    if (data->expireTime == 0) {
+-        data->expireTime = data->timeNow + pol.max_pwd_life;
+-    }
+-
+-    data->policy = pol;
+-
+-    return ret;
+-}
+-
+-/* Searches the dn in directory,
+- *  If found	 : fills in slapi_entry structure and returns 0
+- *  If NOT found : returns the search result as LDAP_NO_SUCH_OBJECT
+- */
+-int ipapwd_getEntry(const char *dn, Slapi_Entry **e2, char **attrlist)
+-{
+-    Slapi_DN *sdn;
+-    int search_result = 0;
+-
+-    LOG_TRACE("=>\n");
+-
+-    sdn = slapi_sdn_new_dn_byref(dn);
+-    search_result = slapi_search_internal_get_entry(sdn, attrlist, e2,
+-                                                    ipapwd_plugin_id);
+-    if (search_result != LDAP_SUCCESS) {
+-        LOG_TRACE("No such entry-(%s), err (%d)\n", dn, search_result);
+-    }
+-
+-    slapi_sdn_free(&sdn);
+-    LOG_TRACE("<= result: %d\n", search_result);
+-    return search_result;
+-}
+-
+-int ipapwd_get_cur_kvno(Slapi_Entry *target)
+-{
+-    Slapi_Attr *krbPrincipalKey = NULL;
+-    Slapi_ValueSet *svs;
+-    Slapi_Value *sv;
+-    BerElement *be = NULL;
+-    const struct berval *cbval;
+-    ber_tag_t tag, tmp;
+-    ber_int_t tkvno;
+-    int hint;
+-    int kvno;
+-    int ret;
+-
+-    /* retrieve current kvno and and keys */
+-    ret = slapi_entry_attr_find(target, "krbPrincipalKey", &krbPrincipalKey);
+-    if (ret != 0) {
+-        return 0;
+-    }
+-
+-    kvno = 0;
+-
+-    slapi_attr_get_valueset(krbPrincipalKey, &svs);
+-    hint = slapi_valueset_first_value(svs, &sv);
+-    while (hint != -1) {
+-        cbval = slapi_value_get_berval(sv);
+-        if (!cbval) {
+-            LOG_TRACE("Error retrieving berval from Slapi_Value\n");
+-            goto next;
+-        }
+-        be = ber_init(discard_const(cbval));
+-        if (!be) {
+-            LOG_TRACE("ber_init() failed!\n");
+-            goto next;
+-        }
+-
+-        tag = ber_scanf(be, "{xxt[i]", &tmp, &tkvno);
+-        if (tag == LBER_ERROR) {
+-            LOG_TRACE("Bad OLD key encoding ?!\n");
+-            ber_free(be, 1);
+-            goto next;
+-        }
+-
+-        if (tkvno > kvno) {
+-            kvno = tkvno;
+-        }
+-
+-        ber_free(be, 1);
+-next:
+-        hint = slapi_valueset_next_value(svs, hint, &sv);
+-    }
+-
+-    return kvno;
+-}
+-
+-/* Modify the Password attributes of the entry */
+-int ipapwd_SetPassword(struct ipapwd_krbcfg *krbcfg,
+-                       struct ipapwd_data *data, int is_krb)
+-{
+-    int ret = 0;
+-    Slapi_Mods *smods = NULL;
+-    Slapi_Value **svals = NULL;
+-    Slapi_Value **ntvals = NULL;
+-    Slapi_Value **pwvals = NULL;
+-    struct tm utctime;
+-    char timestr[GENERALIZED_TIME_LENGTH+1];
+-    char *lm = NULL;
+-    char *nt = NULL;
+-    int is_smb = 0;
+-    int is_ipant = 0;
+-    int is_host = 0;
+-    Slapi_Value *sambaSamAccount;
+-    Slapi_Value *ipaNTUserAttrs;
+-    Slapi_Value *ipaHost;
+-    char *errMesg = NULL;
+-    char *modtime = NULL;
+-
+-    LOG_TRACE("=>\n");
+-
+-    sambaSamAccount = slapi_value_new_string("sambaSamAccount");
+-    if (slapi_entry_attr_has_syntax_value(data->target,
+-                                          "objectClass", sambaSamAccount)) {
+-        is_smb = 1;
+-    }
+-    slapi_value_free(&sambaSamAccount);
+-
+-    ipaNTUserAttrs = slapi_value_new_string("ipaNTUserAttrs");
+-    if (slapi_entry_attr_has_syntax_value(data->target,
+-                                          "objectClass", ipaNTUserAttrs)) {
+-        is_ipant = 1;
+-    }
+-    slapi_value_free(&ipaNTUserAttrs);
+-
+-    ipaHost = slapi_value_new_string("ipaHost");
+-    if (slapi_entry_attr_has_syntax_value(data->target,
+-                                          "objectClass", ipaHost)) {
+-        is_host = 1;
+-    }
+-    slapi_value_free(&ipaHost);
+-
+-    ret = ipapwd_gen_hashes(krbcfg, data,
+-                            data->password,
+-                            is_krb, is_smb, is_ipant,
+-                            &svals, &nt, &lm, &ntvals, &errMesg);
+-    if (ret) {
+-        goto free_and_return;
+-    }
+-
+-    smods = slapi_mods_new();
+-
+-    if (svals) {
+-        slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+-                                  "krbPrincipalKey", svals);
+-
+-		/* krbLastPwdChange is used to tell whether a host entry has a
+-		 * keytab so don't set it on hosts.
+-		 */
+-        if (!is_host) {
+-	        /* change Last Password Change field with the current date */
+-			if (!gmtime_r(&(data->timeNow), &utctime)) {
+-				LOG_FATAL("failed to retrieve current date (buggy gmtime_r ?)\n");
+-				ret = LDAP_OPERATIONS_ERROR;
+-				goto free_and_return;
+-			}
+-			strftime(timestr, GENERALIZED_TIME_LENGTH + 1,
+-                 "%Y%m%d%H%M%SZ", &utctime);
+-			slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                              "krbLastPwdChange", timestr);
+-
+-			/* set Password Expiration date */
+-			if (!gmtime_r(&(data->expireTime), &utctime)) {
+-				LOG_FATAL("failed to convert expiration date\n");
+-				ret = LDAP_OPERATIONS_ERROR;
+-				goto free_and_return;
+-			}
+-			strftime(timestr, GENERALIZED_TIME_LENGTH + 1,
+-                 "%Y%m%d%H%M%SZ", &utctime);
+-			slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                              "krbPasswordExpiration", timestr);
+-		}
+-	}
+-
+-    if (lm && is_smb) {
+-        slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                              "sambaLMPassword", lm);
+-    }
+-
+-    if (nt && is_smb) {
+-        slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                              "sambaNTPassword", nt);
+-    }
+-
+-    if (ntvals && is_ipant) {
+-        slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+-                                  "ipaNTHash", ntvals);
+-    }
+-
+-    if (is_smb) {
+-        /* with samba integration we need to also set sambaPwdLastSet or
+-         * samba will decide the user has to change the password again */
+-        if (data->changetype == IPA_CHANGETYPE_ADMIN) {
+-            /* if it is an admin change instead we need to let know to
+-             * samba as well that the use rmust change its password */
+-            modtime = slapi_ch_smprintf("0");
+-        } else {
+-            modtime = slapi_ch_smprintf("%ld", (long)data->timeNow);
+-        }
+-        if (!modtime) {
+-            LOG_FATAL("failed to smprintf string!\n");
+-            ret = LDAP_OPERATIONS_ERROR;
+-            goto free_and_return;
+-        }
+-        slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                              "sambaPwdLastset", modtime);
+-    }
+-    if (is_krb) {
+-        if (data->changetype == IPA_CHANGETYPE_ADMIN) {
+-            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                                 "krbLoginFailedCount", "0");
+-        }
+-    }
+-    /* let DS encode the password itself, this allows also other plugins to
+-     * intercept it to perform operations like synchronization with Active
+-     * Directory domains through the replication plugin */
+-    slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                          "userPassword", data->password);
+-
+-    /* set password history */
+-    if (data->policy.history_length > 0) {
+-        pwvals = ipapwd_setPasswordHistory(smods, data);
+-        if (pwvals) {
+-            slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+-                                      "passwordHistory", pwvals);
+-        }
+-    }
+-
+-    /* FIXME:
+-     * instead of replace we should use a delete/add so that we are
+-     * completely sure nobody else modified the entry meanwhile and
+-     * fail if that's the case */
+-
+-    /* commit changes */
+-    ret = ipapwd_apply_mods(data->dn, smods);
+-
+-    LOG_TRACE("<= result: %d\n", ret);
+-
+-free_and_return:
+-    if (lm) slapi_ch_free((void **)&lm);
+-    if (nt) slapi_ch_free((void **)&nt);
+-    if (modtime) slapi_ch_free((void **)&modtime);
+-    slapi_mods_free(&smods);
+-    ipapwd_free_slapi_value_array(&svals);
+-    ipapwd_free_slapi_value_array(&ntvals);
+-    ipapwd_free_slapi_value_array(&pwvals);
+-
+-    return ret;
+-}
+-
+-
+-Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods,
+-                                        struct ipapwd_data *data)
+-{
+-    Slapi_Value **pH = NULL;
+-    char **pwd_history = NULL;
+-    char **new_pwd_history = NULL;
+-    int n = 0;
+-    int ret;
+-    int i;
+-
+-    pwd_history = slapi_entry_attr_get_charray(data->target,
+-                                               "passwordHistory");
+-
+-    ret = ipapwd_generate_new_history(data->password, data->timeNow,
+-                                      data->policy.history_length,
+-                                      pwd_history, &new_pwd_history, &n);
+-
+-    if (ret && data->policy.history_length) {
+-        LOG_FATAL("failed to generate new password history!\n");
+-        goto done;
+-    }
+-
+-    pH = (Slapi_Value **)slapi_ch_calloc(n + 1, sizeof(Slapi_Value *));
+-    if (!pH) {
+-        LOG_OOM();
+-        goto done;
+-    }
+-
+-    for (i = 0; i < n; i++) {
+-        pH[i] = slapi_value_new_string(new_pwd_history[i]);
+-        if (!pH[i]) {
+-            ipapwd_free_slapi_value_array(&pH);
+-            LOG_OOM();
+-            goto done;
+-        }
+-    }
+-
+-done:
+-    slapi_ch_array_free(pwd_history);
+-    for (i = 0; i < n; i++) {
+-        free(new_pwd_history[i]);
+-    }
+-    free(new_pwd_history);
+-    return pH;
+-}
+-
+-/* Construct Mods pblock and perform the modify operation
+- * Sets result of operation in SLAPI_PLUGIN_INTOP_RESULT
+- */
+-int ipapwd_apply_mods(const char *dn, Slapi_Mods *mods)
+-{
+-    Slapi_PBlock *pb;
+-    int ret;
+-
+-    LOG_TRACE("=>\n");
+-
+-    if (!mods || (slapi_mods_get_num_mods(mods) == 0)) {
+-        return -1;
+-    }
+-
+-    pb = slapi_pblock_new();
+-    slapi_modify_internal_set_pb(pb, dn,
+-                                 slapi_mods_get_ldapmods_byref(mods),
+-                                 NULL, /* Controls */
+-                                 NULL, /* UniqueID */
+-                                 ipapwd_plugin_id, /* PluginID */
+-                                 0); /* Flags */
+-
+-    ret = slapi_modify_internal_pb(pb);
+-    if (ret) {
+-        LOG_TRACE("WARNING: modify error %d on entry '%s'\n", ret, dn);
+-    } else {
+-
+-        slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+-
+-        if (ret != LDAP_SUCCESS){
+-            LOG_TRACE("WARNING: modify error %d on entry '%s'\n", ret, dn);
+-        } else {
+-            LOG_TRACE("<= Successful\n");
+-        }
+-    }
+-
+-    slapi_pblock_destroy(pb);
+-
+-    return ret;
+-}
+-
+-int ipapwd_set_extradata(const char *dn,
+-                         const char *principal,
+-                         time_t unixtime)
+-{
+-    Slapi_Mods *smods;
+-    Slapi_Value *va[2] = { NULL };
+-    struct berval bv;
+-    char *xdata;
+-    int xd_len;
+-    int p_len;
+-    int ret;
+-
+-    p_len = strlen(principal);
+-    xd_len = 2 + 4 + p_len + 1;
+-    xdata = malloc(xd_len);
+-    if (!xdata) {
+-        return LDAP_OPERATIONS_ERROR;
+-    }
+-
+-    smods = slapi_mods_new();
+-
+-    /* data type id */
+-    xdata[0] = 0x00;
+-    xdata[1] = 0x02;
+-
+-    /* unix timestamp in Little Endian */
+-    xdata[2] = unixtime & 0xff;
+-    xdata[3] = (unixtime & 0xff00) >> 8;
+-    xdata[4] = (unixtime & 0xff0000) >> 16;
+-    xdata[5] = (unixtime & 0xff000000) >> 24;
+-
+-    /* append the principal name */
+-    strncpy(&xdata[6], principal, p_len);
+-
+-    xdata[xd_len -1] = 0;
+-
+-    bv.bv_val = xdata;
+-    bv.bv_len = xd_len;
+-    va[0] = slapi_value_new_berval(&bv);
+-
+-    slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbExtraData", va);
+-
+-    ret = ipapwd_apply_mods(dn, smods);
+-
+-    slapi_value_free(&va[0]);
+-    slapi_mods_free(&smods);
+-
+-    return ret;
+-}
+-
+-void ipapwd_free_slapi_value_array(Slapi_Value ***svals)
+-{
+-    Slapi_Value **sv = *svals;
+-    int i;
+-
+-    if (sv) {
+-        for (i = 0; sv[i]; i++) {
+-            slapi_value_free(&sv[i]);
+-        }
+-    }
+-
+-    slapi_ch_free((void **)sv);
+-}
+-
+-void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg)
+-{
+-    struct ipapwd_krbcfg *c = *cfg;
+-
+-    if (!c) return;
+-
+-    krb5_free_default_realm(c->krbctx, c->realm);
+-    krb5_free_context(c->krbctx);
+-    free(c->kmkey->contents);
+-    free(c->kmkey);
+-    free(c->supp_encsalts);
+-    free(c->pref_encsalts);
+-    slapi_ch_array_free(c->passsync_mgrs);
+-    free(c);
+-    *cfg = NULL;
+-};
+-
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c
+deleted file mode 100644
+index a92eaf0..0000000
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c
++++ /dev/null
+@@ -1,291 +0,0 @@
+-/** BEGIN COPYRIGHT BLOCK
+- * This program is free software; you can redistribute it and/or modify
+- * it under the terms of the GNU General Public License as published by
+- * the Free Software Foundation, either version 3 of the License, or
+- * (at your option) any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+- * GNU General Public License for more details.
+- *
+- * You should have received a copy of the GNU General Public License
+- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+- *
+- * Additional permission under GPLv3 section 7:
+- *
+- * In the following paragraph, "GPL" means the GNU General Public
+- * License, version 3 or any later version, and "Non-GPL Code" means
+- * code that is governed neither by the GPL nor a license
+- * compatible with the GPL.
+- *
+- * You may link the code of this Program with Non-GPL Code and convey
+- * linked combinations including the two, provided that such Non-GPL
+- * Code only links to the code of this Program through those well
+- * defined interfaces identified in the file named EXCEPTION found in
+- * the source code files (the "Approved Interfaces"). The files of
+- * Non-GPL Code may instantiate templates or use macros or inline
+- * functions from the Approved Interfaces without causing the resulting
+- * work to be covered by the GPL. Only the copyright holders of this
+- * Program may make changes or additions to the list of Approved
+- * Interfaces.
+- *
+- * Authors:
+- * Simo Sorce <ssorce at redhat.com>
+- *
+- * Copyright (C) 2007-2010 Red Hat, Inc.
+- * All rights reserved.
+- * END COPYRIGHT BLOCK **/
+-
+-#ifdef HAVE_CONFIG_H
+-#  include <config.h>
+-#endif
+-
+-#include <stdio.h>
+-#include <string.h>
+-#include <sys/types.h>
+-#include <sys/stat.h>
+-#include <fcntl.h>
+-#include <unistd.h>
+-
+-#include <dirsrv/slapi-plugin.h>
+-#include <lber.h>
+-#include <time.h>
+-
+-#include <endian.h>
+-
+-#include "ipapwd.h"
+-#include "util.h"
+-#include "ipa_krb5.h"
+-
+-/* krbTicketFlags */
+-#define KTF_DISALLOW_POSTDATED        0x00000001
+-#define KTF_DISALLOW_FORWARDABLE      0x00000002
+-#define KTF_DISALLOW_TGT_BASED        0x00000004
+-#define KTF_DISALLOW_RENEWABLE        0x00000008
+-#define KTF_DISALLOW_PROXIABLE        0x00000010
+-#define KTF_DISALLOW_DUP_SKEY         0x00000020
+-#define KTF_DISALLOW_ALL_TIX          0x00000040
+-#define KTF_REQUIRES_PRE_AUTH         0x00000080
+-#define KTF_REQUIRES_HW_AUTH          0x00000100
+-#define KTF_REQUIRES_PWCHANGE         0x00000200
+-#define KTF_DISALLOW_SVR              0x00001000
+-#define KTF_PWCHANGE_SERVICE          0x00002000
+-
+-/* ascii hex output of bytes in "in"
+- * out len is 32 (preallocated)
+- * in len is 16 */
+-static const char hexchars[] = "0123456789ABCDEF";
+-static void hexbuf(char *out, const uint8_t *in)
+-{
+-    int i;
+-
+-    for (i = 0; i < 16; i++) {
+-        out[i*2] = hexchars[in[i] >> 4];
+-        out[i*2+1] = hexchars[in[i] & 0x0f];
+-    }
+-}
+-
+-void ipapwd_keyset_free(struct ipapwd_keyset **pkset)
+-{
+-    struct ipapwd_keyset *kset = *pkset;
+-    int i;
+-
+-    if (!kset) return;
+-
+-    for (i = 0; i < kset->num_keys; i++) {
+-        free(kset->keys[i].key_data_contents[0]);
+-        free(kset->keys[i].key_data_contents[1]);
+-    }
+-    free(kset->keys);
+-    free(kset);
+-    *pkset = NULL;
+-}
+-
+-static Slapi_Value **encrypt_encode_key(struct ipapwd_krbcfg *krbcfg,
+-                                        struct ipapwd_data *data,
+-                                        char **errMesg)
+-{
+-    krb5_context krbctx;
+-    char *krbPrincipalName = NULL;
+-    int kvno;
+-    struct berval *bval = NULL;
+-    Slapi_Value **svals = NULL;
+-    krb5_principal princ = NULL;
+-    krb5_error_code krberr;
+-    krb5_data pwd;
+-    struct ipapwd_keyset *kset = NULL;
+-
+-    krbctx = krbcfg->krbctx;
+-
+-    svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *));
+-    if (!svals) {
+-        LOG_OOM();
+-        return NULL;
+-    }
+-
+-    kvno = ipapwd_get_cur_kvno(data->target);
+-
+-    krbPrincipalName = slapi_entry_attr_get_charptr(data->target,
+-                                                    "krbPrincipalName");
+-    if (!krbPrincipalName) {
+-        *errMesg = "no krbPrincipalName present in this entry\n";
+-        LOG_FATAL("%s", *errMesg);
+-        goto enc_error;
+-    }
+-
+-    krberr = krb5_parse_name(krbctx, krbPrincipalName, &princ);
+-    if (krberr) {
+-        LOG_FATAL("krb5_parse_name failed [%s]\n",
+-                  krb5_get_error_message(krbctx, krberr));
+-        goto enc_error;
+-    }
+-
+-    pwd.data = (char *)data->password;
+-    pwd.length = strlen(data->password);
+-
+-    kset = malloc(sizeof(struct ipapwd_keyset));
+-    if (!kset) {
+-        LOG_OOM();
+-        goto enc_error;
+-    }
+-
+-    /* this encoding assumes all keys have the same kvno */
+-    /* major-vno = 1 and minor-vno = 1 */
+-    kset->major_vno = 1;
+-    kset->minor_vno = 1;
+-    /* increment kvno (will be 1 if this is a new entry) */
+-    kvno += 1;
+-    kset->mkvno = krbcfg->mkvno;
+-
+-    krberr = ipa_krb5_generate_key_data(krbctx, princ,
+-                                        pwd, kvno, krbcfg->kmkey,
+-                                        krbcfg->num_pref_encsalts,
+-                                        krbcfg->pref_encsalts,
+-                                        &kset->num_keys, &kset->keys);
+-    if (krberr != 0) {
+-        LOG_FATAL("generating kerberos keys failed [%s]\n",
+-                  krb5_get_error_message(krbctx, krberr));
+-        goto enc_error;
+-    }
+-
+-    krberr = ber_encode_krb5_key_data(kset->keys, kset->num_keys,
+-                                      kset->mkvno, &bval);
+-    if (krberr != 0) {
+-        LOG_FATAL("encoding krb5_key_data failed\n");
+-        goto enc_error;
+-    }
+-
+-    svals[0] = slapi_value_new_berval(bval);
+-    if (!svals[0]) {
+-        LOG_FATAL("Converting berval to Slapi_Value\n");
+-        goto enc_error;
+-    }
+-
+-    ipapwd_keyset_free(&kset);
+-    krb5_free_principal(krbctx, princ);
+-    slapi_ch_free_string(&krbPrincipalName);
+-    ber_bvfree(bval);
+-    return svals;
+-
+-enc_error:
+-    *errMesg = "key encryption/encoding failed\n";
+-    if (kset) ipapwd_keyset_free(&kset);
+-    krb5_free_principal(krbctx, princ);
+-    slapi_ch_free_string(&krbPrincipalName);
+-    if (bval) ber_bvfree(bval);
+-    free(svals);
+-    return NULL;
+-}
+-
+-int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg,
+-                      struct ipapwd_data *data, char *userpw,
+-                      int is_krb, int is_smb, int is_ipant, Slapi_Value ***svals,
+-                      char **nthash, char **lmhash, Slapi_Value ***ntvals,
+-                      char **errMesg)
+-{
+-    int rc;
+-    char *userpw_uc = NULL;
+-
+-    *svals = NULL;
+-    *nthash = NULL;
+-    *lmhash = NULL;
+-    *errMesg = NULL;
+-
+-    if (is_krb) {
+-
+-        *svals = encrypt_encode_key(krbcfg, data, errMesg);
+-
+-        if (!*svals) {
+-            /* errMesg should have been set in encrypt_encode_key() */
+-            LOG_FATAL("key encryption/encoding failed\n");
+-            rc = LDAP_OPERATIONS_ERROR;
+-            goto done;
+-        }
+-    }
+-
+-    if (is_smb || is_ipant) {
+-        char lm[33], nt[33];
+-        struct ntlm_keys ntlm;
+-        int ret;
+-
+-        userpw_uc = (char *) slapi_utf8StrToUpper((unsigned char *) userpw);
+-        if (!userpw_uc) {
+-            *errMesg = "Failed to generate upper case password\n";
+-            LOG_FATAL("%s", *errMesg);
+-            rc = LDAP_OPERATIONS_ERROR;
+-            goto done;
+-        }
+-
+-        ret = encode_ntlm_keys(userpw,
+-                               userpw_uc,
+-                               krbcfg->allow_lm_hash,
+-                               krbcfg->allow_nt_hash,
+-                               &ntlm);
+-        memset(userpw_uc, 0, strlen(userpw_uc));
+-        slapi_ch_free_string(&userpw_uc);
+-        if (ret) {
+-            *errMesg = "Failed to generate NT/LM hashes\n";
+-            LOG_FATAL("%s", *errMesg);
+-            rc = LDAP_OPERATIONS_ERROR;
+-            goto done;
+-        }
+-        if (krbcfg->allow_lm_hash) {
+-            hexbuf(lm, ntlm.lm);
+-            lm[32] = '\0';
+-            *lmhash = slapi_ch_strdup(lm);
+-        }
+-        if (krbcfg->allow_nt_hash) {
+-            hexbuf(nt, ntlm.nt);
+-            nt[32] = '\0';
+-            *nthash = slapi_ch_strdup(nt);
+-        }
+-
+-        if (is_ipant) {
+-            *ntvals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *));
+-            if (!*ntvals) {
+-                LOG_OOM();
+-                rc = LDAP_OPERATIONS_ERROR;
+-                goto done;
+-            }
+-            (*ntvals)[0] = slapi_value_new();
+-            if (slapi_value_set((*ntvals)[0], ntlm.nt, 16) == NULL) {
+-                rc = LDAP_OPERATIONS_ERROR;
+-                goto done;
+-            }
+-        }
+-    }
+-
+-    rc = LDAP_SUCCESS;
+-
+-done:
+-
+-    /* when error, free possibly allocated output parameters */
+-    if (rc) {
+-        ipapwd_free_slapi_value_array(svals);
+-        ipapwd_free_slapi_value_array(ntvals);
+-    }
+-
+-    return rc;
+-}
+-
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c
+deleted file mode 100644
+index 0318cec..0000000
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c
++++ /dev/null
+@@ -1,1349 +0,0 @@
+-/** BEGIN COPYRIGHT BLOCK
+- * This program is free software; you can redistribute it and/or modify
+- * it under the terms of the GNU General Public License as published by
+- * the Free Software Foundation, either version 3 of the License, or
+- * (at your option) any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+- * GNU General Public License for more details.
+- *
+- * You should have received a copy of the GNU General Public License
+- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+- *
+- * Additional permission under GPLv3 section 7:
+- *
+- * In the following paragraph, "GPL" means the GNU General Public
+- * License, version 3 or any later version, and "Non-GPL Code" means
+- * code that is governed neither by the GPL nor a license
+- * compatible with the GPL.
+- *
+- * You may link the code of this Program with Non-GPL Code and convey
+- * linked combinations including the two, provided that such Non-GPL
+- * Code only links to the code of this Program through those well
+- * defined interfaces identified in the file named EXCEPTION found in
+- * the source code files (the "Approved Interfaces"). The files of
+- * Non-GPL Code may instantiate templates or use macros or inline
+- * functions from the Approved Interfaces without causing the resulting
+- * work to be covered by the GPL. Only the copyright holders of this
+- * Program may make changes or additions to the list of Approved
+- * Interfaces.
+- *
+- * Authors:
+- * Simo Sorce <ssorce at redhat.com>
+- *
+- * Copyright (C) 2007-2010 Red Hat, Inc.
+- * All rights reserved.
+- * END COPYRIGHT BLOCK **/
+-
+-#ifdef HAVE_CONFIG_H
+-#  include <config.h>
+-#endif
+-
+-/* strptime needs _XOPEN_SOURCE and endian.h needs __USE_BSD
+- * _GNU_SOURCE imply both, and we use it elsewhere, so use this */
+-#ifndef _GNU_SOURCE
+-#define _GNU_SOURCE 1
+-#endif
+-
+-#include <stdio.h>
+-#include <string.h>
+-#include <strings.h>
+-#include <sys/types.h>
+-#include <sys/stat.h>
+-#include <fcntl.h>
+-#include <unistd.h>
+-
+-#include <dirsrv/slapi-plugin.h>
+-#include <lber.h>
+-#include <time.h>
+-#include <endian.h>
+-
+-#include "ipapwd.h"
+-#include "util.h"
+-
+-#define IPAPWD_OP_NULL 0
+-#define IPAPWD_OP_ADD 1
+-#define IPAPWD_OP_MOD 2
+-
+-extern Slapi_PluginDesc ipapwd_plugin_desc;
+-extern void *ipapwd_plugin_id;
+-extern const char *ipa_realm_tree;
+-
+-/* structure with information for each extension */
+-struct ipapwd_op_ext {
+-    char *object_name;   /* name of the object extended   */
+-    int object_type;     /* handle to the extended object */
+-    int handle;          /* extension handle              */
+-};
+-/*****************************************************************************
+- * pre/post operations to intercept writes to userPassword
+- ****************************************************************************/
+-static struct ipapwd_op_ext ipapwd_op_ext_list;
+-
+-static void *ipapwd_op_ext_constructor(void *object, void *parent)
+-{
+-    struct ipapwd_operation *ext;
+-
+-    ext = (struct ipapwd_operation *)slapi_ch_calloc(1, sizeof(struct ipapwd_operation));
+-    return ext;
+-}
+-
+-static void ipapwd_op_ext_destructor(void *ext, void *object, void *parent)
+-{
+-    struct ipapwd_operation *pwdop = (struct ipapwd_operation *)ext;
+-    if (!pwdop)
+-        return;
+-    if (pwdop->pwd_op != IPAPWD_OP_NULL) {
+-        slapi_ch_free_string(&(pwdop->pwdata.dn));
+-        slapi_ch_free_string(&(pwdop->pwdata.password));
+-    }
+-    slapi_ch_free((void **)&pwdop);
+-}
+-
+-int ipapwd_ext_init(void)
+-{
+-    int ret;
+-
+-    ipapwd_op_ext_list.object_name = SLAPI_EXT_OPERATION;
+-
+-    ret = slapi_register_object_extension(IPAPWD_PLUGIN_NAME,
+-                                          SLAPI_EXT_OPERATION,
+-                                          ipapwd_op_ext_constructor,
+-                                          ipapwd_op_ext_destructor,
+-                                          &ipapwd_op_ext_list.object_type,
+-                                          &ipapwd_op_ext_list.handle);
+-
+-    return ret;
+-}
+-
+-
+-static char *ipapwd_getIpaConfigAttr(const char *attr)
+-{
+-    /* check if migrtion is enabled */
+-    Slapi_Entry *entry = NULL;
+-    const char *attrs_list[] = {attr, 0};
+-    char *value = NULL;
+-    char *dn = NULL;
+-    int ret;
+-
+-    dn = slapi_ch_smprintf("cn=ipaconfig,cn=etc,%s", ipa_realm_tree);
+-    if (!dn) {
+-        LOG_OOM();
+-        goto done;
+-    }
+-
+-    ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list);
+-    if (ret) {
+-        LOG("failed to retrieve config entry: %s\n", dn);
+-        goto done;
+-    }
+-
+-    value = slapi_entry_attr_get_charptr(entry, attr);
+-
+-done:
+-    slapi_entry_free(entry);
+-    slapi_ch_free_string(&dn);
+-    return value;
+-}
+-
+-
+-/* PRE ADD Operation:
+- * Gets the clean text password (fail the operation if the password came
+- * pre-hashed, unless this is a replicated operation or migration mode is
+- * enabled).
+- * Check user is authorized to add it otherwise just returns, operation will
+- * fail later anyway.
+- * Run a password policy check.
+- * Check if krb or smb hashes are required by testing if the krb or smb
+- * objectclasses are present.
+- * store information for the post operation
+- */
+-static int ipapwd_pre_add(Slapi_PBlock *pb)
+-{
+-    struct ipapwd_krbcfg *krbcfg = NULL;
+-    char *errMesg = "Internal operations error\n";
+-    struct slapi_entry *e = NULL;
+-    char *userpw = NULL;
+-    char *dn = NULL;
+-    struct ipapwd_operation *pwdop = NULL;
+-    void *op;
+-    int is_repl_op, is_root, is_krb, is_smb, is_ipant;
+-    int ret;
+-    int rc = LDAP_SUCCESS;
+-
+-    LOG_TRACE("=>\n");
+-
+-    ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
+-    if (ret != 0) {
+-        LOG_FATAL("slapi_pblock_get failed!?\n");
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-
+-    /* pass through if this is a replicated operation */
+-    if (is_repl_op)
+-        return 0;
+-
+-    /* retrieve the entry */
+-    slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
+-    if (NULL == e)
+-        return 0;
+-
+-    /* check this is something interesting for us first */
+-    userpw = slapi_entry_attr_get_charptr(e, SLAPI_USERPWD_ATTR);
+-    if (!userpw) {
+-	/* nothing interesting here */
+-	return 0;
+-    }
+-
+-    /* Ok this is interesting,
+-     * Check this is a clear text password, or refuse operation */
+-    if ('{' == userpw[0]) {
+-        if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
+-            char *tmp = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]);
+-            if (NULL == tmp) {
+-                LOG_OOM();
+-                rc = LDAP_OPERATIONS_ERROR;
+-                goto done;
+-            }
+-            slapi_ch_free_string(&userpw);
+-            userpw = tmp;
+-        } else if (slapi_is_encoded(userpw)) {
+-            const char *userpw_clear = NULL;
+-            Slapi_Value **pwvals = NULL;
+-
+-            /* Try to get clear password from an entry extension.
+-             * This function does not return a copy of the values,
+-             * no need to free them. */
+-            rc = slapi_pw_get_entry_ext(e, &pwvals);
+-            if (LDAP_SUCCESS == rc) {
+-                userpw_clear = slapi_value_get_string(pwvals[0]);
+-            }
+-
+-            /* Fail if we did not get a real clear text password from
+-             * the extension. This will happen if the password is hashed. */
+-            if (!userpw_clear || (0 == strcmp(userpw, userpw_clear))) {
+-                rc = LDAP_CONSTRAINT_VIOLATION;
+-                slapi_ch_free_string(&userpw);
+-            } else {
+-                userpw = slapi_ch_strdup(userpw_clear);
+-            }
+-
+-            if (rc != LDAP_SUCCESS) {
+-                /* we don't have access to the clear text password;
+-                 * let it slide if migration is enabled, but don't
+-                 * generate kerberos keys */
+-                char *enabled = ipapwd_getIpaConfigAttr("ipamigrationenabled");
+-                if (NULL == enabled) {
+-                    LOG("no ipaMigrationEnabled in config, assuming FALSE\n");
+-                } else if (0 == strcmp(enabled, "TRUE")) {
+-                    return 0;
+-                }
+-
+-                LOG("pre-hashed passwords are not valid\n");
+-                errMesg = "pre-hashed passwords are not valid\n";
+-                goto done;
+-            }
+-        }
+-    }
+-
+-    rc = ipapwd_entry_checks(pb, e,
+-                             &is_root, &is_krb, &is_smb, &is_ipant,
+-                             NULL, SLAPI_ACL_ADD);
+-    if (rc != LDAP_SUCCESS) {
+-        goto done;
+-    }
+-
+-    rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
+-    if (rc != LDAP_SUCCESS) {
+-        goto done;
+-    }
+-
+-    /* Get target DN */
+-    ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+-    if (ret) {
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-
+-    /* time to get the operation handler */
+-    ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+-    if (ret != 0) {
+-        LOG_FATAL("slapi_pblock_get failed!?\n");
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-
+-    pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
+-                                       op, ipapwd_op_ext_list.handle);
+-    if (NULL == pwdop) {
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-
+-    pwdop->pwd_op = IPAPWD_OP_ADD;
+-    pwdop->pwdata.password = slapi_ch_strdup(userpw);
+-
+-    if (is_root) {
+-        pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
+-    } else {
+-        char *binddn;
+-        int i;
+-
+-        pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN;
+-
+-        /* Check Bind DN */
+-        slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn);
+-
+-        /* if it is a passsync manager we also need to skip resets */
+-        for (i = 0; i < krbcfg->num_passsync_mgrs; i++) {
+-            if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) {
+-                pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
+-                break;
+-            }
+-        }
+-    }
+-
+-    pwdop->pwdata.dn = slapi_ch_strdup(dn);
+-    pwdop->pwdata.timeNow = time(NULL);
+-    pwdop->pwdata.target = e;
+-
+-    ret = ipapwd_CheckPolicy(&pwdop->pwdata);
+-    if (ret) {
+-        errMesg = ipapwd_error2string(ret);
+-        rc = LDAP_CONSTRAINT_VIOLATION;
+-        goto done;
+-    }
+-
+-    if (is_krb || is_smb || is_ipant) {
+-
+-        Slapi_Value **svals = NULL;
+-        Slapi_Value **ntvals = NULL;
+-        char *nt = NULL;
+-        char *lm = NULL;
+-
+-        pwdop->is_krb = is_krb;
+-
+-        rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata,
+-                               userpw, is_krb, is_smb, is_ipant,
+-                               &svals, &nt, &lm, &ntvals, &errMesg);
+-        if (rc != LDAP_SUCCESS) {
+-            goto done;
+-        }
+-
+-        if (svals) {
+-            /* add/replace values in existing entry */
+-            ret = slapi_entry_attr_replace_sv(e, "krbPrincipalKey", svals);
+-            if (ret) {
+-                LOG_FATAL("failed to set encoded values in entry\n");
+-                rc = LDAP_OPERATIONS_ERROR;
+-                ipapwd_free_slapi_value_array(&svals);
+-                goto done;
+-            }
+-
+-            ipapwd_free_slapi_value_array(&svals);
+-        }
+-
+-        if (lm && is_smb) {
+-            /* set value */
+-            slapi_entry_attr_set_charptr(e, "sambaLMPassword", lm);
+-            slapi_ch_free_string(&lm);
+-        }
+-        if (nt && is_smb) {
+-            /* set value */
+-            slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt);
+-            slapi_ch_free_string(&nt);
+-        }
+-
+-        if (ntvals && is_ipant) {
+-            slapi_entry_attr_replace_sv(e, "ipaNTHash", ntvals);
+-            ipapwd_free_slapi_value_array(&ntvals);
+-        }
+-
+-        if (is_smb) {
+-            /* with samba integration we need to also set sambaPwdLastSet or
+-             * samba will decide the user has to change the password again */
+-            if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) {
+-                /* if it is an admin change instead we need to let know to
+-                * samba as well that the use rmust change its password */
+-                slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L);
+-            } else {
+-                slapi_entry_attr_set_long(e, "sambaPwdLastset",
+-                                      (long)pwdop->pwdata.timeNow);
+-            }
+-        }
+-    }
+-
+-    rc = LDAP_SUCCESS;
+-
+-done:
+-    if (pwdop) pwdop->pwdata.target = NULL;
+-    free_ipapwd_krbcfg(&krbcfg);
+-    slapi_ch_free_string(&userpw);
+-    if (rc != LDAP_SUCCESS) {
+-        slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
+-        return -1;
+-    }
+-    return 0;
+-}
+-
+-#define NTHASH_REGEN_VAL "MagicRegen"
+-#define NTHASH_REGEN_LEN sizeof(NTHASH_REGEN_VAL)
+-static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods,
+-                               char *dn, struct slapi_entry *entry,
+-                               struct ipapwd_krbcfg *krbcfg);
+-
+-/* PRE MOD Operation:
+- * Gets the clean text password (fail the operation if the password came
+- * pre-hashed, unless this is a replicated operation).
+- * Check user is authorized to add it otherwise just returns, operation will
+- * fail later anyway.
+- * Check if krb or smb hashes are required by testing if the krb or smb
+- * objectclasses are present.
+- * Run a password policy check.
+- * store information for the post operation
+- */
+-static int ipapwd_pre_mod(Slapi_PBlock *pb)
+-{
+-    struct ipapwd_krbcfg *krbcfg = NULL;
+-    char *errMesg = NULL;
+-    LDAPMod **mods;
+-    LDAPMod *lmod;
+-    Slapi_Mods *smods = NULL;
+-    char *userpw = NULL;
+-    char *unhashedpw = NULL;
+-    char *dn = NULL;
+-    Slapi_DN *tmp_dn;
+-    struct slapi_entry *e = NULL;
+-    struct ipapwd_operation *pwdop = NULL;
+-    void *op;
+-    int is_repl_op, is_pwd_op, is_root, is_krb, is_smb, is_ipant;
+-    int has_krb_keys = 0;
+-    int has_history = 0;
+-    int gen_krb_keys = 0;
+-    int is_magic_regen = 0;
+-    int ret, rc;
+-
+-    LOG_TRACE( "=>\n");
+-
+-    ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
+-    if (ret != 0) {
+-        LOG_FATAL("slapi_pblock_get failed!?\n");
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-
+-    /* pass through if this is a replicated operation */
+-    if (is_repl_op) {
+-        rc = LDAP_SUCCESS;
+-        goto done;
+-    }
+-
+-    /* grab the mods - we'll put them back later with
+-     * our modifications appended
+-     */
+-    slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+-    smods = slapi_mods_new();
+-    slapi_mods_init_passin(smods, mods);
+-
+-    /* In the first pass,
+-     * only check there is anything we are interested in */
+-    is_pwd_op = 0;
+-    lmod = slapi_mods_get_first_mod(smods);
+-    while (lmod) {
+-        struct berval *bv;
+-
+-        if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) {
+-            /* check op filtering out LDAP_MOD_BVALUES */
+-            switch (lmod->mod_op & 0x0f) {
+-            case LDAP_MOD_ADD:
+-            case LDAP_MOD_REPLACE:
+-                is_pwd_op = 1;
+-            default:
+-                break;
+-            }
+-        } else if (slapi_attr_types_equivalent(lmod->mod_type, "ipaNTHash")) {
+-            /* check op filtering out LDAP_MOD_BVALUES */
+-            switch (lmod->mod_op & 0x0f) {
+-            case LDAP_MOD_ADD:
+-                if (!lmod->mod_bvalues ||
+-                    !lmod->mod_bvalues[0]) {
+-                    rc = LDAP_OPERATIONS_ERROR;
+-                    goto done;
+-                }
+-                bv = lmod->mod_bvalues[0];
+-                if ((bv->bv_len >= NTHASH_REGEN_LEN -1) &&
+-                    (bv->bv_len <= NTHASH_REGEN_LEN) &&
+-                    (strncmp(NTHASH_REGEN_VAL,
+-                             bv->bv_val, bv->bv_len) == 0)) {
+-                    is_magic_regen = 1;
+-                    /* make sure the database will later ignore this mod */
+-                    slapi_mods_remove(smods);
+-                }
+-            default:
+-                break;
+-            }
+-        } else if (slapi_attr_types_equivalent(lmod->mod_type,
+-                                                "unhashed#user#password")) {
+-            /* we check for unahsehd password here so that we are sure to
+-             * catch them early, before further checks go on, this helps
+-             * checking LDAP_MOD_DELETE operations in some corner cases later.
+-             * We keep only the last one if multiple are provided for any
+-             * reason */
+-            if (!lmod->mod_bvalues ||
+-                !lmod->mod_bvalues[0]) {
+-                rc = LDAP_OPERATIONS_ERROR;
+-                goto done;
+-            }
+-            bv = lmod->mod_bvalues[0];
+-            slapi_ch_free_string(&unhashedpw);
+-            unhashedpw = slapi_ch_malloc(bv->bv_len+1);
+-            if (!unhashedpw) {
+-                rc = LDAP_OPERATIONS_ERROR;
+-                goto done;
+-            }
+-            memcpy(unhashedpw, bv->bv_val, bv->bv_len);
+-            unhashedpw[bv->bv_len] = '\0';
+-        }
+-        lmod = slapi_mods_get_next_mod(smods);
+-    }
+-
+-    /* If userPassword is not modified check if this is a request to generate
+-     * NT hashes otherwise we are done here */
+-    if (!is_pwd_op && !is_magic_regen) {
+-        rc = LDAP_SUCCESS;
+-        goto done;
+-    }
+-
+-    /* OK we have something interesting here, start checking for
+-     * pre-requisites */
+-
+-    /* Get target DN */
+-    ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+-    if (ret) {
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-
+-    tmp_dn = slapi_sdn_new_dn_byref(dn);
+-    if (tmp_dn) {
+-        /* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be
+-         * available but it turns out that is only true if you are
+-         * a dbm backend pre-op plugin - lucky dbm backend pre-op
+-         * plugins.
+-         * I think that is wrong since the entry is useful for filter
+-         * tests and schema checks and this plugin shouldn't be limited
+-         * to a single backend type, but I don't want that fight right
+-         * now so we go get the entry here
+-         *
+-         slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e);
+-         */
+-        ret = slapi_search_internal_get_entry(tmp_dn, 0, &e, ipapwd_plugin_id);
+-        slapi_sdn_free(&tmp_dn);
+-        if (ret != LDAP_SUCCESS) {
+-            LOG("Failed to retrieve entry?!\n");
+-           rc = LDAP_NO_SUCH_OBJECT;
+-           goto done;
+-        }
+-    }
+-
+-    rc = ipapwd_entry_checks(pb, e,
+-                             &is_root, &is_krb, &is_smb, &is_ipant,
+-                             SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE);
+-    if (rc) {
+-        goto done;
+-    }
+-
+-    rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
+-    if (rc) {
+-        goto done;
+-    }
+-
+-    if (!is_pwd_op) {
+-        /* This may be a magic op to ask us to generate the NT hashes */
+-        if (is_magic_regen) {
+-            /* Make sense to call only if this entry has krb keys to source
+-             * the nthash from */
+-            if (is_krb) {
+-                rc = ipapwd_regen_nthash(pb, smods, dn, e, krbcfg);
+-            } else {
+-                rc = LDAP_UNWILLING_TO_PERFORM;
+-            }
+-        } else {
+-            rc = LDAP_OPERATIONS_ERROR;
+-        }
+-        goto done;
+-    }
+-
+-    /* run through the mods again and adjust flags if operations affect them */
+-    lmod = slapi_mods_get_first_mod(smods);
+-    while (lmod) {
+-        struct berval *bv;
+-
+-        if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) {
+-            /* check op filtering out LDAP_MOD_BVALUES */
+-            switch (lmod->mod_op & 0x0f) {
+-            case LDAP_MOD_ADD:
+-                /* FIXME: should we try to track cases where we would end up
+-                 * with multiple userPassword entries ?? */
+-            case LDAP_MOD_REPLACE:
+-                is_pwd_op = 1;
+-                if (!lmod->mod_bvalues ||
+-                    !lmod->mod_bvalues[0]) {
+-                    rc = LDAP_OPERATIONS_ERROR;
+-                    goto done;
+-                }
+-                bv = lmod->mod_bvalues[0];
+-                slapi_ch_free_string(&userpw);
+-                userpw = slapi_ch_malloc(bv->bv_len+1);
+-                if (!userpw) {
+-                    rc = LDAP_OPERATIONS_ERROR;
+-                    goto done;
+-                }
+-                memcpy(userpw, bv->bv_val, bv->bv_len);
+-                userpw[bv->bv_len] = '\0';
+-                break;
+-            case LDAP_MOD_DELETE:
+-                /* reset only if we are deleting all values, or the exact
+-                 * same value previously set, otherwise we are just trying to
+-                 * add a new value and delete an existing one */
+-                if (!lmod->mod_bvalues ||
+-                    !lmod->mod_bvalues[0]) {
+-                    is_pwd_op = 0;
+-                } else {
+-                    bv = lmod->mod_bvalues[0];
+-                    if ((userpw &&
+-                         strncmp(userpw, bv->bv_val, bv->bv_len) == 0) ||
+-                        (unhashedpw &&
+-                         strncmp(unhashedpw, bv->bv_val, bv->bv_len) == 0)) {
+-                        is_pwd_op = 0;
+-                    }
+-                }
+-            default:
+-                break;
+-            }
+-
+-        } else if (slapi_attr_types_equivalent(lmod->mod_type,
+-                                                SLAPI_ATTR_OBJECTCLASS)) {
+-            int i;
+-            /* check op filtering out LDAP_MOD_BVALUES */
+-            switch (lmod->mod_op & 0x0f) {
+-            case LDAP_MOD_REPLACE:
+-                /* if objectclasses are replaced we need to start clean with
+-                 * flags, so we sero them out and see if they get set again */
+-                is_krb = 0;
+-                is_smb = 0;
+-                is_ipant = 0;
+-
+-            case LDAP_MOD_ADD:
+-                if (!lmod->mod_bvalues ||
+-                    !lmod->mod_bvalues[0]) {
+-                    rc = LDAP_OPERATIONS_ERROR;
+-                    goto done;
+-                }
+-                for (i = 0; (bv = lmod->mod_bvalues[i]) != NULL; i++) {
+-                    if (strncasecmp("krbPrincipalAux",
+-                                    bv->bv_val, bv->bv_len) == 0) {
+-                        is_krb = 1;
+-                    } else if (strncasecmp("sambaSamAccount",
+-                                           bv->bv_val, bv->bv_len) == 0) {
+-                        is_smb = 1;
+-                    } else if (strncasecmp("ipaNTUserAttrs",
+-                                           bv->bv_val, bv->bv_len) == 0) {
+-                        is_ipant = 1;
+-                    }
+-                }
+-
+-                break;
+-
+-            case LDAP_MOD_DELETE:
+-                /* can this happen for objectclasses ? */
+-                is_krb = 0;
+-                is_smb = 0;
+-                is_ipant = 0;
+-
+-            default:
+-                break;
+-            }
+-
+-        } else if (slapi_attr_types_equivalent(lmod->mod_type,
+-                                               "krbPrincipalKey")) {
+-
+-            /* if we are getting a krbPrincipalKey, also avoid regenerating
+-             * the keys, it means kadmin has alredy done the job and is simply
+-             * keeping userPassword and sambaXXPAssword in sync */
+-
+-            /* we also check we have enough authority */
+-            if (is_root) {
+-                has_krb_keys = 1;
+-            }
+-
+-        } else if (slapi_attr_types_equivalent(lmod->mod_type,
+-                                               "passwordHistory")) {
+-
+-            /* if we are getting a passwordHistory, also avoid regenerating
+-             * the hashes, it means kadmin has alredy done the job and is
+-             * simply keeping userPassword and sambaXXPAssword in sync */
+-
+-            /* we also check we have enough authority */
+-            if (is_root) {
+-                has_history = 1;
+-            }
+-        }
+-
+-        lmod = slapi_mods_get_next_mod(smods);
+-    }
+-
+-    if (is_krb) {
+-        if (has_krb_keys) {
+-            gen_krb_keys = 0;
+-        } else {
+-            gen_krb_keys = 1;
+-        }
+-    }
+-
+-    /* It seem like we have determined that the end result will be deletion of
+-     * the userPassword attribute, so we have no more business here */
+-    if (! is_pwd_op) {
+-        rc = LDAP_SUCCESS;
+-        goto done;
+-    }
+-
+-    /* Check this is a clear text password, or refuse operation (only if we need
+-     * to comput other hashes */
+-    if (! unhashedpw && (gen_krb_keys || is_smb || is_ipant)) {
+-        if ('{' == userpw[0]) {
+-            if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
+-                unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]);
+-                if (NULL == unhashedpw) {
+-                    LOG_OOM();
+-                    rc = LDAP_OPERATIONS_ERROR;
+-                    goto done;
+-                }
+-                slapi_ch_free_string(&userpw);
+-
+-            } else if (slapi_is_encoded(userpw)) {
+-
+-                LOG("Pre-Encoded passwords are not valid\n");
+-                errMesg = "Pre-Encoded passwords are not valid\n";
+-                rc = LDAP_CONSTRAINT_VIOLATION;
+-                goto done;
+-            }
+-        }
+-    }
+-
+-    /* time to get the operation handler */
+-    ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+-    if (ret != 0) {
+-        LOG_FATAL("slapi_pblock_get failed!?\n");
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-
+-    pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
+-                                       op, ipapwd_op_ext_list.handle);
+-    if (NULL == pwdop) {
+-        rc = LDAP_OPERATIONS_ERROR;
+-        goto done;
+-    }
+-
+-    pwdop->is_krb = is_krb;
+-    pwdop->pwd_op = IPAPWD_OP_MOD;
+-    pwdop->pwdata.password = slapi_ch_strdup(unhashedpw);
+-    pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL;
+-    pwdop->skip_history = has_history;
+-    pwdop->skip_keys = has_krb_keys;
+-
+-    if (is_root) {
+-        pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
+-    } else {
+-        char *binddn;
+-        Slapi_DN *bdn, *tdn;
+-        int i;
+-
+-        /* Check Bind DN */
+-        slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn);
+-        bdn = slapi_sdn_new_dn_byref(binddn);
+-        tdn = slapi_sdn_new_dn_byref(dn);
+-
+-        /* if the change is performed by someone else,
+-         * it is an admin change that will require a new
+-         * password change immediately as per our IPA policy */
+-        if (slapi_sdn_compare(bdn, tdn)) {
+-            pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN;
+-
+-            /* if it is a passsync manager we also need to skip resets */
+-            for (i = 0; i < krbcfg->num_passsync_mgrs; i++) {
+-                if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) {
+-                    pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
+-                    break;
+-                }
+-            }
+-
+-        }
+-
+-        slapi_sdn_free(&bdn);
+-        slapi_sdn_free(&tdn);
+-
+-    }
+-
+-    pwdop->pwdata.dn = slapi_ch_strdup(dn);
+-    pwdop->pwdata.timeNow = time(NULL);
+-    pwdop->pwdata.target = e;
+-
+-    /* if krb keys are being set by an external agent we assume password
+-     * policies have been properly checked already, so we check them only
+-     * if no krb keys are available */
+-    if (has_krb_keys == 0) {
+-        ret = ipapwd_CheckPolicy(&pwdop->pwdata);
+-        if (ret) {
+-            errMesg = ipapwd_error2string(ret);
+-            rc = LDAP_CONSTRAINT_VIOLATION;
+-            goto done;
+-        }
+-    }
+-
+-    if (gen_krb_keys || is_smb || is_ipant) {
+-
+-        Slapi_Value **svals = NULL;
+-        Slapi_Value **ntvals = NULL;
+-        char *nt = NULL;
+-        char *lm = NULL;
+-
+-        rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata, unhashedpw,
+-                               gen_krb_keys, is_smb, is_ipant,
+-                               &svals, &nt, &lm, &ntvals, &errMesg);
+-        if (rc) {
+-            goto done;
+-        }
+-
+-        if (svals) {
+-            /* replace values */
+-            slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+-                                      "krbPrincipalKey", svals);
+-            ipapwd_free_slapi_value_array(&svals);
+-        }
+-
+-        if (lm && is_smb) {
+-            /* replace value */
+-            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                                  "sambaLMPassword", lm);
+-            slapi_ch_free_string(&lm);
+-        }
+-        if (nt && is_smb) {
+-            /* replace value */
+-            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                                  "sambaNTPassword", nt);
+-            slapi_ch_free_string(&nt);
+-        }
+-
+-        if (ntvals && is_ipant) {
+-            slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+-                                      "ipaNTHash", ntvals);
+-            ipapwd_free_slapi_value_array(&ntvals);
+-        }
+-
+-        if (is_smb) {
+-            /* with samba integration we need to also set sambaPwdLastSet or
+-             * samba will decide the user has to change the password again */
+-            if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) {
+-                /* if it is an admin change instead we need to let know to
+-                * samba as well that the use rmust change its password */
+-                slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L);
+-            } else {
+-                slapi_entry_attr_set_long(e, "sambaPwdLastset",
+-                                      (long)pwdop->pwdata.timeNow);
+-            }
+-        }
+-    }
+-
+-    rc = LDAP_SUCCESS;
+-
+-done:
+-    free_ipapwd_krbcfg(&krbcfg);
+-    slapi_ch_free_string(&userpw); /* just to be sure */
+-    slapi_ch_free_string(&unhashedpw); /* we copied it to pwdop  */
+-    if (e) slapi_entry_free(e); /* this is a copy in this function */
+-    if (pwdop) pwdop->pwdata.target = NULL;
+-
+-    /* put back a, possibly modified, set of mods */
+-    if (smods) {
+-        mods = slapi_mods_get_ldapmods_passout(smods);
+-        if (slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods)) {
+-            LOG_FATAL("slapi_pblock_set failed!\n");
+-            rc = LDAP_OPERATIONS_ERROR;
+-        }
+-        slapi_mods_free(&smods);
+-    }
+-
+-    if (rc != LDAP_SUCCESS) {
+-        slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
+-        return -1;
+-    }
+-
+-    return 0;
+-}
+-
+-static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods,
+-                               char *dn, struct slapi_entry *entry,
+-                               struct ipapwd_krbcfg *krbcfg)
+-{
+-    Slapi_Attr *attr;
+-    Slapi_Value *value;
+-    const struct berval *val;
+-    struct berval *ntvals[2] = { NULL, NULL };
+-    struct berval bval;
+-    krb5_key_data *keys;
+-    int num_keys;
+-    int mkvno;
+-    int ret;
+-    int i;
+-
+-    ret = slapi_entry_attr_find(entry, "ipaNTHash", &attr);
+-    if (ret == 0) {
+-        /* We refuse to regen if there is already a value */
+-        return LDAP_CONSTRAINT_VIOLATION;
+-    }
+-
+-    /* ok let's see if we can find the RC4 hash in the keys */
+-    ret = slapi_entry_attr_find(entry, "krbPrincipalKey", &attr);
+-    if (ret) {
+-        return LDAP_UNWILLING_TO_PERFORM;
+-    }
+-
+-    ret = slapi_attr_first_value(attr, &value);
+-    if (ret) {
+-        return LDAP_OPERATIONS_ERROR;
+-    }
+-
+-    val = slapi_value_get_berval(value);
+-    if (!val) {
+-        return LDAP_OPERATIONS_ERROR;
+-    }
+-
+-    ret = ber_decode_krb5_key_data((struct berval *)val,
+-                                    &mkvno, &num_keys, &keys);
+-    if (ret) {
+-        return LDAP_OPERATIONS_ERROR;
+-    }
+-
+-    ret = LDAP_UNWILLING_TO_PERFORM;
+-
+-    for (i = 0; i < num_keys; i++) {
+-        char nthash[16];
+-        krb5_enc_data cipher;
+-        krb5_data plain;
+-        krb5_int16 t;
+-
+-        if (keys[i].key_data_type[0] != ENCTYPE_ARCFOUR_HMAC) {
+-            continue;
+-        }
+-
+-        memcpy(&t, keys[i].key_data_contents[0], 2);
+-        plain.length = le16toh(t);
+-        if (plain.length != 16) {
+-            continue;
+-        }
+-        plain.data = nthash;
+-
+-        memset(&cipher, 0, sizeof(krb5_enc_data));
+-        cipher.enctype = krbcfg->kmkey->enctype;
+-        cipher.ciphertext.length = keys[i].key_data_length[0] - 2;
+-        cipher.ciphertext.data = ((char *)keys[i].key_data_contents[0]) + 2;
+-
+-        ret = krb5_c_decrypt(krbcfg->krbctx, krbcfg->kmkey,
+-                             0, NULL, &cipher, &plain);
+-        if (ret) {
+-            ret = LDAP_OPERATIONS_ERROR;
+-            break;
+-        }
+-
+-        bval.bv_val = nthash;
+-        bval.bv_len = 16;
+-        ntvals[0] = &bval;
+-
+-        slapi_mods_add_modbvps(smods, LDAP_MOD_ADD, "ipaNTHash", ntvals);
+-
+-        ret = LDAP_SUCCESS;
+-        break;
+-    }
+-
+-    ipa_krb5_free_key_data(keys, num_keys);
+-
+-    return ret;
+-}
+-
+-static int ipapwd_post_op(Slapi_PBlock *pb)
+-{
+-    void *op;
+-    struct ipapwd_operation *pwdop = NULL;
+-    Slapi_Mods *smods;
+-    Slapi_Value **pwvals;
+-    struct tm utctime;
+-    char timestr[GENERALIZED_TIME_LENGTH+1];
+-    int ret;
+-    char *errMsg = "Internal operations error\n";
+-    struct ipapwd_krbcfg *krbcfg = NULL;
+-    char *principal = NULL;
+-    Slapi_Value *ipahost;
+-
+-    LOG_TRACE("=>\n");
+-
+-    /* time to get the operation handler */
+-    ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+-    if (ret != 0) {
+-        LOG_FATAL("slapi_pblock_get failed!?\n");
+-        return 0;
+-    }
+-
+-    pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
+-                                       op, ipapwd_op_ext_list.handle);
+-    if (NULL == pwdop) {
+-        LOG_FATAL("Internal error, couldn't find pluginextension ?!\n");
+-        return 0;
+-    }
+-
+-    /* not interesting */
+-    if (IPAPWD_OP_NULL == pwdop->pwd_op)
+-        return 0;
+-
+-    if ( ! (pwdop->is_krb)) {
+-        LOG("Not a kerberos user, ignore krb attributes\n");
+-        return 0;
+-    }
+-
+-    if (pwdop->skip_keys && pwdop->skip_history) {
+-        /* nothing to do, caller already set all interesting attributes */
+-        return 0;
+-    }
+-
+-    ret = ipapwd_gen_checks(pb, &errMsg, &krbcfg, 0);
+-    if (ret != 0) {
+-        LOG_FATAL("ipapwd_gen_checks failed!?\n");
+-        return 0;
+-    }
+-
+-    /* prepare changes that can be made only as root */
+-    smods = slapi_mods_new();
+-
+-    /* This was a mod operation on an existing entry, make sure we also update
+-     * the password history based on the entry we saved from the pre-op */
+-    if (IPAPWD_OP_MOD == pwdop->pwd_op && !pwdop->skip_history) {
+-        Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(pwdop->pwdata.dn);
+-        if (tmp_dn) {
+-            ret = slapi_search_internal_get_entry(tmp_dn, 0,
+-                                                  &pwdop->pwdata.target,
+-                                                  ipapwd_plugin_id);
+-            slapi_sdn_free(&tmp_dn);
+-            if (ret != LDAP_SUCCESS) {
+-                LOG("Failed to retrieve entry?!\n");
+-                goto done;
+-            }
+-        }
+-        pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata);
+-        if (pwvals) {
+-            slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+-                                      "passwordHistory", pwvals);
+-        }
+-    }
+-
+-    /* we assume that krb attributes are properly updated too if keys were
+-     * passed in */
+-    if (!pwdop->skip_keys) {
+-        /* Don't set a last password change or expiration on host passwords.
+-         * krbLastPwdChange is used to tell whether we have a valid keytab.
+-         * If we set it on userPassword it confuses enrollment.
+-         * If krbPasswordExpiration is set on a host entry then the keytab
+-         * will appear to be expired.
+-         *
+-         * When a host is issued a keytab these attributes get set properly by
+-         * ipapwd_setkeytab().
+-         */
+-        ipahost = slapi_value_new_string("ipaHost");
+-        if (!pwdop->pwdata.target ||
+-            (slapi_entry_attr_has_syntax_value(pwdop->pwdata.target,
+-                                    SLAPI_ATTR_OBJECTCLASS, ipahost)) == 0) {
+-            /* set Password Expiration date */
+-            if (!gmtime_r(&(pwdop->pwdata.expireTime), &utctime)) {
+-                LOG_FATAL("failed to parse expiration date (buggy gmtime_r ?)\n");
+-                goto done;
+-            }
+-            strftime(timestr, GENERALIZED_TIME_LENGTH+1,
+-                     "%Y%m%d%H%M%SZ", &utctime);
+-            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                                  "krbPasswordExpiration", timestr);
+-
+-            /* change Last Password Change field with the current date */
+-            if (!gmtime_r(&(pwdop->pwdata.timeNow), &utctime)) {
+-                LOG_FATAL("failed to parse current date (buggy gmtime_r ?)\n");
+-                slapi_value_free(&ipahost);
+-                goto done;
+-            }
+-            strftime(timestr, GENERALIZED_TIME_LENGTH+1,
+-                     "%Y%m%d%H%M%SZ", &utctime);
+-            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+-                                  "krbLastPwdChange", timestr);
+-        }
+-        slapi_value_free(&ipahost);
+-    }
+-
+-    ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods);
+-    if (ret)
+-        LOG("Failed to set additional password attributes in the post-op!\n");
+-
+-    if (!pwdop->skip_keys) {
+-        if (pwdop->pwdata.changetype == IPA_CHANGETYPE_NORMAL) {
+-            principal = slapi_entry_attr_get_charptr(pwdop->pwdata.target,
+-                                                     "krbPrincipalName");
+-        } else {
+-            principal = slapi_ch_smprintf("root/admin@%s", krbcfg->realm);
+-        }
+-        ipapwd_set_extradata(pwdop->pwdata.dn, principal, pwdop->pwdata.timeNow);
+-    }
+-
+-done:
+-    if (pwdop && pwdop->pwdata.target) slapi_entry_free(pwdop->pwdata.target);
+-    slapi_mods_free(&smods);
+-    slapi_ch_free_string(&principal);
+-    free_ipapwd_krbcfg(&krbcfg);
+-    return 0;
+-}
+-
+-/* PRE BIND Operation:
+- * Used for password migration from DS to IPA.
+- * Gets the clean text password, authenticates the user and generates
+- * a kerberos key if missing.
+- * Person to blame if anything blows up: Pavel Zuna <pzuna at redhat.com>
+- */
+-static int ipapwd_pre_bind(Slapi_PBlock *pb)
+-{
+-    struct ipapwd_krbcfg *krbcfg = NULL;
+-    struct ipapwd_data pwdata;
+-    struct berval *credentials; /* bind credentials */
+-    Slapi_Entry *entry = NULL;
+-    Slapi_Value **pwd_values = NULL; /* values of userPassword attribute */
+-    Slapi_Value *value = NULL;
+-    Slapi_Attr *attr = NULL;
+-    struct tm expire_tm;
+-    char *errMesg = "Internal operations error\n"; /* error message */
+-    char *expire = NULL; /* passwordExpirationTime attribute value */
+-    char *dn = NULL; /* bind DN */
+-    Slapi_Value *objectclass;
+-    int method; /* authentication method */
+-    int ret = 0;
+-    char *principal = NULL;
+-
+-    LOG_TRACE("=>\n");
+-
+-    /* get BIND parameters */
+-    ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn);
+-    ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
+-    ret |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &credentials);
+-    if (ret) {
+-        LOG_FATAL("slapi_pblock_get failed!?\n");
+-        goto done;
+-    }
+-
+-    /* we're only interested in simple authentication */
+-    if (method != LDAP_AUTH_SIMPLE)
+-        goto done;
+-
+-    /* list of attributes to retrieve */
+-    const char *attrs_list[] = {SLAPI_USERPWD_ATTR, "krbprincipalkey", "uid",
+-                                "krbprincipalname", "objectclass",
+-                                "passwordexpirationtime",  "passwordhistory",
+-                                NULL};
+-
+-    /* retrieve user entry */
+-    ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list);
+-    if (ret) {
+-        LOG("failed to retrieve user entry: %s\n", dn);
+-        goto done;
+-    }
+-
+-    /* check the krbPrincipalName attribute is present */
+-    ret = slapi_entry_attr_find(entry, "krbprincipalname", &attr);
+-    if (ret) {
+-        LOG("no krbPrincipalName in user entry: %s\n", dn);
+-        goto done;
+-    }
+-
+-    /* we aren't interested in host principals */
+-    objectclass = slapi_value_new_string("ipaHost");
+-    if ((slapi_entry_attr_has_syntax_value(entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) == 1) {
+-        slapi_value_free(&objectclass);
+-        goto done;
+-    }
+-    slapi_value_free(&objectclass);
+-
+-    /* check the krbPrincipalKey attribute is NOT present */
+-    ret = slapi_entry_attr_find(entry, "krbprincipalkey", &attr);
+-    if (!ret) {
+-        LOG("kerberos key already present in user entry: %s\n", dn);
+-        goto done;
+-    }
+-
+-    /* retrieve userPassword attribute */
+-    ret = slapi_entry_attr_find(entry, SLAPI_USERPWD_ATTR, &attr);
+-    if (ret) {
+-        LOG("no " SLAPI_USERPWD_ATTR " in user entry: %s\n", dn);
+-        goto done;
+-    }
+-
+-    /* get the number of userPassword values and allocate enough memory */
+-    slapi_attr_get_numvalues(attr, &ret);
+-    ret = (ret + 1) * sizeof (Slapi_Value *);
+-    pwd_values = (Slapi_Value **) slapi_ch_malloc(ret);
+-    if (!pwd_values) {
+-        /* probably not required: should terminate the server anyway */
+-        LOG_OOM();
+-        goto done;
+-    }
+-    /* zero-fill the allocated memory; we need the array ending with NULL */
+-    memset(pwd_values, 0, ret);
+-
+-    /* retrieve userPassword values */
+-    ret = slapi_attr_first_value(attr, &value);
+-    while (ret != -1) {
+-        pwd_values[ret] = value;
+-        ret = slapi_attr_next_value(attr, ret, &value);
+-    }
+-
+-    /* check if BIND password and userPassword match */
+-    value = slapi_value_new_berval(credentials);
+-    ret = slapi_pw_find_sv(pwd_values, value);
+-
+-    /* free before checking ret; we might not get a chance later */
+-    slapi_ch_free((void **) &pwd_values);
+-    slapi_value_free(&value);
+-
+-    if (ret) {
+-        LOG("invalid BIND password for user entry: %s\n", dn);
+-        goto done;
+-    }
+-
+-    /* general checks */
+-    ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
+-    if (ret) {
+-        LOG_FATAL("Generic checks failed: %s", errMesg);
+-        goto done;
+-    }
+-
+-    /* delete userPassword - a new one will be generated later */
+-    /* this is needed, otherwise ipapwd_CheckPolicy will think
+-     * we're changing the password to its previous value
+-     * and force a password change on next login  */
+-    ret = slapi_entry_attr_delete(entry, SLAPI_USERPWD_ATTR);
+-    if (ret) {
+-        LOG_FATAL("failed to delete " SLAPI_USERPWD_ATTR "\n");
+-        goto done;
+-    }
+-
+-    /* prepare data for kerberos key generation */
+-    memset(&pwdata, 0, sizeof (pwdata));
+-    pwdata.dn = dn;
+-    pwdata.target = entry;
+-    pwdata.password = credentials->bv_val;
+-    pwdata.timeNow = time(NULL);
+-    pwdata.changetype = IPA_CHANGETYPE_NORMAL;
+-
+-    /* keep password expiration time from DS, if possible */
+-    expire = slapi_entry_attr_get_charptr(entry, "passwordexpirationtime");
+-    if (expire) {
+-        memset(&expire_tm, 0, sizeof (expire_tm));
+-        if (strptime(expire, "%Y%m%d%H%M%SZ", &expire_tm))
+-            pwdata.expireTime = mktime(&expire_tm);
+-    }
+-
+-    /* check password policy */
+-    ret = ipapwd_CheckPolicy(&pwdata);
+-    if (ret) {
+-        /* Password fails to meet IPA password policy,
+-         * force user to change his password next time he logs in. */
+-        LOG("password policy check failed on user entry: %s"
+-            " (force password change on next login)\n", dn);
+-        pwdata.expireTime = time(NULL);
+-    }
+-
+-    /* generate kerberos keys */
+-    ret = ipapwd_SetPassword(krbcfg, &pwdata, 1);
+-    if (ret) {
+-        LOG("failed to set kerberos key for user entry: %s\n", dn);
+-        goto done;
+-    }
+-
+-    /* we need to make sure the ExtraData is set, otherwise kadmin
+-     * will not like the object */
+-    principal = slapi_entry_attr_get_charptr(entry, "krbPrincipalName");
+-    if (!principal) {
+-        LOG_OOM();
+-        goto done;
+-    }
+-    ipapwd_set_extradata(pwdata.dn, principal, pwdata.timeNow);
+-
+-    LOG("kerberos key generated for user entry: %s\n", dn);
+-
+-done:
+-    slapi_ch_free_string(&principal);
+-    slapi_ch_free_string(&expire);
+-    if (entry)
+-        slapi_entry_free(entry);
+-    free_ipapwd_krbcfg(&krbcfg);
+-
+-    return 0;
+-}
+-
+-
+-
+-/* Init pre ops */
+-int ipapwd_pre_init(Slapi_PBlock *pb)
+-{
+-    int ret;
+-
+-    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *)ipapwd_pre_bind);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *)ipapwd_pre_add);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod);
+-
+-    return ret;
+-}
+-
+-int ipapwd_pre_init_betxn(Slapi_PBlock *pb)
+-{
+-    int ret;
+-
+-    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN, (void *)ipapwd_pre_add);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod);
+-
+-    return ret;
+-}
+-
+-/* Init post ops */
+-int ipapwd_post_init(Slapi_PBlock *pb)
+-{
+-    int ret;
+-
+-    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op);
+-
+-    return ret;
+-}
+-
+-int ipapwd_post_init_betxn(Slapi_PBlock *pb)
+-{
+-    int ret;
+-
+-    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_op);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_op);
+-
+-    return ret;
+-}
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+new file mode 100644
+index 0000000..0318cec
+--- /dev/null
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+@@ -0,0 +1,1349 @@
++/** BEGIN COPYRIGHT BLOCK
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
++ *
++ * Additional permission under GPLv3 section 7:
++ *
++ * In the following paragraph, "GPL" means the GNU General Public
++ * License, version 3 or any later version, and "Non-GPL Code" means
++ * code that is governed neither by the GPL nor a license
++ * compatible with the GPL.
++ *
++ * You may link the code of this Program with Non-GPL Code and convey
++ * linked combinations including the two, provided that such Non-GPL
++ * Code only links to the code of this Program through those well
++ * defined interfaces identified in the file named EXCEPTION found in
++ * the source code files (the "Approved Interfaces"). The files of
++ * Non-GPL Code may instantiate templates or use macros or inline
++ * functions from the Approved Interfaces without causing the resulting
++ * work to be covered by the GPL. Only the copyright holders of this
++ * Program may make changes or additions to the list of Approved
++ * Interfaces.
++ *
++ * Authors:
++ * Simo Sorce <ssorce at redhat.com>
++ *
++ * Copyright (C) 2007-2010 Red Hat, Inc.
++ * All rights reserved.
++ * END COPYRIGHT BLOCK **/
++
++#ifdef HAVE_CONFIG_H
++#  include <config.h>
++#endif
++
++/* strptime needs _XOPEN_SOURCE and endian.h needs __USE_BSD
++ * _GNU_SOURCE imply both, and we use it elsewhere, so use this */
++#ifndef _GNU_SOURCE
++#define _GNU_SOURCE 1
++#endif
++
++#include <stdio.h>
++#include <string.h>
++#include <strings.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <fcntl.h>
++#include <unistd.h>
++
++#include <dirsrv/slapi-plugin.h>
++#include <lber.h>
++#include <time.h>
++#include <endian.h>
++
++#include "ipapwd.h"
++#include "util.h"
++
++#define IPAPWD_OP_NULL 0
++#define IPAPWD_OP_ADD 1
++#define IPAPWD_OP_MOD 2
++
++extern Slapi_PluginDesc ipapwd_plugin_desc;
++extern void *ipapwd_plugin_id;
++extern const char *ipa_realm_tree;
++
++/* structure with information for each extension */
++struct ipapwd_op_ext {
++    char *object_name;   /* name of the object extended   */
++    int object_type;     /* handle to the extended object */
++    int handle;          /* extension handle              */
++};
++/*****************************************************************************
++ * pre/post operations to intercept writes to userPassword
++ ****************************************************************************/
++static struct ipapwd_op_ext ipapwd_op_ext_list;
++
++static void *ipapwd_op_ext_constructor(void *object, void *parent)
++{
++    struct ipapwd_operation *ext;
++
++    ext = (struct ipapwd_operation *)slapi_ch_calloc(1, sizeof(struct ipapwd_operation));
++    return ext;
++}
++
++static void ipapwd_op_ext_destructor(void *ext, void *object, void *parent)
++{
++    struct ipapwd_operation *pwdop = (struct ipapwd_operation *)ext;
++    if (!pwdop)
++        return;
++    if (pwdop->pwd_op != IPAPWD_OP_NULL) {
++        slapi_ch_free_string(&(pwdop->pwdata.dn));
++        slapi_ch_free_string(&(pwdop->pwdata.password));
++    }
++    slapi_ch_free((void **)&pwdop);
++}
++
++int ipapwd_ext_init(void)
++{
++    int ret;
++
++    ipapwd_op_ext_list.object_name = SLAPI_EXT_OPERATION;
++
++    ret = slapi_register_object_extension(IPAPWD_PLUGIN_NAME,
++                                          SLAPI_EXT_OPERATION,
++                                          ipapwd_op_ext_constructor,
++                                          ipapwd_op_ext_destructor,
++                                          &ipapwd_op_ext_list.object_type,
++                                          &ipapwd_op_ext_list.handle);
++
++    return ret;
++}
++
++
++static char *ipapwd_getIpaConfigAttr(const char *attr)
++{
++    /* check if migrtion is enabled */
++    Slapi_Entry *entry = NULL;
++    const char *attrs_list[] = {attr, 0};
++    char *value = NULL;
++    char *dn = NULL;
++    int ret;
++
++    dn = slapi_ch_smprintf("cn=ipaconfig,cn=etc,%s", ipa_realm_tree);
++    if (!dn) {
++        LOG_OOM();
++        goto done;
++    }
++
++    ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list);
++    if (ret) {
++        LOG("failed to retrieve config entry: %s\n", dn);
++        goto done;
++    }
++
++    value = slapi_entry_attr_get_charptr(entry, attr);
++
++done:
++    slapi_entry_free(entry);
++    slapi_ch_free_string(&dn);
++    return value;
++}
++
++
++/* PRE ADD Operation:
++ * Gets the clean text password (fail the operation if the password came
++ * pre-hashed, unless this is a replicated operation or migration mode is
++ * enabled).
++ * Check user is authorized to add it otherwise just returns, operation will
++ * fail later anyway.
++ * Run a password policy check.
++ * Check if krb or smb hashes are required by testing if the krb or smb
++ * objectclasses are present.
++ * store information for the post operation
++ */
++static int ipapwd_pre_add(Slapi_PBlock *pb)
++{
++    struct ipapwd_krbcfg *krbcfg = NULL;
++    char *errMesg = "Internal operations error\n";
++    struct slapi_entry *e = NULL;
++    char *userpw = NULL;
++    char *dn = NULL;
++    struct ipapwd_operation *pwdop = NULL;
++    void *op;
++    int is_repl_op, is_root, is_krb, is_smb, is_ipant;
++    int ret;
++    int rc = LDAP_SUCCESS;
++
++    LOG_TRACE("=>\n");
++
++    ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
++    if (ret != 0) {
++        LOG_FATAL("slapi_pblock_get failed!?\n");
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++
++    /* pass through if this is a replicated operation */
++    if (is_repl_op)
++        return 0;
++
++    /* retrieve the entry */
++    slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
++    if (NULL == e)
++        return 0;
++
++    /* check this is something interesting for us first */
++    userpw = slapi_entry_attr_get_charptr(e, SLAPI_USERPWD_ATTR);
++    if (!userpw) {
++	/* nothing interesting here */
++	return 0;
++    }
++
++    /* Ok this is interesting,
++     * Check this is a clear text password, or refuse operation */
++    if ('{' == userpw[0]) {
++        if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
++            char *tmp = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]);
++            if (NULL == tmp) {
++                LOG_OOM();
++                rc = LDAP_OPERATIONS_ERROR;
++                goto done;
++            }
++            slapi_ch_free_string(&userpw);
++            userpw = tmp;
++        } else if (slapi_is_encoded(userpw)) {
++            const char *userpw_clear = NULL;
++            Slapi_Value **pwvals = NULL;
++
++            /* Try to get clear password from an entry extension.
++             * This function does not return a copy of the values,
++             * no need to free them. */
++            rc = slapi_pw_get_entry_ext(e, &pwvals);
++            if (LDAP_SUCCESS == rc) {
++                userpw_clear = slapi_value_get_string(pwvals[0]);
++            }
++
++            /* Fail if we did not get a real clear text password from
++             * the extension. This will happen if the password is hashed. */
++            if (!userpw_clear || (0 == strcmp(userpw, userpw_clear))) {
++                rc = LDAP_CONSTRAINT_VIOLATION;
++                slapi_ch_free_string(&userpw);
++            } else {
++                userpw = slapi_ch_strdup(userpw_clear);
++            }
++
++            if (rc != LDAP_SUCCESS) {
++                /* we don't have access to the clear text password;
++                 * let it slide if migration is enabled, but don't
++                 * generate kerberos keys */
++                char *enabled = ipapwd_getIpaConfigAttr("ipamigrationenabled");
++                if (NULL == enabled) {
++                    LOG("no ipaMigrationEnabled in config, assuming FALSE\n");
++                } else if (0 == strcmp(enabled, "TRUE")) {
++                    return 0;
++                }
++
++                LOG("pre-hashed passwords are not valid\n");
++                errMesg = "pre-hashed passwords are not valid\n";
++                goto done;
++            }
++        }
++    }
++
++    rc = ipapwd_entry_checks(pb, e,
++                             &is_root, &is_krb, &is_smb, &is_ipant,
++                             NULL, SLAPI_ACL_ADD);
++    if (rc != LDAP_SUCCESS) {
++        goto done;
++    }
++
++    rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
++    if (rc != LDAP_SUCCESS) {
++        goto done;
++    }
++
++    /* Get target DN */
++    ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
++    if (ret) {
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++
++    /* time to get the operation handler */
++    ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
++    if (ret != 0) {
++        LOG_FATAL("slapi_pblock_get failed!?\n");
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++
++    pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
++                                       op, ipapwd_op_ext_list.handle);
++    if (NULL == pwdop) {
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++
++    pwdop->pwd_op = IPAPWD_OP_ADD;
++    pwdop->pwdata.password = slapi_ch_strdup(userpw);
++
++    if (is_root) {
++        pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
++    } else {
++        char *binddn;
++        int i;
++
++        pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN;
++
++        /* Check Bind DN */
++        slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn);
++
++        /* if it is a passsync manager we also need to skip resets */
++        for (i = 0; i < krbcfg->num_passsync_mgrs; i++) {
++            if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) {
++                pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
++                break;
++            }
++        }
++    }
++
++    pwdop->pwdata.dn = slapi_ch_strdup(dn);
++    pwdop->pwdata.timeNow = time(NULL);
++    pwdop->pwdata.target = e;
++
++    ret = ipapwd_CheckPolicy(&pwdop->pwdata);
++    if (ret) {
++        errMesg = ipapwd_error2string(ret);
++        rc = LDAP_CONSTRAINT_VIOLATION;
++        goto done;
++    }
++
++    if (is_krb || is_smb || is_ipant) {
++
++        Slapi_Value **svals = NULL;
++        Slapi_Value **ntvals = NULL;
++        char *nt = NULL;
++        char *lm = NULL;
++
++        pwdop->is_krb = is_krb;
++
++        rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata,
++                               userpw, is_krb, is_smb, is_ipant,
++                               &svals, &nt, &lm, &ntvals, &errMesg);
++        if (rc != LDAP_SUCCESS) {
++            goto done;
++        }
++
++        if (svals) {
++            /* add/replace values in existing entry */
++            ret = slapi_entry_attr_replace_sv(e, "krbPrincipalKey", svals);
++            if (ret) {
++                LOG_FATAL("failed to set encoded values in entry\n");
++                rc = LDAP_OPERATIONS_ERROR;
++                ipapwd_free_slapi_value_array(&svals);
++                goto done;
++            }
++
++            ipapwd_free_slapi_value_array(&svals);
++        }
++
++        if (lm && is_smb) {
++            /* set value */
++            slapi_entry_attr_set_charptr(e, "sambaLMPassword", lm);
++            slapi_ch_free_string(&lm);
++        }
++        if (nt && is_smb) {
++            /* set value */
++            slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt);
++            slapi_ch_free_string(&nt);
++        }
++
++        if (ntvals && is_ipant) {
++            slapi_entry_attr_replace_sv(e, "ipaNTHash", ntvals);
++            ipapwd_free_slapi_value_array(&ntvals);
++        }
++
++        if (is_smb) {
++            /* with samba integration we need to also set sambaPwdLastSet or
++             * samba will decide the user has to change the password again */
++            if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) {
++                /* if it is an admin change instead we need to let know to
++                * samba as well that the use rmust change its password */
++                slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L);
++            } else {
++                slapi_entry_attr_set_long(e, "sambaPwdLastset",
++                                      (long)pwdop->pwdata.timeNow);
++            }
++        }
++    }
++
++    rc = LDAP_SUCCESS;
++
++done:
++    if (pwdop) pwdop->pwdata.target = NULL;
++    free_ipapwd_krbcfg(&krbcfg);
++    slapi_ch_free_string(&userpw);
++    if (rc != LDAP_SUCCESS) {
++        slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
++        return -1;
++    }
++    return 0;
++}
++
++#define NTHASH_REGEN_VAL "MagicRegen"
++#define NTHASH_REGEN_LEN sizeof(NTHASH_REGEN_VAL)
++static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods,
++                               char *dn, struct slapi_entry *entry,
++                               struct ipapwd_krbcfg *krbcfg);
++
++/* PRE MOD Operation:
++ * Gets the clean text password (fail the operation if the password came
++ * pre-hashed, unless this is a replicated operation).
++ * Check user is authorized to add it otherwise just returns, operation will
++ * fail later anyway.
++ * Check if krb or smb hashes are required by testing if the krb or smb
++ * objectclasses are present.
++ * Run a password policy check.
++ * store information for the post operation
++ */
++static int ipapwd_pre_mod(Slapi_PBlock *pb)
++{
++    struct ipapwd_krbcfg *krbcfg = NULL;
++    char *errMesg = NULL;
++    LDAPMod **mods;
++    LDAPMod *lmod;
++    Slapi_Mods *smods = NULL;
++    char *userpw = NULL;
++    char *unhashedpw = NULL;
++    char *dn = NULL;
++    Slapi_DN *tmp_dn;
++    struct slapi_entry *e = NULL;
++    struct ipapwd_operation *pwdop = NULL;
++    void *op;
++    int is_repl_op, is_pwd_op, is_root, is_krb, is_smb, is_ipant;
++    int has_krb_keys = 0;
++    int has_history = 0;
++    int gen_krb_keys = 0;
++    int is_magic_regen = 0;
++    int ret, rc;
++
++    LOG_TRACE( "=>\n");
++
++    ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
++    if (ret != 0) {
++        LOG_FATAL("slapi_pblock_get failed!?\n");
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++
++    /* pass through if this is a replicated operation */
++    if (is_repl_op) {
++        rc = LDAP_SUCCESS;
++        goto done;
++    }
++
++    /* grab the mods - we'll put them back later with
++     * our modifications appended
++     */
++    slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
++    smods = slapi_mods_new();
++    slapi_mods_init_passin(smods, mods);
++
++    /* In the first pass,
++     * only check there is anything we are interested in */
++    is_pwd_op = 0;
++    lmod = slapi_mods_get_first_mod(smods);
++    while (lmod) {
++        struct berval *bv;
++
++        if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) {
++            /* check op filtering out LDAP_MOD_BVALUES */
++            switch (lmod->mod_op & 0x0f) {
++            case LDAP_MOD_ADD:
++            case LDAP_MOD_REPLACE:
++                is_pwd_op = 1;
++            default:
++                break;
++            }
++        } else if (slapi_attr_types_equivalent(lmod->mod_type, "ipaNTHash")) {
++            /* check op filtering out LDAP_MOD_BVALUES */
++            switch (lmod->mod_op & 0x0f) {
++            case LDAP_MOD_ADD:
++                if (!lmod->mod_bvalues ||
++                    !lmod->mod_bvalues[0]) {
++                    rc = LDAP_OPERATIONS_ERROR;
++                    goto done;
++                }
++                bv = lmod->mod_bvalues[0];
++                if ((bv->bv_len >= NTHASH_REGEN_LEN -1) &&
++                    (bv->bv_len <= NTHASH_REGEN_LEN) &&
++                    (strncmp(NTHASH_REGEN_VAL,
++                             bv->bv_val, bv->bv_len) == 0)) {
++                    is_magic_regen = 1;
++                    /* make sure the database will later ignore this mod */
++                    slapi_mods_remove(smods);
++                }
++            default:
++                break;
++            }
++        } else if (slapi_attr_types_equivalent(lmod->mod_type,
++                                                "unhashed#user#password")) {
++            /* we check for unahsehd password here so that we are sure to
++             * catch them early, before further checks go on, this helps
++             * checking LDAP_MOD_DELETE operations in some corner cases later.
++             * We keep only the last one if multiple are provided for any
++             * reason */
++            if (!lmod->mod_bvalues ||
++                !lmod->mod_bvalues[0]) {
++                rc = LDAP_OPERATIONS_ERROR;
++                goto done;
++            }
++            bv = lmod->mod_bvalues[0];
++            slapi_ch_free_string(&unhashedpw);
++            unhashedpw = slapi_ch_malloc(bv->bv_len+1);
++            if (!unhashedpw) {
++                rc = LDAP_OPERATIONS_ERROR;
++                goto done;
++            }
++            memcpy(unhashedpw, bv->bv_val, bv->bv_len);
++            unhashedpw[bv->bv_len] = '\0';
++        }
++        lmod = slapi_mods_get_next_mod(smods);
++    }
++
++    /* If userPassword is not modified check if this is a request to generate
++     * NT hashes otherwise we are done here */
++    if (!is_pwd_op && !is_magic_regen) {
++        rc = LDAP_SUCCESS;
++        goto done;
++    }
++
++    /* OK we have something interesting here, start checking for
++     * pre-requisites */
++
++    /* Get target DN */
++    ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
++    if (ret) {
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++
++    tmp_dn = slapi_sdn_new_dn_byref(dn);
++    if (tmp_dn) {
++        /* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be
++         * available but it turns out that is only true if you are
++         * a dbm backend pre-op plugin - lucky dbm backend pre-op
++         * plugins.
++         * I think that is wrong since the entry is useful for filter
++         * tests and schema checks and this plugin shouldn't be limited
++         * to a single backend type, but I don't want that fight right
++         * now so we go get the entry here
++         *
++         slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e);
++         */
++        ret = slapi_search_internal_get_entry(tmp_dn, 0, &e, ipapwd_plugin_id);
++        slapi_sdn_free(&tmp_dn);
++        if (ret != LDAP_SUCCESS) {
++            LOG("Failed to retrieve entry?!\n");
++           rc = LDAP_NO_SUCH_OBJECT;
++           goto done;
++        }
++    }
++
++    rc = ipapwd_entry_checks(pb, e,
++                             &is_root, &is_krb, &is_smb, &is_ipant,
++                             SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE);
++    if (rc) {
++        goto done;
++    }
++
++    rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
++    if (rc) {
++        goto done;
++    }
++
++    if (!is_pwd_op) {
++        /* This may be a magic op to ask us to generate the NT hashes */
++        if (is_magic_regen) {
++            /* Make sense to call only if this entry has krb keys to source
++             * the nthash from */
++            if (is_krb) {
++                rc = ipapwd_regen_nthash(pb, smods, dn, e, krbcfg);
++            } else {
++                rc = LDAP_UNWILLING_TO_PERFORM;
++            }
++        } else {
++            rc = LDAP_OPERATIONS_ERROR;
++        }
++        goto done;
++    }
++
++    /* run through the mods again and adjust flags if operations affect them */
++    lmod = slapi_mods_get_first_mod(smods);
++    while (lmod) {
++        struct berval *bv;
++
++        if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) {
++            /* check op filtering out LDAP_MOD_BVALUES */
++            switch (lmod->mod_op & 0x0f) {
++            case LDAP_MOD_ADD:
++                /* FIXME: should we try to track cases where we would end up
++                 * with multiple userPassword entries ?? */
++            case LDAP_MOD_REPLACE:
++                is_pwd_op = 1;
++                if (!lmod->mod_bvalues ||
++                    !lmod->mod_bvalues[0]) {
++                    rc = LDAP_OPERATIONS_ERROR;
++                    goto done;
++                }
++                bv = lmod->mod_bvalues[0];
++                slapi_ch_free_string(&userpw);
++                userpw = slapi_ch_malloc(bv->bv_len+1);
++                if (!userpw) {
++                    rc = LDAP_OPERATIONS_ERROR;
++                    goto done;
++                }
++                memcpy(userpw, bv->bv_val, bv->bv_len);
++                userpw[bv->bv_len] = '\0';
++                break;
++            case LDAP_MOD_DELETE:
++                /* reset only if we are deleting all values, or the exact
++                 * same value previously set, otherwise we are just trying to
++                 * add a new value and delete an existing one */
++                if (!lmod->mod_bvalues ||
++                    !lmod->mod_bvalues[0]) {
++                    is_pwd_op = 0;
++                } else {
++                    bv = lmod->mod_bvalues[0];
++                    if ((userpw &&
++                         strncmp(userpw, bv->bv_val, bv->bv_len) == 0) ||
++                        (unhashedpw &&
++                         strncmp(unhashedpw, bv->bv_val, bv->bv_len) == 0)) {
++                        is_pwd_op = 0;
++                    }
++                }
++            default:
++                break;
++            }
++
++        } else if (slapi_attr_types_equivalent(lmod->mod_type,
++                                                SLAPI_ATTR_OBJECTCLASS)) {
++            int i;
++            /* check op filtering out LDAP_MOD_BVALUES */
++            switch (lmod->mod_op & 0x0f) {
++            case LDAP_MOD_REPLACE:
++                /* if objectclasses are replaced we need to start clean with
++                 * flags, so we sero them out and see if they get set again */
++                is_krb = 0;
++                is_smb = 0;
++                is_ipant = 0;
++
++            case LDAP_MOD_ADD:
++                if (!lmod->mod_bvalues ||
++                    !lmod->mod_bvalues[0]) {
++                    rc = LDAP_OPERATIONS_ERROR;
++                    goto done;
++                }
++                for (i = 0; (bv = lmod->mod_bvalues[i]) != NULL; i++) {
++                    if (strncasecmp("krbPrincipalAux",
++                                    bv->bv_val, bv->bv_len) == 0) {
++                        is_krb = 1;
++                    } else if (strncasecmp("sambaSamAccount",
++                                           bv->bv_val, bv->bv_len) == 0) {
++                        is_smb = 1;
++                    } else if (strncasecmp("ipaNTUserAttrs",
++                                           bv->bv_val, bv->bv_len) == 0) {
++                        is_ipant = 1;
++                    }
++                }
++
++                break;
++
++            case LDAP_MOD_DELETE:
++                /* can this happen for objectclasses ? */
++                is_krb = 0;
++                is_smb = 0;
++                is_ipant = 0;
++
++            default:
++                break;
++            }
++
++        } else if (slapi_attr_types_equivalent(lmod->mod_type,
++                                               "krbPrincipalKey")) {
++
++            /* if we are getting a krbPrincipalKey, also avoid regenerating
++             * the keys, it means kadmin has alredy done the job and is simply
++             * keeping userPassword and sambaXXPAssword in sync */
++
++            /* we also check we have enough authority */
++            if (is_root) {
++                has_krb_keys = 1;
++            }
++
++        } else if (slapi_attr_types_equivalent(lmod->mod_type,
++                                               "passwordHistory")) {
++
++            /* if we are getting a passwordHistory, also avoid regenerating
++             * the hashes, it means kadmin has alredy done the job and is
++             * simply keeping userPassword and sambaXXPAssword in sync */
++
++            /* we also check we have enough authority */
++            if (is_root) {
++                has_history = 1;
++            }
++        }
++
++        lmod = slapi_mods_get_next_mod(smods);
++    }
++
++    if (is_krb) {
++        if (has_krb_keys) {
++            gen_krb_keys = 0;
++        } else {
++            gen_krb_keys = 1;
++        }
++    }
++
++    /* It seem like we have determined that the end result will be deletion of
++     * the userPassword attribute, so we have no more business here */
++    if (! is_pwd_op) {
++        rc = LDAP_SUCCESS;
++        goto done;
++    }
++
++    /* Check this is a clear text password, or refuse operation (only if we need
++     * to comput other hashes */
++    if (! unhashedpw && (gen_krb_keys || is_smb || is_ipant)) {
++        if ('{' == userpw[0]) {
++            if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
++                unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]);
++                if (NULL == unhashedpw) {
++                    LOG_OOM();
++                    rc = LDAP_OPERATIONS_ERROR;
++                    goto done;
++                }
++                slapi_ch_free_string(&userpw);
++
++            } else if (slapi_is_encoded(userpw)) {
++
++                LOG("Pre-Encoded passwords are not valid\n");
++                errMesg = "Pre-Encoded passwords are not valid\n";
++                rc = LDAP_CONSTRAINT_VIOLATION;
++                goto done;
++            }
++        }
++    }
++
++    /* time to get the operation handler */
++    ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
++    if (ret != 0) {
++        LOG_FATAL("slapi_pblock_get failed!?\n");
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++
++    pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
++                                       op, ipapwd_op_ext_list.handle);
++    if (NULL == pwdop) {
++        rc = LDAP_OPERATIONS_ERROR;
++        goto done;
++    }
++
++    pwdop->is_krb = is_krb;
++    pwdop->pwd_op = IPAPWD_OP_MOD;
++    pwdop->pwdata.password = slapi_ch_strdup(unhashedpw);
++    pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL;
++    pwdop->skip_history = has_history;
++    pwdop->skip_keys = has_krb_keys;
++
++    if (is_root) {
++        pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
++    } else {
++        char *binddn;
++        Slapi_DN *bdn, *tdn;
++        int i;
++
++        /* Check Bind DN */
++        slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn);
++        bdn = slapi_sdn_new_dn_byref(binddn);
++        tdn = slapi_sdn_new_dn_byref(dn);
++
++        /* if the change is performed by someone else,
++         * it is an admin change that will require a new
++         * password change immediately as per our IPA policy */
++        if (slapi_sdn_compare(bdn, tdn)) {
++            pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN;
++
++            /* if it is a passsync manager we also need to skip resets */
++            for (i = 0; i < krbcfg->num_passsync_mgrs; i++) {
++                if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) {
++                    pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
++                    break;
++                }
++            }
++
++        }
++
++        slapi_sdn_free(&bdn);
++        slapi_sdn_free(&tdn);
++
++    }
++
++    pwdop->pwdata.dn = slapi_ch_strdup(dn);
++    pwdop->pwdata.timeNow = time(NULL);
++    pwdop->pwdata.target = e;
++
++    /* if krb keys are being set by an external agent we assume password
++     * policies have been properly checked already, so we check them only
++     * if no krb keys are available */
++    if (has_krb_keys == 0) {
++        ret = ipapwd_CheckPolicy(&pwdop->pwdata);
++        if (ret) {
++            errMesg = ipapwd_error2string(ret);
++            rc = LDAP_CONSTRAINT_VIOLATION;
++            goto done;
++        }
++    }
++
++    if (gen_krb_keys || is_smb || is_ipant) {
++
++        Slapi_Value **svals = NULL;
++        Slapi_Value **ntvals = NULL;
++        char *nt = NULL;
++        char *lm = NULL;
++
++        rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata, unhashedpw,
++                               gen_krb_keys, is_smb, is_ipant,
++                               &svals, &nt, &lm, &ntvals, &errMesg);
++        if (rc) {
++            goto done;
++        }
++
++        if (svals) {
++            /* replace values */
++            slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
++                                      "krbPrincipalKey", svals);
++            ipapwd_free_slapi_value_array(&svals);
++        }
++
++        if (lm && is_smb) {
++            /* replace value */
++            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                                  "sambaLMPassword", lm);
++            slapi_ch_free_string(&lm);
++        }
++        if (nt && is_smb) {
++            /* replace value */
++            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                                  "sambaNTPassword", nt);
++            slapi_ch_free_string(&nt);
++        }
++
++        if (ntvals && is_ipant) {
++            slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
++                                      "ipaNTHash", ntvals);
++            ipapwd_free_slapi_value_array(&ntvals);
++        }
++
++        if (is_smb) {
++            /* with samba integration we need to also set sambaPwdLastSet or
++             * samba will decide the user has to change the password again */
++            if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) {
++                /* if it is an admin change instead we need to let know to
++                * samba as well that the use rmust change its password */
++                slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L);
++            } else {
++                slapi_entry_attr_set_long(e, "sambaPwdLastset",
++                                      (long)pwdop->pwdata.timeNow);
++            }
++        }
++    }
++
++    rc = LDAP_SUCCESS;
++
++done:
++    free_ipapwd_krbcfg(&krbcfg);
++    slapi_ch_free_string(&userpw); /* just to be sure */
++    slapi_ch_free_string(&unhashedpw); /* we copied it to pwdop  */
++    if (e) slapi_entry_free(e); /* this is a copy in this function */
++    if (pwdop) pwdop->pwdata.target = NULL;
++
++    /* put back a, possibly modified, set of mods */
++    if (smods) {
++        mods = slapi_mods_get_ldapmods_passout(smods);
++        if (slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods)) {
++            LOG_FATAL("slapi_pblock_set failed!\n");
++            rc = LDAP_OPERATIONS_ERROR;
++        }
++        slapi_mods_free(&smods);
++    }
++
++    if (rc != LDAP_SUCCESS) {
++        slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
++        return -1;
++    }
++
++    return 0;
++}
++
++static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods,
++                               char *dn, struct slapi_entry *entry,
++                               struct ipapwd_krbcfg *krbcfg)
++{
++    Slapi_Attr *attr;
++    Slapi_Value *value;
++    const struct berval *val;
++    struct berval *ntvals[2] = { NULL, NULL };
++    struct berval bval;
++    krb5_key_data *keys;
++    int num_keys;
++    int mkvno;
++    int ret;
++    int i;
++
++    ret = slapi_entry_attr_find(entry, "ipaNTHash", &attr);
++    if (ret == 0) {
++        /* We refuse to regen if there is already a value */
++        return LDAP_CONSTRAINT_VIOLATION;
++    }
++
++    /* ok let's see if we can find the RC4 hash in the keys */
++    ret = slapi_entry_attr_find(entry, "krbPrincipalKey", &attr);
++    if (ret) {
++        return LDAP_UNWILLING_TO_PERFORM;
++    }
++
++    ret = slapi_attr_first_value(attr, &value);
++    if (ret) {
++        return LDAP_OPERATIONS_ERROR;
++    }
++
++    val = slapi_value_get_berval(value);
++    if (!val) {
++        return LDAP_OPERATIONS_ERROR;
++    }
++
++    ret = ber_decode_krb5_key_data((struct berval *)val,
++                                    &mkvno, &num_keys, &keys);
++    if (ret) {
++        return LDAP_OPERATIONS_ERROR;
++    }
++
++    ret = LDAP_UNWILLING_TO_PERFORM;
++
++    for (i = 0; i < num_keys; i++) {
++        char nthash[16];
++        krb5_enc_data cipher;
++        krb5_data plain;
++        krb5_int16 t;
++
++        if (keys[i].key_data_type[0] != ENCTYPE_ARCFOUR_HMAC) {
++            continue;
++        }
++
++        memcpy(&t, keys[i].key_data_contents[0], 2);
++        plain.length = le16toh(t);
++        if (plain.length != 16) {
++            continue;
++        }
++        plain.data = nthash;
++
++        memset(&cipher, 0, sizeof(krb5_enc_data));
++        cipher.enctype = krbcfg->kmkey->enctype;
++        cipher.ciphertext.length = keys[i].key_data_length[0] - 2;
++        cipher.ciphertext.data = ((char *)keys[i].key_data_contents[0]) + 2;
++
++        ret = krb5_c_decrypt(krbcfg->krbctx, krbcfg->kmkey,
++                             0, NULL, &cipher, &plain);
++        if (ret) {
++            ret = LDAP_OPERATIONS_ERROR;
++            break;
++        }
++
++        bval.bv_val = nthash;
++        bval.bv_len = 16;
++        ntvals[0] = &bval;
++
++        slapi_mods_add_modbvps(smods, LDAP_MOD_ADD, "ipaNTHash", ntvals);
++
++        ret = LDAP_SUCCESS;
++        break;
++    }
++
++    ipa_krb5_free_key_data(keys, num_keys);
++
++    return ret;
++}
++
++static int ipapwd_post_op(Slapi_PBlock *pb)
++{
++    void *op;
++    struct ipapwd_operation *pwdop = NULL;
++    Slapi_Mods *smods;
++    Slapi_Value **pwvals;
++    struct tm utctime;
++    char timestr[GENERALIZED_TIME_LENGTH+1];
++    int ret;
++    char *errMsg = "Internal operations error\n";
++    struct ipapwd_krbcfg *krbcfg = NULL;
++    char *principal = NULL;
++    Slapi_Value *ipahost;
++
++    LOG_TRACE("=>\n");
++
++    /* time to get the operation handler */
++    ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
++    if (ret != 0) {
++        LOG_FATAL("slapi_pblock_get failed!?\n");
++        return 0;
++    }
++
++    pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type,
++                                       op, ipapwd_op_ext_list.handle);
++    if (NULL == pwdop) {
++        LOG_FATAL("Internal error, couldn't find pluginextension ?!\n");
++        return 0;
++    }
++
++    /* not interesting */
++    if (IPAPWD_OP_NULL == pwdop->pwd_op)
++        return 0;
++
++    if ( ! (pwdop->is_krb)) {
++        LOG("Not a kerberos user, ignore krb attributes\n");
++        return 0;
++    }
++
++    if (pwdop->skip_keys && pwdop->skip_history) {
++        /* nothing to do, caller already set all interesting attributes */
++        return 0;
++    }
++
++    ret = ipapwd_gen_checks(pb, &errMsg, &krbcfg, 0);
++    if (ret != 0) {
++        LOG_FATAL("ipapwd_gen_checks failed!?\n");
++        return 0;
++    }
++
++    /* prepare changes that can be made only as root */
++    smods = slapi_mods_new();
++
++    /* This was a mod operation on an existing entry, make sure we also update
++     * the password history based on the entry we saved from the pre-op */
++    if (IPAPWD_OP_MOD == pwdop->pwd_op && !pwdop->skip_history) {
++        Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(pwdop->pwdata.dn);
++        if (tmp_dn) {
++            ret = slapi_search_internal_get_entry(tmp_dn, 0,
++                                                  &pwdop->pwdata.target,
++                                                  ipapwd_plugin_id);
++            slapi_sdn_free(&tmp_dn);
++            if (ret != LDAP_SUCCESS) {
++                LOG("Failed to retrieve entry?!\n");
++                goto done;
++            }
++        }
++        pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata);
++        if (pwvals) {
++            slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
++                                      "passwordHistory", pwvals);
++        }
++    }
++
++    /* we assume that krb attributes are properly updated too if keys were
++     * passed in */
++    if (!pwdop->skip_keys) {
++        /* Don't set a last password change or expiration on host passwords.
++         * krbLastPwdChange is used to tell whether we have a valid keytab.
++         * If we set it on userPassword it confuses enrollment.
++         * If krbPasswordExpiration is set on a host entry then the keytab
++         * will appear to be expired.
++         *
++         * When a host is issued a keytab these attributes get set properly by
++         * ipapwd_setkeytab().
++         */
++        ipahost = slapi_value_new_string("ipaHost");
++        if (!pwdop->pwdata.target ||
++            (slapi_entry_attr_has_syntax_value(pwdop->pwdata.target,
++                                    SLAPI_ATTR_OBJECTCLASS, ipahost)) == 0) {
++            /* set Password Expiration date */
++            if (!gmtime_r(&(pwdop->pwdata.expireTime), &utctime)) {
++                LOG_FATAL("failed to parse expiration date (buggy gmtime_r ?)\n");
++                goto done;
++            }
++            strftime(timestr, GENERALIZED_TIME_LENGTH+1,
++                     "%Y%m%d%H%M%SZ", &utctime);
++            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                                  "krbPasswordExpiration", timestr);
++
++            /* change Last Password Change field with the current date */
++            if (!gmtime_r(&(pwdop->pwdata.timeNow), &utctime)) {
++                LOG_FATAL("failed to parse current date (buggy gmtime_r ?)\n");
++                slapi_value_free(&ipahost);
++                goto done;
++            }
++            strftime(timestr, GENERALIZED_TIME_LENGTH+1,
++                     "%Y%m%d%H%M%SZ", &utctime);
++            slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
++                                  "krbLastPwdChange", timestr);
++        }
++        slapi_value_free(&ipahost);
++    }
++
++    ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods);
++    if (ret)
++        LOG("Failed to set additional password attributes in the post-op!\n");
++
++    if (!pwdop->skip_keys) {
++        if (pwdop->pwdata.changetype == IPA_CHANGETYPE_NORMAL) {
++            principal = slapi_entry_attr_get_charptr(pwdop->pwdata.target,
++                                                     "krbPrincipalName");
++        } else {
++            principal = slapi_ch_smprintf("root/admin@%s", krbcfg->realm);
++        }
++        ipapwd_set_extradata(pwdop->pwdata.dn, principal, pwdop->pwdata.timeNow);
++    }
++
++done:
++    if (pwdop && pwdop->pwdata.target) slapi_entry_free(pwdop->pwdata.target);
++    slapi_mods_free(&smods);
++    slapi_ch_free_string(&principal);
++    free_ipapwd_krbcfg(&krbcfg);
++    return 0;
++}
++
++/* PRE BIND Operation:
++ * Used for password migration from DS to IPA.
++ * Gets the clean text password, authenticates the user and generates
++ * a kerberos key if missing.
++ * Person to blame if anything blows up: Pavel Zuna <pzuna at redhat.com>
++ */
++static int ipapwd_pre_bind(Slapi_PBlock *pb)
++{
++    struct ipapwd_krbcfg *krbcfg = NULL;
++    struct ipapwd_data pwdata;
++    struct berval *credentials; /* bind credentials */
++    Slapi_Entry *entry = NULL;
++    Slapi_Value **pwd_values = NULL; /* values of userPassword attribute */
++    Slapi_Value *value = NULL;
++    Slapi_Attr *attr = NULL;
++    struct tm expire_tm;
++    char *errMesg = "Internal operations error\n"; /* error message */
++    char *expire = NULL; /* passwordExpirationTime attribute value */
++    char *dn = NULL; /* bind DN */
++    Slapi_Value *objectclass;
++    int method; /* authentication method */
++    int ret = 0;
++    char *principal = NULL;
++
++    LOG_TRACE("=>\n");
++
++    /* get BIND parameters */
++    ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn);
++    ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
++    ret |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &credentials);
++    if (ret) {
++        LOG_FATAL("slapi_pblock_get failed!?\n");
++        goto done;
++    }
++
++    /* we're only interested in simple authentication */
++    if (method != LDAP_AUTH_SIMPLE)
++        goto done;
++
++    /* list of attributes to retrieve */
++    const char *attrs_list[] = {SLAPI_USERPWD_ATTR, "krbprincipalkey", "uid",
++                                "krbprincipalname", "objectclass",
++                                "passwordexpirationtime",  "passwordhistory",
++                                NULL};
++
++    /* retrieve user entry */
++    ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list);
++    if (ret) {
++        LOG("failed to retrieve user entry: %s\n", dn);
++        goto done;
++    }
++
++    /* check the krbPrincipalName attribute is present */
++    ret = slapi_entry_attr_find(entry, "krbprincipalname", &attr);
++    if (ret) {
++        LOG("no krbPrincipalName in user entry: %s\n", dn);
++        goto done;
++    }
++
++    /* we aren't interested in host principals */
++    objectclass = slapi_value_new_string("ipaHost");
++    if ((slapi_entry_attr_has_syntax_value(entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) == 1) {
++        slapi_value_free(&objectclass);
++        goto done;
++    }
++    slapi_value_free(&objectclass);
++
++    /* check the krbPrincipalKey attribute is NOT present */
++    ret = slapi_entry_attr_find(entry, "krbprincipalkey", &attr);
++    if (!ret) {
++        LOG("kerberos key already present in user entry: %s\n", dn);
++        goto done;
++    }
++
++    /* retrieve userPassword attribute */
++    ret = slapi_entry_attr_find(entry, SLAPI_USERPWD_ATTR, &attr);
++    if (ret) {
++        LOG("no " SLAPI_USERPWD_ATTR " in user entry: %s\n", dn);
++        goto done;
++    }
++
++    /* get the number of userPassword values and allocate enough memory */
++    slapi_attr_get_numvalues(attr, &ret);
++    ret = (ret + 1) * sizeof (Slapi_Value *);
++    pwd_values = (Slapi_Value **) slapi_ch_malloc(ret);
++    if (!pwd_values) {
++        /* probably not required: should terminate the server anyway */
++        LOG_OOM();
++        goto done;
++    }
++    /* zero-fill the allocated memory; we need the array ending with NULL */
++    memset(pwd_values, 0, ret);
++
++    /* retrieve userPassword values */
++    ret = slapi_attr_first_value(attr, &value);
++    while (ret != -1) {
++        pwd_values[ret] = value;
++        ret = slapi_attr_next_value(attr, ret, &value);
++    }
++
++    /* check if BIND password and userPassword match */
++    value = slapi_value_new_berval(credentials);
++    ret = slapi_pw_find_sv(pwd_values, value);
++
++    /* free before checking ret; we might not get a chance later */
++    slapi_ch_free((void **) &pwd_values);
++    slapi_value_free(&value);
++
++    if (ret) {
++        LOG("invalid BIND password for user entry: %s\n", dn);
++        goto done;
++    }
++
++    /* general checks */
++    ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN);
++    if (ret) {
++        LOG_FATAL("Generic checks failed: %s", errMesg);
++        goto done;
++    }
++
++    /* delete userPassword - a new one will be generated later */
++    /* this is needed, otherwise ipapwd_CheckPolicy will think
++     * we're changing the password to its previous value
++     * and force a password change on next login  */
++    ret = slapi_entry_attr_delete(entry, SLAPI_USERPWD_ATTR);
++    if (ret) {
++        LOG_FATAL("failed to delete " SLAPI_USERPWD_ATTR "\n");
++        goto done;
++    }
++
++    /* prepare data for kerberos key generation */
++    memset(&pwdata, 0, sizeof (pwdata));
++    pwdata.dn = dn;
++    pwdata.target = entry;
++    pwdata.password = credentials->bv_val;
++    pwdata.timeNow = time(NULL);
++    pwdata.changetype = IPA_CHANGETYPE_NORMAL;
++
++    /* keep password expiration time from DS, if possible */
++    expire = slapi_entry_attr_get_charptr(entry, "passwordexpirationtime");
++    if (expire) {
++        memset(&expire_tm, 0, sizeof (expire_tm));
++        if (strptime(expire, "%Y%m%d%H%M%SZ", &expire_tm))
++            pwdata.expireTime = mktime(&expire_tm);
++    }
++
++    /* check password policy */
++    ret = ipapwd_CheckPolicy(&pwdata);
++    if (ret) {
++        /* Password fails to meet IPA password policy,
++         * force user to change his password next time he logs in. */
++        LOG("password policy check failed on user entry: %s"
++            " (force password change on next login)\n", dn);
++        pwdata.expireTime = time(NULL);
++    }
++
++    /* generate kerberos keys */
++    ret = ipapwd_SetPassword(krbcfg, &pwdata, 1);
++    if (ret) {
++        LOG("failed to set kerberos key for user entry: %s\n", dn);
++        goto done;
++    }
++
++    /* we need to make sure the ExtraData is set, otherwise kadmin
++     * will not like the object */
++    principal = slapi_entry_attr_get_charptr(entry, "krbPrincipalName");
++    if (!principal) {
++        LOG_OOM();
++        goto done;
++    }
++    ipapwd_set_extradata(pwdata.dn, principal, pwdata.timeNow);
++
++    LOG("kerberos key generated for user entry: %s\n", dn);
++
++done:
++    slapi_ch_free_string(&principal);
++    slapi_ch_free_string(&expire);
++    if (entry)
++        slapi_entry_free(entry);
++    free_ipapwd_krbcfg(&krbcfg);
++
++    return 0;
++}
++
++
++
++/* Init pre ops */
++int ipapwd_pre_init(Slapi_PBlock *pb)
++{
++    int ret;
++
++    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *)ipapwd_pre_bind);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *)ipapwd_pre_add);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod);
++
++    return ret;
++}
++
++int ipapwd_pre_init_betxn(Slapi_PBlock *pb)
++{
++    int ret;
++
++    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN, (void *)ipapwd_pre_add);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod);
++
++    return ret;
++}
++
++/* Init post ops */
++int ipapwd_post_init(Slapi_PBlock *pb)
++{
++    int ret;
++
++    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op);
++
++    return ret;
++}
++
++int ipapwd_post_init_betxn(Slapi_PBlock *pb)
++{
++    int ret;
++
++    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_op);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_op);
++
++    return ret;
++}
+-- 
+1.8.2.1
+
diff --git a/0006-Add-OTP-support-to-ipa-pwd-extop.patch b/0006-Add-OTP-support-to-ipa-pwd-extop.patch
new file mode 100644
index 0000000..f17abce
--- /dev/null
+++ b/0006-Add-OTP-support-to-ipa-pwd-extop.patch
@@ -0,0 +1,1711 @@
+From 62d32838e4c8b593a12e39ed282011d3859f7813 Mon Sep 17 00:00:00 2001
+From: Nathaniel McCallum <npmccallum at redhat.com>
+Date: Tue, 16 Apr 2013 16:00:09 -0400
+Subject: [PATCH 6/6] Add OTP support to ipa-pwd-extop
+
+During LDAP bind, this now plugin determines if a user is enabled
+for OTP authentication. If so, then the OTP is validated in addition
+to the password. This allows 2FA during user binds.
+
+    https://fedorahosted.org/freeipa/ticket/3367
+    http://freeipa.org/page/V3/OTP
+---
+ daemons/configure.ac                               |  39 +-
+ .../ipa-slapi-plugins/ipa-pwd-extop/Makefile.am    |  42 ++-
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c     | 398 +++++++++++++++++++++
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c   | 130 +++++++
+ .../ipa-pwd-extop/ipa_pwd_extop.c                  | 109 +++++-
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h   |  36 ++
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c      | 180 ++++++++++
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c  | 307 +++++++++++++++-
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c   |  82 +++++
+ daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c   | 103 ++++++
+ 10 files changed, 1368 insertions(+), 58 deletions(-)
+ create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c
+ create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c
+ create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c
+ create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c
+
+diff --git a/daemons/configure.ac b/daemons/configure.ac
+index 371c28d0493ebe88bc45d3b83def4b8d59461013..21d4e7a77c98e3dc7c630724b1124f1c213d0e6f 100644
+--- a/daemons/configure.ac
++++ b/daemons/configure.ac
+@@ -22,37 +22,10 @@ AM_CONDITIONAL([HAVE_GCC], [test "$ac_cv_prog_gcc" = yes])
+ AC_SUBST(VERSION)
+ 
+ dnl ---------------------------------------------------------------------------
+-dnl - Check for NSPR
++dnl - Check for NSPR/NSS
+ dnl ---------------------------------------------------------------------------
+-AC_CHECK_HEADER(nspr4/nspr.h)
+-AC_CHECK_HEADER(nspr/nspr.h)
+-if test "x$ac_cv_header_nspr4_nspr_h" = "xno" && test "x$ac_cv_header_nspr_nspr_h" = "xno" ; then
+-	AC_MSG_ERROR([Required NSPR header not available (nspr-devel)])
+-fi
+-if test "x$ac_cv_header_nspr4_nspr_h" = "xyes" ; then
+-	NSPR4="-I/usr/include/nspr4"
+-fi
+-if test "x$ac_cv_header_nspr_nspr_h" = "xyes" ; then
+-	NSPR4="-I/usr/include/nspr"
+-fi
+-
+-dnl ---------------------------------------------------------------------------
+-dnl - Check for NSS
+-dnl ---------------------------------------------------------------------------
+-SAVE_CPPFLAGS=$CPPFLAGS
+-CPPFLAGS=$NSPR4
+-AC_CHECK_HEADER(nss3/nss.h)
+-AC_CHECK_HEADER(nss/nss.h)
+-CPPFLAGS=$SAVE_CPPFLAGS
+-if test "x$ac_cv_header_nss3_nss_h" = "xno" && test "x$ac_cv_header_nss_nss_h" = "xno" ; then
+-	AC_MSG_ERROR([Required NSS header not available (nss-devel)])
+-fi
+-if test "x$ac_cv_header_nss3_nss_h" = "xyes" ; then
+-	NSS3="-I/usr/include/nss3"
+-fi
+-if test "x$ac_cv_header_nss_nss_h" = "xyes" ; then
+-	NSS3="-I/usr/include/nss"
+-fi
++PKG_CHECK_MODULES([NSPR], [nspr], [], [AC_MSG_ERROR([libnspr not found])])
++PKG_CHECK_MODULES([NSS], [nss], [], [AC_MSG_ERROR([libnss not found])])
+ 
+ dnl ---------------------------------------------------------------------------
+ dnl - Check for DS slapi plugin
+@@ -60,7 +33,7 @@ dnl ---------------------------------------------------------------------------
+ 
+ # Need to hack CPPFLAGS to be able to correctly detetct slapi-plugin.h
+ SAVE_CPPFLAGS=$CPPFLAGS
+-CPPFLAGS=$NSPR4
++CPPFLAGS=$NSPR_CFLAGS
+ AC_CHECK_HEADER(dirsrv/slapi-plugin.h)
+ if test "x$ac_cv_header_dirsrv_slapi-plugin_h" = "xno" ; then
+ 	AC_MSG_ERROR([Required 389-ds header not available (389-ds-base-devel)])
+@@ -96,7 +69,7 @@ dnl - Check for Mozilla LDAP and OpenLDAP SDK
+ dnl ---------------------------------------------------------------------------
+ 
+ SAVE_CPPFLAGS=$CPPFLAGS
+-CPPFLAGS="$NSPR4 $NSS3"
++CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS"
+ AC_CHECK_HEADER(svrcore.h)
+ AC_CHECK_HEADER(svrcore/svrcore.h)
+ if test "x$ac_cv_header_svrcore_h" = "xno" && test "x$ac_cv_header_svrcore_svrcore_h" = "xno" ; then
+@@ -144,7 +117,7 @@ AC_ARG_WITH([openldap],
+                             [compile plugins with openldap instead of mozldap])],
+             [], [])
+ 
+-LDAP_CFLAGS="${OPENLDAP_CFLAGS} $NSPR4 $NSS3 -DUSE_OPENLDAP"
++LDAP_CFLAGS="${OPENLDAP_CFLAGS} $NSPR_CFLAGS $NSS_CFLAGS -DUSE_OPENLDAP"
+ LDAP_LIBS="${OPENLDAP_LIBS}"
+ AC_DEFINE_UNQUOTED(WITH_OPENLDAP, 1, [Use OpenLDAP libraries])
+ 
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
+index 90f940fd3ad43e637743c8410023a6675792dea6..b53b2e1e445ccc9e756aa1ecb2656f19980cd001 100644
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
+@@ -1,7 +1,8 @@
+ NULL =
+ 
+-PLUGIN_COMMON_DIR=../common
+-KRB5_UTIL_DIR= ../../../util
++MAINTAINERCLEANFILES = *~ Makefile.in
++PLUGIN_COMMON_DIR = ../common
++KRB5_UTIL_DIR = ../../../util
+ KRB5_UTIL_SRCS = $(KRB5_UTIL_DIR)/ipa_krb5.c \
+ 		 $(KRB5_UTIL_DIR)/ipa_pwd.c \
+ 		 $(KRB5_UTIL_DIR)/ipa_pwd_ntlm.c
+@@ -23,13 +24,30 @@ AM_CPPFLAGS =							\
+ 	$(SSL_CFLAGS)						\
+ 	$(WARN_CFLAGS)						\
+ 	$(NULL)
++	
++AM_LDFLAGS = \
++	$(KRB5_LIBS)	\
++	$(SSL_LIBS)	\
++	$(LDAP_LIBS)	\
++	$(NSPR_LIBS)	\
++	$(NSS_LIBS)	\
++	-avoid-version	\
++	-export-symbols-regex ^ipapwd_init$
+ 
+-plugindir = $(libdir)/dirsrv/plugins
+-plugin_LTLIBRARIES = 			\
+-	libipa_pwd_extop.la		\
+-	$(NULL)
++# OTP Convenience Library and Tests
++noinst_LTLIBRARIES = libotp.la
++libotp_la_SOURCES = otp.c
++check_PROGRAMS = t_hotp t_totp
++t_hotp_LDADD = libotp.la
++t_totp_LDADD = libotp.la
++TESTS = $(check_PROGRAMS)
+ 
++# Plugin Binary
++plugindir = $(libdir)/dirsrv/plugins
++plugin_LTLIBRARIES = libipa_pwd_extop.la
++libipa_pwd_extop_la_LIBADD  = libotp.la
+ libipa_pwd_extop_la_SOURCES = 		\
++	auth.c				\
+ 	common.c			\
+ 	encoding.c			\
+ 	prepost.c			\
+@@ -37,14 +55,6 @@ libipa_pwd_extop_la_SOURCES = 		\
+ 	$(KRB5_UTIL_SRCS)		\
+ 	$(NULL)
+ 
+-libipa_pwd_extop_la_LDFLAGS = -avoid-version
+-
+-libipa_pwd_extop_la_LIBADD = 		\
+-	$(KRB5_LIBS)			\
+-	$(SSL_LIBS)			\
+-	$(LDAP_LIBS)			\
+-	$(NULL)
+-
+ appdir = $(IPA_DATA_DIR)
+ app_DATA =			\
+ 	pwd-extop-conf.ldif	\
+@@ -55,6 +65,4 @@ EXTRA_DIST =			\
+ 	$(app_DATA)		\
+ 	$(NULL)
+ 
+-MAINTAINERCLEANFILES =		\
+-	*~			\
+-	Makefile.in
++
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..ae47bab33cc924f9feb3db05b6da5bc094c21914
+--- /dev/null
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c
+@@ -0,0 +1,398 @@
++/** BEGIN COPYRIGHT BLOCK
++ * This Program is free software; you can redistribute it and/or modify it under
++ * the terms of the GNU General Public License as published by the Free Software
++ * Foundation; version 3 of the License.
++ *
++ * This Program is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
++ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
++ * Place, Suite 330, Boston, MA 02111-1307 USA.
++ *
++ * In addition, as a special exception, Red Hat, Inc. gives You the additional
++ * right to link the code of this Program with code not covered under the GNU
++ * General Public License ("Non-GPL Code") and to distribute linked combinations
++ * including the two, subject to the limitations in this paragraph. Non-GPL Code
++ * permitted under this exception must only link to the code of this Program
++ * through those well defined interfaces identified in the file named EXCEPTION
++ * found in the source code files (the "Approved Interfaces"). The files of
++ * Non-GPL Code may instantiate templates or use macros or inline functions from
++ * the Approved Interfaces without causing the resulting work to be covered by
++ * the GNU General Public License. Only Red Hat, Inc. may make changes or
++ * additions to the list of Approved Interfaces. You must obey the GNU General
++ * Public License in all respects for all of the Program code and other code used
++ * in conjunction with the Program except the Non-GPL Code covered by this
++ * exception. If you modify this file, you may extend this exception to your
++ * version of the file, but you are not obligated to do so. If you do not wish to
++ * provide this exception without modification, you must delete this exception
++ * statement from your version and license this file solely under the GPL without
++ * exception.
++ *
++ *
++ * Copyright (C) 2013 Red Hat, Inc.
++ * All rights reserved.
++ * END COPYRIGHT BLOCK **/
++
++#include "ipapwd.h"
++
++#define IPA_OTP_TOKEN_TOTP_OC "ipaTokenTOTP"
++#define IPA_OTP_DEFAULT_TOKEN_ALGORITHM "sha1"
++#define IPA_OTP_DEFAULT_TOKEN_OFFSET 0
++#define IPA_OTP_DEFAULT_TOKEN_STEP 30
++
++/*
++ * From otp.c
++ */
++bool ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits,
++                 uint64_t counter, uint32_t *out);
++
++bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits,
++                 time_t time, int offset, unsigned int step, uint32_t *out);
++
++/* From ipa_pwd_extop.c */
++extern void *ipapwd_plugin_id;
++
++/* Data types. */
++struct token {
++    struct {
++        uint8_t *data;
++        size_t len;
++    } key;
++    char *algo;
++    int len;
++    union {
++        struct {
++            uint64_t counter;
++        } hotp;
++        struct {
++            unsigned int step;
++            int offset;
++        } totp;
++    };
++    bool (*auth)(const struct token *token, uint32_t otp);
++};
++
++struct credentials {
++    struct token token;
++    Slapi_Value *ltp;
++    uint32_t otp;
++};
++
++static const char *valid_algos[] = { "sha1", "sha256", "sha384",
++                                     "sha512", NULL };
++
++static inline bool is_algo_valid(const char *algo)
++{
++    int i, ret;
++
++    for (i = 0; valid_algos[i]; i++) {
++        ret = strcasecmp(algo, valid_algos[i]);
++        if (ret == 0)
++            return true;
++    }
++
++    return false;
++}
++
++static const struct berval *entry_attr_get_berval(const Slapi_Entry* e,
++                                                  const char *type)
++{
++    Slapi_Attr* attr = NULL;
++    Slapi_Value *v;
++    int ret;
++
++    ret = slapi_entry_attr_find(e, type, &attr);
++    if (ret != 0 || attr == NULL)
++        return NULL;
++
++    ret = slapi_attr_first_value(attr, &v);
++    if (ret < 0)
++        return NULL;
++
++    return slapi_value_get_berval(v);
++}
++
++/* Authenticate a totp token. Return zero on success. */
++static bool auth_totp(const struct token *token, uint32_t otp)
++{
++    time_t times[5];
++    uint32_t val;
++    int i;
++
++    /* Get the token value for now and two steps in either direction. */
++    times[0] = time(NULL);
++    times[1] = times[0] + token->totp.step * 1;
++    times[2] = times[0] - token->totp.step * 1;
++    times[3] = times[0] + token->totp.step * 2;
++    times[4] = times[0] - token->totp.step * 2;
++    if (times[0] == -1)
++        return false;
++
++    /* Check all the times for a match. */
++    for (i = 0; i < sizeof(times) / sizeof(times[0]); i++) {
++        if (!ipapwd_totp(token->key.data, token->key.len, token->algo,
++                         token->len, times[i], token->totp.offset,
++                         token->totp.step, &val)) {
++            return false;
++        }
++
++        if (val == otp) {
++            return true;
++        }
++    }
++
++    return false;
++}
++
++static void token_free_contents(struct token *token)
++{
++    if (token == NULL)
++        return;
++
++    slapi_ch_free_string(&token->algo);
++    slapi_ch_free((void **) &token->key.data);
++}
++
++/* Decode an OTP token entry. Return zero on success. */
++static bool token_decode(Slapi_Entry *te, struct token *token)
++{
++    const struct berval *tmp;
++
++    /* Get key. */
++    tmp = entry_attr_get_berval(te, IPA_OTP_TOKEN_KEY_TYPE);
++    if (tmp == NULL) {
++        slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                        "token_decode: key not set for token \"%s\".\n",
++                        slapi_entry_get_ndn(te));
++        return false;
++    }
++    token->key.len = tmp->bv_len;
++    token->key.data = (void *) slapi_ch_malloc(token->key.len);
++    memcpy(token->key.data, tmp->bv_val, token->key.len);
++
++    /* Get length. */
++    token->len = slapi_entry_attr_get_int(te, IPA_OTP_TOKEN_LENGTH_TYPE);
++    if (token->len < 6 || token->len > 10) {
++        slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                        "token_decode: %s is not defined or invalid "
++                        "for token \"%s\".\n", IPA_OTP_TOKEN_LENGTH_TYPE,
++                        slapi_entry_get_ndn(te));
++        token_free_contents(token);
++        return false;
++    }
++
++    /* Get algorithm. */
++    token->algo = slapi_entry_attr_get_charptr(te,
++                                               IPA_OTP_TOKEN_ALGORITHM_TYPE);
++    if (token->algo == NULL)
++        token->algo = slapi_ch_strdup(IPA_OTP_DEFAULT_TOKEN_ALGORITHM);
++    if (!is_algo_valid(token->algo)) {
++        slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                        "token_decode: invalid token algorithm "
++                        "specified for token \"%s\".\n",
++                        slapi_entry_get_ndn(te));
++        token_free_contents(token);
++        return false;
++    }
++
++    /* Currently, we only support TOTP. */
++    token->auth = auth_totp;
++
++    /* Get offset. */
++    token->totp.offset = slapi_entry_attr_get_int(te,
++                                                  IPA_OTP_TOKEN_OFFSET_TYPE);
++    if (token->totp.offset == 0)
++        token->totp.offset = IPA_OTP_DEFAULT_TOKEN_OFFSET;
++
++    /* Get step. */
++    token->totp.step = slapi_entry_attr_get_uint(te, IPA_OTP_TOKEN_STEP_TYPE);
++    if (token->totp.step == 0)
++        token->totp.step = IPA_OTP_DEFAULT_TOKEN_STEP;
++
++    return true;
++}
++
++static void credentials_free_contents(struct credentials *credentials)
++{
++    if (!credentials)
++        return;
++
++    token_free_contents(&credentials->token);
++    slapi_value_free(&credentials->ltp);
++}
++
++/* Parse credentials and token entry. Return zero on success. */
++static bool credentials_parse(Slapi_Entry *te, struct berval *creds,
++                              struct credentials *credentials)
++{
++    char *tmp;
++    int len;
++
++    if (!token_decode(te, &credentials->token))
++        return false;
++
++    /* Is the credential too short?  If so, error. */
++    if (credentials->token.len >= creds->bv_len) {
++        slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                        "credentials_parse: supplied credential is less "
++                        "than or equal to %s for token \"%s\".\n",
++                        IPA_OTP_TOKEN_LENGTH_TYPE, slapi_entry_get_ndn(te));
++        token_free_contents(&credentials->token);
++        return false;
++    }
++
++    /* Extract the password from the supplied credential.  We hand the
++     * memory off to a Slapi_Value, so we don't want to directly free the
++     * string. */
++    len = creds->bv_len - credentials->token.len;
++    tmp = slapi_ch_calloc(len + 1, sizeof(char));
++    strncpy(tmp, creds->bv_val, len);
++    credentials->ltp = slapi_value_new_string_passin(tmp);
++
++    /* Extract the token value as a (minimum) 32-bit unsigned integer. */
++    tmp = slapi_ch_calloc(credentials->token.len + 1, sizeof(char));
++    strncpy(tmp, creds->bv_val + len, credentials->token.len);
++    credentials->otp = strtoul(tmp, NULL, 10);
++    slapi_ch_free_string(&tmp);
++
++    return true;
++}
++
++/*
++ * Attempts to perform OTP authentication for the passed in bind entry using
++ * the passed in credentials.
++ */
++bool ipapwd_do_otp_auth(Slapi_Entry *bind_entry, struct berval *creds)
++{
++    Slapi_PBlock *search_pb = NULL;
++    Slapi_Value **pwd_vals = NULL;
++    Slapi_Attr *pwd_attr = NULL;
++    Slapi_Entry **tokens = NULL;
++    Slapi_DN *base_sdn = NULL;
++    Slapi_Backend *be = NULL;
++    char *user_dn = NULL;
++    char *filter = NULL;
++    int pwd_numvals = 0;
++    bool ret = false;
++    int result = 0;
++    int hint = 0;
++    int i = 0;
++
++    search_pb = slapi_pblock_new();
++
++    /* Fetch the user DN. */
++    user_dn = slapi_entry_get_ndn(bind_entry);
++    if (user_dn == NULL) {
++        slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
++                        "ipapwd_do_otp_auth: error retrieving bind DN.\n");
++        goto done;
++    }
++
++    /* Search for TOTP tokens associated with this user.  We search for
++     * tokens who list this user as the owner in the same backend where
++     * the user entry is located. */
++    filter = slapi_ch_smprintf("(&(%s=%s)(%s=%s))", SLAPI_ATTR_OBJECTCLASS,
++                               IPA_OTP_TOKEN_TOTP_OC, IPA_OTP_TOKEN_OWNER_TYPE,
++                               user_dn);
++
++    be = slapi_be_select(slapi_entry_get_sdn(bind_entry));
++    if (be != NULL) {
++        base_sdn = (Slapi_DN *) slapi_be_getsuffix(be, 0);
++    }
++    if (base_sdn == NULL) {
++        slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
++                        "ipapwd_do_otp_auth: error determining the search "
++                        "base for user \"%s\".\n",
++                        user_dn);
++    }
++
++    slapi_search_internal_set_pb(search_pb, slapi_sdn_get_ndn(base_sdn),
++                                 LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL,
++                                 NULL, ipapwd_plugin_id, 0);
++
++    slapi_search_internal_pb(search_pb);
++    slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
++
++    if (LDAP_SUCCESS != result) {
++        slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
++                        "ipapwd_do_otp_auth: error searching for tokens "
++                        "associated with user \"%s\" (err=%d).\n",
++                        user_dn, result);
++        goto done;
++    }
++
++    slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &tokens);
++
++    if (tokens == NULL) {
++        /* This user has no associated tokens, so just bail out. */
++        goto done;
++    }
++
++    /* Fetch the userPassword values so we can perform the password checks
++     * when processing tokens below. */
++    if (slapi_entry_attr_find(bind_entry, SLAPI_USERPWD_ATTR, &pwd_attr) != 0 ||
++        slapi_attr_get_numvalues(pwd_attr, &pwd_numvals) != 0) {
++        slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                        "ipapwd_do_otp_auth: no passwords are set for user "
++                        "\"%s\".\n", user_dn);
++        goto done;
++    }
++
++    /* We need to create a Slapi_Value  array of the present password values
++     * for the compare function.  There's no nicer way of doing this. */
++    pwd_vals = (Slapi_Value **) slapi_ch_calloc(pwd_numvals,
++                                                sizeof(Slapi_Value *));
++
++    for (hint = slapi_attr_first_value(pwd_attr, &pwd_vals[i]); hint != -1;
++         hint = slapi_attr_next_value(pwd_attr, hint, &pwd_vals[i])) {
++        ++i;
++    }
++
++    /* Loop through each token and attempt to authenticate. */
++    for (i = 0; tokens && tokens[i]; i++) {
++        struct credentials credentials;
++
++        /* Parse the token entry and the credentials. */
++        if (!credentials_parse(tokens[i], creds, &credentials))
++            continue;
++
++        /* Check if the password portion of the credential is correct. */
++        i = slapi_pw_find_sv(pwd_vals, credentials.ltp);
++        if (i != 0) {
++            slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                            "ipapwd_do_otp_auth: password check failed when "
++                            "processing token \"%s\" for user \"%s\".\n",
++                            slapi_entry_get_ndn(tokens[i]), user_dn);
++            credentials_free_contents(&credentials);
++            continue;
++        }
++
++        /* Attempt to perform OTP authentication for this token. */
++        if (!credentials.token.auth(&credentials.token, credentials.otp)) {
++            slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                            "ipapwd_do_otp_auth: OTP auth failed when "
++                            "processing token \"%s\" for user \"%s\".\n",
++                            slapi_entry_get_ndn(tokens[i]), user_dn);
++            credentials_free_contents(&credentials);
++            continue;
++        }
++
++        /* Authentication successful! */
++        credentials_free_contents(&credentials);
++        slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                        "ipapwd_do_otp_auth: successfully "
++                        "authenticated user \"%s\" using token "
++                        "\"%s\".\n",
++                        user_dn, slapi_entry_get_ndn(tokens[i]));
++        ret = true;
++        break;
++    }
++
++done:
++    slapi_ch_free_string(&filter);
++    slapi_free_search_results_internal(search_pb);
++    slapi_pblock_destroy(search_pb);
++    return ret;
++}
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
+index bb1d96ade8c22bf60138a78957e409cf1b0de055..a54e91d87b5e700775b05dd2859c95abd739b626 100644
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
+@@ -40,6 +40,9 @@
+ #include "ipapwd.h"
+ #include "util.h"
+ 
++/* Attribute defines */
++#define IPA_OTP_USER_AUTH_TYPE "ipaUserAuthType"
++
+ /* Type of connection for this operation;*/
+ #define LDAP_EXTOP_PASSMOD_CONN_SECURE
+ 
+@@ -67,6 +70,133 @@ static const char *ipapwd_def_encsalts[] = {
+     NULL
+ };
+ 
++static PRInt32 g_allowed_auth_types = 0;
++
++/*
++ * Checks if an authentication type is allowed.  A NULL terminated
++ * list of allowed auth type values is passed in along with the flag
++ * for the auth type you are inquiring about.  If auth_type_list is
++ * NULL, the global config will be consulted.
++ */
++bool ipapwd_is_auth_type_allowed(char **auth_type_list, int auth_type)
++{
++    char *auth_type_value = NULL;
++    int i = 0;
++
++    /* Get the string value for the authentication type we are checking for. */
++    switch (auth_type) {
++    case IPA_OTP_AUTH_TYPE_OTP:
++        auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_OTP;
++        break;
++    case IPA_OTP_AUTH_TYPE_PASSWORD:
++        auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_PASSWORD;
++        break;
++    case IPA_OTP_AUTH_TYPE_PKINIT:
++        auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_PKINIT;
++        break;
++    default: /* Unknown type.*/
++        return false;
++    }
++
++    if (auth_type_list == NULL) {
++        /* Check if the requested authentication type is in the global list. */
++        PRInt32 auth_type_flags;
++
++        /* Do an atomic read of the allowed auth types bit field. */
++        auth_type_flags = PR_ATOMIC_ADD(&g_allowed_auth_types, 0);
++
++        /* Check if the flag for the desired auth type is set. */
++        return auth_type_flags & auth_type;
++    }
++
++    /* Check if the requested authentication type is in the user list. */
++    for (i = 0; auth_type_list[i]; i++) {
++        if (strcasecmp(auth_type_list[i], auth_type_value) == 0) {
++            return true;
++        }
++    }
++
++    return false;
++}
++
++/*
++ * Parses and validates an OTP config entry.  If apply is non-zero, then
++ * we will load and start using the new config.  You can simply
++ * validate config without making any changes by setting apply to false.
++ */
++bool ipapwd_parse_otp_config_entry(Slapi_Entry * e, bool apply)
++{
++    PRInt32 allowed_auth_types = 0;
++    PRInt32 default_auth_types = 0;
++    char **auth_types = NULL;
++
++    /* If no auth types are set, we default to only allowing password
++     * authentication.  Other authentication types can be allowed at the
++     * user level. */
++    default_auth_types |= IPA_OTP_AUTH_TYPE_PASSWORD;
++
++    if (e == NULL) {
++        /* There is no config entry, so just set the defaults. */
++        allowed_auth_types = default_auth_types;
++        goto done;
++    }
++
++    /* Parse and validate the config entry.  We currently tolerate invalid
++     * config settings, so there is no real validation performed.  We will
++     * likely want to reject invalid config as we expand the plug-in
++     * functionality, so the validation logic is here for us to use later. */
++
++    /* Fetch the auth type values from the config entry. */
++    auth_types = slapi_entry_attr_get_charray(e, IPA_OTP_USER_AUTH_TYPE);
++    if (auth_types == NULL) {
++        /* No allowed auth types are specified, so set the defaults. */
++        allowed_auth_types = default_auth_types;
++        goto done;
++    }
++
++    /* Check each type to see if it is set. */
++    if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_DISABLED)) {
++        allowed_auth_types |= IPA_OTP_AUTH_TYPE_DISABLED;
++    }
++
++    if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_PASSWORD)) {
++        allowed_auth_types |= IPA_OTP_AUTH_TYPE_PASSWORD;
++    }
++
++    if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_OTP)) {
++        allowed_auth_types |= IPA_OTP_AUTH_TYPE_OTP;
++    }
++
++    if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_PKINIT)) {
++        allowed_auth_types |= IPA_OTP_AUTH_TYPE_PKINIT;
++    }
++
++    if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_RADIUS)) {
++        allowed_auth_types |= IPA_OTP_AUTH_TYPE_RADIUS;
++    }
++
++    slapi_ch_array_free(auth_types);
++
++done:
++    if (apply) {
++        /* Atomically set the global allowed types. */
++        PR_ATOMIC_SET(&g_allowed_auth_types, allowed_auth_types);
++    }
++
++    return true;
++}
++
++bool ipapwd_otp_is_disabled(void)
++{
++    PRInt32 auth_type_flags;
++
++    /* Do an atomic read of the allowed auth types bit field. */
++    auth_type_flags = PR_ATOMIC_ADD(&g_allowed_auth_types, 0);
++
++    /* Check if the disabled bit is set. */
++    return auth_type_flags & IPA_OTP_AUTH_TYPE_DISABLED;
++}
++
+ static struct ipapwd_krbcfg *ipapwd_getConfig(void)
+ {
+     krb5_error_code krberr;
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
+index b64084e9d503733a797dacfb06471ad580a0c886..88cdcb10f4e13392f9dc81f9e13adf20c81e01f6 100644
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
+@@ -87,6 +87,30 @@ Slapi_PluginDesc ipapwd_plugin_desc = {
+ void *ipapwd_plugin_id;
+ static int usetxn = 0;
+ 
++static Slapi_DN *_ConfigAreaDN = NULL;
++static Slapi_DN *_PluginDN = NULL;
++static bool g_plugin_started = false;
++
++void *ipapwd_get_plugin_id(void)
++{
++    return ipapwd_plugin_id;
++}
++
++Slapi_DN *ipapwd_get_otp_config_area(void)
++{
++    return _ConfigAreaDN;
++}
++
++Slapi_DN *ipapwd_get_plugin_sdn(void)
++{
++    return _PluginDN;
++}
++
++bool ipapwd_get_plugin_started(void)
++{
++    return g_plugin_started;
++}
++
+ static int filter_keys(struct ipapwd_krbcfg *krbcfg,
+                        struct ipapwd_keyset *kset)
+ {
+@@ -1195,6 +1219,35 @@ Slapi_Filter *ipapwd_string2filter(char *strfilter)
+ 	return ret;
+ }
+ 
++/* Loads the OTP config entry, parses it, and applies it. */
++static inline bool ipapwd_load_otp_config(void)
++{
++    char *config_attrs[] = { IPA_USER_AUTH_TYPE, NULL };
++    Slapi_Entry *config_entry = NULL;
++    Slapi_DN *config_sdn = NULL;
++
++    /* If we are using an alternate config area, check it for our
++     * configuration, otherwise we just use our main plug-in config
++     * entry. */
++    if ((config_sdn = ipapwd_get_otp_config_area()) == NULL) {
++        config_sdn = ipapwd_get_plugin_sdn();
++    }
++
++    slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
++                    "Looking for config settings in \"%s\".\n",
++                    config_sdn ? slapi_sdn_get_ndn(config_sdn) : "null");
++
++    /* Fetch the config entry. */
++    slapi_search_internal_get_entry(config_sdn, config_attrs, &config_entry,
++                                    ipapwd_plugin_id);
++
++    /* Parse and apply the config. */
++    ipapwd_parse_otp_config_entry(config_entry, true);
++
++    slapi_entry_free(config_entry);
++    return true;
++}
++
+ /* Init data structs */
+ static int ipapwd_start( Slapi_PBlock *pb )
+ {
+@@ -1203,8 +1256,37 @@ static int ipapwd_start( Slapi_PBlock *pb )
+     char *realm = NULL;
+     char *config_dn;
+     Slapi_Entry *config_entry = NULL;
++    Slapi_DN *plugindn = NULL;
++    char *config_area = NULL;
+     int ret;
+ 
++    /* Check if we're already started */
++    if (g_plugin_started) {
++        return LDAP_SUCCESS;
++    }
++
++    /* Get the plug-in target dn from the system and store for future use. */
++    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &plugindn);
++    if (plugindn == NULL || slapi_sdn_get_ndn_len(plugindn) == 0) {
++        LOG_FATAL("No plugin dn?\n");
++        return LDAP_OPERATIONS_ERROR;
++    }
++    _PluginDN = slapi_sdn_dup(plugindn);
++
++    /* Set the alternate config area if one is defined. */
++    slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_AREA, &config_area);
++    if (config_area != NULL) {
++        _ConfigAreaDN = slapi_sdn_new_normdn_byval(config_area);
++    }
++
++    /*
++     * Load the config.
++     */
++    if (!ipapwd_load_otp_config()) {
++        LOG_FATAL("Unable to load plug-in config\n");
++        return LDAP_OPERATIONS_ERROR;
++    }
++
+     krberr = krb5_init_context(&krbctx);
+     if (krberr) {
+         LOG_FATAL("krb5_init_context failed\n");
+@@ -1273,6 +1355,7 @@ static int ipapwd_start( Slapi_PBlock *pb )
+     }
+ 
+     ret = LDAP_SUCCESS;
++    g_plugin_started = true;
+ 
+ done:
+     free(realm);
+@@ -1281,7 +1364,25 @@ done:
+     return ret;
+ }
+ 
++/* Clean up any resources allocated at startup. */
++static int ipapwd_close(Slapi_PBlock * pb)
++{
++    if (!g_plugin_started) {
++        goto done;
++    }
+ 
++    g_plugin_started = false;
++
++    /* We are not guaranteed that other threads are finished accessing
++     * PluginDN or ConfigAreaDN, so we don't want to free them.  This is
++     * only a one-time leak at shutdown, so it should be fine.
++     * slapi_sdn_free(&_PluginDN);
++     * slapi_sdn_free(&_ConfigAreaDN);
++     */
++
++done:
++    return 0;
++}
+ 
+ static char *ipapwd_oid_list[] = {
+ 	EXOP_PASSWD_OID,
+@@ -1328,12 +1429,13 @@ int ipapwd_init( Slapi_PBlock *pb )
+      * plug-in function that handles the operation identified by
+      * OID 1.3.6.1.4.1.4203.1.11.1 .  Also specify the version of the server
+      * plug-in */
+-    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
++    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03);
+     if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)ipapwd_start);
+     if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
+     if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list);
+     if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list);
+     if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)ipapwd_extop);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *)ipapwd_close);
+     if (ret) {
+         LOG("Failed to set plug-in version, function, and OID.\n" );
+         return -1;
+@@ -1361,5 +1463,10 @@ int ipapwd_init( Slapi_PBlock *pb )
+                           "IPA pwd post ops", NULL,
+                           ipapwd_plugin_id);
+ 
++    slapi_register_plugin("internalpostoperation", 1,
++                          "ipapwd_intpost_init", ipapwd_intpost_init,
++                          "IPA pwd internal post ops", NULL,
++                          ipapwd_plugin_id);
++
+     return 0;
+ }
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
+index 372441ddd11e4a0d8f4e4f709590ab5e89e5a7d4..74b63627689da9e519ec15d1e2020fa50ea7f75c 100644
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h
+@@ -76,6 +76,30 @@
+ #define IPA_CHANGETYPE_ADMIN 1
+ #define IPA_CHANGETYPE_DSMGR 2
+ 
++/*
++ * Attribute type defines
++ */
++#define IPA_USER_AUTH_TYPE           "ipaUserAuthType"
++#define IPA_OTP_TOKEN_OWNER_TYPE     "ipaTokenOwner"
++#define IPA_OTP_TOKEN_LENGTH_TYPE    "ipaTokenOTPDigits"
++#define IPA_OTP_TOKEN_KEY_TYPE       "ipaTokenOTPKey"
++#define IPA_OTP_TOKEN_ALGORITHM_TYPE "ipaTokenOTPAlgorithm"
++#define IPA_OTP_TOKEN_OFFSET_TYPE    "ipaTokenTOTPClockOffset"
++#define IPA_OTP_TOKEN_STEP_TYPE      "ipaTokenTOTPTimeStep"
++
++/* Authentication type defines */
++#define IPA_OTP_AUTH_TYPE_NONE     0
++#define IPA_OTP_AUTH_TYPE_DISABLED 1
++#define IPA_OTP_AUTH_TYPE_PASSWORD 2
++#define IPA_OTP_AUTH_TYPE_OTP      4
++#define IPA_OTP_AUTH_TYPE_PKINIT   8
++#define IPA_OTP_AUTH_TYPE_RADIUS   16
++#define IPA_OTP_AUTH_TYPE_VALUE_DISABLED "DISABLED"
++#define IPA_OTP_AUTH_TYPE_VALUE_PASSWORD "PASSWORD"
++#define IPA_OTP_AUTH_TYPE_VALUE_OTP      "OTP"
++#define IPA_OTP_AUTH_TYPE_VALUE_PKINIT   "PKINIT"
++#define IPA_OTP_AUTH_TYPE_VALUE_RADIUS   "RADIUS"
++
+ struct ipapwd_data {
+     Slapi_Entry *target;
+     char *dn;
+@@ -112,6 +136,9 @@ struct ipapwd_krbcfg {
+     bool allow_nt_hash;
+ };
+ 
++bool ipapwd_is_auth_type_allowed(char **auth_type_list, int auth_type);
++bool ipapwd_parse_otp_config_entry(Slapi_Entry * e, bool apply);
++bool ipapwd_otp_is_disabled(void);
+ int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
+                         int *is_root, int *is_krb, int *is_smb, int *is_ipant,
+                         char *attr, int access);
+@@ -152,6 +179,15 @@ int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg,
+ int ipapwd_ext_init(void);
+ int ipapwd_pre_init(Slapi_PBlock *pb);
+ int ipapwd_post_init(Slapi_PBlock *pb);
++int ipapwd_intpost_init(Slapi_PBlock *pb);
+ int ipapwd_pre_init_betxn(Slapi_PBlock *pb);
+ int ipapwd_post_init_betxn(Slapi_PBlock *pb);
+ 
++/* from ipa_pwd_extop.c */
++void *ipapwd_get_plugin_id(void);
++Slapi_DN *ipapwd_get_otp_config_area(void);
++Slapi_DN *ipapwd_get_plugin_sdn(void);
++bool ipapwd_get_plugin_started(void);
++
++/* from auth.c */
++bool ipapwd_do_otp_auth(Slapi_Entry *bind_entry, struct berval *creds);
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..6c0f8554b5ee3afd8e78333b30f56272048d8c4d
+--- /dev/null
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c
+@@ -0,0 +1,180 @@
++/** BEGIN COPYRIGHT BLOCK
++ * This Program is free software; you can redistribute it and/or modify it under
++ * the terms of the GNU General Public License as published by the Free Software
++ * Foundation; version 3 of the License.
++ *
++ * This Program is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
++ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
++ * Place, Suite 330, Boston, MA 02111-1307 USA.
++ *
++ * In addition, as a special exception, Red Hat, Inc. gives You the additional
++ * right to link the code of this Program with code not covered under the GNU
++ * General Public License ("Non-GPL Code") and to distribute linked combinations
++ * including the two, subject to the limitations in this paragraph. Non-GPL Code
++ * permitted under this exception must only link to the code of this Program
++ * through those well defined interfaces identified in the file named EXCEPTION
++ * found in the source code files (the "Approved Interfaces"). The files of
++ * Non-GPL Code may instantiate templates or use macros or inline functions from
++ * the Approved Interfaces without causing the resulting work to be covered by
++ * the GNU General Public License. Only Red Hat, Inc. may make changes or
++ * additions to the list of Approved Interfaces. You must obey the GNU General
++ * Public License in all respects for all of the Program code and other code used
++ * in conjunction with the Program except the Non-GPL Code covered by this
++ * exception. If you modify this file, you may extend this exception to your
++ * version of the file, but you are not obligated to do so. If you do not wish to
++ * provide this exception without modification, you must delete this exception
++ * statement from your version and license this file solely under the GPL without
++ * exception.
++ *
++ *
++ * Copyright (C) 2013 Red Hat, Inc.
++ * All rights reserved.
++ * END COPYRIGHT BLOCK **/
++
++/*
++ * This file contains an implementation of HOTP (RFC 4226) and TOTP (RFC 6238).
++ * For details of how these algorithms work, please see the relevant RFCs.
++ */
++
++#include <stdbool.h>
++#include <time.h>
++
++#include <nss.h>
++#include <pk11pub.h>
++#include <hasht.h>
++#include <prnetdb.h>
++
++struct digest_buffer {
++    uint8_t buf[SHA512_LENGTH];
++    unsigned int len;
++};
++
++static const struct {
++    const char *algo;
++    CK_MECHANISM_TYPE mech;
++} algo2mech[] = {
++    { "sha1",   CKM_SHA_1_HMAC },
++    { "sha256", CKM_SHA256_HMAC },
++    { "sha384", CKM_SHA384_HMAC },
++    { "sha512", CKM_SHA512_HMAC },
++    { }
++};
++
++/*
++ * This code is mostly cargo-cult taken from here:
++ *   http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn5.html
++ *
++ * It should implement HMAC with the given mechanism (SHA: 1, 256, 384, 512).
++ */
++static bool hmac(SECItem *key, CK_MECHANISM_TYPE mech, const SECItem *in,
++                 struct digest_buffer *out)
++{
++    SECItem param = { siBuffer, NULL, 0 };
++    PK11SlotInfo *slot = NULL;
++    PK11SymKey *symkey = NULL;
++    PK11Context *ctx = NULL;
++    bool ret = false;
++    SECStatus s;
++
++    slot = PK11_GetBestSlot(mech, NULL);
++    if (slot == NULL) {
++        slot = PK11_GetInternalKeySlot();
++        if (slot == NULL) {
++            goto done;
++        }
++    }
++
++    symkey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap,
++                               CKA_SIGN, key, NULL);
++    if (symkey == NULL)
++        goto done;
++
++    ctx = PK11_CreateContextBySymKey(mech, CKA_SIGN, symkey, &param);
++    if (ctx == NULL)
++        goto done;
++
++    s = PK11_DigestBegin(ctx);
++    if (s != SECSuccess)
++        goto done;
++
++    s = PK11_DigestOp(ctx, in->data, in->len);
++    if (s != SECSuccess)
++        goto done;
++
++    s = PK11_DigestFinal(ctx, out->buf, &out->len, sizeof(out->buf));
++    if (s != SECSuccess)
++        goto done;
++
++    ret = true;
++
++done:
++    if (ctx != NULL)
++        PK11_DestroyContext(ctx, PR_TRUE);
++    if (symkey != NULL)
++        PK11_FreeSymKey(symkey);
++    if (slot != NULL)
++        PK11_FreeSlot(slot);
++    return ret;
++}
++
++/*
++ * An implementation of HOTP (RFC 4226).
++ */
++bool ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits,
++                 uint64_t counter, uint32_t *out)
++{
++    const SECItem cntr = { siBuffer, (uint8_t *) &counter, sizeof(counter) };
++    SECItem keyitm = { siBuffer, (uint8_t *) key, len };
++    CK_MECHANISM_TYPE mech = CKM_SHA_1_HMAC;
++    PRUint64 offset, binary, div;
++    struct digest_buffer digest;
++    int i;
++
++    /* Convert counter to network byte order. */
++    counter = PR_htonll(counter);
++
++    /* Find the mech. */
++    for (i = 0; algo2mech[i].algo; i++) {
++        if (strcasecmp(algo2mech[i].algo, algo) == 0) {
++            mech = algo2mech[i].mech;
++            break;
++        }
++    }
++
++    /* Create the digits divisor. */
++    for (div = 1; digits > 0; digits--) {
++        div *= 10;
++    }
++
++    /* Do the digest. */
++    if (!hmac(&keyitm, mech, &cntr, &digest)) {
++        return false;
++    }
++
++    /* Truncate. */
++    offset  = digest.buf[digest.len - 1] & 0xf;
++    binary  = (digest.buf[offset + 0] & 0x7f) << 0x18;
++    binary |= (digest.buf[offset + 1] & 0xff) << 0x10;
++    binary |= (digest.buf[offset + 2] & 0xff) << 0x08;
++    binary |= (digest.buf[offset + 3] & 0xff) << 0x00;
++    binary  = binary % div;
++
++    *out = binary;
++    return true;
++}
++
++/*
++ * An implementation of TOTP (RFC 6238).
++ */
++bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits,
++                 time_t time, int offset, unsigned int step, uint32_t *out)
++{
++    if (step == 0)
++        return false;
++
++    return ipapwd_hotp(key, len, algo, digits, (time - offset) / step, out);
++}
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+index 0318cecdcc37690838ffc778817cd60c9a8376a0..8a222650cbd7348f419c8b697fa9b9784a66eb22 100644
+--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+@@ -67,6 +67,9 @@
+ #define IPAPWD_OP_ADD 1
+ #define IPAPWD_OP_MOD 2
+ 
++#define IPAPWD_OP_NOT_HANDLED 0
++#define IPAPWD_OP_HANDLED     1
++
+ extern Slapi_PluginDesc ipapwd_plugin_desc;
+ extern void *ipapwd_plugin_id;
+ extern const char *ipa_realm_tree;
+@@ -975,7 +978,77 @@ static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods,
+     return ret;
+ }
+ 
+-static int ipapwd_post_op(Slapi_PBlock *pb)
++/*
++ * Check if we want to process this operation.  We need to be
++ * sure that the operation succeeded.
++ */
++static bool ipapwd_otp_oktodo(Slapi_PBlock *pb)
++{
++    bool ok = false;
++    int oprc = 0;
++    int ret = 1;
++
++    ret = slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc);
++    if (ret != 0) {
++        LOG_FATAL("Could not get parameters\n");
++        goto done;
++    }
++
++    /* This plugin should only execute if the operation succeeded. */
++    ok = oprc == 0;
++
++done:
++    return ok;
++}
++
++static bool ipapwd_dn_is_otp_config(Slapi_DN *sdn)
++{
++    bool ret = false;
++    Slapi_DN *dn;
++
++    /* If an alternate config area is configured, it is considered to be
++     * the config entry, otherwise the main plug-in config entry is used. */
++    if (sdn != NULL) {
++        dn = ipapwd_get_otp_config_area();
++        if (dn == NULL)
++            dn = ipapwd_get_plugin_sdn();
++
++        ret = slapi_sdn_compare(sdn, dn) == 0;
++    }
++
++    return ret;
++}
++
++static int ipapwd_post_modadd_otp(Slapi_PBlock *pb)
++{
++    Slapi_Entry *config_entry = NULL;
++    Slapi_DN *sdn = NULL;
++
++    /* Just bail if we are not started yet, or if the operation failed. */
++    if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) {
++        goto done;
++    }
++
++    /* Check if a change affected our config entry and reload the
++     * in-memory config settings if needed. */
++    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
++    if (ipapwd_dn_is_otp_config(sdn)) {
++        /* The config entry was added or modified, so reload it from
++         * the post-op entry. */
++        slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &config_entry);
++        if (config_entry == NULL) {
++            LOG_FATAL("Unable to retrieve config entry.\n");
++            goto done;
++        }
++
++        ipapwd_parse_otp_config_entry(config_entry, true);
++    }
++
++done:
++    return 0;
++}
++
++static int ipapwd_post_modadd(Slapi_PBlock *pb)
+ {
+     void *op;
+     struct ipapwd_operation *pwdop = NULL;
+@@ -991,6 +1064,11 @@ static int ipapwd_post_op(Slapi_PBlock *pb)
+ 
+     LOG_TRACE("=>\n");
+ 
++    ret = ipapwd_post_modadd_otp(pb);
++    if (ret != 0) {
++        return ret;
++    }
++
+     /* time to get the operation handler */
+     ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+     if (ret != 0) {
+@@ -1111,6 +1189,202 @@ done:
+     return 0;
+ }
+ 
++static int ipapwd_post_modrdn_otp(Slapi_PBlock *pb)
++{
++    Slapi_Entry *config_entry = NULL;
++    Slapi_DN *new_sdn = NULL;
++    Slapi_DN *sdn = NULL;
++
++    /* Just bail if we are not started yet, or if the operation failed. */
++    if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) {
++        goto done;
++    }
++
++    /* Check if a change affected our config entry and reload the
++     * in-memory config settings if needed. */
++    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
++    if (ipapwd_dn_is_otp_config(sdn)) {
++        /* Our config entry was renamed.  We treat this like the entry
++         * was deleted, so just set the defaults. */
++        ipapwd_parse_otp_config_entry(NULL, true);
++    } else {
++        /* Check if an entry was renamed such that it has become our
++         * config entry.  If so, reload the config from this new entry. */
++        slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &config_entry);
++        if (config_entry == NULL) {
++            LOG_FATAL("Unable to retrieve renamed entry.\n");
++            goto done;
++        }
++
++        new_sdn = slapi_entry_get_sdn(config_entry);
++        if (new_sdn == NULL) {
++            LOG_FATAL("Unable to retrieve DN of renamed entry.\n");
++            goto done;
++        }
++
++        if (ipapwd_dn_is_otp_config(new_sdn)) {
++            ipapwd_parse_otp_config_entry(config_entry, true);
++        }
++    }
++
++done:
++    return 0;
++}
++
++static int ipapwd_post_del_otp(Slapi_PBlock *pb)
++{
++    Slapi_DN *sdn = NULL;
++    int ret = 0;
++
++    /* Just bail if we are not started yet, or if the operation failed. */
++    if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) {
++        goto done;
++    }
++
++    /* Check if a change affected our config entry and reload the
++     * in-memory config settings if needed. */
++    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
++    if (ipapwd_dn_is_otp_config(sdn)) {
++        /* The config entry was deleted, so this just sets the defaults. */
++        ipapwd_parse_otp_config_entry(NULL, true);
++    }
++
++done:
++    return ret;
++}
++
++/* Handle OTP authentication. */
++static int ipapwd_pre_bind_otp(Slapi_PBlock * pb)
++{
++    char *user_attrs[] = { IPA_USER_AUTH_TYPE, NULL };
++    int ret = IPAPWD_OP_NOT_HANDLED;
++    Slapi_Entry *bind_entry = NULL;
++    struct berval *creds = NULL;
++    const char *bind_dn = NULL;
++    Slapi_DN *bind_sdn = NULL;
++    int result = LDAP_SUCCESS;
++    char **auth_types = NULL;
++    int method;
++    int i;
++
++    /* If we didn't start successfully, bail. */
++    if (!ipapwd_get_plugin_started()) {
++        goto done;
++    }
++
++    /* If global disabled flag is set, just punt. */
++    if (ipapwd_otp_is_disabled()) {
++        goto done;
++    }
++
++    /* Retrieve parameters for bind operation. */
++    i = slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
++    if (i == 0) {
++        i = slapi_pblock_get(pb, SLAPI_BIND_TARGET_SDN, &bind_sdn);
++        if (i == 0) {
++            i = slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &creds);
++        }
++    }
++    if (i != 0) {
++        LOG_FATAL("Not handled (can't retrieve bind parameters)\n");
++        goto done;
++    }
++
++    bind_dn = slapi_sdn_get_dn(bind_sdn);
++
++    /* We only handle non-anonymous simple binds.  We just pass everything
++     * else through to the server. */
++    if (method != LDAP_AUTH_SIMPLE || *bind_dn == '\0' || creds->bv_len == 0) {
++        LOG_TRACE("Not handled (not simple bind or NULL dn/credentials)\n");
++        goto done;
++    }
++
++    /* Check if any allowed authentication types are set in the user entry.
++     * If not, we just use the global settings from the config entry. */
++    result = slapi_search_internal_get_entry(bind_sdn, user_attrs, &bind_entry,
++                                             ipapwd_get_plugin_id());
++    if (result != LDAP_SUCCESS) {
++        LOG_FATAL("Not handled (could not search for BIND dn %s - error "
++                  "%d : %s)\n", bind_dn, result, ldap_err2string(result));
++        goto done;
++    }
++    if (bind_entry == NULL) {
++        LOG_FATAL("Not handled (could not find entry for BIND dn %s)\n", bind_dn);
++        goto done;
++    }
++
++    i = slapi_check_account_lock(pb, bind_entry, 0, 0, 0);
++    if (i == 1) {
++        LOG_TRACE("Not handled (account %s inactivated.)\n", bind_dn);
++        goto done;
++    }
++
++    auth_types = slapi_entry_attr_get_charray(bind_entry, IPA_USER_AUTH_TYPE);
++
++    /*
++     * IMPORTANT SECTION!
++     *
++     * This section handles authentication logic, so be careful!
++     *
++     * The basic idea of this section is:
++     * 1. If OTP is enabled, try to use it first. If successful, send response.
++     * 2. If OTP was not enabled/successful, check if password is enabled.
++     * 3. If password is not enabled, send failure response.
++     * 4. Otherwise, fall through to standard server password authentication.
++     *
++     */
++
++    /* If OTP is allowed, attempt to do OTP authentication. */
++    if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_OTP)) {
++        LOG_PLUGIN_NAME(IPAPWD_PLUGIN_NAME,
++                        "Attempting OTP authentication for '%s'.\n", bind_dn);
++        if (ipapwd_do_otp_auth(bind_entry, creds)) {
++            /* FIXME - NGK - If the auth type request control was sent,
++             * construct the response control to indicate what auth type was
++             * used.  We might be able to do this in the
++             * SLAPI_PLUGIN_PRE_RESULT_FN callback instead of here. */
++
++            /* FIXME - NGK - What about other controls, like the pwpolicy
++             * control? If any other critical controls are set, we need to
++             * either process them properly or reject the operation with an
++             * unsupported critical control error. */
++
++            /* Send response approving authentication. */
++            slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
++            ret = IPAPWD_OP_HANDLED;
++        }
++    }
++
++    /* If OTP failed or was not enabled, we need to figure out if we can fall
++     * back to standard password authentication or give an error. */
++    if (ret != IPAPWD_OP_HANDLED) {
++        if (!ipapwd_is_auth_type_allowed(auth_types,
++                                         IPA_OTP_AUTH_TYPE_PASSWORD)) {
++            /* Password authentication is disabled, so we have failed. */
++            slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS,
++                                   NULL, NULL, 0, NULL);
++            ret = IPAPWD_OP_HANDLED;
++            goto done;
++        }
++
++        /* Password authentication is permitted, so tell the server that we
++         * didn't handle this request. Then the server will perform standard
++         * password authentication. */
++        LOG_PLUGIN_NAME(IPAPWD_PLUGIN_NAME,
++                        "Attempting PASSWORD authentication for \"%s\".\n",
++                        bind_dn);
++
++        /* FIXME - NGK - Do we need to figure out how to build
++         * the reponse control in this case?  Maybe we can use a
++         * SLAPI_PLUGIN_PRE_RESULT_FN callback to handle that? */
++    }
++
++done:
++    slapi_ch_array_free(auth_types);
++    slapi_entry_free(bind_entry);
++    return ret;
++}
++
+ /* PRE BIND Operation:
+  * Used for password migration from DS to IPA.
+  * Gets the clean text password, authenticates the user and generates
+@@ -1137,6 +1411,12 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
+ 
+     LOG_TRACE("=>\n");
+ 
++    /* Try to do OTP first. */
++    ret = ipapwd_pre_bind_otp(pb);
++    if (ret == IPAPWD_OP_HANDLED) {
++        return ret;
++    }
++
+     /* get BIND parameters */
+     ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn);
+     ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method);
+@@ -1295,8 +1575,6 @@ done:
+     return 0;
+ }
+ 
+-
+-
+ /* Init pre ops */
+ int ipapwd_pre_init(Slapi_PBlock *pb)
+ {
+@@ -1330,20 +1608,35 @@ int ipapwd_post_init(Slapi_PBlock *pb)
+ 
+     ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+     if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_modadd);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *)ipapwd_post_del_otp);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_modadd);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *)ipapwd_post_modrdn_otp);
+ 
+     return ret;
+ }
+ 
++int ipapwd_intpost_init(Slapi_PBlock *pb)
++{
++    int ret;
++
++    ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *)ipapwd_post_modadd_otp);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *)ipapwd_post_del_otp);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *)ipapwd_post_modadd_otp);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *)ipapwd_post_modrdn_otp);
++    return ret;
++}
++
+ int ipapwd_post_init_betxn(Slapi_PBlock *pb)
+ {
+     int ret;
+ 
+     ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+     if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_op);
+-    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_op);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_modadd);
++    if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_modadd);
+ 
+     return ret;
+ }
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..d57f9ab68bebb2c77f3bc327c50bdd6eb480f67e
+--- /dev/null
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c
+@@ -0,0 +1,82 @@
++/** BEGIN COPYRIGHT BLOCK
++ * This Program is free software; you can redistribute it and/or modify it under
++ * the terms of the GNU General Public License as published by the Free Software
++ * Foundation; version 3 of the License.
++ *
++ * This Program is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
++ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
++ * Place, Suite 330, Boston, MA 02111-1307 USA.
++ *
++ * In addition, as a special exception, Red Hat, Inc. gives You the additional
++ * right to link the code of this Program with code not covered under the GNU
++ * General Public License ("Non-GPL Code") and to distribute linked combinations
++ * including the two, subject to the limitations in this paragraph. Non-GPL Code
++ * permitted under this exception must only link to the code of this Program
++ * through those well defined interfaces identified in the file named EXCEPTION
++ * found in the source code files (the "Approved Interfaces"). The files of
++ * Non-GPL Code may instantiate templates or use macros or inline functions from
++ * the Approved Interfaces without causing the resulting work to be covered by
++ * the GNU General Public License. Only Red Hat, Inc. may make changes or
++ * additions to the list of Approved Interfaces. You must obey the GNU General
++ * Public License in all respects for all of the Program code and other code used
++ * in conjunction with the Program except the Non-GPL Code covered by this
++ * exception. If you modify this file, you may extend this exception to your
++ * version of the file, but you are not obligated to do so. If you do not wish to
++ * provide this exception without modification, you must delete this exception
++ * statement from your version and license this file solely under the GPL without
++ * exception.
++ *
++ *
++ * Copyright (C) 2013 Red Hat, Inc.
++ * All rights reserved.
++ * END COPYRIGHT BLOCK **/
++
++#include <assert.h>
++#include <stdbool.h>
++#include <stdint.h>
++#include <stddef.h>
++#include <time.h>
++#include <string.h>
++#include <nss.h>
++
++/*
++ * From otp.c
++ */
++bool ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits,
++                 uint64_t counter, uint32_t *out);
++
++/* All HOTP test examples from RFC 4226 (Appendix D). */
++static const uint8_t *key = (uint8_t *) "12345678901234567890";
++static const uint32_t answers[] = {
++    755224,
++    287082,
++    359152,
++    969429,
++    338314,
++    254676,
++    287922,
++    162583,
++    399871,
++    520489
++};
++
++int
++main(int argc, const char *argv[])
++{
++    uint32_t otp;
++    int i;
++
++    NSS_NoDB_Init(".");
++
++    for (i = 0; i < sizeof(answers) / sizeof(*answers); i++) {
++        assert(ipapwd_hotp(key, 20, "sha1", 6, i, &otp));
++        assert(otp == answers[i]);
++    }
++
++    NSS_Shutdown();
++    return 0;
++}
+diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..2df8d245818f90277ece273a8f0591538a4707a6
+--- /dev/null
++++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c
+@@ -0,0 +1,103 @@
++/** BEGIN COPYRIGHT BLOCK
++ * This Program is free software; you can redistribute it and/or modify it under
++ * the terms of the GNU General Public License as published by the Free Software
++ * Foundation; version 3 of the License.
++ *
++ * This Program is distributed in the hope that it will be useful, but WITHOUT
++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
++ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License along with
++ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
++ * Place, Suite 330, Boston, MA 02111-1307 USA.
++ *
++ * In addition, as a special exception, Red Hat, Inc. gives You the additional
++ * right to link the code of this Program with code not covered under the GNU
++ * General Public License ("Non-GPL Code") and to distribute linked combinations
++ * including the two, subject to the limitations in this paragraph. Non-GPL Code
++ * permitted under this exception must only link to the code of this Program
++ * through those well defined interfaces identified in the file named EXCEPTION
++ * found in the source code files (the "Approved Interfaces"). The files of
++ * Non-GPL Code may instantiate templates or use macros or inline functions from
++ * the Approved Interfaces without causing the resulting work to be covered by
++ * the GNU General Public License. Only Red Hat, Inc. may make changes or
++ * additions to the list of Approved Interfaces. You must obey the GNU General
++ * Public License in all respects for all of the Program code and other code used
++ * in conjunction with the Program except the Non-GPL Code covered by this
++ * exception. If you modify this file, you may extend this exception to your
++ * version of the file, but you are not obligated to do so. If you do not wish to
++ * provide this exception without modification, you must delete this exception
++ * statement from your version and license this file solely under the GPL without
++ * exception.
++ *
++ *
++ * Copyright (C) 2013 Red Hat, Inc.
++ * All rights reserved.
++ * END COPYRIGHT BLOCK **/
++
++#include <assert.h>
++#include <stdbool.h>
++#include <stdint.h>
++#include <stddef.h>
++#include <time.h>
++#include <string.h>
++#include <nss.h>
++
++/*
++ * From otp.c
++ */
++bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits,
++                 time_t time, int offset, unsigned int step, uint32_t *out);
++
++#define SHA1   "sha1",   (uint8_t *) "12345678901234567890",             20
++#define SHA256 "sha256", (uint8_t *) "12345678901234567890123456789012", 32
++#define SHA512 "sha512", (uint8_t *) "12345678901234567890123456789012" \
++                                     "34567890123456789012345678901234", 64
++
++/* All TOTP test examples from RFC 6238 (Appendix B). */
++const static struct {
++    const char *algo;
++    const uint8_t *key;
++    size_t len;
++    time_t time;
++    uint32_t answer;
++} tests[] = {
++    { SHA1,            59, 94287082 },
++    { SHA256,          59, 46119246 },
++    { SHA512,          59, 90693936 },
++    { SHA1,    1111111109,  7081804 },
++    { SHA256,  1111111109, 68084774 },
++    { SHA512,  1111111109, 25091201 },
++    { SHA1,    1111111111, 14050471 },
++    { SHA256,  1111111111, 67062674 },
++    { SHA512,  1111111111, 99943326 },
++    { SHA1,    1234567890, 89005924 },
++    { SHA256,  1234567890, 91819424 },
++    { SHA512,  1234567890, 93441116 },
++    { SHA1,    2000000000, 69279037 },
++    { SHA256,  2000000000, 90698825 },
++    { SHA512,  2000000000, 38618901 },
++#ifdef _LP64 /* Only do these tests on 64-bit systems. */
++    { SHA1,   20000000000, 65353130 },
++    { SHA256, 20000000000, 77737706 },
++    { SHA512, 20000000000, 47863826 },
++#endif
++};
++
++int
++main(int argc, const char *argv[])
++{
++    uint32_t otp;
++    int i;
++
++    NSS_NoDB_Init(".");
++
++    for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
++        assert(ipapwd_totp(tests[i].key, tests[i].len, tests[i].algo,
++                           8, tests[i].time, 0, 30, &otp));
++        assert(otp == tests[i].answer);
++    }
++
++    NSS_Shutdown();
++    return 0;
++}
+-- 
+1.8.2.1
+
diff --git a/freeipa-mkosek-407-set-krb5ccname-so-that-dirsrv-can-work-with-newer-kr.patch b/freeipa-mkosek-407-set-krb5ccname-so-that-dirsrv-can-work-with-newer-kr.patch
new file mode 100644
index 0000000..5fac7b6
--- /dev/null
+++ b/freeipa-mkosek-407-set-krb5ccname-so-that-dirsrv-can-work-with-newer-kr.patch
@@ -0,0 +1,76 @@
+From 1be93108c4c1506ea50879d645c47ab6843a6ee1 Mon Sep 17 00:00:00 2001
+From: Martin Kosek <mkosek at redhat.com>
+Date: Tue, 14 May 2013 18:36:50 +0200
+Subject: [PATCH] Set KRB5CCNAME so that dirsrv can work with newer krb5-server
+
+The DIR ccache format is now the default in krb5-server 1.11.2-4
+but /run/user/<uid> isn't created for Apache by anything so it
+has no ccache (and it doesn't have SELinux permissions to write here
+either).
+
+Use KRB5CCNAME to set a file path instead in /etc/sysconfig/dirsrv.
+
+https://fedorahosted.org/freeipa/ticket/3628
+---
+ install/tools/ipa-upgradeconfig |  1 +
+ ipaserver/install/dsinstance.py | 18 ++++++++++++++++++
+ 2 files changed, 19 insertions(+)
+
+diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig
+index 8fa9b189a2dc207e2d90ab32131e65fac0f1f9e0..8e9357f20fe7c9a88908def6a2e3b2104f07d73a 100644
+--- a/install/tools/ipa-upgradeconfig
++++ b/install/tools/ipa-upgradeconfig
+@@ -919,6 +919,7 @@ def main():
+     http.configure_httpd_ccache()
+ 
+     ds = dsinstance.DsInstance()
++    ds.configure_dirsrv_ccache()
+ 
+     fix_schema_file_syntax(ds)
+ 
+diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
+index e6bb054ddad4a0d91d76d4c79eb477913e8776aa..3b841417e717587675d3ac748ec02182b3e14672 100644
+--- a/ipaserver/install/dsinstance.py
++++ b/ipaserver/install/dsinstance.py
+@@ -26,6 +26,7 @@
+ import time
+ import tempfile
+ import base64
++import stat
+ 
+ from ipapython.ipa_log_manager import *
+ from ipapython import ipautil, sysrestore, dogtag, ipaldap
+@@ -213,6 +214,7 @@ def __common_setup(self, enable_ssl=False):
+         self.step("configuring certmap.conf", self.__certmap_conf)
+         self.step("configure autobind for root", self.__root_autobind)
+         self.step("configure new location for managed entries", self.__repoint_managed_entries)
++        self.step("configure dirsrv ccache", self.configure_dirsrv_ccache)
+         self.step("restarting directory server", self.__restart_instance)
+ 
+     def __common_post_setup(self):
+@@ -515,6 +517,22 @@ def __config_lockout_module(self):
+     def __repoint_managed_entries(self):
+         self._ldap_mod("repoint-managed-entries.ldif", self.sub_dict)
+ 
++    def configure_dirsrv_ccache(self):
++        pent = pwd.getpwnam("dirsrv")
++        ccache = '/tmp/krb5cc_%d' % pent.pw_uid
++        filepath = '/etc/sysconfig/dirsrv'
++        if not os.path.exists(filepath):
++            # file doesn't exist; create it with correct ownership & mode
++            open(filepath, 'a').close()
++            os.chmod(filepath,
++                stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
++            os.chown(filepath, 0, 0)
++
++        replacevars = {'KRB5CCNAME': ccache}
++        old_values = ipautil.backup_config_and_replace_variables(
++            self.fstore, filepath, replacevars=replacevars)
++        ipaservices.restore_context(filepath)
++
+     def __managed_entries(self):
+         self._ldap_mod("managed-entries.ldif", self.sub_dict)
+ 
+-- 
+1.8.1.4
+
diff --git a/freeipa.spec b/freeipa.spec
index b9def08..d8a4310 100644
--- a/freeipa.spec
+++ b/freeipa.spec
@@ -8,7 +8,7 @@
 
 Name:           freeipa
 Version:        3.2.0
-Release:        1%{?dist}
+Release:        2%{?dist}
 Summary:        The Identity, Policy and Audit system
 
 Group:          System Environment/Base
@@ -17,6 +17,14 @@ URL:            http://www.freeipa.org/
 Source0:        http://www.freeipa.org/downloads/src/freeipa-%{VERSION}.tar.gz
 BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
 
+Patch1:         0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch
+Patch2:         0002-Add-IPA-OTP-schema-and-ACLs.patch
+Patch3:         0003-ipa-kdb-Add-OTP-support.patch
+Patch4:         0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch
+Patch5:         0005-Remove-unnecessary-prefixes-from-ipa-pwd-extop-files.patch
+Patch6:         0006-Add-OTP-support-to-ipa-pwd-extop.patch
+Patch7:         freeipa-mkosek-407-set-krb5ccname-so-that-dirsrv-can-work-with-newer-kr.patch
+
 %if ! %{ONLY_CLIENT}
 BuildRequires:  389-ds-base-devel >= 1.3.1.0
 BuildRequires:  svrcore-devel
@@ -74,6 +82,8 @@ BuildRequires:  m2crypto
 BuildRequires:  check
 BuildRequires:  libsss_idmap-devel
 BuildRequires:  java-1.7.0-openjdk
+BuildRequires:  libverto-devel
+BuildRequires:  systemd
 
 # Find out Kerberos middle version to infer ABI changes in DAL driver
 # We cannot load DAL driver into KDC with wrong ABI.
@@ -689,6 +699,7 @@ fi
 %{_sbindir}/ipa-upgradeconfig
 %{_sbindir}/ipa-compliance
 %{_libexecdir}/certmonger/dogtag-ipa-retrieve-agent-submit
+%{_libexecdir}/ipa-otpd
 %{_sysconfdir}/cron.d/ipa-compliance
 %config(noreplace) %{_sysconfdir}/sysconfig/ipa_memcached
 %dir %attr(0700,apache,apache) %{_localstatedir}/run/ipa_memcached/
@@ -701,6 +712,8 @@ fi
 %{_libexecdir}/freeipa-systemd-upgrade
 # Fedora spec file only: END
 # END
+%attr(644,root,root) %{_unitdir}/ipa-otpd.socket
+%attr(644,root,root) %{_unitdir}/ipa-otpd at .service
 %dir %{python_sitelib}/ipaserver
 %dir %{python_sitelib}/ipaserver/install
 %dir %{python_sitelib}/ipaserver/install/plugins
@@ -906,6 +919,10 @@ fi
 %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
 
 %changelog
+* Tue May 14 2013 Rob Crittenden <rcritten at redhat.com> - 3.2.0-2
+- Add OTP patches
+- Add patch to set KRB5CCNAME for 389-ds-base
+
 * Fri May 10 2013 Rob Crittenden <rcritten at redhat.com> - 3.2.0-1
 - Update to upstream 3.2.0 GA
 - ipa-client-install fails if /etc/ipa does not exist (#961483)


More information about the scm-commits mailing list