lists.arthurdejong.org
RSS feed

nss-pam-ldapd branch master updated. 0.8.12-116-gba5f39f

[Date Prev][Date Next] [Thread Prev][Thread Next]

nss-pam-ldapd branch master updated. 0.8.12-116-gba5f39f



This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "nss-pam-ldapd".

The branch, master has been updated
       via  ba5f39f7c21cf3444ca2e84b0c02e89a1f36cd66 (commit)
       via  11ca816768cc10cb8df6fb989aaf2ea9733f4431 (commit)
       via  116d215765ca718b7ee69ef02f39b5cb093bf76d (commit)
       via  ac30060ba57112c23b36cf016f7776e5b6af0d9b (commit)
       via  4e603409f76c14ba7b11c437eac6116a2afce603 (commit)
       via  975ee2ce45d050d1f8aafacfe58d035fff339e79 (commit)
      from  8a67c9ff828aa4e669a25b1639d57dd9fab76312 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=ba5f39f7c21cf3444ca2e84b0c02e89a1f36cd66

commit ba5f39f7c21cf3444ca2e84b0c02e89a1f36cd66
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 9 23:21:03 2013 +0100

    log hex value of action id to make debugging easier

diff --git a/pynslcd/pynslcd.py b/pynslcd/pynslcd.py
index 0f234c3..6c2d28a 100755
--- a/pynslcd/pynslcd.py
+++ b/pynslcd/pynslcd.py
@@ -214,7 +214,7 @@ def acceptconnection(session):
         try:
             handler = handlers[action]
         except KeyError:
-            logging.warning('invalid action id: %r', action)
+            logging.warning('invalid action id: 0x%08x', action)
             return
         handler(fp, session, uid)()
     finally:

http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=11ca816768cc10cb8df6fb989aaf2ea9733f4431

commit 11ca816768cc10cb8df6fb989aaf2ea9733f4431
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 9 22:46:38 2013 +0100

    ensure consistent naming of DN variables

diff --git a/pynslcd/pam.py b/pynslcd/pam.py
index c26c6a9..4009d71 100644
--- a/pynslcd/pam.py
+++ b/pynslcd/pam.py
@@ -32,12 +32,12 @@ import passwd
 import search
 
 
-def try_bind(userdn, password):
+def try_bind(binddn, password):
     # open a new connection
     conn = search.Connection()
     # bind using the specified credentials
     pwctrl = PasswordPolicyControl()
-    res, data, msgid, ctrls = conn.simple_bind_s(userdn, password, 
serverctrls=[pwctrl])
+    res, data, msgid, ctrls = conn.simple_bind_s(binddn, password, 
serverctrls=[pwctrl])
     # go over bind result server controls
     for ctrl in ctrls:
         if ctrl.controlType == PasswordPolicyControl.controlType:
@@ -58,9 +58,9 @@ def try_bind(userdn, password):
             elif ctrl.graceAuthNsRemaining is not None:
                 return constants.NSLCD_PAM_NEW_AUTHTOK_REQD, 'Password 
expired, %d grace logins left' % ctrl.graceAuthNsRemaining
     # perform search for own object (just to do any kind of search)
-    results = conn.search_s(userdn, ldap.SCOPE_BASE, '(objectClass=*)', ['dn', 
])
+    results = conn.search_s(binddn, ldap.SCOPE_BASE, '(objectClass=*)', ['dn', 
])
     for entry in results:
-        if entry[0] == userdn:
+        if entry[0] == binddn:
             return constants.NSLCD_PAM_SUCCESS, ''
     # if our DN wasn't found raise an error to signal bind failure
     raise ldap.NO_SUCH_OBJECT()
@@ -86,11 +86,11 @@ class PAMRequest(common.Request):
             # get the username from the uid attribute
             values = entry[1]['uid']
             if not values or not values[0]:
-                logging.warning('%s: is missing a %s attribute', dn, 
passwd.attmap['uid'])
+                logging.warning('%s: is missing a %s attribute', entry[0], 
passwd.attmap['uid'])
             value = values[0]
         # check the username
         if value and not common.isvalidname(value):
-            raise ValueError('%s: has invalid %s attribute', dn, 
passwd.attmap['uid'])
+            raise ValueError('%s: has invalid %s attribute', entry[0], 
passwd.attmap['uid'])
         # check if the username is different and update it if needed
         if value != parameters['username']:
             logging.info('username changed from %r to %r', 
parameters['username'], value)
@@ -108,7 +108,6 @@ class PAMAuthenticationRequest(PAMRequest):
                     rhost=fp.read_string(),
                     tty=fp.read_string(),
                     password=fp.read_string())
