python samdb.newuser(): use user DN not samaccountname to set password
[sfrench/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(("(distinguishedName=%s)" %
463                                   ldb.binary_encode(user_dn)),
464                                  password,
465                                  force_password_change_at_next_login_req)
466         except:
467             self.transaction_cancel()
468             raise
469         else:
470             self.transaction_commit()
471
472
473     def deleteuser(self, username):
474         """Deletes a user
475
476         :param username: Name of the target user
477         """
478
479         filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
480         self.transaction_start()
481         try:
482             target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
483                                  expression=filter, attrs=[])
484             if len(target) == 0:
485                 raise Exception('Unable to find user "%s"' % username)
486             assert(len(target) == 1)
487             self.delete(target[0].dn)
488         except:
489             self.transaction_cancel()
490             raise
491         else:
492             self.transaction_commit()
493
494     def setpassword(self, search_filter, password,
495             force_change_at_next_login=False, username=None):
496         """Sets the password for a user
497
498         :param search_filter: LDAP filter to find the user (eg
499             samccountname=name)
500         :param password: Password for the user
501         :param force_change_at_next_login: Force password change
502         """
503         self.transaction_start()
504         try:
505             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
506                               expression=search_filter, attrs=[])
507             if len(res) == 0:
508                 raise Exception('Unable to find user "%s"' % (username or search_filter))
509             if len(res) > 1:
510                 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
511             user_dn = res[0].dn
512             pw = unicode('"' + password.encode('utf-8') + '"', 'utf-8').encode('utf-16-le')
513             setpw = """
514 dn: %s
515 changetype: modify
516 replace: unicodePwd
517 unicodePwd:: %s
518 """ % (user_dn, base64.b64encode(pw))
519
520             self.modify_ldif(setpw)
521
522             if force_change_at_next_login:
523                 self.force_password_change_at_next_login(
524                   "(distinguishedName=" + str(user_dn) + ")")
525
526             #  modify the userAccountControl to remove the disabled bit
527             self.enable_account(search_filter)
528         except:
529             self.transaction_cancel()
530             raise
531         else:
532             self.transaction_commit()
533
534     def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
535         """Sets the account expiry for a user
536
537         :param search_filter: LDAP filter to find the user (eg
538             samaccountname=name)
539         :param expiry_seconds: expiry time from now in seconds
540         :param no_expiry_req: if set, then don't expire password
541         """
542         self.transaction_start()
543         try:
544             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
545                           expression=search_filter,
546                           attrs=["userAccountControl", "accountExpires"])
547             if len(res) == 0:
548                 raise Exception('Unable to find user "%s"' % search_filter)
549             assert(len(res) == 1)
550             user_dn = res[0].dn
551
552             userAccountControl = int(res[0]["userAccountControl"][0])
553             accountExpires     = int(res[0]["accountExpires"][0])
554             if no_expiry_req:
555                 userAccountControl = userAccountControl | 0x10000
556                 accountExpires = 0
557             else:
558                 userAccountControl = userAccountControl & ~0x10000
559                 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
560
561             setexp = """
562 dn: %s
563 changetype: modify
564 replace: userAccountControl
565 userAccountControl: %u
566 replace: accountExpires
567 accountExpires: %u
568 """ % (user_dn, userAccountControl, accountExpires)
569
570             self.modify_ldif(setexp)
571         except:
572             self.transaction_cancel()
573             raise
574         else:
575             self.transaction_commit()
576
577     def set_domain_sid(self, sid):
578         """Change the domain SID used by this LDB.
579
580         :param sid: The new domain sid to use.
581         """
582         dsdb._samdb_set_domain_sid(self, sid)
583
584     def get_domain_sid(self):
585         """Read the domain SID used by this LDB. """
586         return dsdb._samdb_get_domain_sid(self)
587
588     domain_sid = property(get_domain_sid, set_domain_sid,
589         "SID for the domain")
590
591     def set_invocation_id(self, invocation_id):
592         """Set the invocation id for this SamDB handle.
593
594         :param invocation_id: GUID of the invocation id.
595         """
596         dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
597
598     def get_invocation_id(self):
599         """Get the invocation_id id"""
600         return dsdb._samdb_ntds_invocation_id(self)
601
602     invocation_id = property(get_invocation_id, set_invocation_id,
603         "Invocation ID GUID")
604
605     def get_oid_from_attid(self, attid):
606         return dsdb._dsdb_get_oid_from_attid(self, attid)
607
608     def get_attid_from_lDAPDisplayName(self, ldap_display_name,
609             is_schema_nc=False):
610         '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
611         return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
612             ldap_display_name, is_schema_nc)
613
614     def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
615         '''return the syntax OID for a LDAP attribute as a string'''
616         return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
617
618     def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
619         '''return the systemFlags for a LDAP attribute as a integer'''
620         return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
621
622     def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
623         '''return the linkID for a LDAP attribute as a integer'''
624         return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
625
626     def get_lDAPDisplayName_by_attid(self, attid):
627         '''return the lDAPDisplayName from an integer DRS attribute ID'''
628         return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
629
630     def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
631         '''return the attribute name of the corresponding backlink from the name
632         of a forward link attribute. If there is no backlink return None'''
633         return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
634
635     def set_ntds_settings_dn(self, ntds_settings_dn):
636         """Set the NTDS Settings DN, as would be returned on the dsServiceName
637         rootDSE attribute.
638
639         This allows the DN to be set before the database fully exists
640
641         :param ntds_settings_dn: The new DN to use
642         """
643         dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
644
645     def get_ntds_GUID(self):
646         """Get the NTDS objectGUID"""
647         return dsdb._samdb_ntds_objectGUID(self)
648
649     def server_site_name(self):
650         """Get the server site name"""
651         return dsdb._samdb_server_site_name(self)
652
653     def host_dns_name(self):
654         """return the DNS name of this host"""
655         res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
656         return res[0]['dNSHostName'][0]
657
658     def domain_dns_name(self):
659         """return the DNS name of the domain root"""
660         domain_dn = self.get_default_basedn()
661         return domain_dn.canonical_str().split('/')[0]
662
663     def forest_dns_name(self):
664         """return the DNS name of the forest root"""
665         forest_dn = self.get_root_basedn()
666         return forest_dn.canonical_str().split('/')[0]
667
668     def load_partition_usn(self, base_dn):
669         return dsdb._dsdb_load_partition_usn(self, base_dn)
670
671     def set_schema(self, schema, write_indices_and_attributes=True):
672         self.set_schema_from_ldb(schema.ldb, write_indices_and_attributes=write_indices_and_attributes)
673
674     def set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes=True):
675         dsdb._dsdb_set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes)
676
677     def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
678         '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
679         return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
680
681     def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
682         '''normalise a list of attribute values'''
683         return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
684
685     def get_attribute_from_attid(self, attid):
686         """ Get from an attid the associated attribute
687
688         :param attid: The attribute id for searched attribute
689         :return: The name of the attribute associated with this id
690         """
691         if len(self.hash_oid_name.keys()) == 0:
692             self._populate_oid_attid()
693         if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
694             return self.hash_oid_name[self.get_oid_from_attid(attid)]
695         else:
696             return None
697
698     def _populate_oid_attid(self):
699         """Populate the hash hash_oid_name.
700
701         This hash contains the oid of the attribute as a key and
702         its display name as a value
703         """
704         self.hash_oid_name = {}
705         res = self.search(expression="objectClass=attributeSchema",
706                            controls=["search_options:1:2"],
707                            attrs=["attributeID",
708                            "lDAPDisplayName"])
709         if len(res) > 0:
710             for e in res:
711                 strDisplay = str(e.get("lDAPDisplayName"))
712                 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
713
714     def get_attribute_replmetadata_version(self, dn, att):
715         """Get the version field trom the replPropertyMetaData for
716         the given field
717
718         :param dn: The on which we want to get the version
719         :param att: The name of the attribute
720         :return: The value of the version field in the replPropertyMetaData
721             for the given attribute. None if the attribute is not replicated
722         """
723
724         res = self.search(expression="distinguishedName=%s" % dn,
725                             scope=ldb.SCOPE_SUBTREE,
726                             controls=["search_options:1:2"],
727                             attrs=["replPropertyMetaData"])
728         if len(res) == 0:
729             return None
730
731         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
732                             str(res[0]["replPropertyMetaData"]))
733         ctr = repl.ctr
734         if len(self.hash_oid_name.keys()) == 0:
735             self._populate_oid_attid()
736         for o in ctr.array:
737             # Search for Description
738             att_oid = self.get_oid_from_attid(o.attid)
739             if self.hash_oid_name.has_key(att_oid) and\
740                att.lower() == self.hash_oid_name[att_oid].lower():
741                 return o.version
742         return None
743
744     def set_attribute_replmetadata_version(self, dn, att, value,
745             addifnotexist=False):
746         res = self.search(expression="distinguishedName=%s" % dn,
747                             scope=ldb.SCOPE_SUBTREE,
748                             controls=["search_options:1:2"],
749                             attrs=["replPropertyMetaData"])
750         if len(res) == 0:
751             return None
752
753         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
754                             str(res[0]["replPropertyMetaData"]))
755         ctr = repl.ctr
756         now = samba.unix2nttime(int(time.time()))
757         found = False
758         if len(self.hash_oid_name.keys()) == 0:
759             self._populate_oid_attid()
760         for o in ctr.array:
761             # Search for Description
762             att_oid = self.get_oid_from_attid(o.attid)
763             if self.hash_oid_name.has_key(att_oid) and\
764                att.lower() == self.hash_oid_name[att_oid].lower():
765                 found = True
766                 seq = self.sequence_number(ldb.SEQ_NEXT)
767                 o.version = value
768                 o.originating_change_time = now
769                 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
770                 o.originating_usn = seq
771                 o.local_usn = seq
772
773         if not found and addifnotexist and len(ctr.array) >0:
774             o2 = drsblobs.replPropertyMetaData1()
775             o2.attid = 589914
776             att_oid = self.get_oid_from_attid(o2.attid)
777             seq = self.sequence_number(ldb.SEQ_NEXT)
778             o2.version = value
779             o2.originating_change_time = now
780             o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
781             o2.originating_usn = seq
782             o2.local_usn = seq
783             found = True
784             tab = ctr.array
785             tab.append(o2)
786             ctr.count = ctr.count + 1
787             ctr.array = tab
788
789         if found :
790             replBlob = ndr_pack(repl)
791             msg = ldb.Message()
792             msg.dn = res[0].dn
793             msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
794                                                 ldb.FLAG_MOD_REPLACE,
795                                                 "replPropertyMetaData")
796             self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
797
798     def write_prefixes_from_schema(self):
799         dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
800
801     def get_partitions_dn(self):
802         return dsdb._dsdb_get_partitions_dn(self)
803
804     def get_nc_root(self, dn):
805         return dsdb._dsdb_get_nc_root(self, dn)
806
807     def get_wellknown_dn(self, nc_root, wkguid):
808         h_nc = self.hash_well_known.get(str(nc_root))
809         dn = None
810         if h_nc is not None:
811             dn = h_nc.get(wkguid)
812         if dn is None:
813             dn = dsdb._dsdb_get_wellknown_dn(self, nc_root, wkguid)
814             if dn is None:
815                 return dn
816             if h_nc is None:
817                 self.hash_well_known[str(nc_root)] = {}
818                 h_nc = self.hash_well_known[str(nc_root)]
819             h_nc[wkguid] = dn
820         return dn
821
822     def set_minPwdAge(self, value):
823         m = ldb.Message()
824         m.dn = ldb.Dn(self, self.domain_dn())
825         m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
826         self.modify(m)
827
828     def get_minPwdAge(self):
829         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
830         if len(res) == 0:
831             return None
832         elif not "minPwdAge" in res[0]:
833             return None
834         else:
835             return res[0]["minPwdAge"][0]
836
837     def set_maxPwdAge(self, value):
838         m = ldb.Message()
839         m.dn = ldb.Dn(self, self.domain_dn())
840         m["maxPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "maxPwdAge")
841         self.modify(m)
842
843
844     def get_maxPwdAge(self):
845         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["maxPwdAge"])
846         if len(res) == 0:
847             return None
848         elif not "maxPwdAge" in res[0]:
849             return None
850         else:
851             return res[0]["maxPwdAge"][0]
852
853
854
855     def set_minPwdLength(self, value):
856         m = ldb.Message()
857         m.dn = ldb.Dn(self, self.domain_dn())
858         m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
859         self.modify(m)
860
861     def get_minPwdLength(self):
862         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
863         if len(res) == 0:
864             return None
865         elif not "minPwdLength" in res[0]:
866             return None
867         else:
868             return res[0]["minPwdLength"][0]
869
870     def set_pwdProperties(self, value):
871         m = ldb.Message()
872         m.dn = ldb.Dn(self, self.domain_dn())
873         m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
874         self.modify(m)
875
876     def get_pwdProperties(self):
877         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
878         if len(res) == 0:
879             return None
880         elif not "pwdProperties" in res[0]:
881             return None
882         else:
883             return res[0]["pwdProperties"][0]
884
885     def set_dsheuristics(self, dsheuristics):
886         m = ldb.Message()
887         m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
888                       % self.get_config_basedn().get_linearized())
889         if dsheuristics is not None:
890             m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
891                 ldb.FLAG_MOD_REPLACE, "dSHeuristics")
892         else:
893             m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
894                 "dSHeuristics")
895         self.modify(m)
896
897     def get_dsheuristics(self):
898         res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
899                           % self.get_config_basedn().get_linearized(),
900                           scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
901         if len(res) == 0:
902             dsheuristics = None
903         elif "dSHeuristics" in res[0]:
904             dsheuristics = res[0]["dSHeuristics"][0]
905         else:
906             dsheuristics = None
907
908         return dsheuristics
909
910     def create_ou(self, ou_dn, description=None, name=None, sd=None):
911         """Creates an organizationalUnit object
912         :param ou_dn: dn of the new object
913         :param description: description attribute
914         :param name: name atttribute
915         :param sd: security descriptor of the object, can be
916         an SDDL string or security.descriptor type
917         """
918         m = {"dn": ou_dn,
919              "objectClass": "organizationalUnit"}
920
921         if description:
922             m["description"] = description
923         if name:
924             m["name"] = name
925
926         if sd:
927             m["nTSecurityDescriptor"] = ndr_pack(sd)
928         self.add(m)
929
930     def sequence_number(self, seq_type):
931         """Returns the value of the sequence number according to the requested type
932         :param seq_type: type of sequence number
933          """
934         self.transaction_start()
935         try:
936             seq = super(SamDB, self).sequence_number(seq_type)
937         except:
938             self.transaction_cancel()
939             raise
940         else:
941             self.transaction_commit()
942         return seq
943
944     def get_dsServiceName(self):
945         '''get the NTDS DN from the rootDSE'''
946         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
947         return res[0]["dsServiceName"][0]
948
949     def get_serverName(self):
950         '''get the server DN from the rootDSE'''
951         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
952         return res[0]["serverName"][0]
953
954     def dns_lookup(self, dns_name, dns_partition=None):
955         '''Do a DNS lookup in the database, returns the NDR database structures'''
956         if dns_partition is None:
957             return dsdb_dns.lookup(self, dns_name)
958         else:
959             return dsdb_dns.lookup(self, dns_name,
960                                    dns_partition=dns_partition)
961
962     def dns_extract(self, el):
963         '''Return the NDR database structures from a dnsRecord element'''
964         return dsdb_dns.extract(self, el)
965
966     def dns_replace(self, dns_name, new_records):
967         '''Do a DNS modification on the database, sets the NDR database
968         structures on a DNS name
969         '''
970         return dsdb_dns.replace(self, dns_name, new_records)
971
972     def dns_replace_by_dn(self, dn, new_records):
973         '''Do a DNS modification on the database, sets the NDR database
974         structures on a LDB DN
975
976         This routine is important because if the last record on the DN
977         is removed, this routine will put a tombstone in the record.
978         '''
979         return dsdb_dns.replace_by_dn(self, dn, new_records)
980
981     def garbage_collect_tombstones(self, dn, current_time,
982                                    tombstone_lifetime=None):
983         '''garbage_collect_tombstones(lp, samdb, [dn], current_time, tombstone_lifetime)
984         -> (num_objects_expunged, num_links_expunged)'''
985
986
987         if tombstone_lifetime is None:
988             return dsdb._dsdb_garbage_collect_tombstones(self, dn,
989                                                          current_time)
990         else:
991             return dsdb._dsdb_garbage_collect_tombstones(self, dn,
992                                                          current_time,
993                                                          tombstone_lifetime)
994
995     def create_own_rid_set(self):
996         '''create a RID set for this DSA'''
997         return dsdb._dsdb_create_own_rid_set(self)
998
999     def allocate_rid(self):
1000         '''return a new RID from the RID Pool on this DSA'''
1001         return dsdb._dsdb_allocate_rid(self)
1002
1003     def normalize_dn_in_domain(self, dn):
1004         '''return a new DN expanded by adding the domain DN
1005
1006         If the dn is already a child of the domain DN, just
1007         return it as-is.
1008
1009         :param dn: relative dn
1010         '''
1011         domain_dn = ldb.Dn(self, self.domain_dn())
1012
1013         if isinstance(dn, ldb.Dn):
1014             dn = str(dn)
1015
1016         full_dn = ldb.Dn(self, dn)
1017         if not full_dn.is_child_of(domain_dn):
1018             full_dn.add_base(domain_dn)
1019         return full_dn