~zulcss/samba/server-dailies-3.4

« back to all changes in this revision

Viewing changes to source4/scripting/python/samba/samba3.py

  • Committer: Chuck Short
  • Date: 2010-09-28 20:38:39 UTC
  • Revision ID: zulcss@ubuntu.com-20100928203839-pgjulytsi9ue63x1
Initial version

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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"))