summaryrefslogtreecommitdiff
path: root/source4/scripting/python
diff options
context:
space:
mode:
Diffstat (limited to 'source4/scripting/python')
-rwxr-xr-xsource4/scripting/python/examples/dnsserver.py89
-rw-r--r--source4/scripting/python/pyglue.c57
-rw-r--r--source4/scripting/python/samba/__init__.py41
-rw-r--r--source4/scripting/python/samba/common.py99
-rw-r--r--source4/scripting/python/samba/dbchecker.py751
-rw-r--r--source4/scripting/python/samba/drs_utils.py110
-rw-r--r--source4/scripting/python/samba/getopt.py71
-rw-r--r--source4/scripting/python/samba/hostconfig.py4
-rw-r--r--source4/scripting/python/samba/idmap.py13
-rw-r--r--source4/scripting/python/samba/join.py913
-rw-r--r--source4/scripting/python/samba/kcc_utils.py2182
-rw-r--r--source4/scripting/python/samba/ms_display_specifiers.py2
-rw-r--r--source4/scripting/python/samba/ms_schema.py53
-rw-r--r--source4/scripting/python/samba/ndr.py7
-rw-r--r--source4/scripting/python/samba/netcmd/__init__.py218
-rw-r--r--source4/scripting/python/samba/netcmd/common.py51
-rw-r--r--source4/scripting/python/samba/netcmd/dbcheck.py127
-rw-r--r--source4/scripting/python/samba/netcmd/delegation.py263
-rw-r--r--source4/scripting/python/samba/netcmd/dns.py1186
-rw-r--r--source4/scripting/python/samba/netcmd/domain.py1344
-rw-r--r--source4/scripting/python/samba/netcmd/domainlevel.py247
-rw-r--r--source4/scripting/python/samba/netcmd/drs.py210
-rw-r--r--source4/scripting/python/samba/netcmd/dsacl.py28
-rw-r--r--source4/scripting/python/samba/netcmd/enableaccount.py60
-rw-r--r--source4/scripting/python/samba/netcmd/export.py56
-rw-r--r--source4/scripting/python/samba/netcmd/fsmo.py230
-rw-r--r--source4/scripting/python/samba/netcmd/gpo.py1070
-rw-r--r--source4/scripting/python/samba/netcmd/group.py231
-rw-r--r--source4/scripting/python/samba/netcmd/join.py78
-rw-r--r--[-rwxr-xr-x]source4/scripting/python/samba/netcmd/ldapcmp.py165
-rw-r--r--source4/scripting/python/samba/netcmd/machinepw.py53
-rw-r--r--source4/scripting/python/samba/netcmd/main.py70
-rw-r--r--source4/scripting/python/samba/netcmd/newuser.py97
-rw-r--r--source4/scripting/python/samba/netcmd/ntacl.py211
-rw-r--r--source4/scripting/python/samba/netcmd/processes.py78
-rw-r--r--source4/scripting/python/samba/netcmd/pwsettings.py197
-rw-r--r--source4/scripting/python/samba/netcmd/rodc.py21
-rw-r--r--source4/scripting/python/samba/netcmd/setexpiry.py67
-rw-r--r--source4/scripting/python/samba/netcmd/setpassword.py80
-rw-r--r--source4/scripting/python/samba/netcmd/sites.py105
-rw-r--r--source4/scripting/python/samba/netcmd/spn.py94
-rw-r--r--source4/scripting/python/samba/netcmd/testparm.py209
-rw-r--r--source4/scripting/python/samba/netcmd/time.py24
-rw-r--r--source4/scripting/python/samba/netcmd/user.py536
-rw-r--r--source4/scripting/python/samba/netcmd/vampire.py9
-rw-r--r--source4/scripting/python/samba/ntacls.py175
-rw-r--r--source4/scripting/python/samba/provision/__init__.py1797
-rw-r--r--source4/scripting/python/samba/provision/backend.py102
-rw-r--r--source4/scripting/python/samba/provision/common.py82
-rw-r--r--source4/scripting/python/samba/provision/descriptor.py305
-rw-r--r--source4/scripting/python/samba/provision/sambadns.py1144
-rw-r--r--source4/scripting/python/samba/samba3.py790
-rw-r--r--source4/scripting/python/samba/samba3/__init__.py408
-rw-r--r--source4/scripting/python/samba/samdb.py218
-rw-r--r--source4/scripting/python/samba/schema.py18
-rw-r--r--source4/scripting/python/samba/sd_utils.py19
-rw-r--r--source4/scripting/python/samba/sites.py125
-rw-r--r--source4/scripting/python/samba/tests/__init__.py31
-rw-r--r--source4/scripting/python/samba/tests/auth.py12
-rw-r--r--source4/scripting/python/samba/tests/blackbox/__init__.py16
-rw-r--r--[-rwxr-xr-x]source4/scripting/python/samba/tests/blackbox/ndrdump.py15
-rw-r--r--source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py3
-rw-r--r--source4/scripting/python/samba/tests/common.py40
-rw-r--r--source4/scripting/python/samba/tests/core.py12
-rw-r--r--source4/scripting/python/samba/tests/credentials.py14
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/__init__.py5
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/bare.py23
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/dnsserver.py241
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/misc.py8
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/registry.py16
-rw-r--r--[-rwxr-xr-x]source4/scripting/python/samba/tests/dcerpc/rpc_talloc.py20
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/rpcecho.py8
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/sam.py9
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/srvsvc.py68
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/testrpc.py19
-rw-r--r--source4/scripting/python/samba/tests/dcerpc/unix.py10
-rw-r--r--source4/scripting/python/samba/tests/dns.py622
-rw-r--r--source4/scripting/python/samba/tests/docs.py127
-rw-r--r--source4/scripting/python/samba/tests/dsdb.py7
-rw-r--r--source4/scripting/python/samba/tests/gensec.py72
-rw-r--r--source4/scripting/python/samba/tests/getopt.py55
-rw-r--r--source4/scripting/python/samba/tests/hostconfig.py14
-rw-r--r--source4/scripting/python/samba/tests/libsmb_samba_internal.py78
-rw-r--r--source4/scripting/python/samba/tests/messaging.py25
-rw-r--r--source4/scripting/python/samba/tests/netcmd.py71
-rw-r--r--source4/scripting/python/samba/tests/ntacls.py84
-rw-r--r--source4/scripting/python/samba/tests/param.py6
-rw-r--r--source4/scripting/python/samba/tests/policy.py34
-rw-r--r--source4/scripting/python/samba/tests/posixacl.py715
-rw-r--r--source4/scripting/python/samba/tests/provision.py105
-rw-r--r--source4/scripting/python/samba/tests/registry.py10
-rw-r--r--source4/scripting/python/samba/tests/samba3.py253
-rw-r--r--source4/scripting/python/samba/tests/samba3sam.py457
-rw-r--r--source4/scripting/python/samba/tests/samba_tool/__init__.py (renamed from source4/scripting/python/samba/netcmd/netacl.py)22
-rw-r--r--source4/scripting/python/samba/tests/samba_tool/base.py108
-rw-r--r--source4/scripting/python/samba/tests/samba_tool/gpo.py79
-rw-r--r--source4/scripting/python/samba/tests/samba_tool/group.py169
-rw-r--r--source4/scripting/python/samba/tests/samba_tool/ntacl.py135
-rw-r--r--source4/scripting/python/samba/tests/samba_tool/processes.py35
-rw-r--r--source4/scripting/python/samba/tests/samba_tool/timecmd.py43
-rw-r--r--source4/scripting/python/samba/tests/samba_tool/user.py237
-rw-r--r--source4/scripting/python/samba/tests/samdb.py16
-rw-r--r--source4/scripting/python/samba/tests/security.py2
-rw-r--r--source4/scripting/python/samba/tests/source.py264
-rw-r--r--source4/scripting/python/samba/tests/strings.py103
-rw-r--r--source4/scripting/python/samba/tests/unicodenames.py29
-rw-r--r--source4/scripting/python/samba/tests/upgrade.py17
-rw-r--r--source4/scripting/python/samba/tests/upgradeprovision.py14
-rw-r--r--source4/scripting/python/samba/tests/upgradeprovisionneeddc.py11
-rw-r--r--source4/scripting/python/samba/tests/xattr.py31
-rw-r--r--source4/scripting/python/samba/upgrade.py829
-rw-r--r--[-rwxr-xr-x]source4/scripting/python/samba/upgradehelpers.py394
-rw-r--r--source4/scripting/python/samba/web_server/__init__.py5
-rw-r--r--source4/scripting/python/samba/xattr.py61
-rw-r--r--source4/scripting/python/wscript_build4
115 files changed, 18447 insertions, 4456 deletions
diff --git a/source4/scripting/python/examples/dnsserver.py b/source4/scripting/python/examples/dnsserver.py
new file mode 100755
index 0000000000..c65b1c4099
--- /dev/null
+++ b/source4/scripting/python/examples/dnsserver.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+
+# script to test the dnsserver RPC protocol
+
+import sys
+from optparse import OptionParser
+
+sys.path.insert(0, "bin/python")
+
+import samba
+import samba.getopt as options
+from samba.dcerpc import dnsserver, security, dnsp
+
+
+########### main code ###########
+if __name__ == "__main__":
+ parser = OptionParser("dnsserver [options] server")
+ sambaopts = options.SambaOptions(parser)
+ credopts = options.CredentialsOptionsDouble(parser)
+ parser.add_option_group(credopts)
+
+ (opts, args) = parser.parse_args()
+
+ if len(args) < 3:
+ print("Usage: dnsserver.py [options] DNSSERVER DNSZONE NEWNAME")
+ sys.exit(1)
+
+ server = args[0]
+ dnszone = args[1]
+ newname = args[2]
+
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ if not creds.authentication_requested():
+ parser.error("You must supply credentials")
+
+ binding_str = "ncacn_ip_tcp:%s[print,sign]" % server
+
+ dns_conn = dnsserver.dnsserver(binding_str, lp, creds)
+
+ print("querying a NS record")
+ res = dns_conn.DnssrvEnumRecords2(0x00070000,
+ 0,
+ server,
+ dnszone,
+ newname,
+ None,
+ dnsp.DNS_TYPE_NS,
+ 0x0f,
+ None,
+ None)
+
+ print("adding a NS glue record")
+ name = dnsserver.DNS_RPC_NAME()
+ name.str = newname
+
+ addrec = dnsserver.DNS_RPC_RECORD()
+ addrec.wType = dnsp.DNS_TYPE_NS
+ addrec.dwFlags = 0
+ addrec.dwSerial = 0
+ addrec.dwTtlSeconds = 3600
+ addrec.dwTimeStamp = 0
+ addrec.dwReserved = 0
+ addrec.data = name
+
+ addrecbuf = dnsserver.DNS_RPC_RECORD_BUF()
+ addrecbuf.rec = addrec
+
+ res = dns_conn.DnssrvUpdateRecord2(0x00070000,
+ 0,
+ server,
+ dnszone,
+ newname,
+ addrecbuf,
+ None)
+
+
+ print("querying the NS record")
+ res = dns_conn.DnssrvEnumRecords2(0x00070000,
+ 0,
+ server,
+ dnszone,
+ newname,
+ None,
+ dnsp.DNS_TYPE_NS,
+ 0x0f,
+ None,
+ None)
diff --git a/source4/scripting/python/pyglue.c b/source4/scripting/python/pyglue.c
index f89785f971..c21de46798 100644
--- a/source4/scripting/python/pyglue.c
+++ b/source4/scripting/python/pyglue.c
@@ -25,6 +25,10 @@
void init_glue(void);
+#ifndef Py_RETURN_NONE
+#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#endif
+
static PyObject *py_generate_random_str(PyObject *self, PyObject *args)
{
int len;
@@ -59,9 +63,13 @@ static PyObject *py_generate_random_password(PyObject *self, PyObject *args)
static PyObject *py_unix2nttime(PyObject *self, PyObject *args)
{
time_t t;
+ unsigned int _t;
NTTIME nt;
- if (!PyArg_ParseTuple(args, "I", &t))
+
+ if (!PyArg_ParseTuple(args, "I", &_t)) {
return NULL;
+ }
+ t = _t;
unix_to_nt_time(&nt, t);
@@ -149,22 +157,22 @@ static PyObject *py_interface_ips(PyObject *self, PyObject *args)
return NULL;
}
- load_interfaces(tmp_ctx, lpcfg_interfaces(lp_ctx), &ifaces);
+ load_interface_list(tmp_ctx, lp_ctx, &ifaces);
- count = iface_count(ifaces);
+ count = iface_list_count(ifaces);
/* first count how many are not loopback addresses */
for (ifcount = i = 0; i<count; i++) {
- const char *ip = iface_n_ip(ifaces, i);
- if (!(!all_interfaces && iface_same_net(ip, "127.0.0.1", "255.0.0.0"))) {
+ const char *ip = iface_list_n_ip(ifaces, i);
+ if (!(!all_interfaces && iface_list_same_net(ip, "127.0.0.1", "255.0.0.0"))) {
ifcount++;
}
}
pylist = PyList_New(ifcount);
for (ifcount = i = 0; i<count; i++) {
- const char *ip = iface_n_ip(ifaces, i);
- if (!(!all_interfaces && iface_same_net(ip, "127.0.0.1", "255.0.0.0"))) {
+ const char *ip = iface_list_n_ip(ifaces, i);
+ if (!(!all_interfaces && iface_list_same_net(ip, "127.0.0.1", "255.0.0.0"))) {
PyList_SetItem(pylist, ifcount, PyString_FromString(ip));
ifcount++;
}
@@ -173,6 +181,30 @@ static PyObject *py_interface_ips(PyObject *self, PyObject *args)
return pylist;
}
+static PyObject *py_strcasecmp_m(PyObject *self, PyObject *args)
+{
+ char *s1, *s2;
+
+ if (!PyArg_ParseTuple(args, "ss", &s1, &s2))
+ return NULL;
+
+ return PyInt_FromLong(strcasecmp_m(s1, s2));
+}
+
+static PyObject *py_strstr_m(PyObject *self, PyObject *args)
+{
+ char *s1, *s2, *ret;
+
+ if (!PyArg_ParseTuple(args, "ss", &s1, &s2))
+ return NULL;
+
+ ret = strstr_m(s1, s2);
+ if (!ret) {
+ Py_RETURN_NONE;
+ }
+ return PyString_FromString(ret);
+}
+
static PyMethodDef py_misc_methods[] = {
{ "generate_random_str", (PyCFunction)py_generate_random_str, METH_VARARGS,
"generate_random_str(len) -> string\n"
@@ -192,6 +224,10 @@ static PyMethodDef py_misc_methods[] = {
"get debug level" },
{ "interface_ips", (PyCFunction)py_interface_ips, METH_VARARGS,
"get interface IP address list"},
+ { "strcasecmp_m", (PyCFunction)py_strcasecmp_m, METH_VARARGS,
+ "(for testing) compare two strings using Samba's strcasecmp_m()"},
+ { "strstr_m", (PyCFunction)py_strstr_m, METH_VARARGS,
+ "(for testing) find one string in another with Samba's strstr_m()"},
{ NULL }
};
@@ -208,12 +244,5 @@ void init_glue(void)
PyModule_AddObject(m, "version",
PyString_FromString(SAMBA_VERSION_STRING));
-
- /* one of the most annoying things about python scripts is
- that they don't die when you hit control-C. This fixes that
- sillyness. As we do all database operations using
- transactions, this is also safe.
- */
- signal(SIGINT, SIG_DFL);
}
diff --git a/source4/scripting/python/samba/__init__.py b/source4/scripting/python/samba/__init__.py
index 2a54f47d2b..cd2a309fc0 100644
--- a/source4/scripting/python/samba/__init__.py
+++ b/source4/scripting/python/samba/__init__.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
#
@@ -26,18 +24,21 @@ __docformat__ = "restructuredText"
import os
import sys
+import samba.param
+
def source_tree_topdir():
- '''return the top level directory (the one containing the source4 directory)'''
- paths = [ "../../..", "../../../.." ]
+ """Return the top level source directory."""
+ paths = ["../../..", "../../../.."]
for p in paths:
topdir = os.path.normpath(os.path.join(os.path.dirname(__file__), p))
if os.path.exists(os.path.join(topdir, 'source4')):
return topdir
raise RuntimeError("unable to find top level source directory")
+
def in_source_tree():
- '''return True if we are running from within the samba source tree'''
+ """Return True if we are running from within the samba source tree"""
try:
topdir = source_tree_topdir()
except RuntimeError:
@@ -45,10 +46,10 @@ def in_source_tree():
return True
-
import ldb
from samba._ldb import Ldb as _Ldb
+
class Ldb(_Ldb):
"""Simple Samba-specific LDB subclass that takes care
of setting up the modules dir, credentials pointers, etc.
@@ -77,8 +78,8 @@ class Ldb(_Ldb):
if modules_dir is not None:
self.set_modules_dir(modules_dir)
- elif lp is not None:
- self.set_modules_dir(os.path.join(lp.get("modules dir"), "ldb"))
+ else:
+ self.set_modules_dir(os.path.join(samba.param.modules_dir(), "ldb"))
if session_info is not None:
self.set_session_info(session_info)
@@ -104,7 +105,7 @@ class Ldb(_Ldb):
# Allow admins to force non-sync ldb for all databases
if lp is not None:
nosync_p = lp.get("nosync", "ldb")
- if nosync_p is not None and nosync_p == True:
+ if nosync_p is not None and nosync_p:
flags |= ldb.FLG_NOSYNC
self.set_create_perms(0600)
@@ -166,7 +167,8 @@ class Ldb(_Ldb):
# Try to delete user/computer accounts to allow deletion of groups
self.erase_users_computers(basedn)
- # Delete the 'visible' records, and the invisble 'deleted' records (if this DB supports it)
+ # Delete the 'visible' records, and the invisble 'deleted' records (if
+ # this DB supports it)
for msg in self.search(basedn, ldb.SCOPE_SUBTREE,
"(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))",
[], controls=["show_deleted:0", "show_recycled:0"]):
@@ -178,7 +180,8 @@ class Ldb(_Ldb):
raise
res = self.search(basedn, ldb.SCOPE_SUBTREE,
- "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))", [], controls=["show_deleted:0", "show_recycled:0"])
+ "(&(|(objectclass=*)(distinguishedName=*))(!(distinguishedName=@BASEINFO)))",
+ [], controls=["show_deleted:0", "show_recycled:0"])
assert len(res) == 0
# delete the specials
@@ -295,14 +298,18 @@ def setup_file(template, fname, subst_vars=None):
finally:
f.close()
+MAX_NETBIOS_NAME_LEN = 15
+def is_valid_netbios_char(c):
+ return (c.isalnum() or c in " !#$%&'()-.@^_{}~")
+
def valid_netbios_name(name):
"""Check whether a name is valid as a NetBIOS name. """
# See crh's book (1.4.1.1)
- if len(name) > 15:
+ if len(name) > MAX_NETBIOS_NAME_LEN:
return False
for x in name:
- if not x.isalnum() and not x in " !#$%&'()-.@^_{}~":
+ if not is_valid_netbios_char(x):
return False
return True
@@ -338,7 +345,11 @@ def ensure_external_module(modulename, location):
import_bundled_package(modulename, location)
-from samba import _glue
+def dn_from_dns_name(dnsdomain):
+ """return a DN from a DNS name domain/forest root"""
+ return "DC=" + ",DC=".join(dnsdomain.split("."))
+
+import _glue
version = _glue.version
interface_ips = _glue.interface_ips
set_debug_level = _glue.set_debug_level
@@ -348,3 +359,5 @@ nttime2string = _glue.nttime2string
nttime2unix = _glue.nttime2unix
unix2nttime = _glue.unix2nttime
generate_random_password = _glue.generate_random_password
+strcasecmp_m = _glue.strcasecmp_m
+strstr_m = _glue.strstr_m
diff --git a/source4/scripting/python/samba/common.py b/source4/scripting/python/samba/common.py
new file mode 100644
index 0000000000..e47f276f81
--- /dev/null
+++ b/source4/scripting/python/samba/common.py
@@ -0,0 +1,99 @@
+# Samba common functions
+#
+# Copyright (C) Matthieu Patou <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/>.
+#
+
+
+import ldb
+import dsdb
+
+
+def confirm(msg, forced=False, allow_all=False):
+ """confirm an action with the user
+
+ :param msg: A string to print to the user
+ :param forced: Are the answer forced
+ """
+ if forced:
+ print("%s [YES]" % msg)
+ return True
+
+ mapping = {
+ 'Y': True,
+ 'YES': True,
+ '': False,
+ 'N': False,
+ 'NO': False,
+ }
+
+ prompt = '[y/N]'
+
+ if allow_all:
+ mapping['ALL'] = 'ALL'
+ mapping['NONE'] = 'NONE'
+ prompt = '[y/N/all/none]'
+
+ while True:
+ v = raw_input(msg + ' %s ' % prompt)
+ v = v.upper()
+ if v in mapping:
+ return mapping[v]
+ print("Unknown response '%s'" % v)
+
+
+def normalise_int32(ivalue):
+ '''normalise a ldap integer to signed 32 bit'''
+ if int(ivalue) & 0x80000000 and int(ivalue) > 0:
+ return str(int(ivalue) - 0x100000000)
+ return str(ivalue)
+
+
+class dsdb_Dn(object):
+ '''a class for binary DN'''
+
+ def __init__(self, samdb, dnstring, syntax_oid=None):
+ '''create a dsdb_Dn'''
+ if syntax_oid is None:
+ # auto-detect based on string
+ if dnstring.startswith("B:"):
+ syntax_oid = dsdb.DSDB_SYNTAX_BINARY_DN
+ elif dnstring.startswith("S:"):
+ syntax_oid = dsdb.DSDB_SYNTAX_STRING_DN
+ else:
+ syntax_oid = dsdb.DSDB_SYNTAX_OR_NAME
+ if syntax_oid in [dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_STRING_DN]:
+ # it is a binary DN
+ colons = dnstring.split(':')
+ if len(colons) < 4:
+ raise RuntimeError("Invalid DN %s" % dnstring)
+ prefix_len = 4 + len(colons[1]) + int(colons[1])
+ self.prefix = dnstring[0:prefix_len]
+ self.binary = self.prefix[4:-1]
+ self.dnstring = dnstring[prefix_len:]
+ else:
+ self.dnstring = dnstring
+ self.prefix = ''
+ self.binary = ''
+ self.dn = ldb.Dn(samdb, self.dnstring)
+
+ def __str__(self):
+ return self.prefix + str(self.dn.extended_str(mode=1))
+
+ def get_binary_integer(self):
+ '''return binary part of a dsdb_Dn as an integer, or None'''
+ if self.prefix == '':
+ return None
+ return int(self.binary, 16)
diff --git a/source4/scripting/python/samba/dbchecker.py b/source4/scripting/python/samba/dbchecker.py
new file mode 100644
index 0000000000..e1be6c4faa
--- /dev/null
+++ b/source4/scripting/python/samba/dbchecker.py
@@ -0,0 +1,751 @@
+# Samba4 AD database checker
+#
+# Copyright (C) Andrew Tridgell 2011
+# Copyright (C) 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 ldb
+from samba import dsdb
+from samba import common
+from samba.dcerpc import misc
+from samba.ndr import ndr_unpack
+from samba.dcerpc import drsblobs
+from samba.common import dsdb_Dn
+
+
+class dbcheck(object):
+ """check a SAM database for errors"""
+
+ def __init__(self, samdb, samdb_schema=None, verbose=False, fix=False,
+ yes=False, quiet=False, in_transaction=False):
+ self.samdb = samdb
+ self.dict_oid_name = None
+ self.samdb_schema = (samdb_schema or samdb)
+ self.verbose = verbose
+ self.fix = fix
+ self.yes = yes
+ self.quiet = quiet
+ self.remove_all_unknown_attributes = False
+ self.remove_all_empty_attributes = False
+ self.fix_all_normalisation = False
+ self.fix_all_DN_GUIDs = False
+ self.remove_all_deleted_DN_links = False
+ self.fix_all_target_mismatch = False
+ self.fix_all_metadata = False
+ self.fix_time_metadata = False
+ self.fix_all_missing_backlinks = False
+ self.fix_all_orphaned_backlinks = False
+ self.fix_rmd_flags = False
+ self.seize_fsmo_role = False
+ self.move_to_lost_and_found = False
+ self.fix_instancetype = False
+ self.in_transaction = in_transaction
+ self.infrastructure_dn = ldb.Dn(samdb, "CN=Infrastructure," + samdb.domain_dn())
+ self.naming_dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
+ self.schema_dn = samdb.get_schema_basedn()
+ self.rid_dn = ldb.Dn(samdb, "CN=RID Manager$,CN=System," + samdb.domain_dn())
+ self.ntds_dsa = samdb.get_dsServiceName()
+
+ res = self.samdb.search(base=self.ntds_dsa, scope=ldb.SCOPE_BASE, attrs=['msDS-hasMasterNCs'])
+ if "msDS-hasMasterNCs" in res[0]:
+ self.write_ncs = res[0]["msDS-hasMasterNCs"]
+ else:
+ self.write_ncs = None
+
+ def check_database(self, DN=None, scope=ldb.SCOPE_SUBTREE, controls=[], attrs=['*']):
+ '''perform a database check, returning the number of errors found'''
+
+ res = self.samdb.search(base=DN, scope=scope, attrs=['dn'], controls=controls)
+ self.report('Checking %u objects' % len(res))
+ error_count = 0
+
+ for object in res:
+ error_count += self.check_object(object.dn, attrs=attrs)
+
+ if DN is None:
+ error_count += self.check_rootdse()
+
+ if error_count != 0 and not self.fix:
+ self.report("Please use --fix to fix these errors")
+
+ self.report('Checked %u objects (%u errors)' % (len(res), error_count))
+ return error_count
+
+ def report(self, msg):
+ '''print a message unless quiet is set'''
+ if not self.quiet:
+ print(msg)
+
+ def confirm(self, msg, allow_all=False, forced=False):
+ '''confirm a change'''
+ if not self.fix:
+ return False
+ if self.quiet:
+ return self.yes
+ if self.yes:
+ forced = True
+ return common.confirm(msg, forced=forced, allow_all=allow_all)
+
+ ################################################################
+ # a local confirm function with support for 'all'
+ def confirm_all(self, msg, all_attr):
+ '''confirm a change with support for "all" '''
+ if not self.fix:
+ return False
+ if self.quiet:
+ return self.yes
+ if getattr(self, all_attr) == 'NONE':
+ return False
+ if getattr(self, all_attr) == 'ALL':
+ forced = True
+ else:
+ forced = self.yes
+ c = common.confirm(msg, forced=forced, allow_all=True)
+ if c == 'ALL':
+ setattr(self, all_attr, 'ALL')
+ return True
+ if c == 'NONE':
+ setattr(self, all_attr, 'NONE')
+ return False
+ return c
+
+ def do_modify(self, m, controls, msg, validate=True):
+ '''perform a modify with optional verbose output'''
+ if self.verbose:
+ self.report(self.samdb.write_ldif(m, ldb.CHANGETYPE_MODIFY))
+ try:
+ controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
+ self.samdb.modify(m, controls=controls, validate=validate)
+ except Exception, err:
+ self.report("%s : %s" % (msg, err))
+ return False
+ return True
+
+ def do_rename(self, from_dn, to_rdn, to_base, controls, msg):
+ '''perform a modify with optional verbose output'''
+ if self.verbose:
+ self.report("""dn: %s
+changeType: modrdn
+newrdn: %s
+deleteOldRdn: 1
+newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base)))
+ try:
+ to_dn = to_rdn + to_base
+ controls = controls + ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK]
+ self.samdb.rename(from_dn, to_dn, controls=controls)
+ except Exception, err:
+ self.report("%s : %s" % (msg, err))
+ return False
+ return True
+
+ def err_empty_attribute(self, dn, attrname):
+ '''fix empty attributes'''
+ self.report("ERROR: Empty attribute %s in %s" % (attrname, dn))
+ if not self.confirm_all('Remove empty attribute %s from %s?' % (attrname, dn), 'remove_all_empty_attributes'):
+ self.report("Not fixing empty attribute %s" % attrname)
+ return
+
+ m = ldb.Message()
+ m.dn = dn
+ m[attrname] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, attrname)
+ if self.do_modify(m, ["relax:0", "show_recycled:1"],
+ "Failed to remove empty attribute %s" % attrname, validate=False):
+ self.report("Removed empty attribute %s" % attrname)
+
+ def err_normalise_mismatch(self, dn, attrname, values):
+ '''fix attribute normalisation errors'''
+ self.report("ERROR: Normalisation error for attribute %s in %s" % (attrname, dn))
+ mod_list = []
+ for val in values:
+ normalised = self.samdb.dsdb_normalise_attributes(
+ self.samdb_schema, attrname, [val])
+ if len(normalised) != 1:
+ self.report("Unable to normalise value '%s'" % val)
+ mod_list.append((val, ''))
+ elif (normalised[0] != val):
+ self.report("value '%s' should be '%s'" % (val, normalised[0]))
+ mod_list.append((val, normalised[0]))
+ if not self.confirm_all('Fix normalisation for %s from %s?' % (attrname, dn), 'fix_all_normalisation'):
+ self.report("Not fixing attribute %s" % attrname)
+ return
+
+ m = ldb.Message()
+ m.dn = dn
+ for i in range(0, len(mod_list)):
+ (val, nval) = mod_list[i]
+ m['value_%u' % i] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
+ if nval != '':
+ m['normv_%u' % i] = ldb.MessageElement(nval, ldb.FLAG_MOD_ADD,
+ attrname)
+
+ if self.do_modify(m, ["relax:0", "show_recycled:1"],
+ "Failed to normalise attribute %s" % attrname,
+ validate=False):
+ self.report("Normalised attribute %s" % attrname)
+
+ def err_normalise_mismatch_replace(self, dn, attrname, values):
+ '''fix attribute normalisation errors'''
+ normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, values)
+ self.report("ERROR: Normalisation error for attribute '%s' in '%s'" % (attrname, dn))
+ self.report("Values/Order of values do/does not match: %s/%s!" % (values, list(normalised)))
+ if list(normalised) == values:
+ return
+ if not self.confirm_all("Fix normalisation for '%s' from '%s'?" % (attrname, dn), 'fix_all_normalisation'):
+ self.report("Not fixing attribute '%s'" % attrname)
+ return
+
+ m = ldb.Message()
+ m.dn = dn
+ m[attrname] = ldb.MessageElement(normalised, ldb.FLAG_MOD_REPLACE, attrname)
+
+ if self.do_modify(m, ["relax:0", "show_recycled:1"],
+ "Failed to normalise attribute %s" % attrname,
+ validate=False):
+ self.report("Normalised attribute %s" % attrname)
+
+ def is_deleted_objects_dn(self, dsdb_dn):
+ '''see if a dsdb_Dn is the special Deleted Objects DN'''
+ return dsdb_dn.prefix == "B:32:18E2EA80684F11D2B9AA00C04F79F805:"
+
+ def err_deleted_dn(self, dn, attrname, val, dsdb_dn, correct_dn):
+ """handle a DN pointing to a deleted object"""
+ self.report("ERROR: target DN is deleted for %s in object %s - %s" % (attrname, dn, val))
+ self.report("Target GUID points at deleted DN %s" % correct_dn)
+ if not self.confirm_all('Remove DN link?', 'remove_all_deleted_DN_links'):
+ self.report("Not removing")
+ return
+ m = ldb.Message()
+ m.dn = dn
+ m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
+ if self.do_modify(m, ["show_recycled:1", "local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK],
+ "Failed to remove deleted DN attribute %s" % attrname):
+ self.report("Removed deleted DN on attribute %s" % attrname)
+
+ def err_missing_dn_GUID(self, dn, attrname, val, dsdb_dn):
+ """handle a missing target DN (both GUID and DN string form are missing)"""
+ # check if its a backlink
+ linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
+ if (linkID & 1 == 0) and str(dsdb_dn).find('DEL\\0A') == -1:
+ self.report("Not removing dangling forward link")
+ return
+ self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn)
+
+ def err_incorrect_dn_GUID(self, dn, attrname, val, dsdb_dn, errstr):
+ """handle a missing GUID extended DN component"""
+ self.report("ERROR: %s component for %s in object %s - %s" % (errstr, attrname, dn, val))
+ controls=["extended_dn:1:1", "show_recycled:1"]
+ try:
+ res = self.samdb.search(base=str(dsdb_dn.dn), scope=ldb.SCOPE_BASE,
+ attrs=[], controls=controls)
+ except ldb.LdbError, (enum, estr):
+ self.report("unable to find object for DN %s - (%s)" % (dsdb_dn.dn, estr))
+ self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
+ return
+ if len(res) == 0:
+ self.report("unable to find object for DN %s" % dsdb_dn.dn)
+ self.err_missing_dn_GUID(dn, attrname, val, dsdb_dn)
+ return
+ dsdb_dn.dn = res[0].dn
+
+ if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_DN_GUIDs'):
+ self.report("Not fixing %s" % errstr)
+ return
+ m = ldb.Message()
+ m.dn = dn
+ m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
+ m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
+
+ if self.do_modify(m, ["show_recycled:1"],
+ "Failed to fix %s on attribute %s" % (errstr, attrname)):
+ self.report("Fixed %s on attribute %s" % (errstr, attrname))
+
+ def err_dn_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, errstr):
+ """handle a DN string being incorrect"""
+ self.report("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname, dn, val))
+ dsdb_dn.dn = correct_dn
+
+ if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_target_mismatch'):
+ self.report("Not fixing %s" % errstr)
+ return
+ m = ldb.Message()
+ m.dn = dn
+ m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
+ m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname)
+ if self.do_modify(m, ["show_recycled:1"],
+ "Failed to fix incorrect DN string on attribute %s" % attrname):
+ self.report("Fixed incorrect DN string on attribute %s" % (attrname))
+
+ def err_unknown_attribute(self, obj, attrname):
+ '''handle an unknown attribute error'''
+ self.report("ERROR: unknown attribute '%s' in %s" % (attrname, obj.dn))
+ if not self.confirm_all('Remove unknown attribute %s' % attrname, 'remove_all_unknown_attributes'):
+ self.report("Not removing %s" % attrname)
+ return
+ m = ldb.Message()
+ m.dn = obj.dn
+ m['old_value'] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, attrname)
+ if self.do_modify(m, ["relax:0", "show_recycled:1"],
+ "Failed to remove unknown attribute %s" % attrname):
+ self.report("Removed unknown attribute %s" % (attrname))
+
+ def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn):
+ '''handle a missing backlink value'''
+ self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn))
+ if not self.confirm_all('Fix missing backlink %s' % backlink_name, 'fix_all_missing_backlinks'):
+ self.report("Not fixing missing backlink %s" % backlink_name)
+ return
+ m = ldb.Message()
+ m.dn = obj.dn
+ m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
+ m['new_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_ADD, attrname)
+ if self.do_modify(m, ["show_recycled:1"],
+ "Failed to fix missing backlink %s" % backlink_name):
+ self.report("Fixed missing backlink %s" % (backlink_name))
+
+ def err_incorrect_rmd_flags(self, obj, attrname, revealed_dn):
+ '''handle a incorrect RMD_FLAGS value'''
+ rmd_flags = int(revealed_dn.dn.get_extended_component("RMD_FLAGS"))
+ self.report("ERROR: incorrect RMD_FLAGS value %u for attribute '%s' in %s for link %s" % (rmd_flags, attrname, obj.dn, revealed_dn.dn.extended_str()))
+ if not self.confirm_all('Fix incorrect RMD_FLAGS %u' % rmd_flags, 'fix_rmd_flags'):
+ self.report("Not fixing incorrect RMD_FLAGS %u" % rmd_flags)
+ return
+ m = ldb.Message()
+ m.dn = obj.dn
+ m['old_value'] = ldb.MessageElement(str(revealed_dn), ldb.FLAG_MOD_DELETE, attrname)
+ if self.do_modify(m, ["show_recycled:1", "reveal_internals:0", "show_deleted:0"],
+ "Failed to fix incorrect RMD_FLAGS %u" % rmd_flags):
+ self.report("Fixed incorrect RMD_FLAGS %u" % (rmd_flags))
+
+ def err_orphaned_backlink(self, obj, attrname, val, link_name, target_dn):
+ '''handle a orphaned backlink value'''
+ self.report("ERROR: orphaned backlink attribute '%s' in %s for link %s in %s" % (attrname, obj.dn, link_name, target_dn))
+ if not self.confirm_all('Remove orphaned backlink %s' % link_name, 'fix_all_orphaned_backlinks'):
+ self.report("Not removing orphaned backlink %s" % link_name)
+ return
+ m = ldb.Message()
+ m.dn = obj.dn
+ m['value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname)
+ if self.do_modify(m, ["show_recycled:1", "relax:0"],
+ "Failed to fix orphaned backlink %s" % link_name):
+ self.report("Fixed orphaned backlink %s" % (link_name))
+
+ def err_no_fsmoRoleOwner(self, obj):
+ '''handle a missing fSMORoleOwner'''
+ self.report("ERROR: fSMORoleOwner not found for role %s" % (obj.dn))
+ res = self.samdb.search("",
+ scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
+ assert len(res) == 1
+ serviceName = res[0]["dsServiceName"][0]
+ if not self.confirm_all('Sieze role %s onto current DC by adding fSMORoleOwner=%s' % (obj.dn, serviceName), 'seize_fsmo_role'):
+ self.report("Not Siezing role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
+ return
+ m = ldb.Message()
+ m.dn = obj.dn
+ m['value'] = ldb.MessageElement(serviceName, ldb.FLAG_MOD_ADD, 'fSMORoleOwner')
+ if self.do_modify(m, [],
+ "Failed to sieze role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName)):
+ self.report("Siezed role %s onto current DC by adding fSMORoleOwner=%s" % (obj.dn, serviceName))
+
+ def err_missing_parent(self, obj):
+ '''handle a missing parent'''
+ self.report("ERROR: parent object not found for %s" % (obj.dn))
+ if not self.confirm_all('Move object %s into LostAndFound?' % (obj.dn), 'move_to_lost_and_found'):
+ self.report('Not moving object %s into LostAndFound' % (obj.dn))
+ return
+
+ keep_transaction = True
+ self.samdb.transaction_start()
+ try:
+ nc_root = self.samdb.get_nc_root(obj.dn);
+ lost_and_found = self.samdb.get_wellknown_dn(nc_root, dsdb.DS_GUID_LOSTANDFOUND_CONTAINER)
+ new_dn = ldb.Dn(self.samdb, str(obj.dn))
+ new_dn.remove_base_components(len(new_dn) - 1)
+ if self.do_rename(obj.dn, new_dn, lost_and_found, ["show_deleted:0", "relax:0"],
+ "Failed to rename object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found)):
+ self.report("Renamed object %s into lostAndFound at %s" % (obj.dn, new_dn + lost_and_found))
+
+ m = ldb.Message()
+ m.dn = obj.dn
+ m['lastKnownParent'] = ldb.MessageElement(str(obj.dn.parent()), ldb.FLAG_MOD_REPLACE, 'lastKnownParent')
+
+ if self.do_modify(m, [],
+ "Failed to set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found)):
+ self.report("Set lastKnownParent on lostAndFound object at %s" % (new_dn + lost_and_found))
+ keep_transaction = True
+ except:
+ self.samdb.transaction_cancel()
+ raise
+
+ if keep_transaction:
+ self.samdb.transaction_commit()
+ else:
+ self.samdb.transaction_cancel()
+
+
+ def err_wrong_instancetype(self, obj, calculated_instancetype):
+ '''handle a wrong instanceType'''
+ self.report("ERROR: wrong instanceType %s on %s, should be %d" % (obj["instanceType"], obj.dn, calculated_instancetype))
+ if not self.confirm_all('Change instanceType from %s to %d on %s?' % (obj["instanceType"], calculated_instancetype, obj.dn), 'fix_instancetype'):
+ self.report('Not changing instanceType from %s to %d on %s' % (obj["instanceType"], calculated_instancetype, obj.dn))
+ return
+
+ m = ldb.Message()
+ m.dn = obj.dn
+ m['value'] = ldb.MessageElement(str(calculated_instancetype), ldb.FLAG_MOD_REPLACE, 'instanceType')
+ if self.do_modify(m, ["local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA],
+ "Failed to correct missing instanceType on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype)):
+ self.report("Corrected instancetype on %s by setting instanceType=%d" % (obj.dn, calculated_instancetype))
+
+ def find_revealed_link(self, dn, attrname, guid):
+ '''return a revealed link in an object'''
+ res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[attrname],
+ controls=["show_deleted:0", "extended_dn:0", "reveal_internals:0"])
+ syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
+ for val in res[0][attrname]:
+ dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
+ guid2 = dsdb_dn.dn.get_extended_component("GUID")
+ if guid == guid2:
+ return dsdb_dn
+ return None
+
+ def check_dn(self, obj, attrname, syntax_oid):
+ '''check a DN attribute for correctness'''
+ error_count = 0
+ for val in obj[attrname]:
+ dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid)
+
+ # all DNs should have a GUID component
+ guid = dsdb_dn.dn.get_extended_component("GUID")
+ if guid is None:
+ error_count += 1
+ self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn,
+ "missing GUID")
+ continue
+
+ guidstr = str(misc.GUID(guid))
+
+ attrs = ['isDeleted']
+ linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)
+ reverse_link_name = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname)
+ if reverse_link_name is not None:
+ attrs.append(reverse_link_name)
+
+ # check its the right GUID
+ try:
+ res = self.samdb.search(base="<GUID=%s>" % guidstr, scope=ldb.SCOPE_BASE,
+ attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1"])
+ except ldb.LdbError, (enum, estr):
+ error_count += 1
+ self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn, "incorrect GUID")
+ continue
+
+ # now we have two cases - the source object might or might not be deleted
+ is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE'
+ target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE'
+
+ # the target DN is not allowed to be deleted, unless the target DN is the
+ # special Deleted Objects container
+ if target_is_deleted and not is_deleted and not self.is_deleted_objects_dn(dsdb_dn):
+ error_count += 1
+ self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn)
+ continue
+
+ # check the DN matches in string form
+ if res[0].dn.extended_str() != dsdb_dn.dn.extended_str():
+ error_count += 1
+ self.err_dn_target_mismatch(obj.dn, attrname, val, dsdb_dn,
+ res[0].dn, "incorrect string version of DN")
+ continue
+
+ if is_deleted and not target_is_deleted and reverse_link_name is not None:
+ revealed_dn = self.find_revealed_link(obj.dn, attrname, guid)
+ rmd_flags = revealed_dn.dn.get_extended_component("RMD_FLAGS")
+ if rmd_flags is not None and (int(rmd_flags) & 1) == 0:
+ # the RMD_FLAGS for this link should be 1, as the target is deleted
+ self.err_incorrect_rmd_flags(obj, attrname, revealed_dn)
+ continue
+
+ # check the reverse_link is correct if there should be one
+ if reverse_link_name is not None:
+ match_count = 0
+ if reverse_link_name in res[0]:
+ for v in res[0][reverse_link_name]:
+ if v == obj.dn.extended_str():
+ match_count += 1
+ if match_count != 1:
+ error_count += 1
+ if linkID & 1:
+ self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
+ else:
+ self.err_missing_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn)
+ continue
+
+ return error_count
+
+
+ def get_originating_time(self, val, attid):
+ '''Read metadata properties and return the originating time for
+ a given attributeId.
+
+ :return: the originating time or 0 if not found
+ '''
+
+ repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
+ obj = repl.ctr
+
+ for o in repl.ctr.array:
+ if o.attid == attid:
+ return o.originating_change_time
+
+ return 0
+
+ def process_metadata(self, val):
+ '''Read metadata properties and list attributes in it'''
+
+ list_att = []
+
+ repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
+ obj = repl.ctr
+
+ for o in repl.ctr.array:
+ att = self.samdb_schema.get_lDAPDisplayName_by_attid(o.attid)
+ list_att.append(att.lower())
+
+ return list_att
+
+
+ def fix_metadata(self, dn, attr):
+ '''re-write replPropertyMetaData elements for a single attribute for a
+ object. This is used to fix missing replPropertyMetaData elements'''
+ res = self.samdb.search(base = dn, scope=ldb.SCOPE_BASE, attrs = [attr],
+ controls = ["search_options:1:2", "show_recycled:1"])
+ msg = res[0]
+ nmsg = ldb.Message()
+ nmsg.dn = dn
+ nmsg[attr] = ldb.MessageElement(msg[attr], ldb.FLAG_MOD_REPLACE, attr)
+ if self.do_modify(nmsg, ["relax:0", "provision:0", "show_recycled:1"],
+ "Failed to fix metadata for attribute %s" % attr):
+ self.report("Fixed metadata for attribute %s" % attr)
+
+ def is_fsmo_role(self, dn):
+ if dn == self.samdb.domain_dn:
+ return True
+ if dn == self.infrastructure_dn:
+ return True
+ if dn == self.naming_dn:
+ return True
+ if dn == self.schema_dn:
+ return True
+ if dn == self.rid_dn:
+ return True
+
+ return False
+
+ def calculate_instancetype(self, dn):
+ instancetype = 0
+ nc_root = self.samdb.get_nc_root(dn)
+ if dn == nc_root:
+ instancetype |= dsdb.INSTANCE_TYPE_IS_NC_HEAD
+ try:
+ self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE, attrs=[], controls=["show_recycled:1"])
+ except ldb.LdbError, (enum, estr):
+ if enum != ldb.ERR_NO_SUCH_OBJECT:
+ raise
+ else:
+ instancetype |= dsdb.INSTANCE_TYPE_NC_ABOVE
+
+ if self.write_ncs is not None and str(nc_root) in self.write_ncs:
+ instancetype |= dsdb.INSTANCE_TYPE_WRITE
+
+ return instancetype
+
+ def check_object(self, dn, attrs=['*']):
+ '''check one object'''
+ if self.verbose:
+ self.report("Checking object %s" % dn)
+ if '*' in attrs:
+ attrs.append("replPropertyMetaData")
+
+ try:
+ res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE,
+ controls=["extended_dn:1:1", "show_recycled:1", "show_deleted:1"],
+ attrs=attrs)
+ except ldb.LdbError, (enum, estr):
+ if enum == ldb.ERR_NO_SUCH_OBJECT:
+ if self.in_transaction:
+ self.report("ERROR: Object %s disappeared during check" % dn)
+ return 1
+ return 0
+ raise
+ if len(res) != 1:
+ self.report("ERROR: Object %s failed to load during check" % dn)
+ return 1
+ obj = res[0]
+ error_count = 0
+ list_attrs_from_md = []
+ list_attrs_seen = []
+ got_repl_property_meta_data = False
+
+ for attrname in obj:
+ if attrname == 'dn':
+ continue
+
+ if str(attrname).lower() == 'replpropertymetadata':
+ list_attrs_from_md = self.process_metadata(obj[attrname])
+ got_repl_property_meta_data = True
+ continue
+
+ if str(attrname).lower() == 'objectclass':
+ normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, list(obj[attrname]))
+ if list(normalised) != list(obj[attrname]):
+ self.err_normalise_mismatch_replace(dn, attrname, list(obj[attrname]))
+ error_count += 1
+ continue
+
+ # check for empty attributes
+ for val in obj[attrname]:
+ if val == '':
+ self.err_empty_attribute(dn, attrname)
+ error_count += 1
+ continue
+
+ # get the syntax oid for the attribute, so we can can have
+ # special handling for some specific attribute types
+ try:
+ syntax_oid = self.samdb_schema.get_syntax_oid_from_lDAPDisplayName(attrname)
+ except Exception, msg:
+ self.err_unknown_attribute(obj, attrname)
+ error_count += 1
+ continue
+
+ flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname)
+ if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED
+ and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED
+ and not self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)):
+ list_attrs_seen.append(str(attrname).lower())
+
+ if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME,
+ dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]:
+ # it's some form of DN, do specialised checking on those
+ error_count += self.check_dn(obj, attrname, syntax_oid)
+
+ # check for incorrectly normalised attributes
+ for val in obj[attrname]:
+ normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val])
+ if len(normalised) != 1 or normalised[0] != val:
+ self.err_normalise_mismatch(dn, attrname, obj[attrname])
+ error_count += 1
+ break
+
+ if str(attrname).lower() == "instancetype":
+ calculated_instancetype = self.calculate_instancetype(dn)
+ if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype):
+ self.err_wrong_instancetype(obj, calculated_instancetype)
+
+ show_dn = True
+ if got_repl_property_meta_data:
+ rdn = (str(dn).split(","))[0]
+ if rdn == "CN=Deleted Objects":
+ isDeletedAttId = 131120
+ # It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
+
+ expectedTimeDo = 2650466015990000000
+ originating = self.get_originating_time(obj["replPropertyMetaData"], isDeletedAttId)
+ if originating != expectedTimeDo:
+ if self.confirm_all("Fix isDeleted originating_change_time on '%s'" % str(dn), 'fix_time_metadata'):
+ nmsg = ldb.Message()
+ nmsg.dn = dn
+ nmsg["isDeleted"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isDeleted")
+ error_count += 1
+ self.samdb.modify(nmsg, controls=["provision:0"])
+
+ else:
+ self.report("Not fixing isDeleted originating_change_time on '%s'" % str(dn))
+ for att in list_attrs_seen:
+ if not att in list_attrs_from_md:
+ if show_dn:
+ self.report("On object %s" % dn)
+ show_dn = False
+ error_count += 1
+ self.report("ERROR: Attribute %s not present in replication metadata" % att)
+ if not self.confirm_all("Fix missing replPropertyMetaData element '%s'" % att, 'fix_all_metadata'):
+ self.report("Not fixing missing replPropertyMetaData element '%s'" % att)
+ continue
+ self.fix_metadata(dn, att)
+
+ if self.is_fsmo_role(dn):
+ if "fSMORoleOwner" not in obj:
+ self.err_no_fsmoRoleOwner(obj)
+ error_count += 1
+
+ try:
+ if dn != self.samdb.get_root_basedn():
+ res = self.samdb.search(base=dn.parent(), scope=ldb.SCOPE_BASE,
+ controls=["show_recycled:1", "show_deleted:1"])
+ except ldb.LdbError, (enum, estr):
+ if enum == ldb.ERR_NO_SUCH_OBJECT:
+ self.err_missing_parent(obj)
+ error_count += 1
+ else:
+ raise
+
+ return error_count
+
+ ################################################################
+ # check special @ROOTDSE attributes
+ def check_rootdse(self):
+ '''check the @ROOTDSE special object'''
+ dn = ldb.Dn(self.samdb, '@ROOTDSE')
+ if self.verbose:
+ self.report("Checking object %s" % dn)
+ res = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE)
+ if len(res) != 1:
+ self.report("Object %s disappeared during check" % dn)
+ return 1
+ obj = res[0]
+ error_count = 0
+
+ # check that the dsServiceName is in GUID form
+ if not 'dsServiceName' in obj:
+ self.report('ERROR: dsServiceName missing in @ROOTDSE')
+ return error_count+1
+
+ if not obj['dsServiceName'][0].startswith('<GUID='):
+ self.report('ERROR: dsServiceName not in GUID form in @ROOTDSE')
+ error_count += 1
+ if not self.confirm('Change dsServiceName to GUID form?'):
+ return error_count
+ res = self.samdb.search(base=ldb.Dn(self.samdb, obj['dsServiceName'][0]),
+ scope=ldb.SCOPE_BASE, attrs=['objectGUID'])
+ guid_str = str(ndr_unpack(misc.GUID, res[0]['objectGUID'][0]))
+ m = ldb.Message()
+ m.dn = dn
+ m['dsServiceName'] = ldb.MessageElement("<GUID=%s>" % guid_str,
+ ldb.FLAG_MOD_REPLACE, 'dsServiceName')
+ if self.do_modify(m, [], "Failed to change dsServiceName to GUID form", validate=False):
+ self.report("Changed dsServiceName to GUID form")
+ return error_count
+
+
+ ###############################################
+ # re-index the database
+ def reindex_database(self):
+ '''re-index the whole database'''
+ m = ldb.Message()
+ m.dn = ldb.Dn(self.samdb, "@ATTRIBUTES")
+ m['add'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_ADD, 'force_reindex')
+ m['delete'] = ldb.MessageElement('NONE', ldb.FLAG_MOD_DELETE, 'force_reindex')
+ return self.do_modify(m, [], 're-indexed database', validate=False)
diff --git a/source4/scripting/python/samba/drs_utils.py b/source4/scripting/python/samba/drs_utils.py
index 77f415ed17..481eec2920 100644
--- a/source4/scripting/python/samba/drs_utils.py
+++ b/source4/scripting/python/samba/drs_utils.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# DRS utility code
#
# Copyright Andrew Tridgell 2010
@@ -24,6 +22,90 @@ from samba.net import Net
import samba, ldb
+class drsException(Exception):
+ """Base element for drs errors"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return "drsException: " + self.value
+
+
+def drsuapi_connect(server, lp, creds):
+ """Make a DRSUAPI connection to the server.
+
+ :param server: the name of the server to connect to
+ :param lp: a samba line parameter object
+ :param creds: credential used for the connection
+ :return: A tuple with the drsuapi bind object, the drsuapi handle
+ and the supported extensions.
+ :raise drsException: if the connection fails
+ """
+
+ binding_options = "seal"
+ if int(lp.get("log level")) >= 5:
+ binding_options += ",print"
+ binding_string = "ncacn_ip_tcp:%s[%s]" % (server, binding_options)
+ try:
+ drsuapiBind = drsuapi.drsuapi(binding_string, lp, creds)
+ (drsuapiHandle, bindSupportedExtensions) = drs_DsBind(drsuapiBind)
+ except Exception, e:
+ raise drsException("DRS connection to %s failed: %s" % (server, e))
+
+ return (drsuapiBind, drsuapiHandle, bindSupportedExtensions)
+
+
+def sendDsReplicaSync(drsuapiBind, drsuapi_handle, source_dsa_guid,
+ naming_context, req_option):
+ """Send DS replica sync request.
+
+ :param drsuapiBind: a drsuapi Bind object
+ :param drsuapi_handle: a drsuapi hanle on the drsuapi connection
+ :param source_dsa_guid: the guid of the source dsa for the replication
+ :param naming_context: the DN of the naming context to replicate
+ :param req_options: replication options for the DsReplicaSync call
+ :raise drsException: if any error occur while sending and receiving the
+ reply for the dsReplicaSync
+ """
+
+ nc = drsuapi.DsReplicaObjectIdentifier()
+ nc.dn = naming_context
+
+ req1 = drsuapi.DsReplicaSyncRequest1()
+ req1.naming_context = nc;
+ req1.options = req_option
+ req1.source_dsa_guid = misc.GUID(source_dsa_guid)
+
+ try:
+ drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
+ except Exception, estr:
+ raise drsException("DsReplicaSync failed %s" % estr)
+
+
+def sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain):
+ """Send RemoveDSServer request.
+
+ :param drsuapiBind: a drsuapi Bind object
+ :param drsuapi_handle: a drsuapi hanle on the drsuapi connection
+ :param server_dsa_dn: a DN object of the server's dsa that we want to
+ demote
+ :param domain: a DN object of the server's domain
+ :raise drsException: if any error occur while sending and receiving the
+ reply for the DsRemoveDSServer
+ """
+
+ try:
+ req1 = drsuapi.DsRemoveDSServerRequest1()
+ req1.server_dn = str(server_dsa_dn)
+ req1.domain_dn = str(domain)
+ req1.commit = 1
+
+ drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
+ except Exception, estr:
+ raise drsException("DsRemoveDSServer failed %s" % estr)
+
+
def drs_DsBind(drs):
'''make a DsBind call, returning the binding handle'''
bind_info = drsuapi.DsBindInfoCtr()
@@ -62,7 +144,7 @@ def drs_DsBind(drs):
return (handle, info.info.supported_extensions)
-class drs_Replicate:
+class drs_Replicate(object):
'''DRS replication calls'''
def __init__(self, binding_string, lp, creds, samdb):
@@ -139,14 +221,14 @@ class drs_Replicate:
req8.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING
else:
req8.replica_flags |= drsuapi.DRSUAPI_DRS_WRIT_REP
- req8.max_object_count = 402
- req8.max_ndr_size = 402116
- req8.extended_op = exop
- req8.fsmo_info = 0
- req8.partial_attribute_set = None
- req8.partial_attribute_set_ex = None
- req8.mapping_ctr.num_mappings = 0
- req8.mapping_ctr.mappings = None
+ req8.max_object_count = 402
+ req8.max_ndr_size = 402116
+ req8.extended_op = exop
+ req8.fsmo_info = 0
+ req8.partial_attribute_set = None
+ req8.partial_attribute_set_ex = None
+ req8.mapping_ctr.num_mappings = 0
+ req8.mapping_ctr.mappings = None
if not schema and rodc:
req8.partial_attribute_set = self.drs_get_rodc_partial_attribute_set()
@@ -162,12 +244,12 @@ class drs_Replicate:
setattr(req5, a, getattr(req8, a))
req = req5
-
while True:
(level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, req_level, req)
- if ctr.first_object == None and ctr.object_count != 0:
+ if ctr.first_object is None and ctr.object_count != 0:
raise RuntimeError("DsGetNCChanges: NULL first_object with object_count=%u" % (ctr.object_count))
- self.net.replicate_chunk(self.replication_state, level, ctr, schema=schema)
+ self.net.replicate_chunk(self.replication_state, level, ctr,
+ schema=schema, req_level=req_level, req=req)
if ctr.more_data == 0:
break
req.highwatermark.tmp_highest_usn = ctr.new_highwatermark.tmp_highest_usn
diff --git a/source4/scripting/python/samba/getopt.py b/source4/scripting/python/samba/getopt.py
index 671142b552..c3c080084e 100644
--- a/source4/scripting/python/samba/getopt.py
+++ b/source4/scripting/python/samba/getopt.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Samba-specific bits for optparse
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
#
@@ -21,9 +19,11 @@
__docformat__ = "restructuredText"
-import optparse, os
+import optparse
+import os
from samba.credentials import (
Credentials,
+ AUTO_USE_KERBEROS,
DONT_USE_KERBEROS,
MUST_USE_KERBEROS,
)
@@ -44,36 +44,46 @@ class SambaOptions(optparse.OptionGroup):
type=int, metavar="DEBUGLEVEL", help="debug level",
callback=self._set_debuglevel)
self.add_option("--option", action="callback",
- type=str, metavar="OPTION", help="set smb.conf option from command line",
+ type=str, metavar="OPTION",
+ help="set smb.conf option from command line",
callback=self._set_option)
self.add_option("--realm", action="callback",
type=str, metavar="REALM", help="set the realm name",
callback=self._set_realm)
self._configfile = None
self._lp = LoadParm()
+ self.realm = None
def get_loadparm_path(self):
- """Return the path to the smb.conf file specified on the command line. """
+ """Return path to the smb.conf file specified on the command line."""
return self._configfile
def _load_configfile(self, option, opt_str, arg, parser):
self._configfile = arg
def _set_debuglevel(self, option, opt_str, arg, parser):
+ if arg < 0:
+ raise optparse.OptionValueError("invalid %s option value: %s" %
+ (opt_str, arg))
self._lp.set('debug level', str(arg))
def _set_realm(self, option, opt_str, arg, parser):
self._lp.set('realm', arg)
+ self.realm = arg
def _set_option(self, option, opt_str, arg, parser):
if arg.find('=') == -1:
- print("--option takes a 'a=b' argument")
- sys.exit(1)
+ raise optparse.OptionValueError(
+ "--option option takes a 'a=b' argument")
a = arg.split('=')
- self._lp.set(a[0], a[1])
+ try:
+ self._lp.set(a[0], a[1])
+ except Exception, e:
+ raise optparse.OptionValueError(
+ "invalid --option option value %r: %s" % (arg, e))
def get_loadparm(self):
- """Return a loadparm object with data specified on the command line. """
+ """Return loadparm object with data specified on the command line."""
if self._configfile is not None:
self._lp.load(self._configfile)
elif os.getenv("SMB_CONF_PATH") is not None:
@@ -90,7 +100,7 @@ class VersionOptions(optparse.OptionGroup):
"""Command line option for printing Samba version."""
def __init__(self, parser):
optparse.OptionGroup.__init__(self, parser, "Version Options")
- self.add_option("--version", action="callback",
+ self.add_option("-V", "--version", action="callback",
callback=self._display_version,
help="Display version number")
@@ -100,8 +110,21 @@ class VersionOptions(optparse.OptionGroup):
sys.exit(0)
+def parse_kerberos_arg(arg, opt_str):
+ if arg.lower() in ["yes", 'true', '1']:
+ return MUST_USE_KERBEROS
+ elif arg.lower() in ["no", 'false', '0']:
+ return DONT_USE_KERBEROS
+ elif arg.lower() in ["auto"]:
+ return AUTO_USE_KERBEROS
+ else:
+ raise optparse.OptionValueError("invalid %s option value: %s" %
+ (opt_str, arg))
+
+
class CredentialsOptions(optparse.OptionGroup):
"""Command line options for specifying credentials."""
+
def __init__(self, parser):
self.no_pass = True
self.ipaddress = None
@@ -124,7 +147,8 @@ class CredentialsOptions(optparse.OptionGroup):
help="Use Kerberos", callback=self._set_kerberos)
self.add_option("", "--ipaddress", metavar="IPADDRESS",
action="callback", type=str,
- help="IP address of server", callback=self._set_ipaddress)
+ help="IP address of server",
+ callback=self._set_ipaddress)
self.creds = Credentials()
def _parse_username(self, option, opt_str, arg, parser):
@@ -141,12 +165,7 @@ class CredentialsOptions(optparse.OptionGroup):
self.ipaddress = arg
def _set_kerberos(self, option, opt_str, arg, parser):
- if arg.lower() in ["yes", 'true', '1']:
- self.creds.set_kerberos_state(MUST_USE_KERBEROS)
- elif arg.lower() in ["no", 'false', '0']:
- self.creds.set_kerberos_state(DONT_USE_KERBEROS)
- else:
- raise optparse.BadOptionErr("invalid kerberos option: %s" % arg)
+ self.creds.set_kerberos_state(parse_kerberos_arg(arg, opt_str))
def _set_simple_bind_dn(self, option, opt_str, arg, parser):
self.creds.set_bind_dn(arg)
@@ -171,8 +190,10 @@ class CredentialsOptions(optparse.OptionGroup):
return self.creds
+
class CredentialsOptionsDouble(CredentialsOptions):
"""Command line options for specifying credentials of two servers."""
+
def __init__(self, parser):
CredentialsOptions.__init__(self, parser)
self.no_pass2 = True
@@ -180,13 +201,16 @@ class CredentialsOptionsDouble(CredentialsOptions):
callback=self._set_simple_bind_dn2, type=str,
help="DN to use for a simple bind")
self.add_option("--password2", metavar="PASSWORD2", action="callback",
- help="Password", type=str, callback=self._set_password2)
+ help="Password", type=str,
+ callback=self._set_password2)
self.add_option("--username2", metavar="USERNAME2",
action="callback", type=str,
- help="Username for second server", callback=self._parse_username2)
+ help="Username for second server",
+ callback=self._parse_username2)
self.add_option("--workgroup2", metavar="WORKGROUP2",
action="callback", type=str,
- help="Workgroup for second server", callback=self._parse_workgroup2)
+ help="Workgroup for second server",
+ callback=self._parse_workgroup2)
self.add_option("--no-pass2", action="store_true",
help="Don't ask for a password for the second server")
self.add_option("--kerberos2", metavar="KERBEROS2",
@@ -205,10 +229,7 @@ class CredentialsOptionsDouble(CredentialsOptions):
self.no_pass2 = False
def _set_kerberos2(self, option, opt_str, arg, parser):
- if bool(arg) or arg.lower() == "yes":
- self.creds2.set_kerberos_state(MUST_USE_KERBEROS)
- else:
- self.creds2.set_kerberos_state(DONT_USE_KERBEROS)
+ self.creds2.set_kerberos_state(parse_kerberos_arg(arg, opt_str))
def _set_simple_bind_dn2(self, option, opt_str, arg, parser):
self.creds2.set_bind_dn(arg)
@@ -223,7 +244,7 @@ class CredentialsOptionsDouble(CredentialsOptions):
if guess:
self.creds2.guess(lp)
elif not self.creds2.get_username():
- self.creds2.set_anonymous()
+ self.creds2.set_anonymous()
if self.no_pass2:
self.creds2.set_cmdline_callbacks()
diff --git a/source4/scripting/python/samba/hostconfig.py b/source4/scripting/python/samba/hostconfig.py
index 3e6dc6b1dd..a66fbc2313 100644
--- a/source4/scripting/python/samba/hostconfig.py
+++ b/source4/scripting/python/samba/hostconfig.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
#
@@ -37,7 +35,7 @@ class Hostconfig(object):
:param session_info: Session info to use
:param credentials: Credentials to access the SamDB with
"""
- return SamDB(url=self.lp.get("sam database"),
+ return SamDB(url=self.lp.samdb_url(),
session_info=session_info, credentials=credentials,
lp=self.lp)
diff --git a/source4/scripting/python/samba/idmap.py b/source4/scripting/python/samba/idmap.py
index 93fca46edd..0cb729fbc2 100644
--- a/source4/scripting/python/samba/idmap.py
+++ b/source4/scripting/python/samba/idmap.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) 2008 Kai Blin <kai@samba.org>
#
@@ -35,13 +33,13 @@ class IDmapDB(samba.Ldb):
def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
credentials=None, flags=0, options=None):
- """Opens the IDMap Database
+ """Opens the IDMap Database.
+
For parameter meanings see the super class (samba.Ldb)
"""
-
self.lp = lp
if url is None:
- url = lp.get("idmap database")
+ url = lp.private_path("idmap.ldb")
super(IDmapDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
session_info=session_info, credentials=credentials, flags=flags,
@@ -51,13 +49,12 @@ class IDmapDB(samba.Ldb):
super(IDmapDB, self).connect(url=self.lp.private_path(url), flags=flags,
options=options)
-
def increment_xid(self):
"""Increment xidNumber, if not present it create and assign it to the lowerBound
:return xid can that be used for SID/unixid mapping
"""
- res = self.search(expression="dn=CN=CONFIG", base="",
+ res = self.search(expression="distinguishedName=CN=CONFIG", base="",
scope=ldb.SCOPE_SUBTREE)
id = res[0].get("xidNumber")
flag = ldb.FLAG_MOD_REPLACE
@@ -99,5 +96,3 @@ cn: %s
""" % (sid, unixid, sid, type_string, sid)
self.add(self.parse_ldif(mod).next()[1])
-
-
diff --git a/source4/scripting/python/samba/join.py b/source4/scripting/python/samba/join.py
index 401f262154..c55c22cad5 100644
--- a/source4/scripting/python/samba/join.py
+++ b/source4/scripting/python/samba/join.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# python join code
# Copyright Andrew Tridgell 2010
# Copyright Andrew Bartlett 2010
@@ -23,30 +21,47 @@
from samba.auth import system_session
from samba.samdb import SamDB
from samba import gensec, Ldb, drs_utils
-import ldb, samba, sys, os, uuid
+import ldb, samba, sys, uuid
from samba.ndr import ndr_pack
-from samba.dcerpc import security, drsuapi, misc, nbt
+from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs
from samba.credentials import Credentials, DONT_USE_KERBEROS
-from samba.provision import secretsdb_self_join, provision, FILL_DRS
+from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
from samba.schema import Schema
from samba.net import Net
+from samba.provision.sambadns import setup_bind9_dns
import logging
import talloc
+import random
+import time
# this makes debugging easier
talloc.enable_null_tracking()
+class DCJoinException(Exception):
+
+ def __init__(self, msg):
+ super(DCJoinException, self).__init__("Can't join, error: %s" % msg)
+
class dc_join(object):
- '''perform a DC join'''
+ """Perform a DC join."""
def __init__(ctx, server=None, creds=None, lp=None, site=None,
- netbios_name=None, targetdir=None, domain=None):
+ netbios_name=None, targetdir=None, domain=None,
+ machinepass=None, use_ntvfs=False, dns_backend=None,
+ promote_existing=False):
ctx.creds = creds
ctx.lp = lp
ctx.site = site
ctx.netbios_name = netbios_name
ctx.targetdir = targetdir
+ ctx.use_ntvfs = use_ntvfs
+
+ ctx.promote_existing = promote_existing
+ ctx.promote_from_dn = None
+
+ ctx.nc_list = []
+ ctx.full_nc_list = []
ctx.creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
ctx.net = Net(creds=ctx.creds, lp=ctx.lp)
@@ -62,6 +77,12 @@ class dc_join(object):
session_info=system_session(),
credentials=ctx.creds, lp=ctx.lp)
+ try:
+ ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
+ except ldb.LdbError, (enum, estr):
+ raise DCJoinException(estr)
+
+
ctx.myname = netbios_name
ctx.samname = "%s$" % ctx.myname
ctx.base_dn = str(ctx.samdb.get_default_basedn())
@@ -70,15 +91,17 @@ class dc_join(object):
ctx.config_dn = str(ctx.samdb.get_config_basedn())
ctx.domsid = ctx.samdb.get_domain_sid()
ctx.domain_name = ctx.get_domain_name()
+ ctx.forest_domain_name = ctx.get_forest_domain_name()
+ ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
- lp.set("workgroup", ctx.domain_name)
- print("workgroup is %s" % ctx.domain_name)
-
- ctx.dc_ntds_dn = ctx.get_dsServiceName()
+ ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
ctx.dc_dnsHostName = ctx.get_dnsHostName()
ctx.behavior_version = ctx.get_behavior_version()
- ctx.acct_pass = samba.generate_random_password(32, 40)
+ if machinepass is not None:
+ ctx.acct_pass = machinepass
+ else:
+ ctx.acct_pass = samba.generate_random_password(32, 40)
# work out the DNs of all the objects we will be adding
ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
@@ -89,14 +112,27 @@ class dc_join(object):
else:
ctx.topology_dn = None
- ctx.dnsdomain = ldb.Dn(ctx.samdb, ctx.base_dn).canonical_str().split('/')[0]
-
- ctx.realm = ctx.dnsdomain
- lp.set("realm", ctx.realm)
+ ctx.dnsdomain = ctx.samdb.domain_dns_name()
+ ctx.dnsforest = ctx.samdb.forest_dns_name()
+ ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
+ ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.base_dn
+
+ res_domaindns = ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL,
+ attrs=[],
+ base=ctx.samdb.get_partitions_dn(),
+ expression="(&(objectClass=crossRef)(ncName=%s))" % ctx.domaindns_zone)
+ if dns_backend is None:
+ ctx.dns_backend = "NONE"
+ else:
+ if len(res_domaindns) == 0:
+ ctx.dns_backend = "NONE"
+ print "NO DNS zone information found in source domain, not replicating DNS"
+ else:
+ ctx.dns_backend = dns_backend
- print("realm is %s" % ctx.realm)
+ ctx.dnshostname = "%s.%s" % (ctx.myname, ctx.dnsdomain)
- ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
+ ctx.realm = ctx.dnsdomain
ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
@@ -104,7 +140,7 @@ class dc_join(object):
ctx.SPNs = [ "HOST/%s" % ctx.myname,
"HOST/%s" % ctx.dnshostname,
- "GC/%s/%s" % (ctx.dnshostname, ctx.dnsdomain) ]
+ "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
# these elements are optional
ctx.never_reveal_sid = None
@@ -114,7 +150,7 @@ class dc_join(object):
ctx.krbtgt_dn = None
ctx.drsuapi = None
ctx.managedby = None
-
+ ctx.subdomain = False
def del_noerror(ctx, dn, recursive=False):
if recursive:
@@ -131,15 +167,18 @@ class dc_join(object):
pass
def cleanup_old_join(ctx):
- '''remove any DNs from a previous join'''
+ """Remove any DNs from a previous join."""
try:
# find the krbtgt link
- print("checking samaccountname")
- res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
- expression='samAccountName=%s' % ctx.samname,
- attrs=["msDS-krbTgtLink"])
- if res:
- ctx.del_noerror(res[0].dn, recursive=True)
+ print("checking sAMAccountName")
+ if ctx.subdomain:
+ res = None
+ else:
+ res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
+ expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
+ attrs=["msDS-krbTgtLink"])
+ if res:
+ ctx.del_noerror(res[0].dn, recursive=True)
if ctx.connection_dn is not None:
ctx.del_noerror(ctx.connection_dn)
if ctx.krbtgt_dn is not None:
@@ -148,16 +187,61 @@ class dc_join(object):
ctx.del_noerror(ctx.server_dn, recursive=True)
if ctx.topology_dn:
ctx.del_noerror(ctx.topology_dn)
+ if ctx.partition_dn:
+ ctx.del_noerror(ctx.partition_dn)
if res:
ctx.new_krbtgt_dn = res[0]["msDS-Krbtgtlink"][0]
ctx.del_noerror(ctx.new_krbtgt_dn)
+
+ if ctx.subdomain:
+ binding_options = "sign"
+ lsaconn = lsa.lsarpc("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
+ ctx.lp, ctx.creds)
+
+ objectAttr = lsa.ObjectAttribute()
+ objectAttr.sec_qos = lsa.QosInfo()
+
+ pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
+ objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
+
+ name = lsa.String()
+ name.string = ctx.realm
+ info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
+
+ lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
+
+ name = lsa.String()
+ name.string = ctx.forest_domain_name
+ info = lsaconn.QueryTrustedDomainInfoByName(pol_handle, name, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
+
+ lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
+
except Exception:
pass
+ def promote_possible(ctx):
+ """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
+ if ctx.subdomain:
+ # This shouldn't happen
+ raise Exception("Can not promote into a subdomain")
+
+ res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
+ expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
+ attrs=["msDS-krbTgtLink", "userAccountControl", "serverReferenceBL", "rIDSetReferences"])
+ if len(res) == 0:
+ raise Exception("Could not find domain member account '%s' to promote to a DC, use 'samba-tool domain join' instead'" % ctx.samname)
+ if "msDS-krbTgtLink" in res[0] or "serverReferenceBL" in res[0] or "rIDSetReferences" in res[0]:
+ raise Exception("Account '%s' appears to be an active DC, use 'samba-tool domain join' if you must re-create this account" % ctx.samname)
+ if (int(res[0]["userAccountControl"][0]) & (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_SERVER_TRUST_ACCOUNT) == 0):
+ raise Exception("Account %s is not a domain member or a bare NT4 BDC, use 'samba-tool domain join' instead'" % ctx.samname)
+
+ ctx.promote_from_dn = res[0].dn
+
+
def find_dc(ctx, domain):
- '''find a writeable DC for the given domain'''
+ """find a writeable DC for the given domain"""
try:
- ctx.cldap_ret = ctx.net.finddc(domain, nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
+ ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
except Exception:
raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
@@ -165,10 +249,6 @@ class dc_join(object):
return ctx.cldap_ret.pdc_dns_name
- def get_dsServiceName(ctx):
- res = ctx.samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
- return res[0]["dsServiceName"][0]
-
def get_behavior_version(ctx):
res = ctx.samdb.search(base=ctx.base_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
if "msDS-Behavior-Version" in res[0]:
@@ -187,6 +267,30 @@ class dc_join(object):
expression='ncName=%s' % ctx.samdb.get_default_basedn())
return res[0]["nETBIOSName"][0]
+ def get_forest_domain_name(ctx):
+ '''get netbios name of the domain from the partitions record'''
+ partitions_dn = ctx.samdb.get_partitions_dn()
+ res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["nETBIOSName"],
+ expression='ncName=%s' % ctx.samdb.get_root_basedn())
+ return res[0]["nETBIOSName"][0]
+
+ def get_parent_partition_dn(ctx):
+ '''get the parent domain partition DN from parent DNS name'''
+ res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
+ expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
+ (ctx.parent_dnsdomain, ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
+ return str(res[0].dn)
+
+ def get_naming_master(ctx):
+ '''get the parent domain partition DN from parent DNS name'''
+ res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
+ scope=ldb.SCOPE_BASE, controls=["extended_dn:1:1"])
+ if not 'fSMORoleOwner' in res[0]:
+ raise DCJoinException("Can't find naming master on partition DN %s" % ctx.partition_dn)
+ master_guid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['fSMORoleOwner'][0]).get_extended_component('GUID')))
+ master_host = '%s._msdcs.%s' % (master_guid, ctx.dnsforest)
+ return master_host
+
def get_mysid(ctx):
'''get the SID of the connected user. Only works with w2k8 and later,
so only used for RODC join'''
@@ -234,9 +338,9 @@ class dc_join(object):
ctx.samdb.rename(ctx.krbtgt_dn, ctx.new_krbtgt_dn)
def drsuapi_connect(ctx):
- '''make a DRSUAPI connection to the server'''
+ '''make a DRSUAPI connection to the naming master'''
binding_options = "seal"
- if ctx.lp.get("log level") >= 5:
+ if int(ctx.lp.get("log level")) >= 4:
binding_options += ",print"
binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
@@ -258,84 +362,72 @@ class dc_join(object):
r.value_ctr = 1
- def DsAddEntry(ctx, rec):
+ def DsAddEntry(ctx, recs):
'''add a record via the DRSUAPI DsAddEntry call'''
if ctx.drsuapi is None:
ctx.drsuapi_connect()
if ctx.tmp_samdb is None:
ctx.create_tmp_samdb()
- id = drsuapi.DsReplicaObjectIdentifier()
- id.dn = rec['dn']
-
- attrs = []
- for a in rec:
- if a == 'dn':
- continue
- if not isinstance(rec[a], list):
- v = [rec[a]]
- else:
- v = rec[a]
- rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
- attrs.append(rattr)
-
- attribute_ctr = drsuapi.DsReplicaAttributeCtr()
- attribute_ctr.num_attributes = len(attrs)
- attribute_ctr.attributes = attrs
-
- object = drsuapi.DsReplicaObject()
- object.identifier = id
- object.attribute_ctr = attribute_ctr
-
- first_object = drsuapi.DsReplicaObjectListItem()
- first_object.object = object
+ objects = []
+ for rec in recs:
+ id = drsuapi.DsReplicaObjectIdentifier()
+ id.dn = rec['dn']
+
+ attrs = []
+ for a in rec:
+ if a == 'dn':
+ continue
+ if not isinstance(rec[a], list):
+ v = [rec[a]]
+ else:
+ v = rec[a]
+ rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
+ attrs.append(rattr)
+
+ attribute_ctr = drsuapi.DsReplicaAttributeCtr()
+ attribute_ctr.num_attributes = len(attrs)
+ attribute_ctr.attributes = attrs
+
+ object = drsuapi.DsReplicaObject()
+ object.identifier = id
+ object.attribute_ctr = attribute_ctr
+
+ list_object = drsuapi.DsReplicaObjectListItem()
+ list_object.object = object
+ objects.append(list_object)
req2 = drsuapi.DsAddEntryRequest2()
- req2.first_object = first_object
+ req2.first_object = objects[0]
+ prev = req2.first_object
+ for o in objects[1:]:
+ prev.next_object = o
+ prev = o
(level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
- if ctr.err_ver != 1:
- raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
- if ctr.err_data.status != (0, 'WERR_OK'):
- print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status,
- ctr.err_data.info.extended_err))
- raise RuntimeError("DsAddEntry failed")
-
- def join_add_objects(ctx):
- '''add the various objects needed for the join'''
- print "Adding %s" % ctx.acct_dn
- rec = {
- "dn" : ctx.acct_dn,
- "objectClass": "computer",
- "displayname": ctx.samname,
- "samaccountname" : ctx.samname,
- "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
- "dnshostname" : ctx.dnshostname}
- if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
- rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
- if ctx.managedby:
- rec["managedby"] = ctx.managedby
- if ctx.never_reveal_sid:
- rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
- if ctx.reveal_sid:
- rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
- ctx.samdb.add(rec)
+ if level == 2:
+ if ctr.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
+ print("DsAddEntry failed with dir_err %u" % ctr.dir_err)
+ raise RuntimeError("DsAddEntry failed")
+ if ctr.extended_err != (0, 'WERR_OK'):
+ print("DsAddEntry failed with status %s info %s" % (ctr.extended_err))
+ raise RuntimeError("DsAddEntry failed")
+ if level == 3:
+ if ctr.err_ver != 1:
+ raise RuntimeError("expected err_ver 1, got %u" % ctr.err_ver)
+ if ctr.err_data.status != (0, 'WERR_OK'):
+ print("DsAddEntry failed with status %s info %s" % (ctr.err_data.status,
+ ctr.err_data.info.extended_err))
+ raise RuntimeError("DsAddEntry failed")
+ if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
+ print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
+ raise RuntimeError("DsAddEntry failed")
+
+ return ctr.objects
+
+ def join_add_ntdsdsa(ctx):
+ '''add the ntdsdsa object'''
- if ctx.krbtgt_dn:
- ctx.add_krbtgt_account()
-
- print "Adding %s" % ctx.server_dn
- rec = {
- "dn": ctx.server_dn,
- "objectclass" : "server",
- "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
- samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
- samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
- "serverReference" : ctx.acct_dn,
- "dnsHostName" : ctx.dnshostname}
- ctx.samdb.add(rec)
-
- # FIXME: the partition (NC) assignment has to be made dynamic
print "Adding %s" % ctx.ntds_dn
rec = {
"dn" : ctx.ntds_dn,
@@ -343,30 +435,95 @@ class dc_join(object):
"systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
"dMDLocation" : ctx.schema_dn}
+ nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
- rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
+ rec["msDS-Behavior-Version"] = str(samba.dsdb.DS_DOMAIN_FUNCTION_2008_R2)
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
rec["msDS-HasDomainNCs"] = ctx.base_dn
if ctx.RODC:
rec["objectCategory"] = "CN=NTDS-DSA-RO,%s" % ctx.schema_dn
- rec["msDS-HasFullReplicaNCs"] = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+ rec["msDS-HasFullReplicaNCs"] = ctx.nc_list
rec["options"] = "37"
ctx.samdb.add(rec, ["rodc_join:1:1"])
else:
rec["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
- rec["HasMasterNCs"] = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+ rec["HasMasterNCs"] = nc_list
if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
- rec["msDS-HasMasterNCs"] = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+ rec["msDS-HasMasterNCs"] = ctx.nc_list
rec["options"] = "1"
- rec["invocationId"] = ndr_pack(misc.GUID(str(uuid.uuid4())))
- ctx.DsAddEntry(rec)
+ rec["invocationId"] = ndr_pack(ctx.invocation_id)
+ ctx.DsAddEntry([rec])
# find the GUID of our NTDS DN
res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
+ def join_add_objects(ctx):
+ '''add the various objects needed for the join'''
+ if ctx.acct_dn:
+ print "Adding %s" % ctx.acct_dn
+ rec = {
+ "dn" : ctx.acct_dn,
+ "objectClass": "computer",
+ "displayname": ctx.samname,
+ "samaccountname" : ctx.samname,
+ "userAccountControl" : str(ctx.userAccountControl | samba.dsdb.UF_ACCOUNTDISABLE),
+ "dnshostname" : ctx.dnshostname}
+ if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2008:
+ rec['msDS-SupportedEncryptionTypes'] = str(samba.dsdb.ENC_ALL_TYPES)
+ elif ctx.promote_existing:
+ rec['msDS-SupportedEncryptionTypes'] = []
+ if ctx.managedby:
+ rec["managedby"] = ctx.managedby
+ elif ctx.promote_existing:
+ rec["managedby"] = []
+
+ if ctx.never_reveal_sid:
+ rec["msDS-NeverRevealGroup"] = ctx.never_reveal_sid
+ elif ctx.promote_existing:
+ rec["msDS-NeverRevealGroup"] = []
+
+ if ctx.reveal_sid:
+ rec["msDS-RevealOnDemandGroup"] = ctx.reveal_sid
+ elif ctx.promote_existing:
+ rec["msDS-RevealOnDemandGroup"] = []
+
+ if ctx.promote_existing:
+ if ctx.promote_from_dn != ctx.acct_dn:
+ ctx.samdb.rename(ctx.promote_from_dn, ctx.acct_dn)
+ ctx.samdb.modify(ldb.Message.from_dict(ctx.samdb, rec, ldb.FLAG_MOD_REPLACE))
+ else:
+ ctx.samdb.add(rec)
+
+ if ctx.krbtgt_dn:
+ ctx.add_krbtgt_account()
+
+ print "Adding %s" % ctx.server_dn
+ rec = {
+ "dn": ctx.server_dn,
+ "objectclass" : "server",
+ # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
+ "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
+ samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
+ samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
+ # windows seems to add the dnsHostName later
+ "dnsHostName" : ctx.dnshostname}
+
+ if ctx.acct_dn:
+ rec["serverReference"] = ctx.acct_dn
+
+ ctx.samdb.add(rec)
+
+ if ctx.subdomain:
+ # the rest is done after replication
+ ctx.ntds_guid = None
+ return
+
+ ctx.join_add_ntdsdsa()
+
if ctx.connection_dn is not None:
print "Adding %s" % ctx.connection_dn
rec = {
@@ -377,43 +534,112 @@ class dc_join(object):
"fromServer" : ctx.dc_ntds_dn}
ctx.samdb.add(rec)
- if ctx.topology_dn:
- print "Adding %s" % ctx.topology_dn
- rec = {
- "dn" : ctx.topology_dn,
- "objectclass" : "msDFSR-Member",
- "msDFSR-ComputerReference" : ctx.acct_dn,
- "serverReference" : ctx.ntds_dn}
- ctx.samdb.add(rec)
+ if ctx.acct_dn:
+ print "Adding SPNs to %s" % ctx.acct_dn
+ m = ldb.Message()
+ m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
+ for i in range(len(ctx.SPNs)):
+ ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
+ m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
+ ldb.FLAG_MOD_REPLACE,
+ "servicePrincipalName")
+ ctx.samdb.modify(m)
+
+ # The account password set operation should normally be done over
+ # LDAP. Windows 2000 DCs however allow this only with SSL
+ # connections which are hard to set up and otherwise refuse with
+ # ERR_UNWILLING_TO_PERFORM. In this case we fall back to libnet
+ # over SAMR.
+ print "Setting account password for %s" % ctx.samname
+ try:
+ ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))"
+ % ldb.binary_encode(ctx.samname),
+ ctx.acct_pass,
+ force_change_at_next_login=False,
+ username=ctx.samname)
+ except ldb.LdbError, (num, _):
+ if num != ldb.ERR_UNWILLING_TO_PERFORM:
+ pass
+ ctx.net.set_password(account_name=ctx.samname,
+ domain_name=ctx.domain_name,
+ newpassword=ctx.acct_pass)
+
+ res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
+ attrs=["msDS-KeyVersionNumber"])
+ if "msDS-KeyVersionNumber" in res[0]:
+ ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
+ else:
+ ctx.key_version_number = None
+
+ print("Enabling account")
+ m = ldb.Message()
+ m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
+ m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
+ ldb.FLAG_MOD_REPLACE,
+ "userAccountControl")
+ ctx.samdb.modify(m)
+
+ def join_add_objects2(ctx):
+ """add the various objects needed for the join, for subdomains post replication"""
+
+ print "Adding %s" % ctx.partition_dn
+ # NOTE: windows sends a ntSecurityDescriptor here, we
+ # let it default
+ rec = {
+ "dn" : ctx.partition_dn,
+ "objectclass" : "crossRef",
+ "objectCategory" : "CN=Cross-Ref,%s" % ctx.schema_dn,
+ "nCName" : ctx.base_dn,
+ "nETBIOSName" : ctx.domain_name,
+ "dnsRoot": ctx.dnsdomain,
+ "trustParent" : ctx.parent_partition_dn,
+ "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN)}
+ if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+ rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
- print "Adding SPNs to %s" % ctx.acct_dn
- m = ldb.Message()
- m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
- for i in range(len(ctx.SPNs)):
- ctx.SPNs[i] = ctx.SPNs[i].replace("$NTDSGUID", str(ctx.ntds_guid))
- m["servicePrincipalName"] = ldb.MessageElement(ctx.SPNs,
- ldb.FLAG_MOD_ADD,
- "servicePrincipalName")
- ctx.samdb.modify(m)
+ rec2 = {
+ "dn" : ctx.ntds_dn,
+ "objectclass" : "nTDSDSA",
+ "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
+ "dMDLocation" : ctx.schema_dn}
- print "Setting account password for %s" % ctx.samname
- ctx.samdb.setpassword("(&(objectClass=user)(sAMAccountName=%s))" % ctx.samname,
- ctx.acct_pass,
- force_change_at_next_login=False,
- username=ctx.samname)
- res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-keyVersionNumber"])
- ctx.key_version_number = int(res[0]["msDS-keyVersionNumber"][0])
+ nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
- print("Enabling account")
- m = ldb.Message()
- m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
- m["userAccountControl"] = ldb.MessageElement(str(ctx.userAccountControl),
- ldb.FLAG_MOD_REPLACE,
- "userAccountControl")
- ctx.samdb.modify(m)
+ if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+ rec2["msDS-Behavior-Version"] = str(ctx.behavior_version)
+
+ if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+ rec2["msDS-HasDomainNCs"] = ctx.base_dn
+
+ rec2["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
+ rec2["HasMasterNCs"] = nc_list
+ if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+ rec2["msDS-HasMasterNCs"] = ctx.nc_list
+ rec2["options"] = "1"
+ rec2["invocationId"] = ndr_pack(ctx.invocation_id)
+
+ objects = ctx.DsAddEntry([rec, rec2])
+ if len(objects) != 2:
+ raise DCJoinException("Expected 2 objects from DsAddEntry")
+
+ ctx.ntds_guid = objects[1].guid
+
+ print("Replicating partition DN")
+ ctx.repl.replicate(ctx.partition_dn,
+ misc.GUID("00000000-0000-0000-0000-000000000000"),
+ ctx.ntds_guid,
+ exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
+ replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
+
+ print("Replicating NTDS DN")
+ ctx.repl.replicate(ctx.ntds_dn,
+ misc.GUID("00000000-0000-0000-0000-000000000000"),
+ ctx.ntds_guid,
+ exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
+ replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
def join_provision(ctx):
- '''provision the local SAM'''
+ """Provision the local SAM."""
print "Calling bare provision"
@@ -421,29 +647,68 @@ class dc_join(object):
logger.addHandler(logging.StreamHandler(sys.stdout))
smbconf = ctx.lp.configfile
- presult = provision(logger, system_session(), None,
- smbconf=smbconf, targetdir=ctx.targetdir, samdb_fill=FILL_DRS,
- realm=ctx.realm, rootdn=ctx.root_dn, domaindn=ctx.base_dn,
- schemadn=ctx.schema_dn,
- configdn=ctx.config_dn,
- serverdn=ctx.server_dn, domain=ctx.domain_name,
- hostname=ctx.myname, domainsid=ctx.domsid,
- machinepass=ctx.acct_pass, serverrole="domain controller",
- sitename=ctx.site, lp=ctx.lp)
+ presult = provision(logger, system_session(), None, smbconf=smbconf,
+ targetdir=ctx.targetdir, samdb_fill=FILL_DRS, realm=ctx.realm,
+ rootdn=ctx.root_dn, domaindn=ctx.base_dn,
+ schemadn=ctx.schema_dn, configdn=ctx.config_dn,
+ serverdn=ctx.server_dn, domain=ctx.domain_name,
+ hostname=ctx.myname, domainsid=ctx.domsid,
+ machinepass=ctx.acct_pass, serverrole="domain controller",
+ sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
+ use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend)
print "Provision OK for domain DN %s" % presult.domaindn
ctx.local_samdb = presult.samdb
ctx.lp = presult.lp
ctx.paths = presult.paths
+ ctx.names = presult.names
+
+ def join_provision_own_domain(ctx):
+ """Provision the local SAM."""
+ # we now operate exclusively on the local database, which
+ # we need to reopen in order to get the newly created schema
+ print("Reconnecting to local samdb")
+ ctx.samdb = SamDB(url=ctx.local_samdb.url,
+ session_info=system_session(),
+ lp=ctx.local_samdb.lp,
+ global_schema=False)
+ ctx.samdb.set_invocation_id(str(ctx.invocation_id))
+ ctx.local_samdb = ctx.samdb
+
+ print("Finding domain GUID from ncName")
+ res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
+ controls=["extended_dn:1:1"])
+ domguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
+ print("Got domain GUID %s" % domguid)
+
+ print("Calling own domain provision")
+
+ logger = logging.getLogger("provision")
+ logger.addHandler(logging.StreamHandler(sys.stdout))
+
+ secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
+
+ presult = provision_fill(ctx.local_samdb, secrets_ldb,
+ logger, ctx.names, ctx.paths, domainsid=security.dom_sid(ctx.domsid),
+ domainguid=domguid,
+ targetdir=ctx.targetdir, samdb_fill=FILL_SUBDOMAIN,
+ machinepass=ctx.acct_pass, serverrole="domain controller",
+ lp=ctx.lp, hostip=ctx.names.hostip, hostip6=ctx.names.hostip6,
+ dns_backend=ctx.dns_backend)
+ print("Provision OK for domain %s" % ctx.names.dnsdomain)
def join_replicate(ctx):
- '''replicate the SAM'''
+ """Replicate the SAM."""
print "Starting replication"
ctx.local_samdb.transaction_start()
try:
source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
- destination_dsa_guid = ctx.ntds_guid
+ if ctx.ntds_guid is None:
+ print("Using DS_BIND_GUID_W2K3")
+ destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
+ else:
+ destination_dsa_guid = ctx.ntds_guid
if ctx.RODC:
repl_creds = Credentials()
@@ -455,7 +720,7 @@ class dc_join(object):
repl_creds = ctx.creds
binding_options = "seal"
- if ctx.lp.get("debug level") >= 5:
+ if int(ctx.lp.get("log level")) >= 5:
binding_options += ",print"
repl = drs_utils.drs_Replicate(
"ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
@@ -467,9 +732,37 @@ class dc_join(object):
repl.replicate(ctx.config_dn, source_dsa_invocation_id,
destination_dsa_guid, rodc=ctx.RODC,
replica_flags=ctx.replica_flags)
- repl.replicate(ctx.base_dn, source_dsa_invocation_id,
- destination_dsa_guid, rodc=ctx.RODC,
- replica_flags=ctx.replica_flags)
+ if not ctx.subdomain:
+ # Replicate first the critical object for the basedn
+ if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
+ print "Replicating critical objects from the base DN of the domain"
+ ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC
+ repl.replicate(ctx.base_dn, source_dsa_invocation_id,
+ destination_dsa_guid, rodc=ctx.RODC,
+ replica_flags=ctx.domain_replica_flags)
+ ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC
+ else:
+ ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_GET_ANC
+ repl.replicate(ctx.base_dn, source_dsa_invocation_id,
+ destination_dsa_guid, rodc=ctx.RODC,
+ replica_flags=ctx.domain_replica_flags)
+ print "Done with always replicated NC (base, config, schema)"
+
+ for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
+ if nc in ctx.nc_list:
+ print "Replicating %s" % (str(nc))
+ repl.replicate(nc, source_dsa_invocation_id,
+ destination_dsa_guid, rodc=ctx.RODC,
+ replica_flags=ctx.replica_flags)
+
+ if 'DC=ForestDnsZones,%s' % ctx.root_dn in ctx.nc_list:
+ repl.replicate('DC=ForestDnsZones,%s' % ctx.root_dn, source_dsa_invocation_id,
+ destination_dsa_guid, rodc=ctx.RODC,
+ replica_flags=ctx.replica_flags)
+ # FIXME At this point we should add an entry in the forestdns and domaindns NC
+ # (those under CN=Partions,DC=...)
+ # in order to indicate that we hold a replica for this NC
+
if ctx.RODC:
repl.replicate(ctx.acct_dn, source_dsa_invocation_id,
destination_dsa_guid,
@@ -477,6 +770,9 @@ class dc_join(object):
repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
destination_dsa_guid,
exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
+ ctx.repl = repl
+ ctx.source_dsa_invocation_id = source_dsa_invocation_id
+ ctx.destination_dsa_guid = destination_dsa_guid
print "Committing SAM database"
except:
@@ -485,15 +781,72 @@ class dc_join(object):
else:
ctx.local_samdb.transaction_commit()
+ def send_DsReplicaUpdateRefs(ctx, dn):
+ r = drsuapi.DsReplicaUpdateRefsRequest1()
+ r.naming_context = drsuapi.DsReplicaObjectIdentifier()
+ r.naming_context.dn = str(dn)
+ r.naming_context.guid = misc.GUID("00000000-0000-0000-0000-000000000000")
+ r.naming_context.sid = security.dom_sid("S-0-0")
+ r.dest_dsa_guid = ctx.ntds_guid
+ r.dest_dsa_dns_name = "%s._msdcs.%s" % (str(ctx.ntds_guid), ctx.dnsforest)
+ r.options = drsuapi.DRSUAPI_DRS_ADD_REF | drsuapi.DRSUAPI_DRS_DEL_REF
+ if not ctx.RODC:
+ r.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
+
+ if ctx.drsuapi:
+ ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
def join_finalise(ctx):
- '''finalise the join, mark us synchronised and setup secrets db'''
+ """Finalise the join, mark us synchronised and setup secrets db."""
+
+ logger = logging.getLogger("provision")
+ logger.addHandler(logging.StreamHandler(sys.stdout))
+
+ # FIXME we shouldn't do this in all cases
+ # If for some reasons we joined in another site than the one of
+ # DC we just replicated from then we don't need to send the updatereplicateref
+ # as replication between sites is time based and on the initiative of the
+ # requesting DC
+ print "Sending DsReplicateUpdateRefs for all the replicated partitions"
+ for nc in ctx.full_nc_list:
+ ctx.send_DsReplicaUpdateRefs(nc)
- print "Setting isSynchronized"
+ if ctx.RODC:
+ print "Setting RODC invocationId"
+ ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
+ ctx.local_samdb.set_opaque_integer("domainFunctionality",
+ ctx.behavior_version)
+ m = ldb.Message()
+ m.dn = ldb.Dn(ctx.local_samdb, "%s" % ctx.ntds_dn)
+ m["invocationId"] = ldb.MessageElement(ndr_pack(ctx.invocation_id),
+ ldb.FLAG_MOD_REPLACE,
+ "invocationId")
+ ctx.local_samdb.modify(m)
+
+ # Note: as RODC the invocationId is only stored
+ # on the RODC itself, the other DCs never see it.
+ #
+ # Thats is why we fix up the replPropertyMetaData stamp
+ # for the 'invocationId' attribute, we need to change
+ # the 'version' to '0', this is what windows 2008r2 does as RODC
+ #
+ # This means if the object on a RWDC ever gets a invocationId
+ # attribute, it will have version '1' (or higher), which will
+ # will overwrite the RODC local value.
+ ctx.local_samdb.set_attribute_replmetadata_version(m.dn,
+ "invocationId",
+ 0)
+
+ print "Setting isSynchronized and dsServiceName"
m = ldb.Message()
- m.dn = ldb.Dn(ctx.samdb, '@ROOTDSE')
+ m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
- ctx.samdb.modify(m)
+ m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(ctx.ntds_guid),
+ ldb.FLAG_MOD_REPLACE, "dsServiceName")
+ ctx.local_samdb.modify(m)
+
+ if ctx.subdomain:
+ return
secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
@@ -507,33 +860,188 @@ class dc_join(object):
secure_channel_type=ctx.secure_channel_type,
key_version_number=ctx.key_version_number)
+ if ctx.dns_backend.startswith("BIND9_"):
+ dnspass = samba.generate_random_password(128, 255)
+
+ setup_bind9_dns(ctx.local_samdb, secrets_ldb, security.dom_sid(ctx.domsid),
+ ctx.names, ctx.paths, ctx.lp, logger,
+ dns_backend=ctx.dns_backend,
+ dnspass=dnspass, os_level=ctx.behavior_version,
+ targetdir=ctx.targetdir)
+
+ def join_setup_trusts(ctx):
+ """provision the local SAM."""
+
+ def arcfour_encrypt(key, data):
+ from Crypto.Cipher import ARC4
+ c = ARC4.new(key)
+ return c.encrypt(data)
+
+ def string_to_array(string):
+ blob = [0] * len(string)
+
+ for i in range(len(string)):
+ blob[i] = ord(string[i])
+
+ return blob
+
+ print "Setup domain trusts with server %s" % ctx.server
+ binding_options = "" # why doesn't signing work here? w2k8r2 claims no session key
+ lsaconn = lsa.lsarpc("ncacn_np:%s[%s]" % (ctx.server, binding_options),
+ ctx.lp, ctx.creds)
+
+ objectAttr = lsa.ObjectAttribute()
+ objectAttr.sec_qos = lsa.QosInfo()
+
+ pol_handle = lsaconn.OpenPolicy2(''.decode('utf-8'),
+ objectAttr, security.SEC_FLAG_MAXIMUM_ALLOWED)
+
+ info = lsa.TrustDomainInfoInfoEx()
+ info.domain_name.string = ctx.dnsdomain
+ info.netbios_name.string = ctx.domain_name
+ info.sid = security.dom_sid(ctx.domsid)
+ info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
+ info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
+ info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
+
+ try:
+ oldname = lsa.String()
+ oldname.string = ctx.dnsdomain
+ oldinfo = lsaconn.QueryTrustedDomainInfoByName(pol_handle, oldname,
+ lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
+ print("Removing old trust record for %s (SID %s)" % (ctx.dnsdomain, oldinfo.info_ex.sid))
+ lsaconn.DeleteTrustedDomain(pol_handle, oldinfo.info_ex.sid)
+ except RuntimeError:
+ pass
+
+ password_blob = string_to_array(ctx.trustdom_pass.encode('utf-16-le'))
+
+ clear_value = drsblobs.AuthInfoClear()
+ clear_value.size = len(password_blob)
+ clear_value.password = password_blob
+
+ clear_authentication_information = drsblobs.AuthenticationInformation()
+ clear_authentication_information.LastUpdateTime = samba.unix2nttime(int(time.time()))
+ clear_authentication_information.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
+ clear_authentication_information.AuthInfo = clear_value
+
+ authentication_information_array = drsblobs.AuthenticationInformationArray()
+ authentication_information_array.count = 1
+ authentication_information_array.array = [clear_authentication_information]
+
+ outgoing = drsblobs.trustAuthInOutBlob()
+ outgoing.count = 1
+ outgoing.current = authentication_information_array
+
+ trustpass = drsblobs.trustDomainPasswords()
+ confounder = [3] * 512
+
+ for i in range(512):
+ confounder[i] = random.randint(0, 255)
+
+ trustpass.confounder = confounder
+
+ trustpass.outgoing = outgoing
+ trustpass.incoming = outgoing
+
+ trustpass_blob = ndr_pack(trustpass)
+
+ encrypted_trustpass = arcfour_encrypt(lsaconn.session_key, trustpass_blob)
+
+ auth_blob = lsa.DATA_BUF2()
+ auth_blob.size = len(encrypted_trustpass)
+ auth_blob.data = string_to_array(encrypted_trustpass)
+
+ auth_info = lsa.TrustDomainInfoAuthInfoInternal()
+ auth_info.auth_blob = auth_blob
+
+ trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
+ info,
+ auth_info,
+ security.SEC_STD_DELETE)
+
+ rec = {
+ "dn" : "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
+ "objectclass" : "trustedDomain",
+ "trustType" : str(info.trust_type),
+ "trustAttributes" : str(info.trust_attributes),
+ "trustDirection" : str(info.trust_direction),
+ "flatname" : ctx.forest_domain_name,
+ "trustPartner" : ctx.dnsforest,
+ "trustAuthIncoming" : ndr_pack(outgoing),
+ "trustAuthOutgoing" : ndr_pack(outgoing)
+ }
+ ctx.local_samdb.add(rec)
+
+ rec = {
+ "dn" : "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
+ "objectclass" : "user",
+ "userAccountControl" : str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
+ "clearTextPassword" : ctx.trustdom_pass.encode('utf-16-le')
+ }
+ ctx.local_samdb.add(rec)
+
+
def do_join(ctx):
- ctx.cleanup_old_join()
+ # full_nc_list is the list of naming context (NC) for which we will
+ # send a updateRef command to the partner DC
+ ctx.nc_list = [ ctx.config_dn, ctx.schema_dn ]
+ ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+
+ if not ctx.subdomain:
+ ctx.nc_list += [ctx.base_dn]
+ if ctx.dns_backend != "NONE":
+ ctx.nc_list += [ctx.domaindns_zone]
+
+ if ctx.dns_backend != "NONE":
+ ctx.full_nc_list += ['DC=DomainDnsZones,%s' % ctx.base_dn]
+ ctx.full_nc_list += ['DC=ForestDnsZones,%s' % ctx.root_dn]
+ ctx.nc_list += ['DC=ForestDnsZones,%s' % ctx.root_dn]
+
+ if ctx.promote_existing:
+ ctx.promote_possible()
+ else:
+ ctx.cleanup_old_join()
+
try:
ctx.join_add_objects()
ctx.join_provision()
ctx.join_replicate()
+ if ctx.subdomain:
+ ctx.join_add_objects2()
+ ctx.join_provision_own_domain()
+ ctx.join_setup_trusts()
ctx.join_finalise()
- except Exception:
+ except:
print "Join failed - cleaning up"
ctx.cleanup_old_join()
raise
def join_RODC(server=None, creds=None, lp=None, site=None, netbios_name=None,
- targetdir=None, domain=None):
- """join as a RODC"""
+ targetdir=None, domain=None, domain_critical_only=False,
+ machinepass=None, use_ntvfs=False, dns_backend=None,
+ promote_existing=False):
+ """Join as a RODC."""
+
+ ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain,
+ machinepass, use_ntvfs, dns_backend, promote_existing)
- ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain)
+ lp.set("workgroup", ctx.domain_name)
+ print("workgroup is %s" % ctx.domain_name)
+
+ lp.set("realm", ctx.realm)
+ print("realm is %s" % ctx.realm)
ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)
# setup some defaults for accounts that should be replicated to this RODC
- ctx.never_reveal_sid = [ "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
- "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
- "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
- "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
- "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS ]
+ ctx.never_reveal_sid = [
+ "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
+ "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
+ "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
+ "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
+ "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)
mysid = ctx.get_mysid()
@@ -556,16 +1064,28 @@ def join_RODC(server=None, creds=None, lp=None, site=None, netbios_name=None,
drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING |
drsuapi.DRSUAPI_DRS_GET_ALL_GROUP_MEMBERSHIP)
- ctx.do_join()
+ ctx.domain_replica_flags = ctx.replica_flags
+ if domain_critical_only:
+ ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
+ ctx.do_join()
print "Joined domain %s (SID %s) as an RODC" % (ctx.domain_name, ctx.domsid)
def join_DC(server=None, creds=None, lp=None, site=None, netbios_name=None,
- targetdir=None, domain=None):
- """join as a DC"""
- ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain)
+ targetdir=None, domain=None, domain_critical_only=False,
+ machinepass=None, use_ntvfs=False, dns_backend=None,
+ promote_existing=False):
+ """Join as a DC."""
+ ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, domain,
+ machinepass, use_ntvfs, dns_backend, promote_existing)
+
+ lp.set("workgroup", ctx.domain_name)
+ print("workgroup is %s" % ctx.domain_name)
+
+ lp.set("realm", ctx.realm)
+ print("realm is %s" % ctx.realm)
ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
@@ -577,6 +1097,53 @@ def join_DC(server=None, creds=None, lp=None, site=None, netbios_name=None,
drsuapi.DRSUAPI_DRS_PER_SYNC |
drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
+ ctx.domain_replica_flags = ctx.replica_flags
+ if domain_critical_only:
+ ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
ctx.do_join()
print "Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid)
+
+def join_subdomain(server=None, creds=None, lp=None, site=None,
+ netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
+ netbios_domain=None, machinepass=None, use_ntvfs=False,
+ dns_backend=None):
+ """Join as a DC."""
+ ctx = dc_join(server, creds, lp, site, netbios_name, targetdir, parent_domain,
+ machinepass, use_ntvfs, dns_backend)
+ ctx.subdomain = True
+ ctx.parent_domain_name = ctx.domain_name
+ ctx.domain_name = netbios_domain
+ ctx.realm = dnsdomain
+ ctx.parent_dnsdomain = ctx.dnsdomain
+ ctx.parent_partition_dn = ctx.get_parent_partition_dn()
+ ctx.dnsdomain = dnsdomain
+ ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
+ ctx.naming_master = ctx.get_naming_master()
+ if ctx.naming_master != ctx.server:
+ print("Reconnecting to naming master %s" % ctx.naming_master)
+ ctx.server = ctx.naming_master
+ ctx.samdb = SamDB(url="ldap://%s" % ctx.server,
+ session_info=system_session(),
+ credentials=ctx.creds, lp=ctx.lp)
+
+ ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
+ ctx.domsid = str(security.random_sid())
+ ctx.acct_dn = None
+ ctx.dnshostname = "%s.%s" % (ctx.myname, ctx.dnsdomain)
+ ctx.trustdom_pass = samba.generate_random_password(128, 128)
+
+ ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
+
+ ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
+ ctx.secure_channel_type = misc.SEC_CHAN_BDC
+
+ ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP |
+ drsuapi.DRSUAPI_DRS_INIT_SYNC |
+ drsuapi.DRSUAPI_DRS_PER_SYNC |
+ drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
+ drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
+ ctx.domain_replica_flags = ctx.replica_flags
+
+ ctx.do_join()
+ print "Created domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid)
diff --git a/source4/scripting/python/samba/kcc_utils.py b/source4/scripting/python/samba/kcc_utils.py
new file mode 100644
index 0000000000..57c31876a6
--- /dev/null
+++ b/source4/scripting/python/samba/kcc_utils.py
@@ -0,0 +1,2182 @@
+# KCC topology utilities
+#
+# Copyright (C) Dave Craft 2011
+# Copyright (C) Jelmer Vernooij 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
+import uuid
+import time
+
+from samba import dsdb, unix2nttime
+from samba.dcerpc import (
+ drsblobs,
+ drsuapi,
+ misc,
+ )
+from samba.common import dsdb_Dn
+from samba.ndr import (ndr_unpack, ndr_pack)
+
+
+class NCType(object):
+ (unknown, schema, domain, config, application) = range(0, 5)
+
+
+class NamingContext(object):
+ """Base class for a naming context.
+
+ Holds the DN, GUID, SID (if available) and type of the DN.
+ Subclasses may inherit from this and specialize
+ """
+
+ def __init__(self, nc_dnstr):
+ """Instantiate a NamingContext
+
+ :param nc_dnstr: NC dn string
+ """
+ self.nc_dnstr = nc_dnstr
+ self.nc_guid = None
+ self.nc_sid = None
+ self.nc_type = NCType.unknown
+
+ def __str__(self):
+ '''Debug dump string output of class'''
+ text = "%s:" % self.__class__.__name__
+ text = text + "\n\tnc_dnstr=%s" % self.nc_dnstr
+ text = text + "\n\tnc_guid=%s" % str(self.nc_guid)
+
+ if self.nc_sid is None:
+ text = text + "\n\tnc_sid=<absent>"
+ else:
+ text = text + "\n\tnc_sid=<present>"
+
+ text = text + "\n\tnc_type=%s" % self.nc_type
+ return text
+
+ def load_nc(self, samdb):
+ attrs = [ "objectGUID",
+ "objectSid" ]
+ try:
+ res = samdb.search(base=self.nc_dnstr,
+ scope=ldb.SCOPE_BASE, attrs=attrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find naming context (%s)" %
+ (self.nc_dnstr, estr))
+ msg = res[0]
+ if "objectGUID" in msg:
+ self.nc_guid = misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["objectGUID"][0]))
+ if "objectSid" in msg:
+ self.nc_sid = msg["objectSid"][0]
+
+ assert self.nc_guid is not None
+
+ def is_schema(self):
+ '''Return True if NC is schema'''
+ assert self.nc_type != NCType.unknown
+ return self.nc_type == NCType.schema
+
+ def is_domain(self):
+ '''Return True if NC is domain'''
+ assert self.nc_type != NCType.unknown
+ return self.nc_type == NCType.domain
+
+ def is_application(self):
+ '''Return True if NC is application'''
+ assert self.nc_type != NCType.unknown
+ return self.nc_type == NCType.application
+
+ def is_config(self):
+ '''Return True if NC is config'''
+ assert self.nc_type != NCType.unknown
+ return self.nc_type == NCType.config
+
+ def identify_by_basedn(self, samdb):
+ """Given an NC object, identify what type is is thru
+ the samdb basedn strings and NC sid value
+ """
+ # Invoke loader to initialize guid and more
+ # importantly sid value (sid is used to identify
+ # domain NCs)
+ if self.nc_guid is None:
+ self.load_nc(samdb)
+
+ # We check against schema and config because they
+ # will be the same for all nTDSDSAs in the forest.
+ # That leaves the domain NCs which can be identified
+ # by sid and application NCs as the last identified
+ if self.nc_dnstr == str(samdb.get_schema_basedn()):
+ self.nc_type = NCType.schema
+ elif self.nc_dnstr == str(samdb.get_config_basedn()):
+ self.nc_type = NCType.config
+ elif self.nc_sid is not None:
+ self.nc_type = NCType.domain
+ else:
+ self.nc_type = NCType.application
+
+ def identify_by_dsa_attr(self, samdb, attr):
+ """Given an NC which has been discovered thru the
+ nTDSDSA database object, determine what type of NC
+ it is (i.e. schema, config, domain, application) via
+ the use of the schema attribute under which the NC
+ was found.
+
+ :param attr: attr of nTDSDSA object where NC DN appears
+ """
+ # If the NC is listed under msDS-HasDomainNCs then
+ # this can only be a domain NC and it is our default
+ # domain for this dsa
+ if attr == "msDS-HasDomainNCs":
+ self.nc_type = NCType.domain
+
+ # If the NC is listed under hasPartialReplicaNCs
+ # this is only a domain NC
+ elif attr == "hasPartialReplicaNCs":
+ self.nc_type = NCType.domain
+
+ # NCs listed under hasMasterNCs are either
+ # default domain, schema, or config. We
+ # utilize the identify_by_basedn() to
+ # identify those
+ elif attr == "hasMasterNCs":
+ self.identify_by_basedn(samdb)
+
+ # Still unknown (unlikely) but for completeness
+ # and for finally identifying application NCs
+ if self.nc_type == NCType.unknown:
+ self.identify_by_basedn(samdb)
+
+
+class NCReplica(NamingContext):
+ """Naming context replica that is relative to a specific DSA.
+
+ This is a more specific form of NamingContext class (inheriting from that
+ class) and it identifies unique attributes of the DSA's replica for a NC.
+ """
+
+ def __init__(self, dsa_dnstr, dsa_guid, nc_dnstr):
+ """Instantiate a Naming Context Replica
+
+ :param dsa_guid: GUID of DSA where replica appears
+ :param nc_dnstr: NC dn string
+ """
+ self.rep_dsa_dnstr = dsa_dnstr
+ self.rep_dsa_guid = dsa_guid
+ self.rep_default = False # replica for DSA's default domain
+ self.rep_partial = False
+ self.rep_ro = False
+ self.rep_instantiated_flags = 0
+
+ self.rep_fsmo_role_owner = None
+
+ # RepsFromTo tuples
+ self.rep_repsFrom = []
+
+ # The (is present) test is a combination of being
+ # enumerated in (hasMasterNCs or msDS-hasFullReplicaNCs or
+ # hasPartialReplicaNCs) as well as its replica flags found
+ # thru the msDS-HasInstantiatedNCs. If the NC replica meets
+ # the first enumeration test then this flag is set true
+ self.rep_present_criteria_one = False
+
+ # Call my super class we inherited from
+ NamingContext.__init__(self, nc_dnstr)
+
+ def __str__(self):
+ '''Debug dump string output of class'''
+ text = "%s:" % self.__class__.__name__
+ text = text + "\n\tdsa_dnstr=%s" % self.rep_dsa_dnstr
+ text = text + "\n\tdsa_guid=%s" % str(self.rep_dsa_guid)
+ text = text + "\n\tdefault=%s" % self.rep_default
+ text = text + "\n\tro=%s" % self.rep_ro
+ text = text + "\n\tpartial=%s" % self.rep_partial
+ text = text + "\n\tpresent=%s" % self.is_present()
+ text = text + "\n\tfsmo_role_owner=%s" % self.rep_fsmo_role_owner
+
+ for rep in self.rep_repsFrom:
+ text = text + "\n%s" % rep
+
+ return "%s\n%s" % (NamingContext.__str__(self), text)
+
+ def set_instantiated_flags(self, flags=None):
+ '''Set or clear NC replica instantiated flags'''
+ if flags is None:
+ self.rep_instantiated_flags = 0
+ else:
+ self.rep_instantiated_flags = flags
+
+ def identify_by_dsa_attr(self, samdb, attr):
+ """Given an NC which has been discovered thru the
+ nTDSDSA database object, determine what type of NC
+ replica it is (i.e. partial, read only, default)
+
+ :param attr: attr of nTDSDSA object where NC DN appears
+ """
+ # If the NC was found under hasPartialReplicaNCs
+ # then a partial replica at this dsa
+ if attr == "hasPartialReplicaNCs":
+ self.rep_partial = True
+ self.rep_present_criteria_one = True
+
+ # If the NC is listed under msDS-HasDomainNCs then
+ # this can only be a domain NC and it is the DSA's
+ # default domain NC
+ elif attr == "msDS-HasDomainNCs":
+ self.rep_default = True
+
+ # NCs listed under hasMasterNCs are either
+ # default domain, schema, or config. We check
+ # against schema and config because they will be
+ # the same for all nTDSDSAs in the forest. That
+ # leaves the default domain NC remaining which
+ # may be different for each nTDSDSAs (and thus
+ # we don't compare agains this samdb's default
+ # basedn
+ elif attr == "hasMasterNCs":
+ self.rep_present_criteria_one = True
+
+ if self.nc_dnstr != str(samdb.get_schema_basedn()) and \
+ self.nc_dnstr != str(samdb.get_config_basedn()):
+ self.rep_default = True
+
+ # RODC only
+ elif attr == "msDS-hasFullReplicaNCs":
+ self.rep_present_criteria_one = True
+ self.rep_ro = True
+
+ # Not RODC
+ elif attr == "msDS-hasMasterNCs":
+ self.rep_ro = False
+
+ # Now use this DSA attribute to identify the naming
+ # context type by calling the super class method
+ # of the same name
+ NamingContext.identify_by_dsa_attr(self, samdb, attr)
+
+ def is_default(self):
+ """Whether this is a default domain for the dsa that this NC appears on
+ """
+ return self.rep_default
+
+ def is_ro(self):
+ '''Return True if NC replica is read only'''
+ return self.rep_ro
+
+ def is_partial(self):
+ '''Return True if NC replica is partial'''
+ return self.rep_partial
+
+ def is_present(self):
+ """Given an NC replica which has been discovered thru the
+ nTDSDSA database object and populated with replica flags
+ from the msDS-HasInstantiatedNCs; return whether the NC
+ replica is present (true) or if the IT_NC_GOING flag is
+ set then the NC replica is not present (false)
+ """
+ if self.rep_present_criteria_one and \
+ self.rep_instantiated_flags & dsdb.INSTANCE_TYPE_NC_GOING == 0:
+ return True
+ return False
+
+ def load_repsFrom(self, samdb):
+ """Given an NC replica which has been discovered thru the nTDSDSA
+ database object, load the repsFrom attribute for the local replica.
+ held by my dsa. The repsFrom attribute is not replicated so this
+ attribute is relative only to the local DSA that the samdb exists on
+ """
+ try:
+ res = samdb.search(base=self.nc_dnstr, scope=ldb.SCOPE_BASE,
+ attrs=[ "repsFrom" ])
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find NC for (%s) - (%s)" %
+ (self.nc_dnstr, estr))
+
+ msg = res[0]
+
+ # Possibly no repsFrom if this is a singleton DC
+ if "repsFrom" in msg:
+ for value in msg["repsFrom"]:
+ rep = RepsFromTo(self.nc_dnstr,
+ ndr_unpack(drsblobs.repsFromToBlob, value))
+ self.rep_repsFrom.append(rep)
+
+ def commit_repsFrom(self, samdb, ro=False):
+ """Commit repsFrom to the database"""
+
+ # XXX - This is not truly correct according to the MS-TECH
+ # docs. To commit a repsFrom we should be using RPCs
+ # IDL_DRSReplicaAdd, IDL_DRSReplicaModify, and
+ # IDL_DRSReplicaDel to affect a repsFrom change.
+ #
+ # Those RPCs are missing in samba, so I'll have to
+ # implement them to get this to more accurately
+ # reflect the reference docs. As of right now this
+ # commit to the database will work as its what the
+ # older KCC also did
+ modify = False
+ newreps = []
+ delreps = []
+
+ for repsFrom in self.rep_repsFrom:
+
+ # Leave out any to be deleted from
+ # replacement list. Build a list
+ # of to be deleted reps which we will
+ # remove from rep_repsFrom list below
+ if repsFrom.to_be_deleted:
+ delreps.append(repsFrom)
+ modify = True
+ continue
+
+ if repsFrom.is_modified():
+ repsFrom.set_unmodified()
+ modify = True
+
+ # current (unmodified) elements also get
+ # appended here but no changes will occur
+ # unless something is "to be modified" or
+ # "to be deleted"
+ newreps.append(ndr_pack(repsFrom.ndr_blob))
+
+ # Now delete these from our list of rep_repsFrom
+ for repsFrom in delreps:
+ self.rep_repsFrom.remove(repsFrom)
+ delreps = []
+
+ # Nothing to do if no reps have been modified or
+ # need to be deleted or input option has informed
+ # us to be "readonly" (ro). Leave database
+ # record "as is"
+ if not modify or ro:
+ return
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, self.nc_dnstr)
+
+ m["repsFrom"] = \
+ ldb.MessageElement(newreps, ldb.FLAG_MOD_REPLACE, "repsFrom")
+
+ try:
+ samdb.modify(m)
+
+ except ldb.LdbError, estr:
+ raise Exception("Could not set repsFrom for (%s) - (%s)" %
+ (self.dsa_dnstr, estr))
+
+ def dumpstr_to_be_deleted(self):
+ text=""
+ for repsFrom in self.rep_repsFrom:
+ if repsFrom.to_be_deleted:
+ if text:
+ text = text + "\n%s" % repsFrom
+ else:
+ text = "%s" % repsFrom
+ return text
+
+ def dumpstr_to_be_modified(self):
+ text=""
+ for repsFrom in self.rep_repsFrom:
+ if repsFrom.is_modified():
+ if text:
+ text = text + "\n%s" % repsFrom
+ else:
+ text = "%s" % repsFrom
+ return text
+
+ def load_fsmo_roles(self, samdb):
+ """Given an NC replica which has been discovered thru the nTDSDSA
+ database object, load the fSMORoleOwner attribute.
+ """
+ try:
+ res = samdb.search(base=self.nc_dnstr, scope=ldb.SCOPE_BASE,
+ attrs=[ "fSMORoleOwner" ])
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find NC for (%s) - (%s)" %
+ (self.nc_dnstr, estr))
+
+ msg = res[0]
+
+ # Possibly no fSMORoleOwner
+ if "fSMORoleOwner" in msg:
+ self.rep_fsmo_role_owner = msg["fSMORoleOwner"]
+
+ def is_fsmo_role_owner(self, dsa_dnstr):
+ if self.rep_fsmo_role_owner is not None and \
+ self.rep_fsmo_role_owner == dsa_dnstr:
+ return True
+ return False
+
+
+class DirectoryServiceAgent(object):
+
+ def __init__(self, dsa_dnstr):
+ """Initialize DSA class.
+
+ Class is subsequently fully populated by calling the load_dsa() method
+
+ :param dsa_dnstr: DN of the nTDSDSA
+ """
+ self.dsa_dnstr = dsa_dnstr
+ self.dsa_guid = None
+ self.dsa_ivid = None
+ self.dsa_is_ro = False
+ self.dsa_is_istg = False
+ self.dsa_options = 0
+ self.dsa_behavior = 0
+ self.default_dnstr = None # default domain dn string for dsa
+
+ # NCReplicas for this dsa that are "present"
+ # Indexed by DN string of naming context
+ self.current_rep_table = {}
+
+ # NCReplicas for this dsa that "should be present"
+ # Indexed by DN string of naming context
+ self.needed_rep_table = {}
+
+ # NTDSConnections for this dsa. These are current
+ # valid connections that are committed or pending a commit
+ # in the database. Indexed by DN string of connection
+ self.connect_table = {}
+
+ def __str__(self):
+ '''Debug dump string output of class'''
+
+ text = "%s:" % self.__class__.__name__
+ if self.dsa_dnstr is not None:
+ text = text + "\n\tdsa_dnstr=%s" % self.dsa_dnstr
+ if self.dsa_guid is not None:
+ text = text + "\n\tdsa_guid=%s" % str(self.dsa_guid)
+ if self.dsa_ivid is not None:
+ text = text + "\n\tdsa_ivid=%s" % str(self.dsa_ivid)
+
+ text = text + "\n\tro=%s" % self.is_ro()
+ text = text + "\n\tgc=%s" % self.is_gc()
+ text = text + "\n\tistg=%s" % self.is_istg()
+
+ text = text + "\ncurrent_replica_table:"
+ text = text + "\n%s" % self.dumpstr_current_replica_table()
+ text = text + "\nneeded_replica_table:"
+ text = text + "\n%s" % self.dumpstr_needed_replica_table()
+ text = text + "\nconnect_table:"
+ text = text + "\n%s" % self.dumpstr_connect_table()
+
+ return text
+
+ def get_current_replica(self, nc_dnstr):
+ if nc_dnstr in self.current_rep_table.keys():
+ return self.current_rep_table[nc_dnstr]
+ else:
+ return None
+
+ def is_istg(self):
+ '''Returns True if dsa is intersite topology generator for it's site'''
+ # The KCC on an RODC always acts as an ISTG for itself
+ return self.dsa_is_istg or self.dsa_is_ro
+
+ def is_ro(self):
+ '''Returns True if dsa a read only domain controller'''
+ return self.dsa_is_ro
+
+ def is_gc(self):
+ '''Returns True if dsa hosts a global catalog'''
+ if (self.options & dsdb.DS_NTDSDSA_OPT_IS_GC) != 0:
+ return True
+ return False
+
+ def is_minimum_behavior(self, version):
+ """Is dsa at minimum windows level greater than or equal to (version)
+
+ :param version: Windows version to test against
+ (e.g. DS_BEHAVIOR_WIN2008)
+ """
+ if self.dsa_behavior >= version:
+ return True
+ return False
+
+ def is_translate_ntdsconn_disabled(self):
+ """Whether this allows NTDSConnection translation in its options."""
+ if (self.options & dsdb.DS_NTDSDSA_OPT_DISABLE_NTDSCONN_XLATE) != 0:
+ return True
+ return False
+
+ def get_rep_tables(self):
+ """Return DSA current and needed replica tables
+ """
+ return self.current_rep_table, self.needed_rep_table
+
+ def get_parent_dnstr(self):
+ """Get the parent DN string of this object."""
+ head, sep, tail = self.dsa_dnstr.partition(',')
+ return tail
+
+ def load_dsa(self, samdb):
+ """Load a DSA from the samdb.
+
+ Prior initialization has given us the DN of the DSA that we are to
+ load. This method initializes all other attributes, including loading
+ the NC replica table for this DSA.
+ """
+ attrs = ["objectGUID",
+ "invocationID",
+ "options",
+ "msDS-isRODC",
+ "msDS-Behavior-Version"]
+ try:
+ res = samdb.search(base=self.dsa_dnstr, scope=ldb.SCOPE_BASE,
+ attrs=attrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find nTDSDSA for (%s) - (%s)" %
+ (self.dsa_dnstr, estr))
+
+ msg = res[0]
+ self.dsa_guid = misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["objectGUID"][0]))
+
+ # RODCs don't originate changes and thus have no invocationId,
+ # therefore we must check for existence first
+ if "invocationId" in msg:
+ self.dsa_ivid = misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["invocationId"][0]))
+
+ if "options" in msg:
+ self.options = int(msg["options"][0])
+
+ if "msDS-isRODC" in msg and msg["msDS-isRODC"][0] == "TRUE":
+ self.dsa_is_ro = True
+ else:
+ self.dsa_is_ro = False
+
+ if "msDS-Behavior-Version" in msg:
+ self.dsa_behavior = int(msg['msDS-Behavior-Version'][0])
+
+ # Load the NC replicas that are enumerated on this dsa
+ self.load_current_replica_table(samdb)
+
+ # Load the nTDSConnection that are enumerated on this dsa
+ self.load_connection_table(samdb)
+
+ def load_current_replica_table(self, samdb):
+ """Method to load the NC replica's listed for DSA object.
+
+ This method queries the samdb for (hasMasterNCs, msDS-hasMasterNCs,
+ hasPartialReplicaNCs, msDS-HasDomainNCs, msDS-hasFullReplicaNCs, and
+ msDS-HasInstantiatedNCs) to determine complete list of NC replicas that
+ are enumerated for the DSA. Once a NC replica is loaded it is
+ identified (schema, config, etc) and the other replica attributes
+ (partial, ro, etc) are determined.
+
+ :param samdb: database to query for DSA replica list
+ """
+ ncattrs = [ # not RODC - default, config, schema (old style)
+ "hasMasterNCs",
+ # not RODC - default, config, schema, app NCs
+ "msDS-hasMasterNCs",
+ # domain NC partial replicas
+ "hasPartialReplicaNCs",
+ # default domain NC
+ "msDS-HasDomainNCs",
+ # RODC only - default, config, schema, app NCs
+ "msDS-hasFullReplicaNCs",
+ # Identifies if replica is coming, going, or stable
+ "msDS-HasInstantiatedNCs" ]
+ try:
+ res = samdb.search(base=self.dsa_dnstr, scope=ldb.SCOPE_BASE,
+ attrs=ncattrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find nTDSDSA NCs for (%s) - (%s)" %
+ (self.dsa_dnstr, estr))
+
+ # The table of NCs for the dsa we are searching
+ tmp_table = {}
+
+ # We should get one response to our query here for
+ # the ntds that we requested
+ if len(res[0]) > 0:
+
+ # Our response will contain a number of elements including
+ # the dn of the dsa as well as elements for each
+ # attribute (e.g. hasMasterNCs). Each of these elements
+ # is a dictonary list which we retrieve the keys for and
+ # then iterate over them
+ for k in res[0].keys():
+ if k == "dn":
+ continue
+
+ # For each attribute type there will be one or more DNs
+ # listed. For instance DCs normally have 3 hasMasterNCs
+ # listed.
+ for value in res[0][k]:
+ # Turn dn into a dsdb_Dn so we can use
+ # its methods to parse a binary DN
+ dsdn = dsdb_Dn(samdb, value)
+ flags = dsdn.get_binary_integer()
+ dnstr = str(dsdn.dn)
+
+ if not dnstr in tmp_table.keys():
+ rep = NCReplica(self.dsa_dnstr, self.dsa_guid, dnstr)
+ tmp_table[dnstr] = rep
+ else:
+ rep = tmp_table[dnstr]
+
+ if k == "msDS-HasInstantiatedNCs":
+ rep.set_instantiated_flags(flags)
+ continue
+
+ rep.identify_by_dsa_attr(samdb, k)
+
+ # if we've identified the default domain NC
+ # then save its DN string
+ if rep.is_default():
+ self.default_dnstr = dnstr
+ else:
+ raise Exception("No nTDSDSA NCs for (%s)" % self.dsa_dnstr)
+
+ # Assign our newly built NC replica table to this dsa
+ self.current_rep_table = tmp_table
+
+ def add_needed_replica(self, rep):
+ """Method to add a NC replica that "should be present" to the
+ needed_rep_table if not already in the table
+ """
+ if not rep.nc_dnstr in self.needed_rep_table.keys():
+ self.needed_rep_table[rep.nc_dnstr] = rep
+
+ def load_connection_table(self, samdb):
+ """Method to load the nTDSConnections listed for DSA object.
+
+ :param samdb: database to query for DSA connection list
+ """
+ try:
+ res = samdb.search(base=self.dsa_dnstr,
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(objectClass=nTDSConnection)")
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find nTDSConnection for (%s) - (%s)" %
+ (self.dsa_dnstr, estr))
+
+ for msg in res:
+ dnstr = str(msg.dn)
+
+ # already loaded
+ if dnstr in self.connect_table.keys():
+ continue
+
+ connect = NTDSConnection(dnstr)
+
+ connect.load_connection(samdb)
+ self.connect_table[dnstr] = connect
+
+ def commit_connections(self, samdb, ro=False):
+ """Method to commit any uncommitted nTDSConnections
+ modifications that are in our table. These would be
+ identified connections that are marked to be added or
+ deleted
+
+ :param samdb: database to commit DSA connection list to
+ :param ro: if (true) then peform internal operations but
+ do not write to the database (readonly)
+ """
+ delconn = []
+
+ for dnstr, connect in self.connect_table.items():
+ if connect.to_be_added:
+ connect.commit_added(samdb, ro)
+
+ if connect.to_be_modified:
+ connect.commit_modified(samdb, ro)
+
+ if connect.to_be_deleted:
+ connect.commit_deleted(samdb, ro)
+ delconn.append(dnstr)
+
+ # Now delete the connection from the table
+ for dnstr in delconn:
+ del self.connect_table[dnstr]
+
+ def add_connection(self, dnstr, connect):
+ assert dnstr not in self.connect_table.keys()
+ self.connect_table[dnstr] = connect
+
+ def get_connection_by_from_dnstr(self, from_dnstr):
+ """Scan DSA nTDSConnection table and return connection
+ with a "fromServer" dn string equivalent to method
+ input parameter.
+
+ :param from_dnstr: search for this from server entry
+ """
+ for dnstr, connect in self.connect_table.items():
+ if connect.get_from_dnstr() == from_dnstr:
+ return connect
+ return None
+
+ def dumpstr_current_replica_table(self):
+ '''Debug dump string output of current replica table'''
+ text=""
+ for k in self.current_rep_table.keys():
+ if text:
+ text = text + "\n%s" % self.current_rep_table[k]
+ else:
+ text = "%s" % self.current_rep_table[k]
+ return text
+
+ def dumpstr_needed_replica_table(self):
+ '''Debug dump string output of needed replica table'''
+ text=""
+ for k in self.needed_rep_table.keys():
+ if text:
+ text = text + "\n%s" % self.needed_rep_table[k]
+ else:
+ text = "%s" % self.needed_rep_table[k]
+ return text
+
+ def dumpstr_connect_table(self):
+ '''Debug dump string output of connect table'''
+ text=""
+ for k in self.connect_table.keys():
+ if text:
+ text = text + "\n%s" % self.connect_table[k]
+ else:
+ text = "%s" % self.connect_table[k]
+ return text
+
+ def new_connection(self, options, flags, transport, from_dnstr, sched):
+ """Set up a new connection for the DSA based on input
+ parameters. Connection will be added to the DSA
+ connect_table and will be marked as "to be added" pending
+ a call to commit_connections()
+ """
+ dnstr = "CN=%s," % str(uuid.uuid4()) + self.dsa_dnstr
+
+ connect = NTDSConnection(dnstr)
+ connect.to_be_added = True
+ connect.enabled = True
+ connect.from_dnstr = from_dnstr
+ connect.options = options
+ connect.flags = flags
+
+ if transport is not None:
+ connect.transport_dnstr = transport.dnstr
+
+ if sched is not None:
+ connect.schedule = sched
+ else:
+ # Create schedule. Attribute valuse set according to MS-TECH
+ # intrasite connection creation document
+ connect.schedule = drsblobs.schedule()
+
+ connect.schedule.size = 188
+ connect.schedule.bandwidth = 0
+ connect.schedule.numberOfSchedules = 1
+
+ header = drsblobs.scheduleHeader()
+ header.type = 0
+ header.offset = 20
+
+ connect.schedule.headerArray = [ header ]
+
+ # 168 byte instances of the 0x01 value. The low order 4 bits
+ # of the byte equate to 15 minute intervals within a single hour.
+ # There are 168 bytes because there are 168 hours in a full week
+ # Effectively we are saying to perform replication at the end of
+ # each hour of the week
+ data = drsblobs.scheduleSlots()
+ data.slots = [ 0x01 ] * 168
+
+ connect.schedule.dataArray = [ data ]
+
+ self.add_connection(dnstr, connect);
+ return connect
+
+
+class NTDSConnection(object):
+ """Class defines a nTDSConnection found under a DSA
+ """
+ def __init__(self, dnstr):
+ self.dnstr = dnstr
+ self.guid = None
+ self.enabled = False
+ self.whenCreated = 0
+ self.to_be_added = False # new connection needs to be added
+ self.to_be_deleted = False # old connection needs to be deleted
+ self.to_be_modified = False
+ self.options = 0
+ self.system_flags = 0
+ self.transport_dnstr = None
+ self.transport_guid = None
+ self.from_dnstr = None
+ self.schedule = None
+
+ def __str__(self):
+ '''Debug dump string output of NTDSConnection object'''
+
+ text = "%s:\n\tdn=%s" % (self.__class__.__name__, self.dnstr)
+ text = text + "\n\tenabled=%s" % self.enabled
+ text = text + "\n\tto_be_added=%s" % self.to_be_added
+ text = text + "\n\tto_be_deleted=%s" % self.to_be_deleted
+ text = text + "\n\tto_be_modified=%s" % self.to_be_modified
+ text = text + "\n\toptions=0x%08X" % self.options
+ text = text + "\n\tsystem_flags=0x%08X" % self.system_flags
+ text = text + "\n\twhenCreated=%d" % self.whenCreated
+ text = text + "\n\ttransport_dn=%s" % self.transport_dnstr
+
+ if self.guid is not None:
+ text = text + "\n\tguid=%s" % str(self.guid)
+
+ if self.transport_guid is not None:
+ text = text + "\n\ttransport_guid=%s" % str(self.transport_guid)
+
+ text = text + "\n\tfrom_dn=%s" % self.from_dnstr
+
+ if self.schedule is not None:
+ text = text + "\n\tschedule.size=%s" % self.schedule.size
+ text = text + "\n\tschedule.bandwidth=%s" % self.schedule.bandwidth
+ text = text + "\n\tschedule.numberOfSchedules=%s" % \
+ self.schedule.numberOfSchedules
+
+ for i, header in enumerate(self.schedule.headerArray):
+ text = text + "\n\tschedule.headerArray[%d].type=%d" % \
+ (i, header.type)
+ text = text + "\n\tschedule.headerArray[%d].offset=%d" % \
+ (i, header.offset)
+ text = text + "\n\tschedule.dataArray[%d].slots[ " % i
+ for slot in self.schedule.dataArray[i].slots:
+ text = text + "0x%X " % slot
+ text = text + "]"
+
+ return text
+
+ def load_connection(self, samdb):
+ """Given a NTDSConnection object with an prior initialization
+ for the object's DN, search for the DN and load attributes
+ from the samdb.
+ """
+ attrs = [ "options",
+ "enabledConnection",
+ "schedule",
+ "whenCreated",
+ "objectGUID",
+ "transportType",
+ "fromServer",
+ "systemFlags" ]
+ try:
+ res = samdb.search(base=self.dnstr, scope=ldb.SCOPE_BASE,
+ attrs=attrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find nTDSConnection for (%s) - (%s)" %
+ (self.dnstr, estr))
+
+ msg = res[0]
+
+ if "options" in msg:
+ self.options = int(msg["options"][0])
+
+ if "enabledConnection" in msg:
+ if msg["enabledConnection"][0].upper().lstrip().rstrip() == "TRUE":
+ self.enabled = True
+
+ if "systemFlags" in msg:
+ self.system_flags = int(msg["systemFlags"][0])
+
+ if "objectGUID" in msg:
+ self.guid = \
+ misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["objectGUID"][0]))
+
+ if "transportType" in msg:
+ dsdn = dsdb_Dn(samdb, msg["tranportType"][0])
+ self.load_connection_transport(str(dsdn.dn))
+
+ if "schedule" in msg:
+ self.schedule = ndr_unpack(drsblobs.replSchedule, msg["schedule"][0])
+
+ if "whenCreated" in msg:
+ self.whenCreated = ldb.string_to_time(msg["whenCreated"][0])
+
+ if "fromServer" in msg:
+ dsdn = dsdb_Dn(samdb, msg["fromServer"][0])
+ self.from_dnstr = str(dsdn.dn)
+ assert self.from_dnstr is not None
+
+ def load_connection_transport(self, tdnstr):
+ """Given a NTDSConnection object which enumerates a transport
+ DN, load the transport information for the connection object
+
+ :param tdnstr: transport DN to load
+ """
+ attrs = [ "objectGUID" ]
+ try:
+ res = samdb.search(base=tdnstr,
+ scope=ldb.SCOPE_BASE, attrs=attrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find transport (%s)" %
+ (tdnstr, estr))
+
+ if "objectGUID" in res[0]:
+ self.transport_dnstr = tdnstr
+ self.transport_guid = \
+ misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["objectGUID"][0]))
+ assert self.transport_dnstr is not None
+ assert self.transport_guid is not None
+
+ def commit_deleted(self, samdb, ro=False):
+ """Local helper routine for commit_connections() which
+ handles committed connections that are to be deleted from
+ the database database
+ """
+ assert self.to_be_deleted
+ self.to_be_deleted = False
+
+ # No database modification requested
+ if ro:
+ return
+
+ try:
+ samdb.delete(self.dnstr)
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Could not delete nTDSConnection for (%s) - (%s)" %
+ (self.dnstr, estr))
+
+ def commit_added(self, samdb, ro=False):
+ """Local helper routine for commit_connections() which
+ handles committed connections that are to be added to the
+ database
+ """
+ assert self.to_be_added
+ self.to_be_added = False
+
+ # No database modification requested
+ if ro:
+ return
+
+ # First verify we don't have this entry to ensure nothing
+ # is programatically amiss
+ found = False
+ try:
+ msg = samdb.search(base=self.dnstr, scope=ldb.SCOPE_BASE)
+ if len(msg) != 0:
+ found = True
+
+ except ldb.LdbError, (enum, estr):
+ if enum != ldb.ERR_NO_SUCH_OBJECT:
+ raise Exception("Unable to search for (%s) - (%s)" %
+ (self.dnstr, estr))
+ if found:
+ raise Exception("nTDSConnection for (%s) already exists!" %
+ self.dnstr)
+
+ if self.enabled:
+ enablestr = "TRUE"
+ else:
+ enablestr = "FALSE"
+
+ # Prepare a message for adding to the samdb
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, self.dnstr)
+
+ m["objectClass"] = \
+ ldb.MessageElement("nTDSConnection", ldb.FLAG_MOD_ADD,
+ "objectClass")
+ m["showInAdvancedViewOnly"] = \
+ ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD,
+ "showInAdvancedViewOnly")
+ m["enabledConnection"] = \
+ ldb.MessageElement(enablestr, ldb.FLAG_MOD_ADD, "enabledConnection")
+ m["fromServer"] = \
+ ldb.MessageElement(self.from_dnstr, ldb.FLAG_MOD_ADD, "fromServer")
+ m["options"] = \
+ ldb.MessageElement(str(self.options), ldb.FLAG_MOD_ADD, "options")
+ m["systemFlags"] = \
+ ldb.MessageElement(str(self.system_flags), ldb.FLAG_MOD_ADD,
+ "systemFlags")
+
+ if self.transport_dnstr is not None:
+ m["transportType"] = \
+ ldb.MessageElement(str(self.transport_dnstr), ldb.FLAG_MOD_ADD,
+ "transportType")
+
+ if self.schedule is not None:
+ m["schedule"] = \
+ ldb.MessageElement(ndr_pack(self.schedule),
+ ldb.FLAG_MOD_ADD, "schedule")
+ try:
+ samdb.add(m)
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Could not add nTDSConnection for (%s) - (%s)" %
+ (self.dnstr, estr))
+
+ def commit_modified(self, samdb, ro=False):
+ """Local helper routine for commit_connections() which
+ handles committed connections that are to be modified to the
+ database
+ """
+ assert self.to_be_modified
+ self.to_be_modified = False
+
+ # No database modification requested
+ if ro:
+ return
+
+ # First verify we have this entry to ensure nothing
+ # is programatically amiss
+ try:
+ msg = samdb.search(base=self.dnstr, scope=ldb.SCOPE_BASE)
+ found = True
+
+ except ldb.LdbError, (enum, estr):
+ if enum == ldb.ERR_NO_SUCH_OBJECT:
+ found = False
+ else:
+ raise Exception("Unable to search for (%s) - (%s)" %
+ (self.dnstr, estr))
+ if not found:
+ raise Exception("nTDSConnection for (%s) doesn't exist!" %
+ self.dnstr)
+
+ if self.enabled:
+ enablestr = "TRUE"
+ else:
+ enablestr = "FALSE"
+
+ # Prepare a message for modifying the samdb
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, self.dnstr)
+
+ m["enabledConnection"] = \
+ ldb.MessageElement(enablestr, ldb.FLAG_MOD_REPLACE,
+ "enabledConnection")
+ m["fromServer"] = \
+ ldb.MessageElement(self.from_dnstr, ldb.FLAG_MOD_REPLACE,
+ "fromServer")
+ m["options"] = \
+ ldb.MessageElement(str(self.options), ldb.FLAG_MOD_REPLACE,
+ "options")
+ m["systemFlags"] = \
+ ldb.MessageElement(str(self.system_flags), ldb.FLAG_MOD_REPLACE,
+ "systemFlags")
+
+ if self.transport_dnstr is not None:
+ m["transportType"] = \
+ ldb.MessageElement(str(self.transport_dnstr),
+ ldb.FLAG_MOD_REPLACE, "transportType")
+ else:
+ m["transportType"] = \
+ ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "transportType")
+
+ if self.schedule is not None:
+ m["schedule"] = \
+ ldb.MessageElement(ndr_pack(self.schedule),
+ ldb.FLAG_MOD_REPLACE, "schedule")
+ else:
+ m["schedule"] = \
+ ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "schedule")
+ try:
+ samdb.modify(m)
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Could not modify nTDSConnection for (%s) - (%s)" %
+ (self.dnstr, estr))
+
+ def set_modified(self, truefalse):
+ self.to_be_modified = truefalse
+
+ def set_added(self, truefalse):
+ self.to_be_added = truefalse
+
+ def set_deleted(self, truefalse):
+ self.to_be_deleted = truefalse
+
+ def is_schedule_minimum_once_per_week(self):
+ """Returns True if our schedule includes at least one
+ replication interval within the week. False otherwise
+ """
+ if self.schedule is None or self.schedule.dataArray[0] is None:
+ return False
+
+ for slot in self.schedule.dataArray[0].slots:
+ if (slot & 0x0F) != 0x0:
+ return True
+ return False
+
+ def is_equivalent_schedule(self, sched):
+ """Returns True if our schedule is equivalent to the input
+ comparison schedule.
+
+ :param shed: schedule to compare to
+ """
+ if self.schedule is not None:
+ if sched is None:
+ return False
+ elif sched is None:
+ return True
+
+ if (self.schedule.size != sched.size or
+ self.schedule.bandwidth != sched.bandwidth or
+ self.schedule.numberOfSchedules != sched.numberOfSchedules):
+ return False
+
+ for i, header in enumerate(self.schedule.headerArray):
+
+ if self.schedule.headerArray[i].type != sched.headerArray[i].type:
+ return False
+
+ if self.schedule.headerArray[i].offset != \
+ sched.headerArray[i].offset:
+ return False
+
+ for a, b in zip(self.schedule.dataArray[i].slots,
+ sched.dataArray[i].slots):
+ if a != b:
+ return False
+ return True
+
+ def convert_schedule_to_repltimes(self):
+ """Convert NTDS Connection schedule to replTime schedule.
+
+ NTDS Connection schedule slots are double the size of
+ the replTime slots but the top portion of the NTDS
+ Connection schedule slot (4 most significant bits in
+ uchar) are unused. The 4 least significant bits have
+ the same (15 minute interval) bit positions as replTimes.
+ We thus pack two elements of the NTDS Connection schedule
+ slots into one element of the replTimes slot
+ If no schedule appears in NTDS Connection then a default
+ of 0x11 is set in each replTimes slot as per behaviour
+ noted in a Windows DC. That default would cause replication
+ within the last 15 minutes of each hour.
+ """
+ times = [0x11] * 84
+
+ for i, slot in enumerate(times):
+ if self.schedule is not None and \
+ self.schedule.dataArray[0] is not None:
+ slot = (self.schedule.dataArray[0].slots[i*2] & 0xF) << 4 | \
+ (self.schedule.dataArray[0].slots[i*2] & 0xF)
+ return times
+
+ def is_rodc_topology(self):
+ """Returns True if NTDS Connection specifies RODC
+ topology only
+ """
+ if self.options & dsdb.NTDSCONN_OPT_RODC_TOPOLOGY == 0:
+ return False
+ return True
+
+ def is_generated(self):
+ """Returns True if NTDS Connection was generated by the
+ KCC topology algorithm as opposed to set by the administrator
+ """
+ if self.options & dsdb.NTDSCONN_OPT_IS_GENERATED == 0:
+ return False
+ return True
+
+ def is_override_notify_default(self):
+ """Returns True if NTDS Connection should override notify default
+ """
+ if self.options & dsdb.NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT == 0:
+ return False
+ return True
+
+ def is_use_notify(self):
+ """Returns True if NTDS Connection should use notify
+ """
+ if self.options & dsdb.NTDSCONN_OPT_USE_NOTIFY == 0:
+ return False
+ return True
+
+ def is_twoway_sync(self):
+ """Returns True if NTDS Connection should use twoway sync
+ """
+ if self.options & dsdb.NTDSCONN_OPT_TWOWAY_SYNC == 0:
+ return False
+ return True
+
+ def is_intersite_compression_disabled(self):
+ """Returns True if NTDS Connection intersite compression
+ is disabled
+ """
+ if self.options & dsdb.NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION == 0:
+ return False
+ return True
+
+ def is_user_owned_schedule(self):
+ """Returns True if NTDS Connection has a user owned schedule
+ """
+ if self.options & dsdb.NTDSCONN_OPT_USER_OWNED_SCHEDULE == 0:
+ return False
+ return True
+
+ def is_enabled(self):
+ """Returns True if NTDS Connection is enabled
+ """
+ return self.enabled
+
+ def get_from_dnstr(self):
+ '''Return fromServer dn string attribute'''
+ return self.from_dnstr
+
+
+class Partition(NamingContext):
+ """A naming context discovered thru Partitions DN of the config schema.
+
+ This is a more specific form of NamingContext class (inheriting from that
+ class) and it identifies unique attributes enumerated in the Partitions
+ such as which nTDSDSAs are cross referenced for replicas
+ """
+ def __init__(self, partstr):
+ self.partstr = partstr
+ self.enabled = True
+ self.system_flags = 0
+ self.rw_location_list = []
+ self.ro_location_list = []
+
+ # We don't have enough info to properly
+ # fill in the naming context yet. We'll get that
+ # fully set up with load_partition().
+ NamingContext.__init__(self, None)
+
+
+ def load_partition(self, samdb):
+ """Given a Partition class object that has been initialized with its
+ partition dn string, load the partition from the sam database, identify
+ the type of the partition (schema, domain, etc) and record the list of
+ nTDSDSAs that appear in the cross reference attributes
+ msDS-NC-Replica-Locations and msDS-NC-RO-Replica-Locations.
+
+ :param samdb: sam database to load partition from
+ """
+ attrs = [ "nCName",
+ "Enabled",
+ "systemFlags",
+ "msDS-NC-Replica-Locations",
+ "msDS-NC-RO-Replica-Locations" ]
+ try:
+ res = samdb.search(base=self.partstr, scope=ldb.SCOPE_BASE,
+ attrs=attrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find partition for (%s) - (%s)" % (
+ self.partstr, estr))
+
+ msg = res[0]
+ for k in msg.keys():
+ if k == "dn":
+ continue
+
+ if k == "Enabled":
+ if msg[k][0].upper().lstrip().rstrip() == "TRUE":
+ self.enabled = True
+ else:
+ self.enabled = False
+ continue
+
+ if k == "systemFlags":
+ self.system_flags = int(msg[k][0])
+ continue
+
+ for value in msg[k]:
+ dsdn = dsdb_Dn(samdb, value)
+ dnstr = str(dsdn.dn)
+
+ if k == "nCName":
+ self.nc_dnstr = dnstr
+ continue
+
+ if k == "msDS-NC-Replica-Locations":
+ self.rw_location_list.append(dnstr)
+ continue
+
+ if k == "msDS-NC-RO-Replica-Locations":
+ self.ro_location_list.append(dnstr)
+ continue
+
+ # Now identify what type of NC this partition
+ # enumerated
+ self.identify_by_basedn(samdb)
+
+ def is_enabled(self):
+ """Returns True if partition is enabled
+ """
+ return self.is_enabled
+
+ def is_foreign(self):
+ """Returns True if this is not an Active Directory NC in our
+ forest but is instead something else (e.g. a foreign NC)
+ """
+ if (self.system_flags & dsdb.SYSTEM_FLAG_CR_NTDS_NC) == 0:
+ return True
+ else:
+ return False
+
+ def should_be_present(self, target_dsa):
+ """Tests whether this partition should have an NC replica
+ on the target dsa. This method returns a tuple of
+ needed=True/False, ro=True/False, partial=True/False
+
+ :param target_dsa: should NC be present on target dsa
+ """
+ needed = False
+ ro = False
+ partial = False
+
+ # If this is the config, schema, or default
+ # domain NC for the target dsa then it should
+ # be present
+ if self.nc_type == NCType.config or \
+ self.nc_type == NCType.schema or \
+ (self.nc_type == NCType.domain and
+ self.nc_dnstr == target_dsa.default_dnstr):
+ needed = True
+
+ # A writable replica of an application NC should be present
+ # if there a cross reference to the target DSA exists. Depending
+ # on whether the DSA is ro we examine which type of cross reference
+ # to look for (msDS-NC-Replica-Locations or
+ # msDS-NC-RO-Replica-Locations
+ if self.nc_type == NCType.application:
+ if target_dsa.is_ro():
+ if target_dsa.dsa_dnstr in self.ro_location_list:
+ needed = True
+ else:
+ if target_dsa.dsa_dnstr in self.rw_location_list:
+ needed = True
+
+ # If the target dsa is a gc then a partial replica of a
+ # domain NC (other than the DSAs default domain) should exist
+ # if there is also a cross reference for the DSA
+ if target_dsa.is_gc() and \
+ self.nc_type == NCType.domain and \
+ self.nc_dnstr != target_dsa.default_dnstr and \
+ (target_dsa.dsa_dnstr in self.ro_location_list or
+ target_dsa.dsa_dnstr in self.rw_location_list):
+ needed = True
+ partial = True
+
+ # partial NCs are always readonly
+ if needed and (target_dsa.is_ro() or partial):
+ ro = True
+
+ return needed, ro, partial
+
+ def __str__(self):
+ '''Debug dump string output of class'''
+ text = "%s" % NamingContext.__str__(self)
+ text = text + "\n\tpartdn=%s" % self.partstr
+ for k in self.rw_location_list:
+ text = text + "\n\tmsDS-NC-Replica-Locations=%s" % k
+ for k in self.ro_location_list:
+ text = text + "\n\tmsDS-NC-RO-Replica-Locations=%s" % k
+ return text
+
+
+class Site(object):
+ """An individual site object discovered thru the configuration
+ naming context. Contains all DSAs that exist within the site
+ """
+ def __init__(self, site_dnstr):
+ self.site_dnstr = site_dnstr
+ self.site_options = 0
+ self.site_topo_generator = None
+ self.site_topo_failover = 0 # appears to be in minutes
+ self.dsa_table = {}
+
+ def load_site(self, samdb):
+ """Loads the NTDS Site Settions options attribute for the site
+ as well as querying and loading all DSAs that appear within
+ the site.
+ """
+ ssdn = "CN=NTDS Site Settings,%s" % self.site_dnstr
+ attrs = ["options",
+ "interSiteTopologyFailover",
+ "interSiteTopologyGenerator"]
+ try:
+ res = samdb.search(base=ssdn, scope=ldb.SCOPE_BASE,
+ attrs=attrs)
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find site settings for (%s) - (%s)" %
+ (ssdn, estr))
+
+ msg = res[0]
+ if "options" in msg:
+ self.site_options = int(msg["options"][0])
+
+ if "interSiteTopologyGenerator" in msg:
+ self.site_topo_generator = str(msg["interSiteTopologyGenerator"][0])
+
+ if "interSiteTopologyFailover" in msg:
+ self.site_topo_failover = int(msg["interSiteTopologyFailover"][0])
+
+ self.load_all_dsa(samdb)
+
+ def load_all_dsa(self, samdb):
+ """Discover all nTDSDSA thru the sites entry and
+ instantiate and load the DSAs. Each dsa is inserted
+ into the dsa_table by dn string.
+ """
+ try:
+ res = samdb.search(self.site_dnstr,
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(objectClass=nTDSDSA)")
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find nTDSDSAs - (%s)" % estr)
+
+ for msg in res:
+ dnstr = str(msg.dn)
+
+ # already loaded
+ if dnstr in self.dsa_table.keys():
+ continue
+
+ dsa = DirectoryServiceAgent(dnstr)
+
+ dsa.load_dsa(samdb)
+
+ # Assign this dsa to my dsa table
+ # and index by dsa dn
+ self.dsa_table[dnstr] = dsa
+
+ def get_dsa_by_guidstr(self, guidstr):
+ for dsa in self.dsa_table.values():
+ if str(dsa.dsa_guid) == guidstr:
+ return dsa
+ return None
+
+ def get_dsa(self, dnstr):
+ """Return a previously loaded DSA object by consulting
+ the sites dsa_table for the provided DSA dn string
+
+ :return: None if DSA doesn't exist
+ """
+ if dnstr in self.dsa_table.keys():
+ return self.dsa_table[dnstr]
+ return None
+
+ def select_istg(self, samdb, mydsa, ro):
+ """Determine if my DC should be an intersite topology
+ generator. If my DC is the istg and is both a writeable
+ DC and the database is opened in write mode then we perform
+ an originating update to set the interSiteTopologyGenerator
+ attribute in the NTDS Site Settings object. An RODC always
+ acts as an ISTG for itself.
+ """
+ # The KCC on an RODC always acts as an ISTG for itself
+ if mydsa.dsa_is_ro:
+ mydsa.dsa_is_istg = True
+ return True
+
+ # Find configuration NC replica for my DSA
+ for c_rep in mydsa.current_rep_table.values():
+ if c_rep.is_config():
+ break
+
+ if c_rep is None:
+ raise Exception("Unable to find config NC replica for (%s)" %
+ mydsa.dsa_dnstr)
+
+ # Load repsFrom if not already loaded so we can get the current
+ # state of the config replica and whether we are getting updates
+ # from the istg
+ c_rep.load_repsFrom(samdb)
+
+ # From MS-Tech ISTG selection:
+ # First, the KCC on a writable DC determines whether it acts
+ # as an ISTG for its site
+ #
+ # Let s be the object such that s!lDAPDisplayName = nTDSDSA
+ # and classSchema in s!objectClass.
+ #
+ # Let D be the sequence of objects o in the site of the local
+ # DC such that o!objectCategory = s. D is sorted in ascending
+ # order by objectGUID.
+ #
+ # Which is a fancy way of saying "sort all the nTDSDSA objects
+ # in the site by guid in ascending order". Place sorted list
+ # in D_sort[]
+ D_sort = []
+ d_dsa = None
+
+ unixnow = int(time.time()) # seconds since 1970
+ ntnow = unix2nttime(unixnow) # double word number of 100 nanosecond
+ # intervals since 1600s
+
+ for dsa in self.dsa_table.values():
+ D_sort.append(dsa)
+
+ D_sort.sort(sort_dsa_by_guid)
+
+ # Let f be the duration o!interSiteTopologyFailover seconds, or 2 hours
+ # if o!interSiteTopologyFailover is 0 or has no value.
+ #
+ # Note: lastSuccess and ntnow are in 100 nanosecond intervals
+ # so it appears we have to turn f into the same interval
+ #
+ # interSiteTopologyFailover (if set) appears to be in minutes
+ # so we'll need to convert to senconds and then 100 nanosecond
+ # intervals
+ #
+ # 10,000,000 is number of 100 nanosecond intervals in a second
+ if self.site_topo_failover == 0:
+ f = 2 * 60 * 60 * 10000000
+ else:
+ f = self.site_topo_failover * 60 * 10000000
+
+ # From MS-Tech ISTG selection:
+ # If o != NULL and o!interSiteTopologyGenerator is not the
+ # nTDSDSA object for the local DC and
+ # o!interSiteTopologyGenerator is an element dj of sequence D:
+ #
+ if self.site_topo_generator is not None and \
+ self.site_topo_generator in self.dsa_table.keys():
+ d_dsa = self.dsa_table[self.site_topo_generator]
+ j_idx = D_sort.index(d_dsa)
+
+ if d_dsa is not None and d_dsa is not mydsa:
+ # From MS-Tech ISTG selection:
+ # Let c be the cursor in the replUpToDateVector variable
+ # associated with the NC replica of the config NC such
+ # that c.uuidDsa = dj!invocationId. If no such c exists
+ # (No evidence of replication from current ITSG):
+ # Let i = j.
+ # Let t = 0.
+ #
+ # Else if the current time < c.timeLastSyncSuccess - f
+ # (Evidence of time sync problem on current ISTG):
+ # Let i = 0.
+ # Let t = 0.
+ #
+ # Else (Evidence of replication from current ITSG):
+ # Let i = j.
+ # Let t = c.timeLastSyncSuccess.
+ #
+ # last_success appears to be a double word containing
+ # number of 100 nanosecond intervals since the 1600s
+ if d_dsa.dsa_ivid != c_rep.source_dsa_invocation_id:
+ i_idx = j_idx
+ t_time = 0
+
+ elif ntnow < (c_rep.last_success - f):
+ i_idx = 0
+ t_time = 0
+ else:
+ i_idx = j_idx
+ t_time = c_rep.last_success
+
+ # Otherwise (Nominate local DC as ISTG):
+ # Let i be the integer such that di is the nTDSDSA
+ # object for the local DC.
+ # Let t = the current time.
+ else:
+ i_idx = D_sort.index(mydsa)
+ t_time = ntnow
+
+ # Compute a function that maintains the current ISTG if
+ # it is alive, cycles through other candidates if not.
+ #
+ # Let k be the integer (i + ((current time - t) /
+ # o!interSiteTopologyFailover)) MOD |D|.
+ #
+ # Note: We don't want to divide by zero here so they must
+ # have meant "f" instead of "o!interSiteTopologyFailover"
+ k_idx = (i_idx + ((ntnow - t_time) / f)) % len(D_sort)
+
+ # The local writable DC acts as an ISTG for its site if and
+ # only if dk is the nTDSDSA object for the local DC. If the
+ # local DC does not act as an ISTG, the KCC skips the
+ # remainder of this task.
+ d_dsa = D_sort[k_idx]
+ d_dsa.dsa_is_istg = True
+
+ # Update if we are the ISTG, otherwise return
+ if d_dsa is not mydsa:
+ return False
+
+ # Nothing to do
+ if self.site_topo_generator == mydsa.dsa_dnstr:
+ return True
+
+ self.site_topo_generator = mydsa.dsa_dnstr
+
+ # If readonly database then do not perform a
+ # persistent update
+ if ro:
+ return True
+
+ # Perform update to the samdb
+ ssdn = "CN=NTDS Site Settings,%s" % self.site_dnstr
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, ssdn)
+
+ m["interSiteTopologyGenerator"] = \
+ ldb.MessageElement(mydsa.dsa_dnstr, ldb.FLAG_MOD_REPLACE,
+ "interSiteTopologyGenerator")
+ try:
+ samdb.modify(m)
+
+ except ldb.LdbError, estr:
+ raise Exception(
+ "Could not set interSiteTopologyGenerator for (%s) - (%s)" %
+ (ssdn, estr))
+ return True
+
+ def is_intrasite_topology_disabled(self):
+ '''Returns True if intra-site topology is disabled for site'''
+ if (self.site_options &
+ dsdb.DS_NTDSSETTINGS_OPT_IS_AUTO_TOPOLOGY_DISABLED) != 0:
+ return True
+ return False
+
+ def is_intersite_topology_disabled(self):
+ '''Returns True if inter-site topology is disabled for site'''
+ if (self.site_options &
+ dsdb.DS_NTDSSETTINGS_OPT_IS_INTER_SITE_AUTO_TOPOLOGY_DISABLED) != 0:
+ return True
+ return False
+
+ def is_random_bridgehead_disabled(self):
+ '''Returns True if selection of random bridgehead is disabled'''
+ if (self.site_options &
+ dsdb.DS_NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED) != 0:
+ return True
+ return False
+
+ def is_detect_stale_disabled(self):
+ '''Returns True if detect stale is disabled for site'''
+ if (self.site_options &
+ dsdb.DS_NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED) != 0:
+ return True
+ return False
+
+ def is_cleanup_ntdsconn_disabled(self):
+ '''Returns True if NTDS Connection cleanup is disabled for site'''
+ if (self.site_options &
+ dsdb.DS_NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED) != 0:
+ return True
+ return False
+
+ def same_site(self, dsa):
+ '''Return True if dsa is in this site'''
+ if self.get_dsa(dsa.dsa_dnstr):
+ return True
+ return False
+
+ def __str__(self):
+ '''Debug dump string output of class'''
+ text = "%s:" % self.__class__.__name__
+ text = text + "\n\tdn=%s" % self.site_dnstr
+ text = text + "\n\toptions=0x%X" % self.site_options
+ text = text + "\n\ttopo_generator=%s" % self.site_topo_generator
+ text = text + "\n\ttopo_failover=%d" % self.site_topo_failover
+ for key, dsa in self.dsa_table.items():
+ text = text + "\n%s" % dsa
+ return text
+
+
+class GraphNode(object):
+ """A graph node describing a set of edges that should be directed to it.
+
+ Each edge is a connection for a particular naming context replica directed
+ from another node in the forest to this node.
+ """
+
+ def __init__(self, dsa_dnstr, max_node_edges):
+ """Instantiate the graph node according to a DSA dn string
+
+ :param max_node_edges: maximum number of edges that should ever
+ be directed to the node
+ """
+ self.max_edges = max_node_edges
+ self.dsa_dnstr = dsa_dnstr
+ self.edge_from = []
+
+ def __str__(self):
+ text = "%s:" % self.__class__.__name__
+ text = text + "\n\tdsa_dnstr=%s" % self.dsa_dnstr
+ text = text + "\n\tmax_edges=%d" % self.max_edges
+
+ for i, edge in enumerate(self.edge_from):
+ text = text + "\n\tedge_from[%d]=%s" % (i, edge)
+ return text
+
+ def add_edge_from(self, from_dsa_dnstr):
+ """Add an edge from the dsa to our graph nodes edge from list
+
+ :param from_dsa_dnstr: the dsa that the edge emanates from
+ """
+ assert from_dsa_dnstr is not None
+
+ # No edges from myself to myself
+ if from_dsa_dnstr == self.dsa_dnstr:
+ return False
+ # Only one edge from a particular node
+ if from_dsa_dnstr in self.edge_from:
+ return False
+ # Not too many edges
+ if len(self.edge_from) >= self.max_edges:
+ return False
+ self.edge_from.append(from_dsa_dnstr)
+ return True
+
+ def add_edges_from_connections(self, dsa):
+ """For each nTDSConnection object associated with a particular
+ DSA, we test if it implies an edge to this graph node (i.e.
+ the "fromServer" attribute). If it does then we add an
+ edge from the server unless we are over the max edges for this
+ graph node
+
+ :param dsa: dsa with a dnstr equivalent to his graph node
+ """
+ for dnstr, connect in dsa.connect_table.items():
+ self.add_edge_from(connect.from_dnstr)
+
+ def add_connections_from_edges(self, dsa):
+ """For each edge directed to this graph node, ensure there
+ is a corresponding nTDSConnection object in the dsa.
+ """
+ for edge_dnstr in self.edge_from:
+ connect = dsa.get_connection_by_from_dnstr(edge_dnstr)
+
+ # For each edge directed to the NC replica that
+ # "should be present" on the local DC, the KCC determines
+ # whether an object c exists such that:
+ #
+ # c is a child of the DC's nTDSDSA object.
+ # c.objectCategory = nTDSConnection
+ #
+ # Given the NC replica ri from which the edge is directed,
+ # c.fromServer is the dsname of the nTDSDSA object of
+ # the DC on which ri "is present".
+ #
+ # c.options does not contain NTDSCONN_OPT_RODC_TOPOLOGY
+ if connect and not connect.is_rodc_topology():
+ exists = True
+ else:
+ exists = False
+
+ # if no such object exists then the KCC adds an object
+ # c with the following attributes
+ if exists:
+ return
+
+ # Generate a new dnstr for this nTDSConnection
+ opt = dsdb.NTDSCONN_OPT_IS_GENERATED
+ flags = dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME + \
+ dsdb.SYSTEM_FLAG_CONFIG_ALLOW_MOVE
+
+ dsa.create_connection(opt, flags, None, edge_dnstr, None)
+
+ def has_sufficient_edges(self):
+ '''Return True if we have met the maximum "from edges" criteria'''
+ if len(self.edge_from) >= self.max_edges:
+ return True
+ return False
+
+
+class Transport(object):
+ """Class defines a Inter-site transport found under Sites
+ """
+
+ def __init__(self, dnstr):
+ self.dnstr = dnstr
+ self.options = 0
+ self.guid = None
+ self.name = None
+ self.address_attr = None
+ self.bridgehead_list = []
+
+ def __str__(self):
+ '''Debug dump string output of Transport object'''
+
+ text = "%s:\n\tdn=%s" % (self.__class__.__name__, self.dnstr)
+ text = text + "\n\tguid=%s" % str(self.guid)
+ text = text + "\n\toptions=%d" % self.options
+ text = text + "\n\taddress_attr=%s" % self.address_attr
+ text = text + "\n\tname=%s" % self.name
+ for dnstr in self.bridgehead_list:
+ text = text + "\n\tbridgehead_list=%s" % dnstr
+
+ return text
+
+ def load_transport(self, samdb):
+ """Given a Transport object with an prior initialization
+ for the object's DN, search for the DN and load attributes
+ from the samdb.
+ """
+ attrs = [ "objectGUID",
+ "options",
+ "name",
+ "bridgeheadServerListBL",
+ "transportAddressAttribute" ]
+ try:
+ res = samdb.search(base=self.dnstr, scope=ldb.SCOPE_BASE,
+ attrs=attrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find Transport for (%s) - (%s)" %
+ (self.dnstr, estr))
+
+ msg = res[0]
+ self.guid = misc.GUID(samdb.schema_format_value("objectGUID",
+ msg["objectGUID"][0]))
+
+ if "options" in msg:
+ self.options = int(msg["options"][0])
+
+ if "transportAddressAttribute" in msg:
+ self.address_attr = str(msg["transportAddressAttribute"][0])
+
+ if "name" in msg:
+ self.name = str(msg["name"][0])
+
+ if "bridgeheadServerListBL" in msg:
+ for value in msg["bridgeheadServerListBL"]:
+ dsdn = dsdb_Dn(samdb, value)
+ dnstr = str(dsdn.dn)
+ if dnstr not in self.bridgehead_list:
+ self.bridgehead_list.append(dnstr)
+
+
+class RepsFromTo(object):
+ """Class encapsulation of the NDR repsFromToBlob.
+
+ Removes the necessity of external code having to
+ understand about other_info or manipulation of
+ update flags.
+ """
+ def __init__(self, nc_dnstr=None, ndr_blob=None):
+
+ self.__dict__['to_be_deleted'] = False
+ self.__dict__['nc_dnstr'] = nc_dnstr
+ self.__dict__['update_flags'] = 0x0
+
+ # WARNING:
+ #
+ # There is a very subtle bug here with python
+ # and our NDR code. If you assign directly to
+ # a NDR produced struct (e.g. t_repsFrom.ctr.other_info)
+ # then a proper python GC reference count is not
+ # maintained.
+ #
+ # To work around this we maintain an internal
+ # reference to "dns_name(x)" and "other_info" elements
+ # of repsFromToBlob. This internal reference
+ # is hidden within this class but it is why you
+ # see statements like this below:
+ #
+ # self.__dict__['ndr_blob'].ctr.other_info = \
+ # self.__dict__['other_info'] = drsblobs.repsFromTo1OtherInfo()
+ #
+ # That would appear to be a redundant assignment but
+ # it is necessary to hold a proper python GC reference
+ # count.
+ if ndr_blob is None:
+ self.__dict__['ndr_blob'] = drsblobs.repsFromToBlob()
+ self.__dict__['ndr_blob'].version = 0x1
+ self.__dict__['dns_name1'] = None
+ self.__dict__['dns_name2'] = None
+
+ self.__dict__['ndr_blob'].ctr.other_info = \
+ self.__dict__['other_info'] = drsblobs.repsFromTo1OtherInfo()
+
+ else:
+ self.__dict__['ndr_blob'] = ndr_blob
+ self.__dict__['other_info'] = ndr_blob.ctr.other_info
+
+ if ndr_blob.version == 0x1:
+ self.__dict__['dns_name1'] = ndr_blob.ctr.other_info.dns_name
+ self.__dict__['dns_name2'] = None
+ else:
+ self.__dict__['dns_name1'] = ndr_blob.ctr.other_info.dns_name1
+ self.__dict__['dns_name2'] = ndr_blob.ctr.other_info.dns_name2
+
+ def __str__(self):
+ '''Debug dump string output of class'''
+
+ text = "%s:" % self.__class__.__name__
+ text = text + "\n\tdnstr=%s" % self.nc_dnstr
+ text = text + "\n\tupdate_flags=0x%X" % self.update_flags
+
+ text = text + "\n\tversion=%d" % self.version
+ text = text + "\n\tsource_dsa_obj_guid=%s" % \
+ str(self.source_dsa_obj_guid)
+ text = text + "\n\tsource_dsa_invocation_id=%s" % \
+ str(self.source_dsa_invocation_id)
+ text = text + "\n\ttransport_guid=%s" % \
+ str(self.transport_guid)
+ text = text + "\n\treplica_flags=0x%X" % \
+ self.replica_flags
+ text = text + "\n\tconsecutive_sync_failures=%d" % \
+ self.consecutive_sync_failures
+ text = text + "\n\tlast_success=%s" % \
+ self.last_success
+ text = text + "\n\tlast_attempt=%s" % \
+ self.last_attempt
+ text = text + "\n\tdns_name1=%s" % \
+ str(self.dns_name1)
+ text = text + "\n\tdns_name2=%s" % \
+ str(self.dns_name2)
+ text = text + "\n\tschedule[ "
+ for slot in self.schedule:
+ text = text + "0x%X " % slot
+ text = text + "]"
+
+ return text
+
+ def __setattr__(self, item, value):
+
+ if item in [ 'schedule', 'replica_flags', 'transport_guid',
+ 'source_dsa_obj_guid', 'source_dsa_invocation_id',
+ 'consecutive_sync_failures', 'last_success',
+ 'last_attempt' ]:
+
+ if item in ['replica_flags']:
+ self.__dict__['update_flags'] |= drsuapi.DRSUAPI_DRS_UPDATE_FLAGS
+ elif item in ['schedule']:
+ self.__dict__['update_flags'] |= drsuapi.DRSUAPI_DRS_UPDATE_SCHEDULE
+
+ setattr(self.__dict__['ndr_blob'].ctr, item, value)
+
+ elif item in ['dns_name1']:
+ self.__dict__['dns_name1'] = value
+
+ if self.__dict__['ndr_blob'].version == 0x1:
+ self.__dict__['ndr_blob'].ctr.other_info.dns_name = \
+ self.__dict__['dns_name1']
+ else:
+ self.__dict__['ndr_blob'].ctr.other_info.dns_name1 = \
+ self.__dict__['dns_name1']
+
+ elif item in ['dns_name2']:
+ self.__dict__['dns_name2'] = value
+
+ if self.__dict__['ndr_blob'].version == 0x1:
+ raise AttributeError(item)
+ else:
+ self.__dict__['ndr_blob'].ctr.other_info.dns_name2 = \
+ self.__dict__['dns_name2']
+
+ elif item in ['nc_dnstr']:
+ self.__dict__['nc_dnstr'] = value
+
+ elif item in ['to_be_deleted']:
+ self.__dict__['to_be_deleted'] = value
+
+ elif item in ['version']:
+ raise AttributeError, "Attempt to set readonly attribute %s" % item
+ else:
+ raise AttributeError, "Unknown attribute %s" % item
+
+ self.__dict__['update_flags'] |= drsuapi.DRSUAPI_DRS_UPDATE_ADDRESS
+
+ def __getattr__(self, item):
+ """Overload of RepsFromTo attribute retrieval.
+
+ Allows external code to ignore substructures within the blob
+ """
+ if item in [ 'schedule', 'replica_flags', 'transport_guid',
+ 'source_dsa_obj_guid', 'source_dsa_invocation_id',
+ 'consecutive_sync_failures', 'last_success',
+ 'last_attempt' ]:
+ return getattr(self.__dict__['ndr_blob'].ctr, item)
+
+ elif item in ['version']:
+ return self.__dict__['ndr_blob'].version
+
+ elif item in ['dns_name1']:
+ if self.__dict__['ndr_blob'].version == 0x1:
+ return self.__dict__['ndr_blob'].ctr.other_info.dns_name
+ else:
+ return self.__dict__['ndr_blob'].ctr.other_info.dns_name1
+
+ elif item in ['dns_name2']:
+ if self.__dict__['ndr_blob'].version == 0x1:
+ raise AttributeError(item)
+ else:
+ return self.__dict__['ndr_blob'].ctr.other_info.dns_name2
+
+ elif item in ['to_be_deleted']:
+ return self.__dict__['to_be_deleted']
+
+ elif item in ['nc_dnstr']:
+ return self.__dict__['nc_dnstr']
+
+ elif item in ['update_flags']:
+ return self.__dict__['update_flags']
+
+ raise AttributeError, "Unknwown attribute %s" % item
+
+ def is_modified(self):
+ return (self.update_flags != 0x0)
+
+ def set_unmodified(self):
+ self.__dict__['update_flags'] = 0x0
+
+
+class SiteLink(object):
+ """Class defines a site link found under sites
+ """
+
+ def __init__(self, dnstr):
+ self.dnstr = dnstr
+ self.options = 0
+ self.system_flags = 0
+ self.cost = 0
+ self.schedule = None
+ self.interval = None
+ self.site_list = []
+
+ def __str__(self):
+ '''Debug dump string output of Transport object'''
+
+ text = "%s:\n\tdn=%s" % (self.__class__.__name__, self.dnstr)
+ text = text + "\n\toptions=%d" % self.options
+ text = text + "\n\tsystem_flags=%d" % self.system_flags
+ text = text + "\n\tcost=%d" % self.cost
+ text = text + "\n\tinterval=%s" % self.interval
+
+ if self.schedule is not None:
+ text = text + "\n\tschedule.size=%s" % self.schedule.size
+ text = text + "\n\tschedule.bandwidth=%s" % self.schedule.bandwidth
+ text = text + "\n\tschedule.numberOfSchedules=%s" % \
+ self.schedule.numberOfSchedules
+
+ for i, header in enumerate(self.schedule.headerArray):
+ text = text + "\n\tschedule.headerArray[%d].type=%d" % \
+ (i, header.type)
+ text = text + "\n\tschedule.headerArray[%d].offset=%d" % \
+ (i, header.offset)
+ text = text + "\n\tschedule.dataArray[%d].slots[ " % i
+ for slot in self.schedule.dataArray[i].slots:
+ text = text + "0x%X " % slot
+ text = text + "]"
+
+ for dnstr in self.site_list:
+ text = text + "\n\tsite_list=%s" % dnstr
+ return text
+
+ def load_sitelink(self, samdb):
+ """Given a siteLink object with an prior initialization
+ for the object's DN, search for the DN and load attributes
+ from the samdb.
+ """
+ attrs = [ "options",
+ "systemFlags",
+ "cost",
+ "schedule",
+ "replInterval",
+ "siteList" ]
+ try:
+ res = samdb.search(base=self.dnstr, scope=ldb.SCOPE_BASE,
+ attrs=attrs)
+
+ except ldb.LdbError, (enum, estr):
+ raise Exception("Unable to find SiteLink for (%s) - (%s)" %
+ (self.dnstr, estr))
+
+ msg = res[0]
+
+ if "options" in msg:
+ self.options = int(msg["options"][0])
+
+ if "systemFlags" in msg:
+ self.system_flags = int(msg["systemFlags"][0])
+
+ if "cost" in msg:
+ self.cost = int(msg["cost"][0])
+
+ if "replInterval" in msg:
+ self.interval = int(msg["replInterval"][0])
+
+ if "siteList" in msg:
+ for value in msg["siteList"]:
+ dsdn = dsdb_Dn(samdb, value)
+ dnstr = str(dsdn.dn)
+ if dnstr not in self.site_list:
+ self.site_list.append(dnstr)
+
+ def is_sitelink(self, site1_dnstr, site2_dnstr):
+ """Given a siteLink object, determine if it is a link
+ between the two input site DNs
+ """
+ if site1_dnstr in self.site_list and site2_dnstr in self.site_list:
+ return True
+ return False
+
+
+class VertexColor(object):
+ (unknown, white, black, red) = range(0, 4)
+
+
+class Vertex(object):
+ """Class encapsulation of a Site Vertex in the
+ intersite topology replication algorithm
+ """
+ def __init__(self, site, part):
+ self.site = site
+ self.part = part
+ self.color = VertexColor.unknown
+
+ def color_vertex(self):
+ """Color each vertex to indicate which kind of NC
+ replica it contains
+ """
+ # IF s contains one or more DCs with full replicas of the
+ # NC cr!nCName
+ # SET v.Color to COLOR.RED
+ # ELSEIF s contains one or more partial replicas of the NC
+ # SET v.Color to COLOR.BLACK
+ #ELSE
+ # SET v.Color to COLOR.WHITE
+
+ # set to minimum (no replica)
+ self.color = VertexColor.white
+
+ for dnstr, dsa in self.site.dsa_table.items():
+ rep = dsa.get_current_replica(self.part.nc_dnstr)
+ if rep is None:
+ continue
+
+ # We have a full replica which is the largest
+ # value so exit
+ if not rep.is_partial():
+ self.color = VertexColor.red
+ break
+ else:
+ self.color = VertexColor.black
+
+ def is_red(self):
+ assert(self.color != VertexColor.unknown)
+ return (self.color == VertexColor.red)
+
+ def is_black(self):
+ assert(self.color != VertexColor.unknown)
+ return (self.color == VertexColor.black)
+
+ def is_white(self):
+ assert(self.color != VertexColor.unknown)
+ return (self.color == VertexColor.white)
+
+##################################################
+# Global Functions
+##################################################
+def sort_dsa_by_guid(dsa1, dsa2):
+ return cmp(dsa1.dsa_guid, dsa2.dsa_guid)
diff --git a/source4/scripting/python/samba/ms_display_specifiers.py b/source4/scripting/python/samba/ms_display_specifiers.py
index fd92b20e66..44dfba07b3 100644
--- a/source4/scripting/python/samba/ms_display_specifiers.py
+++ b/source4/scripting/python/samba/ms_display_specifiers.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Create DisplaySpecifiers LDIF (as a string) from the documents provided by
# Microsoft under the WSPP.
#
diff --git a/source4/scripting/python/samba/ms_schema.py b/source4/scripting/python/samba/ms_schema.py
index 64bb28a967..c16693c9b5 100644
--- a/source4/scripting/python/samba/ms_schema.py
+++ b/source4/scripting/python/samba/ms_schema.py
@@ -1,8 +1,19 @@
-#
# create schema.ldif (as a string) from WSPP documentation
#
# based on minschema.py and minschema_wspp
#
+# 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/>.
"""Generate LDIF from WSPP documentation."""
@@ -17,7 +28,7 @@ bitFields = {}
bitFields["searchflags"] = {
'fATTINDEX': 31, # IX
'fPDNTATTINDEX': 30, # PI
- 'fANR': 29, #AR
+ 'fANR': 29, # AR
'fPRESERVEONDELETE': 28, # PR
'fCOPY': 27, # CP
'fTUPLEINDEX': 26, # TP
@@ -74,7 +85,7 @@ multivalued_attrs = set(["auxiliaryclass","maycontain","mustcontain","posssuperi
def __read_folded_line(f, buffer):
""" reads a line from an LDIF file, unfolding it"""
line = buffer
-
+
while True:
l = f.readline()
@@ -87,7 +98,7 @@ def __read_folded_line(f, buffer):
# preserves '\n '
line = line + l
else:
- # non-continued line
+ # non-continued line
if line == "":
line = l
@@ -100,7 +111,7 @@ def __read_folded_line(f, buffer):
# buffer contains the start of the next possibly folded line
buffer = l
break
-
+
return (line, buffer)
@@ -111,13 +122,13 @@ def __read_raw_entries(f):
attr_type_re = re.compile("^([A-Za-z]+[A-Za-z0-9-]*):")
buffer = ""
-
+
while True:
entry = []
-
+
while True:
(l, buffer) = __read_folded_line(f, buffer)
-
+
if l[:1] == "#":
continue
@@ -129,7 +140,7 @@ def __read_raw_entries(f):
if m:
if l[-1:] == "\n":
l = l[:-1]
-
+
entry.append(l)
else:
print >>sys.stderr, "Invalid line: %s" % l,
@@ -159,7 +170,7 @@ def __convert_bitfield(key, value):
value = value.replace("\n ", "")
value = value.replace(" ", "")
-
+
try:
# some attributes already have numeric values
o = int(value)
@@ -175,7 +186,7 @@ def __convert_bitfield(key, value):
def __write_ldif_one(entry):
"""Write out entry as LDIF"""
out = []
-
+
for l in entry:
if isinstance(l[1], str):
vl = [l[1]]
@@ -185,21 +196,21 @@ def __write_ldif_one(entry):
if l[0].lower() == 'omobjectclass':
out.append("%s:: %s" % (l[0], l[1]))
continue
-
+
for v in vl:
out.append("%s: %s" % (l[0], v))
return "\n".join(out)
-
+
def __transform_entry(entry, objectClass):
"""Perform transformations required to convert the LDIF-like schema
file entries to LDIF, including Samba-specific stuff."""
-
+
entry = [l.split(":", 1) for l in entry]
cn = ""
-
+
for l in entry:
key = l[0].lower()
l[1] = l[1].lstrip()
@@ -207,14 +218,14 @@ def __transform_entry(entry, objectClass):
if not cn and key == "cn":
cn = l[1]
-
+
if key in multivalued_attrs:
# unlike LDIF, these are comma-separated
l[1] = l[1].replace("\n ", "")
l[1] = l[1].replace(" ", "")
l[1] = l[1].split(",")
-
+
if key in bitFields:
l[1] = __convert_bitfield(key, l[1])
@@ -232,7 +243,7 @@ def __transform_entry(entry, objectClass):
entry.insert(2, ["objectGUID", str(uuid.uuid4())])
entry.insert(2, ["adminDescription", cn])
entry.insert(2, ["adminDisplayName", cn])
-
+
for l in entry:
key = l[0].lower()
@@ -245,7 +256,7 @@ def __parse_schema_file(filename, objectClass):
"""Load and transform a schema file."""
out = []
-
+
f = open(filename, "rU")
for entry in __read_raw_entries(f):
out.append(__write_ldif_one(__transform_entry(entry, objectClass)))
@@ -258,7 +269,7 @@ def read_ms_schema(attr_file, classes_file, dump_attributes = True, dump_classes
attr_ldif = ""
classes_ldif = ""
-
+
if dump_attributes:
attr_ldif = __parse_schema_file(attr_file, "attributeSchema")
if dump_classes:
@@ -275,5 +286,5 @@ if __name__ == '__main__':
except IndexError:
print >>sys.stderr, "Usage: %s attr-file.txt classes-file.txt" % (sys.argv[0])
sys.exit(1)
-
+
print read_ms_schema(attr_file, classes_file)
diff --git a/source4/scripting/python/samba/ndr.py b/source4/scripting/python/samba/ndr.py
index 112668523f..39e4a482ef 100644
--- a/source4/scripting/python/samba/ndr.py
+++ b/source4/scripting/python/samba/ndr.py
@@ -23,7 +23,7 @@
def ndr_pack(object):
"""Pack a NDR object.
-
+
:param object: Object to pack
:return: String object with marshalled object.
"""
@@ -33,15 +33,16 @@ def ndr_pack(object):
return ndr_pack()
-def ndr_unpack(cls, data):
+def ndr_unpack(cls, data, allow_remaining=False):
"""NDR unpack an object.
:param cls: Class of the object to unpack
:param data: Buffer to unpack
+ :param allow_remaining: allows remaining data at the end (default=False)
:return: Unpacked object
"""
object = cls()
- object.__ndr_unpack__(data)
+ object.__ndr_unpack__(data, allow_remaining=allow_remaining)
return object
diff --git a/source4/scripting/python/samba/netcmd/__init__.py b/source4/scripting/python/samba/netcmd/__init__.py
index aa74f657b2..a3edf50516 100644
--- a/source4/scripting/python/samba/netcmd/__init__.py
+++ b/source4/scripting/python/samba/netcmd/__init__.py
@@ -1,7 +1,6 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
-# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009
+# 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
@@ -21,18 +20,46 @@ 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 net command."""
+ """A samba-tool command."""
- def _get_description(self):
+ 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_"):
@@ -41,17 +68,26 @@ class Command(object):
name = property(_get_name)
- def usage(self, *args):
- parser, _ = self._create_parser()
- parser.print_usage()
+ # synopsis must be defined in all subclasses in order to provide the
+ # command usage
+ synopsis = None
+ takes_args = []
+ takes_options = []
+ takes_optiongroups = {}
- description = property(_get_description)
+ hidden = False
- def _get_synopsis(self):
- ret = self.name
- if self.takes_args:
- ret += " " + " ".join([x.upper() for x in self.takes_args])
- return ret
+ 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'''
@@ -68,36 +104,30 @@ class Command(object):
if isinstance(inner_exception, LdbError):
(ldb_ecode, ldb_emsg) = inner_exception
- print >>sys.stderr, "ERROR(ldb): %s - %s" % (message, ldb_emsg)
+ self.errf.write("ERROR(ldb): %s - %s\n" % (message, ldb_emsg))
elif isinstance(inner_exception, AssertionError):
- print >>sys.stderr, "ERROR(assert): %s" % message
+ self.errf.write("ERROR(assert): %s\n" % message)
force_traceback = True
elif isinstance(inner_exception, RuntimeError):
- print >>sys.stderr, "ERROR(runtime): %s - %s" % (message, evalue)
+ self.errf.write("ERROR(runtime): %s - %s\n" % (message, evalue))
elif type(inner_exception) is Exception:
- print >>sys.stderr, "ERROR(exception): %s - %s" % (message, evalue)
+ self.errf.write("ERROR(exception): %s - %s\n" % (message, evalue))
force_traceback = True
elif inner_exception is None:
- print >>sys.stderr, "ERROR: %s" % (message)
+ self.errf.write("ERROR: %s\n" % (message))
else:
- print >>sys.stderr, "ERROR(%s): %s - %s" % (str(etype), message, evalue)
+ 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)
-
- synopsis = property(_get_synopsis)
-
- outf = sys.stdout
-
- takes_args = []
- takes_options = []
- takes_optiongroups = {}
-
- def _create_parser(self):
- parser = optparse.OptionParser(self.synopsis)
- parser.prog = "net"
+ 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():
@@ -106,10 +136,10 @@ class Command(object):
return parser, optiongroups
def message(self, text):
- print text
+ self.outf.write(text+"\n")
def _run(self, *argv):
- parser, optiongroups = self._create_parser()
+ parser, optiongroups = self._create_parser(argv[0])
opts, args = parser.parse_args(list(argv))
# Filter out options from option groups
args = args[1:]
@@ -119,17 +149,28 @@ class Command(object):
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] not in ("?", "*"):
- min_args += 1
- max_args += 1
- if arg[-1] == "*":
- max_args = -1
- if len(args) < min_args or (max_args != -1 and len(args) > max_args):
- self.usage(*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:
@@ -140,76 +181,51 @@ class Command(object):
"""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 command with subcommands."""
+ """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(subcommand, *args)
- print "Available subcommands:"
- for cmd in self.subcommands:
- print "\t%-20s - %s" % (cmd, self.subcommands[cmd].description)
- if subcommand in [None, 'help', '-h', '--help' ]:
- return 0
- raise CommandError("No such subcommand '%s'" % subcommand)
-
- def usage(self, myname, subcommand=None, *args):
- if subcommand is None or not subcommand in self.subcommands:
- print "Usage: %s (%s) [options]" % (myname,
- " | ".join(self.subcommands.keys()))
- else:
- return self.subcommands[subcommand].usage(*args)
+ 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 netcmd errors'''
+ """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()
-
-
-commands = {}
-from samba.netcmd.pwsettings import cmd_pwsettings
-commands["pwsettings"] = cmd_pwsettings()
-from samba.netcmd.domainlevel import cmd_domainlevel
-commands["domainlevel"] = cmd_domainlevel()
-from samba.netcmd.setpassword import cmd_setpassword
-commands["setpassword"] = cmd_setpassword()
-from samba.netcmd.setexpiry import cmd_setexpiry
-commands["setexpiry"] = cmd_setexpiry()
-from samba.netcmd.enableaccount import cmd_enableaccount
-commands["enableaccount"] = cmd_enableaccount()
-from samba.netcmd.newuser import cmd_newuser
-commands["newuser"] = cmd_newuser()
-from samba.netcmd.netacl import cmd_acl
-commands["acl"] = cmd_acl()
-from samba.netcmd.fsmo import cmd_fsmo
-commands["fsmo"] = cmd_fsmo()
-from samba.netcmd.export import cmd_export
-commands["export"] = cmd_export()
-from samba.netcmd.time import cmd_time
-commands["time"] = cmd_time()
-from samba.netcmd.user import cmd_user
-commands["user"] = cmd_user()
-from samba.netcmd.vampire import cmd_vampire
-commands["vampire"] = cmd_vampire()
-from samba.netcmd.machinepw import cmd_machinepw
-commands["machinepw"] = cmd_machinepw()
-from samba.netcmd.spn import cmd_spn
-commands["spn"] = cmd_spn()
-from samba.netcmd.group import cmd_group
-commands["group"] = cmd_group()
-from samba.netcmd.join import cmd_join
-commands["join"] = cmd_join()
-from samba.netcmd.rodc import cmd_rodc
-commands["rodc"] = cmd_rodc()
-from samba.netcmd.drs import cmd_drs
-commands["drs"] = cmd_drs()
-from samba.netcmd.gpo import cmd_gpo
-commands["gpo2"] = cmd_gpo()
-from samba.netcmd.ldapcmp import cmd_ldapcmp
-commands["ldapcmp"] = cmd_ldapcmp()
diff --git a/source4/scripting/python/samba/netcmd/common.py b/source4/scripting/python/samba/netcmd/common.py
index bd72c8f361..5c0bd95f08 100644
--- a/source4/scripting/python/samba/netcmd/common.py
+++ b/source4/scripting/python/samba/netcmd/common.py
@@ -1,8 +1,7 @@
-#!/usr/bin/env python
-#
# 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
@@ -18,7 +17,55 @@
# 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/source4/scripting/python/samba/netcmd/dbcheck.py b/source4/scripting/python/samba/netcmd/dbcheck.py
new file mode 100644
index 0000000000..e4ec6b303a
--- /dev/null
+++ b/source4/scripting/python/samba/netcmd/dbcheck.py
@@ -0,0 +1,127 @@
+# 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("-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):
+
+ 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
+
+ samdb = SamDB(session_info=system_session(), url=H,
+ credentials=creds, lp=lp)
+
+ 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")
+ 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/source4/scripting/python/samba/netcmd/delegation.py b/source4/scripting/python/samba/netcmd/delegation.py
new file mode 100644
index 0000000000..47dffb07d5
--- /dev/null
+++ b/source4/scripting/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/source4/scripting/python/samba/netcmd/dns.py b/source4/scripting/python/samba/netcmd/dns.py
new file mode 100644
index 0000000000..c00d17ad72
--- /dev/null
+++ b/source4/scripting/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/source4/scripting/python/samba/netcmd/domain.py b/source4/scripting/python/samba/netcmd/domain.py
new file mode 100644
index 0000000000..4ba305c271
--- /dev/null
+++ b/source4/scripting/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/source4/scripting/python/samba/netcmd/domainlevel.py b/source4/scripting/python/samba/netcmd/domainlevel.py
deleted file mode 100644
index 3d50ccea78..0000000000
--- a/source4/scripting/python/samba/netcmd/domainlevel.py
+++ /dev/null
@@ -1,247 +0,0 @@
-#!/usr/bin/env python
-#
-# Raises domain and forest function levels
-#
-# Copyright Matthias Dieter Wallnoefer 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/>.
-#
-
-# Notice: At the moment we have some more checks to do here on the special
-# attributes (consider attribute "msDS-Behavior-Version). This is due to the
-# fact that we on s4 LDB don't implement their change policy (only certain
-# values, only increments possible...) yet.
-
-import samba.getopt as options
-import ldb
-
-from samba.auth import system_session
-from samba.netcmd import (
- Command,
- CommandError,
- Option,
- )
-from samba.samdb import SamDB
-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,
- )
-
-class cmd_domainlevel(Command):
- """Raises domain and forest function levels"""
-
- synopsis = "(show | raise <options>)"
-
- takes_optiongroups = {
- "sambaopts": options.SambaOptions,
- "credopts": options.CredentialsOptions,
- "versionopts": options.VersionOptions,
- }
-
- takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
- Option("--quiet", help="Be quiet", action="store_true"),
- Option("--forest", type="choice", choices=["2003", "2008", "2008_R2"],
- help="The forest function level (2003 | 2008 | 2008_R2)"),
- Option("--domain", 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=None, domain=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,CN=Configuration," + domain_dn,
- 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,CN=Configuration," + domain_dn,
- 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 is not None:
- if domain == "2003":
- new_level_domain = DS_DOMAIN_FUNCTION_2003
- elif domain == "2008":
- new_level_domain = DS_DOMAIN_FUNCTION_2008
- elif domain == "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 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,CN=Configuration," + domain_dn)
- 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,CN=Configuration," + domain_dn)
- 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 is not None:
- if forest == "2003":
- new_level_forest = DS_DOMAIN_FUNCTION_2003
- elif forest == "2008":
- new_level_forest = DS_DOMAIN_FUNCTION_2008
- elif forest == "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 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,CN=Configuration,"
- + domain_dn)
- 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("Wrong argument '%s'!" % subcommand)
diff --git a/source4/scripting/python/samba/netcmd/drs.py b/source4/scripting/python/samba/netcmd/drs.py
index 7dea9de856..074b7af7e4 100644
--- a/source4/scripting/python/samba/netcmd/drs.py
+++ b/source4/scripting/python/samba/netcmd/drs.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# implement samba_tool drs commands
#
# Copyright Andrew Tridgell 2010
@@ -37,17 +35,11 @@ import common
def drsuapi_connect(ctx):
'''make a DRSUAPI connection to the server'''
- binding_options = "seal"
- if ctx.lp.get("log level") >= 5:
- binding_options += ",print"
- binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
try:
- ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
- (ctx.drsuapi_handle, ctx.bind_supported_extensions) = drs_utils.drs_DsBind(ctx.drsuapi)
+ (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:
@@ -57,7 +49,6 @@ def samdb_connect(ctx):
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
@@ -66,6 +57,7 @@ def drs_errmsg(werr):
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:
@@ -73,6 +65,7 @@ def attr_default(msg, attrname, default):
return default
+
def drs_parse_ntds_dn(ntds_dn):
'''parse a NTDS DN returning a site and server'''
a = ntds_dn.split(',')
@@ -83,16 +76,13 @@ def drs_parse_ntds_dn(ntds_dn):
return (site, server)
-def get_dsServiceName(samdb):
- '''get the NTDS DN from the rootDSE'''
- res = samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
- return res[0]["dsServiceName"][0]
+
class cmd_drs_showrepl(Command):
- """show replication status"""
+ """Show replication status."""
- synopsis = "%prog drs showrepl <DC>"
+ synopsis = "%prog [<DC>] [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -128,7 +118,6 @@ class cmd_drs_showrepl(Command):
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):
@@ -142,7 +131,7 @@ class cmd_drs_showrepl(Command):
samdb_connect(self)
# show domain information
- ntds_dn = get_dsServiceName(self.samdb)
+ 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)
@@ -181,10 +170,13 @@ class cmd_drs_showrepl(Command):
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" % server_dns)
+ 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)))
@@ -200,10 +192,11 @@ class cmd_drs_showrepl(Command):
self.message("\t\t\t%s" % s)
+
class cmd_drs_kcc(Command):
- """trigger knowledge consistency center run"""
+ """Trigger knowledge consistency center run."""
- synopsis = "%prog drs kcc <DC>"
+ synopsis = "%prog [<DC>] [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -234,10 +227,45 @@ class cmd_drs_kcc(Command):
+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"""
+ """Replicate a naming context between two DCs."""
- synopsis = "%prog drs replicate <DEST_DC> <SOURCE_DC> <NC>"
+ synopsis = "%prog <destinationDC> <sourceDC> <NC> [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -250,24 +278,32 @@ class cmd_drs_replicate(Command):
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,
- sambaopts=None,
- credopts=None, versionopts=None, server=None):
+ 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)))" % (SOURCE_DC,
- SOURCE_DC),
+ 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)
@@ -279,34 +315,33 @@ class cmd_drs_replicate(Command):
if len(msg) == 0:
raise CommandError("Failed to find source NTDS DN %s" % SOURCE_DC)
source_dsa_guid = msg[0]['objectGUID'][0]
- options = int(attr_default(msg, 'options', 0))
+ dsa_options = int(attr_default(msg, 'options', 0))
- nc = drsuapi.DsReplicaObjectIdentifier()
- nc.dn = NC
- req1 = drsuapi.DsReplicaSyncRequest1()
- req1.naming_context = nc;
- req1.options = 0
- if not (options & dsdb.DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL):
- req1.options |= drsuapi.DRSUAPI_DRS_WRIT_REP
+ req_options = 0
+ if not (dsa_options & dsdb.DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL):
+ req_options |= drsuapi.DRSUAPI_DRS_WRIT_REP
if add_ref:
- req1.options |= drsuapi.DRSUAPI_DRS_ADD_REF
+ req_options |= drsuapi.DRSUAPI_DRS_ADD_REF
if sync_forced:
- req1.options |= drsuapi.DRSUAPI_DRS_SYNC_FORCED
- req1.source_dsa_guid = misc.GUID(source_dsa_guid)
+ 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:
- self.drsuapi.DsReplicaSync(self.drsuapi_handle, 1, req1)
- except Exception, estr:
+ 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"""
+ """Show DRS capabilities of a server."""
- synopsis = "%prog drs bind <DC>"
+ synopsis = "%prog [<DC>] [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -334,46 +369,46 @@ class cmd_drs_bind(Command):
(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_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")
+ ("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")]
+ ("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:")
@@ -403,11 +438,9 @@ class cmd_drs_bind(Command):
class cmd_drs_options(Command):
- """query or change 'options' for NTDS Settings object of a domain controller"""
+ """Query or change 'options' for NTDS Settings object of a Domain Controller."""
- synopsis = ("%prog drs options <DC>"
- " [--dsa-option={+|-}IS_GC | {+|-}DISABLE_INBOUND_REPL"
- " |{+|-}DISABLE_OUTBOUND_REPL | {+|-}DISABLE_NTDSCONN_XLATE]")
+ synopsis = "%prog [<DC>] [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -415,10 +448,11 @@ class cmd_drs_options(Command):
"credopts": options.CredentialsOptions,
}
- takes_args = ["DC"]
+ takes_args = ["DC?"]
takes_options = [
- Option("--dsa-option", help="DSA option to enable/disable", type="str"),
+ 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,
@@ -426,7 +460,7 @@ class cmd_drs_options(Command):
"DISABLE_OUTBOUND_REPL": 0x00000004,
"DISABLE_NTDSCONN_XLATE": 0x00000008}
- def run(self, DC, dsa_option=None,
+ def run(self, DC=None, dsa_option=None,
sambaopts=None, credopts=None, versionopts=None):
self.lp = sambaopts.get_loadparm()
@@ -437,7 +471,7 @@ class cmd_drs_options(Command):
samdb_connect(self)
- ntds_dn = get_dsServiceName(self.samdb)
+ 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])
@@ -467,7 +501,7 @@ class cmd_drs_options(Command):
class cmd_drs(SuperCommand):
- """DRS commands"""
+ """Directory Replication Services (DRS) management."""
subcommands = {}
subcommands["bind"] = cmd_drs_bind()
diff --git a/source4/scripting/python/samba/netcmd/dsacl.py b/source4/scripting/python/samba/netcmd/dsacl.py
index 58a3552687..28aa843adb 100644
--- a/source4/scripting/python/samba/netcmd/dsacl.py
+++ b/source4/scripting/python/samba/netcmd/dsacl.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Manipulate ACLs on directory objects
#
# Copyright (C) Nadezhda Ivanova <nivanova@samba.org> 2010
@@ -44,10 +42,12 @@ from samba.netcmd import (
Option,
)
-class cmd_ds_acl_set(Command):
- """Modify access list on a directory object"""
- synopsis = "set --objectdn=objectdn --car=control right --action=[deny|allow] --trusteedn=trustee-dn"
+
+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 = {
@@ -57,8 +57,8 @@ class cmd_ds_acl_set(Command):
}
takes_options = [
- Option("--host", help="LDB URL for database or target server",
- type=str),
+ 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",
@@ -132,11 +132,11 @@ class cmd_ds_acl_set(Command):
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))
- print "new descriptor for %s:" % object_dn
- print desc_sddl
+ self.outf.write("new descriptor for %s:\n" % object_dn)
+ self.outf.write(desc_sddl + "\n")
def run(self, car, action, objectdn, trusteedn, sddl,
- host=None, credopts=None, sambaopts=None, versionopts=None):
+ H=None, credopts=None, sambaopts=None, versionopts=None):
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp)
@@ -144,7 +144,7 @@ class cmd_ds_acl_set(Command):
or objectdn is None or trusteedn is None):
return self.usage()
- samdb = SamDB(url=host, session_info=system_session(),
+ 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,
@@ -175,8 +175,8 @@ class cmd_ds_acl_set(Command):
self.print_new_acl(samdb, objectdn)
-class cmd_ds_acl(SuperCommand):
- """DS ACLs manipulation"""
+class cmd_dsacl(SuperCommand):
+ """DS ACLs manipulation."""
subcommands = {}
- subcommands["set"] = cmd_ds_acl_set()
+ subcommands["set"] = cmd_dsacl_set()
diff --git a/source4/scripting/python/samba/netcmd/enableaccount.py b/source4/scripting/python/samba/netcmd/enableaccount.py
deleted file mode 100644
index 3ceddb3fd9..0000000000
--- a/source4/scripting/python/samba/netcmd/enableaccount.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python
-#
-# Enables an user account on 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.auth import system_session
-from samba.netcmd import Command, CommandError, Option
-from samba.samdb import SamDB
-
-class cmd_enableaccount(Command):
- """Enables a user"""
-
- synopsis = "enableaccount [username] [options]"
-
- takes_optiongroups = {
- "sambaopts": options.SambaOptions,
- "versionopts": options.VersionOptions,
- "credopts": options.CredentialsOptions,
- }
-
- takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
- 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))" % (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)
- samdb.enable_account(filter)
diff --git a/source4/scripting/python/samba/netcmd/export.py b/source4/scripting/python/samba/netcmd/export.py
deleted file mode 100644
index 649a2621b1..0000000000
--- a/source4/scripting/python/samba/netcmd/export.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-#
-# Export keytab
-#
-# 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,
- SuperCommand,
- )
-
-class cmd_export_keytab(Command):
- """Dumps kerberos keys of the domain into a keytab"""
- synopsis = "%prog export keytab <keytab>"
-
- takes_optiongroups = {
- "sambaopts": options.SambaOptions,
- "credopts": options.CredentialsOptions,
- "versionopts": options.VersionOptions,
- }
-
- takes_options = [
- ]
-
- takes_args = ["keytab"]
-
- def run(self, keytab, credopts=None, sambaopts=None, versionopts=None):
- lp = sambaopts.get_loadparm()
- net = Net(None, lp, server=credopts.ipaddress)
- net.export_keytab(keytab=keytab)
-
-
-class cmd_export(SuperCommand):
- """Dumps the sam of the domain we are joined to [server connection needed]"""
-
- subcommands = {}
- subcommands["keytab"] = cmd_export_keytab()
-
diff --git a/source4/scripting/python/samba/netcmd/fsmo.py b/source4/scripting/python/samba/netcmd/fsmo.py
index 0c8d17c74f..c938c915fa 100644
--- a/source4/scripting/python/samba/netcmd/fsmo.py
+++ b/source4/scripting/python/samba/netcmd/fsmo.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Changes a FSMO role owner
#
# Copyright Nadezhda Ivanova 2009
@@ -27,14 +25,53 @@ from samba.auth import system_session
from samba.netcmd import (
Command,
CommandError,
+ SuperCommand,
Option,
)
from samba.samdb import SamDB
-class cmd_fsmo(Command):
- """Makes the targer DC transfer or seize a fsmo role [server connection needed]"""
+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 = "(show | transfer <options> | seize <options>)"
+ synopsis = "%prog [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -43,7 +80,8 @@ class cmd_fsmo(Command):
}
takes_options = [
- Option("--host", help="LDB URL for database or target server", type=str),
+ 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
@@ -55,40 +93,7 @@ infrastructure=InfrastructureMasterRole\n
all=all of the above"""),
]
- takes_args = ["subcommand"]
-
- def transfer_role(self, 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.")
- samdb.modify(m)
+ takes_args = []
def seize_role(self, role, samdb, force):
res = samdb.search("",
@@ -96,6 +101,11 @@ all=all of the above"""),
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)
@@ -113,35 +123,72 @@ all=all of the above"""),
if force is None:
self.message("Attempting transfer...")
try:
- self.transfer_role(role, samdb)
- except LdbError, (num, _):
+ transfer_role(self.outf, role, samdb)
+ except CommandError:
#transfer failed, use the big axe...
- self.message("Transfer unsuccessfull, seizing...")
+ self.message("Transfer unsuccessful, seizing...")
m["fSMORoleOwner"]= ldb.MessageElement(
serviceName, ldb.FLAG_MOD_REPLACE,
"fSMORoleOwner")
- samdb.modify(m)
- else:
- self.message("Transfer succeeded.")
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, subcommand, force=None, host=None, role=None,
+ 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=host, session_info=system_session(),
+ 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,CN=Configuration," + domain_dn
- self.schema_dn = "CN=Schema,CN=Configuration," + 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,
@@ -169,29 +216,62 @@ all=all of the above"""),
assert len(res) == 1
self.ridMaster = res[0]["fSMORoleOwner"][0]
- if subcommand == "show":
- 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)
- elif subcommand == "transfer":
- if role == "all":
- self.transfer_role("rid", samdb)
- self.transfer_role("pdc", samdb)
- self.transfer_role("naming", samdb)
- self.transfer_role("infrastructure", samdb)
- self.transfer_role("schema", samdb)
- else:
- self.transfer_role(role, samdb)
- elif subcommand == "seize":
- 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)
+ 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:
- raise CommandError("Wrong argument '%s'!" % subcommand)
+ 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/source4/scripting/python/samba/netcmd/gpo.py b/source4/scripting/python/samba/netcmd/gpo.py
index 19007b361c..23b562eb63 100644
--- a/source4/scripting/python/samba/netcmd/gpo.py
+++ b/source4/scripting/python/samba/netcmd/gpo.py
@@ -1,8 +1,7 @@
-#!/usr/bin/env python
-#
# 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
#
@@ -20,6 +19,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+import os
import samba.getopt as options
import ldb
@@ -31,12 +31,20 @@ from samba.netcmd import (
SuperCommand,
)
from samba.samdb import SamDB
-from samba import drs_utils, nttime2string, dsdb, dcerpc
-from samba.dcerpc import misc
+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'''
@@ -55,18 +63,24 @@ def attr_default(msg, attrname, default):
return default
-def flags_string(flags, value):
- '''return a set of flags as a string'''
- if value == 0:
- return 'NONE'
- ret = ''
- for (str, val) in flags:
- if val & value:
- ret += str + ' '
- value &= ~val
- if value != 0:
- ret += '0x%08x' % value
- return ret.rstrip()
+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):
@@ -83,10 +97,200 @@ def parse_gplink(gplink):
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"""
+ """List all GPOs."""
- synopsis = "%prog gpo listall"
+ synopsis = "%prog [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -95,80 +299,70 @@ class cmd_listall(Command):
}
takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str)
+ 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, server=None):
+ def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
- self.url = H
self.lp = sambaopts.get_loadparm()
-
self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
- samdb_connect(self)
+ self.url = dc_url(self.lp, self.creds, H)
- policies_dn = self.samdb.get_default_basedn()
- policies_dn.add_child(ldb.Dn(self.samdb, "CN=Policies,CN=System"))
+ samdb_connect(self)
- gpo_flags = [
- ("GPO_FLAG_USER_DISABLE", dsdb.GPO_FLAG_USER_DISABLE ),
- ( "GPO_FLAG_MACHINE_DISABLE", dsdb.GPO_FLAG_MACHINE_DISABLE ) ]
+ msg = get_gpo_info(self.samdb, None)
- try:
- msg = self.samdb.search(base=policies_dn, scope=ldb.SCOPE_ONELEVEL,
- expression="(objectClass=groupPolicyContainer)",
- attrs=['nTSecurityDescriptor', 'versionNumber', 'flags', 'name', 'displayName', 'gPCFileSysPath'])
- except Exception, e:
- raise CommandError("Failed to list policies in %s" % policies_dn, e)
for m in msg:
- print("GPO : %s" % m['name'][0])
- print("display name : %s" % m['displayName'][0])
- print("path : %s" % m['gPCFileSysPath'][0])
- print("dn : %s" % m.dn)
- print("version : %s" % attr_default(m, 'version', '0'))
- print("flags : %s" % flags_string(gpo_flags, int(attr_default(m, 'flags', 0))))
- print("")
+ 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 a user"""
+ """List GPOs for an account."""
- synopsis = "%prog gpo list <username>"
+ synopsis = "%prog <username> [options]"
+ takes_args = ['username']
takes_optiongroups = {
"sambaopts": options.SambaOptions,
"versionopts": options.VersionOptions,
"credopts": options.CredentialsOptions,
}
- takes_args = [ 'username' ]
-
takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str)
+ 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, server=None):
+ def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
- self.url = H
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:
- user_dn = self.samdb.search(expression='(&(samAccountName=%s)(objectclass=User))' % username)[0].dn
- except Exception, e:
- raise CommandError("Failed to find user %s" % username, e)
+ 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, e:
- raise CommandError("Failed to find objectClass for user %s" % username, e)
+ except Exception:
+ raise CommandError("Failed to find objectClass for user %s" % username)
session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
AUTH_SESSION_INFO_AUTHENTICATED )
@@ -197,22 +391,25 @@ class cmd_list(Command):
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=['flags', 'ntSecurityDescriptor'])
+ 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:
- print "Failed to fetch gpo object %s" % g['dn']
+ self.outf.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
+ g['dn'])
continue
- secdesc_ndr = gmsg[0]['ntSecurityDescriptor'][0]
- secdesc = ndr_unpack(dcerpc.security.descriptor, secdesc_ndr)
-
try:
samba.security.access_check(secdesc, token,
- dcerpc.security.SEC_STD_READ_CONTROL |
- dcerpc.security.SEC_ADS_LIST |
- dcerpc.security.SEC_ADS_READ_PROP)
+ security.SEC_STD_READ_CONTROL |
+ security.SEC_ADS_LIST |
+ security.SEC_ADS_READ_PROP)
except RuntimeError:
- print "Failed access check on %s" % msg.dn
+ self.outf.write("Failed access check on %s\n" % msg.dn)
continue
# check the flags on the GPO
@@ -221,7 +418,7 @@ class cmd_list(Command):
continue
if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
continue
- gpos.append(g)
+ gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
# check if this blocks inheritance
gpoptions = int(attr_default(msg, 'gPOptions', 0))
@@ -232,14 +429,749 @@ class cmd_list(Command):
break
dn = dn.parent()
- print("GPO's for user %s" % username)
+ 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:
- print("\t%s" % g['dn'])
+ 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):
- """GPO commands"""
+ """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/source4/scripting/python/samba/netcmd/group.py b/source4/scripting/python/samba/netcmd/group.py
index 620a7be866..731d4c1564 100644
--- a/source4/scripting/python/samba/netcmd/group.py
+++ b/source4/scripting/python/samba/netcmd/group.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Adds a new user to a Samba4 server
# Copyright Jelmer Vernooij 2008
#
@@ -22,6 +20,8 @@
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
@@ -40,9 +40,33 @@ distribution_group = dict({"Domain": GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP, "Glo
class cmd_group_add(Command):
- """Creates a new group"""
+ """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
- synopsis = "%prog group add [options] <groupname>"
+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,
@@ -51,10 +75,11 @@ class cmd_group_add(Command):
}
takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
+ 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),
+ 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"],
@@ -71,9 +96,9 @@ class cmd_group_add(Command):
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)
+ gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
else:
- gtype = distribution_group.get(group_scope, GTYPE_DISTRIBUTION_GLOBAL_GROUP)
+ gtype = distribution_group.get(group_scope, GTYPE_DISTRIBUTION_GLOBAL_GROUP)
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp, fallback_machine=True)
@@ -84,13 +109,32 @@ class cmd_group_add(Command):
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):
- """Delete a group"""
+ """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
- synopsis = "%prog group delete <groupname>"
+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,
@@ -99,7 +143,8 @@ class cmd_group_delete(Command):
}
takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
]
takes_args = ["groupname"]
@@ -114,13 +159,30 @@ class cmd_group_delete(Command):
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 (comma-separated list of) group members"""
+ """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.
- synopsis = "%prog group addmembers <groupname> <listofmembers>"
+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,
@@ -129,7 +191,8 @@ class cmd_group_add_members(Command):
}
takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
]
takes_args = ["groupname", "listofmembers"]
@@ -143,15 +206,35 @@ class cmd_group_add_members(Command):
try:
samdb = SamDB(url=H, session_info=system_session(),
credentials=creds, lp=lp)
- samdb.add_remove_group_members(groupname, listofmembers, add_members_operation=True)
+ groupmembers = listofmembers.split(',')
+ samdb.add_remove_group_members(groupname, groupmembers,
+ add_members_operation=True)
except Exception, e:
- raise CommandError('Failed to add members "%s" to group "%s"' % (listofmembers, groupname), 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 (comma-separated list of) group members"""
+ """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 group removemembers <groupname> <listofmembers>"
+ synopsis = "%prog <groupname> <listofmembers> [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -160,7 +243,8 @@ class cmd_group_remove_members(Command):
}
takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
]
takes_args = ["groupname", "listofmembers"]
@@ -174,16 +258,119 @@ class cmd_group_remove_members(Command):
try:
samdb = SamDB(url=H, session_info=system_session(),
credentials=creds, lp=lp)
- samdb.add_remove_group_members(groupname, listofmembers, add_members_operation=False)
+ 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"""
+ """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/source4/scripting/python/samba/netcmd/join.py b/source4/scripting/python/samba/netcmd/join.py
deleted file mode 100644
index 507253ab81..0000000000
--- a/source4/scripting/python/samba/netcmd/join.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env python
-#
-# joins
-#
-# Copyright Jelmer Vernooij 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.net import Net, LIBNET_JOIN_AUTOMATIC
-from samba.netcmd import Command, CommandError, Option
-from samba.dcerpc.misc import SEC_CHAN_WKSTA, SEC_CHAN_BDC
-from samba.join import join_RODC, join_DC
-
-class cmd_join(Command):
- """Joins domain as either member or backup domain controller [server connection needed]"""
-
- synopsis = "%prog join <dnsdomain> [DC | RODC | MEMBER] [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),
- ]
-
- takes_args = ["domain", "role?"]
-
- def run(self, domain, role=None, sambaopts=None, credopts=None,
- versionopts=None, server=None, site=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":
- secure_channel_type = SEC_CHAN_WKSTA
- elif role == "DC":
- join_DC(server=server, creds=creds, lp=lp, domain=domain,
- site=site, netbios_name=netbios_name)
- return
- elif role == "RODC":
- join_RODC(server=server, creds=creds, lp=lp, domain=domain,
- site=site, netbios_name=netbios_name)
- return
- else:
- raise CommandError("Invalid role %s (possible values: MEMBER, BDC, RODC)" % role)
-
- (join_password, sid, domain_name) = net.join(domain,
- netbios_name,
- secure_channel_type,
- LIBNET_JOIN_AUTOMATIC)
-
- self.outf.write("Joined domain %s (%s)\n" % (domain_name, sid))
diff --git a/source4/scripting/python/samba/netcmd/ldapcmp.py b/source4/scripting/python/samba/netcmd/ldapcmp.py
index 160aa31258..8398205e4b 100755..100644
--- a/source4/scripting/python/samba/netcmd/ldapcmp.py
+++ b/source4/scripting/python/samba/netcmd/ldapcmp.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# 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
@@ -30,14 +28,13 @@ import sys
import samba
import samba.getopt as options
from samba import Ldb
-from samba.ndr import ndr_pack, ndr_unpack
+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,
- SuperCommand,
)
global summary
@@ -47,7 +44,8 @@ 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"):
+ view="section", base="", scope="SUB",
+ outf=sys.stdout, errf=sys.stderr):
ldb_options = []
samdb_url = host
if not "://" in host:
@@ -58,6 +56,8 @@ class LDAPBase(object):
# 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,
@@ -71,7 +71,10 @@ class LDAPBase(object):
self.view = view
self.verbose = verbose
self.host = host
- self.base_dn = self.find_basedn()
+ 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(",", ".")
@@ -82,11 +85,15 @@ class LDAPBase(object):
# 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:
- print "\n* Place-holders for %s:" % self.host
- print 4*" " + "${DOMAIN_DN} => %s" % self.base_dn
- print 4*" " + "${DOMAIN_NETBIOS} => %s" % self.domain_netbios
- print 4*" " + "${SERVER_NAME} => %s" % self.server_names
- print 4*" " + "${DOMAIN_NAME} => %s" % self.domain_name
+ 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)
@@ -95,7 +102,7 @@ class LDAPBase(object):
def find_servers(self):
"""
"""
- res = self.ldb.search(base="OU=Domain Controllers,%s" % self.base_dn, \
+ 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 = []
@@ -104,19 +111,13 @@ class LDAPBase(object):
return srv
def find_netbios(self):
- res = self.ldb.search(base="CN=Partitions,CN=Configuration,%s" % self.base_dn, \
+ 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 find_basedn(self):
- res = self.ldb.search(base="", expression="(objectClass=*)", scope=SCOPE_BASE,
- attrs=["defaultNamingContext"])
- assert len(res) == 1
- return res[0]["defaultNamingContext"][0]
-
def object_exists(self, object_dn):
res = None
try:
@@ -252,13 +253,13 @@ class LDAPBase(object):
""" Build dictionary that maps GUID to 'name' attribute found in Schema or Extended-Rights.
"""
self.guid_map = {}
- res = self.ldb.search(base="cn=schema,cn=configuration,%s" % self.base_dn, \
- expression="(schemaIdGuid=*)", scope=SCOPE_SUBTREE, attrs=["schemaIdGuid", "name"])
+ 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,cn=configuration,%s" % self.base_dn, \
- expression="(rightsGuid=*)", scope=SCOPE_SUBTREE, attrs=["rightsGuid", "name"])
+ 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]
@@ -266,8 +267,8 @@ class LDAPBase(object):
""" Build dictionary that maps GUID to 'name' attribute found in Schema or Extended-Rights.
"""
self.sid_map = {}
- res = self.ldb.search(base="%s" % self.base_dn, \
- expression="(objectSid=*)", scope=SCOPE_SUBTREE, attrs=["objectSid", "sAMAccountName"])
+ 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]
@@ -275,7 +276,9 @@ class LDAPBase(object):
pass
class Descriptor(object):
- def __init__(self, connection, dn):
+ 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)
@@ -415,7 +418,10 @@ class Descriptor(object):
return (self_aces == [] and other_aces == [], res)
class LDAPObject(object):
- def __init__(self, connection, dn, summary):
+ 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
@@ -433,11 +439,15 @@ class LDAPObject(object):
# 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",]
+ "prefixMap"]
+ if filter_list:
+ self.ignore_attributes += filter_list
+
self.dn_attributes = []
self.domain_attributes = []
self.servername_attributes = []
@@ -493,7 +503,7 @@ class LDAPObject(object):
Log on the screen if there is no --quiet oprion set
"""
if not self.quiet:
- print msg
+ self.outf.write(msg+"\n")
def fix_dn(self, s):
res = "%s" % s
@@ -533,8 +543,8 @@ class LDAPObject(object):
return self.cmp_attrs(other)
def cmp_desc(self, other):
- d1 = Descriptor(self.con, self.dn)
- d2 = Descriptor(other.con, other.dn)
+ 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":
@@ -665,7 +675,11 @@ class LDAPObject(object):
class LDAPBundel(object):
- def __init__(self, connection, context, dn_list=None):
+
+ 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
@@ -677,9 +691,10 @@ class LDAPBundel(object):
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"]:
+ elif context.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA", "DNSDOMAIN", "DNSFOREST"]:
self.context = context.upper()
self.dn_list = self.get_dn_list(context)
else:
@@ -704,7 +719,7 @@ class LDAPBundel(object):
Log on the screen if there is no --quiet oprion set
"""
if not self.quiet:
- print msg
+ self.outf.write(msg+"\n")
def update_size(self):
self.size = len(self.dn_list)
@@ -754,7 +769,9 @@ class LDAPBundel(object):
try:
object1 = LDAPObject(connection=self.con,
dn=self.dn_list[index],
- summary=self.summary)
+ 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] )
@@ -763,7 +780,9 @@ class LDAPBundel(object):
try:
object2 = LDAPObject(connection=other.con,
dn=other.dn_list[index],
- summary=other.summary)
+ 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] )
@@ -796,11 +815,15 @@ class LDAPBundel(object):
Parse all DNs and filter those that are 'strange' or abnormal.
"""
if context.upper() == "DOMAIN":
- search_base = "%s" % self.con.base_dn
+ search_base = self.con.base_dn
elif context.upper() == "CONFIGURATION":
- search_base = "CN=Configuration,%s" % self.con.base_dn
+ search_base = self.con.config_dn
elif context.upper() == "SCHEMA":
- search_base = "CN=Schema,CN=Configuration,%s" % self.con.base_dn
+ 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:
@@ -814,9 +837,11 @@ class LDAPBundel(object):
self.search_scope = SCOPE_ONELEVEL
else:
raise StandardError("Wrong 'scope' given. Choose from: SUB, ONE, BASE")
- if not self.search_base.upper().endswith(search_base.upper()):
- raise StandardError("Invalid search base specified: %s" % self.search_base)
- res = self.con.ldb.search(base=self.search_base, scope=self.search_scope, attrs=["dn"])
+ 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())
#
@@ -837,9 +862,16 @@ class LDAPBundel(object):
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 = "ldapcmp URL1 URL2 <domain|configuration|schema> [options]"
+ """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,
@@ -868,21 +900,31 @@ class cmd_ldapcmp(Command):
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", credopts=None, sambaopts=None, versionopts=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()
- creds = credopts.get_credentials(lp, fallback_machine=True)
+
+ 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 not creds.authentication_requested():
+ 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
@@ -899,7 +941,7 @@ class cmd_ldapcmp(Command):
for c in [context1, context2, context3]:
if c is None:
continue
- if not c.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA"]:
+ if not c.upper() in ["DOMAIN", "CONFIGURATION", "SCHEMA", "DNSDOMAIN", "DNSFOREST"]:
raise CommandError("Incorrect argument: %s" % c)
contexts.append(c.upper())
@@ -914,33 +956,40 @@ class cmd_ldapcmp(Command):
con1 = LDAPBase(URL1, creds, lp,
two=two, quiet=quiet, descriptor=descriptor, sort_aces=sort_aces,
- verbose=verbose,view=view, base=base, scope=scope)
+ 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)
+ 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:
- print "\n* Comparing [%s] context..." % context
+ self.outf.write("\n* Comparing [%s] context...\n" % context)
- b1 = LDAPBundel(con1, context=context)
- b2 = LDAPBundel(con2, context=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:
- print "\n* Result for [%s]: SUCCESS" % context
+ self.outf.write("\n* Result for [%s]: SUCCESS\n" %
+ context)
else:
if not quiet:
- print "\n* Result for [%s]: FAILURE" % context
+ 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"] = []
- print "\nSUMMARY"
- print "---------"
+ 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
diff --git a/source4/scripting/python/samba/netcmd/machinepw.py b/source4/scripting/python/samba/netcmd/machinepw.py
deleted file mode 100644
index d822b22794..0000000000
--- a/source4/scripting/python/samba/netcmd/machinepw.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python
-#
-# Machine passwords
-# Copyright Jelmer Vernooij 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 import Ldb
-from samba.auth import system_session
-from samba.netcmd import Command, CommandError
-
-
-class cmd_machinepw(Command):
- """Gets a machine password out of our SAM"""
-
- synopsis = "%prog machinepw <accountname>"
-
- takes_optiongroups = {
- "sambaopts": options.SambaOptions,
- "versionopts": options.VersionOptions,
- "credopts": options.CredentialsOptions,
- }
-
- takes_args = ["secret"]
-
- def run(self, secret, sambaopts=None, credopts=None, versionopts=None):
- lp = sambaopts.get_loadparm()
- creds = credopts.get_credentials(lp, fallback_machine=True)
- url = lp.get("secrets database")
- secretsdb = Ldb(url=url, session_info=system_session(),
- credentials=creds, lp=lp)
-
- result = secretsdb.search(attrs=["secret"],
- expression="(&(objectclass=primaryDomain)(samaccountname=%s))" % secret)
-
- if len(result) != 1:
- raise CommandError("search returned %d records, expected 1" % len(result))
-
- self.outf.write("%s\n" % result[0]["secret"])
diff --git a/source4/scripting/python/samba/netcmd/main.py b/source4/scripting/python/samba/netcmd/main.py
new file mode 100644
index 0000000000..5f78823588
--- /dev/null
+++ b/source4/scripting/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/source4/scripting/python/samba/netcmd/newuser.py b/source4/scripting/python/samba/netcmd/newuser.py
deleted file mode 100644
index 3581340577..0000000000
--- a/source4/scripting/python/samba/netcmd/newuser.py
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/env python
-#
-# 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, CommandError, Option
-import ldb
-
-from getpass import getpass
-from samba.auth import system_session
-from samba.samdb import SamDB
-
-class cmd_newuser(Command):
- """Creates a new user"""
-
- synopsis = "newuser [options] <username> [<password>]"
-
- takes_optiongroups = {
- "sambaopts": options.SambaOptions,
- "versionopts": options.VersionOptions,
- "credopts": options.CredentialsOptions,
- }
-
- takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
- Option("--must-change-at-next-login",
- help="Force password to be changed on next login",
- 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),
- ]
-
- takes_args = ["username", "password?"]
-
- def run(self, username, password=None, credopts=None, sambaopts=None,
- versionopts=None, H=None, must_change_at_next_login=None,
- use_username_as_cn=None, 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):
-
- if password is None:
- password = getpass("New Password: ")
-
- lp = sambaopts.get_loadparm()
- creds = credopts.get_credentials(lp)
-
- 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)
- except Exception, e:
- raise CommandError('Failed to create user "%s"' % username, e)
-
- print("User %s created successfully" % username)
diff --git a/source4/scripting/python/samba/netcmd/ntacl.py b/source4/scripting/python/samba/netcmd/ntacl.py
index 49f8fbc77f..838f9bab30 100644
--- a/source4/scripting/python/samba/netcmd/ntacl.py
+++ b/source4/scripting/python/samba/netcmd/ntacl.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Manipulate file NT ACLs
#
# Copyright Matthieu Patou 2010 <mat@matws.net>
@@ -20,10 +18,13 @@
from samba.credentials import DONT_USE_KERBEROS
import samba.getopt as options
-from samba.dcerpc import security
+from samba.dcerpc import security, idmap
from samba.ntacls import setntacl, getntacl
from samba import Ldb
-from samba.ndr import ndr_unpack
+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
@@ -36,9 +37,12 @@ from samba.netcmd import (
Option,
)
-class cmd_acl_set(Command):
- """Set ACLs on a file"""
- synopsis = "%prog set <acl> <file> [--xattr-backend=native|tdb] [--eadb-file=file] [options]"
+
+
+class cmd_ntacl_set(Command):
+ """Set ACLs on a file."""
+
+ synopsis = "%prog <acl> <file> [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -51,37 +55,47 @@ class cmd_acl_set(Command):
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")
]
takes_args = ["acl","file"]
- def run(self, acl, file, quiet=False,xattr_backend=None,eadb_file=None,
+ 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):
+ logger = self.get_logger()
lp = sambaopts.get_loadparm()
- creds = credopts.get_credentials(lp)
- path = os.path.join(lp.get("private dir"), lp.get("secrets database") or "secrets.ldb")
- creds = credopts.get_credentials(lp)
- creds.set_kerberos_state(DONT_USE_KERBEROS)
try:
- ldb = Ldb(path, session_info=system_session(), credentials=creds,
- lp=lp)
+ samdb = SamDB(session_info=system_session(),
+ lp=lp)
except Exception, e:
- raise CommandError("Unable to read domain SID from configuration files", e)
- attrs = ["objectSid"]
- print lp.get("realm")
- res = ldb.search(expression="(objectClass=*)",
- base="flatname=%s,cn=Primary Domains" % lp.get("workgroup"),
- scope=SCOPE_BASE, attrs=attrs)
- if len(res) !=0:
- domainsid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
- setntacl(lp, file, acl, str(domainsid), xattr_backend, eadb_file)
- else:
+ 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)
-class cmd_acl_get(Command):
- """Set ACLs on a file"""
- synopsis = "%prog get <file> [--as-sddl] [--xattr-backend=native|tdb] [--eadb-file=file] [options]"
+ 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,
@@ -94,26 +108,149 @@ class cmd_acl_get(Command):
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")
]
takes_args = ["file"]
- def run(self, file, as_sddl=False, xattr_backend=None, eadb_file=None,
+ 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):
lp = sambaopts.get_loadparm()
- creds = credopts.get_credentials(lp)
- acl = getntacl(lp, file, xattr_backend, eadb_file)
+ 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)
if as_sddl:
- anysid = security.dom_sid(security.SID_NT_SELF)
- print acl.info.as_sddl(anysid)
+ 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:
- acl.dump()
+ 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_nt_acl(SuperCommand):
- """NT ACLs manipulation"""
+class cmd_ntacl(SuperCommand):
+ """NT ACLs manipulation."""
subcommands = {}
- subcommands["set"] = cmd_acl_set()
- subcommands["get"] = cmd_acl_get()
+ subcommands["set"] = cmd_ntacl_set()
+ subcommands["get"] = cmd_ntacl_get()
+ subcommands["sysvolreset"] = cmd_ntacl_sysvolreset()
+ subcommands["sysvolcheck"] = cmd_ntacl_sysvolcheck()
diff --git a/source4/scripting/python/samba/netcmd/processes.py b/source4/scripting/python/samba/netcmd/processes.py
new file mode 100644
index 0000000000..751ab94c21
--- /dev/null
+++ b/source4/scripting/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:
+ print "%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):
+ print "%s\n" % name.name
+ else:
+ names = msg_ctx.irpc_all_servers()
+ for name in names:
+ print "%s: " % name.name
+ for server_id in name.ids:
+ print "%d " % server_id.pid
+ print "\n"
diff --git a/source4/scripting/python/samba/netcmd/pwsettings.py b/source4/scripting/python/samba/netcmd/pwsettings.py
deleted file mode 100644
index 3f1a8e189d..0000000000
--- a/source4/scripting/python/samba/netcmd/pwsettings.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/usr/bin/env python
-#
-# Sets password settings.
-# (Password complexity, history length, minimum password length, the minimum
-# and maximum password age) on a Samba4 server
-#
-# Copyright Matthias Dieter Wallnoefer 2009
-# Copyright Andrew Kroeger 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 samba.auth import system_session
-from samba.samdb import SamDB
-from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
-from samba.netcmd import Command, CommandError, Option
-
-class cmd_pwsettings(Command):
- """Sets password settings
-
- Password complexity, history length, minimum password length, the minimum
- and maximum password age) on a Samba4 server.
- """
-
- synopsis = "(show | set <options>)"
-
- takes_optiongroups = {
- "sambaopts": options.SambaOptions,
- "versionopts": options.VersionOptions,
- "credopts": options.CredentialsOptions,
- }
-
- takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
- 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))
- 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
- 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))
-
- samdb.modify(m)
- msgs.append("All changes applied successfully!")
- self.message("\n".join(msgs))
- else:
- raise CommandError("Wrong argument '%s'!" % subcommand)
diff --git a/source4/scripting/python/samba/netcmd/rodc.py b/source4/scripting/python/samba/netcmd/rodc.py
index f4daac853d..2dc6112a30 100644
--- a/source4/scripting/python/samba/netcmd/rodc.py
+++ b/source4/scripting/python/samba/netcmd/rodc.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# rodc related commands
#
# Copyright Andrew Tridgell 2010
@@ -24,13 +22,13 @@ from samba.samdb import SamDB
from samba.auth import system_session
import ldb
from samba.dcerpc import misc, drsuapi
-from samba.credentials import Credentials
from samba.drs_utils import drs_Replicate
+
class cmd_rodc_preload(Command):
- """Preload one account for an RODC"""
+ """Preload one account for an RODC."""
- synopsis = "%prog rodc preload <SID|DN|accountname>"
+ synopsis = "%prog (<SID>|<DN>|<accountname>) [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -57,18 +55,13 @@ class cmd_rodc_preload(Command):
expression="objectclass=user",
scope=ldb.SCOPE_BASE, attrs=[])
else:
- res = samdb.search(expression="(&(samAccountName=%s)(objectclass=user))" % account,
+ 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 get_dsServiceName(self, samdb):
- res = samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
- return res[0]["dsServiceName"][0]
-
-
def run(self, account, sambaopts=None,
credopts=None, versionopts=None, server=None):
@@ -88,12 +81,12 @@ class cmd_rodc_preload(Command):
credentials=creds, lp=lp)
# work out the source and destination GUIDs
- dc_ntds_dn = self.get_dsServiceName(samdb)
+ 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)
- print "Replicating DN %s" % dn
+ self.outf.write("Replicating DN %s\n" % dn)
destination_dsa_guid = misc.GUID(local_samdb.get_ntds_GUID())
@@ -109,7 +102,7 @@ class cmd_rodc_preload(Command):
class cmd_rodc(SuperCommand):
- """RODC commands"""
+ """Read-Only Domain Controller (RODC) management."""
subcommands = {}
subcommands["preload"] = cmd_rodc_preload()
diff --git a/source4/scripting/python/samba/netcmd/setexpiry.py b/source4/scripting/python/samba/netcmd/setexpiry.py
deleted file mode 100644
index bd8ea166fa..0000000000
--- a/source4/scripting/python/samba/netcmd/setexpiry.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env python
-#
-# Sets the user password expiry on 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/>.
-#
-
-from samba.netcmd import Command, CommandError, Option
-
-import samba.getopt as options
-
-from samba.auth import system_session
-from samba.samdb import SamDB
-
-class cmd_setexpiry(Command):
- """Sets the expiration of a user account"""
-
- synopsis = "setexpiry [username] [options]"
-
- takes_optiongroups = {
- "sambaopts": options.SambaOptions,
- "versionopts": options.VersionOptions,
- "credopts": options.CredentialsOptions,
- }
-
- takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
- Option("--filter", help="LDAP Filter to set password on", type=str),
- Option("--days", help="Days to expiry", type=int),
- Option("--noexpiry", help="Password does never expire", action="store_true"),
- ]
-
- 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))" % (username)
-
- lp = sambaopts.get_loadparm()
- creds = credopts.get_credentials(lp)
-
- if days is None:
- days = 0
-
- samdb = SamDB(url=H, session_info=system_session(),
- credentials=creds, lp=lp)
-
- samdb.setexpiry(filter, days*24*3600, no_expiry_req=noexpiry)
diff --git a/source4/scripting/python/samba/netcmd/setpassword.py b/source4/scripting/python/samba/netcmd/setpassword.py
deleted file mode 100644
index b32b651822..0000000000
--- a/source4/scripting/python/samba/netcmd/setpassword.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python
-#
-# Sets a user password on 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, CommandError, Option
-from getpass import getpass
-from samba.auth import system_session
-from samba.samdb import SamDB
-from samba import gensec
-import ldb
-
-class cmd_setpassword(Command):
- """(Re)sets the password on a user account"""
-
- synopsis = "setpassword [username] [options]"
-
- takes_optiongroups = {
- "sambaopts": options.SambaOptions,
- "versionopts": options.VersionOptions,
- "credopts": options.CredentialsOptions,
- }
-
- takes_options = [
- Option("-H", help="LDB URL for database or target server", type=str),
- 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"),
- ]
-
- 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=None):
- if filter is None and username is None:
- raise CommandError("Either the username or '--filter' must be specified!")
-
- password = newpassword
- if password is None:
- password = getpass("New Password: ")
-
- if filter is None:
- filter = "(&(objectClass=user)(sAMAccountName=%s))" % (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, e:
- raise CommandError('Failed to set password for user "%s"' % username, e)
- print "Changed password OK"
diff --git a/source4/scripting/python/samba/netcmd/sites.py b/source4/scripting/python/samba/netcmd/sites.py
new file mode 100644
index 0000000000..09df55ec9c
--- /dev/null
+++ b/source4/scripting/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/source4/scripting/python/samba/netcmd/spn.py b/source4/scripting/python/samba/netcmd/spn.py
index 4cfa21fa03..03d072ec9b 100644
--- a/source4/scripting/python/samba/netcmd/spn.py
+++ b/source4/scripting/python/samba/netcmd/spn.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# spn management
#
# Copyright Matthieu Patou mat@samba.org 2010
@@ -20,10 +18,10 @@
import samba.getopt as options
import ldb
-import re
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,
@@ -31,30 +29,11 @@ from samba.netcmd import (
Option
)
-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())
class cmd_spn_list(Command):
"""List spns of a given user."""
- synopsis = "%prog spn list <user>"
+
+ synopsis = "%prog <user> [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -73,27 +52,31 @@ class cmd_spn_list(Command):
# TODO once I understand how, use the domain info to naildown
# to the correct domain
(cleaneduser, realm, domain) = _get_user_realm_domain(user)
- print cleaneduser
- res = sam.search(expression="samaccountname=%s" % cleaneduser,
- scope=ldb.SCOPE_SUBTREE,
- attrs=["servicePrincipalName"])
+ 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 != None:
- print "User %s has the following servicePrincipalName: " % str(res[0].dn)
+ if spns is not None:
+ self.outf.write(
+ "User %s has the following servicePrincipalName: \n" %
+ res[0].dn)
for e in spns:
- print "\t %s" % (str(e))
-
+ self.outf.write("\t %s\n" % e)
else:
- print "User %s has no servicePrincipalName" % str(res[0].dn)
+ 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 spn add [--force] <name> <user>"
+
+ synopsis = "%prog <name> <user> [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -101,28 +84,29 @@ class cmd_spn_add(Command):
"versionopts": options.VersionOptions,
}
takes_options = [
- Option("--force", help="Force the addition of the spn"\
+ 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):
+ 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" % name,
- scope=ldb.SCOPE_SUBTREE,
- )
- if len(res) != 0 and not force:
+ 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" % cleaneduser,
- scope=ldb.SCOPE_SUBTREE,
- attrs=["servicePrincipalName"])
+ 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()
@@ -130,7 +114,7 @@ class cmd_spn_add(Command):
tab = []
found = False
flag = ldb.FLAG_MOD_ADD
- if spns != None:
+ if spns is not None:
for e in spns:
if str(e) == name:
found = True
@@ -151,7 +135,8 @@ class cmd_spn_add(Command):
class cmd_spn_delete(Command):
"""Delete a spn."""
- synopsis = "%prog spn delete <name> [user]"
+
+ synopsis = "%prog <name> [user] [options]"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -161,15 +146,17 @@ class cmd_spn_delete(Command):
takes_args = ["name", "user?"]
- def run(self, name, user=None, credopts=None, sambaopts=None, versionopts=None):
+ 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" % name,
- scope=ldb.SCOPE_SUBTREE,
- attrs=["servicePrincipalName", "samAccountName"])
+ 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:
@@ -185,8 +172,8 @@ class cmd_spn_delete(Command):
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"\
+ 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]
@@ -195,7 +182,7 @@ class cmd_spn_delete(Command):
msg = ldb.Message()
spns = result.get("servicePrincipalName")
tab = []
- if spns != None:
+ if spns is not None:
for e in spns:
if str(e) != name:
tab.append(str(e))
@@ -207,8 +194,9 @@ class cmd_spn_delete(Command):
else:
raise CommandError("Service principal %s not affected" % name)
+
class cmd_spn(SuperCommand):
- """SPN management [server connection needed]"""
+ """Service Principal Name (SPN) management."""
subcommands = {}
subcommands["add"] = cmd_spn_add()
diff --git a/source4/scripting/python/samba/netcmd/testparm.py b/source4/scripting/python/samba/netcmd/testparm.py
new file mode 100644
index 0000000000..9251469421
--- /dev/null
+++ b/source4/scripting/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/source4/scripting/python/samba/netcmd/time.py b/source4/scripting/python/samba/netcmd/time.py
index e13d3df96c..694b6adda9 100644
--- a/source4/scripting/python/samba/netcmd/time.py
+++ b/source4/scripting/python/samba/netcmd/time.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# time
#
# Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
@@ -27,8 +25,21 @@ from samba.netcmd import (
)
class cmd_time(Command):
- """Retrieve the time on a remote server [server connection needed]"""
- synopsis = "%prog time <server-name>"
+ """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,
@@ -38,10 +49,11 @@ class cmd_time(Command):
takes_args = ["server_name?"]
- def run(self, server_name=None, credopts=None, sambaopts=None, versionopts=None):
+ 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)
- print net.time(server_name)
+ self.outf.write(net.time(server_name)+"\n")
diff --git a/source4/scripting/python/samba/netcmd/user.py b/source4/scripting/python/samba/netcmd/user.py
index bbc972bcc7..dc50d7779c 100644
--- a/source4/scripting/python/samba/netcmd/user.py
+++ b/source4/scripting/python/samba/netcmd/user.py
@@ -1,8 +1,7 @@
-#!/usr/bin/env python
-#
# 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
@@ -19,17 +18,87 @@
#
import samba.getopt as options
-
+import ldb
+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_add(Command):
- """Create a new user."""
- synopsis = "%prog user add <name> [<password>]"
+
+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.
+
+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.
+
+"""
+ 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),
+ ]
+
+ takes_args = ["username", "password?"]
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -37,20 +106,222 @@ class cmd_user_add(Command):
"versionopts": options.VersionOptions,
}
- takes_args = ["name", "password?"]
+ 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):
+
+ 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")
- def run(self, name, password=None, credopts=None, sambaopts=None, versionopts=None):
lp = sambaopts.get_loadparm()
- creds = credopts.get_credentials(lp )
- net = Net(creds, lp, server=credopts.ipaddress)
- net.create_user(name)
- if password is not None:
- net.set_password(name, creds.get_domain(), password, creds)
+ creds = credopts.get_credentials(lp)
+
+ 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)
+ 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."""
- synopsis = "%prog user delete <name>"
+ """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,
@@ -58,19 +329,242 @@ class cmd_user_delete(Command):
"versionopts": options.VersionOptions,
}
- takes_args = ["name"]
+ 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))
- def run(self, name, 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)
+ 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)
- net.delete_user(name)
+
+ 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 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 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 --password=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 [server connection needed]"""
+ """User management."""
subcommands = {}
- subcommands["add"] = cmd_user_add()
+ 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/source4/scripting/python/samba/netcmd/vampire.py b/source4/scripting/python/samba/netcmd/vampire.py
index 509aa8aacd..b12222e79e 100644
--- a/source4/scripting/python/samba/netcmd/vampire.py
+++ b/source4/scripting/python/samba/netcmd/vampire.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Vampire
#
# Copyright Jelmer Vernooij 2010 <jelmer@samba.org>
@@ -29,9 +27,10 @@ from samba.netcmd import (
CommandError
)
+
class cmd_vampire(Command):
- """Join and synchronise a remote AD domain to the local server [server connection needed]"""
- synopsis = "%prog vampire [options] <domain>"
+ """Join and synchronise a remote AD domain to the local server."""
+ synopsis = "%prog [options] <domain>"
takes_optiongroups = {
"sambaopts": options.SambaOptions,
@@ -48,7 +47,7 @@ class cmd_vampire(Command):
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 join. Use --force to override")
+ 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)
diff --git a/source4/scripting/python/samba/ntacls.py b/source4/scripting/python/samba/ntacls.py
index 78365e98b8..b89e9e9480 100644
--- a/source4/scripting/python/samba/ntacls.py
+++ b/source4/scripting/python/samba/ntacls.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
#
@@ -22,9 +20,10 @@
import os
-import samba.xattr_native, samba.xattr_tdb
-from samba.dcerpc import security, xattr
+import samba.xattr_native, samba.xattr_tdb, samba.posix_eadb
+from samba.dcerpc import security, xattr, idmap
from samba.ndr import ndr_pack, ndr_unpack
+from samba.samba3 import smbd
class XattrBackendError(Exception):
"""A generic xattr backend error."""
@@ -33,57 +32,126 @@ class XattrBackendError(Exception):
def checkset_backend(lp, backend, eadbfile):
'''return the path to the eadb, or None'''
if backend is None:
- return lp.get("posix:eadb")
+ xattr_tdb = lp.get("xattr_tdb:file")
+ if xattr_tdb is not None:
+ return (samba.xattr_tdb, lp.get("xattr_tdb:file"))
+ posix_eadb = lp.get("posix:eadb")
+ if posix_eadb is not None:
+ return (samba.posix_eadb, lp.get("posix:eadb"))
+ return (None, None)
elif backend == "native":
- return None
+ return (None, None)
+ elif backend == "eadb":
+ if eadbfile is not None:
+ return (samba.posix_eadb, eadbfile)
+ else:
+ return (samba.posix_eadb, os.path.abspath(os.path.join(lp.get("private dir"), "eadb.tdb")))
elif backend == "tdb":
if eadbfile is not None:
- return eadbfile
+ return (samba.xattr_tdb, eadbfile)
else:
- return os.path.abspath(os.path.join(lp.get("private dir"), "eadb.tdb"))
+ return (samba.xattr_tdb, os.path.abspath(os.path.join(lp.get("state dir"), "xattr.tdb")))
else:
raise XattrBackendError("Invalid xattr backend choice %s"%backend)
-def getntacl(lp, file, backend=None, eadbfile=None):
- eadbname = checkset_backend(lp, backend, eadbfile)
- if eadbname is not None:
- try:
- attribute = samba.xattr_tdb.wrap_getxattr(eadbname, file,
- xattr.XATTR_NTACL_NAME)
- except Exception:
- # FIXME: Don't catch all exceptions, just those related to opening
- # xattrdb
- print "Fail to open %s" % eadbname
+def getntacl(lp, file, backend=None, eadbfile=None, direct_db_access=True):
+ if direct_db_access:
+ (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
+ if dbname is not None:
+ try:
+ attribute = backend_obj.wrap_getxattr(dbname, file,
+ xattr.XATTR_NTACL_NAME)
+ except Exception:
+ # FIXME: Don't catch all exceptions, just those related to opening
+ # xattrdb
+ print "Fail to open %s" % dbname
+ attribute = samba.xattr_native.wrap_getxattr(file,
+ xattr.XATTR_NTACL_NAME)
+ else:
attribute = samba.xattr_native.wrap_getxattr(file,
- xattr.XATTR_NTACL_NAME)
+ xattr.XATTR_NTACL_NAME)
+ ntacl = ndr_unpack(xattr.NTACL, attribute)
+ if ntacl.version == 1:
+ return ntacl.info
+ elif ntacl.version == 2:
+ return ntacl.info.sd
+ elif ntacl.version == 3:
+ return ntacl.info.sd
+ elif ntacl.version == 4:
+ return ntacl.info.sd
else:
- attribute = samba.xattr_native.wrap_getxattr(file,
- xattr.XATTR_NTACL_NAME)
- ntacl = ndr_unpack(xattr.NTACL, attribute)
- return ntacl
-
-
-def setntacl(lp, file, sddl, domsid, backend=None, eadbfile=None):
- eadbname = checkset_backend(lp, backend, eadbfile)
- ntacl = xattr.NTACL()
- ntacl.version = 1
- sid = security.dom_sid(domsid)
- sd = security.descriptor.from_sddl(sddl, sid)
- ntacl.info = sd
- if eadbname is not None:
- try:
- samba.xattr_tdb.wrap_setxattr(eadbname,
- file, xattr.XATTR_NTACL_NAME, ndr_pack(ntacl))
- except Exception:
- # FIXME: Don't catch all exceptions, just those related to opening
- # xattrdb
- print "Fail to open %s" % eadbname
- samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
- ndr_pack(ntacl))
+ return smbd.get_nt_acl(file, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL | security.SECINFO_SACL)
+
+
+def setntacl(lp, file, sddl, domsid, backend=None, eadbfile=None, use_ntvfs=True, skip_invalid_chown=False, passdb=None):
+ assert(isinstance(domsid, str) or isinstance(domsid, security.dom_sid))
+ if isinstance(domsid, str):
+ sid = security.dom_sid(domsid)
+ elif isinstance(domsid, security.dom_sid):
+ sid = domsid
+ domsid = str(sid)
+
+ assert(isinstance(sddl, str) or isinstance(sddl, security.descriptor))
+ if isinstance(sddl, str):
+ sd = security.descriptor.from_sddl(sddl, sid)
+ elif isinstance(sddl, security.descriptor):
+ sd = sddl
+ sddl = sd.as_sddl(sid)
+
+ if not use_ntvfs and skip_invalid_chown:
+ # Check if the owner can be resolved as a UID
+ (owner_id, owner_type) = passdb.sid_to_id(sd.owner_sid)
+ if ((owner_type != idmap.ID_TYPE_UID) and (owner_type != idmap.ID_TYPE_BOTH)):
+ # Check if this particular owner SID was domain admins,
+ # because we special-case this as mapping to
+ # 'administrator' instead.
+ if sd.owner_sid == security.dom_sid("%s-%d" % (domsid, security.DOMAIN_RID_ADMINS)):
+ administrator = security.dom_sid("%s-%d" % (domsid, security.DOMAIN_RID_ADMINISTRATOR))
+ (admin_id, admin_type) = passdb.sid_to_id(administrator)
+
+ # Confirm we have a UID for administrator
+ if ((admin_type == idmap.ID_TYPE_UID) or (admin_type == idmap.ID_TYPE_BOTH)):
+
+ # Set it, changing the owner to 'administrator' rather than domain admins
+ sd2 = sd
+ sd2.owner_sid = administrator
+
+ smbd.set_nt_acl(file, security.SECINFO_OWNER |security.SECINFO_GROUP | security.SECINFO_DACL | security.SECINFO_SACL, sd2)
+
+ # and then set an NTVFS ACL (which does not set the posix ACL) to pretend the owner really was set
+ use_ntvfs = True
+ else:
+ raise XattrBackendError("Unable to find UID for domain administrator %s, got id %d of type %d" % (administrator, admin_id, admin_type))
+ else:
+ # For all other owning users, reset the owner to root
+ # and then set the ACL without changing the owner
+ #
+ # This won't work in test environments, as it tries a real (rather than xattr-based fake) chown
+
+ os.chown(file, 0, 0)
+ smbd.set_nt_acl(file, security.SECINFO_GROUP | security.SECINFO_DACL | security.SECINFO_SACL, sd)
+
+ if use_ntvfs:
+ (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
+ ntacl = xattr.NTACL()
+ ntacl.version = 1
+ ntacl.info = sd
+ if dbname is not None:
+ try:
+ backend_obj.wrap_setxattr(dbname,
+ file, xattr.XATTR_NTACL_NAME, ndr_pack(ntacl))
+ except Exception:
+ # FIXME: Don't catch all exceptions, just those related to opening
+ # xattrdb
+ print "Fail to open %s" % dbname
+ samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
+ ndr_pack(ntacl))
+ else:
+ samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
+ ndr_pack(ntacl))
else:
- samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
- ndr_pack(ntacl))
+ smbd.set_nt_acl(file, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL | security.SECINFO_SACL, sd)
def ldapmask2filemask(ldm):
@@ -122,14 +190,14 @@ def ldapmask2filemask(ldm):
filemask = ldm & STANDARD_RIGHTS_ALL
if (ldm & RIGHT_DS_READ_PROPERTY) and (ldm & RIGHT_DS_LIST_CONTENTS):
- filemask = filemask | (SYNCHRONIZE | FILE_LIST_DIRECTORY |\
- FILE_READ_ATTRIBUTES | FILE_READ_EA |\
+ filemask = filemask | (SYNCHRONIZE | FILE_LIST_DIRECTORY |
+ FILE_READ_ATTRIBUTES | FILE_READ_EA |
FILE_READ_DATA | FILE_EXECUTE)
if ldm & RIGHT_DS_WRITE_PROPERTY:
- filemask = filemask | (SYNCHRONIZE | FILE_WRITE_DATA |\
- FILE_APPEND_DATA | FILE_WRITE_EA |\
- FILE_WRITE_ATTRIBUTES | FILE_ADD_FILE |\
+ filemask = filemask | (SYNCHRONIZE | FILE_WRITE_DATA |
+ FILE_APPEND_DATA | FILE_WRITE_EA |
+ FILE_WRITE_ATTRIBUTES | FILE_ADD_FILE |
FILE_ADD_SUBDIRECTORY)
if ldm & RIGHT_DS_CREATE_CHILD:
@@ -141,21 +209,19 @@ def ldapmask2filemask(ldm):
return filemask
-def dsacl2fsacl(dssddl, domsid):
+def dsacl2fsacl(dssddl, sid, as_sddl=True):
"""
-
+
This function takes an the SDDL representation of a DS
ACL and return the SDDL representation of this ACL adapted
for files. It's used for Policy object provision
"""
- sid = security.dom_sid(domsid)
ref = security.descriptor.from_sddl(dssddl, sid)
fdescr = security.descriptor()
fdescr.owner_sid = ref.owner_sid
fdescr.group_sid = ref.group_sid
fdescr.type = ref.type
fdescr.revision = ref.revision
- fdescr.sacl = ref.sacl
aces = ref.dacl.aces
for i in range(0, len(aces)):
ace = aces[i]
@@ -168,4 +234,7 @@ def dsacl2fsacl(dssddl, domsid):
ace.access_mask = ldapmask2filemask(ace.access_mask)
fdescr.dacl_add(ace)
+ if not as_sddl:
+ return fdescr
+
return fdescr.as_sddl(sid)
diff --git a/source4/scripting/python/samba/provision/__init__.py b/source4/scripting/python/samba/provision/__init__.py
index 55774c225b..e6ea855b57 100644
--- a/source4/scripting/python/samba/provision/__init__.py
+++ b/source4/scripting/python/samba/provision/__init__.py
@@ -1,8 +1,7 @@
-
# Unix SMB/CIFS implementation.
# backend code for provisioning a Samba4 server
-# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
# Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
#
@@ -37,24 +36,27 @@ import time
import uuid
import socket
import urllib
-import shutil
+import string
+import tempfile
import ldb
from samba.auth import system_session, admin_session
import samba
+from samba.samba3 import smbd, passdb
+from samba.samba3 import param as s3param
+from samba.dsdb import DS_DOMAIN_FUNCTION_2000
from samba import (
Ldb,
+ MAX_NETBIOS_NAME_LEN,
check_all_substituted,
- in_source_tree,
- source_tree_topdir,
- read_and_sub_file,
+ is_valid_netbios_char,
setup_file,
substitute_var,
valid_netbios_name,
version,
)
-from samba.dcerpc import security
+from samba.dcerpc import security, misc
from samba.dcerpc.misc import (
SEC_CHAN_BDC,
SEC_CHAN_WKSTA,
@@ -66,7 +68,7 @@ from samba.dsdb import (
)
from samba.idmap import IDmapDB
from samba.ms_display_specifiers import read_ms_ldif
-from samba.ntacls import setntacl, dsacl2fsacl
+from samba.ntacls import setntacl, getntacl, dsacl2fsacl
from samba.ndr import ndr_pack, ndr_unpack
from samba.provision.backend import (
ExistingBackend,
@@ -74,113 +76,41 @@ from samba.provision.backend import (
LDBBackend,
OpenLDAPBackend,
)
+from samba.provision.descriptor import (
+ get_empty_descriptor,
+ get_config_descriptor,
+ get_config_partitions_descriptor,
+ get_config_sites_descriptor,
+ get_domain_descriptor,
+ get_domain_infrastructure_descriptor,
+ get_domain_builtin_descriptor,
+ get_domain_computers_descriptor,
+ get_domain_users_descriptor,
+ get_domain_controllers_descriptor
+ )
+from samba.provision.common import (
+ setup_path,
+ setup_add_ldif,
+ setup_modify_ldif,
+ )
+from samba.provision.sambadns import (
+ setup_ad_dns,
+ create_dns_update_list
+ )
+
import samba.param
import samba.registry
from samba.schema import Schema
from samba.samdb import SamDB
+from samba.dbchecker import dbcheck
+
-VALID_NETBIOS_CHARS = " !#$%&'()-.@^_{}~"
DEFAULT_POLICY_GUID = "31B2F340-016D-11D2-945F-00C04FB984F9"
DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04fB984F9"
DEFAULTSITE = "Default-First-Site-Name"
LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
-def setup_path(file):
- """Return an absolute path to the provision tempate file specified by file"""
- return os.path.join(samba.param.setup_dir(), file)
-
-# Descriptors of naming contexts and other important objects
-
-# "get_schema_descriptor" is located in "schema.py"
-
-def get_sites_descriptor(domain_sid):
- sddl = "D:(A;;RPLCLORC;;;AU)" \
- "(A;;RPWPCRCCLCLORCWOWDSW;;;EA)" \
- "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
- "S:AI(AU;CISA;CCDCSDDT;;;WD)" \
- "(OU;CIIOSA;CR;;f0f8ffab-1191-11d0-a060-00aa006c33ed;WD)" \
- "(OU;CIIOSA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \
- "(OU;CIIOSA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \
- "(OU;CIIOSA;WP;3e10944c-c354-11d0-aff8-0000f80367c1;b7b13124-b82e-11d0-afee-0000f80367c1;WD)"
- sec = security.descriptor.from_sddl(sddl, domain_sid)
- return ndr_pack(sec)
-
-
-def get_config_descriptor(domain_sid):
- sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
- "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
- "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
- "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
- "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
- "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
- "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
- "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
- "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
- "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
- "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
- sec = security.descriptor.from_sddl(sddl, domain_sid)
- return ndr_pack(sec)
-
-
-def get_domain_descriptor(domain_sid):
- sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
- "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
- "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
- "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
- "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
- "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
- "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
- "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
- "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
- "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
- "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
- "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \
- "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \
- "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \
- "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \
- "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
- "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
- "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \
- "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \
- "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \
- "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
- "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \
- "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
- "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \
- "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
- "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \
- "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \
- "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
- "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
- "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
- "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
- "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \
- "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
- "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
- "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
- "(A;;RPRC;;;RU)" \
- "(A;CI;LC;;;RU)" \
- "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
- "(A;;RP;;;WD)" \
- "(A;;RPLCLORC;;;ED)" \
- "(A;;RPLCLORC;;;AU)" \
- "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
- "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
- "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
- "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)"
- sec = security.descriptor.from_sddl(sddl, domain_sid)
- return ndr_pack(sec)
-
-
class ProvisionPaths(object):
def __init__(self):
@@ -199,6 +129,7 @@ class ProvisionPaths(object):
self.dns = None
self.winsdb = None
self.private_dir = None
+ self.state_dir = None
class ProvisionNames(object):
@@ -218,7 +149,119 @@ class ProvisionNames(object):
self.smbconf = None
-def update_provision_usn(samdb, low, high, replace=False):
+def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf,
+ lp):
+ """Get key provision parameters (realm, domain, ...) from a given provision
+
+ :param samdb: An LDB object connected to the sam.ldb file
+ :param secretsdb: An LDB object connected to the secrets.ldb file
+ :param idmapdb: An LDB object connected to the idmap.ldb file
+ :param paths: A list of path to provision object
+ :param smbconf: Path to the smb.conf file
+ :param lp: A LoadParm object
+ :return: A list of key provision parameters
+ """
+ names = ProvisionNames()
+ names.adminpass = None
+
+ # NT domain, kerberos realm, root dn, domain dn, domain dns name
+ names.domain = string.upper(lp.get("workgroup"))
+ names.realm = lp.get("realm")
+ names.dnsdomain = names.realm.lower()
+ basedn = samba.dn_from_dns_name(names.dnsdomain)
+ names.realm = string.upper(names.realm)
+ # netbiosname
+ # Get the netbiosname first (could be obtained from smb.conf in theory)
+ res = secretsdb.search(expression="(flatname=%s)" %
+ names.domain,base="CN=Primary Domains",
+ scope=ldb.SCOPE_SUBTREE, attrs=["sAMAccountName"])
+ names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
+
+ names.smbconf = smbconf
+
+ # That's a bit simplistic but it's ok as long as we have only 3
+ # partitions
+ current = samdb.search(expression="(objectClass=*)",
+ base="", scope=ldb.SCOPE_BASE,
+ attrs=["defaultNamingContext", "schemaNamingContext",
+ "configurationNamingContext","rootDomainNamingContext"])
+
+ names.configdn = current[0]["configurationNamingContext"]
+ configdn = str(names.configdn)
+ names.schemadn = current[0]["schemaNamingContext"]
+ if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
+ current[0]["defaultNamingContext"][0]))):
+ raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
+ "is not the same ..." % (paths.samdb,
+ str(current[0]["defaultNamingContext"][0]),
+ paths.smbconf, basedn)))
+
+ names.domaindn=current[0]["defaultNamingContext"]
+ names.rootdn=current[0]["rootDomainNamingContext"]
+ # default site name
+ res3 = samdb.search(expression="(objectClass=site)",
+ base="CN=Sites," + configdn, scope=ldb.SCOPE_ONELEVEL, attrs=["cn"])
+ names.sitename = str(res3[0]["cn"])
+
+ # dns hostname and server dn
+ res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
+ base="OU=Domain Controllers,%s" % basedn,
+ scope=ldb.SCOPE_ONELEVEL, attrs=["dNSHostName"])
+ names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain, "")
+
+ server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
+ attrs=[], base=configdn)
+ names.serverdn = server_res[0].dn
+
+ # invocation id/objectguid
+ res5 = samdb.search(expression="(objectClass=*)",
+ base="CN=NTDS Settings,%s" % str(names.serverdn),
+ scope=ldb.SCOPE_BASE,
+ attrs=["invocationID", "objectGUID"])
+ names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
+ names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
+
+ # domain guid/sid
+ res6 = samdb.search(expression="(objectClass=*)", base=basedn,
+ scope=ldb.SCOPE_BASE, attrs=["objectGUID",
+ "objectSid","msDS-Behavior-Version" ])
+ names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
+ names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
+ if res6[0].get("msDS-Behavior-Version") is None or \
+ int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
+ names.domainlevel = DS_DOMAIN_FUNCTION_2000
+ else:
+ names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
+
+ # policy guid
+ res7 = samdb.search(expression="(displayName=Default Domain Policy)",
+ base="CN=Policies,CN=System," + basedn,
+ scope=ldb.SCOPE_ONELEVEL, attrs=["cn","displayName"])
+ names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
+ # dc policy guid
+ res8 = samdb.search(expression="(displayName=Default Domain Controllers"
+ " Policy)",
+ base="CN=Policies,CN=System," + basedn,
+ scope=ldb.SCOPE_ONELEVEL,
+ attrs=["cn","displayName"])
+ if len(res8) == 1:
+ names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
+ else:
+ names.policyid_dc = None
+
+ res9 = idmapdb.search(expression="(cn=%s-%s)" %
+ (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR),
+ attrs=["xidNumber", "type"])
+ if len(res9) != 1:
+ raise ProvisioningError("Unable to find uid/gid for Domain Admins rid (%s-%s" % (str(names.domainsid), security.DOMAIN_RID_ADMINISTRATOR))
+ if res9[0]["type"][0] == "ID_TYPE_BOTH":
+ names.root_gid = res9[0]["xidNumber"][0]
+ else:
+ names.root_gid = pwd.getpwuid(int(res9[0]["xidNumber"][0])).pw_gid
+ return names
+
+
+def update_provision_usn(samdb, low, high, id, replace=False):
"""Update the field provisionUSN in sam.ldb
This field is used to track range of USN modified by provision and
@@ -229,28 +272,35 @@ def update_provision_usn(samdb, low, high, replace=False):
:param samdb: An LDB object connect to sam.ldb
:param low: The lowest USN modified by this upgrade
:param high: The highest USN modified by this upgrade
+ :param id: The invocation id of the samba's dc
:param replace: A boolean indicating if the range should replace any
existing one or appended (default)
"""
tab = []
if not replace:
- entry = samdb.search(expression="(&(dn=@PROVISION)(%s=*))" %
- LAST_PROVISION_USN_ATTRIBUTE, base="",
- scope=ldb.SCOPE_SUBTREE,
- attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
+ entry = samdb.search(base="@PROVISION",
+ scope=ldb.SCOPE_BASE,
+ attrs=[LAST_PROVISION_USN_ATTRIBUTE, "dn"])
for e in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
+ if not re.search(';', e):
+ e = "%s;%s" % (e, id)
tab.append(str(e))
- tab.append("%s-%s" % (low, high))
+ tab.append("%s-%s;%s" % (low, high, id))
delta = ldb.Message()
delta.dn = ldb.Dn(samdb, "@PROVISION")
delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
ldb.FLAG_MOD_REPLACE, LAST_PROVISION_USN_ATTRIBUTE)
+ entry = samdb.search(expression='provisionnerID=*',
+ base="@PROVISION", scope=ldb.SCOPE_BASE,
+ attrs=["provisionnerID"])
+ if len(entry) == 0 or len(entry[0]) == 0:
+ delta["provisionnerID"] = ldb.MessageElement(id, ldb.FLAG_MOD_ADD, "provisionnerID")
samdb.modify(delta)
-def set_provision_usn(samdb, low, high):
+def set_provision_usn(samdb, low, high, id):
"""Set the field provisionUSN in sam.ldb
This field is used to track range of USN modified by provision and
upgradeprovision.
@@ -259,9 +309,12 @@ def set_provision_usn(samdb, low, high):
:param samdb: An LDB object connect to sam.ldb
:param low: The lowest USN modified by this upgrade
- :param high: The highest USN modified by this upgrade"""
+ :param high: The highest USN modified by this upgrade
+ :param id: The invocationId of the provision"""
+
tab = []
- tab.append("%s-%s" % (low, high))
+ tab.append("%s-%s;%s" % (low, high, id))
+
delta = ldb.Message()
delta.dn = ldb.Dn(samdb, "@PROVISION")
delta[LAST_PROVISION_USN_ATTRIBUTE] = ldb.MessageElement(tab,
@@ -286,37 +339,81 @@ def get_max_usn(samdb,basedn):
def get_last_provision_usn(sam):
- """Get the lastest USN modified by a provision or an upgradeprovision
+ """Get USNs ranges modified by a provision or an upgradeprovision
:param sam: An LDB object pointing to the sam.ldb
- :return: an integer corresponding to the highest USN modified by
- (upgrade)provision, 0 is this value is unknown
+ :return: a dictionary which keys are invocation id and values are an array
+ of integer representing the different ranges
"""
- entry = sam.search(expression="(&(dn=@PROVISION)(%s=*))" %
- LAST_PROVISION_USN_ATTRIBUTE,
- base="", scope=ldb.SCOPE_SUBTREE,
- attrs=[LAST_PROVISION_USN_ATTRIBUTE])
- if len(entry):
- range = []
- idx = 0
+ try:
+ entry = sam.search(expression="%s=*" % LAST_PROVISION_USN_ATTRIBUTE,
+ base="@PROVISION", scope=ldb.SCOPE_BASE,
+ attrs=[LAST_PROVISION_USN_ATTRIBUTE, "provisionnerID"])
+ except ldb.LdbError, (ecode, emsg):
+ if ecode == ldb.ERR_NO_SUCH_OBJECT:
+ return None
+ raise
+ if len(entry) > 0:
+ myids = []
+ range = {}
p = re.compile(r'-')
+ if entry[0].get("provisionnerID"):
+ for e in entry[0]["provisionnerID"]:
+ myids.append(str(e))
for r in entry[0][LAST_PROVISION_USN_ATTRIBUTE]:
- tab = p.split(str(r))
- range.append(tab[0])
- range.append(tab[1])
- idx = idx + 1
+ tab1 = str(r).split(';')
+ if len(tab1) == 2:
+ id = tab1[1]
+ else:
+ id = "default"
+ if (len(myids) > 0 and id not in myids):
+ continue
+ tab2 = p.split(tab1[0])
+ if range.get(id) is None:
+ range[id] = []
+ range[id].append(tab2[0])
+ range[id].append(tab2[1])
return range
else:
return None
class ProvisionResult(object):
+ """Result of a provision.
+
+ :ivar server_role: The server role
+ :ivar paths: ProvisionPaths instance
+ :ivar domaindn: The domain dn, as string
+ """
def __init__(self):
+ self.server_role = None
self.paths = None
self.domaindn = None
self.lp = None
self.samdb = None
+ self.idmap = None
+ self.names = None
+ self.domainsid = None
+ self.adminpass_generated = None
+ self.adminpass = None
+ self.backend_result = None
+
+ def report_logger(self, logger):
+ """Report this provision result to a logger."""
+ logger.info(
+ "Once the above files are installed, your Samba4 server will "
+ "be ready to use")
+ if self.adminpass_generated:
+ logger.info("Admin password: %s", self.adminpass)
+ logger.info("Server Role: %s", self.server_role)
+ logger.info("Hostname: %s", self.names.hostname)
+ logger.info("NetBIOS Domain: %s", self.names.domain)
+ logger.info("DNS Domain: %s", self.names.dnsdomain)
+ logger.info("DOMAIN SID: %s", self.domainsid)
+
+ if self.backend_result:
+ self.backend_result.report_logger(logger)
def check_install(lp, session_info, credentials):
@@ -328,7 +425,7 @@ def check_install(lp, session_info, credentials):
"""
if lp.get("realm") == "":
raise Exception("Realm empty")
- samdb = Ldb(lp.get("sam database"), session_info=session_info,
+ samdb = Ldb(lp.samdb_url(), session_info=session_info,
credentials=credentials, lp=lp)
if len(samdb.search("(cn=Administrator)")) != 1:
raise ProvisioningError("No administrator account found")
@@ -353,51 +450,6 @@ findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
-def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
- """Setup a ldb in the private dir.
-
- :param ldb: LDB file to import data into
- :param ldif_path: Path of the LDIF file to load
- :param subst_vars: Optional variables to subsitute in LDIF.
- :param nocontrols: Optional list of controls, can be None for no controls
- """
- assert isinstance(ldif_path, str)
- data = read_and_sub_file(ldif_path, subst_vars)
- ldb.add_ldif(data, controls)
-
-
-def setup_modify_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
- """Modify a ldb in the private dir.
-
- :param ldb: LDB object.
- :param ldif_path: LDIF file path.
- :param subst_vars: Optional dictionary with substitution variables.
- """
- data = read_and_sub_file(ldif_path, subst_vars)
- ldb.modify_ldif(data, controls)
-
-
-def setup_ldb(ldb, ldif_path, subst_vars):
- """Import a LDIF a file into a LDB handle, optionally substituting
- variables.
-
- :note: Either all LDIF data will be added or none (using transactions).
-
- :param ldb: LDB file to import into.
- :param ldif_path: Path to the LDIF file.
- :param subst_vars: Dictionary with substitution variables.
- """
- assert ldb is not None
- ldb.transaction_start()
- try:
- setup_add_ldif(ldb, ldif_path, subst_vars)
- except Exception:
- ldb.transaction_cancel()
- raise
- else:
- ldb.transaction_commit()
-
-
def provision_paths_from_lp(lp, dnsdomain):
"""Set the default paths for provisioning.
@@ -406,6 +458,7 @@ def provision_paths_from_lp(lp, dnsdomain):
"""
paths = ProvisionPaths()
paths.private_dir = lp.get("private dir")
+ paths.state_dir = lp.get("state directory")
# This is stored without path prefix for the "privateKeytab" attribute in
# "secrets_dns.ldif".
@@ -413,12 +466,9 @@ def provision_paths_from_lp(lp, dnsdomain):
paths.keytab = "secrets.keytab"
paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
- paths.samdb = os.path.join(paths.private_dir,
- lp.get("sam database") or "samdb.ldb")
- paths.idmapdb = os.path.join(paths.private_dir,
- lp.get("idmap database") or "idmap.ldb")
- paths.secrets = os.path.join(paths.private_dir,
- lp.get("secrets database") or "secrets.ldb")
+ paths.samdb = os.path.join(paths.private_dir, "sam.ldb")
+ paths.idmapdb = os.path.join(paths.private_dir, "idmap.ldb")
+ paths.secrets = os.path.join(paths.private_dir, "secrets.ldb")
paths.privilege = os.path.join(paths.private_dir, "privilege.ldb")
paths.dns = os.path.join(paths.private_dir, "dns", dnsdomain + ".zone")
paths.dns_update_list = os.path.join(paths.private_dir, "dns_update_list")
@@ -429,8 +479,6 @@ def provision_paths_from_lp(lp, dnsdomain):
paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
- paths.phpldapadminconfig = os.path.join(paths.private_dir,
- "phpldapadmin-config.php")
paths.hklm = "hklm.ldb"
paths.hkcr = "hkcr.ldb"
paths.hkcu = "hkcu.ldb"
@@ -443,6 +491,13 @@ def provision_paths_from_lp(lp, dnsdomain):
return paths
+def determine_netbios_name(hostname):
+ """Determine a netbios name from a hostname."""
+ # remove forbidden chars and force the length to be <16
+ netbiosname = "".join([x for x in hostname if is_valid_netbios_char(x)])
+ return netbiosname[:MAX_NETBIOS_NAME_LEN].upper()
+
+
def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
serverrole=None, rootdn=None, domaindn=None, configdn=None,
schemadn=None, serverdn=None, sitename=None):
@@ -453,15 +508,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
netbiosname = lp.get("netbios name")
if netbiosname is None:
- netbiosname = hostname
- # remove forbidden chars
- newnbname = ""
- for x in netbiosname:
- if x.isalnum() or x in VALID_NETBIOS_CHARS:
- newnbname = "%s%c" % (newnbname, x)
- # force the length to be <16
- netbiosname = newnbname[0:15]
- assert netbiosname is not None
+ netbiosname = determine_netbios_name(hostname)
netbiosname = netbiosname.upper()
if not valid_netbios_name(netbiosname):
raise InvalidNetbiosName(netbiosname)
@@ -489,9 +536,9 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
raise ProvisioningError("guess_names: 'realm=%s' in %s must match chosen realm '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("realm").upper(), realm, lp.configfile))
if lp.get("server role").lower() != serverrole:
- raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role").upper(), serverrole, lp.configfile))
+ raise ProvisioningError("guess_names: 'server role=%s' in %s must match chosen server role '%s'! Please remove the smb.conf file and let provision generate it" % (lp.get("server role"), lp.configfile, serverrole))
- if serverrole == "domain controller":
+ if serverrole == "active directory domain controller":
if domain is None:
# This will, for better or worse, default to 'WORKGROUP'
domain = lp.get("workgroup")
@@ -501,7 +548,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
raise ProvisioningError("guess_names: Workgroup '%s' in smb.conf must match chosen domain '%s'! Please remove the %s file and let provision generate it" % (lp.get("workgroup").upper(), domain, lp.configfile))
if domaindn is None:
- domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
+ domaindn = samba.dn_from_dns_name(dnsdomain)
if domain == netbiosname:
raise ProvisioningError("guess_names: Domain '%s' must not be equal to short host name '%s'!" % (domain, netbiosname))
@@ -529,7 +576,7 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
schemadn = "CN=Schema," + configdn
if sitename is None:
- sitename=DEFAULTSITE
+ sitename = DEFAULTSITE
names = ProvisionNames()
names.rootdn = rootdn
@@ -549,37 +596,20 @@ def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
return names
-def make_smbconf(smbconf, hostname, domain, realm, serverrole,
- targetdir, sid_generator="internal", eadb=False, lp=None):
+def make_smbconf(smbconf, hostname, domain, realm, targetdir,
+ serverrole=None, eadb=False, use_ntvfs=False, lp=None,
+ global_param=None):
"""Create a new smb.conf file based on a couple of basic settings.
"""
assert smbconf is not None
+
if hostname is None:
hostname = socket.gethostname().split(".")[0]
- netbiosname = hostname.upper()
- # remove forbidden chars
- newnbname = ""
- for x in netbiosname:
- if x.isalnum() or x in VALID_NETBIOS_CHARS:
- newnbname = "%s%c" % (newnbname, x)
- #force the length to be <16
- netbiosname = newnbname[0:15]
- else:
- netbiosname = hostname.upper()
-
- if serverrole is None:
- serverrole = "standalone"
- assert serverrole in ("domain controller", "member server", "standalone")
- if serverrole == "domain controller":
- smbconfsuffix = "dc"
- elif serverrole == "member server":
- smbconfsuffix = "member"
- elif serverrole == "standalone":
- smbconfsuffix = "standalone"
+ netbiosname = determine_netbios_name(hostname)
- if sid_generator is None:
- sid_generator = "internal"
+ if serverrole is None:
+ serverrole = "standalone server"
assert domain is not None
domain = domain.upper()
@@ -587,48 +617,70 @@ def make_smbconf(smbconf, hostname, domain, realm, serverrole,
assert realm is not None
realm = realm.upper()
+ global_settings = {
+ "netbios name": netbiosname,
+ "workgroup": domain,
+ "realm": realm,
+ "server role": serverrole,
+ }
+
if lp is None:
lp = samba.param.LoadParm()
- #Load non-existant file
+ #Load non-existent file
if os.path.exists(smbconf):
lp.load(smbconf)
- if eadb and not lp.get("posix:eadb"):
- if targetdir is not None:
- privdir = os.path.join(targetdir, "private")
- else:
- privdir = lp.get("private dir")
- lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
+
+ if global_param is not None:
+ for ent in global_param:
+ if global_param[ent] is not None:
+ global_settings[ent] = " ".join(global_param[ent])
if targetdir is not None:
- privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
- lockdir_line = "lock dir = " + os.path.abspath(targetdir)
+ global_settings["private dir"] = os.path.abspath(os.path.join(targetdir, "private"))
+ global_settings["lock dir"] = os.path.abspath(targetdir)
+ global_settings["state directory"] = os.path.abspath(os.path.join(targetdir, "state"))
+ global_settings["cache directory"] = os.path.abspath(os.path.join(targetdir, "cache"))
lp.set("lock dir", os.path.abspath(targetdir))
- else:
- privatedir_line = ""
- lockdir_line = ""
+ lp.set("state directory", global_settings["state directory"])
+ lp.set("cache directory", global_settings["cache directory"])
- if sid_generator == "internal":
- sid_generator_line = ""
+ if eadb:
+ if use_ntvfs and not lp.get("posix:eadb"):
+ if targetdir is not None:
+ privdir = os.path.join(targetdir, "private")
+ else:
+ privdir = lp.get("private dir")
+ lp.set("posix:eadb", os.path.abspath(os.path.join(privdir, "eadb.tdb")))
+ elif not use_ntvfs and not lp.get("xattr_tdb:file"):
+ if targetdir is not None:
+ statedir = os.path.join(targetdir, "state")
+ else:
+ statedir = lp.get("state directory")
+ lp.set("xattr_tdb:file", os.path.abspath(os.path.join(statedir, "xattr.tdb")))
+
+ shares = {}
+ if serverrole == "active directory domain controller":
+ shares["sysvol"] = os.path.join(lp.get("state directory"), "sysvol")
+ shares["netlogon"] = os.path.join(shares["sysvol"], realm.lower(),
+ "scripts")
else:
- sid_generator_line = "sid generator = " + sid_generator
-
- sysvol = os.path.join(lp.get("lock dir"), "sysvol")
- netlogon = os.path.join(sysvol, realm.lower(), "scripts")
-
- setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
- smbconf, {
- "NETBIOS_NAME": netbiosname,
- "DOMAIN": domain,
- "REALM": realm,
- "SERVERROLE": serverrole,
- "NETLOGONPATH": netlogon,
- "SYSVOLPATH": sysvol,
- "SIDGENERATOR_LINE": sid_generator_line,
- "PRIVATEDIR_LINE": privatedir_line,
- "LOCKDIR_LINE": lockdir_line
- })
+ global_settings["passdb backend"] = "samba_dsdb"
+ f = open(smbconf, 'w')
+ try:
+ f.write("[globals]\n")
+ for key, val in global_settings.iteritems():
+ f.write("\t%s = %s\n" % (key, val))
+ f.write("\n")
+
+ for name, path in shares.iteritems():
+ f.write("[%s]\n" % name)
+ f.write("\tpath = %s\n" % path)
+ f.write("\tread only = no\n")
+ f.write("\n")
+ finally:
+ f.close()
# reload the smb.conf
lp.load(smbconf)
@@ -636,13 +688,14 @@ def make_smbconf(smbconf, hostname, domain, realm, serverrole,
# this ensures that any smb.conf parameters that were set
# on the provision/join command line are set in the resulting smb.conf
f = open(smbconf, mode='w')
- lp.dump(f, False)
- f.close()
-
+ try:
+ lp.dump(f, False)
+ finally:
+ f.close()
-def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
- users_gid, wheel_gid):
+def setup_name_mappings(idmap, sid, root_uid, nobody_uid,
+ users_gid, root_gid):
"""setup reasonable name mappings for sam names to unix names.
:param samdb: SamDB object.
@@ -652,10 +705,9 @@ def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
:param root_uid: uid of the UNIX root user.
:param nobody_uid: uid of the UNIX nobody user.
:param users_gid: gid of the UNIX users group.
- :param wheel_gid: gid of the UNIX wheel group.
+ :param root_gid: gid of the UNIX root group.
"""
idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
- idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
@@ -689,17 +741,14 @@ def setup_samdb_partitions(samdb_path, logger, lp, session_info,
lp=lp, options=["modules:"])
ldap_backend_line = "# No LDAP backend"
- if provision_backend.type is not "ldb":
+ if provision_backend.type != "ldb":
ldap_backend_line = "ldapBackend: %s" % provision_backend.ldap_uri
samdb.transaction_start()
try:
logger.info("Setting up sam.ldb partitions and settings")
setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
- "SCHEMADN": ldb.Dn(schema.ldb, names.schemadn).get_casefold(),
- "CONFIGDN": ldb.Dn(schema.ldb, names.configdn).get_casefold(),
- "DOMAINDN": ldb.Dn(schema.ldb, names.domaindn).get_casefold(),
- "LDAP_BACKEND_LINE": ldap_backend_line,
+ "LDAP_BACKEND_LINE": ldap_backend_line
})
@@ -710,7 +759,7 @@ def setup_samdb_partitions(samdb_path, logger, lp, session_info,
logger.info("Setting up sam.ldb rootDSE")
setup_samdb_rootdse(samdb, names)
- except Exception:
+ except:
samdb.transaction_cancel()
raise
else:
@@ -766,7 +815,7 @@ def secretsdb_self_join(secretsdb, domain,
# but we don't delete the old record that we are about to modify,
# because that would delete the keytab and previous password.
res = secretsdb.search(base="cn=Primary Domains", attrs=attrs,
- expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(dn=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
+ expression=("(&(|(flatname=%s)(realm=%s)(objectSid=%s))(objectclass=primaryDomain)(!(distinguishedName=%s)))" % (domain, realm, str(domainsid), str(msg.dn))),
scope=ldb.SCOPE_ONELEVEL)
for del_msg in res:
@@ -804,29 +853,6 @@ def secretsdb_self_join(secretsdb, domain,
secretsdb.add(msg)
-def secretsdb_setup_dns(secretsdb, names, private_dir, realm,
- dnsdomain, dns_keytab_path, dnspass):
- """Add DNS specific bits to a secrets database.
-
- :param secretsdb: Ldb Handle to the secrets database
- :param machinepass: Machine password
- """
- try:
- os.unlink(os.path.join(private_dir, dns_keytab_path))
- except OSError:
- pass
-
- setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
- "REALM": realm,
- "DNSDOMAIN": dnsdomain,
- "DNS_KEYTAB": dns_keytab_path,
- "DNSPASS_B64": b64encode(dnspass),
- "HOSTNAME": names.hostname,
- "DNSNAME" : '%s.%s' % (
- names.netbiosname.lower(), names.dnsdomain.lower())
- })
-
-
def setup_secretsdb(paths, session_info, backend_credentials, lp):
"""Setup the secrets database.
@@ -852,12 +878,10 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp):
path = paths.secrets
- secrets_ldb = Ldb(path, session_info=session_info,
- lp=lp)
+ secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
secrets_ldb.erase()
secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
- secrets_ldb = Ldb(path, session_info=session_info,
- lp=lp)
+ secrets_ldb = Ldb(path, session_info=session_info, lp=lp)
secrets_ldb.transaction_start()
try:
secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
@@ -877,11 +901,10 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp):
"LDAPADMINREALM": backend_credentials.get_realm(),
"LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
})
-
- return secrets_ldb
- except Exception:
+ except:
secrets_ldb.transaction_cancel()
raise
+ return secrets_ldb
def setup_privileges(path, session_info, lp):
@@ -941,22 +964,26 @@ def setup_samdb_rootdse(samdb, names):
setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
"SCHEMADN": names.schemadn,
"DOMAINDN": names.domaindn,
- "ROOTDN": names.rootdn,
+ "ROOTDN" : names.rootdn,
"CONFIGDN": names.configdn,
"SERVERDN": names.serverdn,
})
-def setup_self_join(samdb, names, machinepass, dnspass,
- domainsid, next_rid, invocationid,
- policyguid, policyguid_dc, domainControllerFunctionality,
- ntdsguid):
+def setup_self_join(samdb, admin_session_info, names, fill, machinepass,
+ dns_backend, dnspass, domainsid, next_rid, invocationid,
+ policyguid, policyguid_dc,
+ domainControllerFunctionality, ntdsguid=None, dc_rid=None):
"""Join a host to its own domain."""
assert isinstance(invocationid, str)
if ntdsguid is not None:
ntdsguid_line = "objectGUID: %s\n"%ntdsguid
else:
ntdsguid_line = ""
+
+ if dc_rid is None:
+ dc_rid = next_rid
+
setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
"CONFIGDN": names.configdn,
"SCHEMADN": names.schemadn,
@@ -967,11 +994,13 @@ def setup_self_join(samdb, names, machinepass, dnspass,
"DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
"MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
"DOMAINSID": str(domainsid),
- "DCRID": str(next_rid),
+ "DCRID": str(dc_rid),
"SAMBA_VERSION_STRING": version,
"NTDSGUID": ntdsguid_line,
"DOMAIN_CONTROLLER_FUNCTIONALITY": str(
- domainControllerFunctionality)})
+ domainControllerFunctionality),
+ "RIDALLOCATIONSTART": str(next_rid + 100),
+ "RIDALLOCATIONEND": str(next_rid + 100 + 499)})
setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
"POLICYGUID": policyguid,
@@ -979,27 +1008,52 @@ def setup_self_join(samdb, names, machinepass, dnspass,
"DNSDOMAIN": names.dnsdomain,
"DOMAINDN": names.domaindn})
- # add the NTDSGUID based SPNs
- ntds_dn = "CN=NTDS Settings,%s" % names.serverdn
- names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
- expression="", scope=ldb.SCOPE_BASE)
- assert isinstance(names.ntdsguid, str)
+ # If we are setting up a subdomain, then this has been replicated in, so we
+ # don't need to add it
+ if fill == FILL_FULL:
+ setup_add_ldif(samdb, setup_path("provision_self_join_config.ldif"), {
+ "CONFIGDN": names.configdn,
+ "SCHEMADN": names.schemadn,
+ "DOMAINDN": names.domaindn,
+ "SERVERDN": names.serverdn,
+ "INVOCATIONID": invocationid,
+ "NETBIOSNAME": names.netbiosname,
+ "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
+ "MACHINEPASS_B64": b64encode(machinepass.encode('utf-16-le')),
+ "DOMAINSID": str(domainsid),
+ "DCRID": str(dc_rid),
+ "SAMBA_VERSION_STRING": version,
+ "NTDSGUID": ntdsguid_line,
+ "DOMAIN_CONTROLLER_FUNCTIONALITY": str(
+ domainControllerFunctionality)})
# Setup fSMORoleOwner entries to point at the newly created DC entry
+ setup_modify_ldif(samdb,
+ setup_path("provision_self_join_modify_config.ldif"), {
+ "CONFIGDN": names.configdn,
+ "SCHEMADN": names.schemadn,
+ "DEFAULTSITE": names.sitename,
+ "NETBIOSNAME": names.netbiosname,
+ "SERVERDN": names.serverdn,
+ })
+
+ system_session_info = system_session()
+ samdb.set_session_info(system_session_info)
+ # Setup fSMORoleOwner entries to point at the newly created DC entry to
+ # modify a serverReference under cn=config when we are a subdomain, we must
+ # be system due to ACLs
setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
"DOMAINDN": names.domaindn,
- "CONFIGDN": names.configdn,
- "SCHEMADN": names.schemadn,
- "DEFAULTSITE": names.sitename,
"SERVERDN": names.serverdn,
"NETBIOSNAME": names.netbiosname,
- "RIDALLOCATIONSTART": str(next_rid + 100),
- "RIDALLOCATIONEND": str(next_rid + 100 + 499),
})
- # This is partially Samba4 specific and should be replaced by the correct
- # DNS AD-style setup
- setup_add_ldif(samdb, setup_path("provision_dns_add.ldif"), {
+ samdb.set_session_info(admin_session_info)
+
+ if dns_backend != "SAMBA_INTERNAL":
+ # This is Samba4 specific and should be replaced by the correct
+ # DNS AD-style setup
+ setup_add_ldif(samdb, setup_path("provision_dns_add_samba.ldif"), {
"DNSDOMAIN": names.dnsdomain,
"DOMAINDN": names.domaindn,
"DNSPASS_B64": b64encode(dnspass.encode('utf-16-le')),
@@ -1017,7 +1071,6 @@ def getpolicypath(sysvolpath, dnsdomain, guid):
:param guid: The GUID of the policy
:return: A string with the complete path to the policy folder
"""
-
if guid[0] != "{":
guid = "{%s}" % guid
policy_path = os.path.join(sysvolpath, dnsdomain, "Policies", guid)
@@ -1027,8 +1080,11 @@ def getpolicypath(sysvolpath, dnsdomain, guid):
def create_gpo_struct(policy_path):
if not os.path.exists(policy_path):
os.makedirs(policy_path, 0775)
- open(os.path.join(policy_path, "GPT.INI"), 'w').write(
- "[General]\r\nVersion=0")
+ f = open(os.path.join(policy_path, "GPT.INI"), 'w')
+ try:
+ f.write("[General]\r\nVersion=0")
+ finally:
+ f.close()
p = os.path.join(policy_path, "MACHINE")
if not os.path.exists(p):
os.makedirs(p, 0775)
@@ -1053,15 +1109,52 @@ def create_default_gpo(sysvolpath, dnsdomain, policyguid, policyguid_dc):
def setup_samdb(path, session_info, provision_backend, lp, names,
- logger, domainsid, domainguid, policyguid, policyguid_dc, fill,
- adminpass, krbtgtpass, machinepass, invocationid, dnspass, ntdsguid,
- serverrole, am_rodc=False, dom_for_fun_level=None, schema=None,
- next_rid=1000):
+ logger, fill, serverrole, schema, am_rodc=False):
"""Setup a complete SAM Database.
:note: This will wipe the main SAM database file!
"""
+ # Also wipes the database
+ setup_samdb_partitions(path, logger=logger, lp=lp,
+ provision_backend=provision_backend, session_info=session_info,
+ names=names, serverrole=serverrole, schema=schema)
+
+ # Load the database, but don's load the global schema and don't connect
+ # quite yet
+ samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
+ credentials=provision_backend.credentials, lp=lp,
+ global_schema=False, am_rodc=am_rodc)
+
+ logger.info("Pre-loading the Samba 4 and AD schema")
+
+ # Load the schema from the one we computed earlier
+ samdb.set_schema(schema, write_indices_and_attributes=False)
+
+ # Set the NTDS settings DN manually - in order to have it already around
+ # before the provisioned tree exists and we connect
+ samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
+
+ # And now we can connect to the DB - the schema won't be loaded from the
+ # DB
+ samdb.connect(path)
+
+ # But we have to give it one more kick to have it use the schema
+ # during provision - it needs, now that it is connected, to write
+ # the schema @ATTRIBUTES and @INDEXLIST records to the database.
+ samdb.set_schema(schema, write_indices_and_attributes=True)
+
+ return samdb
+
+
+def fill_samdb(samdb, lp, names, logger, domainsid, domainguid, policyguid,
+ policyguid_dc, fill, adminpass, krbtgtpass, machinepass, dns_backend,
+ dnspass, invocationid, ntdsguid, serverrole, am_rodc=False,
+ dom_for_fun_level=None, schema=None, next_rid=None, dc_rid=None):
+
+ if next_rid is None:
+ next_rid = 1000
+
# Provision does not make much sense values larger than 1000000000
# as the upper range of the rIDAvailablePool is 1073741823 and
# we don't want to create a domain that cannot allocate rids.
@@ -1084,36 +1177,10 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
domainFunctionality = dom_for_fun_level
forestFunctionality = dom_for_fun_level
- # Also wipes the database
- setup_samdb_partitions(path, logger=logger, lp=lp,
- provision_backend=provision_backend, session_info=session_info,
- names=names, serverrole=serverrole, schema=schema)
-
- if schema is None:
- schema = Schema(domainsid, schemadn=names.schemadn)
-
- # Load the database, but don's load the global schema and don't connect
- # quite yet
- samdb = SamDB(session_info=session_info, url=None, auto_connect=False,
- credentials=provision_backend.credentials, lp=lp,
- global_schema=False, am_rodc=am_rodc)
-
- logger.info("Pre-loading the Samba 4 and AD schema")
-
- # Load the schema from the one we computed earlier
- samdb.set_schema(schema)
-
# Set the NTDS settings DN manually - in order to have it already around
# before the provisioned tree exists and we connect
samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
- # And now we can connect to the DB - the schema won't be loaded from the
- # DB
- samdb.connect(path)
-
- if fill == FILL_DRS:
- return samdb
-
samdb.transaction_start()
try:
# Set the domain functionality levels onto the database.
@@ -1150,7 +1217,7 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
"DOMAINDN": names.domaindn,
- "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
+ "CREATTIME": str(samba.unix2nttime(int(time.time()))),
"NEXTRID": str(next_rid),
"DEFAULTSITE": names.sitename,
"CONFIGDN": names.configdn,
@@ -1159,115 +1226,138 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
"SAMBA_VERSION_STRING": version
})
- logger.info("Adding configuration container")
- descr = b64encode(get_config_descriptor(domainsid))
- setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
- "CONFIGDN": names.configdn,
- "DESCRIPTOR": descr,
- })
-
- # The LDIF here was created when the Schema object was constructed
- logger.info("Setting up sam.ldb schema")
- samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
- samdb.modify_ldif(schema.schema_dn_modify)
- samdb.write_prefixes_from_schema()
- samdb.add_ldif(schema.schema_data, controls=["relax:0"])
- setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
- {"SCHEMADN": names.schemadn})
-
- logger.info("Reopening sam.ldb with new schema")
- except Exception:
+ # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+ if fill == FILL_FULL:
+ logger.info("Adding configuration container")
+ descr = b64encode(get_config_descriptor(domainsid))
+ setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
+ "CONFIGDN": names.configdn,
+ "DESCRIPTOR": descr,
+ })
+
+ # The LDIF here was created when the Schema object was constructed
+ logger.info("Setting up sam.ldb schema")
+ samdb.add_ldif(schema.schema_dn_add, controls=["relax:0"])
+ samdb.modify_ldif(schema.schema_dn_modify)
+ samdb.write_prefixes_from_schema()
+ samdb.add_ldif(schema.schema_data, controls=["relax:0"])
+ setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
+ {"SCHEMADN": names.schemadn})
+
+ # Now register this container in the root of the forest
+ msg = ldb.Message(ldb.Dn(samdb, names.domaindn))
+ msg["subRefs"] = ldb.MessageElement(names.configdn , ldb.FLAG_MOD_ADD,
+ "subRefs")
+
+ except:
samdb.transaction_cancel()
raise
else:
samdb.transaction_commit()
- samdb = SamDB(session_info=admin_session_info, auto_connect=False,
- credentials=provision_backend.credentials, lp=lp,
- global_schema=False, am_rodc=am_rodc)
-
- # Set the NTDS settings DN manually - in order to have it already around
- # before the provisioned tree exists and we connect
- samdb.set_ntds_settings_dn("CN=NTDS Settings,%s" % names.serverdn)
- samdb.connect(path)
-
samdb.transaction_start()
try:
samdb.invocation_id = invocationid
- logger.info("Setting up sam.ldb configuration data")
- descr = b64encode(get_sites_descriptor(domainsid))
- setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
- "CONFIGDN": names.configdn,
- "NETBIOSNAME": names.netbiosname,
- "DEFAULTSITE": names.sitename,
- "DNSDOMAIN": names.dnsdomain,
- "DOMAIN": names.domain,
- "SCHEMADN": names.schemadn,
- "DOMAINDN": names.domaindn,
- "SERVERDN": names.serverdn,
- "FOREST_FUNCTIONALITY": str(forestFunctionality),
- "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
- "SITES_DESCRIPTOR": descr
- })
-
- logger.info("Setting up display specifiers")
- display_specifiers_ldif = read_ms_ldif(
- setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
- display_specifiers_ldif = substitute_var(display_specifiers_ldif,
- {"CONFIGDN": names.configdn})
- check_all_substituted(display_specifiers_ldif)
- samdb.add_ldif(display_specifiers_ldif)
+ # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+ if fill == FILL_FULL:
+ logger.info("Setting up sam.ldb configuration data")
+ partitions_descr = b64encode(get_config_partitions_descriptor(domainsid))
+ sites_descr = b64encode(get_config_sites_descriptor(domainsid))
+ setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
+ "CONFIGDN": names.configdn,
+ "NETBIOSNAME": names.netbiosname,
+ "DEFAULTSITE": names.sitename,
+ "DNSDOMAIN": names.dnsdomain,
+ "DOMAIN": names.domain,
+ "SCHEMADN": names.schemadn,
+ "DOMAINDN": names.domaindn,
+ "SERVERDN": names.serverdn,
+ "FOREST_FUNCTIONALITY": str(forestFunctionality),
+ "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
+ "PARTITIONS_DESCRIPTOR": partitions_descr,
+ "SITES_DESCRIPTOR": sites_descr,
+ })
+
+ logger.info("Setting up display specifiers")
+ display_specifiers_ldif = read_ms_ldif(
+ setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
+ display_specifiers_ldif = substitute_var(display_specifiers_ldif,
+ {"CONFIGDN": names.configdn})
+ check_all_substituted(display_specifiers_ldif)
+ samdb.add_ldif(display_specifiers_ldif)
logger.info("Adding users container")
+ users_desc = b64encode(get_domain_users_descriptor(domainsid))
setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
- "DOMAINDN": names.domaindn})
+ "DOMAINDN": names.domaindn,
+ "USERS_DESCRIPTOR": users_desc
+ })
logger.info("Modifying users container")
setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
"DOMAINDN": names.domaindn})
logger.info("Adding computers container")
+ computers_desc = b64encode(get_domain_computers_descriptor(domainsid))
setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
- "DOMAINDN": names.domaindn})
+ "DOMAINDN": names.domaindn,
+ "COMPUTERS_DESCRIPTOR": computers_desc
+ })
logger.info("Modifying computers container")
setup_modify_ldif(samdb,
setup_path("provision_computers_modify.ldif"), {
"DOMAINDN": names.domaindn})
logger.info("Setting up sam.ldb data")
+ infrastructure_desc = b64encode(get_domain_infrastructure_descriptor(domainsid))
+ builtin_desc = b64encode(get_domain_builtin_descriptor(domainsid))
+ controllers_desc = b64encode(get_domain_controllers_descriptor(domainsid))
setup_add_ldif(samdb, setup_path("provision.ldif"), {
- "CREATTIME": str(int(time.time() * 1e7)), # seconds -> ticks
+ "CREATTIME": str(samba.unix2nttime(int(time.time()))),
"DOMAINDN": names.domaindn,
"NETBIOSNAME": names.netbiosname,
"DEFAULTSITE": names.sitename,
"CONFIGDN": names.configdn,
"SERVERDN": names.serverdn,
"RIDAVAILABLESTART": str(next_rid + 600),
- "POLICYGUID_DC": policyguid_dc
+ "POLICYGUID_DC": policyguid_dc,
+ "INFRASTRUCTURE_DESCRIPTOR": infrastructure_desc,
+ "BUILTIN_DESCRIPTOR": builtin_desc,
+ "DOMAIN_CONTROLLERS_DESCRIPTOR": controllers_desc,
})
- setup_modify_ldif(samdb,
- setup_path("provision_basedn_references.ldif"), {
- "DOMAINDN": names.domaindn})
+ # If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
+ if fill == FILL_FULL:
+ setup_modify_ldif(samdb,
+ setup_path("provision_configuration_references.ldif"), {
+ "CONFIGDN": names.configdn,
+ "SCHEMADN": names.schemadn})
- setup_modify_ldif(samdb,
- setup_path("provision_configuration_references.ldif"), {
+ logger.info("Setting up well known security principals")
+ setup_add_ldif(samdb, setup_path("provision_well_known_sec_princ.ldif"), {
"CONFIGDN": names.configdn,
- "SCHEMADN": names.schemadn})
- if fill == FILL_FULL:
+ })
+
+ if fill == FILL_FULL or fill == FILL_SUBDOMAIN:
+ setup_modify_ldif(samdb,
+ setup_path("provision_basedn_references.ldif"),
+ {"DOMAINDN": names.domaindn})
+
logger.info("Setting up sam.ldb users and groups")
setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
"DOMAINDN": names.domaindn,
"DOMAINSID": str(domainsid),
- "CONFIGDN": names.configdn,
"ADMINPASS_B64": b64encode(adminpass.encode('utf-16-le')),
"KRBTGTPASS_B64": b64encode(krbtgtpass.encode('utf-16-le'))
})
logger.info("Setting up self join")
- setup_self_join(samdb, names=names, invocationid=invocationid,
+ setup_self_join(samdb, admin_session_info, names=names, fill=fill,
+ invocationid=invocationid,
+ dns_backend=dns_backend,
dnspass=dnspass,
machinepass=machinepass,
domainsid=domainsid,
next_rid=next_rid,
+ dc_rid=dc_rid,
policyguid=policyguid,
policyguid_dc=policyguid_dc,
domainControllerFunctionality=domainControllerFunctionality,
@@ -1277,7 +1367,7 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
names.ntdsguid = samdb.searchone(basedn=ntds_dn,
attribute="objectGUID", expression="", scope=ldb.SCOPE_BASE)
assert isinstance(names.ntdsguid, str)
- except Exception:
+ except:
samdb.transaction_cancel()
raise
else:
@@ -1286,22 +1376,25 @@ def setup_samdb(path, session_info, provision_backend, lp, names,
FILL_FULL = "FULL"
+FILL_SUBDOMAIN = "SUBDOMAIN"
FILL_NT4SYNC = "NT4SYNC"
FILL_DRS = "DRS"
SYSVOL_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)"
POLICIES_ACL = "O:LAG:BAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)(A;OICI;0x001301bf;;;PA)"
-def set_dir_acl(path, acl, lp, domsid):
- setntacl(lp, path, acl, domsid)
+def set_dir_acl(path, acl, lp, domsid, use_ntvfs, passdb):
+ setntacl(lp, path, acl, domsid, use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb)
for root, dirs, files in os.walk(path, topdown=False):
for name in files:
- setntacl(lp, os.path.join(root, name), acl, domsid)
+ setntacl(lp, os.path.join(root, name), acl, domsid,
+ use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb)
for name in dirs:
- setntacl(lp, os.path.join(root, name), acl, domsid)
+ setntacl(lp, os.path.join(root, name), acl, domsid,
+ use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb)
-def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
+def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb):
"""Set ACL on the sysvol/<dnsname>/Policies folder and the policy
folders beneath.
@@ -1315,7 +1408,8 @@ def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
# Set ACL for GPO root folder
root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
- setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid))
+ setntacl(lp, root_policy_path, POLICIES_ACL, str(domainsid),
+ use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=passdb)
res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
attrs=["cn", "nTSecurityDescriptor"],
@@ -1325,69 +1419,216 @@ def set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp):
acl = ndr_unpack(security.descriptor,
str(policy["nTSecurityDescriptor"])).as_sddl()
policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
- set_dir_acl(policy_path, dsacl2fsacl(acl, str(domainsid)), lp,
- str(domainsid))
+ set_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp,
+ str(domainsid), use_ntvfs,
+ passdb=passdb)
-def setsysvolacl(samdb, netlogon, sysvol, gid, domainsid, dnsdomain, domaindn,
- lp):
+def setsysvolacl(samdb, netlogon, sysvol, uid, gid, domainsid, dnsdomain,
+ domaindn, lp, use_ntvfs):
"""Set the ACL for the sysvol share and the subfolders
:param samdb: An LDB object on the SAM db
:param netlogon: Physical path for the netlogon folder
:param sysvol: Physical path for the sysvol folder
+ :param uid: The UID of the "Administrator" user
:param gid: The GID of the "Domain adminstrators" group
:param domainsid: The SID of the domain
:param dnsdomain: The DNS name of the domain
:param domaindn: The DN of the domain (ie. DC=...)
"""
+ s4_passdb = None
+
+ if not use_ntvfs:
+ # This will ensure that the smbd code we are running when setting ACLs
+ # is initialised with the smb.conf
+ 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)
+ passdb.reload_static_pdb()
+
+ # ensure that we init the samba_dsdb backend, so the domain sid is
+ # marked in secrets.tdb
+ s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
+
+ # now ensure everything matches correctly, to avoid wierd issues
+ if passdb.get_global_sam_sid() != domainsid:
+ raise ProvisioningError('SID as seen by smbd [%s] does not match SID as seen by the provision script [%s]!' % (passdb.get_global_sam_sid(), domainsid))
+
+ domain_info = s4_passdb.domain_info()
+ if domain_info["dom_sid"] != domainsid:
+ raise ProvisioningError('SID as seen by pdb_samba_dsdb [%s] does not match SID as seen by the provision script [%s]!' % (domain_info["dom_sid"], domainsid))
+
+ if domain_info["dns_domain"].upper() != dnsdomain.upper():
+ raise ProvisioningError('Realm as seen by pdb_samba_dsdb [%s] does not match Realm as seen by the provision script [%s]!' % (domain_info["dns_domain"].upper(), dnsdomain.upper()))
+
try:
- os.chown(sysvol, -1, gid)
+ if use_ntvfs:
+ os.chown(sysvol, -1, gid)
except OSError:
canchown = False
else:
canchown = True
# Set the SYSVOL_ACL on the sysvol folder and subfolder (first level)
- setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid))
+ setntacl(lp,sysvol, SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb)
for root, dirs, files in os.walk(sysvol, topdown=False):
for name in files:
- if canchown:
+ if use_ntvfs and canchown:
os.chown(os.path.join(root, name), -1, gid)
- setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
+ setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb)
for name in dirs:
- if canchown:
+ if use_ntvfs and canchown:
os.chown(os.path.join(root, name), -1, gid)
- setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid))
+ setntacl(lp, os.path.join(root, name), SYSVOL_ACL, str(domainsid), use_ntvfs=use_ntvfs, skip_invalid_chown=True, passdb=s4_passdb)
# Set acls on Policy folder and policies folders
- set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp)
+ set_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp, use_ntvfs, passdb=s4_passdb)
+def acl_type(direct_db_access):
+ if direct_db_access:
+ return "DB"
+ else:
+ return "VFS"
-def provision(logger, session_info, credentials, smbconf=None,
- targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
- domaindn=None, schemadn=None, configdn=None, serverdn=None,
- domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
- next_rid=1000, adminpass=None, ldapadminpass=None, krbtgtpass=None,
- domainguid=None, policyguid=None, policyguid_dc=None,
- invocationid=None, machinepass=None, ntdsguid=None, dnspass=None,
- root=None, nobody=None, users=None, wheel=None, backup=None, aci=None,
- serverrole=None, dom_for_fun_level=None, ldap_backend_extra_port=None,
- ldap_backend_forced_uri=None, backend_type=None, sitename=None,
- ol_mmr_urls=None, ol_olc=None, setup_ds_path=None, slapd_path=None,
- nosync=False, ldap_dryrun_mode=False, useeadb=False, am_rodc=False,
- lp=None):
- """Provision samba4
+def check_dir_acl(path, acl, lp, domainsid, direct_db_access):
+ fsacl = getntacl(lp, path, direct_db_access=direct_db_access)
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != acl:
+ raise ProvisioningError('%s ACL on GPO directory %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), path, fsacl_sddl, acl))
- :note: caution, this wipes all existing data!
+ for root, dirs, files in os.walk(path, topdown=False):
+ for name in files:
+ fsacl = getntacl(lp, os.path.join(root, name), direct_db_access=direct_db_access)
+ if fsacl is None:
+ raise ProvisioningError('%s ACL on GPO file %s %s not found!' % (acl_type(direct_db_access), os.path.join(root, name)))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != acl:
+ raise ProvisioningError('%s ACL on GPO file %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl))
+
+ for name in dirs:
+ fsacl = getntacl(lp, os.path.join(root, name), direct_db_access=direct_db_access)
+ if fsacl is None:
+ raise ProvisioningError('%s ACL on GPO directory %s %s not found!' % (acl_type(direct_db_access), os.path.join(root, name)))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != acl:
+ raise ProvisioningError('%s ACL on GPO directory %s %s does not match expected value %s from GPO object' % (acl_type(direct_db_access), os.path.join(root, name), fsacl_sddl, acl))
+
+
+def check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
+ direct_db_access):
+ """Set ACL on the sysvol/<dnsname>/Policies folder and the policy
+ folders beneath.
+
+ :param sysvol: Physical path for the sysvol folder
+ :param dnsdomain: The DNS name of the domain
+ :param domainsid: The SID of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ :param samdb: An LDB object on the SAM db
+ :param lp: an LP object
"""
- if domainsid is None:
- domainsid = security.random_sid()
- else:
- domainsid = security.dom_sid(domainsid)
+ # Set ACL for GPO root folder
+ root_policy_path = os.path.join(sysvol, dnsdomain, "Policies")
+ fsacl = getntacl(lp, root_policy_path, direct_db_access=direct_db_access)
+ if fsacl is None:
+ raise ProvisioningError('DB ACL on policy root %s %s not found!' % (acl_type(direct_db_access), root_policy_path))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != POLICIES_ACL:
+ raise ProvisioningError('%s ACL on policy root %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), root_policy_path, fsacl_sddl, fsacl))
+ res = samdb.search(base="CN=Policies,CN=System,%s"%(domaindn),
+ attrs=["cn", "nTSecurityDescriptor"],
+ expression="", scope=ldb.SCOPE_ONELEVEL)
+
+ for policy in res:
+ acl = ndr_unpack(security.descriptor,
+ str(policy["nTSecurityDescriptor"])).as_sddl()
+ policy_path = getpolicypath(sysvol, dnsdomain, str(policy["cn"]))
+ check_dir_acl(policy_path, dsacl2fsacl(acl, domainsid), lp,
+ domainsid, direct_db_access)
+
+
+def checksysvolacl(samdb, netlogon, sysvol, domainsid, dnsdomain, domaindn,
+ lp):
+ """Set the ACL for the sysvol share and the subfolders
+
+ :param samdb: An LDB object on the SAM db
+ :param netlogon: Physical path for the netlogon folder
+ :param sysvol: Physical path for the sysvol folder
+ :param uid: The UID of the "Administrator" user
+ :param gid: The GID of the "Domain adminstrators" group
+ :param domainsid: The SID of the domain
+ :param dnsdomain: The DNS name of the domain
+ :param domaindn: The DN of the domain (ie. DC=...)
+ """
+ # This will ensure that the smbd code we are running when setting ACLs is initialised with the smb.conf
+ 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)
+ # ensure that we init the samba_dsdb backend, so the domain sid is marked in secrets.tdb
+ s4_passdb = passdb.PDB(s3conf.get("passdb backend"))
+
+ # now ensure everything matches correctly, to avoid wierd issues
+ if passdb.get_global_sam_sid() != domainsid:
+ raise ProvisioningError('SID as seen by smbd [%s] does not match SID as seen by the provision script [%s]!' % (passdb.get_global_sam_sid(), domainsid))
+
+ domain_info = s4_passdb.domain_info()
+ if domain_info["dom_sid"] != domainsid:
+ raise ProvisioningError('SID as seen by pdb_samba_dsdb [%s] does not match SID as seen by the provision script [%s]!' % (domain_info["dom_sid"], domainsid))
+
+ if domain_info["dns_domain"].upper() != dnsdomain.upper():
+ raise ProvisioningError('Realm as seen by pdb_samba_dsdb [%s] does not match Realm as seen by the provision script [%s]!' % (domain_info["dns_domain"].upper(), dnsdomain.upper()))
+
+ # Ensure we can read this directly, and via the smbd VFS
+ for direct_db_access in [True, False]:
+ # Check the SYSVOL_ACL on the sysvol folder and subfolder (first level)
+ for dir_path in [os.path.join(sysvol, dnsdomain), netlogon]:
+ fsacl = getntacl(lp, dir_path, direct_db_access=direct_db_access)
+ if fsacl is None:
+ raise ProvisioningError('%s ACL on sysvol directory %s not found!' % (acl_type(direct_db_access), dir_path))
+ fsacl_sddl = fsacl.as_sddl(domainsid)
+ if fsacl_sddl != SYSVOL_ACL:
+ raise ProvisioningError('%s ACL on sysvol directory %s %s does not match expected value %s from provision' % (acl_type(direct_db_access), dir_path, fsacl_sddl, SYSVOL_ACL))
+
+ # Check acls on Policy folder and policies folders
+ check_gpos_acl(sysvol, dnsdomain, domainsid, domaindn, samdb, lp,
+ direct_db_access)
+
+
+def interface_ips_v4(lp):
+ """return only IPv4 IPs"""
+ ips = samba.interface_ips(lp, False)
+ ret = []
+ for i in ips:
+ if i.find(':') == -1:
+ ret.append(i)
+ return ret
+
+
+def interface_ips_v6(lp, linklocal=False):
+ """return only IPv6 IPs"""
+ ips = samba.interface_ips(lp, False)
+ ret = []
+ for i in ips:
+ if i.find(':') != -1 and (linklocal or i.find('%') == -1):
+ ret.append(i)
+ return ret
+
+
+def provision_fill(samdb, secrets_ldb, logger, names, paths,
+ domainsid, schema=None,
+ targetdir=None, samdb_fill=FILL_FULL,
+ hostip=None, hostip6=None,
+ next_rid=1000, dc_rid=None, adminpass=None, krbtgtpass=None,
+ domainguid=None, policyguid=None, policyguid_dc=None,
+ invocationid=None, machinepass=None, ntdsguid=None,
+ dns_backend=None, dnspass=None,
+ serverrole=None, dom_for_fun_level=None,
+ am_rodc=False, lp=None, use_ntvfs=False, skip_sysvolacl=False):
# create/adapt the group policy GUIDs
# Default GUID for default policy are described at
# "How Core Group Policy Works"
@@ -1399,32 +1640,198 @@ def provision(logger, session_info, credentials, smbconf=None,
policyguid_dc = DEFAULT_DC_POLICY_GUID
policyguid_dc = policyguid_dc.upper()
- if adminpass is None:
- adminpass = samba.generate_random_password(12, 32)
+ if invocationid is None:
+ invocationid = str(uuid.uuid4())
+
if krbtgtpass is None:
krbtgtpass = samba.generate_random_password(128, 255)
if machinepass is None:
machinepass = samba.generate_random_password(128, 255)
if dnspass is None:
dnspass = samba.generate_random_password(128, 255)
+
+ samdb = fill_samdb(samdb, lp, names, logger=logger,
+ domainsid=domainsid, schema=schema, domainguid=domainguid,
+ policyguid=policyguid, policyguid_dc=policyguid_dc,
+ fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
+ invocationid=invocationid, machinepass=machinepass,
+ dns_backend=dns_backend, dnspass=dnspass,
+ ntdsguid=ntdsguid, serverrole=serverrole,
+ dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
+ next_rid=next_rid, dc_rid=dc_rid)
+
+ if serverrole == "active directory domain controller":
+
+ # Set up group policies (domain policy and domain controller
+ # policy)
+ create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
+ policyguid_dc)
+ if not skip_sysvolacl:
+ setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.root_uid,
+ paths.root_gid, domainsid, names.dnsdomain,
+ names.domaindn, lp, use_ntvfs)
+ else:
+ logger.info("Setting acl on sysvol skipped")
+
+ secretsdb_self_join(secrets_ldb, domain=names.domain,
+ realm=names.realm, dnsdomain=names.dnsdomain,
+ netbiosname=names.netbiosname, domainsid=domainsid,
+ machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
+
+ # Now set up the right msDS-SupportedEncryptionTypes into the DB
+ # In future, this might be determined from some configuration
+ kerberos_enctypes = str(ENC_ALL_TYPES)
+
+ try:
+ msg = ldb.Message(ldb.Dn(samdb,
+ samdb.searchone("distinguishedName",
+ expression="samAccountName=%s$" % names.netbiosname,
+ scope=ldb.SCOPE_SUBTREE)))
+ msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
+ elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
+ name="msDS-SupportedEncryptionTypes")
+ samdb.modify(msg)
+ except ldb.LdbError, (enum, estr):
+ if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
+ # It might be that this attribute does not exist in this schema
+ raise
+
+ setup_ad_dns(samdb, secrets_ldb, domainsid, names, paths, lp, logger,
+ hostip=hostip, hostip6=hostip6, dns_backend=dns_backend,
+ dnspass=dnspass, os_level=dom_for_fun_level,
+ targetdir=targetdir, site=DEFAULTSITE)
+
+ domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
+ attribute="objectGUID")
+ assert isinstance(domainguid, str)
+
+ lastProvisionUSNs = get_last_provision_usn(samdb)
+ maxUSN = get_max_usn(samdb, str(names.rootdn))
+ if lastProvisionUSNs is not None:
+ update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
+ else:
+ set_provision_usn(samdb, 0, maxUSN, invocationid)
+
+ logger.info("Setting up sam.ldb rootDSE marking as synchronized")
+ setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
+ { 'NTDSGUID' : names.ntdsguid })
+
+ # fix any dangling GUIDs from the provision
+ logger.info("Fixing provision GUIDs")
+ chk = dbcheck(samdb, samdb_schema=samdb, verbose=False, fix=True, yes=True,
+ quiet=True)
+ samdb.transaction_start()
+ try:
+ # a small number of GUIDs are missing because of ordering issues in the
+ # provision code
+ for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
+ chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
+ scope=ldb.SCOPE_BASE,
+ attrs=['defaultObjectCategory'])
+ chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
+ scope=ldb.SCOPE_ONELEVEL,
+ attrs=['ipsecOwnersReference',
+ 'ipsecFilterReference',
+ 'ipsecISAKMPReference',
+ 'ipsecNegotiationPolicyReference',
+ 'ipsecNFAReference'])
+ except:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+
+
+_ROLES_MAP = {
+ "ROLE_STANDALONE": "standalone server",
+ "ROLE_DOMAIN_MEMBER": "member server",
+ "ROLE_DOMAIN_BDC": "active directory domain controller",
+ "ROLE_DOMAIN_PDC": "active directory domain controller",
+ "dc": "active directory domain controller",
+ "member": "member server",
+ "domain controller": "active directory domain controller",
+ "active directory domain controller": "active directory domain controller",
+ "member server": "member server",
+ "standalone": "standalone server",
+ "standalone server": "standalone server",
+ }
+
+
+def sanitize_server_role(role):
+ """Sanitize a server role name.
+
+ :param role: Server role
+ :raise ValueError: If the role can not be interpreted
+ :return: Sanitized server role (one of "member server",
+ "active directory domain controller", "standalone server")
+ """
+ try:
+ return _ROLES_MAP[role]
+ except KeyError:
+ raise ValueError(role)
+
+
+def provision_fake_ypserver(logger, samdb, domaindn, netbiosname, nisdomain,
+ maxuid, maxgid):
+ """Create AD entries for the fake ypserver.
+
+ This is needed for being able to manipulate posix attrs via ADUC.
+ """
+ samdb.transaction_start()
+ try:
+ logger.info("Setting up fake yp server settings")
+ setup_add_ldif(samdb, setup_path("ypServ30.ldif"), {
+ "DOMAINDN": domaindn,
+ "NETBIOSNAME": netbiosname,
+ "NISDOMAIN": nisdomain,
+ })
+ except:
+ samdb.transaction_cancel()
+ raise
+ else:
+ samdb.transaction_commit()
+
+
+def provision(logger, session_info, credentials, smbconf=None,
+ targetdir=None, samdb_fill=FILL_FULL, realm=None, rootdn=None,
+ domaindn=None, schemadn=None, configdn=None, serverdn=None,
+ domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
+ next_rid=1000, dc_rid=None, adminpass=None, ldapadminpass=None,
+ krbtgtpass=None, domainguid=None, policyguid=None, policyguid_dc=None,
+ dns_backend=None, dns_forwarder=None, dnspass=None,
+ invocationid=None, machinepass=None, ntdsguid=None,
+ root=None, nobody=None, users=None, backup=None, aci=None,
+ serverrole=None, dom_for_fun_level=None, backend_type=None,
+ sitename=None, ol_mmr_urls=None, ol_olc=None, slapd_path="/bin/false",
+ useeadb=False, am_rodc=False, lp=None, use_ntvfs=False,
+ use_rfc2307=False, maxuid=None, maxgid=None, skip_sysvolacl=True):
+ """Provision samba4
+
+ :note: caution, this wipes all existing data!
+ """
+
+ try:
+ serverrole = sanitize_server_role(serverrole)
+ except ValueError:
+ raise ProvisioningError('server role (%s) should be one of "active directory domain controller", "member server", "standalone server"' % serverrole)
+
if ldapadminpass is None:
# Make a new, random password between Samba and it's LDAP server
- ldapadminpass=samba.generate_random_password(128, 255)
+ ldapadminpass = samba.generate_random_password(128, 255)
if backend_type is None:
backend_type = "ldb"
- sid_generator = "internal"
- if backend_type == "fedora-ds":
- sid_generator = "backend"
+ if domainsid is None:
+ domainsid = security.random_sid()
+ else:
+ domainsid = security.dom_sid(domainsid)
root_uid = findnss_uid([root or "root"])
nobody_uid = findnss_uid([nobody or "nobody"])
users_gid = findnss_gid([users or "users", 'users', 'other', 'staff'])
- if wheel is None:
- wheel_gid = findnss_gid(["wheel", "adm"])
- else:
- wheel_gid = findnss_gid([wheel])
+ root_gid = pwd.getpwuid(root_uid).pw_gid
+
try:
bind_gid = findnss_gid(["bind", "named"])
except KeyError:
@@ -1437,20 +1844,44 @@ def provision(logger, session_info, credentials, smbconf=None,
if not os.path.exists(os.path.dirname(smbconf)):
os.makedirs(os.path.dirname(smbconf))
+ server_services = []
+ global_param = {}
+ if use_rfc2307:
+ global_param["idmap_ldb:use rfc2307"] = ["yes"]
+
+ if dns_backend != "SAMBA_INTERNAL":
+ server_services.append("-dns")
+ else:
+ if dns_forwarder is not None:
+ global_param["dns forwarder"] = [dns_forwarder]
+
+ if use_ntvfs:
+ server_services.append("+smb")
+ server_services.append("-s3fs")
+ global_param["dcerpc endpoint servers"] = ["+winreg", "+srvsvc"]
+
+ if len(server_services) > 0:
+ global_param["server services"] = server_services
+
# only install a new smb.conf if there isn't one there already
if os.path.exists(smbconf):
# if Samba Team members can't figure out the weird errors
# loading an empty smb.conf gives, then we need to be smarter.
# Pretend it just didn't exist --abartlet
- data = open(smbconf, 'r').read()
- data = data.lstrip()
+ f = open(smbconf, 'r')
+ try:
+ data = f.read().lstrip()
+ finally:
+ f.close()
if data is None or data == "":
make_smbconf(smbconf, hostname, domain, realm,
- serverrole, targetdir, sid_generator, useeadb,
- lp=lp)
+ targetdir, serverrole=serverrole,
+ eadb=useeadb, use_ntvfs=use_ntvfs,
+ lp=lp, global_param=global_param)
else:
- make_smbconf(smbconf, hostname, domain, realm, serverrole,
- targetdir, sid_generator, useeadb, lp=lp)
+ make_smbconf(smbconf, hostname, domain, realm, targetdir,
+ serverrole=serverrole,
+ eadb=useeadb, use_ntvfs=use_ntvfs, lp=lp, global_param=global_param)
if lp is None:
lp = samba.param.LoadParm()
@@ -1458,34 +1889,77 @@ def provision(logger, session_info, credentials, smbconf=None,
names = guess_names(lp=lp, hostname=hostname, domain=domain,
dnsdomain=realm, serverrole=serverrole, domaindn=domaindn,
configdn=configdn, schemadn=schemadn, serverdn=serverdn,
- sitename=sitename)
+ sitename=sitename, rootdn=rootdn)
paths = provision_paths_from_lp(lp, names.dnsdomain)
paths.bind_gid = bind_gid
+ paths.root_uid = root_uid;
+ paths.root_gid = root_gid
if hostip is None:
logger.info("Looking up IPv4 addresses")
- hostips = samba.interface_ips(lp, False)
- if len(hostips) == 0:
- logger.warning("No external IPv4 address has been found. Using loopback.")
- hostip = '127.0.0.1'
- else:
+ hostips = interface_ips_v4(lp)
+ if len(hostips) > 0:
hostip = hostips[0]
if len(hostips) > 1:
- logger.warning("More than one IPv4 address found. Using %s.",
+ logger.warning("More than one IPv4 address found. Using %s",
hostip)
+ if hostip == "127.0.0.1":
+ hostip = None
+ if hostip is None:
+ logger.warning("No IPv4 address will be assigned")
+
+ if hostip6 is None:
+ logger.info("Looking up IPv6 addresses")
+ hostips = interface_ips_v6(lp, linklocal=False)
+ if hostips:
+ hostip6 = hostips[0]
+ if len(hostips) > 1:
+ logger.warning("More than one IPv6 address found. Using %s", hostip6)
+ if hostip6 is None:
+ logger.warning("No IPv6 address will be assigned")
+
+ names.hostip = hostip
+ names.hostip6 = hostip6
if serverrole is None:
serverrole = lp.get("server role")
- assert serverrole in ("domain controller", "member server", "standalone")
- if invocationid is None:
- invocationid = str(uuid.uuid4())
-
if not os.path.exists(paths.private_dir):
os.mkdir(paths.private_dir)
if not os.path.exists(os.path.join(paths.private_dir, "tls")):
os.mkdir(os.path.join(paths.private_dir, "tls"))
+ if not os.path.exists(paths.state_dir):
+ os.mkdir(paths.state_dir)
+
+ if paths.sysvol and not os.path.exists(paths.sysvol):
+ os.makedirs(paths.sysvol, 0775)
+
+ if not use_ntvfs and serverrole == "active directory domain controller":
+ s3conf = s3param.get_context()
+ s3conf.load(lp.configfile)
+
+ if paths.sysvol is None:
+ raise MissingShareError("sysvol", paths.smbconf)
+
+ file = tempfile.NamedTemporaryFile(dir=os.path.abspath(paths.sysvol))
+ try:
+ try:
+ smbd.set_simple_acl(file.name, 0755, root_gid)
+ except Exception:
+ if not smbd.have_posix_acls():
+ # This clue is only strictly correct for RPM and
+ # Debian-like Linux systems, but hopefully other users
+ # will get enough clue from it.
+ raise ProvisioningError("Samba was compiled without the posix ACL support that s3fs requires. Try installing libacl1-dev or libacl-devel, then re-run configure and make.")
+
+ raise ProvisioningError("Your filesystem or build does not support posix ACLs, which s3fs requires. Try the mounting the filesystem with the 'acl' option.")
+ try:
+ smbd.chown(file.name, root_uid, root_gid)
+ except Exception:
+ raise ProvisioningError("Unable to chown a file on your filesystem. You may not be running provision as root.")
+ finally:
+ file.close()
ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
@@ -1497,30 +1971,25 @@ def provision(logger, session_info, credentials, smbconf=None,
lp=lp, credentials=credentials,
names=names, logger=logger)
elif backend_type == "existing":
+ # If support for this is ever added back, then the URI will need to be
+ # specified again
provision_backend = ExistingBackend(backend_type, paths=paths,
lp=lp, credentials=credentials,
names=names, logger=logger,
- ldap_backend_forced_uri=ldap_backend_forced_uri)
+ ldap_backend_forced_uri=None)
elif backend_type == "fedora-ds":
provision_backend = FDSBackend(backend_type, paths=paths,
lp=lp, credentials=credentials,
names=names, logger=logger, domainsid=domainsid,
schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
slapd_path=slapd_path,
- ldap_backend_extra_port=ldap_backend_extra_port,
- ldap_dryrun_mode=ldap_dryrun_mode, root=root,
- setup_ds_path=setup_ds_path,
- ldap_backend_forced_uri=ldap_backend_forced_uri)
+ root=root)
elif backend_type == "openldap":
provision_backend = OpenLDAPBackend(backend_type, paths=paths,
lp=lp, credentials=credentials,
names=names, logger=logger, domainsid=domainsid,
schema=schema, hostname=hostname, ldapadminpass=ldapadminpass,
- slapd_path=slapd_path,
- ldap_backend_extra_port=ldap_backend_extra_port,
- ldap_dryrun_mode=ldap_dryrun_mode, ol_mmr_urls=ol_mmr_urls,
- nosync=nosync,
- ldap_backend_forced_uri=ldap_backend_forced_uri)
+ slapd_path=slapd_path, ol_mmr_urls=ol_mmr_urls)
else:
raise ValueError("Unknown LDAP backend type selected")
@@ -1530,8 +1999,7 @@ def provision(logger, session_info, credentials, smbconf=None,
# only install a new shares config db if there is none
if not os.path.exists(paths.shareconf):
logger.info("Setting up share.ldb")
- share_ldb = Ldb(paths.shareconf, session_info=session_info,
- lp=lp)
+ share_ldb = Ldb(paths.shareconf, session_info=session_info, lp=lp)
share_ldb.load_ldif_file_add(setup_path("share.ldif"))
logger.info("Setting up secrets.ldb")
@@ -1541,117 +2009,53 @@ def provision(logger, session_info, credentials, smbconf=None,
try:
logger.info("Setting up the registry")
- setup_registry(paths.hklm, session_info,
- lp=lp)
+ setup_registry(paths.hklm, session_info, lp=lp)
logger.info("Setting up the privileges database")
setup_privileges(paths.privilege, session_info, lp=lp)
logger.info("Setting up idmap db")
- idmap = setup_idmapdb(paths.idmapdb,
- session_info=session_info, lp=lp)
+ idmap = setup_idmapdb(paths.idmapdb, session_info=session_info, lp=lp)
+
+ setup_name_mappings(idmap, sid=str(domainsid),
+ root_uid=root_uid, nobody_uid=nobody_uid,
+ users_gid=users_gid, root_gid=root_gid)
logger.info("Setting up SAM db")
samdb = setup_samdb(paths.samdb, session_info,
- provision_backend, lp, names, logger=logger,
- domainsid=domainsid, schema=schema, domainguid=domainguid,
- policyguid=policyguid, policyguid_dc=policyguid_dc,
- fill=samdb_fill, adminpass=adminpass, krbtgtpass=krbtgtpass,
- invocationid=invocationid, machinepass=machinepass,
- dnspass=dnspass, ntdsguid=ntdsguid, serverrole=serverrole,
- dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
- next_rid=next_rid)
-
- if serverrole == "domain controller":
+ provision_backend, lp, names, logger=logger,
+ serverrole=serverrole,
+ schema=schema, fill=samdb_fill, am_rodc=am_rodc)
+
+ if serverrole == "active directory domain controller":
if paths.netlogon is None:
- logger.info("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
- logger.info("Please either remove %s or see the template at %s" %
- (paths.smbconf, setup_path("provision.smb.conf.dc")))
- assert paths.netlogon is not None
+ raise MissingShareError("netlogon", paths.smbconf)
if paths.sysvol is None:
- logger.info("Existing smb.conf does not have a [sysvol] share, but you"
- " are configuring a DC.")
- logger.info("Please either remove %s or see the template at %s" %
- (paths.smbconf, setup_path("provision.smb.conf.dc")))
- assert paths.sysvol is not None
+ raise MissingShareError("sysvol", paths.smbconf)
if not os.path.isdir(paths.netlogon):
os.makedirs(paths.netlogon, 0755)
- if samdb_fill == FILL_FULL:
- setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
- root_uid=root_uid, nobody_uid=nobody_uid,
- users_gid=users_gid, wheel_gid=wheel_gid)
-
- if serverrole == "domain controller":
- # Set up group policies (domain policy and domain controller
- # policy)
- create_default_gpo(paths.sysvol, names.dnsdomain, policyguid,
- policyguid_dc)
- setsysvolacl(samdb, paths.netlogon, paths.sysvol, wheel_gid,
- domainsid, names.dnsdomain, names.domaindn, lp)
-
- logger.info("Setting up sam.ldb rootDSE marking as synchronized")
- setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
-
- secretsdb_self_join(secrets_ldb, domain=names.domain,
- realm=names.realm, dnsdomain=names.dnsdomain,
- netbiosname=names.netbiosname, domainsid=domainsid,
- machinepass=machinepass, secure_channel_type=SEC_CHAN_BDC)
-
- # Now set up the right msDS-SupportedEncryptionTypes into the DB
- # In future, this might be determined from some configuration
- kerberos_enctypes = str(ENC_ALL_TYPES)
+ if adminpass is None:
+ adminpass = samba.generate_random_password(12, 32)
+ adminpass_generated = True
+ else:
+ adminpass_generated = False
- try:
- msg = ldb.Message(ldb.Dn(samdb,
- samdb.searchone("distinguishedName",
- expression="samAccountName=%s$" % names.netbiosname,
- scope=ldb.SCOPE_SUBTREE)))
- msg["msDS-SupportedEncryptionTypes"] = ldb.MessageElement(
- elements=kerberos_enctypes, flags=ldb.FLAG_MOD_REPLACE,
- name="msDS-SupportedEncryptionTypes")
- samdb.modify(msg)
- except ldb.LdbError, (enum, estr):
- if enum != ldb.ERR_NO_SUCH_ATTRIBUTE:
- # It might be that this attribute does not exist in this schema
- raise
-
- if serverrole == "domain controller":
- secretsdb_setup_dns(secrets_ldb, names,
- paths.private_dir, realm=names.realm,
- dnsdomain=names.dnsdomain,
- dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
-
- domainguid = samdb.searchone(basedn=domaindn,
- attribute="objectGUID")
- assert isinstance(domainguid, str)
-
- # Only make a zone file on the first DC, it should be
- # replicated with DNS replication
- create_zone_file(lp, logger, paths, targetdir,
- dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
- hostname=names.hostname, realm=names.realm,
- domainguid=domainguid, ntdsguid=names.ntdsguid)
-
- create_named_conf(paths, realm=names.realm,
- dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
-
- create_named_txt(paths.namedtxt,
- realm=names.realm, dnsdomain=names.dnsdomain,
- private_dir=paths.private_dir,
- keytab_name=paths.dns_keytab)
- logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
- logger.info("and %s for further documentation required for secure DNS "
- "updates", paths.namedtxt)
-
- lastProvisionUSNs = get_last_provision_usn(samdb)
- maxUSN = get_max_usn(samdb, str(names.rootdn))
- if lastProvisionUSNs is not None:
- update_provision_usn(samdb, 0, maxUSN, 1)
- else:
- set_provision_usn(samdb, 0, maxUSN)
+ if samdb_fill == FILL_FULL:
+ provision_fill(samdb, secrets_ldb, logger, names, paths,
+ schema=schema, targetdir=targetdir, samdb_fill=samdb_fill,
+ hostip=hostip, hostip6=hostip6, domainsid=domainsid,
+ next_rid=next_rid, dc_rid=dc_rid, adminpass=adminpass,
+ krbtgtpass=krbtgtpass, domainguid=domainguid,
+ policyguid=policyguid, policyguid_dc=policyguid_dc,
+ invocationid=invocationid, machinepass=machinepass,
+ ntdsguid=ntdsguid, dns_backend=dns_backend,
+ dnspass=dnspass, serverrole=serverrole,
+ dom_for_fun_level=dom_for_fun_level, am_rodc=am_rodc,
+ lp=lp, use_ntvfs=use_ntvfs,
+ skip_sysvolacl=skip_sysvolacl)
create_krb5_conf(paths.krb5conf,
dnsdomain=names.dnsdomain, hostname=names.hostname,
@@ -1659,15 +2063,13 @@ def provision(logger, session_info, credentials, smbconf=None,
logger.info("A Kerberos configuration suitable for Samba 4 has been "
"generated at %s", paths.krb5conf)
- if serverrole == "domain controller":
+ if serverrole == "active directory domain controller":
create_dns_update_list(lp, logger, paths)
- provision_backend.post_setup()
+ backend_result = provision_backend.post_setup()
provision_backend.shutdown()
- create_phpldapadmin_config(paths.phpldapadminconfig,
- ldapi_url)
- except Exception:
+ except:
secrets_ldb.transaction_cancel()
raise
@@ -1685,42 +2087,30 @@ def provision(logger, session_info, credentials, smbconf=None,
logger.info("Failed to chown %s to bind gid %u",
dns_keytab_path, paths.bind_gid)
-
- logger.info("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php",
- paths.phpldapadminconfig)
-
- logger.info("Once the above files are installed, your Samba4 server will be ready to use")
- logger.info("Server Role: %s" % serverrole)
- logger.info("Hostname: %s" % names.hostname)
- logger.info("NetBIOS Domain: %s" % names.domain)
- logger.info("DNS Domain: %s" % names.dnsdomain)
- logger.info("DOMAIN SID: %s" % str(domainsid))
- if samdb_fill == FILL_FULL:
- logger.info("Admin password: %s" % adminpass)
- if provision_backend.type is not "ldb":
- if provision_backend.credentials.get_bind_dn() is not None:
- logger.info("LDAP Backend Admin DN: %s" %
- provision_backend.credentials.get_bind_dn())
- else:
- logger.info("LDAP Admin User: %s" %
- provision_backend.credentials.get_username())
-
- logger.info("LDAP Admin Password: %s" %
- provision_backend.credentials.get_password())
-
- if provision_backend.slapd_command_escaped is not None:
- # now display slapd_command_file.txt to show how slapd must be
- # started next time
- logger.info("Use later the following commandline to start slapd, then Samba:")
- logger.info(provision_backend.slapd_command_escaped)
- logger.info("This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
- provision_backend.ldapdir)
-
result = ProvisionResult()
+ result.server_role = serverrole
result.domaindn = domaindn
result.paths = paths
+ result.names = names
result.lp = lp
result.samdb = samdb
+ result.idmap = idmap
+ result.domainsid = str(domainsid)
+
+ if samdb_fill == FILL_FULL:
+ result.adminpass_generated = adminpass_generated
+ result.adminpass = adminpass
+ else:
+ result.adminpass_generated = False
+ result.adminpass = None
+
+ result.backend_result = backend_result
+
+ if use_rfc2307:
+ provision_fake_ypserver(logger=logger, samdb=samdb,
+ domaindn=names.domaindn, netbiosname=names.netbiosname,
+ nisdomain=names.domain.lower(), maxuid=maxuid, maxgid=maxgid)
+
return result
@@ -1729,9 +2119,9 @@ def provision_become_dc(smbconf=None, targetdir=None,
serverdn=None, domain=None, hostname=None, domainsid=None,
adminpass=None, krbtgtpass=None, domainguid=None, policyguid=None,
policyguid_dc=None, invocationid=None, machinepass=None, dnspass=None,
- root=None, nobody=None, users=None, wheel=None, backup=None,
- serverrole=None, ldap_backend=None, ldap_backend_type=None,
- sitename=None, debuglevel=1):
+ dns_backend=None, root=None, nobody=None, users=None,
+ backup=None, serverrole=None, ldap_backend=None,
+ ldap_backend_type=None, sitename=None, debuglevel=1, use_ntvfs=False):
logger = logging.getLogger("provision")
samba.set_debug_level(debuglevel)
@@ -1740,162 +2130,15 @@ def provision_become_dc(smbconf=None, targetdir=None,
smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
configdn=configdn, serverdn=serverdn, domain=domain,
- hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
- machinepass=machinepass, serverrole="domain controller",
- sitename=sitename)
+ hostname=hostname, hostip=None, domainsid=domainsid,
+ machinepass=machinepass,
+ serverrole="active directory domain controller",
+ sitename=sitename, dns_backend=dns_backend, dnspass=dnspass,
+ use_ntvfs=use_ntvfs)
res.lp.set("debuglevel", str(debuglevel))
return res
-def create_phpldapadmin_config(path, ldapi_uri):
- """Create a PHP LDAP admin configuration file.
-
- :param path: Path to write the configuration to.
- """
- setup_file(setup_path("phpldapadmin-config.php"), path,
- {"S4_LDAPI_URI": ldapi_uri})
-
-
-def create_zone_file(lp, logger, paths, targetdir, dnsdomain,
- hostip, hostip6, hostname, realm, domainguid,
- ntdsguid):
- """Write out a DNS zone file, from the info in the current database.
-
- :param paths: paths object
- :param dnsdomain: DNS Domain name
- :param domaindn: DN of the Domain
- :param hostip: Local IPv4 IP
- :param hostip6: Local IPv6 IP
- :param hostname: Local hostname
- :param realm: Realm name
- :param domainguid: GUID of the domain.
- :param ntdsguid: GUID of the hosts nTDSDSA record.
- """
- assert isinstance(domainguid, str)
-
- if hostip6 is not None:
- hostip6_base_line = " IN AAAA " + hostip6
- hostip6_host_line = hostname + " IN AAAA " + hostip6
- gc_msdcs_ip6_line = "gc._msdcs IN AAAA " + hostip6
- else:
- hostip6_base_line = ""
- hostip6_host_line = ""
- gc_msdcs_ip6_line = ""
-
- if hostip is not None:
- hostip_base_line = " IN A " + hostip
- hostip_host_line = hostname + " IN A " + hostip
- gc_msdcs_ip_line = "gc._msdcs IN A " + hostip
- else:
- hostip_base_line = ""
- hostip_host_line = ""
- gc_msdcs_ip_line = ""
-
- dns_dir = os.path.dirname(paths.dns)
-
- try:
- shutil.rmtree(dns_dir, True)
- except OSError:
- pass
-
- os.mkdir(dns_dir, 0775)
-
- # we need to freeze the zone while we update the contents
- if targetdir is None:
- rndc = ' '.join(lp.get("rndc command"))
- os.system(rndc + " freeze " + lp.get("realm"))
-
- setup_file(setup_path("provision.zone"), paths.dns, {
- "HOSTNAME": hostname,
- "DNSDOMAIN": dnsdomain,
- "REALM": realm,
- "HOSTIP_BASE_LINE": hostip_base_line,
- "HOSTIP_HOST_LINE": hostip_host_line,
- "DOMAINGUID": domainguid,
- "DATESTRING": time.strftime("%Y%m%d%H"),
- "DEFAULTSITE": DEFAULTSITE,
- "NTDSGUID": ntdsguid,
- "HOSTIP6_BASE_LINE": hostip6_base_line,
- "HOSTIP6_HOST_LINE": hostip6_host_line,
- "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
- "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
- })
-
- # note that we use no variable substitution on this file
- # the substitution is done at runtime by samba_dnsupdate
- setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
-
- # and the SPN update list
- setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
-
- if paths.bind_gid is not None:
- try:
- os.chown(dns_dir, -1, paths.bind_gid)
- os.chown(paths.dns, -1, paths.bind_gid)
- # chmod needed to cope with umask
- os.chmod(dns_dir, 0775)
- os.chmod(paths.dns, 0664)
- except OSError:
- if not os.environ.has_key('SAMBA_SELFTEST'):
- logger.error("Failed to chown %s to bind gid %u" % (
- dns_dir, paths.bind_gid))
-
- if targetdir is None:
- os.system(rndc + " unfreeze " + lp.get("realm"))
-
-
-def create_dns_update_list(lp, logger, paths):
- """Write out a dns_update_list file"""
- # note that we use no variable substitution on this file
- # the substitution is done at runtime by samba_dnsupdate
- setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
- setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
-
-
-def create_named_conf(paths, realm, dnsdomain,
- private_dir):
- """Write out a file containing zone statements suitable for inclusion in a
- named.conf file (including GSS-TSIG configuration).
-
- :param paths: all paths
- :param realm: Realm name
- :param dnsdomain: DNS Domain name
- :param private_dir: Path to private directory
- :param keytab_name: File name of DNS keytab file
- """
-
- setup_file(setup_path("named.conf"), paths.namedconf, {
- "DNSDOMAIN": dnsdomain,
- "REALM": realm,
- "ZONE_FILE": paths.dns,
- "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
- "NAMED_CONF": paths.namedconf,
- "NAMED_CONF_UPDATE": paths.namedconf_update
- })
-
- setup_file(setup_path("named.conf.update"), paths.namedconf_update)
-
-
-def create_named_txt(path, realm, dnsdomain, private_dir,
- keytab_name):
- """Write out a file containing zone statements suitable for inclusion in a
- named.conf file (including GSS-TSIG configuration).
-
- :param path: Path of the new named.conf file.
- :param realm: Realm name
- :param dnsdomain: DNS Domain name
- :param private_dir: Path to private directory
- :param keytab_name: File name of DNS keytab file
- """
- setup_file(setup_path("named.txt"), path, {
- "DNSDOMAIN": dnsdomain,
- "REALM": realm,
- "DNS_KEYTAB": keytab_name,
- "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
- "PRIVATE_DIR": private_dir
- })
-
-
def create_krb5_conf(path, dnsdomain, hostname, realm):
"""Write out a file containing zone statements suitable for inclusion in a
named.conf file (including GSS-TSIG configuration).
@@ -1924,6 +2167,16 @@ class ProvisioningError(Exception):
class InvalidNetbiosName(Exception):
"""A specified name was not a valid NetBIOS name."""
+
def __init__(self, name):
super(InvalidNetbiosName, self).__init__(
"The name '%r' is not a valid NetBIOS name" % name)
+
+
+class MissingShareError(ProvisioningError):
+
+ def __init__(self, name, smbconf):
+ super(MissingShareError, self).__init__(
+ "Existing smb.conf does not have a [%s] share, but you are "
+ "configuring a DC. Please remove %s or add the share manually." %
+ (name, smbconf))
diff --git a/source4/scripting/python/samba/provision/backend.py b/source4/scripting/python/samba/provision/backend.py
index f9dbba85f6..f88b0db89c 100644
--- a/source4/scripting/python/samba/provision/backend.py
+++ b/source4/scripting/python/samba/provision/backend.py
@@ -42,6 +42,7 @@ from samba import Ldb, read_and_sub_file, setup_file
from samba.credentials import Credentials, DONT_USE_KERBEROS
from samba.schema import Schema
+
class SlapdAlreadyRunning(Exception):
def __init__(self, uri):
@@ -51,7 +52,43 @@ class SlapdAlreadyRunning(Exception):
self.ldapi_uri)
+class BackendResult(object):
+
+ def report_logger(self, logger):
+ """Rerport this result to a particular logger.
+
+ """
+ raise NotImplementedError(self.report_logger)
+
+
+class LDAPBackendResult(BackendResult):
+
+ def __init__(self, credentials, slapd_command_escaped, ldapdir):
+ self.credentials = credentials
+ self.slapd_command_escaped = slapd_command_escaped
+ self.ldapdir = ldapdir
+
+ def report_logger(self, logger):
+ if self.credentials.get_bind_dn() is not None:
+ logger.info("LDAP Backend Admin DN: %s" %
+ self.credentials.get_bind_dn())
+ else:
+ logger.info("LDAP Admin User: %s" %
+ self.credentials.get_username())
+
+ if self.slapd_command_escaped is not None:
+ # now display slapd_command_file.txt to show how slapd must be
+ # started next time
+ logger.info(
+ "Use later the following commandline to start slapd, then Samba:")
+ logger.info(self.slapd_command_escaped)
+ logger.info(
+ "This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
+ self.ldapdir)
+
+
class ProvisionBackend(object):
+
def __init__(self, backend_type, paths=None, lp=None,
credentials=None, names=None, logger=None):
"""Provision a backend for samba4"""
@@ -79,7 +116,10 @@ class ProvisionBackend(object):
raise NotImplementedError(self.shutdown)
def post_setup(self):
- """Post setup."""
+ """Post setup.
+
+ :return: A BackendResult or None
+ """
raise NotImplementedError(self.post_setup)
@@ -133,7 +173,7 @@ class LDAPBackend(ProvisionBackend):
credentials=None, names=None, logger=None, domainsid=None,
schema=None, hostname=None, ldapadminpass=None,
slapd_path=None, ldap_backend_extra_port=None,
- ldap_backend_forced_uri=None, ldap_dryrun_mode=False):
+ ldap_backend_forced_uri=None, ldap_dryrun_mode=True):
super(LDAPBackend, self).__init__(backend_type=backend_type,
paths=paths, lp=lp,
@@ -178,9 +218,11 @@ class LDAPBackend(ProvisionBackend):
if err != errno.ENOENT:
raise
else:
- p = f.read()
- f.close()
- self.logger.info("Check for slapd Process with PID: %s and terminate it manually." % p)
+ try:
+ p = f.read()
+ finally:
+ f.close()
+ self.logger.info("Check for slapd process with PID: %s and terminate it manually." % p)
raise SlapdAlreadyRunning(self.ldap_uri)
except LdbError:
# XXX: We should never be catching all Ldb errors
@@ -278,7 +320,8 @@ class LDAPBackend(ProvisionBackend):
self.slapd.communicate()
def post_setup(self):
- pass
+ return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
+ self.ldapdir)
class OpenLDAPBackend(LDAPBackend):
@@ -286,7 +329,7 @@ class OpenLDAPBackend(LDAPBackend):
def __init__(self, backend_type, paths=None, lp=None,
credentials=None, names=None, logger=None, domainsid=None,
schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
- ldap_backend_extra_port=None, ldap_dryrun_mode=False,
+ ldap_backend_extra_port=None, ldap_dryrun_mode=True,
ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None):
from samba.provision import setup_path
super(OpenLDAPBackend, self).__init__( backend_type=backend_type,
@@ -341,7 +384,7 @@ class OpenLDAPBackend(LDAPBackend):
lnkattr = self.schema.linked_attributes()
refint_attributes = ""
memberof_config = "# Generated from Samba4 schema\n"
- for att in lnkattr.keys():
+ for att in lnkattr.keys():
if lnkattr[att] is not None:
refint_attributes = refint_attributes + " " + att
@@ -495,8 +538,11 @@ class OpenLDAPBackend(LDAPBackend):
backend_schema = "backend-schema.schema"
f = open(setup_path(mapping), 'r')
- backend_schema_data = self.schema.convert_to_openldap(
- "openldap", f.read())
+ try:
+ backend_schema_data = self.schema.convert_to_openldap(
+ "openldap", f.read())
+ finally:
+ f.close()
assert backend_schema_data is not None
f = open(os.path.join(self.ldapdir, backend_schema), 'w')
try:
@@ -568,7 +614,7 @@ class FDSBackend(LDAPBackend):
def __init__(self, backend_type, paths=None, lp=None,
credentials=None, names=None, logger=None, domainsid=None,
schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
- ldap_backend_extra_port=None, ldap_dryrun_mode=False, root=None,
+ ldap_backend_extra_port=None, ldap_dryrun_mode=True, root=None,
setup_ds_path=None):
from samba.provision import setup_path
@@ -659,7 +705,11 @@ class FDSBackend(LDAPBackend):
lnkattr = self.schema.linked_attributes()
- refint_config = open(setup_path("fedorads-refint-delete.ldif"), 'r').read()
+ f = open(setup_path("fedorads-refint-delete.ldif"), 'r')
+ try:
+ refint_config = f.read()
+ finally:
+ f.close()
memberof_config = ""
index_config = ""
argnum = 3
@@ -678,8 +728,16 @@ class FDSBackend(LDAPBackend):
setup_path("fedorads-index.ldif"), { "ATTR" : attr })
argnum += 1
- open(self.refint_ldif, 'w').write(refint_config)
- open(self.linked_attrs_ldif, 'w').write(memberof_config)
+ f = open(self.refint_ldif, 'w')
+ try:
+ f.write(refint_config)
+ finally:
+ f.close()
+ f = open(self.linked_attrs_ldif, 'w')
+ try:
+ f.write(memberof_config)
+ finally:
+ f.close()
attrs = ["lDAPDisplayName"]
res = self.schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
@@ -693,7 +751,11 @@ class FDSBackend(LDAPBackend):
index_config += read_and_sub_file(
setup_path("fedorads-index.ldif"), { "ATTR" : attr })
- open(self.index_ldif, 'w').write(index_config)
+ f = open(self.index_ldif, 'w')
+ try:
+ f.write(index_config)
+ finally:
+ f.close()
setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
"SAMBADN": self.sambadn,
@@ -704,8 +766,12 @@ class FDSBackend(LDAPBackend):
backend_schema = "99_ad.ldif"
# Build a schema file in Fedora DS format
- backend_schema_data = self.schema.convert_to_openldap("fedora-ds",
- open(setup_path(mapping), 'r').read())
+ f = open(setup_path(mapping), 'r')
+ try:
+ backend_schema_data = self.schema.convert_to_openldap("fedora-ds",
+ f.read())
+ finally:
+ f.close()
assert backend_schema_data is not None
f = open(os.path.join(self.ldapdir, backend_schema), 'w')
try:
@@ -770,3 +836,5 @@ class FDSBackend(LDAPBackend):
self.names.schemadn):
m.dn = ldb.Dn(ldapi_db, dnstring)
ldapi_db.modify(m)
+ return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
+ self.ldapdir)
diff --git a/source4/scripting/python/samba/provision/common.py b/source4/scripting/python/samba/provision/common.py
new file mode 100644
index 0000000000..f96704bcce
--- /dev/null
+++ b/source4/scripting/python/samba/provision/common.py
@@ -0,0 +1,82 @@
+
+# Unix SMB/CIFS implementation.
+# utility functions for provisioning a Samba4 server
+
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
+# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
+# Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
+#
+# Based on the original in EJS:
+# Copyright (C) Andrew Tridgell <tridge@samba.org> 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/>.
+#
+
+"""Functions for setting up a Samba configuration."""
+
+__docformat__ = "restructuredText"
+
+import os
+from samba import read_and_sub_file
+from samba.param import setup_dir
+
+
+def setup_path(file):
+ """Return an absolute path to the provision tempate file specified by file"""
+ return os.path.join(setup_dir(), file)
+
+
+def setup_add_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
+ """Setup a ldb in the private dir.
+
+ :param ldb: LDB file to import data into
+ :param ldif_path: Path of the LDIF file to load
+ :param subst_vars: Optional variables to subsitute in LDIF.
+ :param nocontrols: Optional list of controls, can be None for no controls
+ """
+ assert isinstance(ldif_path, str)
+ data = read_and_sub_file(ldif_path, subst_vars)
+ ldb.add_ldif(data, controls)
+
+
+def setup_modify_ldif(ldb, ldif_path, subst_vars=None,controls=["relax:0"]):
+ """Modify a ldb in the private dir.
+
+ :param ldb: LDB object.
+ :param ldif_path: LDIF file path.
+ :param subst_vars: Optional dictionary with substitution variables.
+ """
+ data = read_and_sub_file(ldif_path, subst_vars)
+ ldb.modify_ldif(data, controls)
+
+
+def setup_ldb(ldb, ldif_path, subst_vars):
+ """Import a LDIF a file into a LDB handle, optionally substituting
+ variables.
+
+ :note: Either all LDIF data will be added or none (using transactions).
+
+ :param ldb: LDB file to import into.
+ :param ldif_path: Path to the LDIF file.
+ :param subst_vars: Dictionary with substitution variables.
+ """
+ assert ldb is not None
+ ldb.transaction_start()
+ try:
+ setup_add_ldif(ldb, ldif_path, subst_vars)
+ except:
+ ldb.transaction_cancel()
+ raise
+ else:
+ ldb.transaction_commit()
diff --git a/source4/scripting/python/samba/provision/descriptor.py b/source4/scripting/python/samba/provision/descriptor.py
new file mode 100644
index 0000000000..adf75797cc
--- /dev/null
+++ b/source4/scripting/python/samba/provision/descriptor.py
@@ -0,0 +1,305 @@
+
+# Unix SMB/CIFS implementation.
+# backend code for provisioning a Samba4 server
+
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
+# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
+# Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
+# Copyright (C) Amitay Isaacs <amitay@samba.org> 2011
+#
+# Based on the original in EJS:
+# Copyright (C) Andrew Tridgell <tridge@samba.org> 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/>.
+#
+
+"""Functions for setting up a Samba configuration (security descriptors)."""
+
+from samba.dcerpc import security
+from samba.ndr import ndr_pack
+
+# Descriptors of naming contexts and other important objects
+
+def get_empty_descriptor(domain_sid):
+ sddl= ""
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+# "get_schema_descriptor" is located in "schema.py"
+
+def get_config_descriptor(domain_sid):
+ sddl = "O:EAG:EAD:(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(A;;RPLCLORC;;;AU)(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CIIO;RPWPCRCCLCLORCWOWDSDSW;;;DA)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
+ "S:(AU;SA;WPWOWD;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)" \
+ "(OU;SA;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;WD)"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_config_partitions_descriptor(domain_sid):
+ sddl = "D:" \
+ "(A;;LCLORC;;;AU)" \
+ "(OA;;RP;e48d0154-bcf8-11d1-8702-00c04fb96050;;AU)" \
+ "(OA;;RP;d31a8757-2447-4545-8081-3bb610cacbf2;;AU)" \
+ "(OA;;RP;66171887-8f3c-11d0-afda-00c04fd930c9;;AU)" \
+ "(OA;;RP;032160bf-9824-11d1-aec0-0000f80367c1;;AU)" \
+ "(OA;;RP;789ee1eb-8c8e-4e4c-8cec-79b31b7617b5;;AU)" \
+ "(OA;;RP;5706aeaf-b940-4fb2-bcfc-5268683ad9fe;;AU)" \
+ "(A;;RPWPCRCCLCLORCWOWDSW;;;EA)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "(A;;CC;;;ED)" \
+ "(OA;CIIO;WP;3df793df-9858-4417-a701-735a1ecebf74;bf967a8d-0de6-11d0-a285-00aa003049e2;BA)" \
+ "S:" \
+ "(AU;CISA;WPCRCCDCWOWDSDDT;;;WD)"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_config_sites_descriptor(domain_sid):
+ sddl = "D:" \
+ "(A;;RPLCLORC;;;AU)" \
+ "(OA;CIIO;SW;d31a8757-2447-4545-8081-3bb610cacbf2;f0f8ffab-1191-11d0-a060-00aa006c33ed;ER)" \
+ "(A;;RPWPCRCCLCLORCWOWDSW;;;EA)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "S:" \
+ "(AU;CISA;CCDCSDDT;;;WD)" \
+ "(OU;CIIOSA;CR;;f0f8ffab-1191-11d0-a060-00aa006c33ed;WD)" \
+ "(OU;CIIOSA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \
+ "(OU;CIIOSA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967ab3-0de6-11d0-a285-00aa003049e2;WD)" \
+ "(OU;CIIOSA;WP;3e10944c-c354-11d0-aff8-0000f80367c1;b7b13124-b82e-11d0-afee-0000f80367c1;WD)"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_domain_descriptor(domain_sid):
+ sddl= "O:BAG:BAD:AI(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \
+ "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \
+ "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \
+ "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
+ "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \
+ "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \
+ "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
+ "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
+ "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
+ "(A;;RPRC;;;RU)" \
+ "(A;CI;LC;;;RU)" \
+ "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
+ "(A;;RP;;;WD)" \
+ "(A;;RPLCLORC;;;ED)" \
+ "(A;;RPLCLORC;;;AU)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "S:AI(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
+ "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
+ "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_domain_infrastructure_descriptor(domain_sid):
+ sddl = "D:" \
+ "(A;;RPLCLORC;;;AU)" \
+ "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "S:" \
+ "(AU;SA;WPCR;;;WD)"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_domain_builtin_descriptor(domain_sid):
+ sddl = "D:" \
+ "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;DD)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \
+ "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \
+ "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \
+ "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
+ "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \
+ "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \
+ "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
+ "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
+ "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
+ "(A;;RPRC;;;RU)" \
+ "(A;CI;LC;;;RU)" \
+ "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
+ "(A;;RP;;;WD)" \
+ "(A;;RPLCLORC;;;ED)" \
+ "(A;;RPLCLORC;;;AU)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "S:" \
+ "(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
+ "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
+ "(AU;SA;CR;;;DU)" \
+ "(AU;SA;CR;;;BA)" \
+ "(AU;SA;WPWOWD;;;WD)"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_domain_computers_descriptor(domain_sid):
+ sddl = "D:" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSW;;;DA)" \
+ "(OA;;CCDC;bf967a86-0de6-11d0-a285-00aa003049e2;;AO)" \
+ "(OA;;CCDC;bf967aba-0de6-11d0-a285-00aa003049e2;;AO)" \
+ "(OA;;CCDC;bf967a9c-0de6-11d0-a285-00aa003049e2;;AO)" \
+ "(OA;;CCDC;bf967aa8-0de6-11d0-a285-00aa003049e2;;PO)" \
+ "(A;;RPLCLORC;;;AU)" \
+ "(OA;;CCDC;4828cc14-1437-45bc-9b07-ad6f015e5f28;;AO)" \
+ "S:"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_domain_users_descriptor(domain_sid):
+ sddl = "D:" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSW;;;DA)" \
+ "(OA;;CCDC;bf967aba-0de6-11d0-a285-00aa003049e2;;AO)" \
+ "(OA;;CCDC;bf967a9c-0de6-11d0-a285-00aa003049e2;;AO)" \
+ "(OA;;CCDC;bf967aa8-0de6-11d0-a285-00aa003049e2;;PO)" \
+ "(A;;RPLCLORC;;;AU)" \
+ "(OA;;CCDC;4828cc14-1437-45bc-9b07-ad6f015e5f28;;AO)" \
+ "S:"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_domain_controllers_descriptor(domain_sid):
+ sddl = "D:" \
+ "(A;;RPLCLORC;;;AU)" \
+ "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "(A;;RPLCLORC;;;ED)" \
+ "S:" \
+ "(AU;SA;CCDCWOWDSDDT;;;WD)" \
+ "(AU;CISA;WP;;;WD)"
+ sec = security.descriptor.from_sddl(sddl, domain_sid)
+ return ndr_pack(sec)
+
+def get_dns_partition_descriptor(domainsid):
+ sddl = "O:SYG:BAD:AI" \
+ "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;4c164200-20c0-11d0-a768-00aa006e0529;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;5f202010-79a5-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;bc0ac240-79a9-11d0-9020-00c04fc2d4cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;59ba2f42-79a2-11d0-9020-00c04fc2d3cf;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RP;037088f8-0ae1-11d2-b422-00a0c968f939;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ER)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a86-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967a9c-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;CIIO;RP;b7c69e6d-2cc7-11d2-854e-00a0c983f608;bf967aba-0de6-11d0-a285-00aa003049e2;ED)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;BA)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;BA)" \
+ "(OA;;CR;e2a36dc9-ae17-47c3-b58b-be34c55ba633;;IF)" \
+ "(OA;;RP;c7407360-20bf-11d0-a768-00aa006e0529;;RU)" \
+ "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;RU)" \
+ "(OA;CIIO;RPLCLORC;;4828cc14-1437-45bc-9b07-ad6f015e5f28;RU)" \
+ "(OA;CIIO;RPLCLORC;;bf967a9c-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;CIIO;RPLCLORC;;bf967aba-0de6-11d0-a285-00aa003049e2;RU)" \
+ "(OA;;CR;05c74c5e-4deb-43b4-bd9f-86664c2a7fd5;;AU)" \
+ "(OA;;CR;89e95b76-444d-4c62-991a-0facbeda640c;;ED)" \
+ "(OA;;CR;ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501;;AU)" \
+ "(OA;;CR;280f369c-67c7-438e-ae98-1d46f3c6f541;;AU)" \
+ "(OA;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ab-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ac-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ad-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;CR;1131f6ae-9c07-11d1-f79f-00c04fc2dcd2;;ED)" \
+ "(OA;;RP;b8119fd0-04f6-4762-ab7a-4986c76b3f9a;;AU)" \
+ "(OA;CIIO;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
+ "(A;;RPWPCRCCLCLORCWOWDSW;;;DA)" \
+ "(A;CI;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
+ "(A;;RPRC;;;RU)" \
+ "(A;CI;LC;;;RU)" \
+ "(A;CI;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
+ "(A;;RP;;;WD)" \
+ "(A;;RPLCLORC;;;ED)" \
+ "(A;;RPLCLORC;;;AU)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "S:AI" \
+ "(OU;CISA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
+ "(OU;CISA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)" \
+ "(AU;SA;CR;;;DU)(AU;SA;CR;;;BA)(AU;SA;WPWOWD;;;WD)"
+ sec = security.descriptor.from_sddl(sddl, domainsid)
+ return ndr_pack(sec)
diff --git a/source4/scripting/python/samba/provision/sambadns.py b/source4/scripting/python/samba/provision/sambadns.py
new file mode 100644
index 0000000000..a66fde1425
--- /dev/null
+++ b/source4/scripting/python/samba/provision/sambadns.py
@@ -0,0 +1,1144 @@
+# Unix SMB/CIFS implementation.
+# backend code for provisioning DNS for a Samba4 server
+#
+# Copyright (C) Kai Blin <kai@samba.org> 2011
+# Copyright (C) Amitay Isaacs <amitay@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/>.
+#
+
+"""DNS-related provisioning"""
+
+import os
+import uuid
+import shutil
+import time
+import ldb
+from base64 import b64encode
+import samba
+from samba.ndr import ndr_pack, ndr_unpack
+from samba import setup_file
+from samba.dcerpc import dnsp, misc, security
+from samba.dsdb import (
+ DS_DOMAIN_FUNCTION_2000,
+ DS_DOMAIN_FUNCTION_2003,
+ DS_DOMAIN_FUNCTION_2008_R2
+ )
+from samba.provision.descriptor import (
+ get_domain_descriptor,
+ get_dns_partition_descriptor
+ )
+from samba.provision.common import (
+ setup_path,
+ setup_add_ldif,
+ setup_modify_ldif,
+ setup_ldb
+ )
+
+
+def get_domainguid(samdb, domaindn):
+ res = samdb.search(base=domaindn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
+ domainguid = str(ndr_unpack(misc.GUID, res[0]["objectGUID"][0]))
+ return domainguid
+
+
+def get_dnsadmins_sid(samdb, domaindn):
+ res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % domaindn, scope=ldb.SCOPE_BASE,
+ attrs=["objectSid"])
+ dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
+ return dnsadmins_sid
+
+
+class ARecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super(ARecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_A
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self.data = ip_addr
+
+
+class AAAARecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super(AAAARecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_AAAA
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self.data = ip6_addr
+
+
+class CNameRecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super(CNameRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_CNAME
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self.data = cname
+
+
+class NSRecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super(NSRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_NS
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ self.data = dns_server
+
+
+class SOARecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
+ expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE):
+ super(SOARecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_SOA
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ soa = dnsp.soa()
+ soa.serial = serial
+ soa.refresh = refresh
+ soa.retry = retry
+ soa.expire = expire
+ soa.mname = mname
+ soa.rname = rname
+ self.data = soa
+
+
+class SRVRecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900,
+ rank=dnsp.DNS_RANK_ZONE):
+ super(SRVRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_SRV
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ srv = dnsp.srv()
+ srv.nameTarget = target
+ srv.wPort = port
+ srv.wPriority = priority
+ srv.wWeight = weight
+ self.data = srv
+
+
+class TXTRecord(dnsp.DnssrvRpcRecord):
+
+ def __init__(self, slist, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
+ super(TXTRecord, self).__init__()
+ self.wType = dnsp.DNS_TYPE_TXT
+ self.rank = rank
+ self.dwSerial = serial
+ self.dwTtlSeconds = ttl
+ stringlist = dnsp.string_list()
+ stringlist.count = len(slist)
+ stringlist.str = slist
+ self.data = stringlist
+
+
+class TypeProperty(dnsp.DnsProperty):
+
+ def __init__(self, zone_type=dnsp.DNS_ZONE_TYPE_PRIMARY):
+ super(TypeProperty, self).__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_TYPE
+ self.data = zone_type
+
+
+class AllowUpdateProperty(dnsp.DnsProperty):
+
+ def __init__(self, allow_update=dnsp.DNS_ZONE_UPDATE_SECURE):
+ super(AllowUpdateProperty, self).__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_ALLOW_UPDATE
+ self.data = allow_update
+
+
+class SecureTimeProperty(dnsp.DnsProperty):
+
+ def __init__(self, secure_time=0):
+ super(SecureTimeProperty, self).__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_SECURE_TIME
+ self.data = secure_time
+
+
+class NorefreshIntervalProperty(dnsp.DnsProperty):
+
+ def __init__(self, norefresh_interval=0):
+ super(NorefreshIntervalProperty, self).__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_NOREFRESH_INTERVAL
+ self.data = norefresh_interval
+
+
+class RefreshIntervalProperty(dnsp.DnsProperty):
+
+ def __init__(self, refresh_interval=0):
+ super(RefreshIntervalProperty, self).__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_REFRESH_INTERVAL
+ self.data = refresh_interval
+
+
+class AgingStateProperty(dnsp.DnsProperty):
+
+ def __init__(self, aging_enabled=0):
+ super(AgingStateProperty, self).__init__()
+ self.wDataLength = 1
+ self.version = 1
+ self.id = dnsp.DSPROPERTY_ZONE_AGING_STATE
+ self.data = aging_enabled
+
+
+class AgingEnabledTimeProperty(dnsp.DnsProperty):
+
+ def __init__(self, next_cycle_hours=0):
+ super(AgingEnabledTimeProperty, self).__init__()
+ self.wDataLength = 1
+ self.version = 1;
+ self.id = dnsp.DSPROPERTY_ZONE_AGING_ENABLED_TIME
+ self.data = next_cycle_hours
+
+
+def setup_dns_partitions(samdb, domainsid, domaindn, forestdn, configdn,
+ serverdn):
+ domainzone_dn = "DC=DomainDnsZones,%s" % domaindn
+ forestzone_dn = "DC=ForestDnsZones,%s" % forestdn
+ descriptor = get_dns_partition_descriptor(domainsid)
+ setup_add_ldif(samdb, setup_path("provision_dnszones_partitions.ldif"), {
+ "DOMAINZONE_DN": domainzone_dn,
+ "FORESTZONE_DN": forestzone_dn,
+ "SECDESC" : b64encode(descriptor)
+ })
+
+ domainzone_guid = get_domainguid(samdb, domainzone_dn)
+ forestzone_guid = get_domainguid(samdb, forestzone_dn)
+
+ domainzone_guid = str(uuid.uuid4())
+ forestzone_guid = str(uuid.uuid4())
+
+ domainzone_dns = ldb.Dn(samdb, domainzone_dn).canonical_ex_str().strip()
+ forestzone_dns = ldb.Dn(samdb, forestzone_dn).canonical_ex_str().strip()
+
+ setup_add_ldif(samdb, setup_path("provision_dnszones_add.ldif"), {
+ "DOMAINZONE_DN": domainzone_dn,
+ "FORESTZONE_DN": forestzone_dn,
+ "DOMAINZONE_GUID": domainzone_guid,
+ "FORESTZONE_GUID": forestzone_guid,
+ "DOMAINZONE_DNS": domainzone_dns,
+ "FORESTZONE_DNS": forestzone_dns,
+ "CONFIGDN": configdn,
+ "SERVERDN": serverdn,
+ })
+
+ setup_modify_ldif(samdb, setup_path("provision_dnszones_modify.ldif"), {
+ "CONFIGDN": configdn,
+ "SERVERDN": serverdn,
+ "DOMAINZONE_DN": domainzone_dn,
+ "FORESTZONE_DN": forestzone_dn,
+ })
+
+
+def add_dns_accounts(samdb, domaindn):
+ setup_add_ldif(samdb, setup_path("provision_dns_accounts_add.ldif"), {
+ "DOMAINDN": domaindn,
+ })
+
+
+def add_dns_container(samdb, domaindn, prefix, domainsid, dnsadmins_sid):
+ # CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ sddl = "O:SYG:SYD:AI" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
+ "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
+ "S:AI" % dnsadmins_sid
+ sec = security.descriptor.from_sddl(sddl, domainsid)
+ msg = ldb.Message(ldb.Dn(samdb, "CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)))
+ msg["objectClass"] = ["top", "container"]
+ msg["nTSecurityDescriptor"] = ldb.MessageElement(ndr_pack(sec), ldb.FLAG_MOD_ADD,
+ "nTSecurityDescriptor")
+ samdb.add(msg)
+
+
+def add_rootservers(samdb, domaindn, prefix):
+ rootservers = {}
+ rootservers["a.root-servers.net"] = "198.41.0.4"
+ rootservers["b.root-servers.net"] = "192.228.79.201"
+ rootservers["c.root-servers.net"] = "192.33.4.12"
+ rootservers["d.root-servers.net"] = "128.8.10.90"
+ rootservers["e.root-servers.net"] = "192.203.230.10"
+ rootservers["f.root-servers.net"] = "192.5.5.241"
+ rootservers["g.root-servers.net"] = "192.112.36.4"
+ rootservers["h.root-servers.net"] = "128.63.2.53"
+ rootservers["i.root-servers.net"] = "192.36.148.17"
+ rootservers["j.root-servers.net"] = "192.58.128.30"
+ rootservers["k.root-servers.net"] = "193.0.14.129"
+ rootservers["l.root-servers.net"] = "199.7.83.42"
+ rootservers["m.root-servers.net"] = "202.12.27.33"
+
+ rootservers_v6 = {}
+ rootservers_v6["a.root-servers.net"] = "2001:503:ba3e::2:30"
+ rootservers_v6["f.root-servers.net"] = "2001:500:2f::f"
+ rootservers_v6["h.root-servers.net"] = "2001:500:1::803f:235"
+ rootservers_v6["j.root-servers.net"] = "2001:503:c27::2:30"
+ rootservers_v6["k.root-servers.net"] = "2001:7fd::1"
+ rootservers_v6["m.root-servers.net"] = "2001:dc3::35"
+
+ container_dn = "DC=RootDNSServers,CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)
+
+ # Add DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ msg = ldb.Message(ldb.Dn(samdb, container_dn))
+ props = []
+ props.append(ndr_pack(TypeProperty(zone_type=dnsp.DNS_ZONE_TYPE_CACHE)))
+ props.append(ndr_pack(AllowUpdateProperty(allow_update=dnsp.DNS_ZONE_UPDATE_OFF)))
+ props.append(ndr_pack(SecureTimeProperty()))
+ props.append(ndr_pack(NorefreshIntervalProperty()))
+ props.append(ndr_pack(RefreshIntervalProperty()))
+ props.append(ndr_pack(AgingStateProperty()))
+ props.append(ndr_pack(AgingEnabledTimeProperty()))
+ msg["objectClass"] = ["top", "dnsZone"]
+ msg["cn"] = ldb.MessageElement("Zone", ldb.FLAG_MOD_ADD, "cn")
+ msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
+ samdb.add(msg)
+
+ # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ record = []
+ for rserver in rootservers:
+ record.append(ndr_pack(NSRecord(rserver, serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT)))
+
+ msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+ # Add DC=<rootserver>,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ for rserver in rootservers:
+ record = [ndr_pack(ARecord(rootservers[rserver], serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT))]
+ # Add AAAA record as well (How does W2K* add IPv6 records?)
+ #if rserver in rootservers_v6:
+ # record.append(ndr_pack(AAAARecord(rootservers_v6[rserver], serial=0, ttl=0)))
+ msg = ldb.Message(ldb.Dn(samdb, "DC=%s,%s" % (rserver, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+def add_at_record(samdb, container_dn, prefix, hostname, dnsdomain, hostip, hostip6):
+
+ fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
+
+ at_records = []
+
+ # SOA record
+ at_soa_record = SOARecord(fqdn_hostname, "hostmaster.%s" % dnsdomain)
+ at_records.append(ndr_pack(at_soa_record))
+
+ # NS record
+ at_ns_record = NSRecord(fqdn_hostname)
+ at_records.append(ndr_pack(at_ns_record))
+
+ if hostip is not None:
+ # A record
+ at_a_record = ARecord(hostip)
+ at_records.append(ndr_pack(at_a_record))
+
+ if hostip6 is not None:
+ # AAAA record
+ at_aaaa_record = AAAARecord(hostip6)
+ at_records.append(ndr_pack(at_aaaa_record))
+
+ msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(at_records, ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_srv_record(samdb, container_dn, prefix, host, port):
+ srv_record = SRVRecord(host, port)
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(ndr_pack(srv_record), ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_ns_record(samdb, container_dn, prefix, host):
+ ns_record = NSRecord(host)
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_ns_glue_record(samdb, container_dn, prefix, host):
+ ns_record = NSRecord(host, rank=dnsp.DNS_RANK_NS_GLUE)
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_cname_record(samdb, container_dn, prefix, host):
+ cname_record = CNameRecord(host)
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(ndr_pack(cname_record), ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
+ host_records = []
+ if hostip:
+ a_record = ARecord(hostip)
+ host_records.append(ndr_pack(a_record))
+ if hostip6:
+ aaaa_record = AAAARecord(hostip6)
+ host_records.append(ndr_pack(aaaa_record))
+ if host_records:
+ msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+ msg["objectClass"] = ["top", "dnsNode"]
+ msg["dnsRecord"] = ldb.MessageElement(host_records, ldb.FLAG_MOD_ADD, "dnsRecord")
+ samdb.add(msg)
+
+
+def add_domain_record(samdb, domaindn, prefix, dnsdomain, domainsid, dnsadmins_sid):
+ # DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ sddl = "O:SYG:BAD:AI" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
+ "(A;;CC;;;AU)" \
+ "(A;;RPLCLORC;;;WD)" \
+ "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+ "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
+ "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
+ "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
+ "(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
+ "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
+ "(A;CIID;LC;;;RU)" \
+ "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
+ "S:AI" % dnsadmins_sid
+ sec = security.descriptor.from_sddl(sddl, domainsid)
+ props = []
+ props.append(ndr_pack(TypeProperty()))
+ props.append(ndr_pack(AllowUpdateProperty()))
+ props.append(ndr_pack(SecureTimeProperty()))
+ props.append(ndr_pack(NorefreshIntervalProperty(norefresh_interval=168)))
+ props.append(ndr_pack(RefreshIntervalProperty(refresh_interval=168)))
+ props.append(ndr_pack(AgingStateProperty()))
+ props.append(ndr_pack(AgingEnabledTimeProperty()))
+ msg = ldb.Message(ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" % (dnsdomain, prefix, domaindn)))
+ msg["objectClass"] = ["top", "dnsZone"]
+ msg["ntSecurityDescriptor"] = ldb.MessageElement(ndr_pack(sec), ldb.FLAG_MOD_ADD,
+ "nTSecurityDescriptor")
+ msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
+ samdb.add(msg)
+
+
+def add_msdcs_record(samdb, forestdn, prefix, dnsforest):
+ # DC=_msdcs.<DNSFOREST>,CN=MicrosoftDNS,<PREFIX>,<FORESTDN>
+ msg = ldb.Message(ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
+ (dnsforest, prefix, forestdn)))
+ msg["objectClass"] = ["top", "dnsZone"]
+ samdb.add(msg)
+
+
+def add_dc_domain_records(samdb, domaindn, prefix, site, dnsdomain, hostname,
+ hostip, hostip6):
+
+ fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
+
+ # Set up domain container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ domain_container_dn = ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" %
+ (dnsdomain, prefix, domaindn))
+
+ # DC=@ record
+ add_at_record(samdb, domain_container_dn, "DC=@", hostname, dnsdomain,
+ hostip, hostip6)
+
+ # DC=<HOSTNAME> record
+ add_host_record(samdb, domain_container_dn, "DC=%s" % hostname, hostip,
+ hostip6)
+
+ # DC=_kerberos._tcp record
+ add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp",
+ fqdn_hostname, 88)
+
+ # DC=_kerberos._tcp.<SITENAME>._sites record
+ add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp.%s._sites" %
+ site, fqdn_hostname, 88)
+
+ # DC=_kerberos._udp record
+ add_srv_record(samdb, domain_container_dn, "DC=_kerberos._udp",
+ fqdn_hostname, 88)
+
+ # DC=_kpasswd._tcp record
+ add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._tcp",
+ fqdn_hostname, 464)
+
+ # DC=_kpasswd._udp record
+ add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._udp",
+ fqdn_hostname, 464)
+
+ # DC=_ldap._tcp record
+ add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp", fqdn_hostname,
+ 389)
+
+ # DC=_ldap._tcp.<SITENAME>._sites record
+ add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites" %
+ site, fqdn_hostname, 389)
+
+ # FIXME: The number of SRV records depend on the various roles this DC has.
+ # _gc and _msdcs records are added if the we are the forest dc and not subdomain dc
+ #
+ # Assumption: current DC is GC and add all the entries
+
+ # DC=_gc._tcp record
+ add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp", fqdn_hostname,
+ 3268)
+
+ # DC=_gc._tcp.<SITENAME>,_sites record
+ add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp.%s._sites" % site,
+ fqdn_hostname, 3268)
+
+ # DC=_msdcs record
+ add_ns_glue_record(samdb, domain_container_dn, "DC=_msdcs", fqdn_hostname)
+
+ # FIXME: Following entries are added only if DomainDnsZones and ForestDnsZones partitions
+ # are created
+ #
+ # Assumption: Additional entries won't hurt on os_level = 2000
+
+ # DC=_ldap._tcp.<SITENAME>._sites.DomainDnsZones
+ add_srv_record(samdb, domain_container_dn,
+ "DC=_ldap._tcp.%s._sites.DomainDnsZones" % site, fqdn_hostname,
+ 389)
+
+ # DC=_ldap._tcp.<SITENAME>._sites.ForestDnsZones
+ add_srv_record(samdb, domain_container_dn,
+ "DC=_ldap._tcp.%s._sites.ForestDnsZones" % site, fqdn_hostname,
+ 389)
+
+ # DC=_ldap._tcp.DomainDnsZones
+ add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.DomainDnsZones",
+ fqdn_hostname, 389)
+
+ # DC=_ldap._tcp.ForestDnsZones
+ add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.ForestDnsZones",
+ fqdn_hostname, 389)
+
+ # DC=DomainDnsZones
+ add_host_record(samdb, domain_container_dn, "DC=DomainDnsZones", hostip,
+ hostip6)
+
+ # DC=ForestDnsZones
+ add_host_record(samdb, domain_container_dn, "DC=ForestDnsZones", hostip,
+ hostip6)
+
+
+def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
+ hostip, hostip6, domainguid, ntdsguid):
+
+ fqdn_hostname = "%s.%s" % (hostname, dnsforest)
+
+ # Set up forest container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+ forest_container_dn = ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
+ (dnsforest, prefix, forestdn))
+
+ # DC=@ record
+ add_at_record(samdb, forest_container_dn, "DC=@", hostname, dnsforest,
+ None, None)
+
+ # DC=_kerberos._tcp.dc record
+ add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.dc",
+ fqdn_hostname, 88)
+
+ # DC=_kerberos._tcp.<SITENAME>._sites.dc record
+ add_srv_record(samdb, forest_container_dn,
+ "DC=_kerberos._tcp.%s._sites.dc" % site, fqdn_hostname, 88)
+
+ # DC=_ldap._tcp.dc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.dc",
+ fqdn_hostname, 389)
+
+ # DC=_ldap._tcp.<SITENAME>._sites.dc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.dc" %
+ site, fqdn_hostname, 389)
+
+ # DC=_ldap._tcp.<SITENAME>._sites.gc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.gc" %
+ site, fqdn_hostname, 3268)
+
+ # DC=_ldap._tcp.gc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.gc",
+ fqdn_hostname, 3268)
+
+ # DC=_ldap._tcp.pdc record
+ add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.pdc",
+ fqdn_hostname, 389)
+
+ # DC=gc record
+ add_host_record(samdb, forest_container_dn, "DC=gc", hostip, hostip6)
+
+ # DC=_ldap._tcp.<DOMAINGUID>.domains record
+ add_srv_record(samdb, forest_container_dn,
+ "DC=_ldap._tcp.%s.domains" % domainguid, fqdn_hostname, 389)
+
+ # DC=<NTDSGUID>
+ add_cname_record(samdb, forest_container_dn, "DC=%s" % ntdsguid,
+ fqdn_hostname)
+
+
+def secretsdb_setup_dns(secretsdb, names, private_dir, realm,
+ dnsdomain, dns_keytab_path, dnspass):
+ """Add DNS specific bits to a secrets database.
+
+ :param secretsdb: Ldb Handle to the secrets database
+ :param names: Names shortcut
+ :param machinepass: Machine password
+ """
+ try:
+ os.unlink(os.path.join(private_dir, dns_keytab_path))
+ except OSError:
+ pass
+
+ setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
+ "REALM": realm,
+ "DNSDOMAIN": dnsdomain,
+ "DNS_KEYTAB": dns_keytab_path,
+ "DNSPASS_B64": b64encode(dnspass),
+ "HOSTNAME": names.hostname,
+ "DNSNAME" : '%s.%s' % (
+ names.netbiosname.lower(), names.dnsdomain.lower())
+ })
+
+
+def create_dns_dir(logger, paths):
+ """Write out a DNS zone file, from the info in the current database.
+
+ :param logger: Logger object
+ :param paths: paths object
+ """
+ dns_dir = os.path.dirname(paths.dns)
+
+ try:
+ shutil.rmtree(dns_dir, True)
+ except OSError:
+ pass
+
+ os.mkdir(dns_dir, 0770)
+
+ if paths.bind_gid is not None:
+ try:
+ os.chown(dns_dir, -1, paths.bind_gid)
+ # chmod needed to cope with umask
+ os.chmod(dns_dir, 0770)
+ except OSError:
+ if not os.environ.has_key('SAMBA_SELFTEST'):
+ logger.error("Failed to chown %s to bind gid %u" % (
+ dns_dir, paths.bind_gid))
+
+
+def create_zone_file(lp, logger, paths, targetdir, dnsdomain,
+ hostip, hostip6, hostname, realm, domainguid,
+ ntdsguid, site):
+ """Write out a DNS zone file, from the info in the current database.
+
+ :param paths: paths object
+ :param dnsdomain: DNS Domain name
+ :param domaindn: DN of the Domain
+ :param hostip: Local IPv4 IP
+ :param hostip6: Local IPv6 IP
+ :param hostname: Local hostname
+ :param realm: Realm name
+ :param domainguid: GUID of the domain.
+ :param ntdsguid: GUID of the hosts nTDSDSA record.
+ """
+ assert isinstance(domainguid, str)
+
+ if hostip6 is not None:
+ hostip6_base_line = " IN AAAA " + hostip6
+ hostip6_host_line = hostname + " IN AAAA " + hostip6
+ gc_msdcs_ip6_line = "gc._msdcs IN AAAA " + hostip6
+ else:
+ hostip6_base_line = ""
+ hostip6_host_line = ""
+ gc_msdcs_ip6_line = ""
+
+ if hostip is not None:
+ hostip_base_line = " IN A " + hostip
+ hostip_host_line = hostname + " IN A " + hostip
+ gc_msdcs_ip_line = "gc._msdcs IN A " + hostip
+ else:
+ hostip_base_line = ""
+ hostip_host_line = ""
+ gc_msdcs_ip_line = ""
+
+ # we need to freeze the zone while we update the contents
+ if targetdir is None:
+ rndc = ' '.join(lp.get("rndc command"))
+ os.system(rndc + " freeze " + lp.get("realm"))
+
+ setup_file(setup_path("provision.zone"), paths.dns, {
+ "HOSTNAME": hostname,
+ "DNSDOMAIN": dnsdomain,
+ "REALM": realm,
+ "HOSTIP_BASE_LINE": hostip_base_line,
+ "HOSTIP_HOST_LINE": hostip_host_line,
+ "DOMAINGUID": domainguid,
+ "DATESTRING": time.strftime("%Y%m%d%H"),
+ "DEFAULTSITE": site,
+ "NTDSGUID": ntdsguid,
+ "HOSTIP6_BASE_LINE": hostip6_base_line,
+ "HOSTIP6_HOST_LINE": hostip6_host_line,
+ "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
+ "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
+ })
+
+ if paths.bind_gid is not None:
+ try:
+ os.chown(paths.dns, -1, paths.bind_gid)
+ # chmod needed to cope with umask
+ os.chmod(paths.dns, 0664)
+ except OSError:
+ if not os.environ.has_key('SAMBA_SELFTEST'):
+ logger.error("Failed to chown %s to bind gid %u" % (
+ paths.dns, paths.bind_gid))
+
+ if targetdir is None:
+ os.system(rndc + " unfreeze " + lp.get("realm"))
+
+
+def tdb_copy(logger, file1, file2):
+ """Copy tdb file using tdbbackup utility and rename it
+ """
+ # Find the location of tdbbackup tool
+ dirs = ["bin", samba.param.bin_dir()] + os.getenv('PATH').split(os.pathsep)
+ for d in dirs:
+ toolpath = os.path.join(d, "tdbbackup")
+ if os.path.exists(toolpath):
+ break
+ status = os.system("%s -s '.dns' %s" % (toolpath, file1))
+ if status == 0:
+ os.rename("%s.dns" % file1, file2)
+ else:
+ raise Exception("Error copying %s" % file1)
+
+
+def create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid):
+ """Create a copy of samdb and give write permissions to named for dns partitions
+ """
+ private_dir = paths.private_dir
+ samldb_dir = os.path.join(private_dir, "sam.ldb.d")
+ dns_dir = os.path.dirname(paths.dns)
+ dns_samldb_dir = os.path.join(dns_dir, "sam.ldb.d")
+
+ # Find the partitions and corresponding filenames
+ partfile = {}
+ res = samdb.search(base="@PARTITION", scope=ldb.SCOPE_BASE, attrs=["partition"])
+ for tmp in res[0]["partition"]:
+ (nc, fname) = tmp.split(':')
+ partfile[nc.upper()] = fname
+
+ # Create empty domain partition
+ domaindn = names.domaindn.upper()
+ domainpart_file = os.path.join(dns_dir, partfile[domaindn])
+ try:
+ os.mkdir(dns_samldb_dir)
+ file(domainpart_file, 'w').close()
+
+ # Fill the basedn and @OPTION records in domain partition
+ dom_ldb = samba.Ldb(domainpart_file)
+ domainguid_line = "objectGUID: %s\n-" % domainguid
+ descr = b64encode(get_domain_descriptor(domainsid))
+ setup_add_ldif(dom_ldb, setup_path("provision_basedn.ldif"), {
+ "DOMAINDN" : names.domaindn,
+ "DOMAINGUID" : domainguid_line,
+ "DOMAINSID" : str(domainsid),
+ "DESCRIPTOR" : descr})
+ setup_add_ldif(dom_ldb,
+ setup_path("provision_basedn_options.ldif"), None)
+ except:
+ logger.error(
+ "Failed to setup database for BIND, AD based DNS cannot be used")
+ raise
+ del partfile[domaindn]
+
+ # Link dns partitions and metadata
+ domainzonedn = "DC=DOMAINDNSZONES,%s" % names.domaindn.upper()
+ forestzonedn = "DC=FORESTDNSZONES,%s" % names.rootdn.upper()
+ domainzone_file = partfile[domainzonedn]
+ forestzone_file = partfile[forestzonedn]
+ metadata_file = "metadata.tdb"
+ try:
+ os.link(os.path.join(samldb_dir, metadata_file),
+ os.path.join(dns_samldb_dir, metadata_file))
+ os.link(os.path.join(private_dir, domainzone_file),
+ os.path.join(dns_dir, domainzone_file))
+ os.link(os.path.join(private_dir, forestzone_file),
+ os.path.join(dns_dir, forestzone_file))
+ except OSError:
+ logger.error(
+ "Failed to setup database for BIND, AD based DNS cannot be used")
+ raise
+ del partfile[domainzonedn]
+ del partfile[forestzonedn]
+
+ # Copy root, config, schema partitions (and any other if any)
+ # Since samdb is open in the current process, copy them in a child process
+ try:
+ tdb_copy(logger,
+ os.path.join(private_dir, "sam.ldb"),
+ os.path.join(dns_dir, "sam.ldb"))
+ for nc in partfile:
+ pfile = partfile[nc]
+ tdb_copy(logger,
+ os.path.join(private_dir, pfile),
+ os.path.join(dns_dir, pfile))
+ except:
+ logger.error(
+ "Failed to setup database for BIND, AD based DNS cannot be used")
+ raise
+
+ # Give bind read/write permissions dns partitions
+ if paths.bind_gid is not None:
+ try:
+ os.chown(samldb_dir, -1, paths.bind_gid)
+ os.chmod(samldb_dir, 0750)
+
+ for dirname, dirs, files in os.walk(dns_dir):
+ for d in dirs:
+ dpath = os.path.join(dirname, d)
+ os.chown(dpath, -1, paths.bind_gid)
+ os.chmod(dpath, 0770)
+ for f in files:
+ if f.endswith('.ldb') or f.endswith('.tdb'):
+ fpath = os.path.join(dirname, f)
+ os.chown(fpath, -1, paths.bind_gid)
+ os.chmod(fpath, 0660)
+ except OSError:
+ if not os.environ.has_key('SAMBA_SELFTEST'):
+ logger.error(
+ "Failed to set permissions to sam.ldb* files, fix manually")
+ else:
+ if not os.environ.has_key('SAMBA_SELFTEST'):
+ logger.warning("""Unable to find group id for BIND,
+ set permissions to sam.ldb* files manually""")
+
+
+def create_dns_update_list(lp, logger, paths):
+ """Write out a dns_update_list file"""
+ # note that we use no variable substitution on this file
+ # the substitution is done at runtime by samba_dnsupdate, samba_spnupdate
+ setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
+ setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
+
+
+def create_named_conf(paths, realm, dnsdomain, dns_backend):
+ """Write out a file containing zone statements suitable for inclusion in a
+ named.conf file (including GSS-TSIG configuration).
+
+ :param paths: all paths
+ :param realm: Realm name
+ :param dnsdomain: DNS Domain name
+ :param dns_backend: DNS backend type
+ :param keytab_name: File name of DNS keytab file
+ """
+
+ if dns_backend == "BIND9_FLATFILE":
+ setup_file(setup_path("named.conf"), paths.namedconf, {
+ "DNSDOMAIN": dnsdomain,
+ "REALM": realm,
+ "ZONE_FILE": paths.dns,
+ "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
+ "NAMED_CONF": paths.namedconf,
+ "NAMED_CONF_UPDATE": paths.namedconf_update
+ })
+
+ setup_file(setup_path("named.conf.update"), paths.namedconf_update)
+
+ elif dns_backend == "BIND9_DLZ":
+ setup_file(setup_path("named.conf.dlz"), paths.namedconf, {
+ "NAMED_CONF": paths.namedconf,
+ "MODULESDIR" : samba.param.modules_dir(),
+ })
+
+
+def create_named_txt(path, realm, dnsdomain, dnsname, private_dir,
+ keytab_name):
+ """Write out a file containing zone statements suitable for inclusion in a
+ named.conf file (including GSS-TSIG configuration).
+
+ :param path: Path of the new named.conf file.
+ :param realm: Realm name
+ :param dnsdomain: DNS Domain name
+ :param private_dir: Path to private directory
+ :param keytab_name: File name of DNS keytab file
+ """
+ setup_file(setup_path("named.txt"), path, {
+ "DNSDOMAIN": dnsdomain,
+ "DNSNAME" : dnsname,
+ "REALM": realm,
+ "DNS_KEYTAB": keytab_name,
+ "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
+ "PRIVATE_DIR": private_dir
+ })
+
+
+def is_valid_dns_backend(dns_backend):
+ return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
+
+
+def is_valid_os_level(os_level):
+ return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2008_R2
+
+
+def create_dns_legacy(samdb, domainsid, forestdn, dnsadmins_sid):
+ # Set up MicrosoftDNS container
+ add_dns_container(samdb, forestdn, "CN=System", domainsid, dnsadmins_sid)
+ # Add root servers
+ add_rootservers(samdb, forestdn, "CN=System")
+
+
+def fill_dns_data_legacy(samdb, domainsid, forestdn, dnsdomain, site, hostname,
+ hostip, hostip6, dnsadmins_sid):
+ # Add domain record
+ add_domain_record(samdb, forestdn, "CN=System", dnsdomain, domainsid,
+ dnsadmins_sid)
+
+ # Add DNS records for a DC in domain
+ add_dc_domain_records(samdb, forestdn, "CN=System", site, dnsdomain,
+ hostname, hostip, hostip6)
+
+
+def create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
+ dnsadmins_sid):
+ # Set up additional partitions (DomainDnsZones, ForstDnsZones)
+ setup_dns_partitions(samdb, domainsid, domaindn, forestdn,
+ names.configdn, names.serverdn)
+
+ # Set up MicrosoftDNS containers
+ add_dns_container(samdb, domaindn, "DC=DomainDnsZones", domainsid,
+ dnsadmins_sid)
+ add_dns_container(samdb, forestdn, "DC=ForestDnsZones", domainsid,
+ dnsadmins_sid)
+
+
+def fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
+ dnsdomain, dnsforest, hostname, hostip, hostip6,
+ domainguid, ntdsguid, dnsadmins_sid, autofill=True):
+ """Fill data in various AD partitions
+
+ :param samdb: LDB object connected to sam.ldb file
+ :param domainsid: Domain SID (as dom_sid object)
+ :param site: Site name to create hostnames in
+ :param domaindn: DN of the domain
+ :param forestdn: DN of the forest
+ :param dnsdomain: DNS name of the domain
+ :param dnsforest: DNS name of the forest
+ :param hostname: Host name of this DC
+ :param hostip: IPv4 addresses
+ :param hostip6: IPv6 addresses
+ :param domainguid: Domain GUID
+ :param ntdsguid: NTDS GUID
+ :param dnsadmins_sid: SID for DnsAdmins group
+ :param autofill: Create DNS records (using fixed template)
+ """
+
+ ##### Set up DC=DomainDnsZones,<DOMAINDN>
+ # Add rootserver records
+ add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
+
+ # Add domain record
+ add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain,
+ domainsid, dnsadmins_sid)
+
+ # Add DNS records for a DC in domain
+ if autofill:
+ add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site,
+ dnsdomain, hostname, hostip, hostip6)
+
+ ##### Set up DC=ForestDnsZones,<DOMAINDN>
+ # Add _msdcs record
+ add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
+
+ # Add DNS records for a DC in forest
+ if autofill:
+ add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site,
+ dnsforest, hostname, hostip, hostip6,
+ domainguid, ntdsguid)
+
+
+def setup_ad_dns(samdb, secretsdb, domainsid, names, paths, lp, logger,
+ dns_backend, os_level, site, dnspass=None, hostip=None, hostip6=None,
+ targetdir=None):
+ """Provision DNS information (assuming GC role)
+
+ :param samdb: LDB object connected to sam.ldb file
+ :param secretsdb: LDB object connected to secrets.ldb file
+ :param domainsid: Domain SID (as dom_sid object)
+ :param names: Names shortcut
+ :param paths: Paths shortcut
+ :param lp: Loadparm object
+ :param logger: Logger object
+ :param dns_backend: Type of DNS backend
+ :param os_level: Functional level (treated as os level)
+ :param site: Site to create hostnames in
+ :param dnspass: Password for bind's DNS account
+ :param hostip: IPv4 address
+ :param hostip6: IPv6 address
+ :param targetdir: Target directory for creating DNS-related files for BIND9
+ """
+
+ if not is_valid_dns_backend(dns_backend):
+ raise Exception("Invalid dns backend: %r" % dns_backend)
+
+ if not is_valid_os_level(os_level):
+ raise Exception("Invalid os level: %r" % os_level)
+
+ if dns_backend == "NONE":
+ logger.info("No DNS backend set, not configuring DNS")
+ return
+
+ # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
+ logger.info("Adding DNS accounts")
+ add_dns_accounts(samdb, names.domaindn)
+
+ # If dns_backend is BIND9_FLATFILE
+ # Populate only CN=MicrosoftDNS,CN=System,<FORESTDN>
+ #
+ # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
+ # Populate DNS partitions
+
+ # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
+ # All dns records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
+ #
+ # If os_level >= 2003 (DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008,
+ # DS_DOMAIN_FUNCTION_2008_R2)
+ # Root server records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
+ # Domain records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
+ # Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
+ # Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<FORESTDN>
+ domaindn = names.domaindn
+ forestdn = samdb.get_root_basedn().get_linearized()
+
+ dnsdomain = names.dnsdomain.lower()
+ dnsforest = dnsdomain
+
+ hostname = names.netbiosname.lower()
+
+ dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
+ domainguid = get_domainguid(samdb, domaindn)
+
+ # Create CN=System
+ logger.info("Creating CN=MicrosoftDNS,CN=System,%s" % forestdn)
+ create_dns_legacy(samdb, domainsid, forestdn, dnsadmins_sid)
+
+ if os_level == DS_DOMAIN_FUNCTION_2000:
+ # Populating legacy dns
+ logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % forestdn)
+ fill_dns_data_legacy(samdb, domainsid, forestdn, dnsdomain, site,
+ hostname, hostip, hostip6, dnsadmins_sid)
+
+ elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
+ os_level >= DS_DOMAIN_FUNCTION_2003:
+
+ # Create DNS partitions
+ logger.info("Creating DomainDnsZones and ForestDnsZones partitions")
+ create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
+ dnsadmins_sid)
+
+ # Populating dns partitions
+ logger.info("Populating DomainDnsZones and ForestDnsZones partitions")
+ fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
+ dnsdomain, dnsforest, hostname, hostip, hostip6,
+ domainguid, names.ntdsguid, dnsadmins_sid)
+
+ if dns_backend.startswith("BIND9_"):
+ setup_bind9_dns(samdb, secretsdb, domainsid, names, paths, lp, logger,
+ dns_backend, os_level, site=site, dnspass=dnspass, hostip=hostip,
+ hostip6=hostip6, targetdir=targetdir)
+
+
+def setup_bind9_dns(samdb, secretsdb, domainsid, names, paths, lp, logger,
+ dns_backend, os_level, site=None, dnspass=None, hostip=None,
+ hostip6=None, targetdir=None):
+ """Provision DNS information (assuming BIND9 backend in DC role)
+
+ :param samdb: LDB object connected to sam.ldb file
+ :param secretsdb: LDB object connected to secrets.ldb file
+ :param domainsid: Domain SID (as dom_sid object)
+ :param names: Names shortcut
+ :param paths: Paths shortcut
+ :param lp: Loadparm object
+ :param logger: Logger object
+ :param dns_backend: Type of DNS backend
+ :param os_level: Functional level (treated as os level)
+ :param site: Site to create hostnames in
+ :param dnspass: Password for bind's DNS account
+ :param hostip: IPv4 address
+ :param hostip6: IPv6 address
+ :param targetdir: Target directory for creating DNS-related files for BIND9
+ """
+
+ if (not is_valid_dns_backend(dns_backend) or
+ not dns_backend.startswith("BIND9_")):
+ raise Exception("Invalid dns backend: %r" % dns_backend)
+
+ if not is_valid_os_level(os_level):
+ raise Exception("Invalid os level: %r" % os_level)
+
+ domaindn = names.domaindn
+
+ domainguid = get_domainguid(samdb, domaindn)
+
+ secretsdb_setup_dns(secretsdb, names,
+ paths.private_dir, realm=names.realm,
+ dnsdomain=names.dnsdomain,
+ dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
+
+ create_dns_dir(logger, paths)
+
+ if dns_backend == "BIND9_FLATFILE":
+ create_zone_file(lp, logger, paths, targetdir, site=site,
+ dnsdomain=names.dnsdomain, hostip=hostip,
+ hostip6=hostip6, hostname=names.hostname,
+ realm=names.realm, domainguid=domainguid,
+ ntdsguid=names.ntdsguid)
+
+ if dns_backend == "BIND9_DLZ" and os_level >= DS_DOMAIN_FUNCTION_2003:
+ create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid)
+
+ create_named_conf(paths, realm=names.realm,
+ dnsdomain=names.dnsdomain, dns_backend=dns_backend)
+
+ create_named_txt(paths.namedtxt,
+ realm=names.realm, dnsdomain=names.dnsdomain,
+ dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
+ private_dir=paths.private_dir,
+ keytab_name=paths.dns_keytab)
+ logger.info("See %s for an example configuration include file for BIND",
+ paths.namedconf)
+ logger.info("and %s for further documentation required for secure DNS "
+ "updates", paths.namedtxt)
diff --git a/source4/scripting/python/samba/samba3.py b/source4/scripting/python/samba/samba3.py
deleted file mode 100644
index 2c323bd0b4..0000000000
--- a/source4/scripting/python/samba/samba3.py
+++ /dev/null
@@ -1,790 +0,0 @@
-# Unix SMB/CIFS implementation.
-# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
-#
-# 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/>.
-#
-
-"""Support for reading Samba 3 data files."""
-
-__docformat__ = "restructuredText"
-
-REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL"
-REGISTRY_DB_VERSION = 1
-
-import os
-import struct
-import tdb
-
-
-def fetch_uint32(tdb, key):
- try:
- data = tdb[key]
- except KeyError:
- return None
- assert len(data) == 4
- return struct.unpack("<L", data)[0]
-
-
-def fetch_int32(tdb, key):
- try:
- data = tdb[key]
- except KeyError:
- return None
- assert len(data) == 4
- return struct.unpack("<l", data)[0]
-
-
-class TdbDatabase(object):
- """Simple Samba 3 TDB database reader."""
- def __init__(self, file):
- """Open a file.
-
- :param file: Path of the file to open.
- """
- self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
- self._check_version()
-
- def _check_version(self):
- pass
-
- def close(self):
- """Close resources associated with this object."""
- self.tdb.close()
-
-
-class Registry(TdbDatabase):
- """Simple read-only support for reading the Samba3 registry.
-
- :note: This object uses the same syntax for registry key paths as
- Samba 3. This particular format uses forward slashes for key path
- separators and abbreviations for the predefined key names.
- e.g.: HKLM/Software/Bar.
- """
- def __len__(self):
- """Return the number of keys."""
- return len(self.keys())
-
- def keys(self):
- """Return list with all the keys."""
- return [k.rstrip("\x00") for k in self.tdb.iterkeys() if not k.startswith(REGISTRY_VALUE_PREFIX)]
-
- def subkeys(self, key):
- """Retrieve the subkeys for the specified key.
-
- :param key: Key path.
- :return: list with key names
- """
- data = self.tdb.get("%s\x00" % key)
- if data is None:
- return []
- (num, ) = struct.unpack("<L", data[0:4])
- keys = data[4:].split("\0")
- assert keys[-1] == ""
- keys.pop()
- assert len(keys) == num
- return keys
-
- def values(self, key):
- """Return a dictionary with the values set for a specific key.
-
- :param key: Key to retrieve values for.
- :return: Dictionary with value names as key, tuple with type and
- data as value."""
- data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
- if data is None:
- return {}
- ret = {}
- (num, ) = struct.unpack("<L", data[0:4])
- data = data[4:]
- for i in range(num):
- # Value name
- (name, data) = data.split("\0", 1)
-
- (type, ) = struct.unpack("<L", data[0:4])
- data = data[4:]
- (value_len, ) = struct.unpack("<L", data[0:4])
- data = data[4:]
-
- ret[name] = (type, data[:value_len])
- data = data[value_len:]
-
- return ret
-
-
-class PolicyDatabase(TdbDatabase):
- """Samba 3 Account Policy database reader."""
- def __init__(self, file):
- """Open a policy database
-
- :param file: Path to the file to open.
- """
- super(PolicyDatabase, self).__init__(file)
- self.min_password_length = fetch_uint32(self.tdb, "min password length\x00")
- self.password_history = fetch_uint32(self.tdb, "password history\x00")
- self.user_must_logon_to_change_password = fetch_uint32(self.tdb, "user must logon to change pasword\x00")
- self.maximum_password_age = fetch_uint32(self.tdb, "maximum password age\x00")
- self.minimum_password_age = fetch_uint32(self.tdb, "minimum password age\x00")
- self.lockout_duration = fetch_uint32(self.tdb, "lockout duration\x00")
- self.reset_count_minutes = fetch_uint32(self.tdb, "reset count minutes\x00")
- self.bad_lockout_minutes = fetch_uint32(self.tdb, "bad lockout minutes\x00")
- self.disconnect_time = fetch_int32(self.tdb, "disconnect time\x00")
- self.refuse_machine_password_change = fetch_uint32(self.tdb, "refuse machine password change\x00")
-
- # FIXME: Read privileges as well
-
-
-GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
-GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
-
-GROUP_PREFIX = "UNIXGROUP/"
-
-# Alias memberships are stored reverse, as memberships. The performance
-# critical operation is to determine the aliases a SID is member of, not
-# listing alias members. So we store a list of alias SIDs a SID is member of
-# hanging of the member as key.
-MEMBEROF_PREFIX = "MEMBEROF/"
-
-class GroupMappingDatabase(TdbDatabase):
- """Samba 3 group mapping database reader."""
- def _check_version(self):
- assert fetch_int32(self.tdb, "INFO/version\x00") in (GROUPDB_DATABASE_VERSION_V1, GROUPDB_DATABASE_VERSION_V2)
-
- def groupsids(self):
- """Retrieve the SIDs for the groups in this database.
-
- :return: List with sids as strings.
- """
- for k in self.tdb.iterkeys():
- if k.startswith(GROUP_PREFIX):
- yield k[len(GROUP_PREFIX):].rstrip("\0")
-
- def get_group(self, sid):
- """Retrieve the group mapping information for a particular group.
-
- :param sid: SID of the group
- :return: None if the group can not be found, otherwise
- a tuple with gid, sid_name_use, the NT name and comment.
- """
- data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid))
- if data is None:
- return data
- (gid, sid_name_use) = struct.unpack("<lL", data[0:8])
- (nt_name, comment, _) = data[8:].split("\0")
- return (gid, sid_name_use, nt_name, comment)
-
- def aliases(self):
- """Retrieve the aliases in this database."""
- for k in self.tdb.iterkeys():
- if k.startswith(MEMBEROF_PREFIX):
- yield k[len(MEMBEROF_PREFIX):].rstrip("\0")
-
-
-# High water mark keys
-IDMAP_HWM_GROUP = "GROUP HWM\0"
-IDMAP_HWM_USER = "USER HWM\0"
-
-IDMAP_GROUP_PREFIX = "GID "
-IDMAP_USER_PREFIX = "UID "
-
-# idmap version determines auto-conversion
-IDMAP_VERSION_V2 = 2
-
-class IdmapDatabase(TdbDatabase):
- """Samba 3 ID map database reader."""
- def _check_version(self):
- assert fetch_int32(self.tdb, "IDMAP_VERSION\0") == IDMAP_VERSION_V2
-
- def uids(self):
- """Retrieve a list of all uids in this database."""
- for k in self.tdb.iterkeys():
- if k.startswith(IDMAP_USER_PREFIX):
- yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
-
- def gids(self):
- """Retrieve a list of all gids in this database."""
- for k in self.tdb.iterkeys():
- if k.startswith(IDMAP_GROUP_PREFIX):
- yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
-
- def get_user_sid(self, uid):
- """Retrieve the SID associated with a particular uid.
-
- :param uid: UID to retrieve SID for.
- :return: A SID or None if no mapping was found.
- """
- data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
- if data is None:
- return data
- return data.rstrip("\0")
-
- def get_group_sid(self, gid):
- data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
- if data is None:
- return data
- return data.rstrip("\0")
-
- def get_user_hwm(self):
- """Obtain the user high-water mark."""
- return fetch_uint32(self.tdb, IDMAP_HWM_USER)
-
- def get_group_hwm(self):
- """Obtain the group high-water mark."""
- return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
-
-
-class SecretsDatabase(TdbDatabase):
- """Samba 3 Secrets database reader."""
- def get_auth_password(self):
- return self.tdb.get("SECRETS/AUTH_PASSWORD")
-
- def get_auth_domain(self):
- return self.tdb.get("SECRETS/AUTH_DOMAIN")
-
- def get_auth_user(self):
- return self.tdb.get("SECRETS/AUTH_USER")
-
- def get_domain_guid(self, host):
- return self.tdb.get("SECRETS/DOMGUID/%s" % host)
-
- def ldap_dns(self):
- for k in self.tdb.iterkeys():
- if k.startswith("SECRETS/LDAP_BIND_PW/"):
- yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
-
- def domains(self):
- """Iterate over domains in this database.
-
- :return: Iterator over the names of domains in this database.
- """
- for k in self.tdb.iterkeys():
- if k.startswith("SECRETS/SID/"):
- yield k[len("SECRETS/SID/"):].rstrip("\0")
-
- def get_ldap_bind_pw(self, host):
- return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
-
- def get_afs_keyfile(self, host):
- return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
-
- def get_machine_sec_channel_type(self, host):
- return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
-
- def get_machine_last_change_time(self, host):
- return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
-
- def get_machine_password(self, host):
- return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
-
- def get_machine_acc(self, host):
- return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
-
- def get_domtrust_acc(self, host):
- return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
-
- def trusted_domains(self):
- for k in self.tdb.iterkeys():
- if k.startswith("SECRETS/$DOMTRUST.ACC/"):
- yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
-
- def get_random_seed(self):
- return self.tdb.get("INFO/random_seed")
-
- def get_sid(self, host):
- return self.tdb.get("SECRETS/SID/%s" % host.upper())
-
-
-SHARE_DATABASE_VERSION_V1 = 1
-SHARE_DATABASE_VERSION_V2 = 2
-
-class ShareInfoDatabase(TdbDatabase):
- """Samba 3 Share Info database reader."""
- def _check_version(self):
- assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
-
- def get_secdesc(self, name):
- """Obtain the security descriptor on a particular share.
-
- :param name: Name of the share
- """
- secdesc = self.tdb.get("SECDESC/%s" % name)
- # FIXME: Run ndr_pull_security_descriptor
- return secdesc
-
-
-class Shares(object):
- """Container for share objects."""
- def __init__(self, lp, shareinfo):
- self.lp = lp
- self.shareinfo = shareinfo
-
- def __len__(self):
- """Number of shares."""
- return len(self.lp) - 1
-
- def __iter__(self):
- """Iterate over the share names."""
- return self.lp.__iter__()
-
-
-ACB_DISABLED = 0x00000001
-ACB_HOMDIRREQ = 0x00000002
-ACB_PWNOTREQ = 0x00000004
-ACB_TEMPDUP = 0x00000008
-ACB_NORMAL = 0x00000010
-ACB_MNS = 0x00000020
-ACB_DOMTRUST = 0x00000040
-ACB_WSTRUST = 0x00000080
-ACB_SVRTRUST = 0x00000100
-ACB_PWNOEXP = 0x00000200
-ACB_AUTOLOCK = 0x00000400
-ACB_ENC_TXT_PWD_ALLOWED = 0x00000800
-ACB_SMARTCARD_REQUIRED = 0x00001000
-ACB_TRUSTED_FOR_DELEGATION = 0x00002000
-ACB_NOT_DELEGATED = 0x00004000
-ACB_USE_DES_KEY_ONLY = 0x00008000
-ACB_DONT_REQUIRE_PREAUTH = 0x00010000
-ACB_PW_EXPIRED = 0x00020000
-ACB_NO_AUTH_DATA_REQD = 0x00080000
-
-acb_info_mapping = {
- 'N': ACB_PWNOTREQ, # 'N'o password.
- 'D': ACB_DISABLED, # 'D'isabled.
- 'H': ACB_HOMDIRREQ, # 'H'omedir required.
- 'T': ACB_TEMPDUP, # 'T'emp account.
- 'U': ACB_NORMAL, # 'U'ser account (normal).
- 'M': ACB_MNS, # 'M'NS logon user account. What is this ?
- 'W': ACB_WSTRUST, # 'W'orkstation account.
- 'S': ACB_SVRTRUST, # 'S'erver account.
- 'L': ACB_AUTOLOCK, # 'L'ocked account.
- 'X': ACB_PWNOEXP, # No 'X'piry on password
- 'I': ACB_DOMTRUST, # 'I'nterdomain trust account.
- ' ': 0
- }
-
-def decode_acb(text):
- """Decode a ACB field.
-
- :param text: ACB text
- :return: integer with flags set.
- """
- assert not "[" in text and not "]" in text
- ret = 0
- for x in text:
- ret |= acb_info_mapping[x]
- return ret
-
-
-class SAMUser(object):
- """Samba 3 SAM User.
-
- :note: Unknown or unset fields are set to None.
- """
- def __init__(self, name, uid=None, lm_password=None, nt_password=None, acct_ctrl=None,
- last_change_time=None, nt_username=None, fullname=None, logon_time=None, logoff_time=None,
- acct_desc=None, group_rid=None, bad_password_count=None, logon_count=None,
- domain=None, dir_drive=None, munged_dial=None, homedir=None, logon_script=None,
- profile_path=None, workstations=None, kickoff_time=None, bad_password_time=None,
- pass_last_set_time=None, pass_can_change_time=None, pass_must_change_time=None,
- user_rid=None, unknown_6=None, nt_password_history=None,
- unknown_str=None, hours=None, logon_divs=None):
- self.username = name
- self.uid = uid
- self.lm_password = lm_password
- self.nt_password = nt_password
- self.acct_ctrl = acct_ctrl
- self.pass_last_set_time = last_change_time
- self.nt_username = nt_username
- self.fullname = fullname
- self.logon_time = logon_time
- self.logoff_time = logoff_time
- self.acct_desc = acct_desc
- self.group_rid = group_rid
- self.bad_password_count = bad_password_count
- self.logon_count = logon_count
- self.domain = domain
- self.dir_drive = dir_drive
- self.munged_dial = munged_dial
- self.homedir = homedir
- self.logon_script = logon_script
- self.profile_path = profile_path
- self.workstations = workstations
- self.kickoff_time = kickoff_time
- self.bad_password_time = bad_password_time
- self.pass_can_change_time = pass_can_change_time
- self.pass_must_change_time = pass_must_change_time
- self.user_rid = user_rid
- self.unknown_6 = unknown_6
- self.nt_password_history = nt_password_history
- self.unknown_str = unknown_str
- self.hours = hours
- self.logon_divs = logon_divs
-
- def __eq__(self, other):
- if not isinstance(other, SAMUser):
- return False
- return self.__dict__ == other.__dict__
-
-
-class SmbpasswdFile(object):
- """Samba 3 smbpasswd file reader."""
- def __init__(self, file):
- self.users = {}
- f = open(file, 'r')
- for l in f.readlines():
- if len(l) == 0 or l[0] == "#":
- continue # Skip comments and blank lines
- parts = l.split(":")
- username = parts[0]
- uid = int(parts[1])
- acct_ctrl = 0
- last_change_time = None
- if parts[2] == "NO PASSWORD":
- acct_ctrl |= ACB_PWNOTREQ
- lm_password = None
- elif parts[2][0] in ("*", "X"):
- # No password set
- lm_password = None
- else:
- lm_password = parts[2]
-
- if parts[3][0] in ("*", "X"):
- # No password set
- nt_password = None
- else:
- nt_password = parts[3]
-
- if parts[4][0] == '[':
- assert "]" in parts[4]
- acct_ctrl |= decode_acb(parts[4][1:-1])
- if parts[5].startswith("LCT-"):
- last_change_time = int(parts[5][len("LCT-"):], 16)
- else: # old style file
- if username[-1] == "$":
- acct_ctrl &= ~ACB_NORMAL
- acct_ctrl |= ACB_WSTRUST
-
- self.users[username] = SAMUser(username, uid, lm_password, nt_password, acct_ctrl, last_change_time)
-
- f.close()
-
- def __len__(self):
- return len(self.users)
-
- def __getitem__(self, name):
- return self.users[name]
-
- def __iter__(self):
- return iter(self.users)
-
- def close(self): # For consistency
- pass
-
-
-TDBSAM_FORMAT_STRING_V0 = "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
-TDBSAM_FORMAT_STRING_V1 = "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
-TDBSAM_FORMAT_STRING_V2 = "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
-TDBSAM_USER_PREFIX = "USER_"
-
-
-class LdapSam(object):
- """Samba 3 LDAP passdb backend reader."""
- def __init__(self, url):
- self.ldap_url = url
-
-
-class TdbSam(TdbDatabase):
- """Samba 3 TDB passdb backend reader."""
- def _check_version(self):
- self.version = fetch_uint32(self.tdb, "INFO/version\0") or 0
- assert self.version in (0, 1, 2)
-
- def usernames(self):
- """Iterate over the usernames in this Tdb database."""
- for k in self.tdb.iterkeys():
- if k.startswith(TDBSAM_USER_PREFIX):
- yield k[len(TDBSAM_USER_PREFIX):].rstrip("\0")
-
- __iter__ = usernames
-
- def __getitem__(self, name):
- data = self.tdb["%s%s\0" % (TDBSAM_USER_PREFIX, name)]
- user = SAMUser(name)
-
- def unpack_string(data):
- (length, ) = struct.unpack("<L", data[:4])
- data = data[4:]
- if length == 0:
- return (None, data)
- return (data[:length].rstrip("\0"), data[length:])
-
- def unpack_int32(data):
- (value, ) = struct.unpack("<l", data[:4])
- return (value, data[4:])
-
- def unpack_uint32(data):
- (value, ) = struct.unpack("<L", data[:4])
- return (value, data[4:])
-
- def unpack_uint16(data):
- (value, ) = struct.unpack("<H", data[:2])
- return (value, data[2:])
-
- (logon_time, data) = unpack_int32(data)
- (logoff_time, data) = unpack_int32(data)
- (kickoff_time, data) = unpack_int32(data)
-
- if self.version > 0:
- (bad_password_time, data) = unpack_int32(data)
- if bad_password_time != 0:
- user.bad_password_time = bad_password_time
- (pass_last_set_time, data) = unpack_int32(data)
- (pass_can_change_time, data) = unpack_int32(data)
- (pass_must_change_time, data) = unpack_int32(data)
-
- if logon_time != 0:
- user.logon_time = logon_time
- user.logoff_time = logoff_time
- user.kickoff_time = kickoff_time
- if pass_last_set_time != 0:
- user.pass_last_set_time = pass_last_set_time
- user.pass_can_change_time = pass_can_change_time
-
- (user.username, data) = unpack_string(data)
- (user.domain, data) = unpack_string(data)
- (user.nt_username, data) = unpack_string(data)
- (user.fullname, data) = unpack_string(data)
- (user.homedir, data) = unpack_string(data)
- (user.dir_drive, data) = unpack_string(data)
- (user.logon_script, data) = unpack_string(data)
- (user.profile_path, data) = unpack_string(data)
- (user.acct_desc, data) = unpack_string(data)
- (user.workstations, data) = unpack_string(data)
- (user.unknown_str, data) = unpack_string(data)
- (user.munged_dial, data) = unpack_string(data)
-
- (user.user_rid, data) = unpack_int32(data)
- (user.group_rid, data) = unpack_int32(data)
-
- (user.lm_password, data) = unpack_string(data)
- (user.nt_password, data) = unpack_string(data)
-
- if self.version > 1:
- (user.nt_password_history, data) = unpack_string(data)
-
- (user.acct_ctrl, data) = unpack_uint16(data)
- (_, data) = unpack_uint32(data) # remove_me field
- (user.logon_divs, data) = unpack_uint16(data)
- (hours, data) = unpack_string(data)
- user.hours = []
- for entry in hours:
- for i in range(8):
- user.hours.append(ord(entry) & (2 ** i) == (2 ** i))
- (user.bad_password_count, data) = unpack_uint16(data)
- (user.logon_count, data) = unpack_uint16(data)
- (user.unknown_6, data) = unpack_uint32(data)
- assert len(data) == 0
- return user
-
-
-def shellsplit(text):
- """Very simple shell-like line splitting.
-
- :param text: Text to split.
- :return: List with parts of the line as strings.
- """
- ret = list()
- inquotes = False
- current = ""
- for c in text:
- if c == "\"":
- inquotes = not inquotes
- elif c in ("\t", "\n", " ") and not inquotes:
- ret.append(current)
- current = ""
- else:
- current += c
- if current != "":
- ret.append(current)
- return ret
-
-
-class WinsDatabase(object):
- """Samba 3 WINS database reader."""
- def __init__(self, file):
- self.entries = {}
- f = open(file, 'r')
- assert f.readline().rstrip("\n") == "VERSION 1 0"
- for l in f.readlines():
- if l[0] == "#": # skip comments
- continue
- entries = shellsplit(l.rstrip("\n"))
- name = entries[0]
- ttl = int(entries[1])
- i = 2
- ips = []
- while "." in entries[i]:
- ips.append(entries[i])
- i+=1
- nb_flags = int(entries[i][:-1], 16)
- assert not name in self.entries, "Name %s exists twice" % name
- self.entries[name] = (ttl, ips, nb_flags)
- f.close()
-
- def __getitem__(self, name):
- return self.entries[name]
-
- def __len__(self):
- return len(self.entries)
-
- def __iter__(self):
- return iter(self.entries)
-
- def items(self):
- """Return the entries in this WINS database."""
- return self.entries.items()
-
- def close(self): # for consistency
- pass
-
-
-class ParamFile(object):
- """Simple smb.conf-compatible file parser
-
- Does not use a parameter table, unlike the "normal".
- """
-
- def __init__(self, sections=None):
- self._sections = sections or {}
-
- def _sanitize_name(self, name):
- return name.strip().lower().replace(" ","")
-
- def __repr__(self):
- return "ParamFile(%r)" % self._sections
-
- def read(self, filename):
- """Read a file.
-
- :param filename: Path to the file
- """
- section = None
- for i, l in enumerate(open(filename, 'r').xreadlines()):
- l = l.strip()
- if not l or l[0] == '#' or l[0] == ';':
- continue
- if l[0] == "[" and l[-1] == "]":
- section = self._sanitize_name(l[1:-1])
- self._sections.setdefault(section, {})
- elif "=" in l:
- (k, v) = l.split("=", 1)
- self._sections[section][self._sanitize_name(k)] = v
- else:
- raise Exception("Unable to parser line %d: %r" % (i+1,l))
-
- def get(self, param, section=None):
- """Return the value of a parameter.
-
- :param param: Parameter name
- :param section: Section name, defaults to "global"
- :return: parameter value as string if found, None otherwise.
- """
- if section is None:
- section = "global"
- section = self._sanitize_name(section)
- if not section in self._sections:
- return None
- param = self._sanitize_name(param)
- if not param in self._sections[section]:
- return None
- return self._sections[section][param].strip()
-
- def __getitem__(self, section):
- return self._sections[section]
-
- def get_section(self, section):
- return self._sections.get(section)
-
- def add_section(self, section):
- self._sections[self._sanitize_name(section)] = {}
-
- def set_string(self, name, value):
- self._sections["global"][name] = value
-
- def get_string(self, name):
- return self._sections["global"].get(name)
-
-
-class Samba3(object):
- """Samba 3 configuration and state data reader."""
- def __init__(self, libdir, smbconfpath):
- """Open the configuration and data for a Samba 3 installation.
-
- :param libdir: Library directory
- :param smbconfpath: Path to the smb.conf file.
- """
- self.smbconfpath = smbconfpath
- self.libdir = libdir
- self.lp = ParamFile()
- self.lp.read(self.smbconfpath)
-
- def libdir_path(self, path):
- if path[0] == "/" or path[0] == ".":
- return path
- return os.path.join(self.libdir, path)
-
- def get_conf(self):
- return self.lp
-
- def get_sam_db(self):
- lp = self.get_conf()
- backends = (lp.get("passdb backend") or "").split(" ")
- if ":" in backends[0]:
- (name, location) = backends[0].split(":", 2)
- else:
- name = backends[0]
- location = None
- if name == "smbpasswd":
- return SmbpasswdFile(self.libdir_path(location or "smbpasswd"))
- elif name == "tdbsam":
- return TdbSam(self.libdir_path(location or "passdb.tdb"))
- elif name == "ldapsam":
- if location is not None:
- return LdapSam("ldap:%s" % location)
- return LdapSam(lp.get("ldap server"))
- else:
- raise NotImplementedError("unsupported passdb backend %s" % backends[0])
-
- def get_policy_db(self):
- return PolicyDatabase(self.libdir_path("account_policy.tdb"))
-
- def get_registry(self):
- return Registry(self.libdir_path("registry.tdb"))
-
- def get_secrets_db(self):
- return SecretsDatabase(self.libdir_path("secrets.tdb"))
-
- def get_shareinfo_db(self):
- return ShareInfoDatabase(self.libdir_path("share_info.tdb"))
-
- def get_idmap_db(self):
- return IdmapDatabase(self.libdir_path("winbindd_idmap.tdb"))
-
- def get_wins_db(self):
- return WinsDatabase(self.libdir_path("wins.dat"))
-
- def get_shares(self):
- return Shares(self.get_conf(), self.get_shareinfo_db())
-
- def get_groupmapping_db(self):
- return GroupMappingDatabase(self.libdir_path("group_mapping.tdb"))
diff --git a/source4/scripting/python/samba/samba3/__init__.py b/source4/scripting/python/samba/samba3/__init__.py
new file mode 100644
index 0000000000..acccff4e29
--- /dev/null
+++ b/source4/scripting/python/samba/samba3/__init__.py
@@ -0,0 +1,408 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
+#
+# 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/>.
+#
+
+"""Support for reading Samba 3 data files."""
+
+__docformat__ = "restructuredText"
+
+REGISTRY_VALUE_PREFIX = "SAMBA_REGVAL"
+REGISTRY_DB_VERSION = 1
+
+import os
+import struct
+import tdb
+
+import passdb
+import param as s3param
+
+
+def fetch_uint32(tdb, key):
+ try:
+ data = tdb[key]
+ except KeyError:
+ return None
+ assert len(data) == 4
+ return struct.unpack("<L", data)[0]
+
+
+def fetch_int32(tdb, key):
+ try:
+ data = tdb[key]
+ except KeyError:
+ return None
+ assert len(data) == 4
+ return struct.unpack("<l", data)[0]
+
+
+class TdbDatabase(object):
+ """Simple Samba 3 TDB database reader."""
+ def __init__(self, file):
+ """Open a file.
+
+ :param file: Path of the file to open.
+ """
+ self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
+ self._check_version()
+
+ def _check_version(self):
+ pass
+
+ def close(self):
+ """Close resources associated with this object."""
+ self.tdb.close()
+
+
+class Registry(TdbDatabase):
+ """Simple read-only support for reading the Samba3 registry.
+
+ :note: This object uses the same syntax for registry key paths as
+ Samba 3. This particular format uses forward slashes for key path
+ separators and abbreviations for the predefined key names.
+ e.g.: HKLM/Software/Bar.
+ """
+ def __len__(self):
+ """Return the number of keys."""
+ return len(self.keys())
+
+ def keys(self):
+ """Return list with all the keys."""
+ return [k.rstrip("\x00") for k in self.tdb.iterkeys() if not k.startswith(REGISTRY_VALUE_PREFIX)]
+
+ def subkeys(self, key):
+ """Retrieve the subkeys for the specified key.
+
+ :param key: Key path.
+ :return: list with key names
+ """
+ data = self.tdb.get("%s\x00" % key)
+ if data is None:
+ return []
+ (num, ) = struct.unpack("<L", data[0:4])
+ keys = data[4:].split("\0")
+ assert keys[-1] == ""
+ keys.pop()
+ assert len(keys) == num
+ return keys
+
+ def values(self, key):
+ """Return a dictionary with the values set for a specific key.
+
+ :param key: Key to retrieve values for.
+ :return: Dictionary with value names as key, tuple with type and
+ data as value."""
+ data = self.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
+ if data is None:
+ return {}
+ ret = {}
+ (num, ) = struct.unpack("<L", data[0:4])
+ data = data[4:]
+ for i in range(num):
+ # Value name
+ (name, data) = data.split("\0", 1)
+
+ (type, ) = struct.unpack("<L", data[0:4])
+ data = data[4:]
+ (value_len, ) = struct.unpack("<L", data[0:4])
+ data = data[4:]
+
+ ret[name] = (type, data[:value_len])
+ data = data[value_len:]
+
+ return ret
+
+
+# High water mark keys
+IDMAP_HWM_GROUP = "GROUP HWM\0"
+IDMAP_HWM_USER = "USER HWM\0"
+
+IDMAP_GROUP_PREFIX = "GID "
+IDMAP_USER_PREFIX = "UID "
+
+# idmap version determines auto-conversion
+IDMAP_VERSION_V2 = 2
+
+class IdmapDatabase(TdbDatabase):
+ """Samba 3 ID map database reader."""
+
+ def _check_version(self):
+ assert fetch_int32(self.tdb, "IDMAP_VERSION\0") == IDMAP_VERSION_V2
+
+ def ids(self):
+ """Retrieve a list of all ids in this database."""
+ for k in self.tdb.iterkeys():
+ if k.startswith(IDMAP_USER_PREFIX):
+ yield k.rstrip("\0").split(" ")
+ if k.startswith(IDMAP_GROUP_PREFIX):
+ yield k.rstrip("\0").split(" ")
+
+ def uids(self):
+ """Retrieve a list of all uids in this database."""
+ for k in self.tdb.iterkeys():
+ if k.startswith(IDMAP_USER_PREFIX):
+ yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
+
+ def gids(self):
+ """Retrieve a list of all gids in this database."""
+ for k in self.tdb.iterkeys():
+ if k.startswith(IDMAP_GROUP_PREFIX):
+ yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip("\0"))
+
+ def get_sid(self, xid, id_type):
+ """Retrive SID associated with a particular id and type.
+
+ :param xid: UID or GID to retrive SID for.
+ :param id_type: Type of id specified - 'UID' or 'GID'
+ """
+ data = self.tdb.get("%s %s\0" % (id_type, str(xid)))
+ if data is None:
+ return data
+ return data.rstrip("\0")
+
+ def get_user_sid(self, uid):
+ """Retrieve the SID associated with a particular uid.
+
+ :param uid: UID to retrieve SID for.
+ :return: A SID or None if no mapping was found.
+ """
+ data = self.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
+ if data is None:
+ return data
+ return data.rstrip("\0")
+
+ def get_group_sid(self, gid):
+ data = self.tdb.get("%s%d\0" % (IDMAP_GROUP_PREFIX, gid))
+ if data is None:
+ return data
+ return data.rstrip("\0")
+
+ def get_user_hwm(self):
+ """Obtain the user high-water mark."""
+ return fetch_uint32(self.tdb, IDMAP_HWM_USER)
+
+ def get_group_hwm(self):
+ """Obtain the group high-water mark."""
+ return fetch_uint32(self.tdb, IDMAP_HWM_GROUP)
+
+
+class SecretsDatabase(TdbDatabase):
+ """Samba 3 Secrets database reader."""
+
+ def get_auth_password(self):
+ return self.tdb.get("SECRETS/AUTH_PASSWORD")
+
+ def get_auth_domain(self):
+ return self.tdb.get("SECRETS/AUTH_DOMAIN")
+
+ def get_auth_user(self):
+ return self.tdb.get("SECRETS/AUTH_USER")
+
+ def get_domain_guid(self, host):
+ return self.tdb.get("SECRETS/DOMGUID/%s" % host)
+
+ def ldap_dns(self):
+ for k in self.tdb.iterkeys():
+ if k.startswith("SECRETS/LDAP_BIND_PW/"):
+ yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0")
+
+ def domains(self):
+ """Iterate over domains in this database.
+
+ :return: Iterator over the names of domains in this database.
+ """
+ for k in self.tdb.iterkeys():
+ if k.startswith("SECRETS/SID/"):
+ yield k[len("SECRETS/SID/"):].rstrip("\0")
+
+ def get_ldap_bind_pw(self, host):
+ return self.tdb.get("SECRETS/LDAP_BIND_PW/%s" % host)
+
+ def get_afs_keyfile(self, host):
+ return self.tdb.get("SECRETS/AFS_KEYFILE/%s" % host)
+
+ def get_machine_sec_channel_type(self, host):
+ return fetch_uint32(self.tdb, "SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)
+
+ def get_machine_last_change_time(self, host):
+ return fetch_uint32(self.tdb, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host)
+
+ def get_machine_password(self, host):
+ return self.tdb.get("SECRETS/MACHINE_PASSWORD/%s" % host)
+
+ def get_machine_acc(self, host):
+ return self.tdb.get("SECRETS/$MACHINE.ACC/%s" % host)
+
+ def get_domtrust_acc(self, host):
+ return self.tdb.get("SECRETS/$DOMTRUST.ACC/%s" % host)
+
+ def trusted_domains(self):
+ for k in self.tdb.iterkeys():
+ if k.startswith("SECRETS/$DOMTRUST.ACC/"):
+ yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0")
+
+ def get_random_seed(self):
+ return self.tdb.get("INFO/random_seed")
+
+ def get_sid(self, host):
+ return self.tdb.get("SECRETS/SID/%s" % host.upper())
+
+
+SHARE_DATABASE_VERSION_V1 = 1
+SHARE_DATABASE_VERSION_V2 = 2
+
+
+class ShareInfoDatabase(TdbDatabase):
+ """Samba 3 Share Info database reader."""
+
+ def _check_version(self):
+ assert fetch_int32(self.tdb, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
+
+ def get_secdesc(self, name):
+ """Obtain the security descriptor on a particular share.
+
+ :param name: Name of the share
+ """
+ secdesc = self.tdb.get("SECDESC/%s" % name)
+ # FIXME: Run ndr_pull_security_descriptor
+ return secdesc
+
+
+class Shares(object):
+ """Container for share objects."""
+ def __init__(self, lp, shareinfo):
+ self.lp = lp
+ self.shareinfo = shareinfo
+
+ def __len__(self):
+ """Number of shares."""
+ return len(self.lp) - 1
+
+ def __iter__(self):
+ """Iterate over the share names."""
+ return self.lp.__iter__()
+
+
+def shellsplit(text):
+ """Very simple shell-like line splitting.
+
+ :param text: Text to split.
+ :return: List with parts of the line as strings.
+ """
+ ret = list()
+ inquotes = False
+ current = ""
+ for c in text:
+ if c == "\"":
+ inquotes = not inquotes
+ elif c in ("\t", "\n", " ") and not inquotes:
+ if current != "":
+ ret.append(current)
+ current = ""
+ else:
+ current += c
+ if current != "":
+ ret.append(current)
+ return ret
+
+
+class WinsDatabase(object):
+ """Samba 3 WINS database reader."""
+ def __init__(self, file):
+ self.entries = {}
+ f = open(file, 'r')
+ assert f.readline().rstrip("\n") == "VERSION 1 0"
+ for l in f.readlines():
+ if l[0] == "#": # skip comments
+ continue
+ entries = shellsplit(l.rstrip("\n"))
+ name = entries[0]
+ ttl = int(entries[1])
+ i = 2
+ ips = []
+ while "." in entries[i]:
+ ips.append(entries[i])
+ i+=1
+ nb_flags = int(entries[i][:-1], 16)
+ assert not name in self.entries, "Name %s exists twice" % name
+ self.entries[name] = (ttl, ips, nb_flags)
+ f.close()
+
+ def __getitem__(self, name):
+ return self.entries[name]
+
+ def __len__(self):
+ return len(self.entries)
+
+ def __iter__(self):
+ return iter(self.entries)
+
+ def items(self):
+ """Return the entries in this WINS database."""
+ return self.entries.items()
+
+ def close(self): # for consistency
+ pass
+
+
+class Samba3(object):
+ """Samba 3 configuration and state data reader."""
+
+ def __init__(self, smbconfpath, s3_lp_ctx=None):
+ """Open the configuration and data for a Samba 3 installation.
+
+ :param smbconfpath: Path to the smb.conf file.
+ :param s3_lp_ctx: Samba3 Loadparm context
+ """
+ self.smbconfpath = smbconfpath
+ if s3_lp_ctx:
+ self.lp = s3_lp_ctx
+ else:
+ self.lp = s3param.get_context()
+ self.lp.load(smbconfpath)
+
+ def statedir_path(self, path):
+ if path[0] == "/" or path[0] == ".":
+ return path
+ return os.path.join(self.lp.get("state directory"), path)
+
+ def privatedir_path(self, path):
+ if path[0] == "/" or path[0] == ".":
+ return path
+ return os.path.join(self.lp.get("private dir"), path)
+
+ def get_conf(self):
+ return self.lp
+
+ def get_sam_db(self):
+ return passdb.PDB(self.lp.get('passdb backend'))
+
+ def get_registry(self):
+ return Registry(self.statedir_path("registry.tdb"))
+
+ def get_secrets_db(self):
+ return SecretsDatabase(self.privatedir_path("secrets.tdb"))
+
+ def get_shareinfo_db(self):
+ return ShareInfoDatabase(self.statedir_path("share_info.tdb"))
+
+ def get_idmap_db(self):
+ return IdmapDatabase(self.statedir_path("winbindd_idmap.tdb"))
+
+ def get_wins_db(self):
+ return WinsDatabase(self.statedir_path("wins.dat"))
+
+ def get_shares(self):
+ return Shares(self.get_conf(), self.get_shareinfo_db())
diff --git a/source4/scripting/python/samba/samdb.py b/source4/scripting/python/samba/samdb.py
index 99f141e664..d83e0a6f7c 100644
--- a/source4/scripting/python/samba/samdb.py
+++ b/source4/scripting/python/samba/samdb.py
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
# Copyright (C) Matthias Dieter Wallnoefer 2009
#
# Based on the original in EJS:
# Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
+# Copyright (C) Giampaolo Lauria <lauria2@yahoo.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
@@ -27,9 +26,11 @@ import samba
import ldb
import time
import base64
+import os
from samba import dsdb
from samba.ndr import ndr_unpack, ndr_pack
from samba.dcerpc import drsblobs, misc
+from samba.common import normalise_int32
__docformat__ = "restructuredText"
@@ -46,7 +47,9 @@ class SamDB(samba.Ldb):
if not auto_connect:
url = None
elif url is None and lp is not None:
- url = lp.get("sam database")
+ url = lp.samdb_url()
+
+ self.url = url
super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
session_info=session_info, credentials=credentials, flags=flags,
@@ -59,43 +62,89 @@ class SamDB(samba.Ldb):
dsdb._dsdb_set_am_rodc(self, am_rodc)
def connect(self, url=None, flags=0, options=None):
- if self.lp is not None:
+ '''connect to the database'''
+ if self.lp is not None and not os.path.exists(url):
url = self.lp.private_path(url)
+ self.url = url
super(SamDB, self).connect(url=url, flags=flags,
options=options)
def am_rodc(self):
+ '''return True if we are an RODC'''
return dsdb._am_rodc(self)
+ def am_pdc(self):
+ '''return True if we are an PDC emulator'''
+ return dsdb._am_pdc(self)
+
def domain_dn(self):
+ '''return the domain DN'''
return str(self.get_default_basedn())
+ def disable_account(self, search_filter):
+ """Disables an account
+
+ :param search_filter: LDAP filter to find the user (eg
+ samccountname=name)
+ """
+
+ flags = samba.dsdb.UF_ACCOUNTDISABLE
+ self.toggle_userAccountFlags(search_filter, flags, on=True)
+
def enable_account(self, search_filter):
"""Enables an account
:param search_filter: LDAP filter to find the user (eg
samccountname=name)
"""
+
+ flags = samba.dsdb.UF_ACCOUNTDISABLE | samba.dsdb.UF_PASSWD_NOTREQD
+ self.toggle_userAccountFlags(search_filter, flags, on=False)
+
+ def toggle_userAccountFlags(self, search_filter, flags, flags_str=None,
+ on=True, strict=False):
+ """Toggle_userAccountFlags
+
+ :param search_filter: LDAP filter to find the user (eg
+ samccountname=name)
+ :param flags: samba.dsdb.UF_* flags
+ :param on: on=True (default) => set, on=False => unset
+ :param strict: strict=False (default) ignore if no action is needed
+ strict=True raises an Exception if...
+ """
res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
expression=search_filter, attrs=["userAccountControl"])
+ if len(res) == 0:
+ raise Exception("Unable to find account where '%s'" % search_filter)
assert(len(res) == 1)
- user_dn = res[0].dn
+ account_dn = res[0].dn
+
+ old_uac = int(res[0]["userAccountControl"][0])
+ if on:
+ if strict and (old_uac & flags):
+ error = "Account flag(s) '%s' already set" % flags_str
+ raise Exception(error)
+
+ new_uac = old_uac | flags
+ else:
+ if strict and not (old_uac & flags):
+ error = "Account flag(s) '%s' already unset" % flags_str
+ raise Exception(error)
+
+ new_uac = old_uac & ~flags
- userAccountControl = int(res[0]["userAccountControl"][0])
- if userAccountControl & 0x2:
- # remove disabled bit
- userAccountControl = userAccountControl & ~0x2
- if userAccountControl & 0x20:
- # remove 'no password required' bit
- userAccountControl = userAccountControl & ~0x20
+ if old_uac == new_uac:
+ return
mod = """
dn: %s
changetype: modify
-replace: userAccountControl
+delete: userAccountControl
+userAccountControl: %u
+add: userAccountControl
userAccountControl: %u
-""" % (user_dn, userAccountControl)
+""" % (account_dn, old_uac, new_uac)
self.modify_ldif(mod)
def force_password_change_at_next_login(self, search_filter):
@@ -106,6 +155,8 @@ userAccountControl: %u
"""
res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
expression=search_filter, attrs=[])
+ if len(res) == 0:
+ raise Exception('Unable to find user "%s"' % search_filter)
assert(len(res) == 1)
user_dn = res[0].dn
@@ -138,7 +189,7 @@ pwdLastSet: 0
"objectClass": "group"}
if grouptype is not None:
- ldbmessage["groupType"] = "%d" % grouptype
+ ldbmessage["groupType"] = normalise_int32(grouptype)
if description is not None:
ldbmessage["description"] = description
@@ -160,7 +211,7 @@ pwdLastSet: 0
:param groupname: Name of the target group
"""
- groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (groupname, "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
+ groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
self.transaction_start()
try:
targetgroup = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
@@ -169,24 +220,24 @@ pwdLastSet: 0
raise Exception('Unable to find group "%s"' % groupname)
assert(len(targetgroup) == 1)
self.delete(targetgroup[0].dn)
- except Exception:
+ except:
self.transaction_cancel()
raise
else:
self.transaction_commit()
- def add_remove_group_members(self, groupname, listofmembers,
+ def add_remove_group_members(self, groupname, members,
add_members_operation=True):
"""Adds or removes group members
:param groupname: Name of the target group
- :param listofmembers: Comma-separated list of group members
+ :param members: list of group members
:param add_members_operation: Defines if its an add or remove
operation
"""
- groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (groupname, "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
- groupmembers = listofmembers.split(',')
+ groupfilter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (
+ ldb.binary_encode(groupname), "CN=Group,CN=Schema,CN=Configuration", self.domain_dn())
self.transaction_start()
try:
@@ -203,9 +254,10 @@ dn: %s
changetype: modify
""" % (str(targetgroup[0].dn))
- for member in groupmembers:
+ for member in members:
targetmember = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
- expression="(|(sAMAccountName=%s)(CN=%s))" % (member, member), attrs=[])
+ expression="(|(sAMAccountName=%s)(CN=%s))" % (
+ ldb.binary_encode(member), ldb.binary_encode(member)), attrs=[])
if len(targetmember) != 1:
continue
@@ -225,7 +277,7 @@ member: %s
if modified is True:
self.modify_ldif(addtargettogroup)
- except Exception:
+ except:
self.transaction_cancel()
raise
else:
@@ -349,9 +401,31 @@ member: %s
# Sets the password for it
if setpassword:
- self.setpassword("(samAccountName=%s)" % username, password,
+ self.setpassword("(samAccountName=%s)" % ldb.binary_encode(username), password,
force_password_change_at_next_login_req)
- except Exception:
+ except:
+ self.transaction_cancel()
+ raise
+ else:
+ self.transaction_commit()
+
+
+ def deleteuser(self, username):
+ """Deletes a user
+
+ :param username: Name of the target user
+ """
+
+ filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(username), "CN=Person,CN=Schema,CN=Configuration", self.domain_dn())
+ self.transaction_start()
+ try:
+ target = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
+ expression=filter, attrs=[])
+ if len(target) == 0:
+ raise Exception('Unable to find user "%s"' % username)
+ assert(len(target) == 1)
+ self.delete(target[0].dn)
+ except:
self.transaction_cancel()
raise
else:
@@ -386,11 +460,11 @@ unicodePwd:: %s
if force_change_at_next_login:
self.force_password_change_at_next_login(
- "(dn=" + str(user_dn) + ")")
+ "(distinguishedName=" + str(user_dn) + ")")
# modify the userAccountControl to remove the disabled bit
self.enable_account(search_filter)
- except Exception:
+ except:
self.transaction_cancel()
raise
else:
@@ -409,6 +483,8 @@ unicodePwd:: %s
res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
expression=search_filter,
attrs=["userAccountControl", "accountExpires"])
+ if len(res) == 0:
+ raise Exception('Unable to find user "%s"' % search_filter)
assert(len(res) == 1)
user_dn = res[0].dn
@@ -431,7 +507,7 @@ accountExpires: %u
""" % (user_dn, userAccountControl, accountExpires)
self.modify_ldif(setexp)
- except Exception:
+ except:
self.transaction_cancel()
raise
else:
@@ -470,9 +546,31 @@ accountExpires: %u
def get_attid_from_lDAPDisplayName(self, ldap_display_name,
is_schema_nc=False):
+ '''return the attribute ID for a LDAP attribute as an integer as found in DRSUAPI'''
return dsdb._dsdb_get_attid_from_lDAPDisplayName(self,
ldap_display_name, is_schema_nc)
+ def get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name):
+ '''return the syntax OID for a LDAP attribute as a string'''
+ return dsdb._dsdb_get_syntax_oid_from_lDAPDisplayName(self, ldap_display_name)
+
+ def get_systemFlags_from_lDAPDisplayName(self, ldap_display_name):
+ '''return the systemFlags for a LDAP attribute as a integer'''
+ return dsdb._dsdb_get_systemFlags_from_lDAPDisplayName(self, ldap_display_name)
+
+ def get_linkId_from_lDAPDisplayName(self, ldap_display_name):
+ '''return the linkID for a LDAP attribute as a integer'''
+ return dsdb._dsdb_get_linkId_from_lDAPDisplayName(self, ldap_display_name)
+
+ def get_lDAPDisplayName_by_attid(self, attid):
+ '''return the lDAPDisplayName from an integer DRS attribute ID'''
+ return dsdb._dsdb_get_lDAPDisplayName_by_attid(self, attid)
+
+ def get_backlink_from_lDAPDisplayName(self, ldap_display_name):
+ '''return the attribute name of the corresponding backlink from the name
+ of a forward link attribute. If there is no backlink return None'''
+ return dsdb._dsdb_get_backlink_from_lDAPDisplayName(self, ldap_display_name)
+
def set_ntds_settings_dn(self, ntds_settings_dn):
"""Set the NTDS Settings DN, as would be returned on the dsServiceName
rootDSE attribute.
@@ -491,18 +589,38 @@ accountExpires: %u
"""Get the server site name"""
return dsdb._samdb_server_site_name(self)
+ def host_dns_name(self):
+ """return the DNS name of this host"""
+ res = self.search(base='', scope=ldb.SCOPE_BASE, attrs=['dNSHostName'])
+ return res[0]['dNSHostName'][0]
+
+ def domain_dns_name(self):
+ """return the DNS name of the domain root"""
+ domain_dn = self.get_default_basedn()
+ return domain_dn.canonical_str().split('/')[0]
+
+ def forest_dns_name(self):
+ """return the DNS name of the forest root"""
+ forest_dn = self.get_root_basedn()
+ return forest_dn.canonical_str().split('/')[0]
+
def load_partition_usn(self, base_dn):
return dsdb._dsdb_load_partition_usn(self, base_dn)
- def set_schema(self, schema):
- self.set_schema_from_ldb(schema.ldb)
+ def set_schema(self, schema, write_indices_and_attributes=True):
+ self.set_schema_from_ldb(schema.ldb, write_indices_and_attributes=write_indices_and_attributes)
- def set_schema_from_ldb(self, ldb_conn):
- dsdb._dsdb_set_schema_from_ldb(self, ldb_conn)
+ def set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes=True):
+ dsdb._dsdb_set_schema_from_ldb(self, ldb_conn, write_indices_and_attributes)
def dsdb_DsReplicaAttribute(self, ldb, ldap_display_name, ldif_elements):
+ '''convert a list of attribute values to a DRSUAPI DsReplicaAttribute'''
return dsdb._dsdb_DsReplicaAttribute(ldb, ldap_display_name, ldif_elements)
+ def dsdb_normalise_attributes(self, ldb, ldap_display_name, ldif_elements):
+ '''normalise a list of attribute values'''
+ return dsdb._dsdb_normalise_attributes(ldb, ldap_display_name, ldif_elements)
+
def get_attribute_from_attid(self, attid):
""" Get from an attid the associated attribute
@@ -542,7 +660,7 @@ accountExpires: %u
for the given attribute. None if the attribute is not replicated
"""
- res = self.search(expression="dn=%s" % dn,
+ res = self.search(expression="distinguishedName=%s" % dn,
scope=ldb.SCOPE_SUBTREE,
controls=["search_options:1:2"],
attrs=["replPropertyMetaData"])
@@ -564,7 +682,7 @@ accountExpires: %u
def set_attribute_replmetadata_version(self, dn, att, value,
addifnotexist=False):
- res = self.search(expression="dn=%s" % dn,
+ res = self.search(expression="distinguishedName=%s" % dn,
scope=ldb.SCOPE_SUBTREE,
controls=["search_options:1:2"],
attrs=["replPropertyMetaData"])
@@ -622,6 +740,12 @@ accountExpires: %u
def get_partitions_dn(self):
return dsdb._dsdb_get_partitions_dn(self)
+ def get_nc_root(self, dn):
+ return dsdb._dsdb_get_nc_root(self, dn)
+
+ def get_wellknown_dn(self, nc_root, wkguid):
+ return dsdb._dsdb_get_wellknown_dn(self, nc_root, wkguid)
+
def set_minPwdAge(self, value):
m = ldb.Message()
m.dn = ldb.Dn(self, self.domain_dn())
@@ -711,3 +835,27 @@ accountExpires: %u
if sd:
m["nTSecurityDescriptor"] = ndr_pack(sd)
self.add(m)
+
+ def sequence_number(self, seq_type):
+ """Returns the value of the sequence number according to the requested type
+ :param seq_type: type of sequence number
+ """
+ self.transaction_start()
+ try:
+ seq = super(SamDB, self).sequence_number(seq_type)
+ except:
+ self.transaction_cancel()
+ raise
+ else:
+ self.transaction_commit()
+ return seq
+
+ def get_dsServiceName(self):
+ '''get the NTDS DN from the rootDSE'''
+ res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"])
+ return res[0]["dsServiceName"][0]
+
+ def get_serverName(self):
+ '''get the server DN from the rootDSE'''
+ res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
+ return res[0]["serverName"][0]
diff --git a/source4/scripting/python/samba/schema.py b/source4/scripting/python/samba/schema.py
index 8bac26e24f..2d1315093b 100644
--- a/source4/scripting/python/samba/schema.py
+++ b/source4/scripting/python/samba/schema.py
@@ -116,11 +116,11 @@ class Schema(object):
self.prefixmap_data = b64encode(self.prefixmap_data)
# We don't actually add this ldif, just parse it
- prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % self.prefixmap_data
- self.set_from_ldif(prefixmap_ldif, self.schema_data)
+ prefixmap_ldif = "dn: %s\nprefixMap:: %s\n\n" % (self.schemadn, self.prefixmap_data)
+ self.set_from_ldif(prefixmap_ldif, self.schema_data, self.schemadn)
- def set_from_ldif(self, pf, df):
- dsdb._dsdb_set_schema_from_ldif(self.ldb, pf, df)
+ def set_from_ldif(self, pf, df, dn):
+ dsdb._dsdb_set_schema_from_ldif(self.ldb, pf, df, dn)
def write_to_tmp_ldb(self, schemadb_path):
self.ldb.connect(url=schemadb_path)
@@ -137,7 +137,7 @@ dn: @INDEXLIST
self.ldb.add_ldif(self.schema_dn_add)
self.ldb.modify_ldif(self.schema_dn_modify)
self.ldb.add_ldif(self.schema_data)
- except Exception:
+ except:
self.ldb.transaction_cancel()
raise
else:
@@ -155,16 +155,16 @@ dn: @INDEXLIST
return dsdb._dsdb_convert_schema_to_openldap(self.ldb, target, mapping)
-# Return a hash with the forward attribute as a key and the back as the value
+# Return a hash with the forward attribute as a key and the back as the value
def get_linked_attributes(schemadn,schemaldb):
attrs = ["linkID", "lDAPDisplayName"]
res = schemaldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
attributes = {}
for i in range (0, len(res)):
expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
- target = schemaldb.searchone(basedn=schemadn,
- expression=expression,
- attribute="lDAPDisplayName",
+ target = schemaldb.searchone(basedn=schemadn,
+ expression=expression,
+ attribute="lDAPDisplayName",
scope=SCOPE_SUBTREE)
if target is not None:
attributes[str(res[i]["lDAPDisplayName"])]=str(target)
diff --git a/source4/scripting/python/samba/sd_utils.py b/source4/scripting/python/samba/sd_utils.py
index 4694f4cc76..ded9bfc192 100644
--- a/source4/scripting/python/samba/sd_utils.py
+++ b/source4/scripting/python/samba/sd_utils.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Utility methods for security descriptor manipulation
#
# Copyright Nadezhda Ivanova 2010 <nivanova@samba.org>
@@ -18,22 +16,24 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+"""Utility methods for security descriptor manipulation."""
+
import samba
from ldb import Message, MessageElement, Dn
from ldb import FLAG_MOD_REPLACE, SCOPE_BASE
from samba.ndr import ndr_pack, ndr_unpack
from samba.dcerpc import security
-class SDUtils:
- '''Some utilities for manipulation of security descriptors
- on objects'''
+
+class SDUtils(object):
+ """Some utilities for manipulation of security descriptors on objects."""
def __init__(self, samdb):
self.ldb = samdb
self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
def modify_sd_on_dn(self, object_dn, sd, controls=None):
- """ Modify security descriptor using either SDDL string
+ """Modify security descriptor using either SDDL string
or security.descriptor object
"""
m = Message()
@@ -60,20 +60,21 @@ class SDUtils:
return ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
def dacl_add_ace(self, object_dn, ace):
- """ Adds an ACE to an objects security descriptor
+ """Add an ACE to an objects security descriptor
"""
desc = self.read_sd_on_dn(object_dn)
desc_sddl = desc.as_sddl(self.domain_sid)
if ace in desc_sddl:
return
if desc_sddl.find("(") >= 0:
- desc_sddl = desc_sddl[:desc_sddl.index("(")] + ace + desc_sddl[desc_sddl.index("("):]
+ desc_sddl = (desc_sddl[:desc_sddl.index("(")] + ace +
+ desc_sddl[desc_sddl.index("("):])
else:
desc_sddl = desc_sddl + ace
self.modify_sd_on_dn(object_dn, desc_sddl)
def get_sd_as_sddl(self, object_dn, controls=None):
- """ Return object nTSecutiryDescriptor in SDDL format
+ """Return object nTSecutiryDescriptor in SDDL format
"""
desc = self.read_sd_on_dn(object_dn, controls=controls)
return desc.as_sddl(self.domain_sid)
diff --git a/source4/scripting/python/samba/sites.py b/source4/scripting/python/samba/sites.py
new file mode 100644
index 0000000000..76c57dd11c
--- /dev/null
+++ b/source4/scripting/python/samba/sites.py
@@ -0,0 +1,125 @@
+# python site manipulation code
+# 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/>.
+#
+
+"""Manipulating sites."""
+
+import ldb
+from ldb import FLAG_MOD_ADD
+
+
+class SiteException(Exception):
+ """Base element for Sites errors"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return "SiteException: " + self.value
+
+
+class SiteNotFoundException(SiteException):
+ """Raised when the site is not found and it's expected to exists."""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return "SiteNotFoundException: " + self.value
+
+class SiteAlreadyExistsException(SiteException):
+ """Raised when the site is not found and it's expected not to exists."""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return "SiteAlreadyExists: " + self.value
+
+class SiteServerNotEmptyException(SiteException):
+ """Raised when the site still has servers attached."""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return "SiteServerNotEmpty: " + self.value
+
+def create_site(samdb, configDn, siteName):
+ """
+ Create a site
+
+ :param samdb: A samdb connection
+ :param configDn: The DN of the configuration partition
+ :param siteName: Name of the site to create
+ :return: True upon success
+ :raise SiteAlreadyExists: if the site to be created already exists.
+ """
+
+ ret = samdb.search(base=configDn, scope=ldb.SCOPE_SUBTREE,
+ expression='(&(objectclass=Site)(cn=%s))' % siteName)
+ if len(ret) != 0:
+ raise SiteAlreadyExistsException('A site with the name %s already exists' % siteName)
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "Cn=%s,CN=Sites,%s" % (siteName, str(configDn)))
+ m["objectclass"] = ldb.MessageElement("site", FLAG_MOD_ADD, "objectclass")
+
+ samdb.add(m)
+
+ m2 = ldb.Message()
+ m2.dn = ldb.Dn(samdb, "Cn=NTDS Site Settings,%s" % str(m.dn))
+ m2["objectclass"] = ldb.MessageElement("nTDSSiteSettings", FLAG_MOD_ADD, "objectclass")
+
+ samdb.add(m2)
+
+ m3 = ldb.Message()
+ m3.dn = ldb.Dn(samdb, "Cn=Servers,%s" % str(m.dn))
+ m3["objectclass"] = ldb.MessageElement("serversContainer", FLAG_MOD_ADD, "objectclass")
+
+ samdb.add(m3)
+
+ return True
+
+def delete_site(samdb, configDn, siteName):
+ """
+ Delete a site
+
+ :param samdb: A samdb connection
+ :param configDn: The DN of the configuration partition
+ :param siteName: Name of the site to delete
+ :return: True upon success
+ :raise SiteNotFoundException: if the site to be deleted do not exists.
+ :raise SiteServerNotEmpty: if the site has still servers in it.
+ """
+
+ dnsites = ldb.Dn(samdb, "CN=Sites,%s" % (str(configDn)))
+ dnsite = ldb.Dn(samdb, "Cn=%s,CN=Sites,%s" % (siteName, str(configDn)))
+ dnserver = ldb.Dn(samdb, "Cn=Servers,%s" % str(dnsite))
+
+ ret = samdb.search(base=dnsites, scope=ldb.SCOPE_ONELEVEL,
+ expression='(dn=%s)' % str(dnsite))
+ if len(ret) != 1:
+ raise SiteNotFoundException('Site %s do not exists' % siteName)
+
+ ret = samdb.search(base=dnserver, scope=ldb.SCOPE_ONELEVEL,
+ expression='(objectclass=server)')
+ if len(ret) != 0:
+ raise SiteServerNotEmptyException('Site %s still has servers in it, move them before removal' % siteName)
+
+ samdb.delete(dnsite, ["tree_delete:0"])
+
+ return True
diff --git a/source4/scripting/python/samba/tests/__init__.py b/source4/scripting/python/samba/tests/__init__.py
index 58e4130998..2df30a641b 100644
--- a/source4/scripting/python/samba/tests/__init__.py
+++ b/source4/scripting/python/samba/tests/__init__.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2010
#
@@ -28,6 +26,8 @@ from samba.samdb import SamDB
import subprocess
import tempfile
+samba.ensure_external_module("testtools", "testtools")
+
# Other modules import these two classes from here, for convenience:
from testtools.testcase import (
TestCase as TesttoolsTestCase,
@@ -76,11 +76,12 @@ class TestCaseInTempDir(TestCase):
def setUp(self):
super(TestCaseInTempDir, self).setUp()
self.tempdir = tempfile.mkdtemp()
+ self.addCleanup(self._remove_tempdir)
- def tearDown(self):
- super(TestCaseInTempDir, self).tearDown()
+ def _remove_tempdir(self):
self.assertEquals([], os.listdir(self.tempdir))
os.rmdir(self.tempdir)
+ self.tempdir = None
def env_loadparm():
@@ -121,15 +122,20 @@ class ValidNetbiosNameTests(TestCase):
self.assertFalse(samba.valid_netbios_name("*BLA"))
-class BlackboxProcessError(subprocess.CalledProcessError):
- """This exception is raised when a process run by check_output() returns
- a non-zero exit status. Exception instance should contain
- the exact exit code (S.returncode), command line (S.cmd),
- process output (S.stdout) and process error stream (S.stderr)"""
+class BlackboxProcessError(Exception):
+ """This is raised when check_output() process returns a non-zero exit status
+
+ Exception instance should contain the exact exit code (S.returncode),
+ command line (S.cmd), process output (S.stdout) and process error stream
+ (S.stderr)
+ """
+
def __init__(self, returncode, cmd, stdout, stderr):
- super(BlackboxProcessError, self).__init__(returncode, cmd)
+ self.returncode = returncode
+ self.cmd = cmd
self.stdout = stdout
self.stderr = stderr
+
def __str__(self):
return "Command '%s'; exit status %d; stdout: '%s'; stderr: '%s'" % (self.cmd, self.returncode,
self.stdout, self.stderr)
@@ -147,7 +153,10 @@ class BlackboxTestCase(TestCase):
def check_run(self, line):
line = self._make_cmdline(line)
- subprocess.check_call(line, shell=True)
+ p = subprocess.Popen(line, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+ retcode = p.wait()
+ if retcode:
+ raise BlackboxProcessError(retcode, line, p.stdout.read(), p.stderr.read())
def check_output(self, line):
line = self._make_cmdline(line)
diff --git a/source4/scripting/python/samba/tests/auth.py b/source4/scripting/python/samba/tests/auth.py
index 6ecfc2047f..f71e1a784d 100644
--- a/source4/scripting/python/samba/tests/auth.py
+++ b/source4/scripting/python/samba/tests/auth.py
@@ -1,25 +1,23 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
-#
+#
# 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/>.
#
-"""Tests for the Auth Python bindings.
+"""Tests for the Auth Python bindings.
-Note that this just tests the bindings work. It does not intend to test
+Note that this just tests the bindings work. It does not intend to test
the functionality, that's already done in other tests.
"""
diff --git a/source4/scripting/python/samba/tests/blackbox/__init__.py b/source4/scripting/python/samba/tests/blackbox/__init__.py
index 8569cb55bd..361e5cfe5e 100644
--- a/source4/scripting/python/samba/tests/blackbox/__init__.py
+++ b/source4/scripting/python/samba/tests/blackbox/__init__.py
@@ -1 +1,17 @@
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
+
+# 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/>.
+#
+
"""Blackbox tests. """
diff --git a/source4/scripting/python/samba/tests/blackbox/ndrdump.py b/source4/scripting/python/samba/tests/blackbox/ndrdump.py
index c07e32a24f..fca9a93153 100755..100644
--- a/source4/scripting/python/samba/tests/blackbox/ndrdump.py
+++ b/source4/scripting/python/samba/tests/blackbox/ndrdump.py
@@ -1,10 +1,23 @@
-#!/usr/bin/python
# Blackbox tests for ndrdump
# Copyright (C) 2008 Andrew Tridgell <tridge@samba.org>
# Copyright (C) 2008 Andrew Bartlett <abartlet@samba.org>
# Copyright (C) 2010 Jelmer Vernooij <jelmer@samba.org>
# based on test_smbclient.sh
+# 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/>.
+#
+
"""Blackbox tests for ndrdump."""
import os
diff --git a/source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py b/source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py
index 51274cc9bd..62d7bf123b 100644
--- a/source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py
+++ b/source4/scripting/python/samba/tests/blackbox/samba_tool_drs.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Blackbox tests for "samba-tool drs" command
# Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2011
#
@@ -19,7 +17,6 @@
"""Blackbox tests for samba-tool drs."""
-import os
import samba.tests
diff --git a/source4/scripting/python/samba/tests/common.py b/source4/scripting/python/samba/tests/common.py
new file mode 100644
index 0000000000..8794e9dc8b
--- /dev/null
+++ b/source4/scripting/python/samba/tests/common.py
@@ -0,0 +1,40 @@
+# Unix SMB/CIFS implementation. Tests for common.py routines
+# 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/>.
+#
+
+"""Tests for samba.common"""
+
+import samba, os
+import samba.tests
+from samba.common import *
+from samba.samdb import SamDB
+
+
+class CommonTests(samba.tests.TestCase):
+
+ def test_normalise_int32(self):
+ self.assertEquals('17', normalise_int32(17))
+ self.assertEquals('17', normalise_int32('17'))
+ self.assertEquals('-123', normalise_int32('-123'))
+ self.assertEquals('-1294967296', normalise_int32('3000000000'))
+
+ def test_dsdb_Dn(self):
+ sam = samba.Ldb(url='dntest.ldb')
+ dn1 = dsdb_Dn(sam, "DC=foo,DC=bar")
+ dn2 = dsdb_Dn(sam, "B:8:0000000D:<GUID=b3f0ec29-17f4-452a-b002-963e1909d101>;DC=samba,DC=example,DC=com")
+ self.assertEquals(dn2.binary, "0000000D")
+ self.assertEquals(13, dn2.get_binary_integer())
+ os.unlink('dntest.ldb')
diff --git a/source4/scripting/python/samba/tests/core.py b/source4/scripting/python/samba/tests/core.py
index 1c3d7dbe44..8206e68d4f 100644
--- a/source4/scripting/python/samba/tests/core.py
+++ b/source4/scripting/python/samba/tests/core.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
-#
+#
# 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/>.
#
@@ -38,11 +36,11 @@ class SubstituteVarTestCase(TestCase):
samba.substitute_var("foo ${bar}", {"bar": "bla"}))
def test_broken(self):
- self.assertEquals("foo ${bdkjfhsdkfh sdkfh ",
+ self.assertEquals("foo ${bdkjfhsdkfh sdkfh ",
samba.substitute_var("foo ${bdkjfhsdkfh sdkfh ", {"bar": "bla"}))
def test_unknown_var(self):
- self.assertEquals("foo ${bla} gsff",
+ self.assertEquals("foo ${bla} gsff",
samba.substitute_var("foo ${bla} gsff", {"bar": "bla"}))
def test_check_all_substituted(self):
diff --git a/source4/scripting/python/samba/tests/credentials.py b/source4/scripting/python/samba/tests/credentials.py
index 5ed61c6b21..95ee0fa0de 100644
--- a/source4/scripting/python/samba/tests/credentials.py
+++ b/source4/scripting/python/samba/tests/credentials.py
@@ -1,25 +1,23 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
-#
+#
# 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/>.
#
-"""Tests for the Credentials Python bindings.
+"""Tests for the Credentials Python bindings.
-Note that this just tests the bindings work. It does not intend to test
+Note that this just tests the bindings work. It does not intend to test
the functionality, that's already done in other tests.
"""
@@ -80,7 +78,7 @@ class CredentialsTests(samba.tests.TestCase):
def test_get_nt_hash(self):
self.creds.set_password("geheim")
- self.assertEquals('\xc2\xae\x1f\xe6\xe6H\x84cRE>\x81o*\xeb\x93',
+ self.assertEquals('\xc2\xae\x1f\xe6\xe6H\x84cRE>\x81o*\xeb\x93',
self.creds.get_nt_hash())
def test_guess(self):
diff --git a/source4/scripting/python/samba/tests/dcerpc/__init__.py b/source4/scripting/python/samba/tests/dcerpc/__init__.py
index 0f9625570a..d84cb57a09 100644
--- a/source4/scripting/python/samba/tests/dcerpc/__init__.py
+++ b/source4/scripting/python/samba/tests/dcerpc/__init__.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
+#
# Unix SMB/CIFS implementation.
# Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
#
@@ -13,7 +12,7 @@
# 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/>.
diff --git a/source4/scripting/python/samba/tests/dcerpc/bare.py b/source4/scripting/python/samba/tests/dcerpc/bare.py
index 22163697f8..3efbf9d4cf 100644
--- a/source4/scripting/python/samba/tests/dcerpc/bare.py
+++ b/source4/scripting/python/samba/tests/dcerpc/bare.py
@@ -1,24 +1,23 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
+#
# Unix SMB/CIFS implementation.
# Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
-#
+#
# 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/>.
#
-"""Tests for samba.dcerpc.bare."""
+"""Tests for samba.tests.dcerpc.bare."""
from samba.dcerpc import ClientConnection
import samba.tests
@@ -27,26 +26,26 @@ class BareTestCase(samba.tests.TestCase):
def test_bare(self):
# Connect to the echo pipe
- x = ClientConnection("ncalrpc:localhost[DEFAULT]",
+ x = ClientConnection("ncalrpc:localhost[DEFAULT]",
("60a15ec5-4de8-11d7-a637-005056a20182", 1),
lp_ctx=samba.tests.env_loadparm())
self.assertEquals("\x01\x00\x00\x00", x.request(0, chr(0) * 4))
def test_alter_context(self):
- x = ClientConnection("ncalrpc:localhost[DEFAULT]",
+ x = ClientConnection("ncalrpc:localhost[DEFAULT]",
("12345778-1234-abcd-ef00-0123456789ac", 1),
lp_ctx=samba.tests.env_loadparm())
- y = ClientConnection("ncalrpc:localhost",
+ y = ClientConnection("ncalrpc:localhost",
("60a15ec5-4de8-11d7-a637-005056a20182", 1),
basis_connection=x, lp_ctx=samba.tests.env_loadparm())
x.alter_context(("60a15ec5-4de8-11d7-a637-005056a20182", 1))
# FIXME: self.assertEquals("\x01\x00\x00\x00", x.request(0, chr(0) * 4))
def test_two_connections(self):
- x = ClientConnection("ncalrpc:localhost[DEFAULT]",
- ("60a15ec5-4de8-11d7-a637-005056a20182", 1),
+ x = ClientConnection("ncalrpc:localhost[DEFAULT]",
+ ("60a15ec5-4de8-11d7-a637-005056a20182", 1),
lp_ctx=samba.tests.env_loadparm())
- y = ClientConnection("ncalrpc:localhost",
+ y = ClientConnection("ncalrpc:localhost",
("60a15ec5-4de8-11d7-a637-005056a20182", 1),
basis_connection=x, lp_ctx=samba.tests.env_loadparm())
self.assertEquals("\x01\x00\x00\x00", y.request(0, chr(0) * 4))
diff --git a/source4/scripting/python/samba/tests/dcerpc/dnsserver.py b/source4/scripting/python/samba/tests/dcerpc/dnsserver.py
new file mode 100644
index 0000000000..59d6eee761
--- /dev/null
+++ b/source4/scripting/python/samba/tests/dcerpc/dnsserver.py
@@ -0,0 +1,241 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Amitay Isaacs <amitay@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/>.
+#
+
+"""Tests for samba.dcerpc.dnsserver"""
+
+from samba.dcerpc import dnsp, dnsserver
+from samba.tests import RpcInterfaceTestCase, env_get_var_value
+from samba.netcmd.dns import ARecord
+
+class DnsserverTests(RpcInterfaceTestCase):
+
+ def setUp(self):
+ super(DnsserverTests, self).setUp()
+ self.server = env_get_var_value("SERVER_IP")
+ self.zone = env_get_var_value("REALM").lower()
+ self.conn = dnsserver.dnsserver("ncacn_ip_tcp:%s" % (self.server),
+ self.get_loadparm(),
+ self.get_credentials())
+
+ def test_operation2(self):
+ pass
+
+
+ def test_query2(self):
+ typeid, result = self.conn.DnssrvQuery2(dnsserver.DNS_CLIENT_VERSION_W2K,
+ 0,
+ self.server,
+ None,
+ 'ServerInfo')
+ self.assertEquals(dnsserver.DNSSRV_TYPEID_SERVER_INFO_W2K, typeid)
+
+ typeid, result = self.conn.DnssrvQuery2(dnsserver.DNS_CLIENT_VERSION_DOTNET,
+ 0,
+ self.server,
+ None,
+ 'ServerInfo')
+ self.assertEquals(dnsserver.DNSSRV_TYPEID_SERVER_INFO_DOTNET, typeid)
+
+ typeid, result = self.conn.DnssrvQuery2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+ 0,
+ self.server,
+ None,
+ 'ServerInfo')
+ self.assertEquals(dnsserver.DNSSRV_TYPEID_SERVER_INFO, typeid)
+
+ def test_operation2(self):
+ client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+ rev_zone = '1.168.192.in-addr.arpa'
+
+ zone_create = dnsserver.DNS_RPC_ZONE_CREATE_INFO_LONGHORN()
+ zone_create.pszZoneName = rev_zone
+ zone_create.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
+ zone_create.fAllowUpdate = dnsp.DNS_ZONE_UPDATE_SECURE
+ zone_create.fAging = 0
+ zone_create.dwDpFlags = dnsserver.DNS_DP_DOMAIN_DEFAULT
+
+ # Create zone
+ self.conn.DnssrvOperation2(client_version,
+ 0,
+ self.server,
+ None,
+ 0,
+ 'ZoneCreate',
+ dnsserver.DNSSRV_TYPEID_ZONE_CREATE,
+ zone_create)
+
+ request_filter = (dnsserver.DNS_ZONE_REQUEST_REVERSE |
+ dnsserver.DNS_ZONE_REQUEST_PRIMARY)
+ typeid, zones = self.conn.DnssrvComplexOperation2(client_version,
+ 0,
+ self.server,
+ None,
+ 'EnumZones',
+ dnsserver.DNSSRV_TYPEID_DWORD,
+ request_filter)
+ self.assertEquals(1, zones.dwZoneCount)
+
+ # Delete zone
+ self.conn.DnssrvOperation2(client_version,
+ 0,
+ self.server,
+ rev_zone,
+ 0,
+ 'DeleteZoneFromDs',
+ dnsserver.DNSSRV_TYPEID_NULL,
+ None)
+
+ typeid, zones = self.conn.DnssrvComplexOperation2(client_version,
+ 0,
+ self.server,
+ None,
+ 'EnumZones',
+ dnsserver.DNSSRV_TYPEID_DWORD,
+ request_filter)
+ self.assertEquals(0, zones.dwZoneCount)
+
+
+ def test_complexoperation2(self):
+ client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+ request_filter = (dnsserver.DNS_ZONE_REQUEST_FORWARD |
+ dnsserver.DNS_ZONE_REQUEST_PRIMARY)
+ typeid, zones = self.conn.DnssrvComplexOperation2(client_version,
+ 0,
+ self.server,
+ None,
+ 'EnumZones',
+ dnsserver.DNSSRV_TYPEID_DWORD,
+ request_filter)
+ self.assertEquals(dnsserver.DNSSRV_TYPEID_ZONE_LIST, typeid)
+ self.assertEquals(2, zones.dwZoneCount)
+
+ request_filter = (dnsserver.DNS_ZONE_REQUEST_REVERSE |
+ dnsserver.DNS_ZONE_REQUEST_PRIMARY)
+ typeid, zones = self.conn.DnssrvComplexOperation2(client_version,
+ 0,
+ self.server,
+ None,
+ 'EnumZones',
+ dnsserver.DNSSRV_TYPEID_DWORD,
+ request_filter)
+ self.assertEquals(dnsserver.DNSSRV_TYPEID_ZONE_LIST, typeid)
+ self.assertEquals(0, zones.dwZoneCount)
+
+
+ def test_enumrecords2(self):
+ client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+ record_type = dnsp.DNS_TYPE_NS
+ select_flags = (dnsserver.DNS_RPC_VIEW_ROOT_HINT_DATA |
+ dnsserver.DNS_RPC_VIEW_ADDITIONAL_DATA)
+ buflen, roothints = self.conn.DnssrvEnumRecords2(client_version,
+ 0,
+ self.server,
+ '..RootHints',
+ '.',
+ None,
+ record_type,
+ select_flags,
+ None,
+ None)
+ self.assertEquals(14, roothints.count) # 1 NS + 13 A records (a-m)
+
+
+ def test_updaterecords2(self):
+ client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+ record_type = dnsp.DNS_TYPE_A
+ select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA
+
+ name = 'dummy'
+ rec = ARecord('1.2.3.4')
+ rec2 = ARecord('5.6.7.8')
+
+ # Add record
+ add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+ add_rec_buf.rec = rec
+ self.conn.DnssrvUpdateRecord2(client_version,
+ 0,
+ self.server,
+ self.zone,
+ name,
+ add_rec_buf,
+ None)
+
+ buflen, result = self.conn.DnssrvEnumRecords2(client_version,
+ 0,
+ self.server,
+ self.zone,
+ name,
+ None,
+ record_type,
+ select_flags,
+ None,
+ None)
+ self.assertEquals(1, result.count)
+ self.assertEquals(1, result.rec[0].wRecordCount)
+ self.assertEquals(dnsp.DNS_TYPE_A, result.rec[0].records[0].wType)
+ self.assertEquals('1.2.3.4', result.rec[0].records[0].data)
+
+ # Update record
+ add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+ add_rec_buf.rec = rec2
+ del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+ del_rec_buf.rec = rec
+ self.conn.DnssrvUpdateRecord2(client_version,
+ 0,
+ self.server,
+ self.zone,
+ name,
+ add_rec_buf,
+ del_rec_buf)
+
+ buflen, result = self.conn.DnssrvEnumRecords2(client_version,
+ 0,
+ self.server,
+ self.zone,
+ name,
+ None,
+ record_type,
+ select_flags,
+ None,
+ None)
+ self.assertEquals(1, result.count)
+ self.assertEquals(1, result.rec[0].wRecordCount)
+ self.assertEquals(dnsp.DNS_TYPE_A, result.rec[0].records[0].wType)
+ self.assertEquals('5.6.7.8', result.rec[0].records[0].data)
+
+ # Delete record
+ del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+ del_rec_buf.rec = rec2
+ self.conn.DnssrvUpdateRecord2(client_version,
+ 0,
+ self.server,
+ self.zone,
+ name,
+ None,
+ del_rec_buf)
+
+ self.assertRaises(RuntimeError, self.conn.DnssrvEnumRecords2,
+ client_version,
+ 0,
+ self.server,
+ self.zone,
+ name,
+ None,
+ record_type,
+ select_flags,
+ None,
+ None)
diff --git a/source4/scripting/python/samba/tests/dcerpc/misc.py b/source4/scripting/python/samba/tests/dcerpc/misc.py
index 37a647a842..11e14aadfa 100644
--- a/source4/scripting/python/samba/tests/dcerpc/misc.py
+++ b/source4/scripting/python/samba/tests/dcerpc/misc.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
-#
+#
# 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/>.
#
diff --git a/source4/scripting/python/samba/tests/dcerpc/registry.py b/source4/scripting/python/samba/tests/dcerpc/registry.py
index f0716d5ffb..c7bcbfd530 100644
--- a/source4/scripting/python/samba/tests/dcerpc/registry.py
+++ b/source4/scripting/python/samba/tests/dcerpc/registry.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
-#
+#
# 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/>.
#
@@ -27,15 +25,15 @@ class WinregTests(RpcInterfaceTestCase):
def setUp(self):
super(WinregTests, self).setUp()
- self.conn = winreg.winreg("ncalrpc:", self.get_loadparm(),
+ self.conn = winreg.winreg("ncalrpc:", self.get_loadparm(),
self.get_credentials())
def get_hklm(self):
- return self.conn.OpenHKLM(None,
+ return self.conn.OpenHKLM(None,
winreg.KEY_QUERY_VALUE | winreg.KEY_ENUMERATE_SUB_KEYS)
def test_hklm(self):
- handle = self.conn.OpenHKLM(None,
+ handle = self.conn.OpenHKLM(None,
winreg.KEY_QUERY_VALUE | winreg.KEY_ENUMERATE_SUB_KEYS)
self.conn.CloseKey(handle)
@@ -46,7 +44,7 @@ class WinregTests(RpcInterfaceTestCase):
self.conn.CloseKey(handle)
def test_getkeyinfo(self):
- handle = self.conn.OpenHKLM(None,
+ handle = self.conn.OpenHKLM(None,
winreg.KEY_QUERY_VALUE | winreg.KEY_ENUMERATE_SUB_KEYS)
x = self.conn.QueryInfoKey(handle, winreg.String())
self.assertEquals(9, len(x)) # should return a 9-tuple
diff --git a/source4/scripting/python/samba/tests/dcerpc/rpc_talloc.py b/source4/scripting/python/samba/tests/dcerpc/rpc_talloc.py
index fe0f93ef9b..c091f26c1b 100755..100644
--- a/source4/scripting/python/samba/tests/dcerpc/rpc_talloc.py
+++ b/source4/scripting/python/samba/tests/dcerpc/rpc_talloc.py
@@ -1,6 +1,19 @@
-#!/usr/bin/env python
# test generated python code from pidl
-# Andrew Tridgell August 2010
+# Copyright (C) Andrew Tridgell August 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/>.
+#
#
# to run this test, use one of these:
#
@@ -23,6 +36,7 @@ import talloc
talloc.enable_null_tracking()
+
class TallocTests(samba.tests.TestCase):
'''test talloc behaviour of pidl generated python code'''
@@ -40,7 +54,7 @@ class TallocTests(samba.tests.TestCase):
# we expect one block for the object, and one for the structure
self.check_blocks(partial_attribute_set, 2)
- attids = [ 1, 2, 3]
+ attids = [1, 2, 3]
partial_attribute_set.version = 1
partial_attribute_set.attids = attids
partial_attribute_set.num_attids = len(attids)
diff --git a/source4/scripting/python/samba/tests/dcerpc/rpcecho.py b/source4/scripting/python/samba/tests/dcerpc/rpcecho.py
index ba7ad9688b..099f8f619c 100644
--- a/source4/scripting/python/samba/tests/dcerpc/rpcecho.py
+++ b/source4/scripting/python/samba/tests/dcerpc/rpcecho.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
-#
+#
# 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/>.
#
diff --git a/source4/scripting/python/samba/tests/dcerpc/sam.py b/source4/scripting/python/samba/tests/dcerpc/sam.py
index 38b381d3aa..0e09323adb 100644
--- a/source4/scripting/python/samba/tests/dcerpc/sam.py
+++ b/source4/scripting/python/samba/tests/dcerpc/sam.py
@@ -1,19 +1,18 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
+#
# Unix SMB/CIFS implementation.
# Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
-#
+#
# 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/>.
#
diff --git a/source4/scripting/python/samba/tests/dcerpc/srvsvc.py b/source4/scripting/python/samba/tests/dcerpc/srvsvc.py
new file mode 100644
index 0000000000..3206a27e67
--- /dev/null
+++ b/source4/scripting/python/samba/tests/dcerpc/srvsvc.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+#
+# Unix SMB/CIFS implementation.
+# Copyright © Dhananjay Sathe <dhanajaysathe@gmail.com> 2011
+# Copyright © 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/>.
+#
+
+"""Tests for samba.dcerpc.srvsvc."""
+
+from samba.dcerpc import srvsvc
+from samba.tests import RpcInterfaceTestCase
+
+
+class SrvsvcTests(RpcInterfaceTestCase):
+
+ def setUp(self):
+ super(SrvsvcTests, self).setUp()
+ self.conn = srvsvc.srvsvc("ncalrpc:", self.get_loadparm())
+ self.server_unc = "\\\\."
+
+ def getDummyShareObject(self):
+ share = srvsvc.NetShareInfo2()
+
+ share.name = u'test'
+ share.comment = u'test share'
+ share.type = srvsvc.STYPE_DISKTREE
+ share.current_users = 0x00000000
+ share.max_users = -1
+ share.password = None
+ share.path = u'C:\\tmp' # some random path
+ share.permissions = 123434566
+ return share
+
+ def test_NetShareAdd(self):
+ self.skip("Dangerous test")
+ share = self.getDummyShareObject()
+ self.conn.NetShareAdd(self.server_unc, 2, share, None)
+
+ def test_NetShareSetInfo(self):
+ self.skip("Dangerous test")
+ share = self.getDummyShareObject()
+ parm_error = 0x00000000
+ self.conn.NetShareAdd(self.server_unc, 502, share, parm_error)
+ name = share.name
+ share.comment = "now sucessfully modified "
+ parm_error = self.pipe.NetShareSetInfo(self.server_unc, name,
+ 502, share, parm_error)
+
+ def test_NetShareDel(self):
+ self.skip("Dangerous test")
+ share = self.getDummyShareObject()
+ parm_error = 0x00000000
+ self.expectFailure("NetShareAdd doesn't work properly from Python",
+ self.conn.NetShareAdd, self.server_unc, 502, share, parm_error)
+ self.conn.NetShareDel(self.server_unc, share.name, 0)
diff --git a/source4/scripting/python/samba/tests/dcerpc/testrpc.py b/source4/scripting/python/samba/tests/dcerpc/testrpc.py
index 2f3a6a0aef..e35d6b5544 100644
--- a/source4/scripting/python/samba/tests/dcerpc/testrpc.py
+++ b/source4/scripting/python/samba/tests/dcerpc/testrpc.py
@@ -1,7 +1,18 @@
-#!/usr/bin/env python
-#
# test generated python code from pidl
-# Andrew Tridgell August 2010
+# Copyright (C) Andrew Tridgell August 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 sys
@@ -48,7 +59,7 @@ class RpcTests(object):
print "ERROR: Failed to instantiate %s.%s" % (typename, n)
self.errcount += 1
continue
- except:
+ except Exception:
print "ERROR: Failed to instantiate %s.%s" % (typename, n)
self.errcount += 1
continue
diff --git a/source4/scripting/python/samba/tests/dcerpc/unix.py b/source4/scripting/python/samba/tests/dcerpc/unix.py
index 16bf37e749..e8ef4da863 100644
--- a/source4/scripting/python/samba/tests/dcerpc/unix.py
+++ b/source4/scripting/python/samba/tests/dcerpc/unix.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
-#
+#
# 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/>.
#
@@ -46,6 +44,6 @@ class UnixinfoTests(RpcInterfaceTestCase):
def test_uidtosid(self):
self.conn.UidToSid(1000)
-
+
def test_uidtosid_fail(self):
self.assertRaises(TypeError, self.conn.UidToSid, "100")
diff --git a/source4/scripting/python/samba/tests/dns.py b/source4/scripting/python/samba/tests/dns.py
new file mode 100644
index 0000000000..49d699edb7
--- /dev/null
+++ b/source4/scripting/python/samba/tests/dns.py
@@ -0,0 +1,622 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Kai Blin <kai@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/>.
+#
+
+import os
+import struct
+import random
+from samba import socket
+import samba.ndr as ndr
+import samba.dcerpc.dns as dns
+from samba.tests import TestCase
+
+class DNSTest(TestCase):
+
+ def errstr(self, errcode):
+ "Return a readable error code"
+ string_codes = [
+ "OK",
+ "FORMERR",
+ "SERVFAIL",
+ "NXDOMAIN",
+ "NOTIMP",
+ "REFUSED",
+ "YXDOMAIN",
+ "YXRRSET",
+ "NXRRSET",
+ "NOTAUTH",
+ "NOTZONE",
+ ]
+
+ return string_codes[errcode]
+
+
+ def assert_dns_rcode_equals(self, packet, rcode):
+ "Helper function to check return code"
+ p_errcode = packet.operation & 0x000F
+ self.assertEquals(p_errcode, rcode, "Expected RCODE %s, got %s" %
+ (self.errstr(rcode), self.errstr(p_errcode)))
+
+ def assert_dns_opcode_equals(self, packet, opcode):
+ "Helper function to check opcode"
+ p_opcode = packet.operation & 0x7800
+ self.assertEquals(p_opcode, opcode, "Expected OPCODE %s, got %s" %
+ (opcode, p_opcode))
+
+ def make_name_packet(self, opcode, qid=None):
+ "Helper creating a dns.name_packet"
+ p = dns.name_packet()
+ if qid is None:
+ p.id = random.randint(0x0, 0xffff)
+ p.operation = opcode
+ p.questions = []
+ return p
+
+ def finish_name_packet(self, packet, questions):
+ "Helper to finalize a dns.name_packet"
+ packet.qdcount = len(questions)
+ packet.questions = questions
+
+ def make_name_question(self, name, qtype, qclass):
+ "Helper creating a dns.name_question"
+ q = dns.name_question()
+ q.name = name
+ q.question_type = qtype
+ q.question_class = qclass
+ return q
+
+ def get_dns_domain(self):
+ "Helper to get dns domain"
+ return os.getenv('REALM', 'example.com').lower()
+
+ def dns_transaction_udp(self, packet, host=os.getenv('SERVER_IP')):
+ "send a DNS query and read the reply"
+ s = None
+ try:
+ send_packet = ndr.ndr_pack(packet)
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
+ s.connect((host, 53))
+ s.send(send_packet, 0)
+ recv_packet = s.recv(2048, 0)
+ return ndr.ndr_unpack(dns.name_packet, recv_packet)
+ finally:
+ if s is not None:
+ s.close()
+
+ def dns_transaction_tcp(self, packet, host=os.getenv('SERVER_IP')):
+ "send a DNS query and read the reply"
+ s = None
+ try:
+ send_packet = ndr.ndr_pack(packet)
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ s.connect((host, 53))
+ tcp_packet = struct.pack('!H', len(send_packet))
+ tcp_packet += send_packet
+ s.send(tcp_packet, 0)
+ recv_packet = s.recv(0xffff + 2, 0)
+ return ndr.ndr_unpack(dns.name_packet, recv_packet[2:])
+ finally:
+ if s is not None:
+ s.close()
+
+
+class TestSimpleQueries(DNSTest):
+
+ def test_one_a_query(self):
+ "create a query packet containing one query record"
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+ print "asking for ", q.name
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+ self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
+ self.assertEquals(response.ancount, 1)
+ self.assertEquals(response.answers[0].rdata,
+ os.getenv('SERVER_IP'))
+
+ def test_one_a_query_tcp(self):
+ "create a query packet containing one query record via TCP"
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+ print "asking for ", q.name
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_tcp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+ self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
+ self.assertEquals(response.ancount, 1)
+ self.assertEquals(response.answers[0].rdata,
+ os.getenv('SERVER_IP'))
+
+ def test_two_queries(self):
+ "create a query packet containing two query records"
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+ questions.append(q)
+
+ name = "%s.%s" % ('bogusname', self.get_dns_domain())
+ q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
+
+ def test_qtype_all_query(self):
+ "create a QTYPE_ALL query"
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ q = self.make_name_question(name, dns.DNS_QTYPE_ALL, dns.DNS_QCLASS_IN)
+ print "asking for ", q.name
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+
+ num_answers = 1
+ dc_ipv6 = os.getenv('SERVER_IPV6')
+ if dc_ipv6 is not None:
+ num_answers += 1
+
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+ self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
+ self.assertEquals(response.ancount, num_answers)
+ self.assertEquals(response.answers[0].rdata,
+ os.getenv('SERVER_IP'))
+ if dc_ipv6 is not None:
+ self.assertEquals(response.answers[1].rdata, dc_ipv6)
+
+ def test_qclass_none_query(self):
+ "create a QCLASS_NONE query"
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ q = self.make_name_question(name, dns.DNS_QTYPE_ALL, dns.DNS_QCLASS_NONE)
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NOTIMP)
+
+# Only returns an authority section entry in BIND and Win DNS
+# FIXME: Enable one Samba implements this feature
+# def test_soa_hostname_query(self):
+# "create a SOA query for a hostname"
+# p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+# questions = []
+#
+# name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+# q = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+# questions.append(q)
+#
+# self.finish_name_packet(p, questions)
+# response = self.dns_transaction_udp(p)
+# self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+# self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
+# # We don't get SOA records for single hosts
+# self.assertEquals(response.ancount, 0)
+
+ def test_soa_domain_query(self):
+ "create a SOA query for a domain"
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = self.get_dns_domain()
+ q = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+ self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
+ self.assertEquals(response.ancount, 1)
+
+
+class TestDNSUpdates(DNSTest):
+
+ def test_two_updates(self):
+ "create two update requests"
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ u = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+ updates.append(u)
+
+ name = self.get_dns_domain()
+ u = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+ updates.append(u)
+
+ self.finish_name_packet(p, updates)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
+
+ def test_update_wrong_qclass(self):
+ "create update with DNS_QCLASS_NONE"
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+ u = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_NONE)
+ updates.append(u)
+
+ self.finish_name_packet(p, updates)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NOTIMP)
+
+ def test_update_prereq_with_non_null_ttl(self):
+ "test update with a non-null TTL"
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+
+ u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ updates.append(u)
+ self.finish_name_packet(p, updates)
+
+ prereqs = []
+ r = dns.res_rec()
+ r.name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ r.rr_type = dns.DNS_QTYPE_TXT
+ r.rr_class = dns.DNS_QCLASS_NONE
+ r.ttl = 1
+ r.length = 0
+ prereqs.append(r)
+
+ p.ancount = len(prereqs)
+ p.answers = prereqs
+
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
+
+# I'd love to test this one, but it segfaults. :)
+# def test_update_prereq_with_non_null_length(self):
+# "test update with a non-null length"
+# p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+# updates = []
+#
+# name = self.get_dns_domain()
+#
+# u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+# updates.append(u)
+# self.finish_name_packet(p, updates)
+#
+# prereqs = []
+# r = dns.res_rec()
+# r.name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+# r.rr_type = dns.DNS_QTYPE_TXT
+# r.rr_class = dns.DNS_QCLASS_ANY
+# r.ttl = 0
+# r.length = 1
+# prereqs.append(r)
+#
+# p.ancount = len(prereqs)
+# p.answers = prereqs
+#
+# response = self.dns_transaction_udp(p)
+# self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
+
+ def test_update_prereq_nonexisting_name(self):
+ "test update with a nonexisting name"
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+
+ u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ updates.append(u)
+ self.finish_name_packet(p, updates)
+
+ prereqs = []
+ r = dns.res_rec()
+ r.name = "idontexist.%s" % self.get_dns_domain()
+ r.rr_type = dns.DNS_QTYPE_TXT
+ r.rr_class = dns.DNS_QCLASS_ANY
+ r.ttl = 0
+ r.length = 0
+ prereqs.append(r)
+
+ p.ancount = len(prereqs)
+ p.answers = prereqs
+
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXRRSET)
+
+ def test_update_add_txt_record(self):
+ "test adding records works"
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+
+ u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ updates.append(u)
+ self.finish_name_packet(p, updates)
+
+ updates = []
+ r = dns.res_rec()
+ r.name = "textrec.%s" % self.get_dns_domain()
+ r.rr_type = dns.DNS_QTYPE_TXT
+ r.rr_class = dns.DNS_QCLASS_IN
+ r.ttl = 900
+ r.length = 0xffff
+ r.rdata = dns.txt_record()
+ r.rdata.txt = '"This is a test"'
+ updates.append(r)
+ p.nscount = len(updates)
+ p.nsrecs = updates
+
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "textrec.%s" % self.get_dns_domain()
+ q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+ self.assertEquals(response.ancount, 1)
+ self.assertEquals(response.answers[0].rdata.txt, '"This is a test"')
+
+ def test_update_add_two_txt_records(self):
+ "test adding two txt records works"
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+
+ u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ updates.append(u)
+ self.finish_name_packet(p, updates)
+
+ updates = []
+ r = dns.res_rec()
+ r.name = "textrec2.%s" % self.get_dns_domain()
+ r.rr_type = dns.DNS_QTYPE_TXT
+ r.rr_class = dns.DNS_QCLASS_IN
+ r.ttl = 900
+ r.length = 0xffff
+ r.rdata = dns.txt_record()
+ r.rdata.txt = '"This is a test" "and this is a test, too"'
+ updates.append(r)
+ p.nscount = len(updates)
+ p.nsrecs = updates
+
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "textrec2.%s" % self.get_dns_domain()
+ q = self.make_name_question(name, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+ self.assertEquals(response.ancount, 1)
+ self.assertEquals(response.answers[0].rdata.txt, '"This is a test" "and this is a test, too"')
+
+ def test_delete_record(self):
+ "Test if deleting records works"
+
+ NAME = "deleterec.%s" % self.get_dns_domain()
+
+ # First, create a record to make sure we have a record to delete.
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+
+ u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ updates.append(u)
+ self.finish_name_packet(p, updates)
+
+ updates = []
+ r = dns.res_rec()
+ r.name = NAME
+ r.rr_type = dns.DNS_QTYPE_TXT
+ r.rr_class = dns.DNS_QCLASS_IN
+ r.ttl = 900
+ r.length = 0xffff
+ r.rdata = dns.txt_record()
+ r.rdata.txt = '"This is a test"'
+ updates.append(r)
+ p.nscount = len(updates)
+ p.nsrecs = updates
+
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+
+ # Now check the record is around
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+ q = self.make_name_question(NAME, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+
+ # Now delete the record
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+
+ u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ updates.append(u)
+ self.finish_name_packet(p, updates)
+
+ updates = []
+ r = dns.res_rec()
+ r.name = NAME
+ r.rr_type = dns.DNS_QTYPE_TXT
+ r.rr_class = dns.DNS_QCLASS_NONE
+ r.ttl = 0
+ r.length = 0xffff
+ r.rdata = dns.txt_record()
+ r.rdata.txt = '"This is a test"'
+ updates.append(r)
+ p.nscount = len(updates)
+ p.nsrecs = updates
+
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+
+ # And finally check it's gone
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ q = self.make_name_question(NAME, dns.DNS_QTYPE_TXT, dns.DNS_QCLASS_IN)
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXDOMAIN)
+
+
+class TestComplexQueries(DNSTest):
+
+ def setUp(self):
+ super(TestComplexQueries, self).setUp()
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+
+ u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ updates.append(u)
+ self.finish_name_packet(p, updates)
+
+ updates = []
+ r = dns.res_rec()
+ r.name = "cname_test.%s" % self.get_dns_domain()
+ r.rr_type = dns.DNS_QTYPE_CNAME
+ r.rr_class = dns.DNS_QCLASS_IN
+ r.ttl = 900
+ r.length = 0xffff
+ r.rdata = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ updates.append(r)
+ p.nscount = len(updates)
+ p.nsrecs = updates
+
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+
+ def tearDown(self):
+ super(TestComplexQueries, self).tearDown()
+ p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
+ updates = []
+
+ name = self.get_dns_domain()
+
+ u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+ updates.append(u)
+ self.finish_name_packet(p, updates)
+
+ updates = []
+ r = dns.res_rec()
+ r.name = "cname_test.%s" % self.get_dns_domain()
+ r.rr_type = dns.DNS_QTYPE_CNAME
+ r.rr_class = dns.DNS_QCLASS_NONE
+ r.ttl = 0
+ r.length = 0xffff
+ r.rdata = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ updates.append(r)
+ p.nscount = len(updates)
+ p.nsrecs = updates
+
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+
+ def test_one_a_query(self):
+ "create a query packet containing one query record"
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "cname_test.%s" % self.get_dns_domain()
+ q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+ print "asking for ", q.name
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+ self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
+ self.assertEquals(response.ancount, 2)
+ self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_CNAME)
+ self.assertEquals(response.answers[0].rdata, "%s.%s" %
+ (os.getenv('SERVER'), self.get_dns_domain()))
+ self.assertEquals(response.answers[1].rr_type, dns.DNS_QTYPE_A)
+ self.assertEquals(response.answers[1].rdata,
+ os.getenv('SERVER_IP'))
+
+class TestInvalidQueries(DNSTest):
+
+ def test_one_a_query(self):
+ "send 0 bytes follows by create a query packet containing one query record"
+
+ s = None
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
+ s.connect((os.getenv('SERVER_IP'), 53))
+ s.send("", 0)
+ finally:
+ if s is not None:
+ s.close()
+
+ p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+ questions = []
+
+ name = "%s.%s" % (os.getenv('SERVER'), self.get_dns_domain())
+ q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+ print "asking for ", q.name
+ questions.append(q)
+
+ self.finish_name_packet(p, questions)
+ response = self.dns_transaction_udp(p)
+ self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
+ self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
+ self.assertEquals(response.ancount, 1)
+ self.assertEquals(response.answers[0].rdata,
+ os.getenv('SERVER_IP'))
+
+if __name__ == "__main__":
+ import unittest
+ unittest.main()
diff --git a/source4/scripting/python/samba/tests/docs.py b/source4/scripting/python/samba/tests/docs.py
new file mode 100644
index 0000000000..c1b371680d
--- /dev/null
+++ b/source4/scripting/python/samba/tests/docs.py
@@ -0,0 +1,127 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2012
+#
+# Tests for documentation.
+#
+# 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/>.
+#
+
+"""Tests for presence of documentation."""
+
+import samba
+import samba.tests
+from samba.tests import TestSkipped
+
+import errno
+import os
+import re
+import subprocess
+
+
+class TestCase(samba.tests.TestCase):
+
+ def _format_message(self, parameters, message):
+ parameters = list(parameters)
+ parameters.sort()
+ return message + '\n\n %s' % ('\n '.join(parameters))
+
+
+class NoXsltProc(Exception):
+
+ def __init__(self):
+ Exception.__init__(self, "'xsltproc' is not installed")
+
+
+def get_documented_parameters(sourcedir):
+ path = os.path.join(sourcedir, "bin", "default", "docs-xml", "smbdotconf")
+ if not os.path.exists(os.path.join(path, "parameters.all.xml")):
+ raise Exception("Unable to find parameters.all.xml")
+ try:
+ p = subprocess.Popen(
+ ["xsltproc", "--xinclude", "--param", "smb.context", "ALL", os.path.join(sourcedir, "docs-xml", "smbdotconf", "generate-context.xsl"), "parameters.all.xml"],
+ stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
+ cwd=path)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ raise NoXsltProc()
+ raise
+ out, err = p.communicate()
+ assert p.returncode == 0, "returncode was %r" % p.returncode
+ for l in out.splitlines():
+ m = re.match('<samba:parameter .*?name="([^"]*?)"', l)
+ if "removed=\"1\"" in l:
+ continue
+ if m:
+ name = m.group(1)
+ yield name
+ m = re.match('.*<synonym>(.*)</synonym>.*', l)
+ if m:
+ name = m.group(1)
+ yield name
+
+
+def get_implementation_parameters(sourcedir):
+ # Reading entries from source code
+ f = open(os.path.join(sourcedir, "lib/param/param_table.c"), "r")
+ try:
+ # burn through the preceding lines
+ while True:
+ l = f.readline()
+ if l.startswith("static struct parm_struct parm_table"):
+ break
+
+ for l in f.readlines():
+ if re.match("^\s*\}\;\s*$", l):
+ break
+ # pull in the param names only
+ if re.match(".*P_SEPARATOR.*", l):
+ continue
+ m = re.match("\s*\.label\s*=\s*\"(.*)\".*", l)
+ if not m:
+ continue
+
+ name = m.group(1)
+ yield name
+ finally:
+ f.close()
+
+
+class SmbDotConfTests(TestCase):
+
+ def test_unknown(self):
+ topdir = samba.source_tree_topdir()
+ try:
+ documented = set(get_documented_parameters(topdir))
+ except NoXsltProc:
+ raise TestSkipped("'xsltproc' is missing, unable to load parameters")
+ parameters = set(get_implementation_parameters(topdir))
+ # Filter out parametric options, since we can't find them in the parm
+ # table
+ documented = set([p for p in documented if not ":" in p])
+ unknown = documented.difference(parameters)
+ if len(unknown) > 0:
+ self.fail(self._format_message(unknown,
+ "Parameters that are documented but not in the implementation:"))
+
+ def test_undocumented(self):
+ topdir = samba.source_tree_topdir()
+ try:
+ documented = set(get_documented_parameters(topdir))
+ except NoXsltProc:
+ raise TestSkipped("'xsltproc' is missing, unable to load parameters")
+ parameters = set(get_implementation_parameters(topdir))
+ undocumented = parameters.difference(documented)
+ if len(undocumented) > 0:
+ self.fail(self._format_message(undocumented,
+ "Parameters that are in the implementation but undocumented:"))
diff --git a/source4/scripting/python/samba/tests/dsdb.py b/source4/scripting/python/samba/tests/dsdb.py
index d4331f3ef5..3aef1d2fa4 100644
--- a/source4/scripting/python/samba/tests/dsdb.py
+++ b/source4/scripting/python/samba/tests/dsdb.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation. Tests for dsdb
# Copyright (C) Matthieu Patou <mat@matws.net> 2010
#
@@ -22,7 +20,7 @@
from samba.credentials import Credentials
from samba.samdb import SamDB
from samba.auth import system_session
-from testtools.testcase import TestCase
+from samba.tests import TestCase
from samba.ndr import ndr_unpack, ndr_pack
from samba.dcerpc import drsblobs
import ldb
@@ -32,7 +30,6 @@ import samba
class DsdbTests(TestCase):
-
def setUp(self):
super(DsdbTests, self).setUp()
self.lp = samba.param.LoadParm()
@@ -43,11 +40,9 @@ class DsdbTests(TestCase):
self.samdb = SamDB(os.path.join(self.baseprovpath(), "private", "sam.ldb"),
session_info=self.session, credentials=self.creds,lp=self.lp)
-
def baseprovpath(self):
return os.path.join(os.environ['SELFTEST_PREFIX'], "dc")
-
def test_get_oid_from_attrid(self):
oid = self.samdb.get_oid_from_attid(591614)
self.assertEquals(oid, "1.2.840.113556.1.4.1790")
diff --git a/source4/scripting/python/samba/tests/gensec.py b/source4/scripting/python/samba/tests/gensec.py
index ddca0df980..e270c418ea 100644
--- a/source4/scripting/python/samba/tests/gensec.py
+++ b/source4/scripting/python/samba/tests/gensec.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009
#
@@ -19,12 +17,12 @@
"""Tests for GENSEC.
-Note that this just tests the bindings work. It does not intend to test
+Note that this just tests the bindings work. It does not intend to test
the functionality, that's already done in other tests.
"""
from samba.credentials import Credentials
-from samba import gensec
+from samba import gensec, auth
import samba.tests
class GensecTests(samba.tests.TestCase):
@@ -56,7 +54,8 @@ class GensecTests(samba.tests.TestCase):
self.gensec_client.want_feature(gensec.FEATURE_SEAL)
self.gensec_client.start_mech_by_sasl_name("GSSAPI")
- self.gensec_server = gensec.Security.start_server(self.settings)
+ self.gensec_server = gensec.Security.start_server(settings=self.settings,
+ auth_context=auth.AuthContext(lp_ctx=self.lp_ctx))
creds = Credentials()
creds.guess(self.lp_ctx)
creds.set_machine_account(self.lp_ctx)
@@ -68,9 +67,9 @@ class GensecTests(samba.tests.TestCase):
client_finished = False
server_finished = False
server_to_client = ""
-
+
"""Run the actual call loop"""
- while client_finished == False and server_finished == False:
+ while not client_finished and not server_finished:
if not client_finished:
print "running client gensec_update"
(client_finished, client_to_server) = self.gensec_client.update(server_to_client)
@@ -87,4 +86,61 @@ class GensecTests(samba.tests.TestCase):
test_wrapped = self.gensec_server.wrap(test_string)
test_unwrapped = self.gensec_client.unwrap(test_wrapped)
self.assertEqual(test_string, test_unwrapped)
-
+
+ client_session_key = self.gensec_client.session_key()
+ server_session_key = self.gensec_server.session_key()
+ self.assertEqual(client_session_key, server_session_key)
+
+ def test_max_update_size(self):
+ """Test GENSEC by doing an exchange with ourselves using GSSAPI against a KDC"""
+
+ """Start up a client and server GENSEC instance to test things with"""
+
+ self.gensec_client = gensec.Security.start_client(self.settings)
+ self.gensec_client.set_credentials(self.get_credentials())
+ self.gensec_client.want_feature(gensec.FEATURE_SIGN)
+ self.gensec_client.set_max_update_size(5)
+ self.gensec_client.start_mech_by_name("spnego")
+
+ self.gensec_server = gensec.Security.start_server(settings=self.settings,
+ auth_context=auth.AuthContext(lp_ctx=self.lp_ctx))
+ creds = Credentials()
+ creds.guess(self.lp_ctx)
+ creds.set_machine_account(self.lp_ctx)
+ self.gensec_server.set_credentials(creds)
+ self.gensec_server.want_feature(gensec.FEATURE_SIGN)
+ self.gensec_server.set_max_update_size(5)
+ self.gensec_server.start_mech_by_name("spnego")
+
+ client_finished = False
+ server_finished = False
+ server_to_client = ""
+
+ """Run the actual call loop"""
+ i = 0
+ while not client_finished or not server_finished:
+ i += 1
+ if not client_finished:
+ print "running client gensec_update: %d: %r" % (len(server_to_client), server_to_client)
+ (client_finished, client_to_server) = self.gensec_client.update(server_to_client)
+ if not server_finished:
+ print "running server gensec_update: %d: %r" % (len(client_to_server), client_to_server)
+ (server_finished, server_to_client) = self.gensec_server.update(client_to_server)
+
+ """Here we expect a lot more than the typical 1 or 2 roundtrips"""
+ self.assertTrue(i > 10)
+
+ session_info = self.gensec_server.session_info()
+
+ test_string = "Hello Server"
+ test_wrapped = self.gensec_client.wrap(test_string)
+ test_unwrapped = self.gensec_server.unwrap(test_wrapped)
+ self.assertEqual(test_string, test_unwrapped)
+ test_string = "Hello Client"
+ test_wrapped = self.gensec_server.wrap(test_string)
+ test_unwrapped = self.gensec_client.unwrap(test_wrapped)
+ self.assertEqual(test_string, test_unwrapped)
+
+ client_session_key = self.gensec_client.session_key()
+ server_session_key = self.gensec_server.session_key()
+ self.assertEqual(client_session_key, server_session_key)
diff --git a/source4/scripting/python/samba/tests/getopt.py b/source4/scripting/python/samba/tests/getopt.py
new file mode 100644
index 0000000000..14ee0a7428
--- /dev/null
+++ b/source4/scripting/python/samba/tests/getopt.py
@@ -0,0 +1,55 @@
+# 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/>.
+#
+
+"""Tests for option parsing.
+
+"""
+
+import optparse
+from samba.getopt import (
+ AUTO_USE_KERBEROS,
+ DONT_USE_KERBEROS,
+ MUST_USE_KERBEROS,
+ parse_kerberos_arg,
+ )
+import samba.tests
+
+class KerberosOptionTests(samba.tests.TestCase):
+
+ def test_parse_true(self):
+ self.assertEquals(
+ MUST_USE_KERBEROS, parse_kerberos_arg("yes", "--kerberos"))
+ self.assertEquals(
+ MUST_USE_KERBEROS, parse_kerberos_arg("true", "--kerberos"))
+ self.assertEquals(
+ MUST_USE_KERBEROS, parse_kerberos_arg("1", "--kerberos"))
+
+ def test_parse_false(self):
+ self.assertEquals(
+ DONT_USE_KERBEROS, parse_kerberos_arg("no", "--kerberos"))
+ self.assertEquals(
+ DONT_USE_KERBEROS, parse_kerberos_arg("false", "--kerberos"))
+ self.assertEquals(
+ DONT_USE_KERBEROS, parse_kerberos_arg("0", "--kerberos"))
+
+ def test_parse_auto(self):
+ self.assertEquals(
+ AUTO_USE_KERBEROS, parse_kerberos_arg("auto", "--kerberos"))
+
+ def test_parse_invalid(self):
+ self.assertRaises(optparse.OptionValueError,
+ parse_kerberos_arg, "blah?", "--kerberos")
diff --git a/source4/scripting/python/samba/tests/hostconfig.py b/source4/scripting/python/samba/tests/hostconfig.py
index 78ca6202b2..526dc0fe4e 100644
--- a/source4/scripting/python/samba/tests/hostconfig.py
+++ b/source4/scripting/python/samba/tests/hostconfig.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation. Tests for shares
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 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/>.
#
@@ -59,13 +57,15 @@ class ShareTests(TestCase):
def test_iter(self):
self.assertEquals([], list(self._get_shares({})))
self.assertEquals([], list(self._get_shares({"global":{}})))
- self.assertEquals(["bla"], list(self._get_shares({"global":{}, "bla":{}})))
+ self.assertEquals(
+ ["bla"],
+ list(self._get_shares({"global":{}, "bla":{}})))
def test_len(self):
shares = self._get_shares({"global": {}})
self.assertEquals(0, len(shares))
- def test_getitem_nonexistant(self):
+ def test_getitem_nonexistent(self):
shares = self._get_shares({"global": {}})
self.assertRaises(KeyError, shares.__getitem__, "bla")
diff --git a/source4/scripting/python/samba/tests/libsmb_samba_internal.py b/source4/scripting/python/samba/tests/libsmb_samba_internal.py
new file mode 100644
index 0000000000..fe9f197a2c
--- /dev/null
+++ b/source4/scripting/python/samba/tests/libsmb_samba_internal.py
@@ -0,0 +1,78 @@
+# Unix SMB/CIFS implementation.
+# Copyright Volker Lendecke <vl@samba.org> 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/>.
+#
+
+"""Tests for samba.samba3.libsmb_samba_internal."""
+
+from samba.samba3 import libsmb_samba_internal
+from samba.dcerpc import security
+from samba.samba3 import param as s3param
+from samba import credentials
+import samba.tests
+import threading
+import sys
+import os
+
+class LibsmbTestCase(samba.tests.TestCase):
+
+ class OpenClose(threading.Thread):
+
+ def __init__(self, conn, filename, num_ops):
+ threading.Thread.__init__(self)
+ self.conn = conn
+ self.filename = filename
+ self.num_ops = num_ops
+ self.exc = False
+
+ def run(self):
+ c = self.conn
+ try:
+ for i in range(self.num_ops):
+ f = c.create(self.filename, CreateDisposition=3,
+ DesiredAccess=security.SEC_STD_DELETE)
+ c.delete_on_close(f, True)
+ c.close(f)
+ except Exception:
+ self.exc = sys.exc_info()
+
+ def test_OpenClose(self):
+
+ lp = s3param.get_context()
+ lp.load(os.getenv("SMB_CONF_PATH"))
+
+ creds = credentials.Credentials()
+ creds.set_username(os.getenv("USERNAME"))
+ creds.set_password(os.getenv("PASSWORD"))
+
+ c = libsmb_samba_internal.Conn(os.getenv("SERVER_IP"), "tmp", creds)
+
+ mythreads = []
+
+ for i in range(3):
+ t = LibsmbTestCase.OpenClose(c, "test" + str(i), 10)
+ mythreads.append(t)
+
+ for t in mythreads:
+ t.start()
+
+ for t in mythreads:
+ t.join()
+ if t.exc:
+ raise t.exc[0](t.exc[1])
+
+if __name__ == "__main__":
+ import unittest
+ unittest.main()
diff --git a/source4/scripting/python/samba/tests/messaging.py b/source4/scripting/python/samba/tests/messaging.py
index d2a0b73775..f0cd368195 100644
--- a/source4/scripting/python/samba/tests/messaging.py
+++ b/source4/scripting/python/samba/tests/messaging.py
@@ -1,19 +1,18 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
+#
# Unix SMB/CIFS implementation.
# Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
-#
+#
# 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/>.
#
@@ -22,13 +21,13 @@
from samba.messaging import Messaging
from samba.tests import TestCase
+from samba.dcerpc.server_id import server_id
class MessagingTests(TestCase):
def get_context(self, *args, **kwargs):
- kwargs["messaging_path"] = "."
return Messaging(*args, **kwargs)
-
+
def test_register(self):
x = self.get_context()
def callback():
@@ -36,10 +35,18 @@ class MessagingTests(TestCase):
msg_type = x.register(callback)
x.deregister(callback, msg_type)
+ def test_all_servers(self):
+ x = self.get_context()
+ self.assertTrue(isinstance(x.irpc_all_servers(), list))
+
+ def test_by_name(self):
+ x = self.get_context()
+ for name in x.irpc_all_servers():
+ self.assertTrue(isinstance(x.irpc_servers_byname(name.name), list))
+
def test_assign_server_id(self):
x = self.get_context()
- self.assertTrue(isinstance(x.server_id, tuple))
- self.assertEquals(3, len(x.server_id))
+ self.assertTrue(isinstance(x.server_id, server_id))
def test_ping_speed(self):
server_ctx = self.get_context((0, 1))
diff --git a/source4/scripting/python/samba/tests/netcmd.py b/source4/scripting/python/samba/tests/netcmd.py
index 787bcd5a72..2cbac4e8bf 100644
--- a/source4/scripting/python/samba/tests/netcmd.py
+++ b/source4/scripting/python/samba/tests/netcmd.py
@@ -1,7 +1,5 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
-# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009-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
@@ -19,17 +17,74 @@
"""Tests for samba.netcmd."""
+from cStringIO import StringIO
from samba.netcmd import Command
+from samba.netcmd.testparm import cmd_testparm
+from samba.netcmd.main import cmd_sambatool
import samba.tests
-class CommandTests(samba.tests.TestCase):
+class NetCmdTestCase(samba.tests.TestCase):
+
+ def run_netcmd(self, cmd_klass, args, retcode=0):
+ cmd = cmd_klass(outf=StringIO(), errf=StringIO())
+ try:
+ retval = cmd._run(cmd_klass.__name__, *args)
+ except Exception, e:
+ cmd.show_command_error(e)
+ retval = 1
+ self.assertEquals(retcode, retval)
+ return cmd.outf.getvalue(), cmd.errf.getvalue()
+
+ def iter_all_subcommands(self):
+ todo = []
+ todo.extend(cmd_sambatool.subcommands.items())
+ while todo:
+ (path, cmd) = todo.pop()
+ yield path, cmd
+ subcmds = getattr(cmd, "subcommands", {})
+ todo.extend([(path + " " + k, v) for (k, v) in
+ subcmds.iteritems()])
+
+
+class TestParmTests(NetCmdTestCase):
+
+ def test_no_client_ip(self):
+ out, err = self.run_netcmd(cmd_testparm, ["--client-name=foo"],
+ retcode=-1)
+ self.assertEquals("", out)
+ self.assertEquals(
+ "ERROR: Both a DNS name and an IP address are "
+ "required for the host access check\n", err)
+
+
+class CommandTests(NetCmdTestCase):
+
+ def test_description(self):
+ class cmd_foo(Command):
+ """Mydescription"""
+ self.assertEquals("Mydescription", cmd_foo().short_description)
def test_name(self):
class cmd_foo(Command):
pass
self.assertEquals("foo", cmd_foo().name)
- def test_description(self):
- class cmd_foo(Command):
- """Mydescription"""
- self.assertEquals("Mydescription", cmd_foo().description)
+ def test_synopsis_everywhere(self):
+ missing = []
+ for path, cmd in self.iter_all_subcommands():
+ if cmd.synopsis is None:
+ missing.append(path)
+ if missing:
+ self.fail("The following commands do not have a synopsis set: %r" %
+ missing)
+
+ def test_short_description_everywhere(self):
+ missing = []
+ for path, cmd in self.iter_all_subcommands():
+ if cmd.short_description is None:
+ missing.append(path)
+ if not missing:
+ return
+ self.fail(
+ "The following commands do not have a short description set: %r" %
+ missing)
diff --git a/source4/scripting/python/samba/tests/ntacls.py b/source4/scripting/python/samba/tests/ntacls.py
index 2d8d6b9d67..aa9ef6852b 100644
--- a/source4/scripting/python/samba/tests/ntacls.py
+++ b/source4/scripting/python/samba/tests/ntacls.py
@@ -1,7 +1,6 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation. Tests for ntacls manipulation
# Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
+# Copyright (C) Andrew Bartlett 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
@@ -22,80 +21,63 @@
from samba.ntacls import setntacl, getntacl, XattrBackendError
from samba.dcerpc import xattr, security
from samba.param import LoadParm
-from samba.tests import TestCase, TestSkipped
+from samba.tests import TestCaseInTempDir, TestSkipped
import random
import os
-class NtaclsTests(TestCase):
+class NtaclsTests(TestCaseInTempDir):
def test_setntacl(self):
- random.seed()
lp = LoadParm()
- path = os.environ['SELFTEST_PREFIX']
acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
- tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
- ntacl = xattr.NTACL()
- ntacl.version = 1
- open(tempf, 'w').write("empty")
- lp.set("posix:eadb",os.path.join(path,"eadbtest.tdb"))
- setntacl(lp, tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467")
- os.unlink(tempf)
+ open(self.tempf, 'w').write("empty")
+ lp.set("posix:eadb",os.path.join(self.tempdir,"eadbtest.tdb"))
+ setntacl(lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467")
+ os.unlink(os.path.join(self.tempdir,"eadbtest.tdb"))
def test_setntacl_getntacl(self):
- random.seed()
lp = LoadParm()
- path = None
- path = os.environ['SELFTEST_PREFIX']
acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
- tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
- ntacl = xattr.NTACL()
- ntacl.version = 1
- open(tempf, 'w').write("empty")
- lp.set("posix:eadb",os.path.join(path,"eadbtest.tdb"))
- setntacl(lp,tempf,acl,"S-1-5-21-2212615479-2695158682-2101375467")
- facl = getntacl(lp,tempf)
+ open(self.tempf, 'w').write("empty")
+ lp.set("posix:eadb",os.path.join(self.tempdir,"eadbtest.tdb"))
+ setntacl(lp,self.tempf,acl,"S-1-5-21-2212615479-2695158682-2101375467")
+ facl = getntacl(lp,self.tempf)
anysid = security.dom_sid(security.SID_NT_SELF)
- self.assertEquals(facl.info.as_sddl(anysid),acl)
- os.unlink(tempf)
+ self.assertEquals(facl.as_sddl(anysid),acl)
+ os.unlink(os.path.join(self.tempdir,"eadbtest.tdb"))
def test_setntacl_getntacl_param(self):
- random.seed()
lp = LoadParm()
acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
- path = os.environ['SELFTEST_PREFIX']
- tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
- ntacl = xattr.NTACL()
- ntacl.version = 1
- open(tempf, 'w').write("empty")
- setntacl(lp,tempf,acl,"S-1-5-21-2212615479-2695158682-2101375467","tdb",os.path.join(path,"eadbtest.tdb"))
- facl=getntacl(lp,tempf,"tdb",os.path.join(path,"eadbtest.tdb"))
+ open(self.tempf, 'w').write("empty")
+ setntacl(lp,self.tempf,acl,"S-1-5-21-2212615479-2695158682-2101375467","tdb",os.path.join(self.tempdir,"eadbtest.tdb"))
+ facl=getntacl(lp,self.tempf,"tdb",os.path.join(self.tempdir,"eadbtest.tdb"))
domsid=security.dom_sid(security.SID_NT_SELF)
- self.assertEquals(facl.info.as_sddl(domsid),acl)
- os.unlink(tempf)
+ self.assertEquals(facl.as_sddl(domsid),acl)
+ os.unlink(os.path.join(self.tempdir,"eadbtest.tdb"))
def test_setntacl_invalidbackend(self):
- random.seed()
lp = LoadParm()
acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
- path = os.environ['SELFTEST_PREFIX']
- tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
- ntacl = xattr.NTACL()
- ntacl.version = 1
- open(tempf, 'w').write("empty")
- self.assertRaises(XattrBackendError, setntacl, lp, tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467","ttdb", os.path.join(path,"eadbtest.tdb"))
+ open(self.tempf, 'w').write("empty")
+ self.assertRaises(XattrBackendError, setntacl, lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467","ttdb", os.path.join(self.tempdir,"eadbtest.tdb"))
def test_setntacl_forcenative(self):
if os.getuid() == 0:
raise TestSkipped("Running test as root, test skipped")
- random.seed()
lp = LoadParm()
acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
- path = os.environ['SELFTEST_PREFIX']
- tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
- ntacl = xattr.NTACL()
- ntacl.version = 1
- open(tempf, 'w').write("empty")
- lp.set("posix:eadb", os.path.join(path,"eadbtest.tdb"))
- self.assertRaises(Exception, setntacl, lp, tempf ,acl,
+ open(self.tempf, 'w').write("empty")
+ lp.set("posix:eadb", os.path.join(self.tempdir,"eadbtest.tdb"))
+ self.assertRaises(Exception, setntacl, lp, self.tempf ,acl,
"S-1-5-21-2212615479-2695158682-2101375467","native")
- os.unlink(tempf)
+
+
+ def setUp(self):
+ super(NtaclsTests, self).setUp()
+ self.tempf = os.path.join(self.tempdir, "test")
+ open(self.tempf, 'w').write("empty")
+
+ def tearDown(self):
+ os.unlink(self.tempf)
+ super(NtaclsTests, self).tearDown()
diff --git a/source4/scripting/python/samba/tests/param.py b/source4/scripting/python/samba/tests/param.py
index 7848e1c23b..f539eba140 100644
--- a/source4/scripting/python/samba/tests/param.py
+++ b/source4/scripting/python/samba/tests/param.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
#
@@ -53,7 +51,7 @@ class LoadParmTestCase(samba.tests.TestCase):
file = param.LoadParm()
file.load_default()
- def test_section_nonexistant(self):
+ def test_section_nonexistent(self):
samba_lp = param.LoadParm()
samba_lp.load_default()
- self.assertRaises(KeyError, samba_lp.__getitem__, "nonexistant")
+ self.assertRaises(KeyError, samba_lp.__getitem__, "nonexistent")
diff --git a/source4/scripting/python/samba/tests/policy.py b/source4/scripting/python/samba/tests/policy.py
new file mode 100644
index 0000000000..b245745152
--- /dev/null
+++ b/source4/scripting/python/samba/tests/policy.py
@@ -0,0 +1,34 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Jelmer Vernooij <jelmer@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/>.
+#
+
+"""Tests for the libpolicy Python bindings.
+
+"""
+
+from samba.tests import TestCase
+from samba import policy
+
+
+class PolicyTests(TestCase):
+
+ def test_get_gpo_flags(self):
+ self.assertEquals(["GPO_FLAG_USER_DISABLE"],
+ policy.get_gpo_flags(policy.GPO_FLAG_USER_DISABLE))
+
+ def test_get_gplink_options(self):
+ self.assertEquals(["GPLINK_OPT_DISABLE"],
+ policy.get_gplink_options(policy.GPLINK_OPT_DISABLE))
diff --git a/source4/scripting/python/samba/tests/posixacl.py b/source4/scripting/python/samba/tests/posixacl.py
new file mode 100644
index 0000000000..652721f4f1
--- /dev/null
+++ b/source4/scripting/python/samba/tests/posixacl.py
@@ -0,0 +1,715 @@
+# Unix SMB/CIFS implementation. Tests for NT and posix ACL manipulation
+# Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
+# Copyright (C) Andrew Bartlett 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/>.
+#
+
+"""Tests for the Samba3 NT -> posix ACL layer"""
+
+from samba.ntacls import setntacl, getntacl, checkset_backend
+from samba.dcerpc import xattr, security, smb_acl, idmap
+from samba.param import LoadParm
+from samba.tests import TestCaseInTempDir
+from samba import provision
+import random
+import os
+from samba.samba3 import smbd, passdb
+from samba.samba3 import param as s3param
+
+# To print a posix ACL use:
+# for entry in posix_acl.acl:
+# print "a_type: %d" % entry.a_type
+# print "a_perm: %o" % entry.a_perm
+# print "uid: %d" % entry.uid
+# print "gid: %d" % entry.gid
+
+class PosixAclMappingTests(TestCaseInTempDir):
+
+ def test_setntacl(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=False)
+
+ def test_setntacl_smbd_getntacl(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=True)
+ facl = getntacl(self.lp, self.tempf, direct_db_access=True)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(facl.as_sddl(anysid),acl)
+
+ def test_setntacl_smbd_setposixacl_getntacl(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=True)
+
+ # This will invalidate the ACL, as we have a hook!
+ smbd.set_simple_acl(self.tempf, 0640)
+
+ # However, this only asks the xattr
+ try:
+ facl = getntacl(self.lp, self.tempf, direct_db_access=True)
+ self.assertTrue(False)
+ except TypeError:
+ pass
+
+ def test_setntacl_invalidate_getntacl(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=True)
+
+ # This should invalidate the ACL, as we include the posix ACL in the hash
+ (backend_obj, dbname) = checkset_backend(self.lp, None, None)
+ backend_obj.wrap_setxattr(dbname,
+ self.tempf, "system.fake_access_acl", "")
+
+ #however, as this is direct DB access, we do not notice it
+ facl = getntacl(self.lp, self.tempf, direct_db_access=True)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(acl, facl.as_sddl(anysid))
+
+ def test_setntacl_invalidate_getntacl_smbd(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=False)
+
+ # This should invalidate the ACL, as we include the posix ACL in the hash
+ (backend_obj, dbname) = checkset_backend(self.lp, None, None)
+ backend_obj.wrap_setxattr(dbname,
+ self.tempf, "system.fake_access_acl", "")
+
+ #the hash would break, and we return an ACL based only on the mode, except we set the ACL using the 'ntvfs' mode that doesn't include a hash
+ facl = getntacl(self.lp, self.tempf)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(acl, facl.as_sddl(anysid))
+
+ def test_setntacl_smbd_invalidate_getntacl_smbd(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ simple_acl_from_posix = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)(A;;0x001200a9;;;S-1-5-21-2212615479-2695158682-2101375467-513)(A;;;;;WD)"
+ os.chmod(self.tempf, 0750)
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=False)
+
+ # This should invalidate the ACL, as we include the posix ACL in the hash
+ (backend_obj, dbname) = checkset_backend(self.lp, None, None)
+ backend_obj.wrap_setxattr(dbname,
+ self.tempf, "system.fake_access_acl", "")
+
+ #the hash will break, and we return an ACL based only on the mode
+ facl = getntacl(self.lp, self.tempf, direct_db_access=False)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(simple_acl_from_posix, facl.as_sddl(anysid))
+
+ def test_setntacl_getntacl_smbd(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=True)
+ facl = getntacl(self.lp, self.tempf, direct_db_access=False)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(facl.as_sddl(anysid),acl)
+
+ def test_setntacl_smbd_getntacl_smbd(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=False)
+ facl = getntacl(self.lp, self.tempf, direct_db_access=False)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(facl.as_sddl(anysid),acl)
+
+ def test_setntacl_smbd_setposixacl_getntacl_smbd(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ simple_acl_from_posix = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;;0x001f019f;;;S-1-5-21-2212615479-2695158682-2101375467-512)(A;;0x00120089;;;S-1-5-21-2212615479-2695158682-2101375467-513)(A;;;;;WD)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=False)
+ # This invalidates the hash of the NT acl just set because there is a hook in the posix ACL set code
+ smbd.set_simple_acl(self.tempf, 0640)
+ facl = getntacl(self.lp, self.tempf, direct_db_access=False)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(simple_acl_from_posix, facl.as_sddl(anysid))
+
+ def test_setntacl_smbd_setposixacl_group_getntacl_smbd(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+ simple_acl_from_posix = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;;0x001f019f;;;S-1-5-21-2212615479-2695158682-2101375467-512)(A;;0x00120089;;;BA)(A;;0x00120089;;;S-1-5-21-2212615479-2695158682-2101375467-513)(A;;;;;WD)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=False)
+ # This invalidates the hash of the NT acl just set because there is a hook in the posix ACL set code
+ s4_passdb = passdb.PDB(self.lp.get("passdb backend"))
+ (BA_gid,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ smbd.set_simple_acl(self.tempf, 0640, BA_gid)
+
+ # This should re-calculate an ACL based on the posix details
+ facl = getntacl(self.lp,self.tempf, direct_db_access=False)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(simple_acl_from_posix, facl.as_sddl(anysid))
+
+ def test_setntacl_smbd_getntacl_smbd_gpo(self):
+ acl = "O:DAG:DUD:P(A;OICI;0x001f01ff;;;DA)(A;OICI;0x001f01ff;;;EA)(A;OICIIO;0x001f01ff;;;CO)(A;OICI;0x001f01ff;;;DA)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)(A;OICI;0x001200a9;;;ED)S:AI(OU;CIIDSA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)(OU;CIIDSA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=False)
+ facl = getntacl(self.lp, self.tempf, direct_db_access=False)
+ domsid = security.dom_sid("S-1-5-21-2212615479-2695158682-2101375467")
+ self.assertEquals(facl.as_sddl(domsid),acl)
+
+ def test_setntacl_getposixacl(self):
+ acl = "O:S-1-5-21-2212615479-2695158682-2101375467-512G:S-1-5-21-2212615479-2695158682-2101375467-513D:(A;OICI;0x001f01ff;;;S-1-5-21-2212615479-2695158682-2101375467-512)"
+ setntacl(self.lp, self.tempf, acl, "S-1-5-21-2212615479-2695158682-2101375467", use_ntvfs=False)
+ facl = getntacl(self.lp, self.tempf)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(facl.as_sddl(anysid),acl)
+ posix_acl = smbd.get_sys_acl(self.tempf, smb_acl.SMB_ACL_TYPE_ACCESS)
+
+ def test_setposixacl_getposixacl(self):
+ smbd.set_simple_acl(self.tempf, 0640)
+ posix_acl = smbd.get_sys_acl(self.tempf, smb_acl.SMB_ACL_TYPE_ACCESS)
+ self.assertEquals(posix_acl.count, 4)
+
+ self.assertEquals(posix_acl.acl[0].a_type, smb_acl.SMB_ACL_USER_OBJ)
+ self.assertEquals(posix_acl.acl[0].a_perm, 6)
+
+ self.assertEquals(posix_acl.acl[1].a_type, smb_acl.SMB_ACL_GROUP_OBJ)
+ self.assertEquals(posix_acl.acl[1].a_perm, 4)
+
+ self.assertEquals(posix_acl.acl[2].a_type, smb_acl.SMB_ACL_OTHER)
+ self.assertEquals(posix_acl.acl[2].a_perm, 0)
+
+ self.assertEquals(posix_acl.acl[3].a_type, smb_acl.SMB_ACL_MASK)
+ self.assertEquals(posix_acl.acl[3].a_perm, 6)
+
+ def test_setposixacl_getntacl(self):
+ acl = ""
+ smbd.set_simple_acl(self.tempf, 0750)
+ try:
+ facl = getntacl(self.lp, self.tempf)
+ self.assertTrue(False)
+ except TypeError:
+ # We don't expect the xattr to be filled in in this case
+ pass
+
+ def test_setposixacl_getntacl_smbd(self):
+ s4_passdb = passdb.PDB(self.lp.get("passdb backend"))
+ group_SID = s4_passdb.gid_to_sid(os.stat(self.tempf).st_gid)
+ user_SID = s4_passdb.uid_to_sid(os.stat(self.tempf).st_uid)
+ smbd.set_simple_acl(self.tempf, 0640)
+ facl = getntacl(self.lp, self.tempf, direct_db_access=False)
+ acl = "O:%sG:%sD:(A;;0x001f019f;;;%s)(A;;0x00120089;;;%s)(A;;;;;WD)" % (user_SID, group_SID, user_SID, group_SID)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(acl, facl.as_sddl(anysid))
+
+ def test_setposixacl_dir_getntacl_smbd(self):
+ s4_passdb = passdb.PDB(self.lp.get("passdb backend"))
+ user_SID = s4_passdb.uid_to_sid(os.stat(self.tempdir).st_uid)
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+ s4_passdb = passdb.PDB(self.lp.get("passdb backend"))
+ (BA_id,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ self.assertEquals(BA_type, idmap.ID_TYPE_BOTH)
+ SO_sid = security.dom_sid(security.SID_BUILTIN_SERVER_OPERATORS)
+ (SO_id,SO_type) = s4_passdb.sid_to_id(SO_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ smbd.chown(self.tempdir, BA_id, SO_id)
+ smbd.set_simple_acl(self.tempdir, 0750)
+ facl = getntacl(self.lp, self.tempdir, direct_db_access=False)
+ acl = "O:BAG:SOD:(A;;0x001f01ff;;;BA)(A;;0x001200a9;;;SO)(A;;;;;WD)(A;OICIIO;0x001f01ff;;;CO)(A;OICIIO;0x001f01ff;;;CG)(A;OICIIO;0x001f01ff;;;WD)"
+
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(acl, facl.as_sddl(anysid))
+
+ def test_setposixacl_group_getntacl_smbd(self):
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+ s4_passdb = passdb.PDB(self.lp.get("passdb backend"))
+ (BA_gid,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ group_SID = s4_passdb.gid_to_sid(os.stat(self.tempf).st_gid)
+ user_SID = s4_passdb.uid_to_sid(os.stat(self.tempf).st_uid)
+ self.assertEquals(BA_type, idmap.ID_TYPE_BOTH)
+ smbd.set_simple_acl(self.tempf, 0640, BA_gid)
+ facl = getntacl(self.lp, self.tempf, direct_db_access=False)
+ domsid = passdb.get_global_sam_sid()
+ acl = "O:%sG:%sD:(A;;0x001f019f;;;%s)(A;;0x00120089;;;BA)(A;;0x00120089;;;%s)(A;;;;;WD)" % (user_SID, group_SID, user_SID, group_SID)
+ anysid = security.dom_sid(security.SID_NT_SELF)
+ self.assertEquals(acl, facl.as_sddl(anysid))
+
+ def test_setposixacl_getposixacl(self):
+ smbd.set_simple_acl(self.tempf, 0640)
+ posix_acl = smbd.get_sys_acl(self.tempf, smb_acl.SMB_ACL_TYPE_ACCESS)
+ self.assertEquals(posix_acl.count, 4)
+
+ self.assertEquals(posix_acl.acl[0].a_type, smb_acl.SMB_ACL_USER_OBJ)
+ self.assertEquals(posix_acl.acl[0].a_perm, 6)
+
+ self.assertEquals(posix_acl.acl[1].a_type, smb_acl.SMB_ACL_GROUP_OBJ)
+ self.assertEquals(posix_acl.acl[1].a_perm, 4)
+
+ self.assertEquals(posix_acl.acl[2].a_type, smb_acl.SMB_ACL_OTHER)
+ self.assertEquals(posix_acl.acl[2].a_perm, 0)
+
+ self.assertEquals(posix_acl.acl[3].a_type, smb_acl.SMB_ACL_MASK)
+ self.assertEquals(posix_acl.acl[3].a_perm, 7)
+
+ def test_setposixacl_dir_getposixacl(self):
+ smbd.set_simple_acl(self.tempdir, 0750)
+ posix_acl = smbd.get_sys_acl(self.tempdir, smb_acl.SMB_ACL_TYPE_ACCESS)
+ self.assertEquals(posix_acl.count, 4)
+
+ self.assertEquals(posix_acl.acl[0].a_type, smb_acl.SMB_ACL_USER_OBJ)
+ self.assertEquals(posix_acl.acl[0].a_perm, 7)
+
+ self.assertEquals(posix_acl.acl[1].a_type, smb_acl.SMB_ACL_GROUP_OBJ)
+ self.assertEquals(posix_acl.acl[1].a_perm, 5)
+
+ self.assertEquals(posix_acl.acl[2].a_type, smb_acl.SMB_ACL_OTHER)
+ self.assertEquals(posix_acl.acl[2].a_perm, 0)
+
+ self.assertEquals(posix_acl.acl[3].a_type, smb_acl.SMB_ACL_MASK)
+ self.assertEquals(posix_acl.acl[3].a_perm, 7)
+
+ def test_setposixacl_group_getposixacl(self):
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+ s4_passdb = passdb.PDB(self.lp.get("passdb backend"))
+ (BA_gid,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ self.assertEquals(BA_type, idmap.ID_TYPE_BOTH)
+ smbd.set_simple_acl(self.tempf, 0670, BA_gid)
+ posix_acl = smbd.get_sys_acl(self.tempf, smb_acl.SMB_ACL_TYPE_ACCESS)
+
+ self.assertEquals(posix_acl.count, 5)
+
+ self.assertEquals(posix_acl.acl[0].a_type, smb_acl.SMB_ACL_USER_OBJ)
+ self.assertEquals(posix_acl.acl[0].a_perm, 6)
+
+ self.assertEquals(posix_acl.acl[1].a_type, smb_acl.SMB_ACL_GROUP_OBJ)
+ self.assertEquals(posix_acl.acl[1].a_perm, 7)
+
+ self.assertEquals(posix_acl.acl[2].a_type, smb_acl.SMB_ACL_OTHER)
+ self.assertEquals(posix_acl.acl[2].a_perm, 0)
+
+ self.assertEquals(posix_acl.acl[3].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[3].a_perm, 7)
+ self.assertEquals(posix_acl.acl[3].info.gid, BA_gid)
+
+ self.assertEquals(posix_acl.acl[4].a_type, smb_acl.SMB_ACL_MASK)
+ self.assertEquals(posix_acl.acl[4].a_perm, 7)
+
+ def test_setntacl_sysvol_check_getposixacl(self):
+ acl = provision.SYSVOL_ACL
+ domsid = passdb.get_global_sam_sid()
+ setntacl(self.lp, self.tempf,acl,str(domsid), use_ntvfs=False)
+ facl = getntacl(self.lp, self.tempf)
+ self.assertEquals(facl.as_sddl(domsid),acl)
+ posix_acl = smbd.get_sys_acl(self.tempf, smb_acl.SMB_ACL_TYPE_ACCESS)
+
+ LA_sid = security.dom_sid(str(domsid)+"-"+str(security.DOMAIN_RID_ADMINISTRATOR))
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+ SO_sid = security.dom_sid(security.SID_BUILTIN_SERVER_OPERATORS)
+ SY_sid = security.dom_sid(security.SID_NT_SYSTEM)
+ AU_sid = security.dom_sid(security.SID_NT_AUTHENTICATED_USERS)
+
+ s4_passdb = passdb.PDB(self.lp.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)
+ self.assertEquals(LA_type, idmap.ID_TYPE_UID)
+ (BA_gid,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ self.assertEquals(BA_type, idmap.ID_TYPE_BOTH)
+ (SO_gid,SO_type) = s4_passdb.sid_to_id(SO_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ (SY_gid,SY_type) = s4_passdb.sid_to_id(SY_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ (AU_gid,AU_type) = s4_passdb.sid_to_id(AU_sid)
+ self.assertEquals(AU_type, idmap.ID_TYPE_BOTH)
+
+ self.assertEquals(posix_acl.count, 9)
+
+ self.assertEquals(posix_acl.acl[0].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[0].a_perm, 7)
+ self.assertEquals(posix_acl.acl[0].info.gid, BA_gid)
+
+ self.assertEquals(posix_acl.acl[1].a_type, smb_acl.SMB_ACL_USER)
+ self.assertEquals(posix_acl.acl[1].a_perm, 6)
+ self.assertEquals(posix_acl.acl[1].info.uid, LA_uid)
+
+ self.assertEquals(posix_acl.acl[2].a_type, smb_acl.SMB_ACL_OTHER)
+ self.assertEquals(posix_acl.acl[2].a_perm, 0)
+
+ self.assertEquals(posix_acl.acl[3].a_type, smb_acl.SMB_ACL_USER_OBJ)
+ self.assertEquals(posix_acl.acl[3].a_perm, 6)
+
+ self.assertEquals(posix_acl.acl[4].a_type, smb_acl.SMB_ACL_GROUP_OBJ)
+ self.assertEquals(posix_acl.acl[4].a_perm, 7)
+
+ self.assertEquals(posix_acl.acl[5].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[5].a_perm, 5)
+ self.assertEquals(posix_acl.acl[5].info.gid, SO_gid)
+
+ self.assertEquals(posix_acl.acl[6].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[6].a_perm, 7)
+ self.assertEquals(posix_acl.acl[6].info.gid, SY_gid)
+
+ self.assertEquals(posix_acl.acl[7].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[7].a_perm, 5)
+ self.assertEquals(posix_acl.acl[7].info.gid, AU_gid)
+
+ self.assertEquals(posix_acl.acl[8].a_type, smb_acl.SMB_ACL_MASK)
+ self.assertEquals(posix_acl.acl[8].a_perm, 7)
+
+
+# check that it matches:
+# user::rwx
+# user:root:rwx (selftest user actually)
+# group::rwx
+# group:Local Admins:rwx
+# group:3000000:r-x
+# group:3000001:rwx
+# group:3000002:r-x
+# mask::rwx
+# other::---
+
+#
+# This is in this order in the NDR smb_acl (not re-orderded for display)
+# a_type: GROUP
+# a_perm: 7
+# uid: -1
+# gid: 10
+# a_type: USER
+# a_perm: 6
+# uid: 0 (selftest user actually)
+# gid: -1
+# a_type: OTHER
+# a_perm: 0
+# uid: -1
+# gid: -1
+# a_type: USER_OBJ
+# a_perm: 6
+# uid: -1
+# gid: -1
+# a_type: GROUP_OBJ
+# a_perm: 7
+# uid: -1
+# gid: -1
+# a_type: GROUP
+# a_perm: 5
+# uid: -1
+# gid: 3000020
+# a_type: GROUP
+# a_perm: 7
+# uid: -1
+# gid: 3000000
+# a_type: GROUP
+# a_perm: 5
+# uid: -1
+# gid: 3000001
+# a_type: MASK
+# a_perm: 7
+# uid: -1
+# gid: -1
+
+#
+
+
+ def test_setntacl_sysvol_dir_check_getposixacl(self):
+ acl = provision.SYSVOL_ACL
+ domsid = passdb.get_global_sam_sid()
+ setntacl(self.lp, self.tempdir,acl,str(domsid), use_ntvfs=False)
+ facl = getntacl(self.lp, self.tempdir)
+ self.assertEquals(facl.as_sddl(domsid),acl)
+ posix_acl = smbd.get_sys_acl(self.tempdir, smb_acl.SMB_ACL_TYPE_ACCESS)
+
+ LA_sid = security.dom_sid(str(domsid)+"-"+str(security.DOMAIN_RID_ADMINISTRATOR))
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+ SO_sid = security.dom_sid(security.SID_BUILTIN_SERVER_OPERATORS)
+ SY_sid = security.dom_sid(security.SID_NT_SYSTEM)
+ AU_sid = security.dom_sid(security.SID_NT_AUTHENTICATED_USERS)
+
+ s4_passdb = passdb.PDB(self.lp.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)
+ self.assertEquals(LA_type, idmap.ID_TYPE_UID)
+ (BA_gid,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ self.assertEquals(BA_type, idmap.ID_TYPE_BOTH)
+ (SO_gid,SO_type) = s4_passdb.sid_to_id(SO_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ (SY_gid,SY_type) = s4_passdb.sid_to_id(SY_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ (AU_gid,AU_type) = s4_passdb.sid_to_id(AU_sid)
+ self.assertEquals(AU_type, idmap.ID_TYPE_BOTH)
+
+ self.assertEquals(posix_acl.count, 9)
+
+ self.assertEquals(posix_acl.acl[0].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[0].a_perm, 7)
+ self.assertEquals(posix_acl.acl[0].info.gid, BA_gid)
+
+ self.assertEquals(posix_acl.acl[1].a_type, smb_acl.SMB_ACL_USER)
+ self.assertEquals(posix_acl.acl[1].a_perm, 7)
+ self.assertEquals(posix_acl.acl[1].info.uid, LA_uid)
+
+ self.assertEquals(posix_acl.acl[2].a_type, smb_acl.SMB_ACL_OTHER)
+ self.assertEquals(posix_acl.acl[2].a_perm, 0)
+
+ self.assertEquals(posix_acl.acl[3].a_type, smb_acl.SMB_ACL_USER_OBJ)
+ self.assertEquals(posix_acl.acl[3].a_perm, 7)
+
+ self.assertEquals(posix_acl.acl[4].a_type, smb_acl.SMB_ACL_GROUP_OBJ)
+ self.assertEquals(posix_acl.acl[4].a_perm, 7)
+
+ self.assertEquals(posix_acl.acl[5].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[5].a_perm, 5)
+ self.assertEquals(posix_acl.acl[5].info.gid, SO_gid)
+
+ self.assertEquals(posix_acl.acl[6].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[6].a_perm, 7)
+ self.assertEquals(posix_acl.acl[6].info.gid, SY_gid)
+
+ self.assertEquals(posix_acl.acl[7].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[7].a_perm, 5)
+ self.assertEquals(posix_acl.acl[7].info.gid, AU_gid)
+
+ self.assertEquals(posix_acl.acl[8].a_type, smb_acl.SMB_ACL_MASK)
+ self.assertEquals(posix_acl.acl[8].a_perm, 7)
+
+
+# check that it matches:
+# user::rwx
+# user:root:rwx (selftest user actually)
+# group::rwx
+# group:3000000:rwx
+# group:3000001:r-x
+# group:3000002:rwx
+# group:3000003:r-x
+# mask::rwx
+# other::---
+
+
+ def test_setntacl_policies_dir_check_getposixacl(self):
+ acl = provision.POLICIES_ACL
+ domsid = passdb.get_global_sam_sid()
+ setntacl(self.lp, self.tempdir,acl,str(domsid), use_ntvfs=False)
+ facl = getntacl(self.lp, self.tempdir)
+ self.assertEquals(facl.as_sddl(domsid),acl)
+ posix_acl = smbd.get_sys_acl(self.tempdir, smb_acl.SMB_ACL_TYPE_ACCESS)
+
+ LA_sid = security.dom_sid(str(domsid)+"-"+str(security.DOMAIN_RID_ADMINISTRATOR))
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+ SO_sid = security.dom_sid(security.SID_BUILTIN_SERVER_OPERATORS)
+ SY_sid = security.dom_sid(security.SID_NT_SYSTEM)
+ AU_sid = security.dom_sid(security.SID_NT_AUTHENTICATED_USERS)
+ PA_sid = security.dom_sid(str(domsid)+"-"+str(security.DOMAIN_RID_POLICY_ADMINS))
+
+ s4_passdb = passdb.PDB(self.lp.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)
+ self.assertEquals(LA_type, idmap.ID_TYPE_UID)
+ (BA_gid,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ self.assertEquals(BA_type, idmap.ID_TYPE_BOTH)
+ (SO_gid,SO_type) = s4_passdb.sid_to_id(SO_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ (SY_gid,SY_type) = s4_passdb.sid_to_id(SY_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ (AU_gid,AU_type) = s4_passdb.sid_to_id(AU_sid)
+ self.assertEquals(AU_type, idmap.ID_TYPE_BOTH)
+ (PA_gid,PA_type) = s4_passdb.sid_to_id(PA_sid)
+ self.assertEquals(PA_type, idmap.ID_TYPE_BOTH)
+
+ self.assertEquals(posix_acl.count, 10)
+
+ self.assertEquals(posix_acl.acl[0].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[0].a_perm, 7)
+ self.assertEquals(posix_acl.acl[0].info.gid, BA_gid)
+
+ self.assertEquals(posix_acl.acl[1].a_type, smb_acl.SMB_ACL_USER)
+ self.assertEquals(posix_acl.acl[1].a_perm, 7)
+ self.assertEquals(posix_acl.acl[1].info.uid, LA_uid)
+
+ self.assertEquals(posix_acl.acl[2].a_type, smb_acl.SMB_ACL_OTHER)
+ self.assertEquals(posix_acl.acl[2].a_perm, 0)
+
+ self.assertEquals(posix_acl.acl[3].a_type, smb_acl.SMB_ACL_USER_OBJ)
+ self.assertEquals(posix_acl.acl[3].a_perm, 7)
+
+ self.assertEquals(posix_acl.acl[4].a_type, smb_acl.SMB_ACL_GROUP_OBJ)
+ self.assertEquals(posix_acl.acl[4].a_perm, 7)
+
+ self.assertEquals(posix_acl.acl[5].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[5].a_perm, 5)
+ self.assertEquals(posix_acl.acl[5].info.gid, SO_gid)
+
+ self.assertEquals(posix_acl.acl[6].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[6].a_perm, 7)
+ self.assertEquals(posix_acl.acl[6].info.gid, SY_gid)
+
+ self.assertEquals(posix_acl.acl[7].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[7].a_perm, 5)
+ self.assertEquals(posix_acl.acl[7].info.gid, AU_gid)
+
+ self.assertEquals(posix_acl.acl[8].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[8].a_perm, 7)
+ self.assertEquals(posix_acl.acl[8].info.gid, PA_gid)
+
+ self.assertEquals(posix_acl.acl[9].a_type, smb_acl.SMB_ACL_MASK)
+ self.assertEquals(posix_acl.acl[9].a_perm, 7)
+
+
+# check that it matches:
+# user::rwx
+# user:root:rwx (selftest user actually)
+# group::rwx
+# group:3000000:rwx
+# group:3000001:r-x
+# group:3000002:rwx
+# group:3000003:r-x
+# group:3000004:rwx
+# mask::rwx
+# other::---
+
+
+
+ def test_setntacl_policies_check_getposixacl(self):
+ acl = provision.POLICIES_ACL
+
+ domsid = passdb.get_global_sam_sid()
+ setntacl(self.lp, self.tempf, acl, str(domsid), use_ntvfs=False)
+ facl = getntacl(self.lp, self.tempf)
+ self.assertEquals(facl.as_sddl(domsid),acl)
+ posix_acl = smbd.get_sys_acl(self.tempf, smb_acl.SMB_ACL_TYPE_ACCESS)
+
+ LA_sid = security.dom_sid(str(domsid)+"-"+str(security.DOMAIN_RID_ADMINISTRATOR))
+ BA_sid = security.dom_sid(security.SID_BUILTIN_ADMINISTRATORS)
+ SO_sid = security.dom_sid(security.SID_BUILTIN_SERVER_OPERATORS)
+ SY_sid = security.dom_sid(security.SID_NT_SYSTEM)
+ AU_sid = security.dom_sid(security.SID_NT_AUTHENTICATED_USERS)
+ PA_sid = security.dom_sid(str(domsid)+"-"+str(security.DOMAIN_RID_POLICY_ADMINS))
+
+ s4_passdb = passdb.PDB(self.lp.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)
+ self.assertEquals(LA_type, idmap.ID_TYPE_UID)
+ (BA_gid,BA_type) = s4_passdb.sid_to_id(BA_sid)
+ self.assertEquals(BA_type, idmap.ID_TYPE_BOTH)
+ (SO_gid,SO_type) = s4_passdb.sid_to_id(SO_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ (SY_gid,SY_type) = s4_passdb.sid_to_id(SY_sid)
+ self.assertEquals(SO_type, idmap.ID_TYPE_BOTH)
+ (AU_gid,AU_type) = s4_passdb.sid_to_id(AU_sid)
+ self.assertEquals(AU_type, idmap.ID_TYPE_BOTH)
+ (PA_gid,PA_type) = s4_passdb.sid_to_id(PA_sid)
+ self.assertEquals(PA_type, idmap.ID_TYPE_BOTH)
+
+ self.assertEquals(posix_acl.count, 10)
+
+ self.assertEquals(posix_acl.acl[0].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[0].a_perm, 7)
+ self.assertEquals(posix_acl.acl[0].info.gid, BA_gid)
+
+ self.assertEquals(posix_acl.acl[1].a_type, smb_acl.SMB_ACL_USER)
+ self.assertEquals(posix_acl.acl[1].a_perm, 6)
+ self.assertEquals(posix_acl.acl[1].info.uid, LA_uid)
+
+ self.assertEquals(posix_acl.acl[2].a_type, smb_acl.SMB_ACL_OTHER)
+ self.assertEquals(posix_acl.acl[2].a_perm, 0)
+
+ self.assertEquals(posix_acl.acl[3].a_type, smb_acl.SMB_ACL_USER_OBJ)
+ self.assertEquals(posix_acl.acl[3].a_perm, 6)
+
+ self.assertEquals(posix_acl.acl[4].a_type, smb_acl.SMB_ACL_GROUP_OBJ)
+ self.assertEquals(posix_acl.acl[4].a_perm, 7)
+
+ self.assertEquals(posix_acl.acl[5].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[5].a_perm, 5)
+ self.assertEquals(posix_acl.acl[5].info.gid, SO_gid)
+
+ self.assertEquals(posix_acl.acl[6].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[6].a_perm, 7)
+ self.assertEquals(posix_acl.acl[6].info.gid, SY_gid)
+
+ self.assertEquals(posix_acl.acl[7].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[7].a_perm, 5)
+ self.assertEquals(posix_acl.acl[7].info.gid, AU_gid)
+
+ self.assertEquals(posix_acl.acl[8].a_type, smb_acl.SMB_ACL_GROUP)
+ self.assertEquals(posix_acl.acl[8].a_perm, 7)
+ self.assertEquals(posix_acl.acl[8].info.gid, PA_gid)
+
+ self.assertEquals(posix_acl.acl[9].a_type, smb_acl.SMB_ACL_MASK)
+ self.assertEquals(posix_acl.acl[9].a_perm, 7)
+
+
+# check that it matches:
+# user::rwx
+# user:root:rwx (selftest user actually)
+# group::rwx
+# group:Local Admins:rwx
+# group:3000000:r-x
+# group:3000001:rwx
+# group:3000002:r-x
+# group:3000003:rwx
+# mask::rwx
+# other::---
+
+#
+# This is in this order in the NDR smb_acl (not re-orderded for display)
+# a_type: GROUP
+# a_perm: 7
+# uid: -1
+# gid: 10
+# a_type: USER
+# a_perm: 6
+# uid: 0 (selftest user actually)
+# gid: -1
+# a_type: OTHER
+# a_perm: 0
+# uid: -1
+# gid: -1
+# a_type: USER_OBJ
+# a_perm: 6
+# uid: -1
+# gid: -1
+# a_type: GROUP_OBJ
+# a_perm: 7
+# uid: -1
+# gid: -1
+# a_type: GROUP
+# a_perm: 5
+# uid: -1
+# gid: 3000020
+# a_type: GROUP
+# a_perm: 7
+# uid: -1
+# gid: 3000000
+# a_type: GROUP
+# a_perm: 5
+# uid: -1
+# gid: 3000001
+# a_type: GROUP
+# a_perm: 7
+# uid: -1
+# gid: 3000003
+# a_type: MASK
+# a_perm: 7
+# uid: -1
+# gid: -1
+
+#
+
+ def setUp(self):
+ super(PosixAclMappingTests, self).setUp()
+ s3conf = s3param.get_context()
+ s3conf.load(self.get_loadparm().configfile)
+ s3conf.set("xattr_tdb:file", os.path.join(self.tempdir,"xattr.tdb"))
+ self.lp = s3conf
+ self.tempf = os.path.join(self.tempdir, "test")
+ open(self.tempf, 'w').write("empty")
+
+ def tearDown(self):
+ smbd.unlink(self.tempf)
+ os.unlink(os.path.join(self.tempdir,"xattr.tdb"))
+ super(PosixAclMappingTests, self).tearDown()
diff --git a/source4/scripting/python/samba/tests/provision.py b/source4/scripting/python/samba/tests/provision.py
index 39a01606bb..992179124b 100644
--- a/source4/scripting/python/samba/tests/provision.py
+++ b/source4/scripting/python/samba/tests/provision.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
-# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
-#
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-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/>.
#
@@ -20,7 +18,15 @@
"""Tests for samba.provision."""
import os
-from samba.provision import setup_secretsdb, findnss, ProvisionPaths
+from samba.provision import (
+ ProvisionNames,
+ ProvisionPaths,
+ ProvisionResult,
+ determine_netbios_name,
+ sanitize_server_role,
+ setup_secretsdb,
+ findnss,
+ )
import samba.tests
from samba.tests import env_loadparm, TestCase
@@ -49,6 +55,7 @@ class ProvisionTestCase(samba.tests.TestCaseInTempDir):
def test_setup_secretsdb(self):
path = os.path.join(self.tempdir, "secrets.ldb")
+ secrets_tdb_path = os.path.join(self.tempdir, "secrets.tdb")
paths = ProvisionPaths()
paths.secrets = path
paths.private_dir = os.path.dirname(path)
@@ -61,7 +68,8 @@ class ProvisionTestCase(samba.tests.TestCaseInTempDir):
finally:
del ldb
os.unlink(path)
-
+ os.unlink(secrets_tdb_path)
+
class FindNssTests(TestCase):
"""Test findnss() function."""
@@ -96,9 +104,6 @@ class Disabled(object):
def test_setup_samdb_partitions(self):
raise NotImplementedError(self.test_setup_samdb_partitions)
- def test_create_phpldapadmin_config(self):
- raise NotImplementedError(self.test_create_phpldapadmin_config)
-
def test_provision_dns(self):
raise NotImplementedError(self.test_provision_dns)
@@ -115,3 +120,81 @@ class Disabled(object):
raise NotImplementedError(self.test_vampire)
+class SanitizeServerRoleTests(TestCase):
+
+ def test_same(self):
+ self.assertEquals("standalone server",
+ sanitize_server_role("standalone server"))
+ self.assertEquals("member server",
+ sanitize_server_role("member server"))
+
+ def test_invalid(self):
+ self.assertRaises(ValueError, sanitize_server_role, "foo")
+
+ def test_valid(self):
+ self.assertEquals(
+ "standalone server",
+ sanitize_server_role("ROLE_STANDALONE"))
+ self.assertEquals(
+ "standalone server",
+ sanitize_server_role("standalone"))
+ self.assertEquals(
+ "active directory domain controller",
+ sanitize_server_role("domain controller"))
+
+
+class DummyLogger(object):
+
+ def __init__(self):
+ self.entries = []
+
+ def info(self, text, *args):
+ self.entries.append(("INFO", text % args))
+
+
+class ProvisionResultTests(TestCase):
+
+ def report_logger(self, result):
+ logger = DummyLogger()
+ result.report_logger(logger)
+ return logger.entries
+
+ def base_result(self):
+ result = ProvisionResult()
+ result.server_role = "domain controller"
+ result.names = ProvisionNames()
+ result.names.hostname = "hostnaam"
+ result.names.domain = "DOMEIN"
+ result.names.dnsdomain = "dnsdomein"
+ result.domainsid = "S1-1-1"
+ result.paths = ProvisionPaths()
+ return result
+
+ def test_basic_report_logger(self):
+ result = self.base_result()
+ entries = self.report_logger(result)
+ self.assertEquals(entries, [
+ ('INFO', 'Once the above files are installed, your Samba4 server '
+ 'will be ready to use'),
+ ('INFO', 'Server Role: domain controller'),
+ ('INFO', 'Hostname: hostnaam'),
+ ('INFO', 'NetBIOS Domain: DOMEIN'),
+ ('INFO', 'DNS Domain: dnsdomein'),
+ ('INFO', 'DOMAIN SID: S1-1-1')])
+
+ def test_report_logger_adminpass(self):
+ result = self.base_result()
+ result.adminpass_generated = True
+ result.adminpass = "geheim"
+ entries = self.report_logger(result)
+ self.assertEquals(entries[1],
+ ("INFO", 'Admin password: geheim'))
+
+
+class DetermineNetbiosNameTests(TestCase):
+
+ def test_limits_to_15(self):
+ self.assertEquals("A" * 15, determine_netbios_name("a" * 30))
+
+ def test_strips_invalid(self):
+ self.assertEquals("BLABLA", determine_netbios_name("bla/bla"))
diff --git a/source4/scripting/python/samba/tests/registry.py b/source4/scripting/python/samba/tests/registry.py
index 97926850d2..8016a0bb68 100644
--- a/source4/scripting/python/samba/tests/registry.py
+++ b/source4/scripting/python/samba/tests/registry.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
-#
+#
# 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/>.
#
@@ -26,7 +24,7 @@ import samba.tests
class HelperTests(samba.tests.TestCase):
def test_predef_to_name(self):
- self.assertEquals("HKEY_LOCAL_MACHINE",
+ self.assertEquals("HKEY_LOCAL_MACHINE",
registry.get_predef_name(0x80000002))
def test_str_regtype(self):
diff --git a/source4/scripting/python/samba/tests/samba3.py b/source4/scripting/python/samba/tests/samba3.py
index 3a4b851c75..0a7f13c66f 100644
--- a/source4/scripting/python/samba/tests/samba3.py
+++ b/source4/scripting/python/samba/tests/samba3.py
@@ -1,31 +1,34 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
-#
+#
# 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/>.
#
"""Tests for samba.samba3."""
-from samba.samba3 import (GroupMappingDatabase, Registry, PolicyDatabase,
- SecretsDatabase, TdbSam)
-from samba.samba3 import (WinsDatabase, SmbpasswdFile, ACB_NORMAL,
- IdmapDatabase, SAMUser, ParamFile)
-from samba.tests import TestCase
+from samba.samba3 import (
+ Registry,
+ WinsDatabase,
+ IdmapDatabase,
+ )
+from samba.samba3 import passdb
+from samba.samba3 import param as s3param
+from samba.tests import TestCase, TestCaseInTempDir
+from samba.dcerpc.security import dom_sid
import os
+
for p in [ "../../../../../testdata/samba3", "../../../../testdata/samba3" ]:
DATADIR = os.path.join(os.path.dirname(__file__), p)
if os.path.exists(DATADIR):
@@ -52,113 +55,121 @@ class RegistryTestCase(TestCase):
self.assertEquals(["SOFTWARE", "SYSTEM"], self.registry.subkeys("HKLM"))
def test_values(self):
- self.assertEquals({'DisplayName': (1L, 'E\x00v\x00e\x00n\x00t\x00 \x00L\x00o\x00g\x00\x00\x00'),
- 'ErrorControl': (4L, '\x01\x00\x00\x00')},
+ self.assertEquals({'DisplayName': (1L, 'E\x00v\x00e\x00n\x00t\x00 \x00L\x00o\x00g\x00\x00\x00'),
+ 'ErrorControl': (4L, '\x01\x00\x00\x00')},
self.registry.values("HKLM/SYSTEM/CURRENTCONTROLSET/SERVICES/EVENTLOG"))
-class PolicyTestCase(TestCase):
+class PassdbTestCase(TestCaseInTempDir):
def setUp(self):
- super(PolicyTestCase, self).setUp()
- self.policy = PolicyDatabase(os.path.join(DATADIR, "account_policy.tdb"))
+ super (PassdbTestCase, self).setUp()
+ os.system("cp -r %s %s" % (DATADIR, self.tempdir))
+ datadir = os.path.join(self.tempdir, "samba3")
+
+ self.lp = s3param.get_context()
+ self.lp.load(os.path.join(datadir, "smb.conf"))
+ self.lp.set("private dir", datadir)
+ self.lp.set("state directory", datadir)
+ self.lp.set("lock directory", datadir)
+ passdb.set_secrets_dir(datadir)
+ self.pdb = passdb.PDB("tdbsam")
- def test_policy(self):
- self.assertEquals(self.policy.min_password_length, 5)
- self.assertEquals(self.policy.minimum_password_age, 0)
- self.assertEquals(self.policy.maximum_password_age, 999999999)
- self.assertEquals(self.policy.refuse_machine_password_change, 0)
- self.assertEquals(self.policy.reset_count_minutes, 0)
- self.assertEquals(self.policy.disconnect_time, -1)
- self.assertEquals(self.policy.user_must_logon_to_change_password, None)
- self.assertEquals(self.policy.password_history, 0)
- self.assertEquals(self.policy.lockout_duration, 0)
- self.assertEquals(self.policy.bad_lockout_minutes, None)
+ def tearDown(self):
+ self.lp = []
+ self.pdb = []
+ os.system("rm -rf %s" % os.path.join(self.tempdir, "samba3"))
+ super(PassdbTestCase, self).tearDown()
+ def test_param(self):
+ self.assertEquals("BEDWYR", self.lp.get("netbios name"))
+ self.assertEquals("SAMBA", self.lp.get("workgroup"))
+ self.assertEquals("USER", self.lp.get("security"))
-class GroupsTestCase(TestCase):
+ def test_policy(self):
+ policy = self.pdb.get_account_policy()
+ self.assertEquals(0, policy['bad lockout attempt'])
+ self.assertEquals(-1, policy['disconnect time'])
+ self.assertEquals(0, policy['lockout duration'])
+ self.assertEquals(999999999, policy['maximum password age'])
+ self.assertEquals(0, policy['minimum password age'])
+ self.assertEquals(5, policy['min password length'])
+ self.assertEquals(0, policy['password history'])
+ self.assertEquals(0, policy['refuse machine password change'])
+ self.assertEquals(0, policy['reset count minutes'])
+ self.assertEquals(0, policy['user must logon to change password'])
- def setUp(self):
- super(GroupsTestCase, self).setUp()
- self.groupdb = GroupMappingDatabase(os.path.join(DATADIR, "group_mapping.tdb"))
+ def test_get_sid(self):
+ domain_sid = passdb.get_global_sam_sid()
+ self.assertEquals(dom_sid("S-1-5-21-2470180966-3899876309-2637894779"), domain_sid)
- def tearDown(self):
- self.groupdb.close()
- super(GroupsTestCase, self).tearDown()
+ def test_usernames(self):
+ userlist = self.pdb.search_users(0)
+ self.assertEquals(3, len(userlist))
+
+ def test_getuser(self):
+ user = self.pdb.getsampwnam("root")
+
+ self.assertEquals(16, user.acct_ctrl)
+ self.assertEquals("", user.acct_desc)
+ self.assertEquals(0, user.bad_password_count)
+ self.assertEquals(0, user.bad_password_time)
+ self.assertEquals(0, user.code_page)
+ self.assertEquals(0, user.country_code)
+ self.assertEquals("", user.dir_drive)
+ self.assertEquals("BEDWYR", user.domain)
+ self.assertEquals("root", user.full_name)
+ self.assertEquals(dom_sid('S-1-5-21-2470180966-3899876309-2637894779-513'), user.group_sid)
+ self.assertEquals("\\\\BEDWYR\\root", user.home_dir)
+ self.assertEquals([-1 for i in range(21)], user.hours)
+ self.assertEquals(21, user.hours_len)
+ self.assertEquals(9223372036854775807, user.kickoff_time)
+ self.assertEquals(None, user.lanman_passwd)
+ self.assertEquals(9223372036854775807, user.logoff_time)
+ self.assertEquals(0, user.logon_count)
+ self.assertEquals(168, user.logon_divs)
+ self.assertEquals("", user.logon_script)
+ self.assertEquals(0, user.logon_time)
+ self.assertEquals("", user.munged_dial)
+ self.assertEquals('\x87\x8d\x80\x14`l\xda)gzD\xef\xa15?\xc7', user.nt_passwd)
+ self.assertEquals("", user.nt_username)
+ self.assertEquals(1125418267, user.pass_can_change_time)
+ self.assertEquals(1125418267, user.pass_last_set_time)
+ self.assertEquals(2125418266, user.pass_must_change_time)
+ self.assertEquals(None, user.plaintext_passwd)
+ self.assertEquals("\\\\BEDWYR\\root\\profile", user.profile_path)
+ self.assertEquals(None, user.pw_history)
+ self.assertEquals(dom_sid("S-1-5-21-2470180966-3899876309-2637894779-1000"), user.user_sid)
+ self.assertEquals("root", user.username)
+ self.assertEquals("", user.workstations)
def test_group_length(self):
- self.assertEquals(13, len(list(self.groupdb.groupsids())))
+ grouplist = self.pdb.enum_group_mapping()
+ self.assertEquals(13, len(grouplist))
def test_get_group(self):
- self.assertEquals((-1, 5L, 'Administrators', ''), self.groupdb.get_group("S-1-5-32-544"))
+ group = self.pdb.getgrsid(dom_sid("S-1-5-32-544"))
+ self.assertEquals("Administrators", group.nt_name)
+ self.assertEquals(-1, group.gid)
+ self.assertEquals(5, group.sid_name_use)
def test_groupsids(self):
- sids = list(self.groupdb.groupsids())
+ grouplist = self.pdb.enum_group_mapping()
+ sids = []
+ for g in grouplist:
+ sids.append(str(g.sid))
self.assertTrue("S-1-5-32-544" in sids)
+ self.assertTrue("S-1-5-32-545" in sids)
+ self.assertTrue("S-1-5-32-546" in sids)
+ self.assertTrue("S-1-5-32-548" in sids)
+ self.assertTrue("S-1-5-32-549" in sids)
+ self.assertTrue("S-1-5-32-550" in sids)
+ self.assertTrue("S-1-5-32-551" in sids)
def test_alias_length(self):
- self.assertEquals(0, len(list(self.groupdb.aliases())))
-
-
-class SecretsDbTestCase(TestCase):
-
- def setUp(self):
- super(SecretsDbTestCase, self).setUp()
- self.secretsdb = SecretsDatabase(os.path.join(DATADIR, "secrets.tdb"))
-
- def tearDown(self):
- self.secretsdb.close()
- super(SecretsDbTestCase, self).tearDown()
-
- def test_get_sid(self):
- self.assertTrue(self.secretsdb.get_sid("BEDWYR") is not None)
-
-
-class TdbSamTestCase(TestCase):
-
- def setUp(self):
- super(TdbSamTestCase, self).setUp()
- self.samdb = TdbSam(os.path.join(DATADIR, "passdb.tdb"))
-
- def tearDown(self):
- self.samdb.close()
- super(TdbSamTestCase, self).tearDown()
-
- def test_usernames(self):
- self.assertEquals(3, len(list(self.samdb.usernames())))
-
- def test_getuser(self):
- user = SAMUser("root")
- user.logoff_time = 2147483647
- user.kickoff_time = 2147483647
- user.pass_can_change_time = 1125418267
- user.username = "root"
- user.uid = None
- user.lm_password = 'U)\x02\x03\x1b\xed\xe9\xef\xaa\xd3\xb45\xb5\x14\x04\xee'
- user.nt_password = '\x87\x8d\x80\x14`l\xda)gzD\xef\xa15?\xc7'
- user.acct_ctrl = 16
- user.pass_last_set_time = 1125418267
- user.fullname = "root"
- user.nt_username = ""
- user.logoff_time = 2147483647
- user.acct_desc = ""
- user.group_rid = 1001
- user.logon_count = 0
- user.bad_password_count = 0
- user.domain = "BEDWYR"
- user.munged_dial = ""
- user.workstations = ""
- user.user_rid = 1000
- user.kickoff_time = 2147483647
- user.logoff_time = 2147483647
- user.unknown_6 = 1260L
- user.logon_divs = 0
- user.hours = [True for i in range(168)]
- other = self.samdb["root"]
- for name in other.__dict__:
- if other.__dict__[name] != user.__dict__[name]:
- print "%s: %r != %r" % (name, other.__dict__[name], user.__dict__[name])
- self.assertEquals(user, other)
+ aliaslist = self.pdb.search_aliases()
+ self.assertEquals(1, len(aliaslist))
+ self.assertEquals("Jelmers NT Group", aliaslist[0]['account_name'])
class WinsDatabaseTestCase(TestCase):
@@ -178,29 +189,6 @@ class WinsDatabaseTestCase(TestCase):
super(WinsDatabaseTestCase, self).tearDown()
-class SmbpasswdTestCase(TestCase):
-
- def setUp(self):
- super(SmbpasswdTestCase, self).setUp()
- self.samdb = SmbpasswdFile(os.path.join(DATADIR, "smbpasswd"))
-
- def test_length(self):
- self.assertEquals(3, len(self.samdb))
-
- def test_get_user(self):
- user = SAMUser("rootpw")
- user.lm_password = "552902031BEDE9EFAAD3B435B51404EE"
- user.nt_password = "878D8014606CDA29677A44EFA1353FC7"
- user.acct_ctrl = ACB_NORMAL
- user.pass_last_set_time = int(1125418267)
- user.uid = 0
- self.assertEquals(user, self.samdb["rootpw"])
-
- def tearDown(self):
- self.samdb.close()
- super(SmbpasswdTestCase, self).tearDown()
-
-
class IdmapDbTestCase(TestCase):
def setUp(self):
@@ -229,26 +217,3 @@ class IdmapDbTestCase(TestCase):
def tearDown(self):
self.idmapdb.close()
super(IdmapDbTestCase, self).tearDown()
-
-
-class ParamTestCase(TestCase):
-
- def test_init(self):
- file = ParamFile()
- self.assertTrue(file is not None)
-
- def test_add_section(self):
- file = ParamFile()
- file.add_section("global")
- self.assertTrue(file["global"] is not None)
-
- def test_set_param_string(self):
- file = ParamFile()
- file.add_section("global")
- file.set_string("data", "bar")
- self.assertEquals("bar", file.get_string("data"))
-
- def test_get_section(self):
- file = ParamFile()
- self.assertEquals(None, file.get_section("unknown"))
- self.assertRaises(KeyError, lambda: file["unknown"])
diff --git a/source4/scripting/python/samba/tests/samba3sam.py b/source4/scripting/python/samba/tests/samba3sam.py
index a34f0f620c..9c017fb79c 100644
--- a/source4/scripting/python/samba/tests/samba3sam.py
+++ b/source4/scripting/python/samba/tests/samba3sam.py
@@ -1,21 +1,19 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2005-2008
# Copyright (C) Martin Kuehl <mkhl@samba.org> 2006
#
# This is a Python port of the original in testprogs/ejs/samba3sam.js
-#
+#
# 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/>.
#
@@ -30,6 +28,7 @@ from samba.tests import TestCaseInTempDir, env_loadparm
import samba.dcerpc.security
import samba.ndr
from samba.auth import system_session
+from operator import attrgetter
def read_datafile(filename):
@@ -54,17 +53,16 @@ class MapBaseTestCase(TestCaseInTempDir):
"@TO": "sambaDomainName=TESTS," + s3.basedn})
ldb.add({"dn": "@MODULES",
- "@LIST": "rootdse,paged_results,server_sort,asq,samldb,password_hash,operational,objectguid,rdn_name,samba3sam,samba3sid,partition"})
+ "@LIST": "rootdse,paged_results,server_sort,asq,samldb,password_hash,operational,objectguid,rdn_name,samba3sam,samba3sid,show_deleted,partition"})
ldb.add({"dn": "@PARTITION",
- "partition": ["%s" % (s4.basedn_casefold),
+ "partition": ["%s" % (s4.basedn_casefold),
"%s" % (s3.basedn_casefold)],
"replicateEntries": ["@ATTRIBUTES", "@INDEXLIST"],
"modules": "*:"})
def setUp(self):
self.lp = env_loadparm()
- self.lp.set("sid generator", "backend")
self.lp.set("workgroup", "TESTS")
self.lp.set("netbios name", "TESTS")
super(MapBaseTestCase, self).setUp()
@@ -81,11 +79,12 @@ class MapBaseTestCase(TestCaseInTempDir):
tempdir = self.tempdir
class Target:
- """Simple helper class that contains data for a specific SAM
+ """Simple helper class that contains data for a specific SAM
connection."""
def __init__(self, basedn, dn, lp):
self.db = Ldb(lp=lp, session_info=system_session())
+ self.db.set_opaque("skip_allocate_sids", "true");
self.basedn = basedn
self.basedn_casefold = ldb.Dn(self.db, basedn).get_casefold()
self.substvars = {"BASEDN": self.basedn}
@@ -121,6 +120,11 @@ class MapBaseTestCase(TestCaseInTempDir):
os.unlink(self.ldbfile)
os.unlink(self.samba3.file)
os.unlink(self.samba4.file)
+ pdir = "%s.d" % self.ldbfile
+ mdata = os.path.join(pdir, "metadata.tdb")
+ if os.path.exists(mdata):
+ os.unlink(mdata)
+ os.rmdir(pdir)
super(MapBaseTestCase, self).tearDown()
def assertSidEquals(self, text, ndr_sid):
@@ -135,12 +139,14 @@ class Samba3SamTestCase(MapBaseTestCase):
def setUp(self):
super(Samba3SamTestCase, self).setUp()
ldb = Ldb(self.ldburl, lp=self.lp, session_info=system_session())
+ ldb.set_opaque("skip_allocate_sids", "true");
self.samba3.setup_data("samba3.ldif")
ldif = read_datafile("provision_samba3sam.ldif")
ldb.add_ldif(self.samba4.subst(ldif))
self.setup_modules(ldb, self.samba3, self.samba4)
del ldb
self.ldb = Ldb(self.ldburl, lp=self.lp, session_info=system_session())
+ self.ldb.set_opaque("skip_allocate_sids", "true");
def test_search_non_mapped(self):
"""Looking up by non-mapped attribute"""
@@ -163,9 +169,9 @@ class Samba3SamTestCase(MapBaseTestCase):
"""Looking up mapped entry containing SID"""
msg = self.ldb.search(expression="(cn=Replicator)")
self.assertEquals(len(msg), 1)
- self.assertEquals(str(msg[0].dn),
+ self.assertEquals(str(msg[0].dn),
"cn=Replicator,ou=Groups,dc=vernstok,dc=nl")
- self.assertTrue("objectSid" in msg[0])
+ self.assertTrue("objectSid" in msg[0])
self.assertSidEquals("S-1-5-21-4231626423-2410014848-2360679739-552",
msg[0]["objectSid"])
oc = set(msg[0]["objectClass"])
@@ -174,28 +180,28 @@ class Samba3SamTestCase(MapBaseTestCase):
def test_search_by_objclass(self):
"""Looking up by objectClass"""
msg = self.ldb.search(expression="(|(objectClass=user)(cn=Administrator))")
- self.assertEquals(set([str(m.dn) for m in msg]),
- set(["unixName=Administrator,ou=Users,dc=vernstok,dc=nl",
+ self.assertEquals(set([str(m.dn) for m in msg]),
+ set(["unixName=Administrator,ou=Users,dc=vernstok,dc=nl",
"unixName=nobody,ou=Users,dc=vernstok,dc=nl"]))
def test_s3sam_modify(self):
# Adding a record that will be fallbacked
- self.ldb.add({"dn": "cn=Foo",
- "foo": "bar",
- "blah": "Blie",
- "cn": "Foo",
+ self.ldb.add({"dn": "cn=Foo",
+ "foo": "bar",
+ "blah": "Blie",
+ "cn": "Foo",
"showInAdvancedViewOnly": "TRUE"}
)
# Checking for existence of record (local)
- # TODO: This record must be searched in the local database, which is
+ # TODO: This record must be searched in the local database, which is
# currently only supported for base searches
# msg = ldb.search(expression="(cn=Foo)", ['foo','blah','cn','showInAdvancedViewOnly')]
# TODO: Actually, this version should work as well but doesn't...
- #
- #
- msg = self.ldb.search(expression="(cn=Foo)", base="cn=Foo",
- scope=SCOPE_BASE,
+ #
+ #
+ msg = self.ldb.search(expression="(cn=Foo)", base="cn=Foo",
+ scope=SCOPE_BASE,
attrs=['foo','blah','cn','showInAdvancedViewOnly'])
self.assertEquals(len(msg), 1)
self.assertEquals(str(msg[0]["showInAdvancedViewOnly"]), "TRUE")
@@ -210,14 +216,14 @@ class Samba3SamTestCase(MapBaseTestCase):
"cn": "Niemand"})
# Checking for existence of record (remote)
- msg = self.ldb.search(expression="(unixName=bin)",
+ msg = self.ldb.search(expression="(unixName=bin)",
attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
self.assertEquals(len(msg), 1)
self.assertEquals(str(msg[0]["cn"]), "Niemand")
self.assertEquals(str(msg[0]["sambaUnicodePwd"]), "geheim")
# Checking for existence of record (local && remote)
- msg = self.ldb.search(expression="(&(unixName=bin)(sambaUnicodePwd=geheim))",
+ msg = self.ldb.search(expression="(&(unixName=bin)(sambaUnicodePwd=geheim))",
attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
self.assertEquals(len(msg), 1) # TODO: should check with more records
self.assertEquals(str(msg[0]["cn"]), "Niemand")
@@ -225,7 +231,7 @@ class Samba3SamTestCase(MapBaseTestCase):
self.assertEquals(str(msg[0]["sambaUnicodePwd"]), "geheim")
# Checking for existence of record (local || remote)
- msg = self.ldb.search(expression="(|(unixName=bin)(sambaUnicodePwd=geheim))",
+ msg = self.ldb.search(expression="(|(unixName=bin)(sambaUnicodePwd=geheim))",
attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
#print "got %d replies" % len(msg)
self.assertEquals(len(msg), 1) # TODO: should check with more records
@@ -236,7 +242,7 @@ class Samba3SamTestCase(MapBaseTestCase):
# Checking for data in destination database
msg = self.samba3.db.search(expression="(cn=Niemand)")
self.assertTrue(len(msg) >= 1)
- self.assertEquals(str(msg[0]["sambaSID"]),
+ self.assertEquals(str(msg[0]["sambaSID"]),
"S-1-5-21-4231626423-2410014848-2360679739-2001")
self.assertEquals(str(msg[0]["displayName"]), "Niemand")
@@ -280,13 +286,13 @@ delete: description
self.assertTrue(not "description" in msg[0])
# Renaming record...
- self.ldb.rename("cn=Niemand,cn=Users,dc=vernstok,dc=nl",
+ self.ldb.rename("cn=Niemand,cn=Users,dc=vernstok,dc=nl",
"cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
# Checking whether DN has changed...
msg = self.ldb.search(expression="(cn=Niemand2)")
self.assertEquals(len(msg), 1)
- self.assertEquals(str(msg[0].dn),
+ self.assertEquals(str(msg[0].dn),
"cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
# Deleting record...
@@ -302,11 +308,13 @@ class MapTestCase(MapBaseTestCase):
def setUp(self):
super(MapTestCase, self).setUp()
ldb = Ldb(self.ldburl, lp=self.lp, session_info=system_session())
+ ldb.set_opaque("skip_allocate_sids", "true");
ldif = read_datafile("provision_samba3sam.ldif")
ldb.add_ldif(self.samba4.subst(ldif))
self.setup_modules(ldb, self.samba3, self.samba4)
del ldb
self.ldb = Ldb(self.ldburl, lp=self.lp, session_info=system_session())
+ self.ldb.set_opaque("skip_allocate_sids", "true");
def test_map_search(self):
"""Running search tests on mapped data."""
@@ -369,7 +377,7 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
"objectClass": "posixAccount",
"cn": "A",
"sambaNextRid": "x",
- "sambaBadPasswordCount": "x",
+ "sambaBadPasswordCount": "x",
"sambaLogonTime": "x",
"description": "x",
"sambaSID": "S-1-5-21-4231626423-2410014848-2360679739-552",
@@ -397,7 +405,7 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
# Search remote record by local DN
dn = self.samba4.dn("cn=A")
- res = self.ldb.search(dn, scope=SCOPE_BASE,
+ res = self.ldb.search(dn, scope=SCOPE_BASE,
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn)
@@ -406,7 +414,7 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
# Search remote record by remote DN
dn = self.samba3.dn("cn=A")
- res = self.samba3.db.search(dn, scope=SCOPE_BASE,
+ res = self.samba3.db.search(dn, scope=SCOPE_BASE,
attrs=["dnsHostName", "lastLogon", "sambaLogonTime"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn)
@@ -416,7 +424,7 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
# Search split record by local DN
dn = self.samba4.dn("cn=X")
- res = self.ldb.search(dn, scope=SCOPE_BASE,
+ res = self.ldb.search(dn, scope=SCOPE_BASE,
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn)
@@ -425,7 +433,7 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
# Search split record by remote DN
dn = self.samba3.dn("cn=X")
- res = self.samba3.db.search(dn, scope=SCOPE_BASE,
+ res = self.samba3.db.search(dn, scope=SCOPE_BASE,
attrs=["dnsHostName", "lastLogon", "sambaLogonTime"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn)
@@ -436,37 +444,40 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
# Testing search by attribute
# Search by ignored attribute
- res = self.ldb.search(expression="(revision=x)", scope=SCOPE_DEFAULT,
+ res = self.ldb.search(expression="(revision=x)", scope=SCOPE_DEFAULT,
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 2)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
- self.assertEquals(str(res[0]["dnsHostName"]), "y")
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
- self.assertEquals(str(res[1]["dnsHostName"]), "x")
- self.assertEquals(str(res[1]["lastLogon"]), "x")
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(str(res[0]["dnsHostName"]), "x")
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "y")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
# Search by kept attribute
- res = self.ldb.search(expression="(description=y)",
+ res = self.ldb.search(expression="(description=y)",
scope=SCOPE_DEFAULT, attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 2)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[0]["dnsHostName"]), "z")
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[0])
self.assertEquals(str(res[0]["lastLogon"]), "z")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "z")
self.assertEquals(str(res[1]["lastLogon"]), "z")
# Search by renamed attribute
res = self.ldb.search(expression="(badPwdCount=x)", scope=SCOPE_DEFAULT,
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 2)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertTrue(not "dnsHostName" in res[0])
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
self.assertTrue(not "dnsHostName" in res[1])
- self.assertEquals(str(res[1]["lastLogon"]), "x")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
# Search by converted attribute
# TODO:
@@ -475,23 +486,24 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
#res = self.ldb.search("(objectSid=S-1-5-21-4231626423-2410014848-2360679739-552)", scope=SCOPE_DEFAULT, attrs)
res = self.ldb.search(expression="(objectSid=*)", base=None, scope=SCOPE_DEFAULT, attrs=["dnsHostName", "lastLogon", "objectSid"])
self.assertEquals(len(res), 4)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
- self.assertEquals(str(res[0]["dnsHostName"]), "x")
- self.assertEquals(str(res[0]["lastLogon"]), "x")
- self.assertSidEquals("S-1-5-21-4231626423-2410014848-2360679739-552",
- res[0]["objectSid"])
- self.assertTrue("objectSid" in res[0])
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
- self.assertTrue(not "dnsHostName" in res[1])
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "x")
self.assertEquals(str(res[1]["lastLogon"]), "x")
self.assertSidEquals("S-1-5-21-4231626423-2410014848-2360679739-552",
res[1]["objectSid"])
self.assertTrue("objectSid" in res[1])
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertSidEquals("S-1-5-21-4231626423-2410014848-2360679739-552",
+ res[0]["objectSid"])
+ self.assertTrue("objectSid" in res[0])
- # Search by generated attribute
+ # Search by generated attribute
# In most cases, this even works when the mapping is missing
# a `convert_operator' by enumerating the remote db.
- res = self.ldb.search(expression="(primaryGroupID=512)",
+ res = self.ldb.search(expression="(primaryGroupID=512)",
attrs=["dnsHostName", "lastLogon", "primaryGroupID"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
@@ -508,10 +520,10 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
# print obj + ": " + res[i][obj]
# }
# print "---"
- #
+ #
# Search by remote name of renamed attribute */
- res = self.ldb.search(expression="(sambaBadPasswordCount=*)",
+ res = self.ldb.search(expression="(sambaBadPasswordCount=*)",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 0)
@@ -519,12 +531,13 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
attrs = ["dnsHostName", "lastLogon", "objectClass"]
res = self.ldb.search(expression="(objectClass=user)", attrs=attrs)
self.assertEquals(len(res), 2)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
- self.assertEquals(str(res[0]["dnsHostName"]), "x")
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[0])
self.assertEquals(str(res[0]["lastLogon"]), "x")
self.assertEquals(str(res[0]["objectClass"][0]), "user")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
- self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "x")
self.assertEquals(str(res[1]["lastLogon"]), "x")
self.assertEquals(str(res[1]["objectClass"][0]), "user")
@@ -532,95 +545,87 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
res = self.ldb.search(expression="(|(objectClass=user)(badPwdCount=x))",
attrs=attrs)
self.assertEquals(len(res), 3)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertTrue(not "dnsHostName" in res[0])
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(set(res[0]["objectClass"]), set(["top"]))
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
- self.assertEquals(str(res[1]["dnsHostName"]), "x")
- self.assertEquals(str(res[1]["lastLogon"]), "x")
- self.assertEquals(str(res[1]["objectClass"][0]), "user")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
- self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(res[0]["objectClass"][0], "user")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+ self.assertEquals(set(res[1]["objectClass"]), set(["top"]))
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(str(res[2]["dnsHostName"]), "x")
self.assertEquals(str(res[2]["lastLogon"]), "x")
- self.assertEquals(res[2]["objectClass"][0], "user")
+ self.assertEquals(str(res[2]["objectClass"][0]), "user")
# Testing search by parse tree
# Search by conjunction of local attributes
- res = self.ldb.search(expression="(&(codePage=x)(revision=x))",
+ res = self.ldb.search(expression="(&(codePage=x)(revision=x))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 2)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
- self.assertEquals(str(res[0]["dnsHostName"]), "y")
- self.assertEquals(str(res[0]["lastLogon"]), "y")
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(str(res[0]["dnsHostName"]), "x")
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "y")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+
+ # Search by conjunction of remote attributes
+ res = self.ldb.search(expression="(&(lastLogon=x)(description=x))",
+ attrs=["dnsHostName", "lastLogon"])
+ self.assertEquals(len(res), 2)
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
self.assertEquals(str(res[1]["dnsHostName"]), "x")
self.assertEquals(str(res[1]["lastLogon"]), "x")
- # Search by conjunction of remote attributes
- res = self.ldb.search(expression="(&(lastLogon=x)(description=x))",
+ # Search by conjunction of local and remote attribute
+ res = self.ldb.search(expression="(&(codePage=x)(description=x))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 2)
+ res = sorted(res, key=attrgetter('dn'))
self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
self.assertEquals(str(res[0]["dnsHostName"]), "x")
self.assertEquals(str(res[0]["lastLogon"]), "x")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
- self.assertTrue(not "dnsHostName" in res[1])
- self.assertEquals(str(res[1]["lastLogon"]), "x")
-
- # Search by conjunction of local and remote attribute
- res = self.ldb.search(expression="(&(codePage=x)(description=x))",
- attrs=["dnsHostName", "lastLogon"])
- self.assertEquals(len(res), 2)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
- self.assertEquals(str(res[0]["dnsHostName"]), "y")
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
- self.assertEquals(str(res[1]["dnsHostName"]), "x")
- self.assertEquals(str(res[1]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "y")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
# Search by conjunction of local and remote attribute w/o match
attrs = ["dnsHostName", "lastLogon"]
- res = self.ldb.search(expression="(&(codePage=x)(nextRid=x))",
+ res = self.ldb.search(expression="(&(codePage=x)(nextRid=x))",
attrs=attrs)
self.assertEquals(len(res), 0)
- res = self.ldb.search(expression="(&(revision=x)(lastLogon=z))",
+ res = self.ldb.search(expression="(&(revision=x)(lastLogon=z))",
attrs=attrs)
self.assertEquals(len(res), 0)
# Search by disjunction of local attributes
- res = self.ldb.search(expression="(|(revision=x)(dnsHostName=x))",
+ res = self.ldb.search(expression="(|(revision=x)(dnsHostName=x))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 2)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
- self.assertEquals(str(res[0]["dnsHostName"]), "y")
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
- self.assertEquals(str(res[1]["dnsHostName"]), "x")
- self.assertEquals(str(res[1]["lastLogon"]), "x")
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(str(res[0]["dnsHostName"]), "x")
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "y")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
# Search by disjunction of remote attributes
- res = self.ldb.search(expression="(|(badPwdCount=x)(lastLogon=x))",
+ res = self.ldb.search(expression="(|(badPwdCount=x)(lastLogon=x))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 3)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertFalse("dnsHostName" in res[0])
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
- self.assertEquals(str(res[1]["dnsHostName"]), "x")
- self.assertEquals(str(res[1]["lastLogon"]), "x")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
- self.assertFalse("dnsHostName" in res[2])
- self.assertEquals(str(res[2]["lastLogon"]), "x")
-
- # Search by disjunction of local and remote attribute
- res = self.ldb.search(expression="(|(revision=x)(lastLogon=y))",
- attrs=["dnsHostName", "lastLogon"])
- self.assertEquals(len(res), 3)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
- self.assertEquals(str(res[0]["dnsHostName"]), "y")
- self.assertEquals(str(res[0]["lastLogon"]), "y")
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
self.assertFalse("dnsHostName" in res[1])
self.assertEquals(str(res[1]["lastLogon"]), "y")
@@ -628,151 +633,175 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
self.assertEquals(str(res[2]["dnsHostName"]), "x")
self.assertEquals(str(res[2]["lastLogon"]), "x")
+ # Search by disjunction of local and remote attribute
+ res = self.ldb.search(expression="(|(revision=x)(lastLogon=y))",
+ attrs=["dnsHostName", "lastLogon"])
+ self.assertEquals(len(res), 3)
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertFalse("dnsHostName" in res[0])
+ self.assertEquals(str(res[0]["lastLogon"]), "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "x")
+ self.assertEquals(str(res[1]["lastLogon"]), "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(str(res[2]["dnsHostName"]), "y")
+ self.assertEquals(str(res[2]["lastLogon"]), "y")
+
# Search by disjunction of local and remote attribute w/o match
- res = self.ldb.search(expression="(|(codePage=y)(nextRid=z))",
+ res = self.ldb.search(expression="(|(codePage=y)(nextRid=z))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 0)
# Search by negated local attribute
- res = self.ldb.search(expression="(!(revision=x))",
+ res = self.ldb.search(expression="(!(revision=x))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 6)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertTrue(not "dnsHostName" in res[0])
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
self.assertTrue(not "dnsHostName" in res[1])
- self.assertEquals(str(res[1]["lastLogon"]), "x")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[2]["dnsHostName"]), "z")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[2])
self.assertEquals(str(res[2]["lastLogon"]), "z")
- self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[3]["dnsHostName"]), "z")
self.assertEquals(str(res[3]["lastLogon"]), "z")
# Search by negated remote attribute
- res = self.ldb.search(expression="(!(description=x))",
+ res = self.ldb.search(expression="(!(description=x))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 4)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[0]["dnsHostName"]), "z")
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[0])
self.assertEquals(str(res[0]["lastLogon"]), "z")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "z")
self.assertEquals(str(res[1]["lastLogon"]), "z")
# Search by negated conjunction of local attributes
- res = self.ldb.search(expression="(!(&(codePage=x)(revision=x)))",
+ res = self.ldb.search(expression="(!(&(codePage=x)(revision=x)))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 6)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertTrue(not "dnsHostName" in res[0])
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
self.assertTrue(not "dnsHostName" in res[1])
- self.assertEquals(str(res[1]["lastLogon"]), "x")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[2]["dnsHostName"]), "z")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[2])
self.assertEquals(str(res[2]["lastLogon"]), "z")
- self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[3]["dnsHostName"]), "z")
self.assertEquals(str(res[3]["lastLogon"]), "z")
# Search by negated conjunction of remote attributes
- res = self.ldb.search(expression="(!(&(lastLogon=x)(description=x)))",
+ res = self.ldb.search(expression="(!(&(lastLogon=x)(description=x)))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 6)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
- self.assertEquals(str(res[0]["dnsHostName"]), "y")
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C"))
self.assertTrue(not "dnsHostName" in res[1])
- self.assertEquals(str(res[1]["lastLogon"]), "y")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[2]["dnsHostName"]), "z")
- self.assertEquals(str(res[2]["lastLogon"]), "z")
- self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(str(res[1]["lastLogon"]), "z")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(str(res[2]["dnsHostName"]), "y")
+ self.assertEquals(str(res[2]["lastLogon"]), "y")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[3]["dnsHostName"]), "z")
self.assertEquals(str(res[3]["lastLogon"]), "z")
# Search by negated conjunction of local and remote attribute
- res = self.ldb.search(expression="(!(&(codePage=x)(description=x)))",
+ res = self.ldb.search(expression="(!(&(codePage=x)(description=x)))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 6)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertTrue(not "dnsHostName" in res[0])
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
self.assertTrue(not "dnsHostName" in res[1])
- self.assertEquals(str(res[1]["lastLogon"]), "x")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[2]["dnsHostName"]), "z")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[2])
self.assertEquals(str(res[2]["lastLogon"]), "z")
- self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[3]["dnsHostName"]), "z")
self.assertEquals(str(res[3]["lastLogon"]), "z")
# Search by negated disjunction of local attributes
- res = self.ldb.search(expression="(!(|(revision=x)(dnsHostName=x)))",
+ res = self.ldb.search(expression="(!(|(revision=x)(dnsHostName=x)))",
attrs=["dnsHostName", "lastLogon"])
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertTrue(not "dnsHostName" in res[0])
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
self.assertTrue(not "dnsHostName" in res[1])
- self.assertEquals(str(res[1]["lastLogon"]), "x")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[2]["dnsHostName"]), "z")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[2])
self.assertEquals(str(res[2]["lastLogon"]), "z")
- self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[3]["dnsHostName"]), "z")
self.assertEquals(str(res[3]["lastLogon"]), "z")
# Search by negated disjunction of remote attributes
- res = self.ldb.search(expression="(!(|(badPwdCount=x)(lastLogon=x)))",
+ res = self.ldb.search(expression="(!(|(badPwdCount=x)(lastLogon=x)))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 5)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
- self.assertEquals(str(res[0]["dnsHostName"]), "y")
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[1]["dnsHostName"]), "z")
- self.assertEquals(str(res[1]["lastLogon"]), "z")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[2])
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(str(res[0]["lastLogon"]), "z")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(str(res[1]["dnsHostName"]), "y")
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[2]["dnsHostName"]), "z")
self.assertEquals(str(res[2]["lastLogon"]), "z")
# Search by negated disjunction of local and remote attribute
- res = self.ldb.search(expression="(!(|(revision=x)(lastLogon=y)))",
+ res = self.ldb.search(expression="(!(|(revision=x)(lastLogon=y)))",
attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 5)
+ res = sorted(res, key=attrgetter('dn'))
self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertTrue(not "dnsHostName" in res[0])
self.assertEquals(str(res[0]["lastLogon"]), "x")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[1]["dnsHostName"]), "z")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[1])
self.assertEquals(str(res[1]["lastLogon"]), "z")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[2]["dnsHostName"]), "z")
self.assertEquals(str(res[2]["lastLogon"]), "z")
# Search by complex parse tree
res = self.ldb.search(expression="(|(&(revision=x)(dnsHostName=x))(!(&(description=x)(nextRid=y)))(badPwdCount=y))", attrs=["dnsHostName", "lastLogon"])
self.assertEquals(len(res), 7)
- self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ res = sorted(res, key=attrgetter('dn'))
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
self.assertTrue(not "dnsHostName" in res[0])
- self.assertEquals(str(res[0]["lastLogon"]), "y")
- self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
- self.assertEquals(str(res[1]["dnsHostName"]), "x")
- self.assertEquals(str(res[1]["lastLogon"]), "x")
- self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
+ self.assertEquals(str(res[0]["lastLogon"]), "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(str(res[1]["lastLogon"]), "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
self.assertTrue(not "dnsHostName" in res[2])
- self.assertEquals(str(res[2]["lastLogon"]), "x")
- self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
- self.assertEquals(str(res[3]["dnsHostName"]), "z")
- self.assertEquals(str(res[3]["lastLogon"]), "z")
- self.assertEquals(str(res[4].dn), self.samba4.dn("cn=C"))
- self.assertTrue(not "dnsHostName" in res[4])
+ self.assertEquals(str(res[2]["lastLogon"]), "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(str(res[3]["dnsHostName"]), "x")
+ self.assertEquals(str(res[3]["lastLogon"]), "x")
+ self.assertEquals(str(res[4].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(str(res[4]["dnsHostName"]), "z")
self.assertEquals(str(res[4]["lastLogon"]), "z")
# Clean up
@@ -784,7 +813,7 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
"""Modification of local records."""
# Add local record
dn = "cn=test,dc=idealx,dc=org"
- self.ldb.add({"dn": dn,
+ self.ldb.add({"dn": dn,
"cn": "test",
"foo": "bar",
"revision": "1",
@@ -798,11 +827,11 @@ objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
self.assertEquals(str(res[0]["revision"]), "1")
self.assertEquals(str(res[0]["description"]), "test")
# Check it's not in the local db
- res = self.samba4.db.search(expression="(cn=test)",
+ res = self.samba4.db.search(expression="(cn=test)",
scope=SCOPE_DEFAULT, attrs=attrs)
self.assertEquals(len(res), 0)
# Check it's not in the remote db
- res = self.samba3.db.search(expression="(cn=test)",
+ res = self.samba3.db.search(expression="(cn=test)",
scope=SCOPE_DEFAULT, attrs=attrs)
self.assertEquals(len(res), 0)
@@ -845,13 +874,13 @@ description: foo
# Add remote record
dn = self.samba4.dn("cn=test")
dn2 = self.samba3.dn("cn=test")
- self.samba3.db.add({"dn": dn2,
+ self.samba3.db.add({"dn": dn2,
"cn": "test",
"description": "foo",
"sambaBadPasswordCount": "3",
"sambaNextRid": "1001"})
# Check it's there
- res = self.samba3.db.search(dn2, scope=SCOPE_BASE,
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE,
attrs=["description", "sambaBadPasswordCount", "sambaNextRid"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn2)
@@ -880,7 +909,7 @@ badPwdCount: 4
"""
self.ldb.modify_ldif(ldif)
# Check in mapped db
- res = self.ldb.search(dn, scope=SCOPE_BASE,
+ res = self.ldb.search(dn, scope=SCOPE_BASE,
attrs=["description", "badPwdCount", "nextRid"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn)
@@ -888,7 +917,7 @@ badPwdCount: 4
self.assertEquals(str(res[0]["badPwdCount"]), "4")
self.assertEquals(str(res[0]["nextRid"]), "1001")
# Check in remote db
- res = self.samba3.db.search(dn2, scope=SCOPE_BASE,
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE,
attrs=["description", "sambaBadPasswordCount", "sambaNextRid"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn2)
@@ -901,16 +930,16 @@ badPwdCount: 4
self.ldb.rename(dn, dn2)
# Check in mapped db
dn = dn2
- res = self.ldb.search(dn, scope=SCOPE_BASE,
+ res = self.ldb.search(dn, scope=SCOPE_BASE,
attrs=["description", "badPwdCount", "nextRid"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn)
self.assertEquals(str(res[0]["description"]), "test")
self.assertEquals(str(res[0]["badPwdCount"]), "4")
self.assertEquals(str(res[0]["nextRid"]), "1001")
- # Check in remote db
+ # Check in remote db
dn2 = self.samba3.dn("cn=toast")
- res = self.samba3.db.search(dn2, scope=SCOPE_BASE,
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE,
attrs=["description", "sambaBadPasswordCount", "sambaNextRid"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn2)
@@ -932,7 +961,7 @@ badPwdCount: 4
# Add remote record (same as before)
dn = self.samba4.dn("cn=test")
dn2 = self.samba3.dn("cn=test")
- self.samba3.db.add({"dn": dn2,
+ self.samba3.db.add({"dn": dn2,
"cn": "test",
"description": "foo",
"sambaBadPasswordCount": "3",
@@ -1001,7 +1030,7 @@ description: test
self.assertTrue(not "nextRid" in res[0])
self.assertEquals(str(res[0]["revision"]), "1")
# Check in remote db
- attrs = ["description", "sambaBadPasswordCount", "sambaNextRid",
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid",
"revision"]
res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
self.assertEquals(len(res), 1)
@@ -1040,7 +1069,7 @@ revision: 2
self.assertTrue(not "nextRid" in res[0])
self.assertEquals(str(res[0]["revision"]), "2")
# Check in remote db
- attrs = ["description", "sambaBadPasswordCount", "sambaNextRid",
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid",
"revision"]
res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
self.assertEquals(len(res), 1)
@@ -1073,8 +1102,8 @@ revision: 2
self.assertEquals(str(res[0]["revision"]), "2")
# Check in remote db
dn2 = self.samba3.dn("cn=toast")
- res = self.samba3.db.search(dn2, scope=SCOPE_BASE,
- attrs=["description", "sambaBadPasswordCount", "sambaNextRid",
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE,
+ attrs=["description", "sambaBadPasswordCount", "sambaNextRid",
"revision"])
self.assertEquals(len(res), 1)
self.assertEquals(str(res[0].dn), dn2)
diff --git a/source4/scripting/python/samba/netcmd/netacl.py b/source4/scripting/python/samba/tests/samba_tool/__init__.py
index 3f68ee756c..3d7f0591e2 100644
--- a/source4/scripting/python/samba/netcmd/netacl.py
+++ b/source4/scripting/python/samba/tests/samba_tool/__init__.py
@@ -1,9 +1,5 @@
-#!/usr/bin/env python
-#
-# Manipulate ACLs
-#
-# Copyright (C) Matthieu Patou <mat@matws.net> 2010
-# Copyright (C) Nadezhda Ivanova <nivanova@samba.org> 2010
+# Unix SMB/CIFS implementation.
+# Copyright (C) Sean Dague <sdague@linux.vnet.ibm.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
@@ -17,17 +13,3 @@
#
# 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 (
- SuperCommand,
- )
-from samba.netcmd.ntacl import cmd_nt_acl
-from samba.netcmd.dsacl import cmd_ds_acl
-
-class cmd_acl(SuperCommand):
- """NT ACLs manipulation"""
-
- subcommands = {}
- subcommands["nt"] = cmd_nt_acl()
- subcommands["ds"] = cmd_ds_acl()
diff --git a/source4/scripting/python/samba/tests/samba_tool/base.py b/source4/scripting/python/samba/tests/samba_tool/base.py
new file mode 100644
index 0000000000..96f0ddc710
--- /dev/null
+++ b/source4/scripting/python/samba/tests/samba_tool/base.py
@@ -0,0 +1,108 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Sean Dague <sdague@linux.vnet.ibm.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/>.
+
+# This provides a wrapper around the cmd interface so that tests can
+# easily be built on top of it and have minimal code to run basic tests
+# of the commands. A list of the environmental variables can be found in
+# ~/selftest/selftest.pl
+#
+# These can all be accesses via os.environ["VARIBLENAME"] when needed
+
+import random
+import string
+from samba.auth import system_session
+from samba.samdb import SamDB
+from cStringIO import StringIO
+from samba.netcmd.main import cmd_sambatool
+import samba.tests
+
+class SambaToolCmdTest(samba.tests.TestCaseInTempDir):
+
+ def getSamDB(self, *argv):
+ """a convenience function to get a samdb instance so that we can query it"""
+
+ # We build a fake command to get the options created the same
+ # way the command classes do it. It would be better if the command
+ # classes had a way to more cleanly do this, but this lets us write
+ # tests for now
+ cmd = cmd_sambatool.subcommands["user"].subcommands["setexpiry"]
+ parser, optiongroups = cmd._create_parser("user")
+ 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)
+
+ H = kwargs.get("H", None)
+ sambaopts = kwargs.get("sambaopts", None)
+ credopts = kwargs.get("credopts", 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)
+ return samdb
+
+
+ def runcmd(self, name, *args):
+ """run a single level command"""
+ cmd = cmd_sambatool.subcommands[name]
+ cmd.outf = StringIO()
+ cmd.errf = StringIO()
+ result = cmd._run(name, *args)
+ return (result, cmd.outf.getvalue(), cmd.errf.getvalue())
+
+ def runsubcmd(self, name, sub, *args):
+ """run a command with sub commands"""
+ # The reason we need this function seperate from runcmd is
+ # that the .outf StringIO assignment is overriden if we use
+ # runcmd, so we can't capture stdout and stderr
+ cmd = cmd_sambatool.subcommands[name].subcommands[sub]
+ cmd.outf = StringIO()
+ cmd.errf = StringIO()
+ result = cmd._run(name, *args)
+ return (result, cmd.outf.getvalue(), cmd.errf.getvalue())
+
+ def assertCmdSuccess(self, val, msg=""):
+ self.assertIsNone(val, msg)
+
+ def assertCmdFail(self, val, msg=""):
+ self.assertIsNotNone(val, msg)
+
+ def assertMatch(self, base, string, msg=""):
+ self.assertTrue(string in base, msg)
+
+ def randomName(self, count=8):
+ """Create a random name, cap letters and numbers, and always starting with a letter"""
+ name = random.choice(string.ascii_uppercase)
+ name += ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase+ string.digits) for x in range(count - 1))
+ return name
+
+ def randomPass(self, count=16):
+ name = random.choice(string.ascii_uppercase)
+ name += random.choice(string.digits)
+ name += random.choice(string.ascii_lowercase)
+ name += ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase+ string.digits) for x in range(count - 3))
+ return name
+
+ def assertWithin(self, val1, val2, delta, msg=""):
+ """Assert that val1 is within delta of val2, useful for time computations"""
+ self.assertTrue(((val1 + delta) > val2) and ((val1 - delta) < val2), msg)
diff --git a/source4/scripting/python/samba/tests/samba_tool/gpo.py b/source4/scripting/python/samba/tests/samba_tool/gpo.py
new file mode 100644
index 0000000000..e20a97794a
--- /dev/null
+++ b/source4/scripting/python/samba/tests/samba_tool/gpo.py
@@ -0,0 +1,79 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Bartlett 2012
+#
+# based on time.py:
+# Copyright (C) Sean Dague <sdague@linux.vnet.ibm.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 os
+from samba.tests.samba_tool.base import SambaToolCmdTest
+import shutil
+
+class GpoCmdTestCase(SambaToolCmdTest):
+ """Tests for samba-tool time subcommands"""
+
+ gpo_name = "testgpo"
+
+ def test_gpo_list(self):
+ """Run gpo list against the server and make sure it looks accurate"""
+ (result, out, err) = self.runsubcmd("gpo", "listall", "-H", "ldap://%s" % os.environ["SERVER"])
+ self.assertCmdSuccess(result, "Ensuring gpo listall ran successfully")
+
+ def test_fetchfail(self):
+ """Run against a non-existent GPO, and make sure it fails (this hard-coded UUID is very unlikely to exist"""
+ (result, out, err) = self.runsubcmd("gpo", "fetch", "c25cac17-a02a-4151-835d-fae17446ee43", "-H", "ldap://%s" % os.environ["SERVER"])
+ self.assertEquals(result, -1, "check for result code")
+
+ def test_fetch(self):
+ """Run against a real GPO, and make sure it passes"""
+ (result, out, err) = self.runsubcmd("gpo", "fetch", self.gpo_guid, "-H", "ldap://%s" % os.environ["SERVER"], "--tmpdir", self.tempdir)
+ self.assertCmdSuccess(result, "Ensuring gpo fetched successfully")
+ shutil.rmtree(os.path.join(self.tempdir, "policy"))
+
+ def test_show(self):
+ """Show a real GPO, and make sure it passes"""
+ (result, out, err) = self.runsubcmd("gpo", "show", self.gpo_guid, "-H", "ldap://%s" % os.environ["SERVER"])
+ self.assertCmdSuccess(result, "Ensuring gpo fetched successfully")
+
+ def test_show_as_admin(self):
+ """Show a real GPO, and make sure it passes"""
+ (result, out, err) = self.runsubcmd("gpo", "show", self.gpo_guid, "-H", "ldap://%s" % os.environ["SERVER"], "-U%s%%%s" % (os.environ["USERNAME"], os.environ["PASSWORD"]))
+ self.assertCmdSuccess(result, "Ensuring gpo fetched successfully")
+
+ def test_aclcheck(self):
+ """Check all the GPOs on the remote server have correct ACLs"""
+ (result, out, err) = self.runsubcmd("gpo", "aclcheck", "-H", "ldap://%s" % os.environ["SERVER"], "-U%s%%%s" % (os.environ["USERNAME"], os.environ["PASSWORD"]))
+ self.assertCmdSuccess(result, "Ensuring gpo checked successfully")
+
+ def setUp(self):
+ """set up a temporary GPO to work with"""
+ super(GpoCmdTestCase, self).setUp()
+ (result, out, err) = self.runsubcmd("gpo", "create", self.gpo_name,
+ "-H", "ldap://%s" % os.environ["SERVER"],
+ "-U%s%%%s" % (os.environ["USERNAME"], os.environ["PASSWORD"]),
+ "--tmpdir", self.tempdir)
+ shutil.rmtree(os.path.join(self.tempdir, "policy"))
+ self.assertCmdSuccess(result, "Ensuring gpo created successfully")
+ try:
+ self.gpo_guid = "{%s}" % out.split("{")[1].split("}")[0]
+ except IndexError:
+ self.fail("Failed to find GUID in output: %s" % out)
+
+ def tearDown(self):
+ """remove the temporary GPO to work with"""
+ (result, out, err) = self.runsubcmd("gpo", "del", self.gpo_guid, "-H", "ldap://%s" % os.environ["SERVER"], "-U%s%%%s" % (os.environ["USERNAME"], os.environ["PASSWORD"]))
+ self.assertCmdSuccess(result, "Ensuring gpo deleted successfully")
+ super(GpoCmdTestCase, self).tearDown()
diff --git a/source4/scripting/python/samba/tests/samba_tool/group.py b/source4/scripting/python/samba/tests/samba_tool/group.py
new file mode 100644
index 0000000000..2c0c46e5dc
--- /dev/null
+++ b/source4/scripting/python/samba/tests/samba_tool/group.py
@@ -0,0 +1,169 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Michael Adam 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 os
+import time
+import ldb
+from samba.tests.samba_tool.base import SambaToolCmdTest
+from samba import (
+ nttime2unix,
+ dsdb
+ )
+
+class GroupCmdTestCase(SambaToolCmdTest):
+ """Tests for samba-tool group subcommands"""
+ groups = []
+ samdb = None
+
+ def setUp(self):
+ super(GroupCmdTestCase, self).setUp()
+ self.samdb = self.getSamDB("-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
+ self.groups = []
+ self.groups.append(self._randomGroup({"name": "testgroup1"}))
+ self.groups.append(self._randomGroup({"name": "testgroup2"}))
+ self.groups.append(self._randomGroup({"name": "testgroup3"}))
+ self.groups.append(self._randomGroup({"name": "testgroup4"}))
+
+ # setup the 4 groups and ensure they are correct
+ for group in self.groups:
+ (result, out, err) = self._create_group(group)
+
+ self.assertCmdSuccess(result)
+ self.assertEquals(err, "", "There shouldn't be any error message")
+ self.assertIn("Added group %s" % group["name"], out)
+
+ found = self._find_group(group["name"])
+
+ self.assertIsNotNone(found)
+
+ self.assertEquals("%s" % found.get("name"), group["name"])
+ self.assertEquals("%s" % found.get("description"), group["description"])
+
+ def tearDown(self):
+ super(GroupCmdTestCase, self).tearDown()
+ # clean up all the left over groups, just in case
+ for group in self.groups:
+ if self._find_group(group["name"]):
+ self.runsubcmd("group", "delete", group["name"])
+
+
+ def test_newgroup(self):
+ """This tests the "group add" and "group delete" commands"""
+ # try to add all the groups again, this should fail
+ for group in self.groups:
+ (result, out, err) = self._create_group(group)
+ self.assertCmdFail(result, "Succeeded to create existing group")
+ self.assertIn("LDAP error 68 LDAP_ENTRY_ALREADY_EXISTS", err)
+
+ # try to delete all the groups we just added
+ for group in self.groups:
+ (result, out, err) = self.runsubcmd("group", "delete", group["name"])
+ self.assertCmdSuccess(result,
+ "Failed to delete group '%s'" % group["name"])
+ found = self._find_group(group["name"])
+ self.assertIsNone(found,
+ "Deleted group '%s' still exists" % group["name"])
+
+ # test adding groups
+ for group in self.groups:
+ (result, out, err) = self.runsubcmd("group", "add", group["name"],
+ "--description=%s" % group["description"],
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","There shouldn't be any error message")
+ self.assertIn("Added group %s" % group["name"], out)
+
+ found = self._find_group(group["name"])
+
+ self.assertEquals("%s" % found.get("samaccountname"),
+ "%s" % group["name"])
+
+
+ def test_list(self):
+ (result, out, err) = self.runsubcmd("group", "list",
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(result, "Error running list")
+
+ search_filter = "(objectClass=group)"
+
+ grouplist = self.samdb.search(base=self.samdb.domain_dn(),
+ scope=ldb.SCOPE_SUBTREE,
+ expression=search_filter,
+ attrs=["samaccountname"])
+
+ self.assertTrue(len(grouplist) > 0, "no groups found in samdb")
+
+ for groupobj in grouplist:
+ name = groupobj.get("samaccountname", idx=0)
+ found = self.assertMatch(out, name,
+ "group '%s' not found" % name)
+
+ def test_listmembers(self):
+ (result, out, err) = self.runsubcmd("group", "listmembers", "Domain Users",
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(result, "Error running listmembers")
+
+ search_filter = "(|(primaryGroupID=513)(memberOf=CN=Domain Users,CN=Users,%s))" % self.samdb.domain_dn()
+
+ grouplist = self.samdb.search(base=self.samdb.domain_dn(),
+ scope=ldb.SCOPE_SUBTREE,
+ expression=search_filter,
+ attrs=["samAccountName"])
+
+ self.assertTrue(len(grouplist) > 0, "no groups found in samdb")
+
+ for groupobj in grouplist:
+ name = groupobj.get("samAccountName", idx=0)
+ found = self.assertMatch(out, name, "group '%s' not found" % name)
+
+ def _randomGroup(self, base={}):
+ """create a group with random attribute values, you can specify base attributes"""
+ group = {
+ "name": self.randomName(),
+ "description": self.randomName(count=100),
+ }
+ group.update(base)
+ return group
+
+ def _create_group(self, group):
+ return self.runsubcmd("group", "add", group["name"],
+ "--description=%s" % group["description"],
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+
+ def _find_group(self, name):
+ search_filter = ("(&(sAMAccountName=%s)(objectCategory=%s,%s))" %
+ (ldb.binary_encode(name),
+ "CN=Group,CN=Schema,CN=Configuration",
+ self.samdb.domain_dn()))
+ grouplist = self.samdb.search(base=self.samdb.domain_dn(),
+ scope=ldb.SCOPE_SUBTREE,
+ expression=search_filter,
+ attrs=[])
+ if grouplist:
+ return grouplist[0]
+ else:
+ return None
diff --git a/source4/scripting/python/samba/tests/samba_tool/ntacl.py b/source4/scripting/python/samba/tests/samba_tool/ntacl.py
new file mode 100644
index 0000000000..2a329fe7d4
--- /dev/null
+++ b/source4/scripting/python/samba/tests/samba_tool/ntacl.py
@@ -0,0 +1,135 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Bartlett 2012
+#
+# Based on user.py:
+# Copyright (C) Sean Dague <sdague@linux.vnet.ibm.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 os
+import time
+import ldb
+from samba.tests.samba_tool.base import SambaToolCmdTest
+import random
+
+class NtACLCmdSysvolTestCase(SambaToolCmdTest):
+ """Tests for samba-tool ntacl sysvol* subcommands"""
+
+
+ def test_ntvfs(self):
+ (result, out, err) = self.runsubcmd("ntacl", "sysvolreset",
+ "--use-ntvfs")
+ self.assertCmdSuccess(result)
+ self.assertEquals(out,"","Shouldn't be any output messages")
+ self.assertIn("Please note that POSIX permissions have NOT been changed, only the stored NT ACL", err)
+
+ def test_s3fs(self):
+ (result, out, err) = self.runsubcmd("ntacl", "sysvolreset",
+ "--use-s3fs")
+
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertEquals(out,"","Shouldn't be any output messages")
+
+ def test_ntvfs_check(self):
+ (result, out, err) = self.runsubcmd("ntacl", "sysvolreset",
+ "--use-ntvfs")
+ self.assertCmdSuccess(result)
+ self.assertEquals(out,"","Shouldn't be any output messages")
+ self.assertIn("Please note that POSIX permissions have NOT been changed, only the stored NT ACL", err)
+ # Now check they were set correctly
+ (result, out, err) = self.runsubcmd("ntacl", "sysvolcheck")
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertEquals(out,"","Shouldn't be any output messages")
+
+ def test_s3fs_check(self):
+ (result, out, err) = self.runsubcmd("ntacl", "sysvolreset",
+ "--use-s3fs")
+
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertEquals(out,"","Shouldn't be any output messages")
+
+ # Now check they were set correctly
+ (result, out, err) = self.runsubcmd("ntacl", "sysvolcheck")
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertEquals(out,"","Shouldn't be any output messages")
+
+class NtACLCmdGetSetTestCase(SambaToolCmdTest):
+ """Tests for samba-tool ntacl get/set subcommands"""
+
+ acl = "O:DAG:DUD:P(A;OICI;0x001f01ff;;;DA)(A;OICI;0x001f01ff;;;EA)(A;OICIIO;0x001f01ff;;;CO)(A;OICI;0x001f01ff;;;DA)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)(A;OICI;0x001200a9;;;ED)S:AI(OU;CIIDSA;WP;f30e3bbe-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)(OU;CIIDSA;WP;f30e3bbf-9ff0-11d1-b603-0000f80367c1;bf967aa5-0de6-11d0-a285-00aa003049e2;WD)"
+
+
+ def test_ntvfs(self):
+ path = os.environ['SELFTEST_PREFIX']
+ tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
+ open(tempf, 'w').write("empty")
+
+ (result, out, err) = self.runsubcmd("ntacl", "set", self.acl, tempf,
+ "--use-ntvfs")
+ self.assertCmdSuccess(result)
+ self.assertEquals(out,"","Shouldn't be any output messages")
+ self.assertIn("Please note that POSIX permissions have NOT been changed, only the stored NT ACL", err)
+
+ def test_s3fs(self):
+ path = os.environ['SELFTEST_PREFIX']
+ tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
+ open(tempf, 'w').write("empty")
+
+ (result, out, err) = self.runsubcmd("ntacl", "set", self.acl, tempf,
+ "--use-s3fs")
+
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertEquals(out,"","Shouldn't be any output messages")
+
+ def test_ntvfs_check(self):
+ path = os.environ['SELFTEST_PREFIX']
+ tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
+ open(tempf, 'w').write("empty")
+
+ (result, out, err) = self.runsubcmd("ntacl", "set", self.acl, tempf,
+ "--use-ntvfs")
+ self.assertCmdSuccess(result)
+ self.assertEquals(out,"","Shouldn't be any output messages")
+ self.assertIn("Please note that POSIX permissions have NOT been changed, only the stored NT ACL", err)
+
+ # Now check they were set correctly
+ (result, out, err) = self.runsubcmd("ntacl", "get", tempf,
+ "--use-ntvfs", "--as-sddl")
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertEquals(self.acl+"\n", out, "Output should be the ACL")
+
+ def test_s3fs_check(self):
+ path = os.environ['SELFTEST_PREFIX']
+ tempf = os.path.join(path,"pytests"+str(int(100000*random.random())))
+ open(tempf, 'w').write("empty")
+
+ (result, out, err) = self.runsubcmd("ntacl", "set", self.acl, tempf,
+ "--use-s3fs")
+ self.assertCmdSuccess(result)
+ self.assertEquals(out,"","Shouldn't be any output messages")
+ self.assertEquals(err,"","Shouldn't be any error messages")
+
+ # Now check they were set correctly
+ (result, out, err) = self.runsubcmd("ntacl", "get", tempf,
+ "--use-s3fs", "--as-sddl")
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertEquals(self.acl+"\n", out,"Output should be the ACL")
diff --git a/source4/scripting/python/samba/tests/samba_tool/processes.py b/source4/scripting/python/samba/tests/samba_tool/processes.py
new file mode 100644
index 0000000000..91a5266b54
--- /dev/null
+++ b/source4/scripting/python/samba/tests/samba_tool/processes.py
@@ -0,0 +1,35 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Bartlett 2012
+#
+# based on time.py:
+# Copyright (C) Sean Dague <sdague@linux.vnet.ibm.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 os
+from samba.tests.samba_tool.base import SambaToolCmdTest
+
+class ProcessCmdTestCase(SambaToolCmdTest):
+ """Tests for samba-tool process subcommands"""
+
+ def test_name(self):
+ """Run processes command"""
+ (result, out, err) = self.runcmd("processes", "--name", "samba")
+ self.assertCmdSuccess(result, "Ensuring processes ran successfully")
+
+ def test_all(self):
+ """Run processes command"""
+ (result, out, err) = self.runcmd("processes")
+ self.assertCmdSuccess(result, "Ensuring processes ran successfully")
diff --git a/source4/scripting/python/samba/tests/samba_tool/timecmd.py b/source4/scripting/python/samba/tests/samba_tool/timecmd.py
new file mode 100644
index 0000000000..000f0f2828
--- /dev/null
+++ b/source4/scripting/python/samba/tests/samba_tool/timecmd.py
@@ -0,0 +1,43 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Sean Dague <sdague@linux.vnet.ibm.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 os
+from time import localtime, strptime, mktime
+from samba.tests.samba_tool.base import SambaToolCmdTest
+
+class TimeCmdTestCase(SambaToolCmdTest):
+ """Tests for samba-tool time subcommands"""
+
+ def test_timeget(self):
+ """Run time against the server and make sure it looks accurate"""
+ (result, out, err) = self.runcmd("time", os.environ["SERVER"])
+ self.assertCmdSuccess(result, "Ensuring time ran successfully")
+
+ timefmt = strptime(out, "%a %b %d %H:%M:%S %Y %Z\n")
+ servertime = int(mktime(timefmt))
+ now = int(mktime(localtime()))
+
+ # because there is a race here, allow up to 5 seconds difference in times
+ delta = 5
+ self.assertTrue((servertime > (now - delta) and (servertime < (now + delta)), "Time is now"))
+
+ def test_timefail(self):
+ """Run time against a non-existent server, and make sure it fails"""
+ (result, out, err) = self.runcmd("time", "notaserver")
+ self.assertEquals(result, -1, "check for result code")
+ self.assertTrue(err.strip().endswith("NT_STATUS_OBJECT_NAME_NOT_FOUND"), "ensure right error string")
+ self.assertEquals(out, "", "ensure no output returned")
diff --git a/source4/scripting/python/samba/tests/samba_tool/user.py b/source4/scripting/python/samba/tests/samba_tool/user.py
new file mode 100644
index 0000000000..13ef5d0d03
--- /dev/null
+++ b/source4/scripting/python/samba/tests/samba_tool/user.py
@@ -0,0 +1,237 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Sean Dague <sdague@linux.vnet.ibm.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 os
+import time
+import ldb
+from samba.tests.samba_tool.base import SambaToolCmdTest
+from samba import (
+ nttime2unix,
+ dsdb
+ )
+
+class UserCmdTestCase(SambaToolCmdTest):
+ """Tests for samba-tool user subcommands"""
+ users = []
+ samdb = None
+
+ def setUp(self):
+ super(UserCmdTestCase, self).setUp()
+ self.samdb = self.getSamDB("-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
+ self.users = []
+ self.users.append(self._randomUser({"name": "sambatool1", "company": "comp1"}))
+ self.users.append(self._randomUser({"name": "sambatool2", "company": "comp1"}))
+ self.users.append(self._randomUser({"name": "sambatool3", "company": "comp2"}))
+ self.users.append(self._randomUser({"name": "sambatool4", "company": "comp2"}))
+
+ # setup the 4 users and ensure they are correct
+ for user in self.users:
+ (result, out, err) = self._create_user(user)
+
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertIn("User '%s' created successfully" % user["name"], out)
+
+ found = self._find_user(user["name"])
+
+ self.assertEquals("%s" % found.get("name"), "%(given-name)s %(surname)s" % user)
+ self.assertEquals("%s" % found.get("title"), user["job-title"])
+ self.assertEquals("%s" % found.get("company"), user["company"])
+ self.assertEquals("%s" % found.get("description"), user["description"])
+ self.assertEquals("%s" % found.get("department"), user["department"])
+
+ def tearDown(self):
+ super(UserCmdTestCase, self).tearDown()
+ # clean up all the left over users, just in case
+ for user in self.users:
+ if self._find_user(user["name"]):
+ self.runsubcmd("user", "delete", user["name"])
+
+
+ def test_newuser(self):
+ # try to add all the users again, this should fail
+ for user in self.users:
+ (result, out, err) = self._create_user(user)
+ self.assertCmdFail(result, "Ensure that create user fails")
+ self.assertIn("LDAP error 68 LDAP_ENTRY_ALREADY_EXISTS", err)
+
+ # try to delete all the 4 users we just added
+ for user in self.users:
+ (result, out, err) = self.runsubcmd("user", "delete", user["name"])
+ self.assertCmdSuccess(result, "Can we delete users")
+ found = self._find_user(user["name"])
+ self.assertIsNone(found)
+
+ # test adding users with --use-username-as-cn
+ for user in self.users:
+ (result, out, err) = self.runsubcmd("user", "add", user["name"], user["password"],
+ "--use-username-as-cn",
+ "--surname=%s" % user["surname"],
+ "--given-name=%s" % user["given-name"],
+ "--job-title=%s" % user["job-title"],
+ "--department=%s" % user["department"],
+ "--description=%s" % user["description"],
+ "--company=%s" % user["company"],
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
+
+ self.assertCmdSuccess(result)
+ self.assertEquals(err,"","Shouldn't be any error messages")
+ self.assertIn("User '%s' created successfully" % user["name"], out)
+
+ found = self._find_user(user["name"])
+
+ self.assertEquals("%s" % found.get("cn"), "%(name)s" % user)
+ self.assertEquals("%s" % found.get("name"), "%(name)s" % user)
+
+
+
+ def test_setpassword(self):
+ for user in self.users:
+ newpasswd = self.randomPass()
+ (result, out, err) = self.runsubcmd("user", "setpassword",
+ user["name"],
+ "--newpassword=%s" % newpasswd,
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
+ # self.assertCmdSuccess(result, "Ensure setpassword runs")
+ self.assertEquals(err,"","setpassword with url")
+ self.assertMatch(out, "Changed password OK", "setpassword with url")
+
+ for user in self.users:
+ newpasswd = self.randomPass()
+ (result, out, err) = self.runsubcmd("user", "setpassword",
+ user["name"],
+ "--newpassword=%s" % newpasswd)
+ # self.assertCmdSuccess(result, "Ensure setpassword runs")
+ self.assertEquals(err,"","setpassword without url")
+ self.assertMatch(out, "Changed password OK", "setpassword without url")
+
+ for user in self.users:
+ newpasswd = self.randomPass()
+ (result, out, err) = self.runsubcmd("user", "setpassword",
+ user["name"],
+ "--newpassword=%s" % newpasswd,
+ "--must-change-at-next-login",
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
+ # self.assertCmdSuccess(result, "Ensure setpassword runs")
+ self.assertEquals(err,"","setpassword with forced change")
+ self.assertMatch(out, "Changed password OK", "setpassword with forced change")
+
+
+
+
+ def test_setexpiry(self):
+ twodays = time.time() + (2 * 24 * 60 * 60)
+
+ for user in self.users:
+ (result, out, err) = self.runsubcmd("user", "setexpiry", user["name"],
+ "--days=2",
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(result, "Can we run setexpiry with names")
+ self.assertIn("Expiry for user '%s' set to 2 days." % user["name"], out)
+
+ for user in self.users:
+ found = self._find_user(user["name"])
+
+ expires = nttime2unix(int("%s" % found.get("accountExpires")))
+ self.assertWithin(expires, twodays, 5, "Ensure account expires is within 5 seconds of the expected time")
+
+ # TODO: renable this after the filter case is sorted out
+ if "filters are broken, bail now":
+ return
+
+ # now run the expiration based on a filter
+ fourdays = time.time() + (4 * 24 * 60 * 60)
+ (result, out, err) = self.runsubcmd("user", "setexpiry",
+ "--filter", "(&(objectClass=user)(company=comp2))",
+ "--days=4",
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(result, "Can we run setexpiry with a filter")
+
+ for user in self.users:
+ found = self._find_user(user["name"])
+ if ("%s" % found.get("company")) == "comp2":
+ expires = nttime2unix(int("%s" % found.get("accountExpires")))
+ self.assertWithin(expires, fourdays, 5, "Ensure account expires is within 5 seconds of the expected time")
+ else:
+ expires = nttime2unix(int("%s" % found.get("accountExpires")))
+ self.assertWithin(expires, twodays, 5, "Ensure account expires is within 5 seconds of the expected time")
+
+
+ def test_list(self):
+ (result, out, err) = self.runsubcmd("user", "list",
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"],
+ os.environ["DC_PASSWORD"]))
+ self.assertCmdSuccess(result, "Error running list")
+
+ search_filter = ("(&(objectClass=user)(userAccountControl:%s:=%u))" %
+ (ldb.OID_COMPARATOR_AND, dsdb.UF_NORMAL_ACCOUNT))
+
+ userlist = self.samdb.search(base=self.samdb.domain_dn(),
+ scope=ldb.SCOPE_SUBTREE,
+ expression=search_filter,
+ attrs=["samaccountname"])
+
+ self.assertTrue(len(userlist) > 0, "no users found in samdb")
+
+ for userobj in userlist:
+ name = userobj.get("samaccountname", idx=0)
+ found = self.assertMatch(out, name,
+ "user '%s' not found" % name)
+
+
+ def _randomUser(self, base={}):
+ """create a user with random attribute values, you can specify base attributes"""
+ user = {
+ "name": self.randomName(),
+ "password": self.randomPass(),
+ "surname": self.randomName(),
+ "given-name": self.randomName(),
+ "job-title": self.randomName(),
+ "department": self.randomName(),
+ "company": self.randomName(),
+ "description": self.randomName(count=100),
+ }
+ user.update(base)
+ return user
+
+ def _create_user(self, user):
+ return self.runsubcmd("user", "add", user["name"], user["password"],
+ "--surname=%s" % user["surname"],
+ "--given-name=%s" % user["given-name"],
+ "--job-title=%s" % user["job-title"],
+ "--department=%s" % user["department"],
+ "--description=%s" % user["description"],
+ "--company=%s" % user["company"],
+ "-H", "ldap://%s" % os.environ["DC_SERVER"],
+ "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
+
+ def _find_user(self, name):
+ search_filter = "(&(sAMAccountName=%s)(objectCategory=%s,%s))" % (ldb.binary_encode(name), "CN=Person,CN=Schema,CN=Configuration", self.samdb.domain_dn())
+ userlist = self.samdb.search(base=self.samdb.domain_dn(),
+ scope=ldb.SCOPE_SUBTREE,
+ expression=search_filter, attrs=[])
+ if userlist:
+ return userlist[0]
+ else:
+ return None
diff --git a/source4/scripting/python/samba/tests/samdb.py b/source4/scripting/python/samba/tests/samdb.py
index 3df72b0840..5c80391cba 100644
--- a/source4/scripting/python/samba/tests/samdb.py
+++ b/source4/scripting/python/samba/tests/samdb.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation. Tests for SamDB
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
#
@@ -53,24 +51,24 @@ class SamDBTestCase(TestCaseInTempDir):
domainsid = security.random_sid()
path = os.path.join(self.tempdir, "samdb.ldb")
session_info = system_session()
-
+
hostname="foo"
domain="EXAMPLE"
- dnsdomain="example.com"
+ dnsdomain="example.com"
serverrole="domain controller"
policyguid_dc = DEFAULT_DC_POLICY_GUID
smbconf = os.path.join(self.tempdir, "smb.conf")
make_smbconf(smbconf, hostname, domain, dnsdomain,
- serverrole, self.tempdir)
+ self.tempdir, serverrole=serverrole)
self.lp = param.LoadParm()
self.lp.load(smbconf)
- names = guess_names(lp=self.lp, hostname=hostname,
- domain=domain, dnsdomain=dnsdomain,
- serverrole=serverrole,
- domaindn=self.domaindn, configdn=configdn,
+ names = guess_names(lp=self.lp, hostname=hostname,
+ domain=domain, dnsdomain=dnsdomain,
+ serverrole=serverrole,
+ domaindn=self.domaindn, configdn=configdn,
schemadn=schemadn)
paths = provision_paths_from_lp(self.lp, names.dnsdomain)
diff --git a/source4/scripting/python/samba/tests/security.py b/source4/scripting/python/samba/tests/security.py
index 59e3113068..d2938aacb0 100644
--- a/source4/scripting/python/samba/tests/security.py
+++ b/source4/scripting/python/samba/tests/security.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
#
diff --git a/source4/scripting/python/samba/tests/source.py b/source4/scripting/python/samba/tests/source.py
new file mode 100644
index 0000000000..2612ae68cf
--- /dev/null
+++ b/source4/scripting/python/samba/tests/source.py
@@ -0,0 +1,264 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2011
+#
+# Loosely based on bzrlib's test_source.py
+#
+# 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/>.
+#
+
+"""Source level Python tests."""
+
+import errno
+import os
+import re
+import warnings
+
+import samba
+samba.ensure_external_module("pep8", "pep8")
+import pep8
+
+from samba.tests import (
+ TestCase,
+ )
+
+
+def get_python_source_files():
+ """Iterate over all Python source files."""
+ library_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "samba"))
+ assert os.path.isdir(library_dir), library_dir
+
+ for root, dirs, files in os.walk(library_dir):
+ for f in files:
+ if f.endswith(".py"):
+ yield os.path.abspath(os.path.join(root, f))
+
+ bindir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "bin"))
+ assert os.path.isdir(bindir), bindir
+ for f in os.listdir(bindir):
+ p = os.path.abspath(os.path.join(bindir, f))
+ if not os.path.islink(p):
+ continue
+ target = os.readlink(p)
+ if os.path.dirname(target).endswith("scripting/bin"):
+ yield p
+ wafsambadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "buildtools", "wafsamba"))
+ assert os.path.isdir(wafsambadir), wafsambadir
+ for root, dirs, files in os.walk(wafsambadir):
+ for f in files:
+ if f.endswith(".py"):
+ yield os.path.abspath(os.path.join(root, f))
+
+
+def get_source_file_contents():
+ """Iterate over the contents of all python files."""
+ for fname in get_python_source_files():
+ try:
+ f = open(fname, 'rb')
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ warnings.warn("source file %s broken link?" % fname)
+ continue
+ else:
+ raise
+ try:
+ text = f.read()
+ finally:
+ f.close()
+ yield fname, text
+
+
+class TestSource(TestCase):
+
+ def test_copyright(self):
+ """Test that all Python files have a valid copyright statement."""
+ incorrect = []
+
+ copyright_re = re.compile('#\\s*copyright.*(?=\n)', re.I)
+
+ for fname, text in get_source_file_contents():
+ if fname.endswith("ms_schema.py"):
+ # FIXME: Not sure who holds copyright on ms_schema.py
+ continue
+ if "wafsamba" in fname:
+ # FIXME: No copyright headers in wafsamba
+ continue
+ match = copyright_re.search(text)
+ if not match:
+ incorrect.append((fname, 'no copyright line found\n'))
+
+ if incorrect:
+ help_text = ["Some files have missing or incorrect copyright"
+ " statements.",
+ "",
+ ]
+ for fname, comment in incorrect:
+ help_text.append(fname)
+ help_text.append((' ' * 4) + comment)
+
+ self.fail('\n'.join(help_text))
+
+ def test_gpl(self):
+ """Test that all .py files have a GPL disclaimer."""
+ incorrect = []
+
+ gpl_txt = """
+# 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/>.
+"""
+ gpl_re = re.compile(re.escape(gpl_txt), re.MULTILINE)
+
+ for fname, text in get_source_file_contents():
+ if "wafsamba" in fname:
+ # FIXME: License to wafsamba hasn't been clarified yet
+ continue
+ if not gpl_re.search(text):
+ incorrect.append(fname)
+
+ if incorrect:
+ help_text = ['Some files have missing or incomplete GPL statement',
+ gpl_txt]
+ for fname in incorrect:
+ help_text.append((' ' * 4) + fname)
+
+ self.fail('\n'.join(help_text))
+
+ def _push_file(self, dict_, fname, line_no):
+ if fname not in dict_:
+ dict_[fname] = [line_no]
+ else:
+ dict_[fname].append(line_no)
+
+ def _format_message(self, dict_, message):
+ files = ["%s: %s" % (f, ', '.join([str(i + 1) for i in lines]))
+ for f, lines in dict_.items()]
+ files.sort()
+ return message + '\n\n %s' % ('\n '.join(files))
+
+ def _iter_source_files_lines(self):
+ for fname, text in get_source_file_contents():
+ lines = text.splitlines(True)
+ last_line_no = len(lines) - 1
+ for line_no, line in enumerate(lines):
+ yield fname, line_no, line
+
+ def test_no_tabs(self):
+ """Check that there are no tabs in Python files."""
+ tabs = {}
+ for fname, line_no, line in self._iter_source_files_lines():
+ if '\t' in line:
+ self._push_file(tabs, fname, line_no)
+ if tabs:
+ self.fail(self._format_message(tabs,
+ 'Tab characters were found in the following source files.'
+ '\nThey should either be replaced by "\\t" or by spaces:'))
+
+ def test_unix_newlines(self):
+ """Check for unix new lines."""
+ illegal_newlines = {}
+ for fname, line_no, line in self._iter_source_files_lines():
+ if not line.endswith('\n') or line.endswith('\r\n'):
+ self._push_file(illegal_newlines, fname, line_no)
+ if illegal_newlines:
+ self.fail(self._format_message(illegal_newlines,
+ 'Non-unix newlines were found in the following source files:'))
+
+ def test_trailing_whitespace(self):
+ """Check that there is not trailing whitespace in Python files."""
+ trailing_whitespace = {}
+ for fname, line_no, line in self._iter_source_files_lines():
+ if line.rstrip("\n").endswith(" "):
+ self._push_file(trailing_whitespace, fname, line_no)
+ if trailing_whitespace:
+ self.fail(self._format_message(trailing_whitespace,
+ 'Trailing whitespace was found in the following source files.'))
+
+ def test_shebang_lines(self):
+ """Check that files with shebang lines and only those are executable."""
+ files_with_shebang = {}
+ files_without_shebang= {}
+ for fname, line_no, line in self._iter_source_files_lines():
+ if line_no >= 1:
+ continue
+ executable = (os.stat(fname).st_mode & 0111)
+ has_shebang = line.startswith("#!")
+ if has_shebang and not executable:
+ self._push_file(files_with_shebang, fname, line_no)
+ if not has_shebang and executable:
+ self._push_file(files_without_shebang, fname, line_no)
+ if files_with_shebang:
+ self.fail(self._format_message(files_with_shebang,
+ 'Files with shebang line that are not executable:'))
+ if files_without_shebang:
+ self.fail(self._format_message(files_without_shebang,
+ 'Files without shebang line that are executable:'))
+
+ pep8_ignore = [
+ 'E401', # multiple imports on one line
+ 'E501', # line too long
+ 'E251', # no spaces around keyword / parameter equals
+ 'E201', # whitespace after '['
+ 'E202', # whitespace before ')'
+ 'E302', # expected 2 blank lines, found 1
+ 'E231', # missing whitespace after ','
+ 'E225', # missing whitespace around operator
+ 'E111', # indentation is not a multiple of four
+ 'E261', # at least two spaces before inline comment
+ 'E702', # multiple statements on one line (semicolon)
+ 'E221', # multiple spaces before operator
+ 'E303', # too many blank lines (2)
+ 'E203', # whitespace before ':'
+ 'E222', # multiple spaces after operator
+ 'E301', # expected 1 blank line, found 0
+ 'E211', # whitespace before '('
+ 'E701', # multiple statements on one line (colon)
+ ]
+
+ def test_pep8(self):
+ pep8.process_options()
+ pep8.options.repeat = True
+ pep8_errors = []
+ pep8_warnings = []
+ for fname, text in get_source_file_contents():
+ def report_error(line_number, offset, text, check):
+ code = text[:4]
+ if code in self.pep8_ignore:
+ code = 'W' + code[1:]
+ text = code + text[4:]
+ print "%s:%s: %s" % (fname, line_number, text)
+ summary = (fname, line_number, offset, text, check)
+ if code[0] == 'W':
+ pep8_warnings.append(summary)
+ else:
+ pep8_errors.append(summary)
+ lines = text.splitlines(True)
+ checker = pep8.Checker(fname, lines)
+ checker.report_error = report_error
+ checker.check_all()
+ if len(pep8_errors) > 0:
+ d = {}
+ for (fname, line_no, offset, text, check) in pep8_errors:
+ d.setdefault(fname, []).append(line_no - 1)
+ self.fail(self._format_message(d,
+ 'There were %d PEP8 errors:' % len(pep8_errors)))
+
diff --git a/source4/scripting/python/samba/tests/strings.py b/source4/scripting/python/samba/tests/strings.py
new file mode 100644
index 0000000000..23382d756e
--- /dev/null
+++ b/source4/scripting/python/samba/tests/strings.py
@@ -0,0 +1,103 @@
+# subunit test cases for Samba string functions.
+
+# Copyright (C) 2003 by Martin Pool <mbp@samba.org>
+# Copyright (C) 2011 Andrew Bartlett
+#
+# 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/>.
+#
+
+# XXX: All this code assumes that the Unix character set is UTF-8,
+# which is the most common setting. I guess it would be better to
+# force it to that value while running the tests. I'm not sure of the
+# best way to do that yet.
+#
+# -- mbp
+
+from unicodenames import *
+
+import samba.tests
+from samba import strcasecmp_m, strstr_m
+
+def signum(a):
+ if a < 0:
+ return -1
+ elif a > 0:
+ return +1
+ else:
+ return 0
+
+
+class strcasecmp_m_Tests(samba.tests.TestCase):
+ """String comparisons in simple ASCII and unicode"""
+ def test_strcasecmp_m(self):
+ # A, B, strcasecmp(A, B)
+ cases = [('hello', 'hello', 0),
+ ('hello', 'goodbye', +1),
+ ('goodbye', 'hello', -1),
+ ('hell', 'hello', -1),
+ ('', '', 0),
+ ('a', '', +1),
+ ('', 'a', -1),
+ ('a', 'A', 0),
+ ('aa', 'aA', 0),
+ ('Aa', 'aa', 0),
+ ('longstring ' * 100, 'longstring ' * 100, 0),
+ ('longstring ' * 100, 'longstring ' * 100 + 'a', -1),
+ ('longstring ' * 100 + 'a', 'longstring ' * 100, +1),
+ (KATAKANA_LETTER_A, KATAKANA_LETTER_A, 0),
+ (KATAKANA_LETTER_A, 'a', 1),
+ ]
+ for a, b, expect in cases:
+ self.assertEquals(signum(strcasecmp_m(a.encode('utf-8'),
+ b.encode('utf-8'))),
+ expect)
+
+class strstr_m_Tests(samba.tests.TestCase):
+ """strstr_m tests in simple ASCII and unicode strings"""
+
+ def test_strstr_m(self):
+ # A, B, strstr_m(A, B)
+ cases = [('hello', 'hello', 'hello'),
+ ('hello', 'goodbye', None),
+ ('goodbye', 'hello', None),
+ ('hell', 'hello', None),
+ ('hello', 'hell', 'hello'),
+ ('', '', ''),
+ ('a', '', 'a'),
+ ('', 'a', None),
+ ('a', 'A', None),
+ ('aa', 'aA', None),
+ ('Aa', 'aa', None),
+ ('%v foo', '%v', '%v foo'),
+ ('foo %v foo', '%v', '%v foo'),
+ ('foo %v', '%v', '%v'),
+ ('longstring ' * 100, 'longstring ' * 99, 'longstring ' * 100),
+ ('longstring ' * 99, 'longstring ' * 100, None),
+ ('longstring a' * 99, 'longstring ' * 100 + 'a', None),
+ ('longstring ' * 100 + 'a', 'longstring ' * 100, 'longstring ' * 100 + 'a'),
+ (KATAKANA_LETTER_A, KATAKANA_LETTER_A + 'bcd', None),
+ (KATAKANA_LETTER_A + 'bcde', KATAKANA_LETTER_A + 'bcd', KATAKANA_LETTER_A + 'bcde'),
+ ('d'+KATAKANA_LETTER_A + 'bcd', KATAKANA_LETTER_A + 'bcd', KATAKANA_LETTER_A + 'bcd'),
+ ('d'+KATAKANA_LETTER_A + 'bd', KATAKANA_LETTER_A + 'bcd', None),
+
+ ('e'+KATAKANA_LETTER_A + 'bcdf', KATAKANA_LETTER_A + 'bcd', KATAKANA_LETTER_A + 'bcdf'),
+ (KATAKANA_LETTER_A, KATAKANA_LETTER_A + 'bcd', None),
+ (KATAKANA_LETTER_A*3, 'a', None),
+ ]
+ for a, b, expect in cases:
+ if expect is not None:
+ expect = expect.encode('utf-8')
+ self.assertEquals(strstr_m(a.encode('utf-8'),
+ b.encode('utf-8')),
+ expect)
diff --git a/source4/scripting/python/samba/tests/unicodenames.py b/source4/scripting/python/samba/tests/unicodenames.py
new file mode 100644
index 0000000000..ed65de6651
--- /dev/null
+++ b/source4/scripting/python/samba/tests/unicodenames.py
@@ -0,0 +1,29 @@
+# Copyright (C) 2003 by Martin Pool <mbp@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/>.
+#
+
+"""
+Defines symbolic names for a few UNICODE characters, to make test
+source code more readable on machines that don't have all the
+necessary fonts.
+
+You can do "import *" on this file safely.
+"""
+
+LATIN_CAPITAL_LETTER_N_WITH_TILDE = u'\u004e'
+LATIN_CAPITAL_LETTER_O_WITH_DIARESIS = u'\u00d6'
+LATIN_SMALL_LETTER_O_WITH_DIARESIS = u'\u00f6'
+
+KATAKANA_LETTER_A = u'\u30a2'
diff --git a/source4/scripting/python/samba/tests/upgrade.py b/source4/scripting/python/samba/tests/upgrade.py
index 16ccbd567e..b46a417319 100644
--- a/source4/scripting/python/samba/tests/upgrade.py
+++ b/source4/scripting/python/samba/tests/upgrade.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
-#
+#
# 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/>.
#
@@ -22,6 +20,7 @@
from samba.upgrade import import_wins
from samba.tests import LdbTestCase
+
class WinsUpgradeTests(LdbTestCase):
def test_upgrade(self):
@@ -30,10 +29,12 @@ class WinsUpgradeTests(LdbTestCase):
}
import_wins(self.ldb, winsdb)
- self.assertEquals(['name=FOO,type=0x20'],
- [str(m.dn) for m in self.ldb.search(expression="(objectClass=winsRecord)")])
+ self.assertEquals(
+ ['name=FOO,type=0x20'],
+ [str(m.dn) for m in
+ self.ldb.search(expression="(objectClass=winsRecord)")])
def test_version(self):
import_wins(self.ldb, {})
- self.assertEquals("VERSION",
+ self.assertEquals("VERSION",
str(self.ldb.search(expression="(objectClass=winsMaxVersion)")[0]["cn"]))
diff --git a/source4/scripting/python/samba/tests/upgradeprovision.py b/source4/scripting/python/samba/tests/upgradeprovision.py
index b81ee8a8ab..c1c70c4a88 100644
--- a/source4/scripting/python/samba/tests/upgradeprovision.py
+++ b/source4/scripting/python/samba/tests/upgradeprovision.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
#
@@ -20,14 +18,14 @@
"""Tests for samba.upgradeprovision."""
import os
-from samba.upgradehelpers import (usn_in_range, dn_sort,
+from samba.upgradehelpers import (usn_in_range, dn_sort,
get_diff_sddls, update_secrets,
construct_existor_expr)
from samba.tests.provision import create_dummy_secretsdb
from samba.tests import TestCaseInTempDir
from samba import Ldb
-from ldb import SCOPE_SUBTREE
+from ldb import SCOPE_BASE
import samba.tests
def dummymessage(a=None, b=None):
@@ -123,14 +121,12 @@ class UpdateSecretsTests(samba.tests.TestCaseInTempDir):
def test_update_modules(self):
empty_db = self._getEmptyDb()
update_secrets(self.referencedb, empty_db, dummymessage)
- newmodules = empty_db.search(
- expression="dn=@MODULES", base="", scope=SCOPE_SUBTREE)
- refmodules = self.referencedb.search(
- expression="dn=@MODULES", base="", scope=SCOPE_SUBTREE)
+ newmodules = empty_db.search(base="@MODULES", scope=SCOPE_BASE)
+ refmodules = self.referencedb.search(base="@MODULES", scope=SCOPE_BASE)
self.assertEquals(newmodules.msgs, refmodules.msgs)
def tearDown(self):
- for name in ["ref.ldb", "secrets.ldb"]:
+ for name in ["ref.ldb", "secrets.ldb", "secrets.tdb"]:
path = os.path.join(self.tempdir, name)
if os.path.exists(path):
os.unlink(path)
diff --git a/source4/scripting/python/samba/tests/upgradeprovisionneeddc.py b/source4/scripting/python/samba/tests/upgradeprovisionneeddc.py
index 3a9c78e0dc..1de123c71d 100644
--- a/source4/scripting/python/samba/tests/upgradeprovisionneeddc.py
+++ b/source4/scripting/python/samba/tests/upgradeprovisionneeddc.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation.
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
#
@@ -26,9 +24,9 @@ import shutil
from samba import param
from samba.credentials import Credentials
from samba.auth import system_session
-from samba.provision import getpolicypath
+from samba.provision import getpolicypath,find_provision_key_parameters
from samba.upgradehelpers import (get_paths, get_ldbs,
- find_provision_key_parameters, identic_rename,
+ identic_rename,
updateOEMInfo, getOEMInfo, update_gpo,
delta_update_basesamdb,
update_dns_account_password,
@@ -161,7 +159,8 @@ class UpgradeProvisionWithLdbTestCase(TestCaseInTempDir):
self.assertNotEquals(oem, "")
def test_update_dns_account(self):
- update_dns_account_password(self.ldbs.sam, self.ldbs.secrets, self.names)
+ update_dns_account_password(self.ldbs.sam, self.ldbs.secrets,
+ self.names)
def test_updateOEMInfo(self):
realm = self.lp.get("realm")
@@ -173,7 +172,7 @@ class UpgradeProvisionWithLdbTestCase(TestCaseInTempDir):
self.assertTrue(re.match(".*upgrade to.*", str(oem2)))
def tearDown(self):
- for name in ["ref.ldb", "secrets.ldb", "sam.ldb"]:
+ for name in ["ref.ldb", "secrets.ldb", "secrets.tdb", "sam.ldb"]:
path = os.path.join(self.tempdir, name)
if os.path.exists(path):
os.unlink(path)
diff --git a/source4/scripting/python/samba/tests/xattr.py b/source4/scripting/python/samba/tests/xattr.py
index f978ee5b2a..89add28456 100644
--- a/source4/scripting/python/samba/tests/xattr.py
+++ b/source4/scripting/python/samba/tests/xattr.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
# Unix SMB/CIFS implementation. Tests for xattr manipulation
# Copyright (C) Matthieu Patou <mat@matws.net> 2009
#
@@ -20,10 +18,16 @@
"""Tests for samba.xattr_native and samba.xattr_tdb."""
import samba.xattr_native, samba.xattr_tdb
+from samba.xattr import copytree_with_xattrs
from samba.dcerpc import xattr
from samba.ndr import ndr_pack
-from samba.tests import TestCase, TestSkipped
+from samba.tests import (
+ TestCase,
+ TestCaseInTempDir,
+ TestSkipped,
+ )
import random
+import shutil
import os
class XattrTests(TestCase):
@@ -44,7 +48,7 @@ class XattrTests(TestCase):
tempf = self._tmpfilename()
open(tempf, 'w').write("empty")
try:
- samba.xattr_native.wrap_setxattr(tempf, "user.unittests",
+ samba.xattr_native.wrap_setxattr(tempf, "user.unittests",
ndr_pack(ntacl))
except IOError:
raise TestSkipped("the filesystem where the tests are runned do not support XATTR")
@@ -83,7 +87,7 @@ class XattrTests(TestCase):
ntacl.version = 1
open(tempf, 'w').write("empty")
try:
- self.assertRaises(IOError, samba.xattr_tdb.wrap_setxattr,
+ self.assertRaises(IOError, samba.xattr_tdb.wrap_setxattr,
os.path.join("nonexistent", "eadb.tdb"), tempf,
"user.unittests", ndr_pack(ntacl))
finally:
@@ -103,3 +107,20 @@ class XattrTests(TestCase):
finally:
os.unlink(tempf)
os.unlink(eadb_path)
+
+
+class TestCopyTreeWithXattrs(TestCaseInTempDir):
+
+ def test_simple(self):
+ os.chdir(self.tempdir)
+ os.mkdir("a")
+ os.mkdir("a/b")
+ os.mkdir("a/b/c")
+ f = open('a/b/c/d', 'w')
+ try:
+ f.write("foo")
+ finally:
+ f.close()
+ copytree_with_xattrs("a", "b")
+ shutil.rmtree("a")
+ shutil.rmtree("b")
diff --git a/source4/scripting/python/samba/upgrade.py b/source4/scripting/python/samba/upgrade.py
index 5bcc4294ba..13d33c1442 100644
--- a/source4/scripting/python/samba/upgrade.py
+++ b/source4/scripting/python/samba/upgrade.py
@@ -1,5 +1,6 @@
# backend code for upgrading from Samba3
# Copyright Jelmer Vernooij 2005-2007
+# Copyright Andrew Bartlett 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
@@ -19,153 +20,301 @@
__docformat__ = "restructuredText"
-import grp
import ldb
import time
import pwd
from samba import Ldb, registry
from samba.param import LoadParm
-from samba.provision import provision
+from samba.provision import provision, FILL_FULL, ProvisioningError, setsysvolacl
+from samba.samba3 import passdb
+from samba.samba3 import param as s3param
+from samba.dcerpc import lsa, samr, security
+from samba.dcerpc.security import dom_sid
+from samba.credentials import Credentials
+from samba import dsdb
+from samba.ndr import ndr_pack
+from samba import unix2nttime
+
+
+def import_sam_policy(samdb, policy, logger):
+ """Import a Samba 3 policy.
+
+ :param samdb: Samba4 SAM database
+ :param policy: Samba3 account policy
+ :param logger: Logger object
+ """
-def import_sam_policy(samldb, policy, dn):
- """Import a Samba 3 policy database."""
- samldb.modify_ldif("""
-dn: %s
-changetype: modify
-replace: minPwdLength
-minPwdLength: %d
-pwdHistoryLength: %d
-minPwdAge: %d
-maxPwdAge: %d
-lockoutDuration: %d
-samba3ResetCountMinutes: %d
-samba3UserMustLogonToChangePassword: %d
-samba3BadLockoutMinutes: %d
-samba3DisconnectTime: %d
-
-""" % (dn, policy.min_password_length,
- policy.password_history, policy.minimum_password_age,
- policy.maximum_password_age, policy.lockout_duration,
- policy.reset_count_minutes, policy.user_must_logon_to_change_password,
- policy.bad_lockout_minutes, policy.disconnect_time))
-
-
-def import_sam_account(samldb,acc,domaindn,domainsid):
- """Import a Samba 3 SAM account.
-
- :param samldb: Samba 4 SAM Database handle
- :param acc: Samba 3 account
- :param domaindn: Domain DN
- :param domainsid: Domain SID."""
- if acc.nt_username is None or acc.nt_username == "":
- acc.nt_username = acc.username
-
- if acc.fullname is None:
- try:
- acc.fullname = pwd.getpwnam(acc.username)[4].split(",")[0]
- except KeyError:
- pass
+ # Following entries are used -
+ # min password length, password history, minimum password age,
+ # maximum password age, lockout duration
+ #
+ # Following entries are not used -
+ # reset count minutes, user must logon to change password,
+ # bad lockout minutes, disconnect time
+
+ m = ldb.Message()
+ m.dn = samdb.get_default_basedn()
+
+ if 'min password length' in policy:
+ m['a01'] = ldb.MessageElement(str(policy['min password length']),
+ ldb.FLAG_MOD_REPLACE, 'minPwdLength')
+
+ if 'password history' in policy:
+ m['a02'] = ldb.MessageElement(str(policy['password history']),
+ ldb.FLAG_MOD_REPLACE, 'pwdHistoryLength')
+
+ if 'minimum password age' in policy:
+ min_pw_age_unix = policy['minimum password age']
+ min_pw_age_nt = int(-min_pw_age_unix * (1e7))
+ m['a03'] = ldb.MessageElement(str(min_pw_age_nt), ldb.FLAG_MOD_REPLACE,
+ 'minPwdAge')
+
+ if 'maximum password age' in policy:
+ max_pw_age_unix = policy['maximum password age']
+ if max_pw_age_unix == -1 or max_pw_age_unix == 0:
+ max_pw_age_nt = -0x8000000000000000
+ else:
+ max_pw_age_nt = int(-max_pw_age_unix * (1e7))
+
+ m['a04'] = ldb.MessageElement(str(max_pw_age_nt), ldb.FLAG_MOD_REPLACE,
+ 'maxPwdAge')
+
+ if 'lockout duration' in policy:
+ lockout_duration_mins = policy['lockout duration']
+ lockout_duration_nt = unix2nttime(lockout_duration_mins * 60)
+
+ m['a05'] = ldb.MessageElement(str(lockout_duration_nt),
+ ldb.FLAG_MOD_REPLACE, 'lockoutDuration')
- if acc.fullname is None:
- acc.fullname = acc.username
-
- assert acc.fullname is not None
- assert acc.nt_username is not None
-
- samldb.add({
- "dn": "cn=%s,%s" % (acc.fullname, domaindn),
- "objectClass": ["top", "user"],
- "lastLogon": str(acc.logon_time),
- "lastLogoff": str(acc.logoff_time),
- "unixName": acc.username,
- "sAMAccountName": acc.nt_username,
- "cn": acc.nt_username,
- "description": acc.acct_desc,
- "primaryGroupID": str(acc.group_rid),
- "badPwdcount": str(acc.bad_password_count),
- "logonCount": str(acc.logon_count),
- "samba3Domain": acc.domain,
- "samba3DirDrive": acc.dir_drive,
- "samba3MungedDial": acc.munged_dial,
- "samba3Homedir": acc.homedir,
- "samba3LogonScript": acc.logon_script,
- "samba3ProfilePath": acc.profile_path,
- "samba3Workstations": acc.workstations,
- "samba3KickOffTime": str(acc.kickoff_time),
- "samba3BadPwdTime": str(acc.bad_password_time),
- "samba3PassLastSetTime": str(acc.pass_last_set_time),
- "samba3PassCanChangeTime": str(acc.pass_can_change_time),
- "samba3PassMustChangeTime": str(acc.pass_must_change_time),
- "objectSid": "%s-%d" % (domainsid, acc.user_rid),
- "lmPwdHash:": acc.lm_password,
- "ntPwdHash:": acc.nt_password,
- })
-
-
-def import_sam_group(samldb, sid, gid, sid_name_use, nt_name, comment, domaindn):
- """Upgrade a SAM group.
-
- :param samldb: SAM database.
- :param gid: Group GID
- :param sid_name_use: SID name use
- :param nt_name: NT Group Name
- :param comment: NT Group Comment
- :param domaindn: Domain DN
+ try:
+ samdb.modify(m)
+ except ldb.LdbError, e:
+ logger.warn("Could not set account policy, (%s)", str(e))
+
+
+def add_posix_attrs(logger, samdb, sid, name, nisdomain, xid_type, home=None,
+ shell=None, pgid=None):
+ """Add posix attributes for the user/group
+
+ :param samdb: Samba4 sam.ldb database
+ :param sid: user/group sid
+ :param sid: user/group name
+ :param nisdomain: name of the (fake) NIS domain
+ :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
+ :param home: user homedir (Unix homepath)
+ :param shell: user shell
+ :param pgid: users primary group id
"""
- if sid_name_use == 5: # Well-known group
- return None
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "<SID=%s>" % str(sid))
+ if xid_type == "ID_TYPE_UID":
+ m['unixHomeDirectory'] = ldb.MessageElement(
+ str(home), ldb.FLAG_MOD_REPLACE, 'unixHomeDirectory')
+ m['loginShell'] = ldb.MessageElement(
+ str(shell), ldb.FLAG_MOD_REPLACE, 'loginShell')
+ m['gidNumber'] = ldb.MessageElement(
+ str(pgid), ldb.FLAG_MOD_REPLACE, 'gidNumber')
+
+ m['msSFU30NisDomain'] = ldb.MessageElement(
+ str(nisdomain), ldb.FLAG_MOD_REPLACE, 'msSFU30NisDomain')
+
+ samdb.modify(m)
+ except ldb.LdbError, e:
+ logger.warn(
+ 'Could not add posix attrs for AD entry for sid=%s, (%s)',
+ str(sid), str(e))
+
+def add_ad_posix_idmap_entry(samdb, sid, xid, xid_type, logger):
+ """Create idmap entry
+
+ :param samdb: Samba4 sam.ldb database
+ :param sid: user/group sid
+ :param xid: user/group id
+ :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
+ :param logger: Logger object
+ """
- if nt_name in ("Domain Guests", "Domain Users", "Domain Admins"):
- return None
+ try:
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "<SID=%s>" % str(sid))
+ if xid_type == "ID_TYPE_UID":
+ m['uidNumber'] = ldb.MessageElement(
+ str(xid), ldb.FLAG_MOD_REPLACE, 'uidNumber')
+ m['objectClass'] = ldb.MessageElement(
+ "posixAccount", ldb.FLAG_MOD_ADD, 'objectClass')
+ elif xid_type == "ID_TYPE_GID":
+ m['gidNumber'] = ldb.MessageElement(
+ str(xid), ldb.FLAG_MOD_REPLACE, 'gidNumber')
+ m['objectClass'] = ldb.MessageElement(
+ "posixGroup", ldb.FLAG_MOD_ADD, 'objectClass')
+
+ samdb.modify(m)
+ except ldb.LdbError, e:
+ logger.warn(
+ 'Could not modify AD idmap entry for sid=%s, id=%s, type=%s (%s)',
+ str(sid), str(xid), xid_type, str(e))
+
+
+def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
+ """Create idmap entry
+
+ :param idmapdb: Samba4 IDMAP database
+ :param sid: user/group sid
+ :param xid: user/group id
+ :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID)
+ :param logger: Logger object
+ """
- if gid == -1:
- gr = grp.getgrnam(nt_name)
- else:
- gr = grp.getgrgid(gid)
+ # First try to see if we already have this entry
+ found = False
+ msg = idmapdb.search(expression='objectSid=%s' % str(sid))
+ if msg.count == 1:
+ found = True
- if gr is None:
- unixname = "UNKNOWN"
+ if found:
+ try:
+ m = ldb.Message()
+ m.dn = msg[0]['dn']
+ m['xidNumber'] = ldb.MessageElement(
+ str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
+ m['type'] = ldb.MessageElement(
+ xid_type, ldb.FLAG_MOD_REPLACE, 'type')
+ idmapdb.modify(m)
+ except ldb.LdbError, e:
+ logger.warn(
+ 'Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
+ str(sid), str(xid), xid_type, str(e))
else:
- unixname = gr.gr_name
+ try:
+ idmapdb.add({"dn": "CN=%s" % str(sid),
+ "cn": str(sid),
+ "objectClass": "sidMap",
+ "objectSid": ndr_pack(sid),
+ "type": xid_type,
+ "xidNumber": str(xid)})
+ except ldb.LdbError, e:
+ logger.warn(
+ 'Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
+ str(sid), str(xid), xid_type, str(e))
+
+
+def import_idmap(idmapdb, samba3, logger):
+ """Import idmap data.
- assert unixname is not None
+ :param idmapdb: Samba4 IDMAP database
+ :param samba3_idmap: Samba3 IDMAP database to import from
+ :param logger: Logger object
+ """
- samldb.add({
- "dn": "cn=%s,%s" % (nt_name, domaindn),
- "objectClass": ["top", "group"],
- "description": comment,
- "cn": nt_name,
- "objectSid": sid,
- "unixName": unixname,
- "samba3SidNameUse": str(sid_name_use)
- })
+ try:
+ samba3_idmap = samba3.get_idmap_db()
+ except IOError, e:
+ logger.warn('Cannot open idmap database, Ignoring: %s', str(e))
+ return
+
+ currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
+ lowerbound = currentxid
+ # FIXME: upperbound
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
+ m['lowerbound'] = ldb.MessageElement(
+ str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
+ m['xidNumber'] = ldb.MessageElement(
+ str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
+ idmapdb.modify(m)
+
+ for id_type, xid in samba3_idmap.ids():
+ if id_type == 'UID':
+ xid_type = 'ID_TYPE_UID'
+ elif id_type == 'GID':
+ xid_type = 'ID_TYPE_GID'
+ else:
+ logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
+ continue
+ sid = samba3_idmap.get_sid(xid, id_type)
+ add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
-def import_idmap(samdb,samba3_idmap,domaindn):
- """Import idmap data.
- :param samdb: SamDB handle.
- :param samba3_idmap: Samba 3 IDMAP database to import from
- :param domaindn: Domain DN.
+def add_group_from_mapping_entry(samdb, groupmap, logger):
+ """Add or modify group from group mapping entry
+
+ param samdb: Samba4 SAM database
+ param groupmap: Groupmap entry
+ param logger: Logger object
"""
- samdb.add({
- "dn": domaindn,
- "userHwm": str(samba3_idmap.get_user_hwm()),
- "groupHwm": str(samba3_idmap.get_group_hwm())})
- for uid in samba3_idmap.uids():
- samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_user_sid(uid), domaindn),
- "SID": samba3_idmap.get_user_sid(uid),
- "type": "user",
- "unixID": str(uid)})
+ # First try to see if we already have this entry
+ try:
+ msg = samdb.search(
+ base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
+ found = True
+ except ldb.LdbError, (ecode, emsg):
+ if ecode == ldb.ERR_NO_SUCH_OBJECT:
+ found = False
+ else:
+ raise ldb.LdbError(ecode, emsg)
+
+ if found:
+ logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
+ str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
+ else:
+ if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
+ # In a lot of Samba3 databases, aliases are marked as well known groups
+ (group_dom_sid, rid) = groupmap.sid.split()
+ if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
+ return
+
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
+ m['cn'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
+ m['objectClass'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
+ m['objectSid'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD,
+ 'objectSid')
+ m['sAMAccountName'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD,
+ 'sAMAccountName')
+
+ if groupmap.comment:
+ m['description'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD,
+ 'description')
+
+ # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases
+ if groupmap.sid_name_use == lsa.SID_NAME_ALIAS or groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
+ m['groupType'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
+ ldb.FLAG_MOD_ADD, 'groupType')
+
+ try:
+ samdb.add(m, controls=["relax:0"])
+ except ldb.LdbError, e:
+ logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
+
+
+def add_users_to_group(samdb, group, members, logger):
+ """Add user/member to group/alias
+
+ param samdb: Samba4 SAM database
+ param group: Groupmap object
+ param members: List of member SIDs
+ param logger: Logger object
+ """
+ for member_sid in members:
+ m = ldb.Message()
+ m.dn = ldb.Dn(samdb, "<SID=%s>" % str(group.sid))
+ m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_ADD, 'member')
- for gid in samba3_idmap.uids():
- samdb.add({"dn": "SID=%s,%s" % (samba3_idmap.get_group_sid(gid), domaindn),
- "SID": samba3_idmap.get_group_sid(gid),
- "type": "group",
- "unixID": str(gid)})
+ try:
+ samdb.modify(m)
+ except ldb.LdbError, (ecode, emsg):
+ if ecode == ldb.ERR_ENTRY_ALREADY_EXISTS:
+ logger.debug("skipped re-adding member '%s' to group '%s': %s", member_sid, group.sid, emsg)
+ elif ecode == ldb.ERR_NO_SUCH_OBJECT:
+ raise ProvisioningError("Could not add member '%s' to group '%s' as either group or user record doesn't exist: %s" % (member_sid, group.sid, emsg))
+ else:
+ raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid, group.sid, emsg))
def import_wins(samba4_winsdb, samba3_winsdb):
@@ -174,10 +323,11 @@ def import_wins(samba4_winsdb, samba3_winsdb):
:param samba4_winsdb: WINS database to import to
:param samba3_winsdb: WINS database to import from
"""
+
version_id = 0
for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
- version_id+=1
+ version_id += 1
type = int(name.split("#", 1)[1], 16)
@@ -199,7 +349,7 @@ def import_wins(samba4_winsdb, samba3_winsdb):
else:
rState = 0x1 # released
- nType = ((nb_flags & 0x60)>>5)
+ nType = ((nb_flags & 0x60) >> 5)
samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
"type": name.split("#")[1],
@@ -218,6 +368,7 @@ def import_wins(samba4_winsdb, samba3_winsdb):
"objectClass": "winsMaxVersion",
"maxVersion": str(version_id)})
+
def enable_samba3sam(samdb, ldapurl):
"""Enable Samba 3 LDAP URL database.
@@ -279,8 +430,6 @@ smbconf_keep = [
"write raw",
"disable netbios",
"nt status support",
- "announce version",
- "announce as",
"max mux",
"max xmit",
"name resolve order",
@@ -328,7 +477,8 @@ smbconf_keep = [
"host msdfs",
"winbind separator"]
-def upgrade_smbconf(oldconf,mark):
+
+def upgrade_smbconf(oldconf, mark):
"""Remove configuration variables not present in Samba4
:param oldconf: Old configuration structure
@@ -349,7 +499,7 @@ def upgrade_smbconf(oldconf,mark):
if keep:
newconf.set(s, p, oldconf.get(s, p))
elif mark:
- newconf.set(s, "samba3:"+p, oldconf.get(s,p))
+ newconf.set(s, "samba3:" + p, oldconf.get(s, p))
return newconf
@@ -357,6 +507,7 @@ SAMBA3_PREDEF_NAMES = {
'HKLM': registry.HKEY_LOCAL_MACHINE,
}
+
def import_registry(samba4_registry, samba3_regdb):
"""Import a Samba 3 registry database into the Samba 4 registry.
@@ -376,77 +527,397 @@ def import_registry(samba4_registry, samba3_regdb):
for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
key_handle.set_value(value_name, value_type, value_data)
-
-def upgrade_provision(samba3, logger, credentials, session_info,
- smbconf, targetdir):
- oldconf = samba3.get_conf()
-
- if oldconf.get("domain logons") == "True":
- serverrole = "domain controller"
+def get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, user, attr):
+ """Get posix attributes from a samba3 ldap backend
+ :param ldbs: a list of ldb connection objects
+ :param base_dn: the base_dn of the connection
+ :param user: the user to get the attribute for
+ :param attr: the attribute to be retrieved
+ """
+ try:
+ msg = ldb_object.search(base_dn, scope=ldb.SCOPE_SUBTREE,
+ expression=("(&(objectClass=posixAccount)(uid=%s))"
+ % (user)), attrs=[attr])
+ except ldb.LdbError, e:
+ raise ProvisioningError("Failed to retrieve attribute %s for user %s, the error is: %s", attr, user, e)
else:
- if oldconf.get("security") == "user":
- serverrole = "standalone"
+ if msg.count <= 1:
+ # This will raise KeyError (which is what we want) if there isn't a entry for this user
+ return msg[0][attr][0]
else:
- serverrole = "member server"
+ logger.warning("LDAP entry for user %s contains more than one %s", user, attr)
+ raise KeyError
+
+
+def upgrade_from_samba3(samba3, logger, targetdir, session_info=None,
+ useeadb=False, dns_backend=None, use_ntvfs=False):
+ """Upgrade from samba3 database to samba4 AD database
+
+ :param samba3: samba3 object
+ :param logger: Logger object
+ :param targetdir: samba4 database directory
+ :param session_info: Session information
+ """
+ serverrole = samba3.lp.server_role()
+
+ domainname = samba3.lp.get("workgroup")
+ realm = samba3.lp.get("realm")
+ netbiosname = samba3.lp.get("netbios name")
- domainname = oldconf.get("workgroup")
- realm = oldconf.get("realm")
- netbiosname = oldconf.get("netbios name")
+ if samba3.lp.get("ldapsam:trusted") is None:
+ samba3.lp.set("ldapsam:trusted", "yes")
- secrets_db = samba3.get_secrets_db()
+ # secrets db
+ try:
+ secrets_db = samba3.get_secrets_db()
+ except IOError, e:
+ raise ProvisioningError("Could not open '%s', the Samba3 secrets database: %s. Perhaps you specified the incorrect smb.conf, --testparm or --dbdir option?" % (samba3.privatedir_path("secrets.tdb"), str(e)))
- if domainname is None:
+ if not domainname:
domainname = secrets_db.domains()[0]
- logger.warning("No domain specified in smb.conf file, assuming '%s'",
+ logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
domainname)
- if realm is None:
- if oldconf.get("domain logons") == "True":
- logger.warning("No realm specified in smb.conf file and being a DC. That upgrade path doesn't work! Please add a 'realm' directive to your old smb.conf to let us know which one you want to use (generally it's the upcased DNS domainname).")
- return
+ if not realm:
+ if serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC":
+ raise ProvisioningError("No realm specified in smb.conf file and being a DC. That upgrade path doesn't work! Please add a 'realm' directive to your old smb.conf to let us know which one you want to use (it is the DNS name of the AD domain you wish to create.")
else:
realm = domainname.upper()
logger.warning("No realm specified in smb.conf file, assuming '%s'",
realm)
- domainguid = secrets_db.get_domain_guid(domainname)
- domainsid = secrets_db.get_sid(domainname)
- if domainsid is None:
- logger.warning("Can't find domain secrets for '%s'; using random SID",
- domainname)
+ # Find machine account and password
+ next_rid = 1000
- if netbiosname is not None:
+ try:
machinepass = secrets_db.get_machine_password(netbiosname)
- else:
+ except KeyError:
machinepass = None
- result = provision(logger=logger,
- session_info=session_info, credentials=credentials,
- targetdir=targetdir, realm=realm, domain=domainname,
- domainguid=domainguid, domainsid=domainsid,
- hostname=netbiosname, machinepass=machinepass,
- serverrole=serverrole)
+ if samba3.lp.get("passdb backend").split(":")[0].strip() == "ldapsam":
+ base_dn = samba3.lp.get("ldap suffix")
+ ldapuser = samba3.lp.get("ldap admin dn")
+ ldappass = (secrets_db.get_ldap_bind_pw(ldapuser)).strip('\x00')
+ ldap = True
+ else:
+ ldapuser = None
+ ldappass = None
+ ldap = False
+
+ # We must close the direct pytdb database before the C code loads it
+ secrets_db.close()
+
+ # Connect to old password backend
+ passdb.set_secrets_dir(samba3.lp.get("private dir"))
+ s3db = samba3.get_sam_db()
+
+ # Get domain sid
+ try:
+ domainsid = passdb.get_global_sam_sid()
+ except passdb.error:
+ raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
+
+ # Get machine account, sid, rid
+ try:
+ machineacct = s3db.getsampwnam('%s$' % netbiosname)
+ except passdb.error:
+ machinerid = None
+ machinesid = None
+ else:
+ machinesid, machinerid = machineacct.user_sid.split()
+
+ # Export account policy
+ logger.info("Exporting account policy")
+ policy = s3db.get_account_policy()
+
+ # Export groups from old passdb backend
+ logger.info("Exporting groups")
+ grouplist = s3db.enum_group_mapping()
+ groupmembers = {}
+ for group in grouplist:
+ sid, rid = group.sid.split()
+ if sid == domainsid:
+ if rid >= next_rid:
+ next_rid = rid + 1
+
+ # Get members for each group/alias
+ if group.sid_name_use == lsa.SID_NAME_ALIAS:
+ try:
+ members = s3db.enum_aliasmem(group.sid)
+ groupmembers[str(group.sid)] = members
+ except passdb.error, e:
+ logger.warn("Ignoring group '%s' %s listed but then not found: %s",
+ group.nt_name, group.sid, e)
+ continue
+ elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
+ try:
+ members = s3db.enum_group_members(group.sid)
+ groupmembers[str(group.sid)] = members
+ except passdb.error, e:
+ logger.warn("Ignoring group '%s' %s listed but then not found: %s",
+ group.nt_name, group.sid, e)
+ continue
+ elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
+ (group_dom_sid, rid) = group.sid.split()
+ if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
+ logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
+ group.nt_name)
+ continue
+ # A number of buggy databases mix up well known groups and aliases.
+ try:
+ members = s3db.enum_aliasmem(group.sid)
+ groupmembers[str(group.sid)] = members
+ except passdb.error, e:
+ logger.warn("Ignoring group '%s' %s listed but then not found: %s",
+ group.nt_name, group.sid, e)
+ continue
+ else:
+ logger.warn("Ignoring group '%s' %s with sid_name_use=%d",
+ group.nt_name, group.sid, group.sid_name_use)
+ continue
+
+ # Export users from old passdb backend
+ logger.info("Exporting users")
+ userlist = s3db.search_users(0)
+ userdata = {}
+ uids = {}
+ admin_user = None
+ for entry in userlist:
+ if machinerid and machinerid == entry['rid']:
+ continue
+ username = entry['account_name']
+ if entry['rid'] < 1000:
+ logger.info(" Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
+ continue
+ if entry['rid'] >= next_rid:
+ next_rid = entry['rid'] + 1
+
+ user = s3db.getsampwnam(username)
+ acct_type = (user.acct_ctrl & (samr.ACB_NORMAL|samr.ACB_WSTRUST|samr.ACB_SVRTRUST|samr.ACB_DOMTRUST))
+ if (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST):
+ pass
- import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
+ elif acct_type == samr.ACB_SVRTRUST:
+ logger.warn(" Demoting BDC account trust for %s, this DC must be elevated to an AD DC using 'samba-tool domain promote'" % username[:-1])
+ user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_SVRTRUST) | samr.ACB_WSTRUST
- # FIXME: import_registry(registry.Registry(), samba3.get_registry())
+ elif acct_type == samr.ACB_DOMTRUST:
+ logger.warn(" Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
- # FIXME: import_idmap(samdb,samba3.get_idmap_db(),domaindn)
+ elif acct_type == (samr.ACB_NORMAL|samr.ACB_WSTRUST) and username[-1] == '$':
+ logger.warn(" Fixing account %s which had both ACB_NORMAL (U) and ACB_WSTRUST (W) set. Account will be marked as ACB_WSTRUST (W), i.e. as a domain member" % username)
+ user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
- groupdb = samba3.get_groupmapping_db()
- for sid in groupdb.groupsids():
- (gid, sid_name_use, nt_name, comment) = groupdb.get_group(sid)
- # FIXME: import_sam_group(samdb, sid, gid, sid_name_use, nt_name, comment, domaindn)
+ elif acct_type == (samr.ACB_NORMAL|samr.ACB_SVRTRUST) and username[-1] == '$':
+ logger.warn(" Fixing account %s which had both ACB_NORMAL (U) and ACB_SVRTRUST (S) set. Account will be marked as ACB_WSTRUST (S), i.e. as a domain member" % username)
+ user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
- # FIXME: Aliases
+ else:
+ raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
+ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
- passdb = samba3.get_sam_db()
- for name in passdb:
- user = passdb[name]
- #FIXME: import_sam_account(result.samdb, user, domaindn, domainsid)
+Please fix this account before attempting to upgrade again
+"""
+ % (user.acct_flags, username,
+ samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
- if hasattr(passdb, 'ldap_url'):
- logger.info("Enabling Samba3 LDAP mappings for SAM database")
+ userdata[username] = user
+ try:
+ uids[username] = s3db.sid_to_id(user.user_sid)[0]
+ except passdb.error:
+ try:
+ uids[username] = pwd.getpwnam(username).pw_uid
+ except KeyError:
+ pass
+
+ if not admin_user and username.lower() == 'root':
+ admin_user = username
+ if username.lower() == 'administrator':
+ admin_user = username
- enable_samba3sam(result.samdb, passdb.ldap_url)
+ try:
+ group_memberships = s3db.enum_group_memberships(user);
+ for group in group_memberships:
+ if str(group) in groupmembers:
+ if user.user_sid not in groupmembers[str(group)]:
+ groupmembers[str(group)].append(user.user_sid)
+ else:
+ groupmembers[str(group)] = [user.user_sid];
+ except passdb.error, e:
+ logger.warn("Ignoring group memberships of '%s' %s: %s",
+ username, user.user_sid, e)
+
+ logger.info("Next rid = %d", next_rid)
+
+ # Check for same username/groupname
+ group_names = set([g.nt_name for g in grouplist])
+ user_names = set([u['account_name'] for u in userlist])
+ common_names = group_names.intersection(user_names)
+ if common_names:
+ logger.error("Following names are both user names and group names:")
+ for name in common_names:
+ logger.error(" %s" % name)
+ raise ProvisioningError("Please remove common user/group names before upgrade.")
+
+ # Check for same user sid/group sid
+ group_sids = set([str(g.sid) for g in grouplist])
+ if len(grouplist) != len(group_sids):
+ raise ProvisioningError("Please remove duplicate group sid entries before upgrade.")
+ user_sids = set(["%s-%u" % (domainsid, u['rid']) for u in userlist])
+ if len(userlist) != len(user_sids):
+ raise ProvisioningError("Please remove duplicate user sid entries before upgrade.")
+ common_sids = group_sids.intersection(user_sids)
+ if common_sids:
+ logger.error("Following sids are both user and group sids:")
+ for sid in common_sids:
+ logger.error(" %s" % str(sid))
+ raise ProvisioningError("Please remove duplicate sid entries before upgrade.")
+
+ # Get posix attributes from ldap or the os
+ homes = {}
+ shells = {}
+ pgids = {}
+ if ldap:
+ creds = Credentials()
+ creds.guess(samba3.lp)
+ creds.set_bind_dn(ldapuser)
+ creds.set_password(ldappass)
+ urls = samba3.lp.get("passdb backend").split(":",1)[1].strip('"')
+ for url in urls.split():
+ try:
+ ldb_object = Ldb(url, credentials=creds)
+ except ldb.LdbError, e:
+ logger.warning("Could not open ldb connection to %s, the error message is: %s", url, e)
+ else:
+ break
+ logger.info("Exporting posix attributes")
+ userlist = s3db.search_users(0)
+ for entry in userlist:
+ username = entry['account_name']
+ if username in uids.keys():
+ try:
+ if ldap:
+ homes[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "homeDirectory")
+ else:
+ homes[username] = pwd.getpwnam(username).pw_dir
+ except KeyError:
+ pass
+
+ try:
+ if ldap:
+ shells[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "loginShell")
+ else:
+ shells[username] = pwd.getpwnam(username).pw_shell
+ except KeyError:
+ pass
+
+ try:
+ if ldap:
+ pgids[username] = get_posix_attr_from_ldap_backend(logger, ldb_object, base_dn, username, "gidNumber")
+ else:
+ pgids[username] = pwd.getpwnam(username).pw_gid
+ except KeyError:
+ pass
+
+ logger.info("Reading WINS database")
+ samba3_winsdb = None
+ try:
+ samba3_winsdb = samba3.get_wins_db()
+ except IOError, e:
+ logger.warn('Cannot open wins database, Ignoring: %s', str(e))
+
+ if not (serverrole == "ROLE_DOMAIN_BDC" or serverrole == "ROLE_DOMAIN_PDC"):
+ dns_backend = "NONE"
+
+ # Do full provision
+ result = provision(logger, session_info, None,
+ targetdir=targetdir, realm=realm, domain=domainname,
+ domainsid=str(domainsid), next_rid=next_rid,
+ dc_rid=machinerid,
+ dom_for_fun_level=dsdb.DS_DOMAIN_FUNCTION_2003,
+ hostname=netbiosname.lower(), machinepass=machinepass,
+ serverrole=serverrole, samdb_fill=FILL_FULL,
+ useeadb=useeadb, dns_backend=dns_backend, use_rfc2307=True,
+ use_ntvfs=use_ntvfs, skip_sysvolacl=True)
+ result.report_logger(logger)
+
+ # Import WINS database
+ logger.info("Importing WINS database")
+
+ if samba3_winsdb:
+ import_wins(Ldb(result.paths.winsdb), samba3_winsdb)
+
+ # Set Account policy
+ logger.info("Importing Account policy")
+ import_sam_policy(result.samdb, policy, logger)
+
+ # Migrate IDMAP database
+ logger.info("Importing idmap database")
+ import_idmap(result.idmap, samba3, logger)
+
+ # Set the s3 context for samba4 configuration
+ new_lp_ctx = s3param.get_context()
+ new_lp_ctx.load(result.lp.configfile)
+ new_lp_ctx.set("private dir", result.lp.get("private dir"))
+ new_lp_ctx.set("state directory", result.lp.get("state directory"))
+ new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
+
+ # Connect to samba4 backend
+ s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
+
+ # Export groups to samba4 backend
+ logger.info("Importing groups")
+ for g in grouplist:
+ # Ignore uninitialized groups (gid = -1)
+ if g.gid != -1:
+ add_group_from_mapping_entry(result.samdb, g, logger)
+ add_ad_posix_idmap_entry(result.samdb, g.sid, g.gid, "ID_TYPE_GID", logger)
+ add_posix_attrs(samdb=result.samdb, sid=g.sid, name=g.nt_name, nisdomain=domainname.lower(), xid_type="ID_TYPE_GID", logger=logger)
+
+ # Export users to samba4 backend
+ logger.info("Importing users")
+ for username in userdata:
+ if username.lower() == 'administrator':
+ if userdata[username].user_sid != dom_sid(str(domainsid) + "-500"):
+ logger.error("User 'Administrator' in your existing directory has SID %s, expected it to be %s" % (userdata[username].user_sid, dom_sid(str(domainsid) + "-500")))
+ raise ProvisioningError("User 'Administrator' in your existing directory does not have SID ending in -500")
+ if username.lower() == 'root':
+ if userdata[username].user_sid == dom_sid(str(domainsid) + "-500"):
+ logger.warn('User root has been replaced by Administrator')
+ else:
+ logger.warn('User root has been kept in the directory, it should be removed in favour of the Administrator user')
+
+ s4_passdb.add_sam_account(userdata[username])
+ if username in uids:
+ add_ad_posix_idmap_entry(result.samdb, userdata[username].user_sid, uids[username], "ID_TYPE_UID", logger)
+ if (username in homes) and (homes[username] is not None) and \
+ (username in shells) and (shells[username] is not None) and \
+ (username in pgids) and (pgids[username] is not None):
+ add_posix_attrs(samdb=result.samdb, sid=userdata[username].user_sid, name=username, nisdomain=domainname.lower(), xid_type="ID_TYPE_UID", home=homes[username], shell=shells[username], pgid=pgids[username], logger=logger)
+
+ logger.info("Adding users to groups")
+ for g in grouplist:
+ if str(g.sid) in groupmembers:
+ add_users_to_group(result.samdb, g, groupmembers[str(g.sid)], logger)
+
+ # Set password for administrator
+ if admin_user:
+ logger.info("Setting password for administrator")
+ admin_userdata = s4_passdb.getsampwnam("administrator")
+ admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
+ if userdata[admin_user].lanman_passwd:
+ admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
+ admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
+ if userdata[admin_user].pw_history:
+ admin_userdata.pw_history = userdata[admin_user].pw_history
+ s4_passdb.update_sam_account(admin_userdata)
+ logger.info("Administrator password has been set to password of user '%s'", admin_user)
+
+ if result.server_role == "active directory domain controller":
+ setsysvolacl(result.samdb, result.paths.netlogon, result.paths.sysvol,
+ result.paths.root_uid, result.paths.root_gid,
+ security.dom_sid(result.domainsid), result.names.dnsdomain,
+ result.names.domaindn, result.lp, use_ntvfs)
+ # FIXME: import_registry(registry.Registry(), samba3.get_registry())
+ # FIXME: shares
diff --git a/source4/scripting/python/samba/upgradehelpers.py b/source4/scripting/python/samba/upgradehelpers.py
index 3a7dfb3997..81fb8dc0f4 100755..100644
--- a/source4/scripting/python/samba/upgradehelpers.py
+++ b/source4/scripting/python/samba/upgradehelpers.py
@@ -1,7 +1,5 @@
-#!/usr/bin/env python
-#
# Helpers for provision stuff
-# Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
+# Copyright (C) Matthieu Patou <mat@matws.net> 2009-2012
#
# Based on provision a Samba4 server by
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
@@ -21,26 +19,26 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-"""Helers used for upgrading between different database formats."""
+"""Helpers used for upgrading between different database formats."""
import os
-import string
import re
import shutil
import samba
from samba import Ldb, version, ntacls
-from samba.dsdb import DS_DOMAIN_FUNCTION_2000
from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE
import ldb
-from samba.provision import (ProvisionNames, provision_paths_from_lp,
+from samba.provision import (provision_paths_from_lp,
getpolicypath, set_gpos_acl, create_gpo_struct,
FILL_FULL, provision, ProvisioningError,
setsysvolacl, secretsdb_self_join)
-from samba.dcerpc import misc, security, xattr
+from samba.dcerpc import xattr, drsblobs
from samba.dcerpc.misc import SEC_CHAN_BDC
from samba.ndr import ndr_unpack
from samba.samdb import SamDB
+from samba import _glue
+import tempfile
# All the ldb related to registry are commented because the path for them is
# relative in the provisionPath object
@@ -77,11 +75,12 @@ class ProvisionLDB(object):
self.hku = None
self.hklm = None
+ def dbs(self):
+ return (self.sam, self.secrets, self.idmap, self.privilege)
+
def startTransactions(self):
- self.sam.transaction_start()
- self.secrets.transaction_start()
- self.idmap.transaction_start()
- self.privilege.transaction_start()
+ for db in self.dbs():
+ db.transaction_start()
# TO BE DONE
# self.hkcr.transaction_start()
# self.hkcu.transaction_start()
@@ -90,26 +89,11 @@ class ProvisionLDB(object):
def groupedRollback(self):
ok = True
- try:
- self.sam.transaction_cancel()
- except Exception:
- ok = False
-
- try:
- self.secrets.transaction_cancel()
- except Exception:
- ok = False
-
- try:
- self.idmap.transaction_cancel()
- except Exception:
- ok = False
-
- try:
- self.privilege.transaction_cancel()
- except Exception:
- ok = False
-
+ for db in self.dbs():
+ try:
+ db.transaction_cancel()
+ except Exception:
+ ok = False
return ok
# TO BE DONE
# self.hkcr.transaction_cancel()
@@ -119,10 +103,8 @@ class ProvisionLDB(object):
def groupedCommit(self):
try:
- self.sam.transaction_prepare_commit()
- self.secrets.transaction_prepare_commit()
- self.idmap.transaction_prepare_commit()
- self.privilege.transaction_prepare_commit()
+ for db in self.dbs():
+ db.transaction_prepare_commit()
except Exception:
return self.groupedRollback()
# TO BE DONE
@@ -131,10 +113,8 @@ class ProvisionLDB(object):
# self.hku.transaction_prepare_commit()
# self.hklm.transaction_prepare_commit()
try:
- self.sam.transaction_commit()
- self.secrets.transaction_commit()
- self.idmap.transaction_commit()
- self.privilege.transaction_commit()
+ for db in self.dbs():
+ db.transaction_commit()
except Exception:
return self.groupedRollback()
@@ -145,6 +125,7 @@ class ProvisionLDB(object):
# self.hklm.transaction_commit()
return True
+
def get_ldbs(paths, creds, session, lp):
"""Return LDB object mapped on most important databases
@@ -205,6 +186,8 @@ def get_paths(param, targetdir=None, smbconf=None):
:param smbconf: Path to the smb.conf file
:return: A list with the path of important provision objects"""
if targetdir is not None:
+ if not os.path.exists(targetdir):
+ os.mkdir(targetdir)
etcdir = os.path.join(targetdir, "etc")
if not os.path.exists(etcdir):
os.makedirs(etcdir)
@@ -242,112 +225,6 @@ def update_policyids(names, samdb):
names.policyid_dc = None
-def find_provision_key_parameters(samdb, secretsdb, idmapdb, paths, smbconf, lp):
- """Get key provision parameters (realm, domain, ...) from a given provision
-
- :param samdb: An LDB object connected to the sam.ldb file
- :param secretsdb: An LDB object connected to the secrets.ldb file
- :param idmapdb: An LDB object connected to the idmap.ldb file
- :param paths: A list of path to provision object
- :param smbconf: Path to the smb.conf file
- :param lp: A LoadParm object
- :return: A list of key provision parameters
- """
- names = ProvisionNames()
- names.adminpass = None
-
- # NT domain, kerberos realm, root dn, domain dn, domain dns name
- names.domain = string.upper(lp.get("workgroup"))
- names.realm = lp.get("realm")
- basedn = "DC=" + names.realm.replace(".",",DC=")
- names.dnsdomain = names.realm.lower()
- names.realm = string.upper(names.realm)
- # netbiosname
- # Get the netbiosname first (could be obtained from smb.conf in theory)
- res = secretsdb.search(expression="(flatname=%s)" %
- names.domain,base="CN=Primary Domains",
- scope=SCOPE_SUBTREE, attrs=["sAMAccountName"])
- names.netbiosname = str(res[0]["sAMAccountName"]).replace("$","")
-
- names.smbconf = smbconf
-
- # That's a bit simplistic but it's ok as long as we have only 3
- # partitions
- current = samdb.search(expression="(objectClass=*)",
- base="", scope=SCOPE_BASE,
- attrs=["defaultNamingContext", "schemaNamingContext",
- "configurationNamingContext","rootDomainNamingContext"])
-
- names.configdn = current[0]["configurationNamingContext"]
- configdn = str(names.configdn)
- names.schemadn = current[0]["schemaNamingContext"]
- if not (ldb.Dn(samdb, basedn) == (ldb.Dn(samdb,
- current[0]["defaultNamingContext"][0]))):
- raise ProvisioningError(("basedn in %s (%s) and from %s (%s)"
- "is not the same ..." % (paths.samdb,
- str(current[0]["defaultNamingContext"][0]),
- paths.smbconf, basedn)))
-
- names.domaindn=current[0]["defaultNamingContext"]
- names.rootdn=current[0]["rootDomainNamingContext"]
- # default site name
- res3 = samdb.search(expression="(objectClass=*)",
- base="CN=Sites," + configdn, scope=SCOPE_ONELEVEL, attrs=["cn"])
- names.sitename = str(res3[0]["cn"])
-
- # dns hostname and server dn
- res4 = samdb.search(expression="(CN=%s)" % names.netbiosname,
- base="OU=Domain Controllers,%s" % basedn,
- scope=SCOPE_ONELEVEL, attrs=["dNSHostName"])
- names.hostname = str(res4[0]["dNSHostName"]).replace("." + names.dnsdomain,"")
-
- server_res = samdb.search(expression="serverReference=%s" % res4[0].dn,
- attrs=[], base=configdn)
- names.serverdn = server_res[0].dn
-
- # invocation id/objectguid
- res5 = samdb.search(expression="(objectClass=*)",
- base="CN=NTDS Settings,%s" % str(names.serverdn), scope=SCOPE_BASE,
- attrs=["invocationID", "objectGUID"])
- names.invocation = str(ndr_unpack(misc.GUID, res5[0]["invocationId"][0]))
- names.ntdsguid = str(ndr_unpack(misc.GUID, res5[0]["objectGUID"][0]))
-
- # domain guid/sid
- res6 = samdb.search(expression="(objectClass=*)", base=basedn,
- scope=SCOPE_BASE, attrs=["objectGUID",
- "objectSid","msDS-Behavior-Version" ])
- names.domainguid = str(ndr_unpack(misc.GUID, res6[0]["objectGUID"][0]))
- names.domainsid = ndr_unpack( security.dom_sid, res6[0]["objectSid"][0])
- if res6[0].get("msDS-Behavior-Version") is None or \
- int(res6[0]["msDS-Behavior-Version"][0]) < DS_DOMAIN_FUNCTION_2000:
- names.domainlevel = DS_DOMAIN_FUNCTION_2000
- else:
- names.domainlevel = int(res6[0]["msDS-Behavior-Version"][0])
-
- # policy guid
- res7 = samdb.search(expression="(displayName=Default Domain Policy)",
- base="CN=Policies,CN=System," + basedn,
- scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
- names.policyid = str(res7[0]["cn"]).replace("{","").replace("}","")
- # dc policy guid
- res8 = samdb.search(expression="(displayName=Default Domain Controllers"
- " Policy)",
- base="CN=Policies,CN=System," + basedn,
- scope=SCOPE_ONELEVEL, attrs=["cn","displayName"])
- if len(res8) == 1:
- names.policyid_dc = str(res8[0]["cn"]).replace("{","").replace("}","")
- else:
- names.policyid_dc = None
- res9 = idmapdb.search(expression="(cn=%s)" %
- (security.SID_BUILTIN_ADMINISTRATORS),
- attrs=["xidNumber"])
- if len(res9) == 1:
- names.wheel_gid = res9[0]["xidNumber"]
- else:
- raise ProvisioningError("Unable to find uid/gid for Domain Admins rid")
- return names
-
-
def newprovision(names, creds, session, smbconf, provdir, logger):
"""Create a new provision.
@@ -365,7 +242,8 @@ def newprovision(names, creds, session, smbconf, provdir, logger):
shutil.rmtree(provdir)
os.mkdir(provdir)
logger.info("Provision stored in %s", provdir)
- provision(logger, session, creds, smbconf=smbconf,
+ dns_backend="BIND9_DLZ"
+ return provision(logger, session, creds, smbconf=smbconf,
targetdir=provdir, samdb_fill=FILL_FULL, realm=names.realm,
domain=names.domain, domainguid=names.domainguid,
domainsid=str(names.domainsid), ntdsguid=names.ntdsguid,
@@ -373,12 +251,12 @@ def newprovision(names, creds, session, smbconf, provdir, logger):
hostname=names.netbiosname.lower(), hostip=None, hostip6=None,
invocationid=names.invocation, adminpass=names.adminpass,
krbtgtpass=None, machinepass=None, dnspass=None, root=None,
- nobody=None, wheel=None, users=None,
- serverrole="domain controller", ldap_backend_extra_port=None,
+ nobody=None, users=None,
+ serverrole="domain controller",
backend_type=None, ldapadminpass=None, ol_mmr_urls=None,
- slapd_path=None, setup_ds_path=None, nosync=None,
- dom_for_fun_level=names.domainlevel,
- ldap_dryrun_mode=None, useeadb=True)
+ slapd_path=None,
+ dom_for_fun_level=names.domainlevel, dns_backend=dns_backend,
+ useeadb=True, use_ntvfs=True)
def dn_sort(x, y):
@@ -469,7 +347,7 @@ def chunck_sddl(sddl):
return hash
-def get_diff_sddls(refsddl, cursddl):
+def get_diff_sddls(refsddl, cursddl, checkSacl = True):
"""Get the difference between 2 sddl
This function split the textual representation of ACL into smaller
@@ -477,46 +355,54 @@ def get_diff_sddls(refsddl, cursddl):
:param refsddl: First sddl to compare
:param cursddl: Second sddl to compare
+ :param checkSacl: If false we skip the sacl checks
:return: A string that explain difference between sddls
"""
txt = ""
- hash_new = chunck_sddl(cursddl)
+ hash_cur = chunck_sddl(cursddl)
hash_ref = chunck_sddl(refsddl)
- if hash_new["owner"] != hash_ref["owner"]:
+ if not hash_cur.has_key("owner"):
+ txt = "\tNo owner in current SD"
+ elif hash_cur["owner"] != hash_ref["owner"]:
txt = "\tOwner mismatch: %s (in ref) %s" \
- "(in current)\n" % (hash_ref["owner"], hash_new["owner"])
+ "(in current)\n" % (hash_ref["owner"], hash_cur["owner"])
- if hash_new["group"] != hash_ref["group"]:
+ if not hash_cur.has_key("group"):
+ txt = "%s\tNo group in current SD" % txt
+ elif hash_cur["group"] != hash_ref["group"]:
txt = "%s\tGroup mismatch: %s (in ref) %s" \
- "(in current)\n" % (txt, hash_ref["group"], hash_new["group"])
+ "(in current)\n" % (txt, hash_ref["group"], hash_cur["group"])
- for part in ["dacl", "sacl"]:
- if hash_new.has_key(part) and hash_ref.has_key(part):
+ parts = [ "dacl" ]
+ if checkSacl:
+ parts.append("sacl")
+ for part in parts:
+ if hash_cur.has_key(part) and hash_ref.has_key(part):
# both are present, check if they contain the same ACE
- h_new = set()
+ h_cur = set()
h_ref = set()
- c_new = chunck_acl(hash_new[part])
+ c_cur = chunck_acl(hash_cur[part])
c_ref = chunck_acl(hash_ref[part])
- for elem in c_new["aces"]:
- h_new.add(elem)
+ for elem in c_cur["aces"]:
+ h_cur.add(elem)
for elem in c_ref["aces"]:
h_ref.add(elem)
for k in set(h_ref):
- if k in h_new:
- h_new.remove(k)
+ if k in h_cur:
+ h_cur.remove(k)
h_ref.remove(k)
- if len(h_new) + len(h_ref) > 0:
+ if len(h_cur) + len(h_ref) > 0:
txt = "%s\tPart %s is different between reference" \
" and current here is the detail:\n" % (txt, part)
- for item in h_new:
+ for item in h_cur:
txt = "%s\t\t%s ACE is not present in the" \
" reference\n" % (txt, item)
@@ -524,9 +410,9 @@ def get_diff_sddls(refsddl, cursddl):
txt = "%s\t\t%s ACE is not present in the" \
" current\n" % (txt, item)
- elif hash_new.has_key(part) and not hash_ref.has_key(part):
+ elif hash_cur.has_key(part) and not hash_ref.has_key(part):
txt = "%s\tReference ACL hasn't a %s part\n" % (txt, part)
- elif not hash_new.has_key(part) and hash_ref.has_key(part):
+ elif not hash_cur.has_key(part) and hash_ref.has_key(part):
txt = "%s\tCurrent ACL hasn't a %s part\n" % (txt, part)
return txt
@@ -541,11 +427,9 @@ def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
of the updated provision
"""
- messagefunc(SIMPLE, "update secrets.ldb")
- reference = newsecrets_ldb.search(expression="dn=@MODULES", base="",
- scope=SCOPE_SUBTREE)
- current = secrets_ldb.search(expression="dn=@MODULES", base="",
- scope=SCOPE_SUBTREE)
+ messagefunc(SIMPLE, "Update of secrets.ldb")
+ reference = newsecrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
+ current = secrets_ldb.search(base="@MODULES", scope=SCOPE_BASE)
assert reference, "Reference modules list can not be empty"
if len(current) == 0:
# No modules present
@@ -582,9 +466,9 @@ def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
listPresent.append(hash_new[k])
for entry in listMissing:
- reference = newsecrets_ldb.search(expression="dn=%s" % entry,
+ reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
base="", scope=SCOPE_SUBTREE)
- current = secrets_ldb.search(expression="dn=%s" % entry,
+ current = secrets_ldb.search(expression="distinguishedName=%s" % entry,
base="", scope=SCOPE_SUBTREE)
delta = secrets_ldb.msg_diff(empty, reference[0])
for att in hashAttrNotCopied:
@@ -597,9 +481,9 @@ def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
secrets_ldb.add(delta)
for entry in listPresent:
- reference = newsecrets_ldb.search(expression="dn=%s" % entry,
+ reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry,
base="", scope=SCOPE_SUBTREE)
- current = secrets_ldb.search(expression="dn=%s" % entry, base="",
+ current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
scope=SCOPE_SUBTREE)
delta = secrets_ldb.msg_diff(current[0], reference[0])
for att in hashAttrNotCopied:
@@ -613,9 +497,9 @@ def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
delta.remove(att)
for entry in listPresent:
- reference = newsecrets_ldb.search(expression="dn=%s" % entry, base="",
+ reference = newsecrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
scope=SCOPE_SUBTREE)
- current = secrets_ldb.search(expression="dn=%s" % entry, base="",
+ current = secrets_ldb.search(expression="distinguishedName=%s" % entry, base="",
scope=SCOPE_SUBTREE)
delta = secrets_ldb.msg_diff(current[0], reference[0])
for att in hashAttrNotCopied:
@@ -634,7 +518,7 @@ def update_secrets(newsecrets_ldb, secrets_ldb, messagefunc):
res2 = secrets_ldb.search(expression="(samaccountname=dns)",
scope=SCOPE_SUBTREE, attrs=["dn"])
- if (len(res2) == 1):
+ if len(res2) == 1:
messagefunc(SIMPLE, "Remove old dns account")
secrets_ldb.delete(res2[0]["dn"])
@@ -649,7 +533,7 @@ def getOEMInfo(samdb, rootdn):
"""
res = samdb.search(expression="(objectClass=*)", base=str(rootdn),
scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
- if len(res) > 0:
+ if len(res) > 0 and res[0].get("oEMInformation"):
info = res[0]["oEMInformation"]
return info
else:
@@ -666,7 +550,10 @@ def updateOEMInfo(samdb, rootdn):
res = samdb.search(expression="(objectClass=*)", base=rootdn,
scope=SCOPE_BASE, attrs=["dn", "oEMInformation"])
if len(res) > 0:
- info = res[0]["oEMInformation"]
+ if res[0].get("oEMInformation"):
+ info = str(res[0]["oEMInformation"])
+ else:
+ info = ""
info = "%s, upgrade to %s" % (info, version)
delta = ldb.Message()
delta.dn = ldb.Dn(samdb, str(res[0]["dn"]))
@@ -709,22 +596,30 @@ def update_gpo(paths, samdb, names, lp, message, force=0):
dir = getpolicypath(paths.sysvol, names.dnsdomain, names.policyid_dc)
if not os.path.isdir(dir):
create_gpo_struct(dir)
+
+ def acl_error(e):
+ if os.geteuid() == 0:
+ message(ERROR, "Unable to set ACLs on policies related objects: %s" % e)
+ else:
+ message(ERROR, "Unable to set ACLs on policies related objects. "
+ "ACLs must be set as root if file system ACLs "
+ "(rather than posix:eadb) are used.")
+
# We always reinforce acls on GPO folder because they have to be in sync
# with the one in DS
try:
set_gpos_acl(paths.sysvol, names.dnsdomain, names.domainsid,
names.domaindn, samdb, lp)
except TypeError, e:
- message(ERROR, "Unable to set ACLs on policies related objects,"
- " if not using posix:eadb, you must be root to do it")
+ acl_error(e)
if resetacls:
try:
- setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.wheel_gid,
+ setsysvolacl(samdb, paths.netlogon, paths.sysvol, names.root_gid,
names.domainsid, names.dnsdomain, names.domaindn, lp)
except TypeError, e:
- message(ERROR, "Unable to set ACLs on sysvol share, if not using"
- "posix:eadb, you must be root to do it")
+ acl_error(e)
+
def increment_calculated_keyversion_number(samdb, rootdn, hashDns):
"""For a given hash associating dn and a number, this function will
@@ -783,7 +678,7 @@ def delta_update_basesamdb(refsampath, sampath, creds, session, lp, message):
reference = refsam.search(expression="")
for refentry in reference:
- entry = sam.search(expression="dn=%s" % refentry["dn"],
+ entry = sam.search(expression="distinguishedName=%s" % refentry["dn"],
scope=SCOPE_SUBTREE)
if not len(entry):
delta = sam.msg_diff(empty, refentry)
@@ -903,9 +798,6 @@ def update_dns_account_password(samdb, secrets_ldb, names):
"msDS-KeyVersionNumber")
secrets_ldb.modify(msg)
- else:
- raise ProvisioningError("Unable to find an object"
- " with %s" % expression )
def search_constructed_attrs_stored(samdb, rootdn, attrs):
"""Search a given sam DB for calculated attributes that are
@@ -941,6 +833,122 @@ def search_constructed_attrs_stored(samdb, rootdn, attrs):
return hashAtt
+def findprovisionrange(samdb, basedn):
+ """ Find ranges of usn grouped by invocation id and then by timestamp
+ rouned at 1 minute
+
+ :param samdb: An LDB object pointing to the samdb
+ :param basedn: The DN of the forest
+
+ :return: A two level dictionary with invoication id as the
+ first level, timestamp as the second one and then
+ max, min, and number as subkeys, representing respectivily
+ the maximum usn for the range, the minimum usn and the number
+ of object with usn in this range.
+ """
+ nb_obj = 0
+ hash_id = {}
+
+ res = samdb.search(base=basedn, expression="objectClass=*",
+ scope=ldb.SCOPE_SUBTREE,
+ attrs=["replPropertyMetaData"],
+ controls=["search_options:1:2"])
+
+ for e in res:
+ nb_obj = nb_obj + 1
+ obj = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+ str(e["replPropertyMetaData"])).ctr
+
+ for o in obj.array:
+ # like a timestamp but with the resolution of 1 minute
+ minutestamp =_glue.nttime2unix(o.originating_change_time)/60
+ hash_ts = hash_id.get(str(o.originating_invocation_id))
+
+ if hash_ts is None:
+ ob = {}
+ ob["min"] = o.originating_usn
+ ob["max"] = o.originating_usn
+ ob["num"] = 1
+ ob["list"] = [str(e.dn)]
+ hash_ts = {}
+ else:
+ ob = hash_ts.get(minutestamp)
+ if ob is None:
+ ob = {}
+ ob["min"] = o.originating_usn
+ ob["max"] = o.originating_usn
+ ob["num"] = 1
+ ob["list"] = [str(e.dn)]
+ else:
+ if ob["min"] > o.originating_usn:
+ ob["min"] = o.originating_usn
+ if ob["max"] < o.originating_usn:
+ ob["max"] = o.originating_usn
+ if not (str(e.dn) in ob["list"]):
+ ob["num"] = ob["num"] + 1
+ ob["list"].append(str(e.dn))
+ hash_ts[minutestamp] = ob
+ hash_id[str(o.originating_invocation_id)] = hash_ts
+
+ return (hash_id, nb_obj)
+
+def print_provision_ranges(dic, limit_print, dest, samdb_path, invocationid):
+ """ print the differents ranges passed as parameter
+
+ :param dic: A dictionnary as returned by findprovisionrange
+ :param limit_print: minimum number of object in a range in order to print it
+ :param dest: Destination directory
+ :param samdb_path: Path to the sam.ldb file
+ :param invoicationid: Invocation ID for the current provision
+ """
+ ldif = ""
+
+ for id in dic:
+ hash_ts = dic[id]
+ sorted_keys = []
+ sorted_keys.extend(hash_ts.keys())
+ sorted_keys.sort()
+
+ kept_record = []
+ for k in sorted_keys:
+ obj = hash_ts[k]
+ if obj["num"] > limit_print:
+ dt = _glue.nttime2string(_glue.unix2nttime(k*60))
+ print "%s # of modification: %d \tmin: %d max: %d" % (dt , obj["num"],
+ obj["min"],
+ obj["max"])
+ if hash_ts[k]["num"] > 600:
+ kept_record.append(k)
+
+ # Let's try to concatenate consecutive block if they are in the almost same minutestamp
+ for i in range(0, len(kept_record)):
+ if i != 0:
+ key1 = kept_record[i]
+ key2 = kept_record[i-1]
+ if key1 - key2 == 1:
+ # previous record is just 1 minute away from current
+ if int(hash_ts[key1]["min"]) == int(hash_ts[key2]["max"]) + 1:
+ # Copy the highest USN in the previous record
+ # and mark the current as skipped
+ hash_ts[key2]["max"] = hash_ts[key1]["max"]
+ hash_ts[key1]["skipped"] = True
+
+ for k in kept_record:
+ obj = hash_ts[k]
+ if obj.get("skipped") is None:
+ ldif = "%slastProvisionUSN: %d-%d;%s\n" % (ldif, obj["min"],
+ obj["max"], id)
+
+ if ldif != "":
+ file = tempfile.mktemp(dir=dest, prefix="usnprov", suffix=".ldif")
+ print
+ print "To track the USNs modified/created by provision and upgrade proivsion,"
+ print " the following ranges are proposed to be added to your provision sam.ldb: \n%s" % ldif
+ print "We recommend to review them, and if it's correct to integrate the following ldif: %s in your sam.ldb" % file
+ print "You can load this file like this: ldbadd -H %s %s\n"%(str(samdb_path),file)
+ ldif = "dn: @PROVISION\nprovisionnerID: %s\n%s" % (invocationid, ldif)
+ open(file,'w').write(ldif)
+
def int64range2str(value):
"""Display the int64 range stored in value as xxx-yyy
diff --git a/source4/scripting/python/samba/web_server/__init__.py b/source4/scripting/python/samba/web_server/__init__.py
index da528f42d1..c43924097d 100644
--- a/source4/scripting/python/samba/web_server/__init__.py
+++ b/source4/scripting/python/samba/web_server/__init__.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
+#
# Unix SMB/CIFS implementation.
# Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
#
@@ -24,7 +23,7 @@
def render_placeholder(environ, start_response):
status = '200 OK'
- response_headers = [('Content-type','text/html')]
+ response_headers = [('Content-type', 'text/html')]
start_response(status, response_headers)
yield "<!doctype html>\n"
diff --git a/source4/scripting/python/samba/xattr.py b/source4/scripting/python/samba/xattr.py
new file mode 100644
index 0000000000..8516ba99ca
--- /dev/null
+++ b/source4/scripting/python/samba/xattr.py
@@ -0,0 +1,61 @@
+# Utility code for dealing with POSIX extended attributes
+#
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 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/>.
+
+from samba.dcerpc import xattr
+import os
+import samba.xattr_native
+import shutil
+
+
+def copyattrs(frompath, topath):
+ """Copy ACL related attributes from a path to another path."""
+ for attr_name in (xattr.XATTR_NTACL_NAME, "system.posix_acl_access"):
+ # Get the xattr attributes if any
+ try:
+ attribute = samba.xattr_native.wrap_getxattr(frompath,
+ xattr.XATTR_NTACL_NAME)
+ samba.xattr_native.wrap_setxattr(topath,
+ xattr.XATTR_NTACL_NAME,
+ attribute)
+ except Exception:
+ pass
+ # FIXME:Catch a specific exception
+
+
+def copytree_with_xattrs(src, dst):
+ """Recursively copy a directory tree using shutil.copy2(), preserving xattrs.
+
+ The destination directory must not already exist.
+ If exception(s) occur, an Error is raised with a list of reasons.
+ """
+ names = os.listdir(src)
+
+ os.makedirs(dst)
+ errors = []
+ for name in names:
+ srcname = os.path.join(src, name)
+ dstname = os.path.join(dst, name)
+ if os.path.islink(srcname):
+ linkto = os.readlink(srcname)
+ os.symlink(linkto, dstname)
+ elif os.path.isdir(srcname):
+ copytree_with_xattrs(srcname, dstname)
+ else:
+ # Will raise a SpecialFileError for unsupported file types
+ shutil.copy2(srcname, dstname)
+ shutil.copystat(src, dst)
+ copyattrs(src, dst)
diff --git a/source4/scripting/python/wscript_build b/source4/scripting/python/wscript_build
index 540f3b7bb7..9c23a96f8c 100644
--- a/source4/scripting/python/wscript_build
+++ b/source4/scripting/python/wscript_build
@@ -5,12 +5,12 @@ bld.SAMBA_LIBRARY('samba_python',
deps='LIBPYTHON pytalloc-util pyrpc_util',
grouping_library=True,
private_library=True,
- pyext=True)
+ pyembed=True)
bld.SAMBA_SUBSYSTEM('LIBPYTHON',
source='modules.c',
public_deps='',
- init_function_sentinal='{NULL,NULL}',
+ init_function_sentinel='{NULL,NULL}',
deps='talloc',
pyext=True,
)