lists.arthurdejong.org
RSS feed

nss-pam-ldapd commit: r1581 - in nss-pam-ldapd: . pynslcd

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

nss-pam-ldapd commit: r1581 - in nss-pam-ldapd: . pynslcd



Author: arthur
Date: Wed Dec 28 23:52:26 2011
New Revision: 1581
URL: http://arthurdejong.org/viewvc/nss-pam-ldapd?revision=1581&view=revision

Log:
support for reading the configuration file (not all options are used though)

Modified:
   nss-pam-ldapd/configure.ac
   nss-pam-ldapd/pynslcd/cfg.py
   nss-pam-ldapd/pynslcd/common.py
   nss-pam-ldapd/pynslcd/config.py.in
   nss-pam-ldapd/pynslcd/group.py
   nss-pam-ldapd/pynslcd/pynslcd.py

Modified: nss-pam-ldapd/configure.ac
==============================================================================
--- nss-pam-ldapd/configure.ac  Wed Dec 28 23:44:33 2011        (r1580)
+++ nss-pam-ldapd/configure.ac  Wed Dec 28 23:52:26 2011        (r1581)
@@ -183,7 +183,6 @@
 if test "x$configfile_checking" = "xyes"
 then
   AC_DEFINE(ENABLE_CONFIGFILE_CHECKING,1,[Whether to check configfile 
options.])
-  AC_SUBST(ENABLE_CONFIGFILE_CHECKING,1)
 fi
 
 # check the name of the configuration file

Modified: nss-pam-ldapd/pynslcd/cfg.py
==============================================================================
--- nss-pam-ldapd/pynslcd/cfg.py        Wed Dec 28 23:44:33 2011        (r1580)
+++ nss-pam-ldapd/pynslcd/cfg.py        Wed Dec 28 23:52:26 2011        (r1581)
@@ -18,6 +18,9 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301 USA
 
+import re
+import sys
+
 import ldap
 
 
@@ -30,22 +33,254 @@
 
 # the LDAP server to use
 # FIXME: support multiple servers and have a fail-over mechanism
-ldap_uri = 'ldapi:///'
+uri = None
+# LDAP protocol version to use (perhaps fix at 3?)
+ldap_version = ldap.VERSION3
+# the DN to use when binding
+binddn = None
+bindpw = None
+# the DN to use to perform password modifications as root
+rootpwmoddn = None
+rootpwmodpw = None
+
+# SASL configuration
+sasl_mech = None
+sasl_realm = None
+sasl_authcid = None
+sasl_authzid = None
+sasl_secprops = None
 
+# LDAP bases to search
+bases = []
 # default search scope for searches
 scope = ldap.SCOPE_SUBTREE
 
-# LDAP search bases to search
-bases = ('dc=test, dc=tld', )
+deref = ldap.DEREF_NEVER
+referrals = True
 
-# the users for which no initgroups() searches should be done
-nss_initgroups_ignoreusers = []
+# timing configuration
+bind_timelimit = 10
+timelimit = ldap.NO_LIMIT
+idle_timelimit = 0
+reconnect_sleeptime = 1
+reconnect_retrytime = 10
 
