r26592: Finish fixing the samba3dump script.
[metze/old/samba4-sync/samba4-sync.git/.git] / source / scripting / python / samba / samba3.py
1 #!/usr/bin/python
2
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
5 #   
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #   
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #   
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 """Support for reading Samba 3 data files."""
21
22 REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL"
23 REGISTRY_DB_VERSION = 1
24
25 import os
26 import tdb
27
28 class Registry:
29     """Simple read-only support for reading the Samba3 registry."""
30     def __init__(self, file):
31         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
32
33     def close(self):
34         self.tdb.close()
35
36     def __len__(self):
37         """Return the number of keys."""
38         return len(self.keys())
39
40     def keys(self):
41         """Return list with all the keys."""
42         return [k.rstrip("\x00") for k in self.tdb.keys() if not k.startswith(REGISTRY_VALUE_PREFIX)]
43
44     def subkeys(self, key):
45         data = self.tdb.get("%s\x00" % key)
46         if data is None:
47             return []
48         # FIXME: Parse data
49         return []
50
51     def values(self, key):
52         """Return a dictionary with the values set for a specific key."""
53         data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
54         if data is None:
55             return {}
56         # FIXME: Parse data
57         return {}
58
59
60 class PolicyDatabase:
61     def __init__(self, file):
62         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
63         self.min_password_length = self.tdb.fetch_uint32("min password length\x00")
64         self.password_history = self.tdb.fetch_uint32("password history\x00")
65         self.user_must_logon_to_change_password = self.tdb.fetch_uint32("user must logon to change pasword\x00")
66         self.maximum_password_age = self.tdb.fetch_uint32("maximum password age\x00")
67         self.minimum_password_age = self.tdb.fetch_uint32("minimum password age\x00")
68         self.lockout_duration = self.tdb.fetch_uint32("lockout duration\x00")
69         self.reset_count_minutes = self.tdb.fetch_uint32("reset count minutes\x00")
70         self.bad_lockout_minutes = self.tdb.fetch_uint32("bad lockout minutes\x00")
71         self.disconnect_time = self.tdb.fetch_int32("disconnect time\x00")
72         self.refuse_machine_password_change = self.tdb.fetch_uint32("refuse machine password change\x00")
73
74         # FIXME: Read privileges as well
75
76     def close(self):
77         self.tdb.close()
78
79
80 GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
81 GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
82
83 GROUP_PREFIX = "UNIXGROUP/"
84
85 # Alias memberships are stored reverse, as memberships. The performance
86 # critical operation is to determine the aliases a SID is member of, not
87 # listing alias members. So we store a list of alias SIDs a SID is member of
88 # hanging of the member as key.
89 MEMBEROF_PREFIX = "MEMBEROF/"
90
91 class GroupMappingDatabase:
92     def __init__(self, file): 
93         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
94         assert self.tdb.fetch_int32("INFO/version\x00") in (GROUPDB_DATABASE_VERSION_V1, GROUPDB_DATABASE_VERSION_V2)
95
96     def groupsids(self):
97         for k in self.tdb.keys():
98             if k.startswith(GROUP_PREFIX):
99                 yield k[len(GROUP_PREFIX):].rstrip("\0")
100
101     def aliases(self):
102         for k in self.tdb.keys():
103             if k.startswith(MEMBEROF_PREFIX):
104                 yield k[len(MEMBEROF_PREFIX):].rstrip("\0")
105
106     def close(self):
107         self.tdb.close()
108
109
110 # High water mark keys
111 IDMAP_HWM_GROUP = "GROUP HWM\0"
112 IDMAP_HWM_USER = "USER HWM\0"
113
114 IDMAP_GROUP_PREFIX = "GID "
115 IDMAP_USER_PREFIX = "UID "
116
117 # idmap version determines auto-conversion
118 IDMAP_VERSION_V2 = 2
119
120 class IdmapDatabase:
121     def __init__(self, file):
122         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
123         assert self.tdb.fetch_int32("IDMAP_VERSION\0") == IDMAP_VERSION_V2
124
125     def uids(self):
126         for k in self.tdb.keys():
127             if k.startswith(IDMAP_USER_PREFIX):
128                 yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
129
130     def gids(self):
131         for k in self.tdb.keys():
132             if k.startswith(IDMAP_GROUP_PREFIX):
133                 yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
134
135     def get_user_sid(self, uid):
136         data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
137         if data is None:
138             return data
139         return data.rstrip("\0")
140
141     def get_group_sid(self, gid):
142         data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
143         if data is None:
144             return data
145         return data.rstrip("\0")
146
147     def get_user_hwm(self):
148         return self.tdb.fetch_uint32(IDMAP_HWM_USER)
149
150     def get_group_hwm(self):
151         return self.tdb.fetch_uint32(IDMAP_HWM_GROUP)
152
153     def close(self):
154         self.tdb.close()
155
156
157 class SecretsDatabase:
158     def __init__(self, file):
159         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
160
161     def get_auth_password(self):
162         return self.tdb.get("SECRETS/AUTH_PASSWORD")
163
164     def get_auth_domain(self):
165         return self.tdb.get("SECRETS/AUTH_DOMAIN")
166
167     def get_auth_user(self):
168         return self.tdb.get("SECRETS/AUTH_USER")
169
170     def get_dom_guid(self, host):
171         return self.tdb.get("SECRETS/DOMGUID/%s" % host)
172
173     def ldap_dns(self):
174         for k in self.tdb.keys():
175             if k.startswith("SECRETS/LDAP_BIND_PW/"):
176                 yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
177
178     def domains(self):
179         for k in self.tdb.keys():
180             if k.startswith("SECRETS/SID/"):
181                 yield k[len("SECRETS/SID/"):].rstrip("\0")
182
183     def get_ldap_bind_pw(self, host):
184         return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
185     
186     def get_afs_keyfile(self, host):
187         return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
188
189     def get_machine_sec_channel_type(self, host):
190         return self.tdb.fetch_uint32("SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
191
192     def get_machine_last_change_time(self, host):
193         return self.tdb.fetch_uint32("SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
194             
195     def get_machine_password(self, host):
196         return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
197
198     def get_machine_acc(self, host):
199         return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
200
201     def get_domtrust_acc(self, host):
202         return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
203
204     def trusted_domains(self):
205         for k in self.tdb.keys():
206             if k.startswith("SECRETS/$DOMTRUST.ACC/"):
207                 yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
208
209     def get_random_seed(self):
210         return self.tdb.get("INFO/random_seed")
211
212     def get_sid(self, host):
213         return self.tdb.get("SECRETS/SID/%s" % host.upper())
214
215     def close(self):
216         self.tdb.close()
217
218
219 SHARE_DATABASE_VERSION_V1 = 1
220 SHARE_DATABASE_VERSION_V2 = 2
221
222 class ShareInfoDatabase:
223     def __init__(self, file):
224         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
225         assert self.tdb.fetch_int32("INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
226
227     def get_secdesc(self, name):
228         secdesc = self.tdb.get("SECDESC/%s" % name)
229         # FIXME: Run ndr_pull_security_descriptor
230         return secdesc
231
232     def close(self):
233         self.tdb.close()
234
235
236 class Shares:
237     def __init__(self, lp, shareinfo):
238         self.lp = lp
239         self.shareinfo = shareinfo
240
241     def __len__(self):
242         return len(self.lp) - 1
243
244     def __iter__(self):
245         return self.lp.__iter__()
246
247
248 ACB_DISABLED = 0x00000001
249 ACB_HOMDIRREQ = 0x00000002
250 ACB_PWNOTREQ = 0x00000004
251 ACB_TEMPDUP = 0x00000008
252 ACB_NORMAL = 0x00000010
253 ACB_MNS = 0x00000020
254 ACB_DOMTRUST = 0x00000040
255 ACB_WSTRUST = 0x00000080
256 ACB_SVRTRUST = 0x00000100
257 ACB_PWNOEXP = 0x00000200
258 ACB_AUTOLOCK = 0x00000400
259 ACB_ENC_TXT_PWD_ALLOWED = 0x00000800
260 ACB_SMARTCARD_REQUIRED = 0x00001000
261 ACB_TRUSTED_FOR_DELEGATION = 0x00002000
262 ACB_NOT_DELEGATED = 0x00004000
263 ACB_USE_DES_KEY_ONLY = 0x00008000
264 ACB_DONT_REQUIRE_PREAUTH = 0x00010000
265 ACB_PW_EXPIRED = 0x00020000
266 ACB_NO_AUTH_DATA_REQD = 0x00080000
267
268 acb_info_mapping = {
269         'N': ACB_PWNOTREQ,  # 'N'o password. 
270         'D': ACB_DISABLED,  # 'D'isabled.
271                 'H': ACB_HOMDIRREQ, # 'H'omedir required.
272                 'T': ACB_TEMPDUP,   # 'T'emp account.
273                 'U': ACB_NORMAL,    # 'U'ser account (normal).
274                 'M': ACB_MNS,       # 'M'NS logon user account. What is this ?
275                 'W': ACB_WSTRUST,   # 'W'orkstation account.
276                 'S': ACB_SVRTRUST,  # 'S'erver account. 
277                 'L': ACB_AUTOLOCK,  # 'L'ocked account.
278                 'X': ACB_PWNOEXP,   # No 'X'piry on password
279                 'I': ACB_DOMTRUST,  # 'I'nterdomain trust account.
280         ' ': 0
281         }
282
283 def decode_acb(text):
284     assert not "[" in text and not "]" in text
285     ret = 0
286     for x in text:
287         ret |= acb_info_mapping[x]
288     return ret
289
290
291 class SmbpasswdFile:
292     def __init__(self, file):
293         self.users = {}
294         f = open(file, 'r')
295         for l in f.readlines():
296             if len(l) == 0 or l[0] == "#":
297                 continue # Skip comments and blank lines
298             parts = l.split(":")
299             username = parts[0]
300             uid = int(parts[1])
301             acct_ctrl = 0
302             last_change_time = None
303             if parts[2] == "NO PASSWORD":
304                 acct_ctrl |= ACB_PWNOTREQ
305                 lm_password = None
306             elif parts[2][0] in ("*", "X"):
307                 # No password set
308                 lm_password = None
309             else:
310                 lm_password = parts[2]
311
312             if parts[3][0] in ("*", "X"):
313                 # No password set
314                 nt_password = None
315             else:
316                 nt_password = parts[3]
317
318             if parts[4][0] == '[':
319                 assert "]" in parts[4]
320                 acct_ctrl |= decode_acb(parts[4][1:-1])
321                 if parts[5].startswith("LCT-"):
322                     last_change_time = int(parts[5][len("LCT-"):], 16)
323             else: # old style file
324                 if username[-1] == "$":
325                     acct_ctrl &= ~ACB_NORMAL
326                     acct_ctrl |= ACB_WSTRUST
327
328             self.users[username] = (uid, lm_password, nt_password, acct_ctrl, last_change_time)
329
330         f.close()
331
332     def __len__(self):
333         return len(self.users)
334
335     def __getitem__(self, name):
336         return self.users[name]
337
338     def __iter__(self):
339         return iter(self.users)
340
341     def close(self): # For consistency
342         pass
343
344
345 TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
346 TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
347 TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
348 TDBSAM_USER_PREFIX = "USER_"
349
350
351 class TdbSam:
352     def __init__(self, file):
353         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
354         self.version = self.tdb.fetch_uint32("INFO/version") or 0
355         assert self.version in (0, 1, 2)
356
357     def usernames(self):
358         for k in self.tdb.keys():
359             if k.startswith(TDBSAM_USER_PREFIX):
360                 yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0")
361
362     __iter__ = usernames
363
364     def close(self):
365         self.tdb.close()
366
367
368 def shellsplit(text):
369     """Very simple shell-like line splitting.
370     
371     :param text: Text to split.
372     :return: List with parts of the line as strings.
373     """
374     ret = list()
375     inquotes = False
376     current = ""
377     for c in text:
378         if c == "\"":
379             inquotes = not inquotes
380         elif c in ("\t", "\n", " ") and not inquotes:
381             ret.append(current)
382             current = ""
383         else:
384             current += c
385     if current != "":
386         ret.append(current)
387     return ret
388
389
390 class WinsDatabase:
391     def __init__(self, file):
392         self.entries = {}
393         f = open(file, 'r')
394         assert f.readline().rstrip("\n") == "VERSION 1 0"
395         for l in f.readlines():
396             if l[0] == "#": # skip comments
397                 continue
398             entries = shellsplit(l.rstrip("\n"))
399             name = entries[0]
400             ttl = int(entries[1])
401             i = 2
402             ips = []
403             while "." in entries[i]:
404                 ips.append(entries[i])
405                 i+=1
406             nb_flags = entries[i]
407             assert not name in self.entries, "Name %s exists twice" % name
408             self.entries[name] = (ttl, ips, nb_flags)
409         f.close()
410
411     def __getitem__(self, name):
412         return self.entries[name]
413
414     def __len__(self):
415         return len(self.entries)
416
417     def __iter__(self):
418         return iter(self.entries)
419
420     def close(self): # for consistency
421         pass
422
423 class Samba3:
424     def __init__(self, libdir, smbconfpath):
425         self.smbconfpath = smbconfpath
426         self.libdir = libdir
427         import param
428         self.lp = param.ParamFile()
429         self.lp.read(self.smbconfpath)
430
431     def libdir_path(self, path):
432         if path[0] == "/" or path[0] == ".":
433             return path
434         return os.path.join(self.libdir, path)
435
436     def get_conf(self):
437         return self.lp
438
439     def get_sam_db(self):
440         lp = self.get_conf()
441         backends = str(lp.get("passdb backend")).split(" ")
442         if backends[0].startswith("smbpasswd"):
443             if ":" in backends[0]:
444                 return SmbpasswdFile(self.libdir_path(backends[0][len("smbpasswd:"):]))
445             return SmbpasswdFile(self.libdir_path("smbpasswd"))
446         elif backends[0].startswith("tdbsam"):
447             if ":" in backends[0]:
448                 return TdbSam(self.libdir_path(backends[0][len("tdbsam:"):]))
449             return TdbSam(self.libdir_path("passdb.tdb"))
450         else:
451             raise NotImplementedError("unsupported passdb backend %s" % backends[0])
452
453     def get_policy_db(self):
454         return PolicyDatabase(self.libdir_path("account_policy.tdb"))
455     
456     def get_registry(self):
457         return Registry(self.libdir_path("registry.tdb"))
458
459     def get_secrets_db(self):
460         return SecretsDatabase(self.libdir_path("secrets.tdb"))
461
462     def get_shareinfo_db(self):
463         return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
464
465     def get_idmap_db(self):
466         return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
467
468     def get_wins_db(self):
469         return WinsDatabase(self.libdir_path("wins.dat"))
470
471     def get_shares(self):
472         return Shares(self.get_conf(), self.get_shareinfo_db())
473
474     def get_groupmapping_db(self):
475         return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))