summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2013-03-29 20:23:54 +0100
committerArthur de Jong <arthur@arthurdejong.org>2013-03-29 20:23:54 +0100
commitadde1d4cd3b7bfdd9ed523ad9e0c81f1ca5f6de5 (patch)
treec99a498301ee2899150ced655437dd61844c0454
parent65a65adbf953ee8da43a76db2cd03a064a80cd46 (diff)
parenta75cfb9d5f67dd4be325f151f9e0fc9af0864ac2 (diff)
Implement clearing of nscd cache in pynslcd
-rw-r--r--pynslcd/Makefile.am2
-rw-r--r--pynslcd/cfg.py11
-rw-r--r--pynslcd/nscd.py112
-rwxr-xr-xpynslcd/pynslcd.py4
-rw-r--r--pynslcd/search.py22
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):
"""