-# the DN to use to perform password modifications as root
-rootpwmoddn = 'cn=admin, dc=test, dc=tld'
-rootpwmodpw = 'test'
+# SSL/TLS options
+ssl = None
+tls_reqcert = None
+tls_cacertdir = None
+tls_cacertfile = None
+tls_randfile = None
+tls_ciphers = None
+tls_cert = None
+tls_key = None
+
+
+# other options
+pagesize = 0
+nss_initgroups_ignoreusers = set()
+nss_min_uid = 0
+validnames = re.compile(r'^[a-z0-9._@$][a-z0-9._@$ 
\\~-]{0,98}[a-z0-9._@$~-]$', re.IGNORECASE)
+pam_authz_search = None
+
+
+# allowed boolean values
+_boolean_options = {'on': True, 'yes': True, 'true': True, '1': True,
+                    'off': False, 'no': False, 'false': False, '0': False}
+
+# allowed values for scope option
+_scope_options = dict(sub=ldap.SCOPE_SUBTREE, subtree=ldap.SCOPE_SUBTREE,
+                      one=ldap.SCOPE_ONELEVEL, onelevel=ldap.SCOPE_ONELEVEL,
+                      base=ldap.SCOPE_BASE)
+
+# allowed values for the deref option
+_deref_options = dict(never=ldap.DEREF_NEVER,
+                      searching=ldap.DEREF_SEARCHING,
+                      finding=ldap.DEREF_FINDING,
+                      always=ldap.DEREF_ALWAYS)
+
+# allowed values for the ssl option
+_ssl_options = dict(start_tls='STARTTLS', starttls='STARTTLS',
+                    on='LDAPS', off=None)
+
+# allowed values for the tls_reqcert option
+_tls_reqcert_options = {'never': ldap.OPT_X_TLS_NEVER,
+                        'no': ldap.OPT_X_TLS_NEVER,
+                        'allow': ldap.OPT_X_TLS_ALLOW,
+                        'try': ldap.OPT_X_TLS_TRY,
+                        'demand': ldap.OPT_X_TLS_DEMAND,
+                        'yes': ldap.OPT_X_TLS_DEMAND,
+                        'hard': ldap.OPT_X_TLS_HARD}
+
+
+def _get_maps():
+    # separate function as not to pollute the namespace and avoid import loops
+    import alias, ether, group, host, netgroup, network, passwd
+    import protocol, rpc, service, shadow
+    return dict(alias=alias, aliases=alias,
+                 ether=ether, ethers=ether,
+                 group=group,
+                 host=host, hosts=host,
+                 netgroup=netgroup,
+                 network=network, networks=network,
+                 passwd=passwd,
+                 protocol=protocol, protocols=protocol,
+                 rpc=rpc,
+                 service=service, services=service,
+                 shadow=shadow,
+                 none=sys.modules[__name__])
+
+
+class ParseError(Exception):
+
+    def __init__(self, filename, lineno, message):
+        self.message = '%s:%d: %s' % (filename, lineno, message)
+
+    def __repr__(self):
+        return self.message
+
+    __str__ = __repr__
 
 
-# FIXME: implement reading configuration from file
-def read(cfgfile):
-    pass
+def read(filename):
+    maps = _get_maps()
+    lineno = 0
+    for line in open(filename, 'r'):
+        lineno += 1
+        line = line.strip()
+        # skip comments and blank lines
+        if re.match('(#.*)?$', line, re.IGNORECASE):
+            continue
+        # parse options with a single integer argument
+        m = 
re.match('(?P<keyword>threads|ldap_version|bind_timelimit|timelimit|idle_timelimit|reconnect_sleeptime|reconnect_retrytime|pagesize|nss_min_uid)\s+(?P<value>\d+)',
+                     line, re.IGNORECASE)
+        if m:
+            globals()[m.group('keyword').lower()] = int(m.group('value'))
+            continue
+        # parse options with a single boolean argument
+        m = re.match('(?P<keyword>referrals)\s+(?P<value>%s)' %
+                         '|'.join(_boolean_options.keys()),
+                     line, re.IGNORECASE)
+        if m:
+            globals()[m.group('keyword').lower()] = 
_boolean_options[m.group('value').lower()]
+            continue
+        # parse options with a single no-space value
+        m = 
re.match('(?P<keyword>uid|gid|bindpw|rootpwmodpw|sasl_mech)\s+(?P<value>\S+)',
+                     line, re.IGNORECASE)
+        if m:
+            globals()[m.group('keyword').lower()] = m.group('value')
+            continue
+        # parse options with a single value that can contain spaces
+        m = 
re.match('(?P<keyword>binddn|rootpwmoddn|sasl_realm|sasl_authcid|sasl_authzid|sasl_secprops|krb5_ccname|tls_cacertdir|tls_cacertfile|tls_randfile|tls_ciphers|tls_cert|tls_key)\s+(?P<value>\S.*)',
 line, re.IGNORECASE)
