lists.arthurdejong.org
RSS feed

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

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

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



Author: arthur
Date: Wed Dec 29 22:50:17 2010
New Revision: 1347
URL: http://arthurdejong.org/viewvc/nss-pam-ldapd?view=rev&revision=1347

Log:
add an experimental (currently partial) Python implementation of nslcd to see 
if we can get the same features with easier to maintain code

Added:
   nss-pam-ldapd/py-compile   (contents, props changed)
   nss-pam-ldapd/pynslcd/   (props changed)
   nss-pam-ldapd/pynslcd/Makefile.am
   nss-pam-ldapd/pynslcd/alias.py
   nss-pam-ldapd/pynslcd/cfg.py
   nss-pam-ldapd/pynslcd/common.py
   nss-pam-ldapd/pynslcd/config.py.in
   nss-pam-ldapd/pynslcd/debugio.py
   nss-pam-ldapd/pynslcd/ether.py
   nss-pam-ldapd/pynslcd/group.py
   nss-pam-ldapd/pynslcd/mypidfile.py
   nss-pam-ldapd/pynslcd/pam.py
   nss-pam-ldapd/pynslcd/passwd.py
   nss-pam-ldapd/pynslcd/pynslcd.py   (contents, props changed)
   nss-pam-ldapd/pynslcd/shadow.py
   nss-pam-ldapd/pynslcd/tio.py
Modified:
   nss-pam-ldapd/Makefile.am
   nss-pam-ldapd/configure.ac

Modified: nss-pam-ldapd/Makefile.am
==============================================================================
--- nss-pam-ldapd/Makefile.am   Tue Dec 28 23:52:28 2010        (r1346)
+++ nss-pam-ldapd/Makefile.am   Wed Dec 29 22:50:17 2010        (r1347)
@@ -29,6 +29,9 @@
 if ENABLE_NSLCD
   SUBDIRS += nslcd
 endif
+if ENABLE_PYNSLCD
+  SUBDIRS += pynslcd
+endif
 SUBDIRS += man tests
 
 DEBIAN_FILES = debian/changelog debian/compat debian/control \

Modified: nss-pam-ldapd/configure.ac
==============================================================================
--- nss-pam-ldapd/configure.ac  Tue Dec 28 23:52:28 2010        (r1346)
+++ nss-pam-ldapd/configure.ac  Wed Dec 29 22:50:17 2010        (r1347)
@@ -65,6 +65,7 @@
 AC_PROG_RANLIB
 AM_PROG_CC_C_O
 AC_USE_SYSTEM_EXTENSIONS
+AC_PROG_LN_S
 
 # checks for tool to convert docbook to man
 AC_PATH_PROGS(DOCBOOK2X_MAN, docbook2x-man)
@@ -132,14 +133,27 @@
 AM_CONDITIONAL([ENABLE_PAM], [test "x$enable_pam" = "xyes"])
 
 # check whether the nslcd daemon should be built
-AC_MSG_CHECKING([whether to build the nslcd server])
+AC_MSG_CHECKING([whether to build the nslcd daemon])
 AC_ARG_ENABLE(nslcd,
               AS_HELP_STRING([--disable-nslcd],
-                             [build the nslcd server [[default=enabled]]]),,
+                             [build the nslcd daemon [[default=enabled]]]),,
               [enable_nslcd="yes"])
 AC_MSG_RESULT($enable_nslcd)
 AM_CONDITIONAL([ENABLE_NSLCD], [test "x$enable_nslcd" = "xyes"])
 
+# check whether the Python version of the nslcd daemon should be built
+AC_MSG_CHECKING([whether to build the pynslcd daemon])
+AC_ARG_ENABLE(pynslcd,
+              AS_HELP_STRING([--enable-pynslcd],
+                             [build the pynslcd daemon [[default=disabled]]]),,
+              [enable_pynslcd="no"])
+AC_MSG_RESULT($enable_pynslcd)
+AM_CONDITIONAL([ENABLE_PYNSLCD], [test "x$enable_pynslcd" = "xyes"])
+if test "x$enable_pynslcd" = "xyes"
+then
+  AC_MSG_WARN([the pynslcd daemon is experimental])
+fi
+
 # check whether SASL support should be enabled
 AC_MSG_CHECKING([whether to enable SASL support])
 AC_ARG_ENABLE(sasl,
@@ -169,6 +183,7 @@
 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
@@ -702,7 +717,15 @@
   AC_SUBST(nslcd_LIBS)
 fi
 
+# pynslcd daemon-specific tests
+if test "x$enable_pynslcd" = "xyes"
+then
+  # check Python interpreter
+  AM_PATH_PYTHON(2.5)
+fi
+
 # generate files
 AC_CONFIG_FILES([Makefile compat/Makefile common/Makefile nss/Makefile
-                 pam/Makefile nslcd/Makefile man/Makefile tests/Makefile])
+                 pam/Makefile nslcd/Makefile pynslcd/Makefile pynslcd/config.py
+                 man/Makefile tests/Makefile])
 AC_OUTPUT

