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):
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, 3)
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))
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"))