+        if m:
+            globals()[m.group('keyword').lower()] = m.group('value')
+            continue
+        # uri <URI>
+        m = re.match('uri\s+(?P<uri>\S+)', line, re.IGNORECASE)
+        if m:
+            # FIXME: support multiple URI values
+            # FIXME: support special DNS and DNS:domain values
+            global uri
+            uri = m.group('uri')
+            continue
+        # base <MAP>? <BASEDN>
+        m = re.match('base\s+((?P<map>%s)\s+)?(?P<value>\S.*)' %
+                         '|'.join(maps.keys()),
+                     line, re.IGNORECASE)
+        if m:
+            mod = maps[str(m.group('map')).lower()]
+            if not hasattr(mod, 'bases'):
+                mod.bases = []
+            mod.bases.append(m.group('value'))
+            continue
+        # filter <MAP> <SEARCHFILTER>
+        m = re.match('filter\s+(?P<map>%s)\s+(?P<value>\S.*)' %
+                         '|'.join(maps.keys()),
+                     line, re.IGNORECASE)
+        if m:
+            mod = maps[m.group('map').lower()]
+            mod.filter = m.group('value')
+            continue
+        # scope <MAP>? <SCOPE>
+        m = re.match('scope\s+((?P<map>%s)\s+)?(?P<value>%s)' % (
+                         '|'.join(maps.keys()),
+                         '|'.join(_scope_options.keys())),
+                     line, re.IGNORECASE)
+        if m:
+            keyword = m.group('keyword').lower()
+            mod = maps[str(m.group('map')).lower()]
+            mod.scope = _scope_options[m.group('keyword').lower()]
+            continue
+        # map <MAP> <ATTRIBUTE> <ATTMAPPING>
+        m = 
re.match('map\s+(?P<map>%s)\s+(?P<attribute>\S+)\s+(?P<value>\S.*)' %
+                         '|'.join(maps.keys()),
+                     line, re.IGNORECASE)
+        if m:
+            mod = maps[m.group('map').lower()]
+            attribute = m.group('attribute')
+            if attribute not in mod.attmap:
+                raise ParseError(filename, lineno, 'attribute %s unknown' % 
attribute)
+            mod.attmap[attribute] = m.group('value')
+            # TODO: filter out attributes that cannot be an expression
+            continue
+        # deref <DEREF>
+        m = re.match('deref\s+(?P<value>%s)' % '|'.join(_deref_options.keys()),
+                     line, re.IGNORECASE)
+        if m:
+            global deref
+            deref = _deref_options[m.group('value').lower()]
+            continue
+        # nss_initgroups_ignoreusers <USER,USER>|<ALLLOCAL>
+        m = re.match('nss_initgroups_ignoreusers\s+(?P<value>\S.*)',
+                     line, re.IGNORECASE)
+        if m:
+            global nss_initgroups_ignoreusers
+            users = m.group('value')
+            if users.lower() == 'alllocal':
+                # get all users known to the system currently (since nslcd 
isn't yet
+                # running, this should work)
+                import pwd
+                users = (x.pw_name for x in pwd.getpwall())
+            else:
+                users = users.split(',')
+                # TODO: warn about unknown users
+            nss_initgroups_ignoreusers.update(users)
+            continue
+        # pam_authz_search <FILTER>
+        m = re.match('pam_authz_search\s+(?P<value>\S.*)', line, re.IGNORECASE)
+        if m:
+            global pam_authz_search
+            from attmap import Expression
+            pam_authz_search = Expression(m.group('value'))
+            # TODO: check pam_authz_search expression to only contain 
username, service, ruser, rhost, tty, hostname, fqdn, dn or uid variables
+            continue
+        # ssl <on|off|start_tls>
+        m = re.match('ssl\s+(?P<value>%s)' %
+                         '|'.join(_ssl_options.keys()),
+                     line, re.IGNORECASE)
+        if m:
+            global ssl
+            ssl = _ssl_options[m.group('value').lower()]
+            continue
+        # tls_reqcert <demand|hard|yes...>
+        m = re.match('tls_reqcert\s+(?P<value>%s)' %
+                         '|'.join(_tls_reqcert_options.keys()),
+                     line, re.IGNORECASE)
+        if m:
+            global tls_reqcert
+            tls_reqcert = _tls_reqcert_options[m.group('value').lower()]
+            continue
+        # validnames /REGEX/i?
+        m = re.match('validnames\s+/(?P<value>.*)/(?P<flags>[i]?)$',
+                     line, re.IGNORECASE)
+        if m:
+            global validnames
+            flags = 0 | re.IGNORECASE if m.group('flags') == 'i' else 0
+            validnames = re.compile(m.group('value'), flags=flags)
+            continue
+        # unrecognised line
+        raise ParseError(filename, lineno, 'error parsing line %r' % line)
+    # dump config (debugging code)
+    for k, v in globals().items():
+        if not k.startswith('_'):
+            print '%s=%r' % (k, v)

