Difference between revisions of "Entwicklung von Univention Directory Listener-Modulen"

From Univention Wiki

Jump to: navigation, search
Line 40: Line 40:
 
|-
 
|-
 
| filter  
 
| filter  
| Definiert den LDAP-Filter, bei dessen Übereinstimmung das Modul verwendet wird. Es ist zu beachten, dass dieser Filter - im Gegensatz zu <tt>ldapsearch</tt> - case-sensitive ist und in Klammern gesetzt werden muss.<br>Achtung: Der Name dieser Variable überdeckt unglücklicherweise die eingebaute Python-Funktion <tt>filter()</tt>.
+
| Definiert den LDAP-Filter, bei dessen Übereinstimmung das Modul verwendet wird. Es ist zu beachten, dass dieser Filter - im Gegensatz zu <tt>ldapsearch</tt> - case-sensitive ist und in Klammern gesetzt werden muss.<br>'''Achtung:''' Der Name dieser Variable überdeckt unglücklicherweise die eingebaute Python-Funktion <tt>filter()</tt>.
 
|-
 
|-
 
| filters
 
| filters
| '''undocumented'''
+
| Eine Liste von 3-Tupeln <tt>(Basis-DN, LDAP-Scope, LDAP-Filter)</tt>, über die weitere LDAP-Objekte selektiert werden können.<br>'''Achtung:''' Diese Erweiterung funktioniert in UCS-3.x noch nicht zuverlässig und sollte nicht verwendet werden!
 
|-
 
|-
 
| attributes  
 
| attributes  
Line 49: Line 49:
 
|-
 
|-
 
| modrdn
 
| modrdn
| '''undocumented''' {{Bug|23324}}
+
| Wird diese globale Variable mit der Zeichenkette <tt>'1'</tt> initialisiert, ändert sich die Signatur der <tt>handler()</tt>-Funktion: Als 4. Argument wird dann zusätzlich eine Zeichenkette bestehend aus einem Buchstaben übergeben, die angibt, ob das Objekt erzeugt (<tt>a</tt>), geändert (<tt>m</tt>), gelöscht (<tt>d</tt>) oder umbenannt (<tt>r</tt>) wurde. Im letztgenannten Fall wird <tt>handler()</tt> zweimalig aufgerufen: Für den alten ''dn'' mit <tt>handler(dn='old_dn', new=None, old=old_data, command='r')</tt> und direkt danach mit <tt>handler(dn='new_dn', new=new_data, old=None, command='a')</tt>.
 
|}
 
|}
  

Revision as of 15:25, 18 July 2012

Einführung

Der Univention Directory Listener ist Bestandteil der UCS-Domänenreplikation. Er erhält von den Notifier-Systemen der Domäne Informationen über geänderte LDAP-Objekte und kann anschließend die Objekte im LDAP des jeweiligen Notifier-Systems abrufen. Durch eine interne Datenbank kann zusätzlich auf vorherige Werte des jeweiligen Objektes zugegriffen werden.

Der Univention Directory Listener kann über eine Plugin-Architektur erweitert werden, so dass bei Änderungen im LDAP-Verzeichnisdienst lokale Aktionen ausgeführt werden können, zum Beispiel das Neuschreiben von Konfigurationsdateien, Zwischenspeicherung globaler LDAP-Einstellungen in lokalen Variablen oder Neustarts von Diensten.

Module für die Plugin-Architektur werden in Python implementiert.

Jedes Listener-Modul kann durch einen LDAP-Filter festlegen auf welche LDAP-Änderungen es angewendet werden soll. Es können auch mehrere Module auf eine Änderung angewendet werden.

Die Module werden unterhalb des Verzeichnisses /usr/lib/univention-directory-listener/system/ gespeichert. Beim Starten lädt der Listener-Dienst alle Module aus diesem Verzeichnis.

Alle Module legen nach ihrer Initialisierung so genannte Handler-Dateien im Verzeichnis /var/lib/univention-directory-listener/handlers/ ab. Diese geben den Status ihres Moduls in einem Zahlenwert an, eine 3 bedeutet beispielsweise, dass das Modul erfolgreich initialisiert wurde. Ist die Handler-Datei beim Start des Listeners nicht vorhanden, wird das Modul neu initialisiert. Bei der Initialisierung des Moduls wird das LDAP des Notifier-Systems komplett mit dem definierten Filter des Moduls durchsucht und für jedes gefundene LDAP-Objekt das Listener-Modul aufgerufen.

