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 ids in this database."""
213 for k in self.tdb.iterkeys():
214 if k.startswith(IDMAP_USER_PREFIX):
215 yield k.rstrip("\0").split(" ")
216 if k.startswith(IDMAP_GROUP_PREFIX):
217 yield k.rstrip("\0").split(" ")
220 """Retrieve a list of all uids in this database."""
221 for k in self.tdb.iterkeys():
222 if k.startswith(IDMAP_USER_PREFIX):
223 yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
226 """Retrieve a list of all gids in this database."""
227 for k in self.tdb.iterkeys():
228 if k.startswith(IDMAP_GROUP_PREFIX):
229 yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
231 def get_sid(self, xid, id_type):
232 data = self.tdb.get("%s %s\0" % (id_type, str(xid)))
235 return data.rstrip("\0")
237 def get_user_sid(self, uid):
238 """Retrieve the SID associated with a particular uid.
240 :param uid: UID to retrieve SID for.
241 :return: A SID or None if no mapping was found.
243 data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
246 return data.rstrip("\0")
248 def get_group_sid(self, gid):
249 data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
252 return data.rstrip("\0")
254 def get_user_hwm(self):
255 """Obtain the user high-water mark."""
256 return fetch_uint32(self.tdb, IDMAP_HWM_USER)
258 def get_group_hwm(self):
259 """Obtain the group high-water mark."""
260 return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
263 class SecretsDatabase(TdbDatabase):
264 """Samba 3 Secrets database reader."""
265 def get_auth_password(self):
266 return self.tdb.get("SECRETS/AUTH_PASSWORD")
268 def get_auth_domain(self):
269 return self.tdb.get("SECRETS/AUTH_DOMAIN")
271 def get_auth_user(self):
272 return self.tdb.get("SECRETS/AUTH_USER")
274 def get_domain_guid(self, host):
275 return self.tdb.get("SECRETS/DOMGUID/%s" % host)
278 for k in self.tdb.iterkeys():
279 if k.startswith("SECRETS/LDAP_BIND_PW/"):
280 yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
283 """Iterate over domains in this database.
285 :return: Iterator over the names of domains in this database.
287 for k in self.tdb.iterkeys():
288 if k.startswith("SECRETS/SID/"):
289 yield k[len("SECRETS/SID/"):].rstrip("\0")
291 def get_ldap_bind_pw(self, host):
292 return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
294 def get_afs_keyfile(self, host):
295 return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
297 def get_machine_sec_channel_type(self, host):
298 return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
300 def get_machine_last_change_time(self, host):
301 return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
303 def get_machine_password(self, host):
304 return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
306 def get_machine_acc(self, host):
307 return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
309 def get_domtrust_acc(self, host):
310 return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
312 def trusted_domains(self):
313 for k in self.tdb.iterkeys():
314 if k.startswith("SECRETS/$DOMTRUST.ACC/"):
315 yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
317 def get_random_seed(self):
318 return self.tdb.get("INFO/random_seed")
320 def get_sid(self, host):
321 return self.tdb.get("SECRETS/SID/%s" % host.upper())
324 SHARE_DATABASE_VERSION_V1 = 1
325 SHARE_DATABASE_VERSION_V2 = 2
327 class ShareInfoDatabase(TdbDatabase):
328 """Samba 3 Share Info database reader."""
329 def _check_version(self):
330 assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
332 def get_secdesc(self, name):
333 """Obtain the security descriptor on a particular share.
335 :param name: Name of the share
337 secdesc = self.tdb.get("SECDESC/%s" % name)
338 # FIXME: Run ndr_pull_security_descriptor
342 class Shares(object):
343 """Container for share objects."""
344 def __init__(self, lp, shareinfo):
346 self.shareinfo = shareinfo
349 """Number of shares."""
350 return len(self.lp) - 1
353 """Iterate over the share names."""
354 return self.lp.__iter__()
357 ACB_DISABLED = 0x00000001
358 ACB_HOMDIRREQ = 0x00000002
359 ACB_PWNOTREQ = 0x00000004
360 ACB_TEMPDUP = 0x00000008
361 ACB_NORMAL = 0x00000010
363 ACB_DOMTRUST = 0x00000040
364 ACB_WSTRUST = 0x00000080
365 ACB_SVRTRUST = 0x00000100
366 ACB_PWNOEXP = 0x00000200
367 ACB_AUTOLOCK = 0x00000400
368 ACB_ENC_TXT_PWD_ALLOWED = 0x00000800
369 ACB_SMARTCARD_REQUIRED = 0x00001000
370 ACB_TRUSTED_FOR_DELEGATION = 0x00002000
371 ACB_NOT_DELEGATED = 0x00004000
372 ACB_USE_DES_KEY_ONLY = 0x00008000
373 ACB_DONT_REQUIRE_PREAUTH = 0x00010000
374 ACB_PW_EXPIRED = 0x00020000
375 ACB_NO_AUTH_DATA_REQD = 0x00080000
378 'N': ACB_PWNOTREQ, # 'N'o password.
379 'D': ACB_DISABLED, # 'D'isabled.
380 'H': ACB_HOMDIRREQ, # 'H'omedir required.
381 'T': ACB_TEMPDUP, # 'T'emp account.
382 'U': ACB_NORMAL, # 'U'ser account (normal).
383 'M': ACB_MNS, # 'M'NS logon user account. What is this ?
384 'W': ACB_WSTRUST, # 'W'orkstation account.
385 'S': ACB_SVRTRUST, # 'S'erver account.
386 'L': ACB_AUTOLOCK, # 'L'ocked account.
387 'X': ACB_PWNOEXP, # No 'X'piry on password
388 'I': ACB_DOMTRUST, # 'I'nterdomain trust account.
392 def decode_acb(text):
393 """Decode a ACB field.
395 :param text: ACB text
396 :return: integer with flags set.
398 assert not "[" in text and not "]" in text
401 ret |= acb_info_mapping[x]
405 class SAMUser(object):
408 :note: Unknown or unset fields are set to None.
410 def __init__(self, name, uid=None, lm_password=None, nt_password=None, acct_ctrl=None,
411 last_change_time=None, nt_username=None, fullname=None, logon_time=None, logoff_time=None,
412 acct_desc=None, group_rid=None, bad_password_count=None, logon_count=None,
413 domain=None, dir_drive=None, munged_dial=None, homedir=None, logon_script=None,
414 profile_path=None, workstations=None, kickoff_time=None, bad_password_time=None,
415 pass_last_set_time=None, pass_can_change_time=None, pass_must_change_time=None,
416 user_rid=None, unknown_6=None, nt_password_history=None,
417 unknown_str=None, hours=None, logon_divs=None):
420 self.lm_password = lm_password
421 self.nt_password = nt_password
422 self.acct_ctrl = acct_ctrl
423 self.pass_last_set_time = last_change_time
424 self.nt_username = nt_username
425 self.fullname = fullname
426 self.logon_time = logon_time
427 self.logoff_time = logoff_time
428 self.acct_desc = acct_desc
429 self.group_rid = group_rid
430 self.bad_password_count = bad_password_count
431 self.logon_count = logon_count
433 self.dir_drive = dir_drive
434 self.munged_dial = munged_dial
435 self.homedir = homedir
436 self.logon_script = logon_script
437 self.profile_path = profile_path
438 self.workstations = workstations
439 self.kickoff_time = kickoff_time
440 self.bad_password_time = bad_password_time
441 self.pass_can_change_time = pass_can_change_time
442 self.pass_must_change_time = pass_must_change_time
443 self.user_rid = user_rid
444 self.unknown_6 = unknown_6
445 self.nt_password_history = nt_password_history
446 self.unknown_str = unknown_str
448 self.logon_divs = logon_divs
450 def __eq__(self, other):
451 if not isinstance(other, SAMUser):
453 return self.__dict__ == other.__dict__
456 class SmbpasswdFile(object):
457 """Samba 3 smbpasswd file reader."""
458 def __init__(self, file):
461 for l in f.readlines():
462 if len(l) == 0 or l[0] == "#":
463 continue # Skip comments and blank lines
468 last_change_time = None
469 if parts[2] == "NO PASSWORD":
470 acct_ctrl |= ACB_PWNOTREQ
472 elif parts[2][0] in ("*", "X"):
476 lm_password = parts[2]
478 if parts[3][0] in ("*", "X"):
482 nt_password = parts[3]
484 if parts[4][0] == '[':
485 assert "]" in parts[4]
486 acct_ctrl |= decode_acb(parts[4][1:-1])
487 if parts[5].startswith("LCT-"):
488 last_change_time = int(parts[5][len("LCT-"):], 16)
489 else: # old style file
490 if username[-1] == "$":
491 acct_ctrl &= ~ACB_NORMAL
492 acct_ctrl |= ACB_WSTRUST
494 self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time)
499 return len(self.users)
501 def __getitem__(self, name):
502 return self.users[name]
505 return iter(self.users)
507 def close(self): # For consistency
511 TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
512 TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
513 TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
514 TDBSAM_USER_PREFIX = "USER_"
517 class LdapSam(object):
518 """Samba 3 LDAP passdb backend reader."""
519 def __init__(self, url):
523 class TdbSam(TdbDatabase):
524 """Samba 3 TDB passdb backend reader."""
525 def _check_version(self):
526 self.version = fetch_uint32(self.tdb, "INFO/version\0") or 0
527 assert self.version in (0, 1, 2)
530 """Iterate over the usernames in this Tdb database."""
531 for k in self.tdb.iterkeys():
532 if k.startswith(TDBSAM_USER_PREFIX):
533 yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0")
537 def __getitem__(self, name):
538 data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)]
541 def unpack_string(data):
542 (length, ) = struct.unpack("<L", data[:4])
546 return (data[:length].rstrip("\0"), data[length:])
548 def unpack_int32(data):
549 (value, ) = struct.unpack("<l", data[:4])
550 return (value, data[4:])
552 def unpack_uint32(data):
553 (value, ) = struct.unpack("<L", data[:4])
554 return (value, data[4:])
556 def unpack_uint16(data):
557 (value, ) = struct.unpack("<H", data[:2])
558 return (value, data[2:])
560 (logon_time, data) = unpack_int32(data)
561 (logoff_time, data) = unpack_int32(data)
562 (kickoff_time, data) = unpack_int32(data)
565 (bad_password_time, data) = unpack_int32(data)
566 if bad_password_time != 0:
567 user.bad_password_time = bad_password_time
568 (pass_last_set_time, data) = unpack_int32(data)
569 (pass_can_change_time, data) = unpack_int32(data)
570 (pass_must_change_time, data) = unpack_int32(data)
573 user.logon_time = logon_time
574 user.logoff_time = logoff_time
575 user.kickoff_time = kickoff_time
576 if pass_last_set_time != 0:
577 user.pass_last_set_time = pass_last_set_time
578 user.pass_can_change_time = pass_can_change_time
580 (user.username, data) = unpack_string(data)
581 (user.domain, data) = unpack_string(data)
582 (user.nt_username, data) = unpack_string(data)
583 (user.fullname, data) = unpack_string(data)
584 (user.homedir, data) = unpack_string(data)
585 (user.dir_drive, data) = unpack_string(data)
586 (user.logon_script, data) = unpack_string(data)
587 (user.profile_path, data) = unpack_string(data)
588 (user.acct_desc, data) = unpack_string(data)
589 (user.workstations, data) = unpack_string(data)
590 (user.unknown_str, data) = unpack_string(data)
591 (user.munged_dial, data) = unpack_string(data)
593 (user.user_rid, data) = unpack_int32(data)
594 (user.group_rid, data) = unpack_int32(data)
596 (user.lm_password, data) = unpack_string(data)
597 (user.nt_password, data) = unpack_string(data)
600 (user.nt_password_history, data) = unpack_string(data)
602 (user.acct_ctrl, data) = unpack_uint16(data)
603 (_, data) = unpack_uint32(data) # remove_me field
604 (user.logon_divs, data) = unpack_uint16(data)
605 (hours, data) = unpack_string(data)
609 user.hours.append(ord(entry) & (2 ** i) == (2 ** i))
610 (user.bad_password_count, data) = unpack_uint16(data)
611 (user.logon_count, data) = unpack_uint16(data)
612 (user.unknown_6, data) = unpack_uint32(data)
613 assert len(data) == 0
617 def shellsplit(text):
618 """Very simple shell-like line splitting.
620 :param text: Text to split.
621 :return: List with parts of the line as strings.
628 inquotes = not inquotes
629 elif c in ("\t", "\n", " ") and not inquotes:
639 class WinsDatabase(object):
640 """Samba 3 WINS database reader."""
641 def __init__(self, file):
644 assert f.readline().rstrip("\n") == "VERSION 1 0"
645 for l in f.readlines():
646 if l[0] == "#": # skip comments
648 entries = shellsplit(l.rstrip("\n"))
650 ttl = int(entries[1])
653 while "." in entries[i]:
654 ips.append(entries[i])
656 nb_flags = int(entries[i][:-1], 16)
657 assert not name in self.entries, "Name %s exists twice" % name
658 self.entries[name] = (ttl, ips, nb_flags)
661 def __getitem__(self, name):
662 return self.entries[name]
665 return len(self.entries)
668 return iter(self.entries)
671 """Return the entries in this WINS database."""
672 return self.entries.items()
674 def close(self): # for consistency
678 class ParamFile(object):
679 """Simple smb.conf-compatible file parser
681 Does not use a parameter table, unlike the "normal".
684 def __init__(self, sections=None):
685 self._sections = sections or {}
687 def _sanitize_name(self, name):
688 return name.strip().lower().replace(" ","")
691 return "ParamFile(%r)" % self._sections
693 def read(self, filename):
696 :param filename: Path to the file
699 for i, l in enumerate(open(filename, 'r').xreadlines()):
701 if not l or l[0] == '#' or l[0] == ';':
703 if l[0] == "[" and l[-1] == "]":
704 section = self._sanitize_name(l[1:-1])
705 self._sections.setdefault(section, {})
707 (k, v) = l.split("=", 1)
708 self._sections[section][self._sanitize_name(k)] = v
710 raise Exception("Unable to parser line %d: %r" % (i+1,l))
712 def get(self, param, section=None):
713 """Return the value of a parameter.
715 :param param: Parameter name
716 :param section: Section name, defaults to "global"
717 :return: parameter value as string if found, None otherwise.
721 section = self._sanitize_name(section)
722 if not section in self._sections:
724 param = self._sanitize_name(param)
725 if not param in self._sections[section]:
727 return self._sections[section][param].strip()
729 def __getitem__(self, section):
730 return self._sections[section]
732 def get_section(self, section):
733 return self._sections.get(section)
735 def add_section(self, section):
736 self._sections[self._sanitize_name(section)] = {}
738 def set_string(self, name, value):
739 self._sections["global"][name] = value
741 def get_string(self, name):
742 return self._sections["global"].get(name)
745 class Samba3(object):
746 """Samba 3 configuration and state data reader."""
747 def __init__(self, libdir, smbconfpath):
748 """Open the configuration and data for a Samba 3 installation.
750 :param libdir: Library directory
751 :param smbconfpath: Path to the smb.conf file.
753 self.smbconfpath = smbconfpath
755 self.lp = ParamFile()
756 self.lp.read(self.smbconfpath)
758 def libdir_path(self, path):
759 if path[0] == "/" or path[0] == ".":
761 return os.path.join(self.libdir, path)
766 def get_sam_db(self):
768 backends = (lp.get("passdb backend") or "").split(" ")
769 if ":" in backends[0]:
770 (name, location) = backends[0].split(":", 2)
774 if name == "smbpasswd":
775 return SmbpasswdFile(self.libdir_path(location or "smbpasswd"))
776 elif name == "tdbsam":
777 return TdbSam(self.libdir_path(location or "passdb.tdb"))
778 elif name == "ldapsam":
779 if location is not None:
780 return LdapSam("ldap:%s" % location)
781 return LdapSam(lp.get("ldap server"))
783 raise NotImplementedError("unsupported passdb backend %s" % backends[0])
785 def get_policy_db(self):
786 return PolicyDatabase(self.libdir_path("account_policy.tdb"))
788 def get_registry(self):
789 return Registry(self.libdir_path("registry.tdb"))
791 def get_secrets_db(self):
792 return SecretsDatabase(self.libdir_path("secrets.tdb"))
794 def get_shareinfo_db(self):
795 return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
797 def get_idmap_db(self):
798 return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
800 def get_wins_db(self):
801 return WinsDatabase(self.libdir_path("wins.dat"))
803 def get_shares(self):
804 return Shares(self.get_conf(), self.get_shareinfo_db())
806 def get_groupmapping_db(self):
807 return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))