s3-param: Add python wrapper for s3 parameters
[amitay/samba.git] / source4 / scripting / python / samba / samba3 / __init__.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 (appending "2" if TDB2 enabled).
54         """
55         if tdb.__version__.startswith("2"):
56             self.tdb = tdb.Tdb(file + "2", flags=os.O_RDONLY)
57         else:
58             self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
59         self._check_version()
60
61     def _check_version(self):
62         pass
63
64     def close(self):
65         """Close resources associated with this object."""
66         self.tdb.close()
67
68
69 class Registry(TdbDatabase):
70     """Simple read-only support for reading the Samba3 registry.
71
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.
76     """
77     def __len__(self):
78         """Return the number of keys."""
79         return len(self.keys())
80
81     def keys(self):
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)]
84
85     def subkeys(self, key):
86         """Retrieve the subkeys for the specified key.
87
88         :param key: Key path.
89         :return: list with key names
90         """
91         data = self.tdb.get("%s\x00" % key)
92         if data is None:
93             return []
94         (num, ) = struct.unpack("<L", data[0:4])
95         keys = data[4:].split("\0")
96         assert keys[-1] == ""
97         keys.pop()
98         assert len(keys) == num
99         return keys
100
101     def values(self, key):
102         """Return a dictionary with the values set for a specific key.
103
104         :param key: Key to retrieve values for.
105         :return: Dictionary with value names as key, tuple with type and
106             data as value."""
107         data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
108         if data is None:
109             return {}
110         ret = {}
111         (num, ) = struct.unpack("<L", data[0:4])
112         data = data[4:]
113         for i in range(num):
114             # Value name
115             (name, data) = data.split("\0", 1)
116
117             (type, ) = struct.unpack("<L", data[0:4])
118             data = data[4:]
119             (value_len, ) = struct.unpack("<L", data[0:4])
120             data = data[4:]
121
122             ret[name] = (type, data[:value_len])
123             data = data[value_len:]
124
125         return ret
126
127
128 class PolicyDatabase(TdbDatabase):
129     """Samba 3 Account Policy database reader."""
130     def __init__(self, file):
131         """Open a policy database
132
133         :param file: Path to the file to open.
134         """
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")
146
147         # FIXME: Read privileges as well
148
149
150 GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
151 GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
152
153 GROUP_PREFIX = "UNIXGROUP/"
154
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/"
160
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)
165
166     def groupsids(self):
167         """Retrieve the SIDs for the groups in this database.
168
169         :return: List with sids as strings.
170         """
171         for k in self.tdb.iterkeys():
172             if k.startswith(GROUP_PREFIX):
173                 yield k[len(GROUP_PREFIX):].rstrip("\0")
174
175     def get_group(self, sid):
176         """Retrieve the group mapping information for a particular group.
177
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.
181         """
182         data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid))
183         if data is None:
184             return data
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)
188
189     def aliases(self):
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")
194
195
196 # High water mark keys
197 IDMAP_HWM_GROUP = "GROUP HWM\0"
198 IDMAP_HWM_USER = "USER HWM\0"
199
200 IDMAP_GROUP_PREFIX = "GID "
201 IDMAP_USER_PREFIX = "UID "
202
203 # idmap version determines auto-conversion
204 IDMAP_VERSION_V2 = 2
205
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
210
211     def uids(self):
212         """Retrieve a list of all uids in this database."""
213         for k in self.tdb.iterkeys():
214             if k.startswith(IDMAP_USER_PREFIX):
215                 yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
216
217     def gids(self):
218         """Retrieve a list of all gids in this database."""
219         for k in self.tdb.iterkeys():
220             if k.startswith(IDMAP_GROUP_PREFIX):
221                 yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
222
223     def get_user_sid(self, uid):
224         """Retrieve the SID associated with a particular uid.
225
226         :param uid: UID to retrieve SID for.
227         :return: A SID or None if no mapping was found.
228         """
229         data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
230         if data is None:
231             return data
232         return data.rstrip("\0")
233
234     def get_group_sid(self, gid):
235         data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
236         if data is None:
237             return data
238         return data.rstrip("\0")
239
240     def get_user_hwm(self):
241         """Obtain the user high-water mark."""
242         return fetch_uint32(self.tdb, IDMAP_HWM_USER)
243
244     def get_group_hwm(self):
245         """Obtain the group high-water mark."""
246         return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
247
248
249 class SecretsDatabase(TdbDatabase):
250     """Samba 3 Secrets database reader."""
251     def get_auth_password(self):
252         return self.tdb.get("SECRETS/AUTH_PASSWORD")
253
254     def get_auth_domain(self):
255         return self.tdb.get("SECRETS/AUTH_DOMAIN")
256
257     def get_auth_user(self):
258         return self.tdb.get("SECRETS/AUTH_USER")
259
260     def get_domain_guid(self, host):
261         return self.tdb.get("SECRETS/DOMGUID/%s" % host)
262
263     def ldap_dns(self):
264         for k in self.tdb.iterkeys():
265             if k.startswith("SECRETS/LDAP_BIND_PW/"):
266                 yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
267
268     def domains(self):
269         """Iterate over domains in this database.
270
271         :return: Iterator over the names of domains in this database.
272         """
273         for k in self.tdb.iterkeys():
274             if k.startswith("SECRETS/SID/"):
275                 yield k[len("SECRETS/SID/"):].rstrip("\0")
276
277     def get_ldap_bind_pw(self, host):
278         return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
279
280     def get_afs_keyfile(self, host):
281         return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
282
283     def get_machine_sec_channel_type(self, host):
284         return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
285
286     def get_machine_last_change_time(self, host):
287         return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
288
289     def get_machine_password(self, host):
290         return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
291
292     def get_machine_acc(self, host):
293         return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
294
295     def get_domtrust_acc(self, host):
296         return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
297
298     def trusted_domains(self):
299         for k in self.tdb.iterkeys():
300             if k.startswith("SECRETS/$DOMTRUST.ACC/"):
301                 yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
302
303     def get_random_seed(self):
304         return self.tdb.get("INFO/random_seed")
305
306     def get_sid(self, host):
307         return self.tdb.get("SECRETS/SID/%s" % host.upper())
308
309
310 SHARE_DATABASE_VERSION_V1 = 1
311 SHARE_DATABASE_VERSION_V2 = 2
312
313 class ShareInfoDatabase(TdbDatabase):
314     """Samba 3 Share Info database reader."""
315     def _check_version(self):
316         assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
317
318     def get_secdesc(self, name):
319         """Obtain the security descriptor on a particular share.
320
321         :param name: Name of the share
322         """
323         secdesc = self.tdb.get("SECDESC/%s" % name)
324         # FIXME: Run ndr_pull_security_descriptor
325         return secdesc
326
327
328 class Shares(object):
329     """Container for share objects."""
330     def __init__(self, lp, shareinfo):
331         self.lp = lp
332         self.shareinfo = shareinfo
333
334     def __len__(self):
335         """Number of shares."""
336         return len(self.lp) - 1
337
338     def __iter__(self):
339         """Iterate over the share names."""
340         return self.lp.__iter__()
341
342
343 ACB_DISABLED = 0x00000001
344 ACB_HOMDIRREQ = 0x00000002
345 ACB_PWNOTREQ = 0x00000004
346 ACB_TEMPDUP = 0x00000008
347 ACB_NORMAL = 0x00000010
348 ACB_MNS = 0x00000020
349 ACB_DOMTRUST = 0x00000040
350 ACB_WSTRUST = 0x00000080
351 ACB_SVRTRUST = 0x00000100
352 ACB_PWNOEXP = 0x00000200
353 ACB_AUTOLOCK = 0x00000400
354 ACB_ENC_TXT_PWD_ALLOWED = 0x00000800
355 ACB_SMARTCARD_REQUIRED = 0x00001000
356 ACB_TRUSTED_FOR_DELEGATION = 0x00002000
357 ACB_NOT_DELEGATED = 0x00004000
358 ACB_USE_DES_KEY_ONLY = 0x00008000
359 ACB_DONT_REQUIRE_PREAUTH = 0x00010000
360 ACB_PW_EXPIRED = 0x00020000
361 ACB_NO_AUTH_DATA_REQD = 0x00080000
362
363 acb_info_mapping = {
364         'N': ACB_PWNOTREQ,  # 'N'o password.
365         'D': ACB_DISABLED,  # 'D'isabled.
366         'H': ACB_HOMDIRREQ, # 'H'omedir required.
367         'T': ACB_TEMPDUP,   # 'T'emp account.
368         'U': ACB_NORMAL,    # 'U'ser account (normal).
369         'M': ACB_MNS,       # 'M'NS logon user account. What is this ?
370         'W': ACB_WSTRUST,   # 'W'orkstation account.
371         'S': ACB_SVRTRUST,  # 'S'erver account.
372         'L': ACB_AUTOLOCK,  # 'L'ocked account.
373         'X': ACB_PWNOEXP,   # No 'X'piry on password
374         'I': ACB_DOMTRUST,  # 'I'nterdomain trust account.
375         ' ': 0
376         }
377
378 def decode_acb(text):
379     """Decode a ACB field.
380
381     :param text: ACB text
382     :return: integer with flags set.
383     """
384     assert not "[" in text and not "]" in text
385     ret = 0
386     for x in text:
387         ret |= acb_info_mapping[x]
388     return ret
389
390
391 class SAMUser(object):
392     """Samba 3 SAM User.
393
394     :note: Unknown or unset fields are set to None.
395     """
396     def __init__(self, name, uid=None, lm_password=None, nt_password=None, acct_ctrl=None,
397                  last_change_time=None, nt_username=None, fullname=None, logon_time=None, logoff_time=None,
398                  acct_desc=None, group_rid=None, bad_password_count=None, logon_count=None,
399                  domain=None, dir_drive=None, munged_dial=None, homedir=None, logon_script=None,
400                  profile_path=None, workstations=None, kickoff_time=None, bad_password_time=None,
401                  pass_last_set_time=None, pass_can_change_time=None, pass_must_change_time=None,
402                  user_rid=None, unknown_6=None, nt_password_history=None,
403                  unknown_str=None, hours=None, logon_divs=None):
404         self.username = name
405         self.uid = uid
406         self.lm_password = lm_password
407         self.nt_password = nt_password
408         self.acct_ctrl = acct_ctrl
409         self.pass_last_set_time = last_change_time
410         self.nt_username = nt_username
411         self.fullname = fullname
412         self.logon_time = logon_time
413         self.logoff_time = logoff_time
414         self.acct_desc = acct_desc
415         self.group_rid = group_rid
416         self.bad_password_count = bad_password_count
417         self.logon_count = logon_count
418         self.domain = domain
419         self.dir_drive = dir_drive
420         self.munged_dial = munged_dial
421         self.homedir = homedir
422         self.logon_script = logon_script
423         self.profile_path = profile_path
424         self.workstations = workstations
425         self.kickoff_time = kickoff_time
426         self.bad_password_time = bad_password_time
427         self.pass_can_change_time = pass_can_change_time
428         self.pass_must_change_time = pass_must_change_time
429         self.user_rid = user_rid
430         self.unknown_6 = unknown_6
431         self.nt_password_history = nt_password_history
432         self.unknown_str = unknown_str
433         self.hours = hours
434         self.logon_divs = logon_divs
435
436     def __eq__(self, other):
437         if not isinstance(other, SAMUser):
438             return False
439         return self.__dict__ == other.__dict__
440
441
442 class SmbpasswdFile(object):
443     """Samba 3 smbpasswd file reader."""
444     def __init__(self, file):
445         self.users = {}
446         f = open(file, 'r')
447         for l in f.readlines():
448             if len(l) == 0 or l[0] == "#":
449                 continue # Skip comments and blank lines
450             parts = l.split(":")
451             username = parts[0]
452             uid = int(parts[1])
453             acct_ctrl = 0
454             last_change_time = None
455             if parts[2] == "NO PASSWORD":
456                 acct_ctrl |= ACB_PWNOTREQ
457                 lm_password = None
458             elif parts[2][0] in ("*", "X"):
459                 # No password set
460                 lm_password = None
461             else:
462                 lm_password = parts[2]
463
464             if parts[3][0] in ("*", "X"):
465                 # No password set
466                 nt_password = None
467             else:
468                 nt_password = parts[3]
469
470             if parts[4][0] == '[':
471                 assert "]" in parts[4]
472                 acct_ctrl |= decode_acb(parts[4][1:-1])
473                 if parts[5].startswith("LCT-"):
474                     last_change_time = int(parts[5][len("LCT-"):], 16)
475             else: # old style file
476                 if username[-1] == "$":
477                     acct_ctrl &= ~ACB_NORMAL
478                     acct_ctrl |= ACB_WSTRUST
479
480             self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time)
481
482         f.close()
483
484     def __len__(self):
485         return len(self.users)
486
487     def __getitem__(self, name):
488         return self.users[name]
489
490     def __iter__(self):
491         return iter(self.users)
492
493     def close(self): # For consistency
494         pass
495
496
497 TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
498 TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
499 TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
500 TDBSAM_USER_PREFIX = "USER_"
501
502
503 class LdapSam(object):
504     """Samba 3 LDAP passdb backend reader."""
505     def __init__(self, url):
506         self.ldap_url = url
507
508
509 class TdbSam(TdbDatabase):
510     """Samba 3 TDB passdb backend reader."""
511     def _check_version(self):
512         self.version = fetch_uint32(self.tdb, "INFO/version\0") or 0
513         assert self.version in (0, 1, 2)
514
515     def usernames(self):
516         """Iterate over the usernames in this Tdb database."""
517         for k in self.tdb.iterkeys():
518             if k.startswith(TDBSAM_USER_PREFIX):
519                 yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0")
520
521     __iter__ = usernames
522
523     def __getitem__(self, name):
524         data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)]
525         user = SAMUser(name)
526
527         def unpack_string(data):
528             (length, ) = struct.unpack("<L", data[:4])
529             data = data[4:]
530             if length == 0:
531                 return (None, data)
532             return (data[:length].rstrip("\0"), data[length:])
533
534         def unpack_int32(data):
535             (value, ) = struct.unpack("<l", data[:4])
536             return (value, data[4:])
537
538         def unpack_uint32(data):
539             (value, ) = struct.unpack("<L", data[:4])
540             return (value, data[4:])
541
542         def unpack_uint16(data):
543             (value, ) = struct.unpack("<H", data[:2])
544             return (value, data[2:])
545
546         (logon_time, data) = unpack_int32(data)
547         (logoff_time, data) = unpack_int32(data)
548         (kickoff_time, data) = unpack_int32(data)
549
550         if self.version > 0:
551             (bad_password_time, data) = unpack_int32(data)
552             if bad_password_time != 0:
553                 user.bad_password_time = bad_password_time
554         (pass_last_set_time, data) = unpack_int32(data)
555         (pass_can_change_time, data) = unpack_int32(data)
556         (pass_must_change_time, data) = unpack_int32(data)
557
558         if logon_time != 0:
559             user.logon_time = logon_time
560         user.logoff_time = logoff_time
561         user.kickoff_time = kickoff_time
562         if pass_last_set_time != 0:
563             user.pass_last_set_time = pass_last_set_time
564         user.pass_can_change_time = pass_can_change_time
565
566         (user.username, data) = unpack_string(data)
567         (user.domain, data) = unpack_string(data)
568         (user.nt_username, data) = unpack_string(data)
569         (user.fullname, data) = unpack_string(data)
570         (user.homedir, data) = unpack_string(data)
571         (user.dir_drive, data) = unpack_string(data)
572         (user.logon_script, data) = unpack_string(data)
573         (user.profile_path, data) = unpack_string(data)
574         (user.acct_desc, data) = unpack_string(data)
575         (user.workstations, data) = unpack_string(data)
576         (user.unknown_str, data) = unpack_string(data)
577         (user.munged_dial, data) = unpack_string(data)
578
579         (user.user_rid, data) = unpack_int32(data)
580         (user.group_rid, data) = unpack_int32(data)
581
582         (user.lm_password, data) = unpack_string(data)
583         (user.nt_password, data) = unpack_string(data)
584
585         if self.version > 1:
586             (user.nt_password_history, data) = unpack_string(data)
587
588         (user.acct_ctrl, data) = unpack_uint16(data)
589         (_, data) = unpack_uint32(data) # remove_me field
590         (user.logon_divs, data) = unpack_uint16(data)
591         (hours, data) = unpack_string(data)
592         user.hours = []
593         for entry in hours:
594             for i in range(8):
595                 user.hours.append(ord(entry) & (2 ** i) == (2 ** i))
596         (user.bad_password_count, data) = unpack_uint16(data)
597         (user.logon_count, data) = unpack_uint16(data)
598         (user.unknown_6, data) = unpack_uint32(data)
599         assert len(data) == 0
600         return user
601
602
603 def shellsplit(text):
604     """Very simple shell-like line splitting.
605
606     :param text: Text to split.
607     :return: List with parts of the line as strings.
608     """
609     ret = list()
610     inquotes = False
611     current = ""
612     for c in text:
613         if c == "\"":
614             inquotes = not inquotes
615         elif c in ("\t", "\n", " ") and not inquotes:
616             ret.append(current)
617             current = ""
618         else:
619             current += c
620     if current != "":
621         ret.append(current)
622     return ret
623
624
625 class WinsDatabase(object):
626     """Samba 3 WINS database reader."""
627     def __init__(self, file):
628         self.entries = {}
629         f = open(file, 'r')
630         assert f.readline().rstrip("\n") == "VERSION 1 0"
631         for l in f.readlines():
632             if l[0] == "#": # skip comments
633                 continue
634             entries = shellsplit(l.rstrip("\n"))
635             name = entries[0]
636             ttl = int(entries[1])
637             i = 2
638             ips = []
639             while "." in entries[i]:
640                 ips.append(entries[i])
641                 i+=1
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)
645         f.close()
646
647     def __getitem__(self, name):
648         return self.entries[name]
649
650     def __len__(self):
651         return len(self.entries)
652
653     def __iter__(self):
654         return iter(self.entries)
655
656     def items(self):
657         """Return the entries in this WINS database."""
658         return self.entries.items()
659
660     def close(self): # for consistency
661         pass
662
663
664 class ParamFile(object):
665     """Simple smb.conf-compatible file parser
666
667     Does not use a parameter table, unlike the "normal".
668     """
669
670     def __init__(self, sections=None):
671         self._sections = sections or {}
672
673     def _sanitize_name(self, name):
674         return name.strip().lower().replace(" ","")
675
676     def __repr__(self):
677         return "ParamFile(%r)" % self._sections
678
679     def read(self, filename):
680         """Read a file.
681
682         :param filename: Path to the file
683         """
684         section = None
685         for i, l in enumerate(open(filename, 'r').xreadlines()):
686             l = l.strip()
687             if not l or l[0] == '#' or l[0] == ';':
688                 continue
689             if l[0] == "[" and l[-1] == "]":
690                 section = self._sanitize_name(l[1:-1])
691                 self._sections.setdefault(section, {})
692             elif "=" in l:
693                (k, v) = l.split("=", 1)
694                self._sections[section][self._sanitize_name(k)] = v
695             else:
696                 raise Exception("Unable to parser line %d: %r" % (i+1,l))
697
698     def get(self, param, section=None):
699         """Return the value of a parameter.
700
701         :param param: Parameter name
702         :param section: Section name, defaults to "global"
703         :return: parameter value as string if found, None otherwise.
704         """
705         if section is None:
706             section = "global"
707         section = self._sanitize_name(section)
708         if not section in self._sections:
709             return None
710         param = self._sanitize_name(param)
711         if not param in self._sections[section]:
712             return None
713         return self._sections[section][param].strip()
714
715     def __getitem__(self, section):
716         return self._sections[section]
717
718     def get_section(self, section):
719         return self._sections.get(section)
720
721     def add_section(self, section):
722         self._sections[self._sanitize_name(section)] = {}
723
724     def set_string(self, name, value):
725         self._sections["global"][name] = value
726
727     def get_string(self, name):
728         return self._sections["global"].get(name)
729
730
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.
735
736         :param libdir: Library directory
737         :param smbconfpath: Path to the smb.conf file.
738         """
739         self.smbconfpath = smbconfpath
740         self.libdir = libdir
741         self.lp = ParamFile()
742         self.lp.read(self.smbconfpath)
743
744     def libdir_path(self, path):
745         if path[0] == "/" or path[0] == ".":
746             return path
747         return os.path.join(self.libdir, path)
748
749     def get_conf(self):
750         return self.lp
751
752     def get_sam_db(self):
753         lp = self.get_conf()
754         backends = (lp.get("passdb backend") or "").split(" ")
755         if ":" in backends[0]:
756             (name, location) = backends[0].split(":", 2)
757         else:
758             name = backends[0]
759             location = None
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"))
768         else:
769             raise NotImplementedError("unsupported passdb backend %s" % backends[0])
770
771     def get_policy_db(self):
772         return PolicyDatabase(self.libdir_path("account_policy.tdb"))
773
774     def get_registry(self):
775         return Registry(self.libdir_path("registry.tdb"))
776
777     def get_secrets_db(self):
778         return SecretsDatabase(self.libdir_path("secrets.tdb"))
779
780     def get_shareinfo_db(self):
781         return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
782
783     def get_idmap_db(self):
784         return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
785
786     def get_wins_db(self):
787         return WinsDatabase(self.libdir_path("wins.dat"))
788
789     def get_shares(self):
790         return Shares(self.get_conf(), self.get_shareinfo_db())
791
792     def get_groupmapping_db(self):
793         return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))