1 | import imp |
---|
2 | import ldap |
---|
3 | import Bcfg2.Options |
---|
4 | import Bcfg2.Server.Plugin |
---|
5 | |
---|
6 | SCOPE_MAP = { |
---|
7 | "base" : ldap.SCOPE_BASE, |
---|
8 | "one" : ldap.SCOPE_ONELEVEL, |
---|
9 | "sub" : ldap.SCOPE_SUBTREE, |
---|
10 | } |
---|
11 | |
---|
12 | LDAP_QUERIES = [] |
---|
13 | |
---|
14 | def register_query(query): |
---|
15 | LDAP_QUERIES.append(query) |
---|
16 | |
---|
17 | class ConfigFile(Bcfg2.Server.Plugin.FileBacked): |
---|
18 | """ |
---|
19 | Config file for the Ldap plugin |
---|
20 | |
---|
21 | The config file cannot be 'parsed' in the traditional sense as we would |
---|
22 | need some serious type checking ugliness to just get the LdapQuery |
---|
23 | subclasses. The alternative would be to have the user create a list with |
---|
24 | a predefined name that contains all queries. |
---|
25 | The approach implemented here is having the user call a registering |
---|
26 | decorator that updates a global variable in this module. |
---|
27 | """ |
---|
28 | def __init__(self, filename, fam): |
---|
29 | self.filename = filename |
---|
30 | Bcfg2.Server.Plugin.FileBacked.__init__(self, self.filename) |
---|
31 | fam.AddMonitor(self.filename, self) |
---|
32 | |
---|
33 | def Index(self): |
---|
34 | """ |
---|
35 | Reregisters the queries in the config file |
---|
36 | |
---|
37 | The config will take care of actually registering the queries, |
---|
38 | so we just load it once and don't keep it. |
---|
39 | """ |
---|
40 | global LDAP_QUERIES |
---|
41 | LDAP_QUERIES = [] |
---|
42 | imp.load_source("ldap_cfg", self.filename) |
---|
43 | |
---|
44 | class Ldap(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): |
---|
45 | """ |
---|
46 | The Ldap plugin allows adding data from an LDAP server to your metadata. |
---|
47 | """ |
---|
48 | name = "Ldap" |
---|
49 | version = "$Revision: $" |
---|
50 | experimental = True |
---|
51 | debug_flag = False |
---|
52 | |
---|
53 | def __init__(self, core, datastore): |
---|
54 | Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) |
---|
55 | Bcfg2.Server.Plugin.Connector.__init__(self) |
---|
56 | self.config = ConfigFile(self.data + "/config.py", core.fam) |
---|
57 | |
---|
58 | def get_additional_data(self, metadata): |
---|
59 | try: |
---|
60 | data = {} |
---|
61 | self.debug_log("LdapPlugin debug: found queries " + |
---|
62 | str(LDAP_QUERIES)) |
---|
63 | for QueryClass in LDAP_QUERIES: |
---|
64 | query = QueryClass(metadata) |
---|
65 | if query.is_applicable(metadata): |
---|
66 | self.debug_log("LdapPlugin debug: processing query '" + |
---|
67 | query.name + "'") |
---|
68 | data[query.name] = query.get_result(metadata) |
---|
69 | else: |
---|
70 | self.debug_log("LdapPlugin debug: query '" + query.name + |
---|
71 | "' not applicable to host '" + metadata.hostname + "'") |
---|
72 | return data |
---|
73 | except Exception, error_msg: |
---|
74 | if self.debug_flag: |
---|
75 | raise |
---|
76 | else: |
---|
77 | Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + |
---|
78 | str(error_msg)) |
---|
79 | return {} |
---|
80 | |
---|
81 | class LdapConnection(object): |
---|
82 | """ |
---|
83 | Connection to an LDAP server. |
---|
84 | """ |
---|
85 | def __init__(self, host = "localhost", port = 389, |
---|
86 | binddn = None, bindpw = None): |
---|
87 | self.host = host |
---|
88 | self.port = port |
---|
89 | self.binddn = binddn |
---|
90 | self.bindpw = bindpw |
---|
91 | self.conn = None |
---|
92 | |
---|
93 | def __del__(self): |
---|
94 | if self.conn: |
---|
95 | self.conn.unbind() |
---|
96 | |
---|
97 | def init_conn(self): |
---|
98 | self.conn = ldap.initialize(self.url) |
---|
99 | if self.binddn is not None and self.bindpw is not None: |
---|
100 | self.conn.simple_bind_s(self.binddn, self.bindpw) |
---|
101 | |
---|
102 | def run_query(self, query): |
---|
103 | if not self.conn: |
---|
104 | self.init_conn() |
---|
105 | return self.conn.search_s( |
---|
106 | query.base, |
---|
107 | SCOPE_MAP[query.scope], |
---|
108 | query.filter, |
---|
109 | query.attrs, |
---|
110 | ) |
---|
111 | |
---|
112 | @property |
---|
113 | def url(self): |
---|
114 | return "ldap://" + self.host + ":" + str(self.port) |
---|
115 | |
---|
116 | class LdapQuery(object): |
---|
117 | """ |
---|
118 | Query referencing an LdapConnection and providing several |
---|
119 | methods for query manipulation. |
---|
120 | """ |
---|
121 | |
---|
122 | name = "unknown" |
---|
123 | base = "" |
---|
124 | scope = "sub" |
---|
125 | filter = "(objectClass=*)" |
---|
126 | attrs = None |
---|
127 | connection = None |
---|
128 | result = None |
---|
129 | |
---|
130 | def __init__(self, metadata): |
---|
131 | """ |
---|
132 | Override this to alter the query before execution. |
---|
133 | |
---|
134 | In most cases, you will do something like |
---|
135 | |
---|
136 | self.filter = "(cn=" + metadata.hostname + ")" |
---|
137 | |
---|
138 | here. |
---|
139 | """ |
---|
140 | pass |
---|
141 | |
---|
142 | def __unicode__(self): |
---|
143 | return "LdapQuery:" + self.name |
---|
144 | |
---|
145 | def is_applicable(self, metadata): |
---|
146 | """ |
---|
147 | Overrideable method to determine if the query is to be executed for |
---|
148 | the given metadata object. |
---|
149 | Defaults to true. |
---|
150 | """ |
---|
151 | return True |
---|
152 | |
---|
153 | def prepare_query(self, metadata): |
---|
154 | """ |
---|
155 | Overrideable method to alter the query based on metadata. |
---|
156 | Defaults to doing nothing. |
---|
157 | """ |
---|
158 | pass |
---|
159 | |
---|
160 | def process_result(self, metadata): |
---|
161 | """ |
---|
162 | Overrideable method to post-process the query result. |
---|
163 | Defaults to returning the unaltered result. |
---|
164 | """ |
---|
165 | return self.result |
---|
166 | |
---|
167 | def get_result(self, metadata): |
---|
168 | """ |
---|
169 | Method to handle preparing, executing and processing the query. |
---|
170 | """ |
---|
171 | if isinstance(self.connection, LdapConnection): |
---|
172 | self.prepare_query(metadata) |
---|
173 | self.result = self.connection.run_query(self) |
---|
174 | self.result = self.process_result(metadata) |
---|
175 | return self.result |
---|
176 | else: |
---|
177 | Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + |
---|
178 | "No valid connection defined for query " + str(self)) |
---|
179 | return None |
---|
180 | |
---|
181 | class LdapSubQuery(LdapQuery): |
---|
182 | """ |
---|
183 | SubQueries are meant for internal use only and are not added |
---|
184 | to the metadata object. They are useful for situations where |
---|
185 | you need to run more than one query to obtain some data. |
---|
186 | """ |
---|
187 | def prepare_query(self, metadata, subquery_data = None): |
---|
188 | """ |
---|
189 | Overrideable method to alter the query based on metadata. |
---|
190 | Defaults to doing nothing. |
---|
191 | """ |
---|
192 | pass |
---|
193 | |
---|
194 | def get_result(self, metadata, subquery_data = None): |
---|
195 | """ |
---|
196 | Method to handle preparing, executing and processing the query. |
---|
197 | """ |
---|
198 | if isinstance(self.connection, LdapConnection): |
---|
199 | self.prepare_query(metadata, subquery_data) |
---|
200 | self.result = self.connection.run_query(self) |
---|
201 | self.process_result(metadata, subquery_data) |
---|
202 | return self.result |
---|
203 | else: |
---|
204 | Bcfg2.Server.Plugin.logger.error("LdapPlugin error: " + |
---|
205 | "No valid connection defined for query " + str(self)) |
---|
206 | return None |
---|