ldb: Fix ldb public library header files being unusable
[Samba.git] / python / samba / netcmd / domain / provision.py
blob8f13e541f02c4423ed39c36ea8bba3fdd0e1451e
1 # domain management - domain provision
3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
26 import sys
27 import tempfile
29 import samba
30 import samba.getopt as options
31 from samba.auth import system_session
32 from samba.auth_util import system_session_unix
33 from samba.dcerpc import security
34 from samba.dsdb import (
35 DS_DOMAIN_FUNCTION_2000,
36 DS_DOMAIN_FUNCTION_2003,
37 DS_DOMAIN_FUNCTION_2008,
38 DS_DOMAIN_FUNCTION_2008_R2,
39 DS_DOMAIN_FUNCTION_2012,
40 DS_DOMAIN_FUNCTION_2012_R2,
41 DS_DOMAIN_FUNCTION_2016
43 from samba.netcmd import Command, CommandError, Option
44 from samba.provision import DEFAULT_MIN_PWD_LENGTH, ProvisioningError, provision
45 from samba.provision.common import FILL_DRS, FILL_FULL, FILL_NT4SYNC
46 from samba.samdb import get_default_backend_store
47 from samba import functional_level
49 from .common import common_ntvfs_options, common_provision_join_options
52 class cmd_domain_provision(Command):
53 """Provision a domain."""
55 synopsis = "%prog [options]"
57 takes_optiongroups = {
58 "sambaopts": options.SambaOptions,
59 "versionopts": options.VersionOptions,
62 takes_options = [
63 Option("--interactive", help="Ask for names", action="store_true"),
64 Option("--domain", type="string", metavar="DOMAIN",
65 help="NetBIOS domain name to use"),
66 Option("--domain-guid", type="string", metavar="GUID",
67 help="set domainguid (otherwise random)"),
68 Option("--domain-sid", type="string", metavar="SID",
69 help="set domainsid (otherwise random)"),
70 Option("--ntds-guid", type="string", metavar="GUID",
71 help="set NTDS object GUID (otherwise random)"),
72 Option("--invocationid", type="string", metavar="GUID",
73 help="set invocationid (otherwise random)"),
74 Option("--host-name", type="string", metavar="HOSTNAME",
75 help="set hostname"),
76 Option("--host-ip", type="string", metavar="IPADDRESS",
77 help="set IPv4 ipaddress"),
78 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
79 help="set IPv6 ipaddress"),
80 Option("--site", type="string", metavar="SITENAME",
81 help="set site name"),
82 Option("--adminpass", type="string", metavar="PASSWORD",
83 help="choose admin password (otherwise random)"),
84 Option("--krbtgtpass", type="string", metavar="PASSWORD",
85 help="choose krbtgt password (otherwise random)"),
86 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
87 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
88 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
89 "BIND9_FLATFILE uses bind9 text database to store zone information, "
90 "BIND9_DLZ uses samba4 AD to store zone information, "
91 "NONE skips the DNS setup entirely (not recommended)",
92 default="SAMBA_INTERNAL"),
93 Option("--dnspass", type="string", metavar="PASSWORD",
94 help="choose dns password (otherwise random)"),
95 Option("--root", type="string", metavar="USERNAME",
96 help="choose 'root' unix username"),
97 Option("--nobody", type="string", metavar="USERNAME",
98 help="choose 'nobody' user"),
99 Option("--users", type="string", metavar="GROUPNAME",
100 help="choose 'users' group"),
101 Option("--blank", action="store_true",
102 help="do not add users or groups, just the structure"),
103 Option("--server-role", type="choice", metavar="ROLE",
104 choices=["domain controller", "dc", "member server", "member", "standalone"],
105 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
106 default="domain controller"),
107 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
108 choices=["2000", "2003", "2008", "2008_R2", "2016"],
109 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native | 2016). Default is (Windows) 2008_R2 Native.",
110 default="2008_R2"),
111 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
112 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2", "2016", "2019"],
113 help="The base schema files to use. Default is (Windows) 2019.",
114 default="2019"),
115 Option("--adprep-level", type="choice", metavar="FUNCTION_LEVEL",
116 choices=["SKIP", "2008_R2", "2012", "2012_R2", "2016"],
117 help="The highest functional level to prepare for. Default is based on --base-schema",
118 default=None),
119 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
120 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
121 Option("--partitions-only",
122 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
123 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
126 ntvfs_options = [
127 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
128 metavar="[yes|no|auto]",
129 help="Define if we should use the native fs capabilities or a tdb file for "
130 "storing attributes likes ntacl when --use-ntvfs is set. "
131 "auto tries to make an intelligent guess based on the user rights and system capabilities",
132 default="auto")
135 takes_options.extend(common_provision_join_options)
137 if samba.is_ntvfs_fileserver_built():
138 takes_options.extend(common_ntvfs_options)
139 takes_options.extend(ntvfs_options)
141 takes_args = []
143 def run(self, sambaopts=None, versionopts=None,
144 interactive=None,
145 domain=None,
146 domain_guid=None,
147 domain_sid=None,
148 ntds_guid=None,
149 invocationid=None,
150 host_name=None,
151 host_ip=None,
152 host_ip6=None,
153 adminpass=None,
154 site=None,
155 krbtgtpass=None,
156 machinepass=None,
157 dns_backend=None,
158 dns_forwarder=None,
159 dnspass=None,
160 ldapadminpass=None,
161 root=None,
162 nobody=None,
163 users=None,
164 quiet=None,
165 blank=None,
166 server_role=None,
167 function_level=None,
168 adprep_level=None,
169 next_rid=None,
170 partitions_only=None,
171 targetdir=None,
172 use_xattrs="auto",
173 use_ntvfs=False,
174 use_rfc2307=None,
175 base_schema=None,
176 plaintext_secrets=False,
177 backend_store=None,
178 backend_store_size=None):
180 self.logger = self.get_logger(name="provision", quiet=quiet)
182 lp = sambaopts.get_loadparm()
183 smbconf = lp.configfile
185 if dns_forwarder is not None:
186 suggested_forwarder = dns_forwarder
187 else:
188 suggested_forwarder = self._get_nameserver_ip()
189 if suggested_forwarder is None:
190 suggested_forwarder = "none"
192 if not self.raw_argv:
193 interactive = True
195 if interactive:
196 from getpass import getpass
197 import socket
199 def ask(prompt, default=None):
200 if default is not None:
201 print("%s [%s]: " % (prompt, default), end=' ')
202 else:
203 print("%s: " % (prompt,), end=' ')
204 sys.stdout.flush()
205 return sys.stdin.readline().rstrip("\n") or default
207 try:
208 default = socket.getfqdn().split(".", 1)[1].upper()
209 except IndexError:
210 default = None
211 realm = ask("Realm", default)
212 if realm in (None, ""):
213 raise CommandError("No realm set!")
215 try:
216 default = realm.split(".")[0]
217 except IndexError:
218 default = None
219 domain = ask("Domain", default)
220 if domain is None:
221 raise CommandError("No domain set!")
223 server_role = ask("Server Role (dc, member, standalone)", "dc")
225 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
226 if dns_backend in (None, ''):
227 raise CommandError("No DNS backend set!")
229 if dns_backend == "SAMBA_INTERNAL":
230 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
231 if dns_forwarder.lower() in (None, 'none'):
232 suggested_forwarder = None
233 dns_forwarder = None
235 while True:
236 adminpassplain = getpass("Administrator password: ")
237 issue = self._adminpass_issue(adminpassplain)
238 if issue:
239 self.errf.write("%s.\n" % issue)
240 else:
241 adminpassverify = getpass("Retype password: ")
242 if not adminpassplain == adminpassverify:
243 self.errf.write("Sorry, passwords do not match.\n")
244 else:
245 adminpass = adminpassplain
246 break
248 else:
249 realm = sambaopts._lp.get('realm')
250 if realm is None:
251 raise CommandError("No realm set!")
252 if domain is None:
253 raise CommandError("No domain set!")
255 if adminpass:
256 issue = self._adminpass_issue(adminpass)
257 if issue:
258 raise CommandError(issue)
259 else:
260 self.logger.info("Administrator password will be set randomly!")
262 try:
263 dom_for_fun_level = functional_level.string_to_level(function_level)
264 except KeyError:
265 raise CommandError(f"'{function_level}' is not a valid domain level")
267 if adprep_level is None:
268 # Select the adprep_level default based
269 # on what the base schema permits
270 if base_schema in ["2008_R2", "2008_R2_old"]:
271 # without explicit --adprep-level=2008_R2
272 # we will skip the adprep step on
273 # provision
274 adprep_level = "SKIP"
275 elif base_schema in ["2012"]:
276 adprep_level = "2012"
277 elif base_schema in ["2012_R2"]:
278 adprep_level = "2012_R2"
279 else:
280 adprep_level = "2016"
282 if adprep_level == "SKIP":
283 provision_adprep_level = None
284 elif adprep_level == "2008R2":
285 provision_adprep_level = DS_DOMAIN_FUNCTION_2008_R2
286 elif adprep_level == "2012":
287 provision_adprep_level = DS_DOMAIN_FUNCTION_2012
288 elif adprep_level == "2012_R2":
289 provision_adprep_level = DS_DOMAIN_FUNCTION_2012_R2
290 elif adprep_level == "2016":
291 provision_adprep_level = DS_DOMAIN_FUNCTION_2016
293 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
294 dns_forwarder = suggested_forwarder
296 samdb_fill = FILL_FULL
297 if blank:
298 samdb_fill = FILL_NT4SYNC
299 elif partitions_only:
300 samdb_fill = FILL_DRS
302 if targetdir is not None:
303 if not os.path.isdir(targetdir):
304 os.makedirs(targetdir)
306 eadb = True
308 if use_xattrs == "yes":
309 eadb = False
310 elif use_xattrs == "auto" and not use_ntvfs:
311 eadb = False
312 elif not use_ntvfs:
313 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
314 "Please re-run with --use-xattrs omitted.")
315 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
316 if targetdir:
317 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
318 else:
319 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
320 try:
321 try:
322 samba.ntacls.setntacl(lp, file.name,
323 "O:S-1-5-32G:S-1-5-32",
324 "S-1-5-32",
325 system_session_unix(),
326 "native")
327 eadb = False
328 except Exception:
329 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
330 finally:
331 file.close()
333 if eadb:
334 self.logger.info("not using extended attributes to store ACLs and other metadata. If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
336 if domain_sid is not None:
337 domain_sid = security.dom_sid(domain_sid)
339 session = system_session()
340 if backend_store is None:
341 backend_store = get_default_backend_store()
342 try:
343 result = provision(self.logger,
344 session, smbconf=smbconf, targetdir=targetdir,
345 samdb_fill=samdb_fill, realm=realm, domain=domain,
346 domainguid=domain_guid, domainsid=domain_sid,
347 hostname=host_name,
348 hostip=host_ip, hostip6=host_ip6,
349 sitename=site, ntdsguid=ntds_guid,
350 invocationid=invocationid, adminpass=adminpass,
351 krbtgtpass=krbtgtpass, machinepass=machinepass,
352 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
353 dnspass=dnspass, root=root, nobody=nobody,
354 users=users,
355 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
356 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
357 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
358 base_schema=base_schema,
359 adprep_level=provision_adprep_level,
360 plaintext_secrets=plaintext_secrets,
361 backend_store=backend_store,
362 backend_store_size=backend_store_size)
364 except ProvisioningError as e:
365 raise CommandError("Provision failed", e)
367 result.report_logger(self.logger)
369 def _get_nameserver_ip(self):
370 """Grab the nameserver IP address from /etc/resolv.conf."""
371 from os import path
372 RESOLV_CONF = "/etc/resolv.conf"
374 if not path.isfile(RESOLV_CONF):
375 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
376 return None
378 handle = None
379 try:
380 handle = open(RESOLV_CONF, 'r')
381 for line in handle:
382 if not line.startswith('nameserver'):
383 continue
384 # we want the last non-space continuous string of the line
385 return line.strip().split()[-1]
386 finally:
387 if handle is not None:
388 handle.close()
390 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
392 def _adminpass_issue(self, adminpass):
393 """Returns error string for a bad administrator password,
394 or None if acceptable"""
395 if isinstance(adminpass, bytes):
396 adminpass = adminpass.decode('utf8')
397 if len(adminpass) < DEFAULT_MIN_PWD_LENGTH:
398 return "Administrator password does not meet the default minimum" \
399 " password length requirement (%d characters)" \
400 % DEFAULT_MIN_PWD_LENGTH
401 elif not samba.check_password_quality(adminpass):
402 return "Administrator password does not meet the default" \
403 " quality standards"
404 else:
405 return None