3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2009
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/>.
27 import samba
.getopt
as options
34 from samba
.net
import Net
, LIBNET_JOIN_AUTOMATIC
36 from samba
.join
import join_RODC
, join_DC
, join_subdomain
37 from samba
.auth
import system_session
38 from samba
.samdb
import SamDB
39 from samba
.dcerpc
import drsuapi
40 from samba
.dcerpc
.samr
import DOMAIN_PASSWORD_COMPLEX
, DOMAIN_PASSWORD_STORE_CLEARTEXT
41 from samba
.netcmd
import (
47 from samba
.netcmd
.common
import netcmd_get_domain_infos_via_cldap
48 from samba
.samba3
import Samba3
49 from samba
.samba3
import param
as s3param
50 from samba
.upgrade
import upgrade_from_samba3
51 from samba
.drs_utils
import (
52 sendDsReplicaSync
, drsuapi_connect
, drsException
,
56 from samba
.dsdb
import (
57 DS_DOMAIN_FUNCTION_2000
,
58 DS_DOMAIN_FUNCTION_2003
,
59 DS_DOMAIN_FUNCTION_2003_MIXED
,
60 DS_DOMAIN_FUNCTION_2008
,
61 DS_DOMAIN_FUNCTION_2008_R2
,
62 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
,
63 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
,
64 UF_WORKSTATION_TRUST_ACCOUNT
,
65 UF_SERVER_TRUST_ACCOUNT
,
66 UF_TRUSTED_FOR_DELEGATION
69 from samba
.credentials
import DONT_USE_KERBEROS
70 from samba
.provision
import (
78 def get_testparm_var(testparm
, smbconf
, varname
):
79 cmd
= "%s -s -l --parameter-name='%s' %s 2>/dev/null" % (testparm
, varname
, smbconf
)
80 output
= os
.popen(cmd
, 'r').readline()
85 class cmd_domain_export_keytab(Command
):
86 """Dumps kerberos keys of the domain into a keytab"""
88 synopsis
= "%prog <keytab> [options]"
90 takes_optiongroups
= {
91 "sambaopts": options
.SambaOptions
,
92 "credopts": options
.CredentialsOptions
,
93 "versionopts": options
.VersionOptions
,
97 Option("--principal", help="extract only this principal", type=str),
100 takes_args
= ["keytab"]
102 def run(self
, keytab
, credopts
=None, sambaopts
=None, versionopts
=None, principal
=None):
103 lp
= sambaopts
.get_loadparm()
105 net
.export_keytab(keytab
=keytab
, principal
=principal
)
107 cmd_domain_export_keytab
= None
110 class cmd_domain_info(Command
):
111 """Print basic info about a domain and the DC passed as parameter"""
113 synopsis
= "%prog <ip_address> [options]"
118 takes_optiongroups
= {
119 "sambaopts": options
.SambaOptions
,
120 "credopts": options
.CredentialsOptions
,
121 "versionopts": options
.VersionOptions
,
124 takes_args
= ["address"]
126 def run(self
, address
, credopts
=None, sambaopts
=None, versionopts
=None):
127 lp
= sambaopts
.get_loadparm()
129 res
= netcmd_get_domain_infos_via_cldap(lp
, None, address
)
130 print "Forest : %s" % res
.forest
131 print "Domain : %s" % res
.dns_domain
132 print "Netbios domain : %s" % res
.domain_name
133 print "DC name : %s" % res
.pdc_dns_name
134 print "DC netbios name : %s" % res
.pdc_name
135 print "Server site : %s" % res
.server_site
136 print "Client site : %s" % res
.client_site
138 raise CommandError("Invalid IP address '" + address
+ "'!")
140 class cmd_domain_provision(Command
):
141 """Promotes an existing domain member or NT4 PDC to an AD DC"""
143 synopsis
= "%prog <dnsdomain> [DC|RODC] [options]"
145 takes_optiongroups
= {
146 "sambaopts": options
.SambaOptions
,
147 "versionopts": options
.VersionOptions
,
148 "credopts": options
.CredentialsOptions
,
152 Option("--interactive", help="Ask for names", action
="store_true"),
153 Option("--domain", type="string", metavar
="DOMAIN",
155 Option("--domain-guid", type="string", metavar
="GUID",
156 help="set domainguid (otherwise random)"),
157 Option("--domain-sid", type="string", metavar
="SID",
158 help="set domainsid (otherwise random)"),
159 Option("--ntds-guid", type="string", metavar
="GUID",
160 help="set NTDS object GUID (otherwise random)"),
161 Option("--invocationid", type="string", metavar
="GUID",
162 help="set invocationid (otherwise random)"),
163 Option("--host-name", type="string", metavar
="HOSTNAME",
164 help="set hostname"),
165 Option("--host-ip", type="string", metavar
="IPADDRESS",
166 help="set IPv4 ipaddress"),
167 Option("--host-ip6", type="string", metavar
="IP6ADDRESS",
168 help="set IPv6 ipaddress"),
169 Option("--adminpass", type="string", metavar
="PASSWORD",
170 help="choose admin password (otherwise random)"),
171 Option("--krbtgtpass", type="string", metavar
="PASSWORD",
172 help="choose krbtgt password (otherwise random)"),
173 Option("--machinepass", type="string", metavar
="PASSWORD",
174 help="choose machine password (otherwise random)"),
175 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
176 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
177 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " \
178 "BIND9_FLATFILE uses bind9 text database to store zone information, " \
179 "BIND9_DLZ uses samba4 AD to store zone information, " \
180 "NONE skips the DNS setup entirely (not recommended)",
181 default
="SAMBA_INTERNAL"),
182 Option("--dnspass", type="string", metavar
="PASSWORD",
183 help="choose dns password (otherwise random)"),
184 Option("--ldapadminpass", type="string", metavar
="PASSWORD",
185 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
186 Option("--root", type="string", metavar
="USERNAME",
187 help="choose 'root' unix username"),
188 Option("--nobody", type="string", metavar
="USERNAME",
189 help="choose 'nobody' user"),
190 Option("--wheel", type="string", metavar
="GROUPNAME",
191 help="choose 'wheel' privileged group"),
192 Option("--users", type="string", metavar
="GROUPNAME",
193 help="choose 'users' group"),
194 Option("--quiet", help="Be quiet", action
="store_true"),
195 Option("--blank", action
="store_true",
196 help="do not add users or groups, just the structure"),
197 Option("--ldap-backend-type", type="choice", metavar
="LDAP-BACKEND-TYPE",
198 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
199 choices
=["fedora-ds", "openldap"]),
200 Option("--server-role", type="choice", metavar
="ROLE",
201 choices
=["domain controller", "dc", "member server", "member", "standalone"],
202 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
203 default
="domain controller"),
204 Option("--function-level", type="choice", metavar
="FOR-FUN-LEVEL",
205 choices
=["2000", "2003", "2008", "2008_R2"],
206 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2003 Native.",
208 Option("--next-rid", type="int", metavar
="NEXTRID", default
=1000,
209 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
210 Option("--partitions-only",
211 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action
="store_true"),
212 Option("--targetdir", type="string", metavar
="DIR",
213 help="Set target directory"),
214 Option("--ol-mmr-urls", type="string", metavar
="LDAPSERVER",
215 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\""),
216 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"),
217 Option("--use-ntvfs", action
="store_true", help="Use NTVFS for the fileserver (default = no)"),
218 Option("--use-rfc2307", action
="store_true", help="Use AD to store posix attributes (default = no)"),
222 def run(self
, sambaopts
=None, credopts
=None, versionopts
=None,
236 dns_forwarder
= None,
238 ldapadminpass
= None,
245 ldap_backend_type
= None,
247 function_level
= None,
249 partitions_only
= None,
256 self
.logger
= self
.get_logger("provision")
258 self
.logger
.setLevel(logging
.WARNING
)
260 self
.logger
.setLevel(logging
.INFO
)
262 lp
= sambaopts
.get_loadparm()
263 smbconf
= lp
.configfile
265 creds
= credopts
.get_credentials(lp
)
267 creds
.set_kerberos_state(DONT_USE_KERBEROS
)
269 suggested_forwarder
= dns_forwarder
is None and self
._get
_nameserver
_ip
() or dns_forwarder
271 if len(self
.raw_argv
) == 1:
275 from getpass
import getpass
278 def ask(prompt
, default
=None):
279 if default
is not None:
280 print "%s [%s]: " % (prompt
, default
),
282 print "%s: " % (prompt
,),
283 return sys
.stdin
.readline().rstrip("\n") or default
286 default
= socket
.getfqdn().split(".", 1)[1].upper()
289 realm
= ask("Realm", default
)
290 if realm
in (None, ""):
291 raise CommandError("No realm set!")
294 default
= realm
.split(".")[0]
297 domain
= ask("Domain", default
)
299 raise CommandError("No domain set!")
301 server_role
= ask("Server Role (dc, member, standalone)", "dc")
303 dns_backend
= ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
304 if dns_backend
in (None, ''):
305 raise CommandError("No DNS backend set!")
307 if dns_backend
== "SAMBA_INTERNAL":
308 dns_forwarder
= ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder
)
309 if dns_forwarder
.lower() in (None, 'none'):
310 suggested_forwarder
= None
314 adminpassplain
= getpass("Administrator password: ")
315 if not adminpassplain
:
316 print >>sys
.stderr
, "Invalid administrator password."
318 adminpassverify
= getpass("Retype password: ")
319 if not adminpassplain
== adminpassverify
:
320 print >>sys
.stderr
, "Sorry, passwords do not match."
322 adminpass
= adminpassplain
326 realm
= sambaopts
._lp
.get('realm')
328 raise CommandError("No realm set!")
330 raise CommandError("No domain set!")
333 self
.logger
.info("Administrator password will be set randomly!")
335 if function_level
== "2000":
336 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2000
337 elif function_level
== "2003":
338 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2003
339 elif function_level
== "2008":
340 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2008
341 elif function_level
== "2008_R2":
342 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2008_R2
344 if dns_backend
== "SAMBA_INTERNAL" and dns_forwarder
is None:
345 dns_forwarder
= suggested_forwarder
347 samdb_fill
= FILL_FULL
349 samdb_fill
= FILL_NT4SYNC
350 elif partitions_only
:
351 samdb_fill
= FILL_DRS
353 if targetdir
is not None:
354 if not os
.path
.isdir(targetdir
):
359 if use_xattrs
== "yes":
361 elif use_xattrs
== "auto" and not lp
.get("posix:eadb"):
363 file = tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(targetdir
))
365 file = tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(os
.path
.dirname(lp
.get("private dir"))))
368 samba
.ntacls
.setntacl(lp
, file.name
,
369 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
372 self
.logger
.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
377 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.")
379 session
= system_session()
381 result
= provision(self
.logger
,
382 session
, creds
, smbconf
=smbconf
, targetdir
=targetdir
,
383 samdb_fill
=samdb_fill
, realm
=realm
, domain
=domain
,
384 domainguid
=domain_guid
, domainsid
=domain_sid
,
386 hostip
=host_ip
, hostip6
=host_ip6
,
388 invocationid
=invocationid
, adminpass
=adminpass
,
389 krbtgtpass
=krbtgtpass
, machinepass
=machinepass
,
390 dns_backend
=dns_backend
, dns_forwarder
=dns_forwarder
,
391 dnspass
=dnspass
, root
=root
, nobody
=nobody
,
392 wheel
=wheel
, users
=users
,
393 serverrole
=server_role
, dom_for_fun_level
=dom_for_fun_level
,
394 backend_type
=ldap_backend_type
,
395 ldapadminpass
=ldapadminpass
, ol_mmr_urls
=ol_mmr_urls
,
396 useeadb
=eadb
, next_rid
=next_rid
, lp
=lp
, use_ntvfs
=(use_ntvfs
),
397 use_rfc2307
=(use_rfc2307
))
398 except ProvisioningError
, e
:
399 raise CommandError("Provision failed", e
)
401 result
.report_logger(self
.logger
)
403 def _get_nameserver_ip(self
):
404 """Grab the nameserver IP address from /etc/resolv.conf"""
406 RESOLV_CONF
="/etc/resolv.conf"
408 if not path
.isfile(RESOLV_CONF
):
409 self
.logger
.warning("Failed to locate %s" % RESOLV_CONF
)
414 handle
= open(RESOLV_CONF
, 'r')
416 if not line
.startswith('nameserver'):
418 # we want the last non-space continuous string of the line
419 return line
.strip().split()[-1]
421 if handle
is not None:
424 self
.logger
.warning("No nameserver found in %s" % RESOLV_CONF
)
428 class cmd_domain_dcpromo(Command
):
429 """Promotes 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
, dns_backend
=dns_backend
,
481 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)
491 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role
)
494 class cmd_domain_join(Command
):
495 """Joins domain as either member or backup domain controller"""
497 synopsis
= "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
499 takes_optiongroups
= {
500 "sambaopts": options
.SambaOptions
,
501 "versionopts": options
.VersionOptions
,
502 "credopts": options
.CredentialsOptions
,
506 Option("--server", help="DC to join", type=str),
507 Option("--site", help="site to join", type=str),
508 Option("--targetdir", help="where to store provision", type=str),
509 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
510 Option("--domain-critical-only",
511 help="only replicate critical domain objects",
512 action
="store_true"),
513 Option("--machinepass", type=str, metavar
="PASSWORD",
514 help="choose machine password (otherwise random)"),
515 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
516 action
="store_true"),
517 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
518 choices
=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
519 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " \
520 "BIND9_DLZ uses samba4 AD to store zone information, " \
521 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
522 default
="SAMBA_INTERNAL")
525 takes_args
= ["domain", "role?"]
527 def run(self
, domain
, role
=None, sambaopts
=None, credopts
=None,
528 versionopts
=None, server
=None, site
=None, targetdir
=None,
529 domain_critical_only
=False, parent_domain
=None, machinepass
=None,
530 use_ntvfs
=False, dns_backend
=None):
531 lp
= sambaopts
.get_loadparm()
532 creds
= credopts
.get_credentials(lp
)
533 net
= Net(creds
, lp
, server
=credopts
.ipaddress
)
536 site
= "Default-First-Site-Name"
538 netbios_name
= lp
.get("netbios name")
543 if role
is None or role
== "MEMBER":
544 (join_password
, sid
, domain_name
) = net
.join_member(domain
,
546 LIBNET_JOIN_AUTOMATIC
,
547 machinepass
=machinepass
)
549 self
.outf
.write("Joined domain %s (%s)\n" % (domain_name
, sid
))
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
)
558 join_RODC(server
=server
, creds
=creds
, lp
=lp
, domain
=domain
,
559 site
=site
, netbios_name
=netbios_name
, targetdir
=targetdir
,
560 domain_critical_only
=domain_critical_only
,
561 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
, dns_backend
=dns_backend
)
563 elif role
== "SUBDOMAIN":
564 netbios_domain
= lp
.get("workgroup")
565 if parent_domain
is None:
566 parent_domain
= ".".join(domain
.split(".")[1:])
567 join_subdomain(server
=server
, creds
=creds
, lp
=lp
, dnsdomain
=domain
, parent_domain
=parent_domain
,
568 site
=site
, netbios_name
=netbios_name
, netbios_domain
=netbios_domain
, targetdir
=targetdir
,
569 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
, dns_backend
=dns_backend
)
572 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role
)
576 class cmd_domain_demote(Command
):
577 """Demote ourselves from the role of Domain Controller"""
579 synopsis
= "%prog [options]"
582 Option("--server", help="DC to force replication before demote", type=str),
583 Option("--targetdir", help="where provision is stored", type=str),
586 takes_optiongroups
= {
587 "sambaopts": options
.SambaOptions
,
588 "credopts": options
.CredentialsOptions
,
589 "versionopts": options
.VersionOptions
,
592 def run(self
, sambaopts
=None, credopts
=None,
593 versionopts
=None, server
=None, targetdir
=None):
594 lp
= sambaopts
.get_loadparm()
595 creds
= credopts
.get_credentials(lp
)
596 net
= Net(creds
, lp
, server
=credopts
.ipaddress
)
598 netbios_name
= lp
.get("netbios name")
599 samdb
= SamDB(session_info
=system_session(), credentials
=creds
, lp
=lp
)
601 res
= samdb
.search(expression
='(&(objectClass=computer)(serverReferenceBL=*))', attrs
=["dnsHostName", "name"])
603 raise CommandError("Unable to search for servers")
606 raise CommandError("You are the latest server in the domain")
610 if str(e
["name"]).lower() != netbios_name
.lower():
611 server
= e
["dnsHostName"]
614 ntds_guid
= samdb
.get_ntds_GUID()
615 msg
= samdb
.search(base
=str(samdb
.get_config_basedn()), scope
=ldb
.SCOPE_SUBTREE
,
616 expression
="(objectGUID=%s)" % ntds_guid
,
618 if len(msg
) == 0 or "options" not in msg
[0]:
619 raise CommandError("Failed to find options on %s" % ntds_guid
)
622 dsa_options
= int(str(msg
[0]['options']))
624 res
= samdb
.search(expression
="(fSMORoleOwner=%s)" % str(ntds_dn
),
625 controls
=["search_options:1:2"])
628 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res
))
630 print "Using %s as partner server for the demotion" % server
631 (drsuapiBind
, drsuapi_handle
, supportedExtensions
) = drsuapi_connect(server
, lp
, creds
)
633 print "Desactivating inbound replication"
638 dsa_options |
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
639 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
642 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
644 print "Asking partner server %s to synchronize from us" % server
645 for part
in (samdb
.get_schema_basedn(),
646 samdb
.get_config_basedn(),
647 samdb
.get_root_basedn()):
649 sendDsReplicaSync(drsuapiBind
, drsuapi_handle
, ntds_guid
, str(part
), drsuapi
.DRSUAPI_DRS_WRIT_REP
)
650 except drsException
, e
:
651 print "Error while demoting, re-enabling inbound replication"
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 print "Changing userControl and container"
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"]))
670 print "Error while demoting, re-enabling inbound replication"
671 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
672 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
674 raise CommandError("Error while changing account control", e
)
677 print "Error while demoting, re-enabling inbound replication"
678 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
679 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
681 raise CommandError("Unable to find object with samaccountName = %s$"
682 " in the remote dc" % netbios_name
.upper())
686 uac ^
= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION
)
687 uac |
= UF_WORKSTATION_TRUST_ACCOUNT
692 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
693 ldb
.FLAG_MOD_REPLACE
,
694 "userAccountControl")
696 remote_samdb
.modify(msg
)
698 print "Error while demoting, re-enabling inbound replication"
699 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
700 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
703 raise CommandError("Error while changing account control", e
)
705 parent
= msg
.dn
.parent()
707 rdn
= string
.replace(rdn
, ",%s" % str(parent
), "")
708 # Let's move to the Computer container
712 computer_dn
= ldb
.Dn(remote_samdb
, "CN=Computers,%s" % str(remote_samdb
.get_root_basedn()))
713 res
= remote_samdb
.search(base
=computer_dn
, expression
=rdn
, scope
=ldb
.SCOPE_ONELEVEL
)
716 res
= remote_samdb
.search(base
=computer_dn
, expression
="%s-%d" % (rdn
, i
),
717 scope
=ldb
.SCOPE_ONELEVEL
)
718 while(len(res
) != 0 and i
< 100):
720 res
= remote_samdb
.search(base
=computer_dn
, expression
="%s-%d" % (rdn
, i
),
721 scope
=ldb
.SCOPE_ONELEVEL
)
724 print "Error while demoting, re-enabling inbound replication"
725 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
726 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
732 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
733 ldb
.FLAG_MOD_REPLACE
,
734 "userAccountControl")
736 remote_samdb
.modify(msg
)
738 raise CommandError("Unable to find a slot for renaming %s,"
739 " all names from %s-1 to %s-%d seemed used" %
740 (str(dc_dn
), rdn
, rdn
, i
- 9))
742 newrdn
= "%s-%d" % (rdn
, i
)
745 newdn
= ldb
.Dn(remote_samdb
, "%s,%s" % (newrdn
, str(computer_dn
)))
746 remote_samdb
.rename(dc_dn
, newdn
)
748 print "Error while demoting, re-enabling inbound replication"
749 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
750 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
756 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
757 ldb
.FLAG_MOD_REPLACE
,
758 "userAccountControl")
760 remote_samdb
.modify(msg
)
761 raise CommandError("Error while renaming %s to %s" % (str(dc_dn
), str(newdn
)), e
)
764 server_dsa_dn
= samdb
.get_serverName()
765 domain
= remote_samdb
.get_root_basedn()
768 sendRemoveDsServer(drsuapiBind
, drsuapi_handle
, server_dsa_dn
, domain
)
769 except drsException
, e
:
770 print "Error while demoting, re-enabling inbound replication"
771 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
772 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
778 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
779 ldb
.FLAG_MOD_REPLACE
,
780 "userAccountControl")
782 remote_samdb
.modify(msg
)
783 remote_samdb
.rename(newdn
, dc_dn
)
784 raise CommandError("Error while sending a removeDsServer", e
)
786 for s
in ("CN=Entreprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
787 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp
.get("realm"),
788 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
790 remote_samdb
.delete(ldb
.Dn(remote_samdb
,
791 "%s,%s,%s" % (str(rdn
), s
, str(remote_samdb
.get_root_basedn()))))
792 except ldb
.LdbError
, l
:
795 for s
in ("CN=Entreprise,CN=NTFRS Subscriptions",
796 "CN=%s, CN=NTFRS Subscriptions" % lp
.get("realm"),
797 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
798 "CN=NTFRS Subscriptions"):
800 remote_samdb
.delete(ldb
.Dn(remote_samdb
,
801 "%s,%s" % (s
, str(newdn
))))
802 except ldb
.LdbError
, l
:
805 self
.outf
.write("Demote successfull\n")
808 class cmd_domain_level(Command
):
809 """Raises domain and forest function levels"""
811 synopsis
= "%prog (show|raise <options>) [options]"
813 takes_optiongroups
= {
814 "sambaopts": options
.SambaOptions
,
815 "credopts": options
.CredentialsOptions
,
816 "versionopts": options
.VersionOptions
,
820 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
821 metavar
="URL", dest
="H"),
822 Option("--quiet", help="Be quiet", action
="store_true"),
823 Option("--forest-level", type="choice", choices
=["2003", "2008", "2008_R2"],
824 help="The forest function level (2003 | 2008 | 2008_R2)"),
825 Option("--domain-level", type="choice", choices
=["2003", "2008", "2008_R2"],
826 help="The domain function level (2003 | 2008 | 2008_R2)")
829 takes_args
= ["subcommand"]
831 def run(self
, subcommand
, H
=None, forest_level
=None, domain_level
=None,
832 quiet
=False, credopts
=None, sambaopts
=None, versionopts
=None):
833 lp
= sambaopts
.get_loadparm()
834 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
836 samdb
= SamDB(url
=H
, session_info
=system_session(),
837 credentials
=creds
, lp
=lp
)
839 domain_dn
= samdb
.domain_dn()
841 res_forest
= samdb
.search("CN=Partitions,%s" % samdb
.get_config_basedn(),
842 scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-Behavior-Version"])
843 assert len(res_forest
) == 1
845 res_domain
= samdb
.search(domain_dn
, scope
=ldb
.SCOPE_BASE
,
846 attrs
=["msDS-Behavior-Version", "nTMixedDomain"])
847 assert len(res_domain
) == 1
849 res_dc_s
= samdb
.search("CN=Sites,%s" % samdb
.get_config_basedn(),
850 scope
=ldb
.SCOPE_SUBTREE
, expression
="(objectClass=nTDSDSA)",
851 attrs
=["msDS-Behavior-Version"])
852 assert len(res_dc_s
) >= 1
855 level_forest
= int(res_forest
[0]["msDS-Behavior-Version"][0])
856 level_domain
= int(res_domain
[0]["msDS-Behavior-Version"][0])
857 level_domain_mixed
= int(res_domain
[0]["nTMixedDomain"][0])
859 min_level_dc
= int(res_dc_s
[0]["msDS-Behavior-Version"][0]) # Init value
861 if int(msg
["msDS-Behavior-Version"][0]) < min_level_dc
:
862 min_level_dc
= int(msg
["msDS-Behavior-Version"][0])
864 if level_forest
< 0 or level_domain
< 0:
865 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
867 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
868 if level_forest
> level_domain
:
869 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
870 if level_domain
> min_level_dc
:
871 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
874 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
876 if subcommand
== "show":
877 self
.message("Domain and forest function level for domain '%s'" % domain_dn
)
878 if level_forest
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
879 self
.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
880 if level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
881 self
.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
882 if min_level_dc
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
883 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)!")
887 if level_forest
== DS_DOMAIN_FUNCTION_2000
:
889 elif level_forest
== DS_DOMAIN_FUNCTION_2003_MIXED
:
890 outstr
= "2003 with mixed domains/interim (NT4 DC support)"
891 elif level_forest
== DS_DOMAIN_FUNCTION_2003
:
893 elif level_forest
== DS_DOMAIN_FUNCTION_2008
:
895 elif level_forest
== DS_DOMAIN_FUNCTION_2008_R2
:
898 outstr
= "higher than 2008 R2"
899 self
.message("Forest function level: (Windows) " + outstr
)
901 if level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
902 outstr
= "2000 mixed (NT4 DC support)"
903 elif level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
== 0:
905 elif level_domain
== DS_DOMAIN_FUNCTION_2003_MIXED
:
906 outstr
= "2003 with mixed domains/interim (NT4 DC support)"
907 elif level_domain
== DS_DOMAIN_FUNCTION_2003
:
909 elif level_domain
== DS_DOMAIN_FUNCTION_2008
:
911 elif level_domain
== DS_DOMAIN_FUNCTION_2008_R2
:
914 outstr
= "higher than 2008 R2"
915 self
.message("Domain function level: (Windows) " + outstr
)
917 if min_level_dc
== DS_DOMAIN_FUNCTION_2000
:
919 elif min_level_dc
== DS_DOMAIN_FUNCTION_2003
:
921 elif min_level_dc
== DS_DOMAIN_FUNCTION_2008
:
923 elif min_level_dc
== DS_DOMAIN_FUNCTION_2008_R2
:
926 outstr
= "higher than 2008 R2"
927 self
.message("Lowest function level of a DC: (Windows) " + outstr
)
929 elif subcommand
== "raise":
932 if domain_level
is not None:
933 if domain_level
== "2003":
934 new_level_domain
= DS_DOMAIN_FUNCTION_2003
935 elif domain_level
== "2008":
936 new_level_domain
= DS_DOMAIN_FUNCTION_2008
937 elif domain_level
== "2008_R2":
938 new_level_domain
= DS_DOMAIN_FUNCTION_2008_R2
940 if new_level_domain
<= level_domain
and level_domain_mixed
== 0:
941 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
943 if new_level_domain
> min_level_dc
:
944 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
946 # Deactivate mixed/interim domain support
947 if level_domain_mixed
!= 0:
948 # Directly on the base DN
950 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
951 m
["nTMixedDomain"] = ldb
.MessageElement("0",
952 ldb
.FLAG_MOD_REPLACE
, "nTMixedDomain")
956 m
.dn
= ldb
.Dn(samdb
, "CN=" + lp
.get("workgroup") + ",CN=Partitions,%s" % samdb
.get_config_basedn())
957 m
["nTMixedDomain"] = ldb
.MessageElement("0",
958 ldb
.FLAG_MOD_REPLACE
, "nTMixedDomain")
961 except ldb
.LdbError
, (enum
, emsg
):
962 if enum
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
965 # Directly on the base DN
967 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
968 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
969 str(new_level_domain
), ldb
.FLAG_MOD_REPLACE
,
970 "msDS-Behavior-Version")
974 m
.dn
= ldb
.Dn(samdb
, "CN=" + lp
.get("workgroup")
975 + ",CN=Partitions,%s" % samdb
.get_config_basedn())
976 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
977 str(new_level_domain
), ldb
.FLAG_MOD_REPLACE
,
978 "msDS-Behavior-Version")
981 except ldb
.LdbError
, (enum
, emsg
):
982 if enum
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
985 level_domain
= new_level_domain
986 msgs
.append("Domain function level changed!")
988 if forest_level
is not None:
989 if forest_level
== "2003":
990 new_level_forest
= DS_DOMAIN_FUNCTION_2003
991 elif forest_level
== "2008":
992 new_level_forest
= DS_DOMAIN_FUNCTION_2008
993 elif forest_level
== "2008_R2":
994 new_level_forest
= DS_DOMAIN_FUNCTION_2008_R2
995 if new_level_forest
<= level_forest
:
996 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
997 if new_level_forest
> level_domain
:
998 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1000 m
.dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
1001 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
1002 str(new_level_forest
), ldb
.FLAG_MOD_REPLACE
,
1003 "msDS-Behavior-Version")
1005 msgs
.append("Forest function level changed!")
1006 msgs
.append("All changes applied successfully!")
1007 self
.message("\n".join(msgs
))
1009 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand
)
1012 class cmd_domain_passwordsettings(Command
):
1013 """Sets password settings
1015 Password complexity, history length, minimum password length, the minimum
1016 and maximum password age) on a Samba4 server.
1019 synopsis
= "%prog (show|set <options>) [options]"
1021 takes_optiongroups
= {
1022 "sambaopts": options
.SambaOptions
,
1023 "versionopts": options
.VersionOptions
,
1024 "credopts": options
.CredentialsOptions
,
1028 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1029 metavar
="URL", dest
="H"),
1030 Option("--quiet", help="Be quiet", action
="store_true"),
1031 Option("--complexity", type="choice", choices
=["on","off","default"],
1032 help="The password complexity (on | off | default). Default is 'on'"),
1033 Option("--store-plaintext", type="choice", choices
=["on","off","default"],
1034 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1035 Option("--history-length",
1036 help="The password history length (<integer> | default). Default is 24.", type=str),
1037 Option("--min-pwd-length",
1038 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1039 Option("--min-pwd-age",
1040 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1041 Option("--max-pwd-age",
1042 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1045 takes_args
= ["subcommand"]
1047 def run(self
, subcommand
, H
=None, min_pwd_age
=None, max_pwd_age
=None,
1048 quiet
=False, complexity
=None, store_plaintext
=None, history_length
=None,
1049 min_pwd_length
=None, credopts
=None, sambaopts
=None,
1051 lp
= sambaopts
.get_loadparm()
1052 creds
= credopts
.get_credentials(lp
)
1054 samdb
= SamDB(url
=H
, session_info
=system_session(),
1055 credentials
=creds
, lp
=lp
)
1057 domain_dn
= samdb
.domain_dn()
1058 res
= samdb
.search(domain_dn
, scope
=ldb
.SCOPE_BASE
,
1059 attrs
=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1060 "minPwdAge", "maxPwdAge"])
1061 assert(len(res
) == 1)
1063 pwd_props
= int(res
[0]["pwdProperties"][0])
1064 pwd_hist_len
= int(res
[0]["pwdHistoryLength"][0])
1065 cur_min_pwd_len
= int(res
[0]["minPwdLength"][0])
1067 cur_min_pwd_age
= int(abs(int(res
[0]["minPwdAge"][0])) / (1e7
* 60 * 60 * 24))
1068 if int(res
[0]["maxPwdAge"][0]) == -0x8000000000000000:
1071 cur_max_pwd_age
= int(abs(int(res
[0]["maxPwdAge"][0])) / (1e7
* 60 * 60 * 24))
1072 except Exception, e
:
1073 raise CommandError("Could not retrieve password properties!", e
)
1075 if subcommand
== "show":
1076 self
.message("Password informations for domain '%s'" % domain_dn
)
1078 if pwd_props
& DOMAIN_PASSWORD_COMPLEX
!= 0:
1079 self
.message("Password complexity: on")
1081 self
.message("Password complexity: off")
1082 if pwd_props
& DOMAIN_PASSWORD_STORE_CLEARTEXT
!= 0:
1083 self
.message("Store plaintext passwords: on")
1085 self
.message("Store plaintext passwords: off")
1086 self
.message("Password history length: %d" % pwd_hist_len
)
1087 self
.message("Minimum password length: %d" % cur_min_pwd_len
)
1088 self
.message("Minimum password age (days): %d" % cur_min_pwd_age
)
1089 self
.message("Maximum password age (days): %d" % cur_max_pwd_age
)
1090 elif subcommand
== "set":
1093 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
1095 if complexity
is not None:
1096 if complexity
== "on" or complexity
== "default":
1097 pwd_props
= pwd_props | DOMAIN_PASSWORD_COMPLEX
1098 msgs
.append("Password complexity activated!")
1099 elif complexity
== "off":
1100 pwd_props
= pwd_props
& (~DOMAIN_PASSWORD_COMPLEX
)
1101 msgs
.append("Password complexity deactivated!")
1103 if store_plaintext
is not None:
1104 if store_plaintext
== "on" or store_plaintext
== "default":
1105 pwd_props
= pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1106 msgs
.append("Plaintext password storage for changed passwords activated!")
1107 elif store_plaintext
== "off":
1108 pwd_props
= pwd_props
& (~DOMAIN_PASSWORD_STORE_CLEARTEXT
)
1109 msgs
.append("Plaintext password storage for changed passwords deactivated!")
1111 if complexity
is not None or store_plaintext
is not None:
1112 m
["pwdProperties"] = ldb
.MessageElement(str(pwd_props
),
1113 ldb
.FLAG_MOD_REPLACE
, "pwdProperties")
1115 if history_length
is not None:
1116 if history_length
== "default":
1119 pwd_hist_len
= int(history_length
)
1121 if pwd_hist_len
< 0 or pwd_hist_len
> 24:
1122 raise CommandError("Password history length must be in the range of 0 to 24!")
1124 m
["pwdHistoryLength"] = ldb
.MessageElement(str(pwd_hist_len
),
1125 ldb
.FLAG_MOD_REPLACE
, "pwdHistoryLength")
1126 msgs
.append("Password history length changed!")
1128 if min_pwd_length
is not None:
1129 if min_pwd_length
== "default":
1132 min_pwd_len
= int(min_pwd_length
)
1134 if min_pwd_len
< 0 or min_pwd_len
> 14:
1135 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1137 m
["minPwdLength"] = ldb
.MessageElement(str(min_pwd_len
),
1138 ldb
.FLAG_MOD_REPLACE
, "minPwdLength")
1139 msgs
.append("Minimum password length changed!")
1141 if min_pwd_age
is not None:
1142 if min_pwd_age
== "default":
1145 min_pwd_age
= int(min_pwd_age
)
1147 if min_pwd_age
< 0 or min_pwd_age
> 998:
1148 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1151 min_pwd_age_ticks
= -int(min_pwd_age
* (24 * 60 * 60 * 1e7
))
1153 m
["minPwdAge"] = ldb
.MessageElement(str(min_pwd_age_ticks
),
1154 ldb
.FLAG_MOD_REPLACE
, "minPwdAge")
1155 msgs
.append("Minimum password age changed!")
1157 if max_pwd_age
is not None:
1158 if max_pwd_age
== "default":
1161 max_pwd_age
= int(max_pwd_age
)
1163 if max_pwd_age
< 0 or max_pwd_age
> 999:
1164 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1167 if max_pwd_age
== 0:
1168 max_pwd_age_ticks
= -0x8000000000000000
1170 max_pwd_age_ticks
= -int(max_pwd_age
* (24 * 60 * 60 * 1e7
))
1172 m
["maxPwdAge"] = ldb
.MessageElement(str(max_pwd_age_ticks
),
1173 ldb
.FLAG_MOD_REPLACE
, "maxPwdAge")
1174 msgs
.append("Maximum password age changed!")
1176 if max_pwd_age
> 0 and min_pwd_age
>= max_pwd_age
:
1177 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age
, min_pwd_age
))
1180 raise CommandError("You must specify at least one option to set. Try --help")
1182 msgs
.append("All changes applied successfully!")
1183 self
.message("\n".join(msgs
))
1185 raise CommandError("Wrong argument '%s'!" % subcommand
)
1188 class cmd_domain_classicupgrade(Command
):
1189 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1191 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1192 the testparm utility from your classic installation (with --testparm).
1195 synopsis
= "%prog [options] <classic_smb_conf>"
1197 takes_optiongroups
= {
1198 "sambaopts": options
.SambaOptions
,
1199 "versionopts": options
.VersionOptions
1203 Option("--dbdir", type="string", metavar
="DIR",
1204 help="Path to samba classic DC database directory"),
1205 Option("--testparm", type="string", metavar
="PATH",
1206 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1207 Option("--targetdir", type="string", metavar
="DIR",
1208 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1209 Option("--quiet", help="Be quiet", action
="store_true"),
1210 Option("--verbose", help="Be verbose", action
="store_true"),
1211 Option("--use-xattrs", type="choice", choices
=["yes","no","auto"], metavar
="[yes|no|auto]",
1212 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"),
1213 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1214 action
="store_true"),
1215 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
1216 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1217 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " \
1218 "BIND9_FLATFILE uses bind9 text database to store zone information, " \
1219 "BIND9_DLZ uses samba4 AD to store zone information, " \
1220 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1221 default
="SAMBA_INTERNAL")
1224 takes_args
= ["smbconf"]
1226 def run(self
, smbconf
=None, targetdir
=None, dbdir
=None, testparm
=None,
1227 quiet
=False, verbose
=False, use_xattrs
=None, sambaopts
=None, versionopts
=None,
1228 dns_backend
=None, use_ntvfs
=False):
1230 if not os
.path
.exists(smbconf
):
1231 raise CommandError("File %s does not exist" % smbconf
)
1233 if testparm
and not os
.path
.exists(testparm
):
1234 raise CommandError("Testparm utility %s does not exist" % testparm
)
1236 if dbdir
and not os
.path
.exists(dbdir
):
1237 raise CommandError("Directory %s does not exist" % dbdir
)
1239 if not dbdir
and not testparm
:
1240 raise CommandError("Please specify either dbdir or testparm")
1242 logger
= self
.get_logger()
1244 logger
.setLevel(logging
.DEBUG
)
1246 logger
.setLevel(logging
.WARNING
)
1248 logger
.setLevel(logging
.INFO
)
1250 if dbdir
and testparm
:
1251 logger
.warning("both dbdir and testparm specified, ignoring dbdir.")
1254 lp
= sambaopts
.get_loadparm()
1256 s3conf
= s3param
.get_context()
1259 s3conf
.set("realm", sambaopts
.realm
)
1261 if targetdir
is not None:
1262 if not os
.path
.isdir(targetdir
):
1266 if use_xattrs
== "yes":
1268 elif use_xattrs
== "auto" and not s3conf
.get("posix:eadb"):
1270 tmpfile
= tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(targetdir
))
1272 tmpfile
= tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(os
.path
.dirname(lp
.get("private dir"))))
1275 samba
.ntacls
.setntacl(lp
, tmpfile
.name
,
1276 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1279 # FIXME: Don't catch all exceptions here
1280 logger
.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1281 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1285 # Set correct default values from dbdir or testparm
1288 paths
["state directory"] = dbdir
1289 paths
["private dir"] = dbdir
1290 paths
["lock directory"] = dbdir
1291 paths
["smb passwd file"] = dbdir
+ "/smbpasswd"
1293 paths
["state directory"] = get_testparm_var(testparm
, smbconf
, "state directory")
1294 paths
["private dir"] = get_testparm_var(testparm
, smbconf
, "private dir")
1295 paths
["smb passwd file"] = get_testparm_var(testparm
, smbconf
, "smb passwd file")
1296 paths
["lock directory"] = get_testparm_var(testparm
, smbconf
, "lock directory")
1297 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1298 # "state directory", instead make use of "lock directory"
1299 if len(paths
["state directory"]) == 0:
1300 paths
["state directory"] = paths
["lock directory"]
1303 s3conf
.set(p
, paths
[p
])
1305 # load smb.conf parameters
1306 logger
.info("Reading smb.conf")
1307 s3conf
.load(smbconf
)
1308 samba3
= Samba3(smbconf
, s3conf
)
1310 logger
.info("Provisioning")
1311 upgrade_from_samba3(samba3
, logger
, targetdir
, session_info
=system_session(),
1312 useeadb
=eadb
, dns_backend
=dns_backend
, use_ntvfs
=use_ntvfs
)
1314 class cmd_domain(SuperCommand
):
1315 """Domain management"""
1318 subcommands
["demote"] = cmd_domain_demote()
1319 if type(cmd_domain_export_keytab
).__name
__ != 'NoneType':
1320 subcommands
["exportkeytab"] = cmd_domain_export_keytab()
1321 subcommands
["info"] = cmd_domain_info()
1322 subcommands
["provision"] = cmd_domain_provision()
1323 subcommands
["join"] = cmd_domain_join()
1324 subcommands
["dcpromo"] = cmd_domain_dcpromo()
1325 subcommands
["level"] = cmd_domain_level()
1326 subcommands
["passwordsettings"] = cmd_domain_passwordsettings()
1327 subcommands
["classicupgrade"] = cmd_domain_classicupgrade()
1328 subcommands
["samba3upgrade"] = cmd_domain_classicupgrade()