Use convenience function for finding setup_dir based on location of
[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 = ldap_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)
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         (user.bad_password_count, data) = unpack_uint16(data)
596         (user.logon_count, data) = unpack_uint16(data)
597         (user.unknown_6, data) = unpack_uint32(data)
598         assert len(data) == 0
599         return user
600
601
602 def shellsplit(text):
603     """Very simple shell-like line splitting.
604     
605     :param text: Text to split.
606     :return: List with parts of the line as strings.
607     """
608     ret = list()
609     inquotes = False
610     current = ""
611     for c in text:
612         if c == "\"":
613             inquotes = not inquotes
614         elif c in ("\t", "\n", " ") and not inquotes:
615             ret.append(current)
616             current = ""
617         else:
618             current += c
619     if current != "":
620         ret.append(current)
621     return ret
622
623
624 class WinsDatabase(object):
625     """Samba 3 WINS database reader."""
626     def __init__(self, file):
627         self.entries = {}
628         f = open(file, 'r')
629         assert f.readline().rstrip("\n") == "VERSION 1 0"
630         for l in f.readlines():
631             if l[0] == "#": # skip comments
632                 continue
633             entries = shellsplit(l.rstrip("\n"))
634             name = entries[0]
635             ttl = int(entries[1])
636             i = 2
637             ips = []
638             while "." in entries[i]:
639                 ips.append(entries[i])
640                 i+=1
641             nb_flags = int(entries[i][:-1], 16)
642             assert not name in self.entries, "Name %s exists twice" % name
643             self.entries[name] = (ttl, ips, nb_flags)
644         f.close()
645
646     def __getitem__(self, name):
647         return self.entries[name]
648
649     def __len__(self):
650         return len(self.entries)
651
652     def __iter__(self):
653         return iter(self.entries)
654
655     def items(self):
656         """Return the entries in this WINS database."""
657         return self.entries.items()
658
659     def close(self): # for consistency
660         pass
661
662
663 class ParamFile(object):
664     """Simple smb.conf-compatible file parser
665
666     Does not use a parameter table, unlike the "normal".
667     """
668
669     def __init__(self, sections=None):
670         self._sections = sections or {}
671
672     def _sanitize_name(self, name):
673         return name.strip().lower().replace(" ","")
674
675     def __repr__(self):
676         return "ParamFile(%r)" % self._sections
677
678     def read(self, filename):
679         """Read a file.
680
681         :param filename: Path to the file
682         """
683         section = None
684         for i, l in enumerate(open(filename, 'r').xreadlines()):
685             l = l.strip()
686             if not l:
687                 continue
688             if l[0] == "[" and l[-1] == "]":
689                 section = self._sanitize_name(l[1:-1])
690                 self._sections.setdefault(section, {})
691             elif "=" in l:
692                (k, v) = l.split("=", 1) 
693                self._sections[section][self._sanitize_name(k)] = v
694             else:
695                 raise Error("Unable to parser line %d: %r" % (i+1,l))
696
697     def get(self, param, section=None):
698         """Return the value of a parameter.
699
700         :param param: Parameter name
701         :param section: Section name, defaults to "global"
702         :return: parameter value as string if found, None otherwise.
703         """
704         if section is None:
705             section = "global"
706         section = self._sanitize_name(section)
707         if not section in self._sections:
708             return None
709         param = self._sanitize_name(param)
710         if not param in self._sections[section]:
711             return None
712         return self._sections[section][param].strip()
713
714     def __getitem__(self, section):
715         return self._sections[section]
716
717     def get_section(self, section):
718         return self._sections.get(section)
719
720     def add_section(self, section):
721         self._sections[self._sanitize_name(section)] = {}
722
723     def set_string(self, name, value):
724         self._sections["global"][name] = value
725
726     def get_string(self, name):
727         return self._sections["global"].get(name)
728
729
730 class Samba3(object):
731     """Samba 3 configuration and state data reader."""
732     def __init__(self, libdir, smbconfpath):
733         """Open the configuration and data for a Samba 3 installation.
734
735         :param libdir: Library directory
736         :param smbconfpath: Path to the smb.conf file.
737         """
738         self.smbconfpath = smbconfpath
739         self.libdir = libdir
740         self.lp = ParamFile()
741         self.lp.read(self.smbconfpath)
742
743     def libdir_path(self, path):
744         if path[0] == "/" or path[0] == ".":
745             return path
746         return os.path.join(self.libdir, path)
747
748     def get_conf(self):
749         return self.lp
750
751     def get_sam_db(self):
752         lp = self.get_conf()
753         backends = (lp.get("passdb backend") or "").split(" ")
754         if ":" in backends[0]:
755             (name, location) = backends[0].split(":", 2)
756         else:
757             name = backends[0]
758             location = None
759         if name == "smbpasswd":
760             return SmbpasswdFile(self.libdir_path(location or "smbpasswd"))
761         elif name == "tdbsam":
762             return TdbSam(self.libdir_path(location or "passdb.tdb"))
763         elif name == "ldapsam":
764             if location is not None:
765                 return LdapSam("ldap:%s" % location)
766             return LdapSam(lp.get("ldap server"))
767         else:
768             raise NotImplementedError("unsupported passdb backend %s" % backends[0])
769
770     def get_policy_db(self):
771         return PolicyDatabase(self.libdir_path("account_policy.tdb"))
772     
773     def get_registry(self):
774         return Registry(self.libdir_path("registry.tdb"))
775
776     def get_secrets_db(self):
777         return SecretsDatabase(self.libdir_path("secrets.tdb"))
778
779     def get_shareinfo_db(self):
780         return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
781
782     def get_idmap_db(self):
783         return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
784
785     def get_wins_db(self):
786         return WinsDatabase(self.libdir_path("wins.dat"))
787
788     def get_shares(self):
789         return Shares(self.get_conf(), self.get_shareinfo_db())
790
791     def get_groupmapping_db(self):
792         return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))