tdb2: don't use TDB2 versions of test tdb files.
[samba.git] / source4 / scripting / python / samba / samba3 / __init__.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17
18 """Support for reading Samba 3 data files."""
19
20 __docformat__ = "restructuredText"
21
22 REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL"
23 REGISTRY_DB_VERSION = 1
24
25 import os
26 import struct
27 import tdb
28
29 import passdb
30 import param as s3param
31
32
33 def fetch_uint32(tdb, key):
34     try:
35         data = tdb[key]
36     except KeyError:
37         return None
38     assert len(data) == 4
39     return struct.unpack("<L", data)[0]
40
41
42 def fetch_int32(tdb, key):
43     try:
44         data = tdb[key]
45     except KeyError:
46         return None
47     assert len(data) == 4
48     return struct.unpack("<l", data)[0]
49
50
51 class TdbDatabase(object):
52     """Simple Samba 3 TDB database reader."""
53     def __init__(self, file):
54         """Open a file.
55
56         :param file: Path of the file to open.
57         """
58         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
59         self._check_version()
60
61     def _check_version(self):
62         pass
63
64     def close(self):
65         """Close resources associated with this object."""
66         self.tdb.close()
67
68
69 class Registry(TdbDatabase):
70     """Simple read-only support for reading the Samba3 registry.
71
72     :note: This object uses the same syntax for registry key paths as
73         Samba 3. This particular format uses forward slashes for key path
74         separators and abbreviations for the predefined key names.
75         e.g.: HKLM/Software/Bar.
76     """
77     def __len__(self):
78         """Return the number of keys."""
79         return len(self.keys())
80
81     def keys(self):
82         """Return list with all the keys."""
83         return [k.rstrip("\x00") for k in self.tdb.iterkeys() if not k.startswith(REGISTRY_VALUE_PREFIX)]
84
85     def subkeys(self, key):
86         """Retrieve the subkeys for the specified key.
87
88         :param key: Key path.
89         :return: list with key names
90         """
91         data = self.tdb.get("%s\x00" % key)
92         if data is None:
93             return []
94         (num, ) = struct.unpack("<L", data[0:4])
95         keys = data[4:].split("\0")
96         assert keys[-1] == ""
97         keys.pop()
98         assert len(keys) == num
99         return keys
100
101     def values(self, key):
102         """Return a dictionary with the values set for a specific key.
103
104         :param key: Key to retrieve values for.
105         :return: Dictionary with value names as key, tuple with type and
106             data as value."""
107         data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
108         if data is None:
109             return {}
110         ret = {}
111         (num, ) = struct.unpack("<L", data[0:4])
112         data = data[4:]
113         for i in range(num):
114             # Value name
115             (name, data) = data.split("\0", 1)
116
117             (type, ) = struct.unpack("<L", data[0:4])
118             data = data[4:]
119             (value_len, ) = struct.unpack("<L", data[0:4])
120             data = data[4:]
121
122             ret[name] = (type, data[:value_len])
123             data = data[value_len:]
124
125         return ret
126
127
128 # High water mark keys
129 IDMAP_HWM_GROUP = "GROUP HWM\0"
130 IDMAP_HWM_USER = "USER HWM\0"
131
132 IDMAP_GROUP_PREFIX = "GID "
133 IDMAP_USER_PREFIX = "UID "
134
135 # idmap version determines auto-conversion
136 IDMAP_VERSION_V2 = 2
137
138 class IdmapDatabase(TdbDatabase):
139     """Samba 3 ID map database reader."""
140     def _check_version(self):
141         assert fetch_int32(self.tdb, "IDMAP_VERSION\0") == IDMAP_VERSION_V2
142
143     def ids(self):
144         """Retrieve a list of all ids in this database."""
145         for k in self.tdb.iterkeys():
146             if k.startswith(IDMAP_USER_PREFIX):
147                 yield k.rstrip("\0").split(" ")
148             if k.startswith(IDMAP_GROUP_PREFIX):
149                 yield k.rstrip("\0").split(" ")
150
151     def uids(self):
152         """Retrieve a list of all uids in this database."""
153         for k in self.tdb.iterkeys():
154             if k.startswith(IDMAP_USER_PREFIX):
155                 yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
156
157     def gids(self):
158         """Retrieve a list of all gids in this database."""
159         for k in self.tdb.iterkeys():
160             if k.startswith(IDMAP_GROUP_PREFIX):
161                 yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
162
163     def get_sid(self, xid, id_type):
164         """Retrive SID associated with a particular id and type.
165
166         :param xid: UID or GID to retrive SID for.
167         :param id_type: Type of id specified - 'UID' or 'GID'
168         """
169         data = self.tdb.get("%s %s\0" % (id_type, str(xid)))
170         if data is None:
171             return data
172         return data.rstrip("\0")
173
174     def get_user_sid(self, uid):
175         """Retrieve the SID associated with a particular uid.
176
177         :param uid: UID to retrieve SID for.
178         :return: A SID or None if no mapping was found.
179         """
180         data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
181         if data is None:
182             return data
183         return data.rstrip("\0")
184
185     def get_group_sid(self, gid):
186         data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
187         if data is None:
188             return data
189         return data.rstrip("\0")
190
191     def get_user_hwm(self):
192         """Obtain the user high-water mark."""
193         return fetch_uint32(self.tdb, IDMAP_HWM_USER)
194
195     def get_group_hwm(self):
196         """Obtain the group high-water mark."""
197         return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
198
199
200 class SecretsDatabase(TdbDatabase):
201     """Samba 3 Secrets database reader."""
202     def get_auth_password(self):
203         return self.tdb.get("SECRETS/AUTH_PASSWORD")
204
205     def get_auth_domain(self):
206         return self.tdb.get("SECRETS/AUTH_DOMAIN")
207
208     def get_auth_user(self):
209         return self.tdb.get("SECRETS/AUTH_USER")
210
211     def get_domain_guid(self, host):
212         return self.tdb.get("SECRETS/DOMGUID/%s" % host)
213
214     def ldap_dns(self):
215         for k in self.tdb.iterkeys():
216             if k.startswith("SECRETS/LDAP_BIND_PW/"):
217                 yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
218
219     def domains(self):
220         """Iterate over domains in this database.
221
222         :return: Iterator over the names of domains in this database.
223         """
224         for k in self.tdb.iterkeys():
225             if k.startswith("SECRETS/SID/"):
226                 yield k[len("SECRETS/SID/"):].rstrip("\0")
227
228     def get_ldap_bind_pw(self, host):
229         return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
230
231     def get_afs_keyfile(self, host):
232         return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
233
234     def get_machine_sec_channel_type(self, host):
235         return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
236
237     def get_machine_last_change_time(self, host):
238         return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
239
240     def get_machine_password(self, host):
241         return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
242
243     def get_machine_acc(self, host):
244         return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
245
246     def get_domtrust_acc(self, host):
247         return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
248
249     def trusted_domains(self):
250         for k in self.tdb.iterkeys():
251             if k.startswith("SECRETS/$DOMTRUST.ACC/"):
252                 yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
253
254     def get_random_seed(self):
255         return self.tdb.get("INFO/random_seed")
256
257     def get_sid(self, host):
258         return self.tdb.get("SECRETS/SID/%s" % host.upper())
259
260
261 SHARE_DATABASE_VERSION_V1 = 1
262 SHARE_DATABASE_VERSION_V2 = 2
263
264 class ShareInfoDatabase(TdbDatabase):
265     """Samba 3 Share Info database reader."""
266     def _check_version(self):
267         assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
268
269     def get_secdesc(self, name):
270         """Obtain the security descriptor on a particular share.
271
272         :param name: Name of the share
273         """
274         secdesc = self.tdb.get("SECDESC/%s" % name)
275         # FIXME: Run ndr_pull_security_descriptor
276         return secdesc
277
278
279 class Shares(object):
280     """Container for share objects."""
281     def __init__(self, lp, shareinfo):
282         self.lp = lp
283         self.shareinfo = shareinfo
284
285     def __len__(self):
286         """Number of shares."""
287         return len(self.lp) - 1
288
289     def __iter__(self):
290         """Iterate over the share names."""
291         return self.lp.__iter__()
292
293
294 def shellsplit(text):
295     """Very simple shell-like line splitting.
296
297     :param text: Text to split.
298     :return: List with parts of the line as strings.
299     """
300     ret = list()
301     inquotes = False
302     current = ""
303     for c in text:
304         if c == "\"":
305             inquotes = not inquotes
306         elif c in ("\t", "\n", " ") and not inquotes:
307             ret.append(current)
308             current = ""
309         else:
310             current += c
311     if current != "":
312         ret.append(current)
313     return ret
314
315
316 class WinsDatabase(object):
317     """Samba 3 WINS database reader."""
318     def __init__(self, file):
319         self.entries = {}
320         f = open(file, 'r')
321         assert f.readline().rstrip("\n") == "VERSION 1 0"
322         for l in f.readlines():
323             if l[0] == "#": # skip comments
324                 continue
325             entries = shellsplit(l.rstrip("\n"))
326             name = entries[0]
327             ttl = int(entries[1])
328             i = 2
329             ips = []
330             while "." in entries[i]:
331                 ips.append(entries[i])
332                 i+=1
333             nb_flags = int(entries[i][:-1], 16)
334             assert not name in self.entries, "Name %s exists twice" % name
335             self.entries[name] = (ttl, ips, nb_flags)
336         f.close()
337
338     def __getitem__(self, name):
339         return self.entries[name]
340
341     def __len__(self):
342         return len(self.entries)
343
344     def __iter__(self):
345         return iter(self.entries)
346
347     def items(self):
348         """Return the entries in this WINS database."""
349         return self.entries.items()
350
351     def close(self): # for consistency
352         pass
353
354
355 class Samba3(object):
356     """Samba 3 configuration and state data reader."""
357     def __init__(self, smbconfpath, s3_lp_ctx=None):
358         """Open the configuration and data for a Samba 3 installation.
359
360         :param smbconfpath: Path to the smb.conf file.
361         :param s3_lp_ctx: Samba3 Loadparm context
362         """
363         self.smbconfpath = smbconfpath
364         if s3_lp_ctx:
365             self.lp = s3_lp_ctx
366         else:
367             self.lp = s3param.get_context()
368             self.lp.load(smbconfpath)
369
370     def statedir_path(self, path):
371         if path[0] == "/" or path[0] == ".":
372             return path
373         return os.path.join(self.lp.get("state directory"), path)
374
375     def privatedir_path(self, path):
376         if path[0] == "/" or path[0] == ".":
377             return path
378         return os.path.join(self.lp.get("private dir"), path)
379
380     def get_conf(self):
381         return self.lp
382
383     def get_sam_db(self):
384         return passdb.PDB(self.lp.get('passdb backend'))
385
386     def get_registry(self):
387         return Registry(self.statedir_path("registry.tdb"))
388
389     def get_secrets_db(self):
390         return SecretsDatabase(self.privatedir_path("secrets.tdb"))
391
392     def get_shareinfo_db(self):
393         return ShareInfoDatabase(self.statedir_path("share_info.tdb"))
394
395     def get_idmap_db(self):
396         return IdmapDatabase(self.statedir_path("winbindd_idmap.tdb"))
397
398     def get_wins_db(self):
399         return WinsDatabase(self.statedir_path("wins.dat"))
400
401     def get_shares(self):
402         return Shares(self.get_conf(), self.get_shareinfo_db())