3
# Unix SMB/CIFS implementation.
4
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
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.
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.
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/>.
20
"""Support for reading Samba 3 data files."""
22
__docformat__ = "restructuredText"
24
REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL"
25
REGISTRY_DB_VERSION = 1
32
def fetch_uint32(tdb, key):
38
return struct.unpack("<L", data)[0]
41
def fetch_int32(tdb, key):
47
return struct.unpack("<l", data)[0]
50
class TdbDatabase(object):
51
"""Simple Samba 3 TDB database reader."""
52
def __init__(self, file):
55
:param file: Path of the file to open.
57
self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
60
def _check_version(self):
64
"""Close resources associated with this object."""
68
class Registry(TdbDatabase):
69
"""Simple read-only support for reading the Samba3 registry.
71
:note: This object uses the same syntax for registry key paths as
72
Samba 3. This particular format uses forward slashes for key path
73
separators and abbreviations for the predefined key names.
74
e.g.: HKLM/Software/Bar.
77
"""Return the number of keys."""
78
return len(self.keys())
81
"""Return list with all the keys."""
82
return [k.rstrip("\x00") for k in self.tdb.iterkeys() if not k.startswith(REGISTRY_VALUE_PREFIX)]
84
def subkeys(self, key):
85
"""Retrieve the subkeys for the specified key.
88
:return: list with key names
90
data = self.tdb.get("%s\x00" % key)
93
(num, ) = struct.unpack("<L", data[0:4])
94
keys = data[4:].split("\0")
97
assert len(keys) == num
100
def values(self, key):
101
"""Return a dictionary with the values set for a specific key.
103
:param key: Key to retrieve values for.
104
:return: Dictionary with value names as key, tuple with type and
106
data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
110
(num, ) = struct.unpack("<L", data[0:4])
114
(name, data) = data.split("\0", 1)
116
(type, ) = struct.unpack("<L", data[0:4])
118
(value_len, ) = struct.unpack("<L", data[0:4])
121
ret[name] = (type, data[:value_len])
122
data = data[value_len:]
127
class PolicyDatabase(TdbDatabase):
128
"""Samba 3 Account Policy database reader."""
129
def __init__(self, file):
130
"""Open a policy database
132
:param file: Path to the file to open.
134
super(PolicyDatabase, self).__init__(file)
135
self.min_password_length = fetch_uint32(self.tdb, "min password length\x00")
136
self.password_history = fetch_uint32(self.tdb, "password history\x00")
137
self.user_must_logon_to_change_password = fetch_uint32(self.tdb, "user must logon to change pasword\x00")
138
self.maximum_password_age = fetch_uint32(self.tdb, "maximum password age\x00")
139
self.minimum_password_age = fetch_uint32(self.tdb, "minimum password age\x00")
140
self.lockout_duration = fetch_uint32(self.tdb, "lockout duration\x00")
141
self.reset_count_minutes = fetch_uint32(self.tdb, "reset count minutes\x00")
142
self.bad_lockout_minutes = fetch_uint32(self.tdb, "bad lockout minutes\x00")
143
self.disconnect_time = fetch_int32(self.tdb, "disconnect time\x00")
144
self.refuse_machine_password_change = fetch_uint32(self.tdb, "refuse machine password change\x00")
146
# FIXME: Read privileges as well
149
GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
150
GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
152
GROUP_PREFIX = "UNIXGROUP/"
154
# Alias memberships are stored reverse, as memberships. The performance
155
# critical operation is to determine the aliases a SID is member of, not
156
# listing alias members. So we store a list of alias SIDs a SID is member of
157
# hanging of the member as key.
158
MEMBEROF_PREFIX = "MEMBEROF/"
160
class GroupMappingDatabase(TdbDatabase):
161
"""Samba 3 group mapping database reader."""
162
def _check_version(self):
163
assert fetch_int32(self.tdb, "INFO/version\x00") in (GROUPDB_DATABASE_VERSION_V1, GROUPDB_DATABASE_VERSION_V2)
166
"""Retrieve the SIDs for the groups in this database.
168
:return: List with sids as strings.
170
for k in self.tdb.iterkeys():
171
if k.startswith(GROUP_PREFIX):
172
yield k[len(GROUP_PREFIX):].rstrip("\0")
174
def get_group(self, sid):
175
"""Retrieve the group mapping information for a particular group.
177
:param sid: SID of the group
178
:return: None if the group can not be found, otherwise
179
a tuple with gid, sid_name_use, the NT name and comment.
181
data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid))
184
(gid, sid_name_use) = struct.unpack("<lL", data[0:8])
185
(nt_name, comment, _) = data[8:].split("\0")
186
return (gid, sid_name_use, nt_name, comment)
189
"""Retrieve the aliases in this database."""
190
for k in self.tdb.iterkeys():
191
if k.startswith(MEMBEROF_PREFIX):
192
yield k[len(MEMBEROF_PREFIX):].rstrip("\0")
195
# High water mark keys
196
IDMAP_HWM_GROUP = "GROUP HWM\0"
197
IDMAP_HWM_USER = "USER HWM\0"
199
IDMAP_GROUP_PREFIX = "GID "
200
IDMAP_USER_PREFIX = "UID "
202
# idmap version determines auto-conversion
205
class IdmapDatabase(TdbDatabase):
206
"""Samba 3 ID map database reader."""
207
def _check_version(self):
208
assert fetch_int32(self.tdb, "IDMAP_VERSION\0") == IDMAP_VERSION_V2
211
"""Retrieve a list of all uids in this database."""
212
for k in self.tdb.iterkeys():
213
if k.startswith(IDMAP_USER_PREFIX):
214
yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
217
"""Retrieve a list of all gids in this database."""
218
for k in self.tdb.iterkeys():
219
if k.startswith(IDMAP_GROUP_PREFIX):
220
yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
222
def get_user_sid(self, uid):
223
"""Retrieve the SID associated with a particular uid.
225
:param uid: UID to retrieve SID for.
226
:return: A SID or None if no mapping was found.
228
data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
231
return data.rstrip("\0")
233
def get_group_sid(self, gid):
234
data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
237
return data.rstrip("\0")
239
def get_user_hwm(self):
240
"""Obtain the user high-water mark."""
241
return fetch_uint32(self.tdb, IDMAP_HWM_USER)
243
def get_group_hwm(self):
244
"""Obtain the group high-water mark."""
245
return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
248
class SecretsDatabase(TdbDatabase):
249
"""Samba 3 Secrets database reader."""
250
def get_auth_password(self):
251
return self.tdb.get("SECRETS/AUTH_PASSWORD")
253
def get_auth_domain(self):
254
return self.tdb.get("SECRETS/AUTH_DOMAIN")
256
def get_auth_user(self):
257
return self.tdb.get("SECRETS/AUTH_USER")
259
def get_domain_guid(self, host):
260
return self.tdb.get("SECRETS/DOMGUID/%s" % host)
263
for k in self.tdb.iterkeys():
264
if k.startswith("SECRETS/LDAP_BIND_PW/"):
265
yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
268
"""Iterate over domains in this database.
270
:return: Iterator over the names of domains in this database.
272
for k in self.tdb.iterkeys():
273
if k.startswith("SECRETS/SID/"):
274
yield k[len("SECRETS/SID/"):].rstrip("\0")
276
def get_ldap_bind_pw(self, host):
277
return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
279
def get_afs_keyfile(self, host):
280
return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
282
def get_machine_sec_channel_type(self, host):
283
return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
285
def get_machine_last_change_time(self, host):
286
return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
288
def get_machine_password(self, host):
289
return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
291
def get_machine_acc(self, host):
292
return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
294
def get_domtrust_acc(self, host):
295
return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
297
def trusted_domains(self):
298
for k in self.tdb.iterkeys():
299
if k.startswith("SECRETS/$DOMTRUST.ACC/"):
300
yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
302
def get_random_seed(self):
303
return self.tdb.get("INFO/random_seed")
305
def get_sid(self, host):
306
return self.tdb.get("SECRETS/SID/%s" % host.upper())
309
SHARE_DATABASE_VERSION_V1 = 1
310
SHARE_DATABASE_VERSION_V2 = 2
312
class ShareInfoDatabase(TdbDatabase):
313
"""Samba 3 Share Info database reader."""
314
def _check_version(self):
315
assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
317
def get_secdesc(self, name):
318
"""Obtain the security descriptor on a particular share.
320
:param name: Name of the share
322
secdesc = self.tdb.get("SECDESC/%s" % name)
323
# FIXME: Run ndr_pull_security_descriptor
327
class Shares(object):
328
"""Container for share objects."""
329
def __init__(self, lp, shareinfo):
331
self.shareinfo = shareinfo
334
"""Number of shares."""
335
return len(self.lp) - 1
338
"""Iterate over the share names."""
339
return self.lp.__iter__()
342
ACB_DISABLED = 0x00000001
343
ACB_HOMDIRREQ = 0x00000002
344
ACB_PWNOTREQ = 0x00000004
345
ACB_TEMPDUP = 0x00000008
346
ACB_NORMAL = 0x00000010
348
ACB_DOMTRUST = 0x00000040
349
ACB_WSTRUST = 0x00000080
350
ACB_SVRTRUST = 0x00000100
351
ACB_PWNOEXP = 0x00000200
352
ACB_AUTOLOCK = 0x00000400
353
ACB_ENC_TXT_PWD_ALLOWED = 0x00000800
354
ACB_SMARTCARD_REQUIRED = 0x00001000
355
ACB_TRUSTED_FOR_DELEGATION = 0x00002000
356
ACB_NOT_DELEGATED = 0x00004000
357
ACB_USE_DES_KEY_ONLY = 0x00008000
358
ACB_DONT_REQUIRE_PREAUTH = 0x00010000
359
ACB_PW_EXPIRED = 0x00020000
360
ACB_NO_AUTH_DATA_REQD = 0x00080000
363
'N': ACB_PWNOTREQ, # 'N'o password.
364
'D': ACB_DISABLED, # 'D'isabled.
365
'H': ACB_HOMDIRREQ, # 'H'omedir required.
366
'T': ACB_TEMPDUP, # 'T'emp account.
367
'U': ACB_NORMAL, # 'U'ser account (normal).
368
'M': ACB_MNS, # 'M'NS logon user account. What is this ?
369
'W': ACB_WSTRUST, # 'W'orkstation account.
370
'S': ACB_SVRTRUST, # 'S'erver account.
371
'L': ACB_AUTOLOCK, # 'L'ocked account.
372
'X': ACB_PWNOEXP, # No 'X'piry on password
373
'I': ACB_DOMTRUST, # 'I'nterdomain trust account.
377
def decode_acb(text):
378
"""Decode a ACB field.
380
:param text: ACB text
381
:return: integer with flags set.
383
assert not "[" in text and not "]" in text
386
ret |= acb_info_mapping[x]
390
class SAMUser(object):
393
:note: Unknown or unset fields are set to None.
395
def __init__(self, name, uid=None, lm_password=None, nt_password=None, acct_ctrl=None,
396
last_change_time=None, nt_username=None, fullname=None, logon_time=None, logoff_time=None,
397
acct_desc=None, group_rid=None, bad_password_count=None, logon_count=None,
398
domain=None, dir_drive=None, munged_dial=None, homedir=None, logon_script=None,
399
profile_path=None, workstations=None, kickoff_time=None, bad_password_time=None,
400
pass_last_set_time=None, pass_can_change_time=None, pass_must_change_time=None,
401
user_rid=None, unknown_6=None, nt_password_history=None,
402
unknown_str=None, hours=None, logon_divs=None):
405
self.lm_password = lm_password
406
self.nt_password = nt_password
407
self.acct_ctrl = acct_ctrl
408
self.pass_last_set_time = last_change_time
409
self.nt_username = nt_username
410
self.fullname = fullname
411
self.logon_time = logon_time
412
self.logoff_time = logoff_time
413
self.acct_desc = acct_desc
414
self.group_rid = group_rid
415
self.bad_password_count = bad_password_count
416
self.logon_count = logon_count
418
self.dir_drive = dir_drive
419
self.munged_dial = munged_dial
420
self.homedir = homedir
421
self.logon_script = logon_script
422
self.profile_path = profile_path
423
self.workstations = workstations
424
self.kickoff_time = kickoff_time
425
self.bad_password_time = bad_password_time
426
self.pass_can_change_time = pass_can_change_time
427
self.pass_must_change_time = pass_must_change_time
428
self.user_rid = user_rid
429
self.unknown_6 = unknown_6
430
self.nt_password_history = nt_password_history
431
self.unknown_str = unknown_str
433
self.logon_divs = logon_divs
435
def __eq__(self, other):
436
if not isinstance(other, SAMUser):
438
return self.__dict__ == other.__dict__
441
class SmbpasswdFile(object):
442
"""Samba 3 smbpasswd file reader."""
443
def __init__(self, file):
446
for l in f.readlines():
447
if len(l) == 0 or l[0] == "#":
448
continue # Skip comments and blank lines
453
last_change_time = None
454
if parts[2] == "NO PASSWORD":
455
acct_ctrl |= ACB_PWNOTREQ
457
elif parts[2][0] in ("*", "X"):
461
lm_password = parts[2]
463
if parts[3][0] in ("*", "X"):
467
nt_password = parts[3]
469
if parts[4][0] == '[':
470
assert "]" in parts[4]
471
acct_ctrl |= decode_acb(parts[4][1:-1])
472
if parts[5].startswith("LCT-"):
473
last_change_time = int(parts[5][len("LCT-"):], 16)
474
else: # old style file
475
if username[-1] == "$":
476
acct_ctrl &= ~ACB_NORMAL
477
acct_ctrl |= ACB_WSTRUST
479
self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time)
484
return len(self.users)
486
def __getitem__(self, name):
487
return self.users[name]
490
return iter(self.users)
492
def close(self): # For consistency
496
TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
497
TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
498
TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
499
TDBSAM_USER_PREFIX = "USER_"
502
class LdapSam(object):
503
"""Samba 3 LDAP passdb backend reader."""
504
def __init__(self, url):
505
self.ldap_url = ldap_url
508
class TdbSam(TdbDatabase):
509
"""Samba 3 TDB passdb backend reader."""
510
def _check_version(self):
511
self.version = fetch_uint32(self.tdb, "INFO/version\0") or 0
512
assert self.version in (0, 1, 2)
515
"""Iterate over the usernames in this Tdb database."""
516
for k in self.tdb.iterkeys():
517
if k.startswith(TDBSAM_USER_PREFIX):
518
yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0")
522
def __getitem__(self, name):
523
data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)]
526
def unpack_string(data):
527
(length, ) = struct.unpack("<L", data[:4])
531
return (data[:length].rstrip("\0"), data[length:])
533
def unpack_int32(data):
534
(value, ) = struct.unpack("<l", data[:4])
535
return (value, data[4:])
537
def unpack_uint32(data):
538
(value, ) = struct.unpack("<L", data[:4])
539
return (value, data[4:])
541
def unpack_uint16(data):
542
(value, ) = struct.unpack("<H", data[:2])
543
return (value, data[2:])
545
(logon_time, data) = unpack_int32(data)
546
(logoff_time, data) = unpack_int32(data)
547
(kickoff_time, data) = unpack_int32(data)
550
(bad_password_time, data) = unpack_int32(data)
551
if bad_password_time != 0:
552
user.bad_password_time = bad_password_time
553
(pass_last_set_time, data) = unpack_int32(data)
554
(pass_can_change_time, data) = unpack_int32(data)
555
(pass_must_change_time, data) = unpack_int32(data)
558
user.logon_time = logon_time
559
user.logoff_time = logoff_time
560
user.kickoff_time = kickoff_time
561
if pass_last_set_time != 0:
562
user.pass_last_set_time = pass_last_set_time
563
user.pass_can_change_time = pass_can_change_time
565
(user.username, data) = unpack_string(data)
566
(user.domain, data) = unpack_string(data)
567
(user.nt_username, data) = unpack_string(data)
568
(user.fullname, data) = unpack_string(data)
569
(user.homedir, data) = unpack_string(data)
570
(user.dir_drive, data) = unpack_string(data)
571
(user.logon_script, data) = unpack_string(data)
572
(user.profile_path, data) = unpack_string(data)
573
(user.acct_desc, data) = unpack_string(data)
574
(user.workstations, data) = unpack_string(data)
575
(user.unknown_str, data) = unpack_string(data)
576
(user.munged_dial, data) = unpack_string(data)
578
(user.user_rid, data) = unpack_int32(data)
579
(user.group_rid, data) = unpack_int32(data)
581
(user.lm_password, data) = unpack_string(data)
582
(user.nt_password, data) = unpack_string(data)
585
(user.nt_password_history, data) = unpack_string(data)
587
(user.acct_ctrl, data) = unpack_uint16(data)
588
(_, data) = unpack_uint32(data) # remove_me field
589
(user.logon_divs, data) = unpack_uint16(data)
590
(hours, data) = unpack_string(data)
594
user.hours.append(ord(entry) & (2 ** i) == (2 ** i))
595
(user.bad_password_count, data) = unpack_uint16(data)
596
(user.logon_count, data) = unpack_uint16(data)
597
(user.unknown_6, data) = unpack_uint32(data)
598
assert len(data) == 0
602
def shellsplit(text):
603
"""Very simple shell-like line splitting.
605
:param text: Text to split.
606
:return: List with parts of the line as strings.
613
inquotes = not inquotes
614
elif c in ("\t", "\n", " ") and not inquotes:
624
class WinsDatabase(object):
625
"""Samba 3 WINS database reader."""
626
def __init__(self, file):
629
assert f.readline().rstrip("\n") == "VERSION 1 0"
630
for l in f.readlines():
631
if l[0] == "#": # skip comments
633
entries = shellsplit(l.rstrip("\n"))
635
ttl = int(entries[1])
638
while "." in entries[i]:
639
ips.append(entries[i])
641
nb_flags = int(entries[i][:-1], 16)
642
assert not name in self.entries, "Name %s exists twice" % name
643
self.entries[name] = (ttl, ips, nb_flags)
646
def __getitem__(self, name):
647
return self.entries[name]
650
return len(self.entries)
653
return iter(self.entries)
656
"""Return the entries in this WINS database."""
657
return self.entries.items()
659
def close(self): # for consistency
663
class ParamFile(object):
664
"""Simple smb.conf-compatible file parser
666
Does not use a parameter table, unlike the "normal".
669
def __init__(self, sections=None):
670
self._sections = sections or {}
672
def _sanitize_name(self, name):
673
return name.strip().lower().replace(" ","")
676
return "ParamFile(%r)" % self._sections
678
def read(self, filename):
681
:param filename: Path to the file
684
for i, l in enumerate(open(filename, 'r').xreadlines()):
688
if l[0] == "[" and l[-1] == "]":
689
section = self._sanitize_name(l[1:-1])
690
self._sections.setdefault(section, {})
692
(k, v) = l.split("=", 1)
693
self._sections[section][self._sanitize_name(k)] = v
695
raise Error("Unable to parser line %d: %r" % (i+1,l))
697
def get(self, param, section=None):
698
"""Return the value of a parameter.
700
:param param: Parameter name
701
:param section: Section name, defaults to "global"
702
:return: parameter value as string if found, None otherwise.
706
section = self._sanitize_name(section)
707
if not section in self._sections:
709
param = self._sanitize_name(param)
710
if not param in self._sections[section]:
712
return self._sections[section][param].strip()
714
def __getitem__(self, section):
715
return self._sections[section]
717
def get_section(self, section):
718
return self._sections.get(section)
720
def add_section(self, section):
721
self._sections[self._sanitize_name(section)] = {}
723
def set_string(self, name, value):
724
self._sections["global"][name] = value
726
def get_string(self, name):
727
return self._sections["global"].get(name)
730
class Samba3(object):
731
"""Samba 3 configuration and state data reader."""
732
def __init__(self, libdir, smbconfpath):
733
"""Open the configuration and data for a Samba 3 installation.
735
:param libdir: Library directory
736
:param smbconfpath: Path to the smb.conf file.
738
self.smbconfpath = smbconfpath
740
self.lp = ParamFile()
741
self.lp.read(self.smbconfpath)
743
def libdir_path(self, path):
744
if path[0] == "/" or path[0] == ".":
746
return os.path.join(self.libdir, path)
751
def get_sam_db(self):
753
backends = (lp.get("passdb backend") or "").split(" ")
754
if ":" in backends[0]:
755
(name, location) = backends[0].split(":", 2)
759
if name == "smbpasswd":
760
return SmbpasswdFile(self.libdir_path(location or "smbpasswd"))
761
elif name == "tdbsam":
762
return TdbSam(self.libdir_path(location or "passdb.tdb"))
763
elif name == "ldapsam":
764
if location is not None:
765
return LdapSam("ldap:%s" % location)
766
return LdapSam(lp.get("ldap server"))
768
raise NotImplementedError("unsupported passdb backend %s" % backends[0])
770
def get_policy_db(self):
771
return PolicyDatabase(self.libdir_path("account_policy.tdb"))
773
def get_registry(self):
774
return Registry(self.libdir_path("registry.tdb"))
776
def get_secrets_db(self):
777
return SecretsDatabase(self.libdir_path("secrets.tdb"))
779
def get_shareinfo_db(self):
780
return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
782
def get_idmap_db(self):
783
return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
785
def get_wins_db(self):
786
return WinsDatabase(self.libdir_path("wins.dat"))
788
def get_shares(self):
789
return Shares(self.get_conf(), self.get_shareinfo_db())
791
def get_groupmapping_db(self):
792
return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))