fix 2 typos
[Samba.git] / python / samba / netcmd / domain.py
blob276590273ee1db438b5fd86c9a7d6144ce3bab17
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.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
39 from samba.netcmd import (
40 Command,
41 CommandError,
42 SuperCommand,
43 Option
45 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
46 from samba.samba3 import Samba3
47 from samba.samba3 import param as s3param
48 from samba.upgrade import upgrade_from_samba3
49 from samba.drs_utils import (
50 sendDsReplicaSync, drsuapi_connect, drsException,
51 sendRemoveDsServer)
54 from samba.dsdb import (
55 DS_DOMAIN_FUNCTION_2000,
56 DS_DOMAIN_FUNCTION_2003,
57 DS_DOMAIN_FUNCTION_2003_MIXED,
58 DS_DOMAIN_FUNCTION_2008,
59 DS_DOMAIN_FUNCTION_2008_R2,
60 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
61 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
62 UF_WORKSTATION_TRUST_ACCOUNT,
63 UF_SERVER_TRUST_ACCOUNT,
64 UF_TRUSTED_FOR_DELEGATION
67 from samba.credentials import DONT_USE_KERBEROS
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 class cmd_domain_export_keytab(Command):
87 """Dump Kerberos keys of the domain into a keytab."""
89 synopsis = "%prog <keytab> [options]"
91 takes_optiongroups = {
92 "sambaopts": options.SambaOptions,
93 "credopts": options.CredentialsOptions,
94 "versionopts": options.VersionOptions,
97 takes_options = [
98 Option("--principal", help="extract only this principal", type=str),
101 takes_args = ["keytab"]
103 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
104 lp = sambaopts.get_loadparm()
105 net = Net(None, lp)
106 net.export_keytab(keytab=keytab, principal=principal)
107 except:
108 cmd_domain_export_keytab = None
111 class cmd_domain_info(Command):
112 """Print basic info about a domain and the DC passed as parameter."""
114 synopsis = "%prog <ip_address> [options]"
116 takes_options = [
119 takes_optiongroups = {
120 "sambaopts": options.SambaOptions,
121 "credopts": options.CredentialsOptions,
122 "versionopts": options.VersionOptions,
125 takes_args = ["address"]
127 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
128 lp = sambaopts.get_loadparm()
129 try:
130 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
131 except RuntimeError:
132 raise CommandError("Invalid IP address '" + address + "'!")
133 self.outf.write("Forest : %s\n" % res.forest)
134 self.outf.write("Domain : %s\n" % res.dns_domain)
135 self.outf.write("Netbios domain : %s\n" % res.domain_name)
136 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
137 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
138 self.outf.write("Server site : %s\n" % res.server_site)
139 self.outf.write("Client site : %s\n" % res.client_site)
142 class cmd_domain_provision(Command):
143 """Provision a domain."""
145 synopsis = "%prog [options]"
147 takes_optiongroups = {
148 "sambaopts": options.SambaOptions,
149 "versionopts": options.VersionOptions,
152 takes_options = [
153 Option("--interactive", help="Ask for names", action="store_true"),
154 Option("--domain", type="string", metavar="DOMAIN",
155 help="set domain"),
156 Option("--domain-guid", type="string", metavar="GUID",
157 help="set domainguid (otherwise random)"),
158 Option("--domain-sid", type="string", metavar="SID",
159 help="set domainsid (otherwise random)"),
160 Option("--ntds-guid", type="string", metavar="GUID",
161 help="set NTDS object GUID (otherwise random)"),
162 Option("--invocationid", type="string", metavar="GUID",
163 help="set invocationid (otherwise random)"),
164 Option("--host-name", type="string", metavar="HOSTNAME",
165 help="set hostname"),
166 Option("--host-ip", type="string", metavar="IPADDRESS",
167 help="set IPv4 ipaddress"),
168 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
169 help="set IPv6 ipaddress"),
170 Option("--adminpass", type="string", metavar="PASSWORD",
171 help="choose admin password (otherwise random)"),
172 Option("--krbtgtpass", type="string", metavar="PASSWORD",
173 help="choose krbtgt password (otherwise random)"),
174 Option("--machinepass", type="string", metavar="PASSWORD",
175 help="choose machine password (otherwise random)"),
176 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
177 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
178 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
179 "BIND9_FLATFILE uses bind9 text database to store zone information, "
180 "BIND9_DLZ uses samba4 AD to store zone information, "
181 "NONE skips the DNS setup entirely (not recommended)",
182 default="SAMBA_INTERNAL"),
183 Option("--dnspass", type="string", metavar="PASSWORD",
184 help="choose dns password (otherwise random)"),
185 Option("--ldapadminpass", type="string", metavar="PASSWORD",
186 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
187 Option("--root", type="string", metavar="USERNAME",
188 help="choose 'root' unix username"),
189 Option("--nobody", type="string", metavar="USERNAME",
190 help="choose 'nobody' user"),
191 Option("--users", type="string", metavar="GROUPNAME",
192 help="choose 'users' group"),
193 Option("--quiet", help="Be quiet", action="store_true"),
194 Option("--blank", action="store_true",
195 help="do not add users or groups, just the structure"),
196 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
197 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
198 choices=["fedora-ds", "openldap"]),
199 Option("--server-role", type="choice", metavar="ROLE",
200 choices=["domain controller", "dc", "member server", "member", "standalone"],
201 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
202 default="domain controller"),
203 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
204 choices=["2000", "2003", "2008", "2008_R2"],
205 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2003 Native.",
206 default="2003"),
207 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
208 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
209 Option("--partitions-only",
210 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
211 Option("--targetdir", type="string", metavar="DIR",
212 help="Set target directory"),
213 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
214 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\""),
215 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"),
216 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
217 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
220 openldap_options = [
221 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",
222 action="store_true"),
223 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
224 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."),
225 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
226 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
227 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"),
228 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
231 if os.getenv('TEST_LDAP', "no") == "yes":
232 takes_options.extend(openldap_options)
234 takes_args = []
236 def run(self, sambaopts=None, versionopts=None,
237 interactive=None,
238 domain=None,
239 domain_guid=None,
240 domain_sid=None,
241 ntds_guid=None,
242 invocationid=None,
243 host_name=None,
244 host_ip=None,
245 host_ip6=None,
246 adminpass=None,
247 krbtgtpass=None,
248 machinepass=None,
249 dns_backend=None,
250 dns_forwarder=None,
251 dnspass=None,
252 ldapadminpass=None,
253 root=None,
254 nobody=None,
255 users=None,
256 quiet=None,
257 blank=None,
258 ldap_backend_type=None,
259 server_role=None,
260 function_level=None,
261 next_rid=None,
262 partitions_only=None,
263 targetdir=None,
264 ol_mmr_urls=None,
265 use_xattrs=None,
266 slapd_path=None,
267 use_ntvfs=None,
268 use_rfc2307=None,
269 ldap_backend_nosync=None,
270 ldap_backend_extra_port=None,
271 ldap_backend_forced_uri=None,
272 ldap_dryrun_mode=None):
274 self.logger = self.get_logger("provision")
275 if quiet:
276 self.logger.setLevel(logging.WARNING)
277 else:
278 self.logger.setLevel(logging.INFO)
280 lp = sambaopts.get_loadparm()
281 smbconf = lp.configfile
283 if dns_forwarder is not None:
284 suggested_forwarder = dns_forwarder
285 else:
286 suggested_forwarder = self._get_nameserver_ip()
287 if suggested_forwarder is None:
288 suggested_forwarder = "none"
290 if len(self.raw_argv) == 1:
291 interactive = True
293 if interactive:
294 from getpass import getpass
295 import socket
297 def ask(prompt, default=None):
298 if default is not None:
299 print "%s [%s]: " % (prompt, default),
300 else:
301 print "%s: " % (prompt,),
302 return sys.stdin.readline().rstrip("\n") or default
304 try:
305 default = socket.getfqdn().split(".", 1)[1].upper()
306 except IndexError:
307 default = None
308 realm = ask("Realm", default)
309 if realm in (None, ""):
310 raise CommandError("No realm set!")
312 try:
313 default = realm.split(".")[0]
314 except IndexError:
315 default = None
316 domain = ask("Domain", default)
317 if domain is None:
318 raise CommandError("No domain set!")
320 server_role = ask("Server Role (dc, member, standalone)", "dc")
322 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
323 if dns_backend in (None, ''):
324 raise CommandError("No DNS backend set!")
326 if dns_backend == "SAMBA_INTERNAL":
327 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
328 if dns_forwarder.lower() in (None, 'none'):
329 suggested_forwarder = None
330 dns_forwarder = None
332 while True:
333 adminpassplain = getpass("Administrator password: ")
334 if not adminpassplain:
335 self.errf.write("Invalid administrator password.\n")
336 else:
337 adminpassverify = getpass("Retype password: ")
338 if not adminpassplain == adminpassverify:
339 self.errf.write("Sorry, passwords do not match.\n")
340 else:
341 adminpass = adminpassplain
342 break
344 else:
345 realm = sambaopts._lp.get('realm')
346 if realm is None:
347 raise CommandError("No realm set!")
348 if domain is None:
349 raise CommandError("No domain set!")
351 if not adminpass:
352 self.logger.info("Administrator password will be set randomly!")
354 if function_level == "2000":
355 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
356 elif function_level == "2003":
357 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
358 elif function_level == "2008":
359 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
360 elif function_level == "2008_R2":
361 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
363 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
364 dns_forwarder = suggested_forwarder
366 samdb_fill = FILL_FULL
367 if blank:
368 samdb_fill = FILL_NT4SYNC
369 elif partitions_only:
370 samdb_fill = FILL_DRS
372 if targetdir is not None:
373 if not os.path.isdir(targetdir):
374 os.mkdir(targetdir)
376 eadb = True
378 if use_xattrs == "yes":
379 eadb = False
380 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
381 if targetdir:
382 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
383 else:
384 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
385 try:
386 try:
387 samba.ntacls.setntacl(lp, file.name,
388 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
389 eadb = False
390 except Exception:
391 self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
392 finally:
393 file.close()
395 if eadb:
396 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.")
397 if ldap_backend_type == "existing":
398 if dap_backend_forced_uri is not None:
399 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)
400 else:
401 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")
402 else:
403 if ldap_backend_forced_uri is not None:
404 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")
406 session = system_session()
407 try:
408 result = provision(self.logger,
409 session, smbconf=smbconf, targetdir=targetdir,
410 samdb_fill=samdb_fill, realm=realm, domain=domain,
411 domainguid=domain_guid, domainsid=domain_sid,
412 hostname=host_name,
413 hostip=host_ip, hostip6=host_ip6,
414 ntdsguid=ntds_guid,
415 invocationid=invocationid, adminpass=adminpass,
416 krbtgtpass=krbtgtpass, machinepass=machinepass,
417 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
418 dnspass=dnspass, root=root, nobody=nobody,
419 users=users,
420 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
421 backend_type=ldap_backend_type,
422 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
423 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
424 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
425 ldap_backend_extra_port=ldap_backend_extra_port,
426 ldap_backend_forced_uri=ldap_backend_forced_uri,
427 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
429 except ProvisioningError, e:
430 raise CommandError("Provision failed", e)
432 result.report_logger(self.logger)
434 def _get_nameserver_ip(self):
435 """Grab the nameserver IP address from /etc/resolv.conf."""
436 from os import path
437 RESOLV_CONF="/etc/resolv.conf"
439 if not path.isfile(RESOLV_CONF):
440 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
441 return None
443 handle = None
444 try:
445 handle = open(RESOLV_CONF, 'r')
446 for line in handle:
447 if not line.startswith('nameserver'):
448 continue
449 # we want the last non-space continuous string of the line
450 return line.strip().split()[-1]
451 finally:
452 if handle is not None:
453 handle.close()
455 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
458 class cmd_domain_dcpromo(Command):
459 """Promote an existing domain member or NT4 PDC to an AD DC."""
461 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
463 takes_optiongroups = {
464 "sambaopts": options.SambaOptions,
465 "versionopts": options.VersionOptions,
466 "credopts": options.CredentialsOptions,
469 takes_options = [
470 Option("--server", help="DC to join", type=str),
471 Option("--site", help="site to join", type=str),
472 Option("--targetdir", help="where to store provision", type=str),
473 Option("--domain-critical-only",
474 help="only replicate critical domain objects",
475 action="store_true"),
476 Option("--machinepass", type=str, metavar="PASSWORD",
477 help="choose machine password (otherwise random)"),
478 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
479 action="store_true"),
480 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
481 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
482 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
483 "BIND9_DLZ uses samba4 AD to store zone information, "
484 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
485 default="SAMBA_INTERNAL"),
486 Option("--quiet", help="Be quiet", action="store_true"),
487 Option("--verbose", help="Be verbose", action="store_true")
490 takes_args = ["domain", "role?"]
492 def run(self, domain, role=None, sambaopts=None, credopts=None,
493 versionopts=None, server=None, site=None, targetdir=None,
494 domain_critical_only=False, parent_domain=None, machinepass=None,
495 use_ntvfs=False, dns_backend=None,
496 quiet=False, verbose=False):
497 lp = sambaopts.get_loadparm()
498 creds = credopts.get_credentials(lp)
499 net = Net(creds, lp, server=credopts.ipaddress)
501 if site is None:
502 site = "Default-First-Site-Name"
504 logger = self.get_logger()
505 if verbose:
506 logger.setLevel(logging.DEBUG)
507 elif quiet:
508 logger.setLevel(logging.WARNING)
509 else:
510 logger.setLevel(logging.INFO)
512 if site is None:
513 site = "Default-First-Site-Name"
515 netbios_name = lp.get("netbios name")
517 if not role is None:
518 role = role.upper()
520 if role == "DC":
521 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
522 site=site, netbios_name=netbios_name, targetdir=targetdir,
523 domain_critical_only=domain_critical_only,
524 machinepass=machinepass, use_ntvfs=use_ntvfs,
525 dns_backend=dns_backend,
526 promote_existing=True)
527 elif role == "RODC":
528 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
529 site=site, netbios_name=netbios_name, targetdir=targetdir,
530 domain_critical_only=domain_critical_only,
531 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
532 promote_existing=True)
533 else:
534 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
537 class cmd_domain_join(Command):
538 """Join domain as either member or backup domain controller."""
540 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
542 takes_optiongroups = {
543 "sambaopts": options.SambaOptions,
544 "versionopts": options.VersionOptions,
545 "credopts": options.CredentialsOptions,
548 takes_options = [
549 Option("--server", help="DC to join", type=str),
550 Option("--site", help="site to join", type=str),
551 Option("--targetdir", help="where to store provision", type=str),
552 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
553 Option("--domain-critical-only",
554 help="only replicate critical domain objects",
555 action="store_true"),
556 Option("--machinepass", type=str, metavar="PASSWORD",
557 help="choose machine password (otherwise random)"),
558 Option("--adminpass", type="string", metavar="PASSWORD",
559 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
560 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
561 action="store_true"),
562 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
563 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
564 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
565 "BIND9_DLZ uses samba4 AD to store zone information, "
566 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
567 default="SAMBA_INTERNAL"),
568 Option("--quiet", help="Be quiet", action="store_true"),
569 Option("--verbose", help="Be verbose", action="store_true")
572 takes_args = ["domain", "role?"]
574 def run(self, domain, role=None, sambaopts=None, credopts=None,
575 versionopts=None, server=None, site=None, targetdir=None,
576 domain_critical_only=False, parent_domain=None, machinepass=None,
577 use_ntvfs=False, dns_backend=None, adminpass=None,
578 quiet=False, verbose=False):
579 lp = sambaopts.get_loadparm()
580 creds = credopts.get_credentials(lp)
581 net = Net(creds, lp, server=credopts.ipaddress)
583 if site is None:
584 site = "Default-First-Site-Name"
586 logger = self.get_logger()
587 if verbose:
588 logger.setLevel(logging.DEBUG)
589 elif quiet:
590 logger.setLevel(logging.WARNING)
591 else:
592 logger.setLevel(logging.INFO)
594 netbios_name = lp.get("netbios name")
596 if not role is None:
597 role = role.upper()
599 if role is None or role == "MEMBER":
600 (join_password, sid, domain_name) = net.join_member(
601 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
602 machinepass=machinepass)
604 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
605 elif role == "DC":
606 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
607 site=site, netbios_name=netbios_name, targetdir=targetdir,
608 domain_critical_only=domain_critical_only,
609 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
610 elif role == "RODC":
611 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
612 site=site, netbios_name=netbios_name, targetdir=targetdir,
613 domain_critical_only=domain_critical_only,
614 machinepass=machinepass, use_ntvfs=use_ntvfs,
615 dns_backend=dns_backend)
616 elif role == "SUBDOMAIN":
617 if not adminpass:
618 logger.info("Administrator password will be set randomly!")
620 netbios_domain = lp.get("workgroup")
621 if parent_domain is None:
622 parent_domain = ".".join(domain.split(".")[1:])
623 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
624 parent_domain=parent_domain, site=site,
625 netbios_name=netbios_name, netbios_domain=netbios_domain,
626 targetdir=targetdir, machinepass=machinepass,
627 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
628 adminpass=adminpass)
629 else:
630 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
633 class cmd_domain_demote(Command):
634 """Demote ourselves from the role of Domain Controller."""
636 synopsis = "%prog [options]"
638 takes_options = [
639 Option("--server", help="DC to force replication before demote", type=str),
640 Option("--targetdir", help="where provision is stored", type=str),
643 takes_optiongroups = {
644 "sambaopts": options.SambaOptions,
645 "credopts": options.CredentialsOptions,
646 "versionopts": options.VersionOptions,
649 def run(self, sambaopts=None, credopts=None,
650 versionopts=None, server=None, targetdir=None):
651 lp = sambaopts.get_loadparm()
652 creds = credopts.get_credentials(lp)
653 net = Net(creds, lp, server=credopts.ipaddress)
655 netbios_name = lp.get("netbios name")
656 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
657 if not server:
658 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
659 if (len(res) == 0):
660 raise CommandError("Unable to search for servers")
662 if (len(res) == 1):
663 raise CommandError("You are the latest server in the domain")
665 server = None
666 for e in res:
667 if str(e["name"]).lower() != netbios_name.lower():
668 server = e["dnsHostName"]
669 break
671 ntds_guid = samdb.get_ntds_GUID()
672 msg = samdb.search(base=str(samdb.get_config_basedn()),
673 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
674 attrs=['options'])
675 if len(msg) == 0 or "options" not in msg[0]:
676 raise CommandError("Failed to find options on %s" % ntds_guid)
678 ntds_dn = msg[0].dn
679 dsa_options = int(str(msg[0]['options']))
681 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
682 controls=["search_options:1:2"])
684 if len(res) != 0:
685 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
687 self.errf.write("Using %s as partner server for the demotion\n" %
688 server)
689 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
691 self.errf.write("Desactivating inbound replication\n")
693 nmsg = ldb.Message()
694 nmsg.dn = msg[0].dn
696 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
697 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
698 samdb.modify(nmsg)
700 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
702 self.errf.write("Asking partner server %s to synchronize from us\n"
703 % server)
704 for part in (samdb.get_schema_basedn(),
705 samdb.get_config_basedn(),
706 samdb.get_root_basedn()):
707 try:
708 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
709 except drsException, e:
710 self.errf.write(
711 "Error while demoting, "
712 "re-enabling inbound replication\n")
713 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
714 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
715 samdb.modify(nmsg)
716 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
717 try:
718 remote_samdb = SamDB(url="ldap://%s" % server,
719 session_info=system_session(),
720 credentials=creds, lp=lp)
722 self.errf.write("Changing userControl and container\n")
723 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
724 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
725 netbios_name.upper(),
726 attrs=["userAccountControl"])
727 dc_dn = res[0].dn
728 uac = int(str(res[0]["userAccountControl"]))
730 except Exception, e:
731 self.errf.write(
732 "Error while demoting, re-enabling inbound replication\n")
733 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
734 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
735 samdb.modify(nmsg)
736 raise CommandError("Error while changing account control", e)
738 if (len(res) != 1):
739 self.errf.write(
740 "Error while demoting, re-enabling inbound replication")
741 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
742 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
743 samdb.modify(nmsg)
744 raise CommandError("Unable to find object with samaccountName = %s$"
745 " in the remote dc" % netbios_name.upper())
747 olduac = uac
749 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
750 uac |= UF_WORKSTATION_TRUST_ACCOUNT
752 msg = ldb.Message()
753 msg.dn = dc_dn
755 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
756 ldb.FLAG_MOD_REPLACE,
757 "userAccountControl")
758 try:
759 remote_samdb.modify(msg)
760 except Exception, e:
761 self.errf.write(
762 "Error while demoting, re-enabling inbound replication")
763 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
764 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
765 samdb.modify(nmsg)
767 raise CommandError("Error while changing account control", e)
769 parent = msg.dn.parent()
770 rdn = str(res[0].dn)
771 rdn = string.replace(rdn, ",%s" % str(parent), "")
772 # Let's move to the Computer container
773 i = 0
774 newrdn = rdn
776 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
777 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
779 if (len(res) != 0):
780 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
781 scope=ldb.SCOPE_ONELEVEL)
782 while(len(res) != 0 and i < 100):
783 i = i + 1
784 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
785 scope=ldb.SCOPE_ONELEVEL)
787 if i == 100:
788 self.errf.write(
789 "Error while demoting, re-enabling inbound replication\n")
790 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
791 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
792 samdb.modify(nmsg)
794 msg = ldb.Message()
795 msg.dn = dc_dn
797 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
798 ldb.FLAG_MOD_REPLACE,
799 "userAccountControl")
801 remote_samdb.modify(msg)
803 raise CommandError("Unable to find a slot for renaming %s,"
804 " all names from %s-1 to %s-%d seemed used" %
805 (str(dc_dn), rdn, rdn, i - 9))
807 newrdn = "%s-%d" % (rdn, i)
809 try:
810 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
811 remote_samdb.rename(dc_dn, newdn)
812 except Exception, e:
813 self.errf.write(
814 "Error while demoting, re-enabling inbound replication\n")
815 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
816 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
817 samdb.modify(nmsg)
819 msg = ldb.Message()
820 msg.dn = dc_dn
822 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
823 ldb.FLAG_MOD_REPLACE,
824 "userAccountControl")
826 remote_samdb.modify(msg)
827 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
830 server_dsa_dn = samdb.get_serverName()
831 domain = remote_samdb.get_root_basedn()
833 try:
834 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
835 except drsException, e:
836 self.errf.write(
837 "Error while demoting, re-enabling inbound replication\n")
838 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
839 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
840 samdb.modify(nmsg)
842 msg = ldb.Message()
843 msg.dn = newdn
845 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
846 ldb.FLAG_MOD_REPLACE,
847 "userAccountControl")
848 print str(dc_dn)
849 remote_samdb.modify(msg)
850 remote_samdb.rename(newdn, dc_dn)
851 raise CommandError("Error while sending a removeDsServer", e)
853 for s in ("CN=Enterprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
854 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
855 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
856 try:
857 remote_samdb.delete(ldb.Dn(remote_samdb,
858 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
859 except ldb.LdbError, l:
860 pass
862 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
863 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
864 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
865 "CN=NTFRS Subscriptions"):
866 try:
867 remote_samdb.delete(ldb.Dn(remote_samdb,
868 "%s,%s" % (s, str(newdn))))
869 except ldb.LdbError, l:
870 pass
872 self.errf.write("Demote successfull\n")
875 class cmd_domain_level(Command):
876 """Raise domain and forest function levels."""
878 synopsis = "%prog (show|raise <options>) [options]"
880 takes_optiongroups = {
881 "sambaopts": options.SambaOptions,
882 "credopts": options.CredentialsOptions,
883 "versionopts": options.VersionOptions,
886 takes_options = [
887 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
888 metavar="URL", dest="H"),
889 Option("--quiet", help="Be quiet", action="store_true"),
890 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
891 help="The forest function level (2003 | 2008 | 2008_R2)"),
892 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
893 help="The domain function level (2003 | 2008 | 2008_R2)")
896 takes_args = ["subcommand"]
898 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
899 quiet=False, credopts=None, sambaopts=None, versionopts=None):
900 lp = sambaopts.get_loadparm()
901 creds = credopts.get_credentials(lp, fallback_machine=True)
903 samdb = SamDB(url=H, session_info=system_session(),
904 credentials=creds, lp=lp)
906 domain_dn = samdb.domain_dn()
908 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
909 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
910 assert len(res_forest) == 1
912 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
913 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
914 assert len(res_domain) == 1
916 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
917 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
918 attrs=["msDS-Behavior-Version"])
919 assert len(res_dc_s) >= 1
921 try:
922 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
923 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
924 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
926 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
927 for msg in res_dc_s:
928 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
929 min_level_dc = int(msg["msDS-Behavior-Version"][0])
931 if level_forest < 0 or level_domain < 0:
932 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
933 if min_level_dc < 0:
934 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
935 if level_forest > level_domain:
936 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
937 if level_domain > min_level_dc:
938 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
940 except KeyError:
941 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
943 if subcommand == "show":
944 self.message("Domain and forest function level for domain '%s'" % domain_dn)
945 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
946 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
947 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
948 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
949 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
950 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)!")
952 self.message("")
954 if level_forest == DS_DOMAIN_FUNCTION_2000:
955 outstr = "2000"
956 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
957 outstr = "2003 with mixed domains/interim (NT4 DC support)"
958 elif level_forest == DS_DOMAIN_FUNCTION_2003:
959 outstr = "2003"
960 elif level_forest == DS_DOMAIN_FUNCTION_2008:
961 outstr = "2008"
962 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
963 outstr = "2008 R2"
964 else:
965 outstr = "higher than 2008 R2"
966 self.message("Forest function level: (Windows) " + outstr)
968 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
969 outstr = "2000 mixed (NT4 DC support)"
970 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
971 outstr = "2000"
972 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
973 outstr = "2003 with mixed domains/interim (NT4 DC support)"
974 elif level_domain == DS_DOMAIN_FUNCTION_2003:
975 outstr = "2003"
976 elif level_domain == DS_DOMAIN_FUNCTION_2008:
977 outstr = "2008"
978 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
979 outstr = "2008 R2"
980 else:
981 outstr = "higher than 2008 R2"
982 self.message("Domain function level: (Windows) " + outstr)
984 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
985 outstr = "2000"
986 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
987 outstr = "2003"
988 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
989 outstr = "2008"
990 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
991 outstr = "2008 R2"
992 else:
993 outstr = "higher than 2008 R2"
994 self.message("Lowest function level of a DC: (Windows) " + outstr)
996 elif subcommand == "raise":
997 msgs = []
999 if domain_level is not None:
1000 if domain_level == "2003":
1001 new_level_domain = DS_DOMAIN_FUNCTION_2003
1002 elif domain_level == "2008":
1003 new_level_domain = DS_DOMAIN_FUNCTION_2008
1004 elif domain_level == "2008_R2":
1005 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1007 if new_level_domain <= level_domain and level_domain_mixed == 0:
1008 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1010 if new_level_domain > min_level_dc:
1011 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1013 # Deactivate mixed/interim domain support
1014 if level_domain_mixed != 0:
1015 # Directly on the base DN
1016 m = ldb.Message()
1017 m.dn = ldb.Dn(samdb, domain_dn)
1018 m["nTMixedDomain"] = ldb.MessageElement("0",
1019 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1020 samdb.modify(m)
1021 # Under partitions
1022 m = ldb.Message()
1023 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1024 m["nTMixedDomain"] = ldb.MessageElement("0",
1025 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1026 try:
1027 samdb.modify(m)
1028 except ldb.LdbError, (enum, emsg):
1029 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1030 raise
1032 # Directly on the base DN
1033 m = ldb.Message()
1034 m.dn = ldb.Dn(samdb, domain_dn)
1035 m["msDS-Behavior-Version"]= ldb.MessageElement(
1036 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1037 "msDS-Behavior-Version")
1038 samdb.modify(m)
1039 # Under partitions
1040 m = ldb.Message()
1041 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1042 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1043 m["msDS-Behavior-Version"]= ldb.MessageElement(
1044 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1045 "msDS-Behavior-Version")
1046 try:
1047 samdb.modify(m)
1048 except ldb.LdbError, (enum, emsg):
1049 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1050 raise
1052 level_domain = new_level_domain
1053 msgs.append("Domain function level changed!")
1055 if forest_level is not None:
1056 if forest_level == "2003":
1057 new_level_forest = DS_DOMAIN_FUNCTION_2003
1058 elif forest_level == "2008":
1059 new_level_forest = DS_DOMAIN_FUNCTION_2008
1060 elif forest_level == "2008_R2":
1061 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1062 if new_level_forest <= level_forest:
1063 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1064 if new_level_forest > level_domain:
1065 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1066 m = ldb.Message()
1067 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1068 m["msDS-Behavior-Version"]= ldb.MessageElement(
1069 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1070 "msDS-Behavior-Version")
1071 samdb.modify(m)
1072 msgs.append("Forest function level changed!")
1073 msgs.append("All changes applied successfully!")
1074 self.message("\n".join(msgs))
1075 else:
1076 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1079 class cmd_domain_passwordsettings(Command):
1080 """Set password settings.
1082 Password complexity, history length, minimum password length, the minimum
1083 and maximum password age) on a Samba4 server.
1086 synopsis = "%prog (show|set <options>) [options]"
1088 takes_optiongroups = {
1089 "sambaopts": options.SambaOptions,
1090 "versionopts": options.VersionOptions,
1091 "credopts": options.CredentialsOptions,
1094 takes_options = [
1095 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1096 metavar="URL", dest="H"),
1097 Option("--quiet", help="Be quiet", action="store_true"),
1098 Option("--complexity", type="choice", choices=["on","off","default"],
1099 help="The password complexity (on | off | default). Default is 'on'"),
1100 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1101 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1102 Option("--history-length",
1103 help="The password history length (<integer> | default). Default is 24.", type=str),
1104 Option("--min-pwd-length",
1105 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1106 Option("--min-pwd-age",
1107 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1108 Option("--max-pwd-age",
1109 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1112 takes_args = ["subcommand"]
1114 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1115 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1116 min_pwd_length=None, credopts=None, sambaopts=None,
1117 versionopts=None):
1118 lp = sambaopts.get_loadparm()
1119 creds = credopts.get_credentials(lp)
1121 samdb = SamDB(url=H, session_info=system_session(),
1122 credentials=creds, lp=lp)
1124 domain_dn = samdb.domain_dn()
1125 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1126 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1127 "minPwdAge", "maxPwdAge"])
1128 assert(len(res) == 1)
1129 try:
1130 pwd_props = int(res[0]["pwdProperties"][0])
1131 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1132 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1133 # ticks -> days
1134 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1135 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1136 cur_max_pwd_age = 0
1137 else:
1138 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1139 except Exception, e:
1140 raise CommandError("Could not retrieve password properties!", e)
1142 if subcommand == "show":
1143 self.message("Password informations for domain '%s'" % domain_dn)
1144 self.message("")
1145 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1146 self.message("Password complexity: on")
1147 else:
1148 self.message("Password complexity: off")
1149 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1150 self.message("Store plaintext passwords: on")
1151 else:
1152 self.message("Store plaintext passwords: off")
1153 self.message("Password history length: %d" % pwd_hist_len)
1154 self.message("Minimum password length: %d" % cur_min_pwd_len)
1155 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1156 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1157 elif subcommand == "set":
1158 msgs = []
1159 m = ldb.Message()
1160 m.dn = ldb.Dn(samdb, domain_dn)
1162 if complexity is not None:
1163 if complexity == "on" or complexity == "default":
1164 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1165 msgs.append("Password complexity activated!")
1166 elif complexity == "off":
1167 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1168 msgs.append("Password complexity deactivated!")
1170 if store_plaintext is not None:
1171 if store_plaintext == "on" or store_plaintext == "default":
1172 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1173 msgs.append("Plaintext password storage for changed passwords activated!")
1174 elif store_plaintext == "off":
1175 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1176 msgs.append("Plaintext password storage for changed passwords deactivated!")
1178 if complexity is not None or store_plaintext is not None:
1179 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1180 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1182 if history_length is not None:
1183 if history_length == "default":
1184 pwd_hist_len = 24
1185 else:
1186 pwd_hist_len = int(history_length)
1188 if pwd_hist_len < 0 or pwd_hist_len > 24:
1189 raise CommandError("Password history length must be in the range of 0 to 24!")
1191 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1192 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1193 msgs.append("Password history length changed!")
1195 if min_pwd_length is not None:
1196 if min_pwd_length == "default":
1197 min_pwd_len = 7
1198 else:
1199 min_pwd_len = int(min_pwd_length)
1201 if min_pwd_len < 0 or min_pwd_len > 14:
1202 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1204 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1205 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1206 msgs.append("Minimum password length changed!")
1208 if min_pwd_age is not None:
1209 if min_pwd_age == "default":
1210 min_pwd_age = 1
1211 else:
1212 min_pwd_age = int(min_pwd_age)
1214 if min_pwd_age < 0 or min_pwd_age > 998:
1215 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1217 # days -> ticks
1218 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1220 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1221 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1222 msgs.append("Minimum password age changed!")
1224 if max_pwd_age is not None:
1225 if max_pwd_age == "default":
1226 max_pwd_age = 43
1227 else:
1228 max_pwd_age = int(max_pwd_age)
1230 if max_pwd_age < 0 or max_pwd_age > 999:
1231 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1233 # days -> ticks
1234 if max_pwd_age == 0:
1235 max_pwd_age_ticks = -0x8000000000000000
1236 else:
1237 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1239 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1240 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1241 msgs.append("Maximum password age changed!")
1243 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1244 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1246 if len(m) == 0:
1247 raise CommandError("You must specify at least one option to set. Try --help")
1248 samdb.modify(m)
1249 msgs.append("All changes applied successfully!")
1250 self.message("\n".join(msgs))
1251 else:
1252 raise CommandError("Wrong argument '%s'!" % subcommand)
1255 class cmd_domain_classicupgrade(Command):
1256 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1258 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1259 the testparm utility from your classic installation (with --testparm).
1262 synopsis = "%prog [options] <classic_smb_conf>"
1264 takes_optiongroups = {
1265 "sambaopts": options.SambaOptions,
1266 "versionopts": options.VersionOptions
1269 takes_options = [
1270 Option("--dbdir", type="string", metavar="DIR",
1271 help="Path to samba classic DC database directory"),
1272 Option("--testparm", type="string", metavar="PATH",
1273 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1274 Option("--targetdir", type="string", metavar="DIR",
1275 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1276 Option("--quiet", help="Be quiet", action="store_true"),
1277 Option("--verbose", help="Be verbose", action="store_true"),
1278 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1279 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"),
1280 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1281 action="store_true"),
1282 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1283 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1284 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1285 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1286 "BIND9_DLZ uses samba4 AD to store zone information, "
1287 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1288 default="SAMBA_INTERNAL")
1291 takes_args = ["smbconf"]
1293 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1294 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1295 dns_backend=None, use_ntvfs=False):
1297 if not os.path.exists(smbconf):
1298 raise CommandError("File %s does not exist" % smbconf)
1300 if testparm and not os.path.exists(testparm):
1301 raise CommandError("Testparm utility %s does not exist" % testparm)
1303 if dbdir and not os.path.exists(dbdir):
1304 raise CommandError("Directory %s does not exist" % dbdir)
1306 if not dbdir and not testparm:
1307 raise CommandError("Please specify either dbdir or testparm")
1309 logger = self.get_logger()
1310 if verbose:
1311 logger.setLevel(logging.DEBUG)
1312 elif quiet:
1313 logger.setLevel(logging.WARNING)
1314 else:
1315 logger.setLevel(logging.INFO)
1317 if dbdir and testparm:
1318 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1319 dbdir = None
1321 lp = sambaopts.get_loadparm()
1323 s3conf = s3param.get_context()
1325 if sambaopts.realm:
1326 s3conf.set("realm", sambaopts.realm)
1328 if targetdir is not None:
1329 if not os.path.isdir(targetdir):
1330 os.mkdir(targetdir)
1332 eadb = True
1333 if use_xattrs == "yes":
1334 eadb = False
1335 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1336 if targetdir:
1337 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1338 else:
1339 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1340 try:
1341 try:
1342 samba.ntacls.setntacl(lp, tmpfile.name,
1343 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1344 eadb = False
1345 except Exception:
1346 # FIXME: Don't catch all exceptions here
1347 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1348 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1349 finally:
1350 tmpfile.close()
1352 # Set correct default values from dbdir or testparm
1353 paths = {}
1354 if dbdir:
1355 paths["state directory"] = dbdir
1356 paths["private dir"] = dbdir
1357 paths["lock directory"] = dbdir
1358 paths["smb passwd file"] = dbdir + "/smbpasswd"
1359 else:
1360 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1361 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1362 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1363 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1364 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1365 # "state directory", instead make use of "lock directory"
1366 if len(paths["state directory"]) == 0:
1367 paths["state directory"] = paths["lock directory"]
1369 for p in paths:
1370 s3conf.set(p, paths[p])
1372 # load smb.conf parameters
1373 logger.info("Reading smb.conf")
1374 s3conf.load(smbconf)
1375 samba3 = Samba3(smbconf, s3conf)
1377 logger.info("Provisioning")
1378 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1379 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1382 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1383 __doc__ = cmd_domain_classicupgrade.__doc__
1385 # This command is present for backwards compatibility only,
1386 # and should not be shown.
1388 hidden = True
1391 class cmd_domain(SuperCommand):
1392 """Domain management."""
1394 subcommands = {}
1395 subcommands["demote"] = cmd_domain_demote()
1396 if cmd_domain_export_keytab is not None:
1397 subcommands["exportkeytab"] = cmd_domain_export_keytab()
1398 subcommands["info"] = cmd_domain_info()
1399 subcommands["provision"] = cmd_domain_provision()
1400 subcommands["join"] = cmd_domain_join()
1401 subcommands["dcpromo"] = cmd_domain_dcpromo()
1402 subcommands["level"] = cmd_domain_level()
1403 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
1404 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
1405 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()