Fix a typo in the name of the default domain level
[Samba.git] / python / samba / netcmd / domain.py
blobf8e32f00b58b5753d69cece58a231483daf056c3
1 # domain management
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
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 samba.getopt as options
26 import ldb
27 import string
28 import os
29 import sys
30 import tempfile
31 import logging
32 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
33 import samba.ntacls
34 from samba.join import join_RODC, join_DC, join_subdomain
35 from samba.auth import system_session
36 from samba.samdb import SamDB
37 from samba.dcerpc import drsuapi
38 from samba.dcerpc import security
39 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
40 from samba.netcmd import (
41 Command,
42 CommandError,
43 SuperCommand,
44 Option
46 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
47 from samba.samba3 import Samba3
48 from samba.samba3 import param as s3param
49 from samba.upgrade import upgrade_from_samba3
50 from samba.drs_utils import (
51 sendDsReplicaSync, drsuapi_connect, drsException,
52 sendRemoveDsServer)
55 from samba.dsdb import (
56 DS_DOMAIN_FUNCTION_2000,
57 DS_DOMAIN_FUNCTION_2003,
58 DS_DOMAIN_FUNCTION_2003_MIXED,
59 DS_DOMAIN_FUNCTION_2008,
60 DS_DOMAIN_FUNCTION_2008_R2,
61 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
62 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
63 UF_WORKSTATION_TRUST_ACCOUNT,
64 UF_SERVER_TRUST_ACCOUNT,
65 UF_TRUSTED_FOR_DELEGATION
68 from samba.provision import (
69 provision,
70 ProvisioningError
73 from samba.provision.common import (
74 FILL_FULL,
75 FILL_NT4SYNC,
76 FILL_DRS
79 def get_testparm_var(testparm, smbconf, varname):
80 cmd = "%s -s -l --parameter-name='%s' %s 2>/dev/null" % (testparm, varname, smbconf)
81 output = os.popen(cmd, 'r').readline()
82 return output.strip()
84 try:
85 import samba.dckeytab
86 except ImportError:
87 cmd_domain_export_keytab = None
88 else:
89 class cmd_domain_export_keytab(Command):
90 """Dump Kerberos keys of the domain into a keytab."""
92 synopsis = "%prog <keytab> [options]"
94 takes_optiongroups = {
95 "sambaopts": options.SambaOptions,
96 "credopts": options.CredentialsOptions,
97 "versionopts": options.VersionOptions,
100 takes_options = [
101 Option("--principal", help="extract only this principal", type=str),
104 takes_args = ["keytab"]
106 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
107 lp = sambaopts.get_loadparm()
108 net = Net(None, lp)
109 net.export_keytab(keytab=keytab, principal=principal)
112 class cmd_domain_info(Command):
113 """Print basic info about a domain and the DC passed as parameter."""
115 synopsis = "%prog <ip_address> [options]"
117 takes_options = [
120 takes_optiongroups = {
121 "sambaopts": options.SambaOptions,
122 "credopts": options.CredentialsOptions,
123 "versionopts": options.VersionOptions,
126 takes_args = ["address"]
128 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
129 lp = sambaopts.get_loadparm()
130 try:
131 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
132 except RuntimeError:
133 raise CommandError("Invalid IP address '" + address + "'!")
134 self.outf.write("Forest : %s\n" % res.forest)
135 self.outf.write("Domain : %s\n" % res.dns_domain)
136 self.outf.write("Netbios domain : %s\n" % res.domain_name)
137 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
138 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
139 self.outf.write("Server site : %s\n" % res.server_site)
140 self.outf.write("Client site : %s\n" % res.client_site)
143 class cmd_domain_provision(Command):
144 """Provision a domain."""
146 synopsis = "%prog [options]"
148 takes_optiongroups = {
149 "sambaopts": options.SambaOptions,
150 "versionopts": options.VersionOptions,
153 takes_options = [
154 Option("--interactive", help="Ask for names", action="store_true"),
155 Option("--domain", type="string", metavar="DOMAIN",
156 help="set domain"),
157 Option("--domain-guid", type="string", metavar="GUID",
158 help="set domainguid (otherwise random)"),
159 Option("--domain-sid", type="string", metavar="SID",
160 help="set domainsid (otherwise random)"),
161 Option("--ntds-guid", type="string", metavar="GUID",
162 help="set NTDS object GUID (otherwise random)"),
163 Option("--invocationid", type="string", metavar="GUID",
164 help="set invocationid (otherwise random)"),
165 Option("--host-name", type="string", metavar="HOSTNAME",
166 help="set hostname"),
167 Option("--host-ip", type="string", metavar="IPADDRESS",
168 help="set IPv4 ipaddress"),
169 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
170 help="set IPv6 ipaddress"),
171 Option("--site", type="string", metavar="SITENAME",
172 help="set site name"),
173 Option("--adminpass", type="string", metavar="PASSWORD",
174 help="choose admin password (otherwise random)"),
175 Option("--krbtgtpass", type="string", metavar="PASSWORD",
176 help="choose krbtgt password (otherwise random)"),
177 Option("--machinepass", type="string", metavar="PASSWORD",
178 help="choose machine password (otherwise random)"),
179 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
180 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
181 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
182 "BIND9_FLATFILE uses bind9 text database to store zone information, "
183 "BIND9_DLZ uses samba4 AD to store zone information, "
184 "NONE skips the DNS setup entirely (not recommended)",
185 default="SAMBA_INTERNAL"),
186 Option("--dnspass", type="string", metavar="PASSWORD",
187 help="choose dns password (otherwise random)"),
188 Option("--ldapadminpass", type="string", metavar="PASSWORD",
189 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
190 Option("--root", type="string", metavar="USERNAME",
191 help="choose 'root' unix username"),
192 Option("--nobody", type="string", metavar="USERNAME",
193 help="choose 'nobody' user"),
194 Option("--users", type="string", metavar="GROUPNAME",
195 help="choose 'users' group"),
196 Option("--quiet", help="Be quiet", action="store_true"),
197 Option("--blank", action="store_true",
198 help="do not add users or groups, just the structure"),
199 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
200 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
201 choices=["fedora-ds", "openldap"]),
202 Option("--server-role", type="choice", metavar="ROLE",
203 choices=["domain controller", "dc", "member server", "member", "standalone"],
204 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
205 default="domain controller"),
206 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
207 choices=["2000", "2003", "2008", "2008_R2"],
208 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
209 default="2008_R2"),
210 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
211 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
212 Option("--partitions-only",
213 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
214 Option("--targetdir", type="string", metavar="DIR",
215 help="Set target directory"),
216 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
217 help="List of LDAP-URLS [ ldap://<FQHN>:<PORT>/ (where <PORT> has to be different than 389!) ] separated with comma (\",\") for use with OpenLDAP-MMR (Multi-Master-Replication), e.g.: \"ldap://s4dc1:9000,ldap://s4dc2:9000\""),
218 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"], help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default="auto"),
219 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
220 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
223 openldap_options = [
224 Option("--ldap-dryrun-mode", help="Configure LDAP backend, but do not run any binaries and exit early. Used only for the test environment. DO NOT USE",
225 action="store_true"),
226 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
227 help="Path to slapd for LDAP backend [e.g.:'/usr/local/libexec/slapd']. Required for Setup with LDAP-Backend. OpenLDAP Version >= 2.4.17 should be used."),
228 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
229 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
230 help="Force the LDAP backend connection to be to a particular URI. Use this ONLY for 'existing' backends, or when debugging the interaction with the LDAP backend and you need to intercept the LDA"),
231 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
234 if os.getenv('TEST_LDAP', "no") == "yes":
235 takes_options.extend(openldap_options)
237 takes_args = []
239 def run(self, sambaopts=None, versionopts=None,
240 interactive=None,
241 domain=None,
242 domain_guid=None,
243 domain_sid=None,
244 ntds_guid=None,
245 invocationid=None,
246 host_name=None,
247 host_ip=None,
248 host_ip6=None,
249 adminpass=None,
250 site=None,
251 krbtgtpass=None,
252 machinepass=None,
253 dns_backend=None,
254 dns_forwarder=None,
255 dnspass=None,
256 ldapadminpass=None,
257 root=None,
258 nobody=None,
259 users=None,
260 quiet=None,
261 blank=None,
262 ldap_backend_type=None,
263 server_role=None,
264 function_level=None,
265 next_rid=None,
266 partitions_only=None,
267 targetdir=None,
268 ol_mmr_urls=None,
269 use_xattrs=None,
270 slapd_path=None,
271 use_ntvfs=None,
272 use_rfc2307=None,
273 ldap_backend_nosync=None,
274 ldap_backend_extra_port=None,
275 ldap_backend_forced_uri=None,
276 ldap_dryrun_mode=None):
278 self.logger = self.get_logger("provision")
279 if quiet:
280 self.logger.setLevel(logging.WARNING)
281 else:
282 self.logger.setLevel(logging.INFO)
284 lp = sambaopts.get_loadparm()
285 smbconf = lp.configfile
287 if dns_forwarder is not None:
288 suggested_forwarder = dns_forwarder
289 else:
290 suggested_forwarder = self._get_nameserver_ip()
291 if suggested_forwarder is None:
292 suggested_forwarder = "none"
294 if len(self.raw_argv) == 1:
295 interactive = True
297 if interactive:
298 from getpass import getpass
299 import socket
301 def ask(prompt, default=None):
302 if default is not None:
303 print "%s [%s]: " % (prompt, default),
304 else:
305 print "%s: " % (prompt,),
306 return sys.stdin.readline().rstrip("\n") or default
308 try:
309 default = socket.getfqdn().split(".", 1)[1].upper()
310 except IndexError:
311 default = None
312 realm = ask("Realm", default)
313 if realm in (None, ""):
314 raise CommandError("No realm set!")
316 try:
317 default = realm.split(".")[0]
318 except IndexError:
319 default = None
320 domain = ask("Domain", default)
321 if domain is None:
322 raise CommandError("No domain set!")
324 server_role = ask("Server Role (dc, member, standalone)", "dc")
326 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
327 if dns_backend in (None, ''):
328 raise CommandError("No DNS backend set!")
330 if dns_backend == "SAMBA_INTERNAL":
331 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
332 if dns_forwarder.lower() in (None, 'none'):
333 suggested_forwarder = None
334 dns_forwarder = None
336 while True:
337 adminpassplain = getpass("Administrator password: ")
338 if not adminpassplain:
339 self.errf.write("Invalid administrator password.\n")
340 else:
341 adminpassverify = getpass("Retype password: ")
342 if not adminpassplain == adminpassverify:
343 self.errf.write("Sorry, passwords do not match.\n")
344 else:
345 adminpass = adminpassplain
346 break
348 else:
349 realm = sambaopts._lp.get('realm')
350 if realm is None:
351 raise CommandError("No realm set!")
352 if domain is None:
353 raise CommandError("No domain set!")
355 if not adminpass:
356 self.logger.info("Administrator password will be set randomly!")
358 if function_level == "2000":
359 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
360 elif function_level == "2003":
361 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
362 elif function_level == "2008":
363 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
364 elif function_level == "2008_R2":
365 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
367 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
368 dns_forwarder = suggested_forwarder
370 samdb_fill = FILL_FULL
371 if blank:
372 samdb_fill = FILL_NT4SYNC
373 elif partitions_only:
374 samdb_fill = FILL_DRS
376 if targetdir is not None:
377 if not os.path.isdir(targetdir):
378 os.mkdir(targetdir)
380 eadb = True
382 if use_xattrs == "yes":
383 eadb = False
384 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
385 if targetdir:
386 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
387 else:
388 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
389 try:
390 try:
391 samba.ntacls.setntacl(lp, file.name,
392 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
393 eadb = False
394 except Exception:
395 self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
396 finally:
397 file.close()
399 if eadb:
400 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.")
401 if ldap_backend_type == "existing":
402 if ldap_backend_forced_uri is not None:
403 self.logger.warn("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at %s" % ldap_backend_forced_uri)
404 else:
405 self.logger.info("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at the default location")
406 else:
407 if ldap_backend_forced_uri is not None:
408 self.logger.warn("You have specified to use an fixed URI %s for connecting to your LDAP server backend. This is NOT RECOMMENDED, as our default communiation over ldapi:// is more secure and much less")
410 if domain_sid is not None:
411 domain_sid = security.dom_sid(domain_sid)
413 session = system_session()
414 try:
415 result = provision(self.logger,
416 session, smbconf=smbconf, targetdir=targetdir,
417 samdb_fill=samdb_fill, realm=realm, domain=domain,
418 domainguid=domain_guid, domainsid=domain_sid,
419 hostname=host_name,
420 hostip=host_ip, hostip6=host_ip6,
421 sitename=site, ntdsguid=ntds_guid,
422 invocationid=invocationid, adminpass=adminpass,
423 krbtgtpass=krbtgtpass, machinepass=machinepass,
424 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
425 dnspass=dnspass, root=root, nobody=nobody,
426 users=users,
427 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
428 backend_type=ldap_backend_type,
429 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
430 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
431 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
432 ldap_backend_extra_port=ldap_backend_extra_port,
433 ldap_backend_forced_uri=ldap_backend_forced_uri,
434 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
436 except ProvisioningError, e:
437 raise CommandError("Provision failed", e)
439 result.report_logger(self.logger)
441 def _get_nameserver_ip(self):
442 """Grab the nameserver IP address from /etc/resolv.conf."""
443 from os import path
444 RESOLV_CONF="/etc/resolv.conf"
446 if not path.isfile(RESOLV_CONF):
447 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
448 return None
450 handle = None
451 try:
452 handle = open(RESOLV_CONF, 'r')
453 for line in handle:
454 if not line.startswith('nameserver'):
455 continue
456 # we want the last non-space continuous string of the line
457 return line.strip().split()[-1]
458 finally:
459 if handle is not None:
460 handle.close()
462 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
465 class cmd_domain_dcpromo(Command):
466 """Promote an existing domain member or NT4 PDC to an AD DC."""
468 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
470 takes_optiongroups = {
471 "sambaopts": options.SambaOptions,
472 "versionopts": options.VersionOptions,
473 "credopts": options.CredentialsOptions,
476 takes_options = [
477 Option("--server", help="DC to join", type=str),
478 Option("--site", help="site to join", type=str),
479 Option("--targetdir", help="where to store provision", type=str),
480 Option("--domain-critical-only",
481 help="only replicate critical domain objects",
482 action="store_true"),
483 Option("--machinepass", type=str, metavar="PASSWORD",
484 help="choose machine password (otherwise random)"),
485 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
486 action="store_true"),
487 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
488 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
489 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
490 "BIND9_DLZ uses samba4 AD to store zone information, "
491 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
492 default="SAMBA_INTERNAL"),
493 Option("--quiet", help="Be quiet", action="store_true"),
494 Option("--verbose", help="Be verbose", action="store_true")
497 takes_args = ["domain", "role?"]
499 def run(self, domain, role=None, sambaopts=None, credopts=None,
500 versionopts=None, server=None, site=None, targetdir=None,
501 domain_critical_only=False, parent_domain=None, machinepass=None,
502 use_ntvfs=False, dns_backend=None,
503 quiet=False, verbose=False):
504 lp = sambaopts.get_loadparm()
505 creds = credopts.get_credentials(lp)
506 net = Net(creds, lp, server=credopts.ipaddress)
508 if site is None:
509 site = "Default-First-Site-Name"
511 logger = self.get_logger()
512 if verbose:
513 logger.setLevel(logging.DEBUG)
514 elif quiet:
515 logger.setLevel(logging.WARNING)
516 else:
517 logger.setLevel(logging.INFO)
519 netbios_name = lp.get("netbios name")
521 if not role is None:
522 role = role.upper()
524 if role == "DC":
525 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
526 site=site, netbios_name=netbios_name, targetdir=targetdir,
527 domain_critical_only=domain_critical_only,
528 machinepass=machinepass, use_ntvfs=use_ntvfs,
529 dns_backend=dns_backend,
530 promote_existing=True)
531 elif role == "RODC":
532 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
533 site=site, netbios_name=netbios_name, targetdir=targetdir,
534 domain_critical_only=domain_critical_only,
535 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
536 promote_existing=True)
537 else:
538 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
541 class cmd_domain_join(Command):
542 """Join domain as either member or backup domain controller."""
544 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
546 takes_optiongroups = {
547 "sambaopts": options.SambaOptions,
548 "versionopts": options.VersionOptions,
549 "credopts": options.CredentialsOptions,
552 takes_options = [
553 Option("--server", help="DC to join", type=str),
554 Option("--site", help="site to join", type=str),
555 Option("--targetdir", help="where to store provision", type=str),
556 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
557 Option("--domain-critical-only",
558 help="only replicate critical domain objects",
559 action="store_true"),
560 Option("--machinepass", type=str, metavar="PASSWORD",
561 help="choose machine password (otherwise random)"),
562 Option("--adminpass", type="string", metavar="PASSWORD",
563 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
564 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
565 action="store_true"),
566 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
567 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
568 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
569 "BIND9_DLZ uses samba4 AD to store zone information, "
570 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
571 default="SAMBA_INTERNAL"),
572 Option("--quiet", help="Be quiet", action="store_true"),
573 Option("--verbose", help="Be verbose", action="store_true")
576 takes_args = ["domain", "role?"]
578 def run(self, domain, role=None, sambaopts=None, credopts=None,
579 versionopts=None, server=None, site=None, targetdir=None,
580 domain_critical_only=False, parent_domain=None, machinepass=None,
581 use_ntvfs=False, dns_backend=None, adminpass=None,
582 quiet=False, verbose=False):
583 lp = sambaopts.get_loadparm()
584 creds = credopts.get_credentials(lp)
585 net = Net(creds, lp, server=credopts.ipaddress)
587 if site is None:
588 site = "Default-First-Site-Name"
590 logger = self.get_logger()
591 if verbose:
592 logger.setLevel(logging.DEBUG)
593 elif quiet:
594 logger.setLevel(logging.WARNING)
595 else:
596 logger.setLevel(logging.INFO)
598 netbios_name = lp.get("netbios name")
600 if not role is None:
601 role = role.upper()
603 if role is None or role == "MEMBER":
604 (join_password, sid, domain_name) = net.join_member(
605 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
606 machinepass=machinepass)
608 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
609 elif role == "DC":
610 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
611 site=site, netbios_name=netbios_name, targetdir=targetdir,
612 domain_critical_only=domain_critical_only,
613 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
614 elif role == "RODC":
615 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
616 site=site, netbios_name=netbios_name, targetdir=targetdir,
617 domain_critical_only=domain_critical_only,
618 machinepass=machinepass, use_ntvfs=use_ntvfs,
619 dns_backend=dns_backend)
620 elif role == "SUBDOMAIN":
621 if not adminpass:
622 logger.info("Administrator password will be set randomly!")
624 netbios_domain = lp.get("workgroup")
625 if parent_domain is None:
626 parent_domain = ".".join(domain.split(".")[1:])
627 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
628 parent_domain=parent_domain, site=site,
629 netbios_name=netbios_name, netbios_domain=netbios_domain,
630 targetdir=targetdir, machinepass=machinepass,
631 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
632 adminpass=adminpass)
633 else:
634 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
637 class cmd_domain_demote(Command):
638 """Demote ourselves from the role of Domain Controller."""
640 synopsis = "%prog [options]"
642 takes_options = [
643 Option("--server", help="DC to force replication before demote", type=str),
644 Option("--targetdir", help="where provision is stored", type=str),
647 takes_optiongroups = {
648 "sambaopts": options.SambaOptions,
649 "credopts": options.CredentialsOptions,
650 "versionopts": options.VersionOptions,
653 def run(self, sambaopts=None, credopts=None,
654 versionopts=None, server=None, targetdir=None):
655 lp = sambaopts.get_loadparm()
656 creds = credopts.get_credentials(lp)
657 net = Net(creds, lp, server=credopts.ipaddress)
659 netbios_name = lp.get("netbios name")
660 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
661 if not server:
662 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
663 if (len(res) == 0):
664 raise CommandError("Unable to search for servers")
666 if (len(res) == 1):
667 raise CommandError("You are the latest server in the domain")
669 server = None
670 for e in res:
671 if str(e["name"]).lower() != netbios_name.lower():
672 server = e["dnsHostName"]
673 break
675 ntds_guid = samdb.get_ntds_GUID()
676 msg = samdb.search(base=str(samdb.get_config_basedn()),
677 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
678 attrs=['options'])
679 if len(msg) == 0 or "options" not in msg[0]:
680 raise CommandError("Failed to find options on %s" % ntds_guid)
682 ntds_dn = msg[0].dn
683 dsa_options = int(str(msg[0]['options']))
685 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
686 controls=["search_options:1:2"])
688 if len(res) != 0:
689 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
691 self.errf.write("Using %s as partner server for the demotion\n" %
692 server)
693 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
695 self.errf.write("Deactivating inbound replication\n")
697 nmsg = ldb.Message()
698 nmsg.dn = msg[0].dn
700 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
701 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
702 samdb.modify(nmsg)
704 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
706 self.errf.write("Asking partner server %s to synchronize from us\n"
707 % server)
708 for part in (samdb.get_schema_basedn(),
709 samdb.get_config_basedn(),
710 samdb.get_root_basedn()):
711 try:
712 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
713 except drsException, e:
714 self.errf.write(
715 "Error while demoting, "
716 "re-enabling inbound replication\n")
717 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
718 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
719 samdb.modify(nmsg)
720 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
721 try:
722 remote_samdb = SamDB(url="ldap://%s" % server,
723 session_info=system_session(),
724 credentials=creds, lp=lp)
726 self.errf.write("Changing userControl and container\n")
727 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
728 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
729 netbios_name.upper(),
730 attrs=["userAccountControl"])
731 dc_dn = res[0].dn
732 uac = int(str(res[0]["userAccountControl"]))
734 except Exception, e:
735 self.errf.write(
736 "Error while demoting, re-enabling inbound replication\n")
737 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
738 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
739 samdb.modify(nmsg)
740 raise CommandError("Error while changing account control", e)
742 if (len(res) != 1):
743 self.errf.write(
744 "Error while demoting, re-enabling inbound replication")
745 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
746 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
747 samdb.modify(nmsg)
748 raise CommandError("Unable to find object with samaccountName = %s$"
749 " in the remote dc" % netbios_name.upper())
751 olduac = uac
753 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
754 uac |= UF_WORKSTATION_TRUST_ACCOUNT
756 msg = ldb.Message()
757 msg.dn = dc_dn
759 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
760 ldb.FLAG_MOD_REPLACE,
761 "userAccountControl")
762 try:
763 remote_samdb.modify(msg)
764 except Exception, e:
765 self.errf.write(
766 "Error while demoting, re-enabling inbound replication")
767 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
768 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
769 samdb.modify(nmsg)
771 raise CommandError("Error while changing account control", e)
773 parent = msg.dn.parent()
774 rdn = str(res[0].dn)
775 rdn = string.replace(rdn, ",%s" % str(parent), "")
776 # Let's move to the Computer container
777 i = 0
778 newrdn = rdn
780 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
781 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
783 if (len(res) != 0):
784 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
785 scope=ldb.SCOPE_ONELEVEL)
786 while(len(res) != 0 and i < 100):
787 i = i + 1
788 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
789 scope=ldb.SCOPE_ONELEVEL)
791 if i == 100:
792 self.errf.write(
793 "Error while demoting, re-enabling inbound replication\n")
794 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
795 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
796 samdb.modify(nmsg)
798 msg = ldb.Message()
799 msg.dn = dc_dn
801 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
802 ldb.FLAG_MOD_REPLACE,
803 "userAccountControl")
805 remote_samdb.modify(msg)
807 raise CommandError("Unable to find a slot for renaming %s,"
808 " all names from %s-1 to %s-%d seemed used" %
809 (str(dc_dn), rdn, rdn, i - 9))
811 newrdn = "%s-%d" % (rdn, i)
813 try:
814 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
815 remote_samdb.rename(dc_dn, newdn)
816 except Exception, e:
817 self.errf.write(
818 "Error while demoting, re-enabling inbound replication\n")
819 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
820 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
821 samdb.modify(nmsg)
823 msg = ldb.Message()
824 msg.dn = dc_dn
826 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
827 ldb.FLAG_MOD_REPLACE,
828 "userAccountControl")
830 remote_samdb.modify(msg)
831 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
834 server_dsa_dn = samdb.get_serverName()
835 domain = remote_samdb.get_root_basedn()
837 try:
838 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
839 except drsException, e:
840 self.errf.write(
841 "Error while demoting, re-enabling inbound replication\n")
842 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
843 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
844 samdb.modify(nmsg)
846 msg = ldb.Message()
847 msg.dn = newdn
849 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
850 ldb.FLAG_MOD_REPLACE,
851 "userAccountControl")
852 print str(dc_dn)
853 remote_samdb.modify(msg)
854 remote_samdb.rename(newdn, dc_dn)
855 raise CommandError("Error while sending a removeDsServer", e)
857 for s in ("CN=Enterprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
858 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
859 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
860 try:
861 remote_samdb.delete(ldb.Dn(remote_samdb,
862 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
863 except ldb.LdbError, l:
864 pass
866 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
867 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
868 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
869 "CN=NTFRS Subscriptions"):
870 try:
871 remote_samdb.delete(ldb.Dn(remote_samdb,
872 "%s,%s" % (s, str(newdn))))
873 except ldb.LdbError, l:
874 pass
876 self.errf.write("Demote successful\n")
879 class cmd_domain_level(Command):
880 """Raise domain and forest function levels."""
882 synopsis = "%prog (show|raise <options>) [options]"
884 takes_optiongroups = {
885 "sambaopts": options.SambaOptions,
886 "credopts": options.CredentialsOptions,
887 "versionopts": options.VersionOptions,
890 takes_options = [
891 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
892 metavar="URL", dest="H"),
893 Option("--quiet", help="Be quiet", action="store_true"),
894 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
895 help="The forest function level (2003 | 2008 | 2008_R2)"),
896 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
897 help="The domain function level (2003 | 2008 | 2008_R2)")
900 takes_args = ["subcommand"]
902 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
903 quiet=False, credopts=None, sambaopts=None, versionopts=None):
904 lp = sambaopts.get_loadparm()
905 creds = credopts.get_credentials(lp, fallback_machine=True)
907 samdb = SamDB(url=H, session_info=system_session(),
908 credentials=creds, lp=lp)
910 domain_dn = samdb.domain_dn()
912 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
913 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
914 assert len(res_forest) == 1
916 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
917 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
918 assert len(res_domain) == 1
920 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
921 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
922 attrs=["msDS-Behavior-Version"])
923 assert len(res_dc_s) >= 1
925 try:
926 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
927 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
928 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
930 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
931 for msg in res_dc_s:
932 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
933 min_level_dc = int(msg["msDS-Behavior-Version"][0])
935 if level_forest < 0 or level_domain < 0:
936 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
937 if min_level_dc < 0:
938 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
939 if level_forest > level_domain:
940 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
941 if level_domain > min_level_dc:
942 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
944 except KeyError:
945 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
947 if subcommand == "show":
948 self.message("Domain and forest function level for domain '%s'" % domain_dn)
949 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
950 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
951 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
952 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
953 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
954 self.message("\nATTENTION: You run SAMBA 4 on a lowest function level of a DC lower than Windows 2003. This isn't supported! Please step-up or upgrade the concerning DC(s)!")
956 self.message("")
958 if level_forest == DS_DOMAIN_FUNCTION_2000:
959 outstr = "2000"
960 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
961 outstr = "2003 with mixed domains/interim (NT4 DC support)"
962 elif level_forest == DS_DOMAIN_FUNCTION_2003:
963 outstr = "2003"
964 elif level_forest == DS_DOMAIN_FUNCTION_2008:
965 outstr = "2008"
966 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
967 outstr = "2008 R2"
968 else:
969 outstr = "higher than 2008 R2"
970 self.message("Forest function level: (Windows) " + outstr)
972 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
973 outstr = "2000 mixed (NT4 DC support)"
974 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
975 outstr = "2000"
976 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
977 outstr = "2003 with mixed domains/interim (NT4 DC support)"
978 elif level_domain == DS_DOMAIN_FUNCTION_2003:
979 outstr = "2003"
980 elif level_domain == DS_DOMAIN_FUNCTION_2008:
981 outstr = "2008"
982 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
983 outstr = "2008 R2"
984 else:
985 outstr = "higher than 2008 R2"
986 self.message("Domain function level: (Windows) " + outstr)
988 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
989 outstr = "2000"
990 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
991 outstr = "2003"
992 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
993 outstr = "2008"
994 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
995 outstr = "2008 R2"
996 else:
997 outstr = "higher than 2008 R2"
998 self.message("Lowest function level of a DC: (Windows) " + outstr)
1000 elif subcommand == "raise":
1001 msgs = []
1003 if domain_level is not None:
1004 if domain_level == "2003":
1005 new_level_domain = DS_DOMAIN_FUNCTION_2003
1006 elif domain_level == "2008":
1007 new_level_domain = DS_DOMAIN_FUNCTION_2008
1008 elif domain_level == "2008_R2":
1009 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1011 if new_level_domain <= level_domain and level_domain_mixed == 0:
1012 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1014 if new_level_domain > min_level_dc:
1015 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1017 # Deactivate mixed/interim domain support
1018 if level_domain_mixed != 0:
1019 # Directly on the base DN
1020 m = ldb.Message()
1021 m.dn = ldb.Dn(samdb, domain_dn)
1022 m["nTMixedDomain"] = ldb.MessageElement("0",
1023 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1024 samdb.modify(m)
1025 # Under partitions
1026 m = ldb.Message()
1027 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1028 m["nTMixedDomain"] = ldb.MessageElement("0",
1029 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1030 try:
1031 samdb.modify(m)
1032 except ldb.LdbError, (enum, emsg):
1033 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1034 raise
1036 # Directly on the base DN
1037 m = ldb.Message()
1038 m.dn = ldb.Dn(samdb, domain_dn)
1039 m["msDS-Behavior-Version"]= ldb.MessageElement(
1040 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1041 "msDS-Behavior-Version")
1042 samdb.modify(m)
1043 # Under partitions
1044 m = ldb.Message()
1045 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1046 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1047 m["msDS-Behavior-Version"]= ldb.MessageElement(
1048 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1049 "msDS-Behavior-Version")
1050 try:
1051 samdb.modify(m)
1052 except ldb.LdbError, (enum, emsg):
1053 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1054 raise
1056 level_domain = new_level_domain
1057 msgs.append("Domain function level changed!")
1059 if forest_level is not None:
1060 if forest_level == "2003":
1061 new_level_forest = DS_DOMAIN_FUNCTION_2003
1062 elif forest_level == "2008":
1063 new_level_forest = DS_DOMAIN_FUNCTION_2008
1064 elif forest_level == "2008_R2":
1065 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1066 if new_level_forest <= level_forest:
1067 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1068 if new_level_forest > level_domain:
1069 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1070 m = ldb.Message()
1071 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1072 m["msDS-Behavior-Version"]= ldb.MessageElement(
1073 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1074 "msDS-Behavior-Version")
1075 samdb.modify(m)
1076 msgs.append("Forest function level changed!")
1077 msgs.append("All changes applied successfully!")
1078 self.message("\n".join(msgs))
1079 else:
1080 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1083 class cmd_domain_passwordsettings(Command):
1084 """Set password settings.
1086 Password complexity, password lockout policy, history length,
1087 minimum password length, the minimum and maximum password age) on
1088 a Samba AD DC server.
1090 Use against a Windows DC is possible, but group policy will override it.
1093 synopsis = "%prog (show|set <options>) [options]"
1095 takes_optiongroups = {
1096 "sambaopts": options.SambaOptions,
1097 "versionopts": options.VersionOptions,
1098 "credopts": options.CredentialsOptions,
1101 takes_options = [
1102 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1103 metavar="URL", dest="H"),
1104 Option("--quiet", help="Be quiet", action="store_true"),
1105 Option("--complexity", type="choice", choices=["on","off","default"],
1106 help="The password complexity (on | off | default). Default is 'on'"),
1107 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1108 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1109 Option("--history-length",
1110 help="The password history length (<integer> | default). Default is 24.", type=str),
1111 Option("--min-pwd-length",
1112 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1113 Option("--min-pwd-age",
1114 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1115 Option("--max-pwd-age",
1116 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1117 Option("--account-lockout-duration",
1118 help="The the length of time an account is locked out after exeeding the limit on bad password attempts (<integer in mins> | default). Default is 30 mins.", type=str),
1119 Option("--account-lockout-threshold",
1120 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1121 Option("--reset-account-lockout-after",
1122 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1125 takes_args = ["subcommand"]
1127 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1128 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1129 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1130 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1131 versionopts=None):
1132 lp = sambaopts.get_loadparm()
1133 creds = credopts.get_credentials(lp)
1135 samdb = SamDB(url=H, session_info=system_session(),
1136 credentials=creds, lp=lp)
1138 domain_dn = samdb.domain_dn()
1139 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1140 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1141 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1142 "lockOutObservationWindow"])
1143 assert(len(res) == 1)
1144 try:
1145 pwd_props = int(res[0]["pwdProperties"][0])
1146 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1147 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1148 # ticks -> days
1149 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1150 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1151 cur_max_pwd_age = 0
1152 else:
1153 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1154 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1155 # ticks -> mins
1156 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1157 cur_account_lockout_duration = 0
1158 else:
1159 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1160 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1161 except Exception, e:
1162 raise CommandError("Could not retrieve password properties!", e)
1164 if subcommand == "show":
1165 self.message("Password informations for domain '%s'" % domain_dn)
1166 self.message("")
1167 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1168 self.message("Password complexity: on")
1169 else:
1170 self.message("Password complexity: off")
1171 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1172 self.message("Store plaintext passwords: on")
1173 else:
1174 self.message("Store plaintext passwords: off")
1175 self.message("Password history length: %d" % pwd_hist_len)
1176 self.message("Minimum password length: %d" % cur_min_pwd_len)
1177 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1178 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1179 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1180 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1181 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1182 elif subcommand == "set":
1183 msgs = []
1184 m = ldb.Message()
1185 m.dn = ldb.Dn(samdb, domain_dn)
1187 if complexity is not None:
1188 if complexity == "on" or complexity == "default":
1189 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1190 msgs.append("Password complexity activated!")
1191 elif complexity == "off":
1192 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1193 msgs.append("Password complexity deactivated!")
1195 if store_plaintext is not None:
1196 if store_plaintext == "on" or store_plaintext == "default":
1197 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1198 msgs.append("Plaintext password storage for changed passwords activated!")
1199 elif store_plaintext == "off":
1200 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1201 msgs.append("Plaintext password storage for changed passwords deactivated!")
1203 if complexity is not None or store_plaintext is not None:
1204 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1205 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1207 if history_length is not None:
1208 if history_length == "default":
1209 pwd_hist_len = 24
1210 else:
1211 pwd_hist_len = int(history_length)
1213 if pwd_hist_len < 0 or pwd_hist_len > 24:
1214 raise CommandError("Password history length must be in the range of 0 to 24!")
1216 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1217 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1218 msgs.append("Password history length changed!")
1220 if min_pwd_length is not None:
1221 if min_pwd_length == "default":
1222 min_pwd_len = 7
1223 else:
1224 min_pwd_len = int(min_pwd_length)
1226 if min_pwd_len < 0 or min_pwd_len > 14:
1227 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1229 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1230 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1231 msgs.append("Minimum password length changed!")
1233 if min_pwd_age is not None:
1234 if min_pwd_age == "default":
1235 min_pwd_age = 1
1236 else:
1237 min_pwd_age = int(min_pwd_age)
1239 if min_pwd_age < 0 or min_pwd_age > 998:
1240 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1242 # days -> ticks
1243 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1245 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1246 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1247 msgs.append("Minimum password age changed!")
1249 if max_pwd_age is not None:
1250 if max_pwd_age == "default":
1251 max_pwd_age = 43
1252 else:
1253 max_pwd_age = int(max_pwd_age)
1255 if max_pwd_age < 0 or max_pwd_age > 999:
1256 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1258 # days -> ticks
1259 if max_pwd_age == 0:
1260 max_pwd_age_ticks = -0x8000000000000000
1261 else:
1262 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1264 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1265 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1266 msgs.append("Maximum password age changed!")
1268 if account_lockout_duration is not None:
1269 if account_lockout_duration == "default":
1270 account_lockout_duration = 30
1271 else:
1272 account_lockout_duration = int(account_lockout_duration)
1274 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1275 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1277 # days -> ticks
1278 if account_lockout_duration == 0:
1279 account_lockout_duration_ticks = -0x8000000000000000
1280 else:
1281 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1283 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1284 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1285 msgs.append("Account lockout duration changed!")
1287 if account_lockout_threshold is not None:
1288 if account_lockout_threshold == "default":
1289 account_lockout_threshold = 0
1290 else:
1291 account_lockout_threshold = int(account_lockout_threshold)
1293 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1294 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1295 msgs.append("Account lockout threshold changed!")
1297 if reset_account_lockout_after is not None:
1298 if reset_account_lockout_after == "default":
1299 reset_account_lockout_after = 30
1300 else:
1301 reset_account_lockout_after = int(reset_account_lockout_after)
1303 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1304 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1306 # days -> ticks
1307 if reset_account_lockout_after == 0:
1308 reset_account_lockout_after_ticks = -0x8000000000000000
1309 else:
1310 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1312 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1313 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1314 msgs.append("Duration to reset account lockout after changed!")
1316 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1317 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1319 if len(m) == 0:
1320 raise CommandError("You must specify at least one option to set. Try --help")
1321 samdb.modify(m)
1322 msgs.append("All changes applied successfully!")
1323 self.message("\n".join(msgs))
1324 else:
1325 raise CommandError("Wrong argument '%s'!" % subcommand)
1328 class cmd_domain_classicupgrade(Command):
1329 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1331 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1332 the testparm utility from your classic installation (with --testparm).
1335 synopsis = "%prog [options] <classic_smb_conf>"
1337 takes_optiongroups = {
1338 "sambaopts": options.SambaOptions,
1339 "versionopts": options.VersionOptions
1342 takes_options = [
1343 Option("--dbdir", type="string", metavar="DIR",
1344 help="Path to samba classic DC database directory"),
1345 Option("--testparm", type="string", metavar="PATH",
1346 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1347 Option("--targetdir", type="string", metavar="DIR",
1348 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1349 Option("--quiet", help="Be quiet", action="store_true"),
1350 Option("--verbose", help="Be verbose", action="store_true"),
1351 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1352 help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default="auto"),
1353 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1354 action="store_true"),
1355 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1356 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1357 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1358 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1359 "BIND9_DLZ uses samba4 AD to store zone information, "
1360 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1361 default="SAMBA_INTERNAL")
1364 takes_args = ["smbconf"]
1366 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1367 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1368 dns_backend=None, use_ntvfs=False):
1370 if not os.path.exists(smbconf):
1371 raise CommandError("File %s does not exist" % smbconf)
1373 if testparm and not os.path.exists(testparm):
1374 raise CommandError("Testparm utility %s does not exist" % testparm)
1376 if dbdir and not os.path.exists(dbdir):
1377 raise CommandError("Directory %s does not exist" % dbdir)
1379 if not dbdir and not testparm:
1380 raise CommandError("Please specify either dbdir or testparm")
1382 logger = self.get_logger()
1383 if verbose:
1384 logger.setLevel(logging.DEBUG)
1385 elif quiet:
1386 logger.setLevel(logging.WARNING)
1387 else:
1388 logger.setLevel(logging.INFO)
1390 if dbdir and testparm:
1391 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1392 dbdir = None
1394 lp = sambaopts.get_loadparm()
1396 s3conf = s3param.get_context()
1398 if sambaopts.realm:
1399 s3conf.set("realm", sambaopts.realm)
1401 if targetdir is not None:
1402 if not os.path.isdir(targetdir):
1403 os.mkdir(targetdir)
1405 eadb = True
1406 if use_xattrs == "yes":
1407 eadb = False
1408 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1409 if targetdir:
1410 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1411 else:
1412 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1413 try:
1414 try:
1415 samba.ntacls.setntacl(lp, tmpfile.name,
1416 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1417 eadb = False
1418 except Exception:
1419 # FIXME: Don't catch all exceptions here
1420 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1421 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1422 finally:
1423 tmpfile.close()
1425 # Set correct default values from dbdir or testparm
1426 paths = {}
1427 if dbdir:
1428 paths["state directory"] = dbdir
1429 paths["private dir"] = dbdir
1430 paths["lock directory"] = dbdir
1431 paths["smb passwd file"] = dbdir + "/smbpasswd"
1432 else:
1433 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1434 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1435 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1436 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1437 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1438 # "state directory", instead make use of "lock directory"
1439 if len(paths["state directory"]) == 0:
1440 paths["state directory"] = paths["lock directory"]
1442 for p in paths:
1443 s3conf.set(p, paths[p])
1445 # load smb.conf parameters
1446 logger.info("Reading smb.conf")
1447 s3conf.load(smbconf)
1448 samba3 = Samba3(smbconf, s3conf)
1450 logger.info("Provisioning")
1451 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1452 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1455 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1456 __doc__ = cmd_domain_classicupgrade.__doc__
1458 # This command is present for backwards compatibility only,
1459 # and should not be shown.
1461 hidden = True
1464 class cmd_domain(SuperCommand):
1465 """Domain management."""
1467 subcommands = {}
1468 subcommands["demote"] = cmd_domain_demote()
1469 if cmd_domain_export_keytab is not None:
1470 subcommands["exportkeytab"] = cmd_domain_export_keytab()
1471 subcommands["info"] = cmd_domain_info()
1472 subcommands["provision"] = cmd_domain_provision()
1473 subcommands["join"] = cmd_domain_join()
1474 subcommands["dcpromo"] = cmd_domain_dcpromo()
1475 subcommands["level"] = cmd_domain_level()
1476 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
1477 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
1478 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()