diff --git a/.gitignore b/.gitignore index b17643f..502e642 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ /freeipa-3.1.2.tar.gz /freeipa-3.2.0.pre1.tar.gz /freeipa-3.2.0.tar.gz +/freeipa-3.2.2.tar.gz diff --git a/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch b/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch deleted file mode 100644 index 7c6a1ca..0000000 --- a/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch +++ /dev/null @@ -1,49 +0,0 @@ -From f807909bc12041af67b0051d4085b0ee1bee56d7 Mon Sep 17 00:00:00 2001 -From: Nathaniel McCallum -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/0001-Use-TLS-for-CA-replication.patch b/0001-Use-TLS-for-CA-replication.patch deleted file mode 100644 index f0337f3..0000000 --- a/0001-Use-TLS-for-CA-replication.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 98fde54c170eb7974afe80403d54747563c8e3be Mon Sep 17 00:00:00 2001 -From: Rob Crittenden -Date: Fri, 12 Oct 2012 14:35:43 -0400 -Subject: [PATCH] Use TLS for CA replication - -https://fedorahosted.org/freeipa/ticket/3162 ---- - ipaserver/install/cainstance.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py -index aabbba3..f2ac840 100644 ---- a/ipaserver/install/cainstance.py -+++ b/ipaserver/install/cainstance.py -@@ -640,7 +640,7 @@ class CAInstance(service.Service): - "pki_security_domain_hostname": self.master_host, - "pki_security_domain_https_port": "443", - "pki_security_domain_password": self.admin_password, -- "pki_clone_replication_security": "SSL", -+ "pki_clone_replication_security": "TLS", - "pki_clone_uri": \ - "https://%s" % ipautil.format_netloc(self.master_host, 443) - } --- -1.7.11.4 - diff --git a/0002-Add-IPA-OTP-schema-and-ACLs.patch b/0002-Add-IPA-OTP-schema-and-ACLs.patch deleted file mode 100644 index 01a2771..0000000 --- a/0002-Add-IPA-OTP-schema-and-ACLs.patch +++ /dev/null @@ -1,288 +0,0 @@ -From edca6946f81e01ddc5f3d5a8389560a704f11d7b Mon Sep 17 00:00:00 2001 -From: Nathaniel McCallum -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 deleted file mode 100644 index 7db9ac1..0000000 --- a/0003-ipa-kdb-Add-OTP-support.patch +++ /dev/null @@ -1,187 +0,0 @@ -From 5b15278283d3be6d615c98963807facf34da31eb Mon Sep 17 00:00:00 2001 -From: Nathaniel McCallum -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 deleted file mode 100644 index 70df0fc..0000000 --- a/0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch +++ /dev/null @@ -1,1998 +0,0 @@ -From 71fcffc0ef79c3dea590b6e49ff74f90f40b8c65 Mon Sep 17 00:00:00 2001 -From: Nathaniel McCallum -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@.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@.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@.service.in test.py -+systemdsystemunit_DATA = ipa-otpd.socket ipa-otpd@.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 -+ * -+ * 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 . -+ */ -+ -+/* -+ * 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 -+ * -+ * 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 . -+ */ -+ -+/* -+ * 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 -+ * -+ * 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 . -+ */ -+ -+#ifndef INTERNAL_H_ -+#define INTERNAL_H_ -+ -+#include "krad.h" -+ -+#include -+ -+#include -+ -+#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@.service.in b/daemons/ipa-otpd/ipa-otpd@.service.in -new file mode 100644 -index 0000000..b85d5a1 ---- /dev/null -+++ b/daemons/ipa-otpd/ipa-otpd@.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 -+ * -+ * 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 . -+ */ -+ -+/* -+ * 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 -+#include -+ -+/* 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, ": "); -+ 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 \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 -+ * -+ * 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 . -+ */ -+ -+/* -+ * This file parses the user's configuration received from LDAP (see query.c). -+ */ -+ -+#include "internal.h" -+#include -+ -+#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 -+ * -+ * 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 . -+ */ -+ -+/* -+ * 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 -+ -+#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 -+ * -+ * 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 . -+ */ -+ -+/* -+ * 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 -+ * -+ * 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 . -+ */ -+ -+/* -+ * 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 -+# -+# 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 . -+ -+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@.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 deleted file mode 100644 index dd06461..0000000 --- a/0005-Remove-unnecessary-prefixes-from-ipa-pwd-extop-files.patch +++ /dev/null @@ -1,5603 +0,0 @@ -From fe0b5a2cf772c3f85ca2c030b5be2dd0cd9c041b Mon Sep 17 00:00:00 2001 -From: Nathaniel McCallum -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 . -+ * -+ * 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 -+ * -+ * 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 : ""); -+ *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 : ""); -+ *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 . -+ * -+ * 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 -+ * -+ * Copyright (C) 2007-2010 Red Hat, Inc. -+ * All rights reserved. -+ * END COPYRIGHT BLOCK **/ -+ -+#ifdef HAVE_CONFIG_H -+# include -+#endif -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include -+ -+#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 . -- * -- * 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 -- * -- * 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 : ""); -- *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 : ""); -- *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 . -- * -- * 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 -- * -- * Copyright (C) 2007-2010 Red Hat, Inc. -- * All rights reserved. -- * END COPYRIGHT BLOCK **/ -- --#ifdef HAVE_CONFIG_H --# include --#endif -- --#include --#include --#include --#include --#include --#include -- --#include --#include --#include -- --#include -- --#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 . -- * -- * 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 -- * -- * Copyright (C) 2007-2010 Red Hat, Inc. -- * All rights reserved. -- * END COPYRIGHT BLOCK **/ -- --#ifdef HAVE_CONFIG_H --# include --#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 --#include --#include --#include --#include --#include --#include -- --#include --#include --#include --#include -- --#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 -- */ --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 . -+ * -+ * 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 -+ * -+ * Copyright (C) 2007-2010 Red Hat, Inc. -+ * All rights reserved. -+ * END COPYRIGHT BLOCK **/ -+ -+#ifdef HAVE_CONFIG_H -+# include -+#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 -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+#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 -+ */ -+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 deleted file mode 100644 index f17abce..0000000 --- a/0006-Add-OTP-support-to-ipa-pwd-extop.patch +++ /dev/null @@ -1,1711 +0,0 @@ -From 62d32838e4c8b593a12e39ed282011d3859f7813 Mon Sep 17 00:00:00 2001 -From: Nathaniel McCallum -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 -+#include -+ -+#include -+#include -+#include -+#include -+ -+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 -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* -+ * 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 -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* -+ * 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 deleted file mode 100644 index 5fac7b6..0000000 --- a/freeipa-mkosek-407-set-krb5ccname-so-that-dirsrv-can-work-with-newer-kr.patch +++ /dev/null @@ -1,76 +0,0 @@ -From 1be93108c4c1506ea50879d645c47ab6843a6ee1 Mon Sep 17 00:00:00 2001 -From: Martin Kosek -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/ 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 0eca909..3c01671 100644 --- a/freeipa.spec +++ b/freeipa.spec @@ -4,11 +4,11 @@ %global plugin_dir %{_libdir}/dirsrv/plugins %global POLICYCOREUTILSVER 2.1.14-37 %global gettext_domain ipa -%global VERSION 3.2.0 +%global VERSION 3.2.2 Name: freeipa -Version: 3.2.0 -Release: 2%{?dist} +Version: 3.2.2 +Release: 1%{?dist} Summary: The Identity, Policy and Audit system Group: System Environment/Base @@ -17,18 +17,9 @@ 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: 389-ds-base-devel >= 1.3.1.3 BuildRequires: svrcore-devel -BuildRequires: /usr/share/selinux/devel/Makefile BuildRequires: policycoreutils >= %{POLICYCOREUTILSVER} BuildRequires: systemd-units %if 0%{?fedora} >= 18 @@ -103,8 +94,7 @@ Group: System Environment/Base Requires: %{name}-python = %{version}-%{release} Requires: %{name}-client = %{version}-%{release} Requires: %{name}-admintools = %{version}-%{release} -Requires: %{name}-server-selinux = %{version}-%{release} -Requires: 389-ds-base >= 1.3.1.0 +Requires: 389-ds-base >= 1.3.1.3 Requires: openldap-clients > 2.4.35-4 Requires: nss >= 3.14.3-12.0 Requires: nss-tools >= 3.14.3-12.0 @@ -139,7 +129,7 @@ Requires: python-memcached Requires: systemd-units >= 38 Requires(pre): systemd-units Requires(post): systemd-units -Requires: selinux-policy >= 3.12.1-42 +Requires: selinux-policy >= 3.12.1-65 Requires(post): selinux-policy-base Requires: slapi-nis >= 0.44 Requires: pki-ca >= 10.0.2-5 @@ -155,7 +145,11 @@ Requires: zip Requires: policycoreutils >= %{POLICYCOREUTILSVER} Requires: tar Requires(pre): certmonger >= 0.65 -Requires(pre): 389-ds-base >= 1.3.1.0 +Requires(pre): 389-ds-base >= 1.3.1.3 + +# With FreeIPA 3.2.2, package freeipa-server-selinux was obsoleted as the +# entire SELinux policy is stored in the system policy +Obsoletes: freeipa-server-selinux < 3.2.2 # We have a soft-requires on bind. It is an optional part of # IPA but if it is configured we need a way to require versions @@ -186,22 +180,6 @@ to install this package (in other words, most people should NOT install this package). -%package server-selinux -Summary: SELinux rules for freeipa-server daemons -Group: System Environment/Base -Requires(post): %{name}-server = %{version}-%{release} -Requires(postun): %{name}-server = %{version}-%{release} -Requires(pre): policycoreutils >= %{POLICYCOREUTILSVER} - -Obsoletes: ipa-server-selinux >= 1.0 - -%description server-selinux -IPA is an integrated solution to provide centrally managed Identity (machine, -user, virtual machines, groups, authentication credentials), Policy -(configuration settings, access control information) and Audit (events, -logs, analysis thereof). This package provides SELinux rules for the -daemons included in freeipa-server - %package server-trust-ad Summary: Virtual package to install packages required for Active Directory trusts Group: System Environment/Base @@ -231,26 +209,28 @@ Requires(preun): %{_sbindir}/update-alternatives Cross-realm trusts with Active Directory in IPA require working Samba 4 installation. This package is provided for convenience to install all required dependencies at once. -# Fedora spec file only: START. Uncomment when Fedora 20 branches -# %package server-strict -# Summary: Strict package dependencies -# Group: System Environment/Base -# Requires(post): %{name}-server = %{version}-%{release} -# Requires(postun): %{name}-server = %{version}-%{release} -# -# # Specific requires -# Requires(pre): 389-ds-base = 1.3.0.5 -# Requires: krb5-server = 1.11.1 -# Requires: pki-ca = 10.0.1 -# -# %description server-strict -# IPA is an integrated solution to provide centrally managed Identity (machine, -# user, virtual machines, groups, authentication credentials), Policy -# (configuration settings, access control information) and Audit (events, -# logs, analysis thereof). This meta package adds strict version dependencies -# to known working versions. To upgrade to a non-approved version uninstall -# this package. +%if 0%{?fedora} == 19 +# Fedora spec file only: START +%package server-strict +Summary: Strict package dependencies +Group: System Environment/Base +Requires(post): %{name}-server = %{version}-%{release} +Requires(postun): %{name}-server = %{version}-%{release} + +# Specific requires +Requires(pre): 389-ds-base = 1.3.1.3 +Requires: krb5-server = 1.11.3 +Requires: pki-ca = 10.0.3 + +%description server-strict +IPA is an integrated solution to provide centrally managed Identity (machine, +user, virtual machines, groups, authentication credentials), Policy +(configuration settings, access control information) and Audit (events, +logs, analysis thereof). This meta package adds strict version dependencies +to known working versions. To upgrade to a non-approved version uninstall +this package. # Fedora spec file only: END +%endif %endif # ! %{ONLY_CLIENT} @@ -377,9 +357,6 @@ cd install; ../autogen.sh --prefix=%{_usr} --sysconfdir=%{_sysconfdir} --localst %if ! %{ONLY_CLIENT} make IPA_VERSION_IS_GIT_SNAPSHOT=no %{?_smp_mflags} all -cd selinux -# This isn't multi-process make capable yet -make all %else make IPA_VERSION_IS_GIT_SNAPSHOT=no %{?_smp_mflags} client %endif # ! %{ONLY_CLIENT} @@ -397,9 +374,6 @@ export SUPPORTED_PLATFORM=fedora16 rm -f ipapython/services.py %if ! %{ONLY_CLIENT} make install DESTDIR=%{buildroot} -cd selinux -make install DESTDIR=%{buildroot} -cd .. %else make client-install DESTDIR=%{buildroot} %endif # ! %{ONLY_CLIENT} @@ -430,7 +404,6 @@ rm %{buildroot}/%{_libdir}/samba/pdb/ipasam.la mkdir -p %{buildroot}/%{_sysconfdir}/ipa/html mkdir -p %{buildroot}/%{_localstatedir}/cache/ipa/sysrestore mkdir -p %{buildroot}/%{_localstatedir}/cache/ipa/sysupgrade -mkdir -p %{buildroot}/%{_localstatedir}/cache/ipa/pki-ca/publish mkdir %{buildroot}%{_usr}/share/ipa/html/ ln -s ../../../..%{_sysconfdir}/ipa/html/ffconfig.js \ %{buildroot}%{_usr}/share/ipa/html/ffconfig.js @@ -500,7 +473,6 @@ mkdir -p %{buildroot}/%{_localstatedir}/lib/ipa-client/sysrestore mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d install -pm 644 contrib/completion/ipa.bash_completion %{buildroot}%{_sysconfdir}/bash_completion.d/ipa mkdir -p %{buildroot}%{_sysconfdir}/cron.d -install -pm 644 ipa-compliance.cron %{buildroot}%{_sysconfdir}/cron.d/ipa-compliance (cd %{buildroot}/%{python_sitelib}/ipaserver && find . -type f | \ grep -v dcerpc | grep -v adtrustinstance | \ @@ -526,13 +498,22 @@ if [ $1 -gt 1 ] ; then /usr/libexec/freeipa-systemd-upgrade || : # Fedora spec file only: END /bin/systemctl condrestart certmonger.service 2>&1 || : - /usr/sbin/ipa-upgradeconfig --quiet >/dev/null || : fi %posttrans server # This must be run in posttrans so that updates from previous # execution that may no longer be shipped are not applied. /usr/sbin/ipa-ldap-updater --upgrade --quiet >/dev/null || : +/usr/sbin/ipa-upgradeconfig --quiet >/dev/null || : + +# Restart IPA processes. This must be also run in postrans so that plugins +# and software is in consistent state +python -c "import sys; from ipaserver.install import installutils; sys.exit(0 if installutils.is_ipa_configured() else 1);" > /dev/null 2>&1 +# NOTE: systemd specific section +if [ $? -eq 0 ]; then + /bin/systemctl try-restart ipa.service >/dev/null 2>&1 || : +fi +# END %preun server if [ $1 = 0 ]; then @@ -542,14 +523,6 @@ if [ $1 = 0 ]; then # END fi -%postun server -if [ "$1" -ge "1" ]; then -# NOTE: systemd specific section - /bin/systemctl --quiet is-active ipa.service >/dev/null && \ - /bin/systemctl try-restart ipa.service >/dev/null 2>&1 || : -# END -fi - %pre server # Stop ipa_kpasswd if it exists before upgrading so we don't have a # zombie process when we're done. @@ -559,48 +532,6 @@ if [ -e /usr/sbin/ipa_kpasswd ]; then # END fi -%pre server-selinux -if [ -s /etc/selinux/config ]; then - . %{_sysconfdir}/selinux/config - FILE_CONTEXT=%{_sysconfdir}/selinux/targeted/contexts/files/file_contexts - if [ "${SELINUXTYPE}" == targeted -a -f ${FILE_CONTEXT} ]; then \ - cp -f ${FILE_CONTEXT} ${FILE_CONTEXT}.%{name} - fi -fi - -%post server-selinux -semodule -s targeted -i /usr/share/selinux/targeted/ipa_httpd.pp /usr/share/selinux/targeted/ipa_dogtag.pp -. %{_sysconfdir}/selinux/config -FILE_CONTEXT=%{_sysconfdir}/selinux/targeted/contexts/files/file_contexts -selinuxenabled -if [ $? == 0 -a "${SELINUXTYPE}" == targeted -a -f ${FILE_CONTEXT}.%{name} ]; then - fixfiles -C ${FILE_CONTEXT}.%{name} restore - rm -f ${FILE_CONTEXT}.%name -fi - -%preun server-selinux -if [ $1 = 0 ]; then -if [ -s /etc/selinux/config ]; then - . %{_sysconfdir}/selinux/config - FILE_CONTEXT=%{_sysconfdir}/selinux/targeted/contexts/files/file_contexts - if [ "${SELINUXTYPE}" == targeted -a -f ${FILE_CONTEXT} ]; then \ - cp -f ${FILE_CONTEXT} ${FILE_CONTEXT}.%{name} - fi -fi -fi - -%postun server-selinux -if [ $1 = 0 ]; then -semodule -s targeted -r ipa_httpd ipa_dogtag -. %{_sysconfdir}/selinux/config -FILE_CONTEXT=%{_sysconfdir}/selinux/targeted/contexts/files/file_contexts -selinuxenabled -if [ $? == 0 -a "${SELINUXTYPE}" == targeted -a -f ${FILE_CONTEXT}.%{name} ]; then - fixfiles -C ${FILE_CONTEXT}.%{name} restore - rm -f ${FILE_CONTEXT}.%name -fi -fi - %postun server-trust-ad if [ "$1" -ge "1" ]; then if [ "`readlink %{_sysconfdir}/alternatives/winbind_krb5_locator.so`" == "/dev/null" ]; then @@ -611,6 +542,8 @@ fi %post server-trust-ad %{_sbindir}/update-alternatives --install %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so \ winbind_krb5_locator.so /dev/null 90 + +%posttrans server-trust-ad python -c "import sys; from ipaserver.install import installutils; sys.exit(0 if installutils.is_ipa_configured() else 1);" > /dev/null 2>&1 if [ $? -eq 0 ]; then # NOTE: systemd specific section @@ -697,10 +630,8 @@ fi %{_sbindir}/ipa-managed-entries %{_sbindir}/ipactl %{_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/ %dir %attr(0700,root,root) %{_localstatedir}/run/ipa/ @@ -813,9 +744,7 @@ fi %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysrestore %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysupgrade %attr(755,root,root) %dir %{_localstatedir}/lib/ipa/pki-ca -%attr(755,root,root) %dir %{_localstatedir}/lib/ipa/pki-ca/publish -%dir %{_localstatedir}/cache/ipa -%attr(700,apache,apache) %dir %{_localstatedir}/cache/ipa/sessions +%ghost %{_localstatedir}/lib/ipa/pki-ca/publish %attr(755,root,root) %{_libdir}/krb5/plugins/kdb/ipadb.so %{_mandir}/man1/ipa-replica-conncheck.1.gz %{_mandir}/man1/ipa-replica-install.1.gz @@ -832,16 +761,9 @@ fi %{_mandir}/man1/ipa-ldap-updater.1.gz %{_mandir}/man8/ipactl.8.gz %{_mandir}/man8/ipa-upgradeconfig.8.gz -%{_mandir}/man1/ipa-compliance.1.gz %{_mandir}/man1/ipa-backup.1.gz %{_mandir}/man1/ipa-restore.1.gz -%files server-selinux -%defattr(-,root,root,-) -%doc COPYING README Contributors.txt -%{_usr}/share/selinux/targeted/ipa_httpd.pp -%{_usr}/share/selinux/targeted/ipa_dogtag.pp - %files server-trust-ad %{_sbindir}/ipa-adtrust-install %attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so @@ -854,11 +776,13 @@ fi %{python_sitelib}/ipaserver/install/adtrustinstance* %ghost %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so -# Fedora spec file only: START. Uncomment when Fedora 20 branches -# %files server-strict -# %defattr(-,root,root,-) -# %doc COPYING README Contributors.txt +%if 0%{?fedora} == 19 +# Fedora spec file only: START +%files server-strict +%defattr(-,root,root,-) +%doc COPYING README Contributors.txt # Fedora spec file only: END +%endif %endif # ! %{ONLY_CLIENT} %files client @@ -919,6 +843,17 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt %changelog +* Wed Jul 17 2013 Martin Kosek - 3.2.2-1 +- Update to upstream 3.2.2 +- Drop freeipa-server-selinux subpackage +- Drop redundant directory /var/cache/ipa/sessions +- Do not create /var/lib/ipa/pki-ca/publish, retain reference as ghost +- Run ipa-upgradeconfig and server restart in posttrans to avoid inconsistency + issues when there are still old parts of software (like entitlements plugin) + +* Fri Jun 7 2013 Martin Kosek - 3.2.1-1 +- Update to upstream 3.2.1 + * Tue May 14 2013 Rob Crittenden - 3.2.0-2 - Add OTP patches - Add patch to set KRB5CCNAME for 389-ds-base @@ -948,6 +883,17 @@ fi - Update Requires on policycoreutils to 2.1.14-37 - Update Requires on selinux-policy to 3.12.1-42 - Update Requires on 389-ds-base to 1.3.1.0 +- Remove a Requires for java-atk-wrapper + +* Tue Apr 23 2013 Rob Crittenden - 3.2.0-0.4.beta1 +- Remove release from krb5-server in strict sub-package to allow for rebuilds. + +* Mon Apr 22 2013 Rob Crittenden - 3.2.0-0.3.beta1 +- Add a Requires for java-atk-wrapper until we can determine which package + should be pulling it in, dogtag or tomcat. + +* Tue Apr 16 2013 Rob Crittenden - 3.2.0-0.2.beta1 +- Update to upstream 3.2.0 Beta 1 * Tue Apr 2 2013 Martin Kosek - 3.2.0-0.1.pre1 - Update to upstream 3.2.0 Prerelease 1 diff --git a/sources b/sources index 3eb0015..6b672a2 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -e1ce2b1957e4248212de9ac3e95057f9 freeipa-3.2.0.tar.gz +e15e17e72b13361f5d023d7aee45a207 freeipa-3.2.2.tar.gz