python-samba-tool domain classicupgrade: Actually Skip domain trust accounts
[nivanova/samba-autobuild/.git] / 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, uidnumber=None, gidnumber=None, gecos=None,
294             loginshell=None, uid=None):
295         """Adds a new user with additional parameters
296
297         :param username: Name of the new user
298         :param password: Password for the new user
299         :param force_password_change_at_next_login_req: Force password change
300         :param useusernameascn: Use username as cn rather that firstname +
301             initials + lastname
302         :param userou: Object container (without domainDN postfix) for new user
303         :param surname: Surname of the new user
304         :param givenname: First name of the new user
305         :param initials: Initials of the new user
306         :param profilepath: Profile path of the new user
307         :param scriptpath: Logon script path of the new user
308         :param homedrive: Home drive of the new user
309         :param homedirectory: Home directory of the new user
310         :param jobtitle: Job title of the new user
311         :param department: Department of the new user
312         :param company: Company of the new user
313         :param description: of the new user
314         :param mailaddress: Email address of the new user
315         :param internetaddress: Home page of the new user
316         :param telephonenumber: Phone number of the new user
317         :param physicaldeliveryoffice: Office location of the new user
318         :param sd: security descriptor of the object
319         :param setpassword: optionally disable password reset
320         :param uidnumber: RFC2307 Unix numeric UID of the new user
321         :param gidnumber: RFC2307 Unix primary GID of the new user
322         :param gecos: RFC2307 Unix GECOS field of the new user
323         :param loginshell: RFC2307 Unix login shell of the new user
324         :param uid: RFC2307 Unix username of the new user
325         """
326
327         displayname = ""
328         if givenname is not None:
329             displayname += givenname
330
331         if initials is not None:
332             displayname += ' %s.' % initials
333
334         if surname is not None:
335             displayname += ' %s' % surname
336
337         cn = username
338         if useusernameascn is None and displayname is not "":
339             cn = displayname
340
341         user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
342
343         dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
344         user_principal_name = "%s@%s" % (username, dnsdomain)
345         # The new user record. Note the reliance on the SAMLDB module which
346         # fills in the default informations
347         ldbmessage = {"dn": user_dn,
348                       "sAMAccountName": username,
349                       "userPrincipalName": user_principal_name,
350                       "objectClass": "user"}
351
352         if surname is not None:
353             ldbmessage["sn"] = surname
354
355         if givenname is not None:
356             ldbmessage["givenName"] = givenname
357
358         if displayname is not "":
359             ldbmessage["displayName"] = displayname
360             ldbmessage["name"] = displayname
361
362         if initials is not None:
363             ldbmessage["initials"] = '%s.' % initials
364
365         if profilepath is not None:
366             ldbmessage["profilePath"] = profilepath
367
368         if scriptpath is not None:
369             ldbmessage["scriptPath"] = scriptpath
370
371         if homedrive is not None:
372             ldbmessage["homeDrive"] = homedrive
373
374         if homedirectory is not None:
375             ldbmessage["homeDirectory"] = homedirectory
376
377         if jobtitle is not None:
378             ldbmessage["title"] = jobtitle
379
380         if department is not None:
381             ldbmessage["department"] = department
382
383         if company is not None:
384             ldbmessage["company"] = company
385
386         if description is not None:
387             ldbmessage["description"] = description
388
389         if mailaddress is not None:
390             ldbmessage["mail"] = mailaddress
391
392         if internetaddress is not None:
393             ldbmessage["wWWHomePage"] = internetaddress
394
395         if telephonenumber is not None:
396             ldbmessage["telephoneNumber"] = telephonenumber
397
398         if physicaldeliveryoffice is not None:
399             ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
400
401         if sd is not None:
402             ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
403
404         ldbmessage2 = None
405         if any(map(lambda b: b is not None, (uid, uidnumber, gidnumber, gecos, loginshell))):
406             ldbmessage2 = ldb.Message()
407             ldbmessage2.dn = ldb.Dn(self, user_dn)
408             ldbmessage2["objectClass"] = ldb.MessageElement('posixAccount', ldb.FLAG_MOD_ADD, 'objectClass')
409             if uid is not None:
410                 ldbmessage2["uid"] = ldb.MessageElement(str(uid), ldb.FLAG_MOD_REPLACE, 'uid')
411             if uidnumber is not None:
412                 ldbmessage2["uidNumber"] = ldb.MessageElement(str(uidnumber), ldb.FLAG_MOD_REPLACE, 'uidNumber')
413             if gidnumber is not None:
414                 ldbmessage2["gidNumber"] = ldb.MessageElement(str(gidnumber), ldb.FLAG_MOD_REPLACE, 'gidNumber')
415             if gecos is not None:
416                 ldbmessage2["gecos"] = ldb.MessageElement(str(gecos), ldb.FLAG_MOD_REPLACE, 'gecos')
417             if loginshell is not None:
418                 ldbmessage2["loginShell"] = ldb.MessageElement(str(loginshell), ldb.FLAG_MOD_REPLACE, 'loginShell')
419
420         self.transaction_start()
421         try:
422             self.add(ldbmessage)
423             if ldbmessage2:
424                 self.modify(ldbmessage2)
425
426             # Sets the password for it
427             if setpassword:
428                 self.setpassword("(samAccountName=%s)" % ldb.binary_encode(username), password,
429                                  force_password_change_at_next_login_req)
430         except:
431             self.transaction_cancel()
432             raise
433         else:
434             self.transaction_commit()
435
436
437     def deleteuser(self, username):
438         """Deletes a user
439
440         :param username: Name of the target user
441         """
442
443         filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
444         self.transaction_start()
445         try:
446             target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
447                                  expression=filter, attrs=[])
448             if len(target) == 0:
449                 raise Exception('Unable to find user "%s"' % username)
450             assert(len(target) == 1)
451             self.delete(target[0].dn)
452         except:
453             self.transaction_cancel()
454             raise
455         else:
456             self.transaction_commit()
457
458     def setpassword(self, search_filter, password,
459             force_change_at_next_login=False, username=None):
460         """Sets the password for a user
461
462         :param search_filter: LDAP filter to find the user (eg
463             samccountname=name)
464         :param password: Password for the user
465         :param force_change_at_next_login: Force password change
466         """
467         self.transaction_start()
468         try:
469             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
470                               expression=search_filter, attrs=[])
471             if len(res) == 0:
472                 raise Exception('Unable to find user "%s"' % (username or search_filter))
473             if len(res) > 1:
474                 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
475             user_dn = res[0].dn
476             pw = unicode('"' + password + '"', 'utf-8').encode('utf-16-le')
477             setpw = """
478 dn: %s
479 changetype: modify
480 replace: unicodePwd
481 unicodePwd:: %s
482 """ % (user_dn, base64.b64encode(pw))
483
484             self.modify_ldif(setpw)
485
486             if force_change_at_next_login:
487                 self.force_password_change_at_next_login(
488                   "(distinguishedName=" + str(user_dn) + ")")
489
490             #  modify the userAccountControl to remove the disabled bit
491             self.enable_account(search_filter)
492         except:
493             self.transaction_cancel()
494             raise
495         else:
496             self.transaction_commit()
497
498     def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
499         """Sets the account expiry for a user
500
501         :param search_filter: LDAP filter to find the user (eg
502             samaccountname=name)
503         :param expiry_seconds: expiry time from now in seconds
504         :param no_expiry_req: if set, then don't expire password
505         """
506         self.transaction_start()
507         try:
508             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
509                           expression=search_filter,
510                           attrs=["userAccountControl", "accountExpires"])
511             if len(res) == 0:
512                 raise Exception('Unable to find user "%s"' % search_filter)
513             assert(len(res) == 1)
514             user_dn = res[0].dn
515
516             userAccountControl = int(res[0]["userAccountControl"][0])
517             accountExpires     = int(res[0]["accountExpires"][0])
518             if no_expiry_req:
519                 userAccountControl = userAccountControl | 0x10000
520                 accountExpires = 0
521             else:
522                 userAccountControl = userAccountControl & ~0x10000
523                 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
524
525             setexp = """
526 dn: %s
527 changetype: modify
528 replace: userAccountControl
529 userAccountControl: %u
530 replace: accountExpires
531 accountExpires: %u
532 """ % (user_dn, userAccountControl, accountExpires)
533
534             self.modify_ldif(setexp)
535         except:
536             self.transaction_cancel()
537             raise
538         else:
539             self.transaction_commit()
540
541     def set_domain_sid(self, sid):
542         """Change the domain SID used by this LDB.
543
544         :param sid: The new domain sid to use.
545         """
546         dsdb._samdb_set_domain_sid(self, sid)
547
548     def get_domain_sid(self):
549         """Read the domain SID used by this LDB. """
550         return dsdb._samdb_get_domain_sid(self)
551
552     domain_sid = property(get_domain_sid, set_domain_sid,
553         "SID for the domain")
554
555     def set_invocation_id(self, invocation_id):
556         """Set the invocation id for this SamDB handle.
557
558         :param invocation_id: GUID of the invocation id.
559         """
560         dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
561
562     def get_invocation_id(self):
563         """Get the invocation_id id"""
564         return dsdb._samdb_ntds_invocation_id(self)
565
566     invocation_id = property(get_invocation_id, set_invocation_id,
567         "Invocation ID GUID")
568
569     def get_oid_from_attid(self, attid):
570         return dsdb._dsdb_get_oid_from_attid(self, attid)
571
572     def get_attid_from_lDAPDisplayName(self, ldap_display_name,
573             is_schema_nc=False):
574         '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
575         return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
576             ldap_display_name, is_schema_nc)
577
578     def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
579         '''return the syntax OID for a LDAP attribute as a string'''
580         return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
581
582     def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
583         '''return the systemFlags for a LDAP attribute as a integer'''
584         return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
585
586     def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
587         '''return the linkID for a LDAP attribute as a integer'''
588         return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
589
590     def get_lDAPDisplayName_by_attid(self, attid):
591         '''return the lDAPDisplayName from an integer DRS attribute ID'''
592         return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
593
594     def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
595         '''return the attribute name of the corresponding backlink from the name
596         of a forward link attribute. If there is no backlink return None'''
597         return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
598
599     def set_ntds_settings_dn(self, ntds_settings_dn):
600         """Set the NTDS Settings DN, as would be returned on the dsServiceName
601         rootDSE attribute.
602
603         This allows the DN to be set before the database fully exists
604
605         :param ntds_settings_dn: The new DN to use
606         """
607         dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
608
609     def get_ntds_GUID(self):
610         """Get the NTDS objectGUID"""
611         return dsdb._samdb_ntds_objectGUID(self)
612
613     def server_site_name(self):
614         """Get the server site name"""
615         return dsdb._samdb_server_site_name(self)
616
617     def host_dns_name(self):
618         """return the DNS name of this host"""
619         res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
620         return res[0]['dNSHostName'][0]
621
622     def domain_dns_name(self):
623         """return the DNS name of the domain root"""
624         domain_dn = self.get_default_basedn()
625         return domain_dn.canonical_str().split('/')[0]
626
627     def forest_dns_name(self):
628         """return the DNS name of the forest root"""
629         forest_dn = self.get_root_basedn()
630         return forest_dn.canonical_str().split('/')[0]
631
632     def load_partition_usn(self, base_dn):
633         return dsdb._dsdb_load_partition_usn(self, base_dn)
634
635     def set_schema(self, schema, write_indices_and_attributes=True):
636         self.set_schema_from_ldb(schema.ldb, write_indices_and_attributes=write_indices_and_attributes)
637
638     def set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes=True):
639         dsdb._dsdb_set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes)
640
641     def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
642         '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
643         return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
644
645     def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
646         '''normalise a list of attribute values'''
647         return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
648
649     def get_attribute_from_attid(self, attid):
650         """ Get from an attid the associated attribute
651
652         :param attid: The attribute id for searched attribute
653         :return: The name of the attribute associated with this id
654         """
655         if len(self.hash_oid_name.keys()) == 0:
656             self._populate_oid_attid()
657         if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
658             return self.hash_oid_name[self.get_oid_from_attid(attid)]
659         else:
660             return None
661
662     def _populate_oid_attid(self):
663         """Populate the hash hash_oid_name.
664
665         This hash contains the oid of the attribute as a key and
666         its display name as a value
667         """
668         self.hash_oid_name = {}
669         res = self.search(expression="objectClass=attributeSchema",
670                            controls=["search_options:1:2"],
671                            attrs=["attributeID",
672                            "lDAPDisplayName"])
673         if len(res) > 0:
674             for e in res:
675                 strDisplay = str(e.get("lDAPDisplayName"))
676                 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
677
678     def get_attribute_replmetadata_version(self, dn, att):
679         """Get the version field trom the replPropertyMetaData for
680         the given field
681
682         :param dn: The on which we want to get the version
683         :param att: The name of the attribute
684         :return: The value of the version field in the replPropertyMetaData
685             for the given attribute. None if the attribute is not replicated
686         """
687
688         res = self.search(expression="distinguishedName=%s" % dn,
689                             scope=ldb.SCOPE_SUBTREE,
690                             controls=["search_options:1:2"],
691                             attrs=["replPropertyMetaData"])
692         if len(res) == 0:
693             return None
694
695         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
696                             str(res[0]["replPropertyMetaData"]))
697         ctr = repl.ctr
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                 return o.version
706         return None
707
708     def set_attribute_replmetadata_version(self, dn, att, value,
709             addifnotexist=False):
710         res = self.search(expression="distinguishedName=%s" % dn,
711                             scope=ldb.SCOPE_SUBTREE,
712                             controls=["search_options:1:2"],
713                             attrs=["replPropertyMetaData"])
714         if len(res) == 0:
715             return None
716
717         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
718                             str(res[0]["replPropertyMetaData"]))
719         ctr = repl.ctr
720         now = samba.unix2nttime(int(time.time()))
721         found = False
722         if len(self.hash_oid_name.keys()) == 0:
723             self._populate_oid_attid()
724         for o in ctr.array:
725             # Search for Description
726             att_oid = self.get_oid_from_attid(o.attid)
727             if self.hash_oid_name.has_key(att_oid) and\
728                att.lower() == self.hash_oid_name[att_oid].lower():
729                 found = True
730                 seq = self.sequence_number(ldb.SEQ_NEXT)
731                 o.version = value
732                 o.originating_change_time = now
733                 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
734                 o.originating_usn = seq
735                 o.local_usn = seq
736
737         if not found and addifnotexist and len(ctr.array) >0:
738             o2 = drsblobs.replPropertyMetaData1()
739             o2.attid = 589914
740             att_oid = self.get_oid_from_attid(o2.attid)
741             seq = self.sequence_number(ldb.SEQ_NEXT)
742             o2.version = value
743             o2.originating_change_time = now
744             o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
745             o2.originating_usn = seq
746             o2.local_usn = seq
747             found = True
748             tab = ctr.array
749             tab.append(o2)
750             ctr.count = ctr.count + 1
751             ctr.array = tab
752
753         if found :
754             replBlob = ndr_pack(repl)
755             msg = ldb.Message()
756             msg.dn = res[0].dn
757             msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
758                                                 ldb.FLAG_MOD_REPLACE,
759                                                 "replPropertyMetaData")
760             self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
761
762     def write_prefixes_from_schema(self):
763         dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
764
765     def get_partitions_dn(self):
766         return dsdb._dsdb_get_partitions_dn(self)
767
768     def get_nc_root(self, dn):
769         return dsdb._dsdb_get_nc_root(self, dn)
770
771     def get_wellknown_dn(self, nc_root, wkguid):
772         return dsdb._dsdb_get_wellknown_dn(self, nc_root, wkguid)
773
774     def set_minPwdAge(self, value):
775         m = ldb.Message()
776         m.dn = ldb.Dn(self, self.domain_dn())
777         m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
778         self.modify(m)
779
780     def get_minPwdAge(self):
781         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
782         if len(res) == 0:
783             return None
784         elif not "minPwdAge" in res[0]:
785             return None
786         else:
787             return res[0]["minPwdAge"][0]
788
789     def set_minPwdLength(self, value):
790         m = ldb.Message()
791         m.dn = ldb.Dn(self, self.domain_dn())
792         m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
793         self.modify(m)
794
795     def get_minPwdLength(self):
796         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
797         if len(res) == 0:
798             return None
799         elif not "minPwdLength" in res[0]:
800             return None
801         else:
802             return res[0]["minPwdLength"][0]
803
804     def set_pwdProperties(self, value):
805         m = ldb.Message()
806         m.dn = ldb.Dn(self, self.domain_dn())
807         m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
808         self.modify(m)
809
810     def get_pwdProperties(self):
811         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
812         if len(res) == 0:
813             return None
814         elif not "pwdProperties" in res[0]:
815             return None
816         else:
817             return res[0]["pwdProperties"][0]
818
819     def set_dsheuristics(self, dsheuristics):
820         m = ldb.Message()
821         m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
822                       % self.get_config_basedn().get_linearized())
823         if dsheuristics is not None:
824             m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
825                 ldb.FLAG_MOD_REPLACE, "dSHeuristics")
826         else:
827             m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
828                 "dSHeuristics")
829         self.modify(m)
830
831     def get_dsheuristics(self):
832         res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
833                           % self.get_config_basedn().get_linearized(),
834                           scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
835         if len(res) == 0:
836             dsheuristics = None
837         elif "dSHeuristics" in res[0]:
838             dsheuristics = res[0]["dSHeuristics"][0]
839         else:
840             dsheuristics = None
841
842         return dsheuristics
843
844     def create_ou(self, ou_dn, description=None, name=None, sd=None):
845         """Creates an organizationalUnit object
846         :param ou_dn: dn of the new object
847         :param description: description attribute
848         :param name: name atttribute
849         :param sd: security descriptor of the object, can be
850         an SDDL string or security.descriptor type
851         """
852         m = {"dn": ou_dn,
853              "objectClass": "organizationalUnit"}
854
855         if description:
856             m["description"] = description
857         if name:
858             m["name"] = name
859
860         if sd:
861             m["nTSecurityDescriptor"] = ndr_pack(sd)
862         self.add(m)
863
864     def sequence_number(self, seq_type):
865         """Returns the value of the sequence number according to the requested type
866         :param seq_type: type of sequence number
867          """
868         self.transaction_start()
869         try:
870             seq = super(SamDB, self).sequence_number(seq_type)
871         except:
872             self.transaction_cancel()
873             raise
874         else:
875             self.transaction_commit()
876         return seq
877
878     def get_dsServiceName(self):
879         '''get the NTDS DN from the rootDSE'''
880         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
881         return res[0]["dsServiceName"][0]
882
883     def get_serverName(self):
884         '''get the server DN from the rootDSE'''
885         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
886         return res[0]["serverName"][0]