Added: nss-pam-ldapd/py-compile
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/py-compile    Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,146 @@
+#!/bin/sh
+# py-compile - Compile a Python program
+
+scriptversion=2009-04-28.21; # UTC
+
+# Copyright (C) 2000, 2001, 2003, 2004, 2005, 2008, 2009 Free Software
+# Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program 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 General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+if [ -z "$PYTHON" ]; then
+  PYTHON=python
+fi
+
+basedir=
+destdir=
+files=
+while test $# -ne 0; do
+  case "$1" in
+    --basedir)
+      basedir=$2
+      if test -z "$basedir"; then
+        echo "$0: Missing argument to --basedir." 1>&2
+        exit 1
+      fi
+      shift
+      ;;
+    --destdir)
+      destdir=$2
+      if test -z "$destdir"; then
+        echo "$0: Missing argument to --destdir." 1>&2
+        exit 1
+      fi
+      shift
+      ;;
+    -h|--h*)
+      cat <<\EOF
+Usage: py-compile [--help] [--version] [--basedir DIR] [--destdir DIR] 
FILES..."
+
+Byte compile some python scripts FILES.  Use --destdir to specify any
+leading directory path to the FILES that you don't want to include in the
+byte compiled file.  Specify --basedir for any additional path information you
+do want to be shown in the byte compiled file.
+
+Example:
+  py-compile --destdir /tmp/pkg-root --basedir /usr/share/test test.py test2.py
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+      exit $?
+      ;;
+    -v|--v*)
+      echo "py-compile $scriptversion"
+      exit $?
+      ;;
+    *)
+      files="$files $1"
+      ;;
+  esac
+  shift
+done
+
+if test -z "$files"; then
+    echo "$0: No files given.  Try \`$0 --help' for more information." 1>&2
+    exit 1
+fi
+
+# if basedir was given, then it should be prepended to filenames before
+# byte compilation.
+if [ -z "$basedir" ]; then
+    pathtrans="path = file"
+else
+    pathtrans="path = os.path.join('$basedir', file)"
+fi
+
+# if destdir was given, then it needs to be prepended to the filename to
+# byte compile but not go into the compiled file.
+if [ -z "$destdir" ]; then
+    filetrans="filepath = path"
+else
+    filetrans="filepath = os.path.normpath('$destdir' + os.sep + path)"
+fi
+
+$PYTHON -c "
+import sys, os, py_compile
+
+files = '''$files'''
+
+sys.stdout.write('Byte-compiling python modules...\n')
+for file in files.split():
+    $pathtrans
+    $filetrans
+    if not os.path.exists(filepath) or not (len(filepath) >= 3
+                                            and filepath[-3:] == '.py'):
+           continue
+    sys.stdout.write(file)
+    sys.stdout.flush()
+    py_compile.compile(filepath, filepath + 'c', path)
+sys.stdout.write('\n')" || exit $?
+
+# this will fail for python < 1.5, but that doesn't matter ...
+$PYTHON -O -c "
+import sys, os, py_compile
+
+files = '''$files'''
+sys.stdout.write('Byte-compiling python modules (optimized versions) ...\n')
+for file in files.split():
+    $pathtrans
+    $filetrans
+    if not os.path.exists(filepath) or not (len(filepath) >= 3
+                                            and filepath[-3:] == '.py'):
+           continue
+    sys.stdout.write(file)
+    sys.stdout.flush()
+    py_compile.compile(filepath, filepath + 'o', path)
+sys.stdout.write('\n')" 2>/dev/null || :
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:

Added: nss-pam-ldapd/pynslcd/Makefile.am
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/Makefile.am   Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,41 @@
+# Makefile.am - use automake to generate Makefile.in
+#
+# Copyright (C) 2010 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
+
+pynslcddir = $(datadir)/pynslcd
+
+pynslcd_PYTHON = pynslcd.py cfg.py common.py tio.py \
+                 ether.py group.py passwd.py
+nodist_pynslcd_PYTHON = constants.py config.py
+CLEANFILES = $(nodist_pynslcd_PYTHON)
+
+all-local: $(nodist_pynslcd_PYTHON)
+
+# create a symbolic link for the pynslcd daemon and fix permissions
+install-data-hook:
+       chmod a+rx $(DESTDIR)$(pynslcddir)/pynslcd.py
+       $(MKDIR_P) $(DESTDIR)$(sbindir)
+       [ -L $(DESTDIR)$(sbindir)/pynslcd ] || $(LN_S) $(pynslcddir)/pynslcd.py 
$(DESTDIR)$(sbindir)/pynslcd
+
+# generate constants module
+constants.py: $(top_srcdir)/nslcd.h Makefile
+       ( echo "# This file is automatically generated from nslcd.h." ; \
+         echo "# See that file for details." ; \
+         echo "" ; \
+         sed -n 's| */\*.*\*/ *||;s/^.define  *\(NSLCD_[A-Z_]*\)  */\1 = /p' \
+             $< ) > $@

Added: nss-pam-ldapd/pynslcd/alias.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/alias.py      Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,74 @@
+
+# alias.py - lookup functions for aliasnet addresses
+#
+# Copyright (C) 2010 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 constants
+import common
+
+import ldap
+import ldap.filter
+
+
+class AliasRequest(common.Request):
+
+    filter = '(objectClass=nisMailAlias)'
+
+    attmap_cn         = 'cn'
+    attmap_rfc822MailMember = 'rfc822MailMember'
+
+    attributes = ( 'cn', 'rfc822MailMember' )
+
+    def write(self, entry):
+        dn, attributes = entry
+        # get name and check against requested name
+        names = attributes.get(self.attmap_cn, [])
+        if not names:
+            logging.error('Error: entry %s does not contain %s value', dn, 
self.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, [])
+        if not members:
+            logging.error('Error: entry %s does not contain %s value', dn, 
self.attmap_rfc822MailMember)
+            return
+        # write results
+        for name in names:
+            self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
+            self.fp.write_string(name)
+            self.fp.write_stringlist(members)
+
+
+class AliasByNameRequest(AliasRequest):
+
+    action = constants.NSLCD_ACTION_ALIAS_BYNAME
+
+    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):
+
+    action = constants.NSLCD_ACTION_ALIAS_ALL

