__init__.py 99.2 KB
Newer Older
1
# Unix SMB/CIFS implementation.
2
# backend code for provisioning a Samba AD server
3

4
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
5
# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
6
# Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
7
8
#
# Based on the original in EJS:
9
10
11
12
13
14
# Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
15
#
16
17
18
19
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
20
#
21
22
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
23
24
#

25
26
"""Functions for setting up a Samba configuration."""

27
28
__docformat__ = "restructuredText"

29
from samba.compat import urllib_quote
30
from samba.compat import string_types
31
from samba.compat import binary_type
32
from base64 import b64encode
33
import errno
34
import os
35
import stat
36
import re
37
38
import pwd
import grp
39
import logging
40
import time
Jelmer Vernooij's avatar
Jelmer Vernooij committed
41
import uuid
42
import socket
43
import tempfile
44
import samba.dsdb
45

Jelmer Vernooij's avatar
Jelmer Vernooij committed
46
import ldb
47

Jelmer Vernooij's avatar
Jelmer Vernooij committed
48
from samba.auth import system_session, admin_session
49
import samba
50
from samba import auth
51
52
from samba.samba3 import smbd, passdb
from samba.samba3 import param as s3param
53
from samba.dsdb import DS_DOMAIN_FUNCTION_2000
54
55
from samba import (
    Ldb,
56
    MAX_NETBIOS_NAME_LEN,
57
    check_all_substituted,
58
    is_valid_netbios_char,
59
60
61
62
    setup_file,
    substitute_var,
    valid_netbios_name,
    version,
63
    is_heimdal_built,
64
)
65
from samba.dcerpc import security, misc
66
67
68
from samba.dcerpc.misc import (
    SEC_CHAN_BDC,
    SEC_CHAN_WKSTA,
69
)
70
71
72
73
from samba.dsdb import (
    DS_DOMAIN_FUNCTION_2003,
    DS_DOMAIN_FUNCTION_2008_R2,
    ENC_ALL_TYPES,
74
)
Jelmer Vernooij's avatar
Jelmer Vernooij committed
75
from samba.idmap import IDmapDB
Jelmer Vernooij's avatar
Jelmer Vernooij committed
76
from samba.ms_display_specifiers import read_ms_ldif
77
from samba.ntacls import setntacl, getntacl, dsacl2fsacl
78
from samba.ndr import ndr_pack, ndr_unpack
79
from samba.provision.backend import (
Jelmer Vernooij's avatar
Jelmer Vernooij committed
80
81
82
    FDSBackend,
    LDBBackend,
    OpenLDAPBackend,
83
)
84
from samba.descriptor import (
85
    get_empty_descriptor,
86
    get_config_descriptor,
87
    get_config_partitions_descriptor,
88
    get_config_sites_descriptor,
89
    get_config_ntds_quotas_descriptor,
90
91
92
    get_config_delete_protected1_descriptor,
    get_config_delete_protected1wd_descriptor,
    get_config_delete_protected2_descriptor,
93
94
    get_domain_descriptor,
    get_domain_infrastructure_descriptor,
95
    get_domain_builtin_descriptor,
96
    get_domain_computers_descriptor,
97
    get_domain_users_descriptor,
98
    get_domain_controllers_descriptor,
99
100
    get_domain_delete_protected1_descriptor,
    get_domain_delete_protected2_descriptor,
101
    get_dns_partition_descriptor,
102
103
    get_dns_forest_microsoft_dns_descriptor,
    get_dns_domain_microsoft_dns_descriptor,
104
    get_managed_service_accounts_descriptor,
105
)
106
107
108
109
from samba.provision.common import (
    setup_path,
    setup_add_ldif,
    setup_modify_ldif,
110
111
112
113
114
    FILL_FULL,
    FILL_SUBDOMAIN,
    FILL_NT4SYNC,
    FILL_DRS
)
115
from samba.provision.sambadns import (
116
    get_dnsadmins_sid,
117
118
    setup_ad_dns,
    create_dns_update_list
119
)
120