Aufbau eines Listener-Moduls

Header-Informationen

Die folgenden Attribute müssen für jedes Listener-Modul gesetzt werden:

Variable Erklärung
name Der eindeutige Name des Moduls. Er sollte am besten mit dem Namen der Listener-Moduldatei (ohne Erweiterung .py) übereinstimmen. Anhand dieses Bezeichners wird das Modul eindeutig identifiziert und er wird u.a. für die Zustandsdatei unter /var/lib/univention-directory-listener/handlers/ verwendet.
description Eine kurze Beschreibung des Moduls, die bisher nur intern bei der Fehlersuche verwendet wird.
filter Definiert den LDAP-Filter, bei dessen Übereinstimmung das Modul verwendet wird. Es ist zu beachten, dass dieser Filter - im Gegensatz zu ldapsearch - case-sensitive ist und in Klammern gesetzt werden muss.
Achtung: Der Name dieser Variable überdeckt unglücklicherweise die eingebaute Python-Funktion filter().
filters Eine Liste von 3-Tupeln (Basis-DN, LDAP-Scope, LDAP-Filter), über die weitere LDAP-Objekte selektiert werden können.
Achtung: Diese Erweiterung funktioniert in UCS-3.x noch nicht zuverlässig und sollte nicht verwendet werden!
attributes Eine Liste von LDAP-Attributnamen bei deren Änderung das Modul aufgerufen wird. Ist die Liste leer, wir das Modul bei Änderungen an jedem Attribut gestartet.
modrdn Wird diese globale Variable mit der Zeichenkette '1' initialisiert, ändert sich die Signatur der handler()-Funktion: Als 4. Argument wird dann zusätzlich eine Zeichenkette bestehend aus einem Buchstaben übergeben, die angibt, ob das Objekt erzeugt (a), geändert (m), gelöscht (d) oder umbenannt (r) wurde. Im letztgenannten Fall wird handler() zweimalig aufgerufen: Für den alten dn mit handler(dn='old_dn', new=None, old=old_data, command='r') und direkt danach mit handler(dn='new_dn', new=new_data, old=None, command='a').

Beispiel:

name = 'modul_name'
description = 'Ihre Beschreibung'
filter = '(objectClass=posixAccount)'
attributes = ['uid', 'uidNumber', 'cn']

Funktionen der Listener-Klasse

Mit

import listener

wird eine Python-Klasse des Univention Directory Listener eingebunden, die den Zugriff auf interne Listener-Informationen und die Univention Configuration Registry ermöglicht. Über das Python-Dictionary listener.baseConfig['<Variable>'] kann lesend auf alle UCR-Variablen zugegriffen werden.

Listener-Module arbeiten standardmässig mit den Rechten des Systembenutzers "listener". Für einige Operationen - etwa Dateioperationen in Systemverzeichnissen oder Neustarts von Diensten - kann es nötig sein das Modul kurzzeitig mit root-Rechten zu betreiben.

Wenn die listener-Klasse importiert wurde, kann dies durch listener.setuid(0) bzw. listener.unsetuid() durchgeführt werden:

  • listener.setuid(<uid>): Mit dieser Funktion kann das Modul mit einer abweichenden UserID betrieben werden, z.B. 0 für den Nutzer root.
  • listener.unsetuid(): Setzt die UserID wieder zurück

Mit der run-Funktion können Dienste oder Programme aus dem Modul heraus gestartet werden:

listener.run(<Programm>, <Argumente>, <uid>, <Warten>)
  • Programm: Gibt den vollständigen Pfad zum zu startende Programm oder Init-Skript an.
  • Argumente: Eine Liste von Argumenten, die dem zu startenden Programm übergeben werden. Da diese Liste intern an execv() übergeben wird, muß das erste Argument der Liste der Programmname sein.
  • uid: Die UserID unter der das Programm gestartet werden soll. Wird es nicht übergeben, wird das Programm mit der UID von root gestartet.
  • Warten: Gibt als Booleschen Wert (True, False) an, ob das Modul warten soll bis das auszuführende Programm terminiert ist oder der Ablauf sofort fortgesetzt wird. Standard ist Warten.


Funktionen der Debug-Klasse

