Blame pynslcd/group.py

Packit 6bd9ab
Packit 6bd9ab
# group.py - group entry lookup routines
Packit 6bd9ab
#
Packit 6bd9ab
# Copyright (C) 2010-2017 Arthur de Jong
Packit 6bd9ab
#
Packit 6bd9ab
# This library is free software; you can redistribute it and/or
Packit 6bd9ab
# modify it under the terms of the GNU Lesser General Public
Packit 6bd9ab
# License as published by the Free Software Foundation; either
Packit 6bd9ab
# version 2.1 of the License, or (at your option) any later version.
Packit 6bd9ab
#
Packit 6bd9ab
# This library is distributed in the hope that it will be useful,
Packit 6bd9ab
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6bd9ab
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6bd9ab
# Lesser General Public License for more details.
Packit 6bd9ab
#
Packit 6bd9ab
# You should have received a copy of the GNU Lesser General Public
Packit 6bd9ab
# License along with this library; if not, write to the Free Software
Packit 6bd9ab
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 6bd9ab
# 02110-1301 USA
Packit 6bd9ab
Packit 6bd9ab
import logging
Packit 6bd9ab
Packit 6bd9ab
from ldap.filter import escape_filter_chars
Packit 6bd9ab
import ldap
Packit 6bd9ab
Packit 6bd9ab
import cache
Packit 6bd9ab
import cfg
Packit 6bd9ab
import common
Packit 6bd9ab
import constants
Packit 6bd9ab
import passwd
Packit 6bd9ab
import search
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
def clean(lst):
Packit 6bd9ab
    if lst:
Packit 6bd9ab
        for i in lst:
Packit 6bd9ab
            yield i.replace('\0', '')
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
attmap = common.Attributes(cn='cn',
Packit 6bd9ab
                           userPassword='"*"',
Packit 6bd9ab
                           gidNumber='gidNumber',
Packit 6bd9ab
                           memberUid='memberUid',
Packit 6bd9ab
                           member='member')
Packit 6bd9ab
filter = '(objectClass=posixGroup)'
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class Search(search.LDAPSearch):
Packit 6bd9ab
Packit 6bd9ab
    case_sensitive = ('cn', )
Packit 6bd9ab
    limit_attributes = ('cn', 'gidNumber')
Packit 6bd9ab
Packit 6bd9ab
    def __init__(self, *args, **kwargs):
Packit 6bd9ab
        super(Search, self).__init__(*args, **kwargs)
Packit 6bd9ab
        if (cfg.nss_getgrent_skipmembers or
Packit 6bd9ab
                'memberUid' in self.parameters or
Packit 6bd9ab
                'member' in self.parameters):
Packit 6bd9ab
            # set up our own attributes that leave out membership attributes
Packit 6bd9ab
            self.attributes = list(self.attributes)
Packit 6bd9ab
            if attmap['memberUid'] in self.attributes:
Packit 6bd9ab
                self.attributes.remove(attmap['memberUid'])
Packit 6bd9ab
            if attmap['member'] in self.attributes:
Packit 6bd9ab
                self.attributes.remove(attmap['member'])
Packit 6bd9ab
Packit 6bd9ab
    def mk_filter(self):
Packit 6bd9ab
        # we still need a custom mk_filter because this is an | query
Packit 6bd9ab
        if attmap['member'] and 'memberUid' in self.parameters:
Packit 6bd9ab
            memberuid = self.parameters['memberUid']
Packit 6bd9ab
            entry = passwd.uid2entry(self.conn, memberuid)
Packit 6bd9ab
            if entry:
Packit 6bd9ab
                return '(&%s(|(%s=%s)(%s=%s)))' % (
Packit 6bd9ab
                        self.filter,
Packit 6bd9ab
                        attmap['memberUid'], escape_filter_chars(memberuid),
Packit 6bd9ab
                        attmap['member'], escape_filter_chars(entry[0])
Packit 6bd9ab
                    )
Packit 6bd9ab
        if 'gidNumber' in self.parameters:
Packit 6bd9ab
            self.parameters['gidNumber'] -= cfg.nss_gid_offset
Packit 6bd9ab
        return super(Search, self).mk_filter()
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class Cache(cache.Cache):
Packit 6bd9ab
Packit 6bd9ab
    tables = ('group_cache', 'group_member_cache')
