KCC: clarify setup-vertices docstring, remove TODO
[Samba.git] / python / samba / samdb.py
blobe74e823f23ebdab634e043677eec59a085ec41aa
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 gidnumber=None, nisdomain=None):
174 """Adds a new group with additional parameters
176 :param groupname: Name of the new group
177 :param grouptype: Type of the new group
178 :param description: Description of the new group
179 :param mailaddress: Email address of the new group
180 :param notes: Notes of the new group
181 :param gidnumber: GID Number of the new group
182 :param nisdomain: NIS Domain Name of the new group
183 :param sd: security descriptor of the object
186 group_dn = "CN=%s,%s,%s" % (groupname, (groupou or "CN=Users"), self.domain_dn())
188 # The new user record. Note the reliance on the SAMLDB module which
189 # fills in the default informations
190 ldbmessage = {"dn": group_dn,
191 "sAMAccountName": groupname,
192 "objectClass": "group"}
194 ldbmessage["msSFU30Name"] = groupname
196 if grouptype is not None:
197 ldbmessage["groupType"] = normalise_int32(grouptype)
199 if description is not None:
200 ldbmessage["description"] = description
202 if mailaddress is not None:
203 ldbmessage["mail"] = mailaddress
205 if notes is not None:
206 ldbmessage["info"] = notes
208 if gidnumber is not None:
209 ldbmessage["gidNumber"] = normalise_int32(gidnumber)
211 if nisdomain is not None:
212 ldbmessage["msSFU30NisDomain"] = nisdomain
214 if sd is not None:
215 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
217 self.add(ldbmessage)
219 def deletegroup(self, groupname):
220 """Deletes a group
222 :param groupname: Name of the target group
225 groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
226 self.transaction_start()
227 try:
228 targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
229 expression=groupfilter, attrs=[])
230 if len(targetgroup) == 0:
231 raise Exception('Unable to find group "%s"' % groupname)
232 assert(len(targetgroup) == 1)
233 self.delete(targetgroup[0].dn)
234 except:
235 self.transaction_cancel()
236 raise
237 else:
238 self.transaction_commit()
240 def add_remove_group_members(self, groupname, members,
241 add_members_operation=True):
242 """Adds or removes group members
244 :param groupname: Name of the target group
245 :param members: list of group members
246 :param add_members_operation: Defines if its an add or remove
247 operation
250 groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
251 ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
253 self.transaction_start()
254 try:
255 targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
256 expression=groupfilter, attrs=['member'])
257 if len(targetgroup) == 0:
258 raise Exception('Unable to find group "%s"' % groupname)
259 assert(len(targetgroup) == 1)
261 modified = False
263 addtargettogroup = """
264 dn: %s
265 changetype: modify
266 """ % (str(targetgroup[0].dn))
268 for member in members:
269 targetmember = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
270 expression="(|(sAMAccountName=%s)(CN=%s))" % (
271 ldb.binary_encode(member), ldb.binary_encode(member)), attrs=[])
273 if len(targetmember) != 1:
274 raise Exception('Unable to find "%s". Operation cancelled.' % member)
276 if add_members_operation is True and (targetgroup[0].get('member') is None or str(targetmember[0].dn) not in targetgroup[0]['member']):
277 modified = True
278 addtargettogroup += """add: member
279 member: %s
280 """ % (str(targetmember[0].dn))
282 elif add_members_operation is False and (targetgroup[0].get('member') is not None and str(targetmember[0].dn) in targetgroup[0]['member']):
283 modified = True
284 addtargettogroup += """delete: member
285 member: %s
286 """ % (str(targetmember[0].dn))
288 if modified is True:
289 self.modify_ldif(addtargettogroup)
291 except:
292 self.transaction_cancel()
293 raise
294 else:
295 self.transaction_commit()
297 def newuser(self, username, password,
298 force_password_change_at_next_login_req=False,
299 useusernameascn=False, userou=None, surname=None, givenname=None,
300 initials=None, profilepath=None, scriptpath=None, homedrive=None,
301 homedirectory=None, jobtitle=None, department=None, company=None,
302 description=None, mailaddress=None, internetaddress=None,
303 telephonenumber=None, physicaldeliveryoffice=None, sd=None,
304 setpassword=True, uidnumber=None, gidnumber=None, gecos=None,
305 loginshell=None, uid=None, nisdomain=None, unixhome=None):
306 """Adds a new user with additional parameters
308 :param username: Name of the new user
309 :param password: Password for the new user
310 :param force_password_change_at_next_login_req: Force password change
311 :param useusernameascn: Use username as cn rather that firstname +
312 initials + lastname
313 :param userou: Object container (without domainDN postfix) for new user
314 :param surname: Surname of the new user
315 :param givenname: First name of the new user
316 :param initials: Initials of the new user
317 :param profilepath: Profile path of the new user
318 :param scriptpath: Logon script path of the new user
319 :param homedrive: Home drive of the new user
320 :param homedirectory: Home directory of the new user
321 :param jobtitle: Job title of the new user
322 :param department: Department of the new user
323 :param company: Company of the new user
324 :param description: of the new user
325 :param mailaddress: Email address of the new user
326 :param internetaddress: Home page of the new user
327 :param telephonenumber: Phone number of the new user
328 :param physicaldeliveryoffice: Office location of the new user
329 :param sd: security descriptor of the object
330 :param setpassword: optionally disable password reset
331 :param uidnumber: RFC2307 Unix numeric UID of the new user
332 :param gidnumber: RFC2307 Unix primary GID of the new user
333 :param gecos: RFC2307 Unix GECOS field of the new user
334 :param loginshell: RFC2307 Unix login shell of the new user
335 :param uid: RFC2307 Unix username of the new user
336 :param nisdomain: RFC2307 Unix NIS domain of the new user
337 :param unixhome: RFC2307 Unix home directory of the new user
340 displayname = ""
341 if givenname is not None:
342 displayname += givenname
344 if initials is not None:
345 displayname += ' %s.' % initials
347 if surname is not None:
348 displayname += ' %s' % surname
350 cn = username
351 if useusernameascn is None and displayname is not "":
352 cn = displayname
354 user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
356 dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
357 user_principal_name = "%s@%s" % (username, dnsdomain)
358 # The new user record. Note the reliance on the SAMLDB module which
359 # fills in the default informations
360 ldbmessage = {"dn": user_dn,
361 "sAMAccountName": username,
362 "userPrincipalName": user_principal_name,
363 "objectClass": "user"}
365 if surname is not None:
366 ldbmessage["sn"] = surname
368 if givenname is not None:
369 ldbmessage["givenName"] = givenname
371 if displayname is not "":
372 ldbmessage["displayName"] = displayname
373 ldbmessage["name"] = displayname
375 if initials is not None:
376 ldbmessage["initials"] = '%s.' % initials
378 if profilepath is not None:
379 ldbmessage["profilePath"] = profilepath
381 if scriptpath is not None:
382 ldbmessage["scriptPath"] = scriptpath
384 if homedrive is not None:
385 ldbmessage["homeDrive"] = homedrive
387 if homedirectory is not None:
388 ldbmessage["homeDirectory"] = homedirectory
390 if jobtitle is not None:
391 ldbmessage["title"] = jobtitle
393 if department is not None:
394 ldbmessage["department"] = department
396 if company is not None:
397 ldbmessage["company"] = company
399 if description is not None:
400 ldbmessage["description"] = description
402 if mailaddress is not None:
403 ldbmessage["mail"] = mailaddress
405 if internetaddress is not None:
406 ldbmessage["wWWHomePage"] = internetaddress
408 if telephonenumber is not None:
409 ldbmessage["telephoneNumber"] = telephonenumber
411 if physicaldeliveryoffice is not None:
412 ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
414 if sd is not None:
415 ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
417 ldbmessage2 = None
418 if any(map(lambda b: b is not None, (uid, uidnumber, gidnumber, gecos,
419 loginshell, nisdomain, unixhome))):
420 ldbmessage2 = ldb.Message()
421 ldbmessage2.dn = ldb.Dn(self, user_dn)
422 ldbmessage2["objectClass"] = ldb.MessageElement('posixAccount', ldb.FLAG_MOD_ADD, 'objectClass')
423 if uid is not None:
424 ldbmessage2["uid"] = ldb.MessageElement(str(uid), ldb.FLAG_MOD_REPLACE, 'uid')
425 if uidnumber is not None:
426 ldbmessage2["uidNumber"] = ldb.MessageElement(str(uidnumber), ldb.FLAG_MOD_REPLACE, 'uidNumber')
427 if gidnumber is not None:
428 ldbmessage2["gidNumber"] = ldb.MessageElement(str(gidnumber), ldb.FLAG_MOD_REPLACE, 'gidNumber')
429 if gecos is not None:
430 ldbmessage2["gecos"] = ldb.MessageElement(str(gecos), ldb.FLAG_MOD_REPLACE, 'gecos')
431 if loginshell is not None:
432 ldbmessage2["loginShell"] = ldb.MessageElement(str(loginshell), ldb.FLAG_MOD_REPLACE, 'loginShell')
433 if unixhome is not None:
434 ldbmessage2["unixHomeDirectory"] = ldb.MessageElement(
435 str(unixhome), ldb.FLAG_MOD_REPLACE, 'unixHomeDirectory')
436 if nisdomain is not None:
437 ldbmessage2["msSFU30NisDomain"] = ldb.MessageElement(
438 str(nisdomain), ldb.FLAG_MOD_REPLACE, 'msSFU30NisDomain')
439 ldbmessage2["msSFU30Name"] = ldb.MessageElement(
440 str(username), ldb.FLAG_MOD_REPLACE, 'msSFU30Name')
441 ldbmessage2["unixUserPassword"] = ldb.MessageElement(
442 'ABCD!efgh12345$67890', ldb.FLAG_MOD_REPLACE,
443 'unixUserPassword')
445 self.transaction_start()
446 try:
447 self.add(ldbmessage)
448 if ldbmessage2:
449 self.modify(ldbmessage2)
451 # Sets the password for it
452 if setpassword:
453 self.setpassword("(samAccountName=%s)" % ldb.binary_encode(username), password,
454 force_password_change_at_next_login_req)
455 except:
456 self.transaction_cancel()
457 raise
458 else:
459 self.transaction_commit()
462 def deleteuser(self, username):
463 """Deletes a user
465 :param username: Name of the target user
468 filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
469 self.transaction_start()
470 try:
471 target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
472 expression=filter, attrs=[])
473 if len(target) == 0:
474 raise Exception('Unable to find user "%s"' % username)
475 assert(len(target) == 1)
476 self.delete(target[0].dn)
477 except:
478 self.transaction_cancel()
479 raise
480 else:
481 self.transaction_commit()
483 def setpassword(self, search_filter, password,
484 force_change_at_next_login=False, username=None):
485 """Sets the password for a user
487 :param search_filter: LDAP filter to find the user (eg
488 samccountname=name)
489 :param password: Password for the user
490 :param force_change_at_next_login: Force password change
492 self.transaction_start()
493 try:
494 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
495 expression=search_filter, attrs=[])
496 if len(res) == 0:
497 raise Exception('Unable to find user "%s"' % (username or search_filter))
498 if len(res) > 1:
499 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
500 user_dn = res[0].dn
501 pw = unicode('"' + password + '"', 'utf-8').encode('utf-16-le')
502 setpw = """
503 dn: %s
504 changetype: modify
505 replace: unicodePwd
506 unicodePwd:: %s
507 """ % (user_dn, base64.b64encode(pw))
509 self.modify_ldif(setpw)
511 if force_change_at_next_login:
512 self.force_password_change_at_next_login(
513 "(distinguishedName=" + str(user_dn) + ")")
515 # modify the userAccountControl to remove the disabled bit
516 self.enable_account(search_filter)
517 except:
518 self.transaction_cancel()
519 raise
520 else:
521 self.transaction_commit()
523 def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
524 """Sets the account expiry for a user
526 :param search_filter: LDAP filter to find the user (eg
527 samaccountname=name)
528 :param expiry_seconds: expiry time from now in seconds
529 :param no_expiry_req: if set, then don't expire password
531 self.transaction_start()
532 try:
533 res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
534 expression=search_filter,
535 attrs=["userAccountControl", "accountExpires"])
536 if len(res) == 0:
537 raise Exception('Unable to find user "%s"' % search_filter)
538 assert(len(res) == 1)
539 user_dn = res[0].dn
541 userAccountControl = int(res[0]["userAccountControl"][0])
542 accountExpires = int(res[0]["accountExpires"][0])
543 if no_expiry_req:
544 userAccountControl = userAccountControl | 0x10000
545 accountExpires = 0
546 else:
547 userAccountControl = userAccountControl & ~0x10000
548 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
550 setexp = """
551 dn: %s
552 changetype: modify
553 replace: userAccountControl
554 userAccountControl: %u
555 replace: accountExpires
556 accountExpires: %u
557 """ % (user_dn, userAccountControl, accountExpires)
559 self.modify_ldif(setexp)
560 except:
561 self.transaction_cancel()
562 raise
563 else:
564 self.transaction_commit()
566 def set_domain_sid(self, sid):
567 """Change the domain SID used by this LDB.
569 :param sid: The new domain sid to use.
571 dsdb._samdb_set_domain_sid(self, sid)
573 def get_domain_sid(self):
574 """Read the domain SID used by this LDB. """
575 return dsdb._samdb_get_domain_sid(self)
577 domain_sid = property(get_domain_sid, set_domain_sid,
578 "SID for the domain")
580 def set_invocation_id(self, invocation_id):
581 """Set the invocation id for this SamDB handle.
583 :param invocation_id: GUID of the invocation id.
585 dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
587 def get_invocation_id(self):
588 """Get the invocation_id id"""
589 return dsdb._samdb_ntds_invocation_id(self)
591 invocation_id = property(get_invocation_id, set_invocation_id,
592 "Invocation ID GUID")
594 def get_oid_from_attid(self, attid):
595 return dsdb._dsdb_get_oid_from_attid(self, attid)
597 def get_attid_from_lDAPDisplayName(self, ldap_display_name,
598 is_schema_nc=False):
599 '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
600 return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
601 ldap_display_name, is_schema_nc)
603 def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
604 '''return the syntax OID for a LDAP attribute as a string'''
605 return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
607 def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
608 '''return the systemFlags for a LDAP attribute as a integer'''
609 return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
611 def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
612 '''return the linkID for a LDAP attribute as a integer'''
613 return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
615 def get_lDAPDisplayName_by_attid(self, attid):
616 '''return the lDAPDisplayName from an integer DRS attribute ID'''
617 return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
619 def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
620 '''return the attribute name of the corresponding backlink from the name
621 of a forward link attribute. If there is no backlink return None'''
622 return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
624 def set_ntds_settings_dn(self, ntds_settings_dn):
625 """Set the NTDS Settings DN, as would be returned on the dsServiceName
626 rootDSE attribute.
628 This allows the DN to be set before the database fully exists
630 :param ntds_settings_dn: The new DN to use
632 dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
634 def get_ntds_GUID(self):
635 """Get the NTDS objectGUID"""
636 return dsdb._samdb_ntds_objectGUID(self)
638 def server_site_name(self):
639 """Get the server site name"""
640 return dsdb._samdb_server_site_name(self)
642 def host_dns_name(self):
643 """return the DNS name of this host"""
644 res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
645 return res[0]['dNSHostName'][0]
647 def domain_dns_name(self):
648 """return the DNS name of the domain root"""
649 domain_dn = self.get_default_basedn()
650 return domain_dn.canonical_str().split('/')[0]
652 def forest_dns_name(self):
653 """return the DNS name of the forest root"""
654 forest_dn = self.get_root_basedn()
655 return forest_dn.canonical_str().split('/')[0]
657 def load_partition_usn(self, base_dn):
658 return dsdb._dsdb_load_partition_usn(self, base_dn)
660 def set_schema(self, schema, write_indices_and_attributes=True):
661 self.set_schema_from_ldb(schema.ldb, write_indices_and_attributes=write_indices_and_attributes)
663 def set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes=True):
664 dsdb._dsdb_set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes)
666 def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
667 '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
668 return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
670 def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
671 '''normalise a list of attribute values'''
672 return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
674 def get_attribute_from_attid(self, attid):
675 """ Get from an attid the associated attribute
677 :param attid: The attribute id for searched attribute
678 :return: The name of the attribute associated with this id
680 if len(self.hash_oid_name.keys()) == 0:
681 self._populate_oid_attid()
682 if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
683 return self.hash_oid_name[self.get_oid_from_attid(attid)]
684 else:
685 return None
687 def _populate_oid_attid(self):
688 """Populate the hash hash_oid_name.
690 This hash contains the oid of the attribute as a key and
691 its display name as a value
693 self.hash_oid_name = {}
694 res = self.search(expression="objectClass=attributeSchema",
695 controls=["search_options:1:2"],
696 attrs=["attributeID",
697 "lDAPDisplayName"])
698 if len(res) > 0:
699 for e in res:
700 strDisplay = str(e.get("lDAPDisplayName"))
701 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
703 def get_attribute_replmetadata_version(self, dn, att):
704 """Get the version field trom the replPropertyMetaData for
705 the given field
707 :param dn: The on which we want to get the version
708 :param att: The name of the attribute
709 :return: The value of the version field in the replPropertyMetaData
710 for the given attribute. None if the attribute is not replicated
713 res = self.search(expression="distinguishedName=%s" % dn,
714 scope=ldb.SCOPE_SUBTREE,
715 controls=["search_options:1:2"],
716 attrs=["replPropertyMetaData"])
717 if len(res) == 0:
718 return None
720 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
721 str(res[0]["replPropertyMetaData"]))
722 ctr = repl.ctr
723 if len(self.hash_oid_name.keys()) == 0:
724 self._populate_oid_attid()
725 for o in ctr.array:
726 # Search for Description
727 att_oid = self.get_oid_from_attid(o.attid)
728 if self.hash_oid_name.has_key(att_oid) and\
729 att.lower() == self.hash_oid_name[att_oid].lower():
730 return o.version
731 return None
733 def set_attribute_replmetadata_version(self, dn, att, value,
734 addifnotexist=False):
735 res = self.search(expression="distinguishedName=%s" % dn,
736 scope=ldb.SCOPE_SUBTREE,
737 controls=["search_options:1:2"],
738 attrs=["replPropertyMetaData"])
739 if len(res) == 0:
740 return None
742 repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
743 str(res[0]["replPropertyMetaData"]))
744 ctr = repl.ctr
745 now = samba.unix2nttime(int(time.time()))
746 found = False
747 if len(self.hash_oid_name.keys()) == 0:
748 self._populate_oid_attid()
749 for o in ctr.array:
750 # Search for Description
751 att_oid = self.get_oid_from_attid(o.attid)
752 if self.hash_oid_name.has_key(att_oid) and\
753 att.lower() == self.hash_oid_name[att_oid].lower():
754 found = True
755 seq = self.sequence_number(ldb.SEQ_NEXT)
756 o.version = value
757 o.originating_change_time = now
758 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
759 o.originating_usn = seq
760 o.local_usn = seq
762 if not found and addifnotexist and len(ctr.array) >0:
763 o2 = drsblobs.replPropertyMetaData1()
764 o2.attid = 589914
765 att_oid = self.get_oid_from_attid(o2.attid)
766 seq = self.sequence_number(ldb.SEQ_NEXT)
767 o2.version = value
768 o2.originating_change_time = now
769 o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
770 o2.originating_usn = seq
771 o2.local_usn = seq
772 found = True
773 tab = ctr.array
774 tab.append(o2)
775 ctr.count = ctr.count + 1
776 ctr.array = tab
778 if found :
779 replBlob = ndr_pack(repl)
780 msg = ldb.Message()
781 msg.dn = res[0].dn
782 msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
783 ldb.FLAG_MOD_REPLACE,
784 "replPropertyMetaData")
785 self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
787 def write_prefixes_from_schema(self):
788 dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
790 def get_partitions_dn(self):
791 return dsdb._dsdb_get_partitions_dn(self)
793 def get_nc_root(self, dn):
794 return dsdb._dsdb_get_nc_root(self, dn)
796 def get_wellknown_dn(self, nc_root, wkguid):
797 return dsdb._dsdb_get_wellknown_dn(self, nc_root, wkguid)
799 def set_minPwdAge(self, value):
800 m = ldb.Message()
801 m.dn = ldb.Dn(self, self.domain_dn())
802 m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
803 self.modify(m)
805 def get_minPwdAge(self):
806 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
807 if len(res) == 0:
808 return None
809 elif not "minPwdAge" in res[0]:
810 return None
811 else:
812 return res[0]["minPwdAge"][0]
814 def set_minPwdLength(self, value):
815 m = ldb.Message()
816 m.dn = ldb.Dn(self, self.domain_dn())
817 m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
818 self.modify(m)
820 def get_minPwdLength(self):
821 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
822 if len(res) == 0:
823 return None
824 elif not "minPwdLength" in res[0]:
825 return None
826 else:
827 return res[0]["minPwdLength"][0]
829 def set_pwdProperties(self, value):
830 m = ldb.Message()
831 m.dn = ldb.Dn(self, self.domain_dn())
832 m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
833 self.modify(m)
835 def get_pwdProperties(self):
836 res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
837 if len(res) == 0:
838 return None
839 elif not "pwdProperties" in res[0]:
840 return None
841 else:
842 return res[0]["pwdProperties"][0]
844 def set_dsheuristics(self, dsheuristics):
845 m = ldb.Message()
846 m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
847 % self.get_config_basedn().get_linearized())
848 if dsheuristics is not None:
849 m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
850 ldb.FLAG_MOD_REPLACE, "dSHeuristics")
851 else:
852 m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
853 "dSHeuristics")
854 self.modify(m)
856 def get_dsheuristics(self):
857 res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
858 % self.get_config_basedn().get_linearized(),
859 scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
860 if len(res) == 0:
861 dsheuristics = None
862 elif "dSHeuristics" in res[0]:
863 dsheuristics = res[0]["dSHeuristics"][0]
864 else:
865 dsheuristics = None
867 return dsheuristics
869 def create_ou(self, ou_dn, description=None, name=None, sd=None):
870 """Creates an organizationalUnit object
871 :param ou_dn: dn of the new object
872 :param description: description attribute
873 :param name: name atttribute
874 :param sd: security descriptor of the object, can be
875 an SDDL string or security.descriptor type
877 m = {"dn": ou_dn,
878 "objectClass": "organizationalUnit"}
880 if description:
881 m["description"] = description
882 if name:
883 m["name"] = name
885 if sd:
886 m["nTSecurityDescriptor"] = ndr_pack(sd)
887 self.add(m)
889 def sequence_number(self, seq_type):
890 """Returns the value of the sequence number according to the requested type
891 :param seq_type: type of sequence number
893 self.transaction_start()
894 try:
895 seq = super(SamDB, self).sequence_number(seq_type)
896 except:
897 self.transaction_cancel()
898 raise
899 else:
900 self.transaction_commit()
901 return seq
903 def get_dsServiceName(self):
904 '''get the NTDS DN from the rootDSE'''
905 res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
906 return res[0]["dsServiceName"][0]
908 def get_serverName(self):
909 '''get the server DN from the rootDSE'''
910 res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
911 return res[0]["serverName"][0]