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