Modified: nss-pam-ldapd/pynslcd/common.py
==============================================================================
--- nss-pam-ldapd/pynslcd/common.py     Wed Dec 28 23:44:33 2011        (r1580)
+++ nss-pam-ldapd/pynslcd/common.py     Wed Dec 28 23:52:26 2011        (r1581)
@@ -28,9 +28,6 @@
 from attmap import Attributes
 
 
-_validname_re = re.compile(r'^[a-z0-9._@$][a-z0-9._@$ 
\\~-]{0,98}[a-z0-9._@$~-]$', re.IGNORECASE)
-
-
 def isvalidname(name):
     """Checks to see if the specified name seems to be a valid user or group
     name.
@@ -44,14 +41,14 @@
     The standard defines user names valid if they contain characters from
     the set [A-Za-z0-9._-] where the hyphen should not be used as first
     character. As an extension this test allows some more characters."""
-    return bool(_validname_re.match(name))
+    return bool(cfg.validnames.search(name))
 
 
 def validate_name(name):
     """Checks to see if the specified name seems to be a valid user or group
     name. See isvalidname()."""
-    if not _validname_re.match(name):
-        raise ValueError('%r: invalid user name' % name)
+    if not cfg.validnames.search(name):
+        raise ValueError('%r: denied by validnames option' % name)
 
 
 class Search(object):
@@ -109,6 +106,7 @@
         # get search results
         filter = self.mk_filter()
         for base in self.bases:
+            print 'SEARCHING %s' % base
             # do the LDAP search
             try:
                 for entry in self.conn.search_s(base, self.scope, filter, 
self.attributes):

Modified: nss-pam-ldapd/pynslcd/config.py.in
==============================================================================
--- nss-pam-ldapd/pynslcd/config.py.in  Wed Dec 28 23:44:33 2011        (r1580)
+++ nss-pam-ldapd/pynslcd/config.py.in  Wed Dec 28 23:52:26 2011        (r1581)
@@ -42,9 +42,6 @@
 # Version number of package
 VERSION = '''@VERSION@'''
 
-# Whether to check configfile options.
-ENABLE_CONFIGFILE_CHECKING = '''@ENABLE_CONFIGFILE_CHECKING@'''
-
 # Path to bindpw value.
 NSLCD_BINDPW_PATH = '''@NSLCD_BINDPW_PATH@'''
 

Modified: nss-pam-ldapd/pynslcd/group.py
==============================================================================
--- nss-pam-ldapd/pynslcd/group.py      Wed Dec 28 23:44:33 2011        (r1580)
+++ nss-pam-ldapd/pynslcd/group.py      Wed Dec 28 23:52:26 2011        (r1581)
@@ -37,7 +37,7 @@
                            gidNumber='gidNumber',
                            memberUid='memberUid',
                            member='member')
-filter = '(|(objectClass=posixGroup)(objectClass=groupOfNames))'
+filter = '(objectClass=posixGroup)'
 
 
 class Search(common.Search):

Modified: nss-pam-ldapd/pynslcd/pynslcd.py
==============================================================================
--- nss-pam-ldapd/pynslcd/pynslcd.py    Wed Dec 28 23:44:33 2011        (r1580)
+++ nss-pam-ldapd/pynslcd/pynslcd.py    Wed Dec 28 23:52:26 2011        (r1581)
@@ -216,7 +216,7 @@
 def worker():
     # create a new LDAP session
     #session = myldap_create_session()
-    session = ldap.initialize(cfg.ldap_uri)
+    session = ldap.initialize(cfg.uri)
     # start waiting for incoming connections
     while True:
         # wait for a new connection
@@ -242,6 +242,7 @@
     #    sys.exit(1)
     # read configuration file
     cfg.read(config.NSLCD_CONF_PATH)
+    # FIXME: set tls_cacertdir, tls_cacertfile, tls_randfile, tls_ciphers, 
tls_cert, tls_key options immediately after parsing config
     # set a default umask for the pidfile and socket
     os.umask(0022)
     # see if someone already locked the pidfile
@@ -266,7 +267,7 @@
                       pidfile=pidfile,
                       signal_map={
                           signal.SIGTERM: 'terminate',
-                          signal.SIGINT:  'terminate',
+                          signal.SIGINT: 'terminate',
                           signal.SIGPIPE: None,
                       })
     # start daemon
-- 
To unsubscribe send an email to
nss-pam-ldapd-commits-unsubscribe@lists.arthurdejong.org or see
http://lists.arthurdejong.org/nss-pam-ldapd-commits/