python/samba: use an explicit .encode('utf-8') where we expect utf8 passwords
[samba.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, dsdb_dns
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     hash_well_known = {}
43
44     def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
45                  credentials=None, flags=0, options=None, global_schema=True,
46                  auto_connect=True, am_rodc=None):
47         self.lp = lp
48         if not auto_connect:
49             url = None
50         elif url is None and lp is not None:
51             url = lp.samdb_url()
52
53         self.url = url
54
55         super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
56             session_info=session_info, credentials=credentials, flags=flags,
57             options=options)
58
59         if global_schema:
60             dsdb._dsdb_set_global_schema(self)
61
62         if am_rodc is not None:
63             dsdb._dsdb_set_am_rodc(self, am_rodc)
64
65     def connect(self, url=None, flags=0, options=None):
66         '''connect to the database'''
67         if self.lp is not None and not os.path.exists(url):
68             url = self.lp.private_path(url)
69         self.url = url
70
71         super(SamDB, self).connect(url=url, flags=flags,
72                 options=options)
73
74     def am_rodc(self):
75         '''return True if we are an RODC'''
76         return dsdb._am_rodc(self)
77
78     def am_pdc(self):
79         '''return True if we are an PDC emulator'''
80         return dsdb._am_pdc(self)
81
82     def domain_dn(self):
83         '''return the domain DN'''
84         return str(self.get_default_basedn())
85
86     def disable_account(self, search_filter):
87         """Disables an account
88
89         :param search_filter: LDAP filter to find the user (eg
90             samccountname=name)
91         """
92
93         flags = samba.dsdb.UF_ACCOUNTDISABLE
94         self.toggle_userAccountFlags(search_filter, flags, on=True)
95
96     def enable_account(self, search_filter):
97         """Enables an account
98
99         :param search_filter: LDAP filter to find the user (eg
100             samccountname=name)
101         """
102
103         flags = samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_PASSWD_NOTREQD
104         self.toggle_userAccountFlags(search_filter, flags, on=False)
105
106     def toggle_userAccountFlags(self, search_filter, flags, flags_str=None,
107                                 on=True, strict=False):
108         """Toggle_userAccountFlags
109
110         :param search_filter: LDAP filter to find the user (eg
111             samccountname=name)
112         :param flags: samba.dsdb.UF_* flags
113         :param on: on=True (default) => set, on=False => unset
114         :param strict: strict=False (default) ignore if no action is needed
115                  strict=True raises an Exception if...
116         """
117         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
118                           expression=search_filter, attrs=["userAccountControl"])
119         if len(res) == 0:
120                 raise Exception("Unable to find account where '%s'" % search_filter)
121         assert(len(res) == 1)
122         account_dn = res[0].dn
123
124         old_uac = int(res[0]["userAccountControl"][0])
125         if on:
126             if strict and (old_uac & flags):
127                 error = "Account flag(s) '%s' already set" % flags_str
128                 raise Exception(error)
129
130             new_uac = old_uac | flags
131         else:
132             if strict and not (old_uac & flags):
133                 error = "Account flag(s) '%s' already unset" % flags_str
134                 raise Exception(error)
135
136             new_uac = old_uac & ~flags
137
138         if old_uac == new_uac:
139             return
140
141         mod = """
142 dn: %s
143 changetype: modify
144 delete: userAccountControl
145 userAccountControl: %u
146 add: userAccountControl
147 userAccountControl: %u
148 """ % (account_dn, old_uac, new_uac)
149         self.modify_ldif(mod)
150
151     def force_password_change_at_next_login(self, search_filter):
152         """Forces a password change at next login
153
154         :param search_filter: LDAP filter to find the user (eg
155             samccountname=name)
156         """
157         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
158                           expression=search_filter, attrs=[])
159         if len(res) == 0:
160                 raise Exception('Unable to find user "%s"' % search_filter)
161         assert(len(res) == 1)
162         user_dn = res[0].dn
163
164         mod = """
165 dn: %s
166 changetype: modify
167 replace: pwdLastSet
168 pwdLastSet: 0
169 """ % (user_dn)
170         self.modify_ldif(mod)
171
172     def newgroup(self, groupname, groupou=None, grouptype=None,
173                  description=None, mailaddress=None, notes=None, sd=None,
174                  gidnumber=None, nisdomain=None):
175         """Adds a new group with additional parameters
176
177         :param groupname: Name of the new group
178         :param grouptype: Type of the new group
179         :param description: Description of the new group
180         :param mailaddress: Email address of the new group
181         :param notes: Notes of the new group
182         :param gidnumber: GID Number of the new group
183         :param nisdomain: NIS Domain Name of the new group
184         :param sd: security descriptor of the object
185         """
186
187         group_dn = "CN=%s,%s,%s" % (groupname, (groupou or "CN=Users"), self.domain_dn())
188
189         # The new user record. Note the reliance on the SAMLDB module which
190         # fills in the default informations
191         ldbmessage = {"dn": group_dn,
192             "sAMAccountName": groupname,
193             "objectClass": "group"}
194
195         if grouptype is not None:
196             ldbmessage["groupType"] = normalise_int32(grouptype)
197
198         if description is not None:
199             ldbmessage["description"] = description
200
201         if mailaddress is not None:
202             ldbmessage["mail"] = mailaddress
203
204         if notes is not None:
205             ldbmessage["info"] = notes
206
207         if gidnumber is not None:
208             ldbmessage["gidNumber"] = normalise_int32(gidnumber)
209
210         if nisdomain is not None:
211             ldbmessage["msSFU30Name"] = groupname
212             ldbmessage["msSFU30NisDomain"] = nisdomain
213
214         if sd is not None:
215             ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
216
217         self.add(ldbmessage)
218
219     def deletegroup(self, groupname):
220         """Deletes a group
221
222         :param groupname: Name of the target group
223         """
224
225         groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
226         self.transaction_start()
227         try:
228             targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
229                                expression=groupfilter, attrs=[])
230             if len(targetgroup) == 0:
231                 raise Exception('Unable to find group "%s"' % groupname)
232             assert(len(targetgroup) == 1)
233             self.delete(targetgroup[0].dn)
234         except:
235             self.transaction_cancel()
236             raise
237         else:
238             self.transaction_commit()
239
240     def add_remove_group_members(self, groupname, members,
241                                   add_members_operation=True):
242         """Adds or removes group members
243
244         :param groupname: Name of the target group
245         :param members: list of group members
246         :param add_members_operation: Defines if its an add or remove
247             operation
248         """
249
250         groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
251             ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
252
253         self.transaction_start()
254         try:
255             targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
256                                expression=groupfilter, attrs=['member'])
257             if len(targetgroup) == 0:
258                 raise Exception('Unable to find group "%s"' % groupname)
259             assert(len(targetgroup) == 1)
260
261             modified = False
262
263             addtargettogroup = """
264 dn: %s
265 changetype: modify
266 """ % (str(targetgroup[0].dn))
267
268             for member in members:
269                 targetmember = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
270                                     expression="(|(sAMAccountName=%s)(CN=%s))" % (
271                     ldb.binary_encode(member), ldb.binary_encode(member)), attrs=[])
272
273                 if len(targetmember) != 1:
274                     raise Exception('Unable to find "%s". Operation cancelled.' % member)
275
276                 if add_members_operation is True and (targetgroup[0].get('member') is None or str(targetmember[0].dn) not in targetgroup[0]['member']):
277                     modified = True
278                     addtargettogroup += """add: member
279 member: %s
280 """ % (str(targetmember[0].dn))
281
282                 elif add_members_operation is False and (targetgroup[0].get('member') is not None and str(targetmember[0].dn) in targetgroup[0]['member']):
283                     modified = True
284                     addtargettogroup += """delete: member
285 member: %s
286 """ % (str(targetmember[0].dn))
287
288             if modified is True:
289                 self.modify_ldif(addtargettogroup)
290
291         except:
292             self.transaction_cancel()
293             raise
294         else:
295             self.transaction_commit()
296
297     def newuser(self, username, password,
298             force_password_change_at_next_login_req=False,
299             useusernameascn=False, userou=None, surname=None, givenname=None,
300             initials=None, profilepath=None, scriptpath=None, homedrive=None,
301             homedirectory=None, jobtitle=None, department=None, company=None,
302             description=None, mailaddress=None, internetaddress=None,
303             telephonenumber=None, physicaldeliveryoffice=None, sd=None,
304             setpassword=True, uidnumber=None, gidnumber=None, gecos=None,
305             loginshell=None, uid=None, nisdomain=None, unixhome=None,
306             smartcard_required=False):
307         """Adds a new user with additional parameters
308
309         :param username: Name of the new user
310         :param password: Password for the new user
311         :param force_password_change_at_next_login_req: Force password change
312         :param useusernameascn: Use username as cn rather that firstname +
313             initials + lastname
314         :param userou: Object container (without domainDN postfix) for new user
315         :param surname: Surname of the new user
316         :param givenname: First name of the new user
317         :param initials: Initials of the new user
318         :param profilepath: Profile path of the new user
319         :param scriptpath: Logon script path of the new user
320         :param homedrive: Home drive of the new user
321         :param homedirectory: Home directory of the new user
322         :param jobtitle: Job title of the new user
323         :param department: Department of the new user
324         :param company: Company of the new user
325         :param description: of the new user
326         :param mailaddress: Email address of the new user
327         :param internetaddress: Home page of the new user
328         :param telephonenumber: Phone number of the new user
329         :param physicaldeliveryoffice: Office location of the new user
330         :param sd: security descriptor of the object
331         :param setpassword: optionally disable password reset
332         :param uidnumber: RFC2307 Unix numeric UID of the new user
333         :param gidnumber: RFC2307 Unix primary GID of the new user
334         :param gecos: RFC2307 Unix GECOS field of the new user
335         :param loginshell: RFC2307 Unix login shell of the new user
336         :param uid: RFC2307 Unix username of the new user
337         :param nisdomain: RFC2307 Unix NIS domain of the new user
338         :param unixhome: RFC2307 Unix home directory of the new user
339         :param smartcard_required: set the UF_SMARTCARD_REQUIRED bit of the new user
340         """
341
342         displayname = ""
343         if givenname is not None:
344             displayname += givenname
345
346         if initials is not None:
347             displayname += ' %s.' % initials
348
349         if surname is not None:
350             displayname += ' %s' % surname
351
352         cn = username
353         if useusernameascn is None and displayname is not "":
354             cn = displayname
355
356         user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
357
358         dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
359         user_principal_name = "%s@%s" % (username, dnsdomain)
360         # The new user record. Note the reliance on the SAMLDB module which
361         # fills in the default informations
362         ldbmessage = {"dn": user_dn,
363                       "sAMAccountName": username,
364                       "userPrincipalName": user_principal_name,
365                       "objectClass": "user"}
366
367         if smartcard_required:
368             ldbmessage["userAccountControl"] = str(dsdb.UF_NORMAL_ACCOUNT|dsdb.UF_SMARTCARD_REQUIRED)
369             setpassword = False
370
371         if surname is not None:
372             ldbmessage["sn"] = surname
373
374         if givenname is not None:
375             ldbmessage["givenName"] = givenname
376
377         if displayname is not "":
378             ldbmessage["displayName"] = displayname
379             ldbmessage["name"] = displayname
380
381         if initials is not None:
382             ldbmessage["initials"] = '%s.' % initials
383
384         if profilepath is not None:
385             ldbmessage["profilePath"] = profilepath
386
387         if scriptpath is not None:
388             ldbmessage["scriptPath"] = scriptpath
389
390         if homedrive is not None:
391             ldbmessage["homeDrive"] = homedrive
392
393         if homedirectory is not None:
394             ldbmessage["homeDirectory"] = homedirectory
395
396         if jobtitle is not None:
397             ldbmessage["title"] = jobtitle
398
399         if department is not None:
400             ldbmessage["department"] = department
401
402         if company is not None:
403             ldbmessage["company"] = company
404
405         if description is not None:
406             ldbmessage["description"] = description
407
408         if mailaddress is not None:
409             ldbmessage["mail"] = mailaddress
410
411         if internetaddress is not None:
412             ldbmessage["wWWHomePage"] = internetaddress
413
414         if telephonenumber is not None:
415             ldbmessage["telephoneNumber"] = telephonenumber
416
417         if physicaldeliveryoffice is not None:
418             ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
419
420         if sd is not None:
421             ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
422
423         ldbmessage2 = None
424         if any(map(lambda b: b is not None, (uid, uidnumber, gidnumber, gecos,
425                 loginshell, nisdomain, unixhome))):
426             ldbmessage2 = ldb.Message()
427             ldbmessage2.dn = ldb.Dn(self, user_dn)
428             if uid is not None:
429                 ldbmessage2["uid"] = ldb.MessageElement(str(uid), ldb.FLAG_MOD_REPLACE, 'uid')
430             if uidnumber is not None:
431                 ldbmessage2["uidNumber"] = ldb.MessageElement(str(uidnumber), ldb.FLAG_MOD_REPLACE, 'uidNumber')
432             if gidnumber is not None:
433                 ldbmessage2["gidNumber"] = ldb.MessageElement(str(gidnumber), ldb.FLAG_MOD_REPLACE, 'gidNumber')
434             if gecos is not None:
435                 ldbmessage2["gecos"] = ldb.MessageElement(str(gecos), ldb.FLAG_MOD_REPLACE, 'gecos')
436             if loginshell is not None:
437                 ldbmessage2["loginShell"] = ldb.MessageElement(str(loginshell), ldb.FLAG_MOD_REPLACE, 'loginShell')
438             if unixhome is not None:
439                 ldbmessage2["unixHomeDirectory"] = ldb.MessageElement(
440                     str(unixhome), ldb.FLAG_MOD_REPLACE, 'unixHomeDirectory')
441             if nisdomain is not None:
442                 ldbmessage2["msSFU30NisDomain"] = ldb.MessageElement(
443                     str(nisdomain), ldb.FLAG_MOD_REPLACE, 'msSFU30NisDomain')
444                 ldbmessage2["msSFU30Name"] = ldb.MessageElement(
445                     str(username), ldb.FLAG_MOD_REPLACE, 'msSFU30Name')
446                 ldbmessage2["unixUserPassword"] = ldb.MessageElement(
447                     'ABCD!efgh12345$67890', ldb.FLAG_MOD_REPLACE,
448                     'unixUserPassword')
449
450         self.transaction_start()
451         try:
452             self.add(ldbmessage)
453             if ldbmessage2:
454                 self.modify(ldbmessage2)
455
456             # Sets the password for it
457             if setpassword:
458                 self.setpassword("(samAccountName=%s)" % ldb.binary_encode(username), password,
459                                  force_password_change_at_next_login_req)
460         except:
461             self.transaction_cancel()
462             raise
463         else:
464             self.transaction_commit()
465
466
467     def deleteuser(self, username):
468         """Deletes a user
469
470         :param username: Name of the target user
471         """
472
473         filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
474         self.transaction_start()
475         try:
476             target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
477                                  expression=filter, attrs=[])
478             if len(target) == 0:
479                 raise Exception('Unable to find user "%s"' % username)
480             assert(len(target) == 1)
481             self.delete(target[0].dn)
482         except:
483             self.transaction_cancel()
484             raise
485         else:
486             self.transaction_commit()
487
488     def setpassword(self, search_filter, password,
489             force_change_at_next_login=False, username=None):
490         """Sets the password for a user
491
492         :param search_filter: LDAP filter to find the user (eg
493             samccountname=name)
494         :param password: Password for the user
495         :param force_change_at_next_login: Force password change
496         """
497         self.transaction_start()
498         try:
499             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
500                               expression=search_filter, attrs=[])
501             if len(res) == 0:
502                 raise Exception('Unable to find user "%s"' % (username or search_filter))
503             if len(res) > 1:
504                 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
505             user_dn = res[0].dn
506             pw = unicode('"' + password.encode('utf-8') + '"', 'utf-8').encode('utf-16-le')
507             setpw = """
508 dn: %s
509 changetype: modify
510 replace: unicodePwd
511 unicodePwd:: %s
512 """ % (user_dn, base64.b64encode(pw))
513
514             self.modify_ldif(setpw)
515
516             if force_change_at_next_login:
517                 self.force_password_change_at_next_login(
518                   "(distinguishedName=" + str(user_dn) + ")")
519
520             #  modify the userAccountControl to remove the disabled bit
521             self.enable_account(search_filter)
522         except:
523             self.transaction_cancel()
524             raise
525         else:
526             self.transaction_commit()
527
528     def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
529         """Sets the account expiry for a user
530
531         :param search_filter: LDAP filter to find the user (eg
532             samaccountname=name)
533         :param expiry_seconds: expiry time from now in seconds
534         :param no_expiry_req: if set, then don't expire password
535         """
536         self.transaction_start()
537         try:
538             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
539                           expression=search_filter,
540                           attrs=["userAccountControl", "accountExpires"])
541             if len(res) == 0:
542                 raise Exception('Unable to find user "%s"' % search_filter)
543             assert(len(res) == 1)
544             user_dn = res[0].dn
545
546             userAccountControl = int(res[0]["userAccountControl"][0])
547             accountExpires     = int(res[0]["accountExpires"][0])
548             if no_expiry_req:
549                 userAccountControl = userAccountControl | 0x10000
550                 accountExpires = 0
551             else:
552                 userAccountControl = userAccountControl & ~0x10000
553                 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
554
555             setexp = """
556 dn: %s
557 changetype: modify
558 replace: userAccountControl
559 userAccountControl: %u
560 replace: accountExpires
561 accountExpires: %u
562 """ % (user_dn, userAccountControl, accountExpires)
563
564             self.modify_ldif(setexp)
565         except:
566             self.transaction_cancel()
567             raise
568         else:
569             self.transaction_commit()
570
571     def set_domain_sid(self, sid):
572         """Change the domain SID used by this LDB.
573
574         :param sid: The new domain sid to use.
575         """
576         dsdb._samdb_set_domain_sid(self, sid)
577
578     def get_domain_sid(self):
579         """Read the domain SID used by this LDB. """
580         return dsdb._samdb_get_domain_sid(self)
581
582     domain_sid = property(get_domain_sid, set_domain_sid,
583         "SID for the domain")
584
585     def set_invocation_id(self, invocation_id):
586         """Set the invocation id for this SamDB handle.
587
588         :param invocation_id: GUID of the invocation id.
589         """
590         dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
591
592     def get_invocation_id(self):
593         """Get the invocation_id id"""
594         return dsdb._samdb_ntds_invocation_id(self)
595
596     invocation_id = property(get_invocation_id, set_invocation_id,
597         "Invocation ID GUID")
598
599     def get_oid_from_attid(self, attid):
600         return dsdb._dsdb_get_oid_from_attid(self, attid)
601
602     def get_attid_from_lDAPDisplayName(self, ldap_display_name,
603             is_schema_nc=False):
604         '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
605         return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
606             ldap_display_name, is_schema_nc)
607
608     def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
609         '''return the syntax OID for a LDAP attribute as a string'''
610         return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
611
612     def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
613         '''return the systemFlags for a LDAP attribute as a integer'''
614         return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
615
616     def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
617         '''return the linkID for a LDAP attribute as a integer'''
618         return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
619
620     def get_lDAPDisplayName_by_attid(self, attid):
621         '''return the lDAPDisplayName from an integer DRS attribute ID'''
622         return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
623
624     def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
625         '''return the attribute name of the corresponding backlink from the name
626         of a forward link attribute. If there is no backlink return None'''
627         return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
628
629     def set_ntds_settings_dn(self, ntds_settings_dn):
630         """Set the NTDS Settings DN, as would be returned on the dsServiceName
631         rootDSE attribute.
632
633         This allows the DN to be set before the database fully exists
634
635         :param ntds_settings_dn: The new DN to use
636         """
637         dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
638
639     def get_ntds_GUID(self):
640         """Get the NTDS objectGUID"""
641         return dsdb._samdb_ntds_objectGUID(self)
642
643     def server_site_name(self):
644         """Get the server site name"""
645         return dsdb._samdb_server_site_name(self)
646
647     def host_dns_name(self):
648         """return the DNS name of this host"""
649         res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
650         return res[0]['dNSHostName'][0]
651
652     def domain_dns_name(self):
653         """return the DNS name of the domain root"""
654         domain_dn = self.get_default_basedn()
655         return domain_dn.canonical_str().split('/')[0]
656
657     def forest_dns_name(self):
658         """return the DNS name of the forest root"""
659         forest_dn = self.get_root_basedn()
660         return forest_dn.canonical_str().split('/')[0]
661
662     def load_partition_usn(self, base_dn):
663         return dsdb._dsdb_load_partition_usn(self, base_dn)
664
665     def set_schema(self, schema, write_indices_and_attributes=True):
666         self.set_schema_from_ldb(schema.ldb, write_indices_and_attributes=write_indices_and_attributes)
667
668     def set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes=True):
669         dsdb._dsdb_set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes)
670
671     def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
672         '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
673         return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
674
675     def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
676         '''normalise a list of attribute values'''
677         return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
678
679     def get_attribute_from_attid(self, attid):
680         """ Get from an attid the associated attribute
681
682         :param attid: The attribute id for searched attribute
683         :return: The name of the attribute associated with this id
684         """
685         if len(self.hash_oid_name.keys()) == 0:
686             self._populate_oid_attid()
687         if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
688             return self.hash_oid_name[self.get_oid_from_attid(attid)]
689         else:
690             return None
691
692     def _populate_oid_attid(self):
693         """Populate the hash hash_oid_name.
694
695         This hash contains the oid of the attribute as a key and
696         its display name as a value
697         """
698         self.hash_oid_name = {}
699         res = self.search(expression="objectClass=attributeSchema",
700                            controls=["search_options:1:2"],
701                            attrs=["attributeID",
702                            "lDAPDisplayName"])
703         if len(res) > 0:
704             for e in res:
705                 strDisplay = str(e.get("lDAPDisplayName"))
706                 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
707
708     def get_attribute_replmetadata_version(self, dn, att):
709         """Get the version field trom the replPropertyMetaData for
710         the given field
711
712         :param dn: The on which we want to get the version
713         :param att: The name of the attribute
714         :return: The value of the version field in the replPropertyMetaData
715             for the given attribute. None if the attribute is not replicated
716         """
717
718         res = self.search(expression="distinguishedName=%s" % dn,
719                             scope=ldb.SCOPE_SUBTREE,
720                             controls=["search_options:1:2"],
721                             attrs=["replPropertyMetaData"])
722         if len(res) == 0:
723             return None
724
725         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
726                             str(res[0]["replPropertyMetaData"]))
727         ctr = repl.ctr
728         if len(self.hash_oid_name.keys()) == 0:
729             self._populate_oid_attid()
730         for o in ctr.array:
731             # Search for Description
732             att_oid = self.get_oid_from_attid(o.attid)
733             if self.hash_oid_name.has_key(att_oid) and\
734                att.lower() == self.hash_oid_name[att_oid].lower():
735                 return o.version
736         return None
737
738     def set_attribute_replmetadata_version(self, dn, att, value,
739             addifnotexist=False):
740         res = self.search(expression="distinguishedName=%s" % dn,
741                             scope=ldb.SCOPE_SUBTREE,
742                             controls=["search_options:1:2"],
743                             attrs=["replPropertyMetaData"])
744         if len(res) == 0:
745             return None
746
747         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
748                             str(res[0]["replPropertyMetaData"]))
749         ctr = repl.ctr
750         now = samba.unix2nttime(int(time.time()))
751         found = False
752         if len(self.hash_oid_name.keys()) == 0:
753             self._populate_oid_attid()
754         for o in ctr.array:
755             # Search for Description
756             att_oid = self.get_oid_from_attid(o.attid)
757             if self.hash_oid_name.has_key(att_oid) and\
758                att.lower() == self.hash_oid_name[att_oid].lower():
759                 found = True
760                 seq = self.sequence_number(ldb.SEQ_NEXT)
761                 o.version = value
762                 o.originating_change_time = now
763                 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
764                 o.originating_usn = seq
765                 o.local_usn = seq
766
767         if not found and addifnotexist and len(ctr.array) >0:
768             o2 = drsblobs.replPropertyMetaData1()
769             o2.attid = 589914
770             att_oid = self.get_oid_from_attid(o2.attid)
771             seq = self.sequence_number(ldb.SEQ_NEXT)
772             o2.version = value
773             o2.originating_change_time = now
774             o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
775             o2.originating_usn = seq
776             o2.local_usn = seq
777             found = True
778             tab = ctr.array
779             tab.append(o2)
780             ctr.count = ctr.count + 1
781             ctr.array = tab
782
783         if found :
784             replBlob = ndr_pack(repl)
785             msg = ldb.Message()
786             msg.dn = res[0].dn
787             msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
788                                                 ldb.FLAG_MOD_REPLACE,
789                                                 "replPropertyMetaData")
790             self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
791
792     def write_prefixes_from_schema(self):
793         dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
794
795     def get_partitions_dn(self):
796         return dsdb._dsdb_get_partitions_dn(self)
797
798     def get_nc_root(self, dn):
799         return dsdb._dsdb_get_nc_root(self, dn)
800
801     def get_wellknown_dn(self, nc_root, wkguid):
802         h_nc = self.hash_well_known.get(str(nc_root))
803         dn = None
804         if h_nc is not None:
805             dn = h_nc.get(wkguid)
806         if dn is None:
807             dn = dsdb._dsdb_get_wellknown_dn(self, nc_root, wkguid)
808             if dn is None:
809                 return dn
810             if h_nc is None:
811                 self.hash_well_known[str(nc_root)] = {}
812                 h_nc = self.hash_well_known[str(nc_root)]
813             h_nc[wkguid] = dn
814         return dn
815
816     def set_minPwdAge(self, value):
817         m = ldb.Message()
818         m.dn = ldb.Dn(self, self.domain_dn())
819         m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
820         self.modify(m)
821
822     def get_minPwdAge(self):
823         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
824         if len(res) == 0:
825             return None
826         elif not "minPwdAge" in res[0]:
827             return None
828         else:
829             return res[0]["minPwdAge"][0]
830
831     def set_minPwdLength(self, value):
832         m = ldb.Message()
833         m.dn = ldb.Dn(self, self.domain_dn())
834         m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
835         self.modify(m)
836
837     def get_minPwdLength(self):
838         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
839         if len(res) == 0:
840             return None
841         elif not "minPwdLength" in res[0]:
842             return None
843         else:
844             return res[0]["minPwdLength"][0]
845
846     def set_pwdProperties(self, value):
847         m = ldb.Message()
848         m.dn = ldb.Dn(self, self.domain_dn())
849         m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
850         self.modify(m)
851
852     def get_pwdProperties(self):
853         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
854         if len(res) == 0:
855             return None
856         elif not "pwdProperties" in res[0]:
857             return None
858         else:
859             return res[0]["pwdProperties"][0]
860
861     def set_dsheuristics(self, dsheuristics):
862         m = ldb.Message()
863         m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
864                       % self.get_config_basedn().get_linearized())
865         if dsheuristics is not None:
866             m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
867                 ldb.FLAG_MOD_REPLACE, "dSHeuristics")
868         else:
869             m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
870                 "dSHeuristics")
871         self.modify(m)
872
873     def get_dsheuristics(self):
874         res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
875                           % self.get_config_basedn().get_linearized(),
876                           scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
877         if len(res) == 0:
878             dsheuristics = None
879         elif "dSHeuristics" in res[0]:
880             dsheuristics = res[0]["dSHeuristics"][0]
881         else:
882             dsheuristics = None
883
884         return dsheuristics
885
886     def create_ou(self, ou_dn, description=None, name=None, sd=None):
887         """Creates an organizationalUnit object
888         :param ou_dn: dn of the new object
889         :param description: description attribute
890         :param name: name atttribute
891         :param sd: security descriptor of the object, can be
892         an SDDL string or security.descriptor type
893         """
894         m = {"dn": ou_dn,
895              "objectClass": "organizationalUnit"}
896
897         if description:
898             m["description"] = description
899         if name:
900             m["name"] = name
901
902         if sd:
903             m["nTSecurityDescriptor"] = ndr_pack(sd)
904         self.add(m)
905
906     def sequence_number(self, seq_type):
907         """Returns the value of the sequence number according to the requested type
908         :param seq_type: type of sequence number
909          """
910         self.transaction_start()
911         try:
912             seq = super(SamDB, self).sequence_number(seq_type)
913         except:
914             self.transaction_cancel()
915             raise
916         else:
917             self.transaction_commit()
918         return seq
919
920     def get_dsServiceName(self):
921         '''get the NTDS DN from the rootDSE'''
922         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
923         return res[0]["dsServiceName"][0]
924
925     def get_serverName(self):
926         '''get the server DN from the rootDSE'''
927         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
928         return res[0]["serverName"][0]
929
930     def dns_lookup(self, dns_name):
931         '''Do a DNS lookup in the database, returns the NDR database structures'''
932         return dsdb_dns.lookup(self, dns_name)
933
934     def dns_extract(self, el):
935         '''Return the NDR database structures from a dnsRecord element'''
936         return dsdb_dns.extract(el)
937
938     def dns_replace(self, dns_name, new_records):
939         '''Do a DNS modification on the database, sets the NDR database
940         structures on a DNS name
941         '''
942         return dsdb_dns.replace(self, dns_name, new_records)
943
944     def dns_replace_by_dn(self, dn, new_records):
945         '''Do a DNS modification on the database, sets the NDR database
946         structures on a LDB DN
947
948         This routine is important because if the last record on the DN
949         is removed, this routine will put a tombstone in the record.
950         '''
951         return dsdb_dns.replace_by_dn(self, dn, new_records)
952
953     def garbage_collect_tombstones(self, dn, current_time,
954                                    tombstone_lifetime=None):
955         '''garbage_collect_tombstones(lp, samdb, [dn], current_time, tombstone_lifetime)
956         -> (num_objects_expunged, num_links_expunged)'''
957
958
959         if tombstone_lifetime is None:
960             return dsdb._dsdb_garbage_collect_tombstones(self, dn,
961                                                          current_time)
962         else:
963             return dsdb._dsdb_garbage_collect_tombstones(self, dn,
964                                                          current_time,
965                                                          tombstone_lifetime)
966
967     def create_own_rid_set(self):
968         '''create a RID set for this DSA'''
969         return dsdb._dsdb_create_own_rid_set(self)
970
971     def allocate_rid(self):
972         '''return a new RID from the RID Pool on this DSA'''
973         return dsdb._dsdb_allocate_rid(self)