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