1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
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.
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.
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/>.
18 """Support for reading Samba 3 data files."""
20 __docformat__ = "restructuredText"
22 REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL"
23 REGISTRY_DB_VERSION = 1
30 def fetch_uint32(tdb, key):
36 return struct.unpack("<L", data)[0]
39 def fetch_int32(tdb, key):
45 return struct.unpack("<l", data)[0]
48 class TdbDatabase(object):
49 """Simple Samba 3 TDB database reader."""
50 def __init__(self, file):
53 :param file: Path of the file to open (appending "2" if TDB2 enabled).
55 if tdb.__version__.startswith("2"):
56 self.tdb = tdb.Tdb(file + "2", flags=os.O_RDONLY)
58 self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
61 def _check_version(self):
65 """Close resources associated with this object."""
69 class Registry(TdbDatabase):
70 """Simple read-only support for reading the Samba3 registry.
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.
78 """Return the number of keys."""
79 return len(self.keys())
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)]
85 def subkeys(self, key):
86 """Retrieve the subkeys for the specified key.
89 :return: list with key names
91 data = self.tdb.get("%s\x00" % key)
94 (num, ) = struct.unpack("<L", data[0:4])
95 keys = data[4:].split("\0")
98 assert len(keys) == num
101 def values(self, key):
102 """Return a dictionary with the values set for a specific key.
104 :param key: Key to retrieve values for.
105 :return: Dictionary with value names as key, tuple with type and
107 data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
111 (num, ) = struct.unpack("<L", data[0:4])
115 (name, data) = data.split("\0", 1)
117 (type, ) = struct.unpack("<L", data[0:4])
119 (value_len, ) = struct.unpack("<L", data[0:4])
122 ret[name] = (type, data[:value_len])
123 data = data[value_len:]
128 class PolicyDatabase(TdbDatabase):
129 """Samba 3 Account Policy database reader."""
130 def __init__(self, file):
131 """Open a policy database
133 :param file: Path to the file to open.
135 super(PolicyDatabase, self).__init__(file)
136 self.min_password_length = fetch_uint32(self.tdb, "min password length\x00")
137 self.password_history = fetch_uint32(self.tdb, "password history\x00")
138 self.user_must_logon_to_change_password = fetch_uint32(self.tdb, "user must logon to change pasword\x00")
139 self.maximum_password_age = fetch_uint32(self.tdb, "maximum password age\x00")
140 self.minimum_password_age = fetch_uint32(self.tdb, "minimum password age\x00")
141 self.lockout_duration = fetch_uint32(self.tdb, "lockout duration\x00")
142 self.reset_count_minutes = fetch_uint32(self.tdb, "reset count minutes\x00")
143 self.bad_lockout_minutes = fetch_uint32(self.tdb, "bad lockout minutes\x00")
144 self.disconnect_time = fetch_int32(self.tdb, "disconnect time\x00")
145 self.refuse_machine_password_change = fetch_uint32(self.tdb, "refuse machine password change\x00")
147 # FIXME: Read privileges as well
150 GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
151 GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
153 GROUP_PREFIX = "UNIXGROUP/"
155 # Alias memberships are stored reverse, as memberships. The performance
156 # critical operation is to determine the aliases a SID is member of, not
157 # listing alias members. So we store a list of alias SIDs a SID is member of
158 # hanging of the member as key.
159 MEMBEROF_PREFIX = "MEMBEROF/"
161 class GroupMappingDatabase(TdbDatabase):
162 """Samba 3 group mapping database reader."""
163 def _check_version(self):
164 assert fetch_int32(self.tdb, "INFO/version\x00") in (GROUPDB_DATABASE_VERSION_V1, GROUPDB_DATABASE_VERSION_V2)
167 """Retrieve the SIDs for the groups in this database.
169 :return: List with sids as strings.
171 for k in self.tdb.iterkeys():
172 if k.startswith(GROUP_PREFIX):
173 yield k[len(GROUP_PREFIX):].rstrip("\0")
175 def get_group(self, sid):
176 """Retrieve the group mapping information for a particular group.
178 :param sid: SID of the group
179 :return: None if the group can not be found, otherwise
180 a tuple with gid, sid_name_use, the NT name and comment.
182 data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid))
185 (gid, sid_name_use) = struct.unpack("<lL", data[0:8])
186 (nt_name, comment, _) = data[8:].split("\0")
187 return (gid, sid_name_use, nt_name, comment)
190 """Retrieve the aliases in this database."""
191 for k in self.tdb.iterkeys():
192 if k.startswith(MEMBEROF_PREFIX):
193 yield k[len(MEMBEROF_PREFIX):].rstrip("\0")
196 # High water mark keys
197 IDMAP_HWM_GROUP = "GROUP HWM\0"
198 IDMAP_HWM_USER = "USER HWM\0"
200 IDMAP_GROUP_PREFIX = "GID "
201 IDMAP_USER_PREFIX = "UID "
203 # idmap version determines auto-conversion
206 class IdmapDatabase(TdbDatabase):
207 """Samba 3 ID map database reader."""
208 def _check_version(self):
209 assert fetch_int32(self.tdb, "IDMAP_VERSION\0") == IDMAP_VERSION_V2
212 """Retrieve a list of all uids in this database."""
213 for k in self.tdb.iterkeys():
214 if k.startswith(IDMAP_USER_PREFIX):
215 yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
218 """Retrieve a list of all gids in this database."""
219 for k in self.tdb.iterkeys():
220 if k.startswith(IDMAP_GROUP_PREFIX):
221 yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
223 def get_user_sid(self, uid):
224 """Retrieve the SID associated with a particular uid.
226 :param uid: UID to retrieve SID for.
227 :return: A SID or None if no mapping was found.
229 data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
232 return data.rstrip("\0")
234 def get_group_sid(self, gid):
235 data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
238 return data.rstrip("\0")
240 def get_user_hwm(self):
241 """Obtain the user high-water mark."""
242 return fetch_uint32(self.tdb, IDMAP_HWM_USER)
244 def get_group_hwm(self):
245 """Obtain the group high-water mark."""
246 return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
249 class SecretsDatabase(TdbDatabase):
250 """Samba 3 Secrets database reader."""
251 def get_auth_password(self):
252 return self.tdb.get("SECRETS/AUTH_PASSWORD")
254 def get_auth_domain(self):
255 return self.tdb.get("SECRETS/AUTH_DOMAIN")
257 def get_auth_user(self):
258 return self.tdb.get("SECRETS/AUTH_USER")
260 def get_domain_guid(self, host):
261 return self.tdb.get("SECRETS/DOMGUID/%s" % host)
264 for k in self.tdb.iterkeys():
265 if k.startswith("SECRETS/LDAP_BIND_PW/"):
266 yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
269 """Iterate over domains in this database.
271 :return: Iterator over the names of domains in this database.
273 for k in self.tdb.iterkeys():
274 if k.startswith("SECRETS/SID/"):
275 yield k[len("SECRETS/SID/"):].rstrip("\0")
277 def get_ldap_bind_pw(self, host):
278 return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
280 def get_afs_keyfile(self, host):
281 return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
283 def get_machine_sec_channel_type(self, host):
284 return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
286 def get_machine_last_change_time(self, host):
287 return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
289 def get_machine_password(self, host):
290 return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
292 def get_machine_acc(self, host):
293 return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
295 def get_domtrust_acc(self, host):
296 return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
298 def trusted_domains(self):
299 for k in self.tdb.iterkeys():
300 if k.startswith("SECRETS/$DOMTRUST.ACC/"):
301 yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
303 def get_random_seed(self):
304 return self.tdb.get("INFO/random_seed")
306 def get_sid(self, host):
307 return self.tdb.get("SECRETS/SID/%s" % host.upper())
310 SHARE_DATABASE_VERSION_V1 = 1
311 SHARE_DATABASE_VERSION_V2 = 2
313 class ShareInfoDatabase(TdbDatabase):
314 """Samba 3 Share Info database reader."""
315 def _check_version(self):
316 assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
318 def get_secdesc(self, name):
319 """Obtain the security descriptor on a particular share.
321 :param name: Name of the share
323 secdesc = self.tdb.get("SECDESC/%s" % name)
324 # FIXME: Run ndr_pull_security_descriptor
328 class Shares(object):
329 """Container for share objects."""
330 def __init__(self, lp, shareinfo):
332 self.shareinfo = shareinfo
335 """Number of shares."""
336 return len(self.lp) - 1
339 """Iterate over the share names."""
340 return self.lp.__iter__()
343 ACB_DISABLED = 0x00000001
344 ACB_HOMDIRREQ = 0x00000002
345 ACB_PWNOTREQ = 0x00000004
346 ACB_TEMPDUP = 0x00000008
347 ACB_NORMAL = 0x00000010
349 ACB_DOMTRUST = 0x00000040
350 ACB_WSTRUST = 0x00000080
351 ACB_SVRTRUST = 0x00000100
352 ACB_PWNOEXP = 0x00000200
353 ACB_AUTOLOCK = 0x00000400
354 ACB_ENC_TXT_PWD_ALLOWED = 0x00000800
355 ACB_SMARTCARD_REQUIRED = 0x00001000
356 ACB_TRUSTED_FOR_DELEGATION = 0x00002000
357 ACB_NOT_DELEGATED = 0x00004000
358 ACB_USE_DES_KEY_ONLY = 0x00008000
359 ACB_DONT_REQUIRE_PREAUTH = 0x00010000
360 ACB_PW_EXPIRED = 0x00020000
361 ACB_NO_AUTH_DATA_REQD = 0x00080000
364 'N': ACB_PWNOTREQ, # 'N'o password.
365 'D': ACB_DISABLED, # 'D'isabled.
366 'H': ACB_HOMDIRREQ, # 'H'omedir required.
367 'T': ACB_TEMPDUP, # 'T'emp account.
368 'U': ACB_NORMAL, # 'U'ser account (normal).
369 'M': ACB_MNS, # 'M'NS logon user account. What is this ?
370 'W': ACB_WSTRUST, # 'W'orkstation account.
371 'S': ACB_SVRTRUST, # 'S'erver account.
372 'L': ACB_AUTOLOCK, # 'L'ocked account.
373 'X': ACB_PWNOEXP, # No 'X'piry on password
374 'I': ACB_DOMTRUST, # 'I'nterdomain trust account.
378 def decode_acb(text):
379 """Decode a ACB field.
381 :param text: ACB text
382 :return: integer with flags set.
384 assert not "[" in text and not "]" in text
387 ret |= acb_info_mapping[x]
391 class SAMUser(object):
394 :note: Unknown or unset fields are set to None.
396 def __init__(self, name, uid=None, lm_password=None, nt_password=None, acct_ctrl=None,
397 last_change_time=None, nt_username=None, fullname=None, logon_time=None, logoff_time=None,
398 acct_desc=None, group_rid=None, bad_password_count=None, logon_count=None,
399 domain=None, dir_drive=None, munged_dial=None, homedir=None, logon_script=None,
400 profile_path=None, workstations=None, kickoff_time=None, bad_password_time=None,
401 pass_last_set_time=None, pass_can_change_time=None, pass_must_change_time=None,
402 user_rid=None, unknown_6=None, nt_password_history=None,
403 unknown_str=None, hours=None, logon_divs=None):
406 self.lm_password = lm_password
407 self.nt_password = nt_password
408 self.acct_ctrl = acct_ctrl
409 self.pass_last_set_time = last_change_time
410 self.nt_username = nt_username
411 self.fullname = fullname
412 self.logon_time = logon_time
413 self.logoff_time = logoff_time
414 self.acct_desc = acct_desc
415 self.group_rid = group_rid
416 self.bad_password_count = bad_password_count
417 self.logon_count = logon_count
419 self.dir_drive = dir_drive
420 self.munged_dial = munged_dial
421 self.homedir = homedir
422 self.logon_script = logon_script
423 self.profile_path = profile_path
424 self.workstations = workstations
425 self.kickoff_time = kickoff_time
426 self.bad_password_time = bad_password_time
427 self.pass_can_change_time = pass_can_change_time
428 self.pass_must_change_time = pass_must_change_time
429 self.user_rid = user_rid
430 self.unknown_6 = unknown_6
431 self.nt_password_history = nt_password_history
432 self.unknown_str = unknown_str
434 self.logon_divs = logon_divs
436 def __eq__(self, other):
437 if not isinstance(other, SAMUser):
439 return self.__dict__ == other.__dict__
442 class SmbpasswdFile(object):
443 """Samba 3 smbpasswd file reader."""
444 def __init__(self, file):
447 for l in f.readlines():
448 if len(l) == 0 or l[0] == "#":
449 continue # Skip comments and blank lines
454 last_change_time = None
455 if parts[2] == "NO PASSWORD":
456 acct_ctrl |= ACB_PWNOTREQ
458 elif parts[2][0] in ("*", "X"):
462 lm_password = parts[2]
464 if parts[3][0] in ("*", "X"):
468 nt_password = parts[3]
470 if parts[4][0] == '[':
471 assert "]" in parts[4]
472 acct_ctrl |= decode_acb(parts[4][1:-1])
473 if parts[5].startswith("LCT-"):
474 last_change_time = int(parts[5][len("LCT-"):], 16)
475 else: # old style file
476 if username[-1] == "$":
477 acct_ctrl &= ~ACB_NORMAL
478 acct_ctrl |= ACB_WSTRUST
480 self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time)
485 return len(self.users)
487 def __getitem__(self, name):
488 return self.users[name]
491 return iter(self.users)
493 def close(self): # For consistency
497 TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
498 TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
499 TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
500 TDBSAM_USER_PREFIX = "USER_"
503 class LdapSam(object):
504 """Samba 3 LDAP passdb backend reader."""
505 def __init__(self, url):
509 class TdbSam(TdbDatabase):
510 """Samba 3 TDB passdb backend reader."""
511 def _check_version(self):
512 self.version = fetch_uint32(self.tdb, "INFO/version\0") or 0
513 assert self.version in (0, 1, 2)
516 """Iterate over the usernames in this Tdb database."""
517 for k in self.tdb.iterkeys():
518 if k.startswith(TDBSAM_USER_PREFIX):
519 yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0")
523 def __getitem__(self, name):
524 data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)]
527 def unpack_string(data):
528 (length, ) = struct.unpack("<L", data[:4])
532 return (data[:length].rstrip("\0"), data[length:])
534 def unpack_int32(data):
535 (value, ) = struct.unpack("<l", data[:4])
536 return (value, data[4:])
538 def unpack_uint32(data):
539 (value, ) = struct.unpack("<L", data[:4])
540 return (value, data[4:])
542 def unpack_uint16(data):
543 (value, ) = struct.unpack("<H", data[:2])
544 return (value, data[2:])
546 (logon_time, data) = unpack_int32(data)
547 (logoff_time, data) = unpack_int32(data)
548 (kickoff_time, data) = unpack_int32(data)
551 (bad_password_time, data) = unpack_int32(data)
552 if bad_password_time != 0:
553 user.bad_password_time = bad_password_time
554 (pass_last_set_time, data) = unpack_int32(data)
555 (pass_can_change_time, data) = unpack_int32(data)
556 (pass_must_change_time, data) = unpack_int32(data)
559 user.logon_time = logon_time
560 user.logoff_time = logoff_time
561 user.kickoff_time = kickoff_time
562 if pass_last_set_time != 0:
563 user.pass_last_set_time = pass_last_set_time
564 user.pass_can_change_time = pass_can_change_time
566 (user.username, data) = unpack_string(data)
567 (user.domain, data) = unpack_string(data)
568 (user.nt_username, data) = unpack_string(data)
569 (user.fullname, data) = unpack_string(data)
570 (user.homedir, data) = unpack_string(data)
571 (user.dir_drive, data) = unpack_string(data)
572 (user.logon_script, data) = unpack_string(data)
573 (user.profile_path, data) = unpack_string(data)
574 (user.acct_desc, data) = unpack_string(data)
575 (user.workstations, data) = unpack_string(data)
576 (user.unknown_str, data) = unpack_string(data)
577 (user.munged_dial, data) = unpack_string(data)
579 (user.user_rid, data) = unpack_int32(data)
580 (user.group_rid, data) = unpack_int32(data)
582 (user.lm_password, data) = unpack_string(data)
583 (user.nt_password, data) = unpack_string(data)
586 (user.nt_password_history, data) = unpack_string(data)
588 (user.acct_ctrl, data) = unpack_uint16(data)
589 (_, data) = unpack_uint32(data) # remove_me field
590 (user.logon_divs, data) = unpack_uint16(data)
591 (hours, data) = unpack_string(data)
595 user.hours.append(ord(entry) & (2 ** i) == (2 ** i))
596 (user.bad_password_count, data) = unpack_uint16(data)
597 (user.logon_count, data) = unpack_uint16(data)
598 (user.unknown_6, data) = unpack_uint32(data)
599 assert len(data) == 0
603 def shellsplit(text):
604 """Very simple shell-like line splitting.
606 :param text: Text to split.
607 :return: List with parts of the line as strings.
614 inquotes = not inquotes
615 elif c in ("\t", "\n", " ") and not inquotes:
625 class WinsDatabase(object):
626 """Samba 3 WINS database reader."""
627 def __init__(self, file):
630 assert f.readline().rstrip("\n") == "VERSION 1 0"
631 for l in f.readlines():
632 if l[0] == "#": # skip comments
634 entries = shellsplit(l.rstrip("\n"))
636 ttl = int(entries[1])
639 while "." in entries[i]:
640 ips.append(entries[i])
642 nb_flags = int(entries[i][:-1], 16)
643 assert not name in self.entries, "Name %s exists twice" % name
644 self.entries[name] = (ttl, ips, nb_flags)
647 def __getitem__(self, name):
648 return self.entries[name]
651 return len(self.entries)
654 return iter(self.entries)
657 """Return the entries in this WINS database."""
658 return self.entries.items()
660 def close(self): # for consistency
664 class ParamFile(object):
665 """Simple smb.conf-compatible file parser
667 Does not use a parameter table, unlike the "normal".
670 def __init__(self, sections=None):
671 self._sections = sections or {}
673 def _sanitize_name(self, name):
674 return name.strip().lower().replace(" ","")
677 return "ParamFile(%r)" % self._sections
679 def read(self, filename):
682 :param filename: Path to the file
685 for i, l in enumerate(open(filename, 'r').xreadlines()):
687 if not l or l[0] == '#' or l[0] == ';':
689 if l[0] == "[" and l[-1] == "]":
690 section = self._sanitize_name(l[1:-1])
691 self._sections.setdefault(section, {})
693 (k, v) = l.split("=", 1)
694 self._sections[section][self._sanitize_name(k)] = v
696 raise Exception("Unable to parser line %d: %r" % (i+1,l))
698 def get(self, param, section=None):
699 """Return the value of a parameter.
701 :param param: Parameter name
702 :param section: Section name, defaults to "global"
703 :return: parameter value as string if found, None otherwise.
707 section = self._sanitize_name(section)
708 if not section in self._sections:
710 param = self._sanitize_name(param)
711 if not param in self._sections[section]:
713 return self._sections[section][param].strip()
715 def __getitem__(self, section):
716 return self._sections[section]
718 def get_section(self, section):
719 return self._sections.get(section)
721 def add_section(self, section):
722 self._sections[self._sanitize_name(section)] = {}
724 def set_string(self, name, value):
725 self._sections["global"][name] = value
727 def get_string(self, name):
728 return self._sections["global"].get(name)
731 class Samba3(object):
732 """Samba 3 configuration and state data reader."""
733 def __init__(self, libdir, smbconfpath):
734 """Open the configuration and data for a Samba 3 installation.
736 :param libdir: Library directory
737 :param smbconfpath: Path to the smb.conf file.
739 self.smbconfpath = smbconfpath
741 self.lp = ParamFile()
742 self.lp.read(self.smbconfpath)
744 def libdir_path(self, path):
745 if path[0] == "/" or path[0] == ".":
747 return os.path.join(self.libdir, path)
752 def get_sam_db(self):
754 backends = (lp.get("passdb backend") or "").split(" ")
755 if ":" in backends[0]:
756 (name, location) = backends[0].split(":", 2)
760 if name == "smbpasswd":
761 return SmbpasswdFile(self.libdir_path(location or "smbpasswd"))
762 elif name == "tdbsam":
763 return TdbSam(self.libdir_path(location or "passdb.tdb"))
764 elif name == "ldapsam":
765 if location is not None:
766 return LdapSam("ldap:%s" % location)
767 return LdapSam(lp.get("ldap server"))
769 raise NotImplementedError("unsupported passdb backend %s" % backends[0])
771 def get_policy_db(self):
772 return PolicyDatabase(self.libdir_path("account_policy.tdb"))
774 def get_registry(self):
775 return Registry(self.libdir_path("registry.tdb"))
777 def get_secrets_db(self):
778 return SecretsDatabase(self.libdir_path("secrets.tdb"))
780 def get_shareinfo_db(self):
781 return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
783 def get_idmap_db(self):
784 return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
786 def get_wins_db(self):
787 return WinsDatabase(self.libdir_path("wins.dat"))
789 def get_shares(self):
790 return Shares(self.get_conf(), self.get_shareinfo_db())
792 def get_groupmapping_db(self):
793 return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))