-        #self.validate_request()
         # TODO: log call with parameters
 
     def write(self, username, authc=constants.NSLCD_PAM_SUCCESS,
@@ -139,7 +138,7 @@ class PAMAuthenticationRequest(PAMRequest):
             password = parameters['password']
         # try authentication
         try:
-            authz, msg = try_bind(userdn, password)
+            authz, msg = try_bind(binddn, password)
         except ldap.INVALID_CREDENTIALS, e:
             try:
                 msg = e[0]['desc']
@@ -149,7 +148,7 @@ class PAMAuthenticationRequest(PAMRequest):
             self.write(parameters['username'], 
authc=constants.NSLCD_PAM_AUTH_ERR, msg=msg)
             return
         if authz != constants.NSLCD_PAM_SUCCESS:
-            logging.warning('%s: %s: %s', userdn, parameters['username'], msg)
+            logging.warning('%s: %s: %s', binddn, parameters['username'], msg)
         else:
             logging.debug('bind successful')
         # FIXME: perform shadow attribute checks with check_shadow()

http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=116d215765ca718b7ee69ef02f39b5cb093bf76d

commit 116d215765ca718b7ee69ef02f39b5cb093bf76d
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 9 22:55:09 2013 +0100

    clean up imports and use ldap.filter.escape_filter_chars() directly

diff --git a/pynslcd/attmap.py b/pynslcd/attmap.py
index c35d56f..3545a96 100644
--- a/pynslcd/attmap.py
+++ b/pynslcd/attmap.py
@@ -1,7 +1,7 @@
 
 # attmap.py - attribute mapping class
 #
-# 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
@@ -37,9 +37,9 @@ True
 '"${gecos:-$cn}"'
 """
 
-import ldap
+from ldap.filter import escape_filter_chars
+import ldap.dn
 import re
-from ldap.filter import escape_filter_chars as escape
 
 from expr import Expression
 
@@ -62,7 +62,9 @@ class SimpleMapping(str):
         return [self]
 
     def mk_filter(self, value):
-        return '(%s=%s)' % (self, escape(str(value)))
+        return '(%s=%s)' % (
+                self, escape_filter_chars(str(value))
+            )
 
     def values(self, variables):
         """Expand the expression using the variables specified."""
@@ -106,7 +108,9 @@ class FunctionMapping(str):
         return [self.attribute]
 
     def mk_filter(self, value):
-        return '(%s=%s)' % (self.attribute, escape(value))
+        return '(%s=%s)' % (
+                self.attribute, escape_filter_chars(value)
+            )
 
     def values(self, variables):
         return [self.function(value)
@@ -163,4 +167,7 @@ class Attributes(dict):
     def get_rdn_value(self, dn, attribute):
         """Extract the attribute value from from DN if possible. Return None
         otherwise."""
-        return self.translate(dict((x, [y]) for x, y, z in 
ldap.dn.str2dn(dn)[0]))[attribute][0]
+        return self.translate(dict(
+                (x, [y])
+                for x, y, z in ldap.dn.str2dn(dn)[0]
+            ))[attribute][0]
diff --git a/pynslcd/group.py b/pynslcd/group.py
index a9626a3..a43aae5 100644
--- a/pynslcd/group.py
+++ b/pynslcd/group.py
@@ -21,6 +21,8 @@
 import itertools
 import logging
 
+from ldap.filter import escape_filter_chars
+
 from passwd import dn2uid, uid2dn
 import cache
 import common
@@ -62,8 +64,8 @@ class Search(search.LDAPSearch):
             dn = uid2dn(self.conn, memberuid)
             if dn:
                 return '(&%s(|(%s=%s)(%s=%s)))' % (self.filter,
-                          attmap['memberUid'], self.escape(memberuid),
-                          attmap['member'], self.escape(dn))
+                          attmap['memberUid'], escape_filter_chars(memberuid),
+                          attmap['member'], escape_filter_chars(dn))
         return super(Search, self).mk_filter()
 
 
diff --git a/pynslcd/netgroup.py b/pynslcd/netgroup.py
index ca7fb13..20f8779 100644
--- a/pynslcd/netgroup.py
+++ b/pynslcd/netgroup.py
@@ -18,7 +18,6 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301 USA
 
-import logging
 import re
 
 import cache
diff --git a/pynslcd/pam.py b/pynslcd/pam.py
index 2d03cc9..c26c6a9 100644
--- a/pynslcd/pam.py
+++ b/pynslcd/pam.py
@@ -22,7 +22,7 @@ import logging
 import socket
 
 from ldap.controls.ppolicy import PasswordPolicyControl, PasswordPolicyError
-from ldap.filter import escape_filter_chars as escape
+from ldap.filter import escape_filter_chars
 import ldap
 
 import cfg
@@ -178,10 +178,10 @@ class PAMAuthorisationRequest(PAMRequest):
         if not cfg.pam_authz_searches:
             return
         # escape all parameters
-        variables = dict((k, escape(v)) for k, v in parameters.items())
+        variables = dict((k, escape_filter_chars(v)) for k, v in 
parameters.items())
         variables.update(
-                hostname=escape(socket.gethostname()),
-                fqdn=escape(socket.getfqdn()),
+                hostname=escape_filter_chars(socket.gethostname()),
+                fqdn=escape_filter_chars(socket.getfqdn()),
                 dn=variables['userdn'],
                 uid=variables['username'],
             )
diff --git a/pynslcd/search.py b/pynslcd/search.py
index 533e522..9629bec 100644
--- a/pynslcd/search.py
+++ b/pynslcd/search.py
@@ -117,10 +117,6 @@ class LDAPSearch(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:
diff --git a/pynslcd/service.py b/pynslcd/service.py
index f3082e6..19f941d 100644
--- a/pynslcd/service.py
+++ b/pynslcd/service.py
@@ -19,8 +19,6 @@
 # 02110-1301 USA
 
 import datetime
-import ldap.filter
-import logging
 
 import cache
 import common
diff --git a/pynslcd/shadow.py b/pynslcd/shadow.py
index e0170e2..6f7df10 100644
--- a/pynslcd/shadow.py
+++ b/pynslcd/shadow.py
@@ -18,8 +18,6 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301 USA
 
-import logging
-
 import cache
 import common
 import constants

http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=ac30060ba57112c23b36cf016f7776e5b6af0d9b

commit ac30060ba57112c23b36cf016f7776e5b6af0d9b
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 9 21:51:17 2013 +0100

    move get_connection function to search module as Connection class as 
subclass of ReconnectLDAPObject to automatically reconnect to the LDAP server

diff --git a/pynslcd/pam.py b/pynslcd/pam.py
index f2493ec..2d03cc9 100644
--- a/pynslcd/pam.py
+++ b/pynslcd/pam.py
@@ -34,7 +34,7 @@ import search
 
 def try_bind(userdn, password):
     # open a new connection
-    conn = ldap.initialize(cfg.uri)
+    conn = search.Connection()
     # bind using the specified credentials
     pwctrl = PasswordPolicyControl()
     res, data, msgid, ctrls = conn.simple_bind_s(userdn, password, 
serverctrls=[pwctrl])
diff --git a/pynslcd/pynslcd.py b/pynslcd/pynslcd.py
index 43c787a..0f234c3 100755
--- a/pynslcd/pynslcd.py
+++ b/pynslcd/pynslcd.py
@@ -35,6 +35,7 @@ import cfg
 import common
 import constants
 import mypidfile
+import search
 
 
 # the name of the program
@@ -236,31 +237,8 @@ def disable_nss_ldap():
         logging.warn('probably older NSS module loaded', exc_info=True)
 
 
-def get_connection():
-    """Return a connection to the LDAP server."""
-    session = ldap.initialize(cfg.uri)
-    # set session-specific LDAP options
-    if cfg.ldap_version:
-        session.set_option(ldap.OPT_PROTOCOL_VERSION, cfg.ldap_version)
-    if cfg.deref:
-        session.set_option(ldap.OPT_DEREF, cfg.deref)
-    if cfg.timelimit:
-        session.set_option(ldap.OPT_TIMELIMIT, cfg.timelimit)
-        session.set_option(ldap.OPT_TIMEOUT, cfg.timelimit)
-        session.set_option(ldap.OPT_NETWORK_TIMEOUT, cfg.timelimit)
-    if cfg.referrals:
-        session.set_option(ldap.OPT_REFERRALS, cfg.referrals)
-    if cfg.sasl_canonicalize is not None:
-        session.set_option(ldap.OPT_X_SASL_NOCANON, not cfg.sasl_canonicalize)
-    session.set_option(ldap.OPT_RESTART, True)
-    # TODO: register a connection callback (like dis?connect_cb() in myldap.c)
-    if cfg.ssl or cfg.uri.startswith('ldaps://'):
-        session.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_HARD)
-    return session
-
-
 def worker():
-    session = get_connection()
+    session = search.Connection()
     while True:
         try:
             acceptconnection(session)
diff --git a/pynslcd/search.py b/pynslcd/search.py
index 60d36ff..533e522 100644
--- a/pynslcd/search.py
+++ b/pynslcd/search.py
@@ -22,10 +22,35 @@ import logging
 import sys
 
 import ldap
+import ldap.ldapobject
 
 import cfg
 
 
+class Connection(ldap.ldapobject.ReconnectLDAPObject):
+
+    def __init__(self):
+        ldap.ldapobject.ReconnectLDAPObject.__init__(self, cfg.uri,
+            retry_max=1, retry_delay=cfg.reconnect_retrytime)
+        # set connection-specific LDAP options
+        if cfg.ldap_version:
+            self.set_option(ldap.OPT_PROTOCOL_VERSION, cfg.ldap_version)
+        if cfg.deref:
+            self.set_option(ldap.OPT_DEREF, cfg.deref)
+        if cfg.timelimit:
+            self.set_option(ldap.OPT_TIMELIMIT, cfg.timelimit)
+            self.set_option(ldap.OPT_TIMEOUT, cfg.timelimit)
+            self.set_option(ldap.OPT_NETWORK_TIMEOUT, cfg.timelimit)
+        if cfg.referrals:
+            self.set_option(ldap.OPT_REFERRALS, cfg.referrals)
+        if cfg.sasl_canonicalize is not None:
+            self.set_option(ldap.OPT_X_SASL_NOCANON, not cfg.sasl_canonicalize)
+        self.set_option(ldap.OPT_RESTART, True)
+        # TODO: register a connection callback (like dis?connect_cb() in 
myldap.c)
+        if cfg.ssl or cfg.uri.startswith('ldaps://'):
+            self.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_HARD)
+
+
 class LDAPSearch(object):
     """
     Class that performs an LDAP search. Subclasses are expected to define the

http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=4e603409f76c14ba7b11c437eac6116a2afce603

commit 4e603409f76c14ba7b11c437eac6116a2afce603
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 9 23:02:29 2013 +0100

    move Search class to search module

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', )

http://arthurdejong.org/git/nss-pam-ldapd/commit/?id=975ee2ce45d050d1f8aafacfe58d035fff339e79

commit 975ee2ce45d050d1f8aafacfe58d035fff339e79
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 9 19:43:56 2013 +0100

    fix default logging configuration setting in pynslcd

diff --git a/pynslcd/cfg.py b/pynslcd/cfg.py
index ab6668b..57a1be2 100644
--- a/pynslcd/cfg.py
+++ b/pynslcd/cfg.py
@@ -315,7 +315,7 @@ def read(filename):
         raise ParseError(filename, lineno, 'error parsing line %r' % line)
     # if logging is not configured, default to syslog
     if not logs:
-        log.append('syslog', logging.INFO)
+        logs.append(('syslog', logging.INFO))
     # dump config (debugging code)
     for k, v in globals().items():
         if not k.startswith('_'):

-----------------------------------------------------------------------

Summary of changes:
 pynslcd/Makefile.am              |    4 +-
 pynslcd/alias.py                 |    5 +-
 pynslcd/attmap.py                |   19 +++--
 pynslcd/cfg.py                   |    2 +-
 pynslcd/common.py                |  128 +---------------------------
 pynslcd/ether.py                 |    3 +-
 pynslcd/group.py                 |   11 ++-
 pynslcd/host.py                  |    5 +-
 pynslcd/netgroup.py              |    4 +-
 pynslcd/network.py               |    5 +-
 pynslcd/pam.py                   |   32 ++++----
 pynslcd/passwd.py                |    5 +-
 pynslcd/protocol.py              |    5 +-
 pynslcd/pynslcd.py               |   28 +------
 pynslcd/rpc.py                   |    5 +-
 pynslcd/{common.py => search.py} |  173 ++++++++++----------------------------
 pynslcd/service.py               |    7 +-
 pynslcd/shadow.py                |    7 +-
 18 files changed, 120 insertions(+), 328 deletions(-)
 copy pynslcd/{common.py => search.py} (50%)


hooks/post-receive
-- 
nss-pam-ldapd
-- 
To unsubscribe send an email to
nss-pam-ldapd-commits-unsubscribe@lists.arthurdejong.org or see
http://lists.arthurdejong.org/nss-pam-ldapd-commits/