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.
55 self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
58 def _check_version(self):
62 """Close resources associated with this object."""
66 class Registry(TdbDatabase):
67 """Simple read-only support for reading the Samba3 registry.
69 :note: This object uses the same syntax for registry key paths as
70 Samba 3. This particular format uses forward slashes for key path
71 separators and abbreviations for the predefined key names.
72 e.g.: HKLM/Software/Bar.
75 """Return the number of keys."""
76 return len(self.keys())
79 """Return list with all the keys."""
80 return [k.rstrip("\x00") for k in self.tdb.iterkeys() if not k.startswith(REGISTRY_VALUE_PREFIX)]
82 def subkeys(self, key):
83 """Retrieve the subkeys for the specified key.
86 :return: list with key names
88 data = self.tdb.get("%s\x00" % key)
91 (num, ) = struct.unpack("<L", data[0:4])
92 keys = data[4:].split("\0")
95 assert len(keys) == num
98 def values(self, key):
99 """Return a dictionary with the values set for a specific key.
101 :param key: Key to retrieve values for.
102 :return: Dictionary with value names as key, tuple with type and
104 data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
108 (num, ) = struct.unpack("<L", data[0:4])
112 (name, data) = data.split("\0", 1)
114 (type, ) = struct.unpack("<L", data[0:4])
116 (value_len, ) = struct.unpack("<L", data[0:4])
119 ret[name] = (type, data[:value_len])
120 data = data[value_len:]
125 class PolicyDatabase(TdbDatabase):
126 """Samba 3 Account Policy database reader."""
127 def __init__(self, file):
128 """Open a policy database
130 :param file: Path to the file to open.
132 super(PolicyDatabase, self).__init__(file)
133 self.min_password_length = fetch_uint32(self.tdb, "min password length\x00")
134 self.password_history = fetch_uint32(self.tdb, "password history\x00")
135 self.user_must_logon_to_change_password = fetch_uint32(self.tdb, "user must logon to change pasword\x00")
136 self.maximum_password_age = fetch_uint32(self.tdb, "maximum password age\x00")
137 self.minimum_password_age = fetch_uint32(self.tdb, "minimum password age\x00")
138 self.lockout_duration = fetch_uint32(self.tdb, "lockout duration\x00")
139 self.reset_count_minutes = fetch_uint32(self.tdb, "reset count minutes\x00")
140 self.bad_lockout_minutes = fetch_uint32(self.tdb, "bad lockout minutes\x00")
141 self.disconnect_time = fetch_int32(self.tdb, "disconnect time\x00")
142 self.refuse_machine_password_change = fetch_uint32(self.tdb, "refuse machine password change\x00")
144 # FIXME: Read privileges as well
147 GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
148 GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
150 GROUP_PREFIX = "UNIXGROUP/"
152 # Alias memberships are stored reverse, as memberships. The performance
153 # critical operation is to determine the aliases a SID is member of, not
154 # listing alias members. So we store a list of alias SIDs a SID is member of
155 # hanging of the member as key.
156 MEMBEROF_PREFIX = "MEMBEROF/"
158 class GroupMappingDatabase(TdbDatabase):
159 """Samba 3 group mapping database reader."""
160 def _check_version(self):
161 assert fetch_int32(self.tdb, "INFO/version\x00") in (GROUPDB_DATABASE_VERSION_V1, GROUPDB_DATABASE_VERSION_V2)
164 """Retrieve the SIDs for the groups in this database.
166 :return: List with sids as strings.
168 for k in self.tdb.iterkeys():
169 if k.startswith(GROUP_PREFIX):
170 yield k[len(GROUP_PREFIX):].rstrip("\0")
172 def get_group(self, sid):
173 """Retrieve the group mapping information for a particular group.
175 :param sid: SID of the group
176 :return: None if the group can not be found, otherwise
177 a tuple with gid, sid_name_use, the NT name and comment.
179 data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid))
182 (gid, sid_name_use) = struct.unpack("<lL", data[0:8])
183 (nt_name, comment, _) = data[8:].split("\0")
184 return (gid, sid_name_use, nt_name, comment)
187 """Retrieve the aliases in this database."""
188 for k in self.tdb.iterkeys():
189 if k.startswith(MEMBEROF_PREFIX):
190 yield k[len(MEMBEROF_PREFIX):].rstrip("\0")
193 # High water mark keys
194 IDMAP_HWM_GROUP = "GROUP HWM\0"
195 IDMAP_HWM_USER = "USER HWM\0"
197 IDMAP_GROUP_PREFIX = "GID "
198 IDMAP_USER_PREFIX = "UID "
200 # idmap version determines auto-conversion
203 class IdmapDatabase(TdbDatabase):
204 """Samba 3 ID map database reader."""
205 def _check_version(self):
206 assert fetch_int32(self.tdb, "IDMAP_VERSION\0") == IDMAP_VERSION_V2
209 """Retrieve a list of all uids in this database."""
210 for k in self.tdb.iterkeys():
211 if k.startswith(IDMAP_USER_PREFIX):
212 yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
215 """Retrieve a list of all gids in this database."""
216 for k in self.tdb.iterkeys():
217 if k.startswith(IDMAP_GROUP_PREFIX):
218 yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
220 def get_user_sid(self, uid):
221 """Retrieve the SID associated with a particular uid.
223 :param uid: UID to retrieve SID for.
224 :return: A SID or None if no mapping was found.
226 data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
229 return data.rstrip("\0")
231 def get_group_sid(self, gid):
232 data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
235 return data.rstrip("\0")
237 def get_user_hwm(self):
238 """Obtain the user high-water mark."""
239 return fetch_uint32(self.tdb, IDMAP_HWM_USER)
241 def get_group_hwm(self):
242 """Obtain the group high-water mark."""
243 return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
246 class SecretsDatabase(TdbDatabase):
247 """Samba 3 Secrets database reader."""
248 def get_auth_password(self):
249 return self.tdb.get("SECRETS/AUTH_PASSWORD")
251 def get_auth_domain(self):
252 return self.tdb.get("SECRETS/AUTH_DOMAIN")
254 def get_auth_user(self):
255 return self.tdb.get("SECRETS/AUTH_USER")
257 def get_domain_guid(self, host):
258 return self.tdb.get("SECRETS/DOMGUID/%s" % host)
261 for k in self.tdb.iterkeys():
262 if k.startswith("SECRETS/LDAP_BIND_PW/"):
263 yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
266 """Iterate over domains in this database.
268 :return: Iterator over the names of domains in this database.
270 for k in self.tdb.iterkeys():
271 if k.startswith("SECRETS/SID/"):
272 yield k[len("SECRETS/SID/"):].rstrip("\0")
274 def get_ldap_bind_pw(self, host):
275 return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
277 def get_afs_keyfile(self, host):
278 return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
280 def get_machine_sec_channel_type(self, host):
281 return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
283 def get_machine_last_change_time(self, host):
284 return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
286 def get_machine_password(self, host):
287 return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
289 def get_machine_acc(self, host):
290 return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
292 def get_domtrust_acc(self, host):
293 return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
295 def trusted_domains(self):
296 for k in self.tdb.iterkeys():
297 if k.startswith("SECRETS/$DOMTRUST.ACC/"):
298 yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
300 def get_random_seed(self):
301 return self.tdb.get("INFO/random_seed")
303 def get_sid(self, host):
304 return self.tdb.get("SECRETS/SID/%s" % host.upper())
307 SHARE_DATABASE_VERSION_V1 = 1
308 SHARE_DATABASE_VERSION_V2 = 2
310 class ShareInfoDatabase(TdbDatabase):
311 """Samba 3 Share Info database reader."""
312 def _check_version(self):
313 assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
315 def get_secdesc(self, name):
316 """Obtain the security descriptor on a particular share.
318 :param name: Name of the share
320 secdesc = self.tdb.get("SECDESC/%s" % name)
321 # FIXME: Run ndr_pull_security_descriptor
325 class Shares(object):
326 """Container for share objects."""
327 def __init__(self, lp, shareinfo):
329 self.shareinfo = shareinfo
332 """Number of shares."""
333 return len(self.lp) - 1
336 """Iterate over the share names."""
337 return self.lp.__iter__()
340 ACB_DISABLED = 0x00000001
341 ACB_HOMDIRREQ = 0x00000002
342 ACB_PWNOTREQ = 0x00000004
343 ACB_TEMPDUP = 0x00000008
344 ACB_NORMAL = 0x00000010
346 ACB_DOMTRUST = 0x00000040
347 ACB_WSTRUST = 0x00000080
348 ACB_SVRTRUST = 0x00000100
349 ACB_PWNOEXP = 0x00000200
350 ACB_AUTOLOCK = 0x00000400
351 ACB_ENC_TXT_PWD_ALLOWED = 0x00000800
352 ACB_SMARTCARD_REQUIRED = 0x00001000
353 ACB_TRUSTED_FOR_DELEGATION = 0x00002000
354 ACB_NOT_DELEGATED = 0x00004000
355 ACB_USE_DES_KEY_ONLY = 0x00008000
356 ACB_DONT_REQUIRE_PREAUTH = 0x00010000
357 ACB_PW_EXPIRED = 0x00020000
358 ACB_NO_AUTH_DATA_REQD = 0x00080000
361 'N': ACB_PWNOTREQ, # 'N'o password.
362 'D': ACB_DISABLED, # 'D'isabled.
363 'H': ACB_HOMDIRREQ, # 'H'omedir required.
364 'T': ACB_TEMPDUP, # 'T'emp account.
365 'U': ACB_NORMAL, # 'U'ser account (normal).
366 'M': ACB_MNS, # 'M'NS logon user account. What is this ?
367 'W': ACB_WSTRUST, # 'W'orkstation account.
368 'S': ACB_SVRTRUST, # 'S'erver account.
369 'L': ACB_AUTOLOCK, # 'L'ocked account.
370 'X': ACB_PWNOEXP, # No 'X'piry on password
371 'I': ACB_DOMTRUST, # 'I'nterdomain trust account.
375 def decode_acb(text):
376 """Decode a ACB field.
378 :param text: ACB text
379 :return: integer with flags set.
381 assert not "[" in text and not "]" in text
384 ret |= acb_info_mapping[x]
388 class SAMUser(object):
391 :note: Unknown or unset fields are set to None.
393 def __init__(self, name, uid=None, lm_password=None, nt_password=None, acct_ctrl=None,
394 last_change_time=None, nt_username=None, fullname=None, logon_time=None, logoff_time=None,
395 acct_desc=None, group_rid=None, bad_password_count=None, logon_count=None,
396 domain=None, dir_drive=None, munged_dial=None, homedir=None, logon_script=None,
397 profile_path=None, workstations=None, kickoff_time=None, bad_password_time=None,
398 pass_last_set_time=None, pass_can_change_time=None, pass_must_change_time=None,
399 user_rid=None, unknown_6=None, nt_password_history=None,
400 unknown_str=None, hours=None, logon_divs=None):
403 self.lm_password = lm_password
404 self.nt_password = nt_password
405 self.acct_ctrl = acct_ctrl
406 self.pass_last_set_time = last_change_time
407 self.nt_username = nt_username
408 self.fullname = fullname
409 self.logon_time = logon_time
410 self.logoff_time = logoff_time
411 self.acct_desc = acct_desc
412 self.group_rid = group_rid
413 self.bad_password_count = bad_password_count
414 self.logon_count = logon_count
416 self.dir_drive = dir_drive
417 self.munged_dial = munged_dial
418 self.homedir = homedir
419 self.logon_script = logon_script
420 self.profile_path = profile_path
421 self.workstations = workstations
422 self.kickoff_time = kickoff_time
423 self.bad_password_time = bad_password_time
424 self.pass_can_change_time = pass_can_change_time
425 self.pass_must_change_time = pass_must_change_time
426 self.user_rid = user_rid
427 self.unknown_6 = unknown_6
428 self.nt_password_history = nt_password_history
429 self.unknown_str = unknown_str
431 self.logon_divs = logon_divs
433 def __eq__(self, other):
434 if not isinstance(other, SAMUser):
436 return self.__dict__ == other.__dict__
439 class SmbpasswdFile(object):
440 """Samba 3 smbpasswd file reader."""
441 def __init__(self, file):
444 for l in f.readlines():
445 if len(l) == 0 or l[0] == "#":
446 continue # Skip comments and blank lines
451 last_change_time = None
452 if parts[2] == "NO PASSWORD":
453 acct_ctrl |= ACB_PWNOTREQ
455 elif parts[2][0] in ("*", "X"):
459 lm_password = parts[2]
461 if parts[3][0] in ("*", "X"):
465 nt_password = parts[3]
467 if parts[4][0] == '[':
468 assert "]" in parts[4]
469 acct_ctrl |= decode_acb(parts[4][1:-1])
470 if parts[5].startswith("LCT-"):
471 last_change_time = int(parts[5][len("LCT-"):], 16)
472 else: # old style file
473 if username[-1] == "$":
474 acct_ctrl &= ~ACB_NORMAL
475 acct_ctrl |= ACB_WSTRUST
477 self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time)
482 return len(self.users)
484 def __getitem__(self, name):
485 return self.users[name]
488 return iter(self.users)
490 def close(self): # For consistency
494 TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
495 TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
496 TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
497 TDBSAM_USER_PREFIX = "USER_"
500 class LdapSam(object):
501 """Samba 3 LDAP passdb backend reader."""
502 def __init__(self, url):
506 class TdbSam(TdbDatabase):
507 """Samba 3 TDB passdb backend reader."""
508 def _check_version(self):
509 self.version = fetch_uint32(self.tdb, "INFO/version\0") or 0
510 assert self.version in (0, 1, 2)
513 """Iterate over the usernames in this Tdb database."""
514 for k in self.tdb.iterkeys():
515 if k.startswith(TDBSAM_USER_PREFIX):
516 yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0")
520 def __getitem__(self, name):
521 data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)]
524 def unpack_string(data):
525 (length, ) = struct.unpack("<L", data[:4])
529 return (data[:length].rstrip("\0"), data[length:])
531 def unpack_int32(data):
532 (value, ) = struct.unpack("<l", data[:4])
533 return (value, data[4:])
535 def unpack_uint32(data):
536 (value, ) = struct.unpack("<L", data[:4])
537 return (value, data[4:])
539 def unpack_uint16(data):
540 (value, ) = struct.unpack("<H", data[:2])
541 return (value, data[2:])
543 (logon_time, data) = unpack_int32(data)
544 (logoff_time, data) = unpack_int32(data)
545 (kickoff_time, data) = unpack_int32(data)
548 (bad_password_time, data) = unpack_int32(data)
549 if bad_password_time != 0:
550 user.bad_password_time = bad_password_time
551 (pass_last_set_time, data) = unpack_int32(data)
552 (pass_can_change_time, data) = unpack_int32(data)
553 (pass_must_change_time, data) = unpack_int32(data)
556 user.logon_time = logon_time
557 user.logoff_time = logoff_time
558 user.kickoff_time = kickoff_time
559 if pass_last_set_time != 0:
560 user.pass_last_set_time = pass_last_set_time
561 user.pass_can_change_time = pass_can_change_time
563 (user.username, data) = unpack_string(data)
564 (user.domain, data) = unpack_string(data)
565 (user.nt_username, data) = unpack_string(data)
566 (user.fullname, data) = unpack_string(data)
567 (user.homedir, data) = unpack_string(data)
568 (user.dir_drive, data) = unpack_string(data)
569 (user.logon_script, data) = unpack_string(data)
570 (user.profile_path, data) = unpack_string(data)
571 (user.acct_desc, data) = unpack_string(data)
572 (user.workstations, data) = unpack_string(data)
573 (user.unknown_str, data) = unpack_string(data)
574 (user.munged_dial, data) = unpack_string(data)
576 (user.user_rid, data) = unpack_int32(data)
577 (user.group_rid, data) = unpack_int32(data)
579 (user.lm_password, data) = unpack_string(data)
580 (user.nt_password, data) = unpack_string(data)
583 (user.nt_password_history, data) = unpack_string(data)
585 (user.acct_ctrl, data) = unpack_uint16(data)
586 (_, data) = unpack_uint32(data) # remove_me field
587 (user.logon_divs, data) = unpack_uint16(data)
588 (hours, data) = unpack_string(data)
592 user.hours.append(ord(entry) & (2 ** i) == (2 ** i))
593 (user.bad_password_count, data) = unpack_uint16(data)
594 (user.logon_count, data) = unpack_uint16(data)
595 (user.unknown_6, data) = unpack_uint32(data)
596 assert len(data) == 0
600 def shellsplit(text):
601 """Very simple shell-like line splitting.
603 :param text: Text to split.
604 :return: List with parts of the line as strings.
611 inquotes = not inquotes
612 elif c in ("\t", "\n", " ") and not inquotes:
622 class WinsDatabase(object):
623 """Samba 3 WINS database reader."""
624 def __init__(self, file):
627 assert f.readline().rstrip("\n") == "VERSION 1 0"
628 for l in f.readlines():
629 if l[0] == "#": # skip comments
631 entries = shellsplit(l.rstrip("\n"))
633 ttl = int(entries[1])
636 while "." in entries[i]:
637 ips.append(entries[i])
639 nb_flags = int(entries[i][:-1], 16)
640 assert not name in self.entries, "Name %s exists twice" % name
641 self.entries[name] = (ttl, ips, nb_flags)
644 def __getitem__(self, name):
645 return self.entries[name]
648 return len(self.entries)
651 return iter(self.entries)
654 """Return the entries in this WINS database."""
655 return self.entries.items()
657 def close(self): # for consistency
661 class ParamFile(object):
662 """Simple smb.conf-compatible file parser
664 Does not use a parameter table, unlike the "normal".
667 def __init__(self, sections=None):
668 self._sections = sections or {}
670 def _sanitize_name(self, name):
671 return name.strip().lower().replace(" ","")
674 return "ParamFile(%r)" % self._sections
676 def read(self, filename):
679 :param filename: Path to the file
682 for i, l in enumerate(open(filename, 'r').xreadlines()):
684 if not l or l[0] == '#' or l[0] == ';':
686 if l[0] == "[" and l[-1] == "]":
687 section = self._sanitize_name(l[1:-1])
688 self._sections.setdefault(section, {})
690 (k, v) = l.split("=", 1)
691 self._sections[section][self._sanitize_name(k)] = v
693 raise Exception("Unable to parser line %d: %r" % (i+1,l))
695 def get(self, param, section=None):
696 """Return the value of a parameter.
698 :param param: Parameter name
699 :param section: Section name, defaults to "global"
700 :return: parameter value as string if found, None otherwise.
704 section = self._sanitize_name(section)
705 if not section in self._sections:
707 param = self._sanitize_name(param)
708 if not param in self._sections[section]:
710 return self._sections[section][param].strip()
712 def __getitem__(self, section):
713 return self._sections[section]
715 def get_section(self, section):
716 return self._sections.get(section)
718 def add_section(self, section):
719 self._sections[self._sanitize_name(section)] = {}
721 def set_string(self, name, value):
722 self._sections["global"][name] = value
724 def get_string(self, name):
725 return self._sections["global"].get(name)
728 class Samba3(object):
729 """Samba 3 configuration and state data reader."""
730 def __init__(self, libdir, smbconfpath):
731 """Open the configuration and data for a Samba 3 installation.
733 :param libdir: Library directory
734 :param smbconfpath: Path to the smb.conf file.
736 self.smbconfpath = smbconfpath
738 self.lp = ParamFile()
739 self.lp.read(self.smbconfpath)
741 def libdir_path(self, path):
742 if path[0] == "/" or path[0] == ".":
744 return os.path.join(self.libdir, path)
749 def get_sam_db(self):
751 backends = (lp.get("passdb backend") or "").split(" ")
752 if ":" in backends[0]:
753 (name, location) = backends[0].split(":", 2)
757 if name == "smbpasswd":
758 return SmbpasswdFile(self.libdir_path(location or "smbpasswd"))
759 elif name == "tdbsam":
760 return TdbSam(self.libdir_path(location or "passdb.tdb"))
761 elif name == "ldapsam":
762 if location is not None:
763 return LdapSam("ldap:%s" % location)
764 return LdapSam(lp.get("ldap server"))
766 raise NotImplementedError("unsupported passdb backend %s" % backends[0])
768 def get_policy_db(self):
769 return PolicyDatabase(self.libdir_path("account_policy.tdb"))
771 def get_registry(self):
772 return Registry(self.libdir_path("registry.tdb"))
774 def get_secrets_db(self):
775 return SecretsDatabase(self.libdir_path("secrets.tdb"))
777 def get_shareinfo_db(self):
778 return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
780 def get_idmap_db(self):
781 return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
783 def get_wins_db(self):
784 return WinsDatabase(self.libdir_path("wins.dat"))
786 def get_shares(self):
787 return Shares(self.get_conf(), self.get_shareinfo_db())
789 def get_groupmapping_db(self):
790 return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))