Added: nss-pam-ldapd/pynslcd/cfg.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/cfg.py        Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,58 @@
+
+# cfg.py - module for accessing configuration information
+#
+# Copyright (C) 2010 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 ldap
+
+# these values are defined here
+
+# the name of the program
+program_name = 'pynslcd'
+# the debugging level
+debug = 0
+# whether the --check option was passed
+check = False
+# the number of threads to start
+threads = 5
+
+# the user id nslcd should be run as
+uid = None
+# the group id nslcd should be run as
+gid = None
+
+# the LDAP server to use
+# FIXME: support multiple servers and have a fail-over mechanism
+ldap_uri = 'ldapi:///'
+
+# default search scope for searches
+scope = ldap.SCOPE_SUBTREE
+
+# LDAP search bases to search
+bases = ( 'dc=test, dc=tld', )
+
+# the users for which no initgroups() searches should be done
+nss_initgroups_ignoreusers = []
+
+# the DN to use to perform password modifications as root
+rootpwmoddn = 'cn=admin, dc=test, dc=tld'
+rootpwmodpw = 'test'
+
+# FIXME: implement reading configuration from file
+def read(cfgfile):
+    pass

Added: nss-pam-ldapd/pynslcd/common.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/common.py     Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,125 @@
+
+# common.py - functions that are used by different modules
+#
+# Copyright (C) 2010 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 cfg
+import constants
+
+import re
+import ldap
+import ldap.dn
+
+_validname_re = re.compile(r'^[A-Za-z0-9._@$][A-Za-z0-9._@$ 
\\~-]{0,98}[A-Za-z0-9._@$~-]$')
+
+def isvalidname(name):
+    """Checks to see if the specified name seems to be a valid user or group
+    name.
+
+    This test is based on the definition from POSIX (IEEE Std 1003.1, 2004,
+    3.426 User Name, 3.189 Group Name and 3.276 Portable Filename Character 
Set):
+    
http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_426
+    
http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_189
+    
http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_276
+
+    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))
+
+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)
+
+
+class Request(object):
+    """
+    Request handler class. Subclasses are expected to handle actual requests
+    and should implement the following members:
+
+    action: the NSLCD_ACTION_* action that should trigger this handler
+    read_parameters: a function that reads the request parameters of the
+                     request stream
+    filter: LDAP search filter
+    mk_filter (optional): function that returns the LDAP search filter
+    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
+        self.calleruid = calleruid
+        # have default empty values for these
+        self.name = None
+        self.uid = None
+        self.gid = None
+        self.address = None
+
+    def read_parameters(self):
+        """This method should read the parameters from ths stream and
+        store them in self."""
+        pass
+
+    def mk_filter(self):
+        """Return the active search filter (based on the read parameters)."""
+        return self.filter
+
+    def handle_request(self):
+        """This method handles the request based on the parameters read
+        with read_parameters()."""
+        # get search results
+        for base in self.bases:
+            # do the LDAP search
+            try:
+                res = self.conn.search_s(base, self.scope, self.mk_filter(), 
self.attributes)
+                for entry in res:
+                    if entry[0]:
+                        self.write(entry)
+            except ldap.NO_SUCH_OBJECT:
+                # FIXME: log message
+                pass
+        # write the final result code
+        self.fp.write_int32(constants.NSLCD_RESULT_END)
+
+    def __call__(self):
+        self.read_parameters()
+        # TODO: log call with parameters
+        self.fp.write_int32(constants.NSLCD_VERSION)
+        self.fp.write_int32(self.action)
+        self.handle_request()
+
+
+def get_handlers(module):
+    """Return a dictionary mapping actions to Request classes."""
+    import inspect
+    res = {}
+    if isinstance(module, basestring):
+        module = __import__(module, globals())
+    for name, cls in inspect.getmembers(module, inspect.isclass):
+        if issubclass(cls, Request) and hasattr(cls, 'action'):
+            res[cls.action] = cls
+    return res
+
+def get_rdn_value(entry, attribute):
+    dn, attributes = entry
+    return dict((x, y) for x, y, z in ldap.dn.str2dn(dn)[0])[attribute]

Added: nss-pam-ldapd/pynslcd/config.py.in
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/config.py.in  Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,61 @@
+
+# config.py.in - configured information, this file is processed by the
+#                configure script to produce config.py
+#
+# 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
+
+
+# Name of package
+PACKAGE = '''@PACKAGE@'''
+
+# Define to the address where bug reports for this package should be sent.
+PACKAGE_BUGREPORT = '''@PACKAGE_BUGREPORT@'''
+
+# Define to the full name of this package.
+PACKAGE_NAME = '''@PACKAGE_NAME@'''
+
+# Define to the full name and version of this package.
+PACKAGE_STRING = '''@PACKAGE_STRING@'''
+
+# Define to the one symbol short name of this package.
+PACKAGE_TARNAME = '''@PACKAGE_TARNAME@'''
+
+# Define to the home page for this package.
+PACKAGE_URL = '''@PACKAGE_URL@'''
+
+# Define to the version of this package.
+PACKAGE_VERSION = '''@PACKAGE_VERSION@'''
+
+# 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@'''
+
+# Path to nslcd configuration file.
+NSLCD_CONF_PATH = '''@NSLCD_CONF_PATH@'''
+
+# The location of the pidfile used for checking availability of the nslcd.
+NSLCD_PIDFILE = '''@NSLCD_PIDFILE@'''
+
+# The location of the socket used for communicating.
+NSLCD_SOCKET = '''@NSLCD_SOCKET@'''
+
+# The SONAME of the NSS library module.
+NSS_LDAP_SONAME = '''@NSS_LDAP_SONAME@'''