121
122
import samba.param
import samba.registry
Jelmer Vernooij's avatar
Jelmer Vernooij committed
123
from samba.schema import Schema
124
from samba.samdb import SamDB
125
from samba.dbchecker import dbcheck
126
from samba.provision.kerberos import create_kdc_conf
127
from samba.samdb import get_default_backend_store
Jelmer Vernooij's avatar
Jelmer Vernooij committed
128

129
DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
130
DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04FB984F9"
131
132
133
DEFAULTSITE = "Default-First-Site-Name"
LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"

134
135
DEFAULT_MIN_PWD_LENGTH = 7

136

137
class ProvisionPaths(object):
138

139
140
141
142
143
144
145
146
147
    def __init__(self):
        self.shareconf = None
        self.hklm = None
        self.hkcu = None
        self.hkcr = None
        self.hku = None
        self.hkpd = None
        self.hkpt = None
        self.samdb = None
Kai Blin's avatar
Kai Blin committed
148
        self.idmapdb = None
149
150
        self.secrets = None
        self.keytab = None
Jelmer Vernooij's avatar
Jelmer Vernooij committed
151
        self.dns_keytab = None
152
153
        self.dns = None
        self.winsdb = None
154
        self.private_dir = None
155
        self.binddns_dir = None
156
        self.state_dir = None
157

158

159
class ProvisionNames(object):
160

161
    def __init__(self):
162
        self.ncs = None
163
164
165
166
        self.rootdn = None
        self.domaindn = None
        self.configdn = None
        self.schemadn = None
167
168
        self.dnsforestdn = None
        self.dnsdomaindn = None
169
170
171
172
173
174
175
        self.ldapmanagerdn = None
        self.dnsdomain = None
        self.realm = None
        self.netbiosname = None
        self.domain = None
        self.hostname = None
        self.sitename = None
176
        self.smbconf = None
177
178
179
        self.domainsid = None
        self.forestsid = None
        self.domainguid = None
180
        self.name_map = {}
181

182
183

def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
184
                                  lp):
185
186
187
188
189
190
191
192
193
194
195
196
197
198
    """Get key provision parameters (realm, domain, ...) from a given provision

    :param samdb: An LDB object connected to the sam.ldb file
    :param secretsdb: An LDB object connected to the secrets.ldb file
    :param idmapdb: An LDB object connected to the idmap.ldb file
    :param paths: A list of path to provision object
    :param smbconf: Path to the smb.conf file
    :param lp: A LoadParm object
    :return: A list of key provision parameters
    """
    names = ProvisionNames()
    names.adminpass = None

    # NT domain, kerberos realm, root dn, domain dn, domain dns name
199
    names.domain = lp.get("workgroup").upper()
200
201
    names.realm = lp.get("realm")
    names.dnsdomain = names.realm.lower()
202
    basedn = samba.dn_from_dns_name(names.dnsdomain)
203
    names.realm = names.realm.upper()
204
205
206
    # netbiosname
    # Get the netbiosname first (could be obtained from smb.conf in theory)
    res = secretsdb.search(expression="(flatname=%s)" %
207
                           names.domain, base="CN=Primary Domains",
208
                           scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
209
    names.netbiosname = str(res[0]["sAMAccountName"]).replace("$", "")
210
211
212
213
214
215

    names.smbconf = smbconf

    # That's a bit simplistic but it's ok as long as we have only 3
    # partitions
    current = samdb.search(expression="(objectClass=*)",
216
217
                           base="", scope=ldb.SCOPE_BASE,
                           attrs=["defaultNamingContext", "schemaNamingContext",
218
                                  "configurationNamingContext", "rootDomainNamingContext",
219
                                  "namingContexts"])
220

221
222
    names.configdn = str(current[0]["configurationNamingContext"][0])
    names.schemadn = str(current[0]["schemaNamingContext"][0])
223
    if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
224
                                             current[0]["defaultNamingContext"][0].decode('utf8')))):
