s4-python: Move dsdb_convert_schema_to_openldap to dsdb.
[nivanova/samba-autobuild/.git] / source4 / scripting / python / samba / __init__.py
1 #!/usr/bin/python
2
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
5 #
6 # Based on the original in EJS:
7 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
8 #
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.
13 #
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.
18 #
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/>.
21 #
22
23 """Samba 4."""
24
25 __docformat__ = "restructuredText"
26
27 import os
28
29 def _in_source_tree():
30     """Check whether the script is being run from the source dir. """
31     return os.path.exists("%s/../../../selftest/skip" % os.path.dirname(__file__))
32
33
34 # When running, in-tree, make sure bin/python is in the PYTHONPATH
35 if _in_source_tree():
36     import sys
37     srcdir = "%s/../../.." % os.path.dirname(__file__)
38     sys.path.append("%s/bin/python" % srcdir)
39     default_ldb_modules_dir = "%s/bin/modules/ldb" % srcdir
40 else:
41     default_ldb_modules_dir = None
42
43
44 import ldb
45 import dsdb
46 import glue
47
48
49
50 class Ldb(ldb.Ldb):
51     """Simple Samba-specific LDB subclass that takes care
52     of setting up the modules dir, credentials pointers, etc.
53
54     Please note that this is intended to be for all Samba LDB files,
55     not necessarily the Sam database. For Sam-specific helper
56     functions see samdb.py.
57     """
58     def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
59                  credentials=None, flags=0, options=None):
60         """Opens a Samba Ldb file.
61
62         :param url: Optional LDB URL to open
63         :param lp: Optional loadparm object
64         :param modules_dir: Optional modules directory
65         :param session_info: Optional session information
66         :param credentials: Optional credentials, defaults to anonymous.
67         :param flags: Optional LDB flags
68         :param options: Additional options (optional)
69
70         This is different from a regular Ldb file in that the Samba-specific
71         modules-dir is used by default and that credentials and session_info
72         can be passed through (required by some modules).
73         """
74
75         if modules_dir is not None:
76             self.set_modules_dir(modules_dir)
77         elif default_ldb_modules_dir is not None:
78             self.set_modules_dir(default_ldb_modules_dir)
79         elif lp is not None:
80             self.set_modules_dir(os.path.join(lp.get("modules dir"), "ldb"))
81
82         if session_info is not None:
83             self.set_session_info(session_info)
84
85         if credentials is not None:
86             self.set_credentials(credentials)
87
88         if lp is not None:
89             self.set_loadparm(lp)
90
91         # This must be done before we load the schema, as these handlers for
92         # objectSid and objectGUID etc must take precedence over the 'binary
93         # attribute' declaration in the schema
94         glue.ldb_register_samba_handlers(self)
95
96         # TODO set debug
97         def msg(l,text):
98             print text
99         #self.set_debug(msg)
100
101         glue.ldb_set_utf8_casefold(self)
102
103         # Allow admins to force non-sync ldb for all databases
104         if lp is not None:
105             nosync_p = lp.get("nosync", "ldb")
106             if nosync_p is not None and nosync_p == True:
107                 flags |= FLG_NOSYNC
108
109         self.set_create_perms()
110
111         if url is not None:
112             self.connect(url, flags, options)
113
114     def set_session_info(self, session_info):
115         glue.ldb_set_session_info(self, session_info)
116
117     def set_credentials(self, credentials):
118         glue.ldb_set_credentials(self, credentials)
119
120     def set_loadparm(self, lp_ctx):
121         glue.ldb_set_loadparm(self, lp_ctx)
122
123     def set_create_perms(self, perms=0600):
124         # we usually want Samba databases to be private. If we later find we
125         # need one public, we will have to change this here
126         super(Ldb, self).set_create_perms(perms)
127
128     def searchone(self, attribute, basedn=None, expression=None,
129                   scope=ldb.SCOPE_BASE):
130         """Search for one attribute as a string.
131
132         :param basedn: BaseDN for the search.
133         :param attribute: Name of the attribute
134         :param expression: Optional search expression.
135         :param scope: Search scope (defaults to base).
136         :return: Value of attribute as a string or None if it wasn't found.
137         """
138         res = self.search(basedn, scope, expression, [attribute])
139         if len(res) != 1 or res[0][attribute] is None:
140             return None
141         values = set(res[0][attribute])
142         assert len(values) == 1
143         return self.schema_format_value(attribute, values.pop())
144
145     def erase_users_computers(self, dn):
146         """Erases user and computer objects from our AD. This is needed since the 'samldb' module denies the deletion of primary groups. Therefore all groups shouldn't be primary somewhere anymore."""
147
148         try:
149             res = self.search(base=dn, scope=ldb.SCOPE_SUBTREE, attrs=[],
150                       expression="(|(objectclass=user)(objectclass=computer))")
151         except ldb.LdbError, (errno, _):
152             if errno == ldb.ERR_NO_SUCH_OBJECT:
153                 # Ignore no such object errors
154                 return
155             else:
156                 raise
157
158         try:
159             for msg in res:
160                 self.delete(msg.dn)
161         except ldb.LdbError, (errno, _):
162             if errno != ldb.ERR_NO_SUCH_OBJECT:
163                 # Ignore no such object errors
164                 raise
165
166     def erase_except_schema_controlled(self):
167         """Erase this ldb, removing all records, except those that are controlled by Samba4's schema."""
168
169         basedn = ""
170
171         # Try to delete user/computer accounts to allow deletion of groups
172         self.erase_users_computers(basedn)
173
174         # Delete the 'visible' records, and the invisble 'deleted' records (if this DB supports it)
175         for msg in self.search(basedn, ldb.SCOPE_SUBTREE,
176                                "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))",
177                                [], controls=["show_deleted:0"]):
178             try:
179                 self.delete(msg.dn)
180             except ldb.LdbError, (errno, _):
181                 if errno != ldb.ERR_NO_SUCH_OBJECT:
182                     # Ignore no such object errors
183                     raise
184
185         res = self.search(basedn, ldb.SCOPE_SUBTREE,
186                           "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))",
187                           [], controls=["show_deleted:0"])
188         assert len(res) == 0
189
190         # delete the specials
191         for attr in ["@SUBCLASSES", "@MODULES",
192                      "@OPTIONS", "@PARTITION", "@KLUDGEACL"]:
193             try:
194                 self.delete(attr)
195             except ldb.LdbError, (errno, _):
196                 if errno != ldb.ERR_NO_SUCH_OBJECT:
197                     # Ignore missing dn errors
198                     raise
199
200     def erase(self):
201         """Erase this ldb, removing all records."""
202
203         self.erase_except_schema_controlled()
204
205         # delete the specials
206         for attr in ["@INDEXLIST", "@ATTRIBUTES"]:
207             try:
208                 self.delete(attr)
209             except ldb.LdbError, (errno, _):
210                 if errno != ldb.ERR_NO_SUCH_OBJECT:
211                     # Ignore missing dn errors
212                     raise
213
214     def erase_partitions(self):
215         """Erase an ldb, removing all records."""
216
217         def erase_recursive(self, dn):
218             try:
219                 res = self.search(base=dn, scope=ldb.SCOPE_ONELEVEL, attrs=[],
220                                   controls=["show_deleted:0"])
221             except ldb.LdbError, (errno, _):
222                 if errno == ldb.ERR_NO_SUCH_OBJECT:
223                     # Ignore no such object errors
224                     return
225
226             for msg in res:
227                 erase_recursive(self, msg.dn)
228
229             try:
230                 self.delete(dn)
231             except ldb.LdbError, (errno, _):
232                 if errno != ldb.ERR_NO_SUCH_OBJECT:
233                     # Ignore no such object errors
234                     raise
235
236         res = self.search("", ldb.SCOPE_BASE, "(objectClass=*)",
237                          ["namingContexts"])
238         assert len(res) == 1
239         if not "namingContexts" in res[0]:
240             return
241         for basedn in res[0]["namingContexts"]:
242             # Try to delete user/computer accounts to allow deletion of groups
243             self.erase_users_computers(basedn)
244             # Try and erase from the bottom-up in the tree
245             erase_recursive(self, basedn)
246
247     def load_ldif_file_add(self, ldif_path):
248         """Load a LDIF file.
249
250         :param ldif_path: Path to LDIF file.
251         """
252         self.add_ldif(open(ldif_path, 'r').read())
253
254     def add_ldif(self, ldif, controls=None):
255         """Add data based on a LDIF string.
256
257         :param ldif: LDIF text.
258         """
259         for changetype, msg in self.parse_ldif(ldif):
260             assert changetype == ldb.CHANGETYPE_NONE
261             self.add(msg,controls)
262
263     def modify_ldif(self, ldif, controls=None):
264         """Modify database based on a LDIF string.
265
266         :param ldif: LDIF text.
267         """
268         for changetype, msg in self.parse_ldif(ldif):
269             if (changetype == ldb.CHANGETYPE_ADD):
270                 self.add(msg, controls)
271             else:
272                 self.modify(msg, controls)
273
274     def set_domain_sid(self, sid):
275         """Change the domain SID used by this LDB.
276
277         :param sid: The new domain sid to use.
278         """
279         glue.samdb_set_domain_sid(self, sid)
280
281     def domain_sid(self):
282         """Read the domain SID used by this LDB.
283
284         """
285         glue.samdb_get_domain_sid(self)
286
287     def set_schema_from_ldif(self, pf, df):
288         glue.dsdb_set_schema_from_ldif(self, pf, df)
289
290     def set_schema_from_ldb(self, ldb):
291         glue.dsdb_set_schema_from_ldb(self, ldb)
292
293     def write_prefixes_from_schema(self):
294         glue.dsdb_write_prefixes_from_schema_to_ldb(self)
295
296     def convert_schema_to_openldap(self, target, mapping):
297         return dsdb.dsdb_convert_schema_to_openldap(self, target, mapping)
298
299     def set_invocation_id(self, invocation_id):
300         """Set the invocation id for this SamDB handle.
301
302         :param invocation_id: GUID of the invocation id.
303         """
304         glue.dsdb_set_ntds_invocation_id(self, invocation_id)
305
306     def get_invocation_id(self):
307         "Get the invocation_id id"
308         return glue.samdb_ntds_invocation_id(self)
309
310     def get_ntds_GUID(self):
311         "Get the NTDS objectGUID"
312         return glue.samdb_ntds_objectGUID(self)
313
314     def server_site_name(self):
315         "Get the server site name"
316         return dsdb.samdb_server_site_name(self)
317
318     def set_opaque_integer(self, name, value):
319         """Set an integer as an opaque (a flag or other value) value on the database
320
321         :param name: The name for the opaque value
322         :param value: The integer value
323         """
324         dsdb.dsdb_set_opaque_integer(self, name, value)
325
326
327 def substitute_var(text, values):
328     """substitute strings of the form ${NAME} in str, replacing
329     with substitutions from subobj.
330
331     :param text: Text in which to subsitute.
332     :param values: Dictionary with keys and values.
333     """
334
335     for (name, value) in values.items():
336         assert isinstance(name, str), "%r is not a string" % name
337         assert isinstance(value, str), "Value %r for %s is not a string" % (value, name)
338         text = text.replace("${%s}" % name, value)
339
340     return text
341
342
343 def check_all_substituted(text):
344     """Make sure that all substitution variables in a string have been replaced.
345     If not, raise an exception.
346
347     :param text: The text to search for substitution variables
348     """
349     if not "${" in text:
350         return
351
352     var_start = text.find("${")
353     var_end = text.find("}", var_start)
354
355     raise Exception("Not all variables substituted: %s" % text[var_start:var_end+1])
356
357
358 def read_and_sub_file(file, subst_vars):
359     """Read a file and sub in variables found in it
360
361     :param file: File to be read (typically from setup directory)
362      param subst_vars: Optional variables to subsitute in the file.
363     """
364     data = open(file, 'r').read()
365     if subst_vars is not None:
366         data = substitute_var(data, subst_vars)
367         check_all_substituted(data)
368     return data
369
370
371 def setup_file(template, fname, subst_vars=None):
372     """Setup a file in the private dir.
373
374     :param template: Path of the template file.
375     :param fname: Path of the file to create.
376     :param subst_vars: Substitution variables.
377     """
378     f = fname
379
380     if os.path.exists(f):
381         os.unlink(f)
382
383     data = read_and_sub_file(template, subst_vars)
384     open(f, 'w').write(data)
385
386
387 def valid_netbios_name(name):
388     """Check whether a name is valid as a NetBIOS name. """
389     # See crh's book (1.4.1.1)
390     if len(name) > 15:
391         return False
392     for x in name:
393         if not x.isalnum() and not x in " !#$%&'()-.@^_{}~":
394             return False
395     return True
396
397
398 def ensure_external_module(modulename, location):
399     """Add a location to sys.path if an external dependency can't be found.
400
401     :param modulename: Module name to import
402     :param location: Location to add to sys.path (can be relative to 
403         ${srcdir}/lib
404     """
405     try:
406         __import__(modulename)
407     except ImportError:
408         import sys
409         if _in_source_tree():
410             sys.path.insert(0, 
411                 os.path.join(os.path.dirname(__file__),
412                              "../../../../lib", location))
413             __import__(modulename)
414         else:
415             sys.modules[modulename] = __import__(
416                 "samba.external.%s" % modulename, fromlist=["samba.external"])
417
418 version = glue.version
419 interface_ips = glue.interface_ips
420 set_debug_level = glue.set_debug_level
421 unix2nttime = glue.unix2nttime
422 generate_random_password = glue.generate_random_password