samba.samdb: Fix formatting of arguments in docstring.
[samba.git] / source4 / scripting / python / samba / samdb.py
1 #!/usr/bin/env python
2
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
5 # Copyright (C) Matthias Dieter Wallnoefer 2009
6 #
7 # Based on the original in EJS:
8 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
9 # Copyright (C) Giampaolo Lauria <lauria2@yahoo.com> 2011
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 #
24
25 """Convenience functions for using the SAM."""
26
27 import samba
28 import ldb
29 import time
30 import base64
31 import os
32 from samba import dsdb
33 from samba.ndr import ndr_unpack, ndr_pack
34 from samba.dcerpc import drsblobs, misc
35 from samba.common import normalise_int32
36
37 __docformat__ = "restructuredText"
38
39
40 class SamDB(samba.Ldb):
41     """The SAM database."""
42
43     hash_oid_name = {}
44
45     def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
46                  credentials=None, flags=0, options=None, global_schema=True,
47                  auto_connect=True, am_rodc=None):
48         self.lp = lp
49         if not auto_connect:
50             url = None
51         elif url is None and lp is not None:
52             url = lp.samdb_url()
53
54         self.url = url
55
56         super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
57             session_info=session_info, credentials=credentials, flags=flags,
58             options=options)
59
60         if global_schema:
61             dsdb._dsdb_set_global_schema(self)
62
63         if am_rodc is not None:
64             dsdb._dsdb_set_am_rodc(self, am_rodc)
65
66     def connect(self, url=None, flags=0, options=None):
67         '''connect to the database'''
68         if self.lp is not None and not os.path.exists(url):
69             url = self.lp.private_path(url)
70         self.url = url
71
72         super(SamDB, self).connect(url=url, flags=flags,
73                 options=options)
74
75     def am_rodc(self):
76         '''return True if we are an RODC'''
77         return dsdb._am_rodc(self)
78
79     def am_pdc(self):
80         '''return True if we are an PDC emulator'''
81         return dsdb._am_pdc(self)
82
83     def domain_dn(self):
84         '''return the domain DN'''
85         return str(self.get_default_basedn())
86
87     def disable_account(self, search_filter):
88         """Disables an account
89
90         :param search_filter: LDAP filter to find the user (eg
91             samccountname=name)
92         """
93
94         flags = samba.dsdb.UF_ACCOUNTDISABLE
95         self.toggle_userAccountFlags(search_filter, flags, on=True)
96
97     def enable_account(self, search_filter):
98         """Enables an account
99
100         :param search_filter: LDAP filter to find the user (eg
101             samccountname=name)
102         """
103
104         flags = samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_PASSWD_NOTREQD
105         self.toggle_userAccountFlags(search_filter, flags, on=False)
106
107     def toggle_userAccountFlags(self, search_filter, flags, flags_str=None,
108                                 on=True, strict=False):
109         """Toggle_userAccountFlags
110
111         :param search_filter: LDAP filter to find the user (eg
112             samccountname=name)
113         :param flags: samba.dsdb.UF_* flags
114         :param on: on=True (default) => set, on=False => unset
115         :param strict: strict=False (default) ignore if no action is needed
116                  strict=True raises an Exception if...
117         """
118         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
119                           expression=search_filter, attrs=["userAccountControl"])
120         if len(res) == 0:
121                 raise Exception("Unable to find account where '%s'" % search_filter)
122         assert(len(res) == 1)
123         account_dn = res[0].dn
124
125         old_uac = int(res[0]["userAccountControl"][0])
126         if on:
127             if strict and (old_uac & flags):
128                 error = "Account flag(s) '%s' already set" % flags_str
129                 raise Exception(error)
130
131             new_uac = old_uac | flags
132         else:
133             if strict and not (old_uac & flags):
134                 error = "Account flag(s) '%s' already unset" % flags_str
135                 raise Exception(error)
136
137             new_uac = old_uac & ~flags
138
139         if old_uac == new_uac:
140             return
141
142         mod = """
143 dn: %s
144 changetype: modify
145 delete: userAccountControl
146 userAccountControl: %u
147 add: userAccountControl
148 userAccountControl: %u
149 """ % (account_dn, old_uac, new_uac)
150         self.modify_ldif(mod)
151
152     def force_password_change_at_next_login(self, search_filter):
153         """Forces a password change at next login
154
155         :param search_filter: LDAP filter to find the user (eg
156             samccountname=name)
157         """
158         res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
159                           expression=search_filter, attrs=[])
160         if len(res) == 0:
161                 raise Exception('Unable to find user "%s"' % search_filter)
162         assert(len(res) == 1)
163         user_dn = res[0].dn
164
165         mod = """
166 dn: %s
167 changetype: modify
168 replace: pwdLastSet
169 pwdLastSet: 0
170 """ % (user_dn)
171         self.modify_ldif(mod)
172
173     def newgroup(self, groupname, groupou=None, grouptype=None,
174                  description=None, mailaddress=None, notes=None, sd=None):
175         """Adds a new group with additional parameters
176
177         :param groupname: Name of the new group
178         :param grouptype: Type of the new group
179         :param description: Description of the new group
180         :param mailaddress: Email address of the new group
181         :param notes: Notes of the new group
182         :param sd: security descriptor of the object
183         """
184
185         group_dn = "CN=%s,%s,%s" % (groupname, (groupou or "CN=Users"), self.domain_dn())
186
187         # The new user record. Note the reliance on the SAMLDB module which
188         # fills in the default informations
189         ldbmessage = {"dn": group_dn,
190             "sAMAccountName": groupname,
191             "objectClass": "group"}
192
193         if grouptype is not None:
194             ldbmessage["groupType"] = normalise_int32(grouptype)
195
196         if description is not None:
197             ldbmessage["description"] = description
198
199         if mailaddress is not None:
200             ldbmessage["mail"] = mailaddress
201
202         if notes is not None:
203             ldbmessage["info"] = notes
204
205         if sd is not None:
206             ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
207
208         self.add(ldbmessage)
209
210     def deletegroup(self, groupname):
211         """Deletes a group
212
213         :param groupname: Name of the target group
214         """
215
216         groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
217         self.transaction_start()
218         try:
219             targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
220                                expression=groupfilter, attrs=[])
221             if len(targetgroup) == 0:
222                 raise Exception('Unable to find group "%s"' % groupname)
223             assert(len(targetgroup) == 1)
224             self.delete(targetgroup[0].dn)
225         except:
226             self.transaction_cancel()
227             raise
228         else:
229             self.transaction_commit()
230
231     def add_remove_group_members(self, groupname, listofmembers,
232                                   add_members_operation=True):
233         """Adds or removes group members
234
235         :param groupname: Name of the target group
236         :param listofmembers: Comma-separated list of group members
237         :param add_members_operation: Defines if its an add or remove
238             operation
239         """
240
241         groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
242             ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
243         groupmembers = listofmembers.split(',')
244
245         self.transaction_start()
246         try:
247             targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
248                                expression=groupfilter, attrs=['member'])
249             if len(targetgroup) == 0:
250                 raise Exception('Unable to find group "%s"' % groupname)
251             assert(len(targetgroup) == 1)
252
253             modified = False
254
255             addtargettogroup = """
256 dn: %s
257 changetype: modify
258 """ % (str(targetgroup[0].dn))
259
260             for member in groupmembers:
261                 targetmember = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
262                                     expression="(|(sAMAccountName=%s)(CN=%s))" % (
263                     ldb.binary_encode(member), ldb.binary_encode(member)), attrs=[])
264
265                 if len(targetmember) != 1:
266                     continue
267
268                 if add_members_operation is True and (targetgroup[0].get('member') is None or str(targetmember[0].dn) not in targetgroup[0]['member']):
269                     modified = True
270                     addtargettogroup += """add: member
271 member: %s
272 """ % (str(targetmember[0].dn))
273
274                 elif add_members_operation is False and (targetgroup[0].get('member') is not None and str(targetmember[0].dn) in targetgroup[0]['member']):
275                     modified = True
276                     addtargettogroup += """delete: member
277 member: %s
278 """ % (str(targetmember[0].dn))
279
280             if modified is True:
281                 self.modify_ldif(addtargettogroup)
282
283         except:
284             self.transaction_cancel()
285             raise
286         else:
287             self.transaction_commit()
288
289     def newuser(self, username, password,
290             force_password_change_at_next_login_req=False,
291             useusernameascn=False, userou=None, surname=None, givenname=None,
292             initials=None, profilepath=None, scriptpath=None, homedrive=None,
293             homedirectory=None, jobtitle=None, department=None, company=None,
294             description=None, mailaddress=None, internetaddress=None,
295             telephonenumber=None, physicaldeliveryoffice=None, sd=None,
296             setpassword=True):
297         """Adds a new user with additional parameters
298
299         :param username: Name of the new user
300         :param password: Password for the new user
301         :param force_password_change_at_next_login_req: Force password change
302         :param useusernameascn: Use username as cn rather that firstname +
303             initials + lastname
304         :param userou: Object container (without domainDN postfix) for new user
305         :param surname: Surname of the new user
306         :param givenname: First name of the new user
307         :param initials: Initials of the new user
308         :param profilepath: Profile path of the new user
309         :param scriptpath: Logon script path of the new user
310         :param homedrive: Home drive of the new user
311         :param homedirectory: Home directory of the new user
312         :param jobtitle: Job title of the new user
313         :param department: Department of the new user
314         :param company: Company of the new user
315         :param description: of the new user
316         :param mailaddress: Email address of the new user
317         :param internetaddress: Home page of the new user
318         :param telephonenumber: Phone number of the new user
319         :param physicaldeliveryoffice: Office location of the new user
320         :param sd: security descriptor of the object
321         :param setpassword: optionally disable password reset
322         """
323
324         displayname = ""
325         if givenname is not None:
326             displayname += givenname
327
328         if initials is not None:
329             displayname += ' %s.' % initials
330
331         if surname is not None:
332             displayname += ' %s' % surname
333
334         cn = username
335         if useusernameascn is None and displayname is not "":
336             cn = displayname
337
338         user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn())
339
340         dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "")
341         user_principal_name = "%s@%s" % (username, dnsdomain)
342         # The new user record. Note the reliance on the SAMLDB module which
343         # fills in the default informations
344         ldbmessage = {"dn": user_dn,
345                       "sAMAccountName": username,
346                       "userPrincipalName": user_principal_name,
347                       "objectClass": "user"}
348
349         if surname is not None:
350             ldbmessage["sn"] = surname
351
352         if givenname is not None:
353             ldbmessage["givenName"] = givenname
354
355         if displayname is not "":
356             ldbmessage["displayName"] = displayname
357             ldbmessage["name"] = displayname
358
359         if initials is not None:
360             ldbmessage["initials"] = '%s.' % initials
361
362         if profilepath is not None:
363             ldbmessage["profilePath"] = profilepath
364
365         if scriptpath is not None:
366             ldbmessage["scriptPath"] = scriptpath
367
368         if homedrive is not None:
369             ldbmessage["homeDrive"] = homedrive
370
371         if homedirectory is not None:
372             ldbmessage["homeDirectory"] = homedirectory
373
374         if jobtitle is not None:
375             ldbmessage["title"] = jobtitle
376
377         if department is not None:
378             ldbmessage["department"] = department
379
380         if company is not None:
381             ldbmessage["company"] = company
382
383         if description is not None:
384             ldbmessage["description"] = description
385
386         if mailaddress is not None:
387             ldbmessage["mail"] = mailaddress
388
389         if internetaddress is not None:
390             ldbmessage["wWWHomePage"] = internetaddress
391
392         if telephonenumber is not None:
393             ldbmessage["telephoneNumber"] = telephonenumber
394
395         if physicaldeliveryoffice is not None:
396             ldbmessage["physicalDeliveryOfficeName"] = physicaldeliveryoffice
397
398         if sd is not None:
399             ldbmessage["nTSecurityDescriptor"] = ndr_pack(sd)
400
401         self.transaction_start()
402         try:
403             self.add(ldbmessage)
404
405             # Sets the password for it
406             if setpassword:
407                 self.setpassword("(samAccountName=%s)" % ldb.binary_encode(username), password,
408                                  force_password_change_at_next_login_req)
409         except:
410             self.transaction_cancel()
411             raise
412         else:
413             self.transaction_commit()
414
415
416     def deleteuser(self, username):
417         """Deletes a user
418
419         :param username: Name of the target user
420         """
421
422         filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
423         self.transaction_start()
424         try:
425             target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
426                                  expression=filter, attrs=[])
427             if len(target) == 0:
428                 raise Exception('Unable to find user "%s"' % username)
429             assert(len(target) == 1)
430             self.delete(target[0].dn)
431         except:
432             self.transaction_cancel()
433             raise
434         else:
435             self.transaction_commit()
436
437
438     def setpassword(self, search_filter, password,
439             force_change_at_next_login=False, username=None):
440         """Sets the password for a user
441
442         :param search_filter: LDAP filter to find the user (eg
443             samccountname=name)
444         :param password: Password for the user
445         :param force_change_at_next_login: Force password change
446         """
447         self.transaction_start()
448         try:
449             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
450                               expression=search_filter, attrs=[])
451             if len(res) == 0:
452                 raise Exception('Unable to find user "%s"' % (username or search_filter))
453             if len(res) > 1:
454                 raise Exception('Matched %u multiple users with filter "%s"' % (len(res), search_filter))
455             user_dn = res[0].dn
456             setpw = """
457 dn: %s
458 changetype: modify
459 replace: unicodePwd
460 unicodePwd:: %s
461 """ % (user_dn, base64.b64encode(("\"" + password + "\"").encode('utf-16-le')))
462
463             self.modify_ldif(setpw)
464
465             if force_change_at_next_login:
466                 self.force_password_change_at_next_login(
467                   "(dn=" + str(user_dn) + ")")
468
469             #  modify the userAccountControl to remove the disabled bit
470             self.enable_account(search_filter)
471         except:
472             self.transaction_cancel()
473             raise
474         else:
475             self.transaction_commit()
476
477     def setexpiry(self, search_filter, expiry_seconds, no_expiry_req=False):
478         """Sets the account expiry for a user
479
480         :param search_filter: LDAP filter to find the user (eg
481             samaccountname=name)
482         :param expiry_seconds: expiry time from now in seconds
483         :param no_expiry_req: if set, then don't expire password
484         """
485         self.transaction_start()
486         try:
487             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
488                           expression=search_filter,
489                           attrs=["userAccountControl", "accountExpires"])
490             if len(res) == 0:
491                 raise Exception('Unable to find user "%s"' % search_filter)
492             assert(len(res) == 1)
493             user_dn = res[0].dn
494
495             userAccountControl = int(res[0]["userAccountControl"][0])
496             accountExpires     = int(res[0]["accountExpires"][0])
497             if no_expiry_req:
498                 userAccountControl = userAccountControl | 0x10000
499                 accountExpires = 0
500             else:
501                 userAccountControl = userAccountControl & ~0x10000
502                 accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
503
504             setexp = """
505 dn: %s
506 changetype: modify
507 replace: userAccountControl
508 userAccountControl: %u
509 replace: accountExpires
510 accountExpires: %u
511 """ % (user_dn, userAccountControl, accountExpires)
512
513             self.modify_ldif(setexp)
514         except:
515             self.transaction_cancel()
516             raise
517         else:
518             self.transaction_commit()
519
520     def set_domain_sid(self, sid):
521         """Change the domain SID used by this LDB.
522
523         :param sid: The new domain sid to use.
524         """
525         dsdb._samdb_set_domain_sid(self, sid)
526
527     def get_domain_sid(self):
528         """Read the domain SID used by this LDB. """
529         return dsdb._samdb_get_domain_sid(self)
530
531     domain_sid = property(get_domain_sid, set_domain_sid,
532         "SID for the domain")
533
534     def set_invocation_id(self, invocation_id):
535         """Set the invocation id for this SamDB handle.
536
537         :param invocation_id: GUID of the invocation id.
538         """
539         dsdb._dsdb_set_ntds_invocation_id(self, invocation_id)
540
541     def get_invocation_id(self):
542         """Get the invocation_id id"""
543         return dsdb._samdb_ntds_invocation_id(self)
544
545     invocation_id = property(get_invocation_id, set_invocation_id,
546         "Invocation ID GUID")
547
548     def get_oid_from_attid(self, attid):
549         return dsdb._dsdb_get_oid_from_attid(self, attid)
550
551     def get_attid_from_lDAPDisplayName(self, ldap_display_name,
552             is_schema_nc=False):
553         '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
554         return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
555             ldap_display_name, is_schema_nc)
556
557     def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
558         '''return the syntax OID for a LDAP attribute as a string'''
559         return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
560
561     def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
562         '''return the systemFlags for a LDAP attribute as a integer'''
563         return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
564
565     def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
566         '''return the linkID for a LDAP attribute as a integer'''
567         return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
568
569     def get_lDAPDisplayName_by_attid(self, attid):
570         '''return the lDAPDisplayName from an integer DRS attribute ID'''
571         return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
572
573     def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
574         '''return the attribute name of the corresponding backlink from the name
575         of a forward link attribute. If there is no backlink return None'''
576         return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
577
578     def set_ntds_settings_dn(self, ntds_settings_dn):
579         """Set the NTDS Settings DN, as would be returned on the dsServiceName
580         rootDSE attribute.
581
582         This allows the DN to be set before the database fully exists
583
584         :param ntds_settings_dn: The new DN to use
585         """
586         dsdb._samdb_set_ntds_settings_dn(self, ntds_settings_dn)
587
588     def get_ntds_GUID(self):
589         """Get the NTDS objectGUID"""
590         return dsdb._samdb_ntds_objectGUID(self)
591
592     def server_site_name(self):
593         """Get the server site name"""
594         return dsdb._samdb_server_site_name(self)
595
596     def host_dns_name(self):
597         """return the DNS name of this host"""
598         res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
599         return res[0]['dNSHostName'][0]
600
601     def domain_dns_name(self):
602         """return the DNS name of the domain root"""
603         domain_dn = self.get_default_basedn()
604         return domain_dn.canonical_str().split('/')[0]
605
606     def forest_dns_name(self):
607         """return the DNS name of the forest root"""
608         forest_dn = self.get_root_basedn()
609         return forest_dn.canonical_str().split('/')[0]
610
611     def load_partition_usn(self, base_dn):
612         return dsdb._dsdb_load_partition_usn(self, base_dn)
613
614     def set_schema(self, schema):
615         self.set_schema_from_ldb(schema.ldb)
616
617     def set_schema_from_ldb(self, ldb_conn):
618         dsdb._dsdb_set_schema_from_ldb(self, ldb_conn)
619
620     def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
621         '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
622         return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
623
624     def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
625         '''normalise a list of attribute values'''
626         return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
627
628     def get_attribute_from_attid(self, attid):
629         """ Get from an attid the associated attribute
630
631         :param attid: The attribute id for searched attribute
632         :return: The name of the attribute associated with this id
633         """
634         if len(self.hash_oid_name.keys()) == 0:
635             self._populate_oid_attid()
636         if self.hash_oid_name.has_key(self.get_oid_from_attid(attid)):
637             return self.hash_oid_name[self.get_oid_from_attid(attid)]
638         else:
639             return None
640
641     def _populate_oid_attid(self):
642         """Populate the hash hash_oid_name.
643
644         This hash contains the oid of the attribute as a key and
645         its display name as a value
646         """
647         self.hash_oid_name = {}
648         res = self.search(expression="objectClass=attributeSchema",
649                            controls=["search_options:1:2"],
650                            attrs=["attributeID",
651                            "lDAPDisplayName"])
652         if len(res) > 0:
653             for e in res:
654                 strDisplay = str(e.get("lDAPDisplayName"))
655                 self.hash_oid_name[str(e.get("attributeID"))] = strDisplay
656
657     def get_attribute_replmetadata_version(self, dn, att):
658         """Get the version field trom the replPropertyMetaData for
659         the given field
660
661         :param dn: The on which we want to get the version
662         :param att: The name of the attribute
663         :return: The value of the version field in the replPropertyMetaData
664             for the given attribute. None if the attribute is not replicated
665         """
666
667         res = self.search(expression="dn=%s" % dn,
668                             scope=ldb.SCOPE_SUBTREE,
669                             controls=["search_options:1:2"],
670                             attrs=["replPropertyMetaData"])
671         if len(res) == 0:
672             return None
673
674         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
675                             str(res[0]["replPropertyMetaData"]))
676         ctr = repl.ctr
677         if len(self.hash_oid_name.keys()) == 0:
678             self._populate_oid_attid()
679         for o in ctr.array:
680             # Search for Description
681             att_oid = self.get_oid_from_attid(o.attid)
682             if self.hash_oid_name.has_key(att_oid) and\
683                att.lower() == self.hash_oid_name[att_oid].lower():
684                 return o.version
685         return None
686
687     def set_attribute_replmetadata_version(self, dn, att, value,
688             addifnotexist=False):
689         res = self.search(expression="dn=%s" % dn,
690                             scope=ldb.SCOPE_SUBTREE,
691                             controls=["search_options:1:2"],
692                             attrs=["replPropertyMetaData"])
693         if len(res) == 0:
694             return None
695
696         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
697                             str(res[0]["replPropertyMetaData"]))
698         ctr = repl.ctr
699         now = samba.unix2nttime(int(time.time()))
700         found = False
701         if len(self.hash_oid_name.keys()) == 0:
702             self._populate_oid_attid()
703         for o in ctr.array:
704             # Search for Description
705             att_oid = self.get_oid_from_attid(o.attid)
706             if self.hash_oid_name.has_key(att_oid) and\
707                att.lower() == self.hash_oid_name[att_oid].lower():
708                 found = True
709                 seq = self.sequence_number(ldb.SEQ_NEXT)
710                 o.version = value
711                 o.originating_change_time = now
712                 o.originating_invocation_id = misc.GUID(self.get_invocation_id())
713                 o.originating_usn = seq
714                 o.local_usn = seq
715
716         if not found and addifnotexist and len(ctr.array) >0:
717             o2 = drsblobs.replPropertyMetaData1()
718             o2.attid = 589914
719             att_oid = self.get_oid_from_attid(o2.attid)
720             seq = self.sequence_number(ldb.SEQ_NEXT)
721             o2.version = value
722             o2.originating_change_time = now
723             o2.originating_invocation_id = misc.GUID(self.get_invocation_id())
724             o2.originating_usn = seq
725             o2.local_usn = seq
726             found = True
727             tab = ctr.array
728             tab.append(o2)
729             ctr.count = ctr.count + 1
730             ctr.array = tab
731
732         if found :
733             replBlob = ndr_pack(repl)
734             msg = ldb.Message()
735             msg.dn = res[0].dn
736             msg["replPropertyMetaData"] = ldb.MessageElement(replBlob,
737                                                 ldb.FLAG_MOD_REPLACE,
738                                                 "replPropertyMetaData")
739             self.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
740
741     def write_prefixes_from_schema(self):
742         dsdb._dsdb_write_prefixes_from_schema_to_ldb(self)
743
744     def get_partitions_dn(self):
745         return dsdb._dsdb_get_partitions_dn(self)
746
747     def set_minPwdAge(self, value):
748         m = ldb.Message()
749         m.dn = ldb.Dn(self, self.domain_dn())
750         m["minPwdAge"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdAge")
751         self.modify(m)
752
753     def get_minPwdAge(self):
754         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdAge"])
755         if len(res) == 0:
756             return None
757         elif not "minPwdAge" in res[0]:
758             return None
759         else:
760             return res[0]["minPwdAge"][0]
761
762     def set_minPwdLength(self, value):
763         m = ldb.Message()
764         m.dn = ldb.Dn(self, self.domain_dn())
765         m["minPwdLength"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "minPwdLength")
766         self.modify(m)
767
768     def get_minPwdLength(self):
769         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["minPwdLength"])
770         if len(res) == 0:
771             return None
772         elif not "minPwdLength" in res[0]:
773             return None
774         else:
775             return res[0]["minPwdLength"][0]
776
777     def set_pwdProperties(self, value):
778         m = ldb.Message()
779         m.dn = ldb.Dn(self, self.domain_dn())
780         m["pwdProperties"] = ldb.MessageElement(value, ldb.FLAG_MOD_REPLACE, "pwdProperties")
781         self.modify(m)
782
783     def get_pwdProperties(self):
784         res = self.search(self.domain_dn(), scope=ldb.SCOPE_BASE, attrs=["pwdProperties"])
785         if len(res) == 0:
786             return None
787         elif not "pwdProperties" in res[0]:
788             return None
789         else:
790             return res[0]["pwdProperties"][0]
791
792     def set_dsheuristics(self, dsheuristics):
793         m = ldb.Message()
794         m.dn = ldb.Dn(self, "CN=Directory Service,CN=Windows NT,CN=Services,%s"
795                       % self.get_config_basedn().get_linearized())
796         if dsheuristics is not None:
797             m["dSHeuristics"] = ldb.MessageElement(dsheuristics,
798                 ldb.FLAG_MOD_REPLACE, "dSHeuristics")
799         else:
800             m["dSHeuristics"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE,
801                 "dSHeuristics")
802         self.modify(m)
803
804     def get_dsheuristics(self):
805         res = self.search("CN=Directory Service,CN=Windows NT,CN=Services,%s"
806                           % self.get_config_basedn().get_linearized(),
807                           scope=ldb.SCOPE_BASE, attrs=["dSHeuristics"])
808         if len(res) == 0:
809             dsheuristics = None
810         elif "dSHeuristics" in res[0]:
811             dsheuristics = res[0]["dSHeuristics"][0]
812         else:
813             dsheuristics = None
814
815         return dsheuristics
816
817     def create_ou(self, ou_dn, description=None, name=None, sd=None):
818         """Creates an organizationalUnit object
819         :param ou_dn: dn of the new object
820         :param description: description attribute
821         :param name: name atttribute
822         :param sd: security descriptor of the object, can be
823         an SDDL string or security.descriptor type
824         """
825         m = {"dn": ou_dn,
826              "objectClass": "organizationalUnit"}
827
828         if description:
829             m["description"] = description
830         if name:
831             m["name"] = name
832
833         if sd:
834             m["nTSecurityDescriptor"] = ndr_pack(sd)
835         self.add(m)
836
837     def sequence_number(self, seq_type):
838         """Returns the value of the sequence number according to the requested type
839         :param seq_type: type of sequence number
840          """
841         self.transaction_start()
842         try:
843             seq = super(SamDB, self).sequence_number(seq_type)
844         except:
845              self.transaction_cancel()
846              raise
847         else:
848             self.transaction_commit()
849         return seq
850
851     def get_dsServiceName(self):
852         '''get the NTDS DN from the rootDSE'''
853         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
854         return res[0]["dsServiceName"][0]
855
856     def get_serverName(self):
857         '''get the server DN from the rootDSE'''
858         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
859         return res[0]["serverName"][0]