225
226
        raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
                                 "is not the same ..." % (paths.samdb,
227
228
                                                          str(current[0]["defaultNamingContext"][0].decode('utf8')),
                                                          paths.smbconf, basedn)))
229

230
231
    names.domaindn = str(current[0]["defaultNamingContext"][0])
    names.rootdn = str(current[0]["rootDomainNamingContext"][0])
232
    names.ncs = current[0]["namingContexts"]
233
234
235
236
    names.dnsforestdn = None
    names.dnsdomaindn = None

    for i in range(0, len(names.ncs)):
237
        nc = str(names.ncs[i])
238
239
240
241
242
243
244
245
246
247
248

        dnsforestdn = "DC=ForestDnsZones,%s" % (str(names.rootdn))
        if nc == dnsforestdn:
            names.dnsforestdn = dnsforestdn
            continue

        dnsdomaindn = "DC=DomainDnsZones,%s" % (str(names.domaindn))
        if nc == dnsdomaindn:
            names.dnsdomaindn = dnsdomaindn
            continue

249
    # default site name
250
    res3 = samdb.search(expression="(objectClass=site)",
251
                        base="CN=Sites," + str(names.configdn), scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
252
253
254
255
    names.sitename = str(res3[0]["cn"])

    # dns hostname and server dn
    res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
256
257
258
259
260
                        base="OU=Domain Controllers,%s" % basedn,
                        scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
    if len(res4) == 0:
        raise ProvisioningError("Unable to find DC called CN=%s under OU=Domain Controllers,%s" % (names.netbiosname, basedn))

261
    names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain, "")
262
263

    server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
264
                              attrs=[], base=names.configdn)
265
    names.serverdn = str(server_res[0].dn)
266
267
268

    # invocation id/objectguid
    res5 = samdb.search(expression="(objectClass=*)",
269
270
271
                        base="CN=NTDS Settings,%s" % str(names.serverdn),
                        scope=ldb.SCOPE_BASE,
                        attrs=["invocationID", "objectGUID"])
272
273
274
275
276
    names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
    names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))

    # domain guid/sid
    res6 = samdb.search(expression="(objectClass=*)", base=basedn,
277
                        scope=ldb.SCOPE_BASE, attrs=["objectGUID",
278
                                                     "objectSid", "msDS-Behavior-Version"])
279
    names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
Joe Guo's avatar
Joe Guo committed
280
281
    names.domainsid = ndr_unpack(security.dom_sid, res6[0]["objectSid"][0])
    names.forestsid = ndr_unpack(security.dom_sid, res6[0]["objectSid"][0])
282
    if res6[0].get("msDS-Behavior-Version") is None or \
283
            int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
284
285
286
287
288
        names.domainlevel = DS_DOMAIN_FUNCTION_2000
    else:
        names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])

    # policy guid
289
    res7 = samdb.search(expression="(name={%s})" % DEFAULT_POLICY_GUID,
290
                        base="CN=Policies,CN=System," + basedn,
291
292
                        scope=ldb.SCOPE_ONELEVEL, attrs=["cn", "displayName"])
    names.policyid = str(res7[0]["cn"]).replace("{", "").replace("}", "")
293
    # dc policy guid
294
295
296
    res8 = samdb.search(expression="(name={%s})" % DEFAULT_DC_POLICY_GUID,
                        base="CN=Policies,CN=System," + basedn,
                        scope=ldb.SCOPE_ONELEVEL,
297
                        attrs=["cn", "displayName"])
298
    if len(res8) == 1:
299
        names.policyid_dc = str(res8[0]["cn"]).replace("{", "").replace("}", "")
300
301
    else:
        names.policyid_dc = None
302
303
304
305

    res9 = idmapdb.search(expression="(cn=%s-%s)" %
                          (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR),
                          attrs=["xidNumber", "type"])
306
    if len(res9) != 1:
307
        raise ProvisioningError("Unable to find uid/gid for Domain Admins rid (%s-%s" % (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR))
308
309
    if str(res9[0]["type"][0]) == "ID_TYPE_BOTH":
        names.root_gid = int(res9[0]["xidNumber"][0])