Added: nss-pam-ldapd/pynslcd/debugio.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/debugio.py    Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,65 @@
+
+# debugio.py - module for debugging an I/O stream
+#
+# Copyright (C) 2008, 2009 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
+
+class DebugIO():
+    """This class is a file-like object that writes from one file and
+    writes to another. It is mainly used for debugging the serial protocol
+    without a serial connection."""
+
+    def __init__(self, name):
+        import os
+        if not os.path.exists(name+'.in'): os.mkfifo(name+'.in')
+        if not os.path.exists(name+'.out'): os.mkfifo(name+'.out')
+        r = open(name+'.in', 'r', 0)
+        w = open(name+'.out', 'w', 0)
+        self._r = r
+        self._w = w
+        self.write = w.write
+        self.portstr = 'debuging to %s.in and %s.out' % ( name, name )
+        self._timeout = None
+
+    def close(self):
+        self._r.close()
+        self._w.close()
+
+    def inWaiting(self):
+        # we are never out of data and 100 should be enough for everybody
+        return 100
+
+    def setTimeout(self, seconds):
+        self._timeout = seconds
+
+    def getTimeout(self):
+        return self._timeout
+
+    def read(self, size):
+        import select
+        read = ''
+        if size > 0:
+            while len(read) < size:
+                ready, _, _ = select.select([self._r.fileno()], [], [], 
self._timeout)
+                if not ready:
+                    break   #timeout
+                buf = self._r.read(size-len(read))
+                read = read + buf
+                if self._timeout >= 0 and not buf:
+                    break  #early abort on timeout
+        return read
+

Added: nss-pam-ldapd/pynslcd/ether.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/ether.py      Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,100 @@
+
+# ether.py - lookup functions for ethernet addresses
+#
+# Copyright (C) 2010 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 constants
+import common
+
+import struct
+import ldap.filter
+
+
+def ether_aton(ether):
+    return struct.pack('BBBBBB', *(int(x, 16) for x in ether.split(':')))
+
+def ether_ntoa(ether):
+    return ':'.join('%x' % x for x in struct.unpack('6B', ether))
+
+
+class EtherRequest(common.Request):
+
+    filter = '(objectClass=ieee802Device)'
+
+    attmap_cn         = 'cn'
+    attmap_macAddress = 'macAddress'
+
+    attributes = ( 'cn', 'macAddress' )
+
+    def __init__(self, *args):
+        super(EtherRequest, self).__init__(*args)
+        self.ether = None
+
+    def write(self, entry):
+        dn, attributes = entry
+        # get name and check against requested user name
+        names = attributes.get(self.attmap_cn, [])
+        if not names:
+            print 'Error: entry %s does not contain %s value' % ( dn, 
self.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, [])]
+        if not addresses:
+            print 'Error: entry %s does not contain %s value' % ( dn, 
self.attmap_macAddress)
+        if self.ether:
+            if self.ether not in addresses:
+                return
+            addresses = ( self.ether, )
+        # write results
+        for name in names:
+            for ether in addresses:
+                self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
+                self.fp.write_string(name)
+                self.fp.write(ether)
+
+
+class EtherByNameRequest(EtherRequest):
+
+    action = constants.NSLCD_ACTION_ETHER_BYNAME
+
+    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):
+
+    action = constants.NSLCD_ACTION_ETHER_BYETHER
+
+    def read_parameters(self):
+        self.ether = self.fp.read(6)
+
+    def mk_filter(self):
+        return '(&%s(%s=%s))' % ( self.filter,
+                  self.attmap_macAddress, ether_ntoa(self.ether) )
+
+
+class EtherAllRequest(EtherRequest):
+
+    action = constants.NSLCD_ACTION_ETHER_ALL

Added: nss-pam-ldapd/pynslcd/group.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/group.py      Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,174 @@
+
+# group.py - group entry lookup routines
+#
+# Copyright (C) 2010 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 constants
+import common
+import cfg
+
+import logging
+import ldap
+import ldap.filter
+
+
+def clean(lst):
+    for i in lst:
+        yield i.replace('\0', '')
+
+class GroupRequest(common.Request):
+
+    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' )
+
+    wantmembers = True
+
+    def write(self, entry):
+        dn, attributes = entry
+        # get uid attribute and check against requested user name
+        names = attributes.get('uid', [])
+        if self.name:
+            if self.name not in names:
+                return
+            names = ( self.name, )
+        # get user password entry
+        passwd = '*'
+        # get numeric user and group ids
+        uids = ( self.uid, ) if self.uid else 
attributes.get(self.attmap_group_uidNumber, [])
+        uids = [ int(x) for x in uids ]
+        ( gid, ) = attributes[self.attmap_group_gidNumber]
+        gid = int(gid)
+        # FIXME: use expression here
+        gecos = attributes.get(self.attmap_group_gecos, [None])[0] or 
attributes.get(self.attmap_group_cn, [''])[0]
+        ( home, ) = attributes.get(self.attmap_group_homeDirectory, [''])
+        ( shell, ) = attributes.get(self.attmap_group_loginShell, [''])
+        for name in names:
+            if not common.isvalidname(name):
+                print 'Warning: group entry %s contains invalid user name: 
"%s"' % ( dn, name )
+            else:
+                for uid in uids:
+                    self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
+                    self.fp.write_string(name)
+                    self.fp.write_string(passwd)
+                    self.fp.write_uid_t(uid)
+                    self.fp.write_gid_t(gid)
+                    self.fp.write_string(gecos)
+                    self.fp.write_string(home)
+                    self.fp.write_string(shell)
+
+    def write(self, entry):
+        dn, attributes = entry
+        # get group names and check against requested group name
+        names = attributes.get(self.attmap_group_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, ['*'])
+        # get group id(s)
+        gids = ( self.gid, ) if self.gid else 
attributes.get(self.attmap_group_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, 
[])):
+                #print 'found member %r' % member
+                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, [])):
+                member = dn2uid(self.conn, memberdn)
+                #print 'found memberdn %r, member=%r' % ( memberdn, member)
+                if member:
+                    members.add(member)
+        # actually return the results
+        for name in names:
+            if not common.isvalidname(name):
+                print 'Warning: group entry %s contains invalid group name: 
"%s"' % ( dn, name )
+            else:
+                for gid in gids:
+                    self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
+                    self.fp.write_string(name)
+                    self.fp.write_string(passwd)
+                    self.fp.write_gid_t(gid)
+                    self.fp.write_stringlist(members)
+
+
+class GroupByNameRequest(GroupRequest):
+
+    action = constants.NSLCD_ACTION_GROUP_BYNAME
+
+    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
+
+    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 read_parameters(self):
+        self.memberuid = self.fp.read_string()
+        common.validate_name(self.memberuid)
+
+    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) )
+
+
+class GroupAllRequest(GroupRequest):
+
+    action = constants.NSLCD_ACTION_GROUP_ALL

