1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
3 # Copyright (C) Matthias Dieter Wallnoefer 2009
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
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.
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.
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/>.
23 """Convenience functions for using the SAM."""
31 from samba
import dsdb
, dsdb_dns
32 from samba
.ndr
import ndr_unpack
, ndr_pack
33 from samba
.dcerpc
import drsblobs
, misc
34 from samba
.common
import normalise_int32
35 from samba
.common
import get_bytes
, cmp
36 from samba
.dcerpc
import security
37 from samba
import is_ad_dc_built
40 __docformat__
= "restructuredText"
43 def get_default_backend_store():
46 class SamDBError(Exception):
49 class SamDBNotFoundError(SamDBError
):
52 class SamDB(samba
.Ldb
):
53 """The SAM database."""
58 def __init__(self
, url
=None, lp
=None, modules_dir
=None, session_info
=None,
59 credentials
=None, flags
=ldb
.FLG_DONT_CREATE_DB
,
60 options
=None, global_schema
=True,
61 auto_connect
=True, am_rodc
=None):
65 elif url
is None and lp
is not None:
70 super(SamDB
, self
).__init
__(url
=url
, lp
=lp
, modules_dir
=modules_dir
,
71 session_info
=session_info
, credentials
=credentials
, flags
=flags
,
75 dsdb
._dsdb
_set
_global
_schema
(self
)
77 if am_rodc
is not None:
78 dsdb
._dsdb
_set
_am
_rodc
(self
, am_rodc
)
80 def connect(self
, url
=None, flags
=0, options
=None):
81 '''connect to the database'''
82 if self
.lp
is not None and not os
.path
.exists(url
):
83 url
= self
.lp
.private_path(url
)
86 super(SamDB
, self
).connect(url
=url
, flags
=flags
,
90 '''return True if we are an RODC'''
91 return dsdb
._am
_rodc
(self
)
94 '''return True if we are an PDC emulator'''
95 return dsdb
._am
_pdc
(self
)
98 '''return the domain DN'''
99 return str(self
.get_default_basedn())
102 '''return the schema partition dn'''
103 return str(self
.get_schema_basedn())
105 def disable_account(self
, search_filter
):
106 """Disables an account
108 :param search_filter: LDAP filter to find the user (eg
112 flags
= samba
.dsdb
.UF_ACCOUNTDISABLE
113 self
.toggle_userAccountFlags(search_filter
, flags
, on
=True)
115 def enable_account(self
, search_filter
):
116 """Enables an account
118 :param search_filter: LDAP filter to find the user (eg
122 flags
= samba
.dsdb
.UF_ACCOUNTDISABLE | samba
.dsdb
.UF_PASSWD_NOTREQD
123 self
.toggle_userAccountFlags(search_filter
, flags
, on
=False)
125 def toggle_userAccountFlags(self
, search_filter
, flags
, flags_str
=None,
126 on
=True, strict
=False):
127 """Toggle_userAccountFlags
129 :param search_filter: LDAP filter to find the user (eg
131 :param flags: samba.dsdb.UF_* flags
132 :param on: on=True (default) => set, on=False => unset
133 :param strict: strict=False (default) ignore if no action is needed
134 strict=True raises an Exception if...
136 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
137 expression
=search_filter
, attrs
=["userAccountControl"])
139 raise Exception("Unable to find account where '%s'" % search_filter
)
140 assert(len(res
) == 1)
141 account_dn
= res
[0].dn
143 old_uac
= int(res
[0]["userAccountControl"][0])
145 if strict
and (old_uac
& flags
):
146 error
= "Account flag(s) '%s' already set" % flags_str
147 raise Exception(error
)
149 new_uac
= old_uac | flags
151 if strict
and not (old_uac
& flags
):
152 error
= "Account flag(s) '%s' already unset" % flags_str
153 raise Exception(error
)
155 new_uac
= old_uac
& ~flags
157 if old_uac
== new_uac
:
163 delete: userAccountControl
164 userAccountControl: %u
165 add: userAccountControl
166 userAccountControl: %u
167 """ % (account_dn
, old_uac
, new_uac
)
168 self
.modify_ldif(mod
)
170 def force_password_change_at_next_login(self
, search_filter
):
171 """Forces a password change at next login
173 :param search_filter: LDAP filter to find the user (eg
176 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
177 expression
=search_filter
, attrs
=[])
179 raise Exception('Unable to find user "%s"' % search_filter
)
180 assert(len(res
) == 1)
189 self
.modify_ldif(mod
)
191 def unlock_account(self
, search_filter
):
192 """Unlock a user account by resetting lockoutTime to 0.
193 This does also reset the badPwdCount to 0.
195 :param search_filter: LDAP filter to find the user (e.g.
196 sAMAccountName=username)
198 res
= self
.search(base
=self
.domain_dn(),
199 scope
=ldb
.SCOPE_SUBTREE
,
200 expression
=search_filter
,
203 raise SamDBNotFoundError('Unable to find user "%s"' % search_filter
)
205 raise SamDBError('User "%s" is not unique' % search_filter
)
214 self
.modify_ldif(mod
)
216 def newgroup(self
, groupname
, groupou
=None, grouptype
=None,
217 description
=None, mailaddress
=None, notes
=None, sd
=None,
218 gidnumber
=None, nisdomain
=None):
219 """Adds a new group with additional parameters
221 :param groupname: Name of the new group
222 :param grouptype: Type of the new group
223 :param description: Description of the new group
224 :param mailaddress: Email address of the new group
225 :param notes: Notes of the new group
226 :param gidnumber: GID Number of the new group
227 :param nisdomain: NIS Domain Name of the new group
228 :param sd: security descriptor of the object
232 group_dn
= "CN=%s,%s,%s" % (groupname
, groupou
, self
.domain_dn())
234 group_dn
= "CN=%s,%s" % (groupname
, self
.get_wellknown_dn(
235 self
.get_default_basedn(),
236 dsdb
.DS_GUID_USERS_CONTAINER
))
238 # The new user record. Note the reliance on the SAMLDB module which
239 # fills in the default information
240 ldbmessage
= {"dn": group_dn
,
241 "sAMAccountName": groupname
,
242 "objectClass": "group"}
244 if grouptype
is not None:
245 ldbmessage
["groupType"] = normalise_int32(grouptype
)
247 if description
is not None:
248 ldbmessage
["description"] = description
250 if mailaddress
is not None:
251 ldbmessage
["mail"] = mailaddress
253 if notes
is not None:
254 ldbmessage
["info"] = notes
256 if gidnumber
is not None:
257 ldbmessage
["gidNumber"] = normalise_int32(gidnumber
)
259 if nisdomain
is not None:
260 ldbmessage
["msSFU30Name"] = groupname
261 ldbmessage
["msSFU30NisDomain"] = nisdomain
264 ldbmessage
["nTSecurityDescriptor"] = ndr_pack(sd
)
268 def deletegroup(self
, groupname
):
271 :param groupname: Name of the target group
274 groupfilter
= "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb
.binary_encode(groupname
), "CN=Group,CN=Schema,CN=Configuration", self
.domain_dn())
275 self
.transaction_start()
277 targetgroup
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
278 expression
=groupfilter
, attrs
=[])
279 if len(targetgroup
) == 0:
280 raise Exception('Unable to find group "%s"' % groupname
)
281 assert(len(targetgroup
) == 1)
282 self
.delete(targetgroup
[0].dn
)
284 self
.transaction_cancel()
287 self
.transaction_commit()
289 def group_member_filter(self
, member
, member_types
):
292 all_member_types
= [ 'user',
299 if 'all' in member_types
:
300 member_types
= all_member_types
302 for member_type
in member_types
:
303 if member_type
not in all_member_types
:
304 raise Exception('Invalid group member type "%s". '
305 'Valid types are %s and all.' %
306 (member_type
, ", ".join(all_member_types
)))
308 if 'user' in member_types
:
309 filter += ('(&(sAMAccountName=%s)(samAccountType=%d))' %
310 (ldb
.binary_encode(member
), dsdb
.ATYPE_NORMAL_ACCOUNT
))
311 if 'group' in member_types
:
312 filter += ('(&(sAMAccountName=%s)'
313 '(objectClass=group)'
314 '(!(groupType:1.2.840.113556.1.4.803:=1)))' %
315 ldb
.binary_encode(member
))
316 if 'computer' in member_types
:
317 samaccountname
= member
318 if member
[-1] != '$':
319 samaccountname
= "%s$" % member
320 filter += ('(&(samAccountType=%d)'
321 '(!(objectCategory=msDS-ManagedServiceAccount))'
322 '(sAMAccountName=%s))' %
323 (dsdb
.ATYPE_WORKSTATION_TRUST
,
324 ldb
.binary_encode(samaccountname
)))
325 if 'serviceaccount' in member_types
:
326 samaccountname
= member
327 if member
[-1] != '$':
328 samaccountname
= "%s$" % member
329 filter += ('(&(samAccountType=%d)'
330 '(objectCategory=msDS-ManagedServiceAccount)'
331 '(sAMAccountName=%s))' %
332 (dsdb
.ATYPE_WORKSTATION_TRUST
,
333 ldb
.binary_encode(samaccountname
)))
334 if 'contact' in member_types
:
335 filter += ('(&(objectCategory=Person)(!(objectSid=*))(name=%s))' %
336 ldb
.binary_encode(member
))
338 filter = "(|%s)" % filter
342 def add_remove_group_members(self
, groupname
, members
,
343 add_members_operation
=True,
344 member_types
=[ 'user', 'group', 'computer' ],
345 member_base_dn
=None):
346 """Adds or removes group members
348 :param groupname: Name of the target group
349 :param members: list of group members
350 :param add_members_operation: Defines if its an add or remove
354 groupfilter
= "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
355 ldb
.binary_encode(groupname
), "CN=Group,CN=Schema,CN=Configuration", self
.domain_dn())
357 self
.transaction_start()
359 targetgroup
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
360 expression
=groupfilter
, attrs
=['member'])
361 if len(targetgroup
) == 0:
362 raise Exception('Unable to find group "%s"' % groupname
)
363 assert(len(targetgroup
) == 1)
367 addtargettogroup
= """
370 """ % (str(targetgroup
[0].dn
))
372 for member
in members
:
373 targetmember_dn
= None
374 if member_base_dn
is None:
375 member_base_dn
= self
.domain_dn()
378 membersid
= security
.dom_sid(member
)
379 targetmember_dn
= "<SID=%s>" % str(membersid
)
380 except TypeError as e
:
383 if targetmember_dn
is None:
385 member_dn
= ldb
.Dn(self
, member
)
386 if member_dn
.get_linearized() == member_dn
.extended_str(1):
387 full_member_dn
= self
.normalize_dn_in_domain(member_dn
)
389 full_member_dn
= member_dn
390 targetmember_dn
= full_member_dn
.extended_str(1)
391 except ValueError as e
:
394 if targetmember_dn
is None:
395 filter = self
.group_member_filter(member
, member_types
)
396 targetmember
= self
.search(base
=member_base_dn
,
397 scope
=ldb
.SCOPE_SUBTREE
,
401 if len(targetmember
) > 1:
402 targetmemberlist_str
= ""
403 for msg
in targetmember
:
404 targetmemberlist_str
+= "%s\n" % msg
.get("dn")
405 raise Exception('Found multiple results for "%s":\n%s' %
406 (member
, targetmemberlist_str
))
407 if len(targetmember
) != 1:
408 raise Exception('Unable to find "%s". Operation cancelled.' % member
)
409 targetmember_dn
= targetmember
[0].dn
.extended_str(1)
411 if add_members_operation
is True and (targetgroup
[0].get('member') is None or get_bytes(targetmember_dn
) not in [str(x
) for x
in targetgroup
[0]['member']]):
413 addtargettogroup
+= """add: member
415 """ % (str(targetmember_dn
))
417 elif add_members_operation
is False and (targetgroup
[0].get('member') is not None and get_bytes(targetmember_dn
) in targetgroup
[0]['member']):
419 addtargettogroup
+= """delete: member
421 """ % (str(targetmember_dn
))
424 self
.modify_ldif(addtargettogroup
)
427 self
.transaction_cancel()
430 self
.transaction_commit()
432 def prepare_attr_replace(self
, msg
, old
, attr_name
, value
):
433 """Changes the MessageElement with the given attr_name of the
434 given Message. If the value is "" set an empty value and the flag
435 FLAG_MOD_DELETE, otherwise set the new value and FLAG_MOD_REPLACE.
436 If the value is None or the Message contains the attr_name with this
437 value, nothing will changed."""
438 # skip unchanged attribute
441 if attr_name
in old
and str(value
) == str(old
[attr_name
]):
447 el
= ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attr_name
)
452 el
= ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, attr_name
)
455 def fullname_from_names(self
, given_name
=None, initials
=None, surname
=None,
456 old_attrs
={}, fallback_default
=""):
457 """Prepares new combined fullname, using the name parts.
458 Used for things like displayName or cn.
459 Use the original name values, if no new one is specified."""
461 attrs
= {"givenName": given_name
,
462 "initials": initials
,
465 # if the attribute is not specified, try to use the old one
466 for attr_name
, attr_value
in attrs
.items():
467 if attr_value
== None and attr_name
in old_attrs
:
468 attrs
[attr_name
] = str(old_attrs
[attr_name
])
470 # add '.' to initials if initals are not None and not "" and if the initials
471 # don't have already a '.' at the end
472 if attrs
["initials"] and not attrs
["initials"].endswith('.'):
473 attrs
["initials"] += '.'
475 # remove empty values (None and '')
476 attrs_values
= list(filter(None, attrs
.values()))
478 # fullname is the combination of not-empty values as string, separated by ' '
479 fullname
= ' '.join(attrs_values
)
482 return fallback_default
486 def newuser(self
, username
, password
,
487 force_password_change_at_next_login_req
=False,
488 useusernameascn
=False, userou
=None, surname
=None, givenname
=None,
489 initials
=None, profilepath
=None, scriptpath
=None, homedrive
=None,
490 homedirectory
=None, jobtitle
=None, department
=None, company
=None,
491 description
=None, mailaddress
=None, internetaddress
=None,
492 telephonenumber
=None, physicaldeliveryoffice
=None, sd
=None,
493 setpassword
=True, uidnumber
=None, gidnumber
=None, gecos
=None,
494 loginshell
=None, uid
=None, nisdomain
=None, unixhome
=None,
495 smartcard_required
=False):
496 """Adds a new user with additional parameters
498 :param username: Name of the new user
499 :param password: Password for the new user
500 :param force_password_change_at_next_login_req: Force password change
501 :param useusernameascn: Use username as cn rather that firstname +
503 :param userou: Object container (without domainDN postfix) for new user
504 :param surname: Surname of the new user
505 :param givenname: First name of the new user
506 :param initials: Initials of the new user
507 :param profilepath: Profile path of the new user
508 :param scriptpath: Logon script path of the new user
509 :param homedrive: Home drive of the new user
510 :param homedirectory: Home directory of the new user
511 :param jobtitle: Job title of the new user
512 :param department: Department of the new user
513 :param company: Company of the new user
514 :param description: of the new user
515 :param mailaddress: Email address of the new user
516 :param internetaddress: Home page of the new user
517 :param telephonenumber: Phone number of the new user
518 :param physicaldeliveryoffice: Office location of the new user
519 :param sd: security descriptor of the object
520 :param setpassword: optionally disable password reset
521 :param uidnumber: RFC2307 Unix numeric UID of the new user
522 :param gidnumber: RFC2307 Unix primary GID of the new user
523 :param gecos: RFC2307 Unix GECOS field of the new user
524 :param loginshell: RFC2307 Unix login shell of the new user
525 :param uid: RFC2307 Unix username of the new user
526 :param nisdomain: RFC2307 Unix NIS domain of the new user
527 :param unixhome: RFC2307 Unix home directory of the new user
528 :param smartcard_required: set the UF_SMARTCARD_REQUIRED bit of the new user
531 displayname
= self
.fullname_from_names(given_name
=givenname
,
535 if useusernameascn
is None and displayname
!= "":
539 user_dn
= "CN=%s,%s,%s" % (cn
, userou
, self
.domain_dn())
541 user_dn
= "CN=%s,%s" % (cn
, self
.get_wellknown_dn(
542 self
.get_default_basedn(),
543 dsdb
.DS_GUID_USERS_CONTAINER
))
545 dnsdomain
= ldb
.Dn(self
, self
.domain_dn()).canonical_str().replace("/", "")
546 user_principal_name
= "%s@%s" % (username
, dnsdomain
)
547 # The new user record. Note the reliance on the SAMLDB module which
548 # fills in the default information
549 ldbmessage
= {"dn": user_dn
,
550 "sAMAccountName": username
,
551 "userPrincipalName": user_principal_name
,
552 "objectClass": "user"}
554 if smartcard_required
:
555 ldbmessage
["userAccountControl"] = str(dsdb
.UF_NORMAL_ACCOUNT |
556 dsdb
.UF_SMARTCARD_REQUIRED
)
559 if surname
is not None:
560 ldbmessage
["sn"] = surname
562 if givenname
is not None:
563 ldbmessage
["givenName"] = givenname
565 if displayname
!= "":
566 ldbmessage
["displayName"] = displayname
567 ldbmessage
["name"] = displayname
569 if initials
is not None:
570 ldbmessage
["initials"] = '%s.' % initials
572 if profilepath
is not None:
573 ldbmessage
["profilePath"] = profilepath
575 if scriptpath
is not None:
576 ldbmessage
["scriptPath"] = scriptpath
578 if homedrive
is not None:
579 ldbmessage
["homeDrive"] = homedrive
581 if homedirectory
is not None:
582 ldbmessage
["homeDirectory"] = homedirectory
584 if jobtitle
is not None:
585 ldbmessage
["title"] = jobtitle
587 if department
is not None:
588 ldbmessage
["department"] = department
590 if company
is not None:
591 ldbmessage
["company"] = company
593 if description
is not None:
594 ldbmessage
["description"] = description
596 if mailaddress
is not None:
597 ldbmessage
["mail"] = mailaddress
599 if internetaddress
is not None:
600 ldbmessage
["wWWHomePage"] = internetaddress
602 if telephonenumber
is not None:
603 ldbmessage
["telephoneNumber"] = telephonenumber
605 if physicaldeliveryoffice
is not None:
606 ldbmessage
["physicalDeliveryOfficeName"] = physicaldeliveryoffice
609 ldbmessage
["nTSecurityDescriptor"] = ndr_pack(sd
)
612 if any(map(lambda b
: b
is not None, (uid
, uidnumber
, gidnumber
, gecos
,
613 loginshell
, nisdomain
, unixhome
))):
614 ldbmessage2
= ldb
.Message()
615 ldbmessage2
.dn
= ldb
.Dn(self
, user_dn
)
617 ldbmessage2
["uid"] = ldb
.MessageElement(str(uid
), ldb
.FLAG_MOD_REPLACE
, 'uid')
618 if uidnumber
is not None:
619 ldbmessage2
["uidNumber"] = ldb
.MessageElement(str(uidnumber
), ldb
.FLAG_MOD_REPLACE
, 'uidNumber')
620 if gidnumber
is not None:
621 ldbmessage2
["gidNumber"] = ldb
.MessageElement(str(gidnumber
), ldb
.FLAG_MOD_REPLACE
, 'gidNumber')
622 if gecos
is not None:
623 ldbmessage2
["gecos"] = ldb
.MessageElement(str(gecos
), ldb
.FLAG_MOD_REPLACE
, 'gecos')
624 if loginshell
is not None:
625 ldbmessage2
["loginShell"] = ldb
.MessageElement(str(loginshell
), ldb
.FLAG_MOD_REPLACE
, 'loginShell')
626 if unixhome
is not None:
627 ldbmessage2
["unixHomeDirectory"] = ldb
.MessageElement(
628 str(unixhome
), ldb
.FLAG_MOD_REPLACE
, 'unixHomeDirectory')
629 if nisdomain
is not None:
630 ldbmessage2
["msSFU30NisDomain"] = ldb
.MessageElement(
631 str(nisdomain
), ldb
.FLAG_MOD_REPLACE
, 'msSFU30NisDomain')
632 ldbmessage2
["msSFU30Name"] = ldb
.MessageElement(
633 str(username
), ldb
.FLAG_MOD_REPLACE
, 'msSFU30Name')
634 ldbmessage2
["unixUserPassword"] = ldb
.MessageElement(
635 'ABCD!efgh12345$67890', ldb
.FLAG_MOD_REPLACE
,
638 self
.transaction_start()
642 self
.modify(ldbmessage2
)
644 # Sets the password for it
646 self
.setpassword(("(distinguishedName=%s)" %
647 ldb
.binary_encode(user_dn
)),
649 force_password_change_at_next_login_req
)
651 self
.transaction_cancel()
654 self
.transaction_commit()
657 fullcontactname
=None,
668 internetaddress
=None,
669 telephonenumber
=None,
671 physicaldeliveryoffice
=None):
672 """Adds a new contact with additional parameters
674 :param fullcontactname: Optional full name of the new contact
675 :param ou: Object container for new contact
676 :param surname: Surname of the new contact
677 :param givenname: First name of the new contact
678 :param initials: Initials of the new contact
679 :param displayname: displayName of the new contact
680 :param jobtitle: Job title of the new contact
681 :param department: Department of the new contact
682 :param company: Company of the new contact
683 :param description: Description of the new contact
684 :param mailaddress: Email address of the new contact
685 :param internetaddress: Home page of the new contact
686 :param telephonenumber: Phone number of the new contact
687 :param mobilenumber: Primary mobile number of the new contact
688 :param physicaldeliveryoffice: Office location of the new contact
691 # Prepare the contact name like the RSAT, using the name parts.
692 cn
= self
.fullname_from_names(given_name
=givenname
,
696 # Use the specified fullcontactname instead of the previously prepared
697 # contact name, if it is specified.
698 # This is similar to the "Full name" value of the RSAT.
699 if fullcontactname
is not None:
702 if fullcontactname
is None and cn
== "":
703 raise Exception('No name for contact specified')
705 contactcontainer_dn
= self
.domain_dn()
707 contactcontainer_dn
= self
.normalize_dn_in_domain(ou
)
709 contact_dn
= "CN=%s,%s" % (cn
, contactcontainer_dn
)
711 ldbmessage
= {"dn": contact_dn
,
712 "objectClass": "contact",
715 if surname
is not None:
716 ldbmessage
["sn"] = surname
718 if givenname
is not None:
719 ldbmessage
["givenName"] = givenname
721 if displayname
is not None:
722 ldbmessage
["displayName"] = displayname
724 if initials
is not None:
725 ldbmessage
["initials"] = '%s.' % initials
727 if jobtitle
is not None:
728 ldbmessage
["title"] = jobtitle
730 if department
is not None:
731 ldbmessage
["department"] = department
733 if company
is not None:
734 ldbmessage
["company"] = company
736 if description
is not None:
737 ldbmessage
["description"] = description
739 if mailaddress
is not None:
740 ldbmessage
["mail"] = mailaddress
742 if internetaddress
is not None:
743 ldbmessage
["wWWHomePage"] = internetaddress
745 if telephonenumber
is not None:
746 ldbmessage
["telephoneNumber"] = telephonenumber
748 if mobilenumber
is not None:
749 ldbmessage
["mobile"] = mobilenumber
751 if physicaldeliveryoffice
is not None:
752 ldbmessage
["physicalDeliveryOfficeName"] = physicaldeliveryoffice
758 def newcomputer(self
, computername
, computerou
=None, description
=None,
759 prepare_oldjoin
=False, ip_address_list
=None,
760 service_principal_name_list
=None):
761 """Adds a new user with additional parameters
763 :param computername: Name of the new computer
764 :param computerou: Object container for new computer
765 :param description: Description of the new computer
766 :param prepare_oldjoin: Preset computer password for oldjoin mechanism
767 :param ip_address_list: ip address list for DNS A or AAAA record
768 :param service_principal_name_list: string list of servicePincipalName
771 cn
= re
.sub(r
"\$$", "", computername
)
773 raise Exception('Illegal computername "%s"' % computername
)
774 samaccountname
= "%s$" % cn
776 computercontainer_dn
= self
.get_wellknown_dn(self
.get_default_basedn(),
777 dsdb
.DS_GUID_COMPUTERS_CONTAINER
)
779 computercontainer_dn
= self
.normalize_dn_in_domain(computerou
)
781 computer_dn
= "CN=%s,%s" % (cn
, computercontainer_dn
)
783 ldbmessage
= {"dn": computer_dn
,
784 "sAMAccountName": samaccountname
,
785 "objectClass": "computer",
788 if description
is not None:
789 ldbmessage
["description"] = description
791 if service_principal_name_list
:
792 ldbmessage
["servicePrincipalName"] = service_principal_name_list
794 accountcontrol
= str(dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
795 dsdb
.UF_ACCOUNTDISABLE
)
797 accountcontrol
= str(dsdb
.UF_WORKSTATION_TRUST_ACCOUNT
)
798 ldbmessage
["userAccountControl"] = accountcontrol
801 ldbmessage
['dNSHostName'] = '{}.{}'.format(
802 cn
, self
.domain_dns_name())
804 self
.transaction_start()
809 password
= cn
.lower()
810 self
.setpassword(("(distinguishedName=%s)" %
811 ldb
.binary_encode(computer_dn
)),
814 self
.transaction_cancel()
817 self
.transaction_commit()
819 def deleteuser(self
, username
):
822 :param username: Name of the target user
825 filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb
.binary_encode(username
), "CN=Person,CN=Schema,CN=Configuration", self
.domain_dn())
826 self
.transaction_start()
828 target
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
829 expression
=filter, attrs
=[])
831 raise Exception('Unable to find user "%s"' % username
)
832 assert(len(target
) == 1)
833 self
.delete(target
[0].dn
)
835 self
.transaction_cancel()
838 self
.transaction_commit()
840 def setpassword(self
, search_filter
, password
,
841 force_change_at_next_login
=False, username
=None):
842 """Sets the password for a user
844 :param search_filter: LDAP filter to find the user (eg
846 :param password: Password for the user
847 :param force_change_at_next_login: Force password change
849 self
.transaction_start()
851 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
852 expression
=search_filter
, attrs
=[])
854 raise Exception('Unable to find user "%s"' % (username
or search_filter
))
856 raise Exception('Matched %u multiple users with filter "%s"' % (len(res
), search_filter
))
858 if not isinstance(password
, str):
859 pw
= password
.decode('utf-8')
862 pw
= ('"' + pw
+ '"').encode('utf-16-le')
868 """ % (user_dn
, base64
.b64encode(pw
).decode('utf-8'))
870 self
.modify_ldif(setpw
)
872 if force_change_at_next_login
:
873 self
.force_password_change_at_next_login(
874 "(distinguishedName=" + str(user_dn
) + ")")
876 # modify the userAccountControl to remove the disabled bit
877 self
.enable_account(search_filter
)
879 self
.transaction_cancel()
882 self
.transaction_commit()
884 def setexpiry(self
, search_filter
, expiry_seconds
, no_expiry_req
=False):
885 """Sets the account expiry for a user
887 :param search_filter: LDAP filter to find the user (eg
889 :param expiry_seconds: expiry time from now in seconds
890 :param no_expiry_req: if set, then don't expire password
892 self
.transaction_start()
894 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
895 expression
=search_filter
,
896 attrs
=["userAccountControl", "accountExpires"])
898 raise Exception('Unable to find user "%s"' % search_filter
)
899 assert(len(res
) == 1)
902 userAccountControl
= int(res
[0]["userAccountControl"][0])
903 accountExpires
= int(res
[0]["accountExpires"][0])
905 userAccountControl
= userAccountControl |
0x10000
908 userAccountControl
= userAccountControl
& ~
0x10000
909 accountExpires
= samba
.unix2nttime(expiry_seconds
+ int(time
.time()))
914 replace: userAccountControl
915 userAccountControl: %u
916 replace: accountExpires
918 """ % (user_dn
, userAccountControl
, accountExpires
)
920 self
.modify_ldif(setexp
)
922 self
.transaction_cancel()
925 self
.transaction_commit()
927 def set_domain_sid(self
, sid
):
928 """Change the domain SID used by this LDB.
930 :param sid: The new domain sid to use.
932 dsdb
._samdb
_set
_domain
_sid
(self
, sid
)
934 def get_domain_sid(self
):
935 """Read the domain SID used by this LDB. """
936 return dsdb
._samdb
_get
_domain
_sid
(self
)
938 domain_sid
= property(get_domain_sid
, set_domain_sid
,
939 doc
="SID for the domain")
941 def set_invocation_id(self
, invocation_id
):
942 """Set the invocation id for this SamDB handle.
944 :param invocation_id: GUID of the invocation id.
946 dsdb
._dsdb
_set
_ntds
_invocation
_id
(self
, invocation_id
)
948 def get_invocation_id(self
):
949 """Get the invocation_id id"""
950 return dsdb
._samdb
_ntds
_invocation
_id
(self
)
952 invocation_id
= property(get_invocation_id
, set_invocation_id
,
953 doc
="Invocation ID GUID")
955 def get_oid_from_attid(self
, attid
):
956 return dsdb
._dsdb
_get
_oid
_from
_attid
(self
, attid
)
958 def get_attid_from_lDAPDisplayName(self
, ldap_display_name
,
960 '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
961 return dsdb
._dsdb
_get
_attid
_from
_lDAPDisplayName
(self
,
962 ldap_display_name
, is_schema_nc
)
964 def get_syntax_oid_from_lDAPDisplayName(self
, ldap_display_name
):
965 '''return the syntax OID for a LDAP attribute as a string'''
966 return dsdb
._dsdb
_get
_syntax
_oid
_from
_lDAPDisplayName
(self
, ldap_display_name
)
968 def get_systemFlags_from_lDAPDisplayName(self
, ldap_display_name
):
969 '''return the systemFlags for a LDAP attribute as a integer'''
970 return dsdb
._dsdb
_get
_systemFlags
_from
_lDAPDisplayName
(self
, ldap_display_name
)
972 def get_linkId_from_lDAPDisplayName(self
, ldap_display_name
):
973 '''return the linkID for a LDAP attribute as a integer'''
974 return dsdb
._dsdb
_get
_linkId
_from
_lDAPDisplayName
(self
, ldap_display_name
)
976 def get_lDAPDisplayName_by_attid(self
, attid
):
977 '''return the lDAPDisplayName from an integer DRS attribute ID'''
978 return dsdb
._dsdb
_get
_lDAPDisplayName
_by
_attid
(self
, attid
)
980 def get_backlink_from_lDAPDisplayName(self
, ldap_display_name
):
981 '''return the attribute name of the corresponding backlink from the name
982 of a forward link attribute. If there is no backlink return None'''
983 return dsdb
._dsdb
_get
_backlink
_from
_lDAPDisplayName
(self
, ldap_display_name
)
985 def set_ntds_settings_dn(self
, ntds_settings_dn
):
986 """Set the NTDS Settings DN, as would be returned on the dsServiceName
989 This allows the DN to be set before the database fully exists
991 :param ntds_settings_dn: The new DN to use
993 dsdb
._samdb
_set
_ntds
_settings
_dn
(self
, ntds_settings_dn
)
995 def get_ntds_GUID(self
):
996 """Get the NTDS objectGUID"""
997 return dsdb
._samdb
_ntds
_objectGUID
(self
)
999 def get_timestr(self
):
1000 """Get the current time as generalized time string"""
1001 res
= self
.search(base
="",
1002 scope
=ldb
.SCOPE_BASE
,
1003 attrs
=["currentTime"])
1004 return str(res
[0]["currentTime"][0])
1007 """Get the current time as UNIX time"""
1008 return ldb
.string_to_time(self
.get_timestr())
1010 def get_nttime(self
):
1011 """Get the current time as NT time"""
1012 return samba
.unix2nttime(self
.get_time())
1014 def server_site_name(self
):
1015 """Get the server site name"""
1016 return dsdb
._samdb
_server
_site
_name
(self
)
1018 def host_dns_name(self
):
1019 """return the DNS name of this host"""
1020 res
= self
.search(base
='', scope
=ldb
.SCOPE_BASE
, attrs
=['dNSHostName'])
1021 return str(res
[0]['dNSHostName'][0])
1023 def domain_dns_name(self
):
1024 """return the DNS name of the domain root"""
1025 domain_dn
= self
.get_default_basedn()
1026 return domain_dn
.canonical_str().split('/')[0]
1028 def domain_netbios_name(self
):
1029 """return the NetBIOS name of the domain root"""
1030 domain_dn
= self
.get_default_basedn()
1031 dns_name
= self
.domain_dns_name()
1032 filter = "(&(objectClass=crossRef)(nETBIOSName=*)(ncName=%s)(dnsroot=%s))" % (domain_dn
, dns_name
)
1033 partitions_dn
= self
.get_partitions_dn()
1034 res
= self
.search(partitions_dn
,
1035 scope
=ldb
.SCOPE_ONELEVEL
,
1038 netbios_domain
= res
[0]["nETBIOSName"][0].decode()
1041 return netbios_domain
1043 def forest_dns_name(self
):
1044 """return the DNS name of the forest root"""
1045 forest_dn
= self
.get_root_basedn()
1046 return forest_dn
.canonical_str().split('/')[0]
1048 def load_partition_usn(self
, base_dn
):
1049 return dsdb
._dsdb
_load
_partition
_usn
(self
, base_dn
)
1051 def set_schema(self
, schema
, write_indices_and_attributes
=True):
1052 self
.set_schema_from_ldb(schema
.ldb
, write_indices_and_attributes
=write_indices_and_attributes
)
1054 def set_schema_from_ldb(self
, ldb_conn
, write_indices_and_attributes
=True):
1055 dsdb
._dsdb
_set
_schema
_from
_ldb
(self
, ldb_conn
, write_indices_and_attributes
)
1057 def set_schema_update_now(self
):
1061 add: schemaUpdateNow
1064 self
.modify_ldif(ldif
)
1066 def dsdb_DsReplicaAttribute(self
, ldb
, ldap_display_name
, ldif_elements
):
1067 '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
1068 return dsdb
._dsdb
_DsReplicaAttribute
(ldb
, ldap_display_name
, ldif_elements
)
1070 def dsdb_normalise_attributes(self
, ldb
, ldap_display_name
, ldif_elements
):
1071 '''normalise a list of attribute values'''
1072 return dsdb
._dsdb
_normalise
_attributes
(ldb
, ldap_display_name
, ldif_elements
)
1074 def get_attribute_from_attid(self
, attid
):
1075 """ Get from an attid the associated attribute
1077 :param attid: The attribute id for searched attribute
1078 :return: The name of the attribute associated with this id
1080 if len(self
.hash_oid_name
.keys()) == 0:
1081 self
._populate
_oid
_attid
()
1082 if self
.get_oid_from_attid(attid
) in self
.hash_oid_name
:
1083 return self
.hash_oid_name
[self
.get_oid_from_attid(attid
)]
1087 def _populate_oid_attid(self
):
1088 """Populate the hash hash_oid_name.
1090 This hash contains the oid of the attribute as a key and
1091 its display name as a value
1093 self
.hash_oid_name
= {}
1094 res
= self
.search(expression
="objectClass=attributeSchema",
1095 controls
=["search_options:1:2"],
1096 attrs
=["attributeID",
1100 strDisplay
= str(e
.get("lDAPDisplayName"))
1101 self
.hash_oid_name
[str(e
.get("attributeID"))] = strDisplay
1103 def get_attribute_replmetadata_version(self
, dn
, att
):
1104 """Get the version field trom the replPropertyMetaData for
1107 :param dn: The on which we want to get the version
1108 :param att: The name of the attribute
1109 :return: The value of the version field in the replPropertyMetaData
1110 for the given attribute. None if the attribute is not replicated
1113 res
= self
.search(expression
="distinguishedName=%s" % dn
,
1114 scope
=ldb
.SCOPE_SUBTREE
,
1115 controls
=["search_options:1:2"],
1116 attrs
=["replPropertyMetaData"])
1120 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1121 res
[0]["replPropertyMetaData"][0])
1123 if len(self
.hash_oid_name
.keys()) == 0:
1124 self
._populate
_oid
_attid
()
1126 # Search for Description
1127 att_oid
= self
.get_oid_from_attid(o
.attid
)
1128 if att_oid
in self
.hash_oid_name
and\
1129 att
.lower() == self
.hash_oid_name
[att_oid
].lower():
1133 def set_attribute_replmetadata_version(self
, dn
, att
, value
,
1134 addifnotexist
=False):
1135 res
= self
.search(expression
="distinguishedName=%s" % dn
,
1136 scope
=ldb
.SCOPE_SUBTREE
,
1137 controls
=["search_options:1:2"],
1138 attrs
=["replPropertyMetaData"])
1142 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1143 res
[0]["replPropertyMetaData"][0])
1145 now
= samba
.unix2nttime(int(time
.time()))
1147 if len(self
.hash_oid_name
.keys()) == 0:
1148 self
._populate
_oid
_attid
()
1150 # Search for Description
1151 att_oid
= self
.get_oid_from_attid(o
.attid
)
1152 if att_oid
in self
.hash_oid_name
and\
1153 att
.lower() == self
.hash_oid_name
[att_oid
].lower():
1155 seq
= self
.sequence_number(ldb
.SEQ_NEXT
)
1157 o
.originating_change_time
= now
1158 o
.originating_invocation_id
= misc
.GUID(self
.get_invocation_id())
1159 o
.originating_usn
= seq
1162 if not found
and addifnotexist
and len(ctr
.array
) > 0:
1163 o2
= drsblobs
.replPropertyMetaData1()
1165 att_oid
= self
.get_oid_from_attid(o2
.attid
)
1166 seq
= self
.sequence_number(ldb
.SEQ_NEXT
)
1168 o2
.originating_change_time
= now
1169 o2
.originating_invocation_id
= misc
.GUID(self
.get_invocation_id())
1170 o2
.originating_usn
= seq
1175 ctr
.count
= ctr
.count
+ 1
1179 replBlob
= ndr_pack(repl
)
1182 msg
["replPropertyMetaData"] = \
1183 ldb
.MessageElement(replBlob
,
1184 ldb
.FLAG_MOD_REPLACE
,
1185 "replPropertyMetaData")
1186 self
.modify(msg
, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
1188 def write_prefixes_from_schema(self
):
1189 dsdb
._dsdb
_write
_prefixes
_from
_schema
_to
_ldb
(self
)
1191 def get_partitions_dn(self
):
1192 return dsdb
._dsdb
_get
_partitions
_dn
(self
)
1194 def get_nc_root(self
, dn
):
1195 return dsdb
._dsdb
_get
_nc
_root
(self
, dn
)
1197 def get_wellknown_dn(self
, nc_root
, wkguid
):
1198 h_nc
= self
.hash_well_known
.get(str(nc_root
))
1200 if h_nc
is not None:
1201 dn
= h_nc
.get(wkguid
)
1203 dn
= dsdb
._dsdb
_get
_wellknown
_dn
(self
, nc_root
, wkguid
)
1207 self
.hash_well_known
[str(nc_root
)] = {}
1208 h_nc
= self
.hash_well_known
[str(nc_root
)]
1212 def set_minPwdAge(self
, value
):
1213 if not isinstance(value
, bytes
):
1214 value
= str(value
).encode('utf8')
1216 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
1217 m
["minPwdAge"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "minPwdAge")
1220 def get_minPwdAge(self
):
1221 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["minPwdAge"])
1224 elif "minPwdAge" not in res
[0]:
1227 return int(res
[0]["minPwdAge"][0])
1229 def set_maxPwdAge(self
, value
):
1230 if not isinstance(value
, bytes
):
1231 value
= str(value
).encode('utf8')
1233 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
1234 m
["maxPwdAge"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "maxPwdAge")
1237 def get_maxPwdAge(self
):
1238 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["maxPwdAge"])
1241 elif "maxPwdAge" not in res
[0]:
1244 return int(res
[0]["maxPwdAge"][0])
1246 def set_minPwdLength(self
, value
):
1247 if not isinstance(value
, bytes
):
1248 value
= str(value
).encode('utf8')
1250 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
1251 m
["minPwdLength"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "minPwdLength")
1254 def get_minPwdLength(self
):
1255 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["minPwdLength"])
1258 elif "minPwdLength" not in res
[0]:
1261 return int(res
[0]["minPwdLength"][0])
1263 def set_pwdProperties(self
, value
):
1264 if not isinstance(value
, bytes
):
1265 value
= str(value
).encode('utf8')
1267 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
1268 m
["pwdProperties"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "pwdProperties")
1271 def get_pwdProperties(self
):
1272 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["pwdProperties"])
1275 elif "pwdProperties" not in res
[0]:
1278 return int(res
[0]["pwdProperties"][0])
1280 def set_dsheuristics(self
, dsheuristics
):
1282 m
.dn
= ldb
.Dn(self
, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
1283 % self
.get_config_basedn().get_linearized())
1284 if dsheuristics
is not None:
1285 m
["dSHeuristics"] = \
1286 ldb
.MessageElement(dsheuristics
,
1287 ldb
.FLAG_MOD_REPLACE
,
1290 m
["dSHeuristics"] = \
1291 ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
,
1295 def get_dsheuristics(self
):
1296 res
= self
.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
1297 % self
.get_config_basedn().get_linearized(),
1298 scope
=ldb
.SCOPE_BASE
, attrs
=["dSHeuristics"])
1301 elif "dSHeuristics" in res
[0]:
1302 dsheuristics
= res
[0]["dSHeuristics"][0]
1308 def create_ou(self
, ou_dn
, description
=None, name
=None, sd
=None):
1309 """Creates an organizationalUnit object
1310 :param ou_dn: dn of the new object
1311 :param description: description attribute
1312 :param name: name atttribute
1313 :param sd: security descriptor of the object, can be
1314 an SDDL string or security.descriptor type
1317 "objectClass": "organizationalUnit"}
1320 m
["description"] = description
1325 m
["nTSecurityDescriptor"] = ndr_pack(sd
)
1328 def sequence_number(self
, seq_type
):
1329 """Returns the value of the sequence number according to the requested type
1330 :param seq_type: type of sequence number
1332 self
.transaction_start()
1334 seq
= super(SamDB
, self
).sequence_number(seq_type
)
1336 self
.transaction_cancel()
1339 self
.transaction_commit()
1342 def get_dsServiceName(self
):
1343 '''get the NTDS DN from the rootDSE'''
1344 res
= self
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
1345 return str(res
[0]["dsServiceName"][0])
1347 def get_serverName(self
):
1348 '''get the server DN from the rootDSE'''
1349 res
= self
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["serverName"])
1350 return str(res
[0]["serverName"][0])
1352 def dns_lookup(self
, dns_name
, dns_partition
=None):
1353 '''Do a DNS lookup in the database, returns the NDR database structures'''
1354 if dns_partition
is None:
1355 return dsdb_dns
.lookup(self
, dns_name
)
1357 return dsdb_dns
.lookup(self
, dns_name
,
1358 dns_partition
=dns_partition
)
1360 def dns_extract(self
, el
):
1361 '''Return the NDR database structures from a dnsRecord element'''
1362 return dsdb_dns
.extract(self
, el
)
1364 def dns_replace(self
, dns_name
, new_records
):
1365 '''Do a DNS modification on the database, sets the NDR database
1366 structures on a DNS name
1368 return dsdb_dns
.replace(self
, dns_name
, new_records
)
1370 def dns_replace_by_dn(self
, dn
, new_records
):
1371 '''Do a DNS modification on the database, sets the NDR database
1372 structures on a LDB DN
1374 This routine is important because if the last record on the DN
1375 is removed, this routine will put a tombstone in the record.
1377 return dsdb_dns
.replace_by_dn(self
, dn
, new_records
)
1379 def garbage_collect_tombstones(self
, dn
, current_time
,
1380 tombstone_lifetime
=None):
1381 '''garbage_collect_tombstones(lp, samdb, [dn], current_time, tombstone_lifetime)
1382 -> (num_objects_expunged, num_links_expunged)'''
1384 if not is_ad_dc_built():
1385 raise SamDBError('Cannot garbage collect tombstones: ' \
1386 'AD DC was not built')
1388 if tombstone_lifetime
is None:
1389 return dsdb
._dsdb
_garbage
_collect
_tombstones
(self
, dn
,
1392 return dsdb
._dsdb
_garbage
_collect
_tombstones
(self
, dn
,
1396 def create_own_rid_set(self
):
1397 '''create a RID set for this DSA'''
1398 return dsdb
._dsdb
_create
_own
_rid
_set
(self
)
1400 def allocate_rid(self
):
1401 '''return a new RID from the RID Pool on this DSA'''
1402 return dsdb
._dsdb
_allocate
_rid
(self
)
1404 def next_free_rid(self
):
1405 '''return the next free RID from the RID Pool on this DSA.
1407 :note: This function is not intended for general use, and care must be
1408 taken if it is used to generate objectSIDs. The returned RID is not
1409 formally reserved for use, creating the possibility of duplicate
1412 rid
, _
= self
.free_rid_bounds()
1415 def free_rid_bounds(self
):
1416 '''return the low and high bounds (inclusive) of RIDs that are
1417 available for use in this DSA's current RID pool.
1419 :note: This function is not intended for general use, and care must be
1420 taken if it is used to generate objectSIDs. The returned range of
1421 RIDs is not formally reserved for use, creating the possibility of
1422 duplicate objectSIDs.
1424 # Get DN of this server's RID Set
1425 server_name_dn
= ldb
.Dn(self
, self
.get_serverName())
1426 res
= self
.search(base
=server_name_dn
,
1427 scope
=ldb
.SCOPE_BASE
,
1428 attrs
=["serverReference"])
1430 server_ref
= res
[0]["serverReference"]
1433 ldb
.ERR_NO_SUCH_ATTRIBUTE
,
1435 "Cannot find attribute serverReference of %s "
1436 "to calculate reference dn" % server_name_dn
) from None
1437 server_ref_dn
= ldb
.Dn(self
, server_ref
[0].decode("utf-8"))
1439 res
= self
.search(base
=server_ref_dn
,
1440 scope
=ldb
.SCOPE_BASE
,
1441 attrs
=["rIDSetReferences"])
1443 rid_set_refs
= res
[0]["rIDSetReferences"]
1446 ldb
.ERR_NO_SUCH_ATTRIBUTE
,
1448 "Cannot find attribute rIDSetReferences of %s "
1449 "to calculate reference dn" % server_ref_dn
) from None
1450 rid_set_dn
= ldb
.Dn(self
, rid_set_refs
[0].decode("utf-8"))
1452 # Get the alloc pools and next RID of this RID Set
1453 res
= self
.search(base
=rid_set_dn
,
1454 scope
=ldb
.SCOPE_BASE
,
1455 attrs
=["rIDAllocationPool",
1456 "rIDPreviousAllocationPool",
1459 uint32_max
= 2**32 - 1
1460 uint64_max
= 2**64 - 1
1463 alloc_pool
= int(res
[0]["rIDAllocationPool"][0])
1465 alloc_pool
= uint64_max
1466 if alloc_pool
== uint64_max
:
1467 raise ldb
.LdbError(ldb
.ERR_OPERATIONS_ERROR
,
1468 "Bad RID Set %s" % rid_set_dn
)
1471 prev_pool
= int(res
[0]["rIDPreviousAllocationPool"][0])
1473 prev_pool
= uint64_max
1475 next_rid
= int(res
[0]["rIDNextRID"][0])
1477 next_rid
= uint32_max
1479 # If we never used a pool, set up our first pool
1480 if prev_pool
== uint64_max
or next_rid
== uint32_max
:
1481 prev_pool
= alloc_pool
1482 next_rid
= prev_pool
& uint32_max
1486 # Now check if our current pool is still usable
1487 prev_pool_lo
= prev_pool
& uint32_max
1488 prev_pool_hi
= prev_pool
>> 32
1489 if next_rid
> prev_pool_hi
:
1490 # We need a new pool, check if we already have a new one
1491 # Otherwise we return an error code.
1492 if alloc_pool
== prev_pool
:
1493 raise ldb
.LdbError(ldb
.ERR_OPERATIONS_ERROR
,
1494 "RID pools out of RIDs")
1496 # Now use the new pool
1497 prev_pool
= alloc_pool
1498 prev_pool_lo
= prev_pool
& uint32_max
1499 prev_pool_hi
= prev_pool
>> 32
1500 next_rid
= prev_pool_lo
1502 if next_rid
< prev_pool_lo
or next_rid
> prev_pool_hi
:
1503 raise ldb
.LdbError(ldb
.ERR_OPERATIONS_ERROR
,
1504 "Bad RID chosen %d from range %d-%d" %
1505 (next_rid
, prev_pool_lo
, prev_pool_hi
))
1507 return next_rid
, prev_pool_hi
1509 def normalize_dn_in_domain(self
, dn
):
1510 '''return a new DN expanded by adding the domain DN
1512 If the dn is already a child of the domain DN, just
1515 :param dn: relative dn
1517 domain_dn
= ldb
.Dn(self
, self
.domain_dn())
1519 if isinstance(dn
, ldb
.Dn
):
1522 full_dn
= ldb
.Dn(self
, dn
)
1523 if not full_dn
.is_child_of(domain_dn
):
1524 full_dn
.add_base(domain_dn
)
1527 class dsdb_Dn(object):
1528 '''a class for binary DN'''
1530 def __init__(self
, samdb
, dnstring
, syntax_oid
=None):
1531 '''create a dsdb_Dn'''
1532 if syntax_oid
is None:
1533 # auto-detect based on string
1534 if dnstring
.startswith("B:"):
1535 syntax_oid
= dsdb
.DSDB_SYNTAX_BINARY_DN
1536 elif dnstring
.startswith("S:"):
1537 syntax_oid
= dsdb
.DSDB_SYNTAX_STRING_DN
1539 syntax_oid
= dsdb
.DSDB_SYNTAX_OR_NAME
1540 if syntax_oid
in [dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_STRING_DN
]:
1542 colons
= dnstring
.split(':')
1544 raise RuntimeError("Invalid DN %s" % dnstring
)
1545 prefix_len
= 4 + len(colons
[1]) + int(colons
[1])
1546 self
.prefix
= dnstring
[0:prefix_len
]
1547 self
.binary
= self
.prefix
[3 + len(colons
[1]):-1]
1548 self
.dnstring
= dnstring
[prefix_len
:]
1550 self
.dnstring
= dnstring
1553 self
.dn
= ldb
.Dn(samdb
, self
.dnstring
)
1556 return self
.prefix
+ str(self
.dn
.extended_str(mode
=1))
1558 def __cmp__(self
, other
):
1559 ''' compare dsdb_Dn values similar to parsed_dn_compare()'''
1562 guid1
= dn1
.dn
.get_extended_component("GUID")
1563 guid2
= dn2
.dn
.get_extended_component("GUID")
1565 v
= cmp(guid1
, guid2
)
1568 v
= cmp(dn1
.binary
, dn2
.binary
)
1571 # In Python3, __cmp__ is replaced by these 6 methods
1572 def __eq__(self
, other
):
1573 return self
.__cmp
__(other
) == 0
1575 def __ne__(self
, other
):
1576 return self
.__cmp
__(other
) != 0
1578 def __lt__(self
, other
):
1579 return self
.__cmp
__(other
) < 0
1581 def __le__(self
, other
):
1582 return self
.__cmp
__(other
) <= 0
1584 def __gt__(self
, other
):
1585 return self
.__cmp
__(other
) > 0
1587 def __ge__(self
, other
):
1588 return self
.__cmp
__(other
) >= 0
1590 def get_binary_integer(self
):
1591 '''return binary part of a dsdb_Dn as an integer, or None'''
1592 if self
.prefix
== '':
1594 return int(self
.binary
, 16)
1596 def get_bytes(self
):
1597 '''return binary as a byte string'''
1598 return binascii
.unhexlify(self
.binary
)