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
- From: Commits of the nss-pam-ldapd project <nss-pam-ldapd-commits [at] lists.arthurdejong.org>
- To: nss-pam-ldapd-commits [at] lists.arthurdejong.org
- Reply-to: nss-pam-ldapd-users [at] lists.arthurdejong.org
- Subject: nss-pam-ldapd commit: r1347 - in nss-pam-ldapd: . pynslcd
- Date: Wed, 29 Dec 2010 22:50:19 +0100 (CET)
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
- nss-pam-ldapd commit: r1347 - in nss-pam-ldapd: . pynslcd,
Commits of the nss-pam-ldapd project