Hi!

Attached are patches to add support of FreeIPA ID views to Schema
compatibility plugin (slapi-nis). There are two patches for FreeIPA and
a separate patch for slapi-nis. Patches can be applied independently; if
old slapi-nis is installed, it will simply work with new configuration
but do nothing with respect to answering to requests using host-specific
ID views.

I included documentation on how slapi-nis ID views feature supposed to
work, available in slapi-nis/doc/ipa/ipa-sch.txt. Any comments and fixes
are welcome. There are no additional tests in slapi-nis to cover compat
trees, we have multiple tests in FreeIPA for this purpose, will be run
as part of FreeIPA CI effort.

FreeIPA patches add ACIs for accessing ID view-applied entries over
compat tree. They also include additional configuration; this
configuration is needed to properly resolve ID view overrides when
creating compat entries.

A second FreeIPA patch adds support to override login shell. This part
was missing from the original patchset by Tomas.

For trusted AD users one needs patches to SSSD 1.12.2, made by Sumit
Bose. There is also a regression (fixed by Sumit as well) that prevents
authentication of AD users over PAM which affects authentication over
compat tree. With the patch from Sumit authentication works again, both
with ID view and without it.

--
/ Alexander Bokovoy
From 400a6fe320ff5d92311a670e596aba486a8a3bc0 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <aboko...@redhat.com>
Date: Tue, 30 Sep 2014 14:54:50 +0300
Subject: [PATCH 2/4] Support idviews in compat tree

https://fedorahosted.org/freeipa/ticket/3318

---
 ACI.txt                                                 |  6 ++++++
 install/share/71idviews.ldif                            |  1 +
 install/share/schema_compat.uldif                       |  8 ++++++++
 install/updates/10-schema_compat.update                 | 12 ++++++++++++
 ipalib/plugins/group.py                                 | 10 ++++++++++
 ipalib/plugins/user.py                                  | 11 +++++++++++
 ipaserver/install/plugins/update_managed_permissions.py | 11 +++++++++++
 7 files changed, 59 insertions(+)

diff --git a/ACI.txt b/ACI.txt
index cebdc2c..87c057e 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -54,6 +54,8 @@ dn: dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || entryusn || gidnumber || 
memberuid || modifytimestamp || objectclass")(target = 
"ldap:///cn=groups,cn=compat,dc=ipa,dc=example";)(version 3.0;acl 
"permission:System: Read Group Compat Tree";allow (compare,read,search) userdn 
= "ldap:///anyone";;)
 dn: cn=groups,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "member || memberhost || memberof || memberuid || 
memberuser")(targetfilter = 
"(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl 
"permission:System: Read Group Membership";allow (compare,read,search) userdn = 
"ldap:///all";;)
+dn: dc=ipa,dc=example
+aci: (targetattr = "cn || createtimestamp || entryusn || gidnumber || 
memberuid || modifytimestamp || objectclass")(target = 
"ldap:///cn=groups,cn=*,cn=views,cn=compat,dc=ipa,dc=example";)(version 3.0;acl 
"permission:System: Read Group Views Compat Tree";allow (compare,read,search) 
userdn = "ldap:///anyone";;)
 dn: cn=groups,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "businesscategory || cn || createtimestamp || description 
|| entryusn || gidnumber || ipaexternalmember || ipantsecurityidentifier || 
ipauniqueid || mepmanagedby || modifytimestamp || o || objectclass || ou || 
owner || seealso")(targetfilter = 
"(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl 
"permission:System: Read Groups";allow (compare,read,search) userdn = 
"ldap:///anyone";;)
 dn: cn=groups,cn=accounts,dc=ipa,dc=example
@@ -256,6 +258,8 @@ dn: cn=users,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "memberof")(targetfilter = 
"(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User 
Membership";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=users,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || description || displayname || 
entryusn || gecos || gidnumber || givenname || homedirectory || initials || 
ipantsecurityidentifier || loginshell || manager || modifytimestamp || 
objectclass || sn || title || uid || uidnumber")(targetfilter = 
"(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User 
Standard Attributes";allow (compare,read,search) userdn = "ldap:///anyone";;)
+dn: dc=ipa,dc=example
+aci: (targetattr = "cn || createtimestamp || entryusn || gecos || gidnumber || 
homedirectory || loginshell || modifytimestamp || objectclass || uid || 
uidnumber")(target = 
"ldap:///cn=users,cn=*,cn=views,cn=compat,dc=ipa,dc=example";)(version 3.0;acl 
"permission:System: Read User Views Compat Tree";allow (compare,read,search) 
userdn = "ldap:///anyone";;)
 dn: cn=users,cn=accounts,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=posixaccount)")(version 3.0;acl 