Es besteht die Möglichkeit über die Klasse univention.debug zusätzliche Debug-Ausgaben zu erzeugen, die ebenfalls in der listener.log erscheinen:

univention.debug.debug(<id>, <level>, <string>)
  • id: Beschreibt den Dienst in dessen Log-Datei die Ausgaben erscheinen sollen, durch univention.debug.LISTENER in /var/log/univention/listener.log
  • level: Gibt den zu verwendenden Debug-Level an, zur Auswahl stehen:
    • ALL: wird erst ab Log-Level 4 ausgegeben.
    • INFO: Für grundsätzliche Ausgaben, wird erst ab Log-Level 3 ausgegeben.
    • PROCESS: wird erst ab Log-Level 2 ausgegeben.
    • WARN: Warnmeldungen, werden ab Log-Level 1 ausgegeben.
    • ERROR: Fehlermeldungen, werden immer in die Log-Datei und zusätzlich per syslog geschrieben.
  • string: Der auszugebende Text als Zeichenkette.

Funktionen des Moduls

Die folgenden Funktionen können für ein Listener-Modul definiert werden:

 def handler(dn, new, old)
 def handler(dn, new, old, command) # nur bei modrdn=True

Diese Funktion wird aufgerufen wenn der Filter zutrifft. Wird nur new übergeben, wurde das Objekt neu angelegt. Wird nur old übergeben, wird es gelöscht. Werden new und old übergeben, wird das zu behandelnde Objekt verändert.

dn
Der Distinguished Name des LDAP-Objekts als Zeichenkette, das verändert wurde.
old
None oder ein dict, das den gecachten Zustand des Objekts vor der Änderung entspricht.
new
None oder ein dict, das dem Zustand des Objekts nach der Änderung entspricht.
command
undocumented Bug #23324

old und new bilden jeweils die LDAP-Attributnamen auf ein Array von Zeichenketten ab.

def postrun()

Diese Funktion wird fünfzehn Sekunden nach der letzten Änderung aufgerufen, um z.B. Dienste neu zu starten. (Beachte, daß ein stetiger Strom von Änderungen ggf. dazu führt, daß diese Funktion erst sehr viel später ausgeführt wird!)

def prerun()

Diese Funktion wird einmalig vor mehreren Aufrufen von handler() ausgeführt. Der nächste Aufruf erfolgt erst wieder, nachdem postrun() ausgeführt wurde.

def initialize()

Diese Funktion wird einmalig bei der Initialisierung eines Moduls aufgerufen bzw. nach einem Resync. (Der Aufruf wird in der Datei /var/lib/univention-directory-listener/handlers/$MODULNAME protokolliert)

def clean()

Diese Funktion wird bei einem Resync des Modules vor dem nachfolgenden Aufruf von initialize() aufgerufen.

def setdata(key, value)

undocumented Bug #23324

basedn
binddn
bindpw
ldapserver

Neu-Initialisierung eines Listener-Moduls

Listener-Module werden beim Start des Univention Directory Listeners nicht neu initialisiert. Mit dem folgenden Befehl kann ein Modul neu initialisiert werden:

univention-directory-listener-ctrl resync <Modulname>

Der Listener wird dabei angehalten (dies kann ein paar Sekunden dauern), die Handlers-Datei des Moduls wird gelöscht und anschließend das Modul neu geladen.

Wenn alle Listener-Module neu initialisiert werden sollen, kann dies durch Stoppen des Listener, Löschen der bestehenden Handler-Dateien unterhalb von /var/lib/univention-directory-listener/handlers/ und anschließendem Start des Univention Directory Listeners geschehen.

Log-Dateien des Univention Directory Listener

In /var/log/univention/listener.log sind Debug-Ausgaben des Moduls zu finden. Detailliertere Ausgaben können durch Erhöhen des Loglevels in Univention Configuration Registry erzielt werden:

univention-config-registry set listener/debug/level=4

Der Log-Level sollte nach erfolgter Analyse zeitnah zurückgesetzt werden, da sonst die Log-Datei sehr schnell anwachsen kann.

Beispiel-Modul: PrintUsers

Das folgende Beispiel-Modul reagiert auf Änderungen an Nutzer-Objekten und speichert diese in der Datei /tmp/UserList.txt:

"""Beispiel fuer ein Listener Modul, daß Aenderungen an Benutzern loggt."""
__package__ = ''  # workaround for PEP 366
import listener
import os
import univention.debug as ud

