diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2010-12-29 22:50:31 +0000 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2010-12-29 22:50:31 +0000 |
commit | e985efa83458e1cc9c2bcb12e3cc10b6526c3399 (patch) | |
tree | 8311cb525c9d452d62d88280e6cca854496f9c42 /pynslcd/pynslcd.py | |
parent | 4e9224817ee303404b804a1a51f2f9c9a49164e4 (diff) | |
parent | ed6bc27721075adf0215ad8b856fcdcf7b98b9b7 (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-x | pynslcd/pynslcd.py | 276 |
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) |