3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
5 # Copyright (C) Matthias Dieter Wallnoefer 2009
7 # Based on the original in EJS:
8 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 """Convenience functions for using the SAM."""
30 from samba
import dsdb
31 from samba
.ndr
import ndr_unpack
, ndr_pack
32 from samba
.dcerpc
import drsblobs
, misc
34 __docformat__
= "restructuredText"
37 class SamDB(samba
.Ldb
):
38 """The SAM database."""
42 def __init__(self
, url
=None, lp
=None, modules_dir
=None, session_info
=None,
43 credentials
=None, flags
=0, options
=None, global_schema
=True,
44 auto_connect
=True, am_rodc
=None):
48 elif url
is None and lp
is not None:
51 super(SamDB
, self
).__init
__(url
=url
, lp
=lp
, modules_dir
=modules_dir
,
52 session_info
=session_info
, credentials
=credentials
, flags
=flags
,
56 dsdb
._dsdb
_set
_global
_schema
(self
)
58 if am_rodc
is not None:
59 dsdb
._dsdb
_set
_am
_rodc
(self
, am_rodc
)
61 def connect(self
, url
=None, flags
=0, options
=None):
62 if self
.lp
is not None:
63 url
= self
.lp
.private_path(url
)
65 super(SamDB
, self
).connect(url
=url
, flags
=flags
,
69 return dsdb
._am
_rodc
(self
)
72 return str(self
.get_default_basedn())
74 def enable_account(self
, search_filter
):
77 :param search_filter: LDAP filter to find the user (eg
81 flags
= samba
.dsdb
.UF_ACCOUNTDISABLE | samba
.dsdb
.UF_PASSWD_NOTREQD
82 self
.toggle_userAccountFlags(search_filter
, flags
, on
=False)
84 def toggle_userAccountFlags(self
, search_filter
, flags
, on
=True, strict
=False):
85 """toggle_userAccountFlags
87 :param search_filter: LDAP filter to find the user (eg
89 :flags: samba.dsdb.UF_* flags
90 :on: on=True (default) => set, on=False => unset
91 :strict: strict=False (default) ignore if no action is needed
92 strict=True raises an Exception if...
94 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
95 expression
=search_filter
, attrs
=["userAccountControl"])
97 raise Exception('Unable to find user "%s"' % search_filter
)
99 account_dn
= res
[0].dn
101 old_uac
= int(res
[0]["userAccountControl"][0])
103 if strict
and (old_uac
& flags
):
104 error
= 'userAccountFlags[%d:0x%08X] already contain 0x%X' % (old_uac
, old_uac
, flags
)
105 raise Exception(error
)
107 new_uac
= old_uac | flags
109 if strict
and not (old_uac
& flags
):
110 error
= 'userAccountFlags[%d:0x%08X] not contain 0x%X' % (old_uac
, old_uac
, flags
)
111 raise Exception(error
)
113 new_uac
= old_uac
& ~flags
115 if old_uac
== new_uac
:
121 delete: userAccountControl
122 userAccountControl: %u
123 add: userAccountControl
124 userAccountControl: %u
125 """ % (account_dn
, old_uac
, new_uac
)
126 self
.modify_ldif(mod
)
128 def force_password_change_at_next_login(self
, search_filter
):
129 """Forces a password change at next login
131 :param search_filter: LDAP filter to find the user (eg
134 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
135 expression
=search_filter
, attrs
=[])
137 raise Exception('Unable to find user "%s"' % search_filter
)
138 assert(len(res
) == 1)
147 self
.modify_ldif(mod
)
149 def newgroup(self
, groupname
, groupou
=None, grouptype
=None,
150 description
=None, mailaddress
=None, notes
=None, sd
=None):
151 """Adds a new group with additional parameters
153 :param groupname: Name of the new group
154 :param grouptype: Type of the new group
155 :param description: Description of the new group
156 :param mailaddress: Email address of the new group
157 :param notes: Notes of the new group
158 :param sd: security descriptor of the object
161 group_dn
= "CN=%s,%s,%s" % (groupname
, (groupou
or "CN=Users"), self
.domain_dn())
163 # The new user record. Note the reliance on the SAMLDB module which
164 # fills in the default informations
165 ldbmessage
= {"dn": group_dn
,
166 "sAMAccountName": groupname
,
167 "objectClass": "group"}
169 if grouptype
is not None:
170 ldbmessage
["groupType"] = self
.normalise_int32(grouptype
)
172 if description
is not None:
173 ldbmessage
["description"] = description
175 if mailaddress
is not None:
176 ldbmessage
["mail"] = mailaddress
178 if notes
is not None:
179 ldbmessage
["info"] = notes
182 ldbmessage
["nTSecurityDescriptor"] = ndr_pack(sd
)
186 def deletegroup(self
, groupname
):
189 :param groupname: Name of the target group
192 groupfilter
= "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (groupname
, "CN=Group,CN=Schema,CN=Configuration", self
.domain_dn())
193 self
.transaction_start()
195 targetgroup
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
196 expression
=groupfilter
, attrs
=[])
197 if len(targetgroup
) == 0:
198 raise Exception('Unable to find group "%s"' % groupname
)
199 assert(len(targetgroup
) == 1)
200 self
.delete(targetgroup
[0].dn
)
202 self
.transaction_cancel()
205 self
.transaction_commit()
207 def add_remove_group_members(self
, groupname
, listofmembers
,
208 add_members_operation
=True):
209 """Adds or removes group members
211 :param groupname: Name of the target group
212 :param listofmembers: Comma-separated list of group members
213 :param add_members_operation: Defines if its an add or remove
217 groupfilter
= "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (groupname
, "CN=Group,CN=Schema,CN=Configuration", self
.domain_dn())
218 groupmembers
= listofmembers
.split(',')
220 self
.transaction_start()
222 targetgroup
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
223 expression
=groupfilter
, attrs
=['member'])
224 if len(targetgroup
) == 0:
225 raise Exception('Unable to find group "%s"' % groupname
)
226 assert(len(targetgroup
) == 1)
230 addtargettogroup
= """
233 """ % (str(targetgroup
[0].dn
))
235 for member
in groupmembers
:
236 targetmember
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
237 expression
="(|(sAMAccountName=%s)(CN=%s))" % (member
, member
), attrs
=[])
239 if len(targetmember
) != 1:
242 if add_members_operation
is True and (targetgroup
[0].get('member') is None or str(targetmember
[0].dn
) not in targetgroup
[0]['member']):
244 addtargettogroup
+= """add: member
246 """ % (str(targetmember
[0].dn
))
248 elif add_members_operation
is False and (targetgroup
[0].get('member') is not None and str(targetmember
[0].dn
) in targetgroup
[0]['member']):
250 addtargettogroup
+= """delete: member
252 """ % (str(targetmember
[0].dn
))
255 self
.modify_ldif(addtargettogroup
)
258 self
.transaction_cancel()
261 self
.transaction_commit()
263 def newuser(self
, username
, password
,
264 force_password_change_at_next_login_req
=False,
265 useusernameascn
=False, userou
=None, surname
=None, givenname
=None,
266 initials
=None, profilepath
=None, scriptpath
=None, homedrive
=None,
267 homedirectory
=None, jobtitle
=None, department
=None, company
=None,
268 description
=None, mailaddress
=None, internetaddress
=None,
269 telephonenumber
=None, physicaldeliveryoffice
=None, sd
=None,
271 """Adds a new user with additional parameters
273 :param username: Name of the new user
274 :param password: Password for the new user
275 :param force_password_change_at_next_login_req: Force password change
276 :param useusernameascn: Use username as cn rather that firstname +
278 :param userou: Object container (without domainDN postfix) for new user
279 :param surname: Surname of the new user
280 :param givenname: First name of the new user
281 :param initials: Initials of the new user
282 :param profilepath: Profile path of the new user
283 :param scriptpath: Logon script path of the new user
284 :param homedrive: Home drive of the new user
285 :param homedirectory: Home directory of the new user
286 :param jobtitle: Job title of the new user
287 :param department: Department of the new user
288 :param company: Company of the new user
289 :param description: of the new user
290 :param mailaddress: Email address of the new user
291 :param internetaddress: Home page of the new user
292 :param telephonenumber: Phone number of the new user
293 :param physicaldeliveryoffice: Office location of the new user
294 :param sd: security descriptor of the object
295 :param setpassword: optionally disable password reset
299 if givenname
is not None:
300 displayname
+= givenname
302 if initials
is not None:
303 displayname
+= ' %s.' % initials
305 if surname
is not None:
306 displayname
+= ' %s' % surname
309 if useusernameascn
is None and displayname
is not "":
312 user_dn
= "CN=%s,%s,%s" % (cn
, (userou
or "CN=Users"), self
.domain_dn())
314 dnsdomain
= ldb
.Dn(self
, self
.domain_dn()).canonical_str().replace("/", "")
315 user_principal_name
= "%s@%s" % (username
, dnsdomain
)
316 # The new user record. Note the reliance on the SAMLDB module which
317 # fills in the default informations
318 ldbmessage
= {"dn": user_dn
,
319 "sAMAccountName": username
,
320 "userPrincipalName": user_principal_name
,
321 "objectClass": "user"}
323 if surname
is not None:
324 ldbmessage
["sn"] = surname
326 if givenname
is not None:
327 ldbmessage
["givenName"] = givenname
329 if displayname
is not "":
330 ldbmessage
["displayName"] = displayname
331 ldbmessage
["name"] = displayname
333 if initials
is not None:
334 ldbmessage
["initials"] = '%s.' % initials
336 if profilepath
is not None:
337 ldbmessage
["profilePath"] = profilepath
339 if scriptpath
is not None:
340 ldbmessage
["scriptPath"] = scriptpath
342 if homedrive
is not None:
343 ldbmessage
["homeDrive"] = homedrive
345 if homedirectory
is not None:
346 ldbmessage
["homeDirectory"] = homedirectory
348 if jobtitle
is not None:
349 ldbmessage
["title"] = jobtitle
351 if department
is not None:
352 ldbmessage
["department"] = department
354 if company
is not None:
355 ldbmessage
["company"] = company
357 if description
is not None:
358 ldbmessage
["description"] = description
360 if mailaddress
is not None:
361 ldbmessage
["mail"] = mailaddress
363 if internetaddress
is not None:
364 ldbmessage
["wWWHomePage"] = internetaddress
366 if telephonenumber
is not None:
367 ldbmessage
["telephoneNumber"] = telephonenumber
369 if physicaldeliveryoffice
is not None:
370 ldbmessage
["physicalDeliveryOfficeName"] = physicaldeliveryoffice
373 ldbmessage
["nTSecurityDescriptor"] = ndr_pack(sd
)
375 self
.transaction_start()
379 # Sets the password for it
381 self
.setpassword("(samAccountName=%s)" % username
, password
,
382 force_password_change_at_next_login_req
)
384 self
.transaction_cancel()
387 self
.transaction_commit()
389 def setpassword(self
, search_filter
, password
,
390 force_change_at_next_login
=False, username
=None):
391 """Sets the password for a user
393 :param search_filter: LDAP filter to find the user (eg
395 :param password: Password for the user
396 :param force_change_at_next_login: Force password change
398 self
.transaction_start()
400 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
401 expression
=search_filter
, attrs
=[])
403 raise Exception('Unable to find user "%s"' % (username
or search_filter
))
405 raise Exception('Matched %u multiple users with filter "%s"' % (len(res
), search_filter
))
412 """ % (user_dn
, base64
.b64encode(("\"" + password
+ "\"").encode('utf-16-le')))
414 self
.modify_ldif(setpw
)
416 if force_change_at_next_login
:
417 self
.force_password_change_at_next_login(
418 "(dn=" + str(user_dn
) + ")")
420 # modify the userAccountControl to remove the disabled bit
421 self
.enable_account(search_filter
)
423 self
.transaction_cancel()
426 self
.transaction_commit()
428 def setexpiry(self
, search_filter
, expiry_seconds
, no_expiry_req
=False):
429 """Sets the account expiry for a user
431 :param search_filter: LDAP filter to find the user (eg
433 :param expiry_seconds: expiry time from now in seconds
434 :param no_expiry_req: if set, then don't expire password
436 self
.transaction_start()
438 res
= self
.search(base
=self
.domain_dn(), scope
=ldb
.SCOPE_SUBTREE
,
439 expression
=search_filter
,
440 attrs
=["userAccountControl", "accountExpires"])
442 raise Exception('Unable to find user "%s"' % search_filter
)
443 assert(len(res
) == 1)
446 userAccountControl
= int(res
[0]["userAccountControl"][0])
447 accountExpires
= int(res
[0]["accountExpires"][0])
449 userAccountControl
= userAccountControl |
0x10000
452 userAccountControl
= userAccountControl
& ~
0x10000
453 accountExpires
= samba
.unix2nttime(expiry_seconds
+ int(time
.time()))
458 replace: userAccountControl
459 userAccountControl: %u
460 replace: accountExpires
462 """ % (user_dn
, userAccountControl
, accountExpires
)
464 self
.modify_ldif(setexp
)
466 self
.transaction_cancel()
469 self
.transaction_commit()
471 def set_domain_sid(self
, sid
):
472 """Change the domain SID used by this LDB.
474 :param sid: The new domain sid to use.
476 dsdb
._samdb
_set
_domain
_sid
(self
, sid
)
478 def get_domain_sid(self
):
479 """Read the domain SID used by this LDB. """
480 return dsdb
._samdb
_get
_domain
_sid
(self
)
482 domain_sid
= property(get_domain_sid
, set_domain_sid
,
483 "SID for the domain")
485 def set_invocation_id(self
, invocation_id
):
486 """Set the invocation id for this SamDB handle.
488 :param invocation_id: GUID of the invocation id.
490 dsdb
._dsdb
_set
_ntds
_invocation
_id
(self
, invocation_id
)
492 def get_invocation_id(self
):
493 """Get the invocation_id id"""
494 return dsdb
._samdb
_ntds
_invocation
_id
(self
)
496 invocation_id
= property(get_invocation_id
, set_invocation_id
,
497 "Invocation ID GUID")
499 def get_oid_from_attid(self
, attid
):
500 return dsdb
._dsdb
_get
_oid
_from
_attid
(self
, attid
)
502 def get_attid_from_lDAPDisplayName(self
, ldap_display_name
,
504 '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
505 return dsdb
._dsdb
_get
_attid
_from
_lDAPDisplayName
(self
,
506 ldap_display_name
, is_schema_nc
)
508 def get_syntax_oid_from_lDAPDisplayName(self
, ldap_display_name
):
509 '''return the syntax OID for a LDAP attribute as a string'''
510 return dsdb
._dsdb
_get
_syntax
_oid
_from
_lDAPDisplayName
(self
, ldap_display_name
)
512 def set_ntds_settings_dn(self
, ntds_settings_dn
):
513 """Set the NTDS Settings DN, as would be returned on the dsServiceName
516 This allows the DN to be set before the database fully exists
518 :param ntds_settings_dn: The new DN to use
520 dsdb
._samdb
_set
_ntds
_settings
_dn
(self
, ntds_settings_dn
)
522 def get_ntds_GUID(self
):
523 """Get the NTDS objectGUID"""
524 return dsdb
._samdb
_ntds
_objectGUID
(self
)
526 def server_site_name(self
):
527 """Get the server site name"""
528 return dsdb
._samdb
_server
_site
_name
(self
)
530 def load_partition_usn(self
, base_dn
):
531 return dsdb
._dsdb
_load
_partition
_usn
(self
, base_dn
)
533 def set_schema(self
, schema
):
534 self
.set_schema_from_ldb(schema
.ldb
)
536 def set_schema_from_ldb(self
, ldb_conn
):
537 dsdb
._dsdb
_set
_schema
_from
_ldb
(self
, ldb_conn
)
539 def dsdb_DsReplicaAttribute(self
, ldb
, ldap_display_name
, ldif_elements
):
540 '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
541 return dsdb
._dsdb
_DsReplicaAttribute
(ldb
, ldap_display_name
, ldif_elements
)
543 def dsdb_normalise_attributes(self
, ldb
, ldap_display_name
, ldif_elements
):
544 '''normalise a list of attribute values'''
545 return dsdb
._dsdb
_normalise
_attributes
(ldb
, ldap_display_name
, ldif_elements
)
547 def get_attribute_from_attid(self
, attid
):
548 """ Get from an attid the associated attribute
550 :param attid: The attribute id for searched attribute
551 :return: The name of the attribute associated with this id
553 if len(self
.hash_oid_name
.keys()) == 0:
554 self
._populate
_oid
_attid
()
555 if self
.hash_oid_name
.has_key(self
.get_oid_from_attid(attid
)):
556 return self
.hash_oid_name
[self
.get_oid_from_attid(attid
)]
560 def _populate_oid_attid(self
):
561 """Populate the hash hash_oid_name.
563 This hash contains the oid of the attribute as a key and
564 its display name as a value
566 self
.hash_oid_name
= {}
567 res
= self
.search(expression
="objectClass=attributeSchema",
568 controls
=["search_options:1:2"],
569 attrs
=["attributeID",
573 strDisplay
= str(e
.get("lDAPDisplayName"))
574 self
.hash_oid_name
[str(e
.get("attributeID"))] = strDisplay
576 def get_attribute_replmetadata_version(self
, dn
, att
):
577 """Get the version field trom the replPropertyMetaData for
580 :param dn: The on which we want to get the version
581 :param att: The name of the attribute
582 :return: The value of the version field in the replPropertyMetaData
583 for the given attribute. None if the attribute is not replicated
586 res
= self
.search(expression
="dn=%s" % dn
,
587 scope
=ldb
.SCOPE_SUBTREE
,
588 controls
=["search_options:1:2"],
589 attrs
=["replPropertyMetaData"])
593 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
594 str(res
[0]["replPropertyMetaData"]))
596 if len(self
.hash_oid_name
.keys()) == 0:
597 self
._populate
_oid
_attid
()
599 # Search for Description
600 att_oid
= self
.get_oid_from_attid(o
.attid
)
601 if self
.hash_oid_name
.has_key(att_oid
) and\
602 att
.lower() == self
.hash_oid_name
[att_oid
].lower():
606 def set_attribute_replmetadata_version(self
, dn
, att
, value
,
607 addifnotexist
=False):
608 res
= self
.search(expression
="dn=%s" % dn
,
609 scope
=ldb
.SCOPE_SUBTREE
,
610 controls
=["search_options:1:2"],
611 attrs
=["replPropertyMetaData"])
615 repl
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
616 str(res
[0]["replPropertyMetaData"]))
618 now
= samba
.unix2nttime(int(time
.time()))
620 if len(self
.hash_oid_name
.keys()) == 0:
621 self
._populate
_oid
_attid
()
623 # Search for Description
624 att_oid
= self
.get_oid_from_attid(o
.attid
)
625 if self
.hash_oid_name
.has_key(att_oid
) and\
626 att
.lower() == self
.hash_oid_name
[att_oid
].lower():
628 seq
= self
.sequence_number(ldb
.SEQ_NEXT
)
630 o
.originating_change_time
= now
631 o
.originating_invocation_id
= misc
.GUID(self
.get_invocation_id())
632 o
.originating_usn
= seq
635 if not found
and addifnotexist
and len(ctr
.array
) >0:
636 o2
= drsblobs
.replPropertyMetaData1()
638 att_oid
= self
.get_oid_from_attid(o2
.attid
)
639 seq
= self
.sequence_number(ldb
.SEQ_NEXT
)
641 o2
.originating_change_time
= now
642 o2
.originating_invocation_id
= misc
.GUID(self
.get_invocation_id())
643 o2
.originating_usn
= seq
648 ctr
.count
= ctr
.count
+ 1
652 replBlob
= ndr_pack(repl
)
655 msg
["replPropertyMetaData"] = ldb
.MessageElement(replBlob
,
656 ldb
.FLAG_MOD_REPLACE
,
657 "replPropertyMetaData")
658 self
.modify(msg
, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
660 def write_prefixes_from_schema(self
):
661 dsdb
._dsdb
_write
_prefixes
_from
_schema
_to
_ldb
(self
)
663 def get_partitions_dn(self
):
664 return dsdb
._dsdb
_get
_partitions
_dn
(self
)
666 def set_minPwdAge(self
, value
):
668 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
669 m
["minPwdAge"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "minPwdAge")
672 def get_minPwdAge(self
):
673 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["minPwdAge"])
676 elif not "minPwdAge" in res
[0]:
679 return res
[0]["minPwdAge"][0]
681 def set_minPwdLength(self
, value
):
683 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
684 m
["minPwdLength"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "minPwdLength")
687 def get_minPwdLength(self
):
688 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["minPwdLength"])
691 elif not "minPwdLength" in res
[0]:
694 return res
[0]["minPwdLength"][0]
696 def set_pwdProperties(self
, value
):
698 m
.dn
= ldb
.Dn(self
, self
.domain_dn())
699 m
["pwdProperties"] = ldb
.MessageElement(value
, ldb
.FLAG_MOD_REPLACE
, "pwdProperties")
702 def get_pwdProperties(self
):
703 res
= self
.search(self
.domain_dn(), scope
=ldb
.SCOPE_BASE
, attrs
=["pwdProperties"])
706 elif not "pwdProperties" in res
[0]:
709 return res
[0]["pwdProperties"][0]
711 def set_dsheuristics(self
, dsheuristics
):
713 m
.dn
= ldb
.Dn(self
, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
714 % self
.get_config_basedn().get_linearized())
715 if dsheuristics
is not None:
716 m
["dSHeuristics"] = ldb
.MessageElement(dsheuristics
,
717 ldb
.FLAG_MOD_REPLACE
, "dSHeuristics")
719 m
["dSHeuristics"] = ldb
.MessageElement([], ldb
.FLAG_MOD_DELETE
,
723 def get_dsheuristics(self
):
724 res
= self
.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
725 % self
.get_config_basedn().get_linearized(),
726 scope
=ldb
.SCOPE_BASE
, attrs
=["dSHeuristics"])
729 elif "dSHeuristics" in res
[0]:
730 dsheuristics
= res
[0]["dSHeuristics"][0]
736 def create_ou(self
, ou_dn
, description
=None, name
=None, sd
=None):
737 """Creates an organizationalUnit object
738 :param ou_dn: dn of the new object
739 :param description: description attribute
740 :param name: name atttribute
741 :param sd: security descriptor of the object, can be
742 an SDDL string or security.descriptor type
745 "objectClass": "organizationalUnit"}
748 m
["description"] = description
753 m
["nTSecurityDescriptor"] = ndr_pack(sd
)
756 def normalise_int32(self
, ivalue
):
757 '''normalise a ldap integer to signed 32 bit'''
758 if int(ivalue
) & 0x80000000:
759 return str(int(ivalue
) - 0x100000000)