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