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