Added: nss-pam-ldapd/pynslcd/mypidfile.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/mypidfile.py  Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,70 @@
+
+# mypidfile.py - functions for properly locking a PIDFile
+#
+# Copyright (C) 2010 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 fcntl
+import errno
+import os
+
+
+class MyPIDLockFile(object):
+    """Implementation of a PIDFile fit for use with the daemon module
+    that locks the PIDFile with fcntl.lockf()."""
+
+    def __init__(self, path):
+        self.path = path
+
+    def __enter__(self):
+        """Lock the PID file and write the process ID to the file."""
+        fd = os.open(self.path, os.O_RDWR | os.O_CREAT, 0644)
+        try:
+            fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+            pidfile = os.fdopen(fd, 'w')
+        except:
+            os.close(fd)
+            raise
+        pidfile.write('%d\n' % os.getpid())
+        pidfile.flush()
+        self.pidfile = pidfile
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        """Release the lock (close the lockfile)."""
+        fcntl.lockf(self.pidfile.fileno(), fcntl.LOCK_UN)
+        self.pidfile.close()
+        del self.pidfile
+
+    def is_locked(self):
+        """Check whether the file is already present and locked."""
+        try:
+            fd = os.open(self.path, os.O_RDWR, 0644)
+            # Python doesn't seem to have F_TEST so we'll just try to lock
+            fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+            # if we're here we must have aquired the lock
+            fcntl.lockf(fd, fcntl.LOCK_UN)
+            return False
+        except (IOError, OSError), e:
+            if e.errno == errno.ENOENT:
+                return False
+            if e.errno in (errno.EACCES, errno.EAGAIN):
+                return True
+            raise
+        finally:
+            if 'fd' in locals():
+                os.close(fd)

Added: nss-pam-ldapd/pynslcd/pam.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/pam.py        Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,129 @@
+
+# pam.py - functions authentication, authorisation and session handling
+#
+# Copyright (C) 2010 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 constants
+import common
+import cfg
+
+import logging
+import ldap
+
+import passwd
+
+def try_bind(userdn, password):
+    # open a new connection
+    conn = ldap.initialize(cfg.ldap_uri)
+    # bind using the specified credentials
+    conn.simple_bind_s(userdn, password)
+    # perform search for own object (just to do any kind of search)
+    res = conn.search_s(userdn, ldap.SCOPE_BASE, '(objectClass=*)', [ 'dn', ])
+    for entry in res:
+        if entry[0] == userdn:
+            return
+    raise ldap.NO_SUCH_OBJECT()
+
+
+class PAMRequest(common.Request):
+
+    def validate_request(self):
+        """This method checks the provided username for validity and fills
+        in the DN if needed."""
+        from passwd import PasswdRequest
+        # check username for validity
+        common.validate_name(self.username)
+        # look up user DN if not known
+        if not self.userdn:
+            entry = passwd.uid2entry(self.conn, self.username)
+            if not entry:
+                raise ValueError('%r: user not found' % self.username)
+            # save the DN
+            self.userdn = entry[0]
+            # get the "real" username
+            value = common.get_rdn_value(entry, 
PasswdRequest.attmap_passwd_uid)
+            if not value:
+                # get the username from the uid attribute
+                values = myldap_get_values(entry, 
PasswdRequest.attmap_passwd_uid)
+                if not values or not values[0]:
+                    logging.warn('%s: is missing a %s attribute', entry.dn, 
PasswdRequest.attmap_passwd_uid)
+                value = values[0]
+            # check the username
+            if value and not common.isvalidname(value):
+                raise ValueError('%s: has invalid %s attribute', entry.dn, 
PasswdRequest.attmap_passwd_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)
+                self.username = value
+
+
+class PAMAuthenticationRequest(PAMRequest):
+
+    action = constants.NSLCD_ACTION_PAM_AUTHC
+
+    def read_parameters(self):
+        self.username = self.fp.read_string()
+        self.userdn = self.fp.read_string()
+        self.servicename = self.fp.read_string()
+        self.password = self.fp.read_string()
+        #self.validate_request()
+        # TODO: log call with parameters
+
+    def write(self, code=constants.NSLCD_PAM_SUCCESS, msg=''):
+        self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
+        self.fp.write_string(self.username)
+        self.fp.write_string(self.userdn)
+        self.fp.write_int32(code)  # authc
+        self.fp.write_int32(constants.NSLCD_PAM_SUCCESS)  # authz
+        self.fp.write_string(msg) # authzmsg
+        self.fp.write_int32(constants.NSLCD_RESULT_END)
+
+    def handle_request(self):
+        # if the username is blank and rootpwmoddn is configured, try to
+        # authenticate as administrator, otherwise validate request as usual
+        if not self.username and cfg.ldc_rootpwmoddn:
+            # authenticate as rootpwmoddn
+            self.userdn = cfg.ldc_rootpwmoddn
+            # if the caller is root we will allow the use of rootpwmodpw
+            if not self.password and self.calleruid == 0 and cfg.rootpwmodpw:
+                self.password = cfg.rootpwmodpw
+        else:
+            self.validate_request()
+        # try authentication
+        try:
+            try_bind(self.userdn, self.password)
+            logging.debug('bind successful')
+            self.write()
+        except ldap.INVALID_CREDENTIALS, e:
+            try:
+                msg = e[0]['desc']
+            except:
+                msg = str(e)
+            logging.debug('bind failed: %s', msg)
+            self.write(constants.NSLCD_PAM_AUTH_ERR, msg)
+
+#class PAMAuthorisationRequest(PAMRequest):
+
+#    action = constants.NSLCD_ACTION_PAM_AUTHZ
+
+#    def handle_request(self):
+
+
+#NSLCD_ACTION_PAM_SESS_O
+#NSLCD_ACTION_PAM_SESS_C
+#NSLCD_ACTION_PAM_PWMOD

