s3-kerberos: add a missing reference to authdata headers.
[ira/wip.git] / source4 / scripting / python / samba / samba3.py
1 #!/usr/bin/python
2
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
5 #   
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #   
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #   
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 """Support for reading Samba 3 data files."""
21
22 __docformat__ = "restructuredText"
23
24 REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL"
25 REGISTRY_DB_VERSION = 1
26
27 import os
28 import struct
29 import tdb
30
31
32 def fetch_uint32(tdb, key):
33     try:
34         data = tdb[key]
35     except KeyError:
36         return None
37     assert len(data) == 4
38     return struct.unpack("<L", data)[0]
39
40
41 def fetch_int32(tdb, key):
42     try:
43         data = tdb[key]
44     except KeyError:
45         return None
46     assert len(data) == 4
47     return struct.unpack("<l", data)[0]
48
49
50 class TdbDatabase(object):
51     """Simple Samba 3 TDB database reader."""
52     def __init__(self, file):
53         """Open a file.
54
55         :param file: Path of the file to open.
56         """
57         self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
58         self._check_version()
59
60     def _check_version(self):
61         pass
62
63     def close(self):
64         """Close resources associated with this object."""
65         self.tdb.close()
66
67
68 class Registry(TdbDatabase):
69     """Simple read-only support for reading the Samba3 registry.
70     
71     :note: This object uses the same syntax for registry key paths as 
72         Samba 3. This particular format uses forward slashes for key path 
73         separators and abbreviations for the predefined key names. 
74         e.g.: HKLM/Software/Bar.
75     """
76     def __len__(self):
77         """Return the number of keys."""
78         return len(self.keys())
79
80     def keys(self):
81         """Return list with all the keys."""
82         return [k.rstrip("\x00") for k in self.tdb.iterkeys() if not k.startswith(REGISTRY_VALUE_PREFIX)]
83
84     def subkeys(self, key):
85         """Retrieve the subkeys for the specified key.
86
87         :param key: Key path.
88         :return: list with key names
89         """
90         data = self.tdb.get("%s\x00" % key)
91         if data is None:
92             return []
93         (num, ) = struct.unpack("<L", data[0:4])
94         keys = data[4:].split("\0")
95         assert keys[-1] == ""
96         keys.pop()
97         assert len(keys) == num
98         return keys
99
100     def values(self, key):
101         """Return a dictionary with the values set for a specific key.
102         
103         :param key: Key to retrieve values for.
104         :return: Dictionary with value names as key, tuple with type and 
105             data as value."""
106         data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
107         if data is None:
108             return {}
109         ret = {}
110         (num, ) = struct.unpack("<L", data[0:4])
111         data = data[4:]
112         for i in range(num):
113             # Value name
114             (name, data) = data.split("\0", 1)
115
116             (type, ) = struct.unpack("<L", data[0:4])
117             data = data[4:]
118             (value_len, ) = struct.unpack("<L", data[0:4])
119             data = data[4:]
120
121             ret[name] = (type, data[:value_len])
122             data = data[value_len:]
123
124         return ret
125
126
127 class PolicyDatabase(TdbDatabase):
128     """Samba 3 Account Policy database reader."""
129     def __init__(self, file):
130         """Open a policy database
131         
132         :param file: Path to the file to open.
133         """
134         super(PolicyDatabase, self).__init__(file)
135         self.min_password_length = fetch_uint32(self.tdb, "min password length\x00")
136         self.password_history = fetch_uint32(self.tdb, "password history\x00")
137         self.user_must_logon_to_change_password = fetch_uint32(self.tdb, "user must logon to change pasword\x00")
138         self.maximum_password_age = fetch_uint32(self.tdb, "maximum password age\x00")
139         self.minimum_password_age = fetch_uint32(self.tdb, "minimum password age\x00")
140         self.lockout_duration = fetch_uint32(self.tdb, "lockout duration\x00")
141         self.reset_count_minutes = fetch_uint32(self.tdb, "reset count minutes\x00")
142         self.bad_lockout_minutes = fetch_uint32(self.tdb, "bad lockout minutes\x00")
143         self.disconnect_time = fetch_int32(self.tdb, "disconnect time\x00")
144         self.refuse_machine_password_change = fetch_uint32(self.tdb, "refuse machine password change\x00")
145
146         # FIXME: Read privileges as well
147
148
149 GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
150 GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
151
152 GROUP_PREFIX = "UNIXGROUP/"
153
154 # Alias memberships are stored reverse, as memberships. The performance
155 # critical operation is to determine the aliases a SID is member of, not
156 # listing alias members. So we store a list of alias SIDs a SID is member of
157 # hanging of the member as key.
158 MEMBEROF_PREFIX = "MEMBEROF/"
159
160 class GroupMappingDatabase(TdbDatabase):
161     """Samba 3 group mapping database reader."""
162     def _check_version(self):
163         assert fetch_int32(self.tdb, "INFO/version\x00") in (GROUPDB_DATABASE_VERSION_V1, GROUPDB_DATABASE_VERSION_V2)
164
165     def groupsids(self):
166         """Retrieve the SIDs for the groups in this database.
167
168         :return: List with sids as strings.
169         """
170         for k in self.tdb.iterkeys():
171             if k.startswith(GROUP_PREFIX):
172                 yield k[len(GROUP_PREFIX):].rstrip("\0")
173
174     def get_group(self, sid):
175         """Retrieve the group mapping information for a particular group.
176
177         :param sid: SID of the group
178         :return: None if the group can not be found, otherwise 
179             a tuple with gid, sid_name_use, the NT name and comment.
180         """
181         data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid))
182         if data is None:
183             return data
184         (gid, sid_name_use) = struct.unpack("<lL", data[0:8])
185         (nt_name, comment, _) = data[8:].split("\0")
186         return (gid, sid_name_use, nt_name, comment)
187
188     def aliases(self):
189         """Retrieve the aliases in this database."""
190         for k in self.tdb.iterkeys():
191             if k.startswith(MEMBEROF_PREFIX):
192                 yield k[len(MEMBEROF_PREFIX):].rstrip("\0")
193
194
195 # High water mark keys
196 IDMAP_HWM_GROUP = "GROUP HWM\0"
197 IDMAP_HWM_USER = "USER HWM\0"
198
199 IDMAP_GROUP_PREFIX = "GID "
200 IDMAP_USER_PREFIX = "UID "
201
202 # idmap version determines auto-conversion
203 IDMAP_VERSION_V2 = 2
204
205 class IdmapDatabase(TdbDatabase):
206     """Samba 3 ID map database reader."""
207     def _check_version(self):
208         assert fetch_int32(self.tdb, "IDMAP_VERSION\0") == IDMAP_VERSION_V2
209
210     def uids(self):
211         """Retrieve a list of all uids in this database."""
212         for k in self.tdb.iterkeys():
213             if k.startswith(IDMAP_USER_PREFIX):
214                 yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
215
216     def gids(self):
217         """Retrieve a list of all gids in this database."""
218         for k in self.tdb.iterkeys():
219             if k.startswith(IDMAP_GROUP_PREFIX):
220                 yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
221
222     def get_user_sid(self, uid):
223         """Retrieve the SID associated with a particular uid.
224
225         :param uid: UID to retrieve SID for.
226         :return: A SID or None if no mapping was found.
227         """
228         data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
229         if data is None:
230             return data
231         return data.rstrip("\0")
232
233     def get_group_sid(self, gid):
234         data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
235         if data is None:
236             return data
237         return data.rstrip("\0")
238
239     def get_user_hwm(self):
240         """Obtain the user high-water mark."""
241         return fetch_uint32(self.tdb, IDMAP_HWM_USER)
242
243     def get_group_hwm(self):
244         """Obtain the group high-water mark."""
245         return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
246
247
248 class SecretsDatabase(TdbDatabase):
249     """Samba 3 Secrets database reader."""
250     def get_auth_password(self):
251         return self.tdb.get("SECRETS/AUTH_PASSWORD")
252
253     def get_auth_domain(self):
254         return self.tdb.get("SECRETS/AUTH_DOMAIN")
255
256     def get_auth_user(self):
257         return self.tdb.get("SECRETS/AUTH_USER")
258
259     def get_domain_guid(self, host):
260         return self.tdb.get("SECRETS/DOMGUID/%s" % host)
261
262     def ldap_dns(self):
263         for k in self.tdb.iterkeys():
264             if k.startswith("SECRETS/LDAP_BIND_PW/"):
265                 yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
266
267     def domains(self):
268         """Iterate over domains in this database.
269
270         :return: Iterator over the names of domains in this database.
271         """
272         for k in self.tdb.iterkeys():
273             if k.startswith("SECRETS/SID/"):
274                 yield k[len("SECRETS/SID/"):].rstrip("\0")
275
276     def get_ldap_bind_pw(self, host):
277         return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
278     
279     def get_afs_keyfile(self, host):
280         return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
281
282     def get_machine_sec_channel_type(self, host):
283         return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
284
285     def get_machine_last_change_time(self, host):
286         return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
287             
288     def get_machine_password(self, host):
289         return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
290
291     def get_machine_acc(self, host):
292         return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
293
294     def get_domtrust_acc(self, host):
295         return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
296
297     def trusted_domains(self):
298         for k in self.tdb.iterkeys():
299             if k.startswith("SECRETS/$DOMTRUST.ACC/"):
300                 yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
301
302     def get_random_seed(self):
303         return self.tdb.get("INFO/random_seed")
304
305     def get_sid(self, host):
306         return self.tdb.get("SECRETS/SID/%s" % host.upper())
307
308
309 SHARE_DATABASE_VERSION_V1 = 1
310 SHARE_DATABASE_VERSION_V2 = 2
311
312 class ShareInfoDatabase(TdbDatabase):
313     """Samba 3 Share Info database reader."""
314     def _check_version(self):
315         assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
316
317     def get_secdesc(self, name):
318         """Obtain the security descriptor on a particular share.
319         
320         :param name: Name of the share
321         """
322         secdesc = self.tdb.get("SECDESC/%s" % name)
323         # FIXME: Run ndr_pull_security_descriptor
324         return secdesc
325
326
327 class Shares(object):
328     """Container for share objects."""
329     def __init__(self, lp, shareinfo):
330         self.lp = lp
331         self.shareinfo = shareinfo
332
333     def __len__(self):
334         """Number of shares."""
335         return len(self.lp) - 1
336
337     def __iter__(self):
338         """Iterate over the share names."""
339         return self.lp.__iter__()
340
341
342 ACB_DISABLED = 0x00000001
343 ACB_HOMDIRREQ = 0x00000002
344 ACB_PWNOTREQ = 0x00000004
345 ACB_TEMPDUP = 0x00000008
346 ACB_NORMAL = 0x00000010
347 ACB_MNS = 0x00000020
348 ACB_DOMTRUST = 0x00000040
349 ACB_WSTRUST = 0x00000080
350 ACB_SVRTRUST = 0x00000100
351 ACB_PWNOEXP = 0x00000200
352 ACB_AUTOLOCK = 0x00000400
353 ACB_ENC_TXT_PWD_ALLOWED = 0x00000800
354 ACB_SMARTCARD_REQUIRED = 0x00001000
355 ACB_TRUSTED_FOR_DELEGATION = 0x00002000
356 ACB_NOT_DELEGATED = 0x00004000
357 ACB_USE_DES_KEY_ONLY = 0x00008000
358 ACB_DONT_REQUIRE_PREAUTH = 0x00010000
359 ACB_PW_EXPIRED = 0x00020000
360 ACB_NO_AUTH_DATA_REQD = 0x00080000
361
362 acb_info_mapping = {
363         'N': ACB_PWNOTREQ,  # 'N'o password.
364         'D': ACB_DISABLED,  # 'D'isabled.
365         'H': ACB_HOMDIRREQ, # 'H'omedir required.
366         'T': ACB_TEMPDUP,   # 'T'emp account.
367         'U': ACB_NORMAL,    # 'U'ser account (normal).
368         'M': ACB_MNS,       # 'M'NS logon user account. What is this ?
369         'W': ACB_WSTRUST,   # 'W'orkstation account.
370         'S': ACB_SVRTRUST,  # 'S'erver account. 
371         'L': ACB_AUTOLOCK,  # 'L'ocked account.
372         'X': ACB_PWNOEXP,   # No 'X'piry on password
373         'I': ACB_DOMTRUST,  # 'I'nterdomain trust account.
374         ' ': 0
375         }
376
377 def decode_acb(text):
378     """Decode a ACB field.
379
380     :param text: ACB text
381     :return: integer with flags set.
382     """
383     assert not "[" in text and not "]" in text
384     ret = 0
385     for x in text:
386         ret |= acb_info_mapping[x]
387     return ret
388
389
390 class SAMUser(object):
391     """Samba 3 SAM User.
392     
393     :note: Unknown or unset fields are set to None.
394     """
395     def __init__(self, name, uid=None, lm_password=None, nt_password=None, acct_ctrl=None, 
396                  last_change_time=None, nt_username=None, fullname=None, logon_time=None, logoff_time=None,
397                  acct_desc=None, group_rid=None, bad_password_count=None, logon_count=None,
398                  domain=None, dir_drive=None, munged_dial=None, homedir=None, logon_script=None,
399                  profile_path=None, workstations=None, kickoff_time=None, bad_password_time=None,
400                  pass_last_set_time=None, pass_can_change_time=None, pass_must_change_time=None,
401                  user_rid=None, unknown_6=None, nt_password_history=None,
402                  unknown_str=None, hours=None, logon_divs=None):
403         self.username = name
404         self.uid = uid
405         self.lm_password = lm_password
406         self.nt_password = nt_password
407         self.acct_ctrl = acct_ctrl
408         self.pass_last_set_time = last_change_time
409         self.nt_username = nt_username
410         self.fullname = fullname
411         self.logon_time = logon_time
412         self.logoff_time = logoff_time
413         self.acct_desc = acct_desc
414         self.group_rid = group_rid
415         self.bad_password_count = bad_password_count
416         self.logon_count = logon_count
417         self.domain = domain
418         self.dir_drive = dir_drive
419         self.munged_dial = munged_dial
420         self.homedir = homedir
421         self.logon_script = logon_script
422         self.profile_path = profile_path
423         self.workstations = workstations
424         self.kickoff_time = kickoff_time
425         self.bad_password_time = bad_password_time
426         self.pass_can_change_time = pass_can_change_time
427         self.pass_must_change_time = pass_must_change_time
428         self.user_rid = user_rid
429         self.unknown_6 = unknown_6
430         self.nt_password_history = nt_password_history
431         self.unknown_str = unknown_str
432         self.hours = hours
433         self.logon_divs = logon_divs
434
435     def __eq__(self, other): 
436         if not isinstance(other, SAMUser):
437             return False
438         return self.__dict__ == other.__dict__
439
440
441 class SmbpasswdFile(object):
442     """Samba 3 smbpasswd file reader."""
443     def __init__(self, file):
444         self.users = {}
445         f = open(file, 'r')
446         for l in f.readlines():
447             if len(l) == 0 or l[0] == "#":
448                 continue # Skip comments and blank lines
449             parts = l.split(":")
450             username = parts[0]
451             uid = int(parts[1])
452             acct_ctrl = 0
453             last_change_time = None
454             if parts[2] == "NO PASSWORD":
455                 acct_ctrl |= ACB_PWNOTREQ
456                 lm_password = None
457             elif parts[2][0] in ("*", "X"):
458                 # No password set
459                 lm_password = None
460             else:
461                 lm_password = parts[2]
462
463             if parts[3][0] in ("*", "X"):
464                 # No password set
465                 nt_password = None
466             else:
467                 nt_password = parts[3]
468
469             if parts[4][0] == '[':
470                 assert "]" in parts[4]
471                 acct_ctrl |= decode_acb(parts[4][1:-1])
472                 if parts[5].startswith("LCT-"):
473                     last_change_time = int(parts[5][len("LCT-"):], 16)
474             else: # old style file
475                 if username[-1] == "$":
476                     acct_ctrl &= ~ACB_NORMAL
477                     acct_ctrl |= ACB_WSTRUST
478
479             self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time)
480
481         f.close()
482
483     def __len__(self):
484         return len(self.users)
485
486     def __getitem__(self, name):
487         return self.users[name]
488
489     def __iter__(self):
490         return iter(self.users)
491
492     def close(self): # For consistency
493         pass
494
495
496 TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
497 TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
498 TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
499 TDBSAM_USER_PREFIX = "USER_"
500
501
502 class LdapSam(object):
503     """Samba 3 LDAP passdb backend reader."""
504     def __init__(self, url):
505         self.ldap_url = url
506
507
508 class TdbSam(TdbDatabase):
509     """Samba 3 TDB passdb backend reader."""
510     def _check_version(self):
511         self.version = fetch_uint32(self.tdb, "INFO/version\0") or 0
512         assert self.version in (0, 1, 2, 3)
513
514     def usernames(self):
515         """Iterate over the usernames in this Tdb database."""
516         for k in self.tdb.iterkeys():
517             if k.startswith(TDBSAM_USER_PREFIX):
518                 yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0")
519
520     __iter__ = usernames
521     
522     def __getitem__(self, name):
523         data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)]
524         user = SAMUser(name)
525     
526         def unpack_string(data):
527             (length, ) = struct.unpack("<L", data[:4])
528             data = data[4:]
529             if length == 0:
530                 return (None, data)
531             return (data[:length].rstrip("\0"), data[length:])
532
533         def unpack_int32(data):
534             (value, ) = struct.unpack("<l", data[:4])
535             return (value, data[4:])
536
537         def unpack_uint32(data):
538             (value, ) = struct.unpack("<L", data[:4])
539             return (value, data[4:])
540
541         def unpack_uint16(data):
542             (value, ) = struct.unpack("<H", data[:2])
543             return (value, data[2:])
544
545         (logon_time, data) = unpack_int32(data)
546         (logoff_time, data) = unpack_int32(data)
547         (kickoff_time, data) = unpack_int32(data)
548
549         if self.version > 0:
550             (bad_password_time, data) = unpack_int32(data)
551             if bad_password_time != 0:
552                 user.bad_password_time = bad_password_time
553         (pass_last_set_time, data) = unpack_int32(data)
554         (pass_can_change_time, data) = unpack_int32(data)
555         (pass_must_change_time, data) = unpack_int32(data)
556
557         if logon_time != 0:
558             user.logon_time = logon_time
559         user.logoff_time = logoff_time
560         user.kickoff_time = kickoff_time
561         if pass_last_set_time != 0:
562             user.pass_last_set_time = pass_last_set_time
563         user.pass_can_change_time = pass_can_change_time
564
565         (user.username, data) = unpack_string(data)
566         (user.domain, data) = unpack_string(data)
567         (user.nt_username, data) = unpack_string(data)
568         (user.fullname, data) = unpack_string(data)
569         (user.homedir, data) = unpack_string(data)
570         (user.dir_drive, data) = unpack_string(data)
571         (user.logon_script, data) = unpack_string(data)
572         (user.profile_path, data) = unpack_string(data)
573         (user.acct_desc, data) = unpack_string(data)
574         (user.workstations, data) = unpack_string(data)
575         (user.unknown_str, data) = unpack_string(data)
576         (user.munged_dial, data) = unpack_string(data)
577
578         (user.user_rid, data) = unpack_int32(data)
579         (user.group_rid, data) = unpack_int32(data)
580
581         (user.lm_password, data) = unpack_string(data)
582         (user.nt_password, data) = unpack_string(data)
583
584         if self.version > 1:
585             (user.nt_password_history, data) = unpack_string(data)
586
587         (user.acct_ctrl, data) = unpack_uint16(data)
588         (_, data) = unpack_uint32(data) # remove_me field
589         (user.logon_divs, data) = unpack_uint16(data)
590         (hours, data) = unpack_string(data)
591         user.hours = []
592         for entry in hours:
593             for i in range(8):
594                 user.hours.append(ord(entry) & (2 ** i) == (2 ** i))
595         # FIXME (reactivate also the tests in tests/samba3.py after fixing this)
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"))