diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2013-03-29 20:23:54 +0100 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2013-03-29 20:23:54 +0100 |
commit | adde1d4cd3b7bfdd9ed523ad9e0c81f1ca5f6de5 (patch) | |
tree | c99a498301ee2899150ced655437dd61844c0454 | |
parent | 65a65adbf953ee8da43a76db2cd03a064a80cd46 (diff) | |
parent | a75cfb9d5f67dd4be325f151f9e0fc9af0864ac2 (diff) |
Implement clearing of nscd cache in pynslcd
-rw-r--r-- | pynslcd/Makefile.am | 2 | ||||
-rw-r--r-- | pynslcd/cfg.py | 11 | ||||
-rw-r--r-- | pynslcd/nscd.py | 112 | ||||
-rwxr-xr-x | pynslcd/pynslcd.py | 4 | ||||
-rw-r--r-- | pynslcd/search.py | 22 |
5 files changed, 150 insertions, 1 deletions
diff --git a/pynslcd/Makefile.am b/pynslcd/Makefile.am index cff5629..6203d12 100644 --- a/pynslcd/Makefile.am +++ b/pynslcd/Makefile.am @@ -20,7 +20,7 @@ pynslcddir = $(datadir)/pynslcd pynslcd_PYTHON = pynslcd.py attmap.py cache.py cfg.py common.py expr.py \ - mypidfile.py search.py tio.py \ + mypidfile.py nscd.py search.py tio.py \ alias.py ether.py group.py host.py netgroup.py network.py \ pam.py passwd.py protocol.py rpc.py service.py shadow.py nodist_pynslcd_PYTHON = constants.py diff --git a/pynslcd/cfg.py b/pynslcd/cfg.py index b03b8c7..eaeaff0 100644 --- a/pynslcd/cfg.py +++ b/pynslcd/cfg.py @@ -89,6 +89,7 @@ nss_nested_groups = False validnames = re.compile(r'^[a-z0-9._@$][a-z0-9._@$ \\~-]{0,98}[a-z0-9._@$~-]$', re.IGNORECASE) pam_authz_searches = [] pam_password_prohibit_message = None +nscd_invalidate = set() # allowed boolean values @@ -312,6 +313,16 @@ def read(filename): flags = 0 | re.IGNORECASE if m.group('flags') == 'i' else 0 validnames = re.compile(m.group('value'), flags=flags) continue + # nscd_invalidate <MAP>,<MAP>,... + m = re.match('nscd_invalidate\s+(?P<value>\S.*)', + line, re.IGNORECASE) + if m: + dbs = re.split('[ ,]+', m.group('value').lower()) + for db in dbs: + if db not in maps: + raise ParseError(filename, lineno, 'map %s unknown' % db) + nscd_invalidate.update(dbs) + continue # unrecognised line raise ParseError(filename, lineno, 'error parsing line %r' % line) # if logging is not configured, default to syslog diff --git a/pynslcd/nscd.py b/pynslcd/nscd.py new file mode 100644 index 0000000..19e5ceb --- /dev/null +++ b/pynslcd/nscd.py @@ -0,0 +1,112 @@ + +# nscd.py - functions for invalidating the nscd cache +# +# Copyright (C) 2013 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 logging +import os +import subprocess +import struct + +import cfg + + +# the file descriptor used for sending messages to the child process +signalfd = None + + +# mapping between map name and signal character +_db_to_char = dict( + aliases='A', ethers='E', group='G', hosts='H', netgroup='U', + networks='N', passwd='P', protocols='L', rpc='R', services='V', + shadow='S', + ) +_char_to_db = dict((reversed(item) for item in _db_to_char.items())) + + +def exec_invalidate(db): + logging.debug('nscd_invalidator: nscd -i %s', db) + try: + p = subprocess.Popen(['nscd', '-i', 'passwd'], + bufsize=4096, close_fds=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + output, ignored = p.communicate() + if output: + output = ': %s' % output[:1024].strip() + if p.returncode == 0: + logging.debug('nscd_invalidator: nscd -i %s (pid %d) success%s', + db, p.pid, output) + elif p.returncode > 0: + logging.debug('nscd_invalidator: nscd -i %s (pid %d) failed (%d)%s', + db, p.pid, p.returncode, output) + else: # p.returncode < 0 + logging.error('nscd_invalidator: nscd -i %s (pid %d) killed by signal %d%s', + db, p.pid, -p.returncode, output) + except: + logging.warn('nscd_invalidator: nscd -i %s failed', db, exc_info=True) + + +def loop(fd): + # set process title + try: + import setproctitle + setproctitle.setproctitle('(nscd invalidator)') + except ImportError: + pass + # set up clean environment + os.chdir('/') + os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin' + while True: + db = os.read(fd, 1) + # FIXME: define the characters and maps somewhere + if db == '': + break + db = _char_to_db.get(db, None) + if db: + exec_invalidate(db) + + +def start_invalidator(): + r, w = os.pipe() + # mark write end as non-blocking + flags = fcntl.fcntl(w, fcntl.F_GETFL) + fcntl.fcntl(w, fcntl.F_SETFL, flags | os.O_NONBLOCK) + cpid = os.fork() + if cpid == 0: + # we are the child + os.close(w) + loop(r) + os._exit(1) + # we are the parent + global signalfd + signalfd = w + os.close(r) + + +def invalidate(db=None): + if signalfd is None: + return # nothing to do + if db: + db = _db_to_char.get(db, '') + else: + db = ''.join(_db_to_char[x] for x in cfg.nscd_invalidate) + try: + os.write(signalfd, db) + except: + logging.warn('nscd_invalidator: nscd -i %s failed', db, exc_info=True) diff --git a/pynslcd/pynslcd.py b/pynslcd/pynslcd.py index 0082f24..eedab78 100755 --- a/pynslcd/pynslcd.py +++ b/pynslcd/pynslcd.py @@ -35,6 +35,7 @@ import cfg import common import constants import mypidfile +import nscd import search @@ -316,6 +317,9 @@ if __name__ == '__main__': logging.getLogger().setLevel(min(level for method, level in cfg.logs)) logging.getLogger().removeHandler(stderrhandler) logging.info('version %s starting', constants.VERSION) + # start nscd sub-process if needed + if cfg.nscd_invalidate: + nscd.start_invalidator() # create socket nslcd_serversocket = create_socket() # load supplementary groups diff --git a/pynslcd/search.py b/pynslcd/search.py index 9629bec..219929b 100644 --- a/pynslcd/search.py +++ b/pynslcd/search.py @@ -25,6 +25,11 @@ import ldap import ldap.ldapobject import cfg +import nscd + + +# global indicator that there was some error connection to an LDAP server +server_error = False class Connection(ldap.ldapobject.ReconnectLDAPObject): @@ -50,6 +55,23 @@ class Connection(ldap.ldapobject.ReconnectLDAPObject): if cfg.ssl or cfg.uri.startswith('ldaps://'): self.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_HARD) + def reconnect_after_fail(self): + logging.info('connected to LDAP server %s', cfg.uri) + nscd.invalidate() + + def search_s(self, *args, **kwargs): + # wrapper function to keep the global server_error state + global server_error + try: + res = ldap.ldapobject.ReconnectLDAPObject.search_s(self, *args, **kwargs) + except ldap.SERVER_DOWN: + server_error = True + raise + if server_error: + self.reconnect_after_fail() + server_error = False + return res + class LDAPSearch(object): """ |