Merge branch 'master' of ssh://jht@git.samba.org/data/git/samba
[samba.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 glue
46
47 class Ldb(ldb.Ldb):
48     """Simple Samba-specific LDB subclass that takes care 
49     of setting up the modules dir, credentials pointers, etc.
50     
51     Please note that this is intended to be for all Samba LDB files, 
52     not necessarily the Sam database. For Sam-specific helper 
53     functions see samdb.py.
54     """
55     def __init__(self, url=None, session_info=None, credentials=None, 
56                  modules_dir=None, lp=None, options=None):
57         """Open a Samba Ldb file. 
58
59         :param url: Optional LDB URL to open
60         :param session_info: Optional session information
61         :param credentials: Optional credentials, defaults to anonymous.
62         :param modules_dir: Modules directory, if not the default.
63         :param lp: Loadparm object, optional.
64
65         This is different from a regular Ldb file in that the Samba-specific
66         modules-dir is used by default and that credentials and session_info 
67         can be passed through (required by some modules).
68         """
69         super(Ldb, self).__init__(options=options)
70
71         if modules_dir is not None:
72             self.set_modules_dir(modules_dir)
73         elif default_ldb_modules_dir is not None:
74             self.set_modules_dir(default_ldb_modules_dir)
75         elif lp is not None:
76             self.set_modules_dir(os.path.join(lp.get("modules dir"), "ldb"))
77
78         if credentials is not None:
79             self.set_credentials(credentials)
80
81         if session_info is not None:
82             self.set_session_info(session_info)
83
84         glue.ldb_register_samba_handlers(self)
85
86         if lp is not None:
87             self.set_loadparm(lp)
88
89         def msg(l,text):
90             print text
91         #self.set_debug(msg)
92
93         if url is not None:
94             self.connect(url, options=options)
95
96     def set_credentials(self, credentials):
97         glue.ldb_set_credentials(self, credentials)
98
99     def set_session_info(self, session_info):
100         glue.ldb_set_session_info(self, session_info)
101
102     def set_loadparm(self, lp_ctx):
103         glue.ldb_set_loadparm(self, lp_ctx)
104
105     def searchone(self, attribute, basedn=None, expression=None, 
106                   scope=ldb.SCOPE_BASE):
107         """Search for one attribute as a string.
108         
109         :param basedn: BaseDN for the search.
110         :param attribute: Name of the attribute
111         :param expression: Optional search expression.
112         :param scope: Search scope (defaults to base).
113         :return: Value of attribute as a string or None if it wasn't found.
114         """
115         res = self.search(basedn, scope, expression, [attribute])
116         if len(res) != 1 or res[0][attribute] is None:
117             return None
118         values = set(res[0][attribute])
119         assert len(values) == 1
120         return self.schema_format_value(attribute, values.pop())
121
122     def erase(self):
123         """Erase this ldb, removing all records."""
124         # delete the specials
125         for attr in ["@INDEXLIST", "@ATTRIBUTES", "@SUBCLASSES", "@MODULES", 
126                      "@OPTIONS", "@PARTITION", "@KLUDGEACL"]:
127             try:
128                 self.delete(attr)
129             except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
130                 # Ignore missing dn errors
131                 pass
132
133         basedn = ""
134         # and the rest
135         for msg in self.search(basedn, ldb.SCOPE_SUBTREE, 
136                 "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))", 
137                 ["distinguishedName"]):
138             try:
139                 self.delete(msg.dn)
140             except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
141                 # Ignore no such object errors
142                 pass
143
144         res = self.search(basedn, ldb.SCOPE_SUBTREE, "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))", ["distinguishedName"])
145         assert len(res) == 0
146
147     def erase_partitions(self):
148         """Erase an ldb, removing all records."""
149         res = self.search("", ldb.SCOPE_BASE, "(objectClass=*)", 
150                          ["namingContexts"])
151         assert len(res) == 1
152         if not "namingContexts" in res[0]:
153             return
154         for basedn in res[0]["namingContexts"]:
155             previous_remaining = 1
156             current_remaining = 0
157
158             k = 0
159             while ++k < 10 and (previous_remaining != current_remaining):
160                 # and the rest
161                 try:
162                     res2 = self.search(basedn, ldb.SCOPE_SUBTREE, "(|(objectclass=*)(distinguishedName=*))", ["distinguishedName"])
163                 except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
164                     # Ignore missing dn errors
165                     return
166
167                 previous_remaining = current_remaining
168                 current_remaining = len(res2)
169                 for msg in res2:
170                     try:
171                         self.delete(msg.dn)
172                     # Ignore no such object errors
173                     except ldb.LdbError, (ldb.ERR_NO_SUCH_OBJECT, _):
174                         pass
175                     # Ignore not allowed on non leaf errors
176                     except ldb.LdbError, (ldb.ERR_NOT_ALLOWED_ON_NON_LEAF, _):
177                         pass
178
179     def load_ldif_file_add(self, ldif_path):
180         """Load a LDIF file.
181
182         :param ldif_path: Path to LDIF file.
183         """
184         self.add_ldif(open(ldif_path, 'r').read())
185
186     def add_ldif(self, ldif):
187         """Add data based on a LDIF string.
188
189         :param ldif: LDIF text.
190         """
191         for changetype, msg in self.parse_ldif(ldif):
192             assert changetype == ldb.CHANGETYPE_NONE
193             self.add(msg)
194
195     def modify_ldif(self, ldif):
196         """Modify database based on a LDIF string.
197
198         :param ldif: LDIF text.
199         """
200         for changetype, msg in self.parse_ldif(ldif):
201             self.modify(msg)
202
203
204 def substitute_var(text, values):
205     """substitute strings of the form ${NAME} in str, replacing
206     with substitutions from subobj.
207     
208     :param text: Text in which to subsitute.
209     :param values: Dictionary with keys and values.
210     """
211
212     for (name, value) in values.items():
213         assert isinstance(name, str), "%r is not a string" % name
214         assert isinstance(value, str), "Value %r for %s is not a string" % (value, name)
215         text = text.replace("${%s}" % name, value)
216
217     return text
218
219
220 def check_all_substituted(text):
221     """Make sure that all substitution variables in a string have been replaced.
222     If not, raise an exception.
223     
224     :param text: The text to search for substitution variables
225     """
226     if not "${" in text:
227         return
228     
229     var_start = text.find("${")
230     var_end = text.find("}", var_start)
231     
232     raise Exception("Not all variables substituted: %s" % text[var_start:var_end+1])
233
234
235 def valid_netbios_name(name):
236     """Check whether a name is valid as a NetBIOS name. """
237     # See crh's book (1.4.1.1)
238     if len(name) > 15:
239         return False
240     for x in name:
241         if not x.isalnum() and not x in " !#$%&'()-.@^_{}~":
242             return False
243     return True
244
245 version = glue.version
246
247 DS_BEHAVIOR_WIN2000 = glue.DS_BEHAVIOR_WIN2000
248 DS_BEHAVIOR_WIN2003_INTERIM = glue.DS_BEHAVIOR_WIN2003_INTERIM
249 DS_BEHAVIOR_WIN2003 = glue.DS_BEHAVIOR_WIN2003
250 DS_BEHAVIOR_WIN2008 = glue.DS_BEHAVIOR_WIN2008