summaryrefslogtreecommitdiff
path: root/pynslcd
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2013-03-24 22:52:44 +0100
committerArthur de Jong <arthur@arthurdejong.org>2013-03-24 22:52:44 +0100
commit3daa68d35cf18c0dc80c8c24c7aa23c6273d06c4 (patch)
tree3b1e8c1596f292dbe67fb1cc903237de0466be66 /pynslcd
parentedd119c3a0d532fc5f87ccf89585370cb2fa3fed (diff)
parent642064cc205cf484bd904d94141eba8740aa0a28 (diff)
Implement support for nested groups
Diffstat (limited to 'pynslcd')
-rw-r--r--pynslcd/cfg.py3
-rw-r--r--pynslcd/common.py17
-rw-r--r--pynslcd/group.py55
3 files changed, 58 insertions, 17 deletions
diff --git a/pynslcd/cfg.py b/pynslcd/cfg.py
index 57a1be2..b03b8c7 100644
--- a/pynslcd/cfg.py
+++ b/pynslcd/cfg.py
@@ -85,6 +85,7 @@ tls_key = None
pagesize = 0
nss_initgroups_ignoreusers = set()
nss_min_uid = 0
+nss_nested_groups = False
validnames = re.compile(r'^[a-z0-9._@$][a-z0-9._@$ \\~-]{0,98}[a-z0-9._@$~-]$', re.IGNORECASE)
pam_authz_searches = []
pam_password_prohibit_message = None
@@ -176,7 +177,7 @@ def read(filename):
globals()[m.group('keyword').lower()] = int(m.group('value'))
continue
# parse options with a single boolean argument
- m = re.match('(?P<keyword>referrals)\s+(?P<value>%s)' %
+ m = re.match('(?P<keyword>referrals|nss_nested_groups)\s+(?P<value>%s)' %
'|'.join(_boolean_options.keys()),
line, re.IGNORECASE)
if m:
diff --git a/pynslcd/common.py b/pynslcd/common.py
index bbffef4..3a59cbe 100644
--- a/pynslcd/common.py
+++ b/pynslcd/common.py
@@ -82,18 +82,23 @@ class Request(object):
stream."""
pass
+ def get_results(self, parameters):
+ """Provide the result entries by performing a search."""
+ for dn, attributes in self.search(self.conn, parameters=parameters):
+ for values in self.convert(dn, attributes, parameters):
+ yield values
+
def handle_request(self, parameters):
"""This method handles the request based on the parameters read
with read_parameters()."""
try:
#with cache.con:
if True:
- for dn, attributes in self.search(self.conn, parameters=parameters):
- for values in self.convert(dn, attributes, parameters):
- self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
- self.write(*values)
- if self.cache:
- self.cache.store(*values)
+ for values in self.get_results(parameters):
+ self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
+ self.write(*values)
+ if self.cache:
+ self.cache.store(*values)
except ldap.SERVER_DOWN:
if self.cache:
logging.debug('read from cache')
diff --git a/pynslcd/group.py b/pynslcd/group.py
index a43aae5..71a1173 100644
--- a/pynslcd/group.py
+++ b/pynslcd/group.py
@@ -22,9 +22,11 @@ import itertools
import logging
from ldap.filter import escape_filter_chars
+import ldap
from passwd import dn2uid, uid2dn
import cache
+import cfg
import common
import constants
import search
@@ -51,7 +53,7 @@ class Search(search.LDAPSearch):
def __init__(self, *args, **kwargs):
super(Search, self).__init__(*args, **kwargs)
- if 'memberUid' in self.parameters:
+ if 'memberUid' in self.parameters or 'member' in self.parameters:
# set up our own attributes that leave out membership attributes
self.attributes = list(self.attributes)
self.attributes.remove(attmap['memberUid'])
@@ -95,24 +97,39 @@ class GroupRequest(common.Request):
self.fp.write_int32(gid)
self.fp.write_stringlist(members)
- def convert(self, dn, attributes, parameters):
- # get group names and check against requested group name
- names = attributes['cn']
- # get group group password
- passwd = attributes['userPassword'][0]
- # get group id(s)
- gids = [int(x) for x in attributes['gidNumber']]
- # build member list
- members = set()
+ def get_members(self, attributes, members, subgroups, seen):
# add the memberUid values
for member in clean(attributes['memberUid']):
if common.isvalidname(member):
members.add(member)
# translate and add the member values
for memberdn in clean(attributes['member']):
+ if memberdn in seen:
+ continue
+ seen.add(memberdn)
member = dn2uid(self.conn, memberdn)
if member and common.isvalidname(member):
members.add(member)
+ elif cfg.nss_nested_groups:
+ subgroups.append(memberdn)
+
+ def convert(self, dn, attributes, parameters):
+ # get group names and check against requested group name
+ names = attributes['cn']
+ # get group group password
+ passwd = attributes['userPassword'][0]
+ # get group id(s)
+ gids = [int(x) for x in attributes['gidNumber']]
+ # build member list
+ members = set()
+ subgroups = []
+ seen = set([dn])
+ self.get_members(attributes, members, subgroups, seen)
+ # go over subgroups to find more members
+ while subgroups:
+ memberdn = subgroups.pop(0)
+ for dn2, attributes2 in self.search(self.conn, base=memberdn, scope=ldap.SCOPE_BASE):
+ self.get_members(attributes2, members, subgroups, seen)
# actually return the results
for name in names:
if not common.isvalidname(name):
@@ -150,6 +167,24 @@ class GroupByMemberRequest(GroupRequest):
common.validate_name(memberuid)
return dict(memberUid=memberuid)
+ def get_results(self, parameters):
+ seen = set()
+ for dn, attributes in self.search(self.conn, parameters=parameters):
+ seen.add(dn)
+ for values in self.convert(dn, attributes, parameters):
+ yield values
+ if cfg.nss_nested_groups:
+ tocheck = list(seen)
+ # find parent groups
+ while tocheck:
+ group = tocheck.pop(0)
+ for dn, attributes in self.search(self.conn, parameters=dict(member=group)):
+ if dn not in seen:
+ seen.add(dn)
+ tocheck.append(dn)
+ for result in self.convert(dn, attributes, parameters):
+ yield result
+
class GroupAllRequest(GroupRequest):