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