2c323bd0b4282365c0ebe8bc557ea459ff526d8b
[bbaumbach/samba-autobuild/.git] / source4 / scripting / python / samba / samba3.py
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
3 #
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.
8 #
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.
13 #
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/>.
16 #
17
18 """Support for reading Samba 3 data files."""
19
20 __docformat__ = "restructuredText"
21
22 REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL"
23 REGISTRY_DB_VERSION = 1
24
25 import os
26 import struct
27 import tdb
28
29
30 def fetch_uint32(tdb, key):
31     try:
32         data = tdb[key]
33     except KeyError:
34         return None
35     assert len(data) == 4
36     return struct.unpack("<L", data)[0]
37
38
39 def fetch_int32(tdb, key):
40     try:
41         data = tdb[key]
42     except KeyError:
43         return None
44     assert len(data) == 4
45     return struct.unpack("<l", data)[0]
46
47
48 class TdbDatabase(object):
49     """Simple Samba 3 TDB database reader."""
50     def __init__(self, file):
51         """Open a file.
52
53         :param file: Path of the file to open.
54         """
55         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
56         self._check_version()
57
58     def _check_version(self):
59         pass
60
61     def close(self):
62         """Close resources associated with this object."""
63         self.tdb.close()
64
65
66 class Registry(TdbDatabase):
67     """Simple read-only support for reading the Samba3 registry.
68
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.
73     """
74     def __len__(self):
75         """Return the number of keys."""
76         return len(self.keys())
77
78     def keys(self):
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)]
81
82     def subkeys(self, key):
83         """Retrieve the subkeys for the specified key.
84
85         :param key: Key path.
86         :return: list with key names
87         """
88         data = self.tdb.get("%s\x00" % key)
89         if data is None:
90             return []
91         (num, ) = struct.unpack("<L", data[0:4])
92         keys = data[4:].split("\0")
93         assert keys[-1] == ""
94         keys.pop()
95         assert len(keys) == num
96         return keys
97
98     def values(self, key):
99         """Return a dictionary with the values set for a specific key.
100         
101         :param key: Key to retrieve values for.
102         :return: Dictionary with value names as key, tuple with type and 
103             data as value."""
104         data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
105         if data is None:
106             return {}
107         ret = {}
108         (num, ) = struct.unpack("<L", data[0:4])
109         data = data[4:]
110         for i in range(num):
111             # Value name
112             (name, data) = data.split("\0", 1)
113
114             (type, ) = struct.unpack("<L", data[0:4])
115             data = data[4:]
116             (value_len, ) = struct.unpack("<L", data[0:4])
117             data = data[4:]
118
119             ret[name] = (type, data[:value_len])
120             data = data[value_len:]
121
122         return ret
123
124
125 class PolicyDatabase(TdbDatabase):
126     """Samba 3 Account Policy database reader."""
127     def __init__(self, file):
128         """Open a policy database
129         
130         :param file: Path to the file to open.
131         """
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")
143
144         # FIXME: Read privileges as well
145
146
147 GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
148 GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
149
150 GROUP_PREFIX = "UNIXGROUP/"
151
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/"
157
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)
162
163     def groupsids(self):
164         """Retrieve the SIDs for the groups in this database.
165
166         :return: List with sids as strings.
167         """
168         for k in self.tdb.iterkeys():
169             if k.startswith(GROUP_PREFIX):
170                 yield k[len(GROUP_PREFIX):].rstrip("\0")
171
172     def get_group(self, sid):
173         """Retrieve the group mapping information for a particular group.
174
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.
178         """
179         data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid))
180         if data is None:
181             return data
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)
185
186     def aliases(self):
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")
191
192
193 # High water mark keys
194 IDMAP_HWM_GROUP = "GROUP HWM\0"
195 IDMAP_HWM_USER = "USER HWM\0"
196
197 IDMAP_GROUP_PREFIX = "GID "
198 IDMAP_USER_PREFIX = "UID "
199
200 # idmap version determines auto-conversion
201 IDMAP_VERSION_V2 = 2
202
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
207
208     def uids(self):
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"))
213
214     def gids(self):
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"))
219
220     def get_user_sid(self, uid):
221         """Retrieve the SID associated with a particular uid.
222
223         :param uid: UID to retrieve SID for.
224         :return: A SID or None if no mapping was found.
225         """
226         data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
227         if data is None:
228             return data
229         return data.rstrip("\0")
230
231     def get_group_sid(self, gid):
232         data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
233         if data is None:
234             return data
235         return data.rstrip("\0")
236
237     def get_user_hwm(self):
238         """Obtain the user high-water mark."""
239         return fetch_uint32(self.tdb, IDMAP_HWM_USER)
240
241     def get_group_hwm(self):
242         """Obtain the group high-water mark."""
243         return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
244
245
246 class SecretsDatabase(TdbDatabase):
247     """Samba 3 Secrets database reader."""
248     def get_auth_password(self):
249         return self.tdb.get("SECRETS/AUTH_PASSWORD")
250
251     def get_auth_domain(self):
252         return self.tdb.get("SECRETS/AUTH_DOMAIN")
253
254     def get_auth_user(self):
255         return self.tdb.get("SECRETS/AUTH_USER")
256
257     def get_domain_guid(self, host):
258         return self.tdb.get("SECRETS/DOMGUID/%s" % host)
259
260     def ldap_dns(self):
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")
264
265     def domains(self):
266         """Iterate over domains in this database.
267
268         :return: Iterator over the names of domains in this database.
269         """
270         for k in self.tdb.iterkeys():
271             if k.startswith("SECRETS/SID/"):
272                 yield k[len("SECRETS/SID/"):].rstrip("\0")
273
274     def get_ldap_bind_pw(self, host):
275         return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
276     
277     def get_afs_keyfile(self, host):
278         return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
279
280     def get_machine_sec_channel_type(self, host):
281         return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
282
283     def get_machine_last_change_time(self, host):
284         return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
285             
286     def get_machine_password(self, host):
287         return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
288
289     def get_machine_acc(self, host):
290         return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
291
292     def get_domtrust_acc(self, host):
293         return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
294
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")
299
300     def get_random_seed(self):
301         return self.tdb.get("INFO/random_seed")
302
303     def get_sid(self, host):
304         return self.tdb.get("SECRETS/SID/%s" % host.upper())
305
306
307 SHARE_DATABASE_VERSION_V1 = 1
308 SHARE_DATABASE_VERSION_V2 = 2
309
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)
314
315     def get_secdesc(self, name):
316         """Obtain the security descriptor on a particular share.
317         
318         :param name: Name of the share
319         """
320         secdesc = self.tdb.get("SECDESC/%s" % name)
321         # FIXME: Run ndr_pull_security_descriptor
322         return secdesc
323
324
325 class Shares(object):
326     """Container for share objects."""
327     def __init__(self, lp, shareinfo):
328         self.lp = lp
329         self.shareinfo = shareinfo
330
331     def __len__(self):
332         """Number of shares."""
333         return len(self.lp) - 1
334
335     def __iter__(self):
336         """Iterate over the share names."""
337         return self.lp.__iter__()
338
339
340 ACB_DISABLED = 0x00000001
341 ACB_HOMDIRREQ = 0x00000002
342 ACB_PWNOTREQ = 0x00000004
343 ACB_TEMPDUP = 0x00000008
344 ACB_NORMAL = 0x00000010
345 ACB_MNS = 0x00000020
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
359
360 acb_info_mapping = {
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.
372         ' ': 0
373         }
374
375 def decode_acb(text):
376     """Decode a ACB field.
377
378     :param text: ACB text
379     :return: integer with flags set.
380     """
381     assert not "[" in text and not "]" in text
382     ret = 0
383     for x in text:
384         ret |= acb_info_mapping[x]
385     return ret
386
387
388 class SAMUser(object):
389     """Samba 3 SAM User.
390     
391     :note: Unknown or unset fields are set to None.
392     """
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):
401         self.username = name
402         self.uid = uid
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
415         self.domain = domain
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
430         self.hours = hours
431         self.logon_divs = logon_divs
432
433     def __eq__(self, other): 
434         if not isinstance(other, SAMUser):
435             return False
436         return self.__dict__ == other.__dict__
437
438
439 class SmbpasswdFile(object):
440     """Samba 3 smbpasswd file reader."""
441     def __init__(self, file):
442         self.users = {}
443         f = open(file, 'r')
444         for l in f.readlines():
445             if len(l) == 0 or l[0] == "#":
446                 continue # Skip comments and blank lines
447             parts = l.split(":")
448             username = parts[0]
449             uid = int(parts[1])
450             acct_ctrl = 0
451             last_change_time = None
452             if parts[2] == "NO PASSWORD":
453                 acct_ctrl |= ACB_PWNOTREQ
454                 lm_password = None
455             elif parts[2][0] in ("*", "X"):
456                 # No password set
457                 lm_password = None
458             else:
459                 lm_password = parts[2]
460
461             if parts[3][0] in ("*", "X"):
462                 # No password set
463                 nt_password = None
464             else:
465                 nt_password = parts[3]
466
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
476
477             self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time)
478
479         f.close()
480
481     def __len__(self):
482         return len(self.users)
483
484     def __getitem__(self, name):
485         return self.users[name]
486
487     def __iter__(self):
488         return iter(self.users)
489
490     def close(self): # For consistency
491         pass
492
493
494 TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
495 TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
496 TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
497 TDBSAM_USER_PREFIX = "USER_"
498
499
500 class LdapSam(object):
501     """Samba 3 LDAP passdb backend reader."""
502     def __init__(self, url):
503         self.ldap_url = url
504
505
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)
511
512     def usernames(self):
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")
517
518     __iter__ = usernames
519     
520     def __getitem__(self, name):
521         data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)]
522         user = SAMUser(name)
523     
524         def unpack_string(data):
525             (length, ) = struct.unpack("<L", data[:4])
526             data = data[4:]
527             if length == 0:
528                 return (None, data)
529             return (data[:length].rstrip("\0"), data[length:])
530
531         def unpack_int32(data):
532             (value, ) = struct.unpack("<l", data[:4])
533             return (value, data[4:])
534
535         def unpack_uint32(data):
536             (value, ) = struct.unpack("<L", data[:4])
537             return (value, data[4:])
538
539         def unpack_uint16(data):
540             (value, ) = struct.unpack("<H", data[:2])
541             return (value, data[2:])
542
543         (logon_time, data) = unpack_int32(data)
544         (logoff_time, data) = unpack_int32(data)
545         (kickoff_time, data) = unpack_int32(data)
546
547         if self.version > 0:
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)
554
555         if logon_time != 0:
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
562
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)
575
576         (user.user_rid, data) = unpack_int32(data)
577         (user.group_rid, data) = unpack_int32(data)
578
579         (user.lm_password, data) = unpack_string(data)
580         (user.nt_password, data) = unpack_string(data)
581
582         if self.version > 1:
583             (user.nt_password_history, data) = unpack_string(data)
584
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)
589         user.hours = []
590         for entry in hours:
591             for i in range(8):
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
597         return user
598
599
600 def shellsplit(text):
601     """Very simple shell-like line splitting.
602     
603     :param text: Text to split.
604     :return: List with parts of the line as strings.
605     """
606     ret = list()
607     inquotes = False
608     current = ""
609     for c in text:
610         if c == "\"":
611             inquotes = not inquotes
612         elif c in ("\t", "\n", " ") and not inquotes:
613             ret.append(current)
614             current = ""
615         else:
616             current += c
617     if current != "":
618         ret.append(current)
619     return ret
620
621
622 class WinsDatabase(object):
623     """Samba 3 WINS database reader."""
624     def __init__(self, file):
625         self.entries = {}
626         f = open(file, 'r')
627         assert f.readline().rstrip("\n") == "VERSION 1 0"
628         for l in f.readlines():
629             if l[0] == "#": # skip comments
630                 continue
631             entries = shellsplit(l.rstrip("\n"))
632             name = entries[0]
633             ttl = int(entries[1])
634             i = 2
635             ips = []
636             while "." in entries[i]:
637                 ips.append(entries[i])
638                 i+=1
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)
642         f.close()
643
644     def __getitem__(self, name):
645         return self.entries[name]
646
647     def __len__(self):
648         return len(self.entries)
649
650     def __iter__(self):
651         return iter(self.entries)
652
653     def items(self):
654         """Return the entries in this WINS database."""
655         return self.entries.items()
656
657     def close(self): # for consistency
658         pass
659
660
661 class ParamFile(object):
662     """Simple smb.conf-compatible file parser
663
664     Does not use a parameter table, unlike the "normal".
665     """
666
667     def __init__(self, sections=None):
668         self._sections = sections or {}
669
670     def _sanitize_name(self, name):
671         return name.strip().lower().replace(" ","")
672
673     def __repr__(self):
674         return "ParamFile(%r)" % self._sections
675
676     def read(self, filename):
677         """Read a file.
678
679         :param filename: Path to the file
680         """
681         section = None
682         for i, l in enumerate(open(filename, 'r').xreadlines()):
683             l = l.strip()
684             if not l or l[0] == '#' or l[0] == ';':
685                 continue
686             if l[0] == "[" and l[-1] == "]":
687                 section = self._sanitize_name(l[1:-1])
688                 self._sections.setdefault(section, {})
689             elif "=" in l:
690                (k, v) = l.split("=", 1) 
691                self._sections[section][self._sanitize_name(k)] = v
692             else:
693                 raise Exception("Unable to parser line %d: %r" % (i+1,l))
694
695     def get(self, param, section=None):
696         """Return the value of a parameter.
697
698         :param param: Parameter name
699         :param section: Section name, defaults to "global"
700         :return: parameter value as string if found, None otherwise.
701         """
702         if section is None:
703             section = "global"
704         section = self._sanitize_name(section)
705         if not section in self._sections:
706             return None
707         param = self._sanitize_name(param)
708         if not param in self._sections[section]:
709             return None
710         return self._sections[section][param].strip()
711
712     def __getitem__(self, section):
713         return self._sections[section]
714
715     def get_section(self, section):
716         return self._sections.get(section)
717
718     def add_section(self, section):
719         self._sections[self._sanitize_name(section)] = {}
720
721     def set_string(self, name, value):
722         self._sections["global"][name] = value
723
724     def get_string(self, name):
725         return self._sections["global"].get(name)
726
727
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.
732
733         :param libdir: Library directory
734         :param smbconfpath: Path to the smb.conf file.
735         """
736         self.smbconfpath = smbconfpath
737         self.libdir = libdir
738         self.lp = ParamFile()
739         self.lp.read(self.smbconfpath)
740
741     def libdir_path(self, path):
742         if path[0] == "/" or path[0] == ".":
743             return path
744         return os.path.join(self.libdir, path)
745
746     def get_conf(self):
747         return self.lp
748
749     def get_sam_db(self):
750         lp = self.get_conf()
751         backends = (lp.get("passdb backend") or "").split(" ")
752         if ":" in backends[0]:
753             (name, location) = backends[0].split(":", 2)
754         else:
755             name = backends[0]
756             location = None
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"))
765         else:
766             raise NotImplementedError("unsupported passdb backend %s" % backends[0])
767
768     def get_policy_db(self):
769         return PolicyDatabase(self.libdir_path("account_policy.tdb"))
770
771     def get_registry(self):
772         return Registry(self.libdir_path("registry.tdb"))
773
774     def get_secrets_db(self):
775         return SecretsDatabase(self.libdir_path("secrets.tdb"))
776
777     def get_shareinfo_db(self):
778         return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
779
780     def get_idmap_db(self):
781         return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
782
783     def get_wins_db(self):
784         return WinsDatabase(self.libdir_path("wins.dat"))
785
786     def get_shares(self):
787         return Shares(self.get_conf(), self.get_shareinfo_db())
788
789     def get_groupmapping_db(self):
790         return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))