summaryrefslogtreecommitdiff
path: root/python/samba/netcmd
diff options
context:
space:
mode:
authorJelmer Vernooij <jelmer@samba.org>2012-12-28 15:37:14 +0100
committerAndrew Bartlett <abartlet@samba.org>2013-03-02 03:57:34 +0100
commit87afc3aee1ea593069322a49355dd8780d99e123 (patch)
tree8e1ea6678d93b53f21b34c4940b7d5a64e0f5020 /python/samba/netcmd
parent80fce353e740c793619005ac102ab07fb5e7d280 (diff)
downloadsamba-87afc3aee1ea593069322a49355dd8780d99e123.tar.gz
samba-87afc3aee1ea593069322a49355dd8780d99e123.tar.bz2
samba-87afc3aee1ea593069322a49355dd8780d99e123.zip
Move python modules from source4/scripting/python/ to python/.
Reviewed-by: Andrew Bartlett <abartlet@samba.org> Autobuild-User(master): Andrew Bartlett <abartlet@samba.org> Autobuild-Date(master): Sat Mar 2 03:57:34 CET 2013 on sn-devel-104
Diffstat (limited to 'python/samba/netcmd')
-rw-r--r--python/samba/netcmd/__init__.py231
-rw-r--r--python/samba/netcmd/common.py71
-rw-r--r--python/samba/netcmd/dbcheck.py143
-rw-r--r--python/samba/netcmd/delegation.py263
-rw-r--r--python/samba/netcmd/dns.py1186
-rw-r--r--python/samba/netcmd/domain.py1344
-rw-r--r--python/samba/netcmd/drs.py510
-rw-r--r--python/samba/netcmd/dsacl.py182
-rw-r--r--python/samba/netcmd/fsmo.py277
-rw-r--r--python/samba/netcmd/gpo.py1177
-rw-r--r--python/samba/netcmd/group.py376
-rw-r--r--python/samba/netcmd/ldapcmp.py998
-rw-r--r--python/samba/netcmd/main.py70
-rw-r--r--python/samba/netcmd/ntacl.py260
-rw-r--r--python/samba/netcmd/processes.py78
-rw-r--r--python/samba/netcmd/rodc.py108
-rw-r--r--python/samba/netcmd/sites.py105
-rw-r--r--python/samba/netcmd/spn.py205
-rw-r--r--python/samba/netcmd/testparm.py209
-rw-r--r--python/samba/netcmd/time.py59
-rw-r--r--python/samba/netcmd/user.py605
-rw-r--r--python/samba/netcmd/vampire.py55
22 files changed, 8512 insertions, 0 deletions
diff --git a/python/samba/netcmd/__init__.py b/python/samba/netcmd/__init__.py
new file mode 100644
index 0000000000..a3edf50516
--- /dev/null
+++ b/python/samba/netcmd/__init__.py
@@ -0,0 +1,231 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009-2012
+# Copyright (C) Theresa Halloran <theresahalloran@gmail.com> 2011
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import optparse, samba
+from samba import getopt as options
+from ldb import LdbError
+import sys, traceback
+import textwrap
+
+class Option(optparse.Option):
+ pass
+
+# This help formatter does text wrapping and preserves newlines
+class PlainHelpFormatter(optparse.IndentedHelpFormatter):
+ def format_description(self,description=""):
+ desc_width = self.width - self.current_indent
+ indent = " "*self.current_indent
+ paragraphs = description.split('\n')
+ wrapped_paragraphs = [
+ textwrap.fill(p,
+ desc_width,
+ initial_indent=indent,
+ subsequent_indent=indent)
+ for p in paragraphs]
+ result = "\n".join(wrapped_paragraphs) + "\n"
+ return result
+
+ def format_epilog(self, epilog):
+ if epilog:
+ return "\n" + epilog + "\n"
+ else:
+ return ""
+
+class Command(object):
+ """A samba-tool command."""
+
+ def _get_short_description(self):
+ return self.__doc__.splitlines()[0].rstrip("\n")
+
+ short_description = property(_get_short_description)
+
+ def _get_full_description(self):
+ lines = self.__doc__.split("\n")
+ return lines[0] + "\n" + textwrap.dedent("\n".join(lines[1:]))
+
+ full_description = property(_get_full_description)
+
+ def _get_name(self):
+ name = self.__class__.__name__
+ if name.startswith("cmd_"):
+ return name[4:]
+ return name
+
+ name = property(_get_name)
+
+ # synopsis must be defined in all subclasses in order to provide the
+ # command usage
+ synopsis = None
+ takes_args = []
+ takes_options = []
+ takes_optiongroups = {}
+
+ hidden = False
+
+ raw_argv = None
+ raw_args = None
+ raw_kwargs = None
+
+ def __init__(self, outf=sys.stdout, errf=sys.stderr):
+ self.outf = outf
+ self.errf = errf
+
+ def usage(self, prog, *args):
+ parser, _ = self._create_parser(prog)
+ parser.print_usage()
+
+ def show_command_error(self, e):
+ '''display a command error'''
+ if isinstance(e, CommandError):
+ (etype, evalue, etraceback) = e.exception_info
+ inner_exception = e.inner_exception
+ message = e.message
+ force_traceback = False
+ else:
+ (etype, evalue, etraceback) = sys.exc_info()
+ inner_exception = e
+ message = "uncaught exception"
+ force_traceback = True
+
+ if isinstance(inner_exception, LdbError):
+ (ldb_ecode, ldb_emsg) = inner_exception
+ self.errf.write("ERROR(ldb): %s - %s\n" % (message, ldb_emsg))
+ elif isinstance(inner_exception, AssertionError):
+ self.errf.write("ERROR(assert): %s\n" % message)
+ force_traceback = True
+ elif isinstance(inner_exception, RuntimeError):
+ self.errf.write("ERROR(runtime): %s - %s\n" % (message, evalue))
+ elif type(inner_exception) is Exception:
+ self.errf.write("ERROR(exception): %s - %s\n" % (message, evalue))
+ force_traceback = True
+ elif inner_exception is None:
+ self.errf.write("ERROR: %s\n" % (message))
+ else:
+ self.errf.write("ERROR(%s): %s - %s\n" % (str(etype), message, evalue))
+ force_traceback = True
+
+ if force_traceback or samba.get_debug_level() >= 3:
+ traceback.print_tb(etraceback)
+
+ def _create_parser(self, prog, epilog=None):
+ parser = optparse.OptionParser(
+ usage=self.synopsis,
+ description=self.full_description,
+ formatter=PlainHelpFormatter(),
+ prog=prog,epilog=epilog)
+ parser.add_options(self.takes_options)
+ optiongroups = {}
+ for name, optiongroup in self.takes_optiongroups.iteritems():
+ optiongroups[name] = optiongroup(parser)
+ parser.add_option_group(optiongroups[name])
+ return parser, optiongroups
+
+ def message(self, text):
+ self.outf.write(text+"\n")
+
+ def _run(self, *argv):
+ parser, optiongroups = self._create_parser(argv[0])
+ opts, args = parser.parse_args(list(argv))
+ # Filter out options from option groups
+ args = args[1:]
+ kwargs = dict(opts.__dict__)
+ for option_group in parser.option_groups:
+ for option in option_group.option_list:
+ if option.dest is not None:
+ del kwargs[option.dest]
+ kwargs.update(optiongroups)
+
+ # Check for a min a max number of allowed arguments, whenever possible
+ # The suffix "?" means zero or one occurence
+ # The suffix "+" means at least one occurence
+ min_args = 0
+ max_args = 0
+ undetermined_max_args = False
+ for i, arg in enumerate(self.takes_args):
+ if arg[-1] != "?":
+ min_args += 1
+ if arg[-1] == "+":
+ undetermined_max_args = True
+ else:
+ max_args += 1
+ if (len(args) < min_args) or (not undetermined_max_args and len(args) > max_args):
+ parser.print_usage()
+ return -1
+
+ self.raw_argv = list(argv)
+ self.raw_args = args
+ self.raw_kwargs = kwargs
+
+ try:
+ return self.run(*args, **kwargs)
+ except Exception, e:
+ self.show_command_error(e)
+ return -1
+
+ def run(self):
+ """Run the command. This should be overriden by all subclasses."""
+ raise NotImplementedError(self.run)
+
+ def get_logger(self, name="netcmd"):
+ """Get a logger object."""
+ import logging
+ logger = logging.getLogger(name)
+ logger.addHandler(logging.StreamHandler(self.errf))
+ return logger
+
+
+class SuperCommand(Command):
+ """A samba-tool command with subcommands."""
+
+ synopsis = "%prog <subcommand>"
+
+ subcommands = {}
+
+ def _run(self, myname, subcommand=None, *args):
+ if subcommand in self.subcommands:
+ return self.subcommands[subcommand]._run(
+ "%s %s" % (myname, subcommand), *args)
+
+ epilog = "\nAvailable subcommands:\n"
+ subcmds = self.subcommands.keys()
+ subcmds.sort()
+ max_length = max([len(c) for c in subcmds])
+ for cmd_name in subcmds:
+ cmd = self.subcommands[cmd_name]
+ if not cmd.hidden:
+ epilog += " %*s - %s\n" % (
+ -max_length, cmd_name, cmd.short_description)
+ epilog += "For more help on a specific subcommand, please type: %s <subcommand> (-h|--help)\n" % myname
+
+ parser, optiongroups = self._create_parser(myname, epilog=epilog)
+ args_list = list(args)
+ if subcommand:
+ args_list.insert(0, subcommand)
+ opts, args = parser.parse_args(args_list)
+
+ parser.print_help()
+ return -1
+
+
+class CommandError(Exception):
+ """An exception class for samba-tool Command errors."""
+
+ def __init__(self, message, inner_exception=None):
+ self.message = message
+ self.inner_exception = inner_exception
+ self.exception_info = sys.exc_info()
diff --git a/python/samba/netcmd/common.py b/python/samba/netcmd/common.py
new file mode 100644
index 0000000000..5c0bd95f08
--- /dev/null
+++ b/python/samba/netcmd/common.py
@@ -0,0 +1,71 @@
+# common functions for samba-tool python commands
+#
+# Copyright Andrew Tridgell 2010
+# Copyright Giampaolo Lauria 2011 <lauria2@yahoo.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import re
+from samba.dcerpc import nbt
+from samba.net import Net
+
+
+def _get_user_realm_domain(user):
+ """ get the realm or the domain and the base user
+ from user like:
+ * username
+ * DOMAIN\username
+ * username@REALM
+ """
+ baseuser = user
+ realm = ""
+ domain = ""
+ m = re.match(r"(\w+)\\(\w+$)", user)
+ if m:
+ domain = m.group(1)
+ baseuser = m.group(2)
+ return (baseuser.lower(), domain.upper(), realm)
+ m = re.match(r"(\w+)@(\w+)", user)
+ if m:
+ baseuser = m.group(1)
+ realm = m.group(2)
+ return (baseuser.lower(), domain, realm.upper())
+
+
+def netcmd_dnsname(lp):
+ '''return the full DNS name of our own host. Used as a default
+ for hostname when running status queries'''
+ return lp.get('netbios name').lower() + "." + lp.get('realm').lower()
+
+
+def netcmd_finddc(lp, creds, realm=None):
+ '''Return domain-name of a writable/ldap-capable DC for the default
+ domain (parameter "realm" in smb.conf) unless another realm has been
+ specified as argument'''
+ net = Net(creds=creds, lp=lp)
+ if realm is None:
+ realm = lp.get('realm')
+ cldap_ret = net.finddc(domain=realm,
+ flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
+ return cldap_ret.pdc_dns_name
+
+
+def netcmd_get_domain_infos_via_cldap(lp, creds, address=None):
+ '''Return domain informations (CLDAP record) of the ldap-capable
+ DC with the specified address'''
+ net = Net(creds=creds, lp=lp)
+ cldap_ret = net.finddc(address=address,
+ flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
+ return cldap_ret
diff --git a/python/samba/netcmd/dbcheck.py b/python/samba/netcmd/dbcheck.py
new file mode 100644
index 0000000000..889b0ff075
--- /dev/null
+++ b/python/samba/netcmd/dbcheck.py
@@ -0,0 +1,143 @@
+# Samba4 AD database checker
+#
+# Copyright (C) Andrew Tridgell 2011
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import ldb, sys
+import samba.getopt as options
+from samba.auth import system_session
+from samba.samdb import SamDB
+from samba.netcmd import (
+ Command,
+ CommandError,
+ Option
+ )
+from samba.dbchecker import dbcheck
+
+
+class cmd_dbcheck(Command):
+ """Check local AD database for errors."""
+ synopsis = "%prog [<DN>] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptionsDouble,
+ }
+
+ takes_args = ["DN?"]
+
+ takes_options = [
+ Option("--scope", dest="scope", default="SUB",
+ help="Pass search scope that builds DN list. Options: SUB, ONE, BASE"),
+ Option("--fix", dest="fix", default=False, action='store_true',
+ help='Fix any errors found'),
+ Option("--yes", dest="yes", default=False, action='store_true',
+ help="don't confirm changes, just do them all as a single transaction"),
+ Option("--cross-ncs", dest="cross_ncs", default=False, action='store_true',
+ help="cross naming context boundaries"),
+ Option("-v", "--verbose", dest="verbose", action="store_true", default=False,
+ help="Print more details of checking"),
+ Option("--quiet", dest="quiet", action="store_true", default=False,
+ help="don't print details of checking"),
+ Option("--attrs", dest="attrs", default=None, help="list of attributes to check (space separated)"),
+ Option("--reindex", dest="reindex", default=False, action="store_true", help="force database re-index"),
+ Option("--force-modules", dest="force_modules", default=False, action="store_true", help="force loading of Samba modules and ignore the @MODULES record (for very old databases)"),
+ Option("-H", "--URL", help="LDB URL for database or target server (defaults to local SAM database)",
+ type=str, metavar="URL", dest="H"),
+ ]
+
+ def run(self, DN=None, H=None, verbose=False, fix=False, yes=False,
+ cross_ncs=False, quiet=False,
+ scope="SUB", credopts=None, sambaopts=None, versionopts=None,
+ attrs=None, reindex=False, force_modules=False):
+
+ lp = sambaopts.get_loadparm()
+
+ over_ldap = H is not None and H.startswith('ldap')
+
+ if over_ldap:
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+ else:
+ creds = None
+
+ if force_modules:
+ samdb = SamDB(session_info=system_session(), url=H,
+ credentials=creds, lp=lp, options=["modules=samba_dsdb"])
+ else:
+ try:
+ samdb = SamDB(session_info=system_session(), url=H,
+ credentials=creds, lp=lp)
+ except:
+ raise CommandError("Failed to connect to DB at %s. If this is a really old sam.ldb (before alpha9), then try again with --force-modules" % H)
+
+
+ if H is None or not over_ldap:
+ samdb_schema = samdb
+ else:
+ samdb_schema = SamDB(session_info=system_session(), url=None,
+ credentials=creds, lp=lp)
+
+ scope_map = { "SUB": ldb.SCOPE_SUBTREE, "BASE": ldb.SCOPE_BASE, "ONE":ldb.SCOPE_ONELEVEL }
+ scope = scope.upper()
+ if not scope in scope_map:
+ raise CommandError("Unknown scope %s" % scope)
+ search_scope = scope_map[scope]
+
+ controls = ['show_deleted:1']
+ if over_ldap:
+ controls.append('paged_results:1:1000')
+ if cross_ncs:
+ controls.append("search_options:1:2")
+
+ if not attrs:
+ attrs = ['*']
+ else:
+ attrs = attrs.split()
+
+ started_transaction = False
+ if yes and fix:
+ samdb.transaction_start()
+ started_transaction = True
+ try:
+ chk = dbcheck(samdb, samdb_schema=samdb_schema, verbose=verbose,
+ fix=fix, yes=yes, quiet=quiet, in_transaction=started_transaction)
+
+ if reindex:
+ self.outf.write("Re-indexing...\n")
+ error_count = 0
+ if chk.reindex_database():
+ self.outf.write("completed re-index OK\n")
+
+ elif force_modules:
+ self.outf.write("Resetting @MODULES...\n")
+ error_count = 0
+ if chk.reset_modules():
+ self.outf.write("completed @MODULES reset OK\n")
+
+ else:
+ error_count = chk.check_database(DN=DN, scope=search_scope,
+ controls=controls, attrs=attrs)
+ except:
+ if started_transaction:
+ samdb.transaction_cancel()
+ raise
+
+ if started_transaction:
+ samdb.transaction_commit()
+
+ if error_count != 0:
+ sys.exit(1)
diff --git a/python/samba/netcmd/delegation.py b/python/samba/netcmd/delegation.py
new file mode 100644
index 0000000000..47dffb07d5
--- /dev/null
+++ b/python/samba/netcmd/delegation.py
@@ -0,0 +1,263 @@
+# delegation management
+#
+# Copyright Matthieu Patou mat@samba.org 2010
+# Copyright Stefan Metzmacher metze@samba.org 2011
+# Copyright Bjoern Baumbach bb@sernet.de 2011
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+from samba import provision
+from samba import dsdb
+from samba.samdb import SamDB
+from samba.auth import system_session
+from samba.netcmd.common import _get_user_realm_domain
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option
+ )
+
+
+class cmd_delegation_show(Command):
+ """Show the delegation setting of an account."""
+
+ synopsis = "%prog <accountname> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["accountname"]
+
+ def run(self, accountname, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ # TODO once I understand how, use the domain info to naildown
+ # to the correct domain
+ (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname)
+
+ res = sam.search(expression="sAMAccountName=%s" %
+ ldb.binary_encode(cleanedaccount),
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["userAccountControl", "msDS-AllowedToDelegateTo"])
+ if len(res) == 0:
+ raise CommandError("Unable to find account name '%s'" % accountname)
+ assert(len(res) == 1)
+
+ uac = int(res[0].get("userAccountControl")[0])
+ allowed = res[0].get("msDS-AllowedToDelegateTo")
+
+ self.outf.write("Account-DN: %s\n" % str(res[0].dn))
+ self.outf.write("UF_TRUSTED_FOR_DELEGATION: %s\n"
+ % bool(uac & dsdb.UF_TRUSTED_FOR_DELEGATION))
+ self.outf.write("UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: %s\n" %
+ bool(uac & dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION))
+
+ if allowed is not None:
+ for a in allowed:
+ self.outf.write("msDS-AllowedToDelegateTo: %s\n" % a)
+
+
+class cmd_delegation_for_any_service(Command):
+ """Set/unset UF_TRUSTED_FOR_DELEGATION for an account."""
+
+ synopsis = "%prog <accountname> [(on|off)] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["accountname", "onoff"]
+
+ def run(self, accountname, onoff, credopts=None, sambaopts=None,
+ versionopts=None):
+
+ on = False
+ if onoff == "on":
+ on = True
+ elif onoff == "off":
+ on = False
+ else:
+ raise CommandError("invalid argument: '%s' (choose from 'on', 'off')" % onoff)
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ # TODO once I understand how, use the domain info to naildown
+ # to the correct domain
+ (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname)
+
+ search_filter = "sAMAccountName=%s" % ldb.binary_encode(cleanedaccount)
+ flag = dsdb.UF_TRUSTED_FOR_DELEGATION
+ try:
+ sam.toggle_userAccountFlags(search_filter, flag,
+ flags_str="Trusted-for-Delegation",
+ on=on, strict=True)
+ except Exception, err:
+ raise CommandError(err)
+
+
+class cmd_delegation_for_any_protocol(Command):
+ """Set/unset UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION (S4U2Proxy) for an account."""
+
+ synopsis = "%prog <accountname> [(on|off)] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["accountname", "onoff"]
+
+ def run(self, accountname, onoff, credopts=None, sambaopts=None,
+ versionopts=None):
+
+ on = False
+ if onoff == "on":
+ on = True
+ elif onoff == "off":
+ on = False
+ else:
+ raise CommandError("invalid argument: '%s' (choose from 'on', 'off')" % onoff)
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ # TODO once I understand how, use the domain info to naildown
+ # to the correct domain
+ (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname)
+
+ search_filter = "sAMAccountName=%s" % ldb.binary_encode(cleanedaccount)
+ flag = dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION
+ try:
+ sam.toggle_userAccountFlags(search_filter, flag,
+ flags_str="Trusted-to-Authenticate-for-Delegation",
+ on=on, strict=True)
+ except Exception, err:
+ raise CommandError(err)
+
+
+class cmd_delegation_add_service(Command):
+ """Add a service principal as msDS-AllowedToDelegateTo."""
+
+ synopsis = "%prog <accountname> <principal> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["accountname", "principal"]
+
+ def run(self, accountname, principal, credopts=None, sambaopts=None,
+ versionopts=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ # TODO once I understand how, use the domain info to naildown
+ # to the correct domain
+ (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname)
+
+ res = sam.search(expression="sAMAccountName=%s" %
+ ldb.binary_encode(cleanedaccount),
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["msDS-AllowedToDelegateTo"])
+ if len(res) == 0:
+ raise CommandError("Unable to find account name '%s'" % accountname)
+ assert(len(res) == 1)
+
+ msg = ldb.Message()
+ msg.dn = res[0].dn
+ msg["msDS-AllowedToDelegateTo"] = ldb.MessageElement([principal],
+ ldb.FLAG_MOD_ADD,
+ "msDS-AllowedToDelegateTo")
+ try:
+ sam.modify(msg)
+ except Exception, err:
+ raise CommandError(err)
+
+
+class cmd_delegation_del_service(Command):
+ """Delete a service principal as msDS-AllowedToDelegateTo."""
+
+ synopsis = "%prog <accountname> <principal> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["accountname", "principal"]
+
+ def run(self, accountname, principal, credopts=None, sambaopts=None,
+ versionopts=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ # TODO once I understand how, use the domain info to naildown
+ # to the correct domain
+ (cleanedaccount, realm, domain) = _get_user_realm_domain(accountname)
+
+ res = sam.search(expression="sAMAccountName=%s" %
+ ldb.binary_encode(cleanedaccount),
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["msDS-AllowedToDelegateTo"])
+ if len(res) == 0:
+ raise CommandError("Unable to find account name '%s'" % accountname)
+ assert(len(res) == 1)
+
+ msg = ldb.Message()
+ msg.dn = res[0].dn
+ msg["msDS-AllowedToDelegateTo"] = ldb.MessageElement([principal],
+ ldb.FLAG_MOD_DELETE,
+ "msDS-AllowedToDelegateTo")
+ try:
+ sam.modify(msg)
+ except Exception, err:
+ raise CommandError(err)
+
+
+class cmd_delegation(SuperCommand):
+ """Delegation management."""
+
+ subcommands = {}
+ subcommands["show"] = cmd_delegation_show()
+ subcommands["for-any-service"] = cmd_delegation_for_any_service()
+ subcommands["for-any-protocol"] = cmd_delegation_for_any_protocol()
+ subcommands["add-service"] = cmd_delegation_add_service()
+ subcommands["del-service"] = cmd_delegation_del_service()
diff --git a/python/samba/netcmd/dns.py b/python/samba/netcmd/dns.py
new file mode 100644
index 0000000000..c00d17ad72
--- /dev/null
+++ b/python/samba/netcmd/dns.py
@@ -0,0 +1,1186 @@
+# DNS management tool
+#
+# Copyright (C) Amitay Isaacs 2011-2012
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+from struct import pack
+from socket import inet_ntoa
+import shlex
+
+from samba.netcmd import (
+ Command,
+ CommandError,
+ Option,
+ SuperCommand,
+ )
+from samba.dcerpc import dnsp, dnsserver
+
+
+def dns_connect(server, lp, creds):
+ if server.lower() == 'localhost':
+ server = '127.0.0.1'
+ binding_str = "ncacn_ip_tcp:%s[sign]" % server
+ dns_conn = dnsserver.dnsserver(binding_str, lp, creds)
+ return dns_conn
+
+
+def bool_string(flag):
+ if flag == 0:
+ ret = 'FALSE'
+ elif flag == 1:
+ ret = 'TRUE'
+ else:
+ ret = 'UNKNOWN (0x%x)' % flag
+ return ret
+
+
+def enum_string(module, enum_defs, value):
+ ret = None
+ for e in enum_defs:
+ if value == getattr(module, e):
+ ret = e
+ break
+ if not ret:
+ ret = 'UNKNOWN (0x%x)' % value
+ return ret
+
+
+def bitmap_string(module, bitmap_defs, value):
+ ret = ''
+ for b in bitmap_defs:
+ if value & getattr(module, b):
+ ret += '%s ' % b
+ if not ret:
+ ret = 'NONE'
+ return ret
+
+
+def boot_method_string(boot_method):
+ enum_defs = [ 'DNS_BOOT_METHOD_UNINITIALIZED', 'DNS_BOOT_METHOD_FILE',
+ 'DNS_BOOT_METHOD_REGISTRY', 'DNS_BOOT_METHOD_DIRECTORY' ]
+ return enum_string(dnsserver, enum_defs, boot_method)
+
+
+def name_check_flag_string(check_flag):
+ enum_defs = [ 'DNS_ALLOW_RFC_NAMES_ONLY', 'DNS_ALLOW_NONRFC_NAMES',
+ 'DNS_ALLOW_MULTIBYTE_NAMES', 'DNS_ALLOW_ALL_NAMES' ]
+ return enum_string(dnsserver, enum_defs, check_flag)
+
+
+def zone_type_string(zone_type):
+ enum_defs = [ 'DNS_ZONE_TYPE_CACHE', 'DNS_ZONE_TYPE_PRIMARY',
+ 'DNS_ZONE_TYPE_SECONDARY', 'DNS_ZONE_TYPE_STUB',
+ 'DNS_ZONE_TYPE_FORWARDER', 'DNS_ZONE_TYPE_SECONDARY_CACHE' ]
+ return enum_string(dnsp, enum_defs, zone_type)
+
+
+def zone_update_string(zone_update):
+ enum_defs = [ 'DNS_ZONE_UPDATE_OFF', 'DNS_ZONE_UPDATE_SECURE',
+ 'DNS_ZONE_UPDATE_SECURE' ]
+ return enum_string(dnsp, enum_defs, zone_update)
+
+
+def zone_secondary_security_string(security):
+ enum_defs = [ 'DNS_ZONE_SECSECURE_NO_SECURITY', 'DNS_ZONE_SECSECURE_NS_ONLY',
+ 'DNS_ZONE_SECSECURE_LIST_ONLY', 'DNS_ZONE_SECSECURE_NO_XFER' ]
+ return enum_string(dnsserver, enum_defs, security)
+
+
+def zone_notify_level_string(notify_level):
+ enum_defs = [ 'DNS_ZONE_NOTIFY_OFF', 'DNS_ZONE_NOTIFY_ALL_SECONDARIES',
+ 'DNS_ZONE_NOTIFY_LIST_ONLY' ]
+ return enum_string(dnsserver, enum_defs, notify_level)
+
+
+def dp_flags_string(dp_flags):
+ bitmap_defs = [ 'DNS_DP_AUTOCREATED', 'DNS_DP_LEGACY', 'DNS_DP_DOMAIN_DEFAULT',
+ 'DNS_DP_FOREST_DEFAULT', 'DNS_DP_ENLISTED', 'DNS_DP_DELETED' ]
+ return bitmap_string(dnsserver, bitmap_defs, dp_flags)
+
+
+def zone_flags_string(flags):
+ bitmap_defs = [ 'DNS_RPC_ZONE_PAUSED', 'DNS_RPC_ZONE_SHUTDOWN',
+ 'DNS_RPC_ZONE_REVERSE', 'DNS_RPC_ZONE_AUTOCREATED',
+ 'DNS_RPC_ZONE_DSINTEGRATED', 'DNS_RPC_ZONE_AGING',
+ 'DNS_RPC_ZONE_UPDATE_UNSECURE', 'DNS_RPC_ZONE_UPDATE_SECURE',
+ 'DNS_RPC_ZONE_READONLY']
+ return bitmap_string(dnsserver, bitmap_defs, flags)
+
+
+def ip4_array_string(array):
+ ret = []
+ if not array:
+ return ret
+ for i in xrange(array.AddrCount):
+ addr = '%s' % inet_ntoa(pack('i', array.AddrArray[i]))
+ ret.append(addr)
+ return ret
+
+
+def dns_addr_array_string(array):
+ ret = []
+ if not array:
+ return ret
+ for i in xrange(array.AddrCount):
+ if array.AddrArray[i].MaxSa[0] == 0x02:
+ addr = '%d.%d.%d.%d (%d)' % \
+ tuple(array.AddrArray[i].MaxSa[4:8] + [array.AddrArray[i].MaxSa[3]])
+ elif array.AddrArray[i].MaxSa[0] == 0x17:
+ addr = '%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x (%d)' % \
+ tuple(array.AddrArray[i].MaxSa[4:20] + [array.AddrArray[i].MaxSa[3]])
+ else:
+ addr = 'UNKNOWN'
+ ret.append(addr)
+ return ret
+
+
+def dns_type_flag(rec_type):
+ rtype = rec_type.upper()
+ if rtype == 'A':
+ record_type = dnsp.DNS_TYPE_A
+ elif rtype == 'AAAA':
+ record_type = dnsp.DNS_TYPE_AAAA
+ elif rtype == 'PTR':
+ record_type = dnsp.DNS_TYPE_PTR
+ elif rtype == 'NS':
+ record_type = dnsp.DNS_TYPE_NS
+ elif rtype == 'CNAME':
+ record_type = dnsp.DNS_TYPE_CNAME
+ elif rtype == 'SOA':
+ record_type = dnsp.DNS_TYPE_SOA
+ elif rtype == 'MX':
+ record_type = dnsp.DNS_TYPE_MX
+ elif rtype == 'SRV':
+ record_type = dnsp.DNS_TYPE_SRV
+ elif rtype == 'TXT':
+ record_type = dnsp.DNS_TYPE_TXT
+ elif rtype == 'ALL':
+ record_type = dnsp.DNS_TYPE_ALL
+ else:
+ raise CommandError('Unknown type of DNS record %s' % rec_type)
+ return record_type
+
+
+def dns_client_version(cli_version):
+ version = cli_version.upper()
+ if version == 'W2K':
+ client_version = dnsserver.DNS_CLIENT_VERSION_W2K
+ elif version == 'DOTNET':
+ client_version = dnsserver.DNS_CLIENT_VERSION_DOTNET
+ elif version == 'LONGHORN':
+ client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+ else:
+ raise CommandError('Unknown client version %s' % cli_version)
+ return client_version
+
+
+def print_serverinfo(outf, typeid, serverinfo):
+ outf.write(' dwVersion : 0x%x\n' % serverinfo.dwVersion)
+ outf.write(' fBootMethod : %s\n' % boot_method_string(serverinfo.fBootMethod))
+ outf.write(' fAdminConfigured : %s\n' % bool_string(serverinfo.fAdminConfigured))
+ outf.write(' fAllowUpdate : %s\n' % bool_string(serverinfo.fAllowUpdate))
+ outf.write(' fDsAvailable : %s\n' % bool_string(serverinfo.fDsAvailable))
+ outf.write(' pszServerName : %s\n' % serverinfo.pszServerName)
+ outf.write(' pszDsContainer : %s\n' % serverinfo.pszDsContainer)
+
+ if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO:
+ outf.write(' aipServerAddrs : %s\n' %
+ ip4_array_string(serverinfo.aipServerAddrs))
+ outf.write(' aipListenAddrs : %s\n' %
+ ip4_array_string(serverinfo.aipListenAddrs))
+ outf.write(' aipForwarders : %s\n' %
+ ip4_array_string(serverinfo.aipForwarders))
+ else:
+ outf.write(' aipServerAddrs : %s\n' %
+ dns_addr_array_string(serverinfo.aipServerAddrs))
+ outf.write(' aipListenAddrs : %s\n' %
+ dns_addr_array_string(serverinfo.aipListenAddrs))
+ outf.write(' aipForwarders : %s\n' %
+ dns_addr_array_string(serverinfo.aipForwarders))
+
+ outf.write(' dwLogLevel : %d\n' % serverinfo.dwLogLevel)
+ outf.write(' dwDebugLevel : %d\n' % serverinfo.dwDebugLevel)
+ outf.write(' dwForwardTimeout : %d\n' % serverinfo.dwForwardTimeout)
+ outf.write(' dwRpcPrototol : 0x%x\n' % serverinfo.dwRpcProtocol)
+ outf.write(' dwNameCheckFlag : %s\n' % name_check_flag_string(serverinfo.dwNameCheckFlag))
+ outf.write(' cAddressAnswerLimit : %d\n' % serverinfo.cAddressAnswerLimit)
+ outf.write(' dwRecursionRetry : %d\n' % serverinfo.dwRecursionRetry)
+ outf.write(' dwRecursionTimeout : %d\n' % serverinfo.dwRecursionTimeout)
+ outf.write(' dwMaxCacheTtl : %d\n' % serverinfo.dwMaxCacheTtl)
+ outf.write(' dwDsPollingInterval : %d\n' % serverinfo.dwDsPollingInterval)
+ outf.write(' dwScavengingInterval : %d\n' % serverinfo.dwScavengingInterval)
+ outf.write(' dwDefaultRefreshInterval : %d\n' % serverinfo.dwDefaultRefreshInterval)
+ outf.write(' dwDefaultNoRefreshInterval : %d\n' % serverinfo.dwDefaultNoRefreshInterval)
+ outf.write(' fAutoReverseZones : %s\n' % bool_string(serverinfo.fAutoReverseZones))
+ outf.write(' fAutoCacheUpdate : %s\n' % bool_string(serverinfo.fAutoCacheUpdate))
+ outf.write(' fRecurseAfterForwarding : %s\n' % bool_string(serverinfo.fRecurseAfterForwarding))
+ outf.write(' fForwardDelegations : %s\n' % bool_string(serverinfo.fForwardDelegations))
+ outf.write(' fNoRecursion : %s\n' % bool_string(serverinfo.fNoRecursion))
+ outf.write(' fSecureResponses : %s\n' % bool_string(serverinfo.fSecureResponses))
+ outf.write(' fRoundRobin : %s\n' % bool_string(serverinfo.fRoundRobin))
+ outf.write(' fLocalNetPriority : %s\n' % bool_string(serverinfo.fLocalNetPriority))
+ outf.write(' fBindSecondaries : %s\n' % bool_string(serverinfo.fBindSecondaries))
+ outf.write(' fWriteAuthorityNs : %s\n' % bool_string(serverinfo.fWriteAuthorityNs))
+ outf.write(' fStrictFileParsing : %s\n' % bool_string(serverinfo.fStrictFileParsing))
+ outf.write(' fLooseWildcarding : %s\n' % bool_string(serverinfo.fLooseWildcarding))
+ outf.write(' fDefaultAgingState : %s\n' % bool_string(serverinfo.fDefaultAgingState))
+
+ if typeid != dnsserver.DNSSRV_TYPEID_SERVER_INFO_W2K:
+ outf.write(' dwRpcStructureVersion : 0x%x\n' % serverinfo.dwRpcStructureVersion)
+ outf.write(' aipLogFilter : %s\n' % dns_addr_array_string(serverinfo.aipLogFilter))
+ outf.write(' pwszLogFilePath : %s\n' % serverinfo.pwszLogFilePath)
+ outf.write(' pszDomainName : %s\n' % serverinfo.pszDomainName)
+ outf.write(' pszForestName : %s\n' % serverinfo.pszForestName)
+ outf.write(' pszDomainDirectoryPartition : %s\n' % serverinfo.pszDomainDirectoryPartition)
+ outf.write(' pszForestDirectoryPartition : %s\n' % serverinfo.pszForestDirectoryPartition)
+
+ outf.write(' dwLocalNetPriorityNetMask : 0x%x\n' % serverinfo.dwLocalNetPriorityNetMask)
+ outf.write(' dwLastScavengeTime : %d\n' % serverinfo.dwLastScavengeTime)
+ outf.write(' dwEventLogLevel : %d\n' % serverinfo.dwEventLogLevel)
+ outf.write(' dwLogFileMaxSize : %d\n' % serverinfo.dwLogFileMaxSize)
+ outf.write(' dwDsForestVersion : %d\n' % serverinfo.dwDsForestVersion)
+ outf.write(' dwDsDomainVersion : %d\n' % serverinfo.dwDsDomainVersion)
+ outf.write(' dwDsDsaVersion : %d\n' % serverinfo.dwDsDsaVersion)
+
+ if typeid == dnsserver.DNSSRV_TYPEID_SERVER_INFO:
+ outf.write(' fReadOnlyDC : %s\n' % bool_string(serverinfo.fReadOnlyDC))
+
+
+def print_zoneinfo(outf, typeid, zoneinfo):
+ outf.write(' pszZoneName : %s\n' % zoneinfo.pszZoneName)
+ outf.write(' dwZoneType : %s\n' % zone_type_string(zoneinfo.dwZoneType))
+ outf.write(' fReverse : %s\n' % bool_string(zoneinfo.fReverse))
+ outf.write(' fAllowUpdate : %s\n' % zone_update_string(zoneinfo.fAllowUpdate))
+ outf.write(' fPaused : %s\n' % bool_string(zoneinfo.fPaused))
+ outf.write(' fShutdown : %s\n' % bool_string(zoneinfo.fShutdown))
+ outf.write(' fAutoCreated : %s\n' % bool_string(zoneinfo.fAutoCreated))
+ outf.write(' fUseDatabase : %s\n' % bool_string(zoneinfo.fUseDatabase))
+ outf.write(' pszDataFile : %s\n' % zoneinfo.pszDataFile)
+ if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
+ outf.write(' aipMasters : %s\n' %
+ ip4_array_string(zoneinfo.aipMasters))
+ else:
+ outf.write(' aipMasters : %s\n' %
+ dns_addr_array_string(zoneinfo.aipMasters))
+ outf.write(' fSecureSecondaries : %s\n' % zone_secondary_security_string(zoneinfo.fSecureSecondaries))
+ outf.write(' fNotifyLevel : %s\n' % zone_notify_level_string(zoneinfo.fNotifyLevel))
+ if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
+ outf.write(' aipSecondaries : %s\n' %
+ ip4_array_string(zoneinfo.aipSecondaries))
+ outf.write(' aipNotify : %s\n' %
+ ip4_array_string(zoneinfo.aipNotify))
+ else:
+ outf.write(' aipSecondaries : %s\n' %
+ dns_addr_array_string(zoneinfo.aipSecondaries))
+ outf.write(' aipNotify : %s\n' %
+ dns_addr_array_string(zoneinfo.aipNotify))
+ outf.write(' fUseWins : %s\n' % bool_string(zoneinfo.fUseWins))
+ outf.write(' fUseNbstat : %s\n' % bool_string(zoneinfo.fUseNbstat))
+ outf.write(' fAging : %s\n' % bool_string(zoneinfo.fAging))
+ outf.write(' dwNoRefreshInterval : %d\n' % zoneinfo.dwNoRefreshInterval)
+ outf.write(' dwRefreshInterval : %d\n' % zoneinfo.dwRefreshInterval)
+ outf.write(' dwAvailForScavengeTime : %d\n' % zoneinfo.dwAvailForScavengeTime)
+ if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
+ outf.write(' aipScavengeServers : %s\n' %
+ ip4_array_string(zoneinfo.aipScavengeServers))
+ else:
+ outf.write(' aipScavengeServers : %s\n' %
+ dns_addr_array_string(zoneinfo.aipScavengeServers))
+
+ if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO_W2K:
+ outf.write(' dwRpcStructureVersion : 0x%x\n' % zoneinfo.dwRpcStructureVersion)
+ outf.write(' dwForwarderTimeout : %d\n' % zoneinfo.dwForwarderTimeout)
+ outf.write(' fForwarderSlave : %d\n' % zoneinfo.fForwarderSlave)
+ if typeid != dnsserver.DNSSRV_TYPEID_ZONE_INFO:
+ outf.write(' aipLocalMasters : %s\n' %
+ ip4_array_string(zoneinfo.aipLocalMasters))
+ else:
+ outf.write(' aipLocalMasters : %s\n' %
+ dns_addr_array_string(zoneinfo.aipLocalMasters))
+ outf.write(' dwDpFlags : %s\n' % dp_flags_string(zoneinfo.dwDpFlags))
+ outf.write(' pszDpFqdn : %s\n' % zoneinfo.pszDpFqdn)
+ outf.write(' pwszZoneDn : %s\n' % zoneinfo.pwszZoneDn)
+ outf.write(' dwLastSuccessfulSoaCheck : %d\n' % zoneinfo.dwLastSuccessfulSoaCheck)
+ outf.write(' dwLastSuccessfulXfr : %d\n' % zoneinfo.dwLastSuccessfulXfr)
+
+ if typeid == dnsserver.DNSSRV_TYPEID_ZONE_INFO:
+ outf.write(' fQueuedForBackgroundLoad : %s\n' % bool_string(zoneinfo.fQueuedForBackgroundLoad))
+ outf.write(' fBackgroundLoadInProgress : %s\n' % bool_string(zoneinfo.fBackgroundLoadInProgress))
+ outf.write(' fReadOnlyZone : %s\n' % bool_string(zoneinfo.fReadOnlyZone))
+ outf.write(' dwLastXfrAttempt : %d\n' % zoneinfo.dwLastXfrAttempt)
+ outf.write(' dwLastXfrResult : %d\n' % zoneinfo.dwLastXfrResult)
+
+
+def print_zone(outf, typeid, zone):
+ outf.write(' pszZoneName : %s\n' % zone.pszZoneName)
+ outf.write(' Flags : %s\n' % zone_flags_string(zone.Flags))
+ outf.write(' ZoneType : %s\n' % zone_type_string(zone.ZoneType))
+ outf.write(' Version : %s\n' % zone.Version)
+
+ if typeid != dnsserver.DNSSRV_TYPEID_ZONE_W2K:
+ outf.write(' dwDpFlags : %s\n' % dp_flags_string(zone.dwDpFlags))
+ outf.write(' pszDpFqdn : %s\n' % zone.pszDpFqdn)
+
+
+def print_enumzones(outf, typeid, zones):
+ outf.write(' %d zone(s) found\n' % zones.dwZoneCount)
+ for zone in zones.ZoneArray:
+ outf.write('\n')
+ print_zone(outf, typeid, zone)
+
+
+def print_dns_record(outf, rec):
+ if rec.wType == dnsp.DNS_TYPE_A:
+ mesg = 'A: %s' % (rec.data)
+ elif rec.wType == dnsp.DNS_TYPE_AAAA:
+ mesg = 'AAAA: %s' % (rec.data)
+ elif rec.wType == dnsp.DNS_TYPE_PTR:
+ mesg = 'PTR: %s' % (rec.data.str)
+ elif rec.wType == dnsp.DNS_TYPE_NS:
+ mesg = 'NS: %s' % (rec.data.str)
+ elif rec.wType == dnsp.DNS_TYPE_CNAME:
+ mesg = 'CNAME: %s' % (rec.data.str)
+ elif rec.wType == dnsp.DNS_TYPE_SOA:
+ mesg = 'SOA: serial=%d, refresh=%d, retry=%d, expire=%d, ns=%s, email=%s' % (
+ rec.data.dwSerialNo,
+ rec.data.dwRefresh,
+ rec.data.dwRetry,
+ rec.data.dwExpire,
+ rec.data.NamePrimaryServer.str,
+ rec.data.ZoneAdministratorEmail.str)
+ elif rec.wType == dnsp.DNS_TYPE_MX:
+ mesg = 'MX: %s (%d)' % (rec.data.nameExchange.str, rec.data.wPreference)
+ elif rec.wType == dnsp.DNS_TYPE_SRV:
+ mesg = 'SRV: %s (%d, %d, %d)' % (rec.data.nameTarget.str, rec.data.wPort,
+ rec.data.wPriority, rec.data.wWeight)
+ elif rec.wType == dnsp.DNS_TYPE_TXT:
+ slist = ['"%s"' % name.str for name in rec.data.str]
+ mesg = 'TXT: %s' % ','.join(slist)
+ else:
+ mesg = 'Unknown: '
+ outf.write(' %s (flags=%x, serial=%d, ttl=%d)\n' % (
+ mesg, rec.dwFlags, rec.dwSerial, rec.dwTtlSeconds))
+
+
+def print_dnsrecords(outf, records):
+ for rec in records.rec:
+ outf.write(' Name=%s, Records=%d, Children=%d\n' % (
+ rec.dnsNodeName.str,
+ rec.wRecordCount,
+ rec.dwChildCount))
+ for dns_rec in rec.records:
+ print_dns_record(outf, dns_rec)
+
+
+#
+# Always create a copy of strings when creating DNS_RPC_RECORDs
+# to overcome the bug in pidl generated python bindings.
+#
+
+class ARecord(dnsserver.DNS_RPC_RECORD):
+ def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
+ node_flag=0):
+ super(ARecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_A
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self._ip_addr = ip_addr[:]
+ self.data = self._ip_addr
+
+
+class AAAARecord(dnsserver.DNS_RPC_RECORD):
+
+ def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
+ node_flag=0):
+ super(AAAARecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_AAAA
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self._ip6_addr = ip6_addr[:]
+ self.data = self._ip6_addr
+
+
+class PTRRecord(dnsserver.DNS_RPC_RECORD):
+
+ def __init__(self, ptr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
+ node_flag=0):
+ super(PTRRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_PTR
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtleSeconds = ttl
+ self._ptr = ptr[:]
+ ptr_name = dnsserver.DNS_RPC_NAME()
+ ptr_name.str = self._ptr
+ ptr_name.len = len(ptr)
+ self.data = ptr_name
+
+
+class CNameRecord(dnsserver.DNS_RPC_RECORD):
+
+ def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
+ node_flag=0):
+ super(CNameRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_CNAME
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self._cname = cname[:]
+ cname_name = dnsserver.DNS_RPC_NAME()
+ cname_name.str = self._cname
+ cname_name.len = len(cname)
+ self.data = cname_name
+
+
+class NSRecord(dnsserver.DNS_RPC_RECORD):
+
+ def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
+ node_flag=0):
+ super(NSRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_NS
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self._dns_server = dns_server[:]
+ ns = dnsserver.DNS_RPC_NAME()
+ ns.str = self._dns_server
+ ns.len = len(dns_server)
+ self.data = ns
+
+
+class MXRecord(dnsserver.DNS_RPC_RECORD):
+
+ def __init__(self, mail_server, preference, serial=1, ttl=900,
+ rank=dnsp.DNS_RANK_ZONE, node_flag=0):
+ super(MXRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_MX
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self._mail_server = mail_server[:]
+ mx = dnsserver.DNS_RPC_RECORD_NAME_PREFERENCE()
+ mx.wPreference = preference
+ mx.nameExchange.str = self._mail_server
+ mx.nameExchange.len = len(mail_server)
+ self.data = mx
+
+
+class SOARecord(dnsserver.DNS_RPC_RECORD):
+
+ def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
+ expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE,
+ node_flag=dnsp.DNS_RPC_FLAG_AUTH_ZONE_ROOT):
+ super(SOARecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_SOA
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self._mname = mname[:]
+ self._rname = rname[:]
+ soa = dnsserver.DNS_RPC_RECORD_SOA()
+ soa.dwSerialNo = serial
+ soa.dwRefresh = refresh
+ soa.dwRetry = retry
+ soa.dwExpire = expire
+ soa.NamePrimaryServer.str = self._mname
+ soa.NamePrimaryServer.len = len(mname)
+ soa.ZoneAdministratorEmail.str = self._rname
+ soa.ZoneAdministratorEmail.len = len(rname)
+ self.data = soa
+
+
+class SRVRecord(dnsserver.DNS_RPC_RECORD):
+
+ def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900,
+ rank=dnsp.DNS_RANK_ZONE, node_flag=0):
+ super(SRVRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_SRV
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self._target = target[:]
+ srv = dnsserver.DNS_RPC_RECORD_SRV()
+ srv.wPriority = priority
+ srv.wWeight = weight
+ srv.wPort = port
+ srv.nameTarget.str = self._target
+ srv.nameTarget.len = len(target)
+ self.data = srv
+
+
+class TXTRecord(dnsserver.DNS_RPC_RECORD):
+
+ def __init__(self, slist, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE,
+ node_flag=0):
+ super(TXTRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_TXT
+ self.dwFlags = rank | node_flag
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self._slist = []
+ for s in slist:
+ self._slist.append(s[:])
+ names = []
+ for s in self._slist:
+ name = dnsserver.DNS_RPC_NAME()
+ name.str = s
+ name.len = len(s)
+ names.append(name)
+ txt = dnsserver.DNS_RPC_RECORD_STRING()
+ txt.count = len(slist)
+ txt.str = names
+ self.data = txt
+
+
+# Convert data into a dns record
+def data_to_dns_record(record_type, data):
+ if record_type == dnsp.DNS_TYPE_A:
+ rec = ARecord(data)
+ elif record_type == dnsp.DNS_TYPE_AAAA:
+ rec = AAAARecord(data)
+ elif record_type == dnsp.DNS_TYPE_PTR:
+ rec = PTRRecord(data)
+ elif record_type == dnsp.DNS_TYPE_CNAME:
+ rec = CNameRecord(data)
+ elif record_type == dnsp.DNS_TYPE_NS:
+ rec = NSRecord(data)
+ elif record_type == dnsp.DNS_TYPE_MX:
+ tmp = data.split(' ')
+ if len(tmp) != 2:
+ raise CommandError('Data requires 2 elements - mail_server, preference')
+ mail_server = tmp[0]
+ preference = int(tmp[1])
+ rec = MXRecord(mail_server, preference)
+ elif record_type == dnsp.DNS_TYPE_SRV:
+ tmp = data.split(' ')
+ if len(tmp) != 4:
+ raise CommandError('Data requires 4 elements - server, port, priority, weight')
+ server = tmp[0]
+ port = int(tmp[1])
+ priority = int(tmp[2])
+ weight = int(tmp[3])
+ rec = SRVRecord(server, port, priority=priority, weight=weight)
+ elif record_type == dnsp.DNS_TYPE_SOA:
+ tmp = data.split(' ')
+ if len(tmp) != 7:
+ raise CommandError('Data requires 7 elements - nameserver, email, serial, '
+ 'refresh, retry, expire, minimumttl')
+ nameserver = tmp[0]
+ email = tmp[1]
+ serial = int(tmp[2])
+ refresh = int(tmp[3])
+ retry = int(tmp[4])
+ expire = int(tmp[5])
+ minimum = int(tmp[6])
+ rec = SOARecord(nameserver, email, serial=serial, refresh=refresh,
+ retry=retry, expire=expire, minimum=minimum)
+ elif record_type == dnsp.DNS_TYPE_TXT:
+ slist = shlex.split(data)
+ rec = TXTRecord(slist)
+ else:
+ raise CommandError('Unsupported record type')
+ return rec
+
+
+# Match dns name (of type DNS_RPC_NAME)
+def dns_name_equal(n1, n2):
+ return n1.str.rstrip('.').lower() == n2.str.rstrip('.').lower()
+
+
+# Match a dns record with specified data
+def dns_record_match(dns_conn, server, zone, name, record_type, data):
+ urec = data_to_dns_record(record_type, data)
+
+ select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
+
+ try:
+ buflen, res = dns_conn.DnssrvEnumRecords2(
+ dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, zone, name, None,
+ record_type, select_flags, None, None)
+ except RuntimeError, e:
+ return None
+
+ if not res or res.count == 0:
+ return None
+
+ rec_match = None
+ for rec in res.rec[0].records:
+ if rec.wType != record_type:
+ continue
+
+ found = False
+ if record_type == dnsp.DNS_TYPE_A:
+ if rec.data == urec.data:
+ found = True
+ elif record_type == dnsp.DNS_TYPE_AAAA:
+ if rec.data == urec.data:
+ found = True
+ elif record_type == dnsp.DNS_TYPE_PTR:
+ if dns_name_equal(rec.data, urec.data):
+ found = True
+ elif record_type == dnsp.DNS_TYPE_CNAME:
+ if dns_name_equal(rec.data, urec.data):
+ found = True
+ elif record_type == dnsp.DNS_TYPE_NS:
+ if dns_name_equal(rec.data, urec.data):
+ found = True
+ elif record_type == dnsp.DNS_TYPE_MX:
+ if dns_name_equal(rec.data.nameExchange, urec.data.nameExchange) and \
+ rec.data.wPreference == urec.data.wPreference:
+ found = True
+ elif record_type == dnsp.DNS_TYPE_SRV:
+ if rec.data.wPriority == urec.data.wPriority and \
+ rec.data.wWeight == urec.data.wWeight and \
+ rec.data.wPort == urec.data.wPort and \
+ dns_name_equal(rec.data.nameTarget, urec.data.nameTarget):
+ found = True
+ elif record_type == dnsp.DNS_TYPE_SOA:
+ if rec.data.dwSerialNo == urec.data.dwSerialNo and \
+ rec.data.dwRefresh == urec.data.dwRefresh and \
+ rec.data.dwRetry == urec.data.dwRetry and \
+ rec.data.dwExpire == urec.data.dwExpire and \
+ rec.data.dwMinimumTtl == urec.data.dwMinimumTtl and \
+ dns_name_equal(rec.data.NamePrimaryServer,
+ urec.data.NamePrimaryServer) and \
+ dns_name_equal(rec.data.ZoneAdministratorEmail,
+ urec.data.ZoneAdministratorEmail):
+ found = True
+ elif record_type == dnsp.DNS_TYPE_TXT:
+ if rec.data.count == urec.data.count:
+ found = True
+ for i in xrange(rec.data.count):
+ found = found and \
+ (rec.data.str[i].str == urec.data.str[i].str)
+
+ if found:
+ rec_match = rec
+ break
+
+ return rec_match
+
+
+class cmd_serverinfo(Command):
+ """Query for Server information."""
+
+ synopsis = '%prog <server> [options]'
+
+ takes_args = [ 'server' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option('--client-version', help='Client Version',
+ default='longhorn', metavar='w2k|dotnet|longhorn',
+ choices=['w2k','dotnet','longhorn'], dest='cli_ver'),
+ ]
+
+ def run(self, server, cli_ver, sambaopts=None, credopts=None,
+ versionopts=None):
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ client_version = dns_client_version(cli_ver)
+
+ typeid, res = dns_conn.DnssrvQuery2(client_version, 0, server,
+ None, 'ServerInfo')
+ print_serverinfo(self.outf, typeid, res)
+
+
+class cmd_zoneinfo(Command):
+ """Query for zone information."""
+
+ synopsis = '%prog <server> <zone> [options]'
+
+ takes_args = [ 'server', 'zone' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option('--client-version', help='Client Version',
+ default='longhorn', metavar='w2k|dotnet|longhorn',
+ choices=['w2k','dotnet','longhorn'], dest='cli_ver'),
+ ]
+
+ def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
+ versionopts=None):
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ client_version = dns_client_version(cli_ver)
+
+ typeid, res = dns_conn.DnssrvQuery2(client_version, 0, server, zone,
+ 'ZoneInfo')
+ print_zoneinfo(self.outf, typeid, res)
+
+
+class cmd_zonelist(Command):
+ """Query for zones."""
+
+ synopsis = '%prog <server> [options]'
+
+ takes_args = [ 'server' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option('--client-version', help='Client Version',
+ default='longhorn', metavar='w2k|dotnet|longhorn',
+ choices=['w2k','dotnet','longhorn'], dest='cli_ver'),
+ Option('--primary', help='List primary zones (default)',
+ action='store_true', dest='primary'),
+ Option('--secondary', help='List secondary zones',
+ action='store_true', dest='secondary'),
+ Option('--cache', help='List cached zones',
+ action='store_true', dest='cache'),
+ Option('--auto', help='List automatically created zones',
+ action='store_true', dest='auto'),
+ Option('--forward', help='List forward zones',
+ action='store_true', dest='forward'),
+ Option('--reverse', help='List reverse zones',
+ action='store_true', dest='reverse'),
+ Option('--ds', help='List directory integrated zones',
+ action='store_true', dest='ds'),
+ Option('--non-ds', help='List non-directory zones',
+ action='store_true', dest='nonds')
+ ]
+
+ def run(self, server, cli_ver, primary=False, secondary=False, cache=False,
+ auto=False, forward=False, reverse=False, ds=False, nonds=False,
+ sambaopts=None, credopts=None, versionopts=None):
+ request_filter = 0
+
+ if primary:
+ request_filter |= dnsserver.DNS_ZONE_REQUEST_PRIMARY
+ if secondary:
+ request_filter |= dnsserver.DNS_ZONE_REQUEST_SECONDARY
+ if cache:
+ request_filter |= dnsserver.DNS_ZONE_REQUEST_CACHE
+ if auto:
+ request_filter |= dnsserver.DNS_ZONE_REQUEST_AUTO
+ if forward:
+ request_filter |= dnsserver.DNS_ZONE_REQUEST_FORWARD
+ if reverse:
+ request_filter |= dnsserver.DNS_ZONE_REQUEST_REVERSE
+ if ds:
+ request_filter |= dnsserver.DNS_ZONE_REQUEST_DS
+ if nonds:
+ request_filter |= dnsserver.DNS_ZONE_REQUEST_NON_DS
+
+ if request_filter == 0:
+ request_filter = dnsserver.DNS_ZONE_REQUEST_PRIMARY
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ client_version = dns_client_version(cli_ver)
+
+ typeid, res = dns_conn.DnssrvComplexOperation2(client_version,
+ 0, server, None,
+ 'EnumZones',
+ dnsserver.DNSSRV_TYPEID_DWORD,
+ request_filter)
+
+ if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
+ typeid = dnsserver.DNSSRV_TYPEID_ZONE_W2K
+ else:
+ typeid = dnsserver.DNSSRV_TYPEID_ZONE
+ print_enumzones(self.outf, typeid, res)
+
+
+class cmd_zonecreate(Command):
+ """Create a zone."""
+
+ synopsis = '%prog <server> <zone> [options]'
+
+ takes_args = [ 'server', 'zone' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option('--client-version', help='Client Version',
+ default='longhorn', metavar='w2k|dotnet|longhorn',
+ choices=['w2k','dotnet','longhorn'], dest='cli_ver')
+ ]
+
+ def run(self, server, zone, cli_ver, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ zone = zone.lower()
+
+ client_version = dns_client_version(cli_ver)
+ if client_version == dnsserver.DNS_CLIENT_VERSION_W2K:
+ typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE_W2K
+ zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_W2K()
+ zone_create_info.pszZoneName = zone
+ zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
+ zone_create_info.fAllowUpdate = dnsp.DNS_ZONE_UPDATE_SECURE
+ zone_create_info.fAging = 0
+ elif client_version == dnsserver.DNS_CLIENT_VERSION_DOTNET:
+ typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE_DOTNET
+ zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_DOTNET()
+ zone_create_info.pszZoneName = zone
+ zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
+ zone_create_info.fAllowUpdate = dnsp.DNS_ZONE_UPDATE_SECURE
+ zone_create_info.fAging = 0
+ zone_create_info.dwDpFlags = dnsserver.DNS_DP_DOMAIN_DEFAULT
+ else:
+ typeid = dnsserver.DNSSRV_TYPEID_ZONE_CREATE
+ zone_create_info = dnsserver.DNS_RPC_ZONE_CREATE_INFO_LONGHORN()
+ zone_create_info.pszZoneName = zone
+ zone_create_info.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
+ zone_create_info.fAllowUpdate = dnsp.DNS_ZONE_UPDATE_SECURE
+ zone_create_info.fAging = 0
+ zone_create_info.dwDpFlags = dnsserver.DNS_DP_DOMAIN_DEFAULT
+
+ res = dns_conn.DnssrvOperation2(client_version, 0, server, None,
+ 0, 'ZoneCreate', typeid,
+ zone_create_info)
+ self.outf.write('Zone %s created successfully\n' % zone)
+
+
+class cmd_zonedelete(Command):
+ """Delete a zone."""
+
+ synopsis = '%prog <server> <zone> [options]'
+
+ takes_args = [ 'server', 'zone' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ def run(self, server, zone, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ zone = zone.lower()
+ res = dns_conn.DnssrvOperation2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+ 0, server, zone, 0, 'DeleteZoneFromDs',
+ dnsserver.DNSSRV_TYPEID_NULL,
+ None)
+ self.outf.write('Zone %s delete successfully\n' % zone)
+
+
+class cmd_query(Command):
+ """Query a name."""
+
+ synopsis = '%prog <server> <zone> <name> <A|AAAA|CNAME|MX|NS|SOA|SRV|TXT|ALL> [options]'
+
+ takes_args = [ 'server', 'zone', 'name', 'rtype' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option('--authority', help='Search authoritative records (default)',
+ action='store_true', dest='authority'),
+ Option('--cache', help='Search cached records',
+ action='store_true', dest='cache'),
+ Option('--glue', help='Search glue records',
+ action='store_true', dest='glue'),
+ Option('--root', help='Search root hints',
+ action='store_true', dest='root'),
+ Option('--additional', help='List additional records',
+ action='store_true', dest='additional'),
+ Option('--no-children', help='Do not list children',
+ action='store_true', dest='no_children'),
+ Option('--only-children', help='List only children',
+ action='store_true', dest='only_children')
+ ]
+
+ def run(self, server, zone, name, rtype, authority=False, cache=False,
+ glue=False, root=False, additional=False, no_children=False,
+ only_children=False, sambaopts=None, credopts=None,
+ versionopts=None):
+ record_type = dns_type_flag(rtype)
+
+ select_flags = 0
+ if authority:
+ select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
+ if cache:
+ select_flags |= dnsserver.DNS_RPC_VIEW_CACHE_DATA
+ if glue:
+ select_flags |= dnsserver.DNS_RPC_VIEW_GLUE_DATA
+ if root:
+ select_flags |= dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA
+ if additional:
+ select_flags |= dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA
+ if no_children:
+ select_flags |= dnsserver.DNS_RPC_VIEW_NO_CHILDREN
+ if only_children:
+ select_flags |= dnsserver.DNS_RPC_VIEW_ONLY_CHILDREN
+
+ if select_flags == 0:
+ select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
+
+ if select_flags == dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA:
+ self.outf.write('Specify either --authority or --root along with --additional.\n')
+ self.outf.write('Assuming --authority.\n')
+ select_flags |= dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ buflen, res = dns_conn.DnssrvEnumRecords2(
+ dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, zone, name,
+ None, record_type, select_flags, None, None)
+ print_dnsrecords(self.outf, res)
+
+
+class cmd_roothints(Command):
+ """Query root hints."""
+
+ synopsis = '%prog <server> [<name>] [options]'
+
+ takes_args = [ 'server', 'name?' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ def run(self, server, name='.', sambaopts=None, credopts=None,
+ versionopts=None):
+ record_type = dnsp.DNS_TYPE_NS
+ select_flags = (dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA |
+ dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA)
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ buflen, res = dns_conn.DnssrvEnumRecords2(
+ dnsserver.DNS_CLIENT_VERSION_LONGHORN, 0, server, '..RootHints',
+ name, None, record_type, select_flags, None, None)
+ print_dnsrecords(self.outf, res)
+
+
+class cmd_add_record(Command):
+ """Add a DNS record
+
+ For each type data contents are as follows:
+ A ipv4_address_string
+ AAAA ipv6_address_string
+ PTR fqdn_string
+ CNAME fqdn_string
+ NS fqdn_string
+ MX "fqdn_string preference"
+ SRV "fqdn_string port priority weight"
+ TXT "'string1' 'string2' ..."
+ """
+
+ synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>'
+
+ takes_args = [ 'server', 'zone', 'name', 'rtype', 'data' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ def run(self, server, zone, name, rtype, data, sambaopts=None,
+ credopts=None, versionopts=None):
+
+ if rtype.upper() not in ('A','AAAA','PTR','CNAME','NS','MX','SRV','TXT'):
+ raise CommandError('Adding record of type %s is not supported' % rtype)
+
+ record_type = dns_type_flag(rtype)
+ rec = data_to_dns_record(record_type, data)
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ rec_match = dns_record_match(dns_conn, server, zone, name, record_type,
+ data)
+ if rec_match is not None:
+ raise CommandError('Record already exists')
+
+ add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+ add_rec_buf.rec = rec
+
+ dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+ 0, server, zone, name, add_rec_buf, None)
+ self.outf.write('Record added successfully\n')
+
+
+class cmd_update_record(Command):
+ """Update a DNS record
+
+ For each type data contents are as follows:
+ A ipv4_address_string
+ AAAA ipv6_address_string
+ PTR fqdn_string
+ CNAME fqdn_string
+ NS fqdn_string
+ MX "fqdn_string preference"
+ SRV "fqdn_string port priority weight"
+ TXT "'string1' 'string2' ..."
+ """
+
+ synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <olddata> <newdata>'
+
+ takes_args = [ 'server', 'zone', 'name', 'rtype', 'olddata', 'newdata' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ def run(self, server, zone, name, rtype, olddata, newdata,
+ sambaopts=None, credopts=None, versionopts=None):
+
+ if rtype.upper() not in ('A','AAAA','PTR','CNAME','NS','MX','SRV','TXT'):
+ raise CommandError('Updating record of type %s is not supported' % rtype)
+
+ record_type = dns_type_flag(rtype)
+ rec = data_to_dns_record(record_type, newdata)
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ rec_match = dns_record_match(dns_conn, server, zone, name, record_type,
+ olddata)
+ if not rec_match:
+ raise CommandError('Record does not exist')
+
+ # Copy properties from existing record to new record
+ rec.dwFlags = rec_match.dwFlags
+ rec.dwSerial = rec_match.dwSerial
+ rec.dwTtlSeconds = rec_match.dwTtlSeconds
+ rec.dwTimeStamp = rec_match.dwTimeStamp
+
+ add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+ add_rec_buf.rec = rec
+
+ del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+ del_rec_buf.rec = rec_match
+
+ dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+ 0,
+ server,
+ zone,
+ name,
+ add_rec_buf,
+ del_rec_buf)
+ self.outf.write('Record updated succefully\n')
+
+
+class cmd_delete_record(Command):
+ """Delete a DNS record
+
+ For each type data contents are as follows:
+ A ipv4_address_string
+ AAAA ipv6_address_string
+ PTR fqdn_string
+ CNAME fqdn_string
+ NS fqdn_string
+ MX "fqdn_string preference"
+ SRV "fqdn_string port priority weight"
+ TXT "'string1' 'string2' ..."
+ """
+
+ synopsis = '%prog <server> <zone> <name> <A|AAAA|PTR|CNAME|NS|MX|SRV|TXT> <data>'
+
+ takes_args = [ 'server', 'zone', 'name', 'rtype', 'data' ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ def run(self, server, zone, name, rtype, data, sambaopts=None, credopts=None, versionopts=None):
+
+ if rtype.upper() not in ('A','AAAA','PTR','CNAME','NS','MX','SRV','TXT'):
+ raise CommandError('Deleting record of type %s is not supported' % rtype)
+
+ record_type = dns_type_flag(rtype)
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp)
+ dns_conn = dns_connect(server, self.lp, self.creds)
+
+ rec_match = dns_record_match(dns_conn, server, zone, name, record_type, data)
+ if not rec_match:
+ raise CommandError('Record does not exist')
+
+ del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+ del_rec_buf.rec = rec_match
+
+ dns_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+ 0,
+ server,
+ zone,
+ name,
+ None,
+ del_rec_buf)
+ self.outf.write('Record deleted succefully\n')
+
+
+class cmd_dns(SuperCommand):
+ """Domain Name Service (DNS) management."""
+
+ subcommands = {}
+ subcommands['serverinfo'] = cmd_serverinfo()
+ subcommands['zoneinfo'] = cmd_zoneinfo()
+ subcommands['zonelist'] = cmd_zonelist()
+ subcommands['zonecreate'] = cmd_zonecreate()
+ subcommands['zonedelete'] = cmd_zonedelete()
+ subcommands['query'] = cmd_query()
+ subcommands['roothints'] = cmd_roothints()
+ subcommands['add'] = cmd_add_record()
+ subcommands['update'] = cmd_update_record()
+ subcommands['delete'] = cmd_delete_record()
diff --git a/python/samba/netcmd/domain.py b/python/samba/netcmd/domain.py
new file mode 100644
index 0000000000..4ba305c271
--- /dev/null
+++ b/python/samba/netcmd/domain.py
@@ -0,0 +1,1344 @@
+# domain management
+#
+# Copyright Matthias Dieter Wallnoefer 2009
+# Copyright Andrew Kroeger 2009
+# Copyright Jelmer Vernooij 2007-2012
+# Copyright Giampaolo Lauria 2011
+# Copyright Matthieu Patou <mat@matws.net> 2011
+# Copyright Andrew Bartlett 2008
+# Copyright Stefan Metzmacher 2012
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+import string
+import os
+import sys
+import tempfile
+import logging
+from samba.net import Net, LIBNET_JOIN_AUTOMATIC
+import samba.ntacls
+from samba.join import join_RODC, join_DC, join_subdomain
+from samba.auth import system_session
+from samba.samdb import SamDB
+from samba.dcerpc import drsuapi
+from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option
+ )
+from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
+from samba.samba3 import Samba3
+from samba.samba3 import param as s3param
+from samba.upgrade import upgrade_from_samba3
+from samba.drs_utils import (
+ sendDsReplicaSync, drsuapi_connect, drsException,
+ sendRemoveDsServer)
+
+
+from samba.dsdb import (
+ DS_DOMAIN_FUNCTION_2000,
+ DS_DOMAIN_FUNCTION_2003,
+ DS_DOMAIN_FUNCTION_2003_MIXED,
+ DS_DOMAIN_FUNCTION_2008,
+ DS_DOMAIN_FUNCTION_2008_R2,
+ DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
+ DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
+ UF_WORKSTATION_TRUST_ACCOUNT,
+ UF_SERVER_TRUST_ACCOUNT,
+ UF_TRUSTED_FOR_DELEGATION
+ )
+
+from samba.credentials import DONT_USE_KERBEROS
+from samba.provision import (
+ provision,
+ FILL_FULL,
+ FILL_NT4SYNC,
+ FILL_DRS,
+ ProvisioningError,
+ )
+
+def get_testparm_var(testparm, smbconf, varname):
+ cmd = "%s -s -l --parameter-name='%s' %s 2>/dev/null" % (testparm, varname, smbconf)
+ output = os.popen(cmd, 'r').readline()
+ return output.strip()
+
+try:
+ import samba.dckeytab
+ class cmd_domain_export_keytab(Command):
+ """Dump Kerberos keys of the domain into a keytab."""
+
+ synopsis = "%prog <keytab> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--principal", help="extract only this principal", type=str),
+ ]
+
+ takes_args = ["keytab"]
+
+ def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
+ lp = sambaopts.get_loadparm()
+ net = Net(None, lp)
+ net.export_keytab(keytab=keytab, principal=principal)
+except:
+ cmd_domain_export_keytab = None
+
+
+class cmd_domain_info(Command):
+ """Print basic info about a domain and the DC passed as parameter."""
+
+ synopsis = "%prog <ip_address> [options]"
+
+ takes_options = [
+ ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["address"]
+
+ def run(self, address, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ try:
+ res = netcmd_get_domain_infos_via_cldap(lp, None, address)
+ except RuntimeError:
+ raise CommandError("Invalid IP address '" + address + "'!")
+ self.outf.write("Forest : %s\n" % res.forest)
+ self.outf.write("Domain : %s\n" % res.dns_domain)
+ self.outf.write("Netbios domain : %s\n" % res.domain_name)
+ self.outf.write("DC name : %s\n" % res.pdc_dns_name)
+ self.outf.write("DC netbios name : %s\n" % res.pdc_name)
+ self.outf.write("Server site : %s\n" % res.server_site)
+ self.outf.write("Client site : %s\n" % res.client_site)
+
+
+class cmd_domain_provision(Command):
+ """Provision a domain."""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("--interactive", help="Ask for names", action="store_true"),
+ Option("--domain", type="string", metavar="DOMAIN",
+ help="set domain"),
+ Option("--domain-guid", type="string", metavar="GUID",
+ help="set domainguid (otherwise random)"),
+ Option("--domain-sid", type="string", metavar="SID",
+ help="set domainsid (otherwise random)"),
+ Option("--ntds-guid", type="string", metavar="GUID",
+ help="set NTDS object GUID (otherwise random)"),
+ Option("--invocationid", type="string", metavar="GUID",
+ help="set invocationid (otherwise random)"),
+ Option("--host-name", type="string", metavar="HOSTNAME",
+ help="set hostname"),
+ Option("--host-ip", type="string", metavar="IPADDRESS",
+ help="set IPv4 ipaddress"),
+ Option("--host-ip6", type="string", metavar="IP6ADDRESS",
+ help="set IPv6 ipaddress"),
+ Option("--adminpass", type="string", metavar="PASSWORD",
+ help="choose admin password (otherwise random)"),
+ Option("--krbtgtpass", type="string", metavar="PASSWORD",
+ help="choose krbtgt password (otherwise random)"),
+ Option("--machinepass", type="string", metavar="PASSWORD",
+ help="choose machine password (otherwise random)"),
+ Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
+ choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
+ help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
+ "BIND9_FLATFILE uses bind9 text database to store zone information, "
+ "BIND9_DLZ uses samba4 AD to store zone information, "
+ "NONE skips the DNS setup entirely (not recommended)",
+ default="SAMBA_INTERNAL"),
+ Option("--dnspass", type="string", metavar="PASSWORD",
+ help="choose dns password (otherwise random)"),
+ Option("--ldapadminpass", type="string", metavar="PASSWORD",
+ help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
+ Option("--root", type="string", metavar="USERNAME",
+ help="choose 'root' unix username"),
+ Option("--nobody", type="string", metavar="USERNAME",
+ help="choose 'nobody' user"),
+ Option("--users", type="string", metavar="GROUPNAME",
+ help="choose 'users' group"),
+ Option("--quiet", help="Be quiet", action="store_true"),
+ Option("--blank", action="store_true",
+ help="do not add users or groups, just the structure"),
+ Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
+ help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
+ choices=["fedora-ds", "openldap"]),
+ Option("--server-role", type="choice", metavar="ROLE",
+ choices=["domain controller", "dc", "member server", "member", "standalone"],
+ help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
+ default="domain controller"),
+ Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
+ choices=["2000", "2003", "2008", "2008_R2"],
+ help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2003 Native.",
+ default="2003"),
+ Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
+ help="The initial nextRid value (only needed for upgrades). Default is 1000."),
+ Option("--partitions-only",
+ help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
+ Option("--targetdir", type="string", metavar="DIR",
+ help="Set target directory"),
+ Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
+ 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\""),
+ 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"),
+ Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
+ Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
+ ]
+ takes_args = []
+
+ def run(self, sambaopts=None, credopts=None, versionopts=None,
+ interactive=None,
+ domain=None,
+ domain_guid=None,
+ domain_sid=None,
+ ntds_guid=None,
+ invocationid=None,
+ host_name=None,
+ host_ip=None,
+ host_ip6=None,
+ adminpass=None,
+ krbtgtpass=None,
+ machinepass=None,
+ dns_backend=None,
+ dns_forwarder=None,
+ dnspass=None,
+ ldapadminpass=None,
+ root=None,
+ nobody=None,
+ users=None,
+ quiet=None,
+ blank=None,
+ ldap_backend_type=None,
+ server_role=None,
+ function_level=None,
+ next_rid=None,
+ partitions_only=None,
+ targetdir=None,
+ ol_mmr_urls=None,
+ use_xattrs=None,
+ use_ntvfs=None,
+ use_rfc2307=None):
+
+ self.logger = self.get_logger("provision")
+ if quiet:
+ self.logger.setLevel(logging.WARNING)
+ else:
+ self.logger.setLevel(logging.INFO)
+
+ lp = sambaopts.get_loadparm()
+ smbconf = lp.configfile
+
+ creds = credopts.get_credentials(lp)
+
+ creds.set_kerberos_state(DONT_USE_KERBEROS)
+
+ if dns_forwarder is not None:
+ suggested_forwarder = dns_forwarder
+ else:
+ suggested_forwarder = self._get_nameserver_ip()
+ if suggested_forwarder is None:
+ suggested_forwarder = "none"
+
+ if len(self.raw_argv) == 1:
+ interactive = True
+
+ if interactive:
+ from getpass import getpass
+ import socket
+
+ def ask(prompt, default=None):
+ if default is not None:
+ print "%s [%s]: " % (prompt, default),
+ else:
+ print "%s: " % (prompt,),
+ return sys.stdin.readline().rstrip("\n") or default
+
+ try:
+ default = socket.getfqdn().split(".", 1)[1].upper()
+ except IndexError:
+ default = None
+ realm = ask("Realm", default)
+ if realm in (None, ""):
+ raise CommandError("No realm set!")
+
+ try:
+ default = realm.split(".")[0]
+ except IndexError:
+ default = None
+ domain = ask("Domain", default)
+ if domain is None:
+ raise CommandError("No domain set!")
+
+ server_role = ask("Server Role (dc, member, standalone)", "dc")
+
+ dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
+ if dns_backend in (None, ''):
+ raise CommandError("No DNS backend set!")
+
+ if dns_backend == "SAMBA_INTERNAL":
+ dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
+ if dns_forwarder.lower() in (None, 'none'):
+ suggested_forwarder = None
+ dns_forwarder = None
+
+ while True:
+ adminpassplain = getpass("Administrator password: ")
+ if not adminpassplain:
+ self.errf.write("Invalid administrator password.\n")
+ else:
+ adminpassverify = getpass("Retype password: ")
+ if not adminpassplain == adminpassverify:
+ self.errf.write("Sorry, passwords do not match.\n")
+ else:
+ adminpass = adminpassplain
+ break
+
+ else:
+ realm = sambaopts._lp.get('realm')
+ if realm is None:
+ raise CommandError("No realm set!")
+ if domain is None:
+ raise CommandError("No domain set!")
+
+ if not adminpass:
+ self.logger.info("Administrator password will be set randomly!")
+
+ if function_level == "2000":
+ dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
+ elif function_level == "2003":
+ dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
+ elif function_level == "2008":
+ dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
+ elif function_level == "2008_R2":
+ dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
+
+ if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
+ dns_forwarder = suggested_forwarder
+
+ samdb_fill = FILL_FULL
+ if blank:
+ samdb_fill = FILL_NT4SYNC
+ elif partitions_only:
+ samdb_fill = FILL_DRS
+
+ if targetdir is not None:
+ if not os.path.isdir(targetdir):
+ os.mkdir(targetdir)
+
+ eadb = True
+
+ if use_xattrs == "yes":
+ eadb = False
+ elif use_xattrs == "auto" and not lp.get("posix:eadb"):
+ if targetdir:
+ file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
+ else:
+ file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
+ try:
+ try:
+ samba.ntacls.setntacl(lp, file.name,
+ "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
+ eadb = False
+ except Exception:
+ self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
+ finally:
+ file.close()
+
+ if eadb:
+ 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.")
+
+ session = system_session()
+ try:
+ result = provision(self.logger,
+ session, creds, smbconf=smbconf, targetdir=targetdir,
+ samdb_fill=samdb_fill, realm=realm, domain=domain,
+ domainguid=domain_guid, domainsid=domain_sid,
+ hostname=host_name,
+ hostip=host_ip, hostip6=host_ip6,
+ ntdsguid=ntds_guid,
+ invocationid=invocationid, adminpass=adminpass,
+ krbtgtpass=krbtgtpass, machinepass=machinepass,
+ dns_backend=dns_backend, dns_forwarder=dns_forwarder,
+ dnspass=dnspass, root=root, nobody=nobody,
+ users=users,
+ serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
+ backend_type=ldap_backend_type,
+ ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls,
+ useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
+ use_rfc2307=use_rfc2307, skip_sysvolacl=False)
+ except ProvisioningError, e:
+ raise CommandError("Provision failed", e)
+
+ result.report_logger(self.logger)
+
+ def _get_nameserver_ip(self):
+ """Grab the nameserver IP address from /etc/resolv.conf."""
+ from os import path
+ RESOLV_CONF="/etc/resolv.conf"
+
+ if not path.isfile(RESOLV_CONF):
+ self.logger.warning("Failed to locate %s" % RESOLV_CONF)
+ return None
+
+ handle = None
+ try:
+ handle = open(RESOLV_CONF, 'r')
+ for line in handle:
+ if not line.startswith('nameserver'):
+ continue
+ # we want the last non-space continuous string of the line
+ return line.strip().split()[-1]
+ finally:
+ if handle is not None:
+ handle.close()
+
+ self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
+
+
+class cmd_domain_dcpromo(Command):
+ """Promote an existing domain member or NT4 PDC to an AD DC."""
+
+ synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("--server", help="DC to join", type=str),
+ Option("--site", help="site to join", type=str),
+ Option("--targetdir", help="where to store provision", type=str),
+ Option("--domain-critical-only",
+ help="only replicate critical domain objects",
+ action="store_true"),
+ Option("--machinepass", type=str, metavar="PASSWORD",
+ help="choose machine password (otherwise random)"),
+ Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
+ action="store_true"),
+ Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
+ choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
+ help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
+ "BIND9_DLZ uses samba4 AD to store zone information, "
+ "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
+ default="SAMBA_INTERNAL")
+ ]
+
+ takes_args = ["domain", "role?"]
+
+ def run(self, domain, role=None, sambaopts=None, credopts=None,
+ versionopts=None, server=None, site=None, targetdir=None,
+ domain_critical_only=False, parent_domain=None, machinepass=None,
+ use_ntvfs=False, dns_backend=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ net = Net(creds, lp, server=credopts.ipaddress)
+
+ if site is None:
+ site = "Default-First-Site-Name"
+
+ netbios_name = lp.get("netbios name")
+
+ if not role is None:
+ role = role.upper()
+
+ if role == "DC":
+ join_DC(server=server, creds=creds, lp=lp, domain=domain,
+ site=site, netbios_name=netbios_name, targetdir=targetdir,
+ domain_critical_only=domain_critical_only,
+ machinepass=machinepass, use_ntvfs=use_ntvfs,
+ dns_backend=dns_backend,
+ promote_existing=True)
+ elif role == "RODC":
+ join_RODC(server=server, creds=creds, lp=lp, domain=domain,
+ site=site, netbios_name=netbios_name, targetdir=targetdir,
+ domain_critical_only=domain_critical_only,
+ machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
+ promote_existing=True)
+ else:
+ raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
+
+
+class cmd_domain_join(Command):
+ """Join domain as either member or backup domain controller."""
+
+ synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("--server", help="DC to join", type=str),
+ Option("--site", help="site to join", type=str),
+ Option("--targetdir", help="where to store provision", type=str),
+ Option("--parent-domain", help="parent domain to create subdomain under", type=str),
+ Option("--domain-critical-only",
+ help="only replicate critical domain objects",
+ action="store_true"),
+ Option("--machinepass", type=str, metavar="PASSWORD",
+ help="choose machine password (otherwise random)"),
+ Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
+ action="store_true"),
+ Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
+ choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
+ help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
+ "BIND9_DLZ uses samba4 AD to store zone information, "
+ "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
+ default="SAMBA_INTERNAL")
+ ]
+
+ takes_args = ["domain", "role?"]
+
+ def run(self, domain, role=None, sambaopts=None, credopts=None,
+ versionopts=None, server=None, site=None, targetdir=None,
+ domain_critical_only=False, parent_domain=None, machinepass=None,
+ use_ntvfs=False, dns_backend=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ net = Net(creds, lp, server=credopts.ipaddress)
+
+ if site is None:
+ site = "Default-First-Site-Name"
+
+ netbios_name = lp.get("netbios name")
+
+ if not role is None:
+ role = role.upper()
+
+ if role is None or role == "MEMBER":
+ (join_password, sid, domain_name) = net.join_member(
+ domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
+ machinepass=machinepass)
+
+ self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
+ elif role == "DC":
+ join_DC(server=server, creds=creds, lp=lp, domain=domain,
+ site=site, netbios_name=netbios_name, targetdir=targetdir,
+ domain_critical_only=domain_critical_only,
+ machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
+ elif role == "RODC":
+ join_RODC(server=server, creds=creds, lp=lp, domain=domain,
+ site=site, netbios_name=netbios_name, targetdir=targetdir,
+ domain_critical_only=domain_critical_only,
+ machinepass=machinepass, use_ntvfs=use_ntvfs,
+ dns_backend=dns_backend)
+ elif role == "SUBDOMAIN":
+ netbios_domain = lp.get("workgroup")
+ if parent_domain is None:
+ parent_domain = ".".join(domain.split(".")[1:])
+ join_subdomain(server=server, creds=creds, lp=lp, dnsdomain=domain,
+ parent_domain=parent_domain, site=site,
+ netbios_name=netbios_name, netbios_domain=netbios_domain,
+ targetdir=targetdir, machinepass=machinepass,
+ use_ntvfs=use_ntvfs, dns_backend=dns_backend)
+ else:
+ raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
+
+
+class cmd_domain_demote(Command):
+ """Demote ourselves from the role of Domain Controller."""
+
+ synopsis = "%prog [options]"
+
+ takes_options = [
+ Option("--server", help="DC to force replication before demote", type=str),
+ Option("--targetdir", help="where provision is stored", type=str),
+ ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, sambaopts=None, credopts=None,
+ versionopts=None, server=None, targetdir=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ net = Net(creds, lp, server=credopts.ipaddress)
+
+ netbios_name = lp.get("netbios name")
+ samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
+ if not server:
+ res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
+ if (len(res) == 0):
+ raise CommandError("Unable to search for servers")
+
+ if (len(res) == 1):
+ raise CommandError("You are the latest server in the domain")
+
+ server = None
+ for e in res:
+ if str(e["name"]).lower() != netbios_name.lower():
+ server = e["dnsHostName"]
+ break
+
+ ntds_guid = samdb.get_ntds_GUID()
+ msg = samdb.search(base=str(samdb.get_config_basedn()),
+ scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
+ attrs=['options'])
+ if len(msg) == 0 or "options" not in msg[0]:
+ raise CommandError("Failed to find options on %s" % ntds_guid)
+
+ ntds_dn = msg[0].dn
+ dsa_options = int(str(msg[0]['options']))
+
+ res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
+ controls=["search_options:1:2"])
+
+ if len(res) != 0:
+ raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
+
+ self.errf.write("Using %s as partner server for the demotion\n" %
+ server)
+ (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
+
+ self.errf.write("Desactivating inbound replication\n")
+
+ nmsg = ldb.Message()
+ nmsg.dn = msg[0].dn
+
+ dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+ nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+ samdb.modify(nmsg)
+
+ if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
+
+ self.errf.write("Asking partner server %s to synchronize from us\n"
+ % server)
+ for part in (samdb.get_schema_basedn(),
+ samdb.get_config_basedn(),
+ samdb.get_root_basedn()):
+ try:
+ sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
+ except drsException, e:
+ self.errf.write(
+ "Error while demoting, "
+ "re-enabling inbound replication\n")
+ dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+ nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+ samdb.modify(nmsg)
+ raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
+ try:
+ remote_samdb = SamDB(url="ldap://%s" % server,
+ session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ self.errf.write("Changing userControl and container\n")
+ res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
+ expression="(&(objectClass=user)(sAMAccountName=%s$))" %
+ netbios_name.upper(),
+ attrs=["userAccountControl"])
+ dc_dn = res[0].dn
+ uac = int(str(res[0]["userAccountControl"]))
+
+ except Exception, e:
+ self.errf.write(
+ "Error while demoting, re-enabling inbound replication\n")
+ dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+ nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+ samdb.modify(nmsg)
+ raise CommandError("Error while changing account control", e)
+
+ if (len(res) != 1):
+ self.errf.write(
+ "Error while demoting, re-enabling inbound replication")
+ dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+ nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+ samdb.modify(nmsg)
+ raise CommandError("Unable to find object with samaccountName = %s$"
+ " in the remote dc" % netbios_name.upper())
+
+ olduac = uac
+
+ uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
+ uac |= UF_WORKSTATION_TRUST_ACCOUNT
+
+ msg = ldb.Message()
+ msg.dn = dc_dn
+
+ msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
+ ldb.FLAG_MOD_REPLACE,
+ "userAccountControl")
+ try:
+ remote_samdb.modify(msg)
+ except Exception, e:
+ self.errf.write(
+ "Error while demoting, re-enabling inbound replication")
+ dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+ nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+ samdb.modify(nmsg)
+
+ raise CommandError("Error while changing account control", e)
+
+ parent = msg.dn.parent()
+ rdn = str(res[0].dn)
+ rdn = string.replace(rdn, ",%s" % str(parent), "")
+ # Let's move to the Computer container
+ i = 0
+ newrdn = rdn
+
+ computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
+ res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
+
+ if (len(res) != 0):
+ res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
+ scope=ldb.SCOPE_ONELEVEL)
+ while(len(res) != 0 and i < 100):
+ i = i + 1
+ res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
+ scope=ldb.SCOPE_ONELEVEL)
+
+ if i == 100:
+ self.errf.write(
+ "Error while demoting, re-enabling inbound replication\n")
+ dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+ nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+ samdb.modify(nmsg)
+
+ msg = ldb.Message()
+ msg.dn = dc_dn
+
+ msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
+ ldb.FLAG_MOD_REPLACE,
+ "userAccountControl")
+
+ remote_samdb.modify(msg)
+
+ raise CommandError("Unable to find a slot for renaming %s,"
+ " all names from %s-1 to %s-%d seemed used" %
+ (str(dc_dn), rdn, rdn, i - 9))
+
+ newrdn = "%s-%d" % (rdn, i)
+
+ try:
+ newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
+ remote_samdb.rename(dc_dn, newdn)
+ except Exception, e:
+ self.errf.write(
+ "Error while demoting, re-enabling inbound replication\n")
+ dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+ nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+ samdb.modify(nmsg)
+
+ msg = ldb.Message()
+ msg.dn = dc_dn
+
+ msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
+ ldb.FLAG_MOD_REPLACE,
+ "userAccountControl")
+
+ remote_samdb.modify(msg)
+ raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
+
+
+ server_dsa_dn = samdb.get_serverName()
+ domain = remote_samdb.get_root_basedn()
+
+ try:
+ sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
+ except drsException, e:
+ self.errf.write(
+ "Error while demoting, re-enabling inbound replication\n")
+ dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
+ nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
+ samdb.modify(nmsg)
+
+ msg = ldb.Message()
+ msg.dn = newdn
+
+ msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
+ ldb.FLAG_MOD_REPLACE,
+ "userAccountControl")
+ print str(dc_dn)
+ remote_samdb.modify(msg)
+ remote_samdb.rename(newdn, dc_dn)
+ raise CommandError("Error while sending a removeDsServer", e)
+
+ for s in ("CN=Entreprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
+ "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
+ "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
+ try:
+ remote_samdb.delete(ldb.Dn(remote_samdb,
+ "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
+ except ldb.LdbError, l:
+ pass
+
+ for s in ("CN=Entreprise,CN=NTFRS Subscriptions",
+ "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
+ "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
+ "CN=NTFRS Subscriptions"):
+ try:
+ remote_samdb.delete(ldb.Dn(remote_samdb,
+ "%s,%s" % (s, str(newdn))))
+ except ldb.LdbError, l:
+ pass
+
+ self.errf.write("Demote successfull\n")
+
+
+class cmd_domain_level(Command):
+ """Raise domain and forest function levels."""
+
+ synopsis = "%prog (show|raise <options>) [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--quiet", help="Be quiet", action="store_true"),
+ Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
+ help="The forest function level (2003 | 2008 | 2008_R2)"),
+ Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
+ help="The domain function level (2003 | 2008 | 2008_R2)")
+ ]
+
+ takes_args = ["subcommand"]
+
+ def run(self, subcommand, H=None, forest_level=None, domain_level=None,
+ quiet=False, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ domain_dn = samdb.domain_dn()
+
+ res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
+ scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
+ assert len(res_forest) == 1
+
+ res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
+ attrs=["msDS-Behavior-Version", "nTMixedDomain"])
+ assert len(res_domain) == 1
+
+ res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
+ scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
+ attrs=["msDS-Behavior-Version"])
+ assert len(res_dc_s) >= 1
+
+ try:
+ level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
+ level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
+ level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
+
+ min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
+ for msg in res_dc_s:
+ if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
+ min_level_dc = int(msg["msDS-Behavior-Version"][0])
+
+ if level_forest < 0 or level_domain < 0:
+ raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
+ if min_level_dc < 0:
+ raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
+ if level_forest > level_domain:
+ raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
+ if level_domain > min_level_dc:
+ raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
+
+ except KeyError:
+ raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
+
+ if subcommand == "show":
+ self.message("Domain and forest function level for domain '%s'" % domain_dn)
+ if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
+ self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
+ if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
+ self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
+ if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
+ 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)!")
+
+ self.message("")
+
+ if level_forest == DS_DOMAIN_FUNCTION_2000:
+ outstr = "2000"
+ elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
+ outstr = "2003 with mixed domains/interim (NT4 DC support)"
+ elif level_forest == DS_DOMAIN_FUNCTION_2003:
+ outstr = "2003"
+ elif level_forest == DS_DOMAIN_FUNCTION_2008:
+ outstr = "2008"
+ elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
+ outstr = "2008 R2"
+ else:
+ outstr = "higher than 2008 R2"
+ self.message("Forest function level: (Windows) " + outstr)
+
+ if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
+ outstr = "2000 mixed (NT4 DC support)"
+ elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
+ outstr = "2000"
+ elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
+ outstr = "2003 with mixed domains/interim (NT4 DC support)"
+ elif level_domain == DS_DOMAIN_FUNCTION_2003:
+ outstr = "2003"
+ elif level_domain == DS_DOMAIN_FUNCTION_2008:
+ outstr = "2008"
+ elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
+ outstr = "2008 R2"
+ else:
+ outstr = "higher than 2008 R2"
+ self.message("Domain function level: (Windows) " + outstr)
+
+ if min_level_dc == DS_DOMAIN_FUNCTION_2000:
+ outstr = "2000"
+ elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
+ outstr = "2003"
+ elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
+ outstr = "2008"
+ elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
+ outstr = "2008 R2"
+ else:
+ outstr = "higher than 2008 R2"
+ self.message("Lowest function level of a DC: (Windows) " + outstr)
+
+ elif subcommand == "raise":
+ msgs = []
+
+ if domain_level is not None:
+ if domain_level == "2003":
+ new_level_domain = DS_DOMAIN_FUNCTION_2003
+ elif domain_level == "2008":
+ new_level_domain = DS_DOMAIN_FUNCTION_2008
+ elif domain_level == "2008_R2":
+ new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
+
+ if new_level_domain <= level_domain and level_domain_mixed == 0:
+ raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
+
+ if new_level_domain > min_level_dc:
+ raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
+
+ # Deactivate mixed/interim domain support
+ if level_domain_mixed != 0:
+ # Directly on the base DN
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, domain_dn)
+ m["nTMixedDomain"] = ldb.MessageElement("0",
+ ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
+ samdb.modify(m)
+ # Under partitions
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
+ m["nTMixedDomain"] = ldb.MessageElement("0",
+ ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
+ try:
+ samdb.modify(m)
+ except ldb.LdbError, (enum, emsg):
+ if enum != ldb.ERR_UNWILLING_TO_PERFORM:
+ raise
+
+ # Directly on the base DN
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, domain_dn)
+ m["msDS-Behavior-Version"]= ldb.MessageElement(
+ str(new_level_domain), ldb.FLAG_MOD_REPLACE,
+ "msDS-Behavior-Version")
+ samdb.modify(m)
+ # Under partitions
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
+ + ",CN=Partitions,%s" % samdb.get_config_basedn())
+ m["msDS-Behavior-Version"]= ldb.MessageElement(
+ str(new_level_domain), ldb.FLAG_MOD_REPLACE,
+ "msDS-Behavior-Version")
+ try:
+ samdb.modify(m)
+ except ldb.LdbError, (enum, emsg):
+ if enum != ldb.ERR_UNWILLING_TO_PERFORM:
+ raise
+
+ level_domain = new_level_domain
+ msgs.append("Domain function level changed!")
+
+ if forest_level is not None:
+ if forest_level == "2003":
+ new_level_forest = DS_DOMAIN_FUNCTION_2003
+ elif forest_level == "2008":
+ new_level_forest = DS_DOMAIN_FUNCTION_2008
+ elif forest_level == "2008_R2":
+ new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
+ if new_level_forest <= level_forest:
+ raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
+ if new_level_forest > level_domain:
+ raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
+ m["msDS-Behavior-Version"]= ldb.MessageElement(
+ str(new_level_forest), ldb.FLAG_MOD_REPLACE,
+ "msDS-Behavior-Version")
+ samdb.modify(m)
+ msgs.append("Forest function level changed!")
+ msgs.append("All changes applied successfully!")
+ self.message("\n".join(msgs))
+ else:
+ raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
+
+
+class cmd_domain_passwordsettings(Command):
+ """Set password settings.
+
+ Password complexity, history length, minimum password length, the minimum
+ and maximum password age) on a Samba4 server.
+ """
+
+ synopsis = "%prog (show|set <options>) [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--quiet", help="Be quiet", action="store_true"),
+ Option("--complexity", type="choice", choices=["on","off","default"],
+ help="The password complexity (on | off | default). Default is 'on'"),
+ Option("--store-plaintext", type="choice", choices=["on","off","default"],
+ help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
+ Option("--history-length",
+ help="The password history length (<integer> | default). Default is 24.", type=str),
+ Option("--min-pwd-length",
+ help="The minimum password length (<integer> | default). Default is 7.", type=str),
+ Option("--min-pwd-age",
+ help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
+ Option("--max-pwd-age",
+ help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
+ ]
+
+ takes_args = ["subcommand"]
+
+ def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
+ quiet=False, complexity=None, store_plaintext=None, history_length=None,
+ min_pwd_length=None, credopts=None, sambaopts=None,
+ versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ domain_dn = samdb.domain_dn()
+ res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
+ attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
+ "minPwdAge", "maxPwdAge"])
+ assert(len(res) == 1)
+ try:
+ pwd_props = int(res[0]["pwdProperties"][0])
+ pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
+ cur_min_pwd_len = int(res[0]["minPwdLength"][0])
+ # ticks -> days
+ cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
+ if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
+ cur_max_pwd_age = 0
+ else:
+ cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
+ except Exception, e:
+ raise CommandError("Could not retrieve password properties!", e)
+
+ if subcommand == "show":
+ self.message("Password informations for domain '%s'" % domain_dn)
+ self.message("")
+ if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
+ self.message("Password complexity: on")
+ else:
+ self.message("Password complexity: off")
+ if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
+ self.message("Store plaintext passwords: on")
+ else:
+ self.message("Store plaintext passwords: off")
+ self.message("Password history length: %d" % pwd_hist_len)
+ self.message("Minimum password length: %d" % cur_min_pwd_len)
+ self.message("Minimum password age (days): %d" % cur_min_pwd_age)
+ self.message("Maximum password age (days): %d" % cur_max_pwd_age)
+ elif subcommand == "set":
+ msgs = []
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, domain_dn)
+
+ if complexity is not None:
+ if complexity == "on" or complexity == "default":
+ pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
+ msgs.append("Password complexity activated!")
+ elif complexity == "off":
+ pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
+ msgs.append("Password complexity deactivated!")
+
+ if store_plaintext is not None:
+ if store_plaintext == "on" or store_plaintext == "default":
+ pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
+ msgs.append("Plaintext password storage for changed passwords activated!")
+ elif store_plaintext == "off":
+ pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
+ msgs.append("Plaintext password storage for changed passwords deactivated!")
+
+ if complexity is not None or store_plaintext is not None:
+ m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
+ ldb.FLAG_MOD_REPLACE, "pwdProperties")
+
+ if history_length is not None:
+ if history_length == "default":
+ pwd_hist_len = 24
+ else:
+ pwd_hist_len = int(history_length)
+
+ if pwd_hist_len < 0 or pwd_hist_len > 24:
+ raise CommandError("Password history length must be in the range of 0 to 24!")
+
+ m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
+ ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
+ msgs.append("Password history length changed!")
+
+ if min_pwd_length is not None:
+ if min_pwd_length == "default":
+ min_pwd_len = 7
+ else:
+ min_pwd_len = int(min_pwd_length)
+
+ if min_pwd_len < 0 or min_pwd_len > 14:
+ raise CommandError("Minimum password length must be in the range of 0 to 14!")
+
+ m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
+ ldb.FLAG_MOD_REPLACE, "minPwdLength")
+ msgs.append("Minimum password length changed!")
+
+ if min_pwd_age is not None:
+ if min_pwd_age == "default":
+ min_pwd_age = 1
+ else:
+ min_pwd_age = int(min_pwd_age)
+
+ if min_pwd_age < 0 or min_pwd_age > 998:
+ raise CommandError("Minimum password age must be in the range of 0 to 998!")
+
+ # days -> ticks
+ min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
+
+ m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
+ ldb.FLAG_MOD_REPLACE, "minPwdAge")
+ msgs.append("Minimum password age changed!")
+
+ if max_pwd_age is not None:
+ if max_pwd_age == "default":
+ max_pwd_age = 43
+ else:
+ max_pwd_age = int(max_pwd_age)
+
+ if max_pwd_age < 0 or max_pwd_age > 999:
+ raise CommandError("Maximum password age must be in the range of 0 to 999!")
+
+ # days -> ticks
+ if max_pwd_age == 0:
+ max_pwd_age_ticks = -0x8000000000000000
+ else:
+ max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
+
+ m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
+ ldb.FLAG_MOD_REPLACE, "maxPwdAge")
+ msgs.append("Maximum password age changed!")
+
+ if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
+ raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
+
+ if len(m) == 0:
+ raise CommandError("You must specify at least one option to set. Try --help")
+ samdb.modify(m)
+ msgs.append("All changes applied successfully!")
+ self.message("\n".join(msgs))
+ else:
+ raise CommandError("Wrong argument '%s'!" % subcommand)
+
+
+class cmd_domain_classicupgrade(Command):
+ """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
+
+ Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
+ the testparm utility from your classic installation (with --testparm).
+ """
+
+ synopsis = "%prog [options] <classic_smb_conf>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions
+ }
+
+ takes_options = [
+ Option("--dbdir", type="string", metavar="DIR",
+ help="Path to samba classic DC database directory"),
+ Option("--testparm", type="string", metavar="PATH",
+ help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
+ Option("--targetdir", type="string", metavar="DIR",
+ help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
+ Option("--quiet", help="Be quiet", action="store_true"),
+ Option("--verbose", help="Be verbose", action="store_true"),
+ Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[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"),
+ Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
+ action="store_true"),
+ Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
+ choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
+ help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
+ "BIND9_FLATFILE uses bind9 text database to store zone information, "
+ "BIND9_DLZ uses samba4 AD to store zone information, "
+ "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
+ default="SAMBA_INTERNAL")
+ ]
+
+ takes_args = ["smbconf"]
+
+ def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
+ quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
+ dns_backend=None, use_ntvfs=False):
+
+ if not os.path.exists(smbconf):
+ raise CommandError("File %s does not exist" % smbconf)
+
+ if testparm and not os.path.exists(testparm):
+ raise CommandError("Testparm utility %s does not exist" % testparm)
+
+ if dbdir and not os.path.exists(dbdir):
+ raise CommandError("Directory %s does not exist" % dbdir)
+
+ if not dbdir and not testparm:
+ raise CommandError("Please specify either dbdir or testparm")
+
+ logger = self.get_logger()
+ if verbose:
+ logger.setLevel(logging.DEBUG)
+ elif quiet:
+ logger.setLevel(logging.WARNING)
+ else:
+ logger.setLevel(logging.INFO)
+
+ if dbdir and testparm:
+ logger.warning("both dbdir and testparm specified, ignoring dbdir.")
+ dbdir = None
+
+ lp = sambaopts.get_loadparm()
+
+ s3conf = s3param.get_context()
+
+ if sambaopts.realm:
+ s3conf.set("realm", sambaopts.realm)
+
+ if targetdir is not None:
+ if not os.path.isdir(targetdir):
+ os.mkdir(targetdir)
+
+ eadb = True
+ if use_xattrs == "yes":
+ eadb = False
+ elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
+ if targetdir:
+ tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
+ else:
+ tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
+ try:
+ try:
+ samba.ntacls.setntacl(lp, tmpfile.name,
+ "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
+ eadb = False
+ except Exception:
+ # FIXME: Don't catch all exceptions here
+ logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
+ "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
+ finally:
+ tmpfile.close()
+
+ # Set correct default values from dbdir or testparm
+ paths = {}
+ if dbdir:
+ paths["state directory"] = dbdir
+ paths["private dir"] = dbdir
+ paths["lock directory"] = dbdir
+ paths["smb passwd file"] = dbdir + "/smbpasswd"
+ else:
+ paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
+ paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
+ paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
+ paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
+ # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
+ # "state directory", instead make use of "lock directory"
+ if len(paths["state directory"]) == 0:
+ paths["state directory"] = paths["lock directory"]
+
+ for p in paths:
+ s3conf.set(p, paths[p])
+
+ # load smb.conf parameters
+ logger.info("Reading smb.conf")
+ s3conf.load(smbconf)
+ samba3 = Samba3(smbconf, s3conf)
+
+ logger.info("Provisioning")
+ upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
+ useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
+
+
+class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
+ __doc__ = cmd_domain_classicupgrade.__doc__
+
+ # This command is present for backwards compatibility only,
+ # and should not be shown.
+
+ hidden = True
+
+
+class cmd_domain(SuperCommand):
+ """Domain management."""
+
+ subcommands = {}
+ subcommands["demote"] = cmd_domain_demote()
+ if cmd_domain_export_keytab is not None:
+ subcommands["exportkeytab"] = cmd_domain_export_keytab()
+ subcommands["info"] = cmd_domain_info()
+ subcommands["provision"] = cmd_domain_provision()
+ subcommands["join"] = cmd_domain_join()
+ subcommands["dcpromo"] = cmd_domain_dcpromo()
+ subcommands["level"] = cmd_domain_level()
+ subcommands["passwordsettings"] = cmd_domain_passwordsettings()
+ subcommands["classicupgrade"] = cmd_domain_classicupgrade()
+ subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py
new file mode 100644
index 0000000000..ff8d8304eb
--- /dev/null
+++ b/python/samba/netcmd/drs.py
@@ -0,0 +1,510 @@
+# implement samba_tool drs commands
+#
+# Copyright Andrew Tridgell 2010
+#
+# based on C implementation by Kamen Mazdrashki <kamen.mazdrashki@postpath.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ Option,
+ SuperCommand,
+ )
+from samba.samdb import SamDB
+from samba import drs_utils, nttime2string, dsdb
+from samba.dcerpc import drsuapi, misc
+import common
+
+def drsuapi_connect(ctx):
+ '''make a DRSUAPI connection to the server'''
+ try:
+ (ctx.drsuapi, ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drsuapi_connect(ctx.server, ctx.lp, ctx.creds)
+ except Exception, e:
+ raise CommandError("DRS connection to %s failed" % ctx.server, e)
+
+def samdb_connect(ctx):
+ '''make a ldap connection to the server'''
+ try:
+ ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
+ session_info=system_session(),
+ credentials=ctx.creds, lp=ctx.lp)
+ except Exception, e:
+ raise CommandError("LDAP connection to %s failed" % ctx.server, e)
+
+def drs_errmsg(werr):
+ '''return "was successful" or an error string'''
+ (ecode, estring) = werr
+ if ecode == 0:
+ return "was successful"
+ return "failed, result %u (%s)" % (ecode, estring)
+
+
+
+def attr_default(msg, attrname, default):
+ '''get an attribute from a ldap msg with a default'''
+ if attrname in msg:
+ return msg[attrname][0]
+ return default
+
+
+
+def drs_parse_ntds_dn(ntds_dn):
+ '''parse a NTDS DN returning a site and server'''
+ a = ntds_dn.split(',')
+ if a[0] != "CN=NTDS Settings" or a[2] != "CN=Servers" or a[4] != 'CN=Sites':
+ raise RuntimeError("bad NTDS DN %s" % ntds_dn)
+ server = a[1].split('=')[1]
+ site = a[3].split('=')[1]
+ return (site, server)
+
+
+
+
+
+class cmd_drs_showrepl(Command):
+ """Show replication status."""
+
+ synopsis = "%prog [<DC>] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DC?"]
+
+ def print_neighbour(self, n):
+ '''print one set of neighbour information'''
+ self.message("%s" % n.naming_context_dn)
+ try:
+ (site, server) = drs_parse_ntds_dn(n.source_dsa_obj_dn)
+ self.message("\t%s\%s via RPC" % (site, server))
+ except RuntimeError:
+ self.message("\tNTDS DN: %s" % n.source_dsa_obj_dn)
+ self.message("\t\tDSA object GUID: %s" % n.source_dsa_obj_guid)
+ self.message("\t\tLast attempt @ %s %s" % (nttime2string(n.last_attempt),
+ drs_errmsg(n.result_last_attempt)))
+ self.message("\t\t%u consecutive failure(s)." % n.consecutive_sync_failures)
+ self.message("\t\tLast success @ %s" % nttime2string(n.last_success))
+ self.message("")
+
+ def drsuapi_ReplicaInfo(ctx, info_type):
+ '''call a DsReplicaInfo'''
+
+ req1 = drsuapi.DsReplicaGetInfoRequest1()
+ req1.info_type = info_type
+ try:
+ (info_type, info) = ctx.drsuapi.DsReplicaGetInfo(ctx.drsuapi_handle, 1, req1)
+ except Exception, e:
+ raise CommandError("DsReplicaGetInfo of type %u failed" % info_type, e)
+ return (info_type, info)
+
+ def run(self, DC=None, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ self.lp = sambaopts.get_loadparm()
+ if DC is None:
+ DC = common.netcmd_dnsname(self.lp)
+ self.server = DC
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ drsuapi_connect(self)
+ samdb_connect(self)
+
+ # show domain information
+ ntds_dn = self.samdb.get_dsServiceName()
+ server_dns = self.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])[0]['dnsHostName'][0]
+
+ (site, server) = drs_parse_ntds_dn(ntds_dn)
+ try:
+ ntds = self.samdb.search(base=ntds_dn, scope=ldb.SCOPE_BASE, attrs=['options', 'objectGUID', 'invocationId'])
+ except Exception, e:
+ raise CommandError("Failed to search NTDS DN %s" % ntds_dn)
+ conn = self.samdb.search(base=ntds_dn, expression="(objectClass=nTDSConnection)")
+
+ self.message("%s\\%s" % (site, server))
+ self.message("DSA Options: 0x%08x" % int(attr_default(ntds[0], "options", 0)))
+ self.message("DSA object GUID: %s" % self.samdb.schema_format_value("objectGUID", ntds[0]["objectGUID"][0]))
+ self.message("DSA invocationId: %s\n" % self.samdb.schema_format_value("objectGUID", ntds[0]["invocationId"][0]))
+
+ self.message("==== INBOUND NEIGHBORS ====\n")
+ (info_type, info) = self.drsuapi_ReplicaInfo(drsuapi.DRSUAPI_DS_REPLICA_INFO_NEIGHBORS)
+ for n in info.array:
+ self.print_neighbour(n)
+
+
+ self.message("==== OUTBOUND NEIGHBORS ====\n")
+ (info_type, info) = self.drsuapi_ReplicaInfo(drsuapi.DRSUAPI_DS_REPLICA_INFO_REPSTO)
+ for n in info.array:
+ self.print_neighbour(n)
+
+ reasons = ['NTDSCONN_KCC_GC_TOPOLOGY',
+ 'NTDSCONN_KCC_RING_TOPOLOGY',
+ 'NTDSCONN_KCC_MINIMIZE_HOPS_TOPOLOGY',
+ 'NTDSCONN_KCC_STALE_SERVERS_TOPOLOGY',
+ 'NTDSCONN_KCC_OSCILLATING_CONNECTION_TOPOLOGY',
+ 'NTDSCONN_KCC_INTERSITE_GC_TOPOLOGY',
+ 'NTDSCONN_KCC_INTERSITE_TOPOLOGY',
+ 'NTDSCONN_KCC_SERVER_FAILOVER_TOPOLOGY',
+ 'NTDSCONN_KCC_SITE_FAILOVER_TOPOLOGY',
+ 'NTDSCONN_KCC_REDUNDANT_SERVER_TOPOLOGY']
+
+ self.message("==== KCC CONNECTION OBJECTS ====\n")
+ for c in conn:
+ c_rdn, sep, c_server_dn = c['fromServer'][0].partition(',')
+ c_server_res = self.samdb.search(base=c_server_dn, scope=ldb.SCOPE_BASE, attrs=["dnsHostName"])
+ c_server_dns = c_server_res[0]["dnsHostName"][0]
+ self.message("Connection --")
+ self.message("\tConnection name: %s" % c['name'][0])
+ self.message("\tEnabled : %s" % attr_default(c, 'enabledConnection', 'TRUE'))
+ self.message("\tServer DNS name : %s" % c_server_dns)
+ self.message("\tServer DN name : %s" % c['fromServer'][0])
+ self.message("\t\tTransportType: RPC")
+ self.message("\t\toptions: 0x%08X" % int(attr_default(c, 'options', 0)))
+ if not 'mS-DS-ReplicatesNCReason' in c:
+ self.message("Warning: No NC replicated for Connection!")
+ continue
+ for r in c['mS-DS-ReplicatesNCReason']:
+ a = str(r).split(':')
+ self.message("\t\tReplicatesNC: %s" % a[3])
+ self.message("\t\tReason: 0x%08x" % int(a[2]))
+ for s in reasons:
+ if getattr(dsdb, s, 0) & int(a[2]):
+ self.message("\t\t\t%s" % s)
+
+
+
+class cmd_drs_kcc(Command):
+ """Trigger knowledge consistency center run."""
+
+ synopsis = "%prog [<DC>] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DC?"]
+
+ def run(self, DC=None, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ self.lp = sambaopts.get_loadparm()
+ if DC is None:
+ DC = common.netcmd_dnsname(self.lp)
+ self.server = DC
+
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ drsuapi_connect(self)
+
+ req1 = drsuapi.DsExecuteKCC1()
+ try:
+ self.drsuapi.DsExecuteKCC(self.drsuapi_handle, 1, req1)
+ except Exception, e:
+ raise CommandError("DsExecuteKCC failed", e)
+ self.message("Consistency check on %s successful." % DC)
+
+
+
+def drs_local_replicate(self, SOURCE_DC, NC):
+ '''replicate from a source DC to the local SAM'''
+
+ self.server = SOURCE_DC
+ drsuapi_connect(self)
+
+ self.local_samdb = SamDB(session_info=system_session(), url=None,
+ credentials=self.creds, lp=self.lp)
+
+ self.samdb = SamDB(url="ldap://%s" % self.server,
+ session_info=system_session(),
+ credentials=self.creds, lp=self.lp)
+
+ # work out the source and destination GUIDs
+ res = self.local_samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
+ self.ntds_dn = res[0]["dsServiceName"][0]
+
+ res = self.local_samdb.search(base=self.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
+ self.ntds_guid = misc.GUID(self.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
+
+
+ source_dsa_invocation_id = misc.GUID(self.samdb.get_invocation_id())
+ destination_dsa_guid = self.ntds_guid
+
+ self.samdb.transaction_start()
+ repl = drs_utils.drs_Replicate("ncacn_ip_tcp:%s[seal]" % self.server, self.lp,
+ self.creds, self.local_samdb)
+ try:
+ repl.replicate(NC, source_dsa_invocation_id, destination_dsa_guid)
+ except Exception, e:
+ raise CommandError("Error replicating DN %s" % NC, e)
+ self.samdb.transaction_commit()
+
+
+
+class cmd_drs_replicate(Command):
+ """Replicate a naming context between two DCs."""
+
+ synopsis = "%prog <destinationDC> <sourceDC> <NC> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DEST_DC", "SOURCE_DC", "NC"]
+
+ takes_options = [
+ Option("--add-ref", help="use ADD_REF to add to repsTo on source", action="store_true"),
+ Option("--sync-forced", help="use SYNC_FORCED to force inbound replication", action="store_true"),
+ Option("--sync-all", help="use SYNC_ALL to replicate from all DCs", action="store_true"),
+ Option("--full-sync", help="resync all objects", action="store_true"),
+ Option("--local", help="pull changes directly into the local database (destination DC is ignored)", action="store_true"),
+ ]
+
+ def run(self, DEST_DC, SOURCE_DC, NC,
+ add_ref=False, sync_forced=False, sync_all=False, full_sync=False,
+ local=False, sambaopts=None, credopts=None, versionopts=None, server=None):
+
+ self.server = DEST_DC
+ self.lp = sambaopts.get_loadparm()
+
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ if local:
+ drs_local_replicate(self, SOURCE_DC, NC)
+ return
+
+ drsuapi_connect(self)
+ samdb_connect(self)
+
+ # we need to find the NTDS GUID of the source DC
+ msg = self.samdb.search(base=self.samdb.get_config_basedn(),
+ expression="(&(objectCategory=server)(|(name=%s)(dNSHostName=%s)))" % (
+ ldb.binary_encode(SOURCE_DC),
+ ldb.binary_encode(SOURCE_DC)),
+ attrs=[])
+ if len(msg) == 0:
+ raise CommandError("Failed to find source DC %s" % SOURCE_DC)
+ server_dn = msg[0]['dn']
+
+ msg = self.samdb.search(base=server_dn, scope=ldb.SCOPE_ONELEVEL,
+ expression="(|(objectCategory=nTDSDSA)(objectCategory=nTDSDSARO))",
+ attrs=['objectGUID', 'options'])
+ if len(msg) == 0:
+ raise CommandError("Failed to find source NTDS DN %s" % SOURCE_DC)
+ source_dsa_guid = msg[0]['objectGUID'][0]
+ dsa_options = int(attr_default(msg, 'options', 0))
+
+
+ req_options = 0
+ if not (dsa_options & dsdb.DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL):
+ req_options |= drsuapi.DRSUAPI_DRS_WRIT_REP
+ if add_ref:
+ req_options |= drsuapi.DRSUAPI_DRS_ADD_REF
+ if sync_forced:
+ req_options |= drsuapi.DRSUAPI_DRS_SYNC_FORCED
+ if sync_all:
+ req_options |= drsuapi.DRSUAPI_DRS_SYNC_ALL
+ if full_sync:
+ req_options |= drsuapi.DRSUAPI_DRS_FULL_SYNC_NOW
+
+ try:
+ drs_utils.sendDsReplicaSync(self.drsuapi, self.drsuapi_handle, source_dsa_guid, NC, req_options)
+ except drs_utils.drsException, estr:
+ raise CommandError("DsReplicaSync failed", estr)
+ self.message("Replicate from %s to %s was successful." % (SOURCE_DC, DEST_DC))
+
+
+
+class cmd_drs_bind(Command):
+ """Show DRS capabilities of a server."""
+
+ synopsis = "%prog [<DC>] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DC?"]
+
+ def run(self, DC=None, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ self.lp = sambaopts.get_loadparm()
+ if DC is None:
+ DC = common.netcmd_dnsname(self.lp)
+ self.server = DC
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ drsuapi_connect(self)
+
+ bind_info = drsuapi.DsBindInfoCtr()
+ bind_info.length = 28
+ bind_info.info = drsuapi.DsBindInfo28()
+ (info, handle) = self.drsuapi.DsBind(misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID), bind_info)
+
+ optmap = [
+ ("DRSUAPI_SUPPORTED_EXTENSION_BASE", "DRS_EXT_BASE"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION", "DRS_EXT_ASYNCREPL"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI", "DRS_EXT_REMOVEAPI"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2", "DRS_EXT_MOVEREQ_V2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS", "DRS_EXT_GETCHG_DEFLATE"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1", "DRS_EXT_DCINFO_V1"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION", "DRS_EXT_RESTORE_USN_OPTIMIZATION"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY", "DRS_EXT_ADDENTRY"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE", "DRS_EXT_KCC_EXECUTE"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2", "DRS_EXT_ADDENTRY_V2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION", "DRS_EXT_LINKED_VALUE_REPLICATION"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2", "DRS_EXT_DCINFO_V2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD","DRS_EXT_INSTANCE_TYPE_NOT_REQ_ON_MOD"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND", "DRS_EXT_CRYPTO_BIND"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO", "DRS_EXT_GET_REPL_INFO"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION", "DRS_EXT_STRONG_ENCRYPTION"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01", "DRS_EXT_DCINFO_VFFFFFFFF"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP", "DRS_EXT_TRANSITIVE_MEMBERSHIP"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY", "DRS_EXT_ADD_SID_HISTORY"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3", "DRS_EXT_POST_BETA3"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V5", "DRS_EXT_GETCHGREQ_V5"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2", "DRS_EXT_GETMEMBERSHIPS2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6", "DRS_EXT_GETCHGREQ_V6"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS", "DRS_EXT_NONDOMAIN_NCS"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8", "DRS_EXT_GETCHGREQ_V8"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5", "DRS_EXT_GETCHGREPLY_V5"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6", "DRS_EXT_GETCHGREPLY_V6"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3", "DRS_EXT_WHISTLER_BETA3"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7", "DRS_EXT_WHISTLER_BETA3"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT", "DRS_EXT_WHISTLER_BETA3"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_XPRESS_COMPRESS", "DRS_EXT_W2K3_DEFLATE"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V10", "DRS_EXT_GETCHGREQ_V10"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_RESERVED_PART2", "DRS_EXT_RESERVED_FOR_WIN2K_OR_DOTNET_PART2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_RESERVED_PART3", "DRS_EXT_RESERVED_FOR_WIN2K_OR_DOTNET_PART3")
+ ]
+
+ optmap_ext = [
+ ("DRSUAPI_SUPPORTED_EXTENSION_ADAM", "DRS_EXT_ADAM"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_LH_BETA2", "DRS_EXT_LH_BETA2"),
+ ("DRSUAPI_SUPPORTED_EXTENSION_RECYCLE_BIN", "DRS_EXT_RECYCLE_BIN")]
+
+ self.message("Bind to %s succeeded." % DC)
+ self.message("Extensions supported:")
+ for (opt, str) in optmap:
+ optval = getattr(drsuapi, opt, 0)
+ if info.info.supported_extensions & optval:
+ yesno = "Yes"
+ else:
+ yesno = "No "
+ self.message(" %-60s: %s (%s)" % (opt, yesno, str))
+
+ if isinstance(info.info, drsuapi.DsBindInfo48):
+ self.message("\nExtended Extensions supported:")
+ for (opt, str) in optmap_ext:
+ optval = getattr(drsuapi, opt, 0)
+ if info.info.supported_extensions_ext & optval:
+ yesno = "Yes"
+ else:
+ yesno = "No "
+ self.message(" %-60s: %s (%s)" % (opt, yesno, str))
+
+ self.message("\nSite GUID: %s" % info.info.site_guid)
+ self.message("Repl epoch: %u" % info.info.repl_epoch)
+ if isinstance(info.info, drsuapi.DsBindInfo48):
+ self.message("Forest GUID: %s" % info.info.config_dn_guid)
+
+
+
+class cmd_drs_options(Command):
+ """Query or change 'options' for NTDS Settings object of a Domain Controller."""
+
+ synopsis = "%prog [<DC>] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ["DC?"]
+
+ takes_options = [
+ Option("--dsa-option", help="DSA option to enable/disable", type="str",
+ metavar="{+|-}IS_GC | {+|-}DISABLE_INBOUND_REPL | {+|-}DISABLE_OUTBOUND_REPL | {+|-}DISABLE_NTDSCONN_XLATE" ),
+ ]
+
+ option_map = {"IS_GC": 0x00000001,
+ "DISABLE_INBOUND_REPL": 0x00000002,
+ "DISABLE_OUTBOUND_REPL": 0x00000004,
+ "DISABLE_NTDSCONN_XLATE": 0x00000008}
+
+ def run(self, DC=None, dsa_option=None,
+ sambaopts=None, credopts=None, versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ if DC is None:
+ DC = common.netcmd_dnsname(self.lp)
+ self.server = DC
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ samdb_connect(self)
+
+ ntds_dn = self.samdb.get_dsServiceName()
+ res = self.samdb.search(base=ntds_dn, scope=ldb.SCOPE_BASE, attrs=["options"])
+ dsa_opts = int(res[0]["options"][0])
+
+ # print out current DSA options
+ cur_opts = [x for x in self.option_map if self.option_map[x] & dsa_opts]
+ self.message("Current DSA options: " + ", ".join(cur_opts))
+
+ # modify options
+ if dsa_option:
+ if dsa_option[:1] not in ("+", "-"):
+ raise CommandError("Unknown option %s" % dsa_option)
+ flag = dsa_option[1:]
+ if flag not in self.option_map.keys():
+ raise CommandError("Unknown option %s" % dsa_option)
+ if dsa_option[:1] == "+":
+ dsa_opts |= self.option_map[flag]
+ else:
+ dsa_opts &= ~self.option_map[flag]
+ #save new options
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.samdb, ntds_dn)
+ m["options"]= ldb.MessageElement(str(dsa_opts), ldb.FLAG_MOD_REPLACE, "options")
+ self.samdb.modify(m)
+ # print out new DSA options
+ cur_opts = [x for x in self.option_map if self.option_map[x] & dsa_opts]
+ self.message("New DSA options: " + ", ".join(cur_opts))
+
+
+class cmd_drs(SuperCommand):
+ """Directory Replication Services (DRS) management."""
+
+ subcommands = {}
+ subcommands["bind"] = cmd_drs_bind()
+ subcommands["kcc"] = cmd_drs_kcc()
+ subcommands["replicate"] = cmd_drs_replicate()
+ subcommands["showrepl"] = cmd_drs_showrepl()
+ subcommands["options"] = cmd_drs_options()
diff --git a/python/samba/netcmd/dsacl.py b/python/samba/netcmd/dsacl.py
new file mode 100644
index 0000000000..28aa843adb
--- /dev/null
+++ b/python/samba/netcmd/dsacl.py
@@ -0,0 +1,182 @@
+# Manipulate ACLs on directory objects
+#
+# Copyright (C) Nadezhda Ivanova <nivanova@samba.org> 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+from samba.dcerpc import security
+from samba.samdb import SamDB
+from samba.ndr import ndr_unpack, ndr_pack
+from samba.dcerpc.security import (
+ GUID_DRS_ALLOCATE_RIDS, GUID_DRS_CHANGE_DOMAIN_MASTER,
+ GUID_DRS_CHANGE_INFR_MASTER, GUID_DRS_CHANGE_PDC,
+ GUID_DRS_CHANGE_RID_MASTER, GUID_DRS_CHANGE_SCHEMA_MASTER,
+ GUID_DRS_GET_CHANGES, GUID_DRS_GET_ALL_CHANGES,
+ GUID_DRS_GET_FILTERED_ATTRIBUTES, GUID_DRS_MANAGE_TOPOLOGY,
+ GUID_DRS_MONITOR_TOPOLOGY, GUID_DRS_REPL_SYNCRONIZE,
+ GUID_DRS_RO_REPL_SECRET_SYNC)
+
+
+import ldb
+from ldb import SCOPE_BASE
+import re
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option,
+ )
+
+
+
+class cmd_dsacl_set(Command):
+ """Modify access list on a directory object."""
+
+ synopsis = "%prog [options]"
+ car_help = """ The access control right to allow or deny """
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server",
+ type=str, metavar="URL", dest="H"),
+ Option("--car", type="choice", choices=["change-rid",
+ "change-pdc",
+ "change-infrastructure",
+ "change-schema",
+ "change-naming",
+ "allocate_rids",
+ "get-changes",
+ "get-changes-all",
+ "get-changes-filtered",
+ "topology-manage",
+ "topology-monitor",
+ "repl-sync",
+ "ro-repl-secret-sync"],
+ help=car_help),
+ Option("--action", type="choice", choices=["allow", "deny"],
+ help="""Deny or allow access"""),
+ Option("--objectdn", help="DN of the object whose SD to modify",
+ type="string"),
+ Option("--trusteedn", help="DN of the entity that gets access",
+ type="string"),
+ Option("--sddl", help="An ACE or group of ACEs to be added on the object",
+ type="string"),
+ ]
+
+ def find_trustee_sid(self, samdb, trusteedn):
+ res = samdb.search(base=trusteedn, expression="(objectClass=*)",
+ scope=SCOPE_BASE)
+ assert(len(res) == 1)
+ return ndr_unpack( security.dom_sid,res[0]["objectSid"][0])
+
+ def modify_descriptor(self, samdb, object_dn, desc, controls=None):
+ assert(isinstance(desc, security.descriptor))
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, object_dn)
+ m["nTSecurityDescriptor"]= ldb.MessageElement(
+ (ndr_pack(desc)), ldb.FLAG_MOD_REPLACE,
+ "nTSecurityDescriptor")
+ samdb.modify(m)
+
+ def read_descriptor(self, samdb, object_dn):
+ res = samdb.search(base=object_dn, scope=SCOPE_BASE,
+ attrs=["nTSecurityDescriptor"])
+ # we should theoretically always have an SD
+ assert(len(res) == 1)
+ desc = res[0]["nTSecurityDescriptor"][0]
+ return ndr_unpack(security.descriptor, desc)
+
+ def get_domain_sid(self, samdb):
+ res = samdb.search(base=samdb.domain_dn(),
+ expression="(objectClass=*)", scope=SCOPE_BASE)
+ return ndr_unpack( security.dom_sid,res[0]["objectSid"][0])
+
+ def add_ace(self, samdb, object_dn, new_ace):
+ """Add new ace explicitly."""
+ desc = self.read_descriptor(samdb, object_dn)
+ desc_sddl = desc.as_sddl(self.get_domain_sid(samdb))
+ #TODO add bindings for descriptor manipulation and get rid of this
+ desc_aces = re.findall("\(.*?\)", desc_sddl)
+ for ace in desc_aces:
+ if ("ID" in ace):
+ desc_sddl = desc_sddl.replace(ace, "")
+ if new_ace in desc_sddl:
+ return
+ if desc_sddl.find("(") >= 0:
+ desc_sddl = desc_sddl[:desc_sddl.index("(")] + new_ace + desc_sddl[desc_sddl.index("("):]
+ else:
+ desc_sddl = desc_sddl + new_ace
+ desc = security.descriptor.from_sddl(desc_sddl, self.get_domain_sid(samdb))
+ self.modify_descriptor(samdb, object_dn, desc)
+
+ def print_new_acl(self, samdb, object_dn):
+ desc = self.read_descriptor(samdb, object_dn)
+ desc_sddl = desc.as_sddl(self.get_domain_sid(samdb))
+ self.outf.write("new descriptor for %s:\n" % object_dn)
+ self.outf.write(desc_sddl + "\n")
+
+ def run(self, car, action, objectdn, trusteedn, sddl,
+ H=None, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ if sddl is None and (car is None or action is None
+ or objectdn is None or trusteedn is None):
+ return self.usage()
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ cars = {'change-rid' : GUID_DRS_CHANGE_RID_MASTER,
+ 'change-pdc' : GUID_DRS_CHANGE_PDC,
+ 'change-infrastructure' : GUID_DRS_CHANGE_INFR_MASTER,
+ 'change-schema' : GUID_DRS_CHANGE_SCHEMA_MASTER,
+ 'change-naming' : GUID_DRS_CHANGE_DOMAIN_MASTER,
+ 'allocate_rids' : GUID_DRS_ALLOCATE_RIDS,
+ 'get-changes' : GUID_DRS_GET_CHANGES,
+ 'get-changes-all' : GUID_DRS_GET_ALL_CHANGES,
+ 'get-changes-filtered' : GUID_DRS_GET_FILTERED_ATTRIBUTES,
+ 'topology-manage' : GUID_DRS_MANAGE_TOPOLOGY,
+ 'topology-monitor' : GUID_DRS_MONITOR_TOPOLOGY,
+ 'repl-sync' : GUID_DRS_REPL_SYNCRONIZE,
+ 'ro-repl-secret-sync' : GUID_DRS_RO_REPL_SECRET_SYNC,
+ }
+ sid = self.find_trustee_sid(samdb, trusteedn)
+ if sddl:
+ new_ace = sddl
+ elif action == "allow":
+ new_ace = "(OA;;CR;%s;;%s)" % (cars[car], str(sid))
+ elif action == "deny":
+ new_ace = "(OD;;CR;%s;;%s)" % (cars[car], str(sid))
+ else:
+ raise CommandError("Wrong argument '%s'!" % action)
+
+ self.print_new_acl(samdb, objectdn)
+ self.add_ace(samdb, objectdn, new_ace)
+ self.print_new_acl(samdb, objectdn)
+
+
+class cmd_dsacl(SuperCommand):
+ """DS ACLs manipulation."""
+
+ subcommands = {}
+ subcommands["set"] = cmd_dsacl_set()
diff --git a/python/samba/netcmd/fsmo.py b/python/samba/netcmd/fsmo.py
new file mode 100644
index 0000000000..c938c915fa
--- /dev/null
+++ b/python/samba/netcmd/fsmo.py
@@ -0,0 +1,277 @@
+# Changes a FSMO role owner
+#
+# Copyright Nadezhda Ivanova 2009
+# Copyright Jelmer Vernooij 2009
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+from ldb import LdbError
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option,
+ )
+from samba.samdb import SamDB
+
+def transfer_role(outf, role, samdb):
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "")
+ if role == "rid":
+ m["becomeRidMaster"]= ldb.MessageElement(
+ "1", ldb.FLAG_MOD_REPLACE,
+ "becomeRidMaster")
+ elif role == "pdc":
+ domain_dn = samdb.domain_dn()
+ res = samdb.search(domain_dn,
+ scope=ldb.SCOPE_BASE, attrs=["objectSid"])
+ assert len(res) == 1
+ sid = res[0]["objectSid"][0]
+ m["becomePdc"]= ldb.MessageElement(
+ sid, ldb.FLAG_MOD_REPLACE,
+ "becomePdc")
+ elif role == "naming":
+ m["becomeDomainMaster"]= ldb.MessageElement(
+ "1", ldb.FLAG_MOD_REPLACE,
+ "becomeDomainMaster")
+ samdb.modify(m)
+ elif role == "infrastructure":
+ m["becomeInfrastructureMaster"]= ldb.MessageElement(
+ "1", ldb.FLAG_MOD_REPLACE,
+ "becomeInfrastructureMaster")
+ elif role == "schema":
+ m["becomeSchemaMaster"]= ldb.MessageElement(
+ "1", ldb.FLAG_MOD_REPLACE,
+ "becomeSchemaMaster")
+ else:
+ raise CommandError("Invalid FSMO role.")
+ try:
+ samdb.modify(m)
+ except LdbError, (num, msg):
+ raise CommandError("Failed to initiate transfer of '%s' role: %s" % (role, msg))
+ outf.write("FSMO transfer of '%s' role successful\n" % role)
+
+
+class cmd_fsmo_seize(Command):
+ """Seize the role."""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--force", help="Force seizing of the role without attempting to transfer first.", action="store_true"),
+ Option("--role", type="choice", choices=["rid", "pdc", "infrastructure","schema","naming","all"],
+ help="""The FSMO role to seize or transfer.\n
+rid=RidAllocationMasterRole\n
+schema=SchemaMasterRole\n
+pdc=PdcEmulationMasterRole\n
+naming=DomainNamingMasterRole\n
+infrastructure=InfrastructureMasterRole\n
+all=all of the above"""),
+ ]
+
+ takes_args = []
+
+ def seize_role(self, role, samdb, force):
+ res = samdb.search("",
+ scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
+ assert len(res) == 1
+ serviceName = res[0]["dsServiceName"][0]
+ domain_dn = samdb.domain_dn()
+ self.infrastructure_dn = "CN=Infrastructure," + domain_dn
+ self.naming_dn = "CN=Partitions,%s" % samdb.get_config_basedn()
+ self.schema_dn = str(samdb.get_schema_basedn())
+ self.rid_dn = "CN=RID Manager$,CN=System," + domain_dn
+
+ m = ldb.Message()
+ if role == "rid":
+ m.dn = ldb.Dn(samdb, self.rid_dn)
+ elif role == "pdc":
+ m.dn = ldb.Dn(samdb, domain_dn)
+ elif role == "naming":
+ m.dn = ldb.Dn(samdb, self.naming_dn)
+ elif role == "infrastructure":
+ m.dn = ldb.Dn(samdb, self.infrastructure_dn)
+ elif role == "schema":
+ m.dn = ldb.Dn(samdb, self.schema_dn)
+ else:
+ raise CommandError("Invalid FSMO role.")
+ #first try to transfer to avoid problem if the owner is still active
+ if force is None:
+ self.message("Attempting transfer...")
+ try:
+ transfer_role(self.outf, role, samdb)
+ except CommandError:
+ #transfer failed, use the big axe...
+ self.message("Transfer unsuccessful, seizing...")
+ m["fSMORoleOwner"]= ldb.MessageElement(
+ serviceName, ldb.FLAG_MOD_REPLACE,
+ "fSMORoleOwner")
+ else:
+ self.message("Will not attempt transfer, seizing...")
+ m["fSMORoleOwner"]= ldb.MessageElement(
+ serviceName, ldb.FLAG_MOD_REPLACE,
+ "fSMORoleOwner")
+ try:
+ samdb.modify(m)
+ except LdbError, (num, msg):
+ raise CommandError("Failed to initiate role seize of '%s' role: %s" % (role, msg))
+ self.outf.write("FSMO transfer of '%s' role successful\n" % role)
+
+ def run(self, force=None, H=None, role=None,
+ credopts=None, sambaopts=None, versionopts=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ if role == "all":
+ self.seize_role("rid", samdb, force)
+ self.seize_role("pdc", samdb, force)
+ self.seize_role("naming", samdb, force)
+ self.seize_role("infrastructure", samdb, force)
+ self.seize_role("schema", samdb, force)
+ else:
+ self.seize_role(role, samdb, force)
+
+
+class cmd_fsmo_show(Command):
+ """Show the roles."""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ ]
+
+ takes_args = []
+
+ def run(self, H=None, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ domain_dn = samdb.domain_dn()
+ self.infrastructure_dn = "CN=Infrastructure," + domain_dn
+ self.naming_dn = "CN=Partitions,%s" % samdb.get_config_basedn()
+ self.schema_dn = samdb.get_schema_basedn()
+ self.rid_dn = "CN=RID Manager$,CN=System," + domain_dn
+
+ res = samdb.search(self.infrastructure_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.infrastructureMaster = res[0]["fSMORoleOwner"][0]
+
+ res = samdb.search(domain_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.pdcEmulator = res[0]["fSMORoleOwner"][0]
+
+ res = samdb.search(self.naming_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.namingMaster = res[0]["fSMORoleOwner"][0]
+
+ res = samdb.search(self.schema_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.schemaMaster = res[0]["fSMORoleOwner"][0]
+
+ res = samdb.search(self.rid_dn,
+ scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"])
+ assert len(res) == 1
+ self.ridMaster = res[0]["fSMORoleOwner"][0]
+
+ self.message("InfrastructureMasterRole owner: " + self.infrastructureMaster)
+ self.message("RidAllocationMasterRole owner: " + self.ridMaster)
+ self.message("PdcEmulationMasterRole owner: " + self.pdcEmulator)
+ self.message("DomainNamingMasterRole owner: " + self.namingMaster)
+ self.message("SchemaMasterRole owner: " + self.schemaMaster)
+
+
+class cmd_fsmo_transfer(Command):
+ """Transfer the role."""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--role", type="choice", choices=["rid", "pdc", "infrastructure","schema","naming","all"],
+ help="""The FSMO role to seize or transfer.\n
+rid=RidAllocationMasterRole\n
+schema=SchemaMasterRole\n
+pdc=PdcEmulationMasterRole\n
+naming=DomainNamingMasterRole\n
+infrastructure=InfrastructureMasterRole\n
+all=all of the above"""),
+ ]
+
+ takes_args = []
+
+ def run(self, force=None, H=None, role=None,
+ credopts=None, sambaopts=None, versionopts=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ if role == "all":
+ transfer_role(self.outf, "rid", samdb)
+ transfer_role(self.outf, "pdc", samdb)
+ transfer_role(self.outf, "naming", samdb)
+ transfer_role(self.outf, "infrastructure", samdb)
+ transfer_role(self.outf, "schema", samdb)
+ else:
+ transfer_role(self.outf, role, samdb)
+
+
+class cmd_fsmo(SuperCommand):
+ """Flexible Single Master Operations (FSMO) roles management."""
+
+ subcommands = {}
+ subcommands["seize"] = cmd_fsmo_seize()
+ subcommands["show"] = cmd_fsmo_show()
+ subcommands["transfer"] = cmd_fsmo_transfer()
diff --git a/python/samba/netcmd/gpo.py b/python/samba/netcmd/gpo.py
new file mode 100644
index 0000000000..23b562eb63
--- /dev/null
+++ b/python/samba/netcmd/gpo.py
@@ -0,0 +1,1177 @@
+# implement samba_tool gpo commands
+#
+# Copyright Andrew Tridgell 2010
+# Copyright Amitay Isaacs 2011-2012 <amitay@gmail.com>
+#
+# based on C implementation by Guenther Deschner and Wilco Baan Hofman
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import samba.getopt as options
+import ldb
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ Option,
+ SuperCommand,
+ )
+from samba.samdb import SamDB
+from samba import dsdb
+from samba.dcerpc import security
+from samba.ndr import ndr_unpack
+import samba.security
+import samba.auth
+from samba.auth import AUTH_SESSION_INFO_DEFAULT_GROUPS, AUTH_SESSION_INFO_AUTHENTICATED, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
+from samba.netcmd.common import netcmd_finddc
+from samba import policy
+from samba import smb
+import uuid
+from samba.ntacls import dsacl2fsacl
+from samba.dcerpc import nbt
+from samba.net import Net
+
+
+def samdb_connect(ctx):
+ '''make a ldap connection to the server'''
+ try:
+ ctx.samdb = SamDB(url=ctx.url,
+ session_info=system_session(),
+ credentials=ctx.creds, lp=ctx.lp)
+ except Exception, e:
+ raise CommandError("LDAP connection to %s failed " % ctx.url, e)
+
+
+def attr_default(msg, attrname, default):
+ '''get an attribute from a ldap msg with a default'''
+ if attrname in msg:
+ return msg[attrname][0]
+ return default
+
+
+def gpo_flags_string(value):
+ '''return gpo flags string'''
+ flags = policy.get_gpo_flags(value)
+ if not flags:
+ ret = 'NONE'
+ else:
+ ret = ' '.join(flags)
+ return ret
+
+
+def gplink_options_string(value):
+ '''return gplink options string'''
+ options = policy.get_gplink_options(value)
+ if not options:
+ ret = 'NONE'
+ else:
+ ret = ' '.join(options)
+ return ret
+
+
+def parse_gplink(gplink):
+ '''parse a gPLink into an array of dn and options'''
+ ret = []
+ a = gplink.split(']')
+ for g in a:
+ if not g:
+ continue
+ d = g.split(';')
+ if len(d) != 2 or not d[0].startswith("[LDAP://"):
+ raise RuntimeError("Badly formed gPLink '%s'" % g)
+ ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
+ return ret
+
+
+def encode_gplink(gplist):
+ '''Encode an array of dn and options into gPLink string'''
+ ret = ''
+ for g in gplist:
+ ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
+ return ret
+
+
+def dc_url(lp, creds, url=None, dc=None):
+ '''If URL is not specified, return URL for writable DC.
+ If dc is provided, use that to construct ldap URL'''
+
+ if url is None:
+ if dc is None:
+ try:
+ dc = netcmd_finddc(lp, creds)
+ except Exception, e:
+ raise RuntimeError("Could not find a DC for domain", e)
+ url = 'ldap://' + dc
+ return url
+
+
+def get_gpo_dn(samdb, gpo):
+ '''Construct the DN for gpo'''
+
+ dn = samdb.get_default_basedn()
+ dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
+ dn.add_child(ldb.Dn(samdb, "CN=%s" % gpo))
+ return dn
+
+
+def get_gpo_info(samdb, gpo=None, displayname=None, dn=None,
+ sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL|security.SECINFO_SACL):
+ '''Get GPO information using gpo, displayname or dn'''
+
+ policies_dn = samdb.get_default_basedn()
+ policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
+
+ base_dn = policies_dn
+ search_expr = "(objectClass=groupPolicyContainer)"
+ search_scope = ldb.SCOPE_ONELEVEL
+
+ if gpo is not None:
+ search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
+
+ if displayname is not None:
+ search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
+
+ if dn is not None:
+ base_dn = dn
+ search_scope = ldb.SCOPE_BASE
+
+ try:
+ msg = samdb.search(base=base_dn, scope=search_scope,
+ expression=search_expr,
+ attrs=['nTSecurityDescriptor',
+ 'versionNumber',
+ 'flags',
+ 'name',
+ 'displayName',
+ 'gPCFileSysPath'],
+ controls=['sd_flags:1:%d' % sd_flags])
+ except Exception, e:
+ if gpo is not None:
+ mesg = "Cannot get information for GPO %s" % gpo
+ else:
+ mesg = "Cannot get information for GPOs"
+ raise CommandError(mesg, e)
+
+ return msg
+
+
+def get_gpo_containers(samdb, gpo):
+ '''lists dn of containers for a GPO'''
+
+ search_expr = "(&(objectClass=*)(gPLink=*%s*))" % gpo
+ try:
+ msg = samdb.search(expression=search_expr, attrs=['gPLink'])
+ except Exception, e:
+ raise CommandError("Could not find container(s) with GPO %s" % gpo, e)
+
+ return msg
+
+
+def del_gpo_link(samdb, container_dn, gpo):
+ '''delete GPO link for the container'''
+ # Check if valid Container DN and get existing GPlinks
+ try:
+ msg = samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
+ expression="(objectClass=*)",
+ attrs=['gPLink'])[0]
+ except Exception, e:
+ raise CommandError("Container '%s' does not exist" % container_dn, e)
+
+ found = False
+ gpo_dn = str(get_gpo_dn(samdb, gpo))
+ if 'gPLink' in msg:
+ gplist = parse_gplink(msg['gPLink'][0])
+ for g in gplist:
+ if g['dn'].lower() == gpo_dn.lower():
+ gplist.remove(g)
+ found = True
+ break
+ else:
+ raise CommandError("No GPO(s) linked to this container")
+
+ if not found:
+ raise CommandError("GPO '%s' not linked to this container" % gpo)
+
+ m = ldb.Message()
+ m.dn = container_dn
+ if gplist:
+ gplink_str = encode_gplink(gplist)
+ m['r0'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
+ else:
+ m['d0'] = ldb.MessageElement(msg['gPLink'][0], ldb.FLAG_MOD_DELETE, 'gPLink')
+ try:
+ samdb.modify(m)
+ except Exception, e:
+ raise CommandError("Error removing GPO from container", e)
+
+
+def parse_unc(unc):
+ '''Parse UNC string into a hostname, a service, and a filepath'''
+ if unc.startswith('\\\\') and unc.startswith('//'):
+ raise ValueError("UNC doesn't start with \\\\ or //")
+ tmp = unc[2:].split('/', 2)
+ if len(tmp) == 3:
+ return tmp
+ tmp = unc[2:].split('\\', 2)
+ if len(tmp) == 3:
+ return tmp
+ raise ValueError("Invalid UNC string: %s" % unc)
+
+
+def copy_directory_remote_to_local(conn, remotedir, localdir):
+ if not os.path.isdir(localdir):
+ os.mkdir(localdir)
+ r_dirs = [ remotedir ]
+ l_dirs = [ localdir ]
+ while r_dirs:
+ r_dir = r_dirs.pop()
+ l_dir = l_dirs.pop()
+
+ dirlist = conn.list(r_dir)
+ for e in dirlist:
+ r_name = r_dir + '\\' + e['name']
+ l_name = os.path.join(l_dir, e['name'])
+
+ if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
+ r_dirs.append(r_name)
+ l_dirs.append(l_name)
+ os.mkdir(l_name)
+ else:
+ data = conn.loadfile(r_name)
+ file(l_name, 'w').write(data)
+
+
+def copy_directory_local_to_remote(conn, localdir, remotedir):
+ if not conn.chkpath(remotedir):
+ conn.mkdir(remotedir)
+ l_dirs = [ localdir ]
+ r_dirs = [ remotedir ]
+ while l_dirs:
+ l_dir = l_dirs.pop()
+ r_dir = r_dirs.pop()
+
+ dirlist = os.listdir(l_dir)
+ for e in dirlist:
+ l_name = os.path.join(l_dir, e)
+ r_name = r_dir + '\\' + e
+
+ if os.path.isdir(l_name):
+ l_dirs.append(l_name)
+ r_dirs.append(r_name)
+ conn.mkdir(r_name)
+ else:
+ data = file(l_name, 'r').read()
+ conn.savefile(r_name, data)
+
+
+def create_directory_hier(conn, remotedir):
+ elems = remotedir.replace('/', '\\').split('\\')
+ path = ""
+ for e in elems:
+ path = path + '\\' + e
+ if not conn.chkpath(path):
+ conn.mkdir(path)
+
+
+class cmd_listall(Command):
+ """List all GPOs."""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H")
+ ]
+
+ def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+
+ msg = get_gpo_info(self.samdb, None)
+
+ for m in msg:
+ self.outf.write("GPO : %s\n" % m['name'][0])
+ self.outf.write("display name : %s\n" % m['displayName'][0])
+ self.outf.write("path : %s\n" % m['gPCFileSysPath'][0])
+ self.outf.write("dn : %s\n" % m.dn)
+ self.outf.write("version : %s\n" % attr_default(m, 'versionNumber', '0'))
+ self.outf.write("flags : %s\n" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
+ self.outf.write("\n")
+
+
+class cmd_list(Command):
+ """List GPOs for an account."""
+
+ synopsis = "%prog <username> [options]"
+
+ takes_args = ['username']
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server",
+ type=str, metavar="URL", dest="H")
+ ]
+
+ def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+
+ try:
+ msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
+ (ldb.binary_encode(username),ldb.binary_encode(username)))
+ user_dn = msg[0].dn
+ except Exception:
+ raise CommandError("Failed to find account %s" % username)
+
+ # check if its a computer account
+ try:
+ msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
+ is_computer = 'computer' in msg['objectClass']
+ except Exception:
+ raise CommandError("Failed to find objectClass for user %s" % username)
+
+ session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
+ AUTH_SESSION_INFO_AUTHENTICATED )
+
+ # When connecting to a remote server, don't look up the local privilege DB
+ if self.url is not None and self.url.startswith('ldap'):
+ session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
+
+ session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
+ session_info_flags=session_info_flags)
+
+ token = session.security_token
+
+ gpos = []
+
+ inherit = True
+ dn = ldb.Dn(self.samdb, str(user_dn)).parent()
+ while True:
+ msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
+ if 'gPLink' in msg:
+ glist = parse_gplink(msg['gPLink'][0])
+ for g in glist:
+ if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
+ continue
+ if g['options'] & dsdb.GPLINK_OPT_DISABLE:
+ continue
+
+ try:
+ sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL
+ gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
+ attrs=['name', 'displayName', 'flags',
+ 'nTSecurityDescriptor'],
+ controls=['sd_flags:1:%d' % sd_flags])
+ secdesc_ndr = gmsg[0]['nTSecurityDescriptor'][0]
+ secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
+ except Exception:
+ self.outf.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
+ g['dn'])
+ continue
+
+ try:
+ samba.security.access_check(secdesc, token,
+ security.SEC_STD_READ_CONTROL |
+ security.SEC_ADS_LIST |
+ security.SEC_ADS_READ_PROP)
+ except RuntimeError:
+ self.outf.write("Failed access check on %s\n" % msg.dn)
+ continue
+
+ # check the flags on the GPO
+ flags = int(attr_default(gmsg[0], 'flags', 0))
+ if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
+ continue
+ if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
+ continue
+ gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
+
+ # check if this blocks inheritance
+ gpoptions = int(attr_default(msg, 'gPOptions', 0))
+ if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
+ inherit = False
+
+ if dn == self.samdb.get_default_basedn():
+ break
+ dn = dn.parent()
+
+ if is_computer:
+ msg_str = 'computer'
+ else:
+ msg_str = 'user'
+
+ self.outf.write("GPOs for %s %s\n" % (msg_str, username))
+ for g in gpos:
+ self.outf.write(" %s %s\n" % (g[0], g[1]))
+
+
+class cmd_show(Command):
+ """Show information for a GPO."""
+
+ synopsis = "%prog <gpo> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['gpo']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str)
+ ]
+
+ def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+
+ try:
+ msg = get_gpo_info(self.samdb, gpo)[0]
+ except Exception:
+ raise CommandError("GPO '%s' does not exist" % gpo)
+
+ try:
+ secdesc_ndr = msg['nTSecurityDescriptor'][0]
+ secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
+ secdesc_sddl = secdesc.as_sddl()
+ except Exception:
+ secdesc_sddl = "<hidden>"
+
+ self.outf.write("GPO : %s\n" % msg['name'][0])
+ self.outf.write("display name : %s\n" % msg['displayName'][0])
+ self.outf.write("path : %s\n" % msg['gPCFileSysPath'][0])
+ self.outf.write("dn : %s\n" % msg.dn)
+ self.outf.write("version : %s\n" % attr_default(msg, 'versionNumber', '0'))
+ self.outf.write("flags : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
+ self.outf.write("ACL : %s\n" % secdesc_sddl)
+ self.outf.write("\n")
+
+
+class cmd_getlink(Command):
+ """List GPO Links for a container."""
+
+ synopsis = "%prog <container_dn> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['container_dn']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str)
+ ]
+
+ def run(self, container_dn, H=None, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+
+ try:
+ msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
+ expression="(objectClass=*)",
+ attrs=['gPLink'])[0]
+ except Exception:
+ raise CommandError("Container '%s' does not exist" % container_dn)
+
+ if msg['gPLink']:
+ self.outf.write("GPO(s) linked to DN %s\n" % container_dn)
+ gplist = parse_gplink(msg['gPLink'][0])
+ for g in gplist:
+ msg = get_gpo_info(self.samdb, dn=g['dn'])
+ self.outf.write(" GPO : %s\n" % msg[0]['name'][0])
+ self.outf.write(" Name : %s\n" % msg[0]['displayName'][0])
+ self.outf.write(" Options : %s\n" % gplink_options_string(g['options']))
+ self.outf.write("\n")
+ else:
+ self.outf.write("No GPO(s) linked to DN=%s\n" % container_dn)
+
+
+class cmd_setlink(Command):
+ """Add or update a GPO link to a container."""
+
+ synopsis = "%prog <container_dn> <gpo> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['container_dn', 'gpo']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str),
+ Option("--disable", dest="disabled", default=False, action='store_true',
+ help="Disable policy"),
+ Option("--enforce", dest="enforced", default=False, action='store_true',
+ help="Enforce policy")
+ ]
+
+ def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
+ sambaopts=None, credopts=None, versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+
+ gplink_options = 0
+ if disabled:
+ gplink_options |= dsdb.GPLINK_OPT_DISABLE
+ if enforced:
+ gplink_options |= dsdb.GPLINK_OPT_ENFORCE
+
+ # Check if valid GPO DN
+ try:
+ msg = get_gpo_info(self.samdb, gpo=gpo)[0]
+ except Exception:
+ raise CommandError("GPO '%s' does not exist" % gpo)
+ gpo_dn = str(get_gpo_dn(self.samdb, gpo))
+
+ # Check if valid Container DN
+ try:
+ msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
+ expression="(objectClass=*)",
+ attrs=['gPLink'])[0]
+ except Exception:
+ raise CommandError("Container '%s' does not exist" % container_dn)
+
+ # Update existing GPlinks or Add new one
+ existing_gplink = False
+ if 'gPLink' in msg:
+ gplist = parse_gplink(msg['gPLink'][0])
+ existing_gplink = True
+ found = False
+ for g in gplist:
+ if g['dn'].lower() == gpo_dn.lower():
+ g['options'] = gplink_options
+ found = True
+ break
+ if found:
+ raise CommandError("GPO '%s' already linked to this container" % gpo)
+ else:
+ gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
+ else:
+ gplist = []
+ gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
+
+ gplink_str = encode_gplink(gplist)
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.samdb, container_dn)
+
+ if existing_gplink:
+ m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
+ else:
+ m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
+
+ try:
+ self.samdb.modify(m)
+ except Exception, e:
+ raise CommandError("Error adding GPO Link", e)
+
+ self.outf.write("Added/Updated GPO link\n")
+ cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
+
+
+class cmd_dellink(Command):
+ """Delete GPO link from a container."""
+
+ synopsis = "%prog <container_dn> <gpo> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['container', 'gpo']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str),
+ ]
+
+ def run(self, container, gpo, H=None, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+
+ # Check if valid GPO
+ try:
+ get_gpo_info(self.samdb, gpo=gpo)[0]
+ except Exception:
+ raise CommandError("GPO '%s' does not exist" % gpo)
+
+ container_dn = ldb.Dn(self.samdb, container)
+ del_gpo_link(self.samdb, container_dn, gpo)
+ self.outf.write("Deleted GPO link.\n")
+ cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
+
+
+class cmd_listcontainers(Command):
+ """List all linked containers for a GPO."""
+
+ synopsis = "%prog <gpo> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['gpo']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str)
+ ]
+
+ def run(self, gpo, H=None, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+
+ msg = get_gpo_containers(self.samdb, gpo)
+ if len(msg):
+ self.outf.write("Container(s) using GPO %s\n" % gpo)
+ for m in msg:
+ self.outf.write(" DN: %s\n" % m['dn'])
+ else:
+ self.outf.write("No Containers using GPO %s\n" % gpo)
+
+
+class cmd_getinheritance(Command):
+ """Get inheritance flag for a container."""
+
+ synopsis = "%prog <container_dn> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['container_dn']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str)
+ ]
+
+ def run(self, container_dn, H=None, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+
+ try:
+ msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
+ expression="(objectClass=*)",
+ attrs=['gPOptions'])[0]
+ except Exception:
+ raise CommandError("Container '%s' does not exist" % container_dn)
+
+ inheritance = 0
+ if 'gPOptions' in msg:
+ inheritance = int(msg['gPOptions'][0])
+
+ if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
+ self.outf.write("Container has GPO_BLOCK_INHERITANCE\n")
+ else:
+ self.outf.write("Container has GPO_INHERIT\n")
+
+
+class cmd_setinheritance(Command):
+ """Set inheritance flag on a container."""
+
+ synopsis = "%prog <container_dn> <block|inherit> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = [ 'container_dn', 'inherit_state' ]
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str)
+ ]
+
+ def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ if inherit_state.lower() == 'block':
+ inheritance = dsdb.GPO_BLOCK_INHERITANCE
+ elif inherit_state.lower() == 'inherit':
+ inheritance = dsdb.GPO_INHERIT
+ else:
+ raise CommandError("Unknown inheritance state (%s)" % inherit_state)
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ samdb_connect(self)
+ try:
+ msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
+ expression="(objectClass=*)",
+ attrs=['gPOptions'])[0]
+ except Exception:
+ raise CommandError("Container '%s' does not exist" % container_dn)
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.samdb, container_dn)
+
+ if 'gPOptions' in msg:
+ m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
+ else:
+ m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
+
+ try:
+ self.samdb.modify(m)
+ except Exception, e:
+ raise CommandError("Error setting inheritance state %s" % inherit_state, e)
+
+
+class cmd_fetch(Command):
+ """Download a GPO."""
+
+ synopsis = "%prog <gpo> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['gpo']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str),
+ Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
+ ]
+
+ def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ # We need to know writable DC to setup SMB connection
+ if H and H.startswith('ldap://'):
+ dc_hostname = H[7:]
+ self.url = H
+ else:
+ dc_hostname = netcmd_finddc(self.lp, self.creds)
+ self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
+
+ samdb_connect(self)
+ try:
+ msg = get_gpo_info(self.samdb, gpo)[0]
+ except Exception:
+ raise CommandError("GPO '%s' does not exist" % gpo)
+
+ # verify UNC path
+ unc = msg['gPCFileSysPath'][0]
+ try:
+ [dom_name, service, sharepath] = parse_unc(unc)
+ except ValueError:
+ raise CommandError("Invalid GPO path (%s)" % unc)
+
+ # SMB connect to DC
+ try:
+ conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
+ except Exception:
+ raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
+
+ # Copy GPT
+ if tmpdir is None:
+ tmpdir = "/tmp"
+ if not os.path.isdir(tmpdir):
+ raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
+
+ localdir = os.path.join(tmpdir, "policy")
+ if not os.path.isdir(localdir):
+ os.mkdir(localdir)
+
+ gpodir = os.path.join(localdir, gpo)
+ if os.path.isdir(gpodir):
+ raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
+
+ try:
+ os.mkdir(gpodir)
+ copy_directory_remote_to_local(conn, sharepath, gpodir)
+ except Exception, e:
+ # FIXME: Catch more specific exception
+ raise CommandError("Error copying GPO from DC", e)
+ self.outf.write('GPO copied to %s\n' % gpodir)
+
+
+class cmd_create(Command):
+ """Create an empty GPO."""
+
+ synopsis = "%prog <displayname> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['displayname']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str),
+ Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
+ ]
+
+ def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ net = Net(creds=self.creds, lp=self.lp)
+
+ # We need to know writable DC to setup SMB connection
+ if H and H.startswith('ldap://'):
+ dc_hostname = H[7:]
+ self.url = H
+ flags = (nbt.NBT_SERVER_LDAP |
+ nbt.NBT_SERVER_DS |
+ nbt.NBT_SERVER_WRITABLE)
+ cldap_ret = net.finddc(address=dc_hostname, flags=flags)
+ else:
+ flags = (nbt.NBT_SERVER_LDAP |
+ nbt.NBT_SERVER_DS |
+ nbt.NBT_SERVER_WRITABLE)
+ cldap_ret = net.finddc(domain=self.lp.get('realm'), flags=flags)
+ dc_hostname = cldap_ret.pdc_dns_name
+ self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
+
+ samdb_connect(self)
+
+ msg = get_gpo_info(self.samdb, displayname=displayname)
+ if msg.count > 0:
+ raise CommandError("A GPO already existing with name '%s'" % displayname)
+
+ # Create new GUID
+ guid = str(uuid.uuid4())
+ gpo = "{%s}" % guid.upper()
+ realm = cldap_ret.dns_domain
+ unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
+
+ # Create GPT
+ if tmpdir is None:
+ tmpdir = "/tmp"
+ if not os.path.isdir(tmpdir):
+ raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
+
+ localdir = os.path.join(tmpdir, "policy")
+ if not os.path.isdir(localdir):
+ os.mkdir(localdir)
+
+ gpodir = os.path.join(localdir, gpo)
+ if os.path.isdir(gpodir):
+ raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
+
+ try:
+ os.mkdir(gpodir)
+ os.mkdir(os.path.join(gpodir, "Machine"))
+ os.mkdir(os.path.join(gpodir, "User"))
+ gpt_contents = "[General]\r\nVersion=0\r\n"
+ file(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
+ except Exception, e:
+ raise CommandError("Error Creating GPO files", e)
+
+ # Connect to DC over SMB
+ [dom_name, service, sharepath] = parse_unc(unc_path)
+ try:
+ conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
+ except Exception, e:
+ raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
+
+ self.samdb.transaction_start()
+ try:
+ # Add cn=<guid>
+ gpo_dn = get_gpo_dn(self.samdb, gpo)
+
+ m = ldb.Message()
+ m.dn = gpo_dn
+ m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
+ self.samdb.add(m)
+
+ # Add cn=User,cn=<guid>
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
+ m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
+ self.samdb.add(m)
+
+ # Add cn=Machine,cn=<guid>
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
+ m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
+ self.samdb.add(m)
+
+ # Get new security descriptor
+ ds_sd_flags = ( security.SECINFO_OWNER |
+ security.SECINFO_GROUP |
+ security.SECINFO_DACL )
+ msg = get_gpo_info(self.samdb, gpo=gpo, sd_flags=ds_sd_flags)[0]
+ ds_sd_ndr = msg['nTSecurityDescriptor'][0]
+ ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
+
+ # Create a file system security descriptor
+ domain_sid = security.dom_sid(self.samdb.get_domain_sid())
+ sddl = dsacl2fsacl(ds_sd, domain_sid)
+ fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
+
+ # Copy GPO directory
+ create_directory_hier(conn, sharepath)
+
+ # Set ACL
+ sio = ( security.SECINFO_OWNER |
+ security.SECINFO_GROUP |
+ security.SECINFO_DACL |
+ security.SECINFO_PROTECTED_DACL )
+ conn.set_acl(sharepath, fs_sd, sio)
+
+ # Copy GPO files over SMB
+ copy_directory_local_to_remote(conn, gpodir, sharepath)
+
+ m = ldb.Message()
+ m.dn = gpo_dn
+ m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_REPLACE, "displayName")
+ m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_REPLACE, "gPCFileSysPath")
+ m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "versionNumber")
+ m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_REPLACE, "gpcFunctionalityVersion")
+ m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "flags")
+ controls=["permissive_modify:0"]
+ self.samdb.modify(m, controls=controls)
+ except Exception:
+ self.samdb.transaction_cancel()
+ raise
+ else:
+ self.samdb.transaction_commit()
+
+ self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
+
+
+class cmd_del(Command):
+ """Delete a GPO."""
+
+ synopsis = "%prog <gpo> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_args = ['gpo']
+
+ takes_options = [
+ Option("-H", help="LDB URL for database or target server", type=str),
+ ]
+
+ def run(self, gpo, H=None, sambaopts=None, credopts=None,
+ versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ # We need to know writable DC to setup SMB connection
+ if H and H.startswith('ldap://'):
+ dc_hostname = H[7:]
+ self.url = H
+ else:
+ dc_hostname = netcmd_finddc(self.lp, self.creds)
+ self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
+
+ samdb_connect(self)
+
+ # Check if valid GPO
+ try:
+ msg = get_gpo_info(self.samdb, gpo=gpo)[0]
+ unc_path = msg['gPCFileSysPath'][0]
+ except Exception:
+ raise CommandError("GPO '%s' does not exist" % gpo)
+
+ # Connect to DC over SMB
+ [dom_name, service, sharepath] = parse_unc(unc_path)
+ try:
+ conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
+ except Exception, e:
+ raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
+
+ self.samdb.transaction_start()
+ try:
+ # Check for existing links
+ msg = get_gpo_containers(self.samdb, gpo)
+
+ if len(msg):
+ self.outf.write("GPO %s is linked to containers\n" % gpo)
+ for m in msg:
+ del_gpo_link(self.samdb, m['dn'], gpo)
+ self.outf.write(" Removed link from %s.\n" % m['dn'])
+
+ # Remove LDAP entries
+ gpo_dn = get_gpo_dn(self.samdb, gpo)
+ self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
+ self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
+ self.samdb.delete(gpo_dn)
+
+ # Remove GPO files
+ conn.deltree(sharepath)
+
+ except Exception:
+ self.samdb.transaction_cancel()
+ raise
+ else:
+ self.samdb.transaction_commit()
+
+ self.outf.write("GPO %s deleted.\n" % gpo)
+
+
+class cmd_aclcheck(Command):
+ """Check all GPOs have matching LDAP and DS ACLs."""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H")
+ ]
+
+ def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
+
+ self.lp = sambaopts.get_loadparm()
+ self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
+
+ self.url = dc_url(self.lp, self.creds, H)
+
+ # We need to know writable DC to setup SMB connection
+ if H and H.startswith('ldap://'):
+ dc_hostname = H[7:]
+ self.url = H
+ else:
+ dc_hostname = netcmd_finddc(self.lp, self.creds)
+ self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
+
+ samdb_connect(self)
+
+ msg = get_gpo_info(self.samdb, None)
+
+ for m in msg:
+ # verify UNC path
+ unc = m['gPCFileSysPath'][0]
+ try:
+ [dom_name, service, sharepath] = parse_unc(unc)
+ except ValueError:
+ raise CommandError("Invalid GPO path (%s)" % unc)
+
+ # SMB connect to DC
+ try:
+ conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
+ except Exception:
+ raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
+
+ fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
+
+ ds_sd_ndr = m['nTSecurityDescriptor'][0]
+ ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
+
+ # Create a file system security descriptor
+ domain_sid = security.dom_sid(self.samdb.get_domain_sid())
+ expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
+
+ if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
+ raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
+
+
+class cmd_gpo(SuperCommand):
+ """Group Policy Object (GPO) management."""
+
+ subcommands = {}
+ subcommands["listall"] = cmd_listall()
+ subcommands["list"] = cmd_list()
+ subcommands["show"] = cmd_show()
+ subcommands["getlink"] = cmd_getlink()
+ subcommands["setlink"] = cmd_setlink()
+ subcommands["dellink"] = cmd_dellink()
+ subcommands["listcontainers"] = cmd_listcontainers()
+ subcommands["getinheritance"] = cmd_getinheritance()
+ subcommands["setinheritance"] = cmd_setinheritance()
+ subcommands["fetch"] = cmd_fetch()
+ subcommands["create"] = cmd_create()
+ subcommands["del"] = cmd_del()
+ subcommands["aclcheck"] = cmd_aclcheck()
diff --git a/python/samba/netcmd/group.py b/python/samba/netcmd/group.py
new file mode 100644
index 0000000000..731d4c1564
--- /dev/null
+++ b/python/samba/netcmd/group.py
@@ -0,0 +1,376 @@
+# Adds a new user to a Samba4 server
+# Copyright Jelmer Vernooij 2008
+#
+# Based on the original in EJS:
+# Copyright Andrew Tridgell 2005
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import samba.getopt as options
+from samba.netcmd import Command, SuperCommand, CommandError, Option
+import ldb
+from samba.ndr import ndr_unpack
+from samba.dcerpc import security
+
+from getpass import getpass
+from samba.auth import system_session
+from samba.samdb import SamDB
+from samba.dsdb import (
+ GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
+ GTYPE_SECURITY_GLOBAL_GROUP,
+ GTYPE_SECURITY_UNIVERSAL_GROUP,
+ GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
+ GTYPE_DISTRIBUTION_GLOBAL_GROUP,
+ GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
+)
+
+security_group = dict({"Domain": GTYPE_SECURITY_DOMAIN_LOCAL_GROUP, "Global": GTYPE_SECURITY_GLOBAL_GROUP, "Universal": GTYPE_SECURITY_UNIVERSAL_GROUP})
+distribution_group = dict({"Domain": GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP, "Global": GTYPE_DISTRIBUTION_GLOBAL_GROUP, "Universal": GTYPE_DISTRIBUTION_UNIVERSAL_GROUP})
+
+
+class cmd_group_add(Command):
+ """Creates a new AD group.
+
+This command creates a new Active Directory group. The groupname specified on the command is a unique sAMAccountName.
+
+An Active Directory group may contain user and computer accounts as well as other groups. An administrator creates a group and adds members to that group so they can be managed as a single entity. This helps to simplify security and system administration.
+
+Groups may also be used to establish email distribution lists, using --group-type=Distribution.
+
+Groups are located in domains in organizational units (OUs). The group's scope is a characteristic of the group that designates the extent to which the group is applied within the domain tree or forest.
+
+The group location (OU), type (security or distribution) and scope may all be specified on the samba-tool command when the group is created.
+
+The command may be run from the root userid or another authorized userid. The
+-H or --URL= option can be used to execute the command on a remote server.
+
+Example1:
+samba-tool group add Group1 -H ldap://samba.samdom.example.com --description='Simple group'
+
+Example1 adds a new group with the name Group1 added to the Users container on a remote LDAP server. The -U parameter is used to pass the userid and password of a user that exists on the remote server and is authorized to issue the command on that server. It defaults to the security type and global scope.
+
+Example2:
+sudo samba-tool group add Group2 --group-type=Distribution
+
+Example2 adds a new distribution group to the local server. The command is run under root using the sudo command.
+"""
+
+ synopsis = "%prog <groupname> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--groupou",
+ help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
+ type=str),
+ Option("--group-scope", type="choice", choices=["Domain", "Global", "Universal"],
+ help="Group scope (Domain | Global | Universal)"),
+ Option("--group-type", type="choice", choices=["Security", "Distribution"],
+ help="Group type (Security | Distribution)"),
+ Option("--description", help="Group's description", type=str),
+ Option("--mail-address", help="Group's email address", type=str),
+ Option("--notes", help="Groups's notes", type=str),
+ ]
+
+ takes_args = ["groupname"]
+
+ def run(self, groupname, credopts=None, sambaopts=None,
+ versionopts=None, H=None, groupou=None, group_scope=None,
+ group_type=None, description=None, mail_address=None, notes=None):
+
+ if (group_type or "Security") == "Security":
+ gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
+ else:
+ gtype = distribution_group.get(group_scope, GTYPE_DISTRIBUTION_GLOBAL_GROUP)
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.newgroup(groupname, groupou=groupou, grouptype = gtype,
+ description=description, mailaddress=mail_address, notes=notes)
+ except Exception, e:
+ # FIXME: catch more specific exception
+ raise CommandError('Failed to create group "%s"' % groupname, e)
+ self.outf.write("Added group %s\n" % groupname)
+
+
+class cmd_group_delete(Command):
+ """Deletes an AD group.
+
+The command deletes an existing AD group from the Active Directory domain. The groupname specified on the command is the sAMAccountName.
+
+Deleting a group is a permanent operation. When a group is deleted, all permissions and rights that users in the group had inherited from the group account are deleted as well.
+
+The command may be run from the root userid or another authorized userid. The -H or --URL option can be used to execute the command on a remote server.
+
+Example1:
+samba-tool group delete Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
+
+Example1 shows how to delete an AD group from a remote LDAP server. The -U parameter is used to pass the userid and password of a user that exists on the remote server and is authorized to issue the command on that server.
+
+Example2:
+sudo samba-tool group delete Group2
+
+Example2 deletes group Group2 from the local server. The command is run under root using the sudo command.
+"""
+
+ synopsis = "%prog <groupname> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ ]
+
+ takes_args = ["groupname"]
+
+ def run(self, groupname, credopts=None, sambaopts=None, versionopts=None, H=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.deletegroup(groupname)
+ except Exception, e:
+ # FIXME: catch more specific exception
+ raise CommandError('Failed to remove group "%s"' % groupname, e)
+ self.outf.write("Deleted group %s\n" % groupname)
+
+
+class cmd_group_add_members(Command):
+ """Add members to an AD group.
+
+This command adds one or more members to an existing Active Directory group. The command accepts one or more group member names seperated by commas. A group member may be a user or computer account or another Active Directory group.
+
+When a member is added to a group the member may inherit permissions and rights from the group. Likewise, when permission or rights of a group are changed, the changes may reflect in the members through inheritance.
+
+Example1:
+samba-tool group addmembers supergroup Group1,Group2,User1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
+
+Example1 shows how to add two groups, Group1 and Group2 and one user account, User1, to the existing AD group named supergroup. The command will be run on a remote server specified with the -H. The -U parameter is used to pass the userid and password of a user authorized to issue the command on the remote server.
+
+Example2:
+sudo samba-tool group addmembers supergroup User2
+
+Example2 shows how to add a single user account, User2, to the supergroup AD group. It uses the sudo command to run as root when issuing the command.
+"""
+
+ synopsis = "%prog <groupname> <listofmembers> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ ]
+
+ takes_args = ["groupname", "listofmembers"]
+
+ def run(self, groupname, listofmembers, credopts=None, sambaopts=None,
+ versionopts=None, H=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ groupmembers = listofmembers.split(',')
+ samdb.add_remove_group_members(groupname, groupmembers,
+ add_members_operation=True)
+ except Exception, e:
+ # FIXME: catch more specific exception
+ raise CommandError('Failed to add members "%s" to group "%s"' % (
+ listofmembers, groupname), e)
+ self.outf.write("Added members to group %s\n" % groupname)
+
+
+class cmd_group_remove_members(Command):
+ """Remove members from an AD group.
+
+This command removes one or more members from an existing Active Directory group. The command accepts one or more group member names seperated by commas. A group member may be a user or computer account or another Active Directory group that is a member of the group specified on the command.
+
+When a member is removed from a group, inherited permissions and rights will no longer apply to the member.
+
+Example1:
+samba-tool group removemembers supergroup Group1 -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
+
+Example1 shows how to remove Group1 from supergroup. The command will run on the remote server specified on the -H parameter. The -U parameter is used to pass the userid and password of a user authorized to issue the command on the remote server.
+
+Example2:
+sudo samba-tool group removemembers supergroup User1
+
+Example2 shows how to remove a single user account, User2, from the supergroup AD group. It uses the sudo command to run as root when issuing the command.
+"""
+
+ synopsis = "%prog <groupname> <listofmembers> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ ]
+
+ takes_args = ["groupname", "listofmembers"]
+
+ def run(self, groupname, listofmembers, credopts=None, sambaopts=None,
+ versionopts=None, H=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.add_remove_group_members(groupname, listofmembers.split(","),
+ add_members_operation=False)
+ except Exception, e:
+ # FIXME: Catch more specific exception
+ raise CommandError('Failed to remove members "%s" from group "%s"' % (listofmembers, groupname), e)
+ self.outf.write("Removed members from group %s\n" % groupname)
+
+
+class cmd_group_list(Command):
+ """List all groups."""
+
+ synopsis = "%prog [options]"
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ domain_dn = samdb.domain_dn()
+ res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
+ expression=("(objectClass=group)"),
+ attrs=["samaccountname"])
+ if (len(res) == 0):
+ return
+
+ for msg in res:
+ self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
+
+
+class cmd_group_list_members(Command):
+ """List all members of an AD group.
+
+This command lists members from an existing Active Directory group. The command accepts one group name.
+
+Example1:
+samba-tool group listmembers \"Domain Users\" -H ldap://samba.samdom.example.com -Uadministrator%passw0rd
+"""
+
+ synopsis = "%prog <groupname> [options]"
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["groupname"]
+
+ def run(self, groupname, credopts=None, sambaopts=None, versionopts=None, H=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ search_filter = "(&(objectClass=group)(samaccountname=%s))" % groupname
+ res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
+ expression=(search_filter),
+ attrs=["objectSid"])
+
+ if (len(res) != 1):
+ return
+
+ group_dn = res[0].get('dn', idx=0)
+ object_sid = res[0].get('objectSid', idx=0)
+
+ object_sid = ndr_unpack(security.dom_sid, object_sid)
+ (group_dom_sid, rid) = object_sid.split()
+
+ search_filter = "(|(primaryGroupID=%s)(memberOf=%s))" % (rid, group_dn)
+ res = samdb.search(samdb.domain_dn(), scope=ldb.SCOPE_SUBTREE,
+ expression=(search_filter),
+ attrs=["samAccountName", "cn"])
+
+ if (len(res) == 0):
+ return
+
+ for msg in res:
+ member_name = msg.get("samAccountName", idx=0)
+ if member_name is None:
+ member_name = msg.get("cn", idx=0)
+ self.outf.write("%s\n" % member_name)
+
+ except Exception, e:
+ raise CommandError('Failed to list members of "%s" group ' % groupname, e)
+
+
+class cmd_group(SuperCommand):
+ """Group management."""
+
+ subcommands = {}
+ subcommands["add"] = cmd_group_add()
+ subcommands["delete"] = cmd_group_delete()
+ subcommands["addmembers"] = cmd_group_add_members()
+ subcommands["removemembers"] = cmd_group_remove_members()
+ subcommands["list"] = cmd_group_list()
+ subcommands["listmembers"] = cmd_group_list_members()
diff --git a/python/samba/netcmd/ldapcmp.py b/python/samba/netcmd/ldapcmp.py
new file mode 100644
index 0000000000..8398205e4b
--- /dev/null
+++ b/python/samba/netcmd/ldapcmp.py
@@ -0,0 +1,998 @@
+# Unix SMB/CIFS implementation.
+# A command to compare differences of objects and attributes between
+# two LDAP servers both running at the same time. It generally compares
+# one of the three pratitions DOMAIN, CONFIGURATION or SCHEMA. Users
+# that have to be provided sheould be able to read objects in any of the
+# above partitions.
+
+# Copyright (C) Zahari Zahariev <zahari.zahariev@postpath.com> 2009, 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import re
+import sys
+
+import samba
+import samba.getopt as options
+from samba import Ldb
+from samba.ndr import ndr_unpack
+from samba.dcerpc import security
+from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, ERR_NO_SUCH_OBJECT, LdbError
+from samba.netcmd import (
+ Command,
+ CommandError,
+ Option,
+ )
+
+global summary
+summary = {}
+
+class LDAPBase(object):
+
+ def __init__(self, host, creds, lp,
+ two=False, quiet=False, descriptor=False, sort_aces=False, verbose=False,
+ view="section", base="", scope="SUB",
+ outf=sys.stdout, errf=sys.stderr):
+ ldb_options = []
+ samdb_url = host
+ if not "://" in host:
+ if os.path.isfile(host):
+ samdb_url = "tdb://%s" % host
+ else:
+ samdb_url = "ldap://%s" % host
+ # use 'paged_search' module when connecting remotely
+ if samdb_url.lower().startswith("ldap://"):
+ ldb_options = ["modules:paged_searches"]
+ self.outf = outf
+ self.errf = errf
+ self.ldb = Ldb(url=samdb_url,
+ credentials=creds,
+ lp=lp,
+ options=ldb_options)
+ self.search_base = base
+ self.search_scope = scope
+ self.two_domains = two
+ self.quiet = quiet
+ self.descriptor = descriptor
+ self.sort_aces = sort_aces
+ self.view = view
+ self.verbose = verbose
+ self.host = host
+ self.base_dn = str(self.ldb.get_default_basedn())
+ self.root_dn = str(self.ldb.get_root_basedn())
+ self.config_dn = str(self.ldb.get_config_basedn())
+ self.schema_dn = str(self.ldb.get_schema_basedn())
+ self.domain_netbios = self.find_netbios()
+ self.server_names = self.find_servers()
+ self.domain_name = re.sub("[Dd][Cc]=", "", self.base_dn).replace(",", ".")
+ self.domain_sid = self.find_domain_sid()
+ self.get_guid_map()
+ self.get_sid_map()
+ #
+ # Log some domain controller specific place-holers that are being used
+ # when compare content of two DCs. Uncomment for DEBUG purposes.
+ if self.two_domains and not self.quiet:
+ self.outf.write("\n* Place-holders for %s:\n" % self.host)
+ self.outf.write(4*" " + "${DOMAIN_DN} => %s\n" %
+ self.base_dn)
+ self.outf.write(4*" " + "${DOMAIN_NETBIOS} => %s\n" %
+ self.domain_netbios)
+ self.outf.write(4*" " + "${SERVER_NAME} => %s\n" %
+ self.server_names)
+ self.outf.write(4*" " + "${DOMAIN_NAME} => %s\n" %
+ self.domain_name)
+
+ def find_domain_sid(self):
+ res = self.ldb.search(base=self.base_dn, expression="(objectClass=*)", scope=SCOPE_BASE)
+ return ndr_unpack(security.dom_sid,res[0]["objectSid"][0])
+
+ def find_servers(self):
+ """
+ """
+ res = self.ldb.search(base="OU=Domain Controllers,%s" % self.base_dn,
+ scope=SCOPE_SUBTREE, expression="(objectClass=computer)", attrs=["cn"])
+ assert len(res) > 0
+ srv = []
+ for x in res:
+ srv.append(x["cn"][0])
+ return srv
+
+ def find_netbios(self):
+ res = self.ldb.search(base="CN=Partitions,%s" % self.config_dn,
+ scope=SCOPE_SUBTREE, attrs=["nETBIOSName"])
+ assert len(res) > 0
+ for x in res:
+ if "nETBIOSName" in x.keys():
+ return x["nETBIOSName"][0]
+
+ def object_exists(self, object_dn):
+ res = None
+ try:
+ res = self.ldb.search(base=object_dn, scope=SCOPE_BASE)
+ except LdbError, (enum, estr):
+ if enum == ERR_NO_SUCH_OBJECT:
+ return False
+ raise
+ return len(res) == 1
+
+ def delete_force(self, object_dn):
+ try:
+ self.ldb.delete(object_dn)
+ except Ldb.LdbError, e:
+ assert "No such object" in str(e)
+
+ def get_attribute_name(self, key):
+ """ Returns the real attribute name
+ It resolved ranged results e.g. member;range=0-1499
+ """
+
+ r = re.compile("^([^;]+);range=(\d+)-(\d+|\*)$")
+
+ m = r.match(key)
+ if m is None:
+ return key
+
+ return m.group(1)
+
+ def get_attribute_values(self, object_dn, key, vals):
+ """ Returns list with all attribute values
+ It resolved ranged results e.g. member;range=0-1499
+ """
+
+ r = re.compile("^([^;]+);range=(\d+)-(\d+|\*)$")
+
+ m = r.match(key)
+ if m is None:
+ # no range, just return the values
+ return vals
+
+ attr = m.group(1)
+ hi = int(m.group(3))
+
+ # get additional values in a loop
+ # until we get a response with '*' at the end
+ while True:
+
+ n = "%s;range=%d-*" % (attr, hi + 1)
+ res = self.ldb.search(base=object_dn, scope=SCOPE_BASE, attrs=[n])
+ assert len(res) == 1
+ res = dict(res[0])
+ del res["dn"]
+
+ fm = None
+ fvals = None
+
+ for key in res.keys():
+ m = r.match(key)
+
+ if m is None:
+ continue
+
+ if m.group(1) != attr:
+ continue
+
+ fm = m
+ fvals = list(res[key])
+ break
+
+ if fm is None:
+ break
+
+ vals.extend(fvals)
+ if fm.group(3) == "*":
+ # if we got "*" we're done
+ break
+
+ assert int(fm.group(2)) == hi + 1
+ hi = int(fm.group(3))
+
+ return vals
+
+ def get_attributes(self, object_dn):
+ """ Returns dict with all default visible attributes
+ """
+ res = self.ldb.search(base=object_dn, scope=SCOPE_BASE, attrs=["*"])
+ assert len(res) == 1
+ res = dict(res[0])
+ # 'Dn' element is not iterable and we have it as 'distinguishedName'
+ del res["dn"]
+ for key in res.keys():
+ vals = list(res[key])
+ del res[key]
+ name = self.get_attribute_name(key)
+ res[name] = self.get_attribute_values(object_dn, key, vals)
+
+ return res
+
+ def get_descriptor_sddl(self, object_dn):
+ res = self.ldb.search(base=object_dn, scope=SCOPE_BASE, attrs=["nTSecurityDescriptor"])
+ desc = res[0]["nTSecurityDescriptor"][0]
+ desc = ndr_unpack(security.descriptor, desc)
+ return desc.as_sddl(self.domain_sid)
+
+ def guid_as_string(self, guid_blob):
+ """ Translate binary representation of schemaIDGUID to standard string representation.
+ @gid_blob: binary schemaIDGUID
+ """
+ blob = "%s" % guid_blob
+ stops = [4, 2, 2, 2, 6]
+ index = 0
+ res = ""
+ x = 0
+ while x < len(stops):
+ tmp = ""
+ y = 0
+ while y < stops[x]:
+ c = hex(ord(blob[index])).replace("0x", "")
+ c = [None, "0" + c, c][len(c)]
+ if 2 * index < len(blob):
+ tmp = c + tmp
+ else:
+ tmp += c
+ index += 1
+ y += 1
+ res += tmp + " "
+ x += 1
+ assert index == len(blob)
+ return res.strip().replace(" ", "-")
+
+ def get_guid_map(self):
+ """ Build dictionary that maps GUID to 'name' attribute found in Schema or Extended-Rights.
+ """
+ self.guid_map = {}
+ res = self.ldb.search(base=self.schema_dn,
+ expression="(schemaIdGuid=*)", scope=SCOPE_SUBTREE, attrs=["schemaIdGuid", "name"])
+ for item in res:
+ self.guid_map[self.guid_as_string(item["schemaIdGuid"]).lower()] = item["name"][0]
+ #
+ res = self.ldb.search(base="cn=extended-rights,%s" % self.config_dn,
+ expression="(rightsGuid=*)", scope=SCOPE_SUBTREE, attrs=["rightsGuid", "name"])
+ for item in res:
+ self.guid_map[str(item["rightsGuid"]).lower()] = item["name"][0]
+
+ def get_sid_map(self):
+ """ Build dictionary that maps GUID to 'name' attribute found in Schema or Extended-Rights.
+ """
+ self.sid_map = {}
+ res = self.ldb.search(base=self.base_dn,
+ expression="(objectSid=*)", scope=SCOPE_SUBTREE, attrs=["objectSid", "sAMAccountName"])
+ for item in res:
+ try:
+ self.sid_map["%s" % ndr_unpack(security.dom_sid, item["objectSid"][0])] = item["sAMAccountName"][0]
+ except KeyError:
+ pass
+
+class Descriptor(object):
+ def __init__(self, connection, dn, outf=sys.stdout, errf=sys.stderr):
+ self.outf = outf
+ self.errf = errf
+ self.con = connection
+ self.dn = dn
+ self.sddl = self.con.get_descriptor_sddl(self.dn)
+ self.dacl_list = self.extract_dacl()
+ if self.con.sort_aces:
+ self.dacl_list.sort()
+
+ def extract_dacl(self):
+ """ Extracts the DACL as a list of ACE string (with the brakets).
+ """
+ try:
+ if "S:" in self.sddl:
+ res = re.search("D:(.*?)(\(.*?\))S:", self.sddl).group(2)
+ else:
+ res = re.search("D:(.*?)(\(.*\))", self.sddl).group(2)
+ except AttributeError:
+ return []
+ return re.findall("(\(.*?\))", res)
+
+ def fix_guid(self, ace):
+ res = "%s" % ace
+ guids = re.findall("[a-z0-9]+?-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+", res)
+ # If there are not GUIDs to replace return the same ACE
+ if len(guids) == 0:
+ return res
+ for guid in guids:
+ try:
+ name = self.con.guid_map[guid.lower()]
+ res = res.replace(guid, name)
+ except KeyError:
+ # Do not bother if the GUID is not found in
+ # cn=Schema or cn=Extended-Rights
+ pass
+ return res
+
+ def fix_sid(self, ace):
+ res = "%s" % ace
+ sids = re.findall("S-[-0-9]+", res)
+ # If there are not SIDs to replace return the same ACE
+ if len(sids) == 0:
+ return res
+ for sid in sids:
+ try:
+ name = self.con.sid_map[sid]
+ res = res.replace(sid, name)
+ except KeyError:
+ # Do not bother if the SID is not found in baseDN
+ pass
+ return res
+
+ def fixit(self, ace):
+ """ Combine all replacement methods in one
+ """
+ res = "%s" % ace
+ res = self.fix_guid(res)
+ res = self.fix_sid(res)
+ return res
+
+ def diff_1(self, other):
+ res = ""
+ if len(self.dacl_list) != len(other.dacl_list):
+ res += 4*" " + "Difference in ACE count:\n"
+ res += 8*" " + "=> %s\n" % len(self.dacl_list)
+ res += 8*" " + "=> %s\n" % len(other.dacl_list)
+ #
+ i = 0
+ flag = True
+ while True:
+ self_ace = None
+ other_ace = None
+ try:
+ self_ace = "%s" % self.dacl_list[i]
+ except IndexError:
+ self_ace = ""
+ #
+ try:
+ other_ace = "%s" % other.dacl_list[i]
+ except IndexError:
+ other_ace = ""
+ if len(self_ace) + len(other_ace) == 0:
+ break
+ self_ace_fixed = "%s" % self.fixit(self_ace)
+ other_ace_fixed = "%s" % other.fixit(other_ace)
+ if self_ace_fixed != other_ace_fixed:
+ res += "%60s * %s\n" % ( self_ace_fixed, other_ace_fixed )
+ flag = False
+ else:
+ res += "%60s | %s\n" % ( self_ace_fixed, other_ace_fixed )
+ i += 1
+ return (flag, res)
+
+ def diff_2(self, other):
+ res = ""
+ if len(self.dacl_list) != len(other.dacl_list):
+ res += 4*" " + "Difference in ACE count:\n"
+ res += 8*" " + "=> %s\n" % len(self.dacl_list)
+ res += 8*" " + "=> %s\n" % len(other.dacl_list)
+ #
+ common_aces = []
+ self_aces = []
+ other_aces = []
+ self_dacl_list_fixed = []
+ other_dacl_list_fixed = []
+ [self_dacl_list_fixed.append( self.fixit(ace) ) for ace in self.dacl_list]
+ [other_dacl_list_fixed.append( other.fixit(ace) ) for ace in other.dacl_list]
+ for ace in self_dacl_list_fixed:
+ try:
+ other_dacl_list_fixed.index(ace)
+ except ValueError:
+ self_aces.append(ace)
+ else:
+ common_aces.append(ace)
+ self_aces = sorted(self_aces)
+ if len(self_aces) > 0:
+ res += 4*" " + "ACEs found only in %s:\n" % self.con.host
+ for ace in self_aces:
+ res += 8*" " + ace + "\n"
+ #
+ for ace in other_dacl_list_fixed:
+ try:
+ self_dacl_list_fixed.index(ace)
+ except ValueError:
+ other_aces.append(ace)
+ else:
+ common_aces.append(ace)
+ other_aces = sorted(other_aces)
+ if len(other_aces) > 0:
+ res += 4*" " + "ACEs found only in %s:\n" % other.con.host
+ for ace in other_aces:
+ res += 8*" " + ace + "\n"
+ #
+ common_aces = sorted(list(set(common_aces)))
+ if self.con.verbose:
+ res += 4*" " + "ACEs found in both:\n"
+ for ace in common_aces:
+ res += 8*" " + ace + "\n"
+ return (self_aces == [] and other_aces == [], res)
+
+class LDAPObject(object):
+ def __init__(self, connection, dn, summary, filter_list,
+ outf=sys.stdout, errf=sys.stderr):
+ self.outf = outf
+ self.errf = errf
+ self.con = connection
+ self.two_domains = self.con.two_domains
+ self.quiet = self.con.quiet
+ self.verbose = self.con.verbose
+ self.summary = summary
+ self.dn = dn.replace("${DOMAIN_DN}", self.con.base_dn)
+ self.dn = self.dn.replace("CN=${DOMAIN_NETBIOS}", "CN=%s" % self.con.domain_netbios)
+ for x in self.con.server_names:
+ self.dn = self.dn.replace("CN=${SERVER_NAME}", "CN=%s" % x)
+ self.attributes = self.con.get_attributes(self.dn)
+ # Attributes that are considered always to be different e.g based on timestamp etc.
+ #
+ # One domain - two domain controllers
+ self.ignore_attributes = [
+ # Default Naming Context
+ "lastLogon", "lastLogoff", "badPwdCount", "logonCount", "badPasswordTime", "modifiedCount",
+ "operatingSystemVersion","oEMInformation",
+ "ridNextRID", "rIDPreviousAllocationPool",
+ # Configuration Naming Context
+ "repsFrom", "dSCorePropagationData", "msExchServer1HighestUSN",
+ "replUpToDateVector", "repsTo", "whenChanged", "uSNChanged", "uSNCreated",
+ # Schema Naming Context
+ "prefixMap"]
+ if filter_list:
+ self.ignore_attributes += filter_list
+
+ self.dn_attributes = []
+ self.domain_attributes = []
+ self.servername_attributes = []
+ self.netbios_attributes = []
+ self.other_attributes = []
+ # Two domains - two domain controllers
+
+ if self.two_domains:
+ self.ignore_attributes += [
+ "objectCategory", "objectGUID", "objectSid", "whenCreated", "pwdLastSet", "uSNCreated", "creationTime",
+ "modifiedCount", "priorSetTime", "rIDManagerReference", "gPLink", "ipsecNFAReference",
+ "fRSPrimaryMember", "fSMORoleOwner", "masteredBy", "ipsecOwnersReference", "wellKnownObjects",
+ "badPwdCount", "ipsecISAKMPReference", "ipsecFilterReference", "msDs-masteredBy", "lastSetTime",
+ "ipsecNegotiationPolicyReference", "subRefs", "gPCFileSysPath", "accountExpires", "invocationId",
+ # After Exchange preps
+ "targetAddress", "msExchMailboxGuid", "siteFolderGUID"]
+ #
+ # Attributes that contain the unique DN tail part e.g. 'DC=samba,DC=org'
+ self.dn_attributes = [
+ "distinguishedName", "defaultObjectCategory", "member", "memberOf", "siteList", "nCName",
+ "homeMDB", "homeMTA", "interSiteTopologyGenerator", "serverReference",
+ "msDS-HasInstantiatedNCs", "hasMasterNCs", "msDS-hasMasterNCs", "msDS-HasDomainNCs", "dMDLocation",
+ "msDS-IsDomainFor", "rIDSetReferences", "serverReferenceBL",
+ # After Exchange preps
+ "msExchHomeRoutingGroup", "msExchResponsibleMTAServer", "siteFolderServer", "msExchRoutingMasterDN",
+ "msExchRoutingGroupMembersBL", "homeMDBBL", "msExchHomePublicMDB", "msExchOwningServer", "templateRoots",
+ "addressBookRoots", "msExchPolicyRoots", "globalAddressList", "msExchOwningPFTree",
+ "msExchResponsibleMTAServerBL", "msExchOwningPFTreeBL",]
+ self.dn_attributes = [x.upper() for x in self.dn_attributes]
+ #
+ # Attributes that contain the Domain name e.g. 'samba.org'
+ self.domain_attributes = [
+ "proxyAddresses", "mail", "userPrincipalName", "msExchSmtpFullyQualifiedDomainName",
+ "dnsHostName", "networkAddress", "dnsRoot", "servicePrincipalName",]
+ self.domain_attributes = [x.upper() for x in self.domain_attributes]
+ #
+ # May contain DOMAIN_NETBIOS and SERVER_NAME
+ self.servername_attributes = [ "distinguishedName", "name", "CN", "sAMAccountName", "dNSHostName",
+ "servicePrincipalName", "rIDSetReferences", "serverReference", "serverReferenceBL",
+ "msDS-IsDomainFor", "interSiteTopologyGenerator",]
+ self.servername_attributes = [x.upper() for x in self.servername_attributes]
+ #
+ self.netbios_attributes = [ "servicePrincipalName", "CN", "distinguishedName", "nETBIOSName", "name",]
+ self.netbios_attributes = [x.upper() for x in self.netbios_attributes]
+ #
+ self.other_attributes = [ "name", "DC",]
+ self.other_attributes = [x.upper() for x in self.other_attributes]
+ #
+ self.ignore_attributes = [x.upper() for x in self.ignore_attributes]
+
+ def log(self, msg):
+ """
+ Log on the screen if there is no --quiet oprion set
+ """
+ if not self.quiet:
+ self.outf.write(msg+"\n")
+
+ def fix_dn(self, s):
+ res = "%s" % s
+ if not self.two_domains:
+ return res
+ if res.upper().endswith(self.con.base_dn.upper()):
+ res = res[:len(res)-len(self.con.base_dn)] + "${DOMAIN_DN}"
+ return res
+
+ def fix_domain_name(self, s):
+ res = "%s" % s
+ if not self.two_domains:
+ return res
+ res = res.replace(self.con.domain_name.lower(), self.con.domain_name.upper())
+ res = res.replace(self.con.domain_name.upper(), "${DOMAIN_NAME}")
+ return res
+
+ def fix_domain_netbios(self, s):
+ res = "%s" % s
+ if not self.two_domains:
+ return res
+ res = res.replace(self.con.domain_netbios.lower(), self.con.domain_netbios.upper())
+ res = res.replace(self.con.domain_netbios.upper(), "${DOMAIN_NETBIOS}")
+ return res
+
+ def fix_server_name(self, s):
+ res = "%s" % s
+ if not self.two_domains or len(self.con.server_names) > 1:
+ return res
+ for x in self.con.server_names:
+ res = res.upper().replace(x, "${SERVER_NAME}")
+ return res
+
+ def __eq__(self, other):
+ if self.con.descriptor:
+ return self.cmp_desc(other)
+ return self.cmp_attrs(other)
+
+ def cmp_desc(self, other):
+ d1 = Descriptor(self.con, self.dn, outf=self.outf, errf=self.errf)
+ d2 = Descriptor(other.con, other.dn, outf=self.outf, errf=self.errf)
+ if self.con.view == "section":
+ res = d1.diff_2(d2)
+ elif self.con.view == "collision":
+ res = d1.diff_1(d2)
+ else:
+ raise Exception("Unknown --view option value.")
+ #
+ self.screen_output = res[1][:-1]
+ other.screen_output = res[1][:-1]
+ #
+ return res[0]
+
+ def cmp_attrs(self, other):
+ res = ""
+ self.unique_attrs = []
+ self.df_value_attrs = []
+ other.unique_attrs = []
+ if self.attributes.keys() != other.attributes.keys():
+ #
+ title = 4*" " + "Attributes found only in %s:" % self.con.host
+ for x in self.attributes.keys():
+ if not x in other.attributes.keys() and \
+ not x.upper() in [q.upper() for q in other.ignore_attributes]:
+ if title:
+ res += title + "\n"
+ title = None
+ res += 8*" " + x + "\n"
+ self.unique_attrs.append(x)
+ #
+ title = 4*" " + "Attributes found only in %s:" % other.con.host
+ for x in other.attributes.keys():
+ if not x in self.attributes.keys() and \
+ not x.upper() in [q.upper() for q in self.ignore_attributes]:
+ if title:
+ res += title + "\n"
+ title = None
+ res += 8*" " + x + "\n"
+ other.unique_attrs.append(x)
+ #
+ missing_attrs = [x.upper() for x in self.unique_attrs]
+ missing_attrs += [x.upper() for x in other.unique_attrs]
+ title = 4*" " + "Difference in attribute values:"
+ for x in self.attributes.keys():
+ if x.upper() in self.ignore_attributes or x.upper() in missing_attrs:
+ continue
+ if isinstance(self.attributes[x], list) and isinstance(other.attributes[x], list):
+ self.attributes[x] = sorted(self.attributes[x])
+ other.attributes[x] = sorted(other.attributes[x])
+ if self.attributes[x] != other.attributes[x]:
+ p = None
+ q = None
+ m = None
+ n = None
+ # First check if the difference can be fixed but shunting the first part
+ # of the DomainHostName e.g. 'mysamba4.test.local' => 'mysamba4'
+ if x.upper() in self.other_attributes:
+ p = [self.con.domain_name.split(".")[0] == j for j in self.attributes[x]]
+ q = [other.con.domain_name.split(".")[0] == j for j in other.attributes[x]]
+ if p == q:
+ continue
+ # Attribute values that are list that contain DN based values that may differ
+ elif x.upper() in self.dn_attributes:
+ m = p
+ n = q
+ if not p and not q:
+ m = self.attributes[x]
+ n = other.attributes[x]
+ p = [self.fix_dn(j) for j in m]
+ q = [other.fix_dn(j) for j in n]
+ if p == q:
+ continue
+ # Attributes that contain the Domain name in them
+ if x.upper() in self.domain_attributes:
+ m = p
+ n = q
+ if not p and not q:
+ m = self.attributes[x]
+ n = other.attributes[x]
+ p = [self.fix_domain_name(j) for j in m]
+ q = [other.fix_domain_name(j) for j in n]
+ if p == q:
+ continue
+ #
+ if x.upper() in self.servername_attributes:
+ # Attributes with SERVER_NAME
+ m = p
+ n = q
+ if not p and not q:
+ m = self.attributes[x]
+ n = other.attributes[x]
+ p = [self.fix_server_name(j) for j in m]
+ q = [other.fix_server_name(j) for j in n]
+ if p == q:
+ continue
+ #
+ if x.upper() in self.netbios_attributes:
+ # Attributes with NETBIOS Domain name
+ m = p
+ n = q
+ if not p and not q:
+ m = self.attributes[x]
+ n = other.attributes[x]
+ p = [self.fix_domain_netbios(j) for j in m]
+ q = [other.fix_domain_netbios(j) for j in n]
+ if p == q:
+ continue
+ #
+ if title:
+ res += title + "\n"
+ title = None
+ if p and q:
+ res += 8*" " + x + " => \n%s\n%s" % (p, q) + "\n"
+ else:
+ res += 8*" " + x + " => \n%s\n%s" % (self.attributes[x], other.attributes[x]) + "\n"
+ self.df_value_attrs.append(x)
+ #
+ if self.unique_attrs + other.unique_attrs != []:
+ assert self.unique_attrs != other.unique_attrs
+ self.summary["unique_attrs"] += self.unique_attrs
+ self.summary["df_value_attrs"] += self.df_value_attrs
+ other.summary["unique_attrs"] += other.unique_attrs
+ other.summary["df_value_attrs"] += self.df_value_attrs # they are the same
+ #
+ self.screen_output = res[:-1]
+ other.screen_output = res[:-1]
+ #
+ return res == ""
+
+
+class LDAPBundel(object):
+
+ def __init__(self, connection, context, dn_list=None, filter_list=None,
+ outf=sys.stdout, errf=sys.stderr):
+ self.outf = outf
+ self.errf = errf
+ self.con = connection
+ self.two_domains = self.con.two_domains
+ self.quiet = self.con.quiet
+ self.verbose = self.con.verbose
+ self.search_base = self.con.search_base
+ self.search_scope = self.con.search_scope
+ self.summary = {}
+ self.summary["unique_attrs"] = []
+ self.summary["df_value_attrs"] = []
+ self.summary["known_ignored_dn"] = []
+ self.summary["abnormal_ignored_dn"] = []
+ self.filter_list = filter_list
+ if dn_list:
+ self.dn_list = dn_list
+ elif context.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA", "DNSDOMAIN", "DNSFOREST"]:
+ self.context = context.upper()
+ self.dn_list = self.get_dn_list(context)
+ else:
+ raise Exception("Unknown initialization data for LDAPBundel().")
+ counter = 0
+ while counter < len(self.dn_list) and self.two_domains:
+ # Use alias reference
+ tmp = self.dn_list[counter]
+ tmp = tmp[:len(tmp)-len(self.con.base_dn)] + "${DOMAIN_DN}"
+ tmp = tmp.replace("CN=%s" % self.con.domain_netbios, "CN=${DOMAIN_NETBIOS}")
+ if len(self.con.server_names) == 1:
+ for x in self.con.server_names:
+ tmp = tmp.replace("CN=%s" % x, "CN=${SERVER_NAME}")
+ self.dn_list[counter] = tmp
+ counter += 1
+ self.dn_list = list(set(self.dn_list))
+ self.dn_list = sorted(self.dn_list)
+ self.size = len(self.dn_list)
+
+ def log(self, msg):
+ """
+ Log on the screen if there is no --quiet oprion set
+ """
+ if not self.quiet:
+ self.outf.write(msg+"\n")
+
+ def update_size(self):
+ self.size = len(self.dn_list)
+ self.dn_list = sorted(self.dn_list)
+
+ def __eq__(self, other):
+ res = True
+ if self.size != other.size:
+ self.log( "\n* DN lists have different size: %s != %s" % (self.size, other.size) )
+ res = False
+ #
+ # This is the case where we want to explicitly compare two objects with different DNs.
+ # It does not matter if they are in the same DC, in two DC in one domain or in two
+ # different domains.
+ if self.search_scope != SCOPE_BASE:
+ title= "\n* DNs found only in %s:" % self.con.host
+ for x in self.dn_list:
+ if not x.upper() in [q.upper() for q in other.dn_list]:
+ if title:
+ self.log( title )
+ title = None
+ res = False
+ self.log( 4*" " + x )
+ self.dn_list[self.dn_list.index(x)] = ""
+ self.dn_list = [x for x in self.dn_list if x]
+ #
+ title= "\n* DNs found only in %s:" % other.con.host
+ for x in other.dn_list:
+ if not x.upper() in [q.upper() for q in self.dn_list]:
+ if title:
+ self.log( title )
+ title = None
+ res = False
+ self.log( 4*" " + x )
+ other.dn_list[other.dn_list.index(x)] = ""
+ other.dn_list = [x for x in other.dn_list if x]
+ #
+ self.update_size()
+ other.update_size()
+ assert self.size == other.size
+ assert sorted([x.upper() for x in self.dn_list]) == sorted([x.upper() for x in other.dn_list])
+ self.log( "\n* Objects to be compared: %s" % self.size )
+
+ index = 0
+ while index < self.size:
+ skip = False
+ try:
+ object1 = LDAPObject(connection=self.con,
+ dn=self.dn_list[index],
+ summary=self.summary,
+ filter_list=self.filter_list,
+ outf=self.outf, errf=self.errf)
+ except LdbError, (enum, estr):
+ if enum == ERR_NO_SUCH_OBJECT:
+ self.log( "\n!!! Object not found: %s" % self.dn_list[index] )
+ skip = True
+ raise
+ try:
+ object2 = LDAPObject(connection=other.con,
+ dn=other.dn_list[index],
+ summary=other.summary,
+ filter_list=self.filter_list,
+ outf=self.outf, errf=self.errf)
+ except LdbError, (enum, estr):
+ if enum == ERR_NO_SUCH_OBJECT:
+ self.log( "\n!!! Object not found: %s" % other.dn_list[index] )
+ skip = True
+ raise
+ if skip:
+ index += 1
+ continue
+ if object1 == object2:
+ if self.con.verbose:
+ self.log( "\nComparing:" )
+ self.log( "'%s' [%s]" % (object1.dn, object1.con.host) )
+ self.log( "'%s' [%s]" % (object2.dn, object2.con.host) )
+ self.log( 4*" " + "OK" )
+ else:
+ self.log( "\nComparing:" )
+ self.log( "'%s' [%s]" % (object1.dn, object1.con.host) )
+ self.log( "'%s' [%s]" % (object2.dn, object2.con.host) )
+ self.log( object1.screen_output )
+ self.log( 4*" " + "FAILED" )
+ res = False
+ self.summary = object1.summary
+ other.summary = object2.summary
+ index += 1
+ #
+ return res
+
+ def get_dn_list(self, context):
+ """ Query LDAP server about the DNs of certain naming self.con.ext Domain (or Default), Configuration, Schema.
+ Parse all DNs and filter those that are 'strange' or abnormal.
+ """
+ if context.upper() == "DOMAIN":
+ search_base = self.con.base_dn
+ elif context.upper() == "CONFIGURATION":
+ search_base = self.con.config_dn
+ elif context.upper() == "SCHEMA":
+ search_base = self.con.schema_dn
+ elif context.upper() == "DNSDOMAIN":
+ search_base = "DC=DomainDnsZones,%s" % self.con.base_dn
+ elif context.upper() == "DNSFOREST":
+ search_base = "DC=ForestDnsZones,%s" % self.con.root_dn
+
+ dn_list = []
+ if not self.search_base:
+ self.search_base = search_base
+ self.search_scope = self.search_scope.upper()
+ if self.search_scope == "SUB":
+ self.search_scope = SCOPE_SUBTREE
+ elif self.search_scope == "BASE":
+ self.search_scope = SCOPE_BASE
+ elif self.search_scope == "ONE":
+ self.search_scope = SCOPE_ONELEVEL
+ else:
+ raise StandardError("Wrong 'scope' given. Choose from: SUB, ONE, BASE")
+ try:
+ res = self.con.ldb.search(base=self.search_base, scope=self.search_scope, attrs=["dn"])
+ except LdbError, (enum, estr):
+ self.outf.write("Failed search of base=%s\n" % self.search_base)
+ raise
+ for x in res:
+ dn_list.append(x["dn"].get_linearized())
+ #
+ global summary
+ #
+ return dn_list
+
+ def print_summary(self):
+ self.summary["unique_attrs"] = list(set(self.summary["unique_attrs"]))
+ self.summary["df_value_attrs"] = list(set(self.summary["df_value_attrs"]))
+ #
+ if self.summary["unique_attrs"]:
+ self.log( "\nAttributes found only in %s:" % self.con.host )
+ self.log( "".join([str("\n" + 4*" " + x) for x in self.summary["unique_attrs"]]) )
+ #
+ if self.summary["df_value_attrs"]:
+ self.log( "\nAttributes with different values:" )
+ self.log( "".join([str("\n" + 4*" " + x) for x in self.summary["df_value_attrs"]]) )
+ self.summary["df_value_attrs"] = []
+
+
+class cmd_ldapcmp(Command):
+ """Compare two ldap databases."""
+ synopsis = "%prog <URL1> <URL2> (domain|configuration|schema|dnsdomain|dnsforest) [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptionsDouble,
+ }
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptionsDouble,
+ }
+
+ takes_args = ["URL1", "URL2", "context1?", "context2?", "context3?"]
+
+ takes_options = [
+ Option("-w", "--two", dest="two", action="store_true", default=False,
+ help="Hosts are in two different domains"),
+ Option("-q", "--quiet", dest="quiet", action="store_true", default=False,
+ help="Do not print anything but relay on just exit code"),
+ Option("-v", "--verbose", dest="verbose", action="store_true", default=False,
+ help="Print all DN pairs that have been compared"),
+ Option("--sd", dest="descriptor", action="store_true", default=False,
+ help="Compare nTSecurityDescriptor attibutes only"),
+ Option("--sort-aces", dest="sort_aces", action="store_true", default=False,
+ help="Sort ACEs before comparison of nTSecurityDescriptor attribute"),
+ Option("--view", dest="view", default="section",
+ help="Display mode for nTSecurityDescriptor results. Possible values: section or collision."),
+ Option("--base", dest="base", default="",
+ help="Pass search base that will build DN list for the first DC."),
+ Option("--base2", dest="base2", default="",
+ help="Pass search base that will build DN list for the second DC. Used when --two or when compare two different DNs."),
+ Option("--scope", dest="scope", default="SUB",
+ help="Pass search scope that builds DN list. Options: SUB, ONE, BASE"),
+ Option("--filter", dest="filter", default="",
+ help="List of comma separated attributes to ignore in the comparision"),
+ ]
+
+ def run(self, URL1, URL2,
+ context1=None, context2=None, context3=None,
+ two=False, quiet=False, verbose=False, descriptor=False, sort_aces=False,
+ view="section", base="", base2="", scope="SUB", filter="",
+ credopts=None, sambaopts=None, versionopts=None):
+
+ lp = sambaopts.get_loadparm()
+
+ using_ldap = URL1.startswith("ldap") or URL2.startswith("ldap")
+
+ if using_ldap:
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+ else:
+ creds = None
+ creds2 = credopts.get_credentials2(lp, guess=False)
+ if creds2.is_anonymous():
+ creds2 = creds
+ else:
+ creds2.set_domain("")
+ creds2.set_workstation("")
+ if using_ldap and not creds.authentication_requested():
+ raise CommandError("You must supply at least one username/password pair")
+
+ # make a list of contexts to compare in
+ contexts = []
+ if context1 is None:
+ if base and base2:
+ # If search bases are specified context is defaulted to
+ # DOMAIN so the given search bases can be verified.
+ contexts = ["DOMAIN"]
+ else:
+ # if no argument given, we compare all contexts
+ contexts = ["DOMAIN", "CONFIGURATION", "SCHEMA"]
+ else:
+ for c in [context1, context2, context3]:
+ if c is None:
+ continue
+ if not c.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA", "DNSDOMAIN", "DNSFOREST"]:
+ raise CommandError("Incorrect argument: %s" % c)
+ contexts.append(c.upper())
+
+ if verbose and quiet:
+ raise CommandError("You cannot set --verbose and --quiet together")
+ if (not base and base2) or (base and not base2):
+ raise CommandError("You need to specify both --base and --base2 at the same time")
+ if descriptor and view.upper() not in ["SECTION", "COLLISION"]:
+ raise CommandError("Invalid --view value. Choose from: section or collision")
+ if not scope.upper() in ["SUB", "ONE", "BASE"]:
+ raise CommandError("Invalid --scope value. Choose from: SUB, ONE, BASE")
+
+ con1 = LDAPBase(URL1, creds, lp,
+ two=two, quiet=quiet, descriptor=descriptor, sort_aces=sort_aces,
+ verbose=verbose,view=view, base=base, scope=scope,
+ outf=self.outf, errf=self.errf)
+ assert len(con1.base_dn) > 0
+
+ con2 = LDAPBase(URL2, creds2, lp,
+ two=two, quiet=quiet, descriptor=descriptor, sort_aces=sort_aces,
+ verbose=verbose, view=view, base=base2, scope=scope,
+ outf=self.outf, errf=self.errf)
+ assert len(con2.base_dn) > 0
+
+ filter_list = filter.split(",")
+
+ status = 0
+ for context in contexts:
+ if not quiet:
+ self.outf.write("\n* Comparing [%s] context...\n" % context)
+
+ b1 = LDAPBundel(con1, context=context, filter_list=filter_list,
+ outf=self.outf, errf=self.errf)
+ b2 = LDAPBundel(con2, context=context, filter_list=filter_list,
+ outf=self.outf, errf=self.errf)
+
+ if b1 == b2:
+ if not quiet:
+ self.outf.write("\n* Result for [%s]: SUCCESS\n" %
+ context)
+ else:
+ if not quiet:
+ self.outf.write("\n* Result for [%s]: FAILURE\n" % context)
+ if not descriptor:
+ assert len(b1.summary["df_value_attrs"]) == len(b2.summary["df_value_attrs"])
+ b2.summary["df_value_attrs"] = []
+ self.outf.write("\nSUMMARY\n")
+ self.outf.write("---------\n")
+ b1.print_summary()
+ b2.print_summary()
+ # mark exit status as FAILURE if a least one comparison failed
+ status = -1
+ if status != 0:
+ raise CommandError("Compare failed: %d" % status)
diff --git a/python/samba/netcmd/main.py b/python/samba/netcmd/main.py
new file mode 100644
index 0000000000..5f78823588
--- /dev/null
+++ b/python/samba/netcmd/main.py
@@ -0,0 +1,70 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2011
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""The main samba-tool command implementation."""
+
+from samba import getopt as options
+
+from samba.netcmd import SuperCommand
+from samba.netcmd.dbcheck import cmd_dbcheck
+from samba.netcmd.delegation import cmd_delegation
+from samba.netcmd.dns import cmd_dns
+from samba.netcmd.domain import cmd_domain
+from samba.netcmd.drs import cmd_drs
+from samba.netcmd.dsacl import cmd_dsacl
+from samba.netcmd.fsmo import cmd_fsmo
+from samba.netcmd.gpo import cmd_gpo
+from samba.netcmd.group import cmd_group
+from samba.netcmd.ldapcmp import cmd_ldapcmp
+from samba.netcmd.ntacl import cmd_ntacl
+from samba.netcmd.rodc import cmd_rodc
+from samba.netcmd.sites import cmd_sites
+from samba.netcmd.spn import cmd_spn
+from samba.netcmd.testparm import cmd_testparm
+from samba.netcmd.time import cmd_time
+from samba.netcmd.user import cmd_user
+from samba.netcmd.vampire import cmd_vampire
+from samba.netcmd.processes import cmd_processes
+
+
+class cmd_sambatool(SuperCommand):
+ """Main samba administration tool."""
+
+ takes_optiongroups = {
+ "versionopts": options.VersionOptions,
+ }
+
+ subcommands = {}
+ subcommands["dbcheck"] = cmd_dbcheck()
+ subcommands["delegation"] = cmd_delegation()
+ subcommands["dns"] = cmd_dns()
+ subcommands["domain"] = cmd_domain()
+ subcommands["drs"] = cmd_drs()
+ subcommands["dsacl"] = cmd_dsacl()
+ subcommands["fsmo"] = cmd_fsmo()
+ subcommands["gpo"] = cmd_gpo()
+ subcommands["group"] = cmd_group()
+ subcommands["ldapcmp"] = cmd_ldapcmp()
+ subcommands["ntacl"] = cmd_ntacl()
+ subcommands["rodc"] = cmd_rodc()
+ subcommands["sites"] = cmd_sites()
+ subcommands["spn"] = cmd_spn()
+ subcommands["testparm"] = cmd_testparm()
+ subcommands["time"] = cmd_time()
+ subcommands["user"] = cmd_user()
+ subcommands["vampire"] = cmd_vampire()
+ subcommands["processes"] = cmd_processes()
diff --git a/python/samba/netcmd/ntacl.py b/python/samba/netcmd/ntacl.py
new file mode 100644
index 0000000000..6d4d350653
--- /dev/null
+++ b/python/samba/netcmd/ntacl.py
@@ -0,0 +1,260 @@
+# Manipulate file NT ACLs
+#
+# Copyright Matthieu Patou 2010 <mat@matws.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from samba.credentials import DONT_USE_KERBEROS
+import samba.getopt as options
+from samba.dcerpc import security, idmap
+from samba.ntacls import setntacl, getntacl
+from samba import Ldb
+from samba.ndr import ndr_unpack, ndr_print
+from samba.samdb import SamDB
+from samba.samba3 import param as s3param, passdb, smbd
+from samba import provision
+
+from ldb import SCOPE_BASE
+import os
+
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option,
+ )
+
+
+
+class cmd_ntacl_set(Command):
+ """Set ACLs on a file."""
+
+ synopsis = "%prog <acl> <file> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--quiet", help="Be quiet", action="store_true"),
+ Option("--xattr-backend", type="choice", help="xattr backend type (native fs or tdb)",
+ choices=["native","tdb"]),
+ Option("--eadb-file", help="Name of the tdb file where attributes are stored", type="string"),
+ Option("--use-ntvfs", help="Set the ACLs directly to the TDB or xattr for use with the ntvfs file server", action="store_true"),
+ Option("--use-s3fs", help="Set the ACLs for use with the default s3fs file server via the VFS layer", action="store_true"),
+ Option("--service", help="Name of the smb.conf service to use when applying the ACLs", type="string")
+ ]
+
+ takes_args = ["acl","file"]
+
+ def run(self, acl, file, use_ntvfs=False, use_s3fs=False,
+ quiet=False,xattr_backend=None,eadb_file=None,
+ credopts=None, sambaopts=None, versionopts=None,
+ service=None):
+ logger = self.get_logger()
+ lp = sambaopts.get_loadparm()
+ try:
+ samdb = SamDB(session_info=system_session(),
+ lp=lp)
+ except Exception, e:
+ raise CommandError("Unable to open samdb:", e)
+
+ if not use_ntvfs and not use_s3fs:
+ use_ntvfs = "smb" in lp.get("server services")
+ elif use_s3fs:
+ use_ntvfs = False
+
+ try:
+ domain_sid = security.dom_sid(samdb.domain_sid)
+ except:
+ raise CommandError("Unable to read domain SID from configuration files")
+
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+ # ensure we are using the right samba_dsdb passdb backend, no matter what
+ s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
+
+ setntacl(lp, file, acl, str(domain_sid), xattr_backend, eadb_file, use_ntvfs=use_ntvfs, service=service)
+
+ if use_ntvfs:
+ logger.warning("Please note that POSIX permissions have NOT been changed, only the stored NT ACL")
+
+
+class cmd_ntacl_get(Command):
+ """Get ACLs of a file."""
+ synopsis = "%prog <file> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--as-sddl", help="Output ACL in the SDDL format", action="store_true"),
+ Option("--xattr-backend", type="choice", help="xattr backend type (native fs or tdb)",
+ choices=["native","tdb"]),
+ Option("--eadb-file", help="Name of the tdb file where attributes are stored", type="string"),
+ Option("--use-ntvfs", help="Get the ACLs directly from the TDB or xattr used with the ntvfs file server", action="store_true"),
+ Option("--use-s3fs", help="Get the ACLs for use via the VFS layer used by the default s3fs file server", action="store_true"),
+ Option("--service", help="Name of the smb.conf service to use when getting the ACLs", type="string")
+ ]
+
+ takes_args = ["file"]
+
+ def run(self, file, use_ntvfs=False, use_s3fs=False,
+ as_sddl=False, xattr_backend=None, eadb_file=None,
+ credopts=None, sambaopts=None, versionopts=None,
+ service=None):
+ lp = sambaopts.get_loadparm()
+ try:
+ samdb = SamDB(session_info=system_session(),
+ lp=lp)
+ except Exception, e:
+ raise CommandError("Unable to open samdb:", e)
+
+ if not use_ntvfs and not use_s3fs:
+ use_ntvfs = "smb" in lp.get("server services")
+ elif use_s3fs:
+ use_ntvfs = False
+
+
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+ # ensure we are using the right samba_dsdb passdb backend, no matter what
+ s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
+
+ acl = getntacl(lp, file, xattr_backend, eadb_file, direct_db_access=use_ntvfs, service=service)
+ if as_sddl:
+ try:
+ domain_sid = security.dom_sid(samdb.domain_sid)
+ except:
+ raise CommandError("Unable to read domain SID from configuration files")
+ self.outf.write(acl.as_sddl(domain_sid)+"\n")
+ else:
+ self.outf.write(ndr_print(acl))
+
+
+class cmd_ntacl_sysvolreset(Command):
+ """Reset sysvol ACLs to defaults (including correct ACLs on GPOs)."""
+ synopsis = "%prog <file> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--use-ntvfs", help="Set the ACLs for use with the ntvfs file server", action="store_true"),
+ Option("--use-s3fs", help="Set the ACLs for use with the default s3fs file server", action="store_true")
+ ]
+
+ def run(self, use_ntvfs=False, use_s3fs=False,
+ credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ path = lp.private_path("secrets.ldb")
+ creds = credopts.get_credentials(lp)
+ creds.set_kerberos_state(DONT_USE_KERBEROS)
+ logger = self.get_logger()
+
+ netlogon = lp.get("path", "netlogon")
+ sysvol = lp.get("path", "sysvol")
+ try:
+ samdb = SamDB(session_info=system_session(),
+ lp=lp)
+ except Exception, e:
+ raise CommandError("Unable to open samdb:", e)
+
+ if not use_ntvfs and not use_s3fs:
+ use_ntvfs = "smb" in lp.get("server services")
+ elif use_s3fs:
+ use_ntvfs = False
+
+ domain_sid = security.dom_sid(samdb.domain_sid)
+
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+ # ensure we are using the right samba_dsdb passdb backend, no matter what
+ s3conf.set("passdb backend", "samba_dsdb:%s" % samdb.url)
+
+ LA_sid = security.dom_sid(str(domain_sid)
+ +"-"+str(security.DOMAIN_RID_ADMINISTRATOR))
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+
+ s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
+
+ # These assertions correct for current plugin_s4_dc selftest
+ # configuration. When other environments have a broad range of
+ # groups mapped via passdb, we can relax some of these checks
+ (LA_uid,LA_type) = s4_passdb.sid_to_id(LA_sid)
+ if (LA_type != idmap.ID_TYPE_UID and LA_type != idmap.ID_TYPE_BOTH):
+ raise CommandError("SID %s is not mapped to a UID" % LA_sid)
+ (BA_gid,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ if (BA_type != idmap.ID_TYPE_GID and BA_type != idmap.ID_TYPE_BOTH):
+ raise CommandError("SID %s is not mapped to a GID" % BA_sid)
+
+ if use_ntvfs:
+ logger.warning("Please note that POSIX permissions have NOT been changed, only the stored NT ACL")
+
+ provision.setsysvolacl(samdb, netlogon, sysvol,
+ LA_uid, BA_gid, domain_sid,
+ lp.get("realm").lower(), samdb.domain_dn(),
+ lp, use_ntvfs=use_ntvfs)
+
+class cmd_ntacl_sysvolcheck(Command):
+ """Check sysvol ACLs match defaults (including correct ACLs on GPOs)."""
+ synopsis = "%prog <file> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ path = lp.private_path("secrets.ldb")
+ creds = credopts.get_credentials(lp)
+ creds.set_kerberos_state(DONT_USE_KERBEROS)
+ logger = self.get_logger()
+
+ netlogon = lp.get("path", "netlogon")
+ sysvol = lp.get("path", "sysvol")
+ try:
+ samdb = SamDB(session_info=system_session(), lp=lp)
+ except Exception, e:
+ raise CommandError("Unable to open samdb:", e)
+
+ domain_sid = security.dom_sid(samdb.domain_sid)
+
+ provision.checksysvolacl(samdb, netlogon, sysvol,
+ domain_sid,
+ lp.get("realm").lower(), samdb.domain_dn(),
+ lp)
+
+
+class cmd_ntacl(SuperCommand):
+ """NT ACLs manipulation."""
+
+ subcommands = {}
+ subcommands["set"] = cmd_ntacl_set()
+ subcommands["get"] = cmd_ntacl_get()
+ subcommands["sysvolreset"] = cmd_ntacl_sysvolreset()
+ subcommands["sysvolcheck"] = cmd_ntacl_sysvolcheck()
+
diff --git a/python/samba/netcmd/processes.py b/python/samba/netcmd/processes.py
new file mode 100644
index 0000000000..b25a2e453e
--- /dev/null
+++ b/python/samba/netcmd/processes.py
@@ -0,0 +1,78 @@
+# Unix SMB/CIFS implementation.
+# List processes (to aid debugging on systems without setproctitle)
+# Copyright (C) 2010-2011 Jelmer Vernooij <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Testbed for loadparm.c/params.c
+#
+# This module simply loads a specified configuration file and
+# if successful, dumps it's contents to stdout. Note that the
+# operation is performed with DEBUGLEVEL at 3.
+#
+# Useful for a quick 'syntax check' of a configuration file.
+#
+
+import os
+import sys
+
+import samba
+import samba.getopt as options
+from samba.netcmd import Command, CommandError, Option
+from samba.messaging import Messaging
+
+class cmd_processes(Command):
+ """List processes (to aid debugging on systems without setproctitle)."""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions
+ }
+
+ takes_options = [
+ Option("--name", type=str,
+ help="Return only processes associated with one particular name"),
+ Option("--pid", type=int,
+ help="Return only names assoicated with one particular PID"),
+ ]
+
+ takes_args = []
+
+ def run(self, sambaopts, versionopts, section_name=None,
+ name=None, pid=None):
+
+ lp = sambaopts.get_loadparm()
+ logger = self.get_logger("processes")
+
+ msg_ctx = Messaging()
+
+ if name is not None:
+ ids = msg_ctx.irpc_servers_byname(name)
+ for server_id in ids:
+ self.outf.write("%d\n" % server_id.pid)
+ elif pid is not None:
+ names = msg_ctx.irpc_all_servers()
+ for name in names:
+ for server_id in name.ids:
+ if server_id.pid == int(pid):
+ self.outf.write("%s\n" % name.name)
+ else:
+ names = msg_ctx.irpc_all_servers()
+ self.outf.write(" Service: PID \n")
+ self.outf.write("-----------------------------\n")
+ for name in names:
+ for server_id in name.ids:
+ self.outf.write("%-16s %6d\n" % (name.name, server_id.pid))
diff --git a/python/samba/netcmd/rodc.py b/python/samba/netcmd/rodc.py
new file mode 100644
index 0000000000..2dc6112a30
--- /dev/null
+++ b/python/samba/netcmd/rodc.py
@@ -0,0 +1,108 @@
+# rodc related commands
+#
+# Copyright Andrew Tridgell 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from samba.netcmd import Command, CommandError, Option, SuperCommand
+import samba.getopt as options
+from samba.samdb import SamDB
+from samba.auth import system_session
+import ldb
+from samba.dcerpc import misc, drsuapi
+from samba.drs_utils import drs_Replicate
+
+
+class cmd_rodc_preload(Command):
+ """Preload one account for an RODC."""
+
+ synopsis = "%prog (<SID>|<DN>|<accountname>) [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("--server", help="DC to use", type=str),
+ ]
+
+ takes_args = ["account"]
+
+ def get_dn(self, samdb, account):
+ '''work out what DN they meant'''
+
+ # we accept the account in SID, accountname or DN form
+ if account[0:2] == 'S-':
+ res = samdb.search(base="<SID=%s>" % account,
+ expression="objectclass=user",
+ scope=ldb.SCOPE_BASE, attrs=[])
+ elif account.find('=') >= 0:
+ res = samdb.search(base=account,
+ expression="objectclass=user",
+ scope=ldb.SCOPE_BASE, attrs=[])
+ else:
+ res = samdb.search(expression="(&(samAccountName=%s)(objectclass=user))" % ldb.binary_encode(account),
+ scope=ldb.SCOPE_SUBTREE, attrs=[])
+ if len(res) != 1:
+ raise Exception("Failed to find account '%s'" % account)
+ return str(res[0]["dn"])
+
+
+ def run(self, account, sambaopts=None,
+ credopts=None, versionopts=None, server=None):
+
+ if server is None:
+ raise Exception("You must supply a server")
+
+ lp = sambaopts.get_loadparm()
+
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ # connect to the remote and local SAMs
+ samdb = SamDB(url="ldap://%s" % server,
+ session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ local_samdb = SamDB(url=None, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ # work out the source and destination GUIDs
+ dc_ntds_dn = samdb.get_dsServiceName()
+ res = samdb.search(base=dc_ntds_dn, scope=ldb.SCOPE_BASE, attrs=["invocationId"])
+ source_dsa_invocation_id = misc.GUID(local_samdb.schema_format_value("objectGUID", res[0]["invocationId"][0]))
+
+ dn = self.get_dn(samdb, account)
+ self.outf.write("Replicating DN %s\n" % dn)
+
+ destination_dsa_guid = misc.GUID(local_samdb.get_ntds_GUID())
+
+ local_samdb.transaction_start()
+ repl = drs_Replicate("ncacn_ip_tcp:%s[seal,print]" % server, lp, creds, local_samdb)
+ try:
+ repl.replicate(dn, source_dsa_invocation_id, destination_dsa_guid,
+ exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
+ except Exception, e:
+ raise CommandError("Error replicating DN %s" % dn, e)
+ local_samdb.transaction_commit()
+
+
+
+class cmd_rodc(SuperCommand):
+ """Read-Only Domain Controller (RODC) management."""
+
+ subcommands = {}
+ subcommands["preload"] = cmd_rodc_preload()
diff --git a/python/samba/netcmd/sites.py b/python/samba/netcmd/sites.py
new file mode 100644
index 0000000000..09df55ec9c
--- /dev/null
+++ b/python/samba/netcmd/sites.py
@@ -0,0 +1,105 @@
+# sites management
+#
+# Copyright Matthieu Patou <mat@matws.net> 2011
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+from samba import sites
+from samba.samdb import SamDB
+import samba.getopt as options
+from samba.auth import system_session
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand
+ )
+
+
+class cmd_sites_create(Command):
+ """Create a new site."""
+
+ synopsis = "%prog <site> [options]"
+
+ takes_args = ["sitename"]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ def run(self, sitename, sambaopts=None, credopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+ url = lp.private_path("sam.ldb")
+
+ if not os.path.exists(url):
+ raise CommandError("secret database not found at %s " % url)
+ samdb = SamDB(url=url, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ samdb.transaction_start()
+ try:
+ ok = sites.create_site(samdb, samdb.get_config_basedn(), sitename)
+ samdb.transaction_commit()
+ except sites.SiteAlreadyExistsException, e:
+ samdb.transaction_cancel()
+ raise CommandError("Error while creating site %s, error: %s" % (sitename, str(e)))
+
+ self.outf.write("Site %s created !\n" % sitename)
+
+class cmd_sites_delete(Command):
+ """Delete an existing site."""
+
+ synopsis = "%prog <site> [options]"
+
+ takes_args = ["sitename"]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ def run(self, sitename, sambaopts=None, credopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+ url = lp.private_path("sam.ldb")
+
+ if not os.path.exists(url):
+ raise CommandError("secret database not found at %s " % url)
+ samdb = SamDB(url=url, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ samdb.transaction_start()
+ try:
+ ok = sites.delete_site(samdb, samdb.get_config_basedn(), sitename)
+ samdb.transaction_commit()
+ except sites.SiteException, e:
+ samdb.transaction_cancel()
+ raise CommandError(
+ "Error while removing site %s, error: %s" % (sitename, str(e)))
+
+ self.outf.write("Site %s removed!\n" % sitename)
+
+
+
+class cmd_sites(SuperCommand):
+ """Sites management."""
+
+ subcommands = {}
+ subcommands["create"] = cmd_sites_create()
+ subcommands["remove"] = cmd_sites_delete()
diff --git a/python/samba/netcmd/spn.py b/python/samba/netcmd/spn.py
new file mode 100644
index 0000000000..03d072ec9b
--- /dev/null
+++ b/python/samba/netcmd/spn.py
@@ -0,0 +1,205 @@
+# spn management
+#
+# Copyright Matthieu Patou mat@samba.org 2010
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+from samba import provision
+from samba.samdb import SamDB
+from samba.auth import system_session
+from samba.netcmd.common import _get_user_realm_domain
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option
+ )
+
+
+class cmd_spn_list(Command):
+ """List spns of a given user."""
+
+ synopsis = "%prog <user> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["user"]
+
+ def run(self, user, credopts=None, sambaopts=None, versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ # TODO once I understand how, use the domain info to naildown
+ # to the correct domain
+ (cleaneduser, realm, domain) = _get_user_realm_domain(user)
+ self.outf.write(cleaneduser+"\n")
+ res = sam.search(
+ expression="samaccountname=%s" % ldb.binary_encode(cleaneduser),
+ scope=ldb.SCOPE_SUBTREE, attrs=["servicePrincipalName"])
+ if len(res) >0:
+ spns = res[0].get("servicePrincipalName")
+ found = False
+ flag = ldb.FLAG_MOD_ADD
+ if spns is not None:
+ self.outf.write(
+ "User %s has the following servicePrincipalName: \n" %
+ res[0].dn)
+ for e in spns:
+ self.outf.write("\t %s\n" % e)
+ else:
+ self.outf.write("User %s has no servicePrincipalName" %
+ res[0].dn)
+ else:
+ raise CommandError("User %s not found" % user)
+
+
+class cmd_spn_add(Command):
+ """Create a new spn."""
+
+ synopsis = "%prog <name> <user> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+ takes_options = [
+ Option("--force", help="Force the addition of the spn"
+ " even it exists already", action="store_true"),
+ ]
+ takes_args = ["name", "user"]
+
+ def run(self, name, user, force=False, credopts=None, sambaopts=None,
+ versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ res = sam.search(
+ expression="servicePrincipalName=%s" % ldb.binary_encode(name),
+ scope=ldb.SCOPE_SUBTREE)
+ if len(res) != 0 and not force:
+ raise CommandError("Service principal %s already"
+ " affected to another user" % name)
+
+ (cleaneduser, realm, domain) = _get_user_realm_domain(user)
+ res = sam.search(
+ expression="samaccountname=%s" % ldb.binary_encode(cleaneduser),
+ scope=ldb.SCOPE_SUBTREE, attrs=["servicePrincipalName"])
+ if len(res) >0:
+ res[0].dn
+ msg = ldb.Message()
+ spns = res[0].get("servicePrincipalName")
+ tab = []
+ found = False
+ flag = ldb.FLAG_MOD_ADD
+ if spns is not None:
+ for e in spns:
+ if str(e) == name:
+ found = True
+ tab.append(str(e))
+ flag = ldb.FLAG_MOD_REPLACE
+ tab.append(name)
+ msg.dn = res[0].dn
+ msg["servicePrincipalName"] = ldb.MessageElement(tab, flag,
+ "servicePrincipalName")
+ if not found:
+ sam.modify(msg)
+ else:
+ raise CommandError("Service principal %s already"
+ " affected to %s" % (name, user))
+ else:
+ raise CommandError("User %s not found" % user)
+
+
+class cmd_spn_delete(Command):
+ """Delete a spn."""
+
+ synopsis = "%prog <name> [user] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["name", "user?"]
+
+ def run(self, name, user=None, credopts=None, sambaopts=None,
+ versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+ sam = SamDB(paths.samdb, session_info=system_session(),
+ credentials=creds, lp=lp)
+ res = sam.search(
+ expression="servicePrincipalName=%s" % ldb.binary_encode(name),
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["servicePrincipalName", "samAccountName"])
+ if len(res) >0:
+ result = None
+ if user is not None:
+ (cleaneduser, realm, domain) = _get_user_realm_domain(user)
+ for elem in res:
+ if str(elem["samAccountName"]).lower() == cleaneduser:
+ result = elem
+ if result is None:
+ raise CommandError("Unable to find user %s with"
+ " spn %s" % (user, name))
+ else:
+ if len(res) != 1:
+ listUser = ""
+ for r in res:
+ listUser = "%s\n%s" % (listUser, str(r.dn))
+ raise CommandError("More than one user has the spn %s "
+ "and no specific user was specified, list of users"
+ " with this spn:%s" % (name, listUser))
+ else:
+ result=res[0]
+
+
+ msg = ldb.Message()
+ spns = result.get("servicePrincipalName")
+ tab = []
+ if spns is not None:
+ for e in spns:
+ if str(e) != name:
+ tab.append(str(e))
+ flag = ldb.FLAG_MOD_REPLACE
+ msg.dn = result.dn
+ msg["servicePrincipalName"] = ldb.MessageElement(tab, flag,
+ "servicePrincipalName")
+ sam.modify(msg)
+ else:
+ raise CommandError("Service principal %s not affected" % name)
+
+
+class cmd_spn(SuperCommand):
+ """Service Principal Name (SPN) management."""
+
+ subcommands = {}
+ subcommands["add"] = cmd_spn_add()
+ subcommands["list"] = cmd_spn_list()
+ subcommands["delete"] = cmd_spn_delete()
+
diff --git a/python/samba/netcmd/testparm.py b/python/samba/netcmd/testparm.py
new file mode 100644
index 0000000000..9251469421
--- /dev/null
+++ b/python/samba/netcmd/testparm.py
@@ -0,0 +1,209 @@
+# Unix SMB/CIFS implementation.
+# Test validity of smb.conf
+# Copyright (C) 2010-2011 Jelmer Vernooij <jelmer@samba.org>
+#
+# Based on the original in C:
+# Copyright (C) Karl Auer 1993, 1994-1998
+# Extensively modified by Andrew Tridgell, 1995
+# Converted to popt by Jelmer Vernooij (jelmer@nl.linux.org), 2002
+# Updated for Samba4 by Andrew Bartlett <abartlet@samba.org> 2006
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Testbed for loadparm.c/params.c
+#
+# This module simply loads a specified configuration file and
+# if successful, dumps it's contents to stdout. Note that the
+# operation is performed with DEBUGLEVEL at 3.
+#
+# Useful for a quick 'syntax check' of a configuration file.
+#
+
+import os
+import sys
+
+import samba
+import samba.getopt as options
+from samba.netcmd import Command, CommandError, Option
+
+class cmd_testparm(Command):
+ """Syntax check the configuration file."""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions
+ }
+
+ takes_options = [
+ Option("--section-name", type=str,
+ help="Limit testparm to a named section"),
+ Option("--parameter-name", type=str,
+ help="Limit testparm to a named parameter"),
+ Option("--client-name", type=str,
+ help="Client DNS name for 'hosts allow' checking "
+ "(should match reverse lookup)"),
+ Option("--client-ip", type=str,
+ help="Client IP address for 'hosts allow' checking"),
+ Option("--suppress-prompt", action="store_true", default=False,
+ help="Suppress prompt for enter"),
+ Option("-v", "--verbose", action="store_true",
+ default=False, help="Show default options too"),
+ # We need support for smb.conf macros before this will work again
+ Option("--server", type=str, help="Set %L macro to servername"),
+ # These are harder to do with the new code structure
+ Option("--show-all-parameters", action="store_true", default=False,
+ help="Show the parameters, type, possible values")
+ ]
+
+ takes_args = []
+
+ def run(self, sambaopts, versionopts, section_name=None,
+ parameter_name=None, client_ip=None, client_name=None,
+ verbose=False, suppress_prompt=None, show_all_parameters=False,
+ server=None):
+ if server:
+ raise NotImplementedError("--server not yet implemented")
+ if show_all_parameters:
+ raise NotImplementedError("--show-all-parameters not yet implemented")
+ if client_name is not None and client_ip is None:
+ raise CommandError("Both a DNS name and an IP address are "
+ "required for the host access check")
+
+ try:
+ lp = sambaopts.get_loadparm()
+ except RuntimeError, err:
+ raise CommandError(err)
+
+ # We need this to force the output
+ samba.set_debug_level(2)
+
+ logger = self.get_logger("testparm")
+
+ logger.info("Loaded smb config files from %s", lp.configfile)
+ logger.info("Loaded services file OK.")
+
+ valid = self.do_global_checks(lp, logger)
+ valid = valid and self.do_share_checks(lp, logger)
+ if client_name is not None and client_ip is not None:
+ self.check_client_access(lp, logger, client_name, client_ip)
+ else:
+ if section_name is not None or parameter_name is not None:
+ if parameter_name is None:
+ lp[section_name].dump(sys.stdout, lp.default_service,
+ verbose)
+ else:
+ self.outf.write(lp.get(parameter_name, section_name)+"\n")
+ else:
+ if not suppress_prompt:
+ self.outf.write("Press enter to see a dump of your service definitions\n")
+ sys.stdin.readline()
+ lp.dump(sys.stdout, verbose)
+ if valid:
+ return
+ else:
+ raise CommandError("Invalid smb.conf")
+
+ def do_global_checks(self, lp, logger):
+ valid = True
+
+ netbios_name = lp.get("netbios name")
+ if not samba.valid_netbios_name(netbios_name):
+ logger.error("netbios name %s is not a valid netbios name",
+ netbios_name)
+ valid = False
+
+ workgroup = lp.get("workgroup")
+ if not samba.valid_netbios_name(workgroup):
+ logger.error("workgroup name %s is not a valid netbios name",
+ workgroup)
+ valid = False
+
+ lockdir = lp.get("lockdir")
+
+ if not os.path.isdir(lockdir):
+ logger.error("lock directory %s does not exist", lockdir)
+ valid = False
+
+ piddir = lp.get("pid directory")
+
+ if not os.path.isdir(piddir):
+ logger.error("pid directory %s does not exist", piddir)
+ valid = False
+
+ winbind_separator = lp.get("winbind separator")
+
+ if len(winbind_separator) != 1:
+ logger.error("the 'winbind separator' parameter must be a single "
+ "character.")
+ valid = False
+
+ if winbind_separator == '+':
+ logger.error(
+ "'winbind separator = +' might cause problems with group "
+ "membership.")
+ valid = False
+
+ return valid
+
+ def allow_access(self, deny_list, allow_list, cname, caddr):
+ raise NotImplementedError(self.allow_access)
+
+ def do_share_checks(self, lp, logger):
+ valid = True
+ for s in lp.services():
+ if len(s) > 12:
+ logger.warning(
+ "You have some share names that are longer than 12 "
+ "characters. These may not be accessible to some older "
+ "clients. (Eg. Windows9x, WindowsMe, and not listed in "
+ "smbclient in Samba 3.0.)")
+ break
+
+ for s in lp.services():
+ deny_list = lp.get("hosts deny", s)
+ allow_list = lp.get("hosts allow", s)
+ if deny_list:
+ for entry in deny_list:
+ if "*" in entry or "?" in entry:
+ logger.error("Invalid character (* or ?) in hosts deny "
+ "list (%s) for service %s.", entry, s)
+ valid = False
+
+ if allow_list:
+ for entry in allow_list:
+ if "*" in entry or "?" in entry:
+ logger.error("Invalid character (* or ?) in hosts allow "
+ "list (%s) for service %s.", entry, s)
+ valid = False
+ return valid
+
+ def check_client_access(self, lp, logger, cname, caddr):
+ # this is totally ugly, a real `quick' hack
+ for s in lp.services():
+ if (self.allow_access(lp.get("hosts deny"), lp.get("hosts allow"), cname,
+ caddr) and
+ self.allow_access(lp.get("hosts deny", s), lp.get("hosts allow", s),
+ cname, caddr)):
+ logger.info("Allow connection from %s (%s) to %s", cname, caddr, s)
+ else:
+ logger.info("Deny connection from %s (%s) to %s", cname, caddr, s)
+
+## FIXME: We need support for smb.conf macros before this will work again
+##
+## if (new_local_machine) {
+## set_local_machine_name(new_local_machine, True)
+## }
+#
diff --git a/python/samba/netcmd/time.py b/python/samba/netcmd/time.py
new file mode 100644
index 0000000000..694b6adda9
--- /dev/null
+++ b/python/samba/netcmd/time.py
@@ -0,0 +1,59 @@
+# time
+#
+# Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import common
+from samba.net import Net
+
+from samba.netcmd import (
+ Command,
+ )
+
+class cmd_time(Command):
+ """Retrieve the time on a server.
+
+This command returns the date and time of the Active Directory server specified on the command. The server name specified may be the local server or a remote server. If the servername is not specified, the command returns the time and date of the local AD server.
+
+Example1:
+samba-tool time samdom.example.com
+
+Example1 returns the date and time of the server samdom.example.com.
+
+Example2:
+samba-tool time
+
+Example2 return the date and time of the local server.
+"""
+ synopsis = "%prog [server-name] [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_args = ["server_name?"]
+
+ def run(self, server_name=None, credopts=None, sambaopts=None,
+ versionopts=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+ net = Net(creds, lp, server=credopts.ipaddress)
+ if server_name is None:
+ server_name = common.netcmd_dnsname(lp)
+ self.outf.write(net.time(server_name)+"\n")
diff --git a/python/samba/netcmd/user.py b/python/samba/netcmd/user.py
new file mode 100644
index 0000000000..b98ec344b2
--- /dev/null
+++ b/python/samba/netcmd/user.py
@@ -0,0 +1,605 @@
+# user management
+#
+# Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
+# Copyright Theresa Halloran 2011 <theresahalloran@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+import pwd
+from getpass import getpass
+from samba.auth import system_session
+from samba.samdb import SamDB
+from samba import (
+ dsdb,
+ gensec,
+ generate_random_password,
+ )
+from samba.net import Net
+
+from samba.netcmd import (
+ Command,
+ CommandError,
+ SuperCommand,
+ Option,
+ )
+
+
+class cmd_user_create(Command):
+ """Create a new user.
+
+This command creates a new user account in the Active Directory domain. The username specified on the command is the sAMaccountName.
+
+User accounts may represent physical entities, such as people or may be used as service accounts for applications. User accounts are also referred to as security principals and are assigned a security identifier (SID).
+
+A user account enables a user to logon to a computer and domain with an identity that can be authenticated. To maximize security, each user should have their own unique user account and password. A user's access to domain resources is based on permissions assigned to the user account.
+
+Unix (RFC2307) attributes may be added to the user account. Attributes taken from NSS are obtained on the local machine. Explicitly given values override values obtained from NSS. Configure 'idmap_ldb:use rfc2307 = Yes' to use these attributes for UID/GID mapping.
+
+The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command against a remote server.
+
+Example1:
+samba-tool user add User1 passw0rd --given-name=John --surname=Smith --must-change-at-next-login -H ldap://samba.samdom.example.com -Uadministrator%passw1rd
+
+Example1 shows how to create a new user in the domain against a remote LDAP server. The -H parameter is used to specify the remote target server. The -U option is used to pass the userid and password authorized to issue the command remotely.
+
+Example2:
+sudo samba-tool user add User2 passw2rd --given-name=Jane --surname=Doe --must-change-at-next-login
+
+Example2 shows how to create a new user in the domain against the local server. sudo is used so a user may run the command as root. In this example, after User2 is created, he/she will be forced to change their password when they logon.
+
+Example3:
+samba-tool user add User3 passw3rd --userou=OrgUnit
+
+Example3 shows how to create a new user in the OrgUnit organizational unit.
+
+Example4:
+samba-tool user create User4 passw4rd --rfc2307-from-nss --gecos 'some text'
+
+Example4 shows how to create a new user with Unix UID, GID and login-shell set from the local NSS and GECOS set to 'some text'.
+
+"""
+ synopsis = "%prog <username> [<password>] [options]"
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--must-change-at-next-login",
+ help="Force password to be changed on next login",
+ action="store_true"),
+ Option("--random-password",
+ help="Generate random password",
+ action="store_true"),
+ Option("--use-username-as-cn",
+ help="Force use of username as user's CN",
+ action="store_true"),
+ Option("--userou",
+ help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
+ type=str),
+ Option("--surname", help="User's surname", type=str),
+ Option("--given-name", help="User's given name", type=str),
+ Option("--initials", help="User's initials", type=str),
+ Option("--profile-path", help="User's profile path", type=str),
+ Option("--script-path", help="User's logon script path", type=str),
+ Option("--home-drive", help="User's home drive letter", type=str),
+ Option("--home-directory", help="User's home directory path", type=str),
+ Option("--job-title", help="User's job title", type=str),
+ Option("--department", help="User's department", type=str),
+ Option("--company", help="User's company", type=str),
+ Option("--description", help="User's description", type=str),
+ Option("--mail-address", help="User's email address", type=str),
+ Option("--internet-address", help="User's home page", type=str),
+ Option("--telephone-number", help="User's phone number", type=str),
+ Option("--physical-delivery-office", help="User's office location", type=str),
+ Option("--rfc2307-from-nss",
+ help="Copy Unix user attributes from NSS (will be overridden by explicit UID/GID/GECOS/shell)",
+ action="store_true"),
+ Option("--uid", help="User's Unix/RFC2307 username", type=str),
+ Option("--uid-number", help="User's Unix/RFC2307 numeric UID", type=int),
+ Option("--gid-number", help="User's Unix/RFC2307 primary GID number", type=int),
+ Option("--gecos", help="User's Unix/RFC2307 GECOS field", type=str),
+ Option("--login-shell", help="User's Unix/RFC2307 login shell", type=str),
+ ]
+
+ takes_args = ["username", "password?"]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, username, password=None, credopts=None, sambaopts=None,
+ versionopts=None, H=None, must_change_at_next_login=False,
+ random_password=False, use_username_as_cn=False, userou=None,
+ surname=None, given_name=None, initials=None, profile_path=None,
+ script_path=None, home_drive=None, home_directory=None,
+ job_title=None, department=None, company=None, description=None,
+ mail_address=None, internet_address=None, telephone_number=None,
+ physical_delivery_office=None, rfc2307_from_nss=False,
+ uid=None, uid_number=None, gid_number=None, gecos=None, login_shell=None):
+
+ if random_password:
+ password = generate_random_password(128, 255)
+
+ while True:
+ if password is not None and password is not '':
+ break
+ password = getpass("New Password: ")
+ passwordverify = getpass("Retype Password: ")
+ if not password == passwordverify:
+ password = None
+ self.outf.write("Sorry, passwords do not match.\n")
+
+ if rfc2307_from_nss:
+ pwent = pwd.getpwnam(username)
+ if uid is None:
+ uid = username
+ if uid_number is None:
+ uid_number = pwent[2]
+ if gid_number is None:
+ gid_number = pwent[3]
+ if gecos is None:
+ gecos = pwent[4]
+ if login_shell is None:
+ login_shell = pwent[6]
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ if uid_number or gid_number:
+ if not lp.get("idmap_ldb:use rfc2307"):
+ self.outf.write("You are setting a Unix/RFC2307 UID or GID. You may want to set 'idmap_ldb:use rfc2307 = Yes' to use those attributes for XID/SID-mapping.\n")
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.newuser(username, password, force_password_change_at_next_login_req=must_change_at_next_login,
+ useusernameascn=use_username_as_cn, userou=userou, surname=surname, givenname=given_name, initials=initials,
+ profilepath=profile_path, homedrive=home_drive, scriptpath=script_path, homedirectory=home_directory,
+ jobtitle=job_title, department=department, company=company, description=description,
+ mailaddress=mail_address, internetaddress=internet_address,
+ telephonenumber=telephone_number, physicaldeliveryoffice=physical_delivery_office,
+ uid=uid, uidnumber=uid_number, gidnumber=gid_number, gecos=gecos, loginshell=login_shell)
+ except Exception, e:
+ raise CommandError("Failed to add user '%s': " % username, e)
+
+ self.outf.write("User '%s' created successfully\n" % username)
+
+
+class cmd_user_add(cmd_user_create):
+ __doc__ = cmd_user_create.__doc__
+ # take this print out after the add subcommand is removed.
+ # the add subcommand is deprecated but left in for now to allow people to
+ # migrate to create
+
+ def run(self, *args, **kwargs):
+ self.err.write(
+ "Note: samba-tool user add is deprecated. "
+ "Please use samba-tool user create for the same function.\n")
+ return super(self, cmd_user_add).run(*args, **kwargs)
+
+
+class cmd_user_delete(Command):
+ """Delete a user.
+
+This command deletes a user account from the Active Directory domain. The username specified on the command is the sAMAccountName.
+
+Once the account is deleted, all permissions and memberships associated with that account are deleted. If a new user account is added with the same name as a previously deleted account name, the new user does not have the previous permissions. The new account user will be assigned a new security identifier (SID) and permissions and memberships will have to be added.
+
+The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command against a remote server.
+
+Example1:
+samba-tool user delete User1 -H ldap://samba.samdom.example.com --username=administrator --password=passw1rd
+
+Example1 shows how to delete a user in the domain against a remote LDAP server. The -H parameter is used to specify the remote target server. The --username= and --password= options are used to pass the username and password of a user that exists on the remote server and is authorized to issue the command on that server.
+
+Example2:
+sudo samba-tool user delete User2
+
+Example2 shows how to delete a user in the domain against the local server. sudo is used so a user may run the command as root.
+
+"""
+ synopsis = "%prog <username> [options]"
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ ]
+
+ takes_args = ["username"]
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, username, credopts=None, sambaopts=None, versionopts=None,
+ H=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ try:
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ samdb.deleteuser(username)
+ except Exception, e:
+ raise CommandError('Failed to remove user "%s"' % username, e)
+ self.outf.write("Deleted user %s\n" % username)
+
+
+class cmd_user_list(Command):
+ """List all users."""
+
+ synopsis = "%prog [options]"
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ domain_dn = samdb.domain_dn()
+ res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
+ expression=("(&(objectClass=user)(userAccountControl:%s:=%u))"
+ % (ldb.OID_COMPARATOR_AND, dsdb.UF_NORMAL_ACCOUNT)),
+ attrs=["samaccountname"])
+ if (len(res) == 0):
+ return
+
+ for msg in res:
+ self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
+
+
+class cmd_user_enable(Command):
+ """Enable an user.
+
+This command enables a user account for logon to an Active Directory domain. The username specified on the command is the sAMAccountName. The username may also be specified using the --filter option.
+
+There are many reasons why an account may become disabled. These include:
+- If a user exceeds the account policy for logon attempts
+- If an administrator disables the account
+- If the account expires
+
+The samba-tool user enable command allows an administrator to enable an account which has become disabled.
+
+Additionally, the enable function allows an administrator to have a set of created user accounts defined and setup with default permissions that can be easily enabled for use.
+
+The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command against a remote server.
+
+Example1:
+samba-tool user enable Testuser1 --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd
+
+Example1 shows how to enable a user in the domain against a remote LDAP server. The --URL parameter is used to specify the remote target server. The --username= and --password= options are used to pass the username and password of a user that exists on the remote server and is authorized to update that server.
+
+Example2:
+su samba-tool user enable Testuser2
+
+Example2 shows how to enable user Testuser2 for use in the domain on the local server. sudo is used so a user may run the command as root.
+
+Example3:
+samba-tool user enable --filter=samaccountname=Testuser3
+
+Example3 shows how to enable a user in the domain against a local LDAP server. It uses the --filter=samaccountname to specify the username.
+
+"""
+ synopsis = "%prog (<username>|--filter <filter>) [options]"
+
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--filter", help="LDAP Filter to set password on", type=str),
+ ]
+
+ takes_args = ["username?"]
+
+ def run(self, username=None, sambaopts=None, credopts=None,
+ versionopts=None, filter=None, H=None):
+ if username is None and filter is None:
+ raise CommandError("Either the username or '--filter' must be specified!")
+
+ if filter is None:
+ filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ try:
+ samdb.enable_account(filter)
+ except Exception, msg:
+ raise CommandError("Failed to enable user '%s': %s" % (username or filter, msg))
+ self.outf.write("Enabled user '%s'\n" % (username or filter))
+
+
+class cmd_user_disable(Command):
+ """Disable an user."""
+
+ synopsis = "%prog (<username>|--filter <filter>) [options]"
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--filter", help="LDAP Filter to set password on", type=str),
+ ]
+
+ takes_args = ["username?"]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, username=None, sambaopts=None, credopts=None,
+ versionopts=None, filter=None, H=None):
+ if username is None and filter is None:
+ raise CommandError("Either the username or '--filter' must be specified!")
+
+ if filter is None:
+ filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+ try:
+ samdb.disable_account(filter)
+ except Exception, msg:
+ raise CommandError("Failed to disable user '%s': %s" % (username or filter, msg))
+
+
+class cmd_user_setexpiry(Command):
+ """Set the expiration of a user account.
+
+The user can either be specified by their sAMAccountName or using the --filter option.
+
+When a user account expires, it becomes disabled and the user is unable to logon. The administrator may issue the samba-tool user enable command to enable the account for logon. The permissions and memberships associated with the account are retained when the account is enabled.
+
+The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command on a remote server.
+
+Example1:
+samba-tool user setexpiry User1 --days=20 --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd
+
+Example1 shows how to set the expiration of an account in a remote LDAP server. The --URL parameter is used to specify the remote target server. The --username= and --password= options are used to pass the username and password of a user that exists on the remote server and is authorized to update that server.
+
+Example2:
+su samba-tool user setexpiry User2
+
+Example2 shows how to set the account expiration of user User2 so it will never expire. The user in this example resides on the local server. sudo is used so a user may run the command as root.
+
+Example3:
+samba-tool user setexpiry --days=20 --filter=samaccountname=User3
+
+Example3 shows how to set the account expiration date to end of day 20 days from the current day. The username or sAMAccountName is specified using the --filter= paramter and the username in this example is User3.
+
+Example4:
+samba-tool user setexpiry --noexpiry User4
+Example4 shows how to set the account expiration so that it will never expire. The username and sAMAccountName in this example is User4.
+
+"""
+ synopsis = "%prog (<username>|--filter <filter>) [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--filter", help="LDAP Filter to set password on", type=str),
+ Option("--days", help="Days to expiry", type=int, default=0),
+ Option("--noexpiry", help="Password does never expire", action="store_true", default=False),
+ ]
+
+ takes_args = ["username?"]
+
+ def run(self, username=None, sambaopts=None, credopts=None,
+ versionopts=None, H=None, filter=None, days=None, noexpiry=None):
+ if username is None and filter is None:
+ raise CommandError("Either the username or '--filter' must be specified!")
+
+ if filter is None:
+ filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ try:
+ samdb.setexpiry(filter, days*24*3600, no_expiry_req=noexpiry)
+ except Exception, msg:
+ # FIXME: Catch more specific exception
+ raise CommandError("Failed to set expiry for user '%s': %s" % (
+ username or filter, msg))
+ if days:
+ self.outf.write("Expiry for user '%s' set to %u days.\n" % (
+ username or filter, days))
+ else:
+ self.outf.write("Expiry for user '%s' disabled.\n" % (
+ username or filter))
+
+
+class cmd_user_password(Command):
+ """Change password for a user account (the one provided in authentication).
+"""
+
+ synopsis = "%prog [options]"
+
+ takes_options = [
+ Option("--newpassword", help="New password", type=str),
+ ]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, credopts=None, sambaopts=None, versionopts=None,
+ newpassword=None):
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ # get old password now, to get the password prompts in the right order
+ old_password = creds.get_password()
+
+ net = Net(creds, lp, server=credopts.ipaddress)
+
+ password = newpassword
+ while True:
+ if password is not None and password is not '':
+ break
+ password = getpass("New Password: ")
+ passwordverify = getpass("Retype Password: ")
+ if not password == passwordverify:
+ password = None
+ self.outf.write("Sorry, passwords do not match.\n")
+
+ try:
+ net.change_password(password)
+ except Exception, msg:
+ # FIXME: catch more specific exception
+ raise CommandError("Failed to change password : %s" % msg)
+ self.outf.write("Changed password OK\n")
+
+
+class cmd_user_setpassword(Command):
+ """Set or reset the password of a user account.
+
+This command sets or resets the logon password for a user account. The username specified on the command is the sAMAccountName. The username may also be specified using the --filter option.
+
+If the password is not specified on the command through the --newpassword parameter, the user is prompted for the password to be entered through the command line.
+
+It is good security practice for the administrator to use the --must-change-at-next-login option which requires that when the user logs on to the account for the first time following the password change, he/she must change the password.
+
+The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command against a remote server.
+
+Example1:
+samba-tool user setpassword TestUser1 --newpassword=passw0rd --URL=ldap://samba.samdom.example.com -Uadministrator%passw1rd
+
+Example1 shows how to set the password of user TestUser1 on a remote LDAP server. The --URL parameter is used to specify the remote target server. The -U option is used to pass the username and password of a user that exists on the remote server and is authorized to update the server.
+
+Example2:
+sudo samba-tool user setpassword TestUser2 --newpassword=passw0rd --must-change-at-next-login
+
+Example2 shows how an administrator would reset the TestUser2 user's password to passw0rd. The user is running under the root userid using the sudo command. In this example the user TestUser2 must change their password the next time they logon to the account.
+
+Example3:
+samba-tool user setpassword --filter=samaccountname=TestUser3 --newpassword=passw0rd
+
+Example3 shows how an administrator would reset TestUser3 user's password to passw0rd using the --filter= option to specify the username.
+
+"""
+ synopsis = "%prog (<username>|--filter <filter>) [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--filter", help="LDAP Filter to set password on", type=str),
+ Option("--newpassword", help="Set password", type=str),
+ Option("--must-change-at-next-login",
+ help="Force password to be changed on next login",
+ action="store_true"),
+ Option("--random-password",
+ help="Generate random password",
+ action="store_true"),
+ ]
+
+ takes_args = ["username?"]
+
+ def run(self, username=None, filter=None, credopts=None, sambaopts=None,
+ versionopts=None, H=None, newpassword=None,
+ must_change_at_next_login=False, random_password=False):
+ if filter is None and username is None:
+ raise CommandError("Either the username or '--filter' must be specified!")
+
+ if random_password:
+ password = generate_random_password(128, 255)
+ else:
+ password = newpassword
+
+ while 1:
+ if password is not None and password is not '':
+ break
+ password = getpass("New Password: ")
+
+ if filter is None:
+ filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username))
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ try:
+ samdb.setpassword(filter, password,
+ force_change_at_next_login=must_change_at_next_login,
+ username=username)
+ except Exception, msg:
+ # FIXME: catch more specific exception
+ raise CommandError("Failed to set password for user '%s': %s" % (username or filter, msg))
+ self.outf.write("Changed password OK\n")
+
+
+class cmd_user(SuperCommand):
+ """User management."""
+
+ subcommands = {}
+ subcommands["add"] = cmd_user_create()
+ subcommands["create"] = cmd_user_create()
+ subcommands["delete"] = cmd_user_delete()
+ subcommands["disable"] = cmd_user_disable()
+ subcommands["enable"] = cmd_user_enable()
+ subcommands["list"] = cmd_user_list()
+ subcommands["setexpiry"] = cmd_user_setexpiry()
+ subcommands["password"] = cmd_user_password()
+ subcommands["setpassword"] = cmd_user_setpassword()
diff --git a/python/samba/netcmd/vampire.py b/python/samba/netcmd/vampire.py
new file mode 100644
index 0000000000..b12222e79e
--- /dev/null
+++ b/python/samba/netcmd/vampire.py
@@ -0,0 +1,55 @@
+# Vampire
+#
+# Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+
+from samba.net import Net
+
+from samba.netcmd import (
+ Command,
+ Option,
+ SuperCommand,
+ CommandError
+ )
+
+
+class cmd_vampire(Command):
+ """Join and synchronise a remote AD domain to the local server."""
+ synopsis = "%prog [options] <domain>"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ takes_options = [
+ Option("--target-dir", help="Target directory.", type=str),
+ Option("--force", help="force run", action='store_true', default=False),
+ ]
+
+ takes_args = ["domain"]
+
+ def run(self, domain, target_dir=None, credopts=None, sambaopts=None, versionopts=None, force=False):
+ if not force:
+ raise CommandError("samba-tool vampire is deprecated, please use samba-tool domain join. Use --force to override")
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ net = Net(creds, lp, server=credopts.ipaddress)
+ (domain_name, domain_sid) = net.vampire(domain=domain, target_dir=target_dir)
+ self.outf.write("Vampired domain %s (%s)\n" % (domain_name, domain_sid))