s4:torture/smb2: add v2 lease requests
[Samba/id10ts.git] / python / samba / samdb.py
blob2dfc839519e491ba1d8ec7a2fdb7459ffedcecd6
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, uidnumber=None, gidnumber=None, gecos=None,
294 loginshell=None, uid=None):
295 """Adds a new user with additional parameters
297 :param username: Name of the new user
298 :param password: Password for the new user
299 :param force_password_change_at_next_login_req: Force password change
300 :param useusernameascn: Use username as cn rather that firstname +
301 initials + lastname
302 :param userou: Object container (without domainDN postfix) for new user
303 :param surname: Surname of the new user
304 :param givenname: First name of the new user
305 :param initials: Initials of the new user
306 :param profilepath: Profile path of the new user
307 :param scriptpath: Logon script path of the new user
308 :param homedrive: Home drive of the new user
309 :param homedirectory: Home directory of the new user
310 :param jobtitle: Job title of the new user
311 :param department: Department of the new user
312 :param company: Company of the new user
313 :param description: of the new user
314 :param mailaddress: Email address of the new user
315 :param internetaddress: Home page of the new user
316 :param telephonenumber: Phone number of the new user
317 :param physicaldeliveryoffice: Office location of the new user
318 :param sd: security descriptor of the object
319 :param setpassword: optionally disable password reset
320 :param uidnumber: RFC2307 Unix numeric UID of the new user
321 :param gidnumber: RFC2307 Unix primary GID of the new user
322 :param gecos: RFC2307 Unix GECOS field of the new user
323 :param loginshell: RFC2307 Unix login shell of the new user
324 :param uid: RFC2307 Unix username of the new user
327 displayname = ""
328 if givenname is not None:
329 displayname += givenname
331 if initials is not None:
332 displayname += ' %s.' % initials
334 if surname is not None:
335 displayname += ' %s' % surname
337 cn = username
338 if useusernameascn is None and displayname is not "":
339 cn = displayname
341 user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
343 dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
344 user_principal_name = "%s@%s" % (username, dnsdomain)
345 # The new user record. Note the reliance on the SAMLDB module which
346 # fills in the default informations
347 ldbmessage = {"dn": user_dn,
348 "sAMAccountName": username,
349 "userPrincipalName": user_principal_name,
350 "objectClass": "user"}
352 if surname is not None:
353 ldbmessage["sn"] = surname
355 if givenname is not None:
356 ldbmessage["givenName"] = givenname
358 if displayname is not "":
359 ldbmessage["displayName"] = displayname
360 ldbmessage["name"] = displayname
362 if initials is not None:
363 ldbmessage["initials"] = '%s.' % initials
365 if profilepath is not None:
366 ldbmessage["profilePath"] = profilepath
368 if scriptpath is not None:
369 ldbmessage["scriptPath"] = scriptpath
371 if homedrive is not None:
372 ldbmessage["homeDrive"] = homedrive
374 if homedirectory is not None:
375 ldbmessage["homeDirectory"] = homedirectory
377 if jobtitle is not None:
378 ldbmessage["title"] = jobtitle
380 if department is not None:
381 ldbmessage["department"] = department
383 if company is not None:
384 ldbmessage["company"] = company
386 if description is not None:
387 ldbmessage["description"] = description
389 if mailaddress is not None:
390 ldbmessage["mail"] = mailaddress
392 if internetaddress is not None:
393 ldbmessage["wWWHomePage"] = internetaddress
395 if telephonenumber is not None:
396 ldbmessage["telephoneNumber"] = telephonenumber
398 if physicaldeliveryoffice is not None:
399 ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
401 if sd is not None:
402 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
404 ldbmessage2 = None
405 if any(map(lambda b: b is not None, (uid, uidnumber, gidnumber, gecos, loginshell))):
406 ldbmessage2 = ldb.Message()
407 ldbmessage2.dn = ldb.Dn(self, user_dn)
408 ldbmessage2["objectClass"] = ldb.MessageElement('posixAccount', ldb.FLAG_MOD_ADD, 'objectClass')
409 if uid is not None:
410 ldbmessage2["uid"] = ldb.MessageElement(str(uid), ldb.FLAG_MOD_REPLACE, 'uid')
411 if uidnumber is not None:
412 ldbmessage2["uidNumber"] = ldb.MessageElement(str(uidnumber), ldb.FLAG_MOD_REPLACE, 'uidNumber')
413 if gidnumber is not None:
414 ldbmessage2["gidNumber"] = ldb.MessageElement(str(gidnumber), ldb.FLAG_MOD_REPLACE, 'gidNumber')
415 if gecos is not None:
416 ldbmessage2["gecos"] = ldb.MessageElement(str(gecos), ldb.FLAG_MOD_REPLACE, 'gecos')
417 if loginshell is not None:
418 ldbmessage2["loginShell"] = ldb.MessageElement(str(loginshell), ldb.FLAG_MOD_REPLACE, 'loginShell')
420 self.transaction_start()
421 try:
422 self.add(ldbmessage)
423 if ldbmessage2:
424 self.modify(ldbmessage2)
426 # Sets the password for it
427 if setpassword:
428 self.setpassword("(samAccountName=%s)" % ldb.binary_encode(username), password,
429 force_password_change_at_next_login_req)
430 except:
431 self.transaction_cancel()
432 raise
433 else:
434 self.transaction_commit()
437 def deleteuser(self, username):
438 """Deletes a user
440 :param username: Name of the target user
443 filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
444 self.transaction_start()
445 try:
446 target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
447 expression=filter, attrs=[])
448 if len(target) == 0:
449 raise Exception('Unable to find user "%s"' % username)
450 assert(len(target) == 1)
451 self.delete(target[0].dn)
452 except:
453 self.transaction_cancel()
454 raise
455 else:
456 self.transaction_commit()
458 def setpassword(self, search_filter, password,
459 force_change_at_next_login=False, username=None):
460 """Sets the password for a user
462 :param search_filter: LDAP filter to find the user (eg
463 samccountname=name)
464 :param password: Password for the user
465 :param force_change_at_next_login: Force password change
467 self.transaction_start()
468 try:
469 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
470 expression=search_filter, attrs=[])
471 if len(res) == 0:
472 raise Exception('Unable to find user "%s"' % (username or search_filter))
473 if len(res) > 1:
474 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
475 user_dn = res[0].dn
476 pw = unicode('"' + password + '"', 'utf-8').encode('utf-16-le')
477 setpw = """
478 dn: %s
479 changetype: modify
480 replace: unicodePwd
481 unicodePwd:: %s
482 """ % (user_dn, base64.b64encode(pw))
484 self.modify_ldif(setpw)
486 if force_change_at_next_login:
487 self.force_password_change_at_next_login(
488 "(distinguishedName=" + str(user_dn) + ")")
490 # modify the userAccountControl to remove the disabled bit
491 self.enable_account(search_filter)
492 except:
493 self.transaction_cancel()
494 raise
495 else:
496 self.transaction_commit()
498 def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
499 """Sets the account expiry for a user
501 :param search_filter: LDAP filter to find the user (eg
502 samaccountname=name)
503 :param expiry_seconds: expiry time from now in seconds
504 :param no_expiry_req: if set, then don't expire password
506 self.transaction_start()
507 try:
508 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
509 expression=search_filter,
510 attrs=["userAccountControl", "accountExpires"])
511 if len(res) == 0:
512 raise Exception('Unable to find user "%s"' % search_filter)
513 assert(len(res) == 1)
514 user_dn = res[0].dn
516 userAccountControl = int(res[0]["userAccountControl"][0])
517 accountExpires = int(res[0]["accountExpires"][0])
518 if no_expiry_req:
519 userAccountControl = userAccountControl | 0x10000
520 accountExpires = 0
521 else:
522 userAccountControl = userAccountControl & ~0x10000
523 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
525 setexp = """
526 dn: %s
527 changetype: modify
528 replace: userAccountControl
529 userAccountControl: %u
530 replace: accountExpires
531 accountExpires: %u
532 """ % (user_dn, userAccountControl, accountExpires)
534 self.modify_ldif(setexp)
535 except:
536 self.transaction_cancel()
537 raise
538 else:
539 self.transaction_commit()
541 def set_domain_sid(self, sid):
542 """Change the domain SID used by this LDB.
544 :param sid: The new domain sid to use.
546 dsdb._samdb_set_domain_sid(self, sid)
548 def get_domain_sid(self):
549 """Read the domain SID used by this LDB. """
550 return dsdb._samdb_get_domain_sid(self)
552 domain_sid = property(get_domain_sid, set_domain_sid,
553 "SID for the domain")
555 def set_invocation_id(self, invocation_id):
556 """Set the invocation id for this SamDB handle.
558 :param invocation_id: GUID of the invocation id.
560 dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
562 def get_invocation_id(self):
563 """Get the invocation_id id"""
564 return dsdb._samdb_ntds_invocation_id(self)
566 invocation_id = property(get_invocation_id, set_invocation_id,
567 "Invocation ID GUID")
569 def get_oid_from_attid(self, attid):
570 return dsdb._dsdb_get_oid_from_attid(self, attid)
572 def get_attid_from_lDAPDisplayName(self, ldap_display_name,
573 is_schema_nc=False):
574 '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
575 return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
576 ldap_display_name, is_schema_nc)
578 def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
579 '''return the syntax OID for a LDAP attribute as a string'''
580 return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
582 def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
583 '''return the systemFlags for a LDAP attribute as a integer'''
584 return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
586 def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
587 '''return the linkID for a LDAP attribute as a integer'''
588 return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
590 def get_lDAPDisplayName_by_attid(self, attid):
591 '''return the lDAPDisplayName from an integer DRS attribute ID'''
592 return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
594 def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
595 '''return the attribute name of the corresponding backlink from the name
596 of a forward link attribute. If there is no backlink return None'''
597 return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
599 def set_ntds_settings_dn(self, ntds_settings_dn):
600 """Set the NTDS Settings DN, as would be returned on the dsServiceName
601 rootDSE attribute.
603 This allows the DN to be set before the database fully exists
605 :param ntds_settings_dn: The new DN to use
607 dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
609 def get_ntds_GUID(self):
610 """Get the NTDS objectGUID"""
611 return dsdb._samdb_ntds_objectGUID(self)
613 def server_site_name(self):
614 """Get the server site name"""
615 return dsdb._samdb_server_site_name(self)
617 def host_dns_name(self):
618 """return the DNS name of this host"""
619 res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
620 return res[0]['dNSHostName'][0]
622 def domain_dns_name(self):
623 """return the DNS name of the domain root"""
624 domain_dn = self.get_default_basedn()
625 return domain_dn.canonical_str().split('/')[0]
627 def forest_dns_name(self):
628 """return the DNS name of the forest root"""
629 forest_dn = self.get_root_basedn()
630 return forest_dn.canonical_str().split('/')[0]
632 def load_partition_usn(self, base_dn):
633 return dsdb._dsdb_load_partition_usn(self, base_dn)
635 def set_schema(self, schema, write_indices_and_attributes=True):
636 self.set_schema_from_ldb(schema.ldb, write_indices_and_attributes=write_indices_and_attributes)
638 def set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes=True):
639 dsdb._dsdb_set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes)
641 def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
642 '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
643 return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
645 def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
646 '''normalise a list of attribute values'''
647 return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
649 def get_attribute_from_attid(self, attid):
650 """ Get from an attid the associated attribute
652 :param attid: The attribute id for searched attribute
653 :return: The name of the attribute associated with this id
655 if len(self.hash_oid_name.keys()) == 0:
656 self._populate_oid_attid()
657 if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
658 return self.hash_oid_name[self.get_oid_from_attid(attid)]
659 else:
660 return None
662 def _populate_oid_attid(self):
663 """Populate the hash hash_oid_name.
665 This hash contains the oid of the attribute as a key and
666 its display name as a value
668 self.hash_oid_name = {}
669 res = self.search(expression="objectClass=attributeSchema",
670 controls=["search_options:1:2"],
671 attrs=["attributeID",
672 "lDAPDisplayName"])
673 if len(res) > 0:
674 for e in res:
675 strDisplay = str(e.get("lDAPDisplayName"))
676 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
678 def get_attribute_replmetadata_version(self, dn, att):
679 """Get the version field trom the replPropertyMetaData for
680 the given field
682 :param dn: The on which we want to get the version
683 :param att: The name of the attribute
684 :return: The value of the version field in the replPropertyMetaData
685 for the given attribute. None if the attribute is not replicated
688 res = self.search(expression="distinguishedName=%s" % dn,
689 scope=ldb.SCOPE_SUBTREE,
690 controls=["search_options:1:2"],
691 attrs=["replPropertyMetaData"])
692 if len(res) == 0:
693 return None
695 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
696 str(res[0]["replPropertyMetaData"]))
697 ctr = repl.ctr
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 return o.version
706 return None
708 def set_attribute_replmetadata_version(self, dn, att, value,
709 addifnotexist=False):
710 res = self.search(expression="distinguishedName=%s" % dn,
711 scope=ldb.SCOPE_SUBTREE,
712 controls=["search_options:1:2"],
713 attrs=["replPropertyMetaData"])
714 if len(res) == 0:
715 return None
717 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
718 str(res[0]["replPropertyMetaData"]))
719 ctr = repl.ctr
720 now = samba.unix2nttime(int(time.time()))
721 found = False
722 if len(self.hash_oid_name.keys()) == 0:
723 self._populate_oid_attid()
724 for o in ctr.array:
725 # Search for Description
726 att_oid = self.get_oid_from_attid(o.attid)
727 if self.hash_oid_name.has_key(att_oid) and\
728 att.lower() == self.hash_oid_name[att_oid].lower():
729 found = True
730 seq = self.sequence_number(ldb.SEQ_NEXT)
731 o.version = value
732 o.originating_change_time = now
733 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
734 o.originating_usn = seq
735 o.local_usn = seq
737 if not found and addifnotexist and len(ctr.array) >0:
738 o2 = drsblobs.replPropertyMetaData1()
739 o2.attid = 589914
740 att_oid = self.get_oid_from_attid(o2.attid)
741 seq = self.sequence_number(ldb.SEQ_NEXT)
742 o2.version = value
743 o2.originating_change_time = now
744 o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
745 o2.originating_usn = seq
746 o2.local_usn = seq
747 found = True
748 tab = ctr.array
749 tab.append(o2)
750 ctr.count = ctr.count + 1
751 ctr.array = tab
753 if found :
754 replBlob = ndr_pack(repl)
755 msg = ldb.Message()
756 msg.dn = res[0].dn
757 msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
758 ldb.FLAG_MOD_REPLACE,
759 "replPropertyMetaData")
760 self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
762 def write_prefixes_from_schema(self):
763 dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
765 def get_partitions_dn(self):
766 return dsdb._dsdb_get_partitions_dn(self)
768 def get_nc_root(self, dn):
769 return dsdb._dsdb_get_nc_root(self, dn)
771 def get_wellknown_dn(self, nc_root, wkguid):
772 return dsdb._dsdb_get_wellknown_dn(self, nc_root, wkguid)
774 def set_minPwdAge(self, value):
775 m = ldb.Message()
776 m.dn = ldb.Dn(self, self.domain_dn())
777 m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
778 self.modify(m)
780 def get_minPwdAge(self):
781 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
782 if len(res) == 0:
783 return None
784 elif not "minPwdAge" in res[0]:
785 return None
786 else:
787 return res[0]["minPwdAge"][0]
789 def set_minPwdLength(self, value):
790 m = ldb.Message()
791 m.dn = ldb.Dn(self, self.domain_dn())
792 m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
793 self.modify(m)
795 def get_minPwdLength(self):
796 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
797 if len(res) == 0:
798 return None
799 elif not "minPwdLength" in res[0]:
800 return None
801 else:
802 return res[0]["minPwdLength"][0]
804 def set_pwdProperties(self, value):
805 m = ldb.Message()
806 m.dn = ldb.Dn(self, self.domain_dn())
807 m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
808 self.modify(m)
810 def get_pwdProperties(self):
811 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
812 if len(res) == 0:
813 return None
814 elif not "pwdProperties" in res[0]:
815 return None
816 else:
817 return res[0]["pwdProperties"][0]
819 def set_dsheuristics(self, dsheuristics):
820 m = ldb.Message()
821 m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
822 % self.get_config_basedn().get_linearized())
823 if dsheuristics is not None:
824 m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
825 ldb.FLAG_MOD_REPLACE, "dSHeuristics")
826 else:
827 m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
828 "dSHeuristics")
829 self.modify(m)
831 def get_dsheuristics(self):
832 res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
833 % self.get_config_basedn().get_linearized(),
834 scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
835 if len(res) == 0:
836 dsheuristics = None
837 elif "dSHeuristics" in res[0]:
838 dsheuristics = res[0]["dSHeuristics"][0]
839 else:
840 dsheuristics = None
842 return dsheuristics
844 def create_ou(self, ou_dn, description=None, name=None, sd=None):
845 """Creates an organizationalUnit object
846 :param ou_dn: dn of the new object
847 :param description: description attribute
848 :param name: name atttribute
849 :param sd: security descriptor of the object, can be
850 an SDDL string or security.descriptor type
852 m = {"dn": ou_dn,
853 "objectClass": "organizationalUnit"}
855 if description:
856 m["description"] = description
857 if name:
858 m["name"] = name
860 if sd:
861 m["nTSecurityDescriptor"] = ndr_pack(sd)
862 self.add(m)
864 def sequence_number(self, seq_type):
865 """Returns the value of the sequence number according to the requested type
866 :param seq_type: type of sequence number
868 self.transaction_start()
869 try:
870 seq = super(SamDB, self).sequence_number(seq_type)
871 except:
872 self.transaction_cancel()
873 raise
874 else:
875 self.transaction_commit()
876 return seq
878 def get_dsServiceName(self):
879 '''get the NTDS DN from the rootDSE'''
880 res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
881 return res[0]["dsServiceName"][0]
883 def get_serverName(self):
884 '''get the server DN from the rootDSE'''
885 res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
886 return res[0]["serverName"][0]