diff --git a/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch b/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch new file mode 100644 index 0000000..7c6a1ca --- /dev/null +++ b/0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch @@ -0,0 +1,49 @@ +From f807909bc12041af67b0051d4085b0ee1bee56d7 Mon Sep 17 00:00:00 2001 +From: Nathaniel McCallum +Date: Thu, 11 Apr 2013 12:30:23 -0400 +Subject: [PATCH 1/6] Add ipaUserAuthType and ipaUserAuthTypeClass + +This schema addition will be useful for future commits. It allows us to +define permitted external authentication methods on both the user and +global config. The implementation is generic, but the immediate usage +is for otp support. + +https://fedorahosted.org/freeipa/ticket/3365 +http://freeipa.org/page/V3/OTP +--- + install/share/60basev3.ldif | 2 ++ + install/updates/10-60basev3.update | 4 ++++ + 2 files changed, 6 insertions(+) + +diff --git a/install/share/60basev3.ldif b/install/share/60basev3.ldif +index 43da2e7..435948f 100644 +--- a/install/share/60basev3.ldif ++++ b/install/share/60basev3.ldif +@@ -36,6 +36,7 @@ attributeTypes: (2.16.840.1.113730.3.8.11.36 NAME 'ipaSecondaryBaseRID' DESC 'Fi + # 2.16.840.1.113730.3.8.11.37 ipaKrbAuthzData + attributeTypes: (2.16.840.1.113730.3.8.11.38 NAME 'ipaNTSIDBlacklistIncoming' DESC 'Extra SIDs filtered out from incoming MS-PAC' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA v3') + attributeTypes: (2.16.840.1.113730.3.8.11.39 NAME 'ipaNTSIDBlacklistOutgoing' DESC 'Extra SIDs filtered out from outgoing MS-PAC' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA v3') ++attributeTypes: (2.16.840.1.113730.3.8.11.40 NAME 'ipaUserAuthType' DESC 'Allowed authentication methods' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3') + objectClasses: (2.16.840.1.113730.3.8.12.1 NAME 'ipaExternalGroup' SUP top STRUCTURAL MUST ( cn ) MAY ( ipaExternalMember $ memberOf $ description $ owner) X-ORIGIN 'IPA v3' ) + objectClasses: (2.16.840.1.113730.3.8.12.2 NAME 'ipaNTUserAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) MAY ( ipaNTHash $ ipaNTLogonScript $ ipaNTProfilePath $ ipaNTHomeDirectory $ ipaNTHomeDirectoryDrive ) X-ORIGIN 'IPA v3' ) + objectClasses: (2.16.840.1.113730.3.8.12.3 NAME 'ipaNTGroupAttrs' SUP top AUXILIARY MUST ( ipaNTSecurityIdentifier ) X-ORIGIN 'IPA v3' ) +@@ -51,3 +52,4 @@ objectClasses: (2.16.840.1.113730.3.8.12.14 NAME 'ipaIDobject' SUP top AUXILIARY + objectClasses: (2.16.840.1.113730.3.8.12.15 NAME 'ipaIDrange' ABSTRACT MUST ( cn $ ipaBaseID $ ipaIDRangeSize ) X-ORIGIN 'IPA v3' ) + objectClasses: (2.16.840.1.113730.3.8.12.16 NAME 'ipaDomainIDRange' SUP ipaIDrange STRUCTURAL MAY ( ipaBaseRID $ ipaSecondaryBaseRID ) X-ORIGIN 'IPA v3' ) + objectClasses: (2.16.840.1.113730.3.8.12.17 NAME 'ipaTrustedADDomainRange' SUP ipaIDrange STRUCTURAL MUST ( ipaBaseRID $ ipaNTTrustedDomainSID ) X-ORIGIN 'IPA v3' ) ++objectclasses: (2.16.840.1.113730.3.8.12.19 NAME 'ipaUserAuthTypeClass' SUP top AUXILIARY DESC 'Class for authentication methods definition' MAY ipaUserAuthType X-ORIGIN 'IPA v3') +diff --git a/install/updates/10-60basev3.update b/install/updates/10-60basev3.update +index 62dd472..476fa3b 100644 +--- a/install/updates/10-60basev3.update ++++ b/install/updates/10-60basev3.update +@@ -16,3 +16,7 @@ replace:objectClasses: (2.16.840.1.113730.3.8.4.1 NAME 'ipaHost' AUXILIARY MUST + + # Fix dc syntax (RFC 2247) + replace:attributeTypes:"( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domaincomponent' ) DESC 'Standard LDAP attribute type' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'RFC 2247' )::( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domaincomponent' ) DESC 'Standard LDAP attribute type' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN 'RFC 2247' )" ++ ++# Add ipaUserAuthType and ipaUserAuthTypeClass ++add:attributeTypes: (2.16.840.1.113730.3.8.11.40 NAME 'ipaUserAuthType' DESC 'Allowed authentication methods' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3') ++add:objectclasses: (2.16.840.1.113730.3.8.12.19 NAME 'ipaUserAuthTypeClass' SUP top AUXILIARY DESC 'Class for authentication methods definition' MAY ipaUserAuthType X-ORIGIN 'IPA v3') +-- +1.8.2.1 + diff --git a/0002-Add-IPA-OTP-schema-and-ACLs.patch b/0002-Add-IPA-OTP-schema-and-ACLs.patch new file mode 100644 index 0000000..01a2771 --- /dev/null +++ b/0002-Add-IPA-OTP-schema-and-ACLs.patch @@ -0,0 +1,288 @@ +From edca6946f81e01ddc5f3d5a8389560a704f11d7b Mon Sep 17 00:00:00 2001 +From: Nathaniel McCallum +Date: Thu, 11 Apr 2013 13:24:46 -0400 +Subject: [PATCH 2/6] Add IPA OTP schema and ACLs + +This commit adds schema support for two factor authentication via +OTP devices, including RADIUS or TOTP. This schema will be used +by future patches which will enable two factor authentication +directly. + +https://fedorahosted.org/freeipa/ticket/3365 +http://freeipa.org/page/V3/OTP +--- + install/share/70ipaotp.ldif | 28 +++++++++++++++++++++++ + install/share/Makefile.am | 1 + + install/share/copy-schema-to-ca.py | 1 + + install/share/default-aci.ldif | 10 +++++++- + install/updates/10-70ipaotp.update | 25 ++++++++++++++++++++ + install/updates/40-otp.update | 9 ++++++++ + install/updates/Makefile.am | 4 +++- + ipalib/constants.py | 1 + + ipaserver/install/dsinstance.py | 3 ++- + ipaserver/install/plugins/update_anonymous_aci.py | 25 ++++++++++++++------ + 10 files changed, 97 insertions(+), 10 deletions(-) + create mode 100644 install/share/70ipaotp.ldif + create mode 100644 install/updates/10-70ipaotp.update + create mode 100644 install/updates/40-otp.update + +diff --git a/install/share/70ipaotp.ldif b/install/share/70ipaotp.ldif +new file mode 100644 +index 0000000..3cfe872 +--- /dev/null ++++ b/install/share/70ipaotp.ldif +@@ -0,0 +1,28 @@ ++# IPA OTP schema ++# BaseOID: 2.16.840.1.113730.3.8.16 ++# See RFC 4517 for Syntax OID definitions ++dn: cn=schema ++attributeTypes: (2.16.840.1.113730.3.8.16.1.1 NAME 'ipatokenUniqueID' DESC 'Token Unique Identifier' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.2 NAME 'ipatokenDisabled' DESC 'Optionally marks token as Disabled' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.3 NAME 'ipatokenNotBefore' DESC 'Token validity date' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.4 NAME 'ipatokenNotAfter' DESC 'Token expiration date' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.5 NAME 'ipatokenVendor' DESC 'Optional Vendor identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.6 NAME 'ipatokenModel' DESC 'Optional Model identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.7 NAME 'ipatokenSerial' DESC 'OTP Token Serial number' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.8 NAME 'ipatokenOTPkey' DESC 'OTP Token Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.9 NAME 'ipatokenOTPalgorithm' DESC 'OTP Token Algorithm' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.10 NAME 'ipatokenOTPdigits' DESC 'OTP Token Number of digits' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.11 NAME 'ipatokenTOTPclockOffset' DESC 'TOTP clock offset' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.12 NAME 'ipatokenTOTPtimeStep' DESC 'TOTP time-step' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.13 NAME 'ipatokenOwner' DESC 'User entry that owns this token' SUP distinguishedName SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.14 NAME 'ipatokenRadiusUserName' DESC 'Corresponding Radius username' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.15 NAME 'ipatokenRadiusConfigLink' DESC 'Corresponding Radius Configuration link' SUP distinguishedName SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.16 NAME 'ipatokenRadiusServer' DESC 'Server String Configuration' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.17 NAME 'ipatokenRadiusSecret' DESC 'Server's Secret' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.18 NAME 'ipatokenRadiusTimeout' DESC 'Server Timeout' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries' DESC 'Number of allowed Retries' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++attributeTypes: (2.16.840.1.113730.3.8.16.1.20 NAME 'ipatokenUserMapAttribute' DESC 'Attribute to map from the user entry for RADIUS server authentication' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++objectClasses: (2.16.840.1.113730.3.8.16.2.1 NAME 'ipaToken' SUP top ABSTRACT DESC 'Abstract token class for tokens' MUST (ipatokenUniqueID) MAY (description $ ipatokenOwner $ ipatokenDisabled $ ipatokenNotBefore $ ipatokenNotAfter $ ipatokenVendor $ ipatokenModel $ ipatokenSerial) X-ORIGIN 'IPA OTP') ++objectClasses: (2.16.840.1.113730.3.8.16.2.2 NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MAY (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenTOTPclockOffset $ ipatokenTOTPtimeStep) X-ORIGIN 'IPA OTP') ++objectClasses: (2.16.840.1.113730.3.8.16.2.3 NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MUST (ipatokenRadiusConfigLink) MAY (ipatokenRadiusUserName) X-ORIGIN 'IPA OTP') ++objectClasses: (2.16.840.1.113730.3.8.16.2.4 NAME 'ipatokenRadiusConfiguration' SUP top STRUCTURAL DESC 'Proxy Radius Configuration' MUST (cn $ ipatokenRadiusServer $ ipatokenRadiusSecret) MAY (description $ ipatokenRadiusTimeout $ ipatokenRadiusRetries $ ipatokenUserMapAttribute) X-ORIGIN 'IPA OTP') +diff --git a/install/share/Makefile.am b/install/share/Makefile.am +index f8f9b74..8823723 100644 +--- a/install/share/Makefile.am ++++ b/install/share/Makefile.am +@@ -11,6 +11,7 @@ app_DATA = \ + 60ipadns.ldif \ + 61kerberos-ipav3.ldif \ + 65ipasudo.ldif \ ++ 70ipaotp.ldif \ + anonymous-vlv.ldif \ + bootstrap-template.ldif \ + caJarSigningCert.cfg.template \ +diff --git a/install/share/copy-schema-to-ca.py b/install/share/copy-schema-to-ca.py +index 4e2054e..1888f12 100755 +--- a/install/share/copy-schema-to-ca.py ++++ b/install/share/copy-schema-to-ca.py +@@ -31,6 +31,7 @@ SCHEMA_FILENAMES = ( + "60ipadns.ldif", + "61kerberos-ipav3.ldif", + "65ipasudo.ldif", ++ "70ipaotp.ldif", + "05rfc2247.ldif", + ) + +diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif +index f173f79..18881ec 100644 +--- a/install/share/default-aci.ldif ++++ b/install/share/default-aci.ldif +@@ -3,7 +3,7 @@ + dn: $SUFFIX + changetype: modify + add: aci +-aci: (target != "ldap:///idnsname=*,cn=dns,$SUFFIX")(targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey || userPKCS12 || ipaNTHash || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming")(version 3.0; acl "Enable Anonymous access"; allow (read, search, compare) userdn = "ldap:///anyone";) ++aci: (targetfilter = "(&(!(objectClass=ipaToken))(!(objectClass=ipatokenTOTP))(!(objectClass=ipatokenRadiusProxyUser))(!(objectClass=ipatokenRadiusConfiguration)))")(target != "ldap:///idnsname=*,cn=dns,$SUFFIX")(targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey || userPKCS12 || ipaNTHash || ipaNTTrustAuthOutgoing || ipaNTTrustAuthIncoming")(version 3.0; acl "Enable Anonymous access"; allow (read, search, compare) userdn = "ldap:///anyone";) + aci: (targetattr = "memberOf || memberHost || memberUser")(version 3.0; acl "No anonymous access to member information"; deny (read,search,compare) userdn != "ldap:///all";) + aci: (targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey || krbPrincipalName || krbCanonicalName || krbUPEnabled || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData || krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount || ipaUniqueId || memberOf || serverHostName || enrolledBy || ipaNTHash")(version 3.0; acl "Admin can manage any entry"; allow (all) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) + aci: (targetattr = "userpassword || krbprincipalkey || sambalmpassword || sambantpassword")(version 3.0; acl "selfservice:Self can write own password"; allow (write) userdn="ldap:///self";) +@@ -96,3 +96,11 @@ dn: cn=ipa,cn=etc,$SUFFIX + changetype: modify + add: aci + aci: (target="ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX")(targetattr="userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";) ++ ++# Let users manage their own tokens ++dn: $SUFFIX ++changetype: modify ++add: aci ++aci: (targetfilter = "(objectClass=ipaToken)")(targetattrs = "objectclass || ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can read basic token info"; allow (read, search, compare) userattr = "ipatokenOwner#USERDN";) ++aci: (targetfilter = "(objectClass=ipaToken)")(targetattrs = "ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can write basic token info"; allow (write) userattr = "ipatokenOwner#USERDN";) ++aci: (targetfilter = "(objectClass=ipatokenTOTP)")(targetattrs = "ipatokenOTPkey || ipatokenOTPalgorithm || ipatokenOTPdigits || ipatokenTOTPclockOffset || ipatokenTOTPtimeStep")(version 3.0; acl "Users can add TOTP token secrets"; allow (write, search) userattr = "ipatokenOwner#USERDN";) +diff --git a/install/updates/10-70ipaotp.update b/install/updates/10-70ipaotp.update +new file mode 100644 +index 0000000..600ef9c +--- /dev/null ++++ b/install/updates/10-70ipaotp.update +@@ -0,0 +1,25 @@ ++dn: cn=schema ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.1 NAME 'ipatokenUniqueID' DESC 'Token Unique Identifier' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.2 NAME 'ipatokenDisabled' DESC 'Optionally marks token as Disabled' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.3 NAME 'ipatokenNotBefore' DESC 'Token validity date' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.4 NAME 'ipatokenNotAfter' DESC 'Token expiration date' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.5 NAME 'ipatokenVendor' DESC 'Optional Vendor identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.6 NAME 'ipatokenModel' DESC 'Optional Model identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.7 NAME 'ipatokenSerial' DESC 'OTP Token Serial number' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.8 NAME 'ipatokenOTPkey' DESC 'OTP Token Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.9 NAME 'ipatokenOTPalgorithm' DESC 'OTP Token Algorithm' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.10 NAME 'ipatokenOTPdigits' DESC 'OTP Token Number of digits' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.11 NAME 'ipatokenTOTPclockOffset' DESC 'TOTP clock offset' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.12 NAME 'ipatokenTOTPtimeStep' DESC 'TOTP time-step' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.13 NAME 'ipatokenOwner' DESC 'User entry that owns this token' SUP distinguishedName SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.14 NAME 'ipatokenRadiusUserName' DESC 'Corresponding Radius username' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.15 NAME 'ipatokenRadiusConfigLink' DESC 'Corresponding Radius Configuration link' SUP distinguishedName SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.16 NAME 'ipatokenRadiusServer' DESC 'Server String Configuration' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.17 NAME 'ipatokenRadiusSecret' DESC 'Server's Secret' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.18 NAME 'ipatokenRadiusTimeout' DESC 'Server Timeout' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries' DESC 'Number of allowed Retries' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:attributeTypes: (2.16.840.1.113730.3.8.16.1.20 NAME 'ipatokenUserMapAttribute' DESC 'Attribute to map from the user entry for RADIUS server authentication' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP') ++add:objectClasses: (2.16.840.1.113730.3.8.16.2.1 NAME 'ipaToken' SUP top ABSTRACT DESC 'Abstract token class for tokens' MUST (ipatokenUniqueID) MAY (description $$ ipatokenOwner $$ ipatokenDisabled $$ ipatokenNotBefore $$ ipatokenNotAfter $$ ipatokenVendor $$ ipatokenModel $$ ipatokenSerial) X-ORIGIN 'IPA OTP') ++add:objectClasses: (2.16.840.1.113730.3.8.16.2.2 NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MAY (ipatokenOTPkey $$ ipatokenOTPalgorithm $$ ipatokenOTPdigits $$ ipatokenTOTPclockOffset $$ ipatokenTOTPtimeStep) X-ORIGIN 'IPA OTP') ++add:objectClasses: (2.16.840.1.113730.3.8.16.2.3 NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MUST (ipatokenRadiusConfigLink) MAY (ipatokenRadiusUserName) X-ORIGIN 'IPA OTP') ++add:objectClasses: (2.16.840.1.113730.3.8.16.2.4 NAME 'ipatokenRadiusConfiguration' SUP top STRUCTURAL DESC 'Proxy Radius Configuration' MUST (cn $$ ipatokenRadiusServer $$ ipatokenRadiusSecret) MAY (description $$ ipatokenRadiusTimeout $$ ipatokenRadiusRetries $$ ipatokenUserMapAttribute) X-ORIGIN 'IPA OTP') +diff --git a/install/updates/40-otp.update b/install/updates/40-otp.update +new file mode 100644 +index 0000000..ff36c87 +--- /dev/null ++++ b/install/updates/40-otp.update +@@ -0,0 +1,9 @@ ++dn: cn=otp,$SUFFIX ++default: objectClass: nsContainer ++default: objectClass: top ++default: cn: otp ++ ++dn: $SUFFIX ++add: aci:'(targetfilter = "(objectClass=ipaToken)")(targetattrs = "objectclass || ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can read basic token info"; allow (read, search, compare) userattr = "ipatokenOwner#USERDN";)' ++add: aci:'(targetfilter = "(objectClass=ipaToken)")(targetattrs = "ipatokenUniqueID || description || ipatokenOwner || ipatokenNotBefore || ipatokenNotAfter || ipatokenVendor || ipatokenModel || ipatokenSerial")(version 3.0; acl "Users can write basic token info"; allow (write) userattr = "ipatokenOwner#USERDN";)' ++add: aci:'(targetfilter = "(objectClass=ipatokenTOTP)")(targetattrs = "ipatokenOTPkey || ipatokenOTPalgorithm || ipatokenOTPdigits || ipatokenTOTPclockOffset || ipatokenTOTPtimeStep")(version 3.0; acl "Users can add TOTP token secrets"; allow (write, search) userattr = "ipatokenOwner#USERDN";)' +diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am +index ab3f411..787a51c 100644 +--- a/install/updates/Makefile.am ++++ b/install/updates/Makefile.am +@@ -4,6 +4,7 @@ appdir = $(IPA_DATA_DIR)/updates + app_DATA = \ + 10-60basev2.update \ + 10-60basev3.update \ ++ 10-70ipaotp.update \ + 10-RFC2307bis.update \ + 10-RFC4876.update \ + 10-config.update \ +@@ -13,6 +14,7 @@ app_DATA = \ + 10-ssh.update \ + 10-bind-schema.update \ + 10-uniqueness.update \ ++ 10-schema_compat.update \ + 19-managed-entries.update \ + 20-aci.update \ + 20-dna.update \ +@@ -20,7 +22,6 @@ app_DATA = \ + 20-indices.update \ + 20-nss_ldap.update \ + 20-replication.update \ +- 10-schema_compat.update \ + 20-user_private_groups.update \ + 20-winsync_index.update \ + 21-replicas_container.update \ +@@ -32,6 +33,7 @@ app_DATA = \ + 40-replication.update \ + 40-dns.update \ + 40-automember.update \ ++ 40-otp.update \ + 45-roles.update \ + 50-lockout-policy.update \ + 50-groupuuid.update \ +diff --git a/ipalib/constants.py b/ipalib/constants.py +index ecb9255..de08457 100644 +--- a/ipalib/constants.py ++++ b/ipalib/constants.py +@@ -109,6 +109,7 @@ DEFAULT_CONFIG = ( + ('container_dna', DN(('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))), + ('container_dna_posix_ids', DN(('cn', 'posix-ids'), ('cn', 'dna'), ('cn', 'ipa'), ('cn', 'etc'))), + ('container_realm_domains', DN(('cn', 'Realm Domains'), ('cn', 'ipa'), ('cn', 'etc'))), ++ ('container_otp', DN(('cn', 'otp'))), + + # Ports, hosts, and URIs: + # FIXME: let's renamed xmlrpc_uri to rpc_xml_uri +diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py +index e6bb054..7c809ec 100644 +--- a/ipaserver/install/dsinstance.py ++++ b/ipaserver/install/dsinstance.py +@@ -409,7 +409,8 @@ class DsInstance(service.Service): + "60basev3.ldif", + "60ipadns.ldif", + "61kerberos-ipav3.ldif", +- "65ipasudo.ldif"): ++ "65ipasudo.ldif", ++ "70ipaotp.ldif"): + target_fname = schema_dirname(self.serverid) + schema_fname + shutil.copyfile(ipautil.SHARE_DIR + schema_fname, target_fname) + os.chmod(target_fname, 0440) # read access for dirsrv user/group +diff --git a/ipaserver/install/plugins/update_anonymous_aci.py b/ipaserver/install/plugins/update_anonymous_aci.py +index 2b7446a..1e75113 100644 +--- a/ipaserver/install/plugins/update_anonymous_aci.py ++++ b/ipaserver/install/plugins/update_anonymous_aci.py +@@ -20,8 +20,6 @@ + from copy import deepcopy + from ipaserver.install.plugins import FIRST, LAST + from ipaserver.install.plugins.baseupdate import PostUpdate +-#from ipalib.frontend import Updater +-#from ipaserver.install.plugins import baseupdate + from ipalib import api + from ipalib.aci import ACI + from ipalib.plugins import aci +@@ -37,6 +35,8 @@ class update_anonymous_aci(PostUpdate): + aciname = u'Enable Anonymous access' + aciprefix = u'none' + ldap = self.obj.backend ++ targetfilter = '(&(!(objectClass=ipaToken))(!(objectClass=ipatokenTOTP))(!(objectClass=ipatokenRadiusProxyUser))(!(objectClass=ipatokenRadiusConfiguration)))' ++ filter = None + + (dn, entry_attrs) = ldap.get_entry(api.env.basedn, ['aci']) + +@@ -45,6 +45,9 @@ class update_anonymous_aci(PostUpdate): + rawaci = aci._find_aci_by_name(acilist, aciprefix, aciname) + + attrs = rawaci.target['targetattr']['expression'] ++ rawfilter = rawaci.target.get('targetfilter', None) ++ if rawfilter is not None: ++ filter = rawfilter['expression'] + + update_attrs = deepcopy(attrs) + +@@ -54,12 +57,10 @@ class update_anonymous_aci(PostUpdate): + needed_attrs.append(attr) + + update_attrs.extend(needed_attrs) +- if len(attrs) == len(update_attrs): ++ if (len(attrs) == len(update_attrs) and ++ filter == targetfilter): + root_logger.debug("Anonymous ACI already update-to-date") + return (False, False, []) +- else: +- root_logger.debug("New Anonymous ACI attributes needed: %s", +- needed_attrs) + + for tmpaci in acistrs: + candidate = ACI(tmpaci) +@@ -67,7 +68,17 @@ class update_anonymous_aci(PostUpdate): + acistrs.remove(tmpaci) + break + +- rawaci.target['targetattr']['expression'] = update_attrs ++ if len(attrs) != len(update_attrs): ++ root_logger.debug("New Anonymous ACI attributes needed: %s", ++ needed_attrs) ++ ++ rawaci.target['targetattr']['expression'] = update_attrs ++ ++ if filter != targetfilter: ++ root_logger.debug("New Anonymous ACI targetfilter needed.") ++ ++ rawaci.set_target_filter(targetfilter) ++ + acistrs.append(unicode(rawaci)) + entry_attrs['aci'] = acistrs + +-- +1.8.2.1 + diff --git a/0003-ipa-kdb-Add-OTP-support.patch b/0003-ipa-kdb-Add-OTP-support.patch new file mode 100644 index 0000000..7db9ac1 --- /dev/null +++ b/0003-ipa-kdb-Add-OTP-support.patch @@ -0,0 +1,187 @@ +From 5b15278283d3be6d615c98963807facf34da31eb Mon Sep 17 00:00:00 2001 +From: Nathaniel McCallum +Date: Thu, 11 Apr 2013 13:50:42 -0400 +Subject: [PATCH 3/6] ipa-kdb: Add OTP support + +If OTP is enabled for a user, then: + 1. Long-term keys are not provided to KDB + 2. The user string 'otp' is defined to KDB + +Since it is not secure to send radius configuration information +over krb5 user strings, we simply set the string to a known default +('[]') which enables the default configuration in the KDC. + +https://fedorahosted.org/freeipa/ticket/3561 +http://freeipa.org/page/V3/OTP +--- + daemons/ipa-kdb/ipa_kdb.c | 38 +++++++++++++++++++++++++++++++++++- + daemons/ipa-kdb/ipa_kdb.h | 13 ++++++++++++ + daemons/ipa-kdb/ipa_kdb_principals.c | 28 ++++++++++++++++++++++++++ + 3 files changed, 78 insertions(+), 1 deletion(-) + +diff --git a/daemons/ipa-kdb/ipa_kdb.c b/daemons/ipa-kdb/ipa_kdb.c +index e5c718e..8464264 100644 +--- a/daemons/ipa-kdb/ipa_kdb.c ++++ b/daemons/ipa-kdb/ipa_kdb.c +@@ -173,9 +173,42 @@ done: + return base; + } + ++static const struct { ++ const char *name; ++ enum ipadb_user_auth flag; ++} userauth_table[] = { ++ { "disabled", IPADB_USER_AUTH_DISABLED }, ++ { "password", IPADB_USER_AUTH_PASSWORD }, ++ { "radius", IPADB_USER_AUTH_RADIUS }, ++ { "otp", IPADB_USER_AUTH_OTP }, ++ { } ++}; ++ ++void ipadb_get_user_auth(LDAP *lcontext, LDAPMessage *le, ++ enum ipadb_user_auth *userauth) ++{ ++ struct berval **vals; ++ int i, j; ++ ++ *userauth = IPADB_USER_AUTH_EMPTY; ++ vals = ldap_get_values_len(lcontext, le, IPA_USER_AUTH_TYPE); ++ if (!vals) ++ return; ++ ++ for (i = 0; vals[i]; i++) { ++ for (j = 0; userauth_table[j].name; j++) { ++ if (strcasecmp(vals[i]->bv_val, userauth_table[j].name) == 0) { ++ *userauth |= userauth_table[j].flag; ++ break; ++ } ++ } ++ } ++} ++ + int ipadb_get_global_configs(struct ipadb_context *ipactx) + { +- char *attrs[] = { "ipaConfigString", IPA_KRB_AUTHZ_DATA_ATTR, NULL }; ++ char *attrs[] = { "ipaConfigString", IPA_KRB_AUTHZ_DATA_ATTR, ++ IPA_USER_AUTH_TYPE, NULL }; + struct berval **vals = NULL; + LDAPMessage *res = NULL; + LDAPMessage *first; +@@ -203,6 +236,9 @@ int ipadb_get_global_configs(struct ipadb_context *ipactx) + goto done; + } + ++ /* Check for permitted authentication types. */ ++ ipadb_get_user_auth(ipactx->lcontext, res, &ipactx->user_auth); ++ + vals = ldap_get_values_len(ipactx->lcontext, first, + "ipaConfigString"); + if (!vals || !vals[0]) { +diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h +index 9daaab8..54869d8 100644 +--- a/daemons/ipa-kdb/ipa_kdb.h ++++ b/daemons/ipa-kdb/ipa_kdb.h +@@ -75,9 +75,18 @@ + #define IPA_SETUP "ipa-setup-override-restrictions" + + #define IPA_KRB_AUTHZ_DATA_ATTR "ipaKrbAuthzData" ++#define IPA_USER_AUTH_TYPE "ipaUserAuthType" + + struct ipadb_mspac; + ++enum ipadb_user_auth { ++ IPADB_USER_AUTH_EMPTY = 0, ++ IPADB_USER_AUTH_DISABLED = 1 << 0, ++ IPADB_USER_AUTH_PASSWORD = 1 << 1, ++ IPADB_USER_AUTH_RADIUS = 1 << 2, ++ IPADB_USER_AUTH_OTP = 1 << 3, ++}; ++ + struct ipadb_context { + char *uri; + char *base; +@@ -92,6 +101,7 @@ struct ipadb_context { + bool disable_last_success; + bool disable_lockout; + char **authz_data; ++ enum ipadb_user_auth user_auth; + }; + + #define IPA_E_DATA_MAGIC 0x0eda7a +@@ -259,3 +269,6 @@ void ipadb_audit_as_req(krb5_context kcontext, + krb5_timestamp authtime, + krb5_error_code error_code); + ++/* AUTH METHODS */ ++void ipadb_get_user_auth(LDAP *lcontext, LDAPMessage *le, ++ enum ipadb_user_auth *user_auth); +diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c +index 11c155e..3566e1e 100644 +--- a/daemons/ipa-kdb/ipa_kdb_principals.c ++++ b/daemons/ipa-kdb/ipa_kdb_principals.c +@@ -64,6 +64,7 @@ static char *std_principal_attrs[] = { + "nsaccountlock", + "passwordHistory", + IPA_KRB_AUTHZ_DATA_ATTR, ++ IPA_USER_AUTH_TYPE, + + "objectClass", + NULL +@@ -228,6 +229,9 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, + krb5_db_entry **kentry, + uint32_t *polmask) + { ++ krb5_octet otp_string[] = {'o', 't', 'p', 0, '[', ']', 0 }; ++ enum ipadb_user_auth user_ua = IPADB_USER_AUTH_EMPTY; ++ enum ipadb_user_auth *active_ua = &user_ua; + struct ipadb_context *ipactx; + LDAP *lcontext; + krb5_db_entry *entry; +@@ -262,6 +266,17 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, + entry->magic = KRB5_KDB_MAGIC_NUMBER; + entry->len = KRB5_KDB_V1_BASE_LENGTH; + ++ /* Get the user's user_auth settings. */ ++ ipadb_get_user_auth(ipactx->lcontext, lentry, &user_ua); ++ ++ /* TODO: Should we confirm the existence of ipatokenRadiusConfigLink in ++ * the case of RADIUS? Existence of a token for OTP? */ ++ ++ /* Determine which user_auth policy is active: user or global. */ ++ if ((ipactx->user_auth & IPADB_USER_AUTH_DISABLED) ++ || user_ua == IPADB_USER_AUTH_EMPTY) ++ active_ua = &ipactx->user_auth; ++ + /* ignore mask for now */ + + ret = ipadb_ldap_attr_to_int(lcontext, lentry, +@@ -393,6 +408,13 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, + &res_key_data, &result, &mkvno); + switch (ret) { + case 0: ++ /* Only set a principal's key if password auth should be used. */ ++ if ((*active_ua & ~IPADB_USER_AUTH_DISABLED) != IPADB_USER_AUTH_EMPTY ++ && !(*active_ua & IPADB_USER_AUTH_PASSWORD)) { ++ /* This is the same behavior as ENOENT below. */ ++ break; ++ } ++ + entry->key_data = res_key_data; + entry->n_key_data = result; + if (mkvno) { +@@ -515,6 +537,12 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, + ied->authz_data = authz_data_list; + } + ++ /* If enabled, set the otp user string, enabling otp. */ ++ if ((*active_ua & (IPADB_USER_AUTH_RADIUS | IPADB_USER_AUTH_OTP)) && ++ !(*active_ua & IPADB_USER_AUTH_DISABLED)) { ++ ret = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS, ++ sizeof(otp_string), otp_string); ++ } + + kerr = 0; + +-- +1.8.2.1 + diff --git a/0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch b/0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch new file mode 100644 index 0000000..70df0fc --- /dev/null +++ b/0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch @@ -0,0 +1,1998 @@ +From 71fcffc0ef79c3dea590b6e49ff74f90f40b8c65 Mon Sep 17 00:00:00 2001 +From: Nathaniel McCallum +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 new file mode 100644 index 0000000..dd06461 --- /dev/null +++ b/0005-Remove-unnecessary-prefixes-from-ipa-pwd-extop-files.patch @@ -0,0 +1,5603 @@ +From fe0b5a2cf772c3f85ca2c030b5be2dd0cd9c041b Mon Sep 17 00:00:00 2001 +From: Nathaniel McCallum +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 new file mode 100644 index 0000000..f17abce --- /dev/null +++ b/0006-Add-OTP-support-to-ipa-pwd-extop.patch @@ -0,0 +1,1711 @@ +From 62d32838e4c8b593a12e39ed282011d3859f7813 Mon Sep 17 00:00:00 2001 +From: Nathaniel McCallum +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 new file mode 100644 index 0000000..5fac7b6 --- /dev/null +++ b/freeipa-mkosek-407-set-krb5ccname-so-that-dirsrv-can-work-with-newer-kr.patch @@ -0,0 +1,76 @@ +From 1be93108c4c1506ea50879d645c47ab6843a6ee1 Mon Sep 17 00:00:00 2001 +From: Martin Kosek +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 2f51cf4..0eca909 100644 --- a/freeipa.spec +++ b/freeipa.spec @@ -8,7 +8,7 @@ Name: freeipa Version: 3.2.0 -Release: 1%{?dist} +Release: 2%{?dist} Summary: The Identity, Policy and Audit system Group: System Environment/Base @@ -17,6 +17,14 @@ URL: http://www.freeipa.org/ Source0: http://www.freeipa.org/downloads/src/freeipa-%{VERSION}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +Patch1: 0001-Add-ipaUserAuthType-and-ipaUserAuthTypeClass.patch +Patch2: 0002-Add-IPA-OTP-schema-and-ACLs.patch +Patch3: 0003-ipa-kdb-Add-OTP-support.patch +Patch4: 0004-Add-the-krb5-FreeIPA-RADIUS-companion-daemon.patch +Patch5: 0005-Remove-unnecessary-prefixes-from-ipa-pwd-extop-files.patch +Patch6: 0006-Add-OTP-support-to-ipa-pwd-extop.patch +Patch7: freeipa-mkosek-407-set-krb5ccname-so-that-dirsrv-can-work-with-newer-kr.patch + %if ! %{ONLY_CLIENT} BuildRequires: 389-ds-base-devel >= 1.3.1.0 BuildRequires: svrcore-devel @@ -74,6 +82,8 @@ BuildRequires: m2crypto BuildRequires: check BuildRequires: libsss_idmap-devel BuildRequires: java-1.7.0-openjdk +BuildRequires: libverto-devel +BuildRequires: systemd # Find out Kerberos middle version to infer ABI changes in DAL driver # We cannot load DAL driver into KDC with wrong ABI. @@ -689,6 +699,7 @@ fi %{_sbindir}/ipa-upgradeconfig %{_sbindir}/ipa-compliance %{_libexecdir}/certmonger/dogtag-ipa-retrieve-agent-submit +%{_libexecdir}/ipa-otpd %{_sysconfdir}/cron.d/ipa-compliance %config(noreplace) %{_sysconfdir}/sysconfig/ipa_memcached %dir %attr(0700,apache,apache) %{_localstatedir}/run/ipa_memcached/ @@ -701,6 +712,8 @@ fi %{_libexecdir}/freeipa-systemd-upgrade # Fedora spec file only: END # END +%attr(644,root,root) %{_unitdir}/ipa-otpd.socket +%attr(644,root,root) %{_unitdir}/ipa-otpd@.service %dir %{python_sitelib}/ipaserver %dir %{python_sitelib}/ipaserver/install %dir %{python_sitelib}/ipaserver/install/plugins @@ -906,6 +919,10 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt %changelog +* Tue May 14 2013 Rob Crittenden - 3.2.0-2 +- Add OTP patches +- Add patch to set KRB5CCNAME for 389-ds-base + * Fri May 10 2013 Rob Crittenden - 3.2.0-1 - Update to upstream 3.2.0 GA - ipa-client-install fails if /etc/ipa does not exist (#961483)