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