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
231 group_dn = "CN=%s,%s,%s" % (groupname, (groupou or "CN=Users"), self.domain_dn())
233 # The new user record. Note the reliance on the SAMLDB module which
234 # fills in the default information
235 ldbmessage = {"dn": group_dn,
236 "sAMAccountName": groupname,
237 "objectClass": "group"}
239 if grouptype is not None:
240 ldbmessage["groupType"] = normalise_int32(grouptype)
242 if description is not None:
243 ldbmessage["description"] = description
245 if mailaddress is not None:
246 ldbmessage["mail"] = mailaddress
248 if notes is not None:
249 ldbmessage["info"] = notes
251 if gidnumber is not None:
252 ldbmessage["gidNumber"] = normalise_int32(gidnumber)
254 if nisdomain is not None:
255 ldbmessage["msSFU30Name"] = groupname
256 ldbmessage["msSFU30NisDomain"] = nisdomain
259 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
263 def deletegroup(self, groupname):
266 :param groupname: Name of the target group
269 groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
270 self.transaction_start()
272 targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
273 expression=groupfilter, attrs=[])
274 if len(targetgroup) == 0:
275 raise Exception('Unable to find group "%s"' % groupname)
276 assert(len(targetgroup) == 1)
277 self.delete(targetgroup[0].dn)
279 self.transaction_cancel()
282 self.transaction_commit()
284 def group_member_filter(self, member, member_types):
287 all_member_types = [ 'user',
294 if 'all' in member_types:
295 member_types = all_member_types
297 for member_type in member_types:
298 if member_type not in all_member_types:
299 raise Exception('Invalid group member type "%s". '
300 'Valid types are %s and all.' %
301 (member_type, ", ".join(all_member_types)))
303 if 'user' in member_types:
304 filter += ('(&(sAMAccountName=%s)(samAccountType=%d))' %
305 (ldb.binary_encode(member), dsdb.ATYPE_NORMAL_ACCOUNT))
306 if 'group' in member_types:
307 filter += ('(&(sAMAccountName=%s)'
308 '(objectClass=group)'
309 '(!(groupType:1.2.840.113556.1.4.803:=1)))' %
310 ldb.binary_encode(member))
311 if 'computer' in member_types:
312 samaccountname = member
313 if member[-1] != '$':
314 samaccountname = "%s$" % member
315 filter += ('(&(samAccountType=%d)'
316 '(!(objectCategory=msDS-ManagedServiceAccount))'
317 '(sAMAccountName=%s))' %
318 (dsdb.ATYPE_WORKSTATION_TRUST,
319 ldb.binary_encode(samaccountname)))
320 if 'serviceaccount' in member_types:
321 samaccountname = member
322 if member[-1] != '$':
323 samaccountname = "%s$" % member
324 filter += ('(&(samAccountType=%d)'
325 '(objectCategory=msDS-ManagedServiceAccount)'
326 '(sAMAccountName=%s))' %
327 (dsdb.ATYPE_WORKSTATION_TRUST,
328 ldb.binary_encode(samaccountname)))
329 if 'contact' in member_types:
330 filter += ('(&(objectCategory=Person)(!(objectSid=*))(name=%s))' %
331 ldb.binary_encode(member))
333 filter = "(|%s)" % filter
337 def add_remove_group_members(self, groupname, members,
338 add_members_operation=True,
339 member_types=[ 'user', 'group', 'computer' ],
340 member_base_dn=None):
341 """Adds or removes group members
343 :param groupname: Name of the target group
344 :param members: list of group members
345 :param add_members_operation: Defines if its an add or remove
349 groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
350 ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
352 self.transaction_start()
354 targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
355 expression=groupfilter, attrs=['member'])
356 if len(targetgroup) == 0:
357 raise Exception('Unable to find group "%s"' % groupname)
358 assert(len(targetgroup) == 1)
362 addtargettogroup = """
365 """ % (str(targetgroup[0].dn))
367 for member in members:
368 targetmember_dn = None
369 if member_base_dn is None:
370 member_base_dn = self.domain_dn()
373 membersid = security.dom_sid(member)
374 targetmember_dn = "<SID=%s>" % str(membersid)
375 except TypeError as e:
378 if targetmember_dn is None:
380 member_dn = ldb.Dn(self, member)
381 if member_dn.get_linearized() == member_dn.extended_str(1):
382 full_member_dn = self.normalize_dn_in_domain(member_dn)
384 full_member_dn = member_dn
385 targetmember_dn = full_member_dn.extended_str(1)
386 except ValueError as e:
389 if targetmember_dn is None:
390 filter = self.group_member_filter(member, member_types)
391 targetmember = self.search(base=member_base_dn,
392 scope=ldb.SCOPE_SUBTREE,
396 if len(targetmember) > 1:
397 targetmemberlist_str = ""
398 for msg in targetmember:
399 targetmemberlist_str += "%s\n" % msg.get("dn")
400 raise Exception('Found multiple results for "%s":\n%s' %
401 (member, targetmemberlist_str))
402 if len(targetmember) != 1:
403 raise Exception('Unable to find "%s". Operation cancelled.' % member)
404 targetmember_dn = targetmember[0].dn.extended_str(1)
406 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']]):
408 addtargettogroup += """add: member
410 """ % (str(targetmember_dn))
412 elif add_members_operation is False and (targetgroup[0].get('member') is not None and get_bytes(targetmember_dn) in targetgroup[0]['member']):
414 addtargettogroup += """delete: member
416 """ % (str(targetmember_dn))
419 self.modify_ldif(addtargettogroup)
422 self.transaction_cancel()
425 self.transaction_commit()
427 def prepare_attr_replace(self, msg, old, attr_name, value):
428 """Changes the MessageElement with the given attr_name of the
429 given Message. If the value is "" set an empty value and the flag
430 FLAG_MOD_DELETE, otherwise set the new value and FLAG_MOD_REPLACE.
431 If the value is None or the Message contains the attr_name with this
432 value, nothing will changed."""
433 # skip unchanged attribute
436 if attr_name in old and str(value) == str(old[attr_name]):
442 el = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attr_name)
447 el = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, attr_name)
450 def fullname_from_names(self, given_name=None, initials=None, surname=None,
451 old_attrs={}, fallback_default=""):
452 """Prepares new combined fullname, using the name parts.
453 Used for things like displayName or cn.
454 Use the original name values, if no new one is specified."""
456 attrs = {"givenName": given_name,
457 "initials": initials,
460 # if the attribute is not specified, try to use the old one
461 for attr_name, attr_value in attrs.items():
462 if attr_value == None and attr_name in old_attrs:
463 attrs[attr_name] = str(old_attrs[attr_name])
465 # add '.' to initials if initals are not None and not "" and if the initials
466 # don't have already a '.' at the end
467 if attrs["initials"] and not attrs["initials"].endswith('.'):
468 attrs["initials"] += '.'
470 # remove empty values (None and '')
471 attrs_values = list(filter(None, attrs.values()))
473 # fullname is the combination of not-empty values as string, separated by ' '
474 fullname = ' '.join(attrs_values)
477 return fallback_default
481 def newuser(self, username, password,
482 force_password_change_at_next_login_req=False,
483 useusernameascn=False, userou=None, surname=None, givenname=None,
484 initials=None, profilepath=None, scriptpath=None, homedrive=None,
485 homedirectory=None, jobtitle=None, department=None, company=None,
486 description=None, mailaddress=None, internetaddress=None,
487 telephonenumber=None, physicaldeliveryoffice=None, sd=None,
488 setpassword=True, uidnumber=None, gidnumber=None, gecos=None,
489 loginshell=None, uid=None, nisdomain=None, unixhome=None,
490 smartcard_required=False):
491 """Adds a new user with additional parameters
493 :param username: Name of the new user
494 :param password: Password for the new user
495 :param force_password_change_at_next_login_req: Force password change
496 :param useusernameascn: Use username as cn rather that firstname +
498 :param userou: Object container (without domainDN postfix) for new user
499 :param surname: Surname of the new user
500 :param givenname: First name of the new user
501 :param initials: Initials of the new user
502 :param profilepath: Profile path of the new user
503 :param scriptpath: Logon script path of the new user
504 :param homedrive: Home drive of the new user
505 :param homedirectory: Home directory of the new user
506 :param jobtitle: Job title of the new user
507 :param department: Department of the new user
508 :param company: Company of the new user
509 :param description: of the new user
510 :param mailaddress: Email address of the new user
511 :param internetaddress: Home page of the new user
512 :param telephonenumber: Phone number of the new user
513 :param physicaldeliveryoffice: Office location of the new user
514 :param sd: security descriptor of the object
515 :param setpassword: optionally disable password reset
516 :param uidnumber: RFC2307 Unix numeric UID of the new user
517 :param gidnumber: RFC2307 Unix primary GID of the new user
518 :param gecos: RFC2307 Unix GECOS field of the new user
519 :param loginshell: RFC2307 Unix login shell of the new user
520 :param uid: RFC2307 Unix username of the new user
521 :param nisdomain: RFC2307 Unix NIS domain of the new user
522 :param unixhome: RFC2307 Unix home directory of the new user
523 :param smartcard_required: set the UF_SMARTCARD_REQUIRED bit of the new user
526 displayname = self.fullname_from_names(given_name=givenname,
530 if useusernameascn is None and displayname != "":
533 user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
535 dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
536 user_principal_name = "%s@%s" % (username, dnsdomain)
537 # The new user record. Note the reliance on the SAMLDB module which
538 # fills in the default information
539 ldbmessage = {"dn": user_dn,
540 "sAMAccountName": username,
541 "userPrincipalName": user_principal_name,
542 "objectClass": "user"}
544 if smartcard_required:
545 ldbmessage["userAccountControl"] = str(dsdb.UF_NORMAL_ACCOUNT |
546 dsdb.UF_SMARTCARD_REQUIRED)
549 if surname is not None:
550 ldbmessage["sn"] = surname
552 if givenname is not None:
553 ldbmessage["givenName"] = givenname
555 if displayname != "":
556 ldbmessage["displayName"] = displayname
557 ldbmessage["name"] = displayname
559 if initials is not None:
560 ldbmessage["initials"] = '%s.' % initials
562 if profilepath is not None:
563 ldbmessage["profilePath"] = profilepath
565 if scriptpath is not None:
566 ldbmessage["scriptPath"] = scriptpath
568 if homedrive is not None:
569 ldbmessage["homeDrive"] = homedrive
571 if homedirectory is not None:
572 ldbmessage["homeDirectory"] = homedirectory
574 if jobtitle is not None:
575 ldbmessage["title"] = jobtitle
577 if department is not None:
578 ldbmessage["department"] = department
580 if company is not None:
581 ldbmessage["company"] = company
583 if description is not None:
584 ldbmessage["description"] = description
586 if mailaddress is not None:
587 ldbmessage["mail"] = mailaddress
589 if internetaddress is not None:
590 ldbmessage["wWWHomePage"] = internetaddress
592 if telephonenumber is not None:
593 ldbmessage["telephoneNumber"] = telephonenumber
595 if physicaldeliveryoffice is not None:
596 ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
599 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
602 if any(map(lambda b: b is not None, (uid, uidnumber, gidnumber, gecos,
603 loginshell, nisdomain, unixhome))):
604 ldbmessage2 = ldb.Message()
605 ldbmessage2.dn = ldb.Dn(self, user_dn)
607 ldbmessage2["uid"] = ldb.MessageElement(str(uid), ldb.FLAG_MOD_REPLACE, 'uid')
608 if uidnumber is not None:
609 ldbmessage2["uidNumber"] = ldb.MessageElement(str(uidnumber), ldb.FLAG_MOD_REPLACE, 'uidNumber')
610 if gidnumber is not None:
611 ldbmessage2["gidNumber"] = ldb.MessageElement(str(gidnumber), ldb.FLAG_MOD_REPLACE, 'gidNumber')
612 if gecos is not None:
613 ldbmessage2["gecos"] = ldb.MessageElement(str(gecos), ldb.FLAG_MOD_REPLACE, 'gecos')
614 if loginshell is not None:
615 ldbmessage2["loginShell"] = ldb.MessageElement(str(loginshell), ldb.FLAG_MOD_REPLACE, 'loginShell')
616 if unixhome is not None:
617 ldbmessage2["unixHomeDirectory"] = ldb.MessageElement(
618 str(unixhome), ldb.FLAG_MOD_REPLACE, 'unixHomeDirectory')
619 if nisdomain is not None:
620 ldbmessage2["msSFU30NisDomain"] = ldb.MessageElement(
621 str(nisdomain), ldb.FLAG_MOD_REPLACE, 'msSFU30NisDomain')
622 ldbmessage2["msSFU30Name"] = ldb.MessageElement(
623 str(username), ldb.FLAG_MOD_REPLACE, 'msSFU30Name')
624 ldbmessage2["unixUserPassword"] = ldb.MessageElement(
625 'ABCD!efgh12345$67890', ldb.FLAG_MOD_REPLACE,
628 self.transaction_start()
632 self.modify(ldbmessage2)
634 # Sets the password for it
636 self.setpassword(("(distinguishedName=%s)" %
637 ldb.binary_encode(user_dn)),
639 force_password_change_at_next_login_req)
641 self.transaction_cancel()
644 self.transaction_commit()
647 fullcontactname=None,
658 internetaddress=None,
659 telephonenumber=None,
661 physicaldeliveryoffice=None):
662 """Adds a new contact with additional parameters
664 :param fullcontactname: Optional full name of the new contact
665 :param ou: Object container for new contact
666 :param surname: Surname of the new contact
667 :param givenname: First name of the new contact
668 :param initials: Initials of the new contact
669 :param displayname: displayName of the new contact
670 :param jobtitle: Job title of the new contact
671 :param department: Department of the new contact
672 :param company: Company of the new contact
673 :param description: Description of the new contact
674 :param mailaddress: Email address of the new contact
675 :param internetaddress: Home page of the new contact
676 :param telephonenumber: Phone number of the new contact
677 :param mobilenumber: Primary mobile number of the new contact
678 :param physicaldeliveryoffice: Office location of the new contact
681 # Prepare the contact name like the RSAT, using the name parts.
682 cn = self.fullname_from_names(given_name=givenname,
686 # Use the specified fullcontactname instead of the previously prepared
687 # contact name, if it is specified.
688 # This is similar to the "Full name" value of the RSAT.
689 if fullcontactname is not None:
692 if fullcontactname is None and cn == "":
693 raise Exception('No name for contact specified')
695 contactcontainer_dn = self.domain_dn()
697 contactcontainer_dn = self.normalize_dn_in_domain(ou)
699 contact_dn = "CN=%s,%s" % (cn, contactcontainer_dn)
701 ldbmessage = {"dn": contact_dn,
702 "objectClass": "contact",
705 if surname is not None:
706 ldbmessage["sn"] = surname
708 if givenname is not None:
709 ldbmessage["givenName"] = givenname
711 if displayname is not None:
712 ldbmessage["displayName"] = displayname
714 if initials is not None:
715 ldbmessage["initials"] = '%s.' % initials
717 if jobtitle is not None:
718 ldbmessage["title"] = jobtitle
720 if department is not None:
721 ldbmessage["department"] = department
723 if company is not None:
724 ldbmessage["company"] = company
726 if description is not None:
727 ldbmessage["description"] = description
729 if mailaddress is not None:
730 ldbmessage["mail"] = mailaddress
732 if internetaddress is not None:
733 ldbmessage["wWWHomePage"] = internetaddress
735 if telephonenumber is not None:
736 ldbmessage["telephoneNumber"] = telephonenumber
738 if mobilenumber is not None:
739 ldbmessage["mobile"] = mobilenumber
741 if physicaldeliveryoffice is not None:
742 ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
748 def newcomputer(self, computername, computerou=None, description=None,
749 prepare_oldjoin=False, ip_address_list=None,
750 service_principal_name_list=None):
751 """Adds a new user with additional parameters
753 :param computername: Name of the new computer
754 :param computerou: Object container for new computer
755 :param description: Description of the new computer
756 :param prepare_oldjoin: Preset computer password for oldjoin mechanism
757 :param ip_address_list: ip address list for DNS A or AAAA record
758 :param service_principal_name_list: string list of servicePincipalName
761 cn = re.sub(r"\$$", "", computername)
763 raise Exception('Illegal computername "%s"' % computername)
764 samaccountname = "%s$" % cn
766 computercontainer_dn = "CN=Computers,%s" % self.domain_dn()
768 computercontainer_dn = self.normalize_dn_in_domain(computerou)
770 computer_dn = "CN=%s,%s" % (cn, computercontainer_dn)
772 ldbmessage = {"dn": computer_dn,
773 "sAMAccountName": samaccountname,
774 "objectClass": "computer",
777 if description is not None:
778 ldbmessage["description"] = description
780 if service_principal_name_list:
781 ldbmessage["servicePrincipalName"] = service_principal_name_list
783 accountcontrol = str(dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
784 dsdb.UF_ACCOUNTDISABLE)
786 accountcontrol = str(dsdb.UF_WORKSTATION_TRUST_ACCOUNT)
787 ldbmessage["userAccountControl"] = accountcontrol
790 ldbmessage['dNSHostName'] = '{}.{}'.format(
791 cn, self.domain_dns_name())
793 self.transaction_start()
798 password = cn.lower()
799 self.setpassword(("(distinguishedName=%s)" %
800 ldb.binary_encode(computer_dn)),
803 self.transaction_cancel()
806 self.transaction_commit()
808 def deleteuser(self, username):
811 :param username: Name of the target user
814 filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
815 self.transaction_start()
817 target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
818 expression=filter, attrs=[])
820 raise Exception('Unable to find user "%s"' % username)
821 assert(len(target) == 1)
822 self.delete(target[0].dn)
824 self.transaction_cancel()
827 self.transaction_commit()
829 def setpassword(self, search_filter, password,
830 force_change_at_next_login=False, username=None):
831 """Sets the password for a user
833 :param search_filter: LDAP filter to find the user (eg
835 :param password: Password for the user
836 :param force_change_at_next_login: Force password change
838 self.transaction_start()
840 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
841 expression=search_filter, attrs=[])
843 raise Exception('Unable to find user "%s"' % (username or search_filter))
845 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
847 if not isinstance(password, str):
848 pw = password.decode('utf-8')
851 pw = ('"' + pw + '"').encode('utf-16-le')
857 """ % (user_dn, base64.b64encode(pw).decode('utf-8'))
859 self.modify_ldif(setpw)
861 if force_change_at_next_login:
862 self.force_password_change_at_next_login(
863 "(distinguishedName=" + str(user_dn) + ")")
865 # modify the userAccountControl to remove the disabled bit
866 self.enable_account(search_filter)
868 self.transaction_cancel()
871 self.transaction_commit()
873 def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
874 """Sets the account expiry for a user
876 :param search_filter: LDAP filter to find the user (eg
878 :param expiry_seconds: expiry time from now in seconds
879 :param no_expiry_req: if set, then don't expire password
881 self.transaction_start()
883 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
884 expression=search_filter,
885 attrs=["userAccountControl", "accountExpires"])
887 raise Exception('Unable to find user "%s"' % search_filter)
888 assert(len(res) == 1)
891 userAccountControl = int(res[0]["userAccountControl"][0])
892 accountExpires = int(res[0]["accountExpires"][0])
894 userAccountControl = userAccountControl | 0x10000
897 userAccountControl = userAccountControl & ~0x10000
898 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
903 replace: userAccountControl
904 userAccountControl: %u
905 replace: accountExpires
907 """ % (user_dn, userAccountControl, accountExpires)
909 self.modify_ldif(setexp)
911 self.transaction_cancel()
914 self.transaction_commit()
916 def set_domain_sid(self, sid):
917 """Change the domain SID used by this LDB.
919 :param sid: The new domain sid to use.
921 dsdb._samdb_set_domain_sid(self, sid)
923 def get_domain_sid(self):
924 """Read the domain SID used by this LDB. """
925 return dsdb._samdb_get_domain_sid(self)
927 domain_sid = property(get_domain_sid, set_domain_sid,
928 doc="SID for the domain")
930 def set_invocation_id(self, invocation_id):
931 """Set the invocation id for this SamDB handle.
933 :param invocation_id: GUID of the invocation id.
935 dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
937 def get_invocation_id(self):
938 """Get the invocation_id id"""
939 return dsdb._samdb_ntds_invocation_id(self)
941 invocation_id = property(get_invocation_id, set_invocation_id,
942 doc="Invocation ID GUID")
944 def get_oid_from_attid(self, attid):
945 return dsdb._dsdb_get_oid_from_attid(self, attid)
947 def get_attid_from_lDAPDisplayName(self, ldap_display_name,
949 '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
950 return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
951 ldap_display_name, is_schema_nc)
953 def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
954 '''return the syntax OID for a LDAP attribute as a string'''
955 return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
957 def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
958 '''return the systemFlags for a LDAP attribute as a integer'''
959 return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
961 def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
962 '''return the linkID for a LDAP attribute as a integer'''
963 return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
965 def get_lDAPDisplayName_by_attid(self, attid):
966 '''return the lDAPDisplayName from an integer DRS attribute ID'''
967 return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
969 def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
970 '''return the attribute name of the corresponding backlink from the name
971 of a forward link attribute. If there is no backlink return None'''
972 return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
974 def set_ntds_settings_dn(self, ntds_settings_dn):
975 """Set the NTDS Settings DN, as would be returned on the dsServiceName
978 This allows the DN to be set before the database fully exists
980 :param ntds_settings_dn: The new DN to use
982 dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
984 def get_ntds_GUID(self):
985 """Get the NTDS objectGUID"""
986 return dsdb._samdb_ntds_objectGUID(self)
988 def get_timestr(self):
989 """Get the current time as generalized time string"""
990 res = self.search(base="",
991 scope=ldb.SCOPE_BASE,
992 attrs=["currentTime"])
993 return str(res[0]["currentTime"][0])
996 """Get the current time as UNIX time"""
997 return ldb.string_to_time(self.get_timestr())
999 def get_nttime(self):
1000 """Get the current time as NT time"""
1001 return samba.unix2nttime(self.get_time())
1003 def server_site_name(self):
1004 """Get the server site name"""
1005 return dsdb._samdb_server_site_name(self)
1007 def host_dns_name(self):
1008 """return the DNS name of this host"""
1009 res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
1010 return str(res[0]['dNSHostName'][0])
1012 def domain_dns_name(self):
1013 """return the DNS name of the domain root"""
1014 domain_dn = self.get_default_basedn()
1015 return domain_dn.canonical_str().split('/')[0]
1017 def domain_netbios_name(self):
1018 """return the NetBIOS name of the domain root"""
1019 domain_dn = self.get_default_basedn()
1020 dns_name = self.domain_dns_name()
1021 filter = "(&(objectClass=crossRef)(nETBIOSName=*)(ncName=%s)(dnsroot=%s))" % (domain_dn, dns_name)
1022 partitions_dn = self.get_partitions_dn()
1023 res = self.search(partitions_dn,
1024 scope=ldb.SCOPE_ONELEVEL,
1027 netbios_domain = res[0]["nETBIOSName"][0].decode()
1030 return netbios_domain
1032 def forest_dns_name(self):
1033 """return the DNS name of the forest root"""
1034 forest_dn = self.get_root_basedn()
1035 return forest_dn.canonical_str().split('/')[0]
1037 def load_partition_usn(self, base_dn):
1038 return dsdb._dsdb_load_partition_usn(self, base_dn)
1040 def set_schema(self, schema, write_indices_and_attributes=True):
1041 self.set_schema_from_ldb(schema.ldb, write_indices_and_attributes=write_indices_and_attributes)
1043 def set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes=True):
1044 dsdb._dsdb_set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes)
1046 def set_schema_update_now(self):
1050 add: schemaUpdateNow
1053 self.modify_ldif(ldif)
1055 def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
1056 '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
1057 return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
1059 def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
1060 '''normalise a list of attribute values'''
1061 return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
1063 def get_attribute_from_attid(self, attid):
1064 """ Get from an attid the associated attribute
1066 :param attid: The attribute id for searched attribute
1067 :return: The name of the attribute associated with this id
1069 if len(self.hash_oid_name.keys()) == 0:
1070 self._populate_oid_attid()
1071 if self.get_oid_from_attid(attid) in self.hash_oid_name:
1072 return self.hash_oid_name[self.get_oid_from_attid(attid)]
1076 def _populate_oid_attid(self):
1077 """Populate the hash hash_oid_name.
1079 This hash contains the oid of the attribute as a key and
1080 its display name as a value
1082 self.hash_oid_name = {}
1083 res = self.search(expression="objectClass=attributeSchema",
1084 controls=["search_options:1:2"],
1085 attrs=["attributeID",
1089 strDisplay = str(e.get("lDAPDisplayName"))
1090 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
1092 def get_attribute_replmetadata_version(self, dn, att):
1093 """Get the version field trom the replPropertyMetaData for
1096 :param dn: The on which we want to get the version
1097 :param att: The name of the attribute
1098 :return: The value of the version field in the replPropertyMetaData
1099 for the given attribute. None if the attribute is not replicated
1102 res = self.search(expression="distinguishedName=%s" % dn,
1103 scope=ldb.SCOPE_SUBTREE,
1104 controls=["search_options:1:2"],
1105 attrs=["replPropertyMetaData"])
1109 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1110 res[0]["replPropertyMetaData"][0])
1112 if len(self.hash_oid_name.keys()) == 0:
1113 self._populate_oid_attid()
1115 # Search for Description
1116 att_oid = self.get_oid_from_attid(o.attid)
1117 if att_oid in self.hash_oid_name and\
1118 att.lower() == self.hash_oid_name[att_oid].lower():
1122 def set_attribute_replmetadata_version(self, dn, att, value,
1123 addifnotexist=False):
1124 res = self.search(expression="distinguishedName=%s" % dn,
1125 scope=ldb.SCOPE_SUBTREE,
1126 controls=["search_options:1:2"],
1127 attrs=["replPropertyMetaData"])
1131 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
1132 res[0]["replPropertyMetaData"][0])
1134 now = samba.unix2nttime(int(time.time()))
1136 if len(self.hash_oid_name.keys()) == 0:
1137 self._populate_oid_attid()
1139 # Search for Description
1140 att_oid = self.get_oid_from_attid(o.attid)
1141 if att_oid in self.hash_oid_name and\
1142 att.lower() == self.hash_oid_name[att_oid].lower():
1144 seq = self.sequence_number(ldb.SEQ_NEXT)
1146 o.originating_change_time = now
1147 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
1148 o.originating_usn = seq
1151 if not found and addifnotexist and len(ctr.array) > 0:
1152 o2 = drsblobs.replPropertyMetaData1()
1154 att_oid = self.get_oid_from_attid(o2.attid)
1155 seq = self.sequence_number(ldb.SEQ_NEXT)
1157 o2.originating_change_time = now
1158 o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
1159 o2.originating_usn = seq
1164 ctr.count = ctr.count + 1
1168 replBlob = ndr_pack(repl)
1171 msg["replPropertyMetaData"] = \
1172 ldb.MessageElement(replBlob,
1173 ldb.FLAG_MOD_REPLACE,
1174 "replPropertyMetaData")
1175 self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
1177 def write_prefixes_from_schema(self):
1178 dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
1180 def get_partitions_dn(self):
1181 return dsdb._dsdb_get_partitions_dn(self)
1183 def get_nc_root(self, dn):
1184 return dsdb._dsdb_get_nc_root(self, dn)
1186 def get_wellknown_dn(self, nc_root, wkguid):
1187 h_nc = self.hash_well_known.get(str(nc_root))
1189 if h_nc is not None:
1190 dn = h_nc.get(wkguid)
1192 dn = dsdb._dsdb_get_wellknown_dn(self, nc_root, wkguid)
1196 self.hash_well_known[str(nc_root)] = {}
1197 h_nc = self.hash_well_known[str(nc_root)]
1201 def set_minPwdAge(self, value):
1202 if not isinstance(value, bytes):
1203 value = str(value).encode('utf8')
1205 m.dn = ldb.Dn(self, self.domain_dn())
1206 m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
1209 def get_minPwdAge(self):
1210 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
1213 elif "minPwdAge" not in res[0]:
1216 return int(res[0]["minPwdAge"][0])
1218 def set_maxPwdAge(self, value):
1219 if not isinstance(value, bytes):
1220 value = str(value).encode('utf8')
1222 m.dn = ldb.Dn(self, self.domain_dn())
1223 m["maxPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1226 def get_maxPwdAge(self):
1227 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["maxPwdAge"])
1230 elif "maxPwdAge" not in res[0]:
1233 return int(res[0]["maxPwdAge"][0])
1235 def set_minPwdLength(self, value):
1236 if not isinstance(value, bytes):
1237 value = str(value).encode('utf8')
1239 m.dn = ldb.Dn(self, self.domain_dn())
1240 m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
1243 def get_minPwdLength(self):
1244 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
1247 elif "minPwdLength" not in res[0]:
1250 return int(res[0]["minPwdLength"][0])
1252 def set_pwdProperties(self, value):
1253 if not isinstance(value, bytes):
1254 value = str(value).encode('utf8')
1256 m.dn = ldb.Dn(self, self.domain_dn())
1257 m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
1260 def get_pwdProperties(self):
1261 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
1264 elif "pwdProperties" not in res[0]:
1267 return int(res[0]["pwdProperties"][0])
1269 def set_dsheuristics(self, dsheuristics):
1271 m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
1272 % self.get_config_basedn().get_linearized())
1273 if dsheuristics is not None:
1274 m["dSHeuristics"] = \
1275 ldb.MessageElement(dsheuristics,
1276 ldb.FLAG_MOD_REPLACE,
1279 m["dSHeuristics"] = \
1280 ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
1284 def get_dsheuristics(self):
1285 res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
1286 % self.get_config_basedn().get_linearized(),
1287 scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
1290 elif "dSHeuristics" in res[0]:
1291 dsheuristics = res[0]["dSHeuristics"][0]
1297 def create_ou(self, ou_dn, description=None, name=None, sd=None):
1298 """Creates an organizationalUnit object
1299 :param ou_dn: dn of the new object
1300 :param description: description attribute
1301 :param name: name atttribute
1302 :param sd: security descriptor of the object, can be
1303 an SDDL string or security.descriptor type
1306 "objectClass": "organizationalUnit"}
1309 m["description"] = description
1314 m["nTSecurityDescriptor"] = ndr_pack(sd)
1317 def sequence_number(self, seq_type):
1318 """Returns the value of the sequence number according to the requested type
1319 :param seq_type: type of sequence number
1321 self.transaction_start()
1323 seq = super(SamDB, self).sequence_number(seq_type)
1325 self.transaction_cancel()
1328 self.transaction_commit()
1331 def get_dsServiceName(self):
1332 '''get the NTDS DN from the rootDSE'''
1333 res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
1334 return str(res[0]["dsServiceName"][0])
1336 def get_serverName(self):
1337 '''get the server DN from the rootDSE'''
1338 res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
1339 return str(res[0]["serverName"][0])
1341 def dns_lookup(self, dns_name, dns_partition=None):
1342 '''Do a DNS lookup in the database, returns the NDR database structures'''
1343 if dns_partition is None:
1344 return dsdb_dns.lookup(self, dns_name)
1346 return dsdb_dns.lookup(self, dns_name,
1347 dns_partition=dns_partition)
1349 def dns_extract(self, el):
1350 '''Return the NDR database structures from a dnsRecord element'''
1351 return dsdb_dns.extract(self, el)
1353 def dns_replace(self, dns_name, new_records):
1354 '''Do a DNS modification on the database, sets the NDR database
1355 structures on a DNS name
1357 return dsdb_dns.replace(self, dns_name, new_records)
1359 def dns_replace_by_dn(self, dn, new_records):
1360 '''Do a DNS modification on the database, sets the NDR database
1361 structures on a LDB DN
1363 This routine is important because if the last record on the DN
1364 is removed, this routine will put a tombstone in the record.
1366 return dsdb_dns.replace_by_dn(self, dn, new_records)
1368 def garbage_collect_tombstones(self, dn, current_time,
1369 tombstone_lifetime=None):
1370 '''garbage_collect_tombstones(lp, samdb, [dn], current_time, tombstone_lifetime)
1371 -> (num_objects_expunged, num_links_expunged)'''
1373 if not is_ad_dc_built():
1374 raise SamDBError('Cannot garbage collect tombstones: ' \
1375 'AD DC was not built')
1377 if tombstone_lifetime is None:
1378 return dsdb._dsdb_garbage_collect_tombstones(self, dn,
1381 return dsdb._dsdb_garbage_collect_tombstones(self, dn,
1385 def create_own_rid_set(self):
1386 '''create a RID set for this DSA'''
1387 return dsdb._dsdb_create_own_rid_set(self)
1389 def allocate_rid(self):
1390 '''return a new RID from the RID Pool on this DSA'''
1391 return dsdb._dsdb_allocate_rid(self)
1393 def next_free_rid(self):
1394 '''return the next free RID from the RID Pool on this DSA.
1396 :note: This function is not intended for general use, and care must be
1397 taken if it is used to generate objectSIDs. The returned RID is not
1398 formally reserved for use, creating the possibility of duplicate
1401 rid, _ = self.free_rid_bounds()
1404 def free_rid_bounds(self):
1405 '''return the low and high bounds (inclusive) of RIDs that are
1406 available for use in this DSA's current RID pool.
1408 :note: This function is not intended for general use, and care must be
1409 taken if it is used to generate objectSIDs. The returned range of
1410 RIDs is not formally reserved for use, creating the possibility of
1411 duplicate objectSIDs.
1413 # Get DN of this server's RID Set
1414 server_name_dn = ldb.Dn(self, self.get_serverName())
1415 res = self.search(base=server_name_dn,
1416 scope=ldb.SCOPE_BASE,
1417 attrs=["serverReference"])
1419 server_ref = res[0]["serverReference"]
1422 ldb.ERR_NO_SUCH_ATTRIBUTE,
1424 "Cannot find attribute serverReference of %s "
1425 "to calculate reference dn" % server_name_dn) from None
1426 server_ref_dn = ldb.Dn(self, server_ref[0].decode("utf-8"))
1428 res = self.search(base=server_ref_dn,
1429 scope=ldb.SCOPE_BASE,
1430 attrs=["rIDSetReferences"])
1432 rid_set_refs = res[0]["rIDSetReferences"]
1435 ldb.ERR_NO_SUCH_ATTRIBUTE,
1437 "Cannot find attribute rIDSetReferences of %s "
1438 "to calculate reference dn" % server_ref_dn) from None
1439 rid_set_dn = ldb.Dn(self, rid_set_refs[0].decode("utf-8"))
1441 # Get the alloc pools and next RID of this RID Set
1442 res = self.search(base=rid_set_dn,
1443 scope=ldb.SCOPE_BASE,
1444 attrs=["rIDAllocationPool",
1445 "rIDPreviousAllocationPool",
1448 uint32_max = 2**32 - 1
1449 uint64_max = 2**64 - 1
1452 alloc_pool = int(res[0]["rIDAllocationPool"][0])
1454 alloc_pool = uint64_max
1455 if alloc_pool == uint64_max:
1456 raise ldb.LdbError(ldb.ERR_OPERATIONS_ERROR,
1457 "Bad RID Set %s" % rid_set_dn)
1460 prev_pool = int(res[0]["rIDPreviousAllocationPool"][0])
1462 prev_pool = uint64_max
1464 next_rid = int(res[0]["rIDNextRID"][0])
1466 next_rid = uint32_max
1468 # If we never used a pool, set up our first pool
1469 if prev_pool == uint64_max or next_rid == uint32_max:
1470 prev_pool = alloc_pool
1471 next_rid = prev_pool & uint32_max
1475 # Now check if our current pool is still usable
1476 prev_pool_lo = prev_pool & uint32_max
1477 prev_pool_hi = prev_pool >> 32
1478 if next_rid > prev_pool_hi:
1479 # We need a new pool, check if we already have a new one
1480 # Otherwise we return an error code.
1481 if alloc_pool == prev_pool:
1482 raise ldb.LdbError(ldb.ERR_OPERATIONS_ERROR,
1483 "RID pools out of RIDs")
1485 # Now use the new pool
1486 prev_pool = alloc_pool
1487 prev_pool_lo = prev_pool & uint32_max
1488 prev_pool_hi = prev_pool >> 32
1489 next_rid = prev_pool_lo
1491 if next_rid < prev_pool_lo or next_rid > prev_pool_hi:
1492 raise ldb.LdbError(ldb.ERR_OPERATIONS_ERROR,
1493 "Bad RID chosen %d from range %d-%d" %
1494 (next_rid, prev_pool_lo, prev_pool_hi))
1496 return next_rid, prev_pool_hi
1498 def normalize_dn_in_domain(self, dn):
1499 '''return a new DN expanded by adding the domain DN
1501 If the dn is already a child of the domain DN, just
1504 :param dn: relative dn
1506 domain_dn = ldb.Dn(self, self.domain_dn())
1508 if isinstance(dn, ldb.Dn):
1511 full_dn = ldb.Dn(self, dn)
1512 if not full_dn.is_child_of(domain_dn):
1513 full_dn.add_base(domain_dn)
1516 class dsdb_Dn(object):
1517 '''a class for binary DN'''
1519 def __init__(self, samdb, dnstring, syntax_oid=None):
1520 '''create a dsdb_Dn'''
1521 if syntax_oid is None:
1522 # auto-detect based on string
1523 if dnstring.startswith("B:"):
1524 syntax_oid = dsdb.DSDB_SYNTAX_BINARY_DN
1525 elif dnstring.startswith("S:"):
1526 syntax_oid = dsdb.DSDB_SYNTAX_STRING_DN
1528 syntax_oid = dsdb.DSDB_SYNTAX_OR_NAME
1529 if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_STRING_DN]:
1531 colons = dnstring.split(':')
1533 raise RuntimeError("Invalid DN %s" % dnstring)
1534 prefix_len = 4 + len(colons[1]) + int(colons[1])
1535 self.prefix = dnstring[0:prefix_len]
1536 self.binary = self.prefix[3 + len(colons[1]):-1]
1537 self.dnstring = dnstring[prefix_len:]
1539 self.dnstring = dnstring
1542 self.dn = ldb.Dn(samdb, self.dnstring)
1545 return self.prefix + str(self.dn.extended_str(mode=1))
1547 def __cmp__(self, other):
1548 ''' compare dsdb_Dn values similar to parsed_dn_compare()'''
1551 guid1 = dn1.dn.get_extended_component("GUID")
1552 guid2 = dn2.dn.get_extended_component("GUID")
1554 v = cmp(guid1, guid2)
1557 v = cmp(dn1.binary, dn2.binary)
1560 # In Python3, __cmp__ is replaced by these 6 methods
1561 def __eq__(self, other):
1562 return self.__cmp__(other) == 0
1564 def __ne__(self, other):
1565 return self.__cmp__(other) != 0
1567 def __lt__(self, other):
1568 return self.__cmp__(other) < 0
1570 def __le__(self, other):
1571 return self.__cmp__(other) <= 0
1573 def __gt__(self, other):
1574 return self.__cmp__(other) > 0
1576 def __ge__(self, other):
1577 return self.__cmp__(other) >= 0
1579 def get_binary_integer(self):
1580 '''return binary part of a dsdb_Dn as an integer, or None'''
1581 if self.prefix == '':
1583 return int(self.binary, 16)
1585 def get_bytes(self):
1586 '''return binary as a byte string'''
1587 return binascii.unhexlify(self.binary)