From 0b50c9f4873148c441403bf09d9458ba22473876 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sven=20M=C3=A4der?= <maeder@phys.ethz.ch>
Date: Wed, 11 Apr 2018 00:43:46 +0200
Subject: [PATCH] Add slapd to lib, add logging

---
 bin/deltalogparse.py | 70 +++++++++++++++++---------------------------
 lib/isg/dphysldap.py | 48 ++++++++++++++++++++++++++++++
 2 files changed, 75 insertions(+), 43 deletions(-)

diff --git a/bin/deltalogparse.py b/bin/deltalogparse.py
index d4bdab8..5f4e678 100755
--- a/bin/deltalogparse.py
+++ b/bin/deltalogparse.py
@@ -5,18 +5,25 @@ import time
 import datetime
 import re
 import pprint
+import logging
 import lib_path
 import lib
-import ldap3
+import dphysldap
 
+process_name = 'deltalogparse'
 debug = 2
+loglevel = logging.DEBUG
 update_every = 1
 log_path = '/var/log/ldap/delta.log'
 search_base = 'cn=deltalog'
-search_filter = '(|(objectClass=auditModify)(objectClass=auditAdd)(objectClass=auditDelete))'
-search_scope = ldap3.SUBTREE
-attributes = [ldap3.ALL_ATTRIBUTES, ldap3.ALL_OPERATIONAL_ATTRIBUTES]
+object_classes = '(|(objectClass=auditModify)(objectClass=auditAdd)(objectClass=auditDelete))'
 
