s4-s3upgrade: Add my wins.dat and fix the parsing error
[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             if current != "":
308                 ret.append(current)
309             current = ""
310         else:
311             current += c
312     if current != "":
313         ret.append(current)
314     return ret
315
316
317 class WinsDatabase(object):
318     """Samba 3 WINS database reader."""
319     def __init__(self, file):
320         self.entries = {}
321         f = open(file, 'r')
322         assert f.readline().rstrip("\n") == "VERSION 1 0"
323         for l in f.readlines():
324             if l[0] == "#": # skip comments
325                 continue
326             entries = shellsplit(l.rstrip("\n"))
327             name = entries[0]
328             ttl = int(entries[1])
329             i = 2
330             ips = []
331             while "." in entries[i]:
332                 ips.append(entries[i])
333                 i+=1
334             nb_flags = int(entries[i][:-1], 16)
335             assert not name in self.entries, "Name %s exists twice" % name
336             self.entries[name] = (ttl, ips, nb_flags)
337         f.close()
338
339     def __getitem__(self, name):
340         return self.entries[name]
341
342     def __len__(self):
343         return len(self.entries)
344
345     def __iter__(self):
346         return iter(self.entries)
347
348     def items(self):
349         """Return the entries in this WINS database."""
350         return self.entries.items()
351
352     def close(self): # for consistency
353         pass
354
355
356 class Samba3(object):
357     """Samba 3 configuration and state data reader."""
358     def __init__(self, smbconfpath, s3_lp_ctx=None):
359         """Open the configuration and data for a Samba 3 installation.
360
361         :param smbconfpath: Path to the smb.conf file.
362         :param s3_lp_ctx: Samba3 Loadparm context
363         """
364         self.smbconfpath = smbconfpath
365         if s3_lp_ctx:
366             self.lp = s3_lp_ctx
367         else:
368             self.lp = s3param.get_context()
369             self.lp.load(smbconfpath)
370
371     def statedir_path(self, path):
372         if path[0] == "/" or path[0] == ".":
373             return path
374         return os.path.join(self.lp.get("state directory"), path)
375
376     def privatedir_path(self, path):
377         if path[0] == "/" or path[0] == ".":
378             return path
379         return os.path.join(self.lp.get("private dir"), path)
380
381     def get_conf(self):
382         return self.lp
383
384     def get_sam_db(self):
385         return passdb.PDB(self.lp.get('passdb backend'))
386
387     def get_registry(self):
388         return Registry(self.statedir_path("registry.tdb"))
389
390     def get_secrets_db(self):
391         return SecretsDatabase(self.privatedir_path("secrets.tdb"))
392
393     def get_shareinfo_db(self):
394         return ShareInfoDatabase(self.statedir_path("share_info.tdb"))
395
396     def get_idmap_db(self):
397         return IdmapDatabase(self.statedir_path("winbindd_idmap.tdb"))
398
399     def get_wins_db(self):
400         return WinsDatabase(self.statedir_path("wins.dat"))
401
402     def get_shares(self):
403         return Shares(self.get_conf(), self.get_shareinfo_db())