docs: point out side-effects of global "valid users" setting.
[Samba.git] / python / samba / samdb.py
blob5478e24c34e6307486ea0772516a0980025f6387
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."""
25 import samba
26 import ldb
27 import time
28 import base64
29 import os
30 from samba import dsdb
31 from samba.ndr import ndr_unpack, ndr_pack
32 from samba.dcerpc import drsblobs, misc
33 from samba.common import normalise_int32
35 __docformat__ = "restructuredText"
38 class SamDB(samba.Ldb):
39 """The SAM database."""
41 hash_oid_name = {}
43 def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
44 credentials=None, flags=0, options=None, global_schema=True,
45 auto_connect=True, am_rodc=None):
46 self.lp = lp
47 if not auto_connect:
48 url = None
49 elif url is None and lp is not None:
50 url = lp.samdb_url()
52 self.url = url
54 super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
55 session_info=session_info, credentials=credentials, flags=flags,
56 options=options)
58 if global_schema:
59 dsdb._dsdb_set_global_schema(self)
61 if am_rodc is not None:
62 dsdb._dsdb_set_am_rodc(self, am_rodc)
64 def connect(self, url=None, flags=0, options=None):
65 '''connect to the database'''
66 if self.lp is not None and not os.path.exists(url):
67 url = self.lp.private_path(url)
68 self.url = url
70 super(SamDB, self).connect(url=url, flags=flags,
71 options=options)
73 def am_rodc(self):
74 '''return True if we are an RODC'''
75 return dsdb._am_rodc(self)
77 def am_pdc(self):
78 '''return True if we are an PDC emulator'''
79 return dsdb._am_pdc(self)
81 def domain_dn(self):
82 '''return the domain DN'''
83 return str(self.get_default_basedn())
85 def disable_account(self, search_filter):
86 """Disables an account
88 :param search_filter: LDAP filter to find the user (eg
89 samccountname=name)
90 """
92 flags = samba.dsdb.UF_ACCOUNTDISABLE
93 self.toggle_userAccountFlags(search_filter, flags, on=True)
95 def enable_account(self, search_filter):
96 """Enables an account
98 :param search_filter: LDAP filter to find the user (eg
99 samccountname=name)
102 flags = samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_PASSWD_NOTREQD
103 self.toggle_userAccountFlags(search_filter, flags, on=False)
105 def toggle_userAccountFlags(self, search_filter, flags, flags_str=None,
106 on=True, strict=False):
107 """Toggle_userAccountFlags
109 :param search_filter: LDAP filter to find the user (eg
110 samccountname=name)
111 :param flags: samba.dsdb.UF_* flags
112 :param on: on=True (default) => set, on=False => unset
113 :param strict: strict=False (default) ignore if no action is needed
114 strict=True raises an Exception if...
116 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
117 expression=search_filter, attrs=["userAccountControl"])
118 if len(res) == 0:
119 raise Exception("Unable to find account where '%s'" % search_filter)
120 assert(len(res) == 1)
121 account_dn = res[0].dn
123 old_uac = int(res[0]["userAccountControl"][0])
124 if on:
125 if strict and (old_uac & flags):
126 error = "Account flag(s) '%s' already set" % flags_str
127 raise Exception(error)
129 new_uac = old_uac | flags
130 else:
131 if strict and not (old_uac & flags):
132 error = "Account flag(s) '%s' already unset" % flags_str
133 raise Exception(error)
135 new_uac = old_uac & ~flags
137 if old_uac == new_uac:
138 return
140 mod = """
141 dn: %s
142 changetype: modify
143 delete: userAccountControl
144 userAccountControl: %u
145 add: userAccountControl
146 userAccountControl: %u
147 """ % (account_dn, old_uac, new_uac)
148 self.modify_ldif(mod)
150 def force_password_change_at_next_login(self, search_filter):
151 """Forces a password change at next login
153 :param search_filter: LDAP filter to find the user (eg
154 samccountname=name)
156 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
157 expression=search_filter, attrs=[])
158 if len(res) == 0:
159 raise Exception('Unable to find user "%s"' % search_filter)
160 assert(len(res) == 1)
161 user_dn = res[0].dn
163 mod = """
164 dn: %s
165 changetype: modify
166 replace: pwdLastSet
167 pwdLastSet: 0
168 """ % (user_dn)
169 self.modify_ldif(mod)
171 def newgroup(self, groupname, groupou=None, grouptype=None,
172 description=None, mailaddress=None, notes=None, sd=None):
173 """Adds a new group with additional parameters
175 :param groupname: Name of the new group
176 :param grouptype: Type of the new group
177 :param description: Description of the new group
178 :param mailaddress: Email address of the new group
179 :param notes: Notes of the new group
180 :param sd: security descriptor of the object
183 group_dn = "CN=%s,%s,%s" % (groupname, (groupou or "CN=Users"), self.domain_dn())
185 # The new user record. Note the reliance on the SAMLDB module which
186 # fills in the default informations
187 ldbmessage = {"dn": group_dn,
188 "sAMAccountName": groupname,
189 "objectClass": "group"}
191 if grouptype is not None:
192 ldbmessage["groupType"] = normalise_int32(grouptype)
194 if description is not None:
195 ldbmessage["description"] = description
197 if mailaddress is not None:
198 ldbmessage["mail"] = mailaddress
200 if notes is not None:
201 ldbmessage["info"] = notes
203 if sd is not None:
204 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
206 self.add(ldbmessage)
208 def deletegroup(self, groupname):
209 """Deletes a group
211 :param groupname: Name of the target group
214 groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
215 self.transaction_start()
216 try:
217 targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
218 expression=groupfilter, attrs=[])
219 if len(targetgroup) == 0:
220 raise Exception('Unable to find group "%s"' % groupname)
221 assert(len(targetgroup) == 1)
222 self.delete(targetgroup[0].dn)
223 except:
224 self.transaction_cancel()
225 raise
226 else:
227 self.transaction_commit()
229 def add_remove_group_members(self, groupname, members,
230 add_members_operation=True):
231 """Adds or removes group members
233 :param groupname: Name of the target group
234 :param members: list of group members
235 :param add_members_operation: Defines if its an add or remove
236 operation
239 groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
240 ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
242 self.transaction_start()
243 try:
244 targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
245 expression=groupfilter, attrs=['member'])
246 if len(targetgroup) == 0:
247 raise Exception('Unable to find group "%s"' % groupname)
248 assert(len(targetgroup) == 1)
250 modified = False
252 addtargettogroup = """
253 dn: %s
254 changetype: modify
255 """ % (str(targetgroup[0].dn))
257 for member in members:
258 targetmember = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
259 expression="(|(sAMAccountName=%s)(CN=%s))" % (
260 ldb.binary_encode(member), ldb.binary_encode(member)), attrs=[])
262 if len(targetmember) != 1:
263 continue
265 if add_members_operation is True and (targetgroup[0].get('member') is None or str(targetmember[0].dn) not in targetgroup[0]['member']):
266 modified = True
267 addtargettogroup += """add: member
268 member: %s
269 """ % (str(targetmember[0].dn))
271 elif add_members_operation is False and (targetgroup[0].get('member') is not None and str(targetmember[0].dn) in targetgroup[0]['member']):
272 modified = True
273 addtargettogroup += """delete: member
274 member: %s
275 """ % (str(targetmember[0].dn))
277 if modified is True:
278 self.modify_ldif(addtargettogroup)
280 except:
281 self.transaction_cancel()
282 raise
283 else:
284 self.transaction_commit()
286 def newuser(self, username, password,
287 force_password_change_at_next_login_req=False,
288 useusernameascn=False, userou=None, surname=None, givenname=None,
289 initials=None, profilepath=None, scriptpath=None, homedrive=None,
290 homedirectory=None, jobtitle=None, department=None, company=None,
291 description=None, mailaddress=None, internetaddress=None,
292 telephonenumber=None, physicaldeliveryoffice=None, sd=None,
293 setpassword=True):
294 """Adds a new user with additional parameters
296 :param username: Name of the new user
297 :param password: Password for the new user
298 :param force_password_change_at_next_login_req: Force password change
299 :param useusernameascn: Use username as cn rather that firstname +
300 initials + lastname
301 :param userou: Object container (without domainDN postfix) for new user
302 :param surname: Surname of the new user
303 :param givenname: First name of the new user
304 :param initials: Initials of the new user
305 :param profilepath: Profile path of the new user
306 :param scriptpath: Logon script path of the new user
307 :param homedrive: Home drive of the new user
308 :param homedirectory: Home directory of the new user
309 :param jobtitle: Job title of the new user
310 :param department: Department of the new user
311 :param company: Company of the new user
312 :param description: of the new user
313 :param mailaddress: Email address of the new user
314 :param internetaddress: Home page of the new user
315 :param telephonenumber: Phone number of the new user
316 :param physicaldeliveryoffice: Office location of the new user
317 :param sd: security descriptor of the object
318 :param setpassword: optionally disable password reset
321 displayname = ""
322 if givenname is not None:
323 displayname += givenname
325 if initials is not None:
326 displayname += ' %s.' % initials
328 if surname is not None:
329 displayname += ' %s' % surname
331 cn = username
332 if useusernameascn is None and displayname is not "":
333 cn = displayname
335 user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
337 dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
338 user_principal_name = "%s@%s" % (username, dnsdomain)
339 # The new user record. Note the reliance on the SAMLDB module which
340 # fills in the default informations
341 ldbmessage = {"dn": user_dn,
342 "sAMAccountName": username,
343 "userPrincipalName": user_principal_name,
344 "objectClass": "user"}
346 if surname is not None:
347 ldbmessage["sn"] = surname
349 if givenname is not None:
350 ldbmessage["givenName"] = givenname
352 if displayname is not "":
353 ldbmessage["displayName"] = displayname
354 ldbmessage["name"] = displayname
356 if initials is not None:
357 ldbmessage["initials"] = '%s.' % initials
359 if profilepath is not None:
360 ldbmessage["profilePath"] = profilepath
362 if scriptpath is not None:
363 ldbmessage["scriptPath"] = scriptpath
365 if homedrive is not None:
366 ldbmessage["homeDrive"] = homedrive
368 if homedirectory is not None:
369 ldbmessage["homeDirectory"] = homedirectory
371 if jobtitle is not None:
372 ldbmessage["title"] = jobtitle
374 if department is not None:
375 ldbmessage["department"] = department
377 if company is not None:
378 ldbmessage["company"] = company
380 if description is not None:
381 ldbmessage["description"] = description
383 if mailaddress is not None:
384 ldbmessage["mail"] = mailaddress
386 if internetaddress is not None:
387 ldbmessage["wWWHomePage"] = internetaddress
389 if telephonenumber is not None:
390 ldbmessage["telephoneNumber"] = telephonenumber
392 if physicaldeliveryoffice is not None:
393 ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
395 if sd is not None:
396 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
398 self.transaction_start()
399 try:
400 self.add(ldbmessage)
402 # Sets the password for it
403 if setpassword:
404 self.setpassword("(samAccountName=%s)" % ldb.binary_encode(username), password,
405 force_password_change_at_next_login_req)
406 except:
407 self.transaction_cancel()
408 raise
409 else:
410 self.transaction_commit()
413 def deleteuser(self, username):
414 """Deletes a user
416 :param username: Name of the target user
419 filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
420 self.transaction_start()
421 try:
422 target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
423 expression=filter, attrs=[])
424 if len(target) == 0:
425 raise Exception('Unable to find user "%s"' % username)
426 assert(len(target) == 1)
427 self.delete(target[0].dn)
428 except:
429 self.transaction_cancel()
430 raise
431 else:
432 self.transaction_commit()
434 def setpassword(self, search_filter, password,
435 force_change_at_next_login=False, username=None):
436 """Sets the password for a user
438 :param search_filter: LDAP filter to find the user (eg
439 samccountname=name)
440 :param password: Password for the user
441 :param force_change_at_next_login: Force password change
443 self.transaction_start()
444 try:
445 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
446 expression=search_filter, attrs=[])
447 if len(res) == 0:
448 raise Exception('Unable to find user "%s"' % (username or search_filter))
449 if len(res) > 1:
450 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
451 user_dn = res[0].dn
452 pw = unicode('"' + password + '"', 'utf-8').encode('utf-16-le')
453 setpw = """
454 dn: %s
455 changetype: modify
456 replace: unicodePwd
457 unicodePwd:: %s
458 """ % (user_dn, base64.b64encode(pw))
460 self.modify_ldif(setpw)
462 if force_change_at_next_login:
463 self.force_password_change_at_next_login(
464 "(distinguishedName=" + str(user_dn) + ")")
466 # modify the userAccountControl to remove the disabled bit
467 self.enable_account(search_filter)
468 except:
469 self.transaction_cancel()
470 raise
471 else:
472 self.transaction_commit()
474 def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
475 """Sets the account expiry for a user
477 :param search_filter: LDAP filter to find the user (eg
478 samaccountname=name)
479 :param expiry_seconds: expiry time from now in seconds
480 :param no_expiry_req: if set, then don't expire password
482 self.transaction_start()
483 try:
484 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
485 expression=search_filter,
486 attrs=["userAccountControl", "accountExpires"])
487 if len(res) == 0:
488 raise Exception('Unable to find user "%s"' % search_filter)
489 assert(len(res) == 1)
490 user_dn = res[0].dn
492 userAccountControl = int(res[0]["userAccountControl"][0])
493 accountExpires = int(res[0]["accountExpires"][0])
494 if no_expiry_req:
495 userAccountControl = userAccountControl | 0x10000
496 accountExpires = 0
497 else:
498 userAccountControl = userAccountControl & ~0x10000
499 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
501 setexp = """
502 dn: %s
503 changetype: modify
504 replace: userAccountControl
505 userAccountControl: %u
506 replace: accountExpires
507 accountExpires: %u
508 """ % (user_dn, userAccountControl, accountExpires)
510 self.modify_ldif(setexp)
511 except:
512 self.transaction_cancel()
513 raise
514 else:
515 self.transaction_commit()
517 def set_domain_sid(self, sid):
518 """Change the domain SID used by this LDB.
520 :param sid: The new domain sid to use.
522 dsdb._samdb_set_domain_sid(self, sid)
524 def get_domain_sid(self):
525 """Read the domain SID used by this LDB. """
526 return dsdb._samdb_get_domain_sid(self)
528 domain_sid = property(get_domain_sid, set_domain_sid,
529 "SID for the domain")
531 def set_invocation_id(self, invocation_id):
532 """Set the invocation id for this SamDB handle.
534 :param invocation_id: GUID of the invocation id.
536 dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
538 def get_invocation_id(self):
539 """Get the invocation_id id"""
540 return dsdb._samdb_ntds_invocation_id(self)
542 invocation_id = property(get_invocation_id, set_invocation_id,
543 "Invocation ID GUID")
545 def get_oid_from_attid(self, attid):
546 return dsdb._dsdb_get_oid_from_attid(self, attid)
548 def get_attid_from_lDAPDisplayName(self, ldap_display_name,
549 is_schema_nc=False):
550 '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
551 return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
552 ldap_display_name, is_schema_nc)
554 def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
555 '''return the syntax OID for a LDAP attribute as a string'''
556 return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
558 def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
559 '''return the systemFlags for a LDAP attribute as a integer'''
560 return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
562 def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
563 '''return the linkID for a LDAP attribute as a integer'''
564 return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
566 def get_lDAPDisplayName_by_attid(self, attid):
567 '''return the lDAPDisplayName from an integer DRS attribute ID'''
568 return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
570 def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
571 '''return the attribute name of the corresponding backlink from the name
572 of a forward link attribute. If there is no backlink return None'''
573 return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
575 def set_ntds_settings_dn(self, ntds_settings_dn):
576 """Set the NTDS Settings DN, as would be returned on the dsServiceName
577 rootDSE attribute.
579 This allows the DN to be set before the database fully exists
581 :param ntds_settings_dn: The new DN to use
583 dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
585 def get_ntds_GUID(self):
586 """Get the NTDS objectGUID"""
587 return dsdb._samdb_ntds_objectGUID(self)
589 def server_site_name(self):
590 """Get the server site name"""
591 return dsdb._samdb_server_site_name(self)
593 def host_dns_name(self):
594 """return the DNS name of this host"""
595 res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
596 return res[0]['dNSHostName'][0]
598 def domain_dns_name(self):
599 """return the DNS name of the domain root"""
600 domain_dn = self.get_default_basedn()
601 return domain_dn.canonical_str().split('/')[0]
603 def forest_dns_name(self):
604 """return the DNS name of the forest root"""
605 forest_dn = self.get_root_basedn()
606 return forest_dn.canonical_str().split('/')[0]
608 def load_partition_usn(self, base_dn):
609 return dsdb._dsdb_load_partition_usn(self, base_dn)
611 def set_schema(self, schema, write_indices_and_attributes=True):
612 self.set_schema_from_ldb(schema.ldb, write_indices_and_attributes=write_indices_and_attributes)
614 def set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes=True):
615 dsdb._dsdb_set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes)
617 def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
618 '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
619 return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
621 def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
622 '''normalise a list of attribute values'''
623 return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
625 def get_attribute_from_attid(self, attid):
626 """ Get from an attid the associated attribute
628 :param attid: The attribute id for searched attribute
629 :return: The name of the attribute associated with this id
631 if len(self.hash_oid_name.keys()) == 0:
632 self._populate_oid_attid()
633 if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
634 return self.hash_oid_name[self.get_oid_from_attid(attid)]
635 else:
636 return None
638 def _populate_oid_attid(self):
639 """Populate the hash hash_oid_name.
641 This hash contains the oid of the attribute as a key and
642 its display name as a value
644 self.hash_oid_name = {}
645 res = self.search(expression="objectClass=attributeSchema",
646 controls=["search_options:1:2"],
647 attrs=["attributeID",
648 "lDAPDisplayName"])
649 if len(res) > 0:
650 for e in res:
651 strDisplay = str(e.get("lDAPDisplayName"))
652 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
654 def get_attribute_replmetadata_version(self, dn, att):
655 """Get the version field trom the replPropertyMetaData for
656 the given field
658 :param dn: The on which we want to get the version
659 :param att: The name of the attribute
660 :return: The value of the version field in the replPropertyMetaData
661 for the given attribute. None if the attribute is not replicated
664 res = self.search(expression="distinguishedName=%s" % dn,
665 scope=ldb.SCOPE_SUBTREE,
666 controls=["search_options:1:2"],
667 attrs=["replPropertyMetaData"])
668 if len(res) == 0:
669 return None
671 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
672 str(res[0]["replPropertyMetaData"]))
673 ctr = repl.ctr
674 if len(self.hash_oid_name.keys()) == 0:
675 self._populate_oid_attid()
676 for o in ctr.array:
677 # Search for Description
678 att_oid = self.get_oid_from_attid(o.attid)
679 if self.hash_oid_name.has_key(att_oid) and\
680 att.lower() == self.hash_oid_name[att_oid].lower():
681 return o.version
682 return None
684 def set_attribute_replmetadata_version(self, dn, att, value,
685 addifnotexist=False):
686 res = self.search(expression="distinguishedName=%s" % dn,
687 scope=ldb.SCOPE_SUBTREE,
688 controls=["search_options:1:2"],
689 attrs=["replPropertyMetaData"])
690 if len(res) == 0:
691 return None
693 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
694 str(res[0]["replPropertyMetaData"]))
695 ctr = repl.ctr
696 now = samba.unix2nttime(int(time.time()))
697 found = False
698 if len(self.hash_oid_name.keys()) == 0:
699 self._populate_oid_attid()
700 for o in ctr.array:
701 # Search for Description
702 att_oid = self.get_oid_from_attid(o.attid)
703 if self.hash_oid_name.has_key(att_oid) and\
704 att.lower() == self.hash_oid_name[att_oid].lower():
705 found = True
706 seq = self.sequence_number(ldb.SEQ_NEXT)
707 o.version = value
708 o.originating_change_time = now
709 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
710 o.originating_usn = seq
711 o.local_usn = seq
713 if not found and addifnotexist and len(ctr.array) >0:
714 o2 = drsblobs.replPropertyMetaData1()
715 o2.attid = 589914
716 att_oid = self.get_oid_from_attid(o2.attid)
717 seq = self.sequence_number(ldb.SEQ_NEXT)
718 o2.version = value
719 o2.originating_change_time = now
720 o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
721 o2.originating_usn = seq
722 o2.local_usn = seq
723 found = True
724 tab = ctr.array
725 tab.append(o2)
726 ctr.count = ctr.count + 1
727 ctr.array = tab
729 if found :
730 replBlob = ndr_pack(repl)
731 msg = ldb.Message()
732 msg.dn = res[0].dn
733 msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
734 ldb.FLAG_MOD_REPLACE,
735 "replPropertyMetaData")
736 self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
738 def write_prefixes_from_schema(self):
739 dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
741 def get_partitions_dn(self):
742 return dsdb._dsdb_get_partitions_dn(self)
744 def get_nc_root(self, dn):
745 return dsdb._dsdb_get_nc_root(self, dn)
747 def get_wellknown_dn(self, nc_root, wkguid):
748 return dsdb._dsdb_get_wellknown_dn(self, nc_root, wkguid)
750 def set_minPwdAge(self, value):
751 m = ldb.Message()
752 m.dn = ldb.Dn(self, self.domain_dn())
753 m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
754 self.modify(m)
756 def get_minPwdAge(self):
757 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
758 if len(res) == 0:
759 return None
760 elif not "minPwdAge" in res[0]:
761 return None
762 else:
763 return res[0]["minPwdAge"][0]
765 def set_minPwdLength(self, value):
766 m = ldb.Message()
767 m.dn = ldb.Dn(self, self.domain_dn())
768 m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
769 self.modify(m)
771 def get_minPwdLength(self):
772 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
773 if len(res) == 0:
774 return None
775 elif not "minPwdLength" in res[0]:
776 return None
777 else:
778 return res[0]["minPwdLength"][0]
780 def set_pwdProperties(self, value):
781 m = ldb.Message()
782 m.dn = ldb.Dn(self, self.domain_dn())
783 m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
784 self.modify(m)
786 def get_pwdProperties(self):
787 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
788 if len(res) == 0:
789 return None
790 elif not "pwdProperties" in res[0]:
791 return None
792 else:
793 return res[0]["pwdProperties"][0]
795 def set_dsheuristics(self, dsheuristics):
796 m = ldb.Message()
797 m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
798 % self.get_config_basedn().get_linearized())
799 if dsheuristics is not None:
800 m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
801 ldb.FLAG_MOD_REPLACE, "dSHeuristics")
802 else:
803 m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
804 "dSHeuristics")
805 self.modify(m)
807 def get_dsheuristics(self):
808 res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
809 % self.get_config_basedn().get_linearized(),
810 scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
811 if len(res) == 0:
812 dsheuristics = None
813 elif "dSHeuristics" in res[0]:
814 dsheuristics = res[0]["dSHeuristics"][0]
815 else:
816 dsheuristics = None
818 return dsheuristics
820 def create_ou(self, ou_dn, description=None, name=None, sd=None):
821 """Creates an organizationalUnit object
822 :param ou_dn: dn of the new object
823 :param description: description attribute
824 :param name: name atttribute
825 :param sd: security descriptor of the object, can be
826 an SDDL string or security.descriptor type
828 m = {"dn": ou_dn,
829 "objectClass": "organizationalUnit"}
831 if description:
832 m["description"] = description
833 if name:
834 m["name"] = name
836 if sd:
837 m["nTSecurityDescriptor"] = ndr_pack(sd)
838 self.add(m)
840 def sequence_number(self, seq_type):
841 """Returns the value of the sequence number according to the requested type
842 :param seq_type: type of sequence number
844 self.transaction_start()
845 try:
846 seq = super(SamDB, self).sequence_number(seq_type)
847 except:
848 self.transaction_cancel()
849 raise
850 else:
851 self.transaction_commit()
852 return seq
854 def get_dsServiceName(self):
855 '''get the NTDS DN from the rootDSE'''
856 res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
857 return res[0]["dsServiceName"][0]
859 def get_serverName(self):
860 '''get the server DN from the rootDSE'''
861 res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
862 return res[0]["serverName"][0]