8df017054974cf5158795a91bb2aef8223323cf1
[samba.git] / 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(db, key):
34     try:
35         data = db[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(db, key):
43     try:
44         data = db[key]
45     except KeyError:
46         return None
47     assert len(data) == 4
48     return struct.unpack("<l", data)[0]
49
50
51 class DbDatabase(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 .tdb or .ntdb.
57         """
58         self.db = tdb.Tdb(file + ".tdb", 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.db.close()
67
68
69 class Registry(DbDatabase):
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.db.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.db.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.db.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(DbDatabase):
139     """Samba 3 ID map database reader."""
140
141     def _check_version(self):
142         assert fetch_int32(self.db, "IDMAP_VERSION\0") == IDMAP_VERSION_V2
143
144     def ids(self):
145         """Retrieve a list of all ids in this database."""
146         for k in self.db.iterkeys():
147             if k.startswith(IDMAP_USER_PREFIX):
148                 yield k.rstrip("\0").split(" ")
149             if k.startswith(IDMAP_GROUP_PREFIX):
150                 yield k.rstrip("\0").split(" ")
151
152     def uids(self):
153         """Retrieve a list of all uids in this database."""
154         for k in self.db.iterkeys():
155             if k.startswith(IDMAP_USER_PREFIX):
156                 yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
157
158     def gids(self):
159         """Retrieve a list of all gids in this database."""
160         for k in self.db.iterkeys():
161             if k.startswith(IDMAP_GROUP_PREFIX):
162                 yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
163
164     def get_sid(self, xid, id_type):
165         """Retrive SID associated with a particular id and type.
166
167         :param xid: UID or GID to retrive SID for.
168         :param id_type: Type of id specified - 'UID' or 'GID'
169         """
170         data = self.db.get("%s %s\0" % (id_type, str(xid)))
171         if data is None:
172             return data
173         return data.rstrip("\0")
174
175     def get_user_sid(self, uid):
176         """Retrieve the SID associated with a particular uid.
177
178         :param uid: UID to retrieve SID for.
179         :return: A SID or None if no mapping was found.
180         """
181         data = self.db.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
182         if data is None:
183             return data
184         return data.rstrip("\0")
185
186     def get_group_sid(self, gid):
187         data = self.db.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
188         if data is None:
189             return data
190         return data.rstrip("\0")
191
192     def get_user_hwm(self):
193         """Obtain the user high-water mark."""
194         return fetch_uint32(self.db, IDMAP_HWM_USER)
195
196     def get_group_hwm(self):
197         """Obtain the group high-water mark."""
198         return fetch_uint32(self.db, IDMAP_HWM_GROUP)
199
200
201 class SecretsDatabase(DbDatabase):
202     """Samba 3 Secrets database reader."""
203
204     def get_auth_password(self):
205         return self.db.get("SECRETS/AUTH_PASSWORD")
206
207     def get_auth_domain(self):
208         return self.db.get("SECRETS/AUTH_DOMAIN")
209
210     def get_auth_user(self):
211         return self.db.get("SECRETS/AUTH_USER")
212
213     def get_domain_guid(self, host):
214         return self.db.get("SECRETS/DOMGUID/%s" % host)
215
216     def ldap_dns(self):
217         for k in self.db.iterkeys():
218             if k.startswith("SECRETS/LDAP_BIND_PW/"):
219                 yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
220
221     def domains(self):
222         """Iterate over domains in this database.
223
224         :return: Iterator over the names of domains in this database.
225         """
226         for k in self.db.iterkeys():
227             if k.startswith("SECRETS/SID/"):
228                 yield k[len("SECRETS/SID/"):].rstrip("\0")
229
230     def get_ldap_bind_pw(self, host):
231         return self.db.get("SECRETS/LDAP_BIND_PW/%s" % host)
232
233     def get_afs_keyfile(self, host):
234         return self.db.get("SECRETS/AFS_KEYFILE/%s" % host)
235
236     def get_machine_sec_channel_type(self, host):
237         return fetch_uint32(self.db, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
238
239     def get_machine_last_change_time(self, host):
240         return fetch_uint32(self.db, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
241
242     def get_machine_password(self, host):
243         return self.db.get("SECRETS/MACHINE_PASSWORD/%s" % host)
244
245     def get_machine_acc(self, host):
246         return self.db.get("SECRETS/$MACHINE.ACC/%s" % host)
247
248     def get_domtrust_acc(self, host):
249         return self.db.get("SECRETS/$DOMTRUST.ACC/%s" % host)
250
251     def trusted_domains(self):
252         for k in self.db.iterkeys():
253             if k.startswith("SECRETS/$DOMTRUST.ACC/"):
254                 yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
255
256     def get_random_seed(self):
257         return self.db.get("INFO/random_seed")
258
259     def get_sid(self, host):
260         return self.db.get("SECRETS/SID/%s" % host.upper())
261
262
263 SHARE_DATABASE_VERSION_V1 = 1
264 SHARE_DATABASE_VERSION_V2 = 2
265
266
267 class ShareInfoDatabase(DbDatabase):
268     """Samba 3 Share Info database reader."""
269
270     def _check_version(self):
271         assert fetch_int32(self.db, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
272
273     def get_secdesc(self, name):
274         """Obtain the security descriptor on a particular share.
275
276         :param name: Name of the share
277         """
278         secdesc = self.db.get("SECDESC/%s" % name)
279         # FIXME: Run ndr_pull_security_descriptor
280         return secdesc
281
282
283 class Shares(object):
284     """Container for share objects."""
285     def __init__(self, lp, shareinfo):
286         self.lp = lp
287         self.shareinfo = shareinfo
288
289     def __len__(self):
290         """Number of shares."""
291         return len(self.lp) - 1
292
293     def __iter__(self):
294         """Iterate over the share names."""
295         return self.lp.__iter__()
296
297
298 def shellsplit(text):
299     """Very simple shell-like line splitting.
300
301     :param text: Text to split.
302     :return: List with parts of the line as strings.
303     """
304     ret = list()
305     inquotes = False
306     current = ""
307     for c in text:
308         if c == "\"":
309             inquotes = not inquotes
310         elif c in ("\t", "\n", " ") and not inquotes:
311             if current != "":
312                 ret.append(current)
313             current = ""
314         else:
315             current += c
316     if current != "":
317         ret.append(current)
318     return ret
319
320
321 class WinsDatabase(object):
322     """Samba 3 WINS database reader."""
323     def __init__(self, file):
324         self.entries = {}
325         f = open(file, 'r')
326         assert f.readline().rstrip("\n") == "VERSION 1 0"
327         for l in f.readlines():
328             if l[0] == "#": # skip comments
329                 continue
330             entries = shellsplit(l.rstrip("\n"))
331             name = entries[0]
332             ttl = int(entries[1])
333             i = 2
334             ips = []
335             while "." in entries[i]:
336                 ips.append(entries[i])
337                 i+=1
338             nb_flags = int(entries[i][:-1], 16)
339             assert not name in self.entries, "Name %s exists twice" % name
340             self.entries[name] = (ttl, ips, nb_flags)
341         f.close()
342
343     def __getitem__(self, name):
344         return self.entries[name]
345
346     def __len__(self):
347         return len(self.entries)
348
349     def __iter__(self):
350         return iter(self.entries)
351
352     def items(self):
353         """Return the entries in this WINS database."""
354         return self.entries.items()
355
356     def close(self): # for consistency
357         pass
358
359
360 class Samba3(object):
361     """Samba 3 configuration and state data reader."""
362
363     def __init__(self, smbconfpath, s3_lp_ctx=None):
364         """Open the configuration and data for a Samba 3 installation.
365
366         :param smbconfpath: Path to the smb.conf file.
367         :param s3_lp_ctx: Samba3 Loadparm context
368         """
369         self.smbconfpath = smbconfpath
370         if s3_lp_ctx:
371             self.lp = s3_lp_ctx
372         else:
373             self.lp = s3param.get_context()
374             self.lp.load(smbconfpath)
375
376     def statedir_path(self, path):
377         if path[0] == "/" or path[0] == ".":
378             return path
379         return os.path.join(self.lp.get("state 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"))
394
395     def get_secrets_db(self):
396         return SecretsDatabase(self.privatedir_path("secrets"))
397
398     def get_shareinfo_db(self):
399         return ShareInfoDatabase(self.statedir_path("share_info"))
400
401     def get_idmap_db(self):
402         return IdmapDatabase(self.statedir_path("winbindd_idmap"))
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())