Added: nss-pam-ldapd/pynslcd/passwd.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/passwd.py     Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,163 @@
+
+# passwd.py - lookup functions for user account information
+#
+# Copyright (C) 2010 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 constants
+import common
+import cfg
+
+import logging
+import ldap
+import ldap.filter
+
+
+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'
+
+    attributes = ( 'uid', 'userPassword', 'uidNumber', 'gidNumber',
+                   'gecos', 'cn', 'homeDirectory', 'loginShell',
+                   'objectClass' )
+
+    bases = ( 'ou=people,dc=test,dc=tld', )
+
+    def write(self, entry):
+        dn, attributes = entry
+        # get uid attribute and check against requested user name
+        names = attributes.get('uid', [])
+        if self.name:
+            if self.name not in names:
+                return
+            names = ( self.name, )
+        # get user password entry
+        if 'shadowAccount' in attributes.get('objectClass', []):
+            passwd = 'x'
+        else:
+            passwd = '*';
+        # get numeric user and group ids
+        uids = ( self.uid, ) if self.uid else 
attributes.get(self.attmap_passwd_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, [''])
+        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)
+                    self.fp.write_uid_t(uid)
+                    self.fp.write_gid_t(gid)
+                    self.fp.write_string(gecos)
+                    self.fp.write_string(home)
+                    self.fp.write_string(shell)
+
+
+class PasswdByNameRequest(PasswdRequest):
+
+    action = constants.NSLCD_ACTION_PASSWD_BYNAME
+
+    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
+
+    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
+    # 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])
+            for entry in res:
+                if entry[0]:
+                    yield entry
+        except ldap.NO_SUCH_OBJECT:
+            # FIXME: log message
+            pass
+
+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) )
+    for dn, attributes in do_search(conn, myfilter):
+        if uid in attributes[PasswdRequest.attmap_passwd_uid]:
+            return dn, attributes
+
+def uid2dn(conn, uid):
+    """Look up the user by uid and return the DN or None if the user was
+    not found."""
+    x = uid2entry(conn, uid)
+    if x is not None:
+        return x[0]
+
+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]
+    except ldap.NO_SUCH_OBJECT:
+        return None

