2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 """Functions for setting up a Samba configuration."""
28 from base64
import b64encode
43 from credentials
import Credentials
, DONT_USE_KERBEROS
44 from auth
import system_session
, admin_session
45 from samba
import version
, Ldb
, substitute_var
, valid_netbios_name
46 from samba
import check_all_substituted
47 from samba
import DS_DOMAIN_FUNCTION_2003
, DS_DOMAIN_FUNCTION_2008
, DS_DC_FUNCTION_2008
48 from samba
.samdb
import SamDB
49 from samba
.idmap
import IDmapDB
50 from samba
.dcerpc
import security
51 from samba
.ndr
import ndr_pack
53 from ldb
import SCOPE_SUBTREE
, SCOPE_ONELEVEL
, SCOPE_BASE
, LdbError
, timestring
54 from ms_schema
import read_ms_schema
55 from ms_display_specifiers
import read_ms_ldif
56 from signal
import SIGTERM
57 from dcerpc
.misc
import SEC_CHAN_BDC
, SEC_CHAN_WKSTA
59 __docformat__
= "restructuredText"
62 """Find the setup directory used by provision."""
63 dirname
= os
.path
.dirname(__file__
)
64 if "/site-packages/" in dirname
:
65 prefix
= "/".join(dirname
[:dirname
.index("/site-packages/")].split("/")[:-2])
66 for suffix
in ["share/setup", "share/samba/setup", "setup"]:
67 ret
= os
.path
.join(prefix
, suffix
)
68 if os
.path
.isdir(ret
):
71 ret
= os
.path
.join(dirname
, "../../../setup")
72 if os
.path
.isdir(ret
):
74 raise Exception("Unable to find setup directory.")
76 def get_schema_descriptor(domain_sid
):
77 sddl
= "O:SAG:SAD:(A;CI;RPLCLORC;;;AU)(A;CI;RPWPCRCCLCLORCWOWDSW;;;SA)" \
78 "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
79 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
80 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
81 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
82 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
83 "S:(AU;SA;WPCCDCWOWDSDDTSW;;;WD)" \
84 "(AU;CISA;WP;;;WD)(AU;SA;CR;;;BA)" \
85 "(AU;SA;CR;;;DU)(OU;SA;CR;e12b56b6-0a95-11d1-adbb-00c04fd8d5cd;;WD)" \
86 "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
87 sec
= security
.descriptor
.from_sddl(sddl
, domain_sid
)
88 return b64encode(ndr_pack(sec
))
90 def get_config_descriptor(domain_sid
):
91 sddl
= "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
92 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
93 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
94 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
95 "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
96 "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
97 "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
98 "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
99 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
100 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
101 "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
102 "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
103 "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;S-1-5-21-3191434175-1265308384-3577286990-498)" \
104 "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
105 "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
106 sec
= security
.descriptor
.from_sddl(sddl
, domain_sid
)
107 return b64encode(ndr_pack(sec
))
110 DEFAULTSITE
= "Default-First-Site-Name"
114 class ProvisioningError(Exception):
115 """A generic provision error."""
117 class InvalidNetbiosName(Exception):
118 """A specified name was not a valid NetBIOS name."""
119 def __init__(self
, name
):
120 super(InvalidNetbiosName
, self
).__init
__("The name '%r' is not a valid NetBIOS name" % name
)
123 class ProvisionPaths(object):
125 self
.shareconf
= None
136 self
.dns_keytab
= None
139 self
.private_dir
= None
141 self
.slapdconf
= None
142 self
.modulesconf
= None
143 self
.memberofconf
= None
144 self
.fedoradsinf
= None
145 self
.fedoradspartitions
= None
146 self
.fedoradssasl
= None
147 self
.fedoradsdna
= None
148 self
.fedoradspam
= None
149 self
.fedoradsrefint
= None
150 self
.fedoradslinkedattributes
= None
151 self
.fedoradsindex
= None
152 self
.fedoradssamba
= None
154 self
.olmmrserveridsconf
= None
155 self
.olmmrsyncreplconf
= None
158 self
.olcseedldif
= None
161 class ProvisionNames(object):
168 self
.ldapmanagerdn
= None
169 self
.dnsdomain
= None
171 self
.netbiosname
= None
178 class ProvisionResult(object):
186 class Schema(object):
187 def __init__(self
, setup_path
, domain_sid
, schemadn
=None,
188 serverdn
=None, sambadn
=None):
189 """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
191 :param samdb: Load a schema into a SamDB.
192 :param setup_path: Setup path function.
193 :param schemadn: DN of the schema
194 :param serverdn: DN of the server
196 Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
199 self
.schemadn
= schemadn
201 self
.schema_data
= read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
202 setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
203 self
.schema_data
+= open(setup_path("schema_samba4.ldif"), 'r').read()
204 self
.schema_data
= substitute_var(self
.schema_data
, {"SCHEMADN": schemadn
})
205 check_all_substituted(self
.schema_data
)
207 self
.schema_dn_modify
= read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
208 {"SCHEMADN": schemadn
,
209 "SERVERDN": serverdn
,
212 descr
= get_schema_descriptor(domain_sid
)
213 self
.schema_dn_add
= read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
214 {"SCHEMADN": schemadn
,
218 prefixmap
= open(setup_path("prefixMap.txt"), 'r').read()
219 prefixmap
= b64encode(prefixmap
)
223 # We don't actually add this ldif, just parse it
224 prefixmap_ldif
= "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
225 self
.ldb
.set_schema_from_ldif(prefixmap_ldif
, self
.schema_data
)
227 def write_to_tmp_ldb(self
, schemadb_path
):
228 self
.ldb
.connect(schemadb_path
)
229 self
.ldb
.transaction_start()
231 self
.ldb
.add_ldif("""dn: @ATTRIBUTES
236 @IDXATTR: attributeSyntax
238 # These bits of LDIF are supplied when the Schema object is created
239 self
.ldb
.add_ldif(self
.schema_dn_add
)
240 self
.ldb
.modify_ldif(self
.schema_dn_modify
)
241 self
.ldb
.add_ldif(self
.schema_data
)
242 self
.ldb
.transaction_commit()
244 # Return a hash with the forward attribute as a key and the back as the value
245 def linked_attributes(self
):
246 return get_linked_attributes(self
.schemadn
, self
.ldb
)
248 def dnsyntax_attributes(self
):
249 return get_dnsyntax_attributes(self
.schemadn
, self
.ldb
)
251 # Return a hash with the forward attribute as a key and the back as the value
252 def get_linked_attributes(schemadn
,schemaldb
):
253 attrs
= ["linkID", "lDAPDisplayName"]
254 res
= schemaldb
.search(expression
="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base
=schemadn
, scope
=SCOPE_ONELEVEL
, attrs
=attrs
)
256 for i
in range (0, len(res
)):
257 expression
= "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res
[i
]["linkID"][0])+1)
258 target
= schemaldb
.searchone(basedn
=schemadn
,
259 expression
=expression
,
260 attribute
="lDAPDisplayName",
262 if target
is not None:
263 attributes
[str(res
[i
]["lDAPDisplayName"])]=str(target
)
267 def get_dnsyntax_attributes(schemadn
,schemaldb
):
268 attrs
= ["linkID", "lDAPDisplayName"]
269 res
= schemaldb
.search(expression
="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base
=schemadn
, scope
=SCOPE_ONELEVEL
, attrs
=attrs
)
271 for i
in range (0, len(res
)):
272 attributes
.append(str(res
[i
]["lDAPDisplayName"]))
276 def check_install(lp
, session_info
, credentials
):
277 """Check whether the current install seems ok.
279 :param lp: Loadparm context
280 :param session_info: Session information
281 :param credentials: Credentials
283 if lp
.get("realm") == "":
284 raise Exception("Realm empty")
285 ldb
= Ldb(lp
.get("sam database"), session_info
=session_info
,
286 credentials
=credentials
, lp
=lp
)
287 if len(ldb
.search("(cn=Administrator)")) != 1:
288 raise ProvisioningError("No administrator account found")
291 def findnss(nssfn
, names
):
292 """Find a user or group from a list of possibilities.
294 :param nssfn: NSS Function to try (should raise KeyError if not found)
295 :param names: Names to check.
296 :return: Value return by first names list.
303 raise KeyError("Unable to find user/group %r" % names
)
306 findnss_uid
= lambda names
: findnss(pwd
.getpwnam
, names
)[2]
307 findnss_gid
= lambda names
: findnss(grp
.getgrnam
, names
)[2]
310 def read_and_sub_file(file, subst_vars
):
311 """Read a file and sub in variables found in it
313 :param file: File to be read (typically from setup directory)
314 param subst_vars: Optional variables to subsitute in the file.
316 data
= open(file, 'r').read()
317 if subst_vars
is not None:
318 data
= substitute_var(data
, subst_vars
)
319 check_all_substituted(data
)
323 def setup_add_ldif(ldb
, ldif_path
, subst_vars
=None,controls
=["relax:0"]):
324 """Setup a ldb in the private dir.
326 :param ldb: LDB file to import data into
327 :param ldif_path: Path of the LDIF file to load
328 :param subst_vars: Optional variables to subsitute in LDIF.
329 :param nocontrols: Optional list of controls, can be None for no controls
331 assert isinstance(ldif_path
, str)
332 data
= read_and_sub_file(ldif_path
, subst_vars
)
333 ldb
.add_ldif(data
,controls
)
336 def setup_modify_ldif(ldb
, ldif_path
, subst_vars
=None):
337 """Modify a ldb in the private dir.
339 :param ldb: LDB object.
340 :param ldif_path: LDIF file path.
341 :param subst_vars: Optional dictionary with substitution variables.
343 data
= read_and_sub_file(ldif_path
, subst_vars
)
345 ldb
.modify_ldif(data
)
348 def setup_ldb(ldb
, ldif_path
, subst_vars
):
349 """Import a LDIF a file into a LDB handle, optionally substituting variables.
351 :note: Either all LDIF data will be added or none (using transactions).
353 :param ldb: LDB file to import into.
354 :param ldif_path: Path to the LDIF file.
355 :param subst_vars: Dictionary with substitution variables.
357 assert ldb
is not None
358 ldb
.transaction_start()
360 setup_add_ldif(ldb
, ldif_path
, subst_vars
)
362 ldb
.transaction_cancel()
364 ldb
.transaction_commit()
367 def setup_file(template
, fname
, subst_vars
=None):
368 """Setup a file in the private dir.
370 :param template: Path of the template file.
371 :param fname: Path of the file to create.
372 :param subst_vars: Substitution variables.
376 if os
.path
.exists(f
):
379 data
= read_and_sub_file(template
, subst_vars
)
380 open(f
, 'w').write(data
)
383 def provision_paths_from_lp(lp
, dnsdomain
):
384 """Set the default paths for provisioning.
386 :param lp: Loadparm context.
387 :param dnsdomain: DNS Domain name
389 paths
= ProvisionPaths()
390 paths
.private_dir
= lp
.get("private dir")
391 paths
.dns_keytab
= "dns.keytab"
393 paths
.shareconf
= os
.path
.join(paths
.private_dir
, "share.ldb")
394 paths
.samdb
= os
.path
.join(paths
.private_dir
, lp
.get("sam database") or "samdb.ldb")
395 paths
.idmapdb
= os
.path
.join(paths
.private_dir
, lp
.get("idmap database") or "idmap.ldb")
396 paths
.secrets
= os
.path
.join(paths
.private_dir
, lp
.get("secrets database") or "secrets.ldb")
397 paths
.privilege
= os
.path
.join(paths
.private_dir
, "privilege.ldb")
398 paths
.dns
= os
.path
.join(paths
.private_dir
, dnsdomain
+ ".zone")
399 paths
.namedconf
= os
.path
.join(paths
.private_dir
, "named.conf")
400 paths
.namedtxt
= os
.path
.join(paths
.private_dir
, "named.txt")
401 paths
.krb5conf
= os
.path
.join(paths
.private_dir
, "krb5.conf")
402 paths
.winsdb
= os
.path
.join(paths
.private_dir
, "wins.ldb")
403 paths
.s4_ldapi_path
= os
.path
.join(paths
.private_dir
, "ldapi")
404 paths
.phpldapadminconfig
= os
.path
.join(paths
.private_dir
,
405 "phpldapadmin-config.php")
406 paths
.ldapdir
= os
.path
.join(paths
.private_dir
,
408 paths
.slapdconf
= os
.path
.join(paths
.ldapdir
,
410 paths
.slapdpid
= os
.path
.join(paths
.ldapdir
,
412 paths
.modulesconf
= os
.path
.join(paths
.ldapdir
,
414 paths
.memberofconf
= os
.path
.join(paths
.ldapdir
,
416 paths
.fedoradsinf
= os
.path
.join(paths
.ldapdir
,
418 paths
.fedoradspartitions
= os
.path
.join(paths
.ldapdir
,
419 "fedorads-partitions.ldif")
420 paths
.fedoradssasl
= os
.path
.join(paths
.ldapdir
,
421 "fedorads-sasl.ldif")
422 paths
.fedoradsdna
= os
.path
.join(paths
.ldapdir
,
424 paths
.fedoradspam
= os
.path
.join(paths
.ldapdir
,
426 paths
.fedoradsrefint
= os
.path
.join(paths
.ldapdir
,
427 "fedorads-refint.ldif")
428 paths
.fedoradslinkedattributes
= os
.path
.join(paths
.ldapdir
,
429 "fedorads-linked-attributes.ldif")
430 paths
.fedoradsindex
= os
.path
.join(paths
.ldapdir
,
431 "fedorads-index.ldif")
432 paths
.fedoradssamba
= os
.path
.join(paths
.ldapdir
,
433 "fedorads-samba.ldif")
434 paths
.olmmrserveridsconf
= os
.path
.join(paths
.ldapdir
,
435 "mmr_serverids.conf")
436 paths
.olmmrsyncreplconf
= os
.path
.join(paths
.ldapdir
,
438 paths
.olcdir
= os
.path
.join(paths
.ldapdir
,
440 paths
.olcseedldif
= os
.path
.join(paths
.ldapdir
,
442 paths
.hklm
= "hklm.ldb"
443 paths
.hkcr
= "hkcr.ldb"
444 paths
.hkcu
= "hkcu.ldb"
445 paths
.hku
= "hku.ldb"
446 paths
.hkpd
= "hkpd.ldb"
447 paths
.hkpt
= "hkpt.ldb"
449 paths
.sysvol
= lp
.get("path", "sysvol")
451 paths
.netlogon
= lp
.get("path", "netlogon")
453 paths
.smbconf
= lp
.configfile
458 def guess_names(lp
=None, hostname
=None, domain
=None, dnsdomain
=None,
459 serverrole
=None, rootdn
=None, domaindn
=None, configdn
=None,
460 schemadn
=None, serverdn
=None, sitename
=None, sambadn
=None):
461 """Guess configuration settings to use."""
464 hostname
= socket
.gethostname().split(".")[0]
466 netbiosname
= lp
.get("netbios name")
467 if netbiosname
is None:
468 netbiosname
= hostname
469 assert netbiosname
is not None
470 netbiosname
= netbiosname
.upper()
471 if not valid_netbios_name(netbiosname
):
472 raise InvalidNetbiosName(netbiosname
)
474 if dnsdomain
is None:
475 dnsdomain
= lp
.get("realm")
476 assert dnsdomain
is not None
477 dnsdomain
= dnsdomain
.lower()
479 if serverrole
is None:
480 serverrole
= lp
.get("server role")
481 assert serverrole
is not None
482 serverrole
= serverrole
.lower()
484 realm
= dnsdomain
.upper()
486 if lp
.get("realm").upper() != realm
:
487 raise ProvisioningError("guess_names: Realm '%s' in smb.conf must match chosen realm '%s'!", lp
.get("realm").upper(), realm
)
489 if serverrole
== "domain controller":
491 domain
= lp
.get("workgroup")
492 assert domain
is not None
493 domain
= domain
.upper()
495 if lp
.get("workgroup").upper() != domain
:
496 raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'!", lp
.get("workgroup").upper(), domain
)
499 domaindn
= "DC=" + dnsdomain
.replace(".", ",DC=")
503 domaindn
= "DC=" + netbiosname
505 if not valid_netbios_name(domain
):
506 raise InvalidNetbiosName(domain
)
508 if hostname
.upper() == realm
:
509 raise ProvisioningError("guess_names: Realm '%s' must not be equal to hostname '%s'!", realm
, hostname
)
510 if netbiosname
== realm
:
511 raise ProvisioningError("guess_names: Realm '%s' must not be equal to netbios hostname '%s'!", realm
, netbiosname
)
513 raise ProvisioningError("guess_names: Realm '%s' must not be equal to short domain name '%s'!", realm
, domain
)
519 configdn
= "CN=Configuration," + rootdn
521 schemadn
= "CN=Schema," + configdn
528 names
= ProvisionNames()
529 names
.rootdn
= rootdn
530 names
.domaindn
= domaindn
531 names
.configdn
= configdn
532 names
.schemadn
= schemadn
533 names
.sambadn
= sambadn
534 names
.ldapmanagerdn
= "CN=Manager," + rootdn
535 names
.dnsdomain
= dnsdomain
536 names
.domain
= domain
538 names
.netbiosname
= netbiosname
539 names
.hostname
= hostname
540 names
.sitename
= sitename
541 names
.serverdn
= "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname
, sitename
, configdn
)
546 def make_smbconf(smbconf
, setup_path
, hostname
, domain
, realm
, serverrole
,
547 targetdir
, sid_generator
):
548 """Create a new smb.conf file based on a couple of basic settings.
550 assert smbconf
is not None
552 hostname
= socket
.gethostname().split(".")[0]
553 netbiosname
= hostname
.upper()
555 if serverrole
is None:
556 serverrole
= "standalone"
558 assert serverrole
in ("domain controller", "member server", "standalone")
559 if serverrole
== "domain controller":
561 elif serverrole
== "member server":
562 smbconfsuffix
= "member"
563 elif serverrole
== "standalone":
564 smbconfsuffix
= "standalone"
566 if sid_generator
is None:
567 sid_generator
= "internal"
569 assert domain
is not None
570 domain
= domain
.upper()
572 assert realm
is not None
573 realm
= realm
.upper()
575 default_lp
= param
.LoadParm()
576 #Load non-existant file
577 if os
.path
.exists(smbconf
):
578 default_lp
.load(smbconf
)
580 if targetdir
is not None:
581 privatedir_line
= "private dir = " + os
.path
.abspath(os
.path
.join(targetdir
, "private"))
582 lockdir_line
= "lock dir = " + os
.path
.abspath(targetdir
)
584 default_lp
.set("lock dir", os
.path
.abspath(targetdir
))
589 if sid_generator
== "internal":
590 sid_generator_line
= ""
592 sid_generator_line
= "sid generator = " + sid_generator
594 sysvol
= os
.path
.join(default_lp
.get("lock dir"), "sysvol")
595 netlogon
= os
.path
.join(sysvol
, realm
.lower(), "scripts")
597 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix
),
599 "NETBIOS_NAME": netbiosname
,
602 "SERVERROLE": serverrole
,
603 "NETLOGONPATH": netlogon
,
604 "SYSVOLPATH": sysvol
,
605 "SIDGENERATOR_LINE": sid_generator_line
,
606 "PRIVATEDIR_LINE": privatedir_line
,
607 "LOCKDIR_LINE": lockdir_line
611 def setup_name_mappings(samdb
, idmap
, sid
, domaindn
, root_uid
, nobody_uid
,
612 users_gid
, wheel_gid
):
613 """setup reasonable name mappings for sam names to unix names.
615 :param samdb: SamDB object.
616 :param idmap: IDmap db object.
617 :param sid: The domain sid.
618 :param domaindn: The domain DN.
619 :param root_uid: uid of the UNIX root user.
620 :param nobody_uid: uid of the UNIX nobody user.
621 :param users_gid: gid of the UNIX users group.
622 :param wheel_gid: gid of the UNIX wheel group."""
624 idmap
.setup_name_mapping("S-1-5-7", idmap
.TYPE_UID
, nobody_uid
)
625 idmap
.setup_name_mapping("S-1-5-32-544", idmap
.TYPE_GID
, wheel_gid
)
627 idmap
.setup_name_mapping(sid
+ "-500", idmap
.TYPE_UID
, root_uid
)
628 idmap
.setup_name_mapping(sid
+ "-513", idmap
.TYPE_GID
, users_gid
)
630 def setup_samdb_partitions(samdb_path
, setup_path
, message
, lp
, session_info
,
631 provision_backend
, names
, schema
,
634 """Setup the partitions for the SAM database.
636 Alternatively, provision() may call this, and then populate the database.
638 :note: This will wipe the Sam Database!
640 :note: This function always removes the local SAM LDB file. The erase
641 parameter controls whether to erase the existing data, which
642 may not be stored locally but in LDAP.
645 assert session_info
is not None
647 old_partitions
= None
648 new_partitions
= None
650 # We use options=["modules:"] to stop the modules loading - we
651 # just want to wipe and re-initialise the database, not start it up
654 samdb
= Ldb(url
=samdb_path
, session_info
=session_info
,
655 lp
=lp
, options
=["modules:"])
656 res
= samdb
.search(base
="@PARTITION", scope
=SCOPE_BASE
, attrs
=["partition"], expression
="partition=*")
659 old_partitions
= res
[0]["partition"]
663 if old_partitions
is not None:
665 for old_partition
in old_partitions
:
666 new_partition
= old_partition
667 if old_partition
.endswith(".ldb"):
668 p
= old_partition
.split(":")[0]
669 dn
= ldb
.Dn(schema
.ldb
, p
)
670 new_partition
= dn
.get_casefold()
671 new_partitions
.append(new_partition
)
674 samdb
.erase_except_schema_controlled()
676 os
.unlink(samdb_path
)
677 samdb
= Ldb(url
=samdb_path
, session_info
=session_info
,
678 lp
=lp
, options
=["modules:"])
680 samdb
.erase_except_schema_controlled()
682 #Add modules to the list to activate them by default
683 #beware often order is important
685 # Some Known ordering constraints:
686 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
687 # - objectclass must be before password_hash, because password_hash checks
688 # that the objectclass is of type person (filled in by objectclass
689 # module when expanding the objectclass list)
690 # - partition must be last
691 # - each partition has its own module list then
692 modules_list
= ["resolve_oids",
715 "extended_dn_out_ldb"]
716 modules_list2
= ["show_deleted",
721 ldap_backend_line
= "# No LDAP backend"
722 if provision_backend
.type is not "ldb":
723 ldap_backend_line
= "ldapBackend: %s" % provision_backend
.ldapi_uri
725 if provision_backend
.ldap_backend_type
== "fedora-ds":
726 backend_modules
= ["nsuniqueid", "paged_searches"]
727 # We can handle linked attributes here, as we don't have directory-side subtree operations
728 tdb_modules_list
= ["extended_dn_out_fds"]
729 elif ldap_backend
.ldap_backend_type
== "openldap":
730 backend_modules
= ["entryuuid", "paged_searches"]
731 # OpenLDAP handles subtree renames, so we don't want to do any of these things
732 tdb_modules_list
= ["extended_dn_out_openldap"]
734 elif serverrole
== "domain controller":
735 tdb_modules_list
.insert(0, "repl_meta_data")
738 backend_modules
= ["objectguid"]
740 if tdb_modules_list
is None:
741 tdb_modules_list_as_string
= ""
743 tdb_modules_list_as_string
= ","+",".join(tdb_modules_list
)
745 samdb
.transaction_start()
747 message("Setting up sam.ldb partitions and settings")
748 setup_add_ldif(samdb
, setup_path("provision_partitions.ldif"), {
749 "SCHEMADN": ldb
.Dn(schema
.ldb
, names
.schemadn
).get_casefold(),
750 "SCHEMADN_MOD2": ",objectguid",
751 "CONFIGDN": ldb
.Dn(schema
.ldb
, names
.configdn
).get_casefold(),
752 "DOMAINDN": ldb
.Dn(schema
.ldb
, names
.domaindn
).get_casefold(),
753 "SCHEMADN_MOD": "schema_data",
754 "CONFIGDN_MOD": "naming_fsmo",
755 "DOMAINDN_MOD": "pdc_fsmo",
756 "MODULES_LIST": ",".join(modules_list
),
757 "TDB_MODULES_LIST": tdb_modules_list_as_string
,
758 "MODULES_LIST2": ",".join(modules_list2
),
759 "BACKEND_MOD": ",".join(backend_modules
),
760 "LDAP_BACKEND_LINE": ldap_backend_line
,
764 if new_partitions
is not None:
766 m
.dn
= ldb
.Dn(samdb
, "@PARTITION")
768 m
["partition"] = ldb
.MessageElement(new_partitions
, ldb
.FLAG_MOD_ADD
, "partition")
771 samdb
.load_ldif_file_add(setup_path("provision_init.ldif"))
773 message("Setting up sam.ldb rootDSE")
774 setup_samdb_rootdse(samdb
, setup_path
, names
)
777 samdb
.transaction_cancel()
780 samdb
.transaction_commit()
783 def secretsdb_self_join(secretsdb
, domain
,
784 netbiosname
, domainsid
, machinepass
,
785 realm
=None, dnsdomain
=None,
787 key_version_number
=1,
788 secure_channel_type
=SEC_CHAN_WKSTA
):
789 """Add domain join-specific bits to a secrets database.
791 :param secretsdb: Ldb Handle to the secrets database
792 :param machinepass: Machine password
794 attrs
=["whenChanged",
802 msg
= ldb
.Message(ldb
.Dn(secretsdb
, "flatname=%s,cn=Primary Domains" % domain
));
803 msg
["secureChannelType"] = str(secure_channel_type
)
804 msg
["flatname"] = [domain
]
805 msg
["objectClass"] = ["top", "primaryDomain"]
806 if realm
is not None:
807 if dnsdomain
is None:
808 dnsdomain
= realm
.lower()
809 msg
["objectClass"] = ["top", "primaryDomain", "kerberosSecret"]
811 msg
["saltPrincipal"] = "host/%s.%s@%s" % (netbiosname
.lower(), dnsdomain
.lower(), realm
.upper())
812 msg
["msDS-KeyVersionNumber"] = [str(key_version_number
)]
813 msg
["privateKeytab"] = ["secrets.keytab"];
816 msg
["secret"] = [machinepass
]
817 msg
["samAccountName"] = ["%s$" % netbiosname
]
818 msg
["secureChannelType"] = [str(secure_channel_type
)]
819 msg
["objectSid"] = [ndr_pack(domainsid
)]
821 res
= secretsdb
.search(base
="cn=Primary Domains",
823 expression
=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain))" % (domain
, realm
, str(domainsid
))),
824 scope
=SCOPE_ONELEVEL
)
827 if del_msg
.dn
is not msg
.dn
:
828 secretsdb
.delete(del_msg
.dn
)
830 res
= secretsdb
.search(base
=msg
.dn
, attrs
=attrs
, scope
=SCOPE_BASE
)
833 msg
["priorSecret"] = res
[0]["secret"]
834 msg
["priorWhenChanged"] = res
[0]["whenChanged"]
836 if res
["privateKeytab"] is not None:
837 msg
["privateKeytab"] = res
[0]["privateKeytab"]
839 if res
["krb5Keytab"] is not None:
840 msg
["krb5Keytab"] = res
[0]["krb5Keytab"]
843 el
.set_flags(ldb
.FLAG_MOD_REPLACE
)
844 secretsdb
.modify(msg
)
849 def secretsdb_setup_dns(secretsdb
, setup_path
, realm
, dnsdomain
,
850 dns_keytab_path
, dnspass
):
851 """Add DNS specific bits to a secrets database.
853 :param secretsdb: Ldb Handle to the secrets database
854 :param setup_path: Setup path function
855 :param machinepass: Machine password
857 setup_ldb(secretsdb
, setup_path("secrets_dns.ldif"), {
859 "DNSDOMAIN": dnsdomain
,
860 "DNS_KEYTAB": dns_keytab_path
,
861 "DNSPASS_B64": b64encode(dnspass
),
865 def setup_secretsdb(path
, setup_path
, session_info
, backend_credentials
, lp
):
866 """Setup the secrets database.
868 :param path: Path to the secrets database.
869 :param setup_path: Get the path to a setup file.
870 :param session_info: Session info.
871 :param credentials: Credentials
872 :param lp: Loadparm context
873 :return: LDB handle for the created secrets database
875 if os
.path
.exists(path
):
877 secrets_ldb
= Ldb(path
, session_info
=session_info
,
880 secrets_ldb
.load_ldif_file_add(setup_path("secrets_init.ldif"))
881 secrets_ldb
= Ldb(path
, session_info
=session_info
,
883 secrets_ldb
.transaction_start()
884 secrets_ldb
.load_ldif_file_add(setup_path("secrets.ldif"))
886 if backend_credentials
is not None and backend_credentials
.authentication_requested():
887 if backend_credentials
.get_bind_dn() is not None:
888 setup_add_ldif(secrets_ldb
, setup_path("secrets_simple_ldap.ldif"), {
889 "LDAPMANAGERDN": backend_credentials
.get_bind_dn(),
890 "LDAPMANAGERPASS_B64": b64encode(backend_credentials
.get_password())
893 setup_add_ldif(secrets_ldb
, setup_path("secrets_sasl_ldap.ldif"), {
894 "LDAPADMINUSER": backend_credentials
.get_username(),
895 "LDAPADMINREALM": backend_credentials
.get_realm(),
896 "LDAPADMINPASS_B64": b64encode(backend_credentials
.get_password())
901 def setup_privileges(path
, setup_path
, session_info
, lp
):
902 """Setup the privileges database.
904 :param path: Path to the privileges database.
905 :param setup_path: Get the path to a setup file.
906 :param session_info: Session info.
907 :param credentials: Credentials
908 :param lp: Loadparm context
909 :return: LDB handle for the created secrets database
911 if os
.path
.exists(path
):
913 privilege_ldb
= Ldb(path
, session_info
=session_info
, lp
=lp
)
914 privilege_ldb
.erase()
915 privilege_ldb
.load_ldif_file_add(setup_path("provision_privilege.ldif"))
918 def setup_registry(path
, setup_path
, session_info
, lp
):
919 """Setup the registry.
921 :param path: Path to the registry database
922 :param setup_path: Function that returns the path to a setup.
923 :param session_info: Session information
924 :param credentials: Credentials
925 :param lp: Loadparm context
927 reg
= registry
.Registry()
928 hive
= registry
.open_ldb(path
, session_info
=session_info
,
930 reg
.mount_hive(hive
, registry
.HKEY_LOCAL_MACHINE
)
931 provision_reg
= setup_path("provision.reg")
932 assert os
.path
.exists(provision_reg
)
933 reg
.diff_apply(provision_reg
)
936 def setup_idmapdb(path
, setup_path
, session_info
, lp
):
937 """Setup the idmap database.
939 :param path: path to the idmap database
940 :param setup_path: Function that returns a path to a setup file
941 :param session_info: Session information
942 :param credentials: Credentials
943 :param lp: Loadparm context
945 if os
.path
.exists(path
):
948 idmap_ldb
= IDmapDB(path
, session_info
=session_info
,
952 idmap_ldb
.load_ldif_file_add(setup_path("idmap_init.ldif"))
956 def setup_samdb_rootdse(samdb
, setup_path
, names
):
957 """Setup the SamDB rootdse.
959 :param samdb: Sam Database handle
960 :param setup_path: Obtain setup path
962 setup_add_ldif(samdb
, setup_path("provision_rootdse_add.ldif"), {
963 "SCHEMADN": names
.schemadn
,
964 "NETBIOSNAME": names
.netbiosname
,
965 "DNSDOMAIN": names
.dnsdomain
,
966 "REALM": names
.realm
,
967 "DNSNAME": "%s.%s" % (names
.hostname
, names
.dnsdomain
),
968 "DOMAINDN": names
.domaindn
,
969 "ROOTDN": names
.rootdn
,
970 "CONFIGDN": names
.configdn
,
971 "SERVERDN": names
.serverdn
,
975 def setup_self_join(samdb
, names
,
976 machinepass
, dnspass
,
977 domainsid
, invocationid
, setup_path
,
978 policyguid
, policyguid_dc
, domainControllerFunctionality
,
980 """Join a host to its own domain."""
981 assert isinstance(invocationid
, str)
982 if ntdsguid
is not None:
983 ntdsguid_line
= "objectGUID: %s\n"%ntdsguid
986 setup_add_ldif(samdb
, setup_path("provision_self_join.ldif"), {
987 "CONFIGDN": names
.configdn
,
988 "SCHEMADN": names
.schemadn
,
989 "DOMAINDN": names
.domaindn
,
990 "SERVERDN": names
.serverdn
,
991 "INVOCATIONID": invocationid
,
992 "NETBIOSNAME": names
.netbiosname
,
993 "DEFAULTSITE": names
.sitename
,
994 "DNSNAME": "%s.%s" % (names
.hostname
, names
.dnsdomain
),
995 "MACHINEPASS_B64": b64encode(machinepass
),
996 "DNSPASS_B64": b64encode(dnspass
),
997 "REALM": names
.realm
,
998 "DOMAIN": names
.domain
,
999 "DNSDOMAIN": names
.dnsdomain
,
1000 "SAMBA_VERSION_STRING": version
,
1001 "NTDSGUID": ntdsguid_line
,
1002 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality
)})
1004 setup_add_ldif(samdb
, setup_path("provision_group_policy.ldif"), {
1005 "POLICYGUID": policyguid
,
1006 "POLICYGUID_DC": policyguid_dc
,
1007 "DNSDOMAIN": names
.dnsdomain
,
1008 "DOMAINSID": str(domainsid
),
1009 "DOMAINDN": names
.domaindn
})
1011 # add the NTDSGUID based SPNs
1012 ntds_dn
= "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names
.hostname
, names
.domaindn
)
1013 names
.ntdsguid
= samdb
.searchone(basedn
=ntds_dn
, attribute
="objectGUID",
1014 expression
="", scope
=SCOPE_BASE
)
1015 assert isinstance(names
.ntdsguid
, str)
1017 # Setup fSMORoleOwner entries to point at the newly created DC entry
1018 setup_modify_ldif(samdb
, setup_path("provision_self_join_modify.ldif"), {
1019 "DOMAIN": names
.domain
,
1020 "DNSDOMAIN": names
.dnsdomain
,
1021 "DOMAINDN": names
.domaindn
,
1022 "CONFIGDN": names
.configdn
,
1023 "SCHEMADN": names
.schemadn
,
1024 "DEFAULTSITE": names
.sitename
,
1025 "SERVERDN": names
.serverdn
,
1026 "NETBIOSNAME": names
.netbiosname
,
1027 "NTDSGUID": names
.ntdsguid
1031 def setup_samdb(path
, setup_path
, session_info
, provision_backend
, lp
,
1033 domainsid
, domainguid
, policyguid
, policyguid_dc
,
1034 fill
, adminpass
, krbtgtpass
,
1035 machinepass
, invocationid
, dnspass
, ntdsguid
,
1036 serverrole
, dom_for_fun_level
=None,
1038 """Setup a complete SAM Database.
1040 :note: This will wipe the main SAM database file!
1043 # ATTENTION: Do NOT change these default values without discussion with the
1044 # team and/or release manager. They have a big impact on the whole program!
1045 domainControllerFunctionality
= DS_DC_FUNCTION_2008
1047 if dom_for_fun_level
is None:
1048 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2003
1049 if dom_for_fun_level
< DS_DOMAIN_FUNCTION_2003
:
1050 raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level lower than Windows 2003 (Native). This isn't supported!")
1052 if dom_for_fun_level
> domainControllerFunctionality
:
1053 raise ProvisioningError("You want to run SAMBA 4 on a domain and forest function level which itself is higher than its actual DC function level (2008). This won't work!")
1055 domainFunctionality
= dom_for_fun_level
1056 forestFunctionality
= dom_for_fun_level
1058 # Also wipes the database
1059 setup_samdb_partitions(path
, setup_path
, message
=message
, lp
=lp
,
1060 provision_backend
=provision_backend
, session_info
=session_info
,
1062 serverrole
=serverrole
, schema
=schema
)
1064 if (schema
== None):
1065 schema
= Schema(setup_path
, domainsid
, schemadn
=names
.schemadn
, serverdn
=names
.serverdn
,
1066 sambadn
=names
.sambadn
)
1068 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
1069 samdb
= Ldb(session_info
=session_info
,
1070 credentials
=provision_backend
.credentials
, lp
=lp
)
1072 message("Pre-loading the Samba 4 and AD schema")
1074 # Load the schema from the one we computed earlier
1075 samdb
.set_schema_from_ldb(schema
.ldb
)
1077 # And now we can connect to the DB - the schema won't be loaded from the DB
1080 if fill
== FILL_DRS
:
1083 samdb
.transaction_start()
1085 message("Erasing data from partitions")
1086 # Load the schema (again). This time it will force a reindex,
1087 # and will therefore make the erase_partitions() below
1088 # computationally sane
1089 samdb
.set_schema_from_ldb(schema
.ldb
)
1090 samdb
.erase_partitions()
1092 # Set the domain functionality levels onto the database.
1093 # Various module (the password_hash module in particular) need
1094 # to know what level of AD we are emulating.
1096 # These will be fixed into the database via the database
1097 # modifictions below, but we need them set from the start.
1098 samdb
.set_opaque_integer("domainFunctionality", domainFunctionality
)
1099 samdb
.set_opaque_integer("forestFunctionality", forestFunctionality
)
1100 samdb
.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality
)
1102 samdb
.set_domain_sid(str(domainsid
))
1103 if serverrole
== "domain controller":
1104 samdb
.set_invocation_id(invocationid
)
1106 message("Adding DomainDN: %s" % names
.domaindn
)
1108 #impersonate domain admin
1109 admin_session_info
= admin_session(lp
, str(domainsid
))
1110 samdb
.set_session_info(admin_session_info
)
1111 if domainguid
is not None:
1112 domainguid_line
= "objectGUID: %s\n-" % domainguid
1114 domainguid_line
= ""
1115 setup_add_ldif(samdb
, setup_path("provision_basedn.ldif"), {
1116 "DOMAINDN": names
.domaindn
,
1117 "DOMAINGUID": domainguid_line
1121 setup_modify_ldif(samdb
, setup_path("provision_basedn_modify.ldif"), {
1122 "CREATTIME": str(int(time
.time() * 1e7
)), # seconds -> ticks
1123 "DOMAINSID": str(domainsid
),
1124 "SCHEMADN": names
.schemadn
,
1125 "NETBIOSNAME": names
.netbiosname
,
1126 "DEFAULTSITE": names
.sitename
,
1127 "CONFIGDN": names
.configdn
,
1128 "SERVERDN": names
.serverdn
,
1129 "POLICYGUID": policyguid
,
1130 "DOMAINDN": names
.domaindn
,
1131 "DOMAIN_FUNCTIONALITY": str(domainFunctionality
),
1132 "SAMBA_VERSION_STRING": version
1135 message("Adding configuration container")
1136 descr
= get_config_descriptor(domainsid
);
1137 setup_add_ldif(samdb
, setup_path("provision_configuration_basedn.ldif"), {
1138 "CONFIGDN": names
.configdn
,
1139 "DESCRIPTOR": descr
,
1141 message("Modifying configuration container")
1142 setup_modify_ldif(samdb
, setup_path("provision_configuration_basedn_modify.ldif"), {
1143 "CONFIGDN": names
.configdn
,
1144 "SCHEMADN": names
.schemadn
,
1147 # The LDIF here was created when the Schema object was constructed
1148 message("Setting up sam.ldb schema")
1149 samdb
.add_ldif(schema
.schema_dn_add
, controls
=["relax:0"])
1150 samdb
.modify_ldif(schema
.schema_dn_modify
)
1151 samdb
.write_prefixes_from_schema()
1152 samdb
.add_ldif(schema
.schema_data
, controls
=["relax:0"])
1153 setup_add_ldif(samdb
, setup_path("aggregate_schema.ldif"),
1154 {"SCHEMADN": names
.schemadn
})
1156 message("Setting up sam.ldb configuration data")
1157 setup_add_ldif(samdb
, setup_path("provision_configuration.ldif"), {
1158 "CONFIGDN": names
.configdn
,
1159 "NETBIOSNAME": names
.netbiosname
,
1160 "DEFAULTSITE": names
.sitename
,
1161 "DNSDOMAIN": names
.dnsdomain
,
1162 "DOMAIN": names
.domain
,
1163 "SCHEMADN": names
.schemadn
,
1164 "DOMAINDN": names
.domaindn
,
1165 "SERVERDN": names
.serverdn
,
1166 "FOREST_FUNCTIONALALITY": str(forestFunctionality
)
1169 message("Setting up display specifiers")
1170 display_specifiers_ldif
= read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
1171 display_specifiers_ldif
= substitute_var(display_specifiers_ldif
, {"CONFIGDN": names
.configdn
})
1172 check_all_substituted(display_specifiers_ldif
)
1173 samdb
.add_ldif(display_specifiers_ldif
)
1175 message("Adding users container")
1176 setup_add_ldif(samdb
, setup_path("provision_users_add.ldif"), {
1177 "DOMAINDN": names
.domaindn
})
1178 message("Modifying users container")
1179 setup_modify_ldif(samdb
, setup_path("provision_users_modify.ldif"), {
1180 "DOMAINDN": names
.domaindn
})
1181 message("Adding computers container")
1182 setup_add_ldif(samdb
, setup_path("provision_computers_add.ldif"), {
1183 "DOMAINDN": names
.domaindn
})
1184 message("Modifying computers container")
1185 setup_modify_ldif(samdb
, setup_path("provision_computers_modify.ldif"), {
1186 "DOMAINDN": names
.domaindn
})
1187 message("Setting up sam.ldb data")
1188 setup_add_ldif(samdb
, setup_path("provision.ldif"), {
1189 "CREATTIME": str(int(time
.time() * 1e7
)), # seconds -> ticks
1190 "DOMAINDN": names
.domaindn
,
1191 "NETBIOSNAME": names
.netbiosname
,
1192 "DEFAULTSITE": names
.sitename
,
1193 "CONFIGDN": names
.configdn
,
1194 "SERVERDN": names
.serverdn
,
1195 "POLICYGUID_DC": policyguid_dc
1198 if fill
== FILL_FULL
:
1199 message("Setting up sam.ldb users and groups")
1200 setup_add_ldif(samdb
, setup_path("provision_users.ldif"), {
1201 "DOMAINDN": names
.domaindn
,
1202 "DOMAINSID": str(domainsid
),
1203 "CONFIGDN": names
.configdn
,
1204 "ADMINPASS_B64": b64encode(adminpass
),
1205 "KRBTGTPASS_B64": b64encode(krbtgtpass
),
1208 if serverrole
== "domain controller":
1209 message("Setting up self join")
1210 setup_self_join(samdb
, names
=names
, invocationid
=invocationid
,
1212 machinepass
=machinepass
,
1213 domainsid
=domainsid
, policyguid
=policyguid
,
1214 policyguid_dc
=policyguid_dc
,
1215 setup_path
=setup_path
,
1216 domainControllerFunctionality
=domainControllerFunctionality
,
1219 ntds_dn
= "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names
.hostname
, names
.domaindn
)
1220 names
.ntdsguid
= samdb
.searchone(basedn
=ntds_dn
,
1221 attribute
="objectGUID", expression
="", scope
=SCOPE_BASE
)
1222 assert isinstance(names
.ntdsguid
, str)
1225 samdb
.transaction_cancel()
1228 samdb
.transaction_commit()
1233 FILL_NT4SYNC
= "NT4SYNC"
1237 def provision(setup_dir
, message
, session_info
,
1238 credentials
, smbconf
=None, targetdir
=None, samdb_fill
=FILL_FULL
,
1240 rootdn
=None, domaindn
=None, schemadn
=None, configdn
=None,
1242 domain
=None, hostname
=None, hostip
=None, hostip6
=None,
1243 domainsid
=None, adminpass
=None, ldapadminpass
=None,
1244 krbtgtpass
=None, domainguid
=None,
1245 policyguid
=None, policyguid_dc
=None, invocationid
=None,
1246 machinepass
=None, ntdsguid
=None,
1247 dnspass
=None, root
=None, nobody
=None, users
=None,
1248 wheel
=None, backup
=None, aci
=None, serverrole
=None,
1249 dom_for_fun_level
=None,
1250 ldap_backend_extra_port
=None, backend_type
=None,
1252 ol_mmr_urls
=None, ol_olc
=None,
1253 setup_ds_path
=None, slapd_path
=None, nosync
=False,
1254 ldap_dryrun_mode
=False):
1257 :note: caution, this wipes all existing data!
1260 def setup_path(file):
1261 return os
.path
.join(setup_dir
, file)
1263 if domainsid
is None:
1264 domainsid
= security
.random_sid()
1266 domainsid
= security
.dom_sid(domainsid
)
1268 # create/adapt the group policy GUIDs
1269 if policyguid
is None:
1270 policyguid
= str(uuid
.uuid4())
1271 policyguid
= policyguid
.upper()
1272 if policyguid_dc
is None:
1273 policyguid_dc
= str(uuid
.uuid4())
1274 policyguid_dc
= policyguid_dc
.upper()
1276 if adminpass
is None:
1277 adminpass
= glue
.generate_random_str(12)
1278 if krbtgtpass
is None:
1279 krbtgtpass
= glue
.generate_random_str(12)
1280 if machinepass
is None:
1281 machinepass
= glue
.generate_random_str(12)
1283 dnspass
= glue
.generate_random_str(12)
1284 if ldapadminpass
is None:
1285 #Make a new, random password between Samba and it's LDAP server
1286 ldapadminpass
=glue
.generate_random_str(12)
1288 if backend_type
is None:
1289 backend_type
= "ldb"
1291 sid_generator
= "internal"
1292 if backend_type
== "fedora-ds":
1293 sid_generator
= "backend"
1295 root_uid
= findnss_uid([root
or "root"])
1296 nobody_uid
= findnss_uid([nobody
or "nobody"])
1297 users_gid
= findnss_gid([users
or "users"])
1299 wheel_gid
= findnss_gid(["wheel", "adm"])
1301 wheel_gid
= findnss_gid([wheel
])
1303 if targetdir
is not None:
1304 if (not os
.path
.exists(os
.path
.join(targetdir
, "etc"))):
1305 os
.makedirs(os
.path
.join(targetdir
, "etc"))
1306 smbconf
= os
.path
.join(targetdir
, "etc", "smb.conf")
1307 elif smbconf
is None:
1308 smbconf
= param
.default_path()
1310 # only install a new smb.conf if there isn't one there already
1311 if not os
.path
.exists(smbconf
):
1312 make_smbconf(smbconf
, setup_path
, hostname
, domain
, realm
, serverrole
,
1313 targetdir
, sid_generator
)
1315 lp
= param
.LoadParm()
1318 names
= guess_names(lp
=lp
, hostname
=hostname
, domain
=domain
,
1319 dnsdomain
=realm
, serverrole
=serverrole
,
1320 domaindn
=domaindn
, configdn
=configdn
, schemadn
=schemadn
,
1321 serverdn
=serverdn
, sitename
=sitename
)
1323 paths
= provision_paths_from_lp(lp
, names
.dnsdomain
)
1327 hostip
= socket
.getaddrinfo(names
.hostname
, None, socket
.AF_INET
, socket
.AI_CANONNAME
, socket
.IPPROTO_IP
)[0][-1][0]
1328 except socket
.gaierror
, (socket
.EAI_NODATA
, msg
):
1333 hostip6
= socket
.getaddrinfo(names
.hostname
, None, socket
.AF_INET6
, socket
.AI_CANONNAME
, socket
.IPPROTO_IP
)[0][-1][0]
1334 except socket
.gaierror
, (socket
.EAI_NODATA
, msg
):
1337 if serverrole
is None:
1338 serverrole
= lp
.get("server role")
1340 assert serverrole
in ("domain controller", "member server", "standalone")
1341 if invocationid
is None and serverrole
== "domain controller":
1342 invocationid
= str(uuid
.uuid4())
1344 if not os
.path
.exists(paths
.private_dir
):
1345 os
.mkdir(paths
.private_dir
)
1347 ldapi_url
= "ldapi://%s" % urllib
.quote(paths
.s4_ldapi_path
, safe
="")
1349 schema
= Schema(setup_path
, domainsid
, schemadn
=names
.schemadn
, serverdn
=names
.serverdn
,
1350 sambadn
=names
.sambadn
)
1352 provision_backend
= ProvisionBackend(backend_type
,
1353 paths
=paths
, setup_path
=setup_path
,
1354 lp
=lp
, credentials
=credentials
,
1356 message
=message
, hostname
=hostname
,
1357 root
=root
, schema
=schema
,
1358 ldapadminpass
=ldapadminpass
,
1359 ldap_backend_extra_port
=ldap_backend_extra_port
,
1360 ol_mmr_urls
=ol_mmr_urls
,
1361 slapd_path
=slapd_path
,
1362 setup_ds_path
=setup_ds_path
,
1363 ldap_dryrun_mode
=ldap_dryrun_mode
,
1364 domainsid
=domainsid
)
1366 # only install a new shares config db if there is none
1367 if not os
.path
.exists(paths
.shareconf
):
1368 message("Setting up share.ldb")
1369 share_ldb
= Ldb(paths
.shareconf
, session_info
=session_info
,
1371 share_ldb
.load_ldif_file_add(setup_path("share.ldif"))
1374 message("Setting up secrets.ldb")
1375 secrets_ldb
= setup_secretsdb(paths
.secrets
, setup_path
,
1376 session_info
=session_info
,
1377 backend_credentials
=provision_backend
.secrets_credentials
, lp
=lp
)
1379 message("Setting up the registry")
1380 setup_registry(paths
.hklm
, setup_path
, session_info
,
1383 message("Setting up the privileges database")
1384 setup_privileges(paths
.privilege
, setup_path
, session_info
, lp
=lp
)
1386 message("Setting up idmap db")
1387 idmap
= setup_idmapdb(paths
.idmapdb
, setup_path
, session_info
=session_info
,
1390 message("Setting up SAM db")
1391 samdb
= setup_samdb(paths
.samdb
, setup_path
, session_info
,
1392 provision_backend
, lp
, names
,
1394 domainsid
=domainsid
,
1395 schema
=schema
, domainguid
=domainguid
,
1396 policyguid
=policyguid
, policyguid_dc
=policyguid_dc
,
1398 adminpass
=adminpass
, krbtgtpass
=krbtgtpass
,
1399 invocationid
=invocationid
,
1400 machinepass
=machinepass
, dnspass
=dnspass
,
1401 ntdsguid
=ntdsguid
, serverrole
=serverrole
,
1402 dom_for_fun_level
=dom_for_fun_level
)
1404 if serverrole
== "domain controller":
1405 if paths
.netlogon
is None:
1406 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1407 message("Please either remove %s or see the template at %s" %
1408 ( paths
.smbconf
, setup_path("provision.smb.conf.dc")))
1409 assert(paths
.netlogon
is not None)
1411 if paths
.sysvol
is None:
1412 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1413 message("Please either remove %s or see the template at %s" %
1414 (paths
.smbconf
, setup_path("provision.smb.conf.dc")))
1415 assert(paths
.sysvol
is not None)
1417 # Set up group policies (domain policy and domain controller policy)
1419 policy_path
= os
.path
.join(paths
.sysvol
, names
.dnsdomain
, "Policies",
1420 "{" + policyguid
+ "}")
1421 os
.makedirs(policy_path
, 0755)
1422 open(os
.path
.join(policy_path
, "GPT.INI"), 'w').write(
1423 "[General]\r\nVersion=65543")
1424 os
.makedirs(os
.path
.join(policy_path
, "MACHINE"), 0755)
1425 os
.makedirs(os
.path
.join(policy_path
, "USER"), 0755)
1427 policy_path_dc
= os
.path
.join(paths
.sysvol
, names
.dnsdomain
, "Policies",
1428 "{" + policyguid_dc
+ "}")
1429 os
.makedirs(policy_path_dc
, 0755)
1430 open(os
.path
.join(policy_path_dc
, "GPT.INI"), 'w').write(
1431 "[General]\r\nVersion=2")
1432 os
.makedirs(os
.path
.join(policy_path_dc
, "MACHINE"), 0755)
1433 os
.makedirs(os
.path
.join(policy_path_dc
, "USER"), 0755)
1435 if not os
.path
.isdir(paths
.netlogon
):
1436 os
.makedirs(paths
.netlogon
, 0755)
1438 if samdb_fill
== FILL_FULL
:
1439 setup_name_mappings(samdb
, idmap
, str(domainsid
), names
.domaindn
,
1440 root_uid
=root_uid
, nobody_uid
=nobody_uid
,
1441 users_gid
=users_gid
, wheel_gid
=wheel_gid
)
1443 message("Setting up sam.ldb rootDSE marking as synchronized")
1444 setup_modify_ldif(samdb
, setup_path("provision_rootdse_modify.ldif"))
1446 # Only make a zone file on the first DC, it should be replicated with DNS replication
1447 if serverrole
== "domain controller":
1448 secretsdb_self_join(secrets_ldb
, domain
=domain
,
1450 dnsdomain
=names
.dnsdomain
,
1451 netbiosname
=names
.netbiosname
,
1452 domainsid
=domainsid
,
1453 machinepass
=machinepass
,
1454 secure_channel_type
=SEC_CHAN_BDC
)
1456 secretsdb_setup_dns(secrets_ldb
, setup_path
,
1457 realm
=names
.realm
, dnsdomain
=names
.dnsdomain
,
1458 dns_keytab_path
=paths
.dns_keytab
,
1461 domainguid
= samdb
.searchone(basedn
=domaindn
, attribute
="objectGUID")
1462 assert isinstance(domainguid
, str)
1464 create_zone_file(paths
.dns
, setup_path
, dnsdomain
=names
.dnsdomain
,
1466 hostip6
=hostip6
, hostname
=names
.hostname
,
1468 domainguid
=domainguid
, ntdsguid
=names
.ntdsguid
)
1470 create_named_conf(paths
.namedconf
, setup_path
, realm
=names
.realm
,
1471 dnsdomain
=names
.dnsdomain
, private_dir
=paths
.private_dir
)
1473 create_named_txt(paths
.namedtxt
, setup_path
, realm
=names
.realm
,
1474 dnsdomain
=names
.dnsdomain
, private_dir
=paths
.private_dir
,
1475 keytab_name
=paths
.dns_keytab
)
1476 message("See %s for an example configuration include file for BIND" % paths
.namedconf
)
1477 message("and %s for further documentation required for secure DNS updates" % paths
.namedtxt
)
1479 create_krb5_conf(paths
.krb5conf
, setup_path
,
1480 dnsdomain
=names
.dnsdomain
, hostname
=names
.hostname
,
1482 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths
.krb5conf
)
1484 if provision_backend
.post_setup
is not None:
1485 provision_backend
.post_setup()
1487 if provision_backend
.shutdown
is not None:
1488 provision_backend
.shutdown()
1490 create_phpldapadmin_config(paths
.phpldapadminconfig
, setup_path
,
1493 #Now commit the secrets.ldb to disk
1494 secrets_ldb
.transaction_commit()
1496 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths
.phpldapadminconfig
)
1498 message("Once the above files are installed, your Samba4 server will be ready to use")
1499 message("Server Role: %s" % serverrole
)
1500 message("Hostname: %s" % names
.hostname
)
1501 message("NetBIOS Domain: %s" % names
.domain
)
1502 message("DNS Domain: %s" % names
.dnsdomain
)
1503 message("DOMAIN SID: %s" % str(domainsid
))
1504 if samdb_fill
== FILL_FULL
:
1505 message("Admin password: %s" % adminpass
)
1506 if provision_backend
.type is not "ldb":
1507 if provision_backend
.credentials
.get_bind_dn() is not None:
1508 message("LDAP Backend Admin DN: %s" % provision_backend
.credentials
.get_bind_dn())
1510 message("LDAP Admin User: %s" % provision_backend
.credentials
.get_username())
1512 message("LDAP Admin Password: %s" % provision_backend
.credentials
.get_password())
1514 if provision_backend
.slapd_command_escaped
is not None:
1515 # now display slapd_command_file.txt to show how slapd must be started next time
1516 message("Use later the following commandline to start slapd, then Samba:")
1517 message(provision_backend
.slapd_command_escaped
)
1518 message("This slapd-Commandline is also stored under: " + paths
.ldapdir
+ "/ldap_backend_startup.sh")
1521 result
= ProvisionResult()
1522 result
.domaindn
= domaindn
1523 result
.paths
= paths
1525 result
.samdb
= samdb
1530 def provision_become_dc(setup_dir
=None,
1531 smbconf
=None, targetdir
=None, realm
=None,
1532 rootdn
=None, domaindn
=None, schemadn
=None,
1533 configdn
=None, serverdn
=None,
1534 domain
=None, hostname
=None, domainsid
=None,
1535 adminpass
=None, krbtgtpass
=None, domainguid
=None,
1536 policyguid
=None, policyguid_dc
=None, invocationid
=None,
1538 dnspass
=None, root
=None, nobody
=None, users
=None,
1539 wheel
=None, backup
=None, serverrole
=None,
1540 ldap_backend
=None, ldap_backend_type
=None,
1541 sitename
=None, debuglevel
=1):
1544 """print a message if quiet is not set."""
1547 glue
.set_debug_level(debuglevel
)
1549 return provision(setup_dir
, message
, system_session(), None,
1550 smbconf
=smbconf
, targetdir
=targetdir
, samdb_fill
=FILL_DRS
,
1551 realm
=realm
, rootdn
=rootdn
, domaindn
=domaindn
, schemadn
=schemadn
,
1552 configdn
=configdn
, serverdn
=serverdn
, domain
=domain
,
1553 hostname
=hostname
, hostip
="127.0.0.1", domainsid
=domainsid
,
1554 machinepass
=machinepass
, serverrole
="domain controller",
1558 def setup_db_config(setup_path
, dbdir
):
1559 """Setup a Berkeley database.
1561 :param setup_path: Setup path function.
1562 :param dbdir: Database directory."""
1563 if not os
.path
.isdir(os
.path
.join(dbdir
, "bdb-logs")):
1564 os
.makedirs(os
.path
.join(dbdir
, "bdb-logs"), 0700)
1565 if not os
.path
.isdir(os
.path
.join(dbdir
, "tmp")):
1566 os
.makedirs(os
.path
.join(dbdir
, "tmp"), 0700)
1568 setup_file(setup_path("DB_CONFIG"), os
.path
.join(dbdir
, "DB_CONFIG"),
1569 {"LDAPDBDIR": dbdir
})
1571 def ldap_backend_shutdown(self
):
1572 # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1573 if self
.slapd
.poll() is None:
1575 if hasattr(self
.slapd
, "terminate"):
1576 self
.slapd
.terminate()
1578 # Older python versions don't have .terminate()
1580 os
.kill(self
.slapd
.pid
, signal
.SIGTERM
)
1582 #and now wait for it to die
1583 self
.slapd
.communicate()
1586 class ProvisionBackend(object):
1587 def __init__(self
, backend_type
, paths
=None, setup_path
=None, lp
=None, credentials
=None,
1588 names
=None, message
=None,
1589 hostname
=None, root
=None,
1590 schema
=None, ldapadminpass
=None,
1591 ldap_backend_extra_port
=None,
1593 setup_ds_path
=None, slapd_path
=None,
1594 nosync
=False, ldap_dryrun_mode
=False,
1596 """Provision an LDAP backend for samba4
1598 This works for OpenLDAP and Fedora DS
1601 self
.slapd_command
= None
1602 self
.slapd_command_escaped
= None
1604 self
.type = backend_type
1606 # Set a default - the code for "existing" below replaces this
1607 self
.ldap_backend_type
= backend_type
1609 self
.post_setup
= None
1610 self
.shutdown
= None
1612 if self
.type is "ldb":
1613 self
.credentials
= None
1614 self
.secrets_credentials
= None
1617 self
.ldapi_uri
= "ldapi://" + urllib
.quote(os
.path
.join(paths
.ldapdir
, "ldapi"), safe
="")
1619 if self
.type == "existing":
1620 #Check to see that this 'existing' LDAP backend in fact exists
1621 ldapi_db
= Ldb(self
.ldapi_uri
, credentials
=credentials
)
1622 search_ol_rootdse
= ldapi_db
.search(base
="", scope
=SCOPE_BASE
,
1623 expression
="(objectClass=OpenLDAProotDSE)")
1625 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1626 self
.credentials
= credentials
1627 # This caused them to be set into the long-term database later in the script.
1628 self
.secrets_credentials
= credentials
1630 self
.ldap_backend_type
= "openldap" #For now, assume existing backends at least emulate OpenLDAP
1633 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1634 # if another instance of slapd is already running
1636 ldapi_db
= Ldb(self
.ldapi_uri
)
1637 search_ol_rootdse
= ldapi_db
.search(base
="", scope
=SCOPE_BASE
,
1638 expression
="(objectClass=OpenLDAProotDSE)");
1640 f
= open(paths
.slapdpid
, "r")
1643 message("Check for slapd Process with PID: " + str(p
) + " and terminate it manually.")
1647 raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self
.ldapi_uri
+ ". Please shut it down before you continue. ")
1652 # Try to print helpful messages when the user has not specified the path to slapd
1653 if slapd_path
is None:
1654 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1655 if not os
.path
.exists(slapd_path
):
1656 message (slapd_path
)
1657 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1660 if not os
.path
.isdir(paths
.ldapdir
):
1661 os
.makedirs(paths
.ldapdir
, 0700)
1663 # Put the LDIF of the schema into a database so we can search on
1664 # it to generate schema-dependent configurations in Fedora DS and
1666 schemadb_path
= os
.path
.join(paths
.ldapdir
, "schema-tmp.ldb")
1668 os
.unlink(schemadb_path
)
1672 schema
.write_to_tmp_ldb(schemadb_path
);
1674 self
.credentials
= Credentials()
1675 self
.credentials
.guess(lp
)
1676 #Kerberos to an ldapi:// backend makes no sense
1677 self
.credentials
.set_kerberos_state(DONT_USE_KERBEROS
)
1679 self
.secrets_credentials
= Credentials()
1680 self
.secrets_credentials
.guess(lp
)
1681 #Kerberos to an ldapi:// backend makes no sense
1682 self
.secrets_credentials
.set_kerberos_state(DONT_USE_KERBEROS
)
1684 self
.shutdown
= ldap_backend_shutdown
1686 if self
.type == "fedora-ds":
1687 provision_fds_backend(self
, setup_path
=setup_path
,
1688 names
=names
, message
=message
,
1690 ldapadminpass
=ldapadminpass
, root
=root
,
1692 ldap_backend_extra_port
=ldap_backend_extra_port
,
1693 setup_ds_path
=setup_ds_path
,
1694 slapd_path
=slapd_path
,
1696 ldap_dryrun_mode
=ldap_dryrun_mode
,
1697 domainsid
=domainsid
)
1699 elif self
.type == "openldap":
1700 provision_openldap_backend(self
, setup_path
=setup_path
,
1701 names
=names
, message
=message
,
1703 ldapadminpass
=ldapadminpass
, root
=root
,
1705 ldap_backend_extra_port
=ldap_backend_extra_port
,
1706 ol_mmr_urls
=ol_mmr_urls
,
1707 slapd_path
=slapd_path
,
1709 ldap_dryrun_mode
=ldap_dryrun_mode
)
1711 raise ProvisioningError("Unknown LDAP backend type selected")
1713 self
.credentials
.set_password(ldapadminpass
)
1714 self
.secrets_credentials
.set_username("samba-admin")
1715 self
.secrets_credentials
.set_password(ldapadminpass
)
1717 self
.slapd_command_escaped
= "\'" + "\' \'".join(self
.slapd_command
) + "\'"
1718 setup_file(setup_path("ldap_backend_startup.sh"), paths
.ldapdir
+ "/ldap_backend_startup.sh", {
1719 "SLAPD_COMMAND" : slapd_command
})
1721 # Now start the slapd, so we can provision onto it. We keep the
1722 # subprocess context around, to kill this off at the successful
1724 self
.slapd
= subprocess
.Popen(self
.slapd_provision_command
, close_fds
=True, shell
=False)
1726 while self
.slapd
.poll() is None:
1727 # Wait until the socket appears
1729 ldapi_db
= Ldb(self
.ldapi_uri
, lp
=lp
, credentials
=self
.credentials
)
1730 search_ol_rootdse
= ldapi_db
.search(base
="", scope
=SCOPE_BASE
,
1731 expression
="(objectClass=OpenLDAProotDSE)")
1732 # If we have got here, then we must have a valid connection to the LDAP server!
1738 raise ProvisioningError("slapd died before we could make a connection to it")
1741 def provision_openldap_backend(result
, setup_path
=None, names
=None,
1743 hostname
=None, ldapadminpass
=None, root
=None,
1745 ldap_backend_extra_port
=None,
1747 slapd_path
=None, nosync
=False,
1748 ldap_dryrun_mode
=False):
1750 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1753 nosync_config
= "dbnosync"
1755 lnkattr
= schema
.linked_attributes()
1756 refint_attributes
= ""
1757 memberof_config
= "# Generated from Samba4 schema\n"
1758 for att
in lnkattr
.keys():
1759 if lnkattr
[att
] is not None:
1760 refint_attributes
= refint_attributes
+ " " + att
1762 memberof_config
+= read_and_sub_file(setup_path("memberof.conf"),
1763 { "MEMBER_ATTR" : att
,
1764 "MEMBEROF_ATTR" : lnkattr
[att
] })
1766 refint_config
= read_and_sub_file(setup_path("refint.conf"),
1767 { "LINK_ATTRS" : refint_attributes
})
1769 attrs
= ["linkID", "lDAPDisplayName"]
1770 res
= schema
.ldb
.search(expression
="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base
=names
.schemadn
, scope
=SCOPE_ONELEVEL
, attrs
=attrs
)
1772 for i
in range (0, len(res
)):
1773 index_attr
= res
[i
]["lDAPDisplayName"][0]
1774 if index_attr
== "objectGUID":
1775 index_attr
= "entryUUID"
1777 index_config
+= "index " + index_attr
+ " eq\n"
1779 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1781 mmr_replicator_acl
= ""
1782 mmr_serverids_config
= ""
1783 mmr_syncrepl_schema_config
= ""
1784 mmr_syncrepl_config_config
= ""
1785 mmr_syncrepl_user_config
= ""
1788 if ol_mmr_urls
is not None:
1789 # For now, make these equal
1790 mmr_pass
= ldapadminpass
1792 url_list
=filter(None,ol_mmr_urls
.split(' '))
1793 if (len(url_list
) == 1):
1794 url_list
=filter(None,ol_mmr_urls
.split(','))
1797 mmr_on_config
= "MirrorMode On"
1798 mmr_replicator_acl
= " by dn=cn=replicator,cn=samba read"
1800 for url
in url_list
:
1802 mmr_serverids_config
+= read_and_sub_file(setup_path("mmr_serverids.conf"),
1803 { "SERVERID" : str(serverid
),
1804 "LDAPSERVER" : url
})
1807 mmr_syncrepl_schema_config
+= read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1809 "MMRDN": names
.schemadn
,
1811 "MMR_PASSWORD": mmr_pass
})
1814 mmr_syncrepl_config_config
+= read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1816 "MMRDN": names
.configdn
,
1818 "MMR_PASSWORD": mmr_pass
})
1821 mmr_syncrepl_user_config
+= read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1823 "MMRDN": names
.domaindn
,
1825 "MMR_PASSWORD": mmr_pass
})
1826 # OpenLDAP cn=config initialisation
1827 olc_syncrepl_config
= ""
1829 # if mmr = yes, generate cn=config-replication directives
1830 # and olc_seed.lif for the other mmr-servers
1831 if ol_mmr_urls
is not None:
1833 olc_serverids_config
= ""
1834 olc_syncrepl_seed_config
= ""
1835 olc_mmr_config
+= read_and_sub_file(setup_path("olc_mmr.conf"),{})
1837 for url
in url_list
:
1839 olc_serverids_config
+= read_and_sub_file(setup_path("olc_serverid.conf"),
1840 { "SERVERID" : str(serverid
),
1841 "LDAPSERVER" : url
})
1844 olc_syncrepl_config
+= read_and_sub_file(setup_path("olc_syncrepl.conf"),
1847 "MMR_PASSWORD": mmr_pass
})
1849 olc_syncrepl_seed_config
+= read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1851 "LDAPSERVER" : url
})
1853 setup_file(setup_path("olc_seed.ldif"), result
.paths
.olcseedldif
,
1854 {"OLC_SERVER_ID_CONF": olc_serverids_config
,
1855 "OLC_PW": ldapadminpass
,
1856 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config
})
1859 setup_file(setup_path("slapd.conf"), result
.paths
.slapdconf
,
1860 {"DNSDOMAIN": names
.dnsdomain
,
1861 "LDAPDIR": result
.paths
.ldapdir
,
1862 "DOMAINDN": names
.domaindn
,
1863 "CONFIGDN": names
.configdn
,
1864 "SCHEMADN": names
.schemadn
,
1865 "MEMBEROF_CONFIG": memberof_config
,
1866 "MIRRORMODE": mmr_on_config
,
1867 "REPLICATOR_ACL": mmr_replicator_acl
,
1868 "MMR_SERVERIDS_CONFIG": mmr_serverids_config
,
1869 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config
,
1870 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config
,
1871 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config
,
1872 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config
,
1873 "OLC_MMR_CONFIG": olc_mmr_config
,
1874 "REFINT_CONFIG": refint_config
,
1875 "INDEX_CONFIG": index_config
,
1876 "NOSYNC": nosync_config
})
1878 setup_db_config(setup_path
, os
.path
.join(result
.paths
.ldapdir
, "db", "user"))
1879 setup_db_config(setup_path
, os
.path
.join(result
.paths
.ldapdir
, "db", "config"))
1880 setup_db_config(setup_path
, os
.path
.join(result
.paths
.ldapdir
, "db", "schema"))
1882 if not os
.path
.exists(os
.path
.join(result
.paths
.ldapdir
, "db", "samba", "cn=samba")):
1883 os
.makedirs(os
.path
.join(result
.paths
.ldapdir
, "db", "samba", "cn=samba"), 0700)
1885 setup_file(setup_path("cn=samba.ldif"),
1886 os
.path
.join(result
.paths
.ldapdir
, "db", "samba", "cn=samba.ldif"),
1887 { "UUID": str(uuid
.uuid4()),
1888 "LDAPTIME": timestring(int(time
.time()))} )
1889 setup_file(setup_path("cn=samba-admin.ldif"),
1890 os
.path
.join(result
.paths
.ldapdir
, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1891 {"LDAPADMINPASS_B64": b64encode(ldapadminpass
),
1892 "UUID": str(uuid
.uuid4()),
1893 "LDAPTIME": timestring(int(time
.time()))} )
1895 if ol_mmr_urls
is not None:
1896 setup_file(setup_path("cn=replicator.ldif"),
1897 os
.path
.join(result
.paths
.ldapdir
, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1898 {"MMR_PASSWORD_B64": b64encode(mmr_pass
),
1899 "UUID": str(uuid
.uuid4()),
1900 "LDAPTIME": timestring(int(time
.time()))} )
1903 mapping
= "schema-map-openldap-2.3"
1904 backend_schema
= "backend-schema.schema"
1906 backend_schema_data
= schema
.ldb
.convert_schema_to_openldap("openldap", open(setup_path(mapping
), 'r').read())
1907 assert backend_schema_data
is not None
1908 open(os
.path
.join(result
.paths
.ldapdir
, backend_schema
), 'w').write(backend_schema_data
)
1910 # now we generate the needed strings to start slapd automatically,
1911 # first ldapi_uri...
1912 if ldap_backend_extra_port
is not None:
1913 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1914 # specified there as part of it's clue as to it's own name,
1915 # and not to replicate to itself
1916 if ol_mmr_urls
is None:
1917 server_port_string
= "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1919 server_port_string
= "ldap://" + names
.hostname
+ "." + names
.dnsdomain
+":%d" % ldap_backend_extra_port
1921 server_port_string
= ""
1923 # Prepare the 'result' information - the commands to return in particular
1924 result
.slapd_provision_command
= [slapd_path
]
1926 result
.slapd_provision_command
.append("-F" + result
.paths
.olcdir
)
1928 result
.slapd_provision_command
.append("-h")
1930 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1931 result
.slapd_command
= list(result
.slapd_provision_command
)
1933 result
.slapd_provision_command
.append(result
.ldapi_uri
)
1934 result
.slapd_provision_command
.append("-d0")
1936 uris
= result
.ldapi_uri
1937 if server_port_string
is not "":
1938 uris
= uris
+ " " + server_port_string
1940 result
.slapd_command
.append(uris
)
1942 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1943 result
.credentials
.set_username("samba-admin")
1945 # If we were just looking for crashes up to this point, it's a
1946 # good time to exit before we realise we don't have OpenLDAP on
1948 if ldap_dryrun_mode
:
1951 # Finally, convert the configuration into cn=config style!
1952 if not os
.path
.isdir(result
.paths
.olcdir
):
1953 os
.makedirs(result
.paths
.olcdir
, 0770)
1955 retcode
= subprocess
.call([slapd_path
, "-Ttest", "-f", result
.paths
.slapdconf
, "-F", result
.paths
.olcdir
], close_fds
=True, shell
=False)
1957 # We can't do this, as OpenLDAP is strange. It gives an error
1958 # output to the above, but does the conversion sucessfully...
1961 # raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1963 if not os
.path
.exists(os
.path
.join(result
.paths
.olcdir
, "cn=config.ldif")):
1964 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1966 # Don't confuse the admin by leaving the slapd.conf around
1967 os
.remove(result
.paths
.slapdconf
)
1970 def provision_fds_backend(result
, setup_path
=None, names
=None,
1972 hostname
=None, ldapadminpass
=None, root
=None,
1974 ldap_backend_extra_port
=None,
1978 ldap_dryrun_mode
=False,
1981 if ldap_backend_extra_port
is not None:
1982 serverport
= "ServerPort=%d" % ldap_backend_extra_port
1986 setup_file(setup_path("fedorads.inf"), result
.paths
.fedoradsinf
,
1988 "HOSTNAME": hostname
,
1989 "DNSDOMAIN": names
.dnsdomain
,
1990 "LDAPDIR": result
.paths
.ldapdir
,
1991 "DOMAINDN": names
.domaindn
,
1992 "LDAPMANAGERDN": names
.ldapmanagerdn
,
1993 "LDAPMANAGERPASS": ldapadminpass
,
1994 "SERVERPORT": serverport
})
1996 setup_file(setup_path("fedorads-partitions.ldif"), result
.paths
.fedoradspartitions
,
1997 {"CONFIGDN": names
.configdn
,
1998 "SCHEMADN": names
.schemadn
,
1999 "SAMBADN": names
.sambadn
,
2002 setup_file(setup_path("fedorads-sasl.ldif"), result
.paths
.fedoradssasl
,
2003 {"SAMBADN": names
.sambadn
,
2006 setup_file(setup_path("fedorads-dna.ldif"), result
.paths
.fedoradsdna
,
2007 {"DOMAINDN": names
.domaindn
,
2008 "SAMBADN": names
.sambadn
,
2009 "DOMAINSID": str(domainsid
),
2012 setup_file(setup_path("fedorads-pam.ldif"), result
.paths
.fedoradspam
)
2014 lnkattr
= schema
.linked_attributes()
2016 refint_config
= data
= open(setup_path("fedorads-refint-delete.ldif"), 'r').read()
2017 memberof_config
= ""
2021 for attr
in lnkattr
.keys():
2022 if lnkattr
[attr
] is not None:
2023 refint_config
+= read_and_sub_file(setup_path("fedorads-refint-add.ldif"),
2024 { "ARG_NUMBER" : str(argnum
) ,
2025 "LINK_ATTR" : attr
})
2026 memberof_config
+= read_and_sub_file(setup_path("fedorads-linked-attributes.ldif"),
2027 { "MEMBER_ATTR" : attr
,
2028 "MEMBEROF_ATTR" : lnkattr
[attr
] })
2029 index_config
+= read_and_sub_file(setup_path("fedorads-index.ldif"),
2033 open(result
.paths
.fedoradsrefint
, 'w').write(refint_config
)
2034 open(result
.paths
.fedoradslinkedattributes
, 'w').write(memberof_config
)
2036 attrs
= ["lDAPDisplayName"]
2037 res
= schema
.ldb
.search(expression
="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base
=names
.schemadn
, scope
=SCOPE_ONELEVEL
, attrs
=attrs
)
2039 for i
in range (0, len(res
)):
2040 attr
= res
[i
]["lDAPDisplayName"][0]
2042 if attr
== "objectGUID":
2045 index_config
+= read_and_sub_file(setup_path("fedorads-index.ldif"),
2048 open(result
.paths
.fedoradsindex
, 'w').write(index_config
)
2050 setup_file(setup_path("fedorads-samba.ldif"), result
.paths
.fedoradssamba
,
2051 {"SAMBADN": names
.sambadn
,
2052 "LDAPADMINPASS": ldapadminpass
2055 mapping
= "schema-map-fedora-ds-1.0"
2056 backend_schema
= "99_ad.ldif"
2058 # Build a schema file in Fedora DS format
2059 backend_schema_data
= schema
.ldb
.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping
), 'r').read())
2060 assert backend_schema_data
is not None
2061 open(os
.path
.join(result
.paths
.ldapdir
, backend_schema
), 'w').write(backend_schema_data
)
2063 result
.credentials
.set_bind_dn(names
.ldapmanagerdn
)
2065 # Destory the target directory, or else setup-ds.pl will complain
2066 fedora_ds_dir
= os
.path
.join(result
.paths
.ldapdir
, "slapd-samba4")
2067 shutil
.rmtree(fedora_ds_dir
, True)
2069 result
.slapd_provision_command
= [slapd_path
, "-D", fedora_ds_dir
, "-i", result
.paths
.slapdpid
];
2070 #In the 'provision' command line, stay in the foreground so we can easily kill it
2071 result
.slapd_provision_command
.append("-d0")
2073 #the command for the final run is the normal script
2074 result
.slapd_command
= [os
.path
.join(result
.paths
.ldapdir
, "slapd-samba4", "start-slapd")]
2076 # If we were just looking for crashes up to this point, it's a
2077 # good time to exit before we realise we don't have Fedora DS on
2078 if ldap_dryrun_mode
:
2081 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
2082 if setup_ds_path
is None:
2083 raise ProvisioningError("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
2084 if not os
.path
.exists(setup_ds_path
):
2085 message (setup_ds_path
)
2086 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
2088 # Run the Fedora DS setup utility
2089 retcode
= subprocess
.call([setup_ds_path
, "--silent", "--file", result
.paths
.fedoradsinf
], close_fds
=True, shell
=False)
2091 raise ProvisioningError("setup-ds failed")
2094 retcode
= subprocess
.call([
2095 os
.path
.join(result
.paths
.ldapdir
, "slapd-samba4", "ldif2db"), "-s", names
.sambadn
, "-i", result
.paths
.fedoradssamba
],
2096 close_fds
=True, shell
=False)
2098 raise("ldib2db failed")
2100 # Leave a hook to do the 'post initilisation' setup
2101 def fds_post_setup(self
):
2102 ldapi_db
= Ldb(self
.ldapi_uri
, credentials
=self
.credentials
)
2104 # delete default SASL mappings
2105 res
= ldapi_db
.search(expression
="(!(cn=samba-admin mapping))", base
="cn=mapping,cn=sasl,cn=config", scope
=SCOPE_ONELEVEL
, attrs
=["dn"])
2107 # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
2108 for i
in range (0, len(res
)):
2109 dn
= str(res
[i
]["dn"])
2112 aci
= """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names
.sambadn
2115 m
["aci"] = ldb
.MessageElement([aci
], ldb
.FLAG_MOD_REPLACE
, "aci")
2117 m
.dn
= ldb
.Dn(1, names
.domaindn
)
2120 m
.dn
= ldb
.Dn(1, names
.configdn
)
2123 m
.dn
= ldb
.Dn(1, names
.schemadn
)
2126 result
.post_setup
= fds_post_setup
2129 def create_phpldapadmin_config(path
, setup_path
, ldapi_uri
):
2130 """Create a PHP LDAP admin configuration file.
2132 :param path: Path to write the configuration to.
2133 :param setup_path: Function to generate setup paths.
2135 setup_file(setup_path("phpldapadmin-config.php"), path
,
2136 {"S4_LDAPI_URI": ldapi_uri
})
2139 def create_zone_file(path
, setup_path
, dnsdomain
,
2140 hostip
, hostip6
, hostname
, realm
, domainguid
,
2142 """Write out a DNS zone file, from the info in the current database.
2144 :param path: Path of the new zone file.
2145 :param setup_path: Setup path function.
2146 :param dnsdomain: DNS Domain name
2147 :param domaindn: DN of the Domain
2148 :param hostip: Local IPv4 IP
2149 :param hostip6: Local IPv6 IP
2150 :param hostname: Local hostname
2151 :param realm: Realm name
2152 :param domainguid: GUID of the domain.
2153 :param ntdsguid: GUID of the hosts nTDSDSA record.
2155 assert isinstance(domainguid
, str)
2157 if hostip6
is not None:
2158 hostip6_base_line
= " IN AAAA " + hostip6
2159 hostip6_host_line
= hostname
+ " IN AAAA " + hostip6
2161 hostip6_base_line
= ""
2162 hostip6_host_line
= ""
2164 if hostip
is not None:
2165 hostip_base_line
= " IN A " + hostip
2166 hostip_host_line
= hostname
+ " IN A " + hostip
2168 hostip_base_line
= ""
2169 hostip_host_line
= ""
2171 setup_file(setup_path("provision.zone"), path
, {
2172 "HOSTNAME": hostname
,
2173 "DNSDOMAIN": dnsdomain
,
2175 "HOSTIP_BASE_LINE": hostip_base_line
,
2176 "HOSTIP_HOST_LINE": hostip_host_line
,
2177 "DOMAINGUID": domainguid
,
2178 "DATESTRING": time
.strftime("%Y%m%d%H"),
2179 "DEFAULTSITE": DEFAULTSITE
,
2180 "NTDSGUID": ntdsguid
,
2181 "HOSTIP6_BASE_LINE": hostip6_base_line
,
2182 "HOSTIP6_HOST_LINE": hostip6_host_line
,
2186 def create_named_conf(path
, setup_path
, realm
, dnsdomain
,
2188 """Write out a file containing zone statements suitable for inclusion in a
2189 named.conf file (including GSS-TSIG configuration).
2191 :param path: Path of the new named.conf file.
2192 :param setup_path: Setup path function.
2193 :param realm: Realm name
2194 :param dnsdomain: DNS Domain name
2195 :param private_dir: Path to private directory
2196 :param keytab_name: File name of DNS keytab file
2199 setup_file(setup_path("named.conf"), path
, {
2200 "DNSDOMAIN": dnsdomain
,
2202 "REALM_WC": "*." + ".".join(realm
.split(".")[1:]),
2203 "PRIVATE_DIR": private_dir
2206 def create_named_txt(path
, setup_path
, realm
, dnsdomain
,
2207 private_dir
, keytab_name
):
2208 """Write out a file containing zone statements suitable for inclusion in a
2209 named.conf file (including GSS-TSIG configuration).
2211 :param path: Path of the new named.conf file.
2212 :param setup_path: Setup path function.
2213 :param realm: Realm name
2214 :param dnsdomain: DNS Domain name
2215 :param private_dir: Path to private directory
2216 :param keytab_name: File name of DNS keytab file
2219 setup_file(setup_path("named.txt"), path
, {
2220 "DNSDOMAIN": dnsdomain
,
2222 "DNS_KEYTAB": keytab_name
,
2223 "DNS_KEYTAB_ABS": os
.path
.join(private_dir
, keytab_name
),
2224 "PRIVATE_DIR": private_dir
2227 def create_krb5_conf(path
, setup_path
, dnsdomain
, hostname
, realm
):
2228 """Write out a file containing zone statements suitable for inclusion in a
2229 named.conf file (including GSS-TSIG configuration).
2231 :param path: Path of the new named.conf file.
2232 :param setup_path: Setup path function.
2233 :param dnsdomain: DNS Domain name
2234 :param hostname: Local hostname
2235 :param realm: Realm name
2238 setup_file(setup_path("krb5.conf"), path
, {
2239 "DNSDOMAIN": dnsdomain
,
2240 "HOSTNAME": hostname
,