|
|
|
1 |
#!/usr/bin/python2.4 |
2 |
# -*- coding: utf-8 -*- |
3 |
# |
4 |
# Univention AD Connector Password Sync |
5 |
# |
6 |
# Copyright 2004-2011 Univention GmbH |
7 |
# |
8 |
# http://www.univention.de/ |
9 |
# |
10 |
# All rights reserved. |
11 |
# |
12 |
# The source code of this program is made available |
13 |
# under the terms of the GNU Affero General Public License version 3 |
14 |
# (GNU AGPL V3) as published by the Free Software Foundation. |
15 |
# |
16 |
# Binary versions of this program provided by Univention to you as |
17 |
# well as other copyrighted, protected or trademarked materials like |
18 |
# Logos, graphics, fonts, specific documentations and configurations, |
19 |
# cryptographic keys etc. are subject to a license agreement between |
20 |
# you and Univention and not subject to the GNU AGPL V3. |
21 |
# |
22 |
# In the case you use this program under the terms of the GNU AGPL V3, |
23 |
# the program is provided in the hope that it will be useful, |
24 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
26 |
# GNU Affero General Public License for more details. |
27 |
# |
28 |
# You should have received a copy of the GNU Affero General Public |
29 |
# License with the Debian GNU/Linux or Univention distribution in file |
30 |
# /usr/share/common-licenses/AGPL-3; if not, see |
31 |
# <http://www.gnu.org/licenses/>. |
32 |
|
33 |
import sys, string, os, time, signal, shutil |
34 |
import base64, pdb, copy, types |
35 |
from optparse import OptionParser |
36 |
|
37 |
import ldap, traceback |
38 |
import univention |
39 |
import univention.connector |
40 |
import univention.connector.ad |
41 |
import univention.uldap |
42 |
import univention.admin.uldap |
43 |
import univention.admin.modules |
44 |
import univention.admin.objects |
45 |
import univention.debug2 as ud |
46 |
import univention.admin.uexceptions |
47 |
|
48 |
from univention.connector.ad import ad |
49 |
|
50 |
import univention_baseconfig |
51 |
|
52 |
from ldap.controls import LDAPControl |
53 |
|
54 |
# parse commandline options |
55 |
|
56 |
parser = OptionParser() |
57 |
parser.add_option("--configbasename", dest="configbasename", |
58 |
help="", metavar="CONFIGBASENAME", default="connector") |
59 |
(options, args) = parser.parse_args() |
60 |
|
61 |
CONFIGBASENAME = "connector" |
62 |
if options.configbasename: |
63 |
CONFIGBASENAME = options.configbasename |
64 |
STATUSLOGFILE = "/var/log/univention/%s-status.log" % CONFIGBASENAME |
65 |
|
66 |
sys.path=['/etc/univention/%s/ad/' % CONFIGBASENAME]+sys.path |
67 |
|
68 |
import mapping |
69 |
|
70 |
def daemon(): |
71 |
try: |
72 |
pid = os.fork() |
73 |
except OSError, e: |
74 |
print 'Daemon Mode Error: %s' % e.strerror |
75 |
|
76 |
if (pid == 0): |
77 |
os.setsid() |
78 |
signal.signal(signal.SIGHUP, signal.SIG_IGN) |
79 |
try: |
80 |
pid = os.fork() |
81 |
except OSError, e: |
82 |
print 'Daemon Mode Error: %s' % e.strerror |
83 |
if (pid == 0): |
84 |
os.chdir("/") |
85 |
os.umask(0) |
86 |
else: |
87 |
pf=open('/var/run/univention-ad-%s' % CONFIGBASENAME, 'w+') |
88 |
pf.write(str(pid)) |
89 |
pf.close() |
90 |
os._exit(0) |
91 |
else: |
92 |
os._exit(0) |
93 |
|
94 |
try: |
95 |
maxfd = os.sysconf("SC_OPEN_MAX") |
96 |
except (AttributeError, ValueError): |
97 |
maxfd = 256 # default maximum |
98 |
|
99 |
for fd in range(0, maxfd): |
100 |
try: |
101 |
os.close(fd) |
102 |
except OSError: # ERROR (ignore) |
103 |
pass |
104 |
|
105 |
os.open("/dev/null", os.O_RDONLY) |
106 |
os.open("/dev/null", os.O_RDWR) |
107 |
os.open("/dev/null", os.O_RDWR) |
108 |
|
109 |
|
110 |
def connect(): |
111 |
|
112 |
daemon() |
113 |
|
114 |
f=open(STATUSLOGFILE, 'w+') |
115 |
sys.stdout=f |
116 |
print time.ctime() |
117 |
|
118 |
baseConfig=univention_baseconfig.baseConfig() |
119 |
baseConfig.load() |
120 |
|
121 |
if not baseConfig.has_key('%s/ad/ldap/host' % CONFIGBASENAME): |
122 |
print '%s/ad/ldap/host not set' % CONFIGBASENAME |
123 |
f.close() |
124 |
sys.exit(1) |
125 |
if not baseConfig.has_key('%s/ad/ldap/port' % CONFIGBASENAME): |
126 |
print '%s/ad/ldap/port not set' % CONFIGBASENAME |
127 |
f.close() |
128 |
sys.exit(1) |
129 |
if not baseConfig.has_key('%s/ad/ldap/base' % CONFIGBASENAME): |
130 |
print '%s/ad/ldap/base not set' % CONFIGBASENAME |
131 |
f.close() |
132 |
sys.exit(1) |
133 |
if not baseConfig.has_key('%s/ad/ldap/binddn' % CONFIGBASENAME): |
134 |
print '%s/ad/ldap/binddn not set' % CONFIGBASENAME |
135 |
f.close() |
136 |
sys.exit(1) |
137 |
if not baseConfig.has_key('%s/ad/ldap/bindpw' % CONFIGBASENAME): |
138 |
print '%s/ad/ldap/bindpw not set' % CONFIGBASENAME |
139 |
f.close() |
140 |
sys.exit(1) |
141 |
|
142 |
if not baseConfig.has_key('%s/ad/ldap/certificate' % CONFIGBASENAME) and not (baseConfig.has_key('%s/ad/ldap/ssl' % CONFIGBASENAME) and baseConfig['%s/ad/ldap/ssl' % CONFIGBASENAME] == 'no') : |
143 |
print '%s/ad/ldap/certificate not set' % CONFIGBASENAME |
144 |
f.close() |
145 |
sys.exit(1) |
146 |
|
147 |
if baseConfig.is_true('%s/ad/ldap/ssl' % CONFIGBASENAME, True) or baseConfig.is_true('%s/ad/ldap/ldaps' % CONFIGBASENAME, False): |
148 |
# create a new CAcert file, which contains the UCS CA and the AD CA, |
149 |
# see Bug #17768 for details |
150 |
# https://forge.univention.org/bugzilla/show_bug.cgi?id=17768 |
151 |
new_ca_filename = '/var/cache/univention-ad-connector/CAcert-%s.pem' % CONFIGBASENAME |
152 |
new_ca = open(new_ca_filename, 'w') |
153 |
|
154 |
ca = open('/etc/univention/ssl/ucsCA/CAcert.pem', 'r') |
155 |
new_ca.write(string.join(ca.readlines(),'')) |
156 |
ca.close() |
157 |
|
158 |
ca = open(baseConfig['%s/ad/ldap/certificate' % CONFIGBASENAME]) |
159 |
new_ca.write(string.join(ca.readlines(),'')) |
160 |
ca.close() |
161 |
|
162 |
new_ca.close() |
163 |
|
164 |
ldap.set_option( ldap.OPT_X_TLS_CACERTFILE, new_ca_filename ) |
165 |
|
166 |
|
167 |
if not baseConfig.has_key('%s/ad/listener/dir' % CONFIGBASENAME): |
168 |
print '%s/ad/listener/dir not set' % CONFIGBASENAME |
169 |
f.close() |
170 |
sys.exit(1) |
171 |
|
172 |
if not baseConfig.has_key('%s/ad/retryrejected' % CONFIGBASENAME): |
173 |
baseconfig_retry_rejected=10 |
174 |
else: |
175 |
baseconfig_retry_rejected=baseConfig['%s/ad/retryrejected' % CONFIGBASENAME] |
176 |
|
177 |
ad_ldap_bindpw=open(baseConfig['%s/ad/ldap/bindpw' % CONFIGBASENAME]).read() |
178 |
if ad_ldap_bindpw[-1] == '\n': |
179 |
ad_ldap_bindpw=ad_ldap_bindpw[0:-1] |
180 |
|
181 |
poll_sleep=int(baseConfig['%s/ad/poll/sleep' % CONFIGBASENAME]) |
182 |
ad_init=None |
183 |
while not ad_init: |
184 |
try: |
185 |
ad_pwd=adpwd( CONFIGBASENAME, |
186 |
mapping.ad_mapping, |
187 |
baseConfig, |
188 |
baseConfig['%s/ad/ldap/host' % CONFIGBASENAME], |
189 |
baseConfig['%s/ad/ldap/port' % CONFIGBASENAME], |
190 |
baseConfig['%s/ad/ldap/base' % CONFIGBASENAME], |
191 |
baseConfig['%s/ad/ldap/binddn' % CONFIGBASENAME], |
192 |
ad_ldap_bindpw, |
193 |
baseConfig['%s/ad/ldap/certificate' % CONFIGBASENAME], |
194 |
baseConfig['%s/ad/listener/dir' % CONFIGBASENAME]) |
195 |
ad_init=True |
196 |
except ldap.SERVER_DOWN: |
197 |
print "Warning: Can't initialize LDAP-Connections, wait..." |
198 |
sys.stdout.flush() |
199 |
time.sleep(poll_sleep) |
200 |
pass |
201 |
|
202 |
|
203 |
ad_init=None |
204 |
|
205 |
while not ad_init: |
206 |
try: |
207 |
ad_pwd.initialize() |
208 |
ad_init=True |
209 |
except ldap.SERVER_DOWN: |
210 |
time.sleep(poll_sleep) |
211 |
pass |
212 |
|
213 |
f.close() |
214 |
retry_rejected=0 |
215 |
connected = True |
216 |
while connected: |
217 |
f=open(STATUSLOGFILE, 'w+') |
218 |
sys.stdout=f |
219 |
print time.ctime() |
220 |
# Poll for changes |
221 |
change_counter=1 |
222 |
while change_counter != 0: |
223 |
sys.stdout.flush() |
224 |
try: |
225 |
change_counter=ad_pwd.poll() |
226 |
except ldap.SERVER_DOWN: |
227 |
print "Can't contact LDAP server during ad-poll, sync not possible." |
228 |
connected = False |
229 |
sys.stdout.flush() |
230 |
time.sleep(poll_sleep) |
231 |
|
232 |
if change_counter > 0: |
233 |
retry_rejected=0 |
234 |
|
235 |
if str(retry_rejected) == baseconfig_retry_rejected: |
236 |
ad_pwd.ad.resync_rejected() |
237 |
retry_rejected=0 |
238 |
else: |
239 |
retry_rejected+=1 |
240 |
|
241 |
print '- sleep %s seconds (%s/%s until resync) -'%(poll_sleep, retry_rejected, baseconfig_retry_rejected) |
242 |
sys.stdout.flush() |
243 |
time.sleep(poll_sleep) |
244 |
f.close() |
245 |
ad_pwd.ad.close_debug() |
246 |
|
247 |
class adpwd: |
248 |
def __init__(self, CONFIGBASENAME, property, baseConfig, ad_ldap_host, ad_ldap_port, ad_ldap_base, ad_ldap_binddn, ad_ldap_bindpw, ad_ldap_certificate, listener_dir): |
249 |
self.ad = ad(CONFIGBASENAME, property, baseConfig, ad_ldap_host, ad_ldap_port, ad_ldap_base, ad_ldap_binddn, ad_ldap_bindpw, ad_ldap_certificate, listener_dir) |
250 |
|
251 |
bindpw=open('/etc/ldap.secret').read() |
252 |
if bindpw[-1] == '\n': |
253 |
bindpw=bindpw[0:-1] |
254 |
|
255 |
self.ad.lo=univention.admin.uldap.access(host=baseConfig['ldap/master'], base=baseConfig['ldap/base'], binddn='cn=admin,'+baseConfig['ldap/base'], bindpw=bindpw, start_tls=2) |
256 |
|
257 |
# load UCS Modules |
258 |
self.ad.modules={} |
259 |
for key in self.ad.property.keys(): |
260 |
if self.ad.property[key].ucs_module: |
261 |
self.ad.modules[key]=univention.admin.modules.get(self.ad.property[key].ucs_module) |
262 |
else: |
263 |
self.ad.modules[key]=None |
264 |
|
265 |
def initialize(self): |
266 |
_d=ud.function('ldap.initialize') |
267 |
|
268 |
|
269 |
print "--------------------------------------" |
270 |
print "Initialize sync from AD" |
271 |
self.ad.resync_rejected() |
272 |
if self.ad._get_lastUSN() == 0: # we startup new |
273 |
ud.debug(ud.LDAP, ud.INFO, "initialize AD: last USN is 0, sync all") |
274 |
# query highest USN in LDAP |
275 |
highestCommittedUSN = self.ad._ad__get_highestCommittedUSN() |
276 |
|
277 |
# poll for all objects without deleted objects |
278 |
polled=self.poll(show_deleted=False) |
279 |
|
280 |
# compare highest USN from poll with highest before poll, if the last changes deletes |
281 |
# the highest USN from poll is to low |
282 |
self.ad._set_lastUSN(max(highestCommittedUSN,self._get_lastUSN())) |
283 |
ud.debug(ud.LDAP, ud.INFO, "initialize AD: sync of all objects finished, lastUSN is %d", self.ad._ad__get_highestCommittedUSN()) |
284 |
else: |
285 |
polled=self.poll() |
286 |
print "--------------------------------------" |
287 |
|
288 |
def poll(self, show_deleted=True): |
289 |
''' |
290 |
poll for changes in AD |
291 |
''' |
292 |
_d=ud.function('ldap.poll') |
293 |
# search from last_usn for changes |
294 |
change_count = 0 |
295 |
changes = [] |
296 |
try: |
297 |
# call private methode |
298 |
changes = self.ad._ad__search_ad_changes(show_deleted=show_deleted) |
299 |
except (ldap.SERVER_DOWN, SystemExit): |
300 |
raise |
301 |
except: # FIXME: which exception is to be caught? |
302 |
self._debug_traceback(ud.WARN,"Exception during search_ad_changes") |
303 |
|
304 |
print "--------------------------------------" |
305 |
print "try to sync %s changes from AD" % len(changes) |
306 |
print "done:", |
307 |
sys.stdout.flush() |
308 |
done_counter = 0 |
309 |
object = None |
310 |
|
311 |
for element in changes: |
312 |
try: |
313 |
if element[0] == 'None': # referrals |
314 |
continue |
315 |
old_element = copy.deepcopy(element) |
316 |
object = self.ad._ad__object_from_element(element) |
317 |
except: # FIXME: which exception is to be caught? |
318 |
#ud.debug(ud.LDAP, ud.ERROR, "Exception during poll/object-mapping, tried to map element: %s" % old_element[0]) |
319 |
#ud.debug(ud.LDAP, ud.ERROR, "This object will not be synced again!") |
320 |
# debug-trace may lead to a segfault here :( |
321 |
self._debug_traceback(ud.ERROR,"Exception during poll/object-mapping, object will not be synced again!") |
322 |
|
323 |
if object: |
324 |
property_key = self.ad._ad__identify(object) |
325 |
if property_key: |
326 |
|
327 |
if self.ad._ignore_object(property_key,object): |
328 |
# call private methode |
329 |
self.ad._ad__update_lastUSN(object) |
330 |
done_counter += 1 |
331 |
print "%s"%done_counter, |
332 |
continue |
333 |
|
334 |
sync_successfull = False |
335 |
try: |
336 |
ud.debug(ud.LDAP, ud.INFO, "Sync object (%s) property_key: %s" % (object['dn'], property_key)) |
337 |
if property_key == 'user' and object['modtype'] == 'modify': |
338 |
if not self.ad._ignore_object(property_key,object): |
339 |
property_ucs = self.ad._ad__identify(object) |
340 |
object_ucs = self.ad._object_mapping(property_key,object) |
341 |
|
342 |
try: |
343 |
univention.connector.ad.password.password_sync(self.ad, property_key, object_ucs) |
344 |
except univention.admin.uexceptions.noObject: |
345 |
# This is possible if the user does not exist in UCS (the main AD connector is in write mode) |
346 |
ud.debug(ud.LDAP, ud.INFO, "Object (%s) was not found in UCS (ignore)" % (object['dn'])) |
347 |
pass |
348 |
sync_successfull = True |
349 |
else: |
350 |
sync_successfull = True |
351 |
else: |
352 |
sync_successfull = True |
353 |
except (ldap.SERVER_DOWN, SystemExit): |
354 |
raise |
355 |
except univention.admin.uexceptions.ldapError, msg: |
356 |
ud.debug(ud.LDAP, ud.INFO, "Exception during poll with message (1) %s"%msg) |
357 |
if msg == "Can't contact LDAP server": |
358 |
raise ldap.SERVER_DOWN |
359 |
else: |
360 |
self._debug_traceback(ud.WARN,"Exception during poll/sync_to_ucs") |
361 |
except univention.admin.uexceptions.ldapError, msg: |
362 |
ud.debug(ud.LDAP, ud.INFO, "Exception during poll with message (2) %s"%msg) |
363 |
if msg == "Can't contact LDAP server": |
364 |
raise ldap.SERVER_DOWN |
365 |
else: |
366 |
self._debug_traceback(ud.WARN,"Exception during poll") |
367 |
except: # FIXME: which exception is to be caught? |
368 |
self.ad._debug_traceback(ud.WARN, "Exception during poll/sync_to_ucs") |
369 |
|
370 |
|
371 |
|
372 |
if not sync_successfull: |
373 |
ud.debug(ud.LDAP, ud.WARN, |
374 |
"sync to ucs was not successfull, save rejected") |
375 |
ud.debug(ud.LDAP, ud.WARN, |
376 |
"object was: %s"%object['dn']) |
377 |
|
378 |
if sync_successfull: |
379 |
change_count+=1 |
380 |
# call private methode |
381 |
self.ad._ad__update_lastUSN(object) |
382 |
try: |
383 |
GUID = old_element[1]['objectGUID'][0] |
384 |
self.ad._set_DN_for_GUID(GUID,old_element[0]) |
385 |
except (ldap.SERVER_DOWN, SystemExit): |
386 |
raise |
387 |
except: # FIXME: which exception is to be caught? |
388 |
self._debug_traceback(ud.WARN, |
389 |
"Exception during set_DN_for_GUID") |
390 |
|
391 |
else: |
392 |
self.ad.save_rejected(object) |
393 |
else: |
394 |
# call private methode |
395 |
self.ad._ad__update_lastUSN(object) |
396 |
|
397 |
done_counter += 1 |
398 |
print "%s"%done_counter, |
399 |
else: |
400 |
done_counter += 1 |
401 |
print "(%s)"%done_counter, |
402 |
sys.stdout.flush() |
403 |
|
404 |
print "" |
405 |
|
406 |
# return number of synced objects |
407 |
rejected = self.ad._list_rejected() |
408 |
if rejected: |
409 |
print "Changes from AD: %s (%s saved rejected)" % (change_count, len(rejected)) |
410 |
else: |
411 |
print "Changes from AD: %s (%s saved rejected)" % (change_count, '0') |
412 |
print "--------------------------------------" |
413 |
sys.stdout.flush() |
414 |
return change_count |
415 |
|
416 |
|
417 |
def main(): |
418 |
while True: |
419 |
try: |
420 |
connect() |
421 |
except SystemExit: |
422 |
raise |
423 |
except: |
424 |
f=open(STATUSLOGFILE, 'w+') |
425 |
sys.stdout=f |
426 |
print time.ctime() |
427 |
|
428 |
text = '' |
429 |
exc_info = sys.exc_info() |
430 |
lines = apply(traceback.format_exception, exc_info) |
431 |
text = text + '\n' |
432 |
for line in lines: |
433 |
text += line |
434 |
print " --- connect failed, failure was: ---" |
435 |
print text |
436 |
print " --- retry in 30 seconds ---" |
437 |
sys.stdout.flush() |
438 |
time.sleep(30) |
439 |
|
440 |
f.close() |
441 |
|
442 |
|
443 |
if __name__ == "__main__": |
444 |
main() |
445 |
|