310
311
    else:
        names.root_gid = pwd.getpwuid(int(res9[0]["xidNumber"][0])).pw_gid
312

313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
    res10 = samdb.search(expression="(samaccountname=dns)",
                         scope=ldb.SCOPE_SUBTREE, attrs=["dn"],
                         controls=["search_options:1:2"])
    if (len(res10) > 0):
        has_legacy_dns_account = True
    else:
        has_legacy_dns_account = False

    res11 = samdb.search(expression="(samaccountname=dns-%s)" % names.netbiosname,
                         scope=ldb.SCOPE_SUBTREE, attrs=["dn"],
                         controls=["search_options:1:2"])
    if (len(res11) > 0):
        has_dns_account = True
    else:
        has_dns_account = False

    if names.dnsdomaindn is not None:
        if has_dns_account:
            names.dns_backend = 'BIND9_DLZ'
        else:
            names.dns_backend = 'SAMBA_INTERNAL'
    elif has_dns_account or has_legacy_dns_account:
        names.dns_backend = 'BIND9_FLATFILE'
    else:
        names.dns_backend = 'NONE'

339
340
341
    dns_admins_sid = get_dnsadmins_sid(samdb, names.domaindn)
    names.name_map['DnsAdmins'] = str(dns_admins_sid)

342
    return names
343

344

345
def update_provision_usn(samdb, low, high, id, replace=False):
346
    """Update the field provisionUSN in sam.ldb
347

348
    This field is used to track range of USN modified by provision and
349
    upgradeprovision.
350
    This value is used afterward by next provision to figure out if
351
352
353
354
355
    the field have been modified since last provision.

    :param samdb: An LDB object connect to sam.ldb
    :param low: The lowest USN modified by this upgrade
    :param high: The highest USN modified by this upgrade
356
    :param id: The invocation id of the samba's dc
357
    :param replace: A boolean indicating if the range should replace any
358
359
                    existing one or appended (default)
    """
360
361
362

    tab = []
    if not replace:
