summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Ldap.py
diff options
context:
space:
mode:
authorSol Jerome <sol.jerome@gmail.com>2012-03-24 11:20:07 -0500
committerSol Jerome <sol.jerome@gmail.com>2012-03-24 11:20:07 -0500
commitdab1d03d81c538966d03fb9318a4588a9e803b44 (patch)
treef51e27fa55887e9fb961766805fe43f0da56c5b9 /src/lib/Bcfg2/Server/Plugins/Ldap.py
parent5cd6238df496a3cea178e4596ecd87967cce1ce6 (diff)
downloadbcfg2-dab1d03d81c538966d03fb9318a4588a9e803b44.tar.gz
bcfg2-dab1d03d81c538966d03fb9318a4588a9e803b44.tar.bz2
bcfg2-dab1d03d81c538966d03fb9318a4588a9e803b44.zip
Allow to run directly from a git checkout (#1037)
Signed-off-by: Sol Jerome <sol.jerome@gmail.com>
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins/Ldap.py')
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Ldap.py245
1 files changed, 245 insertions, 0 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/Ldap.py b/src/lib/Bcfg2/Server/Plugins/Ldap.py
new file mode 100644
index 000000000..29abf5b13
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Ldap.py
@@ -0,0 +1,245 @@
+import imp
+import logging
+import sys
+import time
+import traceback
+import Bcfg2.Options
+import Bcfg2.Server.Plugin
+
+logger = logging.getLogger('Bcfg2.Plugins.Ldap')
+
+try:
+ import ldap
+except:
+ logger.error("Unable to load ldap module. Is python-ldap installed?")
+ raise ImportError
+
+# time in seconds between retries after failed LDAP connection
+RETRY_DELAY = 5
+# how many times to try reaching the LDAP server if a connection is broken
+# at the very minimum, one retry is needed to handle a restarted LDAP daemon
+RETRY_COUNT = 3
+
+SCOPE_MAP = {
+ "base" : ldap.SCOPE_BASE,
+ "one" : ldap.SCOPE_ONELEVEL,
+ "sub" : ldap.SCOPE_SUBTREE,
+}
+
+LDAP_QUERIES = []
+
+def register_query(query):
+ LDAP_QUERIES.append(query)
+
+class ConfigFile(Bcfg2.Server.Plugin.FileBacked):
+ """
+ Config file for the Ldap plugin
+
+ The config file cannot be 'parsed' in the traditional sense as we would
+ need some serious type checking ugliness to just get the LdapQuery
+ subclasses. The alternative would be to have the user create a list with
+ a predefined name that contains all queries.
+ The approach implemented here is having the user call a registering
+ decorator that updates a global variable in this module.
+ """
+ def __init__(self, filename, fam):
+ self.filename = filename
+ Bcfg2.Server.Plugin.FileBacked.__init__(self, self.filename)
+ fam.AddMonitor(self.filename, self)
+
+ def Index(self):
+ """
+ Reregisters the queries in the config file
+
+ The config will take care of actually registering the queries,
+ so we just load it once and don't keep it.
+ """
+ global LDAP_QUERIES
+ LDAP_QUERIES = []
+ imp.load_source("ldap_cfg", self.filename)
+
+class Ldap(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector):
+ """
+ The Ldap plugin allows adding data from an LDAP server to your metadata.
+ """
+ name = "Ldap"
+ experimental = True
+ debug_flag = False
+
+ def __init__(self, core, datastore):
+ Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
+ Bcfg2.Server.Plugin.Connector.__init__(self)
+ self.config = ConfigFile(self.data + "/config.py", core.fam)
+
+ def debug_log(self, message, flag = None):
+ if (flag is None) and self.debug_flag or flag:
+ self.logger.error(message)
+
+ def get_additional_data(self, metadata):
+ query = None
+ try:
+ data = {}
+ self.debug_log("LdapPlugin debug: found queries " +
+ str(LDAP_QUERIES))
+ for QueryClass in LDAP_QUERIES:
+ query = QueryClass()
+ if query.is_applicable(metadata):
+ self.debug_log("LdapPlugin debug: processing query '" +
+ query.name + "'")
+ data[query.name] = query.get_result(metadata)
+ else:
+ self.debug_log("LdapPlugin debug: query '" + query.name +
+ "' not applicable to host '" + metadata.hostname + "'")
+ return data
+ except Exception:
+ if hasattr(query, "name"):
+ Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " +
+ "Exception during processing of query named '" +
+ str(query.name) +
+ "', query results will be empty" +
+ " and may cause bind failures")
+ for line in traceback.format_exception(sys.exc_info()[0],
+ sys.exc_info()[1],
+ sys.exc_info()[2]):
+ Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " +
+ line.replace("\n", ""))
+ return {}
+
+class LdapConnection(object):
+ """
+ Connection to an LDAP server.
+ """
+ def __init__(self, host = "localhost", port = 389,
+ binddn = None, bindpw = None):
+ self.host = host
+ self.port = port
+ self.binddn = binddn
+ self.bindpw = bindpw
+ self.conn = None
+
+ def __del__(self):
+ if self.conn:
+ self.conn.unbind()
+
+ def init_conn(self):
+ self.conn = ldap.initialize(self.url)
+ if self.binddn is not None and self.bindpw is not None:
+ self.conn.simple_bind_s(self.binddn, self.bindpw)
+
+ def run_query(self, query):
+ result = None
+ for attempt in range(RETRY_COUNT + 1):
+ if attempt >= 1:
+ Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " +
+ "LDAP server down (retry " + str(attempt) + "/" +
+ str(RETRY_COUNT) + ")")
+ try:
+ if not self.conn:
+ self.init_conn()
+ result = self.conn.search_s(
+ query.base,
+ SCOPE_MAP[query.scope],
+ query.filter,
+ query.attrs,
+ )
+ break
+ except ldap.SERVER_DOWN:
+ self.conn = None
+ time.sleep(RETRY_DELAY)
+ return result
+
+ @property
+ def url(self):
+ return "ldap://" + self.host + ":" + str(self.port)
+
+class LdapQuery(object):
+ """
+ Query referencing an LdapConnection and providing several
+ methods for query manipulation.
+ """
+
+ name = "unknown"
+ base = ""
+ scope = "sub"
+ filter = "(objectClass=*)"
+ attrs = None
+ connection = None
+ result = None
+
+ def __unicode__(self):
+ return "LdapQuery:" + self.name
+
+ def is_applicable(self, metadata):
+ """
+ Overrideable method to determine if the query is to be executed for
+ the given metadata object.
+ Defaults to true.
+ """
+ return True
+
+ def prepare_query(self, metadata):
+ """
+ Overrideable method to alter the query based on metadata.
+ Defaults to doing nothing.
+
+ In most cases, you will do something like
+
+ self.filter = "(cn=" + metadata.hostname + ")"
+
+ here.
+ """
+ pass
+
+ def process_result(self, metadata):
+ """
+ Overrideable method to post-process the query result.
+ Defaults to returning the unaltered result.
+ """
+ return self.result
+
+ def get_result(self, metadata):
+ """
+ Method to handle preparing, executing and processing the query.
+ """
+ if isinstance(self.connection, LdapConnection):
+ self.prepare_query(metadata)
+ self.result = self.connection.run_query(self)
+ self.result = self.process_result(metadata)
+ return self.result
+ else:
+ Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " +
+ "No valid connection defined for query " + str(self))
+ return None
+
+class LdapSubQuery(LdapQuery):
+ """
+ SubQueries are meant for internal use only and are not added
+ to the metadata object. They are useful for situations where
+ you need to run more than one query to obtain some data.
+ """
+ def prepare_query(self, metadata, **kwargs):
+ """
+ Overrideable method to alter the query based on metadata.
+ Defaults to doing nothing.
+ """
+ pass
+
+ def process_result(self, metadata, **kwargs):
+ """
+ Overrideable method to post-process the query result.
+ Defaults to returning the unaltered result.
+ """
+ return self.result
+
+ def get_result(self, metadata, **kwargs):
+ """
+ Method to handle preparing, executing and processing the query.
+ """
+ if isinstance(self.connection, LdapConnection):
+ self.prepare_query(metadata, **kwargs)
+ self.result = self.connection.run_query(self)
+ return self.process_result(metadata, **kwargs)
+ else:
+ Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " +
+ "No valid connection defined for query " + str(self))
+ return None