s4:python/samba/samdb: add toggle_userAccountFlags() helper function
[Samba.git] / source4 / scripting / python / samba / samdb.py
blobadb4eb08a5a8f290538494d2057ab3b15a45978c
1 #!/usr/bin/env python
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
5 # Copyright (C) Matthias Dieter Wallnoefer 2009
7 # Based on the original in EJS:
8 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 """Convenience functions for using the SAM."""
26 import samba
27 import ldb
28 import time
29 import base64
30 from samba import dsdb
31 from samba.ndr import ndr_unpack, ndr_pack
32 from samba.dcerpc import drsblobs, misc
34 __docformat__ = "restructuredText"
37 class SamDB(samba.Ldb):
38 """The SAM database."""
40 hash_oid_name = {}
42 def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
43 credentials=None, flags=0, options=None, global_schema=True,
44 auto_connect=True, am_rodc=None):
45 self.lp = lp
46 if not auto_connect:
47 url = None
48 elif url is None and lp is not None:
49 url = lp.samdb_url()
51 super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
52 session_info=session_info, credentials=credentials, flags=flags,
53 options=options)
55 if global_schema:
56 dsdb._dsdb_set_global_schema(self)
58 if am_rodc is not None:
59 dsdb._dsdb_set_am_rodc(self, am_rodc)
61 def connect(self, url=None, flags=0, options=None):
62 if self.lp is not None:
63 url = self.lp.private_path(url)
65 super(SamDB, self).connect(url=url, flags=flags,
66 options=options)
68 def am_rodc(self):
69 return dsdb._am_rodc(self)
71 def domain_dn(self):
72 return str(self.get_default_basedn())
74 def enable_account(self, search_filter):
75 """Enables an account
77 :param search_filter: LDAP filter to find the user (eg
78 samccountname=name)
79 """
81 flags = samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_PASSWD_NOTREQD
82 self.toggle_userAccountFlags(search_filter, flags, on=False)
84 def toggle_userAccountFlags(self, search_filter, flags, on=True, strict=False):
85 """toggle_userAccountFlags
87 :param search_filter: LDAP filter to find the user (eg
88 samccountname=name)
89 :flags: samba.dsdb.UF_* flags
90 :on: on=True (default) => set, on=False => unset
91 :strict: strict=False (default) ignore if no action is needed
92 strict=True raises an Exception if...
93 """
94 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
95 expression=search_filter, attrs=["userAccountControl"])
96 if len(res) == 0:
97 raise Exception('Unable to find user "%s"' % search_filter)
98 assert(len(res) == 1)
99 account_dn = res[0].dn
101 old_uac = int(res[0]["userAccountControl"][0])
102 if on:
103 if strict and (old_uac & flags):
104 error = 'userAccountFlags[%d:0x%08X] already contain 0x%X' % (old_uac, old_uac, flags)
105 raise Exception(error)
107 new_uac = old_uac | flags
108 else:
109 if strict and not (old_uac & flags):
110 error = 'userAccountFlags[%d:0x%08X] not contain 0x%X' % (old_uac, old_uac, flags)
111 raise Exception(error)
113 new_uac = old_uac & ~flags
115 if old_uac == new_uac:
116 return
118 mod = """
119 dn: %s
120 changetype: modify
121 delete: userAccountControl
122 userAccountControl: %u
123 add: userAccountControl
124 userAccountControl: %u
125 """ % (account_dn, old_uac, new_uac)
126 self.modify_ldif(mod)
128 def force_password_change_at_next_login(self, search_filter):
129 """Forces a password change at next login
131 :param search_filter: LDAP filter to find the user (eg
132 samccountname=name)
134 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
135 expression=search_filter, attrs=[])
136 if len(res) == 0:
137 raise Exception('Unable to find user "%s"' % search_filter)
138 assert(len(res) == 1)
139 user_dn = res[0].dn
141 mod = """
142 dn: %s
143 changetype: modify
144 replace: pwdLastSet
145 pwdLastSet: 0
146 """ % (user_dn)
147 self.modify_ldif(mod)
149 def newgroup(self, groupname, groupou=None, grouptype=None,
150 description=None, mailaddress=None, notes=None, sd=None):
151 """Adds a new group with additional parameters
153 :param groupname: Name of the new group
154 :param grouptype: Type of the new group
155 :param description: Description of the new group
156 :param mailaddress: Email address of the new group
157 :param notes: Notes of the new group
158 :param sd: security descriptor of the object
161 group_dn = "CN=%s,%s,%s" % (groupname, (groupou or "CN=Users"), self.domain_dn())
163 # The new user record. Note the reliance on the SAMLDB module which
164 # fills in the default informations
165 ldbmessage = {"dn": group_dn,
166 "sAMAccountName": groupname,
167 "objectClass": "group"}
169 if grouptype is not None:
170 ldbmessage["groupType"] = self.normalise_int32(grouptype)
172 if description is not None:
173 ldbmessage["description"] = description
175 if mailaddress is not None:
176 ldbmessage["mail"] = mailaddress
178 if notes is not None:
179 ldbmessage["info"] = notes
181 if sd is not None:
182 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
184 self.add(ldbmessage)
186 def deletegroup(self, groupname):
187 """Deletes a group
189 :param groupname: Name of the target group
192 groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (groupname, "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
193 self.transaction_start()
194 try:
195 targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
196 expression=groupfilter, attrs=[])
197 if len(targetgroup) == 0:
198 raise Exception('Unable to find group "%s"' % groupname)
199 assert(len(targetgroup) == 1)
200 self.delete(targetgroup[0].dn)
201 except Exception:
202 self.transaction_cancel()
203 raise
204 else:
205 self.transaction_commit()
207 def add_remove_group_members(self, groupname, listofmembers,
208 add_members_operation=True):
209 """Adds or removes group members
211 :param groupname: Name of the target group
212 :param listofmembers: Comma-separated list of group members
213 :param add_members_operation: Defines if its an add or remove
214 operation
217 groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (groupname, "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
218 groupmembers = listofmembers.split(',')
220 self.transaction_start()
221 try:
222 targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
223 expression=groupfilter, attrs=['member'])
224 if len(targetgroup) == 0:
225 raise Exception('Unable to find group "%s"' % groupname)
226 assert(len(targetgroup) == 1)
228 modified = False
230 addtargettogroup = """
231 dn: %s
232 changetype: modify
233 """ % (str(targetgroup[0].dn))
235 for member in groupmembers:
236 targetmember = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
237 expression="(|(sAMAccountName=%s)(CN=%s))" % (member, member), attrs=[])
239 if len(targetmember) != 1:
240 continue
242 if add_members_operation is True and (targetgroup[0].get('member') is None or str(targetmember[0].dn) not in targetgroup[0]['member']):
243 modified = True
244 addtargettogroup += """add: member
245 member: %s
246 """ % (str(targetmember[0].dn))
248 elif add_members_operation is False and (targetgroup[0].get('member') is not None and str(targetmember[0].dn) in targetgroup[0]['member']):
249 modified = True
250 addtargettogroup += """delete: member
251 member: %s
252 """ % (str(targetmember[0].dn))
254 if modified is True:
255 self.modify_ldif(addtargettogroup)
257 except Exception:
258 self.transaction_cancel()
259 raise
260 else:
261 self.transaction_commit()
263 def newuser(self, username, password,
264 force_password_change_at_next_login_req=False,
265 useusernameascn=False, userou=None, surname=None, givenname=None,
266 initials=None, profilepath=None, scriptpath=None, homedrive=None,
267 homedirectory=None, jobtitle=None, department=None, company=None,
268 description=None, mailaddress=None, internetaddress=None,
269 telephonenumber=None, physicaldeliveryoffice=None, sd=None,
270 setpassword=True):
271 """Adds a new user with additional parameters
273 :param username: Name of the new user
274 :param password: Password for the new user
275 :param force_password_change_at_next_login_req: Force password change
276 :param useusernameascn: Use username as cn rather that firstname +
277 initials + lastname
278 :param userou: Object container (without domainDN postfix) for new user
279 :param surname: Surname of the new user
280 :param givenname: First name of the new user
281 :param initials: Initials of the new user
282 :param profilepath: Profile path of the new user
283 :param scriptpath: Logon script path of the new user
284 :param homedrive: Home drive of the new user
285 :param homedirectory: Home directory of the new user
286 :param jobtitle: Job title of the new user
287 :param department: Department of the new user
288 :param company: Company of the new user
289 :param description: of the new user
290 :param mailaddress: Email address of the new user
291 :param internetaddress: Home page of the new user
292 :param telephonenumber: Phone number of the new user
293 :param physicaldeliveryoffice: Office location of the new user
294 :param sd: security descriptor of the object
295 :param setpassword: optionally disable password reset
298 displayname = ""
299 if givenname is not None:
300 displayname += givenname
302 if initials is not None:
303 displayname += ' %s.' % initials
305 if surname is not None:
306 displayname += ' %s' % surname
308 cn = username
309 if useusernameascn is None and displayname is not "":
310 cn = displayname
312 user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
314 dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
315 user_principal_name = "%s@%s" % (username, dnsdomain)
316 # The new user record. Note the reliance on the SAMLDB module which
317 # fills in the default informations
318 ldbmessage = {"dn": user_dn,
319 "sAMAccountName": username,
320 "userPrincipalName": user_principal_name,
321 "objectClass": "user"}
323 if surname is not None:
324 ldbmessage["sn"] = surname
326 if givenname is not None:
327 ldbmessage["givenName"] = givenname
329 if displayname is not "":
330 ldbmessage["displayName"] = displayname
331 ldbmessage["name"] = displayname
333 if initials is not None:
334 ldbmessage["initials"] = '%s.' % initials
336 if profilepath is not None:
337 ldbmessage["profilePath"] = profilepath
339 if scriptpath is not None:
340 ldbmessage["scriptPath"] = scriptpath
342 if homedrive is not None:
343 ldbmessage["homeDrive"] = homedrive
345 if homedirectory is not None:
346 ldbmessage["homeDirectory"] = homedirectory
348 if jobtitle is not None:
349 ldbmessage["title"] = jobtitle
351 if department is not None:
352 ldbmessage["department"] = department
354 if company is not None:
355 ldbmessage["company"] = company
357 if description is not None:
358 ldbmessage["description"] = description
360 if mailaddress is not None:
361 ldbmessage["mail"] = mailaddress
363 if internetaddress is not None:
364 ldbmessage["wWWHomePage"] = internetaddress
366 if telephonenumber is not None:
367 ldbmessage["telephoneNumber"] = telephonenumber
369 if physicaldeliveryoffice is not None:
370 ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
372 if sd is not None:
373 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
375 self.transaction_start()
376 try:
377 self.add(ldbmessage)
379 # Sets the password for it
380 if setpassword:
381 self.setpassword("(samAccountName=%s)" % username, password,
382 force_password_change_at_next_login_req)
383 except Exception:
384 self.transaction_cancel()
385 raise
386 else:
387 self.transaction_commit()
389 def setpassword(self, search_filter, password,
390 force_change_at_next_login=False, username=None):
391 """Sets the password for a user
393 :param search_filter: LDAP filter to find the user (eg
394 samccountname=name)
395 :param password: Password for the user
396 :param force_change_at_next_login: Force password change
398 self.transaction_start()
399 try:
400 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
401 expression=search_filter, attrs=[])
402 if len(res) == 0:
403 raise Exception('Unable to find user "%s"' % (username or search_filter))
404 if len(res) > 1:
405 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
406 user_dn = res[0].dn
407 setpw = """
408 dn: %s
409 changetype: modify
410 replace: unicodePwd
411 unicodePwd:: %s
412 """ % (user_dn, base64.b64encode(("\"" + password + "\"").encode('utf-16-le')))
414 self.modify_ldif(setpw)
416 if force_change_at_next_login:
417 self.force_password_change_at_next_login(
418 "(dn=" + str(user_dn) + ")")
420 # modify the userAccountControl to remove the disabled bit
421 self.enable_account(search_filter)
422 except Exception:
423 self.transaction_cancel()
424 raise
425 else:
426 self.transaction_commit()
428 def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
429 """Sets the account expiry for a user
431 :param search_filter: LDAP filter to find the user (eg
432 samaccountname=name)
433 :param expiry_seconds: expiry time from now in seconds
434 :param no_expiry_req: if set, then don't expire password
436 self.transaction_start()
437 try:
438 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
439 expression=search_filter,
440 attrs=["userAccountControl", "accountExpires"])
441 if len(res) == 0:
442 raise Exception('Unable to find user "%s"' % search_filter)
443 assert(len(res) == 1)
444 user_dn = res[0].dn
446 userAccountControl = int(res[0]["userAccountControl"][0])
447 accountExpires = int(res[0]["accountExpires"][0])
448 if no_expiry_req:
449 userAccountControl = userAccountControl | 0x10000
450 accountExpires = 0
451 else:
452 userAccountControl = userAccountControl & ~0x10000
453 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
455 setexp = """
456 dn: %s
457 changetype: modify
458 replace: userAccountControl
459 userAccountControl: %u
460 replace: accountExpires
461 accountExpires: %u
462 """ % (user_dn, userAccountControl, accountExpires)
464 self.modify_ldif(setexp)
465 except Exception:
466 self.transaction_cancel()
467 raise
468 else:
469 self.transaction_commit()
471 def set_domain_sid(self, sid):
472 """Change the domain SID used by this LDB.
474 :param sid: The new domain sid to use.
476 dsdb._samdb_set_domain_sid(self, sid)
478 def get_domain_sid(self):
479 """Read the domain SID used by this LDB. """
480 return dsdb._samdb_get_domain_sid(self)
482 domain_sid = property(get_domain_sid, set_domain_sid,
483 "SID for the domain")
485 def set_invocation_id(self, invocation_id):
486 """Set the invocation id for this SamDB handle.
488 :param invocation_id: GUID of the invocation id.
490 dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
492 def get_invocation_id(self):
493 """Get the invocation_id id"""
494 return dsdb._samdb_ntds_invocation_id(self)
496 invocation_id = property(get_invocation_id, set_invocation_id,
497 "Invocation ID GUID")
499 def get_oid_from_attid(self, attid):
500 return dsdb._dsdb_get_oid_from_attid(self, attid)
502 def get_attid_from_lDAPDisplayName(self, ldap_display_name,
503 is_schema_nc=False):
504 '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
505 return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
506 ldap_display_name, is_schema_nc)
508 def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
509 '''return the syntax OID for a LDAP attribute as a string'''
510 return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
512 def set_ntds_settings_dn(self, ntds_settings_dn):
513 """Set the NTDS Settings DN, as would be returned on the dsServiceName
514 rootDSE attribute.
516 This allows the DN to be set before the database fully exists
518 :param ntds_settings_dn: The new DN to use
520 dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
522 def get_ntds_GUID(self):
523 """Get the NTDS objectGUID"""
524 return dsdb._samdb_ntds_objectGUID(self)
526 def server_site_name(self):
527 """Get the server site name"""
528 return dsdb._samdb_server_site_name(self)
530 def load_partition_usn(self, base_dn):
531 return dsdb._dsdb_load_partition_usn(self, base_dn)
533 def set_schema(self, schema):
534 self.set_schema_from_ldb(schema.ldb)
536 def set_schema_from_ldb(self, ldb_conn):
537 dsdb._dsdb_set_schema_from_ldb(self, ldb_conn)
539 def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
540 '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
541 return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
543 def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
544 '''normalise a list of attribute values'''
545 return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
547 def get_attribute_from_attid(self, attid):
548 """ Get from an attid the associated attribute
550 :param attid: The attribute id for searched attribute
551 :return: The name of the attribute associated with this id
553 if len(self.hash_oid_name.keys()) == 0:
554 self._populate_oid_attid()
555 if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
556 return self.hash_oid_name[self.get_oid_from_attid(attid)]
557 else:
558 return None
560 def _populate_oid_attid(self):
561 """Populate the hash hash_oid_name.
563 This hash contains the oid of the attribute as a key and
564 its display name as a value
566 self.hash_oid_name = {}
567 res = self.search(expression="objectClass=attributeSchema",
568 controls=["search_options:1:2"],
569 attrs=["attributeID",
570 "lDAPDisplayName"])
571 if len(res) > 0:
572 for e in res:
573 strDisplay = str(e.get("lDAPDisplayName"))
574 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
576 def get_attribute_replmetadata_version(self, dn, att):
577 """Get the version field trom the replPropertyMetaData for
578 the given field
580 :param dn: The on which we want to get the version
581 :param att: The name of the attribute
582 :return: The value of the version field in the replPropertyMetaData
583 for the given attribute. None if the attribute is not replicated
586 res = self.search(expression="dn=%s" % dn,
587 scope=ldb.SCOPE_SUBTREE,
588 controls=["search_options:1:2"],
589 attrs=["replPropertyMetaData"])
590 if len(res) == 0:
591 return None
593 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
594 str(res[0]["replPropertyMetaData"]))
595 ctr = repl.ctr
596 if len(self.hash_oid_name.keys()) == 0:
597 self._populate_oid_attid()
598 for o in ctr.array:
599 # Search for Description
600 att_oid = self.get_oid_from_attid(o.attid)
601 if self.hash_oid_name.has_key(att_oid) and\
602 att.lower() == self.hash_oid_name[att_oid].lower():
603 return o.version
604 return None
606 def set_attribute_replmetadata_version(self, dn, att, value,
607 addifnotexist=False):
608 res = self.search(expression="dn=%s" % dn,
609 scope=ldb.SCOPE_SUBTREE,
610 controls=["search_options:1:2"],
611 attrs=["replPropertyMetaData"])
612 if len(res) == 0:
613 return None
615 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
616 str(res[0]["replPropertyMetaData"]))
617 ctr = repl.ctr
618 now = samba.unix2nttime(int(time.time()))
619 found = False
620 if len(self.hash_oid_name.keys()) == 0:
621 self._populate_oid_attid()
622 for o in ctr.array:
623 # Search for Description
624 att_oid = self.get_oid_from_attid(o.attid)
625 if self.hash_oid_name.has_key(att_oid) and\
626 att.lower() == self.hash_oid_name[att_oid].lower():
627 found = True
628 seq = self.sequence_number(ldb.SEQ_NEXT)
629 o.version = value
630 o.originating_change_time = now
631 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
632 o.originating_usn = seq
633 o.local_usn = seq
635 if not found and addifnotexist and len(ctr.array) >0:
636 o2 = drsblobs.replPropertyMetaData1()
637 o2.attid = 589914
638 att_oid = self.get_oid_from_attid(o2.attid)
639 seq = self.sequence_number(ldb.SEQ_NEXT)
640 o2.version = value
641 o2.originating_change_time = now
642 o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
643 o2.originating_usn = seq
644 o2.local_usn = seq
645 found = True
646 tab = ctr.array
647 tab.append(o2)
648 ctr.count = ctr.count + 1
649 ctr.array = tab
651 if found :
652 replBlob = ndr_pack(repl)
653 msg = ldb.Message()
654 msg.dn = res[0].dn
655 msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
656 ldb.FLAG_MOD_REPLACE,
657 "replPropertyMetaData")
658 self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
660 def write_prefixes_from_schema(self):
661 dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
663 def get_partitions_dn(self):
664 return dsdb._dsdb_get_partitions_dn(self)
666 def set_minPwdAge(self, value):
667 m = ldb.Message()
668 m.dn = ldb.Dn(self, self.domain_dn())
669 m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
670 self.modify(m)
672 def get_minPwdAge(self):
673 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
674 if len(res) == 0:
675 return None
676 elif not "minPwdAge" in res[0]:
677 return None
678 else:
679 return res[0]["minPwdAge"][0]
681 def set_minPwdLength(self, value):
682 m = ldb.Message()
683 m.dn = ldb.Dn(self, self.domain_dn())
684 m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
685 self.modify(m)
687 def get_minPwdLength(self):
688 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
689 if len(res) == 0:
690 return None
691 elif not "minPwdLength" in res[0]:
692 return None
693 else:
694 return res[0]["minPwdLength"][0]
696 def set_pwdProperties(self, value):
697 m = ldb.Message()
698 m.dn = ldb.Dn(self, self.domain_dn())
699 m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
700 self.modify(m)
702 def get_pwdProperties(self):
703 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
704 if len(res) == 0:
705 return None
706 elif not "pwdProperties" in res[0]:
707 return None
708 else:
709 return res[0]["pwdProperties"][0]
711 def set_dsheuristics(self, dsheuristics):
712 m = ldb.Message()
713 m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
714 % self.get_config_basedn().get_linearized())
715 if dsheuristics is not None:
716 m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
717 ldb.FLAG_MOD_REPLACE, "dSHeuristics")
718 else:
719 m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
720 "dSHeuristics")
721 self.modify(m)
723 def get_dsheuristics(self):
724 res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
725 % self.get_config_basedn().get_linearized(),
726 scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
727 if len(res) == 0:
728 dsheuristics = None
729 elif "dSHeuristics" in res[0]:
730 dsheuristics = res[0]["dSHeuristics"][0]
731 else:
732 dsheuristics = None
734 return dsheuristics
736 def create_ou(self, ou_dn, description=None, name=None, sd=None):
737 """Creates an organizationalUnit object
738 :param ou_dn: dn of the new object
739 :param description: description attribute
740 :param name: name atttribute
741 :param sd: security descriptor of the object, can be
742 an SDDL string or security.descriptor type
744 m = {"dn": ou_dn,
745 "objectClass": "organizationalUnit"}
747 if description:
748 m["description"] = description
749 if name:
750 m["name"] = name
752 if sd:
753 m["nTSecurityDescriptor"] = ndr_pack(sd)
754 self.add(m)
756 def normalise_int32(self, ivalue):
757 '''normalise a ldap integer to signed 32 bit'''
758 if int(ivalue) & 0x80000000:
759 return str(int(ivalue) - 0x100000000)
760 return str(ivalue)