Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/pynslcd
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2013-03-09 23:02:29 +0100
committerArthur de Jong <arthur@arthurdejong.org>2013-03-09 23:27:00 +0100
commit4e603409f76c14ba7b11c437eac6116a2afce603 (patch)
tree2d27a197193b6fbde8c1ef7fc47c12154e575d9d /pynslcd
parent975ee2ce45d050d1f8aafacfe58d035fff339e79 (diff)
move Search class to search module
Diffstat (limited to 'pynslcd')
-rw-r--r--pynslcd/Makefile.am4
-rw-r--r--pynslcd/alias.py5
-rw-r--r--pynslcd/common.py128
-rw-r--r--pynslcd/ether.py3
-rw-r--r--pynslcd/group.py5
-rw-r--r--pynslcd/host.py5
-rw-r--r--pynslcd/netgroup.py3
-rw-r--r--pynslcd/network.py5
-rw-r--r--pynslcd/pam.py5
-rw-r--r--pynslcd/passwd.py5
-rw-r--r--pynslcd/protocol.py5
-rw-r--r--pynslcd/rpc.py5
-rw-r--r--pynslcd/search.py144
-rw-r--r--pynslcd/service.py5
-rw-r--r--pynslcd/shadow.py5
15 files changed, 184 insertions, 148 deletions
diff --git a/pynslcd/Makefile.am b/pynslcd/Makefile.am
index 1381b6b..cff5629 100644
--- a/pynslcd/Makefile.am
+++ b/pynslcd/Makefile.am
@@ -1,6 +1,6 @@
# Makefile.am - use automake to generate Makefile.in
#
-# Copyright (C) 2010, 2011, 2012 Arthur de Jong
+# Copyright (C) 2010, 2011, 2012, 2013 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
@@ -20,7 +20,7 @@
pynslcddir = $(datadir)/pynslcd
pynslcd_PYTHON = pynslcd.py attmap.py cache.py cfg.py common.py expr.py \
- mypidfile.py tio.py \
+ mypidfile.py search.py tio.py \
alias.py ether.py group.py host.py netgroup.py network.py \
pam.py passwd.py protocol.py rpc.py service.py shadow.py
nodist_pynslcd_PYTHON = constants.py
diff --git a/pynslcd/alias.py b/pynslcd/alias.py
index 06b1e44..46c4d6b 100644
--- a/pynslcd/alias.py
+++ b/pynslcd/alias.py
@@ -1,7 +1,7 @@
# alias.py - lookup functions for email aliases
#
-# Copyright (C) 2010, 2011, 2012 Arthur de Jong
+# Copyright (C) 2010, 2011, 2012, 2013 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
@@ -21,13 +21,14 @@
import cache
import common
import constants
+import search
attmap = common.Attributes(cn='cn', rfc822MailMember='rfc822MailMember')
filter = '(objectClass=nisMailAlias)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_insensitive = ('cn', )
limit_attributes = ('cn', )
diff --git a/pynslcd/common.py b/pynslcd/common.py
index 208e321..bbffef4 100644
--- a/pynslcd/common.py
+++ b/pynslcd/common.py
@@ -1,7 +1,7 @@
# common.py - functions that are used by different modules
#
-# Copyright (C) 2010, 2011, 2012 Arthur de Jong
+# Copyright (C) 2010, 2011, 2012, 2013 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
@@ -22,7 +22,6 @@ import logging
import sys
import ldap
-import ldap.dn
from attmap import Attributes
#import cache
@@ -53,125 +52,6 @@ def validate_name(name):
raise ValueError('%r: denied by validnames option' % name)
-class Search(object):
- """
- Class that performs a search. Subclasses are expected to define the actual
- searches and should implement the following members:
-
- case_sensitive - check that these attributes are present in the response
- if they were in the request
- case_insensitive - check that these attributes are present in the
- response if they were in the request
- limit_attributes - override response attributes with request attributes
- (ensure that only one copy of the value is returned)
- required - attributes that are required
- canonical_first - search the DN for these attributes and ensure that
- they are listed first in the attribute values
- mk_filter() (optional) - function that returns the LDAP search filter
-
- The module that contains the Request class can also contain the following
- definitions:
-
- attmap - an attribute mapping definition (using he Attributes class)
- filter - an LDAP search filter
- bases - search bases to be used, falls back to cfg.bases
- scope - search scope, falls back to cfg.scope
-
- """
-
- canonical_first = []
- required = []
- case_sensitive = []
- case_insensitive = []
- limit_attributes = []
-
-# FIXME: figure out which of these arguments are actually needed
-
- def __init__(self, conn, base=None, scope=None, filter=None, attributes=None,
- parameters=None):
- # load information from module that defines the class
- self.conn = conn
- module = sys.modules[self.__module__]
- self.attmap = getattr(module, 'attmap', None)
- self.filter = filter or getattr(module, 'filter', None)
- self.parameters = parameters or {}
- if base:
- self.bases = [base]
- else:
- self.bases = getattr(module, 'bases', cfg.bases)
- self.scope = scope or getattr(module, 'scope', cfg.scope)
- self.attributes = attributes or self.attmap.attributes()
-
- def __iter__(self):
- return self.items()
-
- def items(self):
- """Return the results from the search."""
- filter = self.mk_filter()
- for base in self.bases:
- logging.debug('SEARCHING %s %s', base, filter)
- try:
- for entry in self.conn.search_s(base, self.scope, filter, self.attributes):
- if entry[0]:
- entry = self.handle_entry(entry[0], entry[1])
- if entry:
- yield entry
- except ldap.NO_SUCH_OBJECT:
- # FIXME: log message
- pass
-
- def escape(self, value):
- """Escape the provided value so it may be used in a search filter."""
- return ldap.filter.escape_filter_chars(str(value))
-
- def mk_filter(self):
- """Return the active search filter (based on the read parameters)."""
- if self.parameters:
- return '(&%s%s)' % (
- self.filter,
- ''.join(self.attmap.mk_filter(attribute, value)
- for attribute, value in self.parameters.items()))
- return self.filter
-
- def handle_entry(self, dn, attributes):
- """Handle an entry with the specified attributes, filtering it with
- the request parameters where needed."""
- # translate the attributes using the attribute mapping
- if self.attmap:
- attributes = self.attmap.translate(attributes)
- # make sure value from DN is first value
- for attr in self.canonical_first:
- primary_value = self.attmap.get_rdn_value(dn, attr)
- if primary_value:
- values = attributes[attr]
- if primary_value in values:
- values.remove(primary_value)
- attributes[attr] = [primary_value] + values
- # check that these attributes have at least one value
- for attr in self.required:
- if not attributes.get(attr, None):
- logging.warning('%s: %s: missing', dn, self.attmap[attr])
- return
- # check that requested attribute is present (case sensitive)
- for attr in self.case_sensitive:
- value = self.parameters.get(attr, None)
- if value and str(value) not in attributes[attr]:
- logging.debug('%s: %s: does not contain %r value', dn, self.attmap[attr], value)
- return # not found, skip entry
- # check that requested attribute is present (case insensitive)
- for attr in self.case_insensitive:
- value = self.parameters.get(attr, None)
- if value and str(value).lower() not in (x.lower() for x in attributes[attr]):
- logging.debug('%s: %s: does not contain %r value', dn, self.attmap[attr], value)
- return # not found, skip entry
- # limit attribute values to requested value
- for attr in self.limit_attributes:
- if attr in self.parameters:
- attributes[attr] = [self.parameters[attr]]
- # return the entry
- return dn, attributes
-
-
class Request(object):
"""
Request handler class. Subclasses are expected to handle actual requests
@@ -198,8 +78,8 @@ class Request(object):
self.cache = None
def read_parameters(self, fp):
- """This method should read the parameters from ths stream and
- store them in self."""
+ """This method should read and return the parameters from the
+ stream."""
pass
def handle_request(self, parameters):
@@ -208,7 +88,7 @@ class Request(object):
try:
#with cache.con:
if True:
- for dn, attributes in self.search(conn=self.conn, parameters=parameters):
+ 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)
diff --git a/pynslcd/ether.py b/pynslcd/ether.py
index e6975d5..d5d8c06 100644
--- a/pynslcd/ether.py
+++ b/pynslcd/ether.py
@@ -23,6 +23,7 @@ import struct
import cache
import common
import constants
+import search
def ether_aton(ether):
@@ -40,7 +41,7 @@ attmap = common.Attributes(cn='cn', macAddress='macAddress')
filter = '(objectClass=ieee802Device)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_insensitive = ('cn', )
limit_attributes = ('cn', 'macAddress')
diff --git a/pynslcd/group.py b/pynslcd/group.py
index ab07adc..a9626a3 100644
--- a/pynslcd/group.py
+++ b/pynslcd/group.py
@@ -1,7 +1,7 @@
# group.py - group entry lookup routines
#
-# Copyright (C) 2010, 2011, 2012 Arthur de Jong
+# Copyright (C) 2010, 2011, 2012, 2013 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
@@ -25,6 +25,7 @@ from passwd import dn2uid, uid2dn
import cache
import common
import constants
+import search
def clean(lst):
@@ -41,7 +42,7 @@ attmap = common.Attributes(cn='cn',
filter = '(objectClass=posixGroup)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_sensitive = ('cn', )
limit_attributes = ('cn', 'gidNumber')
diff --git a/pynslcd/host.py b/pynslcd/host.py
index 49de45b..ffd9588 100644
--- a/pynslcd/host.py
+++ b/pynslcd/host.py
@@ -1,7 +1,7 @@
# host.py - lookup functions for host names and addresses
#
-# Copyright (C) 2011, 2012 Arthur de Jong
+# Copyright (C) 2011, 2012, 2013 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
@@ -21,13 +21,14 @@
import cache
import common
import constants
+import search
attmap = common.Attributes(cn='cn', ipHostNumber='ipHostNumber')
filter = '(objectClass=ipHost)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
canonical_first = ('cn', )
required = ('cn', )
diff --git a/pynslcd/netgroup.py b/pynslcd/netgroup.py
index aa6d79a..ca7fb13 100644
--- a/pynslcd/netgroup.py
+++ b/pynslcd/netgroup.py
@@ -24,6 +24,7 @@ import re
import cache
import common
import constants
+import search
_netgroup_triple_re = re.compile(r'^\s*\(\s*(?P<host>.*)\s*,\s*(?P<user>.*)\s*,\s*(?P<domain>.*)\s*\)\s*$')
@@ -35,7 +36,7 @@ attmap = common.Attributes(cn='cn',
filter = '(objectClass=nisNetgroup)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_sensitive = ('cn', )
required = ('cn', )
diff --git a/pynslcd/network.py b/pynslcd/network.py
index 88778d7..dc91d68 100644
--- a/pynslcd/network.py
+++ b/pynslcd/network.py
@@ -1,7 +1,7 @@
# network.py - lookup functions for network names and addresses
#
-# Copyright (C) 2011, 2012 Arthur de Jong
+# Copyright (C) 2011, 2012, 2013 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
@@ -21,6 +21,7 @@
import cache
import common
import constants
+import search
attmap = common.Attributes(cn='cn',
@@ -28,7 +29,7 @@ attmap = common.Attributes(cn='cn',
filter = '(objectClass=ipNetwork)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
canonical_first = ('cn', )
required = ('cn', )
diff --git a/pynslcd/pam.py b/pynslcd/pam.py
index abde4de..f2493ec 100644
--- a/pynslcd/pam.py
+++ b/pynslcd/pam.py
@@ -29,6 +29,7 @@ import cfg
import common
import constants
import passwd
+import search
def try_bind(userdn, password):
@@ -188,9 +189,9 @@ class PAMAuthorisationRequest(PAMRequest):
for x in cfg.pam_authz_searches:
filter = x.value(variables)
logging.debug('trying pam_authz_search "%s"', filter)
- search = common.Search(self.conn, filter=filter, attributes=('dn', ))
+ srch = search.LDAPSearch(self.conn, filter=filter, attributes=('dn', ))
try:
- dn, values = search.items().next()
+ dn, values = srch.items().next()
except StopIteration:
logging.error('pam_authz_search "%s" found no matches', filter)
raise
diff --git a/pynslcd/passwd.py b/pynslcd/passwd.py
index 222d3c6..3be7885 100644
--- a/pynslcd/passwd.py
+++ b/pynslcd/passwd.py
@@ -1,7 +1,7 @@
# passwd.py - lookup functions for user account information
#
-# Copyright (C) 2010, 2011, 2012 Arthur de Jong
+# Copyright (C) 2010, 2011, 2012, 2013 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
@@ -23,6 +23,7 @@ import logging
import cache
import common
import constants
+import search
attmap = common.Attributes(uid='uid',
@@ -36,7 +37,7 @@ attmap = common.Attributes(uid='uid',
filter = '(objectClass=posixAccount)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_sensitive = ('uid', 'uidNumber', )
limit_attributes = ('uid', 'uidNumber', )
diff --git a/pynslcd/protocol.py b/pynslcd/protocol.py
index 91feaea..cafda9d 100644
--- a/pynslcd/protocol.py
+++ b/pynslcd/protocol.py
@@ -1,7 +1,7 @@
# protocol.py - protocol name and number lookup routines
#
-# Copyright (C) 2011, 2012 Arthur de Jong
+# Copyright (C) 2011, 2012, 2013 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
@@ -21,13 +21,14 @@
import cache
import common
import constants
+import search
attmap = common.Attributes(cn='cn', ipProtocolNumber='ipProtocolNumber')
filter = '(objectClass=ipProtocol)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_sensitive = ('cn', )
canonical_first = ('cn', )
diff --git a/pynslcd/rpc.py b/pynslcd/rpc.py
index c667ffa..f20960e 100644
--- a/pynslcd/rpc.py
+++ b/pynslcd/rpc.py
@@ -1,7 +1,7 @@
# rpc.py - rpc name lookup routines
#
-# Copyright (C) 2011, 2012 Arthur de Jong
+# Copyright (C) 2011, 2012, 2013 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
@@ -21,13 +21,14 @@
import cache
import common
import constants
+import search
attmap = common.Attributes(cn='cn', oncRpcNumber='oncRpcNumber')
filter = '(objectClass=oncRpc)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_sensitive = ('cn', )
canonical_first = ('cn', )
diff --git a/pynslcd/search.py b/pynslcd/search.py
new file mode 100644
index 0000000..60d36ff
--- /dev/null
+++ b/pynslcd/search.py
@@ -0,0 +1,144 @@
+
+# search.py - functions for searching the LDAP database
+#
+# Copyright (C) 2010, 2011, 2012, 2013 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
+
+import logging
+import sys
+
+import ldap
+
+import cfg
+
+
+class LDAPSearch(object):
+ """
+ Class that performs an LDAP search. Subclasses are expected to define the
+ actual searches and should implement the following members:
+
+ case_sensitive - check that these attributes are present in the response
+ if they were in the request
+ case_insensitive - check that these attributes are present in the
+ response if they were in the request
+ limit_attributes - override response attributes with request attributes
+ (ensure that only one copy of the value is returned)
+ required - attributes that are required
+ canonical_first - search the DN for these attributes and ensure that
+ they are listed first in the attribute values
+ mk_filter() (optional) - function that returns the LDAP search filter
+
+ The module that contains the Search class can also contain the following
+ definitions:
+
+ bases - list of search bases to be used, if absent or empty falls back
+ to cfg.bases
+ scope - search scope, falls back to cfg.scope if absent or empty
+ filter - an LDAP search filter
+ attmap - an attribute mapping definition (using he Attributes class)
+
+ """
+
+ canonical_first = []
+ required = []
+ case_sensitive = []
+ case_insensitive = []
+ limit_attributes = []
+
+ def __init__(self, conn, base=None, scope=None, filter=None,
+ attributes=None, parameters=None):
+ self.conn = conn
+ # load information from module that defines the class
+ module = sys.modules[self.__module__]
+ if base:
+ self.bases = [base]
+ else:
+ self.bases = getattr(module, 'bases', cfg.bases)
+ self.scope = scope or getattr(module, 'scope', cfg.scope)
+ self.filter = filter or getattr(module, 'filter', None)
+ self.attmap = getattr(module, 'attmap', None)
+ self.attributes = attributes or self.attmap.attributes()
+ self.parameters = parameters or {}
+
+ def __iter__(self):
+ return self.items()
+
+ def items(self):
+ """Return the results from the search."""
+ filter = self.mk_filter()
+ for base in self.bases:
+ logging.debug('LDAPSearch(base=%r, filter=%r)', base, filter)
+ try:
+ for entry in self.conn.search_s(base, self.scope, filter, self.attributes):
+ if entry[0]:
+ entry = self._transform(entry[0], entry[1])
+ if entry:
+ yield entry
+ except ldap.NO_SUCH_OBJECT:
+ # FIXME: log message
+ pass
+
+ def escape(self, value):
+ """Escape the provided value so it may be used in a search filter."""
+ return ldap.filter.escape_filter_chars(str(value))
+
+ def mk_filter(self):
+ """Return the active search filter (based on the read parameters)."""
+ if self.parameters:
+ return '(&%s%s)' % (
+ self.filter,
+ ''.join(self.attmap.mk_filter(attribute, value)
+ for attribute, value in self.parameters.items()))
+ return self.filter
+
+ def _transform(self, dn, attributes):
+ """Handle a single search result entry filtering it with the request
+ parameters, search options and attribute mapping."""
+ # translate the attributes using the attribute mapping
+ if self.attmap:
+ attributes = self.attmap.translate(attributes)
+ # make sure value from DN is first value
+ for attr in self.canonical_first:
+ primary_value = self.attmap.get_rdn_value(dn, attr)
+ if primary_value:
+ values = attributes[attr]
+ if primary_value in values:
+ values.remove(primary_value)
+ attributes[attr] = [primary_value] + values
+ # check that these attributes have at least one value
+ for attr in self.required:
+ if not attributes.get(attr, None):
+ logging.warning('%s: %s: missing', dn, self.attmap[attr])
+ return
+ # check that requested attribute is present (case sensitive)
+ for attr in self.case_sensitive:
+ value = self.parameters.get(attr, None)
+ if value and str(value) not in attributes[attr]:
+ logging.debug('%s: %s: does not contain %r value', dn, self.attmap[attr], value)
+ return # not found, skip entry
+ # check that requested attribute is present (case insensitive)
+ for attr in self.case_insensitive:
+ value = self.parameters.get(attr, None)
+ if value and str(value).lower() not in (x.lower() for x in attributes[attr]):
+ logging.debug('%s: %s: does not contain %r value', dn, self.attmap[attr], value)
+ return # not found, skip entry
+ # limit attribute values to requested value
+ for attr in self.limit_attributes:
+ if attr in self.parameters:
+ attributes[attr] = [self.parameters[attr]]
+ # return the entry
+ return dn, attributes
diff --git a/pynslcd/service.py b/pynslcd/service.py
index a3c00f1..f3082e6 100644
--- a/pynslcd/service.py
+++ b/pynslcd/service.py
@@ -1,7 +1,7 @@
# service.py - service entry lookup routines
#
-# Copyright (C) 2011, 2012 Arthur de Jong
+# Copyright (C) 2011, 2012, 2013 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
@@ -25,6 +25,7 @@ import logging
import cache
import common
import constants
+import search
attmap = common.Attributes(cn='cn',
@@ -33,7 +34,7 @@ attmap = common.Attributes(cn='cn',
filter = '(objectClass=ipService)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_sensitive = ('cn', 'ipServiceProtocol')
limit_attributes = ('ipServiceProtocol', )
diff --git a/pynslcd/shadow.py b/pynslcd/shadow.py
index a8d3382..e0170e2 100644
--- a/pynslcd/shadow.py
+++ b/pynslcd/shadow.py
@@ -1,7 +1,7 @@
# shadow.py - lookup functions for shadow information
#
-# Copyright (C) 2010, 2011, 2012 Arthur de Jong
+# Copyright (C) 2010, 2011, 2012, 2013 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
@@ -23,6 +23,7 @@ import logging
import cache
import common
import constants
+import search
attmap = common.Attributes(uid='uid',
@@ -37,7 +38,7 @@ attmap = common.Attributes(uid='uid',
filter = '(objectClass=shadowAccount)'
-class Search(common.Search):
+class Search(search.LDAPSearch):
case_sensitive = ('uid', )
limit_attributes = ('uid', )