Packit 6bd9ab
Packit 6bd9ab
    create_sql = '''
Packit 6bd9ab
        CREATE TABLE IF NOT EXISTS `group_cache`
Packit 6bd9ab
          ( `cn` TEXT PRIMARY KEY,
Packit 6bd9ab
            `userPassword` TEXT,
Packit 6bd9ab
            `gidNumber` INTEGER NOT NULL UNIQUE,
Packit 6bd9ab
            `mtime` TIMESTAMP NOT NULL );
Packit 6bd9ab
        CREATE TABLE IF NOT EXISTS `group_member_cache`
Packit 6bd9ab
          ( `group` TEXT NOT NULL,
Packit 6bd9ab
            `memberUid` TEXT NOT NULL,
Packit 6bd9ab
            FOREIGN KEY(`group`) REFERENCES `group_cache`(`cn`)
Packit 6bd9ab
            ON DELETE CASCADE ON UPDATE CASCADE );
Packit 6bd9ab
        CREATE INDEX IF NOT EXISTS `group_member_idx` ON `group_member_cache`(`group`);
Packit 6bd9ab
    '''
Packit 6bd9ab
Packit 6bd9ab
    retrieve_sql = '''
Packit 6bd9ab
        SELECT `group_cache`.`cn` AS `cn`, `userPassword`, `gidNumber`,
Packit 6bd9ab
               `memberUid`, `mtime`
Packit 6bd9ab
        FROM `group_cache`
Packit 6bd9ab
        LEFT JOIN `group_member_cache`
Packit 6bd9ab
          ON `group_member_cache`.`group` = `group_cache`.`cn`
Packit 6bd9ab
    '''
Packit 6bd9ab
Packit 6bd9ab
    retrieve_by = dict(
Packit 6bd9ab
        memberUid='''
Packit 6bd9ab
            `cn` IN (
Packit 6bd9ab
                SELECT `a`.`group`
Packit 6bd9ab
                FROM `group_member_cache` `a`
Packit 6bd9ab
                WHERE `a`.`memberUid` = ?)
Packit 6bd9ab
        ''',
Packit 6bd9ab
    )
Packit 6bd9ab
Packit 6bd9ab
    group_by = (0, )  # cn
Packit 6bd9ab
    group_columns = (3, )  # memberUid
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class GroupRequest(common.Request):
Packit 6bd9ab
Packit 6bd9ab
    def write(self, name, passwd, gid, members):
Packit 6bd9ab
        self.fp.write_string(name)
Packit 6bd9ab
        self.fp.write_string(passwd)
Packit 6bd9ab
        self.fp.write_int32(gid)
Packit 6bd9ab
        self.fp.write_stringlist(members)
Packit 6bd9ab
Packit 6bd9ab
    def get_members(self, attributes, members, subgroups, seen):
Packit 6bd9ab
        # add the memberUid values
Packit 6bd9ab
        for member in clean(attributes['memberUid']):
Packit 6bd9ab
            if common.is_valid_name(member):
Packit 6bd9ab
                members.add(member)
Packit 6bd9ab
        # translate and add the member values
Packit 6bd9ab
        if attmap['member']:
Packit 6bd9ab
            for memberdn in clean(attributes['member']):
Packit 6bd9ab
                if memberdn in seen:
Packit 6bd9ab
                    continue
Packit 6bd9ab
                seen.add(memberdn)
Packit 6bd9ab
                member = passwd.dn2uid(self.conn, memberdn)
Packit 6bd9ab
                if member and common.is_valid_name(member):
Packit 6bd9ab
                    members.add(member)
Packit 6bd9ab
                elif cfg.nss_nested_groups:
Packit 6bd9ab
                    subgroups.append(memberdn)
Packit 6bd9ab
Packit 6bd9ab
    def convert(self, dn, attributes, parameters):
Packit 6bd9ab
        # get group names and check against requested group name
Packit 6bd9ab
        names = attributes['cn']
Packit 6bd9ab
        # get group password
Packit 6bd9ab
        try:
Packit 6bd9ab
            passwd = attributes['userPassword'][0]
Packit 6bd9ab
        except IndexError:
Packit 6bd9ab
            passwd = None
Packit 6bd9ab
        if not passwd or self.calleruid != 0:
Packit 6bd9ab
            passwd = '*'
Packit 6bd9ab
        # get group id(s)
Packit 6bd9ab
        gids = [int(x) + cfg.nss_gid_offset for x in attributes['gidNumber']]
Packit 6bd9ab
        # build member list
Packit 6bd9ab
        members = set()