name = 'printusers'
description = 'print all names/users/uidNumbers into a file'
filter = '''\
(|(&(objectClass=posixAccount)
    (objectClass=shadowAccount))
  (objectClass=univentionMail)
  (objectClass=sambaSamAccount)
  (&(objectClass=person)
    (objectClass=organizationalPerson)
    (objectClass=inetOrgPerson)))'''
attributes = ['uid', 'uidNumber', 'cn']

user_list = '/tmp/UserList.txt'


def _writeit(cn, uid, uidnr, com):
  """ Haengt CommonName, UserIDentifier, UID-Nummer und
  Kommentar an Datei an. """
  if com is None:
    indent = '\t'
  else:
    indent = ''
  try:
    out = open(user_list, 'a')
    try:
      print >> out, '%sName: "%s"' % (indent, cn.decode('utf-8'))
      print >> out, '%sUser: "%s"' % (indent, uid)
      print >> out, '%sUID: "%s"' % (indent, uidnr)
      if com:
        print >> out, '\t%s' % (com,)
    finally:
      out.close()
  except IOError, ex:
    ud.debug(ud.LISTENER, ud.ERROR,
      'Failed to write "%s": %s' % (user_list, ex))


def _handle_change(dn, new, old):
  """Wenn ein Objekt veraendert wird:."""
  o_uid, o_num, o_name = old['uid'][0], old['uidNumber'][0], old['cn'][0]
  n_uid, n_num, n_name = new['uid'][0], new['uidNumber'][0], new['cn'][0]
  # Schliesst Computerobjekte aus:
  if n_uid.endswith('$') or o_uid.endswith('$'):
    pass
  else:
    ud.debug(ud.LISTENER, ud.INFO, 'Edited User "%s"' % (o_uid,))
    if o_uid in ('root', 'spam'):
      _writeit(o_name, o_uid, '****', 'edited. Is now:')
      _writeit(n_name, n_uid, '****', None)
    else:
      _writeit(o_name, o_uid, o_num, 'edited. Is now:')
      _writeit(n_name, n_uid, n_num, None)


def _handle_add(dn, new):
  """Wenn ein neues Objekt erzeugt wird:"""
  #     (Nach einer Initialisierung sind alle Benutzer "neu"!)
  n_uid, n_num, n_name = new['uid'][0], new['uidNumber'][0], new['cn'][0]
  if n_uid.endswith('$'):
    pass
  else:
    ud.debug(ud.LISTENER, ud.INFO, 'Added User "%s"' % (n_uid,))
    if n_uid in ('root', 'spam'):
      _writeit(n_name, n_uid, '****', 'added.')
    else:
      _writeit(n_name, n_uid, n_num, 'added')


def _handle_remove(dn, old):
  """Wenn ein Objekt geloescht wird:"""
  o_uid, o_num, o_name = old['uid'][0], old['uidNumber'][0], old['cn'][0]
  if o_uid.endswith('$'):
    pass
  else:
    ud.debug(ud.LISTENER, ud.INFO, 'Removed User "%s"' % (o_uid,))
    if o_uid in ('root', 'spam'):
      _writeit(o_name, o_uid, '****', 'removed')
    else:
      _writeit(o_name, o_uid, o_num, 'removed')


def handler(dn, new, old):
  """ Funktion um in eine Textdatei zu schreiben, mit setuid(0)
  da der Handler als root ausgefuehrt wird: """
  listener.setuid(0)
  try:
    if new and old:
      _handle_change(dn, new, old)
    elif new and not old:
      _handle_add(dn, new)
    if old and not new:
      _handle_remove(dn, old)
  finally:
    listener.unsetuid()


def initialize():
  """ Initialisierungsfunktion, die Output-Datei wird
  geloescht, falls vorhanden. """
  if os.path.exists(user_list):
    try:
      listener.setuid(0)
      try:
        os.remove(user_list)
      finally:
        listener.unsetuid()
      ud.debug(ud.LISTENER, ud.INFO,
        'Successfully deleted "%s"' % (user_list,))
    except OSError, ex:
      ud.debug(ud.LISTENER, ud.WARN,
        'Failed to delete file "%s": %s' % (user_list, ex))
  else:
    ud.debug(ud.LISTENER, ud.INFO,
      'File "%s" does not exist, going to create it!' % (user_list,))
Personal tools