"permission:System: Remove Users";allow (delete) groupdn = "ldap:///cn=System: 
Remove Users,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=users,cn=accounts,dc=ipa,dc=example
@@ -264,6 +268,8 @@ dn: cn=ca_renewal,cn=ipa,cn=etc,dc=ipa,dc=example
 aci: (target = "ldap:///cn=caSigningCert 
cert-pki-ca,cn=ca_renewal,cn=ipa,cn=etc,dc=ipa,dc=example")(targetfilter = 
"(objectclass=pkiuser)")(version 3.0;acl "permission:System: Add CA Certificate 
For Renewal";allow (add) groupdn = "ldap:///cn=System: Add CA Certificate For 
Renewal,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=certificates,cn=ipa,cn=etc,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=ipacertificate)")(version 3.0;acl 
"permission:System: Add Certificate Store Entry";allow (add) groupdn = 
"ldap:///cn=System: Add Certificate Store 
Entry,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: dc=ipa,dc=example
+aci: (targetattr = "ipaanchoruuid")(target = 
"ldap:///cn=*,cn=compat,dc=ipa,dc=example";)(targetfilter = 
"(objectclass=ipaOverrideTarget)")(version 3.0;acl "permission:System: Compat 
Tree ID View targets";allow (compare,read,search) userdn = "ldap:///anyone";;)
 dn: cn=CAcert,cn=ipa,cn=etc,dc=ipa,dc=example
 aci: (targetattr = "cacertificate")(targetfilter = 
"(objectclass=pkica)")(version 3.0;acl "permission:System: Modify CA 
Certificate";allow (write) groupdn = "ldap:///cn=System: Modify CA 
Certificate,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=ca_renewal,cn=ipa,cn=etc,dc=ipa,dc=example
diff --git a/install/share/71idviews.ldif b/install/share/71idviews.ldif
index 3f8df2e..caa5cff 100644
--- a/install/share/71idviews.ldif
+++ b/install/share/71idviews.ldif
@@ -5,3 +5,4 @@ objectClasses: (2.16.840.1.113730.3.8.12.29 NAME 'ipaIDView' 
SUP nsContainer STR
 objectClasses: (2.16.840.1.113730.3.8.12.30 NAME 'ipaOverrideAnchor' SUP top 
STRUCTURAL MUST ( ipaAnchorUUID ) MAY ( description ) X-ORIGIN 'IPA v4' )
 objectClasses: (2.16.840.1.113730.3.8.12.31 NAME 'ipaUserOverride' DESC 
'Override for User Attributes' SUP ipaOverrideAnchor STRUCTURAL MAY ( uid $ 
uidNumber $ gidNumber $ homeDirectory $ loginShell $ gecos $ ipaOriginalUid ) 
X-ORIGIN 'IPA v4' )
 objectClasses: (2.16.840.1.113730.3.8.12.32 NAME 'ipaGroupOverride' DESC 
'Override for Group Attributes' SUP ipaOverrideAnchor STRUCTURAL MAY ( 
gidNumber $ cn ) X-ORIGIN 'IPA v4' )
+objectClasses: (2.16.840.1.113730.3.8.12.34 NAME 'ipaOverrideTarget' SUP top 
STRUCTURAL MUST ( ipaAnchorUUID ) X-ORIGIN 'IPA v4' )
diff --git a/install/share/schema_compat.uldif 
b/install/share/schema_compat.uldif
index 6de812f..6769fd1 100644
--- a/install/share/schema_compat.uldif
+++ b/install/share/schema_compat.uldif
@@ -38,6 +38,10 @@ default:schema-compat-entry-attribute: uidNumber=%{uidNumber}
 default:schema-compat-entry-attribute: gidNumber=%{gidNumber}
 default:schema-compat-entry-attribute: loginShell=%{loginShell}
 default:schema-compat-entry-attribute: homeDirectory=%{homeDirectory}
+default:schema-compat-entry-attribute: 
%ifeq("ipauniqueid","%{ipauniqueid}","objectclass=ipaOverrideTarget","")
+default:schema-compat-entry-attribute: 
%ifeq("ipauniqueid","%{ipauniqueid}","ipaanchoruuid=:IPA:$DOMAIN:%{ipauniqueid}","")
+default:schema-compat-entry-attribute: ipaanchoruuid=%{ipaanchoruuid}
+default:schema-compat-entry-attribute: 
%ifeq("ipaanchoruuid","%{ipaanchoruuid}","objectclass=ipaOverrideTarget","")
 
 dn: cn=groups, cn=Schema Compatibility, cn=plugins, cn=config
 default:objectClass: top
@@ -52,6 +56,10 @@ default:schema-compat-entry-attribute: objectclass=posixGroup
 default:schema-compat-entry-attribute: gidNumber=%{gidNumber}
 default:schema-compat-entry-attribute: memberUid=%{memberUid}
 default:schema-compat-entry-attribute: memberUid=%deref_r("member","uid")
+default:schema-compat-entry-attribute: 
%ifeq("ipauniqueid","%{ipauniqueid}","objectclass=ipaOverrideTarget","")
+default:schema-compat-entry-attribute: 
%ifeq("ipauniqueid","%{ipauniqueid}","ipaanchoruuid=:IPA:$DOMAIN:%{ipauniqueid}","")
+default:schema-compat-entry-attribute: ipaanchoruuid=%{ipaanchoruuid}
+default:schema-compat-entry-attribute: 
%ifeq("ipaanchoruuid","%{ipaanchoruuid}","objectclass=ipaOverrideTarget","")
 
 dn: cn=ng,cn=Schema Compatibility,cn=plugins,cn=config
 add:objectClass: top
diff --git a/install/updates/10-schema_compat.update 
b/install/updates/10-schema_compat.update
index aeddadb..e88b492 100644
--- a/install/updates/10-schema_compat.update
+++ b/install/updates/10-schema_compat.update
@@ -47,3 +47,15 @@ dn: cn=Schema Compatibility,cn=plugins,cn=config
 # rewritten to the original entry if needed
 add:nsslapd-pluginprecedence: 49
 
+dn: cn=users,cn=Schema Compatibility,cn=plugins,cn=config
+add:schema-compat-entry-attribute: 
'%ifeq("ipauniqueid","%{ipauniqueid}","objectclass=ipaOverrideTarget","")'
+add:schema-compat-entry-attribute: 
'%ifeq("ipauniqueid","%{ipauniqueid}","ipaanchoruuid=:IPA:$DOMAIN:%{ipauniqueid}","")'
+add:schema-compat-entry-attribute: 'ipaanchoruuid=%{ipaanchoruuid}'
+add:schema-compat-entry-attribute: 
'%ifeq("ipaanchoruuid","%{ipaanchoruuid}","objectclass=ipaOverrideTarget","")'
+
+dn: cn=groups,cn=Schema Compatibility,cn=plugins,cn=config
+add:schema-compat-entry-attribute: 
'%ifeq("ipauniqueid","%{ipauniqueid}","objectclass=ipaOverrideTarget","")'
+add:schema-compat-entry-attribute: 
'%ifeq("ipauniqueid","%{ipauniqueid}","ipaanchoruuid=:IPA:$DOMAIN:%{ipauniqueid}","")'
+add:schema-compat-entry-attribute: 'ipaanchoruuid=%{ipaanchoruuid}'
+add:schema-compat-entry-attribute: 
'%ifeq("ipaanchoruuid","%{ipaanchoruuid}","objectclass=ipaOverrideTarget","")'
+
diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py
index 9c2e308..03e6893 100644
--- a/ipalib/plugins/group.py
+++ b/ipalib/plugins/group.py
@@ -212,6 +212,16 @@ class group(LDAPObject):
                 'objectclass', 'cn', 'memberuid', 'gidnumber',
             },
         },
+        'System: Read Group Views Compat Tree': {
+            'non_object': True,
+            'ipapermbindruletype': 'anonymous',
+            'ipapermlocation': api.env.basedn,
+            'ipapermtarget': DN('cn=groups', 'cn=*', 'cn=views', 'cn=compat', 
api.env.basedn),
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'objectclass', 'cn', 'memberuid', 'gidnumber',
+            },
+        },
     }
 
     label = _('User Groups')
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index f95b4fd..e206289 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -435,6 +435,17 @@ class user(LDAPObject):
                 'homedirectory', 'loginshell',
             },
         },
+        'System: Read User Views Compat Tree': {
+            'non_object': True,
+            'ipapermbindruletype': 'anonymous',
+            'ipapermlocation': api.env.basedn,
+            'ipapermtarget': DN('cn=users', 'cn=*', 'cn=views', 'cn=compat', 
api.env.basedn),
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'objectclass', 'uid', 'cn', 'gecos', 'gidnumber', 'uidnumber',
+                'homedirectory', 'loginshell',
+            },
+        },
     }
 
     label = _('Users')
diff --git a/ipaserver/install/plugins/update_managed_permissions.py 
b/ipaserver/install/plugins/update_managed_permissions.py
index df49d5d..032485a 100644
--- a/ipaserver/install/plugins/update_managed_permissions.py
+++ b/ipaserver/install/plugins/update_managed_permissions.py
@@ -117,6 +117,17 @@ NONOBJECT_PERMISSIONS = {
         },
         'default_privileges': {'IPA Masters Readers'},
     },
