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