From 4c19151250e318fa38dac33e5db1397b9d95a43e Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sun, 1 May 2011 19:08:39 +0000 Subject: implement attribute mapping functionality and do some refactoring git-svn-id: http://arthurdejong.org/svn/nss-pam-ldapd/nss-pam-ldapd@1454 ef36b2f9-881f-0410-afb5-c4e39611909c --- pynslcd/alias.py | 24 +++---- pynslcd/attmap.py | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pynslcd/common.py | 24 +++++-- pynslcd/ether.py | 28 ++++---- pynslcd/group.py | 79 ++++++++++----------- pynslcd/host.py | 32 +++------ pynslcd/netgroup.py | 29 ++++---- pynslcd/network.py | 33 ++++----- pynslcd/pam.py | 8 +-- pynslcd/passwd.py | 86 ++++++++++------------- pynslcd/protocol.py | 31 +++------ pynslcd/rpc.py | 31 +++------ pynslcd/service.py | 45 ++++++------ pynslcd/shadow.py | 62 ++++++++--------- pynslcd/tio.py | 2 +- 15 files changed, 417 insertions(+), 291 deletions(-) create mode 100644 pynslcd/attmap.py diff --git a/pynslcd/alias.py b/pynslcd/alias.py index 940a98f..d432945 100644 --- a/pynslcd/alias.py +++ b/pynslcd/alias.py @@ -18,35 +18,32 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import ldap.filter +import logging import constants import common -class AliasRequest(common.Request): - - filter = '(objectClass=nisMailAlias)' +attmap = common.Attributes(cn='cn', rfc822MailMember='rfc822MailMember') +filter = '(objectClass=nisMailAlias)' - attmap_cn = 'cn' - attmap_rfc822MailMember = 'rfc822MailMember' - attributes = ( 'cn', 'rfc822MailMember' ) +class AliasRequest(common.Request): def write(self, dn, attributes): # get name and check against requested name - names = attributes.get(self.attmap_cn, []) + names = attributes['cn'] if not names: - logging.error('Error: entry %s does not contain %s value', dn, self.attmap_cn) + logging.error('Error: entry %s does not contain %s value', dn, attmap['cn']) return if self.name: if self.name.lower() not in (x.lower() for x in names): return names = ( self.name, ) # get the members of the alias - members = attributes.get(self.attmap_rfc822MailMember, []) + members = attributes['rfc822MailMember'] if not members: - logging.error('Error: entry %s does not contain %s value', dn, self.attmap_rfc822MailMember) + logging.error('Error: entry %s does not contain %s value', dn, attmap['rfc822MailMember']) return # write results for name in names: @@ -58,14 +55,11 @@ class AliasRequest(common.Request): class AliasByNameRequest(AliasRequest): action = constants.NSLCD_ACTION_ALIAS_BYNAME + filter_attrs = dict(cn='name') def read_parameters(self): self.name = self.fp.read_string() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name) ) - class AliasAllRequest(AliasRequest): diff --git a/pynslcd/attmap.py b/pynslcd/attmap.py new file mode 100644 index 0000000..f72fa62 --- /dev/null +++ b/pynslcd/attmap.py @@ -0,0 +1,194 @@ + +# attributes.py - attribute mapping functions +# +# Copyright (C) 2011 Arthur de Jong +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +"""Module for handling attribute mappings used for LDAP searches. + +>>> attrs = Attributes(uid='uid', +... userPassword='userPassword', +... uidNumber='uidNumber', +... gidNumber='gidNumber', +... gecos='"${gecos:-$cn}"', +... homeDirectory='homeDirectory', +... loginShell='loginShell') +>>> attrs.attributes() +('uid', 'userPassword', 'uidNumber', 'gidNumber', 'gecos', 'cn', 'homeDirectory', 'loginShell') +>>> attrs.value('gecos', {'cn': 'test'}) +['test'] +>>> attrs.search('uidNumber', 100) +'(uidNumber=100)' +>>> attrs['foo'] = '\"bar\"' +>>> attrs.get('foo', {}) +['bar'] +""" + +# exported names +__all__ = ( 'Attributes', ) + + +# FIXME: support multiple attribute values +# TODO: support objectSid attributes +# TODO: do more expression validity checking +# TODO: handle userPassword specially to do filtering of results + + +class MyIter(object): + """Custom iterator-like class with a back() method.""" + + def __init__(self, value): + self.value = value + self.pos = 0 + + def next(self): + self.pos += 1 + return self.value[self.pos-1] + + def back(self): + self.pos -= 1 + + def __iter__(self): + return self + + +class DollarExpression(object): + """Class for handling a variable $xxx ${xxx}, ${xxx:-yyy} or ${xxx:+yyy} + expression.""" + + def _parse_varname(self, value): + """Read a variable name from the value iterator.""" + name = '' + for c in value: + if not c.isalnum(): + value.back() + return name + name += c + + def __init__(self, value): + """Parse the expression as the start of a $-expression.""" + self.op = None + self.expr = None + c = value.next() + if c == '{': + self.name = self._parse_varname(value) + c = value.next() + if c == '}': + return + self.op = c + value.next() + self.expr = Expression(value, endat='}') + else: + value.back() + self.name = self._parse_varname(value) + + def value(self, variables): + """Expand the expression using the variables specified.""" + value = variables.get(self.name, [''])[0] + # FIXME: expand list + if self.op == ':-': + return value if value else self.expr.value(variables) + elif self.op == ':+': + return self.expr.value(variables) if value else '' + return value + + def variables(self, results): + """Add the variables used in the expression to results.""" + results.add(self.name) + if self.expr: + self.expr.variables(results) + + +class Expression(object): + """Class for parsing and expanding an expression.""" + + def __init__(self, value, endat=None): + """Parse the expression as a string.""" + if not isinstance(value, MyIter): + value = MyIter(value) + if not endat: + endat = value.next() # skip opening quote + expr = [] + literal = '' + c = value.next() + while c != endat: + if c == '$': + if literal: + expr.append(literal) + expr.append(DollarExpression(value)) + literal = '' + elif c == '\\': + literal += value.next() + else: + literal += c + c = value.next() + if literal: + expr.append(literal) + self.expr = expr + + def value(self, variables): + """Expand the expression using the variables specified.""" + res = '' + for x in self.expr: + if hasattr(x, 'value'): + res += x.value(variables) + else: + res += x + return res + + def variables(self, results=None): + """Return the variables defined in the expression.""" + if not results: + results = set() + for x in self.expr: + if hasattr(x, 'variables'): + x.variables(results) + return results + + +class Attributes(dict): + """Dictionary-like class for handling a list of attributes.""" + + def _prepare(self): + """Go over all values to parse any expressions.""" + updates = dict() + for k, v in self.iteritems(): + if isinstance(v, basestring) and v[0] == '"': + updates[k] = Expression(v) + self.update(updates) + + def attributes(self): + """Return a set of attributes that are referenced in this attribute + mapping.""" + self._prepare() + results = set() + for value in self.itervalues(): + if hasattr(value, 'variables'): + results.update(value.variables()) + else: + results.add(value) + return list(results) + + def mapped(self, variables): + """Return a dictionary with every attribute mapped to their value from + the specified variables.""" + results = dict() + for k, v in self.iteritems(): + if hasattr(v, 'value'): + results[k] = [v.value(variables)] + else: + results[k] = variables.get(v, []) + return results diff --git a/pynslcd/common.py b/pynslcd/common.py index 0440272..acf0725 100644 --- a/pynslcd/common.py +++ b/pynslcd/common.py @@ -21,9 +21,11 @@ import re import ldap import ldap.dn +import sys import cfg import constants +from attmap import Attributes _validname_re = re.compile(r'^[a-z0-9._@$][a-z0-9._@$ \\~-]{0,98}[a-z0-9._@$~-]$', re.IGNORECASE) @@ -63,9 +65,6 @@ class Request(object): write: function that writes a single LDAP entry to the result stream """ - bases = cfg.bases - scope = cfg.scope - def __init__(self, fp, conn, calleruid): self.fp = fp self.conn = conn @@ -75,14 +74,28 @@ class Request(object): self.uid = None self.gid = None self.address = None + module = sys.modules[self.__module__] + self.attmap = module.attmap + self.filter = module.filter + self.bases = getattr(module, 'bases', cfg.bases) + self.scope = getattr(module, 'scope', cfg.scope) def read_parameters(self): """This method should read the parameters from ths stream and store them in self.""" pass + def attributes(self): + """Return the attributes that should be used in the LDAP search.""" + return self.attmap.attributes() + def mk_filter(self): """Return the active search filter (based on the read parameters).""" + if hasattr(self, 'filter_attrs'): + return '(&%s(%s))' % ( self.filter, + ')('.join('%s=%s' % (self.attmap[attribute], + ldap.filter.escape_filter_chars(str(getattr(self, name)))) + for attribute, name in self.filter_attrs.items()) ) return self.filter def handle_request(self): @@ -92,10 +105,10 @@ class Request(object): for base in self.bases: # do the LDAP search try: - res = self.conn.search_s(base, self.scope, self.mk_filter(), self.attributes) + res = self.conn.search_s(base, self.scope, self.mk_filter(), self.attributes()) for entry in res: if entry[0]: - self.write(entry[0], entry[1]) + self.write(entry[0], self.attmap.mapped(entry[1])) except ldap.NO_SUCH_OBJECT: # FIXME: log message pass @@ -122,5 +135,4 @@ def get_handlers(module): return res def get_rdn_value(dn, attribute): - dn, attributes = entry return dict((x, y) for x, y, z in ldap.dn.str2dn(dn)[0])[attribute] diff --git a/pynslcd/ether.py b/pynslcd/ether.py index fca25e6..e1ab05e 100644 --- a/pynslcd/ether.py +++ b/pynslcd/ether.py @@ -19,27 +19,26 @@ # 02110-1301 USA import struct -import ldap.filter import constants import common def ether_aton(ether): + """Converst an ethernet address to binary form in network byte order.""" return struct.pack('BBBBBB', *(int(x, 16) for x in ether.split(':'))) def ether_ntoa(ether): + """Conversts an ethernet address in network byte order to the string + representation.""" return ':'.join('%x' % x for x in struct.unpack('6B', ether)) -class EtherRequest(common.Request): - - filter = '(objectClass=ieee802Device)' +attmap = common.Attributes(cn='cn', macAddress='macAddress') +filter = '(objectClass=ieee802Device)' - attmap_cn = 'cn' - attmap_macAddress = 'macAddress' - attributes = ( 'cn', 'macAddress' ) +class EtherRequest(common.Request): def __init__(self, *args): super(EtherRequest, self).__init__(*args) @@ -47,17 +46,17 @@ class EtherRequest(common.Request): def write(self, dn, attributes): # get name and check against requested name - names = attributes.get(self.attmap_cn, []) + names = attributes['cn'] if not names: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_cn) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['cn']) if self.name: if self.name.lower() not in (x.lower() for x in names): return # skip entry names = ( self.name, ) # get addresses and convert to binary form - addresses = [ether_aton(x) for x in attributes.get(self.attmap_macAddress, [])] + addresses = [ether_aton(x) for x in attributes['macAddress']] if not addresses: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_macAddress) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['macAddress']) if self.ether: if self.ether not in addresses: return @@ -73,14 +72,11 @@ class EtherRequest(common.Request): class EtherByNameRequest(EtherRequest): action = constants.NSLCD_ACTION_ETHER_BYNAME + filter_attrs = dict(cn='name') def read_parameters(self): self.name = self.fp.read_string() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name) ) - class EtherByEtherRequest(EtherRequest): @@ -91,7 +87,7 @@ class EtherByEtherRequest(EtherRequest): def mk_filter(self): return '(&%s(%s=%s))' % ( self.filter, - self.attmap_macAddress, ether_ntoa(self.ether) ) + attmap['macAddress'], ether_ntoa(self.ether) ) class EtherAllRequest(EtherRequest): diff --git a/pynslcd/group.py b/pynslcd/group.py index e3ee70a..361153a 100644 --- a/pynslcd/group.py +++ b/pynslcd/group.py @@ -23,53 +23,50 @@ import ldap.filter import constants import common +from passwd import dn2uid, uid2dn def clean(lst): - for i in lst: - yield i.replace('\0', '') + if lst: + for i in lst: + yield i.replace('\0', '') -class GroupRequest(common.Request): - - filter = '(|(objectClass=posixGroup)(objectClass=groupOfUniqueNames))' +attmap = common.Attributes(cn='cn', + userPassword='"*"', + gidNumber='gidNumber', + memberUid='memberUid', + uniqueMember='uniqueMember') +filter = '(|(objectClass=posixGroup)(objectClass=groupOfUniqueNames))' - attmap_group_cn = 'cn' - attmap_group_userPassword = 'userPassword' - attmap_group_gidNumber = 'gidNumber' - attmap_group_memberUid = 'memberUid' - attmap_group_uniqueMember = 'uniqueMember' - attributes = ( 'cn', 'userPassword', 'gidNumber', 'memberUid', - 'uniqueMember' ) +class GroupRequest(common.Request): wantmembers = True - def write(self, entry): - dn, attributes = entry + def write(self, dn, attributes): # get group names and check against requested group name - names = attributes.get(self.attmap_group_cn, []) + names = attributes['cn'] if self.name: if self.name not in names: return names = ( self.name, ) # get group group password - ( passwd, ) = attributes.get(self.attmap_group_userPassword, ['*']) + passwd = attributes['userPassword'][0] # get group id(s) - gids = ( self.gid, ) if self.gid else attributes.get(self.attmap_group_gidNumber, []) + gids = ( self.gid, ) if self.gid else attributes['gidNumber'] gids = [ int(x) for x in gids ] # build member list members = set() if self.wantmembers: # add the memberUid values - for member in clean(attributes.get(self.attmap_group_memberUid, [])): + for member in clean(attributes['memberUid']): if common.isvalidname(member): members.add(member) # translate and add the uniqueMember values - from passwd import dn2uid - for memberdn in clean(attributes.get(self.attmap_group_uniqueMember, [])): + for memberdn in clean(attributes['uniqueMember']): member = dn2uid(self.conn, memberdn) - if member: + if member and common.isvalidname(member): members.add(member) # actually return the results for name in names: @@ -87,50 +84,50 @@ class GroupRequest(common.Request): class GroupByNameRequest(GroupRequest): action = constants.NSLCD_ACTION_GROUP_BYNAME + filter_attrs = dict(cn='name') def read_parameters(self): self.name = self.fp.read_string() common.validate_name(self.name) - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_group_cn, ldap.filter.escape_filter_chars(self.name) ) - class GroupByGidRequest(GroupRequest): action = constants.NSLCD_ACTION_GROUP_BYGID + filter_attrs = dict(gidNumber='gid') def read_parameters(self): self.gid = self.fp.read_gid_t() - def mk_filter(self): - return '(&%s(%s=%d))' % ( self.filter, - self.attmap_group_gidNumber, self.gid ) - class GroupByMemberRequest(GroupRequest): action = constants.NSLCD_ACTION_GROUP_BYMEMBER wantmembers = False - attributes = ( 'cn', 'userPassword', 'gidNumber' ) + + def __init__(self, *args, **kwargs): + super(GroupByMemberRequest, self).__init__(*args, **kwargs) + # set up our own attributes that leave out membership attributes + self.attmap = common.Attributes(attmap) + del self.attmap['memberUid'] + del self.attmap['uniqueMember'] def read_parameters(self): self.memberuid = self.fp.read_string() common.validate_name(self.memberuid) + def attributes(self): + return self.attmap.attributes() + def mk_filter(self): - # try to translate uid to DN - # TODO: only do this if memberuid attribute is mapped - import passwd - dn = passwd.uid2dn(self.conn, self.memberuid) - if dn: - return '(&%s(|(%s=%s)(%s=%s)))' % ( self.filter, - self.attmap_group_memberUid, ldap.filter.escape_filter_chars(self.memberuid), - self.attmap_group_uniqueMember, ldap.filter.escape_filter_chars(dn) ) - else: - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_group_memberUid, ldap.filter.escape_filter_chars(self.memberuid) ) + if attmap['uniqueMember']: + dn = uid2dn(self.conn, self.memberuid) + if dn: + return '(&%s(|(%s=%s)(%s=%s)))' % ( self.filter, + attmap['memberUid'], ldap.filter.escape_filter_chars(self.memberuid), + attmap['uniqueMember'], ldap.filter.escape_filter_chars(dn) ) + return '(&%s(%s=%s))' % ( self.filter, + attmap['memberUid'], ldap.filter.escape_filter_chars(self.memberuid) ) class GroupAllRequest(GroupRequest): diff --git a/pynslcd/host.py b/pynslcd/host.py index b8c86d0..5fcbc31 100644 --- a/pynslcd/host.py +++ b/pynslcd/host.py @@ -18,33 +18,30 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import ldap.filter +import logging import constants import common -class HostRequest(common.Request): - - filter = '(objectClass=ipHost)' +attmap = common.Attributes(cn='cn', ipHostNumber='ipHostNumber') +filter = '(objectClass=ipHost)' - attmap_cn = 'cn' - attmap_ipHostNumber = 'ipHostNumber' - attributes = ( 'cn', 'ipHostNumber' ) +class HostRequest(common.Request): def write(self, dn, attributes): - hostname = common.get_rdn_value(dn, self.attmap_cn) - hostnames = attributes.get(self.attmap_cn, []) + hostname = common.get_rdn_value(dn, attmap['cn']) + hostnames = attributes['cn'] if not hostnames: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_cn ) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['cn'] ) if not hostname: hostname = hostnames.pop(0) elif hostname in hostnames: hostnames.remove(hostname) - addresses = attributes.get(self.attmap_ipHostNumber, []) + addresses = attributes['ipHostNumber'] if not addresses: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_ipHostNumber ) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['ipHostNumber'] ) # write result self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) self.fp.write_string(hostname) @@ -57,27 +54,20 @@ class HostRequest(common.Request): class HostByNameRequest(HostRequest): action = constants.NSLCD_ACTION_HOST_BYNAME + filter_attrs = dict(cn='name') def read_parameters(self): self.name = self.fp.read_string() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name) ) - class HostByAddressRequest(HostRequest): action = constants.NSLCD_ACTION_HOST_BYADDR + filter_attrs = dict(ipHostNumber='address') def read_parameters(self): self.address = self.fp.read_address() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_ipHostNumber, - ldap.filter.escape_filter_chars(self.address) ) - class HostAllRequest(HostRequest): diff --git a/pynslcd/netgroup.py b/pynslcd/netgroup.py index a7c6344..21be7a8 100644 --- a/pynslcd/netgroup.py +++ b/pynslcd/netgroup.py @@ -18,7 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import ldap.filter +import logging import re import constants @@ -28,30 +28,28 @@ import common _netgroup_triple_re = re.compile(r'^\s*\(\s*(?P.*)\s*,\s*(?P.*)\s*,\s*(?P.*)\s*\)\s*$') -class NetgroupRequest(common.Request): - - filter = '(objectClass=nisNetgroup)' +attmap = common.Attributes(cn='cn', + nisNetgroupTriple='nisNetgroupTriple', + memberNisNetgroup='memberNisNetgroup') +filter = '(objectClass=nisNetgroup)' - attmap_cn = 'cn' - attmap_nisNetgroupTriple = 'nisNetgroupTriple' - attmap_memberNisNetgroup = 'memberNisNetgroup' - attributes = ( 'cn', 'nisNetgroupTriple', 'memberNisNetgroup' ) +class NetgroupRequest(common.Request): def write(self, dn, attributes): # get names and check against requested user name - names = attributes.get(self.attmap_cn, []) + names = attributes['cn'] if self.name: if self.name not in names: return names = ( self.name, ) if not names: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_cn) + print 'Error: entry %s does not contain %s value' % (dn, attmap['cn']) # write the netgroup triples - for triple in attributes.get(self.attmap_nisNetgroupTriple, []): + for triple in attributes['nisNetgroupTriple']: m = _netgroup_triple_re.match(triple) if not m: - print 'Warning: entry %s contains invalid %s value: %r' % ( dn, self.attmap_nisNetgroupTriple, triple) + print 'Warning: entry %s contains invalid %s value: %r' % ( dn, attmap['nisNetgroupTriple'], triple) else: self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) self.fp.write_int32(constants.NSLCD_NETGROUP_TYPE_TRIPLE) @@ -59,7 +57,7 @@ class NetgroupRequest(common.Request): self.fp.write_string(m.group('user')) self.fp.write_string(m.group('domain')) # write netgroup members - for member in attributes.get(self.attmap_memberNisNetgroup, []): + for member in attributes['memberNisNetgroup']: self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) self.fp.write_int32(constants.NSLCD_NETGROUP_TYPE_NETGROUP) self.fp.write_string(member) @@ -68,10 +66,7 @@ class NetgroupRequest(common.Request): class NetgroupByNameRequest(NetgroupRequest): action = constants.NSLCD_ACTION_NETGROUP_BYNAME + filter_attrs = dict(cn='name') def read_parameters(self): self.name = self.fp.read_string() - - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name) ) diff --git a/pynslcd/network.py b/pynslcd/network.py index ca54cfe..a056327 100644 --- a/pynslcd/network.py +++ b/pynslcd/network.py @@ -18,33 +18,31 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import ldap.filter +import logging import constants import common -class NetworkRequest(common.Request): - - filter = '(objectClass=ipNetwork)' +attmap = common.Attributes(cn='cn', + ipNetworkNumber='ipNetworkNumber') +filter = '(objectClass=ipNetwork)' - attmap_cn = 'cn' - attmap_ipNetworkNumber = 'ipNetworkNumber' - attributes = ( 'cn', 'ipNetworkNumber' ) +class NetworkRequest(common.Request): def write(self, dn, attributes): - networkname = common.get_rdn_value(dn, self.attmap_cn) - networknames = attributes.get(self.attmap_cn, []) + networkname = common.get_rdn_value(dn, attmap['cn']) + networknames = attributes['cn'] if not networknames: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_cn) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['cn']) if not networkname: networkname = networknames.pop(0) elif networkname in networknames: networknames.remove(networkname) - addresses = attributes.get(self.attmap_ipNetworkNumber, []) + addresses = attributes['ipNetworkNumber'] if not addresses: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_ipNetworkNumber) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['ipNetworkNumber']) # write result self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) self.fp.write_string(networkname) @@ -57,27 +55,20 @@ class NetworkRequest(common.Request): class NetworkByNameRequest(NetworkRequest): action = constants.NSLCD_ACTION_NETWORK_BYNAME + filter_attrs = dict(cn='name') def read_parameters(self): self.name = self.fp.read_string() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name) ) - class NetworkByAddressRequest(NetworkRequest): action = constants.NSLCD_ACTION_NETWORK_BYADDR + filter_attrs = dict(ipNetworkNumber='address') def read_parameters(self): self.address = self.fp.read_address() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_ipNetworkNumber, - ldap.filter.escape_filter_chars(self.address) ) - class NetworkAllRequest(NetworkRequest): diff --git a/pynslcd/pam.py b/pynslcd/pam.py index 2a5e999..8149e87 100644 --- a/pynslcd/pam.py +++ b/pynslcd/pam.py @@ -55,16 +55,16 @@ class PAMRequest(common.Request): # save the DN self.userdn = entry[0] # get the "real" username - value = common.get_rdn_value(entry[0], passwd.PasswdRequest.attmap_passwd_uid) + value = common.get_rdn_value(entry[0], passwd.attmap['uid']) if not value: # get the username from the uid attribute - values = myldap_get_values(entry, passwd.PasswdRequest.attmap_passwd_uid) + values = myldap_get_values(entry, passwd.attmap['uid']) if not values or not values[0]: - logging.warn('%s: is missing a %s attribute', entry.dn, passwd.PasswdRequest.attmap_passwd_uid) + logging.warn('%s: is missing a %s attribute', dn, passwd.attmap['uid']) value = values[0] # check the username if value and not common.isvalidname(value): - raise ValueError('%s: has invalid %s attribute', entry.dn, passwd.PasswdRequest.attmap_passwd_uid) + raise ValueError('%s: has invalid %s attribute', dn, passwd.attmap['uid']) # check if the username is different and update it if needed if value != self.username: logging.info('username changed from %r to %r', self.username, value) diff --git a/pynslcd/passwd.py b/pynslcd/passwd.py index 1876b15..406e614 100644 --- a/pynslcd/passwd.py +++ b/pynslcd/passwd.py @@ -25,60 +25,46 @@ import constants import common -class PasswdRequest(common.Request): - - attmap = { 'uid': 'uid', 'userPassword': 'userPassword', - 'uidNumber': 'uidNumber', 'gidNumber': 'gidNumber', - 'gecos': '"${gecos:-$cn}"', 'cn': 'cn', - 'homeDirectory': 'homeDirectory', - 'loginShell': 'loginShell', - 'objectClass': 'objectClass' } - filter = '(objectClass=posixAccount)' - - attmap_passwd_uid = 'uid' - attmap_passwd_userPassword = 'userPassword' - attmap_passwd_uidNumber = 'uidNumber' - attmap_passwd_gidNumber = 'gidNumber' - attmap_passwd_gecos = '"${gecos:-$cn}"' - attmap_passwd_homeDirectory = 'homeDirectory' - attmap_passwd_loginShell = 'loginShell' - - # these should be removed - attmap_passwd_cn = 'cn' +attmap = common.Attributes(uid='uid', + userPassword='"*"', + uidNumber='uidNumber', + gidNumber='gidNumber', + gecos='"${gecos:-$cn}"', + homeDirectory='homeDirectory', + loginShell='loginShell', + objectClass='objectClass') +filter = '(objectClass=posixAccount)' +bases = ( 'ou=people,dc=test,dc=tld', ) - attributes = ( 'uid', 'userPassword', 'uidNumber', 'gidNumber', - 'gecos', 'cn', 'homeDirectory', 'loginShell', - 'objectClass' ) - bases = ( 'ou=people,dc=test,dc=tld', ) +class PasswdRequest(common.Request): def write(self, dn, attributes): # get uid attribute and check against requested user name - names = attributes.get('uid', []) + names = attributes['uid'] if self.name: if self.name not in names: return names = ( self.name, ) # get user password entry - if 'shadowAccount' in attributes.get('objectClass', []): + if 'shadowAccount' in attributes['objectClass']: passwd = 'x' else: - passwd = '*'; + passwd = attributes['userPassword'][0] # get numeric user and group ids - uids = ( self.uid, ) if self.uid else attributes.get(self.attmap_passwd_uidNumber, []) + uids = ( self.uid, ) if self.uid else attributes['uidNumber'] uids = [ int(x) for x in uids ] - ( gid, ) = attributes[self.attmap_passwd_gidNumber] - gid = int(gid) - # FIXME: use expression here - gecos = attributes.get(self.attmap_passwd_gecos, [None])[0] or attributes.get(self.attmap_passwd_cn, [''])[0] - ( home, ) = attributes.get(self.attmap_passwd_homeDirectory, ['']) - ( shell, ) = attributes.get(self.attmap_passwd_loginShell, ['']) + # get other passwd properties + gid = int(attributes['gidNumber'][0]) + gecos = attributes['gecos'][0] + home = attributes['homeDirectory'][0] + shell = attributes['loginShell'][0] + # write results for name in names: if not common.isvalidname(name): print 'Warning: passwd entry %s contains invalid user name: "%s"' % ( dn, name ) else: for uid in uids: - #print '%s:%s:%d:%d:%s:%s:%s' % ( name, passwd, uid, gid, gecos, home, shell ) self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) self.fp.write_string(name) self.fp.write_string(passwd) @@ -92,41 +78,37 @@ class PasswdRequest(common.Request): class PasswdByNameRequest(PasswdRequest): action = constants.NSLCD_ACTION_PASSWD_BYNAME + filter_attrs = dict(uid='name') def read_parameters(self): self.name = self.fp.read_string() common.validate_name(self.name) - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_passwd_uid, ldap.filter.escape_filter_chars(self.name) ) - class PasswdByUidRequest(PasswdRequest): action = constants.NSLCD_ACTION_PASSWD_BYUID + filter_attrs = dict(uidNumber='uid') def read_parameters(self): self.uid = self.fp.read_uid_t() - def mk_filter(self): - return '(&%s(%s=%d))' % ( self.filter, - self.attmap_passwd_uidNumber, self.uid ) - class PasswdAllRequest(PasswdRequest): action = constants.NSLCD_ACTION_PASSWD_ALL -def do_search(conn, filter=None, base=None): - mybases = ( base, ) if base else PasswdRequest.bases - filter = filter or PasswdRequest.filter +def do_search(conn, flt=None, base=None): + mybases = ( base, ) if base else bases + flt = flt or filter + import cfg # perform a search for each search base for base in mybases: # do the LDAP search try: - res = conn.search_s(base, PasswdRequest.scope, filter, [PasswdRequest.attmap_passwd_uid]) + scope = locals().get('scope', cfg.scope) + res = conn.search_s(base, scope, flt, [attmap['uid']]) for entry in res: if entry[0]: yield entry @@ -137,10 +119,10 @@ def do_search(conn, filter=None, base=None): def uid2entry(conn, uid): """Look up the user by uid and return the LDAP entry or None if the user was not found.""" - myfilter = '(&%s(%s=%s))' % ( PasswdRequest.filter, - PasswdRequest.attmap_passwd_uid, ldap.filter.escape_filter_chars(uid) ) + myfilter = '(&%s(%s=%s))' % ( filter, + attmap['uid'], ldap.filter.escape_filter_chars(uid) ) for dn, attributes in do_search(conn, myfilter): - if uid in attributes[PasswdRequest.attmap_passwd_uid]: + if uid in attributes[attmap['uid']]: return dn, attributes def uid2dn(conn, uid): @@ -150,11 +132,13 @@ def uid2dn(conn, uid): if x is not None: return x[0] +# FIXME: use cache of dn2uid and try to use DN to get uid attribute + def dn2uid(conn, dn): """Look up the user by dn and return a uid or None if the user was not found.""" try: for dn, attributes in do_search(conn, base=dn): - return attributes[PasswdRequest.attmap_passwd_uid][0] + return attributes[attmap['uid']][0] except ldap.NO_SUCH_OBJECT: return None diff --git a/pynslcd/protocol.py b/pynslcd/protocol.py index b14de99..a37e633 100644 --- a/pynslcd/protocol.py +++ b/pynslcd/protocol.py @@ -18,27 +18,24 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import ldap.filter +import logging import constants import common -class ProtocolRequest(common.Request): - - filter = '(objectClass=ipProtocol)' +attmap = common.Attributes(cn='cn', ipProtocolNumber='ipProtocolNumber') +filter = '(objectClass=ipProtocol)' - attmap_cn = 'cn' - attmap_ipProtocolNumber = 'ipProtocolNumber' - attributes = ( 'cn', 'ipProtocolNumber' ) +class ProtocolRequest(common.Request): def write(self, dn, attributes): # get name - name = common.get_rdn_value(dn, self.attmap_cn) - names = attributes.get(self.attmap_cn, []) + name = common.get_rdn_value(dn, attmap['cn']) + names = attributes['cn'] if not names: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_cn ) + print 'Error: entry %s does not contain %s value' % (dn, attmap['cn']) if self.name and self.name not in names: return # case of result entry did not match if not name: @@ -46,9 +43,9 @@ class ProtocolRequest(common.Request): elif name in names: names.remove(name) # get number - ( number, ) = attributes.get(self.attmap_ipProtocolNumber, []) + ( number, ) = attributes['ipProtocolNumber'] if not number: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_ipProtocolNumber) + print 'Error: entry %s does not contain %s value' % (dn, attmap['ipProtocolNumber']) number = int(number) # write result self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) @@ -60,26 +57,20 @@ class ProtocolRequest(common.Request): class ProtocolByNameRequest(ProtocolRequest): action = constants.NSLCD_ACTION_PROTOCOL_BYNAME + filter_attrs = dict(cn='name') def read_parameters(self): self.name = self.fp.read_string() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name) ) - class ProtocolByNumberRequest(ProtocolRequest): action = constants.NSLCD_ACTION_PROTOCOL_BYNUMBER + filter_attrs = dict(ipProtocolNumber='number') def read_parameters(self): self.number = self.fp.read_int32() - def mk_filter(self): - return '(&%s(%s=%d))' % ( self.filter, - self.attmap_ipProtocolNumber, self.number ) - class ProtocolAllRequest(ProtocolRequest): diff --git a/pynslcd/rpc.py b/pynslcd/rpc.py index 94c63e2..9e71893 100644 --- a/pynslcd/rpc.py +++ b/pynslcd/rpc.py @@ -18,27 +18,24 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import ldap.filter +import logging import constants import common -class RpcRequest(common.Request): - - filter = '(objectClass=oncRpc)' +attmap = common.Attributes(cn='cn', oncRpcNumber='oncRpcNumber') +filter = '(objectClass=oncRpc)' - attmap_cn = 'cn' - attmap_oncRpcNumber = 'oncRpcNumber' - attributes = ( 'cn', 'oncRpcNumber' ) +class RpcRequest(common.Request): def write(self, dn, attributes): # get name - name = common.get_rdn_value(dn, self.attmap_cn) - names = attributes.get(self.attmap_cn, []) + name = common.get_rdn_value(dn, attmap['cn']) + names = attributes['cn'] if not names: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_cn ) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['cn'] ) if self.name and self.name not in names: return # case of result entry did not match if not name: @@ -46,9 +43,9 @@ class RpcRequest(common.Request): elif name in names: names.remove(name) # get number - ( number, ) = attributes.get(self.attmap_oncRpcNumber, []) + ( number, ) = attributes['oncRpcNumber'] if not number: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_oncRpcNumber) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['oncRpcNumber']) number = int(number) # write result self.fp.write_int32(constants.NSLCD_RESULT_BEGIN) @@ -60,26 +57,20 @@ class RpcRequest(common.Request): class RpcByNameRequest(RpcRequest): action = constants.NSLCD_ACTION_RPC_BYNAME + filter_attrs = dict(cn='name') def read_parameters(self): self.name = self.fp.read_string() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name) ) - class RpcByNumberRequest(RpcRequest): action = constants.NSLCD_ACTION_RPC_BYNUMBER + filter_attrs = dict(oncRpcNumber='number') def read_parameters(self): self.number = self.fp.read_int32() - def mk_filter(self): - return '(&%s(%s=%d))' % ( self.filter, - self.attmap_oncRpcNumber, self.number ) - class RpcAllRequest(RpcRequest): diff --git a/pynslcd/service.py b/pynslcd/service.py index 106f744..6d97b87 100644 --- a/pynslcd/service.py +++ b/pynslcd/service.py @@ -18,21 +18,20 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA +import logging import ldap.filter import constants import common -class ServiceRequest(common.Request): - - filter = '(objectClass=ipService)' +attmap = common.Attributes(cn='cn', + ipServicePort='ipServicePort', + ipServiceProtocol='ipServiceProtocol') +filter = '(objectClass=ipService)' - attmap_cn = 'cn' - attmap_ipServicePort = 'ipServicePort' - attmap_ipServiceProtocol = 'ipServiceProtocol' - attributes = ( 'cn', 'ipServicePort', 'ipServiceProtocol' ) +class ServiceRequest(common.Request): def __init__(self, *args): super(ServiceRequest, self).__init__(*args) @@ -40,10 +39,10 @@ class ServiceRequest(common.Request): def write(self, dn, attributes): # get name - name = common.get_rdn_value(dn, self.attmap_cn) - names = attributes.get(self.attmap_cn, []) + name = common.get_rdn_value(dn, attmap['cn']) + names = attributes['cn'] if not names: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_cn ) + print 'Error: entry %s does not contain %s value' % (dn, attmap['cn']) if self.name and self.name not in names + [ name, ]: return # case of result entry did not match if not name: @@ -51,12 +50,12 @@ class ServiceRequest(common.Request): elif name in names: names.remove(name) # get port number - ( port, ) = attributes.get(self.attmap_ipServicePort, []) + ( port, ) = attributes['ipServicePort'] if not port: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_ipServicePort) + print 'Error: entry %s does not contain %s value' % (dn, attmap['ipServicePort']) port = int(port) # get protocol - protocols = attributes.get(self.attmap_ipServiceProtocol, []) + protocols = attributes['ipServiceProtocol'] if self.protocol: if self.protocol not in protocols: return @@ -80,12 +79,12 @@ class ServiceByNameRequest(ServiceRequest): def mk_filter(self): if self.protocol: - return '(&%s(%s=%s)(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name), - self.attmap_ipServiceProtocol, ldap.filter.escape_filter_chars(self.protocol) ) + return '(&%s(%s=%s)(%s=%s))' % ( self.filter, + attmap['cn'], ldap.filter.escape_filter_chars(self.name), + attmap['ipServiceProtocol'], ldap.filter.escape_filter_chars(self.protocol) ) else: - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_cn, ldap.filter.escape_filter_chars(self.name) ) + return '(&%s(%s=%s))' % ( self.filter, + attmap['cn'], ldap.filter.escape_filter_chars(self.name) ) class ServiceByNumberRequest(ServiceRequest): @@ -98,12 +97,12 @@ class ServiceByNumberRequest(ServiceRequest): def mk_filter(self): if self.protocol: - return '(&%s(%s=%d)(%s=%s))' % ( self.filter, - self.attmap_ipServicePort, self.number, - self.attmap_ipServiceProtocol, ldap.filter.escape_filter_chars(self.protocol) ) + return '(&%s(%s=%d)(%s=%s))' % ( self.filter, + attmap['ipServicePort'], self.number, + attmap['ipServiceProtocol'], ldap.filter.escape_filter_chars(self.protocol) ) else: - return '(&%s(%s=%d))' % ( self.filter, - self.attmap_ipServicePort, self.number ) + return '(&%s(%s=%d))' % ( self.filter, + attmap['ipServicePort'], self.number ) class ServiceAllRequest(ServiceRequest): diff --git a/pynslcd/shadow.py b/pynslcd/shadow.py index ed54eab..4408c0c 100644 --- a/pynslcd/shadow.py +++ b/pynslcd/shadow.py @@ -18,46 +18,41 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -import ldap.filter +import logging import constants import common -class ShadowRequest(common.Request): - - filter = '(objectClass=shadowAccount)' - - attmap_uid = 'uid' - attmap_userPassword = 'userPassword' - attmap_shadowLastChange = 'shadowLastChange' - attmap_shadowMin = 'shadowMin' - attmap_shadowMax = 'shadowMax' - attmap_shadowWarning = 'shadowWarning' - attmap_shadowInactive = 'shadowInactive' - attmap_shadowExpire = 'shadowExpire' - attmap_shadowFlag = 'shadowFlag' +attmap = common.Attributes(uid='uid', + userPassword='"*"', + shadowLastChange='"${shadowLastChange:--1}"', + shadowMin='"${shadowMin:--1}"', + shadowMax='"${shadowMax:--1}"', + shadowWarning='"${shadowWarning:--1}"', + shadowInactive='"${shadowInactive:--1}"', + shadowExpire='"${shadowExpire:--1}"', + shadowFlag='"${shadowFlag:-0}"') +filter = '(objectClass=shadowAccount)' +bases = ( 'ou=people,dc=test,dc=tld', ) - attributes = ( 'uid', 'userPassword', 'shadowLastChange', 'shadowMin', - 'shadowMax', 'shadowWarning', 'shadowInactive', - 'shadowExpire', 'shadowFlag' ) - bases = ( 'ou=people,dc=test,dc=tld', ) +class ShadowRequest(common.Request): def write(self, dn, attributes): # get name and check against requested name - names = attributes.get(self.attmap_uid, []) + names = attributes['uid'] if not names: - print 'Error: entry %s does not contain %s value' % ( dn, self.attmap_uid) + print 'Error: entry %s does not contain %s value' % ( dn, attmap['uid'] ) return if self.name: if self.name not in names: return names = ( self.name, ) # get password - (passwd, ) = attributes.get(self.attmap_userPassword, ['x']) + (passwd, ) = attributes['userPassword'] if not passwd or self.calleruid != 0: - passwd = '*'; + passwd = '*' # function for making an int def mk_int(attr): try: @@ -65,22 +60,22 @@ class ShadowRequest(common.Request): except TypeError: return None # get lastchange date - lastchangedate = int(attributes.get(self.attmap_shadowLastChange, [-1])[0]) + lastchangedate = int(attributes.get('shadowLastChange', [0])[0]) # we expect an AD 64-bit datetime value; # we should do date=date/864000000000-134774 # but that causes problems on 32-bit platforms, # first we devide by 1000000000 by stripping the # last 9 digits from the string and going from there */ - if self.attmap_shadowLastChange == 'pwdLastSet': + if attmap['shadowLastChange'] == 'pwdLastSet': lastchangedate = ( lastchangedate / 864000000000 ) - 134774 # get longs - mindays = int(attributes.get(self.attmap_shadowMin, [-1])[0]) - maxdays = int(attributes.get(self.attmap_shadowMax, [-1])[0]) - warndays = int(attributes.get(self.attmap_shadowWarning, [-1])[0]) - inactdays = int(attributes.get(self.attmap_shadowInactive, [-1])[0]) - expiredate = int(attributes.get(self.attmap_shadowExpire, [-1])[0]) - flag = int(attributes.get(self.attmap_shadowFlag, [0])[0]) - if self.attmap_shadowFlag == 'pwdLastSet': + mindays = int(attributes.get('shadowMin', [-1])[0]) + maxdays = int(attributes.get('shadowMax', [-1])[0]) + warndays = int(attributes.get('shadowWarning', [-1])[0]) + inactdays = int(attributes.get('shadowInactive', [-1])[0]) + expiredate = int(attributes.get('shadowExpire', [-1])[0]) + flag = int(attributes.get('shadowFlag', [0])[0]) + if attmap['shadowFlag'] == 'pwdLastSet': if flag & 0x10000: maxdays = -1 flag = 0 @@ -101,14 +96,11 @@ class ShadowRequest(common.Request): class ShadowByNameRequest(ShadowRequest): action = constants.NSLCD_ACTION_SHADOW_BYNAME + filter_attrs = dict(uid='name') def read_parameters(self): self.name = self.fp.read_string() - def mk_filter(self): - return '(&%s(%s=%s))' % ( self.filter, - self.attmap_uid, ldap.filter.escape_filter_chars(self.name) ) - class ShadowAllRequest(ShadowRequest): diff --git a/pynslcd/tio.py b/pynslcd/tio.py index 5bf70ca..f7091c5 100644 --- a/pynslcd/tio.py +++ b/pynslcd/tio.py @@ -21,7 +21,7 @@ import struct import os import socket -import errno + # definition for reading and writing INT32 values _int32 = struct.Struct('i') -- cgit v1.2.3