+    'System: Compat Tree ID View targets': {
+        'replaces_global_anonymous_aci': True,
+        'ipapermlocation':  api.env.basedn,
+        'ipapermtarget': DN('cn=*,cn=compat', api.env.basedn),
+        'ipapermtargetfilter': {'(objectclass=ipaOverrideTarget)'},
+        'ipapermbindruletype': 'anonymous',
+        'ipapermright': {'read', 'search', 'compare'},
+        'ipapermdefaultattr': {
+            'ipaAnchorUUID',
+        },
+    },
     'System: Read DNA Configuration': {
         'replaces_global_anonymous_aci': True,
         'ipapermlocation': DN('cn=dna,cn=ipa,cn=etc', api.env.basedn),
-- 
2.1.0

From 1d8f69a01cd1fa7413bc73f6081c5d40aefebbea Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <aboko...@redhat.com>
Date: Tue, 30 Sep 2014 15:44:31 +0300
Subject: [PATCH 4/4] Support overridding user shell in ID views

https://fedorahosted.org/freeipa/ticket/3318
---
 ACI.txt                   | 2 +-
 API.txt                   | 9 ++++++---
 ipalib/plugins/idviews.py | 8 ++++++--
 3 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 87c057e..b2192ce 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -123,7 +123,7 @@ aci: (targetfilter = "(objectclass=ipahostgroup)")(version 
3.0;acl "permission:S
 dn: cn=views,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || description || entryusn || 
gidnumber || ipaanchoruuid || modifytimestamp || objectclass")(targetfilter = 
"(objectclass=ipaGroupOverride)")(version 3.0;acl "permission:System: Read 
Group ID Overrides";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=views,cn=accounts,dc=ipa,dc=example
-aci: (targetattr = "createtimestamp || description || entryusn || 
homedirectory || ipaanchoruuid || ipaoriginaluid || modifytimestamp || 
objectclass || uid || uidnumber")(targetfilter = 
"(objectclass=ipaUserOverride)")(version 3.0;acl "permission:System: Read User 
ID Overrides";allow (compare,read,search) userdn = "ldap:///all";;)
+aci: (targetattr = "createtimestamp || description || entryusn || 
homedirectory || ipaanchoruuid || ipaoriginaluid || loginshell || 
modifytimestamp || objectclass || uid || uidnumber")(targetfilter = 
"(objectclass=ipaUserOverride)")(version 3.0;acl "permission:System: Read User 
ID Overrides";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=ranges,cn=etc,dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || entryusn || ipabaseid || 
ipabaserid || ipaidrangesize || ipanttrusteddomainsid || iparangetype || 
ipasecondarybaserid || modifytimestamp || objectclass")(targetfilter = 
"(objectclass=ipaidrange)")(version 3.0;acl "permission:System: Read ID 
Ranges";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=views,cn=accounts,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index c5e76c7..41b852b 100644
--- a/API.txt
+++ b/API.txt
@@ -2104,7 +2104,7 @@ output: Entry('result', <type 'dict'>, Gettext('A 
dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: idoverrideuser_add
-args: 2,10,3
+args: 2,11,3
 arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, 
query=True, required=True)
 arg: Str('ipaanchoruuid', attribute=True, cli_name='anchor', multivalue=False, 
primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
@@ -2112,6 +2112,7 @@ option: Flag('all', autofill=True, cli_name='all', 
default=False, exclude='webui
 option: Str('description', attribute=True, cli_name='desc', multivalue=False, 
required=False)
 option: Str('homedirectory', attribute=True, cli_name='homedir', 
multivalue=False, required=False)
 option: Str('ipaoriginaluid', attribute=True, cli_name='ipaoriginaluid', 
multivalue=False, required=False)
+option: Str('loginshell', attribute=True, cli_name='shell', multivalue=False, 
required=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
 option: Str('setattr*', cli_name='setattr', exclude='webui')
 option: Str('uid', attribute=True, cli_name='login', maxlength=255, 
multivalue=False, 
pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', required=False)
@@ -2130,7 +2131,7 @@ output: Output('result', <type 'dict'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: ListOfPrimaryKeys('value', None, None)
 command: idoverrideuser_find
-args: 2,12,4
+args: 2,13,4
 arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, 
query=True, required=True)
 arg: Str('criteria?', noextrawhitespace=False)
 option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
@@ -2138,6 +2139,7 @@ option: Str('description', attribute=True, 
autofill=False, cli_name='desc', mult
 option: Str('homedirectory', attribute=True, autofill=False, 
cli_name='homedir', multivalue=False, query=True, required=False)
 option: Str('ipaanchoruuid', attribute=True, autofill=False, 
cli_name='anchor', multivalue=False, primary_key=True, query=True, 
required=False)
 option: Str('ipaoriginaluid', attribute=True, autofill=False, 
cli_name='ipaoriginaluid', multivalue=False, query=True, required=False)
+option: Str('loginshell', attribute=True, autofill=False, cli_name='shell', 
multivalue=False, query=True, required=False)
 option: Flag('pkey_only?', autofill=True, default=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
 option: Int('sizelimit?', autofill=False, minvalue=0)
@@ -2150,7 +2152,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 
'tuple'>), Gettext('A list
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: idoverrideuser_mod
-args: 2,13,3
+args: 2,14,3
 arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, 
query=True, required=True)
 arg: Str('ipaanchoruuid', attribute=True, cli_name='anchor', multivalue=False, 
primary_key=True, query=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
@@ -2159,6 +2161,7 @@ option: Str('delattr*', cli_name='delattr', 
exclude='webui')
 option: Str('description', attribute=True, autofill=False, cli_name='desc', 
multivalue=False, required=False)
 option: Str('homedirectory', attribute=True, autofill=False, 
cli_name='homedir', multivalue=False, required=False)
 option: Str('ipaoriginaluid', attribute=True, autofill=False, 
cli_name='ipaoriginaluid', multivalue=False, required=False)
+option: Str('loginshell', attribute=True, autofill=False, cli_name='shell', 
multivalue=False, required=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
 option: Str('rename', cli_name='rename', multivalue=False, primary_key=True, 
required=False)
 option: Flag('rights', autofill=True, default=False)
diff --git a/ipalib/plugins/idviews.py b/ipalib/plugins/idviews.py
index 3b0df02..afaa6f9 100644
--- a/ipalib/plugins/idviews.py
+++ b/ipalib/plugins/idviews.py
@@ -650,14 +650,14 @@ class idoverrideuser(baseidoverride):
             'ipapermright': {'read', 'search', 'compare'},
             'ipapermdefaultattr': {
                 'objectClass', 'ipaAnchorUUID', 'uidNumber', 'description',
-                'homeDirectory', 'uid', 'ipaOriginalUid',
+                'homeDirectory', 'uid', 'ipaOriginalUid', 'loginShell',
             },
         },
     }
 
     object_class = baseidoverride.object_class + ['ipaUserOverride']
     default_attributes = baseidoverride.default_attributes + [
-       'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid',
+       'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell',
     ]
 
     takes_params = baseidoverride.takes_params + (
@@ -679,6 +679,10 @@ class idoverrideuser(baseidoverride):
             cli_name='homedir',
             label=_('Home directory'),
         ),
+        Str('loginshell?',
+            cli_name='shell',
+            label=_('Login shell'),
+        ),
         Str('ipaoriginaluid?',
             flags=['no_option', 'no_output']
             ),
-- 
2.1.0

From 30b5a09cf410fbafb52ab069ebb1ae854eeba8dd Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <aboko...@redhat.com>
Date: Tue, 29 Jul 2014 12:04:34 +0300
Subject: [PATCH] Add support for FreeIPA ID views

FreeIPA ID views allow to override POSIX attributes for certain
users and groups.

A support is added to allow using specific ID view when serving
compatibility tree. Each user or group entry which has an override
in the view is amended with the overridden values from the view
before served out to the LDAP client.

A view to use is specified as a part of base DN:

        cn=<view>,cn=views,cn=compat,$SUFFIX

where cn=compat,$SUFFIX is the original compatibility tree base DN.

Each entry, when served through the view, gets new DN rewritten to
specify the view. Additionally, if override in the view changes
uid (for users) or cn (for groups) attribute, the entry's RDN is changed
accordingly.

For groups memberUid attribute is modified as well in case there is an override
in the view that changes uid value of that member.

FreeIPA ID views support overrides for users of trusted Active Directory 
domains.
In case of a trusted AD domain's user or group is returned via compatibility 
tree,
view overrides are applied in two stages:
  1. SSSD applies default view for AD users
  2. slapi-nis applies explicitly specified (host-specific) view
     on top of the entry returned by SSSD

Thus, slapi-nis does not need to apply default view for AD users and if there 
are
no host-specific views in use, there is no need to specify a view in the base 
DN,
making overhead of a default view for AD users lower.
---
 configure.ac          |  14 ++
 doc/ipa/sch-ipa.txt   |  93 +++++++++++++
 src/Makefile.am       |   4 +
 src/back-sch-idview.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/back-sch-nss.c    |  63 ++++++---
 src/back-sch.c        |  62 +++++++--
 src/back-sch.h        |  35 +++++
 7 files changed, 618 insertions(+), 29 deletions(-)
 create mode 100644 src/back-sch-idview.c

diff --git a/configure.ac b/configure.ac
index 84b84d1..71dbdc7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -383,6 +383,20 @@ if test "x$use_nsswitch" != xno ; then
        AC_DEFINE(USE_NSSWITCH,1,[Use nsswitch API to lookup users and groups 
not found in the LDAP tree])
 fi
 
+use_idviews=true
+AC_ARG_WITH(idviews,
+           AS_HELP_STRING([--with-idviews], [Use FreeIPA ID views to override 
POSIX IDs of users and groups]),
+           use_idviews=$withval,use_idviews=yes)
+if test "x$use_idviews" = xyes ; then
+       AC_MSG_RESULT([FreeIPA ID views support is enabled])
+       AC_DEFINE(USE_IPA_IDVIEWS,1,[Use FreeIPA ID views to override POSIX 
attributes of users and groups per view.])
+       AC_DEFINE(IPA_IDVIEWS_ATTR_ANCHORUUID, ["ipaAnchorUUID"],[FreeIPA attr 
unique pointer for id overrides])
+       AC_DEFINE(IPA_IDVIEWS_ATTR_ORIGINALUID, ["ipaOriginalUid"],[FreeIPA 
attr original uid value for user id overrides])
+else
+       AC_MSG_RESULT([FreeIPA ID views support is disabled])
+fi
+AM_CONDITIONAL([USE_IPA_IDVIEWS], [test "x$use_idviews" != xno])
+
 mylibdir=`eval echo "$libdir" | sed "s,NONE,${ac_default_prefix},g"`
 mylibdir=`eval echo "$mylibdir" | sed "s,NONE,${ac_prefix},g"`
 case "$server" in
diff --git a/doc/ipa/sch-ipa.txt b/doc/ipa/sch-ipa.txt
index b5a585b..f560580 100644
--- a/doc/ipa/sch-ipa.txt
+++ b/doc/ipa/sch-ipa.txt
@@ -87,3 +87,96 @@ on  IPA masters.
 
 As 'system-auth' PAM service is not used directly by any other application, it
 is safe to use it for trusted domain users via compatibility path.
+
+== Support for ID views ==
+
+When FreeIPA 4.1 is in use, Schema compatibility plugin can be configured to
+override POSIX attributes according to an identity view (ID View) which
+contains overrides for users and groups.
+
+The overrides are managed by FreeIPA separately for users and groups:
+
+* management of ID views:
+  ipa idview-add 'my view'
+
+* management of an override for a user:
+  ipa idoverrideuser-add 'my view' auser --login=foo --shell=/bin/ksh \
+                                         --homedir=/home/foo --uid=12345
+
+* management of an override for a group:
+  ipa idoverridegroup-add 'my view' agroup --group-name=bgroup --gid=54321
+
+FreeIPA transparently supports overrides for users and groups from trusted AD
+domains.  This means that for trusted domains, IPA doesn't require to place
+POSIX attributes in Active Directory. Instead, a global ID view named 'Default
+Trust View' is used to contain POSIX attributes for existing AD users.
+Additionally, specialized ID views can be added on top of the 'Default Trust
+View' and then assigned to specific hosts.
+
+Schema compatibility plugin does support ID overrides from a single view. The
+feature is designed to allow host-specific ID view, where the view is specified
+through the search base.
+
+For native IPA users default POSIX attributes are stored natively, thus only a
+host-specific ID view is applied, if any. For trusted AD users 'Default Trust
+View' ID view is applied automatically by SSSD running on the IPA master, thus
+Schema compatibility plugin only applies a host-specific ID view, if specified.
+
+In FreeIPA Schema compatibility is configured to serve entries through the
+host-specific ID view with base DN of cn=<ID view>,cn=views,cn=compat,$SUFFIX.
+
+=== ID views implementation ===
+Detailed design of ID views in FreeIPA can be seen here:
+http://www.freeipa.org/page/V4/Migrating_existing_environments_to_Trust
+
+In Schema compatibility plugin support for ID views is done on top of existing
+map cache. It is expected that there are less overrides than non-overridden
+entries for IPA users and groups. For trusted AD users POSIX attributes from
+'Default Trust View' are applied by SSSD on IPA master. Thus, if there are no
+host-specific overrides, trusted AD users treated by Schema compatibility
+plugin as before -- as entries which content comes from nssswitch API.
+
+This approach allows to only keep original entry in the memory and apply
+host-specific override only at the time when entry with explicitly requested ID
+view is returned as part of a search result.
+
+In order to map original entry to an override, FreeIPA configuration for Schema
+compatibility plugin adds ipaAnchorUUID attribute and ipaOverrideTarget object
+class to every generated entry. ipaAnchorUUID is based on ipaUniqueID for IPA
+objects and on SID for trusted AD objects:
+
+* ipaAnchorUUID=:IPA:<domain>:<ipaUniqueID> for IPA object (user, group)_
+
+* ipaAnchorUUID=:SID:<SID> for trusted AD user or group
+
+For ID overrides FreeIPA maintains ipaAnchorUUID with the same value so that an
+override can be found by simple matching of the ipaAnchorUUID attribute's
+value. FreeIPA also stores original uid value for user objects in ID override
+as ipaOriginalUid attribute, to allow mapping back memberUid values for groups.
+
+When a query request comes, the view in the base DN is detected and remembered.
+Base DN is rewritten to exclude the cn=<ID view>,cn=views so that a normal
+search can be performed against cached entries. Additionally, search filter is
+analyzed to replace references to rewritten uid (for user) and cn (for group)
+attributes by references to the original objects. The references come from the
+ID view overrides, if they exist.
+
+Once search results are gathered for the map, they are processed in order to
+apply an override.  For users entry attributes overridden with the values from
+an override. For groups additional processing is performed on values of
+memberUid attribute.
+
+As opposed to member attribute, memberUid attribute contains only values of uid
+attribute of the original member entry.  Given that an ID override may redefine
+uid value, corresponding memberUid value of a group needs to be rewritten to
+include redefined uid value. In order to do that, original memberUid value is
+compared with ipaOriginalUid attribute's value to find an override
+corresponding to the original user object. If such override is detected, 
memberUid
+value is replaced by the uid value of the override.
+
+When attributes of the entry are processed and optionally amended with 
overridden
+values, DN of the entry is rewritten as well, to reflect the fact that entry is
+served through the view.
+
+For all returned entries ipaAnchorUUID attribute and ipaOverrideTarget 
objectclass
+are removed. Resulting entry is sent to the client.
diff --git a/src/Makefile.am b/src/Makefile.am
index e4fe1a9..6f4926e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -78,6 +78,10 @@ schemacompat_plugin_la_SOURCES += back-sch-pam.c
 schemacompat_plugin_la_LIBADD += $(PAM_LIBS)
 endif
 
+if USE_IPA_IDVIEWS
+schemacompat_plugin_la_SOURCES += back-sch-idview.c
+endif
+
 noinst_LTLIBRARIES = dummy-nis-plugin.la
 dummy_nis_plugin_la_SOURCES = \
        disp-nis.c \
diff --git a/src/back-sch-idview.c b/src/back-sch-idview.c
new file mode 100644
index 0000000..9f4025a
--- /dev/null
+++ b/src/back-sch-idview.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright 2014 Red Hat, Inc.
+ *
+ * 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 2 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef HAVE_DIRSRV_SLAPI_PLUGIN_H
+#include <nspr.h>
+#include <nss.h>
+#include <dirsrv/slapi-plugin.h>
+#else
+#include <slapi-plugin.h>
+#endif
+
+#include <rpc/xdr.h>
+#include "../yp/yp.h"
+
+#ifdef HAVE_TCPD_H
+#include <tcpd.h>
+#endif
+
+#include "backend.h"
+#include "back-shr.h"
+#include "format.h"
+#include "plugin.h"
+#include "map.h"
+#include "back-sch.h"
+
+void
+idview_get_overrides(struct backend_search_cbdata *cbdata)
+{
+       char *dn = NULL;
+       int ret = 0;
+       const Slapi_DN *suffix = NULL;
+       Slapi_PBlock *pb;
+
+       if (cbdata->idview == NULL)
+               return;
+
+       pb = wrap_pblock_new(cbdata->pb);
+       if (pb == NULL)
+               return;
+
+       wrap_inc_call_level();
+
+       suffix = slapi_get_suffix_by_dn(cbdata->target_dn);
+       dn = slapi_ch_smprintf("cn=%s,cn=views,cn=accounts,%s", cbdata->idview, 
slapi_sdn_get_dn(suffix));
+       /* Fetch all attributes; there is a bug in 389-ds: it gives out all 
attributes for the entry anyway
+        * when search returns Slapi_Entry* objects. Instead, we'll do removal 
later */
+       slapi_search_internal_set_pb(pb, dn, LDAP_SCOPE_SUBTREE,
+                                    "(objectclass=ipaOverrideAnchor)", NULL, 0,
+                                    NULL, NULL, 
cbdata->state->plugin_identity, 0);
+       ret = slapi_search_internal_pb(pb);
+       slapi_ch_free_string(&dn);
+
+       if (ret == 0) {
+               /* Steal search result entries to avoid re-allocating them */
+               slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, 
&(cbdata->overrides));
+               slapi_pblock_set(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, NULL);
+       }
+
+       wrap_dec_call_level();
+       slapi_pblock_destroy(pb);
+}
+
+void
+idview_free_overrides(struct backend_search_cbdata *cbdata)
+{
+       int i = 0;
+       if (cbdata->overrides != NULL) {
+               for(i=0; cbdata->overrides[i] != NULL; i++) {
+                       slapi_entry_free(cbdata->overrides[i]);
+               }
+               slapi_ch_free((void**)&(cbdata->overrides));
+       }
+}
+
+void
+idview_process_overrides(struct backend_search_cbdata *cbdata,
+                      const char *key, const char *map, const char *domain,
+                      Slapi_Entry *entry)
+{
+#define VIEW_TEMPLATE_KEY_MAP_DOMAIN 0
+#define VIEW_TEMPLATE_KEY_MAP_DOMAIN_NEWKEY 3
+#define VIEW_TEMPLATE_MAP_DOMAIN 1
+#define VIEW_TEMPLATE_DOMAIN 2
+       /* After view was applied, entry's DN needs to reflect the view */
+       const char *dn_template[] = {"%s,%s,cn=%s,cn=views,%s",    /* an entry 
for user or group */
+                                    "%s,cn=%s,cn=views,%s",       /* an entry 
for a map (container for users or groups) */
+                                    "cn=%s,cn=views,%s",          /* an entry 
is a base of the compat tree */
+                                    "%s=%s,%s,cn=%s,cn=views,%s", /* an entry 
for user or group which RDN was overridden with new value */
+                                    };
+       const char *filterout_attrs[] = {"objectclass", "creatorsname", 
"modifiersname",
+                                        "createtimestamp", "modifytimestamp", 
"parentid",
+                                        "entryusn", "ipaanchoruuid", NULL };
+       char *new_dn = NULL, *new_key = NULL, *sep = NULL, *new_val = NULL;
+       Slapi_Entry *override_entry = NULL;
+       Slapi_Attr *anchor = NULL, *id_attr = NULL;
+       Slapi_Value *anchor_value = NULL, *id_value = NULL;
+       int i, result, dn_template_id;
+
+       if (cbdata->overrides == NULL) {
+               /* Only retrieve overrides for the view first time when 
neccessary */
+               idview_get_overrides(cbdata);
+               if (cbdata->overrides == NULL)
+                       return;
+       }
+
+       /* 1. See if the entry has ipaAnchorUUID and selected idview has an 
override for it */
+       /* The code below intentionally uses Slapi_Value instead of comparing 
string values to
+        * avoid allocating additional memory */
+       result = slapi_entry_attr_find(entry, IPA_IDVIEWS_ATTR_ANCHORUUID, 
&anchor);
+       if ((result == 0) && (anchor != NULL) && (cbdata->overrides != NULL)) {
+               result = slapi_attr_first_value(anchor, &anchor_value);
+               for(i = 0; cbdata->overrides[i] != NULL; i++) {
+                       result = slapi_entry_attr_find(cbdata->overrides[i], 
IPA_IDVIEWS_ATTR_ANCHORUUID, &id_attr);
+                       if ((result == 0) && (id_attr != NULL)) {
+                               result = slapi_attr_first_value(id_attr, 
&id_value);
+                               result = slapi_value_compare(id_attr, 
anchor_value, id_value);
+                               if (result == 0) {
+                                       override_entry = cbdata->overrides[i];
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       /* 2. If there is indeed an override, replace attribute values except 
for the ones that should be ignored */
+       if (override_entry != NULL) {
+               Slapi_Attr  *entry_attr = NULL, *override_attr = NULL;
+               result = slapi_entry_first_attr(override_entry, &override_attr);
+               while (result == 0) {
+                       Slapi_ValueSet *override_valueset = NULL;
+                       char *override_type = NULL;
+
+                       /* Filter out override attributes that we don't care 
about */
+                       result = slapi_attr_get_type(override_attr, 
&override_type);
+                       for (i = 0; filterout_attrs[i] != NULL; i++) {
+                               if (strcasecmp(override_type, 
filterout_attrs[i]) == 0) {
+                                       break;
+                               }
+                       }
+
+                       if (filterout_attrs[i] == NULL) {
+                               /* Replace the attribute's value with the 
override */
+                               result = slapi_entry_attr_find(entry, 
override_type, &entry_attr);
+                               if (result == 0) {
+                                       result = 
slapi_attr_get_valueset(override_attr, &override_valueset);
+                                       result = 
slapi_attr_set_valueset(entry_attr, override_valueset);
+                               }
+                       }
+                       result = slapi_entry_next_attr(override_entry, 
override_attr, &override_attr);
+               }
+       }
+
+       /* 3. If entry has memberUid, we need to replace memberUid values too, 
if they were overridden */
+       result = slapi_entry_attr_find(entry, "memberUid", &anchor);
+       if ((result == 0) && (anchor != NULL) && (cbdata->overrides != NULL)) {
+               int value_idx = 0;
+               Slapi_ValueSet *new_valueset = slapi_valueset_new();
+
+               if (new_valueset != NULL) {
+                       /* For each memberUid value, find an override with 
ipaOriginalUid attribute of the same value */
+                       value_idx = slapi_attr_first_value(anchor, 
&anchor_value);
+                       while (value_idx != -1) {
+                               bool_t value_found = FALSE;
+                               for(i = 0; cbdata->overrides[i] != NULL; i++) {
+                                       result = 
slapi_entry_attr_find(cbdata->overrides[i], IPA_IDVIEWS_ATTR_ORIGINALUID, 
&id_attr);
+                                       if ((result == 0) && (id_attr != NULL)) 
{
+                                               result = 
slapi_attr_first_value(id_attr, &id_value);
+                                               result = 
slapi_value_compare(id_attr, anchor_value, id_value);
+                                               if (result == 0) {
+                                                       /* If there is an 
override with ipaOriginalUid: <memberUid value>, use its 'uid' value to 
override */
+                                                       result = 
slapi_entry_attr_find(cbdata->overrides[i], "uid", &id_attr);
+                                                       if ((result == 0) && 
(id_attr != NULL)) {
+                                                               result = 
slapi_attr_first_value(id_attr, &id_value);
+                                                               if (result == 
0) {
+                                                                       /* 
finally: we have an override with ipaOriginalUid: <memberUid value> _and_
+                                                                        * this 
override is changing the 'uid' attribute so we have something to replace */
+                                                                       
slapi_valueset_add_value(new_valueset, id_value);
+                                                                       
value_found = TRUE;
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               if (value_found == FALSE) {
+                                       slapi_valueset_add_value(new_valueset, 
anchor_value);
+                               }
+                               value_idx = slapi_attr_next_value(anchor, 
value_idx, &anchor_value);
+                       }
+
+                       slapi_attr_set_valueset(anchor, new_valueset);
+                       slapi_valueset_free(new_valueset);
+               }
+       }
+
+       /* 4. Even if there were no overrides, since we are serving throught 
the view, replace DN value */
+       dn_template_id = (key == NULL ? 1 : 0) + (map == NULL ? 1 : 0);
+       switch (dn_template_id) {
+               case VIEW_TEMPLATE_KEY_MAP_DOMAIN:
+                       /* update RDN with proper value from the entry after 
overrides were applied */
+                       sep = strchr(key, '=');
+                       if (sep != NULL) {
+                               sep[0] = '\0';
+                               new_val = slapi_entry_attr_get_charptr(entry, 
key);
+                               new_dn = 
slapi_ch_smprintf(dn_template[VIEW_TEMPLATE_KEY_MAP_DOMAIN_NEWKEY], key, 
new_val, map, cbdata->idview, domain);
+                               slapi_ch_free_string(&new_val);
+                               sep[0] = '=';
+                       } else {
+                               new_dn = 
slapi_ch_smprintf(dn_template[dn_template_id], key, map, cbdata->idview, 
domain);
+                       }
+                       break;
+               case VIEW_TEMPLATE_MAP_DOMAIN:
+                       new_dn = slapi_ch_smprintf(dn_template[dn_template_id], 
map, cbdata->idview, domain);
+                       break;
+               case VIEW_TEMPLATE_DOMAIN:
+                       new_dn = slapi_ch_smprintf(dn_template[dn_template_id], 
cbdata->idview, domain);
+                       break;
+       };
+       slapi_entry_set_dn(entry, new_dn);
+}
+
+void
+idview_replace_target_dn(char **target, char **idview)
+{
+       char *idview_p = NULL;
+       char *cnviews = NULL;
+       char *new_target = NULL;
+
+       cnviews = strstr(*target, ",cn=views,");
+       if (cnviews != NULL && cnviews != *target) {
+               cnviews[0] = '\0';
+               idview_p = strrchr(*target, ',');
+               if (idview_p == NULL) {
+                       idview_p = *target;
+               } else {
+                       idview_p++;
+               }
+               if (strstr(idview_p, "cn=") != idview_p) {
+                       cnviews[0] = ',';
+                       return;
+               }
+               *idview = slapi_ch_strdup(&idview_p[3]);
+               if (idview_p !=  *target) {
+                       idview_p[0] = '\0';
+                       new_target = slapi_ch_smprintf("%s%s", *target, 
cnviews+10);
+                       idview_p--;
+                       idview_p[0] = ',';
+               } else {
+                       new_target = slapi_ch_smprintf("%s", cnviews+10);
+               }
+               cnviews[0] = ',';
+               *target = new_target;
+       }
+}
+
+struct filter_cb_data {
+       struct backend_search_cbdata *cbdata;
+       Slapi_Filter *filter;
+};
+
+static void
+idview_process_filter_cb(Slapi_Filter *filter, const char *filter_type, struct 
berval *bval, struct backend_search_filter_config *config)
+{
+       int res, i;
+       Slapi_Value *filter_val, *value, *anchor_val;
+       Slapi_Attr *anchor, *attr = NULL;
+       struct backend_search_cbdata *cbdata = (struct backend_search_cbdata *) 
config->callback_data;
+
+       if (cbdata == NULL || cbdata->idview == NULL) {
+               return;
+       }
+
+       if (filter_type == NULL || config->name == NULL) {
+               return;
+       }
+
+       if (cbdata->overrides == NULL) {
+               /* Only retrieve overrides for the view first time when 
neccessary */
+               idview_get_overrides(cbdata);
+       }
+
+       if (cbdata->overrides == NULL) {
+               return;
+       }
+
+       filter_val = slapi_value_new_berval(bval);
+
+       /* If filter contains an attribute name which is overridden in the view 
and filter value
+        * corresponds to the override, replace the filter by 
(ipaAnchorUUID=...) from the override
+        * to point to the original because otherwise an entry will not be 
found in the slapi-nis map */
+       for(i=0; cbdata->overrides[i] != NULL; i++) {
+               res = slapi_entry_attr_find(cbdata->overrides[i], filter_type, 
&attr);
+               if ((res == 0) && (attr != NULL)) {
+                       res = slapi_attr_first_value(attr, &value);
+                       res = slapi_value_compare(attr, value, filter_val);
+                       if (res == 0) {
+                               res = 
slapi_entry_attr_find(cbdata->overrides[i], "ipaAnchorUUID", &anchor);
+                               if (res == 0) {
+                                       res = slapi_attr_first_value(anchor, 
&anchor_val);
+                                       slapi_filter_changetype(filter, 
"ipaanchoruuid");
+                                       slapi_ber_bvdone(bval);
+                                       slapi_ber_bvcpy(bval, 
slapi_value_get_berval(anchor_val));
+                                       config->override_found = TRUE;
+                                       break;
+                               }
+
+                       }
+               }
+       }
+
+       slapi_value_free(&filter_val);
+
+}
+
+/* Traverse through the filter and replace overridden attribute/value pairs 
with references to the original
+ * entries. This allows to properly handle overrides of uid and cn attributes 
where searches look like
+ * (&(objectclass=posixAccount)(uid=foobar)) -- if uid=foobar is part of an 
override for uid=admin, we need
+ * to point back to uid=admin to be able to find original entry in the 
slapi-nis cache.
+ *
+ * Note that in reality we don't use original value of the uid/cn attribue. 
Instead, we use ipaAnchorUUID
+ * to refer to the original entry. */
+void
+idview_replace_filter(struct backend_search_cbdata *cbdata)
+{
+       struct filter_cb_data filter_data = {cbdata, NULL};
+       struct backend_search_filter_config config =
+               {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL, 
NULL, NULL};
+       int res = 0;
+
+
+       config.callback = idview_process_filter_cb;
+       config.callback_data = cbdata;
+
+       res = backend_analyze_search_filter(cbdata->filter, &config);
+
+       if (filter_data.filter != NULL) {
+               Slapi_Filter *f = cbdata->filter;
+               cbdata->filter = filter_data.filter;
+               slapi_filter_free(f, 1);
+       }
+
+       if (config.name != NULL) {
+               slapi_ch_free_string(&config.name);
+       }
+
+}
diff --git a/src/back-sch-nss.c b/src/back-sch-nss.c
index f192bb9..db57cb8 100644
--- a/src/back-sch-nss.c
+++ b/src/back-sch-nss.c
@@ -52,17 +52,6 @@
 #include "back-sch.h"
 #include "format.h"
 
-struct backend_search_filter_config {
-       bool_t search_user;
-       bool_t search_group;
-       bool_t search_uid;
-       bool_t search_gid;
-       bool_t search_members;
-       bool_t name_set;
-       bool_t wrong_search;
-       char *name;
-};
-
 static int
 bvstrcasecmp(const struct berval *bval, const char *s)
 {
@@ -127,6 +116,10 @@ backend_search_filter_has_cn_uid(Slapi_Filter *filter, 
void *arg)
                }
        }
 
+       if (config->callback != NULL) {
+               config->callback(filter, filter_type, bval, config);
+       }
+
        if ((config->search_uid ||
             config->search_gid ||
             config->search_user ||
@@ -211,8 +204,6 @@ backend_make_user_entry_from_nsswitch_passwd(struct passwd 
*pwd,
        slapi_entry_add_string(entry,
                               "objectClass", "posixAccount");
        slapi_entry_add_string(entry,
-                              "objectClass", "extensibleObject");
-       slapi_entry_add_string(entry,
                               "uid", pwd->pw_name);
        slapi_entry_attr_set_uint(entry,
                                 "uidNumber", pwd->pw_uid);
@@ -240,7 +231,20 @@ backend_make_user_entry_from_nsswitch_passwd(struct passwd 
*pwd,
 #ifdef HAVE_SSS_NSS_IDMAP
        rc = sss_nss_getsidbyid(pwd->pw_uid, &sid_str, &id_type);
        if ((rc == 0) && (sid_str != NULL)) {
+#ifdef USE_IPA_IDVIEWS
+               char *anchor = NULL;
+               /* For overrides of AD users to work correctly, we need to 
generate
+                * ipaAnchorUUID value so that idviews can be properly searched 
for the override */
+               anchor = slapi_ch_smprintf(":SID:%s", sid_str);
+               if (anchor != NULL) {
+                       slapi_entry_add_string(entry, "objectClass", 
"ipaOverrideTarget");
+                       slapi_entry_add_string(entry, "ipaAnchorUUID", anchor);
+                       slapi_ch_free_string(&anchor);
+               }
+#else
+               slapi_entry_add_string(entry, "objectClass", 
"extensibleObject");
                slapi_entry_add_string(entry, "ipaNTSecurityIdentifier", 
sid_str);
+#endif
                free(sid_str);
        }
 #endif
@@ -335,8 +339,6 @@ backend_make_group_entry_from_nsswitch_group(struct group 
*grp,
        slapi_entry_add_string(entry,
                               "objectClass", "posixGroup");
        slapi_entry_add_string(entry,
-                              "objectClass", "extensibleObject");
-       slapi_entry_add_string(entry,
                               "cn", grp->gr_name);
        slapi_entry_attr_set_uint(entry,
                                 "gidNumber", grp->gr_gid);
@@ -352,7 +354,20 @@ backend_make_group_entry_from_nsswitch_group(struct group 
*grp,
 #ifdef HAVE_SSS_NSS_IDMAP
        rc = sss_nss_getsidbyid(grp->gr_gid, &sid_str, &id_type);
        if ((rc == 0) && (sid_str != NULL)) {
+#ifdef USE_IPA_IDVIEWS
+               char *anchor = NULL;
+               /* For overrides of AD users to work correctly, we need to 
generate
+                * ipaAnchorUUID value so that idviews can be properly searched 
for the override */
+               anchor = slapi_ch_smprintf(":SID:%s", sid_str);
+               if (anchor != NULL) {
+                       slapi_entry_add_string(entry, "objectClass", 
"ipaOverrideTarget");
+                       slapi_entry_add_string(entry, "ipaAnchorUUID", anchor);
+                       slapi_ch_free_string(&anchor);
+               }
+#else
+               slapi_entry_add_string(entry, "objectClass", 
"extensibleObject");
                slapi_entry_add_string(entry, "ipaNTSecurityIdentifier", 
sid_str);
+#endif
                free(sid_str);
        }
 #endif
@@ -558,6 +573,16 @@ nsswitch_type_to_name(enum sch_search_nsswitch_t type)
        return "(unknown)";
 }
 
+int
+backend_analyze_search_filter(Slapi_Filter *filter, struct 
backend_search_filter_config *config)
+{
+       int result, rc;
+       result = slapi_filter_apply(filter,
+                                   backend_search_filter_has_cn_uid,
+                                   config, &rc);
+       return (result != SLAPI_FILTER_SCAN_STOP) ? 1 : 0;
+}
+
 /* Check if the filter is one (like uid=<value>) that should trigger an
  * nsswitch lookup, and if it is, make a note that we should perform such a
  * lookup. */
@@ -566,15 +591,15 @@ backend_search_nsswitch(struct backend_set_data *set_data,
                        struct backend_search_cbdata *cbdata)
 {
        int result, rc;
-       struct backend_search_filter_config config = {FALSE, FALSE, FALSE, 
FALSE, FALSE, FALSE, FALSE, NULL};
+       struct backend_search_filter_config config =
+               {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL, 
NULL, NULL};
        struct backend_staged_search *staged = NULL;
        char *idptr = NULL;
        unsigned long id;
 
        /* First, we search the filter to see if it includes a cn|uid=<value> 
test. */
-       result = slapi_filter_apply(cbdata->filter,
-                                   backend_search_filter_has_cn_uid, &config, 
&rc);
-       if ((result != SLAPI_FILTER_SCAN_STOP)) {
+       result = backend_analyze_search_filter(cbdata->filter, &config);
+       if (result != 0) {
                return;
        }
 
diff --git a/src/back-sch.c b/src/back-sch.c
index 78f2627..1c388da 100644
--- a/src/back-sch.c
+++ b/src/back-sch.c
@@ -259,15 +259,6 @@ backend_set_config_read_config(struct plugin_state *state, 
Slapi_Entry *e,
                free(nsswitch_min_id);
        }
 
-       if (ret.check_nsswitch != SCH_NSSWITCH_NONE) {
-               /* If we're adding nsswitch-based entries to this map, make
-                * sure that we copy the schema-compat-origin and SID
-                * attributes, so that we can read the former during the BIND
-                * callback. */
-               backend_shr_add_strlist(&ret.attribute_format, 
"objectClass=%ifeq(\"%{ipaNTSecurityIdentifier}\",\"\",\"\",\"extensibleObject\")");
-               backend_shr_add_strlist(&ret.attribute_format, 
"ipaNTSecurityIdentifier=%{ipaNTSecurityIdentifier}");
-       }
-
        *pret = backend_copy_set_config(&ret);
        if (*pret == NULL) {
                if (strlen(container) > 0) {
@@ -1005,6 +996,7 @@ backend_search_entry_cb(const char *domain, const char 
*map, bool_t secure,
                        void *backend_data, void *cb_data)
 {
        Slapi_DN *sdn;
+       Slapi_Entry *entry;
        struct backend_search_cbdata *cbdata;
        struct backend_entry_data *entry_data;
        int result;
@@ -1043,9 +1035,25 @@ backend_search_entry_cb(const char *domain, const char 
*map, bool_t secure,
                                cbdata->state->plugin_desc->spd_id,
                                "search matched %s\n",
                                slapi_sdn_get_ndn(sdn));
-               slapi_send_ldap_search_entry(cbdata->pb, entry_data->e, NULL,
+               entry = entry_data->e;
+#ifdef USE_IPA_IDVIEWS
+               if (cbdata->idview != NULL) {
+                       entry = slapi_entry_dup(entry_data->e);
+                       idview_process_overrides(cbdata, key, map, domain, 
entry);
+               }
+
+               if (slapi_entry_attr_exists(entry, IPA_IDVIEWS_ATTR_ANCHORUUID) 
== 1) {
+                       slapi_entry_attr_delete(entry, 
IPA_IDVIEWS_ATTR_ANCHORUUID);
+                       slapi_entry_delete_string(entry, "objectClass", 
"ipaOverrideTarget");
+               }
+#endif
+               slapi_send_ldap_search_entry(cbdata->pb, entry, NULL,
                                             cbdata->attrs, cbdata->attrsonly);
                cbdata->n_entries++;
+
+               if (entry != entry_data->e) {
+                       slapi_entry_free(entry);
+               }
                break;
        }
 
@@ -1104,6 +1112,13 @@ backend_search_set_cb(const char *group, const char 
*set, bool_t flag,
                        slapi_log_error(SLAPI_LOG_PLUGIN,
                                        cbdata->state->plugin_desc->spd_id,
                                        "search matched %s\n", ndn);
+#ifdef USE_IPA_IDVIEWS
+                       if (cbdata->idview != NULL) {
+                               idview_process_overrides(cbdata, NULL,
+                                                        set_data->common.set,
+                                                        
set_data->common.group, set_entry);
+                       }
+#endif
                        slapi_send_ldap_search_entry(cbdata->pb, set_entry,
                                                     NULL, cbdata->attrs,
                                                     cbdata->attrsonly);
@@ -1216,6 +1231,11 @@ backend_search_group_cb(const char *group, void *cb_data)
                        slapi_log_error(SLAPI_LOG_PLUGIN,
                                        cbdata->state->plugin_desc->spd_id,
                                        "search matched %s\n", group);
+#ifdef USE_IPA_IDVIEWS
+                       if (cbdata->idview != NULL) {
+                               idview_process_overrides(cbdata, NULL, NULL, 
group, group_entry);
+                       }
+#endif
                        slapi_send_ldap_search_entry(cbdata->pb, group_entry,
                                                     NULL, cbdata->attrs,
                                                     cbdata->attrsonly);
@@ -1313,11 +1333,16 @@ backend_search_cb(Slapi_PBlock *pb)
        cbdata.n_entries = 0;
        cbdata.staged = NULL;
        cbdata.cur_staged = NULL;
+       cbdata.idview = NULL;
+       cbdata.overrides = NULL;
        /* Okay, we can search. */
        slapi_log_error(SLAPI_LOG_PLUGIN, cbdata.state->plugin_desc->spd_id,
                        "searching from \"%s\" for \"%s\" with scope %d%s\n",
                        cbdata.target, cbdata.strfilter, cbdata.scope,
                        backend_sch_scope_as_string(cbdata.scope));
+#ifdef USE_IPA_IDVIEWS
+       idview_replace_target_dn(&cbdata.target, &cbdata.idview);
+#endif
        cbdata.target_dn = slapi_sdn_new_dn_byval(cbdata.target);
        /* Check if there's a backend handling this search. */
        if (!slapi_be_exist(cbdata.target_dn)) {
@@ -1326,10 +1351,18 @@ backend_search_cb(Slapi_PBlock *pb)
                                "slapi_be_exists(\"%s\") = 0, "
                                "ignoring search\n", cbdata.target);
                slapi_sdn_free(&cbdata.target_dn);
+               slapi_ch_free_string(&cbdata.idview);
+#ifdef USE_IPA_IDVIEWS
+               idview_free_overrides(&cbdata);
+#endif
                return 0;
        }
+
        /* Walk the list of groups. */
        wrap_inc_call_level();
+#ifdef USE_IPA_IDVIEWS
+       idview_replace_filter(&cbdata);
+#endif
        if (map_rdlock() == 0) {
                map_data_foreach_domain(cbdata.state, backend_search_group_cb,
                                        &cbdata);
@@ -1468,6 +1501,10 @@ backend_search_cb(Slapi_PBlock *pb)
                                 cbdata.n_entries, NULL);
        }
        slapi_sdn_free(&cbdata.target_dn);
+       slapi_ch_free_string(&cbdata.idview);
+#ifdef USE_IPA_IDVIEWS
+       idview_free_overrides(&cbdata);
+#endif
        free(cbdata.closest_match);
        free(cbdata.text);
        return cbdata.answer ? -1 : 0;
@@ -1525,6 +1562,7 @@ static void
 backend_locate(Slapi_PBlock *pb, struct backend_entry_data **data, const char 
**group, const char**set)
 {
        struct backend_locate_cbdata cbdata;
+       char *idview = NULL;
 
        slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &cbdata.state);
        if (cbdata.state->plugin_base == NULL) {
@@ -1533,6 +1571,9 @@ backend_locate(Slapi_PBlock *pb, struct 
backend_entry_data **data, const char **
                return;
        }
        slapi_pblock_get(pb, SLAPI_TARGET_DN, &cbdata.target);
+#ifdef USE_IPA_IDVIEWS
+       idview_replace_target_dn(&cbdata.target, &idview);
+#endif
        cbdata.target_dn = slapi_sdn_new_dn_byval(cbdata.target);
        cbdata.entry_data = NULL;
        cbdata.entry_group = NULL;
@@ -1542,6 +1583,7 @@ backend_locate(Slapi_PBlock *pb, struct 
backend_entry_data **data, const char **
        *group = cbdata.entry_group;
        *set = cbdata.entry_set;
        slapi_sdn_free(&cbdata.target_dn);
+       slapi_ch_free_string(&idview);
 }
 
 /* Check if the target DN is part of this group's tree.  If it is, return an
diff --git a/src/back-sch.h b/src/back-sch.h
index 2f4a3df..f74e82d 100644
--- a/src/back-sch.h
+++ b/src/back-sch.h
@@ -69,6 +69,8 @@ struct backend_search_cbdata {
        Slapi_PBlock *pb;
        struct plugin_state *state;
        char *target, *strfilter, **attrs;
+       char *idview;
+       Slapi_Entry **overrides;
        int scope, sizelimit, timelimit, attrsonly;
        bool_t check_access;
        enum sch_search_nsswitch_t check_nsswitch;
@@ -87,6 +89,29 @@ struct backend_search_cbdata {
        struct backend_staged_search *cur_staged;
 };
 
+struct backend_search_filter_config {
+       bool_t search_user;
+       bool_t search_group;
+       bool_t search_uid;
+       bool_t search_gid;
+       bool_t search_members;
+       bool_t name_set;
+       bool_t wrong_search;
+       bool_t override_found;
+       char *name;
+       /* If callback is defined, it is called on each filter after analyzing 
it */
+       void (*callback)(Slapi_Filter *filter, const char *filter_type, struct 
berval *bval, struct backend_search_filter_config *config);
+       void *callback_data;
+};
+
+/* Analyzes the filter to decide what kind of NSS search is it
+ * Returns 0 on success, 1 on failure
+ * struct backend_search_filter_config is populated with information about the 
filter
+ * config.name should be freed with slapi_ch_free_string()
+ */
+
+int backend_analyze_search_filter(Slapi_Filter *filter, struct 
backend_search_filter_config *config);
+
 void backend_search_nsswitch(struct backend_set_data *set_data,
                             struct backend_search_cbdata *cbdata);
 
@@ -95,4 +120,14 @@ bool_t backend_retrieve_from_nsswitch(struct 
backend_staged_search *staged,
 
 int backend_sch_do_pam_auth(Slapi_PBlock *pb, const char *username);
 
+#ifdef USE_IPA_IDVIEWS
+void idview_get_overrides(struct backend_search_cbdata *cbdata);
+void idview_free_overrides(struct backend_search_cbdata *cbdata);
+void idview_process_overrides(struct backend_search_cbdata *cbdata,
+                             const char *key, const char *map, const char 
*domain,
+                             Slapi_Entry *entry);
+void idview_replace_target_dn(char **target, char **idview);
+void idview_replace_filter(struct backend_search_cbdata *cbdata);
+#endif
+
 #endif
-- 
2.1.0

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to