Added: nss-pam-ldapd/pynslcd/pynslcd.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/pynslcd.py    Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,276 @@
+#!/usr/bin/env python
+
+# pynslcd.py - main daemon module
+#
+# Copyright (C) 2010 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 os
+import sys
+import daemon
+import mypidfile
+import threading
+import logging
+import logging.handlers
+import signal
+import ldap
+
+import constants  # from nslcd.h
+import config     # from configure
+import cfg        # from nslcd.conf
+import common
+
+from tio import TIOStream
+
+
+# configure logging
+class MyFormatter(logging.Formatter):
+    def format(self, record):
+        msg = logging.Formatter.format(self, record)
+        if record.levelno == logging.DEBUG:
+            msg = 'DEBUG: %s' % msg
+        return msg
+#logging.basicConfig(level=logging.INFO)
+# , format='%(message)s'
+formatter = MyFormatter('%(message)s')
+stderrhandler = logging.StreamHandler(sys.stderr)
+stderrhandler.setFormatter(formatter)
+##sysloghandler = logging.handlers.SysLogHandler(address='/dev/log')
+##sysloghandler.setFormatter(formatter)
+#logging.getLogger().setFormatter(MyFormatter())
+logging.getLogger().addHandler(stderrhandler)
+
+#logger = logging.getLogger()
+#logger.setLevel(logging.INFO)
+#syslog = logging.handlers.SysLogHandler(address='/dev/log')
+#formatter = logging.Formatter('%(name)s: %(levelname)s %(message)s')
+#syslog.setFormatter(formatter)
+#logger.addHandler(syslog)
+
+def display_version(fp):
+    fp.write('%(PACKAGE_STRING)s\n'
+             'Written by Arthur de Jong.\n'
+             '\n'
+             'Copyright (C) 2010 Arthur de Jong\n'
+             'This is free software; see the source for copying conditions.  
There is NO\n'
+             'warranty; not even for MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE.\n'
+             % { 'PACKAGE_STRING': config.PACKAGE_STRING, } );
+
+def display_usage(fp):
+    fp.write("Usage: %(program_name)s [OPTION]...\n"
+             "Name Service LDAP connection daemon.\n"
+             "  -c, --check        check if the daemon already is running\n"
+             "  -d, --debug        don't fork and print debugging to stderr\n"
+             "      --help         display this help and exit\n"
+             "      --version      output version information and exit\n"
+             "\n"
+             "Report bugs to <%(PACKAGE_BUGREPORT)s>.\n"
+             % { 'program_name': cfg.program_name,
+                 'PACKAGE_BUGREPORT': config.PACKAGE_BUGREPORT, } )
+
+def parse_cmdline():
+    """Parse command-line arguments."""
+    import getopt
+    cfg.program_name = sys.argv[0] or 'pynslcd'
+    try:
+        optlist, args = getopt.gnu_getopt(sys.argv[1:],
+          'cdhV', ('check', 'debug', 'help', 'version', ))
+        for flag, arg in optlist:
+            if flag in ('-c', '--check'):
+                cfg.check = True
+            elif flag in ('-d', '--debug'):
+                cfg.debug += 1
+            elif flag in ('-h', '--help'):
+                display_usage(sys.stdout)
+                sys.exit(0)
+            elif flag in ('-V', '--version'):
+                display_version(sys.stdout)
+                sys.exit(0)
+        if len(args):
+            raise getopt.GetoptError('unrecognized option \'%s\'' % args[0], 
args[0])
+    except getopt.GetoptError, reason:
+        sys.stderr.write("%(program_name)s: %(reason)s\n"
+                         "Try '%(program_name)s --help' for more 
information.\n"
+                          % { 'program_name': cfg.program_name,
+                              'reason': reason, })
+        sys.exit(1)
+
+def create_socket():
+    """Returns a socket ready to answer requests from the client."""
+    import socket
+    import fcntl
+    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+    # remove existing named socket
+    try:
+        os.unlink(config.NSLCD_SOCKET)
+    except OSError:
+        pass # ignore any problems
+    # bind to named socket
+    sock.bind((config.NSLCD_SOCKET))
+    # close the file descriptor on exit
+    fcntl.fcntl(sock, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
+    # set permissions of socket so anybody can do requests
+    os.chmod(config.NSLCD_SOCKET, 0666)
+    # start listening for connections
+    sock.listen(socket.SOMAXCONN)
+    return sock
+
+def log_newsession():
+    pass
+    # FIXME: implement
+
+def getpeercred(fd):
+    return (None, None, None)
+    # FIXME: implement and return uid, gid, pid
+
+handlers = {}
+handlers.update(common.get_handlers('alias'))
+handlers.update(common.get_handlers('ether'))
+handlers.update(common.get_handlers('group'))
+handlers.update(common.get_handlers('pam'))
+handlers.update(common.get_handlers('passwd'))
+handlers.update(common.get_handlers('shadow'))
+
+def acceptconnection(session):
+    # accept a new connection
+    conn, addr = nslcd_serversocket.accept()
+    # See: http://docs.python.org/library/socket.html#socket.socket.settimeout
+    fp = None
+    try:
+        # probably use finally
+        # indicate new connection to logging module (genrates unique id)
+        log_newsession()
+        # log connection
+        try:
+            uid, gid, pid = getpeercred(conn)
+            logging.debug('connection from pid=%r uid=%r gid=%r', pid, uid, 
gid)
+        except:
+            raise # FIXME: handle exception gracefully
+        # create a stream object
+        fp = TIOStream(conn)
+        # read request
+        version = fp.read_int32()
+        if version != constants.NSLCD_VERSION:
+            logging.debug('wrong nslcd version id (%r)', version)
+            return
+        action = fp.read_int32()
+        try:
+            handler = handlers[action]
+        except KeyError:
+            logging.warn('invalid action id: %r', action)
+            return
+        handler(fp, session, uid)()
+    finally:
+        if fp:
+            fp.close()
+
+def disable_nss_ldap():
+    """Disable the nss_ldap module to avoid lookup loops."""
+    import ctypes
+    lib = ctypes.CDLL(config.NSS_LDAP_SONAME)
+    ctypes.c_int.in_dll(lib, '_nss_ldap_enablelookups').value = 0
+
+def worker():
+    # create a new LDAP session
+    #session = myldap_create_session()
+    session = ldap.initialize(cfg.ldap_uri)
+    # start waiting for incoming connections
+    while True:
+        # wait for a new connection
+        acceptconnection(session)
+        # FIXME: handle exceptions
+
+if __name__ == '__main__':
+    # parse options
+    parse_cmdline()
+    # clean the environment
+    os.environ.clear()
+    os.putenv('HOME', '/')
+    os.putenv('TMPDIR', '/tmp')
+    os.putenv('LDAPNOINIT', '1')
+    # disable ldap lookups of host names to avoid lookup loop
+    disable_nss_ldap()
+    # set log level
+    if cfg.debug:
+        logging.getLogger().setLevel(logging.DEBUG)
+    # FIXME: implement
+    #if myldap_set_debuglevel(cfg.debug) != LDAP_SUCCESS:
+    #    sys.exit(1)
+    # read configuration file
+    cfg.read(config.NSLCD_CONF_PATH)
+    # set a default umask for the pidfile and socket
+    os.umask(0022)
+    # see if someone already locked the pidfile
+    pidfile = mypidfile.MyPIDLockFile(config.NSLCD_PIDFILE)
+    # see if --check option was given
+    if cfg.check:
+        if pidfile.is_locked():
+            logging.debug('pidfile (%s) is locked', config.NSLCD_PIDFILE)
+            sys.exit(0)
+        else:
+            logging.debug('pidfile (%s) is not locked', config.NSLCD_PIDFILE)
+            sys.exit(1)
+    # normal check for pidfile locked
+    if pidfile.is_locked():
+        logging.error('daemon may already be active, cannot acquire lock 
(%s)', config.NSLCD_PIDFILE)
+        sys.exit(1)
+    # daemonize
+    if cfg.debug:
+        daemon = pidfile
+    else:
+        daemon = daemon.DaemonContext(
+                      pidfile=pidfile,
+                      signal_map={
+                          signal.SIGTERM: 'terminate',
+                          signal.SIGINT:  'terminate',
+                          signal.SIGPIPE: None,
+                      })
+    # start daemon
+    with daemon:
+        # start normal logging
+        if not cfg.debug:
+            log_startlogging();
+        logging.info('version %s starting', config.VERSION)
+        # create socket
+        nslcd_serversocket = create_socket();
+        # drop all supplemental groups
+        try:
+            os.setgroups(())
+        except OSError, e:
+            logging.warn('cannot setgroups(()) (ignored): %s', e)
+        # change to nslcd gid
+        if cfg.gid is not None:
+            import grp
+            os.setgid(grp.getgrnam(cfg.gid).gr_gid)
+        # change to nslcd uid
+        if cfg.uid is not None:
+            import pwd
+            u = pwd.getpwnam(cfg.uid)
+            os.setuid(u.pw_uid)
+            os.environ['HOME'] = u.pw_dir
+        logging.info('accepting connections')
+        # start worker threads
+        threads = []
+        for i in range(cfg.threads):
+            thread = threading.Thread(target=worker, name='thread%d' % i)
+            thread.setDaemon(True)
+            thread.start()
+            logging.debug('started thread %s' % thread.getName())
+            threads.append(thread)
+        # wait for all threads to die
+        for thread in threads:
+            thread.join(10000)

Added: nss-pam-ldapd/pynslcd/shadow.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/shadow.py     Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,116 @@
+
+# shadow.py - lookup functions for shadownet addresses
+#
+# Copyright (C) 2010 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 constants
+import common
+
+import ldap.filter
+
+
+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'
+
+    attributes = ( 'uid', 'userPassword', 'shadowLastChange', 'shadowMin',
+                   'shadowMax', 'shadowWarning', 'shadowInactive',
+                   'shadowExpire', 'shadowFlag' )
+
+    bases = ( 'ou=people,dc=test,dc=tld', )
+
+    def write(self, entry):
+        dn, attributes = entry
+        # get name and check against requested name
+        names = attributes.get(self.attmap_uid, [])
+        if not names:
+            print 'Error: entry %s does not contain %s value' % ( dn, 
self.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'])
+        if not passwd or self.calleruid != 0:
+            passwd = '*';
+        # function for making an int
+        def mk_int(attr):
+            try:
+                return
+            except TypeError:
+                return None
+        # get lastchange date
+        lastchangedate = int(attributes.get(self.attmap_shadowLastChange, 
[-1])[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':
+            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':
+            if flag & 0x10000:
+                maxdays = 99999
+            flag = 0
+        # write results
+        for name in names:
+            self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
+            self.fp.write_string(name)
+            self.fp.write_string(passwd)
+            self.fp.write_int32(lastchangedate)
+            self.fp.write_int32(mindays)
+            self.fp.write_int32(maxdays)
+            self.fp.write_int32(warndays)
+            self.fp.write_int32(inactdays)
+            self.fp.write_int32(expiredate)
+            self.fp.write_int32(flag)
+
+
+class ShadowByNameRequest(ShadowRequest):
+
+    action = constants.NSLCD_ACTION_SHADOW_BYNAME
+
+    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):
+
+    action = constants.NSLCD_ACTION_SHADOW_ALL

Added: nss-pam-ldapd/pynslcd/tio.py
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ nss-pam-ldapd/pynslcd/tio.py        Wed Dec 29 22:50:17 2010        (r1347)
@@ -0,0 +1,98 @@
+
+# tio.py - I/O functions
+#
+# Copyright (C) 2010 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 struct
+import os
+import socket
+import errno
+
+# definition for reading and writing INT32 values
+_int32 = struct.Struct('i')
+
+# FIXME: use something from config.py to determine the correct size
+_uid_t = struct.Struct('i')
+
+# FIXME: use something from config.py to determine the correct size
+_gid_t = struct.Struct('i')
+
+# FIXME: use something from config.py to determine the correct size
+_struct_timeval = struct.Struct('ll')
+
+class TIOStreamError(Exception):
+    pass
+
+class TIOStream(object):
+    """File-like object that allows reading and writing nslcd-protocol
+    entities."""
+
+    def __init__(self, conn):
+        conn.setblocking(1)
+        conn.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, 
_struct_timeval.pack(0, 500000))
+        conn.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, 
_struct_timeval.pack(60, 0))
+        self.fp = os.fdopen(conn.fileno(), 'w+b', 1024*1024)
+
+    def read(self, size):
+        return self.fp.read(size)
+
+    def read_int32(self):
+        return _int32.unpack(self.read(_int32.size))[0]
+
+    def read_uid_t(self):
+        return _uid_t.unpack(self.read(_uid_t.size))[0]
+
+    def read_gid_t(self):
+        return _gid_t.unpack(self.read(_gid_t.size))[0]
+
+    def read_string(self, maxsize=None):
+        len = self.read_int32()
+        if maxsize and len >= maxsize:
+            raise TIOStreamError()
+        return self.read(len)
+
+    def write(self, value):
+        self.fp.write(value)
+
+    def write_int32(self, value):
+        self.write(_int32.pack(value))
+
+    def write_uid_t(self, value):
+        self.write(_uid_t.pack(value))
+
+    def write_gid_t(self, value):
+        self.write(_gid_t.pack(value))
+
+    def write_string(self, value):
+        self.write_int32(len(value))
+        self.write(value)
+
+    def write_stringlist(self, value):
+        lst = tuple(value)
+        self.write_int32(len(lst))
+        for string in lst:
+            self.write_string(string)
+
+    def close(self):
+        try:
+            self.fp.close()
+        except IOError:
+            pass
+
+    def __del__(self):
+        self.close()
--
To unsubscribe send an email to
nss-pam-ldapd-commits-unsubscribe@lists.arthurdejong.org or see
http://lists.arthurdejong.org/nss-pam-ldapd-commits