+logging.basicConfig(
+    filename='/var/log/ldap/deltalogparse.log',
+    level=loglevel,
+    format='%(asctime)s ' + process_name + '[%(process)d]:%(levelname)s: %(message)s',
+    datefmt='%Y-%m-%d %H:%M:%S'
+)
 
 rgx_skip = re.compile(
     r'^('
@@ -40,8 +47,7 @@ rgx_filter = re.compile(
 def is_skipped(attributes):
     for attribute in attributes:
         if not rgx_skip.search(attribute):
-            if debug > 0:
-                print('hmm... interesting attribute: %s' % attribute)
+            logging.info('hmm... interesting attribute: %s' % attribute)
             return False
 
     return True
@@ -55,11 +61,8 @@ def log(entry):
     entry['attributes'] = attributes
     del entry['raw_attributes']
 
-    if debug > 1:
-        pprint.pprint(entry, indent=4)
-    
-    if debug > 0:
-        print('log: %s %s' % (req_type, req_dn))
+    logging.debug(pprint.pformat(entry, indent=4))
+    logging.info('log: %s %s' % (req_type, req_dn))
 
 
 def sleep(start_time):
@@ -67,8 +70,7 @@ def sleep(start_time):
     elapsed_time = current_time - start_time
     sleep_time = update_every - elapsed_time
 
-    if debug > 0:
-        print('\nruntime %0.3fs, sleeping %0.3fs' % (elapsed_time, sleep_time))
+    logging.info('runtime %0.3fs, sleeping %0.3fs' % (elapsed_time, sleep_time))
 
     if sleep_time > 0:
         time.sleep(sleep_time)
@@ -76,40 +78,24 @@ def sleep(start_time):
 
 def main():
     """Connect to slapd socket and parse accesslog"""
-    server = ldap3.Server(
-        'ldapi:///var/run/slapd/ldapi',
-        get_info=[
-            ldap3.ALL,
-            ldap3.OFFLINE_SLAPD_2_4
-        ]
-    )
-
-    connection = ldap3.Connection(
-        server,
-        authentication=ldap3.SASL,
-        sasl_mechanism=ldap3.EXTERNAL,
-        sasl_credentials='',
-        auto_bind='NONE',
-        version=3,
-        client_strategy='SYNC'
-    )
-
-    connection.bind()
+    slapd = dphysldap.Slapd()
 
     req_start = datetime.datetime.utcnow().strftime('%Y%m%d%H%M%S.%fZ')
 
     while True:
         start_time = time.perf_counter()
         
-        response = connection.search(
-            search_base=search_base,
-            search_filter=''.join(['(&', search_filter, '(reqStart>=', req_start, ')',')']),
-            search_scope=search_scope,
-            attributes=attributes
-        )
+        search_filter=''.join(['(&', object_classes, '(reqStart>=', req_start, ')',')'])
+
+        try:
+            response = slapd.search(search_base, search_filter)
+        except:
+            logging.error('socket error!')
+            if slapd.reconnect():
+                logging.error('socket reconnected.')
 
         if response:
-            for entry in connection.response:
+            for entry in slapd.response():
                 if entry['attributes']['reqStart'][0] == req_start:
                     continue
 
@@ -117,14 +103,12 @@ def main():
                 req_type = entry['attributes']['reqType'][0]
                 req_dn = entry['attributes']['reqDN'][0]
 
-                if debug > 0:
-                    print(' '.join(['\nprocessing:', req_start, entry['dn']]))
+                logging.info(' '.join(['processing:', req_start, entry['dn']]))
 
                 if req_type == 'modify':
                     req_mods = entry['attributes']['reqMod']
                     if is_skipped(req_mods):
-                        if debug > 0:
-                            print('skip: %s %s' % (req_type, req_dn))
+                        logging.info('skip: %s %s' % (req_type, req_dn))
                         continue
 
                 log(entry)
diff --git a/lib/isg/dphysldap.py b/lib/isg/dphysldap.py
index 171b160..123a5b9 100644
--- a/lib/isg/dphysldap.py
+++ b/lib/isg/dphysldap.py
@@ -2,6 +2,7 @@
 
 import ssl
 import collections
+import time
 import lib_path
 import lib
 import ldap3
@@ -11,6 +12,10 @@ FMT = 'simple'
 SERVERS = ['phd-aa1.ethz.ch', 'phd-aa2.ethz.ch', 'phd-aa3.ethz.ch']
 BASE = 'dc=phys,dc=ethz,dc=ch'
 CA_CERTS = '/etc/ssl/certs/ca-certificates.crt'
+SLAPD_SOCKET = 'ldapi:///var/run/slapd/ldapi'
+SLAPD_INFO = [ldap3.ALL, ldap3.OFFLINE_SLAPD_2_4]
+SLAPD_SEARCH_SCOPE = ldap3.SUBTREE
+SLAPD_SEARCH_ATTRS = [ldap3.ALL_ATTRIBUTES, ldap3.ALL_OPERATIONAL_ATTRIBUTES]
 
 
 class AttributeValue(list):
@@ -85,6 +90,49 @@ class User(Entry):
         Entry.__init__(self, *args, **kwargs)
 
 
+class Slapd(object):
+    """
+    SLAPD connection to socket
+    """
+    def __init__(self, socket=SLAPD_SOCKET, get_info=SLAPD_INFO):
+        self.server = ldap3.Server(socket, get_info=get_info)
+        self.connect()
+
+    def connect(self):
+        self.connection = ldap3.Connection(
+            self.server,
+            authentication=ldap3.SASL,
+            sasl_mechanism=ldap3.EXTERNAL,
+            sasl_credentials='',
+            auto_bind='NONE',
+            version=3,
+            client_strategy='SYNC'
+        )
+        self.connection.bind()
+
+    def search(self, search_base, search_filter, search_scope=SLAPD_SEARCH_SCOPE,
+               attributes=SLAPD_SEARCH_ATTRS):
+        response = self.connection.search(search_base, search_filter,
+                                          search_scope=search_scope,
+                                          attributes=attributes)
+        return response
+
+    def response(self):
+        return self.connection.response
+
+    def reconnect(self, interval=1, retries=0):
+        forever = True if not retries else False
+
+        while forever or retries > 0:
+            try:
+                self.connect()
+                return True
+            except ldap3.core.exceptions.LDAPSocketOpenError:
+                retries -= 1
+                time.sleep(interval)
+
+        return False
+
 class Ldap(object):
     """
     LDAP connection to random server in pool
-- 
GitLab