363
364
365
        entry = samdb.search(base="@PROVISION",
                             scope=ldb.SCOPE_BASE,
                             attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
366
        for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
367
368
            if not re.search(';', str(e)):
                e = "%s;%s" % (str(e), id)
369
370
            tab.append(str(e))

371
    tab.append("%s-%s;%s" % (low, high, id))
372
    delta = ldb.Message()
373
    delta.dn = ldb.Dn(samdb, "@PROVISION")
374
375
376
377
    delta[LAST_PROVISION_USN_ATTRIBUTE] = \
        ldb.MessageElement(tab,
                           ldb.FLAG_MOD_REPLACE,
                           LAST_PROVISION_USN_ATTRIBUTE)
378
379
380
    entry = samdb.search(expression='provisionnerID=*',
                         base="@PROVISION", scope=ldb.SCOPE_BASE,
                         attrs=["provisionnerID"])
381
382
    if len(entry) == 0 or len(entry[0]) == 0:
        delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
383
384
    samdb.modify(delta)

385

386
def set_provision_usn(samdb, low, high, id):
387
388
389
390
391
392
393
394
    """Set the field provisionUSN in sam.ldb
    This field is used to track range of USN modified by provision and
    upgradeprovision.
    This value is used afterward by next provision to figure out if
    the field have been modified since last provision.

    :param samdb: An LDB object connect to sam.ldb
    :param low: The lowest USN modified by this upgrade
395
396
397
    :param high: The highest USN modified by this upgrade
    :param id: The invocationId of the provision"""

398
    tab = []
399
400
    tab.append("%s-%s;%s" % (low, high, id))

401
    delta = ldb.Message()
402
    delta.dn = ldb.Dn(samdb, "@PROVISION")
403
404
405
406
    delta[LAST_PROVISION_USN_ATTRIBUTE] = \
        ldb.MessageElement(tab,
                           ldb.FLAG_MOD_ADD,
                           LAST_PROVISION_USN_ATTRIBUTE)
407
408
    samdb.add(delta)

409

410
def get_max_usn(samdb, basedn):
411
412
413
414
415
416
417
    """ This function return the biggest USN present in the provision

    :param samdb: A LDB object pointing to the sam.ldb
    :param basedn: A string containing the base DN of the provision
                    (ie. DC=foo, DC=bar)
    :return: The biggest USN in the provision"""

418
419
    res = samdb.search(expression="objectClass=*", base=basedn,
                       scope=ldb.SCOPE_SUBTREE, attrs=["uSNChanged"],
420
421
422
                       controls=["search_options:1:2",
                                 "server_sort:1:1:uSNChanged",
                                 "paged_results:1:1"])
423
    return res[0]["uSNChanged"]
424

425

426
def get_last_provision_usn(sam):
427
    """Get USNs ranges modified by a provision or an upgradeprovision
428
429

    :param sam: An LDB object pointing to the sam.ldb
430
    :return: a dictionary which keys are invocation id and values are an array
431
             of integer representing the different ranges
432
    """
433
434
    try:
        entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
435
436
                           base="@PROVISION", scope=ldb.SCOPE_BASE,
                           attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
437
438
    except ldb.LdbError as e1:
        (ecode, emsg) = e1.args
439
440
441
        if ecode == ldb.ERR_NO_SUCH_OBJECT:
            return None
        raise
442
    if len(entry) > 0:
443
444
        myids = []
        range = {}
445
        p = re.compile(r'-')
446
447
448
        if entry[0].get("provisionnerID"):
            for e in entry[0]["provisionnerID"]:
                myids.append(str(e))
449
        for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
450
451
452
453
454
455
456
457
            tab1 = str(r).split(';')
            if len(tab1) == 2:
                id = tab1[1]
            else:
                id = "default"
            if (len(myids) > 0 and id not in myids):
                continue
            tab2 = p.split(tab1[0])
458
            if range.get(id) is None:
459
460
461
                range[id] = []
            range[id].append(tab2[0])
            range[id].append(tab2[1])
462
463
464
        return range
    else:
        return None
465

466

467
class ProvisionResult(object):
468
469
470
471
472
473
    """Result of a provision.

    :ivar server_role: The server role
    :ivar paths: ProvisionPaths instance
    :ivar domaindn: The domain dn, as string
    """
474

475
    def __init__(self):
476
        self.server_role = None
477
478
479
480
        self.paths = None
        self.domaindn = None
        self.lp = None
        self.samdb = None
481
        self.idmap = None
482
        self.names = None
483
        self.domainsid = None
484
485
        self.adminpass_generated = None
        self.adminpass = None
486
        self.backend_result = None
487
488
489

    def report_logger(self, logger):
        """Report this provision result to a logger."""
490
        logger.info(
491
            "Once the above files are installed, your Samba AD server will "
492
            "be ready to use")
493
494
        if self.adminpass_generated:
            logger.info("Admin password:        %s", self.adminpass)
495
496
497
498
499
500
        logger.info("Server Role:           %s", self.server_role)
        logger.info("Hostname:              %s", self.names.hostname)
        logger.info("NetBIOS Domain:        %s", self.names.domain)
        logger.info("DNS Domain:            %s", self.names.dnsdomain)
        logger.info("DOMAIN SID:            %s", self.domainsid)

501
502
503
        if self.backend_result:
            self.backend_result.report_logger(logger)

504

505
def check_install(lp, session_info, credentials):
506
    """Check whether the current install seems ok.
507

508
509
510
511
    :param lp: Loadparm context
    :param session_info: Session information
    :param credentials: Credentials
    """
512
    if lp.get("realm") == "":
513
        raise Exception("Realm empty")
514
    samdb = Ldb(lp.samdb_url(), session_info=session_info,
515
                credentials=credentials, lp=lp)
516
    if len(samdb.search("(cn=Administrator)")) != 1:
517
        raise ProvisioningError("No administrator account found")
518
519


520
521
def findnss(nssfn, names):
    """Find a user or group from a list of possibilities.
522

523
524
525
526
    :param nssfn: NSS Function to try (should raise KeyError if not found)
    :param names: Names to check.
    :return: Value return by first names list.
    """
527
528
529
530
531
    for name in names:
        try:
            return nssfn(name)
        except KeyError:
            pass
532
    raise KeyError("Unable to find user/group in %r" % names)
533
534


535
536
537
538
539
540
def findnss_uid(names):
    return findnss(pwd.getpwnam, names)[2]


def findnss_gid(names):
    return findnss(grp.getgrnam, names)[2]
Jelmer Vernooij's avatar
Jelmer Vernooij committed
541
542


Jelmer Vernooij's avatar
Jelmer Vernooij committed
543
def provision_paths_from_lp(lp, dnsdomain):
544
545
546
    """Set the default paths for provisioning.

    :param lp: Loadparm context.
547
    :param dnsdomain: DNS Domain name
548
549
    """
    paths = ProvisionPaths()
550
    paths.private_dir = lp.get("private dir")
551
    paths.binddns_dir = lp.get("binddns dir")
552
    paths.state_dir = lp.get("state directory")
553
554
555

    # This is stored without path prefix for the "privateKeytab" attribute in
    # "secrets_dns.ldif".
Jelmer Vernooij's avatar
Jelmer Vernooij committed
556
    paths.dns_keytab = "dns.keytab"
557
    paths.keytab = "secrets.keytab"
558

559
    paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
560
    paths.samdb = os.path.join(paths.private_dir, "sam.ldb")
561
    paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
562
    paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
563
    paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
564
    paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
565
    paths.spn_update_list = os.path.join(paths.private_dir, "spn_update_list")
566
    paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
567
    paths.kdcconf = os.path.join(paths.private_dir, "kdc.conf")
568
569
    paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
    paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
570
571
572
    paths.encrypted_secrets_key_path = os.path.join(
        paths.private_dir,
        "encrypted_secrets.key")
573
574
575
576
577
578

    paths.dns = os.path.join(paths.binddns_dir, "dns", dnsdomain + ".zone")
    paths.namedconf = os.path.join(paths.binddns_dir, "named.conf")
    paths.namedconf_update = os.path.join(paths.binddns_dir, "named.conf.update")
    paths.namedtxt = os.path.join(paths.binddns_dir, "named.txt")

579
580
581
582
583
584
    paths.hklm = "hklm.ldb"
    paths.hkcr = "hkcr.ldb"
    paths.hkcu = "hkcu.ldb"
    paths.hku = "hku.ldb"
    paths.hkpd = "hkpd.ldb"
    paths.hkpt = "hkpt.ldb"
585
586
    paths.sysvol = lp.get("path", "sysvol")
    paths.netlogon = lp.get("path", "netlogon")
Jelmer Vernooij's avatar
Jelmer Vernooij committed
587
    paths.smbconf = lp.configfile
588
589
    return paths

590

591
592
def determine_netbios_name(hostname):
    """Determine a netbios name from a hostname."""
593
594
595
    # remove forbidden chars and force the length to be <16
    netbiosname = "".join([x for x in hostname if is_valid_netbios_char(x)])
    return netbiosname[:MAX_NETBIOS_NAME_LEN].upper()
596
597


598
599
def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
                serverrole=None, rootdn=None, domaindn=None, configdn=None,
600
601
                schemadn=None, serverdn=None, sitename=None,
                domain_names_forced=False):
