summaryrefslogtreecommitdiff
path: root/pynslcd/pynslcd.py
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2010-12-29 22:50:31 +0000
committerArthur de Jong <arthur@arthurdejong.org>2010-12-29 22:50:31 +0000
commite985efa83458e1cc9c2bcb12e3cc10b6526c3399 (patch)
tree8311cb525c9d452d62d88280e6cca854496f9c42 /pynslcd/pynslcd.py
parent4e9224817ee303404b804a1a51f2f9c9a49164e4 (diff)
parented6bc27721075adf0215ad8b856fcdcf7b98b9b7 (diff)
merge changes from trunk
git-svn-id: http://arthurdejong.org/svn/nss-pam-ldapd/nss-pam-ldapd-solaris@1349 ef36b2f9-881f-0410-afb5-c4e39611909c
Diffstat (limited to 'pynslcd/pynslcd.py')
-rwxr-xr-xpynslcd/pynslcd.py276
1 files changed, 276 insertions, 0 deletions
diff --git a/pynslcd/pynslcd.py b/pynslcd/pynslcd.py
new file mode 100755
index 0000000..4644a35
--- /dev/null
+++ b/pynslcd/pynslcd.py
@@ -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)