Packit 6bd9ab
        subgroups = []
Packit 6bd9ab
        seen = set([dn])
Packit 6bd9ab
        self.get_members(attributes, members, subgroups, seen)
Packit 6bd9ab
        # go over subgroups to find more members
Packit 6bd9ab
        while subgroups:
Packit 6bd9ab
            memberdn = subgroups.pop(0)
Packit 6bd9ab
            for dn2, attributes2 in self.search(self.conn, base=memberdn, scope=ldap.SCOPE_BASE):
Packit 6bd9ab
                self.get_members(attributes2, members, subgroups, seen)
Packit 6bd9ab
        # actually return the results
Packit 6bd9ab
        for name in names:
Packit 6bd9ab
            if not common.is_valid_name(name):
Packit 6bd9ab
                logging.warning('%s: %s: denied by validnames option', dn,
Packit 6bd9ab
                                attmap['cn'])
Packit 6bd9ab
            else:
Packit 6bd9ab
                for gid in gids:
Packit 6bd9ab
                    yield (name, passwd, gid, members)
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class GroupByNameRequest(GroupRequest):
Packit 6bd9ab
Packit 6bd9ab
    action = constants.NSLCD_ACTION_GROUP_BYNAME
Packit 6bd9ab
Packit 6bd9ab
    def read_parameters(self, fp):
Packit 6bd9ab
        name = fp.read_string()
Packit 6bd9ab
        common.validate_name(name)
Packit 6bd9ab
        return dict(cn=name)
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class GroupByGidRequest(GroupRequest):
Packit 6bd9ab
Packit 6bd9ab
    action = constants.NSLCD_ACTION_GROUP_BYGID
Packit 6bd9ab
Packit 6bd9ab
    def read_parameters(self, fp):
Packit 6bd9ab
        return dict(gidNumber=fp.read_int32())
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class GroupByMemberRequest(GroupRequest):
Packit 6bd9ab
Packit 6bd9ab
    action = constants.NSLCD_ACTION_GROUP_BYMEMBER
Packit 6bd9ab
Packit 6bd9ab
    def read_parameters(self, fp):
Packit 6bd9ab
        memberuid = fp.read_string()
Packit 6bd9ab
        common.validate_name(memberuid)
Packit 6bd9ab
        return dict(memberUid=memberuid)
Packit 6bd9ab
Packit 6bd9ab
    def get_results(self, parameters):
Packit 6bd9ab
        seen = set()
Packit 6bd9ab
        for dn, attributes in self.search(self.conn, parameters=parameters):
Packit 6bd9ab
            seen.add(dn)
Packit 6bd9ab
            for values in self.convert(dn, attributes, parameters):
Packit 6bd9ab
                yield values
Packit 6bd9ab
        if cfg.nss_nested_groups and attmap['member']:
Packit 6bd9ab
            tocheck = list(seen)
Packit 6bd9ab
            # find parent groups
Packit 6bd9ab
            while tocheck:
Packit 6bd9ab
                group = tocheck.pop(0)
Packit 6bd9ab
                for dn, attributes in self.search(self.conn, parameters=dict(member=group)):
Packit 6bd9ab
                    if dn not in seen:
Packit 6bd9ab
                        seen.add(dn)
Packit 6bd9ab
                        tocheck.append(dn)
Packit 6bd9ab
                        for result in self.convert(dn, attributes, parameters):
Packit 6bd9ab
                            yield result
Packit 6bd9ab
Packit 6bd9ab
    def handle_request(self, parameters):
Packit 6bd9ab
        # check whether requested user is in nss_initgroups_ignoreusers
Packit 6bd9ab
        if parameters['memberUid'] in cfg.nss_initgroups_ignoreusers:
Packit 6bd9ab
            # write the final result code to signify empty results
Packit 6bd9ab
            self.fp.write_int32(constants.NSLCD_RESULT_END)
Packit 6bd9ab
            return
Packit 6bd9ab
        return super(GroupByMemberRequest, self).handle_request(parameters)
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class GroupAllRequest(GroupRequest):
Packit 6bd9ab
Packit 6bd9ab
    action = constants.NSLCD_ACTION_GROUP_ALL
Packit 6bd9ab
Packit 6bd9ab
    def handle_request(self, parameters):
Packit 6bd9ab
        if not cfg.nss_disable_enumeration:
Packit 6bd9ab
            return super(GroupAllRequest, self).handle_request(parameters)