Jelmer Vernooij's avatar
Jelmer Vernooij committed
602
    """Guess configuration settings to use."""
603
604

    if hostname is None:
605
        hostname = socket.gethostname().split(".")[0]
606

607
608
    netbiosname = lp.get("netbios name")
    if netbiosname is None:
609
        netbiosname = determine_netbios_name(hostname)
610
    netbiosname = netbiosname.upper()
611
612
613
    if not valid_netbios_name(netbiosname):
        raise InvalidNetbiosName(netbiosname)

614
615
    if dnsdomain is None:
        dnsdomain = lp.get("realm")
616
        if dnsdomain is None or dnsdomain == "":
617
618
619
            raise ProvisioningError(
                "guess_names: 'realm' not specified in supplied %s!" %
                lp.configfile)
620

621
622
623
624
    dnsdomain = dnsdomain.lower()

    if serverrole is None:
        serverrole = lp.get("server role")
625
        if serverrole is None:
626
            raise ProvisioningError("guess_names: 'server role' not specified in supplied %s!" % lp.configfile)
627

628
629
630
631
    serverrole = serverrole.lower()

    realm = dnsdomain.upper()

632
633
634
    if lp.get("realm") == "":
        raise ProvisioningError("guess_names: 'realm =' was not specified in supplied %s.  Please remove the smb.conf file and let provision generate it" % lp.configfile)

