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