[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, ¶m);
++ 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