1 2# group.py - group entry lookup routines 3# 4# Copyright (C) 2010-2019 Arthur de Jong 5# 6# This library is free software; you can redistribute it and/or 7# modify it under the terms of the GNU Lesser General Public 8# License as published by the Free Software Foundation; either 9# version 2.1 of the License, or (at your option) any later version. 10# 11# This library is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14# Lesser General Public License for more details. 15# 16# You should have received a copy of the GNU Lesser General Public 17# License along with this library; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19# 02110-1301 USA 20 21import logging 22 23import ldap 24from ldap.filter import escape_filter_chars 25 26import cache 27import cfg 28import common 29import constants 30import passwd 31import search 32 33 34def clean(lst): 35 if lst: 36 for i in lst: 37 yield i.replace('\0', '') 38 39 40attmap = common.Attributes( 41 cn='cn', 42 userPassword='"*"', 43 gidNumber='gidNumber', 44 memberUid='memberUid', 45 member='member') 46filter = '(objectClass=posixGroup)' 47 48 49class Search(search.LDAPSearch): 50 51 case_sensitive = ('cn', ) 52 limit_attributes = ('cn', 'gidNumber') 53 54 def __init__(self, *args, **kwargs): 55 super(Search, self).__init__(*args, **kwargs) 56 if (cfg.nss_getgrent_skipmembers or 57 'memberUid' in self.parameters or 58 'member' in self.parameters): 59 # set up our own attributes that leave out membership attributes 60 self.attributes = list(self.attributes) 61 if attmap['memberUid'] in self.attributes: 62 self.attributes.remove(attmap['memberUid']) 63 if attmap['member'] in self.attributes: 64 self.attributes.remove(attmap['member']) 65 66 def mk_filter(self): 67 # we still need a custom mk_filter because this is an | query 68 if attmap['member'] and 'memberUid' in self.parameters: 69 memberuid = self.parameters['memberUid'] 70 entry = passwd.uid2entry(self.conn, memberuid) 71 if entry: 72 return '(&%s(|(%s=%s)(%s=%s)))' % ( 73 self.filter, 74 attmap['memberUid'], escape_filter_chars(memberuid), 75 attmap['member'], escape_filter_chars(entry[0]), 76 ) 77 if 'gidNumber' in self.parameters: 78 self.parameters['gidNumber'] -= cfg.nss_gid_offset 79 return super(Search, self).mk_filter() 80 81 82class Cache(cache.Cache): 83 84 tables = ('group_cache', 'group_member_cache') 85 86 create_sql = ''' 87 CREATE TABLE IF NOT EXISTS `group_cache` 88 ( `cn` TEXT PRIMARY KEY, 89 `userPassword` TEXT, 90 `gidNumber` INTEGER NOT NULL UNIQUE, 91 `mtime` TIMESTAMP NOT NULL ); 92 CREATE TABLE IF NOT EXISTS `group_member_cache` 93 ( `group` TEXT NOT NULL, 94 `memberUid` TEXT NOT NULL, 95 FOREIGN KEY(`group`) REFERENCES `group_cache`(`cn`) 96 ON DELETE CASCADE ON UPDATE CASCADE ); 97 CREATE INDEX IF NOT EXISTS `group_member_idx` ON `group_member_cache`(`group`); 98 ''' 99 100 retrieve_sql = ''' 101 SELECT `group_cache`.`cn` AS `cn`, `userPassword`, `gidNumber`, 102 `memberUid`, `mtime` 103 FROM `group_cache` 104 LEFT JOIN `group_member_cache` 105 ON `group_member_cache`.`group` = `group_cache`.`cn` 106 ''' 107 108 retrieve_by = dict( 109 memberUid=''' 110 `cn` IN ( 111 SELECT `a`.`group` 112 FROM `group_member_cache` `a` 113 WHERE `a`.`memberUid` = ?) 114 ''', 115 ) 116 117 group_by = (0, ) # cn 118 group_columns = (3, ) # memberUid 119 120 121class GroupRequest(common.Request): 122 123 def write(self, name, passwd, gid, members): 124 self.fp.write_string(name) 125 self.fp.write_string(passwd) 126 self.fp.write_int32(gid) 127 self.fp.write_stringlist(members) 128 129 def get_members(self, attributes, members, subgroups, seen): 130 # add the memberUid values 131 for member in clean(attributes['memberUid']): 132 if common.is_valid_name(member): 133 members.add(member) 134 # translate and add the member values 135 if attmap['member']: 136 for memberdn in clean(attributes['member']): 137 if memberdn in seen: 138 continue 139 seen.add(memberdn) 140 member = passwd.dn2uid(self.conn, memberdn) 141 if member and common.is_valid_name(member): 142 members.add(member) 143 elif cfg.nss_nested_groups: 144 subgroups.append(memberdn) 145 146 def convert(self, dn, attributes, parameters): 147 # get group names and check against requested group name 148 names = attributes['cn'] 149 # get group password 150 try: 151 passwd = attributes['userPassword'][0] 152 except IndexError: 153 passwd = None 154 if not passwd or self.calleruid != 0: 155 passwd = '*' 156 # get group id(s) 157 gids = [int(x) + cfg.nss_gid_offset for x in attributes['gidNumber']] 158 # build member list 159 members = set() 160 subgroups = [] 161 seen = set([dn]) 162 self.get_members(attributes, members, subgroups, seen) 163 # go over subgroups to find more members 164 while subgroups: 165 memberdn = subgroups.pop(0) 166 for dn2, attributes2 in self.search(self.conn, base=memberdn, scope=ldap.SCOPE_BASE): 167 self.get_members(attributes2, members, subgroups, seen) 168 # actually return the results 169 for name in names: 170 if not common.is_valid_name(name): 171 logging.warning('%s: %s: denied by validnames option', dn, 172 attmap['cn']) 173 else: 174 for gid in gids: 175 yield (name, passwd, gid, members) 176 177 178class GroupByNameRequest(GroupRequest): 179 180 action = constants.NSLCD_ACTION_GROUP_BYNAME 181 182 def read_parameters(self, fp): 183 name = fp.read_string() 184 common.validate_name(name) 185 return dict(cn=name) 186 187 188class GroupByGidRequest(GroupRequest): 189 190 action = constants.NSLCD_ACTION_GROUP_BYGID 191 192 def read_parameters(self, fp): 193 return dict(gidNumber=fp.read_int32()) 194 195 196class GroupByMemberRequest(GroupRequest): 197 198 action = constants.NSLCD_ACTION_GROUP_BYMEMBER 199 200 def read_parameters(self, fp): 201 memberuid = fp.read_string() 202 common.validate_name(memberuid) 203 return dict(memberUid=memberuid) 204 205 def get_results(self, parameters): 206 seen = set() 207 for dn, attributes in self.search(self.conn, parameters=parameters): 208 seen.add(dn) 209 for values in self.convert(dn, attributes, parameters): 210 yield values 211 if cfg.nss_nested_groups and attmap['member']: 212 tocheck = list(seen) 213 # find parent groups 214 while tocheck: 215 group = tocheck.pop(0) 216 for dn, attributes in self.search(self.conn, parameters=dict(member=group)): 217 if dn not in seen: 218 seen.add(dn) 219 tocheck.append(dn) 220 for result in self.convert(dn, attributes, parameters): 221 yield result 222 223 def handle_request(self, parameters): 224 # check whether requested user is in nss_initgroups_ignoreusers 225 if parameters['memberUid'] in cfg.nss_initgroups_ignoreusers: 226 # write the final result code to signify empty results 227 self.fp.write_int32(constants.NSLCD_RESULT_END) 228 return 229 return super(GroupByMemberRequest, self).handle_request(parameters) 230 231 232class GroupAllRequest(GroupRequest): 233 234 action = constants.NSLCD_ACTION_GROUP_ALL 235 236 def handle_request(self, parameters): 237 if not cfg.nss_disable_enumeration: 238 return super(GroupAllRequest, self).handle_request(parameters) 239