635
    if lp.get("realm").upper() != realm:
636
        raise ProvisioningError("guess_names: 'realm=%s' in %s must match chosen realm '%s'!  Please remove the smb.conf file and let provision generate it" % (lp.get("realm").upper(), lp.configfile, realm))
637

638
    if lp.get("server role").lower() != serverrole:
639
        raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'!  Please remove the smb.conf file and let provision generate it" % (lp.get("server role"), lp.configfile, serverrole))
640

641
    if serverrole == "active directory domain controller":
642
        if domain is None:
643
            # This will, for better or worse, default to 'WORKGROUP'
644
645
646
647
            domain = lp.get("workgroup")
        domain = domain.upper()

        if lp.get("workgroup").upper() != domain:
648
            raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'!  Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
649

650
        if domaindn is None:
651
            domaindn = samba.dn_from_dns_name(dnsdomain)
652
653
654

        if domain == netbiosname:
            raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
655
656
657
    else:
        domain = netbiosname
        if domaindn is None:
658
            domaindn = "DC=" + netbiosname
659

660
661
    if not valid_netbios_name(domain):
        raise InvalidNetbiosName(domain)
662

663
    if hostname.upper() == realm:
664
        raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!" % (realm, hostname))
665
    if netbiosname.upper() == realm:
666
667
        raise ProvisioningError("guess_names: Realm '%s' must not be equal to NetBIOS hostname '%s'!" % (realm, netbiosname))
    if domain == realm and not domain_names_forced:
668
        raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!" % (realm, domain))
669

670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
    if serverrole != "active directory domain controller":
        #
        # This is the code path for a domain member
        # where we provision the database as if we where
        # on a domain controller, so we should not use
        # the same dnsdomain as the domain controllers
        # of our primary domain.
        #
        # This will be important if we start doing
        # SID/name filtering and reject the local
        # sid and names if they come from a domain
        # controller.
        #
        realm = netbiosname
        dnsdomain = netbiosname.lower()

686
    if rootdn is None:
687
        rootdn = domaindn
688

689
690
691
692
693
694
    if configdn is None:
        configdn = "CN=Configuration," + rootdn
    if schemadn is None:
        schemadn = "CN=Schema," + configdn

    if sitename is None:
695
        sitename = DEFAULTSITE
696
697
698
699
700
701
702
703
704
705
706
707
708

    names = ProvisionNames()
    names.rootdn = rootdn
    names.domaindn = domaindn
    names.configdn = configdn
    names.schemadn = schemadn
    names.ldapmanagerdn = "CN=Manager," + rootdn
    names.dnsdomain = dnsdomain
    names.domain = domain
    names.realm = realm
    names.netbiosname = netbiosname
    names.hostname = hostname
    names.sitename = sitename
709
710
    names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (
        netbiosname, sitename, configdn)
711

712
713
    return names

714

715
def make_smbconf(smbconf, hostname, domain, realm, targetdir,
716
                 serverrole=None, eadb=False, use_ntvfs=False, lp=None,
717
                 global_param=None):
718
719
720
    """Create a new smb.conf file based on a couple of basic settings.
    """
    assert smbconf is not None
721

722
    if hostname is None:
723
        hostname = socket.gethostname().split(".")[0]
