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