samba-tool: Some more unifications...
[Samba/gbeck.git] / source4 / scripting / python / samba / netcmd / domain.py
blob6e3f35a0afccc862d761c1ef202f3cff9c8f58c8
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("--wheel", type="string", metavar="GROUPNAME",
190 help="choose 'wheel' privileged group"),
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)"),
219 takes_args = []
221 def run(self, sambaopts=None, credopts=None, versionopts=None,
222 interactive=None,
223 domain=None,
224 domain_guid=None,
225 domain_sid=None,
226 ntds_guid=None,
227 invocationid=None,
228 host_name=None,
229 host_ip=None,
230 host_ip6=None,
231 adminpass=None,
232 krbtgtpass=None,
233 machinepass=None,
234 dns_backend=None,
235 dns_forwarder=None,
236 dnspass=None,
237 ldapadminpass=None,
238 root=None,
239 nobody=None,
240 wheel=None,
241 users=None,
242 quiet=None,
243 blank=None,
244 ldap_backend_type=None,
245 server_role=None,
246 function_level=None,
247 next_rid=None,
248 partitions_only=None,
249 targetdir=None,
250 ol_mmr_urls=None,
251 use_xattrs=None,
252 use_ntvfs=None,
253 use_rfc2307=None):
255 self.logger = self.get_logger("provision")
256 if quiet:
257 self.logger.setLevel(logging.WARNING)
258 else:
259 self.logger.setLevel(logging.INFO)
261 lp = sambaopts.get_loadparm()
262 smbconf = lp.configfile
264 creds = credopts.get_credentials(lp)
266 creds.set_kerberos_state(DONT_USE_KERBEROS)
268 if dns_forwarder is not None:
269 suggested_forwarder = dns_forwarder
270 else:
271 suggested_forwarder = self._get_nameserver_ip()
272 if suggested_forwarder is None:
273 suggested_forwarder = "none"
275 if len(self.raw_argv) == 1:
276 interactive = True
278 if interactive:
279 from getpass import getpass
280 import socket
282 def ask(prompt, default=None):
283 if default is not None:
284 print "%s [%s]: " % (prompt, default),
285 else:
286 print "%s: " % (prompt,),
287 return sys.stdin.readline().rstrip("\n") or default
289 try:
290 default = socket.getfqdn().split(".", 1)[1].upper()
291 except IndexError:
292 default = None
293 realm = ask("Realm", default)
294 if realm in (None, ""):
295 raise CommandError("No realm set!")
297 try:
298 default = realm.split(".")[0]
299 except IndexError:
300 default = None
301 domain = ask("Domain", default)
302 if domain is None:
303 raise CommandError("No domain set!")
305 server_role = ask("Server Role (dc, member, standalone)", "dc")
307 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
308 if dns_backend in (None, ''):
309 raise CommandError("No DNS backend set!")
311 if dns_backend == "SAMBA_INTERNAL":
312 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
313 if dns_forwarder.lower() in (None, 'none'):
314 suggested_forwarder = None
315 dns_forwarder = None
317 while True:
318 adminpassplain = getpass("Administrator password: ")
319 if not adminpassplain:
320 self.errf.write("Invalid administrator password.\n")
321 else:
322 adminpassverify = getpass("Retype password: ")
323 if not adminpassplain == adminpassverify:
324 self.errf.write("Sorry, passwords do not match.\n")
325 else:
326 adminpass = adminpassplain
327 break
329 else:
330 realm = sambaopts._lp.get('realm')
331 if realm is None:
332 raise CommandError("No realm set!")
333 if domain is None:
334 raise CommandError("No domain set!")
336 if not adminpass:
337 self.logger.info("Administrator password will be set randomly!")
339 if function_level == "2000":
340 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
341 elif function_level == "2003":
342 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
343 elif function_level == "2008":
344 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
345 elif function_level == "2008_R2":
346 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
348 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
349 dns_forwarder = suggested_forwarder
351 samdb_fill = FILL_FULL
352 if blank:
353 samdb_fill = FILL_NT4SYNC
354 elif partitions_only:
355 samdb_fill = FILL_DRS
357 if targetdir is not None:
358 if not os.path.isdir(targetdir):
359 os.mkdir(targetdir)
361 eadb = True
363 if use_xattrs == "yes":
364 eadb = False
365 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
366 if targetdir:
367 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
368 else:
369 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
370 try:
371 try:
372 samba.ntacls.setntacl(lp, file.name,
373 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
374 eadb = False
375 except Exception:
376 self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
377 finally:
378 file.close()
380 if eadb:
381 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.")
383 session = system_session()
384 try:
385 result = provision(self.logger,
386 session, creds, smbconf=smbconf, targetdir=targetdir,
387 samdb_fill=samdb_fill, realm=realm, domain=domain,
388 domainguid=domain_guid, domainsid=domain_sid,
389 hostname=host_name,
390 hostip=host_ip, hostip6=host_ip6,
391 ntdsguid=ntds_guid,
392 invocationid=invocationid, adminpass=adminpass,
393 krbtgtpass=krbtgtpass, machinepass=machinepass,
394 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
395 dnspass=dnspass, root=root, nobody=nobody,
396 wheel=wheel, users=users,
397 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
398 backend_type=ldap_backend_type,
399 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls,
400 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
401 use_rfc2307=use_rfc2307, skip_sysvolacl=False)
402 except ProvisioningError, e:
403 raise CommandError("Provision failed", e)
405 result.report_logger(self.logger)
407 def _get_nameserver_ip(self):
408 """Grab the nameserver IP address from /etc/resolv.conf."""
409 from os import path
410 RESOLV_CONF="/etc/resolv.conf"
412 if not path.isfile(RESOLV_CONF):
413 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
414 return None
416 handle = None
417 try:
418 handle = open(RESOLV_CONF, 'r')
419 for line in handle:
420 if not line.startswith('nameserver'):
421 continue
422 # we want the last non-space continuous string of the line
423 return line.strip().split()[-1]
424 finally:
425 if handle is not None:
426 handle.close()
428 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
431 class cmd_domain_dcpromo(Command):
432 """Promote an existing domain member or NT4 PDC to an AD DC."""
434 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
436 takes_optiongroups = {
437 "sambaopts": options.SambaOptions,
438 "versionopts": options.VersionOptions,
439 "credopts": options.CredentialsOptions,
442 takes_options = [
443 Option("--server", help="DC to join", type=str),
444 Option("--site", help="site to join", type=str),
445 Option("--targetdir", help="where to store provision", type=str),
446 Option("--domain-critical-only",
447 help="only replicate critical domain objects",
448 action="store_true"),
449 Option("--machinepass", type=str, metavar="PASSWORD",
450 help="choose machine password (otherwise random)"),
451 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
452 action="store_true"),
453 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
454 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
455 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
456 "BIND9_DLZ uses samba4 AD to store zone information, "
457 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
458 default="SAMBA_INTERNAL")
461 takes_args = ["domain", "role?"]
463 def run(self, domain, role=None, sambaopts=None, credopts=None,
464 versionopts=None, server=None, site=None, targetdir=None,
465 domain_critical_only=False, parent_domain=None, machinepass=None,
466 use_ntvfs=False, dns_backend=None):
467 lp = sambaopts.get_loadparm()
468 creds = credopts.get_credentials(lp)
469 net = Net(creds, lp, server=credopts.ipaddress)
471 if site is None:
472 site = "Default-First-Site-Name"
474 netbios_name = lp.get("netbios name")
476 if not role is None:
477 role = role.upper()
479 if role == "DC":
480 join_DC(server=server, creds=creds, lp=lp, domain=domain,
481 site=site, netbios_name=netbios_name, targetdir=targetdir,
482 domain_critical_only=domain_critical_only,
483 machinepass=machinepass, use_ntvfs=use_ntvfs,
484 dns_backend=dns_backend,
485 promote_existing=True)
486 elif role == "RODC":
487 join_RODC(server=server, creds=creds, lp=lp, domain=domain,
488 site=site, netbios_name=netbios_name, targetdir=targetdir,
489 domain_critical_only=domain_critical_only,
490 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
491 promote_existing=True)
492 else:
493 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
496 class cmd_domain_join(Command):
497 """Join domain as either member or backup domain controller."""
499 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
501 takes_optiongroups = {
502 "sambaopts": options.SambaOptions,
503 "versionopts": options.VersionOptions,
504 "credopts": options.CredentialsOptions,
507 takes_options = [
508 Option("--server", help="DC to join", type=str),
509 Option("--site", help="site to join", type=str),
510 Option("--targetdir", help="where to store provision", type=str),
511 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
512 Option("--domain-critical-only",
513 help="only replicate critical domain objects",
514 action="store_true"),
515 Option("--machinepass", type=str, metavar="PASSWORD",
516 help="choose machine password (otherwise random)"),
517 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
518 action="store_true"),
519 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
520 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
521 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
522 "BIND9_DLZ uses samba4 AD to store zone information, "
523 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
524 default="SAMBA_INTERNAL")
527 takes_args = ["domain", "role?"]
529 def run(self, domain, role=None, sambaopts=None, credopts=None,
530 versionopts=None, server=None, site=None, targetdir=None,
531 domain_critical_only=False, parent_domain=None, machinepass=None,
532 use_ntvfs=False, dns_backend=None):
533 lp = sambaopts.get_loadparm()
534 creds = credopts.get_credentials(lp)
535 net = Net(creds, lp, server=credopts.ipaddress)
537 if site is None:
538 site = "Default-First-Site-Name"
540 netbios_name = lp.get("netbios name")
542 if not role is None:
543 role = role.upper()
545 if role is None or role == "MEMBER":
546 (join_password, sid, domain_name) = net.join_member(
547 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
548 machinepass=machinepass)
550 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
551 elif role == "DC":
552 join_DC(server=server, creds=creds, lp=lp, domain=domain,
553 site=site, netbios_name=netbios_name, targetdir=targetdir,
554 domain_critical_only=domain_critical_only,
555 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
556 elif role == "RODC":
557 join_RODC(server=server, creds=creds, lp=lp, domain=domain,
558 site=site, netbios_name=netbios_name, targetdir=targetdir,
559 domain_critical_only=domain_critical_only,
560 machinepass=machinepass, use_ntvfs=use_ntvfs,
561 dns_backend=dns_backend)
562 elif role == "SUBDOMAIN":
563 netbios_domain = lp.get("workgroup")
564 if parent_domain is None:
565 parent_domain = ".".join(domain.split(".")[1:])
566 join_subdomain(server=server, creds=creds, lp=lp, dnsdomain=domain,
567 parent_domain=parent_domain, site=site,
568 netbios_name=netbios_name, netbios_domain=netbios_domain,
569 targetdir=targetdir, machinepass=machinepass,
570 use_ntvfs=use_ntvfs, dns_backend=dns_backend)
571 else:
572 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
575 class cmd_domain_demote(Command):
576 """Demote ourselves from the role of Domain Controller."""
578 synopsis = "%prog [options]"
580 takes_options = [
581 Option("--server", help="DC to force replication before demote", type=str),
582 Option("--targetdir", help="where provision is stored", type=str),
585 takes_optiongroups = {
586 "sambaopts": options.SambaOptions,
587 "credopts": options.CredentialsOptions,
588 "versionopts": options.VersionOptions,
591 def run(self, sambaopts=None, credopts=None,
592 versionopts=None, server=None, targetdir=None):
593 lp = sambaopts.get_loadparm()
594 creds = credopts.get_credentials(lp)
595 net = Net(creds, lp, server=credopts.ipaddress)
597 netbios_name = lp.get("netbios name")
598 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
599 if not server:
600 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
601 if (len(res) == 0):
602 raise CommandError("Unable to search for servers")
604 if (len(res) == 1):
605 raise CommandError("You are the latest server in the domain")
607 server = None
608 for e in res:
609 if str(e["name"]).lower() != netbios_name.lower():
610 server = e["dnsHostName"]
611 break
613 ntds_guid = samdb.get_ntds_GUID()
614 msg = samdb.search(base=str(samdb.get_config_basedn()),
615 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
616 attrs=['options'])
617 if len(msg) == 0 or "options" not in msg[0]:
618 raise CommandError("Failed to find options on %s" % ntds_guid)
620 ntds_dn = msg[0].dn
621 dsa_options = int(str(msg[0]['options']))
623 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
624 controls=["search_options:1:2"])
626 if len(res) != 0:
627 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
629 self.errf.write("Using %s as partner server for the demotion\n" %
630 server)
631 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
633 self.errf.write("Desactivating inbound replication\n")
635 nmsg = ldb.Message()
636 nmsg.dn = msg[0].dn
638 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
639 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
640 samdb.modify(nmsg)
642 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
644 self.errf.write("Asking partner server %s to synchronize from us\n"
645 % server)
646 for part in (samdb.get_schema_basedn(),
647 samdb.get_config_basedn(),
648 samdb.get_root_basedn()):
649 try:
650 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
651 except drsException, e:
652 self.errf.write(
653 "Error while demoting, "
654 "re-enabling inbound replication\n")
655 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
656 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
657 samdb.modify(nmsg)
658 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
659 try:
660 remote_samdb = SamDB(url="ldap://%s" % server,
661 session_info=system_session(),
662 credentials=creds, lp=lp)
664 self.errf.write("Changing userControl and container\n")
665 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
666 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
667 netbios_name.upper(),
668 attrs=["userAccountControl"])
669 dc_dn = res[0].dn
670 uac = int(str(res[0]["userAccountControl"]))
672 except Exception, e:
673 self.errf.write(
674 "Error while demoting, re-enabling inbound replication\n")
675 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
676 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
677 samdb.modify(nmsg)
678 raise CommandError("Error while changing account control", e)
680 if (len(res) != 1):
681 self.errf.write(
682 "Error while demoting, re-enabling inbound replication")
683 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
684 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
685 samdb.modify(nmsg)
686 raise CommandError("Unable to find object with samaccountName = %s$"
687 " in the remote dc" % netbios_name.upper())
689 olduac = uac
691 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
692 uac |= UF_WORKSTATION_TRUST_ACCOUNT
694 msg = ldb.Message()
695 msg.dn = dc_dn
697 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
698 ldb.FLAG_MOD_REPLACE,
699 "userAccountControl")
700 try:
701 remote_samdb.modify(msg)
702 except Exception, e:
703 self.errf.write(
704 "Error while demoting, re-enabling inbound replication")
705 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
706 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
707 samdb.modify(nmsg)
709 raise CommandError("Error while changing account control", e)
711 parent = msg.dn.parent()
712 rdn = str(res[0].dn)
713 rdn = string.replace(rdn, ",%s" % str(parent), "")
714 # Let's move to the Computer container
715 i = 0
716 newrdn = rdn
718 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
719 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
721 if (len(res) != 0):
722 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
723 scope=ldb.SCOPE_ONELEVEL)
724 while(len(res) != 0 and i < 100):
725 i = i + 1
726 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
727 scope=ldb.SCOPE_ONELEVEL)
729 if i == 100:
730 self.errf.write(
731 "Error while demoting, re-enabling inbound replication\n")
732 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
733 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
734 samdb.modify(nmsg)
736 msg = ldb.Message()
737 msg.dn = dc_dn
739 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
740 ldb.FLAG_MOD_REPLACE,
741 "userAccountControl")
743 remote_samdb.modify(msg)
745 raise CommandError("Unable to find a slot for renaming %s,"
746 " all names from %s-1 to %s-%d seemed used" %
747 (str(dc_dn), rdn, rdn, i - 9))
749 newrdn = "%s-%d" % (rdn, i)
751 try:
752 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
753 remote_samdb.rename(dc_dn, newdn)
754 except Exception, e:
755 self.errf.write(
756 "Error while demoting, re-enabling inbound replication\n")
757 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
758 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
759 samdb.modify(nmsg)
761 msg = ldb.Message()
762 msg.dn = dc_dn
764 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
765 ldb.FLAG_MOD_REPLACE,
766 "userAccountControl")
768 remote_samdb.modify(msg)
769 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
772 server_dsa_dn = samdb.get_serverName()
773 domain = remote_samdb.get_root_basedn()
775 try:
776 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
777 except drsException, e:
778 self.errf.write(
779 "Error while demoting, re-enabling inbound replication\n")
780 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
781 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
782 samdb.modify(nmsg)
784 msg = ldb.Message()
785 msg.dn = newdn
787 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
788 ldb.FLAG_MOD_REPLACE,
789 "userAccountControl")
790 print str(dc_dn)
791 remote_samdb.modify(msg)
792 remote_samdb.rename(newdn, dc_dn)
793 raise CommandError("Error while sending a removeDsServer", e)
795 for s in ("CN=Entreprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
796 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
797 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
798 try:
799 remote_samdb.delete(ldb.Dn(remote_samdb,
800 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
801 except ldb.LdbError, l:
802 pass
804 for s in ("CN=Entreprise,CN=NTFRS Subscriptions",
805 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
806 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
807 "CN=NTFRS Subscriptions"):
808 try:
809 remote_samdb.delete(ldb.Dn(remote_samdb,
810 "%s,%s" % (s, str(newdn))))
811 except ldb.LdbError, l:
812 pass
814 self.errf.write("Demote successfull\n")
817 class cmd_domain_level(Command):
818 """Raise domain and forest function levels."""
820 synopsis = "%prog (show|raise <options>) [options]"
822 takes_optiongroups = {
823 "sambaopts": options.SambaOptions,
824 "credopts": options.CredentialsOptions,
825 "versionopts": options.VersionOptions,
828 takes_options = [
829 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
830 metavar="URL", dest="H"),
831 Option("--quiet", help="Be quiet", action="store_true"),
832 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
833 help="The forest function level (2003 | 2008 | 2008_R2)"),
834 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
835 help="The domain function level (2003 | 2008 | 2008_R2)")
838 takes_args = ["subcommand"]
840 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
841 quiet=False, credopts=None, sambaopts=None, versionopts=None):
842 lp = sambaopts.get_loadparm()
843 creds = credopts.get_credentials(lp, fallback_machine=True)
845 samdb = SamDB(url=H, session_info=system_session(),
846 credentials=creds, lp=lp)
848 domain_dn = samdb.domain_dn()
850 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
851 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
852 assert len(res_forest) == 1
854 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
855 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
856 assert len(res_domain) == 1
858 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
859 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
860 attrs=["msDS-Behavior-Version"])
861 assert len(res_dc_s) >= 1
863 try:
864 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
865 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
866 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
868 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
869 for msg in res_dc_s:
870 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
871 min_level_dc = int(msg["msDS-Behavior-Version"][0])
873 if level_forest < 0 or level_domain < 0:
874 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
875 if min_level_dc < 0:
876 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
877 if level_forest > level_domain:
878 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
879 if level_domain > min_level_dc:
880 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
882 except KeyError:
883 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
885 if subcommand == "show":
886 self.message("Domain and forest function level for domain '%s'" % domain_dn)
887 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
888 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
889 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
890 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
891 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
892 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)!")
894 self.message("")
896 if level_forest == DS_DOMAIN_FUNCTION_2000:
897 outstr = "2000"
898 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
899 outstr = "2003 with mixed domains/interim (NT4 DC support)"
900 elif level_forest == DS_DOMAIN_FUNCTION_2003:
901 outstr = "2003"
902 elif level_forest == DS_DOMAIN_FUNCTION_2008:
903 outstr = "2008"
904 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
905 outstr = "2008 R2"
906 else:
907 outstr = "higher than 2008 R2"
908 self.message("Forest function level: (Windows) " + outstr)
910 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
911 outstr = "2000 mixed (NT4 DC support)"
912 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
913 outstr = "2000"
914 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
915 outstr = "2003 with mixed domains/interim (NT4 DC support)"
916 elif level_domain == DS_DOMAIN_FUNCTION_2003:
917 outstr = "2003"
918 elif level_domain == DS_DOMAIN_FUNCTION_2008:
919 outstr = "2008"
920 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
921 outstr = "2008 R2"
922 else:
923 outstr = "higher than 2008 R2"
924 self.message("Domain function level: (Windows) " + outstr)
926 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
927 outstr = "2000"
928 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
929 outstr = "2003"
930 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
931 outstr = "2008"
932 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
933 outstr = "2008 R2"
934 else:
935 outstr = "higher than 2008 R2"
936 self.message("Lowest function level of a DC: (Windows) " + outstr)
938 elif subcommand == "raise":
939 msgs = []
941 if domain_level is not None:
942 if domain_level == "2003":
943 new_level_domain = DS_DOMAIN_FUNCTION_2003
944 elif domain_level == "2008":
945 new_level_domain = DS_DOMAIN_FUNCTION_2008
946 elif domain_level == "2008_R2":
947 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
949 if new_level_domain <= level_domain and level_domain_mixed == 0:
950 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
952 if new_level_domain > min_level_dc:
953 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
955 # Deactivate mixed/interim domain support
956 if level_domain_mixed != 0:
957 # Directly on the base DN
958 m = ldb.Message()
959 m.dn = ldb.Dn(samdb, domain_dn)
960 m["nTMixedDomain"] = ldb.MessageElement("0",
961 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
962 samdb.modify(m)
963 # Under partitions
964 m = ldb.Message()
965 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
966 m["nTMixedDomain"] = ldb.MessageElement("0",
967 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
968 try:
969 samdb.modify(m)
970 except ldb.LdbError, (enum, emsg):
971 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
972 raise
974 # Directly on the base DN
975 m = ldb.Message()
976 m.dn = ldb.Dn(samdb, domain_dn)
977 m["msDS-Behavior-Version"]= ldb.MessageElement(
978 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
979 "msDS-Behavior-Version")
980 samdb.modify(m)
981 # Under partitions
982 m = ldb.Message()
983 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
984 + ",CN=Partitions,%s" % samdb.get_config_basedn())
985 m["msDS-Behavior-Version"]= ldb.MessageElement(
986 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
987 "msDS-Behavior-Version")
988 try:
989 samdb.modify(m)
990 except ldb.LdbError, (enum, emsg):
991 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
992 raise
994 level_domain = new_level_domain
995 msgs.append("Domain function level changed!")
997 if forest_level is not None:
998 if forest_level == "2003":
999 new_level_forest = DS_DOMAIN_FUNCTION_2003
1000 elif forest_level == "2008":
1001 new_level_forest = DS_DOMAIN_FUNCTION_2008
1002 elif forest_level == "2008_R2":
1003 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1004 if new_level_forest <= level_forest:
1005 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1006 if new_level_forest > level_domain:
1007 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1008 m = ldb.Message()
1009 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1010 m["msDS-Behavior-Version"]= ldb.MessageElement(
1011 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1012 "msDS-Behavior-Version")
1013 samdb.modify(m)
1014 msgs.append("Forest function level changed!")
1015 msgs.append("All changes applied successfully!")
1016 self.message("\n".join(msgs))
1017 else:
1018 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1021 class cmd_domain_passwordsettings(Command):
1022 """Set password settings.
1024 Password complexity, history length, minimum password length, the minimum
1025 and maximum password age) on a Samba4 server.
1028 synopsis = "%prog (show|set <options>) [options]"
1030 takes_optiongroups = {
1031 "sambaopts": options.SambaOptions,
1032 "versionopts": options.VersionOptions,
1033 "credopts": options.CredentialsOptions,
1036 takes_options = [
1037 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1038 metavar="URL", dest="H"),
1039 Option("--quiet", help="Be quiet", action="store_true"),
1040 Option("--complexity", type="choice", choices=["on","off","default"],
1041 help="The password complexity (on | off | default). Default is 'on'"),
1042 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1043 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1044 Option("--history-length",
1045 help="The password history length (<integer> | default). Default is 24.", type=str),
1046 Option("--min-pwd-length",
1047 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1048 Option("--min-pwd-age",
1049 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1050 Option("--max-pwd-age",
1051 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1054 takes_args = ["subcommand"]
1056 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1057 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1058 min_pwd_length=None, credopts=None, sambaopts=None,
1059 versionopts=None):
1060 lp = sambaopts.get_loadparm()
1061 creds = credopts.get_credentials(lp)
1063 samdb = SamDB(url=H, session_info=system_session(),
1064 credentials=creds, lp=lp)
1066 domain_dn = samdb.domain_dn()
1067 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1068 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1069 "minPwdAge", "maxPwdAge"])
1070 assert(len(res) == 1)
1071 try:
1072 pwd_props = int(res[0]["pwdProperties"][0])
1073 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1074 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1075 # ticks -> days
1076 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1077 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1078 cur_max_pwd_age = 0
1079 else:
1080 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1081 except Exception, e:
1082 raise CommandError("Could not retrieve password properties!", e)
1084 if subcommand == "show":
1085 self.message("Password informations for domain '%s'" % domain_dn)
1086 self.message("")
1087 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1088 self.message("Password complexity: on")
1089 else:
1090 self.message("Password complexity: off")
1091 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1092 self.message("Store plaintext passwords: on")
1093 else:
1094 self.message("Store plaintext passwords: off")
1095 self.message("Password history length: %d" % pwd_hist_len)
1096 self.message("Minimum password length: %d" % cur_min_pwd_len)
1097 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1098 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1099 elif subcommand == "set":
1100 msgs = []
1101 m = ldb.Message()
1102 m.dn = ldb.Dn(samdb, domain_dn)
1104 if complexity is not None:
1105 if complexity == "on" or complexity == "default":
1106 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1107 msgs.append("Password complexity activated!")
1108 elif complexity == "off":
1109 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1110 msgs.append("Password complexity deactivated!")
1112 if store_plaintext is not None:
1113 if store_plaintext == "on" or store_plaintext == "default":
1114 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1115 msgs.append("Plaintext password storage for changed passwords activated!")
1116 elif store_plaintext == "off":
1117 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1118 msgs.append("Plaintext password storage for changed passwords deactivated!")
1120 if complexity is not None or store_plaintext is not None:
1121 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1122 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1124 if history_length is not None:
1125 if history_length == "default":
1126 pwd_hist_len = 24
1127 else:
1128 pwd_hist_len = int(history_length)
1130 if pwd_hist_len < 0 or pwd_hist_len > 24:
1131 raise CommandError("Password history length must be in the range of 0 to 24!")
1133 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1134 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1135 msgs.append("Password history length changed!")
1137 if min_pwd_length is not None:
1138 if min_pwd_length == "default":
1139 min_pwd_len = 7
1140 else:
1141 min_pwd_len = int(min_pwd_length)
1143 if min_pwd_len < 0 or min_pwd_len > 14:
1144 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1146 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1147 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1148 msgs.append("Minimum password length changed!")
1150 if min_pwd_age is not None:
1151 if min_pwd_age == "default":
1152 min_pwd_age = 1
1153 else:
1154 min_pwd_age = int(min_pwd_age)
1156 if min_pwd_age < 0 or min_pwd_age > 998:
1157 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1159 # days -> ticks
1160 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1162 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1163 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1164 msgs.append("Minimum password age changed!")
1166 if max_pwd_age is not None:
1167 if max_pwd_age == "default":
1168 max_pwd_age = 43
1169 else:
1170 max_pwd_age = int(max_pwd_age)
1172 if max_pwd_age < 0 or max_pwd_age > 999:
1173 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1175 # days -> ticks
1176 if max_pwd_age == 0:
1177 max_pwd_age_ticks = -0x8000000000000000
1178 else:
1179 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1181 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1182 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1183 msgs.append("Maximum password age changed!")
1185 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1186 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1188 if len(m) == 0:
1189 raise CommandError("You must specify at least one option to set. Try --help")
1190 samdb.modify(m)
1191 msgs.append("All changes applied successfully!")
1192 self.message("\n".join(msgs))
1193 else:
1194 raise CommandError("Wrong argument '%s'!" % subcommand)
1197 class cmd_domain_classicupgrade(Command):
1198 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1200 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1201 the testparm utility from your classic installation (with --testparm).
1204 synopsis = "%prog [options] <classic_smb_conf>"
1206 takes_optiongroups = {
1207 "sambaopts": options.SambaOptions,
1208 "versionopts": options.VersionOptions
1211 takes_options = [
1212 Option("--dbdir", type="string", metavar="DIR",
1213 help="Path to samba classic DC database directory"),
1214 Option("--testparm", type="string", metavar="PATH",
1215 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1216 Option("--targetdir", type="string", metavar="DIR",
1217 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1218 Option("--quiet", help="Be quiet", action="store_true"),
1219 Option("--verbose", help="Be verbose", action="store_true"),
1220 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1221 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"),
1222 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1223 action="store_true"),
1224 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1225 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1226 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1227 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1228 "BIND9_DLZ uses samba4 AD to store zone information, "
1229 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1230 default="SAMBA_INTERNAL")
1233 takes_args = ["smbconf"]
1235 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1236 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1237 dns_backend=None, use_ntvfs=False):
1239 if not os.path.exists(smbconf):
1240 raise CommandError("File %s does not exist" % smbconf)
1242 if testparm and not os.path.exists(testparm):
1243 raise CommandError("Testparm utility %s does not exist" % testparm)
1245 if dbdir and not os.path.exists(dbdir):
1246 raise CommandError("Directory %s does not exist" % dbdir)
1248 if not dbdir and not testparm:
1249 raise CommandError("Please specify either dbdir or testparm")
1251 logger = self.get_logger()
1252 if verbose:
1253 logger.setLevel(logging.DEBUG)
1254 elif quiet:
1255 logger.setLevel(logging.WARNING)
1256 else:
1257 logger.setLevel(logging.INFO)
1259 if dbdir and testparm:
1260 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1261 dbdir = None
1263 lp = sambaopts.get_loadparm()
1265 s3conf = s3param.get_context()
1267 if sambaopts.realm:
1268 s3conf.set("realm", sambaopts.realm)
1270 if targetdir is not None:
1271 if not os.path.isdir(targetdir):
1272 os.mkdir(targetdir)
1274 eadb = True
1275 if use_xattrs == "yes":
1276 eadb = False
1277 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1278 if targetdir:
1279 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1280 else:
1281 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1282 try:
1283 try:
1284 samba.ntacls.setntacl(lp, tmpfile.name,
1285 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1286 eadb = False
1287 except Exception:
1288 # FIXME: Don't catch all exceptions here
1289 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1290 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1291 finally:
1292 tmpfile.close()
1294 # Set correct default values from dbdir or testparm
1295 paths = {}
1296 if dbdir:
1297 paths["state directory"] = dbdir
1298 paths["private dir"] = dbdir
1299 paths["lock directory"] = dbdir
1300 paths["smb passwd file"] = dbdir + "/smbpasswd"
1301 else:
1302 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1303 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1304 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1305 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1306 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1307 # "state directory", instead make use of "lock directory"
1308 if len(paths["state directory"]) == 0:
1309 paths["state directory"] = paths["lock directory"]
1311 for p in paths:
1312 s3conf.set(p, paths[p])
1314 # load smb.conf parameters
1315 logger.info("Reading smb.conf")
1316 s3conf.load(smbconf)
1317 samba3 = Samba3(smbconf, s3conf)
1319 logger.info("Provisioning")
1320 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1321 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1324 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1325 __doc__ = cmd_domain_classicupgrade.__doc__
1327 # This command is present for backwards compatibility only,
1328 # and should not be shown.
1330 hidden = True
1333 class cmd_domain(SuperCommand):
1334 """Domain management."""
1336 subcommands = {}
1337 subcommands["demote"] = cmd_domain_demote()
1338 if cmd_domain_export_keytab is not None:
1339 subcommands["exportkeytab"] = cmd_domain_export_keytab()
1340 subcommands["info"] = cmd_domain_info()
1341 subcommands["provision"] = cmd_domain_provision()
1342 subcommands["join"] = cmd_domain_join()
1343 subcommands["dcpromo"] = cmd_domain_dcpromo()
1344 subcommands["level"] = cmd_domain_level()
1345 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
1346 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
1347 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()