724
725

    netbiosname = determine_netbios_name(hostname)
726

727
    if serverrole is None:
728
        serverrole = "standalone server"
729

730
    assert domain is not None
731
732
    domain = domain.upper()

733
    assert realm is not None
734
    realm = realm.upper()
735

736
737
738
739
740
    global_settings = {
        "netbios name": netbiosname,
        "workgroup": domain,
        "realm": realm,
        "server role": serverrole,
741
    }
742

743
744
    if lp is None:
        lp = samba.param.LoadParm()
745
    # Load non-existent file
746
    if os.path.exists(smbconf):
747
        lp.load(smbconf)
748

749
750
751
752
    if global_param is not None:
        for ent in global_param:
            if global_param[ent] is not None:
                global_settings[ent] = " ".join(global_param[ent])
753

754
    if targetdir is not None:
755
756
        global_settings["private dir"] = os.path.abspath(os.path.join(targetdir, "private"))
        global_settings["lock dir"] = os.path.abspath(targetdir)
757
758
        global_settings["state directory"] = os.path.abspath(os.path.join(targetdir, "state"))
        global_settings["cache directory"] = os.path.abspath(os.path.join(targetdir, "cache"))
759
        global_settings["binddns dir"] = os.path.abspath(os.path.join(targetdir, "bind-dns"))
760

761
        lp.set("lock dir", os.path.abspath(targetdir))
762
        lp.set("state directory", global_settings["state directory"])
763
        lp.set("cache directory", global_settings["cache directory"])
764
        lp.set("binddns dir", global_settings["binddns dir"])
765

766
767
768
769
770
771
772
773
774
775
776
777
778
779
    if eadb:
        if use_ntvfs and not lp.get("posix:eadb"):
            if targetdir is not None:
                privdir = os.path.join(targetdir, "private")
            else:
                privdir = lp.get("private dir")
            lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
        elif not use_ntvfs and not lp.get("xattr_tdb:file"):
            if targetdir is not None:
                statedir = os.path.join(targetdir, "state")
            else:
                statedir = lp.get("state directory")
            lp.set("xattr_tdb:file", os.path.abspath(os.path.join(statedir, "xattr.tdb")))

780
    shares = {}
781
    if serverrole == "active directory domain controller":
782
        shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol")
783
        shares["netlogon"] = os.path.join(shares["sysvol"], realm.lower(),
784
                                          "scripts")
785
786
    else:
        global_settings["passdb backend"] = "samba_dsdb"
787
788
789
790

    f = open(smbconf, 'w')
    try:
        f.write("[globals]\n")
791
        for key, val in global_settings.items():
792
793
794
            f.write("\t%s = %s\n" % (key, val))
        f.write("\n")

795
        for name, path in shares.items():
796
797
798
799
800
801
            f.write("[%s]\n" % name)
            f.write("\tpath = %s\n" % path)
            f.write("\tread only = no\n")
            f.write("\n")
    finally:
        f.close()
802
803
804
805
806
807
    # reload the smb.conf
    lp.load(smbconf)

    # and dump it without any values that are the default
    # this ensures that any smb.conf parameters that were set
    # on the provision/join command line are set in the resulting smb.conf
808
    lp.dump(False, smbconf)
809
810


811
def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
812
                        users_gid, root_gid):
813
    """setup reasonable name mappings for sam names to unix names.
814
815
816

    :param samdb: SamDB object.
    :param idmap: IDmap db object.
817
818
    :param sid: The domain sid.
    :param domaindn: The domain DN.
819
820
821
    :param root_uid: uid of the UNIX root user.
    :param nobody_uid: uid of the UNIX nobody user.
    :param users_gid: gid of the UNIX users group.
822
    :param root_gid: gid of the UNIX root group.
823
    """
Andrew Tridgell's avatar
Andrew Tridgell committed
824
    idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
825

Andrew Tridgell's avatar
Andrew Tridgell committed
826
827
    idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
    idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
828

829

830
def setup_samdb_partitions(samdb_path, logger, lp, session_info,