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
39 __docformat__
= "restructuredText"
42 def get_default_backend_store():
45 class SamDBError(Exception):
48 class SamDBNotFoundError(SamDBError
):
51 class SamDB(samba
.Ldb
):
52 """The SAM database."""
57 def __init__(self
, url
=None, lp
=None, modules_dir
=None, session_info
=None,
58 credentials
=None, flags
=ldb
.FLG_DONT_CREATE_DB
,
59 options
=None, global_schema
=True,
60 auto_connect
=True, am_rodc
=None):
64 elif url
is None and lp
is not None:
69 super(SamDB
, self
).__init
__(url
=url
, lp
=lp
, modules_dir
=modules_dir
,
70 session_info
=session_info
, credentials
=credentials
, flags
=flags
,
74 dsdb
._dsdb
_set
_global
_schema
(self
)
76 if am_rodc
is not None:
77 dsdb
._dsdb
_set
_am
_rodc
(self
, am_rodc
)
79 def connect(self
, url
=None, flags
=0, options
=None):
80 '''connect to the database'''
81 if self
.lp
is not None and not os
.path
.exists(url
):
82 url
= self
.lp
.private_path(url
)
85 super(SamDB
, self
).connect(url
=url
, flags
=flags
,
89 '''return True if we are an RODC'''
90 return dsdb
._am
_rodc
(self
)
93 '''return True if we are an PDC emulator'''
94 return dsdb
._am
_pdc
(self
)
97 '''return the domain DN'''
98 return str(self
.get_default_basedn())
101 '''return the schema partition dn'''
102 return str(self
.get_schema_basedn())
104 def disable_account(self
, search_filter
):
105 """Disables an account
107 :param search_filter: LDAP filter to find the user (eg
111 flags
= samba
.dsdb
.UF_ACCOUNTDISABLE
112 self
.toggle_userAccountFlags(search_filter
, flags
, on
=True)
114 def enable_account(self
, search_filter
):
115 """Enables an account
117 :param search_filter: LDAP filter to find the user (eg
121 flags
= samba
.dsdb
.UF_ACCOUNTDISABLE | samba
.dsdb
.UF_PASSWD_NOTREQD
122 self
.toggle_userAccountFlags(search_filter
, flags
, on
=False)
124 def toggle_userAccountFlags(self
, search_filter
, flags
, flags_str
=None,
125 on
=True, strict
=False):
126 """Toggle_userAccountFlags
128 :param search_filter: LDAP filter to find the user (eg
130 :param flags: samba.dsdb.UF_* flags
131 :param on: on=True (default) => set, on=False => unset
132 :param strict: strict=False (default) ignore if no action is needed
133 strict=True raises an Exception if...
135 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
136 expression
=search_filter
, attrs
=["userAccountControl"])
138 raise Exception("Unable to find account where '%s'" % search_filter
)
139 assert(len(res
) == 1)
140 account_dn
= res
[0].dn
142 old_uac
= int(res
[0]["userAccountControl"][0])
144 if strict
and (old_uac
& flags
):
145 error
= "Account flag(s) '%s' already set" % flags_str
146 raise Exception(error
)
148 new_uac
= old_uac | flags
150 if strict
and not (old_uac
& flags
):
151 error
= "Account flag(s) '%s' already unset" % flags_str
152 raise Exception(error
)
154 new_uac
= old_uac
& ~flags
156 if old_uac
== new_uac
:
162 delete: userAccountControl
163 userAccountControl: %u
164 add: userAccountControl
165 userAccountControl: %u
166 """ % (account_dn
, old_uac
, new_uac
)
167 self
.modify_ldif(mod
)
169 def force_password_change_at_next_login(self
, search_filter
):
170 """Forces a password change at next login
172 :param search_filter: LDAP filter to find the user (eg
175 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
176 expression
=search_filter
, attrs
=[])
178 raise Exception('Unable to find user "%s"' % search_filter
)
179 assert(len(res
) == 1)
188 self
.modify_ldif(mod
)
190 def unlock_account(self
, search_filter
):
191 """Unlock a user account by resetting lockoutTime to 0.
192 This does also reset the badPwdCount to 0.
194 :param search_filter: LDAP filter to find the user (e.g.
195 sAMAccountName=username)
197 res
= self
.search(base
=self
.domain_dn(),
198 scope
=ldb
.SCOPE_SUBTREE
,
199 expression
=search_filter
,
202 raise SamDBNotFoundError('Unable to find user "%s"' % search_filter
)
204 raise SamDBError('User "%s" is not unique' % search_filter
)
213 self
.modify_ldif(mod
)
215 def newgroup(self
, groupname
, groupou
=None, grouptype
=None,
216 description
=None, mailaddress
=None, notes
=None, sd
=None,
217 gidnumber
=None, nisdomain
=None):
218 """Adds a new group with additional parameters
220 :param groupname: Name of the new group
221 :param grouptype: Type of the new group
222 :param description: Description of the new group
223 :param mailaddress: Email address of the new group
224 :param notes: Notes of the new group
225 :param gidnumber: GID Number of the new group
226 :param nisdomain: NIS Domain Name of the new group
227 :param sd: security descriptor of the object
230 group_dn
= "CN=%s,%s,%s" % (groupname
, (groupou
or "CN=Users"), self
.domain_dn())
232 # The new user record. Note the reliance on the SAMLDB module which
233 # fills in the default information
234 ldbmessage
= {"dn": group_dn
,
235 "sAMAccountName": groupname
,
236 "objectClass": "group"}
238 if grouptype
is not None:
239 ldbmessage
["groupType"] = normalise_int32(grouptype
)
241 if description
is not None:
242 ldbmessage
["description"] = description
244 if mailaddress
is not None:
245 ldbmessage
["mail"] = mailaddress
247 if notes
is not None:
248 ldbmessage
["info"] = notes
250 if gidnumber
is not None:
251 ldbmessage
["gidNumber"] = normalise_int32(gidnumber
)
253 if nisdomain
is not None:
254 ldbmessage
["msSFU30Name"] = groupname
255 ldbmessage
["msSFU30NisDomain"] = nisdomain
258 ldbmessage
["nTSecurityDescriptor"] = ndr_pack(sd
)
262 def deletegroup(self
, groupname
):
265 :param groupname: Name of the target group
268 groupfilter
= "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb
.binary_encode(groupname
), "CN=Group,CN=Schema,CN=Configuration", self
.domain_dn())
269 self
.transaction_start()
271 targetgroup
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
272 expression
=groupfilter
, attrs
=[])
273 if len(targetgroup
) == 0:
274 raise Exception('Unable to find group "%s"' % groupname
)
275 assert(len(targetgroup
) == 1)
276 self
.delete(targetgroup
[0].dn
)
278 self
.transaction_cancel()
281 self
.transaction_commit()
283 def group_member_filter(self
, member
, member_types
):
286 all_member_types
= [ 'user',
293 if 'all' in member_types
:
294 member_types
= all_member_types
296 for member_type
in member_types
:
297 if member_type
not in all_member_types
:
298 raise Exception('Invalid group member type "%s". '
299 'Valid types are %s and all.' %
300 (member_type
, ", ".join(all_member_types
)))
302 if 'user' in member_types
:
303 filter += ('(&(sAMAccountName=%s)(samAccountType=%d))' %
304 (ldb
.binary_encode(member
), dsdb
.ATYPE_NORMAL_ACCOUNT
))
305 if 'group' in member_types
:
306 filter += ('(&(sAMAccountName=%s)'
307 '(objectClass=group)'
308 '(!(groupType:1.2.840.113556.1.4.803:=1)))' %
309 ldb
.binary_encode(member
))
310 if 'computer' in member_types
:
311 samaccountname
= member
312 if member
[-1] != '$':
313 samaccountname
= "%s$" % member
314 filter += ('(&(samAccountType=%d)'
315 '(!(objectCategory=msDS-ManagedServiceAccount))'
316 '(sAMAccountName=%s))' %
317 (dsdb
.ATYPE_WORKSTATION_TRUST
,
318 ldb
.binary_encode(samaccountname
)))
319 if 'serviceaccount' in member_types
:
320 samaccountname
= member
321 if member
[-1] != '$':
322 samaccountname
= "%s$" % member
323 filter += ('(&(samAccountType=%d)'
324 '(objectCategory=msDS-ManagedServiceAccount)'
325 '(sAMAccountName=%s))' %
326 (dsdb
.ATYPE_WORKSTATION_TRUST
,
327 ldb
.binary_encode(samaccountname
)))
328 if 'contact' in member_types
:
329 filter += ('(&(objectCategory=Person)(!(objectSid=*))(name=%s))' %
330 ldb
.binary_encode(member
))
332 filter = "(|%s)" % filter
336 def add_remove_group_members(self
, groupname
, members
,
337 add_members_operation
=True,
338 member_types
=[ 'user', 'group', 'computer' ],
339 member_base_dn
=None):
340 """Adds or removes group members
342 :param groupname: Name of the target group
343 :param members: list of group members
344 :param add_members_operation: Defines if its an add or remove
348 groupfilter
= "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
349 ldb
.binary_encode(groupname
), "CN=Group,CN=Schema,CN=Configuration", self
.domain_dn())
351 self
.transaction_start()
353 targetgroup
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
354 expression
=groupfilter
, attrs
=['member'])
355 if len(targetgroup
) == 0:
356 raise Exception('Unable to find group "%s"' % groupname
)
357 assert(len(targetgroup
) == 1)
361 addtargettogroup
= """
364 """ % (str(targetgroup
[0].dn
))
366 for member
in members
:
367 targetmember_dn
= None
368 if member_base_dn
is None:
369 member_base_dn
= self
.domain_dn()
372 membersid
= security
.dom_sid(member
)
373 targetmember_dn
= "<SID=%s>" % str(membersid
)
374 except TypeError as e
:
377 if targetmember_dn
is None:
379 member_dn
= ldb
.Dn(self
, member
)
380 if member_dn
.get_linearized() == member_dn
.extended_str(1):
381 full_member_dn
= self
.normalize_dn_in_domain(member_dn
)
383 full_member_dn
= member_dn
384 targetmember_dn
= full_member_dn
.extended_str(1)
385 except ValueError as e
:
388 if targetmember_dn
is None:
389 filter = self
.group_member_filter(member
, member_types
)
390 targetmember
= self
.search(base
=member_base_dn
,
391 scope
=ldb
.SCOPE_SUBTREE
,
395 if len(targetmember
) > 1:
396 targetmemberlist_str
= ""
397 for msg
in targetmember
:
398 targetmemberlist_str
+= "%s\n" % msg
.get("dn")
399 raise Exception('Found multiple results for "%s":\n%s' %
400 (member
, targetmemberlist_str
))
401 if len(targetmember
) != 1:
402 raise Exception('Unable to find "%s". Operation cancelled.' % member
)
403 targetmember_dn
= targetmember
[0].dn
.extended_str(1)
405 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']]):
407 addtargettogroup
+= """add: member
409 """ % (str(targetmember_dn
))
411 elif add_members_operation
is False and (targetgroup
[0].get('member') is not None and get_bytes(targetmember_dn
) in targetgroup
[0]['member']):
413 addtargettogroup
+= """delete: member
415 """ % (str(targetmember_dn
))
418 self
.modify_ldif(addtargettogroup
)
421 self
.transaction_cancel()
424 self
.transaction_commit()
426 def prepare_attr_replace(self
, msg
, old
, attr_name
, value
):
427 """Changes the MessageElement with the given attr_name of the
428 given Message. If the value is "" set an empty value and the flag
429 FLAG_MOD_DELETE, otherwise set the new value and FLAG_MOD_REPLACE.
430 If the value is None or the Message contains the attr_name with this
431 value, nothing will changed."""
432 # skip unchanged attribute
435 if attr_name
in old
and str(value
) == str(old
[attr_name
]):
441 el
= ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
, attr_name
)
446 el
= ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, attr_name
)
449 def fullname_from_names(self
, given_name
=None, initials
=None, surname
=None,
450 old_attrs
={}, fallback_default
=""):
451 """Prepares new combined fullname, using the name parts.
452 Used for things like displayName or cn.
453 Use the original name values, if no new one is specified."""
455 attrs
= {"givenName": given_name
,
456 "initials": initials
,
459 # if the attribute is not specified, try to use the old one
460 for attr_name
, attr_value
in attrs
.items():
461 if attr_value
== None and attr_name
in old_attrs
:
462 attrs
[attr_name
] = str(old_attrs
[attr_name
])
464 # add '.' to initials if initals are not None and not "" and if the initials
465 # don't have already a '.' at the end
466 if attrs
["initials"] and not attrs
["initials"].endswith('.'):
467 attrs
["initials"] += '.'
469 # remove empty values (None and '')
470 attrs_values
= list(filter(None, attrs
.values()))
472 # fullname is the combination of not-empty values as string, separated by ' '
473 fullname
= ' '.join(attrs_values
)
476 return fallback_default
480 def newuser(self
, username
, password
,
481 force_password_change_at_next_login_req
=False,
482 useusernameascn
=False, userou
=None, surname
=None, givenname
=None,
483 initials
=None, profilepath
=None, scriptpath
=None, homedrive
=None,
484 homedirectory
=None, jobtitle
=None, department
=None, company
=None,
485 description
=None, mailaddress
=None, internetaddress
=None,
486 telephonenumber
=None, physicaldeliveryoffice
=None, sd
=None,
487 setpassword
=True, uidnumber
=None, gidnumber
=None, gecos
=None,
488 loginshell
=None, uid
=None, nisdomain
=None, unixhome
=None,
489 smartcard_required
=False):
490 """Adds a new user with additional parameters
492 :param username: Name of the new user
493 :param password: Password for the new user
494 :param force_password_change_at_next_login_req: Force password change
495 :param useusernameascn: Use username as cn rather that firstname +
497 :param userou: Object container (without domainDN postfix) for new user
498 :param surname: Surname of the new user
499 :param givenname: First name of the new user
500 :param initials: Initials of the new user
501 :param profilepath: Profile path of the new user
502 :param scriptpath: Logon script path of the new user
503 :param homedrive: Home drive of the new user
504 :param homedirectory: Home directory of the new user
505 :param jobtitle: Job title of the new user
506 :param department: Department of the new user
507 :param company: Company of the new user
508 :param description: of the new user
509 :param mailaddress: Email address of the new user
510 :param internetaddress: Home page of the new user
511 :param telephonenumber: Phone number of the new user
512 :param physicaldeliveryoffice: Office location of the new user
513 :param sd: security descriptor of the object
514 :param setpassword: optionally disable password reset
515 :param uidnumber: RFC2307 Unix numeric UID of the new user
516 :param gidnumber: RFC2307 Unix primary GID of the new user
517 :param gecos: RFC2307 Unix GECOS field of the new user
518 :param loginshell: RFC2307 Unix login shell of the new user
519 :param uid: RFC2307 Unix username of the new user
520 :param nisdomain: RFC2307 Unix NIS domain of the new user
521 :param unixhome: RFC2307 Unix home directory of the new user
522 :param smartcard_required: set the UF_SMARTCARD_REQUIRED bit of the new user
525 displayname
= self
.fullname_from_names(given_name
=givenname
,
529 if useusernameascn
is None and displayname
!= "":
532 user_dn
= "CN=%s,%s,%s" % (cn
, (userou
or "CN=Users"), self
.domain_dn())
534 dnsdomain
= ldb
.Dn(self
, self
.domain_dn()).canonical_str().replace("/", "")
535 user_principal_name
= "%s@%s" % (username
, dnsdomain
)
536 # The new user record. Note the reliance on the SAMLDB module which
537 # fills in the default information
538 ldbmessage
= {"dn": user_dn
,
539 "sAMAccountName": username
,
540 "userPrincipalName": user_principal_name
,
541 "objectClass": "user"}
543 if smartcard_required
:
544 ldbmessage
["userAccountControl"] = str(dsdb
.UF_NORMAL_ACCOUNT |
545 dsdb
.UF_SMARTCARD_REQUIRED
)
548 if surname
is not None:
549 ldbmessage
["sn"] = surname
551 if givenname
is not None:
552 ldbmessage
["givenName"] = givenname
554 if displayname
!= "":
555 ldbmessage
["displayName"] = displayname
556 ldbmessage
["name"] = displayname
558 if initials
is not None:
559 ldbmessage
["initials"] = '%s.' % initials
561 if profilepath
is not None:
562 ldbmessage
["profilePath"] = profilepath
564 if scriptpath
is not None:
565 ldbmessage
["scriptPath"] = scriptpath
567 if homedrive
is not None:
568 ldbmessage
["homeDrive"] = homedrive
570 if homedirectory
is not None:
571 ldbmessage
["homeDirectory"] = homedirectory
573 if jobtitle
is not None:
574 ldbmessage
["title"] = jobtitle
576 if department
is not None:
577 ldbmessage
["department"] = department
579 if company
is not None:
580 ldbmessage
["company"] = company
582 if description
is not None:
583 ldbmessage
["description"] = description
585 if mailaddress
is not None:
586 ldbmessage
["mail"] = mailaddress
588 if internetaddress
is not None:
589 ldbmessage
["wWWHomePage"] = internetaddress
591 if telephonenumber
is not None:
592 ldbmessage
["telephoneNumber"] = telephonenumber
594 if physicaldeliveryoffice
is not None:
595 ldbmessage
["physicalDeliveryOfficeName"] = physicaldeliveryoffice
598 ldbmessage
["nTSecurityDescriptor"] = ndr_pack(sd
)
601 if any(map(lambda b
: b
is not None, (uid
, uidnumber
, gidnumber
, gecos
,
602 loginshell
, nisdomain
, unixhome
))):
603 ldbmessage2
= ldb
.Message()
604 ldbmessage2
.dn
= ldb
.Dn(self
, user_dn
)
606 ldbmessage2
["uid"] = ldb
.MessageElement(str(uid
), ldb
.FLAG_MOD_REPLACE
, 'uid')
607 if uidnumber
is not None:
608 ldbmessage2
["uidNumber"] = ldb
.MessageElement(str(uidnumber
), ldb
.FLAG_MOD_REPLACE
, 'uidNumber')
609 if gidnumber
is not None:
610 ldbmessage2
["gidNumber"] = ldb
.MessageElement(str(gidnumber
), ldb
.FLAG_MOD_REPLACE
, 'gidNumber')
611 if gecos
is not None:
612 ldbmessage2
["gecos"] = ldb
.MessageElement(str(gecos
), ldb
.FLAG_MOD_REPLACE
, 'gecos')
613 if loginshell
is not None:
614 ldbmessage2
["loginShell"] = ldb
.MessageElement(str(loginshell
), ldb
.FLAG_MOD_REPLACE
, 'loginShell')
615 if unixhome
is not None:
616 ldbmessage2
["unixHomeDirectory"] = ldb
.MessageElement(
617 str(unixhome
), ldb
.FLAG_MOD_REPLACE
, 'unixHomeDirectory')
618 if nisdomain
is not None:
619 ldbmessage2
["msSFU30NisDomain"] = ldb
.MessageElement(
620 str(nisdomain
), ldb
.FLAG_MOD_REPLACE
, 'msSFU30NisDomain')
621 ldbmessage2
["msSFU30Name"] = ldb
.MessageElement(
622 str(username
), ldb
.FLAG_MOD_REPLACE
, 'msSFU30Name')
623 ldbmessage2
["unixUserPassword"] = ldb
.MessageElement(
624 'ABCD!efgh12345$67890', ldb
.FLAG_MOD_REPLACE
,
627 self
.transaction_start()
631 self
.modify(ldbmessage2
)
633 # Sets the password for it
635 self
.setpassword(("(distinguishedName=%s)" %
636 ldb
.binary_encode(user_dn
)),
638 force_password_change_at_next_login_req
)
640 self
.transaction_cancel()
643 self
.transaction_commit()
646 fullcontactname
=None,
657 internetaddress
=None,
658 telephonenumber
=None,
660 physicaldeliveryoffice
=None):
661 """Adds a new contact with additional parameters
663 :param fullcontactname: Optional full name of the new contact
664 :param ou: Object container for new contact
665 :param surname: Surname of the new contact
666 :param givenname: First name of the new contact
667 :param initials: Initials of the new contact
668 :param displayname: displayName of the new contact
669 :param jobtitle: Job title of the new contact
670 :param department: Department of the new contact
671 :param company: Company of the new contact
672 :param description: Description of the new contact
673 :param mailaddress: Email address of the new contact
674 :param internetaddress: Home page of the new contact
675 :param telephonenumber: Phone number of the new contact
676 :param mobilenumber: Primary mobile number of the new contact
677 :param physicaldeliveryoffice: Office location of the new contact
680 # Prepare the contact name like the RSAT, using the name parts.
681 cn
= self
.fullname_from_names(given_name
=givenname
,
685 # Use the specified fullcontactname instead of the previously prepared
686 # contact name, if it is specified.
687 # This is similar to the "Full name" value of the RSAT.
688 if fullcontactname
is not None:
691 if fullcontactname
is None and cn
== "":
692 raise Exception('No name for contact specified')
694 contactcontainer_dn
= self
.domain_dn()
696 contactcontainer_dn
= self
.normalize_dn_in_domain(ou
)
698 contact_dn
= "CN=%s,%s" % (cn
, contactcontainer_dn
)
700 ldbmessage
= {"dn": contact_dn
,
701 "objectClass": "contact",
704 if surname
is not None:
705 ldbmessage
["sn"] = surname
707 if givenname
is not None:
708 ldbmessage
["givenName"] = givenname
710 if displayname
is not None:
711 ldbmessage
["displayName"] = displayname
713 if initials
is not None:
714 ldbmessage
["initials"] = '%s.' % initials
716 if jobtitle
is not None:
717 ldbmessage
["title"] = jobtitle
719 if department
is not None:
720 ldbmessage
["department"] = department
722 if company
is not None:
723 ldbmessage
["company"] = company
725 if description
is not None:
726 ldbmessage
["description"] = description
728 if mailaddress
is not None:
729 ldbmessage
["mail"] = mailaddress
731 if internetaddress
is not None:
732 ldbmessage
["wWWHomePage"] = internetaddress
734 if telephonenumber
is not None:
735 ldbmessage
["telephoneNumber"] = telephonenumber
737 if mobilenumber
is not None:
738 ldbmessage
["mobile"] = mobilenumber
740 if physicaldeliveryoffice
is not None:
741 ldbmessage
["physicalDeliveryOfficeName"] = physicaldeliveryoffice
747 def newcomputer(self
, computername
, computerou
=None, description
=None,
748 prepare_oldjoin
=False, ip_address_list
=None,
749 service_principal_name_list
=None):
750 """Adds a new user with additional parameters
752 :param computername: Name of the new computer
753 :param computerou: Object container for new computer
754 :param description: Description of the new computer
755 :param prepare_oldjoin: Preset computer password for oldjoin mechanism
756 :param ip_address_list: ip address list for DNS A or AAAA record
757 :param service_principal_name_list: string list of servicePincipalName
760 cn
= re
.sub(r
"\$$", "", computername
)
762 raise Exception('Illegal computername "%s"' % computername
)
763 samaccountname
= "%s$" % cn
765 computercontainer_dn
= "CN=Computers,%s" % self
.domain_dn()
767 computercontainer_dn
= self
.normalize_dn_in_domain(computerou
)
769 computer_dn
= "CN=%s,%s" % (cn
, computercontainer_dn
)
771 ldbmessage
= {"dn": computer_dn
,
772 "sAMAccountName": samaccountname
,
773 "objectClass": "computer",
776 if description
is not None:
777 ldbmessage
["description"] = description
779 if service_principal_name_list
:
780 ldbmessage
["servicePrincipalName"] = service_principal_name_list
782 accountcontrol
= str(dsdb
.UF_WORKSTATION_TRUST_ACCOUNT |
783 dsdb
.UF_ACCOUNTDISABLE
)
785 accountcontrol
= str(dsdb
.UF_WORKSTATION_TRUST_ACCOUNT
)
786 ldbmessage
["userAccountControl"] = accountcontrol
789 ldbmessage
['dNSHostName'] = '{}.{}'.format(
790 cn
, self
.domain_dns_name())
792 self
.transaction_start()
797 password
= cn
.lower()
798 self
.setpassword(("(distinguishedName=%s)" %
799 ldb
.binary_encode(computer_dn
)),
802 self
.transaction_cancel()
805 self
.transaction_commit()
807 def deleteuser(self
, username
):
810 :param username: Name of the target user
813 filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb
.binary_encode(username
), "CN=Person,CN=Schema,CN=Configuration", self
.domain_dn())
814 self
.transaction_start()
816 target
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
817 expression
=filter, attrs
=[])
819 raise Exception('Unable to find user "%s"' % username
)
820 assert(len(target
) == 1)
821 self
.delete(target
[0].dn
)
823 self
.transaction_cancel()
826 self
.transaction_commit()
828 def setpassword(self
, search_filter
, password
,
829 force_change_at_next_login
=False, username
=None):
830 """Sets the password for a user
832 :param search_filter: LDAP filter to find the user (eg
834 :param password: Password for the user
835 :param force_change_at_next_login: Force password change
837 self
.transaction_start()
839 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
840 expression
=search_filter
, attrs
=[])
842 raise Exception('Unable to find user "%s"' % (username
or search_filter
))
844 raise Exception('Matched %u multiple users with filter "%s"' % (len(res
), search_filter
))
846 if not isinstance(password
, str):
847 pw
= password
.decode('utf-8')
850 pw
= ('"' + pw
+ '"').encode('utf-16-le')
856 """ % (user_dn
, base64
.b64encode(pw
).decode('utf-8'))
858 self
.modify_ldif(setpw
)
860 if force_change_at_next_login
:
861 self
.force_password_change_at_next_login(
862 "(distinguishedName=" + str(user_dn
) + ")")
864 # modify the userAccountControl to remove the disabled bit
865 self
.enable_account(search_filter
)
867 self
.transaction_cancel()
870 self
.transaction_commit()
872 def setexpiry(self
, search_filter
, expiry_seconds
, no_expiry_req
=False):
873 """Sets the account expiry for a user
875 :param search_filter: LDAP filter to find the user (eg
877 :param expiry_seconds: expiry time from now in seconds
878 :param no_expiry_req: if set, then don't expire password
880 self
.transaction_start()
882 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
883 expression
=search_filter
,
884 attrs
=["userAccountControl", "accountExpires"])
886 raise Exception('Unable to find user "%s"' % search_filter
)
887 assert(len(res
) == 1)
890 userAccountControl
= int(res
[0]["userAccountControl"][0])
891 accountExpires
= int(res
[0]["accountExpires"][0])
893 userAccountControl
= userAccountControl |
0x10000
896 userAccountControl
= userAccountControl
& ~
0x10000
897 accountExpires
= samba
.unix2nttime(expiry_seconds
+ int(time
.time()))
902 replace: userAccountControl
903 userAccountControl: %u
904 replace: accountExpires
906 """ % (user_dn
, userAccountControl
, accountExpires
)
908 self
.modify_ldif(setexp
)
910 self
.transaction_cancel()
913 self
.transaction_commit()
915 def set_domain_sid(self
, sid
):
916 """Change the domain SID used by this LDB.
918 :param sid: The new domain sid to use.
920 dsdb
._samdb
_set
_domain
_sid
(self
, sid
)
922 def get_domain_sid(self
):
923 """Read the domain SID used by this LDB. """
924 return dsdb
._samdb
_get
_domain
_sid
(self
)
926 domain_sid
= property(get_domain_sid
, set_domain_sid
,
927 doc
="SID for the domain")
929 def set_invocation_id(self
, invocation_id
):
930 """Set the invocation id for this SamDB handle.
932 :param invocation_id: GUID of the invocation id.
934 dsdb
._dsdb
_set
_ntds
_invocation
_id
(self
, invocation_id
)
936 def get_invocation_id(self
):
937 """Get the invocation_id id"""
938 return dsdb
._samdb
_ntds
_invocation
_id
(self
)
940 invocation_id
= property(get_invocation_id
, set_invocation_id
,
941 doc
="Invocation ID GUID")
943 def get_oid_from_attid(self
, attid
):
944 return dsdb
._dsdb
_get
_oid
_from
_attid
(self
, attid
)
946 def get_attid_from_lDAPDisplayName(self
, ldap_display_name
,
948 '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
949 return dsdb
._dsdb
_get
_attid
_from
_lDAPDisplayName
(self
,
950 ldap_display_name
, is_schema_nc
)
952 def get_syntax_oid_from_lDAPDisplayName(self
, ldap_display_name
):
953 '''return the syntax OID for a LDAP attribute as a string'''
954 return dsdb
._dsdb
_get
_syntax
_oid
_from
_lDAPDisplayName
(self
, ldap_display_name
)
956 def get_systemFlags_from_lDAPDisplayName(self
, ldap_display_name
):
957 '''return the systemFlags for a LDAP attribute as a integer'''
958 return dsdb
._dsdb
_get
_systemFlags
_from
_lDAPDisplayName
(self
, ldap_display_name
)
960 def get_linkId_from_lDAPDisplayName(self
, ldap_display_name
):
961 '''return the linkID for a LDAP attribute as a integer'''
962 return dsdb
._dsdb
_get
_linkId
_from
_lDAPDisplayName
(self
, ldap_display_name
)
964 def get_lDAPDisplayName_by_attid(self
, attid
):
965 '''return the lDAPDisplayName from an integer DRS attribute ID'''
966 return dsdb
._dsdb
_get
_lDAPDisplayName
_by
_attid
(self
, attid
)
968 def get_backlink_from_lDAPDisplayName(self
, ldap_display_name
):
969 '''return the attribute name of the corresponding backlink from the name
970 of a forward link attribute. If there is no backlink return None'''
971 return dsdb
._dsdb
_get
_backlink
_from
_lDAPDisplayName
(self
, ldap_display_name
)
973 def set_ntds_settings_dn(self
, ntds_settings_dn
):
974 """Set the NTDS Settings DN, as would be returned on the dsServiceName
977 This allows the DN to be set before the database fully exists
979 :param ntds_settings_dn: The new DN to use
981 dsdb
._samdb
_set
_ntds
_settings
_dn
(self
, ntds_settings_dn
)
983 def get_ntds_GUID(self
):
984 """Get the NTDS objectGUID"""
985 return dsdb
._samdb
_ntds
_objectGUID
(self
)
987 def server_site_name(self
):
988 """Get the server site name"""
989 return dsdb
._samdb
_server
_site
_name
(self
)
991 def host_dns_name(self
):
992 """return the DNS name of this host"""
993 res
= self
.search(base
='', scope
=ldb
.SCOPE_BASE
, attrs
=['dNSHostName'])
994 return str(res
[0]['dNSHostName'][0])
996 def domain_dns_name(self
):
997 """return the DNS name of the domain root"""
998 domain_dn
= self
.get_default_basedn()
999 return domain_dn
.canonical_str().split('/')[0]
1001 def domain_netbios_name(self
):
1002 """return the NetBIOS name of the domain root"""
1003 domain_dn
= self
.get_default_basedn()
1004 dns_name
= self
.domain_dns_name()
1005 filter = "(&(objectClass=crossRef)(nETBIOSName=*)(ncName=%s)(dnsroot=%s))" % (domain_dn
, dns_name
)
1006 partitions_dn
= self
.get_partitions_dn()
1007 res
= self
.search(partitions_dn
,
1008 scope
=ldb
.SCOPE_ONELEVEL
,
1011 netbios_domain
= res
[0]["nETBIOSName"][0].decode()
1014 return netbios_domain
1016 def forest_dns_name(self
):
1017 """return the DNS name of the forest root"""
1018 forest_dn
= self
.get_root_basedn()
1019 return forest_dn
.canonical_str().split('/')[0]
1021 def load_partition_usn(self
, base_dn
):
1022 return dsdb
._dsdb
_load
_partition
_usn
(self
, base_dn
)
1024 def set_schema(self
, schema
, write_indices_and_attributes
=True):
1025 self
.set_schema_from_ldb(schema
.ldb
, write_indices_and_attributes
=write_indices_and_attributes
)
1027 def set_schema_from_ldb(self
, ldb_conn
, write_indices_and_attributes
=True):
1028 dsdb
._dsdb
_set
_schema
_from
_ldb
(self
, ldb_conn
, write_indices_and_attributes
)
1030 def set_schema_update_now(self
):
1034 add: schemaUpdateNow
1037 self
.modify_ldif(ldif
)
1039 def dsdb_DsReplicaAttribute(self
, ldb
, ldap_display_name
, ldif_elements
):
1040 '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
1041 return dsdb
._dsdb
_DsReplicaAttribute
(ldb
, ldap_display_name
, ldif_elements
)
1043 def dsdb_normalise_attributes(self
, ldb
, ldap_display_name
, ldif_elements
):
1044 '''normalise a list of attribute values'''
1045 return dsdb
._dsdb
_normalise
_attributes
(ldb
, ldap_display_name
, ldif_elements
)
1047 def get_attribute_from_attid(self
, attid
):
1048 """ Get from an attid the associated attribute
1050 :param attid: The attribute id for searched attribute
1051 :return: The name of the attribute associated with this id
1053 if len(self
.hash_oid_name
.keys()) == 0:
1054 self
._populate
_oid
_attid
()
1055 if self
.get_oid_from_attid(attid
) in self
.hash_oid_name
:
1056 return self
.hash_oid_name
[self
.get_oid_from_attid(attid
)]
1060 def _populate_oid_attid(self
):
1061 """Populate the hash hash_oid_name.
1063 This hash contains the oid of the attribute as a key and
1064 its display name as a value
1066 self
.hash_oid_name
= {}
1067 res
= self
.search(expression
="objectClass=attributeSchema",
1068 controls
=["search_options:1:2"],
1069 attrs
=["attributeID",
1073 strDisplay
= str(e
.get("lDAPDisplayName"))
1074 self
.hash_oid_name
[str(e
.get("attributeID"))] = strDisplay
1076 def get_attribute_replmetadata_version(self
, dn
, att
):
1077 """Get the version field trom the replPropertyMetaData for
1080 :param dn: The on which we want to get the version
1081 :param att: The name of the attribute
1082 :return: The value of the version field in the replPropertyMetaData
1083 for the given attribute. None if the attribute is not replicated
1086 res
= self
.search(expression
="distinguishedName=%s" % dn
,
1087 scope
=ldb
.SCOPE_SUBTREE
,
1088 controls
=["search_options:1:2"],
1089 attrs
=["replPropertyMetaData"])
1093 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1094 res
[0]["replPropertyMetaData"][0])
1096 if len(self
.hash_oid_name
.keys()) == 0:
1097 self
._populate
_oid
_attid
()
1099 # Search for Description
1100 att_oid
= self
.get_oid_from_attid(o
.attid
)
1101 if att_oid
in self
.hash_oid_name
and\
1102 att
.lower() == self
.hash_oid_name
[att_oid
].lower():
1106 def set_attribute_replmetadata_version(self
, dn
, att
, value
,
1107 addifnotexist
=False):
1108 res
= self
.search(expression
="distinguishedName=%s" % dn
,
1109 scope
=ldb
.SCOPE_SUBTREE
,
1110 controls
=["search_options:1:2"],
1111 attrs
=["replPropertyMetaData"])
1115 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
1116 res
[0]["replPropertyMetaData"][0])
1118 now
= samba
.unix2nttime(int(time
.time()))
1120 if len(self
.hash_oid_name
.keys()) == 0:
1121 self
._populate
_oid
_attid
()
1123 # Search for Description
1124 att_oid
= self
.get_oid_from_attid(o
.attid
)
1125 if att_oid
in self
.hash_oid_name
and\
1126 att
.lower() == self
.hash_oid_name
[att_oid
].lower():
1128 seq
= self
.sequence_number(ldb
.SEQ_NEXT
)
1130 o
.originating_change_time
= now
1131 o
.originating_invocation_id
= misc
.GUID(self
.get_invocation_id())
1132 o
.originating_usn
= seq
1135 if not found
and addifnotexist
and len(ctr
.array
) > 0:
1136 o2
= drsblobs
.replPropertyMetaData1()
1138 att_oid
= self
.get_oid_from_attid(o2
.attid
)
1139 seq
= self
.sequence_number(ldb
.SEQ_NEXT
)
1141 o2
.originating_change_time
= now
1142 o2
.originating_invocation_id
= misc
.GUID(self
.get_invocation_id())
1143 o2
.originating_usn
= seq
1148 ctr
.count
= ctr
.count
+ 1
1152 replBlob
= ndr_pack(repl
)
1155 msg
["replPropertyMetaData"] = \
1156 ldb
.MessageElement(replBlob
,
1157 ldb
.FLAG_MOD_REPLACE
,
1158 "replPropertyMetaData")
1159 self
.modify(msg
, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
1161 def write_prefixes_from_schema(self
):
1162 dsdb
._dsdb
_write
_prefixes
_from
_schema
_to
_ldb
(self
)
1164 def get_partitions_dn(self
):
1165 return dsdb
._dsdb
_get
_partitions
_dn
(self
)
1167 def get_nc_root(self
, dn
):
1168 return dsdb
._dsdb
_get
_nc
_root
(self
, dn
)
1170 def get_wellknown_dn(self
, nc_root
, wkguid
):
1171 h_nc
= self
.hash_well_known
.get(str(nc_root
))
1173 if h_nc
is not None:
1174 dn
= h_nc
.get(wkguid
)
1176 dn
= dsdb
._dsdb
_get
_wellknown
_dn
(self
, nc_root
, wkguid
)
1180 self
.hash_well_known
[str(nc_root
)] = {}
1181 h_nc
= self
.hash_well_known
[str(nc_root
)]
1185 def set_minPwdAge(self
, value
):
1186 if not isinstance(value
, bytes
):
1187 value
= str(value
).encode('utf8')
1189 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
1190 m
["minPwdAge"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "minPwdAge")
1193 def get_minPwdAge(self
):
1194 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["minPwdAge"])
1197 elif "minPwdAge" not in res
[0]:
1200 return int(res
[0]["minPwdAge"][0])
1202 def set_maxPwdAge(self
, value
):
1203 if not isinstance(value
, bytes
):
1204 value
= str(value
).encode('utf8')
1206 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
1207 m
["maxPwdAge"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "maxPwdAge")
1210 def get_maxPwdAge(self
):
1211 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["maxPwdAge"])
1214 elif "maxPwdAge" not in res
[0]:
1217 return int(res
[0]["maxPwdAge"][0])
1219 def set_minPwdLength(self
, value
):
1220 if not isinstance(value
, bytes
):
1221 value
= str(value
).encode('utf8')
1223 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
1224 m
["minPwdLength"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "minPwdLength")
1227 def get_minPwdLength(self
):
1228 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["minPwdLength"])
1231 elif "minPwdLength" not in res
[0]:
1234 return int(res
[0]["minPwdLength"][0])
1236 def set_pwdProperties(self
, value
):
1237 if not isinstance(value
, bytes
):
1238 value
= str(value
).encode('utf8')
1240 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
1241 m
["pwdProperties"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "pwdProperties")
1244 def get_pwdProperties(self
):
1245 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["pwdProperties"])
1248 elif "pwdProperties" not in res
[0]:
1251 return int(res
[0]["pwdProperties"][0])
1253 def set_dsheuristics(self
, dsheuristics
):
1255 m
.dn
= ldb
.Dn(self
, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
1256 % self
.get_config_basedn().get_linearized())
1257 if dsheuristics
is not None:
1258 m
["dSHeuristics"] = \
1259 ldb
.MessageElement(dsheuristics
,
1260 ldb
.FLAG_MOD_REPLACE
,
1263 m
["dSHeuristics"] = \
1264 ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
,
1268 def get_dsheuristics(self
):
1269 res
= self
.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
1270 % self
.get_config_basedn().get_linearized(),
1271 scope
=ldb
.SCOPE_BASE
, attrs
=["dSHeuristics"])
1274 elif "dSHeuristics" in res
[0]:
1275 dsheuristics
= res
[0]["dSHeuristics"][0]
1281 def create_ou(self
, ou_dn
, description
=None, name
=None, sd
=None):
1282 """Creates an organizationalUnit object
1283 :param ou_dn: dn of the new object
1284 :param description: description attribute
1285 :param name: name atttribute
1286 :param sd: security descriptor of the object, can be
1287 an SDDL string or security.descriptor type
1290 "objectClass": "organizationalUnit"}
1293 m
["description"] = description
1298 m
["nTSecurityDescriptor"] = ndr_pack(sd
)
1301 def sequence_number(self
, seq_type
):
1302 """Returns the value of the sequence number according to the requested type
1303 :param seq_type: type of sequence number
1305 self
.transaction_start()
1307 seq
= super(SamDB
, self
).sequence_number(seq_type
)
1309 self
.transaction_cancel()
1312 self
.transaction_commit()
1315 def get_dsServiceName(self
):
1316 '''get the NTDS DN from the rootDSE'''
1317 res
= self
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
1318 return str(res
[0]["dsServiceName"][0])
1320 def get_serverName(self
):
1321 '''get the server DN from the rootDSE'''
1322 res
= self
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["serverName"])
1323 return str(res
[0]["serverName"][0])
1325 def dns_lookup(self
, dns_name
, dns_partition
=None):
1326 '''Do a DNS lookup in the database, returns the NDR database structures'''
1327 if dns_partition
is None:
1328 return dsdb_dns
.lookup(self
, dns_name
)
1330 return dsdb_dns
.lookup(self
, dns_name
,
1331 dns_partition
=dns_partition
)
1333 def dns_extract(self
, el
):
1334 '''Return the NDR database structures from a dnsRecord element'''
1335 return dsdb_dns
.extract(self
, el
)
1337 def dns_replace(self
, dns_name
, new_records
):
1338 '''Do a DNS modification on the database, sets the NDR database
1339 structures on a DNS name
1341 return dsdb_dns
.replace(self
, dns_name
, new_records
)
1343 def dns_replace_by_dn(self
, dn
, new_records
):
1344 '''Do a DNS modification on the database, sets the NDR database
1345 structures on a LDB DN
1347 This routine is important because if the last record on the DN
1348 is removed, this routine will put a tombstone in the record.
1350 return dsdb_dns
.replace_by_dn(self
, dn
, new_records
)
1352 def garbage_collect_tombstones(self
, dn
, current_time
,
1353 tombstone_lifetime
=None):
1354 '''garbage_collect_tombstones(lp, samdb, [dn], current_time, tombstone_lifetime)
1355 -> (num_objects_expunged, num_links_expunged)'''
1357 if tombstone_lifetime
is None:
1358 return dsdb
._dsdb
_garbage
_collect
_tombstones
(self
, dn
,
1361 return dsdb
._dsdb
_garbage
_collect
_tombstones
(self
, dn
,
1365 def create_own_rid_set(self
):
1366 '''create a RID set for this DSA'''
1367 return dsdb
._dsdb
_create
_own
_rid
_set
(self
)
1369 def allocate_rid(self
):
1370 '''return a new RID from the RID Pool on this DSA'''
1371 return dsdb
._dsdb
_allocate
_rid
(self
)
1373 def normalize_dn_in_domain(self
, dn
):
1374 '''return a new DN expanded by adding the domain DN
1376 If the dn is already a child of the domain DN, just
1379 :param dn: relative dn
1381 domain_dn
= ldb
.Dn(self
, self
.domain_dn())
1383 if isinstance(dn
, ldb
.Dn
):
1386 full_dn
= ldb
.Dn(self
, dn
)
1387 if not full_dn
.is_child_of(domain_dn
):
1388 full_dn
.add_base(domain_dn
)
1391 class dsdb_Dn(object):
1392 '''a class for binary DN'''
1394 def __init__(self
, samdb
, dnstring
, syntax_oid
=None):
1395 '''create a dsdb_Dn'''
1396 if syntax_oid
is None:
1397 # auto-detect based on string
1398 if dnstring
.startswith("B:"):
1399 syntax_oid
= dsdb
.DSDB_SYNTAX_BINARY_DN
1400 elif dnstring
.startswith("S:"):
1401 syntax_oid
= dsdb
.DSDB_SYNTAX_STRING_DN
1403 syntax_oid
= dsdb
.DSDB_SYNTAX_OR_NAME
1404 if syntax_oid
in [dsdb
.DSDB_SYNTAX_BINARY_DN
, dsdb
.DSDB_SYNTAX_STRING_DN
]:
1406 colons
= dnstring
.split(':')
1408 raise RuntimeError("Invalid DN %s" % dnstring
)
1409 prefix_len
= 4 + len(colons
[1]) + int(colons
[1])
1410 self
.prefix
= dnstring
[0:prefix_len
]
1411 self
.binary
= self
.prefix
[3 + len(colons
[1]):-1]
1412 self
.dnstring
= dnstring
[prefix_len
:]
1414 self
.dnstring
= dnstring
1417 self
.dn
= ldb
.Dn(samdb
, self
.dnstring
)
1420 return self
.prefix
+ str(self
.dn
.extended_str(mode
=1))
1422 def __cmp__(self
, other
):
1423 ''' compare dsdb_Dn values similar to parsed_dn_compare()'''
1426 guid1
= dn1
.dn
.get_extended_component("GUID")
1427 guid2
= dn2
.dn
.get_extended_component("GUID")
1429 v
= cmp(guid1
, guid2
)
1432 v
= cmp(dn1
.binary
, dn2
.binary
)
1435 # In Python3, __cmp__ is replaced by these 6 methods
1436 def __eq__(self
, other
):
1437 return self
.__cmp
__(other
) == 0
1439 def __ne__(self
, other
):
1440 return self
.__cmp
__(other
) != 0
1442 def __lt__(self
, other
):
1443 return self
.__cmp
__(other
) < 0
1445 def __le__(self
, other
):
1446 return self
.__cmp
__(other
) <= 0
1448 def __gt__(self
, other
):
1449 return self
.__cmp
__(other
) > 0
1451 def __ge__(self
, other
):
1452 return self
.__cmp
__(other
) >= 0
1454 def get_binary_integer(self
):
1455 '''return binary part of a dsdb_Dn as an integer, or None'''
1456 if self
.prefix
== '':
1458 return int(self
.binary
, 16)
1460 def get_bytes(self
):
1461 '''return binary as a byte string'''
1462 return binascii
.unhexlify(self
.binary
)