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
32 from samba
.net
import Net
, LIBNET_JOIN_AUTOMATIC
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 (
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
,
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 (
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()
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
,
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()
103 net
.export_keytab(keytab
=keytab
, principal
=principal
)
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]"
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()
127 res
= netcmd_get_domain_infos_via_cldap(lp
, None, address
)
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
,
151 Option("--interactive", help="Ask for names", action
="store_true"),
152 Option("--domain", type="string", metavar
="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.",
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)"),
219 def run(self
, sambaopts
=None, credopts
=None, versionopts
=None,
241 ldap_backend_type
=None,
245 partitions_only
=None,
252 self
.logger
= self
.get_logger("provision")
254 self
.logger
.setLevel(logging
.WARNING
)
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
268 suggested_forwarder
= self
._get
_nameserver
_ip
()
269 if suggested_forwarder
is None:
270 suggested_forwarder
= "none"
272 if len(self
.raw_argv
) == 1:
276 from getpass
import getpass
279 def ask(prompt
, default
=None):
280 if default
is not None:
281 print "%s [%s]: " % (prompt
, default
),
283 print "%s: " % (prompt
,),
284 return sys
.stdin
.readline().rstrip("\n") or default
287 default
= socket
.getfqdn().split(".", 1)[1].upper()
290 realm
= ask("Realm", default
)
291 if realm
in (None, ""):
292 raise CommandError("No realm set!")
295 default
= realm
.split(".")[0]
298 domain
= ask("Domain", default
)
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
315 adminpassplain
= getpass("Administrator password: ")
316 if not adminpassplain
:
317 self
.errf
.write("Invalid administrator password.\n")
319 adminpassverify
= getpass("Retype password: ")
320 if not adminpassplain
== adminpassverify
:
321 self
.errf
.write("Sorry, passwords do not match.\n")
323 adminpass
= adminpassplain
327 realm
= sambaopts
._lp
.get('realm')
329 raise CommandError("No realm set!")
331 raise CommandError("No domain set!")
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
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
):
360 if use_xattrs
== "yes":
362 elif use_xattrs
== "auto" and not lp
.get("posix:eadb"):
364 file = tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(targetdir
))
366 file = tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(os
.path
.dirname(lp
.get("private dir"))))
369 samba
.ntacls
.setntacl(lp
, file.name
,
370 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
373 self
.logger
.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
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()
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
,
387 hostip
=host_ip
, hostip6
=host_ip6
,
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
,
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."""
407 RESOLV_CONF
="/etc/resolv.conf"
409 if not path
.isfile(RESOLV_CONF
):
410 self
.logger
.warning("Failed to locate %s" % RESOLV_CONF
)
415 handle
= open(RESOLV_CONF
, 'r')
417 if not line
.startswith('nameserver'):
419 # we want the last non-space continuous string of the line
420 return line
.strip().split()[-1]
422 if handle
is not None:
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
,
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
)
469 site
= "Default-First-Site-Name"
471 netbios_name
= lp
.get("netbios name")
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)
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)
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
,
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
)
535 site
= "Default-First-Site-Name"
537 netbios_name
= lp
.get("netbios name")
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
))
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
)
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
)
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]"
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
)
597 res
= samdb
.search(expression
='(&(objectClass=computer)(serverReferenceBL=*))', attrs
=["dnsHostName", "name"])
599 raise CommandError("Unable to search for servers")
602 raise CommandError("You are the latest server in the domain")
606 if str(e
["name"]).lower() != netbios_name
.lower():
607 server
= e
["dnsHostName"]
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
,
614 if len(msg
) == 0 or "options" not in msg
[0]:
615 raise CommandError("Failed to find options on %s" % ntds_guid
)
618 dsa_options
= int(str(msg
[0]['options']))
620 res
= samdb
.search(expression
="(fSMORoleOwner=%s)" % str(ntds_dn
),
621 controls
=["search_options:1:2"])
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" %
628 (drsuapiBind
, drsuapi_handle
, supportedExtensions
) = drsuapi_connect(server
, lp
, creds
)
630 self
.errf
.write("Desactivating inbound replication\n")
635 dsa_options |
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
636 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
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"
643 for part
in (samdb
.get_schema_basedn(),
644 samdb
.get_config_basedn(),
645 samdb
.get_root_basedn()):
647 sendDsReplicaSync(drsuapiBind
, drsuapi_handle
, ntds_guid
, str(part
), drsuapi
.DRSUAPI_DRS_WRIT_REP
)
648 except drsException
, e
:
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")
655 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part
), e
)
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"])
667 uac
= int(str(res
[0]["userAccountControl"]))
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")
675 raise CommandError("Error while changing account control", e
)
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")
683 raise CommandError("Unable to find object with samaccountName = %s$"
684 " in the remote dc" % netbios_name
.upper())
688 uac ^
= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION
)
689 uac |
= UF_WORKSTATION_TRUST_ACCOUNT
694 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
695 ldb
.FLAG_MOD_REPLACE
,
696 "userAccountControl")
698 remote_samdb
.modify(msg
)
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")
706 raise CommandError("Error while changing account control", e
)
708 parent
= msg
.dn
.parent()
710 rdn
= string
.replace(rdn
, ",%s" % str(parent
), "")
711 # Let's move to the Computer container
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
)
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):
723 res
= remote_samdb
.search(base
=computer_dn
, expression
="%s-%d" % (rdn
, i
),
724 scope
=ldb
.SCOPE_ONELEVEL
)
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")
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
)
749 newdn
= ldb
.Dn(remote_samdb
, "%s,%s" % (newrdn
, str(computer_dn
)))
750 remote_samdb
.rename(dc_dn
, newdn
)
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")
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()
773 sendRemoveDsServer(drsuapiBind
, drsuapi_handle
, server_dsa_dn
, domain
)
774 except drsException
, e
:
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")
784 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
785 ldb
.FLAG_MOD_REPLACE
,
786 "userAccountControl")
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"):
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
:
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"):
806 remote_samdb
.delete(ldb
.Dn(remote_samdb
,
807 "%s,%s" % (s
, str(newdn
))))
808 except ldb
.LdbError
, l
:
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
,
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
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
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!")
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!")
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)!")
893 if level_forest
== DS_DOMAIN_FUNCTION_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
:
899 elif level_forest
== DS_DOMAIN_FUNCTION_2008
:
901 elif level_forest
== DS_DOMAIN_FUNCTION_2008_R2
:
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:
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
:
915 elif level_domain
== DS_DOMAIN_FUNCTION_2008
:
917 elif level_domain
== DS_DOMAIN_FUNCTION_2008_R2
:
920 outstr
= "higher than 2008 R2"
921 self
.message("Domain function level: (Windows) " + outstr
)
923 if min_level_dc
== DS_DOMAIN_FUNCTION_2000
:
925 elif min_level_dc
== DS_DOMAIN_FUNCTION_2003
:
927 elif min_level_dc
== DS_DOMAIN_FUNCTION_2008
:
929 elif min_level_dc
== DS_DOMAIN_FUNCTION_2008_R2
:
932 outstr
= "higher than 2008 R2"
933 self
.message("Lowest function level of a DC: (Windows) " + outstr
)
935 elif subcommand
== "raise":
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
956 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
957 m
["nTMixedDomain"] = ldb
.MessageElement("0",
958 ldb
.FLAG_MOD_REPLACE
, "nTMixedDomain")
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")
967 except ldb
.LdbError
, (enum
, emsg
):
968 if enum
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
971 # Directly on the base DN
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")
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")
987 except ldb
.LdbError
, (enum
, emsg
):
988 if enum
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
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!")
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")
1011 msgs
.append("Forest function level changed!")
1012 msgs
.append("All changes applied successfully!")
1013 self
.message("\n".join(msgs
))
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
,
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,
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)
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])
1073 cur_min_pwd_age
= int(abs(int(res
[0]["minPwdAge"][0])) / (1e7
* 60 * 60 * 24))
1074 if int(res
[0]["maxPwdAge"][0]) == -0x8000000000000000:
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
)
1084 if pwd_props
& DOMAIN_PASSWORD_COMPLEX
!= 0:
1085 self
.message("Password complexity: on")
1087 self
.message("Password complexity: off")
1088 if pwd_props
& DOMAIN_PASSWORD_STORE_CLEARTEXT
!= 0:
1089 self
.message("Store plaintext passwords: on")
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":
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":
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":
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":
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!")
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":
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!")
1173 if max_pwd_age
== 0:
1174 max_pwd_age_ticks
= -0x8000000000000000
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
))
1186 raise CommandError("You must specify at least one option to set. Try --help")
1188 msgs
.append("All changes applied successfully!")
1189 self
.message("\n".join(msgs
))
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
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()
1250 logger
.setLevel(logging
.DEBUG
)
1252 logger
.setLevel(logging
.WARNING
)
1254 logger
.setLevel(logging
.INFO
)
1256 if dbdir
and testparm
:
1257 logger
.warning("both dbdir and testparm specified, ignoring dbdir.")
1260 lp
= sambaopts
.get_loadparm()
1262 s3conf
= s3param
.get_context()
1265 s3conf
.set("realm", sambaopts
.realm
)
1267 if targetdir
is not None:
1268 if not os
.path
.isdir(targetdir
):
1272 if use_xattrs
== "yes":
1274 elif use_xattrs
== "auto" and not s3conf
.get("posix:eadb"):
1276 tmpfile
= tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(targetdir
))
1278 tmpfile
= tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(os
.path
.dirname(lp
.get("private dir"))))
1281 samba
.ntacls
.setntacl(lp
, tmpfile
.name
,
1282 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
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.")
1291 # Set correct default values from dbdir or testparm
1294 paths
["state directory"] = dbdir
1295 paths
["private dir"] = dbdir
1296 paths
["lock directory"] = dbdir
1297 paths
["smb passwd file"] = dbdir
+ "/smbpasswd"
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"]
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.
1330 class cmd_domain(SuperCommand
):
1331 """Domain management."""
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()