build: Change bin/default/python -> bin/python symlink to bin/default/python_modules
[Samba.git] / source4 / scripting / python / samba / netcmd / domain.py
blob4ba305c2713fb3a9f3288a535a3d620d0c8a1891
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 FILL_FULL,
71 FILL_NT4SYNC,
72 FILL_DRS,
73 ProvisioningError,
76 def get_testparm_var(testparm, smbconf, varname):
77 cmd = "%s -s -l --parameter-name='%s' %s 2>/dev/null" % (testparm, varname, smbconf)
78 output = os.popen(cmd, 'r').readline()
79 return output.strip()
81 try:
82 import samba.dckeytab
83 class cmd_domain_export_keytab(Command):
84 """Dump Kerberos keys of the domain into a keytab."""
86 synopsis = "%prog <keytab> [options]"
88 takes_optiongroups = {
89 "sambaopts": options.SambaOptions,
90 "credopts": options.CredentialsOptions,
91 "versionopts": options.VersionOptions,
94 takes_options = [
95 Option("--principal", help="extract only this principal", type=str),
98 takes_args = ["keytab"]
100 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
101 lp = sambaopts.get_loadparm()
102 net = Net(None, lp)
103 net.export_keytab(keytab=keytab, principal=principal)
104 except:
105 cmd_domain_export_keytab = None
108 class cmd_domain_info(Command):
109 """Print basic info about a domain and the DC passed as parameter."""
111 synopsis = "%prog <ip_address> [options]"
113 takes_options = [
116 takes_optiongroups = {
117 "sambaopts": options.SambaOptions,
118 "credopts": options.CredentialsOptions,
119 "versionopts": options.VersionOptions,
122 takes_args = ["address"]
124 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
125 lp = sambaopts.get_loadparm()
126 try:
127 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
128 except RuntimeError:
129 raise CommandError("Invalid IP address '" + address + "'!")
130 self.outf.write("Forest : %s\n" % res.forest)
131 self.outf.write("Domain : %s\n" % res.dns_domain)
132 self.outf.write("Netbios domain : %s\n" % res.domain_name)
133 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
134 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
135 self.outf.write("Server site : %s\n" % res.server_site)
136 self.outf.write("Client site : %s\n" % res.client_site)
139 class cmd_domain_provision(Command):
140 """Provision a domain."""
142 synopsis = "%prog [options]"
144 takes_optiongroups = {
145 "sambaopts": options.SambaOptions,
146 "versionopts": options.VersionOptions,
147 "credopts": options.CredentialsOptions,
150 takes_options = [
151 Option("--interactive", help="Ask for names", action="store_true"),
152 Option("--domain", type="string", metavar="DOMAIN",
153 help="set domain"),
154 Option("--domain-guid", type="string", metavar="GUID",
155 help="set domainguid (otherwise random)"),
156 Option("--domain-sid", type="string", metavar="SID",
157 help="set domainsid (otherwise random)"),
158 Option("--ntds-guid", type="string", metavar="GUID",
159 help="set NTDS object GUID (otherwise random)"),
160 Option("--invocationid", type="string", metavar="GUID",
161 help="set invocationid (otherwise random)"),
162 Option("--host-name", type="string", metavar="HOSTNAME",
163 help="set hostname"),
164 Option("--host-ip", type="string", metavar="IPADDRESS",
165 help="set IPv4 ipaddress"),
166 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
167 help="set IPv6 ipaddress"),
168 Option("--adminpass", type="string", metavar="PASSWORD",
169 help="choose admin password (otherwise random)"),
170 Option("--krbtgtpass", type="string", metavar="PASSWORD",
171 help="choose krbtgt password (otherwise random)"),
172 Option("--machinepass", type="string", metavar="PASSWORD",
173 help="choose machine password (otherwise random)"),
174 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
175 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
176 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
177 "BIND9_FLATFILE uses bind9 text database to store zone information, "
178 "BIND9_DLZ uses samba4 AD to store zone information, "
179 "NONE skips the DNS setup entirely (not recommended)",
180 default="SAMBA_INTERNAL"),
181 Option("--dnspass", type="string", metavar="PASSWORD",
182 help="choose dns password (otherwise random)"),
183 Option("--ldapadminpass", type="string", metavar="PASSWORD",
184 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
185 Option("--root", type="string", metavar="USERNAME",
186 help="choose 'root' unix username"),
187 Option("--nobody", type="string", metavar="USERNAME",
188 help="choose 'nobody' user"),
189 Option("--users", type="string", metavar="GROUPNAME",
190 help="choose 'users' group"),
191 Option("--quiet", help="Be quiet", action="store_true"),
192 Option("--blank", action="store_true",
193 help="do not add users or groups, just the structure"),
194 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
195 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
196 choices=["fedora-ds", "openldap"]),
197 Option("--server-role", type="choice", metavar="ROLE",
198 choices=["domain controller", "dc", "member server", "member", "standalone"],
199 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
200 default="domain controller"),
201 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
202 choices=["2000", "2003", "2008", "2008_R2"],
203 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2003 Native.",
204 default="2003"),
205 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
206 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
207 Option("--partitions-only",
208 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
209 Option("--targetdir", type="string", metavar="DIR",
210 help="Set target directory"),
211 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
212 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\""),
213 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"),
214 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
215 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
217 takes_args = []
219 def run(self, sambaopts=None, credopts=None, versionopts=None,
220 interactive=None,
221 domain=None,
222 domain_guid=None,
223 domain_sid=None,
224 ntds_guid=None,
225 invocationid=None,
226 host_name=None,
227 host_ip=None,
228 host_ip6=None,
229 adminpass=None,
230 krbtgtpass=None,
231 machinepass=None,
232 dns_backend=None,
233 dns_forwarder=None,
234 dnspass=None,
235 ldapadminpass=None,
236 root=None,
237 nobody=None,
238 users=None,
239 quiet=None,
240 blank=None,
241 ldap_backend_type=None,
242 server_role=None,
243 function_level=None,
244 next_rid=None,
245 partitions_only=None,
246 targetdir=None,
247 ol_mmr_urls=None,
248 use_xattrs=None,
249 use_ntvfs=None,
250 use_rfc2307=None):
252 self.logger = self.get_logger("provision")
253 if quiet:
254 self.logger.setLevel(logging.WARNING)
255 else:
256 self.logger.setLevel(logging.INFO)
258 lp = sambaopts.get_loadparm()
259 smbconf = lp.configfile
261 creds = credopts.get_credentials(lp)
263 creds.set_kerberos_state(DONT_USE_KERBEROS)
265 if dns_forwarder is not None:
266 suggested_forwarder = dns_forwarder
267 else:
268 suggested_forwarder = self._get_nameserver_ip()
269 if suggested_forwarder is None:
270 suggested_forwarder = "none"
272 if len(self.raw_argv) == 1:
273 interactive = True
275 if interactive:
276 from getpass import getpass
277 import socket
279 def ask(prompt, default=None):
280 if default is not None:
281 print "%s [%s]: " % (prompt, default),
282 else:
283 print "%s: " % (prompt,),
284 return sys.stdin.readline().rstrip("\n") or default
286 try:
287 default = socket.getfqdn().split(".", 1)[1].upper()
288 except IndexError:
289 default = None
290 realm = ask("Realm", default)
291 if realm in (None, ""):
292 raise CommandError("No realm set!")
294 try:
295 default = realm.split(".")[0]
296 except IndexError:
297 default = None
298 domain = ask("Domain", default)
299 if domain is None:
300 raise CommandError("No domain set!")
302 server_role = ask("Server Role (dc, member, standalone)", "dc")
304 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
305 if dns_backend in (None, ''):
306 raise CommandError("No DNS backend set!")
308 if dns_backend == "SAMBA_INTERNAL":
309 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
310 if dns_forwarder.lower() in (None, 'none'):
311 suggested_forwarder = None
312 dns_forwarder = None
314 while True:
315 adminpassplain = getpass("Administrator password: ")
316 if not adminpassplain:
317 self.errf.write("Invalid administrator password.\n")
318 else:
319 adminpassverify = getpass("Retype password: ")
320 if not adminpassplain == adminpassverify:
321 self.errf.write("Sorry, passwords do not match.\n")
322 else:
323 adminpass = adminpassplain
324 break
326 else:
327 realm = sambaopts._lp.get('realm')
328 if realm is None:
329 raise CommandError("No realm set!")
330 if domain is None:
331 raise CommandError("No domain set!")
333 if not adminpass:
334 self.logger.info("Administrator password will be set randomly!")
336 if function_level == "2000":
337 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
338 elif function_level == "2003":
339 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
340 elif function_level == "2008":
341 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
342 elif function_level == "2008_R2":
343 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
345 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
346 dns_forwarder = suggested_forwarder
348 samdb_fill = FILL_FULL
349 if blank:
350 samdb_fill = FILL_NT4SYNC
351 elif partitions_only:
352 samdb_fill = FILL_DRS
354 if targetdir is not None:
355 if not os.path.isdir(targetdir):
356 os.mkdir(targetdir)
358 eadb = True
360 if use_xattrs == "yes":
361 eadb = False
362 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
363 if targetdir:
364 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
365 else:
366 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
367 try:
368 try:
369 samba.ntacls.setntacl(lp, file.name,
370 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
371 eadb = False
372 except Exception:
373 self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
374 finally:
375 file.close()
377 if eadb:
378 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.")
380 session = system_session()
381 try:
382 result = provision(self.logger,
383 session, creds, smbconf=smbconf, targetdir=targetdir,
384 samdb_fill=samdb_fill, realm=realm, domain=domain,
385 domainguid=domain_guid, domainsid=domain_sid,
386 hostname=host_name,
387 hostip=host_ip, hostip6=host_ip6,
388 ntdsguid=ntds_guid,
389 invocationid=invocationid, adminpass=adminpass,
390 krbtgtpass=krbtgtpass, machinepass=machinepass,
391 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
392 dnspass=dnspass, root=root, nobody=nobody,
393 users=users,
394 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
395 backend_type=ldap_backend_type,
396 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls,
397 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
398 use_rfc2307=use_rfc2307, skip_sysvolacl=False)
399 except ProvisioningError, e:
400 raise CommandError("Provision failed", e)
402 result.report_logger(self.logger)
404 def _get_nameserver_ip(self):
405 """Grab the nameserver IP address from /etc/resolv.conf."""
406 from os import path
407 RESOLV_CONF="/etc/resolv.conf"
409 if not path.isfile(RESOLV_CONF):
410 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
411 return None
413 handle = None
414 try:
415 handle = open(RESOLV_CONF, 'r')
416 for line in handle:
417 if not line.startswith('nameserver'):
418 continue
419 # we want the last non-space continuous string of the line
420 return line.strip().split()[-1]
421 finally:
422 if handle is not None:
423 handle.close()
425 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
428 class cmd_domain_dcpromo(Command):
429 """Promote an existing domain member or NT4 PDC to an AD DC."""
431 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
433 takes_optiongroups = {
434 "sambaopts": options.SambaOptions,
435 "versionopts": options.VersionOptions,
436 "credopts": options.CredentialsOptions,
439 takes_options = [
440 Option("--server", help="DC to join", type=str),
441 Option("--site", help="site to join", type=str),
442 Option("--targetdir", help="where to store provision", type=str),
443 Option("--domain-critical-only",
444 help="only replicate critical domain objects",
445 action="store_true"),
446 Option("--machinepass", type=str, metavar="PASSWORD",
447 help="choose machine password (otherwise random)"),
448 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
449 action="store_true"),
450 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
451 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
452 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
453 "BIND9_DLZ uses samba4 AD to store zone information, "
454 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
455 default="SAMBA_INTERNAL")
458 takes_args = ["domain", "role?"]
460 def run(self, domain, role=None, sambaopts=None, credopts=None,
461 versionopts=None, server=None, site=None, targetdir=None,
462 domain_critical_only=False, parent_domain=None, machinepass=None,
463 use_ntvfs=False, dns_backend=None):
464 lp = sambaopts.get_loadparm()
465 creds = credopts.get_credentials(lp)
466 net = Net(creds, lp, server=credopts.ipaddress)
468 if site is None:
469 site = "Default-First-Site-Name"
471 netbios_name = lp.get("netbios name")
473 if not role is None:
474 role = role.upper()
476 if role == "DC":
477 join_DC(server=server, creds=creds, lp=lp, domain=domain,
478 site=site, netbios_name=netbios_name, targetdir=targetdir,
479 domain_critical_only=domain_critical_only,
480 machinepass=machinepass, use_ntvfs=use_ntvfs,
481 dns_backend=dns_backend,
482 promote_existing=True)
483 elif role == "RODC":
484 join_RODC(server=server, creds=creds, lp=lp, domain=domain,
485 site=site, netbios_name=netbios_name, targetdir=targetdir,
486 domain_critical_only=domain_critical_only,
487 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
488 promote_existing=True)
489 else:
490 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
493 class cmd_domain_join(Command):
494 """Join domain as either member or backup domain controller."""
496 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
498 takes_optiongroups = {
499 "sambaopts": options.SambaOptions,
500 "versionopts": options.VersionOptions,
501 "credopts": options.CredentialsOptions,
504 takes_options = [
505 Option("--server", help="DC to join", type=str),
506 Option("--site", help="site to join", type=str),
507 Option("--targetdir", help="where to store provision", type=str),
508 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
509 Option("--domain-critical-only",
510 help="only replicate critical domain objects",
511 action="store_true"),
512 Option("--machinepass", type=str, metavar="PASSWORD",
513 help="choose machine password (otherwise random)"),
514 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
515 action="store_true"),
516 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
517 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
518 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
519 "BIND9_DLZ uses samba4 AD to store zone information, "
520 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
521 default="SAMBA_INTERNAL")
524 takes_args = ["domain", "role?"]
526 def run(self, domain, role=None, sambaopts=None, credopts=None,
527 versionopts=None, server=None, site=None, targetdir=None,
528 domain_critical_only=False, parent_domain=None, machinepass=None,
529 use_ntvfs=False, dns_backend=None):
530 lp = sambaopts.get_loadparm()
531 creds = credopts.get_credentials(lp)
532 net = Net(creds, lp, server=credopts.ipaddress)
534 if site is None:
535 site = "Default-First-Site-Name"
537 netbios_name = lp.get("netbios name")
539 if not role is None:
540 role = role.upper()
542 if role is None or role == "MEMBER":
543 (join_password, sid, domain_name) = net.join_member(
544 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
545 machinepass=machinepass)
547 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
548 elif role == "DC":
549 join_DC(server=server, creds=creds, lp=lp, domain=domain,
550 site=site, netbios_name=netbios_name, targetdir=targetdir,
551 domain_critical_only=domain_critical_only,
552 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
553 elif role == "RODC":
554 join_RODC(server=server, creds=creds, lp=lp, domain=domain,
555 site=site, netbios_name=netbios_name, targetdir=targetdir,
556 domain_critical_only=domain_critical_only,
557 machinepass=machinepass, use_ntvfs=use_ntvfs,
558 dns_backend=dns_backend)
559 elif role == "SUBDOMAIN":
560 netbios_domain = lp.get("workgroup")
561 if parent_domain is None:
562 parent_domain = ".".join(domain.split(".")[1:])
563 join_subdomain(server=server, creds=creds, lp=lp, dnsdomain=domain,
564 parent_domain=parent_domain, site=site,
565 netbios_name=netbios_name, netbios_domain=netbios_domain,
566 targetdir=targetdir, machinepass=machinepass,
567 use_ntvfs=use_ntvfs, dns_backend=dns_backend)
568 else:
569 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
572 class cmd_domain_demote(Command):
573 """Demote ourselves from the role of Domain Controller."""
575 synopsis = "%prog [options]"
577 takes_options = [
578 Option("--server", help="DC to force replication before demote", type=str),
579 Option("--targetdir", help="where provision is stored", type=str),
582 takes_optiongroups = {
583 "sambaopts": options.SambaOptions,
584 "credopts": options.CredentialsOptions,
585 "versionopts": options.VersionOptions,
588 def run(self, sambaopts=None, credopts=None,
589 versionopts=None, server=None, targetdir=None):
590 lp = sambaopts.get_loadparm()
591 creds = credopts.get_credentials(lp)
592 net = Net(creds, lp, server=credopts.ipaddress)
594 netbios_name = lp.get("netbios name")
595 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
596 if not server:
597 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
598 if (len(res) == 0):
599 raise CommandError("Unable to search for servers")
601 if (len(res) == 1):
602 raise CommandError("You are the latest server in the domain")
604 server = None
605 for e in res:
606 if str(e["name"]).lower() != netbios_name.lower():
607 server = e["dnsHostName"]
608 break
610 ntds_guid = samdb.get_ntds_GUID()
611 msg = samdb.search(base=str(samdb.get_config_basedn()),
612 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
613 attrs=['options'])
614 if len(msg) == 0 or "options" not in msg[0]:
615 raise CommandError("Failed to find options on %s" % ntds_guid)
617 ntds_dn = msg[0].dn
618 dsa_options = int(str(msg[0]['options']))
620 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
621 controls=["search_options:1:2"])
623 if len(res) != 0:
624 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
626 self.errf.write("Using %s as partner server for the demotion\n" %
627 server)
628 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
630 self.errf.write("Desactivating inbound replication\n")
632 nmsg = ldb.Message()
633 nmsg.dn = msg[0].dn
635 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
636 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
637 samdb.modify(nmsg)
639 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
641 self.errf.write("Asking partner server %s to synchronize from us\n"
642 % server)
643 for part in (samdb.get_schema_basedn(),
644 samdb.get_config_basedn(),
645 samdb.get_root_basedn()):
646 try:
647 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
648 except drsException, e:
649 self.errf.write(
650 "Error while demoting, "
651 "re-enabling inbound replication\n")
652 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
653 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
654 samdb.modify(nmsg)
655 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
656 try:
657 remote_samdb = SamDB(url="ldap://%s" % server,
658 session_info=system_session(),
659 credentials=creds, lp=lp)
661 self.errf.write("Changing userControl and container\n")
662 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
663 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
664 netbios_name.upper(),
665 attrs=["userAccountControl"])
666 dc_dn = res[0].dn
667 uac = int(str(res[0]["userAccountControl"]))
669 except Exception, e:
670 self.errf.write(
671 "Error while demoting, re-enabling inbound replication\n")
672 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
673 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
674 samdb.modify(nmsg)
675 raise CommandError("Error while changing account control", e)
677 if (len(res) != 1):
678 self.errf.write(
679 "Error while demoting, re-enabling inbound replication")
680 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
681 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
682 samdb.modify(nmsg)
683 raise CommandError("Unable to find object with samaccountName = %s$"
684 " in the remote dc" % netbios_name.upper())
686 olduac = uac
688 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
689 uac |= UF_WORKSTATION_TRUST_ACCOUNT
691 msg = ldb.Message()
692 msg.dn = dc_dn
694 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
695 ldb.FLAG_MOD_REPLACE,
696 "userAccountControl")
697 try:
698 remote_samdb.modify(msg)
699 except Exception, e:
700 self.errf.write(
701 "Error while demoting, re-enabling inbound replication")
702 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
703 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
704 samdb.modify(nmsg)
706 raise CommandError("Error while changing account control", e)
708 parent = msg.dn.parent()
709 rdn = str(res[0].dn)
710 rdn = string.replace(rdn, ",%s" % str(parent), "")
711 # Let's move to the Computer container
712 i = 0
713 newrdn = rdn
715 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
716 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
718 if (len(res) != 0):
719 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
720 scope=ldb.SCOPE_ONELEVEL)
721 while(len(res) != 0 and i < 100):
722 i = i + 1
723 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
724 scope=ldb.SCOPE_ONELEVEL)
726 if i == 100:
727 self.errf.write(
728 "Error while demoting, re-enabling inbound replication\n")
729 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
730 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
731 samdb.modify(nmsg)
733 msg = ldb.Message()
734 msg.dn = dc_dn
736 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
737 ldb.FLAG_MOD_REPLACE,
738 "userAccountControl")
740 remote_samdb.modify(msg)
742 raise CommandError("Unable to find a slot for renaming %s,"
743 " all names from %s-1 to %s-%d seemed used" %
744 (str(dc_dn), rdn, rdn, i - 9))
746 newrdn = "%s-%d" % (rdn, i)
748 try:
749 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
750 remote_samdb.rename(dc_dn, newdn)
751 except Exception, e:
752 self.errf.write(
753 "Error while demoting, re-enabling inbound replication\n")
754 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
755 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
756 samdb.modify(nmsg)
758 msg = ldb.Message()
759 msg.dn = dc_dn
761 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
762 ldb.FLAG_MOD_REPLACE,
763 "userAccountControl")
765 remote_samdb.modify(msg)
766 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
769 server_dsa_dn = samdb.get_serverName()
770 domain = remote_samdb.get_root_basedn()
772 try:
773 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
774 except drsException, e:
775 self.errf.write(
776 "Error while demoting, re-enabling inbound replication\n")
777 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
778 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
779 samdb.modify(nmsg)
781 msg = ldb.Message()
782 msg.dn = newdn
784 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
785 ldb.FLAG_MOD_REPLACE,
786 "userAccountControl")
787 print str(dc_dn)
788 remote_samdb.modify(msg)
789 remote_samdb.rename(newdn, dc_dn)
790 raise CommandError("Error while sending a removeDsServer", e)
792 for s in ("CN=Entreprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
793 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
794 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
795 try:
796 remote_samdb.delete(ldb.Dn(remote_samdb,
797 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
798 except ldb.LdbError, l:
799 pass
801 for s in ("CN=Entreprise,CN=NTFRS Subscriptions",
802 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
803 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
804 "CN=NTFRS Subscriptions"):
805 try:
806 remote_samdb.delete(ldb.Dn(remote_samdb,
807 "%s,%s" % (s, str(newdn))))
808 except ldb.LdbError, l:
809 pass
811 self.errf.write("Demote successfull\n")
814 class cmd_domain_level(Command):
815 """Raise domain and forest function levels."""
817 synopsis = "%prog (show|raise <options>) [options]"
819 takes_optiongroups = {
820 "sambaopts": options.SambaOptions,
821 "credopts": options.CredentialsOptions,
822 "versionopts": options.VersionOptions,
825 takes_options = [
826 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
827 metavar="URL", dest="H"),
828 Option("--quiet", help="Be quiet", action="store_true"),
829 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
830 help="The forest function level (2003 | 2008 | 2008_R2)"),
831 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
832 help="The domain function level (2003 | 2008 | 2008_R2)")
835 takes_args = ["subcommand"]
837 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
838 quiet=False, credopts=None, sambaopts=None, versionopts=None):
839 lp = sambaopts.get_loadparm()
840 creds = credopts.get_credentials(lp, fallback_machine=True)
842 samdb = SamDB(url=H, session_info=system_session(),
843 credentials=creds, lp=lp)
845 domain_dn = samdb.domain_dn()
847 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
848 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
849 assert len(res_forest) == 1
851 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
852 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
853 assert len(res_domain) == 1
855 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
856 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
857 attrs=["msDS-Behavior-Version"])
858 assert len(res_dc_s) >= 1
860 try:
861 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
862 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
863 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
865 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
866 for msg in res_dc_s:
867 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
868 min_level_dc = int(msg["msDS-Behavior-Version"][0])
870 if level_forest < 0 or level_domain < 0:
871 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
872 if min_level_dc < 0:
873 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
874 if level_forest > level_domain:
875 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
876 if level_domain > min_level_dc:
877 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
879 except KeyError:
880 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
882 if subcommand == "show":
883 self.message("Domain and forest function level for domain '%s'" % domain_dn)
884 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
885 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
886 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
887 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
888 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
889 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)!")
891 self.message("")
893 if level_forest == DS_DOMAIN_FUNCTION_2000:
894 outstr = "2000"
895 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
896 outstr = "2003 with mixed domains/interim (NT4 DC support)"
897 elif level_forest == DS_DOMAIN_FUNCTION_2003:
898 outstr = "2003"
899 elif level_forest == DS_DOMAIN_FUNCTION_2008:
900 outstr = "2008"
901 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
902 outstr = "2008 R2"
903 else:
904 outstr = "higher than 2008 R2"
905 self.message("Forest function level: (Windows) " + outstr)
907 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
908 outstr = "2000 mixed (NT4 DC support)"
909 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
910 outstr = "2000"
911 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
912 outstr = "2003 with mixed domains/interim (NT4 DC support)"
913 elif level_domain == DS_DOMAIN_FUNCTION_2003:
914 outstr = "2003"
915 elif level_domain == DS_DOMAIN_FUNCTION_2008:
916 outstr = "2008"
917 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
918 outstr = "2008 R2"
919 else:
920 outstr = "higher than 2008 R2"
921 self.message("Domain function level: (Windows) " + outstr)
923 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
924 outstr = "2000"
925 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
926 outstr = "2003"
927 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
928 outstr = "2008"
929 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
930 outstr = "2008 R2"
931 else:
932 outstr = "higher than 2008 R2"
933 self.message("Lowest function level of a DC: (Windows) " + outstr)
935 elif subcommand == "raise":
936 msgs = []
938 if domain_level is not None:
939 if domain_level == "2003":
940 new_level_domain = DS_DOMAIN_FUNCTION_2003
941 elif domain_level == "2008":
942 new_level_domain = DS_DOMAIN_FUNCTION_2008
943 elif domain_level == "2008_R2":
944 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
946 if new_level_domain <= level_domain and level_domain_mixed == 0:
947 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
949 if new_level_domain > min_level_dc:
950 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
952 # Deactivate mixed/interim domain support
953 if level_domain_mixed != 0:
954 # Directly on the base DN
955 m = ldb.Message()
956 m.dn = ldb.Dn(samdb, domain_dn)
957 m["nTMixedDomain"] = ldb.MessageElement("0",
958 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
959 samdb.modify(m)
960 # Under partitions
961 m = ldb.Message()
962 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
963 m["nTMixedDomain"] = ldb.MessageElement("0",
964 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
965 try:
966 samdb.modify(m)
967 except ldb.LdbError, (enum, emsg):
968 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
969 raise
971 # Directly on the base DN
972 m = ldb.Message()
973 m.dn = ldb.Dn(samdb, domain_dn)
974 m["msDS-Behavior-Version"]= ldb.MessageElement(
975 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
976 "msDS-Behavior-Version")
977 samdb.modify(m)
978 # Under partitions
979 m = ldb.Message()
980 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
981 + ",CN=Partitions,%s" % samdb.get_config_basedn())
982 m["msDS-Behavior-Version"]= ldb.MessageElement(
983 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
984 "msDS-Behavior-Version")
985 try:
986 samdb.modify(m)
987 except ldb.LdbError, (enum, emsg):
988 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
989 raise
991 level_domain = new_level_domain
992 msgs.append("Domain function level changed!")
994 if forest_level is not None:
995 if forest_level == "2003":
996 new_level_forest = DS_DOMAIN_FUNCTION_2003
997 elif forest_level == "2008":
998 new_level_forest = DS_DOMAIN_FUNCTION_2008
999 elif forest_level == "2008_R2":
1000 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1001 if new_level_forest <= level_forest:
1002 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1003 if new_level_forest > level_domain:
1004 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1005 m = ldb.Message()
1006 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1007 m["msDS-Behavior-Version"]= ldb.MessageElement(
1008 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1009 "msDS-Behavior-Version")
1010 samdb.modify(m)
1011 msgs.append("Forest function level changed!")
1012 msgs.append("All changes applied successfully!")
1013 self.message("\n".join(msgs))
1014 else:
1015 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1018 class cmd_domain_passwordsettings(Command):
1019 """Set password settings.
1021 Password complexity, history length, minimum password length, the minimum
1022 and maximum password age) on a Samba4 server.
1025 synopsis = "%prog (show|set <options>) [options]"
1027 takes_optiongroups = {
1028 "sambaopts": options.SambaOptions,
1029 "versionopts": options.VersionOptions,
1030 "credopts": options.CredentialsOptions,
1033 takes_options = [
1034 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1035 metavar="URL", dest="H"),
1036 Option("--quiet", help="Be quiet", action="store_true"),
1037 Option("--complexity", type="choice", choices=["on","off","default"],
1038 help="The password complexity (on | off | default). Default is 'on'"),
1039 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1040 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1041 Option("--history-length",
1042 help="The password history length (<integer> | default). Default is 24.", type=str),
1043 Option("--min-pwd-length",
1044 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1045 Option("--min-pwd-age",
1046 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1047 Option("--max-pwd-age",
1048 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1051 takes_args = ["subcommand"]
1053 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1054 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1055 min_pwd_length=None, credopts=None, sambaopts=None,
1056 versionopts=None):
1057 lp = sambaopts.get_loadparm()
1058 creds = credopts.get_credentials(lp)
1060 samdb = SamDB(url=H, session_info=system_session(),
1061 credentials=creds, lp=lp)
1063 domain_dn = samdb.domain_dn()
1064 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1065 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1066 "minPwdAge", "maxPwdAge"])
1067 assert(len(res) == 1)
1068 try:
1069 pwd_props = int(res[0]["pwdProperties"][0])
1070 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1071 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1072 # ticks -> days
1073 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1074 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1075 cur_max_pwd_age = 0
1076 else:
1077 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1078 except Exception, e:
1079 raise CommandError("Could not retrieve password properties!", e)
1081 if subcommand == "show":
1082 self.message("Password informations for domain '%s'" % domain_dn)
1083 self.message("")
1084 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1085 self.message("Password complexity: on")
1086 else:
1087 self.message("Password complexity: off")
1088 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1089 self.message("Store plaintext passwords: on")
1090 else:
1091 self.message("Store plaintext passwords: off")
1092 self.message("Password history length: %d" % pwd_hist_len)
1093 self.message("Minimum password length: %d" % cur_min_pwd_len)
1094 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1095 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1096 elif subcommand == "set":
1097 msgs = []
1098 m = ldb.Message()
1099 m.dn = ldb.Dn(samdb, domain_dn)
1101 if complexity is not None:
1102 if complexity == "on" or complexity == "default":
1103 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1104 msgs.append("Password complexity activated!")
1105 elif complexity == "off":
1106 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1107 msgs.append("Password complexity deactivated!")
1109 if store_plaintext is not None:
1110 if store_plaintext == "on" or store_plaintext == "default":
1111 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1112 msgs.append("Plaintext password storage for changed passwords activated!")
1113 elif store_plaintext == "off":
1114 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1115 msgs.append("Plaintext password storage for changed passwords deactivated!")
1117 if complexity is not None or store_plaintext is not None:
1118 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1119 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1121 if history_length is not None:
1122 if history_length == "default":
1123 pwd_hist_len = 24
1124 else:
1125 pwd_hist_len = int(history_length)
1127 if pwd_hist_len < 0 or pwd_hist_len > 24:
1128 raise CommandError("Password history length must be in the range of 0 to 24!")
1130 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1131 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1132 msgs.append("Password history length changed!")
1134 if min_pwd_length is not None:
1135 if min_pwd_length == "default":
1136 min_pwd_len = 7
1137 else:
1138 min_pwd_len = int(min_pwd_length)
1140 if min_pwd_len < 0 or min_pwd_len > 14:
1141 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1143 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1144 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1145 msgs.append("Minimum password length changed!")
1147 if min_pwd_age is not None:
1148 if min_pwd_age == "default":
1149 min_pwd_age = 1
1150 else:
1151 min_pwd_age = int(min_pwd_age)
1153 if min_pwd_age < 0 or min_pwd_age > 998:
1154 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1156 # days -> ticks
1157 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1159 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1160 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1161 msgs.append("Minimum password age changed!")
1163 if max_pwd_age is not None:
1164 if max_pwd_age == "default":
1165 max_pwd_age = 43
1166 else:
1167 max_pwd_age = int(max_pwd_age)
1169 if max_pwd_age < 0 or max_pwd_age > 999:
1170 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1172 # days -> ticks
1173 if max_pwd_age == 0:
1174 max_pwd_age_ticks = -0x8000000000000000
1175 else:
1176 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1178 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1179 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1180 msgs.append("Maximum password age changed!")
1182 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1183 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1185 if len(m) == 0:
1186 raise CommandError("You must specify at least one option to set. Try --help")
1187 samdb.modify(m)
1188 msgs.append("All changes applied successfully!")
1189 self.message("\n".join(msgs))
1190 else:
1191 raise CommandError("Wrong argument '%s'!" % subcommand)
1194 class cmd_domain_classicupgrade(Command):
1195 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1197 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1198 the testparm utility from your classic installation (with --testparm).
1201 synopsis = "%prog [options] <classic_smb_conf>"
1203 takes_optiongroups = {
1204 "sambaopts": options.SambaOptions,
1205 "versionopts": options.VersionOptions
1208 takes_options = [
1209 Option("--dbdir", type="string", metavar="DIR",
1210 help="Path to samba classic DC database directory"),
1211 Option("--testparm", type="string", metavar="PATH",
1212 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1213 Option("--targetdir", type="string", metavar="DIR",
1214 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1215 Option("--quiet", help="Be quiet", action="store_true"),
1216 Option("--verbose", help="Be verbose", action="store_true"),
1217 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1218 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"),
1219 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1220 action="store_true"),
1221 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1222 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1223 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1224 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1225 "BIND9_DLZ uses samba4 AD to store zone information, "
1226 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1227 default="SAMBA_INTERNAL")
1230 takes_args = ["smbconf"]
1232 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1233 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1234 dns_backend=None, use_ntvfs=False):
1236 if not os.path.exists(smbconf):
1237 raise CommandError("File %s does not exist" % smbconf)
1239 if testparm and not os.path.exists(testparm):
1240 raise CommandError("Testparm utility %s does not exist" % testparm)
1242 if dbdir and not os.path.exists(dbdir):
1243 raise CommandError("Directory %s does not exist" % dbdir)
1245 if not dbdir and not testparm:
1246 raise CommandError("Please specify either dbdir or testparm")
1248 logger = self.get_logger()
1249 if verbose:
1250 logger.setLevel(logging.DEBUG)
1251 elif quiet:
1252 logger.setLevel(logging.WARNING)
1253 else:
1254 logger.setLevel(logging.INFO)
1256 if dbdir and testparm:
1257 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1258 dbdir = None
1260 lp = sambaopts.get_loadparm()
1262 s3conf = s3param.get_context()
1264 if sambaopts.realm:
1265 s3conf.set("realm", sambaopts.realm)
1267 if targetdir is not None:
1268 if not os.path.isdir(targetdir):
1269 os.mkdir(targetdir)
1271 eadb = True
1272 if use_xattrs == "yes":
1273 eadb = False
1274 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1275 if targetdir:
1276 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1277 else:
1278 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1279 try:
1280 try:
1281 samba.ntacls.setntacl(lp, tmpfile.name,
1282 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1283 eadb = False
1284 except Exception:
1285 # FIXME: Don't catch all exceptions here
1286 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1287 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1288 finally:
1289 tmpfile.close()
1291 # Set correct default values from dbdir or testparm
1292 paths = {}
1293 if dbdir:
1294 paths["state directory"] = dbdir
1295 paths["private dir"] = dbdir
1296 paths["lock directory"] = dbdir
1297 paths["smb passwd file"] = dbdir + "/smbpasswd"
1298 else:
1299 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1300 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1301 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1302 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1303 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1304 # "state directory", instead make use of "lock directory"
1305 if len(paths["state directory"]) == 0:
1306 paths["state directory"] = paths["lock directory"]
1308 for p in paths:
1309 s3conf.set(p, paths[p])
1311 # load smb.conf parameters
1312 logger.info("Reading smb.conf")
1313 s3conf.load(smbconf)
1314 samba3 = Samba3(smbconf, s3conf)
1316 logger.info("Provisioning")
1317 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1318 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1321 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1322 __doc__ = cmd_domain_classicupgrade.__doc__
1324 # This command is present for backwards compatibility only,
1325 # and should not be shown.
1327 hidden = True
1330 class cmd_domain(SuperCommand):
1331 """Domain management."""
1333 subcommands = {}
1334 subcommands["demote"] = cmd_domain_demote()
1335 if cmd_domain_export_keytab is not None:
1336 subcommands["exportkeytab"] = cmd_domain_export_keytab()
1337 subcommands["info"] = cmd_domain_info()
1338 subcommands["provision"] = cmd_domain_provision()
1339 subcommands["join"] = cmd_domain_join()
1340 subcommands["dcpromo"] = cmd_domain_dcpromo()
1341 subcommands["level"] = cmd_domain_level()
1342 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
1343 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
1344 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()