1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import string, types, re
21 import datetime, time
22 import crypt
23
24
25 import pprint
26
27
28 from Crypto.Hash import MD4
29 from Crypto.Cipher import DES
30
31 import random
32 import locale
33 import copy
34
35
36 import ldap, ldap.schema
37
38 from easyLDAP_defaults import *
39 from easyLDAP_defaults import _invalidIA5map
40 from easyLDAP_class_base import easyLDAP
41
42 from easyLDAP_exceptions import *
43
45 """
46 Remove duplicate elements from a Python list.
47
48 @param duplicates_list: list that may contain duplicate items
49 @type duplicates_list: list
50 """
51 if type(duplicates_list) is types.ListType:
52 uniqueList = []
53 for element in duplicates_list:
54 if element not in uniqueList:
55 uniqueList.append(element)
56 return uniqueList
57 return []
58
60 """
61 Checks, whether all alphabetical characters in a given string
62 are upper case characters.
63
64 @param test_str: string that shall be tested for uppercase characters
65 @type test_str: str
66 """
67 return test_str.uppercase() == test_str
68
69
71 """
72 Checks, whether all alphabetical characters in a given string
73 are lower case characters.
74
75 @param test_str: string that shall be tested for lowercase characters
76 @type test_str: str
77 """
78 return test_str.lowercase() == test_str
79
80
82 """
83 Checks, whether lower as well as upper case characters appear
84 in a given string.
85
86 @param test_str: string that shall be tested for mixed lowercase
87 and uppercase characters
88 @type test_str: str
89 """
90 return (test_str.lowercase() != test_str) and (test_str.uppercase() != test_str)
91
92
94 """
95 Tries to map special characters in a human readable string
96 to IA5 string characters.
97
98 @param non_ia5_str: string that shall be mapped to an IA5 string
99 @type non_ia5_str: str
100 """
101 convertedStringAsList = []
102 if type(non_ia5_string) is types.StringType:
103 for char in non_ia5_string:
104
105
106 if char in string.printable:
107 convertedStringAsList.append(char)
108
109
110 elif char in [ key.encode(locale.getdefaultlocale()[1]) for key in _invalidIA5map.keys() ]:
111 convertedStringAsList.append(_invalidIA5map[char.decode(locale.getdefaultlocale()[1])])
112
113
114 else:
115
116 pass
117
118 return string.join(convertedStringAsList,'')
119
120
122 """
123 Calculate number of days since 1970.
124 """
125 return int(time.time() / 24 / 3600)
126 shadowAgeToday = days_since_1970
127
128
130 """
131 Generate a crypt password hash for a given plain text password.
132
133 @param password: plain text password that is to be hashed with the Crypt algorithm
134 @type password: str
135 """
136 salt = random.choice (string.letters+string.digits) + random.choice (string.letters+string.digits)
137 return '{crypt}'+crypt.crypt(password,salt)
138
139
141 """
142 Generate a smbpasswd-style NT hash.
143
144 @param password: plain text password that is to be hashed with the NT hash
145 algorithm (MD4)
146 @type password: str
147 """
148
149 hash = MD4.new()
150 hash.update(password.encode("utf-16-le"))
151 return hash.hexdigest().upper()
152
153
155 """
156 Generate a smbpasswd-style LanManager (Win9x) hash.
157
158 @param password: plain text password that is to be hashed
159 with the LanManager hash algorithm
160 @type password: str
161 """
162 bin = ['0000','0001','0010','0011',
163 '0100','0101','0110','0111',
164 '1000','1001','1010','1011',
165 '1100','1101','1110','1111']
166 hx = '0123456789ABCDEF'
167
168
169 lmPasswordStr = password.upper()
170 lmPasswordLen = len(lmPasswordStr)
171 if lmPasswordLen < 14:
172 for i in range(lmPasswordLen,14):
173 lmPasswordStr = lmPasswordStr + "\x00"
174 lmPasswordStr = lmPasswordStr[:14]
175 lmPasswordBin = ''
176 for s in lmPasswordStr:
177 hexstr = string.zfill(str(hex(ord(s)))[2:],2)
178 for h in hexstr:
179 lmPasswordBin += bin[int(h,base=16)]
180
181
182 j = 0
183 parBit = 0
184 lmPwBinList = list(lmPasswordBin)
185 for i in range(0,len(lmPwBinList)+1):
186 if i % 7 == 0:
187 lmPwBinList.insert (i + j, str(parBit)+'|')
188 j += 1
189 parBit = 0
190 if i+j <= len(lmPasswordBin):
191 parBit = (parBit + int(lmPwBinList[i+j])) % 2
192 del lmPwBinList[0]
193
194
195 lmDESKey1BinStr = string.replace(string.replace(string.replace(string.join(lmPwBinList),'0 ','0'),'1 ','1'),'| ','|')[:71]
196 lmDESKey2BinStr = string.replace(string.replace(string.replace(string.join(lmPwBinList),'0 ','0'),'1 ','1'),'| ','|')[72:]
197
198 lmDESKey1BinList = string.split(string.rstrip(lmDESKey1BinStr,'|'), '|')
199 lmDESKey2BinList = string.split(string.rstrip(lmDESKey2BinStr,'|'), '|')
200
201
202 lmDESKey1 = ''
203 lmDESKey2 = ''
204 for i in range(len(lmDESKey1BinList)):
205 lmDESKey1 += chr(bin.index(lmDESKey1BinList[i][:4])*16 + bin.index(lmDESKey1BinList[i][4:]))
206 for i in range(len(lmDESKey2BinList)):
207 lmDESKey2 += chr(bin.index(lmDESKey2BinList[i][:4])*16 + bin.index(lmDESKey2BinList[i][4:]))
208
209
210 des1 = DES.new(lmDESKey1,DES.MODE_ECB)
211 des2 = DES.new(lmDESKey2,DES.MODE_ECB)
212 lmhash1Str=des1.encrypt('KGS!@#$%')
213 lmhash2Str=des2.encrypt('KGS!@#$%')
214
215
216 lmhash1 = ''
217 lmhash2 = ''
218 for ch in lmhash1Str:
219 lmhash1 += hx[ord(ch)/16]+hx[ord(ch)%16]
220 for ch in lmhash2Str:
221 lmhash2 += hx[ord(ch)/16]+hx[ord(ch)%16]
222
223 return lmhash1+lmhash2
224
225
226
228 return string.split(this_attrdesc,';')[0]
229
230
232 try:
233 return string.split(this_attrdesc,';')[1]
234 except IndexError:
235 return ''
236
237
239 if this_attropt:
240 return '%s;%s' % (this_attrtype, this_attropt)
241 else:
242 return this_attrtype
243
244
246 """
247 This function is used to replace aliases of attribute types in
248 the easyLDAP internal data structure.
249
250 The function expects a dictionary of attribute descriptions and their
251 values (i.e. dictionary keys of the form <attrType>;<attrOption>).
252
253 The function will fail if the attribute descriptions (i.e. the dictionary
254 keys) are not all of the same attribute type. It furtheron expects
255 an alias of the attribute type name that shall replace the commonly
256 used attribute type in the formerly named dictionary.
257
258 The attribute options, however, must be supported by the LDAPv3 protocol.
259 Attribute description values must be given as multi-valued or single-valued
260 lists (depending on the attribute's properties in the server's LDAP schema).
261
262 Example::
263
264 attrdesc_dict = { 'ou': ['network administration team',],
265 'ou;lang-de': ['NETZWERKTEAM',],}
266
267 attrtype_alias = 'organizationalUnit'
268
269 result = { 'organizationalUnit': ['network administration team',],
270 'organizationalUnit;lang-de': ['NETZWERKTEAM',],}
271
272 If the function fails, the value C{None} is returned.
273
274 @param attrdesc_dict: attribute description dictionary (pre-requisites, see above)
275 @type attrdesc_dict: dict
276 @param attrtype_alias: alias attribute type name that will replace attribute type
277 names in the dictionary keys of attrdesc_dict
278 """
279 try:
280
281
282 attrType = attrtype(attrdesc_dict.keys()[0])
283
284
285 for attrDesc in attrdesc_dict.keys():
286 if attrtype(attrDesc) != attrType:
287
288 return None
289
290
291 attrOptions = [ attropt(option) for option in attrdesc_dict.keys() ]
292
293 resultDict = {}
294
295
296 for option in attrOptions:
297 resultDict[_attrdesc(attrtype_alias,option)] = attrdesc_dict[_attrdesc(attrType,option)]
298
299 return resultDict
300
301 except:
302 pass
303
304 return None
305
306
307
309 """
310 Return C{True}, if the given data structure (a single Python
311 dictionary in a Python list) conforms to the Python LDAP data format
312 and only contains data of a single DN.
313
314 @param pyldapobject: the LDAP data structure to be tested for Python LDAP
315 compliancy
316 @type pyldapobject: list
317 @param strict: test if the LDAP object also contains objectClass values
318 @type strict: bool
319 """
320 if type(pyldapobject) != types.ListType:
321 return False
322
323 if len (pyldapobject) != 1:
324 return False
325
326 if (type(pyldapobject[0]) != types.ListType) and (type(pyldapobject[0]) != types.TupleType):
327 return False
328
329 if len(pyldapobject[0]) != 2:
330 return False
331
332 if (type(pyldapobject[0][0]) != types.StringType) or (type(pyldapobject[0][1]) != types.DictType):
333 return False
334
335 if strict:
336 if ('objectclass' not in [ key.lower() for key in pyldapobject[0][1].keys() ]):
337 return False
338
339 return True
340
342 """
343 Return C{True}, if the given data structure (several Python dictionaries
344 in a Python list) conforms to the Python LDAP data format and all the
345 contained LDAP DNs form a coherent LDAP (sub)tree.
346
347 @param pyldaptree: the LDAP data structure to be tested for Python LDAP
348 compliancy
349 @type pyldaptree: list
350 """
351
352
353
354 if len(pyldaptree) > 1:
355 for dn_object in pyldaptree:
356 if not is_pyldapobject([dn_object], strict=False):
357 return False
358 else:
359 return False
360
361 return True
362
363
365 """
366 Convert any object of a standard Pythonian variable type to a list of strings.
367
368 @param values: value(s) to be converted
369 @type values; tuple, list
370 """
371
372 if type(values) not in (types.BooleanType, types.FloatType, types.IntType, types.ListType, types.LongType, types.NoneType, types.StringType, types.TupleType, types.UnicodeType):
373 raise TypeError
374
375
376 if type(values) not in (types.ListType, types.TupleType):
377 values = [str(values)]
378
379
380 if type(values) == types.TupleType:
381 values = list(values)
382
383
384 if type(values) == types.ListType:
385 for i in range(len(values)):
386 if type(i) in (types.TupleType, types.ListType):
387 raise TypeError
388 values[i] = str(values[i])
389
390 return values
391
392
393 -def generate_ldif(from_single_pyldapobject, to_single_pyldapobject):
394 """
395 Generate an ldif formatted text, that can be used with official
396 OpenLDAP Utils (ldapadd, ldapmodify) or any other LDIF capable
397 LDAP tool.
398
399 The method expects two Python LDAP objects that only contain a single
400 DN each. To create an LDIF output it is also a pre-requisite that
401 both LDAP objects share the same DN.
402
403 The method returns a list, each item represents a line of the LDIF output
404 format.
405
406 If this function returns an empty list it means that both Python LDAP
407 objects are identical.
408
409 If this function returns C{None} it means that we met an error on our
410 way.
411
412 @param from_single_pyldapobject: current LDAP object in database
413 @type from_single_pyldapobject: list
414 @param to_single_pyldapobject: LDAP object containing the database changes that shall
415 be represented in the expected LDIF output text
416 @type to_single_pyldapobject: list
417 """
418 easyldap = easyLDAP()
419 ldif_output = []
420 if (len(from_single_pyldapobject) <= 1) and (len(to_single_pyldapobject) <= 1):
421 if (is_pyldapobject(from_single_pyldapobject)) and (is_pyldapobject(to_single_pyldapobject)):
422
423
424 if (not from_single_pyldapobject[0][1]) and to_single_pyldapobject[0][1]:
425
426
427 ldif_output.append('dn: %s' % to_single_pyldapobject[0][0])
428
429 for objectClass in to_single_pyldapobject[0][1]['objectClass']:
430 ldif_output.append('objectClass: %s' % objectClass)
431
432 for attrDesc in [ key for key in to_single_pyldapobject[0][1].keys() if key != 'objectClass' ]:
433 for value in to_single_pyldapobject[0][1][attrDesc]:
434 ldif_output.append('%s: %s' % (attrDesc, value))
435
436
437 elif from_single_pyldapobject[0][1] and (not to_single_pyldapobject[0][1]):
438
439 ldif_output.append('dn: %s' % to_single_pyldapobject[0][0])
440 ldif_output.append('changeType: delete')
441
442
443 elif from_single_pyldapobject[0][1] and to_single_pyldapobject[0][1]:
444
445 ldif_output.append('dn: %s' % to_single_pyldapobject[0][0])
446 ldif_output.append('changeType: modify')
447
448 for compAttrDesc, compValues in to_single_pyldapobject[0][1].items ():
449
450
451 compAttrType = _attrtype(compAttrDesc)
452 if _attropt(compAttrDesc):
453 compAttrOption = ';'+_attropt(compAttrDesc)
454 else:
455 compAttrOption = ''
456
457
458
459 if easyldap.has_oid_set(easyldap.map_attrtypes2oids(compAttrType)[0],from_single_pyldapobject) and (_attropt(compAttrDesc) in easyldap.get_used_attroptions(compAttrType,from_single_pyldapobject)):
460 for mappedAttrType in easyldap.map_oid2attrtypes(easyldap.map_attrtypes2oids(compAttrType)[0]):
461 if mappedAttrType+compAttrOption in from_single_pyldapobject[0][1].keys():
462 if from_single_pyldapobject[0][1][mappedAttrType+compAttrOption] != to_single_pyldapobject[0][1][compAttrDesc]:
463 ldif_output.append ('replace: %s' % compAttrDesc)
464 for value in compValues:
465 ldif_output.append ('%s: %s' % (compAttrDesc, value))
466 ldif_output.append ('-')
467
468 else:
469 ldif_output.append ('add: %s' % compAttrDesc)
470 for value in compValues:
471 ldif_output.append ('%s: %s' % (compAttrDesc, value))
472 ldif_output.append ('-')
473
474 for compAttrDesc in from_single_pyldapobject[0][1].keys ():
475 if not to_single_pyldapobject[0][1].has_key (compAttrDesc):
476 ldif_output.append ('delete: %s' % compAttrDesc)
477 ldif_output.append ('-')
478
479 else:
480
481 raise easyLDAP_exceptions.LDAPOBJECTS_EMPTY
482 else:
483
484 raise easyLDAP_exceptions.NOT_A_PYTHON_LDAPOBJECT
485 else:
486
487 raise easyLDAP_exceptions.NOT_A_single_pyldapobject
488
489 return ldif_output
490
492 """
493 Generate a modification list, that can be used with the Python LDAP
494 methods::
495
496 ldap.ldap_modify()
497 ldap.modify_s()
498
499 The method expects two Python LDAP objects that only contain a single
500 DN each. To create an LDAP modlist it is also a pre-requisite that
501 both LDAP objects share the same DN.
502
503 If this function returns an empty list it means that both Python LDAP
504 objects are identical.
505
506 If this function returns C{None} it means that we met an error on our
507 way.
508
509 @param from_single_pyldapobject: current LDAP object in database
510 @type from_single_pyldapobject: list
511 @param to_single_pyldapobject: LDAP object containing the database changes that shall
512 be represented in the expected Python LDAP stylish list of modifications
513 @type to_single_pyldapobject: list
514 """
515 easyldap = easyLDAP()
516 modlist = []
517
518 if (len(from_single_pyldapobject) <= 1) and (len(to_single_pyldapobject) <= 1):
519
520 if (is_pyldapobject(from_single_pyldapobject, strict=False)) and (is_pyldapobject(to_single_pyldapobject, strict=False)):
521
522
523 for pyobj in (from_single_pyldapobject, to_single_pyldapobject):
524 for k in pyobj[0][1].keys():
525 if k != k.lower():
526 pyobj[0][1][k.lower()] = pyobj[0][1][k]
527 del pyobj[0][1][k]
528
529 for compAttrDesc, compValues in to_single_pyldapobject[0][1].items():
530
531
532 compAttrType = _attrtype(compAttrDesc)
533 if _attropt(compAttrDesc):
534 compAttrOption = ';'+_attropt(compAttrDesc)
535 else:
536 compAttrOption = ''
537
538
539
540
541 if (easyldap.has_oid_set(easyldap.map_attrtypes2oids(compAttrType)[0],from_single_pyldapobject)) and ((_attropt(compAttrDesc) in easyldap.get_used_attroptions(compAttrType,from_single_pyldapobject)) or (not easyldap.get_used_attroptions(compAttrType,from_single_pyldapobject))):
542 for mappedAttrType in easyldap.map_oid2attrtypes(easyldap.map_attrtypes2oids(compAttrType)[0]):
543 if mappedAttrType+compAttrOption in from_single_pyldapobject[0][1].keys():
544 if from_single_pyldapobject[0][1][mappedAttrType+compAttrOption] != to_single_pyldapobject[0][1][compAttrDesc]:
545 modlist.append ([ldap.MOD_REPLACE, compAttrDesc, compValues])
546
547 else:
548 modlist.append([ldap.MOD_ADD, compAttrDesc, compValues])
549
550 for compAttrDesc, compValues in from_single_pyldapobject[0][1].items():
551 if not compAttrDesc in to_single_pyldapobject[0][1].keys():
552 modlist.append([ldap.MOD_DELETE, compAttrDesc, compValues])
553 else:
554
555 raise easyLDAP_exceptions.NOT_A_PYTHON_LDAPOBJECT
556 else:
557
558 raise easyLDAP_exceptions.NOT_A_SINGLE_LDAPOBJECT
559
560
561 for modlist_idx in range(len(modlist)):
562 orig_values = modlist[modlist_idx][2]
563 encoded_values = locale2ldapEncode(orig_values, easyldap.LDAPServerCharsetEncoding)
564 modlist[modlist_idx][1] = easyldap._rewrite_attrtype_names(modlist[modlist_idx][1])
565 for idx in range(len(encoded_values)):
566 modlist[modlist_idx][2][idx] = encoded_values[idx]
567 modlist[modlist_idx] = tuple(modlist[modlist_idx])
568
569 return modlist
570
571
573 """
574 Return LDAP DNs that have all the same depth level
575 in an LDAP tree hierarchy.
576
577 @param dn_list: list of LDAP DN strings
578 @type dn_list: list
579 @param level: depth level in the LDAP tree
580 @type level: int
581 """
582 return [ dn.lower() for dn in dn_list if len(split_dn(dn)) == level ]
583
584
586 """
587 Sort a DN list so that it can be looked at
588 as an LDAP tree after sorting.
589
590 @param dn_list: list of LDAP DN strings
591 @type dn_list: list
592 """
593 if dn_list == []:
594 return ([],[])
595
596 dn_list = [ dn.lower() for dn in dn_list ]
597
598 maxdepth = 0
599 for dn in dn_list:
600 len_dn = len(split_dn(dn))
601 maxdepth = max(maxdepth, len_dn)
602
603 basedepth = maxdepth
604 for dn in dn_list:
605 len_dn = len(split_dn(dn))
606 basedepth = min(basedepth, len_dn)
607
608 sorted_dnlist = [ dn for dn in dn_list if len(split_dn(dn)) == basedepth ]
609 sorted_dnlist.sort()
610
611
612 for dn in sorted_dnlist:
613 dn_list.remove(dn)
614
615 level = len(split_dn(sorted_dnlist[0])) +1
616
617 modified = True
618 while dn_list and modified == True:
619
620 modified = False
621 temp_sorted_dnlist = sorted_dnlist
622 for search_dn in temp_sorted_dnlist:
623
624 sub_dnlist = []
625 for add_dn in [ dn for dn in get_onelevel_dnlist(dn_list, level) if dn.endswith(search_dn) and len(split_dn(dn)) == len(split_dn(search_dn))+1 ]:
626 sub_dnlist.append(add_dn)
627 dn_list.remove(add_dn)
628
629 if sub_dnlist:
630 modified = True
631 sub_dnlist.sort()
632
633 n = sorted_dnlist.index(search_dn)+1
634 sorted_dnlist[n:n] = sub_dnlist
635
636 level += 1
637
638 orphaned_dnlist = dn_list
639 return (sorted_dnlist, orphaned_dnlist)
640
641
642 -def genpasswd(length=8, chars=string.letters + string.digits):
643 """
644 Generate a random password.
645
646 @param length: password length
647 @type length: int
648 @param chars: choice of characters for the random password
649 @type chars: str
650 """
651 return ''.join([random.choice(chars) for i in range(length)])
652
653
655 """
656 Split a DN into its RDN components.
657
658 @param dn: DN to be split
659 @type dn: str
660 @param basedn: if given, the base DN at the end of the DN will not
661 be split into RDNs
662 @type basedn: str
663 """
664 append_basedn = False
665 if basedn and dn.lower() == basedn.lower():
666 return dn
667
668 if basedn and dn.lower().endswith(basedn.lower()):
669 append_basedn = True
670 dn = dn[:-(len(basedn)+1)]
671
672 s = dn.split('=')
673 result = [s[0]]
674
675 i=0
676 while len(s) >= 3:
677
678 pos = s[1].rindex(',')
679 result[i] += '='+s[1][:pos]
680 result.append(s[1][pos+1:])
681 i += 1
682 del s[1]
683
684 result[i] += '='+s[1]
685
686 if append_basedn:
687 result.append(basedn.lower())
688
689 return result
690
691
693 """
694 Return the parent DN of a given DN.
695
696 If the given DN already is the base DN of the LDAP tree,
697 then the Base DN is returned.
698
699 @param dn: child DN
700 @type dn: str
701 @param basedn: if the given DN equals the base DN then the base DN will
702 be returned
703 @type basedn: str
704 """
705 dn_split = split_dn(dn, basedn=basedn)
706 return ','.join(dn_split[1:])
707
708
710 """
711 Return the RDN of a given DN.
712
713 If the given DN already is the base DN of the LDAP tree,
714 then the Base DN is returned.
715
716 @param dn: find out RDN of this DN
717 @type dn: str
718 @param basedn: if the given DN equals the base DN then the base DN will
719 be returned
720 @type basedn: str
721 """
722 dn_split = split_dn(dn, basedn=basedn)
723 return dn_split[0]
724
725
727 """
728 Check if DN has a valid syntax.
729
730 @param dn: DN to be checked for valid syntax
731 @type dn: str
732 """
733
734 if type(dn) != types.StringType:
735 return False
736 if '=' not in dn:
737 return False
738
739 return True
740
741
742
744 """
745 Detect the nameing attribute type of a given DN.
746
747 @param dn: detect naming attribute type from this DN
748 @type dn: str
749 """
750 dn = dn.lower()
751 rdn = dn.split(',')[0]
752 (attr,value) = rdn.split('=')
753 return (attr, value)
754
755
757 """
758 Find the (mathematical) intersection of two different
759 sets of items.
760
761 @param l1: first list of items for intersection
762 @type l1: list
763 @param l2: second list of items for intersection
764 type l2: list
765 """
766 intersection = []
767
768 for i in l1:
769 if i in l2: intersection.append(i)
770
771 return intersection
772
773
774 if __name__=='__main__':
775 pass
776